michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: michael@0: #pragma comment(lib, "wtsapi32.lib") michael@0: #pragma comment(lib, "userenv.lib") michael@0: #pragma comment(lib, "shlwapi.lib") michael@0: #pragma comment(lib, "ole32.lib") michael@0: #pragma comment(lib, "rpcrt4.lib") michael@0: michael@0: #include "nsWindowsHelpers.h" michael@0: michael@0: #include "workmonitor.h" michael@0: #include "serviceinstall.h" michael@0: #include "servicebase.h" michael@0: #include "registrycertificates.h" michael@0: #include "uachelper.h" michael@0: #include "updatehelper.h" michael@0: #include "errors.h" michael@0: michael@0: // Wait 15 minutes for an update operation to run at most. michael@0: // Updates usually take less than a minute so this seems like a michael@0: // significantly large and safe amount of time to wait. michael@0: static const int TIME_TO_WAIT_ON_UPDATER = 15 * 60 * 1000; michael@0: char16_t* MakeCommandLine(int argc, char16_t **argv); michael@0: BOOL WriteStatusFailure(LPCWSTR updateDirPath, int errorCode); michael@0: BOOL PathGetSiblingFilePath(LPWSTR destinationBuffer, LPCWSTR siblingFilePath, michael@0: LPCWSTR newFileName); michael@0: michael@0: /* michael@0: * Read the update.status file and sets isApplying to true if michael@0: * the status is set to applying michael@0: * michael@0: * @param updateDirPath The directory where update.status is stored michael@0: * @param isApplying Out parameter for specifying if the status michael@0: * is set to applying or not. michael@0: * @return TRUE if the information was filled. michael@0: */ michael@0: static BOOL michael@0: IsStatusApplying(LPCWSTR updateDirPath, BOOL &isApplying) michael@0: { michael@0: isApplying = FALSE; michael@0: WCHAR updateStatusFilePath[MAX_PATH + 1] = {L'\0'}; michael@0: wcsncpy(updateStatusFilePath, updateDirPath, MAX_PATH); michael@0: if (!PathAppendSafe(updateStatusFilePath, L"update.status")) { michael@0: LOG_WARN(("Could not append path for update.status file")); michael@0: return FALSE; michael@0: } michael@0: michael@0: nsAutoHandle statusFile(CreateFileW(updateStatusFilePath, GENERIC_READ, michael@0: FILE_SHARE_READ | michael@0: FILE_SHARE_WRITE | michael@0: FILE_SHARE_DELETE, michael@0: nullptr, OPEN_EXISTING, 0, nullptr)); michael@0: michael@0: if (INVALID_HANDLE_VALUE == statusFile) { michael@0: LOG_WARN(("Could not open update.status file")); michael@0: return FALSE; michael@0: } michael@0: michael@0: char buf[32] = { 0 }; michael@0: DWORD read; michael@0: if (!ReadFile(statusFile, buf, sizeof(buf), &read, nullptr)) { michael@0: LOG_WARN(("Could not read from update.status file")); michael@0: return FALSE; michael@0: } michael@0: michael@0: LOG(("updater.exe returned status: %s", buf)); michael@0: michael@0: const char kApplying[] = "applying"; michael@0: isApplying = strncmp(buf, kApplying, michael@0: sizeof(kApplying) - 1) == 0; michael@0: return TRUE; michael@0: } michael@0: michael@0: /** michael@0: * Determines whether we're staging an update. michael@0: * michael@0: * @param argc The argc value normally sent to updater.exe michael@0: * @param argv The argv value normally sent to updater.exe michael@0: * @param boolean True if we're staging an update michael@0: */ michael@0: static bool michael@0: IsUpdateBeingStaged(int argc, LPWSTR *argv) michael@0: { michael@0: // PID will be set to -1 if we're supposed to stage an update. michael@0: return argc == 4 && !wcscmp(argv[3], L"-1"); michael@0: } michael@0: michael@0: /** michael@0: * Gets the installation directory from the arguments passed to updater.exe. michael@0: * michael@0: * @param argcTmp The argc value normally sent to updater.exe michael@0: * @param argvTmp The argv value normally sent to updater.exe michael@0: * @param aResultDir Buffer to hold the installation directory. michael@0: */ michael@0: static BOOL michael@0: GetInstallationDir(int argcTmp, LPWSTR *argvTmp, WCHAR aResultDir[MAX_PATH + 1]) michael@0: { michael@0: if (argcTmp < 2) { michael@0: return FALSE; michael@0: } michael@0: wcsncpy(aResultDir, argvTmp[2], MAX_PATH); michael@0: WCHAR* backSlash = wcsrchr(aResultDir, L'\\'); michael@0: // Make sure that the path does not include trailing backslashes michael@0: if (backSlash && (backSlash[1] == L'\0')) { michael@0: *backSlash = L'\0'; michael@0: } michael@0: bool backgroundUpdate = IsUpdateBeingStaged(argcTmp, argvTmp); michael@0: bool replaceRequest = (argcTmp >= 4 && wcsstr(argvTmp[3], L"/replace")); michael@0: if (backgroundUpdate || replaceRequest) { michael@0: return PathRemoveFileSpecW(aResultDir); michael@0: } michael@0: return TRUE; michael@0: } michael@0: michael@0: /** michael@0: * Runs an update process as the service using the SYSTEM account. michael@0: * michael@0: * @param argc The number of arguments in argv michael@0: * @param argv The arguments normally passed to updater.exe michael@0: * argv[0] must be the path to updater.exe michael@0: * @param processStarted Set to TRUE if the process was started. michael@0: * @return TRUE if the update process was run had a return code of 0. michael@0: */ michael@0: BOOL michael@0: StartUpdateProcess(int argc, michael@0: LPWSTR *argv, michael@0: LPCWSTR installDir, michael@0: BOOL &processStarted) michael@0: { michael@0: LOG(("Starting update process as the service in session 0.")); michael@0: STARTUPINFO si = {0}; michael@0: si.cb = sizeof(STARTUPINFO); michael@0: si.lpDesktop = L"winsta0\\Default"; michael@0: PROCESS_INFORMATION pi = {0}; michael@0: michael@0: // The updater command line is of the form: michael@0: // updater.exe update-dir apply [wait-pid [callback-dir callback-path args]] michael@0: LPWSTR cmdLine = MakeCommandLine(argc, argv); michael@0: michael@0: // If we're about to start the update process from session 0, michael@0: // then we should not show a GUI. This only really needs to be done michael@0: // on Vista and higher, but it's better to keep everything consistent michael@0: // across all OS if it's of no harm. michael@0: if (argc >= 2 ) { michael@0: // Setting the desktop to blank will ensure no GUI is displayed michael@0: si.lpDesktop = L""; michael@0: si.dwFlags |= STARTF_USESHOWWINDOW; michael@0: si.wShowWindow = SW_HIDE; michael@0: } michael@0: michael@0: // We move the updater.ini file out of the way because we will handle michael@0: // executing PostUpdate through the service. We handle PostUpdate from michael@0: // the service because there are some per user things that happen that michael@0: // can't run in session 0 which we run updater.exe in. michael@0: // Once we are done running updater.exe we rename updater.ini back so michael@0: // that if there were any errors the next updater.exe will run correctly. michael@0: WCHAR updaterINI[MAX_PATH + 1]; michael@0: WCHAR updaterINITemp[MAX_PATH + 1]; michael@0: BOOL selfHandlePostUpdate = FALSE; michael@0: // We use the updater.ini from the same directory as the updater.exe michael@0: // because of background updates. michael@0: if (PathGetSiblingFilePath(updaterINI, argv[0], L"updater.ini") && michael@0: PathGetSiblingFilePath(updaterINITemp, argv[0], L"updater.tmp")) { michael@0: selfHandlePostUpdate = MoveFileExW(updaterINI, updaterINITemp, michael@0: MOVEFILE_REPLACE_EXISTING); michael@0: } michael@0: michael@0: // Add an env var for MOZ_USING_SERVICE so the updater.exe can michael@0: // do anything special that it needs to do for service updates. michael@0: // Search in updater.cpp for more info on MOZ_USING_SERVICE. michael@0: putenv(const_cast("MOZ_USING_SERVICE=1")); michael@0: LOG(("Starting service with cmdline: %ls", cmdLine)); michael@0: processStarted = CreateProcessW(argv[0], cmdLine, michael@0: nullptr, nullptr, FALSE, michael@0: CREATE_DEFAULT_ERROR_MODE, michael@0: nullptr, michael@0: nullptr, &si, &pi); michael@0: // Empty value on putenv is how you remove an env variable in Windows michael@0: putenv(const_cast("MOZ_USING_SERVICE=")); michael@0: michael@0: BOOL updateWasSuccessful = FALSE; michael@0: if (processStarted) { michael@0: // Wait for the updater process to finish michael@0: LOG(("Process was started... waiting on result.")); michael@0: DWORD waitRes = WaitForSingleObject(pi.hProcess, TIME_TO_WAIT_ON_UPDATER); michael@0: if (WAIT_TIMEOUT == waitRes) { michael@0: // We waited a long period of time for updater.exe and it never finished michael@0: // so kill it. michael@0: TerminateProcess(pi.hProcess, 1); michael@0: } else { michael@0: // Check the return code of updater.exe to make sure we get 0 michael@0: DWORD returnCode; michael@0: if (GetExitCodeProcess(pi.hProcess, &returnCode)) { michael@0: LOG(("Process finished with return code %d.", returnCode)); michael@0: // updater returns 0 if successful. michael@0: updateWasSuccessful = (returnCode == 0); michael@0: } else { michael@0: LOG_WARN(("Process finished but could not obtain return code.")); michael@0: } michael@0: } michael@0: CloseHandle(pi.hProcess); michael@0: CloseHandle(pi.hThread); michael@0: michael@0: // Check just in case updater.exe didn't change the status from michael@0: // applying. If this is the case we report an error. michael@0: BOOL isApplying = FALSE; michael@0: if (IsStatusApplying(argv[1], isApplying) && isApplying) { michael@0: if (updateWasSuccessful) { michael@0: LOG(("update.status is still applying even know update " michael@0: " was successful.")); michael@0: if (!WriteStatusFailure(argv[1], michael@0: SERVICE_STILL_APPLYING_ON_SUCCESS)) { michael@0: LOG_WARN(("Could not write update.status still applying on" michael@0: " success error.")); michael@0: } michael@0: // Since we still had applying we know updater.exe didn't do its michael@0: // job correctly. michael@0: updateWasSuccessful = FALSE; michael@0: } else { michael@0: LOG_WARN(("update.status is still applying and update was not successful.")); michael@0: if (!WriteStatusFailure(argv[1], michael@0: SERVICE_STILL_APPLYING_ON_FAILURE)) { michael@0: LOG_WARN(("Could not write update.status still applying on" michael@0: " success error.")); michael@0: } michael@0: } michael@0: } michael@0: } else { michael@0: DWORD lastError = GetLastError(); michael@0: LOG_WARN(("Could not create process as current user, " michael@0: "updaterPath: %ls; cmdLine: %ls. (%d)", michael@0: argv[0], cmdLine, lastError)); michael@0: } michael@0: michael@0: // Now that we're done with the update, restore back the updater.ini file michael@0: // We use it ourselves, and also we want it back in case we had any type michael@0: // of error so that the normal update process can use it. michael@0: if (selfHandlePostUpdate) { michael@0: MoveFileExW(updaterINITemp, updaterINI, MOVEFILE_REPLACE_EXISTING); michael@0: michael@0: // Only run the PostUpdate if the update was successful michael@0: if (updateWasSuccessful && argc > 2) { michael@0: LPCWSTR updateInfoDir = argv[1]; michael@0: bool backgroundUpdate = IsUpdateBeingStaged(argc, argv); michael@0: michael@0: // Launch the PostProcess with admin access in session 0. This is michael@0: // actually launching the post update process but it takes in the michael@0: // callback app path to figure out where to apply to. michael@0: // The PostUpdate process with user only access will be done inside michael@0: // the unelevated updater.exe after the update process is complete michael@0: // from the service. We don't know here which session to start michael@0: // the user PostUpdate process from. michael@0: // Note that we don't need to do this if we're just staging the michael@0: // update in the background, as the PostUpdate step runs when michael@0: // performing the replacing in that case. michael@0: if (!backgroundUpdate) { michael@0: LOG(("Launching post update process as the service in session 0.")); michael@0: if (!LaunchWinPostProcess(installDir, updateInfoDir, true, nullptr)) { michael@0: LOG_WARN(("The post update process could not be launched." michael@0: " installDir: %ls, updateInfoDir: %ls", michael@0: installDir, updateInfoDir)); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: free(cmdLine); michael@0: return updateWasSuccessful; michael@0: } michael@0: michael@0: /** michael@0: * Processes a software update command michael@0: * michael@0: * @param argc The number of arguments in argv michael@0: * @param argv The arguments normally passed to updater.exe michael@0: * argv[0] must be the path to updater.exe michael@0: * @return TRUE if the update was successful. michael@0: */ michael@0: BOOL michael@0: ProcessSoftwareUpdateCommand(DWORD argc, LPWSTR *argv) michael@0: { michael@0: BOOL result = TRUE; michael@0: if (argc < 3) { michael@0: LOG_WARN(("Not enough command line parameters specified. " michael@0: "Updating update.status.")); michael@0: michael@0: // We can only update update.status if argv[1] exists. argv[1] is michael@0: // the directory where the update.status file exists. michael@0: if (argc < 2 || michael@0: !WriteStatusFailure(argv[1], michael@0: SERVICE_NOT_ENOUGH_COMMAND_LINE_ARGS)) { michael@0: LOG_WARN(("Could not write update.status service update failure. (%d)", michael@0: GetLastError())); michael@0: } michael@0: return FALSE; michael@0: } michael@0: michael@0: WCHAR installDir[MAX_PATH + 1] = {L'\0'}; michael@0: if (!GetInstallationDir(argc, argv, installDir)) { michael@0: LOG_WARN(("Could not get the installation directory")); michael@0: if (!WriteStatusFailure(argv[1], michael@0: SERVICE_INSTALLDIR_ERROR)) { michael@0: LOG_WARN(("Could not write update.status for GetInstallationDir failure.")); michael@0: } michael@0: return FALSE; michael@0: } michael@0: michael@0: // Make sure the path to the updater to use for the update is local. michael@0: // We do this check to make sure that file locking is available for michael@0: // race condition security checks. michael@0: BOOL isLocal = FALSE; michael@0: if (!IsLocalFile(argv[0], isLocal) || !isLocal) { michael@0: LOG_WARN(("Filesystem in path %ls is not supported (%d)", michael@0: argv[0], GetLastError())); michael@0: if (!WriteStatusFailure(argv[1], michael@0: SERVICE_UPDATER_NOT_FIXED_DRIVE)) { michael@0: LOG_WARN(("Could not write update.status service update failure. (%d)", michael@0: GetLastError())); michael@0: } michael@0: return FALSE; michael@0: } michael@0: michael@0: nsAutoHandle noWriteLock(CreateFileW(argv[0], GENERIC_READ, FILE_SHARE_READ, michael@0: nullptr, OPEN_EXISTING, 0, nullptr)); michael@0: if (INVALID_HANDLE_VALUE == noWriteLock) { michael@0: LOG_WARN(("Could not set no write sharing access on file. (%d)", michael@0: GetLastError())); michael@0: if (!WriteStatusFailure(argv[1], michael@0: SERVICE_COULD_NOT_LOCK_UPDATER)) { michael@0: LOG_WARN(("Could not write update.status service update failure. (%d)", michael@0: GetLastError())); michael@0: } michael@0: return FALSE; michael@0: } michael@0: michael@0: // Verify that the updater.exe that we are executing is the same michael@0: // as the one in the installation directory which we are updating. michael@0: // The installation dir that we are installing to is installDir. michael@0: WCHAR installDirUpdater[MAX_PATH + 1] = { L'\0' }; michael@0: wcsncpy(installDirUpdater, installDir, MAX_PATH); michael@0: if (!PathAppendSafe(installDirUpdater, L"updater.exe")) { michael@0: LOG_WARN(("Install directory updater could not be determined.")); michael@0: result = FALSE; michael@0: } michael@0: michael@0: BOOL updaterIsCorrect; michael@0: if (result && !VerifySameFiles(argv[0], installDirUpdater, michael@0: updaterIsCorrect)) { michael@0: LOG_WARN(("Error checking if the updaters are the same.\n" michael@0: "Path 1: %ls\nPath 2: %ls", argv[0], installDirUpdater)); michael@0: result = FALSE; michael@0: } michael@0: michael@0: if (result && !updaterIsCorrect) { michael@0: LOG_WARN(("The updaters do not match, updater will not run.")); michael@0: result = FALSE; michael@0: } michael@0: michael@0: if (result) { michael@0: LOG(("updater.exe was compared successfully to the installation directory" michael@0: " updater.exe.")); michael@0: } else { michael@0: if (!WriteStatusFailure(argv[1], michael@0: SERVICE_UPDATER_COMPARE_ERROR)) { michael@0: LOG_WARN(("Could not write update.status updater compare failure.")); michael@0: } michael@0: return FALSE; michael@0: } michael@0: michael@0: // Check to make sure the updater.exe module has the unique updater identity. michael@0: // This is a security measure to make sure that the signed executable that michael@0: // we will run is actually an updater. michael@0: HMODULE updaterModule = LoadLibraryEx(argv[0], nullptr, michael@0: LOAD_LIBRARY_AS_DATAFILE); michael@0: if (!updaterModule) { michael@0: LOG_WARN(("updater.exe module could not be loaded. (%d)", GetLastError())); michael@0: result = FALSE; michael@0: } else { michael@0: char updaterIdentity[64]; michael@0: if (!LoadStringA(updaterModule, IDS_UPDATER_IDENTITY, michael@0: updaterIdentity, sizeof(updaterIdentity))) { michael@0: LOG_WARN(("The updater.exe application does not contain the Mozilla" michael@0: " updater identity.")); michael@0: result = FALSE; michael@0: } michael@0: michael@0: if (strcmp(updaterIdentity, UPDATER_IDENTITY_STRING)) { michael@0: LOG_WARN(("The updater.exe identity string is not valid.")); michael@0: result = FALSE; michael@0: } michael@0: FreeLibrary(updaterModule); michael@0: } michael@0: michael@0: if (result) { michael@0: LOG(("The updater.exe application contains the Mozilla" michael@0: " updater identity.")); michael@0: } else { michael@0: if (!WriteStatusFailure(argv[1], michael@0: SERVICE_UPDATER_IDENTITY_ERROR)) { michael@0: LOG_WARN(("Could not write update.status no updater identity.")); michael@0: } michael@0: return TRUE; michael@0: } michael@0: michael@0: // Check for updater.exe sign problems michael@0: BOOL updaterSignProblem = FALSE; michael@0: #ifndef DISABLE_UPDATER_AUTHENTICODE_CHECK michael@0: updaterSignProblem = !DoesBinaryMatchAllowedCertificates(installDir, michael@0: argv[0]); michael@0: #endif michael@0: michael@0: // Only proceed with the update if we have no signing problems michael@0: if (!updaterSignProblem) { michael@0: BOOL updateProcessWasStarted = FALSE; michael@0: if (StartUpdateProcess(argc, argv, installDir, michael@0: updateProcessWasStarted)) { michael@0: LOG(("updater.exe was launched and run successfully!")); michael@0: LogFlush(); michael@0: michael@0: // Don't attempt to update the service when the update is being staged. michael@0: if (!IsUpdateBeingStaged(argc, argv)) { michael@0: // We might not execute code after StartServiceUpdate because michael@0: // the service installer will stop the service if it is running. michael@0: StartServiceUpdate(installDir); michael@0: } michael@0: } else { michael@0: result = FALSE; michael@0: LOG_WARN(("Error running update process. Updating update.status (%d)", michael@0: GetLastError())); michael@0: LogFlush(); michael@0: michael@0: // If the update process was started, then updater.exe is responsible for michael@0: // setting the failure code. If it could not be started then we do the michael@0: // work. We set an error instead of directly setting status pending michael@0: // so that the app.update.service.errors pref can be updated when michael@0: // the callback app restarts. michael@0: if (!updateProcessWasStarted) { michael@0: if (!WriteStatusFailure(argv[1], michael@0: SERVICE_UPDATER_COULD_NOT_BE_STARTED)) { michael@0: LOG_WARN(("Could not write update.status service update failure. (%d)", michael@0: GetLastError())); michael@0: } michael@0: } michael@0: } michael@0: } else { michael@0: result = FALSE; michael@0: LOG_WARN(("Could not start process due to certificate check error on " michael@0: "updater.exe. Updating update.status. (%d)", GetLastError())); michael@0: michael@0: // When there is a certificate check error on the updater.exe application, michael@0: // we want to write out the error. michael@0: if (!WriteStatusFailure(argv[1], michael@0: SERVICE_UPDATER_SIGN_ERROR)) { michael@0: LOG_WARN(("Could not write pending state to update.status. (%d)", michael@0: GetLastError())); michael@0: } michael@0: } michael@0: michael@0: return result; michael@0: } michael@0: michael@0: /** michael@0: * Obtains the updater path alongside a subdir of the service binary. michael@0: * The purpose of this function is to return a path that is likely high michael@0: * integrity and therefore more safe to execute code from. michael@0: * michael@0: * @param serviceUpdaterPath Out parameter for the path where the updater michael@0: * should be copied to. michael@0: * @return TRUE if a file path was obtained. michael@0: */ michael@0: BOOL michael@0: GetSecureUpdaterPath(WCHAR serviceUpdaterPath[MAX_PATH + 1]) michael@0: { michael@0: if (!GetModuleFileNameW(nullptr, serviceUpdaterPath, MAX_PATH)) { michael@0: LOG_WARN(("Could not obtain module filename when attempting to " michael@0: "use a secure updater path. (%d)", GetLastError())); michael@0: return FALSE; michael@0: } michael@0: michael@0: if (!PathRemoveFileSpecW(serviceUpdaterPath)) { michael@0: LOG_WARN(("Couldn't remove file spec when attempting to use a secure " michael@0: "updater path. (%d)", GetLastError())); michael@0: return FALSE; michael@0: } michael@0: michael@0: if (!PathAppendSafe(serviceUpdaterPath, L"update")) { michael@0: LOG_WARN(("Couldn't append file spec when attempting to use a secure " michael@0: "updater path. (%d)", GetLastError())); michael@0: return FALSE; michael@0: } michael@0: michael@0: CreateDirectoryW(serviceUpdaterPath, nullptr); michael@0: michael@0: if (!PathAppendSafe(serviceUpdaterPath, L"updater.exe")) { michael@0: LOG_WARN(("Couldn't append file spec when attempting to use a secure " michael@0: "updater path. (%d)", GetLastError())); michael@0: return FALSE; michael@0: } michael@0: michael@0: return TRUE; michael@0: } michael@0: michael@0: /** michael@0: * Deletes the passed in updater path and the associated updater.ini file. michael@0: * michael@0: * @param serviceUpdaterPath The path to delete. michael@0: * @return TRUE if a file was deleted. michael@0: */ michael@0: BOOL michael@0: DeleteSecureUpdater(WCHAR serviceUpdaterPath[MAX_PATH + 1]) michael@0: { michael@0: BOOL result = FALSE; michael@0: if (serviceUpdaterPath[0]) { michael@0: result = DeleteFileW(serviceUpdaterPath); michael@0: if (!result && GetLastError() != ERROR_PATH_NOT_FOUND && michael@0: GetLastError() != ERROR_FILE_NOT_FOUND) { michael@0: LOG_WARN(("Could not delete service updater path: '%ls'.", michael@0: serviceUpdaterPath)); michael@0: } michael@0: michael@0: WCHAR updaterINIPath[MAX_PATH + 1] = { L'\0' }; michael@0: if (PathGetSiblingFilePath(updaterINIPath, serviceUpdaterPath, michael@0: L"updater.ini")) { michael@0: result = DeleteFileW(updaterINIPath); michael@0: if (!result && GetLastError() != ERROR_PATH_NOT_FOUND && michael@0: GetLastError() != ERROR_FILE_NOT_FOUND) { michael@0: LOG_WARN(("Could not delete service updater INI path: '%ls'.", michael@0: updaterINIPath)); michael@0: } michael@0: } michael@0: } michael@0: return result; michael@0: } michael@0: michael@0: /** michael@0: * Executes a service command. michael@0: * michael@0: * @param argc The number of arguments in argv michael@0: * @param argv The service command line arguments, argv[0] and argv[1] michael@0: * and automatically included by Windows. argv[2] is the michael@0: * service command. michael@0: * michael@0: * @return FALSE if there was an error executing the service command. michael@0: */ michael@0: BOOL michael@0: ExecuteServiceCommand(int argc, LPWSTR *argv) michael@0: { michael@0: if (argc < 3) { michael@0: LOG_WARN(("Not enough command line arguments to execute a service command")); michael@0: return FALSE; michael@0: } michael@0: michael@0: // The tests work by making sure the log has changed, so we put a michael@0: // unique ID in the log. michael@0: RPC_WSTR guidString = RPC_WSTR(L""); michael@0: GUID guid; michael@0: HRESULT hr = CoCreateGuid(&guid); michael@0: if (SUCCEEDED(hr)) { michael@0: UuidToString(&guid, &guidString); michael@0: } michael@0: LOG(("Executing service command %ls, ID: %ls", michael@0: argv[2], reinterpret_cast(guidString))); michael@0: RpcStringFree(&guidString); michael@0: michael@0: BOOL result = FALSE; michael@0: if (!lstrcmpi(argv[2], L"software-update")) { michael@0: michael@0: // Use the passed in command line arguments for the update, except for the michael@0: // path to updater.exe. We copy updater.exe to a the directory of the michael@0: // MozillaMaintenance service so that a low integrity process cannot michael@0: // replace the updater.exe at any point and use that for the update. michael@0: // It also makes DLL injection attacks harder. michael@0: LPWSTR oldUpdaterPath = argv[3]; michael@0: WCHAR secureUpdaterPath[MAX_PATH + 1] = { L'\0' }; michael@0: result = GetSecureUpdaterPath(secureUpdaterPath); // Does its own logging michael@0: if (result) { michael@0: LOG(("Passed in path: '%ls'; Using this path for updating: '%ls'.", michael@0: oldUpdaterPath, secureUpdaterPath)); michael@0: DeleteSecureUpdater(secureUpdaterPath); michael@0: result = CopyFileW(oldUpdaterPath, secureUpdaterPath, FALSE); michael@0: } michael@0: michael@0: if (!result) { michael@0: LOG_WARN(("Could not copy path to secure location. (%d)", michael@0: GetLastError())); michael@0: if (argc > 4 && !WriteStatusFailure(argv[4], michael@0: SERVICE_COULD_NOT_COPY_UPDATER)) { michael@0: LOG_WARN(("Could not write update.status could not copy updater error")); michael@0: } michael@0: } else { michael@0: michael@0: // We obtained the path and copied it successfully, update the path to michael@0: // use for the service update. michael@0: argv[3] = secureUpdaterPath; michael@0: michael@0: WCHAR oldUpdaterINIPath[MAX_PATH + 1] = { L'\0' }; michael@0: WCHAR secureUpdaterINIPath[MAX_PATH + 1] = { L'\0' }; michael@0: if (PathGetSiblingFilePath(secureUpdaterINIPath, secureUpdaterPath, michael@0: L"updater.ini") && michael@0: PathGetSiblingFilePath(oldUpdaterINIPath, oldUpdaterPath, michael@0: L"updater.ini")) { michael@0: // This is non fatal if it fails there is no real harm michael@0: if (!CopyFileW(oldUpdaterINIPath, secureUpdaterINIPath, FALSE)) { michael@0: LOG_WARN(("Could not copy updater.ini from: '%ls' to '%ls'. (%d)", michael@0: oldUpdaterINIPath, secureUpdaterINIPath, GetLastError())); michael@0: } michael@0: } michael@0: michael@0: result = ProcessSoftwareUpdateCommand(argc - 3, argv + 3); michael@0: DeleteSecureUpdater(secureUpdaterPath); michael@0: } michael@0: michael@0: // We might not reach here if the service install succeeded michael@0: // because the service self updates itself and the service michael@0: // installer will stop the service. michael@0: LOG(("Service command %ls complete.", argv[2])); michael@0: } else { michael@0: LOG_WARN(("Service command not recognized: %ls.", argv[2])); michael@0: // result is already set to FALSE michael@0: } michael@0: michael@0: LOG(("service command %ls complete with result: %ls.", michael@0: argv[1], (result ? L"Success" : L"Failure"))); michael@0: return TRUE; michael@0: }