| OLD | NEW |
| (Empty) |
| 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::Process 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 nullptr); | |
| 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, nullptr); | |
| 150 if (FAILED(hr)) { | |
| 151 Done(hr); | |
| 152 return; | |
| 153 } | |
| 154 | |
| 155 hr = dispatch::Invoke(V_DISPATCH(&bundle_), L"checkForUpdate", | |
| 156 DISPATCH_METHOD, nullptr); | |
| 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, nullptr); | |
| 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, nullptr); | |
| 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 = base::WideToUTF16( | |
| 297 base::StringPrintf(kGoogleUpdateCommandLineFormat, | |
| 298 google_update.c_str(), | |
| 299 kHostOmahaAppid, | |
| 300 kOmahaLanguage)); | |
| 301 | |
| 302 base::LaunchOptions options; | |
| 303 process_ = base::LaunchProcess(command_line, options); | |
| 304 if (!process_.IsValid()) { | |
| 305 result = GetLastError(); | |
| 306 Done(HRESULT_FROM_WIN32(result)); | |
| 307 return; | |
| 308 } | |
| 309 | |
| 310 if (!process_watcher_.StartWatching(process_.Handle(), this)) { | |
| 311 result = GetLastError(); | |
| 312 Done(HRESULT_FROM_WIN32(result)); | |
| 313 return; | |
| 314 } | |
| 315 } | |
| 316 | |
| 317 void DaemonCommandLineInstallerWin::OnObjectSignaled(HANDLE object) { | |
| 318 // Check if the updater process returned success. | |
| 319 DWORD exit_code; | |
| 320 if (GetExitCodeProcess(process_.Handle(), &exit_code) && exit_code == 0) { | |
| 321 Done(S_OK); | |
| 322 } else { | |
| 323 Done(E_FAIL); | |
| 324 } | |
| 325 } | |
| 326 | |
| 327 DaemonInstallerWin::DaemonInstallerWin(const CompletionCallback& done) | |
| 328 : done_(done) { | |
| 329 } | |
| 330 | |
| 331 DaemonInstallerWin::~DaemonInstallerWin() { | |
| 332 } | |
| 333 | |
| 334 void DaemonInstallerWin::Done(HRESULT result) { | |
| 335 CompletionCallback done = done_; | |
| 336 done_.Reset(); | |
| 337 done.Run(result); | |
| 338 } | |
| 339 | |
| 340 // static | |
| 341 scoped_ptr<DaemonInstallerWin> DaemonInstallerWin::Create( | |
| 342 HWND window_handle, | |
| 343 CompletionCallback done) { | |
| 344 HRESULT result = E_FAIL; | |
| 345 ScopedComPtr<IDispatch> update3; | |
| 346 | |
| 347 // Check if the machine instance of Omaha is available. The COM elevation is | |
| 348 // supported on Vista+, so on XP/W2K3 we assume that we are running under | |
| 349 // a privileged user and get ACCESS_DENIED later if we are not. | |
| 350 if (base::win::GetVersion() < base::win::VERSION_VISTA) { | |
| 351 CLSID class_id; | |
| 352 result = CLSIDFromProgID(kGoogleUpdate, &class_id); | |
| 353 if (SUCCEEDED(result)) { | |
| 354 result = CoCreateInstance(class_id, | |
| 355 nullptr, | |
| 356 CLSCTX_LOCAL_SERVER, | |
| 357 IID_IDispatch, | |
| 358 update3.ReceiveVoid()); | |
| 359 } | |
| 360 } else { | |
| 361 BIND_OPTS3 bind_options; | |
| 362 memset(&bind_options, 0, sizeof(bind_options)); | |
| 363 bind_options.cbStruct = sizeof(bind_options); | |
| 364 bind_options.hwnd = GetTopLevelWindow(window_handle); | |
| 365 bind_options.dwClassContext = CLSCTX_LOCAL_SERVER; | |
| 366 result = CoGetObject(kGoogleUpdateElevationMoniker, | |
| 367 &bind_options, | |
| 368 IID_IDispatch, | |
| 369 update3.ReceiveVoid()); | |
| 370 } | |
| 371 | |
| 372 if (result == CO_E_CLASSSTRING) { | |
| 373 // The machine instance of Omaha is not available so we will have to run | |
| 374 // GoogleUpdate.exe manually passing "needsadmin=True". This will cause | |
| 375 // Omaha to install the machine instance first and then install Chromoting | |
| 376 // Host. | |
| 377 return make_scoped_ptr(new DaemonCommandLineInstallerWin(done)); | |
| 378 } | |
| 379 | |
| 380 if (!SUCCEEDED(result)) { | |
| 381 // The user declined the UAC prompt or some other error occured. | |
| 382 done.Run(result); | |
| 383 return nullptr; | |
| 384 } | |
| 385 | |
| 386 // The machine instance of Omaha is available and we successfully passed | |
| 387 // the UAC prompt. | |
| 388 return make_scoped_ptr(new DaemonComInstallerWin(update3, done)); | |
| 389 } | |
| 390 | |
| 391 HWND GetTopLevelWindow(HWND window) { | |
| 392 if (window == nullptr) { | |
| 393 return nullptr; | |
| 394 } | |
| 395 | |
| 396 for (;;) { | |
| 397 LONG style = GetWindowLong(window, GWL_STYLE); | |
| 398 if ((style & WS_OVERLAPPEDWINDOW) == WS_OVERLAPPEDWINDOW || | |
| 399 (style & WS_POPUP) == WS_POPUP) { | |
| 400 return window; | |
| 401 } | |
| 402 | |
| 403 HWND parent = GetAncestor(window, GA_PARENT); | |
| 404 if (parent == nullptr) { | |
| 405 return window; | |
| 406 } | |
| 407 | |
| 408 window = parent; | |
| 409 } | |
| 410 } | |
| 411 | |
| 412 } // namespace remoting | |
| OLD | NEW |