summaryrefslogtreecommitdiffstats
path: root/toolkit/components/maintenanceservice/serviceinstall.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /toolkit/components/maintenanceservice/serviceinstall.cpp
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'toolkit/components/maintenanceservice/serviceinstall.cpp')
-rw-r--r--toolkit/components/maintenanceservice/serviceinstall.cpp760
1 files changed, 760 insertions, 0 deletions
diff --git a/toolkit/components/maintenanceservice/serviceinstall.cpp b/toolkit/components/maintenanceservice/serviceinstall.cpp
new file mode 100644
index 0000000000..c59a74b94a
--- /dev/null
+++ b/toolkit/components/maintenanceservice/serviceinstall.cpp
@@ -0,0 +1,760 @@
+/* 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 "updatererrors.h"
+#include "commonupdatedir.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\\MozillaMaintenan" \
+ L"ceService"
+
+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";
+ mozilla::UniquePtr<char[]> serviceString;
+ int result = ReadStrings(path, kServiceKeys, kNumStrings, &serviceString);
+ if (result != OK) {
+ results->serviceDescription = mozilla::MakeUnique<char[]>(1);
+ results->serviceDescription.get()[0] = '\0';
+ }
+ results->serviceDescription.swap(serviceString);
+ 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. (%lu)", 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. (%lu)",
+ 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. (%lu)",
+ GetLastError()));
+ return FALSE;
+ }
+
+ if (!PathRemoveFileSpecW(updaterINIPath)) {
+ LOG_WARN(
+ ("Could not remove file spec when attempting to "
+ "modify service description. (%lu)",
+ GetLastError()));
+ return FALSE;
+ }
+
+ if (!PathAppendSafe(updaterINIPath, L"updater.ini")) {
+ LOG_WARN(
+ ("Could not append updater.ini filename when attempting to "
+ "modify service description. (%lu)",
+ GetLastError()));
+ return FALSE;
+ }
+
+ if (GetFileAttributesW(updaterINIPath) == INVALID_FILE_ATTRIBUTES) {
+ LOG_WARN(
+ ("updater.ini file does not exist, will not modify "
+ "service description. (%lu)",
+ GetLastError()));
+ return FALSE;
+ }
+
+ MaintenanceServiceStringTable serviceStrings;
+ int rv = ReadMaintenanceServiceStrings(updaterINIPath, &serviceStrings);
+ if (rv != OK || !strlen(serviceStrings.serviceDescription.get())) {
+ LOG_WARN(
+ ("updater.ini file does not contain a maintenance "
+ "service description."));
+ return FALSE;
+ }
+
+ int bufferSize = MultiByteToWideChar(
+ CP_UTF8, 0, serviceStrings.serviceDescription.get(), -1, nullptr, 0);
+ mozilla::UniquePtr<WCHAR[]> serviceDescription =
+ mozilla::MakeUnique<WCHAR[]>(bufferSize);
+ if (!MultiByteToWideChar(CP_UTF8, 0, serviceStrings.serviceDescription.get(),
+ -1, serviceDescription.get(), bufferSize)) {
+ LOG_WARN(("Could not convert description to wide string format. (%lu)",
+ GetLastError()));
+ return FALSE;
+ }
+
+ SERVICE_DESCRIPTIONW descriptionConfig;
+ descriptionConfig.lpDescription = serviceDescription.get();
+ if (!ChangeServiceConfig2W(serviceHandle, SERVICE_CONFIG_DESCRIPTION,
+ &descriptionConfig)) {
+ LOG_WARN(("Could not change service config. (%lu)", 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. (%lu)", GetLastError()));
+ return FALSE;
+ }
+ if (!PathAppendSafe(fixedPath, L"maintenanceservice.exe")) {
+ LOG_WARN(("Couldn't append file spec. (%lu)", 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. (%lu)", 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. (%lu)", 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. (%lu)",
+ 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. (%lu)", 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. (%lu)",
+ 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. (%lu)",
+ 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. (%lu)",
+ 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. (%lu)",
+ 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. (%lu)",
+ 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. (%lu)",
+ 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. (%lu)",
+ 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. (%lu)",
+ 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. (%lu)", GetLastError()));
+ return FALSE;
+ }
+
+ // Open the service
+ nsAutoServiceHandle schService(
+ OpenServiceW(schSCManager, SVC_NAME, SERVICE_ALL_ACCESS));
+ if (!schService) {
+ LOG_WARN(("Could not open service. (%lu)", 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. (%lu)", 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: %lu", 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. (%lu)", GetLastError()));
+ return FALSE;
+ }
+
+ // Open the service
+ nsAutoServiceHandle schService(
+ OpenServiceW(schSCManager, SVC_NAME, SERVICE_ALL_ACCESS));
+ if (!schService) {
+ LOG_WARN(("Could not open service. (%lu)", 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. (%lu)",
+ GetLastError()));
+ return GetLastError();
+ }
+
+ DWORD size = needed;
+ psd = (PSECURITY_DESCRIPTOR)LocalAlloc(LPTR, size);
+ if (!psd) {
+ LOG_WARN(
+ ("Could not allocate security descriptor. (%lu)", 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. (%lu)", 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. (%lu)", GetLastError()));
+ return GetLastError();
+ }
+
+ PSID sidBuiltinUsers;
+ DWORD SIDSize = SECURITY_MAX_SID_SIZE;
+ sidBuiltinUsers = LocalAlloc(LMEM_FIXED, SIDSize);
+ if (!sidBuiltinUsers) {
+ LOG_WARN(("Could not allocate SID memory. (%lu)", GetLastError()));
+ return GetLastError();
+ }
+ UniqueSidPtr uniqueSidBuiltinUsers(sidBuiltinUsers);
+
+ if (!CreateWellKnownSid(WinBuiltinUsersSid, nullptr, sidBuiltinUsers,
+ &SIDSize)) {
+ DWORD lastError = GetLastError();
+ LOG_WARN(("Could not create BI\\Users SID. (%lu)", lastError));
+ return lastError;
+ }
+
+ PSID sidInteractive;
+ SIDSize = SECURITY_MAX_SID_SIZE;
+ sidInteractive = LocalAlloc(LMEM_FIXED, SIDSize);
+ if (!sidInteractive) {
+ LOG_WARN(("Could not allocate SID memory. (%lu)", GetLastError()));
+ return GetLastError();
+ }
+ UniqueSidPtr uniqueSidInteractive(sidInteractive);
+
+ if (!CreateWellKnownSid(WinInteractiveSid, nullptr, sidInteractive,
+ &SIDSize)) {
+ DWORD lastError = GetLastError();
+ LOG_WARN(("Could not create Interactive SID. (%lu)", lastError));
+ return lastError;
+ }
+
+ const size_t eaCount = 2;
+ EXPLICIT_ACCESS ea[eaCount];
+ ZeroMemory(ea, sizeof(ea));
+ ea[0].grfAccessMode = REVOKE_ACCESS;
+ ea[0].Trustee.TrusteeForm = TRUSTEE_IS_SID;
+ ea[0].Trustee.TrusteeType = TRUSTEE_IS_GROUP;
+ ea[0].Trustee.ptstrName = static_cast<LPWSTR>(sidBuiltinUsers);
+ ea[1].grfAccessPermissions = SERVICE_START | SERVICE_STOP | GENERIC_READ;
+ ea[1].grfAccessMode = SET_ACCESS;
+ ea[1].grfInheritance = NO_INHERITANCE;
+ ea[1].Trustee.TrusteeForm = TRUSTEE_IS_SID;
+ ea[1].Trustee.TrusteeType = TRUSTEE_IS_GROUP;
+ ea[1].Trustee.ptstrName = static_cast<LPWSTR>(sidInteractive);
+
+ DWORD lastError = SetEntriesInAclW(eaCount, ea, pacl, &pNewAcl);
+ if (ERROR_SUCCESS != lastError) {
+ LOG_WARN(("Could not set entries in ACL. (%lu)", lastError));
+ return lastError;
+ }
+
+ // Initialize a new security descriptor.
+ SECURITY_DESCRIPTOR sd;
+ if (!InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION)) {
+ LOG_WARN(
+ ("Could not initialize security descriptor. (%lu)", 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. (%lu)", 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. (%lu)", GetLastError()));
+ return GetLastError();
+ }
+
+ // Woohoo, raise the roof
+ LOG(("User access was set successfully on the service."));
+ return ERROR_SUCCESS;
+}