OLD | NEW |
| (Empty) |
1 // Copyright 2007-2010 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 #include "omaha/net/network_config.h" | |
17 | |
18 #include <winhttp.h> | |
19 #include <atlconv.h> | |
20 #include <atlsecurity.h> | |
21 #include <algorithm> | |
22 #include <hash_set> | |
23 #include <vector> | |
24 #include "base/error.h" | |
25 #include "base/scoped_ptr.h" | |
26 #include "base/scope_guard.h" | |
27 #include "omaha/base/browser_utils.h" | |
28 #include "omaha/base/const_object_names.h" | |
29 #include "omaha/base/constants.h" | |
30 #include "omaha/base/debug.h" | |
31 #include "omaha/base/error.h" | |
32 #include "omaha/base/encrypt.h" | |
33 #include "omaha/base/logging.h" | |
34 #include "omaha/base/omaha_version.h" | |
35 #include "omaha/base/path.h" | |
36 #include "omaha/base/reg_key.h" | |
37 #include "omaha/base/scoped_ptr_address.h" | |
38 #include "omaha/base/string.h" | |
39 #include "omaha/base/system.h" | |
40 #include "omaha/base/user_info.h" | |
41 #include "omaha/base/utils.h" | |
42 #include "omaha/common/config_manager.h" | |
43 #include "omaha/net/cup_request.h" | |
44 #include "omaha/net/http_client.h" | |
45 | |
46 using omaha::encrypt::EncryptData; | |
47 using omaha::encrypt::DecryptData; | |
48 | |
49 namespace omaha { | |
50 | |
51 // Computes the hash value of a ProxyConfig object. Names in the stdext | |
52 // namespace are not currently part of the ISO C++ standard. | |
53 uint32 hash_value(const ProxyConfig& config) { | |
54 uint32 hash = stdext::hash_value(config.auto_detect) ^ | |
55 stdext::hash_value(config.auto_config_url.GetString()) ^ | |
56 stdext::hash_value(config.proxy.GetString()) ^ | |
57 stdext::hash_value(config.proxy_bypass.GetString()); | |
58 return hash; | |
59 } | |
60 | |
61 const TCHAR* const NetworkConfigManager::kNetworkSubkey = _T("network"); | |
62 const TCHAR* const NetworkConfigManager::kNetworkCupSubkey = _T("secure"); | |
63 const TCHAR* const NetworkConfigManager::kCupClientSecretKey = _T("sk"); | |
64 const TCHAR* const NetworkConfigManager::kCupClientCookie = _T("c"); | |
65 | |
66 const TCHAR* const NetworkConfig::kUserAgent = _T("Google Update/%s"); | |
67 | |
68 const TCHAR* const NetworkConfig::kRegKeyProxy = GOOPDATE_MAIN_KEY _T("proxy"); | |
69 const TCHAR* const NetworkConfig::kRegValueSource = _T("source"); | |
70 | |
71 const TCHAR* const NetworkConfig::kWPADIdentifier = _T("auto"); | |
72 const TCHAR* const NetworkConfig::kDirectConnectionIdentifier = _T("direct"); | |
73 | |
74 NetworkConfig::NetworkConfig(bool is_machine) | |
75 : is_machine_(is_machine), | |
76 is_initialized_(false) {} | |
77 | |
78 NetworkConfig::~NetworkConfig() { | |
79 if (session_.session_handle && http_client_.get()) { | |
80 http_client_->Close(session_.session_handle); | |
81 session_.session_handle = NULL; | |
82 } | |
83 Clear(); | |
84 } | |
85 | |
86 // Initialize creates or opens a global lock to synchronize access to | |
87 // registry where CUP credentials are stored. Each user including non-elevated | |
88 // admins stores network configuration data, such as the CUP password in | |
89 // its HKCU. The admin users, including the LOCAL_SYSTEM, store data in HKLM. | |
90 // Therefore, the naming of the global lock is different: users have their | |
91 // lock postfixed with their sid, so the serialization only occurs within the | |
92 // same user's programs. Admin users use the same named lock since they store | |
93 // data in a shared HKLM. The data of the admin users is disambiguated by | |
94 // postfixing their registry sub key with sids. | |
95 // In conclusion, users have sid-postfixed locks and their data goes in | |
96 // their respective HKCU. Admin users have the same lock and their data goes | |
97 // under HKLM in sid-postfixed stores. | |
98 // | |
99 // The named lock is created in the global namespace to account for users | |
100 // logging in from different TS sessions. | |
101 // | |
102 // The CUP credentials must be protected with ACLs so non-elevated admins can't | |
103 // read elevated-admins' keys and attack the protocol. | |
104 // | |
105 // Also, an Internet session is created. | |
106 HRESULT NetworkConfig::Initialize() { | |
107 ASSERT1(!is_initialized_); | |
108 | |
109 http_client_.reset(CreateHttpClient()); | |
110 ASSERT1(http_client_.get()); | |
111 if (!http_client_.get()) { | |
112 NET_LOG(LE, (_T("[CreateHttpClient failed]"))); | |
113 return E_UNEXPECTED; | |
114 } | |
115 HRESULT hr = http_client_->Initialize(); | |
116 if (FAILED(hr)) { | |
117 // TODO(omaha): This makes an assumption that only WinHttp is | |
118 // supported by the network code. | |
119 NET_LOG(LE, (_T("[http_client_->Initialize() failed][0x%x]"), hr)); | |
120 return OMAHA_NET_E_WINHTTP_NOT_AVAILABLE; | |
121 } | |
122 | |
123 // Initializes the WinHttp session and configures WinHttp to work in | |
124 // asynchronous mode. In this mode, the network requests are non-blocking | |
125 // and asynchronous events are generated when a request is complete. | |
126 hr = http_client_->Open(NULL, | |
127 WINHTTP_ACCESS_TYPE_NO_PROXY, | |
128 WINHTTP_NO_PROXY_NAME, | |
129 WINHTTP_NO_PROXY_BYPASS, | |
130 WINHTTP_FLAG_ASYNC, | |
131 &session_.session_handle); | |
132 if (FAILED(hr)) { | |
133 NET_LOG(LE, (_T("[http_client_->Open() failed][0x%x]"), hr)); | |
134 return hr; | |
135 } | |
136 | |
137 Add(new UpdateDevProxyDetector); | |
138 BrowserType browser_type(BROWSER_UNKNOWN); | |
139 GetDefaultBrowserType(&browser_type); | |
140 if (browser_type == BROWSER_FIREFOX) { | |
141 Add(new FirefoxProxyDetector); | |
142 } | |
143 // There is no Chrome detector because it uses the same proxy settings as IE. | |
144 Add(new IEProxyDetector); | |
145 Add(new DefaultProxyDetector); | |
146 | |
147 // Use a global network configuration override if available. | |
148 ConfigManager* config_manager = ConfigManager::Instance(); | |
149 CString net_config; | |
150 if (SUCCEEDED(config_manager->GetNetConfig(&net_config))) { | |
151 ProxyConfig config_override = NetworkConfig::ParseNetConfig(net_config); | |
152 SetConfigurationOverride(&config_override); | |
153 } | |
154 | |
155 ConfigureProxyAuth(); | |
156 | |
157 is_initialized_ = true; | |
158 return S_OK; | |
159 } | |
160 | |
161 void NetworkConfig::Add(ProxyDetectorInterface* detector) { | |
162 ASSERT1(detector); | |
163 __mutexBlock(lock_) { | |
164 detectors_.push_back(detector); | |
165 } | |
166 } | |
167 | |
168 void NetworkConfig::Clear() { | |
169 __mutexBlock(lock_) { | |
170 for (size_t i = 0; i != detectors_.size(); ++i) { | |
171 delete detectors_[i]; | |
172 } | |
173 detectors_.clear(); | |
174 configurations_.clear(); | |
175 } | |
176 } | |
177 | |
178 HRESULT NetworkConfig::Detect() { | |
179 __mutexBlock(lock_) { | |
180 std::vector<ProxyConfig> configurations; | |
181 | |
182 for (size_t i = 0; i != detectors_.size(); ++i) { | |
183 ProxyConfig config; | |
184 if (SUCCEEDED(detectors_[i]->Detect(&config))) { | |
185 configurations.push_back(config); | |
186 } | |
187 } | |
188 configurations_.swap(configurations); | |
189 } | |
190 | |
191 return S_OK; | |
192 } | |
193 | |
194 void NetworkConfig::SortProxies(std::vector<ProxyConfig>* configurations) { | |
195 ASSERT1(configurations); | |
196 | |
197 std::stable_sort(configurations->begin(), configurations->end(), | |
198 ProxySortPredicate); | |
199 } | |
200 | |
201 HRESULT NetworkConfig::ConfigFromIdentifier(const CString& id, | |
202 ProxyConfig* config) { | |
203 ASSERT1(config); | |
204 | |
205 *config = ProxyConfig(); | |
206 if (id == kWPADIdentifier) { | |
207 config->source = kWPADIdentifier; | |
208 config->auto_detect = true; | |
209 } else if (id == kDirectConnectionIdentifier) { | |
210 config->source = kDirectConnectionIdentifier; | |
211 } else { | |
212 return HRESULT_FROM_WIN32(ERROR_NOT_FOUND); | |
213 } | |
214 | |
215 return S_OK; | |
216 } | |
217 | |
218 void NetworkConfig::AppendLastKnownGoodProxyConfig( | |
219 std::vector<ProxyConfig>* configurations) const { | |
220 ASSERT1(configurations); | |
221 ProxyConfig last_known_good_config; | |
222 if (SUCCEEDED(LoadProxyConfig(&last_known_good_config))) { | |
223 configurations->push_back(last_known_good_config); | |
224 } | |
225 } | |
226 | |
227 void NetworkConfig::AppendStaticProxyConfigs( | |
228 std::vector<ProxyConfig>* configurations) { | |
229 ASSERT1(configurations); | |
230 ProxyConfig config; | |
231 | |
232 HRESULT hr = ConfigFromIdentifier(kWPADIdentifier, &config); | |
233 if (SUCCEEDED(hr)) { | |
234 configurations->push_back(config); | |
235 } | |
236 | |
237 hr = ConfigFromIdentifier(kDirectConnectionIdentifier, &config); | |
238 if (SUCCEEDED(hr)) { | |
239 configurations->push_back(config); | |
240 } | |
241 } | |
242 | |
243 HRESULT NetworkConfig::Detect(const CString& proxy_source, | |
244 ProxyConfig* config) const { | |
245 ASSERT1(config); | |
246 __mutexBlock(lock_) { | |
247 std::vector<ProxyConfig> configurations; | |
248 for (size_t i = 0; i != detectors_.size(); ++i) { | |
249 if (proxy_source == detectors_[i]->source()) { | |
250 return detectors_[i]->Detect(config); | |
251 } | |
252 } | |
253 } | |
254 | |
255 return HRESULT_FROM_WIN32(ERROR_NOT_FOUND); | |
256 } | |
257 | |
258 std::vector<ProxyConfig> NetworkConfig::GetConfigurations() const { | |
259 std::vector<ProxyConfig> configurations; | |
260 __mutexBlock(lock_) { | |
261 configurations = configurations_; | |
262 } | |
263 return configurations; | |
264 } | |
265 | |
266 HRESULT NetworkConfig::GetConfigurationOverride(ProxyConfig* config) { | |
267 ASSERT1(config); | |
268 __mutexBlock(lock_) { | |
269 if (configuration_override_.get()) { | |
270 *config = *configuration_override_; | |
271 return S_OK; | |
272 } | |
273 } | |
274 return E_FAIL; | |
275 } | |
276 | |
277 void NetworkConfig::SetConfigurationOverride( | |
278 const ProxyConfig* configuration_override) { | |
279 __mutexBlock(lock_) { | |
280 if (configuration_override) { | |
281 configuration_override_.reset(new ProxyConfig); | |
282 *configuration_override_ = *configuration_override; | |
283 configuration_override_->source = _T("updatedev/netconfig"); | |
284 } else { | |
285 configuration_override_.reset(); | |
286 } | |
287 } | |
288 } | |
289 | |
290 HRESULT NetworkConfig::GetCupCredentials( | |
291 CupCredentials* cup_credentials) const { | |
292 return NetworkConfigManager::Instance().GetCupCredentials(cup_credentials); | |
293 } | |
294 | |
295 HRESULT NetworkConfig::SetCupCredentials( | |
296 const CupCredentials* cup_credentials) const { | |
297 NetworkConfigManager& network_manager = NetworkConfigManager::Instance(); | |
298 if (cup_credentials == NULL) { | |
299 network_manager.ClearCupCredentials(); | |
300 return S_OK; | |
301 } | |
302 | |
303 return network_manager.SetCupCredentials(*cup_credentials); | |
304 } | |
305 | |
306 // Serializes configurations for debugging purposes. | |
307 CString NetworkConfig::ToString(const ProxyConfig& config) { | |
308 CString result; | |
309 result.AppendFormat(_T("priority=%u, source=%s, "), | |
310 config.priority, config.source); | |
311 | |
312 switch (GetAccessType(config)) { | |
313 case WINHTTP_ACCESS_TYPE_NO_PROXY: | |
314 result.AppendFormat(_T("direct connection")); | |
315 break; | |
316 case WINHTTP_ACCESS_TYPE_NAMED_PROXY: | |
317 result.AppendFormat(_T("named proxy=%s, bypass=%s"), | |
318 config.proxy, config.proxy_bypass); | |
319 break; | |
320 case WINHTTP_ACCESS_TYPE_AUTO_DETECT: | |
321 result.AppendFormat(_T("wpad=%d, script=%s"), | |
322 config.auto_detect, config.auto_config_url); | |
323 break; | |
324 default: | |
325 ASSERT1(false); | |
326 break; | |
327 } | |
328 return result; | |
329 } | |
330 | |
331 CString NetworkConfig::ToString(const std::vector<ProxyConfig>& config) { | |
332 CString result; | |
333 for (size_t i = 0; i != config.size(); ++i) { | |
334 result.Append(NetworkConfig::ToString(config[i])); | |
335 result.Append(_T("\r\n")); | |
336 } | |
337 return result; | |
338 } | |
339 | |
340 int NetworkConfig::GetAccessType(const ProxyConfig& config) { | |
341 if (config.auto_detect || !config.auto_config_url.IsEmpty()) { | |
342 return WINHTTP_ACCESS_TYPE_AUTO_DETECT; | |
343 } else if (!config.proxy.IsEmpty()) { | |
344 return WINHTTP_ACCESS_TYPE_NAMED_PROXY; | |
345 } else { | |
346 return WINHTTP_ACCESS_TYPE_NO_PROXY; | |
347 } | |
348 } | |
349 | |
350 bool NetworkConfig::IsUsingCupTestKeys() { | |
351 DWORD value = 0; | |
352 if (SUCCEEDED(RegKey::GetValue(MACHINE_REG_UPDATE_DEV, | |
353 kRegValueCupKeys, | |
354 &value))) { | |
355 return value != 0; | |
356 } else { | |
357 return false; | |
358 } | |
359 } | |
360 | |
361 void NetworkConfig::ConfigureProxyAuth() { | |
362 const uint32 kProxyMaxPrompts = 1; | |
363 return proxy_auth_.ConfigureProxyAuth(is_machine_, kProxyMaxPrompts); | |
364 } | |
365 | |
366 bool NetworkConfig::GetProxyCredentials(bool allow_ui, | |
367 bool force_ui, | |
368 const CString& proxy_settings, | |
369 const ProxyAuthConfig& config, | |
370 bool is_https, | |
371 CString* username, | |
372 CString* password, | |
373 uint32* auth_scheme) { | |
374 ASSERT1(username); | |
375 ASSERT1(password); | |
376 ASSERT1(auth_scheme); | |
377 | |
378 const CString& proxy = ProxyAuth::ExtractProxy(proxy_settings, is_https); | |
379 return proxy_auth_.GetProxyCredentials(allow_ui, force_ui, proxy, | |
380 config, username, | |
381 password, auth_scheme); | |
382 } | |
383 | |
384 HRESULT NetworkConfig::SetProxyAuthScheme(const CString& proxy_settings, | |
385 bool is_https, | |
386 uint32 auth_scheme) { | |
387 ASSERT1(auth_scheme != UNKNOWN_AUTH_SCHEME); | |
388 const CString& proxy = ProxyAuth::ExtractProxy(proxy_settings, is_https); | |
389 return proxy_auth_.SetProxyAuthScheme(proxy, auth_scheme); | |
390 } | |
391 | |
392 // TODO(omaha): the code does WPAD auto detect in all cases. It is possible for | |
393 // a configuration to specify no auto detection but provide the proxy script | |
394 // url. The current code does not account for this yet. | |
395 HRESULT NetworkConfig::GetProxyForUrl(const CString& url, | |
396 const CString& auto_config_url, | |
397 HttpClient::ProxyInfo* proxy_info) { | |
398 ASSERT1(proxy_info); | |
399 | |
400 NET_LOG(L3, (_T("[NetworkConfig::GetProxyForUrl][%s]"), url)); | |
401 | |
402 HttpClient::AutoProxyOptions auto_proxy_options = {0}; | |
403 auto_proxy_options.flags = WINHTTP_AUTOPROXY_AUTO_DETECT; | |
404 auto_proxy_options.auto_detect_flags = WINHTTP_AUTO_DETECT_TYPE_DHCP | | |
405 WINHTTP_AUTO_DETECT_TYPE_DNS_A; | |
406 if (!auto_config_url.IsEmpty()) { | |
407 auto_proxy_options.auto_config_url = auto_config_url; | |
408 auto_proxy_options.flags |= WINHTTP_AUTOPROXY_CONFIG_URL; | |
409 } | |
410 auto_proxy_options.auto_logon_if_challenged = true; | |
411 | |
412 HRESULT hr = http_client_->GetProxyForUrl(session_.session_handle, | |
413 url, | |
414 &auto_proxy_options, | |
415 proxy_info); | |
416 | |
417 if (FAILED(hr) && ::UrlIsFileUrl(auto_config_url)) { | |
418 // Some HttpClient implementations, namely WinHTTP, only support PAC files | |
419 // with http or https schemes. Attempt an alternate resolution scheme using | |
420 // jsproxy.dll if the initial attempt fails. | |
421 ASSERT1(user_info::IsThreadImpersonating()); | |
422 | |
423 CString local_file; | |
424 hr = ConvertFileUriToLocalPath(auto_config_url, &local_file); | |
425 if (FAILED(hr)) { | |
426 NET_LOG(LE, (_T("[ConvertFileUriToLocalPath failed][0x%08x]"), hr)); | |
427 return hr; | |
428 } | |
429 | |
430 hr = GetProxyForUrlLocal(url, local_file, proxy_info); | |
431 } | |
432 | |
433 return hr; | |
434 } | |
435 | |
436 CString NetworkConfig::GetUserAgent() { | |
437 CString user_agent; | |
438 user_agent.Format(kUserAgent, GetVersionString()); | |
439 return user_agent; | |
440 } | |
441 | |
442 CString NetworkConfig::GetMID() { | |
443 CString mid; | |
444 RegKey::GetValue(MACHINE_REG_UPDATE_DEV, kRegValueMID, &mid); | |
445 return mid; | |
446 } | |
447 | |
448 CString NetworkConfig::JoinStrings(const TCHAR* s1, | |
449 const TCHAR* s2, | |
450 const TCHAR* delim) { | |
451 CString result; | |
452 const TCHAR* components[] = {s1, s2}; | |
453 JoinStringsInArray(components, arraysize(components), delim, &result); | |
454 return result; | |
455 } | |
456 | |
457 // Using std::hash_set adds about 2K uncompressed code size. Using a CAtlMap | |
458 // adds about 1.5K. Usually, there are only five detected configurations so | |
459 // an O(n^2) algorithm would work well. The advantage of the current | |
460 // implementation is simplicity. It also does not handle conflicts. Conflicts | |
461 // are not expected, due to how the ProxyConfig structure is being used. | |
462 // TODO(omaha): consider not using the hash_set and save about 1K of code. | |
463 void NetworkConfig::RemoveDuplicates(std::vector<ProxyConfig>* config) { | |
464 ASSERT1(config); | |
465 | |
466 // Iterate over the input configurations, remember the hash of each | |
467 // distinct configuration, and remove the duplicates by skipping the | |
468 // configurations seen before. | |
469 std::vector<ProxyConfig> input(*config); | |
470 config->clear(); | |
471 | |
472 typedef stdext::hash_set<uint32> Keys; | |
473 Keys keys; | |
474 for (size_t i = 0; i != input.size(); ++i) { | |
475 std::pair<Keys::iterator, bool> result(keys.insert(hash_value(input[i]))); | |
476 if (result.second) { | |
477 config->push_back(input[i]); | |
478 } | |
479 } | |
480 } | |
481 | |
482 HRESULT NetworkConfig::CreateProxyConfigRegKey(RegKey* key) { | |
483 ASSERT1(key); | |
484 CString config_root; | |
485 | |
486 if (!user_info::IsRunningAsSystem()) { | |
487 scoped_hkey user_root_key; | |
488 HRESULT hr = ::RegOpenCurrentUser(KEY_READ | KEY_WRITE, | |
489 address(user_root_key)); | |
490 if (FAILED(hr)) { | |
491 return hr; | |
492 } | |
493 return key->Create(get(user_root_key), kRegKeyProxy); | |
494 } else { | |
495 return key->Create(HKEY_LOCAL_MACHINE, kRegKeyProxy); | |
496 } | |
497 } | |
498 | |
499 HRESULT NetworkConfig::SaveProxyConfig(const ProxyConfig& config) { | |
500 const CString& new_configuration = config.source; | |
501 NET_LOG(L3, (_T("[NetworkConfig::SaveProxyConfig][%s]"), new_configuration)); | |
502 | |
503 RegKey key; | |
504 HRESULT hr = CreateProxyConfigRegKey(&key); | |
505 if (FAILED(hr)) { | |
506 return hr; | |
507 } | |
508 | |
509 CString current_configuration; | |
510 if (SUCCEEDED(key.GetValue(kRegValueSource, ¤t_configuration)) && | |
511 current_configuration != new_configuration) { | |
512 NET_LOG(L3, (_T("[Network configuration changed from %s to %s"), | |
513 current_configuration, new_configuration)); | |
514 } | |
515 | |
516 return key.SetValue(kRegValueSource, new_configuration); | |
517 } | |
518 | |
519 HRESULT NetworkConfig::LoadProxyConfig(ProxyConfig* config) const { | |
520 ASSERT1(config); | |
521 | |
522 *config = ProxyConfig(); | |
523 | |
524 RegKey key; | |
525 HRESULT hr = CreateProxyConfigRegKey(&key); | |
526 if (FAILED(hr)) { | |
527 return hr; | |
528 } | |
529 | |
530 CString source; | |
531 hr = key.GetValue(kRegValueSource, &source); | |
532 if (FAILED(hr)) { | |
533 return hr; | |
534 } | |
535 | |
536 hr = NetworkConfig::ConfigFromIdentifier(source, config); | |
537 if (FAILED(hr)) { | |
538 hr = Detect(source, config); | |
539 if (FAILED(hr)) { | |
540 return hr; | |
541 } | |
542 } | |
543 | |
544 config->priority = ProxyConfig::PROXY_PRIORITY_LAST_KNOWN_GOOD; | |
545 | |
546 return S_OK; | |
547 } | |
548 | |
549 ProxyConfig NetworkConfig::ParseNetConfig(const CString& net_config) { | |
550 ProxyConfig config; | |
551 int pos(0); | |
552 CString token = net_config.Tokenize(_T(";"), pos); | |
553 while (pos != -1) { | |
554 CString name, value; | |
555 if (ParseNameValuePair(token, _T('='), &name, &value)) { | |
556 bool auto_detect(false); | |
557 if (name == _T("wpad") && | |
558 SUCCEEDED(String_StringToBool(value, &auto_detect))) { | |
559 config.auto_detect = auto_detect; | |
560 } else if (name == _T("script")) { | |
561 config.auto_config_url = value; | |
562 } else if (name == _T("proxy")) { | |
563 config.proxy = value; | |
564 } | |
565 } | |
566 token = net_config.Tokenize(_T(";"), pos); | |
567 } | |
568 return config; | |
569 } | |
570 | |
571 // Note: The jsproxy functions are exposed to public users as part of the | |
572 // DOJ consent decree and are not formally supported by Microsoft. | |
573 | |
574 GPA_WRAP(jsproxy.dll, | |
575 InternetInitializeAutoProxyDll, | |
576 (DWORD dwVersion, LPSTR lpszDownloadedTempFile, LPSTR lpszMime, LPCVOID
lpAutoProxyCallbacks, LPCVOID lpAutoProxyScriptBuffer), // NOLINT | |
577 (dwVersion, lpszDownloadedTempFile, lpszMime, lpAutoProxyCallbacks, lpA
utoProxyScriptBuffer), // NOLINT | |
578 WINAPI, | |
579 BOOL, | |
580 FALSE); | |
581 | |
582 GPA_WRAP(jsproxy.dll, | |
583 InternetGetProxyInfo, | |
584 (LPCSTR lpszUrl, DWORD dwUrlLength, LPSTR lpszUrlHostName, DWORD dwUrlH
ostNameLength, LPSTR *lplpszProxyHostName, LPDWORD lpdwProxyHostNameLength), //
NOLINT | |
585 (lpszUrl, dwUrlLength, lpszUrlHostName, dwUrlHostNameLength, lplpszProx
yHostName, lpdwProxyHostNameLength), // NOLINT | |
586 WINAPI, | |
587 BOOL, | |
588 FALSE); | |
589 | |
590 GPA_WRAP(jsproxy.dll, | |
591 InternetDeInitializeAutoProxyDll, | |
592 (LPSTR lpszMime, DWORD dwReserved), | |
593 (lpszMime, dwReserved), | |
594 WINAPI, | |
595 BOOL, | |
596 FALSE); | |
597 | |
598 HRESULT NetworkConfig::GetProxyForUrlLocal(const CString& url, | |
599 const CString& path_to_pac_file, | |
600 HttpClient::ProxyInfo* proxy_info) { | |
601 scoped_library jsproxy_lib(::LoadLibrary(_T("jsproxy.dll"))); | |
602 ASSERT1(jsproxy_lib); | |
603 if (!jsproxy_lib) { | |
604 HRESULT hr = HRESULTFromLastError(); | |
605 NET_LOG(LE, (_T("[GetProxyForUrlLocal][jsproxy not loaded][0x%08x]"), hr)); | |
606 return hr; | |
607 } | |
608 | |
609 // Convert the inputs to ANSI, and call into JSProxy to execute the PAC | |
610 // script; we should get back out a PAC-format list of proxies to use. | |
611 // | |
612 // TODO(omaha3): The MSDN prototypes specify LPSTR, and I've assumed this | |
613 // implies CP_ACP. However, depending on how this was implemented internally, | |
614 // conversion to UTF8 might work better. Investigate this later and confirm. | |
615 CStringA path_a(path_to_pac_file); | |
616 | |
617 if (FALSE == InternetInitializeAutoProxyDllWrap(0, CStrBufA(path_a, MAX_PATH), | |
618 NULL, NULL, NULL)) { | |
619 HRESULT hr = HRESULTFromLastError(); | |
620 NET_LOG(LE, (_T("[GetProxyForUrlLocal][jsproxy init failed][0x%08x]"), hr)); | |
621 return hr; | |
622 } | |
623 | |
624 ON_SCOPE_EXIT(InternetDeInitializeAutoProxyDllWrap, (LPSTR)NULL, 0); | |
625 | |
626 CStringA url_a(url); | |
627 CStringA url_hostname_a(GetUriHostNameHostOnly(url, false)); | |
628 | |
629 scoped_hglobal proxy_ptr; | |
630 DWORD proxy_len = 0; | |
631 if (FALSE == InternetGetProxyInfoWrap( | |
632 url_a, | |
633 url_a.GetLength(), | |
634 CStrBufA(url_hostname_a, url_hostname_a.GetLength()), | |
635 url_hostname_a.GetLength(), | |
636 reinterpret_cast<LPSTR*>(address(proxy_ptr)), | |
637 &proxy_len)) { | |
638 HRESULT hr = HRESULTFromLastError(); | |
639 NET_LOG(LE, (_T("[GetProxyForUrlLocal][jsproxy failed][0x%08x]"), hr)); | |
640 return hr; | |
641 } | |
642 | |
643 ASSERT1(proxy_ptr && proxy_len > 0); | |
644 CStringA proxy(reinterpret_cast<LPSTR>(get(proxy_ptr)), proxy_len); | |
645 ConvertPacResponseToProxyInfo(proxy, proxy_info); | |
646 return S_OK; | |
647 } | |
648 | |
649 void NetworkConfig::ConvertPacResponseToProxyInfo( | |
650 const CStringA& response, | |
651 HttpClient::ProxyInfo* proxy_info) { | |
652 ASSERT1(proxy_info); | |
653 | |
654 NET_LOG(L4, (_T("[ConvertPacResponseToProxyInfo][%s]"), CString(response))); | |
655 | |
656 // The proxy list response from a PAC file for a file is a string of proxies | |
657 // to attempt in order, delimited by semicolons, with a keyword denoting how | |
658 // to use the proxy. For example: | |
659 // | |
660 // PROXY prx1.samp.com; PROXY prx2.test.com:8080; SOCKS prx3.test.com; DIRECT | |
661 // | |
662 // We convert this to a direct semicolon-separated list of host/ports. We | |
663 // stop parsing if we see DIRECT; we omit any non-PROXY entries. | |
664 CString proxy_list; | |
665 for (int start = 0; start >= 0 && start < response.GetLength();) { | |
666 int semi_pos = response.Find(';', start); | |
667 if (semi_pos < 0) { | |
668 semi_pos = response.GetLength(); | |
669 } | |
670 | |
671 CStringA entry = response.Mid(start, semi_pos - start).Trim().MakeLower(); | |
672 if (entry == "direct") { | |
673 break; | |
674 } | |
675 if (0 == entry.Find("proxy ")) { | |
676 // This is a valid proxy entry. Strip the leading "PROXY " and add it | |
677 // to our parsed list. | |
678 if (!proxy_list.IsEmpty()) { | |
679 proxy_list.AppendChar(_T(';')); | |
680 } | |
681 proxy_list.Append(CString(entry.Mid(6))); | |
682 } | |
683 | |
684 start = semi_pos + 1; | |
685 } | |
686 | |
687 if (proxy_list.IsEmpty()) { | |
688 proxy_info->access_type = WINHTTP_ACCESS_TYPE_NO_PROXY; | |
689 proxy_info->proxy = NULL; | |
690 proxy_info->proxy_bypass = NULL; | |
691 } else { | |
692 // The convention is that any strings in a WINHTTP_PROXY_INFO are expected | |
693 // to be freed by the caller using GlobalFree(). Convert our intermediary | |
694 // CString to a GlobalAlloc() buffer and write that out. | |
695 size_t list_len = (proxy_list.GetLength() + 1) * sizeof(TCHAR); | |
696 TCHAR* list_hglob = reinterpret_cast<TCHAR*>(::GlobalAlloc(GPTR, list_len)); | |
697 memcpy(list_hglob, proxy_list.GetString(), list_len); | |
698 proxy_info->access_type = WINHTTP_ACCESS_TYPE_NAMED_PROXY; | |
699 proxy_info->proxy = list_hglob; | |
700 proxy_info->proxy_bypass = NULL; | |
701 } | |
702 } | |
703 | |
704 const NetworkConfigManager* const NetworkConfigManager::kInvalidInstance = | |
705 reinterpret_cast<const NetworkConfigManager* const>(-1); | |
706 NetworkConfigManager* NetworkConfigManager::instance_ = NULL; | |
707 LLock NetworkConfigManager::instance_lock_; | |
708 bool NetworkConfigManager::is_machine_ = false; | |
709 | |
710 NetworkConfigManager::NetworkConfigManager() { | |
711 } | |
712 | |
713 NetworkConfigManager::~NetworkConfigManager() { | |
714 SaveCupCredentialsToRegistry(); | |
715 } | |
716 | |
717 HRESULT NetworkConfigManager::CreateInstance() { | |
718 __mutexScope(instance_lock_); | |
719 ASSERT1(instance_ != kInvalidInstance); | |
720 if (!instance_) { | |
721 NET_LOG(L1, (_T("[NetworkConfigManager::CreateInstance][is_machine: %d]"), | |
722 is_machine_)); | |
723 instance_ = new NetworkConfigManager(); | |
724 VERIFY1(SUCCEEDED(instance_->InitializeLock())); | |
725 VERIFY1(SUCCEEDED(instance_->InitializeRegistryKey())); | |
726 instance_->LoadCupCredentialsFromRegistry(); | |
727 } | |
728 | |
729 return S_OK; | |
730 } | |
731 | |
732 void NetworkConfigManager::DeleteInstance() { | |
733 ASSERT1(instance_ != kInvalidInstance); | |
734 | |
735 NetworkConfigManager* instance = | |
736 omaha::interlocked_exchange_pointer(&instance_, kInvalidInstance); | |
737 | |
738 if (kInvalidInstance != instance && NULL != instance) { | |
739 instance->DeleteInstanceInternal(); | |
740 delete instance; | |
741 } | |
742 } | |
743 | |
744 NetworkConfigManager& NetworkConfigManager::Instance() { | |
745 __mutexScope(instance_lock_); | |
746 if (!instance_) { | |
747 VERIFY1(SUCCEEDED(NetworkConfigManager::CreateInstance())); | |
748 } | |
749 return *instance_; | |
750 } | |
751 | |
752 void NetworkConfigManager::set_is_machine(bool is_machine) { | |
753 __mutexScope(instance_lock_); | |
754 if (instance_) { | |
755 NET_LOG(LE, (_T("set_is_machine called after instance created."))); | |
756 } | |
757 | |
758 is_machine_ = is_machine; | |
759 } | |
760 | |
761 void NetworkConfigManager::DeleteInstanceInternal() { | |
762 __mutexBlock(lock_) { | |
763 std::map<CString, NetworkConfig*>::iterator it; | |
764 | |
765 for (it = user_network_config_map_.begin(); | |
766 it != user_network_config_map_.end(); | |
767 ++it) { | |
768 if (NULL != it->second) { | |
769 delete it->second; | |
770 } | |
771 } | |
772 user_network_config_map_.clear(); | |
773 } | |
774 } | |
775 | |
776 HRESULT NetworkConfigManager::GetUserNetworkConfig( | |
777 NetworkConfig** network_config) { | |
778 CString sid; | |
779 HRESULT hr = user_info::GetEffectiveUserSid(&sid); | |
780 if (FAILED(hr)) { | |
781 NET_LOG(LE, (_T("[GetEffectiveUserSid failed][0x%x]"), hr)); | |
782 return hr; | |
783 } | |
784 | |
785 __mutexBlock(lock_) { | |
786 std::map<CString, NetworkConfig*>::iterator it; | |
787 it = user_network_config_map_.find(sid); | |
788 | |
789 if (user_network_config_map_.end() != it) { | |
790 *network_config = it->second; | |
791 return S_OK; | |
792 } | |
793 | |
794 hr = CreateNetworkConfigInstance(network_config, is_machine_); | |
795 if (SUCCEEDED(hr)) { | |
796 user_network_config_map_.insert(std::make_pair(sid, *network_config)); | |
797 } | |
798 | |
799 return hr; | |
800 } | |
801 | |
802 return E_FAIL; | |
803 } | |
804 | |
805 HRESULT NetworkConfigManager::CreateNetworkConfigInstance( | |
806 NetworkConfig** network_config_ptr, | |
807 bool is_machine) { | |
808 ASSERT1(network_config_ptr); | |
809 | |
810 NetworkConfig* network_config(new NetworkConfig(is_machine)); | |
811 HRESULT hr = network_config->Initialize(); | |
812 if (FAILED(hr)) { | |
813 NET_LOG(LE, (_T("[NetworkConfig::Initialize() failed][0x%x]"), hr)); | |
814 delete network_config; | |
815 return hr; | |
816 } | |
817 | |
818 *network_config_ptr = network_config; | |
819 return S_OK; | |
820 } | |
821 | |
822 HRESULT NetworkConfigManager::InitializeLock() { | |
823 NamedObjectAttributes lock_attr; | |
824 GetNamedObjectAttributes(kNetworkConfigLock, is_machine_, &lock_attr); | |
825 return global_lock_.InitializeWithSecAttr(lock_attr.name, &lock_attr.sa) ? | |
826 S_OK : E_FAIL; | |
827 } | |
828 | |
829 HRESULT NetworkConfigManager::InitializeRegistryKey() { | |
830 // The registry path under which to store persistent network configuration. | |
831 // The "network" subkey is created with default security. Below "network", | |
832 // the "secure" key is created so that only system and administrators have | |
833 // access to it. | |
834 CString reg_path = is_machine_ ? MACHINE_REG_UPDATE : USER_REG_UPDATE; | |
835 reg_path = AppendRegKeyPath(reg_path, kNetworkSubkey); | |
836 RegKey reg_key_network; | |
837 DWORD disposition = 0; | |
838 HRESULT hr = reg_key_network.Create(reg_path, | |
839 NULL, // Class. | |
840 0, // Options. | |
841 KEY_CREATE_SUB_KEY, // SAM desired. | |
842 NULL, // Security attrs. | |
843 &disposition); | |
844 if (FAILED(hr)) { | |
845 return hr; | |
846 } | |
847 | |
848 // When initializing for machine, grant access to administrators and system. | |
849 scoped_ptr<CSecurityAttributes> sa; | |
850 if (is_machine_) { | |
851 sa.reset(new CSecurityAttributes); | |
852 GetAdminDaclSecurityAttributes(sa.get(), GENERIC_ALL); | |
853 } | |
854 | |
855 disposition = 0; | |
856 RegKey reg_key_network_secure; | |
857 hr = reg_key_network_secure.Create(reg_key_network.Key(), // Parent. | |
858 kNetworkCupSubkey, // Subkey name. | |
859 NULL, // Class. | |
860 0, // Options. | |
861 KEY_READ, // SAM desired. | |
862 sa.get(), // Security attrs. | |
863 &disposition); | |
864 if (FAILED(hr)) { | |
865 return hr; | |
866 } | |
867 return S_OK; | |
868 } | |
869 | |
870 HRESULT NetworkConfigManager::SetCupCredentials( | |
871 const CupCredentials& cup_credentials) { | |
872 __mutexScope(lock_); | |
873 | |
874 const std::vector<uint8>& sk_in(cup_credentials.sk); | |
875 | |
876 if (sk_in.empty()) { | |
877 return E_INVALIDARG; | |
878 } | |
879 | |
880 std::vector<uint8> sk_out; | |
881 HRESULT hr = EncryptData(NULL, 0, &sk_in.front(), sk_in.size(), &sk_out); | |
882 if (FAILED(hr)) { | |
883 return hr; | |
884 } | |
885 | |
886 cup_credentials_.reset(new CupCredentials); | |
887 | |
888 cup_credentials_->sk.swap(sk_out); | |
889 cup_credentials_->c.SetString(cup_credentials.c); | |
890 | |
891 return S_OK; | |
892 } | |
893 | |
894 HRESULT NetworkConfigManager::GetCupCredentials( | |
895 CupCredentials* cup_credentials) { | |
896 ASSERT1(cup_credentials); | |
897 __mutexScope(lock_); | |
898 if (cup_credentials_ == NULL || cup_credentials_->sk.empty()) { | |
899 return HRESULT_FROM_WIN32(ERROR_NOT_FOUND); | |
900 } | |
901 | |
902 std::vector<uint8> decrypted_sk; | |
903 HRESULT hr = DecryptData(NULL, | |
904 0, | |
905 &cup_credentials_->sk.front(), | |
906 cup_credentials_->sk.size(), | |
907 &decrypted_sk); | |
908 if (FAILED(hr)) { | |
909 return hr; | |
910 } | |
911 cup_credentials->sk.swap(decrypted_sk); | |
912 cup_credentials->c.SetString(cup_credentials_->c); | |
913 | |
914 return S_OK; | |
915 } | |
916 | |
917 // This function should be called in singleton creation stage, | |
918 // thus no lock is needed. | |
919 HRESULT NetworkConfigManager::LoadCupCredentialsFromRegistry() { | |
920 __mutexScope(global_lock_); | |
921 | |
922 CString reg_path = is_machine_ ? MACHINE_REG_UPDATE : USER_REG_UPDATE; | |
923 reg_path = AppendRegKeyPath(reg_path, kNetworkSubkey); | |
924 CString key_name = AppendRegKeyPath(reg_path, kNetworkCupSubkey); | |
925 RegKey reg_key; | |
926 HRESULT hr = reg_key.Open(key_name, KEY_READ); | |
927 if (FAILED(hr)) { | |
928 return hr; | |
929 } | |
930 scoped_array<byte> buf; | |
931 DWORD buf_length = 0; | |
932 hr = reg_key.GetValue(kCupClientSecretKey, address(buf), &buf_length); | |
933 if (FAILED(hr)) { | |
934 return hr; | |
935 } | |
936 CString cookie; | |
937 hr = reg_key.GetValue(kCupClientCookie, &cookie); | |
938 if (FAILED(hr)) { | |
939 return hr; | |
940 } | |
941 if (buf_length == 0) { | |
942 return E_FAIL; | |
943 } | |
944 cup_credentials_.reset(new CupCredentials); | |
945 cup_credentials_->sk.resize(buf_length); | |
946 memcpy(&cup_credentials_->sk.front(), buf.get(), buf_length); | |
947 cup_credentials_->c = CT2A(cookie); | |
948 | |
949 return S_OK; | |
950 } | |
951 | |
952 // This function is called in the destructor only thus no lock is needed. | |
953 HRESULT NetworkConfigManager::SaveCupCredentialsToRegistry() { | |
954 __mutexScope(global_lock_); | |
955 | |
956 CString reg_path = is_machine_ ? MACHINE_REG_UPDATE : USER_REG_UPDATE; | |
957 reg_path = AppendRegKeyPath(reg_path, kNetworkSubkey); | |
958 CString key_name = AppendRegKeyPath(reg_path, kNetworkCupSubkey); | |
959 RegKey reg_key; | |
960 HRESULT hr = reg_key.Open(key_name, KEY_WRITE); | |
961 if (FAILED(hr)) { | |
962 NET_LOG(L2, (_T("[Registry key open failed][%s][0x%08x]"), key_name, hr)); | |
963 return hr; | |
964 } | |
965 | |
966 if (cup_credentials_ == NULL || cup_credentials_->sk.empty()) { | |
967 HRESULT hr1 = reg_key.DeleteValue(kCupClientSecretKey); | |
968 HRESULT hr2 = reg_key.DeleteValue(kCupClientCookie); | |
969 return (SUCCEEDED(hr1) && SUCCEEDED(hr2)) ? S_OK : HRESULTFromLastError(); | |
970 } | |
971 | |
972 hr = reg_key.SetValue(kCupClientSecretKey, | |
973 static_cast<const byte*>(&cup_credentials_->sk.front()), | |
974 cup_credentials_->sk.size()); | |
975 if (FAILED(hr)) { | |
976 return hr; | |
977 } | |
978 hr = reg_key.SetValue(kCupClientCookie, CA2T(cup_credentials_->c)); | |
979 if (FAILED(hr)) { | |
980 return hr; | |
981 } | |
982 return S_OK; | |
983 } | |
984 | |
985 void NetworkConfigManager::ClearCupCredentials() { | |
986 __mutexScope(lock_); | |
987 cup_credentials_.reset(NULL); | |
988 } | |
989 | |
990 } // namespace omaha | |
991 | |
OLD | NEW |