Add support for SOCKS5 basic authentication

This commit is contained in:
Christophe Guillon 2019-06-10 15:01:23 +02:00
parent 42cfa697f2
commit 58c30dc7d1
10 changed files with 309 additions and 21 deletions

View File

@ -891,10 +891,42 @@ Applicable socket types:: all
ZMQ_SOCKS_PROXY: Set SOCKS5 proxy address ZMQ_SOCKS_PROXY: Set SOCKS5 proxy address
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Sets the SOCKS5 proxy address that shall be used by the socket for the TCP Sets the SOCKS5 proxy address that shall be used by the socket for the TCP
connection(s). Does not support SOCKS5 authentication. If the endpoints are connection(s). Supported authentication methods are: no authentication
domain names instead of addresses they shall not be resolved and they shall or basic authentication when setup with ZMQ_SOCKS_USERNAME. If the endpoints
be forwarded unchanged to the SOCKS proxy service in the client connection are domain names instead of addresses they shall not be resolved and they
request message (address type 0x03 domain name). shall be forwarded unchanged to the SOCKS proxy service in the client
connection request message (address type 0x03 domain name).
[horizontal]
Option value type:: character string
Option value unit:: N/A
Default value:: not set
Applicable socket types:: all, when using TCP transport
ZMQ_SOCKS_USERNAME: Set SOCKS username and select basic authentication
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Sets the username for authenticated connection to the SOCKS5 proxy.
If you set this to a non-null and non-empty value, the authentication
method used for the SOCKS5 connection shall be basic authentication.
In this case, use ZMQ_SOCKS_PASSWORD option in order to set the password.
If you set this to a null value or empty value, the authentication method
shall be no authentication, the default.
[horizontal]
Option value type:: character string
Option value unit:: N/A
Default value:: not set
Applicable socket types:: all, when using TCP transport
ZMQ_SOCKS_PASSWORD: Set SOCKS basic authentication password
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Sets the password for authenticating to the SOCKS5 proxy server.
This is used only when the SOCKS5 authentication method has been
set to basic authentication through the ZMQ_SOCKS_USERNAME option.
Setting this to a null value (the default) is equivalent to an
empty password string.
[horizontal] [horizontal]
Option value type:: character string Option value type:: character string

View File

@ -656,6 +656,8 @@ ZMQ_EXPORT void zmq_threadclose (void *thread_);
#define ZMQ_MULTICAST_LOOP 96 #define ZMQ_MULTICAST_LOOP 96
#define ZMQ_ROUTER_NOTIFY 97 #define ZMQ_ROUTER_NOTIFY 97
#define ZMQ_XPUB_MANUAL_LAST_VALUE 98 #define ZMQ_XPUB_MANUAL_LAST_VALUE 98
#define ZMQ_SOCKS_USERNAME 99
#define ZMQ_SOCKS_PASSWORD 100
/* DRAFT Context options */ /* DRAFT Context options */
#define ZMQ_ZERO_COPY_RECV 10 #define ZMQ_ZERO_COPY_RECV 10

View File

