toolkit/components/maintenanceservice/serviceinstall.cpp

changeset 0
6474c204b198
     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 +}

mercurial