From 3230c369b4d8cb1641b22482c629678f31df08c6 Mon Sep 17 00:00:00 2001 From: rofl0r Date: Wed, 1 Mar 2017 12:57:31 +0000 Subject: [PATCH] first version of microsocks socks5 server --- sockssrv.c | 369 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 369 insertions(+) create mode 100644 sockssrv.c diff --git a/sockssrv.c b/sockssrv.c new file mode 100644 index 0000000..78a5b05 --- /dev/null +++ b/sockssrv.c @@ -0,0 +1,369 @@ +/* + MicroSocks - multithreaded, small, efficient SOCKS5 server. + + Copyright (C) 2017 rofl0r. + + This is the successor of "rocksocks5", and it was written with + different goals in mind: + + - prefer usage of standard libc functions over homegrown ones + - no artificial limits + - do not aim for minimal binary size, but for minimal source code size, + and maximal readability, reusability, and extensibility. + + as a result of that, ipv4, dns, and ipv6 is supported out of the box + and can use the same code, while rocksocks5 has several compile time + defines to bring down the size of the resulting binary to extreme values + like 10 KB static linked when only ipv4 support is enabled. + + still, if optimized for size, *this* program when static linked against musl + libc is not even 50 KB. that's easily usable even on the cheapest routers. + +*/ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include "server.h" +#include "../lib/include/sblist.h" + +static const char* auth_user; +static const char* auth_pass; + +enum socksstate { + SS_1_CONNECTED, + SS_2_NEED_AUTH, /* skipped if NO_AUTH method supported */ + SS_3_AUTHED, +}; + +enum authmethod { + AM_NO_AUTH = 0, + AM_GSSAPI = 1, + AM_USERNAME = 2, + AM_INVALID = 0xFF +}; + +enum errorcode { + EC_SUCCESS = 0, + EC_GENERAL_FAILURE = 1, + EC_NOT_ALLOWED = 2, + EC_NET_UNREACHABLE = 3, + EC_HOST_UNREACHABLE = 4, + EC_CONN_REFUSED = 5, + EC_TTL_EXPIRED = 6, + EC_COMMAND_NOT_SUPPORTED = 7, + EC_ADDRESSTYPE_NOT_SUPPORTED = 8, +}; + +struct thread { + pthread_t pt; + struct client client; + enum socksstate state; + volatile int done; +}; +#ifndef CONFIG_LOG +#define CONFIG_LOG 1 +#endif +#if CONFIG_LOG +/* we log to stderr because it's not using line buffering, i.e. malloc which would need + locking when called from different threads. for the same reason we use dprintf, + which writes directly to an fd. */ +#define dolog(...) dprintf(2, __VA_ARGS__) +#else +static void dolog(const char* fmt, ...) { } +#endif + +static int connect_socks_target(unsigned char *buf, size_t n, struct client *client) { + 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 */ + if(buf[2] != 0) return -EC_GENERAL_FAILURE; /* malformed packet */ + + int af = AF_INET; + size_t minlen = 4 + 4 + 2, l; + char namebuf[256]; + struct addrinfo* remote; + + switch(buf[3]) { + case 4: /* ipv6 */ + af = AF_INET6; + minlen = 4 + 2 + 16; + /* fall through */ + case 1: /* ipv4 */ + if(n < minlen) return -EC_GENERAL_FAILURE; + if(namebuf != inet_ntop(af, buf+4, namebuf, sizeof namebuf)) + return -EC_GENERAL_FAILURE; /* malformed or too long addr */ + break; + case 3: /* dns name */ + l = buf[4]; + minlen = 4 + 2 + l + 1; + if(n < 4 + 2 + l + 1) return -EC_GENERAL_FAILURE; + memcpy(namebuf, buf+4+1, l); + namebuf[l] = 0; + break; + default: + return -EC_ADDRESSTYPE_NOT_SUPPORTED; + } + unsigned short port; + port = (buf[minlen-2] << 8) | buf[minlen-1]; + if(resolve(namebuf, port, &remote)) return -9; + int fd = socket(remote->ai_addr->sa_family, SOCK_STREAM, 0); + if(fd == -1) { + eval_errno: + freeaddrinfo(remote); + switch(errno) { + case EPROTOTYPE: + case EPROTONOSUPPORT: + case EAFNOSUPPORT: + return -EC_ADDRESSTYPE_NOT_SUPPORTED; + case ECONNREFUSED: + return -EC_CONN_REFUSED; + case ENETDOWN: + case ENETUNREACH: + return -EC_NET_UNREACHABLE; + case EHOSTUNREACH: + return -EC_HOST_UNREACHABLE; + case EBADF: + default: + perror("socket/connect"); + return -EC_GENERAL_FAILURE; + } + } + if(connect(fd, remote->ai_addr, remote->ai_addrlen) == -1) + goto eval_errno; + + freeaddrinfo(remote); + if(CONFIG_LOG) { + char clientname[256]; + af = client->addr.v4.sin_family; + void *ipdata = af == AF_INET ? (void*)&client->addr.v4.sin_addr : (void*)&client->addr.v6.sin6_addr; + inet_ntop(af, ipdata, clientname, sizeof clientname); + dolog("client[%d] %s: connected to %s:%d\n", client->fd, clientname, namebuf, port); + } + return fd; +} + +static enum authmethod check_auth_method(unsigned char *buf, size_t n) { + if(buf[0] != 5) return AM_INVALID; + size_t idx = 1; + if(idx >= n ) return AM_INVALID; + int n_methods = buf[idx]; + idx++; + while(idx < n) { + if(buf[idx] == AM_NO_AUTH) { + if(!auth_user) return AM_NO_AUTH; + } else if(buf[idx] == AM_USERNAME) { + if(auth_user) return AM_USERNAME; + } + idx++; + } + return AM_INVALID; +} + +static void send_auth_response(int fd, enum authmethod meth) { + unsigned char buf[2]; + buf[0] = 5; + buf[1] = meth; + write(fd, buf, 2); +} + +static void send_error(int fd, enum errorcode ec) { + /* position 4 contains ATYP, the address type, which is the same as used in the connect + request. we're lazy and return always IPV4 address type in errors. */ + char buf[10] = { 5, ec, 0, 1 /*AT_IPV4*/, 0,0,0,0, 0,0 }; + write(fd, buf, 10); +} + +static void copyloop(int fd1, int fd2) { + int maxfd = fd2; + if(fd1 > fd2) maxfd = fd1; + fd_set fdsc, fds; + FD_ZERO(&fdsc); + FD_SET(fd1, &fdsc); + FD_SET(fd2, &fdsc); + + while(1) { + memcpy(&fds, &fdsc, sizeof(fds)); + /* inactive connections are reaped after 15 min to free resources. + usually programs send keep-alive packets so this should only happen + when a connection is really unused. */ + struct timeval timeout = {.tv_sec = 60*15, .tv_usec = 0}; + switch(select(maxfd+1, &fds, 0, 0, &timeout)) { + case 0: + send_error(fd1, EC_TTL_EXPIRED); + return; + case -1: + if(errno = EINTR) continue; + else perror("select"); + return; + } + int infd = FD_ISSET(fd1, &fds) ? fd1 : fd2; + int outfd = infd == fd2 ? fd1 : fd2; + char buf[1024]; + ssize_t sent = 0, n = read(infd, buf, sizeof buf); + if(n <= 0) return; + while(sent < n) { + ssize_t m = write(outfd, buf+sent, n-sent); + if(m < 0) return; + sent += m; + } + } +} + +static enum errorcode check_credentials(unsigned char* buf, size_t n) { + if(n < 5) return EC_GENERAL_FAILURE; + if(buf[0] != 1) return EC_GENERAL_FAILURE; + unsigned ulen, plen; + ulen=buf[1]; + if(n < 2 + ulen + 2) return EC_GENERAL_FAILURE; + plen=buf[2+ulen]; + if(n < 2 + ulen + 1 + plen) return EC_GENERAL_FAILURE; + char user[256], pass[256]; + memcpy(user, buf+2, ulen); + memcpy(pass, buf+2+ulen+1, plen); + if(!strcmp(user, auth_user) && !strcmp(pass, auth_pass)) return EC_SUCCESS; + return EC_NOT_ALLOWED; +} + +static void* clientthread(void *data) { + struct thread *t = data; + t->state = SS_1_CONNECTED; + unsigned char buf[1024]; + ssize_t n; + int ret; + int remotefd = -1; + enum authmethod am; + while((n = recv(t->client.fd, buf, sizeof buf, 0)) > 0) { + switch(t->state) { + case SS_1_CONNECTED: + am = check_auth_method(buf, n); + if(am == AM_NO_AUTH) t->state = SS_3_AUTHED; + else if (am == AM_USERNAME) t->state = SS_2_NEED_AUTH; + send_auth_response(t->client.fd, am); + if(am == AM_INVALID) goto breakloop; + break; + case SS_2_NEED_AUTH: + ret = check_credentials(buf, n); + send_auth_response(t->client.fd, ret); + if(ret != EC_SUCCESS) + goto breakloop; + t->state = SS_3_AUTHED; + break; + case SS_3_AUTHED: + ret = connect_socks_target(buf, n, &t->client); + if(ret < 0) { + send_error(t->client.fd, ret*-1); + goto breakloop; + } + remotefd = ret; + send_error(t->client.fd, EC_SUCCESS); + copyloop(t->client.fd, remotefd); + goto breakloop; + + } + } +breakloop: + + if(remotefd != -1) + close(remotefd); + + close(t->client.fd); + t->done = 1; + + return 0; +} + +static void collect(sblist *threads) { + size_t i; + for(i=0;idone) { + pthread_join(thread->pt, 0); + sblist_delete(threads, i); + free(thread); + } else + i++; + } +} + +static int usage(void) { + dprintf(2, + "MicroSocks SOCKS5 Server\n" + "------------------------\n" + "usage: microsocks -i listenip -p port -u user -P password\n" + "all arguments are optional.\n" + "by default listenip is 0.0.0.0 and port 1080.\n" + ); + return 1; +} + +/* prevent username and password from showing up in top. */ +static void zero_arg(char *s) { + size_t i, l = strlen(s); + for(i=0;idone = 0; + if(server_waitclient(&s, &c)) continue; + curr->client = c; + if(!sblist_add(threads, &curr)) { + free(curr); + oom: + dolog("rejecting connection due to OOM\n"); + usleep(16); /* prevent 100% CPU usage in OOM situation */ + continue; + } + pthread_attr_t *a = 0, attr; + if(pthread_attr_init(&attr) == 0) { + a = &attr; + pthread_attr_setstacksize(a, 8192); /* 4KB for us, 4KB for libc */ + } + if(pthread_create(&curr->pt, a, clientthread, curr) != 0) + dolog("pthread_create failed. OOM?\n"); + if(a) pthread_attr_destroy(&attr); + } +} -- 2.11.4.GIT