Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
michael@0 | 1 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 4 | |
michael@0 | 5 | #include <windows.h> |
michael@0 | 6 | #include <aclapi.h> |
michael@0 | 7 | #include <stdlib.h> |
michael@0 | 8 | #include <shlwapi.h> |
michael@0 | 9 | |
michael@0 | 10 | // Used for DNLEN and UNLEN |
michael@0 | 11 | #include <lm.h> |
michael@0 | 12 | |
michael@0 | 13 | #include <nsWindowsHelpers.h> |
michael@0 | 14 | #include "mozilla/Scoped.h" |
michael@0 | 15 | |
michael@0 | 16 | #include "serviceinstall.h" |
michael@0 | 17 | #include "servicebase.h" |
michael@0 | 18 | #include "updatehelper.h" |
michael@0 | 19 | #include "shellapi.h" |
michael@0 | 20 | #include "readstrings.h" |
michael@0 | 21 | #include "errors.h" |
michael@0 | 22 | |
michael@0 | 23 | #pragma comment(lib, "version.lib") |
michael@0 | 24 | |
michael@0 | 25 | /** |
michael@0 | 26 | * A wrapper function to read strings for the maintenance service. |
michael@0 | 27 | * |
michael@0 | 28 | * @param path The path of the ini file to read from |
michael@0 | 29 | * @param results The maintenance service strings that were read |
michael@0 | 30 | * @return OK on success |
michael@0 | 31 | */ |
michael@0 | 32 | static int |
michael@0 | 33 | ReadMaintenanceServiceStrings(LPCWSTR path, |
michael@0 | 34 | MaintenanceServiceStringTable *results) |
michael@0 | 35 | { |
michael@0 | 36 | // Read in the maintenance service description string if specified. |
michael@0 | 37 | const unsigned int kNumStrings = 1; |
michael@0 | 38 | const char *kServiceKeys = "MozillaMaintenanceDescription\0"; |
michael@0 | 39 | char serviceStrings[kNumStrings][MAX_TEXT_LEN]; |
michael@0 | 40 | int result = ReadStrings(path, kServiceKeys, |
michael@0 | 41 | kNumStrings, serviceStrings); |
michael@0 | 42 | if (result != OK) { |
michael@0 | 43 | serviceStrings[0][0] = '\0'; |
michael@0 | 44 | } |
michael@0 | 45 | strncpy(results->serviceDescription, |
michael@0 | 46 | serviceStrings[0], MAX_TEXT_LEN - 1); |
michael@0 | 47 | results->serviceDescription[MAX_TEXT_LEN - 1] = '\0'; |
michael@0 | 48 | return result; |
michael@0 | 49 | } |
michael@0 | 50 | |
michael@0 | 51 | /** |
michael@0 | 52 | * Obtains the version number from the specified PE file's version information |
michael@0 | 53 | * Version Format: A.B.C.D (Example 10.0.0.300) |
michael@0 | 54 | * |
michael@0 | 55 | * @param path The path of the file to check the version on |
michael@0 | 56 | * @param A The first part of the version number |
michael@0 | 57 | * @param B The second part of the version number |
michael@0 | 58 | * @param C The third part of the version number |
michael@0 | 59 | * @param D The fourth part of the version number |
michael@0 | 60 | * @return TRUE if successful |
michael@0 | 61 | */ |
michael@0 | 62 | static BOOL |
michael@0 | 63 | GetVersionNumberFromPath(LPWSTR path, DWORD &A, DWORD &B, |
michael@0 | 64 | DWORD &C, DWORD &D) |
michael@0 | 65 | { |
michael@0 | 66 | DWORD fileVersionInfoSize = GetFileVersionInfoSizeW(path, 0); |
michael@0 | 67 | mozilla::ScopedDeleteArray<char> fileVersionInfo(new char[fileVersionInfoSize]); |
michael@0 | 68 | if (!GetFileVersionInfoW(path, 0, fileVersionInfoSize, |
michael@0 | 69 | fileVersionInfo.get())) { |
michael@0 | 70 | LOG_WARN(("Could not obtain file info of old service. (%d)", |
michael@0 | 71 | GetLastError())); |
michael@0 | 72 | return FALSE; |
michael@0 | 73 | } |
michael@0 | 74 | |
michael@0 | 75 | VS_FIXEDFILEINFO *fixedFileInfo = |
michael@0 | 76 | reinterpret_cast<VS_FIXEDFILEINFO *>(fileVersionInfo.get()); |
michael@0 | 77 | UINT size; |
michael@0 | 78 | if (!VerQueryValueW(fileVersionInfo.get(), L"\\", |
michael@0 | 79 | reinterpret_cast<LPVOID*>(&fixedFileInfo), &size)) { |
michael@0 | 80 | LOG_WARN(("Could not query file version info of old service. (%d)", |
michael@0 | 81 | GetLastError())); |
michael@0 | 82 | return FALSE; |
michael@0 | 83 | } |
michael@0 | 84 | |
michael@0 | 85 | A = HIWORD(fixedFileInfo->dwFileVersionMS); |
michael@0 | 86 | B = LOWORD(fixedFileInfo->dwFileVersionMS); |
michael@0 | 87 | C = HIWORD(fixedFileInfo->dwFileVersionLS); |
michael@0 | 88 | D = LOWORD(fixedFileInfo->dwFileVersionLS); |
michael@0 | 89 | return TRUE; |
michael@0 | 90 | } |
michael@0 | 91 | |
michael@0 | 92 | /** |
michael@0 | 93 | * Updates the service description with what is stored in updater.ini |
michael@0 | 94 | * at the same path as the currently executing module binary. |
michael@0 | 95 | * |
michael@0 | 96 | * @param serviceHandle A handle to an opened service with |
michael@0 | 97 | * SERVICE_CHANGE_CONFIG access right |
michael@0 | 98 | * @param TRUE on succcess. |
michael@0 | 99 | */ |
michael@0 | 100 | BOOL |
michael@0 | 101 | UpdateServiceDescription(SC_HANDLE serviceHandle) |
michael@0 | 102 | { |
michael@0 | 103 | WCHAR updaterINIPath[MAX_PATH + 1]; |
michael@0 | 104 | if (!GetModuleFileNameW(nullptr, updaterINIPath, |
michael@0 | 105 | sizeof(updaterINIPath) / |
michael@0 | 106 | sizeof(updaterINIPath[0]))) { |
michael@0 | 107 | LOG_WARN(("Could not obtain module filename when attempting to " |
michael@0 | 108 | "modify service description. (%d)", GetLastError())); |
michael@0 | 109 | return FALSE; |
michael@0 | 110 | } |
michael@0 | 111 | |
michael@0 | 112 | if (!PathRemoveFileSpecW(updaterINIPath)) { |
michael@0 | 113 | LOG_WARN(("Could not remove file spec when attempting to " |
michael@0 | 114 | "modify service description. (%d)", GetLastError())); |
michael@0 | 115 | return FALSE; |
michael@0 | 116 | } |
michael@0 | 117 | |
michael@0 | 118 | if (!PathAppendSafe(updaterINIPath, L"updater.ini")) { |
michael@0 | 119 | LOG_WARN(("Could not append updater.ini filename when attempting to " |
michael@0 | 120 | "modify service description. (%d)", GetLastError())); |
michael@0 | 121 | return FALSE; |
michael@0 | 122 | } |
michael@0 | 123 | |
michael@0 | 124 | if (GetFileAttributesW(updaterINIPath) == INVALID_FILE_ATTRIBUTES) { |
michael@0 | 125 | LOG_WARN(("updater.ini file does not exist, will not modify " |
michael@0 | 126 | "service description. (%d)", GetLastError())); |
michael@0 | 127 | return FALSE; |
michael@0 | 128 | } |
michael@0 | 129 | |
michael@0 | 130 | MaintenanceServiceStringTable serviceStrings; |
michael@0 | 131 | int rv = ReadMaintenanceServiceStrings(updaterINIPath, &serviceStrings); |
michael@0 | 132 | if (rv != OK || !strlen(serviceStrings.serviceDescription)) { |
michael@0 | 133 | LOG_WARN(("updater.ini file does not contain a maintenance " |
michael@0 | 134 | "service description.")); |
michael@0 | 135 | return FALSE; |
michael@0 | 136 | } |
michael@0 | 137 | |
michael@0 | 138 | WCHAR serviceDescription[MAX_TEXT_LEN]; |
michael@0 | 139 | if (!MultiByteToWideChar(CP_UTF8, 0, |
michael@0 | 140 | serviceStrings.serviceDescription, -1, |
michael@0 | 141 | serviceDescription, |
michael@0 | 142 | sizeof(serviceDescription) / |
michael@0 | 143 | sizeof(serviceDescription[0]))) { |
michael@0 | 144 | LOG_WARN(("Could not convert description to wide string format. (%d)", |
michael@0 | 145 | GetLastError())); |
michael@0 | 146 | return FALSE; |
michael@0 | 147 | } |
michael@0 | 148 | |
michael@0 | 149 | SERVICE_DESCRIPTIONW descriptionConfig; |
michael@0 | 150 | descriptionConfig.lpDescription = serviceDescription; |
michael@0 | 151 | if (!ChangeServiceConfig2W(serviceHandle, |
michael@0 | 152 | SERVICE_CONFIG_DESCRIPTION, |
michael@0 | 153 | &descriptionConfig)) { |
michael@0 | 154 | LOG_WARN(("Could not change service config. (%d)", GetLastError())); |
michael@0 | 155 | return FALSE; |
michael@0 | 156 | } |
michael@0 | 157 | |
michael@0 | 158 | LOG(("The service description was updated successfully.")); |
michael@0 | 159 | return TRUE; |
michael@0 | 160 | } |
michael@0 | 161 | |
michael@0 | 162 | /** |
michael@0 | 163 | * Determines if the MozillaMaintenance service path needs to be updated |
michael@0 | 164 | * and fixes it if it is wrong. |
michael@0 | 165 | * |
michael@0 | 166 | * @param service A handle to the service to fix. |
michael@0 | 167 | * @param currentServicePath The current (possibly wrong) path that is used. |
michael@0 | 168 | * @param servicePathWasWrong Out parameter set to TRUE if a fix was needed. |
michael@0 | 169 | * @return TRUE if the service path is now correct. |
michael@0 | 170 | */ |
michael@0 | 171 | BOOL |
michael@0 | 172 | FixServicePath(SC_HANDLE service, |
michael@0 | 173 | LPCWSTR currentServicePath, |
michael@0 | 174 | BOOL &servicePathWasWrong) |
michael@0 | 175 | { |
michael@0 | 176 | // When we originally upgraded the MozillaMaintenance service we |
michael@0 | 177 | // would uninstall the service on each upgrade. This had an |
michael@0 | 178 | // intermittent error which could cause the service to use the file |
michael@0 | 179 | // maintenanceservice_tmp.exe as the install path. Only a small number |
michael@0 | 180 | // of Nightly users would be affected by this, but we check for this |
michael@0 | 181 | // state here and fix the user if they are affected. |
michael@0 | 182 | // |
michael@0 | 183 | // We also fix the path in the case of the path not being quoted. |
michael@0 | 184 | size_t currentServicePathLen = wcslen(currentServicePath); |
michael@0 | 185 | bool doesServiceHaveCorrectPath = |
michael@0 | 186 | currentServicePathLen > 2 && |
michael@0 | 187 | !wcsstr(currentServicePath, L"maintenanceservice_tmp.exe") && |
michael@0 | 188 | currentServicePath[0] == L'\"' && |
michael@0 | 189 | currentServicePath[currentServicePathLen - 1] == L'\"'; |
michael@0 | 190 | |
michael@0 | 191 | if (doesServiceHaveCorrectPath) { |
michael@0 | 192 | LOG(("The MozillaMaintenance service path is correct.")); |
michael@0 | 193 | servicePathWasWrong = FALSE; |
michael@0 | 194 | return TRUE; |
michael@0 | 195 | } |
michael@0 | 196 | // This is a recoverable situation so not logging as a warning |
michael@0 | 197 | LOG(("The MozillaMaintenance path is NOT correct. It was: %ls", |
michael@0 | 198 | currentServicePath)); |
michael@0 | 199 | |
michael@0 | 200 | servicePathWasWrong = TRUE; |
michael@0 | 201 | WCHAR fixedPath[MAX_PATH + 1] = { L'\0' }; |
michael@0 | 202 | wcsncpy(fixedPath, currentServicePath, MAX_PATH); |
michael@0 | 203 | PathUnquoteSpacesW(fixedPath); |
michael@0 | 204 | if (!PathRemoveFileSpecW(fixedPath)) { |
michael@0 | 205 | LOG_WARN(("Couldn't remove file spec. (%d)", GetLastError())); |
michael@0 | 206 | return FALSE; |
michael@0 | 207 | } |
michael@0 | 208 | if (!PathAppendSafe(fixedPath, L"maintenanceservice.exe")) { |
michael@0 | 209 | LOG_WARN(("Couldn't append file spec. (%d)", GetLastError())); |
michael@0 | 210 | return FALSE; |
michael@0 | 211 | } |
michael@0 | 212 | PathQuoteSpacesW(fixedPath); |
michael@0 | 213 | |
michael@0 | 214 | |
michael@0 | 215 | if (!ChangeServiceConfigW(service, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, |
michael@0 | 216 | SERVICE_NO_CHANGE, fixedPath, nullptr, nullptr, |
michael@0 | 217 | nullptr, nullptr, nullptr, nullptr)) { |
michael@0 | 218 | LOG_WARN(("Could not fix service path. (%d)", GetLastError())); |
michael@0 | 219 | return FALSE; |
michael@0 | 220 | } |
michael@0 | 221 | |
michael@0 | 222 | LOG(("Fixed service path to: %ls.", fixedPath)); |
michael@0 | 223 | return TRUE; |
michael@0 | 224 | } |
michael@0 | 225 | |
michael@0 | 226 | /** |
michael@0 | 227 | * Installs or upgrades the SVC_NAME service. |
michael@0 | 228 | * If an existing service is already installed, we replace it with the |
michael@0 | 229 | * currently running process. |
michael@0 | 230 | * |
michael@0 | 231 | * @param action The action to perform. |
michael@0 | 232 | * @return TRUE if the service was installed/upgraded |
michael@0 | 233 | */ |
michael@0 | 234 | BOOL |
michael@0 | 235 | SvcInstall(SvcInstallAction action) |
michael@0 | 236 | { |
michael@0 | 237 | // Get a handle to the local computer SCM database with full access rights. |
michael@0 | 238 | nsAutoServiceHandle schSCManager(OpenSCManager(nullptr, nullptr, |
michael@0 | 239 | SC_MANAGER_ALL_ACCESS)); |
michael@0 | 240 | if (!schSCManager) { |
michael@0 | 241 | LOG_WARN(("Could not open service manager. (%d)", GetLastError())); |
michael@0 | 242 | return FALSE; |
michael@0 | 243 | } |
michael@0 | 244 | |
michael@0 | 245 | WCHAR newServiceBinaryPath[MAX_PATH + 1]; |
michael@0 | 246 | if (!GetModuleFileNameW(nullptr, newServiceBinaryPath, |
michael@0 | 247 | sizeof(newServiceBinaryPath) / |
michael@0 | 248 | sizeof(newServiceBinaryPath[0]))) { |
michael@0 | 249 | LOG_WARN(("Could not obtain module filename when attempting to " |
michael@0 | 250 | "install service. (%d)", |
michael@0 | 251 | GetLastError())); |
michael@0 | 252 | return FALSE; |
michael@0 | 253 | } |
michael@0 | 254 | |
michael@0 | 255 | // Check if we already have the service installed. |
michael@0 | 256 | nsAutoServiceHandle schService(OpenServiceW(schSCManager, |
michael@0 | 257 | SVC_NAME, |
michael@0 | 258 | SERVICE_ALL_ACCESS)); |
michael@0 | 259 | DWORD lastError = GetLastError(); |
michael@0 | 260 | if (!schService && ERROR_SERVICE_DOES_NOT_EXIST != lastError) { |
michael@0 | 261 | // The service exists but we couldn't open it |
michael@0 | 262 | LOG_WARN(("Could not open service. (%d)", GetLastError())); |
michael@0 | 263 | return FALSE; |
michael@0 | 264 | } |
michael@0 | 265 | |
michael@0 | 266 | if (schService) { |
michael@0 | 267 | // The service exists but it may not have the correct permissions. |
michael@0 | 268 | // This could happen if the permissions were not set correctly originally |
michael@0 | 269 | // or have been changed after the installation. This will reset the |
michael@0 | 270 | // permissions back to allow limited user accounts. |
michael@0 | 271 | if (!SetUserAccessServiceDACL(schService)) { |
michael@0 | 272 | LOG_WARN(("Could not reset security ACE on service handle. It might not be " |
michael@0 | 273 | "possible to start the service. This error should never " |
michael@0 | 274 | "happen. (%d)", GetLastError())); |
michael@0 | 275 | } |
michael@0 | 276 | |
michael@0 | 277 | // The service exists and we opened it |
michael@0 | 278 | DWORD bytesNeeded; |
michael@0 | 279 | if (!QueryServiceConfigW(schService, nullptr, 0, &bytesNeeded) && |
michael@0 | 280 | GetLastError() != ERROR_INSUFFICIENT_BUFFER) { |
michael@0 | 281 | LOG_WARN(("Could not determine buffer size for query service config. (%d)", |
michael@0 | 282 | GetLastError())); |
michael@0 | 283 | return FALSE; |
michael@0 | 284 | } |
michael@0 | 285 | |
michael@0 | 286 | // Get the service config information, in particular we want the binary |
michael@0 | 287 | // path of the service. |
michael@0 | 288 | mozilla::ScopedDeleteArray<char> serviceConfigBuffer(new char[bytesNeeded]); |
michael@0 | 289 | if (!QueryServiceConfigW(schService, |
michael@0 | 290 | reinterpret_cast<QUERY_SERVICE_CONFIGW*>(serviceConfigBuffer.get()), |
michael@0 | 291 | bytesNeeded, &bytesNeeded)) { |
michael@0 | 292 | LOG_WARN(("Could open service but could not query service config. (%d)", |
michael@0 | 293 | GetLastError())); |
michael@0 | 294 | return FALSE; |
michael@0 | 295 | } |
michael@0 | 296 | QUERY_SERVICE_CONFIGW &serviceConfig = |
michael@0 | 297 | *reinterpret_cast<QUERY_SERVICE_CONFIGW*>(serviceConfigBuffer.get()); |
michael@0 | 298 | |
michael@0 | 299 | // Check if we need to fix the service path |
michael@0 | 300 | BOOL servicePathWasWrong; |
michael@0 | 301 | static BOOL alreadyCheckedFixServicePath = FALSE; |
michael@0 | 302 | if (!alreadyCheckedFixServicePath) { |
michael@0 | 303 | if (!FixServicePath(schService, serviceConfig.lpBinaryPathName, |
michael@0 | 304 | servicePathWasWrong)) { |
michael@0 | 305 | LOG_WARN(("Could not fix service path. This should never happen. (%d)", |
michael@0 | 306 | GetLastError())); |
michael@0 | 307 | // True is returned because the service is pointing to |
michael@0 | 308 | // maintenanceservice_tmp.exe so it actually was upgraded to the |
michael@0 | 309 | // newest installed service. |
michael@0 | 310 | return TRUE; |
michael@0 | 311 | } else if (servicePathWasWrong) { |
michael@0 | 312 | // Now that the path is fixed we should re-attempt the install. |
michael@0 | 313 | // This current process' image path is maintenanceservice_tmp.exe. |
michael@0 | 314 | // The service used to point to maintenanceservice_tmp.exe. |
michael@0 | 315 | // The service was just fixed to point to maintenanceservice.exe. |
michael@0 | 316 | // Re-attempting an install from scratch will work as normal. |
michael@0 | 317 | alreadyCheckedFixServicePath = TRUE; |
michael@0 | 318 | LOG(("Restarting install action: %d", action)); |
michael@0 | 319 | return SvcInstall(action); |
michael@0 | 320 | } |
michael@0 | 321 | } |
michael@0 | 322 | |
michael@0 | 323 | // Ensure the service path is not quoted. We own this memory and know it to |
michael@0 | 324 | // be large enough for the quoted path, so it is large enough for the |
michael@0 | 325 | // unquoted path. This function cannot fail. |
michael@0 | 326 | PathUnquoteSpacesW(serviceConfig.lpBinaryPathName); |
michael@0 | 327 | |
michael@0 | 328 | // Obtain the existing maintenanceservice file's version number and |
michael@0 | 329 | // the new file's version number. Versions are in the format of |
michael@0 | 330 | // A.B.C.D. |
michael@0 | 331 | DWORD existingA, existingB, existingC, existingD; |
michael@0 | 332 | DWORD newA, newB, newC, newD; |
michael@0 | 333 | BOOL obtainedExistingVersionInfo = |
michael@0 | 334 | GetVersionNumberFromPath(serviceConfig.lpBinaryPathName, |
michael@0 | 335 | existingA, existingB, |
michael@0 | 336 | existingC, existingD); |
michael@0 | 337 | if (!GetVersionNumberFromPath(newServiceBinaryPath, newA, |
michael@0 | 338 | newB, newC, newD)) { |
michael@0 | 339 | LOG_WARN(("Could not obtain version number from new path")); |
michael@0 | 340 | return FALSE; |
michael@0 | 341 | } |
michael@0 | 342 | |
michael@0 | 343 | // Check if we need to replace the old binary with the new one |
michael@0 | 344 | // If we couldn't get the old version info then we assume we should |
michael@0 | 345 | // replace it. |
michael@0 | 346 | if (ForceInstallSvc == action || |
michael@0 | 347 | !obtainedExistingVersionInfo || |
michael@0 | 348 | (existingA < newA) || |
michael@0 | 349 | (existingA == newA && existingB < newB) || |
michael@0 | 350 | (existingA == newA && existingB == newB && |
michael@0 | 351 | existingC < newC) || |
michael@0 | 352 | (existingA == newA && existingB == newB && |
michael@0 | 353 | existingC == newC && existingD < newD)) { |
michael@0 | 354 | |
michael@0 | 355 | // We have a newer updater, so update the description from the INI file. |
michael@0 | 356 | UpdateServiceDescription(schService); |
michael@0 | 357 | |
michael@0 | 358 | schService.reset(); |
michael@0 | 359 | if (!StopService()) { |
michael@0 | 360 | return FALSE; |
michael@0 | 361 | } |
michael@0 | 362 | |
michael@0 | 363 | if (!wcscmp(newServiceBinaryPath, serviceConfig.lpBinaryPathName)) { |
michael@0 | 364 | LOG(("File is already in the correct location, no action needed for " |
michael@0 | 365 | "upgrade. The path is: \"%ls\"", newServiceBinaryPath)); |
michael@0 | 366 | return TRUE; |
michael@0 | 367 | } |
michael@0 | 368 | |
michael@0 | 369 | BOOL result = TRUE; |
michael@0 | 370 | |
michael@0 | 371 | // Attempt to copy the new binary over top the existing binary. |
michael@0 | 372 | // If there is an error we try to move it out of the way and then |
michael@0 | 373 | // copy it in. First try the safest / easiest way to overwrite the file. |
michael@0 | 374 | if (!CopyFileW(newServiceBinaryPath, |
michael@0 | 375 | serviceConfig.lpBinaryPathName, FALSE)) { |
michael@0 | 376 | LOG_WARN(("Could not overwrite old service binary file. " |
michael@0 | 377 | "This should never happen, but if it does the next " |
michael@0 | 378 | "upgrade will fix it, the service is not a critical " |
michael@0 | 379 | "component that needs to be installed for upgrades " |
michael@0 | 380 | "to work. (%d)", GetLastError())); |
michael@0 | 381 | |
michael@0 | 382 | // We rename the last 3 filename chars in an unsafe way. Manually |
michael@0 | 383 | // verify there are more than 3 chars for safe failure in MoveFileExW. |
michael@0 | 384 | const size_t len = wcslen(serviceConfig.lpBinaryPathName); |
michael@0 | 385 | if (len > 3) { |
michael@0 | 386 | // Calculate the temp file path that we're moving the file to. This |
michael@0 | 387 | // is the same as the proper service path but with a .old extension. |
michael@0 | 388 | LPWSTR oldServiceBinaryTempPath = |
michael@0 | 389 | new WCHAR[len + 1]; |
michael@0 | 390 | memset(oldServiceBinaryTempPath, 0, (len + 1) * sizeof (WCHAR)); |
michael@0 | 391 | wcsncpy(oldServiceBinaryTempPath, serviceConfig.lpBinaryPathName, len); |
michael@0 | 392 | // Rename the last 3 chars to 'old' |
michael@0 | 393 | wcsncpy(oldServiceBinaryTempPath + len - 3, L"old", 3); |
michael@0 | 394 | |
michael@0 | 395 | // Move the current (old) service file to the temp path. |
michael@0 | 396 | if (MoveFileExW(serviceConfig.lpBinaryPathName, |
michael@0 | 397 | oldServiceBinaryTempPath, |
michael@0 | 398 | MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH)) { |
michael@0 | 399 | // The old binary is moved out of the way, copy in the new one. |
michael@0 | 400 | if (!CopyFileW(newServiceBinaryPath, |
michael@0 | 401 | serviceConfig.lpBinaryPathName, FALSE)) { |
michael@0 | 402 | // It is best to leave the old service binary in this condition. |
michael@0 | 403 | LOG_WARN(("The new service binary could not be copied in." |
michael@0 | 404 | " The service will not be upgraded.")); |
michael@0 | 405 | result = FALSE; |
michael@0 | 406 | } else { |
michael@0 | 407 | LOG(("The new service binary was copied in by first moving the" |
michael@0 | 408 | " old one out of the way.")); |
michael@0 | 409 | } |
michael@0 | 410 | |
michael@0 | 411 | // Attempt to get rid of the old service temp path. |
michael@0 | 412 | if (DeleteFileW(oldServiceBinaryTempPath)) { |
michael@0 | 413 | LOG(("The old temp service path was deleted: %ls.", |
michael@0 | 414 | oldServiceBinaryTempPath)); |
michael@0 | 415 | } else { |
michael@0 | 416 | // The old temp path could not be removed. It will be removed |
michael@0 | 417 | // the next time the user can't copy the binary in or on uninstall. |
michael@0 | 418 | LOG_WARN(("The old temp service path was not deleted.")); |
michael@0 | 419 | } |
michael@0 | 420 | } else { |
michael@0 | 421 | // It is best to leave the old service binary in this condition. |
michael@0 | 422 | LOG_WARN(("Could not move old service file out of the way from:" |
michael@0 | 423 | " \"%ls\" to \"%ls\". Service will not be upgraded. (%d)", |
michael@0 | 424 | serviceConfig.lpBinaryPathName, |
michael@0 | 425 | oldServiceBinaryTempPath, GetLastError())); |
michael@0 | 426 | result = FALSE; |
michael@0 | 427 | } |
michael@0 | 428 | delete[] oldServiceBinaryTempPath; |
michael@0 | 429 | } else { |
michael@0 | 430 | // It is best to leave the old service binary in this condition. |
michael@0 | 431 | LOG_WARN(("Service binary path was less than 3, service will" |
michael@0 | 432 | " not be updated. This should never happen.")); |
michael@0 | 433 | result = FALSE; |
michael@0 | 434 | } |
michael@0 | 435 | } else { |
michael@0 | 436 | LOG(("The new service binary was copied in.")); |
michael@0 | 437 | } |
michael@0 | 438 | |
michael@0 | 439 | // We made a copy of ourselves to the existing location. |
michael@0 | 440 | // The tmp file (the process of which we are executing right now) will be |
michael@0 | 441 | // left over. Attempt to delete the file on the next reboot. |
michael@0 | 442 | if (MoveFileExW(newServiceBinaryPath, nullptr, |
michael@0 | 443 | MOVEFILE_DELAY_UNTIL_REBOOT)) { |
michael@0 | 444 | LOG(("Deleting the old file path on the next reboot: %ls.", |
michael@0 | 445 | newServiceBinaryPath)); |
michael@0 | 446 | } else { |
michael@0 | 447 | LOG_WARN(("Call to delete the old file path failed: %ls.", |
michael@0 | 448 | newServiceBinaryPath)); |
michael@0 | 449 | } |
michael@0 | 450 | |
michael@0 | 451 | return result; |
michael@0 | 452 | } |
michael@0 | 453 | |
michael@0 | 454 | // We don't need to copy ourselves to the existing location. |
michael@0 | 455 | // The tmp file (the process of which we are executing right now) will be |
michael@0 | 456 | // left over. Attempt to delete the file on the next reboot. |
michael@0 | 457 | MoveFileExW(newServiceBinaryPath, nullptr, MOVEFILE_DELAY_UNTIL_REBOOT); |
michael@0 | 458 | |
michael@0 | 459 | // nothing to do, we already have a newer service installed |
michael@0 | 460 | return TRUE; |
michael@0 | 461 | } |
michael@0 | 462 | |
michael@0 | 463 | // If the service does not exist and we are upgrading, don't install it. |
michael@0 | 464 | if (UpgradeSvc == action) { |
michael@0 | 465 | // The service does not exist and we are upgrading, so don't install it |
michael@0 | 466 | return TRUE; |
michael@0 | 467 | } |
michael@0 | 468 | |
michael@0 | 469 | // Quote the path only if it contains spaces. |
michael@0 | 470 | PathQuoteSpacesW(newServiceBinaryPath); |
michael@0 | 471 | // The service does not already exist so create the service as on demand |
michael@0 | 472 | schService.own(CreateServiceW(schSCManager, SVC_NAME, SVC_DISPLAY_NAME, |
michael@0 | 473 | SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, |
michael@0 | 474 | SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL, |
michael@0 | 475 | newServiceBinaryPath, nullptr, nullptr, |
michael@0 | 476 | nullptr, nullptr, nullptr)); |
michael@0 | 477 | if (!schService) { |
michael@0 | 478 | LOG_WARN(("Could not create Windows service. " |
michael@0 | 479 | "This error should never happen since a service install " |
michael@0 | 480 | "should only be called when elevated. (%d)", GetLastError())); |
michael@0 | 481 | return FALSE; |
michael@0 | 482 | } |
michael@0 | 483 | |
michael@0 | 484 | if (!SetUserAccessServiceDACL(schService)) { |
michael@0 | 485 | LOG_WARN(("Could not set security ACE on service handle, the service will not " |
michael@0 | 486 | "be able to be started from unelevated processes. " |
michael@0 | 487 | "This error should never happen. (%d)", |
michael@0 | 488 | GetLastError())); |
michael@0 | 489 | } |
michael@0 | 490 | |
michael@0 | 491 | UpdateServiceDescription(schService); |
michael@0 | 492 | |
michael@0 | 493 | return TRUE; |
michael@0 | 494 | } |
michael@0 | 495 | |
michael@0 | 496 | /** |
michael@0 | 497 | * Stops the Maintenance service. |
michael@0 | 498 | * |
michael@0 | 499 | * @return TRUE if successful. |
michael@0 | 500 | */ |
michael@0 | 501 | BOOL |
michael@0 | 502 | StopService() |
michael@0 | 503 | { |
michael@0 | 504 | // Get a handle to the local computer SCM database with full access rights. |
michael@0 | 505 | nsAutoServiceHandle schSCManager(OpenSCManager(nullptr, nullptr, |
michael@0 | 506 | SC_MANAGER_ALL_ACCESS)); |
michael@0 | 507 | if (!schSCManager) { |
michael@0 | 508 | LOG_WARN(("Could not open service manager. (%d)", GetLastError())); |
michael@0 | 509 | return FALSE; |
michael@0 | 510 | } |
michael@0 | 511 | |
michael@0 | 512 | // Open the service |
michael@0 | 513 | nsAutoServiceHandle schService(OpenServiceW(schSCManager, SVC_NAME, |
michael@0 | 514 | SERVICE_ALL_ACCESS)); |
michael@0 | 515 | if (!schService) { |
michael@0 | 516 | LOG_WARN(("Could not open service. (%d)", GetLastError())); |
michael@0 | 517 | return FALSE; |
michael@0 | 518 | } |
michael@0 | 519 | |
michael@0 | 520 | LOG(("Sending stop request...")); |
michael@0 | 521 | SERVICE_STATUS status; |
michael@0 | 522 | SetLastError(ERROR_SUCCESS); |
michael@0 | 523 | if (!ControlService(schService, SERVICE_CONTROL_STOP, &status) && |
michael@0 | 524 | GetLastError() != ERROR_SERVICE_NOT_ACTIVE) { |
michael@0 | 525 | LOG_WARN(("Error sending stop request. (%d)", GetLastError())); |
michael@0 | 526 | } |
michael@0 | 527 | |
michael@0 | 528 | schSCManager.reset(); |
michael@0 | 529 | schService.reset(); |
michael@0 | 530 | |
michael@0 | 531 | LOG(("Waiting for service stop...")); |
michael@0 | 532 | DWORD lastState = WaitForServiceStop(SVC_NAME, 30); |
michael@0 | 533 | |
michael@0 | 534 | // The service can be in a stopped state but the exe still in use |
michael@0 | 535 | // so make sure the process is really gone before proceeding |
michael@0 | 536 | WaitForProcessExit(L"maintenanceservice.exe", 30); |
michael@0 | 537 | LOG(("Done waiting for service stop, last service state: %d", lastState)); |
michael@0 | 538 | |
michael@0 | 539 | return lastState == SERVICE_STOPPED; |
michael@0 | 540 | } |
michael@0 | 541 | |
michael@0 | 542 | /** |
michael@0 | 543 | * Uninstalls the Maintenance service. |
michael@0 | 544 | * |
michael@0 | 545 | * @return TRUE if successful. |
michael@0 | 546 | */ |
michael@0 | 547 | BOOL |
michael@0 | 548 | SvcUninstall() |
michael@0 | 549 | { |
michael@0 | 550 | // Get a handle to the local computer SCM database with full access rights. |
michael@0 | 551 | nsAutoServiceHandle schSCManager(OpenSCManager(nullptr, nullptr, |
michael@0 | 552 | SC_MANAGER_ALL_ACCESS)); |
michael@0 | 553 | if (!schSCManager) { |
michael@0 | 554 | LOG_WARN(("Could not open service manager. (%d)", GetLastError())); |
michael@0 | 555 | return FALSE; |
michael@0 | 556 | } |
michael@0 | 557 | |
michael@0 | 558 | // Open the service |
michael@0 | 559 | nsAutoServiceHandle schService(OpenServiceW(schSCManager, SVC_NAME, |
michael@0 | 560 | SERVICE_ALL_ACCESS)); |
michael@0 | 561 | if (!schService) { |
michael@0 | 562 | LOG_WARN(("Could not open service. (%d)", GetLastError())); |
michael@0 | 563 | return FALSE; |
michael@0 | 564 | } |
michael@0 | 565 | |
michael@0 | 566 | //Stop the service so it deletes faster and so the uninstaller |
michael@0 | 567 | // can actually delete its EXE. |
michael@0 | 568 | DWORD totalWaitTime = 0; |
michael@0 | 569 | SERVICE_STATUS status; |
michael@0 | 570 | static const int maxWaitTime = 1000 * 60; // Never wait more than a minute |
michael@0 | 571 | if (ControlService(schService, SERVICE_CONTROL_STOP, &status)) { |
michael@0 | 572 | do { |
michael@0 | 573 | Sleep(status.dwWaitHint); |
michael@0 | 574 | totalWaitTime += (status.dwWaitHint + 10); |
michael@0 | 575 | if (status.dwCurrentState == SERVICE_STOPPED) { |
michael@0 | 576 | break; |
michael@0 | 577 | } else if (totalWaitTime > maxWaitTime) { |
michael@0 | 578 | break; |
michael@0 | 579 | } |
michael@0 | 580 | } while (QueryServiceStatus(schService, &status)); |
michael@0 | 581 | } |
michael@0 | 582 | |
michael@0 | 583 | // Delete the service or mark it for deletion |
michael@0 | 584 | BOOL deleted = DeleteService(schService); |
michael@0 | 585 | if (!deleted) { |
michael@0 | 586 | deleted = (GetLastError() == ERROR_SERVICE_MARKED_FOR_DELETE); |
michael@0 | 587 | } |
michael@0 | 588 | |
michael@0 | 589 | return deleted; |
michael@0 | 590 | } |
michael@0 | 591 | |
michael@0 | 592 | /** |
michael@0 | 593 | * Sets the access control list for user access for the specified service. |
michael@0 | 594 | * |
michael@0 | 595 | * @param hService The service to set the access control list on |
michael@0 | 596 | * @return TRUE if successful |
michael@0 | 597 | */ |
michael@0 | 598 | BOOL |
michael@0 | 599 | SetUserAccessServiceDACL(SC_HANDLE hService) |
michael@0 | 600 | { |
michael@0 | 601 | PACL pNewAcl = nullptr; |
michael@0 | 602 | PSECURITY_DESCRIPTOR psd = nullptr; |
michael@0 | 603 | DWORD lastError = SetUserAccessServiceDACL(hService, pNewAcl, psd); |
michael@0 | 604 | if (pNewAcl) { |
michael@0 | 605 | LocalFree((HLOCAL)pNewAcl); |
michael@0 | 606 | } |
michael@0 | 607 | if (psd) { |
michael@0 | 608 | LocalFree((LPVOID)psd); |
michael@0 | 609 | } |
michael@0 | 610 | return ERROR_SUCCESS == lastError; |
michael@0 | 611 | } |
michael@0 | 612 | |
michael@0 | 613 | /** |
michael@0 | 614 | * Sets the access control list for user access for the specified service. |
michael@0 | 615 | * |
michael@0 | 616 | * @param hService The service to set the access control list on |
michael@0 | 617 | * @param pNewAcl The out param ACL which should be freed by caller |
michael@0 | 618 | * @param psd out param security descriptor, should be freed by caller |
michael@0 | 619 | * @return ERROR_SUCCESS if successful |
michael@0 | 620 | */ |
michael@0 | 621 | DWORD |
michael@0 | 622 | SetUserAccessServiceDACL(SC_HANDLE hService, PACL &pNewAcl, |
michael@0 | 623 | PSECURITY_DESCRIPTOR psd) |
michael@0 | 624 | { |
michael@0 | 625 | // Get the current security descriptor needed size |
michael@0 | 626 | DWORD needed = 0; |
michael@0 | 627 | if (!QueryServiceObjectSecurity(hService, DACL_SECURITY_INFORMATION, |
michael@0 | 628 | &psd, 0, &needed)) { |
michael@0 | 629 | if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) { |
michael@0 | 630 | LOG_WARN(("Could not query service object security size. (%d)", |
michael@0 | 631 | GetLastError())); |
michael@0 | 632 | return GetLastError(); |
michael@0 | 633 | } |
michael@0 | 634 | |
michael@0 | 635 | DWORD size = needed; |
michael@0 | 636 | psd = (PSECURITY_DESCRIPTOR)LocalAlloc(LPTR, size); |
michael@0 | 637 | if (!psd) { |
michael@0 | 638 | LOG_WARN(("Could not allocate security descriptor. (%d)", |
michael@0 | 639 | GetLastError())); |
michael@0 | 640 | return ERROR_INSUFFICIENT_BUFFER; |
michael@0 | 641 | } |
michael@0 | 642 | |
michael@0 | 643 | // Get the actual security descriptor now |
michael@0 | 644 | if (!QueryServiceObjectSecurity(hService, DACL_SECURITY_INFORMATION, |
michael@0 | 645 | psd, size, &needed)) { |
michael@0 | 646 | LOG_WARN(("Could not allocate security descriptor. (%d)", |
michael@0 | 647 | GetLastError())); |
michael@0 | 648 | return GetLastError(); |
michael@0 | 649 | } |
michael@0 | 650 | } |
michael@0 | 651 | |
michael@0 | 652 | // Get the current DACL from the security descriptor. |
michael@0 | 653 | PACL pacl = nullptr; |
michael@0 | 654 | BOOL bDaclPresent = FALSE; |
michael@0 | 655 | BOOL bDaclDefaulted = FALSE; |
michael@0 | 656 | if ( !GetSecurityDescriptorDacl(psd, &bDaclPresent, &pacl, |
michael@0 | 657 | &bDaclDefaulted)) { |
michael@0 | 658 | LOG_WARN(("Could not obtain DACL. (%d)", GetLastError())); |
michael@0 | 659 | return GetLastError(); |
michael@0 | 660 | } |
michael@0 | 661 | |
michael@0 | 662 | PSID sid; |
michael@0 | 663 | DWORD SIDSize = SECURITY_MAX_SID_SIZE; |
michael@0 | 664 | sid = LocalAlloc(LMEM_FIXED, SIDSize); |
michael@0 | 665 | if (!sid) { |
michael@0 | 666 | LOG_WARN(("Could not allocate SID memory. (%d)", GetLastError())); |
michael@0 | 667 | return GetLastError(); |
michael@0 | 668 | } |
michael@0 | 669 | |
michael@0 | 670 | if (!CreateWellKnownSid(WinBuiltinUsersSid, nullptr, sid, &SIDSize)) { |
michael@0 | 671 | DWORD lastError = GetLastError(); |
michael@0 | 672 | LOG_WARN(("Could not create well known SID. (%d)", lastError)); |
michael@0 | 673 | LocalFree(sid); |
michael@0 | 674 | return lastError; |
michael@0 | 675 | } |
michael@0 | 676 | |
michael@0 | 677 | // Lookup the account name, the function fails if you don't pass in |
michael@0 | 678 | // a buffer for the domain name but it's not used since we're using |
michael@0 | 679 | // the built in account Sid. |
michael@0 | 680 | SID_NAME_USE accountType; |
michael@0 | 681 | WCHAR accountName[UNLEN + 1] = { L'\0' }; |
michael@0 | 682 | WCHAR domainName[DNLEN + 1] = { L'\0' }; |
michael@0 | 683 | DWORD accountNameSize = UNLEN + 1; |
michael@0 | 684 | DWORD domainNameSize = DNLEN + 1; |
michael@0 | 685 | if (!LookupAccountSidW(nullptr, sid, accountName, |
michael@0 | 686 | &accountNameSize, |
michael@0 | 687 | domainName, &domainNameSize, &accountType)) { |
michael@0 | 688 | LOG_WARN(("Could not lookup account Sid, will try Users. (%d)", |
michael@0 | 689 | GetLastError())); |
michael@0 | 690 | wcsncpy(accountName, L"Users", UNLEN); |
michael@0 | 691 | } |
michael@0 | 692 | |
michael@0 | 693 | // We already have the group name so we can get rid of the SID |
michael@0 | 694 | FreeSid(sid); |
michael@0 | 695 | sid = nullptr; |
michael@0 | 696 | |
michael@0 | 697 | // Build the ACE, BuildExplicitAccessWithName cannot fail so it is not logged. |
michael@0 | 698 | EXPLICIT_ACCESS ea; |
michael@0 | 699 | BuildExplicitAccessWithNameW(&ea, accountName, |
michael@0 | 700 | SERVICE_START | SERVICE_STOP | GENERIC_READ, |
michael@0 | 701 | SET_ACCESS, NO_INHERITANCE); |
michael@0 | 702 | DWORD lastError = SetEntriesInAclW(1, (PEXPLICIT_ACCESS)&ea, pacl, &pNewAcl); |
michael@0 | 703 | if (ERROR_SUCCESS != lastError) { |
michael@0 | 704 | LOG_WARN(("Could not set entries in ACL. (%d)", lastError)); |
michael@0 | 705 | return lastError; |
michael@0 | 706 | } |
michael@0 | 707 | |
michael@0 | 708 | // Initialize a new security descriptor. |
michael@0 | 709 | SECURITY_DESCRIPTOR sd; |
michael@0 | 710 | if (!InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION)) { |
michael@0 | 711 | LOG_WARN(("Could not initialize security descriptor. (%d)", |
michael@0 | 712 | GetLastError())); |
michael@0 | 713 | return GetLastError(); |
michael@0 | 714 | } |
michael@0 | 715 | |
michael@0 | 716 | // Set the new DACL in the security descriptor. |
michael@0 | 717 | if (!SetSecurityDescriptorDacl(&sd, TRUE, pNewAcl, FALSE)) { |
michael@0 | 718 | LOG_WARN(("Could not set security descriptor DACL. (%d)", |
michael@0 | 719 | GetLastError())); |
michael@0 | 720 | return GetLastError(); |
michael@0 | 721 | } |
michael@0 | 722 | |
michael@0 | 723 | // Set the new security descriptor for the service object. |
michael@0 | 724 | if (!SetServiceObjectSecurity(hService, DACL_SECURITY_INFORMATION, &sd)) { |
michael@0 | 725 | LOG_WARN(("Could not set object security. (%d)", |
michael@0 | 726 | GetLastError())); |
michael@0 | 727 | return GetLastError(); |
michael@0 | 728 | } |
michael@0 | 729 | |
michael@0 | 730 | // Woohoo, raise the roof |
michael@0 | 731 | LOG(("User access was set successfully on the service.")); |
michael@0 | 732 | return ERROR_SUCCESS; |
michael@0 | 733 | } |