1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/toolkit/components/maintenanceservice/serviceinstall.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,733 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +#include <windows.h> 1.9 +#include <aclapi.h> 1.10 +#include <stdlib.h> 1.11 +#include <shlwapi.h> 1.12 + 1.13 +// Used for DNLEN and UNLEN 1.14 +#include <lm.h> 1.15 + 1.16 +#include <nsWindowsHelpers.h> 1.17 +#include "mozilla/Scoped.h" 1.18 + 1.19 +#include "serviceinstall.h" 1.20 +#include "servicebase.h" 1.21 +#include "updatehelper.h" 1.22 +#include "shellapi.h" 1.23 +#include "readstrings.h" 1.24 +#include "errors.h" 1.25 + 1.26 +#pragma comment(lib, "version.lib") 1.27 + 1.28 +/** 1.29 + * A wrapper function to read strings for the maintenance service. 1.30 + * 1.31 + * @param path The path of the ini file to read from 1.32 + * @param results The maintenance service strings that were read 1.33 + * @return OK on success 1.34 +*/ 1.35 +static int 1.36 +ReadMaintenanceServiceStrings(LPCWSTR path, 1.37 + MaintenanceServiceStringTable *results) 1.38 +{ 1.39 + // Read in the maintenance service description string if specified. 1.40 + const unsigned int kNumStrings = 1; 1.41 + const char *kServiceKeys = "MozillaMaintenanceDescription\0"; 1.42 + char serviceStrings[kNumStrings][MAX_TEXT_LEN]; 1.43 + int result = ReadStrings(path, kServiceKeys, 1.44 + kNumStrings, serviceStrings); 1.45 + if (result != OK) { 1.46 + serviceStrings[0][0] = '\0'; 1.47 + } 1.48 + strncpy(results->serviceDescription, 1.49 + serviceStrings[0], MAX_TEXT_LEN - 1); 1.50 + results->serviceDescription[MAX_TEXT_LEN - 1] = '\0'; 1.51 + return result; 1.52 +} 1.53 + 1.54 +/** 1.55 + * Obtains the version number from the specified PE file's version information 1.56 + * Version Format: A.B.C.D (Example 10.0.0.300) 1.57 + * 1.58 + * @param path The path of the file to check the version on 1.59 + * @param A The first part of the version number 1.60 + * @param B The second part of the version number 1.61 + * @param C The third part of the version number 1.62 + * @param D The fourth part of the version number 1.63 + * @return TRUE if successful 1.64 + */ 1.65 +static BOOL 1.66 +GetVersionNumberFromPath(LPWSTR path, DWORD &A, DWORD &B, 1.67 + DWORD &C, DWORD &D) 1.68 +{ 1.69 + DWORD fileVersionInfoSize = GetFileVersionInfoSizeW(path, 0); 1.70 + mozilla::ScopedDeleteArray<char> fileVersionInfo(new char[fileVersionInfoSize]); 1.71 + if (!GetFileVersionInfoW(path, 0, fileVersionInfoSize, 1.72 + fileVersionInfo.get())) { 1.73 + LOG_WARN(("Could not obtain file info of old service. (%d)", 1.74 + GetLastError())); 1.75 + return FALSE; 1.76 + } 1.77 + 1.78 + VS_FIXEDFILEINFO *fixedFileInfo = 1.79 + reinterpret_cast<VS_FIXEDFILEINFO *>(fileVersionInfo.get()); 1.80 + UINT size; 1.81 + if (!VerQueryValueW(fileVersionInfo.get(), L"\\", 1.82 + reinterpret_cast<LPVOID*>(&fixedFileInfo), &size)) { 1.83 + LOG_WARN(("Could not query file version info of old service. (%d)", 1.84 + GetLastError())); 1.85 + return FALSE; 1.86 + } 1.87 + 1.88 + A = HIWORD(fixedFileInfo->dwFileVersionMS); 1.89 + B = LOWORD(fixedFileInfo->dwFileVersionMS); 1.90 + C = HIWORD(fixedFileInfo->dwFileVersionLS); 1.91 + D = LOWORD(fixedFileInfo->dwFileVersionLS); 1.92 + return TRUE; 1.93 +} 1.94 + 1.95 +/** 1.96 + * Updates the service description with what is stored in updater.ini 1.97 + * at the same path as the currently executing module binary. 1.98 + * 1.99 + * @param serviceHandle A handle to an opened service with 1.100 + * SERVICE_CHANGE_CONFIG access right 1.101 + * @param TRUE on succcess. 1.102 +*/ 1.103 +BOOL 1.104 +UpdateServiceDescription(SC_HANDLE serviceHandle) 1.105 +{ 1.106 + WCHAR updaterINIPath[MAX_PATH + 1]; 1.107 + if (!GetModuleFileNameW(nullptr, updaterINIPath, 1.108 + sizeof(updaterINIPath) / 1.109 + sizeof(updaterINIPath[0]))) { 1.110 + LOG_WARN(("Could not obtain module filename when attempting to " 1.111 + "modify service description. (%d)", GetLastError())); 1.112 + return FALSE; 1.113 + } 1.114 + 1.115 + if (!PathRemoveFileSpecW(updaterINIPath)) { 1.116 + LOG_WARN(("Could not remove file spec when attempting to " 1.117 + "modify service description. (%d)", GetLastError())); 1.118 + return FALSE; 1.119 + } 1.120 + 1.121 + if (!PathAppendSafe(updaterINIPath, L"updater.ini")) { 1.122 + LOG_WARN(("Could not append updater.ini filename when attempting to " 1.123 + "modify service description. (%d)", GetLastError())); 1.124 + return FALSE; 1.125 + } 1.126 + 1.127 + if (GetFileAttributesW(updaterINIPath) == INVALID_FILE_ATTRIBUTES) { 1.128 + LOG_WARN(("updater.ini file does not exist, will not modify " 1.129 + "service description. (%d)", GetLastError())); 1.130 + return FALSE; 1.131 + } 1.132 + 1.133 + MaintenanceServiceStringTable serviceStrings; 1.134 + int rv = ReadMaintenanceServiceStrings(updaterINIPath, &serviceStrings); 1.135 + if (rv != OK || !strlen(serviceStrings.serviceDescription)) { 1.136 + LOG_WARN(("updater.ini file does not contain a maintenance " 1.137 + "service description.")); 1.138 + return FALSE; 1.139 + } 1.140 + 1.141 + WCHAR serviceDescription[MAX_TEXT_LEN]; 1.142 + if (!MultiByteToWideChar(CP_UTF8, 0, 1.143 + serviceStrings.serviceDescription, -1, 1.144 + serviceDescription, 1.145 + sizeof(serviceDescription) / 1.146 + sizeof(serviceDescription[0]))) { 1.147 + LOG_WARN(("Could not convert description to wide string format. (%d)", 1.148 + GetLastError())); 1.149 + return FALSE; 1.150 + } 1.151 + 1.152 + SERVICE_DESCRIPTIONW descriptionConfig; 1.153 + descriptionConfig.lpDescription = serviceDescription; 1.154 + if (!ChangeServiceConfig2W(serviceHandle, 1.155 + SERVICE_CONFIG_DESCRIPTION, 1.156 + &descriptionConfig)) { 1.157 + LOG_WARN(("Could not change service config. (%d)", GetLastError())); 1.158 + return FALSE; 1.159 + } 1.160 + 1.161 + LOG(("The service description was updated successfully.")); 1.162 + return TRUE; 1.163 +} 1.164 + 1.165 +/** 1.166 + * Determines if the MozillaMaintenance service path needs to be updated 1.167 + * and fixes it if it is wrong. 1.168 + * 1.169 + * @param service A handle to the service to fix. 1.170 + * @param currentServicePath The current (possibly wrong) path that is used. 1.171 + * @param servicePathWasWrong Out parameter set to TRUE if a fix was needed. 1.172 + * @return TRUE if the service path is now correct. 1.173 +*/ 1.174 +BOOL 1.175 +FixServicePath(SC_HANDLE service, 1.176 + LPCWSTR currentServicePath, 1.177 + BOOL &servicePathWasWrong) 1.178 +{ 1.179 + // When we originally upgraded the MozillaMaintenance service we 1.180 + // would uninstall the service on each upgrade. This had an 1.181 + // intermittent error which could cause the service to use the file 1.182 + // maintenanceservice_tmp.exe as the install path. Only a small number 1.183 + // of Nightly users would be affected by this, but we check for this 1.184 + // state here and fix the user if they are affected. 1.185 + // 1.186 + // We also fix the path in the case of the path not being quoted. 1.187 + size_t currentServicePathLen = wcslen(currentServicePath); 1.188 + bool doesServiceHaveCorrectPath = 1.189 + currentServicePathLen > 2 && 1.190 + !wcsstr(currentServicePath, L"maintenanceservice_tmp.exe") && 1.191 + currentServicePath[0] == L'\"' && 1.192 + currentServicePath[currentServicePathLen - 1] == L'\"'; 1.193 + 1.194 + if (doesServiceHaveCorrectPath) { 1.195 + LOG(("The MozillaMaintenance service path is correct.")); 1.196 + servicePathWasWrong = FALSE; 1.197 + return TRUE; 1.198 + } 1.199 + // This is a recoverable situation so not logging as a warning 1.200 + LOG(("The MozillaMaintenance path is NOT correct. It was: %ls", 1.201 + currentServicePath)); 1.202 + 1.203 + servicePathWasWrong = TRUE; 1.204 + WCHAR fixedPath[MAX_PATH + 1] = { L'\0' }; 1.205 + wcsncpy(fixedPath, currentServicePath, MAX_PATH); 1.206 + PathUnquoteSpacesW(fixedPath); 1.207 + if (!PathRemoveFileSpecW(fixedPath)) { 1.208 + LOG_WARN(("Couldn't remove file spec. (%d)", GetLastError())); 1.209 + return FALSE; 1.210 + } 1.211 + if (!PathAppendSafe(fixedPath, L"maintenanceservice.exe")) { 1.212 + LOG_WARN(("Couldn't append file spec. (%d)", GetLastError())); 1.213 + return FALSE; 1.214 + } 1.215 + PathQuoteSpacesW(fixedPath); 1.216 + 1.217 + 1.218 + if (!ChangeServiceConfigW(service, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, 1.219 + SERVICE_NO_CHANGE, fixedPath, nullptr, nullptr, 1.220 + nullptr, nullptr, nullptr, nullptr)) { 1.221 + LOG_WARN(("Could not fix service path. (%d)", GetLastError())); 1.222 + return FALSE; 1.223 + } 1.224 + 1.225 + LOG(("Fixed service path to: %ls.", fixedPath)); 1.226 + return TRUE; 1.227 +} 1.228 + 1.229 +/** 1.230 + * Installs or upgrades the SVC_NAME service. 1.231 + * If an existing service is already installed, we replace it with the 1.232 + * currently running process. 1.233 + * 1.234 + * @param action The action to perform. 1.235 + * @return TRUE if the service was installed/upgraded 1.236 + */ 1.237 +BOOL 1.238 +SvcInstall(SvcInstallAction action) 1.239 +{ 1.240 + // Get a handle to the local computer SCM database with full access rights. 1.241 + nsAutoServiceHandle schSCManager(OpenSCManager(nullptr, nullptr, 1.242 + SC_MANAGER_ALL_ACCESS)); 1.243 + if (!schSCManager) { 1.244 + LOG_WARN(("Could not open service manager. (%d)", GetLastError())); 1.245 + return FALSE; 1.246 + } 1.247 + 1.248 + WCHAR newServiceBinaryPath[MAX_PATH + 1]; 1.249 + if (!GetModuleFileNameW(nullptr, newServiceBinaryPath, 1.250 + sizeof(newServiceBinaryPath) / 1.251 + sizeof(newServiceBinaryPath[0]))) { 1.252 + LOG_WARN(("Could not obtain module filename when attempting to " 1.253 + "install service. (%d)", 1.254 + GetLastError())); 1.255 + return FALSE; 1.256 + } 1.257 + 1.258 + // Check if we already have the service installed. 1.259 + nsAutoServiceHandle schService(OpenServiceW(schSCManager, 1.260 + SVC_NAME, 1.261 + SERVICE_ALL_ACCESS)); 1.262 + DWORD lastError = GetLastError(); 1.263 + if (!schService && ERROR_SERVICE_DOES_NOT_EXIST != lastError) { 1.264 + // The service exists but we couldn't open it 1.265 + LOG_WARN(("Could not open service. (%d)", GetLastError())); 1.266 + return FALSE; 1.267 + } 1.268 + 1.269 + if (schService) { 1.270 + // The service exists but it may not have the correct permissions. 1.271 + // This could happen if the permissions were not set correctly originally 1.272 + // or have been changed after the installation. This will reset the 1.273 + // permissions back to allow limited user accounts. 1.274 + if (!SetUserAccessServiceDACL(schService)) { 1.275 + LOG_WARN(("Could not reset security ACE on service handle. It might not be " 1.276 + "possible to start the service. This error should never " 1.277 + "happen. (%d)", GetLastError())); 1.278 + } 1.279 + 1.280 + // The service exists and we opened it 1.281 + DWORD bytesNeeded; 1.282 + if (!QueryServiceConfigW(schService, nullptr, 0, &bytesNeeded) && 1.283 + GetLastError() != ERROR_INSUFFICIENT_BUFFER) { 1.284 + LOG_WARN(("Could not determine buffer size for query service config. (%d)", 1.285 + GetLastError())); 1.286 + return FALSE; 1.287 + } 1.288 + 1.289 + // Get the service config information, in particular we want the binary 1.290 + // path of the service. 1.291 + mozilla::ScopedDeleteArray<char> serviceConfigBuffer(new char[bytesNeeded]); 1.292 + if (!QueryServiceConfigW(schService, 1.293 + reinterpret_cast<QUERY_SERVICE_CONFIGW*>(serviceConfigBuffer.get()), 1.294 + bytesNeeded, &bytesNeeded)) { 1.295 + LOG_WARN(("Could open service but could not query service config. (%d)", 1.296 + GetLastError())); 1.297 + return FALSE; 1.298 + } 1.299 + QUERY_SERVICE_CONFIGW &serviceConfig = 1.300 + *reinterpret_cast<QUERY_SERVICE_CONFIGW*>(serviceConfigBuffer.get()); 1.301 + 1.302 + // Check if we need to fix the service path 1.303 + BOOL servicePathWasWrong; 1.304 + static BOOL alreadyCheckedFixServicePath = FALSE; 1.305 + if (!alreadyCheckedFixServicePath) { 1.306 + if (!FixServicePath(schService, serviceConfig.lpBinaryPathName, 1.307 + servicePathWasWrong)) { 1.308 + LOG_WARN(("Could not fix service path. This should never happen. (%d)", 1.309 + GetLastError())); 1.310 + // True is returned because the service is pointing to 1.311 + // maintenanceservice_tmp.exe so it actually was upgraded to the 1.312 + // newest installed service. 1.313 + return TRUE; 1.314 + } else if (servicePathWasWrong) { 1.315 + // Now that the path is fixed we should re-attempt the install. 1.316 + // This current process' image path is maintenanceservice_tmp.exe. 1.317 + // The service used to point to maintenanceservice_tmp.exe. 1.318 + // The service was just fixed to point to maintenanceservice.exe. 1.319 + // Re-attempting an install from scratch will work as normal. 1.320 + alreadyCheckedFixServicePath = TRUE; 1.321 + LOG(("Restarting install action: %d", action)); 1.322 + return SvcInstall(action); 1.323 + } 1.324 + } 1.325 + 1.326 + // Ensure the service path is not quoted. We own this memory and know it to 1.327 + // be large enough for the quoted path, so it is large enough for the 1.328 + // unquoted path. This function cannot fail. 1.329 + PathUnquoteSpacesW(serviceConfig.lpBinaryPathName); 1.330 + 1.331 + // Obtain the existing maintenanceservice file's version number and 1.332 + // the new file's version number. Versions are in the format of 1.333 + // A.B.C.D. 1.334 + DWORD existingA, existingB, existingC, existingD; 1.335 + DWORD newA, newB, newC, newD; 1.336 + BOOL obtainedExistingVersionInfo = 1.337 + GetVersionNumberFromPath(serviceConfig.lpBinaryPathName, 1.338 + existingA, existingB, 1.339 + existingC, existingD); 1.340 + if (!GetVersionNumberFromPath(newServiceBinaryPath, newA, 1.341 + newB, newC, newD)) { 1.342 + LOG_WARN(("Could not obtain version number from new path")); 1.343 + return FALSE; 1.344 + } 1.345 + 1.346 + // Check if we need to replace the old binary with the new one 1.347 + // If we couldn't get the old version info then we assume we should 1.348 + // replace it. 1.349 + if (ForceInstallSvc == action || 1.350 + !obtainedExistingVersionInfo || 1.351 + (existingA < newA) || 1.352 + (existingA == newA && existingB < newB) || 1.353 + (existingA == newA && existingB == newB && 1.354 + existingC < newC) || 1.355 + (existingA == newA && existingB == newB && 1.356 + existingC == newC && existingD < newD)) { 1.357 + 1.358 + // We have a newer updater, so update the description from the INI file. 1.359 + UpdateServiceDescription(schService); 1.360 + 1.361 + schService.reset(); 1.362 + if (!StopService()) { 1.363 + return FALSE; 1.364 + } 1.365 + 1.366 + if (!wcscmp(newServiceBinaryPath, serviceConfig.lpBinaryPathName)) { 1.367 + LOG(("File is already in the correct location, no action needed for " 1.368 + "upgrade. The path is: \"%ls\"", newServiceBinaryPath)); 1.369 + return TRUE; 1.370 + } 1.371 + 1.372 + BOOL result = TRUE; 1.373 + 1.374 + // Attempt to copy the new binary over top the existing binary. 1.375 + // If there is an error we try to move it out of the way and then 1.376 + // copy it in. First try the safest / easiest way to overwrite the file. 1.377 + if (!CopyFileW(newServiceBinaryPath, 1.378 + serviceConfig.lpBinaryPathName, FALSE)) { 1.379 + LOG_WARN(("Could not overwrite old service binary file. " 1.380 + "This should never happen, but if it does the next " 1.381 + "upgrade will fix it, the service is not a critical " 1.382 + "component that needs to be installed for upgrades " 1.383 + "to work. (%d)", GetLastError())); 1.384 + 1.385 + // We rename the last 3 filename chars in an unsafe way. Manually 1.386 + // verify there are more than 3 chars for safe failure in MoveFileExW. 1.387 + const size_t len = wcslen(serviceConfig.lpBinaryPathName); 1.388 + if (len > 3) { 1.389 + // Calculate the temp file path that we're moving the file to. This 1.390 + // is the same as the proper service path but with a .old extension. 1.391 + LPWSTR oldServiceBinaryTempPath = 1.392 + new WCHAR[len + 1]; 1.393 + memset(oldServiceBinaryTempPath, 0, (len + 1) * sizeof (WCHAR)); 1.394 + wcsncpy(oldServiceBinaryTempPath, serviceConfig.lpBinaryPathName, len); 1.395 + // Rename the last 3 chars to 'old' 1.396 + wcsncpy(oldServiceBinaryTempPath + len - 3, L"old", 3); 1.397 + 1.398 + // Move the current (old) service file to the temp path. 1.399 + if (MoveFileExW(serviceConfig.lpBinaryPathName, 1.400 + oldServiceBinaryTempPath, 1.401 + MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH)) { 1.402 + // The old binary is moved out of the way, copy in the new one. 1.403 + if (!CopyFileW(newServiceBinaryPath, 1.404 + serviceConfig.lpBinaryPathName, FALSE)) { 1.405 + // It is best to leave the old service binary in this condition. 1.406 + LOG_WARN(("The new service binary could not be copied in." 1.407 + " The service will not be upgraded.")); 1.408 + result = FALSE; 1.409 + } else { 1.410 + LOG(("The new service binary was copied in by first moving the" 1.411 + " old one out of the way.")); 1.412 + } 1.413 + 1.414 + // Attempt to get rid of the old service temp path. 1.415 + if (DeleteFileW(oldServiceBinaryTempPath)) { 1.416 + LOG(("The old temp service path was deleted: %ls.", 1.417 + oldServiceBinaryTempPath)); 1.418 + } else { 1.419 + // The old temp path could not be removed. It will be removed 1.420 + // the next time the user can't copy the binary in or on uninstall. 1.421 + LOG_WARN(("The old temp service path was not deleted.")); 1.422 + } 1.423 + } else { 1.424 + // It is best to leave the old service binary in this condition. 1.425 + LOG_WARN(("Could not move old service file out of the way from:" 1.426 + " \"%ls\" to \"%ls\". Service will not be upgraded. (%d)", 1.427 + serviceConfig.lpBinaryPathName, 1.428 + oldServiceBinaryTempPath, GetLastError())); 1.429 + result = FALSE; 1.430 + } 1.431 + delete[] oldServiceBinaryTempPath; 1.432 + } else { 1.433 + // It is best to leave the old service binary in this condition. 1.434 + LOG_WARN(("Service binary path was less than 3, service will" 1.435 + " not be updated. This should never happen.")); 1.436 + result = FALSE; 1.437 + } 1.438 + } else { 1.439 + LOG(("The new service binary was copied in.")); 1.440 + } 1.441 + 1.442 + // We made a copy of ourselves to the existing location. 1.443 + // The tmp file (the process of which we are executing right now) will be 1.444 + // left over. Attempt to delete the file on the next reboot. 1.445 + if (MoveFileExW(newServiceBinaryPath, nullptr, 1.446 + MOVEFILE_DELAY_UNTIL_REBOOT)) { 1.447 + LOG(("Deleting the old file path on the next reboot: %ls.", 1.448 + newServiceBinaryPath)); 1.449 + } else { 1.450 + LOG_WARN(("Call to delete the old file path failed: %ls.", 1.451 + newServiceBinaryPath)); 1.452 + } 1.453 + 1.454 + return result; 1.455 + } 1.456 + 1.457 + // We don't need to copy ourselves to the existing location. 1.458 + // The tmp file (the process of which we are executing right now) will be 1.459 + // left over. Attempt to delete the file on the next reboot. 1.460 + MoveFileExW(newServiceBinaryPath, nullptr, MOVEFILE_DELAY_UNTIL_REBOOT); 1.461 + 1.462 + // nothing to do, we already have a newer service installed 1.463 + return TRUE; 1.464 + } 1.465 + 1.466 + // If the service does not exist and we are upgrading, don't install it. 1.467 + if (UpgradeSvc == action) { 1.468 + // The service does not exist and we are upgrading, so don't install it 1.469 + return TRUE; 1.470 + } 1.471 + 1.472 + // Quote the path only if it contains spaces. 1.473 + PathQuoteSpacesW(newServiceBinaryPath); 1.474 + // The service does not already exist so create the service as on demand 1.475 + schService.own(CreateServiceW(schSCManager, SVC_NAME, SVC_DISPLAY_NAME, 1.476 + SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, 1.477 + SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL, 1.478 + newServiceBinaryPath, nullptr, nullptr, 1.479 + nullptr, nullptr, nullptr)); 1.480 + if (!schService) { 1.481 + LOG_WARN(("Could not create Windows service. " 1.482 + "This error should never happen since a service install " 1.483 + "should only be called when elevated. (%d)", GetLastError())); 1.484 + return FALSE; 1.485 + } 1.486 + 1.487 + if (!SetUserAccessServiceDACL(schService)) { 1.488 + LOG_WARN(("Could not set security ACE on service handle, the service will not " 1.489 + "be able to be started from unelevated processes. " 1.490 + "This error should never happen. (%d)", 1.491 + GetLastError())); 1.492 + } 1.493 + 1.494 + UpdateServiceDescription(schService); 1.495 + 1.496 + return TRUE; 1.497 +} 1.498 + 1.499 +/** 1.500 + * Stops the Maintenance service. 1.501 + * 1.502 + * @return TRUE if successful. 1.503 + */ 1.504 +BOOL 1.505 +StopService() 1.506 +{ 1.507 + // Get a handle to the local computer SCM database with full access rights. 1.508 + nsAutoServiceHandle schSCManager(OpenSCManager(nullptr, nullptr, 1.509 + SC_MANAGER_ALL_ACCESS)); 1.510 + if (!schSCManager) { 1.511 + LOG_WARN(("Could not open service manager. (%d)", GetLastError())); 1.512 + return FALSE; 1.513 + } 1.514 + 1.515 + // Open the service 1.516 + nsAutoServiceHandle schService(OpenServiceW(schSCManager, SVC_NAME, 1.517 + SERVICE_ALL_ACCESS)); 1.518 + if (!schService) { 1.519 + LOG_WARN(("Could not open service. (%d)", GetLastError())); 1.520 + return FALSE; 1.521 + } 1.522 + 1.523 + LOG(("Sending stop request...")); 1.524 + SERVICE_STATUS status; 1.525 + SetLastError(ERROR_SUCCESS); 1.526 + if (!ControlService(schService, SERVICE_CONTROL_STOP, &status) && 1.527 + GetLastError() != ERROR_SERVICE_NOT_ACTIVE) { 1.528 + LOG_WARN(("Error sending stop request. (%d)", GetLastError())); 1.529 + } 1.530 + 1.531 + schSCManager.reset(); 1.532 + schService.reset(); 1.533 + 1.534 + LOG(("Waiting for service stop...")); 1.535 + DWORD lastState = WaitForServiceStop(SVC_NAME, 30); 1.536 + 1.537 + // The service can be in a stopped state but the exe still in use 1.538 + // so make sure the process is really gone before proceeding 1.539 + WaitForProcessExit(L"maintenanceservice.exe", 30); 1.540 + LOG(("Done waiting for service stop, last service state: %d", lastState)); 1.541 + 1.542 + return lastState == SERVICE_STOPPED; 1.543 +} 1.544 + 1.545 +/** 1.546 + * Uninstalls the Maintenance service. 1.547 + * 1.548 + * @return TRUE if successful. 1.549 + */ 1.550 +BOOL 1.551 +SvcUninstall() 1.552 +{ 1.553 + // Get a handle to the local computer SCM database with full access rights. 1.554 + nsAutoServiceHandle schSCManager(OpenSCManager(nullptr, nullptr, 1.555 + SC_MANAGER_ALL_ACCESS)); 1.556 + if (!schSCManager) { 1.557 + LOG_WARN(("Could not open service manager. (%d)", GetLastError())); 1.558 + return FALSE; 1.559 + } 1.560 + 1.561 + // Open the service 1.562 + nsAutoServiceHandle schService(OpenServiceW(schSCManager, SVC_NAME, 1.563 + SERVICE_ALL_ACCESS)); 1.564 + if (!schService) { 1.565 + LOG_WARN(("Could not open service. (%d)", GetLastError())); 1.566 + return FALSE; 1.567 + } 1.568 + 1.569 + //Stop the service so it deletes faster and so the uninstaller 1.570 + // can actually delete its EXE. 1.571 + DWORD totalWaitTime = 0; 1.572 + SERVICE_STATUS status; 1.573 + static const int maxWaitTime = 1000 * 60; // Never wait more than a minute 1.574 + if (ControlService(schService, SERVICE_CONTROL_STOP, &status)) { 1.575 + do { 1.576 + Sleep(status.dwWaitHint); 1.577 + totalWaitTime += (status.dwWaitHint + 10); 1.578 + if (status.dwCurrentState == SERVICE_STOPPED) { 1.579 + break; 1.580 + } else if (totalWaitTime > maxWaitTime) { 1.581 + break; 1.582 + } 1.583 + } while (QueryServiceStatus(schService, &status)); 1.584 + } 1.585 + 1.586 + // Delete the service or mark it for deletion 1.587 + BOOL deleted = DeleteService(schService); 1.588 + if (!deleted) { 1.589 + deleted = (GetLastError() == ERROR_SERVICE_MARKED_FOR_DELETE); 1.590 + } 1.591 + 1.592 + return deleted; 1.593 +} 1.594 + 1.595 +/** 1.596 + * Sets the access control list for user access for the specified service. 1.597 + * 1.598 + * @param hService The service to set the access control list on 1.599 + * @return TRUE if successful 1.600 + */ 1.601 +BOOL 1.602 +SetUserAccessServiceDACL(SC_HANDLE hService) 1.603 +{ 1.604 + PACL pNewAcl = nullptr; 1.605 + PSECURITY_DESCRIPTOR psd = nullptr; 1.606 + DWORD lastError = SetUserAccessServiceDACL(hService, pNewAcl, psd); 1.607 + if (pNewAcl) { 1.608 + LocalFree((HLOCAL)pNewAcl); 1.609 + } 1.610 + if (psd) { 1.611 + LocalFree((LPVOID)psd); 1.612 + } 1.613 + return ERROR_SUCCESS == lastError; 1.614 +} 1.615 + 1.616 +/** 1.617 + * Sets the access control list for user access for the specified service. 1.618 + * 1.619 + * @param hService The service to set the access control list on 1.620 + * @param pNewAcl The out param ACL which should be freed by caller 1.621 + * @param psd out param security descriptor, should be freed by caller 1.622 + * @return ERROR_SUCCESS if successful 1.623 + */ 1.624 +DWORD 1.625 +SetUserAccessServiceDACL(SC_HANDLE hService, PACL &pNewAcl, 1.626 + PSECURITY_DESCRIPTOR psd) 1.627 +{ 1.628 + // Get the current security descriptor needed size 1.629 + DWORD needed = 0; 1.630 + if (!QueryServiceObjectSecurity(hService, DACL_SECURITY_INFORMATION, 1.631 + &psd, 0, &needed)) { 1.632 + if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) { 1.633 + LOG_WARN(("Could not query service object security size. (%d)", 1.634 + GetLastError())); 1.635 + return GetLastError(); 1.636 + } 1.637 + 1.638 + DWORD size = needed; 1.639 + psd = (PSECURITY_DESCRIPTOR)LocalAlloc(LPTR, size); 1.640 + if (!psd) { 1.641 + LOG_WARN(("Could not allocate security descriptor. (%d)", 1.642 + GetLastError())); 1.643 + return ERROR_INSUFFICIENT_BUFFER; 1.644 + } 1.645 + 1.646 + // Get the actual security descriptor now 1.647 + if (!QueryServiceObjectSecurity(hService, DACL_SECURITY_INFORMATION, 1.648 + psd, size, &needed)) { 1.649 + LOG_WARN(("Could not allocate security descriptor. (%d)", 1.650 + GetLastError())); 1.651 + return GetLastError(); 1.652 + } 1.653 + } 1.654 + 1.655 + // Get the current DACL from the security descriptor. 1.656 + PACL pacl = nullptr; 1.657 + BOOL bDaclPresent = FALSE; 1.658 + BOOL bDaclDefaulted = FALSE; 1.659 + if ( !GetSecurityDescriptorDacl(psd, &bDaclPresent, &pacl, 1.660 + &bDaclDefaulted)) { 1.661 + LOG_WARN(("Could not obtain DACL. (%d)", GetLastError())); 1.662 + return GetLastError(); 1.663 + } 1.664 + 1.665 + PSID sid; 1.666 + DWORD SIDSize = SECURITY_MAX_SID_SIZE; 1.667 + sid = LocalAlloc(LMEM_FIXED, SIDSize); 1.668 + if (!sid) { 1.669 + LOG_WARN(("Could not allocate SID memory. (%d)", GetLastError())); 1.670 + return GetLastError(); 1.671 + } 1.672 + 1.673 + if (!CreateWellKnownSid(WinBuiltinUsersSid, nullptr, sid, &SIDSize)) { 1.674 + DWORD lastError = GetLastError(); 1.675 + LOG_WARN(("Could not create well known SID. (%d)", lastError)); 1.676 + LocalFree(sid); 1.677 + return lastError; 1.678 + } 1.679 + 1.680 + // Lookup the account name, the function fails if you don't pass in 1.681 + // a buffer for the domain name but it's not used since we're using 1.682 + // the built in account Sid. 1.683 + SID_NAME_USE accountType; 1.684 + WCHAR accountName[UNLEN + 1] = { L'\0' }; 1.685 + WCHAR domainName[DNLEN + 1] = { L'\0' }; 1.686 + DWORD accountNameSize = UNLEN + 1; 1.687 + DWORD domainNameSize = DNLEN + 1; 1.688 + if (!LookupAccountSidW(nullptr, sid, accountName, 1.689 + &accountNameSize, 1.690 + domainName, &domainNameSize, &accountType)) { 1.691 + LOG_WARN(("Could not lookup account Sid, will try Users. (%d)", 1.692 + GetLastError())); 1.693 + wcsncpy(accountName, L"Users", UNLEN); 1.694 + } 1.695 + 1.696 + // We already have the group name so we can get rid of the SID 1.697 + FreeSid(sid); 1.698 + sid = nullptr; 1.699 + 1.700 + // Build the ACE, BuildExplicitAccessWithName cannot fail so it is not logged. 1.701 + EXPLICIT_ACCESS ea; 1.702 + BuildExplicitAccessWithNameW(&ea, accountName, 1.703 + SERVICE_START | SERVICE_STOP | GENERIC_READ, 1.704 + SET_ACCESS, NO_INHERITANCE); 1.705 + DWORD lastError = SetEntriesInAclW(1, (PEXPLICIT_ACCESS)&ea, pacl, &pNewAcl); 1.706 + if (ERROR_SUCCESS != lastError) { 1.707 + LOG_WARN(("Could not set entries in ACL. (%d)", lastError)); 1.708 + return lastError; 1.709 + } 1.710 + 1.711 + // Initialize a new security descriptor. 1.712 + SECURITY_DESCRIPTOR sd; 1.713 + if (!InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION)) { 1.714 + LOG_WARN(("Could not initialize security descriptor. (%d)", 1.715 + GetLastError())); 1.716 + return GetLastError(); 1.717 + } 1.718 + 1.719 + // Set the new DACL in the security descriptor. 1.720 + if (!SetSecurityDescriptorDacl(&sd, TRUE, pNewAcl, FALSE)) { 1.721 + LOG_WARN(("Could not set security descriptor DACL. (%d)", 1.722 + GetLastError())); 1.723 + return GetLastError(); 1.724 + } 1.725 + 1.726 + // Set the new security descriptor for the service object. 1.727 + if (!SetServiceObjectSecurity(hService, DACL_SECURITY_INFORMATION, &sd)) { 1.728 + LOG_WARN(("Could not set object security. (%d)", 1.729 + GetLastError())); 1.730 + return GetLastError(); 1.731 + } 1.732 + 1.733 + // Woohoo, raise the roof 1.734 + LOG(("User access was set successfully on the service.")); 1.735 + return ERROR_SUCCESS; 1.736 +}