diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 16:51:28 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 16:51:28 +0000 |
commit | 940b4d1848e8c70ab7642901a68594e8016caffc (patch) | |
tree | eb72f344ee6c3d9b80a7ecc079ea79e9fba8676d /onlineupdate | |
parent | Initial commit. (diff) | |
download | libreoffice-upstream.tar.xz libreoffice-upstream.zip |
Adding upstream version 1:7.0.4.upstream/1%7.0.4upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'onlineupdate')
108 files changed, 18103 insertions, 0 deletions
diff --git a/onlineupdate/CustomTarget_generated.mk b/onlineupdate/CustomTarget_generated.mk new file mode 100644 index 000000000..266b2db98 --- /dev/null +++ b/onlineupdate/CustomTarget_generated.mk @@ -0,0 +1,32 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# + +$(eval $(call gb_CustomTarget_CustomTarget,onlineupdate/generated)) + +onlineupdate_INC := $(call gb_CustomTarget_get_workdir,onlineupdate/generated)/onlineupdate + +$(onlineupdate_INC)/primaryCert.h : \ + $(SRCDIR)/onlineupdate/source/update/updater/gen_cert_header.py \ + $(call gb_ExternalExecutable_get_dependencies,python) + $(call gb_Output_announce,$(subst $(WORKDIR)/,,$@),build,,1) + mkdir -p $(dir $@) + $(call gb_ExternalExecutable_get_command,python) $(SRCDIR)/onlineupdate/source/update/updater/gen_cert_header.py "primaryCertData" $(UPDATE_CONFIG) > $(onlineupdate_INC)/primaryCert.h #"$(UPDATE_CONFIG)" + +$(onlineupdate_INC)/secondaryCert.h : \ + $(SRCDIR)/onlineupdate/source/update/updater/gen_cert_header.py \ + $(call gb_ExternalExecutable_get_dependencies,python) + $(call gb_Output_announce,$(subst $(WORKDIR)/,,$@),build,,1) + mkdir -p $(dir $@) + $(call gb_ExternalExecutable_get_command,python) $(SRCDIR)/onlineupdate/source/update/updater/gen_cert_header.py "secondaryCertData" $(UPDATE_CONFIG) > $(onlineupdate_INC)/secondaryCert.h #"$(UPDATE_CONFIG)" + +$(call gb_CustomTarget_get_target,onlineupdate/generated) : \ + $(onlineupdate_INC)/primaryCert.h \ + $(onlineupdate_INC)/secondaryCert.h \ + +# vim: set noet sw=4 ts=4: diff --git a/onlineupdate/Executable_mar.mk b/onlineupdate/Executable_mar.mk new file mode 100644 index 000000000..5352f4ac2 --- /dev/null +++ b/onlineupdate/Executable_mar.mk @@ -0,0 +1,49 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# + +$(eval $(call gb_Executable_Executable,mar)) + +$(eval $(call gb_Executable_set_include,mar,\ + -I$(SRCDIR)/onlineupdate/source/libmar/src/ \ + -I$(SRCDIR)/onlineupdate/source/libmar/verify/ \ + -I$(SRCDIR)/onlineupdate/source/libmar/sign/ \ + $$(INCLUDE) \ +)) + +$(eval $(call gb_Executable_use_static_libraries,mar,\ + libmar \ + libmarverify \ +)) + +ifeq ($(OS),WNT) +$(eval $(call gb_Executable_add_libs,mar,\ + ws2_32.lib \ + Crypt32.lib \ +)) +endif + +$(eval $(call gb_Executable_use_externals,mar,\ + nss3 \ +)) + +$(eval $(call gb_Executable_add_defs,mar,\ + -DMAR_NSS \ +)) + +$(eval $(call gb_Executable_add_defs,mar,\ + -DAPP_VERSION=\"$(LIBO_VERSION_MAJOR).$(LIBO_VERSION_MINOR).$(LIBO_VERSION_MICRO).$(LIBO_VERSION_PATCH)\" \ +)) + +$(eval $(call gb_Executable_add_cobjects,mar,\ + onlineupdate/source/libmar/sign/nss_secutil \ + onlineupdate/source/libmar/sign/mar_sign \ + onlineupdate/source/libmar/tool/mar \ +)) + +# vim:set shiftwidth=4 tabstop=4 noexpandtab: */ diff --git a/onlineupdate/Executable_mbsdiff.mk b/onlineupdate/Executable_mbsdiff.mk new file mode 100644 index 000000000..9c528a58b --- /dev/null +++ b/onlineupdate/Executable_mbsdiff.mk @@ -0,0 +1,32 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# + +$(eval $(call gb_Executable_Executable,mbsdiff)) + +$(eval $(call gb_Executable_set_include,mbsdiff,\ + -I$(SRCDIR)/onlineupdate/inc \ + $$(INCLUDE) \ +)) + +$(eval $(call gb_Executable_use_externals,mbsdiff,\ + bzip2 \ +)) + + +ifeq ($(OS),WNT) +$(eval $(call gb_Executable_add_libs,mbsdiff,\ + ws2_32.lib \ +)) +endif + +$(eval $(call gb_Executable_add_cxxobjects,mbsdiff,\ + onlineupdate/source/mbsdiff/bsdiff \ +)) + +# vim:set shiftwidth=4 tabstop=4 noexpandtab: */ diff --git a/onlineupdate/Executable_test_updater_dialog.mk b/onlineupdate/Executable_test_updater_dialog.mk new file mode 100644 index 000000000..a2988d007 --- /dev/null +++ b/onlineupdate/Executable_test_updater_dialog.mk @@ -0,0 +1,63 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# + +$(eval $(call gb_Executable_Executable,test_updater_dialog)) + +$(eval $(call gb_Executable_set_include,test_updater_dialog,\ + -I$(SRCDIR)/onlineupdate/inc \ + -I$(SRCDIR)/onlineupdate/source/update/common \ + -I$(SRCDIR)/onlineupdate/source/update/updater/xpcom/glue \ + -I$(SRCDIR)/onlineupdate/source/update/updater \ + $$(INCLUDE) \ + $(if $(filter-out WNT,$(OS)),$$(GTK3_CFLAGS) ) \ +)) + +$(eval $(call gb_Executable_use_static_libraries,test_updater_dialog,\ + updatehelper \ +)) + +ifeq ($(OS),WNT) +$(eval $(call gb_Executable_add_libs,test_updater_dialog,\ + Ws2_32.lib \ + Gdi32.lib \ + Comctl32.lib \ + Shell32.lib \ + Shlwapi.lib \ + Crypt32.lib \ +)) + +$(eval $(call gb_Executable_set_targettype_gui,test_updater_dialog,YES)) + +$(eval $(call gb_Executable_add_nativeres,test_updater_dialog,updaterres)) + +$(eval $(call gb_Executable_add_ldflags,test_updater_dialog,\ + /ENTRY:wmainCRTStartup \ +)) + +else + +$(eval $(call gb_Executable_add_defs,test_updater_dialog,\ + -DVERIFY_MAR_SIGNATURE \ + -DNSS3 \ +)) + +$(eval $(call gb_Executable_use_externals,test_updater_dialog,\ + nss3 \ +)) + +$(eval $(call gb_Executable_add_libs,test_updater_dialog,\ + $(GTK3_LIBS) \ +)) +endif + +$(eval $(call gb_Executable_add_exception_objects,test_updater_dialog,\ + onlineupdate/workben/test_dialog \ +)) + +# vim:set shiftwidth=4 tabstop=4 noexpandtab: */ diff --git a/onlineupdate/Executable_update_service.mk b/onlineupdate/Executable_update_service.mk new file mode 100644 index 000000000..edd02f6ec --- /dev/null +++ b/onlineupdate/Executable_update_service.mk @@ -0,0 +1,58 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# + +$(eval $(call gb_Executable_Executable,update_service)) + +$(eval $(call gb_Executable_set_targettype_gui,update_service,YES)) + +$(eval $(call gb_Executable_set_include,update_service,\ + -I$(SRCDIR)/onlineupdate/inc \ + -I$(SRCDIR)/onlineupdate/source/libmar/src/ \ + -I$(SRCDIR)/onlineupdate/source/libmar/verify/ \ + -I$(SRCDIR)/onlineupdate/source/libmar/sign/ \ + -I$(SRCDIR)/onlineupdate/source/update/common/ \ + $$(INCLUDE) \ +)) + +$(eval $(call gb_Executable_use_static_libraries,update_service,\ + updatehelper \ + windows_process \ +)) + +$(eval $(call gb_Executable_add_libs,update_service,\ + ws2_32.lib \ + Crypt32.lib \ + shell32.lib \ + wintrust.lib \ + version.lib \ + wtsapi32.lib \ + userenv.lib \ + shlwapi.lib \ + ole32.lib \ + rpcrt4.lib \ + comctl32.lib \ + shlwapi.lib \ + kernel32.lib \ + advapi32.lib \ +)) + +$(eval $(call gb_Executable_add_ldflags,update_service,\ + /ENTRY:wmainCRTStartup \ +)) + +$(eval $(call gb_Executable_add_exception_objects,update_service,\ + onlineupdate/source/service/certificatecheck \ + onlineupdate/source/service/maintenanceservice \ + onlineupdate/source/service/registrycertificates \ + onlineupdate/source/service/servicebase \ + onlineupdate/source/service/serviceinstall \ + onlineupdate/source/service/workmonitor \ +)) + +# vim:set shiftwidth=4 tabstop=4 noexpandtab: */ diff --git a/onlineupdate/Executable_updater.mk b/onlineupdate/Executable_updater.mk new file mode 100644 index 000000000..ab15a94e4 --- /dev/null +++ b/onlineupdate/Executable_updater.mk @@ -0,0 +1,85 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# + +$(eval $(call gb_Executable_Executable,updater)) + +$(eval $(call gb_Executable_set_include,updater,\ + -I$(SRCDIR)/onlineupdate/inc \ + -I$(SRCDIR)/onlineupdate/source/update/common \ + -I$(SRCDIR)/onlineupdate/source/update/updater/xpcom/glue \ + $$(INCLUDE) \ + $(if $(filter-out WNT,$(OS)),$$(GTK3_CFLAGS) ) \ +)) + +$(eval $(call gb_Executable_use_custom_headers,updater,onlineupdate/generated)) + +$(eval $(call gb_Executable_use_static_libraries,updater,\ + libmar \ + libmarverify \ + updatehelper \ + $(if $(filter WNT,$(OS)), \ + windows_process )\ +)) + +$(eval $(call gb_Executable_use_externals,updater,\ + bzip2 \ +)) + +ifeq ($(OS),WNT) +$(eval $(call gb_Executable_add_libs,updater,\ + Ws2_32.lib \ + Gdi32.lib \ + Comctl32.lib \ + Shell32.lib \ + Shlwapi.lib \ + Crypt32.lib \ +)) + +$(eval $(call gb_Executable_set_targettype_gui,updater,YES)) + +$(eval $(call gb_Executable_add_nativeres,updater,updaterres)) + +$(eval $(call gb_Executable_add_ldflags,updater,\ + /ENTRY:wmainCRTStartup \ +)) + +$(eval $(call gb_Executable_add_defs,updater,\ + -DVERIFY_MAR_SIGNATURE \ +)) + +else + +$(eval $(call gb_Executable_add_defs,updater,\ + -DVERIFY_MAR_SIGNATURE \ + -DNSS3 \ +)) + +$(eval $(call gb_Executable_use_externals,updater,\ + nss3 \ +)) + +$(eval $(call gb_Executable_add_libs,updater,\ + $(GTK3_LIBS) \ +)) + +endif + +$(eval $(call gb_Executable_add_exception_objects,updater,\ + onlineupdate/source/update/updater/xpcom/glue/nsVersionComparator \ + onlineupdate/source/update/updater/archivereader \ + onlineupdate/source/update/updater/bspatch \ + onlineupdate/source/update/updater/progressui_gtk \ + onlineupdate/source/update/updater/updater \ + $(if $(filter WNT,$(OS)),\ + onlineupdate/source/update/updater/loaddlls \ + onlineupdate/source/update/updater/progressui_win \ + onlineupdate/source/update/updater/win_dirent )\ +)) + +# vim:set shiftwidth=4 tabstop=4 noexpandtab: */ diff --git a/onlineupdate/Makefile b/onlineupdate/Makefile new file mode 100644 index 000000000..0997e6284 --- /dev/null +++ b/onlineupdate/Makefile @@ -0,0 +1,14 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# + +module_directory:=$(dir $(realpath $(firstword $(MAKEFILE_LIST)))) + +include $(module_directory)/../solenv/gbuild/partial_build.mk + +# vim: set noet sw=4 ts=4: diff --git a/onlineupdate/Module_onlineupdate.mk b/onlineupdate/Module_onlineupdate.mk new file mode 100644 index 000000000..58e1d3de4 --- /dev/null +++ b/onlineupdate/Module_onlineupdate.mk @@ -0,0 +1,29 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# + +$(eval $(call gb_Module_Module,onlineupdate)) + +ifneq ($(ENABLE_ONLINE_UPDATE_MAR),) +$(eval $(call gb_Module_add_targets,onlineupdate,\ + StaticLibrary_libmar \ + StaticLibrary_libmarverify \ + StaticLibrary_updatehelper \ + $(if $(filter WNT,$(OS)),\ + Executable_update_service \ + WinResTarget_updater )\ + Executable_test_updater_dialog \ + Executable_mar \ + Executable_updater \ + Executable_mbsdiff \ + CustomTarget_generated \ +)) +endif + +# vim: set noet sw=4 ts=4: diff --git a/onlineupdate/README b/onlineupdate/README new file mode 100644 index 000000000..485a71849 --- /dev/null +++ b/onlineupdate/README @@ -0,0 +1,25 @@ +Online update implementation based on Mozilla's MAR format + update mechanism + +Parts of this code are copied from the mozilla repository, and adapted to +LibreOffice needs: + +firefox/modules/libmar -> onlineupdate/source/libmar +firefox/toolkit/mozapps/update -> onlineupdate/source/update + +The source/service directory contains the code for the silent windows updater that avoids the repeated administrator check for an update. + +== NOTE == +The updater executable should not depend on any other dynamic library in the LibreOffice installation as we would need to copy that one also to a temporary directory during update. We can't update any library or executable that is currently in use. For the updater executable we solve this problem by copying the updater before using it to a temporary directory. + +On Windows we use the system to provide us with a crypto library whereas on Linux we use NSS. + +== Update procedure == + +The updater executable is run two times. In a first run, the current installation is copied to a "update" directory and the update is applied in this "update" directory. During the next run, a replacement request is executed. The replacement request removes the old installation directory and replaces it with the content of the "update" directory. + +=== User profile in the installation directory === + +The archive based installations have the user profile by default inside of the installation directory. During the update process this causes some problems that need special handling in the updater. + +* The "update" directory is inside of the user profile resulting in recursive copying. +* During the replacement request the updater log is in the user profile, which changes location from the actual location to a backup location. diff --git a/onlineupdate/StaticLibrary_libmar.mk b/onlineupdate/StaticLibrary_libmar.mk new file mode 100644 index 000000000..9b82e726a --- /dev/null +++ b/onlineupdate/StaticLibrary_libmar.mk @@ -0,0 +1,27 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# + +$(eval $(call gb_StaticLibrary_StaticLibrary,libmar)) + +$(eval $(call gb_StaticLibrary_set_include,libmar,\ + -I$(SRCDIR)/onlineupdate/source/libmar/src/ \ + $$(INCLUDE) \ +)) + +$(eval $(call gb_StaticLibrary_add_cobjects,libmar,\ + onlineupdate/source/libmar/src/mar_create \ + onlineupdate/source/libmar/src/mar_extract \ + onlineupdate/source/libmar/src/mar_read \ +)) + +$(eval $(call gb_StaticLibrary_use_static_libraries,\ + libmarverify \ +)) + +# vim:set shiftwidth=4 tabstop=4 noexpandtab: */ diff --git a/onlineupdate/StaticLibrary_libmarverify.mk b/onlineupdate/StaticLibrary_libmarverify.mk new file mode 100644 index 000000000..41ac492a7 --- /dev/null +++ b/onlineupdate/StaticLibrary_libmarverify.mk @@ -0,0 +1,32 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# + +$(eval $(call gb_StaticLibrary_StaticLibrary,libmarverify)) + +$(eval $(call gb_StaticLibrary_set_include,libmarverify,\ + -I$(SRCDIR)/onlineupdate/source/libmar/src/ \ + $$(INCLUDE) \ +)) + +ifneq ($(OS),WNT) +$(eval $(call gb_StaticLibrary_add_defs,libmarverify,\ + -DMAR_NSS \ +)) + +$(eval $(call gb_StaticLibrary_use_externals,libmarverify,\ + nss3 \ +)) +endif + +$(eval $(call gb_StaticLibrary_add_cobjects,libmarverify,\ + onlineupdate/source/libmar/verify/mar_verify \ + onlineupdate/source/libmar/verify/cryptox \ +)) + +# vim:set shiftwidth=4 tabstop=4 noexpandtab: */ diff --git a/onlineupdate/StaticLibrary_updatehelper.mk b/onlineupdate/StaticLibrary_updatehelper.mk new file mode 100644 index 000000000..1c3654cbc --- /dev/null +++ b/onlineupdate/StaticLibrary_updatehelper.mk @@ -0,0 +1,31 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# + +$(eval $(call gb_StaticLibrary_StaticLibrary,updatehelper)) + +$(eval $(call gb_StaticLibrary_set_include,updatehelper,\ + -I$(SRCDIR)/onlineupdate/inc/ \ + -I$(SRCDIR)/onlineupdate/source/update/common \ + $$(INCLUDE) \ +)) + +$(eval $(call gb_StaticLibrary_add_defs,updatehelper,\ + -DNSS3 \ + -DVERIFY_MAR_SIGNATURE \ +)) + +$(eval $(call gb_StaticLibrary_add_exception_objects,updatehelper,\ + onlineupdate/source/update/common/pathhash \ + onlineupdate/source/update/common/readstrings \ + onlineupdate/source/update/common/uachelper \ + onlineupdate/source/update/common/updatehelper \ + onlineupdate/source/update/common/updatelogging \ +)) + +# vim:set shiftwidth=4 tabstop=4 noexpandtab: */ diff --git a/onlineupdate/WinResTarget_updater.mk b/onlineupdate/WinResTarget_updater.mk new file mode 100644 index 000000000..f01b3ff42 --- /dev/null +++ b/onlineupdate/WinResTarget_updater.mk @@ -0,0 +1,15 @@ +$(eval $(call gb_WinResTarget_WinResTarget,updaterres)) + +$(eval $(call gb_WinResTarget_set_include,updaterres,\ + $$(INCLUDE) \ + -I$(SRCDIR)/onlineupdate/source/update/common \ + -I$(SRCDIR)/onlineupdate/source/update/updater \ +)) + +$(eval $(call gb_WinResTarget_add_dependencies,updaterres, \ + onlineupdate/source/update/updater/updater.ico \ +)) + +$(eval $(call gb_WinResTarget_set_rcfile,updaterres, \ + onlineupdate/source/update/updater/updater \ +)) diff --git a/onlineupdate/astyle.options b/onlineupdate/astyle.options new file mode 100644 index 000000000..fc8d9f69f --- /dev/null +++ b/onlineupdate/astyle.options @@ -0,0 +1,7 @@ +--style=allman +-s4 +--indent=spaces=4 +--attach-namespaces +--indent-switches +--indent-col1-comments +--pad-header diff --git a/onlineupdate/inc/Char16.h b/onlineupdate/inc/Char16.h new file mode 100644 index 000000000..bc9917981 --- /dev/null +++ b/onlineupdate/inc/Char16.h @@ -0,0 +1,194 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Implements a UTF-16 character type. */ + +#ifndef mozilla_Char16_h +#define mozilla_Char16_h + +#ifdef __cplusplus + +/* + * C++11 introduces a char16_t type and support for UTF-16 string and character + * literals. C++11's char16_t is a distinct builtin type. Technically, char16_t + * is a 16-bit code unit of a Unicode code point, not a "character". + */ + +#ifdef _WIN32 +# define MOZ_USE_CHAR16_WRAPPER +# include <cstdint> + /** + * Win32 API extensively uses wchar_t, which is represented by a separated + * builtin type than char16_t per spec. It's not the case for MSVC prior to + * MSVC 2015, but other compilers follow the spec. We want to mix wchar_t and + * char16_t on Windows builds. This class is supposed to make it easier. It + * stores char16_t const pointer, but provides implicit casts for wchar_t as + * well. On other platforms, we simply use + * |typedef const char16_t* char16ptr_t|. Here, we want to make the class as + * similar to this typedef, including providing some casts that are allowed + * by the typedef. + */ +class char16ptr_t +{ +private: + const char16_t* mPtr; + static_assert(sizeof(char16_t) == sizeof(wchar_t), + "char16_t and wchar_t sizes differ"); + +public: + char16ptr_t(const char16_t* aPtr) : mPtr(aPtr) {} + char16ptr_t(const wchar_t* aPtr) : + mPtr(reinterpret_cast<const char16_t*>(aPtr)) + {} + + /* Without this, nullptr assignment would be ambiguous. */ + constexpr char16ptr_t(decltype(nullptr)) : mPtr(nullptr) {} + + operator const char16_t*() const + { + return mPtr; + } + operator const wchar_t*() const + { + return reinterpret_cast<const wchar_t*>(mPtr); + } + operator const void*() const + { + return mPtr; + } + operator bool() const + { + return mPtr != nullptr; + } + + /* Explicit cast operators to allow things like (char16_t*)str. */ + explicit operator char16_t*() const + { + return const_cast<char16_t*>(mPtr); + } + explicit operator wchar_t*() const + { + return const_cast<wchar_t*>(static_cast<const wchar_t*>(*this)); + } + explicit operator int() const + { + return reinterpret_cast<intptr_t>(mPtr); + } + explicit operator unsigned int() const + { + return reinterpret_cast<uintptr_t>(mPtr); + } + explicit operator long() const + { + return reinterpret_cast<intptr_t>(mPtr); + } + explicit operator unsigned long() const + { + return reinterpret_cast<uintptr_t>(mPtr); + } + explicit operator long long() const + { + return reinterpret_cast<intptr_t>(mPtr); + } + explicit operator unsigned long long() const + { + return reinterpret_cast<uintptr_t>(mPtr); + } + + /** + * Some Windows API calls accept BYTE* but require that data actually be + * WCHAR*. Supporting this requires explicit operators to support the + * requisite explicit casts. + */ + explicit operator const char*() const + { + return reinterpret_cast<const char*>(mPtr); + } + explicit operator const unsigned char*() const + { + return reinterpret_cast<const unsigned char*>(mPtr); + } + explicit operator unsigned char*() const + { + return + const_cast<unsigned char*>(reinterpret_cast<const unsigned char*>(mPtr)); + } + explicit operator void*() const + { + return const_cast<char16_t*>(mPtr); + } + + /* Some operators used on pointers. */ + char16_t operator[](size_t aIndex) const + { + return mPtr[aIndex]; + } + bool operator==(const char16ptr_t& aOther) const + { + return mPtr == aOther.mPtr; + } + bool operator==(decltype(nullptr)) const + { + return mPtr == nullptr; + } + bool operator!=(const char16ptr_t& aOther) const + { + return mPtr != aOther.mPtr; + } + bool operator!=(decltype(nullptr)) const + { + return mPtr != nullptr; + } + char16ptr_t operator+(int aValue) const + { + return char16ptr_t(mPtr + aValue); + } + char16ptr_t operator+(unsigned int aValue) const + { + return char16ptr_t(mPtr + aValue); + } + char16ptr_t operator+(long aValue) const + { + return char16ptr_t(mPtr + aValue); + } + char16ptr_t operator+(unsigned long aValue) const + { + return char16ptr_t(mPtr + aValue); + } + char16ptr_t operator+(long long aValue) const + { + return char16ptr_t(mPtr + aValue); + } + char16ptr_t operator+(unsigned long long aValue) const + { + return char16ptr_t(mPtr + aValue); + } + ptrdiff_t operator-(const char16ptr_t& aOther) const + { + return mPtr - aOther.mPtr; + } +}; + +inline decltype((char*)0-(char*)0) +operator-(const char16_t* aX, const char16ptr_t aY) +{ + return aX - static_cast<const char16_t*>(aY); +} + +#else + +typedef const char16_t* char16ptr_t; + +#endif + +static_assert(sizeof(char16_t) == 2, "Is char16_t type 16 bits?"); +static_assert(char16_t(-1) > char16_t(0), "Is char16_t type unsigned?"); +static_assert(sizeof(u'A') == 2, "Is unicode char literal 16 bits?"); +static_assert(sizeof(u""[0]) == 2, "Is unicode string char 16 bits?"); + +#endif + +#endif /* mozilla_Char16_h */ diff --git a/onlineupdate/inc/bspatch.h b/onlineupdate/inc/bspatch.h new file mode 100644 index 000000000..ff1a80729 --- /dev/null +++ b/onlineupdate/inc/bspatch.h @@ -0,0 +1,95 @@ +/*- + * Copyright 2003,2004 Colin Percival + * All rights reserved + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted providing that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * Changelog: + * 2005-04-26 - Define the header as a C structure, add a CRC32 checksum to + * the header, and make all the types 32-bit. + * --Benjamin Smedberg <benjamin@smedbergs.us> + */ + +#ifndef bspatch_h__ +#define bspatch_h__ + +#include <stdint.h> +#include <stdio.h> + +typedef struct MBSPatchHeader_ +{ + /* "MBDIFF10" */ + char tag[8]; + + /* Length of the file to be patched */ + uint32_t slen; + + /* CRC32 of the file to be patched */ + uint32_t scrc32; + + /* Length of the result file */ + uint32_t dlen; + + /* Length of the control block in bytes */ + uint32_t cblen; + + /* Length of the diff block in bytes */ + uint32_t difflen; + + /* Length of the extra block in bytes */ + uint32_t extralen; + + /* Control block (MBSPatchTriple[]) */ + /* Diff block (binary data) */ + /* Extra block (binary data) */ +} MBSPatchHeader; + +/** + * Read the header of a patch file into the MBSPatchHeader structure. + * + * @param fd Must have been opened for reading, and be at the beginning + * of the file. + */ +int MBS_ReadHeader(FILE* file, MBSPatchHeader *header); + +/** + * Apply a patch. This method does not validate the checksum of the original + * file: client code should validate the checksum before calling this method. + * + * @param patchfd Must have been processed by MBS_ReadHeader + * @param fbuffer The original file read into a memory buffer of length + * header->slen. + * @param filefd Must have been opened for writing. Should be truncated + * to header->dlen if it is an existing file. The offset + * should be at the beginning of the file. + */ +int MBS_ApplyPatch(const MBSPatchHeader *header, FILE* patchFile, + unsigned char *fbuffer, FILE* file); + +typedef struct MBSPatchTriple_ +{ + uint32_t x; /* add x bytes from oldfile to x bytes from the diff block */ + uint32_t y; /* copy y bytes from the extra block */ + int32_t z; /* seek forwards in oldfile by z bytes */ +} MBSPatchTriple; + +#endif // bspatch_h__ diff --git a/onlineupdate/inc/types.hxx b/onlineupdate/inc/types.hxx new file mode 100644 index 000000000..3e83a19c7 --- /dev/null +++ b/onlineupdate/inc/types.hxx @@ -0,0 +1,25 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#ifndef INCLUDED_ONLINEUPDATE_TYPES_HXX +#define INCLUDED_ONLINEUPDATE_TYPES_HXX + +#include <string> + +#if defined(_WIN32) +typedef std::wstring tstring; +typedef WCHAR NS_tchar; +#else +typedef std::string tstring; +typedef char NS_tchar; +#endif + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/onlineupdate/qa/lang_packs/Makefile b/onlineupdate/qa/lang_packs/Makefile new file mode 100644 index 000000000..d328472f5 --- /dev/null +++ b/onlineupdate/qa/lang_packs/Makefile @@ -0,0 +1,21 @@ +gb_Side := host +include ../../../config_host.mk + +UPDATER_DIR := $(WORKDIR)/updater/lang_pack + +all : call + +unpack: + @echo "Unpacking the updater test project" + @rm -r $(UPDATER_DIR) || true + @mkdir -p $(WORKDIR)/updater/ + @unzip updater_lang.zip -d $(WORKDIR)/updater > /dev/null + @cp update_signed.mar $(UPDATER_DIR)/user/patch/update.mar + @cp update_en_signed.mar $(UPDATER_DIR)/user/patch/update_en_signed.mar + +call: unpack + @echo "Update the test project" + @$(INSTDIR)/program/updater $(UPDATER_DIR)/user/patch $(UPDATER_DIR) $(UPDATER_DIR)/user/update -1 + +call-gdb: unpack + gdb --args $(INSTDIR)/program/updater $(UPDATER_DIR)/user/patch $(UPDATER_DIR) $(UPDATER_DIR)/user/update -1 diff --git a/onlineupdate/qa/lang_packs/update_en_signed.mar b/onlineupdate/qa/lang_packs/update_en_signed.mar Binary files differnew file mode 100644 index 000000000..b72d1c5df --- /dev/null +++ b/onlineupdate/qa/lang_packs/update_en_signed.mar diff --git a/onlineupdate/qa/lang_packs/update_signed.mar b/onlineupdate/qa/lang_packs/update_signed.mar Binary files differnew file mode 100644 index 000000000..a145d6325 --- /dev/null +++ b/onlineupdate/qa/lang_packs/update_signed.mar diff --git a/onlineupdate/qa/lang_packs/updater_lang.zip b/onlineupdate/qa/lang_packs/updater_lang.zip Binary files differnew file mode 100644 index 000000000..05b206bc8 --- /dev/null +++ b/onlineupdate/qa/lang_packs/updater_lang.zip diff --git a/onlineupdate/qa/replace_request/Makefile b/onlineupdate/qa/replace_request/Makefile new file mode 100644 index 000000000..476e93213 --- /dev/null +++ b/onlineupdate/qa/replace_request/Makefile @@ -0,0 +1,17 @@ +gb_Side := host +include ../../../config_host.mk + +UPDATER_DIR := $(WORKDIR)/updater/replace_request + +unpack: + @echo "Unpacking the updater test project" + @rm -r $(UPDATER_DIR) || true + @mkdir -p $(WORKDIR)/updater + @unzip updater.zip -d $(WORKDIR)/updater > /dev/null + +call: unpack + @echo "Update the test project" + @$(INSTDIR)/program/updater $(UPDATER_DIR)/user/patch $(UPDATER_DIR) $(UPDATER_DIR)/user/update /replace + +call-gdb: unpack + gdb --args $(INSTDIR)/program/updater $(UPDATER_DIR)/user/patch $(UPDATER_DIR) $(UPDATER_DIR)/user/update /replace diff --git a/onlineupdate/qa/replace_request/README b/onlineupdate/qa/replace_request/README new file mode 100644 index 000000000..f50c692fd --- /dev/null +++ b/onlineupdate/qa/replace_request/README @@ -0,0 +1,3 @@ +Tests the replacement request done by the automatic updater + +After calling 'make call' the content in workdir/updater/program/datei.txt should say "new". diff --git a/onlineupdate/qa/replace_request/updater.zip b/onlineupdate/qa/replace_request/updater.zip Binary files differnew file mode 100644 index 000000000..dc5e26b0f --- /dev/null +++ b/onlineupdate/qa/replace_request/updater.zip diff --git a/onlineupdate/qa/single_step/Makefile b/onlineupdate/qa/single_step/Makefile new file mode 100644 index 000000000..f303992ba --- /dev/null +++ b/onlineupdate/qa/single_step/Makefile @@ -0,0 +1,27 @@ +gb_Side := host +include ../../../config_host.mk + +UPDATER_DIR := $(WORKDIR)/updater/single_step + +all : call + +unpack: + @echo "Unpacking the updater test project" + @rm -r $(UPDATER_DIR) || true + @mkdir -p $(UPDATER_DIR) + @unzip single_step.zip -d $(WORKDIR)/updater > /dev/null + @mkdir -p $(UPDATER_DIR)/patch + +call-complete: unpack + @echo "Update the test project" + @cp complete_signed.mar $(UPDATER_DIR)/patch/update.mar + @$(INSTDIR)/program/updater $(UPDATER_DIR)/patch $(UPDATER_DIR) $(UPDATER_DIR) 0 + +call-incremental: unpack + @echo "Update the test project" + @cp incremental_signed.mar $(UPDATER_DIR)/patch/update.mar + @$(INSTDIR)/program/updater $(UPDATER_DIR)/patch $(UPDATER_DIR) $(UPDATER_DIR) 0 + +call-gdb: unpack + @cp incremental_signed.mar $(UPDATER_DIR)/patch/update.mar + gdb --args $(INSTDIR)/program/updater $(UPDATER_DIR)/patch $(UPDATER_DIR) $(UPDATER_DIR) 0 diff --git a/onlineupdate/qa/single_step/complete_signed.mar b/onlineupdate/qa/single_step/complete_signed.mar Binary files differnew file mode 100644 index 000000000..7b0631dd3 --- /dev/null +++ b/onlineupdate/qa/single_step/complete_signed.mar diff --git a/onlineupdate/qa/single_step/incremental_signed.mar b/onlineupdate/qa/single_step/incremental_signed.mar Binary files differnew file mode 100644 index 000000000..6ec6e7a1a --- /dev/null +++ b/onlineupdate/qa/single_step/incremental_signed.mar diff --git a/onlineupdate/qa/single_step/single_step.zip b/onlineupdate/qa/single_step/single_step.zip Binary files differnew file mode 100644 index 000000000..823a590e0 --- /dev/null +++ b/onlineupdate/qa/single_step/single_step.zip diff --git a/onlineupdate/source/libmar/README b/onlineupdate/source/libmar/README new file mode 100644 index 000000000..422a28959 --- /dev/null +++ b/onlineupdate/source/libmar/README @@ -0,0 +1,6 @@ +This directory contains code for a simple archive file format, which +is documented at http://wiki.mozilla.org/Software_Update:MAR + +The src directory builds a small static library used to create, read, and +extract an archive file. The tool directory builds a command line utility +around the library. diff --git a/onlineupdate/source/libmar/sign/Makefile.in b/onlineupdate/source/libmar/sign/Makefile.in new file mode 100644 index 000000000..c5eaeb444 --- /dev/null +++ b/onlineupdate/source/libmar/sign/Makefile.in @@ -0,0 +1,10 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +# This makefile just builds support for reading archives. +include $(topsrcdir)/config/rules.mk + +# The intermediate (.ii/.s) files for host and target can have the same name... +# disable parallel builds +.NOTPARALLEL: diff --git a/onlineupdate/source/libmar/sign/mar_sign.c b/onlineupdate/source/libmar/sign/mar_sign.c new file mode 100644 index 000000000..161cadc0d --- /dev/null +++ b/onlineupdate/source/libmar/sign/mar_sign.c @@ -0,0 +1,1160 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifdef _WIN32 +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#endif + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <stdlib.h> +#include <string.h> +#include <onlineupdate/mar_private.h> +#include <onlineupdate/mar_cmdline.h> +#include <onlineupdate/mar.h> +#include "cryptox.h" +#ifndef _WIN32 +#include <unistd.h> +#endif + +#include "nss_secutil.h" +#include "base64.h" + +/** + * Initializes the NSS context. + * + * @param NSSConfigDir The config dir containing the private key to use + * @return 0 on success + * -1 on error +*/ +int +NSSInitCryptoContext(const char *NSSConfigDir) +{ + SECStatus status = NSS_Initialize(NSSConfigDir, + "", "", SECMOD_DB, NSS_INIT_READONLY); + if (SECSuccess != status) { + fprintf(stderr, "ERROR: Could not initialize NSS\n"); + return -1; + } + + return 0; +} + +/** + * Obtains a signing context. + * + * @param ctx A pointer to the signing context to fill + * @return 0 on success + * -1 on error +*/ +int +NSSSignBegin(const char *certName, + SGNContext **ctx, + SECKEYPrivateKey **privKey, + CERTCertificate **cert, + uint32_t *signatureLength) +{ + secuPWData pwdata = { PW_NONE, 0 }; + if (!certName || !ctx || !privKey || !cert || !signatureLength) { + fprintf(stderr, "ERROR: Invalid parameter passed to NSSSignBegin\n"); + return -1; + } + + /* Get the cert and embedded public key out of the database */ + *cert = PK11_FindCertFromNickname(certName, &pwdata); + if (!*cert) { + fprintf(stderr, "ERROR: Could not find cert from nickname\n"); + return -1; + } + + /* Get the private key out of the database */ + *privKey = PK11_FindKeyByAnyCert(*cert, &pwdata); + if (!*privKey) { + fprintf(stderr, "ERROR: Could not find private key\n"); + return -1; + } + + *signatureLength = PK11_SignatureLen(*privKey); + + if (*signatureLength > BLOCKSIZE) { + fprintf(stderr, + "ERROR: Program must be compiled with a larger block size" + " to support signing with signatures this large: %u.\n", + *signatureLength); + return -1; + } + + /* Check that the key length is large enough for our requirements */ + if (*signatureLength < XP_MIN_SIGNATURE_LEN_IN_BYTES) { + fprintf(stderr, "ERROR: Key length must be >= %d bytes\n", + XP_MIN_SIGNATURE_LEN_IN_BYTES); + return -1; + } + + *ctx = SGN_NewContext (SEC_OID_ISO_SHA1_WITH_RSA_SIGNATURE, *privKey); + if (!*ctx) { + fprintf(stderr, "ERROR: Could not create signature context\n"); + return -1; + } + + if (SGN_Begin(*ctx) != SECSuccess) { + fprintf(stderr, "ERROR: Could not begin signature\n"); + return -1; + } + + return 0; +} + +/** + * Writes the passed buffer to the file fp and updates the signature contexts. + * + * @param fpDest The file pointer to write to. + * @param buffer The buffer to write. + * @param size The size of the buffer to write. + * @param ctxs Pointer to the first element in an array of signature + * contexts to update. + * @param ctxCount The number of signature contexts pointed to by ctxs + * @param err The name of what is being written to in case of error. + * @return 0 on success + * -2 on write error + * -3 on signature update error +*/ +int +WriteAndUpdateSignatures(FILE *fpDest, void *buffer, + uint32_t size, SGNContext **ctxs, + uint32_t ctxCount, + const char *err) +{ + uint32_t k; + if (!size) { + return 0; + } + + if (fwrite(buffer, size, 1, fpDest) != 1) { + fprintf(stderr, "ERROR: Could not write %s\n", err); + return -2; + } + + for (k = 0; k < ctxCount; ++k) { + if (SGN_Update(ctxs[k], buffer, size) != SECSuccess) { + fprintf(stderr, "ERROR: Could not update signature context for %s\n", err); + return -3; + } + } + return 0; +} + +/** + * Adjusts each entry's content offset in the passed in index by the + * specified amount. + * + * @param indexBuf A buffer containing the MAR index + * @param indexLength The length of the MAR index + * @param offsetAmount The amount to adjust each index entry by +*/ +void +AdjustIndexContentOffsets(char *indexBuf, uint32_t indexLength, uint32_t offsetAmount) +{ + char *indexBufLoc = indexBuf; + + /* Consume the index and adjust each index by the specified amount */ + while (indexBufLoc != (indexBuf + indexLength)) { + /* Adjust the offset */ + uint32_t* offsetToContent = (uint32_t *)indexBufLoc; + *offsetToContent = ntohl(*offsetToContent); + *offsetToContent += offsetAmount; + *offsetToContent = htonl(*offsetToContent); + /* Skip past the offset, length, and flags */ + indexBufLoc += 3 * sizeof(uint32_t); + indexBufLoc += strlen(indexBufLoc) + 1; + } +} + +/** + * Reads from fpSrc, writes it to fpDest, and updates the signature contexts. + * + * @param fpSrc The file pointer to read from. + * @param fpDest The file pointer to write to. + * @param buffer The buffer to write. + * @param size The size of the buffer to write. + * @param ctxs Pointer to the first element in an array of signature + * contexts to update. + * @param ctxCount The number of signature contexts pointed to by ctxs + * @param err The name of what is being written to in case of error. + * @return 0 on success + * -1 on read error + * -2 on write error + * -3 on signature update error +*/ +int +ReadWriteAndUpdateSignatures(FILE *fpSrc, FILE *fpDest, void *buffer, + uint32_t size, SGNContext **ctxs, + uint32_t ctxCount, + const char *err) +{ + if (!size) { + return 0; + } + + if (fread(buffer, size, 1, fpSrc) != 1) { + fprintf(stderr, "ERROR: Could not read %s\n", err); + return -1; + } + + return WriteAndUpdateSignatures(fpDest, buffer, size, ctxs, ctxCount, err); +} + + +/** + * Reads from fpSrc, writes it to fpDest. + * + * @param fpSrc The file pointer to read from. + * @param fpDest The file pointer to write to. + * @param buffer The buffer to write. + * @param size The size of the buffer to write. + * @param err The name of what is being written to in case of error. + * @return 0 on success + * -1 on read error + * -2 on write error +*/ +int +ReadAndWrite(FILE *fpSrc, FILE *fpDest, void *buffer, + uint32_t size, const char *err) +{ + if (!size) { + return 0; + } + + if (fread(buffer, size, 1, fpSrc) != 1) { + fprintf(stderr, "ERROR: Could not read %s\n", err); + return -1; + } + + if (fwrite(buffer, size, 1, fpDest) != 1) { + fprintf(stderr, "ERROR: Could not write %s\n", err); + return -2; + } + + return 0; +} + +/** + * Writes out a copy of the MAR at src but with the signature block stripped. + * + * @param src The path of the source MAR file + * @param dest The path of the MAR file to write out that + has no signature block + * @return 0 on success + * -1 on error +*/ +int +strip_signature_block(const char *src, const char * dest) +{ + uint32_t offsetToIndex, dstOffsetToIndex, indexLength, + numSignatures = 0, leftOver; + int32_t stripAmount = 0; + int64_t oldPos, sizeOfEntireMAR = 0, realSizeOfSrcMAR, numBytesToCopy, + numChunks, i; + FILE *fpSrc = NULL, *fpDest = NULL; + int rv = -1, hasSignatureBlock; + char buf[BLOCKSIZE]; + char *indexBuf = NULL; + + if (!src || !dest) { + fprintf(stderr, "ERROR: Invalid parameter passed in.\n"); + return -1; + } + + fpSrc = fopen(src, "rb"); + if (!fpSrc) { + fprintf(stderr, "ERROR: could not open source file: %s\n", src); + goto failure; + } + + fpDest = fopen(dest, "wb"); + if (!fpDest) { + fprintf(stderr, "ERROR: could not create target file: %s\n", dest); + goto failure; + } + + /* Determine if the source MAR file has the new fields for signing or not */ + if (get_mar_file_info(src, &hasSignatureBlock, NULL, NULL, NULL, NULL)) { + fprintf(stderr, "ERROR: could not determine if MAR is old or new.\n"); + goto failure; + } + + /* MAR ID */ + if (ReadAndWrite(fpSrc, fpDest, buf, MAR_ID_SIZE, "MAR ID")) { + goto failure; + } + + /* Offset to index */ + if (fread(&offsetToIndex, sizeof(offsetToIndex), 1, fpSrc) != 1) { + fprintf(stderr, "ERROR: Could not read offset\n"); + goto failure; + } + offsetToIndex = ntohl(offsetToIndex); + + /* Get the real size of the MAR */ + oldPos = ftello(fpSrc); + if (fseeko(fpSrc, 0, SEEK_END)) { + fprintf(stderr, "ERROR: Could not seek to end of file.\n"); + goto failure; + } + realSizeOfSrcMAR = ftello(fpSrc); + if (fseeko(fpSrc, oldPos, SEEK_SET)) { + fprintf(stderr, "ERROR: Could not seek back to current location.\n"); + goto failure; + } + + if (hasSignatureBlock) { + /* Get the MAR length and adjust its size */ + if (fread(&sizeOfEntireMAR, + sizeof(sizeOfEntireMAR), 1, fpSrc) != 1) { + fprintf(stderr, "ERROR: Could read mar size\n"); + goto failure; + } + sizeOfEntireMAR = NETWORK_TO_HOST64(sizeOfEntireMAR); + if (sizeOfEntireMAR != realSizeOfSrcMAR) { + fprintf(stderr, "ERROR: Source MAR is not of the right size\n"); + goto failure; + } + + /* Get the num signatures in the source file so we know what to strip */ + if (fread(&numSignatures, sizeof(numSignatures), 1, fpSrc) != 1) { + fprintf(stderr, "ERROR: Could read num signatures\n"); + goto failure; + } + numSignatures = ntohl(numSignatures); + + for (i = 0; i < numSignatures; i++) { + uint32_t signatureLen; + + /* Skip past the signature algorithm ID */ + if (fseeko(fpSrc, sizeof(uint32_t), SEEK_CUR)) { + fprintf(stderr, "ERROR: Could not skip past signature algorithm ID\n"); + } + + /* Read in the length of the signature so we know how far to skip */ + if (fread(&signatureLen, sizeof(uint32_t), 1, fpSrc) != 1) { + fprintf(stderr, "ERROR: Could not read signatures length.\n"); + return CryptoX_Error; + } + signatureLen = ntohl(signatureLen); + + /* Skip past the signature */ + if (fseeko(fpSrc, signatureLen, SEEK_CUR)) { + fprintf(stderr, "ERROR: Could not skip past signature algorithm ID\n"); + } + + stripAmount += sizeof(uint32_t) + sizeof(uint32_t) + signatureLen; + } + + } else { + sizeOfEntireMAR = realSizeOfSrcMAR; + numSignatures = 0; + } + + if (((int64_t)offsetToIndex) > sizeOfEntireMAR) { + fprintf(stderr, "ERROR: Offset to index is larger than the file size.\n"); + goto failure; + } + + dstOffsetToIndex = offsetToIndex; + if (!hasSignatureBlock) { + dstOffsetToIndex += sizeof(sizeOfEntireMAR) + sizeof(numSignatures); + } + dstOffsetToIndex -= stripAmount; + + /* Write out the index offset */ + dstOffsetToIndex = htonl(dstOffsetToIndex); + if (fwrite(&dstOffsetToIndex, sizeof(dstOffsetToIndex), 1, fpDest) != 1) { + fprintf(stderr, "ERROR: Could not write offset to index\n"); + goto failure; + } + dstOffsetToIndex = ntohl(dstOffsetToIndex); + + /* Write out the new MAR file size */ + if (!hasSignatureBlock) { + sizeOfEntireMAR += sizeof(sizeOfEntireMAR) + sizeof(numSignatures); + } + sizeOfEntireMAR -= stripAmount; + + /* Write out the MAR size */ + sizeOfEntireMAR = HOST_TO_NETWORK64(sizeOfEntireMAR); + if (fwrite(&sizeOfEntireMAR, sizeof(sizeOfEntireMAR), 1, fpDest) != 1) { + fprintf(stderr, "ERROR: Could not write size of MAR\n"); + goto failure; + } + sizeOfEntireMAR = NETWORK_TO_HOST64(sizeOfEntireMAR); + + /* Write out the number of signatures, which is 0 */ + numSignatures = 0; + if (fwrite(&numSignatures, sizeof(numSignatures), 1, fpDest) != 1) { + fprintf(stderr, "ERROR: Could not write out num signatures\n"); + goto failure; + } + + /* Write out the rest of the MAR excluding the index header and index + offsetToIndex unfortunately has to remain 32-bit because for backwards + compatibility with the old MAR file format. */ + if (ftello(fpSrc) > ((int64_t)offsetToIndex)) { + fprintf(stderr, "ERROR: Index offset is too small.\n"); + goto failure; + } + numBytesToCopy = ((int64_t)offsetToIndex) - ftello(fpSrc); + numChunks = numBytesToCopy / BLOCKSIZE; + leftOver = numBytesToCopy % BLOCKSIZE; + + /* Read each file and write it to the MAR file */ + for (i = 0; i < numChunks; ++i) { + if (ReadAndWrite(fpSrc, fpDest, buf, BLOCKSIZE, "content block")) { + goto failure; + } + } + + /* Write out the left over */ + if (ReadAndWrite(fpSrc, fpDest, buf, + leftOver, "left over content block")) { + goto failure; + } + + /* Length of the index */ + if (ReadAndWrite(fpSrc, fpDest, &indexLength, + sizeof(indexLength), "index length")) { + goto failure; + } + indexLength = ntohl(indexLength); + + /* Consume the index and adjust each index by the difference */ + indexBuf = malloc(indexLength); + if (fread(indexBuf, indexLength, 1, fpSrc) != 1) { + fprintf(stderr, "ERROR: Could not read index\n"); + goto failure; + } + + /* Adjust each entry in the index */ + if (hasSignatureBlock) { + AdjustIndexContentOffsets(indexBuf, indexLength, -stripAmount); + } else { + AdjustIndexContentOffsets(indexBuf, indexLength, + sizeof(sizeOfEntireMAR) + + sizeof(numSignatures) - + stripAmount); + } + + if (fwrite(indexBuf, indexLength, 1, fpDest) != 1) { + fprintf(stderr, "ERROR: Could not write index\n"); + goto failure; + } + + rv = 0; +failure: + if (fpSrc) { + fclose(fpSrc); + } + + if (fpDest) { + fclose(fpDest); + } + + if (rv) { + remove(dest); + } + + if (indexBuf) { + free(indexBuf); + } + + if (rv) { + remove(dest); + } + return rv; +} + +/** + * Extracts a signature from a MAR file, base64 encodes it, and writes it out + * + * @param src The path of the source MAR file + * @param sigIndex The index of the signature to extract + * @param dest The path of file to write the signature to + * @return 0 on success + * -1 on error +*/ +int +extract_signature(const char *src, uint32_t sigIndex, const char * dest) +{ + FILE *fpSrc = NULL, *fpDest = NULL; + uint32_t i; + uint32_t signatureCount; + uint32_t signatureLen = 0; + uint8_t *extractedSignature = NULL; + char *base64Encoded = NULL; + int rv = -1; + if (!src || !dest) { + fprintf(stderr, "ERROR: Invalid parameter passed in.\n"); + goto failure; + } + + fpSrc = fopen(src, "rb"); + if (!fpSrc) { + fprintf(stderr, "ERROR: could not open source file: %s\n", src); + goto failure; + } + + fpDest = fopen(dest, "wb"); + if (!fpDest) { + fprintf(stderr, "ERROR: could not create target file: %s\n", dest); + goto failure; + } + + /* Skip to the start of the signature block */ + if (fseeko(fpSrc, SIGNATURE_BLOCK_OFFSET, SEEK_SET)) { + fprintf(stderr, "ERROR: could not seek to signature block\n"); + goto failure; + } + + /* Get the number of signatures */ + if (fread(&signatureCount, sizeof(signatureCount), 1, fpSrc) != 1) { + fprintf(stderr, "ERROR: could not read signature count\n"); + goto failure; + } + signatureCount = ntohl(signatureCount); + if (sigIndex >= signatureCount) { + fprintf(stderr, "ERROR: Signature index was out of range\n"); + goto failure; + } + + /* Skip to the correct signature */ + for (i = 0; i <= sigIndex; i++) { + /* Avoid leaking while skipping signatures */ + free(extractedSignature); + + /* skip past the signature algorithm ID */ + if (fseeko(fpSrc, sizeof(uint32_t), SEEK_CUR)) { + fprintf(stderr, "ERROR: Could not seek past sig algorithm ID.\n"); + goto failure; + } + + /* Get the signature length */ + if (fread(&signatureLen, sizeof(signatureLen), 1, fpSrc) != 1) { + fprintf(stderr, "ERROR: could not read signature length\n"); + goto failure; + } + signatureLen = ntohl(signatureLen); + + /* Get the signature */ + extractedSignature = malloc(signatureLen); + if (fread(extractedSignature, signatureLen, 1, fpSrc) != 1) { + fprintf(stderr, "ERROR: could not read signature\n"); + goto failure; + } + } + + base64Encoded = BTOA_DataToAscii(extractedSignature, signatureLen); + if (!base64Encoded) { + fprintf(stderr, "ERROR: could not obtain base64 encoded data\n"); + goto failure; + } + + if (fwrite(base64Encoded, strlen(base64Encoded), 1, fpDest) != 1) { + fprintf(stderr, "ERROR: Could not write base64 encoded string\n"); + goto failure; + } + + rv = 0; +failure: + if (base64Encoded) { + PORT_Free(base64Encoded); + } + + if (extractedSignature) { + free(extractedSignature); + } + + if (fpSrc) { + fclose(fpSrc); + } + + if (fpDest) { + fclose(fpDest); + } + + if (rv) { + remove(dest); + } + + return rv; +} + +/** + * Imports a base64 encoded signature into a MAR file + * + * @param src The path of the source MAR file + * @param sigIndex The index of the signature to import + * @param base64SigFile A file which contains the signature to import + * @param dest The path of the destination MAR file with replaced signature + * @return 0 on success + * -1 on error +*/ +int +import_signature(const char *src, uint32_t sigIndex, + const char *base64SigFile, const char *dest) +{ + int rv = -1; + FILE *fpSrc = NULL; + FILE *fpDest = NULL; + FILE *fpSigFile = NULL; + uint32_t i; + uint32_t signatureCount, signatureLen, signatureAlgorithmID, + numChunks, leftOver; + char buf[BLOCKSIZE]; + uint64_t sizeOfSrcMAR, sizeOfBase64EncodedFile; + char *passedInSignatureB64 = NULL; + uint8_t *passedInSignatureRaw = NULL; + uint8_t *extractedMARSignature = NULL; + unsigned int passedInSignatureLenRaw; + + if (!src || !dest) { + fprintf(stderr, "ERROR: Invalid parameter passed in.\n"); + goto failure; + } + + fpSrc = fopen(src, "rb"); + if (!fpSrc) { + fprintf(stderr, "ERROR: could not open source file: %s\n", src); + goto failure; + } + + fpDest = fopen(dest, "wb"); + if (!fpDest) { + fprintf(stderr, "ERROR: could not open dest file: %s\n", dest); + goto failure; + } + + fpSigFile = fopen(base64SigFile , "rb"); + if (!fpSigFile) { + fprintf(stderr, "ERROR: could not open sig file: %s\n", base64SigFile); + goto failure; + } + + /* Get the src file size */ + if (fseeko(fpSrc, 0, SEEK_END)) { + fprintf(stderr, "ERROR: Could not seek to end of src file.\n"); + goto failure; + } + sizeOfSrcMAR = ftello(fpSrc); + if (fseeko(fpSrc, 0, SEEK_SET)) { + fprintf(stderr, "ERROR: Could not seek to start of src file.\n"); + goto failure; + } + + /* Get the sig file size */ + if (fseeko(fpSigFile, 0, SEEK_END)) { + fprintf(stderr, "ERROR: Could not seek to end of sig file.\n"); + goto failure; + } + sizeOfBase64EncodedFile= ftello(fpSigFile); + if (fseeko(fpSigFile, 0, SEEK_SET)) { + fprintf(stderr, "ERROR: Could not seek to start of sig file.\n"); + goto failure; + } + + /* Read in the base64 encoded signature to import */ + passedInSignatureB64 = malloc(sizeOfBase64EncodedFile + 1); + passedInSignatureB64[sizeOfBase64EncodedFile] = '\0'; + if (fread(passedInSignatureB64, sizeOfBase64EncodedFile, 1, fpSigFile) != 1) { + fprintf(stderr, "ERROR: Could read b64 sig file.\n"); + goto failure; + } + + /* Decode the base64 encoded data */ + passedInSignatureRaw = ATOB_AsciiToData(passedInSignatureB64, &passedInSignatureLenRaw); + if (!passedInSignatureRaw) { + fprintf(stderr, "ERROR: could not obtain base64 decoded data\n"); + goto failure; + } + + /* Read everything up until the signature block offset and write it out */ + if (ReadAndWrite(fpSrc, fpDest, buf, + SIGNATURE_BLOCK_OFFSET, "signature block offset")) { + goto failure; + } + + /* Get the number of signatures */ + if (ReadAndWrite(fpSrc, fpDest, &signatureCount, + sizeof(signatureCount), "signature count")) { + goto failure; + } + signatureCount = ntohl(signatureCount); + if (signatureCount > MAX_SIGNATURES) { + fprintf(stderr, "ERROR: Signature count was out of range\n"); + goto failure; + } + + if (sigIndex >= signatureCount) { + fprintf(stderr, "ERROR: Signature index was out of range\n"); + goto failure; + } + + /* Read and write the whole signature block, but if we reach the + signature offset, then we should replace it with the specified + base64 decoded signature */ + for (i = 0; i < signatureCount; i++) { + /* Read/Write the signature algorithm ID */ + if (ReadAndWrite(fpSrc, fpDest, + &signatureAlgorithmID, + sizeof(signatureAlgorithmID), "sig algorithm ID")) { + goto failure; + } + + /* Read/Write the signature length */ + if (ReadAndWrite(fpSrc, fpDest, + &signatureLen, sizeof(signatureLen), "sig length")) { + goto failure; + } + signatureLen = ntohl(signatureLen); + + /* Get the signature */ + if (extractedMARSignature) { + free(extractedMARSignature); + } + extractedMARSignature = malloc(signatureLen); + + if (sigIndex == i) { + if (passedInSignatureLenRaw != signatureLen) { + fprintf(stderr, "ERROR: Signature length must be the same\n"); + goto failure; + } + + if (fread(extractedMARSignature, signatureLen, 1, fpSrc) != 1) { + fprintf(stderr, "ERROR: Could not read signature\n"); + goto failure; + } + + if (fwrite(passedInSignatureRaw, passedInSignatureLenRaw, + 1, fpDest) != 1) { + fprintf(stderr, "ERROR: Could not write signature\n"); + goto failure; + } + } else { + if (ReadAndWrite(fpSrc, fpDest, + extractedMARSignature, signatureLen, "signature")) { + goto failure; + } + } + } + + /* We replaced the signature so let's just skip past the rest o the + file. */ + numChunks = (sizeOfSrcMAR - ftello(fpSrc)) / BLOCKSIZE; + leftOver = (sizeOfSrcMAR - ftello(fpSrc)) % BLOCKSIZE; + + /* Read each file and write it to the MAR file */ + for (i = 0; i < numChunks; ++i) { + if (ReadAndWrite(fpSrc, fpDest, buf, BLOCKSIZE, "content block")) { + goto failure; + } + } + + if (ReadAndWrite(fpSrc, fpDest, buf, leftOver, "left over content block")) { + goto failure; + } + + rv = 0; + +failure: + + if (fpSrc) { + fclose(fpSrc); + } + + if (fpDest) { + fclose(fpDest); + } + + if (fpSigFile) { + fclose(fpSigFile); + } + + if (rv) { + remove(dest); + } + + if (extractedMARSignature) { + free(extractedMARSignature); + } + + if (passedInSignatureB64) { + free(passedInSignatureB64); + } + + if (passedInSignatureRaw) { + PORT_Free(passedInSignatureRaw); + } + + return rv; +} + +/** + * Writes out a copy of the MAR at src but with embedded signatures. + * The passed in MAR file must not already be signed or an error will + * be returned. + * + * @param NSSConfigDir The NSS directory containing the private key for signing + * @param certNames The nicknames of the certificate to use for signing + * @param certCount The number of certificate names contained in certNames. + * One signature will be produced for each certificate. + * @param src The path of the source MAR file to sign + * @param dest The path of the MAR file to write out that is signed + * @return 0 on success + * -1 on error +*/ +int +mar_repackage_and_sign(const char *NSSConfigDir, + const char * const *certNames, + uint32_t certCount, + const char *src, + const char *dest) +{ + uint32_t offsetToIndex, dstOffsetToIndex, indexLength, + numSignatures = 0, leftOver, + signatureAlgorithmID, signatureSectionLength = 0; + uint32_t signatureLengths[MAX_SIGNATURES]; + int64_t oldPos, sizeOfEntireMAR = 0, realSizeOfSrcMAR, + signaturePlaceholderOffset, numBytesToCopy, + numChunks, i; + FILE *fpSrc = NULL, *fpDest = NULL; + int rv = -1, hasSignatureBlock; + SGNContext *ctxs[MAX_SIGNATURES]; + SECItem secItems[MAX_SIGNATURES]; + char buf[BLOCKSIZE]; + SECKEYPrivateKey *privKeys[MAX_SIGNATURES]; + CERTCertificate *certs[MAX_SIGNATURES]; + char *indexBuf = NULL; + uint32_t k; + + memset(signatureLengths, 0, sizeof(signatureLengths)); + memset(ctxs, 0, sizeof(ctxs)); + memset(secItems, 0, sizeof(secItems)); + memset(privKeys, 0, sizeof(privKeys)); + memset(certs, 0, sizeof(certs)); + + if (!NSSConfigDir || !certNames || certCount == 0 || !src || !dest) { + fprintf(stderr, "ERROR: Invalid parameter passed in.\n"); + return -1; + } + + if (NSSInitCryptoContext(NSSConfigDir)) { + fprintf(stderr, "ERROR: Could not init config dir: %s\n", NSSConfigDir); + goto failure; + } + + PK11_SetPasswordFunc(SECU_GetModulePassword); + + fpSrc = fopen(src, "rb"); + if (!fpSrc) { + fprintf(stderr, "ERROR: could not open source file: %s\n", src); + goto failure; + } + + fpDest = fopen(dest, "wb"); + if (!fpDest) { + fprintf(stderr, "ERROR: could not create target file: %s\n", dest); + goto failure; + } + + /* Determine if the source MAR file has the new fields for signing or not */ + if (get_mar_file_info(src, &hasSignatureBlock, NULL, NULL, NULL, NULL)) { + fprintf(stderr, "ERROR: could not determine if MAR is old or new.\n"); + goto failure; + } + + for (k = 0; k < certCount; k++) { + if (NSSSignBegin(certNames[k], &ctxs[k], &privKeys[k], + &certs[k], &signatureLengths[k])) { + fprintf(stderr, "ERROR: NSSSignBegin failed\n"); + goto failure; + } + } + + /* MAR ID */ + if (ReadWriteAndUpdateSignatures(fpSrc, fpDest, + buf, MAR_ID_SIZE, + ctxs, certCount, "MAR ID")) { + goto failure; + } + + /* Offset to index */ + if (fread(&offsetToIndex, sizeof(offsetToIndex), 1, fpSrc) != 1) { + fprintf(stderr, "ERROR: Could not read offset\n"); + goto failure; + } + offsetToIndex = ntohl(offsetToIndex); + + /* Get the real size of the MAR */ + oldPos = ftello(fpSrc); + if (fseeko(fpSrc, 0, SEEK_END)) { + fprintf(stderr, "ERROR: Could not seek to end of file.\n"); + goto failure; + } + realSizeOfSrcMAR = ftello(fpSrc); + if (fseeko(fpSrc, oldPos, SEEK_SET)) { + fprintf(stderr, "ERROR: Could not seek back to current location.\n"); + goto failure; + } + + if (hasSignatureBlock) { + /* Get the MAR length and adjust its size */ + if (fread(&sizeOfEntireMAR, + sizeof(sizeOfEntireMAR), 1, fpSrc) != 1) { + fprintf(stderr, "ERROR: Could read mar size\n"); + goto failure; + } + sizeOfEntireMAR = NETWORK_TO_HOST64(sizeOfEntireMAR); + if (sizeOfEntireMAR != realSizeOfSrcMAR) { + fprintf(stderr, "ERROR: Source MAR is not of the right size\n"); + goto failure; + } + + /* Get the num signatures in the source file */ + if (fread(&numSignatures, sizeof(numSignatures), 1, fpSrc) != 1) { + fprintf(stderr, "ERROR: Could read num signatures\n"); + goto failure; + } + numSignatures = ntohl(numSignatures); + + /* We do not support resigning, if you have multiple signatures, + you must add them all at the same time. */ + if (numSignatures) { + fprintf(stderr, "ERROR: MAR is already signed\n"); + goto failure; + } + } else { + sizeOfEntireMAR = realSizeOfSrcMAR; + } + + if (((int64_t)offsetToIndex) > sizeOfEntireMAR) { + fprintf(stderr, "ERROR: Offset to index is larger than the file size.\n"); + goto failure; + } + + /* Calculate the total signature block length */ + for (k = 0; k < certCount; k++) { + signatureSectionLength += sizeof(signatureAlgorithmID) + + sizeof(signatureLengths[k]) + + signatureLengths[k]; + } + dstOffsetToIndex = offsetToIndex; + if (!hasSignatureBlock) { + dstOffsetToIndex += sizeof(sizeOfEntireMAR) + sizeof(numSignatures); + } + dstOffsetToIndex += signatureSectionLength; + + /* Write out the index offset */ + dstOffsetToIndex = htonl(dstOffsetToIndex); + if (WriteAndUpdateSignatures(fpDest, &dstOffsetToIndex, + sizeof(dstOffsetToIndex), ctxs, certCount, + "index offset")) { + goto failure; + } + dstOffsetToIndex = ntohl(dstOffsetToIndex); + + /* Write out the new MAR file size */ + sizeOfEntireMAR += signatureSectionLength; + if (!hasSignatureBlock) { + sizeOfEntireMAR += sizeof(sizeOfEntireMAR) + sizeof(numSignatures); + } + + /* Write out the MAR size */ + sizeOfEntireMAR = HOST_TO_NETWORK64(sizeOfEntireMAR); + if (WriteAndUpdateSignatures(fpDest, &sizeOfEntireMAR, + sizeof(sizeOfEntireMAR), ctxs, certCount, + "size of MAR")) { + goto failure; + } + sizeOfEntireMAR = NETWORK_TO_HOST64(sizeOfEntireMAR); + + /* Write out the number of signatures */ + numSignatures = certCount; + numSignatures = htonl(numSignatures); + if (WriteAndUpdateSignatures(fpDest, &numSignatures, + sizeof(numSignatures), ctxs, certCount, + "num signatures")) { + goto failure; + } + numSignatures = ntohl(numSignatures); + + signaturePlaceholderOffset = ftello(fpDest); + + for (k = 0; k < certCount; k++) { + /* Write out the signature algorithm ID, Only an ID of 1 is supported */ + signatureAlgorithmID = htonl(1); + if (WriteAndUpdateSignatures(fpDest, &signatureAlgorithmID, + sizeof(signatureAlgorithmID), + ctxs, certCount, "num signatures")) { + goto failure; + } + signatureAlgorithmID = ntohl(signatureAlgorithmID); + + /* Write out the signature length */ + signatureLengths[k] = htonl(signatureLengths[k]); + if (WriteAndUpdateSignatures(fpDest, &signatureLengths[k], + sizeof(signatureLengths[k]), + ctxs, certCount, "signature length")) { + goto failure; + } + signatureLengths[k] = ntohl(signatureLengths[k]); + + /* Write out a placeholder for the signature, we'll come back to this later + *** THIS IS NOT SIGNED because it is a placeholder that will be replaced + below, plus it is going to be the signature itself. *** */ + memset(buf, 0, sizeof(buf)); + if (fwrite(buf, signatureLengths[k], 1, fpDest) != 1) { + fprintf(stderr, "ERROR: Could not write signature length\n"); + goto failure; + } + } + + /* Write out the rest of the MAR excluding the index header and index + offsetToIndex unfortunately has to remain 32-bit because for backwards + compatibility with the old MAR file format. */ + if (ftello(fpSrc) > ((int64_t)offsetToIndex)) { + fprintf(stderr, "ERROR: Index offset is too small.\n"); + goto failure; + } + numBytesToCopy = ((int64_t)offsetToIndex) - ftello(fpSrc); + numChunks = numBytesToCopy / BLOCKSIZE; + leftOver = numBytesToCopy % BLOCKSIZE; + + /* Read each file and write it to the MAR file */ + for (i = 0; i < numChunks; ++i) { + if (ReadWriteAndUpdateSignatures(fpSrc, fpDest, buf, + BLOCKSIZE, ctxs, certCount, + "content block")) { + goto failure; + } + } + + /* Write out the left over */ + if (ReadWriteAndUpdateSignatures(fpSrc, fpDest, buf, + leftOver, ctxs, certCount, + "left over content block")) { + goto failure; + } + + /* Length of the index */ + if (ReadWriteAndUpdateSignatures(fpSrc, fpDest, &indexLength, + sizeof(indexLength), ctxs, certCount, + "index length")) { + goto failure; + } + indexLength = ntohl(indexLength); + + /* Consume the index and adjust each index by signatureSectionLength */ + indexBuf = malloc(indexLength); + if (fread(indexBuf, indexLength, 1, fpSrc) != 1) { + fprintf(stderr, "ERROR: Could not read index\n"); + goto failure; + } + + /* Adjust each entry in the index */ + if (hasSignatureBlock) { + AdjustIndexContentOffsets(indexBuf, indexLength, signatureSectionLength); + } else { + AdjustIndexContentOffsets(indexBuf, indexLength, + sizeof(sizeOfEntireMAR) + + sizeof(numSignatures) + + signatureSectionLength); + } + + if (WriteAndUpdateSignatures(fpDest, indexBuf, + indexLength, ctxs, certCount, "index")) { + goto failure; + } + + /* Ensure that we don't sign a file that is too large to be accepted by + the verification function. */ + if (ftello(fpDest) > MAX_SIZE_OF_MAR_FILE) { + goto failure; + } + + for (k = 0; k < certCount; k++) { + /* Get the signature */ + if (SGN_End(ctxs[k], &secItems[k]) != SECSuccess) { + fprintf(stderr, "ERROR: Could not end signature context\n"); + goto failure; + } + if (signatureLengths[k] != secItems[k].len) { + fprintf(stderr, "ERROR: Signature is not the expected length\n"); + goto failure; + } + } + + /* Get back to the location of the signature placeholder */ + if (fseeko(fpDest, signaturePlaceholderOffset, SEEK_SET)) { + fprintf(stderr, "ERROR: Could not seek to signature offset\n"); + goto failure; + } + + for (k = 0; k < certCount; k++) { + /* Skip to the position of the next signature */ + if (fseeko(fpDest, sizeof(signatureAlgorithmID) + + sizeof(signatureLengths[k]), SEEK_CUR)) { + fprintf(stderr, "ERROR: Could not seek to signature offset\n"); + goto failure; + } + + /* Write out the calculated signature. + *** THIS IS NOT SIGNED because it is the signature itself. *** */ + if (fwrite(secItems[k].data, secItems[k].len, 1, fpDest) != 1) { + fprintf(stderr, "ERROR: Could not write signature\n"); + goto failure; + } + } + + rv = 0; +failure: + if (fpSrc) { + fclose(fpSrc); + } + + if (fpDest) { + fclose(fpDest); + } + + if (rv) { + remove(dest); + } + + if (indexBuf) { + free(indexBuf); + } + + /* Cleanup */ + for (k = 0; k < certCount; k++) { + if (ctxs[k]) { + SGN_DestroyContext(ctxs[k], PR_TRUE); + } + + if (certs[k]) { + CERT_DestroyCertificate(certs[k]); + } + + if (privKeys[k]) { + SECKEY_DestroyPrivateKey(privKeys[k]); + } + + SECITEM_FreeItem(&secItems[k], PR_FALSE); + } + + if (rv) { + remove(dest); + } + + return rv; +} diff --git a/onlineupdate/source/libmar/sign/nss_secutil.c b/onlineupdate/source/libmar/sign/nss_secutil.c new file mode 100644 index 000000000..875c14309 --- /dev/null +++ b/onlineupdate/source/libmar/sign/nss_secutil.c @@ -0,0 +1,238 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* With the exception of GetPasswordString, this file was + copied from NSS's cmd/lib/secutil.c hg revision 8f011395145e */ + +#include "nss_secutil.h" + +#include "prprf.h" +#ifdef _WIN32 +#include <io.h> +#else +#include <unistd.h> +#endif + +static char consoleName[] = { +#ifdef UNIX + "/dev/tty" +#else + "CON:" +#endif +}; + +#if defined(_WINDOWS) +static char * quiet_fgets (char *buf, int length, FILE *input) +{ + char *end = buf; + + /* fflush (input); */ + memset (buf, 0, length); + + if (!isatty(fileno(input))) { + return fgets(buf,length,input); + } + + while (1) + { + int c; +#if defined (_WIN32_WCE) + c = getchar(); /* gets a character from stdin */ +#else + c = getch(); /* getch gets a character from the console */ +#endif + if (c == '\b') + { + if (end > buf) + end--; + } + + else if (--length > 0) + *end++ = c; + + if (!c || c == '\n' || c == '\r') + break; + } + + return buf; +} +#endif + +char * +GetPasswordString(void *arg, char *prompt) +{ + FILE *input = stdin; + char phrase[200] = {'\0'}; + int isInputTerminal = isatty(fileno(stdin)); + + (void) arg; (void) prompt; // avoid warnings + +#ifndef _WINDOWS + if (isInputTerminal) { + input = fopen(consoleName, "r"); + if (input == NULL) { + fprintf(stderr, "Error opening input terminal for read\n"); + return NULL; + } + } +#endif + + if (isInputTerminal) { + fprintf(stdout, "Please enter your password:\n"); + fflush(stdout); + } + + QUIET_FGETS (phrase, sizeof(phrase), input); + + if (isInputTerminal) { + fprintf(stdout, "\n"); + } + +#ifndef _WINDOWS + if (isInputTerminal) { + fclose(input); + } +#endif + + /* Strip off the newlines if present */ + if (phrase[PORT_Strlen(phrase)-1] == '\n' || + phrase[PORT_Strlen(phrase)-1] == '\r') { + phrase[PORT_Strlen(phrase)-1] = 0; + } + return (char*) PORT_Strdup(phrase); +} + +char * +SECU_FilePasswd(PK11SlotInfo *slot, PRBool retry, void *arg) +{ + char* phrases, *phrase; + PRFileDesc *fd; + int32_t nb; + char *pwFile = arg; + int i; + const long maxPwdFileSize = 4096; + char* tokenName = NULL; + int tokenLen = 0; + + if (!pwFile) + return 0; + + if (retry) { + return 0; /* no good retrying - the files contents will be the same */ + } + + phrases = PORT_ZAlloc(maxPwdFileSize + 1); + + if (!phrases) { + return 0; /* out of memory */ + } + + fd = PR_Open(pwFile, PR_RDONLY, 0); + if (!fd) { + fprintf(stderr, "No password file \"%s\" exists.\n", pwFile); + PORT_Free(phrases); + return NULL; + } + + nb = PR_Read(fd, phrases, maxPwdFileSize); + + PR_Close(fd); + + if (nb == 0) { + fprintf(stderr,"password file contains no data\n"); + PORT_Free(phrases); + return NULL; + } + + if (slot) { + tokenName = PK11_GetTokenName(slot); + if (tokenName) { + tokenLen = PORT_Strlen(tokenName); + } + } + i = 0; + do + { + int startphrase = i; + int phraseLen; + + /* handle the Windows EOL case */ + while (i < nb && phrases[i] != '\r' && phrases[i] != '\n') i++; + /* terminate passphrase */ + phrases[i++] = '\0'; + /* clean up any EOL before the start of the next passphrase */ + while ( (i<nb) && (phrases[i] == '\r' || phrases[i] == '\n')) { + phrases[i++] = '\0'; + } + /* now analyze the current passphrase */ + phrase = &phrases[startphrase]; + if (!tokenName) + break; + if (PORT_Strncmp(phrase, tokenName, tokenLen)) continue; + phraseLen = PORT_Strlen(phrase); + if (phraseLen < (tokenLen+1)) continue; + if (phrase[tokenLen] != ':') continue; + phrase = &phrase[tokenLen+1]; + break; + + } while (i<nb); + + phrase = PORT_Strdup((char*)phrase); + PORT_Free(phrases); + return phrase; +} + +char * +SECU_GetModulePassword(PK11SlotInfo *slot, PRBool retry, void *arg) +{ + char prompt[255]; + secuPWData *pwdata = (secuPWData *)arg; + secuPWData pwnull = { PW_NONE, 0 }; + secuPWData pwxtrn = { PW_EXTERNAL, "external" }; + char *pw; + + if (pwdata == NULL) + pwdata = &pwnull; + + if (PK11_ProtectedAuthenticationPath(slot)) { + pwdata = &pwxtrn; + } + if (retry && pwdata->source != PW_NONE) { + PR_fprintf(PR_STDERR, "Incorrect password/PIN entered.\n"); + return NULL; + } + + switch (pwdata->source) { + case PW_NONE: + sprintf(prompt, "Enter Password or Pin for \"%s\":", + PK11_GetTokenName(slot)); + return GetPasswordString(NULL, prompt); + case PW_FROMFILE: + /* Instead of opening and closing the file every time, get the pw + * once, then keep it in memory (duh). + */ + pw = SECU_FilePasswd(slot, retry, pwdata->data); + pwdata->source = PW_PLAINTEXT; + pwdata->data = strdup(pw); + /* it's already been dup'ed */ + return pw; + case PW_EXTERNAL: + sprintf(prompt, + "Press Enter, then enter PIN for \"%s\" on external device.\n", + PK11_GetTokenName(slot)); + pw = GetPasswordString(NULL, prompt); + if (pw) { + memset(pw, 0, PORT_Strlen(pw)); + PORT_Free(pw); + } + /* Fall Through */ + case PW_PLAINTEXT: + return strdup(pwdata->data); + default: + break; + } + + PR_fprintf(PR_STDERR, "Password check failed: No password found.\n"); + return NULL; +} diff --git a/onlineupdate/source/libmar/sign/nss_secutil.h b/onlineupdate/source/libmar/sign/nss_secutil.h new file mode 100644 index 000000000..f599fcce5 --- /dev/null +++ b/onlineupdate/source/libmar/sign/nss_secutil.h @@ -0,0 +1,43 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* With the exception of GetPasswordString, this file was + copied from NSS's cmd/lib/secutil.h hg revision 8f011395145e */ + +#ifndef NSS_SECUTIL_H_ +#define NSS_SECUTIL_H_ + +#include "nss.h" +#include "pk11pub.h" +#include "cryptohi.h" +#include "hasht.h" +#include "cert.h" +#include "key.h" +#include <stdint.h> + +typedef struct +{ + enum + { + PW_NONE = 0, + PW_FROMFILE = 1, + PW_PLAINTEXT = 2, + PW_EXTERNAL = 3 + } source; + char *data; +} secuPWData; + +#if( defined(_WINDOWS) && !defined(_WIN32_WCE)) +#include <conio.h> +#include <io.h> +#define QUIET_FGETS quiet_fgets +static char * quiet_fgets (char *buf, int length, FILE *input); +#else +#define QUIET_FGETS fgets +#endif + +char * +SECU_GetModulePassword(PK11SlotInfo *slot, PRBool retry, void *arg); + +#endif diff --git a/onlineupdate/source/libmar/src/Makefile.in b/onlineupdate/source/libmar/src/Makefile.in new file mode 100644 index 000000000..1da582e5b --- /dev/null +++ b/onlineupdate/source/libmar/src/Makefile.in @@ -0,0 +1,13 @@ +# vim:set ts=8 sw=8 sts=8 noet: +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +# This makefile just builds support for reading archives. + +include $(topsrcdir)/config/rules.mk + +# The intermediate (.ii/.s) files for host and target can have the same name... +# disable parallel builds +.NOTPARALLEL: diff --git a/onlineupdate/source/libmar/src/mar_create.c b/onlineupdate/source/libmar/src/mar_create.c new file mode 100644 index 000000000..599e0a2a7 --- /dev/null +++ b/onlineupdate/source/libmar/src/mar_create.c @@ -0,0 +1,398 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <stdlib.h> +#include <string.h> +#include <onlineupdate/mar_private.h> +#include <onlineupdate/mar_cmdline.h> +#include <onlineupdate/mar.h> + +#ifdef _WIN32 +#include <winsock2.h> +#else +#include <netinet/in.h> +#include <unistd.h> +#endif + +struct MarItemStack { + void *head; + uint32_t size_used; + uint32_t size_allocated; + uint32_t last_offset; +}; + +/** + * Push a new item onto the stack of items. The stack is a single block + * of memory. + */ +static int mar_push(struct MarItemStack *stack, uint32_t length, uint32_t flags, + const char *name) { + int namelen; + uint32_t n_offset, n_length, n_flags; + uint32_t size; + char *data; + + namelen = strlen(name); + size = MAR_ITEM_SIZE(namelen); + + if (stack->size_allocated - stack->size_used < size) { + /* increase size of stack */ + size_t size_needed = ROUND_UP(stack->size_used + size, BLOCKSIZE); + stack->head = realloc(stack->head, size_needed); + if (!stack->head) + return -1; + stack->size_allocated = size_needed; + } + + data = (((char *) stack->head) + stack->size_used); + + n_offset = htonl(stack->last_offset); + n_length = htonl(length); + n_flags = htonl(flags); + + memcpy(data, &n_offset, sizeof(n_offset)); + data += sizeof(n_offset); + + memcpy(data, &n_length, sizeof(n_length)); + data += sizeof(n_length); + + memcpy(data, &n_flags, sizeof(n_flags)); + data += sizeof(n_flags); + + memcpy(data, name, namelen + 1); + + stack->size_used += size; + stack->last_offset += length; + return 0; +} + +static int mar_concat_file(FILE *fp, const char *path) { + FILE *in; + char buf[BLOCKSIZE]; + size_t len; + int rv = 0; + + in = fopen(path, "rb"); + if (!in) { + fprintf(stderr, "ERROR: could not open file in mar_concat_file()\n"); + perror(path); + return -1; + } + + while ((len = fread(buf, 1, BLOCKSIZE, in)) > 0) { + if (fwrite(buf, len, 1, fp) != 1) { + rv = -1; + break; + } + } + + fclose(in); + return rv; +} + +/** + * Writes out the product information block to the specified file. + * + * @param fp The opened MAR file being created. + * @param stack A pointer to the MAR item stack being used to create + * the MAR + * @param infoBlock The product info block to store in the file. + * @return 0 on success. +*/ +static int +mar_concat_product_info_block(FILE *fp, + struct MarItemStack *stack, + struct ProductInformationBlock *infoBlock) +{ + char buf[PIB_MAX_MAR_CHANNEL_ID_SIZE + PIB_MAX_PRODUCT_VERSION_SIZE]; + uint32_t additionalBlockID = 1, infoBlockSize, unused; + if (!fp || !infoBlock || + !infoBlock->MARChannelID || + !infoBlock->productVersion) { + return -1; + } + + /* The MAR channel name must be < 64 bytes per the spec */ + if (strlen(infoBlock->MARChannelID) > PIB_MAX_MAR_CHANNEL_ID_SIZE) { + return -1; + } + + /* The product version must be < 32 bytes per the spec */ + if (strlen(infoBlock->productVersion) > PIB_MAX_PRODUCT_VERSION_SIZE) { + return -1; + } + + /* Although we don't need the product information block size to include the + maximum MAR channel name and product version, we allocate the maximum + amount to make it easier to modify the MAR file for repurposing MAR files + to different MAR channels. + 2 is for the NULL terminators. */ + infoBlockSize = sizeof(infoBlockSize) + + sizeof(additionalBlockID) + + PIB_MAX_MAR_CHANNEL_ID_SIZE + + PIB_MAX_PRODUCT_VERSION_SIZE + 2; + if (stack) { + stack->last_offset += infoBlockSize; + } + + /* Write out the product info block size */ + infoBlockSize = htonl(infoBlockSize); + if (fwrite(&infoBlockSize, + sizeof(infoBlockSize), 1, fp) != 1) { + return -1; + } + infoBlockSize = ntohl(infoBlockSize); + + /* Write out the product info block ID */ + additionalBlockID = htonl(additionalBlockID); + if (fwrite(&additionalBlockID, + sizeof(additionalBlockID), 1, fp) != 1) { + return -1; + } + additionalBlockID = ntohl(additionalBlockID); + + /* Write out the channel name and NULL terminator */ + if (fwrite(infoBlock->MARChannelID, + strlen(infoBlock->MARChannelID) + 1, 1, fp) != 1) { + return -1; + } + + /* Write out the product version string and NULL terminator */ + if (fwrite(infoBlock->productVersion, + strlen(infoBlock->productVersion) + 1, 1, fp) != 1) { + return -1; + } + + /* Write out the rest of the block that is unused */ + unused = infoBlockSize - (sizeof(infoBlockSize) + + sizeof(additionalBlockID) + + strlen(infoBlock->MARChannelID) + + strlen(infoBlock->productVersion) + 2); + memset(buf, 0, sizeof(buf)); + if (fwrite(buf, unused, 1, fp) != 1) { + return -1; + } + return 0; +} + +/** + * Refreshes the product information block with the new information. + * The input MAR must not be signed or the function call will fail. + * + * @param path The path to the MAR file whose product info block + * should be refreshed. + * @param infoBlock Out parameter for where to store the result to + * @return 0 on success, -1 on failure +*/ +int +refresh_product_info_block(const char *path, + struct ProductInformationBlock *infoBlock) +{ + FILE *fp ; + int rv; + uint32_t numSignatures, additionalBlockSize, additionalBlockID, + offsetAdditionalBlocks, numAdditionalBlocks, i; + int additionalBlocks, hasSignatureBlock; + + rv = get_mar_file_info(path, + &hasSignatureBlock, + &numSignatures, + &additionalBlocks, + &offsetAdditionalBlocks, + &numAdditionalBlocks); + if (rv) { + fprintf(stderr, "ERROR: Could not obtain MAR information.\n"); + return -1; + } + + if (hasSignatureBlock && numSignatures) { + fprintf(stderr, "ERROR: Cannot refresh a signed MAR\n"); + return -1; + } + + fp = fopen(path, "r+b"); + if (!fp) { + fprintf(stderr, "ERROR: could not open target file: %s\n", path); + return -1; + } + + if (fseeko(fp, offsetAdditionalBlocks, SEEK_SET)) { + fprintf(stderr, "ERROR: could not seek to additional blocks\n"); + fclose(fp); + return -1; + } + + for (i = 0; i < numAdditionalBlocks; ++i) { + /* Get the position of the start of this block */ + int64_t oldPos = ftello(fp); + + /* Read the additional block size */ + if (fread(&additionalBlockSize, + sizeof(additionalBlockSize), + 1, fp) != 1) { + fclose(fp); + return -1; + } + additionalBlockSize = ntohl(additionalBlockSize); + + /* Read the additional block ID */ + if (fread(&additionalBlockID, + sizeof(additionalBlockID), + 1, fp) != 1) { + fclose(fp); + return -1; + } + additionalBlockID = ntohl(additionalBlockID); + + if (PRODUCT_INFO_BLOCK_ID == additionalBlockID) { + if (fseeko(fp, oldPos, SEEK_SET)) { + fprintf(stderr, "Could not seek back to Product Information Block\n"); + fclose(fp); + return -1; + } + + if (mar_concat_product_info_block(fp, NULL, infoBlock)) { + fprintf(stderr, "Could not concat Product Information Block\n"); + fclose(fp); + return -1; + } + + fclose(fp); + return 0; + } else { + /* This is not the additional block you're looking for. Move along. */ + if (fseek(fp, additionalBlockSize, SEEK_CUR)) { + fprintf(stderr, "ERROR: Could not seek past current block.\n"); + fclose(fp); + return -1; + } + } + } + + /* If we had a product info block we would have already returned */ + fclose(fp); + fprintf(stderr, "ERROR: Could not refresh because block does not exist\n"); + return -1; +} + +/** + * Create a MAR file from a set of files. + * @param dest The path to the file to create. This path must be + * compatible with fopen. + * @param numfiles The number of files to store in the archive. + * @param files The list of null-terminated file paths. Each file + * path must be compatible with fopen. + * @param infoBlock The information to store in the product information block. + * @return A non-zero value if an error occurs. + */ +int mar_create(const char *dest, int + num_files, char **files, + struct ProductInformationBlock *infoBlock) { + struct MarItemStack stack; + uint32_t offset_to_index = 0, size_of_index, + numSignatures, numAdditionalSections; + uint64_t sizeOfEntireMAR = 0; + struct stat st; + FILE *fp; + int i, rv = -1; + + memset(&stack, 0, sizeof(stack)); + + fp = fopen(dest, "wb"); + if (!fp) { + fprintf(stderr, "ERROR: could not create target file: %s\n", dest); + return -1; + } + + if (fwrite(MAR_ID, MAR_ID_SIZE, 1, fp) != 1) + goto failure; + if (fwrite(&offset_to_index, sizeof(uint32_t), 1, fp) != 1) + goto failure; + + stack.last_offset = MAR_ID_SIZE + + sizeof(offset_to_index) + + sizeof(numSignatures) + + sizeof(numAdditionalSections) + + sizeof(sizeOfEntireMAR); + + /* We will circle back on this at the end of the MAR creation to fill it */ + if (fwrite(&sizeOfEntireMAR, sizeof(sizeOfEntireMAR), 1, fp) != 1) { + goto failure; + } + + /* Write out the number of signatures, for now only at most 1 is supported */ + numSignatures = 0; + if (fwrite(&numSignatures, sizeof(numSignatures), 1, fp) != 1) { + goto failure; + } + + /* Write out the number of additional sections, for now just 1 + for the product info block */ + numAdditionalSections = htonl(1); + if (fwrite(&numAdditionalSections, + sizeof(numAdditionalSections), 1, fp) != 1) { + goto failure; + } + numAdditionalSections = ntohl(numAdditionalSections); + + if (mar_concat_product_info_block(fp, &stack, infoBlock)) { + goto failure; + } + + for (i = 0; i < num_files; ++i) { + if (stat(files[i], &st)) { + fprintf(stderr, "ERROR: file not found: %s\n", files[i]); + goto failure; + } + + if (mar_push(&stack, st.st_size, st.st_mode & 0777, files[i])) + goto failure; + + /* concatenate input file to archive */ + if (mar_concat_file(fp, files[i])) + goto failure; + } + + /* write out the index (prefixed with length of index) */ + size_of_index = htonl(stack.size_used); + if (fwrite(&size_of_index, sizeof(size_of_index), 1, fp) != 1) + goto failure; + if (fwrite(stack.head, stack.size_used, 1, fp) != 1) + goto failure; + + /* To protect against invalid MAR files, we assume that the MAR file + size is less than or equal to MAX_SIZE_OF_MAR_FILE. */ + if (ftell(fp) > MAX_SIZE_OF_MAR_FILE) { + goto failure; + } + + /* write out offset to index file in network byte order */ + offset_to_index = htonl(stack.last_offset); + if (fseek(fp, MAR_ID_SIZE, SEEK_SET)) + goto failure; + if (fwrite(&offset_to_index, sizeof(offset_to_index), 1, fp) != 1) + goto failure; + offset_to_index = ntohl(stack.last_offset); + + sizeOfEntireMAR = ((uint64_t)stack.last_offset) + + stack.size_used + + sizeof(size_of_index); + sizeOfEntireMAR = HOST_TO_NETWORK64(sizeOfEntireMAR); + if (fwrite(&sizeOfEntireMAR, sizeof(sizeOfEntireMAR), 1, fp) != 1) + goto failure; + sizeOfEntireMAR = NETWORK_TO_HOST64(sizeOfEntireMAR); + + rv = 0; +failure: + if (stack.head) + free(stack.head); + fclose(fp); + if (rv) + remove(dest); + return rv; +} diff --git a/onlineupdate/source/libmar/src/mar_extract.c b/onlineupdate/source/libmar/src/mar_extract.c new file mode 100644 index 000000000..11e570242 --- /dev/null +++ b/onlineupdate/source/libmar/src/mar_extract.c @@ -0,0 +1,85 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <string.h> +#include <stdlib.h> +#include <onlineupdate/mar_private.h> +#include <onlineupdate/mar.h> + +#ifdef _WIN32 +#include <io.h> +#include <direct.h> +#endif + +/* Ensure that the directory containing this file exists */ +static int mar_ensure_parent_dir(const char *path) +{ + char *slash = strrchr(path, '/'); + if (slash) + { + *slash = '\0'; + mar_ensure_parent_dir(path); +#ifdef _WIN32 + _mkdir(path); +#else + mkdir(path, 0755); +#endif + *slash = '/'; + } + return 0; +} + +static int mar_test_callback(MarFile *mar, const MarItem *item, void *unused) { + FILE *fp; + char buf[BLOCKSIZE]; + int fd, len, offset = 0; + + (void) unused; // avoid warnings + + if (mar_ensure_parent_dir(item->name)) + return -1; + +#ifdef _WIN32 + fd = _open(item->name, _O_BINARY|_O_CREAT|_O_TRUNC|_O_WRONLY, item->flags); +#else + fd = creat(item->name, item->flags); +#endif + if (fd == -1) { + fprintf(stderr, "ERROR: could not create file in mar_test_callback()\n"); + perror(item->name); + return -1; + } + + fp = fdopen(fd, "wb"); + if (!fp) + return -1; + + while ((len = mar_read(mar, item, offset, buf, sizeof(buf))) > 0) { + if (fwrite(buf, len, 1, fp) != 1) + break; + offset += len; + } + + fclose(fp); + return len == 0 ? 0 : -1; +} + +int mar_extract(const char *path) { + MarFile *mar; + int rv; + + mar = mar_open(path); + if (!mar) + return -1; + + rv = mar_enum_items(mar, mar_test_callback, NULL); + + mar_close(mar); + return rv; +} diff --git a/onlineupdate/source/libmar/src/mar_read.c b/onlineupdate/source/libmar/src/mar_read.c new file mode 100644 index 000000000..2a6238ca2 --- /dev/null +++ b/onlineupdate/source/libmar/src/mar_read.c @@ -0,0 +1,572 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <sys/types.h> +#include <fcntl.h> +#include <stdlib.h> +#include <string.h> +#include <onlineupdate/mar_private.h> +#include <onlineupdate/mar.h> + +#ifdef _WIN32 +#include <winsock2.h> +#else +#include <netinet/in.h> +#endif + + +/* this is the same hash algorithm used by nsZipArchive.cpp */ +static uint32_t mar_hash_name(const char *name) { + uint32_t val = 0; + unsigned char* c; + + for (c = (unsigned char *) name; *c; ++c) + val = val*37 + *c; + + return val % TABLESIZE; +} + +static int mar_insert_item(MarFile *mar, const char *name, int namelen, + uint32_t offset, uint32_t length, uint32_t flags) { + MarItem *item, *root; + uint32_t hash; + + item = (MarItem *) malloc(sizeof(MarItem) + namelen); + if (!item) + return -1; + item->next = NULL; + item->offset = offset; + item->length = length; + item->flags = flags; + memcpy(item->name, name, namelen + 1); + + hash = mar_hash_name(name); + + root = mar->item_table[hash]; + if (!root) { + mar->item_table[hash] = item; + } else { + /* append item */ + while (root->next) + root = root->next; + root->next = item; + } + return 0; +} + +static int mar_consume_index(MarFile *mar, char **buf, const char *buf_end) { + /* + * Each item has the following structure: + * uint32_t offset (network byte order) + * uint32_t length (network byte order) + * uint32_t flags (network byte order) + * char name[N] (where N >= 1) + * char null_byte; + */ + uint32_t offset; + uint32_t length; + uint32_t flags; + const char *name; + int namelen; + + if ((buf_end - *buf) < (int)(3*sizeof(uint32_t) + 2)) + return -1; + + memcpy(&offset, *buf, sizeof(offset)); + *buf += sizeof(offset); + + memcpy(&length, *buf, sizeof(length)); + *buf += sizeof(length); + + memcpy(&flags, *buf, sizeof(flags)); + *buf += sizeof(flags); + + offset = ntohl(offset); + length = ntohl(length); + flags = ntohl(flags); + + name = *buf; + /* find namelen; must take care not to read beyond buf_end */ + while (**buf) { + if (*buf == buf_end) + return -1; + ++(*buf); + } + namelen = (*buf - name); + /* consume null byte */ + if (*buf == buf_end) + return -1; + ++(*buf); + + return mar_insert_item(mar, name, namelen, offset, length, flags); +} + +static int mar_read_index(MarFile *mar) { + char id[MAR_ID_SIZE], *buf, *bufptr, *bufend; + uint32_t offset_to_index, size_of_index; + + /* verify MAR ID */ + if (fread(id, MAR_ID_SIZE, 1, mar->fp) != 1) + return -1; + if (memcmp(id, MAR_ID, MAR_ID_SIZE) != 0) + return -1; + + if (fread(&offset_to_index, sizeof(uint32_t), 1, mar->fp) != 1) + return -1; + offset_to_index = ntohl(offset_to_index); + + if (fseek(mar->fp, offset_to_index, SEEK_SET)) + return -1; + if (fread(&size_of_index, sizeof(uint32_t), 1, mar->fp) != 1) + return -1; + size_of_index = ntohl(size_of_index); + + buf = (char *) malloc(size_of_index); + if (!buf) + return -1; + if (fread(buf, size_of_index, 1, mar->fp) != 1) { + free(buf); + return -1; + } + + bufptr = buf; + bufend = buf + size_of_index; + while (bufptr < bufend && mar_consume_index(mar, &bufptr, bufend) == 0); + + free(buf); + return (bufptr == bufend) ? 0 : -1; +} + +/** + * Internal shared code for mar_open and mar_wopen. + * On failure, will fclose(fp). + */ +static MarFile *mar_fpopen(FILE *fp) +{ + MarFile *mar; + + mar = (MarFile *) malloc(sizeof(*mar)); + if (!mar) { + fclose(fp); + return NULL; + } + + mar->fp = fp; + memset(mar->item_table, 0, sizeof(mar->item_table)); + if (mar_read_index(mar)) { + mar_close(mar); + return NULL; + } + + return mar; +} + +MarFile *mar_open(const char *path) { + FILE *fp; + + fp = fopen(path, "rb"); + if (!fp) { + fprintf(stderr, "ERROR: could not open file in mar_open()\n"); + perror(path); + return NULL; + } + + return mar_fpopen(fp); +} + +#ifdef _WIN32 +MarFile *mar_wopen(const wchar_t *path) { + FILE *fp; + + _wfopen_s(&fp, path, L"rb"); + if (!fp) { + fprintf(stderr, "ERROR: could not open file in mar_wopen()\n"); + _wperror(path); + return NULL; + } + + return mar_fpopen(fp); +} +#endif + +void mar_close(MarFile *mar) { + MarItem *item; + int i; + + fclose(mar->fp); + + for (i = 0; i < TABLESIZE; ++i) { + item = mar->item_table[i]; + while (item) { + MarItem *temp = item; + item = item->next; + free(temp); + } + } + + free(mar); +} + +/** + * Determines the MAR file information. + * + * @param fp An opened MAR file in read mode. + * @param hasSignatureBlock Optional out parameter specifying if the MAR + * file has a signature block or not. + * @param numSignatures Optional out parameter for storing the number + * of signatures in the MAR file. + * @param hasAdditionalBlocks Optional out parameter specifying if the MAR + * file has additional blocks or not. + * @param offsetAdditionalBlocks Optional out parameter for the offset to the + * first additional block. Value is only valid if + * hasAdditionalBlocks is not equal to 0. + * @param numAdditionalBlocks Optional out parameter for the number of + * additional blocks. Value is only valid if + * hasAdditionalBlocks is not equal to 0. + * @return 0 on success and non-zero on failure. + */ +int get_mar_file_info_fp(FILE *fp, + int *hasSignatureBlock, + uint32_t *numSignatures, + int *hasAdditionalBlocks, + uint32_t *offsetAdditionalBlocks, + uint32_t *numAdditionalBlocks) +{ + uint32_t offsetToIndex, offsetToContent, signatureCount, signatureLen, i; + + /* One of hasSignatureBlock or hasAdditionalBlocks must be non NULL */ + if (!hasSignatureBlock && !hasAdditionalBlocks) { + return -1; + } + + + /* Skip to the start of the offset index */ + if (fseek(fp, MAR_ID_SIZE, SEEK_SET)) { + return -1; + } + + /* Read the offset to the index. */ + if (fread(&offsetToIndex, sizeof(offsetToIndex), 1, fp) != 1) { + return -1; + } + offsetToIndex = ntohl(offsetToIndex); + + if (numSignatures) { + /* Skip past the MAR file size field */ + if (fseek(fp, sizeof(uint64_t), SEEK_CUR)) { + return -1; + } + + /* Read the number of signatures field */ + if (fread(numSignatures, sizeof(*numSignatures), 1, fp) != 1) { + return -1; + } + *numSignatures = ntohl(*numSignatures); + } + + /* Skip to the first index entry past the index size field + We do it in 2 calls because offsetToIndex + sizeof(uint32_t) + could overflow in theory. */ + if (fseek(fp, offsetToIndex, SEEK_SET)) { + return -1; + } + + if (fseek(fp, sizeof(uint32_t), SEEK_CUR)) { + return -1; + } + + /* Read the first offset to content field. */ + if (fread(&offsetToContent, sizeof(offsetToContent), 1, fp) != 1) { + return -1; + } + offsetToContent = ntohl(offsetToContent); + + /* Check if we have a new or old MAR file */ + if (hasSignatureBlock) { + if (offsetToContent == MAR_ID_SIZE + sizeof(uint32_t)) { + *hasSignatureBlock = 0; + } else { + *hasSignatureBlock = 1; + } + } + + /* If the caller doesn't care about the product info block + value, then just return */ + if (!hasAdditionalBlocks) { + return 0; + } + + /* Skip to the start of the signature block */ + if (fseeko(fp, SIGNATURE_BLOCK_OFFSET, SEEK_SET)) { + return -1; + } + + /* Get the number of signatures */ + if (fread(&signatureCount, sizeof(signatureCount), 1, fp) != 1) { + return -1; + } + signatureCount = ntohl(signatureCount); + + /* Check that we have less than the max amount of signatures so we don't + waste too much of either updater's or signmar's time. */ + if (signatureCount > MAX_SIGNATURES) { + return -1; + } + + /* Skip past the whole signature block */ + for (i = 0; i < signatureCount; i++) { + /* Skip past the signature algorithm ID */ + if (fseek(fp, sizeof(uint32_t), SEEK_CUR)) { + return -1; + } + + /* Read the signature length and skip past the signature */ + if (fread(&signatureLen, sizeof(uint32_t), 1, fp) != 1) { + return -1; + } + signatureLen = ntohl(signatureLen); + if (fseek(fp, signatureLen, SEEK_CUR)) { + return -1; + } + } + + if (ftell(fp) == (long)offsetToContent) { + *hasAdditionalBlocks = 0; + } else { + if (numAdditionalBlocks) { + /* We have an additional block, so read in the number of additional blocks + and set the offset. */ + *hasAdditionalBlocks = 1; + if (fread(numAdditionalBlocks, sizeof(uint32_t), 1, fp) != 1) { + return -1; + } + *numAdditionalBlocks = ntohl(*numAdditionalBlocks); + if (offsetAdditionalBlocks) { + *offsetAdditionalBlocks = ftell(fp); + } + } else if (offsetAdditionalBlocks) { + /* numAdditionalBlocks is not specified but offsetAdditionalBlocks + is, so fill it! */ + *offsetAdditionalBlocks = ftell(fp) + sizeof(uint32_t); + } + } + + return 0; +} + +/** + * Reads the product info block from the MAR file's additional block section. + * The caller is responsible for freeing the fields in infoBlock + * if the return is successful. + * + * @param infoBlock Out parameter for where to store the result to + * @return 0 on success, -1 on failure +*/ +int +read_product_info_block(char *path, + struct ProductInformationBlock *infoBlock) +{ + int rv; + MarFile mar; + mar.fp = fopen(path, "rb"); + if (!mar.fp) { + fprintf(stderr, "ERROR: could not open file in read_product_info_block()\n"); + perror(path); + return -1; + } + rv = mar_read_product_info_block(&mar, infoBlock); + fclose(mar.fp); + return rv; +} + +/** + * Reads the product info block from the MAR file's additional block section. + * The caller is responsible for freeing the fields in infoBlock + * if the return is successful. + * + * @param infoBlock Out parameter for where to store the result to + * @return 0 on success, -1 on failure +*/ +int +mar_read_product_info_block(MarFile *mar, + struct ProductInformationBlock *infoBlock) +{ + uint32_t i, offsetAdditionalBlocks, numAdditionalBlocks, + additionalBlockSize, additionalBlockID; + int hasAdditionalBlocks; + + /* The buffer size is 97 bytes because the MAR channel name < 64 bytes, and + product version < 32 bytes + 3 NULL terminator bytes. */ + char buf[97] = { '\0' }; + int ret = get_mar_file_info_fp(mar->fp, NULL, NULL, + &hasAdditionalBlocks, + &offsetAdditionalBlocks, + &numAdditionalBlocks); + if (ret) + return ret; + for (i = 0; i < numAdditionalBlocks; ++i) { + /* Read the additional block size */ + if (fread(&additionalBlockSize, + sizeof(additionalBlockSize), + 1, mar->fp) != 1) { + return -1; + } + additionalBlockSize = ntohl(additionalBlockSize) - + sizeof(additionalBlockSize) - + sizeof(additionalBlockID); + + /* Read the additional block ID */ + if (fread(&additionalBlockID, + sizeof(additionalBlockID), + 1, mar->fp) != 1) { + return -1; + } + additionalBlockID = ntohl(additionalBlockID); + + if (PRODUCT_INFO_BLOCK_ID == additionalBlockID) { + const char *location; + int len; + + /* This block must be at most 104 bytes. + MAR channel name < 64 bytes, and product version < 32 bytes + 3 NULL + terminator bytes. We only check for 96 though because we remove 8 + bytes above from the additionalBlockSize: We subtract + sizeof(additionalBlockSize) and sizeof(additionalBlockID) */ + if (additionalBlockSize > 96) { + return -1; + } + + if (fread(buf, additionalBlockSize, 1, mar->fp) != 1) { + return -1; + } + + /* Extract the MAR channel name from the buffer. For now we + point to the stack allocated buffer but we strdup this + if we are within bounds of each field's max length. */ + location = buf; + len = strlen(location); + infoBlock->MARChannelID = location; + location += len + 1; + if (len >= 64) { + infoBlock->MARChannelID = NULL; + return -1; + } + + /* Extract the version from the buffer */ + len = strlen(location); + infoBlock->productVersion = location; + location += len + 1; + if (len >= 32) { + infoBlock->MARChannelID = NULL; + infoBlock->productVersion = NULL; + return -1; + } + infoBlock->MARChannelID = + strdup(infoBlock->MARChannelID); + infoBlock->productVersion = + strdup(infoBlock->productVersion); + return 0; + } else { + /* This is not the additional block you're looking for. Move along. */ + if (fseek(mar->fp, additionalBlockSize, SEEK_CUR)) { + return -1; + } + } + } + + /* If we had a product info block we would have already returned */ + return -1; +} + +const MarItem *mar_find_item(MarFile *mar, const char *name) { + uint32_t hash; + const MarItem *item; + + hash = mar_hash_name(name); + + item = mar->item_table[hash]; + while (item && strcmp(item->name, name) != 0) + item = item->next; + + return item; +} + +int mar_enum_items(MarFile *mar, MarItemCallback callback, void *closure) { + MarItem *item; + int i; + + for (i = 0; i < TABLESIZE; ++i) { + item = mar->item_table[i]; + while (item) { + int rv = callback(mar, item, closure); + if (rv) + return rv; + item = item->next; + } + } + + return 0; +} + +int mar_read(MarFile *mar, const MarItem *item, int offset, char *buf, + int bufsize) { + int nr; + + if (offset == (int) item->length) + return 0; + if (offset > (int) item->length) + return -1; + + nr = item->length - offset; + if (nr > bufsize) + nr = bufsize; + + if (fseek(mar->fp, item->offset + offset, SEEK_SET)) + return -1; + + return fread(buf, 1, nr, mar->fp); +} + +/** + * Determines the MAR file information. + * + * @param path The path of the MAR file to check. + * @param hasSignatureBlock Optional out parameter specifying if the MAR + * file has a signature block or not. + * @param numSignatures Optional out parameter for storing the number + * of signatures in the MAR file. + * @param hasAdditionalBlocks Optional out parameter specifying if the MAR + * file has additional blocks or not. + * @param offsetAdditionalBlocks Optional out parameter for the offset to the + * first additional block. Value is only valid if + * hasAdditionalBlocks is not equal to 0. + * @param numAdditionalBlocks Optional out parameter for the number of + * additional blocks. Value is only valid if + * has_additional_blocks is not equal to 0. + * @return 0 on success and non-zero on failure. + */ +int get_mar_file_info(const char *path, + int *hasSignatureBlock, + uint32_t *numSignatures, + int *hasAdditionalBlocks, + uint32_t *offsetAdditionalBlocks, + uint32_t *numAdditionalBlocks) +{ + int rv; + FILE *fp = fopen(path, "rb"); + if (!fp) { + fprintf(stderr, "ERROR: could not open file in get_mar_file_info()\n"); + perror(path); + return -1; + } + + rv = get_mar_file_info_fp(fp, hasSignatureBlock, + numSignatures, hasAdditionalBlocks, + offsetAdditionalBlocks, numAdditionalBlocks); + + fclose(fp); + return rv; +} diff --git a/onlineupdate/source/libmar/tool/Makefile.in b/onlineupdate/source/libmar/tool/Makefile.in new file mode 100644 index 000000000..20a7c475a --- /dev/null +++ b/onlineupdate/source/libmar/tool/Makefile.in @@ -0,0 +1,23 @@ +# vim:set ts=8 sw=8 sts=8 noet: +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +# The mar executable is output into dist/host/bin since it is something that +# would only be used by our build system and should not itself be included in a +# Mozilla distribution. + +HOST_CFLAGS += \ + -DNO_SIGN_VERIFY \ + $(DEFINES) \ + $(NULL) + +include $(topsrcdir)/config/rules.mk + +ifdef CROSS_COMPILE +ifdef HOST_NSPR_MDCPUCFG +HOST_CFLAGS += -DMDCPUCFG=$(HOST_NSPR_MDCPUCFG) +CFLAGS += -DMDCPUCFG=$(HOST_NSPR_MDCPUCFG) +endif +endif diff --git a/onlineupdate/source/libmar/tool/mar.c b/onlineupdate/source/libmar/tool/mar.c new file mode 100644 index 000000000..3db3bb86e --- /dev/null +++ b/onlineupdate/source/libmar/tool/mar.c @@ -0,0 +1,463 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <onlineupdate/mar.h> +#include <onlineupdate/mar_cmdline.h> + +#ifdef _WIN32 +#include <windows.h> +#include <direct.h> +#define chdir _chdir +#else +#include <unistd.h> +#include <errno.h> +#endif + +#ifndef APP_VERSION +#error "Missing APP_VERSION" +#endif + +#define MAR_CHANNEL_ID "LOOnlineUpdater" /* Dummy value; replace or + remove in the future */ + +#if !defined(NO_SIGN_VERIFY) && (!defined(_WIN32) || defined(MAR_NSS)) +#include "cert.h" +#include "pk11pub.h" +int NSSInitCryptoContext(const char *NSSConfigDir); +#endif + +int mar_repackage_and_sign(const char *NSSConfigDir, + const char * const *certNames, + uint32_t certCount, + const char *src, + const char * dest); + +static void print_version(void) { + printf("Version: %s\n", APP_VERSION); + printf("Default Channel ID: %s\n", MAR_CHANNEL_ID); +} + +static void print_usage(void) { + printf("usage:\n"); + printf("Create a MAR file:\n"); + printf(" mar [-H MARChannelID] [-V ProductVersion] [-C workingDir] " + "-c archive.mar [files...]\n"); + printf(" mar [-H MARChannelID] [-V ProductVersion] [-C workingDir] " + "-c archive.mar -f input_file.txt\n"); + + printf("Extract a MAR file:\n"); + printf(" mar [-C workingDir] -x archive.mar\n"); +#ifndef NO_SIGN_VERIFY + printf("Sign a MAR file:\n"); + printf(" mar [-C workingDir] -d NSSConfigDir -n certname -s " + "archive.mar out_signed_archive.mar\n"); + + printf("Strip a MAR signature:\n"); + printf(" mar [-C workingDir] -r " + "signed_input_archive.mar output_archive.mar\n"); + + printf("Extract a MAR signature:\n"); + printf(" mar [-C workingDir] -n(i) -X " + "signed_input_archive.mar base_64_encoded_signature_file\n"); + + printf("Import a MAR signature:\n"); + printf(" mar [-C workingDir] -n(i) -I " + "signed_input_archive.mar base_64_encoded_signature_file " + "changed_signed_output.mar\n"); + printf("(i) is the index of the certificate to extract\n"); +#if defined(MACOSX) || (defined(_WIN32) && !defined(MAR_NSS)) + printf("Verify a MAR file:\n"); + printf(" mar [-C workingDir] -D DERFilePath -v signed_archive.mar\n"); + printf("At most %d signature certificate DER files are specified by " + "-D0 DERFilePath1 -D1 DERFilePath2, ...\n", MAX_SIGNATURES); +#else + printf("Verify a MAR file:\n"); + printf(" mar [-C workingDir] -d NSSConfigDir -n certname " + "-v signed_archive.mar\n"); + printf("At most %d signature certificate names are specified by " + "-n0 certName -n1 certName2, ...\n", MAX_SIGNATURES); +#endif + printf("At most %d verification certificate names are specified by " + "-n0 certName -n1 certName2, ...\n", MAX_SIGNATURES); +#endif + printf("Print information on a MAR file:\n"); + printf(" mar -t archive.mar\n"); + + printf("Print detailed information on a MAR file including signatures:\n"); + printf(" mar -T archive.mar\n"); + + printf("Refresh the product information block of a MAR file:\n"); + printf(" mar [-H MARChannelID] [-V ProductVersion] [-C workingDir] " + "-i unsigned_archive_to_refresh.mar\n"); + + printf("Print executable version:\n"); + printf(" mar --version\n"); + printf("This program does not handle unicode file paths properly\n"); +} + +static int mar_test_callback(MarFile *mar, + const MarItem *item, + void *unused) { + (void) mar; (void) unused; // avoid warnings + + printf("%u\t0%o\t%s\n", item->length, item->flags, item->name); + return 0; +} + +static int mar_test(const char *path) { + MarFile *mar; + + mar = mar_open(path); + if (!mar) + return -1; + + printf("SIZE\tMODE\tNAME\n"); + mar_enum_items(mar, mar_test_callback, NULL); + + mar_close(mar); + return 0; +} + +int main(int argc, char **argv) { + char *NSSConfigDir = NULL; + const char *certNames[MAX_SIGNATURES]; + char *MARChannelID = MAR_CHANNEL_ID; + char *productVersion = APP_VERSION; +#ifndef NO_SIGN_VERIFY + uint32_t k; +#endif + int rv = -1; + uint32_t certCount = 0; + int32_t sigIndex = -1; + +#if !defined(NO_SIGN_VERIFY) + uint32_t fileSizes[MAX_SIGNATURES]; + const uint8_t* certBuffers[MAX_SIGNATURES]; + char* DERFilePaths[MAX_SIGNATURES]; +#if (!defined(_WIN32) && !defined(MACOSX)) || defined(MAR_NSS) + CERTCertificate* certs[MAX_SIGNATURES]; +#endif +#endif + + memset((void*)certNames, 0, sizeof(certNames)); +#if defined(_WIN32) && !defined(MAR_NSS) && !defined(NO_SIGN_VERIFY) + memset((void*)certBuffers, 0, sizeof(certBuffers)); +#endif +#if !defined(NO_SIGN_VERIFY) && ((!defined(MAR_NSS) && defined(_WIN32)) || \ + defined(MACOSX)) + memset(DERFilePaths, 0, sizeof(DERFilePaths)); + memset(fileSizes, 0, sizeof(fileSizes)); +#endif + + if (argc > 1 && 0 == strcmp(argv[1], "--version")) { + print_version(); + return 0; + } + + if (argc < 3) { + print_usage(); + return -1; + } + + while (argc > 0) { + if (argv[1][0] == '-' && (argv[1][1] == 'c' || + argv[1][1] == 't' || argv[1][1] == 'x' || + argv[1][1] == 'v' || argv[1][1] == 's' || + argv[1][1] == 'i' || argv[1][1] == 'T' || + argv[1][1] == 'r' || argv[1][1] == 'X' || + argv[1][1] == 'I')) { + break; + /* -C workingdirectory */ + } else if (argv[1][0] == '-' && argv[1][1] == 'C') { + chdir(argv[2]); + argv += 2; + argc -= 2; + } +#if !defined(NO_SIGN_VERIFY) && ((!defined(MAR_NSS) && defined(_WIN32)) || \ + defined(MACOSX)) + /* -D DERFilePath, also matches -D[index] DERFilePath + We allow an index for verifying to be symmetric + with the import and export command line arguments. */ + else if (argv[1][0] == '-' && + argv[1][1] == 'D' && + (argv[1][2] == (char)('0' + certCount) || argv[1][2] == '\0')) { + if (certCount >= MAX_SIGNATURES) { + print_usage(); + return -1; + } + DERFilePaths[certCount++] = argv[2]; + argv += 2; + argc -= 2; + } +#endif + /* -d NSSConfigdir */ + else if (argv[1][0] == '-' && argv[1][1] == 'd') { + NSSConfigDir = argv[2]; + argv += 2; + argc -= 2; + /* -n certName, also matches -n[index] certName + We allow an index for verifying to be symmetric + with the import and export command line arguments. */ + } else if (argv[1][0] == '-' && + argv[1][1] == 'n' && + (argv[1][2] == (char)('0' + certCount) || + argv[1][2] == '\0' || + !strcmp(argv[2], "-X") || + !strcmp(argv[2], "-I"))) { + if (certCount >= MAX_SIGNATURES) { + print_usage(); + return -1; + } + certNames[certCount++] = argv[2]; + if (strlen(argv[1]) > 2 && + (!strcmp(argv[2], "-X") || !strcmp(argv[2], "-I")) && + argv[1][2] >= '0' && argv[1][2] <= '9') { + sigIndex = argv[1][2] - '0'; + argv++; + argc--; + } else { + argv += 2; + argc -= 2; + } + /* MAR channel ID */ + } else if (argv[1][0] == '-' && argv[1][1] == 'H') { + MARChannelID = argv[2]; + argv += 2; + argc -= 2; + /* Product Version */ + } else if (argv[1][0] == '-' && argv[1][1] == 'V') { + productVersion = argv[2]; + argv += 2; + argc -= 2; + } + else { + print_usage(); + return -1; + } + } + + if (argv[1][0] != '-') { + print_usage(); + return -1; + } + + switch (argv[1][1]) { + case 'c': { + struct ProductInformationBlock infoBlock; + infoBlock.MARChannelID = MARChannelID; + infoBlock.productVersion = productVersion; + if (argv[argc - 2][0] == '-' && argv[argc - 2][1] == 'f') + { + char buf[1000]; + FILE* file; + char** files; + int num_files = 0; + + files = (char **)malloc(sizeof(char*)*10000); + errno = 0; + file = fopen(argv[argc - 1], "r"); + if (!file) + { + printf("%d %s", errno, strerror(errno)); + printf("Could not open file: %s", argv[argc - 1]); + exit(1); + } + + while(fgets(buf, 1000, file) != NULL) + { + int j; + size_t str_len; + for (j=strlen(buf)-1;j>=0 && (buf[j]=='\n' || buf[j]=='\r');j--) + ; + buf[j+1]='\0'; + str_len = strlen(buf) + 1; + files[num_files] = (char*)malloc(sizeof(char)*str_len); + strcpy(files[num_files], buf); + ++num_files; + } + fclose(file); + return mar_create(argv[2], num_files, files, &infoBlock); + } + else + return mar_create(argv[2], argc - 3, argv + 3, &infoBlock); + } + case 'i': { + struct ProductInformationBlock infoBlock; + infoBlock.MARChannelID = MARChannelID; + infoBlock.productVersion = productVersion; + return refresh_product_info_block(argv[2], &infoBlock); + } + case 'T': { + struct ProductInformationBlock infoBlock; + uint32_t numSignatures, numAdditionalBlocks; + int hasSignatureBlock, hasAdditionalBlock; + if (!get_mar_file_info(argv[2], + &hasSignatureBlock, + &numSignatures, + &hasAdditionalBlock, + NULL, &numAdditionalBlocks)) { + if (hasSignatureBlock) { + printf("Signature block found with %d signature%s\n", + numSignatures, + numSignatures != 1 ? "s" : ""); + } + if (hasAdditionalBlock) { + printf("%d additional block%s found:\n", + numAdditionalBlocks, + numAdditionalBlocks != 1 ? "s" : ""); + } + + rv = read_product_info_block(argv[2], &infoBlock); + if (!rv) { + printf(" - Product Information Block:\n"); + printf(" - MAR channel name: %s\n" + " - Product version: %s\n", + infoBlock.MARChannelID, + infoBlock.productVersion); + free((void *)infoBlock.MARChannelID); + free((void *)infoBlock.productVersion); + } + } + printf("\n"); + /* The fall through from 'T' to 't' is intentional */ + } + /* Fall through */ + case 't': + return mar_test(argv[2]); + + /* Extract a MAR file */ + case 'x': + return mar_extract(argv[2]); + +#ifndef NO_SIGN_VERIFY + /* Extract a MAR signature */ + case 'X': + if (sigIndex == -1) { + fprintf(stderr, "ERROR: Signature index was not passed.\n"); + return -1; + } + if (sigIndex >= MAX_SIGNATURES || sigIndex < -1) { + fprintf(stderr, "ERROR: Signature index is out of range: %d.\n", + sigIndex); + return -1; + } + return extract_signature(argv[2], sigIndex, argv[3]); + + /* Import a MAR signature */ + case 'I': + if (sigIndex == -1) { + fprintf(stderr, "ERROR: signature index was not passed.\n"); + return -1; + } + if (sigIndex >= MAX_SIGNATURES || sigIndex < -1) { + fprintf(stderr, "ERROR: Signature index is out of range: %d.\n", + sigIndex); + return -1; + } + if (argc < 5) { + print_usage(); + return -1; + } + return import_signature(argv[2], sigIndex, argv[3], argv[4]); + + case 'v': + if (certCount == 0) { + print_usage(); + return -1; + } + +#if (!defined(_WIN32) && !defined(MACOSX)) || defined(MAR_NSS) + if (!NSSConfigDir || certCount == 0) { + print_usage(); + return -1; + } + + if (NSSInitCryptoContext(NSSConfigDir)) { + fprintf(stderr, "ERROR: Could not initialize crypto library.\n"); + return -1; + } +#endif + + rv = 0; + for (k = 0; k < certCount; ++k) { +#if (defined(_WIN32) || defined(MACOSX)) && !defined(MAR_NSS) + rv = mar_read_entire_file(DERFilePaths[k], MAR_MAX_CERT_SIZE, + &certBuffers[k], &fileSizes[k]); +#else + /* It is somewhat circuitous to look up a CERTCertificate and then pass + * in its DER encoding just so we can later re-create that + * CERTCertificate to extract the public key out of it. However, by doing + * things this way, we maximize the reuse of the mar_verify_signatures + * function and also we keep the control flow as similar as possible + * between programs and operating systems, at least for the functions + * that are critically important to security. + */ + certs[k] = PK11_FindCertFromNickname(certNames[k], NULL); + if (certs[k]) { + certBuffers[k] = certs[k]->derCert.data; + fileSizes[k] = certs[k]->derCert.len; + } else { + rv = -1; + } +#endif + if (rv) { + fprintf(stderr, "ERROR: could not read file %s", DERFilePaths[k]); + break; + } + } + + if (!rv) { + MarFile *mar = mar_open(argv[2]); + if (mar) { + rv = mar_verify_signatures(mar, certBuffers, fileSizes, certCount); + mar_close(mar); + } else { + fprintf(stderr, "ERROR: Could not open MAR file.\n"); + rv = -1; + } + } + for (k = 0; k < certCount; ++k) { +#if (defined(_WIN32) || defined(MACOSX)) && !defined(MAR_NSS) + free((void*)certBuffers[k]); +#else + /* certBuffers[k] is owned by certs[k] so don't free it */ + CERT_DestroyCertificate(certs[k]); +#endif + } + if (rv) { + /* Determine if the source MAR file has the new fields for signing */ + int hasSignatureBlock; + if (get_mar_file_info(argv[2], &hasSignatureBlock, + NULL, NULL, NULL, NULL)) { + fprintf(stderr, "ERROR: could not determine if MAR is old or new.\n"); + } else if (!hasSignatureBlock) { + fprintf(stderr, "ERROR: The MAR file is in the old format so has" + " no signature to verify.\n"); + } + return -1; + } + return 0; + + case 's': + if (!NSSConfigDir || certCount == 0 || argc < 4) { + print_usage(); + return -1; + } + return mar_repackage_and_sign(NSSConfigDir, certNames, certCount, + argv[2], argv[3]); + + case 'r': + return strip_signature_block(argv[2], argv[3]); +#endif /* endif NO_SIGN_VERIFY disabled */ + + default: + print_usage(); + return -1; + } +} diff --git a/onlineupdate/source/libmar/verify/MacVerifyCrypto.cpp b/onlineupdate/source/libmar/verify/MacVerifyCrypto.cpp new file mode 100644 index 000000000..e86fac3c5 --- /dev/null +++ b/onlineupdate/source/libmar/verify/MacVerifyCrypto.cpp @@ -0,0 +1,414 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <CoreFoundation/CoreFoundation.h> +#include <Security/Security.h> +#include <dlfcn.h> + +#include "cryptox.h" + +// We declare the necessary parts of the Security Transforms API here since +// we're building with the 10.6 SDK, which doesn't know about Security +// Transforms. +extern "C" { + const CFStringRef kSecTransformInputAttributeName = CFSTR("INPUT"); + typedef CFTypeRef SecTransformRef; + typedef struct OpaqueSecKeyRef* SecKeyRef; + + typedef SecTransformRef (*SecTransformCreateReadTransformWithReadStreamFunc) + (CFReadStreamRef inputStream); + SecTransformCreateReadTransformWithReadStreamFunc + SecTransformCreateReadTransformWithReadStreamPtr = NULL; + typedef CFTypeRef (*SecTransformExecuteFunc)(SecTransformRef transform, + CFErrorRef* error); + SecTransformExecuteFunc SecTransformExecutePtr = NULL; + typedef SecTransformRef (*SecVerifyTransformCreateFunc)(SecKeyRef key, + CFDataRef signature, + CFErrorRef* error); + SecVerifyTransformCreateFunc SecVerifyTransformCreatePtr = NULL; + typedef Boolean (*SecTransformSetAttributeFunc)(SecTransformRef transform, + CFStringRef key, + CFTypeRef value, + CFErrorRef* error); + SecTransformSetAttributeFunc SecTransformSetAttributePtr = NULL; +} + +#define MAC_OS_X_VERSION_10_7_HEX 0x00001070 + +static int sOnLionOrLater = -1; + +static bool OnLionOrLater() +{ + if (sOnLionOrLater < 0) { + SInt32 major = 0, minor = 0; + + CFURLRef url = + CFURLCreateWithString(kCFAllocatorDefault, + CFSTR("file:///System/Library/CoreServices/SystemVersion.plist"), + NULL); + CFReadStreamRef stream = + CFReadStreamCreateWithFile(kCFAllocatorDefault, url); + CFReadStreamOpen(stream); + CFDictionaryRef sysVersionPlist = (CFDictionaryRef) + CFPropertyListCreateWithStream(kCFAllocatorDefault, + stream, 0, kCFPropertyListImmutable, + NULL, NULL); + CFReadStreamClose(stream); + CFRelease(stream); + CFRelease(url); + + CFStringRef versionString = (CFStringRef) + CFDictionaryGetValue(sysVersionPlist, CFSTR("ProductVersion")); + CFArrayRef versions = + CFStringCreateArrayBySeparatingStrings(kCFAllocatorDefault, + versionString, CFSTR(".")); + CFIndex count = CFArrayGetCount(versions); + if (count > 0) { + CFStringRef component = (CFStringRef) CFArrayGetValueAtIndex(versions, 0); + major = CFStringGetIntValue(component); + if (count > 1) { + component = (CFStringRef) CFArrayGetValueAtIndex(versions, 1); + minor = CFStringGetIntValue(component); + } + } + CFRelease(sysVersionPlist); + CFRelease(versions); + + if (major < 10) { + sOnLionOrLater = 0; + } else { + int version = 0x1000 + (minor << 4); + sOnLionOrLater = version >= MAC_OS_X_VERSION_10_7_HEX ? 1 : 0; + } + } + + return sOnLionOrLater > 0 ? true : false; +} + +static bool sCssmInitialized = false; +static CSSM_VERSION sCssmVersion = {2, 0}; +static const CSSM_GUID sMozCssmGuid = + { 0x9243121f, 0x5820, 0x4b41, + { 0xa6, 0x52, 0xba, 0xb6, 0x3f, 0x9d, 0x3d, 0x7f }}; +static CSSM_CSP_HANDLE sCspHandle = CSSM_INVALID_HANDLE; + +void* cssmMalloc (CSSM_SIZE aSize, void* aAllocRef) { + (void)aAllocRef; + return malloc(aSize); +} + +void cssmFree (void* aPtr, void* aAllocRef) { + (void)aAllocRef; + free(aPtr); + return; +} + +void* cssmRealloc (void* aPtr, CSSM_SIZE aSize, void* aAllocRef) { + (void)aAllocRef; + return realloc(aPtr, aSize); +} + +void* cssmCalloc (uint32 aNum, CSSM_SIZE aSize, void* aAllocRef) { + (void)aAllocRef; + return calloc(aNum, aSize); +} + +static CSSM_API_MEMORY_FUNCS cssmMemFuncs = { + &cssmMalloc, + &cssmFree, + &cssmRealloc, + &cssmCalloc, + NULL + }; + +CryptoX_Result +CryptoMac_InitCryptoProvider() +{ + if (!OnLionOrLater()) { + return CryptoX_Success; + } + + if (!SecTransformCreateReadTransformWithReadStreamPtr) { + SecTransformCreateReadTransformWithReadStreamPtr = + (SecTransformCreateReadTransformWithReadStreamFunc) + dlsym(RTLD_DEFAULT, "SecTransformCreateReadTransformWithReadStream"); + } + if (!SecTransformExecutePtr) { + SecTransformExecutePtr = (SecTransformExecuteFunc) + dlsym(RTLD_DEFAULT, "SecTransformExecute"); + } + if (!SecVerifyTransformCreatePtr) { + SecVerifyTransformCreatePtr = (SecVerifyTransformCreateFunc) + dlsym(RTLD_DEFAULT, "SecVerifyTransformCreate"); + } + if (!SecTransformSetAttributePtr) { + SecTransformSetAttributePtr = (SecTransformSetAttributeFunc) + dlsym(RTLD_DEFAULT, "SecTransformSetAttribute"); + } + if (!SecTransformCreateReadTransformWithReadStreamPtr || + !SecTransformExecutePtr || + !SecVerifyTransformCreatePtr || + !SecTransformSetAttributePtr) { + return CryptoX_Error; + } + return CryptoX_Success; +} + +CryptoX_Result +CryptoMac_VerifyBegin(CryptoX_SignatureHandle* aInputData) +{ + if (!aInputData) { + return CryptoX_Error; + } + + void* inputData = CFDataCreateMutable(kCFAllocatorDefault, 0); + if (!inputData) { + return CryptoX_Error; + } + + if (!OnLionOrLater()) { + CSSM_DATA_PTR cssmData = (CSSM_DATA_PTR)malloc(sizeof(CSSM_DATA)); + if (!cssmData) { + CFRelease(inputData); + return CryptoX_Error; + } + cssmData->Data = (uint8*)inputData; + cssmData->Length = 0; + *aInputData = cssmData; + return CryptoX_Success; + } + + *aInputData = inputData; + return CryptoX_Success; +} + +CryptoX_Result +CryptoMac_VerifyUpdate(CryptoX_SignatureHandle* aInputData, void* aBuf, + unsigned int aLen) +{ + if (aLen == 0) { + return CryptoX_Success; + } + if (!aInputData || !*aInputData) { + return CryptoX_Error; + } + + CFMutableDataRef inputData; + if (!OnLionOrLater()) { + inputData = (CFMutableDataRef)((CSSM_DATA_PTR)*aInputData)->Data; + ((CSSM_DATA_PTR)*aInputData)->Length += aLen; + } else { + inputData = (CFMutableDataRef)*aInputData; + } + + CFDataAppendBytes(inputData, (const uint8*)aBuf, aLen); + return CryptoX_Success; +} + +CryptoX_Result +CryptoMac_LoadPublicKey(const unsigned char* aCertData, + unsigned int aDataSize, + CryptoX_PublicKey* aPublicKey) +{ + if (!aCertData || aDataSize == 0 || !aPublicKey) { + return CryptoX_Error; + } + *aPublicKey = NULL; + + if (!OnLionOrLater()) { + if (!sCspHandle) { + CSSM_RETURN rv; + if (!sCssmInitialized) { + CSSM_PVC_MODE pvcPolicy = CSSM_PVC_NONE; + rv = CSSM_Init(&sCssmVersion, + CSSM_PRIVILEGE_SCOPE_PROCESS, + &sMozCssmGuid, + CSSM_KEY_HIERARCHY_NONE, + &pvcPolicy, + NULL); + if (rv != CSSM_OK) { + return CryptoX_Error; + } + sCssmInitialized = true; + } + + rv = CSSM_ModuleLoad(&gGuidAppleCSP, + CSSM_KEY_HIERARCHY_NONE, + NULL, + NULL); + if (rv != CSSM_OK) { + return CryptoX_Error; + } + + CSSM_CSP_HANDLE cspHandle; + rv = CSSM_ModuleAttach(&gGuidAppleCSP, + &sCssmVersion, + &cssmMemFuncs, + 0, + CSSM_SERVICE_CSP, + 0, + CSSM_KEY_HIERARCHY_NONE, + NULL, + 0, + NULL, + &cspHandle); + if (rv != CSSM_OK) { + return CryptoX_Error; + } + sCspHandle = cspHandle; + } + } + + CFDataRef certData = CFDataCreate(kCFAllocatorDefault, + aCertData, + aDataSize); + if (!certData) { + return CryptoX_Error; + } + + SecCertificateRef cert = SecCertificateCreateWithData(kCFAllocatorDefault, + certData); + CFRelease(certData); + if (!cert) { + return CryptoX_Error; + } + + OSStatus status = SecCertificateCopyPublicKey(cert, + (SecKeyRef*)aPublicKey); + CFRelease(cert); + if (status != 0) { + return CryptoX_Error; + } + + return CryptoX_Success; +} + +CryptoX_Result +CryptoMac_VerifySignature(CryptoX_SignatureHandle* aInputData, + CryptoX_PublicKey* aPublicKey, + const unsigned char* aSignature, + unsigned int aSignatureLen) +{ + if (!aInputData || !*aInputData || !aPublicKey || !*aPublicKey || + !aSignature || aSignatureLen == 0) { + return CryptoX_Error; + } + + if (!OnLionOrLater()) { + if (!sCspHandle) { + return CryptoX_Error; + } + + CSSM_KEY* publicKey; + OSStatus status = SecKeyGetCSSMKey((SecKeyRef)*aPublicKey, + (const CSSM_KEY**)&publicKey); + if (status) { + return CryptoX_Error; + } + + CSSM_CC_HANDLE ccHandle; + if (CSSM_CSP_CreateSignatureContext(sCspHandle, + CSSM_ALGID_SHA1WithRSA, + NULL, + publicKey, + &ccHandle) != CSSM_OK) { + return CryptoX_Error; + } + + CryptoX_Result result = CryptoX_Error; + CSSM_DATA signatureData; + signatureData.Data = (uint8*)aSignature; + signatureData.Length = aSignatureLen; + CSSM_DATA inputData; + inputData.Data = + CFDataGetMutableBytePtr((CFMutableDataRef) + (((CSSM_DATA_PTR)*aInputData)->Data)); + inputData.Length = ((CSSM_DATA_PTR)*aInputData)->Length; + if (CSSM_VerifyData(ccHandle, + &inputData, + 1, + CSSM_ALGID_NONE, + &signatureData) == CSSM_OK) { + result = CryptoX_Success; + } + return result; + } + + CFDataRef signatureData = CFDataCreate(kCFAllocatorDefault, + aSignature, aSignatureLen); + if (!signatureData) { + return CryptoX_Error; + } + + CFErrorRef error; + SecTransformRef verifier = + SecVerifyTransformCreatePtr((SecKeyRef)*aPublicKey, + signatureData, + &error); + if (!verifier || error) { + CFRelease(signatureData); + return CryptoX_Error; + } + + SecTransformSetAttributePtr(verifier, + kSecTransformInputAttributeName, + (CFDataRef)*aInputData, + &error); + if (error) { + CFRelease(signatureData); + CFRelease(verifier); + return CryptoX_Error; + } + + CryptoX_Result result = CryptoX_Error; + CFTypeRef rv = SecTransformExecutePtr(verifier, &error); + if (error) { + CFRelease(signatureData); + CFRelease(verifier); + return CryptoX_Error; + } + + if (CFGetTypeID(rv) == CFBooleanGetTypeID() && + CFBooleanGetValue((CFBooleanRef)rv) == true) { + result = CryptoX_Success; + } + + CFRelease(signatureData); + CFRelease(verifier); + + return result; +} + +void +CryptoMac_FreeSignatureHandle(CryptoX_SignatureHandle* aInputData) +{ + if (!aInputData || !*aInputData) { + return; + } + + CFMutableDataRef inputData = NULL; + if (OnLionOrLater()) { + inputData = (CFMutableDataRef)*aInputData; + } else { + inputData = (CFMutableDataRef)((CSSM_DATA_PTR)*aInputData)->Data; + } + + CFRelease(inputData); + if (!OnLionOrLater()) { + free((CSSM_DATA_PTR)*aInputData); + } +} + +void +CryptoMac_FreePublicKey(CryptoX_PublicKey* aPublicKey) +{ + if (!aPublicKey || !*aPublicKey) { + return; + } + if (!OnLionOrLater() && sCspHandle != CSSM_INVALID_HANDLE) { + CSSM_ModuleDetach(sCspHandle); + sCspHandle = CSSM_INVALID_HANDLE; + } + CFRelease((SecKeyRef)*aPublicKey); +} diff --git a/onlineupdate/source/libmar/verify/cryptox.c b/onlineupdate/source/libmar/verify/cryptox.c new file mode 100644 index 000000000..5cda191de --- /dev/null +++ b/onlineupdate/source/libmar/verify/cryptox.c @@ -0,0 +1,282 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifdef _WIN32 +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#endif + +#include <stdlib.h> +#include "cryptox.h" + +#ifdef _WIN32 +#pragma warning(push) +#pragma warning(disable: 4204) +#endif + +#if defined(MAR_NSS) + +/** + * Loads the public key for the specified cert name from the NSS store. + * + * @param certData The DER-encoded X509 certificate to extract the key from. + * @param certDataSize The size of certData. + * @param publicKey Out parameter for the public key to use. + * @return CryptoX_Success on success, CryptoX_Error on error. +*/ +CryptoX_Result +NSS_LoadPublicKey(const unsigned char *certData, unsigned int certDataSize, + SECKEYPublicKey **publicKey) +{ + CERTCertificate * cert; + SECItem certDataItem = { siBuffer, (unsigned char*) certData, certDataSize }; + + if (!certData || !publicKey) { + return CryptoX_Error; + } + + cert = CERT_NewTempCertificate(CERT_GetDefaultCertDB(), &certDataItem, NULL, + PR_FALSE, PR_TRUE); + /* Get the cert and embedded public key out of the database */ + if (!cert) { + return CryptoX_Error; + } + *publicKey = CERT_ExtractPublicKey(cert); + CERT_DestroyCertificate(cert); + + if (!*publicKey) { + return CryptoX_Error; + } + return CryptoX_Success; +} + +CryptoX_Result +NSS_VerifyBegin(VFYContext **ctx, + SECKEYPublicKey * const *publicKey) +{ + SECStatus status; + if (!ctx || !publicKey || !*publicKey) { + return CryptoX_Error; + } + + /* Check that the key length is large enough for our requirements */ + if ((SECKEY_PublicKeyStrength(*publicKey) * 8) < + XP_MIN_SIGNATURE_LEN_IN_BYTES) { + fprintf(stderr, "ERROR: Key length must be >= %d bytes\n", + XP_MIN_SIGNATURE_LEN_IN_BYTES); + return CryptoX_Error; + } + + *ctx = VFY_CreateContext(*publicKey, NULL, + SEC_OID_ISO_SHA1_WITH_RSA_SIGNATURE, NULL); + if (*ctx == NULL) { + return CryptoX_Error; + } + + status = VFY_Begin(*ctx); + return SECSuccess == status ? CryptoX_Success : CryptoX_Error; +} + +/** + * Verifies if a verify context matches the passed in signature. + * + * @param ctx The verify context that the signature should match. + * @param signature The signature to match. + * @param signatureLen The length of the signature. + * @return CryptoX_Success on success, CryptoX_Error on error. +*/ +CryptoX_Result +NSS_VerifySignature(VFYContext * const *ctx, + const unsigned char *signature, + unsigned int signatureLen) +{ + SECItem signedItem; + SECStatus status; + if (!ctx || !signature || !*ctx) { + return CryptoX_Error; + } + + signedItem.len = signatureLen; + signedItem.data = (unsigned char*)signature; + status = VFY_EndWithSignature(*ctx, &signedItem); + return SECSuccess == status ? CryptoX_Success : CryptoX_Error; +} + +#elif defined(WNT) +/** + * Verifies if a signature + public key matches a hash context. + * + * @param hash The hash context that the signature should match. + * @param pubKey The public key to use on the signature. + * @param signature The signature to check. + * @param signatureLen The length of the signature. + * @return CryptoX_Success on success, CryptoX_Error on error. +*/ +CryptoX_Result +CyprtoAPI_VerifySignature(HCRYPTHASH *hash, + HCRYPTKEY *pubKey, + const BYTE *signature, + DWORD signatureLen) +{ + DWORD i; + BOOL result; +/* Windows APIs expect the bytes in the signature to be in little-endian + * order, but we write the signature in big-endian order. Other APIs like + * NSS and OpenSSL expect big-endian order. + */ + BYTE *signatureReversed; + if (!hash || !pubKey || !signature || signatureLen < 1) { + return CryptoX_Error; + } + + signatureReversed = malloc(signatureLen); + if (!signatureReversed) { + return CryptoX_Error; + } + + for (i = 0; i < signatureLen; i++) { + signatureReversed[i] = signature[signatureLen - 1 - i]; + } + result = CryptVerifySignature(*hash, signatureReversed, + signatureLen, *pubKey, NULL, 0); + free(signatureReversed); + return result ? CryptoX_Success : CryptoX_Error; +} + +/** + * Obtains the public key for the passed in cert data + * + * @param provider The crypto provider + * @param certData Data of the certificate to extract the public key from + * @param sizeOfCertData The size of the certData buffer + * @param certStore Pointer to the handle of the certificate store to use + * @param CryptoX_Success on success +*/ +CryptoX_Result +CryptoAPI_LoadPublicKey(HCRYPTPROV provider, + BYTE *certData, + DWORD sizeOfCertData, + HCRYPTKEY *publicKey) +{ + CRYPT_DATA_BLOB blob; + CERT_CONTEXT *context; + if (!provider || !certData || !publicKey) { + return CryptoX_Error; + } + + blob.cbData = sizeOfCertData; + blob.pbData = certData; + if (!CryptQueryObject(CERT_QUERY_OBJECT_BLOB, &blob, + CERT_QUERY_CONTENT_FLAG_CERT, + CERT_QUERY_FORMAT_FLAG_BINARY, + 0, NULL, NULL, NULL, + NULL, NULL, (const void **)&context)) { + return CryptoX_Error; + } + + if (!CryptImportPublicKeyInfo(provider, + PKCS_7_ASN_ENCODING | X509_ASN_ENCODING, + &context->pCertInfo->SubjectPublicKeyInfo, + publicKey)) { + CertFreeCertificateContext(context); + return CryptoX_Error; + } + + CertFreeCertificateContext(context); + return CryptoX_Success; +} + +/* Try to acquire context in this way: + * 1. Enhanced provider without creating a new key set + * 2. Enhanced provider with creating a new key set + * 3. Default provider without creating a new key set + * 4. Default provider without creating a new key set + * #2 and #4 should not be needed because of the CRYPT_VERIFYCONTEXT, + * but we add it just in case. + * + * @param provider Out parameter containing the provider handle. + * @return CryptoX_Success on success, CryptoX_Error on error. + */ +CryptoX_Result +CryptoAPI_InitCryptoContext(HCRYPTPROV *provider) +{ + if (!CryptAcquireContext(provider, + NULL, + MS_ENHANCED_PROV, + PROV_RSA_FULL, + CRYPT_VERIFYCONTEXT)) { + if (!CryptAcquireContext(provider, + NULL, + MS_ENHANCED_PROV, + PROV_RSA_FULL, + CRYPT_NEWKEYSET | CRYPT_VERIFYCONTEXT)) { + if (!CryptAcquireContext(provider, + NULL, + NULL, + PROV_RSA_FULL, + CRYPT_VERIFYCONTEXT)) { + if (!CryptAcquireContext(provider, + NULL, + NULL, + PROV_RSA_FULL, + CRYPT_NEWKEYSET | CRYPT_VERIFYCONTEXT)) { + *provider = CryptoX_InvalidHandleValue; + return CryptoX_Error; + } + } + } + } + return CryptoX_Success; +} + +/** + * Begins a signature verification hash context + * + * @param provider The crypt provider to use + * @param hash Out parameter for a handle to the hash context + * @return CryptoX_Success on success, CryptoX_Error on error. +*/ +CryptoX_Result +CryptoAPI_VerifyBegin(HCRYPTPROV provider, HCRYPTHASH* hash) +{ + BOOL result; + if (!provider || !hash) { + return CryptoX_Error; + } + + *hash = (HCRYPTHASH)NULL; + result = CryptCreateHash(provider, CALG_SHA1, + 0, 0, hash); + return result ? CryptoX_Success : CryptoX_Error; +} + +/** + * Updates a signature verification hash context + * + * @param hash The hash context to update + * @param buf The buffer to update the hash context with + * @param len The size of the passed in buffer + * @return CryptoX_Success on success, CryptoX_Error on error. +*/ +CryptoX_Result +CryptoAPI_VerifyUpdate(HCRYPTHASH* hash, BYTE *buf, DWORD len) +{ + BOOL result; + if (!hash || !buf) { + return CryptoX_Error; + } + + result = CryptHashData(*hash, buf, len, 0); + return result ? CryptoX_Success : CryptoX_Error; +} + +#ifdef _WIN32 +#pragma warning(pop) +#endif + +#endif + + + diff --git a/onlineupdate/source/libmar/verify/cryptox.h b/onlineupdate/source/libmar/verify/cryptox.h new file mode 100644 index 000000000..eb8548fa7 --- /dev/null +++ b/onlineupdate/source/libmar/verify/cryptox.h @@ -0,0 +1,172 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef CRYPTOX_H +#define CRYPTOX_H + +#define XP_MIN_SIGNATURE_LEN_IN_BYTES 256 + +#define CryptoX_Result int +#define CryptoX_Success 0 +#define CryptoX_Error (-1) +#define CryptoX_Succeeded(X) ((X) == CryptoX_Success) +#define CryptoX_Failed(X) ((X) != CryptoX_Success) + +#if defined(MAR_NSS) + +#include "cert.h" +#include "keyhi.h" +#include "cryptohi.h" + +#define CryptoX_InvalidHandleValue NULL +#define CryptoX_ProviderHandle void* +#define CryptoX_SignatureHandle VFYContext * +#define CryptoX_PublicKey SECKEYPublicKey * +#define CryptoX_Certificate CERTCertificate * + +#ifdef __cplusplus +extern "C" { +#endif +CryptoX_Result NSS_LoadPublicKey(const unsigned char* certData, + unsigned int certDataSize, + SECKEYPublicKey** publicKey); +CryptoX_Result NSS_VerifyBegin(VFYContext **ctx, + SECKEYPublicKey * const *publicKey); +CryptoX_Result NSS_VerifySignature(VFYContext * const *ctx , + const unsigned char *signature, + unsigned int signatureLen); +#ifdef __cplusplus +} // extern "C" +#endif + +#define CryptoX_InitCryptoProvider(CryptoHandle) \ + CryptoX_Success +#define CryptoX_VerifyBegin(CryptoHandle, SignatureHandle, PublicKey) \ + NSS_VerifyBegin(SignatureHandle, PublicKey) +#define CryptoX_FreeSignatureHandle(SignatureHandle) \ + VFY_DestroyContext(*SignatureHandle, PR_TRUE) +#define CryptoX_VerifyUpdate(SignatureHandle, buf, len) \ + VFY_Update(*SignatureHandle, (const unsigned char*)(buf), len) +#define CryptoX_LoadPublicKey(CryptoHandle, certData, dataSize, publicKey) \ + NSS_LoadPublicKey(certData, dataSize, publicKey) +#define CryptoX_VerifySignature(hash, publicKey, signedData, len) \ + NSS_VerifySignature(hash, (const unsigned char *)(signedData), len) +#define CryptoX_FreePublicKey(key) \ + SECKEY_DestroyPublicKey(*key) +#define CryptoX_FreeCertificate(cert) \ + CERT_DestroyCertificate(*cert) + +#elif defined(MACOSX) + +#define CryptoX_InvalidHandleValue NULL +#define CryptoX_ProviderHandle void* +#define CryptoX_SignatureHandle void* +#define CryptoX_PublicKey void* +#define CryptoX_Certificate void* + +// Forward-declare Objective-C functions implemented in MacVerifyCrypto.mm. +#ifdef __cplusplus +extern "C" { +#endif +CryptoX_Result CryptoMac_InitCryptoProvider(); +CryptoX_Result CryptoMac_VerifyBegin(CryptoX_SignatureHandle* aInputData); +CryptoX_Result CryptoMac_VerifyUpdate(CryptoX_SignatureHandle* aInputData, + void* aBuf, unsigned int aLen); +CryptoX_Result CryptoMac_LoadPublicKey(const unsigned char* aCertData, + unsigned int aDataSize, + CryptoX_PublicKey* aPublicKey); +CryptoX_Result CryptoMac_VerifySignature(CryptoX_SignatureHandle* aInputData, + CryptoX_PublicKey* aPublicKey, + const unsigned char* aSignature, + unsigned int aSignatureLen); +void CryptoMac_FreeSignatureHandle(CryptoX_SignatureHandle* aInputData); +void CryptoMac_FreePublicKey(CryptoX_PublicKey* aPublicKey); +#ifdef __cplusplus +} // extern "C" +#endif + +#define CryptoX_InitCryptoProvider(aProviderHandle) \ + CryptoMac_InitCryptoProvider() +#define CryptoX_VerifyBegin(aCryptoHandle, aInputData, aPublicKey) \ + CryptoMac_VerifyBegin(aInputData) +#define CryptoX_VerifyUpdate(aInputData, aBuf, aLen) \ + CryptoMac_VerifyUpdate(aInputData, aBuf, aLen) +#define CryptoX_LoadPublicKey(aProviderHandle, aCertData, aDataSize, \ + aPublicKey) \ + CryptoMac_LoadPublicKey(aCertData, aDataSize, aPublicKey) +#define CryptoX_VerifySignature(aInputData, aPublicKey, aSignature, \ + aSignatureLen) \ + CryptoMac_VerifySignature(aInputData, aPublicKey, aSignature, aSignatureLen) +#define CryptoX_FreeSignatureHandle(aInputData) \ + CryptoMac_FreeSignatureHandle(aInputData) +#define CryptoX_FreePublicKey(aPublicKey) \ + CryptoMac_FreePublicKey(aPublicKey) +#define CryptoX_FreeCertificate(aCertificate) + +#elif defined(WNT) + +#include <windows.h> +#include <wincrypt.h> + +CryptoX_Result CryptoAPI_InitCryptoContext(HCRYPTPROV *provider); +CryptoX_Result CryptoAPI_LoadPublicKey(HCRYPTPROV hProv, + BYTE *certData, + DWORD sizeOfCertData, + HCRYPTKEY *publicKey); +CryptoX_Result CryptoAPI_VerifyBegin(HCRYPTPROV provider, HCRYPTHASH* hash); +CryptoX_Result CryptoAPI_VerifyUpdate(HCRYPTHASH* hash, + BYTE *buf, DWORD len); +CryptoX_Result CyprtoAPI_VerifySignature(HCRYPTHASH *hash, + HCRYPTKEY *pubKey, + const BYTE *signature, + DWORD signatureLen); + +#define CryptoX_InvalidHandleValue ((ULONG_PTR)NULL) +#define CryptoX_ProviderHandle HCRYPTPROV +#define CryptoX_SignatureHandle HCRYPTHASH +#define CryptoX_PublicKey HCRYPTKEY +#define CryptoX_Certificate HCERTSTORE +#define CryptoX_InitCryptoProvider(CryptoHandle) \ + CryptoAPI_InitCryptoContext(CryptoHandle) +#define CryptoX_VerifyBegin(CryptoHandle, SignatureHandle, PublicKey) \ + CryptoAPI_VerifyBegin(CryptoHandle, SignatureHandle) +#define CryptoX_FreeSignatureHandle(SignatureHandle) +#define CryptoX_VerifyUpdate(SignatureHandle, buf, len) \ + CryptoAPI_VerifyUpdate(SignatureHandle, (BYTE *)(buf), len) +#define CryptoX_LoadPublicKey(CryptoHandle, certData, dataSize, publicKey) \ + CryptoAPI_LoadPublicKey(CryptoHandle, (BYTE*)(certData), dataSize, publicKey) +#define CryptoX_VerifySignature(hash, publicKey, signedData, len) \ + CyprtoAPI_VerifySignature(hash, publicKey, signedData, len) +#define CryptoX_FreePublicKey(key) \ + CryptDestroyKey(*(key)) +#define CryptoX_FreeCertificate(cert) \ + CertCloseStore(*(cert), CERT_CLOSE_STORE_FORCE_FLAG); + +#else + +/* This default implementation is necessary because we don't want to + * link to NSS from updater code on non Windows platforms. On Windows + * we use CryptoAPI instead of NSS. We don't call any function as they + * would just fail, but this simplifies linking. + */ + +#define CryptoX_InvalidHandleValue NULL +#define CryptoX_ProviderHandle void* +#define CryptoX_SignatureHandle void* +#define CryptoX_PublicKey void* +#define CryptoX_Certificate void* +#define CryptoX_InitCryptoProvider(CryptoHandle) \ + CryptoX_Error +#define CryptoX_VerifyBegin(CryptoHandle, SignatureHandle, PublicKey) \ + CryptoX_Error +#define CryptoX_FreeSignatureHandle(SignatureHandle) +#define CryptoX_VerifyUpdate(SignatureHandle, buf, len) CryptoX_Error +#define CryptoX_LoadPublicKey(CryptoHandle, certData, dataSize, publicKey) \ + CryptoX_Error +#define CryptoX_VerifySignature(hash, publicKey, signedData, len) CryptoX_Error +#define CryptoX_FreePublicKey(key) CryptoX_Error + +#endif + +#endif diff --git a/onlineupdate/source/libmar/verify/mar_verify.c b/onlineupdate/source/libmar/verify/mar_verify.c new file mode 100644 index 000000000..9f33f8bad --- /dev/null +++ b/onlineupdate/source/libmar/verify/mar_verify.c @@ -0,0 +1,466 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifdef _WIN32 +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#endif + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <onlineupdate/mar_private.h> +#include <onlineupdate/mar.h> +#include "cryptox.h" + +int +mar_read_entire_file(const char * filePath, uint32_t maxSize, + /*out*/ const uint8_t * *data, + /*out*/ uint32_t *size) +{ + int result; + FILE * f; + + if (!filePath || !data || !size) { + return -1; + } + + f = fopen(filePath, "rb"); + if (!f) { + return -1; + } + + result = -1; + if (!fseeko(f, 0, SEEK_END)) { + int64_t fileSize = ftello(f); + if (fileSize > 0 && fileSize <= maxSize && !fseeko(f, 0, SEEK_SET)) { + unsigned char * fileData; + + *size = (unsigned int) fileSize; + fileData = malloc(*size); + if (fileData) { + if (fread(fileData, *size, 1, f) == 1) { + *data = fileData; + result = 0; + } else { + free(fileData); + } + } + } + } + + fclose(f); + + return result; +} + +int mar_extract_and_verify_signatures_fp(FILE *fp, + CryptoX_ProviderHandle provider, + CryptoX_PublicKey *keys, + uint32_t keyCount); +int mar_verify_signatures_for_fp(FILE *fp, + CryptoX_ProviderHandle provider, + CryptoX_PublicKey *keys, + const uint8_t * const *extractedSignatures, + uint32_t keyCount, + uint32_t *numVerified); + +/** + * Reads the specified number of bytes from the file pointer and + * stores them in the passed buffer. + * + * @param fp The file pointer to read from. + * @param buffer The buffer to store the read results. + * @param size The number of bytes to read, buffer must be + * at least of this size. + * @param ctxs Pointer to the first element in an array of verify context. + * @param count The number of elements in ctxs + * @param err The name of what is being written to in case of error. + * @return 0 on success + * -1 on read error + * -2 on verify update error +*/ +int +ReadAndUpdateVerifyContext(FILE *fp, + void *buffer, + uint32_t size, + CryptoX_SignatureHandle *ctxs, + uint32_t count, + const char *err) +{ + uint32_t k; + if (!fp || !buffer || !ctxs || count == 0 || !err) { + fprintf(stderr, "ERROR: Invalid parameter specified.\n"); + return CryptoX_Error; + } + + if (!size) { + return CryptoX_Success; + } + + if (fread(buffer, size, 1, fp) != 1) { + fprintf(stderr, "ERROR: Could not read %s\n", err); + return CryptoX_Error; + } + + for (k = 0; k < count; k++) { + if (CryptoX_Failed(CryptoX_VerifyUpdate(&ctxs[k], buffer, size))) { + fprintf(stderr, "ERROR: Could not update verify context for %s\n", err); + return -2; + } + } + return CryptoX_Success; +} + +/** + * Verifies a MAR file by verifying each signature with the corresponding + * certificate. That is, the first signature will be verified using the first + * certificate given, the second signature will be verified using the second + * certificate given, etc. The signature count must exactly match the number of + * certificates given, and all signature verifications must succeed. + * + * @param mar The file who's signature should be calculated + * @param certData Pointer to the first element in an array of + * certificate data + * @param certDataSizes Pointer to the first element in an array for size of + * the data stored + * @param certCount The number of elements in certData and certDataSizes + * @return 0 on success +*/ +int +mar_verify_signatures(MarFile *mar, + const uint8_t * const *certData, + const uint32_t *certDataSizes, + uint32_t certCount) { + int rv = -1; + CryptoX_ProviderHandle provider = CryptoX_InvalidHandleValue; + CryptoX_PublicKey keys[MAX_SIGNATURES]; + uint32_t k; + + memset(keys, 0, sizeof(keys)); + + if (!mar || !certData || !certDataSizes || certCount == 0) { + fprintf(stderr, "ERROR: Invalid parameter specified.\n"); + goto failure; + } + + if (!mar->fp) { + fprintf(stderr, "ERROR: MAR file is not open.\n"); + goto failure; + } + + if (CryptoX_Failed(CryptoX_InitCryptoProvider(&provider))) { + fprintf(stderr, "ERROR: Could not init crypto library.\n"); + goto failure; + } + + for (k = 0; k < certCount; ++k) { + if (CryptoX_Failed(CryptoX_LoadPublicKey(provider, certData[k], certDataSizes[k], + &keys[k]))) { + fprintf(stderr, "ERROR: Could not load public key.\n"); + goto failure; + } + } + + rv = mar_extract_and_verify_signatures_fp(mar->fp, provider, keys, certCount); + +failure: + + for (k = 0; k < certCount; ++k) { + if (keys[k]) { + CryptoX_FreePublicKey(&keys[k]); + } + } + + return rv; +} + +/** + * Extracts each signature from the specified MAR file, + * then calls mar_verify_signatures_for_fp to verify each signature. + * + * @param fp An opened MAR file handle + * @param provider A library provider + * @param keys The public keys to use to verify the MAR + * @param keyCount The number of keys pointed to by keys + * @return 0 on success +*/ +int +mar_extract_and_verify_signatures_fp(FILE *fp, + CryptoX_ProviderHandle provider, + CryptoX_PublicKey *keys, + uint32_t keyCount) { + uint32_t signatureCount, signatureLen, numVerified = 0; + uint32_t signatureAlgorithmIDs[MAX_SIGNATURES]; + int rv = -1; + uint8_t *extractedSignatures[MAX_SIGNATURES]; + uint32_t i; + + memset(signatureAlgorithmIDs, 0, sizeof(signatureAlgorithmIDs)); + memset(extractedSignatures, 0, sizeof(extractedSignatures)); + + if (!fp) { + fprintf(stderr, "ERROR: Invalid file pointer passed.\n"); + return CryptoX_Error; + } + + /* To protect against invalid MAR files, we assume that the MAR file + size is less than or equal to MAX_SIZE_OF_MAR_FILE. */ + if (fseeko(fp, 0, SEEK_END)) { + fprintf(stderr, "ERROR: Could not seek to the end of the MAR file.\n"); + return CryptoX_Error; + } + if (ftello(fp) > MAX_SIZE_OF_MAR_FILE) { + fprintf(stderr, "ERROR: MAR file is too large to be verified.\n"); + return CryptoX_Error; + } + + /* Skip to the start of the signature block */ + if (fseeko(fp, SIGNATURE_BLOCK_OFFSET, SEEK_SET)) { + fprintf(stderr, "ERROR: Could not seek to the signature block.\n"); + return CryptoX_Error; + } + + /* Get the number of signatures */ + if (fread(&signatureCount, sizeof(signatureCount), 1, fp) != 1) { + fprintf(stderr, "ERROR: Could not read number of signatures.\n"); + return CryptoX_Error; + } + signatureCount = ntohl(signatureCount); + + /* Check that we have less than the max amount of signatures so we don't + waste too much of either updater's or signmar's time. */ + if (signatureCount > MAX_SIGNATURES) { + fprintf(stderr, "ERROR: At most %d signatures can be specified.\n", + MAX_SIGNATURES); + return CryptoX_Error; + } + + for (i = 0; i < signatureCount; i++) { + /* Get the signature algorithm ID */ + if (fread(&signatureAlgorithmIDs[i], sizeof(uint32_t), 1, fp) != 1) { + fprintf(stderr, "ERROR: Could not read signatures algorithm ID.\n"); + return CryptoX_Error; + } + signatureAlgorithmIDs[i] = ntohl(signatureAlgorithmIDs[i]); + + if (fread(&signatureLen, sizeof(uint32_t), 1, fp) != 1) { + fprintf(stderr, "ERROR: Could not read signatures length.\n"); + return CryptoX_Error; + } + signatureLen = ntohl(signatureLen); + + /* To protected against invalid input make sure the signature length + isn't too big. */ + if (signatureLen > MAX_SIGNATURE_LENGTH) { + fprintf(stderr, "ERROR: Signature length is too large to verify.\n"); + return CryptoX_Error; + } + + extractedSignatures[i] = malloc(signatureLen); + if (!extractedSignatures[i]) { + fprintf(stderr, "ERROR: Could allocate buffer for signature.\n"); + return CryptoX_Error; + } + if (fread(extractedSignatures[i], signatureLen, 1, fp) != 1) { + fprintf(stderr, "ERROR: Could not read extracted signature.\n"); + for (i = 0; i < signatureCount; ++i) { + free(extractedSignatures[i]); + } + return CryptoX_Error; + } + + /* We don't try to verify signatures we don't know about */ + if (signatureAlgorithmIDs[i] != 1) { + fprintf(stderr, "ERROR: Unknown signature algorithm ID.\n"); + for (i = 0; i < signatureCount; ++i) { + free(extractedSignatures[i]); + } + return CryptoX_Error; + } + } + + rv = mar_verify_signatures_for_fp(fp, + provider, + keys, + (const uint8_t * const *)extractedSignatures, + signatureCount, + &numVerified); + for (i = 0; i < signatureCount; ++i) { + free(extractedSignatures[i]); + } + + /* If we reached here and we verified every + signature, return success. */ + if (numVerified == signatureCount && keyCount == numVerified) { + assert(rv == 0); (void) rv; + return CryptoX_Success; + } + + if (numVerified == 0) { + fprintf(stderr, "ERROR: Not all signatures were verified.\n"); + } else { + fprintf(stderr, "ERROR: Only %d of %d signatures were verified.\n", + numVerified, signatureCount); + } + return CryptoX_Error; +} + +/** + * Verifies a MAR file by verifying each signature with the corresponding + * certificate. That is, the first signature will be verified using the first + * certificate given, the second signature will be verified using the second + * certificate given, etc. The signature count must exactly match the number of + * certificates given, and all signature verifications must succeed. + * + * @param fp An opened MAR file handle + * @param provider A library provider + * @param keys A pointer to the first element in an + * array of keys. + * @param extractedSignatures Pointer to the first element in an array + * of extracted signatures. + * @param signatureCount The number of signatures in the MAR file + * @param numVerified Out parameter which will be filled with + * the number of verified signatures. + * This information can be useful for printing + * error messages. + * @return 0 on success, *numVerified == signatureCount. +*/ +int +mar_verify_signatures_for_fp(FILE *fp, + CryptoX_ProviderHandle provider, + CryptoX_PublicKey *keys, + const uint8_t * const *extractedSignatures, + uint32_t signatureCount, + uint32_t *numVerified) +{ + CryptoX_SignatureHandle signatureHandles[MAX_SIGNATURES]; + char buf[BLOCKSIZE]; + uint32_t signatureLengths[MAX_SIGNATURES]; + uint32_t i; + int rv = CryptoX_Error; + + (void) provider; (void) keys; // avoid warnings + + memset(signatureHandles, 0, sizeof(signatureHandles)); + memset(signatureLengths, 0, sizeof(signatureLengths)); + + if (!extractedSignatures || !numVerified) { + fprintf(stderr, "ERROR: Invalid parameter specified.\n"); + goto failure; + } + + *numVerified = 0; + + /* This function is only called when we have at least one signature, + but to protected against future people who call this function we + make sure a non zero value is passed in. + */ + if (!signatureCount) { + fprintf(stderr, "ERROR: There must be at least one signature.\n"); + goto failure; + } + + for (i = 0; i < signatureCount; i++) { + if (CryptoX_Failed(CryptoX_VerifyBegin(provider, + &signatureHandles[i], &keys[i]))) { + fprintf(stderr, "ERROR: Could not initialize signature handle.\n"); + goto failure; + } + } + + /* Skip to the start of the file */ + if (fseeko(fp, 0, SEEK_SET)) { + fprintf(stderr, "ERROR: Could not seek to start of the file\n"); + goto failure; + } + + /* Bytes 0-3: MAR1 + Bytes 4-7: index offset + Bytes 8-15: size of entire MAR + */ + if (CryptoX_Failed(ReadAndUpdateVerifyContext(fp, buf, + SIGNATURE_BLOCK_OFFSET + + sizeof(uint32_t), + signatureHandles, + signatureCount, + "signature block"))) { + goto failure; + } + + /* Read the signature block */ + for (i = 0; i < signatureCount; i++) { + /* Get the signature algorithm ID */ + if (CryptoX_Failed(ReadAndUpdateVerifyContext(fp, + &buf, + sizeof(uint32_t), + signatureHandles, + signatureCount, + "signature algorithm ID"))) { + goto failure; + } + + if (CryptoX_Failed(ReadAndUpdateVerifyContext(fp, + &signatureLengths[i], + sizeof(uint32_t), + signatureHandles, + signatureCount, + "signature length"))) { + goto failure; + } + signatureLengths[i] = ntohl(signatureLengths[i]); + if (signatureLengths[i] > MAX_SIGNATURE_LENGTH) { + fprintf(stderr, "ERROR: Embedded signature length is too large.\n"); + goto failure; + } + + /* Skip past the signature itself as those are not included */ + if (fseeko(fp, signatureLengths[i], SEEK_CUR)) { + fprintf(stderr, "ERROR: Could not seek past signature.\n"); + goto failure; + } + } + + /* Read the rest of the file after the signature block */ + while (!feof(fp)) { + int numRead = fread(buf, 1, BLOCKSIZE , fp); + if (ferror(fp)) { + fprintf(stderr, "ERROR: Error reading data block.\n"); + goto failure; + } + + for (i = 0; i < signatureCount; i++) { + if (CryptoX_Failed(CryptoX_VerifyUpdate(&signatureHandles[i], + buf, numRead))) { + fprintf(stderr, "ERROR: Error updating verify context with" + " data block.\n"); + goto failure; + } + } + } + + /* Verify the signatures */ + for (i = 0; i < signatureCount; i++) { + if (CryptoX_Failed(CryptoX_VerifySignature(&signatureHandles[i], + &keys[i], + extractedSignatures[i], + signatureLengths[i]))) { + fprintf(stderr, "ERROR: Error verifying signature.\n"); + goto failure; + } + ++*numVerified; + } + + rv = CryptoX_Success; +failure: + for (i = 0; i < signatureCount; i++) { + CryptoX_FreeSignatureHandle(&signatureHandles[i]); + } + + return rv; +} diff --git a/onlineupdate/source/mbsdiff/bsdiff.cxx b/onlineupdate/source/mbsdiff/bsdiff.cxx new file mode 100644 index 000000000..72bbcde18 --- /dev/null +++ b/onlineupdate/source/mbsdiff/bsdiff.cxx @@ -0,0 +1,405 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + bsdiff.c -- Binary patch generator. + + Copyright 2003 Colin Percival + + For the terms under which this work may be distributed, please see + the adjoining file "LICENSE". + + ChangeLog: + 2005-05-05 - Use the modified header struct from bspatch.h; use 32-bit + values throughout. + --Benjamin Smedberg <benjamin@smedbergs.us> + 2005-05-18 - Use the same CRC algorithm as bzip2, and leverage the CRC table + provided by libbz2. + --Darin Fisher <darin@meer.net> +*/ + +#include "bspatch.h" + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <fcntl.h> +#include <stdarg.h> +#ifdef _WIN32 +#include <io.h> +#include <winsock2.h> +#else +#include <unistd.h> +#include <arpa/inet.h> +#define _O_BINARY 0 +#endif + +#undef MIN +#define MIN(x,y) (((x)<(y)) ? (x) : (y)) + +/*---------------------------------------------------------------------------*/ + +/* This variable lives in libbz2. It's declared in bzlib_private.h, so we just + * declare it here to avoid including that entire header file. + */ +extern "C" unsigned int BZ2_crc32Table[256]; + +static unsigned int +crc32(const unsigned char *buf, unsigned int len) +{ + unsigned int crc = 0xffffffffL; + + const unsigned char *end = buf + len; + for (; buf != end; ++buf) + crc = (crc << 8) ^ BZ2_crc32Table[(crc >> 24) ^ *buf]; + + crc = ~crc; + return crc; +} + +/*---------------------------------------------------------------------------*/ + +static void +reporterr(int e, const char *fmt, ...) +{ + if (fmt) { + va_list args; + va_start(args, fmt); + vfprintf(stderr, fmt, args); + va_end(args); + } + + exit(e); +} + +static void +split(int32_t *I,int32_t *V,int32_t start,int32_t len,int32_t h) +{ + int32_t i,j,k,x,tmp,jj,kk; + + if(len<16) { + for(k=start;k<start+len;k+=j) { + j=1;x=V[I[k]+h]; + for(i=1;k+i<start+len;i++) { + if(V[I[k+i]+h]<x) { + x=V[I[k+i]+h]; + j=0; + }; + if(V[I[k+i]+h]==x) { + tmp=I[k+j];I[k+j]=I[k+i];I[k+i]=tmp; + j++; + }; + }; + for(i=0;i<j;i++) V[I[k+i]]=k+j-1; + if(j==1) I[k]=-1; + }; + return; + }; + + x=V[I[start+len/2]+h]; + jj=0;kk=0; + for(i=start;i<start+len;i++) { + if(V[I[i]+h]<x) jj++; + if(V[I[i]+h]==x) kk++; + }; + jj+=start;kk+=jj; + + i=start;j=0;k=0; + while(i<jj) { + if(V[I[i]+h]<x) { + i++; + } else if(V[I[i]+h]==x) { + tmp=I[i];I[i]=I[jj+j];I[jj+j]=tmp; + j++; + } else { + tmp=I[i];I[i]=I[kk+k];I[kk+k]=tmp; + k++; + }; + }; + + while(jj+j<kk) { + if(V[I[jj+j]+h]==x) { + j++; + } else { + tmp=I[jj+j];I[jj+j]=I[kk+k];I[kk+k]=tmp; + k++; + }; + }; + + if(jj>start) split(I,V,start,jj-start,h); + + for(i=0;i<kk-jj;i++) V[I[jj+i]]=kk-1; + if(jj==kk-1) I[jj]=-1; + + if(start+len>kk) split(I,V,kk,start+len-kk,h); +} + +static void +qsufsort(int32_t *I,int32_t *V,unsigned char *old,int32_t oldsize) +{ + int32_t buckets[256]; + int32_t i,h,len; + + for(i=0;i<256;i++) buckets[i]=0; + for(i=0;i<oldsize;i++) buckets[old[i]]++; + for(i=1;i<256;i++) buckets[i]+=buckets[i-1]; + for(i=255;i>0;i--) buckets[i]=buckets[i-1]; + buckets[0]=0; + + for(i=0;i<oldsize;i++) I[++buckets[old[i]]]=i; + I[0]=oldsize; + for(i=0;i<oldsize;i++) V[i]=buckets[old[i]]; + V[oldsize]=0; + for(i=1;i<256;i++) if(buckets[i]==buckets[i-1]+1) I[buckets[i]]=-1; + I[0]=-1; + + for(h=1;I[0]!=-(oldsize+1);h+=h) { + len=0; + for(i=0;i<oldsize+1;) { + if(I[i]<0) { + len-=I[i]; + i-=I[i]; + } else { + if(len) I[i-len]=-len; + len=V[I[i]]+1-i; + split(I,V,i,len,h); + i+=len; + len=0; + }; + }; + if(len) I[i-len]=-len; + }; + + for(i=0;i<oldsize+1;i++) I[V[i]]=i; +} + +static int32_t +matchlen(unsigned char *old,int32_t oldsize,unsigned char *newbuf,int32_t newsize) +{ + int32_t i; + + for(i=0;(i<oldsize)&&(i<newsize);i++) + if(old[i]!=newbuf[i]) break; + + return i; +} + +static int32_t +search(int32_t *I,unsigned char *old,int32_t oldsize, + unsigned char *newbuf,int32_t newsize,int32_t st,int32_t en,int32_t *pos) +{ + int32_t x,y; + + if(en-st<2) { + x=matchlen(old+I[st],oldsize-I[st],newbuf,newsize); + y=matchlen(old+I[en],oldsize-I[en],newbuf,newsize); + + if(x>y) { + *pos=I[st]; + return x; + } else { + *pos=I[en]; + return y; + } + }; + + x=st+(en-st)/2; + if(memcmp(old+I[x],newbuf,MIN(oldsize-I[x],newsize))<0) { + return search(I,old,oldsize,newbuf,newsize,x,en,pos); + } else { + return search(I,old,oldsize,newbuf,newsize,st,x,pos); + }; +} + +int main(int argc,char *argv[]) +{ + int fd; + unsigned char *old = nullptr,*newbuf = nullptr; + int32_t oldsize = 0, newsize = 0; + int32_t *I = nullptr,*V = nullptr; + + int32_t scan,pos = 0,len; + int32_t lastscan,lastpos,lastoffset; + int32_t oldscore,scsc; + + int32_t s,Sf,lenf,Sb,lenb; + int32_t overlap,Ss,lens; + int32_t i; + + int32_t dblen,eblen; + unsigned char *db,*eb = nullptr; + + unsigned int scrc; + + MBSPatchHeader header = { + {'M','B','D','I','F','F','1','0'}, + 0, 0, 0, 0, 0, 0 + }; + + uint32_t numtriples; + + if(argc!=4) + reporterr(1,"usage: %s <oldfile> <newfile> <patchfile>\n",argv[0]); + + /* Allocate oldsize+1 bytes instead of oldsize bytes to ensure + that we never try to malloc(0) and get a NULL pointer */ + if(((fd=open(argv[1],O_RDONLY|_O_BINARY,0))<0) || + ((oldsize=lseek(fd,0,SEEK_END))==-1) || + ((old=(unsigned char*) malloc(oldsize+1))==NULL) || + (lseek(fd,0,SEEK_SET)!=0) || + (read(fd,old,oldsize)!=oldsize) || + (close(fd)==-1)) + reporterr(1,"%s\n",argv[1]); + + scrc = crc32(old, oldsize); + + if(((I=(int32_t*) malloc((oldsize+1)*sizeof(int32_t)))==NULL) || + ((V=(int32_t*) malloc((oldsize+1)*sizeof(int32_t)))==NULL)) + reporterr(1,NULL); + + qsufsort(I,V,old,oldsize); + + free(V); + + /* Allocate newsize+1 bytes instead of newsize bytes to ensure + that we never try to malloc(0) and get a NULL pointer */ + if(((fd=open(argv[2],O_RDONLY|_O_BINARY,0))<0) || + ((newsize=lseek(fd,0,SEEK_END))==-1) || + ((newbuf=(unsigned char*) malloc(newsize+1))==NULL) || + (lseek(fd,0,SEEK_SET)!=0) || + (read(fd,newbuf,newsize)!=newsize) || + (close(fd)==-1)) reporterr(1,"%s\n",argv[2]); + + if(((db=(unsigned char*) malloc(newsize+1))==NULL) || + ((eb=(unsigned char*) malloc(newsize+1))==NULL)) + reporterr(1,NULL); + + dblen=0; + eblen=0; + + if((fd=open(argv[3],O_CREAT|O_TRUNC|O_WRONLY|_O_BINARY,0666))<0) + reporterr(1,"%s\n",argv[3]); + + /* start writing here */ + + /* We don't know the lengths yet, so we will write the header again + at the end */ + + if(write(fd,&header,sizeof(MBSPatchHeader))!=sizeof(MBSPatchHeader)) + reporterr(1,"%s\n",argv[3]); + + scan=0;len=0; + lastscan=0;lastpos=0;lastoffset=0; + numtriples = 0; + while(scan<newsize) { + oldscore=0; + + for(scsc=scan+=len;scan<newsize;scan++) { + len=search(I,old,oldsize,newbuf+scan,newsize-scan, + 0,oldsize,&pos); + + for(;scsc<scan+len;scsc++) + if((scsc+lastoffset<oldsize) && + (old[scsc+lastoffset] == newbuf[scsc])) + oldscore++; + + if(((len==oldscore) && (len!=0)) || + (len>oldscore+8)) break; + + if((scan+lastoffset<oldsize) && + (old[scan+lastoffset] == newbuf[scan])) + oldscore--; + }; + + if((len!=oldscore) || (scan==newsize)) { + MBSPatchTriple triple; + + s=0;Sf=0;lenf=0; + for(i=0;(lastscan+i<scan)&&(lastpos+i<oldsize);) { + if(old[lastpos+i]==newbuf[lastscan+i]) s++; + i++; + if(s*2-i>Sf*2-lenf) { Sf=s; lenf=i; }; + }; + + lenb=0; + if(scan<newsize) { + s=0;Sb=0; + for(i=1;(scan>=lastscan+i)&&(pos>=i);i++) { + if(old[pos-i]==newbuf[scan-i]) s++; + if(s*2-i>Sb*2-lenb) { Sb=s; lenb=i; }; + }; + }; + + if(lastscan+lenf>scan-lenb) { + overlap=(lastscan+lenf)-(scan-lenb); + s=0;Ss=0;lens=0; + for(i=0;i<overlap;i++) { + if(newbuf[lastscan+lenf-overlap+i]== + old[lastpos+lenf-overlap+i]) s++; + if(newbuf[scan-lenb+i]== + old[pos-lenb+i]) s--; + if(s>Ss) { Ss=s; lens=i+1; }; + }; + + lenf+=lens-overlap; + lenb-=lens; + }; + + for(i=0;i<lenf;i++) + db[dblen+i]=newbuf[lastscan+i]-old[lastpos+i]; + for(i=0;i<(scan-lenb)-(lastscan+lenf);i++) + eb[eblen+i]=newbuf[lastscan+lenf+i]; + + dblen+=lenf; + eblen+=(scan-lenb)-(lastscan+lenf); + + triple.x = htonl(lenf); + triple.y = htonl((scan-lenb)-(lastscan+lenf)); + triple.z = htonl((pos-lenb)-(lastpos+lenf)); + if (write(fd,&triple,sizeof(triple)) != sizeof(triple)) + reporterr(1,NULL); + +#ifdef DEBUG_bsmedberg + printf("Writing a block:\n" + " X: %u\n" + " Y: %u\n" + " Z: %i\n", + (uint32_t) lenf, + (uint32_t) ((scan-lenb)-(lastscan+lenf)), + (uint32_t) ((pos-lenb)-(lastpos+lenf))); +#endif + + ++numtriples; + + lastscan=scan-lenb; + lastpos=pos-lenb; + lastoffset=pos-scan; + }; + }; + + if(write(fd,db,dblen)!=dblen) + reporterr(1,NULL); + + if(write(fd,eb,eblen)!=eblen) + reporterr(1,NULL); + + header.slen = htonl(oldsize); + header.scrc32 = htonl(scrc); + header.dlen = htonl(newsize); + header.cblen = htonl(numtriples * sizeof(MBSPatchTriple)); + header.difflen = htonl(dblen); + header.extralen = htonl(eblen); + + if (lseek(fd,0,SEEK_SET) == -1 || + write(fd,&header,sizeof(header)) != sizeof(header) || + close(fd) == -1) + reporterr(1,NULL); + + free(db); + free(eb); + free(I); + free(old); + free(newbuf); + + return 0; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/onlineupdate/source/service/certificatecheck.cxx b/onlineupdate/source/service/certificatecheck.cxx new file mode 100644 index 000000000..58d70162f --- /dev/null +++ b/onlineupdate/source/service/certificatecheck.cxx @@ -0,0 +1,292 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <stdio.h> +#include <stdlib.h> +#include <windows.h> +#include <softpub.h> +#include <wintrust.h> + +#include "certificatecheck.hxx" +#include "servicebase.hxx" + +#pragma comment(lib, "wintrust.lib") +#pragma comment(lib, "crypt32.lib") + +static const int ENCODING = X509_ASN_ENCODING | PKCS_7_ASN_ENCODING; + +/** + * Checks to see if a file stored at filePath matches the specified info. + * + * @param filePath The PE file path to check + * @param infoToMatch The acceptable information to match + * @return ERROR_SUCCESS if successful, ERROR_NOT_FOUND if the info + * does not match, or the last error otherwise. + */ +DWORD +CheckCertificateForPEFile(LPCWSTR filePath, + CertificateCheckInfo &infoToMatch) +{ + HCERTSTORE certStore = nullptr; + HCRYPTMSG cryptMsg = nullptr; + PCCERT_CONTEXT certContext = nullptr; + PCMSG_SIGNER_INFO signerInfo = nullptr; + DWORD lastError = ERROR_SUCCESS; + + // Get the HCERTSTORE and HCRYPTMSG from the signed file. + DWORD encoding, contentType, formatType; + BOOL result = CryptQueryObject(CERT_QUERY_OBJECT_FILE, + filePath, + CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED, + CERT_QUERY_CONTENT_FLAG_ALL, + 0, &encoding, &contentType, + &formatType, &certStore, &cryptMsg, nullptr); + if (!result) + { + lastError = GetLastError(); + LOG_WARN(("CryptQueryObject failed. (%d)", lastError)); + goto cleanup; + } + + // Pass in nullptr to get the needed signer information size. + DWORD signerInfoSize; + result = CryptMsgGetParam(cryptMsg, CMSG_SIGNER_INFO_PARAM, 0, + nullptr, &signerInfoSize); + if (!result) + { + lastError = GetLastError(); + LOG_WARN(("CryptMsgGetParam failed. (%d)", lastError)); + goto cleanup; + } + + // Allocate the needed size for the signer information. + signerInfo = (PCMSG_SIGNER_INFO)LocalAlloc(LPTR, signerInfoSize); + if (!signerInfo) + { + lastError = GetLastError(); + LOG_WARN(("Unable to allocate memory for Signer Info. (%d)", lastError)); + goto cleanup; + } + + // Get the signer information (PCMSG_SIGNER_INFO). + // In particular we want the issuer and serial number. + result = CryptMsgGetParam(cryptMsg, CMSG_SIGNER_INFO_PARAM, 0, + (PVOID)signerInfo, &signerInfoSize); + if (!result) + { + lastError = GetLastError(); + LOG_WARN(("CryptMsgGetParam failed. (%d)", lastError)); + goto cleanup; + } + + // Search for the signer certificate in the certificate store. + CERT_INFO certInfo; + certInfo.Issuer = signerInfo->Issuer; + certInfo.SerialNumber = signerInfo->SerialNumber; + certContext = CertFindCertificateInStore(certStore, ENCODING, 0, + CERT_FIND_SUBJECT_CERT, + (PVOID)&certInfo, nullptr); + if (!certContext) + { + lastError = GetLastError(); + LOG_WARN(("CertFindCertificateInStore failed. (%d)", lastError)); + goto cleanup; + } + + if (!DoCertificateAttributesMatch(certContext, infoToMatch)) + { + lastError = ERROR_NOT_FOUND; + LOG_WARN(("Certificate did not match issuer or name. (%d)", lastError)); + goto cleanup; + } + +cleanup: + if (signerInfo) + { + LocalFree(signerInfo); + } + if (certContext) + { + CertFreeCertificateContext(certContext); + } + if (certStore) + { + CertCloseStore(certStore, 0); + } + if (cryptMsg) + { + CryptMsgClose(cryptMsg); + } + return lastError; +} + +/** + * Checks to see if a file stored at filePath matches the specified info. + * + * @param certContext The certificate context of the file + * @param infoToMatch The acceptable information to match + * @return FALSE if the info does not match or if any error occurs in the check + */ +BOOL +DoCertificateAttributesMatch(PCCERT_CONTEXT certContext, + CertificateCheckInfo &infoToMatch) +{ + DWORD dwData; + LPTSTR szName = nullptr; + + if (infoToMatch.issuer) + { + // Pass in nullptr to get the needed size of the issuer buffer. + dwData = CertGetNameString(certContext, + CERT_NAME_SIMPLE_DISPLAY_TYPE, + CERT_NAME_ISSUER_FLAG, nullptr, + nullptr, 0); + + if (!dwData) + { + LOG_WARN(("CertGetNameString failed. (%d)", GetLastError())); + return FALSE; + } + + // Allocate memory for Issuer name buffer. + LPTSTR szName = (LPTSTR)LocalAlloc(LPTR, dwData * sizeof(WCHAR)); + if (!szName) + { + LOG_WARN(("Unable to allocate memory for issuer name. (%d)", + GetLastError())); + return FALSE; + } + + // Get Issuer name. + if (!CertGetNameString(certContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, + CERT_NAME_ISSUER_FLAG, nullptr, szName, dwData)) + { + LOG_WARN(("CertGetNameString failed. (%d)", GetLastError())); + LocalFree(szName); + return FALSE; + } + + // If the issuer does not match, return a failure. + if (!infoToMatch.issuer || + wcscmp(szName, infoToMatch.issuer)) + { + LocalFree(szName); + return FALSE; + } + + LocalFree(szName); + szName = nullptr; + } + + if (infoToMatch.name) + { + // Pass in nullptr to get the needed size of the name buffer. + dwData = CertGetNameString(certContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, + 0, nullptr, nullptr, 0); + if (!dwData) + { + LOG_WARN(("CertGetNameString failed. (%d)", GetLastError())); + return FALSE; + } + + // Allocate memory for the name buffer. + szName = (LPTSTR)LocalAlloc(LPTR, dwData * sizeof(WCHAR)); + if (!szName) + { + LOG_WARN(("Unable to allocate memory for subject name. (%d)", + GetLastError())); + return FALSE; + } + + // Obtain the name. + if (!(CertGetNameString(certContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, + nullptr, szName, dwData))) + { + LOG_WARN(("CertGetNameString failed. (%d)", GetLastError())); + LocalFree(szName); + return FALSE; + } + + // If the issuer does not match, return a failure. + if (!infoToMatch.name || + wcscmp(szName, infoToMatch.name)) + { + LocalFree(szName); + return FALSE; + } + + // We have a match! + LocalFree(szName); + } + + // If there were any errors we would have aborted by now. + return TRUE; +} + +/** + * Duplicates the specified string + * + * @param inputString The string to duplicate + * @return The duplicated string which should be freed by the caller. + */ +LPWSTR +AllocateAndCopyWideString(LPCWSTR inputString) +{ + LPWSTR outputString = + (LPWSTR)LocalAlloc(LPTR, (wcslen(inputString) + 1) * sizeof(WCHAR)); + if (outputString) + { + lstrcpyW(outputString, inputString); + } + return outputString; +} + +/** + * Verifies the trust of the specified file path. + * + * @param filePath The file path to check. + * @return ERROR_SUCCESS if successful, or the last error code otherwise. + */ +DWORD +VerifyCertificateTrustForFile(LPCWSTR filePath) +{ + // Setup the file to check. + WINTRUST_FILE_INFO fileToCheck; + ZeroMemory(&fileToCheck, sizeof(fileToCheck)); + fileToCheck.cbStruct = sizeof(WINTRUST_FILE_INFO); + fileToCheck.pcwszFilePath = filePath; + + // Setup what to check, we want to check it is signed and trusted. + WINTRUST_DATA trustData; + ZeroMemory(&trustData, sizeof(trustData)); + trustData.cbStruct = sizeof(trustData); + trustData.pPolicyCallbackData = nullptr; + trustData.pSIPClientData = nullptr; + trustData.dwUIChoice = WTD_UI_NONE; + trustData.fdwRevocationChecks = WTD_REVOKE_NONE; + trustData.dwUnionChoice = WTD_CHOICE_FILE; + trustData.dwStateAction = 0; + trustData.hWVTStateData = nullptr; + trustData.pwszURLReference = nullptr; + // no UI + trustData.dwUIContext = 0; + trustData.pFile = &fileToCheck; + + GUID policyGUID = WINTRUST_ACTION_GENERIC_VERIFY_V2; + // Check if the file is signed by something that is trusted. + LONG ret = WinVerifyTrust(nullptr, &policyGUID, &trustData); + if (ERROR_SUCCESS == ret) + { + // The hash that represents the subject is trusted and there were no + // verification errors. No publisher nor time stamp chain errors. + LOG(("The file \"%ls\" is signed and the signature was verified.", + filePath)); + return ERROR_SUCCESS; + } + + DWORD lastError = GetLastError(); + LOG_WARN(("There was an error validating trust of the certificate for file" + " \"%ls\". Returned: %d. (%d)", filePath, ret, lastError)); + return ret; +} diff --git a/onlineupdate/source/service/certificatecheck.hxx b/onlineupdate/source/service/certificatecheck.hxx new file mode 100644 index 000000000..1efbcb153 --- /dev/null +++ b/onlineupdate/source/service/certificatecheck.hxx @@ -0,0 +1,22 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _CERTIFICATECHECK_H_ +#define _CERTIFICATECHECK_H_ + +#include <wincrypt.h> + +struct CertificateCheckInfo +{ + LPCWSTR name; + LPCWSTR issuer; +}; + +BOOL DoCertificateAttributesMatch(PCCERT_CONTEXT pCertContext, + CertificateCheckInfo &infoToMatch); +DWORD VerifyCertificateTrustForFile(LPCWSTR filePath); +DWORD CheckCertificateForPEFile(LPCWSTR filePath, + CertificateCheckInfo &infoToMatch); + +#endif diff --git a/onlineupdate/source/service/maintenanceservice.cxx b/onlineupdate/source/service/maintenanceservice.cxx new file mode 100644 index 000000000..da324de54 --- /dev/null +++ b/onlineupdate/source/service/maintenanceservice.cxx @@ -0,0 +1,438 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <windows.h> +#include <shlwapi.h> +#include <stdio.h> +#include <wchar.h> +#include <shlobj.h> + +#include "serviceinstall.hxx" +#include "maintenanceservice.hxx" +#include "servicebase.hxx" +#include "workmonitor.hxx" +#include "uachelper.h" +#include "updatehelper.h" + +// Link w/ subsystem window so we don't get a console when executing +// this binary through the installer. +#pragma comment(linker, "/SUBSYSTEM:windows") + +SERVICE_STATUS gSvcStatus = { 0 }; +SERVICE_STATUS_HANDLE gSvcStatusHandle = nullptr; +HANDLE gWorkDoneEvent = nullptr; +HANDLE gThread = nullptr; +bool gServiceControlStopping = false; + +// logs are pretty small, about 20 lines, so 10 seems reasonable. +#define LOGS_TO_KEEP 10 + +BOOL GetLogDirectoryPath(WCHAR *path); + +int +wmain(int argc, WCHAR **argv) +{ + if (argc < 2) + { + LOG_WARN(("missing mandatory command line argument")); + return 1; + } + // If command-line parameter is "install", install the service + // or upgrade if already installed + // If command line parameter is "forceinstall", install the service + // even if it is older than what is already installed. + // If command-line parameter is "upgrade", upgrade the service + // but do not install it if it is not already installed. + // If command line parameter is "uninstall", uninstall the service. + // Otherwise, the service is probably being started by the SCM. + bool forceInstall = !lstrcmpi(argv[1], L"forceinstall"); + if (!lstrcmpi(argv[1], L"install") || forceInstall) + { + WCHAR updatePath[MAX_PATH + 1]; + if (GetLogDirectoryPath(updatePath)) + { + LogInit(updatePath, L"maintenanceservice-install.log"); + } + + SvcInstallAction action = InstallSvc; + if (forceInstall) + { + action = ForceInstallSvc; + LOG(("Installing service with force specified...")); + } + else + { + LOG(("Installing service...")); + } + + bool ret = SvcInstall(action); + if (!ret) + { + LOG_WARN(("Could not install service. (%d)", GetLastError())); + LogFinish(); + return 1; + } + + LOG(("The service was installed successfully")); + LogFinish(); + return 0; + } + + if (!lstrcmpi(argv[1], L"upgrade")) + { + WCHAR updatePath[MAX_PATH + 1]; + if (GetLogDirectoryPath(updatePath)) + { + LogInit(updatePath, L"maintenanceservice-install.log"); + } + + LOG(("Upgrading service if installed...")); + if (!SvcInstall(UpgradeSvc)) + { + LOG_WARN(("Could not upgrade service. (%d)", GetLastError())); + LogFinish(); + return 1; + } + + LOG(("The service was upgraded successfully")); + LogFinish(); + return 0; + } + + if (!lstrcmpi(argv[1], L"uninstall")) + { + WCHAR updatePath[MAX_PATH + 1]; + if (GetLogDirectoryPath(updatePath)) + { + LogInit(updatePath, L"maintenanceservice-uninstall.log"); + } + LOG(("Uninstalling service...")); + if (!SvcUninstall()) + { + LOG_WARN(("Could not uninstall service. (%d)", GetLastError())); + LogFinish(); + return 1; + } + LOG(("The service was uninstalled successfully")); + LogFinish(); + return 0; + } + + SERVICE_TABLE_ENTRYW DispatchTable[] = + { + { SVC_NAME, (LPSERVICE_MAIN_FUNCTIONW) SvcMain }, + { nullptr, nullptr } + }; + + // This call returns when the service has stopped. + // The process should simply terminate when the call returns. + if (!StartServiceCtrlDispatcherW(DispatchTable)) + { + LOG_WARN(("StartServiceCtrlDispatcher failed. (%d)", GetLastError())); + } + + return 0; +} + +/** + * Obtains the base path where logs should be stored + * + * @param path The out buffer for the backup log path of size MAX_PATH + 1 + * @return TRUE if successful. + */ +BOOL +GetLogDirectoryPath(WCHAR *path) +{ + HRESULT hr = SHGetFolderPathW(nullptr, CSIDL_COMMON_APPDATA, nullptr, + SHGFP_TYPE_CURRENT, path); + if (FAILED(hr)) + { + return FALSE; + } + + if (!PathAppendSafe(path, L"Mozilla")) + { + return FALSE; + } + // The directory should already be created from the installer, but + // just to be safe in case someone deletes. + CreateDirectoryW(path, nullptr); + + if (!PathAppendSafe(path, L"logs")) + { + return FALSE; + } + CreateDirectoryW(path, nullptr); + return TRUE; +} + +/** + * Calculated a backup path based on the log number. + * + * @param path The out buffer to store the log path of size MAX_PATH + 1 + * @param basePath The base directory where the calculated path should go + * @param logNumber The log number, 0 == updater.log + * @return TRUE if successful. + */ +BOOL +GetBackupLogPath(LPWSTR path, LPCWSTR basePath, int logNumber) +{ + WCHAR logName[64] = { L'\0' }; + wcsncpy(path, basePath, sizeof(logName) / sizeof(logName[0]) - 1); + if (logNumber <= 0) + { + swprintf(logName, sizeof(logName) / sizeof(logName[0]), + L"maintenanceservice.log"); + } + else + { + swprintf(logName, sizeof(logName) / sizeof(logName[0]), + L"maintenanceservice-%d.log", logNumber); + } + return PathAppendSafe(path, logName); +} + +/** + * Moves the old log files out of the way before a new one is written. + * If you for example keep 3 logs, then this function will do: + * updater2.log -> updater3.log + * updater1.log -> updater2.log + * updater.log -> updater1.log + * Which clears room for a new updater.log in the basePath directory + * + * @param basePath The base directory path where log files are stored + * @param numLogsToKeep The number of logs to keep + */ +void +BackupOldLogs(LPCWSTR basePath, int numLogsToKeep) +{ + WCHAR oldPath[MAX_PATH + 1]; + WCHAR newPath[MAX_PATH + 1]; + for (int i = numLogsToKeep; i >= 1; i--) + { + if (!GetBackupLogPath(oldPath, basePath, i -1)) + { + continue; + } + + if (!GetBackupLogPath(newPath, basePath, i)) + { + continue; + } + + if (!MoveFileExW(oldPath, newPath, MOVEFILE_REPLACE_EXISTING)) + { + continue; + } + } +} + +/** + * Ensures the service is shutdown once all work is complete. + * There is an issue on XP SP2 and below where the service can hang + * in a stop pending state even though the SCM is notified of a stopped + * state. Control *should* be returned to StartServiceCtrlDispatcher from the + * call to SetServiceStatus on a stopped state in the wmain thread. + * Sometimes this is not the case though. This thread will terminate the process + * if it has been 5 seconds after all work is done and the process is still not + * terminated. This thread is only started once a stopped state was sent to the + * SCM. The stop pending hang can be reproduced intermittently even if you set + * a stopped state directly and never set a stop pending state. It is safe to + * forcefully terminate the process ourselves since all work is done once we + * start this thread. +*/ +DWORD WINAPI +EnsureProcessTerminatedThread(LPVOID) +{ + Sleep(5000); + exit(0); +} + +void +StartTerminationThread() +{ + // If the process does not self terminate like it should, this thread + // will terminate the process after 5 seconds. + HANDLE thread = CreateThread(nullptr, 0, EnsureProcessTerminatedThread, + nullptr, 0, nullptr); + if (thread) + { + CloseHandle(thread); + } +} + +/** + * Main entry point when running as a service. + */ +void WINAPI +SvcMain(DWORD argc, LPWSTR *argv) +{ + // Setup logging, and backup the old logs + WCHAR updatePath[MAX_PATH + 1]; + if (GetLogDirectoryPath(updatePath)) + { + BackupOldLogs(updatePath, LOGS_TO_KEEP); + LogInit(updatePath, L"maintenanceservice.log"); + } + + // Disable every privilege we don't need. Processes started using + // CreateProcess will use the same token as this process. + UACHelper::DisablePrivileges(nullptr); + + // Register the handler function for the service + gSvcStatusHandle = RegisterServiceCtrlHandlerW(SVC_NAME, SvcCtrlHandler); + if (!gSvcStatusHandle) + { + LOG_WARN(("RegisterServiceCtrlHandler failed. (%d)", GetLastError())); + ExecuteServiceCommand(argc, argv); + LogFinish(); + exit(1); + } + + // These values will be re-used later in calls involving gSvcStatus + gSvcStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS; + gSvcStatus.dwServiceSpecificExitCode = 0; + + // Report initial status to the SCM + ReportSvcStatus(SERVICE_START_PENDING, NO_ERROR, 3000); + + // This event will be used to tell the SvcCtrlHandler when the work is + // done for when a stop command is manually issued. + gWorkDoneEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr); + if (!gWorkDoneEvent) + { + ReportSvcStatus(SERVICE_STOPPED, 1, 0); + StartTerminationThread(); + return; + } + + // Initialization complete and we're about to start working on + // the actual command. Report the service state as running to the SCM. + ReportSvcStatus(SERVICE_RUNNING, NO_ERROR, 0); + + // The service command was executed, stop logging and set an event + // to indicate the work is done in case someone is waiting on a + // service stop operation. + ExecuteServiceCommand(argc, argv); + LogFinish(); + + SetEvent(gWorkDoneEvent); + + // If we aren't already in a stopping state then tell the SCM we're stopped + // now. If we are already in a stopping state then the SERVICE_STOPPED state + // will be set by the SvcCtrlHandler. + if (!gServiceControlStopping) + { + ReportSvcStatus(SERVICE_STOPPED, NO_ERROR, 0); + StartTerminationThread(); + } +} + +/** + * Sets the current service status and reports it to the SCM. + * + * @param currentState The current state (see SERVICE_STATUS) + * @param exitCode The system error code + * @param waitHint Estimated time for pending operation in milliseconds + */ +void +ReportSvcStatus(DWORD currentState, + DWORD exitCode, + DWORD waitHint) +{ + static DWORD dwCheckPoint = 1; + + gSvcStatus.dwCurrentState = currentState; + gSvcStatus.dwWin32ExitCode = exitCode; + gSvcStatus.dwWaitHint = waitHint; + + if (SERVICE_START_PENDING == currentState || + SERVICE_STOP_PENDING == currentState) + { + gSvcStatus.dwControlsAccepted = 0; + } + else + { + gSvcStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | + SERVICE_ACCEPT_SHUTDOWN; + } + + if ((SERVICE_RUNNING == currentState) || + (SERVICE_STOPPED == currentState)) + { + gSvcStatus.dwCheckPoint = 0; + } + else + { + gSvcStatus.dwCheckPoint = dwCheckPoint++; + } + + // Report the status of the service to the SCM. + SetServiceStatus(gSvcStatusHandle, &gSvcStatus); +} + +/** + * Since the SvcCtrlHandler should only spend at most 30 seconds before + * returning, this function does the service stop work for the SvcCtrlHandler. +*/ +DWORD WINAPI +StopServiceAndWaitForCommandThread(LPVOID) +{ + do + { + ReportSvcStatus(SERVICE_STOP_PENDING, NO_ERROR, 1000); + } + while (WaitForSingleObject(gWorkDoneEvent, 100) == WAIT_TIMEOUT); + CloseHandle(gWorkDoneEvent); + gWorkDoneEvent = nullptr; + ReportSvcStatus(SERVICE_STOPPED, NO_ERROR, 0); + StartTerminationThread(); + return 0; +} + +/** + * Called by SCM whenever a control code is sent to the service + * using the ControlService function. + */ +void WINAPI +SvcCtrlHandler(DWORD dwCtrl) +{ + // After a SERVICE_CONTROL_STOP there should be no more commands sent to + // the SvcCtrlHandler. + if (gServiceControlStopping) + { + return; + } + + // Handle the requested control code. + switch (dwCtrl) + { + case SERVICE_CONTROL_SHUTDOWN: + case SERVICE_CONTROL_STOP: + { + gServiceControlStopping = true; + ReportSvcStatus(SERVICE_STOP_PENDING, NO_ERROR, 1000); + + // The SvcCtrlHandler thread should not spend more than 30 seconds in + // shutdown so we spawn a new thread for stopping the service + HANDLE thread = CreateThread(nullptr, 0, + StopServiceAndWaitForCommandThread, + nullptr, 0, nullptr); + if (thread) + { + CloseHandle(thread); + } + else + { + // Couldn't start the thread so just call the stop ourselves. + // If it happens to take longer than 30 seconds the caller will + // get an error. + StopServiceAndWaitForCommandThread(nullptr); + } + } + break; + default: + break; + } +} diff --git a/onlineupdate/source/service/maintenanceservice.hxx b/onlineupdate/source/service/maintenanceservice.hxx new file mode 100644 index 000000000..af5db9e29 --- /dev/null +++ b/onlineupdate/source/service/maintenanceservice.hxx @@ -0,0 +1,10 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +void WINAPI SvcMain(DWORD dwArgc, LPWSTR *lpszArgv); +void SvcInit(DWORD dwArgc, LPWSTR *lpszArgv); +void WINAPI SvcCtrlHandler(DWORD dwCtrl); +void ReportSvcStatus(DWORD dwCurrentState, + DWORD dwWin32ExitCode, + DWORD dwWaitHint); diff --git a/onlineupdate/source/service/registrycertificates.cxx b/onlineupdate/source/service/registrycertificates.cxx new file mode 100644 index 000000000..ea0905cea --- /dev/null +++ b/onlineupdate/source/service/registrycertificates.cxx @@ -0,0 +1,181 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <stdio.h> +#include <stdlib.h> +#include <windows.h> + +#include <memory> + +#include "registrycertificates.hxx" +#include "pathhash.h" +#include "servicebase.hxx" +#include "updatehelper.h" +#define MAX_KEY_LENGTH 255 + +namespace { + +struct AutoRegKey +{ + AutoRegKey(HKEY key): + mKey(key) + { + } + + ~AutoRegKey() + { + releaseKey(mKey); + } + + void releaseKey(HKEY key) + { + if (key != nullptr) + { + RegCloseKey(key); + } + } + + HKEY mKey; + + HKEY get() + { + return mKey; + } +}; + +} + +/** + * Verifies if the file path matches any certificate stored in the registry. + * + * @param filePath The file path of the application to check if allowed. + * @return TRUE if the binary matches any of the allowed certificates. + */ +BOOL +DoesBinaryMatchAllowedCertificates(LPCWSTR basePathForUpdate, LPCWSTR filePath) +{ + WCHAR maintenanceServiceKey[MAX_PATH + 1]; + if (!CalculateRegistryPathFromFilePath(basePathForUpdate, + maintenanceServiceKey)) + { + return FALSE; + } + + // We use KEY_WOW64_64KEY to always force 64-bit view. + // The user may have both x86 and x64 applications installed + // which each register information. We need a consistent place + // to put those certificate attributes in and hence why we always + // force the non redirected registry under Wow6432Node. + // This flag is ignored on 32bit systems. + HKEY baseKeyRaw; + LONG retCode = RegOpenKeyExW(HKEY_LOCAL_MACHINE, + maintenanceServiceKey, 0, + KEY_READ | KEY_WOW64_64KEY, &baseKeyRaw); + if (retCode != ERROR_SUCCESS) + { + LOG_WARN(("Could not open key. (%d)", retCode)); + // Our tests run with a different apply directory for each test. + // We use this registry key on our test slaves to store the + // allowed name/issuers. + retCode = RegOpenKeyExW(HKEY_LOCAL_MACHINE, + TEST_ONLY_FALLBACK_KEY_PATH, 0, + KEY_READ | KEY_WOW64_64KEY, &baseKeyRaw); + if (retCode != ERROR_SUCCESS) + { + LOG_WARN(("Could not open fallback key. (%d)", retCode)); + return FALSE; + } + } + AutoRegKey baseKey(baseKeyRaw); + + // Get the number of subkeys. + DWORD subkeyCount = 0; + retCode = RegQueryInfoKeyW(baseKey.get(), nullptr, nullptr, nullptr, &subkeyCount, + nullptr, nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr); + if (retCode != ERROR_SUCCESS) + { + LOG_WARN(("Could not query info key. (%d)", retCode)); + return FALSE; + } + + // Enumerate the subkeys, each subkey represents an allowed certificate. + for (DWORD i = 0; i < subkeyCount; i++) + { + WCHAR subkeyBuffer[MAX_KEY_LENGTH]; + DWORD subkeyBufferCount = MAX_KEY_LENGTH; + retCode = RegEnumKeyExW(baseKey.get(), i, subkeyBuffer, + &subkeyBufferCount, nullptr, + nullptr, nullptr, nullptr); + if (retCode != ERROR_SUCCESS) + { + LOG_WARN(("Could not enum certs. (%d)", retCode)); + return FALSE; + } + + // Open the subkey for the current certificate + HKEY subKeyRaw; + retCode = RegOpenKeyExW(baseKey.get(), + subkeyBuffer, + 0, + KEY_READ | KEY_WOW64_64KEY, + &subKeyRaw); + AutoRegKey subKey(subKeyRaw); + if (retCode != ERROR_SUCCESS) + { + LOG_WARN(("Could not open subkey. (%d)", retCode)); + continue; // Try the next subkey + } + + const int MAX_CHAR_COUNT = 256; + DWORD valueBufSize = MAX_CHAR_COUNT * sizeof(WCHAR); + WCHAR name[MAX_CHAR_COUNT] = { L'\0' }; + WCHAR issuer[MAX_CHAR_COUNT] = { L'\0' }; + + // Get the name from the registry + retCode = RegQueryValueExW(subKey.get(), L"name", 0, nullptr, + (LPBYTE)name, &valueBufSize); + if (retCode != ERROR_SUCCESS) + { + LOG_WARN(("Could not obtain name from registry. (%d)", retCode)); + continue; // Try the next subkey + } + + // Get the issuer from the registry + valueBufSize = MAX_CHAR_COUNT * sizeof(WCHAR); + retCode = RegQueryValueExW(subKey.get(), L"issuer", 0, nullptr, + (LPBYTE)issuer, &valueBufSize); + if (retCode != ERROR_SUCCESS) + { + LOG_WARN(("Could not obtain issuer from registry. (%d)", retCode)); + continue; // Try the next subkey + } + + CertificateCheckInfo allowedCertificate = + { + name, + issuer, + }; + + retCode = CheckCertificateForPEFile(filePath, allowedCertificate); + if (retCode != ERROR_SUCCESS) + { + LOG_WARN(("Error on certificate check. (%d)", retCode)); + continue; // Try the next subkey + } + + retCode = VerifyCertificateTrustForFile(filePath); + if (retCode != ERROR_SUCCESS) + { + LOG_WARN(("Error on certificate trust check. (%d)", retCode)); + continue; // Try the next subkey + } + + // Raise the roof, we found a match! + return TRUE; + } + + // No certificates match, :'( + return FALSE; +} diff --git a/onlineupdate/source/service/registrycertificates.hxx b/onlineupdate/source/service/registrycertificates.hxx new file mode 100644 index 000000000..858b05d7c --- /dev/null +++ b/onlineupdate/source/service/registrycertificates.hxx @@ -0,0 +1,13 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _REGISTRYCERTIFICATES_H_ +#define _REGISTRYCERTIFICATES_H_ + +#include "certificatecheck.hxx" + +BOOL DoesBinaryMatchAllowedCertificates(LPCWSTR basePathForUpdate, + LPCWSTR filePath); + +#endif diff --git a/onlineupdate/source/service/resource.hxx b/onlineupdate/source/service/resource.hxx new file mode 100644 index 000000000..f1a1c3836 --- /dev/null +++ b/onlineupdate/source/service/resource.hxx @@ -0,0 +1,20 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by updater.rc +// +#define IDI_DIALOG 1003 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1003 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/onlineupdate/source/service/servicebase.cxx b/onlineupdate/source/service/servicebase.cxx new file mode 100644 index 000000000..4fb632878 --- /dev/null +++ b/onlineupdate/source/service/servicebase.cxx @@ -0,0 +1,97 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "servicebase.hxx" +#include "windowsHelper.hxx" + +// Shared code between applications and updater.exe + +/** + * Verifies if 2 files are byte for byte equivalent. + * + * @param file1Path The first file to verify. + * @param file2Path The second file to verify. + * @param sameContent Out parameter, TRUE if the files are equal + * @return TRUE If there was no error checking the files. + */ +BOOL +VerifySameFiles(LPCWSTR file1Path, LPCWSTR file2Path, BOOL &sameContent) +{ + sameContent = FALSE; + AutoHandle file1(CreateFileW(file1Path, GENERIC_READ, FILE_SHARE_READ, + nullptr, OPEN_EXISTING, 0, nullptr)); + if (file1 == INVALID_HANDLE_VALUE) + { + return FALSE; + } + AutoHandle file2(CreateFileW(file2Path, GENERIC_READ, FILE_SHARE_READ, + nullptr, OPEN_EXISTING, 0, nullptr)); + if (file2 == INVALID_HANDLE_VALUE) + { + return FALSE; + } + + DWORD fileSize1 = GetFileSize(file1.get(), nullptr); + DWORD fileSize2 = GetFileSize(file2.get(), nullptr); + if (INVALID_FILE_SIZE == fileSize1 || INVALID_FILE_SIZE == fileSize2) + { + return FALSE; + } + + if (fileSize1 != fileSize2) + { + // sameContent is already set to FALSE + return TRUE; + } + + char buf1[COMPARE_BLOCKSIZE]; + char buf2[COMPARE_BLOCKSIZE]; + DWORD numBlocks = fileSize1 / COMPARE_BLOCKSIZE; + DWORD leftOver = fileSize1 % COMPARE_BLOCKSIZE; + DWORD readAmount; + for (DWORD i = 0; i < numBlocks; i++) + { + if (!ReadFile(file1.get(), buf1, COMPARE_BLOCKSIZE, &readAmount, nullptr) || + readAmount != COMPARE_BLOCKSIZE) + { + return FALSE; + } + + if (!ReadFile(file2.get(), buf2, COMPARE_BLOCKSIZE, &readAmount, nullptr) || + readAmount != COMPARE_BLOCKSIZE) + { + return FALSE; + } + + if (memcmp(buf1, buf2, COMPARE_BLOCKSIZE)) + { + // sameContent is already set to FALSE + return TRUE; + } + } + + if (leftOver) + { + if (!ReadFile(file1.get(), buf1, leftOver, &readAmount, nullptr) || + readAmount != leftOver) + { + return FALSE; + } + + if (!ReadFile(file2.get(), buf2, leftOver, &readAmount, nullptr) || + readAmount != leftOver) + { + return FALSE; + } + + if (memcmp(buf1, buf2, leftOver)) + { + // sameContent is already set to FALSE + return TRUE; + } + } + + sameContent = TRUE; + return TRUE; +} diff --git a/onlineupdate/source/service/servicebase.hxx b/onlineupdate/source/service/servicebase.hxx new file mode 100644 index 000000000..c47f966ad --- /dev/null +++ b/onlineupdate/source/service/servicebase.hxx @@ -0,0 +1,22 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <windows.h> +#include "updatelogging.h" + +BOOL PathAppendSafe(LPWSTR base, LPCWSTR extra); +BOOL VerifySameFiles(LPCWSTR file1Path, LPCWSTR file2Path, BOOL &sameContent); + +// 32KiB for comparing files at a time seems reasonable. +// The bigger the better for speed, but this will be used +// on the stack so I don't want it to be too big. +#define COMPARE_BLOCKSIZE 32768 + +// The following string resource value is used to uniquely identify the signed +// Mozilla application as an updater. Before the maintenance service will +// execute the updater it must have this updater identity string in its string +// table. No other signed Mozilla product will have this string table value. +#define UPDATER_IDENTITY_STRING \ + "libreoffice-updater.exe-7bab28a0-0599-4f37-9efe-f7f8b71f05e3" +#define IDS_UPDATER_IDENTITY 1006 diff --git a/onlineupdate/source/service/serviceinstall.cxx b/onlineupdate/source/service/serviceinstall.cxx new file mode 100644 index 000000000..7d53d25cb --- /dev/null +++ b/onlineupdate/source/service/serviceinstall.cxx @@ -0,0 +1,850 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <windows.h> +#include <aclapi.h> +#include <stdlib.h> +#include <shlwapi.h> + +// Used for DNLEN and UNLEN +#include <lm.h> + +#include "serviceinstall.hxx" +#include "servicebase.hxx" +#include "updatehelper.h" +#include "shellapi.h" +#include "readstrings.h" +#include "errors.h" + +#include <memory> + +#pragma comment(lib, "version.lib") + +namespace { + +struct AutoServiceHandle +{ + AutoServiceHandle(SC_HANDLE handle): + mHandle(handle) + { + } + + ~AutoServiceHandle() + { + releaseHandle(mHandle); + } + + void releaseHandle(SC_HANDLE handle) + { + if (handle != nullptr) + CloseServiceHandle(handle); + } + + SC_HANDLE get() const + { + return mHandle; + } + + operator bool() const + { + return mHandle != nullptr; + } + + void reset(SC_HANDLE handle = nullptr) + { + releaseHandle(mHandle); + mHandle = handle; + } + + SC_HANDLE mHandle; +}; + +} + +/** + * A wrapper function to read strings for the maintenance service. + * + * @param path The path of the ini file to read from + * @param results The maintenance service strings that were read + * @return OK on success +*/ +static int +ReadMaintenanceServiceStrings(LPCWSTR path, + MaintenanceServiceStringTable *results) +{ + // Read in the maintenance service description string if specified. + const unsigned int kNumStrings = 1; + // TODO: moggi: needs adaptation for LibreOffice + const char *kServiceKeys = "MozillaMaintenanceDescription\0"; + char serviceStrings[kNumStrings][MAX_TEXT_LEN]; + int result = ReadStrings(path, kServiceKeys, + kNumStrings, serviceStrings); + if (result != OK) + { + serviceStrings[0][0] = '\0'; + } + strncpy(results->serviceDescription, + serviceStrings[0], MAX_TEXT_LEN - 1); + results->serviceDescription[MAX_TEXT_LEN - 1] = '\0'; + return result; +} + +/** + * Obtains the version number from the specified PE file's version information + * Version Format: A.B.C.D (Example 10.0.0.300) + * + * @param path The path of the file to check the version on + * @param A The first part of the version number + * @param B The second part of the version number + * @param C The third part of the version number + * @param D The fourth part of the version number + * @return TRUE if successful + */ +static BOOL +GetVersionNumberFromPath(LPWSTR path, DWORD &A, DWORD &B, + DWORD &C, DWORD &D) +{ + DWORD fileVersionInfoSize = GetFileVersionInfoSizeW(path, 0); + std::unique_ptr<char[]> fileVersionInfo(new char[fileVersionInfoSize]); + if (!GetFileVersionInfoW(path, 0, fileVersionInfoSize, + fileVersionInfo.get())) + { + LOG_WARN(("Could not obtain file info of old service. (%d)", + GetLastError())); + return FALSE; + } + + VS_FIXEDFILEINFO *fixedFileInfo = + reinterpret_cast<VS_FIXEDFILEINFO *>(fileVersionInfo.get()); + UINT size; + if (!VerQueryValueW(fileVersionInfo.get(), L"\\", + reinterpret_cast<LPVOID*>(&fixedFileInfo), &size)) + { + LOG_WARN(("Could not query file version info of old service. (%d)", + GetLastError())); + return FALSE; + } + + A = HIWORD(fixedFileInfo->dwFileVersionMS); + B = LOWORD(fixedFileInfo->dwFileVersionMS); + C = HIWORD(fixedFileInfo->dwFileVersionLS); + D = LOWORD(fixedFileInfo->dwFileVersionLS); + return TRUE; +} + +/** + * Updates the service description with what is stored in updater.ini + * at the same path as the currently executing module binary. + * + * @param serviceHandle A handle to an opened service with + * SERVICE_CHANGE_CONFIG access right + * @param TRUE on success. +*/ +BOOL +UpdateServiceDescription(SC_HANDLE serviceHandle) +{ + WCHAR updaterINIPath[MAX_PATH + 1]; + if (!GetModuleFileNameW(nullptr, updaterINIPath, + sizeof(updaterINIPath) / + sizeof(updaterINIPath[0]))) + { + LOG_WARN(("Could not obtain module filename when attempting to " + "modify service description. (%d)", GetLastError())); + return FALSE; + } + + if (!PathRemoveFileSpecW(updaterINIPath)) + { + LOG_WARN(("Could not remove file spec when attempting to " + "modify service description. (%d)", GetLastError())); + return FALSE; + } + + if (!PathAppendSafe(updaterINIPath, L"updater.ini")) + { + LOG_WARN(("Could not append updater.ini filename when attempting to " + "modify service description. (%d)", GetLastError())); + return FALSE; + } + + if (GetFileAttributesW(updaterINIPath) == INVALID_FILE_ATTRIBUTES) + { + LOG_WARN(("updater.ini file does not exist, will not modify " + "service description. (%d)", GetLastError())); + return FALSE; + } + + MaintenanceServiceStringTable serviceStrings; + int rv = ReadMaintenanceServiceStrings(updaterINIPath, &serviceStrings); + if (rv != OK || !strlen(serviceStrings.serviceDescription)) + { + LOG_WARN(("updater.ini file does not contain a maintenance " + "service description.")); + return FALSE; + } + + WCHAR serviceDescription[MAX_TEXT_LEN]; + if (!MultiByteToWideChar(CP_UTF8, 0, + serviceStrings.serviceDescription, -1, + serviceDescription, + sizeof(serviceDescription) / + sizeof(serviceDescription[0]))) + { + LOG_WARN(("Could not convert description to wide string format. (%d)", + GetLastError())); + return FALSE; + } + + SERVICE_DESCRIPTIONW descriptionConfig; + descriptionConfig.lpDescription = serviceDescription; + if (!ChangeServiceConfig2W(serviceHandle, + SERVICE_CONFIG_DESCRIPTION, + &descriptionConfig)) + { + LOG_WARN(("Could not change service config. (%d)", GetLastError())); + return FALSE; + } + + LOG(("The service description was updated successfully.")); + return TRUE; +} + +/** + * Determines if the MozillaMaintenance service path needs to be updated + * and fixes it if it is wrong. + * + * @param service A handle to the service to fix. + * @param currentServicePath The current (possibly wrong) path that is used. + * @param servicePathWasWrong Out parameter set to TRUE if a fix was needed. + * @return TRUE if the service path is now correct. +*/ +BOOL +FixServicePath(SC_HANDLE service, + LPCWSTR currentServicePath, + BOOL &servicePathWasWrong) +{ + // When we originally upgraded the MozillaMaintenance service we + // would uninstall the service on each upgrade. This had an + // intermittent error which could cause the service to use the file + // maintenanceservice_tmp.exe as the install path. Only a small number + // of Nightly users would be affected by this, but we check for this + // state here and fix the user if they are affected. + // + // We also fix the path in the case of the path not being quoted. + size_t currentServicePathLen = wcslen(currentServicePath); + bool doesServiceHaveCorrectPath = + currentServicePathLen > 2 && + !wcsstr(currentServicePath, L"maintenanceservice_tmp.exe") && + currentServicePath[0] == L'\"' && + currentServicePath[currentServicePathLen - 1] == L'\"'; + + if (doesServiceHaveCorrectPath) + { + LOG(("The MozillaMaintenance service path is correct.")); + servicePathWasWrong = FALSE; + return TRUE; + } + // This is a recoverable situation so not logging as a warning + LOG(("The MozillaMaintenance path is NOT correct. It was: %ls", + currentServicePath)); + + servicePathWasWrong = TRUE; + WCHAR fixedPath[MAX_PATH + 1] = { L'\0' }; + wcsncpy(fixedPath, currentServicePath, MAX_PATH); + PathUnquoteSpacesW(fixedPath); + if (!PathRemoveFileSpecW(fixedPath)) + { + LOG_WARN(("Couldn't remove file spec. (%d)", GetLastError())); + return FALSE; + } + if (!PathAppendSafe(fixedPath, L"maintenanceservice.exe")) + { + LOG_WARN(("Couldn't append file spec. (%d)", GetLastError())); + return FALSE; + } + PathQuoteSpacesW(fixedPath); + + + if (!ChangeServiceConfigW(service, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, + SERVICE_NO_CHANGE, fixedPath, nullptr, nullptr, + nullptr, nullptr, nullptr, nullptr)) + { + LOG_WARN(("Could not fix service path. (%d)", GetLastError())); + return FALSE; + } + + LOG(("Fixed service path to: %ls.", fixedPath)); + return TRUE; +} + +/** + * Installs or upgrades the SVC_NAME service. + * If an existing service is already installed, we replace it with the + * currently running process. + * + * @param action The action to perform. + * @return TRUE if the service was installed/upgraded + */ +BOOL +SvcInstall(SvcInstallAction action) +{ + // Get a handle to the local computer SCM database with full access rights. + AutoServiceHandle schSCManager(OpenSCManager(nullptr, nullptr, + SC_MANAGER_ALL_ACCESS)); + if (!schSCManager) + { + LOG_WARN(("Could not open service manager. (%d)", GetLastError())); + return FALSE; + } + + WCHAR newServiceBinaryPath[MAX_PATH + 1]; + if (!GetModuleFileNameW(nullptr, newServiceBinaryPath, + sizeof(newServiceBinaryPath) / + sizeof(newServiceBinaryPath[0]))) + { + LOG_WARN(("Could not obtain module filename when attempting to " + "install service. (%d)", + GetLastError())); + return FALSE; + } + + // Check if we already have the service installed. + AutoServiceHandle schService(OpenServiceW(schSCManager.get(), + SVC_NAME, + SERVICE_ALL_ACCESS)); + DWORD lastError = GetLastError(); + if (!schService && ERROR_SERVICE_DOES_NOT_EXIST != lastError) + { + // The service exists but we couldn't open it + LOG_WARN(("Could not open service. (%d)", GetLastError())); + return FALSE; + } + + if (schService) + { + // The service exists but it may not have the correct permissions. + // This could happen if the permissions were not set correctly originally + // or have been changed after the installation. This will reset the + // permissions back to allow limited user accounts. + if (!SetUserAccessServiceDACL(schService.get())) + { + LOG_WARN(("Could not reset security ACE on service handle. It might not be " + "possible to start the service. This error should never " + "happen. (%d)", GetLastError())); + } + + // The service exists and we opened it + DWORD bytesNeeded; + if (!QueryServiceConfigW(schService.get(), nullptr, 0, &bytesNeeded) && + GetLastError() != ERROR_INSUFFICIENT_BUFFER) + { + LOG_WARN(("Could not determine buffer size for query service config. (%d)", + GetLastError())); + return FALSE; + } + + // Get the service config information, in particular we want the binary + // path of the service. + std::unique_ptr<char[]> serviceConfigBuffer(new char[bytesNeeded]); + if (!QueryServiceConfigW(schService.get(), + reinterpret_cast<QUERY_SERVICE_CONFIGW*>(serviceConfigBuffer.get()), + bytesNeeded, &bytesNeeded)) + { + LOG_WARN(("Could open service but could not query service config. (%d)", + GetLastError())); + return FALSE; + } + QUERY_SERVICE_CONFIGW &serviceConfig = + *reinterpret_cast<QUERY_SERVICE_CONFIGW*>(serviceConfigBuffer.get()); + + // Check if we need to fix the service path + BOOL servicePathWasWrong; + static BOOL alreadyCheckedFixServicePath = FALSE; + if (!alreadyCheckedFixServicePath) + { + if (!FixServicePath(schService.get(), serviceConfig.lpBinaryPathName, + servicePathWasWrong)) + { + LOG_WARN(("Could not fix service path. This should never happen. (%d)", + GetLastError())); + // True is returned because the service is pointing to + // maintenanceservice_tmp.exe so it actually was upgraded to the + // newest installed service. + return TRUE; + } + else if (servicePathWasWrong) + { + // Now that the path is fixed we should re-attempt the install. + // This current process' image path is maintenanceservice_tmp.exe. + // The service used to point to maintenanceservice_tmp.exe. + // The service was just fixed to point to maintenanceservice.exe. + // Re-attempting an install from scratch will work as normal. + alreadyCheckedFixServicePath = TRUE; + LOG(("Restarting install action: %d", action)); + return SvcInstall(action); + } + } + + // Ensure the service path is not quoted. We own this memory and know it to + // be large enough for the quoted path, so it is large enough for the + // unquoted path. This function cannot fail. + PathUnquoteSpacesW(serviceConfig.lpBinaryPathName); + + // Obtain the existing maintenanceservice file's version number and + // the new file's version number. Versions are in the format of + // A.B.C.D. + DWORD existingA, existingB, existingC, existingD; + DWORD newA, newB, newC, newD; + BOOL obtainedExistingVersionInfo = + GetVersionNumberFromPath(serviceConfig.lpBinaryPathName, + existingA, existingB, + existingC, existingD); + if (!GetVersionNumberFromPath(newServiceBinaryPath, newA, + newB, newC, newD)) + { + LOG_WARN(("Could not obtain version number from new path")); + return FALSE; + } + + // Check if we need to replace the old binary with the new one + // If we couldn't get the old version info then we assume we should + // replace it. + if (ForceInstallSvc == action || + !obtainedExistingVersionInfo || + (existingA < newA) || + (existingA == newA && existingB < newB) || + (existingA == newA && existingB == newB && + existingC < newC) || + (existingA == newA && existingB == newB && + existingC == newC && existingD < newD)) + { + + // We have a newer updater, so update the description from the INI file. + UpdateServiceDescription(schService.get()); + + schService.reset(); + if (!StopService()) + { + return FALSE; + } + + if (!wcscmp(newServiceBinaryPath, serviceConfig.lpBinaryPathName)) + { + LOG(("File is already in the correct location, no action needed for " + "upgrade. The path is: \"%ls\"", newServiceBinaryPath)); + return TRUE; + } + + BOOL result = TRUE; + + // Attempt to copy the new binary over top the existing binary. + // If there is an error we try to move it out of the way and then + // copy it in. First try the safest / easiest way to overwrite the file. + if (!CopyFileW(newServiceBinaryPath, + serviceConfig.lpBinaryPathName, FALSE)) + { + LOG_WARN(("Could not overwrite old service binary file. " + "This should never happen, but if it does the next " + "upgrade will fix it, the service is not a critical " + "component that needs to be installed for upgrades " + "to work. (%d)", GetLastError())); + + // We rename the last 3 filename chars in an unsafe way. Manually + // verify there are more than 3 chars for safe failure in MoveFileExW. + const size_t len = wcslen(serviceConfig.lpBinaryPathName); + if (len > 3) + { + // Calculate the temp file path that we're moving the file to. This + // is the same as the proper service path but with a .old extension. + LPWSTR oldServiceBinaryTempPath = + new WCHAR[len + 1]; + memset(oldServiceBinaryTempPath, 0, (len + 1) * sizeof (WCHAR)); + wcsncpy(oldServiceBinaryTempPath, serviceConfig.lpBinaryPathName, len); + // Rename the last 3 chars to 'old' + wcsncpy(oldServiceBinaryTempPath + len - 3, L"old", 3); + + // Move the current (old) service file to the temp path. + if (MoveFileExW(serviceConfig.lpBinaryPathName, + oldServiceBinaryTempPath, + MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH)) + { + // The old binary is moved out of the way, copy in the new one. + if (!CopyFileW(newServiceBinaryPath, + serviceConfig.lpBinaryPathName, FALSE)) + { + // It is best to leave the old service binary in this condition. + LOG_WARN(("The new service binary could not be copied in." + " The service will not be upgraded.")); + result = FALSE; + } + else + { + LOG(("The new service binary was copied in by first moving the" + " old one out of the way.")); + } + + // Attempt to get rid of the old service temp path. + if (DeleteFileW(oldServiceBinaryTempPath)) + { + LOG(("The old temp service path was deleted: %ls.", + oldServiceBinaryTempPath)); + } + else + { + // The old temp path could not be removed. It will be removed + // the next time the user can't copy the binary in or on uninstall. + LOG_WARN(("The old temp service path was not deleted.")); + } + } + else + { + // It is best to leave the old service binary in this condition. + LOG_WARN(("Could not move old service file out of the way from:" + " \"%ls\" to \"%ls\". Service will not be upgraded. (%d)", + serviceConfig.lpBinaryPathName, + oldServiceBinaryTempPath, GetLastError())); + result = FALSE; + } + delete[] oldServiceBinaryTempPath; + } + else + { + // It is best to leave the old service binary in this condition. + LOG_WARN(("Service binary path was less than 3, service will" + " not be updated. This should never happen.")); + result = FALSE; + } + } + else + { + LOG(("The new service binary was copied in.")); + } + + // We made a copy of ourselves to the existing location. + // The tmp file (the process of which we are executing right now) will be + // left over. Attempt to delete the file on the next reboot. + if (MoveFileExW(newServiceBinaryPath, nullptr, + MOVEFILE_DELAY_UNTIL_REBOOT)) + { + LOG(("Deleting the old file path on the next reboot: %ls.", + newServiceBinaryPath)); + } + else + { + LOG_WARN(("Call to delete the old file path failed: %ls.", + newServiceBinaryPath)); + } + + return result; + } + + // We don't need to copy ourselves to the existing location. + // The tmp file (the process of which we are executing right now) will be + // left over. Attempt to delete the file on the next reboot. + MoveFileExW(newServiceBinaryPath, nullptr, MOVEFILE_DELAY_UNTIL_REBOOT); + + // nothing to do, we already have a newer service installed + return TRUE; + } + + // If the service does not exist and we are upgrading, don't install it. + if (UpgradeSvc == action) + { + // The service does not exist and we are upgrading, so don't install it + return TRUE; + } + + // Quote the path only if it contains spaces. + PathQuoteSpacesW(newServiceBinaryPath); + // The service does not already exist so create the service as on demand + schService.reset(CreateServiceW(schSCManager.get(), SVC_NAME, SVC_DISPLAY_NAME, + SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, + SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL, + newServiceBinaryPath, nullptr, nullptr, + nullptr, nullptr, nullptr)); + if (!schService) + { + LOG_WARN(("Could not create Windows service. " + "This error should never happen since a service install " + "should only be called when elevated. (%d)", GetLastError())); + return FALSE; + } + + if (!SetUserAccessServiceDACL(schService.get())) + { + LOG_WARN(("Could not set security ACE on service handle, the service will not " + "be able to be started from unelevated processes. " + "This error should never happen. (%d)", + GetLastError())); + } + + UpdateServiceDescription(schService.get()); + + return TRUE; +} + +/** + * Stops the Maintenance service. + * + * @return TRUE if successful. + */ +BOOL +StopService() +{ + // Get a handle to the local computer SCM database with full access rights. + AutoServiceHandle schSCManager(OpenSCManager(nullptr, nullptr, + SC_MANAGER_ALL_ACCESS)); + if (!schSCManager) + { + LOG_WARN(("Could not open service manager. (%d)", GetLastError())); + return FALSE; + } + + // Open the service + AutoServiceHandle schService(OpenServiceW(schSCManager.get(), SVC_NAME, + SERVICE_ALL_ACCESS)); + if (!schService) + { + LOG_WARN(("Could not open service. (%d)", GetLastError())); + return FALSE; + } + + LOG(("Sending stop request...")); + SERVICE_STATUS status; + SetLastError(ERROR_SUCCESS); + if (!ControlService(schService.get(), SERVICE_CONTROL_STOP, &status) && + GetLastError() != ERROR_SERVICE_NOT_ACTIVE) + { + LOG_WARN(("Error sending stop request. (%d)", GetLastError())); + } + + schSCManager.reset(); + schService.reset(); + + LOG(("Waiting for service stop...")); + DWORD lastState = WaitForServiceStop(SVC_NAME, 30); + + // The service can be in a stopped state but the exe still in use + // so make sure the process is really gone before proceeding + WaitForProcessExit(L"maintenanceservice.exe", 30); + LOG(("Done waiting for service stop, last service state: %d", lastState)); + + return lastState == SERVICE_STOPPED; +} + +/** + * Uninstalls the Maintenance service. + * + * @return TRUE if successful. + */ +BOOL +SvcUninstall() +{ + // Get a handle to the local computer SCM database with full access rights. + AutoServiceHandle schSCManager(OpenSCManager(nullptr, nullptr, + SC_MANAGER_ALL_ACCESS)); + if (!schSCManager) + { + LOG_WARN(("Could not open service manager. (%d)", GetLastError())); + return FALSE; + } + + // Open the service + AutoServiceHandle schService(OpenServiceW(schSCManager.get(), SVC_NAME, + SERVICE_ALL_ACCESS)); + if (!schService) + { + LOG_WARN(("Could not open service. (%d)", GetLastError())); + return FALSE; + } + + //Stop the service so it deletes faster and so the uninstaller + // can actually delete its EXE. + DWORD totalWaitTime = 0; + SERVICE_STATUS status; + static const int maxWaitTime = 1000 * 60; // Never wait more than a minute + if (ControlService(schService.get(), SERVICE_CONTROL_STOP, &status)) + { + do + { + Sleep(status.dwWaitHint); + totalWaitTime += (status.dwWaitHint + 10); + if (status.dwCurrentState == SERVICE_STOPPED) + { + break; + } + else if (totalWaitTime > maxWaitTime) + { + break; + } + } + while (QueryServiceStatus(schService.get(), &status)); + } + + // Delete the service or mark it for deletion + BOOL deleted = DeleteService(schService.get()); + if (!deleted) + { + deleted = (GetLastError() == ERROR_SERVICE_MARKED_FOR_DELETE); + } + + return deleted; +} + +/** + * Sets the access control list for user access for the specified service. + * + * @param hService The service to set the access control list on + * @return TRUE if successful + */ +BOOL +SetUserAccessServiceDACL(SC_HANDLE hService) +{ + PACL pNewAcl = nullptr; + PSECURITY_DESCRIPTOR psd = nullptr; + DWORD lastError = SetUserAccessServiceDACL(hService, pNewAcl, psd); + if (pNewAcl) + { + LocalFree((HLOCAL)pNewAcl); + } + if (psd) + { + LocalFree((LPVOID)psd); + } + return ERROR_SUCCESS == lastError; +} + +/** + * Sets the access control list for user access for the specified service. + * + * @param hService The service to set the access control list on + * @param pNewAcl The out param ACL which should be freed by caller + * @param psd out param security descriptor, should be freed by caller + * @return ERROR_SUCCESS if successful + */ +DWORD +SetUserAccessServiceDACL(SC_HANDLE hService, PACL &pNewAcl, + PSECURITY_DESCRIPTOR psd) +{ + // Get the current security descriptor needed size + DWORD needed = 0; + if (!QueryServiceObjectSecurity(hService, DACL_SECURITY_INFORMATION, + &psd, 0, &needed)) + { + if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) + { + LOG_WARN(("Could not query service object security size. (%d)", + GetLastError())); + return GetLastError(); + } + + DWORD size = needed; + psd = (PSECURITY_DESCRIPTOR)LocalAlloc(LPTR, size); + if (!psd) + { + LOG_WARN(("Could not allocate security descriptor. (%d)", + GetLastError())); + return ERROR_INSUFFICIENT_BUFFER; + } + + // Get the actual security descriptor now + if (!QueryServiceObjectSecurity(hService, DACL_SECURITY_INFORMATION, + psd, size, &needed)) + { + LOG_WARN(("Could not allocate security descriptor. (%d)", + GetLastError())); + return GetLastError(); + } + } + + // Get the current DACL from the security descriptor. + PACL pacl = nullptr; + BOOL bDaclPresent = FALSE; + BOOL bDaclDefaulted = FALSE; + if ( !GetSecurityDescriptorDacl(psd, &bDaclPresent, &pacl, + &bDaclDefaulted)) + { + LOG_WARN(("Could not obtain DACL. (%d)", GetLastError())); + return GetLastError(); + } + + PSID sid; + DWORD SIDSize = SECURITY_MAX_SID_SIZE; + sid = LocalAlloc(LMEM_FIXED, SIDSize); + if (!sid) + { + LOG_WARN(("Could not allocate SID memory. (%d)", GetLastError())); + return GetLastError(); + } + + if (!CreateWellKnownSid(WinBuiltinUsersSid, nullptr, sid, &SIDSize)) + { + DWORD lastError = GetLastError(); + LOG_WARN(("Could not create well known SID. (%d)", lastError)); + LocalFree(sid); + return lastError; + } + + // Lookup the account name, the function fails if you don't pass in + // a buffer for the domain name but it's not used since we're using + // the built in account Sid. + SID_NAME_USE accountType; + WCHAR accountName[UNLEN + 1] = { L'\0' }; + WCHAR domainName[DNLEN + 1] = { L'\0' }; + DWORD accountNameSize = UNLEN + 1; + DWORD domainNameSize = DNLEN + 1; + if (!LookupAccountSidW(nullptr, sid, accountName, + &accountNameSize, + domainName, &domainNameSize, &accountType)) + { + LOG_WARN(("Could not lookup account Sid, will try Users. (%d)", + GetLastError())); + wcsncpy(accountName, L"Users", UNLEN); + } + + // We already have the group name so we can get rid of the SID + FreeSid(sid); + sid = nullptr; + + // Build the ACE, BuildExplicitAccessWithName cannot fail so it is not logged. + EXPLICIT_ACCESS ea; + BuildExplicitAccessWithNameW(&ea, accountName, + SERVICE_START | SERVICE_STOP | GENERIC_READ, + SET_ACCESS, NO_INHERITANCE); + DWORD lastError = SetEntriesInAclW(1, (PEXPLICIT_ACCESS)&ea, pacl, &pNewAcl); + if (ERROR_SUCCESS != lastError) + { + LOG_WARN(("Could not set entries in ACL. (%d)", lastError)); + return lastError; + } + + // Initialize a new security descriptor. + SECURITY_DESCRIPTOR sd; + if (!InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION)) + { + LOG_WARN(("Could not initialize security descriptor. (%d)", + GetLastError())); + return GetLastError(); + } + + // Set the new DACL in the security descriptor. + if (!SetSecurityDescriptorDacl(&sd, TRUE, pNewAcl, FALSE)) + { + LOG_WARN(("Could not set security descriptor DACL. (%d)", + GetLastError())); + return GetLastError(); + } + + // Set the new security descriptor for the service object. + if (!SetServiceObjectSecurity(hService, DACL_SECURITY_INFORMATION, &sd)) + { + LOG_WARN(("Could not set object security. (%d)", + GetLastError())); + return GetLastError(); + } + + // Woohoo, raise the roof + LOG(("User access was set successfully on the service.")); + return ERROR_SUCCESS; +} diff --git a/onlineupdate/source/service/serviceinstall.hxx b/onlineupdate/source/service/serviceinstall.hxx new file mode 100644 index 000000000..1afaada84 --- /dev/null +++ b/onlineupdate/source/service/serviceinstall.hxx @@ -0,0 +1,21 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "readstrings.h" + +#define SVC_DISPLAY_NAME L"Mozilla Maintenance Service" + +enum SvcInstallAction { UpgradeSvc, InstallSvc, ForceInstallSvc }; +BOOL SvcInstall(SvcInstallAction action); +BOOL SvcUninstall(); +BOOL StopService(); +BOOL SetUserAccessServiceDACL(SC_HANDLE hService); +DWORD SetUserAccessServiceDACL(SC_HANDLE hService, PACL &pNewAcl, + PSECURITY_DESCRIPTOR psd); + +struct MaintenanceServiceStringTable +{ + char serviceDescription[MAX_TEXT_LEN]; +}; + diff --git a/onlineupdate/source/service/windowsHelper.hxx b/onlineupdate/source/service/windowsHelper.hxx new file mode 100644 index 000000000..e17b40c61 --- /dev/null +++ b/onlineupdate/source/service/windowsHelper.hxx @@ -0,0 +1,48 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#ifndef INCLUDED_ONLINEUPDATE_SERVICE_WINDOWSHELPER_HXX +#define INCLUDED_ONLINEUPDATE_SERVICE_WINDOWSHELPER_HXX + +struct AutoHandle +{ + AutoHandle(HANDLE handle): + mHandle(handle) + { + } + + ~AutoHandle() + { + release(mHandle); + } + + void release(HANDLE handle) + { + if (handle && handle != INVALID_HANDLE_VALUE) + { + CloseHandle(handle); + } + } + + HANDLE get() const + { + return mHandle; + } + + bool operator==(const AutoHandle& rhs) const + { + return mHandle == rhs.mHandle; + } + + HANDLE mHandle; +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/onlineupdate/source/service/workmonitor.cxx b/onlineupdate/source/service/workmonitor.cxx new file mode 100644 index 000000000..6228b1e6c --- /dev/null +++ b/onlineupdate/source/service/workmonitor.cxx @@ -0,0 +1,770 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <shlobj.h> +#include <shlwapi.h> +#include <wtsapi32.h> +#include <userenv.h> +#include <shellapi.h> +#include <cstddef> + +#pragma comment(lib, "wtsapi32.lib") +#pragma comment(lib, "userenv.lib") +#pragma comment(lib, "shlwapi.lib") +#pragma comment(lib, "ole32.lib") +#pragma comment(lib, "rpcrt4.lib") + +#include "workmonitor.hxx" +#include "serviceinstall.hxx" +#include "servicebase.hxx" +#include "registrycertificates.hxx" +#include "uachelper.h" +#include "updatehelper.h" +#include "errors.h" +#include "windowsHelper.hxx" + +// Wait 15 minutes for an update operation to run at most. +// Updates usually take less than a minute so this seems like a +// significantly large and safe amount of time to wait. +static const int TIME_TO_WAIT_ON_UPDATER = 15 * 60 * 1000; +wchar_t* MakeCommandLine(int argc, wchar_t **argv); +BOOL WriteStatusFailure(LPCWSTR updateDirPath, int errorCode); +BOOL PathGetSiblingFilePath(LPWSTR destinationBuffer, LPCWSTR siblingFilePath, + LPCWSTR newFileName); + +/* + * Read the update.status file and sets isApplying to true if + * the status is set to applying. + * + * @param updateDirPath The directory where update.status is stored + * @param isApplying Out parameter for specifying if the status + * is set to applying or not. + * @return TRUE if the information was filled. +*/ +static BOOL +IsStatusApplying(LPCWSTR updateDirPath, BOOL &isApplying) +{ + isApplying = FALSE; + WCHAR updateStatusFilePath[MAX_PATH + 1] = {L'\0'}; + wcsncpy(updateStatusFilePath, updateDirPath, MAX_PATH); + if (!PathAppendSafe(updateStatusFilePath, L"update.status")) + { + LOG_WARN(("Could not append path for update.status file")); + return FALSE; + } + + AutoHandle statusFile(CreateFileW(updateStatusFilePath, GENERIC_READ, + FILE_SHARE_READ | + FILE_SHARE_WRITE | + FILE_SHARE_DELETE, + nullptr, OPEN_EXISTING, 0, nullptr)); + + if (statusFile == INVALID_HANDLE_VALUE) + { + LOG_WARN(("Could not open update.status file")); + return FALSE; + } + + char buf[32] = { 0 }; + DWORD read; + if (!ReadFile(statusFile.get(), buf, sizeof(buf), &read, nullptr)) + { + LOG_WARN(("Could not read from update.status file")); + return FALSE; + } + + LOG(("updater.exe returned status: %s", buf)); + + const char kApplying[] = "applying"; + isApplying = strncmp(buf, kApplying, + sizeof(kApplying) - 1) == 0; + return TRUE; +} + +/** + * Determines whether we're staging an update. + * + * @param argc The argc value normally sent to updater.exe + * @param argv The argv value normally sent to updater.exe + * @return boolean True if we're staging an update + */ +static bool +IsUpdateBeingStaged(int argc, LPWSTR *argv) +{ + // PID will be set to -1 if we're supposed to stage an update. + return argc == 4 && !wcscmp(argv[3], L"-1") || + argc == 5 && !wcscmp(argv[4], L"-1"); +} + +/** + * Determines whether the param only contains digits. + * + * @param str The string to check + * @param boolean True if the param only contains digits + */ +static bool +IsDigits(WCHAR *str) +{ + while (*str) + { + if (!iswdigit(*str++)) + { + return FALSE; + } + } + return TRUE; +} + +/** + * Determines whether the command line contains just the directory to apply the + * update to (old command line) or if it contains the installation directory and + * the directory to apply the update to. + * + * @param argc The argc value normally sent to updater.exe + * @param argv The argv value normally sent to updater.exe + * @param boolean True if the command line contains just the directory to apply + * the update to + */ +static bool +IsOldCommandline(int argc, LPWSTR *argv) +{ + return argc == 4 && !wcscmp(argv[3], L"-1") || + argc >= 4 && (wcsstr(argv[3], L"/replace") || IsDigits(argv[3])); +} + +/** + * Gets the installation directory from the arguments passed to updater.exe. + * + * @param argcTmp The argc value normally sent to updater.exe + * @param argvTmp The argv value normally sent to updater.exe + * @param aResultDir Buffer to hold the installation directory. + */ +static BOOL +GetInstallationDir(int argcTmp, LPWSTR *argvTmp, WCHAR aResultDir[MAX_PATH + 1]) +{ + int index = 3; + if (IsOldCommandline(argcTmp, argvTmp)) + { + index = 2; + } + + if (argcTmp < index) + { + return FALSE; + } + + wcsncpy(aResultDir, argvTmp[2], MAX_PATH); + WCHAR* backSlash = wcsrchr(aResultDir, L'\\'); + // Make sure that the path does not include trailing backslashes + if (backSlash && (backSlash[1] == L'\0')) + { + *backSlash = L'\0'; + } + + // The new command line's argv[2] is always the installation directory. + if (index == 2) + { + bool backgroundUpdate = IsUpdateBeingStaged(argcTmp, argvTmp); + bool replaceRequest = (argcTmp >= 4 && wcsstr(argvTmp[3], L"/replace")); + if (backgroundUpdate || replaceRequest) + { + return PathRemoveFileSpecW(aResultDir); + } + } + return TRUE; +} + +/** + * Runs an update process as the service using the SYSTEM account. + * + * @param argc The number of arguments in argv + * @param argv The arguments normally passed to updater.exe + * argv[0] must be the path to updater.exe + * @param processStarted Set to TRUE if the process was started. + * @return TRUE if the update process was run had a return code of 0. + */ +BOOL +StartUpdateProcess(int argc, + LPWSTR *argv, + LPCWSTR installDir, + BOOL &processStarted) +{ + LOG(("Starting update process as the service in session 0.")); + STARTUPINFO si = {0}; + si.cb = sizeof(STARTUPINFO); + si.lpDesktop = L"winsta0\\Default"; + PROCESS_INFORMATION pi = {0}; + + // The updater command line is of the form: + // updater.exe update-dir apply [wait-pid [callback-dir callback-path args]] + LPWSTR cmdLine = MakeCommandLine(argc, argv); + + int index = 3; + if (IsOldCommandline(argc, argv)) + { + index = 2; + } + + // If we're about to start the update process from session 0, + // then we should not show a GUI. This only really needs to be done + // on Vista and higher, but it's better to keep everything consistent + // across all OS if it's of no harm. + if (argc >= index) + { + // Setting the desktop to blank will ensure no GUI is displayed + si.lpDesktop = L""; + si.dwFlags |= STARTF_USESHOWWINDOW; + si.wShowWindow = SW_HIDE; + } + + // We move the updater.ini file out of the way because we will handle + // executing PostUpdate through the service. We handle PostUpdate from + // the service because there are some per user things that happen that + // can't run in session 0 which we run updater.exe in. + // Once we are done running updater.exe we rename updater.ini back so + // that if there were any errors the next updater.exe will run correctly. + WCHAR updaterINI[MAX_PATH + 1]; + WCHAR updaterINITemp[MAX_PATH + 1]; + BOOL selfHandlePostUpdate = FALSE; + // We use the updater.ini from the same directory as the updater.exe + // because of background updates. + if (PathGetSiblingFilePath(updaterINI, argv[0], L"updater.ini") && + PathGetSiblingFilePath(updaterINITemp, argv[0], L"updater.tmp")) + { + selfHandlePostUpdate = MoveFileExW(updaterINI, updaterINITemp, + MOVEFILE_REPLACE_EXISTING); + } + + // Add an env var for USING_SERVICE so the updater.exe can + // do anything special that it needs to do for service updates. + // Search in updater.cpp for more info on USING_SERVICE. + putenv(const_cast<char*>("USING_SERVICE=1")); + LOG(("Starting service with cmdline: %ls", cmdLine)); + processStarted = CreateProcessW(argv[0], cmdLine, + nullptr, nullptr, FALSE, + CREATE_DEFAULT_ERROR_MODE, + nullptr, + nullptr, &si, &pi); + // Empty value on putenv is how you remove an env variable in Windows + putenv(const_cast<char*>("USING_SERVICE=")); + + BOOL updateWasSuccessful = FALSE; + if (processStarted) + { + // Wait for the updater process to finish + LOG(("Process was started... waiting on result.")); + DWORD waitRes = WaitForSingleObject(pi.hProcess, TIME_TO_WAIT_ON_UPDATER); + if (WAIT_TIMEOUT == waitRes) + { + // We waited a long period of time for updater.exe and it never finished + // so kill it. + TerminateProcess(pi.hProcess, 1); + } + else + { + // Check the return code of updater.exe to make sure we get 0 + DWORD returnCode; + if (GetExitCodeProcess(pi.hProcess, &returnCode)) + { + LOG(("Process finished with return code %d.", returnCode)); + // updater returns 0 if successful. + updateWasSuccessful = (returnCode == 0); + } + else + { + LOG_WARN(("Process finished but could not obtain return code.")); + } + } + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + + // Check just in case updater.exe didn't change the status from + // applying. If this is the case we report an error. + BOOL isApplying = FALSE; + if (IsStatusApplying(argv[1], isApplying) && isApplying) + { + if (updateWasSuccessful) + { + LOG(("update.status is still applying even know update " + " was successful.")); + if (!WriteStatusFailure(argv[1], + SERVICE_STILL_APPLYING_ON_SUCCESS)) + { + LOG_WARN(("Could not write update.status still applying on" + " success error.")); + } + // Since we still had applying we know updater.exe didn't do its + // job correctly. + updateWasSuccessful = FALSE; + } + else + { + LOG_WARN(("update.status is still applying and update was not successful.")); + if (!WriteStatusFailure(argv[1], + SERVICE_STILL_APPLYING_ON_FAILURE)) + { + LOG_WARN(("Could not write update.status still applying on" + " success error.")); + } + } + } + } + else + { + DWORD lastError = GetLastError(); + LOG_WARN(("Could not create process as current user, " + "updaterPath: %ls; cmdLine: %ls. (%d)", + argv[0], cmdLine, lastError)); + } + + // Now that we're done with the update, restore back the updater.ini file + // We use it ourselves, and also we want it back in case we had any type + // of error so that the normal update process can use it. + if (selfHandlePostUpdate) + { + MoveFileExW(updaterINITemp, updaterINI, MOVEFILE_REPLACE_EXISTING); + + // Only run the PostUpdate if the update was successful + if (updateWasSuccessful && argc > index) + { + LPCWSTR updateInfoDir = argv[1]; + bool stagingUpdate = IsUpdateBeingStaged(argc, argv); + + // Launch the PostProcess with admin access in session 0. This is + // actually launching the post update process but it takes in the + // callback app path to figure out where to apply to. + // The PostUpdate process with user only access will be done inside + // the unelevated updater.exe after the update process is complete + // from the service. We don't know here which session to start + // the user PostUpdate process from. + // Note that we don't need to do this if we're just staging the + // update in the background, as the PostUpdate step runs when + // performing the replacing in that case. + if (!stagingUpdate) + { + LOG(("Launching post update process as the service in session 0.")); + if (!LaunchWinPostProcess(installDir, updateInfoDir, true, nullptr)) + { + LOG_WARN(("The post update process could not be launched." + " installDir: %ls, updateInfoDir: %ls", + installDir, updateInfoDir)); + } + } + } + } + + free(cmdLine); + return updateWasSuccessful; +} + +/** + * Processes a software update command + * + * @param argc The number of arguments in argv + * @param argv The arguments normally passed to updater.exe + * argv[0] must be the path to updater.exe + * @return TRUE if the update was successful. + */ +BOOL +ProcessSoftwareUpdateCommand(DWORD argc, LPWSTR *argv) +{ + BOOL result = TRUE; + if (argc < 3) + { + LOG_WARN(("Not enough command line parameters specified. " + "Updating update.status.")); + + // We can only update update.status if argv[1] exists. argv[1] is + // the directory where the update.status file exists. + if (argc < 2 || + !WriteStatusFailure(argv[1], + SERVICE_NOT_ENOUGH_COMMAND_LINE_ARGS)) + { + LOG_WARN(("Could not write update.status service update failure. (%d)", + GetLastError())); + } + return FALSE; + } + + WCHAR installDir[MAX_PATH + 1] = {L'\0'}; + if (!GetInstallationDir(argc, argv, installDir)) + { + LOG_WARN(("Could not get the installation directory")); + if (!WriteStatusFailure(argv[1], + SERVICE_INSTALLDIR_ERROR)) + { + LOG_WARN(("Could not write update.status for GetInstallationDir failure.")); + } + return FALSE; + } + + // Make sure the path to the updater to use for the update is local. + // We do this check to make sure that file locking is available for + // race condition security checks. + BOOL isLocal = FALSE; + if (!IsLocalFile(argv[0], isLocal) || !isLocal) + { + LOG_WARN(("Filesystem in path %ls is not supported (%d)", + argv[0], GetLastError())); + if (!WriteStatusFailure(argv[1], + SERVICE_UPDATER_NOT_FIXED_DRIVE)) + { + LOG_WARN(("Could not write update.status service update failure. (%d)", + GetLastError())); + } + return FALSE; + } + + AutoHandle noWriteLock(CreateFileW(argv[0], GENERIC_READ, FILE_SHARE_READ, + nullptr, OPEN_EXISTING, 0, nullptr)); + if (noWriteLock == INVALID_HANDLE_VALUE) + { + LOG_WARN(("Could not set no write sharing access on file. (%d)", + GetLastError())); + if (!WriteStatusFailure(argv[1], + SERVICE_COULD_NOT_LOCK_UPDATER)) + { + LOG_WARN(("Could not write update.status service update failure. (%d)", + GetLastError())); + } + return FALSE; + } + + // Verify that the updater.exe that we are executing is the same + // as the one in the installation directory which we are updating. + // The installation dir that we are installing to is installDir. + WCHAR installDirUpdater[MAX_PATH + 1] = { L'\0' }; + wcsncpy(installDirUpdater, installDir, MAX_PATH); + if (!PathAppendSafe(installDirUpdater, L"updater.exe")) + { + LOG_WARN(("Install directory updater could not be determined.")); + result = FALSE; + } + + BOOL updaterIsCorrect = FALSE; + if (result && !VerifySameFiles(argv[0], installDirUpdater, + updaterIsCorrect)) + { + LOG_WARN(("Error checking if the updaters are the same.\n" + "Path 1: %ls\nPath 2: %ls", argv[0], installDirUpdater)); + result = FALSE; + } + + if (result && !updaterIsCorrect) + { + LOG_WARN(("The updaters do not match, updater will not run.\n" + "Path 1: %ls\nPath 2: %ls", argv[0], installDirUpdater)); + result = FALSE; + } + + if (result) + { + LOG(("updater.exe was compared successfully to the installation directory" + " updater.exe.")); + } + else + { + if (!WriteStatusFailure(argv[1], + SERVICE_UPDATER_COMPARE_ERROR)) + { + LOG_WARN(("Could not write update.status updater compare failure.")); + } + return FALSE; + } + + // Check to make sure the updater.exe module has the unique updater identity. + // This is a security measure to make sure that the signed executable that + // we will run is actually an updater. + HMODULE updaterModule = LoadLibraryEx(argv[0], nullptr, + LOAD_LIBRARY_AS_DATAFILE); + if (!updaterModule) + { + LOG_WARN(("updater.exe module could not be loaded. (%d)", GetLastError())); + result = FALSE; + } + else + { + char updaterIdentity[64]; + if (!LoadStringA(updaterModule, IDS_UPDATER_IDENTITY, + updaterIdentity, sizeof(updaterIdentity))) + { + LOG_WARN(("The updater.exe application does not contain the Mozilla" + " updater identity.")); + result = FALSE; + } + + if (strcmp(updaterIdentity, UPDATER_IDENTITY_STRING)) + { + LOG_WARN(("The updater.exe identity string is not valid.")); + result = FALSE; + } + FreeLibrary(updaterModule); + } + + if (result) + { + LOG(("The updater.exe application contains the Mozilla" + " updater identity.")); + } + else + { + if (!WriteStatusFailure(argv[1], + SERVICE_UPDATER_IDENTITY_ERROR)) + { + LOG_WARN(("Could not write update.status no updater identity.")); + } + return TRUE; + } + + // Check for updater.exe sign problems + BOOL updaterSignProblem = FALSE; +#ifndef DISABLE_UPDATER_AUTHENTICODE_CHECK + updaterSignProblem = !DoesBinaryMatchAllowedCertificates(installDir, + argv[0]); +#endif + + // Only proceed with the update if we have no signing problems + if (!updaterSignProblem) + { + BOOL updateProcessWasStarted = FALSE; + if (StartUpdateProcess(argc, argv, installDir, + updateProcessWasStarted)) + { + LOG(("updater.exe was launched and run successfully!")); + LogFlush(); + + // Don't attempt to update the service when the update is being staged. + if (!IsUpdateBeingStaged(argc, argv)) + { + // We might not execute code after StartServiceUpdate because + // the service installer will stop the service if it is running. + StartServiceUpdate(installDir); + } + } + else + { + result = FALSE; + LOG_WARN(("Error running update process. Updating update.status (%d)", + GetLastError())); + LogFlush(); + + // If the update process was started, then updater.exe is responsible for + // setting the failure code. If it could not be started then we do the + // work. We set an error instead of directly setting status pending + // so that the app.update.service.errors pref can be updated when + // the callback app restarts. + if (!updateProcessWasStarted) + { + if (!WriteStatusFailure(argv[1], + SERVICE_UPDATER_COULD_NOT_BE_STARTED)) + { + LOG_WARN(("Could not write update.status service update failure. (%d)", + GetLastError())); + } + } + } + } + else + { + result = FALSE; + LOG_WARN(("Could not start process due to certificate check error on " + "updater.exe. Updating update.status. (%d)", GetLastError())); + + // When there is a certificate check error on the updater.exe application, + // we want to write out the error. + if (!WriteStatusFailure(argv[1], + SERVICE_UPDATER_SIGN_ERROR)) + { + LOG_WARN(("Could not write pending state to update.status. (%d)", + GetLastError())); + } + } + + return result; +} + +/** + * Obtains the updater path alongside a subdir of the service binary. + * The purpose of this function is to return a path that is likely high + * integrity and therefore more safe to execute code from. + * + * @param serviceUpdaterPath Out parameter for the path where the updater + * should be copied to. + * @return TRUE if a file path was obtained. + */ +BOOL +GetSecureUpdaterPath(WCHAR serviceUpdaterPath[MAX_PATH + 1]) +{ + if (!GetModuleFileNameW(nullptr, serviceUpdaterPath, MAX_PATH)) + { + LOG_WARN(("Could not obtain module filename when attempting to " + "use a secure updater path. (%d)", GetLastError())); + return FALSE; + } + + if (!PathRemoveFileSpecW(serviceUpdaterPath)) + { + LOG_WARN(("Couldn't remove file spec when attempting to use a secure " + "updater path. (%d)", GetLastError())); + return FALSE; + } + + if (!PathAppendSafe(serviceUpdaterPath, L"update")) + { + LOG_WARN(("Couldn't append file spec when attempting to use a secure " + "updater path. (%d)", GetLastError())); + return FALSE; + } + + CreateDirectoryW(serviceUpdaterPath, nullptr); + + if (!PathAppendSafe(serviceUpdaterPath, L"updater.exe")) + { + LOG_WARN(("Couldn't append file spec when attempting to use a secure " + "updater path. (%d)", GetLastError())); + return FALSE; + } + + return TRUE; +} + +/** + * Deletes the passed in updater path and the associated updater.ini file. + * + * @param serviceUpdaterPath The path to delete. + * @return TRUE if a file was deleted. + */ +BOOL +DeleteSecureUpdater(WCHAR serviceUpdaterPath[MAX_PATH + 1]) +{ + BOOL result = FALSE; + if (serviceUpdaterPath[0]) + { + result = DeleteFileW(serviceUpdaterPath); + if (!result && GetLastError() != ERROR_PATH_NOT_FOUND && + GetLastError() != ERROR_FILE_NOT_FOUND) + { + LOG_WARN(("Could not delete service updater path: '%ls'.", + serviceUpdaterPath)); + } + + WCHAR updaterINIPath[MAX_PATH + 1] = { L'\0' }; + if (PathGetSiblingFilePath(updaterINIPath, serviceUpdaterPath, + L"updater.ini")) + { + result = DeleteFileW(updaterINIPath); + if (!result && GetLastError() != ERROR_PATH_NOT_FOUND && + GetLastError() != ERROR_FILE_NOT_FOUND) + { + LOG_WARN(("Could not delete service updater INI path: '%ls'.", + updaterINIPath)); + } + } + } + return result; +} + +/** + * Executes a service command. + * + * @param argc The number of arguments in argv + * @param argv The service command line arguments, argv[0] and argv[1] + * and automatically included by Windows. argv[2] is the + * service command. + * + * @return FALSE if there was an error executing the service command. + */ +BOOL +ExecuteServiceCommand(int argc, LPWSTR *argv) +{ + if (argc < 3) + { + LOG_WARN(("Not enough command line arguments to execute a service command")); + return FALSE; + } + + // The tests work by making sure the log has changed, so we put a + // unique ID in the log. + RPC_WSTR guidString = RPC_WSTR(L""); + GUID guid; + HRESULT hr = CoCreateGuid(&guid); + if (SUCCEEDED(hr)) + { + UuidToString(&guid, &guidString); + } + LOG(("Executing service command %ls, ID: %ls", + argv[2], reinterpret_cast<LPCWSTR>(guidString))); + RpcStringFree(&guidString); + + BOOL result = FALSE; + if (!lstrcmpi(argv[2], L"software-update")) + { + + // Use the passed in command line arguments for the update, except for the + // path to updater.exe. We copy updater.exe to the directory of the + // MozillaMaintenance service so that a low integrity process cannot + // replace the updater.exe at any point and use that for the update. + // It also makes DLL injection attacks harder. + LPWSTR oldUpdaterPath = argv[3]; + WCHAR secureUpdaterPath[MAX_PATH + 1] = { L'\0' }; + result = GetSecureUpdaterPath(secureUpdaterPath); // Does its own logging + if (result) + { + LOG(("Passed in path: '%ls'; Using this path for updating: '%ls'.", + oldUpdaterPath, secureUpdaterPath)); + DeleteSecureUpdater(secureUpdaterPath); + result = CopyFileW(oldUpdaterPath, secureUpdaterPath, FALSE); + } + + if (!result) + { + LOG_WARN(("Could not copy path to secure location. (%d)", + GetLastError())); + if (argc > 4 && !WriteStatusFailure(argv[4], + SERVICE_COULD_NOT_COPY_UPDATER)) + { + LOG_WARN(("Could not write update.status could not copy updater error")); + } + } + else + { + + // We obtained the path and copied it successfully, update the path to + // use for the service update. + argv[3] = secureUpdaterPath; + + WCHAR oldUpdaterINIPath[MAX_PATH + 1] = { L'\0' }; + WCHAR secureUpdaterINIPath[MAX_PATH + 1] = { L'\0' }; + if (PathGetSiblingFilePath(secureUpdaterINIPath, secureUpdaterPath, + L"updater.ini") && + PathGetSiblingFilePath(oldUpdaterINIPath, oldUpdaterPath, + L"updater.ini")) + { + // This is non fatal if it fails there is no real harm + if (!CopyFileW(oldUpdaterINIPath, secureUpdaterINIPath, FALSE)) + { + LOG_WARN(("Could not copy updater.ini from: '%ls' to '%ls'. (%d)", + oldUpdaterINIPath, secureUpdaterINIPath, GetLastError())); + } + } + + result = ProcessSoftwareUpdateCommand(argc - 3, argv + 3); + DeleteSecureUpdater(secureUpdaterPath); + } + + // We might not reach here if the service install succeeded + // because the service self updates itself and the service + // installer will stop the service. + LOG(("Service command %ls complete.", argv[2])); + } + else + { + LOG_WARN(("Service command not recognized: %ls.", argv[2])); + // result is already set to FALSE + } + + LOG(("service command %ls complete with result: %ls.", + argv[1], (result ? L"Success" : L"Failure"))); + return TRUE; +} diff --git a/onlineupdate/source/service/workmonitor.hxx b/onlineupdate/source/service/workmonitor.hxx new file mode 100644 index 000000000..ac4cd679b --- /dev/null +++ b/onlineupdate/source/service/workmonitor.hxx @@ -0,0 +1,5 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +BOOL ExecuteServiceCommand(int argc, LPWSTR *argv); diff --git a/onlineupdate/source/update/common/errors.h b/onlineupdate/source/update/common/errors.h new file mode 100644 index 000000000..95bdcdfa7 --- /dev/null +++ b/onlineupdate/source/update/common/errors.h @@ -0,0 +1,96 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef Errors_h__ +#define Errors_h__ + +#define OK 0 + +// Error codes that are no longer used should not be used again unless they +// aren't used in client code (e.g. nsUpdateService.js, updates.js, +// UpdatePrompt.js, etc.). + +#define MAR_ERROR_EMPTY_ACTION_LIST 1 +#define LOADSOURCE_ERROR_WRONG_SIZE 2 + +// Error codes 3-16 are for general update problems. +#define USAGE_ERROR 3 +#define CRC_ERROR 4 +#define PARSE_ERROR 5 +#define READ_ERROR 6 +#define WRITE_ERROR 7 +// #define UNEXPECTED_ERROR 8 // Replaced with errors 38-42 +#define ELEVATION_CANCELED 9 +#define READ_STRINGS_MEM_ERROR 10 +#define ARCHIVE_READER_MEM_ERROR 11 +#define BSPATCH_MEM_ERROR 12 +#define UPDATER_MEM_ERROR 13 +#define UPDATER_QUOTED_PATH_MEM_ERROR 14 +#define BAD_ACTION_ERROR 15 +#define STRING_CONVERSION_ERROR 16 + +// Error codes 17-23 are related to security tasks for MAR +// signing and MAR protection. +#define CERT_LOAD_ERROR 17 +#define CERT_HANDLING_ERROR 18 +#define CERT_VERIFY_ERROR 19 +#define ARCHIVE_NOT_OPEN 20 +#define COULD_NOT_READ_PRODUCT_INFO_BLOCK_ERROR 21 +#define MAR_CHANNEL_MISMATCH_ERROR 22 +#define VERSION_DOWNGRADE_ERROR 23 + +// Error codes 24-33 and 49-51 are for the Windows maintenance service. +#define SERVICE_UPDATER_COULD_NOT_BE_STARTED 24 +#define SERVICE_NOT_ENOUGH_COMMAND_LINE_ARGS 25 +#define SERVICE_UPDATER_SIGN_ERROR 26 +#define SERVICE_UPDATER_COMPARE_ERROR 27 +#define SERVICE_UPDATER_IDENTITY_ERROR 28 +#define SERVICE_STILL_APPLYING_ON_SUCCESS 29 +#define SERVICE_STILL_APPLYING_ON_FAILURE 30 +#define SERVICE_UPDATER_NOT_FIXED_DRIVE 31 +#define SERVICE_COULD_NOT_LOCK_UPDATER 32 +#define SERVICE_INSTALLDIR_ERROR 33 + +#define NO_INSTALLDIR_ERROR 34 +#define WRITE_ERROR_ACCESS_DENIED 35 +// #define WRITE_ERROR_SHARING_VIOLATION 36 // Replaced with errors 46-48 +#define WRITE_ERROR_CALLBACK_APP 37 +#define UNEXPECTED_BZIP_ERROR 39 +#define UNEXPECTED_MAR_ERROR 40 +#define UNEXPECTED_BSPATCH_ERROR 41 +#define UNEXPECTED_FILE_OPERATION_ERROR 42 +#define FILESYSTEM_MOUNT_READWRITE_ERROR 43 +#define DELETE_ERROR_EXPECTED_DIR 46 +#define DELETE_ERROR_EXPECTED_FILE 47 +#define RENAME_ERROR_EXPECTED_FILE 48 + +// Error codes 24-33 and 49-51 are for the Windows maintenance service. +#define SERVICE_COULD_NOT_COPY_UPDATER 49 +#define SERVICE_STILL_APPLYING_TERMINATED 50 +#define SERVICE_STILL_APPLYING_NO_EXIT_CODE 51 + +#define WRITE_ERROR_FILE_COPY 61 +#define WRITE_ERROR_DELETE_FILE 62 +#define WRITE_ERROR_OPEN_PATCH_FILE 63 +#define WRITE_ERROR_PATCH_FILE 64 +#define WRITE_ERROR_APPLY_DIR_PATH 65 +#define WRITE_ERROR_CALLBACK_PATH 66 +#define WRITE_ERROR_FILE_ACCESS_DENIED 67 +#define WRITE_ERROR_DIR_ACCESS_DENIED 68 +#define WRITE_ERROR_DELETE_BACKUP 69 +#define WRITE_ERROR_EXTRACT 70 + +// Error codes 80 through 99 are reserved for nsUpdateService.js + +// The following error codes are only used by updater.exe +// when a fallback key exists for tests. +#define FALLBACKKEY_UNKNOWN_ERROR 100 +#define FALLBACKKEY_REGPATH_ERROR 101 +#define FALLBACKKEY_NOKEY_ERROR 102 +#define FALLBACKKEY_SERVICE_NO_STOP_ERROR 103 +#define FALLBACKKEY_LAUNCH_ERROR 104 + +#endif // Errors_h__ diff --git a/onlineupdate/source/update/common/pathhash.cxx b/onlineupdate/source/update/common/pathhash.cxx new file mode 100644 index 000000000..67e53fa35 --- /dev/null +++ b/onlineupdate/source/update/common/pathhash.cxx @@ -0,0 +1,153 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifdef _WIN32 +#include <windows.h> +#include <wincrypt.h> +#include "pathhash.h" + + +/** + * Converts a binary sequence into a hex string + * + * @param hash The binary data sequence + * @param hashSize The size of the binary data sequence + * @param hexString A buffer to store the hex string, must be of + * size 2 * @hashSize +*/ +static void +BinaryDataToHexString(const BYTE *hash, DWORD &hashSize, + LPWSTR hexString) +{ + WCHAR *p = hexString; + for (DWORD i = 0; i < hashSize; ++i) + { + wsprintfW(p, L"%.2x", hash[i]); + p += 2; + } +} + +/** + * Calculates an MD5 hash for the given input binary data + * + * @param data Any sequence of bytes + * @param dataSize The number of bytes inside @data + * @param hash Output buffer to store hash, must be freed by the caller + * @param hashSize The number of bytes in the output buffer + * @return TRUE on success +*/ +static BOOL +CalculateMD5(const char *data, DWORD dataSize, + BYTE **hash, DWORD &hashSize) +{ + HCRYPTPROV hProv = 0; + HCRYPTHASH hHash = 0; + + if (!CryptAcquireContext(&hProv, nullptr, nullptr, PROV_RSA_FULL, + CRYPT_VERIFYCONTEXT)) + { + if (NTE_BAD_KEYSET != GetLastError()) + { + return FALSE; + } + + // Maybe it doesn't exist, try to create it. + if (!CryptAcquireContext(&hProv, nullptr, nullptr, PROV_RSA_FULL, + CRYPT_VERIFYCONTEXT | CRYPT_NEWKEYSET)) + { + return FALSE; + } + } + + if (!CryptCreateHash(hProv, CALG_MD5, 0, 0, &hHash)) + { + return FALSE; + } + + if (!CryptHashData(hHash, reinterpret_cast<const BYTE*>(data), + dataSize, 0)) + { + return FALSE; + } + + DWORD dwCount = sizeof(DWORD); + if (!CryptGetHashParam(hHash, HP_HASHSIZE, (BYTE *)&hashSize, + &dwCount, 0)) + { + return FALSE; + } + + *hash = new BYTE[hashSize]; + ZeroMemory(*hash, hashSize); + if (!CryptGetHashParam(hHash, HP_HASHVAL, *hash, &hashSize, 0)) + { + return FALSE; + } + + if (hHash) + { + CryptDestroyHash(hHash); + } + + if (hProv) + { + CryptReleaseContext(hProv,0); + } + + return TRUE; +} + +/** + * Converts a file path into a unique registry location for cert storage + * + * @param filePath The input file path to get a registry path from + * @param registryPath A buffer to write the registry path to, must + * be of size in WCHARs MAX_PATH + 1 + * @return TRUE if successful +*/ +BOOL +CalculateRegistryPathFromFilePath(const LPCWSTR filePath, + LPWSTR registryPath) +{ + size_t filePathLen = wcslen(filePath); + if (!filePathLen) + { + return FALSE; + } + + // If the file path ends in a slash, ignore that character + if (filePath[filePathLen -1] == L'\\' || + filePath[filePathLen - 1] == L'/') + { + filePathLen--; + } + + // Copy in the full path into our own buffer. + // Copying in the extra slash is OK because we calculate the hash + // based on the filePathLen which excludes the slash. + // +2 to account for the possibly trailing slash and the null terminator. + WCHAR *lowercasePath = new WCHAR[filePathLen + 2]; + memset(lowercasePath, 0, (filePathLen + 2) * sizeof(WCHAR)); + wcsncpy(lowercasePath, filePath, filePathLen + 1); + _wcslwr(lowercasePath); + + BYTE *hash; + DWORD hashSize = 0; + if (!CalculateMD5(reinterpret_cast<const char*>(lowercasePath), + filePathLen * 2, + &hash, hashSize)) + { + delete[] lowercasePath; + return FALSE; + } + delete[] lowercasePath; + + LPCWSTR baseRegPath = L"SOFTWARE\\LibreOffice\\MaintenanceService\\"; + wcsncpy(registryPath, baseRegPath, MAX_PATH); + BinaryDataToHexString(hash, hashSize, + registryPath + wcslen(baseRegPath)); + delete[] hash; + return TRUE; +} +#endif diff --git a/onlineupdate/source/update/common/pathhash.h b/onlineupdate/source/update/common/pathhash.h new file mode 100644 index 000000000..9856b4cf4 --- /dev/null +++ b/onlineupdate/source/update/common/pathhash.h @@ -0,0 +1,19 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _PATHHASH_H_ +#define _PATHHASH_H_ + +/** + * Converts a file path into a unique registry location for cert storage + * + * @param filePath The input file path to get a registry path from + * @param registryPath A buffer to write the registry path to, must + * be of size in WCHARs MAX_PATH + 1 + * @return TRUE if successful +*/ +BOOL CalculateRegistryPathFromFilePath(const LPCWSTR filePath, + LPWSTR registryPath); + +#endif diff --git a/onlineupdate/source/update/common/readstrings.cxx b/onlineupdate/source/update/common/readstrings.cxx new file mode 100644 index 000000000..e1366cddd --- /dev/null +++ b/onlineupdate/source/update/common/readstrings.cxx @@ -0,0 +1,267 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <limits.h> +#include <string.h> +#include <stdio.h> +#include "readstrings.h" +#include "errors.h" + +#ifdef _WIN32 +# define NS_tfopen _wfopen +# define OPEN_MODE L"rb" +#else +# define NS_tfopen fopen +# define OPEN_MODE "r" +#endif + +// stack based FILE wrapper to ensure that fclose is called. +class AutoFILE +{ +public: + explicit AutoFILE(FILE *fp) : fp_(fp) {} + ~AutoFILE() + { + if (fp_) fclose(fp_); + } + operator FILE *() + { + return fp_; + } +private: + FILE *fp_; +}; + +class AutoCharArray +{ +public: + explicit AutoCharArray(size_t len) + { + ptr_ = new char[len]; + } + ~AutoCharArray() + { + delete[] ptr_; + } + operator char *() + { + return ptr_; + } +private: + char *ptr_; +}; + +static const char kNL[] = "\r\n"; +static const char kEquals[] = "="; +static const char kWhitespace[] = " \t"; +static const char kRBracket[] = "]"; + +static const char* +NS_strspnp(const char *delims, const char *str) +{ + const char *d; + do + { + for (d = delims; *d != '\0'; ++d) + { + if (*str == *d) + { + ++str; + break; + } + } + } + while (*d); + + return str; +} + +static char* +NS_strtok(const char *delims, char **str) +{ + if (!*str) + return nullptr; + + char *ret = (char*) NS_strspnp(delims, *str); + + if (!*ret) + { + *str = ret; + return nullptr; + } + + char *i = ret; + do + { + for (const char *d = delims; *d != '\0'; ++d) + { + if (*i == *d) + { + *i = '\0'; + *str = ++i; + return ret; + } + } + ++i; + } + while (*i); + + *str = nullptr; + return ret; +} + +/** + * Find a key in a keyList containing zero-delimited keys ending with "\0\0". + * Returns a zero-based index of the key in the list, or -1 if the key is not found. + */ +static int +find_key(const char *keyList, char* key) +{ + if (!keyList) + return -1; + + int index = 0; + const char *p = keyList; + while (*p) + { + if (strcmp(key, p) == 0) + return index; + + p += strlen(p) + 1; + index++; + } + + // The key was not found if we came here + return -1; +} + +/** + * A very basic parser for updater.ini taken mostly from nsINIParser.cpp + * that can be used by standalone apps. + * + * @param path Path to the .ini file to read + * @param keyList List of zero-delimited keys ending with two zero characters + * @param numStrings Number of strings to read into results buffer - must be equal to the number of keys + * @param results Two-dimensional array of strings to be filled in the same order as the keys provided + * @param section Optional name of the section to read; defaults to "Strings" + */ +int +ReadStrings(const NS_tchar *path, + const char *keyList, + unsigned int numStrings, + char results[][MAX_TEXT_LEN], + const char *section) +{ + AutoFILE fp(NS_tfopen(path, OPEN_MODE)); + + if (!fp) + return READ_ERROR; + + /* get file size */ + if (fseek(fp, 0, SEEK_END) != 0) + return READ_ERROR; + + long len = ftell(fp); + if (len <= 0) + return READ_ERROR; + + size_t flen = size_t(len); + AutoCharArray fileContents(flen + 1); + if (!fileContents) + return READ_STRINGS_MEM_ERROR; + + /* read the file in one swoop */ + if (fseek(fp, 0, SEEK_SET) != 0) + return READ_ERROR; + + size_t rd = fread(fileContents, sizeof(char), flen, fp); + if (rd != flen) + return READ_ERROR; + + fileContents[flen] = '\0'; + + char *buffer = fileContents; + bool inStringsSection = false; + + unsigned int read = 0; + + while (char *token = NS_strtok(kNL, &buffer)) + { + if (token[0] == '#' || token[0] == ';') // it's a comment + continue; + + token = (char*) NS_strspnp(kWhitespace, token); + if (!*token) // empty line + continue; + + if (token[0] == '[') // section header! + { + ++token; + char const * currSection = token; + + char *rb = NS_strtok(kRBracket, &token); + if (!rb || NS_strtok(kWhitespace, &token)) + { + // there's either an unclosed [Section or a [Section]Moretext! + // we could frankly decide that this INI file is malformed right + // here and stop, but we won't... keep going, looking for + // a well-formed [section] to continue working with + inStringsSection = false; + } + else + { + if (section) + inStringsSection = strcmp(currSection, section) == 0; + else + inStringsSection = strcmp(currSection, "Strings") == 0; + } + + continue; + } + + if (!inStringsSection) + { + // If we haven't found a section header (or we found a malformed + // section header), or this isn't the [Strings] section don't bother + // parsing this line. + continue; + } + + char *key = token; + char *e = NS_strtok(kEquals, &token); + if (!e) + continue; + + int keyIndex = find_key(keyList, key); + if (keyIndex >= 0 && (unsigned int)keyIndex < numStrings) + { + strncpy(results[keyIndex], token, MAX_TEXT_LEN - 1); + results[keyIndex][MAX_TEXT_LEN - 1] = '\0'; + read++; + } + } + + return (read == numStrings) ? OK : PARSE_ERROR; +} + +// A wrapper function to read strings for the updater. +// Added for compatibility with the original code. +int +ReadStrings(const NS_tchar *path, StringTable *results) +{ + const unsigned int kNumStrings = 2; + const char *kUpdaterKeys = "Title\0Info\0"; + char updater_strings[kNumStrings][MAX_TEXT_LEN]; + + int result = ReadStrings(path, kUpdaterKeys, kNumStrings, updater_strings); + + strncpy(results->title, updater_strings[0], MAX_TEXT_LEN - 1); + results->title[MAX_TEXT_LEN - 1] = '\0'; + strncpy(results->info, updater_strings[1], MAX_TEXT_LEN - 1); + results->info[MAX_TEXT_LEN - 1] = '\0'; + + return result; +} diff --git a/onlineupdate/source/update/common/readstrings.h b/onlineupdate/source/update/common/readstrings.h new file mode 100644 index 000000000..747081394 --- /dev/null +++ b/onlineupdate/source/update/common/readstrings.h @@ -0,0 +1,38 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef READSTRINGS_H__ +#define READSTRINGS_H__ + +#define MAX_TEXT_LEN 600 + +#ifdef _WIN32 +# include <windows.h> +#endif + +#include "types.hxx" + +struct StringTable +{ + char title[MAX_TEXT_LEN]; + char info[MAX_TEXT_LEN]; +}; + +/** + * This function reads in localized strings from updater.ini + */ +int ReadStrings(const NS_tchar *path, StringTable *results); + +/** + * This function reads in localized strings corresponding to the keys from a given .ini + */ +int ReadStrings(const NS_tchar *path, + const char *keyList, + unsigned int numStrings, + char results[][MAX_TEXT_LEN], + const char *section = nullptr); + +#endif // READSTRINGS_H__ diff --git a/onlineupdate/source/update/common/sources.mozbuild b/onlineupdate/source/update/common/sources.mozbuild new file mode 100644 index 000000000..3de907b32 --- /dev/null +++ b/onlineupdate/source/update/common/sources.mozbuild @@ -0,0 +1,19 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +sources = [] + +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows': + sources += [ + 'pathhash.cpp', + 'uachelper.cpp', + 'updatehelper.cpp', + ] + +sources += [ + 'readstrings.cpp', + 'updatelogging.cpp', +] + +SOURCES += sorted(['%s/%s' % (srcdir, s) for s in sources]) diff --git a/onlineupdate/source/update/common/uachelper.cxx b/onlineupdate/source/update/common/uachelper.cxx new file mode 100644 index 000000000..831b12680 --- /dev/null +++ b/onlineupdate/source/update/common/uachelper.cxx @@ -0,0 +1,237 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifdef _WIN32 +#include <windows.h> +#include <wtsapi32.h> +#include "uachelper.h" +#include "updatelogging.h" + +// See the MSDN documentation with title: Privilege Constants +// At the time of this writing, this documentation is located at: +// http://msdn.microsoft.com/en-us/library/windows/desktop/bb530716%28v=vs.85%29.aspx +LPCTSTR UACHelper::PrivsToDisable[] = +{ + SE_ASSIGNPRIMARYTOKEN_NAME, + SE_AUDIT_NAME, + SE_BACKUP_NAME, + // CreateProcess will succeed but the app will fail to launch on some WinXP + // machines if SE_CHANGE_NOTIFY_NAME is disabled. In particular this happens + // for limited user accounts on those machines. The define is kept here as a + // reminder that it should never be re-added. + // This permission is for directory watching but also from MSDN: "This + // privilege also causes the system to skip all traversal access checks." + // SE_CHANGE_NOTIFY_NAME, + SE_CREATE_GLOBAL_NAME, + SE_CREATE_PAGEFILE_NAME, + SE_CREATE_PERMANENT_NAME, + SE_CREATE_SYMBOLIC_LINK_NAME, + SE_CREATE_TOKEN_NAME, + SE_DEBUG_NAME, + SE_ENABLE_DELEGATION_NAME, + SE_IMPERSONATE_NAME, + SE_INC_BASE_PRIORITY_NAME, + SE_INCREASE_QUOTA_NAME, + SE_INC_WORKING_SET_NAME, + SE_LOAD_DRIVER_NAME, + SE_LOCK_MEMORY_NAME, + SE_MACHINE_ACCOUNT_NAME, + SE_MANAGE_VOLUME_NAME, + SE_PROF_SINGLE_PROCESS_NAME, + SE_RELABEL_NAME, + SE_REMOTE_SHUTDOWN_NAME, + SE_RESTORE_NAME, + SE_SECURITY_NAME, + SE_SHUTDOWN_NAME, + SE_SYNC_AGENT_NAME, + SE_SYSTEM_ENVIRONMENT_NAME, + SE_SYSTEM_PROFILE_NAME, + SE_SYSTEMTIME_NAME, + SE_TAKE_OWNERSHIP_NAME, + SE_TCB_NAME, + SE_TIME_ZONE_NAME, + SE_TRUSTED_CREDMAN_ACCESS_NAME, + SE_UNDOCK_NAME, + SE_UNSOLICITED_INPUT_NAME +}; + +/** + * Opens a user token for the given session ID + * + * @param sessionID The session ID for the token to obtain + * @return A handle to the token to obtain which will be primary if enough + * permissions exist. Caller should close the handle. + */ +HANDLE +UACHelper::OpenUserToken(DWORD sessionID) +{ + HMODULE module = LoadLibraryW(L"wtsapi32.dll"); + HANDLE token = nullptr; + decltype(WTSQueryUserToken)* wtsQueryUserToken = + (decltype(WTSQueryUserToken)*) GetProcAddress(module, "WTSQueryUserToken"); + if (wtsQueryUserToken) + { + wtsQueryUserToken(sessionID, &token); + } + FreeLibrary(module); + return token; +} + +/** + * Opens a linked token for the specified token. + * + * @param token The token to get the linked token from + * @return A linked token or nullptr if one does not exist. + * Caller should close the handle. + */ +HANDLE +UACHelper::OpenLinkedToken(HANDLE token) +{ + // Magic below... + // UAC creates 2 tokens. One is the restricted token which we have. + // the other is the UAC elevated one. Since we are running as a service + // as the system account we have access to both. + TOKEN_LINKED_TOKEN tlt; + HANDLE hNewLinkedToken = nullptr; + DWORD len; + if (GetTokenInformation(token, (TOKEN_INFORMATION_CLASS)TokenLinkedToken, + &tlt, sizeof(TOKEN_LINKED_TOKEN), &len)) + { + token = tlt.LinkedToken; + hNewLinkedToken = token; + } + return hNewLinkedToken; +} + + +/** + * Enables or disables a privilege for the specified token. + * + * @param token The token to adjust the privilege on. + * @param priv The privilege to adjust. + * @param enable Whether to enable or disable it + * @return TRUE if the token was adjusted to the specified value. + */ +BOOL +UACHelper::SetPrivilege(HANDLE token, LPCTSTR priv, BOOL enable) +{ + LUID luidOfPriv; + if (!LookupPrivilegeValue(nullptr, priv, &luidOfPriv)) + { + return FALSE; + } + + TOKEN_PRIVILEGES tokenPriv; + tokenPriv.PrivilegeCount = 1; + tokenPriv.Privileges[0].Luid = luidOfPriv; + tokenPriv.Privileges[0].Attributes = enable ? SE_PRIVILEGE_ENABLED : 0; + + SetLastError(ERROR_SUCCESS); + if (!AdjustTokenPrivileges(token, false, &tokenPriv, + sizeof(tokenPriv), nullptr, nullptr)) + { + return FALSE; + } + + return GetLastError() == ERROR_SUCCESS; +} + +/** + * For each privilege that is specified, an attempt will be made to + * drop the privilege. + * + * @param token The token to adjust the privilege on. + * Pass nullptr for current token. + * @param unneededPrivs An array of unneeded privileges. + * @param count The size of the array + * @return TRUE if there were no errors + */ +BOOL +UACHelper::DisableUnneededPrivileges(HANDLE token, + LPCTSTR *unneededPrivs, + size_t count) +{ + HANDLE obtainedToken = nullptr; + if (!token) + { + // Note: This handle is a pseudo-handle and need not be closed + HANDLE process = GetCurrentProcess(); + if (!OpenProcessToken(process, TOKEN_ALL_ACCESS_P, &obtainedToken)) + { + LOG_WARN(("Could not obtain token for current process, no " + "privileges changed. (%d)", GetLastError())); + return FALSE; + } + token = obtainedToken; + } + + BOOL result = TRUE; + for (size_t i = 0; i < count; i++) + { + if (SetPrivilege(token, unneededPrivs[i], FALSE)) + { + LOG(("Disabled unneeded token privilege: %s.", + unneededPrivs[i])); + } + else + { + LOG(("Could not disable token privilege value: %s. (%d)", + unneededPrivs[i], GetLastError())); + result = FALSE; + } + } + + if (obtainedToken) + { + CloseHandle(obtainedToken); + } + return result; +} + +/** + * Disables privileges for the specified token. + * The privileges to disable are in PrivsToDisable. + * In the future there could be new privs and we are not sure if we should + * explicitly disable these or not. + * + * @param token The token to drop the privilege on. + * Pass nullptr for current token. + * @return TRUE if there were no errors + */ +BOOL +UACHelper::DisablePrivileges(HANDLE token) +{ + static const size_t PrivsToDisableSize = + sizeof(UACHelper::PrivsToDisable) / sizeof(UACHelper::PrivsToDisable[0]); + + return DisableUnneededPrivileges(token, UACHelper::PrivsToDisable, + PrivsToDisableSize); +} + +/** + * Check if the current user can elevate. + * + * @return true if the user can elevate. + * false otherwise. + */ +bool +UACHelper::CanUserElevate() +{ + HANDLE token; + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)) + { + return false; + } + + TOKEN_ELEVATION_TYPE elevationType; + DWORD len; + bool canElevate = GetTokenInformation(token, TokenElevationType, + &elevationType, + sizeof(elevationType), &len) && + (elevationType == TokenElevationTypeLimited); + CloseHandle(token); + + return canElevate; +} +#endif diff --git a/onlineupdate/source/update/common/uachelper.h b/onlineupdate/source/update/common/uachelper.h new file mode 100644 index 000000000..4e8d1c842 --- /dev/null +++ b/onlineupdate/source/update/common/uachelper.h @@ -0,0 +1,23 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _UACHELPER_H_ +#define _UACHELPER_H_ + +class UACHelper +{ +public: + static HANDLE OpenUserToken(DWORD sessionID); + static HANDLE OpenLinkedToken(HANDLE token); + static BOOL DisablePrivileges(HANDLE token); + static bool CanUserElevate(); + +private: + static BOOL SetPrivilege(HANDLE token, LPCTSTR privs, BOOL enable); + static BOOL DisableUnneededPrivileges(HANDLE token, + LPCTSTR *unneededPrivs, size_t count); + static LPCTSTR PrivsToDisable[]; +}; + +#endif diff --git a/onlineupdate/source/update/common/updatedefines.h b/onlineupdate/source/update/common/updatedefines.h new file mode 100644 index 000000000..748f9e098 --- /dev/null +++ b/onlineupdate/source/update/common/updatedefines.h @@ -0,0 +1,135 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef UPDATEDEFINES_H +#define UPDATEDEFINES_H + +#include "readstrings.h" + +#ifndef MAXPATHLEN +# ifdef PATH_MAX +# define MAXPATHLEN PATH_MAX +# elif defined(MAX_PATH) +# define MAXPATHLEN MAX_PATH +# elif defined(_MAX_PATH) +# define MAXPATHLEN _MAX_PATH +# elif defined(CCHMAXPATH) +# define MAXPATHLEN CCHMAXPATH +# else +# define MAXPATHLEN 1024 +# endif +#endif + +#if defined(_WIN32) +# include <windows.h> +# include <shlwapi.h> +# include <direct.h> +# include <io.h> +# include <stdio.h> +# include <stdarg.h> + +# define F_OK 00 +# define W_OK 02 +# define R_OK 04 +# define S_ISDIR(s) (((s) & _S_IFMT) == _S_IFDIR) +# define S_ISREG(s) (((s) & _S_IFMT) == _S_IFREG) + +# define access _access + +# define putenv _putenv +# define DELETE_DIR L"tobedeleted" +# define CALLBACK_BACKUP_EXT L".moz-callback" + +# define LOG_S "%S" +# define NS_T(str) L ## str +# define NS_SLASH NS_T('\\') + +static inline int mywcsprintf(WCHAR* dest, size_t count, const WCHAR* fmt, ...) +{ + size_t _count = count - 1; + va_list varargs; + va_start(varargs, fmt); + int result = _vsnwprintf(dest, count - 1, fmt, varargs); + va_end(varargs); + dest[_count] = L'\0'; + return result; +} +#define NS_tsnprintf mywcsprintf +# define NS_taccess _waccess +# define NS_tchdir _wchdir +# define NS_tchmod _wchmod +# define NS_tfopen _wfopen +# define NS_tmkdir(path, perms) _wmkdir(path) +# define NS_tremove _wremove +// _wrename is used to avoid the link tracking service. +# define NS_trename _wrename +# define NS_trmdir _wrmdir +# define NS_tstat _wstat +# define NS_tlstat _wstat // No symlinks on Windows +# define NS_tstat_t _stat +# define NS_tstrcat wcscat +# define NS_tstrcmp wcscmp +# define NS_tstrncmp wcsncmp +# define NS_tstricmp wcsicmp +# define NS_tstrcpy wcscpy +# define NS_tstrncpy wcsncpy +# define NS_tstrlen wcslen +# define NS_tstrchr wcschr +# define NS_tstrrchr wcsrchr +# define NS_tstrstr wcsstr +# include "win_dirent.h" +# define NS_tDIR DIR +# define NS_tdirent dirent +# define NS_topendir opendir +# define NS_tclosedir closedir +# define NS_treaddir readdir +#else +# include <sys/wait.h> +# include <unistd.h> + +#ifdef __sun +# include <sys/stat.h> +#else +# include <fts.h> +#endif +# include <dirent.h> + +#ifdef MACOSX +# include <sys/time.h> +#endif + +# define LOG_S "%s" +# define NS_T(str) str +# define NS_SLASH NS_T('/') +# define NS_tsnprintf snprintf +# define NS_taccess access +# define NS_tchdir chdir +# define NS_tchmod chmod +# define NS_tfopen fopen +# define NS_tmkdir mkdir +# define NS_tremove remove +# define NS_trename rename +# define NS_trmdir rmdir +# define NS_tstat stat +# define NS_tstat_t stat +# define NS_tlstat lstat +# define NS_tstrcat strcat +# define NS_tstrcmp strcmp +# define NS_tstrncmp strncmp +# define NS_tstricmp strcasecmp +# define NS_tstrcpy strcpy +# define NS_tstrncpy strncpy +# define NS_tstrlen strlen +# define NS_tstrrchr strrchr +# define NS_tstrstr strstr +# define NS_tDIR DIR +# define NS_tdirent dirent +# define NS_topendir opendir +# define NS_tclosedir closedir +# define NS_treaddir readdir +#endif + +#define BACKUP_EXT NS_T(".moz-backup") + +#endif diff --git a/onlineupdate/source/update/common/updatehelper.cxx b/onlineupdate/source/update/common/updatehelper.cxx new file mode 100644 index 000000000..09f857b1a --- /dev/null +++ b/onlineupdate/source/update/common/updatehelper.cxx @@ -0,0 +1,807 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifdef _WIN32 +#include <windows.h> + +// Needed for CreateToolhelp32Snapshot +#include <tlhelp32.h> +#ifndef ONLY_SERVICE_LAUNCHING + +#include <stdio.h> +#include "shlobj.h" +#include "updatehelper.h" +#include "uachelper.h" +#include "pathhash.h" + +#include <memory> + +// Needed for PathAppendW +#include <shlwapi.h> + +BOOL PathAppendSafe(LPWSTR base, LPCWSTR extra); + +/** + * Obtains the path of a file in the same directory as the specified file. + * + * @param destinationBuffer A buffer of size MAX_PATH + 1 to store the result. + * @param siblingFIlePath The path of another file in the same directory + * @param newFileName The filename of another file in the same directory + * @return TRUE if successful + */ +BOOL +PathGetSiblingFilePath(LPWSTR destinationBuffer, + LPCWSTR siblingFilePath, + LPCWSTR newFileName) +{ + if (wcslen(siblingFilePath) >= MAX_PATH) + { + return FALSE; + } + + wcsncpy(destinationBuffer, siblingFilePath, MAX_PATH); + if (!PathRemoveFileSpecW(destinationBuffer)) + { + return FALSE; + } + + if (wcslen(destinationBuffer) + wcslen(newFileName) >= MAX_PATH) + { + return FALSE; + } + + return PathAppendSafe(destinationBuffer, newFileName); +} + +/** + * Launch the post update application as the specified user (helper.exe). + * It takes in the path of the callback application to calculate the path + * of helper.exe. For service updates this is called from both the system + * account and the current user account. + * + * @param installationDir The path to the callback application binary. + * @param updateInfoDir The directory where update info is stored. + * @param forceSync If true even if the ini file specifies async, the + * process will wait for termination of PostUpdate. + * @param userToken The user token to run as, if nullptr the current + * user will be used. + * @return TRUE if there was no error starting the process. + */ +BOOL +LaunchWinPostProcess(const WCHAR *installationDir, + const WCHAR *updateInfoDir, + bool forceSync, + HANDLE userToken) +{ + WCHAR workingDirectory[MAX_PATH + 1] = { L'\0' }; + wcsncpy(workingDirectory, installationDir, MAX_PATH); + + // Launch helper.exe to perform post processing (e.g. registry and log file + // modifications) for the update. + WCHAR inifile[MAX_PATH + 1] = { L'\0' }; + wcsncpy(inifile, installationDir, MAX_PATH); + if (!PathAppendSafe(inifile, L"updater.ini")) + { + return FALSE; + } + + WCHAR exefile[MAX_PATH + 1]; + WCHAR exearg[MAX_PATH + 1]; + WCHAR exeasync[10]; + bool async = true; + if (!GetPrivateProfileStringW(L"PostUpdateWin", L"ExeRelPath", nullptr, + exefile, MAX_PATH + 1, inifile)) + { + return FALSE; + } + + if (!GetPrivateProfileStringW(L"PostUpdateWin", L"ExeArg", nullptr, exearg, + MAX_PATH + 1, inifile)) + { + return FALSE; + } + + if (!GetPrivateProfileStringW(L"PostUpdateWin", L"ExeAsync", L"TRUE", + exeasync, + sizeof(exeasync)/sizeof(exeasync[0]), + inifile)) + { + return FALSE; + } + + WCHAR exefullpath[MAX_PATH + 1] = { L'\0' }; + wcsncpy(exefullpath, installationDir, MAX_PATH); + if (!PathAppendSafe(exefullpath, exefile)) + { + return false; + } + + WCHAR dlogFile[MAX_PATH + 1]; + if (!PathGetSiblingFilePath(dlogFile, exefullpath, L"uninstall.update")) + { + return FALSE; + } + + WCHAR slogFile[MAX_PATH + 1] = { L'\0' }; + wcsncpy(slogFile, updateInfoDir, MAX_PATH); + if (!PathAppendSafe(slogFile, L"update.log")) + { + return FALSE; + } + + WCHAR dummyArg[14] = { L'\0' }; + wcsncpy(dummyArg, L"argv0ignored ", sizeof(dummyArg) / sizeof(dummyArg[0]) - 1); + + size_t len = wcslen(exearg) + wcslen(dummyArg); + WCHAR *cmdline = (WCHAR *) malloc((len + 1) * sizeof(WCHAR)); + if (!cmdline) + { + return FALSE; + } + + wcsncpy(cmdline, dummyArg, len); + wcscat(cmdline, exearg); + + if (forceSync || + !_wcsnicmp(exeasync, L"false", 6) || + !_wcsnicmp(exeasync, L"0", 2)) + { + async = false; + } + + // We want to launch the post update helper app to update the Windows + // registry even if there is a failure with removing the uninstall.update + // file or copying the update.log file. + CopyFileW(slogFile, dlogFile, false); + + STARTUPINFOW si = {sizeof(si), 0}; + si.lpDesktop = L""; + PROCESS_INFORMATION pi = {0}; + + bool ok; + if (userToken) + { + ok = CreateProcessAsUserW(userToken, + exefullpath, + cmdline, + nullptr, // no special security attributes + nullptr, // no special thread attributes + false, // don't inherit filehandles + 0, // No special process creation flags + nullptr, // inherit my environment + workingDirectory, + &si, + &pi); + } + else + { + ok = CreateProcessW(exefullpath, + cmdline, + nullptr, // no special security attributes + nullptr, // no special thread attributes + false, // don't inherit filehandles + 0, // No special process creation flags + nullptr, // inherit my environment + workingDirectory, + &si, + &pi); + } + free(cmdline); + if (ok) + { + if (!async) + WaitForSingleObject(pi.hProcess, INFINITE); + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + } + return ok; +} + +/** + * Starts the upgrade process for update of the service if it is + * already installed. + * + * @param installDir the installation directory where + * maintenanceservice_installer.exe is located. + * @return TRUE if successful + */ +BOOL +StartServiceUpdate(LPCWSTR installDir) +{ + // Get a handle to the local computer SCM database + SC_HANDLE manager = OpenSCManager(nullptr, nullptr, + SC_MANAGER_ALL_ACCESS); + if (!manager) + { + return FALSE; + } + + // Open the service + SC_HANDLE svc = OpenServiceW(manager, SVC_NAME, + SERVICE_ALL_ACCESS); + if (!svc) + { + CloseServiceHandle(manager); + return FALSE; + } + + // If we reach here, then the service is installed, so + // proceed with upgrading it. + + CloseServiceHandle(manager); + + // The service exists and we opened it, get the config bytes needed + DWORD bytesNeeded; + if (!QueryServiceConfigW(svc, nullptr, 0, &bytesNeeded) && + GetLastError() != ERROR_INSUFFICIENT_BUFFER) + { + CloseServiceHandle(svc); + return FALSE; + } + + // Get the service config information, in particular we want the binary + // path of the service. + std::unique_ptr<char[]> serviceConfigBuffer = std::make_unique<char[]>(bytesNeeded); + if (!QueryServiceConfigW(svc, + reinterpret_cast<QUERY_SERVICE_CONFIGW*>(serviceConfigBuffer.get()), + bytesNeeded, &bytesNeeded)) + { + CloseServiceHandle(svc); + return FALSE; + } + + CloseServiceHandle(svc); + + QUERY_SERVICE_CONFIGW &serviceConfig = + *reinterpret_cast<QUERY_SERVICE_CONFIGW*>(serviceConfigBuffer.get()); + + PathUnquoteSpacesW(serviceConfig.lpBinaryPathName); + + // Obtain the temp path of the maintenance service binary + WCHAR tmpService[MAX_PATH + 1] = { L'\0' }; + if (!PathGetSiblingFilePath(tmpService, serviceConfig.lpBinaryPathName, + L"maintenanceservice_tmp.exe")) + { + return FALSE; + } + + // Get the new maintenance service path from the install dir + WCHAR newMaintServicePath[MAX_PATH + 1] = { L'\0' }; + wcsncpy(newMaintServicePath, installDir, MAX_PATH); + PathAppendSafe(newMaintServicePath, + L"maintenanceservice.exe"); + + // Copy the temp file in alongside the maintenance service. + // This is a requirement for maintenance service upgrades. + if (!CopyFileW(newMaintServicePath, tmpService, FALSE)) + { + return FALSE; + } + + // Start the upgrade comparison process + STARTUPINFOW si = {0}; + si.cb = sizeof(STARTUPINFOW); + // No particular desktop because no UI + si.lpDesktop = L""; + PROCESS_INFORMATION pi = {0}; + WCHAR cmdLine[64] = { '\0' }; + wcsncpy(cmdLine, L"dummyparam.exe upgrade", + sizeof(cmdLine) / sizeof(cmdLine[0]) - 1); + BOOL svcUpdateProcessStarted = CreateProcessW(tmpService, + cmdLine, + nullptr, nullptr, FALSE, + 0, + nullptr, installDir, &si, &pi); + if (svcUpdateProcessStarted) + { + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + } + return svcUpdateProcessStarted; +} + +#endif + +/** + * Executes a maintenance service command + * + * @param argc The total number of arguments in argv + * @param argv An array of null terminated strings to pass to the service, + * @return ERROR_SUCCESS if the service command was started. + * Less than 16000, a windows system error code from StartServiceW + * More than 20000, 20000 + the last state of the service constant if + * the last state is something other than stopped. + * 17001 if the SCM could not be opened + * 17002 if the service could not be opened +*/ +DWORD +StartServiceCommand(int argc, LPCWSTR* argv) +{ + DWORD lastState = WaitForServiceStop(SVC_NAME, 5); + if (lastState != SERVICE_STOPPED) + { + return 20000 + lastState; + } + + // Get a handle to the SCM database. + SC_HANDLE serviceManager = OpenSCManager(nullptr, nullptr, + SC_MANAGER_CONNECT | + SC_MANAGER_ENUMERATE_SERVICE); + if (!serviceManager) + { + return 17001; + } + + // Get a handle to the service. + SC_HANDLE service = OpenServiceW(serviceManager, + SVC_NAME, + SERVICE_START); + if (!service) + { + CloseServiceHandle(serviceManager); + return 17002; + } + + // Wait at most 5 seconds trying to start the service in case of errors + // like ERROR_SERVICE_DATABASE_LOCKED or ERROR_SERVICE_REQUEST_TIMEOUT. + const DWORD maxWaitMS = 5000; + DWORD currentWaitMS = 0; + DWORD lastError = ERROR_SUCCESS; + while (currentWaitMS < maxWaitMS) + { + BOOL result = StartServiceW(service, argc, argv); + if (result) + { + lastError = ERROR_SUCCESS; + break; + } + else + { + lastError = GetLastError(); + } + Sleep(100); + currentWaitMS += 100; + } + CloseServiceHandle(service); + CloseServiceHandle(serviceManager); + return lastError; +} + +#ifndef ONLY_SERVICE_LAUNCHING + +/** + * Launch a service initiated action for a software update with the + * specified arguments. + * + * @param exePath The path of the executable to run + * @param argc The total number of arguments in argv + * @param argv An array of null terminated strings to pass to the exePath, + * argv[0] must be the path to the updater.exe + * @return ERROR_SUCCESS if successful + */ +DWORD +LaunchServiceSoftwareUpdateCommand(int argc, LPCWSTR* argv) +{ + // The service command is the same as the updater.exe command line except + // it has 2 extra args: 1) The Path to updater.exe, and 2) the command + // being executed which is "software-update" + LPCWSTR *updaterServiceArgv = new LPCWSTR[argc + 2]; + updaterServiceArgv[0] = L"MozillaMaintenance"; + updaterServiceArgv[1] = L"software-update"; + + for (int i = 0; i < argc; ++i) + { + updaterServiceArgv[i + 2] = argv[i]; + } + + // Execute the service command by starting the service with + // the passed in arguments. + DWORD ret = StartServiceCommand(argc + 2, updaterServiceArgv); + delete[] updaterServiceArgv; + return ret; +} + +/** + * Joins a base directory path with a filename. + * + * @param base The base directory path of size MAX_PATH + 1 + * @param extra The filename to append + * @return TRUE if the file name was successful appended to base + */ +BOOL +PathAppendSafe(LPWSTR base, LPCWSTR extra) +{ + if (wcslen(base) + wcslen(extra) >= MAX_PATH) + { + return FALSE; + } + + return PathAppendW(base, extra); +} + +/** + * Sets update.status to pending so that the next startup will not use + * the service and instead will attempt an update the with a UAC prompt. + * + * @param updateDirPath The path of the update directory + * @return TRUE if successful + */ +BOOL +WriteStatusPending(LPCWSTR updateDirPath) +{ + WCHAR updateStatusFilePath[MAX_PATH + 1] = { L'\0' }; + wcsncpy(updateStatusFilePath, updateDirPath, MAX_PATH); + if (!PathAppendSafe(updateStatusFilePath, L"update.status")) + { + return FALSE; + } + + const char pending[] = "pending"; + HANDLE statusFile = CreateFileW(updateStatusFilePath, GENERIC_WRITE, 0, + nullptr, CREATE_ALWAYS, 0, nullptr); + if (statusFile == INVALID_HANDLE_VALUE) + { + return FALSE; + } + + DWORD wrote; + BOOL ok = WriteFile(statusFile, pending, + sizeof(pending) - 1, &wrote, nullptr); + CloseHandle(statusFile); + return ok && (wrote == sizeof(pending) - 1); +} + +/** + * Sets update.status to a specific failure code + * + * @param updateDirPath The path of the update directory + * @return TRUE if successful + */ +BOOL +WriteStatusFailure(LPCWSTR updateDirPath, int errorCode) +{ + WCHAR updateStatusFilePath[MAX_PATH + 1] = { L'\0' }; + wcsncpy(updateStatusFilePath, updateDirPath, MAX_PATH); + if (!PathAppendSafe(updateStatusFilePath, L"update.status")) + { + return FALSE; + } + + HANDLE statusFile = CreateFileW(updateStatusFilePath, GENERIC_WRITE, 0, + nullptr, CREATE_ALWAYS, 0, nullptr); + if (statusFile == INVALID_HANDLE_VALUE) + { + return FALSE; + } + char failure[32]; + sprintf(failure, "failed: %d", errorCode); + + DWORD toWrite = strlen(failure); + DWORD wrote; + BOOL ok = WriteFile(statusFile, failure, + toWrite, &wrote, nullptr); + CloseHandle(statusFile); + return ok && wrote == toWrite; +} + +#endif + +/** + * Waits for a service to enter a stopped state. + * This function does not stop the service, it just blocks until the service + * is stopped. + * + * @param serviceName The service to wait for. + * @param maxWaitSeconds The maximum number of seconds to wait + * @return state of the service after a timeout or when stopped. + * A value of 255 is returned for an error. Typical values are: + * SERVICE_STOPPED 0x00000001 + * SERVICE_START_PENDING 0x00000002 + * SERVICE_STOP_PENDING 0x00000003 + * SERVICE_RUNNING 0x00000004 + * SERVICE_CONTINUE_PENDING 0x00000005 + * SERVICE_PAUSE_PENDING 0x00000006 + * SERVICE_PAUSED 0x00000007 + * last status not set 0x000000CF + * Could no query status 0x000000DF + * Could not open service, access denied 0x000000EB + * Could not open service, invalid handle 0x000000EC + * Could not open service, invalid name 0x000000ED + * Could not open service, does not exist 0x000000EE + * Could not open service, other error 0x000000EF + * Could not open SCM, access denied 0x000000FD + * Could not open SCM, database does not exist 0x000000FE; + * Could not open SCM, other error 0x000000FF; + * Note: The strange choice of error codes above SERVICE_PAUSED are chosen + * in case Windows comes out with other service stats higher than 7, they + * would likely call it 8 and above. JS code that uses this in TestAUSHelper + * only handles values up to 255 so that's why we don't use GetLastError + * directly. + */ +DWORD +WaitForServiceStop(LPCWSTR serviceName, DWORD maxWaitSeconds) +{ + // 0x000000CF is defined above to be not set + DWORD lastServiceState = 0x000000CF; + + // Get a handle to the SCM database. + SC_HANDLE serviceManager = OpenSCManager(nullptr, nullptr, + SC_MANAGER_CONNECT | + SC_MANAGER_ENUMERATE_SERVICE); + if (!serviceManager) + { + DWORD lastError = GetLastError(); + switch (lastError) + { + case ERROR_ACCESS_DENIED: + return 0x000000FD; + case ERROR_DATABASE_DOES_NOT_EXIST: + return 0x000000FE; + default: + return 0x000000FF; + } + } + + // Get a handle to the service. + SC_HANDLE service = OpenServiceW(serviceManager, + serviceName, + SERVICE_QUERY_STATUS); + if (!service) + { + DWORD lastError = GetLastError(); + CloseServiceHandle(serviceManager); + switch (lastError) + { + case ERROR_ACCESS_DENIED: + return 0x000000EB; + case ERROR_INVALID_HANDLE: + return 0x000000EC; + case ERROR_INVALID_NAME: + return 0x000000ED; + case ERROR_SERVICE_DOES_NOT_EXIST: + return 0x000000EE; + default: + return 0x000000EF; + } + } + + DWORD currentWaitMS = 0; + SERVICE_STATUS_PROCESS ssp; + ssp.dwCurrentState = lastServiceState; + while (currentWaitMS < maxWaitSeconds * 1000) + { + DWORD bytesNeeded; + if (!QueryServiceStatusEx(service, SC_STATUS_PROCESS_INFO, (LPBYTE)&ssp, + sizeof(SERVICE_STATUS_PROCESS), &bytesNeeded)) + { + DWORD lastError = GetLastError(); + switch (lastError) + { + case ERROR_INVALID_HANDLE: + ssp.dwCurrentState = 0x000000D9; + break; + case ERROR_ACCESS_DENIED: + ssp.dwCurrentState = 0x000000DA; + break; + case ERROR_INSUFFICIENT_BUFFER: + ssp.dwCurrentState = 0x000000DB; + break; + case ERROR_INVALID_PARAMETER: + ssp.dwCurrentState = 0x000000DC; + break; + case ERROR_INVALID_LEVEL: + ssp.dwCurrentState = 0x000000DD; + break; + case ERROR_SHUTDOWN_IN_PROGRESS: + ssp.dwCurrentState = 0x000000DE; + break; + // These 3 errors can occur when the service is not yet stopped but + // it is stopping. + case ERROR_INVALID_SERVICE_CONTROL: + case ERROR_SERVICE_CANNOT_ACCEPT_CTRL: + case ERROR_SERVICE_NOT_ACTIVE: + currentWaitMS += 50; + Sleep(50); + continue; + default: + ssp.dwCurrentState = 0x000000DF; + } + + // We couldn't query the status so just break out + break; + } + + // The service is already in use. + if (ssp.dwCurrentState == SERVICE_STOPPED) + { + break; + } + currentWaitMS += 50; + Sleep(50); + } + + lastServiceState = ssp.dwCurrentState; + CloseServiceHandle(service); + CloseServiceHandle(serviceManager); + return lastServiceState; +} + +#ifndef ONLY_SERVICE_LAUNCHING + +/** + * Determines if there is at least one process running for the specified + * application. A match will be found across any session for any user. + * + * @param process The process to check for existence + * @return ERROR_NOT_FOUND if the process was not found + * ERROR_SUCCESS if the process was found and there were no errors + * Other Win32 system error code for other errors +**/ +DWORD +IsProcessRunning(LPCWSTR filename) +{ + // Take a snapshot of all processes in the system. + HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if (INVALID_HANDLE_VALUE == snapshot) + { + return GetLastError(); + } + + PROCESSENTRY32W processEntry; + processEntry.dwSize = sizeof(PROCESSENTRY32W); + if (!Process32FirstW(snapshot, &processEntry)) + { + DWORD lastError = GetLastError(); + CloseHandle(snapshot); + return lastError; + } + + do + { + if (wcsicmp(filename, processEntry.szExeFile) == 0) + { + CloseHandle(snapshot); + return ERROR_SUCCESS; + } + } + while (Process32NextW(snapshot, &processEntry)); + CloseHandle(snapshot); + return ERROR_NOT_FOUND; +} + +/** + * Waits for the specified application to exit. + * + * @param filename The application to wait for. + * @param maxSeconds The maximum amount of seconds to wait for all + * instances of the application to exit. + * @return ERROR_SUCCESS if no instances of the application exist + * WAIT_TIMEOUT if the process is still running after maxSeconds. + * Any other Win32 system error code. +*/ +DWORD +WaitForProcessExit(LPCWSTR filename, DWORD maxSeconds) +{ + DWORD applicationRunningError = WAIT_TIMEOUT; + for (DWORD i = 0; i < maxSeconds; i++) + { + applicationRunningError = IsProcessRunning(filename); + if (ERROR_NOT_FOUND == applicationRunningError) + { + return ERROR_SUCCESS; + } + Sleep(1000); + } + + if (ERROR_SUCCESS == applicationRunningError) + { + return WAIT_TIMEOUT; + } + + return applicationRunningError; +} + +/** + * Determines if the fallback key exists or not + * + * @return TRUE if the fallback key exists and there was no error checking +*/ +BOOL +DoesFallbackKeyExist() +{ + HKEY testOnlyFallbackKey; + if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, + TEST_ONLY_FALLBACK_KEY_PATH, 0, + KEY_READ | KEY_WOW64_64KEY, + &testOnlyFallbackKey) != ERROR_SUCCESS) + { + return FALSE; + } + + RegCloseKey(testOnlyFallbackKey); + return TRUE; +} + +#endif + +/** + * Determines if the file system for the specified file handle is local + * @param file path to check the filesystem type for, must be at most MAX_PATH + * @param isLocal out parameter which will hold TRUE if the drive is local + * @return TRUE if the call succeeded +*/ +BOOL +IsLocalFile(LPCWSTR file, BOOL &isLocal) +{ + WCHAR rootPath[MAX_PATH + 1] = { L'\0' }; + if (wcslen(file) > MAX_PATH) + { + return FALSE; + } + + wcsncpy(rootPath, file, MAX_PATH); + PathStripToRootW(rootPath); + isLocal = GetDriveTypeW(rootPath) == DRIVE_FIXED; + return TRUE; +} + + +/** + * Determines the DWORD value of a registry key value + * + * @param key The base key to where the value name exists + * @param valueName The name of the value + * @param retValue Out parameter which will hold the value + * @return TRUE on success +*/ +static BOOL +GetDWORDValue(HKEY key, LPCWSTR valueName, DWORD &retValue) +{ + DWORD regDWORDValueSize = sizeof(DWORD); + LONG retCode = RegQueryValueExW(key, valueName, 0, nullptr, + reinterpret_cast<LPBYTE>(&retValue), + ®DWORDValueSize); + return ERROR_SUCCESS == retCode; +} + +/** + * Determines if the system's elevation type allows + * unprompted elevation. + * + * @param isUnpromptedElevation Out parameter which specifies if unprompted + * elevation is allowed. + * @return TRUE if the user can actually elevate and the value was obtained + * successfully. +*/ +BOOL +IsUnpromptedElevation(BOOL &isUnpromptedElevation) +{ + if (!UACHelper::CanUserElevate()) + { + return FALSE; + } + + LPCWSTR UACBaseRegKey = + L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\System"; + HKEY baseKey; + LONG retCode = RegOpenKeyExW(HKEY_LOCAL_MACHINE, + UACBaseRegKey, 0, + KEY_READ, &baseKey); + if (retCode != ERROR_SUCCESS) + { + return FALSE; + } + + DWORD consent; + DWORD secureDesktop = 0; + BOOL success = GetDWORDValue(baseKey, L"ConsentPromptBehaviorAdmin", + consent); + success = success && + GetDWORDValue(baseKey, L"PromptOnSecureDesktop", secureDesktop); + isUnpromptedElevation = !consent && !secureDesktop; + + RegCloseKey(baseKey); + return success; +} +#endif diff --git a/onlineupdate/source/update/common/updatehelper.h b/onlineupdate/source/update/common/updatehelper.h new file mode 100644 index 000000000..b8c557bd6 --- /dev/null +++ b/onlineupdate/source/update/common/updatehelper.h @@ -0,0 +1,34 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +BOOL LaunchWinPostProcess(const WCHAR *installationDir, + const WCHAR *updateInfoDir, + bool forceSync, + HANDLE userToken); +BOOL StartServiceUpdate(LPCWSTR installDir); +BOOL GetUpdateDirectoryPath(LPWSTR path); +DWORD LaunchServiceSoftwareUpdateCommand(int argc, LPCWSTR *argv); +BOOL WriteStatusFailure(LPCWSTR updateDirPath, int errorCode); +BOOL WriteStatusPending(LPCWSTR updateDirPath); +DWORD WaitForServiceStop(LPCWSTR serviceName, DWORD maxWaitSeconds); +DWORD WaitForProcessExit(LPCWSTR filename, DWORD maxSeconds); +BOOL DoesFallbackKeyExist(); +BOOL IsLocalFile(LPCWSTR file, BOOL &isLocal); +DWORD StartServiceCommand(int argc, LPCWSTR* argv); +BOOL IsUnpromptedElevation(BOOL &isUnpromptedElevation); + +#define SVC_NAME L"LibreOfficeMaintenance" + +#define BASE_SERVICE_REG_KEY \ + L"SOFTWARE\\LibreOffice\\MaintenanceService" + +// The test only fallback key, as its name implies, is only present on machines +// that will use automated tests. Since automated tests always run from a +// different directory for each test, the presence of this key bypasses the +// "This is a valid installation directory" check. This key also stores +// the allowed name and issuer for cert checks so that the cert check +// code can still be run unchanged. +#define TEST_ONLY_FALLBACK_KEY_PATH \ + BASE_SERVICE_REG_KEY L"\\3932ecacee736d366d6436db0f55bce4" + diff --git a/onlineupdate/source/update/common/updatelogging.cxx b/onlineupdate/source/update/common/updatelogging.cxx new file mode 100644 index 000000000..ed055cd47 --- /dev/null +++ b/onlineupdate/source/update/common/updatelogging.cxx @@ -0,0 +1,85 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#if defined(_WIN32) +#include <windows.h> +#endif + + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <stdarg.h> + +#include "updatelogging.h" + +UpdateLog::UpdateLog() + : logFP(nullptr) + , sourcePath(nullptr) +{ +} + +void UpdateLog::Init(NS_tchar* sourcePathParam, + const NS_tchar* fileName, + const NS_tchar* alternateFileName, + bool append) +{ + if (logFP) + return; + + sourcePath = sourcePathParam; + NS_tchar logFile[MAXPATHLEN]; + NS_tsnprintf(logFile, sizeof(logFile)/sizeof(logFile[0]), + NS_T("%s/%s"), sourcePathParam, fileName); + + if (alternateFileName && NS_taccess(logFile, F_OK)) + { + NS_tsnprintf(logFile, sizeof(logFile)/sizeof(logFile[0]), + NS_T("%s/%s"), sourcePathParam, alternateFileName); + } + + logFP = NS_tfopen(logFile, append ? NS_T("a") : NS_T("w")); +} + +void UpdateLog::Finish() +{ + if (!logFP) + return; + + fclose(logFP); + logFP = nullptr; +} + +void UpdateLog::Flush() +{ + if (!logFP) + return; + + fflush(logFP); +} + +void UpdateLog::Printf(const char *fmt, ... ) +{ + if (!logFP) + return; + + va_list ap; + va_start(ap, fmt); + vfprintf(logFP, fmt, ap); + fprintf(logFP, "\n"); + va_end(ap); +} + +void UpdateLog::WarnPrintf(const char *fmt, ... ) +{ + if (!logFP) + return; + + va_list ap; + va_start(ap, fmt); + fprintf(logFP, "*** Warning: "); + vfprintf(logFP, fmt, ap); + fprintf(logFP, "***\n"); + va_end(ap); +} diff --git a/onlineupdate/source/update/common/updatelogging.h b/onlineupdate/source/update/common/updatelogging.h new file mode 100644 index 000000000..591cb3f4f --- /dev/null +++ b/onlineupdate/source/update/common/updatelogging.h @@ -0,0 +1,47 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef UPDATELOGGING_H +#define UPDATELOGGING_H + +#include "updatedefines.h" +#include <stdio.h> + +class UpdateLog +{ +public: + static UpdateLog & GetPrimaryLog() + { + static UpdateLog primaryLog; + return primaryLog; + } + + void Init(NS_tchar* sourcePath, const NS_tchar* fileName, + const NS_tchar* alternateFileName, bool append); + void Finish(); + void Flush(); + void Printf(const char *fmt, ... ); + void WarnPrintf(const char *fmt, ... ); + + ~UpdateLog() + { + Finish(); + } + +protected: + UpdateLog(); + FILE *logFP; + NS_tchar* sourcePath; +}; + +#define LOG_WARN(args) UpdateLog::GetPrimaryLog().WarnPrintf args +#define LOG(args) UpdateLog::GetPrimaryLog().Printf args +#define LogInit(PATHNAME_, FILENAME_) \ + UpdateLog::GetPrimaryLog().Init(PATHNAME_, FILENAME_, 0, false) +#define LogInitAppend(PATHNAME_, FILENAME_, ALTERNATE_) \ + UpdateLog::GetPrimaryLog().Init(PATHNAME_, FILENAME_, ALTERNATE_, true) +#define LogFinish() UpdateLog::GetPrimaryLog().Finish() +#define LogFlush() UpdateLog::GetPrimaryLog().Flush() + +#endif diff --git a/onlineupdate/source/update/common/win_dirent.h b/onlineupdate/source/update/common/win_dirent.h new file mode 100644 index 000000000..ee2ab6c67 --- /dev/null +++ b/onlineupdate/source/update/common/win_dirent.h @@ -0,0 +1,31 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef WINDIRENT_H__ +#define WINDIRENT_H__ +#ifdef _WIN32 +#include <windows.h> + +struct DIR +{ + explicit DIR(const WCHAR* path); + ~DIR(); + HANDLE findHandle; + WCHAR name[MAX_PATH]; +}; + +struct dirent +{ + dirent(); + WCHAR d_name[MAX_PATH]; +}; + +DIR* opendir(const WCHAR* path); +int closedir(DIR* dir); +dirent* readdir(DIR* dir); + +#endif // WNT +#endif // WINDIRENT_H__ diff --git a/onlineupdate/source/update/updater/Makefile.in b/onlineupdate/source/update/updater/Makefile.in new file mode 100644 index 000000000..57813b36a --- /dev/null +++ b/onlineupdate/source/update/updater/Makefile.in @@ -0,0 +1,28 @@ +# vim:set ts=8 sw=8 sts=8 noet: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +# For changes here, also consider ./updater-xpcshell/Makefile.in + +# This is how the xpcshellCertificate.der file is generated, in case we ever +# have to regenerate it. +# ./certutil -L -d modules/libmar/tests/unit/data -n mycert -r > xpcshellCertificate.der +xpcshellCert.h: xpcshellCertificate.der + +ifdef MOZ_WIDGET_GTK +libs:: updater.png + $(NSINSTALL) -D $(DIST)/bin/icons + $(INSTALL) $(IFLAGS1) $^ $(DIST)/bin/icons +endif + +ifeq (cocoa,$(MOZ_WIDGET_TOOLKIT)) +libs:: + $(NSINSTALL) -D $(DIST)/bin/updater.app + rsync -a -C --exclude '*.in' $(srcdir)/macbuild/Contents $(DIST)/bin/updater.app + sed -e 's/%APP_NAME%/$(MOZ_APP_DISPLAYNAME)/' $(srcdir)/macbuild/Contents/Resources/English.lproj/InfoPlist.strings.in | \ + iconv -f UTF-8 -t UTF-16 > $(DIST)/bin/updater.app/Contents/Resources/English.lproj/InfoPlist.strings + $(NSINSTALL) -D $(DIST)/bin/updater.app/Contents/MacOS + $(NSINSTALL) $(DIST)/bin/updater $(DIST)/bin/updater.app/Contents/MacOS + rm -f $(DIST)/bin/updater +endif diff --git a/onlineupdate/source/update/updater/archivereader.cxx b/onlineupdate/source/update/updater/archivereader.cxx new file mode 100644 index 000000000..d66921143 --- /dev/null +++ b/onlineupdate/source/update/updater/archivereader.cxx @@ -0,0 +1,349 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <string.h> +#include <stdlib.h> +#include <fcntl.h> +#include "bzlib.h" +#include "archivereader.h" +#include "errors.h" +#ifdef _WIN32 +#include "updatehelper.h" +#endif + +// These are generated at compile time based on the DER file for the channel +// being used +#ifdef VERIFY_MAR_SIGNATURE +#ifdef TEST_UPDATER +#include "../xpcshellCert.h" +#else +#include "onlineupdate/primaryCert.h" +#include "onlineupdate/secondaryCert.h" +#endif +#endif + +#if defined(_WIN32) +#define UPDATER_NO_STRING_GLUE_STL +#endif +#include "nsVersionComparator.h" +#undef UPDATER_NO_STRING_GLUE_STL + +#if defined(UNIX) +# include <sys/types.h> +#elif defined(_WIN32) +# include <io.h> +#endif + +static int inbuf_size = 262144; +static int outbuf_size = 262144; +static char *inbuf = nullptr; +static char *outbuf = nullptr; + +/** + * Performs a verification on the opened MAR file with the passed in + * certificate name ID and type ID. + * + * @param archive The MAR file to verify the signature on. + * @param certData The certificate data. + * @return OK on success, CERT_VERIFY_ERROR on failure. +*/ +template<uint32_t SIZE> +int +VerifyLoadedCert(MarFile *archive, const uint8_t (&certData)[SIZE]) +{ + (void)archive; + (void)certData; + +#ifdef VERIFY_MAR_SIGNATURE + const uint32_t size = SIZE; + const uint8_t* const data = &certData[0]; + if (mar_verify_signatures(archive, &data, &size, 1)) + { + return CERT_VERIFY_ERROR; + } +#endif + + return OK; +} + +/** + * Performs a verification on the opened MAR file. Both the primary and backup + * keys stored are stored in the current process and at least the primary key + * will be tried. Success will be returned as long as one of the two + * signatures verify. + * + * @return OK on success +*/ +int +ArchiveReader::VerifySignature() +{ + if (!mArchive) + { + return ARCHIVE_NOT_OPEN; + } + +#ifndef VERIFY_MAR_SIGNATURE + return OK; +#else +#ifdef TEST_UPDATER + int rv = VerifyLoadedCert(mArchive, xpcshellCertData); +#else + int rv = VerifyLoadedCert(mArchive, primaryCertData); + if (rv != OK) + { + rv = VerifyLoadedCert(mArchive, secondaryCertData); + } +#endif + return rv; +#endif +} + +/** + * Verifies that the MAR file matches the current product, channel, and version + * + * @param MARChannelID The MAR channel name to use, only updates from MARs + * with a matching MAR channel name will succeed. + * If an empty string is passed, no check will be done + * for the channel name in the product information block. + * If a comma separated list of values is passed then + * one value must match. + * @param appVersion The application version to use, only MARs with an + * application version >= to appVersion will be applied. + * @return OK on success + * COULD_NOT_READ_PRODUCT_INFO_BLOCK if the product info block + * could not be read. + * MARCHANNEL_MISMATCH_ERROR if update-settings.ini's MAR + * channel ID doesn't match the MAR + * file's MAR channel ID. + * VERSION_DOWNGRADE_ERROR if the application version for + * this updater is newer than the + * one in the MAR. + */ +int +ArchiveReader::VerifyProductInformation(const char *MARChannelID, + const char *appVersion) +{ + if (!mArchive) + { + return ARCHIVE_NOT_OPEN; + } + + ProductInformationBlock productInfoBlock; + int rv = mar_read_product_info_block(mArchive, + &productInfoBlock); + if (rv != OK) + { + return COULD_NOT_READ_PRODUCT_INFO_BLOCK_ERROR; + } + + // Only check the MAR channel name if specified, it should be passed in from + // the update-settings.ini file. + if (MARChannelID && strlen(MARChannelID)) + { + // Check for at least one match in the comma separated list of values. + const char *delimiter = " ,\t"; + // Make a copy of the string in case a read only memory buffer + // was specified. strtok modifies the input buffer. + char channelCopy[512] = { 0 }; + strncpy(channelCopy, MARChannelID, sizeof(channelCopy) - 1); + char *channel = strtok(channelCopy, delimiter); + rv = MAR_CHANNEL_MISMATCH_ERROR; + while (channel) + { + if (!strcmp(channel, productInfoBlock.MARChannelID)) + { + rv = OK; + break; + } + channel = strtok(nullptr, delimiter); + } + } + + if (rv == OK) + { + /* Compare both versions to ensure we don't have a downgrade + -1 if appVersion is older than productInfoBlock.productVersion + 1 if appVersion is newer than productInfoBlock.productVersion + 0 if appVersion is the same as productInfoBlock.productVersion + This even works with strings like: + - 12.0a1 being older than 12.0a2 + - 12.0a2 being older than 12.0b1 + - 12.0a1 being older than 12.0 + - 12.0 being older than 12.1a1 */ + int versionCompareResult = + mozilla::CompareVersions(appVersion, productInfoBlock.productVersion); + if (1 == versionCompareResult) + { + rv = VERSION_DOWNGRADE_ERROR; + } + } + + free((void *)productInfoBlock.MARChannelID); + free((void *)productInfoBlock.productVersion); + return rv; +} + +int +ArchiveReader::Open(const NS_tchar *path) +{ + if (mArchive) + Close(); + + if (!inbuf) + { + inbuf = (char *)malloc(inbuf_size); + if (!inbuf) + { + // Try again with a smaller buffer. + inbuf_size = 1024; + inbuf = (char *)malloc(inbuf_size); + if (!inbuf) + return ARCHIVE_READER_MEM_ERROR; + } + } + + if (!outbuf) + { + outbuf = (char *)malloc(outbuf_size); + if (!outbuf) + { + // Try again with a smaller buffer. + outbuf_size = 1024; + outbuf = (char *)malloc(outbuf_size); + if (!outbuf) + return ARCHIVE_READER_MEM_ERROR; + } + } + +#ifdef _WIN32 + mArchive = mar_wopen(path); +#else + mArchive = mar_open(path); +#endif + if (!mArchive) + return READ_ERROR; + + return OK; +} + +void +ArchiveReader::Close() +{ + if (mArchive) + { + mar_close(mArchive); + mArchive = nullptr; + } + + if (inbuf) + { + free(inbuf); + inbuf = nullptr; + } + + if (outbuf) + { + free(outbuf); + outbuf = nullptr; + } +} + +int +ArchiveReader::ExtractFile(const char *name, const NS_tchar *dest) +{ + const MarItem *item = mar_find_item(mArchive, name); + if (!item) + return READ_ERROR; + +#ifdef _WIN32 + FILE* fp = _wfopen(dest, L"wb+"); +#else + int fd = creat(dest, item->flags); + if (fd == -1) + return WRITE_ERROR; + + FILE *fp = fdopen(fd, "wb"); +#endif + if (!fp) + return WRITE_ERROR; + + int rv = ExtractItemToStream(item, fp); + + fclose(fp); + return rv; +} + +int +ArchiveReader::ExtractFileToStream(const char *name, FILE *fp) +{ + const MarItem *item = mar_find_item(mArchive, name); + if (!item) + return READ_ERROR; + + return ExtractItemToStream(item, fp); +} + +int +ArchiveReader::ExtractItemToStream(const MarItem *item, FILE *fp) +{ + /* decompress the data chunk by chunk */ + + bz_stream strm; + int offset, inlen, ret = OK; + + memset(&strm, 0, sizeof(strm)); + if (BZ2_bzDecompressInit(&strm, 0, 0) != BZ_OK) + return UNEXPECTED_BZIP_ERROR; + + offset = 0; + for (;;) + { + if (!item->length) + { + ret = UNEXPECTED_MAR_ERROR; + break; + } + + if (offset < (int) item->length && strm.avail_in == 0) + { + inlen = mar_read(mArchive, item, offset, inbuf, inbuf_size); + if (inlen <= 0) + return READ_ERROR; + offset += inlen; + strm.next_in = inbuf; + strm.avail_in = inlen; + } + + strm.next_out = outbuf; + strm.avail_out = outbuf_size; + + ret = BZ2_bzDecompress(&strm); + if (ret != BZ_OK && ret != BZ_STREAM_END) + { + ret = UNEXPECTED_BZIP_ERROR; + break; + } + + int outlen = outbuf_size - strm.avail_out; + if (outlen) + { + if (fwrite(outbuf, outlen, 1, fp) != 1) + { + ret = WRITE_ERROR_EXTRACT; + break; + } + } + + if (ret == BZ_STREAM_END) + { + ret = OK; + break; + } + } + + BZ2_bzDecompressEnd(&strm); + return ret; +} diff --git a/onlineupdate/source/update/updater/archivereader.h b/onlineupdate/source/update/updater/archivereader.h new file mode 100644 index 000000000..090b787f9 --- /dev/null +++ b/onlineupdate/source/update/updater/archivereader.h @@ -0,0 +1,39 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef ArchiveReader_h__ +#define ArchiveReader_h__ + +#include <stdio.h> +#include <onlineupdate/mar.h> +#include "types.hxx" + +// This class provides an API to extract files from an update archive. +class ArchiveReader +{ +public: + ArchiveReader() : mArchive(nullptr) {} + ~ArchiveReader() + { + Close(); + } + + int Open(const NS_tchar *path); + int VerifySignature(); + int VerifyProductInformation(const char *MARChannelID, + const char *appVersion); + void Close(); + + int ExtractFile(const char *item, const NS_tchar *destination); + int ExtractFileToStream(const char *item, FILE *fp); + +private: + int ExtractItemToStream(const MarItem *item, FILE *fp); + + MarFile *mArchive; +}; + +#endif // ArchiveReader_h__ diff --git a/onlineupdate/source/update/updater/bspatch.cxx b/onlineupdate/source/update/updater/bspatch.cxx new file mode 100644 index 000000000..219c4d74c --- /dev/null +++ b/onlineupdate/source/update/updater/bspatch.cxx @@ -0,0 +1,198 @@ +/*- + * Copyright 2003,2004 Colin Percival + * All rights reserved + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted providing that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * Changelog: + * 2005-04-26 - Define the header as a C structure, add a CRC32 checksum to + * the header, and make all the types 32-bit. + * --Benjamin Smedberg <benjamin@smedbergs.us> + */ + +#include "bspatch.h" +#include "errors.h" + +#include <algorithm> +#include <sys/stat.h> +#include <stdlib.h> +#include <stdio.h> +#include <fcntl.h> +#include <string.h> +#include <limits.h> + +#if defined(_WIN32) +# include <io.h> +#else +# include <unistd.h> +#endif + +#ifdef _WIN32 +# include <winsock2.h> +#else +# include <arpa/inet.h> +#endif + +#ifndef SSIZE_MAX +# define SSIZE_MAX LONG_MAX +#endif + +int +MBS_ReadHeader(FILE* file, MBSPatchHeader *header) +{ + size_t s = fread(header, 1, sizeof(MBSPatchHeader), file); + if (s != sizeof(MBSPatchHeader)) + return READ_ERROR; + + header->slen = ntohl(header->slen); + header->scrc32 = ntohl(header->scrc32); + header->dlen = ntohl(header->dlen); + header->cblen = ntohl(header->cblen); + header->difflen = ntohl(header->difflen); + header->extralen = ntohl(header->extralen); + + struct stat hs; + s = fstat(fileno(file), &hs); + if (s) + return READ_ERROR; + + if (memcmp(header->tag, "MBDIFF10", 8) != 0) + return UNEXPECTED_BSPATCH_ERROR; + + if (sizeof(MBSPatchHeader) + + header->cblen + + header->difflen + + header->extralen != uint32_t(hs.st_size)) + return UNEXPECTED_BSPATCH_ERROR; + + return OK; +} + +int +MBS_ApplyPatch(const MBSPatchHeader *header, FILE* patchFile, + unsigned char *fbuffer, FILE* file) +{ + unsigned char *fbufend = fbuffer + header->slen; + + unsigned char *buf = (unsigned char*) malloc(header->cblen + + header->difflen + + header->extralen); + if (!buf) + return BSPATCH_MEM_ERROR; + + int rv = OK; + + size_t r = header->cblen + header->difflen + header->extralen; + unsigned char *wb = buf; + while (r) + { + const size_t count = std::min(r, size_t(SSIZE_MAX)); + size_t c = fread(wb, 1, count, patchFile); + if (c != count) + { + rv = READ_ERROR; + goto end; + } + + r -= c; + wb += c; + } + + { + MBSPatchTriple *ctrlsrc = (MBSPatchTriple*) buf; + unsigned char *diffsrc = buf + header->cblen; + unsigned char *extrasrc = diffsrc + header->difflen; + + MBSPatchTriple *ctrlend = (MBSPatchTriple*) diffsrc; + unsigned char *diffend = extrasrc; + unsigned char *extraend = extrasrc + header->extralen; + + do + { + ctrlsrc->x = ntohl(ctrlsrc->x); + ctrlsrc->y = ntohl(ctrlsrc->y); + ctrlsrc->z = ntohl(ctrlsrc->z); + +#ifdef DEBUG_bsmedberg + printf("Applying block:\n" + " x: %u\n" + " y: %u\n" + " z: %i\n", + ctrlsrc->x, + ctrlsrc->y, + ctrlsrc->z); +#endif + + /* Add x bytes from oldfile to x bytes from the diff block */ + + if (fbuffer + ctrlsrc->x > fbufend || + diffsrc + ctrlsrc->x > diffend) + { + rv = UNEXPECTED_BSPATCH_ERROR; + goto end; + } + for (uint32_t i = 0; i < ctrlsrc->x; ++i) + { + diffsrc[i] += fbuffer[i]; + } + if ((uint32_t) fwrite(diffsrc, 1, ctrlsrc->x, file) != ctrlsrc->x) + { + rv = WRITE_ERROR_PATCH_FILE; + goto end; + } + fbuffer += ctrlsrc->x; + diffsrc += ctrlsrc->x; + + /* Copy y bytes from the extra block */ + + if (extrasrc + ctrlsrc->y > extraend) + { + rv = UNEXPECTED_BSPATCH_ERROR; + goto end; + } + if ((uint32_t) fwrite(extrasrc, 1, ctrlsrc->y, file) != ctrlsrc->y) + { + rv = WRITE_ERROR_PATCH_FILE; + goto end; + } + extrasrc += ctrlsrc->y; + + /* "seek" forwards in oldfile by z bytes */ + + if (fbuffer + ctrlsrc->z > fbufend) + { + rv = UNEXPECTED_BSPATCH_ERROR; + goto end; + } + fbuffer += ctrlsrc->z; + + /* and on to the next control block */ + + ++ctrlsrc; + } + while (ctrlsrc < ctrlend); + } + +end: + free(buf); + return rv; +} diff --git a/onlineupdate/source/update/updater/gen_cert_header.py b/onlineupdate/source/update/updater/gen_cert_header.py new file mode 100755 index 000000000..3f3798cfb --- /dev/null +++ b/onlineupdate/source/update/updater/gen_cert_header.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python + +from __future__ import print_function +import os +import sys +import binascii + +try: + from configparser import ConfigParser +except ImportError: + from ConfigParser import SafeConfigParser as ConfigParser + +def file_byte_generator(filename): + with open(filename, "rb") as f: + block = f.read() + return block + +def eprint(*args, **kwargs): + print(*args, file=sys.stderr, **kwargs) + +def create_header(array_name, in_filename): + if sys.version_info >= (3,0): + hexified = ["0x" + binascii.hexlify(bytes([inp])).decode('ascii') for inp in file_byte_generator(in_filename)] + else: + hexified = ["0x" + binascii.hexlify(inp).decode('ascii') for inp in file_byte_generator(in_filename)] + print("const uint8_t " + array_name + "[] = {") + print(", ".join(hexified)) + print("};") + return 0 + +if __name__ == '__main__': + if len(sys.argv) < 3: + eprint('ERROR: usage: gen_cert_header.py array_name update_config_file') + sys.exit(1) + + if not os.path.exists(sys.argv[2]): + eprint('The config file %s does not exist'%(sys.argv[2])) + sys.exit(1) + + config = ConfigParser() + config.read(sys.argv[2]) + sys.exit(create_header(sys.argv[1], config.get('Updater', 'certificate-der'))) diff --git a/onlineupdate/source/update/updater/launchchild_osx.mm b/onlineupdate/source/update/updater/launchchild_osx.mm new file mode 100644 index 000000000..9e4e08d37 --- /dev/null +++ b/onlineupdate/source/update/updater/launchchild_osx.mm @@ -0,0 +1,138 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <Cocoa/Cocoa.h> +#include <CoreServices/CoreServices.h> +#include <crt_externs.h> +#include <stdlib.h> +#include <stdio.h> +#include <spawn.h> +#include "readstrings.h" + +// Prefer the currently running architecture (this is the same as the +// architecture that launched the updater) and fallback to CPU_TYPE_ANY if it +// is no longer available after the update. +static cpu_type_t pref_cpu_types[2] = { +#if defined(__i386__) + CPU_TYPE_X86, +#elif defined(__x86_64__) + CPU_TYPE_X86_64, +#elif defined(__ppc__) + CPU_TYPE_POWERPC, +#endif + CPU_TYPE_ANY }; + +void LaunchChild(int argc, char **argv) +{ + // Initialize spawn attributes. + posix_spawnattr_t spawnattr; + if (posix_spawnattr_init(&spawnattr) != 0) { + printf("Failed to init posix spawn attribute."); + return; + } + + // Set spawn attributes. + size_t attr_count = 2; + size_t attr_ocount = 0; + if (posix_spawnattr_setbinpref_np(&spawnattr, attr_count, pref_cpu_types, &attr_ocount) != 0 || + attr_ocount != attr_count) { + printf("Failed to set binary preference on posix spawn attribute."); + posix_spawnattr_destroy(&spawnattr); + return; + } + + // "posix_spawnp" uses null termination for arguments rather than a count. + // Note that we are not duplicating the argument strings themselves. + char** argv_copy = (char**)malloc((argc + 1) * sizeof(char*)); + if (!argv_copy) { + printf("Failed to allocate memory for arguments."); + posix_spawnattr_destroy(&spawnattr); + return; + } + for (int i = 0; i < argc; i++) { + argv_copy[i] = argv[i]; + } + argv_copy[argc] = NULL; + + // Pass along our environment. + char** envp = NULL; + char*** cocoaEnvironment = _NSGetEnviron(); + if (cocoaEnvironment) { + envp = *cocoaEnvironment; + } + + int result = posix_spawnp(NULL, argv_copy[0], NULL, &spawnattr, argv_copy, envp); + + free(argv_copy); + posix_spawnattr_destroy(&spawnattr); + + if (result != 0) { + printf("Process spawn failed with code %d!", result); + } +} + +void +LaunchMacPostProcess(const char* aAppBundle) +{ + // Launch helper to perform post processing for the update; this is the Mac + // analogue of LaunchWinPostProcess (PostUpdateWin). + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + NSString* iniPath = [NSString stringWithUTF8String:aAppBundle]; + iniPath = + [iniPath stringByAppendingPathComponent:@"Contents/Resources/updater.ini"]; + + NSFileManager* fileManager = [NSFileManager defaultManager]; + if (![fileManager fileExistsAtPath:iniPath]) { + // the file does not exist; there is nothing to run + [pool release]; + return; + } + + int readResult; + char values[2][MAX_TEXT_LEN]; + readResult = ReadStrings([iniPath UTF8String], + "ExeRelPath\0ExeArg\0", + 2, + values, + "PostUpdateMac"); + if (readResult) { + [pool release]; + return; + } + + NSString *exeRelPath = [NSString stringWithUTF8String:values[0]]; + NSString *exeArg = [NSString stringWithUTF8String:values[1]]; + if (!exeArg || !exeRelPath) { + [pool release]; + return; + } + + NSString* exeFullPath = [NSString stringWithUTF8String:aAppBundle]; + exeFullPath = [exeFullPath stringByAppendingPathComponent:exeRelPath]; + + char optVals[1][MAX_TEXT_LEN]; + readResult = ReadStrings([iniPath UTF8String], + "ExeAsync\0", + 1, + optVals, + "PostUpdateMac"); + + NSTask *task = [[NSTask alloc] init]; + [task setLaunchPath:exeFullPath]; + [task setArguments:[NSArray arrayWithObject:exeArg]]; + [task launch]; + if (!readResult) { + NSString *exeAsync = [NSString stringWithUTF8String:optVals[0]]; + if ([exeAsync isEqualToString:@"false"]) { + [task waitUntilExit]; + } + } + // ignore the return value of the task, there's nothing we can do with it + [task release]; + + [pool release]; +} diff --git a/onlineupdate/source/update/updater/loaddlls.cxx b/onlineupdate/source/update/updater/loaddlls.cxx new file mode 100644 index 000000000..6a0c8a61e --- /dev/null +++ b/onlineupdate/source/update/updater/loaddlls.cxx @@ -0,0 +1,119 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + + +#ifdef _WIN32 +#ifndef UNICODE +#define UNICODE +#endif +#include <windows.h> + +// Delayed load libraries are loaded when the first symbol is used. +// The following ensures that we load the delayed loaded libraries from the +// system directory. +struct AutoLoadSystemDependencies +{ + AutoLoadSystemDependencies() + { + // Remove the current directory from the search path for dynamically loaded + // DLLs as a precaution. This call has no effect for delay load DLLs. + SetDllDirectory(L""); + + HMODULE module = ::GetModuleHandleW(L"kernel32.dll"); + if (module) + { + // SetDefaultDllDirectories is always available on Windows 8 and above. It + // is also available on Windows Vista, Windows Server 2008, and + // Windows 7 when MS KB2533623 has been applied. + decltype(SetDefaultDllDirectories)* setDefaultDllDirectories = + (decltype(SetDefaultDllDirectories)*) GetProcAddress(module, "SetDefaultDllDirectories"); + if (setDefaultDllDirectories) + { + setDefaultDllDirectories(LOAD_LIBRARY_SEARCH_SYSTEM32); + return; + } + } + + // TODO: moggi: do we need all that code? + // When SetDefaultDllDirectories is not available, fallback to preloading + // dlls. The order that these are loaded does not matter since they are + // loaded using the LOAD_WITH_ALTERED_SEARCH_PATH flag. +#ifdef HAVE_64BIT_BUILD + // DLLs for Firefox x64 on Windows 7 (x64). + // Note: dwmapi.dll is preloaded since a crash will try to load it from the + // application's directory. + static LPCWSTR delayDLLs[] = { L"apphelp.dll", + L"cryptbase.dll", + L"cryptsp.dll", + L"dwmapi.dll", + L"mpr.dll", + L"ntmarta.dll", + L"profapi.dll", + L"propsys.dll", + L"sspicli.dll", + L"wsock32.dll" + }; + +#else + // DLLs for Firefox x86 on Windows XP through Windows 7 (x86 and x64). + // Note: dwmapi.dll is preloaded since a crash will try to load it from the + // application's directory. + static LPCWSTR delayDLLs[] = { L"apphelp.dll", + L"crypt32.dll", + L"cryptbase.dll", + L"cryptsp.dll", + L"dwmapi.dll", + L"mpr.dll", + L"msasn1.dll", + L"ntmarta.dll", + L"profapi.dll", + L"propsys.dll", + L"psapi.dll", + L"secur32.dll", + L"sspicli.dll", + L"userenv.dll", + L"uxtheme.dll", + L"ws2_32.dll", + L"ws2help.dll", + L"wsock32.dll" + }; +#endif + + WCHAR systemDirectory[MAX_PATH + 1] = { L'\0' }; + // If GetSystemDirectory fails we accept that we'll load the DLLs from the + // normal search path. + GetSystemDirectoryW(systemDirectory, MAX_PATH + 1); + size_t systemDirLen = wcslen(systemDirectory); + + // Make the system directory path terminate with a slash + if (systemDirectory[systemDirLen - 1] != L'\\' && systemDirLen) + { + systemDirectory[systemDirLen] = L'\\'; + ++systemDirLen; + // No need to re-null terminate + } + + // For each known DLL ensure it is loaded from the system32 directory + for (size_t i = 0; i < sizeof(delayDLLs) / sizeof(delayDLLs[0]); ++i) + { + size_t fileLen = wcslen(delayDLLs[i]); + wcsncpy(systemDirectory + systemDirLen, delayDLLs[i], + MAX_PATH - systemDirLen); + if (systemDirLen + fileLen <= MAX_PATH) + { + systemDirectory[systemDirLen + fileLen] = L'\0'; + } + else + { + systemDirectory[MAX_PATH] = L'\0'; + } + LPCWSTR fullModulePath = systemDirectory; // just for code readability + // LOAD_WITH_ALTERED_SEARCH_PATH makes a dll look in its own directory for + // dependencies and is only available on Win 7 and below. + LoadLibraryExW(fullModulePath, nullptr, LOAD_WITH_ALTERED_SEARCH_PATH); + } + } +} loadDLLs; +#endif diff --git a/onlineupdate/source/update/updater/macbuild/Contents/Info.plist b/onlineupdate/source/update/updater/macbuild/Contents/Info.plist new file mode 100644 index 000000000..f104b55b9 --- /dev/null +++ b/onlineupdate/source/update/updater/macbuild/Contents/Info.plist @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleDevelopmentRegion</key> + <string>English</string> + <key>CFBundleExecutable</key> + <string>updater</string> + <key>CFBundleIconFile</key> + <string>updater.icns</string> + <key>CFBundleIdentifier</key> + <string>org.mozilla.updater</string> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>CFBundlePackageType</key> + <string>APPL</string> + <key>CFBundleSignature</key> + <string>????</string> + <key>CFBundleVersion</key> + <string>1.0</string> + <key>NSMainNibFile</key> + <string>MainMenu</string> + <key>NSPrincipalClass</key> + <string>NSApplication</string> + <key>LSMinimumSystemVersion</key> + <string>10.5</string> + <key>LSMinimumSystemVersionByArchitecture</key> + <dict> + <key>i386</key> + <string>10.5.0</string> + <key>x86_64</key> + <string>10.6.0</string> + </dict> +</dict> +</plist> diff --git a/onlineupdate/source/update/updater/macbuild/Contents/PkgInfo b/onlineupdate/source/update/updater/macbuild/Contents/PkgInfo new file mode 100644 index 000000000..bd04210fb --- /dev/null +++ b/onlineupdate/source/update/updater/macbuild/Contents/PkgInfo @@ -0,0 +1 @@ +APPL????
\ No newline at end of file diff --git a/onlineupdate/source/update/updater/macbuild/Contents/Resources/English.lproj/InfoPlist.strings.in b/onlineupdate/source/update/updater/macbuild/Contents/Resources/English.lproj/InfoPlist.strings.in new file mode 100644 index 000000000..bca4022e7 --- /dev/null +++ b/onlineupdate/source/update/updater/macbuild/Contents/Resources/English.lproj/InfoPlist.strings.in @@ -0,0 +1,7 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Localized versions of Info.plist keys */ + +CFBundleName = "%APP_NAME% Software Update"; diff --git a/onlineupdate/source/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/classes.nib b/onlineupdate/source/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/classes.nib new file mode 100644 index 000000000..6cfb50406 --- /dev/null +++ b/onlineupdate/source/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/classes.nib @@ -0,0 +1,19 @@ +{ + IBClasses = ( + { + CLASS = FirstResponder; + LANGUAGE = ObjC; + SUPERCLASS = NSObject; +}, + { + CLASS = UpdaterUI; + LANGUAGE = ObjC; + OUTLETS = { + progressBar = NSProgressIndicator; + progressTextField = NSTextField; + }; + SUPERCLASS = NSObject; +} + ); + IBVersion = 1; +}
\ No newline at end of file diff --git a/onlineupdate/source/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/info.nib b/onlineupdate/source/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/info.nib new file mode 100644 index 000000000..150917837 --- /dev/null +++ b/onlineupdate/source/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/info.nib @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>IBDocumentLocation</key> + <string>111 162 356 240 0 0 1440 878 </string> + <key>IBEditorPositions</key> + <dict> + <key>29</key> + <string>106 299 84 44 0 0 1440 878 </string> + </dict> + <key>IBFramework Version</key> + <string>489.0</string> + <key>IBOpenObjects</key> + <array> + <integer>21</integer> + <integer>29</integer> + </array> + <key>IBSystem Version</key> + <string>10J567</string> +</dict> +</plist> diff --git a/onlineupdate/source/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/keyedobjects.nib b/onlineupdate/source/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/keyedobjects.nib Binary files differnew file mode 100644 index 000000000..61ff02600 --- /dev/null +++ b/onlineupdate/source/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/keyedobjects.nib diff --git a/onlineupdate/source/update/updater/macbuild/Contents/Resources/updater.icns b/onlineupdate/source/update/updater/macbuild/Contents/Resources/updater.icns Binary files differnew file mode 100644 index 000000000..d7499c669 --- /dev/null +++ b/onlineupdate/source/update/updater/macbuild/Contents/Resources/updater.icns diff --git a/onlineupdate/source/update/updater/progressui-unused/progressui_gonk.cxx b/onlineupdate/source/update/updater/progressui-unused/progressui_gonk.cxx new file mode 100644 index 000000000..c855e548e --- /dev/null +++ b/onlineupdate/source/update/updater/progressui-unused/progressui_gonk.cxx @@ -0,0 +1,54 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=8 et : + */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <assert.h> +#include <stdio.h> + +#include <string> + +#include "android/log.h" + +#include "progressui.h" + +#define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "GeckoUpdater" , ## args) + +using namespace std; + +int InitProgressUI(int *argc, char ***argv) +{ + return 0; +} + +int ShowProgressUI() +{ + LOG("Starting to apply update ...\n"); + return 0; +} + +void QuitProgressUI() +{ + LOG("Finished applying update\n"); +} + +void UpdateProgressUI(float progress) +{ + assert(0.0f <= progress && progress <= 100.0f); + + static const size_t kProgressBarLength = 50; + static size_t sLastNumBars; + size_t numBars = size_t(float(kProgressBarLength) * progress / 100.0f); + if (numBars == sLastNumBars) + { + return; + } + sLastNumBars = numBars; + + size_t numSpaces = kProgressBarLength - numBars; + string bars(numBars, '='); + string spaces(numSpaces, ' '); + LOG("Progress [ %s%s ]\n", bars.c_str(), spaces.c_str()); +} diff --git a/onlineupdate/source/update/updater/progressui-unused/progressui_osx.mm b/onlineupdate/source/update/updater/progressui-unused/progressui_osx.mm new file mode 100644 index 000000000..8e3ce01eb --- /dev/null +++ b/onlineupdate/source/update/updater/progressui-unused/progressui_osx.mm @@ -0,0 +1,141 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#import <Cocoa/Cocoa.h> +#include <stdio.h> +#include <unistd.h> +#include "progressui.h" +#include "readstrings.h" +#include "errors.h" + +#define TIMER_INTERVAL 0.2 + +static float sProgressVal; // between 0 and 100 +static BOOL sQuit = FALSE; +static StringTable sLabels; +static const char *sUpdatePath; + +@interface UpdaterUI : NSObject +{ + IBOutlet NSProgressIndicator *progressBar; + IBOutlet NSTextField *progressTextField; +} +@end + +@implementation UpdaterUI + +-(void)awakeFromNib +{ + NSWindow *w = [progressBar window]; + + [w setTitle:[NSString stringWithUTF8String:sLabels.title]]; + [progressTextField setStringValue:[NSString stringWithUTF8String:sLabels.info]]; + + NSRect origTextFrame = [progressTextField frame]; + [progressTextField sizeToFit]; + + int widthAdjust = progressTextField.frame.size.width - origTextFrame.size.width; + + if (widthAdjust > 0) { + NSRect f; + f.size.width = w.frame.size.width + widthAdjust; + f.size.height = w.frame.size.height; + [w setFrame:f display:YES]; + } + + [w center]; + + [progressBar setIndeterminate:NO]; + [progressBar setDoubleValue:0.0]; + + [[NSTimer scheduledTimerWithTimeInterval:TIMER_INTERVAL target:self + selector:@selector(updateProgressUI:) + userInfo:nil repeats:YES] retain]; + + // Make sure we are on top initially + [NSApp activateIgnoringOtherApps:YES]; +} + +// called when the timer goes off +-(void)updateProgressUI:(NSTimer *)aTimer +{ + if (sQuit) { + [aTimer invalidate]; + [aTimer release]; + + // It seems to be necessary to activate and hide ourselves before we stop, + // otherwise the "run" method will not return until the user focuses some + // other app. The activate step is necessary if we are not the active app. + // This is a big hack, but it seems to do the trick. + [NSApp activateIgnoringOtherApps:YES]; + [NSApp hide:self]; + [NSApp stop:self]; + } + + float progress = sProgressVal; + + [progressBar setDoubleValue:(double)progress]; +} + +// leave this as returning a BOOL instead of NSApplicationTerminateReply +// for backward compatibility +- (BOOL)applicationShouldTerminate:(NSApplication *)sender +{ + return sQuit; +} + +@end + +int +InitProgressUI(int *pargc, char ***pargv) +{ + sUpdatePath = (*pargv)[1]; + + return 0; +} + +int +ShowProgressUI() +{ + // Only show the Progress UI if the process is taking a significant amount of + // time where a significant amount of time is defined as .5 seconds after + // ShowProgressUI is called sProgress is less than 70. + usleep(500000); + + if (sQuit || sProgressVal > 70.0f) + return 0; + + char path[PATH_MAX]; + snprintf(path, sizeof(path), "%s/updater.ini", sUpdatePath); + if (ReadStrings(path, &sLabels) != OK) + return -1; + + // Continue the update without showing the Progress UI if any of the supplied + // strings are larger than MAX_TEXT_LEN (Bug 628829). + if (!(strlen(sLabels.title) < MAX_TEXT_LEN - 1 && + strlen(sLabels.info) < MAX_TEXT_LEN - 1)) + return -1; + + [NSApplication sharedApplication]; + [NSBundle loadNibNamed:@"MainMenu" owner:NSApp]; + [NSApp run]; + + return 0; +} + +// Called on a background thread +void +QuitProgressUI() +{ + sQuit = TRUE; +} + +// Called on a background thread +void +UpdateProgressUI(float progress) +{ + sProgressVal = progress; // 32-bit writes are atomic +} diff --git a/onlineupdate/source/update/updater/progressui.h b/onlineupdate/source/update/updater/progressui.h new file mode 100644 index 000000000..cb1468d39 --- /dev/null +++ b/onlineupdate/source/update/updater/progressui.h @@ -0,0 +1,36 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef PROGRESSUI_H__ +#define PROGRESSUI_H__ + +#include "updatedefines.h" +#include "types.hxx" + +#if defined(_WIN32) +#define NS_main wmain +#else +#define NS_main main +#endif + +// Called to perform any initialization of the widget toolkit +int InitProgressUI(int *argc, NS_tchar ***argv); + +#if defined(_WIN32) +// Called on the main thread at startup +int ShowProgressUI(bool indeterminate = false, bool initUIStrings = true); +int InitProgressUIStrings(); +#else +// Called on the main thread at startup +int ShowProgressUI(); +#endif +// May be called from any thread +void QuitProgressUI(); + +// May be called from any thread: progress is a number between 0 and 100 +void UpdateProgressUI(float progress); + +#endif // PROGRESSUI_H__ diff --git a/onlineupdate/source/update/updater/progressui_gtk.cxx b/onlineupdate/source/update/updater/progressui_gtk.cxx new file mode 100644 index 000000000..7c3bcfcd8 --- /dev/null +++ b/onlineupdate/source/update/updater/progressui_gtk.cxx @@ -0,0 +1,146 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#if defined(UNIX) || defined(MACOSX) +#include <stdio.h> +#include <gtk/gtk.h> +#include <unistd.h> +#include "progressui.h" +#include "readstrings.h" +#include "errors.h" +#include <string.h> +#include "progressui_gtk_icon.h" + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + +#define TIMER_INTERVAL 100 + +static float sProgressVal; // between 0 and 100 +static gboolean sQuit = FALSE; +static gboolean sEnableUI; +static guint sTimerID; + +static GtkWidget *sWin; +static GtkWidget *sLabel; +static GtkWidget *sProgressBar; + +static const char *sProgramPath; + +static gboolean +UpdateDialog(gpointer /*data*/) +{ + if (sQuit) + { + gtk_widget_hide(sWin); + gtk_main_quit(); + } + + float progress = sProgressVal; + + gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(sProgressBar), + progress / 100.0); + + return TRUE; +} + +static gboolean +OnDeleteEvent(GtkWidget * /*widget*/, GdkEvent * /*event*/, gpointer /*user_data*/) +{ + return TRUE; +} + +int +InitProgressUI(int *pargc, char ***pargv) +{ + sProgramPath = (*pargv)[0]; + + sEnableUI = gtk_init_check(pargc, pargv); + return 0; +} + +int +ShowProgressUI() +{ + if (!sEnableUI) + return -1; + + // Only show the Progress UI if the process is taking a significant amount of + // time where a significant amount of time is defined as .5 seconds after + // ShowProgressUI is called sProgress is less than 70. + usleep(500000); + + if (sQuit || sProgressVal > 70.0f) + return 0; + + char ini_path[PATH_MAX]; + snprintf(ini_path, sizeof(ini_path), "%s.ini", sProgramPath); + + StringTable strings; + if (ReadStrings(ini_path, &strings) != OK) + { + strcpy(strings.title, "LibreOffice Update"); + strcpy(strings.info, "Please wait while we update your installation."); + } + + sWin = gtk_window_new(GTK_WINDOW_TOPLEVEL); + if (!sWin) + return -1; + + static GdkPixbuf *pixbuf; + + g_signal_connect(G_OBJECT(sWin), "delete_event", + G_CALLBACK(OnDeleteEvent), nullptr); + + gtk_window_set_title(GTK_WINDOW(sWin), strings.title); + gtk_window_set_type_hint(GTK_WINDOW(sWin), GDK_WINDOW_TYPE_HINT_DIALOG); + gtk_window_set_position(GTK_WINDOW(sWin), GTK_WIN_POS_CENTER_ALWAYS); + gtk_window_set_resizable(GTK_WINDOW(sWin), FALSE); + gtk_window_set_decorated(GTK_WINDOW(sWin), TRUE); + gtk_window_set_deletable(GTK_WINDOW(sWin),FALSE); + pixbuf = gdk_pixbuf_new_from_xpm_data (icon_data); + gtk_window_set_icon(GTK_WINDOW(sWin), pixbuf); + g_object_unref(pixbuf); + + GtkWidget *vbox = gtk_vbox_new(TRUE, 6); + sLabel = gtk_label_new(strings.info); + gtk_misc_set_alignment(GTK_MISC(sLabel), 0.0f, 0.0f); + sProgressBar = gtk_progress_bar_new(); + + gtk_box_pack_start(GTK_BOX(vbox), sLabel, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(vbox), sProgressBar, TRUE, TRUE, 0); + + sTimerID = g_timeout_add(TIMER_INTERVAL, UpdateDialog, nullptr); + + gtk_container_set_border_width(GTK_CONTAINER(sWin), 10); + gtk_container_add(GTK_CONTAINER(sWin), vbox); + gtk_widget_show_all(sWin); + + gtk_main(); + return 0; +} + +// Called on a background thread +void +QuitProgressUI() +{ + sQuit = TRUE; +} + +// Called on a background thread +void +UpdateProgressUI(float progress) +{ + sProgressVal = progress; // 32-bit writes are atomic +} + +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + +#endif // defined(UNIX) || defined(MACOSX) diff --git a/onlineupdate/source/update/updater/progressui_gtk_icon.h b/onlineupdate/source/update/updater/progressui_gtk_icon.h new file mode 100644 index 000000000..6660c071f --- /dev/null +++ b/onlineupdate/source/update/updater/progressui_gtk_icon.h @@ -0,0 +1,205 @@ +/* XPM */ +static const char *icon_data[] = { +/* columns rows colors chars-per-pixel */ +"32 32 167 2 ", +" c #915B05", +". c #955F0C", +"X c #95610D", +"o c #8F6013", +"O c #8C601D", +"+ c #966311", +"@ c #9A6615", +"# c #9A6817", +"$ c #9C6B1D", +"% c #8B652A", +"& c #966B26", +"* c #976D28", +"= c #9C7129", +"- c #8E6D34", +"; c #A17025", +": c #A2752E", +"> c #AB7A2B", +", c #A77A34", +"< c #A97C33", +"1 c #8D7751", +"2 c #8D7A57", +"3 c #8A7658", +"4 c #A17D42", +"5 c #8E7E63", +"6 c #AE823B", +"7 c #B88635", +"8 c #B2853B", +"9 c #BB8A3A", +"0 c #C08B36", +"q c #AA864A", +"w c #AF894D", +"e c #BC8D42", +"r c #AA8B57", +"t c #B38F56", +"y c #B79256", +"u c #968468", +"i c #958A73", +"p c #9B8970", +"a c #938D7D", +"s c #9D927A", +"d c #A3916F", +"f c #AD966D", +"g c #B1996D", +"h c #BF9F6E", +"j c #A89473", +"k c #B59D75", +"l c #BEA375", +"z c #C08F42", +"x c #D29E4B", +"c c #C69C5C", +"v c #DBA44D", +"b c #D5A351", +"n c #DCA651", +"m c #DFAA56", +"M c #D8A75A", +"N c #DCAA5B", +"B c #C79E60", +"V c #C09F6E", +"C c #C8A162", +"Z c #CCA66A", +"A c #DEAE62", +"S c #D6AD6E", +"D c #DEB16A", +"F c #C1A272", +"G c #C6AA7E", +"H c #D3AE73", +"J c #D8AF71", +"K c #DEB676", +"L c #D8B47C", +"P c #E2B266", +"I c #E3B36A", +"U c #E5B974", +"Y c #E3BB7C", +"T c #8B8C85", +"R c #8D8E88", +"E c #918F83", +"W c #8F908C", +"Q c #9A9483", +"! c #92938E", +"~ c #98968B", +"^ c #9E9788", +"/ c #939591", +"( c #969894", +") c #9C9A94", +"_ c #9C9E99", +"` c #A19C94", +"' c #A29E99", +"] c #9FA19C", +"[ c #ADA189", +"{ c #B9A486", +"} c #AEA695", +"| c #A1A29C", +" . c #A9A89F", +".. c #B7AE98", +"X. c #A4A6A2", +"o. c #A6A9A4", +"O. c #AAABA7", +"+. c #ACACA9", +"@. c #AEB1AB", +"#. c #B0B2AE", +"$. c #B3B5B1", +"%. c #B6B8B2", +"&. c #B9BBB7", +"*. c #BBBCBA", +"=. c #C3AD8A", +"-. c #CDB288", +";. c #C9B495", +":. c #D3BC96", +">. c #E3BD84", +",. c #E1BF88", +"<. c #D2BEA0", +"1. c #C8BFB0", +"2. c #BFC1BB", +"3. c #D8C095", +"4. c #E5C087", +"5. c #EAC385", +"6. c #E6C289", +"7. c #E8C288", +"8. c #E4C492", +"9. c #E6C99B", +"0. c #EBCD9E", +"q. c #D4C1A5", +"w. c #D8C5A7", +"e. c #CBC2B1", +"r. c #C1C4BE", +"t. c #D1C7B7", +"y. c #DFCEB4", +"u. c #E7CDA5", +"i. c #EDD0A3", +"p. c #E6D0AF", +"a. c #EED5AC", +"s. c #E6D2B0", +"d. c #EDD6B3", +"f. c #EBD7B9", +"g. c #ECD9BC", +"h. c #F3DBB5", +"j. c #C4C5C3", +"k. c #C6C9C3", +"l. c #C9CCC6", +"z. c #CCCDCA", +"x. c #D4CFC3", +"c. c #CFD0CC", +"v. c #D1D2CF", +"b. c #D3D4D3", +"n. c #D6D8D5", +"m. c #DADAD6", +"M. c #DADBDA", +"N. c #E7D8C0", +"B. c #ECDCC2", +"V. c #DFE1DE", +"C. c #F3E1C3", +"Z. c #F2E4CC", +"A. c #ECE2D4", +"S. c #EEE7DA", +"D. c #F1E5D2", +"F. c #F7E9D6", +"G. c #F0E8DC", +"H. c #E3E3E3", +"J. c #E7E8E7", +"K. c #E8E8E7", +"L. c #ECECEB", +"P. c #F4EFE6", +"I. c #F5F1EB", +"U. c #F3F4F3", +"Y. c #F8F5F1", +"T. c #FAF9F7", +"R. c #FFFFFF", +/* pixels */ +"R.R.R.R.R.R.R.R.R.R.R.R.R.R.R.R.R.R.R.R.R.R.F X ; A.R.R.R.R.R.R.", +"R.R.R.R.R.R.R.R.R.R.R.R.R.R.R.R.R.R.R.R.R.R.< u.H # S.R.R.R.R.R.", +"R.R.R.R.R.R.R.R.R.R.R.R.R.R.R.R.R.T.y.F l R., d.a.Z # G.R.R.R.R.", +"R.R.R.R.R.R.R.R.R.R.R.R.R.R.A.V , + w :., y.< f.Y 8.C # P.R.R.R.", +"R.R.R.R.R.R.R.R.R.R.R.R.R.G : -.N.I.Y.T.t X . f.5.n 4.e # T.R.R.", +"R.R.R.R.R.R.R.R.R.R.R.R.R.$ p.F.Z.Z.D.Y.F ..l s.h.U D 6.# :.R.R.", +"R.R.R.R.R.R.R.R.R.R.R.U.l.* u.d.i.d.B.I.y k 3., C.5.N >.> h R.R.", +"R.R.R.R.R.R.R.R.R.U.%.W ] , 8.0.0.| R T T T T T T ~ Q >.< h R.R.", +"R.R.R.R.R.R.R.R.n.W ] 2.k.q 3.h.a.| v.U.U.L.L.L.L.U.o.9.< V R.R.", +"R.R.R.R.R.R.R.z.R @.j.k.z.q K 6 ) M.R.R.R.R.R.R.R.o.L > F R.R.", +"R.R.R.R.R.R.M.W $.2.k.l.j.r 0.8.6.! b.R.R.R.R.R.R.R.o.p.8 -.R.R.", +"R.R.R.R.R.T.! @.2.k.l.O.W 6 4.I N E j.R.R.R.R.R.$./ T J 9 w.R.R.", +"R.R.R.R.R.*._ 2.r.l.] X.U., Y A v a k.R.R.L.R.R.U.E 0 x 7 w.R.R.", +"R.R.R.R.R.! %.r.k.@.| T.R., Y N v a j.R.R.@.U.R.R.*.r b 7 <.R.R.", +"R.R.R.R.n.! r.k.k.W L.R.R., Y A v f M.R.R.O.&.R.R.H.a M 9 ;.T.R.", +"R.R.R.R.&.X.j.l.$.O.R.R.T., Y P n g M.R.R.o.W R.R.R.R D 9 { L.T.", +"R.R.w + 2 @.l.c. .1 + . q.: >.P m g ! ! / T E L.R.R._ K e j H.R.", +"R.R.6 Z. .%.c.b.' <.a.c =.= Y P m N M M M M s H.R.R.X.>.z u n.T.", +"R.R., Z.} $.v.n.O.=.i.c { * U 7.7.5.7.4.4.4.Q L.R.R.] ,.8 5 n.T.", +"R.R.6 Z...@.n.M.&.[ i.B =.5 . @ @ @ @ @ @ # R T.R.R.! @ o ] V.T.", +"R.R., A.x._ m.V.m.! G.:.;.z.+.' ` ` ` ^ ) W $.R.R.K.T ) $.b.U.R.", +"R.T., L K R m.H.K.#.[ y ;.U.J.H.H.H.H.H.b.W L.R.R.j.+.H.L.U.R.R.", +"R.M.& H A j #.K.L.L.| 1 + # # # # # $ t./ c.R.R.T./ H.R.T.R.R.R.", +"L.O.O S 8.6.W M.U.T.Y.O.) e.g.f.a.w.d ! b.R.R.R.&.+.R.R.R.R.R.R.", +"U.2.% - ( U.T.R.R.M.+.W R T _ *.U.R.R.R.b.( U.R.R.R.R.R.R.", +"R.U.V.n.b.b.b.o._ L.R.R.R.R.R.T.R.R.R.R.R.R.z./ J.R.R.R.R.R.R.R.", +"R.R.R.R.T.R.R.T.j./ c.R.R.R.R.R.R.R.R.R.U.#.( L.R.R.R.R.R.R.R.R.", +"R.R.R.R.R.R.R.R.T.4 ^ ) j.J.T.R.R.U.M.$.! &.T.R.R.R.R.R.R.R.R.R.", +"R.R.R.R.R.R.R.T.M.& H Z g a T R T E i Q L.R.R.R.R.R.R.R.R.R.R.R.", +"R.R.R.R.R.R.R.U.+.O S ,.>.7.>.7.,.6.< 3 H.R.R.R.R.R.R.R.R.R.R.R.", +"R.R.R.R.R.R.R.U.2.% # $ & $ $ $ $ & X p L.R.R.R.R.R.R.R.R.R.R.R.", +"R.R.R.R.R.R.R.R.Y.m.e.1.1.1.1.1.1.e.t.L.R.R.R.R.R.R.R.R.R.R.R.R." +}; diff --git a/onlineupdate/source/update/updater/progressui_null.cxx b/onlineupdate/source/update/updater/progressui_null.cxx new file mode 100644 index 000000000..66d294a13 --- /dev/null +++ b/onlineupdate/source/update/updater/progressui_null.cxx @@ -0,0 +1,27 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#if !defined(MACOSX) && !defined(UNIX) && !defined(_WIN32) +#include "progressui.h" + +int InitProgressUI(int *argc, char ***argv) +{ + return 0; +} + +int ShowProgressUI() +{ + return 0; +} + +void QuitProgressUI() +{ +} + +void UpdateProgressUI(float progress) +{ +} +#endif // !defined(MACOSX) && !defined(UNIX) && !defined(_WIN32) diff --git a/onlineupdate/source/update/updater/progressui_win.cxx b/onlineupdate/source/update/updater/progressui_win.cxx new file mode 100644 index 000000000..0b8017823 --- /dev/null +++ b/onlineupdate/source/update/updater/progressui_win.cxx @@ -0,0 +1,348 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifdef _WIN32 +#include <stdio.h> +#ifndef UNICODE +#define UNICODE +#endif +#include <windows.h> +#include <commctrl.h> +#include <process.h> +#include <io.h> + +#include "resource.h" +#include "progressui.h" +#include "readstrings.h" +#include "errors.h" + +#define TIMER_ID 1 +#define TIMER_INTERVAL 100 + +#define RESIZE_WINDOW(hwnd, extrax, extray) \ + { \ + RECT windowSize; \ + GetWindowRect(hwnd, &windowSize); \ + SetWindowPos(hwnd, 0, 0, 0, windowSize.right - windowSize.left + extrax, \ + windowSize.bottom - windowSize.top + extray, \ + SWP_NOMOVE | SWP_NOZORDER); \ + } + +#define MOVE_WINDOW(hwnd, dx, dy) \ + { \ + RECT rc; \ + POINT pt; \ + GetWindowRect(hwnd, &rc); \ + pt.x = rc.left; \ + pt.y = rc.top; \ + ScreenToClient(GetParent(hwnd), &pt); \ + SetWindowPos(hwnd, 0, pt.x + dx, pt.y + dy, 0, 0, \ + SWP_NOSIZE | SWP_NOZORDER); \ + } + +static float sProgress; // between 0 and 100 +static BOOL sQuit = FALSE; +static BOOL sIndeterminate = FALSE; +static StringTable sUIStrings; + +static BOOL +GetStringsFile(WCHAR filename[MAX_PATH]) +{ + if (!GetModuleFileNameW(nullptr, filename, MAX_PATH)) + return FALSE; + + WCHAR *dot = wcsrchr(filename, '.'); + if (!dot || wcsicmp(dot + 1, L"exe")) + return FALSE; + + wcscpy(dot + 1, L"ini"); + return TRUE; +} + +static void +UpdateDialog(HWND hDlg) +{ + int pos = int(sProgress + 0.5f); + HWND hWndPro = GetDlgItem(hDlg, IDC_PROGRESS); + SendMessage(hWndPro, PBM_SETPOS, pos, 0L); +} + +// The code in this function is from MSDN: +// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winui/winui/windowsuserinterface/windowing/dialogboxes/usingdialogboxes.asp +static void +CenterDialog(HWND hDlg) +{ + RECT rc, rcOwner, rcDlg; + + // Get the owner window and dialog box rectangles. + HWND desktop = GetDesktopWindow(); + + GetWindowRect(desktop, &rcOwner); + GetWindowRect(hDlg, &rcDlg); + CopyRect(&rc, &rcOwner); + + // Offset the owner and dialog box rectangles so that + // right and bottom values represent the width and + // height, and then offset the owner again to discard + // space taken up by the dialog box. + + OffsetRect(&rcDlg, -rcDlg.left, -rcDlg.top); + OffsetRect(&rc, -rc.left, -rc.top); + OffsetRect(&rc, -rcDlg.right, -rcDlg.bottom); + + // The new position is the sum of half the remaining + // space and the owner's original position. + + SetWindowPos(hDlg, + HWND_TOP, + rcOwner.left + (rc.right / 2), + rcOwner.top + (rc.bottom / 2), + 0, 0, // ignores size arguments + SWP_NOSIZE); +} + +static void +InitDialog(HWND hDlg) +{ + WCHAR szwTitle[MAX_TEXT_LEN]; + WCHAR szwInfo[MAX_TEXT_LEN]; + + MultiByteToWideChar(CP_UTF8, 0, sUIStrings.title, -1, szwTitle, + sizeof(szwTitle)/sizeof(szwTitle[0])); + MultiByteToWideChar(CP_UTF8, 0, sUIStrings.info, -1, szwInfo, + sizeof(szwInfo)/sizeof(szwInfo[0])); + + SetWindowTextW(hDlg, szwTitle); + SetWindowTextW(GetDlgItem(hDlg, IDC_INFO), szwInfo); + + // Set dialog icon + HICON hIcon = LoadIcon(GetModuleHandle(nullptr), + MAKEINTRESOURCE(IDI_DIALOG)); + if (hIcon) + SendMessage(hDlg, WM_SETICON, ICON_BIG, (LPARAM) hIcon); + + HWND hWndPro = GetDlgItem(hDlg, IDC_PROGRESS); + SendMessage(hWndPro, PBM_SETRANGE, 0, MAKELPARAM(0, 100)); + if (sIndeterminate) + { + LONG_PTR val = GetWindowLongPtr(hWndPro, GWL_STYLE); + SetWindowLongPtr(hWndPro, GWL_STYLE, val|PBS_MARQUEE); + SendMessage(hWndPro,(UINT) PBM_SETMARQUEE,(WPARAM) TRUE,(LPARAM)50 ); + } + + // Resize the dialog to fit all of the text if necessary. + RECT infoSize, textSize; + HWND hWndInfo = GetDlgItem(hDlg, IDC_INFO); + + // Get the control's font for calculating the new size for the control + HDC hDCInfo = GetDC(hWndInfo); + HFONT hInfoFont; + HFONT hOldFont = 0; + hInfoFont = (HFONT)SendMessage(hWndInfo, WM_GETFONT, 0, 0); + + if (hInfoFont) + hOldFont = (HFONT)SelectObject(hDCInfo, hInfoFont); + + // Measure the space needed for the text on a single line. DT_CALCRECT means + // nothing is drawn. + if (DrawText(hDCInfo, szwInfo, -1, &textSize, + DT_CALCRECT | DT_NOCLIP | DT_SINGLELINE)) + { + GetClientRect(hWndInfo, &infoSize); + SIZE extra; + // Calculate the additional space needed for the text by subtracting from + // the rectangle returned by DrawText the existing client rectangle's width + // and height. + extra.cx = (textSize.right - textSize.left) - \ + (infoSize.right - infoSize.left); + extra.cy = (textSize.bottom - textSize.top) - \ + (infoSize.bottom - infoSize.top); + if (extra.cx < 0) + extra.cx = 0; + if (extra.cy < 0) + extra.cy = 0; + if ((extra.cx > 0) || (extra.cy > 0)) + { + RESIZE_WINDOW(hDlg, extra.cx, extra.cy); + RESIZE_WINDOW(hWndInfo, extra.cx, extra.cy); + RESIZE_WINDOW(hWndPro, extra.cx, 0); + MOVE_WINDOW(hWndPro, 0, extra.cy); + } + } + + if (hOldFont) + SelectObject(hDCInfo, hOldFont); + + ReleaseDC(hWndInfo, hDCInfo); + + CenterDialog(hDlg); // make dialog appear in the center of the screen + + SetTimer(hDlg, TIMER_ID, TIMER_INTERVAL, nullptr); +} + +// Message handler for update dialog. +static LRESULT CALLBACK +DialogProc(HWND hDlg, UINT message, WPARAM /*wParam*/, LPARAM /*lParam*/) +{ + switch (message) + { + case WM_INITDIALOG: + InitDialog(hDlg); + return TRUE; + + case WM_TIMER: + if (sQuit) + { + EndDialog(hDlg, 0); + } + else + { + UpdateDialog(hDlg); + } + return TRUE; + + case WM_COMMAND: + return TRUE; + } + return FALSE; +} + +int +InitProgressUI(int* /*argc*/, WCHAR*** /*argv*/) +{ + return 0; +} + +/** + * Initializes the progress UI strings + * + * @return 0 on success, -1 on error +*/ +int +InitProgressUIStrings() +{ + // If we do not have updater.ini, then we should not bother showing UI. + WCHAR filename[MAX_PATH]; + if (!GetStringsFile(filename)) + { + strcpy(sUIStrings.title, "LibreOffice Update"); + strcpy(sUIStrings.info, "Please wait while we update your installation."); + return 0; + } + + if (_waccess(filename, 04)) + { + strcpy(sUIStrings.title, "LibreOffice Update"); + strcpy(sUIStrings.info, "Please wait while we update your installation."); + return 0; + } + + // If the updater.ini doesn't have the required strings, then we should not + // bother showing UI. + if (ReadStrings(filename, &sUIStrings) != OK) + { + strcpy(sUIStrings.title, "LibreOffice Update"); + strcpy(sUIStrings.info, "Please wait while we update your installation."); + } + + return 0; +} + +int +ShowProgressUI(bool indeterminate, bool initUIStrings) +{ + sIndeterminate = indeterminate; + if (!indeterminate) + { + // Only show the Progress UI if the process is taking a significant amount of + // time where a significant amount of time is defined as .5 seconds after + // ShowProgressUI is called sProgress is less than 70. + Sleep(500); + + if (sQuit || sProgress > 70.0f) + return 0; + } + + // Don't load the UI if there's an <exe_name>.Local directory for redirection. + WCHAR appPath[MAX_PATH + 1] = { L'\0' }; + if (!GetModuleFileNameW(nullptr, appPath, MAX_PATH)) + { + return -1; + } + + if (wcslen(appPath) + wcslen(L".Local") >= MAX_PATH) + { + return -1; + } + + wcscat(appPath, L".Local"); + + if (!_waccess(appPath, 04)) + { + return -1; + } + + // Don't load the UI if the strings for the UI are not provided. + if (initUIStrings && InitProgressUIStrings() == -1) + { + return -1; + } + + if (!GetModuleFileNameW(nullptr, appPath, MAX_PATH)) + { + return -1; + } + + // Use an activation context that supports visual styles for the controls. + ACTCTXW actx = {0}; + actx.cbSize = sizeof(ACTCTXW); + actx.dwFlags = ACTCTX_FLAG_RESOURCE_NAME_VALID | ACTCTX_FLAG_HMODULE_VALID; + actx.hModule = GetModuleHandle(NULL); // Use the embedded manifest + // This is needed only for Win XP but doesn't cause a problem with other + // versions of Windows. + actx.lpSource = appPath; + actx.lpResourceName = MAKEINTRESOURCE(IDR_COMCTL32_MANIFEST); + + HANDLE hactx = CreateActCtxW(&actx); + ULONG_PTR actxCookie = NULL; + if (hactx != INVALID_HANDLE_VALUE) + { + // Push the specified activation context to the top of the activation stack. + ActivateActCtx(hactx, &actxCookie); + } + + INITCOMMONCONTROLSEX icc = + { + sizeof(INITCOMMONCONTROLSEX), + ICC_PROGRESS_CLASS + }; + InitCommonControlsEx(&icc); + + DialogBox(GetModuleHandle(nullptr), + MAKEINTRESOURCE(IDD_DIALOG), nullptr, + (DLGPROC) DialogProc); + + if (hactx != INVALID_HANDLE_VALUE) + { + // Deactivate the context now that the comctl32.dll is loaded. + DeactivateActCtx(0, actxCookie); + } + + return 0; +} + +void +QuitProgressUI() +{ + sQuit = TRUE; +} + +void +UpdateProgressUI(float progress) +{ + sProgress = progress; // 32-bit writes are atomic +} +#endif // _WIN32 diff --git a/onlineupdate/source/update/updater/resource.h b/onlineupdate/source/update/updater/resource.h new file mode 100644 index 000000000..6b6091c7b --- /dev/null +++ b/onlineupdate/source/update/updater/resource.h @@ -0,0 +1,29 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by updater.rc +// +#define IDD_DIALOG 101 +#define IDC_PROGRESS 1000 +#define IDC_INFO 1002 +#define IDI_DIALOG 1003 +#define TYPE_CERT 512 +#define IDR_PRIMARY_CERT 1004 +#define IDR_BACKUP_CERT 1005 +#define IDS_UPDATER_IDENTITY 1006 +#define IDR_XPCSHELL_CERT 1007 +#define IDR_COMCTL32_MANIFEST 17 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1008 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/onlineupdate/source/update/updater/updater-common.build b/onlineupdate/source/update/updater/updater-common.build new file mode 100644 index 000000000..6516a4843 --- /dev/null +++ b/onlineupdate/source/update/updater/updater-common.build @@ -0,0 +1,119 @@ +# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +srcs = [ + 'archivereader.cpp', + 'bspatch.cpp', + 'updater.cpp', +] + +have_progressui = 0 + +if CONFIG['VERIFY_MAR_SIGNATURE']: + USE_LIBS += [ + 'verifymar', + ] + +if CONFIG['OS_ARCH'] == 'WINNT': + have_progressui = 1 + srcs += [ + 'loaddlls.cpp', + 'progressui_win.cpp', + 'win_dirent.cpp', + ] + RCINCLUDE = '%supdater.rc' % updater_rel_path + DEFINES['UNICODE'] = True + DEFINES['_UNICODE'] = True + DEFINES['NOMINMAX'] = True + USE_STATIC_LIBS = True + + # Pick up nsWindowsRestart.cpp + LOCAL_INCLUDES += [ + '/toolkit/xre', + ] + USE_LIBS += [ + 'updatecommon-standalone', + ] + OS_LIBS += [ + 'comctl32', + 'ws2_32', + 'shell32', + 'shlwapi', + 'crypt32', + 'advapi32', + ] +elif CONFIG['OS_ARCH'] == 'Linux' and CONFIG['VERIFY_MAR_SIGNATURE']: + USE_LIBS += [ + 'nss', + 'signmar', + 'updatecommon', + ] + OS_LIBS += CONFIG['NSPR_LIBS'] +else: + USE_LIBS += [ + 'updatecommon', + ] + +USE_LIBS += [ + 'mar', +] + +if CONFIG['MOZ_NATIVE_BZ2']: + OS_LIBS += CONFIG['MOZ_BZ2_LIBS'] +else: + USE_LIBS += [ + 'bz2', + ] + +if CONFIG['MOZ_ENABLE_GTK']: + have_progressui = 1 + srcs += [ + 'progressui_gtk.cpp', + ] + +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa': + have_progressui = 1 + srcs += [ + 'launchchild_osx.mm', + 'progressui_osx.mm', + ] + OS_LIBS += [ + '-framework Cocoa', + '-framework Security', + ] + +if have_progressui == 0: + srcs += [ + 'progressui_null.cpp', + ] + +SOURCES += sorted(srcs) + +DEFINES['NS_NO_XPCOM'] = True +DISABLE_STL_WRAPPING = True +for var in ('MAR_CHANNEL_ID', 'MOZ_APP_VERSION'): + DEFINES[var] = '"%s"' % CONFIG[var] + +LOCAL_INCLUDES += [ + '/toolkit/mozapps/update/common', + '/xpcom/glue', +] + +DELAYLOAD_DLLS += [ + 'crypt32.dll', + 'comctl32.dll', + 'userenv.dll', + 'wsock32.dll', +] + +if CONFIG['_MSC_VER']: + WIN32_EXE_LDFLAGS += ['-ENTRY:wmainCRTStartup'] +elif CONFIG['OS_ARCH'] == 'WINNT': + WIN32_EXE_LDFLAGS += ['-municode'] + +if CONFIG['MOZ_WIDGET_GTK']: + CXXFLAGS += CONFIG['TK_CFLAGS'] + OS_LIBS += CONFIG['TK_LIBS'] diff --git a/onlineupdate/source/update/updater/updater-xpcshell/Makefile.in b/onlineupdate/source/update/updater/updater-xpcshell/Makefile.in new file mode 100644 index 000000000..213648834 --- /dev/null +++ b/onlineupdate/source/update/updater/updater-xpcshell/Makefile.in @@ -0,0 +1,32 @@ +# vim:set ts=8 sw=8 sts=8 noet: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +# For changes here, also consider ../Makefile.in + +XPCSHELLTESTROOT = $(abspath $(DEPTH))/_tests/xpcshell/toolkit/mozapps/update/tests +MOCHITESTROOT = $(abspath $(DEPTH))/_tests/testing/mochitest/chrome/toolkit/mozapps/update/tests + +include $(topsrcdir)/config/rules.mk + +libs:: +ifeq (cocoa,$(MOZ_WIDGET_TOOLKIT)) + # Copy for xpcshell tests + $(NSINSTALL) -D $(XPCSHELLTESTROOT)/data/updater-xpcshell.app + rsync -a -C --exclude '*.in' $(srcdir)/../macbuild/Contents $(XPCSHELLTESTROOT)/data/updater-xpcshell.app + sed -e 's/%APP_NAME%/$(MOZ_APP_DISPLAYNAME)/' $(srcdir)/../macbuild/Contents/Resources/English.lproj/InfoPlist.strings.in | \ + iconv -f UTF-8 -t UTF-16 > $(XPCSHELLTESTROOT)/data/updater-xpcshell.app/Contents/Resources/English.lproj/InfoPlist.strings + $(NSINSTALL) -D $(XPCSHELLTESTROOT)/data/updater-xpcshell.app/Contents/MacOS/updater-xpcshell + $(NSINSTALL) $(PROGRAM) $(XPCSHELLTESTROOT)/data/updater-xpcshell.app/Contents/MacOS + rm -f $(PROGRAM) + rm -Rf $(XPCSHELLTESTROOT)/data/updater.app + mv $(XPCSHELLTESTROOT)/data/updater-xpcshell.app $(XPCSHELLTESTROOT)/data/updater.app + mv $(XPCSHELLTESTROOT)/data/updater.app/Contents/MacOS/updater-xpcshell $(XPCSHELLTESTROOT)/data/updater.app/Contents/MacOS/updater + + # Copy for mochitest chrome tests + rsync -a -C $(XPCSHELLTESTROOT)/data/updater.app $(MOCHITESTROOT)/data/updater.app +else + cp $(PROGRAM) $(XPCSHELLTESTROOT)/data/updater$(BIN_SUFFIX) + cp $(PROGRAM) $(MOCHITESTROOT)/data/updater$(BIN_SUFFIX) +endif diff --git a/onlineupdate/source/update/updater/updater-xpcshell/moz.build b/onlineupdate/source/update/updater/updater-xpcshell/moz.build new file mode 100644 index 000000000..ba5c844cf --- /dev/null +++ b/onlineupdate/source/update/updater/updater-xpcshell/moz.build @@ -0,0 +1,13 @@ +# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +Program('updater-xpcshell') + +updater_rel_path = '../' +DIST_INSTALL = False +DEFINES['TEST_UPDATER'] = True +include('../updater-common.build') +FAIL_ON_WARNINGS = True diff --git a/onlineupdate/source/update/updater/updater.cxx b/onlineupdate/source/update/updater/updater.cxx new file mode 100644 index 000000000..54750afb4 --- /dev/null +++ b/onlineupdate/source/update/updater/updater.cxx @@ -0,0 +1,4591 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Manifest Format + * --------------- + * + * contents = 1*( line ) + * line = method LWS *( param LWS ) CRLF + * CRLF = "\r\n" + * LWS = 1*( " " | "\t" ) + * + * Available methods for the manifest file: + * + * updatev2.manifest + * ----------------- + * method = "add" | "add-if" | "patch" | "patch-if" | "remove" | + * "rmdir" | "rmrfdir" | type + * + * 'type' is the update type (e.g. complete or partial) and when present MUST + * be the first entry in the update manifest. The type is used to support + * downgrades by causing the actions defined in precomplete to be performed. + * + * updatev3.manifest + * ----------------- + * method = "add" | "add-if" | "add-if-not" | "patch" | "patch-if" | + * "remove" | "rmdir" | "rmrfdir" | type + * + * 'add-if-not' adds a file if it doesn't exist. + * + * precomplete + * ----------- + * method = "remove" | "rmdir" + */ +#include "bspatch.h" +#include "progressui.h" +#include "archivereader.h" +#include "readstrings.h" +#include "errors.h" +#include "bzlib.h" +#include <thread> +#include <vector> + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <stdarg.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <limits.h> +#include <errno.h> +#include <algorithm> +#include <memory> + +#include <config_version.h> + +#include "updatelogging.h" + +#include <onlineupdate/mozilla/Compiler.h> +#include <onlineupdate/mozilla/Types.h> + +#ifdef _WIN32 +#include <comphelper/windowsStart.hxx> +#include "uachelper.h" +#include "pathhash.h" + +// TODO:moggi taken from the mozilla code -- find a better solution +#define INVALID_APPLYTO_DIR_ERROR 74 +#define REMOVE_FILE_SPEC_ERROR 71 +#define INVALID_APPLYTO_DIR_STAGED_ERROR 72 + +#endif + + +// Amount of the progress bar to use in each of the 3 update stages, +// should total 100.0. +#define PROGRESS_PREPARE_SIZE 20.0f +#define PROGRESS_EXECUTE_SIZE 75.0f +#define PROGRESS_FINISH_SIZE 5.0f + +// Amount of time in ms to wait for the parent process to close +#ifdef _WIN32 +#define PARENT_WAIT 5000 +#endif + +#if defined(MACOSX) +// These functions are defined in launchchild_osx.mm +void CleanupElevatedMacUpdate(bool aFailureOccurred); +bool IsOwnedByGroupAdmin(const char* aAppBundle); +bool IsRecursivelyWritable(const char* aPath); +void LaunchChild(int argc, const char** argv); +void LaunchMacPostProcess(const char* aAppBundle); +bool ObtainUpdaterArguments(int* argc, char*** argv); +bool ServeElevatedUpdate(int argc, const char** argv); +void SetGroupOwnershipAndPermissions(const char* aAppBundle); +struct UpdateServerThreadArgs +{ + int argc; + const NS_tchar** argv; +}; +#endif + +#ifndef SSIZE_MAX +# define SSIZE_MAX LONG_MAX +#endif + +// We want to use execv to invoke the callback executable on platforms where +// we were launched using execv. See nsUpdateDriver.cpp. +#if defined(UNIX) && !defined(MACOSX) +#define USE_EXECV +#endif + +#if defined(VERIFY_MAR_SIGNATURE) && !defined(_WIN32) && !defined(MACOSX) +#include <nss.h> +#include <nspr.h> +#endif + +#ifdef _WIN32 +#ifdef MAINTENANCE_SERVICE +#include "registrycertificates.h" +#endif +BOOL PathAppendSafe(LPWSTR base, LPCWSTR extra); +BOOL PathGetSiblingFilePath(LPWSTR destinationBuffer, + LPCWSTR siblingFilePath, + LPCWSTR newFileName); +#include "updatehelper.h" + +// Closes the handle if valid and if the updater is elevated returns with the +// return code specified. This prevents multiple launches of the callback +// application by preventing the elevated process from launching the callback. +#define EXIT_WHEN_ELEVATED(path, handle, retCode) \ +{ \ + if (handle != INVALID_HANDLE_VALUE) { \ + CloseHandle(handle); \ + } \ + if (_waccess(path, F_OK) == 0 && NS_tremove(path) != 0) { \ + LogFinish(); \ + return retCode; \ + } \ +} +#endif + +//----------------------------------------------------------------------------- + +// This variable lives in libbz2. It's declared in bzlib_private.h, so we just +// declare it here to avoid including that entire header file. +#if defined __GNUC__ +extern "C" __attribute__((visibility("default"))) unsigned int BZ2_crc32Table[256]; +#elif defined(__SUNPRO_C) || defined(__SUNPRO_CC) +extern "C" __global unsigned int BZ2_crc32Table[256]; +#else +extern "C" unsigned int BZ2_crc32Table[256]; +#endif + +static unsigned int +crc32(const unsigned char *buf, unsigned int len) +{ + unsigned int crc = 0xffffffffL; + + const unsigned char *end = buf + len; + for (; buf != end; ++buf) + crc = (crc << 8) ^ BZ2_crc32Table[(crc >> 24) ^ *buf]; + + crc = ~crc; + return crc; +} + +//----------------------------------------------------------------------------- + +// A simple stack based container for a FILE struct that closes the +// file descriptor from its destructor. +class AutoFile +{ +public: + explicit AutoFile(FILE* file = nullptr) + : mFile(file) + { + } + + ~AutoFile() + { + if (mFile != nullptr) + fclose(mFile); + } + + AutoFile &operator=(FILE* file) + { + if (mFile != 0) + fclose(mFile); + mFile = file; + return *this; + } + + operator FILE*() + { + return mFile; + } + + FILE* get() + { + return mFile; + } + +private: + FILE* mFile; +}; + +struct MARChannelStringTable +{ + MARChannelStringTable() + { + MARChannelID[0] = '\0'; + } + + char MARChannelID[MAX_TEXT_LEN]; +}; + +//----------------------------------------------------------------------------- + +static NS_tchar* gPatchDirPath; +static NS_tchar gInstallDirPath[MAXPATHLEN]; +static NS_tchar gWorkingDirPath[MAXPATHLEN]; +static bool gSucceeded = false; +static bool sStagedUpdate = false; +static bool sReplaceRequest = false; +static bool sUsingService = false; + +#ifdef _WIN32 +// The current working directory specified in the command line. +static NS_tchar* gDestPath; +static NS_tchar gCallbackRelPath[MAXPATHLEN]; +static NS_tchar gCallbackBackupPath[MAXPATHLEN]; +static NS_tchar gDeleteDirPath[MAXPATHLEN]; +#endif + +static const NS_tchar kWhitespace[] = NS_T(" \t"); +static const NS_tchar kNL[] = NS_T("\r\n"); +static const NS_tchar kQuote[] = NS_T("\""); + +static NS_tchar* +mstrtok(const NS_tchar *delims, NS_tchar **str) +{ + if (!*str || !**str) + { + *str = nullptr; + return nullptr; + } + + // skip leading "whitespace" + NS_tchar *ret = *str; + const NS_tchar *d; + do + { + for (d = delims; *d != NS_T('\0'); ++d) + { + if (*ret == *d) + { + ++ret; + break; + } + } + } + while (*d); + + if (!*ret) + { + *str = ret; + return nullptr; + } + + NS_tchar *i = ret; + do + { + for (d = delims; *d != NS_T('\0'); ++d) + { + if (*i == *d) + { + *i = NS_T('\0'); + *str = ++i; + return ret; + } + } + ++i; + } + while (*i); + + *str = nullptr; + return ret; +} + +#if defined(_WIN32) && defined(MAINTENANCE_SERVICE) +static bool +EnvHasValue(const char *name) +{ + const char *val = getenv(name); + return (val && *val); +} +#endif + +/** + * Converts a relative update path to an absolute path related to the working + * or install directory. Allocates a new NS_tchar[] based path! + * + * @param relpath + * The relative path to convert to a full path. + * @return valid filesystem full path or nullptr if memory allocation fails. + */ +static NS_tchar* +new_absolute_path(const NS_tchar *relpath) +{ + NS_tchar *destpath = sStagedUpdate ? gWorkingDirPath : gInstallDirPath; + size_t lendestpath = NS_tstrlen(destpath); + size_t lenrelpath = NS_tstrlen(relpath); + NS_tchar *s = new NS_tchar[lendestpath + lenrelpath + 2]; + + NS_tchar *c = s; + + NS_tstrcpy(c, destpath); + c += lendestpath; + NS_tstrcat(c, NS_T("/")); + c++; + + NS_tstrcat(c, relpath); + c += lenrelpath; + *c = NS_T('\0'); + return s; +} + +namespace { + +bool is_userprofile_in_instdir() +{ + return false; + /* + // the algorithm is: + // 1.) if userprofile path length is smaller than installation dir, + // the profile is surely not in instdir + // 2.) else comparing the two paths looking only at the installation dir + // characters should yield an equal string + NS_tchar userprofile[MAXPATHLEN]; + NS_tstrcpy(userprofile, gPatchDirPath); + NS_tchar *slash = (NS_tchar *) NS_tstrrchr(userprofile, NS_T('/')); + if (slash) + *slash = NS_T('\0'); + + size_t userprofile_len = NS_tstrlen(userprofile); + size_t installdir_len = NS_tstrlen(gInstallDirPath); + + if (userprofile_len < installdir_len) + return false; + + return NS_tstrncmp(userprofile, gInstallDirPath, installdir_len) == 0; + */ +} + +} + +/** + * Get a pointer in the absolute path, relative to the working or install + * directory. Returns itself, if not absolute or outside of the directory. + * + * @param abs_path + * An absolute path. + * return pointer to the location within fullpath where the relative path starts + * or fullpath itself if it already looks relative. + */ +static const NS_tchar* +get_relative_offset(const NS_tchar *abs_path) +{ + // If the path isn't absolute, just return it as-is. +#ifdef _WIN32 + if (abs_path[1] != ':' && abs_path[2] != '\\') + { +#else + if (abs_path[0] != '/') + { +#endif + return abs_path; + } + + NS_tchar *prefix = sStagedUpdate ? gWorkingDirPath : gInstallDirPath; + + size_t len = NS_tstrlen(prefix); + if (NS_tstrlen(abs_path) <= len) + return abs_path; + if (0 != strncmp(abs_path, prefix, len)) + return abs_path; + return abs_path + len + 1; +} + +/** + * Gets the platform specific path and performs simple checks to the path. If + * the path checks don't pass nullptr will be returned. + * + * @param line + * The line from the manifest that contains the path. + * @param isdir + * Whether the path is a directory path. Defaults to false. + * @return valid filesystem path or nullptr if the path checks fail. + */ +static NS_tchar* +get_valid_path(NS_tchar **line, bool isdir = false) +{ + NS_tchar *path = mstrtok(kQuote, line); + if (!path) + { + LOG(("get_valid_path: unable to determine path: " LOG_S, line)); + return nullptr; + } + + // All paths must be relative from the current working directory + if (path[0] == NS_T('/')) + { + LOG(("get_valid_path: path must be relative: " LOG_S, path)); + return nullptr; + } + +#ifdef _WIN32 + // All paths must be relative from the current working directory + if (path[0] == NS_T('\\') || path[1] == NS_T(':')) + { + LOG(("get_valid_path: path must be relative: " LOG_S, path)); + return nullptr; + } +#endif + + if (isdir) + { + // Directory paths must have a trailing forward slash. + if (path[NS_tstrlen(path) - 1] != NS_T('/')) + { + LOG(("get_valid_path: directory paths must have a trailing forward " \ + "slash: " LOG_S, path)); + return nullptr; + } + + // Remove the trailing forward slash because stat on Windows will return + // ENOENT if the path has a trailing slash. + path[NS_tstrlen(path) - 1] = NS_T('\0'); + } + + // Don't allow relative paths that resolve to a parent directory. + if (NS_tstrstr(path, NS_T("..")) != nullptr) + { + LOG(("get_valid_path: paths must not contain '..': " LOG_S, path)); + return nullptr; + } + + return path; +} + +static NS_tchar* +get_quoted_path(const NS_tchar *path) +{ + size_t lenQuote = NS_tstrlen(kQuote); + size_t lenPath = NS_tstrlen(path); + size_t len = lenQuote + lenPath + lenQuote + 1; + + NS_tchar *s = (NS_tchar *) malloc(len * sizeof(NS_tchar)); + if (!s) + return nullptr; + + NS_tchar *c = s; + NS_tstrcpy(c, kQuote); + c += lenQuote; + NS_tstrcat(c, path); + c += lenPath; + NS_tstrcat(c, kQuote); + c += lenQuote; + *c = NS_T('\0'); + c++; + return s; +} + +static void ensure_write_permissions(const NS_tchar *path) +{ +#ifdef _WIN32 + (void) _wchmod(path, _S_IREAD | _S_IWRITE); +#else + struct stat fs; + if (!stat(path, &fs) && !(fs.st_mode & S_IWUSR)) + { + (void)chmod(path, fs.st_mode | S_IWUSR); + } +#endif +} + +static int ensure_remove(const NS_tchar *path) +{ + ensure_write_permissions(path); + int rv = NS_tremove(path); + if (rv) + LOG(("ensure_remove: failed to remove file: " LOG_S ", rv: %d, err: %d", + path, rv, errno)); + return rv; +} + +// Remove the directory pointed to by path and all of its files and sub-directories. +static int ensure_remove_recursive(const NS_tchar *path, + bool continueEnumOnFailure = false) +{ + // We use lstat rather than stat here so that we can successfully remove + // symlinks. + struct NS_tstat_t sInfo; + int rv = NS_tlstat(path, &sInfo); + if (rv) + { + // This error is benign + return rv; + } + if (!S_ISDIR(sInfo.st_mode)) + { + return ensure_remove(path); + } + + NS_tDIR *dir; + NS_tdirent *entry; + + dir = NS_topendir(path); + if (!dir) + { + LOG(("ensure_remove_recursive: unable to open directory: " LOG_S + ", rv: %d, err: %d", path, rv, errno)); + return rv; + } + + while ((entry = NS_treaddir(dir)) != 0) + { + if (NS_tstrcmp(entry->d_name, NS_T(".")) && + NS_tstrcmp(entry->d_name, NS_T(".."))) + { + NS_tchar childPath[MAXPATHLEN]; + NS_tsnprintf(childPath, sizeof(childPath)/sizeof(childPath[0]), + NS_T("%s/%s"), path, entry->d_name); + rv = ensure_remove_recursive(childPath); + if (rv && !continueEnumOnFailure) + { + break; + } + } + } + + NS_tclosedir(dir); + + if (rv == OK) + { + ensure_write_permissions(path); + rv = NS_trmdir(path); + if (rv) + { + LOG(("ensure_remove_recursive: unable to remove directory: " LOG_S + ", rv: %d, err: %d", path, rv, errno)); + } + } + return rv; +} + +static bool is_read_only(const NS_tchar *flags) +{ + size_t length = NS_tstrlen(flags); + if (length == 0) + return false; + + // Make sure the string begins with "r" + if (flags[0] != NS_T('r')) + return false; + + // Look for "r+" or "r+b" + if (length > 1 && flags[1] == NS_T('+')) + return false; + + // Look for "rb+" + if (NS_tstrcmp(flags, NS_T("rb+")) == 0) + return false; + + return true; +} + +static FILE* ensure_open(const NS_tchar *path, const NS_tchar *flags, unsigned int options) +{ + ensure_write_permissions(path); + FILE* f = NS_tfopen(path, flags); + if (is_read_only(flags)) + { + // Don't attempt to modify the file permissions if the file is being opened + // in read-only mode. + return f; + } + if (NS_tchmod(path, options) != 0) + { + if (f != nullptr) + { + fclose(f); + } + return nullptr; + } + struct NS_tstat_t ss; + if (NS_tstat(path, &ss) != 0 || ss.st_mode != options) + { + if (f != nullptr) + { + fclose(f); + } + return nullptr; + } + return f; +} + +// Ensure that the directory containing this file exists. +static int ensure_parent_dir(const NS_tchar *path) +{ + int rv = OK; + + NS_tchar *slash = (NS_tchar *) NS_tstrrchr(path, NS_T('/')); + if (slash) + { + *slash = NS_T('\0'); + rv = ensure_parent_dir(path); + // Only attempt to create the directory if we're not at the root + if (rv == OK && *path) + { + rv = NS_tmkdir(path, 0755); + // If the directory already exists, then ignore the error. + if (rv < 0 && errno != EEXIST) + { + LOG(("ensure_parent_dir: failed to create directory: " LOG_S ", " \ + "err: %d", path, errno)); + rv = WRITE_ERROR; + } + else + { + rv = OK; + } + } + *slash = NS_T('/'); + } + return rv; +} + +#ifdef UNIX +static int ensure_copy_symlink(const NS_tchar *path, const NS_tchar *dest) +{ + // Copy symlinks by creating a new symlink to the same target + NS_tchar target[MAXPATHLEN + 1] = {NS_T('\0')}; + int rv = readlink(path, target, MAXPATHLEN); + if (rv == -1) + { + LOG(("ensure_copy_symlink: failed to read the link: " LOG_S ", err: %d", + path, errno)); + return READ_ERROR; + } + rv = symlink(target, dest); + if (rv == -1) + { + LOG(("ensure_copy_symlink: failed to create the new link: " LOG_S ", target: " LOG_S " err: %d", + dest, target, errno)); + return READ_ERROR; + } + return 0; +} +#endif + +// Copy the file named path onto a new file named dest. +static int ensure_copy(const NS_tchar *path, const NS_tchar *dest) +{ +#ifdef _WIN32 + // Fast path for Windows + bool result = CopyFileW(path, dest, false); + if (!result) + { + LOG(("ensure_copy: failed to copy the file " LOG_S " over to " LOG_S ", lasterr: %x", + path, dest, GetLastError())); + return WRITE_ERROR_FILE_COPY; + } + return OK; +#else + struct NS_tstat_t ss; + int rv = NS_tlstat(path, &ss); + if (rv) + { + LOG(("ensure_copy: failed to read file status info: " LOG_S ", err: %d", + path, errno)); + return READ_ERROR; + } + +#ifdef UNIX + if (S_ISLNK(ss.st_mode)) + { + return ensure_copy_symlink(path, dest); + } +#endif + + AutoFile infile(ensure_open(path, NS_T("rb"), ss.st_mode)); + if (!infile) + { + LOG(("ensure_copy: failed to open the file for reading: " LOG_S ", err: %d", + path, errno)); + return READ_ERROR; + } + AutoFile outfile(ensure_open(dest, NS_T("wb"), ss.st_mode)); + if (!outfile) + { + LOG(("ensure_copy: failed to open the file for writing: " LOG_S ", err: %d", + dest, errno)); + return WRITE_ERROR; + } + + // This block size was chosen pretty arbitrarily but seems like a reasonable + // compromise. For example, the optimal block size on a modern macOS machine + // is 100k */ + const int blockSize = 32 * 1024; + void* buffer = malloc(blockSize); + if (!buffer) + return UPDATER_MEM_ERROR; + + while (!feof(infile.get())) + { + size_t read = fread(buffer, 1, blockSize, infile); + if (ferror(infile.get())) + { + LOG(("ensure_copy: failed to read the file: " LOG_S ", err: %d", + path, errno)); + free(buffer); + return READ_ERROR; + } + + size_t written = 0; + + while (written < read) + { + size_t nCount = read - written; + size_t chunkWritten = fwrite(buffer, 1, nCount, outfile); + if (chunkWritten != nCount) + { + LOG(("ensure_copy: failed to write the file: " LOG_S ", err: %d", + dest, errno)); + free(buffer); + return WRITE_ERROR_FILE_COPY; + } + + written += chunkWritten; + } + } + + rv = NS_tchmod(dest, ss.st_mode); + + free(buffer); + return rv; +#endif +} + +template <unsigned N> +struct copy_recursive_skiplist +{ + NS_tchar paths[N][MAXPATHLEN]; + + void append(unsigned index, const NS_tchar *path, const NS_tchar *suffix) + { + NS_tsnprintf(paths[index], MAXPATHLEN, NS_T("%s/%s"), path, suffix); + } + + void append(unsigned index, const NS_tchar* path) + { + NS_tstrcpy(paths[index], path); + } + + bool find(const NS_tchar *path) + { + for (int i = 0; i < static_cast<int>(N); ++i) + { + if (!NS_tstricmp(paths[i], path)) + { + return true; + } + } + return false; + } +}; + +// Copy all of the files and subdirectories under path to a new directory named dest. +// The path names in the skiplist will be skipped and will not be copied. +template <unsigned N> +static int ensure_copy_recursive(const NS_tchar *path, const NS_tchar *dest, + copy_recursive_skiplist<N>& skiplist) +{ + struct NS_tstat_t sInfo; + int rv = NS_tlstat(path, &sInfo); + if (rv) + { + LOG(("ensure_copy_recursive: path doesn't exist: " LOG_S ", rv: %d, err: %d", + path, rv, errno)); + return READ_ERROR; + } + +#ifdef UNIX + if (S_ISLNK(sInfo.st_mode)) + { + return ensure_copy_symlink(path, dest); + } +#endif + + if (!S_ISDIR(sInfo.st_mode)) + { + return ensure_copy(path, dest); + } + + rv = NS_tmkdir(dest, sInfo.st_mode); + if (rv < 0 && errno != EEXIST) + { + LOG(("ensure_copy_recursive: could not create destination directory: " LOG_S ", rv: %d, err: %d", + path, rv, errno)); + return WRITE_ERROR; + } + + NS_tDIR *dir; + NS_tdirent *entry; + + dir = NS_topendir(path); + if (!dir) + { + LOG(("ensure_copy_recursive: path is not a directory: " LOG_S ", rv: %d, err: %d", + path, rv, errno)); + return READ_ERROR; + } + + while ((entry = NS_treaddir(dir)) != 0) + { + if (NS_tstrcmp(entry->d_name, NS_T(".")) && + NS_tstrcmp(entry->d_name, NS_T(".."))) + { + NS_tchar childPath[MAXPATHLEN]; + NS_tsnprintf(childPath, sizeof(childPath)/sizeof(childPath[0]), + NS_T("%s/%s"), path, entry->d_name); + if (skiplist.find(childPath)) + { + continue; + } + NS_tchar childPathDest[MAXPATHLEN]; + NS_tsnprintf(childPathDest, sizeof(childPathDest)/sizeof(childPathDest[0]), + NS_T("%s/%s"), dest, entry->d_name); + rv = ensure_copy_recursive(childPath, childPathDest, skiplist); + if (rv) + { + break; + } + } + } + NS_tclosedir(dir); + return rv; +} + +// Renames the specified file to the new file specified. If the destination file +// exists it is removed. +static int rename_file(const NS_tchar *spath, const NS_tchar *dpath, + bool allowDirs = false) +{ + int rv = ensure_parent_dir(dpath); + if (rv) + return rv; + + struct NS_tstat_t spathInfo; + rv = NS_tstat(spath, &spathInfo); + if (rv) + { + LOG(("rename_file: failed to read file status info: " LOG_S ", " \ + "err: %d", spath, errno)); + return READ_ERROR; + } + + if (!S_ISREG(spathInfo.st_mode)) + { + if (allowDirs && !S_ISDIR(spathInfo.st_mode)) + { + LOG(("rename_file: path present, but not a file: " LOG_S ", err: %d", + spath, errno)); + return RENAME_ERROR_EXPECTED_FILE; + } + else + { + LOG(("rename_file: proceeding to rename the directory")); + } + } + + if (!NS_taccess(dpath, F_OK)) + { + if (ensure_remove(dpath)) + { + LOG(("rename_file: destination file exists and could not be " \ + "removed: " LOG_S, dpath)); + return WRITE_ERROR_DELETE_FILE; + } + } + + if (NS_trename(spath, dpath) != 0) + { + LOG(("rename_file: failed to rename file - src: " LOG_S ", " \ + "dst:" LOG_S ", err: %d", spath, dpath, errno)); + return WRITE_ERROR; + } + + return OK; +} + +#ifdef _WIN32 +// Remove the directory pointed to by path and all of its files and +// sub-directories. If a file is in use move it to the tobedeleted directory +// and attempt to schedule removal of the file on reboot +static int remove_recursive_on_reboot(const NS_tchar *path, const NS_tchar *deleteDir) +{ + struct NS_tstat_t sInfo; + int rv = NS_tlstat(path, &sInfo); + if (rv) + { + // This error is benign + return rv; + } + + if (!S_ISDIR(sInfo.st_mode)) + { + NS_tchar tmpDeleteFile[MAXPATHLEN]; + GetTempFileNameW(deleteDir, L"rep", 0, tmpDeleteFile); + NS_tremove(tmpDeleteFile); + rv = rename_file(path, tmpDeleteFile, false); + if (MoveFileEx(rv ? path : tmpDeleteFile, nullptr, MOVEFILE_DELAY_UNTIL_REBOOT)) + { + LOG(("remove_recursive_on_reboot: file will be removed on OS reboot: " + LOG_S, rv ? path : tmpDeleteFile)); + } + else + { + LOG(("remove_recursive_on_reboot: failed to schedule OS reboot removal of " + "file: " LOG_S, rv ? path : tmpDeleteFile)); + } + return rv; + } + + NS_tDIR *dir; + NS_tdirent *entry; + + dir = NS_topendir(path); + if (!dir) + { + LOG(("remove_recursive_on_reboot: unable to open directory: " LOG_S + ", rv: %d, err: %d", + path, rv, errno)); + return rv; + } + + while ((entry = NS_treaddir(dir)) != 0) + { + if (NS_tstrcmp(entry->d_name, NS_T(".")) && + NS_tstrcmp(entry->d_name, NS_T(".."))) + { + NS_tchar childPath[MAXPATHLEN]; + NS_tsnprintf(childPath, sizeof(childPath)/sizeof(childPath[0]), + NS_T("%s/%s"), path, entry->d_name); + // There is no need to check the return value of this call since this + // function is only called after an update is successful and there is not + // much that can be done to recover if it isn't successful. There is also + // no need to log the value since it will have already been logged. + remove_recursive_on_reboot(childPath, deleteDir); + } + } + + NS_tclosedir(dir); + + if (rv == OK) + { + ensure_write_permissions(path); + rv = NS_trmdir(path); + if (rv) + { + LOG(("remove_recursive_on_reboot: unable to remove directory: " LOG_S + ", rv: %d, err: %d", path, rv, errno)); + } + } + return rv; +} +#endif + +//----------------------------------------------------------------------------- + +// Create a backup of the specified file by renaming it. +static int backup_create(const NS_tchar *path) +{ + NS_tchar backup[MAXPATHLEN]; + NS_tsnprintf(backup, sizeof(backup)/sizeof(backup[0]), + NS_T("%s") BACKUP_EXT, path); + + return rename_file(path, backup); +} + +// Rename the backup of the specified file that was created by renaming it back +// to the original file. +static int backup_restore(const NS_tchar *path, const NS_tchar *relPath) +{ + NS_tchar backup[MAXPATHLEN]; + NS_tsnprintf(backup, sizeof(backup)/sizeof(backup[0]), + NS_T("%s") BACKUP_EXT, path); + + NS_tchar relBackup[MAXPATHLEN]; + NS_tsnprintf(relBackup, sizeof(relBackup) / sizeof(relBackup[0]), + NS_T("%s") BACKUP_EXT, relPath); + + if (NS_taccess(backup, F_OK)) + { + LOG(("backup_restore: backup file doesn't exist: " LOG_S, relBackup)); + return OK; + } + + return rename_file(backup, path); +} + +// Discard the backup of the specified file that was created by renaming it. +static int backup_discard(const NS_tchar *path, const NS_tchar *relPath) +{ + NS_tchar backup[MAXPATHLEN]; + NS_tsnprintf(backup, sizeof(backup)/sizeof(backup[0]), + NS_T("%s") BACKUP_EXT, path); + + NS_tchar relBackup[MAXPATHLEN]; + NS_tsnprintf(relBackup, sizeof(relBackup) / sizeof(relBackup[0]), + NS_T("%s") BACKUP_EXT, relPath); + + // Nothing to discard + if (NS_taccess(backup, F_OK)) + { + return OK; + } + + int rv = ensure_remove(backup); +#if defined(_WIN32) + if (rv && !sStagedUpdate && !sReplaceRequest) + { + LOG(("backup_discard: unable to remove: " LOG_S, relBackup)); + NS_tchar path[MAXPATHLEN]; + GetTempFileNameW(gDeleteDirPath, L"moz", 0, path); + if (rename_file(backup, path)) + { + LOG(("backup_discard: failed to rename file:" LOG_S ", dst:" LOG_S, + relBackup, relPath)); + return WRITE_ERROR_DELETE_BACKUP; + } + // The MoveFileEx call to remove the file on OS reboot will fail if the + // process doesn't have write access to the HKEY_LOCAL_MACHINE registry key + // but this is ok since the installer / uninstaller will delete the + // directory containing the file along with its contents after an update is + // applied, on reinstall, and on uninstall. + if (MoveFileEx(path, nullptr, MOVEFILE_DELAY_UNTIL_REBOOT)) + { + LOG(("backup_discard: file renamed and will be removed on OS " \ + "reboot: " LOG_S, relPath)); + } + else + { + LOG(("backup_discard: failed to schedule OS reboot removal of " \ + "file: " LOG_S, relPath)); + } + } +#else + if (rv) + return WRITE_ERROR_DELETE_BACKUP; +#endif + + return OK; +} + +// Helper function for post-processing a temporary backup. +static void backup_finish(const NS_tchar *path, const NS_tchar *relPath, + int status) +{ + if (status == OK) + backup_discard(path, relPath); + else + backup_restore(path, relPath); +} + +//----------------------------------------------------------------------------- + +static int DoUpdate(ArchiveReader& ArchiveReader); + +class Action +{ +public: + Action() : mProgressCost(1), mNext(nullptr) { } + virtual ~Action() { } + + virtual int Parse(NS_tchar *line) = 0; + + // Do any preprocessing to ensure that the action can be performed. Execute + // will be called if this Action and all others return OK from this method. + virtual int Prepare() = 0; + + // Perform the operation. Return OK to indicate success. After all actions + // have been executed, Finish will be called. A requirement of Execute is + // that its operation be reversible from Finish. + virtual int Execute() = 0; + + // Finish is called after execution of all actions. If status is OK, then + // all actions were successfully executed. Otherwise, some action failed. + virtual void Finish(int status) = 0; + + int mProgressCost; +private: + Action* mNext; + + friend class ActionList; +}; + +class RemoveFile : public Action +{ +public: + RemoveFile() : mSkip(0) { } + + int Parse(NS_tchar *line); + int Prepare(); + int Execute(); + void Finish(int status); + +private: + std::unique_ptr<const NS_tchar[]> mFile; + std::unique_ptr<NS_tchar[]> mRelPath; + int mSkip; +}; + +int +RemoveFile::Parse(NS_tchar *line) +{ + // format "<deadfile>" + + NS_tchar* validPath = get_valid_path(&line); + if (!validPath) + return PARSE_ERROR; + + mRelPath.reset(new NS_tchar[MAXPATHLEN]); + NS_tstrcpy(mRelPath.get(), validPath); + + mFile.reset(new_absolute_path(validPath)); + if (!mFile) + { + return PARSE_ERROR; + } + + return OK; +} + +int +RemoveFile::Prepare() +{ + // Skip the file if it already doesn't exist. + int rv = NS_taccess(mFile.get(), F_OK); + if (rv) + { + mSkip = 1; + mProgressCost = 0; + return OK; + } + + LOG(("PREPARE REMOVEFILE " LOG_S, mRelPath.get())); + + // Make sure that we're actually a file... + struct NS_tstat_t fileInfo; + rv = NS_tstat(mFile.get(), &fileInfo); + if (rv) + { + LOG(("failed to read file status info: " LOG_S ", err: %d", mFile.get(), + errno)); + return READ_ERROR; + } + + if (!S_ISREG(fileInfo.st_mode)) + { + LOG(("path present, but not a file: " LOG_S, mFile.get())); + return DELETE_ERROR_EXPECTED_FILE; + } + + NS_tchar *slash = (NS_tchar *) NS_tstrrchr(mFile.get(), NS_T('/')); + if (slash) + { + *slash = NS_T('\0'); + rv = NS_taccess(mFile.get(), W_OK); + *slash = NS_T('/'); + } + else + { + rv = NS_taccess(NS_T("."), W_OK); + } + + if (rv) + { + LOG(("access failed: %d", errno)); + return WRITE_ERROR_FILE_ACCESS_DENIED; + } + + return OK; +} + +int +RemoveFile::Execute() +{ + if (mSkip) + return OK; + + LOG(("EXECUTE REMOVEFILE " LOG_S, mRelPath.get())); + + // The file is checked for existence here and in Prepare since it might have + // been removed by a separate instruction: bug 311099. + int rv = NS_taccess(mFile.get(), F_OK); + if (rv) + { + LOG(("file cannot be removed because it does not exist; skipping")); + mSkip = 1; + return OK; + } + + // Rename the old file. It will be removed in Finish. + rv = backup_create(mFile.get()); + if (rv) + { + LOG(("backup_create failed: %d", rv)); + return rv; + } + + return OK; +} + +void +RemoveFile::Finish(int status) +{ + if (mSkip) + return; + + LOG(("FINISH REMOVEFILE " LOG_S, mRelPath.get())); + + backup_finish(mFile.get(), mRelPath.get(), status); +} + +class RemoveDir : public Action +{ +public: + RemoveDir() : mSkip(0) { } + + virtual int Parse(NS_tchar *line); + virtual int Prepare(); // check that the source dir exists + virtual int Execute(); + virtual void Finish(int status); + +private: + std::unique_ptr<NS_tchar[]> mDir; + std::unique_ptr<NS_tchar[]> mRelPath; + int mSkip; +}; + +int +RemoveDir::Parse(NS_tchar *line) +{ + // format "<deaddir>/" + + NS_tchar* validPath = get_valid_path(&line, true); + if (!validPath) + return PARSE_ERROR; + + mRelPath.reset(new NS_tchar[MAXPATHLEN]); + NS_tstrcpy(mRelPath.get(), validPath); + + mDir.reset(new_absolute_path(validPath)); + if (!mDir) + { + return PARSE_ERROR; + } + + return OK; +} + +int +RemoveDir::Prepare() +{ + // We expect the directory to exist if we are to remove it. + int rv = NS_taccess(mDir.get(), F_OK); + if (rv) + { + mSkip = 1; + mProgressCost = 0; + return OK; + } + + LOG(("PREPARE REMOVEDIR " LOG_S "/", mRelPath.get())); + + // Make sure that we're actually a dir. + struct NS_tstat_t dirInfo; + rv = NS_tstat(mDir.get(), &dirInfo); + if (rv) + { + LOG(("failed to read directory status info: " LOG_S ", err: %d", mRelPath.get(), + errno)); + return READ_ERROR; + } + + if (!S_ISDIR(dirInfo.st_mode)) + { + LOG(("path present, but not a directory: " LOG_S, mRelPath.get())); + return DELETE_ERROR_EXPECTED_DIR; + } + + rv = NS_taccess(mDir.get(), W_OK); + if (rv) + { + LOG(("access failed: %d, %d", rv, errno)); + return WRITE_ERROR_DIR_ACCESS_DENIED; + } + + return OK; +} + +int +RemoveDir::Execute() +{ + if (mSkip) + return OK; + + LOG(("EXECUTE REMOVEDIR " LOG_S "/", mRelPath.get())); + + // The directory is checked for existence at every step since it might have + // been removed by a separate instruction: bug 311099. + int rv = NS_taccess(mDir.get(), F_OK); + if (rv) + { + LOG(("directory no longer exists; skipping")); + mSkip = 1; + } + + return OK; +} + +void +RemoveDir::Finish(int status) +{ + if (mSkip || status != OK) + return; + + LOG(("FINISH REMOVEDIR " LOG_S "/", mRelPath.get())); + + // The directory is checked for existence at every step since it might have + // been removed by a separate instruction: bug 311099. + int rv = NS_taccess(mDir.get(), F_OK); + if (rv) + { + LOG(("directory no longer exists; skipping")); + return; + } + + + if (status == OK) + { + if (NS_trmdir(mDir.get())) + { + LOG(("non-fatal error removing directory: " LOG_S "/, rv: %d, err: %d", + mRelPath.get(), rv, errno)); + } + } +} + +class AddFile : public Action +{ +public: + AddFile(ArchiveReader& ar) : mAdded(false), mArchiveReader(ar) { } + + virtual int Parse(NS_tchar *line); + virtual int Prepare(); + virtual int Execute(); + virtual void Finish(int status); + +private: + std::unique_ptr<NS_tchar[]> mFile; + std::unique_ptr<NS_tchar[]> mRelPath; + bool mAdded; + ArchiveReader& mArchiveReader; +}; + +int +AddFile::Parse(NS_tchar *line) +{ + // format "<newfile>" + + NS_tchar* validPath = get_valid_path(&line); + if (!validPath) + return PARSE_ERROR; + + mRelPath.reset(new NS_tchar[MAXPATHLEN]); + NS_tstrcpy(mRelPath.get(), validPath); + + mFile.reset(new_absolute_path(validPath)); + if (!mFile) + { + return PARSE_ERROR; + } + + return OK; +} + +int +AddFile::Prepare() +{ + LOG(("PREPARE ADD " LOG_S, mRelPath.get())); + + return OK; +} + +int +AddFile::Execute() +{ + LOG(("EXECUTE ADD " LOG_S, mRelPath.get())); + + int rv; + + // First make sure that we can actually get rid of any existing file. + rv = NS_taccess(mFile.get(), F_OK); + if (rv == 0) + { + rv = backup_create(mFile.get()); + if (rv) + return rv; + } + else + { + rv = ensure_parent_dir(mFile.get()); + if (rv) + return rv; + } + +#ifdef _WIN32 + char sourcefile[MAXPATHLEN]; + if (!WideCharToMultiByte(CP_UTF8, 0, mRelPath.get(), -1, sourcefile, + MAXPATHLEN, nullptr, nullptr)) + { + LOG(("error converting wchar to utf8: %d", GetLastError())); + return STRING_CONVERSION_ERROR; + } + + rv = mArchiveReader.ExtractFile(sourcefile, mFile.get()); +#else + rv = mArchiveReader.ExtractFile(mRelPath.get(), mFile.get()); +#endif + if (!rv) + { + mAdded = true; + } + return rv; +} + +void +AddFile::Finish(int status) +{ + LOG(("FINISH ADD " LOG_S, mRelPath.get())); + // When there is an update failure and a file has been added it is removed + // here since there might not be a backup to replace it. + if (status && mAdded) + NS_tremove(mFile.get()); + backup_finish(mFile.get(), mRelPath.get(), status); +} + +class PatchFile : public Action +{ +public: + PatchFile(ArchiveReader& ar) : mPatchFile(nullptr), mPatchIndex(-1), buf(nullptr), mArchiveReader(ar) { } + + virtual ~PatchFile(); + + virtual int Parse(NS_tchar *line); + virtual int Prepare(); // should check for patch file and for checksum here + virtual int Execute(); + virtual void Finish(int status); + +private: + int LoadSourceFile(FILE* ofile); + + static int sPatchIndex; + + const NS_tchar *mPatchFile; + std::unique_ptr<NS_tchar> mFile; + std::unique_ptr<NS_tchar> mFileRelPath; + int mPatchIndex; + MBSPatchHeader header; + unsigned char *buf; + NS_tchar spath[MAXPATHLEN]; + AutoFile mPatchStream; + ArchiveReader& mArchiveReader; +}; + +int PatchFile::sPatchIndex = 0; + +PatchFile::~PatchFile() +{ + // Make sure mPatchStream gets unlocked on Windows; the system will do that, + // but not until some indeterminate future time, and we want determinism. + // Normally this happens at the end of Execute, when we close the stream; + // this call is here in case Execute errors out. +#ifdef _WIN32 + if (mPatchStream) + { + UnlockFile((HANDLE)_get_osfhandle(fileno(mPatchStream)), (DWORD)0, (DWORD)0, (DWORD)-1, (DWORD)-1); + } +#endif + + // delete the temporary patch file + if (spath[0]) + NS_tremove(spath); + + if (buf) + free(buf); +} + +int +PatchFile::LoadSourceFile(FILE* ofile) +{ + struct stat os; + int rv = fstat(fileno((FILE *)ofile), &os); + if (rv) + { + LOG(("LoadSourceFile: unable to stat destination file: " LOG_S ", " \ + "err: %d", mFileRelPath.get(), errno)); + return READ_ERROR; + } + + if (uint32_t(os.st_size) != header.slen) + { + LOG(("LoadSourceFile: destination file size %d does not match expected size %d", + uint32_t(os.st_size), header.slen)); + return LOADSOURCE_ERROR_WRONG_SIZE; + } + + buf = (unsigned char *) malloc(header.slen); + if (!buf) + return UPDATER_MEM_ERROR; + + size_t r = header.slen; + unsigned char *rb = buf; + while (r) + { + const size_t count = std::min<size_t>(SSIZE_MAX, r); + size_t c = fread(rb, 1, count, ofile); + if (c != count) + { + LOG(("LoadSourceFile: error reading destination file: " LOG_S, + mFileRelPath.get())); + return READ_ERROR; + } + + r -= c; + rb += c; + } + + // Verify that the contents of the source file correspond to what we expect. + + unsigned int crc = crc32(buf, header.slen); + + if (crc != header.scrc32) + { + LOG(("LoadSourceFile: destination file crc %d does not match expected " \ + "crc %d", crc, header.scrc32)); + return CRC_ERROR; + } + + return OK; +} + +int +PatchFile::Parse(NS_tchar *line) +{ + // format "<patchfile>" "<filetopatch>" + + // Get the path to the patch file inside of the mar + mPatchFile = mstrtok(kQuote, &line); + if (!mPatchFile) + return PARSE_ERROR; + + // consume whitespace between args + NS_tchar *q = mstrtok(kQuote, &line); + if (!q) + return PARSE_ERROR; + + NS_tchar* validPath = get_valid_path(&line); + if (!validPath) + return PARSE_ERROR; + mFileRelPath.reset(new NS_tchar[MAXPATHLEN]); + NS_tstrcpy(mFileRelPath.get(), validPath); + + mFile.reset(new_absolute_path(validPath)); + if (!mFile) + { + return PARSE_ERROR; + } + + return OK; +} + +int +PatchFile::Prepare() +{ + LOG(("PREPARE PATCH " LOG_S, mFileRelPath.get())); + + // extract the patch to a temporary file + mPatchIndex = sPatchIndex++; + + int nWrittenBytes = NS_tsnprintf(spath, sizeof(spath)/sizeof(spath[0]), + NS_T("%s/updating/%d.patch"), gWorkingDirPath, mPatchIndex); + (void) nWrittenBytes; + + NS_tremove(spath); + + mPatchStream = NS_tfopen(spath, NS_T("wb+")); + if (!mPatchStream) + return WRITE_ERROR; + +#ifdef _WIN32 + // Lock the patch file, so it can't be messed with between + // when we're done creating it and when we go to apply it. + if (!LockFile((HANDLE)_get_osfhandle(fileno(mPatchStream)), (DWORD)0, (DWORD)0, (DWORD)-1, (DWORD)-1)) + { + LOG(("Couldn't lock patch file: %d", GetLastError())); + // TODO: moggi: fix the build problem with LOCK_ERROR_PATCH_FILE + return WRITE_ERROR; //return LOCK_ERROR_PATCH_FILE; + } + char sourcefile[MAXPATHLEN]; + if (!WideCharToMultiByte(CP_UTF8, 0, mPatchFile, -1, sourcefile, MAXPATHLEN, + nullptr, nullptr)) + { + LOG(("error converting wchar to utf8: %d", GetLastError())); + return STRING_CONVERSION_ERROR; + } + + int rv = mArchiveReader.ExtractFileToStream(sourcefile, mPatchStream); +#else + int rv = mArchiveReader.ExtractFileToStream(mPatchFile, mPatchStream); +#endif + + return rv; +} + +int +PatchFile::Execute() +{ + LOG(("EXECUTE PATCH " LOG_S, mFileRelPath.get())); + + fseek(mPatchStream, 0, SEEK_SET); + + int rv = MBS_ReadHeader(mPatchStream, &header); + if (rv) + return rv; + + FILE *origfile = nullptr; +#ifdef _WIN32 + if (NS_tstrcmp(mFileRelPath.get(), gCallbackRelPath) == 0) + { + // Read from the copy of the callback when patching since the callback can't + // be opened for reading to prevent the application from being launched. + origfile = NS_tfopen(gCallbackBackupPath, NS_T("rb")); + } + else + { + origfile = NS_tfopen(mFile.get(), NS_T("rb")); + } +#else + origfile = NS_tfopen(mFile.get(), NS_T("rb")); +#endif + + if (!origfile) + { + LOG(("unable to open destination file: " LOG_S ", err: %d", + mFileRelPath.get(), errno)); + return READ_ERROR; + } + + rv = LoadSourceFile(origfile); + fclose(origfile); + if (rv) + { + LOG(("LoadSourceFile failed")); + return rv; + } + + // Rename the destination file if it exists before proceeding so it can be + // used to restore the file to its original state if there is an error. + struct NS_tstat_t ss; + rv = NS_tstat(mFile.get(), &ss); + if (rv) + { + LOG(("failed to read file status info: " LOG_S ", err: %d", + mFileRelPath.get(), errno)); + return READ_ERROR; + } + + rv = backup_create(mFile.get()); + if (rv) + return rv; + +#if defined(HAVE_POSIX_FALLOCATE) + AutoFile ofile(ensure_open(mFile.get(), NS_T("wb+"), ss.st_mode)); + posix_fallocate(fileno((FILE *)ofile), 0, header.dlen); +#elif defined(_WIN32) + bool shouldTruncate = true; + // Creating the file, setting the size, and then closing the file handle + // lessens fragmentation more than any other method tested. Other methods that + // have been tested are: + // 1. _chsize / _chsize_s reduced fragmentation though not completely. + // 2. _get_osfhandle and then setting the size reduced fragmentation though + // not completely. There are also reports of _get_osfhandle failing on + // mingw. + HANDLE hfile = CreateFileW(mFile.get(), + GENERIC_WRITE, + 0, + nullptr, + CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL, + nullptr); + + if (hfile != INVALID_HANDLE_VALUE) + { + if (SetFilePointer(hfile, header.dlen, + nullptr, FILE_BEGIN) != INVALID_SET_FILE_POINTER && + SetEndOfFile(hfile) != 0) + { + shouldTruncate = false; + } + CloseHandle(hfile); + } + + AutoFile ofile(ensure_open(mFile.get(), shouldTruncate ? NS_T("wb+") : NS_T("rb+"), + ss.st_mode)); +#elif defined(MACOSX) + AutoFile ofile(ensure_open(mFile.get(), NS_T("wb+"), ss.st_mode)); + // Modified code from FileUtils.cpp + fstore_t store = {F_ALLOCATECONTIG, F_PEOFPOSMODE, 0, header.dlen}; + // Try to get a continuous chunk of disk space + rv = fcntl(fileno((FILE *)ofile), F_PREALLOCATE, &store); + if (rv == -1) + { + // OK, perhaps we are too fragmented, allocate non-continuous + store.fst_flags = F_ALLOCATEALL; + rv = fcntl(fileno((FILE *)ofile), F_PREALLOCATE, &store); + } + + if (rv != -1) + { + ftruncate(fileno((FILE *)ofile), header.dlen); + } +#else + AutoFile ofile(ensure_open(mFile.get(), NS_T("wb+"), ss.st_mode)); +#endif + + if (ofile == nullptr) + { + LOG(("unable to create new file: " LOG_S ", err: %d", mFileRelPath.get(), + errno)); + return WRITE_ERROR_OPEN_PATCH_FILE; + } + +#ifdef _WIN32 + if (!shouldTruncate) + { + fseek(ofile, 0, SEEK_SET); + } +#endif + + rv = MBS_ApplyPatch(&header, mPatchStream, buf, ofile); + + // Go ahead and do a bit of cleanup now to minimize runtime overhead. + // Make sure mPatchStream gets unlocked on Windows; the system will do that, + // but not until some indeterminate future time, and we want determinism. +#ifdef _WIN32 + UnlockFile((HANDLE)_get_osfhandle(fileno(mPatchStream)), (DWORD)0, (DWORD)0, (DWORD)-1, (DWORD)-1); +#endif + // Set mPatchStream to nullptr to make AutoFile close the file, + // so it can be deleted on Windows. + mPatchStream = nullptr; + NS_tremove(spath); + spath[0] = NS_T('\0'); + free(buf); + buf = nullptr; + return rv; +} + +void +PatchFile::Finish(int status) +{ + LOG(("FINISH PATCH " LOG_S, mFileRelPath.get())); + + backup_finish(mFile.get(), mFileRelPath.get(), status); +} + +class AddIfFile : public AddFile +{ +public: + AddIfFile(ArchiveReader& archiveReader); + + virtual int Parse(NS_tchar *line); + virtual int Prepare(); + virtual int Execute(); + virtual void Finish(int status); + +protected: + std::unique_ptr<NS_tchar[]> mTestFile; +}; + +AddIfFile::AddIfFile(ArchiveReader& archiveReader): + AddFile(archiveReader) +{ +} + +int +AddIfFile::Parse(NS_tchar *line) +{ + // format "<testfile>" "<newfile>" + + mTestFile.reset(new_absolute_path(get_valid_path(&line))); + if (!mTestFile) + return PARSE_ERROR; + + // consume whitespace between args + NS_tchar *q = mstrtok(kQuote, &line); + if (!q) + return PARSE_ERROR; + + return AddFile::Parse(line); +} + +int +AddIfFile::Prepare() +{ + // If the test file does not exist, then skip this action. + if (NS_taccess(mTestFile.get(), F_OK)) + { + mTestFile = nullptr; + return OK; + } + + return AddFile::Prepare(); +} + +int +AddIfFile::Execute() +{ + if (!mTestFile) + return OK; + + return AddFile::Execute(); +} + +void +AddIfFile::Finish(int status) +{ + if (!mTestFile) + return; + + AddFile::Finish(status); +} + +class AddIfNotFile : public AddFile +{ +public: + AddIfNotFile(ArchiveReader& archiveReader); + + virtual int Parse(NS_tchar *line); + virtual int Prepare(); + virtual int Execute(); + virtual void Finish(int status); + +protected: + std::unique_ptr<NS_tchar[]> mTestFile; +}; + +AddIfNotFile::AddIfNotFile(ArchiveReader& archiveReader): + AddFile(archiveReader) +{ +} + +int +AddIfNotFile::Parse(NS_tchar *line) +{ + // format "<testfile>" "<newfile>" + + mTestFile.reset(new_absolute_path(get_valid_path(&line))); + if (!mTestFile) + return PARSE_ERROR; + + // consume whitespace between args + NS_tchar *q = mstrtok(kQuote, &line); + if (!q) + return PARSE_ERROR; + + return AddFile::Parse(line); +} + +int +AddIfNotFile::Prepare() +{ + // If the test file exists, then skip this action. + if (!NS_taccess(mTestFile.get(), F_OK)) + { + mTestFile = NULL; + return OK; + } + + return AddFile::Prepare(); +} + +int +AddIfNotFile::Execute() +{ + if (!mTestFile) + return OK; + + return AddFile::Execute(); +} + +void +AddIfNotFile::Finish(int status) +{ + if (!mTestFile) + return; + + AddFile::Finish(status); +} + +class PatchIfFile : public PatchFile +{ +public: + PatchIfFile(ArchiveReader& archiveReader); + + virtual int Parse(NS_tchar *line); + virtual int Prepare(); // should check for patch file and for checksum here + virtual int Execute(); + virtual void Finish(int status); + +private: + std::unique_ptr<NS_tchar[]> mTestFile; +}; + +PatchIfFile::PatchIfFile(ArchiveReader& archiveReader): + PatchFile(archiveReader) +{ +} + +int +PatchIfFile::Parse(NS_tchar *line) +{ + // format "<testfile>" "<patchfile>" "<filetopatch>" + + mTestFile.reset(new_absolute_path(get_valid_path(&line))); + if (!mTestFile) + return PARSE_ERROR; + + // consume whitespace between args + NS_tchar *q = mstrtok(kQuote, &line); + if (!q) + return PARSE_ERROR; + + return PatchFile::Parse(line); +} + +int +PatchIfFile::Prepare() +{ + // If the test file does not exist, then skip this action. + if (NS_taccess(mTestFile.get(), F_OK)) + { + mTestFile = nullptr; + return OK; + } + + return PatchFile::Prepare(); +} + +int +PatchIfFile::Execute() +{ + if (!mTestFile) + return OK; + + return PatchFile::Execute(); +} + +void +PatchIfFile::Finish(int status) +{ + if (!mTestFile) + return; + + PatchFile::Finish(status); +} + +//----------------------------------------------------------------------------- + +#ifdef _WIN32 + +/** + * Launch the post update application (helper.exe). It takes in the path of the + * callback application to calculate the path of helper.exe. For service updates + * this is called from both the system account and the current user account. + * + * @param installationDir The path to the callback application binary. + * @param updateInfoDir The directory where update info is stored. + * @return true if there was no error starting the process. + */ +bool +LaunchWinPostProcess(const WCHAR *installationDir, + const WCHAR *updateInfoDir) +{ + WCHAR workingDirectory[MAX_PATH + 1] = { L'\0' }; + wcsncpy(workingDirectory, installationDir, MAX_PATH); + + // TODO: moggi: needs adaptation for LibreOffice + // Most likely we don't have the helper method yet. Check if we really need it. + + // Launch helper.exe to perform post processing (e.g. registry and log file + // modifications) for the update. + WCHAR inifile[MAX_PATH + 1] = { L'\0' }; + wcsncpy(inifile, installationDir, MAX_PATH); + if (!PathAppendSafe(inifile, L"updater.ini")) + { + return false; + } + + WCHAR exefile[MAX_PATH + 1]; + WCHAR exearg[MAX_PATH + 1]; + WCHAR exeasync[10]; + bool async = true; + if (!GetPrivateProfileStringW(L"PostUpdateWin", L"ExeRelPath", nullptr, + exefile, MAX_PATH + 1, inifile)) + { + return false; + } + + if (!GetPrivateProfileStringW(L"PostUpdateWin", L"ExeArg", nullptr, exearg, + MAX_PATH + 1, inifile)) + { + return false; + } + + if (!GetPrivateProfileStringW(L"PostUpdateWin", L"ExeAsync", L"TRUE", + exeasync, + sizeof(exeasync)/sizeof(exeasync[0]), + inifile)) + { + return false; + } + + // Verify that exeFile doesn't contain relative paths + if (wcsstr(exefile, L"..") != nullptr) + { + return false; + } + + WCHAR exefullpath[MAX_PATH + 1] = { L'\0' }; + wcsncpy(exefullpath, installationDir, MAX_PATH); + if (!PathAppendSafe(exefullpath, exefile)) + { + return false; + } + +#if !defined(TEST_UPDATER) && defined(MAINTENANCE_SERVICE) + if (sUsingService && + !DoesBinaryMatchAllowedCertificates(installationDir, exefullpath)) + { + return false; + } +#endif + + WCHAR dlogFile[MAX_PATH + 1]; + if (!PathGetSiblingFilePath(dlogFile, exefullpath, L"uninstall.update")) + { + return false; + } + + WCHAR slogFile[MAX_PATH + 1] = { L'\0' }; + wcsncpy(slogFile, updateInfoDir, MAX_PATH); + if (!PathAppendSafe(slogFile, L"update.log")) + { + return false; + } + + WCHAR dummyArg[14] = { L'\0' }; + wcsncpy(dummyArg, L"argv0ignored ", sizeof(dummyArg) / sizeof(dummyArg[0]) - 1); + + size_t len = wcslen(exearg) + wcslen(dummyArg); + WCHAR *cmdline = (WCHAR *) malloc((len + 1) * sizeof(WCHAR)); + if (!cmdline) + { + return false; + } + + wcsncpy(cmdline, dummyArg, len); + wcscat(cmdline, exearg); + + if (sUsingService || + !_wcsnicmp(exeasync, L"false", 6) || + !_wcsnicmp(exeasync, L"0", 2)) + { + async = false; + } + + // We want to launch the post update helper app to update the Windows + // registry even if there is a failure with removing the uninstall.update + // file or copying the update.log file. + CopyFileW(slogFile, dlogFile, false); + + STARTUPINFOW si = {sizeof(si), 0}; + si.lpDesktop = L""; + PROCESS_INFORMATION pi = {0}; + + bool ok = CreateProcessW(exefullpath, + cmdline, + nullptr, // no special security attributes + nullptr, // no special thread attributes + false, // don't inherit filehandles + 0, // No special process creation flags + nullptr, // inherit my environment + workingDirectory, + &si, + &pi); + free(cmdline); + if (ok) + { + if (!async) + { + WaitForSingleObject(pi.hProcess, INFINITE); + } + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + } + return ok; +} + +#endif + +static void +LaunchCallbackApp(const NS_tchar *workingDir, + int argc, + NS_tchar **argv, + bool usingService) +{ + putenv(const_cast<char*>("NO_EM_RESTART=")); + putenv(const_cast<char*>("MOZ_LAUNCHED_CHILD=1")); + + // Run from the specified working directory (see bug 312360). This is not + // necessary on Windows CE since the application that launches the updater + // passes the working directory as an --environ: command line argument. + if (NS_tchdir(workingDir) != 0) + { + LOG(("Warning: chdir failed")); + } + +#if defined(USE_EXECV) + (void) argc; + (void) usingService; // avoid warnings + execv(argv[0], argv); +#elif defined(MACOSX) + LaunchChild(argc, (const char**)argv); +#elif defined(_WIN32) + // Do not allow the callback to run when running an update through the + // service as session 0. The unelevated updater.exe will do the launching. + if (!usingService) + { + WinLaunchChild(argv[0], argc, argv, nullptr); + } +#else +# warning "Need implementation of LaunchCallbackApp" +#endif +} + +static bool +WriteStatusFile(const char* aStatus) +{ + NS_tchar filename[MAXPATHLEN] = {NS_T('\0')}; +#if defined(_WIN32) + // The temp file is not removed on failure since there is client code that + // will remove it. + GetTempFileNameW(gPatchDirPath, L"sta", 0, filename); +#else + NS_tsnprintf(filename, sizeof(filename)/sizeof(filename[0]), + NS_T("%s/update.status"), gPatchDirPath); +#endif + + // Make sure that the directory for the update status file exists + if (ensure_parent_dir(filename)) + return false; + + // This is scoped to make the AutoFile close the file so it is possible to + // move the temp file to the update.status file on Windows. + { + AutoFile file(NS_tfopen(filename, NS_T("wb+"))); + if (file == nullptr) + { + return false; + } + + if (fwrite(aStatus, strlen(aStatus), 1, file) != 1) + { + return false; + } + } + +#if defined(_WIN32) + NS_tchar dstfilename[MAXPATHLEN] = {NS_T('\0')}; + NS_tsnprintf(dstfilename, sizeof(dstfilename)/sizeof(dstfilename[0]), + NS_T("%s\\update.status"), gPatchDirPath); + if (MoveFileExW(filename, dstfilename, MOVEFILE_REPLACE_EXISTING) == 0) + { + return false; + } +#endif + + return true; +} + +static void +WriteStatusFile(int status) +{ + const char *text; + + char buf[32]; + if (status == OK) + { + if (sStagedUpdate) + { + text = "applied\n"; + } + else + { + text = "succeeded\n"; + } + } + else + { + snprintf(buf, sizeof(buf)/sizeof(buf[0]), "failed: %d\n", status); + text = buf; + } + + WriteStatusFile(text); +} + +#ifdef MAINTENANCE_SERVICE +/* + * Read the update.status file and sets isPendingService to true if + * the status is set to pending-service. + * + * @param isPendingService Out parameter for specifying if the status + * is set to pending-service or not. + * @return true if the information was retrieved and it is pending + * or pending-service. + */ +static bool +IsUpdateStatusPendingService() +{ + NS_tchar filename[MAXPATHLEN]; + NS_tsnprintf(filename, sizeof(filename)/sizeof(filename[0]), + NS_T("%s/update.status"), gPatchDirPath); + + AutoFile file(NS_tfopen(filename, NS_T("rb"))); + if (file == nullptr) + return false; + + char buf[32] = { 0 }; + fread(buf, sizeof(buf), 1, file); + + const char kPendingService[] = "pending-service"; + const char kAppliedService[] = "applied-service"; + + return (strncmp(buf, kPendingService, + sizeof(kPendingService) - 1) == 0) || + (strncmp(buf, kAppliedService, + sizeof(kAppliedService) - 1) == 0); +} +#endif + +#ifdef _WIN32 +/* + * Read the update.status file and sets isSuccess to true if + * the status is set to succeeded. + * + * @param isSucceeded Out parameter for specifying if the status + * is set to succeeded or not. + * @return true if the information was retrieved and it is succeeded. + */ +static bool +IsUpdateStatusSucceeded(bool &isSucceeded) +{ + isSucceeded = false; + NS_tchar filename[MAXPATHLEN]; + NS_tsnprintf(filename, sizeof(filename)/sizeof(filename[0]), + NS_T("%s/update.status"), gPatchDirPath); + + AutoFile file(NS_tfopen(filename, NS_T("rb"))); + if (file == nullptr) + return false; + + char buf[32] = { 0 }; + fread(buf, sizeof(buf), 1, file); + + const char kSucceeded[] = "succeeded"; + isSucceeded = strncmp(buf, kSucceeded, + sizeof(kSucceeded) - 1) == 0; + return true; +} +#endif + +/* + * Copy the entire contents of the application installation directory to the + * destination directory for the update process. + * + * @return 0 if successful, an error code otherwise. + */ +static int +CopyInstallDirToDestDir() +{ + // These files should not be copied over to the updated app +#ifdef _WIN32 +#define SKIPLIST_COUNT 4 +#elif defined(MACOSX) +#define SKIPLIST_COUNT 1 +#else +#define SKIPLIST_COUNT 3 +#endif + copy_recursive_skiplist<SKIPLIST_COUNT> skiplist; + + std::unique_ptr<NS_tchar[]> pUserProfile(new NS_tchar[MAXPATHLEN]); + NS_tstrcpy(pUserProfile.get(), gPatchDirPath); + NS_tchar *slash = (NS_tchar *) NS_tstrrchr(pUserProfile.get(), NS_T('/')); + if (slash) + *slash = NS_T('\0'); + + LOG(("ignore user profile directory during copy: " LOG_S, pUserProfile.get())); + + skiplist.append(0, pUserProfile.get()); +#ifndef MACOSX + skiplist.append(1, gInstallDirPath, NS_T("updated")); + skiplist.append(2, gInstallDirPath, NS_T("updates/0")); +#ifdef _WIN32 + skiplist.append(4, gInstallDirPath, NS_T("updated.update_in_progress.lock")); +#endif +#endif + + return ensure_copy_recursive(gInstallDirPath, gWorkingDirPath, skiplist); +} + +/* + * Replace the application installation directory with the destination + * directory in order to finish a staged update task + * + * @return 0 if successful, an error code otherwise. + */ +static int +ProcessReplaceRequest() +{ + // TODO: moggi: handle the user profile in the installation dir also + // during the replacement request + // The replacement algorithm is like this: + // 1. Move destDir to tmpDir. In case of failure, abort. + // 2. Move newDir to destDir. In case of failure, revert step 1 and abort. + // 3. Delete tmpDir (or defer it to the next reboot). + +#ifdef MACOSX + NS_tchar destDir[MAXPATHLEN]; + NS_tsnprintf(destDir, sizeof(destDir)/sizeof(destDir[0]), + NS_T("%s/Contents"), gInstallDirPath); +#elif defined(_WIN32) + // Windows preserves the case of the file/directory names. We use the + // GetLongPathName API in order to get the correct case for the directory + // name, so that if the user has used a different case when launching the + // application, the installation directory's name does not change. + NS_tchar destDir[MAXPATHLEN]; + if (!GetLongPathNameW(gInstallDirPath, destDir, + sizeof(destDir)/sizeof(destDir[0]))) + { + return NO_INSTALLDIR_ERROR; + } +#else + NS_tchar* destDir = gInstallDirPath; +#endif + + NS_tchar tmpDir[MAXPATHLEN]; + NS_tsnprintf(tmpDir, sizeof(tmpDir)/sizeof(tmpDir[0]), + NS_T("%s.bak"), destDir); + + // First try to remove the possibly existing temp directory, because if this + // directory exists, we will fail to rename destDir. + // No need to error check here because if this fails, we will fail in the + // next step anyways. + ensure_remove_recursive(tmpDir); + + LOG(("Begin moving destDir (" LOG_S ") to tmpDir (" LOG_S ")", + destDir, tmpDir)); + LogFlush(); + int rv = rename_file(destDir, tmpDir, true); +#ifdef _WIN32 + // On Windows, if Firefox is launched using the shortcut, it will hold a handle + // to its installation directory open, which might not get released in time. + // Therefore we wait a little bit here to see if the handle is released. + // If it's not released, we just fail to perform the replace request. + const int max_retries = 10; + int retries = 0; + while (rv == WRITE_ERROR && (retries++ < max_retries)) + { + LOG(("PerformReplaceRequest: destDir rename attempt %d failed. " \ + "File: " LOG_S ". Last error: %d, err: %d", retries, + destDir, GetLastError(), rv)); + + Sleep(100); + + rv = rename_file(destDir, tmpDir, true); + } +#endif + if (rv) + { + // The status file will have 'pending' written to it so there is no value in + // returning an error specific for this failure. + LOG(("Moving destDir to tmpDir failed, err: %d", rv)); + return rv; + } + + NS_tchar newDir[MAXPATHLEN]; + if (is_userprofile_in_instdir()) + { + LOG(("user profile in instdir")); + NS_tstrcpy(newDir, tmpDir); + NS_tstrcat(newDir, gWorkingDirPath + NS_tstrlen(gInstallDirPath)); + LOG((LOG_S, newDir)); + } + else + { + NS_tsnprintf(newDir, sizeof(newDir)/sizeof(newDir[0]), + NS_T("%s"), + gWorkingDirPath); + } + + LOG(("Begin moving newDir (" LOG_S ") to destDir (" LOG_S ")", + newDir, destDir)); + rv = rename_file(newDir, destDir, true); +#ifdef MACOSX + if (rv) + { + LOG(("Moving failed. Begin copying newDir (" LOG_S ") to destDir (" LOG_S ")", + newDir, destDir)); + copy_recursive_skiplist<0> skiplist; + rv = ensure_copy_recursive(newDir, destDir, skiplist); + } +#endif + if (rv) + { + LOG(("Moving newDir to destDir failed, err: %d", rv)); + LOG(("Now, try to move tmpDir back to destDir")); + ensure_remove_recursive(destDir); + int rv2 = rename_file(tmpDir, destDir, true); + if (rv2) + { + LOG(("Moving tmpDir back to destDir failed, err: %d", rv2)); + } + // The status file will be have 'pending' written to it so there is no value + // in returning an error specific for this failure. + return rv; + } + + if (is_userprofile_in_instdir()) + { + // 1.) calculate path of the user profile in the backup directory + // 2.) move the user profile from the backup to the install directory + NS_tchar backup_user_profile[MAXPATHLEN]; + NS_tchar userprofile[MAXPATHLEN]; + + NS_tstrcpy(userprofile, gPatchDirPath); + NS_tchar* slash = (NS_tchar *) NS_tstrrchr(userprofile, NS_T('/')); + if (slash) + *slash = NS_T('\0'); + NS_tstrcpy(backup_user_profile, tmpDir); + size_t installdir_len = NS_tstrlen(destDir); + + NS_tstrcat(backup_user_profile, userprofile + installdir_len); + LOG(("copy user profile back from " LOG_S " to " LOG_S, backup_user_profile, userprofile)); + int rv2 = rename_file(backup_user_profile, userprofile); + if (rv2) + { + LOG(("failed to copy user profile back")); + } + if (slash) + *slash = NS_T('/'); + } + +#if !defined(_WIN32) && !defined(MACOSX) + // Platforms that have their updates directory in the installation directory + // need to have the last-update.log and backup-update.log files moved from the + // old installation directory to the new installation directory. + NS_tchar tmpLog[MAXPATHLEN]; + int nWrittenBytes = NS_tsnprintf(tmpLog, sizeof(tmpLog)/sizeof(tmpLog[0]), + NS_T("%s/updates/last-update.log"), tmpDir); + (void) nWrittenBytes; + if (!NS_taccess(tmpLog, F_OK)) + { + NS_tchar destLog[MAXPATHLEN]; + NS_tsnprintf(destLog, sizeof(destLog)/sizeof(destLog[0]), + NS_T("%s/updates/last-update.log"), destDir); + NS_tremove(destLog); + NS_trename(tmpLog, destLog); + } +#endif + + LOG(("Now, remove the tmpDir")); + rv = ensure_remove_recursive(tmpDir, true); + if (rv) + { + LOG(("Removing tmpDir failed, err: %d", rv)); +#ifdef _WIN32 + NS_tchar deleteDir[MAXPATHLEN]; + NS_tsnprintf(deleteDir, sizeof(deleteDir)/sizeof(deleteDir[0]), + NS_T("%s\\%s"), destDir, DELETE_DIR); + // Attempt to remove the tobedeleted directory and then recreate it if it + // was successfully removed. + _wrmdir(deleteDir); + if (NS_taccess(deleteDir, F_OK)) + { + NS_tmkdir(deleteDir, 0755); + } + remove_recursive_on_reboot(tmpDir, deleteDir); +#endif + } + +#ifdef MACOSX + // On macOS, we need to remove the staging directory after its Contents + // directory has been moved. + NS_tchar updatedAppDir[MAXPATHLEN]; + NS_tsnprintf(updatedAppDir, sizeof(updatedAppDir)/sizeof(updatedAppDir[0]), + NS_T("%s/Updated.app"), gPatchDirPath); + ensure_remove_recursive(updatedAppDir); +#endif + + gSucceeded = true; + + return 0; +} + +#ifdef _WIN32 +static void +WaitForServiceFinishThread(void* /*param*/) +{ + // We wait at most 10 minutes, we already waited 5 seconds previously + // before deciding to show this UI. + WaitForServiceStop(SVC_NAME, 595); + QuitProgressUI(); +} +#endif + +#ifdef VERIFY_MAR_SIGNATURE +/** + * This function reads in the ACCEPTED_MAR_CHANNEL_IDS from update-settings.ini + * + * @param path The path to the ini file that is to be read + * @param results A pointer to the location to store the read strings + * @return OK on success + */ +static int +ReadMARChannelIDs(const NS_tchar *path, MARChannelStringTable *results) +{ + // TODO: moggi: needs adaptation for LibreOffice + // Check where this function gets its parameters from + const unsigned int kNumStrings = 1; + const char *kUpdaterKeys = "ACCEPTED_MAR_CHANNEL_IDS\0"; + char updater_strings[kNumStrings][MAX_TEXT_LEN]; + + int result = ReadStrings(path, kUpdaterKeys, kNumStrings, + updater_strings, "Settings"); + + strncpy(results->MARChannelID, updater_strings[0], MAX_TEXT_LEN - 1); + results->MARChannelID[MAX_TEXT_LEN - 1] = 0; + + return result; +} +#endif + +static int +GetUpdateFileNames(std::vector<tstring>& fileNames) +{ + NS_tchar fileName[MAXPATHLEN]; + NS_tsnprintf(fileName, MAXPATHLEN, + NS_T("%s/update.mar"), gPatchDirPath); + fileNames.push_back(fileName); + + // add the language packs + NS_tDIR* dir = NS_topendir(gPatchDirPath); + if (!dir) + { + LOG(("Could not open directory " LOG_S, gPatchDirPath)); + return READ_ERROR; + } + + NS_tdirent* entry; + while ((entry = NS_treaddir(dir)) != nullptr) + { + if (NS_tstrcmp(entry->d_name, NS_T(".")) && + NS_tstrcmp(entry->d_name, NS_T("..")) && + NS_tstrcmp(entry->d_name, NS_T("update.mar"))) + { + if (NS_tstrncmp(entry->d_name, NS_T("update"), 6) == 0) + { + NS_tchar *dot = NS_tstrrchr(entry->d_name, NS_T('.')); + if (dot && !NS_tstrcmp(dot, NS_T(".mar"))) + { + NS_tchar updatePath[MAXPATHLEN]; + NS_tsnprintf(updatePath, sizeof(updatePath)/sizeof(updatePath[0]), + NS_T("%s/%s"), gPatchDirPath, entry->d_name); + + LOG (("Found language update file: " LOG_S, updatePath)); + fileNames.push_back(updatePath); + } + } + } + } + return OK; +} + +static int +CheckSignature(ArchiveReader& archiveReader) +{ +#ifdef VERIFY_MAR_SIGNATURE +#ifdef _WIN32 + HKEY baseKey = nullptr; + wchar_t valueName[] = L"Image Path"; + wchar_t rasenh[] = L"rsaenh.dll"; + bool reset = false; + if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, + L"SOFTWARE\\Microsoft\\Cryptography\\Defaults\\Provider\\Microsoft Enhanced Cryptographic Provider v1.0", + 0, KEY_READ | KEY_WRITE, + &baseKey) == ERROR_SUCCESS) + { + wchar_t path[MAX_PATH + 1]; + DWORD size = sizeof(path); + DWORD type; + if (RegQueryValueExW(baseKey, valueName, 0, &type, + (LPBYTE)path, &size) == ERROR_SUCCESS) + { + if (type == REG_SZ && wcscmp(path, rasenh) == 0) + { + wchar_t rasenhFullPath[] = L"%SystemRoot%\\System32\\rsaenh.dll"; + if (RegSetValueExW(baseKey, valueName, 0, REG_SZ, + (const BYTE*)rasenhFullPath, + sizeof(rasenhFullPath)) == ERROR_SUCCESS) + { + reset = true; + } + } + } + } +#endif + int rv = archiveReader.VerifySignature(); +#ifdef _WIN32 + if (baseKey) + { + if (reset) + { + RegSetValueExW(baseKey, valueName, 0, REG_SZ, + (const BYTE*)rasenh, + sizeof(rasenh)); + } + RegCloseKey(baseKey); + } +#endif + + + if (rv == OK) + { + if (rv == OK) + { + NS_tchar updateSettingsPath[MAX_TEXT_LEN]; + + // TODO: moggi: needs adaptation for LibreOffice + // These paths need to be adapted for us. + int nWrittenBytes = NS_tsnprintf(updateSettingsPath, + sizeof(updateSettingsPath) / sizeof(updateSettingsPath[0]), +#ifdef MACOSX + NS_T("%s/Contents/Resources/update-settings.ini"), +#else + NS_T("%s/update-settings.ini"), +#endif + gWorkingDirPath); + (void) nWrittenBytes; + MARChannelStringTable MARStrings; + if (ReadMARChannelIDs(updateSettingsPath, &MARStrings) != OK) + { + // If we can't read from update-settings.ini then we shouldn't impose + // a MAR restriction. Some installations won't even include this file. + MARStrings.MARChannelID[0] = '\0'; + } + + rv = archiveReader.VerifyProductInformation(MARStrings.MARChannelID, + LIBO_VERSION_DOTTED); + } + } +#endif + + return rv; +} + +static void +UpdateThreadFunc(void * /*param*/) +{ + // open ZIP archive and process... + int rv = OK; + if (sReplaceRequest) + { + rv = ProcessReplaceRequest(); + } + else + { + std::vector<tstring> fileNames; + GetUpdateFileNames(fileNames); + + for (auto& fileName: fileNames) + { + ArchiveReader archiveReader; + rv = archiveReader.Open(fileName.c_str()); + if (rv != OK) + { + LOG(("Could not open " LOG_S, fileName.c_str())); + break; + } + + rv = CheckSignature(archiveReader); + if (rv != OK) + { + LOG(("Could not verify the signature of " LOG_S, fileName.c_str())); + break; + } + } + + if (rv == OK && sStagedUpdate) + { + rv = CopyInstallDirToDestDir(); + } + + if (rv == OK) + { + for (auto& fileName: fileNames) + { + ArchiveReader archiveReader; + archiveReader.Open(fileName.c_str()); + rv = DoUpdate(archiveReader); + } + NS_tchar updatingDir[MAXPATHLEN]; + int nWrittenBytes = NS_tsnprintf(updatingDir, sizeof(updatingDir)/sizeof(updatingDir[0]), + NS_T("%s/updating"), gWorkingDirPath); + (void) nWrittenBytes; + ensure_remove_recursive(updatingDir); + } + } + + if (rv && (sReplaceRequest || sStagedUpdate)) + { +#ifdef _WIN32 + // On Windows, the current working directory of the process should be changed + // so that it's not locked. + if (sStagedUpdate) + { + NS_tchar sysDir[MAX_PATH + 1] = { L'\0' }; + if (GetSystemDirectoryW(sysDir, MAX_PATH + 1)) + { + NS_tchdir(sysDir); + } + } +#endif + ensure_remove_recursive(gWorkingDirPath); + // When attempting to replace the application, we should fall back + // to non-staged updates in case of a failure. We do this by + // setting the status to pending, exiting the updater, and + // launching the callback application. The callback application's + // startup path will see the pending status, and will start the + // updater application again in order to apply the update without + // staging. + if (sReplaceRequest) + { + WriteStatusFile(sUsingService ? "pending-service" : "pending"); + } + else + { + WriteStatusFile(rv); + } +#ifdef TEST_UPDATER + // Some tests need to use --test-process-updates again. + putenv(const_cast<char*>("MOZ_TEST_PROCESS_UPDATES=")); +#endif + } + else + { + if (rv) + { + LOG(("failed: %d", rv)); + } + else + { +#ifdef MACOSX + // If the update was successful we need to update the timestamp on the + // top-level macOS bundle directory so that macOS's Launch Services + // picks up any major changes when the bundle is updated. + if (!sStagedUpdate && utimes(gInstallDirPath, nullptr) != 0) + { + LOG(("Couldn't set access/modification time on application bundle.")); + } +#endif + + LOG(("succeeded")); + } + WriteStatusFile(rv); + } + + LOG(("calling QuitProgressUI")); + QuitProgressUI(); +} + +#ifdef MACOSX +static void +ServeElevatedUpdateThreadFunc(void* param) +{ + UpdateServerThreadArgs* threadArgs = (UpdateServerThreadArgs*)param; + gSucceeded = ServeElevatedUpdate(threadArgs->argc, threadArgs->argv); + if (!gSucceeded) + { + WriteStatusFile(ELEVATION_CANCELED); + } + QuitProgressUI(); +} + +void freeArguments(int argc, char** argv) +{ + for (int i = 0; i < argc; i++) + { + free(argv[i]); + } + free(argv); +} +#endif + +int LaunchCallbackAndPostProcessApps(int argc, NS_tchar** argv, + int callbackIndex +#ifdef _WIN32 + , const WCHAR* elevatedLockFilePath + , HANDLE updateLockFileHandle +#elif defined(MACOSX) + , bool isElevated +#endif + ) +{ + if (argc > callbackIndex) + { +#if defined(_WIN32) + if (gSucceeded) + { + if (!LaunchWinPostProcess(gInstallDirPath, gPatchDirPath)) + { + fprintf(stderr, "The post update process was not launched"); + } + + // The service update will only be executed if it is already installed. + // For first time installs of the service, the install will happen from + // the PostUpdate process. We do the service update process here + // because it's possible we are updating with updater.exe without the + // service if the service failed to apply the update. We want to update + // the service to a newer version in that case. If we are not running + // through the service, then USING_SERVICE will not exist. + if (!sUsingService) + { + StartServiceUpdate(gInstallDirPath); + } + } + EXIT_WHEN_ELEVATED(elevatedLockFilePath, updateLockFileHandle, 0); +#elif defined(MACOSX) + if (!isElevated) + { + if (gSucceeded) + { + LaunchMacPostProcess(gInstallDirPath); + } +#endif + + LaunchCallbackApp(argv[5], + argc - callbackIndex, + argv + callbackIndex, + sUsingService); +#ifdef XP_MACOSX + } // if (!isElevated) +#endif /* XP_MACOSX */ +} +return 0; +} + +int NS_main(int argc, NS_tchar **argv) +{ + // The callback is the remaining arguments starting at callbackIndex. + // The argument specified by callbackIndex is the callback executable and the + // argument prior to callbackIndex is the working directory. + const int callbackIndex = 6; + +#ifdef MACOSX + // TODO: moggi: needs adaptation for LibreOffice + bool isElevated = + strstr(argv[0], "/Library/PrivilegedHelperTools/org.mozilla.updater") != 0; + if (isElevated) + { + if (!ObtainUpdaterArguments(&argc, &argv)) + { + // Won't actually get here because ObtainUpdaterArguments will terminate + // the current process on failure. + return 1; + } + } +#endif + +#if defined(VERIFY_MAR_SIGNATURE) && !defined(_WIN32) && !defined(MACOSX) + // On Windows and Mac we rely on native APIs to do verifications so we don't + // need to initialize NSS at all there. + // Otherwise, minimize the amount of NSS we depend on by avoiding all the NSS + // databases. + if (NSS_NoDB_Init(NULL) != SECSuccess) + { + PRErrorCode error = PR_GetError(); + fprintf(stderr, "Could not initialize NSS: %s (%d)", + PR_ErrorToName(error), (int) error); + _exit(1); + } +#endif + +#ifdef MACOSX + if (!isElevated) + { +#endif + InitProgressUI(&argc, &argv); +#ifdef MACOSX + } +#endif + + // To process an update the updater command line must at a minimum have the + // directory path containing the updater.mar file to process as the first + // argument, the install directory as the second argument, and the directory + // to apply the update to as the third argument. When the updater is launched + // by another process the PID of the parent process should be provided in the + // optional fourth argument and the updater will wait on the parent process to + // exit if the value is non-zero and the process is present. This is necessary + // due to not being able to update files that are in use on Windows. The + // optional fifth argument is the callback's working directory and the + // optional sixth argument is the callback path. The callback is the + // application to launch after updating and it will be launched when these + // arguments are provided whether the update was successful or not. All + // remaining arguments are optional and are passed to the callback when it is + // launched. + if (argc < 4) + { + fprintf(stderr, "Usage: updater patch-dir install-dir apply-to-dir [wait-pid [callback-working-dir callback-path args...]]\n"); +#ifdef MACOSX + if (isElevated) + { + freeArguments(argc, argv); + CleanupElevatedMacUpdate(true); + } +#endif + return 1; + } + + // The directory containing the update information. + gPatchDirPath = argv[1]; + + // The directory we're going to update to. + // We copy this string because we need to remove trailing slashes. The C++ + // standard says that it's always safe to write to strings pointed to by argv + // elements, but I don't necessarily believe it. + NS_tstrncpy(gInstallDirPath, argv[2], MAXPATHLEN); + gInstallDirPath[MAXPATHLEN - 1] = NS_T('\0'); + NS_tchar *slash = NS_tstrrchr(gInstallDirPath, NS_SLASH); + if (slash && !slash[1]) + { + *slash = NS_T('\0'); + } + +#ifdef _WIN32 + bool useService = false; + bool testOnlyFallbackKeyExists = false; + bool noServiceFallback = false; + + // We never want the service to be used unless we build with + // the maintenance service. +#ifdef MAINTENANCE_SERVICE + useService = IsUpdateStatusPendingService(); + // Our tests run with a different apply directory for each test. + // We use this registry key on our test slaves to store the + // allowed name/issuers. + testOnlyFallbackKeyExists = DoesFallbackKeyExist(); +#endif + + // Remove everything except close window from the context menu + { + // TODO: moggi: needs adaptation for LibreOffice + HKEY hkApp = nullptr; + RegCreateKeyExW(HKEY_CURRENT_USER, L"Software\\Classes\\Applications", + 0, nullptr, REG_OPTION_NON_VOLATILE, KEY_SET_VALUE, nullptr, + &hkApp, nullptr); + RegCloseKey(hkApp); + if (RegCreateKeyExW(HKEY_CURRENT_USER, + L"Software\\Classes\\Applications\\updater.exe", + 0, nullptr, REG_OPTION_VOLATILE, KEY_SET_VALUE, nullptr, + &hkApp, nullptr) == ERROR_SUCCESS) + { + RegSetValueExW(hkApp, L"IsHostApp", 0, REG_NONE, 0, 0); + RegSetValueExW(hkApp, L"NoOpenWith", 0, REG_NONE, 0, 0); + RegSetValueExW(hkApp, L"NoStartPage", 0, REG_NONE, 0, 0); + RegCloseKey(hkApp); + } + } +#endif + + // If there is a PID specified and it is not '0' then wait for the process to exit. +#ifdef _WIN32 + __int64 pid = 0; +#else + int pid = 0; +#endif + if (argc > 4) + { +#ifdef _WIN32 + pid = _wtoi64(argv[4]); +#else + pid = atoi(argv[4]); +#endif + if (pid == -1) + { + // This is a signal from the parent process that the updater should stage + // the update. + sStagedUpdate = true; + } + else if (NS_tstrstr(argv[4], NS_T("/replace"))) + { + // We're processing a request to replace the application with a staged + // update. + sReplaceRequest = true; + } + } + + // The directory we're going to update to. + // We copy this string because we need to remove trailing slashes. The C++ + // standard says that it's always safe to write to strings pointed to by argv + // elements, but I don't necessarily believe it. + NS_tstrncpy(gWorkingDirPath, argv[3], MAXPATHLEN); + gWorkingDirPath[MAXPATHLEN - 1] = NS_T('\0'); + slash = NS_tstrrchr(gWorkingDirPath, NS_SLASH); + if (slash && !slash[1]) + { + *slash = NS_T('\0'); + } + +#ifdef MACOSX + if (!isElevated && !IsRecursivelyWritable(argv[2])) + { + // If the app directory isn't recursively writeable, an elevated update is + // required. + UpdateServerThreadArgs threadArgs; + threadArgs.argc = argc; + threadArgs.argv = const_cast<const NS_tchar**>(argv); + + Thread t1; + if (t1.Run(ServeElevatedUpdateThreadFunc, &threadArgs) == 0) + { + // Show an indeterminate progress bar while an elevated update is in + // progress. + ShowProgressUI(true); + } + t1.Join(); + + LaunchCallbackAndPostProcessApps(argc, argv, callbackIndex, false); + return gSucceeded ? 0 : 1; + } +#endif + + LogInit(gPatchDirPath, NS_T("update.log")); + + if (!WriteStatusFile("applying")) + { + LOG(("failed setting status to 'applying'")); +#ifdef MACOSX + if (isElevated) + { + freeArguments(argc, argv); + CleanupElevatedMacUpdate(true); + } +#endif + return 1; + } + + if (sStagedUpdate) + { + LOG(("Performing a staged update")); + } + else if (sReplaceRequest) + { + LOG(("Performing a replace request")); + } + + LOG(("PATCH DIRECTORY " LOG_S, gPatchDirPath)); + LOG(("INSTALLATION DIRECTORY " LOG_S, gInstallDirPath)); + LOG(("WORKING DIRECTORY " LOG_S, gWorkingDirPath)); + +#ifdef _WIN32 + if (_wcsnicmp(gWorkingDirPath, gInstallDirPath, MAX_PATH) != 0) + { + if (!sStagedUpdate && !sReplaceRequest) + { + WriteStatusFile(INVALID_APPLYTO_DIR_ERROR); + LOG(("Installation directory and working directory must be the same " + "for non-staged updates. Exiting.")); + LogFinish(); + return 1; + } + + NS_tchar workingDirParent[MAX_PATH]; + NS_tsnprintf(workingDirParent, + sizeof(workingDirParent) / sizeof(workingDirParent[0]), + NS_T("%s"), gWorkingDirPath); + if (!PathRemoveFileSpecW(workingDirParent)) + { + WriteStatusFile(REMOVE_FILE_SPEC_ERROR); + LOG(("Error calling PathRemoveFileSpecW: %d", GetLastError())); + LogFinish(); + return 1; + } + + if (_wcsnicmp(workingDirParent, gInstallDirPath, MAX_PATH) != 0) + { + WriteStatusFile(INVALID_APPLYTO_DIR_STAGED_ERROR); + LOG(("The apply-to directory must be the same as or " + "a child of the installation directory! Exiting.")); + LogFinish(); + return 1; + } + } +#endif + + +#ifdef _WIN32 + if (pid > 0) + { + HANDLE parent = OpenProcess(SYNCHRONIZE, false, (DWORD) pid); + // May return nullptr if the parent process has already gone away. + // Otherwise, wait for the parent process to exit before starting the + // update. + if (parent) + { + DWORD waitTime = PARENT_WAIT; + DWORD result = WaitForSingleObject(parent, waitTime); + CloseHandle(parent); + if (result != WAIT_OBJECT_0) + return 1; + } + } +#else + if (pid > 0) + waitpid(pid, nullptr, 0); +#endif + +#if defined(_WIN32) +#ifdef MAINTENANCE_SERVICE + sUsingService = EnvHasValue("USING_SERVICE"); + putenv(const_cast<char*>("USING_SERVICE=")); +#endif + // lastFallbackError keeps track of the last error for the service not being + // used, in case of an error when fallback is not enabled we write the + // error to the update.status file. + // When fallback is disabled (MOZ_NO_SERVICE_FALLBACK does not exist) then + // we will instead fallback to not using the service and display a UAC prompt. + int lastFallbackError = FALLBACKKEY_UNKNOWN_ERROR; + + // Launch a second instance of the updater with the runas verb on Windows + // when write access is denied to the installation directory. + HANDLE updateLockFileHandle = INVALID_HANDLE_VALUE; + NS_tchar elevatedLockFilePath[MAXPATHLEN] = {NS_T('\0')}; + if (!sUsingService && + (argc > callbackIndex || sStagedUpdate || sReplaceRequest)) + { + NS_tchar updateLockFilePath[MAXPATHLEN]; + if (sStagedUpdate) + { + // When staging an update, the lock file is: + // <install_dir>\updated.update_in_progress.lock + NS_tsnprintf(updateLockFilePath, + sizeof(updateLockFilePath)/sizeof(updateLockFilePath[0]), + NS_T("%s/updated.update_in_progress.lock"), gInstallDirPath); + } + else if (sReplaceRequest) + { + // When processing a replace request, the lock file is: + // <install_dir>\..\moz_update_in_progress.lock + NS_tchar installDir[MAXPATHLEN]; + NS_tstrcpy(installDir, gInstallDirPath); + NS_tchar *slash = (NS_tchar *) NS_tstrrchr(installDir, NS_SLASH); + *slash = NS_T('\0'); + NS_tsnprintf(updateLockFilePath, + sizeof(updateLockFilePath)/sizeof(updateLockFilePath[0]), + NS_T("%s\\moz_update_in_progress.lock"), installDir); + } + else + { + // In the non-staging update case, the lock file is: + // <install_dir>\<app_name>.exe.update_in_progress.lock + NS_tsnprintf(updateLockFilePath, + sizeof(updateLockFilePath)/sizeof(updateLockFilePath[0]), + NS_T("%s.update_in_progress.lock"), argv[callbackIndex]); + } + + // The update_in_progress.lock file should only exist during an update. In + // case it exists attempt to remove it and exit if that fails to prevent + // simultaneous updates occurring. + if (!_waccess(updateLockFilePath, F_OK) && + NS_tremove(updateLockFilePath) != 0) + { + // Try to fall back to the old way of doing updates if a staged + // update fails. + if (sStagedUpdate || sReplaceRequest) + { + // Note that this could fail, but if it does, there isn't too much we + // can do in order to recover anyways. + WriteStatusFile("pending"); + } + LOG(("Update already in progress! Exiting")); + return 1; + } + + updateLockFileHandle = CreateFileW(updateLockFilePath, + GENERIC_READ | GENERIC_WRITE, + 0, + nullptr, + OPEN_ALWAYS, + FILE_FLAG_DELETE_ON_CLOSE, + nullptr); + + NS_tsnprintf(elevatedLockFilePath, + sizeof(elevatedLockFilePath)/sizeof(elevatedLockFilePath[0]), + NS_T("%s/update_elevated.lock"), gPatchDirPath); + + // Even if a file has no sharing access, you can still get its attributes + bool startedFromUnelevatedUpdater = + GetFileAttributesW(elevatedLockFilePath) != INVALID_FILE_ATTRIBUTES; + + // If we're running from the service, then we were started with the same + // token as the service so the permissions are already dropped. If we're + // running from an elevated updater that was started from an unelevated + // updater, then we drop the permissions here. We do not drop the + // permissions on the originally called updater because we use its token + // to start the callback application. + if (startedFromUnelevatedUpdater) + { + // Disable every privilege we don't need. Processes started using + // CreateProcess will use the same token as this process. + UACHelper::DisablePrivileges(nullptr); + } + + if (updateLockFileHandle == INVALID_HANDLE_VALUE || + (useService && testOnlyFallbackKeyExists && noServiceFallback)) + { + if (!_waccess(elevatedLockFilePath, F_OK) && + NS_tremove(elevatedLockFilePath) != 0) + { + fprintf(stderr, "Unable to create elevated lock file! Exiting\n"); + return 1; + } + + HANDLE elevatedFileHandle; + elevatedFileHandle = CreateFileW(elevatedLockFilePath, + GENERIC_READ | GENERIC_WRITE, + 0, + nullptr, + OPEN_ALWAYS, + FILE_FLAG_DELETE_ON_CLOSE, + nullptr); + + if (elevatedFileHandle == INVALID_HANDLE_VALUE) + { + LOG(("Unable to create elevated lock file! Exiting")); + return 1; + } + + wchar_t *cmdLine = MakeCommandLine(argc - 1, argv + 1); + if (!cmdLine) + { + CloseHandle(elevatedFileHandle); + return 1; + } + + // Make sure the path to the updater to use for the update is on local. + // We do this check to make sure that file locking is available for + // race condition security checks. + if (useService) + { + BOOL isLocal = FALSE; + useService = IsLocalFile(argv[0], isLocal) && isLocal; + } + + // If we have unprompted elevation we should NOT use the service + // for the update. Service updates happen with the SYSTEM account + // which has more privs than we need to update with. + // Windows 8 provides a user interface so users can configure this + // behavior and it can be configured in the registry in all Windows + // versions that support UAC. + if (useService) + { + BOOL unpromptedElevation; + if (IsUnpromptedElevation(unpromptedElevation)) + { + useService = !unpromptedElevation; + } + } + + // Make sure the service registry entries for the installation path + // are available. If not don't use the service. + if (useService) + { + WCHAR maintenanceServiceKey[MAX_PATH + 1]; + // TODO: moggi: needs adaptation for LibreOffice + // Most likely the registry part is not correct yet + if (CalculateRegistryPathFromFilePath(gInstallDirPath, + maintenanceServiceKey)) + { + HKEY baseKey = nullptr; + if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, + maintenanceServiceKey, 0, + KEY_READ | KEY_WOW64_64KEY, + &baseKey) == ERROR_SUCCESS) + { + RegCloseKey(baseKey); + } + else + { +#ifdef TEST_UPDATER + useService = testOnlyFallbackKeyExists; +#endif + if (!useService) + { + lastFallbackError = FALLBACKKEY_NOKEY_ERROR; + } + } + } + else + { + useService = false; + lastFallbackError = FALLBACKKEY_REGPATH_ERROR; + } + } + + // Originally we used to write "pending" to update.status before + // launching the service command. This is no longer needed now + // since the service command is launched from updater.exe. If anything + // fails in between, we can fall back to using the normal update process + // on our own. + + // If we still want to use the service try to launch the service + // command for the update. + if (useService) + { + // If the update couldn't be started, then set useService to false so + // we do the update the old way. + DWORD ret = LaunchServiceSoftwareUpdateCommand(argc, (LPCWSTR *)argv); + useService = (ret == ERROR_SUCCESS); + // If the command was launched then wait for the service to be done. + if (useService) + { + bool showProgressUI = false; + // Never show the progress UI when staging updates. + if (!sStagedUpdate) + { + // We need to call this separately instead of allowing ShowProgressUI + // to initialize the strings because the service will move the + // ini file out of the way when running updater. + showProgressUI = !InitProgressUIStrings(); + } + + // Wait for the service to stop for 5 seconds. If the service + // has still not stopped then show an indeterminate progress bar. + DWORD lastState = WaitForServiceStop(SVC_NAME, 5); + if (lastState != SERVICE_STOPPED) + { + std::thread waitThread(WaitForServiceFinishThread, nullptr); + if (showProgressUI) + { + ShowProgressUI(true, false); + } + waitThread.join(); + } + + lastState = WaitForServiceStop(SVC_NAME, 1); + if (lastState != SERVICE_STOPPED) + { + // If the service doesn't stop after 10 minutes there is + // something seriously wrong. + lastFallbackError = FALLBACKKEY_SERVICE_NO_STOP_ERROR; + useService = false; + } + } + else + { + lastFallbackError = FALLBACKKEY_LAUNCH_ERROR; + } + } + + // If the service can't be used when staging and update, make sure that + // the UAC prompt is not shown! In this case, just set the status to + // pending and the update will be applied during the next startup. + if (!useService && sStagedUpdate) + { + if (updateLockFileHandle != INVALID_HANDLE_VALUE) + { + CloseHandle(updateLockFileHandle); + } + WriteStatusFile("pending"); + return 0; + } + + // If we started the service command, and it finished, check the + // update.status file to make sure it succeeded, and if it did + // we need to manually start the PostUpdate process from the + // current user's session of this unelevated updater.exe the + // current process is running as. + // Note that we don't need to do this if we're just staging the update, + // as the PostUpdate step runs when performing the replacing in that case. + if (useService && !sStagedUpdate) + { + bool updateStatusSucceeded = false; + if (IsUpdateStatusSucceeded(updateStatusSucceeded) && + updateStatusSucceeded) + { + if (!LaunchWinPostProcess(gInstallDirPath, gPatchDirPath)) + { + fprintf(stderr, "The post update process which runs as the user" + " for service update could not be launched."); + } + } + } + + // If we didn't want to use the service at all, or if an update was + // already happening, or launching the service command failed, then + // launch the elevated updater.exe as we do without the service. + // We don't launch the elevated updater in the case that we did have + // write access all along because in that case the only reason we're + // using the service is because we are testing. + if (!useService && !noServiceFallback && + updateLockFileHandle == INVALID_HANDLE_VALUE) + { + SHELLEXECUTEINFO sinfo; + memset(&sinfo, 0, sizeof(SHELLEXECUTEINFO)); + sinfo.cbSize = sizeof(SHELLEXECUTEINFO); + sinfo.fMask = SEE_MASK_FLAG_NO_UI | + SEE_MASK_FLAG_DDEWAIT | + SEE_MASK_NOCLOSEPROCESS; + sinfo.hwnd = nullptr; + sinfo.lpFile = argv[0]; + sinfo.lpParameters = cmdLine; + sinfo.lpVerb = L"runas"; + sinfo.nShow = SW_SHOWNORMAL; + + bool result = ShellExecuteEx(&sinfo); + free(cmdLine); + + if (result) + { + WaitForSingleObject(sinfo.hProcess, INFINITE); + CloseHandle(sinfo.hProcess); + } + else + { + WriteStatusFile(ELEVATION_CANCELED); + } + } + + if (argc > callbackIndex) + { + LaunchCallbackApp(argv[5], argc - callbackIndex, + argv + callbackIndex, sUsingService); + } + + CloseHandle(elevatedFileHandle); + + if (!useService && !noServiceFallback && + INVALID_HANDLE_VALUE == updateLockFileHandle) + { + // We didn't use the service and we did run the elevated updater.exe. + // The elevated updater.exe is responsible for writing out the + // update.status file. + return 0; + } + else if (useService) + { + // The service command was launched. The service is responsible for + // writing out the update.status file. + if (updateLockFileHandle != INVALID_HANDLE_VALUE) + { + CloseHandle(updateLockFileHandle); + } + return 0; + } + else + { + // Otherwise the service command was not launched at all. + // We are only reaching this code path because we had write access + // all along to the directory and a fallback key existed, and we + // have fallback disabled (MOZ_NO_SERVICE_FALLBACK env var exists). + // We only currently use this env var from XPCShell tests. + CloseHandle(updateLockFileHandle); + WriteStatusFile(lastFallbackError); + return 0; + } + } + } +#endif + + if (sStagedUpdate) + { + // When staging updates, blow away the old installation directory and create + // it from scratch. + ensure_remove_recursive(gWorkingDirPath); + } + if (!sReplaceRequest) + { + // Try to create the destination directory if it doesn't exist + int rv = NS_tmkdir(gWorkingDirPath, 0755); + if (rv != OK && errno != EEXIST) + { +#ifdef MACOSX + if (isElevated) + { + freeArguments(argc, argv); + CleanupElevatedMacUpdate(true); + } +#endif + return 1; + } + } + +#ifdef _WIN32 + // For replace requests, we don't need to do any real updates, so this is not + // necessary. + if (!sReplaceRequest) + { + // Allocate enough space for the length of the path an optional additional + // trailing slash and null termination. + NS_tchar *destpath = (NS_tchar *) malloc((NS_tstrlen(gWorkingDirPath) + 2) * sizeof(NS_tchar)); + if (!destpath) + return 1; + + NS_tchar *c = destpath; + NS_tstrcpy(c, gWorkingDirPath); + c += NS_tstrlen(gWorkingDirPath); + if (gWorkingDirPath[NS_tstrlen(gWorkingDirPath) - 1] != NS_T('/') && + gWorkingDirPath[NS_tstrlen(gWorkingDirPath) - 1] != NS_T('\\')) + { + NS_tstrcat(c, NS_T("/")); + c += NS_tstrlen(NS_T("/")); + } + *c = NS_T('\0'); + c++; + + gDestPath = destpath; + } + + NS_tchar applyDirLongPath[MAXPATHLEN]; + if (!GetLongPathNameW(gWorkingDirPath, applyDirLongPath, + sizeof(applyDirLongPath)/sizeof(applyDirLongPath[0]))) + { + LOG(("NS_main: unable to find apply to dir: " LOG_S, gWorkingDirPath)); + LogFinish(); + WriteStatusFile(WRITE_ERROR_APPLY_DIR_PATH); + EXIT_WHEN_ELEVATED(elevatedLockFilePath, updateLockFileHandle, 1); + if (argc > callbackIndex) + { + LaunchCallbackApp(argv[5], argc - callbackIndex, + argv + callbackIndex, sUsingService); + } + return 1; + } + + HANDLE callbackFile = INVALID_HANDLE_VALUE; + if (argc > callbackIndex) + { + // If the callback executable is specified it must exist for a successful + // update. It is important we null out the whole buffer here because later + // we make the assumption that the callback application is inside the + // apply-to dir. If we don't have a fully null'ed out buffer it can lead + // to stack corruption which causes crashes and other problems. + NS_tchar callbackLongPath[MAXPATHLEN]; + ZeroMemory(callbackLongPath, sizeof(callbackLongPath)); + NS_tchar *targetPath = argv[callbackIndex]; + NS_tchar buffer[MAXPATHLEN * 2] = { NS_T('\0') }; + size_t bufferLeft = MAXPATHLEN * 2; + if (sReplaceRequest) + { + // In case of replace requests, we should look for the callback file in + // the destination directory. + size_t commonPrefixLength = PathCommonPrefixW(argv[callbackIndex], + gInstallDirPath, + nullptr); + NS_tchar *p = buffer; + NS_tstrncpy(p, argv[callbackIndex], commonPrefixLength); + p += commonPrefixLength; + bufferLeft -= commonPrefixLength; + NS_tstrncpy(p, gInstallDirPath + commonPrefixLength, bufferLeft); + + size_t len = NS_tstrlen(gInstallDirPath + commonPrefixLength); + p += len; + bufferLeft -= len; + *p = NS_T('\\'); + ++p; + bufferLeft--; + *p = NS_T('\0'); + NS_tchar installDir[MAXPATHLEN]; + NS_tstrcpy(installDir, gInstallDirPath); + size_t callbackPrefixLength = PathCommonPrefixW(argv[callbackIndex], + installDir, + nullptr); + NS_tstrncpy(p, argv[callbackIndex] + std::max(callbackPrefixLength, + commonPrefixLength), bufferLeft); + targetPath = buffer; + } + if (!GetLongPathNameW(targetPath, callbackLongPath, + sizeof(callbackLongPath)/sizeof(callbackLongPath[0]))) + { + LOG(("NS_main: unable to find callback file: " LOG_S, targetPath)); + LogFinish(); + WriteStatusFile(WRITE_ERROR_CALLBACK_PATH); + EXIT_WHEN_ELEVATED(elevatedLockFilePath, updateLockFileHandle, 1); + if (argc > callbackIndex) + { + LaunchCallbackApp(argv[5], + argc - callbackIndex, + argv + callbackIndex, + sUsingService); + } + return 1; + } + + // Doing this is only necessary when we're actually applying a patch. + if (!sReplaceRequest) + { + int len = NS_tstrlen(applyDirLongPath); + NS_tchar *s = callbackLongPath; + NS_tchar *d = gCallbackRelPath; + // advance to the apply to directory and advance past the trailing backslash + // if present. + s += len; + if (*s == NS_T('\\')) + ++s; + + // Copy the string and replace backslashes with forward slashes along the + // way. + do + { + if (*s == NS_T('\\')) + *d = NS_T('/'); + else + *d = *s; + ++s; + ++d; + } + while (*s); + *d = NS_T('\0'); + ++d; + + // Make a copy of the callback executable so it can be read when patching. + NS_tsnprintf(gCallbackBackupPath, + sizeof(gCallbackBackupPath)/sizeof(gCallbackBackupPath[0]), + NS_T("%s" CALLBACK_BACKUP_EXT), argv[callbackIndex]); + NS_tremove(gCallbackBackupPath); + if (!CopyFileW(argv[callbackIndex], gCallbackBackupPath, true)) + { + DWORD copyFileError = GetLastError(); + LOG(("NS_main: failed to copy callback file " LOG_S + " into place at " LOG_S, argv[callbackIndex], gCallbackBackupPath)); + LogFinish(); + if (copyFileError == ERROR_ACCESS_DENIED) + { + WriteStatusFile(WRITE_ERROR_ACCESS_DENIED); + } + else + { + WriteStatusFile(WRITE_ERROR_CALLBACK_APP); + } + + EXIT_WHEN_ELEVATED(elevatedLockFilePath, updateLockFileHandle, 1); + LaunchCallbackApp(argv[callbackIndex], + argc - callbackIndex, + argv + callbackIndex, + sUsingService); + return 1; + } + + // Since the process may be signaled as exited by WaitForSingleObject before + // the release of the executable image try to lock the main executable file + // multiple times before giving up. If we end up giving up, we won't + // fail the update. + const int max_retries = 10; + int retries = 1; + DWORD lastWriteError = 0; + do + { + // By opening a file handle without FILE_SHARE_READ to the callback + // executable, the OS will prevent launching the process while it is + // being updated. + callbackFile = CreateFileW(targetPath, + DELETE | GENERIC_WRITE, + // allow delete, rename, and write + FILE_SHARE_DELETE | FILE_SHARE_WRITE, + nullptr, OPEN_EXISTING, 0, nullptr); + if (callbackFile != INVALID_HANDLE_VALUE) + break; + + lastWriteError = GetLastError(); + LOG(("NS_main: callback app file open attempt %d failed. " \ + "File: " LOG_S ". Last error: %d", retries, + targetPath, lastWriteError)); + + Sleep(100); + } + while (++retries <= max_retries); + + // CreateFileW will fail if the callback executable is already in use. + if (callbackFile == INVALID_HANDLE_VALUE) + { + // Only fail the update if the last error was not a sharing violation. + if (lastWriteError != ERROR_SHARING_VIOLATION) + { + LOG(("NS_main: callback app file in use, failed to exclusively open " \ + "executable file: " LOG_S, argv[callbackIndex])); + LogFinish(); + if (lastWriteError == ERROR_ACCESS_DENIED) + { + WriteStatusFile(WRITE_ERROR_ACCESS_DENIED); + } + else + { + WriteStatusFile(WRITE_ERROR_CALLBACK_APP); + } + + NS_tremove(gCallbackBackupPath); + EXIT_WHEN_ELEVATED(elevatedLockFilePath, updateLockFileHandle, 1); + LaunchCallbackApp(argv[5], + argc - callbackIndex, + argv + callbackIndex, + sUsingService); + return 1; + } + LOG(("NS_main: callback app file in use, continuing without " \ + "exclusive access for executable file: " LOG_S, + argv[callbackIndex])); + } + } + } + + // DELETE_DIR is not required when performing a staged update or replace + // request; it can be used during a replace request but then it doesn't + // use gDeleteDirPath. + if (!sStagedUpdate && !sReplaceRequest) + { + // The directory to move files that are in use to on Windows. This directory + // will be deleted after the update is finished, on OS reboot using + // MoveFileEx if it contains files that are in use, or by the post update + // process after the update finishes. On Windows when performing a normal + // update (e.g. the update is not a staged update and is not a replace + // request) gWorkingDirPath is the same as gInstallDirPath and + // gWorkingDirPath is used because it is the destination directory. + NS_tsnprintf(gDeleteDirPath, + sizeof(gDeleteDirPath) / sizeof(gDeleteDirPath[0]), + NS_T("%s/%s"), gWorkingDirPath, DELETE_DIR); + + if (NS_taccess(gDeleteDirPath, F_OK)) + { + NS_tmkdir(gDeleteDirPath, 0755); + } + } +#endif /* _WIN32 */ + + // Run update process on a background thread. ShowProgressUI may return + // before QuitProgressUI has been called, so wait for UpdateThreadFunc to + // terminate. Avoid showing the progress UI when staging an update, or if this + // is an elevated process on OSX. + std::thread t(UpdateThreadFunc, nullptr); + if (!sStagedUpdate && !sReplaceRequest +#ifdef XP_MACOSX + && !isElevated +#endif + ) + { + ShowProgressUI(); + } + t.join(); + +#ifdef _WIN32 + if (argc > callbackIndex && !sReplaceRequest) + { + if (callbackFile != INVALID_HANDLE_VALUE) + { + CloseHandle(callbackFile); + } + // Remove the copy of the callback executable. + NS_tremove(gCallbackBackupPath); + } + + if (!sStagedUpdate && !sReplaceRequest && _wrmdir(gDeleteDirPath)) + { + LOG(("NS_main: unable to remove directory: " LOG_S ", err: %d", + DELETE_DIR, errno)); + // The directory probably couldn't be removed due to it containing files + // that are in use and will be removed on OS reboot. The call to remove the + // directory on OS reboot is done after the calls to remove the files so the + // files are removed first on OS reboot since the directory must be empty + // for the directory removal to be successful. The MoveFileEx call to remove + // the directory on OS reboot will fail if the process doesn't have write + // access to the HKEY_LOCAL_MACHINE registry key but this is ok since the + // installer / uninstaller will delete the directory along with its contents + // after an update is applied, on reinstall, and on uninstall. + if (MoveFileEx(gDeleteDirPath, nullptr, MOVEFILE_DELAY_UNTIL_REBOOT)) + { + LOG(("NS_main: directory will be removed on OS reboot: " LOG_S, + DELETE_DIR)); + } + else + { + LOG(("NS_main: failed to schedule OS reboot removal of " \ + "directory: " LOG_S, DELETE_DIR)); + } + } +#endif /* _WIN32 */ + + +#ifdef MACOSX + // When the update is successful remove the precomplete file in the root of + // the application bundle and move the distribution directory from + // Contents/MacOS to Contents/Resources and if both exist delete the + // directory under Contents/MacOS (see Bug 1068439). + if (gSucceeded && !sStagedUpdate) + { + NS_tchar oldPrecomplete[MAXPATHLEN]; + NS_tsnprintf(oldPrecomplete, sizeof(oldPrecomplete)/sizeof(oldPrecomplete[0]), + NS_T("%s/precomplete"), gInstallDirPath); + NS_tremove(oldPrecomplete); + + NS_tchar oldDistDir[MAXPATHLEN]; + NS_tsnprintf(oldDistDir, sizeof(oldDistDir)/sizeof(oldDistDir[0]), + NS_T("%s/Contents/MacOS/distribution"), gInstallDirPath); + int rv = NS_taccess(oldDistDir, F_OK); + if (!rv) + { + NS_tchar newDistDir[MAXPATHLEN]; + NS_tsnprintf(newDistDir, sizeof(newDistDir)/sizeof(newDistDir[0]), + NS_T("%s/Contents/Resources/distribution"), gInstallDirPath); + rv = NS_taccess(newDistDir, F_OK); + if (!rv) + { + LOG(("New distribution directory already exists... removing old " \ + "distribution directory: " LOG_S, oldDistDir)); + rv = ensure_remove_recursive(oldDistDir); + if (rv) + { + LOG(("Removing old distribution directory failed - err: %d", rv)); + } + } + else + { + LOG(("Moving old distribution directory to new location. src: " LOG_S \ + ", dst:" LOG_S, oldDistDir, newDistDir)); + rv = rename_file(oldDistDir, newDistDir, true); + if (rv) + { + LOG(("Moving old distribution directory to new location failed - " \ + "err: %d", rv)); + } + } + } + } + + if (isElevated) + { + SetGroupOwnershipAndPermissions(gInstallDirPath); + freeArguments(argc, argv); + CleanupElevatedMacUpdate(false); + } + else if (IsOwnedByGroupAdmin(gInstallDirPath)) + { + // If the group ownership of the Firefox .app bundle was set to the "admin" + // group during a previous elevated update, we need to ensure that all files + // in the bundle have group ownership of "admin" as well as write permission + // for the group to not break updates in the future. + SetGroupOwnershipAndPermissions(gInstallDirPath); + } +#endif /* MACOSX */ + + LogFinish(); + + int retVal = LaunchCallbackAndPostProcessApps(argc, argv, callbackIndex +#ifdef _WIN32 + , elevatedLockFilePath + , updateLockFileHandle +#elif defined(MACOSX) + , isElevated +#endif + ); + + return retVal ? retVal : (gSucceeded ? 0 : 1); +} + +class ActionList +{ +public: + ActionList() : mFirst(nullptr), mLast(nullptr), mCount(0) { } + ~ActionList(); + + void Append(Action* action); + int Prepare(); + int Execute(); + void Finish(int status); + +private: + Action *mFirst; + Action *mLast; + int mCount; +}; + +ActionList::~ActionList() +{ + Action* a = mFirst; + while (a) + { + Action *b = a; + a = a->mNext; + delete b; + } +} + +void +ActionList::Append(Action *action) +{ + if (mLast) + mLast->mNext = action; + else + mFirst = action; + + mLast = action; + mCount++; +} + +int +ActionList::Prepare() +{ + // If the action list is empty then we should fail in order to signal that + // something has gone wrong. Otherwise we report success when nothing is + // actually done. See bug 327140. + if (mCount == 0) + { + LOG(("empty action list")); + return MAR_ERROR_EMPTY_ACTION_LIST; + } + + Action *a = mFirst; + int i = 0; + while (a) + { + int rv = a->Prepare(); + if (rv) + return rv; + + float percent = float(++i) / float(mCount); + UpdateProgressUI(PROGRESS_PREPARE_SIZE * percent); + + a = a->mNext; + } + + return OK; +} + +int +ActionList::Execute() +{ + int currentProgress = 0, maxProgress = 0; + Action *a = mFirst; + while (a) + { + maxProgress += a->mProgressCost; + a = a->mNext; + } + + a = mFirst; + while (a) + { + int rv = a->Execute(); + if (rv) + { + LOG(("### execution failed")); + return rv; + } + + currentProgress += a->mProgressCost; + float percent = float(currentProgress) / float(maxProgress); + UpdateProgressUI(PROGRESS_PREPARE_SIZE + + PROGRESS_EXECUTE_SIZE * percent); + + a = a->mNext; + } + + return OK; +} + +void +ActionList::Finish(int status) +{ + Action *a = mFirst; + int i = 0; + while (a) + { + a->Finish(status); + + float percent = float(++i) / float(mCount); + UpdateProgressUI(PROGRESS_PREPARE_SIZE + + PROGRESS_EXECUTE_SIZE + + PROGRESS_FINISH_SIZE * percent); + + a = a->mNext; + } + + if (status == OK) + gSucceeded = true; +} + + +#ifdef _WIN32 +int add_dir_entries(const NS_tchar *dirpath, ActionList *list) +{ + int rv = OK; + WIN32_FIND_DATAW finddata; + HANDLE hFindFile; + NS_tchar searchspec[MAXPATHLEN]; + NS_tchar foundpath[MAXPATHLEN]; + + NS_tsnprintf(searchspec, sizeof(searchspec)/sizeof(searchspec[0]), + NS_T("%s*"), dirpath); + std::unique_ptr<const NS_tchar[]> pszSpec(new_absolute_path(searchspec)); + + hFindFile = FindFirstFileW(pszSpec.get(), &finddata); + if (hFindFile != INVALID_HANDLE_VALUE) + { + do + { + // Don't process the current or parent directory. + if (NS_tstrcmp(finddata.cFileName, NS_T(".")) == 0 || + NS_tstrcmp(finddata.cFileName, NS_T("..")) == 0) + continue; + + NS_tsnprintf(foundpath, sizeof(foundpath)/sizeof(foundpath[0]), + NS_T("%s%s"), dirpath, finddata.cFileName); + if (finddata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + { + NS_tsnprintf(foundpath, sizeof(foundpath)/sizeof(foundpath[0]), + NS_T("%s/"), foundpath); + // Recurse into the directory. + rv = add_dir_entries(foundpath, list); + if (rv) + { + LOG(("add_dir_entries error: " LOG_S ", err: %d", foundpath, rv)); + return rv; + } + } + else + { + // Add the file to be removed to the ActionList. + NS_tchar *quotedpath = get_quoted_path(foundpath); + if (!quotedpath) + return PARSE_ERROR; + + Action *action = new RemoveFile(); + rv = action->Parse(quotedpath); + if (rv) + { + LOG(("add_dir_entries Parse error on recurse: " LOG_S ", err: %d", + quotedpath, rv)); + return rv; + } + free(quotedpath); + + list->Append(action); + } + } + while (FindNextFileW(hFindFile, &finddata) != 0); + + FindClose(hFindFile); + { + // Add the directory to be removed to the ActionList. + NS_tchar *quotedpath = get_quoted_path(dirpath); + if (!quotedpath) + return PARSE_ERROR; + + Action *action = new RemoveDir(); + rv = action->Parse(quotedpath); + if (rv) + LOG(("add_dir_entries Parse error on close: " LOG_S ", err: %d", + quotedpath, rv)); + else + list->Append(action); + free(quotedpath); + } + } + + return rv; +} + +#elif defined(__sun) +int add_dir_entries(const NS_tchar *dirpath, ActionList *list) +{ + int rv = OK; + NS_tchar foundpath[MAXPATHLEN]; + struct + { + dirent dent_buffer; + char chars[MAXNAMLEN]; + } ent_buf; + struct dirent* ent; + std::unique_ptr<NS_tchar[]> searchpath(new_absolute_path(dirpath)); + + DIR* dir = opendir(searchpath.get()); + if (!dir) + { + LOG(("add_dir_entries error on opendir: " LOG_S ", err: %d", searchpath.get(), + errno)); + return UNEXPECTED_FILE_OPERATION_ERROR; + } + + while (readdir_r(dir, (dirent *)&ent_buf, &ent) == 0 && ent) + { + if ((strcmp(ent->d_name, ".") == 0) || + (strcmp(ent->d_name, "..") == 0)) + continue; + + NS_tsnprintf(foundpath, sizeof(foundpath)/sizeof(foundpath[0]), + NS_T("%s%s"), searchpath.get(), ent->d_name); + struct stat64 st_buf; + int test = stat64(foundpath, &st_buf); + if (test) + { + closedir(dir); + return UNEXPECTED_FILE_OPERATION_ERROR; + } + if (S_ISDIR(st_buf.st_mode)) + { + NS_tsnprintf(foundpath, sizeof(foundpath)/sizeof(foundpath[0]), + NS_T("%s/"), foundpath); + // Recurse into the directory. + rv = add_dir_entries(foundpath, list); + if (rv) + { + LOG(("add_dir_entries error: " LOG_S ", err: %d", foundpath, rv)); + closedir(dir); + return rv; + } + } + else + { + // Add the file to be removed to the ActionList. + NS_tchar *quotedpath = get_quoted_path(get_relative_offset(foundpath)); + if (!quotedpath) + { + closedir(dir); + return PARSE_ERROR; + } + + Action *action = new RemoveFile(); + rv = action->Parse(quotedpath); + if (rv) + { + LOG(("add_dir_entries Parse error on recurse: " LOG_S ", err: %d", + quotedpath, rv)); + closedir(dir); + return rv; + } + + list->Append(action); + } + } + closedir(dir); + + // Add the directory to be removed to the ActionList. + NS_tchar *quotedpath = get_quoted_path(get_relative_offset(dirpath)); + if (!quotedpath) + return PARSE_ERROR; + + Action *action = new RemoveDir(); + rv = action->Parse(quotedpath); + if (rv) + { + LOG(("add_dir_entries Parse error on close: " LOG_S ", err: %d", + quotedpath, rv)); + } + else + { + list->Append(action); + } + + return rv; +} + +#else + +int add_dir_entries(const NS_tchar *dirpath, ActionList *list) +{ + int rv = OK; + FTS *ftsdir; + FTSENT *ftsdirEntry; + std::unique_ptr<NS_tchar[]> searchpath(new_absolute_path(dirpath)); + + // Remove the trailing slash so the paths don't contain double slashes. The + // existence of the slash has already been checked in DoUpdate. + searchpath.get()[NS_tstrlen(searchpath.get()) - 1] = NS_T('\0'); + char* const pathargv[] = {searchpath.get(), nullptr}; + + // FTS_NOCHDIR is used so relative paths from the destination directory are + // returned. + if (!(ftsdir = fts_open(pathargv, + FTS_PHYSICAL | FTS_NOSTAT | FTS_XDEV | FTS_NOCHDIR, + nullptr))) + return UNEXPECTED_FILE_OPERATION_ERROR; + + while ((ftsdirEntry = fts_read(ftsdir)) != nullptr) + { + NS_tchar foundpath[MAXPATHLEN]; + NS_tchar *quotedpath = nullptr; + Action *action = nullptr; + + switch (ftsdirEntry->fts_info) + { + // Filesystem objects that shouldn't be in the application's directories + case FTS_SL: + case FTS_SLNONE: + case FTS_DEFAULT: + LOG(("add_dir_entries: found a non-standard file: " LOG_S, + ftsdirEntry->fts_path)); + /* Fall through */ // and try to remove as a file + + // Files + case FTS_F: + case FTS_NSOK: + // Add the file to be removed to the ActionList. + NS_tsnprintf(foundpath, sizeof(foundpath)/sizeof(foundpath[0]), + NS_T("%s"), ftsdirEntry->fts_accpath); + quotedpath = get_quoted_path(get_relative_offset(foundpath)); + if (!quotedpath) + { + rv = UPDATER_QUOTED_PATH_MEM_ERROR; + break; + } + action = new RemoveFile(); + rv = action->Parse(quotedpath); + free(quotedpath); + if (!rv) + list->Append(action); + break; + + // Directories + case FTS_DP: + rv = OK; + // Add the directory to be removed to the ActionList. + NS_tsnprintf(foundpath, sizeof(foundpath)/sizeof(foundpath[0]), + NS_T("%s/"), ftsdirEntry->fts_accpath); + quotedpath = get_quoted_path(get_relative_offset(foundpath)); + if (!quotedpath) + { + rv = UPDATER_QUOTED_PATH_MEM_ERROR; + break; + } + + action = new RemoveDir(); + rv = action->Parse(quotedpath); + free(quotedpath); + if (!rv) + list->Append(action); + break; + + // Errors + case FTS_DNR: + case FTS_NS: + // ENOENT is an acceptable error for FTS_DNR and FTS_NS and means that + // we're racing with ourselves. Though strange, the entry will be + // removed anyway. + if (ENOENT == ftsdirEntry->fts_errno) + { + rv = OK; + break; + } + // Fall through + + case FTS_ERR: + rv = UNEXPECTED_FILE_OPERATION_ERROR; + LOG(("add_dir_entries: fts_read() error: " LOG_S ", err: %d", + ftsdirEntry->fts_path, ftsdirEntry->fts_errno)); + break; + + case FTS_DC: + rv = UNEXPECTED_FILE_OPERATION_ERROR; + LOG(("add_dir_entries: fts_read() returned FT_DC: " LOG_S, + ftsdirEntry->fts_path)); + break; + + default: + // FTS_D is ignored and FTS_DP is used instead (post-order). + rv = OK; + break; + } + + if (rv != OK) + break; + } + + fts_close(ftsdir); + + return rv; +} +#endif + +static NS_tchar* +GetManifestContents(const NS_tchar *manifest) +{ + AutoFile mfile(NS_tfopen(manifest, NS_T("rb"))); + if (mfile == nullptr) + { + LOG(("GetManifestContents: error opening manifest file: " LOG_S, manifest)); + return nullptr; + } + + struct stat ms; + int rv = fstat(fileno((FILE *)mfile), &ms); + if (rv) + { + LOG(("GetManifestContents: error stating manifest file: " LOG_S, manifest)); + return nullptr; + } + + char *mbuf = (char *) malloc(ms.st_size + 1); + if (!mbuf) + return nullptr; + + size_t r = ms.st_size; + char *rb = mbuf; + while (r) + { + const size_t count = std::min<size_t>(SSIZE_MAX, r); + size_t c = fread(rb, 1, count, mfile); + if (c != count) + { + LOG(("GetManifestContents: error reading manifest file: " LOG_S, manifest)); + free(mbuf); + return nullptr; + } + + r -= c; + rb += c; + } + mbuf[ms.st_size] = '\0'; + rb = mbuf; + +#ifndef _WIN32 + return rb; +#else + NS_tchar *wrb = (NS_tchar *) malloc((ms.st_size + 1) * sizeof(NS_tchar)); + if (!wrb) + { + free(mbuf); + return nullptr; + } + + if (!MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, rb, -1, wrb, + ms.st_size + 1)) + { + LOG(("GetManifestContents: error converting utf8 to utf16le: %d", GetLastError())); + free(mbuf); + free(wrb); + return nullptr; + } + free(mbuf); + + return wrb; +#endif +} + +int AddPreCompleteActions(ActionList *list) +{ +#ifdef MACOSX + std::unique_ptr<NS_tchar[]> manifestPath(new_absolute_path( + NS_T("Contents/Resources/precomplete"))); +#else + std::unique_ptr<NS_tchar[]> manifestPath(new_absolute_path( + NS_T("precomplete"))); +#endif + + NS_tchar *rb = GetManifestContents(manifestPath.get()); + if (rb == nullptr) + { + LOG(("AddPreCompleteActions: error getting contents of precomplete " \ + "manifest")); + // Applications aren't required to have a precomplete manifest. The mar + // generation scripts enforce the presence of a precomplete manifest. + return OK; + } + + int rv; + NS_tchar *line; + while ((line = mstrtok(kNL, &rb)) != 0) + { + // skip comments + if (*line == NS_T('#')) + continue; + + NS_tchar *token = mstrtok(kWhitespace, &line); + if (!token) + { + LOG(("AddPreCompleteActions: token not found in manifest")); + return PARSE_ERROR; + } + + Action *action = nullptr; + if (NS_tstrcmp(token, NS_T("remove")) == 0) // rm file + { + action = new RemoveFile(); + } + else if (NS_tstrcmp(token, NS_T("remove-cc")) == 0) // no longer supported + { + continue; + } + else if (NS_tstrcmp(token, NS_T("rmdir")) == 0) // rmdir if empty + { + action = new RemoveDir(); + } + else + { + LOG(("AddPreCompleteActions: unknown token: " LOG_S, token)); + return PARSE_ERROR; + } + + if (!action) + return BAD_ACTION_ERROR; + + rv = action->Parse(line); + if (rv) + return rv; + + list->Append(action); + } + + return OK; +} + +int DoUpdate(ArchiveReader& archiveReader) +{ + NS_tchar manifest[MAXPATHLEN]; + int nWrittenBytes = NS_tsnprintf(manifest, sizeof(manifest)/sizeof(manifest[0]), + NS_T("%s/updating/update.manifest"), gWorkingDirPath); + (void) nWrittenBytes; + ensure_parent_dir(manifest); + + // extract the manifest + // TODO: moggi: needs adaptation for LibreOffice + // Why would we need the manifest? Even if we need it why would we need 2? + int rv = archiveReader.ExtractFile("updatev3.manifest", manifest); + if (rv) + { + rv = archiveReader.ExtractFile("updatev2.manifest", manifest); + if (rv) + { + LOG(("DoUpdate: error extracting manifest file")); + return rv; + } + } + + NS_tchar *rb = GetManifestContents(manifest); + NS_tremove(manifest); + if (rb == nullptr) + { + LOG(("DoUpdate: error opening manifest file: " LOG_S, manifest)); + return READ_ERROR; + } + + ActionList list; + NS_tchar *line; + bool isFirstAction = true; + + while ((line = mstrtok(kNL, &rb)) != 0) + { + // skip comments + if (*line == NS_T('#')) + continue; + + NS_tchar *token = mstrtok(kWhitespace, &line); + if (!token) + { + LOG(("DoUpdate: token not found in manifest")); + return PARSE_ERROR; + } + + if (isFirstAction) + { + isFirstAction = false; + // The update manifest isn't required to have a type declaration. The mar + // generation scripts enforce the presence of the type declaration. + if (NS_tstrcmp(token, NS_T("type")) == 0) + { + const NS_tchar *type = mstrtok(kQuote, &line); + LOG(("UPDATE TYPE " LOG_S, type)); + if (NS_tstrcmp(type, NS_T("complete")) == 0) + { + rv = AddPreCompleteActions(&list); + if (rv) + return rv; + } + continue; + } + } + + Action *action = nullptr; + if (NS_tstrcmp(token, NS_T("remove")) == 0) // rm file + { + action = new RemoveFile(); + } + else if (NS_tstrcmp(token, NS_T("rmdir")) == 0) // rmdir if empty + { + action = new RemoveDir(); + } + else if (NS_tstrcmp(token, NS_T("rmrfdir")) == 0) // rmdir recursive + { + const NS_tchar *reldirpath = mstrtok(kQuote, &line); + if (!reldirpath) + return PARSE_ERROR; + + if (reldirpath[NS_tstrlen(reldirpath) - 1] != NS_T('/')) + return PARSE_ERROR; + + rv = add_dir_entries(reldirpath, &list); + if (rv) + return rv; + + continue; + } + else if (NS_tstrcmp(token, NS_T("add")) == 0) + { + action = new AddFile(archiveReader); + } + else if (NS_tstrcmp(token, NS_T("patch")) == 0) + { + action = new PatchFile(archiveReader); + } + else if (NS_tstrcmp(token, NS_T("add-if")) == 0) // Add if exists + { + action = new AddIfFile(archiveReader); + } + else if (NS_tstrcmp(token, NS_T("add-if-not")) == 0) // Add if not exists + { + action = new AddIfNotFile(archiveReader); + } + else if (NS_tstrcmp(token, NS_T("patch-if")) == 0) // Patch if exists + { + action = new PatchIfFile(archiveReader); + } + else + { + LOG(("DoUpdate: unknown token: " LOG_S, token)); + return PARSE_ERROR; + } + + if (!action) + return BAD_ACTION_ERROR; + + rv = action->Parse(line); + if (rv) + return rv; + + list.Append(action); + } + + rv = list.Prepare(); + if (rv) + return rv; + + rv = list.Execute(); + + list.Finish(rv); + return rv; +} diff --git a/onlineupdate/source/update/updater/updater.exe.comctl32.manifest b/onlineupdate/source/update/updater/updater.exe.comctl32.manifest new file mode 100644 index 000000000..9a6cdb565 --- /dev/null +++ b/onlineupdate/source/update/updater/updater.exe.comctl32.manifest @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes"?> +<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> +<assemblyIdentity + version="1.0.0.0" + processorArchitecture="*" + name="Updater" + type="win32" +/> +<description>Updater</description> +<dependency> + <dependentAssembly> + <assemblyIdentity + type="win32" + name="Microsoft.Windows.Common-Controls" + version="6.0.0.0" + processorArchitecture="*" + publicKeyToken="6595b64144ccf1df" + language="*" + /> + </dependentAssembly> +</dependency> +<ms_asmv3:trustInfo xmlns:ms_asmv3="urn:schemas-microsoft-com:asm.v3"> + <ms_asmv3:security> + <ms_asmv3:requestedPrivileges> + <ms_asmv3:requestedExecutionLevel level="asInvoker" uiAccess="false" /> + </ms_asmv3:requestedPrivileges> + </ms_asmv3:security> +</ms_asmv3:trustInfo> + <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1"> + <application> + <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/> + <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/> + <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/> + <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/> + <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/> + </application> + </compatibility> +</assembly> diff --git a/onlineupdate/source/update/updater/updater.exe.manifest b/onlineupdate/source/update/updater/updater.exe.manifest new file mode 100644 index 000000000..cd229c954 --- /dev/null +++ b/onlineupdate/source/update/updater/updater.exe.manifest @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes"?> +<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> +<assemblyIdentity + version="1.0.0.0" + processorArchitecture="*" + name="Updater" + type="win32" +/> +<description>Updater</description> +<ms_asmv3:trustInfo xmlns:ms_asmv3="urn:schemas-microsoft-com:asm.v3"> + <ms_asmv3:security> + <ms_asmv3:requestedPrivileges> + <ms_asmv3:requestedExecutionLevel level="asInvoker" uiAccess="false" /> + </ms_asmv3:requestedPrivileges> + </ms_asmv3:security> +</ms_asmv3:trustInfo> + <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1"> + <application> + <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/> + <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/> + <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/> + <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/> + <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/> + </application> + </compatibility> +</assembly> diff --git a/onlineupdate/source/update/updater/updater.ico b/onlineupdate/source/update/updater/updater.ico Binary files differnew file mode 100644 index 000000000..7b20ba3ec --- /dev/null +++ b/onlineupdate/source/update/updater/updater.ico diff --git a/onlineupdate/source/update/updater/updater.png b/onlineupdate/source/update/updater/updater.png Binary files differnew file mode 100644 index 000000000..9d8c6df77 --- /dev/null +++ b/onlineupdate/source/update/updater/updater.png diff --git a/onlineupdate/source/update/updater/updater.rc b/onlineupdate/source/update/updater/updater.rc new file mode 100644 index 000000000..d77cea2fe --- /dev/null +++ b/onlineupdate/source/update/updater/updater.rc @@ -0,0 +1,137 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Microsoft Visual C++ generated resource script. +// +#ifdef TEST_UPDATER +#include "../resource.h" +#define MANIFEST_PATH "../updater.exe.manifest" +#define COMCTL32_MANIFEST_PATH "../updater.exe.comctl32.manifest" +#define ICON_PATH "../updater.ico" +#else +#include "resource.h" +#define MANIFEST_PATH "updater.exe.manifest" +#define COMCTL32_MANIFEST_PATH "updater.exe.comctl32.manifest" +#define ICON_PATH "updater.ico" +#endif + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winresrc.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +///////////////////////////////////////////////////////////////////////////// +// +// RT_MANIFEST +// + +1 RT_MANIFEST MANIFEST_PATH +IDR_COMCTL32_MANIFEST RT_MANIFEST COMCTL32_MANIFEST_PATH + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +IDI_DIALOG ICON ICON_PATH + + +///////////////////////////////////////////////////////////////////////////// +// +// Embedded an identifier to uniquely identify this as a Mozilla updater. +// + +STRINGTABLE +{ + IDS_UPDATER_IDENTITY, "libreoffice-updater.exe-7bab28a0-0599-4f37-9efe-f7f8b71f05e3" +} + + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_DIALOG DIALOGEX 0, 0, 253, 41 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + CONTROL "",IDC_PROGRESS,"msctls_progress32",WS_BORDER,7,24,239,10 + LTEXT "",IDC_INFO,7,8,239,13,SS_NOPREFIX +END + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO +BEGIN + IDD_DIALOG, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 246 + TOPMARGIN, 7 + BOTTOMMARGIN, 39 + END +END +#endif // APSTUDIO_INVOKED + + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winresrc.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/onlineupdate/source/update/updater/updater.svg b/onlineupdate/source/update/updater/updater.svg new file mode 100644 index 000000000..6a3e88563 --- /dev/null +++ b/onlineupdate/source/update/updater/updater.svg @@ -0,0 +1 @@ +<svg height="32" viewBox="0 0 32 32.000001" width="32" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><radialGradient id="a" cx="5" cy="13.375" gradientTransform="matrix(2.6502653 .00000002 0 1.1249099 -5.937606 7.116442)" gradientUnits="userSpaceOnUse" r="5" xlink:href="#b"/><linearGradient id="b"><stop offset="0" stop-color="#fff"/><stop offset="1" stop-color="#fff" stop-opacity=".487603"/></linearGradient><linearGradient id="c" gradientUnits="userSpaceOnUse" x1="312.625" x2="312.0625" xlink:href="#d" y1="196.541335" y2="191.797335"/><linearGradient id="d"><stop offset="0"/><stop offset="1" stop-opacity="0"/></linearGradient><filter id="e" color-interpolation-filters="sRGB" height="1.588154" width="1.259382" x="-.129691" y="-.294077"><feGaussianBlur stdDeviation=".86460582"/></filter><linearGradient id="f" gradientUnits="userSpaceOnUse" x1="17.92817" x2="17.92817" y1="8.43984" y2="5.29526"><stop offset="0" stop-color="#e9b96e"/><stop offset="1" stop-color="#eeeeec"/></linearGradient><linearGradient id="g" gradientTransform="matrix(.06998908 0 0 .06896555 12.18409 2.80365)" gradientUnits="userSpaceOnUse" x1="174.01562" x2="30" y1="236" y2="64"><stop offset="0" stop-color="#c17d11"/><stop offset="1" stop-color="#e9b96e"/></linearGradient><radialGradient id="h" cx="321.86331" cy="70.499977" gradientTransform="matrix(.6227245 0 0 .09677408 -177.2342 3.75694)" gradientUnits="userSpaceOnUse" r="15.500023"><stop offset="0" stop-color="#faefde"/><stop offset=".68321055" stop-color="#e9b96e"/><stop offset="1" stop-color="#e9b96e" stop-opacity="0"/></radialGradient><radialGradient id="i" cx="321.7785" cy="91.857727" gradientTransform="matrix(2.0837843 0 0 .3006166 -644.48535 -10.03444)" gradientUnits="userSpaceOnUse" r="15.5"><stop offset="0" stop-color="#e9b96e"/><stop offset="1" stop-color="#e9b96e" stop-opacity="0"/></radialGradient><radialGradient id="j" cx="323.89844" cy="70.5" gradientTransform="matrix(.3715153 0 0 .3516126 -97.26516 -14.20919)" gradientUnits="userSpaceOnUse" r="15.5" xlink:href="#b"/><linearGradient id="k" gradientTransform="matrix(.3791614 0 0 .6106884 -99.96394 -32.72591)" gradientUnits="userSpaceOnUse" x1="316.90683" x2="322.41666" xlink:href="#d" y1="69.553268" y2="69.553268"/><linearGradient id="l" gradientTransform="matrix(.970062 -.2428574 0 1 -362.71624 -176.92048)" gradientUnits="userSpaceOnUse" x1="394.59244" x2="389.02057" y1="277.23956" y2="280.54391"><stop offset="0" stop-color="#eeeeec"/><stop offset="1" stop-color="#e9b96e"/></linearGradient><linearGradient id="m" gradientTransform="matrix(.3925197 0 0 .6106884 -104.79786 -32.72591)" gradientUnits="userSpaceOnUse" x1="333.88629" x2="326.32867" xlink:href="#d" y1="69.490768" y2="69.490768"/><linearGradient id="n" gradientTransform="matrix(1.0000001 1.0190348 0 1.0165713 -342.71627 -558.00916)" gradientUnits="userSpaceOnUse" x1="373.8385" x2="375.07596" y1="187.7484" y2="181.79832"><stop offset="0" stop-color="#e9b96e"/><stop offset="1" stop-color="#c17d11"/></linearGradient><linearGradient id="o" gradientUnits="userSpaceOnUse" x1="19.53376" x2="22.28376" xlink:href="#b" y1="14.70452" y2="5.98577"/><linearGradient id="p" gradientUnits="userSpaceOnUse" x1="24.81501" x2="27.63508" xlink:href="#b" y1="5.51702" y2="7.37133"/><linearGradient id="q" gradientTransform="matrix(.970062 -.2428574 0 1 -362.71624 -176.92048)" gradientUnits="userSpaceOnUse" x1="391.0188" x2="389.73026" xlink:href="#b" y1="277.72043" y2="279.4614"/><filter id="r" color-interpolation-filters="sRGB" height="1.86087" width="1.2475" x="-.12375" y="-.430435"><feGaussianBlur stdDeviation=".515625"/></filter><radialGradient id="s" cx="5" cy="13.375" gradientTransform="matrix(2.6502653 .00000002 0 1.1249099 1.062394 13.293218)" gradientUnits="userSpaceOnUse" r="5" xlink:href="#b"/><linearGradient id="t" gradientTransform="matrix(1.2807984 0 0 1.2807976 -376.53809 -266.21095)" gradientUnits="userSpaceOnUse" x1="305.62964" x2="299.38138" y1="222.08656" y2="217.11935"><stop offset="0" stop-color="#fff"/><stop offset="1" stop-color="#babdb6"/></linearGradient><rect fill="url(#c)" filter="url(#e)" height="7.056155" opacity=".753425" rx="1.3125" transform="matrix(1.0441942 0 0 1 -306.59321 -176.92048)" width="16" x="306" y="190.17233"/><rect filter="url(#r)" height="2.875" opacity=".579208" rx="1.25" ry="1.4375" transform="matrix(1.15 0 0 1 -591.83312 5.636224)" width="10" x="515.875" y="16.625"/><rect fill="#d9a452" fill-rule="evenodd" height="8.000001" rx=".09427" ry=".094274" stroke="#8f5902" width="9.015612" x="2.798108" y="16.474611"/><rect fill="#e8c48d" fill-rule="evenodd" height="4.1875" rx=".09427" ry=".094274" transform="scale(-1 1)" width="8.125" x="-11.37622" y="16.912111"/><path d="m3.75122 17.412112v6.125h7.125v-6.125z" fill="none" opacity=".439759" stroke="url(#a)"/><path d="m4.2582637 17.108049 7.3125003-.151651-.170181.91895-6.9783737.173896-.1639456 2.064009 7.0710683-.08839.08839 1.149048-8.1317282-.08839-.088388-4.154253z" fill="#f5efe4" fill-rule="evenodd"/><rect filter="url(#r)" height="2.875" opacity=".579208" rx="1.25" ry="1.4375" transform="matrix(1.15 0 0 1 -584.83312 11.636224)" width="10" x="515.875" y="16.625"/><rect fill="#d9a452" fill-rule="evenodd" height="8.000001" rx=".09427" ry=".094274" stroke="#8f5902" width="9.015612" x="9.798108" y="22.651388"/><rect fill="#e8c48d" fill-rule="evenodd" height="4.1875" rx=".09427" ry=".094274" transform="scale(-1 1)" width="8.125" x="-18.376221" y="23.088888"/><path d="m10.75122 23.588888v6.125h7.125v-6.125z" fill="none" opacity=".439759" stroke="url(#s)"/><path d="m11.258264 23.284825 7.3125-.151651-7.18713.986747-.12537 2.170108 7.071068-.08839.08839 1.149048-8.131727-.08839-.08839-4.154253 1.060661.176777z" fill="#f5efe4" fill-rule="evenodd"/><rect fill="url(#f)" height="14.999985" rx="1.016466" stroke="#8f5902" stroke-dashoffset=".5" stroke-linecap="round" stroke-linejoin="round" width="15" x="13.78376" y="4.579536"/><path d="m18.28175 8.07952h7.90593v1.00676h-7.90593z" fill="#fff" opacity=".2"/><path d="m14.7037 10.07952c-.23265 0-.41994.18455-.41994.41378v7.89657.27586c0 .22924.18729.41379.41994.41379h13.16012c.23265 0 .41994-.18455.41994-.41379v-8.17243c0-.22923-.18729-.41378-.41994-.41378z" fill="url(#g)" fill-rule="evenodd"/><path d="m14.78376 5.57952h13v13.00002h-13z" style="opacity:.342342;fill:none;stroke-linecap:round;stroke-linejoin:round;stroke-dashoffset:.5;stroke:url(#o)"/><path d="m14.34513 9.07952-.0562 2.96875 13.98983.0312-.0562-3h-13.87753z" fill="url(#h)" fill-rule="evenodd" opacity=".5"/><path d="m14.7037 10.07952c-.23265 0-.41994.18454-.41994.41379v7.89655.27587c0 .22925.18729.41379.41994.41379h13.16012c.23265 0 .41994-.18455.41994-.41379v-8.17242c0-.22925-.18729-.41379-.41994-.41379z" fill="url(#i)" fill-rule="evenodd" opacity=".815315"/><path d="m14.28376 10.07952h14v1h-14z" fill="url(#j)" opacity=".747748"/><path d="m18.28175 7.07952h6.18718v1.00676h-6.18718z" fill="#c17d11" opacity=".40991"/><path d="m16.3849 5.07952c-.0485 0-.0757.0294-.11659.11449-.0409.0851-.0772.2227-.10363.3626l-.88092 4.52291h7v-5z" fill="url(#k)" fill-rule="evenodd" opacity=".468468"/><path d="m13.783748 9.4403349v-3.97196c0-.56176.453339-1.1275045 1.01646-1.2684833l4.955804-1.2406977c.563131-.1409811 1.01647.1977743 1.01647.7595343v3.97196c0 .56176-.453339 1.1275046-1.01647 1.2684857l-4.03474.6503971" fill="url(#l)" stroke="#8f5902" stroke-dashoffset=".5" stroke-linecap="round" stroke-linejoin="round"/><path d="m22.28376 5.07952v5h5l-.99706-4.59925c-.0288-.13456-.0614-.26033-.10264-.32442-.04126-.06408-.0783-.07633-.14664-.07633z" fill="url(#m)" fill-rule="evenodd" opacity=".468468"/><path d="m28.78376 10.14202v-4.03125c0-.57245-.46814-1.48866-1.03125-2.0625l-2.9375-3.03125s-.48496-.76809-1.71875-.4375c-.18373-.00045-.3125.15128-.3125.4375v4.03125c0 .57245.46812 1.48866 1.03125 2.0625l3.00562 2.51312" fill="url(#n)" stroke="#8f5902" stroke-dashoffset=".5" stroke-linecap="round" stroke-linejoin="round"/><path d="m13.271484 6.2226562c-5.2651384 1.0467841-9.271484 5.7180798-9.271484 11.2773438 0 6.321666 5.1783326 11.5 11.5 11.5 6.321667 0 11.5-5.178334 11.5-11.5 0-4.428937-2.546314-8.2880167-6.242188-10.2070312v6.3710932c.779838 1.072341 1.242188 2.393081 1.242188 3.835938 0 3.619464-2.880531 6.5-6.5 6.5s-6.5-2.880536-6.5-6.5c0-2.833183 1.768714-5.206891 4.271484-6.107422z" fill="#888a85"/><g fill="none"><g stroke-linecap="round"><path d="m23.75251 1.52093v3.52734c0 .0439.36048.96696.74609 1.37109l2.77344 2.31055.54297.14649v-2.76563c0-.0443-.36385-.98728-.75391-1.38477a.96884687.96884687 0 0 1 -.004-.004l-2.9375-3.03125a.96884687.96884687 0 0 1 -.12305-.15625s.0335.0321-.043-.01c-.0253-.0139-.15681.009-.20117-.004z" opacity=".572072" stroke="url(#p)" stroke-dashoffset=".5" stroke-linejoin="round"/><path d="m23.69001 1.32952c.14068 1.70518-.31678 3.76924.5625 5.03125" opacity=".414414" stroke="#fff"/><path d="m14.819765 5.2499556v3.16797l1.115949.097569 3.794989-.6258648v-3.86914z" opacity=".716216" stroke="url(#q)" stroke-dashoffset=".5"/></g><path d="m24.78174 9.58626h-5v5" stroke="#888a85" stroke-linecap="square" stroke-width="5"/></g><path d="m13.322266 7.2519531c-4.740376 1.0081786-8.322266 5.2153889-8.322266 10.2480469 0 5.781225 4.7187725 10.5 10.5 10.5 5.781228 0 10.5-4.718775 10.5-10.5 0-4.918766-3.420176-9.0588504-8-10.1875v3.119141c2.91831 1.024849 5 3.787879 5 7.068359 0 4.159904-3.340092 7.499999-7.5 7.5-4.159908 0-7.5-3.340096-7.5-7.5 0-3.399893 2.235404-6.239226 5.322266-7.166016z" fill="url(#t)"/><path d="m24.78174 9.58626h-5v5" fill="none" stroke="#fff" stroke-linecap="square" stroke-width="3"/><path d="m17.281748 7.0863v1 6h1v-6h7v-1h-7z" fill="#888a85"/></svg>
\ No newline at end of file diff --git a/onlineupdate/source/update/updater/win_dirent.cxx b/onlineupdate/source/update/updater/win_dirent.cxx new file mode 100644 index 000000000..2368613ee --- /dev/null +++ b/onlineupdate/source/update/updater/win_dirent.cxx @@ -0,0 +1,89 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifdef _WIN32 +#include "win_dirent.h" +#include <errno.h> +#include <string.h> + +// This file implements the minimum set of dirent APIs used by updater.cpp on +// Windows. If updater.cpp is modified to use more of this API, we need to +// implement those parts here too. + +static dirent gDirEnt; + +DIR::DIR(const WCHAR* path) + : findHandle(INVALID_HANDLE_VALUE) +{ + memset(name, 0, sizeof(name)); + wcsncpy(name, path, sizeof(name)/sizeof(name[0])); + wcsncat(name, L"\\*", sizeof(name)/sizeof(name[0]) - wcslen(name) - 1); +} + +DIR::~DIR() +{ + if (findHandle != INVALID_HANDLE_VALUE) + { + FindClose(findHandle); + } +} + +dirent::dirent() +{ + d_name[0] = L'\0'; +} + +DIR* +opendir(const WCHAR* path) +{ + return new DIR(path); +} + +int +closedir(DIR* dir) +{ + delete dir; + return 0; +} + +dirent* readdir(DIR* dir) +{ + WIN32_FIND_DATAW data; + if (dir->findHandle != INVALID_HANDLE_VALUE) + { + BOOL result = FindNextFileW(dir->findHandle, &data); + if (!result) + { + if (GetLastError() != ERROR_FILE_NOT_FOUND) + { + errno = ENOENT; + } + return 0; + } + } + else + { + // Reading the first directory entry + dir->findHandle = FindFirstFileW(dir->name, &data); + if (dir->findHandle == INVALID_HANDLE_VALUE) + { + if (GetLastError() == ERROR_FILE_NOT_FOUND) + { + errno = ENOENT; + } + else + { + errno = EBADF; + } + return 0; + } + } + memset(gDirEnt.d_name, 0, sizeof(gDirEnt.d_name)); + wcsncpy(gDirEnt.d_name, data.cFileName, + sizeof(gDirEnt.d_name)/sizeof(gDirEnt.d_name[0])); + return &gDirEnt; +} +#endif diff --git a/onlineupdate/source/update/updater/xpcom/glue/nsVersionComparator.cxx b/onlineupdate/source/update/updater/xpcom/glue/nsVersionComparator.cxx new file mode 100644 index 000000000..def164d38 --- /dev/null +++ b/onlineupdate/source/update/updater/xpcom/glue/nsVersionComparator.cxx @@ -0,0 +1,450 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsVersionComparator.h" + +#include <stdlib.h> +#include <string.h> +#include <stdint.h> +#if defined(_WIN32) && !defined(UPDATER_NO_STRING_GLUE_STL) +#include <wchar.h> +#include "Char16.h" +#endif + +#ifdef _WIN32 +// from Mozilla's nsAlgorithm.h +template <class T> +inline const T& +XPCOM_MIN(const T& aA, const T& aB) +{ + return aB < aA ? aB : aA; +} +#endif + +struct VersionPart +{ + int32_t numA; + + const char* strB; // NOT null-terminated, can be a null pointer + uint32_t strBlen; + + int32_t numC; + + char* extraD; // null-terminated +}; + +#ifdef _WIN32 +struct VersionPartW +{ + int32_t numA; + + wchar_t* strB; // NOT null-terminated, can be a null pointer + uint32_t strBlen; + + int32_t numC; + + wchar_t* extraD; // null-terminated + +}; +#endif + +/** + * Parse a version part into a number and "extra text". + * + * @returns A pointer to the next versionpart, or null if none. + */ +static char* +ParseVP(char* aPart, VersionPart& aResult) +{ + char* dot; + + aResult.numA = 0; + aResult.strB = nullptr; + aResult.strBlen = 0; + aResult.numC = 0; + aResult.extraD = nullptr; + + if (!aPart) + { + return aPart; + } + + dot = strchr(aPart, '.'); + if (dot) + { + *dot = '\0'; + } + + if (aPart[0] == '*' && aPart[1] == '\0') + { + aResult.numA = INT32_MAX; + aResult.strB = ""; + } + else + { + aResult.numA = strtol(aPart, const_cast<char**>(&aResult.strB), 10); + } + + if (!*aResult.strB) + { + aResult.strB = nullptr; + aResult.strBlen = 0; + } + else + { + if (aResult.strB[0] == '+') + { + static const char kPre[] = "pre"; + + ++aResult.numA; + aResult.strB = kPre; + aResult.strBlen = sizeof(kPre) - 1; + } + else + { + const char* numstart = strpbrk(aResult.strB, "0123456789+-"); + if (!numstart) + { + aResult.strBlen = strlen(aResult.strB); + } + else + { + aResult.strBlen = numstart - aResult.strB; + + aResult.numC = strtol(numstart, &aResult.extraD, 10); + if (!*aResult.extraD) + { + aResult.extraD = nullptr; + } + } + } + } + + if (dot) + { + ++dot; + + if (!*dot) + { + dot = nullptr; + } + } + + return dot; +} + + +/** + * Parse a version part into a number and "extra text". + * + * @returns A pointer to the next versionpart, or null if none. + */ +#ifdef _WIN32 +static wchar_t* +ParseVP(wchar_t* aPart, VersionPartW& aResult) +{ + + wchar_t* dot; + + aResult.numA = 0; + aResult.strB = nullptr; + aResult.strBlen = 0; + aResult.numC = 0; + aResult.extraD = nullptr; + + if (!aPart) + { + return aPart; + } + + dot = wcschr(aPart, '.'); + if (dot) + { + *dot = '\0'; + } + + if (aPart[0] == '*' && aPart[1] == '\0') + { + aResult.numA = INT32_MAX; + aResult.strB = L""; + } + else + { + aResult.numA = wcstol(aPart, const_cast<wchar_t**>(&aResult.strB), 10); + } + + if (!*aResult.strB) + { + aResult.strB = nullptr; + aResult.strBlen = 0; + } + else + { + if (aResult.strB[0] == '+') + { + static wchar_t kPre[] = L"pre"; + + ++aResult.numA; + aResult.strB = kPre; + aResult.strBlen = sizeof(kPre) - 1; + } + else + { + const wchar_t* numstart = wcspbrk(aResult.strB, L"0123456789+-"); + if (!numstart) + { + aResult.strBlen = wcslen(aResult.strB); + } + else + { + aResult.strBlen = numstart - aResult.strB; + + aResult.numC = wcstol(numstart, &aResult.extraD, 10); + if (!*aResult.extraD) + { + aResult.extraD = nullptr; + } + } + } + } + + if (dot) + { + ++dot; + + if (!*dot) + { + dot = nullptr; + } + } + + return dot; +} +#endif + +// compare two null-terminated strings, which may be null pointers +static int32_t +ns_strcmp(const char* aStr1, const char* aStr2) +{ + // any string is *before* no string + if (!aStr1) + { + return aStr2 != 0; + } + + if (!aStr2) + { + return -1; + } + + return strcmp(aStr1, aStr2); +} + +// compare two length-specified string, which may be null pointers +static int32_t +ns_strnncmp(const char* aStr1, uint32_t aLen1, + const char* aStr2, uint32_t aLen2) +{ + // any string is *before* no string + if (!aStr1) + { + return aStr2 != 0; + } + + if (!aStr2) + { + return -1; + } + + for (; aLen1 && aLen2; --aLen1, --aLen2, ++aStr1, ++aStr2) + { + if (*aStr1 < *aStr2) + { + return -1; + } + + if (*aStr1 > *aStr2) + { + return 1; + } + } + + if (aLen1 == 0) + { + return aLen2 == 0 ? 0 : -1; + } + + return 1; +} + +// compare two int32_t +static int32_t +ns_cmp(int32_t aNum1, int32_t aNum2) +{ + if (aNum1 < aNum2) + { + return -1; + } + + return aNum1 != aNum2; +} + +/** + * Compares two VersionParts + */ +static int32_t +CompareVP(VersionPart& aVer1, VersionPart& aVer2) +{ + int32_t r = ns_cmp(aVer1.numA, aVer2.numA); + if (r) + { + return r; + } + + r = ns_strnncmp(aVer1.strB, aVer1.strBlen, aVer2.strB, aVer2.strBlen); + if (r) + { + return r; + } + + r = ns_cmp(aVer1.numC, aVer2.numC); + if (r) + { + return r; + } + + return ns_strcmp(aVer1.extraD, aVer2.extraD); +} + +/** + * Compares two VersionParts + */ +#ifdef _WIN32 +static int32_t +CompareVP(VersionPartW& aVer1, VersionPartW& aVer2) +{ + int32_t r = ns_cmp(aVer1.numA, aVer2.numA); + if (r) + { + return r; + } + + r = wcsncmp(aVer1.strB, aVer2.strB, XPCOM_MIN(aVer1.strBlen, aVer2.strBlen)); + if (r) + { + return r; + } + + r = ns_cmp(aVer1.numC, aVer2.numC); + if (r) + { + return r; + } + + if (!aVer1.extraD) + { + return aVer2.extraD != 0; + } + + if (!aVer2.extraD) + { + return -1; + } + + return wcscmp(aVer1.extraD, aVer2.extraD); +} +#endif + +namespace mozilla { + +#ifdef _WIN32 +int32_t +CompareVersions(const wchar_t* aStrA, const wchar_t* aStrB) +{ + wchar_t* A2 = wcsdup(aStrA); + if (!A2) + { + return 1; + } + + wchar_t* B2 = wcsdup(aStrB); + if (!B2) + { + free(A2); + return 1; + } + + int32_t result; + wchar_t* a = A2; + wchar_t* b = B2; + + do + { + VersionPartW va, vb; + + a = ParseVP(a, va); + b = ParseVP(b, vb); + + result = CompareVP(va, vb); + if (result) + { + break; + } + + } + while (a || b); + + free(A2); + free(B2); + + return result; +} +#endif + +int32_t +CompareVersions(const char* aStrA, const char* aStrB) +{ + char* A2 = strdup(aStrA); + if (!A2) + { + return 1; + } + + char* B2 = strdup(aStrB); + if (!B2) + { + free(A2); + return 1; + } + + int32_t result; + char* a = A2; + char* b = B2; + + do + { + VersionPart va, vb; + + a = ParseVP(a, va); + b = ParseVP(b, vb); + + result = CompareVP(va, vb); + if (result) + { + break; + } + + } + while (a || b); + + free(A2); + free(B2); + + return result; +} + +} // namespace mozilla + diff --git a/onlineupdate/source/update/updater/xpcom/glue/nsVersionComparator.h b/onlineupdate/source/update/updater/xpcom/glue/nsVersionComparator.h new file mode 100644 index 000000000..d793e345e --- /dev/null +++ b/onlineupdate/source/update/updater/xpcom/glue/nsVersionComparator.h @@ -0,0 +1,174 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsVersionComparator_h__ +#define nsVersionComparator_h__ + +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#if defined(_WIN32) && !defined(UPDATER_NO_STRING_GLUE_STL) +#include <wchar.h> +#include <stdint.h> + +#endif + +/** + * In order to compare version numbers in Mozilla, you need to use the + * mozilla::Version class. You can construct an object of this type by passing + * in a string version number to the constructor. Objects of this type can be + * compared using the standard comparison operators. + * + * For example, let's say that you want to make sure that a given version + * number is not older than 15.a2. Here's how you would write a function to + * do that. + * + * bool IsVersionValid(const char* version) { + * return mozilla::Version("15.a2") <= mozilla::Version(version); + * } + * + * Or, since Version's constructor is implicit, you can simplify this code: + * + * bool IsVersionValid(const char* version) { + * return mozilla::Version("15.a2") <= version; + * } + * + * On Windows, if your version strings are wide characters, you should use the + * mozilla::VersionW variant instead. The semantics of that class is the same + * as Version. + */ + +namespace mozilla { + +int32_t CompareVersions(const char* aStrA, const char* aStrB); + +#ifdef _WIN32 +int32_t CompareVersions(const wchar_t* aStrA, const wchar_t* aStrB); +#endif + +struct Version +{ + explicit Version(const char* aVersionString) + { + versionContent = strdup(aVersionString); + } + + const char* ReadContent() const + { + return versionContent; + } + + ~Version() + { + free(versionContent); + } + + bool operator<(const Version& aRhs) const + { + return CompareVersions(versionContent, aRhs.ReadContent()) == -1; + } + bool operator<=(const Version& aRhs) const + { + return CompareVersions(versionContent, aRhs.ReadContent()) < 1; + } + bool operator>(const Version& aRhs) const + { + return CompareVersions(versionContent, aRhs.ReadContent()) == 1; + } + bool operator>=(const Version& aRhs) const + { + return CompareVersions(versionContent, aRhs.ReadContent()) > -1; + } + bool operator==(const Version& aRhs) const + { + return CompareVersions(versionContent, aRhs.ReadContent()) == 0; + } + bool operator!=(const Version& aRhs) const + { + return CompareVersions(versionContent, aRhs.ReadContent()) != 0; + } + bool operator<(const char* aRhs) const + { + return CompareVersions(versionContent, aRhs) == -1; + } + bool operator<=(const char* aRhs) const + { + return CompareVersions(versionContent, aRhs) < 1; + } + bool operator>(const char* aRhs) const + { + return CompareVersions(versionContent, aRhs) == 1; + } + bool operator>=(const char* aRhs) const + { + return CompareVersions(versionContent, aRhs) > -1; + } + bool operator==(const char* aRhs) const + { + return CompareVersions(versionContent, aRhs) == 0; + } + bool operator!=(const char* aRhs) const + { + return CompareVersions(versionContent, aRhs) != 0; + } + +private: + char* versionContent; +}; + +#ifdef _WIN32 +struct VersionW +{ + explicit VersionW(const wchar_t* aVersionStringW) + { + versionContentW = + reinterpret_cast<wchar_t*>(wcsdup(aVersionStringW)); + } + + const wchar_t* ReadContentW() const + { + return versionContentW; + } + + ~VersionW() + { + free(versionContentW); + } + + bool operator<(const VersionW& aRhs) const + { + return CompareVersions(versionContentW, aRhs.ReadContentW()) == -1; + } + bool operator<=(const VersionW& aRhs) const + { + return CompareVersions(versionContentW, aRhs.ReadContentW()) < 1; + } + bool operator>(const VersionW& aRhs) const + { + return CompareVersions(versionContentW, aRhs.ReadContentW()) == 1; + } + bool operator>=(const VersionW& aRhs) const + { + return CompareVersions(versionContentW, aRhs.ReadContentW()) > -1; + } + bool operator==(const VersionW& aRhs) const + { + return CompareVersions(versionContentW, aRhs.ReadContentW()) == 0; + } + bool operator!=(const VersionW& aRhs) const + { + return CompareVersions(versionContentW, aRhs.ReadContentW()) != 0; + } + +private: + wchar_t* versionContentW; +}; +#endif + +} // namespace mozilla + +#endif // nsVersionComparator_h__ + diff --git a/onlineupdate/workben/test_dialog.cxx b/onlineupdate/workben/test_dialog.cxx new file mode 100644 index 000000000..ba9f258df --- /dev/null +++ b/onlineupdate/workben/test_dialog.cxx @@ -0,0 +1,39 @@ +#include "progressui.h" + +#if defined(_WIN32) +#include "progressui_win.cxx" +#else +#include "progressui_gtk.cxx" +#endif + +#include <thread> +#include <chrono> +#include <iostream> + +void func() +{ + for (int i = 0; i <= 100; ++i) + { + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + UpdateProgressUI(i); + } + QuitProgressUI(); +} + +int NS_main(int argc, NS_tchar** argv) +{ + InitProgressUI(&argc, &argv); + std::thread a(func); + /* + volatile bool b = false; + do + { + std::this_thread::sleep_for(std::chrono::seconds(1)); + } + while (!b); + */ + int result = ShowProgressUI(); + std::cout << result << std::endl; + a.join(); + return 0; +} |