Upstream version 7.36.149.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     LOG_GETLASTERROR(ERROR)
98         << "Failed to connect to the service control manager";
99     return error;
100   }
101
102   ScopedScHandle service(
103       ::OpenServiceW(scmanager, kWindowsServiceName, SERVICE_QUERY_STATUS));
104   if (!service.IsValid()) {
105     DWORD error = GetLastError();
106     if (error != ERROR_SERVICE_DOES_NOT_EXIST) {
107       LOG_GETLASTERROR(ERROR)
108           << "Failed to open to the '" << kWindowsServiceName << "' service";
109     }
110     return error;
111   }
112
113   service_out->Set(service.Take());
114   return ERROR_SUCCESS;
115 }
116
117 DaemonController::AsyncResult HResultToAsyncResult(
118     HRESULT hr) {
119   if (SUCCEEDED(hr)) {
120     return DaemonController::RESULT_OK;
121   } else if (hr == HRESULT_FROM_WIN32(ERROR_CANCELLED)) {
122     return DaemonController::RESULT_CANCELLED;
123   } else {
124     // TODO(sergeyu): Report other errors to the webapp once it knows
125     // how to handle them.
126     return DaemonController::RESULT_FAILED;
127   }
128 }
129
130 void InvokeCompletionCallback(
131     const DaemonController::CompletionCallback& done, HRESULT hr) {
132   done.Run(HResultToAsyncResult(hr));
133 }
134
135 }  // namespace
136
137 DaemonControllerDelegateWin::DaemonControllerDelegateWin()
138     : control_is_elevated_(false),
139       window_handle_(NULL) {
140 }
141
142 DaemonControllerDelegateWin::~DaemonControllerDelegateWin() {
143 }
144
145 DaemonController::State DaemonControllerDelegateWin::GetState() {
146   if (base::win::GetVersion() < base::win::VERSION_XP) {
147     return DaemonController::STATE_NOT_IMPLEMENTED;
148   }
149   // TODO(alexeypa): Make the thread alertable, so we can switch to APC
150   // notifications rather than polling.
151   ScopedScHandle service;
152   DWORD error = OpenService(&service);
153
154   switch (error) {
155     case ERROR_SUCCESS: {
156       SERVICE_STATUS status;
157       if (::QueryServiceStatus(service, &status)) {
158         return ConvertToDaemonState(status.dwCurrentState);
159       } else {
160         LOG_GETLASTERROR(ERROR)
161             << "Failed to query the state of the '" << kWindowsServiceName
162             << "' service";
163         return DaemonController::STATE_UNKNOWN;
164       }
165       break;
166     }
167     case ERROR_SERVICE_DOES_NOT_EXIST:
168       return DaemonController::STATE_NOT_INSTALLED;
169     default:
170       return DaemonController::STATE_UNKNOWN;
171   }
172 }
173
174 scoped_ptr<base::DictionaryValue> DaemonControllerDelegateWin::GetConfig() {
175   // Configure and start the Daemon Controller if it is installed already.
176   HRESULT hr = ActivateController();
177   if (FAILED(hr))
178     return scoped_ptr<base::DictionaryValue>();
179
180   // Get the host configuration.
181   ScopedBstr host_config;
182   hr = control_->GetConfig(host_config.Receive());
183   if (FAILED(hr))
184     return scoped_ptr<base::DictionaryValue>();
185
186   // Parse the string into a dictionary.
187   base::string16 file_content(
188       static_cast<BSTR>(host_config), host_config.Length());
189   scoped_ptr<base::Value> config(
190       base::JSONReader::Read(base::UTF16ToUTF8(file_content),
191           base::JSON_ALLOW_TRAILING_COMMAS));
192
193   if (!config || config->GetType() != base::Value::TYPE_DICTIONARY)
194     return scoped_ptr<base::DictionaryValue>();
195
196   return scoped_ptr<base::DictionaryValue>(
197       static_cast<base::DictionaryValue*>(config.release()));
198 }
199
200 void DaemonControllerDelegateWin::InstallHost(
201     const DaemonController::CompletionCallback& done) {
202   DoInstallHost(base::Bind(&InvokeCompletionCallback, done));
203 }
204
205 void DaemonControllerDelegateWin::SetConfigAndStart(
206     scoped_ptr<base::DictionaryValue> config,
207     bool consent,
208     const DaemonController::CompletionCallback& done) {
209   DoInstallHost(
210       base::Bind(&DaemonControllerDelegateWin::StartHostWithConfig,
211                  base::Unretained(this), base::Passed(&config), consent, done));
212 }
213
214 void DaemonControllerDelegateWin::DoInstallHost(
215     const DaemonInstallerWin::CompletionCallback& done) {
216   // Configure and start the Daemon Controller if it is installed already.
217   HRESULT hr = ActivateElevatedController();
218   if (SUCCEEDED(hr)) {
219     done.Run(S_OK);
220     return;
221   }
222
223   // Otherwise, install it if its COM registration entry is missing.
224   if (hr == CO_E_CLASSSTRING) {
225     DCHECK(!installer_);
226
227     installer_ = DaemonInstallerWin::Create(
228         GetTopLevelWindow(window_handle_), done);
229     installer_->Install();
230     return;
231   }
232
233   LOG(ERROR) << "Failed to initiate the Chromoting Host installation "
234              << "(error: 0x" << std::hex << hr << std::dec << ").";
235   done.Run(hr);
236 }
237
238 void DaemonControllerDelegateWin::UpdateConfig(
239     scoped_ptr<base::DictionaryValue> config,
240     const DaemonController::CompletionCallback& done) {
241   HRESULT hr = ActivateElevatedController();
242   if (FAILED(hr)) {
243     InvokeCompletionCallback(done, hr);
244     return;
245   }
246
247   // Update the configuration.
248   ScopedBstr config_str(NULL);
249   ConfigToString(*config, &config_str);
250   if (config_str == NULL) {
251     InvokeCompletionCallback(done, E_OUTOFMEMORY);
252     return;
253   }
254
255   // Make sure that the PIN confirmation dialog is focused properly.
256   hr = control_->SetOwnerWindow(
257       reinterpret_cast<LONG_PTR>(GetTopLevelWindow(window_handle_)));
258   if (FAILED(hr)) {
259     InvokeCompletionCallback(done, hr);
260     return;
261   }
262
263   hr = control_->UpdateConfig(config_str);
264   InvokeCompletionCallback(done, hr);
265 }
266
267 void DaemonControllerDelegateWin::Stop(
268     const DaemonController::CompletionCallback& done) {
269   HRESULT hr = ActivateElevatedController();
270   if (SUCCEEDED(hr))
271     hr = control_->StopDaemon();
272
273   InvokeCompletionCallback(done, hr);
274 }
275
276 void DaemonControllerDelegateWin::SetWindow(void* window_handle) {
277   window_handle_ = reinterpret_cast<HWND>(window_handle);
278 }
279
280 std::string DaemonControllerDelegateWin::GetVersion() {
281   // Configure and start the Daemon Controller if it is installed already.
282   HRESULT hr = ActivateController();
283   if (FAILED(hr))
284     return std::string();
285
286   // Get the version string.
287   ScopedBstr version;
288   hr = control_->GetVersion(version.Receive());
289   if (FAILED(hr))
290     return std::string();
291
292   return base::UTF16ToUTF8(
293       base::string16(static_cast<BSTR>(version), version.Length()));
294 }
295
296 DaemonController::UsageStatsConsent
297 DaemonControllerDelegateWin::GetUsageStatsConsent() {
298   DaemonController::UsageStatsConsent consent;
299   consent.supported = true;
300   consent.allowed = false;
301   consent.set_by_policy = false;
302
303   // Activate the Daemon Controller and see if it supports |IDaemonControl2|.
304   HRESULT hr = ActivateController();
305   if (FAILED(hr)) {
306     // The host is not installed yet. Assume that the user didn't consent to
307     // collecting crash dumps.
308     return consent;
309   }
310
311   if (control2_.get() == NULL) {
312     // The host is installed and does not support crash dump reporting.
313     return consent;
314   }
315
316   // Get the recorded user's consent.
317   BOOL allowed;
318   BOOL set_by_policy;
319   hr = control2_->GetUsageStatsConsent(&allowed, &set_by_policy);
320   if (FAILED(hr)) {
321     // If the user's consent is not recorded yet, assume that the user didn't
322     // consent to collecting crash dumps.
323     return consent;
324   }
325
326   consent.allowed = !!allowed;
327   consent.set_by_policy = !!set_by_policy;
328   return consent;
329 }
330
331 HRESULT DaemonControllerDelegateWin::ActivateController() {
332   if (!control_) {
333     CLSID class_id;
334     HRESULT hr = CLSIDFromProgID(kDaemonController, &class_id);
335     if (FAILED(hr)) {
336       return hr;
337     }
338
339     hr = CoCreateInstance(class_id, NULL, CLSCTX_LOCAL_SERVER,
340                           IID_IDaemonControl, control_.ReceiveVoid());
341     if (FAILED(hr)) {
342       return hr;
343     }
344
345     // Ignore the error. IID_IDaemonControl2 is optional.
346     control_.QueryInterface(IID_IDaemonControl2, control2_.ReceiveVoid());
347
348     // Release |control_| upon expiration of the timeout.
349     release_timer_.reset(new base::OneShotTimer<DaemonControllerDelegateWin>());
350     release_timer_->Start(FROM_HERE,
351                           base::TimeDelta::FromSeconds(kUnprivilegedTimeoutSec),
352                           this,
353                           &DaemonControllerDelegateWin::ReleaseController);
354   }
355
356   return S_OK;
357 }
358
359 HRESULT DaemonControllerDelegateWin::ActivateElevatedController() {
360   // The COM elevation is supported on Vista and above.
361   if (base::win::GetVersion() < base::win::VERSION_VISTA)
362     return ActivateController();
363
364   // Release an unprivileged instance of the daemon controller if any.
365   if (!control_is_elevated_)
366     ReleaseController();
367
368   if (!control_) {
369     BIND_OPTS3 bind_options;
370     memset(&bind_options, 0, sizeof(bind_options));
371     bind_options.cbStruct = sizeof(bind_options);
372     bind_options.hwnd = GetTopLevelWindow(window_handle_);
373     bind_options.dwClassContext  = CLSCTX_LOCAL_SERVER;
374
375     HRESULT hr = ::CoGetObject(
376         kDaemonControllerElevationMoniker,
377         &bind_options,
378         IID_IDaemonControl,
379         control_.ReceiveVoid());
380     if (FAILED(hr)) {
381       return hr;
382     }
383
384     // Ignore the error. IID_IDaemonControl2 is optional.
385     control_.QueryInterface(IID_IDaemonControl2, control2_.ReceiveVoid());
386
387     // Note that we hold a reference to an elevated instance now.
388     control_is_elevated_ = true;
389
390     // Release |control_| upon expiration of the timeout.
391     release_timer_.reset(new base::OneShotTimer<DaemonControllerDelegateWin>());
392     release_timer_->Start(FROM_HERE,
393                           base::TimeDelta::FromSeconds(kPrivilegedTimeoutSec),
394                           this,
395                           &DaemonControllerDelegateWin::ReleaseController);
396   }
397
398   return S_OK;
399 }
400
401 void DaemonControllerDelegateWin::ReleaseController() {
402   control_.Release();
403   control2_.Release();
404   release_timer_.reset();
405   control_is_elevated_ = false;
406 }
407
408 void DaemonControllerDelegateWin::StartHostWithConfig(
409     scoped_ptr<base::DictionaryValue> config,
410     bool consent,
411     const DaemonController::CompletionCallback& done,
412     HRESULT hr) {
413   installer_.reset();
414
415   if (FAILED(hr)) {
416     LOG(ERROR) << "Failed to install the Chromoting Host "
417                << "(error: 0x" << std::hex << hr << std::dec << ").";
418     InvokeCompletionCallback(done, hr);
419     return;
420   }
421
422   hr = ActivateElevatedController();
423   if (FAILED(hr)) {
424     InvokeCompletionCallback(done, hr);
425     return;
426   }
427
428   // Record the user's consent.
429   if (control2_) {
430     hr = control2_->SetUsageStatsConsent(consent);
431     if (FAILED(hr)) {
432       InvokeCompletionCallback(done, hr);
433       return;
434     }
435   }
436
437   // Set the configuration.
438   ScopedBstr config_str(NULL);
439   ConfigToString(*config, &config_str);
440   if (config_str == NULL) {
441     InvokeCompletionCallback(done, E_OUTOFMEMORY);
442     return;
443   }
444
445   hr = control_->SetOwnerWindow(
446       reinterpret_cast<LONG_PTR>(GetTopLevelWindow(window_handle_)));
447   if (FAILED(hr)) {
448     InvokeCompletionCallback(done, hr);
449     return;
450   }
451
452   hr = control_->SetConfig(config_str);
453   if (FAILED(hr)) {
454     InvokeCompletionCallback(done, hr);
455     return;
456   }
457
458   // Start daemon.
459   hr = control_->StartDaemon();
460   InvokeCompletionCallback(done, hr);
461 }
462
463 scoped_refptr<DaemonController> DaemonController::Create() {
464   scoped_ptr<DaemonController::Delegate> delegate(
465       new DaemonControllerDelegateWin());
466   return new DaemonController(delegate.Pass());
467 }
468
469 }  // namespace remoting