diff --git a/sockssrv.c b/sockssrv.c index d59fc27..12a9b38 100644 --- a/sockssrv.c +++ b/sockssrv.c @@ -33,8 +33,10 @@ #include #include #include +#include #include "server.h" #include "sblist.h" +#define MICROSOCKS_VERSION "1.0.5-forward" /* timeout in microseconds on resource exhaustion to prevent excessive cpu usage. */ @@ -71,6 +73,7 @@ static sblist* auth_ips; static pthread_rwlock_t auth_ips_lock = PTHREAD_RWLOCK_INITIALIZER; static const struct server* server; static union sockaddr_union bind_addr = {.v4.sin_family = AF_UNSPEC}; +static sblist *fwd_rules; enum socksstate { SS_1_CONNECTED, @@ -97,6 +100,17 @@ enum errorcode { EC_ADDRESSTYPE_NOT_SUPPORTED = 8, }; +struct fwd_rule { + char *match_name; + short match_port; + char *auth_buf; /* Username/Password request buffer (RFC-1929) */ + size_t auth_len; + char *upstream_name; + short upstream_port; + char *req_buf; /* Client Connection Request buffer to send to upstream */ + size_t req_len; +}; + struct thread { pthread_t pt; struct client client; @@ -116,6 +130,109 @@ struct thread { static void dolog(const char* fmt, ...) { } #endif +static int upstream_handshake(const struct fwd_rule* rule, unsigned char *client_buf, size_t client_buf_len, + int client_fd, int upstream_fd, unsigned short client_port) { + unsigned char sbuf[512]; + ssize_t r; + + if(rule->auth_buf) { + unsigned char handshake[4] = {5, 2, 0, 2}; + if (write(upstream_fd, handshake, 4) != 4) { + close(upstream_fd); + return -1; + } + } else { + unsigned char handshake[3] = {5, 1, 0}; + if (write(upstream_fd, handshake, 3) != 3) { + close(upstream_fd); + return -1; + } + } + + if (read(upstream_fd, sbuf, 2) != 2 || sbuf[0] != 5) { + close(upstream_fd); + return -1; + } + + if (sbuf[1] == 2) { + if (!rule->auth_buf) { + close(upstream_fd); + return -1; + } + if (write(upstream_fd, rule->auth_buf, rule->auth_len) != (ssize_t)rule->auth_len) { + close(upstream_fd); + return -1; + } + if (read(upstream_fd, sbuf, 2) != 2 || sbuf[0] != 1 || sbuf[1] != 0) { + close(upstream_fd); + return -1; + } + } else if (sbuf[1] != 0) { + close(upstream_fd); + return -1; + } + + if (write(upstream_fd, client_buf, client_buf_len) != (ssize_t)client_buf_len) { + close(upstream_fd); + return -1; + } + + size_t total = 0; + size_t need = 4; + + while (total < need) { + r = read(upstream_fd, sbuf + total, need - total); + if (r <= 0) { + close(upstream_fd); + return -1; + } + total += r; + } + + if (sbuf[1] != 0) { + close(upstream_fd); + return -sbuf[1]; + } + + size_t need_more = 0; + switch (sbuf[3]) { + case 1: + need_more = 4 + 2; + break; + case 4: + need_more = 16 + 2; + break; + case 3: + r = read(upstream_fd, sbuf + total, 1); + if (r != 1) { + close(upstream_fd); + return -1; + } + total += r; + need_more = sbuf[4] + 2; + break; + default: + close(upstream_fd); + return -EC_ADDRESSTYPE_NOT_SUPPORTED; + } + + while (total < need + need_more) { + r = read(upstream_fd, sbuf + total, (need + need_more) - total); + if (r <= 0) { + close(upstream_fd); + return -1; + } + total += r; + } + + if (write(client_fd, sbuf, total) != (ssize_t)total) { + close(upstream_fd); + return -1; + } + + return upstream_fd; +} + static struct addrinfo* addr_choose(struct addrinfo* list, union sockaddr_union* bindaddr) { int af = SOCKADDR_UNION_AF(bindaddr); if(af == AF_UNSPEC) return list; @@ -125,7 +242,9 @@ static struct addrinfo* addr_choose(struct addrinfo* list, union sockaddr_union* return list; } -static int connect_socks_target(unsigned char *buf, size_t n, struct client *client) { +static int connect_socks_target(unsigned char *buf, size_t n, struct client *client, int *used_rule) { + *used_rule = 0; + if(n < 5) return -EC_GENERAL_FAILURE; if(buf[0] != 5) return -EC_GENERAL_FAILURE; if(buf[1] != 1) return -EC_COMMAND_NOT_SUPPORTED; /* we support only CONNECT method */ @@ -158,6 +277,29 @@ static int connect_socks_target(unsigned char *buf, size_t n, struct client *cli } unsigned short port; port = (buf[minlen-2] << 8) | buf[minlen-1]; + + size_t i; + struct fwd_rule *rule = NULL; + char original_name[256]; + unsigned short original_port = port; + strncpy(original_name, namebuf, sizeof(original_name) - 1); + original_name[sizeof(original_name) - 1] = '\0'; + if(fwd_rules) { + for(i=0;imatch_name[0]=='\0' || strcmp(r->match_name, namebuf) == 0); + int port_match = (r->match_port == 0 || r->match_port == port); + if(name_match && port_match) { + rule = r; + *used_rule = 1; + strncpy(namebuf, r->upstream_name, sizeof(namebuf)-1); + namebuf[sizeof(namebuf)-1] = '\0'; + port = r->upstream_port; + break; + } + } + } + /* there's no suitable errorcode in rfc1928 for dns lookup failure */ if(resolve(namebuf, port, &remote)) return -EC_GENERAL_FAILURE; struct addrinfo* raddr = addr_choose(remote, &bind_addr); @@ -186,6 +328,11 @@ static int connect_socks_target(unsigned char *buf, size_t n, struct client *cli return -EC_GENERAL_FAILURE; } } + + struct timeval tv = {5, 0}; + setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof(tv)); + setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, (const char*)&tv, sizeof(tv)); + if(SOCKADDR_UNION_AF(&bind_addr) == raddr->ai_family && bindtoip(fd, &bind_addr) == -1) goto eval_errno; @@ -198,9 +345,22 @@ static int connect_socks_target(unsigned char *buf, size_t n, struct client *cli af = SOCKADDR_UNION_AF(&client->addr); void *ipdata = SOCKADDR_UNION_ADDRESS(&client->addr); inet_ntop(af, ipdata, clientname, sizeof clientname); - dolog("client[%d] %s: connected to %s:%d\n", client->fd, clientname, namebuf, port); + if (rule) { + dolog("client[%d] %s: %s:%d -> via %s:%d\n", client->fd, clientname, original_name, original_port, rule->upstream_name, rule->upstream_port); + } else { + dolog("client[%d] %s: connected to %s:%d\n", client->fd, clientname, namebuf, port); + } } - return fd; + + if (rule) { + int result = upstream_handshake(rule, buf, n, client->fd, fd, original_port); + if (result < 0) { + close(fd); + return result; + } + return result; + } + return fd; } static int is_authed(union sockaddr_union *client, union sockaddr_union *authedip) { @@ -322,6 +482,7 @@ static int handshake(struct thread *t) { ssize_t n; int ret; enum authmethod am; + int used_rule = 0; t->state = SS_1_CONNECTED; while((n = recv(t->client.fd, buf, sizeof buf, 0)) > 0) { switch(t->state) { @@ -345,12 +506,14 @@ static int handshake(struct thread *t) { } break; case SS_3_AUTHED: - ret = connect_socks_target(buf, n, &t->client); + ret = connect_socks_target(buf, n, &t->client, &used_rule); if(ret < 0) { send_error(t->client.fd, ret*-1); return -1; } - send_error(t->client.fd, EC_SUCCESS); + if (!used_rule) { + send_error(t->client.fd, EC_SUCCESS); + } return ret; } } @@ -382,11 +545,131 @@ static void collect(sblist *threads) { } } +static short host_get_port(char *name) { + int p,n; + char *c; + if((c = strrchr(name, ':')) && sscanf(c+1,"%d%n",&p, &n)==1 && n == (int)(strlen(c + 1)) && p >= 0 && p < USHRT_MAX) + return (*c='\0'),(short)p; + else + return -1; +} + +static int fwd_rules_add(char *str) { + char *match = NULL, *upstream = NULL, *remote = NULL; + unsigned short match_port, upstream_port, remote_port; + int ncred; + + if(sscanf(str, "%m[^,],%n%m[^,],%ms\n", &match, &ncred, &upstream, &remote) != 3) + return 1; + + match_port = host_get_port(match); + upstream_port = host_get_port(upstream); + remote_port = host_get_port(remote); + + if(match_port < 0 || upstream_port <= 0 || remote_port < 0) { + free(match); + free(upstream); + free(remote); + return 1; + } + + char *match_copy = strdup(match); + char *upstream_copy = strdup(upstream); + char *remote_copy = strdup(remote); + + struct fwd_rule *rule = (struct fwd_rule*)malloc(sizeof(struct fwd_rule)); + if (!rule) { + free(match_copy); + free(upstream_copy); + free(remote_copy); + free(match); + free(upstream); + free(remote); + return 1; + } + + if(strcmp(match_copy, "0.0.0.0") == 0 || strcmp(match_copy, "*") == 0) { + free(match_copy); + rule->match_name = strdup(""); + } else { + rule->match_name = match_copy; + } + rule->match_port = match_port; + rule->auth_buf = NULL; + rule->auth_len = 0; + + char *at_sign = strchr(upstream_copy, '@'); + if (at_sign) { + *at_sign = '\0'; + char *auth_part = upstream_copy; + char *host_part = at_sign + 1; + char *colon = strchr(auth_part, ':'); + if (!colon) { + free(rule); + free(upstream_copy); + free(remote_copy); + free(match); + free(upstream); + free(remote); + return 1; + } + *colon++ = '\0'; + char *username = auth_part; + char *password = colon; + size_t ulen = strlen(username); + size_t plen = strlen(password); + if (ulen > 255 || plen > 255) { + free(rule); + free(upstream_copy); + free(remote_copy); + free(match); + free(upstream); + free(remote); + return 1; + } + rule->auth_len = 1 + 1 + ulen + 1 + plen; + rule->auth_buf = malloc(rule->auth_len); + rule->auth_buf[0] = 1; + rule->auth_buf[1] = ulen; + memcpy(&rule->auth_buf[2], username, ulen); + rule->auth_buf[2 + ulen] = plen; + memcpy(&rule->auth_buf[3 + ulen], password, plen); + rule->upstream_name = strdup(host_part); + rule->upstream_port = upstream_port; + /* hide from ps */ + memset(str+ncred, '*', ulen+1+plen); + } else { + rule->upstream_name = strdup(upstream_copy); + rule->upstream_port = upstream_port; + } + + free(upstream_copy); + short rlen = strlen(remote_copy); + rule->req_len = 3 + 1 + 1 + rlen + 2; + rule->req_buf = (char*)malloc(rule->req_len); + rule->req_buf[0] = 5; + rule->req_buf[1] = 1; + rule->req_buf[2] = 0; + rule->req_buf[3] = 3; + rule->req_buf[4] = rlen; + memcpy(&rule->req_buf[5], remote_copy, rlen); + unsigned short rport = remote_port ? remote_port : 0; + rule->req_buf[5 + rlen] = (rport >> 8) & 0xFF; + rule->req_buf[5 + rlen + 1] = (rport & 0xFF); + free(remote_copy); + sblist_add(fwd_rules, rule); + free(match); + free(upstream); + free(remote); + + return 0; +} + static int usage(void) { dprintf(2, "MicroSocks SOCKS5 Server\n" "------------------------\n" - "usage: microsocks -1 -q -i listenip -p port -u user -P pass -b bindaddr -w ips\n" + "usage: microsocks -1 -q -i listenip -p port -u user -P pass -b bindaddr -w ips -f fwdrule\n" "all arguments are optional.\n" "by default listenip is 0.0.0.0 and port 1080.\n\n" "option -q disables logging.\n" @@ -401,6 +684,12 @@ static int usage(void) { " this is handy for programs like firefox that don't support\n" " user/pass auth. for it to work you'd basically make one connection\n" " with another program that supports it, and then you can use firefox too.\n" + "option -f specifies a forwarding rule of the form\n" + " match_name:match_port,[user:password@]upstream_name:upstream_port,remote_name:remote_port\n" + " this will cause requests that /match/ to be renamed to /remote/\n" + " and sent to the /upstream/ SOCKS5 proxy server.\n" + " this option may be specified multiple times.\n" + "option -V prints version information and exits.\n" ); return 1; } @@ -416,7 +705,7 @@ int main(int argc, char** argv) { const char *listenip = "0.0.0.0"; char *p, *q; unsigned port = 1080; - while((ch = getopt(argc, argv, ":1qb:i:p:u:P:w:")) != -1) { + while((ch = getopt(argc, argv, ":1qb:i:p:u:P:w:f:V")) != -1) { switch(ch) { case 'w': /* fall-through */ case '1': @@ -456,11 +745,20 @@ int main(int argc, char** argv) { case 'p': port = atoi(optarg); break; + case 'f': + if(!fwd_rules) + fwd_rules = sblist_new(sizeof(struct fwd_rule), 16); + if(fwd_rules_add(optarg)) + return dprintf(2, "error: could not parse forwarding rule %s\n", optarg), 1; + break; case ':': dprintf(2, "error: option -%c requires an operand\n", optopt); /* fall through */ case '?': return usage(); + case 'V': + dprintf(1, "MicroSocks %s\n", MICROSOCKS_VERSION); + return 0; } } if((auth_user && !auth_pass) || (!auth_user && auth_pass)) {