Home | History | Annotate | Download | only in setup
      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(
    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";
    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, &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