summaryrefslogtreecommitdiffstats
path: root/onlineupdate/source/service/serviceinstall.cxx
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:06:44 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:06:44 +0000
commited5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch)
tree7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /onlineupdate/source/service/serviceinstall.cxx
parentInitial commit. (diff)
downloadlibreoffice-cb75148ebd0135178ff46f89a30139c44f8d2040.tar.xz
libreoffice-cb75148ebd0135178ff46f89a30139c44f8d2040.zip
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'onlineupdate/source/service/serviceinstall.cxx')
-rw-r--r--onlineupdate/source/service/serviceinstall.cxx850
1 files changed, 850 insertions, 0 deletions
diff --git a/onlineupdate/source/service/serviceinstall.cxx b/onlineupdate/source/service/serviceinstall.cxx
new file mode 100644
index 000000000..7d53d25cb
--- /dev/null
+++ b/onlineupdate/source/service/serviceinstall.cxx
@@ -0,0 +1,850 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <windows.h>
+#include <aclapi.h>
+#include <stdlib.h>
+#include <shlwapi.h>
+
+// Used for DNLEN and UNLEN
+#include <lm.h>
+
+#include "serviceinstall.hxx"
+#include "servicebase.hxx"
+#include "updatehelper.h"
+#include "shellapi.h"
+#include "readstrings.h"
+#include "errors.h"
+
+#include <memory>
+
+#pragma comment(lib, "version.lib")
+
+namespace {
+
+struct AutoServiceHandle
+{
+ AutoServiceHandle(SC_HANDLE handle):
+ mHandle(handle)
+ {
+ }
+
+ ~AutoServiceHandle()
+ {
+ releaseHandle(mHandle);
+ }
+
+ void releaseHandle(SC_HANDLE handle)
+ {
+ if (handle != nullptr)
+ CloseServiceHandle(handle);
+ }
+
+ SC_HANDLE get() const
+ {
+ return mHandle;
+ }
+
+ operator bool() const
+ {
+ return mHandle != nullptr;
+ }
+
+ void reset(SC_HANDLE handle = nullptr)
+ {
+ releaseHandle(mHandle);
+ mHandle = handle;
+ }
+
+ SC_HANDLE mHandle;
+};
+
+}
+
+/**
+ * A wrapper function to read strings for the maintenance service.
+ *
+ * @param path The path of the ini file to read from
+ * @param results The maintenance service strings that were read
+ * @return OK on success
+*/
+static int
+ReadMaintenanceServiceStrings(LPCWSTR path,
+ MaintenanceServiceStringTable *results)
+{
+ // Read in the maintenance service description string if specified.
+ const unsigned int kNumStrings = 1;
+ // TODO: moggi: needs adaptation for LibreOffice
+ const char *kServiceKeys = "MozillaMaintenanceDescription\0";
+ char serviceStrings[kNumStrings][MAX_TEXT_LEN];
+ int result = ReadStrings(path, kServiceKeys,
+ kNumStrings, serviceStrings);
+ if (result != OK)
+ {
+ serviceStrings[0][0] = '\0';
+ }
+ strncpy(results->serviceDescription,
+ serviceStrings[0], MAX_TEXT_LEN - 1);
+ results->serviceDescription[MAX_TEXT_LEN - 1] = '\0';
+ return result;
+}
+
+/**
+ * Obtains the version number from the specified PE file's version information
+ * Version Format: A.B.C.D (Example 10.0.0.300)
+ *
+ * @param path The path of the file to check the version on
+ * @param A The first part of the version number
+ * @param B The second part of the version number
+ * @param C The third part of the version number
+ * @param D The fourth part of the version number
+ * @return TRUE if successful
+ */
+static BOOL
+GetVersionNumberFromPath(LPWSTR path, DWORD &A, DWORD &B,
+ DWORD &C, DWORD &D)
+{
+ DWORD fileVersionInfoSize = GetFileVersionInfoSizeW(path, 0);
+ std::unique_ptr<char[]> fileVersionInfo(new char[fileVersionInfoSize]);
+ if (!GetFileVersionInfoW(path, 0, fileVersionInfoSize,
+ fileVersionInfo.get()))
+ {
+ LOG_WARN(("Could not obtain file info of old service. (%d)",
+ GetLastError()));
+ return FALSE;
+ }
+
+ VS_FIXEDFILEINFO *fixedFileInfo =
+ reinterpret_cast<VS_FIXEDFILEINFO *>(fileVersionInfo.get());
+ UINT size;
+ if (!VerQueryValueW(fileVersionInfo.get(), L"\\",
+ reinterpret_cast<LPVOID*>(&fixedFileInfo), &size))
+ {
+ LOG_WARN(("Could not query file version info of old service. (%d)",
+ GetLastError()));
+ return FALSE;
+ }
+
+ A = HIWORD(fixedFileInfo->dwFileVersionMS);
+ B = LOWORD(fixedFileInfo->dwFileVersionMS);
+ C = HIWORD(fixedFileInfo->dwFileVersionLS);
+ D = LOWORD(fixedFileInfo->dwFileVersionLS);
+ return TRUE;
+}
+
+/**
+ * Updates the service description with what is stored in updater.ini
+ * at the same path as the currently executing module binary.
+ *
+ * @param serviceHandle A handle to an opened service with
+ * SERVICE_CHANGE_CONFIG access right
+ * @param TRUE on success.
+*/
+BOOL
+UpdateServiceDescription(SC_HANDLE serviceHandle)
+{
+ WCHAR updaterINIPath[MAX_PATH + 1];
+ if (!GetModuleFileNameW(nullptr, updaterINIPath,
+ sizeof(updaterINIPath) /
+ sizeof(updaterINIPath[0])))
+ {
+ LOG_WARN(("Could not obtain module filename when attempting to "
+ "modify service description. (%d)", GetLastError()));
+ return FALSE;
+ }
+
+ if (!PathRemoveFileSpecW(updaterINIPath))
+ {
+ LOG_WARN(("Could not remove file spec when attempting to "
+ "modify service description. (%d)", GetLastError()));
+ return FALSE;
+ }
+
+ if (!PathAppendSafe(updaterINIPath, L"updater.ini"))
+ {
+ LOG_WARN(("Could not append updater.ini filename when attempting to "
+ "modify service description. (%d)", GetLastError()));
+ return FALSE;
+ }
+
+ if (GetFileAttributesW(updaterINIPath) == INVALID_FILE_ATTRIBUTES)
+ {
+ LOG_WARN(("updater.ini file does not exist, will not modify "
+ "service description. (%d)", GetLastError()));
+ return FALSE;
+ }
+
+ MaintenanceServiceStringTable serviceStrings;
+ int rv = ReadMaintenanceServiceStrings(updaterINIPath, &serviceStrings);
+ if (rv != OK || !strlen(serviceStrings.serviceDescription))
+ {
+ LOG_WARN(("updater.ini file does not contain a maintenance "
+ "service description."));
+ return FALSE;
+ }
+
+ WCHAR serviceDescription[MAX_TEXT_LEN];
+ if (!MultiByteToWideChar(CP_UTF8, 0,
+ serviceStrings.serviceDescription, -1,
+ serviceDescription,
+ sizeof(serviceDescription) /
+ sizeof(serviceDescription[0])))
+ {
+ LOG_WARN(("Could not convert description to wide string format. (%d)",
+ GetLastError()));
+ return FALSE;
+ }
+
+ SERVICE_DESCRIPTIONW descriptionConfig;
+ descriptionConfig.lpDescription = serviceDescription;
+ if (!ChangeServiceConfig2W(serviceHandle,
+ SERVICE_CONFIG_DESCRIPTION,
+ &descriptionConfig))
+ {
+ LOG_WARN(("Could not change service config. (%d)", GetLastError()));
+ return FALSE;
+ }
+
+ LOG(("The service description was updated successfully."));
+ return TRUE;
+}
+
+/**
+ * Determines if the MozillaMaintenance service path needs to be updated
+ * and fixes it if it is wrong.
+ *
+ * @param service A handle to the service to fix.
+ * @param currentServicePath The current (possibly wrong) path that is used.
+ * @param servicePathWasWrong Out parameter set to TRUE if a fix was needed.
+ * @return TRUE if the service path is now correct.
+*/
+BOOL
+FixServicePath(SC_HANDLE service,
+ LPCWSTR currentServicePath,
+ BOOL &servicePathWasWrong)
+{
+ // When we originally upgraded the MozillaMaintenance service we
+ // would uninstall the service on each upgrade. This had an
+ // intermittent error which could cause the service to use the file
+ // maintenanceservice_tmp.exe as the install path. Only a small number
+ // of Nightly users would be affected by this, but we check for this
+ // state here and fix the user if they are affected.
+ //
+ // We also fix the path in the case of the path not being quoted.
+ size_t currentServicePathLen = wcslen(currentServicePath);
+ bool doesServiceHaveCorrectPath =
+ currentServicePathLen > 2 &&
+ !wcsstr(currentServicePath, L"maintenanceservice_tmp.exe") &&
+ currentServicePath[0] == L'\"' &&
+ currentServicePath[currentServicePathLen - 1] == L'\"';
+
+ if (doesServiceHaveCorrectPath)
+ {
+ LOG(("The MozillaMaintenance service path is correct."));
+ servicePathWasWrong = FALSE;
+ return TRUE;
+ }
+ // This is a recoverable situation so not logging as a warning
+ LOG(("The MozillaMaintenance path is NOT correct. It was: %ls",
+ currentServicePath));
+
+ servicePathWasWrong = TRUE;
+ WCHAR fixedPath[MAX_PATH + 1] = { L'\0' };
+ wcsncpy(fixedPath, currentServicePath, MAX_PATH);
+ PathUnquoteSpacesW(fixedPath);
+ if (!PathRemoveFileSpecW(fixedPath))
+ {
+ LOG_WARN(("Couldn't remove file spec. (%d)", GetLastError()));
+ return FALSE;
+ }
+ if (!PathAppendSafe(fixedPath, L"maintenanceservice.exe"))
+ {
+ LOG_WARN(("Couldn't append file spec. (%d)", GetLastError()));
+ return FALSE;
+ }
+ PathQuoteSpacesW(fixedPath);
+
+
+ if (!ChangeServiceConfigW(service, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE,
+ SERVICE_NO_CHANGE, fixedPath, nullptr, nullptr,
+ nullptr, nullptr, nullptr, nullptr))
+ {
+ LOG_WARN(("Could not fix service path. (%d)", GetLastError()));
+ return FALSE;
+ }
+
+ LOG(("Fixed service path to: %ls.", fixedPath));
+ return TRUE;
+}
+
+/**
+ * Installs or upgrades the SVC_NAME service.
+ * If an existing service is already installed, we replace it with the
+ * currently running process.
+ *
+ * @param action The action to perform.
+ * @return TRUE if the service was installed/upgraded
+ */
+BOOL
+SvcInstall(SvcInstallAction action)
+{
+ // Get a handle to the local computer SCM database with full access rights.
+ AutoServiceHandle schSCManager(OpenSCManager(nullptr, nullptr,
+ SC_MANAGER_ALL_ACCESS));
+ if (!schSCManager)
+ {
+ LOG_WARN(("Could not open service manager. (%d)", GetLastError()));
+ return FALSE;
+ }
+
+ WCHAR newServiceBinaryPath[MAX_PATH + 1];
+ if (!GetModuleFileNameW(nullptr, newServiceBinaryPath,
+ sizeof(newServiceBinaryPath) /
+ sizeof(newServiceBinaryPath[0])))
+ {
+ LOG_WARN(("Could not obtain module filename when attempting to "
+ "install service. (%d)",
+ GetLastError()));
+ return FALSE;
+ }
+
+ // Check if we already have the service installed.
+ AutoServiceHandle schService(OpenServiceW(schSCManager.get(),
+ SVC_NAME,
+ SERVICE_ALL_ACCESS));
+ DWORD lastError = GetLastError();
+ if (!schService && ERROR_SERVICE_DOES_NOT_EXIST != lastError)
+ {
+ // The service exists but we couldn't open it
+ LOG_WARN(("Could not open service. (%d)", GetLastError()));
+ return FALSE;
+ }
+
+ if (schService)
+ {
+ // The service exists but it may not have the correct permissions.
+ // This could happen if the permissions were not set correctly originally
+ // or have been changed after the installation. This will reset the
+ // permissions back to allow limited user accounts.
+ if (!SetUserAccessServiceDACL(schService.get()))
+ {
+ LOG_WARN(("Could not reset security ACE on service handle. It might not be "
+ "possible to start the service. This error should never "
+ "happen. (%d)", GetLastError()));
+ }
+
+ // The service exists and we opened it
+ DWORD bytesNeeded;
+ if (!QueryServiceConfigW(schService.get(), nullptr, 0, &bytesNeeded) &&
+ GetLastError() != ERROR_INSUFFICIENT_BUFFER)
+ {
+ LOG_WARN(("Could not determine buffer size for query service config. (%d)",
+ GetLastError()));
+ return FALSE;
+ }
+
+ // Get the service config information, in particular we want the binary
+ // path of the service.
+ std::unique_ptr<char[]> serviceConfigBuffer(new char[bytesNeeded]);
+ if (!QueryServiceConfigW(schService.get(),
+ reinterpret_cast<QUERY_SERVICE_CONFIGW*>(serviceConfigBuffer.get()),
+ bytesNeeded, &bytesNeeded))
+ {
+ LOG_WARN(("Could open service but could not query service config. (%d)",
+ GetLastError()));
+ return FALSE;
+ }
+ QUERY_SERVICE_CONFIGW &serviceConfig =
+ *reinterpret_cast<QUERY_SERVICE_CONFIGW*>(serviceConfigBuffer.get());
+
+ // Check if we need to fix the service path
+ BOOL servicePathWasWrong;
+ static BOOL alreadyCheckedFixServicePath = FALSE;
+ if (!alreadyCheckedFixServicePath)
+ {
+ if (!FixServicePath(schService.get(), serviceConfig.lpBinaryPathName,
+ servicePathWasWrong))
+ {
+ LOG_WARN(("Could not fix service path. This should never happen. (%d)",
+ GetLastError()));
+ // True is returned because the service is pointing to
+ // maintenanceservice_tmp.exe so it actually was upgraded to the
+ // newest installed service.
+ return TRUE;
+ }
+ else if (servicePathWasWrong)
+ {
+ // Now that the path is fixed we should re-attempt the install.
+ // This current process' image path is maintenanceservice_tmp.exe.
+ // The service used to point to maintenanceservice_tmp.exe.
+ // The service was just fixed to point to maintenanceservice.exe.
+ // Re-attempting an install from scratch will work as normal.
+ alreadyCheckedFixServicePath = TRUE;
+ LOG(("Restarting install action: %d", action));
+ return SvcInstall(action);
+ }
+ }
+
+ // Ensure the service path is not quoted. We own this memory and know it to
+ // be large enough for the quoted path, so it is large enough for the
+ // unquoted path. This function cannot fail.
+ PathUnquoteSpacesW(serviceConfig.lpBinaryPathName);
+
+ // Obtain the existing maintenanceservice file's version number and
+ // the new file's version number. Versions are in the format of
+ // A.B.C.D.
+ DWORD existingA, existingB, existingC, existingD;
+ DWORD newA, newB, newC, newD;
+ BOOL obtainedExistingVersionInfo =
+ GetVersionNumberFromPath(serviceConfig.lpBinaryPathName,
+ existingA, existingB,
+ existingC, existingD);
+ if (!GetVersionNumberFromPath(newServiceBinaryPath, newA,
+ newB, newC, newD))
+ {
+ LOG_WARN(("Could not obtain version number from new path"));
+ return FALSE;
+ }
+
+ // Check if we need to replace the old binary with the new one
+ // If we couldn't get the old version info then we assume we should
+ // replace it.
+ if (ForceInstallSvc == action ||
+ !obtainedExistingVersionInfo ||
+ (existingA < newA) ||
+ (existingA == newA && existingB < newB) ||
+ (existingA == newA && existingB == newB &&
+ existingC < newC) ||
+ (existingA == newA && existingB == newB &&
+ existingC == newC && existingD < newD))
+ {
+
+ // We have a newer updater, so update the description from the INI file.
+ UpdateServiceDescription(schService.get());
+
+ schService.reset();
+ if (!StopService())
+ {
+ return FALSE;
+ }
+
+ if (!wcscmp(newServiceBinaryPath, serviceConfig.lpBinaryPathName))
+ {
+ LOG(("File is already in the correct location, no action needed for "
+ "upgrade. The path is: \"%ls\"", newServiceBinaryPath));
+ return TRUE;
+ }
+
+ BOOL result = TRUE;
+
+ // Attempt to copy the new binary over top the existing binary.
+ // If there is an error we try to move it out of the way and then
+ // copy it in. First try the safest / easiest way to overwrite the file.
+ if (!CopyFileW(newServiceBinaryPath,
+ serviceConfig.lpBinaryPathName, FALSE))
+ {
+ LOG_WARN(("Could not overwrite old service binary file. "
+ "This should never happen, but if it does the next "
+ "upgrade will fix it, the service is not a critical "
+ "component that needs to be installed for upgrades "
+ "to work. (%d)", GetLastError()));
+
+ // We rename the last 3 filename chars in an unsafe way. Manually
+ // verify there are more than 3 chars for safe failure in MoveFileExW.
+ const size_t len = wcslen(serviceConfig.lpBinaryPathName);
+ if (len > 3)
+ {
+ // Calculate the temp file path that we're moving the file to. This
+ // is the same as the proper service path but with a .old extension.
+ LPWSTR oldServiceBinaryTempPath =
+ new WCHAR[len + 1];
+ memset(oldServiceBinaryTempPath, 0, (len + 1) * sizeof (WCHAR));
+ wcsncpy(oldServiceBinaryTempPath, serviceConfig.lpBinaryPathName, len);
+ // Rename the last 3 chars to 'old'
+ wcsncpy(oldServiceBinaryTempPath + len - 3, L"old", 3);
+
+ // Move the current (old) service file to the temp path.
+ if (MoveFileExW(serviceConfig.lpBinaryPathName,
+ oldServiceBinaryTempPath,
+ MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH))
+ {
+ // The old binary is moved out of the way, copy in the new one.
+ if (!CopyFileW(newServiceBinaryPath,
+ serviceConfig.lpBinaryPathName, FALSE))
+ {
+ // It is best to leave the old service binary in this condition.
+ LOG_WARN(("The new service binary could not be copied in."
+ " The service will not be upgraded."));
+ result = FALSE;
+ }
+ else
+ {
+ LOG(("The new service binary was copied in by first moving the"
+ " old one out of the way."));
+ }
+
+ // Attempt to get rid of the old service temp path.
+ if (DeleteFileW(oldServiceBinaryTempPath))
+ {
+ LOG(("The old temp service path was deleted: %ls.",
+ oldServiceBinaryTempPath));
+ }
+ else
+ {
+ // The old temp path could not be removed. It will be removed
+ // the next time the user can't copy the binary in or on uninstall.
+ LOG_WARN(("The old temp service path was not deleted."));
+ }
+ }
+ else
+ {
+ // It is best to leave the old service binary in this condition.
+ LOG_WARN(("Could not move old service file out of the way from:"
+ " \"%ls\" to \"%ls\". Service will not be upgraded. (%d)",
+ serviceConfig.lpBinaryPathName,
+ oldServiceBinaryTempPath, GetLastError()));
+ result = FALSE;
+ }
+ delete[] oldServiceBinaryTempPath;
+ }
+ else
+ {
+ // It is best to leave the old service binary in this condition.
+ LOG_WARN(("Service binary path was less than 3, service will"
+ " not be updated. This should never happen."));
+ result = FALSE;
+ }
+ }
+ else
+ {
+ LOG(("The new service binary was copied in."));
+ }
+
+ // We made a copy of ourselves to the existing location.
+ // The tmp file (the process of which we are executing right now) will be
+ // left over. Attempt to delete the file on the next reboot.
+ if (MoveFileExW(newServiceBinaryPath, nullptr,
+ MOVEFILE_DELAY_UNTIL_REBOOT))
+ {
+ LOG(("Deleting the old file path on the next reboot: %ls.",
+ newServiceBinaryPath));
+ }
+ else
+ {
+ LOG_WARN(("Call to delete the old file path failed: %ls.",
+ newServiceBinaryPath));
+ }
+
+ return result;
+ }
+
+ // We don't need to copy ourselves to the existing location.
+ // The tmp file (the process of which we are executing right now) will be
+ // left over. Attempt to delete the file on the next reboot.
+ MoveFileExW(newServiceBinaryPath, nullptr, MOVEFILE_DELAY_UNTIL_REBOOT);
+
+ // nothing to do, we already have a newer service installed
+ return TRUE;
+ }
+
+ // If the service does not exist and we are upgrading, don't install it.
+ if (UpgradeSvc == action)
+ {
+ // The service does not exist and we are upgrading, so don't install it
+ return TRUE;
+ }
+
+ // Quote the path only if it contains spaces.
+ PathQuoteSpacesW(newServiceBinaryPath);
+ // The service does not already exist so create the service as on demand
+ schService.reset(CreateServiceW(schSCManager.get(), SVC_NAME, SVC_DISPLAY_NAME,
+ SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS,
+ SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL,
+ newServiceBinaryPath, nullptr, nullptr,
+ nullptr, nullptr, nullptr));
+ if (!schService)
+ {
+ LOG_WARN(("Could not create Windows service. "
+ "This error should never happen since a service install "
+ "should only be called when elevated. (%d)", GetLastError()));
+ return FALSE;
+ }
+
+ if (!SetUserAccessServiceDACL(schService.get()))
+ {
+ LOG_WARN(("Could not set security ACE on service handle, the service will not "
+ "be able to be started from unelevated processes. "
+ "This error should never happen. (%d)",
+ GetLastError()));
+ }
+
+ UpdateServiceDescription(schService.get());
+
+ return TRUE;
+}
+
+/**
+ * Stops the Maintenance service.
+ *
+ * @return TRUE if successful.
+ */
+BOOL
+StopService()
+{
+ // Get a handle to the local computer SCM database with full access rights.
+ AutoServiceHandle schSCManager(OpenSCManager(nullptr, nullptr,
+ SC_MANAGER_ALL_ACCESS));
+ if (!schSCManager)
+ {
+ LOG_WARN(("Could not open service manager. (%d)", GetLastError()));
+ return FALSE;
+ }
+
+ // Open the service
+ AutoServiceHandle schService(OpenServiceW(schSCManager.get(), SVC_NAME,
+ SERVICE_ALL_ACCESS));
+ if (!schService)
+ {
+ LOG_WARN(("Could not open service. (%d)", GetLastError()));
+ return FALSE;
+ }
+
+ LOG(("Sending stop request..."));
+ SERVICE_STATUS status;
+ SetLastError(ERROR_SUCCESS);
+ if (!ControlService(schService.get(), SERVICE_CONTROL_STOP, &status) &&
+ GetLastError() != ERROR_SERVICE_NOT_ACTIVE)
+ {
+ LOG_WARN(("Error sending stop request. (%d)", GetLastError()));
+ }
+
+ schSCManager.reset();
+ schService.reset();
+
+ LOG(("Waiting for service stop..."));
+ DWORD lastState = WaitForServiceStop(SVC_NAME, 30);
+
+ // The service can be in a stopped state but the exe still in use
+ // so make sure the process is really gone before proceeding
+ WaitForProcessExit(L"maintenanceservice.exe", 30);
+ LOG(("Done waiting for service stop, last service state: %d", lastState));
+
+ return lastState == SERVICE_STOPPED;
+}
+
+/**
+ * Uninstalls the Maintenance service.
+ *
+ * @return TRUE if successful.
+ */
+BOOL
+SvcUninstall()
+{
+ // Get a handle to the local computer SCM database with full access rights.
+ AutoServiceHandle schSCManager(OpenSCManager(nullptr, nullptr,
+ SC_MANAGER_ALL_ACCESS));
+ if (!schSCManager)
+ {
+ LOG_WARN(("Could not open service manager. (%d)", GetLastError()));
+ return FALSE;
+ }
+
+ // Open the service
+ AutoServiceHandle schService(OpenServiceW(schSCManager.get(), SVC_NAME,
+ SERVICE_ALL_ACCESS));
+ if (!schService)
+ {
+ LOG_WARN(("Could not open service. (%d)", GetLastError()));
+ return FALSE;
+ }
+
+ //Stop the service so it deletes faster and so the uninstaller
+ // can actually delete its EXE.
+ DWORD totalWaitTime = 0;
+ SERVICE_STATUS status;
+ static const int maxWaitTime = 1000 * 60; // Never wait more than a minute
+ if (ControlService(schService.get(), SERVICE_CONTROL_STOP, &status))
+ {
+ do
+ {
+ Sleep(status.dwWaitHint);
+ totalWaitTime += (status.dwWaitHint + 10);
+ if (status.dwCurrentState == SERVICE_STOPPED)
+ {
+ break;
+ }
+ else if (totalWaitTime > maxWaitTime)
+ {
+ break;
+ }
+ }
+ while (QueryServiceStatus(schService.get(), &status));
+ }
+
+ // Delete the service or mark it for deletion
+ BOOL deleted = DeleteService(schService.get());
+ if (!deleted)
+ {
+ deleted = (GetLastError() == ERROR_SERVICE_MARKED_FOR_DELETE);
+ }
+
+ return deleted;
+}
+
+/**
+ * Sets the access control list for user access for the specified service.
+ *
+ * @param hService The service to set the access control list on
+ * @return TRUE if successful
+ */
+BOOL
+SetUserAccessServiceDACL(SC_HANDLE hService)
+{
+ PACL pNewAcl = nullptr;
+ PSECURITY_DESCRIPTOR psd = nullptr;
+ DWORD lastError = SetUserAccessServiceDACL(hService, pNewAcl, psd);
+ if (pNewAcl)
+ {
+ LocalFree((HLOCAL)pNewAcl);
+ }
+ if (psd)
+ {
+ LocalFree((LPVOID)psd);
+ }
+ return ERROR_SUCCESS == lastError;
+}
+
+/**
+ * Sets the access control list for user access for the specified service.
+ *
+ * @param hService The service to set the access control list on
+ * @param pNewAcl The out param ACL which should be freed by caller
+ * @param psd out param security descriptor, should be freed by caller
+ * @return ERROR_SUCCESS if successful
+ */
+DWORD
+SetUserAccessServiceDACL(SC_HANDLE hService, PACL &pNewAcl,
+ PSECURITY_DESCRIPTOR psd)
+{
+ // Get the current security descriptor needed size
+ DWORD needed = 0;
+ if (!QueryServiceObjectSecurity(hService, DACL_SECURITY_INFORMATION,
+ &psd, 0, &needed))
+ {
+ if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
+ {
+ LOG_WARN(("Could not query service object security size. (%d)",
+ GetLastError()));
+ return GetLastError();
+ }
+
+ DWORD size = needed;
+ psd = (PSECURITY_DESCRIPTOR)LocalAlloc(LPTR, size);
+ if (!psd)
+ {
+ LOG_WARN(("Could not allocate security descriptor. (%d)",
+ GetLastError()));
+ return ERROR_INSUFFICIENT_BUFFER;
+ }
+
+ // Get the actual security descriptor now
+ if (!QueryServiceObjectSecurity(hService, DACL_SECURITY_INFORMATION,
+ psd, size, &needed))
+ {
+ LOG_WARN(("Could not allocate security descriptor. (%d)",
+ GetLastError()));
+ return GetLastError();
+ }
+ }
+
+ // Get the current DACL from the security descriptor.
+ PACL pacl = nullptr;
+ BOOL bDaclPresent = FALSE;
+ BOOL bDaclDefaulted = FALSE;
+ if ( !GetSecurityDescriptorDacl(psd, &bDaclPresent, &pacl,
+ &bDaclDefaulted))
+ {
+ LOG_WARN(("Could not obtain DACL. (%d)", GetLastError()));
+ return GetLastError();
+ }
+
+ PSID sid;
+ DWORD SIDSize = SECURITY_MAX_SID_SIZE;
+ sid = LocalAlloc(LMEM_FIXED, SIDSize);
+ if (!sid)
+ {
+ LOG_WARN(("Could not allocate SID memory. (%d)", GetLastError()));
+ return GetLastError();
+ }
+
+ if (!CreateWellKnownSid(WinBuiltinUsersSid, nullptr, sid, &SIDSize))
+ {
+ DWORD lastError = GetLastError();
+ LOG_WARN(("Could not create well known SID. (%d)", lastError));
+ LocalFree(sid);
+ return lastError;
+ }
+
+ // Lookup the account name, the function fails if you don't pass in
+ // a buffer for the domain name but it's not used since we're using
+ // the built in account Sid.
+ SID_NAME_USE accountType;
+ WCHAR accountName[UNLEN + 1] = { L'\0' };
+ WCHAR domainName[DNLEN + 1] = { L'\0' };
+ DWORD accountNameSize = UNLEN + 1;
+ DWORD domainNameSize = DNLEN + 1;
+ if (!LookupAccountSidW(nullptr, sid, accountName,
+ &accountNameSize,
+ domainName, &domainNameSize, &accountType))
+ {
+ LOG_WARN(("Could not lookup account Sid, will try Users. (%d)",
+ GetLastError()));
+ wcsncpy(accountName, L"Users", UNLEN);
+ }
+
+ // We already have the group name so we can get rid of the SID
+ FreeSid(sid);
+ sid = nullptr;
+
+ // Build the ACE, BuildExplicitAccessWithName cannot fail so it is not logged.
+ EXPLICIT_ACCESS ea;
+ BuildExplicitAccessWithNameW(&ea, accountName,
+ SERVICE_START | SERVICE_STOP | GENERIC_READ,
+ SET_ACCESS, NO_INHERITANCE);
+ DWORD lastError = SetEntriesInAclW(1, (PEXPLICIT_ACCESS)&ea, pacl, &pNewAcl);
+ if (ERROR_SUCCESS != lastError)
+ {
+ LOG_WARN(("Could not set entries in ACL. (%d)", lastError));
+ return lastError;
+ }
+
+ // Initialize a new security descriptor.
+ SECURITY_DESCRIPTOR sd;
+ if (!InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION))
+ {
+ LOG_WARN(("Could not initialize security descriptor. (%d)",
+ GetLastError()));
+ return GetLastError();
+ }
+
+ // Set the new DACL in the security descriptor.
+ if (!SetSecurityDescriptorDacl(&sd, TRUE, pNewAcl, FALSE))
+ {
+ LOG_WARN(("Could not set security descriptor DACL. (%d)",
+ GetLastError()));
+ return GetLastError();
+ }
+
+ // Set the new security descriptor for the service object.
+ if (!SetServiceObjectSecurity(hService, DACL_SECURITY_INFORMATION, &sd))
+ {
+ LOG_WARN(("Could not set object security. (%d)",
+ GetLastError()));
+ return GetLastError();
+ }
+
+ // Woohoo, raise the roof
+ LOG(("User access was set successfully on the service."));
+ return ERROR_SUCCESS;
+}