chromium/chrome/credential_provider/extension/os_service_manager.cc

// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/credential_provider/extension/os_service_manager.h"

#include "base/command_line.h"
#include "chrome/credential_provider/extension/extension_strings.h"
#include "chrome/credential_provider/gaiacp/logging.h"

namespace credential_provider {
namespace extension {

namespace {
const unsigned int kServiceQueryWaitTimeMs = 100;
// The number of iterations to poll if a service is stopped correctly.
const unsigned int kMaxServiceQueryIterations = 100;
}  // namespace

OSServiceManager** OSServiceManager::GetInstanceStorage() {
  static OSServiceManager* instance = new OSServiceManager();

  return &instance;
}

// static
OSServiceManager* OSServiceManager::Get() {
  return *GetInstanceStorage();
}

OSServiceManager::~OSServiceManager() {}

DWORD OSServiceManager::InstallService(
    const base::FilePath& service_binary_path,
    ScopedScHandle* sc_handle) {
  ScopedScHandle scm_handle(
      ::OpenSCManager(nullptr, nullptr, SC_MANAGER_CREATE_SERVICE));
  if (!scm_handle.IsValid())
    return ::GetLastError();

  base::CommandLine command_line(base::CommandLine::NO_PROGRAM);
  command_line.SetProgram(service_binary_path);

  *sc_handle = ScopedScHandle(::CreateService(
      scm_handle.Get(),                             // SCM database
      kGCPWExtensionServiceName,                    // name of service
      kGCPWExtensionServiceDisplayName,             // service name to display
      SERVICE_ALL_ACCESS,                           // desired access
      SERVICE_WIN32_OWN_PROCESS,                    // service type
      SERVICE_AUTO_START,                           // start type
      SERVICE_ERROR_NORMAL,                         // error control type
      command_line.GetCommandLineString().c_str(),  // path to service's binary
      nullptr,                                      // no load ordering group
      nullptr,                                      // no tag identifier
      nullptr,                                      // no dependencies
      nullptr,                                      // LocalSystem account
      nullptr));

  if (!sc_handle->IsValid())
    return ::GetLastError();

  return ERROR_SUCCESS;
}

DWORD OSServiceManager::GetServiceStatus(SERVICE_STATUS* service_status) {
  DCHECK(service_status);

  ScopedScHandle scm_handle(
      ::OpenSCManager(nullptr, nullptr, SC_MANAGER_CONNECT));
  if (!scm_handle.IsValid())
    return ::GetLastError();

  ScopedScHandle sc_handle(::OpenService(
      scm_handle.Get(), kGCPWExtensionServiceName, SERVICE_QUERY_STATUS));
  if (!sc_handle.IsValid())
    return ::GetLastError();

  if (!::QueryServiceStatus(sc_handle.Get(), service_status)) {
    return ::GetLastError();
  }

  return ERROR_SUCCESS;
}

DWORD OSServiceManager::DeleteService() {
  ScopedScHandle scm_handle(
      ::OpenSCManager(nullptr, nullptr, SC_MANAGER_ALL_ACCESS));
  if (!scm_handle.IsValid())
    return ::GetLastError();

  ScopedScHandle sc_handle(
      ::OpenService(scm_handle.Get(), kGCPWExtensionServiceName, DELETE));
  if (!sc_handle.IsValid())
    return ::GetLastError();

  // The DeleteService function marks a service for deletion from the service
  // control manager database. The database entry is not removed until all open
  // handles to the service have been closed by calls to the CloseServiceHandle
  // function, and the service is not running.
  if (!::DeleteService(sc_handle.Get()))
    return ::GetLastError();

  return ERROR_SUCCESS;
}

DWORD OSServiceManager::StartGCPWService() {
  ScopedScHandle scm_handle(
      ::OpenSCManager(nullptr, nullptr, SC_MANAGER_ALL_ACCESS));
  if (!scm_handle.IsValid())
    return ::GetLastError();

  ScopedScHandle sc_handle(::OpenService(
      scm_handle.Get(), kGCPWExtensionServiceName, SERVICE_START));
  if (!sc_handle.IsValid())
    return ::GetLastError();

  if (!::StartService(sc_handle.Get(), 0, nullptr))
    return ::GetLastError();

  LOGFN(INFO) << "GCPW extension started successfully.";

  return ERROR_SUCCESS;
}

DWORD OSServiceManager::WaitForServiceStopped() {
  LOGFN(VERBOSE);

  ScopedScHandle scm_handle(
      ::OpenSCManager(nullptr, nullptr, SC_MANAGER_ALL_ACCESS));
  if (scm_handle.Get() == nullptr)
    return ::GetLastError();

  ScopedScHandle s_handle(::OpenService(
      scm_handle.Get(), kGCPWExtensionServiceName, SERVICE_QUERY_STATUS));
  if (s_handle.Get() == nullptr)
    return ::GetLastError();

  // Wait until the service is completely stopped.
  for (unsigned int iteration = 0; iteration < kMaxServiceQueryIterations;
       ++iteration) {
    SERVICE_STATUS service_status;
    if (!QueryServiceStatus(s_handle.Get(), &service_status)) {
      DWORD error = ::GetLastError();
      LOGFN(ERROR) << "QueryServiceStatus failed error=" << error;
      return error;
    }
    if (service_status.dwCurrentState == SERVICE_STOPPED)
      return ERROR_SUCCESS;

    if (service_status.dwCurrentState != SERVICE_STOP_PENDING &&
        service_status.dwCurrentState != SERVICE_RUNNING) {
      LOGFN(ERROR) << "Cannot stop service state="
                   << service_status.dwCurrentState;
      return E_FAIL;
    }
    ::Sleep(kServiceQueryWaitTimeMs);
  }

  // The service didn't terminate.
  LOGFN(ERROR) << "Stopping service timed out";

  return E_FAIL;
}

DWORD OSServiceManager::ControlService(DWORD control) {
  LOGFN(VERBOSE);

  ScopedScHandle scm_handle(
      ::OpenSCManager(nullptr, nullptr, SC_MANAGER_ALL_ACCESS));
  if (!scm_handle.IsValid())
    return ::GetLastError();

  // TODO(crbug.com/40141510): More granular access rights corresponding to the
  // controls can be specified.
  ScopedScHandle s_handle(::OpenService(
      scm_handle.Get(), kGCPWExtensionServiceName, SERVICE_ALL_ACCESS));
  if (!s_handle.IsValid())
    return ::GetLastError();

  SERVICE_STATUS service_status;
  if (!::ControlService(s_handle.Get(), control, &service_status)) {
    DWORD error = ::GetLastError();
    LOGFN(ERROR) << "ControlService failed with error=" << error;
    return error;
  }

  return ERROR_SUCCESS;
}

DWORD OSServiceManager::ChangeServiceConfig(DWORD dwServiceType,
                                            DWORD dwStartType,
                                            DWORD dwErrorControl) {
  LOGFN(VERBOSE);

  ScopedScHandle scm_handle(
      ::OpenSCManager(nullptr, nullptr, SC_MANAGER_ALL_ACCESS));
  if (!scm_handle.IsValid())
    return ::GetLastError();

  ScopedScHandle s_handle(::OpenService(
      scm_handle.Get(), kGCPWExtensionServiceName, SERVICE_CHANGE_CONFIG));
  if (!s_handle.IsValid())
    return ::GetLastError();

  if (!::ChangeServiceConfig(s_handle.Get(), dwServiceType, dwStartType,
                             dwErrorControl, nullptr, nullptr, nullptr,
                             nullptr, nullptr, nullptr, nullptr)) {
    return ::GetLastError();
  }

  return ERROR_SUCCESS;
}

DWORD OSServiceManager::StartServiceCtrlDispatcher(
    LPSERVICE_MAIN_FUNCTION service_main) {
  SERVICE_TABLE_ENTRY dispatch_table[] = {
      {(LPWSTR)kGCPWExtensionServiceName, service_main}, {nullptr, nullptr}};

  if (!::StartServiceCtrlDispatcher(dispatch_table))
    return ::GetLastError();

  return ERROR_SUCCESS;
}

DWORD OSServiceManager::RegisterCtrlHandler(
    LPHANDLER_FUNCTION handler_proc,
    SERVICE_STATUS_HANDLE* service_status_handle) {
  DCHECK(handler_proc);
  DCHECK(service_status_handle);

  SERVICE_STATUS_HANDLE sc_status_handle =
      ::RegisterServiceCtrlHandler(kGCPWExtensionServiceName, handler_proc);
  if (!sc_status_handle)
    return ::GetLastError();

  *service_status_handle = sc_status_handle;
  return ERROR_SUCCESS;
}

DWORD OSServiceManager::SetServiceStatus(
    SERVICE_STATUS_HANDLE service_status_handle,
    SERVICE_STATUS service) {
  if (!::SetServiceStatus(service_status_handle, &service))
    return ::GetLastError();
  return ERROR_SUCCESS;
}

}  // namespace extension
}  // namespace credential_provider