mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-09-04 23:08:39 +09:00
parent
aa5b2021ca
commit
46927bbb59
14 changed files with 0 additions and 2529 deletions
|
|
@ -1,13 +0,0 @@
|
|||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
ifndef MOZ_WINCONSOLE
|
||||
ifdef MOZ_DEBUG
|
||||
MOZ_WINCONSOLE = 1
|
||||
else
|
||||
MOZ_WINCONSOLE = 0
|
||||
endif
|
||||
endif
|
||||
|
||||
include $(topsrcdir)/config/rules.mk
|
||||
|
|
@ -1,278 +0,0 @@
|
|||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
; 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
|
||||
|
||||
; The commands inside this ifdef require NSIS 3.0a2 or greater so the ifdef can
|
||||
; be removed after we require NSIS 3.0a2 or greater.
|
||||
!ifdef NSIS_PACKEDVERSION
|
||||
Unicode true
|
||||
ManifestSupportedOS all
|
||||
ManifestDPIAware true
|
||||
!endif
|
||||
|
||||
!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"
|
||||
|
||||
; Always install into the 32-bit location even if we have a 64-bit build.
|
||||
; This is because we use only 1 service for all Firefox channels.
|
||||
; Allow either x86 and x64 builds to exist at this location, depending on
|
||||
; what is the latest build.
|
||||
InstallDir "$PROGRAMFILES32\${MaintFullName}\"
|
||||
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_CONFIRM
|
||||
!insertmacro MUI_UNPAGE_INSTFILES
|
||||
|
||||
################################################################################
|
||||
# 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
|
||||
; Remove the current exe directory from the search order.
|
||||
; This only effects LoadLibrary calls and not implicitly loaded DLLs.
|
||||
System::Call 'kernel32::SetDllDirectoryW(w "")'
|
||||
|
||||
SetSilent silent
|
||||
; 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
|
||||
; Remove the current exe directory from the search order.
|
||||
; This only effects LoadLibrary calls and not implicitly loaded DLLs.
|
||||
System::Call 'kernel32::SetDllDirectoryW(w "")'
|
||||
|
||||
; The commands inside this ifndef are needed prior to NSIS 3.0a2 and can be
|
||||
; removed after we require NSIS 3.0a2 or greater.
|
||||
!ifndef NSIS_PACKEDVERSION
|
||||
${If} ${AtLeastWinVista}
|
||||
System::Call 'user32::SetProcessDPIAware()'
|
||||
${EndIf}
|
||||
!endif
|
||||
|
||||
StrCpy $BrandFullNameDA "${MaintFullName}"
|
||||
StrCpy $BrandFullName "${MaintFullName}"
|
||||
FunctionEnd
|
||||
|
||||
Section "MaintenanceService"
|
||||
AllowSkipFiles off
|
||||
|
||||
CreateDirectory $INSTDIR
|
||||
SetOutPath $INSTDIR
|
||||
|
||||
; If the service already exists, then it will be stopped when upgrading it
|
||||
; via the maintenanceservice_tmp.exe command executed below.
|
||||
; The maintenanceservice_tmp.exe command will rename the file to
|
||||
; maintenanceservice.exe if maintenanceservice_tmp.exe is newer.
|
||||
; If the service does not exist yet, we install it and drop the file on
|
||||
; disk as maintenanceservice.exe directly.
|
||||
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"
|
||||
|
||||
; The updater.ini file is only used when performing an install or upgrade,
|
||||
; and only if that install or upgrade is successful. If an old updater.ini
|
||||
; happened to be copied into the maintenance service installation directory
|
||||
; but the service was not newer, the updater.ini file would be unused.
|
||||
; It is used to fill the description of the service on success.
|
||||
CopyFiles "$EXEDIR\updater.ini" "$INSTDIR\updater.ini"
|
||||
|
||||
; 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}
|
||||
ExecWait '"$INSTDIR\$TempMaintServiceName" forceinstall'
|
||||
;${Else}
|
||||
; The upgrade cmdline is the same as install except
|
||||
; It will fail if the service isn't already installed.
|
||||
; ExecWait '"$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}"
|
||||
WriteRegDWORD HKLM "${MaintUninstallKey}" "NoModify" 1
|
||||
${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.
|
||||
${If} ${RunningX64}
|
||||
SetRegView 64
|
||||
${EndIf}
|
||||
WriteRegDWORD HKLM "Software\Mozilla\MaintenanceService" "Attempted" 1
|
||||
WriteRegDWORD HKLM "Software\Mozilla\MaintenanceService" "Installed" 1
|
||||
DeleteRegValue HKLM "Software\Mozilla\MaintenanceService" "FFPrefetchDisabled"
|
||||
|
||||
; 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" "DigiCert SHA2 Assured ID Code Signing CA"
|
||||
WriteRegStr HKLM "${FallbackKey}\1" "name" "Mozilla Fake SPC"
|
||||
WriteRegStr HKLM "${FallbackKey}\1" "issuer" "Mozilla Fake CA"
|
||||
${If} ${RunningX64}
|
||||
SetRegView lastused
|
||||
${EndIf}
|
||||
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
|
||||
ExecWait '"$INSTDIR\maintenanceservice.exe" uninstall'
|
||||
|
||||
Push "$INSTDIR\updater.ini"
|
||||
Call un.RenameDelete
|
||||
Push "$INSTDIR\maintenanceservice.exe"
|
||||
Call un.RenameDelete
|
||||
Push "$INSTDIR\maintenanceservice_tmp.exe"
|
||||
Call un.RenameDelete
|
||||
Push "$INSTDIR\maintenanceservice.old"
|
||||
Call un.RenameDelete
|
||||
Push "$INSTDIR\Uninstall.exe"
|
||||
Call un.RenameDelete
|
||||
Push "$INSTDIR\update\updater.ini"
|
||||
Call un.RenameDelete
|
||||
Push "$INSTDIR\update\updater.exe"
|
||||
Call un.RenameDelete
|
||||
RMDir /REBOOTOK "$INSTDIR\update"
|
||||
RMDir /REBOOTOK "$INSTDIR"
|
||||
DeleteRegKey HKLM "${MaintUninstallKey}"
|
||||
|
||||
${If} ${RunningX64}
|
||||
SetRegView 64
|
||||
${EndIf}
|
||||
DeleteRegValue HKLM "Software\Mozilla\MaintenanceService" "Installed"
|
||||
DeleteRegValue HKLM "Software\Mozilla\MaintenanceService" "FFPrefetchDisabled"
|
||||
DeleteRegKey HKLM "${FallbackKey}\"
|
||||
${If} ${RunningX64}
|
||||
SetRegView lastused
|
||||
${EndIf}
|
||||
SectionEnd
|
||||
|
||||
|
|
@ -1,391 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include <windows.h>
|
||||
#include <shlwapi.h>
|
||||
#include <stdio.h>
|
||||
#include <wchar.h>
|
||||
#include <shlobj.h>
|
||||
|
||||
#include "serviceinstall.h"
|
||||
#include "maintenanceservice.h"
|
||||
#include "servicebase.h"
|
||||
#include "workmonitor.h"
|
||||
#include "uachelper.h"
|
||||
#include "updatehelper.h"
|
||||
#include "registrycertificates.h"
|
||||
|
||||
// Link w/ subsystem window so we don't get a console when executing
|
||||
// this binary through the installer.
|
||||
#pragma comment(linker, "/SUBSYSTEM:windows")
|
||||
|
||||
SERVICE_STATUS gSvcStatus = { 0 };
|
||||
SERVICE_STATUS_HANDLE gSvcStatusHandle = nullptr;
|
||||
HANDLE gWorkDoneEvent = nullptr;
|
||||
HANDLE gThread = nullptr;
|
||||
bool gServiceControlStopping = false;
|
||||
|
||||
// logs are pretty small, about 20 lines, so 10 seems reasonable.
|
||||
#define LOGS_TO_KEEP 10
|
||||
|
||||
BOOL GetLogDirectoryPath(WCHAR *path);
|
||||
|
||||
int
|
||||
wmain(int argc, WCHAR **argv)
|
||||
{
|
||||
// If command-line parameter is "install", install the service
|
||||
// or upgrade if already installed
|
||||
// If command line parameter is "forceinstall", install the service
|
||||
// even if it is older than what is already installed.
|
||||
// If command-line parameter is "upgrade", upgrade the service
|
||||
// but do not install it if it is not already installed.
|
||||
// If command line parameter is "uninstall", uninstall the service.
|
||||
// Otherwise, the service is probably being started by the SCM.
|
||||
bool forceInstall = !lstrcmpi(argv[1], L"forceinstall");
|
||||
if (!lstrcmpi(argv[1], L"install") || forceInstall) {
|
||||
WCHAR updatePath[MAX_PATH + 1];
|
||||
if (GetLogDirectoryPath(updatePath)) {
|
||||
LogInit(updatePath, L"maintenanceservice-install.log");
|
||||
}
|
||||
|
||||
SvcInstallAction action = InstallSvc;
|
||||
if (forceInstall) {
|
||||
action = ForceInstallSvc;
|
||||
LOG(("Installing service with force specified..."));
|
||||
} else {
|
||||
LOG(("Installing service..."));
|
||||
}
|
||||
|
||||
bool ret = SvcInstall(action);
|
||||
if (!ret) {
|
||||
LOG_WARN(("Could not install service. (%d)", GetLastError()));
|
||||
LogFinish();
|
||||
return 1;
|
||||
}
|
||||
|
||||
LOG(("The service was installed successfully"));
|
||||
LogFinish();
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!lstrcmpi(argv[1], L"upgrade")) {
|
||||
WCHAR updatePath[MAX_PATH + 1];
|
||||
if (GetLogDirectoryPath(updatePath)) {
|
||||
LogInit(updatePath, L"maintenanceservice-install.log");
|
||||
}
|
||||
|
||||
LOG(("Upgrading service if installed..."));
|
||||
if (!SvcInstall(UpgradeSvc)) {
|
||||
LOG_WARN(("Could not upgrade service. (%d)", GetLastError()));
|
||||
LogFinish();
|
||||
return 1;
|
||||
}
|
||||
|
||||
LOG(("The service was upgraded successfully"));
|
||||
LogFinish();
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!lstrcmpi(argv[1], L"uninstall")) {
|
||||
WCHAR updatePath[MAX_PATH + 1];
|
||||
if (GetLogDirectoryPath(updatePath)) {
|
||||
LogInit(updatePath, L"maintenanceservice-uninstall.log");
|
||||
}
|
||||
LOG(("Uninstalling service..."));
|
||||
if (!SvcUninstall()) {
|
||||
LOG_WARN(("Could not uninstall service. (%d)", GetLastError()));
|
||||
LogFinish();
|
||||
return 1;
|
||||
}
|
||||
LOG(("The service was uninstalled successfully"));
|
||||
LogFinish();
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!lstrcmpi(argv[1], L"check-cert") && argc > 2) {
|
||||
return DoesBinaryMatchAllowedCertificates(argv[2], argv[3], FALSE) ? 0 : 1;
|
||||
}
|
||||
|
||||
SERVICE_TABLE_ENTRYW DispatchTable[] = {
|
||||
{ SVC_NAME, (LPSERVICE_MAIN_FUNCTIONW) SvcMain },
|
||||
{ nullptr, nullptr }
|
||||
};
|
||||
|
||||
// This call returns when the service has stopped.
|
||||
// The process should simply terminate when the call returns.
|
||||
if (!StartServiceCtrlDispatcherW(DispatchTable)) {
|
||||
LOG_WARN(("StartServiceCtrlDispatcher failed. (%d)", GetLastError()));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains the base path where logs should be stored
|
||||
*
|
||||
* @param path The out buffer for the backup log path of size MAX_PATH + 1
|
||||
* @return TRUE if successful.
|
||||
*/
|
||||
BOOL
|
||||
GetLogDirectoryPath(WCHAR *path)
|
||||
{
|
||||
if (!GetModuleFileNameW(nullptr, path, MAX_PATH)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!PathRemoveFileSpecW(path)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!PathAppendSafe(path, L"logs")) {
|
||||
return FALSE;
|
||||
}
|
||||
CreateDirectoryW(path, nullptr);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculated a backup path based on the log number.
|
||||
*
|
||||
* @param path The out buffer to store the log path of size MAX_PATH + 1
|
||||
* @param basePath The base directory where the calculated path should go
|
||||
* @param logNumber The log number, 0 == updater.log
|
||||
* @return TRUE if successful.
|
||||
*/
|
||||
BOOL
|
||||
GetBackupLogPath(LPWSTR path, LPCWSTR basePath, int logNumber)
|
||||
{
|
||||
WCHAR logName[64] = { L'\0' };
|
||||
wcsncpy(path, basePath, sizeof(logName) / sizeof(logName[0]) - 1);
|
||||
if (logNumber <= 0) {
|
||||
swprintf(logName, sizeof(logName) / sizeof(logName[0]),
|
||||
L"maintenanceservice.log");
|
||||
} else {
|
||||
swprintf(logName, sizeof(logName) / sizeof(logName[0]),
|
||||
L"maintenanceservice-%d.log", logNumber);
|
||||
}
|
||||
return PathAppendSafe(path, logName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the old log files out of the way before a new one is written.
|
||||
* If you for example keep 3 logs, then this function will do:
|
||||
* updater2.log -> updater3.log
|
||||
* updater1.log -> updater2.log
|
||||
* updater.log -> updater1.log
|
||||
* Which clears room for a new updater.log in the basePath directory
|
||||
*
|
||||
* @param basePath The base directory path where log files are stored
|
||||
* @param numLogsToKeep The number of logs to keep
|
||||
*/
|
||||
void
|
||||
BackupOldLogs(LPCWSTR basePath, int numLogsToKeep)
|
||||
{
|
||||
WCHAR oldPath[MAX_PATH + 1];
|
||||
WCHAR newPath[MAX_PATH + 1];
|
||||
for (int i = numLogsToKeep; i >= 1; i--) {
|
||||
if (!GetBackupLogPath(oldPath, basePath, i -1)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!GetBackupLogPath(newPath, basePath, i)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!MoveFileExW(oldPath, newPath, MOVEFILE_REPLACE_EXISTING)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the service is shutdown once all work is complete.
|
||||
* There is an issue on XP SP2 and below where the service can hang
|
||||
* in a stop pending state even though the SCM is notified of a stopped
|
||||
* state. Control *should* be returned to StartServiceCtrlDispatcher from the
|
||||
* call to SetServiceStatus on a stopped state in the wmain thread.
|
||||
* Sometimes this is not the case though. This thread will terminate the process
|
||||
* if it has been 5 seconds after all work is done and the process is still not
|
||||
* terminated. This thread is only started once a stopped state was sent to the
|
||||
* SCM. The stop pending hang can be reproduced intermittently even if you set
|
||||
* a stopped state dirctly and never set a stop pending state. It is safe to
|
||||
* forcefully terminate the process ourselves since all work is done once we
|
||||
* start this thread.
|
||||
*/
|
||||
DWORD WINAPI
|
||||
EnsureProcessTerminatedThread(LPVOID)
|
||||
{
|
||||
Sleep(5000);
|
||||
exit(0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
StartTerminationThread()
|
||||
{
|
||||
// If the process does not self terminate like it should, this thread
|
||||
// will terminate the process after 5 seconds.
|
||||
HANDLE thread = CreateThread(nullptr, 0, EnsureProcessTerminatedThread,
|
||||
nullptr, 0, nullptr);
|
||||
if (thread) {
|
||||
CloseHandle(thread);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Main entry point when running as a service.
|
||||
*/
|
||||
void WINAPI
|
||||
SvcMain(DWORD argc, LPWSTR *argv)
|
||||
{
|
||||
// Setup logging, and backup the old logs
|
||||
WCHAR updatePath[MAX_PATH + 1];
|
||||
if (GetLogDirectoryPath(updatePath)) {
|
||||
BackupOldLogs(updatePath, LOGS_TO_KEEP);
|
||||
LogInit(updatePath, L"maintenanceservice.log");
|
||||
}
|
||||
|
||||
// Disable every privilege we don't need. Processes started using
|
||||
// CreateProcess will use the same token as this process.
|
||||
UACHelper::DisablePrivileges(nullptr);
|
||||
|
||||
// Register the handler function for the service
|
||||
gSvcStatusHandle = RegisterServiceCtrlHandlerW(SVC_NAME, SvcCtrlHandler);
|
||||
if (!gSvcStatusHandle) {
|
||||
LOG_WARN(("RegisterServiceCtrlHandler failed. (%d)", GetLastError()));
|
||||
ExecuteServiceCommand(argc, argv);
|
||||
LogFinish();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// These values will be re-used later in calls involving gSvcStatus
|
||||
gSvcStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
|
||||
gSvcStatus.dwServiceSpecificExitCode = 0;
|
||||
|
||||
// Report initial status to the SCM
|
||||
ReportSvcStatus(SERVICE_START_PENDING, NO_ERROR, 3000);
|
||||
|
||||
// This event will be used to tell the SvcCtrlHandler when the work is
|
||||
// done for when a stop comamnd is manually issued.
|
||||
gWorkDoneEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr);
|
||||
if (!gWorkDoneEvent) {
|
||||
ReportSvcStatus(SERVICE_STOPPED, 1, 0);
|
||||
StartTerminationThread();
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialization complete and we're about to start working on
|
||||
// the actual command. Report the service state as running to the SCM.
|
||||
ReportSvcStatus(SERVICE_RUNNING, NO_ERROR, 0);
|
||||
|
||||
// The service command was executed, stop logging and set an event
|
||||
// to indicate the work is done in case someone is waiting on a
|
||||
// service stop operation.
|
||||
ExecuteServiceCommand(argc, argv);
|
||||
LogFinish();
|
||||
|
||||
SetEvent(gWorkDoneEvent);
|
||||
|
||||
// If we aren't already in a stopping state then tell the SCM we're stopped
|
||||
// now. If we are already in a stopping state then the SERVICE_STOPPED state
|
||||
// will be set by the SvcCtrlHandler.
|
||||
if (!gServiceControlStopping) {
|
||||
ReportSvcStatus(SERVICE_STOPPED, NO_ERROR, 0);
|
||||
StartTerminationThread();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current service status and reports it to the SCM.
|
||||
*
|
||||
* @param currentState The current state (see SERVICE_STATUS)
|
||||
* @param exitCode The system error code
|
||||
* @param waitHint Estimated time for pending operation in milliseconds
|
||||
*/
|
||||
void
|
||||
ReportSvcStatus(DWORD currentState,
|
||||
DWORD exitCode,
|
||||
DWORD waitHint)
|
||||
{
|
||||
static DWORD dwCheckPoint = 1;
|
||||
|
||||
gSvcStatus.dwCurrentState = currentState;
|
||||
gSvcStatus.dwWin32ExitCode = exitCode;
|
||||
gSvcStatus.dwWaitHint = waitHint;
|
||||
|
||||
if (SERVICE_START_PENDING == currentState ||
|
||||
SERVICE_STOP_PENDING == currentState) {
|
||||
gSvcStatus.dwControlsAccepted = 0;
|
||||
} else {
|
||||
gSvcStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP |
|
||||
SERVICE_ACCEPT_SHUTDOWN;
|
||||
}
|
||||
|
||||
if ((SERVICE_RUNNING == currentState) ||
|
||||
(SERVICE_STOPPED == currentState)) {
|
||||
gSvcStatus.dwCheckPoint = 0;
|
||||
} else {
|
||||
gSvcStatus.dwCheckPoint = dwCheckPoint++;
|
||||
}
|
||||
|
||||
// Report the status of the service to the SCM.
|
||||
SetServiceStatus(gSvcStatusHandle, &gSvcStatus);
|
||||
}
|
||||
|
||||
/**
|
||||
* Since the SvcCtrlHandler should only spend at most 30 seconds before
|
||||
* returning, this function does the service stop work for the SvcCtrlHandler.
|
||||
*/
|
||||
DWORD WINAPI
|
||||
StopServiceAndWaitForCommandThread(LPVOID)
|
||||
{
|
||||
do {
|
||||
ReportSvcStatus(SERVICE_STOP_PENDING, NO_ERROR, 1000);
|
||||
} while(WaitForSingleObject(gWorkDoneEvent, 100) == WAIT_TIMEOUT);
|
||||
CloseHandle(gWorkDoneEvent);
|
||||
gWorkDoneEvent = nullptr;
|
||||
ReportSvcStatus(SERVICE_STOPPED, NO_ERROR, 0);
|
||||
StartTerminationThread();
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by SCM whenever a control code is sent to the service
|
||||
* using the ControlService function.
|
||||
*/
|
||||
void WINAPI
|
||||
SvcCtrlHandler(DWORD dwCtrl)
|
||||
{
|
||||
// After a SERVICE_CONTROL_STOP there should be no more commands sent to
|
||||
// the SvcCtrlHandler.
|
||||
if (gServiceControlStopping) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle the requested control code.
|
||||
switch(dwCtrl) {
|
||||
case SERVICE_CONTROL_SHUTDOWN:
|
||||
case SERVICE_CONTROL_STOP: {
|
||||
gServiceControlStopping = true;
|
||||
ReportSvcStatus(SERVICE_STOP_PENDING, NO_ERROR, 1000);
|
||||
|
||||
// The SvcCtrlHandler thread should not spend more than 30 seconds in
|
||||
// shutdown so we spawn a new thread for stopping the service
|
||||
HANDLE thread = CreateThread(nullptr, 0,
|
||||
StopServiceAndWaitForCommandThread,
|
||||
nullptr, 0, nullptr);
|
||||
if (thread) {
|
||||
CloseHandle(thread);
|
||||
} else {
|
||||
// Couldn't start the thread so just call the stop ourselves.
|
||||
// If it happens to take longer than 30 seconds the caller will
|
||||
// get an error.
|
||||
StopServiceAndWaitForCommandThread(nullptr);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
<?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>
|
||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
|
||||
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
|
||||
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
|
||||
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
|
||||
<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>
|
||||
</application>
|
||||
</compatibility>
|
||||
</assembly>
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
void WINAPI SvcMain(DWORD dwArgc, LPWSTR *lpszArgv);
|
||||
void SvcInit(DWORD dwArgc, LPWSTR *lpszArgv);
|
||||
void WINAPI SvcCtrlHandler(DWORD dwCtrl);
|
||||
void ReportSvcStatus(DWORD dwCurrentState,
|
||||
DWORD dwWin32ExitCode,
|
||||
DWORD dwWaitHint);
|
||||
|
|
@ -1,86 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
// Microsoft Visual C++ generated resource script.
|
||||
//
|
||||
#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
|
||||
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
Program('maintenanceservice')
|
||||
|
||||
SOURCES += [
|
||||
'maintenanceservice.cpp',
|
||||
'servicebase.cpp',
|
||||
'serviceinstall.cpp',
|
||||
'workmonitor.cpp',
|
||||
]
|
||||
|
||||
if CONFIG['OS_ARCH'] == 'WINNT':
|
||||
USE_LIBS += [
|
||||
'updatecommon-standalone',
|
||||
]
|
||||
else:
|
||||
USE_LIBS += [
|
||||
'updatecommon',
|
||||
]
|
||||
|
||||
# For debugging purposes only
|
||||
#DEFINES['DISABLE_UPDATER_AUTHENTICODE_CHECK'] = True
|
||||
|
||||
DEFINES['UNICODE'] = True
|
||||
DEFINES['_UNICODE'] = True
|
||||
DEFINES['NS_NO_XPCOM'] = True
|
||||
|
||||
# Pick up nsWindowsRestart.cpp
|
||||
LOCAL_INCLUDES += [
|
||||
'/toolkit/mozapps/update/common',
|
||||
'/toolkit/xre',
|
||||
]
|
||||
|
||||
USE_STATIC_LIBS = True
|
||||
|
||||
if CONFIG['_MSC_VER']:
|
||||
WIN32_EXE_LDFLAGS += ['-ENTRY:wmainCRTStartup']
|
||||
|
||||
RCINCLUDE = 'maintenanceservice.rc'
|
||||
|
||||
DISABLE_STL_WRAPPING = True
|
||||
|
||||
OS_LIBS += [
|
||||
'comctl32',
|
||||
'ws2_32',
|
||||
'shell32',
|
||||
]
|
||||
|
||||
with Files('**'):
|
||||
BUG_COMPONENT = ('Toolkit', 'Application Update')
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
//{{NO_DEPENDENCIES}}
|
||||
// Microsoft Visual C++ generated include file.
|
||||
// Used by updater.rc
|
||||
//
|
||||
#define IDI_DIALOG 1003
|
||||
|
||||
// Next default values for new objects
|
||||
//
|
||||
#ifdef APSTUDIO_INVOKED
|
||||
#ifndef APSTUDIO_READONLY_SYMBOLS
|
||||
#define _APS_NEXT_RESOURCE_VALUE 102
|
||||
#define _APS_NEXT_COMMAND_VALUE 40001
|
||||
#define _APS_NEXT_CONTROL_VALUE 1003
|
||||
#define _APS_NEXT_SYMED_VALUE 101
|
||||
#endif
|
||||
#endif
|
||||
|
|
@ -1,86 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "servicebase.h"
|
||||
#include "nsWindowsHelpers.h"
|
||||
|
||||
// Shared code between applications and updater.exe
|
||||
#include "nsWindowsRestart.cpp"
|
||||
|
||||
/**
|
||||
* Verifies if 2 files are byte for byte equivalent.
|
||||
*
|
||||
* @param file1Path The first file to verify.
|
||||
* @param file2Path The second file to verify.
|
||||
* @param sameContent Out parameter, TRUE if the files are equal
|
||||
* @return TRUE If there was no error checking the files.
|
||||
*/
|
||||
BOOL
|
||||
VerifySameFiles(LPCWSTR file1Path, LPCWSTR file2Path, BOOL &sameContent)
|
||||
{
|
||||
sameContent = FALSE;
|
||||
nsAutoHandle file1(CreateFileW(file1Path, GENERIC_READ, FILE_SHARE_READ,
|
||||
nullptr, OPEN_EXISTING, 0, nullptr));
|
||||
if (INVALID_HANDLE_VALUE == file1) {
|
||||
return FALSE;
|
||||
}
|
||||
nsAutoHandle file2(CreateFileW(file2Path, GENERIC_READ, FILE_SHARE_READ,
|
||||
nullptr, OPEN_EXISTING, 0, nullptr));
|
||||
if (INVALID_HANDLE_VALUE == file2) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
DWORD fileSize1 = GetFileSize(file1, nullptr);
|
||||
DWORD fileSize2 = GetFileSize(file2, nullptr);
|
||||
if (INVALID_FILE_SIZE == fileSize1 || INVALID_FILE_SIZE == fileSize2) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (fileSize1 != fileSize2) {
|
||||
// sameContent is already set to FALSE
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
char buf1[COMPARE_BLOCKSIZE];
|
||||
char buf2[COMPARE_BLOCKSIZE];
|
||||
DWORD numBlocks = fileSize1 / COMPARE_BLOCKSIZE;
|
||||
DWORD leftOver = fileSize1 % COMPARE_BLOCKSIZE;
|
||||
DWORD readAmount;
|
||||
for (DWORD i = 0; i < numBlocks; i++) {
|
||||
if (!ReadFile(file1, buf1, COMPARE_BLOCKSIZE, &readAmount, nullptr) ||
|
||||
readAmount != COMPARE_BLOCKSIZE) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!ReadFile(file2, buf2, COMPARE_BLOCKSIZE, &readAmount, nullptr) ||
|
||||
readAmount != COMPARE_BLOCKSIZE) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (memcmp(buf1, buf2, COMPARE_BLOCKSIZE)) {
|
||||
// sameContent is already set to FALSE
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
if (leftOver) {
|
||||
if (!ReadFile(file1, buf1, leftOver, &readAmount, nullptr) ||
|
||||
readAmount != leftOver) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!ReadFile(file2, buf2, leftOver, &readAmount, nullptr) ||
|
||||
readAmount != leftOver) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (memcmp(buf1, buf2, leftOver)) {
|
||||
// sameContent is already set to FALSE
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
sameContent = TRUE;
|
||||
return TRUE;
|
||||
}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include <windows.h>
|
||||
#include "updatecommon.h"
|
||||
|
||||
BOOL PathAppendSafe(LPWSTR base, LPCWSTR extra);
|
||||
BOOL VerifySameFiles(LPCWSTR file1Path, LPCWSTR file2Path, BOOL &sameContent);
|
||||
|
||||
// 32KiB for comparing files at a time seems reasonable.
|
||||
// The bigger the better for speed, but this will be used
|
||||
// on the stack so I don't want it to be too big.
|
||||
#define COMPARE_BLOCKSIZE 32768
|
||||
|
||||
// The following string resource value is used to uniquely identify the signed
|
||||
// Mozilla application as an updater. Before the maintenance service will
|
||||
// execute the updater it must have this updater identity string in its string
|
||||
// table. No other signed Mozilla product will have this string table value.
|
||||
#define UPDATER_IDENTITY_STRING \
|
||||
"moz-updater.exe-4cdccec4-5ee0-4a06-9817-4cd899a9db49"
|
||||
#define IDS_UPDATER_IDENTITY 1006
|
||||
|
|
@ -1,759 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include <windows.h>
|
||||
#include <aclapi.h>
|
||||
#include <stdlib.h>
|
||||
#include <shlwapi.h>
|
||||
|
||||
// Used for DNLEN and UNLEN
|
||||
#include <lm.h>
|
||||
|
||||
#include <nsWindowsHelpers.h>
|
||||
#include "mozilla/UniquePtr.h"
|
||||
|
||||
#include "serviceinstall.h"
|
||||
#include "servicebase.h"
|
||||
#include "updatehelper.h"
|
||||
#include "shellapi.h"
|
||||
#include "readstrings.h"
|
||||
#include "errors.h"
|
||||
|
||||
#pragma comment(lib, "version.lib")
|
||||
|
||||
// This uninstall key is defined originally in maintenanceservice_installer.nsi
|
||||
#define MAINT_UNINSTALL_KEY L"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\MozillaMaintenanceService"
|
||||
|
||||
static BOOL
|
||||
UpdateUninstallerVersionString(LPWSTR versionString)
|
||||
{
|
||||
HKEY uninstallKey;
|
||||
if (RegOpenKeyExW(HKEY_LOCAL_MACHINE,
|
||||
MAINT_UNINSTALL_KEY, 0,
|
||||
KEY_WRITE | KEY_WOW64_32KEY,
|
||||
&uninstallKey) != ERROR_SUCCESS) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
LONG rv = RegSetValueExW(uninstallKey, L"DisplayVersion", 0, REG_SZ,
|
||||
reinterpret_cast<const BYTE *>(versionString),
|
||||
(wcslen(versionString) + 1) * sizeof(WCHAR));
|
||||
RegCloseKey(uninstallKey);
|
||||
return rv == ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* A wrapper function to read strings for the maintenance service.
|
||||
*
|
||||
* @param path The path of the ini file to read from
|
||||
* @param results The maintenance service strings that were read
|
||||
* @return OK on success
|
||||
*/
|
||||
static int
|
||||
ReadMaintenanceServiceStrings(LPCWSTR path,
|
||||
MaintenanceServiceStringTable *results)
|
||||
{
|
||||
// Read in the maintenance service description string if specified.
|
||||
const unsigned int kNumStrings = 1;
|
||||
const char *kServiceKeys = "MozillaMaintenanceDescription\0";
|
||||
char serviceStrings[kNumStrings][MAX_TEXT_LEN];
|
||||
int result = ReadStrings(path, kServiceKeys,
|
||||
kNumStrings, serviceStrings);
|
||||
if (result != OK) {
|
||||
serviceStrings[0][0] = '\0';
|
||||
}
|
||||
strncpy(results->serviceDescription,
|
||||
serviceStrings[0], MAX_TEXT_LEN - 1);
|
||||
results->serviceDescription[MAX_TEXT_LEN - 1] = '\0';
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains the version number from the specified PE file's version information
|
||||
* Version Format: A.B.C.D (Example 10.0.0.300)
|
||||
*
|
||||
* @param path The path of the file to check the version on
|
||||
* @param A The first part of the version number
|
||||
* @param B The second part of the version number
|
||||
* @param C The third part of the version number
|
||||
* @param D The fourth part of the version number
|
||||
* @return TRUE if successful
|
||||
*/
|
||||
static BOOL
|
||||
GetVersionNumberFromPath(LPWSTR path, DWORD &A, DWORD &B,
|
||||
DWORD &C, DWORD &D)
|
||||
{
|
||||
DWORD fileVersionInfoSize = GetFileVersionInfoSizeW(path, 0);
|
||||
mozilla::UniquePtr<char[]> fileVersionInfo(new char[fileVersionInfoSize]);
|
||||
if (!GetFileVersionInfoW(path, 0, fileVersionInfoSize,
|
||||
fileVersionInfo.get())) {
|
||||
LOG_WARN(("Could not obtain file info of old service. (%d)",
|
||||
GetLastError()));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
VS_FIXEDFILEINFO *fixedFileInfo =
|
||||
reinterpret_cast<VS_FIXEDFILEINFO *>(fileVersionInfo.get());
|
||||
UINT size;
|
||||
if (!VerQueryValueW(fileVersionInfo.get(), L"\\",
|
||||
reinterpret_cast<LPVOID*>(&fixedFileInfo), &size)) {
|
||||
LOG_WARN(("Could not query file version info of old service. (%d)",
|
||||
GetLastError()));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
A = HIWORD(fixedFileInfo->dwFileVersionMS);
|
||||
B = LOWORD(fixedFileInfo->dwFileVersionMS);
|
||||
C = HIWORD(fixedFileInfo->dwFileVersionLS);
|
||||
D = LOWORD(fixedFileInfo->dwFileVersionLS);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the service description with what is stored in updater.ini
|
||||
* at the same path as the currently executing module binary.
|
||||
*
|
||||
* @param serviceHandle A handle to an opened service with
|
||||
* SERVICE_CHANGE_CONFIG access right
|
||||
* @param TRUE on succcess.
|
||||
*/
|
||||
BOOL
|
||||
UpdateServiceDescription(SC_HANDLE serviceHandle)
|
||||
{
|
||||
WCHAR updaterINIPath[MAX_PATH + 1];
|
||||
if (!GetModuleFileNameW(nullptr, updaterINIPath,
|
||||
sizeof(updaterINIPath) /
|
||||
sizeof(updaterINIPath[0]))) {
|
||||
LOG_WARN(("Could not obtain module filename when attempting to "
|
||||
"modify service description. (%d)", GetLastError()));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!PathRemoveFileSpecW(updaterINIPath)) {
|
||||
LOG_WARN(("Could not remove file spec when attempting to "
|
||||
"modify service description. (%d)", GetLastError()));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!PathAppendSafe(updaterINIPath, L"updater.ini")) {
|
||||
LOG_WARN(("Could not append updater.ini filename when attempting to "
|
||||
"modify service description. (%d)", GetLastError()));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (GetFileAttributesW(updaterINIPath) == INVALID_FILE_ATTRIBUTES) {
|
||||
LOG_WARN(("updater.ini file does not exist, will not modify "
|
||||
"service description. (%d)", GetLastError()));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
MaintenanceServiceStringTable serviceStrings;
|
||||
int rv = ReadMaintenanceServiceStrings(updaterINIPath, &serviceStrings);
|
||||
if (rv != OK || !strlen(serviceStrings.serviceDescription)) {
|
||||
LOG_WARN(("updater.ini file does not contain a maintenance "
|
||||
"service description."));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
WCHAR serviceDescription[MAX_TEXT_LEN];
|
||||
if (!MultiByteToWideChar(CP_UTF8, 0,
|
||||
serviceStrings.serviceDescription, -1,
|
||||
serviceDescription,
|
||||
sizeof(serviceDescription) /
|
||||
sizeof(serviceDescription[0]))) {
|
||||
LOG_WARN(("Could not convert description to wide string format. (%d)",
|
||||
GetLastError()));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
SERVICE_DESCRIPTIONW descriptionConfig;
|
||||
descriptionConfig.lpDescription = serviceDescription;
|
||||
if (!ChangeServiceConfig2W(serviceHandle,
|
||||
SERVICE_CONFIG_DESCRIPTION,
|
||||
&descriptionConfig)) {
|
||||
LOG_WARN(("Could not change service config. (%d)", GetLastError()));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
LOG(("The service description was updated successfully."));
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the MozillaMaintenance service path needs to be updated
|
||||
* and fixes it if it is wrong.
|
||||
*
|
||||
* @param service A handle to the service to fix.
|
||||
* @param currentServicePath The current (possibly wrong) path that is used.
|
||||
* @param servicePathWasWrong Out parameter set to TRUE if a fix was needed.
|
||||
* @return TRUE if the service path is now correct.
|
||||
*/
|
||||
BOOL
|
||||
FixServicePath(SC_HANDLE service,
|
||||
LPCWSTR currentServicePath,
|
||||
BOOL &servicePathWasWrong)
|
||||
{
|
||||
// When we originally upgraded the MozillaMaintenance service we
|
||||
// would uninstall the service on each upgrade. This had an
|
||||
// intermittent error which could cause the service to use the file
|
||||
// maintenanceservice_tmp.exe as the install path. Only a small number
|
||||
// of Nightly users would be affected by this, but we check for this
|
||||
// state here and fix the user if they are affected.
|
||||
//
|
||||
// We also fix the path in the case of the path not being quoted.
|
||||
size_t currentServicePathLen = wcslen(currentServicePath);
|
||||
bool doesServiceHaveCorrectPath =
|
||||
currentServicePathLen > 2 &&
|
||||
!wcsstr(currentServicePath, L"maintenanceservice_tmp.exe") &&
|
||||
currentServicePath[0] == L'\"' &&
|
||||
currentServicePath[currentServicePathLen - 1] == L'\"';
|
||||
|
||||
if (doesServiceHaveCorrectPath) {
|
||||
LOG(("The MozillaMaintenance service path is correct."));
|
||||
servicePathWasWrong = FALSE;
|
||||
return TRUE;
|
||||
}
|
||||
// This is a recoverable situation so not logging as a warning
|
||||
LOG(("The MozillaMaintenance path is NOT correct. It was: %ls",
|
||||
currentServicePath));
|
||||
|
||||
servicePathWasWrong = TRUE;
|
||||
WCHAR fixedPath[MAX_PATH + 1] = { L'\0' };
|
||||
wcsncpy(fixedPath, currentServicePath, MAX_PATH);
|
||||
PathUnquoteSpacesW(fixedPath);
|
||||
if (!PathRemoveFileSpecW(fixedPath)) {
|
||||
LOG_WARN(("Couldn't remove file spec. (%d)", GetLastError()));
|
||||
return FALSE;
|
||||
}
|
||||
if (!PathAppendSafe(fixedPath, L"maintenanceservice.exe")) {
|
||||
LOG_WARN(("Couldn't append file spec. (%d)", GetLastError()));
|
||||
return FALSE;
|
||||
}
|
||||
PathQuoteSpacesW(fixedPath);
|
||||
|
||||
|
||||
if (!ChangeServiceConfigW(service, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE,
|
||||
SERVICE_NO_CHANGE, fixedPath, nullptr, nullptr,
|
||||
nullptr, nullptr, nullptr, nullptr)) {
|
||||
LOG_WARN(("Could not fix service path. (%d)", GetLastError()));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
LOG(("Fixed service path to: %ls.", fixedPath));
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs or upgrades the SVC_NAME service.
|
||||
* If an existing service is already installed, we replace it with the
|
||||
* currently running process.
|
||||
*
|
||||
* @param action The action to perform.
|
||||
* @return TRUE if the service was installed/upgraded
|
||||
*/
|
||||
BOOL
|
||||
SvcInstall(SvcInstallAction action)
|
||||
{
|
||||
// Get a handle to the local computer SCM database with full access rights.
|
||||
nsAutoServiceHandle schSCManager(OpenSCManager(nullptr, nullptr,
|
||||
SC_MANAGER_ALL_ACCESS));
|
||||
if (!schSCManager) {
|
||||
LOG_WARN(("Could not open service manager. (%d)", GetLastError()));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
WCHAR newServiceBinaryPath[MAX_PATH + 1];
|
||||
if (!GetModuleFileNameW(nullptr, newServiceBinaryPath,
|
||||
sizeof(newServiceBinaryPath) /
|
||||
sizeof(newServiceBinaryPath[0]))) {
|
||||
LOG_WARN(("Could not obtain module filename when attempting to "
|
||||
"install service. (%d)",
|
||||
GetLastError()));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Check if we already have the service installed.
|
||||
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_WARN(("Could not open service. (%d)", GetLastError()));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (schService) {
|
||||
// The service exists but it may not have the correct permissions.
|
||||
// This could happen if the permissions were not set correctly originally
|
||||
// or have been changed after the installation. This will reset the
|
||||
// permissions back to allow limited user accounts.
|
||||
if (!SetUserAccessServiceDACL(schService)) {
|
||||
LOG_WARN(("Could not reset security ACE on service handle. It might not be "
|
||||
"possible to start the service. This error should never "
|
||||
"happen. (%d)", GetLastError()));
|
||||
}
|
||||
|
||||
// The service exists and we opened it
|
||||
DWORD bytesNeeded;
|
||||
if (!QueryServiceConfigW(schService, nullptr, 0, &bytesNeeded) &&
|
||||
GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
|
||||
LOG_WARN(("Could not determine buffer size for query service config. (%d)",
|
||||
GetLastError()));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Get the service config information, in particular we want the binary
|
||||
// path of the service.
|
||||
mozilla::UniquePtr<char[]> serviceConfigBuffer(new char[bytesNeeded]);
|
||||
if (!QueryServiceConfigW(schService,
|
||||
reinterpret_cast<QUERY_SERVICE_CONFIGW*>(serviceConfigBuffer.get()),
|
||||
bytesNeeded, &bytesNeeded)) {
|
||||
LOG_WARN(("Could open service but could not query service config. (%d)",
|
||||
GetLastError()));
|
||||
return FALSE;
|
||||
}
|
||||
QUERY_SERVICE_CONFIGW &serviceConfig =
|
||||
*reinterpret_cast<QUERY_SERVICE_CONFIGW*>(serviceConfigBuffer.get());
|
||||
|
||||
// Check if we need to fix the service path
|
||||
BOOL servicePathWasWrong;
|
||||
static BOOL alreadyCheckedFixServicePath = FALSE;
|
||||
if (!alreadyCheckedFixServicePath) {
|
||||
if (!FixServicePath(schService, serviceConfig.lpBinaryPathName,
|
||||
servicePathWasWrong)) {
|
||||
LOG_WARN(("Could not fix service path. This should never happen. (%d)",
|
||||
GetLastError()));
|
||||
// True is returned because the service is pointing to
|
||||
// maintenanceservice_tmp.exe so it actually was upgraded to the
|
||||
// newest installed service.
|
||||
return TRUE;
|
||||
} else if (servicePathWasWrong) {
|
||||
// Now that the path is fixed we should re-attempt the install.
|
||||
// This current process' image path is maintenanceservice_tmp.exe.
|
||||
// The service used to point to maintenanceservice_tmp.exe.
|
||||
// The service was just fixed to point to maintenanceservice.exe.
|
||||
// Re-attempting an install from scratch will work as normal.
|
||||
alreadyCheckedFixServicePath = TRUE;
|
||||
LOG(("Restarting install action: %d", action));
|
||||
return SvcInstall(action);
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure the service path is not quoted. We own this memory and know it to
|
||||
// be large enough for the quoted path, so it is large enough for the
|
||||
// unquoted path. This function cannot fail.
|
||||
PathUnquoteSpacesW(serviceConfig.lpBinaryPathName);
|
||||
|
||||
// Obtain the existing maintenanceservice file's version number and
|
||||
// the new file's version number. Versions are in the format of
|
||||
// A.B.C.D.
|
||||
DWORD existingA, existingB, existingC, existingD;
|
||||
DWORD newA, newB, newC, newD;
|
||||
BOOL obtainedExistingVersionInfo =
|
||||
GetVersionNumberFromPath(serviceConfig.lpBinaryPathName,
|
||||
existingA, existingB,
|
||||
existingC, existingD);
|
||||
if (!GetVersionNumberFromPath(newServiceBinaryPath, newA,
|
||||
newB, newC, newD)) {
|
||||
LOG_WARN(("Could not obtain version number from new path"));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Check if we need to replace the old binary with the new one
|
||||
// If we couldn't get the old version info then we assume we should
|
||||
// replace it.
|
||||
if (ForceInstallSvc == action ||
|
||||
!obtainedExistingVersionInfo ||
|
||||
(existingA < newA) ||
|
||||
(existingA == newA && existingB < newB) ||
|
||||
(existingA == newA && existingB == newB &&
|
||||
existingC < newC) ||
|
||||
(existingA == newA && existingB == newB &&
|
||||
existingC == newC && existingD < newD)) {
|
||||
|
||||
// We have a newer updater, so update the description from the INI file.
|
||||
UpdateServiceDescription(schService);
|
||||
|
||||
schService.reset();
|
||||
if (!StopService()) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!wcscmp(newServiceBinaryPath, serviceConfig.lpBinaryPathName)) {
|
||||
LOG(("File is already in the correct location, no action needed for "
|
||||
"upgrade. The path is: \"%ls\"", newServiceBinaryPath));
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
BOOL result = TRUE;
|
||||
|
||||
// Attempt to copy the new binary over top the existing binary.
|
||||
// If there is an error we try to move it out of the way and then
|
||||
// copy it in. First try the safest / easiest way to overwrite the file.
|
||||
if (!CopyFileW(newServiceBinaryPath,
|
||||
serviceConfig.lpBinaryPathName, FALSE)) {
|
||||
LOG_WARN(("Could not overwrite old service binary file. "
|
||||
"This should never happen, but if it does the next "
|
||||
"upgrade will fix it, the service is not a critical "
|
||||
"component that needs to be installed for upgrades "
|
||||
"to work. (%d)", GetLastError()));
|
||||
|
||||
// We rename the last 3 filename chars in an unsafe way. Manually
|
||||
// verify there are more than 3 chars for safe failure in MoveFileExW.
|
||||
const size_t len = wcslen(serviceConfig.lpBinaryPathName);
|
||||
if (len > 3) {
|
||||
// Calculate the temp file path that we're moving the file to. This
|
||||
// is the same as the proper service path but with a .old extension.
|
||||
LPWSTR oldServiceBinaryTempPath =
|
||||
new WCHAR[len + 1];
|
||||
memset(oldServiceBinaryTempPath, 0, (len + 1) * sizeof (WCHAR));
|
||||
wcsncpy(oldServiceBinaryTempPath, serviceConfig.lpBinaryPathName, len);
|
||||
// Rename the last 3 chars to 'old'
|
||||
wcsncpy(oldServiceBinaryTempPath + len - 3, L"old", 3);
|
||||
|
||||
// Move the current (old) service file to the temp path.
|
||||
if (MoveFileExW(serviceConfig.lpBinaryPathName,
|
||||
oldServiceBinaryTempPath,
|
||||
MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH)) {
|
||||
// The old binary is moved out of the way, copy in the new one.
|
||||
if (!CopyFileW(newServiceBinaryPath,
|
||||
serviceConfig.lpBinaryPathName, FALSE)) {
|
||||
// It is best to leave the old service binary in this condition.
|
||||
LOG_WARN(("The new service binary could not be copied in."
|
||||
" The service will not be upgraded."));
|
||||
result = FALSE;
|
||||
} else {
|
||||
LOG(("The new service binary was copied in by first moving the"
|
||||
" old one out of the way."));
|
||||
}
|
||||
|
||||
// Attempt to get rid of the old service temp path.
|
||||
if (DeleteFileW(oldServiceBinaryTempPath)) {
|
||||
LOG(("The old temp service path was deleted: %ls.",
|
||||
oldServiceBinaryTempPath));
|
||||
} else {
|
||||
// The old temp path could not be removed. It will be removed
|
||||
// the next time the user can't copy the binary in or on uninstall.
|
||||
LOG_WARN(("The old temp service path was not deleted."));
|
||||
}
|
||||
} else {
|
||||
// It is best to leave the old service binary in this condition.
|
||||
LOG_WARN(("Could not move old service file out of the way from:"
|
||||
" \"%ls\" to \"%ls\". Service will not be upgraded. (%d)",
|
||||
serviceConfig.lpBinaryPathName,
|
||||
oldServiceBinaryTempPath, GetLastError()));
|
||||
result = FALSE;
|
||||
}
|
||||
delete[] oldServiceBinaryTempPath;
|
||||
} else {
|
||||
// It is best to leave the old service binary in this condition.
|
||||
LOG_WARN(("Service binary path was less than 3, service will"
|
||||
" not be updated. This should never happen."));
|
||||
result = FALSE;
|
||||
}
|
||||
} else {
|
||||
WCHAR versionStr[128] = { L'\0' };
|
||||
swprintf(versionStr, 128, L"%d.%d.%d.%d", newA, newB, newC, newD);
|
||||
if (!UpdateUninstallerVersionString(versionStr)) {
|
||||
LOG(("The uninstaller version string could not be updated."));
|
||||
}
|
||||
LOG(("The new service binary was copied in."));
|
||||
}
|
||||
|
||||
// We made a copy of ourselves to the existing location.
|
||||
// The tmp file (the process of which we are executing right now) will be
|
||||
// left over. Attempt to delete the file on the next reboot.
|
||||
if (MoveFileExW(newServiceBinaryPath, nullptr,
|
||||
MOVEFILE_DELAY_UNTIL_REBOOT)) {
|
||||
LOG(("Deleting the old file path on the next reboot: %ls.",
|
||||
newServiceBinaryPath));
|
||||
} else {
|
||||
LOG_WARN(("Call to delete the old file path failed: %ls.",
|
||||
newServiceBinaryPath));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// We don't need to copy ourselves to the existing location.
|
||||
// The tmp file (the process of which we are executing right now) will be
|
||||
// left over. Attempt to delete the file on the next reboot.
|
||||
MoveFileExW(newServiceBinaryPath, nullptr, MOVEFILE_DELAY_UNTIL_REBOOT);
|
||||
|
||||
// nothing to do, we already have a newer service installed
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// If the service does not exist and we are upgrading, don't install it.
|
||||
if (UpgradeSvc == action) {
|
||||
// The service does not exist and we are upgrading, so don't install it
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// Quote the path only if it contains spaces.
|
||||
PathQuoteSpacesW(newServiceBinaryPath);
|
||||
// The service does not already exist so create the service as on demand
|
||||
schService.own(CreateServiceW(schSCManager, SVC_NAME, SVC_DISPLAY_NAME,
|
||||
SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS,
|
||||
SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL,
|
||||
newServiceBinaryPath, nullptr, nullptr,
|
||||
nullptr, nullptr, nullptr));
|
||||
if (!schService) {
|
||||
LOG_WARN(("Could not create Windows service. "
|
||||
"This error should never happen since a service install "
|
||||
"should only be called when elevated. (%d)", GetLastError()));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!SetUserAccessServiceDACL(schService)) {
|
||||
LOG_WARN(("Could not set security ACE on service handle, the service will not "
|
||||
"be able to be started from unelevated processes. "
|
||||
"This error should never happen. (%d)",
|
||||
GetLastError()));
|
||||
}
|
||||
|
||||
UpdateServiceDescription(schService);
|
||||
|
||||
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(nullptr, nullptr,
|
||||
SC_MANAGER_ALL_ACCESS));
|
||||
if (!schSCManager) {
|
||||
LOG_WARN(("Could not open service manager. (%d)", GetLastError()));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Open the service
|
||||
nsAutoServiceHandle schService(OpenServiceW(schSCManager, SVC_NAME,
|
||||
SERVICE_ALL_ACCESS));
|
||||
if (!schService) {
|
||||
LOG_WARN(("Could not open service. (%d)", GetLastError()));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
LOG(("Sending stop request..."));
|
||||
SERVICE_STATUS status;
|
||||
SetLastError(ERROR_SUCCESS);
|
||||
if (!ControlService(schService, SERVICE_CONTROL_STOP, &status) &&
|
||||
GetLastError() != ERROR_SERVICE_NOT_ACTIVE) {
|
||||
LOG_WARN(("Error sending stop request. (%d)", GetLastError()));
|
||||
}
|
||||
|
||||
schSCManager.reset();
|
||||
schService.reset();
|
||||
|
||||
LOG(("Waiting for service stop..."));
|
||||
DWORD lastState = WaitForServiceStop(SVC_NAME, 30);
|
||||
|
||||
// The service can be in a stopped state but the exe still in use
|
||||
// so make sure the process is really gone before proceeding
|
||||
WaitForProcessExit(L"maintenanceservice.exe", 30);
|
||||
LOG(("Done waiting for service stop, last service state: %d", lastState));
|
||||
|
||||
return lastState == SERVICE_STOPPED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uninstalls the Maintenance service.
|
||||
*
|
||||
* @return TRUE if successful.
|
||||
*/
|
||||
BOOL
|
||||
SvcUninstall()
|
||||
{
|
||||
// Get a handle to the local computer SCM database with full access rights.
|
||||
nsAutoServiceHandle schSCManager(OpenSCManager(nullptr, nullptr,
|
||||
SC_MANAGER_ALL_ACCESS));
|
||||
if (!schSCManager) {
|
||||
LOG_WARN(("Could not open service manager. (%d)", GetLastError()));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Open the service
|
||||
nsAutoServiceHandle schService(OpenServiceW(schSCManager, SVC_NAME,
|
||||
SERVICE_ALL_ACCESS));
|
||||
if (!schService) {
|
||||
LOG_WARN(("Could not open service. (%d)", GetLastError()));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
//Stop the service so it deletes faster and so the uninstaller
|
||||
// can actually delete its EXE.
|
||||
DWORD totalWaitTime = 0;
|
||||
SERVICE_STATUS status;
|
||||
static const int maxWaitTime = 1000 * 60; // Never wait more than a minute
|
||||
if (ControlService(schService, 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 = nullptr;
|
||||
PSECURITY_DESCRIPTOR psd = nullptr;
|
||||
DWORD lastError = SetUserAccessServiceDACL(hService, pNewAcl, psd);
|
||||
if (pNewAcl) {
|
||||
LocalFree((HLOCAL)pNewAcl);
|
||||
}
|
||||
if (psd) {
|
||||
LocalFree((LPVOID)psd);
|
||||
}
|
||||
return ERROR_SUCCESS == lastError;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the access control list for user access for the specified service.
|
||||
*
|
||||
* @param hService The service to set the access control list on
|
||||
* @param pNewAcl The out param ACL which should be freed by caller
|
||||
* @param psd out param security descriptor, should be freed by caller
|
||||
* @return ERROR_SUCCESS if successful
|
||||
*/
|
||||
DWORD
|
||||
SetUserAccessServiceDACL(SC_HANDLE hService, PACL &pNewAcl,
|
||||
PSECURITY_DESCRIPTOR psd)
|
||||
{
|
||||
// Get the current security descriptor needed size
|
||||
DWORD needed = 0;
|
||||
if (!QueryServiceObjectSecurity(hService, DACL_SECURITY_INFORMATION,
|
||||
&psd, 0, &needed)) {
|
||||
if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
|
||||
LOG_WARN(("Could not query service object security size. (%d)",
|
||||
GetLastError()));
|
||||
return GetLastError();
|
||||
}
|
||||
|
||||
DWORD size = needed;
|
||||
psd = (PSECURITY_DESCRIPTOR)LocalAlloc(LPTR, size);
|
||||
if (!psd) {
|
||||
LOG_WARN(("Could not allocate security descriptor. (%d)",
|
||||
GetLastError()));
|
||||
return ERROR_INSUFFICIENT_BUFFER;
|
||||
}
|
||||
|
||||
// Get the actual security descriptor now
|
||||
if (!QueryServiceObjectSecurity(hService, DACL_SECURITY_INFORMATION,
|
||||
psd, size, &needed)) {
|
||||
LOG_WARN(("Could not allocate security descriptor. (%d)",
|
||||
GetLastError()));
|
||||
return GetLastError();
|
||||
}
|
||||
}
|
||||
|
||||
// Get the current DACL from the security descriptor.
|
||||
PACL pacl = nullptr;
|
||||
BOOL bDaclPresent = FALSE;
|
||||
BOOL bDaclDefaulted = FALSE;
|
||||
if ( !GetSecurityDescriptorDacl(psd, &bDaclPresent, &pacl,
|
||||
&bDaclDefaulted)) {
|
||||
LOG_WARN(("Could not obtain DACL. (%d)", GetLastError()));
|
||||
return GetLastError();
|
||||
}
|
||||
|
||||
PSID sid;
|
||||
DWORD SIDSize = SECURITY_MAX_SID_SIZE;
|
||||
sid = LocalAlloc(LMEM_FIXED, SIDSize);
|
||||
if (!sid) {
|
||||
LOG_WARN(("Could not allocate SID memory. (%d)", GetLastError()));
|
||||
return GetLastError();
|
||||
}
|
||||
|
||||
if (!CreateWellKnownSid(WinBuiltinUsersSid, nullptr, sid, &SIDSize)) {
|
||||
DWORD lastError = GetLastError();
|
||||
LOG_WARN(("Could not create well known SID. (%d)", lastError));
|
||||
LocalFree(sid);
|
||||
return lastError;
|
||||
}
|
||||
|
||||
// Lookup the account name, the function fails if you don't pass in
|
||||
// a buffer for the domain name but it's not used since we're using
|
||||
// the built in account Sid.
|
||||
SID_NAME_USE accountType;
|
||||
WCHAR accountName[UNLEN + 1] = { L'\0' };
|
||||
WCHAR domainName[DNLEN + 1] = { L'\0' };
|
||||
DWORD accountNameSize = UNLEN + 1;
|
||||
DWORD domainNameSize = DNLEN + 1;
|
||||
if (!LookupAccountSidW(nullptr, sid, accountName,
|
||||
&accountNameSize,
|
||||
domainName, &domainNameSize, &accountType)) {
|
||||
LOG_WARN(("Could not lookup account Sid, will try Users. (%d)",
|
||||
GetLastError()));
|
||||
wcsncpy(accountName, L"Users", UNLEN);
|
||||
}
|
||||
|
||||
// We already have the group name so we can get rid of the SID
|
||||
FreeSid(sid);
|
||||
sid = nullptr;
|
||||
|
||||
// Build the ACE, BuildExplicitAccessWithName cannot fail so it is not logged.
|
||||
EXPLICIT_ACCESS ea;
|
||||
BuildExplicitAccessWithNameW(&ea, accountName,
|
||||
SERVICE_START | SERVICE_STOP | GENERIC_READ,
|
||||
SET_ACCESS, NO_INHERITANCE);
|
||||
DWORD lastError = SetEntriesInAclW(1, (PEXPLICIT_ACCESS)&ea, pacl, &pNewAcl);
|
||||
if (ERROR_SUCCESS != lastError) {
|
||||
LOG_WARN(("Could not set entries in ACL. (%d)", lastError));
|
||||
return lastError;
|
||||
}
|
||||
|
||||
// Initialize a new security descriptor.
|
||||
SECURITY_DESCRIPTOR sd;
|
||||
if (!InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION)) {
|
||||
LOG_WARN(("Could not initialize security descriptor. (%d)",
|
||||
GetLastError()));
|
||||
return GetLastError();
|
||||
}
|
||||
|
||||
// Set the new DACL in the security descriptor.
|
||||
if (!SetSecurityDescriptorDacl(&sd, TRUE, pNewAcl, FALSE)) {
|
||||
LOG_WARN(("Could not set security descriptor DACL. (%d)",
|
||||
GetLastError()));
|
||||
return GetLastError();
|
||||
}
|
||||
|
||||
// Set the new security descriptor for the service object.
|
||||
if (!SetServiceObjectSecurity(hService, DACL_SECURITY_INFORMATION, &sd)) {
|
||||
LOG_WARN(("Could not set object security. (%d)",
|
||||
GetLastError()));
|
||||
return GetLastError();
|
||||
}
|
||||
|
||||
// Woohoo, raise the roof
|
||||
LOG(("User access was set successfully on the service."));
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "readstrings.h"
|
||||
|
||||
#define SVC_DISPLAY_NAME L"Mozilla Maintenance Service"
|
||||
|
||||
enum SvcInstallAction { UpgradeSvc, InstallSvc, ForceInstallSvc };
|
||||
BOOL SvcInstall(SvcInstallAction action);
|
||||
BOOL SvcUninstall();
|
||||
BOOL StopService();
|
||||
BOOL SetUserAccessServiceDACL(SC_HANDLE hService);
|
||||
DWORD SetUserAccessServiceDACL(SC_HANDLE hService, PACL &pNewAcl,
|
||||
PSECURITY_DESCRIPTOR psd);
|
||||
|
||||
struct MaintenanceServiceStringTable
|
||||
{
|
||||
char serviceDescription[MAX_TEXT_LEN];
|
||||
};
|
||||
|
||||
|
|
@ -1,758 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include <shlobj.h>
|
||||
#include <shlwapi.h>
|
||||
#include <wtsapi32.h>
|
||||
#include <userenv.h>
|
||||
#include <shellapi.h>
|
||||
|
||||
#pragma comment(lib, "wtsapi32.lib")
|
||||
#pragma comment(lib, "userenv.lib")
|
||||
#pragma comment(lib, "shlwapi.lib")
|
||||
#pragma comment(lib, "ole32.lib")
|
||||
#pragma comment(lib, "rpcrt4.lib")
|
||||
|
||||
#include "nsWindowsHelpers.h"
|
||||
|
||||
#include "workmonitor.h"
|
||||
#include "serviceinstall.h"
|
||||
#include "servicebase.h"
|
||||
#include "registrycertificates.h"
|
||||
#include "uachelper.h"
|
||||
#include "updatehelper.h"
|
||||
#include "pathhash.h"
|
||||
#include "errors.h"
|
||||
|
||||
// Wait 15 minutes for an update operation to run at most.
|
||||
// Updates usually take less than a minute so this seems like a
|
||||
// significantly large and safe amount of time to wait.
|
||||
static const int TIME_TO_WAIT_ON_UPDATER = 15 * 60 * 1000;
|
||||
wchar_t* MakeCommandLine(int argc, wchar_t** argv);
|
||||
BOOL WriteStatusFailure(LPCWSTR updateDirPath, int errorCode);
|
||||
BOOL PathGetSiblingFilePath(LPWSTR destinationBuffer, LPCWSTR siblingFilePath,
|
||||
LPCWSTR newFileName);
|
||||
BOOL DoesFallbackKeyExist();
|
||||
|
||||
/*
|
||||
* Read the update.status file and sets isApplying to true if
|
||||
* the status is set to applying.
|
||||
*
|
||||
* @param updateDirPath The directory where update.status is stored
|
||||
* @param isApplying Out parameter for specifying if the status
|
||||
* is set to applying or not.
|
||||
* @return TRUE if the information was filled.
|
||||
*/
|
||||
static BOOL
|
||||
IsStatusApplying(LPCWSTR updateDirPath, BOOL &isApplying)
|
||||
{
|
||||
isApplying = FALSE;
|
||||
WCHAR updateStatusFilePath[MAX_PATH + 1] = {L'\0'};
|
||||
wcsncpy(updateStatusFilePath, updateDirPath, MAX_PATH);
|
||||
if (!PathAppendSafe(updateStatusFilePath, L"update.status")) {
|
||||
LOG_WARN(("Could not append path for update.status file"));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
nsAutoHandle statusFile(CreateFileW(updateStatusFilePath, GENERIC_READ,
|
||||
FILE_SHARE_READ |
|
||||
FILE_SHARE_WRITE |
|
||||
FILE_SHARE_DELETE,
|
||||
nullptr, OPEN_EXISTING, 0, nullptr));
|
||||
|
||||
if (INVALID_HANDLE_VALUE == statusFile) {
|
||||
LOG_WARN(("Could not open update.status file"));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
char buf[32] = { 0 };
|
||||
DWORD read;
|
||||
if (!ReadFile(statusFile, buf, sizeof(buf), &read, nullptr)) {
|
||||
LOG_WARN(("Could not read from update.status file"));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
const char kApplying[] = "applying";
|
||||
isApplying = strncmp(buf, kApplying,
|
||||
sizeof(kApplying) - 1) == 0;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether we're staging an update.
|
||||
*
|
||||
* @param argc The argc value normally sent to updater.exe
|
||||
* @param argv The argv value normally sent to updater.exe
|
||||
* @return boolean True if we're staging an update
|
||||
*/
|
||||
static bool
|
||||
IsUpdateBeingStaged(int argc, LPWSTR *argv)
|
||||
{
|
||||
// PID will be set to -1 if we're supposed to stage an update.
|
||||
return argc == 4 && !wcscmp(argv[3], L"-1") ||
|
||||
argc == 5 && !wcscmp(argv[4], L"-1");
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the param only contains digits.
|
||||
*
|
||||
* @param str The string to check
|
||||
* @param boolean True if the param only contains digits
|
||||
*/
|
||||
static bool
|
||||
IsDigits(WCHAR *str)
|
||||
{
|
||||
while (*str) {
|
||||
if (!iswdigit(*str++)) {
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the command line contains just the directory to apply the
|
||||
* update to (old command line) or if it contains the installation directory and
|
||||
* the directory to apply the update to.
|
||||
*
|
||||
* @param argc The argc value normally sent to updater.exe
|
||||
* @param argv The argv value normally sent to updater.exe
|
||||
* @param boolean True if the command line contains just the directory to apply
|
||||
* the update to
|
||||
*/
|
||||
static bool
|
||||
IsOldCommandline(int argc, LPWSTR *argv)
|
||||
{
|
||||
return argc == 4 && !wcscmp(argv[3], L"-1") ||
|
||||
argc >= 4 && (wcsstr(argv[3], L"/replace") || IsDigits(argv[3]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the installation directory from the arguments passed to updater.exe.
|
||||
*
|
||||
* @param argcTmp The argc value normally sent to updater.exe
|
||||
* @param argvTmp The argv value normally sent to updater.exe
|
||||
* @param aResultDir Buffer to hold the installation directory.
|
||||
*/
|
||||
static BOOL
|
||||
GetInstallationDir(int argcTmp, LPWSTR *argvTmp, WCHAR aResultDir[MAX_PATH + 1])
|
||||
{
|
||||
int index = 3;
|
||||
if (IsOldCommandline(argcTmp, argvTmp)) {
|
||||
index = 2;
|
||||
}
|
||||
|
||||
if (argcTmp < index) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
wcsncpy(aResultDir, argvTmp[2], MAX_PATH);
|
||||
WCHAR* backSlash = wcsrchr(aResultDir, L'\\');
|
||||
// Make sure that the path does not include trailing backslashes
|
||||
if (backSlash && (backSlash[1] == L'\0')) {
|
||||
*backSlash = L'\0';
|
||||
}
|
||||
|
||||
// The new command line's argv[2] is always the installation directory.
|
||||
if (index == 2) {
|
||||
bool backgroundUpdate = IsUpdateBeingStaged(argcTmp, argvTmp);
|
||||
bool replaceRequest = (argcTmp >= 4 && wcsstr(argvTmp[3], L"/replace"));
|
||||
if (backgroundUpdate || replaceRequest) {
|
||||
return PathRemoveFileSpecW(aResultDir);
|
||||
}
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs an update process as the service using the SYSTEM account.
|
||||
*
|
||||
* @param argc The number of arguments in argv
|
||||
* @param argv The arguments normally passed to updater.exe
|
||||
* argv[0] must be the path to updater.exe
|
||||
* @param processStarted Set to TRUE if the process was started.
|
||||
* @return TRUE if the update process was run had a return code of 0.
|
||||
*/
|
||||
BOOL
|
||||
StartUpdateProcess(int argc,
|
||||
LPWSTR *argv,
|
||||
LPCWSTR installDir,
|
||||
BOOL &processStarted)
|
||||
{
|
||||
LOG(("Starting update process as the service in session 0."));
|
||||
STARTUPINFO si = {0};
|
||||
si.cb = sizeof(STARTUPINFO);
|
||||
si.lpDesktop = L"winsta0\\Default";
|
||||
PROCESS_INFORMATION pi = {0};
|
||||
|
||||
// The updater command line is of the form:
|
||||
// updater.exe update-dir apply [wait-pid [callback-dir callback-path args]]
|
||||
LPWSTR cmdLine = MakeCommandLine(argc, argv);
|
||||
|
||||
int index = 3;
|
||||
if (IsOldCommandline(argc, argv)) {
|
||||
index = 2;
|
||||
}
|
||||
|
||||
// If we're about to start the update process from session 0,
|
||||
// then we should not show a GUI. This only really needs to be done
|
||||
// on Vista and higher, but it's better to keep everything consistent
|
||||
// across all OS if it's of no harm.
|
||||
if (argc >= index) {
|
||||
// Setting the desktop to blank will ensure no GUI is displayed
|
||||
si.lpDesktop = L"";
|
||||
si.dwFlags |= STARTF_USESHOWWINDOW;
|
||||
si.wShowWindow = SW_HIDE;
|
||||
}
|
||||
|
||||
// Add an env var for MOZ_USING_SERVICE so the updater.exe can
|
||||
// do anything special that it needs to do for service updates.
|
||||
// Search in updater.cpp for more info on MOZ_USING_SERVICE.
|
||||
putenv(const_cast<char*>("MOZ_USING_SERVICE=1"));
|
||||
LOG(("Starting service with cmdline: %ls", cmdLine));
|
||||
processStarted = CreateProcessW(argv[0], cmdLine,
|
||||
nullptr, nullptr, FALSE,
|
||||
CREATE_DEFAULT_ERROR_MODE,
|
||||
nullptr,
|
||||
nullptr, &si, &pi);
|
||||
|
||||
BOOL updateWasSuccessful = FALSE;
|
||||
if (processStarted) {
|
||||
BOOL processTerminated = FALSE;
|
||||
BOOL noProcessExitCode = FALSE;
|
||||
// Wait for the updater process to finish
|
||||
LOG(("Process was started... waiting on result."));
|
||||
DWORD waitRes = WaitForSingleObject(pi.hProcess, TIME_TO_WAIT_ON_UPDATER);
|
||||
if (WAIT_TIMEOUT == waitRes) {
|
||||
// We waited a long period of time for updater.exe and it never finished
|
||||
// so kill it.
|
||||
TerminateProcess(pi.hProcess, 1);
|
||||
processTerminated = TRUE;
|
||||
} else {
|
||||
// Check the return code of updater.exe to make sure we get 0
|
||||
DWORD returnCode;
|
||||
if (GetExitCodeProcess(pi.hProcess, &returnCode)) {
|
||||
LOG(("Process finished with return code %d.", returnCode));
|
||||
// updater returns 0 if successful.
|
||||
updateWasSuccessful = (returnCode == 0);
|
||||
} else {
|
||||
LOG_WARN(("Process finished but could not obtain return code."));
|
||||
noProcessExitCode = TRUE;
|
||||
}
|
||||
}
|
||||
CloseHandle(pi.hProcess);
|
||||
CloseHandle(pi.hThread);
|
||||
|
||||
// Check just in case updater.exe didn't change the status from
|
||||
// applying. If this is the case we report an error.
|
||||
BOOL isApplying = FALSE;
|
||||
if (IsStatusApplying(argv[1], isApplying) && isApplying) {
|
||||
if (updateWasSuccessful) {
|
||||
LOG(("update.status is still applying even though update was "
|
||||
"successful."));
|
||||
if (!WriteStatusFailure(argv[1],
|
||||
SERVICE_STILL_APPLYING_ON_SUCCESS)) {
|
||||
LOG_WARN(("Could not write update.status still applying on "
|
||||
"success error."));
|
||||
}
|
||||
// Since we still had applying we know updater.exe didn't do its
|
||||
// job correctly.
|
||||
updateWasSuccessful = FALSE;
|
||||
} else {
|
||||
LOG_WARN(("update.status is still applying and update was not successful."));
|
||||
int failcode = SERVICE_STILL_APPLYING_ON_FAILURE;
|
||||
if (noProcessExitCode) {
|
||||
failcode = SERVICE_STILL_APPLYING_NO_EXIT_CODE;
|
||||
} else if (processTerminated) {
|
||||
failcode = SERVICE_STILL_APPLYING_TERMINATED;
|
||||
}
|
||||
if (!WriteStatusFailure(argv[1], failcode)) {
|
||||
LOG_WARN(("Could not write update.status still applying on "
|
||||
"failure error."));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
DWORD lastError = GetLastError();
|
||||
LOG_WARN(("Could not create process as current user, "
|
||||
"updaterPath: %ls; cmdLine: %ls. (%d)",
|
||||
argv[0], cmdLine, lastError));
|
||||
}
|
||||
|
||||
// Empty value on putenv is how you remove an env variable in Windows
|
||||
putenv(const_cast<char*>("MOZ_USING_SERVICE="));
|
||||
|
||||
free(cmdLine);
|
||||
return updateWasSuccessful;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a file as an official updater.
|
||||
*
|
||||
* @param updater Path to the updater to validate
|
||||
* @param installDir Path to the application installation
|
||||
* being updated
|
||||
* @param updateDir Update applyTo direcotry,
|
||||
* where logs will be written
|
||||
*
|
||||
* @return true if updater is the path to a valid updater
|
||||
*/
|
||||
static bool
|
||||
UpdaterIsValid(LPWSTR updater, LPWSTR installDir, LPWSTR updateDir)
|
||||
{
|
||||
// Make sure the path to the updater to use for the update is local.
|
||||
// We do this check to make sure that file locking is available for
|
||||
// race condition security checks.
|
||||
BOOL isLocal = FALSE;
|
||||
if (!IsLocalFile(updater, isLocal) || !isLocal) {
|
||||
LOG_WARN(("Filesystem in path %ls is not supported (%d)",
|
||||
updater, GetLastError()));
|
||||
if (!WriteStatusFailure(updateDir, SERVICE_UPDATER_NOT_FIXED_DRIVE)) {
|
||||
LOG_WARN(("Could not write update.status service update failure. (%d)",
|
||||
GetLastError()));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
nsAutoHandle noWriteLock(CreateFileW(updater, GENERIC_READ, FILE_SHARE_READ,
|
||||
nullptr, OPEN_EXISTING, 0, nullptr));
|
||||
if (INVALID_HANDLE_VALUE == noWriteLock) {
|
||||
LOG_WARN(("Could not set no write sharing access on file. (%d)",
|
||||
GetLastError()));
|
||||
if (!WriteStatusFailure(updateDir, SERVICE_COULD_NOT_LOCK_UPDATER)) {
|
||||
LOG_WARN(("Could not write update.status service update failure. (%d)",
|
||||
GetLastError()));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Verify that the updater.exe that we are executing is the same
|
||||
// as the one in the installation directory which we are updating.
|
||||
// The installation dir that we are installing to is installDir.
|
||||
WCHAR installDirUpdater[MAX_PATH + 1] = { L'\0' };
|
||||
wcsncpy(installDirUpdater, installDir, MAX_PATH);
|
||||
if (!PathAppendSafe(installDirUpdater, L"updater.exe")) {
|
||||
LOG_WARN(("Install directory updater could not be determined."));
|
||||
return false;
|
||||
}
|
||||
|
||||
BOOL updaterIsCorrect;
|
||||
if (!VerifySameFiles(updater, installDirUpdater, updaterIsCorrect)) {
|
||||
LOG_WARN(("Error checking if the updaters are the same.\n"
|
||||
"Path 1: %ls\nPath 2: %ls", updater, installDirUpdater));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!updaterIsCorrect) {
|
||||
LOG_WARN(("The updaters do not match, updater will not run.\n"
|
||||
"Path 1: %ls\nPath 2: %ls", updater, installDirUpdater));
|
||||
if (!WriteStatusFailure(updateDir, SERVICE_UPDATER_COMPARE_ERROR)) {
|
||||
LOG_WARN(("Could not write update.status updater compare failure."));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
LOG(("updater.exe was compared successfully to the installation directory"
|
||||
" updater.exe."));
|
||||
|
||||
// Check to make sure the updater.exe module has the unique updater identity.
|
||||
// This is a security measure to make sure that the signed executable that
|
||||
// we will run is actually an updater.
|
||||
bool result = true;
|
||||
HMODULE updaterModule = LoadLibraryEx(updater, nullptr,
|
||||
LOAD_LIBRARY_AS_DATAFILE);
|
||||
if (!updaterModule) {
|
||||
LOG_WARN(("updater.exe module could not be loaded. (%d)", GetLastError()));
|
||||
result = false;
|
||||
} else {
|
||||
char updaterIdentity[64];
|
||||
if (!LoadStringA(updaterModule, IDS_UPDATER_IDENTITY,
|
||||
updaterIdentity, sizeof(updaterIdentity))) {
|
||||
LOG_WARN(("The updater.exe application does not contain the Mozilla"
|
||||
" updater identity."));
|
||||
result = false;
|
||||
}
|
||||
|
||||
if (strcmp(updaterIdentity, UPDATER_IDENTITY_STRING)) {
|
||||
LOG_WARN(("The updater.exe identity string is not valid."));
|
||||
result = false;
|
||||
}
|
||||
FreeLibrary(updaterModule);
|
||||
}
|
||||
|
||||
if (result) {
|
||||
LOG(("The updater.exe application contains the Mozilla"
|
||||
" updater identity."));
|
||||
} else {
|
||||
if (!WriteStatusFailure(updateDir, SERVICE_UPDATER_IDENTITY_ERROR)) {
|
||||
LOG_WARN(("Could not write update.status no updater identity."));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifndef DISABLE_UPDATER_AUTHENTICODE_CHECK
|
||||
return DoesBinaryMatchAllowedCertificates(installDir, updater);
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a software update command
|
||||
*
|
||||
* @param argc The number of arguments in argv
|
||||
* @param argv The arguments normally passed to updater.exe
|
||||
* argv[0] must be the path to updater.exe
|
||||
* @return TRUE if the update was successful.
|
||||
*/
|
||||
BOOL
|
||||
ProcessSoftwareUpdateCommand(DWORD argc, LPWSTR *argv)
|
||||
{
|
||||
BOOL result = TRUE;
|
||||
if (argc < 3) {
|
||||
LOG_WARN(("Not enough command line parameters specified. "
|
||||
"Updating update.status."));
|
||||
|
||||
// We can only update update.status if argv[1] exists. argv[1] is
|
||||
// the directory where the update.status file exists.
|
||||
if (argc < 2 ||
|
||||
!WriteStatusFailure(argv[1], SERVICE_NOT_ENOUGH_COMMAND_LINE_ARGS)) {
|
||||
LOG_WARN(("Could not write update.status service update failure. (%d)",
|
||||
GetLastError()));
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
WCHAR installDir[MAX_PATH + 1] = {L'\0'};
|
||||
if (!GetInstallationDir(argc, argv, installDir)) {
|
||||
LOG_WARN(("Could not get the installation directory"));
|
||||
if (!WriteStatusFailure(argv[1], SERVICE_INSTALLDIR_ERROR)) {
|
||||
LOG_WARN(("Could not write update.status for GetInstallationDir failure."));
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (UpdaterIsValid(argv[0], installDir, argv[1])) {
|
||||
BOOL updateProcessWasStarted = FALSE;
|
||||
if (StartUpdateProcess(argc, argv, installDir,
|
||||
updateProcessWasStarted)) {
|
||||
LOG(("updater.exe was launched and run successfully!"));
|
||||
LogFlush();
|
||||
|
||||
// Don't attempt to update the service when the update is being staged.
|
||||
if (!IsUpdateBeingStaged(argc, argv)) {
|
||||
// We might not execute code after StartServiceUpdate because
|
||||
// the service installer will stop the service if it is running.
|
||||
StartServiceUpdate(installDir);
|
||||
}
|
||||
} else {
|
||||
result = FALSE;
|
||||
LOG_WARN(("Error running update process. Updating update.status (%d)",
|
||||
GetLastError()));
|
||||
LogFlush();
|
||||
|
||||
// If the update process was started, then updater.exe is responsible for
|
||||
// setting the failure code. If it could not be started then we do the
|
||||
// work. We set an error instead of directly setting status pending
|
||||
// so that the app.update.service.errors pref can be updated when
|
||||
// the callback app restarts.
|
||||
if (!updateProcessWasStarted) {
|
||||
if (!WriteStatusFailure(argv[1],
|
||||
SERVICE_UPDATER_COULD_NOT_BE_STARTED)) {
|
||||
LOG_WARN(("Could not write update.status service update failure. (%d)",
|
||||
GetLastError()));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
result = FALSE;
|
||||
LOG_WARN(("Could not start process due to certificate check error on "
|
||||
"updater.exe. Updating update.status. (%d)", GetLastError()));
|
||||
|
||||
// When there is a certificate check error on the updater.exe application,
|
||||
// we want to write out the error.
|
||||
if (!WriteStatusFailure(argv[1],
|
||||
SERVICE_UPDATER_SIGN_ERROR)) {
|
||||
LOG_WARN(("Could not write pending state to update.status. (%d)",
|
||||
GetLastError()));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains the updater path alongside a subdir of the service binary.
|
||||
* The purpose of this function is to return a path that is likely high
|
||||
* integrity and therefore more safe to execute code from.
|
||||
*
|
||||
* @param serviceUpdaterPath Out parameter for the path where the updater
|
||||
* should be copied to.
|
||||
* @return TRUE if a file path was obtained.
|
||||
*/
|
||||
BOOL
|
||||
GetSecureUpdaterPath(WCHAR serviceUpdaterPath[MAX_PATH + 1])
|
||||
{
|
||||
if (!GetModuleFileNameW(nullptr, serviceUpdaterPath, MAX_PATH)) {
|
||||
LOG_WARN(("Could not obtain module filename when attempting to "
|
||||
"use a secure updater path. (%d)", GetLastError()));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!PathRemoveFileSpecW(serviceUpdaterPath)) {
|
||||
LOG_WARN(("Couldn't remove file spec when attempting to use a secure "
|
||||
"updater path. (%d)", GetLastError()));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!PathAppendSafe(serviceUpdaterPath, L"update")) {
|
||||
LOG_WARN(("Couldn't append file spec when attempting to use a secure "
|
||||
"updater path. (%d)", GetLastError()));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
CreateDirectoryW(serviceUpdaterPath, nullptr);
|
||||
|
||||
if (!PathAppendSafe(serviceUpdaterPath, L"updater.exe")) {
|
||||
LOG_WARN(("Couldn't append file spec when attempting to use a secure "
|
||||
"updater path. (%d)", GetLastError()));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the passed in updater path and the associated updater.ini file.
|
||||
*
|
||||
* @param serviceUpdaterPath The path to delete.
|
||||
* @return TRUE if a file was deleted.
|
||||
*/
|
||||
BOOL
|
||||
DeleteSecureUpdater(WCHAR serviceUpdaterPath[MAX_PATH + 1])
|
||||
{
|
||||
BOOL result = FALSE;
|
||||
if (serviceUpdaterPath[0]) {
|
||||
result = DeleteFileW(serviceUpdaterPath);
|
||||
if (!result && GetLastError() != ERROR_PATH_NOT_FOUND &&
|
||||
GetLastError() != ERROR_FILE_NOT_FOUND) {
|
||||
LOG_WARN(("Could not delete service updater path: '%ls'.",
|
||||
serviceUpdaterPath));
|
||||
}
|
||||
|
||||
WCHAR updaterINIPath[MAX_PATH + 1] = { L'\0' };
|
||||
if (PathGetSiblingFilePath(updaterINIPath, serviceUpdaterPath,
|
||||
L"updater.ini")) {
|
||||
result = DeleteFileW(updaterINIPath);
|
||||
if (!result && GetLastError() != ERROR_PATH_NOT_FOUND &&
|
||||
GetLastError() != ERROR_FILE_NOT_FOUND) {
|
||||
LOG_WARN(("Could not delete service updater INI path: '%ls'.",
|
||||
updaterINIPath));
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a service command.
|
||||
*
|
||||
* @param argc The number of arguments in argv
|
||||
* @param argv The service command line arguments, argv[0] and argv[1]
|
||||
* and automatically included by Windows. argv[2] is the
|
||||
* service command.
|
||||
*
|
||||
* @return FALSE if there was an error executing the service command.
|
||||
*/
|
||||
BOOL
|
||||
ExecuteServiceCommand(int argc, LPWSTR *argv)
|
||||
{
|
||||
if (argc < 3) {
|
||||
LOG_WARN(("Not enough command line arguments to execute a service command"));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// The tests work by making sure the log has changed, so we put a
|
||||
// unique ID in the log.
|
||||
RPC_WSTR guidString = RPC_WSTR(L"");
|
||||
GUID guid;
|
||||
HRESULT hr = CoCreateGuid(&guid);
|
||||
if (SUCCEEDED(hr)) {
|
||||
UuidToString(&guid, &guidString);
|
||||
}
|
||||
LOG(("Executing service command %ls, ID: %ls",
|
||||
argv[2], reinterpret_cast<LPCWSTR>(guidString)));
|
||||
RpcStringFree(&guidString);
|
||||
|
||||
BOOL result = FALSE;
|
||||
if (!lstrcmpi(argv[2], L"software-update")) {
|
||||
// This check is also performed in updater.cpp and is performed here
|
||||
// as well since the maintenance service can be called directly.
|
||||
if (argc < 4 || !IsValidFullPath(argv[4])) {
|
||||
// Since the status file is written to the patch directory and the patch
|
||||
// directory is invalid don't write the status file.
|
||||
LOG_WARN(("The patch directory path is not valid for this application."));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// This check is also performed in updater.cpp and is performed here
|
||||
// as well since the maintenance service can be called directly.
|
||||
if (argc < 5 || !IsValidFullPath(argv[5])) {
|
||||
LOG_WARN(("The install directory path is not valid for this application."));
|
||||
if (!WriteStatusFailure(argv[4], SERVICE_INVALID_INSTALL_DIR_PATH_ERROR)) {
|
||||
LOG_WARN(("Could not write update.status for previous failure."));
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!IsOldCommandline(argc - 3, argv + 3)) {
|
||||
// This check is also performed in updater.cpp and is performed here
|
||||
// as well since the maintenance service can be called directly.
|
||||
if (argc < 6 || !IsValidFullPath(argv[6])) {
|
||||
LOG_WARN(("The working directory path is not valid for this application."));
|
||||
if (!WriteStatusFailure(argv[4], SERVICE_INVALID_WORKING_DIR_PATH_ERROR)) {
|
||||
LOG_WARN(("Could not write update.status for previous failure."));
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// These checks are also performed in updater.cpp and is performed here
|
||||
// as well since the maintenance service can be called directly.
|
||||
if (_wcsnicmp(argv[6], argv[5], MAX_PATH) != 0) {
|
||||
if (wcscmp(argv[7], L"-1") != 0 && !wcsstr(argv[7], L"/replace")) {
|
||||
LOG_WARN(("Installation directory and working directory must be the "
|
||||
"same for non-staged updates. Exiting."));
|
||||
if (!WriteStatusFailure(argv[4], SERVICE_INVALID_APPLYTO_DIR_ERROR)) {
|
||||
LOG_WARN(("Could not write update.status for previous failure."));
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
NS_tchar workingDirParent[MAX_PATH];
|
||||
NS_tsnprintf(workingDirParent,
|
||||
sizeof(workingDirParent) / sizeof(workingDirParent[0]),
|
||||
NS_T("%s"), argv[6]);
|
||||
if (!PathRemoveFileSpecW(workingDirParent)) {
|
||||
LOG_WARN(("Couldn't remove file spec when attempting to verify the "
|
||||
"working directory path. (%d)", GetLastError()));
|
||||
if (!WriteStatusFailure(argv[4], REMOVE_FILE_SPEC_ERROR)) {
|
||||
LOG_WARN(("Could not write update.status for previous failure."));
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (_wcsnicmp(workingDirParent, argv[5], MAX_PATH) != 0) {
|
||||
LOG_WARN(("The apply-to directory must be the same as or "
|
||||
"a child of the installation directory! Exiting."));
|
||||
if (!WriteStatusFailure(argv[4], SERVICE_INVALID_APPLYTO_DIR_STAGED_ERROR)) {
|
||||
LOG_WARN(("Could not write update.status for previous failure."));
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Use the passed in command line arguments for the update, except for the
|
||||
// path to updater.exe. We always look for updater.exe in the installation
|
||||
// directory, then we copy updater.exe to a the directory of the
|
||||
// MozillaMaintenance service so that a low integrity process cannot
|
||||
// replace the updater.exe at any point and use that for the update.
|
||||
// It also makes DLL injection attacks harder.
|
||||
WCHAR installDir[MAX_PATH + 1] = { L'\0' };
|
||||
if (!GetInstallationDir(argc - 3, argv + 3, installDir)) {
|
||||
LOG_WARN(("Could not get the installation directory"));
|
||||
if (!WriteStatusFailure(argv[4], SERVICE_INSTALLDIR_ERROR)) {
|
||||
LOG_WARN(("Could not write update.status for previous failure."));
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!DoesFallbackKeyExist()) {
|
||||
WCHAR maintenanceServiceKey[MAX_PATH + 1];
|
||||
if (CalculateRegistryPathFromFilePath(installDir, maintenanceServiceKey)) {
|
||||
LOG(("Checking for Maintenance Service registry. key: '%ls'",
|
||||
maintenanceServiceKey));
|
||||
HKEY baseKey = nullptr;
|
||||
if (RegOpenKeyExW(HKEY_LOCAL_MACHINE,
|
||||
maintenanceServiceKey, 0,
|
||||
KEY_READ | KEY_WOW64_64KEY,
|
||||
&baseKey) != ERROR_SUCCESS) {
|
||||
LOG_WARN(("The maintenance service registry key does not exist."));
|
||||
if (!WriteStatusFailure(argv[4], SERVICE_INSTALL_DIR_REG_ERROR)) {
|
||||
LOG_WARN(("Could not write update.status for previous failure."));
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
RegCloseKey(baseKey);
|
||||
} else {
|
||||
if (!WriteStatusFailure(argv[4], SERVICE_CALC_REG_PATH_ERROR)) {
|
||||
LOG_WARN(("Could not write update.status for previous failure."));
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
WCHAR installDirUpdater[MAX_PATH + 1] = { L'\0' };
|
||||
wcsncpy(installDirUpdater, installDir, MAX_PATH);
|
||||
if (!PathAppendSafe(installDirUpdater, L"updater.exe")) {
|
||||
LOG_WARN(("Install directory updater could not be determined."));
|
||||
result = FALSE;
|
||||
}
|
||||
|
||||
result = UpdaterIsValid(installDirUpdater, installDir, argv[4]);
|
||||
|
||||
WCHAR secureUpdaterPath[MAX_PATH + 1] = { L'\0' };
|
||||
if (result) {
|
||||
result = GetSecureUpdaterPath(secureUpdaterPath); // Does its own logging
|
||||
}
|
||||
if (result) {
|
||||
LOG(("Passed in path: '%ls'; Using this path for updating: '%ls'.",
|
||||
installDirUpdater, secureUpdaterPath));
|
||||
DeleteSecureUpdater(secureUpdaterPath);
|
||||
result = CopyFileW(installDirUpdater, secureUpdaterPath, FALSE);
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
LOG_WARN(("Could not copy path to secure location. (%d)",
|
||||
GetLastError()));
|
||||
if (!WriteStatusFailure(argv[4], SERVICE_COULD_NOT_COPY_UPDATER)) {
|
||||
LOG_WARN(("Could not write update.status could not copy updater error"));
|
||||
}
|
||||
} else {
|
||||
|
||||
// We obtained the path and copied it successfully, update the path to
|
||||
// use for the service update.
|
||||
argv[3] = secureUpdaterPath;
|
||||
|
||||
WCHAR installDirUpdaterINIPath[MAX_PATH + 1] = { L'\0' };
|
||||
WCHAR secureUpdaterINIPath[MAX_PATH + 1] = { L'\0' };
|
||||
if (PathGetSiblingFilePath(secureUpdaterINIPath, secureUpdaterPath,
|
||||
L"updater.ini") &&
|
||||
PathGetSiblingFilePath(installDirUpdaterINIPath, installDirUpdater,
|
||||
L"updater.ini")) {
|
||||
// This is non fatal if it fails there is no real harm
|
||||
if (!CopyFileW(installDirUpdaterINIPath, secureUpdaterINIPath, FALSE)) {
|
||||
LOG_WARN(("Could not copy updater.ini from: '%ls' to '%ls'. (%d)",
|
||||
installDirUpdaterINIPath, secureUpdaterINIPath, GetLastError()));
|
||||
}
|
||||
}
|
||||
|
||||
result = ProcessSoftwareUpdateCommand(argc - 3, argv + 3);
|
||||
DeleteSecureUpdater(secureUpdaterPath);
|
||||
}
|
||||
|
||||
// We might not reach here if the service install succeeded
|
||||
// because the service self updates itself and the service
|
||||
// installer will stop the service.
|
||||
LOG(("Service command %ls complete.", argv[2]));
|
||||
} else {
|
||||
LOG_WARN(("Service command not recognized: %ls.", argv[2]));
|
||||
// result is already set to FALSE
|
||||
}
|
||||
|
||||
LOG(("service command %ls complete with result: %ls.",
|
||||
argv[1], (result ? L"Success" : L"Failure")));
|
||||
return TRUE;
|
||||
}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
BOOL ExecuteServiceCommand(int argc, LPWSTR *argv);
|
||||
Loading…
Add table
Add a link
Reference in a new issue