webrtc/rtc_base/socket_adapters.cc
Anders Klemets 1425d40369 Remove MessageBoxA UI API call from socket code
There is code in socket_adapters.cc that was trying to display UI by
invoking the MessageBoxA API. This causes a linker failure when building
apps for versions of Windows that do not have the MessageBoxA API.

The text message that the socket code tries display also does not seem
right. It references Google Talk and provides a HTTP URI that is
invalid. The message is only in English instead of being localized in
all the languages supported by the app.

I am fixing this by replacing the call to MessageBoxA with a call to
RTC_LOG(LS_ERROR).
I am also attempting to clean up the text of the message by removing
the invalid URL and removing references to Google products. I am trying
to make the logging message more matter-of-fact about what is going on.
As I understand it, the message is displayed when a HTTP proxy sends a
Proxy-Authenticate HTTP response header that specifies an unsupported
authentication scheme. I changed the text of the logging message to
state this.

Bug: webrtc:11187
Change-Id: I14df32943b62130ac623f72fe901e8f2bb1e8f24
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/161475
Reviewed-by: Tommi <tommi@webrtc.org>
Reviewed-by: Karl Wiberg <kwiberg@webrtc.org>
Reviewed-by: Henrik Andreassson <henrika@webrtc.org>
Commit-Queue: Tommi <tommi@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#30046}
2019-12-10 08:32:10 +00:00

657 lines
19 KiB
C++