@ -461,6 +461,24 @@ int zmq::options_t::setsockopt (int option_,
return do_setsockopt_string_allow_empty_strict ( return do_setsockopt_string_allow_empty_strict (
optval_, optvallen_, &socks_proxy_address, SIZE_MAX); optval_, optvallen_, &socks_proxy_address, SIZE_MAX);
case ZMQ_SOCKS_USERNAME:
/* Make empty string or NULL equivalent. */
if (optval_ == NULL || optvallen_ == 0) {
socks_proxy_username.clear();
return 0;
} else {
return do_setsockopt_string_allow_empty_strict (
optval_, optvallen_, &socks_proxy_username, 255);
}
case ZMQ_SOCKS_PASSWORD:
/* Make empty string or NULL equivalent. */
if (optval_ == NULL || optvallen_ == 0) {
socks_proxy_password.clear();
return 0;
} else {
return do_setsockopt_string_allow_empty_strict (
optval_, optvallen_, &socks_proxy_password, 255);
}
case ZMQ_TCP_KEEPALIVE: case ZMQ_TCP_KEEPALIVE:
if (is_int && (value == -1 || value == 0 || value == 1)) { if (is_int && (value == -1 || value == 0 || value == 1)) {
tcp_keepalive = value; tcp_keepalive = value;
@ -959,6 +977,14 @@ int zmq::options_t::getsockopt (int option_,
return do_getsockopt (optval_, optvallen_, socks_proxy_address); return do_getsockopt (optval_, optvallen_, socks_proxy_address);
break; break;
case ZMQ_SOCKS_USERNAME:
return do_getsockopt (optval_, optvallen_, socks_proxy_username);
break;
case ZMQ_SOCKS_PASSWORD:
return do_getsockopt (optval_, optvallen_, socks_proxy_password);
break;
case ZMQ_TCP_KEEPALIVE: case ZMQ_TCP_KEEPALIVE:
if (is_int) { if (is_int) {
*value = tcp_keepalive; *value = tcp_keepalive;

View File

@ -159,6 +159,12 @@ struct options_t
// Address of SOCKS proxy // Address of SOCKS proxy
std::string socks_proxy_address; std::string socks_proxy_address;
// Credentials for SOCKS proxy.
// Conneciton method will be basic auth if username
// is not empty, no auth otherwise.
std::string socks_proxy_username;
std::string socks_proxy_password;
// TCP keep-alive settings. // TCP keep-alive settings.
// Defaults to -1 = do not change socket options // Defaults to -1 = do not change socket options
int tcp_keepalive; int tcp_keepalive;

View File

@ -668,8 +668,14 @@ zmq::own_t *zmq::session_base_t::create_connecter_tcp (io_thread_t *io_thread_,
address_t *proxy_address = new (std::nothrow) address_t ( address_t *proxy_address = new (std::nothrow) address_t (
protocol_name::tcp, options.socks_proxy_address, this->get_ctx ()); protocol_name::tcp, options.socks_proxy_address, this->get_ctx ());
alloc_assert (proxy_address); alloc_assert (proxy_address);
return new (std::nothrow) socks_connecter_t ( socks_connecter_t *connecter = new (std::nothrow) socks_connecter_t (
io_thread_, this, options, _addr, proxy_address, wait_); io_thread_, this, options, _addr, proxy_address, wait_);
alloc_assert(connecter);
if (!options.socks_proxy_username.empty ()) {
connecter->set_auth_method_basic(options.socks_proxy_username,
options.socks_proxy_password);
}
return connecter;
} }
return new (std::nothrow) return new (std::nothrow)
tcp_connecter_t (io_thread_, this, options, _addr, wait_); tcp_connecter_t (io_thread_, this, options, _addr, wait_);

View File

@ -129,6 +129,96 @@ void zmq::socks_choice_decoder_t::reset ()
} }
zmq::socks_basic_auth_request_t::socks_basic_auth_request_t (std::string username_,
std::string password_) :
username (username_),
password (password_)
{
zmq_assert (username_.size () <= UINT8_MAX);
zmq_assert (password_.size () <= UINT8_MAX);
}
zmq::socks_basic_auth_request_encoder_t::socks_basic_auth_request_encoder_t () :
_bytes_encoded (0),
_bytes_written (0)
{
}
void zmq::socks_basic_auth_request_encoder_t::encode (const socks_basic_auth_request_t &req_)
{
unsigned char *ptr = _buf;
*ptr++ = 0x01;
*ptr++ = static_cast<unsigned char> (req_.username.size ());
memcpy (ptr, req_.username.c_str (), req_.username.size ());
ptr += req_.username.size ();
*ptr++ = static_cast<unsigned char> (req_.password.size ());
memcpy (ptr, req_.password.c_str (), req_.password.size ());
ptr += req_.password.size ();
_bytes_encoded = ptr - _buf;
_bytes_written = 0;
}
int zmq::socks_basic_auth_request_encoder_t::output (fd_t fd_)
{
const int rc =
tcp_write (fd_, _buf + _bytes_written, _bytes_encoded - _bytes_written);
if (rc > 0)
_bytes_written += static_cast<size_t> (rc);
return rc;
}
bool zmq::socks_basic_auth_request_encoder_t::has_pending_data () const
{
return _bytes_written < _bytes_encoded;
}
void zmq::socks_basic_auth_request_encoder_t::reset ()
{
_bytes_encoded = _bytes_written = 0;
}
zmq::socks_auth_response_t::socks_auth_response_t (uint8_t response_code_) :
response_code (response_code_)
{
}
zmq::socks_auth_response_decoder_t::socks_auth_response_decoder_t () : _bytes_read (0)
{
}
int zmq::socks_auth_response_decoder_t::input (fd_t fd_)
{
zmq_assert (_bytes_read < 2);
const int rc = tcp_read (fd_, _buf + _bytes_read, 2 - _bytes_read);
if (rc > 0) {
_bytes_read += static_cast<size_t> (rc);
if (_buf[0] != 0x01)
return -1;
}
return rc;
}
bool zmq::socks_auth_response_decoder_t::message_ready () const
{
return _bytes_read == 2;
}
zmq::socks_auth_response_t zmq::socks_auth_response_decoder_t::decode ()
{
zmq_assert (message_ready ());
return socks_auth_response_t (_buf[1]);
}
void zmq::socks_auth_response_decoder_t::reset ()
{
_bytes_read = 0;
}
zmq::socks_request_t::socks_request_t (uint8_t command_, zmq::socks_request_t::socks_request_t (uint8_t command_,
std::string hostname_, std::string hostname_,
uint16_t port_) : uint16_t port_) :

View File

@ -81,6 +81,50 @@ class socks_choice_decoder_t
size_t _bytes_read; size_t _bytes_read;
}; };
struct socks_basic_auth_request_t
{
socks_basic_auth_request_t (std::string username_, std::string password_);
const std::string username;
const std::string password;
};
class socks_basic_auth_request_encoder_t
{
public:
socks_basic_auth_request_encoder_t ();
void encode (const socks_basic_auth_request_t &req_);
int output (fd_t fd_);
bool has_pending_data () const;
void reset ();
private:
size_t _bytes_encoded;
size_t _bytes_written;
uint8_t _buf[1 + 1 + UINT8_MAX + 1 + UINT8_MAX];
};
struct socks_auth_response_t
{
socks_auth_response_t (uint8_t response_code_);
uint8_t response_code;
};
class socks_auth_response_decoder_t
{
public:
socks_auth_response_decoder_t ();
int input (fd_t fd_);
bool message_ready () const;
socks_auth_response_t decode ();
void reset ();
private:
int8_t _buf[2];
size_t _bytes_read;
};
struct socks_request_t struct socks_request_t
{ {
socks_request_t (uint8_t command_, std::string hostname_, uint16_t port_); socks_request_t (uint8_t command_, std::string hostname_, uint16_t port_);

View File

@ -61,6 +61,7 @@ zmq::socks_connecter_t::socks_connecter_t (class io_thread_t *io_thread_,
stream_connecter_base_t ( stream_connecter_base_t (
io_thread_, session_, options_, addr_, delayed_start_), io_thread_, session_, options_, addr_, delayed_start_),
_proxy_addr (proxy_addr_), _proxy_addr (proxy_addr_),
_auth_method(socks_no_auth_required),
_status (unplugged) _status (unplugged)
{ {
zmq_assert (_addr->protocol == protocol_name::tcp); zmq_assert (_addr->protocol == protocol_name::tcp);
@ -72,8 +73,24 @@ zmq::socks_connecter_t::~socks_connecter_t ()
LIBZMQ_DELETE (_proxy_addr); LIBZMQ_DELETE (_proxy_addr);
} }
void zmq::socks_connecter_t::set_auth_method_none ()
{
_auth_method = socks_no_auth_required;
_auth_username.clear();
_auth_password.clear();
}
void zmq::socks_connecter_t::set_auth_method_basic (const std::string username,
const std::string password)
{
_auth_method = socks_basic_auth;
_auth_username = username;
_auth_password = password;
}
void zmq::socks_connecter_t::in_event () void zmq::socks_connecter_t::in_event ()
{ {
int expected_status = -1;
zmq_assert (_status != unplugged); zmq_assert (_status != unplugged);
if (_status == waiting_for_choice) { if (_status == waiting_for_choice) {
@ -86,17 +103,21 @@ void zmq::socks_connecter_t::in_event ()
if (rc == -1) if (rc == -1)
error (); error ();
else { else {
std::string hostname; if (choice.method == socks_basic_auth) expected_status = sending_basic_auth_request;
uint16_t port = 0; else expected_status = sending_request;
if (parse_address (_addr->address, hostname, port) == -1) }
error (); }
else { } else if (_status == waiting_for_auth_response) {
_request_encoder.encode ( int rc = _auth_response_decoder.input (_s);
socks_request_t (1, hostname, port)); if (rc == 0 || rc == -1)
reset_pollin (_handle); error ();
set_pollout (_handle); else if (_auth_response_decoder.message_ready ()) {
_status = sending_request; const socks_auth_response_t auth_response = _auth_response_decoder.decode ();
} rc = process_server_response (auth_response);
if (rc == -1)
error ();
else {
expected_status = sending_request;
} }
} }
} else if (_status == waiting_for_response) { } else if (_status == waiting_for_response) {
@ -118,12 +139,33 @@ void zmq::socks_connecter_t::in_event ()
} }
} else } else
error (); error ();
if (expected_status == sending_basic_auth_request) {
_basic_auth_request_encoder.encode (socks_basic_auth_request_t (_auth_username, _auth_password));
reset_pollin (_handle);
set_pollout (_handle);
_status = sending_basic_auth_request;
} else if (expected_status == sending_request) {
std::string hostname;
uint16_t port = 0;
if (parse_address (_addr->address, hostname, port) == -1)
error ();
else {
_request_encoder.encode (socks_request_t (1, hostname, port));
reset_pollin (_handle);
set_pollout (_handle);
_status = sending_request;
}
}
} }
void zmq::socks_connecter_t::out_event () void zmq::socks_connecter_t::out_event ()
{ {
zmq_assert (_status == waiting_for_proxy_connection zmq_assert (_status == waiting_for_proxy_connection
|| _status == sending_greeting || _status == sending_request); || _status == sending_greeting
|| _status == sending_basic_auth_request
|| _status == sending_request);
if (_status == waiting_for_proxy_connection) { if (_status == waiting_for_proxy_connection) {
const int rc = static_cast<int> (check_proxy_connection ()); const int rc = static_cast<int> (check_proxy_connection ());
@ -131,7 +173,7 @@ void zmq::socks_connecter_t::out_event ()
error (); error ();
else { else {
_greeting_encoder.encode ( _greeting_encoder.encode (
socks_greeting_t (socks_no_auth_required)); socks_greeting_t (_auth_method));
_status = sending_greeting; _status = sending_greeting;
} }
} else if (_status == sending_greeting) { } else if (_status == sending_greeting) {
@ -144,6 +186,16 @@ void zmq::socks_connecter_t::out_event ()
set_pollin (_handle); set_pollin (_handle);
_status = waiting_for_choice; _status = waiting_for_choice;
} }
} else if (_status == sending_basic_auth_request) {
zmq_assert (_basic_auth_request_encoder.has_pending_data ());
const int rc = _basic_auth_request_encoder.output (_s);
if (rc == -1 || rc == 0)
error ();
else if (!_basic_auth_request_encoder.has_pending_data ()) {
reset_pollout (_handle);
set_pollin (_handle);
_status = waiting_for_auth_response;
}
} else { } else {
zmq_assert (_request_encoder.has_pending_data ()); zmq_assert (_request_encoder.has_pending_data ());
const int rc = _request_encoder.output (_s); const int rc = _request_encoder.output (_s);
@ -189,8 +241,9 @@ void zmq::socks_connecter_t::start_connecting ()
int zmq::socks_connecter_t::process_server_response ( int zmq::socks_connecter_t::process_server_response (
const socks_choice_t &response_) const socks_choice_t &response_)
{ {
// We do not support any authentication method for now. return response_.method == socks_no_auth_required ||
return response_.method == 0 ? 0 : -1; response_.method == socks_basic_auth ?
0 : -1;
} }
int zmq::socks_connecter_t::process_server_response ( int zmq::socks_connecter_t::process_server_response (
@ -199,12 +252,20 @@ int zmq::socks_connecter_t::process_server_response (
return response_.response_code == 0 ? 0 : -1; return response_.response_code == 0 ? 0 : -1;
} }
int zmq::socks_connecter_t::process_server_response (
const socks_auth_response_t &response_)
{
return response_.response_code == 0 ? 0 : -1;
}
void zmq::socks_connecter_t::error () void zmq::socks_connecter_t::error ()
{ {
rm_fd (_handle); rm_fd (_handle);
close (); close ();
_greeting_encoder.reset (); _greeting_encoder.reset ();
_choice_decoder.reset (); _choice_decoder.reset ();
_basic_auth_request_encoder.reset ();
_auth_response_decoder.reset ();
_request_encoder.reset (); _request_encoder.reset ();
_response_decoder.reset (); _response_decoder.reset ();
_status = unplugged; _status = unplugged;

View File

@ -54,6 +54,11 @@ class socks_connecter_t : public stream_connecter_base_t
bool delayed_start_); bool delayed_start_);
~socks_connecter_t (); ~socks_connecter_t ();
void set_auth_method_basic (const std::string username,
const std::string password);
void set_auth_method_none ();
private: private:
enum enum
{ {
@ -62,6 +67,8 @@ class socks_connecter_t : public stream_connecter_base_t
waiting_for_proxy_connection, waiting_for_proxy_connection,
sending_greeting, sending_greeting,
waiting_for_choice, waiting_for_choice,
sending_basic_auth_request,
waiting_for_auth_response,
sending_request, sending_request,
waiting_for_response waiting_for_response
}; };
@ -69,7 +76,9 @@ class socks_connecter_t : public stream_connecter_base_t
// Method ID // Method ID
enum enum
{ {
socks_no_auth_required = 0 socks_no_auth_required = 0x00,
socks_basic_auth = 0x02,
socks_no_acceptable_method = 0xff
}; };
// Handlers for I/O events. // Handlers for I/O events.
@ -81,6 +90,7 @@ class socks_connecter_t : public stream_connecter_base_t
int process_server_response (const socks_choice_t &response_); int process_server_response (const socks_choice_t &response_);
int process_server_response (const socks_response_t &response_); int process_server_response (const socks_response_t &response_);
int process_server_response (const socks_auth_response_t &response_);
int parse_address (const std::string &address_, int parse_address (const std::string &address_,
std::string &hostname_, std::string &hostname_,
@ -101,12 +111,21 @@ class socks_connecter_t : public stream_connecter_base_t
socks_greeting_encoder_t _greeting_encoder; socks_greeting_encoder_t _greeting_encoder;
socks_choice_decoder_t _choice_decoder; socks_choice_decoder_t _choice_decoder;
socks_basic_auth_request_encoder_t _basic_auth_request_encoder;
socks_auth_response_decoder_t _auth_response_decoder;
socks_request_encoder_t _request_encoder; socks_request_encoder_t _request_encoder;
socks_response_decoder_t _response_decoder; socks_response_decoder_t _response_decoder;
// SOCKS address; owned by this connecter. // SOCKS address; owned by this connecter.
address_t *_proxy_addr; address_t *_proxy_addr;
// User defined authentication method
int _auth_method;
// Credentials for basic authentication
std::string _auth_username;
std::string _auth_password;
int _status; int _status;
socks_connecter_t (const socks_connecter_t &); socks_connecter_t (const socks_connecter_t &);

View File

@ -53,6 +53,8 @@
#define ZMQ_MULTICAST_LOOP 96 #define ZMQ_MULTICAST_LOOP 96
#define ZMQ_ROUTER_NOTIFY 97 #define ZMQ_ROUTER_NOTIFY 97
#define ZMQ_XPUB_MANUAL_LAST_VALUE 98 #define ZMQ_XPUB_MANUAL_LAST_VALUE 98
#define ZMQ_SOCKS_USERNAME 99
#define ZMQ_SOCKS_PASSWORD 100
/* DRAFT Context options */ /* DRAFT Context options */
#define ZMQ_ZERO_COPY_RECV 10 #define ZMQ_ZERO_COPY_RECV 10