toolkit/components/maintenanceservice/serviceinstall.cpp

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

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 }

mercurial