Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / remoting / host / setup / daemon_controller_delegate_win.cc
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