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
29using base::win::ScopedBstr;
30using base::win::ScopedComPtr;
31
32namespace remoting {
33
34namespace {
35
36// ProgID of the daemon controller.
37const wchar_t kDaemonController[] =
38    L"ChromotingElevatedController.ElevatedController";
39
40// The COM elevation moniker for the Elevated Controller.
41const 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.
48const 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.
55const int kUnprivilegedTimeoutSec = 60;
56
57void 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
64DaemonController::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
90DWORD 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
116DaemonController::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
129void InvokeCompletionCallback(
130    const DaemonController::CompletionCallback& done, HRESULT hr) {
131  done.Run(HResultToAsyncResult(hr));
132}
133
134}  // namespace
135
136DaemonControllerDelegateWin::DaemonControllerDelegateWin()
137    : control_is_elevated_(false),
138      window_handle_(NULL) {
139}
140
141DaemonControllerDelegateWin::~DaemonControllerDelegateWin() {
142}
143
144DaemonController::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
172scoped_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
198void DaemonControllerDelegateWin::InstallHost(
199    const DaemonController::CompletionCallback& done) {
200  DoInstallHost(base::Bind(&InvokeCompletionCallback, done));
201}
202
203void 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
212void 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
236void 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
265void 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
274void DaemonControllerDelegateWin::SetWindow(void* window_handle) {
275  window_handle_ = reinterpret_cast<HWND>(window_handle);
276}
277
278std::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
294DaemonController::UsageStatsConsent
295DaemonControllerDelegateWin::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
329HRESULT 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
357HRESULT 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
399void DaemonControllerDelegateWin::ReleaseController() {
400  control_.Release();
401  control2_.Release();
402  release_timer_.reset();
403  control_is_elevated_ = false;
404}
405
406void 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
461scoped_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