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 |