| 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 |