Updating trunk VERSION from 2139.0 to 2140.0
[chromium-blink-merge.git] / remoting / host / setup / daemon_controller_delegate_win.cc
blob1f965cdacb670a9a3c4e254dcf95309372cae192
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.
5 #include "remoting/host/setup/daemon_controller_delegate_win.h"
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/usage_stats_consent.h"
29 using base::win::ScopedBstr;
30 using base::win::ScopedComPtr;
32 namespace remoting {
34 namespace {
36 // ProgID of the daemon controller.
37 const wchar_t kDaemonController[] =
38 L"ChromotingElevatedController.ElevatedController";
40 // The COM elevation moniker for the Elevated Controller.
41 const wchar_t kDaemonControllerElevationMoniker[] =
42 L"Elevation:Administrator!new:"
43 L"ChromotingElevatedController.ElevatedController";
45 // The maximum duration of keeping a reference to a privileged instance of
46 // the Daemon Controller. This effectively reduces number of UAC prompts a user
47 // sees.
48 const int kPrivilegedTimeoutSec = 5 * 60;
50 // The maximum duration of keeping a reference to an unprivileged instance of
51 // the Daemon Controller. This interval should not be too long. If upgrade
52 // happens while there is a live reference to a Daemon Controller instance
53 // the old binary still can be used. So dropping the references often makes sure
54 // that the old binary will go away sooner.
55 const int kUnprivilegedTimeoutSec = 60;
57 void ConfigToString(const base::DictionaryValue& config, ScopedBstr* out) {
58 std::string config_str;
59 base::JSONWriter::Write(&config, &config_str);
60 ScopedBstr config_scoped_bstr(base::UTF8ToUTF16(config_str).c_str());
61 out->Swap(config_scoped_bstr);
64 DaemonController::State ConvertToDaemonState(DWORD service_state) {
65 switch (service_state) {
66 case SERVICE_RUNNING:
67 return DaemonController::STATE_STARTED;
69 case SERVICE_CONTINUE_PENDING:
70 case SERVICE_START_PENDING:
71 return DaemonController::STATE_STARTING;
72 break;
74 case SERVICE_PAUSE_PENDING:
75 case SERVICE_STOP_PENDING:
76 return DaemonController::STATE_STOPPING;
77 break;
79 case SERVICE_PAUSED:
80 case SERVICE_STOPPED:
81 return DaemonController::STATE_STOPPED;
82 break;
84 default:
85 NOTREACHED();
86 return DaemonController::STATE_UNKNOWN;
90 DWORD OpenService(ScopedScHandle* service_out) {
91 // Open the service and query its current state.
92 ScopedScHandle scmanager(
93 ::OpenSCManagerW(NULL, SERVICES_ACTIVE_DATABASE,
94 SC_MANAGER_CONNECT | SC_MANAGER_ENUMERATE_SERVICE));
95 if (!scmanager.IsValid()) {
96 DWORD error = GetLastError();
97 PLOG(ERROR) << "Failed to connect to the service control manager";
98 return error;
101 ScopedScHandle service(
102 ::OpenServiceW(scmanager, kWindowsServiceName, SERVICE_QUERY_STATUS));
103 if (!service.IsValid()) {
104 DWORD error = GetLastError();
105 if (error != ERROR_SERVICE_DOES_NOT_EXIST) {
106 PLOG(ERROR) << "Failed to open to the '" << kWindowsServiceName
107 << "' service";
109 return error;
112 service_out->Set(service.Take());
113 return ERROR_SUCCESS;
116 DaemonController::AsyncResult HResultToAsyncResult(
117 HRESULT hr) {
118 if (SUCCEEDED(hr)) {
119 return DaemonController::RESULT_OK;
120 } else if (hr == HRESULT_FROM_WIN32(ERROR_CANCELLED)) {
121 return DaemonController::RESULT_CANCELLED;
122 } else {
123 // TODO(sergeyu): Report other errors to the webapp once it knows
124 // how to handle them.
125 return DaemonController::RESULT_FAILED;
129 void InvokeCompletionCallback(
130 const DaemonController::CompletionCallback& done, HRESULT hr) {
131 done.Run(HResultToAsyncResult(hr));
134 } // namespace
136 DaemonControllerDelegateWin::DaemonControllerDelegateWin()
137 : control_is_elevated_(false),
138 window_handle_(NULL) {
141 DaemonControllerDelegateWin::~DaemonControllerDelegateWin() {
144 DaemonController::State DaemonControllerDelegateWin::GetState() {
145 if (base::win::GetVersion() < base::win::VERSION_XP) {
146 return DaemonController::STATE_NOT_IMPLEMENTED;
148 // TODO(alexeypa): Make the thread alertable, so we can switch to APC
149 // notifications rather than polling.
150 ScopedScHandle service;
151 DWORD error = OpenService(&service);
153 switch (error) {
154 case ERROR_SUCCESS: {
155 SERVICE_STATUS status;
156 if (::QueryServiceStatus(service, &status)) {
157 return ConvertToDaemonState(status.dwCurrentState);
158 } else {
159 PLOG(ERROR) << "Failed to query the state of the '"
160 << kWindowsServiceName << "' service";
161 return DaemonController::STATE_UNKNOWN;
163 break;
165 case ERROR_SERVICE_DOES_NOT_EXIST:
166 return DaemonController::STATE_NOT_INSTALLED;
167 default:
168 return DaemonController::STATE_UNKNOWN;
172 scoped_ptr<base::DictionaryValue> DaemonControllerDelegateWin::GetConfig() {
173 // Configure and start the Daemon Controller if it is installed already.
174 HRESULT hr = ActivateController();
175 if (FAILED(hr))
176 return scoped_ptr<base::DictionaryValue>();
178 // Get the host configuration.
179 ScopedBstr host_config;
180 hr = control_->GetConfig(host_config.Receive());
181 if (FAILED(hr))
182 return scoped_ptr<base::DictionaryValue>();
184 // Parse the string into a dictionary.
185 base::string16 file_content(
186 static_cast<BSTR>(host_config), host_config.Length());
187 scoped_ptr<base::Value> config(
188 base::JSONReader::Read(base::UTF16ToUTF8(file_content),
189 base::JSON_ALLOW_TRAILING_COMMAS));
191 if (!config || config->GetType() != base::Value::TYPE_DICTIONARY)
192 return scoped_ptr<base::DictionaryValue>();
194 return scoped_ptr<base::DictionaryValue>(
195 static_cast<base::DictionaryValue*>(config.release()));
198 void DaemonControllerDelegateWin::InstallHost(
199 const DaemonController::CompletionCallback& done) {
200 DoInstallHost(base::Bind(&InvokeCompletionCallback, done));
203 void DaemonControllerDelegateWin::SetConfigAndStart(
204 scoped_ptr<base::DictionaryValue> config,
205 bool consent,
206 const DaemonController::CompletionCallback& done) {
207 DoInstallHost(
208 base::Bind(&DaemonControllerDelegateWin::StartHostWithConfig,
209 base::Unretained(this), base::Passed(&config), consent, done));
212 void DaemonControllerDelegateWin::DoInstallHost(
213 const DaemonInstallerWin::CompletionCallback& done) {
214 // Configure and start the Daemon Controller if it is installed already.
215 HRESULT hr = ActivateElevatedController();
216 if (SUCCEEDED(hr)) {
217 done.Run(S_OK);
218 return;
221 // Otherwise, install it if its COM registration entry is missing.
222 if (hr == CO_E_CLASSSTRING) {
223 DCHECK(!installer_);
225 installer_ = DaemonInstallerWin::Create(
226 GetTopLevelWindow(window_handle_), done);
227 installer_->Install();
228 return;
231 LOG(ERROR) << "Failed to initiate the Chromoting Host installation "
232 << "(error: 0x" << std::hex << hr << std::dec << ").";
233 done.Run(hr);
236 void DaemonControllerDelegateWin::UpdateConfig(
237 scoped_ptr<base::DictionaryValue> config,
238 const DaemonController::CompletionCallback& done) {
239 HRESULT hr = ActivateElevatedController();
240 if (FAILED(hr)) {
241 InvokeCompletionCallback(done, hr);
242 return;
245 // Update the configuration.
246 ScopedBstr config_str(NULL);
247 ConfigToString(*config, &config_str);
248 if (config_str == NULL) {
249 InvokeCompletionCallback(done, E_OUTOFMEMORY);
250 return;
253 // Make sure that the PIN confirmation dialog is focused properly.
254 hr = control_->SetOwnerWindow(
255 reinterpret_cast<LONG_PTR>(GetTopLevelWindow(window_handle_)));
256 if (FAILED(hr)) {
257 InvokeCompletionCallback(done, hr);
258 return;
261 hr = control_->UpdateConfig(config_str);
262 InvokeCompletionCallback(done, hr);
265 void DaemonControllerDelegateWin::Stop(
266 const DaemonController::CompletionCallback& done) {
267 HRESULT hr = ActivateElevatedController();
268 if (SUCCEEDED(hr))
269 hr = control_->StopDaemon();
271 InvokeCompletionCallback(done, hr);
274 void DaemonControllerDelegateWin::SetWindow(void* window_handle) {
275 window_handle_ = reinterpret_cast<HWND>(window_handle);
278 std::string DaemonControllerDelegateWin::GetVersion() {
279 // Configure and start the Daemon Controller if it is installed already.
280 HRESULT hr = ActivateController();
281 if (FAILED(hr))
282 return std::string();
284 // Get the version string.
285 ScopedBstr version;
286 hr = control_->GetVersion(version.Receive());
287 if (FAILED(hr))
288 return std::string();
290 return base::UTF16ToUTF8(
291 base::string16(static_cast<BSTR>(version), version.Length()));
294 DaemonController::UsageStatsConsent
295 DaemonControllerDelegateWin::GetUsageStatsConsent() {
296 DaemonController::UsageStatsConsent consent;
297 consent.supported = true;
298 consent.allowed = false;
299 consent.set_by_policy = false;
301 // Activate the Daemon Controller and see if it supports |IDaemonControl2|.
302 HRESULT hr = ActivateController();
303 if (FAILED(hr)) {
304 // The host is not installed yet. Assume that the user didn't consent to
305 // collecting crash dumps.
306 return consent;
309 if (control2_.get() == NULL) {
310 // The host is installed and does not support crash dump reporting.
311 return consent;
314 // Get the recorded user's consent.
315 BOOL allowed;
316 BOOL set_by_policy;
317 hr = control2_->GetUsageStatsConsent(&allowed, &set_by_policy);
318 if (FAILED(hr)) {
319 // If the user's consent is not recorded yet, assume that the user didn't
320 // consent to collecting crash dumps.
321 return consent;
324 consent.allowed = !!allowed;
325 consent.set_by_policy = !!set_by_policy;
326 return consent;
329 HRESULT DaemonControllerDelegateWin::ActivateController() {
330 if (!control_) {
331 CLSID class_id;
332 HRESULT hr = CLSIDFromProgID(kDaemonController, &class_id);
333 if (FAILED(hr)) {
334 return hr;
337 hr = CoCreateInstance(class_id, NULL, CLSCTX_LOCAL_SERVER,
338 IID_IDaemonControl, control_.ReceiveVoid());
339 if (FAILED(hr)) {
340 return hr;
343 // Ignore the error. IID_IDaemonControl2 is optional.
344 control_.QueryInterface(IID_IDaemonControl2, control2_.ReceiveVoid());
346 // Release |control_| upon expiration of the timeout.
347 release_timer_.reset(new base::OneShotTimer<DaemonControllerDelegateWin>());
348 release_timer_->Start(FROM_HERE,
349 base::TimeDelta::FromSeconds(kUnprivilegedTimeoutSec),
350 this,
351 &DaemonControllerDelegateWin::ReleaseController);
354 return S_OK;
357 HRESULT DaemonControllerDelegateWin::ActivateElevatedController() {
358 // The COM elevation is supported on Vista and above.
359 if (base::win::GetVersion() < base::win::VERSION_VISTA)
360 return ActivateController();
362 // Release an unprivileged instance of the daemon controller if any.
363 if (!control_is_elevated_)
364 ReleaseController();
366 if (!control_) {
367 BIND_OPTS3 bind_options;
368 memset(&bind_options, 0, sizeof(bind_options));
369 bind_options.cbStruct = sizeof(bind_options);
370 bind_options.hwnd = GetTopLevelWindow(window_handle_);
371 bind_options.dwClassContext = CLSCTX_LOCAL_SERVER;
373 HRESULT hr = ::CoGetObject(
374 kDaemonControllerElevationMoniker,
375 &bind_options,
376 IID_IDaemonControl,
377 control_.ReceiveVoid());
378 if (FAILED(hr)) {
379 return hr;
382 // Ignore the error. IID_IDaemonControl2 is optional.
383 control_.QueryInterface(IID_IDaemonControl2, control2_.ReceiveVoid());
385 // Note that we hold a reference to an elevated instance now.
386 control_is_elevated_ = true;
388 // Release |control_| upon expiration of the timeout.
389 release_timer_.reset(new base::OneShotTimer<DaemonControllerDelegateWin>());
390 release_timer_->Start(FROM_HERE,
391 base::TimeDelta::FromSeconds(kPrivilegedTimeoutSec),
392 this,
393 &DaemonControllerDelegateWin::ReleaseController);
396 return S_OK;
399 void DaemonControllerDelegateWin::ReleaseController() {
400 control_.Release();
401 control2_.Release();
402 release_timer_.reset();
403 control_is_elevated_ = false;
406 void DaemonControllerDelegateWin::StartHostWithConfig(
407 scoped_ptr<base::DictionaryValue> config,
408 bool consent,
409 const DaemonController::CompletionCallback& done,
410 HRESULT hr) {
411 installer_.reset();
413 if (FAILED(hr)) {
414 LOG(ERROR) << "Failed to install the Chromoting Host "
415 << "(error: 0x" << std::hex << hr << std::dec << ").";
416 InvokeCompletionCallback(done, hr);
417 return;
420 hr = ActivateElevatedController();
421 if (FAILED(hr)) {
422 InvokeCompletionCallback(done, hr);
423 return;
426 // Record the user's consent.
427 if (control2_) {
428 hr = control2_->SetUsageStatsConsent(consent);
429 if (FAILED(hr)) {
430 InvokeCompletionCallback(done, hr);
431 return;
435 // Set the configuration.
436 ScopedBstr config_str(NULL);
437 ConfigToString(*config, &config_str);
438 if (config_str == NULL) {
439 InvokeCompletionCallback(done, E_OUTOFMEMORY);
440 return;
443 hr = control_->SetOwnerWindow(
444 reinterpret_cast<LONG_PTR>(GetTopLevelWindow(window_handle_)));
445 if (FAILED(hr)) {
446 InvokeCompletionCallback(done, hr);
447 return;
450 hr = control_->SetConfig(config_str);
451 if (FAILED(hr)) {
452 InvokeCompletionCallback(done, hr);
453 return;
456 // Start daemon.
457 hr = control_->StartDaemon();
458 InvokeCompletionCallback(done, hr);
461 scoped_refptr<DaemonController> DaemonController::Create() {
462 scoped_ptr<DaemonController::Delegate> delegate(
463 new DaemonControllerDelegateWin());
464 return new DaemonController(delegate.Pass());
467 } // namespace remoting