Home | History | Annotate | Download | only in setup
      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 
     28 using base::win::ScopedBstr;
     29 using base::win::ScopedComPtr;
     30 using base::win::ScopedVariant;
     31 
     32 namespace {
     33 
     34 // ProgID of the per-machine Omaha COM server.
     35 const wchar_t kGoogleUpdate[] = L"GoogleUpdate.Update3WebMachine";
     36 
     37 // The COM elevation moniker for the per-machine Omaha COM server.
     38 const wchar_t kGoogleUpdateElevationMoniker[] =
     39     L"Elevation:Administrator!new:GoogleUpdate.Update3WebMachine";
     40 
     41 // The registry key where the configuration of Omaha is stored.
     42 const wchar_t kOmahaUpdateKeyName[] = L"Software\\Google\\Update";
     43 
     44 // The name of the value where the full path to GoogleUpdate.exe is stored.
     45 const wchar_t kOmahaPathValueName[] = L"path";
     46 
     47 // The command line format string for GoogleUpdate.exe
     48 const 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.
     53 const wchar_t kOmahaLanguage[] = L"en";
     54 
     55 // An empty string for optional parameters.
     56 const wchar_t kOmahaEmpty[] = L"";
     57 
     58 // The installation status polling interval.
     59 const int kOmahaPollIntervalMs = 500;
     60 
     61 }  // namespace
     62 
     63 namespace remoting {
     64 
     65 // This class implements on-demand installation of the Chromoting Host via
     66 // per-machine Omaha instance.
     67 class 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.
     90 class 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 
    111 DaemonComInstallerWin::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 
    124 void 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 
    177 void 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 
    268 DaemonCommandLineInstallerWin::DaemonCommandLineInstallerWin(
    269     const CompletionCallback& done) : DaemonInstallerWin(done) {
    270 }
    271 
    272 DaemonCommandLineInstallerWin::~DaemonCommandLineInstallerWin() {
    273   process_watcher_.StopWatching();
    274 }
    275 
    276 void 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 
    316 void 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 
    326 DaemonInstallerWin::DaemonInstallerWin(const CompletionCallback& done)
    327     : done_(done) {
    328 }
    329 
    330 DaemonInstallerWin::~DaemonInstallerWin() {
    331 }
    332 
    333 void DaemonInstallerWin::Done(HRESULT result) {
    334   CompletionCallback done = done_;
    335   done_.Reset();
    336   done.Run(result);
    337 }
    338 
    339 // static
    340 scoped_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 
    389 HWND 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