| // Copyright 2020 The Chromium Authors. All rights reserved. |
| // 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/1108932): 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 |