OLD | NEW |
---|---|
(Empty) | |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
Sergey Ulanov
2013/09/09 18:12:31
This shows up as a new file, so it's hard to revie
alexeypa (please no reviews)
2013/09/09 19:30:57
Done.
| |
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 "remoting/host/setup/daemon_controller_delegate_win.h" | |
6 | |
7 #include "base/basictypes.h" | |
8 #include "base/bind.h" | |
9 #include "base/bind_helpers.h" | |
10 #include "base/compiler_specific.h" | |
11 #include "base/json/json_reader.h" | |
12 #include "base/json/json_writer.h" | |
13 #include "base/logging.h" | |
14 #include "base/strings/string16.h" | |
15 #include "base/strings/utf_string_conversions.h" | |
16 #include "base/thread_task_runner_handle.h" | |
17 #include "base/time/time.h" | |
18 #include "base/timer/timer.h" | |
19 #include "base/values.h" | |
20 #include "base/win/scoped_bstr.h" | |
21 #include "base/win/scoped_comptr.h" | |
22 #include "base/win/windows_version.h" | |
23 #include "remoting/base/scoped_sc_handle_win.h" | |
24 #include "remoting/host/branding.h" | |
25 // chromoting_lib.h contains MIDL-generated declarations. | |
26 #include "remoting/host/chromoting_lib.h" | |
27 #include "remoting/host/setup/daemon_installer_win.h" | |
28 #include "remoting/host/usage_stats_consent.h" | |
29 | |
30 using base::win::ScopedBstr; | |
31 using base::win::ScopedComPtr; | |
32 | |
33 namespace remoting { | |
34 | |
35 namespace { | |
36 | |
37 // ProgID of the daemon controller. | |
38 const wchar_t kDaemonController[] = | |
39 L"ChromotingElevatedController.ElevatedController"; | |
40 | |
41 // The COM elevation moniker for the Elevated Controller. | |
42 const wchar_t kDaemonControllerElevationMoniker[] = | |
43 L"Elevation:Administrator!new:" | |
44 L"ChromotingElevatedController.ElevatedController"; | |
45 | |
46 // The maximum duration of keeping a reference to a privileged instance of | |
47 // the Daemon Controller. This effectively reduces number of UAC prompts a user | |
48 // sees. | |
49 const int kPrivilegedTimeoutSec = 5 * 60; | |
50 | |
51 // The maximum duration of keeping a reference to an unprivileged instance of | |
52 // the Daemon Controller. This interval should not be too long. If upgrade | |
53 // happens while there is a live reference to a Daemon Controller instance | |
54 // the old binary still can be used. So dropping the references often makes sure | |
55 // that the old binary will go away sooner. | |
56 const int kUnprivilegedTimeoutSec = 60; | |
57 | |
58 void ConfigToString(const base::DictionaryValue& config, ScopedBstr* out) { | |
59 std::string config_str; | |
60 base::JSONWriter::Write(&config, &config_str); | |
61 ScopedBstr config_scoped_bstr(UTF8ToUTF16(config_str).c_str()); | |
62 out->Swap(config_scoped_bstr); | |
63 } | |
64 | |
65 DaemonController::State ConvertToDaemonState(DWORD service_state) { | |
66 switch (service_state) { | |
67 case SERVICE_RUNNING: | |
68 return DaemonController::STATE_STARTED; | |
69 | |
70 case SERVICE_CONTINUE_PENDING: | |
71 case SERVICE_START_PENDING: | |
72 return DaemonController::STATE_STARTING; | |
73 break; | |
74 | |
75 case SERVICE_PAUSE_PENDING: | |
76 case SERVICE_STOP_PENDING: | |
77 return DaemonController::STATE_STOPPING; | |
78 break; | |
79 | |
80 case SERVICE_PAUSED: | |
81 case SERVICE_STOPPED: | |
82 return DaemonController::STATE_STOPPED; | |
83 break; | |
84 | |
85 default: | |
86 NOTREACHED(); | |
87 return DaemonController::STATE_UNKNOWN; | |
88 } | |
89 } | |
90 | |
91 DWORD OpenService(ScopedScHandle* service_out) { | |
92 // Open the service and query its current state. | |
93 ScopedScHandle scmanager( | |
94 ::OpenSCManagerW(NULL, SERVICES_ACTIVE_DATABASE, | |
95 SC_MANAGER_CONNECT | SC_MANAGER_ENUMERATE_SERVICE)); | |
96 if (!scmanager.IsValid()) { | |
97 DWORD error = GetLastError(); | |
98 LOG_GETLASTERROR(ERROR) | |
99 << "Failed to connect to the service control manager"; | |
100 return error; | |
101 } | |
102 | |
103 ScopedScHandle service( | |
104 ::OpenServiceW(scmanager, kWindowsServiceName, SERVICE_QUERY_STATUS)); | |
105 if (!service.IsValid()) { | |
106 DWORD error = GetLastError(); | |
107 if (error != ERROR_SERVICE_DOES_NOT_EXIST) { | |
108 LOG_GETLASTERROR(ERROR) | |
109 << "Failed to open to the '" << kWindowsServiceName << "' service"; | |
110 } | |
111 return error; | |
112 } | |
113 | |
114 service_out->Set(service.Take()); | |
115 return ERROR_SUCCESS; | |
116 } | |
117 | |
118 DaemonController::AsyncResult HResultToAsyncResult( | |
119 HRESULT hr) { | |
120 if (SUCCEEDED(hr)) { | |
121 return DaemonController::RESULT_OK; | |
122 } else if (hr == HRESULT_FROM_WIN32(ERROR_CANCELLED)) { | |
123 return DaemonController::RESULT_CANCELLED; | |
124 } else { | |
125 // TODO(sergeyu): Report other errors to the webapp once it knows | |
126 // how to handle them. | |
127 return DaemonController::RESULT_FAILED; | |
128 } | |
129 } | |
130 | |
131 } // namespace | |
132 | |
133 DaemonControllerDelegateWin::DaemonControllerDelegateWin() | |
134 : control_is_elevated_(false), | |
135 window_handle_(NULL) { | |
136 } | |
137 | |
138 DaemonControllerDelegateWin::~DaemonControllerDelegateWin() { | |
139 } | |
140 | |
141 DaemonController::State DaemonControllerDelegateWin::GetState() { | |
142 if (base::win::GetVersion() < base::win::VERSION_XP) { | |
143 return DaemonController::STATE_NOT_IMPLEMENTED; | |
144 } | |
145 // TODO(alexeypa): Make the thread alertable, so we can switch to APC | |
146 // notifications rather than polling. | |
147 ScopedScHandle service; | |
148 DWORD error = OpenService(&service); | |
149 | |
150 switch (error) { | |
151 case ERROR_SUCCESS: { | |
152 SERVICE_STATUS status; | |
153 if (::QueryServiceStatus(service, &status)) { | |
154 return ConvertToDaemonState(status.dwCurrentState); | |
155 } else { | |
156 LOG_GETLASTERROR(ERROR) | |
157 << "Failed to query the state of the '" << kWindowsServiceName | |
158 << "' service"; | |
159 return DaemonController::STATE_UNKNOWN; | |
160 } | |
161 break; | |
162 } | |
163 case ERROR_SERVICE_DOES_NOT_EXIST: | |
164 return DaemonController::STATE_NOT_INSTALLED; | |
165 default: | |
166 return DaemonController::STATE_UNKNOWN; | |
167 } | |
168 } | |
169 | |
170 scoped_ptr<base::DictionaryValue> DaemonControllerDelegateWin::GetConfig() { | |
171 // Configure and start the Daemon Controller if it is installed already. | |
172 HRESULT hr = ActivateController(); | |
173 if (FAILED(hr)) | |
174 return scoped_ptr<base::DictionaryValue>(); | |
175 | |
176 // Get the host configuration. | |
177 ScopedBstr host_config; | |
178 hr = control_->GetConfig(host_config.Receive()); | |
179 if (FAILED(hr)) | |
180 return scoped_ptr<base::DictionaryValue>(); | |
181 | |
182 // Parse the string into a dictionary. | |
183 string16 file_content(static_cast<BSTR>(host_config), host_config.Length()); | |
184 scoped_ptr<base::Value> config( | |
185 base::JSONReader::Read(UTF16ToUTF8(file_content), | |
186 base::JSON_ALLOW_TRAILING_COMMAS)); | |
187 | |
188 if (!config || config->GetType() != base::Value::TYPE_DICTIONARY) | |
189 return scoped_ptr<base::DictionaryValue>(); | |
190 | |
191 return scoped_ptr<base::DictionaryValue>( | |
192 static_cast<base::DictionaryValue*>(config.release())); | |
193 } | |
194 | |
195 void DaemonControllerDelegateWin::SetConfigAndStart( | |
196 scoped_ptr<base::DictionaryValue> config, | |
197 bool consent, | |
198 const DaemonController::CompletionCallback& done) { | |
199 // Configure and start the Daemon Controller if it is installed already. | |
200 HRESULT hr = ActivateElevatedController(); | |
201 if (SUCCEEDED(hr)) { | |
202 OnInstallationComplete(config.Pass(), consent, done, S_OK); | |
203 return; | |
204 } | |
205 | |
206 // Otherwise, install it if its COM registration entry is missing. | |
207 if (hr == CO_E_CLASSSTRING) { | |
208 DCHECK(!installer_); | |
209 | |
210 installer_ = DaemonInstallerWin::Create( | |
211 GetTopLevelWindow(window_handle_), | |
212 base::Bind(&DaemonControllerDelegateWin::OnInstallationComplete, | |
213 base::Unretained(this), | |
214 base::Passed(&config), | |
215 consent, | |
216 done)); | |
217 installer_->Install(); | |
218 return; | |
219 } | |
220 | |
221 LOG(ERROR) << "Failed to initiate the Chromoting Host installation " | |
222 << "(error: 0x" << std::hex << hr << std::dec << ")."; | |
223 done.Run(HResultToAsyncResult(hr)); | |
224 } | |
225 | |
226 void DaemonControllerDelegateWin::UpdateConfig( | |
227 scoped_ptr<base::DictionaryValue> config, | |
228 const DaemonController::CompletionCallback& done) { | |
229 HRESULT hr = ActivateElevatedController(); | |
230 if (FAILED(hr)) { | |
231 done.Run(HResultToAsyncResult(hr)); | |
232 return; | |
233 } | |
234 | |
235 // Update the configuration. | |
236 ScopedBstr config_str(NULL); | |
237 ConfigToString(*config, &config_str); | |
238 if (config_str == NULL) { | |
239 done.Run(HResultToAsyncResult(E_OUTOFMEMORY)); | |
240 return; | |
241 } | |
242 | |
243 // Make sure that the PIN confirmation dialog is focused properly. | |
244 hr = control_->SetOwnerWindow( | |
245 reinterpret_cast<LONG_PTR>(GetTopLevelWindow(window_handle_))); | |
246 if (FAILED(hr)) { | |
247 done.Run(HResultToAsyncResult(hr)); | |
248 return; | |
249 } | |
250 | |
251 hr = control_->UpdateConfig(config_str); | |
252 done.Run(HResultToAsyncResult(hr)); | |
253 } | |
254 | |
255 void DaemonControllerDelegateWin::Stop( | |
256 const DaemonController::CompletionCallback& done) { | |
257 HRESULT hr = ActivateElevatedController(); | |
258 if (SUCCEEDED(hr)) | |
259 hr = control_->StopDaemon(); | |
260 | |
261 done.Run(HResultToAsyncResult(hr)); | |
262 } | |
263 | |
264 void DaemonControllerDelegateWin::SetWindow(void* window_handle) { | |
265 window_handle_ = reinterpret_cast<HWND>(window_handle); | |
266 } | |
267 | |
268 std::string DaemonControllerDelegateWin::GetVersion() { | |
269 // Configure and start the Daemon Controller if it is installed already. | |
270 HRESULT hr = ActivateController(); | |
271 if (FAILED(hr)) | |
272 return std::string(); | |
273 | |
274 // Get the version string. | |
275 ScopedBstr version; | |
276 hr = control_->GetVersion(version.Receive()); | |
277 if (FAILED(hr)) | |
278 return std::string(); | |
279 | |
280 return UTF16ToUTF8(string16(static_cast<BSTR>(version), version.Length())); | |
281 } | |
282 | |
283 DaemonController::UsageStatsConsent | |
284 DaemonControllerDelegateWin::GetUsageStatsConsent() { | |
285 DaemonController::UsageStatsConsent consent; | |
286 consent.supported = true; | |
287 consent.allowed = false; | |
288 consent.set_by_policy = false; | |
289 | |
290 // Activate the Daemon Controller and see if it supports |IDaemonControl2|. | |
291 HRESULT hr = ActivateController(); | |
292 if (FAILED(hr)) { | |
293 // The host is not installed yet. Assume that the user didn't consent to | |
294 // collecting crash dumps. | |
295 return consent; | |
296 } | |
297 | |
298 if (control2_.get() == NULL) { | |
299 // The host is installed and does not support crash dump reporting. | |
300 return consent; | |
301 } | |
302 | |
303 // Get the recorded user's consent. | |
304 BOOL allowed; | |
305 BOOL set_by_policy; | |
306 hr = control2_->GetUsageStatsConsent(&allowed, &set_by_policy); | |
307 if (FAILED(hr)) { | |
308 // If the user's consent is not recorded yet, assume that the user didn't | |
309 // consent to collecting crash dumps. | |
310 return consent; | |
311 } | |
312 | |
313 consent.allowed = !!allowed; | |
314 consent.set_by_policy = !!set_by_policy; | |
315 return consent; | |
316 } | |
317 | |
318 HRESULT DaemonControllerDelegateWin::ActivateController() { | |
319 if (!control_) { | |
320 CLSID class_id; | |
321 HRESULT hr = CLSIDFromProgID(kDaemonController, &class_id); | |
322 if (FAILED(hr)) { | |
323 return hr; | |
324 } | |
325 | |
326 hr = CoCreateInstance(class_id, NULL, CLSCTX_LOCAL_SERVER, | |
327 IID_IDaemonControl, control_.ReceiveVoid()); | |
328 if (FAILED(hr)) { | |
329 return hr; | |
330 } | |
331 | |
332 // Ignore the error. IID_IDaemonControl2 is optional. | |
333 control_.QueryInterface(IID_IDaemonControl2, control2_.ReceiveVoid()); | |
334 | |
335 // Release |control_| upon expiration of the timeout. | |
336 release_timer_.reset(new base::OneShotTimer<DaemonControllerDelegateWin>()); | |
337 release_timer_->Start(FROM_HERE, | |
338 base::TimeDelta::FromSeconds(kUnprivilegedTimeoutSec), | |
339 this, | |
340 &DaemonControllerDelegateWin::ReleaseController); | |
341 } | |
342 | |
343 return S_OK; | |
344 } | |
345 | |
346 HRESULT DaemonControllerDelegateWin::ActivateElevatedController() { | |
347 // The COM elevation is supported on Vista and above. | |
348 if (base::win::GetVersion() < base::win::VERSION_VISTA) | |
349 return ActivateController(); | |
350 | |
351 // Release an unprivileged instance of the daemon controller if any. | |
352 if (!control_is_elevated_) | |
353 ReleaseController(); | |
354 | |
355 if (!control_) { | |
356 BIND_OPTS3 bind_options; | |
357 memset(&bind_options, 0, sizeof(bind_options)); | |
358 bind_options.cbStruct = sizeof(bind_options); | |
359 bind_options.hwnd = GetTopLevelWindow(window_handle_); | |
360 bind_options.dwClassContext = CLSCTX_LOCAL_SERVER; | |
361 | |
362 HRESULT hr = ::CoGetObject( | |
363 kDaemonControllerElevationMoniker, | |
364 &bind_options, | |
365 IID_IDaemonControl, | |
366 control_.ReceiveVoid()); | |
367 if (FAILED(hr)) { | |
368 return hr; | |
369 } | |
370 | |
371 // Ignore the error. IID_IDaemonControl2 is optional. | |
372 control_.QueryInterface(IID_IDaemonControl2, control2_.ReceiveVoid()); | |
373 | |
374 // Note that we hold a reference to an elevated instance now. | |
375 control_is_elevated_ = true; | |
376 | |
377 // Release |control_| upon expiration of the timeout. | |
378 release_timer_.reset(new base::OneShotTimer<DaemonControllerDelegateWin>()); | |
379 release_timer_->Start(FROM_HERE, | |
380 base::TimeDelta::FromSeconds(kPrivilegedTimeoutSec), | |
381 this, | |
382 &DaemonControllerDelegateWin::ReleaseController); | |
383 } | |
384 | |
385 return S_OK; | |
386 } | |
387 | |
388 void DaemonControllerDelegateWin::ReleaseController() { | |
389 control_.Release(); | |
390 control2_.Release(); | |
391 release_timer_.reset(); | |
392 control_is_elevated_ = false; | |
393 } | |
394 | |
395 void DaemonControllerDelegateWin::OnInstallationComplete( | |
396 scoped_ptr<base::DictionaryValue> config, | |
397 bool consent, | |
398 const DaemonController::CompletionCallback& done, | |
399 HRESULT hr) { | |
400 installer_.reset(); | |
401 | |
402 if (FAILED(hr)) { | |
403 LOG(ERROR) << "Failed to install the Chromoting Host " | |
404 << "(error: 0x" << std::hex << hr << std::dec << ")."; | |
405 done.Run(HResultToAsyncResult(hr)); | |
406 return; | |
407 } | |
408 | |
409 hr = ActivateElevatedController(); | |
410 if (FAILED(hr)) { | |
411 done.Run(HResultToAsyncResult(hr)); | |
412 return; | |
413 } | |
414 | |
415 // Record the user's consent. | |
416 if (control2_) { | |
417 hr = control2_->SetUsageStatsConsent(consent); | |
418 if (FAILED(hr)) { | |
419 done.Run(HResultToAsyncResult(hr)); | |
420 return; | |
421 } | |
422 } | |
423 | |
424 // Set the configuration. | |
425 ScopedBstr config_str(NULL); | |
426 ConfigToString(*config, &config_str); | |
427 if (config_str == NULL) { | |
428 done.Run(HResultToAsyncResult(E_OUTOFMEMORY)); | |
429 return; | |
430 } | |
431 | |
432 hr = control_->SetOwnerWindow( | |
433 reinterpret_cast<LONG_PTR>(GetTopLevelWindow(window_handle_))); | |
434 if (FAILED(hr)) { | |
435 done.Run(HResultToAsyncResult(hr)); | |
436 return; | |
437 } | |
438 | |
439 hr = control_->SetConfig(config_str); | |
440 if (FAILED(hr)) { | |
441 done.Run(HResultToAsyncResult(hr)); | |
442 return; | |
443 } | |
444 | |
445 // Start daemon. | |
446 hr = control_->StartDaemon(); | |
447 done.Run(HResultToAsyncResult(hr)); | |
448 } | |
449 | |
450 scoped_refptr<DaemonController> DaemonController::Create() { | |
451 scoped_ptr<DaemonController::Delegate> delegate( | |
452 new DaemonControllerDelegateWin()); | |
453 return new DaemonController(delegate.Pass()); | |
454 } | |
455 | |
456 } // namespace remoting | |
OLD | NEW |