diff --git a/doc/zmq_setsockopt.txt b/doc/zmq_setsockopt.txt index 1680a71a..2b1e6707 100644 --- a/doc/zmq_setsockopt.txt +++ b/doc/zmq_setsockopt.txt @@ -891,10 +891,42 @@ Applicable socket types:: all ZMQ_SOCKS_PROXY: Set SOCKS5 proxy address ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 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 -domain names instead of addresses they shall not be resolved and they shall -be forwarded unchanged to the SOCKS proxy service in the client connection -request message (address type 0x03 domain name). +connection(s). Supported authentication methods are: no authentication +or basic authentication when setup with ZMQ_SOCKS_USERNAME. If the endpoints +are domain names instead of addresses they shall not be resolved and they +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] Option value type:: character string diff --git a/include/zmq.h b/include/zmq.h index f26cf9ed..8c1b6854 100644 --- a/include/zmq.h +++ b/include/zmq.h @@ -656,6 +656,8 @@ ZMQ_EXPORT void zmq_threadclose (void *thread_); #define ZMQ_MULTICAST_LOOP 96 #define ZMQ_ROUTER_NOTIFY 97 #define ZMQ_XPUB_MANUAL_LAST_VALUE 98 +#define ZMQ_SOCKS_USERNAME 99 +#define ZMQ_SOCKS_PASSWORD 100 /* DRAFT Context options */ #define ZMQ_ZERO_COPY_RECV 10 diff --git a/src/options.cpp b/src/options.cpp index b6ae3ea2..d1ceed38 100644 --- a/src/options.cpp +++ b/src/options.cpp @@ -461,6 +461,24 @@ int zmq::options_t::setsockopt (int option_, return do_setsockopt_string_allow_empty_strict ( 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: if (is_int && (value == -1 || value == 0 || value == 1)) { tcp_keepalive = value; @@ -959,6 +977,14 @@ int zmq::options_t::getsockopt (int option_, return do_getsockopt (optval_, optvallen_, socks_proxy_address); 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: if (is_int) { *value = tcp_keepalive; diff --git a/src/options.hpp b/src/options.hpp index aeaa86da..e15d642d 100644 --- a/src/options.hpp +++ b/src/options.hpp @@ -159,6 +159,12 @@ struct options_t // Address of SOCKS proxy 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. // Defaults to -1 = do not change socket options int tcp_keepalive; diff --git a/src/session_base.cpp b/src/session_base.cpp index 5fda4d8c..fa7f3b11 100644 --- a/src/session_base.cpp +++ b/src/session_base.cpp @@ -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 ( protocol_name::tcp, options.socks_proxy_address, this->get_ctx ()); 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_); + 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) tcp_connecter_t (io_thread_, this, options, _addr, wait_); diff --git a/src/socks.cpp b/src/socks.cpp index 5f449395..52091572 100644 --- a/src/socks.cpp +++ b/src/socks.cpp @@ -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 (req_.username.size ()); + memcpy (ptr, req_.username.c_str (), req_.username.size ()); + ptr += req_.username.size (); + *ptr++ = static_cast (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 (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 (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_, std::string hostname_, uint16_t port_) : diff --git a/src/socks.hpp b/src/socks.hpp index de76786e..9c86abda 100644 --- a/src/socks.hpp +++ b/src/socks.hpp @@ -81,6 +81,50 @@ class socks_choice_decoder_t 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 { socks_request_t (uint8_t command_, std::string hostname_, uint16_t port_); diff --git a/src/socks_connecter.cpp b/src/socks_connecter.cpp index 43987a6e..377c819f 100644 --- a/src/socks_connecter.cpp +++ b/src/socks_connecter.cpp @@ -61,6 +61,7 @@ zmq::socks_connecter_t::socks_connecter_t (class io_thread_t *io_thread_, stream_connecter_base_t ( io_thread_, session_, options_, addr_, delayed_start_), _proxy_addr (proxy_addr_), + _auth_method(socks_no_auth_required), _status (unplugged) { zmq_assert (_addr->protocol == protocol_name::tcp); @@ -72,8 +73,24 @@ zmq::socks_connecter_t::~socks_connecter_t () 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 () { + int expected_status = -1; zmq_assert (_status != unplugged); if (_status == waiting_for_choice) { @@ -86,17 +103,21 @@ void zmq::socks_connecter_t::in_event () if (rc == -1) error (); else { - 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; - } + if (choice.method == socks_basic_auth) expected_status = sending_basic_auth_request; + else expected_status = sending_request; + } + } + } else if (_status == waiting_for_auth_response) { + int rc = _auth_response_decoder.input (_s); + if (rc == 0 || rc == -1) + error (); + else if (_auth_response_decoder.message_ready ()) { + 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) { @@ -118,12 +139,33 @@ void zmq::socks_connecter_t::in_event () } } else 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 () { 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) { const int rc = static_cast (check_proxy_connection ()); @@ -131,7 +173,7 @@ void zmq::socks_connecter_t::out_event () error (); else { _greeting_encoder.encode ( - socks_greeting_t (socks_no_auth_required)); + socks_greeting_t (_auth_method)); _status = sending_greeting; } } else if (_status == sending_greeting) { @@ -144,6 +186,16 @@ void zmq::socks_connecter_t::out_event () set_pollin (_handle); _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 { zmq_assert (_request_encoder.has_pending_data ()); 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 ( const socks_choice_t &response_) { - // We do not support any authentication method for now. - return response_.method == 0 ? 0 : -1; + return response_.method == socks_no_auth_required || + response_.method == socks_basic_auth ? + 0 : -1; } 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; } +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 () { rm_fd (_handle); close (); _greeting_encoder.reset (); _choice_decoder.reset (); + _basic_auth_request_encoder.reset (); + _auth_response_decoder.reset (); _request_encoder.reset (); _response_decoder.reset (); _status = unplugged; diff --git a/src/socks_connecter.hpp b/src/socks_connecter.hpp index f344ba38..e563bffd 100644 --- a/src/socks_connecter.hpp +++ b/src/socks_connecter.hpp @@ -54,6 +54,11 @@ class socks_connecter_t : public stream_connecter_base_t bool delayed_start_); ~socks_connecter_t (); + void set_auth_method_basic (const std::string username, + const std::string password); + void set_auth_method_none (); + + private: enum { @@ -62,6 +67,8 @@ class socks_connecter_t : public stream_connecter_base_t waiting_for_proxy_connection, sending_greeting, waiting_for_choice, + sending_basic_auth_request, + waiting_for_auth_response, sending_request, waiting_for_response }; @@ -69,7 +76,9 @@ class socks_connecter_t : public stream_connecter_base_t // Method ID 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. @@ -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_response_t &response_); + int process_server_response (const socks_auth_response_t &response_); int parse_address (const std::string &address_, std::string &hostname_, @@ -101,12 +111,21 @@ class socks_connecter_t : public stream_connecter_base_t socks_greeting_encoder_t _greeting_encoder; 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_response_decoder_t _response_decoder; // SOCKS address; owned by this connecter. 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; socks_connecter_t (const socks_connecter_t &); diff --git a/src/zmq_draft.h b/src/zmq_draft.h index c37ba319..6faa5029 100644 --- a/src/zmq_draft.h +++ b/src/zmq_draft.h @@ -53,6 +53,8 @@ #define ZMQ_MULTICAST_LOOP 96 #define ZMQ_ROUTER_NOTIFY 97 #define ZMQ_XPUB_MANUAL_LAST_VALUE 98 +#define ZMQ_SOCKS_USERNAME 99 +#define ZMQ_SOCKS_PASSWORD 100 /* DRAFT Context options */ #define ZMQ_ZERO_COPY_RECV 10