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(¤t_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(¤t_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 = 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