OLD | NEW |
| (Empty) |
1 // Copyright 2008-2009 Google Inc. | |
2 // | |
3 // Licensed under the Apache License, Version 2.0 (the "License"); | |
4 // you may not use this file except in compliance with the License. | |
5 // You may obtain a copy of the License at | |
6 // | |
7 // http://www.apache.org/licenses/LICENSE-2.0 | |
8 // | |
9 // Unless required by applicable law or agreed to in writing, software | |
10 // distributed under the License is distributed on an "AS IS" BASIS, | |
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
12 // See the License for the specific language governing permissions and | |
13 // limitations under the License. | |
14 // ======================================================================== | |
15 // | |
16 // A simple tool for performing and interacting with on demand updates. | |
17 #include "omaha/tools/performondemand/performondemand.h" | |
18 #include <windows.h> | |
19 #include <sddl.h> | |
20 #include <shlobj.h> | |
21 #include <atltime.h> | |
22 #include <tchar.h> | |
23 #include "omaha/common/system.h" | |
24 #include "omaha/common/system_info.h" | |
25 #include "omaha/common/utils.h" | |
26 #include "omaha/common/vistautil.h" | |
27 | |
28 namespace omaha { | |
29 | |
30 bool ParseParams(int argc, TCHAR* argv[], CString* guid, bool* is_machine, | |
31 bool* is_update_check_only, int* timeout) { | |
32 ASSERT1(argv); | |
33 ASSERT1(guid); | |
34 ASSERT1(is_machine); | |
35 ASSERT1(is_update_check_only); | |
36 ASSERT1(timeout); | |
37 if (argc < 3 || argc > 5) { | |
38 return false; | |
39 } | |
40 *guid = argv[1]; | |
41 | |
42 // Verify that the guid is valid. | |
43 GUID parsed = StringToGuid(*guid); | |
44 if (parsed == GUID_NULL) { | |
45 return false; | |
46 } | |
47 | |
48 *is_machine = !!_ttoi(argv[2]); | |
49 | |
50 if (argc >= 4) { | |
51 *is_update_check_only = !!_ttoi(argv[3]); | |
52 } else { | |
53 *is_update_check_only = false; | |
54 } | |
55 | |
56 if (argc >= 5) { | |
57 *timeout = _ttoi(argv[4]); | |
58 if (*timeout == 0) { | |
59 return false; | |
60 } | |
61 } else { | |
62 *timeout = 60; | |
63 } | |
64 | |
65 return true; | |
66 } | |
67 | |
68 | |
69 DWORD SetTokenIntegrityLevelMedium(HANDLE token) { | |
70 PSID medium_sid = NULL; | |
71 if (!::ConvertStringSidToSid(SDDL_ML_MEDIUM, &medium_sid)) { | |
72 return ::GetLastError(); | |
73 } | |
74 | |
75 TOKEN_MANDATORY_LABEL label = {0}; | |
76 label.Label.Attributes = SE_GROUP_INTEGRITY; | |
77 label.Label.Sid = medium_sid; | |
78 | |
79 size_t size = sizeof(TOKEN_MANDATORY_LABEL) + ::GetLengthSid(medium_sid); | |
80 BOOL success = ::SetTokenInformation(token, TokenIntegrityLevel, &label, | |
81 size); | |
82 DWORD result = success ? ERROR_SUCCESS : ::GetLastError(); | |
83 ::LocalFree(medium_sid); | |
84 return result; | |
85 } | |
86 | |
87 // Reads the Proxy information for the given interface from HKCU, and registers | |
88 // it with COM. | |
89 HRESULT RegisterHKCUPSClsid(IID iid, | |
90 HMODULE* proxy_module, | |
91 DWORD* revoke_cookie) { | |
92 ASSERT1(proxy_module); | |
93 ASSERT1(revoke_cookie); | |
94 *proxy_module = NULL; | |
95 *revoke_cookie = 0; | |
96 | |
97 const TCHAR* const hkcu_classes_key = _T("HKCU\\Software\\Classes\\"); | |
98 | |
99 // Get the registered proxy for the interface. | |
100 CString interface_proxy_clsid_key; | |
101 interface_proxy_clsid_key.Format(_T("%sInterface\\%s\\ProxyStubClsid32"), | |
102 hkcu_classes_key, GuidToString(iid)); | |
103 CString proxy_clsid32_value; | |
104 HRESULT hr = RegKey::GetValue(interface_proxy_clsid_key, | |
105 NULL, | |
106 &proxy_clsid32_value); | |
107 if (FAILED(hr)) { | |
108 wprintf(_T("RegKey::GetValue failed [%s][0x%x]\n"), | |
109 interface_proxy_clsid_key, hr); | |
110 return hr; | |
111 } | |
112 | |
113 // Get the location of the proxy/stub DLL. | |
114 CString proxy_server32_entry; | |
115 proxy_server32_entry.Format(_T("%sClsid\\%s\\InprocServer32"), | |
116 hkcu_classes_key, proxy_clsid32_value); | |
117 CString hkcu_proxy_dll_path; | |
118 hr = RegKey::GetValue(proxy_server32_entry, | |
119 NULL, | |
120 &hkcu_proxy_dll_path); | |
121 if (FAILED(hr)) { | |
122 wprintf(_T("RegKey::GetValue failed [%s][0x%x]\n"), | |
123 proxy_server32_entry, hr); | |
124 return hr; | |
125 } | |
126 | |
127 // Get the proxy/stub class object. | |
128 typedef HRESULT (STDAPICALLTYPE *DllGetClassObjectTypedef)(REFCLSID clsid, | |
129 REFIID iid, | |
130 void** ptr); | |
131 *proxy_module = ::LoadLibrary(hkcu_proxy_dll_path); | |
132 DllGetClassObjectTypedef fn = NULL; | |
133 if (!GPA(*proxy_module, "DllGetClassObject", &fn)) { | |
134 hr = HRESULT_FROM_WIN32(::GetLastError()); | |
135 wprintf(_T("GetProcAddress DllGetClassObject failed [0x%x]\n"), hr); | |
136 return hr; | |
137 } | |
138 CComPtr<IPSFactoryBuffer> fb; | |
139 CLSID proxy_clsid = StringToGuid(proxy_clsid32_value); | |
140 hr = (*fn)(proxy_clsid, IID_IPSFactoryBuffer, reinterpret_cast<void**>(&fb)); | |
141 if (FAILED(hr)) { | |
142 wprintf(_T("DllGetClassObject failed [0x%x]\n"), hr); | |
143 return hr; | |
144 } | |
145 | |
146 // Register the proxy/stub class object. | |
147 hr = ::CoRegisterClassObject(proxy_clsid, fb, CLSCTX_INPROC_SERVER, | |
148 REGCLS_MULTIPLEUSE, revoke_cookie); | |
149 if (FAILED(hr)) { | |
150 wprintf(_T("CoRegisterClassObject failed [0x%x]\n"), hr); | |
151 return hr; | |
152 } | |
153 | |
154 // Relate the interface with the proxy/stub, so COM does not do a lookup when | |
155 // unmarshaling the interface. | |
156 hr = ::CoRegisterPSClsid(iid, proxy_clsid); | |
157 if (FAILED(hr)) { | |
158 wprintf(_T("CoRegisterPSClsid failed [0x%x]\n"), hr); | |
159 return hr; | |
160 } | |
161 | |
162 return S_OK; | |
163 } | |
164 | |
165 // A helper class for clients of the Omaha on-demand out-of-proc COM server. | |
166 // An instance of this class is typically created on the stack. The class does | |
167 // nothing for cases where the OS is not Vista RTM with UAC off. | |
168 // This class does the following: | |
169 // * Calls CoInitializeSecurity with cloaking set to dynamic. This makes COM | |
170 // use the thread token instead of the process token. | |
171 // * Impersonates and sets the thread token to medium integrity. This allows for | |
172 // out-of-proc HKCU COM server activation. | |
173 // * Reads and registers per-user proxies for the interfaces that on-demand | |
174 // exposes. | |
175 class VistaProxyRegistrar { | |
176 public: | |
177 VistaProxyRegistrar() | |
178 : googleupdate_cookie_(0), | |
179 jobobserver_cookie_(0), | |
180 progresswndevents_cookie_(0), | |
181 is_impersonated(false) { | |
182 HRESULT hr = VistaProxyRegistrarImpl(); | |
183 if (FAILED(hr)) { | |
184 wprintf(_T("VistaProxyRegistrarImpl failed [0x%x]\n"), hr); | |
185 } | |
186 } | |
187 | |
188 ~VistaProxyRegistrar() { | |
189 if (googleupdate_cookie_) { | |
190 VERIFY1(SUCCEEDED(::CoRevokeClassObject(googleupdate_cookie_))); | |
191 } | |
192 | |
193 if (jobobserver_cookie_) { | |
194 VERIFY1(SUCCEEDED(::CoRevokeClassObject(jobobserver_cookie_))); | |
195 } | |
196 | |
197 if (progresswndevents_cookie_) { | |
198 VERIFY1(SUCCEEDED(::CoRevokeClassObject(progresswndevents_cookie_))); | |
199 } | |
200 | |
201 if (is_impersonated) { | |
202 VERIFY1(::RevertToSelf()); | |
203 } | |
204 } | |
205 | |
206 private: | |
207 HRESULT VistaProxyRegistrarImpl() { | |
208 if (!SystemInfo::IsRunningOnVistaRTM() || !::IsUserAnAdmin()) { | |
209 return S_OK; | |
210 } | |
211 | |
212 bool is_split_token = false; | |
213 HRESULT hr = vista_util::IsUserRunningSplitToken(&is_split_token); | |
214 if (FAILED(hr)) { | |
215 return hr; | |
216 } | |
217 if (is_split_token) { | |
218 return S_OK; | |
219 } | |
220 | |
221 // Needs to be called very early on in a process. | |
222 // Turn on dynamic cloaking so COM picks up the impersonated thread token. | |
223 hr = ::CoInitializeSecurity( | |
224 NULL, | |
225 -1, | |
226 NULL, | |
227 NULL, | |
228 RPC_C_AUTHN_LEVEL_PKT_PRIVACY, | |
229 RPC_C_IMP_LEVEL_IDENTIFY, | |
230 NULL, | |
231 EOAC_DYNAMIC_CLOAKING, | |
232 NULL); | |
233 if (FAILED(hr)) { | |
234 wprintf(_T("[CoInitializeSecurity failed][0x%x]"), hr); | |
235 return hr; | |
236 } | |
237 | |
238 is_impersonated = !!::ImpersonateSelf(SecurityImpersonation); | |
239 if (!is_impersonated) { | |
240 hr = HRESULT_FROM_WIN32(::GetLastError()); | |
241 wprintf(_T("[main: ImpersonateSelf failed][0x%x]"), hr); | |
242 return hr; | |
243 } | |
244 | |
245 scoped_handle thread_token; | |
246 if (!::OpenThreadToken(::GetCurrentThread(), | |
247 TOKEN_ALL_ACCESS, | |
248 false, | |
249 address(thread_token))) { | |
250 hr = HRESULT_FROM_WIN32(::GetLastError()); | |
251 wprintf(_T("[main: OpenThreadToken failed][0x%x]"), hr); | |
252 return hr; | |
253 } | |
254 | |
255 DWORD result = SetTokenIntegrityLevelMedium(get(thread_token)); | |
256 if (result != ERROR_SUCCESS) { | |
257 wprintf(_T("[main: SetTokenIntegrityLevelMedium failed][0x%x]"), result); | |
258 return HRESULT_FROM_WIN32(result); | |
259 } | |
260 | |
261 hr = RegisterHKCUPSClsid(__uuidof(IGoogleUpdate), | |
262 address(googleupdate_library_), | |
263 &googleupdate_cookie_); | |
264 if (FAILED(hr)) { | |
265 wprintf(_T("RegisterHKCUPSClsid for IGoogleUpdate failed [0x%x]\n"), hr); | |
266 return hr; | |
267 } | |
268 | |
269 hr = RegisterHKCUPSClsid(__uuidof(IJobObserver), | |
270 address(jobobserver_library_), | |
271 &jobobserver_cookie_); | |
272 if (FAILED(hr)) { | |
273 wprintf(_T("RegisterHKCUPSClsid for IJobObserver failed [0x%x]\n"), hr); | |
274 return hr; | |
275 } | |
276 | |
277 hr = RegisterHKCUPSClsid(__uuidof(IProgressWndEvents), | |
278 address(progresswndevents_library_), | |
279 &progresswndevents_cookie_); | |
280 if (FAILED(hr)) { | |
281 wprintf(_T("RegisterHKCUPSClsid for IProgressWndEvents failed [0x%x]\n"), | |
282 hr); | |
283 return hr; | |
284 } | |
285 | |
286 return S_OK; | |
287 } | |
288 | |
289 private: | |
290 scoped_library googleupdate_library_; | |
291 scoped_library jobobserver_library_; | |
292 scoped_library progresswndevents_library_; | |
293 | |
294 DWORD googleupdate_cookie_; | |
295 DWORD jobobserver_cookie_; | |
296 DWORD progresswndevents_cookie_; | |
297 bool is_impersonated; | |
298 }; | |
299 | |
300 int DoMain(int argc, TCHAR* argv[]) { | |
301 CString guid; | |
302 bool is_machine = false; | |
303 bool is_update_check_only = false; | |
304 int timeout = 60; | |
305 if (!ParseParams(argc, argv, &guid, &is_machine, | |
306 &is_update_check_only, &timeout)) { | |
307 wprintf(_T("Usage: performondemand.exe {GUID} {is_machine: 0|1} ") | |
308 _T("[is_update_check_only=0] [timeout=60]\n")); | |
309 return -1; | |
310 } | |
311 wprintf(_T("GUID: %s\n"), guid); | |
312 CComModule module; | |
313 scoped_co_init com_apt; | |
314 VistaProxyRegistrar registrar; | |
315 | |
316 CComObject<JobObserver>* job_observer; | |
317 HRESULT hr = CComObject<JobObserver>::CreateInstance(&job_observer); | |
318 if (!SUCCEEDED(hr)) { | |
319 wprintf(_T("CComObject<JobObserver>::CreateInstance failed [0x%x]\n"), hr); | |
320 return -1; | |
321 } | |
322 CComPtr<IJobObserver> job_holder(job_observer); | |
323 | |
324 CComPtr<IGoogleUpdate> on_demand; | |
325 if (is_machine && !is_update_check_only) { | |
326 hr = System::CoCreateInstanceAsAdmin(NULL, | |
327 __uuidof(OnDemandMachineAppsClass), | |
328 __uuidof(on_demand), | |
329 reinterpret_cast<void**>(&on_demand)); | |
330 } else { | |
331 hr = on_demand.CoCreateInstance(is_machine ? | |
332 __uuidof(OnDemandMachineAppsClass) : | |
333 __uuidof(OnDemandUserAppsClass)); | |
334 } | |
335 | |
336 if (!SUCCEEDED(hr)) { | |
337 wprintf(_T("Could not create COM instance [0x%x]\n"), hr); | |
338 return -1; | |
339 } | |
340 | |
341 if (is_update_check_only) { | |
342 hr = on_demand->CheckForUpdate(guid, job_observer); | |
343 } else { | |
344 hr = on_demand->Update(guid, job_observer); | |
345 } | |
346 | |
347 if (!SUCCEEDED(hr)) { | |
348 wprintf(_T("on_demand->%sUpdate failed [0x%x]\n"), | |
349 is_update_check_only ? _T("CheckFor") : _T(""), hr); | |
350 return -1; | |
351 } | |
352 | |
353 // Main message loop: | |
354 MSG msg; | |
355 SYSTEMTIME start_system_time = {0}; | |
356 SYSTEMTIME current_system_time = {0}; | |
357 ::GetSystemTime(&start_system_time); | |
358 CTime start_time(start_system_time); | |
359 CTimeSpan timeout_period(0, 0, 0, timeout); | |
360 | |
361 while (::GetMessage(&msg, NULL, 0, 0)) { | |
362 ::TranslateMessage(&msg); | |
363 ::DispatchMessage(&msg); | |
364 ::GetSystemTime(¤t_system_time); | |
365 CTime current_time(current_system_time); | |
366 CTimeSpan elapsed_time = current_time - start_time; | |
367 if (timeout_period < elapsed_time) { | |
368 wprintf(_T("Timed out.\n")); | |
369 // TODO(omaha): Right now the timeout does correctly break, but then | |
370 // the COM interactions continue on to completion. | |
371 break; | |
372 } | |
373 } | |
374 int ret_val = job_observer->observed; | |
375 | |
376 return ret_val; | |
377 } | |
378 | |
379 } // namespace omaha | |
380 | |
381 int _tmain(int argc, TCHAR* argv[]) { | |
382 return omaha::DoMain(argc, argv); | |
383 } | |
384 | |
OLD | NEW |