clean up stacksize selection macros
[rofl0r-microsocks.git] / sockssrv.c
blobafe707323d83e1f5ca96d1671b99fb7de50fe797
1 /*
2 MicroSocks - multithreaded, small, efficient SOCKS5 server.
4 Copyright (C) 2017 rofl0r.
6 This is the successor of "rocksocks5", and it was written with
7 different goals in mind:
9 - prefer usage of standard libc functions over homegrown ones
10 - no artificial limits
11 - do not aim for minimal binary size, but for minimal source code size,
12 and maximal readability, reusability, and extensibility.
14 as a result of that, ipv4, dns, and ipv6 is supported out of the box
15 and can use the same code, while rocksocks5 has several compile time
16 defines to bring down the size of the resulting binary to extreme values
17 like 10 KB static linked when only ipv4 support is enabled.
19 still, if optimized for size, *this* program when static linked against musl
20 libc is not even 50 KB. that's easily usable even on the cheapest routers.
24 #define _GNU_SOURCE
25 #include <unistd.h>
26 #define _POSIX_C_SOURCE 200809L
27 #include <stdlib.h>
28 #include <string.h>
29 #include <stdio.h>
30 #include <pthread.h>
31 #include <signal.h>
32 #include <sys/select.h>
33 #include <arpa/inet.h>
34 #include <errno.h>
35 #include <limits.h>
36 #include "server.h"
37 #include "sblist.h"
39 #ifndef MAX
40 #define MAX(x, y) ((x) > (y) ? (x) : (y))
41 #endif
43 #ifdef PTHREAD_STACK_MIN
44 #define THREAD_STACK_SIZE MAX(8*1024, PTHREAD_STACK_MIN)
45 #else
46 #define THREAD_STACK_SIZE 64*1024
47 #endif
49 #if defined(__APPLE__)
50 #undef THREAD_STACK_SIZE
51 #define THREAD_STACK_SIZE 64*1024
52 #elif defined(__GLIBC__) || defined(__FreeBSD__)
53 #undef THREAD_STACK_SIZE
54 #define THREAD_STACK_SIZE 32*1024
55 #endif
57 static const char* auth_user;
58 static const char* auth_pass;
59 static sblist* auth_ips;
60 static pthread_mutex_t auth_ips_mutex = PTHREAD_MUTEX_INITIALIZER;
61 static const struct server* server;
62 static union sockaddr_union bind_addr = {.v4.sin_family = AF_UNSPEC};
64 enum socksstate {
65 SS_1_CONNECTED,
66 SS_2_NEED_AUTH, /* skipped if NO_AUTH method supported */
67 SS_3_AUTHED,
70 enum authmethod {
71 AM_NO_AUTH = 0,
72 AM_GSSAPI = 1,
73 AM_USERNAME = 2,
74 AM_INVALID = 0xFF
77 enum errorcode {
78 EC_SUCCESS = 0,
79 EC_GENERAL_FAILURE = 1,
80 EC_NOT_ALLOWED = 2,
81 EC_NET_UNREACHABLE = 3,
82 EC_HOST_UNREACHABLE = 4,
83 EC_CONN_REFUSED = 5,
84 EC_TTL_EXPIRED = 6,
85 EC_COMMAND_NOT_SUPPORTED = 7,
86 EC_ADDRESSTYPE_NOT_SUPPORTED = 8,
89 struct thread {
90 pthread_t pt;
91 struct client client;
92 enum socksstate state;
93 volatile int done;
96 #ifndef CONFIG_LOG
97 #define CONFIG_LOG 1
98 #endif
99 #if CONFIG_LOG
100 /* we log to stderr because it's not using line buffering, i.e. malloc which would need
101 locking when called from different threads. for the same reason we use dprintf,
102 which writes directly to an fd. */
103 #define dolog(...) dprintf(2, __VA_ARGS__)
104 #else
105 static void dolog(const char* fmt, ...) { }
106 #endif
108 static int connect_socks_target(unsigned char *buf, size_t n, struct client *client) {
109 if(n < 5) return -EC_GENERAL_FAILURE;
110 if(buf[0] != 5) return -EC_GENERAL_FAILURE;
111 if(buf[1] != 1) return -EC_COMMAND_NOT_SUPPORTED; /* we support only CONNECT method */
112 if(buf[2] != 0) return -EC_GENERAL_FAILURE; /* malformed packet */
114 int af = AF_INET;
115 size_t minlen = 4 + 4 + 2, l;
116 char namebuf[256];
117 struct addrinfo* remote;
119 switch(buf[3]) {
120 case 4: /* ipv6 */
121 af = AF_INET6;
122 minlen = 4 + 2 + 16;
123 /* fall through */
124 case 1: /* ipv4 */
125 if(n < minlen) return -EC_GENERAL_FAILURE;
126 if(namebuf != inet_ntop(af, buf+4, namebuf, sizeof namebuf))
127 return -EC_GENERAL_FAILURE; /* malformed or too long addr */
128 break;
129 case 3: /* dns name */
130 l = buf[4];
131 minlen = 4 + 2 + l + 1;
132 if(n < 4 + 2 + l + 1) return -EC_GENERAL_FAILURE;
133 memcpy(namebuf, buf+4+1, l);
134 namebuf[l] = 0;
135 break;
136 default:
137 return -EC_ADDRESSTYPE_NOT_SUPPORTED;
139 unsigned short port;
140 port = (buf[minlen-2] << 8) | buf[minlen-1];
141 /* there's no suitable errorcode in rfc1928 for dns lookup failure */
142 if(resolve(namebuf, port, &remote)) return -EC_GENERAL_FAILURE;
143 int fd = socket(remote->ai_addr->sa_family, SOCK_STREAM, 0);
144 if(fd == -1) {
145 eval_errno:
146 if(fd != -1) close(fd);
147 freeaddrinfo(remote);
148 switch(errno) {
149 case ETIMEDOUT:
150 return -EC_TTL_EXPIRED;
151 case EPROTOTYPE:
152 case EPROTONOSUPPORT:
153 case EAFNOSUPPORT:
154 return -EC_ADDRESSTYPE_NOT_SUPPORTED;
155 case ECONNREFUSED:
156 return -EC_CONN_REFUSED;
157 case ENETDOWN:
158 case ENETUNREACH:
159 return -EC_NET_UNREACHABLE;
160 case EHOSTUNREACH:
161 return -EC_HOST_UNREACHABLE;
162 case EBADF:
163 default:
164 perror("socket/connect");
165 return -EC_GENERAL_FAILURE;
168 if(SOCKADDR_UNION_AF(&bind_addr) != AF_UNSPEC && bindtoip(fd, &bind_addr) == -1)
169 goto eval_errno;
170 if(connect(fd, remote->ai_addr, remote->ai_addrlen) == -1)
171 goto eval_errno;
173 freeaddrinfo(remote);
174 if(CONFIG_LOG) {
175 char clientname[256];
176 af = SOCKADDR_UNION_AF(&client->addr);
177 void *ipdata = SOCKADDR_UNION_ADDRESS(&client->addr);
178 inet_ntop(af, ipdata, clientname, sizeof clientname);
179 dolog("client[%d] %s: connected to %s:%d\n", client->fd, clientname, namebuf, port);
181 return fd;
184 static int is_authed(union sockaddr_union *client, union sockaddr_union *authedip) {
185 int af = SOCKADDR_UNION_AF(authedip);
186 if(af == SOCKADDR_UNION_AF(client)) {
187 size_t cmpbytes = af == AF_INET ? 4 : 16;
188 void *cmp1 = SOCKADDR_UNION_ADDRESS(client);
189 void *cmp2 = SOCKADDR_UNION_ADDRESS(authedip);
190 if(!memcmp(cmp1, cmp2, cmpbytes)) return 1;
192 return 0;
195 static enum authmethod check_auth_method(unsigned char *buf, size_t n, struct client*client) {
196 if(buf[0] != 5) return AM_INVALID;
197 size_t idx = 1;
198 if(idx >= n ) return AM_INVALID;
199 int n_methods = buf[idx];
200 idx++;
201 while(idx < n && n_methods > 0) {
202 if(buf[idx] == AM_NO_AUTH) {
203 if(!auth_user) return AM_NO_AUTH;
204 else if(auth_ips) {
205 size_t i;
206 int authed = 0;
207 pthread_mutex_lock(&auth_ips_mutex);
208 for(i=0;i<sblist_getsize(auth_ips);i++) {
209 if((authed = is_authed(&client->addr, sblist_get(auth_ips, i))))
210 break;
212 pthread_mutex_unlock(&auth_ips_mutex);
213 if(authed) return AM_NO_AUTH;
215 } else if(buf[idx] == AM_USERNAME) {
216 if(auth_user) return AM_USERNAME;
218 idx++;
219 n_methods--;
221 return AM_INVALID;
224 static void add_auth_ip(struct client*client) {
225 pthread_mutex_lock(&auth_ips_mutex);
226 sblist_add(auth_ips, &client->addr);
227 pthread_mutex_unlock(&auth_ips_mutex);
230 static void send_auth_response(int fd, int version, enum authmethod meth) {
231 unsigned char buf[2];
232 buf[0] = version;
233 buf[1] = meth;
234 write(fd, buf, 2);
237 static void send_error(int fd, enum errorcode ec) {
238 /* position 4 contains ATYP, the address type, which is the same as used in the connect
239 request. we're lazy and return always IPV4 address type in errors. */
240 char buf[10] = { 5, ec, 0, 1 /*AT_IPV4*/, 0,0,0,0, 0,0 };
241 write(fd, buf, 10);
244 static void copyloop(int fd1, int fd2) {
245 int maxfd = fd2;
246 if(fd1 > fd2) maxfd = fd1;
247 fd_set fdsc, fds;
248 FD_ZERO(&fdsc);
249 FD_SET(fd1, &fdsc);
250 FD_SET(fd2, &fdsc);
252 while(1) {
253 memcpy(&fds, &fdsc, sizeof(fds));
254 /* inactive connections are reaped after 15 min to free resources.
255 usually programs send keep-alive packets so this should only happen
256 when a connection is really unused. */
257 struct timeval timeout = {.tv_sec = 60*15, .tv_usec = 0};
258 switch(select(maxfd+1, &fds, 0, 0, &timeout)) {
259 case 0:
260 send_error(fd1, EC_TTL_EXPIRED);
261 return;
262 case -1:
263 if(errno == EINTR) continue;
264 else perror("select");
265 return;
267 int infd = FD_ISSET(fd1, &fds) ? fd1 : fd2;
268 int outfd = infd == fd2 ? fd1 : fd2;
269 char buf[1024];
270 ssize_t sent = 0, n = read(infd, buf, sizeof buf);
271 if(n <= 0) return;
272 while(sent < n) {
273 ssize_t m = write(outfd, buf+sent, n-sent);
274 if(m < 0) return;
275 sent += m;
280 static enum errorcode check_credentials(unsigned char* buf, size_t n) {
281 if(n < 5) return EC_GENERAL_FAILURE;
282 if(buf[0] != 1) return EC_GENERAL_FAILURE;
283 unsigned ulen, plen;
284 ulen=buf[1];
285 if(n < 2 + ulen + 2) return EC_GENERAL_FAILURE;
286 plen=buf[2+ulen];
287 if(n < 2 + ulen + 1 + plen) return EC_GENERAL_FAILURE;
288 char user[256], pass[256];
289 memcpy(user, buf+2, ulen);
290 memcpy(pass, buf+2+ulen+1, plen);
291 user[ulen] = 0;
292 pass[plen] = 0;
293 if(!strcmp(user, auth_user) && !strcmp(pass, auth_pass)) return EC_SUCCESS;
294 return EC_NOT_ALLOWED;
297 static void* clientthread(void *data) {
298 struct thread *t = data;
299 t->state = SS_1_CONNECTED;
300 unsigned char buf[1024];
301 ssize_t n;
302 int ret;
303 int remotefd = -1;
304 enum authmethod am;
305 while((n = recv(t->client.fd, buf, sizeof buf, 0)) > 0) {
306 switch(t->state) {
307 case SS_1_CONNECTED:
308 am = check_auth_method(buf, n, &t->client);
309 if(am == AM_NO_AUTH) t->state = SS_3_AUTHED;
310 else if (am == AM_USERNAME) t->state = SS_2_NEED_AUTH;
311 send_auth_response(t->client.fd, 5, am);
312 if(am == AM_INVALID) goto breakloop;
313 break;
314 case SS_2_NEED_AUTH:
315 ret = check_credentials(buf, n);
316 send_auth_response(t->client.fd, 1, ret);
317 if(ret != EC_SUCCESS)
318 goto breakloop;
319 t->state = SS_3_AUTHED;
320 if(auth_ips) add_auth_ip(&t->client);
321 break;
322 case SS_3_AUTHED:
323 ret = connect_socks_target(buf, n, &t->client);
324 if(ret < 0) {
325 send_error(t->client.fd, ret*-1);
326 goto breakloop;
328 remotefd = ret;
329 send_error(t->client.fd, EC_SUCCESS);
330 copyloop(t->client.fd, remotefd);
331 goto breakloop;
335 breakloop:
337 if(remotefd != -1)
338 close(remotefd);
340 close(t->client.fd);
341 t->done = 1;
343 return 0;
346 static void collect(sblist *threads) {
347 size_t i;
348 for(i=0;i<sblist_getsize(threads);) {
349 struct thread* thread = *((struct thread**)sblist_get(threads, i));
350 if(thread->done) {
351 pthread_join(thread->pt, 0);
352 sblist_delete(threads, i);
353 free(thread);
354 } else
355 i++;
359 static int usage(void) {
360 dprintf(2,
361 "MicroSocks SOCKS5 Server\n"
362 "------------------------\n"
363 "usage: microsocks -1 -i listenip -p port -u user -P password -b bindaddr\n"
364 "all arguments are optional.\n"
365 "by default listenip is 0.0.0.0 and port 1080.\n\n"
366 "option -b specifies which ip outgoing connections are bound to\n"
367 "option -1 activates auth_once mode: once a specific ip address\n"
368 "authed successfully with user/pass, it is added to a whitelist\n"
369 "and may use the proxy without auth.\n"
370 "this is handy for programs like firefox that don't support\n"
371 "user/pass auth. for it to work you'd basically make one connection\n"
372 "with another program that supports it, and then you can use firefox too.\n"
374 return 1;
377 /* prevent username and password from showing up in top. */
378 static void zero_arg(char *s) {
379 size_t i, l = strlen(s);
380 for(i=0;i<l;i++) s[i] = 0;
383 int main(int argc, char** argv) {
384 int ch;
385 const char *listenip = "0.0.0.0";
386 unsigned port = 1080;
387 while((ch = getopt(argc, argv, ":1b:i:p:u:P:")) != -1) {
388 switch(ch) {
389 case '1':
390 auth_ips = sblist_new(sizeof(union sockaddr_union), 8);
391 break;
392 case 'b':
393 resolve_sa(optarg, 0, &bind_addr);
394 break;
395 case 'u':
396 auth_user = strdup(optarg);
397 zero_arg(optarg);
398 break;
399 case 'P':
400 auth_pass = strdup(optarg);
401 zero_arg(optarg);
402 break;
403 case 'i':
404 listenip = optarg;
405 break;
406 case 'p':
407 port = atoi(optarg);
408 break;
409 case ':':
410 dprintf(2, "error: option -%c requires an operand\n", optopt);
411 /* fall through */
412 case '?':
413 return usage();
416 if((auth_user && !auth_pass) || (!auth_user && auth_pass)) {
417 dprintf(2, "error: user and pass must be used together\n");
418 return 1;
420 if(auth_ips && !auth_pass) {
421 dprintf(2, "error: auth-once option must be used together with user/pass\n");
422 return 1;
424 signal(SIGPIPE, SIG_IGN);
425 struct server s;
426 sblist *threads = sblist_new(sizeof (struct thread*), 8);
427 if(server_setup(&s, listenip, port)) {
428 perror("server_setup");
429 return 1;
431 server = &s;
433 while(1) {
434 collect(threads);
435 struct client c;
436 struct thread *curr = malloc(sizeof (struct thread));
437 if(!curr) goto oom;
438 curr->done = 0;
439 if(server_waitclient(&s, &c)) continue;
440 curr->client = c;
441 if(!sblist_add(threads, &curr)) {
442 close(curr->client.fd);
443 free(curr);
444 oom:
445 dolog("rejecting connection due to OOM\n");
446 usleep(16); /* prevent 100% CPU usage in OOM situation */
447 continue;
449 pthread_attr_t *a = 0, attr;
450 if(pthread_attr_init(&attr) == 0) {
451 a = &attr;
452 pthread_attr_setstacksize(a, THREAD_STACK_SIZE);
454 if(pthread_create(&curr->pt, a, clientthread, curr) != 0)
455 dolog("pthread_create failed. OOM?\n");
456 if(a) pthread_attr_destroy(&attr);