diff --git a/p2p/base/basic_packet_socket_factory.cc b/p2p/base/basic_packet_socket_factory.cc index 2fe00cf09f..816d964360 100644 --- a/p2p/base/basic_packet_socket_factory.cc +++ b/p2p/base/basic_packet_socket_factory.cc @@ -119,6 +119,12 @@ AsyncPacketSocket* BasicPacketSocketFactory::CreateClientTcpSocket( << socket->GetError(); } + if (proxy_info.type == PROXY_HTTPS) { + socket = + new AsyncHttpsProxySocket(socket, user_agent, proxy_info.address, + proxy_info.username, proxy_info.password); + } + // Assert that at most one TLS option is used. int tlsOpts = tcp_options.opts & (PacketSocketFactory::OPT_TLS | PacketSocketFactory::OPT_TLS_FAKE | diff --git a/p2p/base/p2p_transport_channel_unittest.cc b/p2p/base/p2p_transport_channel_unittest.cc index 79ca2a5f26..261c5821e5 100644 --- a/p2p/base/p2p_transport_channel_unittest.cc +++ b/p2p/base/p2p_transport_channel_unittest.cc @@ -100,6 +100,12 @@ static const SocketAddress kAlternateAddrs[2] = { static const SocketAddress kIPv6AlternateAddrs[2] = { SocketAddress("2401:4030:1:2c00:be30:abcd:efab:cdef", 0), SocketAddress("2601:0:1000:1b03:2e41:38ff:fea6:f2a4", 0)}; +// Addresses for HTTP proxy servers. +static const SocketAddress kHttpsProxyAddrs[2] = { + SocketAddress("11.11.11.1", 443), SocketAddress("22.22.22.1", 443)}; +// Addresses for SOCKS proxy servers. +static const SocketAddress kSocksProxyAddrs[2] = { + SocketAddress("11.11.11.1", 1080), SocketAddress("22.22.22.1", 1080)}; // Internal addresses for NAT boxes. static const SocketAddress kNatAddrs[2] = {SocketAddress("192.168.1.1", 0), SocketAddress("192.168.2.1", 0)}; @@ -311,6 +317,7 @@ class P2PTransportChannelTestBase : public ::testing::Test, BLOCK_UDP, // Firewall, UDP in/out blocked BLOCK_UDP_AND_INCOMING_TCP, // Firewall, UDP in/out and TCP in blocked BLOCK_ALL_BUT_OUTGOING_HTTP, // Firewall, only TCP out on 80/443 + PROXY_HTTPS, // All traffic through HTTPS proxy NUM_CONFIGS }; @@ -556,6 +563,13 @@ class P2PTransportChannelTestBase : public ::testing::Test, GetEndpoint(endpoint)->network_manager_.RemoveInterface(addr); fw()->AddRule(false, rtc::FP_ANY, rtc::FD_ANY, addr); } + void SetProxy(int endpoint, rtc::ProxyType type) { + rtc::ProxyInfo info; + info.type = type; + info.address = (type == rtc::PROXY_HTTPS) ? kHttpsProxyAddrs[endpoint] + : kSocksProxyAddrs[endpoint]; + GetAllocator(endpoint)->set_proxy("unittest/1.0", info); + } void SetAllocatorFlags(int endpoint, int flags) { GetAllocator(endpoint)->set_flags(flags); } @@ -1151,6 +1165,7 @@ class P2PTransportChannelTest : public P2PTransportChannelTestBase { case BLOCK_UDP: case BLOCK_UDP_AND_INCOMING_TCP: case BLOCK_ALL_BUT_OUTGOING_HTTP: + case PROXY_HTTPS: AddAddress(endpoint, kPublicAddrs[endpoint]); // Block all UDP fw()->AddRule(false, rtc::FP_UDP, rtc::FD_ANY, kPublicAddrs[endpoint]); @@ -1166,6 +1181,13 @@ class P2PTransportChannelTest : public P2PTransportChannelTestBase { SocketAddress(rtc::IPAddress(INADDR_ANY), 443)); fw()->AddRule(false, rtc::FP_TCP, rtc::FD_ANY, kPublicAddrs[endpoint]); + } else if (config == PROXY_HTTPS) { + // Block all TCP to/from the endpoint except to the proxy server + fw()->AddRule(true, rtc::FP_TCP, kPublicAddrs[endpoint], + kHttpsProxyAddrs[endpoint]); + fw()->AddRule(false, rtc::FP_TCP, rtc::FD_ANY, + kPublicAddrs[endpoint]); + SetProxy(endpoint, rtc::PROXY_HTTPS); } break; default: @@ -1205,30 +1227,33 @@ class P2PTransportChannelMatrixTest : public P2PTransportChannelTest, // Test matrix. Originator behavior defined by rows, receiever by columns. // TODO(?): Fix NULLs caused by lack of TCP support in NATSocket. +// TODO(?): Fix NULLs caused by no HTTP proxy support. // TODO(?): Rearrange rows/columns from best to worst. const P2PTransportChannelMatrixTest::Result* P2PTransportChannelMatrixTest::kMatrix[NUM_CONFIGS][NUM_CONFIGS] = { - // OPEN CONE ADDR PORT SYMM 2CON SCON !UDP !TCP HTTP + // OPEN CONE ADDR PORT SYMM 2CON SCON !UDP !TCP HTTP PRXH /*OP*/ - {LULU, LUSU, LUSU, LUSU, LUPU, LUSU, LUPU, LTPT, LTPT, LSRS}, + {LULU, LUSU, LUSU, LUSU, LUPU, LUSU, LUPU, LTPT, LTPT, LSRS, NULL}, /*CO*/ - {SULU, SUSU, SUSU, SUSU, SUPU, SUSU, SUPU, NULL, NULL, LSRS}, + {SULU, SUSU, SUSU, SUSU, SUPU, SUSU, SUPU, NULL, NULL, LSRS, NULL}, /*AD*/ - {SULU, SUSU, SUSU, SUSU, SUPU, SUSU, SUPU, NULL, NULL, LSRS}, + {SULU, SUSU, SUSU, SUSU, SUPU, SUSU, SUPU, NULL, NULL, LSRS, NULL}, /*PO*/ - {SULU, SUSU, SUSU, SUSU, RUPU, SUSU, RUPU, NULL, NULL, LSRS}, + {SULU, SUSU, SUSU, SUSU, RUPU, SUSU, RUPU, NULL, NULL, LSRS, NULL}, /*SY*/ - {PULU, PUSU, PUSU, PURU, PURU, PUSU, PURU, NULL, NULL, LSRS}, + {PULU, PUSU, PUSU, PURU, PURU, PUSU, PURU, NULL, NULL, LSRS, NULL}, /*2C*/ - {SULU, SUSU, SUSU, SUSU, SUPU, SUSU, SUPU, NULL, NULL, LSRS}, + {SULU, SUSU, SUSU, SUSU, SUPU, SUSU, SUPU, NULL, NULL, LSRS, NULL}, /*SC*/ - {PULU, PUSU, PUSU, PURU, PURU, PUSU, PURU, NULL, NULL, LSRS}, + {PULU, PUSU, PUSU, PURU, PURU, PUSU, PURU, NULL, NULL, LSRS, NULL}, /*!U*/ - {LTPT, NULL, NULL, NULL, NULL, NULL, NULL, LTPT, LTPT, LSRS}, + {LTPT, NULL, NULL, NULL, NULL, NULL, NULL, LTPT, LTPT, LSRS, NULL}, /*!T*/ - {PTLT, NULL, NULL, NULL, NULL, NULL, NULL, PTLT, LTRT, LSRS}, + {PTLT, NULL, NULL, NULL, NULL, NULL, NULL, PTLT, LTRT, LSRS, NULL}, /*HT*/ - {LSRS, LSRS, LSRS, LSRS, LSRS, LSRS, LSRS, LSRS, LSRS, LSRS}, + {LSRS, LSRS, LSRS, LSRS, LSRS, LSRS, LSRS, LSRS, LSRS, LSRS, NULL}, + /*PR*/ + {NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}, }; // The actual tests that exercise all the various configurations. @@ -1256,6 +1281,7 @@ const P2PTransportChannelMatrixTest::Result* P2P_TEST(x, BLOCK_UDP) \ P2P_TEST(x, BLOCK_UDP_AND_INCOMING_TCP) \ P2P_TEST(x, BLOCK_ALL_BUT_OUTGOING_HTTP) \ + P2P_TEST(x, PROXY_HTTPS) P2P_TEST_SET(OPEN) P2P_TEST_SET(NAT_FULL_CONE) @@ -1267,6 +1293,7 @@ P2P_TEST_SET(NAT_SYMMETRIC_THEN_CONE) P2P_TEST_SET(BLOCK_UDP) P2P_TEST_SET(BLOCK_UDP_AND_INCOMING_TCP) P2P_TEST_SET(BLOCK_ALL_BUT_OUTGOING_HTTP) +P2P_TEST_SET(PROXY_HTTPS) INSTANTIATE_TEST_SUITE_P( All, diff --git a/p2p/base/port.h b/p2p/base/port.h index fade90a4d6..40dc34443b 100644 --- a/p2p/base/port.h +++ b/p2p/base/port.h @@ -53,6 +53,7 @@ #include "rtc_base/network.h" #include "rtc_base/network/received_packet.h" #include "rtc_base/network/sent_packet.h" +#include "rtc_base/proxy_info.h" #include "rtc_base/rate_tracker.h" #include "rtc_base/socket_address.h" #include "rtc_base/system/rtc_export.h" @@ -323,18 +324,11 @@ class RTC_EXPORT Port : public PortInterface, public sigslot::has_slots<> { const std::vector& unknown_types); void set_proxy(absl::string_view user_agent, const rtc::ProxyInfo& proxy) { - RTC_DCHECK_NOTREACHED(); user_agent_ = std::string(user_agent); proxy_ = proxy; } - const std::string& user_agent() override { - RTC_DCHECK_NOTREACHED(); - return user_agent_; - } - const rtc::ProxyInfo& proxy() override { - RTC_DCHECK_NOTREACHED(); - return proxy_; - } + const std::string& user_agent() override { return user_agent_; } + const rtc::ProxyInfo& proxy() override { return proxy_; } void EnablePortPackets() override; diff --git a/p2p/base/port_allocator.h b/p2p/base/port_allocator.h index 7958538e1c..63ecfc6904 100644 --- a/p2p/base/port_allocator.h +++ b/p2p/base/port_allocator.h @@ -29,6 +29,7 @@ #include "rtc_base/checks.h" #include "rtc_base/helpers.h" #include "rtc_base/network.h" +#include "rtc_base/proxy_info.h" #include "rtc_base/socket_address.h" #include "rtc_base/ssl_certificate.h" #include "rtc_base/system/rtc_export.h" @@ -462,6 +463,25 @@ class RTC_EXPORT PortAllocator : public sigslot::has_slots<> { flags_ = flags; } + // These three methods are deprecated. If connections need to go through a + // proxy, the application should create a BasicPortAllocator given a custom + // PacketSocketFactory that creates proxy sockets. + const std::string& user_agent() const { + CheckRunOnValidThreadIfInitialized(); + return agent_; + } + + const rtc::ProxyInfo& proxy() const { + CheckRunOnValidThreadIfInitialized(); + return proxy_; + } + + void set_proxy(absl::string_view agent, const rtc::ProxyInfo& proxy) { + CheckRunOnValidThreadIfInitialized(); + agent_ = std::string(agent); + proxy_ = proxy; + } + // Gets/Sets the port range to use when choosing client ports. int min_port() const { CheckRunOnValidThreadIfInitialized(); @@ -609,6 +629,8 @@ class RTC_EXPORT PortAllocator : public sigslot::has_slots<> { bool initialized_ = false; uint32_t flags_; + std::string agent_; + rtc::ProxyInfo proxy_; int min_port_; int max_port_; int max_ipv6_networks_; diff --git a/p2p/base/port_interface.h b/p2p/base/port_interface.h index 8801d97d4e..70b2085155 100644 --- a/p2p/base/port_interface.h +++ b/p2p/base/port_interface.h @@ -154,10 +154,8 @@ class PortInterface { // The factory used to create the sockets of this port. virtual rtc::PacketSocketFactory* socket_factory() const = 0; - [[deprecated("Unsupported function")]] virtual const std::string& - user_agent() = 0; - [[deprecated("Unsupported function")]] virtual const rtc::ProxyInfo& - proxy() = 0; + virtual const std::string& user_agent() = 0; + virtual const rtc::ProxyInfo& proxy() = 0; // Identifies the generation that this port was created in. virtual uint32_t generation() const = 0; diff --git a/p2p/base/tcp_port.cc b/p2p/base/tcp_port.cc index e4dd4880a1..b1c94a6be7 100644 --- a/p2p/base/tcp_port.cc +++ b/p2p/base/tcp_port.cc @@ -596,7 +596,8 @@ void TCPConnection::CreateOutgoingTcpSocket() { tcp_opts.opts = opts; socket_.reset(port()->socket_factory()->CreateClientTcpSocket( rtc::SocketAddress(port()->Network()->GetBestIP(), 0), - remote_candidate().address(), rtc::ProxyInfo(), std::string(), tcp_opts)); + remote_candidate().address(), port()->proxy(), port()->user_agent(), + tcp_opts)); if (socket_) { RTC_LOG(LS_VERBOSE) << ToString() << ": Connecting from " << socket_->GetLocalAddress().ToSensitiveString() diff --git a/p2p/base/turn_port.cc b/p2p/base/turn_port.cc index 6c2110a873..8f8a64f6e7 100644 --- a/p2p/base/turn_port.cc +++ b/p2p/base/turn_port.cc @@ -439,7 +439,7 @@ bool TurnPort::CreateTurnClientSocket() { tcp_options.tls_cert_verifier = tls_cert_verifier_; socket_ = socket_factory()->CreateClientTcpSocket( rtc::SocketAddress(Network()->GetBestIP(), 0), server_address_.address, - rtc::ProxyInfo(), std::string(), tcp_options); + proxy(), user_agent(), tcp_options); } if (!socket_) { diff --git a/p2p/client/basic_port_allocator.cc b/p2p/client/basic_port_allocator.cc index 832d6da27a..a0b93be23f 100644 --- a/p2p/client/basic_port_allocator.cc +++ b/p2p/client/basic_port_allocator.cc @@ -937,6 +937,8 @@ void BasicPortAllocatorSession::AddAllocatedPort(Port* port, port->set_content_name(content_name()); port->set_component(component()); port->set_generation(generation()); + if (allocator_->proxy().type != rtc::PROXY_NONE) + port->set_proxy(allocator_->user_agent(), allocator_->proxy()); port->set_send_retransmit_count_attribute( (flags() & PORTALLOCATOR_ENABLE_STUN_RETRANSMIT_ATTRIBUTE) != 0); diff --git a/rtc_base/BUILD.gn b/rtc_base/BUILD.gn index 6d52046ca0..2ba00e0034 100644 --- a/rtc_base/BUILD.gn +++ b/rtc_base/BUILD.gn @@ -1275,6 +1275,7 @@ rtc_library("socket_adapters") { ":byte_buffer", ":checks", ":crypt_string", + ":http_common", ":logging", ":stringutils", ":zero_memory", @@ -1388,8 +1389,14 @@ rtc_library("dscp") { rtc_library("proxy_info") { visibility = [ "*" ] - sources = [ "proxy_info.h" ] - deps = [] + sources = [ + "proxy_info.cc", + "proxy_info.h", + ] + deps = [ + ":crypt_string", + ":socket_address", + ] } rtc_library("file_rotating_stream") { @@ -1587,6 +1594,24 @@ rtc_library("crypt_string") { ] } +rtc_library("http_common") { + sources = [ + "http_common.cc", + "http_common.h", + ] + deps = [ + ":crypt_string", + ":logging", + ":socket_address", + ":ssl", + ":stringutils", + ":zero_memory", + "third_party/base64", + ] + + absl_deps = [ "//third_party/abseil-cpp/absl/strings" ] +} + rtc_source_set("gtest_prod") { sources = [ "gtest_prod_util.h" ] } diff --git a/rtc_base/http_common.cc b/rtc_base/http_common.cc new file mode 100644 index 0000000000..1457fbf8e6 --- /dev/null +++ b/rtc_base/http_common.cc @@ -0,0 +1,554 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include + +#include "absl/strings/string_view.h" + +#if defined(WEBRTC_WIN) +#include +#include +#include + +#define SECURITY_WIN32 +#include +#endif + +#include // for isspace +#include // for sprintf + +#include // for pair +#include + +#include "absl/strings/match.h" +#include "rtc_base/crypt_string.h" // for CryptString +#include "rtc_base/http_common.h" +#include "rtc_base/logging.h" +#include "rtc_base/message_digest.h" +#include "rtc_base/socket_address.h" +#include "rtc_base/string_utils.h" +#include "rtc_base/strings/string_builder.h" +#include "rtc_base/third_party/base64/base64.h" // for Base64 +#include "rtc_base/zero_memory.h" // for ExplicitZeroMemory + +namespace rtc { +namespace { +#if defined(WEBRTC_WIN) && !defined(WINUWP) +/////////////////////////////////////////////////////////////////////////////// +// ConstantToLabel can be used to easily generate string names from constant +// values. This can be useful for logging descriptive names of error messages. +// Usage: +// const ConstantToLabel LIBRARY_ERRORS[] = { +// KLABEL(SOME_ERROR), +// KLABEL(SOME_OTHER_ERROR), +// ... +// LASTLABEL +// } +// +// int err = LibraryFunc(); +// LOG(LS_ERROR) << "LibraryFunc returned: " +// << GetErrorName(err, LIBRARY_ERRORS); +struct ConstantToLabel { + int value; + const char* label; +}; + +const char* LookupLabel(int value, const ConstantToLabel entries[]) { + for (int i = 0; entries[i].label; ++i) { + if (value == entries[i].value) { + return entries[i].label; + } + } + return 0; +} + +std::string GetErrorName(int err, const ConstantToLabel* err_table) { + if (err == 0) + return "No error"; + + if (err_table != 0) { + if (const char* value = LookupLabel(err, err_table)) + return value; + } + + char buffer[16]; + snprintf(buffer, sizeof(buffer), "0x%08x", err); + return buffer; +} + +#define KLABEL(x) \ + { x, #x } +#define LASTLABEL \ + { 0, 0 } + +const ConstantToLabel SECURITY_ERRORS[] = { + KLABEL(SEC_I_COMPLETE_AND_CONTINUE), + KLABEL(SEC_I_COMPLETE_NEEDED), + KLABEL(SEC_I_CONTEXT_EXPIRED), + KLABEL(SEC_I_CONTINUE_NEEDED), + KLABEL(SEC_I_INCOMPLETE_CREDENTIALS), + KLABEL(SEC_I_RENEGOTIATE), + KLABEL(SEC_E_CERT_EXPIRED), + KLABEL(SEC_E_INCOMPLETE_MESSAGE), + KLABEL(SEC_E_INSUFFICIENT_MEMORY), + KLABEL(SEC_E_INTERNAL_ERROR), + KLABEL(SEC_E_INVALID_HANDLE), + KLABEL(SEC_E_INVALID_TOKEN), + KLABEL(SEC_E_LOGON_DENIED), + KLABEL(SEC_E_NO_AUTHENTICATING_AUTHORITY), + KLABEL(SEC_E_NO_CREDENTIALS), + KLABEL(SEC_E_NOT_OWNER), + KLABEL(SEC_E_OK), + KLABEL(SEC_E_SECPKG_NOT_FOUND), + KLABEL(SEC_E_TARGET_UNKNOWN), + KLABEL(SEC_E_UNKNOWN_CREDENTIALS), + KLABEL(SEC_E_UNSUPPORTED_FUNCTION), + KLABEL(SEC_E_UNTRUSTED_ROOT), + KLABEL(SEC_E_WRONG_PRINCIPAL), + LASTLABEL}; +#undef KLABEL +#undef LASTLABEL +#endif // defined(WEBRTC_WIN) && !defined(WINUWP) + +typedef std::pair HttpAttribute; +typedef std::vector HttpAttributeList; + +inline bool IsEndOfAttributeName(size_t pos, absl::string_view data) { + if (pos >= data.size()) + return true; + if (isspace(static_cast(data[pos]))) + return true; + // The reason for this complexity is that some attributes may contain trailing + // equal signs (like base64 tokens in Negotiate auth headers) + if ((pos + 1 < data.size()) && (data[pos] == '=') && + !isspace(static_cast(data[pos + 1])) && + (data[pos + 1] != '=')) { + return true; + } + return false; +} + +void HttpParseAttributes(absl::string_view data, + HttpAttributeList& attributes) { + size_t pos = 0; + const size_t len = data.size(); + while (true) { + // Skip leading whitespace + while ((pos < len) && isspace(static_cast(data[pos]))) { + ++pos; + } + + // End of attributes? + if (pos >= len) + return; + + // Find end of attribute name + size_t start = pos; + while (!IsEndOfAttributeName(pos, data)) { + ++pos; + } + + HttpAttribute attribute; + attribute.first.assign(data.data() + start, data.data() + pos); + + // Attribute has value? + if ((pos < len) && (data[pos] == '=')) { + ++pos; // Skip '=' + // Check if quoted value + if ((pos < len) && (data[pos] == '"')) { + while (++pos < len) { + if (data[pos] == '"') { + ++pos; + break; + } + if ((data[pos] == '\\') && (pos + 1 < len)) + ++pos; + attribute.second.append(1, data[pos]); + } + } else { + while ((pos < len) && !isspace(static_cast(data[pos])) && + (data[pos] != ',')) { + attribute.second.append(1, data[pos++]); + } + } + } + + attributes.push_back(attribute); + if ((pos < len) && (data[pos] == ',')) + ++pos; // Skip ',' + } +} + +bool HttpHasAttribute(const HttpAttributeList& attributes, + absl::string_view name, + std::string* value) { + for (HttpAttributeList::const_iterator it = attributes.begin(); + it != attributes.end(); ++it) { + if (it->first == name) { + if (value) { + *value = it->second; + } + return true; + } + } + return false; +} + +bool HttpHasNthAttribute(HttpAttributeList& attributes, + size_t index, + std::string* name, + std::string* value) { + if (index >= attributes.size()) + return false; + + if (name) + *name = attributes[index].first; + if (value) + *value = attributes[index].second; + return true; +} + +std::string quote(absl::string_view str) { + std::string result; + result.push_back('"'); + for (size_t i = 0; i < str.size(); ++i) { + if ((str[i] == '"') || (str[i] == '\\')) + result.push_back('\\'); + result.push_back(str[i]); + } + result.push_back('"'); + return result; +} + +#if defined(WEBRTC_WIN) && !defined(WINUWP) +struct NegotiateAuthContext : public HttpAuthContext { + CredHandle cred; + CtxtHandle ctx; + size_t steps; + bool specified_credentials; + + NegotiateAuthContext(absl::string_view auth, CredHandle c1, CtxtHandle c2) + : HttpAuthContext(auth), + cred(c1), + ctx(c2), + steps(0), + specified_credentials(false) {} + + ~NegotiateAuthContext() override { + DeleteSecurityContext(&ctx); + FreeCredentialsHandle(&cred); + } +}; +#endif // defined(WEBRTC_WIN) && !defined(WINUWP) + +} // anonymous namespace + +HttpAuthResult HttpAuthenticate(absl::string_view challenge, + const SocketAddress& server, + absl::string_view method, + absl::string_view uri, + absl::string_view username, + const CryptString& password, + HttpAuthContext*& context, + std::string& response, + std::string& auth_method) { + HttpAttributeList args; + HttpParseAttributes(challenge, args); + HttpHasNthAttribute(args, 0, &auth_method, nullptr); + + if (context && (context->auth_method != auth_method)) + return HAR_IGNORE; + + // BASIC + if (absl::EqualsIgnoreCase(auth_method, "basic")) { + if (context) + return HAR_CREDENTIALS; // Bad credentials + if (username.empty()) + return HAR_CREDENTIALS; // Missing credentials + + context = new HttpAuthContext(auth_method); + + // TODO(bugs.webrtc.org/8905): Convert sensitive to a CryptString and also + // return response as CryptString so contents get securely deleted + // automatically. + // std::string decoded = username + ":" + password; + size_t len = username.size() + password.GetLength() + 2; + char* sensitive = new char[len]; + size_t pos = strcpyn(sensitive, len, username); + pos += strcpyn(sensitive + pos, len - pos, ":"); + password.CopyTo(sensitive + pos, true); + + response = auth_method; + response.append(" "); + // TODO: create a sensitive-source version of Base64::encode + response.append(Base64::Encode(sensitive)); + ExplicitZeroMemory(sensitive, len); + delete[] sensitive; + return HAR_RESPONSE; + } + + // DIGEST + if (absl::EqualsIgnoreCase(auth_method, "digest")) { + if (context) + return HAR_CREDENTIALS; // Bad credentials + if (username.empty()) + return HAR_CREDENTIALS; // Missing credentials + + context = new HttpAuthContext(auth_method); + + std::string cnonce, ncount; + char buffer[256]; + snprintf(buffer, sizeof(buffer), "%d", static_cast(time(0))); + cnonce = MD5(buffer); + ncount = "00000001"; + + std::string realm, nonce, qop, opaque; + HttpHasAttribute(args, "realm", &realm); + HttpHasAttribute(args, "nonce", &nonce); + bool has_qop = HttpHasAttribute(args, "qop", &qop); + bool has_opaque = HttpHasAttribute(args, "opaque", &opaque); + + // TODO(bugs.webrtc.org/8905): Convert sensitive to a CryptString and also + // return response as CryptString so contents get securely deleted + // automatically. + // std::string A1 = username + ":" + realm + ":" + password; + size_t len = username.size() + realm.size() + password.GetLength() + 3; + char* sensitive = new char[len]; // A1 + size_t pos = strcpyn(sensitive, len, username); + pos += strcpyn(sensitive + pos, len - pos, ":"); + pos += strcpyn(sensitive + pos, len - pos, realm); + pos += strcpyn(sensitive + pos, len - pos, ":"); + password.CopyTo(sensitive + pos, true); + + std::string A2 = std::string(method) + ":" + std::string(uri); + std::string middle; + if (has_qop) { + qop = "auth"; + middle = nonce + ":" + ncount + ":" + cnonce + ":" + qop; + } else { + middle = nonce; + } + std::string HA1 = MD5(sensitive); + ExplicitZeroMemory(sensitive, len); + delete[] sensitive; + std::string HA2 = MD5(A2); + std::string dig_response = MD5(HA1 + ":" + middle + ":" + HA2); + + rtc::StringBuilder ss; + ss << auth_method; + ss << " username=" << quote(username); + ss << ", realm=" << quote(realm); + ss << ", nonce=" << quote(nonce); + ss << ", uri=" << quote(uri); + if (has_qop) { + ss << ", qop=" << qop; + ss << ", nc=" << ncount; + ss << ", cnonce=" << quote(cnonce); + } + ss << ", response=\"" << dig_response << "\""; + if (has_opaque) { + ss << ", opaque=" << quote(opaque); + } + response = ss.str(); + return HAR_RESPONSE; + } + +#if defined(WEBRTC_WIN) && !defined(WINUWP) +#if 1 + bool want_negotiate = absl::EqualsIgnoreCase(auth_method, "negotiate"); + bool want_ntlm = absl::EqualsIgnoreCase(auth_method, "ntlm"); + // SPNEGO & NTLM + if (want_negotiate || want_ntlm) { + const size_t MAX_MESSAGE = 12000, MAX_SPN = 256; + char out_buf[MAX_MESSAGE], spn[MAX_SPN]; + +#if 0 // Requires funky windows versions + DWORD len = MAX_SPN; + if (DsMakeSpn("HTTP", server.HostAsURIString().c_str(), nullptr, + server.port(), + 0, &len, spn) != ERROR_SUCCESS) { + RTC_LOG_F(LS_WARNING) << "(Negotiate) - DsMakeSpn failed"; + return HAR_IGNORE; + } +#else + snprintf(spn, MAX_SPN, "HTTP/%s", server.ToString().c_str()); +#endif + + SecBuffer out_sec; + out_sec.pvBuffer = out_buf; + out_sec.cbBuffer = sizeof(out_buf); + out_sec.BufferType = SECBUFFER_TOKEN; + + SecBufferDesc out_buf_desc; + out_buf_desc.ulVersion = 0; + out_buf_desc.cBuffers = 1; + out_buf_desc.pBuffers = &out_sec; + + const ULONG NEG_FLAGS_DEFAULT = + // ISC_REQ_ALLOCATE_MEMORY + ISC_REQ_CONFIDENTIALITY + //| ISC_REQ_EXTENDED_ERROR + //| ISC_REQ_INTEGRITY + | ISC_REQ_REPLAY_DETECT | ISC_REQ_SEQUENCE_DETECT + //| ISC_REQ_STREAM + //| ISC_REQ_USE_SUPPLIED_CREDS + ; + + ::TimeStamp lifetime; + SECURITY_STATUS ret = S_OK; + ULONG ret_flags = 0, flags = NEG_FLAGS_DEFAULT; + + bool specify_credentials = !username.empty(); + size_t steps = 0; + + // uint32_t now = Time(); + + NegotiateAuthContext* neg = static_cast(context); + if (neg) { + const size_t max_steps = 10; + if (++neg->steps >= max_steps) { + RTC_LOG(LS_WARNING) << "HttpAuthenticate (Negotiate) too many retries"; + return HAR_ERROR; + } + steps = neg->steps; + + std::string challenge, decoded_challenge; + if (HttpHasNthAttribute(args, 1, &challenge, nullptr) && + Base64::Decode(challenge, Base64::DO_STRICT, &decoded_challenge, + nullptr)) { + SecBuffer in_sec; + in_sec.pvBuffer = const_cast(decoded_challenge.data()); + in_sec.cbBuffer = static_cast(decoded_challenge.size()); + in_sec.BufferType = SECBUFFER_TOKEN; + + SecBufferDesc in_buf_desc; + in_buf_desc.ulVersion = 0; + in_buf_desc.cBuffers = 1; + in_buf_desc.pBuffers = &in_sec; + + ret = InitializeSecurityContextA( + &neg->cred, &neg->ctx, spn, flags, 0, SECURITY_NATIVE_DREP, + &in_buf_desc, 0, &neg->ctx, &out_buf_desc, &ret_flags, &lifetime); + if (FAILED(ret)) { + RTC_LOG(LS_ERROR) << "InitializeSecurityContext returned: " + << GetErrorName(ret, SECURITY_ERRORS); + return HAR_ERROR; + } + } else if (neg->specified_credentials) { + // Try again with default credentials + specify_credentials = false; + delete context; + context = neg = 0; + } else { + return HAR_CREDENTIALS; + } + } + + if (!neg) { + unsigned char userbuf[256], passbuf[256], domainbuf[16]; + SEC_WINNT_AUTH_IDENTITY_A auth_id, *pauth_id = 0; + if (specify_credentials) { + memset(&auth_id, 0, sizeof(auth_id)); + size_t len = password.GetLength() + 1; + char* sensitive = new char[len]; + password.CopyTo(sensitive, true); + absl::string_view::size_type pos = username.find('\\'); + if (pos == absl::string_view::npos) { + auth_id.UserLength = static_cast( + std::min(sizeof(userbuf) - 1, username.size())); + memcpy(userbuf, username.data(), auth_id.UserLength); + userbuf[auth_id.UserLength] = 0; + auth_id.DomainLength = 0; + domainbuf[auth_id.DomainLength] = 0; + auth_id.PasswordLength = static_cast( + std::min(sizeof(passbuf) - 1, password.GetLength())); + memcpy(passbuf, sensitive, auth_id.PasswordLength); + passbuf[auth_id.PasswordLength] = 0; + } else { + auth_id.UserLength = static_cast( + std::min(sizeof(userbuf) - 1, username.size() - pos - 1)); + memcpy(userbuf, username.data() + pos + 1, auth_id.UserLength); + userbuf[auth_id.UserLength] = 0; + auth_id.DomainLength = + static_cast(std::min(sizeof(domainbuf) - 1, pos)); + memcpy(domainbuf, username.data(), auth_id.DomainLength); + domainbuf[auth_id.DomainLength] = 0; + auth_id.PasswordLength = static_cast( + std::min(sizeof(passbuf) - 1, password.GetLength())); + memcpy(passbuf, sensitive, auth_id.PasswordLength); + passbuf[auth_id.PasswordLength] = 0; + } + ExplicitZeroMemory(sensitive, len); + delete[] sensitive; + auth_id.User = userbuf; + auth_id.Domain = domainbuf; + auth_id.Password = passbuf; + auth_id.Flags = SEC_WINNT_AUTH_IDENTITY_ANSI; + pauth_id = &auth_id; + RTC_LOG(LS_VERBOSE) + << "Negotiate protocol: Using specified credentials"; + } else { + RTC_LOG(LS_VERBOSE) << "Negotiate protocol: Using default credentials"; + } + + CredHandle cred; + ret = AcquireCredentialsHandleA( + 0, const_cast(want_negotiate ? NEGOSSP_NAME_A : NTLMSP_NAME_A), + SECPKG_CRED_OUTBOUND, 0, pauth_id, 0, 0, &cred, &lifetime); + if (ret != SEC_E_OK) { + RTC_LOG(LS_ERROR) << "AcquireCredentialsHandle error: " + << GetErrorName(ret, SECURITY_ERRORS); + return HAR_IGNORE; + } + + // CSecBufferBundle<5, CSecBufferBase::FreeSSPI> sb_out; + + CtxtHandle ctx; + ret = InitializeSecurityContextA(&cred, 0, spn, flags, 0, + SECURITY_NATIVE_DREP, 0, 0, &ctx, + &out_buf_desc, &ret_flags, &lifetime); + if (FAILED(ret)) { + RTC_LOG(LS_ERROR) << "InitializeSecurityContext returned: " + << GetErrorName(ret, SECURITY_ERRORS); + FreeCredentialsHandle(&cred); + return HAR_IGNORE; + } + + RTC_DCHECK(!context); + context = neg = new NegotiateAuthContext(auth_method, cred, ctx); + neg->specified_credentials = specify_credentials; + neg->steps = steps; + } + + if ((ret == SEC_I_COMPLETE_NEEDED) || + (ret == SEC_I_COMPLETE_AND_CONTINUE)) { + ret = CompleteAuthToken(&neg->ctx, &out_buf_desc); + RTC_LOG(LS_VERBOSE) << "CompleteAuthToken returned: " + << GetErrorName(ret, SECURITY_ERRORS); + if (FAILED(ret)) { + return HAR_ERROR; + } + } + + std::string decoded(out_buf, out_buf + out_sec.cbBuffer); + response = auth_method; + response.append(" "); + response.append(Base64::Encode(decoded)); + return HAR_RESPONSE; + } +#endif +#endif // defined(WEBRTC_WIN) && !defined(WINUWP) + + return HAR_IGNORE; +} + +////////////////////////////////////////////////////////////////////// + +} // namespace rtc diff --git a/rtc_base/http_common.h b/rtc_base/http_common.h new file mode 100644 index 0000000000..06e42c6703 --- /dev/null +++ b/rtc_base/http_common.h @@ -0,0 +1,53 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef RTC_BASE_HTTP_COMMON_H_ +#define RTC_BASE_HTTP_COMMON_H_ + +#include + +#include "absl/strings/string_view.h" + +namespace rtc { + +class CryptString; +class SocketAddress; + +////////////////////////////////////////////////////////////////////// +// Http Authentication +////////////////////////////////////////////////////////////////////// + +struct HttpAuthContext { + std::string auth_method; + HttpAuthContext(absl::string_view auth) : auth_method(auth) {} + virtual ~HttpAuthContext() {} +}; + +enum HttpAuthResult { HAR_RESPONSE, HAR_IGNORE, HAR_CREDENTIALS, HAR_ERROR }; + +// 'context' is used by this function to record information between calls. +// Start by passing a null pointer, then pass the same pointer each additional +// call. When the authentication attempt is finished, delete the context. +// TODO(bugs.webrtc.org/8905): Change "response" to "ZeroOnFreeBuffer". +HttpAuthResult HttpAuthenticate(absl::string_view challenge, + const SocketAddress& server, + absl::string_view method, + absl::string_view uri, + absl::string_view username, + const CryptString& password, + HttpAuthContext*& context, + std::string& response, + std::string& auth_method); + +////////////////////////////////////////////////////////////////////// + +} // namespace rtc + +#endif // RTC_BASE_HTTP_COMMON_H_ diff --git a/rtc_base/proxy_info.cc b/rtc_base/proxy_info.cc new file mode 100644 index 0000000000..23d60afa74 --- /dev/null +++ b/rtc_base/proxy_info.cc @@ -0,0 +1,23 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "rtc_base/proxy_info.h" + +namespace rtc { + +const char* ProxyToString(ProxyType proxy) { + const char* const PROXY_NAMES[] = {"none", "https", "socks5", "unknown"}; + return PROXY_NAMES[proxy]; +} + +ProxyInfo::ProxyInfo() : type(PROXY_NONE), autodetect(false) {} +ProxyInfo::~ProxyInfo() = default; + +} // namespace rtc diff --git a/rtc_base/proxy_info.h b/rtc_base/proxy_info.h index a12ae1925b..e614692025 100644 --- a/rtc_base/proxy_info.h +++ b/rtc_base/proxy_info.h @@ -11,9 +11,28 @@ #ifndef RTC_BASE_PROXY_INFO_H_ #define RTC_BASE_PROXY_INFO_H_ +#include + +#include "rtc_base/crypt_string.h" +#include "rtc_base/socket_address.h" + namespace rtc { -// TODO(tommi): Remove. -struct ProxyInfo {}; + +enum ProxyType { PROXY_NONE, PROXY_HTTPS, PROXY_SOCKS5, PROXY_UNKNOWN }; +const char* ProxyToString(ProxyType proxy); + +struct ProxyInfo { + ProxyType type; + SocketAddress address; + std::string autoconfig_url; + bool autodetect; + std::string bypass_list; + std::string username; + CryptString password; + + ProxyInfo(); + ~ProxyInfo(); +}; } // namespace rtc diff --git a/rtc_base/socket_adapters.cc b/rtc_base/socket_adapters.cc index 2a157bfc49..f6fa182f5d 100644 --- a/rtc_base/socket_adapters.cc +++ b/rtc_base/socket_adapters.cc @@ -21,6 +21,7 @@ #include "rtc_base/buffer.h" #include "rtc_base/byte_buffer.h" #include "rtc_base/checks.h" +#include "rtc_base/http_common.h" #include "rtc_base/logging.h" #include "rtc_base/strings/string_builder.h" #include "rtc_base/zero_memory.h" @@ -215,4 +216,257 @@ void AsyncSSLSocket::ProcessInput(char* data, size_t* len) { SignalReadEvent(this); } +/////////////////////////////////////////////////////////////////////////////// + +AsyncHttpsProxySocket::AsyncHttpsProxySocket(Socket* socket, + absl::string_view user_agent, + const SocketAddress& proxy, + absl::string_view username, + const CryptString& password) + : BufferedReadAdapter(socket, 1024), + proxy_(proxy), + agent_(user_agent), + user_(username), + pass_(password), + force_connect_(false), + state_(PS_ERROR), + context_(0) {} + +AsyncHttpsProxySocket::~AsyncHttpsProxySocket() { + delete context_; +} + +int AsyncHttpsProxySocket::Connect(const SocketAddress& addr) { + int ret; + RTC_LOG(LS_VERBOSE) << "AsyncHttpsProxySocket::Connect(" + << proxy_.ToSensitiveString() << ")"; + dest_ = addr; + state_ = PS_INIT; + if (ShouldIssueConnect()) { + BufferInput(true); + } + ret = BufferedReadAdapter::Connect(proxy_); + // TODO: Set state_ appropriately if Connect fails. + return ret; +} + +SocketAddress AsyncHttpsProxySocket::GetRemoteAddress() const { + return dest_; +} + +int AsyncHttpsProxySocket::Close() { + headers_.clear(); + state_ = PS_ERROR; + dest_.Clear(); + delete context_; + context_ = nullptr; + return BufferedReadAdapter::Close(); +} + +Socket::ConnState AsyncHttpsProxySocket::GetState() const { + if (state_ < PS_TUNNEL) { + return CS_CONNECTING; + } else if (state_ == PS_TUNNEL) { + return CS_CONNECTED; + } else { + return CS_CLOSED; + } +} + +void AsyncHttpsProxySocket::OnConnectEvent(Socket* socket) { + RTC_LOG(LS_VERBOSE) << "AsyncHttpsProxySocket::OnConnectEvent"; + if (!ShouldIssueConnect()) { + state_ = PS_TUNNEL; + BufferedReadAdapter::OnConnectEvent(socket); + return; + } + SendRequest(); +} + +void AsyncHttpsProxySocket::OnCloseEvent(Socket* socket, int err) { + RTC_LOG(LS_VERBOSE) << "AsyncHttpsProxySocket::OnCloseEvent(" << err << ")"; + if ((state_ == PS_WAIT_CLOSE) && (err == 0)) { + state_ = PS_ERROR; + Connect(dest_); + } else { + BufferedReadAdapter::OnCloseEvent(socket, err); + } +} + +void AsyncHttpsProxySocket::ProcessInput(char* data, size_t* len) { + size_t start = 0; + for (size_t pos = start; state_ < PS_TUNNEL && pos < *len;) { + if (state_ == PS_SKIP_BODY) { + size_t consume = std::min(*len - pos, content_length_); + pos += consume; + start = pos; + content_length_ -= consume; + if (content_length_ == 0) { + EndResponse(); + } + continue; + } + + if (data[pos++] != '\n') + continue; + + size_t length = pos - start - 1; + if ((length > 0) && (data[start + length - 1] == '\r')) + --length; + + data[start + length] = 0; + ProcessLine(data + start, length); + start = pos; + } + + *len -= start; + if (*len > 0) { + memmove(data, data + start, *len); + } + + if (state_ != PS_TUNNEL) + return; + + bool remainder = (*len > 0); + BufferInput(false); + SignalConnectEvent(this); + + // FIX: if SignalConnect causes the socket to be destroyed, we are in trouble + if (remainder) + SignalReadEvent(this); // TODO: signal this?? +} + +bool AsyncHttpsProxySocket::ShouldIssueConnect() const { + // TODO: Think about whether a more sophisticated test + // than dest port == 80 is needed. + return force_connect_ || (dest_.port() != 80); +} + +void AsyncHttpsProxySocket::SendRequest() { + rtc::StringBuilder ss; + ss << "CONNECT " << dest_.ToString() << " HTTP/1.0\r\n"; + ss << "User-Agent: " << agent_ << "\r\n"; + ss << "Host: " << dest_.HostAsURIString() << "\r\n"; + ss << "Content-Length: 0\r\n"; + ss << "Proxy-Connection: Keep-Alive\r\n"; + ss << headers_; + ss << "\r\n"; + std::string str = ss.str(); + DirectSend(str.c_str(), str.size()); + state_ = PS_LEADER; + expect_close_ = true; + content_length_ = 0; + headers_.clear(); + + RTC_LOG(LS_VERBOSE) << "AsyncHttpsProxySocket >> " << str; +} + +void AsyncHttpsProxySocket::ProcessLine(char* data, size_t len) { + RTC_LOG(LS_VERBOSE) << "AsyncHttpsProxySocket << " << data; + + if (len == 0) { + if (state_ == PS_TUNNEL_HEADERS) { + state_ = PS_TUNNEL; + } else if (state_ == PS_ERROR_HEADERS) { + Error(defer_error_); + return; + } else if (state_ == PS_SKIP_HEADERS) { + if (content_length_) { + state_ = PS_SKIP_BODY; + } else { + EndResponse(); + return; + } + } else { + if (!unknown_mechanisms_.empty()) { + RTC_LOG(LS_ERROR) << "Unsupported authentication methods: " + << unknown_mechanisms_; + } + // Unexpected end of headers + Error(0); + return; + } + } else if (state_ == PS_LEADER) { + unsigned int code; + if (sscanf(data, "HTTP/%*u.%*u %u", &code) != 1) { + Error(0); + return; + } + switch (code) { + case 200: + // connection good! + state_ = PS_TUNNEL_HEADERS; + return; +#if defined(HTTP_STATUS_PROXY_AUTH_REQ) && (HTTP_STATUS_PROXY_AUTH_REQ != 407) +#error Wrong code for HTTP_STATUS_PROXY_AUTH_REQ +#endif + case 407: // HTTP_STATUS_PROXY_AUTH_REQ + state_ = PS_AUTHENTICATE; + return; + default: + defer_error_ = 0; + state_ = PS_ERROR_HEADERS; + return; + } + } else if ((state_ == PS_AUTHENTICATE) && + absl::StartsWithIgnoreCase(data, "Proxy-Authenticate:")) { + std::string response, auth_method; + switch (HttpAuthenticate(absl::string_view(data + 19, len - 19), proxy_, + "CONNECT", "/", user_, pass_, context_, response, + auth_method)) { + case HAR_IGNORE: + RTC_LOG(LS_VERBOSE) << "Ignoring Proxy-Authenticate: " << auth_method; + if (!unknown_mechanisms_.empty()) + unknown_mechanisms_.append(", "); + unknown_mechanisms_.append(auth_method); + break; + case HAR_RESPONSE: + headers_ = "Proxy-Authorization: "; + headers_.append(response); + headers_.append("\r\n"); + state_ = PS_SKIP_HEADERS; + unknown_mechanisms_.clear(); + break; + case HAR_CREDENTIALS: + defer_error_ = SOCKET_EACCES; + state_ = PS_ERROR_HEADERS; + unknown_mechanisms_.clear(); + break; + case HAR_ERROR: + defer_error_ = 0; + state_ = PS_ERROR_HEADERS; + unknown_mechanisms_.clear(); + break; + } + } else if (absl::StartsWithIgnoreCase(data, "Content-Length:")) { + content_length_ = strtoul(data + 15, 0, 0); + } else if (absl::StartsWithIgnoreCase(data, "Proxy-Connection: Keep-Alive")) { + expect_close_ = false; + /* + } else if (absl::StartsWithIgnoreCase(data, "Connection: close") { + expect_close_ = true; + */ + } +} + +void AsyncHttpsProxySocket::EndResponse() { + if (!expect_close_) { + SendRequest(); + return; + } + + // No point in waiting for the server to close... let's close now + // TODO: Refactor out PS_WAIT_CLOSE + state_ = PS_WAIT_CLOSE; + BufferedReadAdapter::Close(); + OnCloseEvent(this, 0); +} + +void AsyncHttpsProxySocket::Error(int error) { + BufferInput(false); + Close(); + SetError(error); + SignalCloseEvent(this, error); +} + } // namespace rtc diff --git a/rtc_base/socket_adapters.h b/rtc_base/socket_adapters.h index 33ae94ebe8..e67c8d4db9 100644 --- a/rtc_base/socket_adapters.h +++ b/rtc_base/socket_adapters.h @@ -77,6 +77,66 @@ class AsyncSSLSocket : public BufferedReadAdapter { void ProcessInput(char* data, size_t* len) override; }; +/////////////////////////////////////////////////////////////////////////////// + +// Implements a socket adapter that speaks the HTTP/S proxy protocol. +class AsyncHttpsProxySocket : public BufferedReadAdapter { + public: + AsyncHttpsProxySocket(Socket* socket, + absl::string_view user_agent, + const SocketAddress& proxy, + absl::string_view username, + const CryptString& password); + ~AsyncHttpsProxySocket() override; + + AsyncHttpsProxySocket(const AsyncHttpsProxySocket&) = delete; + AsyncHttpsProxySocket& operator=(const AsyncHttpsProxySocket&) = delete; + + // If connect is forced, the adapter will always issue an HTTP CONNECT to the + // target address. Otherwise, it will connect only if the destination port + // is not port 80. + void SetForceConnect(bool force) { force_connect_ = force; } + + int Connect(const SocketAddress& addr) override; + SocketAddress GetRemoteAddress() const override; + int Close() override; + ConnState GetState() const override; + + protected: + void OnConnectEvent(Socket* socket) override; + void OnCloseEvent(Socket* socket, int err) override; + void ProcessInput(char* data, size_t* len) override; + + bool ShouldIssueConnect() const; + void SendRequest(); + void ProcessLine(char* data, size_t len); + void EndResponse(); + void Error(int error); + + private: + SocketAddress proxy_, dest_; + std::string agent_, user_, headers_; + CryptString pass_; + bool force_connect_; + size_t content_length_; + int defer_error_; + bool expect_close_; + enum ProxyState { + PS_INIT, + PS_LEADER, + PS_AUTHENTICATE, + PS_SKIP_HEADERS, + PS_ERROR_HEADERS, + PS_TUNNEL_HEADERS, + PS_SKIP_BODY, + PS_TUNNEL, + PS_WAIT_CLOSE, + PS_ERROR + } state_; + HttpAuthContext* context_; + std::string unknown_mechanisms_; +}; + } // namespace rtc #endif // RTC_BASE_SOCKET_ADAPTERS_H_