1// Copyright (c) 2012 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_installer_win.h"
6
7#include <windows.h>
8
9#include "base/bind.h"
10#include "base/message_loop/message_loop.h"
11#include "base/process/launch.h"
12#include "base/strings/string16.h"
13#include "base/strings/stringprintf.h"
14#include "base/strings/utf_string_conversions.h"
15#include "base/time/time.h"
16#include "base/timer/timer.h"
17#include "base/win/object_watcher.h"
18#include "base/win/registry.h"
19#include "base/win/scoped_bstr.h"
20#include "base/win/scoped_comptr.h"
21#include "base/win/scoped_handle.h"
22#include "base/win/scoped_variant.h"
23#include "base/win/windows_version.h"
24#include "google_update/google_update_idl.h"
25#include "remoting/base/dispatch_win.h"
26#include "remoting/host/win/omaha.h"
27
28using base::win::ScopedBstr;
29using base::win::ScopedComPtr;
30using base::win::ScopedVariant;
31
32namespace {
33
34// ProgID of the per-machine Omaha COM server.
35const wchar_t kGoogleUpdate[] = L"GoogleUpdate.Update3WebMachine";
36
37// The COM elevation moniker for the per-machine Omaha COM server.
38const wchar_t kGoogleUpdateElevationMoniker[] =
39    L"Elevation:Administrator!new:GoogleUpdate.Update3WebMachine";
40
41// The registry key where the configuration of Omaha is stored.
42const wchar_t kOmahaUpdateKeyName[] = L"Software\\Google\\Update";
43
44// The name of the value where the full path to GoogleUpdate.exe is stored.
45const wchar_t kOmahaPathValueName[] = L"path";
46
47// The command line format string for GoogleUpdate.exe
48const wchar_t kGoogleUpdateCommandLineFormat[] =
49    L"\"%ls\" /install \"bundlename=Chromoting%%20Host&appguid=%ls&"
50    L"appname=Chromoting%%20Host&needsadmin=True&lang=%ls\"";
51
52// TODO(alexeypa): Get the desired laungage from the web app.
53const wchar_t kOmahaLanguage[] = L"en";
54
55// An empty string for optional parameters.
56const wchar_t kOmahaEmpty[] = L"";
57
58// The installation status polling interval.
59const int kOmahaPollIntervalMs = 500;
60
61}  // namespace
62
63namespace remoting {
64
65// This class implements on-demand installation of the Chromoting Host via
66// per-machine Omaha instance.
67class DaemonComInstallerWin : public DaemonInstallerWin {
68 public:
69  DaemonComInstallerWin(const ScopedComPtr<IDispatch>& update3,
70                        const CompletionCallback& done);
71
72  // DaemonInstallerWin implementation.
73  virtual void Install() OVERRIDE;
74
75 private:
76  // Polls the installation status performing state-specific actions (such as
77  // starting installation once download has finished).
78  void PollInstallationStatus();
79
80  // Omaha interfaces.
81  ScopedVariant app_;
82  ScopedVariant bundle_;
83  ScopedComPtr<IDispatch> update3_;
84
85  base::Timer polling_timer_;
86};
87
88// This class implements on-demand installation of the Chromoting Host by
89// launching a per-user instance of Omaha and requesting elevation.
90class DaemonCommandLineInstallerWin
91    : public DaemonInstallerWin,
92      public base::win::ObjectWatcher::Delegate {
93 public:
94  DaemonCommandLineInstallerWin(const CompletionCallback& done);
95  ~DaemonCommandLineInstallerWin();
96
97  // DaemonInstallerWin implementation.
98  virtual void Install() OVERRIDE;
99
100  // base::win::ObjectWatcher::Delegate implementation.
101  virtual void OnObjectSignaled(HANDLE object) OVERRIDE;
102
103 private:
104  // Handle of the launched process.
105  base::win::ScopedHandle process_;
106
107  // Used to determine when the launched process terminates.
108  base::win::ObjectWatcher process_watcher_;
109};
110
111DaemonComInstallerWin::DaemonComInstallerWin(
112    const ScopedComPtr<IDispatch>& update3,
113    const CompletionCallback& done)
114    : DaemonInstallerWin(done),
115      update3_(update3),
116      polling_timer_(
117          FROM_HERE,
118          base::TimeDelta::FromMilliseconds(kOmahaPollIntervalMs),
119          base::Bind(&DaemonComInstallerWin::PollInstallationStatus,
120                     base::Unretained(this)),
121          false) {
122}
123
124void DaemonComInstallerWin::Install() {
125  // Create an app bundle.
126  HRESULT hr = dispatch::Invoke(update3_.get(), L"createAppBundleWeb",
127                                DISPATCH_METHOD, bundle_.Receive());
128  if (FAILED(hr)) {
129    Done(hr);
130    return;
131  }
132  if (bundle_.type() != VT_DISPATCH) {
133    Done(DISP_E_TYPEMISMATCH);
134    return;
135  }
136
137  hr = dispatch::Invoke(V_DISPATCH(&bundle_), L"initialize", DISPATCH_METHOD,
138                        NULL);
139  if (FAILED(hr)) {
140    Done(hr);
141    return;
142  }
143
144  // Add Chromoting Host to the bundle.
145  ScopedVariant appid(kHostOmahaAppid);
146  ScopedVariant empty(kOmahaEmpty);
147  ScopedVariant language(kOmahaLanguage);
148  hr = dispatch::Invoke(V_DISPATCH(&bundle_), L"createApp", DISPATCH_METHOD,
149                        appid, empty, language, empty, NULL);
150  if (FAILED(hr)) {
151    Done(hr);
152    return;
153  }
154
155  hr = dispatch::Invoke(V_DISPATCH(&bundle_), L"checkForUpdate",
156                        DISPATCH_METHOD, NULL);
157  if (FAILED(hr)) {
158    Done(hr);
159    return;
160  }
161
162  hr = dispatch::Invoke(V_DISPATCH(&bundle_), L"appWeb",
163                        DISPATCH_PROPERTYGET, ScopedVariant(0), app_.Receive());
164  if (FAILED(hr)) {
165    Done(hr);
166    return;
167  }
168  if (app_.type() != VT_DISPATCH) {
169    Done(DISP_E_TYPEMISMATCH);
170    return;
171  }
172
173  // Now poll for the installation status.
174  PollInstallationStatus();
175}
176
177void DaemonComInstallerWin::PollInstallationStatus() {
178  // Get the current application installation state.
179  // N.B. The object underlying the ICurrentState interface has static data that
180  // does not get updated as the server state changes. To get the most "current"
181  // state, the currentState property needs to be queried again.
182  ScopedVariant current_state;
183  HRESULT hr = dispatch::Invoke(V_DISPATCH(&app_), L"currentState",
184                                DISPATCH_PROPERTYGET, current_state.Receive());
185  if (FAILED(hr)) {
186    Done(hr);
187    return;
188  }
189  if (current_state.type() != VT_DISPATCH) {
190    Done(DISP_E_TYPEMISMATCH);
191    return;
192  }
193
194  ScopedVariant state;
195  hr = dispatch::Invoke(V_DISPATCH(&current_state), L"stateValue",
196                        DISPATCH_PROPERTYGET, state.Receive());
197  if (state.type() != VT_I4) {
198    Done(DISP_E_TYPEMISMATCH);
199    return;
200  }
201
202  // Perform state-specific actions.
203  switch (V_I4(&state)) {
204    case STATE_INIT:
205    case STATE_WAITING_TO_CHECK_FOR_UPDATE:
206    case STATE_CHECKING_FOR_UPDATE:
207    case STATE_WAITING_TO_DOWNLOAD:
208    case STATE_RETRYING_DOWNLOAD:
209    case STATE_DOWNLOADING:
210    case STATE_WAITING_TO_INSTALL:
211    case STATE_INSTALLING:
212    case STATE_PAUSED:
213      break;
214
215    case STATE_UPDATE_AVAILABLE:
216      hr = dispatch::Invoke(V_DISPATCH(&bundle_), L"download",
217                            DISPATCH_METHOD, NULL);
218      if (FAILED(hr)) {
219        Done(hr);
220        return;
221      }
222      break;
223
224    case STATE_DOWNLOAD_COMPLETE:
225    case STATE_EXTRACTING:
226    case STATE_APPLYING_DIFFERENTIAL_PATCH:
227    case STATE_READY_TO_INSTALL:
228      hr = dispatch::Invoke(V_DISPATCH(&bundle_), L"install",
229                            DISPATCH_METHOD, NULL);
230      if (FAILED(hr)) {
231        Done(hr);
232        return;
233      }
234      break;
235
236    case STATE_INSTALL_COMPLETE:
237    case STATE_NO_UPDATE:
238      // Installation complete or not required. Report success.
239      Done(S_OK);
240      return;
241
242    case STATE_ERROR: {
243      ScopedVariant error_code;
244      hr = dispatch::Invoke(V_DISPATCH(&current_state), L"errorCode",
245                            DISPATCH_PROPERTYGET, error_code.Receive());
246      if (FAILED(hr)) {
247        Done(hr);
248        return;
249      }
250      if (error_code.type() != VT_UI4) {
251        Done(DISP_E_TYPEMISMATCH);
252        return;
253      }
254      Done(V_UI4(&error_code));
255      return;
256    }
257
258    default:
259      LOG(ERROR) << "Unknown bundle state: " << V_I4(&state) << ".";
260      Done(E_FAIL);
261      return;
262  }
263
264  // Keep polling.
265  polling_timer_.Reset();
266}
267
268DaemonCommandLineInstallerWin::DaemonCommandLineInstallerWin(
269    const CompletionCallback& done) : DaemonInstallerWin(done) {
270}
271
272DaemonCommandLineInstallerWin::~DaemonCommandLineInstallerWin() {
273  process_watcher_.StopWatching();
274}
275
276void DaemonCommandLineInstallerWin::Install() {
277  // Get the full path to GoogleUpdate.exe from the registry.
278  base::win::RegKey update_key;
279  LONG result = update_key.Open(HKEY_CURRENT_USER,
280                                kOmahaUpdateKeyName,
281                                KEY_READ);
282  if (result != ERROR_SUCCESS) {
283    Done(HRESULT_FROM_WIN32(result));
284    return;
285  }
286
287  // presubmit: allow wstring
288  std::wstring google_update;
289  result = update_key.ReadValue(kOmahaPathValueName, &google_update);
290  if (result != ERROR_SUCCESS) {
291    Done(HRESULT_FROM_WIN32(result));
292    return;
293  }
294
295  // Launch the updater process and wait for its termination.
296  base::string16 command_line = base::WideToUTF16(
297      base::StringPrintf(kGoogleUpdateCommandLineFormat,
298                         google_update.c_str(),
299                         kHostOmahaAppid,
300                         kOmahaLanguage));
301
302  base::LaunchOptions options;
303  if (!base::LaunchProcess(command_line, options, &process_)) {
304    result = GetLastError();
305    Done(HRESULT_FROM_WIN32(result));
306    return;
307  }
308
309  if (!process_watcher_.StartWatching(process_.Get(), this)) {
310    result = GetLastError();
311    Done(HRESULT_FROM_WIN32(result));
312    return;
313  }
314}
315
316void DaemonCommandLineInstallerWin::OnObjectSignaled(HANDLE object) {
317  // Check if the updater process returned success.
318  DWORD exit_code;
319  if (GetExitCodeProcess(process_.Get(), &exit_code) && exit_code == 0) {
320    Done(S_OK);
321  } else {
322    Done(E_FAIL);
323  }
324}
325
326DaemonInstallerWin::DaemonInstallerWin(const CompletionCallback& done)
327    : done_(done) {
328}
329
330DaemonInstallerWin::~DaemonInstallerWin() {
331}
332
333void DaemonInstallerWin::Done(HRESULT result) {
334  CompletionCallback done = done_;
335  done_.Reset();
336  done.Run(result);
337}
338
339// static
340scoped_ptr<DaemonInstallerWin> DaemonInstallerWin::Create(
341    HWND window_handle,
342    CompletionCallback done) {
343  HRESULT result = E_FAIL;
344  ScopedComPtr<IDispatch> update3;
345
346  // Check if the machine instance of Omaha is available. The COM elevation is
347  // supported on Vista+, so on XP/W2K3 we assume that we are running under
348  // a privileged user and get ACCESS_DENIED later if we are not.
349  if (base::win::GetVersion() < base::win::VERSION_VISTA) {
350    CLSID class_id;
351    result = CLSIDFromProgID(kGoogleUpdate, &class_id);
352    if (SUCCEEDED(result)) {
353      result = CoCreateInstance(class_id,
354                                NULL,
355                                CLSCTX_LOCAL_SERVER,
356                                IID_IDispatch,
357                                update3.ReceiveVoid());
358    }
359  } else {
360    BIND_OPTS3 bind_options;
361    memset(&bind_options, 0, sizeof(bind_options));
362    bind_options.cbStruct = sizeof(bind_options);
363    bind_options.hwnd = GetTopLevelWindow(window_handle);
364    bind_options.dwClassContext = CLSCTX_LOCAL_SERVER;
365    result = CoGetObject(kGoogleUpdateElevationMoniker,
366                         &bind_options,
367                         IID_IDispatch,
368                         update3.ReceiveVoid());
369  }
370  if (SUCCEEDED(result)) {
371    // The machine instance of Omaha is available and we successfully passed
372    // the UAC prompt.
373    return scoped_ptr<DaemonInstallerWin>(
374        new DaemonComInstallerWin(update3, done));
375  } else if (result == CO_E_CLASSSTRING) {
376    // The machine instance of Omaha is not available so we will have to run
377    // GoogleUpdate.exe manually passing "needsadmin=True". This will cause
378    // Omaha to install the machine instance first and then install Chromoting
379    // Host.
380    return scoped_ptr<DaemonInstallerWin>(
381        new DaemonCommandLineInstallerWin(done));
382  } else {
383    // The user declined the UAC prompt or some other error occured.
384    done.Run(result);
385    return scoped_ptr<DaemonInstallerWin>();
386  }
387}
388
389HWND GetTopLevelWindow(HWND window) {
390  if (window == NULL) {
391    return NULL;
392  }
393
394  for (;;) {
395    LONG style = GetWindowLong(window, GWL_STYLE);
396    if ((style & WS_OVERLAPPEDWINDOW) == WS_OVERLAPPEDWINDOW ||
397        (style & WS_POPUP) == WS_POPUP) {
398      return window;
399    }
400
401    HWND parent = GetAncestor(window, GA_PARENT);
402    if (parent == NULL) {
403      return window;
404    }
405
406    window = parent;
407  }
408}
409
410}  // namespace remoting
411