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