/* 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 #include #include #include // Used for DNLEN and UNLEN #include #include #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(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 serviceString; int result = ReadStrings(path, kServiceKeys, kNumStrings, &serviceString); if (result != OK) { results->serviceDescription = mozilla::MakeUnique(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 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(fileVersionInfo.get()); UINT size; if (!VerQueryValueW(fileVersionInfo.get(), L"\\", reinterpret_cast(&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 serviceDescription = mozilla::MakeUnique(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 serviceConfigBuffer(new char[bytesNeeded]); if (!QueryServiceConfigW( schService, reinterpret_cast(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(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(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(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; }