/*
* 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.
*/
#if defined(_MSC_VER) && _MSC_VER < 1300
#pragma warning(disable : 4786)
#endif
#include <algorithm>
#include "absl/strings/match.h"
#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/socket_adapters.h"
#include "rtc_base/strings/string_builder.h"
#include "rtc_base/zero_memory.h"
namespace rtc {
BufferedReadAdapter::BufferedReadAdapter(AsyncSocket* socket, size_t size)
: AsyncSocketAdapter(socket),
buffer_size_(size),
data_len_(0),
buffering_(false) {
buffer_ = new char[buffer_size_];
}
BufferedReadAdapter::~BufferedReadAdapter() {
delete[] buffer_;
}
int BufferedReadAdapter::Send(const void* pv, size_t cb) {
if (buffering_) {
// TODO: Spoof error better; Signal Writeable
socket_->SetError(EWOULDBLOCK);
return -1;
}
return AsyncSocketAdapter::Send(pv, cb);
}
int BufferedReadAdapter::Recv(void* pv, size_t cb, int64_t* timestamp) {
if (buffering_) {
socket_->SetError(EWOULDBLOCK);
return -1;
}
size_t read = 0;
if (data_len_) {
read = std::min(cb, data_len_);
memcpy(pv, buffer_, read);
data_len_ -= read;
if (data_len_ > 0) {
memmove(buffer_, buffer_ + read, data_len_);
}
pv = static_cast<char*>(pv) + read;
cb -= read;
}
// FIX: If cb == 0, we won't generate another read event
int res = AsyncSocketAdapter::Recv(pv, cb, timestamp);
if (res >= 0) {
// Read from socket and possibly buffer; return combined length
return res + static_cast<int>(read);
}
if (read > 0) {
// Failed to read from socket, but still read something from buffer
return static_cast<int>(read);
}
// Didn't read anything; return error from socket
return res;
}
void BufferedReadAdapter::BufferInput(bool on) {
buffering_ = on;
}
void BufferedReadAdapter::OnReadEvent(AsyncSocket* socket) {
RTC_DCHECK(socket == socket_);
if (!buffering_) {
AsyncSocketAdapter::OnReadEvent(socket);
return;
}
if (data_len_ >= buffer_size_) {
RTC_LOG(LS_ERROR) << "Input buffer overflow";
RTC_NOTREACHED();
data_len_ = 0;
}
int len =
socket_->Recv(buffer_ + data_len_, buffer_size_ - data_len_, nullptr);
if (len < 0) {
// TODO: Do something better like forwarding the error to the user.
RTC_LOG_ERR(INFO) << "Recv";
return;
}
data_len_ += len;
ProcessInput(buffer_, &data_len_);
}
///////////////////////////////////////////////////////////////////////////////
// This is a SSL v2 CLIENT_HELLO message.
// TODO: Should this have a session id? The response doesn't have a
// certificate, so the hello should have a session id.
static const uint8_t kSslClientHello[] = {
0x80, 0x46, // msg len
0x01, // CLIENT_HELLO
0x03, 0x01, // SSL 3.1
0x00, 0x2d, // ciphersuite len
0x00, 0x00, // session id len
0x00, 0x10, // challenge len
0x01, 0x00, 0x80, 0x03, 0x00, 0x80, 0x07, 0x00, 0xc0, // ciphersuites
0x06, 0x00, 0x40, 0x02, 0x00, 0x80, 0x04, 0x00, 0x80, //
0x00, 0x00, 0x04, 0x00, 0xfe, 0xff, 0x00, 0x00, 0x0a, //
0x00, 0xfe, 0xfe, 0x00, 0x00, 0x09, 0x00, 0x00, 0x64, //
0x00, 0x00, 0x62, 0x00, 0x00, 0x03, 0x00, 0x00, 0x06, //
0x1f, 0x17, 0x0c, 0xa6, 0x2f, 0x00, 0x78, 0xfc, // challenge
0x46, 0x55, 0x2e, 0xb1, 0x83, 0x39, 0xf1, 0xea //
};
// static
ArrayView<const uint8_t> AsyncSSLSocket::SslClientHello() {
// Implicit conversion directly from kSslClientHello to ArrayView fails when
// built with gcc.
return {kSslClientHello, sizeof(kSslClientHello)};
}
// This is a TLSv1 SERVER_HELLO message.
static const uint8_t kSslServerHello[] = {
0x16, // handshake message
0x03, 0x01, // SSL 3.1
0x00, 0x4a, // message len
0x02, // SERVER_HELLO
0x00, 0x00, 0x46, // handshake len
0x03, 0x01, // SSL 3.1
0x42, 0x85, 0x45, 0xa7, 0x27, 0xa9, 0x5d, 0xa0, // server random
0xb3, 0xc5, 0xe7, 0x53, 0xda, 0x48, 0x2b, 0x3f, //
0xc6, 0x5a, 0xca, 0x89, 0xc1, 0x58, 0x52, 0xa1, //
0x78, 0x3c, 0x5b, 0x17, 0x46, 0x00, 0x85, 0x3f, //
0x20, // session id len
0x0e, 0xd3, 0x06, 0x72, 0x5b, 0x5b, 0x1b, 0x5f, // session id
0x15, 0xac, 0x13, 0xf9, 0x88, 0x53, 0x9d, 0x9b, //
0xe8, 0x3d, 0x7b, 0x0c, 0x30, 0x32, 0x6e, 0x38, //
0x4d, 0xa2, 0x75, 0x57, 0x41, 0x6c, 0x34, 0x5c, //
0x00, 0x04, // RSA/RC4-128/MD5
0x00 // null compression
};
// static
ArrayView<const uint8_t> AsyncSSLSocket::SslServerHello() {
return {kSslServerHello, sizeof(kSslServerHello)};
}
AsyncSSLSocket::AsyncSSLSocket(AsyncSocket* socket)
: BufferedReadAdapter(socket, 1024) {}
int AsyncSSLSocket::Connect(const SocketAddress& addr) {
// Begin buffering before we connect, so that there isn't a race condition
// between potential senders and receiving the OnConnectEvent signal
BufferInput(true);
return BufferedReadAdapter::Connect(addr);
}
void AsyncSSLSocket::OnConnectEvent(AsyncSocket* socket) {
RTC_DCHECK(socket == socket_);
// TODO: we could buffer output too...
const int res = DirectSend(kSslClientHello, sizeof(kSslClientHello));
RTC_DCHECK_EQ(sizeof(kSslClientHello), res);
}
void AsyncSSLSocket::ProcessInput(char* data, size_t* len) {
if (*len < sizeof(kSslServerHello))
return;
if (memcmp(kSslServerHello, data, sizeof(kSslServerHello)) != 0) {
Close();
SignalCloseEvent(this, 0); // TODO: error code?
return;
}
*len -= sizeof(kSslServerHello);
if (*len > 0) {
memmove(data, data + sizeof(kSslServerHello), *len);
}
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);
}
///////////////////////////////////////////////////////////////////////////////
AsyncHttpsProxySocket::AsyncHttpsProxySocket(AsyncSocket* socket,
const std::string& user_agent,
const SocketAddress& proxy,
const std::string& 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(AsyncSocket* socket) {
RTC_LOG(LS_VERBOSE) << "AsyncHttpsProxySocket::OnConnectEvent";
if (!ShouldIssueConnect()) {
state_ = PS_TUNNEL;
BufferedReadAdapter::OnConnectEvent(socket);
return;
}
SendRequest();
}
void AsyncHttpsProxySocket::OnCloseEvent(AsyncSocket* 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 len = pos - start - 1;
if ((len > 0) && (data[start + len - 1] == '\r'))
--len;
data[start + len] = 0;
ProcessLine(data + start, len);
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(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);
}
///////////////////////////////////////////////////////////////////////////////
AsyncSocksProxySocket::AsyncSocksProxySocket(AsyncSocket* socket,
const SocketAddress& proxy,
const std::string& username,
const CryptString& password)
: BufferedReadAdapter(socket, 1024),
state_(SS_ERROR),
proxy_(proxy),
user_(username),
pass_(password) {}
AsyncSocksProxySocket::~AsyncSocksProxySocket() = default;
int AsyncSocksProxySocket::Connect(const SocketAddress& addr) {
int ret;
dest_ = addr;
state_ = SS_INIT;
BufferInput(true);
ret = BufferedReadAdapter::Connect(proxy_);
// TODO: Set state_ appropriately if Connect fails.
return ret;
}
SocketAddress AsyncSocksProxySocket::GetRemoteAddress() const {
return dest_;
}
int AsyncSocksProxySocket::Close() {
state_ = SS_ERROR;
dest_.Clear();
return BufferedReadAdapter::Close();
}
Socket::ConnState AsyncSocksProxySocket::GetState() const {
if (state_ < SS_TUNNEL) {
return CS_CONNECTING;
} else if (state_ == SS_TUNNEL) {
return CS_CONNECTED;
} else {
return CS_CLOSED;
}
}
void AsyncSocksProxySocket::OnConnectEvent(AsyncSocket* socket) {
SendHello();
}
void AsyncSocksProxySocket::ProcessInput(char* data, size_t* len) {
RTC_DCHECK(state_ < SS_TUNNEL);
ByteBufferReader response(data, *len);
if (state_ == SS_HELLO) {
uint8_t ver, method;
if (!response.ReadUInt8(&ver) || !response.ReadUInt8(&method))
return;
if (ver != 5) {
Error(0);
return;
}
if (method == 0) {
SendConnect();
} else if (method == 2) {
SendAuth();
} else {
Error(0);
return;
}
} else if (state_ == SS_AUTH) {
uint8_t ver, status;
if (!response.ReadUInt8(&ver) || !response.ReadUInt8(&status))
return;
if ((ver != 1) || (status != 0)) {
Error(SOCKET_EACCES);
return;
}
SendConnect();
} else if (state_ == SS_CONNECT) {
uint8_t ver, rep, rsv, atyp;
if (!response.ReadUInt8(&ver) || !response.ReadUInt8(&rep) ||
!response.ReadUInt8(&rsv) || !response.ReadUInt8(&atyp))
return;
if ((ver != 5) || (rep != 0)) {
Error(0);
return;
}
uint16_t port;
if (atyp == 1) {
uint32_t addr;
if (!response.ReadUInt32(&addr) || !response.ReadUInt16(&port))
return;
RTC_LOG(LS_VERBOSE) << "Bound on " << addr << ":" << port;
} else if (atyp == 3) {
uint8_t len;
std::string addr;
if (!response.ReadUInt8(&len) || !response.ReadString(&addr, len) ||
!response.ReadUInt16(&port))
return;
RTC_LOG(LS_VERBOSE) << "Bound on " << addr << ":" << port;
} else if (atyp == 4) {
std::string addr;
if (!response.ReadString(&addr, 16) || !response.ReadUInt16(&port))
return;
RTC_LOG(LS_VERBOSE) << "Bound on <IPV6>:" << port;
} else {
Error(0);
return;
}
state_ = SS_TUNNEL;
}
// Consume parsed data
*len = response.Length();
memmove(data, response.Data(), *len);
if (state_ != SS_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??
}
void AsyncSocksProxySocket::SendHello() {
ByteBufferWriter request;
request.WriteUInt8(5); // Socks Version
if (user_.empty()) {
request.WriteUInt8(1); // Authentication Mechanisms
request.WriteUInt8(0); // No authentication
} else {
request.WriteUInt8(2); // Authentication Mechanisms
request.WriteUInt8(0); // No authentication
request.WriteUInt8(2); // Username/Password
}
DirectSend(request.Data(), request.Length());
state_ = SS_HELLO;
}
void AsyncSocksProxySocket::SendAuth() {
ByteBufferWriterT<ZeroOnFreeBuffer<char>> request;
request.WriteUInt8(1); // Negotiation Version
request.WriteUInt8(static_cast<uint8_t>(user_.size()));
request.WriteString(user_); // Username
request.WriteUInt8(static_cast<uint8_t>(pass_.GetLength()));
size_t len = pass_.GetLength() + 1;
char* sensitive = new char[len];
pass_.CopyTo(sensitive, true);
request.WriteBytes(sensitive, pass_.GetLength()); // Password
ExplicitZeroMemory(sensitive, len);
delete[] sensitive;
DirectSend(request.Data(), request.Length());
state_ = SS_AUTH;
}
void AsyncSocksProxySocket::SendConnect() {
ByteBufferWriter request;
request.WriteUInt8(5); // Socks Version
request.WriteUInt8(1); // CONNECT
request.WriteUInt8(0); // Reserved
if (dest_.IsUnresolvedIP()) {
std::string hostname = dest_.hostname();
request.WriteUInt8(3); // DOMAINNAME
request.WriteUInt8(static_cast<uint8_t>(hostname.size()));
request.WriteString(hostname); // Destination Hostname
} else {
request.WriteUInt8(1); // IPV4
request.WriteUInt32(dest_.ip()); // Destination IP
}
request.WriteUInt16(dest_.port()); // Destination Port
DirectSend(request.Data(), request.Length());
state_ = SS_CONNECT;
}
void AsyncSocksProxySocket::Error(int error) {
state_ = SS_ERROR;
BufferInput(false);
Close();
SetError(SOCKET_EACCES);
SignalCloseEvent(this, error);
}
} // namespace rtc