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"
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
;
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
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
) {
67 return DaemonController::STATE_STARTED
;
69 case SERVICE_CONTINUE_PENDING
:
70 case SERVICE_START_PENDING
:
71 return DaemonController::STATE_STARTING
;
74 case SERVICE_PAUSE_PENDING
:
75 case SERVICE_STOP_PENDING
:
76 return DaemonController::STATE_STOPPING
;
81 return DaemonController::STATE_STOPPED
;
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";
101 ScopedScHandle
service(::OpenServiceW(scmanager
.Get(), kWindowsServiceName
,
102 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
112 service_out
->Set(service
.Take());
113 return ERROR_SUCCESS
;
116 DaemonController::AsyncResult
HResultToAsyncResult(
119 return DaemonController::RESULT_OK
;
120 } else if (hr
== HRESULT_FROM_WIN32(ERROR_CANCELLED
)) {
121 return DaemonController::RESULT_CANCELLED
;
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
));
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
);
154 case ERROR_SUCCESS
: {
155 SERVICE_STATUS status
;
156 if (::QueryServiceStatus(service
.Get(), &status
)) {
157 return ConvertToDaemonState(status
.dwCurrentState
);
159 PLOG(ERROR
) << "Failed to query the state of the '"
160 << kWindowsServiceName
<< "' service";
161 return DaemonController::STATE_UNKNOWN
;
165 case ERROR_SERVICE_DOES_NOT_EXIST
:
166 return DaemonController::STATE_NOT_INSTALLED
;
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();
178 // Get the host configuration.
179 ScopedBstr host_config
;
180 hr
= control_
->GetConfig(host_config
.Receive());
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
->IsType(base::Value::TYPE_DICTIONARY
))
194 return make_scoped_ptr(static_cast<base::DictionaryValue
*>(config
.release()));
197 void DaemonControllerDelegateWin::InstallHost(
198 const DaemonController::CompletionCallback
& done
) {
199 DoInstallHost(base::Bind(&InvokeCompletionCallback
, done
));
202 void DaemonControllerDelegateWin::SetConfigAndStart(
203 scoped_ptr
<base::DictionaryValue
> config
,
205 const DaemonController::CompletionCallback
& done
) {
207 base::Bind(&DaemonControllerDelegateWin::StartHostWithConfig
,
208 base::Unretained(this), base::Passed(&config
), consent
, done
));
211 void DaemonControllerDelegateWin::DoInstallHost(
212 const DaemonInstallerWin::CompletionCallback
& done
) {
213 // Configure and start the Daemon Controller if it is installed already.
214 HRESULT hr
= ActivateElevatedController();
220 // Otherwise, install it if its COM registration entry is missing.
221 if (hr
== CO_E_CLASSSTRING
) {
224 installer_
= DaemonInstallerWin::Create(
225 GetTopLevelWindow(window_handle_
), done
);
226 installer_
->Install();
230 LOG(ERROR
) << "Failed to initiate the Chromoting Host installation "
231 << "(error: 0x" << std::hex
<< hr
<< std::dec
<< ").";
235 void DaemonControllerDelegateWin::UpdateConfig(
236 scoped_ptr
<base::DictionaryValue
> config
,
237 const DaemonController::CompletionCallback
& done
) {
238 HRESULT hr
= ActivateElevatedController();
240 InvokeCompletionCallback(done
, hr
);
244 // Update the configuration.
245 ScopedBstr
config_str(NULL
);
246 ConfigToString(*config
, &config_str
);
247 if (config_str
== NULL
) {
248 InvokeCompletionCallback(done
, E_OUTOFMEMORY
);
252 // Make sure that the PIN confirmation dialog is focused properly.
253 hr
= control_
->SetOwnerWindow(
254 reinterpret_cast<LONG_PTR
>(GetTopLevelWindow(window_handle_
)));
256 InvokeCompletionCallback(done
, hr
);
260 hr
= control_
->UpdateConfig(config_str
);
261 InvokeCompletionCallback(done
, hr
);
264 void DaemonControllerDelegateWin::Stop(
265 const DaemonController::CompletionCallback
& done
) {
266 HRESULT hr
= ActivateElevatedController();
268 hr
= control_
->StopDaemon();
270 InvokeCompletionCallback(done
, hr
);
273 void DaemonControllerDelegateWin::SetWindow(void* window_handle
) {
274 window_handle_
= reinterpret_cast<HWND
>(window_handle
);
277 std::string
DaemonControllerDelegateWin::GetVersion() {
278 // Configure and start the Daemon Controller if it is installed already.
279 HRESULT hr
= ActivateController();
281 return std::string();
283 // Get the version string.
285 hr
= control_
->GetVersion(version
.Receive());
287 return std::string();
289 return base::UTF16ToUTF8(
290 base::string16(static_cast<BSTR
>(version
), version
.Length()));
293 DaemonController::UsageStatsConsent
294 DaemonControllerDelegateWin::GetUsageStatsConsent() {
295 DaemonController::UsageStatsConsent consent
;
296 consent
.supported
= true;
297 consent
.allowed
= false;
298 consent
.set_by_policy
= false;
300 // Activate the Daemon Controller and see if it supports |IDaemonControl2|.
301 HRESULT hr
= ActivateController();
303 // The host is not installed yet. Assume that the user didn't consent to
304 // collecting crash dumps.
308 if (control2_
.get() == NULL
) {
309 // The host is installed and does not support crash dump reporting.
313 // Get the recorded user's consent.
316 hr
= control2_
->GetUsageStatsConsent(&allowed
, &set_by_policy
);
318 // If the user's consent is not recorded yet, assume that the user didn't
319 // consent to collecting crash dumps.
323 consent
.allowed
= !!allowed
;
324 consent
.set_by_policy
= !!set_by_policy
;
328 HRESULT
DaemonControllerDelegateWin::ActivateController() {
329 if (!control_
.get()) {
331 HRESULT hr
= CLSIDFromProgID(kDaemonController
, &class_id
);
336 hr
= CoCreateInstance(class_id
, NULL
, CLSCTX_LOCAL_SERVER
,
337 IID_IDaemonControl
, control_
.ReceiveVoid());
342 // Ignore the error. IID_IDaemonControl2 is optional.
343 control_
.QueryInterface(IID_IDaemonControl2
, control2_
.ReceiveVoid());
345 // Release |control_| upon expiration of the timeout.
346 release_timer_
.reset(new base::OneShotTimer
<DaemonControllerDelegateWin
>());
347 release_timer_
->Start(FROM_HERE
,
348 base::TimeDelta::FromSeconds(kUnprivilegedTimeoutSec
),
350 &DaemonControllerDelegateWin::ReleaseController
);
356 HRESULT
DaemonControllerDelegateWin::ActivateElevatedController() {
357 // The COM elevation is supported on Vista and above.
358 if (base::win::GetVersion() < base::win::VERSION_VISTA
)
359 return ActivateController();
361 // Release an unprivileged instance of the daemon controller if any.
362 if (!control_is_elevated_
)
365 if (!control_
.get()) {
366 BIND_OPTS3 bind_options
;
367 memset(&bind_options
, 0, sizeof(bind_options
));
368 bind_options
.cbStruct
= sizeof(bind_options
);
369 bind_options
.hwnd
= GetTopLevelWindow(window_handle_
);
370 bind_options
.dwClassContext
= CLSCTX_LOCAL_SERVER
;
372 HRESULT hr
= ::CoGetObject(
373 kDaemonControllerElevationMoniker
,
376 control_
.ReceiveVoid());
381 // Ignore the error. IID_IDaemonControl2 is optional.
382 control_
.QueryInterface(IID_IDaemonControl2
, control2_
.ReceiveVoid());
384 // Note that we hold a reference to an elevated instance now.
385 control_is_elevated_
= true;
387 // Release |control_| upon expiration of the timeout.
388 release_timer_
.reset(new base::OneShotTimer
<DaemonControllerDelegateWin
>());
389 release_timer_
->Start(FROM_HERE
,
390 base::TimeDelta::FromSeconds(kPrivilegedTimeoutSec
),
392 &DaemonControllerDelegateWin::ReleaseController
);
398 void DaemonControllerDelegateWin::ReleaseController() {
401 release_timer_
.reset();
402 control_is_elevated_
= false;
405 void DaemonControllerDelegateWin::StartHostWithConfig(
406 scoped_ptr
<base::DictionaryValue
> config
,
408 const DaemonController::CompletionCallback
& done
,
413 LOG(ERROR
) << "Failed to install the Chromoting Host "
414 << "(error: 0x" << std::hex
<< hr
<< std::dec
<< ").";
415 InvokeCompletionCallback(done
, hr
);
419 hr
= ActivateElevatedController();
421 InvokeCompletionCallback(done
, hr
);
425 // Record the user's consent.
426 if (control2_
.get()) {
427 hr
= control2_
->SetUsageStatsConsent(consent
);
429 InvokeCompletionCallback(done
, hr
);
434 // Set the configuration.
435 ScopedBstr
config_str(NULL
);
436 ConfigToString(*config
, &config_str
);
437 if (config_str
== NULL
) {
438 InvokeCompletionCallback(done
, E_OUTOFMEMORY
);
442 hr
= control_
->SetOwnerWindow(
443 reinterpret_cast<LONG_PTR
>(GetTopLevelWindow(window_handle_
)));
445 InvokeCompletionCallback(done
, hr
);
449 hr
= control_
->SetConfig(config_str
);
451 InvokeCompletionCallback(done
, hr
);
456 hr
= control_
->StartDaemon();
457 InvokeCompletionCallback(done
, hr
);
460 scoped_refptr
<DaemonController
> DaemonController::Create() {
461 scoped_ptr
<DaemonController::Delegate
> delegate(
462 new DaemonControllerDelegateWin());
463 return new DaemonController(delegate
.Pass());
466 } // namespace remoting