Add explicit |forceOnlineSignin| to user pod status
[chromium-blink-merge.git] / remoting / host / setup / daemon_controller_delegate_win.cc
blob2070c49a124146be5da49bac30de6ff2e55e6a54
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/setup/daemon_installer_win.h"
28 #include "remoting/host/usage_stats_consent.h"
30 using base::win::ScopedBstr;
31 using base::win::ScopedComPtr;
33 namespace remoting {
35 namespace {
37 // ProgID of the daemon controller.
38 const wchar_t kDaemonController[] =
39 L"ChromotingElevatedController.ElevatedController";
41 // The COM elevation moniker for the Elevated Controller.
42 const wchar_t kDaemonControllerElevationMoniker[] =
43 L"Elevation:Administrator!new:"
44 L"ChromotingElevatedController.ElevatedController";
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;
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;
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(base::UTF8ToUTF16(config_str).c_str());
62 out->Swap(config_scoped_bstr);
65 DaemonController::State ConvertToDaemonState(DWORD service_state) {
66 switch (service_state) {
67 case SERVICE_RUNNING:
68 return DaemonController::STATE_STARTED;
70 case SERVICE_CONTINUE_PENDING:
71 case SERVICE_START_PENDING:
72 return DaemonController::STATE_STARTING;
73 break;
75 case SERVICE_PAUSE_PENDING:
76 case SERVICE_STOP_PENDING:
77 return DaemonController::STATE_STOPPING;
78 break;
80 case SERVICE_PAUSED:
81 case SERVICE_STOPPED:
82 return DaemonController::STATE_STOPPED;
83 break;
85 default:
86 NOTREACHED();
87 return DaemonController::STATE_UNKNOWN;
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;
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";
111 return error;
114 service_out->Set(service.Take());
115 return ERROR_SUCCESS;
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;
131 } // namespace
133 DaemonControllerDelegateWin::DaemonControllerDelegateWin()
134 : control_is_elevated_(false),
135 window_handle_(NULL) {
138 DaemonControllerDelegateWin::~DaemonControllerDelegateWin() {
141 DaemonController::State DaemonControllerDelegateWin::GetState() {
142 if (base::win::GetVersion() < base::win::VERSION_XP) {
143 return DaemonController::STATE_NOT_IMPLEMENTED;
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);
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;
161 break;
163 case ERROR_SERVICE_DOES_NOT_EXIST:
164 return DaemonController::STATE_NOT_INSTALLED;
165 default:
166 return DaemonController::STATE_UNKNOWN;
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>();
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>();
182 // Parse the string into a dictionary.
183 base::string16 file_content(
184 static_cast<BSTR>(host_config), host_config.Length());
185 scoped_ptr<base::Value> config(
186 base::JSONReader::Read(base::UTF16ToUTF8(file_content),
187 base::JSON_ALLOW_TRAILING_COMMAS));
189 if (!config || config->GetType() != base::Value::TYPE_DICTIONARY)
190 return scoped_ptr<base::DictionaryValue>();
192 return scoped_ptr<base::DictionaryValue>(
193 static_cast<base::DictionaryValue*>(config.release()));
196 void DaemonControllerDelegateWin::SetConfigAndStart(
197 scoped_ptr<base::DictionaryValue> config,
198 bool consent,
199 const DaemonController::CompletionCallback& done) {
200 // Configure and start the Daemon Controller if it is installed already.
201 HRESULT hr = ActivateElevatedController();
202 if (SUCCEEDED(hr)) {
203 OnInstallationComplete(config.Pass(), consent, done, S_OK);
204 return;
207 // Otherwise, install it if its COM registration entry is missing.
208 if (hr == CO_E_CLASSSTRING) {
209 DCHECK(!installer_);
211 installer_ = DaemonInstallerWin::Create(
212 GetTopLevelWindow(window_handle_),
213 base::Bind(&DaemonControllerDelegateWin::OnInstallationComplete,
214 base::Unretained(this),
215 base::Passed(&config),
216 consent,
217 done));
218 installer_->Install();
219 return;
222 LOG(ERROR) << "Failed to initiate the Chromoting Host installation "
223 << "(error: 0x" << std::hex << hr << std::dec << ").";
224 done.Run(HResultToAsyncResult(hr));
227 void DaemonControllerDelegateWin::UpdateConfig(
228 scoped_ptr<base::DictionaryValue> config,
229 const DaemonController::CompletionCallback& done) {
230 HRESULT hr = ActivateElevatedController();
231 if (FAILED(hr)) {
232 done.Run(HResultToAsyncResult(hr));
233 return;
236 // Update the configuration.
237 ScopedBstr config_str(NULL);
238 ConfigToString(*config, &config_str);
239 if (config_str == NULL) {
240 done.Run(HResultToAsyncResult(E_OUTOFMEMORY));
241 return;
244 // Make sure that the PIN confirmation dialog is focused properly.
245 hr = control_->SetOwnerWindow(
246 reinterpret_cast<LONG_PTR>(GetTopLevelWindow(window_handle_)));
247 if (FAILED(hr)) {
248 done.Run(HResultToAsyncResult(hr));
249 return;
252 hr = control_->UpdateConfig(config_str);
253 done.Run(HResultToAsyncResult(hr));
256 void DaemonControllerDelegateWin::Stop(
257 const DaemonController::CompletionCallback& done) {
258 HRESULT hr = ActivateElevatedController();
259 if (SUCCEEDED(hr))
260 hr = control_->StopDaemon();
262 done.Run(HResultToAsyncResult(hr));
265 void DaemonControllerDelegateWin::SetWindow(void* window_handle) {
266 window_handle_ = reinterpret_cast<HWND>(window_handle);
269 std::string DaemonControllerDelegateWin::GetVersion() {
270 // Configure and start the Daemon Controller if it is installed already.
271 HRESULT hr = ActivateController();
272 if (FAILED(hr))
273 return std::string();
275 // Get the version string.
276 ScopedBstr version;
277 hr = control_->GetVersion(version.Receive());
278 if (FAILED(hr))
279 return std::string();
281 return base::UTF16ToUTF8(
282 base::string16(static_cast<BSTR>(version), version.Length()));
285 DaemonController::UsageStatsConsent
286 DaemonControllerDelegateWin::GetUsageStatsConsent() {
287 DaemonController::UsageStatsConsent consent;
288 consent.supported = true;
289 consent.allowed = false;
290 consent.set_by_policy = false;
292 // Activate the Daemon Controller and see if it supports |IDaemonControl2|.
293 HRESULT hr = ActivateController();
294 if (FAILED(hr)) {
295 // The host is not installed yet. Assume that the user didn't consent to
296 // collecting crash dumps.
297 return consent;
300 if (control2_.get() == NULL) {
301 // The host is installed and does not support crash dump reporting.
302 return consent;
305 // Get the recorded user's consent.
306 BOOL allowed;
307 BOOL set_by_policy;
308 hr = control2_->GetUsageStatsConsent(&allowed, &set_by_policy);
309 if (FAILED(hr)) {
310 // If the user's consent is not recorded yet, assume that the user didn't
311 // consent to collecting crash dumps.
312 return consent;
315 consent.allowed = !!allowed;
316 consent.set_by_policy = !!set_by_policy;
317 return consent;
320 HRESULT DaemonControllerDelegateWin::ActivateController() {
321 if (!control_) {
322 CLSID class_id;
323 HRESULT hr = CLSIDFromProgID(kDaemonController, &class_id);
324 if (FAILED(hr)) {
325 return hr;
328 hr = CoCreateInstance(class_id, NULL, CLSCTX_LOCAL_SERVER,
329 IID_IDaemonControl, control_.ReceiveVoid());
330 if (FAILED(hr)) {
331 return hr;
334 // Ignore the error. IID_IDaemonControl2 is optional.
335 control_.QueryInterface(IID_IDaemonControl2, control2_.ReceiveVoid());
337 // Release |control_| upon expiration of the timeout.
338 release_timer_.reset(new base::OneShotTimer<DaemonControllerDelegateWin>());
339 release_timer_->Start(FROM_HERE,
340 base::TimeDelta::FromSeconds(kUnprivilegedTimeoutSec),
341 this,
342 &DaemonControllerDelegateWin::ReleaseController);
345 return S_OK;
348 HRESULT DaemonControllerDelegateWin::ActivateElevatedController() {
349 // The COM elevation is supported on Vista and above.
350 if (base::win::GetVersion() < base::win::VERSION_VISTA)
351 return ActivateController();
353 // Release an unprivileged instance of the daemon controller if any.
354 if (!control_is_elevated_)
355 ReleaseController();
357 if (!control_) {
358 BIND_OPTS3 bind_options;
359 memset(&bind_options, 0, sizeof(bind_options));
360 bind_options.cbStruct = sizeof(bind_options);
361 bind_options.hwnd = GetTopLevelWindow(window_handle_);
362 bind_options.dwClassContext = CLSCTX_LOCAL_SERVER;
364 HRESULT hr = ::CoGetObject(
365 kDaemonControllerElevationMoniker,
366 &bind_options,
367 IID_IDaemonControl,
368 control_.ReceiveVoid());
369 if (FAILED(hr)) {
370 return hr;
373 // Ignore the error. IID_IDaemonControl2 is optional.
374 control_.QueryInterface(IID_IDaemonControl2, control2_.ReceiveVoid());
376 // Note that we hold a reference to an elevated instance now.
377 control_is_elevated_ = true;
379 // Release |control_| upon expiration of the timeout.
380 release_timer_.reset(new base::OneShotTimer<DaemonControllerDelegateWin>());
381 release_timer_->Start(FROM_HERE,
382 base::TimeDelta::FromSeconds(kPrivilegedTimeoutSec),
383 this,
384 &DaemonControllerDelegateWin::ReleaseController);
387 return S_OK;
390 void DaemonControllerDelegateWin::ReleaseController() {
391 control_.Release();
392 control2_.Release();
393 release_timer_.reset();
394 control_is_elevated_ = false;
397 void DaemonControllerDelegateWin::OnInstallationComplete(
398 scoped_ptr<base::DictionaryValue> config,
399 bool consent,
400 const DaemonController::CompletionCallback& done,
401 HRESULT hr) {
402 installer_.reset();
404 if (FAILED(hr)) {
405 LOG(ERROR) << "Failed to install the Chromoting Host "
406 << "(error: 0x" << std::hex << hr << std::dec << ").";
407 done.Run(HResultToAsyncResult(hr));
408 return;
411 hr = ActivateElevatedController();
412 if (FAILED(hr)) {
413 done.Run(HResultToAsyncResult(hr));
414 return;
417 // Record the user's consent.
418 if (control2_) {
419 hr = control2_->SetUsageStatsConsent(consent);
420 if (FAILED(hr)) {
421 done.Run(HResultToAsyncResult(hr));
422 return;
426 // Set the configuration.
427 ScopedBstr config_str(NULL);
428 ConfigToString(*config, &config_str);
429 if (config_str == NULL) {
430 done.Run(HResultToAsyncResult(E_OUTOFMEMORY));
431 return;
434 hr = control_->SetOwnerWindow(
435 reinterpret_cast<LONG_PTR>(GetTopLevelWindow(window_handle_)));
436 if (FAILED(hr)) {
437 done.Run(HResultToAsyncResult(hr));
438 return;
441 hr = control_->SetConfig(config_str);
442 if (FAILED(hr)) {
443 done.Run(HResultToAsyncResult(hr));
444 return;
447 // Start daemon.
448 hr = control_->StartDaemon();
449 done.Run(HResultToAsyncResult(hr));
452 scoped_refptr<DaemonController> DaemonController::Create() {
453 scoped_ptr<DaemonController::Delegate> delegate(
454 new DaemonControllerDelegateWin());
455 return new DaemonController(delegate.Pass());
458 } // namespace remoting