fix thread stack size for solaris
[rofl0r-microsocks.git] / sockssrv.c
blobbc919f46e375416c656bbffd38f8886bfb3d81a7
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 <poll.h>
33 #include <arpa/inet.h>
34 #include <errno.h>
35 #include <limits.h>
36 #include "server.h"
37 #include "sblist.h"
39 /* timeout in microseconds on resource exhaustion to prevent excessive
40 cpu usage. */
41 #ifndef FAILURE_TIMEOUT
42 #define FAILURE_TIMEOUT 64
43 #endif
45 #ifndef MAX
46 #define MAX(x, y) ((x) > (y) ? (x) : (y))
47 #endif
49 #ifdef PTHREAD_STACK_MIN
50 #define THREAD_STACK_SIZE MAX(8*1024, PTHREAD_STACK_MIN)
51 #else
52 #define THREAD_STACK_SIZE 64*1024
53 #endif
55 #if defined(__APPLE__)
56 #undef THREAD_STACK_SIZE
57 #define THREAD_STACK_SIZE 64*1024
58 #elif defined(__GLIBC__) || defined(__FreeBSD__) || defined(__sun__)
59 #undef THREAD_STACK_SIZE
60 #define THREAD_STACK_SIZE 32*1024
61 #endif
63 static const char* auth_user;
64 static const char* auth_pass;
65 static sblist* auth_ips;
66 static pthread_rwlock_t auth_ips_lock = PTHREAD_RWLOCK_INITIALIZER;
67 static const struct server* server;
68 static union sockaddr_union bind_addr = {.v4.sin_family = AF_UNSPEC};
70 enum socksstate {
71 SS_1_CONNECTED,
72 SS_2_NEED_AUTH, /* skipped if NO_AUTH method supported */
73 SS_3_AUTHED,
76 enum authmethod {
77 AM_NO_AUTH = 0,
78 AM_GSSAPI = 1,
79 AM_USERNAME = 2,
80 AM_INVALID = 0xFF
83 enum errorcode {
84 EC_SUCCESS = 0,
85 EC_GENERAL_FAILURE = 1,
86 EC_NOT_ALLOWED = 2,
87 EC_NET_UNREACHABLE = 3,
88 EC_HOST_UNREACHABLE = 4,
89 EC_CONN_REFUSED = 5,
90 EC_TTL_EXPIRED = 6,
91 EC_COMMAND_NOT_SUPPORTED = 7,
92 EC_ADDRESSTYPE_NOT_SUPPORTED = 8,
95 struct thread {
96 pthread_t pt;
97 struct client client;
98 enum socksstate state;
99 volatile int done;
102 #ifndef CONFIG_LOG
103 #define CONFIG_LOG 1
104 #endif
105 #if CONFIG_LOG
106 /* we log to stderr because it's not using line buffering, i.e. malloc which would need
107 locking when called from different threads. for the same reason we use dprintf,
108 which writes directly to an fd. */
109 #define dolog(...) dprintf(2, __VA_ARGS__)
110 #else
111 static void dolog(const char* fmt, ...) { }
112 #endif
114 static struct addrinfo* addr_choose(struct addrinfo* list, union sockaddr_union* bind_addr) {
115 int af = SOCKADDR_UNION_AF(bind_addr);
116 if(af == AF_UNSPEC) return list;
117 struct addrinfo* p;
118 for(p=list; p; p=p->ai_next)
119 if(p->ai_family == af) return p;
120 return list;
123 static int connect_socks_target(unsigned char *buf, size_t n, struct client *client) {
124 if(n < 5) return -EC_GENERAL_FAILURE;
125 if(buf[0] != 5) return -EC_GENERAL_FAILURE;
126 if(buf[1] != 1) return -EC_COMMAND_NOT_SUPPORTED; /* we support only CONNECT method */
127 if(buf[2] != 0) return -EC_GENERAL_FAILURE; /* malformed packet */
129 int af = AF_INET;
130 size_t minlen = 4 + 4 + 2, l;
131 char namebuf[256];
132 struct addrinfo* remote;
134 switch(buf[3]) {
135 case 4: /* ipv6 */
136 af = AF_INET6;
137 minlen = 4 + 2 + 16;
138 /* fall through */
139 case 1: /* ipv4 */
140 if(n < minlen) return -EC_GENERAL_FAILURE;
141 if(namebuf != inet_ntop(af, buf+4, namebuf, sizeof namebuf))
142 return -EC_GENERAL_FAILURE; /* malformed or too long addr */
143 break;
144 case 3: /* dns name */
145 l = buf[4];
146 minlen = 4 + 2 + l + 1;
147 if(n < 4 + 2 + l + 1) return -EC_GENERAL_FAILURE;
148 memcpy(namebuf, buf+4+1, l);
149 namebuf[l] = 0;
150 break;
151 default:
152 return -EC_ADDRESSTYPE_NOT_SUPPORTED;
154 unsigned short port;
155 port = (buf[minlen-2] << 8) | buf[minlen-1];
156 /* there's no suitable errorcode in rfc1928 for dns lookup failure */
157 if(resolve(namebuf, port, &remote)) return -EC_GENERAL_FAILURE;
158 struct addrinfo* raddr = addr_choose(remote, &bind_addr);
159 int fd = socket(raddr->ai_family, SOCK_STREAM, 0);
160 if(fd == -1) {
161 eval_errno:
162 if(fd != -1) close(fd);
163 freeaddrinfo(remote);
164 switch(errno) {
165 case ETIMEDOUT:
166 return -EC_TTL_EXPIRED;
167 case EPROTOTYPE:
168 case EPROTONOSUPPORT:
169 case EAFNOSUPPORT:
170 return -EC_ADDRESSTYPE_NOT_SUPPORTED;
171 case ECONNREFUSED:
172 return -EC_CONN_REFUSED;
173 case ENETDOWN:
174 case ENETUNREACH:
175 return -EC_NET_UNREACHABLE;
176 case EHOSTUNREACH:
177 return -EC_HOST_UNREACHABLE;
178 case EBADF:
179 default:
180 perror("socket/connect");
181 return -EC_GENERAL_FAILURE;
184 if(SOCKADDR_UNION_AF(&bind_addr) == raddr->ai_family &&
185 bindtoip(fd, &bind_addr) == -1)
186 goto eval_errno;
187 if(connect(fd, raddr->ai_addr, raddr->ai_addrlen) == -1)
188 goto eval_errno;
190 freeaddrinfo(remote);
191 if(CONFIG_LOG) {
192 char clientname[256];
193 af = SOCKADDR_UNION_AF(&client->addr);
194 void *ipdata = SOCKADDR_UNION_ADDRESS(&client->addr);
195 inet_ntop(af, ipdata, clientname, sizeof clientname);
196 dolog("client[%d] %s: connected to %s:%d\n", client->fd, clientname, namebuf, port);
198 return fd;
201 static int is_authed(union sockaddr_union *client, union sockaddr_union *authedip) {
202 int af = SOCKADDR_UNION_AF(authedip);
203 if(af == SOCKADDR_UNION_AF(client)) {
204 size_t cmpbytes = af == AF_INET ? 4 : 16;
205 void *cmp1 = SOCKADDR_UNION_ADDRESS(client);
206 void *cmp2 = SOCKADDR_UNION_ADDRESS(authedip);
207 if(!memcmp(cmp1, cmp2, cmpbytes)) return 1;
209 return 0;
212 static int is_in_authed_list(union sockaddr_union *caddr) {
213 size_t i;
214 for(i=0;i<sblist_getsize(auth_ips);i++)
215 if(is_authed(caddr, sblist_get(auth_ips, i)))
216 return 1;
217 return 0;
220 static void add_auth_ip(union sockaddr_union *caddr) {
221 sblist_add(auth_ips, caddr);
224 static enum authmethod check_auth_method(unsigned char *buf, size_t n, struct client*client) {
225 if(buf[0] != 5) return AM_INVALID;
226 size_t idx = 1;
227 if(idx >= n ) return AM_INVALID;
228 int n_methods = buf[idx];
229 idx++;
230 while(idx < n && n_methods > 0) {
231 if(buf[idx] == AM_NO_AUTH) {
232 if(!auth_user) return AM_NO_AUTH;
233 else if(auth_ips) {
234 int authed = 0;
235 if(pthread_rwlock_rdlock(&auth_ips_lock) == 0) {
236 authed = is_in_authed_list(&client->addr);
237 pthread_rwlock_unlock(&auth_ips_lock);
239 if(authed) return AM_NO_AUTH;
241 } else if(buf[idx] == AM_USERNAME) {
242 if(auth_user) return AM_USERNAME;
244 idx++;
245 n_methods--;
247 return AM_INVALID;
250 static void send_auth_response(int fd, int version, enum authmethod meth) {
251 unsigned char buf[2];
252 buf[0] = version;
253 buf[1] = meth;
254 write(fd, buf, 2);
257 static void send_error(int fd, enum errorcode ec) {
258 /* position 4 contains ATYP, the address type, which is the same as used in the connect
259 request. we're lazy and return always IPV4 address type in errors. */
260 char buf[10] = { 5, ec, 0, 1 /*AT_IPV4*/, 0,0,0,0, 0,0 };
261 write(fd, buf, 10);
264 static void copyloop(int fd1, int fd2) {
265 struct pollfd fds[2] = {
266 [0] = {.fd = fd1, .events = POLLIN},
267 [1] = {.fd = fd2, .events = POLLIN},
270 while(1) {
271 /* inactive connections are reaped after 15 min to free resources.
272 usually programs send keep-alive packets so this should only happen
273 when a connection is really unused. */
274 switch(poll(fds, 2, 60*15*1000)) {
275 case 0:
276 return;
277 case -1:
278 if(errno == EINTR || errno == EAGAIN) continue;
279 else perror("poll");
280 return;
282 int infd = (fds[0].revents & POLLIN) ? fd1 : fd2;
283 int outfd = infd == fd2 ? fd1 : fd2;
284 char buf[1024];
285 ssize_t sent = 0, n = read(infd, buf, sizeof buf);
286 if(n <= 0) return;
287 while(sent < n) {
288 ssize_t m = write(outfd, buf+sent, n-sent);
289 if(m < 0) return;
290 sent += m;
295 static enum errorcode check_credentials(unsigned char* buf, size_t n) {
296 if(n < 5) return EC_GENERAL_FAILURE;
297 if(buf[0] != 1) return EC_GENERAL_FAILURE;
298 unsigned ulen, plen;
299 ulen=buf[1];
300 if(n < 2 + ulen + 2) return EC_GENERAL_FAILURE;
301 plen=buf[2+ulen];
302 if(n < 2 + ulen + 1 + plen) return EC_GENERAL_FAILURE;
303 char user[256], pass[256];
304 memcpy(user, buf+2, ulen);
305 memcpy(pass, buf+2+ulen+1, plen);
306 user[ulen] = 0;
307 pass[plen] = 0;
308 if(!strcmp(user, auth_user) && !strcmp(pass, auth_pass)) return EC_SUCCESS;
309 return EC_NOT_ALLOWED;
312 static void* clientthread(void *data) {
313 struct thread *t = data;
314 t->state = SS_1_CONNECTED;
315 unsigned char buf[1024];
316 ssize_t n;
317 int ret;
318 int remotefd = -1;
319 enum authmethod am;
320 while((n = recv(t->client.fd, buf, sizeof buf, 0)) > 0) {
321 switch(t->state) {
322 case SS_1_CONNECTED:
323 am = check_auth_method(buf, n, &t->client);
324 if(am == AM_NO_AUTH) t->state = SS_3_AUTHED;
325 else if (am == AM_USERNAME) t->state = SS_2_NEED_AUTH;
326 send_auth_response(t->client.fd, 5, am);
327 if(am == AM_INVALID) goto breakloop;
328 break;
329 case SS_2_NEED_AUTH:
330 ret = check_credentials(buf, n);
331 send_auth_response(t->client.fd, 1, ret);
332 if(ret != EC_SUCCESS)
333 goto breakloop;
334 t->state = SS_3_AUTHED;
335 if(auth_ips && !pthread_rwlock_wrlock(&auth_ips_lock)) {
336 if(!is_in_authed_list(&t->client.addr))
337 add_auth_ip(&t->client.addr);
338 pthread_rwlock_unlock(&auth_ips_lock);
340 break;
341 case SS_3_AUTHED:
342 ret = connect_socks_target(buf, n, &t->client);
343 if(ret < 0) {
344 send_error(t->client.fd, ret*-1);
345 goto breakloop;
347 remotefd = ret;
348 send_error(t->client.fd, EC_SUCCESS);
349 copyloop(t->client.fd, remotefd);
350 goto breakloop;
354 breakloop:
356 if(remotefd != -1)
357 close(remotefd);
359 close(t->client.fd);
360 t->done = 1;
362 return 0;
365 static void collect(sblist *threads) {
366 size_t i;
367 for(i=0;i<sblist_getsize(threads);) {
368 struct thread* thread = *((struct thread**)sblist_get(threads, i));
369 if(thread->done) {
370 pthread_join(thread->pt, 0);
371 sblist_delete(threads, i);
372 free(thread);
373 } else
374 i++;
378 static int usage(void) {
379 dprintf(2,
380 "MicroSocks SOCKS5 Server\n"
381 "------------------------\n"
382 "usage: microsocks -1 -i listenip -p port -u user -P password -b bindaddr\n"
383 "all arguments are optional.\n"
384 "by default listenip is 0.0.0.0 and port 1080.\n\n"
385 "option -b specifies which ip outgoing connections are bound to\n"
386 "option -1 activates auth_once mode: once a specific ip address\n"
387 "authed successfully with user/pass, it is added to a whitelist\n"
388 "and may use the proxy without auth.\n"
389 "this is handy for programs like firefox that don't support\n"
390 "user/pass auth. for it to work you'd basically make one connection\n"
391 "with another program that supports it, and then you can use firefox too.\n"
393 return 1;
396 /* prevent username and password from showing up in top. */
397 static void zero_arg(char *s) {
398 size_t i, l = strlen(s);
399 for(i=0;i<l;i++) s[i] = 0;
402 int main(int argc, char** argv) {
403 int ch;
404 const char *listenip = "0.0.0.0";
405 unsigned port = 1080;
406 while((ch = getopt(argc, argv, ":1b:i:p:u:P:")) != -1) {
407 switch(ch) {
408 case '1':
409 auth_ips = sblist_new(sizeof(union sockaddr_union), 8);
410 break;
411 case 'b':
412 resolve_sa(optarg, 0, &bind_addr);
413 break;
414 case 'u':
415 auth_user = strdup(optarg);
416 zero_arg(optarg);
417 break;
418 case 'P':
419 auth_pass = strdup(optarg);
420 zero_arg(optarg);
421 break;
422 case 'i':
423 listenip = optarg;
424 break;
425 case 'p':
426 port = atoi(optarg);
427 break;
428 case ':':
429 dprintf(2, "error: option -%c requires an operand\n", optopt);
430 /* fall through */
431 case '?':
432 return usage();
435 if((auth_user && !auth_pass) || (!auth_user && auth_pass)) {
436 dprintf(2, "error: user and pass must be used together\n");
437 return 1;
439 if(auth_ips && !auth_pass) {
440 dprintf(2, "error: auth-once option must be used together with user/pass\n");
441 return 1;
443 signal(SIGPIPE, SIG_IGN);
444 struct server s;
445 sblist *threads = sblist_new(sizeof (struct thread*), 8);
446 if(server_setup(&s, listenip, port)) {
447 perror("server_setup");
448 return 1;
450 server = &s;
452 while(1) {
453 collect(threads);
454 struct client c;
455 struct thread *curr = malloc(sizeof (struct thread));
456 if(!curr) goto oom;
457 curr->done = 0;
458 if(server_waitclient(&s, &c)) {
459 dolog("failed to accept connection\n");
460 free(curr);
461 usleep(FAILURE_TIMEOUT);
462 continue;
464 curr->client = c;
465 if(!sblist_add(threads, &curr)) {
466 close(curr->client.fd);
467 free(curr);
468 oom:
469 dolog("rejecting connection due to OOM\n");
470 usleep(FAILURE_TIMEOUT); /* prevent 100% CPU usage in OOM situation */
471 continue;
473 pthread_attr_t *a = 0, attr;
474 if(pthread_attr_init(&attr) == 0) {
475 a = &attr;
476 pthread_attr_setstacksize(a, THREAD_STACK_SIZE);
478 if(pthread_create(&curr->pt, a, clientthread, curr) != 0)
479 dolog("pthread_create failed. OOM?\n");
480 if(a) pthread_attr_destroy(&attr);