1// Copyright 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "cloud_print/service/win/service_controller.h"
6
7#include <atlbase.h>
8#include <atlcom.h>
9#include <atlctl.h>
10
11#include "base/command_line.h"
12#include "base/files/file_path.h"
13#include "base/files/file_util.h"
14#include "base/path_service.h"
15#include "base/win/scoped_handle.h"
16#include "chrome/common/chrome_switches.h"
17#include "cloud_print/common/win/cloud_print_utils.h"
18#include "cloud_print/service/service_constants.h"
19#include "cloud_print/service/service_switches.h"
20#include "cloud_print/service/win/chrome_launcher.h"
21#include "cloud_print/service/win/local_security_policy.h"
22#include "cloud_print/service/win/service_utils.h"
23
24namespace {
25
26const wchar_t kServiceExeName[] = L"cloud_print_service.exe";
27
28// The traits class for Windows Service.
29class ServiceHandleTraits {
30 public:
31  typedef SC_HANDLE Handle;
32
33  // Closes the handle.
34  static bool CloseHandle(Handle handle) {
35    return ::CloseServiceHandle(handle) != FALSE;
36  }
37
38  static bool IsHandleValid(Handle handle) {
39    return handle != NULL;
40  }
41
42  static Handle NullHandle() {
43    return NULL;
44  }
45
46 private:
47  DISALLOW_IMPLICIT_CONSTRUCTORS(ServiceHandleTraits);
48};
49
50typedef base::win::GenericScopedHandle<
51    ServiceHandleTraits, base::win::DummyVerifierTraits> ServiceHandle;
52
53HRESULT OpenServiceManager(ServiceHandle* service_manager) {
54  if (!service_manager)
55    return E_POINTER;
56
57  service_manager->Set(::OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS));
58  if (!service_manager->IsValid())
59    return cloud_print::GetLastHResult();
60
61  return S_OK;
62}
63
64HRESULT OpenService(const base::string16& name, DWORD access,
65                    ServiceHandle* service) {
66  if (!service)
67    return E_POINTER;
68
69  ServiceHandle scm;
70  HRESULT hr = OpenServiceManager(&scm);
71  if (FAILED(hr))
72    return hr;
73
74  service->Set(::OpenService(scm.Get(), name.c_str(), access));
75
76  if (!service->IsValid())
77    return cloud_print::GetLastHResult();
78
79  return S_OK;
80}
81
82}  // namespace
83
84ServiceController::ServiceController()
85    : name_(cloud_print::LoadLocalString(IDS_SERVICE_NAME)),
86      command_line_(CommandLine::NO_PROGRAM) {
87}
88
89ServiceController::~ServiceController() {
90}
91
92HRESULT ServiceController::StartService() {
93  ServiceHandle service;
94  HRESULT hr = OpenService(name_, SERVICE_START| SERVICE_QUERY_STATUS,
95                           &service);
96  if (FAILED(hr))
97    return hr;
98  if (!::StartService(service.Get(), 0, NULL))
99    return cloud_print::GetLastHResult();
100  SERVICE_STATUS status = {0};
101  while (::QueryServiceStatus(service.Get(), &status) &&
102          status.dwCurrentState == SERVICE_START_PENDING) {
103    Sleep(100);
104  }
105  return S_OK;
106}
107
108HRESULT ServiceController::StopService() {
109  ServiceHandle service;
110  HRESULT hr = OpenService(name_, SERVICE_STOP | SERVICE_QUERY_STATUS,
111                           &service);
112  if (FAILED(hr))
113    return hr;
114  SERVICE_STATUS status = {0};
115  if (!::ControlService(service.Get(), SERVICE_CONTROL_STOP, &status))
116    return cloud_print::GetLastHResult();
117  while (::QueryServiceStatus(service.Get(), &status) &&
118          status.dwCurrentState > SERVICE_STOPPED) {
119    Sleep(500);
120    ::ControlService(service.Get(), SERVICE_CONTROL_STOP, &status);
121  }
122  return S_OK;
123}
124
125base::FilePath ServiceController::GetBinary() const {
126  base::FilePath service_path;
127  CHECK(PathService::Get(base::FILE_EXE, &service_path));
128  return service_path.DirName().Append(base::FilePath(kServiceExeName));
129}
130
131HRESULT ServiceController::InstallConnectorService(
132    const base::string16& user,
133    const base::string16& password,
134    const base::FilePath& user_data_dir,
135    bool enable_logging) {
136  return InstallService(user, password, true, kServiceSwitch, user_data_dir,
137                        enable_logging);
138}
139
140HRESULT ServiceController::InstallCheckService(
141    const base::string16& user,
142    const base::string16& password,
143    const base::FilePath& user_data_dir) {
144  return InstallService(user, password, false, kRequirementsSwitch,
145                        user_data_dir, true);
146}
147
148HRESULT ServiceController::InstallService(const base::string16& user,
149                                          const base::string16& password,
150                                          bool auto_start,
151                                          const std::string& run_switch,
152                                          const base::FilePath& user_data_dir,
153                                          bool enable_logging) {
154  // TODO(vitalybuka): consider "lite" version if we don't want unregister
155  // printers here.
156  HRESULT hr = UninstallService();
157  if (FAILED(hr))
158    return hr;
159
160  hr = UpdateRegistryAppId(true);
161  if (FAILED(hr))
162    return hr;
163
164  base::FilePath service_path = GetBinary();
165  if (!base::PathExists(service_path))
166    return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
167  CommandLine command_line(service_path);
168  command_line.AppendSwitch(run_switch);
169  if (!user_data_dir.empty())
170    command_line.AppendSwitchPath(switches::kUserDataDir, user_data_dir);
171  if (enable_logging) {
172    command_line.AppendSwitch(switches::kEnableLogging);
173    command_line.AppendSwitchASCII(switches::kV, "1");
174  }
175
176  CopyChromeSwitchesFromCurrentProcess(&command_line);
177
178  LocalSecurityPolicy local_security_policy;
179  if (local_security_policy.Open()) {
180    if (!local_security_policy.IsPrivilegeSet(user, kSeServiceLogonRight)) {
181      LOG(WARNING) << "Setting " << kSeServiceLogonRight << " for " << user;
182      if (!local_security_policy.SetPrivilege(user, kSeServiceLogonRight)) {
183        LOG(ERROR) << "Failed to set" << kSeServiceLogonRight;
184        LOG(ERROR) << "Make sure you can run the service as " << user << ".";
185      }
186    }
187  } else {
188    LOG(ERROR) << "Failed to open security policy.";
189  }
190
191  ServiceHandle scm;
192  hr = OpenServiceManager(&scm);
193  if (FAILED(hr))
194    return hr;
195
196  base::string16 display_name =
197      cloud_print::LoadLocalString(IDS_SERVICE_DISPLAY_NAME);
198  ServiceHandle service(
199      ::CreateService(
200          scm.Get(), name_.c_str(), display_name.c_str(), SERVICE_ALL_ACCESS,
201          SERVICE_WIN32_OWN_PROCESS,
202          auto_start ? SERVICE_AUTO_START : SERVICE_DEMAND_START,
203          SERVICE_ERROR_NORMAL, command_line.GetCommandLineString().c_str(),
204          NULL, NULL, NULL, user.empty() ? NULL : user.c_str(),
205          password.empty() ? NULL : password.c_str()));
206
207  if (!service.IsValid()) {
208    LOG(ERROR) << "Failed to install service as " << user << ".";
209    return cloud_print::GetLastHResult();
210  }
211
212  base::string16 description_string =
213      cloud_print::LoadLocalString(IDS_SERVICE_DESCRIPTION);
214  SERVICE_DESCRIPTION description = {0};
215  description.lpDescription = const_cast<wchar_t*>(description_string.c_str());
216  ::ChangeServiceConfig2(service.Get(), SERVICE_CONFIG_DESCRIPTION,
217                         &description);
218
219  return S_OK;
220}
221
222HRESULT ServiceController::UninstallService() {
223  StopService();
224
225  ServiceHandle service;
226  OpenService(name_, SERVICE_STOP | DELETE, &service);
227  HRESULT hr = S_FALSE;
228  if (service.IsValid()) {
229    if (!::DeleteService(service.Get())) {
230      LOG(ERROR) << "Failed to uninstall service";
231      hr = cloud_print::GetLastHResult();
232    }
233  }
234  UpdateRegistryAppId(false);
235  return hr;
236}
237
238HRESULT ServiceController::UpdateBinaryPath() {
239  UpdateState();
240  ServiceController::State origina_state = state();
241  if (origina_state < ServiceController::STATE_STOPPED)
242    return S_FALSE;
243
244  ServiceHandle service;
245  HRESULT hr = OpenService(name_, SERVICE_CHANGE_CONFIG, &service);
246  if (FAILED(hr))
247    return hr;
248
249  base::FilePath service_path = GetBinary();
250  if (!base::PathExists(service_path))
251    return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
252
253  command_line_.SetProgram(service_path);
254  if (!::ChangeServiceConfig(service.Get(), SERVICE_NO_CHANGE,
255                             SERVICE_NO_CHANGE, SERVICE_NO_CHANGE,
256                             command_line_.GetCommandLineString().c_str(), NULL,
257                             NULL, NULL, NULL, NULL, NULL)) {
258    return cloud_print::GetLastHResult();
259  }
260
261  if (origina_state != ServiceController::STATE_RUNNING)
262    return S_OK;
263
264  hr = StopService();
265  if (FAILED(hr))
266    return hr;
267
268  hr = StartService();
269  if (FAILED(hr))
270    return hr;
271
272  return S_OK;
273}
274
275void ServiceController::UpdateState() {
276  state_ = STATE_NOT_FOUND;
277  user_.clear();
278  is_logging_enabled_ = false;
279
280  ServiceHandle service;
281  HRESULT hr = OpenService(name_, SERVICE_QUERY_STATUS | SERVICE_QUERY_CONFIG,
282                           &service);
283  if (FAILED(hr))
284    return;
285
286  state_ = STATE_STOPPED;
287  SERVICE_STATUS status = {0};
288  if (::QueryServiceStatus(service.Get(), &status) &&
289      status.dwCurrentState == SERVICE_RUNNING) {
290    state_ = STATE_RUNNING;
291  }
292
293  DWORD config_size = 0;
294  ::QueryServiceConfig(service.Get(), NULL, 0, &config_size);
295  if (!config_size)
296    return;
297
298  std::vector<uint8> buffer(config_size, 0);
299  QUERY_SERVICE_CONFIG* config =
300      reinterpret_cast<QUERY_SERVICE_CONFIG*>(&buffer[0]);
301  if (!::QueryServiceConfig(service.Get(), config, buffer.size(),
302                            &config_size) ||
303      config_size != buffer.size()) {
304    return;
305  }
306
307  command_line_ = CommandLine::FromString(config->lpBinaryPathName);
308  if (!command_line_.HasSwitch(kServiceSwitch)) {
309    state_ = STATE_NOT_FOUND;
310    return;
311  }
312  is_logging_enabled_ = command_line_.HasSwitch(switches::kEnableLogging);
313  user_ = config->lpServiceStartName;
314}
315
316bool ServiceController::is_logging_enabled() const {
317  return command_line_.HasSwitch(switches::kEnableLogging);
318}
319