author | Brian R. Bondy <netzen@gmail.com> |
Wed, 04 Jan 2012 23:19:14 -0500 | |
changeset 83737 | 314cab6ecbe347e0e2149a8bc477646b85426735 |
parent 83736 | 34cbeb81ea8e86d6dc8dd3584bfd25e7d81cb83f |
child 83738 | 1bd9f069576e63b5527ea9b4d99b7f3aca1bd5c4 |
push id | unknown |
push user | unknown |
push date | unknown |
reviewers | rstrong |
bugs | 481815 |
milestone | 12.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/browser/Makefile.in +++ b/browser/Makefile.in @@ -66,11 +66,13 @@ include $(topsrcdir)/config/rules.mk ifeq ($(OS_ARCH),WINNT) ifdef MOZ_INSTALLER # For Windows build the uninstaller during the application build since the # uninstaller is included with the application for mar file generation. libs:: $(MAKE) -C installer/windows uninstaller - +ifdef MOZ_MAINTENANCE_SERVICE + $(MAKE) -C installer/windows maintenanceservice_installer endif endif +endif
--- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -195,16 +195,21 @@ pref("app.update.showInstalledUI", false // 0 = suppress prompting for incompatibilities if there are updates available // to newer versions of installed addons that resolve them. // 1 = suppress prompting for incompatibilities only if there are VersionInfo // updates available to installed addons that resolve them, not newer // versions. pref("app.update.incompatible.mode", 0); +// Whether or not to attempt using the service for updates. +#ifdef MOZ_MAINTENANCE_SERVICE +pref("app.update.service.enabled", true); +#endif + // Symmetric (can be overridden by individual extensions) update preferences. // e.g. // extensions.{GUID}.update.enabled // extensions.{GUID}.update.url // .. etc .. // pref("extensions.update.enabled", true); pref("extensions.update.url", "https://versioncheck.addons.mozilla.org/update/VersionCheck.php?reqVersion=%REQ_VERSION%&id=%ITEM_ID%&version=%ITEM_VERSION%&maxAppVersion=%ITEM_MAXAPPVERSION%&status=%ITEM_STATUS%&appID=%APP_ID%&appVersion=%APP_VERSION%&appOS=%APP_OS%&appABI=%APP_ABI%&locale=%APP_LOCALE%¤tAppVersion=%CURRENT_APP_VERSION%&updateType=%UPDATE_TYPE%&compatMode=%COMPATIBILITY_MODE%");
--- a/browser/base/content/aboutDialog.js +++ b/browser/base/content/aboutDialog.js @@ -183,19 +183,23 @@ function appUpdater() appUpdater.prototype = { // true when there is an update check in progress. isChecking: false, // true when there is an update already staged / ready to be applied. get isPending() { - if (this.update) - return this.update.state == "pending"; - return this.um.activeUpdate && this.um.activeUpdate.state == "pending"; + if (this.update) { + return this.update.state == "pending" || + this.update.state == "pending-service"; + } + return this.um.activeUpdate && + (this.um.activeUpdate.state == "pending" || + this.um.activeUpdate.state == "pending-service"); }, // true when there is an update download in progress. get isDownloading() { if (this.update) return this.update.state == "downloading"; return this.um.activeUpdate && this.um.activeUpdate.state == "downloading";
--- a/browser/components/preferences/advanced.js +++ b/browser/components/preferences/advanced.js @@ -526,16 +526,36 @@ var gAdvancedPane = { // A locked pref is sufficient to disable the radiogroup. radiogroup.disabled = !canCheck || enabledPref.locked || autoPref.locked; var modePref = document.getElementById("app.update.mode"); var warnIncompatible = document.getElementById("warnIncompatible"); // the warnIncompatible checkbox value is set by readAddonWarn warnIncompatible.disabled = radiogroup.disabled || modePref.locked || !enabledPref.value || !autoPref.value; + +#ifdef MOZ_MAINTENANCE_SERVICE + // Check to see if the maintenance service is installed. + // If it is don't show the preference at all. + var installed; + try { + Components.utils.reportError("0"); + var wrk = Components.classes["@mozilla.org/windows-registry-key;1"] + .createInstance(Components.interfaces.nsIWindowsRegKey); + wrk.open(wrk.ROOT_KEY_LOCAL_MACHINE, + "SOFTWARE\\Mozilla\\MaintenanceService", + wrk.ACCESS_READ | wrk.WOW64_64); + installed = wrk.readIntValue("Installed"); + wrk.close(); + } catch(e) { + } + if (installed != 1) { + document.getElementById("useService").hidden = true; + } +#endif }, /** * Sets the pref values based on the selected item of the radiogroup, * and sets the disabled state of the warnIncompatible checkbox accordingly. */ updateWritePrefs: function () {
--- a/browser/components/preferences/advanced.xul +++ b/browser/components/preferences/advanced.xul @@ -103,16 +103,22 @@ #ifdef MOZ_UPDATER <preference id="app.update.enabled" name="app.update.enabled" type="bool"/> <preference id="app.update.auto" name="app.update.auto" type="bool"/> <preference id="app.update.mode" name="app.update.mode" type="int"/> <preference id="app.update.disable_button.showUpdateHistory" name="app.update.disable_button.showUpdateHistory" type="bool"/> + +#ifdef MOZ_MAINTENANCE_SERVICE + <preference id="app.update.service.enabled" + name="app.update.service.enabled" + type="bool"/> +#endif #endif <preference id="browser.search.update" name="browser.search.update" type="bool"/> <!-- Encryption tab --> <preference id="security.enable_ssl3" name="security.enable_ssl3" type="bool"/> <preference id="security.enable_tls" name="security.enable_tls" type="bool"/> @@ -331,16 +337,23 @@ <hbox> <button id="showUpdateHistory" label="&updateHistory.label;" accesskey="&updateHistory.accesskey;" preference="app.update.disable_button.showUpdateHistory" oncommand="gAdvancedPane.showUpdates();"/> </hbox> + +#ifdef MOZ_MAINTENANCE_SERVICE + <checkbox id="useService" + label="&useService.label;" + accesskey="&useService.accesskey;" + preference="app.update.service.enabled"/> +#endif </groupbox> #endif <groupbox id="updateOthers"> <caption label="&updateOthers.label;"/> <checkbox id="enableSearchUpdate" label="&enableSearchUpdate.label;" accesskey="&enableSearchUpdate.accesskey;" preference="browser.search.update"/>
--- a/browser/installer/package-manifest.in +++ b/browser/installer/package-manifest.in @@ -539,16 +539,23 @@ bin/libfreebl_32int64_3.so ; [Updater] ; #ifdef XP_MACOSX @BINPATH@/updater.app/ #else @BINPATH@/updater@BIN_SUFFIX@ #endif +; [MaintenanceService] +; +#ifdef MOZ_MAINTENANCE_SERVICE +@BINPATH@/maintenanceservice.exe +@BINPATH@/maintenanceservice_installer.exe +#endif + ; [Crash Reporter] ; #ifdef MOZ_CRASHREPORTER #ifdef XP_MACOSX @BINPATH@/crashreporter.app/ #else @BINPATH@/crashreporter@BIN_SUFFIX@ @BINPATH@/crashreporter.ini
--- a/browser/installer/removed-files.in +++ b/browser/installer/removed-files.in @@ -1447,8 +1447,12 @@ extensions/inspector@mozilla.org/chrome/ extensions/inspector@mozilla.org/defaults/preferences/inspector.js extensions/inspector@mozilla.org/platform/WINNT/chrome/icons/default/winInspectorMain.ico extensions/inspector@mozilla.org/components/inspector.xpt extensions/inspector@mozilla.org/components/@DLL_PREFIX@inspector@DLL_SUFFIX@ extensions/inspector@mozilla.org/chrome/icons/default/winInspectorMain.ico components/nsPlacesTransactionsService.js components/browserplaces.xpt components/nsPlacesDBFlush.js +#ifndef MOZ_MAINTENANCE_SERVICE +maintenanceservice.exe +maintenanceservice_installer.exe +#endif
--- a/browser/installer/windows/Makefile.in +++ b/browser/installer/windows/Makefile.in @@ -56,16 +56,23 @@ DEFINES += -DPRE_RELEASE_SUFFIX="$(PRE_R INSTALLER_FILES = \ app.tag \ nsis/installer.nsi \ nsis/uninstaller.nsi \ nsis/shared.nsh \ $(NULL) +ifdef MOZ_MAINTENANCE_SERVICE +INSTALLER_FILES += \ + nsis/maintenanceservice_installer.nsi \ + $(NULL) +endif + + BRANDING_FILES = \ branding.nsi \ wizHeader.bmp \ wizHeaderRTL.bmp \ wizWatermark.bmp \ $(NULL) DEFINES += \ @@ -99,16 +106,27 @@ uninstaller:: $(INSTALL) $(addprefix $(srcdir)/,$(INSTALLER_FILES)) $(CONFIG_DIR) $(INSTALL) $(addprefix $(DIST)/branding/,$(BRANDING_FILES)) $(CONFIG_DIR) $(PYTHON) $(topsrcdir)/config/Preprocessor.py -Fsubstitution $(DEFINES) $(ACDEFINES) \ $(srcdir)/nsis/defines.nsi.in > $(CONFIG_DIR)/defines.nsi $(PYTHON) $(topsrcdir)/toolkit/mozapps/installer/windows/nsis/preprocess-locale.py \ --preprocess-locale $(topsrcdir) \ $(PPL_LOCALE_ARGS) $(AB_CD) $(CONFIG_DIR) +# For building the maintenanceservice installer +ifdef MOZ_MAINTENANCE_SERVICE +maintenanceservice_installer:: + $(INSTALL) $(addprefix $(srcdir)/,$(INSTALLER_FILES)) $(CONFIG_DIR) + $(PYTHON) $(topsrcdir)/config/Preprocessor.py -Fsubstitution $(DEFINES) $(ACDEFINES) \ + $(srcdir)/nsis/defines.nsi.in > $(CONFIG_DIR)/defines.nsi + $(PYTHON) $(topsrcdir)/toolkit/mozapps/installer/windows/nsis/preprocess-locale.py \ + --preprocess-locale $(topsrcdir) \ + $(PPL_LOCALE_ARGS) $(AB_CD) $(CONFIG_DIR) +endif + $(CONFIG_DIR)/setup.exe:: $(RM) -r $(CONFIG_DIR) $(MKDIR) $(CONFIG_DIR) $(INSTALL) $(addprefix $(srcdir)/,$(INSTALLER_FILES)) $(CONFIG_DIR) $(INSTALL) $(addprefix $(DIST)/branding/,$(BRANDING_FILES)) $(CONFIG_DIR) $(PYTHON) $(topsrcdir)/config/Preprocessor.py -Fsubstitution $(DEFINES) $(ACDEFINES) \ $(srcdir)/nsis/defines.nsi.in > $(CONFIG_DIR)/defines.nsi $(PYTHON) $(topsrcdir)/toolkit/mozapps/installer/windows/nsis/preprocess-locale.py \
--- a/browser/installer/windows/nsis/defines.nsi.in +++ b/browser/installer/windows/nsis/defines.nsi.in @@ -12,16 +12,19 @@ !define AppRegName "Firefox" !define BrandShortName "@MOZ_APP_DISPLAYNAME@" !define PreReleaseSuffix "@PRE_RELEASE_SUFFIX@" !define BrandFullName "${BrandFullNameInternal}${PreReleaseSuffix}" !define NO_UNINSTALL_SURVEY +!define CERTIFICATE_NAME "Mozilla Corporation" +!define CERTIFICATE_ISSUER "Thawte Code Signing CA - G2" + # LSP_CATEGORIES is the permitted LSP categories for the application. Each LSP # category value is ANDed together to set multiple permitted categories. # See http://msdn.microsoft.com/en-us/library/ms742253%28VS.85%29.aspx # The value below removes all LSP categories previously set. !define LSP_CATEGORIES "0x00000000" # NO_INSTDIR_FROM_REG is defined for pre-releases which have a PreReleaseSuffix # (e.g. Alpha X, Beta X, etc.) to prevent finding a non-default installation @@ -38,16 +41,20 @@ !define HAVE_64BIT_OS !define ARCH "x64" !define MinSupportedVer "Microsoft Windows Vista x64" #else !define ARCH "x86" !define MinSupportedVer "Microsoft Windows 2000" #endif +#ifdef MOZ_MAINTENANCE_SERVICE +!define MOZ_MAINTENANCE_SERVICE +#endif + # File details shared by both the installer and uninstaller VIProductVersion "1.0.0.0" VIAddVersionKey "ProductName" "${BrandShortName}" VIAddVersionKey "CompanyName" "${CompanyName}" #ifdef MOZ_OFFICIAL_BRANDING VIAddVersionKey "LegalTrademarks" "${BrandShortName} is a Trademark of The Mozilla Foundation." #endif VIAddVersionKey "LegalCopyright" "${CompanyName}"
--- a/browser/installer/windows/nsis/installer.nsi +++ b/browser/installer/windows/nsis/installer.nsi @@ -14,37 +14,39 @@ # The Original Code is the Mozilla Installer code. # # The Initial Developer of the Original Code is Mozilla Foundation # Portions created by the Initial Developer are Copyright (C) 2006 # the Initial Developer. All Rights Reserved. # # Contributor(s): # Robert Strong <robert.bugzilla@gmail.com> +# Brian R. Bondy <netzen@gmail.com> # # Alternatively, the contents of this file may be used under the terms of # either the GNU General Public License Version 2 or later (the "GPL"), or # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), # in which case the provisions of the GPL or the LGPL are applicable instead # of those above. If you wish to allow use of your version of this file only # under the terms of either the GPL or the LGPL, and not to allow others to # use your version of this file under the terms of the MPL, indicate your # decision by deleting the provisions above and replace them with the notice # and other provisions required by the GPL or the LGPL. If you do not delete # the provisions above, a recipient may use your version of this file under # the terms of any one of the MPL, the GPL or the LGPL. # # ***** END LICENSE BLOCK ***** # Required Plugins: -# AppAssocReg http://nsis.sourceforge.net/Application_Association_Registration_plug-in -# ApplicationID http://nsis.sourceforge.net/ApplicationID_plug-in -# CityHash http://mxr.mozilla.org/mozilla-central/source/other-licenses/nsis/Contrib/CityHash -# ShellLink http://nsis.sourceforge.net/ShellLink_plug-in -# UAC http://nsis.sourceforge.net/UAC_plug-in +# AppAssocReg http://nsis.sourceforge.net/Application_Association_Registration_plug-in +# ApplicationID http://nsis.sourceforge.net/ApplicationID_plug-in +# CityHash http://mxr.mozilla.org/mozilla-central/source/other-licenses/nsis/Contrib/CityHash +# ShellLink http://nsis.sourceforge.net/ShellLink_plug-in +# UAC http://nsis.sourceforge.net/UAC_plug-in +# ServicesHelper Mozilla specific plugin that is located in /other-licenses/nsis ; Set verbosity to 3 (e.g. no script) to lessen the noise in the build logs !verbose 3 ; 7-Zip provides better compression than the lzma from NSIS so we add the files ; uncompressed and use 7-Zip to create a SFX archive of it SetDatablockOptimize on SetCompress off @@ -54,16 +56,17 @@ RequestExecutionLevel user !addplugindir ./ Var TmpVal Var InstallType Var AddStartMenuSC Var AddQuickLaunchSC Var AddDesktopSC +Var InstallMaintenanceService Var PageName ; By defining NO_STARTMENU_DIR an installer that doesn't provide an option for ; an application's Start Menu PROGRAMS directory and doesn't define the ; StartMenuDir variable can use the common InstallOnInitCommon macro. !define NO_STARTMENU_DIR ; On Vista and above attempt to elevate Standard Users in addition to users that @@ -106,16 +109,17 @@ VIAddVersionKey "OriginalFilename" "setu !insertmacro CleanUpdatesDir !insertmacro CopyFilesFromDir !insertmacro CreateRegKey !insertmacro GetPathFromString !insertmacro GetParent !insertmacro InitHashAppModelId !insertmacro IsHandlerForInstallDir !insertmacro IsPinnedToTaskBar +!insertmacro IsUserAdmin !insertmacro LogDesktopShortcut !insertmacro LogQuickLaunchShortcut !insertmacro LogStartMenuShortcut !insertmacro ManualCloseAppPrompt !insertmacro PinnedToStartMenuLnkCount !insertmacro RegCleanAppHandler !insertmacro RegCleanMain !insertmacro RegCleanUninstall @@ -178,16 +182,21 @@ ShowInstDetails nevershow Page custom preOptions leaveOptions ; Select Install Directory Page !define MUI_PAGE_CUSTOMFUNCTION_PRE preDirectory !define MUI_PAGE_CUSTOMFUNCTION_LEAVE leaveDirectory !define MUI_DIRECTORYPAGE_VERIFYONLEAVE !insertmacro MUI_PAGE_DIRECTORY +; Custom Components Page +!ifdef MOZ_MAINTENANCE_SERVICE +Page custom preComponents leaveComponents +!endif + ; Custom Shortcuts Page Page custom preShortcuts leaveShortcuts ; Custom Summary Page Page custom preSummary leaveSummary ; Install Files Page !insertmacro MUI_PAGE_INSTFILES @@ -368,16 +377,47 @@ Section "-Application" APP_IDX ${If} $AddDesktopSC == 1 ${OrIf} $AddStartMenuSC == 1 WriteRegDWORD HKLM "$0" "IconsVisible" 1 ${Else} WriteRegDWORD HKLM "$0" "IconsVisible" 0 ${EndIf} ${EndIf} +!ifdef MOZ_MAINTENANCE_SERVICE + ; If the maintenance service page was displayed then a value was already + ; explicitly selected for installing the maintenance service and + ; and so InstallMaintenanceService will already be 0 or 1. + ; If the maintenance service page was not displayed then + ; InstallMaintenanceService will be equal to "". + ${If} $InstallMaintenanceService == "" + Call IsUserAdmin + Pop $R0 + ${If} $R0 == "true" + ; Only proceed if we have HKLM write access + ${AndIf} $TmpVal == "HKLM" + ; On Windows 2000 we do not install the maintenance service. + ${AndIf} ${AtLeastWinXP} + ; The user is an admin so we should default to install service yes + StrCpy $InstallMaintenanceService "1" + ${Else} + ; The user is not admin so we should default to install service no + StrCpy $InstallMaintenanceService "0" + ${EndIf} + ${EndIf} + + ${If} $InstallMaintenanceService == "1" + ; The user wants to install the maintenance service, so execute + ; the pre-packaged maintenance service installer. + ; This option can only be turned on if the user is an admin so there + ; is no need to use ExecShell w/ verb runas to enforce elevated. + nsExec::Exec "$INSTDIR\maintenanceservice_installer.exe" + ${EndIf} +!endif + ; These need special handling on uninstall since they may be overwritten by ; an install into a different location. StrCpy $0 "Software\Microsoft\Windows\CurrentVersion\App Paths\${FileMainEXE}" ${WriteRegStr2} $TmpVal "$0" "" "$INSTDIR\${FileMainEXE}" 0 ${WriteRegStr2} $TmpVal "$0" "Path" "$INSTDIR" 0 StrCpy $0 "Software\Microsoft\MediaPlayer\ShimInclusionList\$R9" ${CreateRegKey} "$TmpVal" "$0" 0 @@ -421,17 +461,17 @@ Section "-Application" APP_IDX ; maintenance for the unelevated user context in case that is different than ; the current user. ClearErrors ${GetParameters} $0 ${GetOptions} "$0" "/UAC:" $0 ${Unless} ${Errors} GetFunctionAddress $0 FixShortcutAppModelIDs UAC::ExecCodeSegment $0 - ${EndIf} + ${EndUnless} ; UAC only allows elevating to an Admin account so there is no need to add ; the Start Menu or Desktop shortcuts from the original unelevated process ; since this will either add it for the user if unelevated or All Users if ; elevated. ${If} $AddStartMenuSC == 1 CreateShortCut "$SMPROGRAMS\${BrandFullName}.lnk" "$INSTDIR\${FileMainEXE}" ${If} ${FileExists} "$SMPROGRAMS\${BrandFullName}.lnk" @@ -477,16 +517,23 @@ Section "-Application" APP_IDX ; add the log entry without the path since there is no simple way to ; know the correct full path. ${LogMsg} "Added Quick Launch Shortcut: ${BrandFullName}.lnk" GetFunctionAddress $0 AddQuickLaunchShortcut UAC::ExecCodeSegment $0 ${EndIf} ${EndUnless} ${EndIf} + +!ifdef MOZ_MAINTENANCE_SERVICE + ${If} $TmpVal == "HKLM" + ; Add the registry keys for allowed certificates. + ${AddMaintCertKeys} + ${EndIf} +!endif SectionEnd ; Cleanup operations to perform at the end of the installation. Section "-InstallEndCleanup" SetDetailsPrint both DetailPrint "$(STATUS_CLEANUP)" SetDetailsPrint none @@ -794,16 +841,68 @@ Function leaveShortcuts ${MUI_INSTALLOPTIONS_READ} $AddQuickLaunchSC "shortcuts.ini" "Field 4" "State" ${EndUnless} ${If} $InstallType == ${INSTALLTYPE_CUSTOM} Call CheckExistingInstall ${EndIf} FunctionEnd +!ifdef MOZ_MAINTENANCE_SERVICE +Function preComponents + ; If the service already exists, don't show this page + ServicesHelper::IsInstalled "MozillaMaintenance" + Pop $R9 + ${If} $R9 == 1 + ; The service already exists so don't show this page. + Abort + ${EndIf} + + ; On Windows 2000 we do not install the maintenance service. + ${Unless} ${AtLeastWinXP} + Abort + ${EndUnless} + + ; Don't show the custom components page if the + ; user is not an admin + Call IsUserAdmin + Pop $R9 + ${If} $R9 != "true" + Abort + ${EndIf} + + ; Only show the maintenance service page if we have write access to HKLM + ClearErrors + WriteRegStr HKLM "Software\Mozilla" \ + "${BrandShortName}InstallerTest" "Write Test" + ${If} ${Errors} + ClearErrors + Abort + ${Else} + DeleteRegValue HKLM "Software\Mozilla" "${BrandShortName}InstallerTest" + ${EndIf} + + StrCpy $PageName "Components" + ${CheckCustomCommon} + !insertmacro MUI_HEADER_TEXT "$(COMPONENTS_PAGE_TITLE)" "$(COMPONENTS_PAGE_SUBTITLE)" + !insertmacro MUI_INSTALLOPTIONS_DISPLAY "components.ini" +FunctionEnd + +Function leaveComponents + ${MUI_INSTALLOPTIONS_READ} $0 "components.ini" "Settings" "State" + ${If} $0 != 0 + Abort + ${EndIf} + ${MUI_INSTALLOPTIONS_READ} $InstallMaintenanceService "components.ini" "Field 2" "State" + ${If} $InstallType == ${INSTALLTYPE_CUSTOM} + Call CheckExistingInstall + ${EndIf} +FunctionEnd +!endif + Function preSummary StrCpy $PageName "Summary" ; Setup the summary.ini file for the Custom Summary Page WriteINIStr "$PLUGINSDIR\summary.ini" "Settings" NumFields "3" WriteINIStr "$PLUGINSDIR\summary.ini" "Field 1" Type "label" WriteINIStr "$PLUGINSDIR\summary.ini" "Field 1" Text "$(SUMMARY_INSTALLED_TO)" WriteINIStr "$PLUGINSDIR\summary.ini" "Field 1" Left "0" @@ -929,54 +1028,55 @@ Function .onInit StrCpy $PageName "" StrCpy $LANGUAGE 0 ${SetBrandNameVars} "$EXEDIR\core\distribution\setup.ini" ${InstallOnInitCommon} "$(WARN_MIN_SUPPORTED_OS_MSG)" !insertmacro InitInstallOptionsFile "options.ini" !insertmacro InitInstallOptionsFile "shortcuts.ini" + !insertmacro InitInstallOptionsFile "components.ini" !insertmacro InitInstallOptionsFile "summary.ini" WriteINIStr "$PLUGINSDIR\options.ini" "Settings" NumFields "5" WriteINIStr "$PLUGINSDIR\options.ini" "Field 1" Type "label" WriteINIStr "$PLUGINSDIR\options.ini" "Field 1" Text "$(OPTIONS_SUMMARY)" WriteINIStr "$PLUGINSDIR\options.ini" "Field 1" Left "0" WriteINIStr "$PLUGINSDIR\options.ini" "Field 1" Right "-1" WriteINIStr "$PLUGINSDIR\options.ini" "Field 1" Top "0" WriteINIStr "$PLUGINSDIR\options.ini" "Field 1" Bottom "10" WriteINIStr "$PLUGINSDIR\options.ini" "Field 2" Type "RadioButton" WriteINIStr "$PLUGINSDIR\options.ini" "Field 2" Text "$(OPTION_STANDARD_RADIO)" - WriteINIStr "$PLUGINSDIR\options.ini" "Field 2" Left "15" + WriteINIStr "$PLUGINSDIR\options.ini" "Field 2" Left "0" WriteINIStr "$PLUGINSDIR\options.ini" "Field 2" Right "-1" WriteINIStr "$PLUGINSDIR\options.ini" "Field 2" Top "25" WriteINIStr "$PLUGINSDIR\options.ini" "Field 2" Bottom "35" WriteINIStr "$PLUGINSDIR\options.ini" "Field 2" State "1" WriteINIStr "$PLUGINSDIR\options.ini" "Field 2" Flags "GROUP" WriteINIStr "$PLUGINSDIR\options.ini" "Field 3" Type "RadioButton" WriteINIStr "$PLUGINSDIR\options.ini" "Field 3" Text "$(OPTION_CUSTOM_RADIO)" - WriteINIStr "$PLUGINSDIR\options.ini" "Field 3" Left "15" + WriteINIStr "$PLUGINSDIR\options.ini" "Field 3" Left "0" WriteINIStr "$PLUGINSDIR\options.ini" "Field 3" Right "-1" WriteINIStr "$PLUGINSDIR\options.ini" "Field 3" Top "55" WriteINIStr "$PLUGINSDIR\options.ini" "Field 3" Bottom "65" WriteINIStr "$PLUGINSDIR\options.ini" "Field 3" State "0" WriteINIStr "$PLUGINSDIR\options.ini" "Field 4" Type "label" WriteINIStr "$PLUGINSDIR\options.ini" "Field 4" Text "$(OPTION_STANDARD_DESC)" - WriteINIStr "$PLUGINSDIR\options.ini" "Field 4" Left "30" + WriteINIStr "$PLUGINSDIR\options.ini" "Field 4" Left "15" WriteINIStr "$PLUGINSDIR\options.ini" "Field 4" Right "-1" WriteINIStr "$PLUGINSDIR\options.ini" "Field 4" Top "37" WriteINIStr "$PLUGINSDIR\options.ini" "Field 4" Bottom "57" WriteINIStr "$PLUGINSDIR\options.ini" "Field 5" Type "label" WriteINIStr "$PLUGINSDIR\options.ini" "Field 5" Text "$(OPTION_CUSTOM_DESC)" - WriteINIStr "$PLUGINSDIR\options.ini" "Field 5" Left "30" + WriteINIStr "$PLUGINSDIR\options.ini" "Field 5" Left "15" WriteINIStr "$PLUGINSDIR\options.ini" "Field 5" Right "-1" WriteINIStr "$PLUGINSDIR\options.ini" "Field 5" Top "67" WriteINIStr "$PLUGINSDIR\options.ini" "Field 5" Bottom "87" ; Setup the shortcuts.ini file for the Custom Shortcuts Page ; Don't offer to install the quick launch shortcut on Windows 7 ${If} ${AtLeastWin7} WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Settings" NumFields "3" @@ -988,42 +1088,61 @@ Function .onInit WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 1" Text "$(CREATE_ICONS_DESC)" WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 1" Left "0" WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 1" Right "-1" WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 1" Top "5" WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 1" Bottom "15" WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 2" Type "checkbox" WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 2" Text "$(ICONS_DESKTOP)" - WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 2" Left "15" + WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 2" Left "0" WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 2" Right "-1" WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 2" Top "20" WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 2" Bottom "30" WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 2" State "1" WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 2" Flags "GROUP" WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 3" Type "checkbox" WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 3" Text "$(ICONS_STARTMENU)" - WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 3" Left "15" + WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 3" Left "0" WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 3" Right "-1" WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 3" Top "40" WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 3" Bottom "50" WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 3" State "1" ; Don't offer to install the quick launch shortcut on Windows 7 ${Unless} ${AtLeastWin7} WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 4" Type "checkbox" WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 4" Text "$(ICONS_QUICKLAUNCH)" - WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 4" Left "15" + WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 4" Left "0" WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 4" Right "-1" WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 4" Top "60" WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 4" Bottom "70" WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 4" State "1" ${EndUnless} + ; Setup the components.ini file for the Components Page + WriteINIStr "$PLUGINSDIR\components.ini" "Settings" NumFields "2" + + WriteINIStr "$PLUGINSDIR\components.ini" "Field 1" Type "label" + WriteINIStr "$PLUGINSDIR\components.ini" "Field 1" Text "$(OPTIONAL_COMPONENTS_DESC)" + WriteINIStr "$PLUGINSDIR\components.ini" "Field 1" Left "0" + WriteINIStr "$PLUGINSDIR\components.ini" "Field 1" Right "-1" + WriteINIStr "$PLUGINSDIR\components.ini" "Field 1" Top "5" + WriteINIStr "$PLUGINSDIR\components.ini" "Field 1" Bottom "15" + + WriteINIStr "$PLUGINSDIR\components.ini" "Field 2" Type "checkbox" + WriteINIStr "$PLUGINSDIR\components.ini" "Field 2" Text "$(MAINTENANCE_SERVICE_CHECKBOX_DESC)" + WriteINIStr "$PLUGINSDIR\components.ini" "Field 2" Left "0" + WriteINIStr "$PLUGINSDIR\components.ini" "Field 2" Right "-1" + WriteINIStr "$PLUGINSDIR\components.ini" "Field 2" Top "20" + WriteINIStr "$PLUGINSDIR\components.ini" "Field 2" Bottom "30" + WriteINIStr "$PLUGINSDIR\components.ini" "Field 2" State "1" + WriteINIStr "$PLUGINSDIR\components.ini" "Field 2" Flags "GROUP" + ; There must always be a core directory. ${GetSize} "$EXEDIR\core\" "/S=0K" $R5 $R7 $R8 SectionSetSize ${APP_IDX} $R5 ; Initialize $hHeaderBitmap to prevent redundant changing of the bitmap if ; the user clicks the back button StrCpy $hHeaderBitmap "" FunctionEnd
new file mode 100644 --- /dev/null +++ b/browser/installer/windows/nsis/maintenanceservice_installer.nsi @@ -0,0 +1,283 @@ +# ***** BEGIN LICENSE BLOCK ***** +# Version: MPL 1.1/GPL 2.0/LGPL 2.1 +# +# The contents of this file are subject to the Mozilla Public License Version +# 1.1 (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# http://www.mozilla.org/MPL/ +# +# Software distributed under the License is distributed on an "AS IS" basis, +# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License +# for the specific language governing rights and limitations under the +# License. +# +# The Original Code is an NSIS installer for the maintenance service +# +# The Initial Developer of the Original Code is +# Mozilla Foundation. +# Portions created by the Initial Developer are Copyright (C) 2011 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Brian R. Bondy <netzen@gmail.com> +# +# Alternatively, the contents of this file may be used under the terms of +# either the GNU General Public License Version 2 or later (the "GPL"), or +# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), +# in which case the provisions of the GPL or the LGPL are applicable instead +# of those above. If you wish to allow use of your version of this file only +# under the terms of either the GPL or the LGPL, and not to allow others to +# use your version of this file under the terms of the MPL, indicate your +# decision by deleting the provisions above and replace them with the notice +# and other provisions required by the GPL or the LGPL. If you do not delete +# the provisions above, a recipient may use your version of this file under +# the terms of any one of the MPL, the GPL or the LGPL. +# +# ***** END LICENSE BLOCK ***** + +# Required Plugins: +# ServicesHelper Mozilla specific plugin that is located in /other-licenses/nsis + +; Set verbosity to 3 (e.g. no script) to lessen the noise in the build logs +!verbose 3 + +; 7-Zip provides better compression than the lzma from NSIS so we add the files +; uncompressed and use 7-Zip to create a SFX archive of it +SetDatablockOptimize on +SetCompress off +CRCCheck on + +RequestExecutionLevel admin +!addplugindir ./ + +; Variables +Var TempMaintServiceName +Var BrandFullNameDA +Var BrandFullName + +; Other included files may depend upon these includes! +; The following includes are provided by NSIS. +!include FileFunc.nsh +!include LogicLib.nsh +!include MUI.nsh +!include WinMessages.nsh +!include WinVer.nsh +!include WordFunc.nsh + +!insertmacro GetOptions +!insertmacro GetParameters +!insertmacro GetSize + +; The test slaves use this fallback key to run tests. +; And anyone that wants to run tests themselves should already have +; this installed. +!define FallbackKey \ + "SOFTWARE\Mozilla\MaintenanceService\3932ecacee736d366d6436db0f55bce4" + +!define CompanyName "Mozilla Corporation" +!define BrandFullNameInternal "" + +; The following includes are custom. +!include defines.nsi +; We keep defines.nsi defined so that we get other things like +; the version number, but we redefine BrandFullName +!define MaintFullName "Mozilla Maintenance Service" +!undef BrandFullName +!define BrandFullName "${MaintFullName}" + +!include common.nsh +!include locales.nsi + +VIAddVersionKey "FileDescription" "${MaintFullName} Installer" +VIAddVersionKey "OriginalFilename" "maintenanceservice_installer.exe" + +Name "${MaintFullName}" +OutFile "maintenanceservice_installer.exe" + +; Get installation folder from registry if available +InstallDirRegKey HKLM "Software\Mozilla\MaintenanceService" "" + +SetOverwrite on + +!define MaintUninstallKey \ + "Software\Microsoft\Windows\CurrentVersion\Uninstall\MozillaMaintenanceService" + +; The HAVE_64BIT_OS define also means that we have an x64 build, +; not just an x64 OS. +!ifdef HAVE_64BIT_OS + ; See below, we actually abort the install for x64 builds currently. + InstallDir "$PROGRAMFILES64\${MaintFullName}\" +!else + InstallDir "$PROGRAMFILES32\${MaintFullName}\" +!endif +ShowUnInstDetails nevershow + +################################################################################ +# Modern User Interface - MUI + +!define MUI_ICON setup.ico +!define MUI_UNICON setup.ico +!define MUI_WELCOMEPAGE_TITLE_3LINES +!define MUI_UNWELCOMEFINISHPAGE_BITMAP wizWatermark.bmp + +;Interface Settings +!define MUI_ABORTWARNING + +; Uninstaller Pages +!insertmacro MUI_UNPAGE_WELCOME +!insertmacro MUI_UNPAGE_INSTFILES +!insertmacro MUI_UNPAGE_FINISH + +################################################################################ +# Language + +!insertmacro MOZ_MUI_LANGUAGE 'baseLocale' +!verbose push +!verbose 3 +!include "overrideLocale.nsh" +!include "customLocale.nsh" +!verbose pop + +; Set this after the locale files to override it if it is in the locale +; using " " for BrandingText will hide the "Nullsoft Install System..." branding +BrandingText " " + +Function .onInit + SetSilent silent +!ifdef HAVE_64BIT_OS + ; We plan to eventually enable 64bit native builds to use the maintenance + ; service, but for the initial release, to reduce testing and development, + ; 64-bit builds will not install the maintenanceservice. + Abort +!endif + + ; On Windows 2000 we do not install the maintenance service. + ; We won't run this installer from the parent installer, but just in case + ; someone tries to execute it on Windows 2000... + ${Unless} ${AtLeastWinXP} + Abort + ${EndUnless} +FunctionEnd + +Function un.onInit + StrCpy $BrandFullNameDA "${MaintFullName}" + StrCpy $BrandFullName "${MaintFullName}" +FunctionEnd + +Section "MaintenanceService" + AllowSkipFiles off + + CreateDirectory $INSTDIR + SetOutPath $INSTDIR + + ; If the service already exists, then stop it if it is running. + ServicesHelper::IsInstalled "MozillaMaintenance" + Pop $R9 + ${If} $R9 == 1 + ; Stop the maintenance service so we can overwrite any + ; binaries that it uses. + ServicesHelper::Stop "MozillaMaintenance" + ${EndIf} + + ; If we don't have maintenanceservice.exe already installed + ; then keep that name. If we do use maintenanceservice_tmp.exe + ; which will auto install itself when you call it with the install parameter. + StrCpy $TempMaintServiceName "maintenanceservice.exe" + IfFileExists "$INSTDIR\maintenanceservice.exe" 0 skipAlreadyExists + StrCpy $TempMaintServiceName "maintenanceservice_tmp.exe" + skipAlreadyExists: + + ; We always write out a copy and then decide whether to install it or + ; not via calling its 'install' cmdline which works by version comparison. + CopyFiles "$EXEDIR\maintenanceservice.exe" "$INSTDIR\$TempMaintServiceName" + + ; Install the application maintenance service. + ; If a service already exists, the command line parameter will stop the + ; service and only install itself if it is newer than the already installed + ; service. If successful it will remove the old maintenanceservice.exe + ; and replace it with maintenanceservice_tmp.exe. + ClearErrors + ${GetParameters} $0 + ${GetOptions} "$0" "/Upgrade" $0 + ${If} ${Errors} + nsExec::Exec '"$INSTDIR\$TempMaintServiceName" install' + ${Else} + ; The upgrade cmdline is the same as install except + ; It will fail if the service isn't already installed. + nsExec::Exec '"$INSTDIR\$TempMaintServiceName" upgrade' + ${EndIf} + + WriteUninstaller "$INSTDIR\Uninstall.exe" + WriteRegStr HKLM "${MaintUninstallKey}" "DisplayName" "${MaintFullName}" + WriteRegStr HKLM "${MaintUninstallKey}" "UninstallString" \ + '"$INSTDIR\uninstall.exe"' + WriteRegStr HKLM "${MaintUninstallKey}" "DisplayIcon" \ + "$INSTDIR\Uninstall.exe,0" + WriteRegStr HKLM "${MaintUninstallKey}" "DisplayVersion" "${AppVersion}" + WriteRegStr HKLM "${MaintUninstallKey}" "Publisher" "Mozilla" + WriteRegStr HKLM "${MaintUninstallKey}" "Comments" \ + "${BrandFullName} ${AppVersion} (${ARCH} ${AB_CD})" + ${GetSize} "$INSTDIR" "/S=0K" $R2 $R3 $R4 + WriteRegDWORD HKLM "${MaintUninstallKey}" "EstimatedSize" $R2 + + ; Write out that a maintenance service was attempted. + ; We do this because on upgrades we will check this value and we only + ; want to install once on the first upgrade to maintenance service. + ; Also write out that we are currently installed, preferences will check + ; this value to determine if we should show the service update pref. + ; Since the Maintenance service can be installed either x86 or x64, + ; always use the 64-bit registry for checking if an attempt was made. + SetRegView 64 + WriteRegDWORD HKLM "Software\Mozilla\MaintenanceService" "Attempted" 1 + WriteRegDWORD HKLM "Software\Mozilla\MaintenanceService" "Installed" 1 + + ; Included here for debug purposes only. + ; These keys are used to bypass the installation dir is a valid installation + ; check from the service so that tests can be run. + ; WriteRegStr HKLM "${FallbackKey}\0" "name" "Mozilla Corporation" + ; WriteRegStr HKLM "${FallbackKey}\0" "issuer" "Thawte Code Signing CA - G2" + SetRegView lastused + + # The Mozilla/updates directory will have an inherited permission + # which allows any user to write to it. Work items are written there. + SetShellVarContext all + CreateDirectory "$APPDATA\Mozilla\updates" +SectionEnd + +; By renaming before deleting we improve things slightly in case +; there is a file in use error. In this case a new install can happen. +Function un.RenameDelete + Pop $9 + ; If the .moz-delete file already exists previously, delete it + ; If it doesn't exist, the call is ignored. + ; We don't need to pass /REBOOTOK here since it was already marked that way + ; if it exists. + Delete "$9.moz-delete" + Rename "$9" "$9.moz-delete" + ${If} ${Errors} + Delete /REBOOTOK "$9" + ${Else} + Delete /REBOOTOK "$9.moz-delete" + ${EndIf} + ClearErrors +FunctionEnd + +Section "Uninstall" + ; Delete the service so that no updates will be attempted + nsExec::Exec '"$INSTDIR\maintenanceservice.exe" uninstall' + + Push "$INSTDIR\maintenanceservice.exe" + Call un.RenameDelete + Push "$INSTDIR\maintenanceservice_tmp.exe" + Call un.RenameDelete + Push "$INSTDIR\Uninstall.exe" + Call un.RenameDelete + RMDir /REBOOTOK "$INSTDIR" + + DeleteRegKey HKLM "${MaintUninstallKey}" + + SetRegView 64 + DeleteRegValue HKLM "Software\Mozilla\MaintenanceService" "Installed" + DeleteRegKey HKLM "${FallbackKey}\" + SetRegView lastused +SectionEnd
--- a/browser/installer/windows/nsis/shared.nsh +++ b/browser/installer/windows/nsis/shared.nsh @@ -14,16 +14,17 @@ # The Original Code is the Mozilla Installer code. # # The Initial Developer of the Original Code is Mozilla Foundation # Portions created by the Initial Developer are Copyright (C) 2006 # the Initial Developer. All Rights Reserved. # # Contributor(s): # Robert Strong <robert.bugzilla@gmail.com> +# Brian R. Bondy <netzen@gmail.com> # # Alternatively, the contents of this file may be used under the terms of # either the GNU General Public License Version 2 or later (the "GPL"), or # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), # in which case the provisions of the GPL or the LGPL are applicable instead # of those above. If you wish to allow use of your version of this file only # under the terms of either the GPL or the LGPL, and not to allow others to # use your version of this file under the terms of the MPL, indicate your @@ -100,16 +101,56 @@ ${FixClassKeys} ${SetUninstallKeys} ; Remove files that may be left behind by the application in the ; VirtualStore directory. ${CleanVirtualStore} ${RemoveDeprecatedFiles} + +!ifdef MOZ_MAINTENANCE_SERVICE + Call IsUserAdmin + Pop $R0 + ${If} $R0 == "true" + ; Only proceed if we have HKLM write access + ${AndIf} $TmpVal == "HKLM" + ; On Windows 2000 we do not install the maintenance service. + ${AndIf} ${AtLeastWinXP} + ; Add the registry keys for allowed certificates. + ${AddMaintCertKeys} + + ; We check to see if the maintenance service install was already attempted. + ; Since the Maintenance service can be installed either x86 or x64, + ; always use the 64-bit registry for checking if an attempt was made. + SetRegView 64 + ReadRegDWORD $5 HKLM "Software\Mozilla\MaintenanceService" "Attempted" + ClearErrors + SetRegView lastused + + ; If the maintenance service is already installed, do nothing. + ; The maintenance service will launch: + ; maintenanceservice_installer.exe /Upgrade to upgrade the maintenance + ; service if necessary. If the update was done from updater.exe without + ; the service (i.e. service is failing), updater.exe will do the update of + ; the service. The reasons we do not do it here is because we don't want + ; to have to prompt for limited user accounts when the service isn't used + ; and we currently call the PostUpdate twice, once for the user and once + ; for the SYSTEM account. Also, this would stop the maintenance service + ; and we need a return result back to the service when run that way. + ${If} $5 == "" + ; An install of maintenance service was never attempted. + ; We call ExecShell (which is ShellExecute) with the verb "runas" + ; to ask for elevation if the user isn't already elevated. If the user + ; is already elevated it will just launch the program. + ExecShell "runas" "$INSTDIR\maintenanceservice_installer.exe" + ${EndIf} + ${EndIf} +!endif + !macroend !define PostUpdate "!insertmacro PostUpdate" !macro SetAsDefaultAppGlobal ${RemoveDeprecatedKeys} SetShellVarContext all ; Set SHCTX to all users (e.g. HKLM) ${SetHandlers} @@ -432,48 +473,61 @@ !macroend !define SetAppKeys "!insertmacro SetAppKeys" ; Add uninstall registry entries. This macro tests for write access to determine ; if the uninstall keys should be added to HKLM or HKCU. !macro SetUninstallKeys StrCpy $0 "Software\Microsoft\Windows\CurrentVersion\Uninstall\${BrandFullNameInternal} ${AppVersion} (${ARCH} ${AB_CD})" + StrCpy $2 "" + ClearErrors WriteRegStr HKLM "$0" "${BrandShortName}InstallerTest" "Write Test" ${If} ${Errors} - StrCpy $1 "HKCU" - SetShellVarContext current ; Set SHCTX to the current user (e.g. HKCU) + ; If the uninstall keys already exist in HKLM don't create them in HKCU + ClearErrors + ReadRegStr $2 "HKLM" $0 "DisplayName" + ${If} $2 == "" + ; Otherwise we don't have any keys for this product in HKLM so proceeed + ; to create them in HKCU. Better handling for this will be done in: + ; Bug 711044 - Better handling for 2 uninstall icons + StrCpy $1 "HKCU" + SetShellVarContext current ; Set SHCTX to the current user (e.g. HKCU) + ${EndIf} + ClearErrors ${Else} StrCpy $1 "HKLM" SetShellVarContext all ; Set SHCTX to all users (e.g. HKLM) DeleteRegValue HKLM "$0" "${BrandShortName}InstallerTest" ${EndIf} - ${GetLongPath} "$INSTDIR" $8 + ${If} $2 == "" + ${GetLongPath} "$INSTDIR" $8 - ; Write the uninstall registry keys - ${WriteRegStr2} $1 "$0" "Comments" "${BrandFullNameInternal} ${AppVersion} (${ARCH} ${AB_CD})" 0 - ${WriteRegStr2} $1 "$0" "DisplayIcon" "$8\${FileMainEXE},0" 0 - ${WriteRegStr2} $1 "$0" "DisplayName" "${BrandFullNameInternal} ${AppVersion} (${ARCH} ${AB_CD})" 0 - ${WriteRegStr2} $1 "$0" "DisplayVersion" "${AppVersion}" 0 - ${WriteRegStr2} $1 "$0" "InstallLocation" "$8" 0 - ${WriteRegStr2} $1 "$0" "Publisher" "Mozilla" 0 - ${WriteRegStr2} $1 "$0" "UninstallString" "$8\uninstall\helper.exe" 0 - ${WriteRegStr2} $1 "$0" "URLInfoAbout" "${URLInfoAbout}" 0 - ${WriteRegStr2} $1 "$0" "URLUpdateInfo" "${URLUpdateInfo}" 0 - ${WriteRegDWORD2} $1 "$0" "NoModify" 1 0 - ${WriteRegDWORD2} $1 "$0" "NoRepair" 1 0 + ; Write the uninstall registry keys + ${WriteRegStr2} $1 "$0" "Comments" "${BrandFullNameInternal} ${AppVersion} (${ARCH} ${AB_CD})" 0 + ${WriteRegStr2} $1 "$0" "DisplayIcon" "$8\${FileMainEXE},0" 0 + ${WriteRegStr2} $1 "$0" "DisplayName" "${BrandFullNameInternal} ${AppVersion} (${ARCH} ${AB_CD})" 0 + ${WriteRegStr2} $1 "$0" "DisplayVersion" "${AppVersion}" 0 + ${WriteRegStr2} $1 "$0" "InstallLocation" "$8" 0 + ${WriteRegStr2} $1 "$0" "Publisher" "Mozilla" 0 + ${WriteRegStr2} $1 "$0" "UninstallString" "$8\uninstall\helper.exe" 0 + ${WriteRegStr2} $1 "$0" "URLInfoAbout" "${URLInfoAbout}" 0 + ${WriteRegStr2} $1 "$0" "URLUpdateInfo" "${URLUpdateInfo}" 0 + ${WriteRegDWORD2} $1 "$0" "NoModify" 1 0 + ${WriteRegDWORD2} $1 "$0" "NoRepair" 1 0 - ${GetSize} "$8" "/S=0K" $R2 $R3 $R4 - ${WriteRegDWORD2} $1 "$0" "EstimatedSize" $R2 0 + ${GetSize} "$8" "/S=0K" $R2 $R3 $R4 + ${WriteRegDWORD2} $1 "$0" "EstimatedSize" $R2 0 - ${If} "$TmpVal" == "HKLM" - SetShellVarContext all ; Set SHCTX to all users (e.g. HKLM) - ${Else} - SetShellVarContext current ; Set SHCTX to the current user (e.g. HKCU) + ${If} "$TmpVal" == "HKLM" + SetShellVarContext all ; Set SHCTX to all users (e.g. HKLM) + ${Else} + SetShellVarContext current ; Set SHCTX to the current user (e.g. HKCU) + ${EndIf} ${EndIf} !macroend !define SetUninstallKeys "!insertmacro SetUninstallKeys" ; Add app specific handler registry entries under Software\Classes if they ; don't exist (does not use SHCTX). !macro FixClassKeys StrCpy $1 "SOFTWARE\Classes" @@ -542,16 +596,45 @@ ${IsHandlerForInstallDir} "https" $R9 ${If} "$R9" == "true" ${AddDDEHandlerValues} "https" "$2" "$8,1" "" "" \ "${DDEApplication}" "$3" "WWW_OpenURL" ${EndIf} !macroend !define UpdateProtocolHandlers "!insertmacro UpdateProtocolHandlers" +!ifdef MOZ_MAINTENANCE_SERVICE +; Adds maintenance service certificate keys for the install dir. +; For the cert to work, it must also be signed by a trusted cert for the user. +!macro AddMaintCertKeys + Push $R0 + ; Allow main Mozilla cert information for updates + ; This call will push the needed key on the stack + ServicesHelper::PathToUniqueRegistryPath "$INSTDIR" + Pop $R0 + ${If} $R0 != "" + ; More than one certificate can be specified in a different subfolder + ; for example: $R0\1, but each individual binary can be signed + ; with at most one certificate. A fallback certificate can only be used + ; if the binary is replaced with a different certificate. + ; We always use the 64bit registry for certs. + ; This call is ignored on 32-bit systems. + SetRegView 64 + DeleteRegKey HKLM "$R0" + WriteRegStr HKLM "$R0\0" "name" "${CERTIFICATE_NAME}" + WriteRegStr HKLM "$R0\0" "issuer" "${CERTIFICATE_ISSUER}" + SetRegView lastused + ClearErrors + ${EndIf} + ; Restore the previously used value back + Pop $R0 +!macroend +!define AddMaintCertKeys "!insertmacro AddMaintCertKeys" +!endif + ; Removes various registry entries for reasons noted below (does not use SHCTX). !macro RemoveDeprecatedKeys StrCpy $0 "SOFTWARE\Classes" ; Remove support for launching gopher urls from the shell during install or ; update if the DefaultIcon is from firefox.exe. ${RegCleanAppHandler} "gopher" ; Remove support for launching chrome urls from the shell during install or @@ -1015,17 +1098,16 @@ Function SetAsDefaultAppUserHKCU ; as the default browser. ClearErrors ReadRegStr $0 HKLM "Software\RegisteredApplications" "${AppRegName}" ${Unless} ${Errors} AppAssocReg::SetAppAsDefaultAll "${AppRegName}" ${EndUnless} ${EndIf} ${RemoveDeprecatedKeys} - ${PinToTaskBar} FunctionEnd ; Helper for updating the shortcut application model IDs. Function FixShortcutAppModelIDs ${If} ${AtLeastWin7} ${AndIf} "$AppUserModelID" != "" ${UpdateShortcutAppModelIDs} "$INSTDIR\${FileMainEXE}" "$AppUserModelID" $0
--- a/browser/installer/windows/nsis/uninstaller.nsi +++ b/browser/installer/windows/nsis/uninstaller.nsi @@ -56,16 +56,17 @@ RequestExecutionLevel user ; On Vista and above attempt to elevate Standard Users in addition to users that ; are a member of the Administrators group. !define NONADMIN_ELEVATE ; prevents compiling of the reg write logging. !define NO_LOG Var TmpVal +Var MaintCertKey ; Other included files may depend upon these includes! ; The following includes are provided by NSIS. !include FileFunc.nsh !include LogicLib.nsh !include MUI.nsh !include WinMessages.nsh !include WinVer.nsh @@ -91,16 +92,17 @@ VIAddVersionKey "OriginalFilename" "help !insertmacro AddDDEHandlerValues !insertmacro CleanVirtualStore !insertmacro ElevateUAC !insertmacro GetLongPath !insertmacro GetPathFromString !insertmacro InitHashAppModelId !insertmacro IsHandlerForInstallDir !insertmacro IsPinnedToTaskBar +!insertmacro IsUserAdmin !insertmacro LogDesktopShortcut !insertmacro LogQuickLaunchShortcut !insertmacro LogStartMenuShortcut !insertmacro PinnedToStartMenuLnkCount !insertmacro RegCleanAppHandler !insertmacro RegCleanMain !insertmacro RegCleanUninstall !insertmacro SetAppLSPCategories @@ -375,16 +377,31 @@ Section "Uninstall" FileClose $0 ${EndUnless} ${EndIf} ; Refresh desktop icons otherwise the start menu internet item won't be ; removed and other ugly things will happen like recreation of the app's ; clients registry key by the OS under some conditions. System::Call "shell32::SHChangeNotify(i, i, i, i) v (0x08000000, 0, 0, 0)" + +!ifdef MOZ_MAINTENANCE_SERVICE + ; Get the path the allowed cert is at and remove it + ; Keep this block of code last since it modfies the reg view + ServicesHelper::PathToUniqueRegistryPath "$INSTDIR" + Pop $MaintCertKey + ${If} $MaintCertKey != "" + ; We always use the 64bit registry for certs + ; This call is ignored on 32-bit systems. + SetRegView 64 + DeleteRegKey HKLM "$MaintCertKey\" + SetRegView lastused + ${EndIf} +!endif + SectionEnd ################################################################################ # Helper Functions ; Don't setup the survey controls, functions, etc. when the application has ; defined NO_UNINSTALL_SURVEY !ifndef NO_UNINSTALL_SURVEY
new file mode 100644 --- /dev/null +++ b/other-licenses/nsis/Contrib/ServicesHelper/Services.cpp @@ -0,0 +1,274 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is an NSIS plugin for managing Windows NT services. + * + * The Initial Developer of the Original Code is + * Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Brian R. Bondy <netzen@gmail.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include <windows.h> +#include "../../../../toolkit/components/maintenanceservice/pathhash.h" + +#pragma comment(lib, "advapi32.lib") + +typedef struct _stack_t { + struct _stack_t *next; + TCHAR text[MAX_PATH]; +} stack_t; + +int popstring(stack_t **stacktop, LPTSTR str, int len); +void pushstring(stack_t **stacktop, LPCTSTR str, int len); + +/** + * Determines if the specified service exists or not + * + * @param serviceName The name of the service to check + * @param exists Whether or not the service exists + * @return TRUE if there were no errors + */ +static BOOL +IsServiceInstalled(LPCWSTR serviceName, BOOL &exists) +{ + exists = FALSE; + + // Get a handle to the local computer SCM database with full access rights. + SC_HANDLE serviceManager = OpenSCManager(NULL, NULL, + SC_MANAGER_ENUMERATE_SERVICE); + if (!serviceManager) { + return FALSE; + } + + SC_HANDLE serviceHandle = OpenServiceW(serviceManager, + serviceName, + SERVICE_QUERY_CONFIG); + if (!serviceHandle && GetLastError() != ERROR_SERVICE_DOES_NOT_EXIST) { + CloseServiceHandle(serviceManager); + return FALSE; + } + + if (serviceHandle) { + CloseServiceHandle(serviceHandle); + exists = TRUE; + } + + CloseServiceHandle(serviceManager); + return TRUE; +} + +/** + * Determines if the specified service is installed or not + * + * @param stacktop A pointer to the top of the stack + * @param variables A pointer to the NSIS variables + * @return 0 if the service does not exist + * 1 if the service does exist + * -1 if there was an error. + */ +extern "C" void __declspec(dllexport) +IsInstalled(HWND hwndParent, int string_size, + TCHAR *variables, stack_t **stacktop, void *extra) +{ + TCHAR tmp[MAX_PATH] = { L'\0' }; + WCHAR serviceName[MAX_PATH] = { '\0' }; + popstring(stacktop, tmp, MAX_PATH); + +#if !defined(UNICODE) + MultiByteToWideChar(CP_ACP, 0, tmp, -1, serviceName, MAX_PATH); +#else + wcscpy(serviceName, tmp); +#endif + + BOOL serviceInstalled; + if (!IsServiceInstalled(serviceName, serviceInstalled)) { + pushstring(stacktop, TEXT("-1"), 3); + } else { + pushstring(stacktop, serviceInstalled ? TEXT("1") : TEXT("0"), 2); + } +} + +/** + * Stops the specified service. + * + * @param serviceName The name of the service to stop + * @return TRUE if the operation was successful + */ +static BOOL +StopService(LPCWSTR serviceName) +{ + // Get a handle to the local computer SCM database with full access rights. + SC_HANDLE serviceManager = OpenSCManager(NULL, NULL, + SC_MANAGER_ENUMERATE_SERVICE); + if (!serviceManager) { + return FALSE; + } + + SC_HANDLE serviceHandle = OpenServiceW(serviceManager, + serviceName, + SERVICE_STOP); + if (!serviceHandle) { + CloseServiceHandle(serviceManager); + 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 + BOOL stopped = FALSE; + if (ControlService(serviceHandle, SERVICE_CONTROL_STOP, &status)) { + do { + Sleep(status.dwWaitHint); + // + 10 milliseconds to make sure we always approach maxWaitTime + totalWaitTime += (status.dwWaitHint + 10); + if (status.dwCurrentState == SERVICE_STOPPED) { + stopped = true; + break; + } else if (totalWaitTime > maxWaitTime) { + break; + } + } while (QueryServiceStatus(serviceHandle, &status)); + } + + CloseServiceHandle(serviceHandle); + CloseServiceHandle(serviceManager); + return stopped; +} + +/** + * Stops the specified service + * + * @param stacktop A pointer to the top of the stack + * @param variables A pointer to the NSIS variables + * @return 1 if the service was stopped, 0 on error + */ +extern "C" void __declspec(dllexport) +Stop(HWND hwndParent, int string_size, + TCHAR *variables, stack_t **stacktop, void *extra) +{ + TCHAR tmp[MAX_PATH] = { L'\0' }; + WCHAR serviceName[MAX_PATH] = { '\0' }; + + popstring(stacktop, tmp, MAX_PATH); + +#if !defined(UNICODE) + MultiByteToWideChar(CP_ACP, 0, tmp, -1, serviceName, MAX_PATH); +#else + wcscpy(serviceName, tmp); +#endif + + if (StopService(serviceName)) { + pushstring(stacktop, TEXT("1"), 2); + } else { + pushstring(stacktop, TEXT("0"), 2); + } +} + +/** + * Determines a unique registry path from a file or directory path + * + * @param stacktop A pointer to the top of the stack + * @param variables A pointer to the NSIS variables + * @return The unique registry path or an empty string on error + */ +extern "C" void __declspec(dllexport) +PathToUniqueRegistryPath(HWND hwndParent, int string_size, + TCHAR *variables, stack_t **stacktop, + void *extra) +{ + TCHAR tmp[MAX_PATH] = { L'\0' }; + WCHAR installBasePath[MAX_PATH] = { '\0' }; + popstring(stacktop, tmp, MAX_PATH); + +#if !defined(UNICODE) + MultiByteToWideChar(CP_ACP, 0, tmp, -1, installBasePath, MAX_PATH); +#else + wcscpy(installBasePath, tmp); +#endif + + WCHAR registryPath[MAX_PATH + 1] = { '\0' }; + if (CalculateRegistryPathFromFilePath(installBasePath, registryPath)) { + pushstring(stacktop, registryPath, wcslen(registryPath) + 1); + } else { + pushstring(stacktop, TEXT(""), 1); + } +} + +BOOL WINAPI +DllMain(HANDLE hInst, ULONG ul_reason_for_call, LPVOID lpReserved) +{ + return TRUE; +} + +/** + * Removes an element from the top of the NSIS stack + * + * @param stacktop A pointer to the top of the stack + * @param str The string to pop to + * @param len The max length + * @return 0 on success +*/ +int popstring(stack_t **stacktop, TCHAR *str, int len) +{ + // Removes the element from the top of the stack and puts it in the buffer + stack_t *th; + if (!stacktop || !*stacktop) { + return 1; + } + + th = (*stacktop); + lstrcpyn(str,th->text, len); + *stacktop = th->next; + GlobalFree((HGLOBAL)th); + return 0; +} + +/** + * Adds an element to the top of the NSIS stack + * + * @param stacktop A pointer to the top of the stack + * @param str The string to push on the stack + * @param len The length of the string to push on the stack + * @return 0 on success +*/ +void pushstring(stack_t **stacktop, const TCHAR *str, int len) +{ + stack_t *th; + if (!stacktop) { + return; + } + + th = (stack_t*)GlobalAlloc(GPTR, sizeof(stack_t) + len); + lstrcpyn(th->text, str, len); + th->next = *stacktop; + *stacktop = th; +}
new file mode 100644 --- /dev/null +++ b/other-licenses/nsis/Contrib/ServicesHelper/ServicesHelper.dsp @@ -0,0 +1,115 @@ +# Microsoft Developer Studio Project File - Name="ServicesHelper" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102 + +CFG=ServicesHelper - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "ServicesHelper.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "ServicesHelper.mak" CFG="ServicesHelper - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "ServicesHelper - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "ServicesHelper - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "ServicesHelper - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "EXDLL_EXPORTS" /YX /FD /c +# ADD CPP /nologo /MT /W3 /GX /O1 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_UNICODE" /D "UNICODE" /D "_USRDLL" /D "EXDLL_EXPORTS" /D _WIN32_WINNT=0x0400 /FR /YX /FD /c +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib MSVCRT.LIB /nologo /entry:"DllMain" /dll /machine:I386 /nodefaultlib /out:"../../Plugins/ServicesHelper.dll" /opt:nowin98 +# SUBTRACT LINK32 /pdb:none + +!ELSEIF "$(CFG)" == "ServicesHelper - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "EXDLL_EXPORTS" /YX /FD /GZ /c +# ADD CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "EXDLL_EXPORTS" /YX /FD /GZ /c +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /debug /machine:I386 /pdbtype:sept + +!ENDIF + +# Begin Target + +# Name "ServicesHelper - Win32 Release" +# Name "ServicesHelper - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=\toolkit\components\maintenanceservice\pathhash.cpp +# End Source File +# Begin Source File + +SOURCE=\toolkit\components\maintenanceservice\pathhash.h +# End Source File +# Begin Source File + +SOURCE=.\Services.cpp +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe" +# End Group +# End Target +# End Project
new file mode 100644 --- /dev/null +++ b/other-licenses/nsis/Contrib/ServicesHelper/ServicesHelper.dsw @@ -0,0 +1,29 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! + +############################################################################### + +Project: "ServicesHelper"=.\ServicesHelper.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ +}}} + +Package=<3> +{{{ +}}} + +############################################################################### +
new file mode 100644 --- /dev/null +++ b/other-licenses/nsis/Contrib/ServicesHelper/ServicesHelper.rc @@ -0,0 +1,99 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "afxres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""afxres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,0,0,0 + PRODUCTVERSION 1,0,0,0 + FILEFLAGSMASK 0x17L +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x4L + FILETYPE 0x2L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "FileDescription", "NSIS Plug-in for managing Windows services" + VALUE "FileVersion", "1, 0, 0, 0" + VALUE "InternalName", "ServicesHelper" + VALUE "LegalCopyright", "MPL 1.1+/GPL 2.0+/LGPL 2.1+" + VALUE "OriginalFilename", "ServicesHelper.dll" + VALUE "ProductName", "ServicesHelper" + VALUE "ProductVersion", "1, 0, 0, 0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED +
new file mode 100644 --- /dev/null +++ b/other-licenses/nsis/Contrib/ServicesHelper/ServicesHelper.sln @@ -0,0 +1,20 @@ + +Microsoft Visual Studio Solution File, Format Version 9.00 +# Visual Studio 2005 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ServicesHelper", "ServicesHelper.vcproj", "{A0D0AD52-1D8B-402E-92EF-81AE0C320BD7}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A0D0AD52-1D8B-402E-92EF-81AE0C320BD7}.Debug|Win32.ActiveCfg = Debug|Win32 + {A0D0AD52-1D8B-402E-92EF-81AE0C320BD7}.Debug|Win32.Build.0 = Debug|Win32 + {A0D0AD52-1D8B-402E-92EF-81AE0C320BD7}.Release|Win32.ActiveCfg = Release|Win32 + {A0D0AD52-1D8B-402E-92EF-81AE0C320BD7}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal
new file mode 100644 --- /dev/null +++ b/other-licenses/nsis/Contrib/ServicesHelper/ServicesHelper.vcproj @@ -0,0 +1,212 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioProject + ProjectType="Visual C++" + Version="8.00" + Name="ServicesHelper" + ProjectGUID="{A0D0AD52-1D8B-402E-92EF-81AE0C320BD7}" + RootNamespace="ServicesHelper" + Keyword="Win32Proj" + > + <Platforms> + <Platform + Name="Win32" + /> + </Platforms> + <ToolFiles> + </ToolFiles> + <Configurations> + <Configuration + Name="Debug|Win32" + OutputDirectory="../../Plugins" + IntermediateDirectory="$(ConfigurationName)" + ConfigurationType="2" + CharacterSet="1" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + Optimization="0" + AdditionalIncludeDirectories="../../../../toolkit/components/maintenanceservice/" + PreprocessorDefinitions="WIN32;_DEBUG;_WINDOWS;_USRDLL;APPLICATIONID_EXPORTS" + MinimalRebuild="true" + BasicRuntimeChecks="3" + RuntimeLibrary="3" + UsePrecompiledHeader="0" + WarningLevel="3" + Detect64BitPortabilityProblems="true" + DebugInformationFormat="4" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLinkerTool" + LinkIncremental="2" + GenerateDebugInformation="true" + SubSystem="2" + TargetMachine="1" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCManifestTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCAppVerifierTool" + /> + <Tool + Name="VCWebDeploymentTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + <Configuration + Name="Release|Win32" + OutputDirectory="../../Plugins" + IntermediateDirectory="$(ConfigurationName)" + ConfigurationType="2" + ATLMinimizesCRunTimeLibraryUsage="true" + CharacterSet="1" + WholeProgramOptimization="1" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + AdditionalIncludeDirectories="../../../../toolkit/components/maintenanceservice/" + PreprocessorDefinitions="WIN32;NDEBUG;_WINDOWS;_USRDLL;APPLICATIONID_EXPORTS" + RuntimeLibrary="0" + UsePrecompiledHeader="0" + WarningLevel="3" + Detect64BitPortabilityProblems="true" + DebugInformationFormat="3" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLinkerTool" + LinkIncremental="1" + GenerateDebugInformation="true" + SubSystem="2" + OptimizeReferences="2" + EnableCOMDATFolding="2" + TargetMachine="1" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCManifestTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCAppVerifierTool" + /> + <Tool + Name="VCWebDeploymentTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + </Configurations> + <References> + </References> + <Files> + <Filter + Name="Source Files" + Filter="cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx" + UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}" + > + <File + RelativePath="..\..\..\..\toolkit\components\maintenanceservice\pathhash.cpp" + > + </File> + <File + RelativePath=".\Services.cpp" + > + </File> + </Filter> + <Filter + Name="Header Files" + Filter="h;hpp;hxx;hm;inl;inc;xsd" + UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}" + > + <File + RelativePath=".\resource.h" + > + </File> + </Filter> + <Filter + Name="Resource Files" + Filter="rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav" + UniqueIdentifier="{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}" + > + <File + RelativePath=".\ServicesHelper.rc" + > + </File> + </Filter> + </Files> + <Globals> + </Globals> +</VisualStudioProject>
new file mode 100644 --- /dev/null +++ b/other-licenses/nsis/Contrib/ServicesHelper/resource.h @@ -0,0 +1,14 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by ServicesHelper.rc + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 101 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif
new file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..152bd357727a3e9754157a55e3414017cdaec797 GIT binary patch literal 4608 zc%1E5Z){sv6+h2$T_^KWkJ+e0sdy=lHX&vbzc^WGOY_fZB9inaFY!yeH0i7JQrmj9 zz32DZroz%PV+r%HG)0JMd|+ROCec-VnNXS7Fl3_ZU{fWwGKnu8Op`|f3apAe7Wz2% zzUMf!QnzpIhu-$wd(XMQb9{g29^YJ#9(xuV0Kkc2ngEx;YCZ7zt&GzA)mNM0mBzPQ zFEJx;w@$_rSvI4lPpQ&rHYTM~X^lN8v#OqA6DfA!h`^ps&&$CFA8ZQPF(S^s-!k$y zmh1BP@*_CL&L3KK;rQ|SN0%Gu_}H=o$B9HNPH?q7@DYF!#tGxWt7A3Wb!cvVfN=rf ze{fs2*L>J{QCO;a>D*!Q2B?p~*A<5irP!^}8rJs$<Z;Yky!ksN2@^T_1#)>?!?>9{ zEC)6DoQC7<4tql`*hXS2#EGCfFKH4$w!tz`Ow(;!><L;hND@OkSmc{;!<B=otfXTA zKeP=L=e-Tr_xS|xw|k*Y8EfN$aoimhg-RdRlCgfYu%R7UFb1+W7Y@#V=2|%TP0*RK z(m<DkuRKB^RY9yY;Y6(5#A+%<+<Dmzd?~WYmz-U^STQhe{tdQJ7WJmedk8tol~)f{ z{(v(h=Xo8=%X`@wa54Z<vGNPFMk|*|Am?EW;^s|#v+^*8AmejR8Y;<5&&@u0WkoP% z0^X=-d^h0ZHuiO&VU2-+ch@hSE<QejQ3cZ<XTh6)!;@ce4({((ucOxrQp~S;gs2hr z;G)QxrASpwGNQ;AbJb#Q)4WzF>#e506ITy@85*D7RVd)cC*6o4P@VuZo$wRIc*E0V zXI0bhML61zi>B@|bvN$T^!qH^u%Y{s&S_&$0QJ)74RJim7b7<a<WIQ#b+fB-6~B+h zsJF<NT_Rt3hiDPpxL1st?Ma7?)SZ|(c3*B&Yoy^>47V>=)tW@QgBDb4nL(0EYd*}@ zLjGqr{);#Ht4qj1G~VLMrPnsl@W};SS^ErEjJFmp5bHlbqt*+h0&$3w^T6EuDlg+# z+OqMb0`cH0Ee+(HH@EZ-dcrM*@|@n7Y>1!0+^;^I&uzk`8Jy}nE=}W*bkJ}=auBSC zZV@ZZ6XiCN&S<pAnSLh{*#G-t#6yX8<DtwUGpqo=o30pPyt2IdL-yGOke=4anX5bS zNQ(PO-me*#k^XUuzSp8(y_NodYV_B))BpDt{p$bGzsL~!Rg3;QrMrh_#F~GNQs0f# zlMZcXobX-U5hr|!^Gi&fY<`UuqDf~G?^KJbv0m6H>YLiRB#SJg7E9y(!e8`8R0!48 z<E6-TKFMGq@3u<m+oEys084^b%|9EFYAMr{&wa>fZA&*#{2k!z9^!!T$1HgB7EW_D zt-d#BWl%7$k@~qd%av=T(&D1I`tZ|Ra`n-tcP<s;P|p={v}{|Y@h_;?;IE!RrCxxy z6x9;;RuO4??WM|Z(4mL7HS!5jnr`ovl_V37+ez*NJIPr5zw-dS4?G1^d(`O0yQ|gI z0>09qlgDwl>2D`Fc14RjP5%=(#X4)C-<cKS<wma{xm0}7Qr?57%qM$He-Aw?f%wxn zR*aoRSFwBc$+?w3nr%Eek+w<NPt$&g_A|6ULHiN**1wDQV%v-Fm?4xWP?}JxtpF92 z49<U!V;SXLlw*4>d=lpaC^IONC}&zN`zyHqKFYHwKOw#-ucN$U`z_lzXKVelzlU>7 zEgaZp`zg-x4u^Nyy6qVf1ND||xWAHpZ^PV8PG4aT%i4&P)gr2zRsnwO5ENO?!1tNM zO8TUv463q>53~j)tEsWfVoHQ1d!<()kE!oOCdMKoTqroNC;%57XR{eKk<u1Km=Yor zHG2a3)y0f9AuF<!mHX2v`U0G^tOGK}PA?8g**L&S%Z8gGIxpd492P7aF$|EG-ngar ztFolY)a{66>5V<5C)8Se5ug+Lm2_4XWc5rUhS1c!f^3GEBN;hm8xb{V>HVWpN;)O0 zXqjThby;1sA%Z4pdKMrIB(j=TY&$^jK(v=1uCe^saW<AsVV3aAbLbG)bF_Dw@15>B z2C&A=;gWJzg})IDfxs@rgb;&}VS@RHtSE8{;CaVs`E*vsC-SHe?Vp&mqFb%@TcHc@ za=gjt?;j$&Q4gTBqrCYLz$f?$x%?%7VbrHkQsnMO4+q$r&ko5-MpkL;;p}iKt4WF? z&jT-M@yYa5D)E#qPspbdcs7f~27;Ep)1&?_Gy%_j5UWUpUYLLg%)lt5;XGEGg8w9{ zOQ@wl0~vb>C5AHrMTIlCLbMqgs||t>f^&3EenOB}!sE2R-|pGwwSJ#w+pn}=ZU0C6 z10CP$80(nrxY+UQj!1|PiJ@HRxzG!tmqI<<VJ^<)xF2xOb1!mlavyLncYe^h*69nk ag}cHB!hPYP@K|^<d@OvoDcx^hy!{W$0+aXv
--- a/toolkit/Makefile.in +++ b/toolkit/Makefile.in @@ -50,22 +50,31 @@ PARALLEL_DIRS = \ content \ locales \ mozapps/downloads \ mozapps/extensions \ mozapps/handling \ mozapps/preferences \ mozapps/plugins \ mozapps/shared \ - mozapps/update \ obsolete \ profile \ themes \ $(NULL) +DIRS += \ + mozapps/update \ + $(NULL) + +ifdef MOZ_MAINTENANCE_SERVICE +DIRS += \ + components/maintenanceservice \ + $(NULL) +endif + ifneq (,$(filter gtk2 qt,$(MOZ_WIDGET_TOOLKIT))) PARALLEL_DIRS += system/unixproxy endif ifneq (,$(filter cocoa,$(MOZ_WIDGET_TOOLKIT))) PARALLEL_DIRS += system/osxproxy endif
new file mode 100644 --- /dev/null +++ b/toolkit/components/maintenanceservice/Makefile.in @@ -0,0 +1,100 @@ +# ***** BEGIN LICENSE BLOCK ***** +# Version: MPL 1.1/GPL 2.0/LGPL 2.1 +# +# The contents of this file are subject to the Mozilla Public License Version +# 1.1 (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# http://www.mozilla.org/MPL/ +# +# Software distributed under the License is distributed on an "AS IS" basis, +# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License +# for the specific language governing rights and limitations under the +# License. +# +# The Original Code is Mozilla maintenance service build. +# +# The Initial Developer of the Original Code is +# Mozilla Foundation. +# Portions created by the Initial Developer are Copyright (C) 2011 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Brian R. Bondy <netzen@gmail.com> +# +# Alternatively, the contents of this file may be used under the terms of +# either the GNU General Public License Version 2 or later (the "GPL"), or +# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), +# in which case the provisions of the GPL or the LGPL are applicable instead +# of those above. If you wish to allow use of your version of this file only +# under the terms of either the GPL or the LGPL, and not to allow others to +# use your version of this file under the terms of the MPL, indicate your +# decision by deleting the provisions above and replace them with the notice +# and other provisions required by the GPL or the LGPL. If you do not delete +# the provisions above, a recipient may use your version of this file under +# the terms of any one of the MPL, the GPL or the LGPL. +# +# ***** END LICENSE BLOCK ***** + +DEPTH = ../../.. +topsrcdir = @top_srcdir@ +srcdir = @srcdir@ +VPATH = @srcdir@ + +include $(DEPTH)/config/autoconf.mk + +CPPSRCS = \ + maintenanceservice.cpp \ + serviceinstall.cpp \ + workmonitor.cpp \ + certificatecheck.cpp \ + servicebase.cpp \ + registrycertificates.cpp \ + pathhash.cpp \ + $(NULL) + +# For debugging purposes only +#DEFINES += -DDISABLE_SERVICE_AUTHENTICODE_CHECK \ +# -DDISABLE_CALLBACK_AUTHENTICODE_CHECK + +PROGRAM = maintenanceservice$(BIN_SUFFIX) +DIST_PROGRAM = maintenanceservice$(BIN_SUFFIX) + +# Don't link the maintenanceservice against libmozutils. See bug 687139 +MOZ_UTILS_LDFLAGS = +MOZ_UTILS_PROGRAM_LDFLAGS = + +LIBS += \ + ../../mozapps/update/common/$(LIB_PREFIX)updatecommon.$(LIB_SUFFIX) \ + $(NULL) + +USE_STATIC_LIBS = 1 +HAVE_PROGRESSUI = 1 +RCINCLUDE = maintenanceservice.rc + +OS_LIBS += $(call EXPAND_LIBNAME,comctl32 ws2_32 shell32) +DEFINES += -DUNICODE -D_UNICODE +ifndef GNU_CC +RCFLAGS += -I$(srcdir) +else +RCFLAGS += --include-dir $(srcdir) +endif + +ifndef MOZ_WINCONSOLE +ifdef MOZ_DEBUG +MOZ_WINCONSOLE = 1 +else +MOZ_WINCONSOLE = 0 +endif +endif + +include $(topsrcdir)/config/rules.mk + +DEFINES += -DNS_NO_XPCOM + +ifdef _MSC_VER +WIN32_EXE_LDFLAGS += -ENTRY:wmainCRTStartup +endif + +# Pick up nsWindowsRestart.cpp +LOCAL_INCLUDES += -I$(topsrcdir)/toolkit/xre \ + -I$(topsrcdir)/toolkit/mozapps/update/common
new file mode 100644 --- /dev/null +++ b/toolkit/components/maintenanceservice/certificatecheck.cpp @@ -0,0 +1,301 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Maintenance service certificate check code. + * + * The Initial Developer of the Original Code is + * Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Brian R. Bondy <netzen@gmail.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include <stdio.h> +#include <stdlib.h> +#include <windows.h> +#include <softpub.h> +#include <wintrust.h> + +#include "certificatecheck.h" +#include "servicebase.h" + +#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 = NULL; + HCRYPTMSG cryptMsg = NULL; + PCCERT_CONTEXT certContext = NULL; + PCMSG_SIGNER_INFO signerInfo = NULL; + 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, NULL); + if (!result) { + lastError = GetLastError(); + LOG(("CryptQueryObject failed with %d\n", lastError)); + goto cleanup; + } + + // Pass in NULL to get the needed signer information size. + DWORD signerInfoSize; + result = CryptMsgGetParam(cryptMsg, CMSG_SIGNER_INFO_PARAM, 0, + NULL, &signerInfoSize); + if (!result) { + lastError = GetLastError(); + LOG(("CryptMsgGetParam failed with %d\n", lastError)); + goto cleanup; + } + + // Allocate the needed size for the signer information. + signerInfo = (PCMSG_SIGNER_INFO)LocalAlloc(LPTR, signerInfoSize); + if (!signerInfo) { + lastError = GetLastError(); + LOG(("Unable to allocate memory for Signer Info.\n")); + 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(("CryptMsgGetParam failed with %d\n", 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, NULL); + if (!certContext) { + lastError = GetLastError(); + LOG(("CertFindCertificateInStore failed with %d\n", lastError)); + goto cleanup; + } + + if (!DoCertificateAttributesMatch(certContext, infoToMatch)) { + lastError = ERROR_NOT_FOUND; + LOG(("Certificate did not match issuer or name\n")); + 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 = NULL; + + if (infoToMatch.issuer) { + // Pass in NULL to get the needed size of the issuer buffer. + dwData = CertGetNameString(certContext, + CERT_NAME_SIMPLE_DISPLAY_TYPE, + CERT_NAME_ISSUER_FLAG, NULL, + NULL, 0); + + if (!dwData) { + LOG(("CertGetNameString failed.\n")); + return FALSE; + } + + // Allocate memory for Issuer name buffer. + LPTSTR szName = (LPTSTR)LocalAlloc(LPTR, dwData * sizeof(WCHAR)); + if (!szName) { + LOG(("Unable to allocate memory for issuer name.\n")); + return FALSE; + } + + // Get Issuer name. + if (!CertGetNameString(certContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, + CERT_NAME_ISSUER_FLAG, NULL, szName, dwData)) { + LOG(("CertGetNameString failed.\n")); + 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 = NULL; + } + + if (infoToMatch.name) { + // Pass in NULL to get the needed size of the name buffer. + dwData = CertGetNameString(certContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, + 0, NULL, NULL, 0); + if (!dwData) { + LOG(("CertGetNameString failed.\n")); + return FALSE; + } + + // Allocate memory for the name buffer. + szName = (LPTSTR)LocalAlloc(LPTR, dwData * sizeof(WCHAR)); + if (!szName) { + LOG(("Unable to allocate memory for subject name.\n")); + return FALSE; + } + + // Obtain the name. + if (!(CertGetNameString(certContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, + NULL, szName, dwData))) { + LOG(("CertGetNameString failed.\n")); + 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 = NULL; + trustData.pSIPClientData = NULL; + trustData.dwUIChoice = WTD_UI_NONE; + trustData.fdwRevocationChecks = WTD_REVOKE_NONE; + trustData.dwUnionChoice = WTD_CHOICE_FILE; + trustData.dwStateAction = 0; + trustData.hWVTStateData = NULL; + trustData.pwszURLReference = NULL; + // 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(NULL, &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.\n", + filePath)); + return ERROR_SUCCESS; + } + + DWORD lastError = GetLastError(); + LOG(("There was an error validating trust of the certificate for file" + " \"%ls\". Returned: %d, Last error: %d\n", filePath, ret, lastError)); + return ret; +}
new file mode 100644 --- /dev/null +++ b/toolkit/components/maintenanceservice/certificatecheck.h @@ -0,0 +1,55 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Maintenance service certificate check code. + * + * The Initial Developer of the Original Code is + * Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Brian R. Bondy <netzen@gmail.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions /PGM and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#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
new file mode 100644 --- /dev/null +++ b/toolkit/components/maintenanceservice/maintenanceservice.cpp @@ -0,0 +1,379 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Maintenance service. + * + * The Initial Developer of the Original Code is + * Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Brian R. Bondy <netzen@gmail.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include <windows.h> +#include <shlwapi.h> +#include <stdio.h> +#include <wchar.h> + +#include "serviceinstall.h" +#include "maintenanceservice.h" +#include "servicebase.h" +#include "workmonitor.h" +#include "shlobj.h" + +SERVICE_STATUS gSvcStatus = { 0 }; +SERVICE_STATUS_HANDLE gSvcStatusHandle = NULL; +HANDLE ghSvcStopEvent = NULL; +BOOL gServiceStopping = FALSE; + +// logs are pretty small ~10 lines, so 5 seems reasonable. +#define LOGS_TO_KEEP 5 + +BOOL GetLogDirectoryPath(WCHAR *path); + +int +wmain(int argc, WCHAR **argv) +{ + // 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"); + } + + LOG(("Installing service")); + SvcInstallAction action = InstallSvc; + if (forceInstall) { + action = ForceInstallSvc; + LOG((" with force specified")); + } + LOG(("...\n")); + + if (!SvcInstall(action)) { + LOG(("Could not install service (%d)\n", GetLastError())); + LogFinish(); + return 1; + } + + LOG(("The service was installed successfully\n")); + 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...\n")); + if (!SvcInstall(UpgradeSvc)) { + LOG(("Could not upgrade service (%d)\n", GetLastError())); + LogFinish(); + return 1; + } + + LOG(("The service was upgraded successfully\n")); + 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...\n")); + if (!SvcUninstall()) { + LOG(("Could not uninstall service (%d)\n", GetLastError())); + LogFinish(); + return 1; + } + LOG(("The service was uninstalled successfully\n")); + LogFinish(); + return 0; + } + + SERVICE_TABLE_ENTRYW DispatchTable[] = { + { SVC_NAME, (LPSERVICE_MAIN_FUNCTION) SvcMain }, + { NULL, NULL } + }; + + // This call returns when the service has stopped. + // The process should simply terminate when the call returns. + if (!StartServiceCtrlDispatcher(DispatchTable)) { + LOG(("StartServiceCtrlDispatcher failed (%d)\n", GetLastError())); + } + + return 0; +} + +/** + * Wrapper callback for the monitoring thread. + * + * @param param Unused thread callback parameter + */ +DWORD +WINAPI StartMonitoringThreadProc(LPVOID param) +{ + StartDirectoryChangeMonitor(); + 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(NULL, CSIDL_COMMON_APPDATA, NULL, + 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, NULL); + + if (!PathAppendSafe(path, L"logs")) { + return FALSE; + } + CreateDirectoryW(path, NULL); + 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]; + wcscpy(path, basePath); + if (logNumber <= 0) { + swprintf(logName, L"maintenanceservice.log"); + } else { + swprintf(logName, 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 (!MoveFileEx(oldPath, newPath, MOVEFILE_REPLACE_EXISTING)) { + continue; + } + } +} + +/** + * Main entry point when running as a service. + */ +void WINAPI +SvcMain(DWORD dwArgc, LPWSTR *lpszArgv) +{ + // 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"); + } + + // Register the handler function for the service + gSvcStatusHandle = RegisterServiceCtrlHandlerW(SVC_NAME, SvcCtrlHandler); + if (!gSvcStatusHandle) { + LOG(("RegisterServiceCtrlHandler failed (%d)\n", GetLastError())); + return; + } + + // These SERVICE_STATUS members remain as set here + gSvcStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS; + gSvcStatus.dwServiceSpecificExitCode = 0; + + // Report initial status to the SCM + ReportSvcStatus(SERVICE_START_PENDING, NO_ERROR, 3000); + + // Perform service-specific initialization and work. + SvcInit(dwArgc, lpszArgv); +} + +/** + * Service initialization. + */ +void +SvcInit(DWORD dwArgc, LPWSTR *lpszArgv) +{ + // Create an event. The control handler function, SvcCtrlHandler, + // signals this event when it receives the stop control code. + ghSvcStopEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + if (NULL == ghSvcStopEvent) { + ReportSvcStatus(SERVICE_STOPPED, 1, 0); + return; + } + + DWORD threadID; + HANDLE thread = CreateThread(NULL, 0, StartMonitoringThreadProc, 0, + 0, &threadID); + + // Report running status when initialization is complete. + ReportSvcStatus(SERVICE_RUNNING, NO_ERROR, 0); + + // Perform work until service stops. + for(;;) { + // Check whether to stop the service. + WaitForSingleObject(ghSvcStopEvent, INFINITE); + + WCHAR stopFilePath[MAX_PATH +1]; + if (!GetUpdateDirectoryPath(stopFilePath)) { + LOG(("Could not obtain update directory path, terminating thread " + "forcefully.\n")); + TerminateThread(thread, 1); + } + + // The stop file is to wake up the synchronous call for watching directory + // changes. Directory watching will only actually be stopped if + // gServiceStopping is also set to TRUE. + gServiceStopping = TRUE; + if (!PathAppendSafe(stopFilePath, L"stop")) { + TerminateThread(thread, 2); + } + HANDLE stopFile = CreateFile(stopFilePath, GENERIC_READ, 0, + NULL, CREATE_ALWAYS, 0, NULL); + if (stopFile == INVALID_HANDLE_VALUE) { + LOG(("Could not create stop file, terminating thread forcefully.\n")); + TerminateThread(thread, 3); + } else { + CloseHandle(stopFile); + DeleteFile(stopFilePath); + } + + ReportSvcStatus(SERVICE_STOPPED, NO_ERROR, 0); + return; + } +} + +/** + * 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; + + // Fill in the SERVICE_STATUS structure. + gSvcStatus.dwCurrentState = currentState; + gSvcStatus.dwWin32ExitCode = exitCode; + gSvcStatus.dwWaitHint = waitHint; + + if (SERVICE_START_PENDING == currentState) { + gSvcStatus.dwControlsAccepted = 0; + } else { + gSvcStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP; + } + + 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); +} + +/** + * Called by SCM whenever a control code is sent to the service + * using the ControlService function. + */ +void WINAPI +SvcCtrlHandler(DWORD dwCtrl) +{ + // Handle the requested control code. + switch(dwCtrl) { + case SERVICE_CONTROL_STOP: + ReportSvcStatus(SERVICE_STOP_PENDING, NO_ERROR, 0); + // Signal the service to stop. + SetEvent(ghSvcStopEvent); + ReportSvcStatus(gSvcStatus.dwCurrentState, NO_ERROR, 0); + LogFinish(); + break; + case SERVICE_CONTROL_INTERROGATE: + break; + default: + break; + } +}
new file mode 100644 --- /dev/null +++ b/toolkit/components/maintenanceservice/maintenanceservice.exe.manifest @@ -0,0 +1,25 @@ +<?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="MaintenanceService" + type="win32" +/> +<description>MaintenanceService</description> +<ms_asmv3:trustInfo xmlns:ms_asmv3="urn:schemas-microsoft-com:asm.v3"> + <ms_asmv3:security> + <ms_asmv3:requestedPrivileges> + <ms_asmv3:requestedExecutionLevel level="requireAdministrator" uiAccess="false" /> + </ms_asmv3:requestedPrivileges> + </ms_asmv3:security> +</ms_asmv3:trustInfo> + <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1"> + <application> + <!--The ID below indicates application support for Windows Vista --> + <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/> + <!--The ID below indicates application support for Windows 7 --> + <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/> + </application> + </compatibility> +</assembly>
new file mode 100644 --- /dev/null +++ b/toolkit/components/maintenanceservice/maintenanceservice.h @@ -0,0 +1,43 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Maintenance service. + * + * The Initial Developer of the Original Code is + * Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Brian R. Bondy <netzen@gmail.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +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);
new file mode 100644 --- /dev/null +++ b/toolkit/components/maintenanceservice/maintenanceservice.rc @@ -0,0 +1,82 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" + +#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 "maintenanceservice.exe.manifest" + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO +BEGIN +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 +
new file mode 100644 --- /dev/null +++ b/toolkit/components/maintenanceservice/pathhash.cpp @@ -0,0 +1,170 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is for Maintenance service path hashing + * + * The Initial Developer of the Original Code is + * Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Brian R. Bondy <netzen@gmail.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions /PGM and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#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, NULL, NULL, PROV_RSA_FULL, 0)) { + if (NTE_BAD_KEYSET != GetLastError()) { + return FALSE; + } + + // Maybe it doesn't exist, try to create it. + if (!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, + 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]; + wcscpy(lowercasePath, filePath); + _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\\Mozilla\\" + L"MaintenanceService\\"; + wcsncpy(registryPath, baseRegPath, MAX_PATH); + BinaryDataToHexString(hash, hashSize, + registryPath + wcslen(baseRegPath)); + delete[] hash; + return TRUE; +}
new file mode 100644 --- /dev/null +++ b/toolkit/components/maintenanceservice/pathhash.h @@ -0,0 +1,52 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is for Maintenance service path hashing + * + * The Initial Developer of the Original Code is + * Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Brian R. Bondy <netzen@gmail.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions /PGM and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#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
new file mode 100644 --- /dev/null +++ b/toolkit/components/maintenanceservice/registrycertificates.cpp @@ -0,0 +1,168 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is for Maintenance service listing allowed certificates + * + * The Initial Developer of the Original Code is + * Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Brian R. Bondy <netzen@gmail.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include <stdio.h> +#include <stdlib.h> +#include <windows.h> + +#include "registrycertificates.h" +#include "pathhash.h" +#include "nsWindowsHelpers.h" +#include "servicebase.h" +#define MAX_KEY_LENGTH 255 + +/** + * 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; + LSTATUS retCode = RegOpenKeyExW(HKEY_LOCAL_MACHINE, + maintenanceServiceKey, 0, + KEY_READ | KEY_WOW64_64KEY, &baseKeyRaw); + if (retCode != ERROR_SUCCESS) { + LOG(("Could not open key. (%d)\n", 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, + L"SOFTWARE\\Mozilla\\MaintenanceService" + L"\\3932ecacee736d366d6436db0f55bce4", 0, + KEY_READ | KEY_WOW64_64KEY, &baseKeyRaw); + if (retCode != ERROR_SUCCESS) { + LOG(("Could not open fallback key. (%d)\n", retCode)); + return FALSE; + } + } + nsAutoRegKey baseKey(baseKeyRaw); + + // Get the number of subkeys. + DWORD subkeyCount = 0; + retCode = RegQueryInfoKeyW(baseKey, NULL, NULL, NULL, &subkeyCount, NULL, + NULL, NULL, NULL, NULL, NULL, NULL); + if (retCode != ERROR_SUCCESS) { + LOG(("Could not query info key: %d\n", 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, i, subkeyBuffer, + &subkeyBufferCount, NULL, + NULL, NULL, NULL); + if (retCode != ERROR_SUCCESS) { + LOG(("Could not enum Certs: %d\n", retCode)); + return FALSE; + } + + // Open the subkey for the current certificate + HKEY subKeyRaw; + retCode = RegOpenKeyExW(baseKey, + subkeyBuffer, + 0, + KEY_READ | KEY_WOW64_64KEY, + &subKeyRaw); + nsAutoRegKey subKey(subKeyRaw); + if (retCode != ERROR_SUCCESS) { + LOG(("Could not open subkey: %d\n", 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, L"name", 0, NULL, + (LPBYTE)name, &valueBufSize); + if (retCode != ERROR_SUCCESS) { + LOG(("Could not obtain name from registry: %d\n", retCode)); + continue; // Try the next subkey + } + + // Get the issuer from the registry + valueBufSize = MAX_CHAR_COUNT * sizeof(WCHAR); + retCode = RegQueryValueExW(subKey, L"issuer", 0, NULL, + (LPBYTE)issuer, &valueBufSize); + if (retCode != ERROR_SUCCESS) { + LOG(("Could not obtain issuer from registry: %d\n", retCode)); + continue; // Try the next subkey + } + + CertificateCheckInfo allowedCertificate = { + name, + issuer, + }; + + retCode = CheckCertificateForPEFile(filePath, allowedCertificate); + if (retCode != ERROR_SUCCESS) { + LOG(("Error on certificate check: %d\n", retCode)); + continue; // Try the next subkey + } + + retCode = VerifyCertificateTrustForFile(filePath); + if (retCode != ERROR_SUCCESS) { + LOG(("Error on certificate trust check: %d\n", retCode)); + continue; // Try the next subkey + } + + // Raise the roof, we found a match! + return TRUE; + } + + // No certificates match, :'( + return FALSE; +}
new file mode 100644 --- /dev/null +++ b/toolkit/components/maintenanceservice/registrycertificates.h @@ -0,0 +1,46 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is for Maintenance service listing allowed certificates + * + * The Initial Developer of the Original Code is + * Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Brian R. Bondy <netzen@gmail.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions /PGM and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef _REGISTRYCERTIFICATES_H_ +#define _REGISTRYCERTIFICATES_H_ + +#include "certificatecheck.h" + +BOOL DoesBinaryMatchAllowedCertificates(LPCWSTR basePathForUpdate, + LPCWSTR filePath); + +#endif
new file mode 100644 --- /dev/null +++ b/toolkit/components/maintenanceservice/resource.h @@ -0,0 +1,16 @@ +//{{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
new file mode 100644 --- /dev/null +++ b/toolkit/components/maintenanceservice/servicebase.cpp @@ -0,0 +1,41 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Maintenance service base code. + * + * The Initial Developer of the Original Code is + * Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Brian R. Bondy <netzen@gmail.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include "servicebase.h" + +// Shared code between applications and updater.exe +#include "nsWindowsRestart.cpp"
new file mode 100644 --- /dev/null +++ b/toolkit/components/maintenanceservice/servicebase.h @@ -0,0 +1,43 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Maintenance service base code. + * + * The Initial Developer of the Original Code is + * Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Brian R. Bondy <netzen@gmail.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include <windows.h> +#include "updatelogging.h" + +BOOL PathAppendSafe(LPWSTR base, LPCWSTR extra); + +#define SERVICE_EVENT_NAME L"Global\\moz-5b780de9-065b-4341-a04f-ddd94b3723e5"
new file mode 100644 --- /dev/null +++ b/toolkit/components/maintenanceservice/serviceinstall.cpp @@ -0,0 +1,416 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Maintenance service service installer code. + * + * The Initial Developer of the Original Code is + * Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Brian R. Bondy <netzen@gmail.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include <windows.h> +#include <aclapi.h> +#include <stdlib.h> + +#include <nsAutoPtr.h> +#include <nsWindowsHelpers.h> +#include <nsMemory.h> + +#include "serviceinstall.h" +#include "servicebase.h" +#include "shellapi.h" + +#pragma comment(lib, "version.lib") + +/** + * 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); + nsAutoArrayPtr<char> fileVersionInfo = new char[fileVersionInfoSize]; + if (!GetFileVersionInfoW(path, 0, fileVersionInfoSize, + fileVersionInfo.get())) { + LOG(("Could not obtain file info of old service. (%d)\n", + 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(("Could not query file version info of old service. (%d)\n", + GetLastError())); + return FALSE; + } + + A = HIWORD(fixedFileInfo->dwFileVersionMS); + B = LOWORD(fixedFileInfo->dwFileVersionMS); + C = HIWORD(fixedFileInfo->dwFileVersionLS); + D = LOWORD(fixedFileInfo->dwFileVersionLS); + return TRUE; +} + +/** + * Installs or upgrades the MozillaMaintenance 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. + nsAutoServiceHandle schSCManager(OpenSCManager(NULL, NULL, + SC_MANAGER_ALL_ACCESS)); + if (!schSCManager) { + LOG(("Could not open service manager. (%d)\n", GetLastError())); + return FALSE; + } + + WCHAR newServiceBinaryPath[MAX_PATH + 1]; + if (!GetModuleFileNameW(NULL, newServiceBinaryPath, + sizeof(newServiceBinaryPath) / + sizeof(newServiceBinaryPath[0]))) { + LOG(("Could not obtain module filename when attempting to " + "install service. (%d)\n", + GetLastError())); + return FALSE; + } + + // Check if we already have an open service + nsAutoServiceHandle schService(OpenServiceW(schSCManager, + 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(("Could not open service. (%d)\n", GetLastError())); + return FALSE; + } + + if (schService) { + // The service exists and we opened it + DWORD bytesNeeded; + if (!QueryServiceConfigW(schService, NULL, 0, &bytesNeeded) && + GetLastError() != ERROR_INSUFFICIENT_BUFFER) { + LOG(("Could not determine buffer size for query service config. (%d)\n", + GetLastError())); + return FALSE; + } + + // Get the service config information, in particular we want the binary + // path of the service. + nsAutoArrayPtr<char> serviceConfigBuffer = new char[bytesNeeded]; + if (!QueryServiceConfigW(schService, + reinterpret_cast<QUERY_SERVICE_CONFIGW*>(serviceConfigBuffer.get()), + bytesNeeded, &bytesNeeded)) { + LOG(("Could open service but could not query service config. (%d)\n", + GetLastError())); + return FALSE; + } + QUERY_SERVICE_CONFIGW &serviceConfig = + *reinterpret_cast<QUERY_SERVICE_CONFIGW*>(serviceConfigBuffer.get()); + + // 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(("Could not obtain version number from new path\n")); + return FALSE; + } + + schService.reset(); //Explicitly close the handle so we can delete it + + // 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)) { + if (!SvcUninstall()) { + return FALSE; + } + + if (!DeleteFile(serviceConfig.lpBinaryPathName)) { + LOG(("Could not delete old service binary file. (%d)\n", GetLastError())); + return FALSE; + } + + if (!CopyFile(newServiceBinaryPath, + serviceConfig.lpBinaryPathName, FALSE)) { + LOG(("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)\n", + GetLastError())); + return FALSE; + } + + // 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. + MoveFileEx(newServiceBinaryPath, NULL, MOVEFILE_DELAY_UNTIL_REBOOT); + + // Setup the new module path + wcsncpy(newServiceBinaryPath, serviceConfig.lpBinaryPathName, MAX_PATH); + // Fall through so we replace the service + } else { + // 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. + MoveFileEx(newServiceBinaryPath, NULL, MOVEFILE_DELAY_UNTIL_REBOOT); + + return TRUE; // nothing to do, we already have a newer service installed + } + } else if (UpgradeSvc == action) { + // The service does not exist and we are upgrading, so don't install it + return TRUE; + } + + // Create the service as on demand + schService.own(CreateServiceW(schSCManager, SVC_NAME, SVC_DISPLAY_NAME, + SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, + SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL, + newServiceBinaryPath, NULL, NULL, NULL, + NULL, NULL)); + if (!schService) { + LOG(("Could not create Windows service. " + "This error should never happen since a service install " + "should only be called when elevated. (%d)\n", GetLastError())); + return FALSE; + } + + if (!SetUserAccessServiceDACL(schService)) { + LOG(("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)\n", + GetLastError())); + } + + 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. + nsAutoServiceHandle schSCManager(OpenSCManager(NULL, NULL, + SC_MANAGER_ALL_ACCESS)); + if (!schSCManager) { + LOG(("Could not open service manager. (%d)\n", GetLastError())); + return FALSE; + } + + // Open the service + nsAutoServiceHandle schService(OpenServiceW(schSCManager, SVC_NAME, + SERVICE_ALL_ACCESS)); + if (!schService) { + LOG(("Could not open service. (%d)\n", GetLastError())); + return FALSE; + } + + SERVICE_STATUS status; + return ControlService(schService, SERVICE_CONTROL_STOP, &status); +} + +/** + * Uninstalls the Maintenance service. + * + * @return TRUE if successful. + */ +BOOL +SvcUninstall() +{ + // Get a handle to the local computer SCM database with full access rights. + nsAutoServiceHandle schSCManager(OpenSCManager(NULL, NULL, + SC_MANAGER_ALL_ACCESS)); + if (!schSCManager) { + LOG(("Could not open service manager. (%d)\n", GetLastError())); + return FALSE; + } + + // Open the service + nsAutoServiceHandle schService(OpenServiceW(schSCManager, SVC_NAME, + SERVICE_ALL_ACCESS)); + if (!schService) { + LOG(("Could not open service. (%d)\n", 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, 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, &status)); + } + + // Delete the service or mark it for deletion + BOOL deleted = DeleteService(schService); + 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 = NULL; + PSECURITY_DESCRIPTOR psd = NULL; + 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 TRUE 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) { + return GetLastError(); + } + + DWORD size = needed; + psd = (PSECURITY_DESCRIPTOR)LocalAlloc(LPTR, size); + if (!psd) { + return ERROR_INSUFFICIENT_BUFFER; + } + + // Get the actual security descriptor now + if (!QueryServiceObjectSecurity(hService, DACL_SECURITY_INFORMATION, + psd, size, &needed)) { + return GetLastError(); + } + } + + // Get the current DACL from the security descriptor. + PACL pacl = NULL; + BOOL bDaclPresent = FALSE; + BOOL bDaclDefaulted = FALSE; + if ( !GetSecurityDescriptorDacl(psd, &bDaclPresent, &pacl, + &bDaclDefaulted)) { + return GetLastError(); + } + + // Build the ACE. + EXPLICIT_ACCESS ea; + BuildExplicitAccessWithName(&ea, TEXT("Users"), + SERVICE_START | SERVICE_STOP | GENERIC_READ, + SET_ACCESS, NO_INHERITANCE); + DWORD lastError = SetEntriesInAcl(1, (PEXPLICIT_ACCESS)&ea, pacl, &pNewAcl); + if (ERROR_SUCCESS != lastError) { + return lastError; + } + + // Initialize a new security descriptor. + SECURITY_DESCRIPTOR sd; + if (!InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION)) { + return GetLastError(); + } + + // Set the new DACL in the security descriptor. + if (!SetSecurityDescriptorDacl(&sd, TRUE, pNewAcl, FALSE)) { + return GetLastError(); + } + + // Set the new security descriptor for the service object. + if (!SetServiceObjectSecurity(hService, DACL_SECURITY_INFORMATION, &sd)) { + return GetLastError(); + } + + // Woohoo, raise the roof + return ERROR_SUCCESS; +}
new file mode 100644 --- /dev/null +++ b/toolkit/components/maintenanceservice/serviceinstall.h @@ -0,0 +1,47 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Maintenance service service installer code. + * + * The Initial Developer of the Original Code is + * Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Brian R. Bondy <netzen@gmail.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#define SVC_NAME L"MozillaMaintenance" +#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);
new file mode 100644 --- /dev/null +++ b/toolkit/components/maintenanceservice/workmonitor.cpp @@ -0,0 +1,647 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Maintenance service file system monitoring. + * + * The Initial Developer of the Original Code is + * Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Brian R. Bondy <netzen@gmail.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include <shlobj.h> +#include <shlwapi.h> +#include <wtsapi32.h> +#include <userenv.h> +#include <shellapi.h> + +#pragma comment(lib, "wtsapi32.lib") +#pragma comment(lib, "userenv.lib") +#pragma comment(lib, "shlwapi.lib") + +#include "nsWindowsHelpers.h" +#include "nsAutoPtr.h" + +#include "workmonitor.h" +#include "serviceinstall.h" +#include "servicebase.h" +#include "registrycertificates.h" +#include "uachelper.h" +#include "launchwinprocess.h" + +extern BOOL gServiceStopping; + +// 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; +PRUnichar* MakeCommandLine(int argc, PRUnichar **argv); +BOOL WriteStatusFailure(LPCWSTR updateDirPath, int errorCode); +BOOL WriteStatusPending(LPCWSTR updateDirPath); +BOOL StartCallbackApp(int argcTmp, LPWSTR *argvTmp, DWORD callbackSessionID); +BOOL PathGetSiblingFilePath(LPWSTR destinationBuffer, LPCWSTR siblingFilePath, + LPCWSTR newFileName); + +// The error codes start from 16000 since Windows system error +// codes only go up to 15999 +const int SERVICE_UPDATER_COULD_NOT_BE_STARTED = 16000; +const int SERVICE_NOT_ENOUGH_COMMAND_LINE_ARGS = 16001; +const int SERVICE_UPDATER_SIGN_ERROR = 16002; +const int SERVICE_CALLBACK_SIGN_ERROR = 16003; + +/** + * Runs an update process in the specified sessionID as an elevated process. + * + * @param updaterPath The path to the update process to start. + * @param workingDir The working directory to execute the update process + * @param cmdLine in. The command line parameters to pass to the update + * process. If they specify a callback application, it + * will be executed with an associated unelevated token + * for the sessionID. + * @param processStarted Returns TRUE if the process was started. + * @param callbackSessionID + * If 0 and Windows Vista, the callback application will + * not be run. If non zero the callback application will + * be injected into the user's session as a non-elevated + * process. + * @return TRUE if the update process was run had a return code of 0. + */ +BOOL +StartUpdateProcess(LPCWSTR updaterPath, + LPCWSTR workingDir, + int argcTmp, + LPWSTR *argvTmp, + BOOL &processStarted, + DWORD callbackSessionID = 0) +{ + DWORD myProcessID = GetCurrentProcessId(); + DWORD mySessionID = 0; + ProcessIdToSessionId(myProcessID, &mySessionID); + + STARTUPINFO si = {0}; + si.cb = sizeof(STARTUPINFO); + si.lpDesktop = L"winsta0\\Default"; + PROCESS_INFORMATION pi = {0}; + + LOG(("Starting process in an elevated session. Service " + "session ID: %d; Requested callback session ID: %d\n", + mySessionID, callbackSessionID)); + + // The updater command line is of the form: + // updater.exe update-dir apply [wait-pid [callback-dir callback-path args]] + // So update callback-dir is the 4th, callback-path is the 5th and its args + // are the 6th index. So that we can execute the callback out of line we + // won't call updater.exe with those callback args and we will manage the + // callback ourselves. + LPWSTR cmdLine = MakeCommandLine(argcTmp, argvTmp); + + // 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 (argcTmp >= 2 ) { + // 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, argvTmp[0], L"updater.ini") && + PathGetSiblingFilePath(updaterINITemp, argvTmp[0], L"updater.tmp")) { + selfHandlePostUpdate = MoveFileEx(updaterINI, updaterINITemp, + MOVEFILE_REPLACE_EXISTING); + } + + // Create an environment block for the process we're about to start using + // the user's token. + WCHAR envVarString[32]; + wsprintf(envVarString, L"MOZ_SESSION_ID=%d", callbackSessionID); + _wputenv(envVarString); + LPVOID environmentBlock = NULL; + if (!CreateEnvironmentBlock(&environmentBlock, NULL, TRUE)) { + LOG(("Could not create an environment block, setting it to NULL.\n")); + environmentBlock = NULL; + } + // Empty value on _wputenv is how you remove an env variable in Windows + _wputenv(L"MOZ_SESSION_ID="); + processStarted = CreateProcessW(updaterPath, cmdLine, + NULL, NULL, FALSE, + CREATE_DEFAULT_ERROR_MODE | + CREATE_UNICODE_ENVIRONMENT, + environmentBlock, + workingDir, &si, &pi); + if (environmentBlock) { + DestroyEnvironmentBlock(environmentBlock); + } + BOOL updateWasSuccessful = FALSE; + if (processStarted) { + // Wait for the updater process to finish + LOG(("Process was started... waiting on result.\n")); + 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.\n", returnCode)); + // updater returns 0 if successful. + updateWasSuccessful = (returnCode == 0); + } else { + LOG(("Process finished but could not obtain return code.\n")); + } + } + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + } else { + DWORD lastError = GetLastError(); + LOG(("Could not create process as current user, " + "updaterPath: %ls; cmdLine: %l. (%d)\n", + updaterPath, 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) { + MoveFileEx(updaterINITemp, updaterINI, MOVEFILE_REPLACE_EXISTING); + + // Only run the PostUpdate if the update was successful and if we have + // a callback application. This is the same thing updater.exe does. + if (updateWasSuccessful && argcTmp > 5) { + LPCWSTR callbackApplication = argvTmp[5]; + LPCWSTR updateInfoDir = argvTmp[1]; + // Launch the PostUpdate process with SYSTEM in session 0. We force sync + // because we run this twice and we want to make sure the uninstaller + // keys get added to HKLM before the ones try to get added to HKCU. If + // we did it async we'd have a race condition that would sometimes lead + // to 2 uninstall icons. + LaunchWinPostProcess(callbackApplication, updateInfoDir, true, NULL); + nsAutoHandle userToken(UACHelper::OpenUserToken(callbackSessionID)); + LaunchWinPostProcess(callbackApplication, + updateInfoDir, false, userToken); + } + } + + free(cmdLine); + return updateWasSuccessful; +} + +/** + * Processes a work item (file by the name of .mz) and executes + * the command within. + * The only supported command at this time is running an update. + * + * @param monitoringBasePath The base path that is being monitored. + * @param notifyInfo the notifyInfo struct for the changes. + * @return TRUE if we want the service to stop. + */ +BOOL +ProcessWorkItem(LPCWSTR monitoringBasePath, + FILE_NOTIFY_INFORMATION ¬ifyInfo) +{ + int filenameLength = notifyInfo.FileNameLength / + sizeof(notifyInfo.FileName[0]); + notifyInfo.FileName[filenameLength] = L'\0'; + + // When the file is ready for processing it will be renamed + // to have a .mz extension + BOOL haveWorkItem = notifyInfo.Action == FILE_ACTION_RENAMED_NEW_NAME && + notifyInfo.FileNameLength > 3 && + notifyInfo.FileName[filenameLength - 3] == L'.' && + notifyInfo.FileName[filenameLength - 2] == L'm' && + notifyInfo.FileName[filenameLength - 1] == L'z'; + if (!haveWorkItem) { + // We don't have a work item, keep looking + return FALSE; + } + + // Indicate that the service is busy and shouldn't be used by anyone else + // by creating a named event. Programs should check if this event exists + // before writing a work item out. + nsAutoHandle serviceRunning(CreateEventW(NULL, TRUE, + FALSE, SERVICE_EVENT_NAME)); + + LOG(("Processing new command meta file: %ls\n", notifyInfo.FileName)); + WCHAR fullMetaUpdateFilePath[MAX_PATH + 1]; + wcscpy(fullMetaUpdateFilePath, monitoringBasePath); + + // We only support file paths in monitoring directories that are MAX_PATH + // chars or less. + if (!PathAppendSafe(fullMetaUpdateFilePath, notifyInfo.FileName)) { + // Cannot process update, skipfileSize it. + return TRUE; + } + + nsAutoHandle metaUpdateFile(CreateFile(fullMetaUpdateFilePath, + GENERIC_READ, + FILE_SHARE_READ | + FILE_SHARE_WRITE | + FILE_SHARE_DELETE, + NULL, + OPEN_EXISTING, + 0, NULL)); + if (metaUpdateFile == INVALID_HANDLE_VALUE) { + LOG(("Could not open command meta file: %ls\n", notifyInfo.FileName)); + return TRUE; + } + + DWORD fileSize = GetFileSize(metaUpdateFile, NULL); + DWORD sessionID = 0, commandID = 0; + // The file should be in wide characters so if it's of odd size it's + // an invalid file. + const int kSanityCheckFileSize = 1024 * 64; + if (fileSize == INVALID_FILE_SIZE || + (fileSize %2) != 0 || + fileSize > kSanityCheckFileSize || + fileSize < sizeof(DWORD)) { + LOG(("Could not obtain file size or an improper file size was encountered " + "for command meta file: %ls\n", + notifyInfo.FileName)); + return TRUE; + } + + // The first 4 bytes are for the command ID. + // Currently only command ID 1 which is for updates is supported. + DWORD commandIDCount; + BOOL result = ReadFile(metaUpdateFile, &commandID, + sizeof(DWORD), &commandIDCount, NULL); + fileSize -= sizeof(DWORD); + + // The next 4 bytes are for the process ID + DWORD sessionIDCount; + result |= ReadFile(metaUpdateFile, &sessionID, + sizeof(DWORD), &sessionIDCount, NULL); + fileSize -= sizeof(DWORD); + + // The next MAX_PATH wchar's are for the app to start + WCHAR updaterPath[MAX_PATH + 1]; + ZeroMemory(updaterPath, sizeof(updaterPath)); + DWORD updaterPathCount; + result |= ReadFile(metaUpdateFile, updaterPath, + MAX_PATH * sizeof(WCHAR), &updaterPathCount, NULL); + fileSize -= updaterPathCount; + + // The next MAX_PATH wchar's are for the app to start + WCHAR workingDirectory[MAX_PATH + 1]; + ZeroMemory(workingDirectory, sizeof(workingDirectory)); + DWORD workingDirectoryCount; + result |= ReadFile(metaUpdateFile, workingDirectory, + MAX_PATH * sizeof(WCHAR), &workingDirectoryCount, NULL); + fileSize -= workingDirectoryCount; + + // + 2 for wide char termination + nsAutoArrayPtr<char> cmdlineBuffer = new char[fileSize + 2]; + DWORD cmdLineBufferRead; + result |= ReadFile(metaUpdateFile, cmdlineBuffer, + fileSize, &cmdLineBufferRead, NULL); + fileSize -= cmdLineBufferRead; + + // We have all the info we need from the work item file, get rid of it. + metaUpdateFile.reset(); + if (!DeleteFileW(fullMetaUpdateFilePath)) { + LOG(("Could not delete work item file: %ls. (%d)\n", + fullMetaUpdateFilePath, GetLastError())); + } + + if (!result || + commandID != 1 || + commandIDCount != sizeof(DWORD) || + sessionIDCount != sizeof(DWORD) || + updaterPathCount != MAX_PATH * sizeof(WCHAR) || + workingDirectoryCount != MAX_PATH * sizeof(WCHAR) || + fileSize != 0) { + LOG(("Could not read command data for command meta file: %ls\n", + notifyInfo.FileName)); + return TRUE; + } + cmdlineBuffer[cmdLineBufferRead] = '\0'; + cmdlineBuffer[cmdLineBufferRead + 1] = '\0'; + WCHAR *cmdlineBufferWide = reinterpret_cast<WCHAR*>(cmdlineBuffer.get()); + LOG(("An update command was detected and is being processed for command meta " + "file: %ls\n", + notifyInfo.FileName)); + + int argcTmp = 0; + LPWSTR* argvTmp = CommandLineToArgvW(cmdlineBufferWide, &argcTmp); + + // Check for callback application sign problems + BOOL callbackSignProblem = FALSE; +#ifndef DISABLE_CALLBACK_AUTHENTICODE_CHECK + // If we have less than 6 params, then there is no callback to check, so + // we have no callback sign problem. + if (argcTmp > 5) { + LPWSTR callbackApplication = argvTmp[5]; + callbackSignProblem = + !DoesBinaryMatchAllowedCertificates(argvTmp[2], callbackApplication); + } +#endif + + // Check for updater.exe sign problems + BOOL updaterSignProblem = FALSE; +#ifndef DISABLE_SERVICE_AUTHENTICODE_CHECK + if (argcTmp > 2) { + updaterSignProblem = !DoesBinaryMatchAllowedCertificates(argvTmp[2], + updaterPath); + } +#endif + + // In order to proceed with the update we need at least 3 command line + // parameters and no sign problems. + if (argcTmp > 2 && !updaterSignProblem && !callbackSignProblem) { + BOOL updateProcessWasStarted = FALSE; + if (StartUpdateProcess(updaterPath, workingDirectory, + argcTmp, argvTmp, + updateProcessWasStarted, + sessionID)) { + LOG(("updater.exe was launched and run successfully!\n")); + StartServiceUpdate(argcTmp, argvTmp); + } else { + LOG(("Error running process in session %d. " + "Updating update.status. Last error: %d\n", + sessionID, GetLastError())); + + // If the update process was started, then updater.exe is responsible for + // setting the failure code and running the callback. 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(argvTmp[1], + SERVICE_UPDATER_COULD_NOT_BE_STARTED)) { + LOG(("Could not write update.status service update failure." + "Last error: %d\n", GetLastError())); + } + + // We only hit this condition when there is no callback sign error + // so we don't need to check it. + StartCallbackApp(argcTmp, argvTmp, sessionID); + } + } + } else if (argcTmp <= 2) { + LOG(("Not enough command line parameters specified. " + "Updating update.status.\n")); + + // We can't start the callback in this case because there + // are not enough command line parameters. 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 (argcTmp != 2 || + !WriteStatusFailure(argvTmp[1], + SERVICE_NOT_ENOUGH_COMMAND_LINE_ARGS)) { + LOG(("Could not write update.status service update failure." + "Last error: %d\n", GetLastError())); + } + } else if (callbackSignProblem) { + LOG(("Will not run updater nor callback due to callback sign error " + "in session %d. Updating update.status. Last error: %d\n", + sessionID, GetLastError())); + + // When there is a certificate check error on the callback application, we + // want to write out the error. + if (!WriteStatusFailure(argvTmp[1], + SERVICE_CALLBACK_SIGN_ERROR)) { + LOG(("Could not write pending state to update.status. (%d)\n", + GetLastError())); + } + } else { + LOG(("Could not start process due to certificate check error on " + "updater.exe. Updating update.status. Last error: %d\n", GetLastError())); + + // When there is a certificate check error on the updater.exe application, + // we want to write out the error. + if (!WriteStatusFailure(argvTmp[1], + SERVICE_UPDATER_SIGN_ERROR)) { + LOG(("Could not write pending state to update.status. (%d)\n", + GetLastError())); + } + + // On certificate check errors on updater.exe, updater.exe won't run at all + // so we need to manually start the callback application ourselves. + // This condition will only be hit when the callback app has no sign errors + StartCallbackApp(argcTmp, argvTmp, sessionID); + } + LocalFree(argvTmp); + + // We processed a work item, whether or not it was successful. + return TRUE; +} + +/** + * Starts monitoring the update directory for work items. + * + * @return FALSE if there was an error starting directory monitoring. + */ +BOOL +StartDirectoryChangeMonitor() +{ + LOG(("Starting directory change monitor...\n")); + + // Init the update directory path and ensure it exists. + // Example: C:\programData\Mozilla\Firefox\updates\[channel] + // The channel is not included here as we want to monitor the base directory + WCHAR updateData[MAX_PATH + 1]; + if (!GetUpdateDirectoryPath(updateData)) { + LOG(("Could not obtain update directory path\n")); + return FALSE; + } + + // Obtain a directory handle, FILE_FLAG_BACKUP_SEMANTICS is needed for this. + nsAutoHandle directoryHandle(CreateFile(updateData, + FILE_LIST_DIRECTORY, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, + NULL)); + if (directoryHandle == INVALID_HANDLE_VALUE) { + LOG(("Could not obtain directory handle to monitor\n")); + return FALSE; + } + + // Allocate a buffer that is big enough to hold 128 changes + // Note that FILE_NOTIFY_INFORMATION is a variable length struct + // so that's why we don't create a simple array directly. + char fileNotifyInfoBuffer[(sizeof(FILE_NOTIFY_INFORMATION) + + MAX_PATH) * 128]; + ZeroMemory(&fileNotifyInfoBuffer, sizeof(fileNotifyInfoBuffer)); + + DWORD bytesReturned; + // Listen for directory changes to the update directory + while (ReadDirectoryChangesW(directoryHandle, + fileNotifyInfoBuffer, + sizeof(fileNotifyInfoBuffer), + TRUE, FILE_NOTIFY_CHANGE_FILE_NAME, + &bytesReturned, NULL, NULL)) { + char *fileNotifyInfoBufferLocation = fileNotifyInfoBuffer; + + // Make sure we have at least one entry to process + while (bytesReturned/sizeof(FILE_NOTIFY_INFORMATION) > 0) { + // Point to the current directory info which is changed + FILE_NOTIFY_INFORMATION ¬ifyInfo = + *((FILE_NOTIFY_INFORMATION*)fileNotifyInfoBufferLocation); + fileNotifyInfoBufferLocation += notifyInfo .NextEntryOffset; + bytesReturned -= notifyInfo .NextEntryOffset; + if (!wcscmp(notifyInfo.FileName, L"stop") && gServiceStopping) { + LOG(("A stop command was issued.\n")); + return TRUE; + } + + BOOL processedWorkItem = ProcessWorkItem(updateData, notifyInfo); + if (processedWorkItem) { + // We don't return here because during stop we will write out a stop + // file which will stop monitoring. + StopService(); + break; + } + + // NextEntryOffset will be 0 if there are no more items to monitor, + // and we're ready to make another call to ReadDirectoryChangesW. + if (0 == notifyInfo.NextEntryOffset) { + break; + } + } + } + + return TRUE; +} + +/** + * Starts the callback application from the updater.exe command line arguments. + * + * @param argcTmp The argc value normally sent to updater.exe + * @param argvTmp The argv value normally sent to updater.exe + * @param callbackSessionID The session ID to start the callback with + * @return TRUE if successful + */ +BOOL +StartCallbackApp(int argcTmp, LPWSTR *argvTmp, DWORD callbackSessionID) +{ + if (0 == callbackSessionID && UACHelper::IsVistaOrLater()) { + LOG(("Attempted to run callback application in session 0, not allowed.\n")); + LOG(("Session 0 is only for services on Vista and up.\n")); + return FALSE; + } + + if (argcTmp < 5) { + LOG(("No callback application specified.\n")); + return FALSE; + } + + LPWSTR callbackArgs = NULL; + LPWSTR callbackDirectory = argvTmp[4]; + LPWSTR callbackApplication = argvTmp[5]; + if (argcTmp > 6) { + // Make room for all of the ending args minus the first 6 + // + 1 for the app itself + const size_t callbackCommandLineLen = argcTmp - 5; + WCHAR **params = new WCHAR*[callbackCommandLineLen]; + params[0] = callbackApplication; + for (size_t i = 1; i < callbackCommandLineLen; ++i) { + params[i] = argvTmp[5 + i]; + } + callbackArgs = MakeCommandLine(callbackCommandLineLen, params); + delete[] params; + } + + if (!callbackApplication) { + LOG(("Callback application is NULL.\n")); + if (callbackArgs) { + free(callbackArgs); + } + return FALSE; + } + + nsAutoHandle unelevatedToken(UACHelper::OpenUserToken(callbackSessionID)); + if (!unelevatedToken) { + LOG(("Could not obtain unelevated token for callback app.\n")); + if (callbackArgs) { + free(callbackArgs); + } + return FALSE; + } + + // Create an environment block for the process we're about to start using + // the user's token. + LPVOID environmentBlock = NULL; + if (!CreateEnvironmentBlock(&environmentBlock, unelevatedToken, TRUE)) { + LOG(("Could not create an environment block, setting it to NULL.\n")); + environmentBlock = NULL; + } + + STARTUPINFOW si = {0}; + si.cb = sizeof(STARTUPINFOW); + si.lpDesktop = L"winsta0\\Default"; + PROCESS_INFORMATION pi = {0}; + if (CreateProcessAsUserW(unelevatedToken, + callbackApplication, + callbackArgs, + NULL, NULL, FALSE, + CREATE_DEFAULT_ERROR_MODE | +#ifdef DEBUG + CREATE_NEW_CONSOLE | +#endif + CREATE_UNICODE_ENVIRONMENT, + environmentBlock, + callbackDirectory, + &si, &pi)) { + LOG(("Callback app was run successfully.\n")); + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + if (environmentBlock) { + DestroyEnvironmentBlock(environmentBlock); + } + if (callbackArgs) { + free(callbackArgs); + } + return TRUE; + } + + LOG(("Could not run callback app, last error: %d", GetLastError())); + if (environmentBlock) { + DestroyEnvironmentBlock(environmentBlock); + } + if (callbackArgs) { + free(callbackArgs); + } + return FALSE; +}
new file mode 100644 --- /dev/null +++ b/toolkit/components/maintenanceservice/workmonitor.h @@ -0,0 +1,41 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Maintenance service file system monitoring. + * + * The Initial Developer of the Original Code is + * Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Brian R. Bondy <netzen@gmail.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#define STOP_CMD L"stop" + +BOOL StartDirectoryChangeMonitor(); +BOOL GetUpdateDirectoryPath(LPWSTR);
--- a/toolkit/mozapps/installer/windows/nsis/common.nsh +++ b/toolkit/mozapps/installer/windows/nsis/common.nsh @@ -16,16 +16,17 @@ # The Initial Developer of the Original Code is Mozilla Foundation # Portions created by the Initial Developer are Copyright (C) 2006 # the Initial Developer. All Rights Reserved. # # Contributor(s): # Robert Strong <robert.bugzilla@gmail.com> # Ehsan Akhgari <ehsan.akhgari@gmail.com> # Amir Szekely <kichik@gmail.com> +# Brian R. Bondy <netzen@gmail.com> # # Alternatively, the contents of this file may be used under the terms of # either the GNU General Public License Version 2 or later (the "GPL"), or # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), # in which case the provisions of the GPL or the LGPL are applicable instead # of those above. If you wish to allow use of your version of this file only # under the terms of either the GPL or the LGPL, and not to allow others to # use your version of this file under the terms of the MPL, indicate your @@ -6675,16 +6676,50 @@ !verbose ${_MOZFUNC_VERBOSE} Push "${_APP_ID}" Push "${_EXE_PATH}" Call UpdateShortcutAppModelIDs Pop ${_RESULT} !verbose pop !macroend +!macro IsUserAdmin + ; Copied from: http://nsis.sourceforge.net/IsUserAdmin + Function IsUserAdmin + Push $R0 + Push $R1 + Push $R2 + + ClearErrors + UserInfo::GetName + IfErrors Win9x + Pop $R1 + UserInfo::GetAccountType + Pop $R2 + + StrCmp $R2 "Admin" 0 Continue + StrCpy $R0 "true" + Goto Done + + Continue: + + StrCmp $R2 "" Win9x + StrCpy $R0 "false" + Goto Done + + Win9x: + StrCpy $R0 "true" + + Done: + Pop $R2 + Pop $R1 + Exch $R0 + FunctionEnd +!macroend + /** * Retrieve if present or generate and store a 64 bit hash of an install path * using the City Hash algorithm. On return the resulting id is saved in the * $AppUserModelID variable declared by inserting this macro. InitHashAppModelId * will attempt to load from HKLM/_REG_PATH first, then HKCU/_REG_PATH. If found * in either it will return the hash it finds. If not found it will generate a * new hash and attempt to store the hash in HKLM/_REG_PATH, then HKCU/_REG_PATH. * Subsequent calls will then retreive the stored hash value. On any failure,
--- a/toolkit/mozapps/installer/windows/nsis/makensis.mk +++ b/toolkit/mozapps/installer/windows/nsis/makensis.mk @@ -58,16 +58,17 @@ TOOLKIT_NSIS_FILES = \ CUSTOM_NSIS_PLUGINS = \ AccessControl.dll \ AppAssocReg.dll \ ApplicationID.dll \ CityHash.dll \ InvokeShellVerb.dll \ ShellLink.dll \ UAC.dll \ + ServicesHelper.dll \ $(NULL) $(CONFIG_DIR)/setup.exe:: $(INSTALL) $(addprefix $(MOZILLA_DIR)/toolkit/mozapps/installer/windows/nsis/,$(TOOLKIT_NSIS_FILES)) $(CONFIG_DIR) $(INSTALL) $(addprefix $(MOZILLA_DIR)/other-licenses/nsis/Plugins/,$(CUSTOM_NSIS_PLUGINS)) $(CONFIG_DIR) cd $(CONFIG_DIR) && $(MAKENSISU) installer.nsi # Support for building the uninstaller when repackaging locales ifeq ($(CONFIG_DIR),l10ngen) @@ -94,8 +95,15 @@ endif # For building the uninstaller during the application build so it can be # included for mar file generation. uninstaller:: $(INSTALL) $(addprefix $(MOZILLA_DIR)/toolkit/mozapps/installer/windows/nsis/,$(TOOLKIT_NSIS_FILES)) $(CONFIG_DIR) $(INSTALL) $(addprefix $(MOZILLA_DIR)/other-licenses/nsis/Plugins/,$(CUSTOM_NSIS_PLUGINS)) $(CONFIG_DIR) cd $(CONFIG_DIR) && $(MAKENSISU) uninstaller.nsi $(NSINSTALL) -D $(DIST)/bin/uninstall cp $(CONFIG_DIR)/helper.exe $(DIST)/bin/uninstall + +ifdef MOZ_MAINTENANCE_SERVICE +maintenanceservice_installer:: + cd $(CONFIG_DIR) && $(MAKENSISU) maintenanceservice_installer.nsi + $(NSINSTALL) -D $(DIST)/bin/ + cp $(CONFIG_DIR)/maintenanceservice_installer.exe $(DIST)/bin +endif
--- a/toolkit/mozapps/readstrings/Makefile.in +++ b/toolkit/mozapps/readstrings/Makefile.in @@ -44,10 +44,11 @@ include $(DEPTH)/config/autoconf.mk MODULE = readstrings LIBRARY_NAME = readstrings FORCE_STATIC_LIB = 1 export NO_SHUNT = 1 USE_STATIC_LIBS = 1 CPPSRCS = readstrings.cpp +EXPORTS = readstrings.h include $(topsrcdir)/config/rules.mk
--- a/toolkit/mozapps/update/Makefile.in +++ b/toolkit/mozapps/update/Makefile.in @@ -47,16 +47,17 @@ MODULE = update XPIDLSRCS = nsIUpdateTimerManager.idl EXTRA_PP_COMPONENTS = nsUpdateTimerManager.js nsUpdateTimerManager.manifest ifdef MOZ_UPDATER DIRS = ../readstrings ifneq ($(OS_TARGET),Android) +DIRS += common DIRS += updater endif XPIDLSRCS += nsIUpdateService.idl EXTRA_PP_COMPONENTS += \ nsUpdateService.js \ nsUpdateServiceStub.js \
new file mode 100644 --- /dev/null +++ b/toolkit/mozapps/update/common/Makefile.in @@ -0,0 +1,72 @@ +# ***** BEGIN LICENSE BLOCK ***** +# Version: MPL 1.1/GPL 2.0/LGPL 2.1 +# +# The contents of this file are subject to the Mozilla Public License Version +# 1.1 (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# http://www.mozilla.org/MPL/ +# +# Software distributed under the License is distributed on an "AS IS" basis, +# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License +# for the specific language governing rights and limitations under the +# License. +# +# The Original Code is Mozilla maintenance service build. +# +# The Initial Developer of the Original Code is +# Mozilla Foundation. +# Portions created by the Initial Developer are Copyright (C) 2011 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Brian R. Bondy <netzen@gmail.com> +# +# Alternatively, the contents of this file may be used under the terms of +# either the GNU General Public License Version 2 or later (the "GPL"), or +# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), +# in which case the provisions of the GPL or the LGPL are applicable instead +# of those above. If you wish to allow use of your version of this file only +# under the terms of either the GPL or the LGPL, and not to allow others to +# use your version of this file under the terms of the MPL, indicate your +# decision by deleting the provisions above and replace them with the notice +# and other provisions required by the GPL or the LGPL. If you do not delete +# the provisions above, a recipient may use your version of this file under +# the terms of any one of the MPL, the GPL or the LGPL. +# +# ***** END LICENSE BLOCK ***** + +DEPTH = ../../../.. +topsrcdir = @top_srcdir@ +srcdir = @srcdir@ +VPATH = @srcdir@ + +include $(DEPTH)/config/autoconf.mk + +MODULE = libupdatecommon +LIBRARY_NAME = updatecommon +FORCE_STATIC_LIB =1 +LIBXUL_LIBRARY = 1 +ifeq ($(OS_ARCH),WINNT) +USE_STATIC_LIBS = 1 +endif + +CPPSRCS = \ + updatelogging.cpp \ + $(NULL) + +EXPORTS = updatelogging.h \ + updatedefines.h \ + $(NULL) + +ifeq ($(MOZ_WIDGET_TOOLKIT),windows) +CPPSRCS += launchwinprocess.cpp \ + uachelper.cpp \ + $(NULL) + +EXPORTS = launchwinprocess.h \ + uachelper.h \ + $(NULL) +endif + +include $(topsrcdir)/config/rules.mk +
new file mode 100644 --- /dev/null +++ b/toolkit/mozapps/update/common/launchwinprocess.cpp @@ -0,0 +1,259 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is common code between maintenanceservice and updater + * + * The Initial Developer of the Original Code is + * Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Brian R. Bondy <netzen@gmail.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include <windows.h> +#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; + } + + wcscpy(destinationBuffer, siblingFilePath); + 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 appExe 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 NULL the current user + * will be used. + */ +BOOL +LaunchWinPostProcess(const WCHAR *appExe, + const WCHAR *updateInfoDir, + bool forceSync, + HANDLE userToken) +{ + WCHAR workingDirectory[MAX_PATH + 1]; + wcscpy(workingDirectory, appExe); + if (!PathRemoveFileSpecW(workingDirectory)) { + return FALSE; + } + + // Launch helper.exe to perform post processing (e.g. registry and log file + // modifications) for the update. + WCHAR inifile[MAX_PATH + 1]; + if (!PathGetSiblingFilePath(inifile, appExe, 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", NULL, exefile, + MAX_PATH + 1, inifile)) { + return FALSE; + } + + if (!GetPrivateProfileStringW(L"PostUpdateWin", L"ExeArg", NULL, 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]; + if (!PathGetSiblingFilePath(exefullpath, appExe, exefile)) { + return FALSE; + } + + WCHAR dlogFile[MAX_PATH + 1]; + if (!PathGetSiblingFilePath(dlogFile, exefullpath, L"uninstall.update")) { + return FALSE; + } + + WCHAR slogFile[MAX_PATH + 1]; + wcscpy(slogFile, updateInfoDir); + if (!PathAppendSafe(slogFile, L"update.log")) { + return FALSE; + } + + WCHAR dummyArg[14]; + wcscpy(dummyArg, L"argv0ignored "); + + size_t len = wcslen(exearg) + wcslen(dummyArg); + WCHAR *cmdline = (WCHAR *) malloc((len + 1) * sizeof(WCHAR)); + if (!cmdline) { + return FALSE; + } + + wcscpy(cmdline, dummyArg); + 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, + NULL, // no special security attributes + NULL, // no special thread attributes + false, // don't inherit filehandles + 0, // No special process creation flags + NULL, // inherit my environment + workingDirectory, + &si, + &pi); + } else { + ok = CreateProcessW(exefullpath, + cmdline, + NULL, // no special security attributes + NULL, // no special thread attributes + false, // don't inherit filehandles + 0, // No special process creation flags + NULL, // 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 argc The argc value normally sent to updater.exe + * @param argv The argv value normally sent to updater.exe + * @return TRUE if successful + */ +BOOL +StartServiceUpdate(int argc, LPWSTR *argv) +{ + if (argc < 2) { + return FALSE; + } + + // Get a handle to the local computer SCM database + SC_HANDLE manager = OpenSCManager(NULL, NULL, + SC_MANAGER_ALL_ACCESS); + if (!manager) { + return FALSE; + } + + // Open the service + SC_HANDLE svc = OpenServiceW(manager, L"MozillaMaintenance", + SERVICE_ALL_ACCESS); + if (!svc) { + CloseServiceHandle(manager); + return FALSE; + } + CloseServiceHandle(svc); + CloseServiceHandle(manager); + + // If we reach here, then the service is installed, so + // proceed with upgrading it. + + STARTUPINFOW si = {0}; + si.cb = sizeof(STARTUPINFOW); + // No particular desktop because no UI + si.lpDesktop = L""; + PROCESS_INFORMATION pi = {0}; + + WCHAR maintserviceInstallerPath[MAX_PATH + 1]; + wcscpy(maintserviceInstallerPath, argv[2]); + PathAppendSafe(maintserviceInstallerPath, + L"maintenanceservice_installer.exe"); + WCHAR cmdLine[64]; + wcscpy(cmdLine, L"dummyparam.exe /Upgrade"); + BOOL svcUpdateProcessStarted = CreateProcessW(maintserviceInstallerPath, + cmdLine, + NULL, NULL, FALSE, + CREATE_DEFAULT_ERROR_MODE | + CREATE_UNICODE_ENVIRONMENT, + NULL, argv[2], &si, &pi); + if (svcUpdateProcessStarted) { + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + } + return svcUpdateProcessStarted; +}
new file mode 100644 --- /dev/null +++ b/toolkit/mozapps/update/common/launchwinprocess.h @@ -0,0 +1,42 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is common code between maintenanceservice and updater + * + * The Initial Developer of the Original Code is + * Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Brian R. Bondy <netzen@gmail.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +BOOL LaunchWinPostProcess(const WCHAR *appExe, + const WCHAR *updateInfoDir, + bool forceSync, + HANDLE userToken); +BOOL StartServiceUpdate(int argc, LPWSTR *argv);
new file mode 100644 --- /dev/null +++ b/toolkit/mozapps/update/common/uachelper.cpp @@ -0,0 +1,101 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Maintenance service UAC helper functions. + * + * The Initial Developer of the Original Code is + * Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Brian R. Bondy <netzen@gmail.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include <windows.h> +#include "uachelper.h" + +typedef BOOL (WINAPI *LPWTSQueryUserToken)(ULONG, PHANDLE); + +/** + * Determines if the OS is vista or later + * + * @return TRUE if the OS is vista or later. + */ +BOOL +UACHelper::IsVistaOrLater() +{ + // Check if we are running Vista or later. + OSVERSIONINFO osInfo; + osInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); + return GetVersionEx(&osInfo) && osInfo.dwMajorVersion >= 6; +} + +/** + * 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 = NULL; + LPWTSQueryUserToken wtsQueryUserToken = + (LPWTSQueryUserToken)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 NULL 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 = NULL; + DWORD len; + if (GetTokenInformation(token, (TOKEN_INFORMATION_CLASS)TokenLinkedToken, + &tlt, sizeof(TOKEN_LINKED_TOKEN), &len)) { + token = tlt.LinkedToken; + hNewLinkedToken = token; + } + return hNewLinkedToken; +}
new file mode 100644 --- /dev/null +++ b/toolkit/mozapps/update/common/uachelper.h @@ -0,0 +1,49 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Maintenance service UAC helper functions. + * + * The Initial Developer of the Original Code is + * Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Brian R. Bondy <netzen@gmail.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef _UACHELPER_H_ +#define _UACHELPER_H_ + +class UACHelper +{ +public: + static BOOL IsVistaOrLater(); + static HANDLE OpenUserToken(DWORD sessionID); + static HANDLE OpenLinkedToken(HANDLE token); +}; + +#endif
new file mode 100644 --- /dev/null +++ b/toolkit/mozapps/update/common/updatedefines.h @@ -0,0 +1,143 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is common code between maintenanceservice and updater + * + * The Initial Developer of the Original Code is + * Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Brian R. Bondy <netzen@gmail.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef UPDATEDEFINES_H +#define UPDATEDEFINES_H + +#include "prtypes.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(XP_WIN) +# include <windows.h> +# include <direct.h> +# include <io.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 stat _stat +# define DELETE_DIR L"tobedeleted" +# define CALLBACK_BACKUP_EXT L".moz-callback" + +# define LOG_S "%S" +# define NS_T(str) L ## str +// On Windows, _snprintf and _snwprintf don't guarantee null termination. These +// macros always leave room in the buffer for null termination and set the end +// of the buffer to null in case the string is larger than the buffer. Having +// multiple nulls in a string is fine and this approach is simpler (possibly +// faster) than calculating the string length to place the null terminator and +// truncates the string as _snprintf and _snwprintf do on other platforms. +# define snprintf(dest, count, fmt, ...) \ + PR_BEGIN_MACRO \ + int _count = count - 1; \ + _snprintf(dest, _count, fmt, ##__VA_ARGS__); \ + dest[_count] = '\0'; \ + PR_END_MACRO +#define NS_tsnprintf(dest, count, fmt, ...) \ + PR_BEGIN_MACRO \ + int _count = count - 1; \ + _snwprintf(dest, _count, fmt, ##__VA_ARGS__); \ + dest[_count] = L'\0'; \ + PR_END_MACRO +# 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_tstrcat wcscat +# define NS_tstrcmp wcscmp +# define NS_tstrcpy wcscpy +# define NS_tstrlen wcslen +# define NS_tstrrchr wcsrchr +# define NS_tstrstr wcsstr +#else +# include <sys/wait.h> +# include <unistd.h> +# include <fts.h> + +#ifdef XP_MACOSX +# include <sys/time.h> +#endif + +# define LOG_S "%s" +# define NS_T(str) str +# 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_tstrcat strcat +# define NS_tstrcmp strcmp +# define NS_tstrcpy strcpy +# define NS_tstrlen strlen +# define NS_tstrrchr strrchr +# define NS_tstrstr strstr +#endif + +#define BACKUP_EXT NS_T(".moz-backup") + +#endif
new file mode 100644 --- /dev/null +++ b/toolkit/mozapps/update/common/updatelogging.cpp @@ -0,0 +1,87 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is common code between maintenanceservice and updater + * + * The Initial Developer of the Original Code is + * Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Brian R. Bondy <netzen@gmail.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#if defined(XP_WIN) +#include <windows.h> +#endif + + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <stdarg.h> + +#include "updatelogging.h" + +UpdateLog* UpdateLog::primaryLog = NULL; + +UpdateLog::UpdateLog() : logFP(NULL) +{ +} + +void UpdateLog::Init(NS_tchar* sourcePath, NS_tchar* fileName) +{ + if (logFP) + return; + + this->sourcePath = sourcePath; + NS_tchar logFile[MAXPATHLEN]; + NS_tsnprintf(logFile, sizeof(logFile)/sizeof(logFile[0]), + NS_T("%s/%s"), sourcePath, fileName); + + logFP = NS_tfopen(logFile, NS_T("w")); +} + +void UpdateLog::Finish() +{ + if (!logFP) + return; + + fclose(logFP); + logFP = NULL; +} + +void UpdateLog::Printf(const char *fmt, ... ) +{ + if (!logFP) + return; + + va_list ap; + va_start(ap, fmt); + vfprintf(logFP, fmt, ap); + va_end(ap); +}
new file mode 100644 --- /dev/null +++ b/toolkit/mozapps/update/common/updatelogging.h @@ -0,0 +1,77 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is common code between maintenanceservice and updater + * + * The Initial Developer of the Original Code is + * Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Brian R. Bondy <netzen@gmail.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef UPDATELOGGING_H +#define UPDATELOGGING_H + +#include "updatedefines.h" +#include <stdio.h> + +class UpdateLog +{ +public: + static UpdateLog & GetPrimaryLog() + { + if (!primaryLog) { + primaryLog = new UpdateLog(); + } + return *primaryLog; + } + + void Init(NS_tchar* sourcePath, NS_tchar* fileName); + void Finish(); + void Printf(const char *fmt, ... ); + + ~UpdateLog() + { + delete primaryLog; + } + +protected: + UpdateLog(); + FILE *logFP; + NS_tchar* sourcePath; + + static UpdateLog* primaryLog; +}; + +#define LOG(args) UpdateLog::GetPrimaryLog().Printf args +#define LogInit(PATHNAME_, FILENAME_) \ + UpdateLog::GetPrimaryLog().Init(PATHNAME_, FILENAME_) +#define LogFinish() UpdateLog::GetPrimaryLog().Finish() + +#endif
--- a/toolkit/mozapps/update/content/updates.js +++ b/toolkit/mozapps/update/content/updates.js @@ -63,16 +63,17 @@ const PREF_PLUGINS_UPDATEURL const PREF_EM_HOTFIX_ID = "extensions.hotfix.id"; const UPDATE_TEST_LOOP_INTERVAL = 2000; const URI_UPDATES_PROPERTIES = "chrome://mozapps/locale/update/updates.properties"; const STATE_DOWNLOADING = "downloading"; const STATE_PENDING = "pending"; +const STATE_PENDING_SVC = "pending-service"; const STATE_APPLYING = "applying"; const STATE_SUCCEEDED = "succeeded"; const STATE_DOWNLOAD_FAILED = "download-failed"; const STATE_FAILED = "failed"; const SRCEVT_FOREGROUND = 1; const SRCEVT_BACKGROUND = 2; @@ -438,16 +439,17 @@ var gUpdates = { state = STATE_DOWNLOAD_FAILED; } } // Now select the best page to start with, given the current state of // the Update. switch (state) { case STATE_PENDING: + case STATE_PENDING_SVC: this.sourceEvent = SRCEVT_BACKGROUND; aCallback("finishedBackground"); return; case STATE_DOWNLOADING: aCallback("downloading"); return; case STATE_FAILED: window.getAttention(); @@ -1675,16 +1677,17 @@ var gErrorPatchingPage = { */ onPageShow: function() { gUpdates.setButtons(null, null, "okButton", true); }, onWizardNext: function() { switch (gUpdates.update.selectedPatch.state) { case STATE_PENDING: + case STATE_PENDING_SVC: gUpdates.wiz.goTo("finished"); break; case STATE_DOWNLOADING: gUpdates.wiz.goTo("downloading"); break; case STATE_DOWNLOAD_FAILED: gUpdates.wiz.goTo("errors"); break;
--- a/toolkit/mozapps/update/nsIUpdateService.idl +++ b/toolkit/mozapps/update/nsIUpdateService.idl @@ -240,22 +240,23 @@ interface nsIUpdate : nsISupports /** * The currently selected patch for this update. */ readonly attribute nsIUpdatePatch selectedPatch; /** * The state of the selected patch: - * "downloading" The update is being downloaded. - * "pending" The update is ready to be applied. - * "applying" The update is being applied. - * "succeeded" The update was successfully applied. - * "download-failed" The update failed to be downloaded. - * "failed" The update failed to be applied. + * "downloading" The update is being downloaded. + * "pending" The update is ready to be applied. + * "pending-service" The update is ready to be applied with the service. + * "applying" The update is being applied. + * "succeeded" The update was successfully applied. + * "download-failed" The update failed to be downloaded. + * "failed" The update failed to be applied. */ attribute AString state; /** * A numeric error code that conveys additional information about the state * of a failed update or failed certificate attribute check during an update * check. If the update is not in the "failed" state or the certificate * attribute check has not failed the value is zero.
--- a/toolkit/mozapps/update/nsUpdateService.js +++ b/toolkit/mozapps/update/nsUpdateService.js @@ -22,16 +22,17 @@ # Contributor(s): # Ben Goodger <ben@mozilla.org> (Original Author) # Darin Fisher <darin@meer.net> # Ben Turner <bent.mozilla@gmail.com> # Jeff Walden <jwalden+code@mit.edu> # Alexander J. Vincent <ajvincent@gmail.com> # Dão Gottwald <dao@mozilla.com> # Robert Strong <robert.bugzilla@gmail.com> +# Brian R. Bondy <netzen@gmail.com> # # Alternatively, the contents of this file may be used under the terms of # either the GNU General Public License Version 2 or later (the "GPL"), or # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), # in which case the provisions of the GPL or the LGPL are applicable instead # of those above. If you wish to allow use of your version of this file only # under the terms of either the GPL or the LGPL, and not to allow others to # use your version of this file under the terms of the MPL, indicate your @@ -76,16 +77,19 @@ const PREF_APP_UPDATE_MODE const PREF_APP_UPDATE_NEVER_BRANCH = "app.update.never."; const PREF_APP_UPDATE_POSTUPDATE = "app.update.postupdate"; const PREF_APP_UPDATE_PROMPTWAITTIME = "app.update.promptWaitTime"; const PREF_APP_UPDATE_SHOW_INSTALLED_UI = "app.update.showInstalledUI"; const PREF_APP_UPDATE_SILENT = "app.update.silent"; const PREF_APP_UPDATE_URL = "app.update.url"; const PREF_APP_UPDATE_URL_DETAILS = "app.update.url.details"; const PREF_APP_UPDATE_URL_OVERRIDE = "app.update.url.override"; +const PREF_APP_UPDATE_SERVICE_ENABLED = "app.update.service.enabled"; +const PREF_APP_UPDATE_SERVICE_ERRORS = "app.update.service.errors"; +const PREF_APP_UPDATE_SERVICE_MAX_ERRORS = "app.update.service.maxErrors"; const PREF_PARTNER_BRANCH = "app.partner."; const PREF_APP_DISTRIBUTION = "distribution.id"; const PREF_APP_DISTRIBUTION_VERSION = "distribution.version"; const PREF_EM_HOTFIX_ID = "extensions.hotfix.id"; const URI_UPDATE_PROMPT_DIALOG = "chrome://mozapps/content/update/updates.xul"; @@ -124,36 +128,45 @@ const FILE_UPDATE_ACTIVE = "active-upda const FILE_PERMS_TEST = "update.test"; const FILE_LAST_LOG = "last-update.log"; const FILE_BACKUP_LOG = "backup-update.log"; const FILE_UPDATE_LOCALE = "update.locale"; const STATE_NONE = "null"; const STATE_DOWNLOADING = "downloading"; const STATE_PENDING = "pending"; +const STATE_PENDING_SVC = "pending-service"; const STATE_APPLYING = "applying"; const STATE_SUCCEEDED = "succeeded"; const STATE_DOWNLOAD_FAILED = "download-failed"; const STATE_FAILED = "failed"; // From updater/errors.h: const WRITE_ERROR = 7; const UNEXPECTED_ERROR = 8; const ELEVATION_CANCELED = 9; +const SERVICE_UPDATER_COULD_NOT_BE_STARTED = 16000; +const SERVICE_NOT_ENOUGH_COMMAND_LINE_ARGS = 16001; +const SERVICE_UPDATER_SIGN_ERROR = 16002; +const SERVICE_CALLBACK_SIGN_ERROR = 16003; const CERT_ATTR_CHECK_FAILED_NO_UPDATE = 100; const CERT_ATTR_CHECK_FAILED_HAS_UPDATE = 101; const BACKGROUNDCHECK_MULTIPLE_FAILURES = 110; const DOWNLOAD_CHUNK_SIZE = 300000; // bytes const DOWNLOAD_BACKGROUND_INTERVAL = 600; // seconds const DOWNLOAD_FOREGROUND_INTERVAL = 0; const UPDATE_WINDOW_NAME = "Update:Wizard"; +// The number of consecutive failures when updating using the service before +// setting the app.update.service.enabled preference to false. +const DEFAULT_SERVICE_MAX_ERRORS = 10; + var gLocale = null; XPCOMUtils.defineLazyGetter(this, "gLogEnabled", function aus_gLogEnabled() { return getPref("getBoolPref", PREF_APP_UPDATE_LOG, false); }); XPCOMUtils.defineLazyGetter(this, "gUpdateBundle", function aus_gUpdateBundle() { return Services.strings.createBundle(URI_UPDATES_PROPERTIES); @@ -456,17 +469,17 @@ function LOG(string) { # Gets a preference value, handling the case where there is no default. # @param func # The name of the preference function to call, on nsIPrefBranch # @param preference # The name of the preference # @param defaultValue # The default value to return in the event the preference has # no setting -# @returns The value of the preference, or undefined if there was no +# @return The value of the preference, or undefined if there was no # user or default value. */ function getPref(func, preference, defaultValue) { try { return Services.prefs[func](preference); } catch (e) { } @@ -525,17 +538,17 @@ function getUpdateFile(pathArray) { /** * Returns human readable status text from the updates.properties bundle * based on an error code * @param code * The error code to look up human readable status text for * @param defaultCode * The default code to look up should human readable status text * not exist for |code| - * @returns A human readable status text string + * @return A human readable status text string */ function getStatusTextFromCode(code, defaultCode) { var reason; try { reason = gUpdateBundle.GetStringFromName("check_error-" + code); LOG("getStatusTextFromCode - transfer error: " + reason + ", code: " + code); } @@ -545,30 +558,30 @@ function getStatusTextFromCode(code, def LOG("getStatusTextFromCode - transfer error: " + reason + ", default code: " + defaultCode); } return reason; } /** * Get the Active Updates directory - * @returns The active updates directory, as a nsIFile object + * @return The active updates directory, as a nsIFile object */ function getUpdatesDir() { // Right now, we only support downloading one patch at a time, so we always // use the same target directory. return getUpdateDirCreate([DIR_UPDATES, "0"]); } /** * Reads the update state from the update.status file in the specified * directory. * @param dir * The dir to look for an update.status file in - * @returns The status value of the update. + * @return The status value of the update. */ function readStatusFile(dir) { var statusFile = dir.clone(); statusFile.append(FILE_UPDATE_STATUS); var status = readStringFromFile(statusFile) || STATE_NONE; LOG("readStatusFile - status: " + status + ", path: " + statusFile.path); return status; } @@ -585,16 +598,32 @@ function readStatusFile(dir) { */ function writeStatusFile(dir, state) { var statusFile = dir.clone(); statusFile.append(FILE_UPDATE_STATUS); writeStringToFile(statusFile, state); } /** + * Determines if the service should be used to attempt an update + * or not. For now this is only when PREF_APP_UPDATE_SERVICE_ENABLED + * is true and we have Firefox. + * + * @return true if the service should be used for updates. + */ +function shouldUseService() { +#ifdef MOZ_MAINTENANCE_SERVICE + return getPref("getBoolPref", + PREF_APP_UPDATE_SERVICE_ENABLED, false); +#else + return false; +#endif +} + +/** # Writes the update's application version to a file in the patch directory. If # the update doesn't provide application version information via the # appVersion attribute the string "null" will be written to the file. # This value is compared during startup (in nsUpdateDriver.cpp) to determine if # the update should be applied. Note that this won't provide protection from # downgrade of the application for the nightly user case where the application # version doesn't change. # @param dir @@ -1394,17 +1423,44 @@ UpdateService.prototype = { update.state = ary[0]; if (update.state == STATE_FAILED && ary[1]) { update.errorCode = parseInt(ary[1]); if (update.errorCode == WRITE_ERROR) { prompter.showUpdateError(update); writeStatusFile(getUpdatesDir(), update.state = STATE_PENDING); return; } - else if (update.errorCode == ELEVATION_CANCELED) { + + if (update.errorCode == ELEVATION_CANCELED) { + writeStatusFile(getUpdatesDir(), update.state = STATE_PENDING); + return; + } + + if (update.errorCode == SERVICE_UPDATER_COULD_NOT_BE_STARTED || + update.errorCode == SERVICE_NOT_ENOUGH_COMMAND_LINE_ARGS || + update.errorCode == SERVICE_UPDATER_SIGN_ERROR || + update.errorCode == SERVICE_CALLBACK_SIGN_ERROR) { + var failCount = getPref("getIntPref", + PREF_APP_UPDATE_SERVICE_ERRORS, 0); + var maxFail = getPref("getIntPref", + PREF_APP_UPDATE_SERVICE_MAX_ERRORS, + DEFAULT_SERVICE_MAX_ERRORS); + + // As a safety, when the service reaches maximum failures, it will + // disable itself and fallback to using the normal update mechanism + // without the service. + if (failCount >= maxFail) { + Services.prefs.setBoolPref(PREF_APP_UPDATE_SERVICE_ENABLED, false); + Services.prefs.clearUserPref(PREF_APP_UPDATE_SERVICE_ERRORS); + } else { + failCount++; + Services.prefs.setIntPref(PREF_APP_UPDATE_SERVICE_ERRORS, + failCount); + } + writeStatusFile(getUpdatesDir(), update.state = STATE_PENDING); return; } } // Something went wrong with the patch application process. cleanupActiveUpdate(); @@ -1517,17 +1573,17 @@ UpdateService.prototype = { }, /** * Determine the update from the specified updates that should be offered. * If both valid major and minor updates are available the minor update will * be offered. * @param updates * An array of available nsIUpdate items - * @returns The nsIUpdate to offer. + * @return The nsIUpdate to offer. */ selectUpdate: function AUS_selectUpdate(updates) { if (updates.length == 0) return null; if (getDesiredChannel()) { LOG("UpdateService:selectUpdate - skipping version checks for channel " + "change request"); @@ -2006,17 +2062,17 @@ UpdateManager.prototype = { this._activeUpdate = updates[0]; } }, /** * Loads an updates.xml formatted file into an array of nsIUpdate items. * @param file * A nsIFile for the updates.xml file - * @returns The array of nsIUpdate items held in the file. + * @return The array of nsIUpdate items held in the file. */ _loadXMLFileIntoArray: function UM__loadXMLFileIntoArray(file) { if (!file.exists()) { LOG("UpdateManager:_loadXMLFileIntoArray: XML file does not exist"); return []; } var result = []; @@ -2190,17 +2246,17 @@ UpdateManager.prototype = { this._ensureUpdates(); // Don't write updates that have a temporary state to the updates.xml file. if (this._updates) { let updates = this._updates.slice(); for (let i = updates.length - 1; i >= 0; --i) { let state = updates[i].state; if (state == STATE_NONE || state == STATE_DOWNLOADING || - state == STATE_PENDING) { + state == STATE_PENDING || state == STATE_PENDING_SVC) { updates.splice(i, 1); } } this._writeUpdatesToXMLFile(updates.slice(0, 10), getUpdateFile([FILE_UPDATES_DB])); } }, @@ -2525,17 +2581,18 @@ Downloader.prototype = { if (this._request && this._request instanceof Ci.nsIRequest) this._request.cancel(Cr.NS_BINDING_ABORTED); }, /** * Whether or not a patch has been downloaded and staged for installation. */ get patchIsStaged() { - return readStatusFile(getUpdatesDir()) == STATE_PENDING; + var readState = readStatusFile(getUpdatesDir()); + return readState == STATE_PENDING || readState == STATE_PENDING_SVC; }, /** * Verify the downloaded file. We assume that the download is complete at * this point. */ _verifyDownload: function Downloader__verifyDownload() { if (!this._request) @@ -2577,27 +2634,27 @@ Downloader.prototype = { /** * Select the patch to use given the current state of updateDir and the given * set of update patches. * @param update * A nsIUpdate object to select a patch from * @param updateDir * A nsIFile representing the update directory - * @returns A nsIUpdatePatch object to download + * @return A nsIUpdatePatch object to download */ _selectPatch: function Downloader__selectPatch(update, updateDir) { // Given an update to download, we will always try to download the patch // for a partial update over the patch for a full update. /** * Return the first UpdatePatch with the given type. * @param type * The type of the patch ("complete" or "partial") - * @returns A nsIUpdatePatch object matching the type specified + * @return A nsIUpdatePatch object matching the type specified */ function getPatchOfType(type) { for (var i = 0; i < update.patchCount; ++i) { var patch = update.getPatchAt(i); if (patch && patch.type == type) return patch; } return null; @@ -2615,16 +2672,17 @@ Downloader.prototype = { var useComplete = getDesiredChannel() ? true : false; if (selectedPatch) { LOG("Downloader:_selectPatch - found existing patch with state: " + state); switch (state) { case STATE_DOWNLOADING: LOG("Downloader:_selectPatch - resuming download"); return selectedPatch; + case STATE_PENDING_SVC: case STATE_PENDING: LOG("Downloader:_selectPatch - already downloaded and staged"); return null; default: // Something went wrong when we tried to apply the previous patch. // Try the complete patch next time. if (update && selectedPatch.type == "partial") { useComplete = true; @@ -2842,17 +2900,17 @@ Downloader.prototype = { LOG("Downloader:onStopRequest - original URI spec: " + request.URI.spec + ", final URI spec: " + request.finalURI.spec + ", status: " + status); var state = this._patch.state; var shouldShowPrompt = false; var deleteActiveUpdate = false; if (Components.isSuccessCode(status)) { if (this._verifyDownload()) { - state = STATE_PENDING; + state = shouldUseService() ? STATE_PENDING_SVC : STATE_PENDING // We only need to explicitly show the prompt if this is a background // download, since otherwise some kind of UI is already visible and // that UI will notify. if (this.background) shouldShowPrompt = true; // Tell the updater.exe we're ready to apply.
--- a/toolkit/mozapps/update/test/Makefile.in +++ b/toolkit/mozapps/update/test/Makefile.in @@ -73,16 +73,17 @@ INI_TEST_FILES = \ TestAUSReadStrings1.ini \ TestAUSReadStrings2.ini \ TestAUSReadStrings3.ini \ $(NULL) LOCAL_INCLUDES += \ -I$(srcdir) \ -I$(topsrcdir)/toolkit/mozapps/update \ + -I$(topsrcdir)/toolkit/mozapps/update/common \ $(NULL) MOZ_WINCONSOLE = 1 LIBS += \ ../../readstrings/$(LIB_PREFIX)readstrings.$(LIB_SUFFIX) \ $(NULL)
--- a/toolkit/mozapps/update/test/sharedUpdateXML.js +++ b/toolkit/mozapps/update/test/sharedUpdateXML.js @@ -24,16 +24,17 @@ const SHA384_HASH_SIMPLE_MAR = "a5725055 "99c453"; const SHA512_HASH_SIMPLE_MAR = "55d3e2a86acaeb0abb7a444c13bba748846fcbac7ff05" + "8f8ee9c9260ba01e6aef86fa4a6c46a3016b675ef94e7" + "7e63fbe912f64d155bed9b1c341dd56e575a26"; const STATE_NONE = "null"; const STATE_DOWNLOADING = "downloading"; const STATE_PENDING = "pending"; +const STATE_PENDING_SVC = "pending-service"; const STATE_APPLYING = "applying"; const STATE_SUCCEEDED = "succeeded"; const STATE_DOWNLOAD_FAILED = "download-failed"; const STATE_FAILED = "failed"; /** * Constructs a string representing a remote update xml file. *
--- a/toolkit/mozapps/update/updater/Makefile.in +++ b/toolkit/mozapps/update/updater/Makefile.in @@ -50,19 +50,21 @@ CPPSRCS = \ $(NULL) PROGRAM = updater$(BIN_SUFFIX) # Don't link the updater against libmozutils. See bug 687139 MOZ_UTILS_LDFLAGS = MOZ_UTILS_PROGRAM_LDFLAGS = -LOCAL_INCLUDES += -I$(srcdir)/../../readstrings +LOCAL_INCLUDES += -I$(srcdir)/../../readstrings \ + -I$(srcdir)/../common LIBS += \ + ../common/$(LIB_PREFIX)updatecommon.$(LIB_SUFFIX) \ $(DEPTH)/modules/libmar/src/$(LIB_PREFIX)mar.$(LIB_SUFFIX) \ ../../readstrings/$(LIB_PREFIX)readstrings.$(LIB_SUFFIX) \ $(BZ2_LIBS) \ $(NULL) ifeq ($(OS_ARCH),WINNT) USE_STATIC_LIBS = 1 HAVE_PROGRESSUI = 1
--- a/toolkit/mozapps/update/updater/progressui.h +++ b/toolkit/mozapps/update/updater/progressui.h @@ -34,16 +34,18 @@ * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #ifndef PROGRESSUI_H__ #define PROGRESSUI_H__ +#include "updatedefines.h" + #if defined(XP_WIN) typedef WCHAR NS_tchar; #define NS_main wmain #else typedef char NS_tchar; #define NS_main main #endif
--- a/toolkit/mozapps/update/updater/updater.cpp +++ b/toolkit/mozapps/update/updater/updater.cpp @@ -64,102 +64,16 @@ * downgrades by causing the actions defined in precomplete to be performed. * * precomplete * ----------- * method = "remove" | "rmdir" | "remove-cc" * * 'remove-cc' is a remove action to perform on channel change. */ - -#if defined(XP_WIN) -# include <windows.h> -# include <direct.h> -# include <io.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 stat _stat -# define DELETE_DIR L"tobedeleted" -# define CALLBACK_BACKUP_EXT L".moz-callback" - -# define LOG_S "%S" -# define NS_T(str) L ## str -// On Windows, _snprintf and _snwprintf don't guarantee null termination. These -// macros always leave room in the buffer for null termination and set the end -// of the buffer to null in case the string is larger than the buffer. Having -// multiple nulls in a string is fine and this approach is simpler (possibly -// faster) than calculating the string length to place the null terminator and -// truncates the string as _snprintf and _snwprintf do on other platforms. -# define snprintf(dest, count, fmt, ...) \ - PR_BEGIN_MACRO \ - int _count = count - 1; \ - _snprintf(dest, _count, fmt, ##__VA_ARGS__); \ - dest[_count] = '\0'; \ - PR_END_MACRO -# define NS_tsnprintf(dest, count, fmt, ...) \ - PR_BEGIN_MACRO \ - int _count = count - 1; \ - _snwprintf(dest, _count, fmt, ##__VA_ARGS__); \ - dest[_count] = L'\0'; \ - PR_END_MACRO -# 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_tstrcat wcscat -# define NS_tstrcmp wcscmp -# define NS_tstrcpy wcscpy -# define NS_tstrlen wcslen -# define NS_tstrrchr wcsrchr -# define NS_tstrstr wcsstr -#else -# include <sys/wait.h> -# include <unistd.h> -# include <fts.h> - -#ifdef XP_MACOSX -# include <sys/time.h> -#endif - -# define LOG_S "%s" -# define NS_T(str) str -# 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_tstrcat strcat -# define NS_tstrcmp strcmp -# define NS_tstrcpy strcpy -# define NS_tstrlen strlen -# define NS_tstrrchr strrchr -# define NS_tstrstr strstr -#endif - -#define BACKUP_EXT NS_T(".moz-backup") - #include "bspatch.h" #include "progressui.h" #include "archivereader.h" #include "errors.h" #include "bzlib.h" #include <stdio.h> #include <string.h> @@ -167,16 +81,18 @@ #include <stdarg.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <limits.h> #include <errno.h> +#include "updatelogging.h" + // 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 #if defined(XP_MACOSX) // These functions are defined in launchchild_osx.mm @@ -191,37 +107,25 @@ void LaunchMacPostProcess(const char* aA #ifndef NULL # define NULL (0) #endif #ifndef SSIZE_MAX # define SSIZE_MAX LONG_MAX #endif -#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 - // We want to use execv to invoke the callback executable on platforms where // we were launched using execv. See nsUpdateDriver.cpp. #if defined(XP_UNIX) && !defined(XP_MACOSX) #define USE_EXECV #endif #ifdef XP_WIN +#include "launchwinprocess.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); \ } \ @@ -396,57 +300,16 @@ static NS_tchar* gDestPath; static NS_tchar gCallbackRelPath[MAXPATHLEN]; static NS_tchar gCallbackBackupPath[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("\""); -//----------------------------------------------------------------------------- -// LOGGING - -static FILE *gLogFP = NULL; - -static void LogInit() -{ - if (gLogFP) - return; - - NS_tchar logFile[MAXPATHLEN]; - NS_tsnprintf(logFile, sizeof(logFile)/sizeof(logFile[0]), - NS_T("%s/update.log"), gSourcePath); - - gLogFP = NS_tfopen(logFile, NS_T("w")); -} - -static void LogFinish() -{ - if (!gLogFP) - return; - - fclose(gLogFP); - gLogFP = NULL; -} - -static void LogPrintf(const char *fmt, ... ) -{ - if (!gLogFP) - return; - - va_list ap; - va_start(ap, fmt); - vfprintf(gLogFP, fmt, ap); - va_end(ap); -} - -#define LOG(args) LogPrintf args - -//----------------------------------------------------------------------------- - static inline size_t mmin(size_t a, size_t b) { return (a > b) ? b : a; } static NS_tchar* mstrtok(const NS_tchar *delims, NS_tchar **str) @@ -1514,126 +1377,57 @@ PatchIfFile::Finish(int status) PatchFile::Finish(status); } //----------------------------------------------------------------------------- #ifdef XP_WIN #include "nsWindowsRestart.cpp" - -static void -LaunchWinPostProcess(const WCHAR *appExe) -{ - // Launch helper.exe to perform post processing (e.g. registry and log file - // modifications) for the update. - WCHAR inifile[MAXPATHLEN]; - wcscpy(inifile, appExe); - - WCHAR *slash = wcsrchr(inifile, '\\'); - if (!slash) - return; - - wcscpy(slash + 1, L"updater.ini"); - - WCHAR exefile[MAXPATHLEN]; - WCHAR exearg[MAXPATHLEN]; - WCHAR exeasync[10]; - bool async = true; - if (!GetPrivateProfileStringW(L"PostUpdateWin", L"ExeRelPath", NULL, exefile, - MAXPATHLEN, inifile)) - return; - - if (!GetPrivateProfileStringW(L"PostUpdateWin", L"ExeArg", NULL, exearg, - MAXPATHLEN, inifile)) - return; - - if (!GetPrivateProfileStringW(L"PostUpdateWin", L"ExeAsync", L"TRUE", exeasync, - sizeof(exeasync)/sizeof(exeasync[0]), inifile)) - return; - - WCHAR exefullpath[MAXPATHLEN]; - wcscpy(exefullpath, appExe); - - slash = wcsrchr(exefullpath, '\\'); - wcscpy(slash + 1, exefile); - - WCHAR dlogFile[MAXPATHLEN]; - wcscpy(dlogFile, exefullpath); - - slash = wcsrchr(dlogFile, '\\'); - wcscpy(slash + 1, L"uninstall.update"); - - WCHAR slogFile[MAXPATHLEN]; - NS_tsnprintf(slogFile, sizeof(slogFile)/sizeof(slogFile[0]), - NS_T("%s/update.log"), gSourcePath); - - WCHAR dummyArg[13]; - wcscpy(dummyArg, L"argv0ignored "); - - size_t len = wcslen(exearg) + wcslen(dummyArg); - WCHAR *cmdline = (WCHAR *) malloc((len + 1) * sizeof(WCHAR)); - if (!cmdline) - return; - - wcscpy(cmdline, dummyArg); - wcscat(cmdline, exearg); - - if (!_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. - NS_tremove(dlogFile); - CopyFile(slogFile, dlogFile, false); - - STARTUPINFOW si = {sizeof(si), 0}; - PROCESS_INFORMATION pi = {0}; - - bool ok = CreateProcessW(exefullpath, - cmdline, - NULL, // no special security attributes - NULL, // no special thread attributes - false, // don't inherit filehandles - 0, // No special process creation flags - NULL, // inherit my environment - NULL, // use my current directory - &si, - &pi); - free(cmdline); - - if (ok) { - if (!async) - WaitForSingleObject(pi.hProcess, INFINITE); - - CloseHandle(pi.hProcess); - CloseHandle(pi.hThread); - } -} +#include "uachelper.h" #endif static void LaunchCallbackApp(const NS_tchar *workingDir, int argc, NS_tchar **argv) { 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) + if (NS_tchdir(workingDir) != 0) { LOG(("Warning: chdir failed\n")); + } #if defined(USE_EXECV) execv(argv[0], argv); #elif defined(XP_MACOSX) LaunchChild(argc, argv); +#elif defined(MOZ_MAINTENANCE_SERVICE) + // If updater.exe is run as session ID 0 and we have a MOZ_SESSION_ID + // set, then get the unelevated token and use that to start the callback + // application. Getting tokens will only work if the process is running + // as the system account. + DWORD myProcessID = GetCurrentProcessId(); + DWORD mySessionID = 0; + ProcessIdToSessionId(myProcessID, &mySessionID); + nsAutoHandle unelevatedToken(NULL); + if (mySessionID == 0) { + WCHAR *sessionIDStr = _wgetenv(L"MOZ_SESSION_ID"); + if (sessionIDStr) { + // Remove the env var now that we have its value. + int callbackSessionID = _wtoi(sessionIDStr); + _wputenv(L"MOZ_SESSION_ID="); + unelevatedToken.own(UACHelper::OpenUserToken(callbackSessionID)); + } + } + WinLaunchChild(argv[0], argc, argv, unelevatedToken); #elif defined(XP_WIN) - WinLaunchChild(argv[0], argc, argv); + WinLaunchChild(argv[0], argc, argv, NULL); #else # warning "Need implementaton of LaunchCallbackApp" #endif } static void WriteStatusFile(int status) { @@ -1867,17 +1661,17 @@ int NS_main(int argc, NS_tchar **argv) } CloseHandle(elevatedFileHandle); return 0; } } #endif - LogInit(); + LogInit(gSourcePath, NS_T("update.log")); LOG(("SOURCE DIRECTORY " LOG_S "\n", argv[1])); LOG(("DESTINATION DIRECTORY " LOG_S "\n", argv[2])); #ifdef XP_WIN // 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(argv[2]) + 2) * sizeof(NS_tchar)); if (!destpath) @@ -2030,17 +1824,30 @@ int NS_main(int argc, NS_tchar **argv) } #endif /* XP_WIN */ LogFinish(); if (argc > callbackIndex) { #if defined(XP_WIN) if (gSucceeded) { - LaunchWinPostProcess(argv[callbackIndex]); + // 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 MOZ_SESSION_ID will not exist. + WCHAR *sessionIDStr = _wgetenv(L"MOZ_SESSION_ID"); + if (!sessionIDStr) { + if (!LaunchWinPostProcess(argv[2], gSourcePath, false, NULL)) { + LOG(("NS_main: The post update process could not be launched.\n")); + } + StartServiceUpdate(argc, argv); + } } EXIT_WHEN_ELEVATED(elevatedLockFilePath, updateLockFileHandle, 0); #endif /* XP_WIN */ #ifdef XP_MACOSX if (gSucceeded) { LaunchMacPostProcess(argv[callbackIndex]); } #endif /* XP_MACOSX */
--- a/toolkit/toolkit-makefiles.sh +++ b/toolkit/toolkit-makefiles.sh @@ -468,16 +468,17 @@ MAKEFILES_xulapp=" toolkit/components/console/Makefile toolkit/components/contentprefs/Makefile toolkit/components/cookie/Makefile toolkit/components/downloads/Makefile toolkit/components/exthelper/Makefile toolkit/components/filepicker/Makefile toolkit/components/find/Makefile toolkit/components/intl/Makefile + toolkit/components/maintenanceservice/Makefile toolkit/components/microformats/Makefile toolkit/components/parentalcontrols/Makefile toolkit/components/passwordmgr/Makefile toolkit/components/perf/Makefile toolkit/components/places/Makefile toolkit/components/prompts/Makefile toolkit/components/prompts/src/Makefile toolkit/components/startup/Makefile
--- a/toolkit/xre/nsAppRunner.h +++ b/toolkit/xre/nsAppRunner.h @@ -132,17 +132,22 @@ NS_HIDDEN_(nsresult) NS_LockProfilePath(nsILocalFile* aPath, nsILocalFile* aTempPath, nsIProfileUnlocker* *aUnlocker, nsIProfileLock* *aResult); NS_HIDDEN_(void) WriteConsoleLog(); #ifdef XP_WIN BOOL -WinLaunchChild(const PRUnichar *exePath, int argc, char **argv); +WinLaunchChild(const PRUnichar *exePath, int argc, + char **argv, HANDLE userToken = NULL); +BOOL +WinLaunchServiceCommand(const PRUnichar *exePath, int argc, char **argv); +BOOL +WriteStatusPending(LPCWSTR updateDirPath); #endif #define NS_NATIVEAPPSUPPORT_CONTRACTID "@mozilla.org/toolkit/native-app-support;1" // Like nsXREAppData, but releases all strong refs/allocated memory // in the destructor. class ScopedAppData : public nsXREAppData {
--- a/toolkit/xre/nsUpdateDriver.cpp +++ b/toolkit/xre/nsUpdateDriver.cpp @@ -19,16 +19,17 @@ * Portions created by the Initial Developer are Copyright (C) 2005 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Darin Fisher <darin@meer.net> * Ben Turner <mozilla@songbirdnest.com> * Robert Strong <robert.bugzilla@gmail.com> * Josh Aas <josh@mozilla.com> + * Brian R. Bondy <netzen@gmail.com> * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your @@ -185,39 +186,48 @@ GetFile(nsIFile *dir, const nsCSubstring static bool GetStatusFile(nsIFile *dir, nsCOMPtr<nsILocalFile> &result) { return GetFile(dir, NS_LITERAL_CSTRING("update.status"), result); } static bool -IsPending(nsILocalFile *statusFile) +IsPending(nsILocalFile *statusFile, bool &isPendingService) { PRFileDesc *fd = nsnull; nsresult rv = statusFile->OpenNSPRFileDesc(PR_RDONLY, 0660, &fd); if (NS_FAILED(rv)) return false; char buf[32]; const PRInt32 n = PR_Read(fd, buf, sizeof(buf)); PR_Close(fd); if (n < 0) return false; const char kPending[] = "pending"; - return (strncmp(buf, kPending, sizeof(kPending) - 1) == 0); + bool isPending = (strncmp(buf, kPending, sizeof(kPending) - 1) == 0); + + const char kPendingService[] = "pending-service"; + isPendingService = (strncmp(buf, kPendingService, + sizeof(kPendingService) - 1) == 0); + + return isPending || isPendingService; } static bool SetStatusApplying(nsILocalFile *statusFile) { PRFileDesc *fd = nsnull; - nsresult rv = statusFile->OpenNSPRFileDesc(PR_WRONLY, 0660, &fd); + nsresult rv = statusFile->OpenNSPRFileDesc(PR_WRONLY | + PR_TRUNCATE | + PR_CREATE_FILE, + 0660, &fd); if (NS_FAILED(rv)) return false; static const char kApplying[] = "Applying\n"; PR_Write(fd, kApplying, sizeof(kApplying) - 1); PR_Close(fd); return true; @@ -329,17 +339,17 @@ CopyUpdaterIntoUpdateDir(nsIFile *greDir return false; #endif rv = updater->AppendNative(NS_LITERAL_CSTRING(kUpdaterBin)); return NS_SUCCEEDED(rv); } static void ApplyUpdate(nsIFile *greDir, nsIFile *updateDir, nsILocalFile *statusFile, - nsIFile *appDir, int appArgc, char **appArgv) + nsIFile *appDir, int appArgc, char **appArgv, bool isPendingService) { nsresult rv; // Steps: // - mark update as 'applying' // - copy updater into update dir // - run updater w/ appDir as the current working dir @@ -475,18 +485,52 @@ ApplyUpdate(nsIFile *greDir, nsIFile *up PR_SetEnv("MOZ_SAFE_MODE_RESTART=1"); } LOG(("spawning updater process [%s]\n", updaterPath.get())); #if defined(USE_EXECV) execv(updaterPath.get(), argv); #elif defined(XP_WIN) - if (!WinLaunchChild(updaterPathW.get(), argc, argv)) - return; + +#ifndef MOZ_MAINTENANCE_SERVICE + // We never want the service to be used unless we have Firefox + isPendingService = false; +#endif + + if (isPendingService) { + // Make sure the service isn't already busy processing another work item. + SetLastError(ERROR_SUCCESS); + HANDLE serviceRunningEvent = + OpenEvent(EVENT_ALL_ACCESS, + FALSE, + L"Global\\moz-5b780de9-065b-4341-a04f-ddd94b3723e5"); + // Only use the service if we know the event exists. + // If we have a non NULL handle, or if ERROR_ACCESS_DENIED is returned, + // then the event exists. + isPendingService = !serviceRunningEvent && + GetLastError() != ERROR_ACCESS_DENIED; + if (serviceRunningEvent) { + CloseHandle(serviceRunningEvent); + } + } + + // Launch the update operation using the service if the status file said so. + // We also set the status to pending to ensure we never attempt to use the + // service more than once in a row for a single update. + if (!isPendingService || + !WriteStatusPending(NS_ConvertUTF8toUTF16(updateDirPath).get()) || + !WinLaunchServiceCommand(updaterPathW.get(), argc, argv)) { + // Launch the update using updater.exe + if (!WinLaunchChild(updaterPathW.get(), argc, argv)) { + return; + } + } + + // We are going to process an update so we should exit now _exit(0); #elif defined(XP_MACOSX) CommandLineServiceMac::SetupMacCommandLine(argc, argv, true); // LaunchChildMac uses posix_spawnp and prefers the current // architecture when launching. It doesn't require a // null-terminated string but it doesn't matter if we pass one. LaunchChildMac(argc, argv); exit(0); @@ -511,25 +555,28 @@ ProcessUpdates(nsIFile *greDir, nsIFile if (NS_FAILED(rv)) return rv; rv = updatesDir->AppendNative(NS_LITERAL_CSTRING("0")); if (NS_FAILED(rv)) return rv; nsCOMPtr<nsILocalFile> statusFile; - if (GetStatusFile(updatesDir, statusFile) && IsPending(statusFile)) { + bool isPendingService; + if (GetStatusFile(updatesDir, statusFile) && + IsPending(statusFile, isPendingService)) { nsCOMPtr<nsILocalFile> versionFile; nsCOMPtr<nsILocalFile> channelChangeFile; // Remove the update if the update application version file doesn't exist // or if the update's application version is less than the current // application version. if (!GetChannelChangeFile(updatesDir, channelChangeFile) && (!GetVersionFile(updatesDir, versionFile) || IsOlderVersion(versionFile, appVersion))) { updatesDir->Remove(true); } else { - ApplyUpdate(greDir, updatesDir, statusFile, appDir, argc, argv); + ApplyUpdate(greDir, updatesDir, statusFile, appDir, + argc, argv, isPendingService); } } return NS_OK; }
--- a/toolkit/xre/nsWindowsRestart.cpp +++ b/toolkit/xre/nsWindowsRestart.cpp @@ -16,16 +16,17 @@ * The Initial Developer of the Original Code is * Benjamin Smedberg <benjamin@smedbergs.us>. * * Portions created by the Initial Developer are Copyright (C) 2005 * the Mozilla Foundation. All Rights Reserved. * * Contributor(s): * Robert Strong <robert.bugzilla@gmail.com> + * Brian R. Bondy <netzen@gmail.com> * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your @@ -41,18 +42,29 @@ #ifdef nsWindowsRestart_cpp #error "nsWindowsRestart.cpp is not a header file, and must only be included once." #else #define nsWindowsRestart_cpp #endif #include "nsUTF8Utils.h" +#include "nsWindowsHelpers.h" #include <shellapi.h> +#include <shlwapi.h> +#include <shlobj.h> +#include <stdio.h> +#include <wchar.h> +#include <rpc.h> +#include <userenv.h> + +#pragma comment(lib, "shlwapi.lib") +#pragma comment(lib, "rpcrt4.lib") +#pragma comment(lib, "userenv.lib") #ifndef ERROR_ELEVATION_REQUIRED #define ERROR_ELEVATION_REQUIRED 740L #endif BOOL (WINAPI *pCreateProcessWithTokenW)(HANDLE, DWORD, LPCWSTR, @@ -157,17 +169,17 @@ static PRUnichar* ArgToString(PRUnichar } /** * Creates a command line from a list of arguments. The returned * string is allocated with "malloc" and should be "free"d. * * argv is UTF8 */ -static PRUnichar* +PRUnichar* MakeCommandLine(int argc, PRUnichar **argv) { int i; int len = 0; // The + 1 of the last argument handles the allocation for null termination for (i = 0; i < argc; ++i) len += ArgStrLen(argv[i]) + 1; @@ -220,84 +232,429 @@ FreeAllocStrings(int argc, PRUnichar **a --argc; delete [] argv[argc]; } delete [] argv; } /** - * Launch a child process with the specified arguments. - * @note argv[0] is ignored - * @note The form of this function that takes char **argv expects UTF-8 + * Determines if the maintenance service is running or not. + * + * @return TRUE if the maintenance service is running. +*/ +BOOL +EnsureWindowsServiceRunning() { + // Get a handle to the SCM database. + nsAutoServiceHandle serviceManager(OpenSCManager(NULL, NULL, + SC_MANAGER_CONNECT | + SC_MANAGER_ENUMERATE_SERVICE)); + if (!serviceManager) { + return FALSE; + } + + // Get a handle to the service. + nsAutoServiceHandle service(OpenServiceW(serviceManager, + L"MozillaMaintenance", + SERVICE_QUERY_STATUS | SERVICE_START)); + if (!service) { + return FALSE; + } + + // Make sure the service is not stopped. + SERVICE_STATUS_PROCESS ssp; + DWORD bytesNeeded; + if (!QueryServiceStatusEx(service, SC_STATUS_PROCESS_INFO, (LPBYTE)&ssp, + sizeof(SERVICE_STATUS_PROCESS), &bytesNeeded)) { + return FALSE; + } + + if (ssp.dwCurrentState == SERVICE_STOPPED) { + if (!StartService(service, 0, NULL)) { + return FALSE; + } + + // Make sure we can get into a started state without waiting too long. + // This usually starts instantly but the extra code is just in case it + // takes longer. + DWORD totalWaitTime = 0; + static const int maxWaitTime = 1000 * 5; // Never wait more than 5 seconds + while (QueryServiceStatusEx(service, SC_STATUS_PROCESS_INFO, (LPBYTE)&ssp, + sizeof(SERVICE_STATUS_PROCESS), &bytesNeeded)) { + if (ssp.dwCurrentState == SERVICE_RUNNING) { + break; + } + + if (ssp.dwCurrentState == SERVICE_START_PENDING && + totalWaitTime > maxWaitTime) { + // We will probably eventually start, but we can't wait any longer. + break; + } + + if (ssp.dwCurrentState != SERVICE_START_PENDING) { + return FALSE; + } + + Sleep(ssp.dwWaitHint); + // Increment by at least 10 milliseconds to ensure we always make + // progress towards maxWaitTime in case dwWaitHint is 0. + totalWaitTime += (ssp.dwWaitHint + 10); + } + } + + return ssp.dwCurrentState == SERVICE_RUNNING; +} + +/** + * 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); +} + +/** + * Obtains the directory path to store work item files. + * + * @return TRUE if the path was obtained successfully. +*/ +BOOL +GetUpdateDirectoryPath(PRUnichar *path) +{ + HRESULT hr = SHGetFolderPathW(NULL, CSIDL_COMMON_APPDATA, NULL, + 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, NULL); + + if (!PathAppendSafe(path, L"updates")) { + return FALSE; + } + CreateDirectoryW(path, NULL); + return TRUE; +} + +/** + * Launch a service initiated action 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] is ignored + * @return TRUE if successful + */ +BOOL +WinLaunchServiceCommand(const PRUnichar *exePath, int argc, PRUnichar **argv) +{ + // Ensure the service is running, if not we should try to start it, if it is + // not in a running state we cannot execute a service command. + if (!EnsureWindowsServiceRunning()) { + return FALSE; + } + + PRUnichar updateData[MAX_PATH + 1]; + if (!GetUpdateDirectoryPath(updateData)) { + return FALSE; + } -BOOL -WinLaunchChild(const PRUnichar *exePath, int argc, PRUnichar **argv); + // Get a unique filename + PRUnichar tempFilePath[MAX_PATH + 1]; + const int USE_SYSTEM_TIME = 0; + if (!GetTempFileNameW(updateData, L"moz", USE_SYSTEM_TIME, tempFilePath)) { + return FALSE; + } + + const int FILE_SHARE_NONE = 0; + nsAutoHandle updateMetaFile(CreateFileW(tempFilePath, GENERIC_WRITE, + FILE_SHARE_NONE, NULL, CREATE_ALWAYS, + 0, NULL)); + if (updateMetaFile == INVALID_HANDLE_VALUE) { + return FALSE; + } + + // Write out the command ID. + // Command ID 1 is for an update work item file, which is the only supported + // command at this time. + DWORD commandID = 1, commandIDWrote; + BOOL result = WriteFile(updateMetaFile, &commandID, + sizeof(DWORD), + &commandIDWrote, NULL); + + // Write out the command line arguments that are passed to updater.exe + PRUnichar *commandLineBuffer = MakeCommandLine(argc, argv); + DWORD sessionID, sessionIDWrote; + ProcessIdToSessionId(GetCurrentProcessId(), &sessionID); + result |= WriteFile(updateMetaFile, &sessionID, + sizeof(DWORD), + &sessionIDWrote, NULL); + + PRUnichar appBuffer[MAX_PATH + 1]; + ZeroMemory(appBuffer, sizeof(appBuffer)); + wcscpy(appBuffer, exePath); + DWORD appBufferWrote; + result |= WriteFile(updateMetaFile, appBuffer, + MAX_PATH * sizeof(PRUnichar), + &appBufferWrote, NULL); + + PRUnichar workingDirectory[MAX_PATH + 1]; + ZeroMemory(workingDirectory, sizeof(appBuffer)); + GetCurrentDirectoryW(sizeof(workingDirectory) / sizeof(workingDirectory[0]), + workingDirectory); + DWORD workingDirectoryWrote; + result |= WriteFile(updateMetaFile, workingDirectory, + MAX_PATH * sizeof(PRUnichar), + &workingDirectoryWrote, NULL); + + DWORD commandLineLength = wcslen(commandLineBuffer) * sizeof(PRUnichar); + DWORD commandLineWrote; + result |= WriteFile(updateMetaFile, commandLineBuffer, + commandLineLength, + &commandLineWrote, NULL); + free(commandLineBuffer); + if (!result || + sessionIDWrote != sizeof(DWORD) || + commandIDWrote != sizeof(DWORD) || + appBufferWrote != MAX_PATH * sizeof(PRUnichar) || + workingDirectoryWrote != MAX_PATH * sizeof(PRUnichar) || + commandLineWrote != commandLineLength) { + updateMetaFile.reset(); + DeleteFileW(tempFilePath); + return FALSE; + } + + // Note we construct the 'service work' meta object with a .tmp extension, + // When we want the service to start processing it we simply rename it to + // have a .mz extension. This ensures that the service will never try to + // process a partial update work meta file. + updateMetaFile.reset(); + PRUnichar completedMetaFilePath[MAX_PATH + 1]; + wcscpy(completedMetaFilePath, tempFilePath); + // Change the file extension of the temp file path from .tmp to .mz + LPWSTR extensionPart = + &(completedMetaFilePath[wcslen(completedMetaFilePath) - 3]); + wcscpy(extensionPart, L"mz"); + return MoveFileExW(tempFilePath, completedMetaFilePath, + MOVEFILE_REPLACE_EXISTING); +} + +/** + * 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 -WinLaunchChild(const PRUnichar *exePath, int argc, char **argv) +WriteStatusPending(LPCWSTR updateDirPath) +{ + PRUnichar updateStatusFilePath[MAX_PATH + 1]; + wcscpy(updateStatusFilePath, updateDirPath); + if (!PathAppendSafe(updateStatusFilePath, L"update.status")) { + return FALSE; + } + + const char pending[] = "pending"; + nsAutoHandle statusFile(CreateFileW(updateStatusFilePath, GENERIC_WRITE, 0, + NULL, CREATE_ALWAYS, 0, NULL)); + if (statusFile == INVALID_HANDLE_VALUE) { + return FALSE; + } + + DWORD wrote; + BOOL ok = WriteFile(statusFile, pending, + sizeof(pending) - 1, &wrote, NULL); + 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) +{ + PRUnichar updateStatusFilePath[MAX_PATH + 1]; + wcscpy(updateStatusFilePath, updateDirPath); + if (!PathAppendSafe(updateStatusFilePath, L"update.status")) { + return FALSE; + } + + nsAutoHandle statusFile(CreateFileW(updateStatusFilePath, GENERIC_WRITE, 0, + NULL, CREATE_ALWAYS, 0, NULL)); + 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, NULL); + return ok && wrote == toWrite; +} + +/** + * Launch a service initiated action 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] is ignored + * @return TRUE if successful + */ +BOOL +WinLaunchServiceCommand(const PRUnichar *exePath, int argc, char **argv) { PRUnichar** argvConverted = new PRUnichar*[argc]; if (!argvConverted) return FALSE; for (int i = 0; i < argc; ++i) { argvConverted[i] = AllocConvertUTF8toUTF16(argv[i]); if (!argvConverted[i]) { FreeAllocStrings(i, argvConverted); return FALSE; } } - BOOL ok = WinLaunchChild(exePath, argc, argvConverted); + BOOL ok = WinLaunchServiceCommand(exePath, argc, argvConverted); + FreeAllocStrings(argc, argvConverted); + return ok; +} + + + +/** + * Launch a child process with the specified arguments. + * @note argv[0] is ignored + * @note The form of this function that takes char **argv expects UTF-8 + */ + +BOOL +WinLaunchChild(const PRUnichar *exePath, + int argc, PRUnichar **argv, + HANDLE userToken = NULL); + +BOOL +WinLaunchChild(const PRUnichar *exePath, + int argc, char **argv, + HANDLE userToken) +{ + PRUnichar** argvConverted = new PRUnichar*[argc]; + if (!argvConverted) + return FALSE; + + for (int i = 0; i < argc; ++i) { + argvConverted[i] = AllocConvertUTF8toUTF16(argv[i]); + if (!argvConverted[i]) { + FreeAllocStrings(i, argvConverted); + return FALSE; + } + } + + BOOL ok = WinLaunchChild(exePath, argc, argvConverted, userToken); FreeAllocStrings(argc, argvConverted); return ok; } BOOL -WinLaunchChild(const PRUnichar *exePath, int argc, PRUnichar **argv) +WinLaunchChild(const PRUnichar *exePath, + int argc, + PRUnichar **argv, + HANDLE userToken) { PRUnichar *cl; BOOL ok; cl = MakeCommandLine(argc, argv); - if (!cl) + if (!cl) { return FALSE; + } - STARTUPINFOW si = {sizeof(si), 0}; + STARTUPINFOW si = {0}; + si.cb = sizeof(STARTUPINFOW); + si.lpDesktop = L"winsta0\\Default"; PROCESS_INFORMATION pi = {0}; - ok = CreateProcessW(exePath, - cl, - NULL, // no special security attributes - NULL, // no special thread attributes - FALSE, // don't inherit filehandles - 0, // No special process creation flags - NULL, // inherit my environment - NULL, // use my current directory - &si, - &pi); + if (userToken == NULL) { + ok = CreateProcessW(exePath, + cl, + NULL, // no special security attributes + NULL, // no special thread attributes + FALSE, // don't inherit filehandles + 0, // No special process creation flags + NULL, // inherit my environment + NULL, // use my current directory + &si, + &pi); + } else { + // Create an environment block for the process we're about to start using + // the user's token. + LPVOID environmentBlock = NULL; + if (!CreateEnvironmentBlock(&environmentBlock, userToken, TRUE)) { + environmentBlock = NULL; + } + + ok = CreateProcessAsUserW(userToken, + exePath, + cl, + NULL, // no special security attributes + NULL, // no special thread attributes + FALSE, // don't inherit filehandles + CREATE_DEFAULT_ERROR_MODE | +#ifdef DEBUG + CREATE_NEW_CONSOLE | +#endif + CREATE_UNICODE_ENVIRONMENT, + environmentBlock, + NULL, // use my current directory + &si, + &pi); + + if (environmentBlock) { + DestroyEnvironmentBlock(environmentBlock); + } + } if (ok) { CloseHandle(pi.hProcess); CloseHandle(pi.hThread); } else { LPVOID lpMsgBuf = NULL; FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | - FORMAT_MESSAGE_FROM_SYSTEM | - FORMAT_MESSAGE_IGNORE_INSERTS, - NULL, - GetLastError(), - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - (LPTSTR) &lpMsgBuf, - 0, - NULL - ); + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + GetLastError(), + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR) &lpMsgBuf, + 0, + NULL); wprintf(L"Error restarting: %s\n", lpMsgBuf ? lpMsgBuf : L"(null)"); if (lpMsgBuf) LocalFree(lpMsgBuf); } free(cl); return ok; } +
--- a/xpcom/base/Makefile.in +++ b/xpcom/base/Makefile.in @@ -133,16 +133,22 @@ SDK_HEADERS = \ nsAutoPtr.h \ nsError.h \ nsISupportsBase.h \ nscore.h \ nsAtomicRefcnt.h \ nsCycleCollector.h \ nsObjCExceptions.h \ +ifeq ($(OS_ARCH),WINNT) +SDK_HEADERS += \ + nsWindowsHelpers.h \ + $(NULL) +endif + XPIDLSRCS = \ nsIConsoleListener.idl \ nsIConsoleMessage.idl \ nsIConsoleService.idl \ nsICycleCollectorListener.idl \ nsIDebug2.idl \ nsIErrorService.idl \ nsIException.idl \
new file mode 100644 --- /dev/null +++ b/xpcom/base/nsWindowsHelpers.h @@ -0,0 +1,118 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is an RAII helper classes for Windows development. + * + * The Initial Developer of the Original Code is + * Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Brian R. Bondy <netzen@gmail.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef nsWindowsHelpers_h +#define nsWindowsHelpers_h + +#include "nsAutoRef.h" +#include "nscore.h" + +template<> +class nsAutoRefTraits<HKEY> +{ +public: + typedef HKEY RawRef; + static HKEY Void() + { + return NULL; + } + + static void Release(RawRef aFD) + { + if (aFD != Void()) { + RegCloseKey(aFD); + } + } +}; + +template<> +class nsAutoRefTraits<SC_HANDLE> +{ +public: + typedef SC_HANDLE RawRef; + static SC_HANDLE Void() + { + return NULL; + } + + static void Release(RawRef aFD) + { + if (aFD != Void()) { + CloseServiceHandle(aFD); + } + } +}; + +template<> +class nsSimpleRef<HANDLE> +{ +protected: + typedef HANDLE RawRef; + + nsSimpleRef() : mRawRef(NULL) + { + } + + nsSimpleRef(RawRef aRawRef) : mRawRef(aRawRef) + { + } + + bool HaveResource() const + { + return mRawRef != NULL && mRawRef != INVALID_HANDLE_VALUE; + } + +public: + RawRef get() const + { + return mRawRef; + } + + static void Release(RawRef aRawRef) + { + if (aRawRef != NULL && aRawRef != INVALID_HANDLE_VALUE) { + CloseHandle(aRawRef); + } + } + RawRef mRawRef; +}; + +typedef nsAutoRef<HKEY> nsAutoRegKey; +typedef nsAutoRef<SC_HANDLE> nsAutoServiceHandle; +typedef nsAutoRef<HANDLE> nsAutoHandle; + +#endif
--- a/xpcom/ds/nsIWindowsRegKey.idl +++ b/xpcom/ds/nsIWindowsRegKey.idl @@ -78,16 +78,19 @@ interface nsIWindowsRegKey : nsISupports ACCESS_QUERY_VALUE | ACCESS_ENUMERATE_SUB_KEYS | ACCESS_NOTIFY; const unsigned long ACCESS_WRITE = ACCESS_BASIC | ACCESS_SET_VALUE | ACCESS_CREATE_SUB_KEY; const unsigned long ACCESS_ALL = ACCESS_READ | ACCESS_WRITE; + const unsigned long WOW64_32 = 0x00000200; + const unsigned long WOW64_64 = 0x00000100; + /** * Values for the type of a registry value. The numeric values of these * constants are taken directly from WinNT.h in the MS Platform SDK. * The Microsoft documentation should be consulted for the exact meaning of * these value types. * * This interface is somewhat restricted to using only these value types.