From bf136b94de39de65638ce3daaf2e87731cd0a44a Mon Sep 17 00:00:00 2001 From: Robert Hogan Date: Tue, 3 Aug 2010 22:28:55 +0100 Subject: [PATCH] bug1666 - Pass-through support for SOCKS5 authentication If a SOCKS5 client insists on authentication, allow it to negotiate a connection with Tor's SOCKS server successfully. Any credentials the client provides are ignored. This allows Tor to work with SOCKS5 clients that can only support 'authenticated' connections. Also add a bunch of basic unit tests for SOCKS4/4a/5 support in buffers.c. --- doc/spec/socks-extensions.txt | 5 +- src/or/buffers.c | 64 ++++++--- src/or/config.c | 3 +- src/or/config.h | 2 + src/or/or.h | 2 + src/test/test.c | 237 ++++++++++++++++++++++++++++++++++ 6 files changed, 294 insertions(+), 19 deletions(-) diff --git a/doc/spec/socks-extensions.txt b/doc/spec/socks-extensions.txt index 62d86acd9f..52d6834552 100644 --- a/doc/spec/socks-extensions.txt +++ b/doc/spec/socks-extensions.txt @@ -31,8 +31,9 @@ Tor's extensions to the SOCKS protocol SOCKS5: - The (SOCKS5) "UDP ASSOCIATE" command is not supported. - IPv6 is not supported in CONNECT commands. - - Only the "NO AUTHENTICATION" (SOCKS5) authentication method [00] is - supported. + - The "NO AUTHENTICATION" (SOCKS5) authentication method [00] and the + "USERNAME/PASSWORD" (SOCKS5) authentication method [02] are both + supported. Any credentials passed in the latter are ignored. 2. Name lookup diff --git a/src/or/buffers.c b/src/or/buffers.c index 11c4fc8b9b..534f31c17a 100644 --- a/src/or/buffers.c +++ b/src/or/buffers.c @@ -1633,40 +1633,74 @@ parse_socks(const char *data, size_t datalen, socks_request_t *req, uint8_t socksver; enum {socks4, socks4a} socks4_prot = socks4a; char *next, *startaddr; + unsigned char usernamelen, passlen; struct in_addr in; socksver = *data; switch (socksver) { /* which version of socks? */ + case 1: /* socks5: username/password authentication request */ + + usernamelen = (unsigned char)*(buf->head->data + 1); + if (buf->datalen < 2u + usernamelen) + return 0; + buf_pullup(buf, 2u + usernamelen + 1, 0); + passlen = (unsigned char)*(buf->head->data + 2u + usernamelen); + if (buf->datalen < 2u + usernamelen + 1u + passlen) + return 0; + if (buf->datalen > 2u + usernamelen + 1u + passlen) { + log_warn(LD_APP, + "socks5: Malformed username/password. Rejecting."); + return -1; + } + req->replylen = 2; /* 2 bytes of response */ + req->reply[0] = 5; + req->reply[1] = 0; /* authentication successful */ + buf_clear(buf); + log_debug(LD_APP, + "socks5: Accepted username/password without checking."); + return 0; + case 5: /* socks5 */ if (req->socks_version != 5) { /* we need to negotiate a method */ unsigned char nummethods = (unsigned char)*(data+1); + int r=0; tor_assert(!req->socks_version); if (datalen < 2u+nummethods) { *want_length_out = 2u+nummethods; return 0; } - if (!nummethods || !memchr(data+2, 0, nummethods)) { - log_warn(LD_APP, - "socks5: offered methods don't include 'no auth'. " - "Rejecting."); - req->replylen = 2; /* 2 bytes of response */ - req->reply[0] = 5; - req->reply[1] = '\xFF'; /* reject all methods */ + buf_pullup(buf, 2u+nummethods, 0); + if (!nummethods) return -1; + req->replylen = 2; /* 2 bytes of response */ + req->reply[0] = 5; /* socks5 reply */ + if (memchr(buf->head->data+2, SOCKS_NO_AUTH, nummethods)) { + req->reply[1] = SOCKS_NO_AUTH; /* tell client to use "none" auth + method */ + req->socks_version = 5; /* remember we've already negotiated auth */ + log_debug(LD_APP,"socks5: accepted method 0 (no authentication)"); + r=0; + }else if (memchr(buf->head->data+2, SOCKS_USER_PASS,nummethods)) { + req->reply[1] = SOCKS_USER_PASS; /* tell client to use "user/pass" + auth method */ + req->socks_version = 5; /* remember we've already negotiated auth */ + log_debug(LD_APP,"socks5: accepted method 2 (username/password)"); + r=0; + } else { + log_warn(LD_APP, + "socks5: offered methods don't include 'no auth' or " + "username/password. Rejecting."); + req->reply[1] = '\xFF'; /* reject all methods */ + r=-1; } /* remove packet from buf. also remove any other extraneous * bytes, to support broken socks clients. */ *drain_out = -1; - req->replylen = 2; /* 2 bytes of response */ - req->reply[0] = 5; /* socks5 reply */ - req->reply[1] = 0; /* tell client to use "none" auth method */ - req->socks_version = 5; /* remember we've already negotiated auth */ - log_debug(LD_APP,"socks5: accepted method 0"); - return 0; + return r; } /* we know the method; read in the request */ log_debug(LD_APP,"socks5: checking request"); @@ -1761,8 +1795,8 @@ parse_socks(const char *data, size_t datalen, socks_request_t *req, } tor_assert(0); case 4: /* socks4 */ - /* http://archive.socks.permeo.com/protocol/socks4.protocol */ - /* http://archive.socks.permeo.com/protocol/socks4a.protocol */ + /* http://ss5.sourceforge.net/socks4.protocol.txt */ + /* http://ss5.sourceforge.net/socks4A.protocol.txt */ req->socks_version = 4; if (datalen < SOCKS4_NETWORK_LEN) {/* basic info available? */ diff --git a/src/or/config.c b/src/or/config.c index cd6b0b0c3c..d2dbdaa5dd 100644 --- a/src/or/config.c +++ b/src/or/config.c @@ -542,7 +542,6 @@ static int options_transition_affects_workers(or_options_t *old_options, static int options_transition_affects_descriptor(or_options_t *old_options, or_options_t *new_options); static int check_nickname_list(const char *lst, const char *name, char **msg); -static void config_register_addressmaps(or_options_t *options); static int parse_bridge_line(const char *line, int validate_only); static int parse_dir_server_line(const char *line, @@ -4314,7 +4313,7 @@ get_torrc_fname(void) /** Adjust the address map based on the MapAddress elements in the * configuration options */ -static void +void config_register_addressmaps(or_options_t *options) { smartlist_t *elts; diff --git a/src/or/config.h b/src/or/config.h index bd5827b4e8..db871d472a 100644 --- a/src/or/config.h +++ b/src/or/config.h @@ -76,5 +76,7 @@ uint32_t get_effective_bwburst(or_options_t *options); or_options_t *options_new(void); #endif +void config_register_addressmaps(or_options_t *options); + #endif diff --git a/src/or/or.h b/src/or/or.h index c5349aed55..6ae62e8899 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -3067,6 +3067,8 @@ static INLINE void or_state_mark_dirty(or_state_t *state, time_t when) #define MAX_SOCKS_REPLY_LEN 1024 #define MAX_SOCKS_ADDR_LEN 256 +#define SOCKS_NO_AUTH 0x00 +#define SOCKS_USER_PASS 0x02 /** Please open a TCP connection to this addr:port. */ #define SOCKS_COMMAND_CONNECT 0x01 diff --git a/src/test/test.c b/src/test/test.c index 9b0251b316..68f1aebf7c 100644 --- a/src/test/test.c +++ b/src/test/test.c @@ -197,6 +197,207 @@ free_pregenerated_keys(void) } } +/** Helper: Perform supported SOCKS 5 commands */ +static void +test_buffers_socks4_unsupported_commands_helper(const char *cp, buf_t *buf, + socks_request_t *socks) +{ + /* SOCKS 4 Send BIND [02] to IP address 2.2.2.2:4369 */ + cp = "\x04\x02\x11\x11\x02\x02\x02\x02\x00"; + write_to_buf(cp, 9, buf); + test_assert(fetch_from_buf_socks(buf, socks, get_options()->TestSocks, + get_options()->SafeSocks) == -1); + test_eq(4, socks->socks_version); + test_eq(0, socks->replylen); /* XXX: shouldn't tor reply? */ + +done: + ; +} + +/** Helper: Perform supported SOCKS 5 commands */ +static void +test_buffers_socks4_supported_commands_helper(const char *cp, buf_t *buf, + socks_request_t *socks) +{ + /* SOCKS 4 Send CONNECT [01] to IP address 2.2.2.2:4369 */ + cp = "\x04\x01\x11\x11\x02\x02\x02\x02\x00"; + write_to_buf(cp, 9, buf); + test_assert(fetch_from_buf_socks(buf, socks, get_options()->TestSocks, + get_options()->SafeSocks) == 1); + test_eq(4, socks->socks_version); + test_eq(0, socks->replylen); /* XXX: shouldn't tor reply? */ + test_streq("2.2.2.2", socks->address); + test_eq(4369, socks->port); + + /* SOCKS 4 Send CONNECT [01] to IP address 2.2.2.2:4369 with userid*/ + cp = "\x04\x01\x11\x11\x02\x02\x02\x02\x02me\x00"; + write_to_buf(cp, 12, buf); + test_assert(fetch_from_buf_socks(buf, socks, get_options()->TestSocks, + get_options()->SafeSocks) == 1); + test_eq(4, socks->socks_version); + test_eq(0, socks->replylen); /* XXX: shouldn't tor reply? */ + test_streq("2.2.2.2", socks->address); + test_eq(4369, socks->port); + + /* SOCKS 4a Send RESOLVE [F0] request for torproject.org:4369 */ + cp = "\x04\xF0\x01\x01\x00\x00\x00\x02\x02me\x00tor.org\x00"; + write_to_buf(cp, 20, buf); + test_assert(fetch_from_buf_socks(buf, socks, get_options()->TestSocks, + get_options()->SafeSocks)); + test_eq(4, socks->socks_version); + test_eq(0, socks->replylen); /* XXX: shouldn't tor reply? */ + test_streq("tor.org", socks->address); + +done: + ; +} + +/** Helper: Perform supported SOCKS 5 commands */ +static void +test_buffers_socks5_unsupported_commands_helper(const char *cp, buf_t *buf, + socks_request_t *socks) +{ + /* SOCKS 5 Send unsupported BIND [02] command */ + cp = "\x05\x02\x00\x01\x02\x02\x02\x02\x01\x01"; + write_to_buf(cp, 10, buf); + test_assert(fetch_from_buf_socks(buf, socks, get_options()->TestSocks, + get_options()->SafeSocks) == -1); + test_eq(5, socks->socks_version); + test_eq(2, socks->replylen); + test_eq(5, socks->reply[0]); + test_eq(0, socks->reply[1]); /* XXX: shouldn't tor reply 'command + not supported' [07]? */ + + /* SOCKS 5 Send unsupported UDP_ASSOCIATE [03] command */ + cp = "\x05\x03\x00\x01\x02\x02\x02\x02\x01\x01"; + write_to_buf(cp, 10, buf); + test_assert(fetch_from_buf_socks(buf, socks, get_options()->TestSocks, + get_options()->SafeSocks) == -1); + test_eq(5, socks->socks_version); + test_eq(2, socks->replylen); + test_eq(5, socks->reply[0]); + test_eq(0, socks->reply[1]); /* XXX: shouldn't tor reply 'command + not supported' [07]? */ + +done: + ; +} + +/** Helper: Perform supported SOCKS 5 commands */ +static void +test_buffers_socks5_supported_commands_helper(const char *cp, buf_t *buf, + socks_request_t *socks) +{ + /* SOCKS 5 Send CONNECT [01] to IP address 2.2.2.2:4369 */ + cp = "\x05\x01\x00\x01\x02\x02\x02\x02\x11\x11"; + write_to_buf(cp, 10, buf); + test_assert(fetch_from_buf_socks(buf, socks, get_options()->TestSocks, + get_options()->SafeSocks) == 1); + test_eq(5, socks->socks_version); + test_eq(2, socks->replylen); + test_eq(5, socks->reply[0]); + test_eq(0, socks->reply[1]); + test_streq("2.2.2.2", socks->address); + test_eq(4369, socks->port); + + /* SOCKS 5 Send CONNECT [01] to FQDN torproject.org:4369 */ + cp = "\x05\x01\x00\x03\x07tor.org\x11\x11"; + write_to_buf(cp, 14, buf); + test_assert(fetch_from_buf_socks(buf, socks, get_options()->TestSocks, + get_options()->SafeSocks)); + test_eq(5, socks->socks_version); + test_eq(2, socks->replylen); + test_eq(5, socks->reply[0]); + test_eq(0, socks->reply[1]); + test_streq("tor.org", socks->address); + test_eq(4369, socks->port); + + /* SOCKS 5 Send RESOLVE [F0] request for torproject.org:4369 */ + cp = "\x05\xF0\x00\x03\x07tor.org"; + write_to_buf(cp, 14, buf); + test_assert(fetch_from_buf_socks(buf, socks, get_options()->TestSocks, + get_options()->SafeSocks)); + test_eq(5, socks->socks_version); + test_eq(2, socks->replylen); + test_eq(5, socks->reply[0]); + test_eq(0, socks->reply[1]); + test_streq("tor.org", socks->address); + + /* SOCKS 5 Send RESOLVE_PTR [F1] for IP address 2.2.2.2 */ + cp = "\x05\xF1\x00\x01\x02\x02\x02\x02"; + write_to_buf(cp, 10, buf); + test_assert(fetch_from_buf_socks(buf, socks, get_options()->TestSocks, + get_options()->SafeSocks) == 1); + test_eq(5, socks->socks_version); + test_eq(2, socks->replylen); + test_eq(5, socks->reply[0]); + test_eq(0, socks->reply[1]); + test_streq("2.2.2.2", socks->address); + +done: + ; +} + +/** Helper: Perform SOCKS 5 authentication */ +static void +test_buffers_socks5_no_authenticate_helper(const char *cp, buf_t *buf, + socks_request_t *socks) +{ + /*SOCKS 5 No Authentication */ + cp = "\x05\x01\x00"; + write_to_buf(cp, 3, buf); + test_assert(!fetch_from_buf_socks(buf, socks, + get_options()->TestSocks, + get_options()->SafeSocks)); + test_eq(2, socks->replylen); + test_eq(5, socks->reply[0]); + test_eq(SOCKS_NO_AUTH, socks->reply[1]); + + /*SOCKS 5 Send username/password anyway - pretend to be broken */ + cp = "\x01\x02\x01\x01\x02\x01\x01"; + write_to_buf(cp, 7, buf); + test_assert(!fetch_from_buf_socks(buf, socks, + get_options()->TestSocks, + get_options()->SafeSocks)); + test_eq(5, socks->socks_version); + test_eq(2, socks->replylen); + test_eq(5, socks->reply[0]); + test_eq(0, socks->reply[1]); + +done: + ; +} + +/** Helper: Perform SOCKS 5 authentication */ +static void +test_buffers_socks5_authenticate_helper(const char *cp, buf_t *buf, + socks_request_t *socks) +{ + /* SOCKS 5 Negotiate username/password authentication */ + cp = "\x05\x01\x02"; + write_to_buf(cp, 3, buf); + test_assert(!fetch_from_buf_socks(buf, socks, + get_options()->TestSocks, + get_options()->SafeSocks)); + test_eq(2, socks->replylen); + test_eq(5, socks->reply[0]); + test_eq(SOCKS_USER_PASS, socks->reply[1]); + test_eq(5, socks->socks_version); + + /* SOCKS 5 Send username/password */ + cp = "\x01\x02me\x02me"; + write_to_buf(cp, 7, buf); + test_assert(!fetch_from_buf_socks(buf, socks, + get_options()->TestSocks, + get_options()->SafeSocks)); + test_eq(5, socks->socks_version); + test_eq(2, socks->replylen); + test_eq(5, socks->reply[0]); + test_eq(0, socks->reply[1]); +done: + ; +} + /** Run unit tests for buffers.c */ static void test_buffers(void) @@ -206,6 +407,7 @@ test_buffers(void) buf_t *buf = NULL, *buf2 = NULL; const char *cp; + socks_request_t *socks; int j; size_t r; @@ -363,6 +565,41 @@ test_buffers(void) buf_free(buf); buf = NULL; + /* Test fetch_from_buf_socks() */ + buf = buf_new_with_capacity(256); + socks = tor_malloc_zero(sizeof(socks_request_t));; + config_register_addressmaps(get_options()); + + /* A SOCKS 5 client that only supports authentication */ + test_buffers_socks5_authenticate_helper(cp, buf, socks); + test_buffers_socks5_supported_commands_helper(cp, buf, socks); + test_buffers_socks5_unsupported_commands_helper(cp, buf, socks); + + tor_free(socks); + buf_free(buf); + buf = NULL; + buf = buf_new_with_capacity(256); + socks = tor_malloc_zero(sizeof(socks_request_t));; + + /* A SOCKS 5 client that doesn't want authentication */ + test_buffers_socks5_no_authenticate_helper(cp, buf, socks); + test_buffers_socks5_supported_commands_helper(cp, buf, socks); + test_buffers_socks5_unsupported_commands_helper(cp, buf, socks); + + tor_free(socks); + buf_free(buf); + buf = NULL; + buf = buf_new_with_capacity(256); + socks = tor_malloc_zero(sizeof(socks_request_t));; + + /* A SOCKS 4(a) client */ + test_buffers_socks4_supported_commands_helper(cp, buf, socks); + test_buffers_socks4_unsupported_commands_helper(cp, buf, socks); + + tor_free(socks); + buf_free(buf); + buf = NULL; + done: if (buf) buf_free(buf);