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