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/file_util.h"
13 #include "base/files/file_path.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
24 namespace {
25
26 const wchar_t kServiceExeName[] = L"cloud_print_service.exe";
27
28 // The traits class for Windows Service.
29 class ServiceHandleTraits {
30 public:
31 typedef SC_HANDLE Handle;
32
33 // Closes the handle.
CloseHandle(Handle handle)34 static bool CloseHandle(Handle handle) {
35 return ::CloseServiceHandle(handle) != FALSE;
36 }
37
IsHandleValid(Handle handle)38 static bool IsHandleValid(Handle handle) {
39 return handle != NULL;
40 }
41
NullHandle()42 static Handle NullHandle() {
43 return NULL;
44 }
45
46 private:
47 DISALLOW_IMPLICIT_CONSTRUCTORS(ServiceHandleTraits);
48 };
49
50 typedef base::win::GenericScopedHandle<
51 ServiceHandleTraits, base::win::DummyVerifierTraits> ServiceHandle;
52
OpenServiceManager(ServiceHandle * service_manager)53 HRESULT 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
OpenService(const base::string16 & name,DWORD access,ServiceHandle * service)64 HRESULT 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, name.c_str(), access));
75
76 if (!service->IsValid())
77 return cloud_print::GetLastHResult();
78
79 return S_OK;
80 }
81
82 } // namespace
83
ServiceController()84 ServiceController::ServiceController()
85 : name_(cloud_print::LoadLocalString(IDS_SERVICE_NAME)),
86 command_line_(CommandLine::NO_PROGRAM) {
87 }
88
~ServiceController()89 ServiceController::~ServiceController() {
90 }
91
StartService()92 HRESULT 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, 0, NULL))
99 return cloud_print::GetLastHResult();
100 SERVICE_STATUS status = {0};
101 while (::QueryServiceStatus(service, &status) &&
102 status.dwCurrentState == SERVICE_START_PENDING) {
103 Sleep(100);
104 }
105 return S_OK;
106 }
107
StopService()108 HRESULT 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, SERVICE_CONTROL_STOP, &status))
116 return cloud_print::GetLastHResult();
117 while (::QueryServiceStatus(service, &status) &&
118 status.dwCurrentState > SERVICE_STOPPED) {
119 Sleep(500);
120 ::ControlService(service, SERVICE_CONTROL_STOP, &status);
121 }
122 return S_OK;
123 }
124
GetBinary() const125 base::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
InstallConnectorService(const base::string16 & user,const base::string16 & password,const base::FilePath & user_data_dir,bool enable_logging)131 HRESULT 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
InstallCheckService(const base::string16 & user,const base::string16 & password,const base::FilePath & user_data_dir)140 HRESULT 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
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)148 HRESULT 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, 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, SERVICE_CONFIG_DESCRIPTION, &description);
217
218 return S_OK;
219 }
220
UninstallService()221 HRESULT ServiceController::UninstallService() {
222 StopService();
223
224 ServiceHandle service;
225 OpenService(name_, SERVICE_STOP | DELETE, &service);
226 HRESULT hr = S_FALSE;
227 if (service) {
228 if (!::DeleteService(service)) {
229 LOG(ERROR) << "Failed to uninstall service";
230 hr = cloud_print::GetLastHResult();
231 }
232 }
233 UpdateRegistryAppId(false);
234 return hr;
235 }
236
UpdateBinaryPath()237 HRESULT ServiceController::UpdateBinaryPath() {
238 UpdateState();
239 ServiceController::State origina_state = state();
240 if (origina_state < ServiceController::STATE_STOPPED)
241 return S_FALSE;
242
243 ServiceHandle service;
244 HRESULT hr = OpenService(name_, SERVICE_CHANGE_CONFIG, &service);
245 if (FAILED(hr))
246 return hr;
247
248 base::FilePath service_path = GetBinary();
249 if (!base::PathExists(service_path))
250 return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
251
252 command_line_.SetProgram(service_path);
253 if (!::ChangeServiceConfig(service, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE,
254 SERVICE_NO_CHANGE,
255 command_line_.GetCommandLineString().c_str(), NULL,
256 NULL, NULL, NULL, NULL, NULL)) {
257 return cloud_print::GetLastHResult();
258 }
259
260 if (origina_state != ServiceController::STATE_RUNNING)
261 return S_OK;
262
263 hr = StopService();
264 if (FAILED(hr))
265 return hr;
266
267 hr = StartService();
268 if (FAILED(hr))
269 return hr;
270
271 return S_OK;
272 }
273
UpdateState()274 void ServiceController::UpdateState() {
275 state_ = STATE_NOT_FOUND;
276 user_.clear();
277 is_logging_enabled_ = false;
278
279 ServiceHandle service;
280 HRESULT hr = OpenService(name_, SERVICE_QUERY_STATUS | SERVICE_QUERY_CONFIG,
281 &service);
282 if (FAILED(hr))
283 return;
284
285 state_ = STATE_STOPPED;
286 SERVICE_STATUS status = {0};
287 if (::QueryServiceStatus(service, &status) &&
288 status.dwCurrentState == SERVICE_RUNNING) {
289 state_ = STATE_RUNNING;
290 }
291
292 DWORD config_size = 0;
293 ::QueryServiceConfig(service, NULL, 0, &config_size);
294 if (!config_size)
295 return;
296
297 std::vector<uint8> buffer(config_size, 0);
298 QUERY_SERVICE_CONFIG* config =
299 reinterpret_cast<QUERY_SERVICE_CONFIG*>(&buffer[0]);
300 if (!::QueryServiceConfig(service, config, buffer.size(), &config_size) ||
301 config_size != buffer.size()) {
302 return;
303 }
304
305 command_line_ = CommandLine::FromString(config->lpBinaryPathName);
306 if (!command_line_.HasSwitch(kServiceSwitch)) {
307 state_ = STATE_NOT_FOUND;
308 return;
309 }
310 is_logging_enabled_ = command_line_.HasSwitch(switches::kEnableLogging);
311 user_ = config->lpServiceStartName;
312 }
313
is_logging_enabled() const314 bool ServiceController::is_logging_enabled() const {
315 return command_line_.HasSwitch(switches::kEnableLogging);
316 }
317