From d10e1407bff7725c4d781c799a71c3f19c7bc26e Mon Sep 17 00:00:00 2001 From: klzgrad Date: Mon, 19 Apr 2021 22:34:08 +0800 Subject: [PATCH] Support SOCKS proxy authentication --- src/net/tools/naive/naive_proxy.cc | 5 + src/net/tools/naive/naive_proxy.h | 4 + src/net/tools/naive/naive_proxy_bin.cc | 13 +- src/net/tools/naive/socks5_server_socket.cc | 179 ++++++++++++++++---- src/net/tools/naive/socks5_server_socket.h | 21 ++- 5 files changed, 185 insertions(+), 37 deletions(-) diff --git a/src/net/tools/naive/naive_proxy.cc b/src/net/tools/naive/naive_proxy.cc index b4d5feee68..2971996f9e 100644 --- a/src/net/tools/naive/naive_proxy.cc +++ b/src/net/tools/naive/naive_proxy.cc @@ -29,12 +29,16 @@ namespace net { NaiveProxy::NaiveProxy(std::unique_ptr listen_socket, ClientProtocol protocol, + const std::string& listen_user, + const std::string& listen_pass, int concurrency, RedirectResolver* resolver, HttpNetworkSession* session, const NetworkTrafficAnnotationTag& traffic_annotation) : listen_socket_(std::move(listen_socket)), protocol_(protocol), + listen_user_(listen_user), + listen_pass_(listen_pass), concurrency_(std::min(4, std::max(1, concurrency))), resolver_(resolver), session_(session), @@ -108,6 +112,7 @@ void NaiveProxy::DoConnect() { if (protocol_ == ClientProtocol::kSocks5) { socket = std::make_unique(std::move(accepted_socket_), + listen_user_, listen_pass_, traffic_annotation_); } else if (protocol_ == ClientProtocol::kHttp) { socket = std::make_unique(std::move(accepted_socket_), diff --git a/src/net/tools/naive/naive_proxy.h b/src/net/tools/naive/naive_proxy.h index 640720bcf6..9aaea531c7 100644 --- a/src/net/tools/naive/naive_proxy.h +++ b/src/net/tools/naive/naive_proxy.h @@ -34,6 +34,8 @@ class NaiveProxy { public: NaiveProxy(std::unique_ptr server_socket, ClientProtocol protocol, + const std::string& listen_user, + const std::string& listen_pass, int concurrency, RedirectResolver* resolver, HttpNetworkSession* session, @@ -59,6 +61,8 @@ class NaiveProxy { std::unique_ptr listen_socket_; ClientProtocol protocol_; + std::string listen_user_; + std::string listen_pass_; int concurrency_; ProxyInfo proxy_info_; SSLConfig server_ssl_config_; diff --git a/src/net/tools/naive/naive_proxy_bin.cc b/src/net/tools/naive/naive_proxy_bin.cc index 63fedaf10f..11569c5d90 100644 --- a/src/net/tools/naive/naive_proxy_bin.cc +++ b/src/net/tools/naive/naive_proxy_bin.cc @@ -19,6 +19,7 @@ #include "base/macros.h" #include "base/rand_util.h" #include "base/run_loop.h" +#include "base/strings/escape.h" #include "base/strings/string16.h" #include "base/strings/string_number_conversions.h" #include "base/strings/stringprintf.h" @@ -96,6 +97,8 @@ struct CommandLine { struct Params { net::ClientProtocol protocol; + std::string listen_user; + std::string listen_pass; std::string listen_addr; int listen_port; int concurrency; @@ -234,7 +237,8 @@ bool ParseCommandLine(const CommandLine& cmdline, Params* params) { params->protocol = net::ClientProtocol::kSocks5; params->listen_addr = "0.0.0.0"; params->listen_port = 1080; - url::AddStandardScheme("socks", url::SCHEME_WITH_HOST_AND_PORT); + url::AddStandardScheme("socks", + url::SCHEME_WITH_HOST_PORT_AND_USER_INFORMATION); url::AddStandardScheme("redir", url::SCHEME_WITH_HOST_AND_PORT); if (!cmdline.listen.empty()) { GURL url(cmdline.listen); @@ -256,6 +260,12 @@ bool ParseCommandLine(const CommandLine& cmdline, Params* params) { std::cerr << "Invalid scheme in --listen" << std::endl; return false; } + if (!url.username().empty()) { + params->listen_user = base::UnescapeBinaryURLComponent(url.username()); + } + if (!url.password().empty()) { + params->listen_pass = base::UnescapeBinaryURLComponent(url.password()); + } if (!url.host().empty()) { params->listen_addr = url.host(); } @@ -575,6 +585,7 @@ int main(int argc, char* argv[]) { } net::NaiveProxy naive_proxy(std::move(listen_socket), params.protocol, + params.listen_user, params.listen_pass, params.concurrency, resolver.get(), session, kTrafficAnnotation); diff --git a/src/net/tools/naive/socks5_server_socket.cc b/src/net/tools/naive/socks5_server_socket.cc index d7d691b0ca..0866cbe4de 100644 --- a/src/net/tools/naive/socks5_server_socket.cc +++ b/src/net/tools/naive/socks5_server_socket.cc @@ -27,11 +27,16 @@ enum SocksCommandType { }; static constexpr unsigned int kGreetReadHeaderSize = 2; +static constexpr unsigned int kAuthReadHeaderSize = 2; static constexpr unsigned int kReadHeaderSize = 5; static constexpr char kSOCKS5Version = '\x05'; static constexpr char kSOCKS5Reserved = '\x00'; static constexpr char kAuthMethodNone = '\x00'; +static constexpr char kAuthMethodUserPass = '\x02'; static constexpr char kAuthMethodNoAcceptable = '\xff'; +static constexpr char kSubnegotiationVersion = '\x01'; +static constexpr char kAuthStatusSuccess = '\x00'; +static constexpr char kAuthStatusFailure = '\xff'; static constexpr char kReplySuccess = '\x00'; static constexpr char kReplyCommandNotSupported = '\x07'; @@ -40,17 +45,18 @@ static_assert(sizeof(struct in6_addr) == 16, "incorrect system size of IPv6"); Socks5ServerSocket::Socks5ServerSocket( std::unique_ptr transport_socket, + const std::string& user, + const std::string& pass, const NetworkTrafficAnnotationTag& traffic_annotation) : io_callback_(base::BindRepeating(&Socks5ServerSocket::OnIOComplete, base::Unretained(this))), transport_(std::move(transport_socket)), next_state_(STATE_NONE), completed_handshake_(false), - bytes_received_(0), bytes_sent_(0), - greet_read_header_size_(kGreetReadHeaderSize), - read_header_size_(kReadHeaderSize), was_ever_used_(false), + user_(user), + pass_(pass), net_log_(transport_->NetLog()), traffic_annotation_(traffic_annotation) {} @@ -251,6 +257,20 @@ int Socks5ServerSocket::DoLoop(int last_io_result) { net_log_.EndEventWithNetErrorCode(NetLogEventType::SOCKS5_GREET_WRITE, rv); break; + case STATE_AUTH_READ: + DCHECK_EQ(OK, rv); + rv = DoAuthRead(); + break; + case STATE_AUTH_READ_COMPLETE: + rv = DoAuthReadComplete(rv); + break; + case STATE_AUTH_WRITE: + DCHECK_EQ(OK, rv); + rv = DoAuthWrite(); + break; + case STATE_AUTH_WRITE_COMPLETE: + rv = DoAuthWriteComplete(rv); + break; case STATE_HANDSHAKE_READ: DCHECK_EQ(OK, rv); net_log_.BeginEvent(NetLogEventType::SOCKS5_HANDSHAKE_READ); @@ -284,11 +304,10 @@ int Socks5ServerSocket::DoGreetRead() { next_state_ = STATE_GREET_READ_COMPLETE; if (buffer_.empty()) { - DCHECK_EQ(0U, bytes_received_); - DCHECK_EQ(kGreetReadHeaderSize, greet_read_header_size_); + read_header_size_ = kGreetReadHeaderSize; } - int handshake_buf_len = greet_read_header_size_ - bytes_received_; + int handshake_buf_len = read_header_size_ - buffer_.size(); DCHECK_LT(0, handshake_buf_len); handshake_buf_ = base::MakeRefCounted(handshake_buf_len); return transport_->Read(handshake_buf_.get(), handshake_buf_len, @@ -305,32 +324,37 @@ int Socks5ServerSocket::DoGreetReadComplete(int result) { return ERR_SOCKS_CONNECTION_FAILED; } - bytes_received_ += result; buffer_.append(handshake_buf_->data(), result); // When the first few bytes are read, check how many more are required // and accordingly increase them - if (bytes_received_ == kGreetReadHeaderSize) { + if (buffer_.size() == kGreetReadHeaderSize) { if (buffer_[0] != kSOCKS5Version) { net_log_.AddEventWithIntParams(NetLogEventType::SOCKS_UNEXPECTED_VERSION, "version", buffer_[0]); return ERR_SOCKS_CONNECTION_FAILED; } - if (buffer_[1] == 0) { + int nmethods = buffer_[1]; + if (nmethods == 0) { net_log_.AddEvent(NetLogEventType::SOCKS_NO_REQUESTED_AUTH); return ERR_SOCKS_CONNECTION_FAILED; } - greet_read_header_size_ += buffer_[1]; + read_header_size_ += nmethods; next_state_ = STATE_GREET_READ; return OK; } - if (bytes_received_ == greet_read_header_size_) { - void* match = std::memchr(&buffer_[kGreetReadHeaderSize], kAuthMethodNone, - greet_read_header_size_ - kGreetReadHeaderSize); + if (buffer_.size() == read_header_size_) { + int nmethods = buffer_[1]; + char expected_method = kAuthMethodNone; + if (!user_.empty() || !pass_.empty()) { + expected_method = kAuthMethodUserPass; + } + void* match = + std::memchr(&buffer_[kGreetReadHeaderSize], expected_method, nmethods); if (match) { - auth_method_ = kAuthMethodNone; + auth_method_ = expected_method; } else { auth_method_ = kAuthMethodNoAcceptable; } @@ -367,9 +391,10 @@ int Socks5ServerSocket::DoGreetWriteComplete(int result) { bytes_sent_ += result; if (bytes_sent_ == buffer_.size()) { buffer_.clear(); - bytes_received_ = 0; - if (auth_method_ != kAuthMethodNoAcceptable) { + if (auth_method_ == kAuthMethodNone) { next_state_ = STATE_HANDSHAKE_READ; + } else if (auth_method_ == kAuthMethodUserPass) { + next_state_ = STATE_AUTH_READ; } else { net_log_.AddEvent(NetLogEventType::SOCKS_NO_ACCEPTABLE_AUTH); return ERR_SOCKS_CONNECTION_FAILED; @@ -380,15 +405,112 @@ int Socks5ServerSocket::DoGreetWriteComplete(int result) { return OK; } +int Socks5ServerSocket::DoAuthRead() { + next_state_ = STATE_AUTH_READ_COMPLETE; + + if (buffer_.empty()) { + read_header_size_ = kAuthReadHeaderSize; + } + + int handshake_buf_len = read_header_size_ - buffer_.size(); + DCHECK_LT(0, handshake_buf_len); + handshake_buf_ = base::MakeRefCounted(handshake_buf_len); + return transport_->Read(handshake_buf_.get(), handshake_buf_len, + io_callback_); +} + +int Socks5ServerSocket::DoAuthReadComplete(int result) { + if (result < 0) + return result; + + if (result == 0) { + return ERR_SOCKS_CONNECTION_FAILED; + } + + buffer_.append(handshake_buf_->data(), result); + + // When the first few bytes are read, check how many more are required + // and accordingly increase them + if (buffer_.size() == kAuthReadHeaderSize) { + if (buffer_[0] != kSubnegotiationVersion) { + net_log_.AddEventWithIntParams(NetLogEventType::SOCKS_UNEXPECTED_VERSION, + "version", buffer_[0]); + return ERR_SOCKS_CONNECTION_FAILED; + } + int username_len = buffer_[1]; + read_header_size_ += username_len + 1; + next_state_ = STATE_AUTH_READ; + return OK; + } + + if (buffer_.size() == read_header_size_) { + int username_len = buffer_[1]; + int password_len = buffer_[kAuthReadHeaderSize + username_len]; + size_t password_offset = kAuthReadHeaderSize + username_len + 1; + if (buffer_.size() == password_offset && password_len != 0) { + read_header_size_ += password_len; + next_state_ = STATE_AUTH_READ; + return OK; + } + + if (buffer_.compare(kAuthReadHeaderSize, username_len, user_) == 0 && + buffer_.compare(password_offset, password_len, pass_) == 0) { + auth_status_ = kAuthStatusSuccess; + } else { + auth_status_ = kAuthStatusFailure; + } + buffer_.clear(); + next_state_ = STATE_AUTH_WRITE; + return OK; + } + + next_state_ = STATE_AUTH_READ; + return OK; +} + +int Socks5ServerSocket::DoAuthWrite() { + if (buffer_.empty()) { + const char write_data[] = {kSubnegotiationVersion, auth_status_}; + buffer_ = std::string(write_data, base::size(write_data)); + bytes_sent_ = 0; + } + + next_state_ = STATE_AUTH_WRITE_COMPLETE; + int handshake_buf_len = buffer_.size() - bytes_sent_; + DCHECK_LT(0, handshake_buf_len); + handshake_buf_ = base::MakeRefCounted(handshake_buf_len); + std::memcpy(handshake_buf_->data(), &buffer_.data()[bytes_sent_], + handshake_buf_len); + return transport_->Write(handshake_buf_.get(), handshake_buf_len, + io_callback_, traffic_annotation_); +} + +int Socks5ServerSocket::DoAuthWriteComplete(int result) { + if (result < 0) + return result; + + bytes_sent_ += result; + if (bytes_sent_ == buffer_.size()) { + buffer_.clear(); + if (auth_status_ == kAuthStatusSuccess) { + next_state_ = STATE_HANDSHAKE_READ; + } else { + return ERR_SOCKS_CONNECTION_FAILED; + } + } else { + next_state_ = STATE_AUTH_WRITE; + } + return OK; +} + int Socks5ServerSocket::DoHandshakeRead() { next_state_ = STATE_HANDSHAKE_READ_COMPLETE; if (buffer_.empty()) { - DCHECK_EQ(0U, bytes_received_); - DCHECK_EQ(kReadHeaderSize, read_header_size_); + read_header_size_ = kReadHeaderSize; } - int handshake_buf_len = read_header_size_ - bytes_received_; + int handshake_buf_len = read_header_size_ - buffer_.size(); DCHECK_LT(0, handshake_buf_len); handshake_buf_ = base::MakeRefCounted(handshake_buf_len); return transport_->Read(handshake_buf_.get(), handshake_buf_len, @@ -407,11 +529,10 @@ int Socks5ServerSocket::DoHandshakeReadComplete(int result) { } buffer_.append(handshake_buf_->data(), result); - bytes_received_ += result; // When the first few bytes are read, check how many more are required // and accordingly increase them - if (bytes_received_ == kReadHeaderSize) { + if (buffer_.size() == kReadHeaderSize) { if (buffer_[0] != kSOCKS5Version || buffer_[2] != kSOCKS5Reserved) { net_log_.AddEventWithIntParams(NetLogEventType::SOCKS_UNEXPECTED_VERSION, "version", buffer_[0]); @@ -462,7 +583,7 @@ int Socks5ServerSocket::DoHandshakeReadComplete(int result) { } // When the final bytes are read, setup handshake. - if (bytes_received_ == read_header_size_) { + if (buffer_.size() == read_header_size_) { size_t port_start = read_header_size_ - sizeof(uint16_t); uint16_t port_net; std::memcpy(&port_net, &buffer_[port_start], sizeof(uint16_t)); @@ -494,16 +615,14 @@ int Socks5ServerSocket::DoHandshakeWrite() { if (buffer_.empty()) { const char write_data[] = { + // clang-format off kSOCKS5Version, reply_, kSOCKS5Reserved, kEndPointResolvedIPv4, - 0x00, - 0x00, - 0x00, - 0x00, // BND.ADDR - 0x00, - 0x00, // BND.PORT + 0x00, 0x00, 0x00, 0x00, // BND.ADDR + 0x00, 0x00, // BND.PORT + // clang-format on }; buffer_ = std::string(write_data, base::size(write_data)); bytes_sent_ = 0; @@ -535,10 +654,8 @@ int Socks5ServerSocket::DoHandshakeWriteComplete(int result) { "error_code", reply_); return ERR_SOCKS_CONNECTION_FAILED; } - } else if (bytes_sent_ < buffer_.size()) { - next_state_ = STATE_HANDSHAKE_WRITE; } else { - NOTREACHED(); + next_state_ = STATE_HANDSHAKE_WRITE; } return OK; diff --git a/src/net/tools/naive/socks5_server_socket.h b/src/net/tools/naive/socks5_server_socket.h index 988c233cf9..cb50e50199 100644 --- a/src/net/tools/naive/socks5_server_socket.h +++ b/src/net/tools/naive/socks5_server_socket.h @@ -32,6 +32,8 @@ struct NetworkTrafficAnnotationTag; class Socks5ServerSocket : public StreamSocket { public: Socks5ServerSocket(std::unique_ptr transport_socket, + const std::string& user, + const std::string& pass, const NetworkTrafficAnnotationTag& traffic_annotation); // On destruction Disconnect() is called. @@ -78,6 +80,10 @@ class Socks5ServerSocket : public StreamSocket { STATE_GREET_READ_COMPLETE, STATE_GREET_WRITE, STATE_GREET_WRITE_COMPLETE, + STATE_AUTH_READ, + STATE_AUTH_READ_COMPLETE, + STATE_AUTH_WRITE, + STATE_AUTH_WRITE_COMPLETE, STATE_HANDSHAKE_WRITE, STATE_HANDSHAKE_WRITE_COMPLETE, STATE_HANDSHAKE_READ, @@ -97,10 +103,14 @@ class Socks5ServerSocket : public StreamSocket { void OnReadWriteComplete(CompletionOnceCallback callback, int result); int DoLoop(int last_io_result); - int DoGreetWrite(); - int DoGreetWriteComplete(int result); int DoGreetRead(); int DoGreetReadComplete(int result); + int DoGreetWrite(); + int DoGreetWriteComplete(int result); + int DoAuthRead(); + int DoAuthReadComplete(int result); + int DoAuthWrite(); + int DoAuthWriteComplete(int result); int DoHandshakeRead(); int DoHandshakeReadComplete(int result); int DoHandshakeWrite(); @@ -129,11 +139,9 @@ class Socks5ServerSocket : public StreamSocket { // overlying connection is free to communicate. bool completed_handshake_; - // These contain the bytes received / sent by the SOCKS handshake. - size_t bytes_received_; + // Contains the bytes sent by the SOCKS handshake. size_t bytes_sent_; - size_t greet_read_header_size_; size_t read_header_size_; bool was_ever_used_; @@ -141,7 +149,10 @@ class Socks5ServerSocket : public StreamSocket { SocksEndPointAddressType address_type_; int address_size_; + std::string user_; + std::string pass_; char auth_method_; + char auth_status_; char reply_; HostPortPair request_endpoint_;