
commit 891596713a8868b1d05b3fbfefc8b8674feb8b7a Author: George Kadianakis <desnacked@gmail.com> Date: Tue May 24 05:18:04 2011 +0200 Added support for more SOCKS5 replies. --- src/network.c | 4 +- src/socks.c | 121 +++++++++++++++++++++++++++++++++------------ src/socks.h | 12 +++- src/test/unittest_socks.c | 37 ++++++++++++-- 4 files changed, 134 insertions(+), 40 deletions(-) diff --git a/src/network.c b/src/network.c index 7b017b5..835dff1 100644 --- a/src/network.c +++ b/src/network.c @@ -358,7 +358,7 @@ output_event_cb(struct bufferevent *bev, short what, void *arg) bufferevent_enable(conn->input, EV_WRITE); bufferevent_disable(conn->input, EV_READ); socks_send_reply(conn->socks_state, bufferevent_get_output(conn->input), - SOCKS_FAILED); + EVUTIL_SOCKET_ERROR()); bufferevent_setcb(conn->input, NULL, close_conn_on_flush, output_event_cb, conn); return; @@ -400,7 +400,7 @@ output_event_cb(struct bufferevent *bev, short what, void *arg) socks_state_set_address(conn->socks_state, sa); } socks_send_reply(conn->socks_state, bufferevent_get_output(conn->input), - SOCKS_SUCCESS); + 0); /* we sent a socks reply. We can finally move over to being a regular input bufferevent. */ socks_state_free(conn->socks_state); diff --git a/src/socks.c b/src/socks.c index 5d73965..a388363 100644 --- a/src/socks.c +++ b/src/socks.c @@ -6,6 +6,7 @@ #include <string.h> #include <stdlib.h> #include <stdio.h> +#include <errno.h> #define SOCKS_PRIVATE #include "socks.h" @@ -34,7 +35,7 @@ "Method Negotiation Packet" is handled by: socks5_handle_negotiation() "Method Negotiation Reply" is done by: socks5_do_negotiation() "Client request" is handled by: socks5_handle_request() - "Server reply" is done by: socks5_send_reply + "Server reply" is done by: socks5_send_reply() */ static int socks5_do_negotiation(struct evbuffer *dest, @@ -59,20 +60,53 @@ socks_state_free(socks_state_t *s) memset(s,0x0b, sizeof(socks_state_t)); free(s); } + /** - This function handles connection requests by authenticated SOCKS - clients. - Considering a request packet from 'source', it evaluates it and pushes - the appropriate reply to 'dest'. - If the request was correct and can be fulfilled, it connects 'output' - to the location the client specified to actually set up the proxying. - - XXX: You will notice some "sizeof(req)-1" that initially make no - sense, but because of handle_socks() removing the version byte, - there are only 3 bytes (==sizeof(req)-1) between the start - of 'source' and addrlen. I don't know if replacing it with plain "3" - would make more sense. + This function receives an errno(3) 'error' and returns the reply + code that should be sent to the SOCKS client. + If 'error' is 0 it means that no errors were encountered, and + SOCKS{4,5}_SUCCESS is returned. +*/ +static int +socks_errno_to_reply(socks_state_t *state, int error) +{ + if (state->version == SOCKS4_VERSION) { + if (!error) + return SOCKS4_SUCCESS; + else + return SOCKS4_FAILED; + } else if (state->version == SOCKS5_VERSION) { + if (!error) + return SOCKS5_SUCCESS; + else { + switch (error) { + case ENETUNREACH: + return SOCKS5_FAILED_NETUNREACH; + case EHOSTUNREACH: + return SOCKS5_FAILED_HOSTUNREACH; + case ECONNREFUSED: + return SOCKS5_FAILED_REFUSED; + default: + return SOCKS5_FAILED_GENERAL; + } + } + } + return -1; +} +/** + Takes a command request from 'source', it evaluates it and if it's + legit it parses into 'parsereq'. + + It returns '1' if everything went fine. + It returns '0' if we need more data from the client. + It returns '-1' if we didn't like something. + It returns '-2' if the client asked for something else than CONNECT. + If that's the case we should send a reply back to the client + telling him that we don't support it. + + Disclaimer: This is just a temporary documentation. + Client Request (Client -> Server) */ int @@ -100,10 +134,12 @@ socks5_handle_request(struct evbuffer *source, struct parsereq *parsereq) /* p[0] = Version p[1] = Command field p[2] = Reserved field */ - if (p[0] != SOCKS5_VERSION || p[1] != SOCKS5_CMD_CONNECT || p[2] != 0x00) { - printf("socks: Only CONNECT supported. Sowwy!\n"); + if (p[0] != SOCKS5_VERSION || p[2] != 0x00) { + printf("socks: Corrupted packet. Discarding.\n"); goto err; } + if (p[1] != SOCKS5_CMD_CONNECT) + return -2; /* We must send reply to the client. */ unsigned int addrlen,af,extralen=0; /* p[3] is Address type field */ @@ -183,7 +219,6 @@ int socks5_send_reply(struct evbuffer *reply_dest, socks_state_t *state, int status) { - /* This is the buffer that contains our reply to the client. */ uchar p[4]; uchar addr[16]; const char *extra = NULL; @@ -193,19 +228,31 @@ socks5_send_reply(struct evbuffer *reply_dest, socks_state_t *state, Either way, we should send something back to the client */ p[0] = SOCKS5_VERSION; /* Version field */ /* Reply field */ - p[1] = (status == SOCKS_SUCCESS) ? SOCKS5_SUCCESS : SOCKS5_FAILED; + p[1] = status; p[2] = 0; /* Reserved */ - if (state->parsereq.af == AF_UNSPEC) { - addrlen = 1; - addr[0] = strlen(state->parsereq.addr); - extra = state->parsereq.addr; - p[3] = SOCKS5_ATYP_FQDN; + + /* If status is SOCKS5_FAILED_UNSUPPORTED, it means that we failed + before populating state->parsereq. We will just feel the rest + of the reply packet with zeroes and ship it off. + */ + if (status == SOCKS5_FAILED_UNSUPPORTED) { + addrlen = 4; + p[3] = SOCKS5_ATYP_IPV4; + memset(addr,'\x00',4); + port = 0; } else { - addrlen = (state->parsereq.af == AF_INET) ? 4 : 16; - p[3] = (state->parsereq.af == AF_INET) ? SOCKS5_ATYP_IPV4 : SOCKS5_ATYP_IPV6; - evutil_inet_pton(state->parsereq.af, state->parsereq.addr, addr); + if (state->parsereq.af == AF_UNSPEC) { + addrlen = 1; + addr[0] = strlen(state->parsereq.addr); + extra = state->parsereq.addr; + p[3] = SOCKS5_ATYP_FQDN; + } else { + addrlen = (state->parsereq.af == AF_INET) ? 4 : 16; + p[3] = (state->parsereq.af == AF_INET) ? SOCKS5_ATYP_IPV4 : SOCKS5_ATYP_IPV6; + evutil_inet_pton(state->parsereq.af, state->parsereq.addr, addr); + } + port = htons(state->parsereq.port); } - port = htons(state->parsereq.port); evbuffer_add(reply_dest, p, 4); evbuffer_add(reply_dest, addr, addrlen); @@ -377,7 +424,7 @@ socks4_send_reply(struct evbuffer *dest, socks_state_t *state, int status) /* Nul byte */ msg[0] = 0; /* convert to socks4 status */ - msg[1] = (status == SOCKS_SUCCESS) ? SOCKS4_SUCCESS : SOCKS4_FAILED; + msg[1] = status; memcpy(msg+2, &portnum, 2); /* ASN: What should we do here in the case of an FQDN request? */ memcpy(msg+4, &in.s_addr, 4); @@ -450,7 +497,11 @@ handle_socks(struct evbuffer *source, struct evbuffer *dest, r = socks5_handle_request(source,&socks_state->parsereq); if (r == -1) goto broken; - else if (r == 1) + else if (r == -2) { + socks5_send_reply(dest,socks_state, + SOCKS5_FAILED_UNSUPPORTED); + goto broken; + } else if (r == 1) socks_state->state = ST_HAVE_ADDR; return r; } @@ -513,12 +564,20 @@ socks_state_set_address(socks_state_t *state, const struct sockaddr *sa) return 0; } +/** + 'error' is 0 if no errors were encountered during the SOCKS + operation (normally a CONNECT with no errors means that the + connect() was successful). + If 'error' is not 0, it means that an error was encountered and + error carries the errno(3) of the error. +*/ int -socks_send_reply(socks_state_t *state, struct evbuffer *dest, int status) +socks_send_reply(socks_state_t *state, struct evbuffer *dest, int error) { - if (state->version == 5) + int status = socks_errno_to_reply(state, error); + if (state->version == SOCKS5_VERSION) return socks5_send_reply(dest, state, status); - else if (state->version == 4) + else if (state->version == SOCKS4_VERSION) return socks4_send_reply(dest, state, status); else return -1; diff --git a/src/socks.h b/src/socks.h index 589cd9c..01a76ff 100644 --- a/src/socks.h +++ b/src/socks.h @@ -27,13 +27,19 @@ int socks_state_get_address(const socks_state_t *state, const char **addr_out, int *port_out); int socks_state_set_address(socks_state_t *state, const struct sockaddr *sa); -int socks_send_reply(socks_state_t *state, struct evbuffer *dest, int status); +int socks_send_reply(socks_state_t *state, struct evbuffer *dest, int error); #define SOCKS_SUCCESS 0x0 #define SOCKS_FAILED 0x01 -#define SOCKS5_SUCCESS 0x0 -#define SOCKS5_FAILED 0x01 +#define SOCKS5_SUCCESS 0x0 +#define SOCKS5_FAILED_GENERAL 0x01 +#define SOCKS5_FAILED_NOTALLOWED 0x02 +#define SOCKS5_FAILED_NETUNREACH 0x03 +#define SOCKS5_FAILED_HOSTUNREACH 0x04 +#define SOCKS5_FAILED_REFUSED 0x05 +#define SOCKS5_FAILED_TTLEXPIRED 0x06 +#define SOCKS5_FAILED_UNSUPPORTED 0x07 #ifdef SOCKS_PRIVATE diff --git a/src/test/unittest_socks.c b/src/test/unittest_socks.c index 8f06d51..8839eff 100644 --- a/src/test/unittest_socks.c +++ b/src/test/unittest_socks.c @@ -266,6 +266,21 @@ test_socks_socks5_request(void *data) buffer_len = evbuffer_get_length(source); tt_int_op(0, ==, evbuffer_drain(source, buffer_len)); + /* Eigth test: + Everything is dreamy... if only the requested command was CONNECT... */ + uchar req8[9]; + req8[0] = 3; + req8[1] = 0; + req8[2] = 1; + memcpy(req8+3,&addr_ipv4,4); + memcpy(req8+7,&port,2); + + evbuffer_add(source, "\x05", 1); + evbuffer_add(source, req8, 9); + /* '-2' means that we don't support the requested command. */ + tt_int_op(-2, ==, socks5_handle_request(source,&pr1)); + + end: if (state) socks_state_free(state); @@ -301,7 +316,7 @@ test_socks_socks5_request_reply(void *data) We ask the server to send us a reply on an IPv4 request with succesful status. */ tt_int_op(1, ==, socks5_send_reply(reply_dest, - state, SOCKS_SUCCESS)); + state, SOCKS5_SUCCESS)); uchar rep1[255]; evbuffer_remove(reply_dest,rep1,255); /* yes, this is dirty */ @@ -348,7 +363,7 @@ test_socks_socks5_request_reply(void *data) strcpy(state->parsereq.addr, fqdn); tt_int_op(1, ==, socks5_send_reply(reply_dest, - state, SOCKS_FAILED)); + state, SOCKS5_FAILED_GENERAL)); uchar rep3[255]; evbuffer_remove(reply_dest,rep3,255); @@ -360,6 +375,20 @@ test_socks_socks5_request_reply(void *data) /* check port */ tt_int_op(0, ==, memcmp(rep3+5+strlen(fqdn),"\x1c\xbd",2)); + /* Fourth test: + We ask the server while having an empty parsereq and with a + SOCKS5_FAILED_UNSUPPORTED status. */ + memset(&state->parsereq,'\x00',sizeof(struct parsereq)); + + tt_int_op(1, ==, socks5_send_reply(reply_dest, + state, SOCKS5_FAILED_UNSUPPORTED)); + uchar rep4[255]; + evbuffer_remove(reply_dest,rep4,255); + + tt_assert(rep4[3] == SOCKS5_ATYP_IPV4); + tt_int_op(0, ==, memcmp(rep4+4,"\x00\x00\x00\x00",4)); + tt_int_op(0, ==, memcmp(rep4+4+4, "\x00\x00", 2)); + end: if (state) socks_state_free(state); @@ -534,7 +563,7 @@ test_socks_socks4_request_reply(void *data) We ask the server to send us a reply on an IPv4 request with succesful status. */ tt_int_op(1, ==, socks4_send_reply(reply_dest, - state, SOCKS_SUCCESS)); + state, SOCKS4_SUCCESS)); uchar rep1[255]; evbuffer_remove(reply_dest,rep1,255); /* yes, this is dirty */ @@ -558,7 +587,7 @@ test_socks_socks4_request_reply(void *data) strcpy(state->parsereq.addr, fqdn); tt_int_op(1, ==, socks4_send_reply(reply_dest, - state, SOCKS_FAILED)); + state, SOCKS4_FAILED)); uchar rep2[255]; evbuffer_remove(reply_dest,rep2,255);