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. 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/usage_stats_consent.h" 28 29 using base::win::ScopedBstr; 30 using base::win::ScopedComPtr; 31 32 namespace remoting { 33 34 namespace { 35 36 // ProgID of the daemon controller. 37 const wchar_t kDaemonController[] = 38 L"ChromotingElevatedController.ElevatedController"; 39 40 // The COM elevation moniker for the Elevated Controller. 41 const wchar_t kDaemonControllerElevationMoniker[] = 42 L"Elevation:Administrator!new:" 43 L"ChromotingElevatedController.ElevatedController"; 44 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; 49 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; 56 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); 62 } 63 64 DaemonController::State ConvertToDaemonState(DWORD service_state) { 65 switch (service_state) { 66 case SERVICE_RUNNING: 67 return DaemonController::STATE_STARTED; 68 69 case SERVICE_CONTINUE_PENDING: 70 case SERVICE_START_PENDING: 71 return DaemonController::STATE_STARTING; 72 break; 73 74 case SERVICE_PAUSE_PENDING: 75 case SERVICE_STOP_PENDING: 76 return DaemonController::STATE_STOPPING; 77 break; 78 79 case SERVICE_PAUSED: 80 case SERVICE_STOPPED: 81 return DaemonController::STATE_STOPPED; 82 break; 83 84 default: 85 NOTREACHED(); 86 return DaemonController::STATE_UNKNOWN; 87 } 88 } 89 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; 99 } 100 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 107 << "' service"; 108 } 109 return error; 110 } 111 112 service_out->Set(service.Take()); 113 return ERROR_SUCCESS; 114 } 115 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; 126 } 127 } 128 129 void InvokeCompletionCallback( 130 const DaemonController::CompletionCallback& done, HRESULT hr) { 131 done.Run(HResultToAsyncResult(hr)); 132 } 133 134 } // namespace 135 136 DaemonControllerDelegateWin::DaemonControllerDelegateWin() 137 : control_is_elevated_(false), 138 window_handle_(NULL) { 139 } 140 141 DaemonControllerDelegateWin::~DaemonControllerDelegateWin() { 142 } 143 144 DaemonController::State DaemonControllerDelegateWin::GetState() { 145 if (base::win::GetVersion() < base::win::VERSION_XP) { 146 return DaemonController::STATE_NOT_IMPLEMENTED; 147 } 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); 152 153 switch (error) { 154 case ERROR_SUCCESS: { 155 SERVICE_STATUS status; 156 if (::QueryServiceStatus(service.Get(), &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; 162 } 163 break; 164 } 165 case ERROR_SERVICE_DOES_NOT_EXIST: 166 return DaemonController::STATE_NOT_INSTALLED; 167 default: 168 return DaemonController::STATE_UNKNOWN; 169 } 170 } 171 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>(); 177 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>(); 183 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)); 190 191 if (!config || config->GetType() != base::Value::TYPE_DICTIONARY) 192 return scoped_ptr<base::DictionaryValue>(); 193 194 return scoped_ptr<base::DictionaryValue>( 195 static_cast<base::DictionaryValue*>(config.release())); 196 } 197 198 void DaemonControllerDelegateWin::InstallHost( 199 const DaemonController::CompletionCallback& done) { 200 DoInstallHost(base::Bind(&InvokeCompletionCallback, done)); 201 } 202 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)); 210 } 211 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; 219 } 220 221 // Otherwise, install it if its COM registration entry is missing. 222 if (hr == CO_E_CLASSSTRING) { 223 DCHECK(!installer_); 224 225 installer_ = DaemonInstallerWin::Create( 226 GetTopLevelWindow(window_handle_), done); 227 installer_->Install(); 228 return; 229 } 230 231 LOG(ERROR) << "Failed to initiate the Chromoting Host installation " 232 << "(error: 0x" << std::hex << hr << std::dec << ")."; 233 done.Run(hr); 234 } 235 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; 243 } 244 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; 251 } 252 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; 259 } 260 261 hr = control_->UpdateConfig(config_str); 262 InvokeCompletionCallback(done, hr); 263 } 264 265 void DaemonControllerDelegateWin::Stop( 266 const DaemonController::CompletionCallback& done) { 267 HRESULT hr = ActivateElevatedController(); 268 if (SUCCEEDED(hr)) 269 hr = control_->StopDaemon(); 270 271 InvokeCompletionCallback(done, hr); 272 } 273 274 void DaemonControllerDelegateWin::SetWindow(void* window_handle) { 275 window_handle_ = reinterpret_cast<HWND>(window_handle); 276 } 277 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(); 283 284 // Get the version string. 285 ScopedBstr version; 286 hr = control_->GetVersion(version.Receive()); 287 if (FAILED(hr)) 288 return std::string(); 289 290 return base::UTF16ToUTF8( 291 base::string16(static_cast<BSTR>(version), version.Length())); 292 } 293 294 DaemonController::UsageStatsConsent 295 DaemonControllerDelegateWin::GetUsageStatsConsent() { 296 DaemonController::UsageStatsConsent consent; 297 consent.supported = true; 298 consent.allowed = false; 299 consent.set_by_policy = false; 300 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; 307 } 308 309 if (control2_.get() == NULL) { 310 // The host is installed and does not support crash dump reporting. 311 return consent; 312 } 313 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; 322 } 323 324 consent.allowed = !!allowed; 325 consent.set_by_policy = !!set_by_policy; 326 return consent; 327 } 328 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; 335 } 336 337 hr = CoCreateInstance(class_id, NULL, CLSCTX_LOCAL_SERVER, 338 IID_IDaemonControl, control_.ReceiveVoid()); 339 if (FAILED(hr)) { 340 return hr; 341 } 342 343 // Ignore the error. IID_IDaemonControl2 is optional. 344 control_.QueryInterface(IID_IDaemonControl2, control2_.ReceiveVoid()); 345 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); 352 } 353 354 return S_OK; 355 } 356 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(); 361 362 // Release an unprivileged instance of the daemon controller if any. 363 if (!control_is_elevated_) 364 ReleaseController(); 365 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; 372 373 HRESULT hr = ::CoGetObject( 374 kDaemonControllerElevationMoniker, 375 &bind_options, 376 IID_IDaemonControl, 377 control_.ReceiveVoid()); 378 if (FAILED(hr)) { 379 return hr; 380 } 381 382 // Ignore the error. IID_IDaemonControl2 is optional. 383 control_.QueryInterface(IID_IDaemonControl2, control2_.ReceiveVoid()); 384 385 // Note that we hold a reference to an elevated instance now. 386 control_is_elevated_ = true; 387 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); 394 } 395 396 return S_OK; 397 } 398 399 void DaemonControllerDelegateWin::ReleaseController() { 400 control_.Release(); 401 control2_.Release(); 402 release_timer_.reset(); 403 control_is_elevated_ = false; 404 } 405 406 void DaemonControllerDelegateWin::StartHostWithConfig( 407 scoped_ptr<base::DictionaryValue> config, 408 bool consent, 409 const DaemonController::CompletionCallback& done, 410 HRESULT hr) { 411 installer_.reset(); 412 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; 418 } 419 420 hr = ActivateElevatedController(); 421 if (FAILED(hr)) { 422 InvokeCompletionCallback(done, hr); 423 return; 424 } 425 426 // Record the user's consent. 427 if (control2_) { 428 hr = control2_->SetUsageStatsConsent(consent); 429 if (FAILED(hr)) { 430 InvokeCompletionCallback(done, hr); 431 return; 432 } 433 } 434 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; 441 } 442 443 hr = control_->SetOwnerWindow( 444 reinterpret_cast<LONG_PTR>(GetTopLevelWindow(window_handle_))); 445 if (FAILED(hr)) { 446 InvokeCompletionCallback(done, hr); 447 return; 448 } 449 450 hr = control_->SetConfig(config_str); 451 if (FAILED(hr)) { 452 InvokeCompletionCallback(done, hr); 453 return; 454 } 455 456 // Start daemon. 457 hr = control_->StartDaemon(); 458 InvokeCompletionCallback(done, hr); 459 } 460 461 scoped_refptr<DaemonController> DaemonController::Create() { 462 scoped_ptr<DaemonController::Delegate> delegate( 463 new DaemonControllerDelegateWin()); 464 return new DaemonController(delegate.Pass()); 465 } 466 467 } // namespace remoting 468