// Copyright 2013 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 "cloud_print/service/win/service_controller.h"
#include <atlbase.h>
#include <atlcom.h>
#include <atlctl.h>
#include "base/command_line.h"
#include "base/file_util.h"
#include "base/files/file_path.h"
#include "base/path_service.h"
#include "base/win/scoped_handle.h"
#include "chrome/common/chrome_switches.h"
#include "cloud_print/common/win/cloud_print_utils.h"
#include "cloud_print/service/service_constants.h"
#include "cloud_print/service/service_switches.h"
#include "cloud_print/service/win/chrome_launcher.h"
#include "cloud_print/service/win/local_security_policy.h"
#include "cloud_print/service/win/service_utils.h"
namespace {
const wchar_t kServiceExeName[] = L"cloud_print_service.exe";
// The traits class for Windows Service.
class ServiceHandleTraits {
public:
typedef SC_HANDLE Handle;
// Closes the handle.
static bool CloseHandle(Handle handle) {
return ::CloseServiceHandle(handle) != FALSE;
}
static bool IsHandleValid(Handle handle) {
return handle != NULL;
}
static Handle NullHandle() {
return NULL;
}
private:
DISALLOW_IMPLICIT_CONSTRUCTORS(ServiceHandleTraits);
};
typedef base::win::GenericScopedHandle<
ServiceHandleTraits, base::win::DummyVerifierTraits> ServiceHandle;
HRESULT OpenServiceManager(ServiceHandle* service_manager) {
if (!service_manager)
return E_POINTER;
service_manager->Set(::OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS));
if (!service_manager->IsValid())
return cloud_print::GetLastHResult();
return S_OK;
}
HRESULT OpenService(const base::string16& name, DWORD access,
ServiceHandle* service) {
if (!service)
return E_POINTER;
ServiceHandle scm;
HRESULT hr = OpenServiceManager(&scm);
if (FAILED(hr))
return hr;
service->Set(::OpenService(scm, name.c_str(), access));
if (!service->IsValid())
return cloud_print::GetLastHResult();
return S_OK;
}
} // namespace
ServiceController::ServiceController()
: name_(cloud_print::LoadLocalString(IDS_SERVICE_NAME)),
command_line_(CommandLine::NO_PROGRAM) {
}
ServiceController::~ServiceController() {
}
HRESULT ServiceController::StartService() {
ServiceHandle service;
HRESULT hr = OpenService(name_, SERVICE_START| SERVICE_QUERY_STATUS,
&service);
if (FAILED(hr))
return hr;
if (!::StartService(service, 0, NULL))
return cloud_print::GetLastHResult();
SERVICE_STATUS status = {0};
while (::QueryServiceStatus(service, &status) &&
status.dwCurrentState == SERVICE_START_PENDING) {
Sleep(100);
}
return S_OK;
}
HRESULT ServiceController::StopService() {
ServiceHandle service;
HRESULT hr = OpenService(name_, SERVICE_STOP | SERVICE_QUERY_STATUS,
&service);
if (FAILED(hr))
return hr;
SERVICE_STATUS status = {0};
if (!::ControlService(service, SERVICE_CONTROL_STOP, &status))
return cloud_print::GetLastHResult();
while (::QueryServiceStatus(service, &status) &&
status.dwCurrentState > SERVICE_STOPPED) {
Sleep(500);
::ControlService(service, SERVICE_CONTROL_STOP, &status);
}
return S_OK;
}
base::FilePath ServiceController::GetBinary() const {
base::FilePath service_path;
CHECK(PathService::Get(base::FILE_EXE, &service_path));
return service_path.DirName().Append(base::FilePath(kServiceExeName));
}
HRESULT ServiceController::InstallConnectorService(
const base::string16& user,
const base::string16& password,
const base::FilePath& user_data_dir,
bool enable_logging) {
return InstallService(user, password, true, kServiceSwitch, user_data_dir,
enable_logging);
}
HRESULT ServiceController::InstallCheckService(
const base::string16& user,
const base::string16& password,
const base::FilePath& user_data_dir) {
return InstallService(user, password, false, kRequirementsSwitch,
user_data_dir, true);
}
HRESULT ServiceController::InstallService(const base::string16& user,
const base::string16& password,
bool auto_start,
const std::string& run_switch,
const base::FilePath& user_data_dir,
bool enable_logging) {
// TODO(vitalybuka): consider "lite" version if we don't want unregister
// printers here.
HRESULT hr = UninstallService();
if (FAILED(hr))
return hr;
hr = UpdateRegistryAppId(true);
if (FAILED(hr))
return hr;
base::FilePath service_path = GetBinary();
if (!base::PathExists(service_path))
return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
CommandLine command_line(service_path);
command_line.AppendSwitch(run_switch);
if (!user_data_dir.empty())
command_line.AppendSwitchPath(switches::kUserDataDir, user_data_dir);
if (enable_logging) {
command_line.AppendSwitch(switches::kEnableLogging);
command_line.AppendSwitchASCII(switches::kV, "1");
}
CopyChromeSwitchesFromCurrentProcess(&command_line);
LocalSecurityPolicy local_security_policy;
if (local_security_policy.Open()) {
if (!local_security_policy.IsPrivilegeSet(user, kSeServiceLogonRight)) {
LOG(WARNING) << "Setting " << kSeServiceLogonRight << " for " << user;
if (!local_security_policy.SetPrivilege(user, kSeServiceLogonRight)) {
LOG(ERROR) << "Failed to set" << kSeServiceLogonRight;
LOG(ERROR) << "Make sure you can run the service as " << user << ".";
}
}
} else {
LOG(ERROR) << "Failed to open security policy.";
}
ServiceHandle scm;
hr = OpenServiceManager(&scm);
if (FAILED(hr))
return hr;
base::string16 display_name =
cloud_print::LoadLocalString(IDS_SERVICE_DISPLAY_NAME);
ServiceHandle service(
::CreateService(
scm, name_.c_str(), display_name.c_str(), SERVICE_ALL_ACCESS,
SERVICE_WIN32_OWN_PROCESS,
auto_start ? SERVICE_AUTO_START : SERVICE_DEMAND_START,
SERVICE_ERROR_NORMAL, command_line.GetCommandLineString().c_str(),
NULL, NULL, NULL, user.empty() ? NULL : user.c_str(),
password.empty() ? NULL : password.c_str()));
if (!service.IsValid()) {
LOG(ERROR) << "Failed to install service as " << user << ".";
return cloud_print::GetLastHResult();
}
base::string16 description_string =
cloud_print::LoadLocalString(IDS_SERVICE_DESCRIPTION);
SERVICE_DESCRIPTION description = {0};
description.lpDescription = const_cast<wchar_t*>(description_string.c_str());
::ChangeServiceConfig2(service, SERVICE_CONFIG_DESCRIPTION, &description);
return S_OK;
}
HRESULT ServiceController::UninstallService() {
StopService();
ServiceHandle service;
OpenService(name_, SERVICE_STOP | DELETE, &service);
HRESULT hr = S_FALSE;
if (service) {
if (!::DeleteService(service)) {
LOG(ERROR) << "Failed to uninstall service";
hr = cloud_print::GetLastHResult();
}
}
UpdateRegistryAppId(false);
return hr;
}
HRESULT ServiceController::UpdateBinaryPath() {
UpdateState();
ServiceController::State origina_state = state();
if (origina_state < ServiceController::STATE_STOPPED)
return S_FALSE;
ServiceHandle service;
HRESULT hr = OpenService(name_, SERVICE_CHANGE_CONFIG, &service);
if (FAILED(hr))
return hr;
base::FilePath service_path = GetBinary();
if (!base::PathExists(service_path))
return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
command_line_.SetProgram(service_path);
if (!::ChangeServiceConfig(service, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE,
SERVICE_NO_CHANGE,
command_line_.GetCommandLineString().c_str(), NULL,
NULL, NULL, NULL, NULL, NULL)) {
return cloud_print::GetLastHResult();
}
if (origina_state != ServiceController::STATE_RUNNING)
return S_OK;
hr = StopService();
if (FAILED(hr))
return hr;
hr = StartService();
if (FAILED(hr))
return hr;
return S_OK;
}
void ServiceController::UpdateState() {
state_ = STATE_NOT_FOUND;
user_.clear();
is_logging_enabled_ = false;
ServiceHandle service;
HRESULT hr = OpenService(name_, SERVICE_QUERY_STATUS | SERVICE_QUERY_CONFIG,
&service);
if (FAILED(hr))
return;
state_ = STATE_STOPPED;
SERVICE_STATUS status = {0};
if (::QueryServiceStatus(service, &status) &&
status.dwCurrentState == SERVICE_RUNNING) {
state_ = STATE_RUNNING;
}
DWORD config_size = 0;
::QueryServiceConfig(service, NULL, 0, &config_size);
if (!config_size)
return;
std::vector<uint8> buffer(config_size, 0);
QUERY_SERVICE_CONFIG* config =
reinterpret_cast<QUERY_SERVICE_CONFIG*>(&buffer[0]);
if (!::QueryServiceConfig(service, config, buffer.size(), &config_size) ||
config_size != buffer.size()) {
return;
}
command_line_ = CommandLine::FromString(config->lpBinaryPathName);
if (!command_line_.HasSwitch(kServiceSwitch)) {
state_ = STATE_NOT_FOUND;
return;
}
is_logging_enabled_ = command_line_.HasSwitch(switches::kEnableLogging);
user_ = config->lpServiceStartName;
}
bool ServiceController::is_logging_enabled() const {
return command_line_.HasSwitch(switches::kEnableLogging);
}