/* * libjingle * Copyright 2004--2005, Google Inc. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "talk/base/proxydetect.h" #ifdef WIN32 #include "talk/base/win32.h" #include #endif // WIN32 #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef OSX #include #include #include #include #include "macconversion.h" #endif #include #include "talk/base/fileutils.h" #include "talk/base/httpcommon.h" #include "talk/base/httpcommon-inl.h" #include "talk/base/pathutils.h" #include "talk/base/stringutils.h" #ifdef WIN32 #define _TRY_WINHTTP 1 #define _TRY_JSPROXY 0 #define _TRY_WM_FINDPROXY 0 #define _TRY_IE_LAN_SETTINGS 1 #endif // WIN32 // For all platforms try Firefox. #define _TRY_FIREFOX 1 // Use profiles.ini to find the correct profile for this user. // If not set, we'll just look for the default one. #define USE_FIREFOX_PROFILES_INI 1 static const size_t kMaxLineLength = 1024; static const char kFirefoxPattern[] = "Firefox"; static const char kInternetExplorerPattern[] = "MSIE"; struct StringMap { public: void Add(const char * name, const char * value) { map_[name] = value; } const std::string& Get(const char * name, const char * def = "") const { std::map::const_iterator it = map_.find(name); if (it != map_.end()) return it->second; def_ = def; return def_; } bool IsSet(const char * name) const { return (map_.find(name) != map_.end()); } private: std::map map_; mutable std::string def_; }; enum UserAgent { UA_FIREFOX, UA_INTERNETEXPLORER, UA_OTHER, UA_UNKNOWN }; #if _TRY_WINHTTP //#include // Note: From winhttp.h const char WINHTTP[] = "winhttp"; typedef LPVOID HINTERNET; typedef struct { DWORD dwAccessType; // see WINHTTP_ACCESS_* types below LPWSTR lpszProxy; // proxy server list LPWSTR lpszProxyBypass; // proxy bypass list } WINHTTP_PROXY_INFO, * LPWINHTTP_PROXY_INFO; typedef struct { DWORD dwFlags; DWORD dwAutoDetectFlags; LPCWSTR lpszAutoConfigUrl; LPVOID lpvReserved; DWORD dwReserved; BOOL fAutoLogonIfChallenged; } WINHTTP_AUTOPROXY_OPTIONS; typedef struct { BOOL fAutoDetect; LPWSTR lpszAutoConfigUrl; LPWSTR lpszProxy; LPWSTR lpszProxyBypass; } WINHTTP_CURRENT_USER_IE_PROXY_CONFIG; extern "C" { typedef HINTERNET (WINAPI * pfnWinHttpOpen) ( IN LPCWSTR pwszUserAgent, IN DWORD dwAccessType, IN LPCWSTR pwszProxyName OPTIONAL, IN LPCWSTR pwszProxyBypass OPTIONAL, IN DWORD dwFlags ); typedef BOOL (STDAPICALLTYPE * pfnWinHttpCloseHandle) ( IN HINTERNET hInternet ); typedef BOOL (STDAPICALLTYPE * pfnWinHttpGetProxyForUrl) ( IN HINTERNET hSession, IN LPCWSTR lpcwszUrl, IN WINHTTP_AUTOPROXY_OPTIONS * pAutoProxyOptions, OUT WINHTTP_PROXY_INFO * pProxyInfo ); typedef BOOL (STDAPICALLTYPE * pfnWinHttpGetIEProxyConfig) ( IN OUT WINHTTP_CURRENT_USER_IE_PROXY_CONFIG * pProxyConfig ); } // extern "C" #define WINHTTP_AUTOPROXY_AUTO_DETECT 0x00000001 #define WINHTTP_AUTOPROXY_CONFIG_URL 0x00000002 #define WINHTTP_AUTOPROXY_RUN_INPROCESS 0x00010000 #define WINHTTP_AUTOPROXY_RUN_OUTPROCESS_ONLY 0x00020000 #define WINHTTP_AUTO_DETECT_TYPE_DHCP 0x00000001 #define WINHTTP_AUTO_DETECT_TYPE_DNS_A 0x00000002 #define WINHTTP_ACCESS_TYPE_DEFAULT_PROXY 0 #define WINHTTP_ACCESS_TYPE_NO_PROXY 1 #define WINHTTP_ACCESS_TYPE_NAMED_PROXY 3 #define WINHTTP_NO_PROXY_NAME NULL #define WINHTTP_NO_PROXY_BYPASS NULL #endif // _TRY_WINHTTP #if _TRY_JSPROXY extern "C" { typedef BOOL (STDAPICALLTYPE * pfnInternetGetProxyInfo) ( LPCSTR lpszUrl, DWORD dwUrlLength, LPSTR lpszUrlHostName, DWORD dwUrlHostNameLength, LPSTR * lplpszProxyHostName, LPDWORD lpdwProxyHostNameLength ); } // extern "C" #endif // _TRY_JSPROXY #if _TRY_WM_FINDPROXY #include #include #include #endif // _TRY_WM_FINDPROXY #if _TRY_IE_LAN_SETTINGS #include #include #endif // _TRY_IE_LAN_SETTINGS namespace talk_base { ////////////////////////////////////////////////////////////////////// // Utility Functions ////////////////////////////////////////////////////////////////////// #ifdef WIN32 #ifdef _UNICODE typedef std::wstring tstring; std::string Utf8String(const tstring& str) { return ToUtf8(str); } #else // !_UNICODE typedef std::string tstring; std::string Utf8String(const tstring& str) { return str; } #endif // !_UNICODE #endif // WIN32 bool ProxyItemMatch(const Url& url, char * item, size_t len) { // hostname:443 if (char * port = ::strchr(item, ':')) { *port++ = '\0'; if (url.port() != atol(port)) { return false; } } // A.B.C.D or A.B.C.D/24 int a, b, c, d, m; int match = sscanf(item, "%d.%d.%d.%d/%d", &a, &b, &c, &d, &m); if (match >= 4) { uint32 ip = ((a & 0xFF) << 24) | ((b & 0xFF) << 16) | ((c & 0xFF) << 8) | (d & 0xFF); if ((match < 5) || (m > 32)) m = 32; else if (m < 0) m = 0; uint32 mask = (m == 0) ? 0 : (~0UL) << (32 - m); SocketAddress addr(url.host(), 0); // TODO: Support IPv6 proxyitems. This code block is IPv4 only anyway. return !addr.IsUnresolved() && ((addr.ipaddr().v4AddressAsHostOrderInteger() & mask) == (ip & mask)); } // .foo.com if (*item == '.') { size_t hostlen = url.host().length(); return (hostlen > len) && (stricmp(url.host().c_str() + (hostlen - len), item) == 0); } // localhost or www.*.com if (!string_match(url.host().c_str(), item)) return false; return true; } bool ProxyListMatch(const Url& url, const std::string& proxy_list, char sep) { const size_t BUFSIZE = 256; char buffer[BUFSIZE]; const char* list = proxy_list.c_str(); while (*list) { // Remove leading space if (isspace(*list)) { ++list; continue; } // Break on separator size_t len; const char * start = list; if (const char * end = ::strchr(list, sep)) { len = (end - list); list += len + 1; } else { len = strlen(list); list += len; } // Remove trailing space while ((len > 0) && isspace(start[len-1])) --len; // Check for oversized entry if (len >= BUFSIZE) continue; memcpy(buffer, start, len); buffer[len] = 0; if (!ProxyItemMatch(url, buffer, len)) continue; return true; } return false; } bool Better(ProxyType lhs, const ProxyType rhs) { // PROXY_NONE, PROXY_HTTPS, PROXY_SOCKS5, PROXY_UNKNOWN const int PROXY_VALUE[5] = { 0, 2, 3, 1 }; return (PROXY_VALUE[lhs] > PROXY_VALUE[rhs]); } bool ParseProxy(const std::string& saddress, ProxyInfo* proxy) { const size_t kMaxAddressLength = 1024; // Allow semicolon, space, or tab as an address separator const char* const kAddressSeparator = " ;\t"; ProxyType ptype; std::string host; uint16 port; const char* address = saddress.c_str(); while (*address) { size_t len; const char * start = address; if (const char * sep = strchr(address, kAddressSeparator)) { len = (sep - address); address += len + 1; while (*address != '\0' && ::strchr(kAddressSeparator, *address)) { address += 1; } } else { len = strlen(address); address += len; } if (len > kMaxAddressLength - 1) { LOG(LS_WARNING) << "Proxy address too long [" << start << "]"; continue; } char buffer[kMaxAddressLength]; memcpy(buffer, start, len); buffer[len] = 0; char * colon = ::strchr(buffer, ':'); if (!colon) { LOG(LS_WARNING) << "Proxy address without port [" << buffer << "]"; continue; } *colon = 0; char * endptr; port = static_cast(strtol(colon + 1, &endptr, 0)); if (*endptr != 0) { LOG(LS_WARNING) << "Proxy address with invalid port [" << buffer << "]"; continue; } if (char * equals = ::strchr(buffer, '=')) { *equals = 0; host = equals + 1; if (_stricmp(buffer, "socks") == 0) { ptype = PROXY_SOCKS5; } else if (_stricmp(buffer, "https") == 0) { ptype = PROXY_HTTPS; } else { LOG(LS_WARNING) << "Proxy address with unknown protocol [" << buffer << "]"; ptype = PROXY_UNKNOWN; } } else { host = buffer; ptype = PROXY_UNKNOWN; } if (Better(ptype, proxy->type)) { proxy->type = ptype; proxy->address.SetIP(host); proxy->address.SetPort(port); } } return proxy->type != PROXY_NONE; } UserAgent GetAgent(const char* agent) { if (agent) { std::string agent_str(agent); if (agent_str.find(kFirefoxPattern) != std::string::npos) { return UA_FIREFOX; } else if (agent_str.find(kInternetExplorerPattern) != std::string::npos) { return UA_INTERNETEXPLORER; } else if (agent_str.empty()) { return UA_UNKNOWN; } } return UA_OTHER; } bool EndsWith(const std::string& a, const std::string& b) { if (b.size() > a.size()) { return false; } int result = a.compare(a.size() - b.size(), b.size(), b); return result == 0; } bool GetFirefoxProfilePath(Pathname* path) { #ifdef WIN32 wchar_t w_path[MAX_PATH]; if (SHGetFolderPath(0, CSIDL_APPDATA, 0, SHGFP_TYPE_CURRENT, w_path) != S_OK) { LOG(LS_ERROR) << "SHGetFolderPath failed"; return false; } path->SetFolder(ToUtf8(w_path, wcslen(w_path))); path->AppendFolder("Mozilla"); path->AppendFolder("Firefox"); #elif OSX FSRef fr; if (0 != FSFindFolder(kUserDomain, kApplicationSupportFolderType, kCreateFolder, &fr)) { LOG(LS_ERROR) << "FSFindFolder failed"; return false; } char buffer[NAME_MAX + 1]; if (0 != FSRefMakePath(&fr, reinterpret_cast(buffer), ARRAY_SIZE(buffer))) { LOG(LS_ERROR) << "FSRefMakePath failed"; return false; } path->SetFolder(std::string(buffer)); path->AppendFolder("Firefox"); #else char* user_home = getenv("HOME"); if (user_home == NULL) { return false; } path->SetFolder(std::string(user_home)); path->AppendFolder(".mozilla"); path->AppendFolder("firefox"); #endif // WIN32 return true; } bool GetDefaultFirefoxProfile(Pathname* profile_path) { ASSERT(NULL != profile_path); Pathname path; if (!GetFirefoxProfilePath(&path)) { return false; } #if USE_FIREFOX_PROFILES_INI // [Profile0] // Name=default // IsRelative=1 // Path=Profiles/2de53ejb.default // Default=1 // Note: we are looking for the first entry with "Default=1", or the last // entry in the file path.SetFilename("profiles.ini"); scoped_ptr fs(Filesystem::OpenFile(path, "r")); if (!fs) { return false; } Pathname candidate; bool relative = true; std::string line; while (fs->ReadLine(&line) == SR_SUCCESS) { if (line.length() == 0) { continue; } if (line.at(0) == '[') { relative = true; candidate.clear(); } else if (line.find("IsRelative=") == 0 && line.length() >= 12) { // TODO: The initial Linux public launch revealed a fairly // high number of machines where IsRelative= did not have anything after // it. Perhaps that is legal profiles.ini syntax? relative = (line.at(11) != '0'); } else if (line.find("Path=") == 0 && line.length() >= 6) { if (relative) { candidate = path; } else { candidate.clear(); } candidate.AppendFolder(line.substr(5)); } else if (line.find("Default=") == 0 && line.length() >= 9) { if ((line.at(8) != '0') && !candidate.empty()) { break; } } } fs->Close(); if (candidate.empty()) { return false; } profile_path->SetPathname(candidate.pathname()); #else // !USE_FIREFOX_PROFILES_INI path.AppendFolder("Profiles"); DirectoryIterator* it = Filesystem::IterateDirectory(); it->Iterate(path); std::string extension(".default"); while (!EndsWith(it->Name(), extension)) { if (!it->Next()) { return false; } } profile_path->SetPathname(path); profile->AppendFolder("Profiles"); profile->AppendFolder(it->Name()); delete it; #endif // !USE_FIREFOX_PROFILES_INI return true; } bool ReadFirefoxPrefs(const Pathname& filename, const char * prefix, StringMap* settings) { scoped_ptr fs(Filesystem::OpenFile(filename, "r")); if (!fs) { LOG(LS_ERROR) << "Failed to open file: " << filename.pathname(); return false; } std::string line; while (fs->ReadLine(&line) == SR_SUCCESS) { size_t prefix_len = strlen(prefix); // Skip blank lines and too long lines. if ((line.length() == 0) || (line.length() > kMaxLineLength) || (line.at(0) == '#') || line.compare(0, 2, "/*") == 0 || line.compare(0, 2, " *") == 0) { continue; } char buffer[kMaxLineLength]; strcpyn(buffer, sizeof(buffer), line.c_str()); int nstart = 0, nend = 0, vstart = 0, vend = 0; sscanf(buffer, "user_pref(\"%n%*[^\"]%n\", %n%*[^)]%n);", &nstart, &nend, &vstart, &vend); if (vend > 0) { char* name = buffer + nstart; name[nend - nstart] = 0; if ((vend - vstart >= 2) && (buffer[vstart] == '"')) { vstart += 1; vend -= 1; } char* value = buffer + vstart; value[vend - vstart] = 0; if ((strncmp(name, prefix, prefix_len) == 0) && *value) { settings->Add(name + prefix_len, value); } } else { LOG_F(LS_WARNING) << "Unparsed pref [" << buffer << "]"; } } fs->Close(); return true; } bool GetFirefoxProxySettings(const char* url, ProxyInfo* proxy) { Url purl(url); Pathname path; bool success = false; if (GetDefaultFirefoxProfile(&path)) { StringMap settings; path.SetFilename("prefs.js"); if (ReadFirefoxPrefs(path, "network.proxy.", &settings)) { success = true; proxy->bypass_list = settings.Get("no_proxies_on", "localhost, 127.0.0.1"); if (settings.Get("type") == "1") { // User has manually specified a proxy, try to figure out what // type it is. if (ProxyListMatch(purl, proxy->bypass_list.c_str(), ',')) { // Our url is in the list of url's to bypass proxy. } else if (settings.Get("share_proxy_settings") == "true") { proxy->type = PROXY_UNKNOWN; proxy->address.SetIP(settings.Get("http")); proxy->address.SetPort(atoi(settings.Get("http_port").c_str())); } else if (settings.IsSet("socks")) { proxy->type = PROXY_SOCKS5; proxy->address.SetIP(settings.Get("socks")); proxy->address.SetPort(atoi(settings.Get("socks_port").c_str())); } else if (settings.IsSet("ssl")) { proxy->type = PROXY_HTTPS; proxy->address.SetIP(settings.Get("ssl")); proxy->address.SetPort(atoi(settings.Get("ssl_port").c_str())); } else if (settings.IsSet("http")) { proxy->type = PROXY_HTTPS; proxy->address.SetIP(settings.Get("http")); proxy->address.SetPort(atoi(settings.Get("http_port").c_str())); } } else if (settings.Get("type") == "2") { // Browser is configured to get proxy settings from a given url. proxy->autoconfig_url = settings.Get("autoconfig_url").c_str(); } else if (settings.Get("type") == "4") { // Browser is configured to auto detect proxy config. proxy->autodetect = true; } else { // No proxy set. } } } return success; } #ifdef WIN32 // Windows specific implementation for reading Internet // Explorer proxy settings. void LogGetProxyFault() { LOG_GLEM(LERROR, WINHTTP) << "WinHttpGetProxyForUrl faulted!!"; } BOOL MyWinHttpGetProxyForUrl(pfnWinHttpGetProxyForUrl pWHGPFU, HINTERNET hWinHttp, LPCWSTR url, WINHTTP_AUTOPROXY_OPTIONS *options, WINHTTP_PROXY_INFO *info) { // WinHttpGetProxyForUrl() can call plugins which can crash. // In the case of McAfee scriptproxy.dll, it does crash in // older versions. Try to catch crashes here and treat as an // error. BOOL success = FALSE; #if (_HAS_EXCEPTIONS == 0) __try { success = pWHGPFU(hWinHttp, url, options, info); } __except(EXCEPTION_EXECUTE_HANDLER) { // This is a separate function to avoid // Visual C++ error 2712 when compiling with C++ EH LogGetProxyFault(); } #else success = pWHGPFU(hWinHttp, url, options, info); #endif // (_HAS_EXCEPTIONS == 0) return success; } bool IsDefaultBrowserFirefox() { HKEY key; LONG result = RegOpenKeyEx(HKEY_CLASSES_ROOT, L"http\\shell\\open\\command", 0, KEY_READ, &key); if (ERROR_SUCCESS != result) return false; DWORD size, type; bool success = false; result = RegQueryValueEx(key, L"", 0, &type, NULL, &size); if (result == ERROR_SUCCESS && type == REG_SZ) { wchar_t* value = new wchar_t[size+1]; BYTE* buffer = reinterpret_cast(value); result = RegQueryValueEx(key, L"", 0, &type, buffer, &size); if (result == ERROR_SUCCESS) { // Size returned by RegQueryValueEx is in bytes, convert to number of // wchar_t's. size /= sizeof(value[0]); value[size] = L'\0'; for (size_t i = 0; i < size; ++i) { value[i] = tolowercase(value[i]); } success = (NULL != strstr(value, L"firefox.exe")); } delete[] value; } RegCloseKey(key); return success; } bool GetWinHttpProxySettings(const char* url, ProxyInfo* proxy) { HMODULE winhttp_handle = LoadLibrary(L"winhttp.dll"); if (winhttp_handle == NULL) { LOG(LS_ERROR) << "Failed to load winhttp.dll."; return false; } WINHTTP_CURRENT_USER_IE_PROXY_CONFIG iecfg; memset(&iecfg, 0, sizeof(iecfg)); Url purl(url); pfnWinHttpGetIEProxyConfig pWHGIEPC = reinterpret_cast( GetProcAddress(winhttp_handle, "WinHttpGetIEProxyConfigForCurrentUser")); bool success = false; if (pWHGIEPC && pWHGIEPC(&iecfg)) { // We were read proxy config successfully. success = true; if (iecfg.fAutoDetect) { proxy->autodetect = true; } if (iecfg.lpszAutoConfigUrl) { proxy->autoconfig_url = ToUtf8(iecfg.lpszAutoConfigUrl); GlobalFree(iecfg.lpszAutoConfigUrl); } if (iecfg.lpszProxyBypass) { proxy->bypass_list = ToUtf8(iecfg.lpszProxyBypass); GlobalFree(iecfg.lpszProxyBypass); } if (iecfg.lpszProxy) { if (!ProxyListMatch(purl, proxy->bypass_list, ';')) { ParseProxy(ToUtf8(iecfg.lpszProxy), proxy); } GlobalFree(iecfg.lpszProxy); } } FreeLibrary(winhttp_handle); return success; } // Uses the WinHTTP API to auto detect proxy for the given url. Firefox and IE // have slightly different option dialogs for proxy settings. In Firefox, // either a location of a proxy configuration file can be specified or auto // detection can be selected. In IE theese two options can be independently // selected. For the case where both options are selected (only IE) we try to // fetch the config file first, and if that fails we'll perform an auto // detection. // // Returns true if we successfully performed an auto detection not depending on // whether we found a proxy or not. Returns false on error. bool WinHttpAutoDetectProxyForUrl(const char* agent, const char* url, ProxyInfo* proxy) { Url purl(url); bool success = true; HMODULE winhttp_handle = LoadLibrary(L"winhttp.dll"); if (winhttp_handle == NULL) { LOG(LS_ERROR) << "Failed to load winhttp.dll."; return false; } pfnWinHttpOpen pWHO = reinterpret_cast(GetProcAddress(winhttp_handle, "WinHttpOpen")); pfnWinHttpCloseHandle pWHCH = reinterpret_cast( GetProcAddress(winhttp_handle, "WinHttpCloseHandle")); pfnWinHttpGetProxyForUrl pWHGPFU = reinterpret_cast( GetProcAddress(winhttp_handle, "WinHttpGetProxyForUrl")); if (pWHO && pWHCH && pWHGPFU) { if (HINTERNET hWinHttp = pWHO(ToUtf16(agent).c_str(), WINHTTP_ACCESS_TYPE_NO_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0)) { BOOL result = FALSE; WINHTTP_PROXY_INFO info; memset(&info, 0, sizeof(info)); if (proxy->autodetect) { // Use DHCP and DNS to try to find any proxy to use. WINHTTP_AUTOPROXY_OPTIONS options; memset(&options, 0, sizeof(options)); options.fAutoLogonIfChallenged = TRUE; options.dwFlags |= WINHTTP_AUTOPROXY_AUTO_DETECT; options.dwAutoDetectFlags |= WINHTTP_AUTO_DETECT_TYPE_DHCP | WINHTTP_AUTO_DETECT_TYPE_DNS_A; result = MyWinHttpGetProxyForUrl( pWHGPFU, hWinHttp, ToUtf16(url).c_str(), &options, &info); } if (!result && !proxy->autoconfig_url.empty()) { // We have the location of a proxy config file. Download it and // execute it to find proxy settings for our url. WINHTTP_AUTOPROXY_OPTIONS options; memset(&options, 0, sizeof(options)); memset(&info, 0, sizeof(info)); options.fAutoLogonIfChallenged = TRUE; std::wstring autoconfig_url16((ToUtf16)(proxy->autoconfig_url)); options.dwFlags |= WINHTTP_AUTOPROXY_CONFIG_URL; options.lpszAutoConfigUrl = autoconfig_url16.c_str(); result = MyWinHttpGetProxyForUrl( pWHGPFU, hWinHttp, ToUtf16(url).c_str(), &options, &info); } if (result) { // Either the given auto config url was valid or auto // detection found a proxy on this network. if (info.lpszProxy) { // TODO: Does this bypass list differ from the list // retreived from GetWinHttpProxySettings earlier? if (info.lpszProxyBypass) { proxy->bypass_list = ToUtf8(info.lpszProxyBypass); GlobalFree(info.lpszProxyBypass); } else { proxy->bypass_list.clear(); } if (!ProxyListMatch(purl, proxy->bypass_list, ';')) { // Found proxy for this URL. If parsing the address turns // out ok then we are successful. success = ParseProxy(ToUtf8(info.lpszProxy), proxy); } GlobalFree(info.lpszProxy); } } else { // We could not find any proxy for this url. LOG(LS_INFO) << "No proxy detected for " << url; } pWHCH(hWinHttp); } } else { LOG(LS_ERROR) << "Failed loading WinHTTP functions."; success = false; } FreeLibrary(winhttp_handle); return success; } #if 0 // Below functions currently not used. bool GetJsProxySettings(const char* url, ProxyInfo* proxy) { Url purl(url); bool success = false; if (HMODULE hModJS = LoadLibrary(_T("jsproxy.dll"))) { pfnInternetGetProxyInfo pIGPI = reinterpret_cast( GetProcAddress(hModJS, "InternetGetProxyInfo")); if (pIGPI) { char proxy[256], host[256]; memset(proxy, 0, sizeof(proxy)); char * ptr = proxy; DWORD proxylen = sizeof(proxy); std::string surl = Utf8String(url); DWORD hostlen = _snprintf(host, sizeof(host), "http%s://%S", purl.secure() ? "s" : "", purl.server()); if (pIGPI(surl.data(), surl.size(), host, hostlen, &ptr, &proxylen)) { LOG(INFO) << "Proxy: " << proxy; } else { LOG_GLE(INFO) << "InternetGetProxyInfo"; } } FreeLibrary(hModJS); } return success; } bool GetWmProxySettings(const char* url, ProxyInfo* proxy) { Url purl(url); bool success = false; INSNetSourceCreator * nsc = 0; HRESULT hr = CoCreateInstance(CLSID_ClientNetManager, 0, CLSCTX_ALL, IID_INSNetSourceCreator, (LPVOID *) &nsc); if (SUCCEEDED(hr)) { if (SUCCEEDED(hr = nsc->Initialize())) { VARIANT dispatch; VariantInit(&dispatch); if (SUCCEEDED(hr = nsc->GetNetSourceAdminInterface(L"http", &dispatch))) { IWMSInternalAdminNetSource * ians = 0; if (SUCCEEDED(hr = dispatch.pdispVal->QueryInterface( IID_IWMSInternalAdminNetSource, (LPVOID *) &ians))) { _bstr_t host(purl.server()); BSTR proxy = 0; BOOL bProxyEnabled = FALSE; DWORD port, context = 0; if (SUCCEEDED(hr = ians->FindProxyForURL( L"http", host, &bProxyEnabled, &proxy, &port, &context))) { success = true; if (bProxyEnabled) { _bstr_t sproxy = proxy; proxy->ptype = PT_HTTPS; proxy->host = sproxy; proxy->port = port; } } SysFreeString(proxy); if (FAILED(hr = ians->ShutdownProxyContext(context))) { LOG(LS_INFO) << "IWMSInternalAdminNetSource::ShutdownProxyContext" << "failed: " << hr; } ians->Release(); } } VariantClear(&dispatch); if (FAILED(hr = nsc->Shutdown())) { LOG(LS_INFO) << "INSNetSourceCreator::Shutdown failed: " << hr; } } nsc->Release(); } return success; } bool GetIePerConnectionProxySettings(const char* url, ProxyInfo* proxy) { Url purl(url); bool success = false; INTERNET_PER_CONN_OPTION_LIST list; INTERNET_PER_CONN_OPTION options[3]; memset(&list, 0, sizeof(list)); memset(&options, 0, sizeof(options)); list.dwSize = sizeof(list); list.dwOptionCount = 3; list.pOptions = options; options[0].dwOption = INTERNET_PER_CONN_FLAGS; options[1].dwOption = INTERNET_PER_CONN_PROXY_SERVER; options[2].dwOption = INTERNET_PER_CONN_PROXY_BYPASS; DWORD dwSize = sizeof(list); if (!InternetQueryOption(0, INTERNET_OPTION_PER_CONNECTION_OPTION, &list, &dwSize)) { LOG(LS_INFO) << "InternetQueryOption failed: " << GetLastError(); } else if ((options[0].Value.dwValue & PROXY_TYPE_PROXY) != 0) { success = true; if (!ProxyListMatch(purl, nonnull(options[2].Value.pszValue), _T(';'))) { ParseProxy(nonnull(options[1].Value.pszValue), proxy); } } else if ((options[0].Value.dwValue & PROXY_TYPE_DIRECT) != 0) { success = true; } else { LOG(LS_INFO) << "unknown internet access type: " << options[0].Value.dwValue; } if (options[1].Value.pszValue) { GlobalFree(options[1].Value.pszValue); } if (options[2].Value.pszValue) { GlobalFree(options[2].Value.pszValue); } return success; } #endif // 0 // Uses the InternetQueryOption function to retrieve proxy settings // from the registry. This will only give us the 'static' settings, // ie, not any information about auto config etc. bool GetIeLanProxySettings(const char* url, ProxyInfo* proxy) { Url purl(url); bool success = false; wchar_t buffer[1024]; memset(buffer, 0, sizeof(buffer)); INTERNET_PROXY_INFO * info = reinterpret_cast(buffer); DWORD dwSize = sizeof(buffer); if (!InternetQueryOption(0, INTERNET_OPTION_PROXY, info, &dwSize)) { LOG(LS_INFO) << "InternetQueryOption failed: " << GetLastError(); } else if (info->dwAccessType == INTERNET_OPEN_TYPE_DIRECT) { success = true; } else if (info->dwAccessType == INTERNET_OPEN_TYPE_PROXY) { success = true; if (!ProxyListMatch(purl, nonnull(reinterpret_cast( info->lpszProxyBypass)), ' ')) { ParseProxy(nonnull(reinterpret_cast(info->lpszProxy)), proxy); } } else { LOG(LS_INFO) << "unknown internet access type: " << info->dwAccessType; } return success; } bool GetIeProxySettings(const char* agent, const char* url, ProxyInfo* proxy) { bool success = GetWinHttpProxySettings(url, proxy); if (!success) { // TODO: Should always call this if no proxy were detected by // GetWinHttpProxySettings? // WinHttp failed. Try using the InternetOptionQuery method instead. return GetIeLanProxySettings(url, proxy); } return true; } #endif // WIN32 #ifdef OSX // OSX specific implementation for reading system wide // proxy settings. bool p_getProxyInfoForTypeFromDictWithKeys(ProxyInfo* proxy, ProxyType type, const CFDictionaryRef proxyDict, const CFStringRef enabledKey, const CFStringRef hostKey, const CFStringRef portKey) { // whether or not we set up the proxy info. bool result = false; // we use this as a scratch variable for determining if operations // succeeded. bool converted = false; // the data we need to construct the SocketAddress for the proxy. std::string hostname; int port; if ((proxyDict != NULL) && (CFGetTypeID(proxyDict) == CFDictionaryGetTypeID())) { // CoreFoundation stuff that we'll have to get from // the dictionaries and interpret or convert into more usable formats. CFNumberRef enabledCFNum; CFNumberRef portCFNum; CFStringRef hostCFStr; enabledCFNum = (CFNumberRef)CFDictionaryGetValue(proxyDict, enabledKey); if (p_isCFNumberTrue(enabledCFNum)) { // let's see if we can get the address and port. hostCFStr = (CFStringRef)CFDictionaryGetValue(proxyDict, hostKey); converted = p_convertHostCFStringRefToCPPString(hostCFStr, hostname); if (converted) { portCFNum = (CFNumberRef)CFDictionaryGetValue(proxyDict, portKey); converted = p_convertCFNumberToInt(portCFNum, &port); if (converted) { // we have something enabled, with a hostname and a port. // That's sufficient to set up the proxy info. proxy->type = type; proxy->address.SetIP(hostname); proxy->address.SetPort(port); result = true; } } } } return result; } // Looks for proxy information in the given dictionary, // return true if it found sufficient information to define one, // false otherwise. This is guaranteed to not change the values in proxy // unless a full-fledged proxy description was discovered in the dictionary. // However, at the present time this does not support username or password. // Checks first for a SOCKS proxy, then for HTTPS, then HTTP. bool GetMacProxySettingsFromDictionary(ProxyInfo* proxy, const CFDictionaryRef proxyDict) { // the function result. bool gotProxy = false; // first we see if there's a SOCKS proxy in place. gotProxy = p_getProxyInfoForTypeFromDictWithKeys(proxy, PROXY_SOCKS5, proxyDict, kSCPropNetProxiesSOCKSEnable, kSCPropNetProxiesSOCKSProxy, kSCPropNetProxiesSOCKSPort); if (!gotProxy) { // okay, no SOCKS proxy, let's look for https. gotProxy = p_getProxyInfoForTypeFromDictWithKeys(proxy, PROXY_HTTPS, proxyDict, kSCPropNetProxiesHTTPSEnable, kSCPropNetProxiesHTTPSProxy, kSCPropNetProxiesHTTPSPort); if (!gotProxy) { // Finally, try HTTP proxy. Note that flute doesn't // differentiate between HTTPS and HTTP, hence we are using the // same flute type here, ie. PROXY_HTTPS. gotProxy = p_getProxyInfoForTypeFromDictWithKeys( proxy, PROXY_HTTPS, proxyDict, kSCPropNetProxiesHTTPEnable, kSCPropNetProxiesHTTPProxy, kSCPropNetProxiesHTTPPort); } } return gotProxy; } // TODO(hughv) Update keychain functions. They work on 10.8, but are depricated. #pragma GCC diagnostic ignored "-Wdeprecated-declarations" bool p_putPasswordInProxyInfo(ProxyInfo* proxy) { bool result = true; // by default we assume we're good. // for all we know there isn't any password. We'll set to false // if we find a problem. // Ask the keychain for an internet password search for the given protocol. OSStatus oss = 0; SecKeychainAttributeList attrList; attrList.count = 3; SecKeychainAttribute attributes[3]; attrList.attr = attributes; attributes[0].tag = kSecProtocolItemAttr; attributes[0].length = sizeof(SecProtocolType); SecProtocolType protocol; switch (proxy->type) { case PROXY_HTTPS : protocol = kSecProtocolTypeHTTPS; break; case PROXY_SOCKS5 : protocol = kSecProtocolTypeSOCKS; break; default : LOG(LS_ERROR) << "asked for proxy password for unknown proxy type."; result = false; break; } attributes[0].data = &protocol; UInt32 port = proxy->address.port(); attributes[1].tag = kSecPortItemAttr; attributes[1].length = sizeof(UInt32); attributes[1].data = &port; std::string ip = proxy->address.ipaddr().ToString(); attributes[2].tag = kSecServerItemAttr; attributes[2].length = ip.length(); attributes[2].data = const_cast(ip.c_str()); if (result) { LOG(LS_INFO) << "trying to get proxy username/password"; SecKeychainSearchRef sref; oss = SecKeychainSearchCreateFromAttributes(NULL, kSecInternetPasswordItemClass, &attrList, &sref); if (0 == oss) { LOG(LS_INFO) << "SecKeychainSearchCreateFromAttributes was good"; // Get the first item, if there is one. SecKeychainItemRef iref; oss = SecKeychainSearchCopyNext(sref, &iref); if (0 == oss) { LOG(LS_INFO) << "...looks like we have the username/password data"; // If there is, get the username and the password. SecKeychainAttributeInfo attribsToGet; attribsToGet.count = 1; UInt32 tag = kSecAccountItemAttr; UInt32 format = CSSM_DB_ATTRIBUTE_FORMAT_STRING; void *data; UInt32 length; SecKeychainAttributeList *localList; attribsToGet.tag = &tag; attribsToGet.format = &format; OSStatus copyres = SecKeychainItemCopyAttributesAndData(iref, &attribsToGet, NULL, &localList, &length, &data); if (0 == copyres) { LOG(LS_INFO) << "...and we can pull it out."; // now, we know from experimentation (sadly not from docs) // that the username is in the local attribute list, // and the password in the data, // both without null termination but with info on their length. // grab the password from the data. std::string password; password.append(static_cast(data), length); // make the password into a CryptString // huh, at the time of writing, you can't. // so we'll skip that for now and come back to it later. // now put the username in the proxy. if (1 <= localList->attr->length) { proxy->username.append( static_cast(localList->attr->data), localList->attr->length); LOG(LS_INFO) << "username is " << proxy->username; } else { LOG(LS_ERROR) << "got keychain entry with no username"; result = false; } } else { LOG(LS_ERROR) << "couldn't copy info from keychain."; result = false; } SecKeychainItemFreeAttributesAndData(localList, data); } else if (errSecItemNotFound == oss) { LOG(LS_INFO) << "...username/password info not found"; } else { // oooh, neither 0 nor itemNotFound. LOG(LS_ERROR) << "Couldn't get keychain information, error code" << oss; result = false; } } else if (errSecItemNotFound == oss) { // noop } else { // oooh, neither 0 nor itemNotFound. LOG(LS_ERROR) << "Couldn't get keychain information, error code" << oss; result = false; } } return result; } bool GetMacProxySettings(ProxyInfo* proxy) { // based on the Apple Technical Q&A QA1234 // http://developer.apple.com/qa/qa2001/qa1234.html CFDictionaryRef proxyDict = SCDynamicStoreCopyProxies(NULL); bool result = false; if (proxyDict != NULL) { // sending it off to another function makes it easier to unit test // since we can make our own dictionary to hand to that function. result = GetMacProxySettingsFromDictionary(proxy, proxyDict); if (result) { result = p_putPasswordInProxyInfo(proxy); } // We created the dictionary with something that had the // word 'copy' in it, so we have to release it, according // to the Carbon memory management standards. CFRelease(proxyDict); } else { LOG(LS_ERROR) << "SCDynamicStoreCopyProxies failed"; } return result; } #endif // OSX bool AutoDetectProxySettings(const char* agent, const char* url, ProxyInfo* proxy) { #ifdef WIN32 return WinHttpAutoDetectProxyForUrl(agent, url, proxy); #else LOG(LS_WARNING) << "Proxy auto-detection not implemented for this platform"; return false; #endif } bool GetSystemDefaultProxySettings(const char* agent, const char* url, ProxyInfo* proxy) { #ifdef WIN32 return GetIeProxySettings(agent, url, proxy); #elif OSX return GetMacProxySettings(proxy); #else // TODO: Get System settings if browser is not firefox. return GetFirefoxProxySettings(url, proxy); #endif } bool GetProxySettingsForUrl(const char* agent, const char* url, ProxyInfo* proxy, bool long_operation) { UserAgent a = GetAgent(agent); bool result; switch (a) { case UA_FIREFOX: { result = GetFirefoxProxySettings(url, proxy); break; } #ifdef WIN32 case UA_INTERNETEXPLORER: result = GetIeProxySettings(agent, url, proxy); break; case UA_UNKNOWN: // Agent not defined, check default browser. if (IsDefaultBrowserFirefox()) { result = GetFirefoxProxySettings(url, proxy); } else { result = GetIeProxySettings(agent, url, proxy); } break; #endif // WIN32 default: result = GetSystemDefaultProxySettings(agent, url, proxy); break; } // TODO: Consider using the 'long_operation' parameter to // decide whether to do the auto detection. if (result && (proxy->autodetect || !proxy->autoconfig_url.empty())) { // Use WinHTTP to auto detect proxy for us. result = AutoDetectProxySettings(agent, url, proxy); if (!result) { // Either auto detection is not supported or we simply didn't // find any proxy, reset type. proxy->type = talk_base::PROXY_NONE; } } return result; } } // namespace talk_base