webrtc/talk/base/proxydetect.cc

1264 lines
41 KiB
C++
Raw Normal View History

/*
* 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 <shlobj.h>
#endif // WIN32
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifdef OSX
#include <SystemConfiguration/SystemConfiguration.h>
#include <CoreFoundation/CoreFoundation.h>
#include <CoreServices/CoreServices.h>
#include <Security/Security.h>
#include "macconversion.h"
#endif
#include <map>
#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<std::string, std::string>::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<std::string, std::string> map_;
mutable std::string def_;
};
enum UserAgent {
UA_FIREFOX,
UA_INTERNETEXPLORER,
UA_OTHER,
UA_UNKNOWN
};
#if _TRY_WINHTTP
//#include <winhttp.h>
// 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 <comutil.h>
#include <wmnetsourcecreator.h>
#include <wmsinternaladminnetsource.h>
#endif // _TRY_WM_FINDPROXY
#if _TRY_IE_LAN_SETTINGS
#include <wininet.h>
#include <string>
#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<char>& 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<char>& 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<uint16>(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<uint8*>(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<FileStream> 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<FileStream> 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<char> 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;
wchar_t* value = NULL;
DWORD size, type;
result = RegQueryValueEx(key, L"", 0, &type, NULL, &size);
if (REG_SZ != type) {
result = ERROR_ACCESS_DENIED; // Any error is fine
} else if (ERROR_SUCCESS == result) {
value = new wchar_t[size+1];
BYTE* buffer = reinterpret_cast<BYTE*>(value);
result = RegQueryValueEx(key, L"", 0, &type, buffer, &size);
}
RegCloseKey(key);
bool success = false;
if (ERROR_SUCCESS == result) {
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;
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<char> purl(url);
pfnWinHttpGetIEProxyConfig pWHGIEPC =
reinterpret_cast<pfnWinHttpGetIEProxyConfig>(
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<char> 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<pfnWinHttpOpen>(GetProcAddress(winhttp_handle,
"WinHttpOpen"));
pfnWinHttpCloseHandle pWHCH =
reinterpret_cast<pfnWinHttpCloseHandle>(
GetProcAddress(winhttp_handle, "WinHttpCloseHandle"));
pfnWinHttpGetProxyForUrl pWHGPFU =
reinterpret_cast<pfnWinHttpGetProxyForUrl>(
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<char> purl(url);
bool success = false;
if (HMODULE hModJS = LoadLibrary(_T("jsproxy.dll"))) {
pfnInternetGetProxyInfo pIGPI =
reinterpret_cast<pfnInternetGetProxyInfo>(
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<char> 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<char> 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<char> purl(url);
bool success = false;
wchar_t buffer[1024];
memset(buffer, 0, sizeof(buffer));
INTERNET_PROXY_INFO * info = reinterpret_cast<INTERNET_PROXY_INFO *>(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<const char*>(
info->lpszProxyBypass)), ' ')) {
ParseProxy(nonnull(reinterpret_cast<const char*>(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<char*>(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<const char*>(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<const char*>(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