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/setup/daemon_installer_win.h"
28 #include "remoting/host/usage_stats_consent.h"
30 using base::win::ScopedBstr
;
31 using base::win::ScopedComPtr
;
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
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
) {
68 return DaemonController::STATE_STARTED
;
70 case SERVICE_CONTINUE_PENDING
:
71 case SERVICE_START_PENDING
:
72 return DaemonController::STATE_STARTING
;
75 case SERVICE_PAUSE_PENDING
:
76 case SERVICE_STOP_PENDING
:
77 return DaemonController::STATE_STOPPING
;
82 return DaemonController::STATE_STOPPED
;
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";
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";
114 service_out
->Set(service
.Take());
115 return ERROR_SUCCESS
;
118 DaemonController::AsyncResult
HResultToAsyncResult(
121 return DaemonController::RESULT_OK
;
122 } else if (hr
== HRESULT_FROM_WIN32(ERROR_CANCELLED
)) {
123 return DaemonController::RESULT_CANCELLED
;
125 // TODO(sergeyu): Report other errors to the webapp once it knows
126 // how to handle them.
127 return DaemonController::RESULT_FAILED
;
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
);
151 case ERROR_SUCCESS
: {
152 SERVICE_STATUS status
;
153 if (::QueryServiceStatus(service
, &status
)) {
154 return ConvertToDaemonState(status
.dwCurrentState
);
156 LOG_GETLASTERROR(ERROR
)
157 << "Failed to query the state of the '" << kWindowsServiceName
159 return DaemonController::STATE_UNKNOWN
;
163 case ERROR_SERVICE_DOES_NOT_EXIST
:
164 return DaemonController::STATE_NOT_INSTALLED
;
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();
174 return scoped_ptr
<base::DictionaryValue
>();
176 // Get the host configuration.
177 ScopedBstr host_config
;
178 hr
= control_
->GetConfig(host_config
.Receive());
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
,
199 const DaemonController::CompletionCallback
& done
) {
200 // Configure and start the Daemon Controller if it is installed already.
201 HRESULT hr
= ActivateElevatedController();
203 OnInstallationComplete(config
.Pass(), consent
, done
, S_OK
);
207 // Otherwise, install it if its COM registration entry is missing.
208 if (hr
== CO_E_CLASSSTRING
) {
211 installer_
= DaemonInstallerWin::Create(
212 GetTopLevelWindow(window_handle_
),
213 base::Bind(&DaemonControllerDelegateWin::OnInstallationComplete
,
214 base::Unretained(this),
215 base::Passed(&config
),
218 installer_
->Install();
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();
232 done
.Run(HResultToAsyncResult(hr
));
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
));
244 // Make sure that the PIN confirmation dialog is focused properly.
245 hr
= control_
->SetOwnerWindow(
246 reinterpret_cast<LONG_PTR
>(GetTopLevelWindow(window_handle_
)));
248 done
.Run(HResultToAsyncResult(hr
));
252 hr
= control_
->UpdateConfig(config_str
);
253 done
.Run(HResultToAsyncResult(hr
));
256 void DaemonControllerDelegateWin::Stop(
257 const DaemonController::CompletionCallback
& done
) {
258 HRESULT hr
= ActivateElevatedController();
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();
273 return std::string();
275 // Get the version string.
277 hr
= control_
->GetVersion(version
.Receive());
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();
295 // The host is not installed yet. Assume that the user didn't consent to
296 // collecting crash dumps.
300 if (control2_
.get() == NULL
) {
301 // The host is installed and does not support crash dump reporting.
305 // Get the recorded user's consent.
308 hr
= control2_
->GetUsageStatsConsent(&allowed
, &set_by_policy
);
310 // If the user's consent is not recorded yet, assume that the user didn't
311 // consent to collecting crash dumps.
315 consent
.allowed
= !!allowed
;
316 consent
.set_by_policy
= !!set_by_policy
;
320 HRESULT
DaemonControllerDelegateWin::ActivateController() {
323 HRESULT hr
= CLSIDFromProgID(kDaemonController
, &class_id
);
328 hr
= CoCreateInstance(class_id
, NULL
, CLSCTX_LOCAL_SERVER
,
329 IID_IDaemonControl
, control_
.ReceiveVoid());
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
),
342 &DaemonControllerDelegateWin::ReleaseController
);
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_
)
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
,
368 control_
.ReceiveVoid());
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
),
384 &DaemonControllerDelegateWin::ReleaseController
);
390 void DaemonControllerDelegateWin::ReleaseController() {
393 release_timer_
.reset();
394 control_is_elevated_
= false;
397 void DaemonControllerDelegateWin::OnInstallationComplete(
398 scoped_ptr
<base::DictionaryValue
> config
,
400 const DaemonController::CompletionCallback
& done
,
405 LOG(ERROR
) << "Failed to install the Chromoting Host "
406 << "(error: 0x" << std::hex
<< hr
<< std::dec
<< ").";
407 done
.Run(HResultToAsyncResult(hr
));
411 hr
= ActivateElevatedController();
413 done
.Run(HResultToAsyncResult(hr
));
417 // Record the user's consent.
419 hr
= control2_
->SetUsageStatsConsent(consent
);
421 done
.Run(HResultToAsyncResult(hr
));
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
));
434 hr
= control_
->SetOwnerWindow(
435 reinterpret_cast<LONG_PTR
>(GetTopLevelWindow(window_handle_
)));
437 done
.Run(HResultToAsyncResult(hr
));
441 hr
= control_
->SetConfig(config_str
);
443 done
.Run(HResultToAsyncResult(hr
));
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