fix rfc1929 user/pass auth subnegotation response version field
[rofl0r-microsocks.git] / sockssrv.c
blob91a8858d09fed03eb37bd28e1985aa163f9ffdd3
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 #if !defined(PTHREAD_STACK_MIN) || defined(__APPLE__)
44 /* MAC says its min is 8KB, but then crashes in our face. thx hunkOLard */
45 #define PTHREAD_STACK_MIN 64*1024
46 #endif
48 static const char* auth_user;
49 static const char* auth_pass;
50 static sblist* auth_ips;
51 static pthread_mutex_t auth_ips_mutex = PTHREAD_MUTEX_INITIALIZER;
52 static const struct server* server;
53 static int bind_mode;
55 enum socksstate {
56 SS_1_CONNECTED,
57 SS_2_NEED_AUTH, /* skipped if NO_AUTH method supported */
58 SS_3_AUTHED,
61 enum authmethod {
62 AM_NO_AUTH = 0,
63 AM_GSSAPI = 1,
64 AM_USERNAME = 2,
65 AM_INVALID = 0xFF
68 enum errorcode {
69 EC_SUCCESS = 0,
70 EC_GENERAL_FAILURE = 1,
71 EC_NOT_ALLOWED = 2,
72 EC_NET_UNREACHABLE = 3,
73 EC_HOST_UNREACHABLE = 4,
74 EC_CONN_REFUSED = 5,
75 EC_TTL_EXPIRED = 6,
76 EC_COMMAND_NOT_SUPPORTED = 7,
77 EC_ADDRESSTYPE_NOT_SUPPORTED = 8,
80 struct thread {
81 pthread_t pt;
82 struct client client;
83 enum socksstate state;
84 volatile int done;
87 #ifndef CONFIG_LOG
88 #define CONFIG_LOG 1
89 #endif
90 #if CONFIG_LOG
91 /* we log to stderr because it's not using line buffering, i.e. malloc which would need
92 locking when called from different threads. for the same reason we use dprintf,
93 which writes directly to an fd. */
94 #define dolog(...) dprintf(2, __VA_ARGS__)
95 #else
96 static void dolog(const char* fmt, ...) { }
97 #endif
99 static int connect_socks_target(unsigned char *buf, size_t n, struct client *client) {
100 if(n < 5) return -EC_GENERAL_FAILURE;
101 if(buf[0] != 5) return -EC_GENERAL_FAILURE;
102 if(buf[1] != 1) return -EC_COMMAND_NOT_SUPPORTED; /* we support only CONNECT method */
103 if(buf[2] != 0) return -EC_GENERAL_FAILURE; /* malformed packet */
105 int af = AF_INET;
106 size_t minlen = 4 + 4 + 2, l;
107 char namebuf[256];
108 struct addrinfo* remote;
110 switch(buf[3]) {
111 case 4: /* ipv6 */
112 af = AF_INET6;
113 minlen = 4 + 2 + 16;
114 /* fall through */
115 case 1: /* ipv4 */
116 if(n < minlen) return -EC_GENERAL_FAILURE;
117 if(namebuf != inet_ntop(af, buf+4, namebuf, sizeof namebuf))
118 return -EC_GENERAL_FAILURE; /* malformed or too long addr */
119 break;
120 case 3: /* dns name */
121 l = buf[4];
122 minlen = 4 + 2 + l + 1;
123 if(n < 4 + 2 + l + 1) return -EC_GENERAL_FAILURE;
124 memcpy(namebuf, buf+4+1, l);
125 namebuf[l] = 0;
126 break;
127 default:
128 return -EC_ADDRESSTYPE_NOT_SUPPORTED;
130 unsigned short port;
131 port = (buf[minlen-2] << 8) | buf[minlen-1];
132 if(resolve(namebuf, port, &remote)) return -9;
133 int fd = socket(remote->ai_addr->sa_family, SOCK_STREAM, 0);
134 if(fd == -1) {
135 eval_errno:
136 freeaddrinfo(remote);
137 switch(errno) {
138 case EPROTOTYPE:
139 case EPROTONOSUPPORT:
140 case EAFNOSUPPORT:
141 return -EC_ADDRESSTYPE_NOT_SUPPORTED;
142 case ECONNREFUSED:
143 return -EC_CONN_REFUSED;
144 case ENETDOWN:
145 case ENETUNREACH:
146 return -EC_NET_UNREACHABLE;
147 case EHOSTUNREACH:
148 return -EC_HOST_UNREACHABLE;
149 case EBADF:
150 default:
151 perror("socket/connect");
152 return -EC_GENERAL_FAILURE;
155 if(bind_mode && server_bindtoip(server, fd) == -1)
156 goto eval_errno;
157 if(connect(fd, remote->ai_addr, remote->ai_addrlen) == -1)
158 goto eval_errno;
160 freeaddrinfo(remote);
161 if(CONFIG_LOG) {
162 char clientname[256];
163 af = client->addr.v4.sin_family;
164 void *ipdata = af == AF_INET ? (void*)&client->addr.v4.sin_addr : (void*)&client->addr.v6.sin6_addr;
165 inet_ntop(af, ipdata, clientname, sizeof clientname);
166 dolog("client[%d] %s: connected to %s:%d\n", client->fd, clientname, namebuf, port);
168 return fd;
171 static int is_authed(union sockaddr_union *client, union sockaddr_union *authedip) {
172 if(authedip->v4.sin_family == client->v4.sin_family) {
173 int af = authedip->v4.sin_family;
174 size_t cmpbytes = af == AF_INET ? 4 : 16;
175 void *cmp1 = af == AF_INET ? (void*)&client->v4.sin_addr : (void*)&client->v6.sin6_addr;
176 void *cmp2 = af == AF_INET ? (void*)&authedip->v4.sin_addr : (void*)&authedip->v6.sin6_addr;
177 if(!memcmp(cmp1, cmp2, cmpbytes)) return 1;
179 return 0;
182 static enum authmethod check_auth_method(unsigned char *buf, size_t n, struct client*client) {
183 if(buf[0] != 5) return AM_INVALID;
184 size_t idx = 1;
185 if(idx >= n ) return AM_INVALID;
186 int n_methods = buf[idx];
187 idx++;
188 while(idx < n && n_methods > 0) {
189 if(buf[idx] == AM_NO_AUTH) {
190 if(!auth_user) return AM_NO_AUTH;
191 else if(auth_ips) {
192 size_t i;
193 int authed = 0;
194 pthread_mutex_lock(&auth_ips_mutex);
195 for(i=0;i<sblist_getsize(auth_ips);i++) {
196 if((authed = is_authed(&client->addr, sblist_get(auth_ips, i))))
197 break;
199 pthread_mutex_unlock(&auth_ips_mutex);
200 if(authed) return AM_NO_AUTH;
202 } else if(buf[idx] == AM_USERNAME) {
203 if(auth_user) return AM_USERNAME;
205 idx++;
206 n_methods--;
208 return AM_INVALID;
211 static void add_auth_ip(struct client*client) {
212 pthread_mutex_lock(&auth_ips_mutex);
213 sblist_add(auth_ips, &client->addr);
214 pthread_mutex_unlock(&auth_ips_mutex);
217 static void send_auth_response(int fd, int version, enum authmethod meth) {
218 unsigned char buf[2];
219 buf[0] = version;
220 buf[1] = meth;
221 write(fd, buf, 2);
224 static void send_error(int fd, enum errorcode ec) {
225 /* position 4 contains ATYP, the address type, which is the same as used in the connect
226 request. we're lazy and return always IPV4 address type in errors. */
227 char buf[10] = { 5, ec, 0, 1 /*AT_IPV4*/, 0,0,0,0, 0,0 };
228 write(fd, buf, 10);
231 static void copyloop(int fd1, int fd2) {
232 int maxfd = fd2;
233 if(fd1 > fd2) maxfd = fd1;
234 fd_set fdsc, fds;
235 FD_ZERO(&fdsc);
236 FD_SET(fd1, &fdsc);
237 FD_SET(fd2, &fdsc);
239 while(1) {
240 memcpy(&fds, &fdsc, sizeof(fds));
241 /* inactive connections are reaped after 15 min to free resources.
242 usually programs send keep-alive packets so this should only happen
243 when a connection is really unused. */
244 struct timeval timeout = {.tv_sec = 60*15, .tv_usec = 0};
245 switch(select(maxfd+1, &fds, 0, 0, &timeout)) {
246 case 0:
247 send_error(fd1, EC_TTL_EXPIRED);
248 return;
249 case -1:
250 if(errno == EINTR) continue;
251 else perror("select");
252 return;
254 int infd = FD_ISSET(fd1, &fds) ? fd1 : fd2;
255 int outfd = infd == fd2 ? fd1 : fd2;
256 char buf[1024];
257 ssize_t sent = 0, n = read(infd, buf, sizeof buf);
258 if(n <= 0) return;
259 while(sent < n) {
260 ssize_t m = write(outfd, buf+sent, n-sent);
261 if(m < 0) return;
262 sent += m;
267 static enum errorcode check_credentials(unsigned char* buf, size_t n) {
268 if(n < 5) return EC_GENERAL_FAILURE;
269 if(buf[0] != 1) return EC_GENERAL_FAILURE;
270 unsigned ulen, plen;
271 ulen=buf[1];
272 if(n < 2 + ulen + 2) return EC_GENERAL_FAILURE;
273 plen=buf[2+ulen];
274 if(n < 2 + ulen + 1 + plen) return EC_GENERAL_FAILURE;
275 char user[256], pass[256];
276 memcpy(user, buf+2, ulen);
277 memcpy(pass, buf+2+ulen+1, plen);
278 if(!strcmp(user, auth_user) && !strcmp(pass, auth_pass)) return EC_SUCCESS;
279 return EC_NOT_ALLOWED;
282 static void* clientthread(void *data) {
283 struct thread *t = data;
284 t->state = SS_1_CONNECTED;
285 unsigned char buf[1024];
286 ssize_t n;
287 int ret;
288 int remotefd = -1;
289 enum authmethod am;
290 while((n = recv(t->client.fd, buf, sizeof buf, 0)) > 0) {
291 switch(t->state) {
292 case SS_1_CONNECTED:
293 am = check_auth_method(buf, n, &t->client);
294 if(am == AM_NO_AUTH) t->state = SS_3_AUTHED;
295 else if (am == AM_USERNAME) t->state = SS_2_NEED_AUTH;
296 send_auth_response(t->client.fd, 5, am);
297 if(am == AM_INVALID) goto breakloop;
298 break;
299 case SS_2_NEED_AUTH:
300 ret = check_credentials(buf, n);
301 send_auth_response(t->client.fd, 1, ret);
302 if(ret != EC_SUCCESS)
303 goto breakloop;
304 t->state = SS_3_AUTHED;
305 if(auth_ips) add_auth_ip(&t->client);
306 break;
307 case SS_3_AUTHED:
308 ret = connect_socks_target(buf, n, &t->client);
309 if(ret < 0) {
310 send_error(t->client.fd, ret*-1);
311 goto breakloop;
313 remotefd = ret;
314 send_error(t->client.fd, EC_SUCCESS);
315 copyloop(t->client.fd, remotefd);
316 goto breakloop;
320 breakloop:
322 if(remotefd != -1)
323 close(remotefd);
325 close(t->client.fd);
326 t->done = 1;
328 return 0;
331 static void collect(sblist *threads) {
332 size_t i;
333 for(i=0;i<sblist_getsize(threads);) {
334 struct thread* thread = *((struct thread**)sblist_get(threads, i));
335 if(thread->done) {
336 pthread_join(thread->pt, 0);
337 sblist_delete(threads, i);
338 free(thread);
339 } else
340 i++;
344 static int usage(void) {
345 dprintf(2,
346 "MicroSocks SOCKS5 Server\n"
347 "------------------------\n"
348 "usage: microsocks -1 -b -i listenip -p port -u user -P password\n"
349 "all arguments are optional.\n"
350 "by default listenip is 0.0.0.0 and port 1080.\n\n"
351 "option -b forces outgoing connections to be bound to the ip specified with -i\n"
352 "option -1 activates auth_once mode: once a specific ip address\n"
353 "authed successfully with user/pass, it is added to a whitelist\n"
354 "and may use the proxy without auth.\n"
355 "this is handy for programs like firefox that don't support\n"
356 "user/pass auth. for it to work you'd basically make one connection\n"
357 "with another program that supports it, and then you can use firefox too.\n"
359 return 1;
362 /* prevent username and password from showing up in top. */
363 static void zero_arg(char *s) {
364 size_t i, l = strlen(s);
365 for(i=0;i<l;i++) s[i] = 0;
368 int main(int argc, char** argv) {
369 int c;
370 const char *listenip = "0.0.0.0";
371 unsigned port = 1080;
372 while((c = getopt(argc, argv, ":1bi:p:u:P:")) != -1) {
373 switch(c) {
374 case '1':
375 auth_ips = sblist_new(sizeof(union sockaddr_union), 8);
376 break;
377 case 'b':
378 bind_mode = 1;
379 break;
380 case 'u':
381 auth_user = strdup(optarg);
382 zero_arg(optarg);
383 break;
384 case 'P':
385 auth_pass = strdup(optarg);
386 zero_arg(optarg);
387 break;
388 case 'i':
389 listenip = optarg;
390 break;
391 case 'p':
392 port = atoi(optarg);
393 break;
394 case ':':
395 dprintf(2, "error: option -%c requires an operand\n", optopt);
396 case '?':
397 return usage();
400 if((auth_user && !auth_pass) || (!auth_user && auth_pass)) {
401 dprintf(2, "error: user and pass must be used together\n");
402 return 1;
404 if(auth_ips && !auth_pass) {
405 dprintf(2, "error: auth-once option must be used together with user/pass\n");
406 return 1;
408 signal(SIGPIPE, SIG_IGN);
409 struct server s;
410 sblist *threads = sblist_new(sizeof (struct thread*), 8);
411 if(server_setup(&s, listenip, port)) {
412 perror("server_setup");
413 return 1;
415 server = &s;
416 size_t stacksz = MAX(8192, PTHREAD_STACK_MIN); /* 4KB for us, 4KB for libc */
418 while(1) {
419 collect(threads);
420 struct client c;
421 struct thread *curr = malloc(sizeof (struct thread));
422 if(!curr) goto oom;
423 curr->done = 0;
424 if(server_waitclient(&s, &c)) continue;
425 curr->client = c;
426 if(!sblist_add(threads, &curr)) {
427 close(curr->client.fd);
428 free(curr);
429 oom:
430 dolog("rejecting connection due to OOM\n");
431 usleep(16); /* prevent 100% CPU usage in OOM situation */
432 continue;
434 pthread_attr_t *a = 0, attr;
435 if(pthread_attr_init(&attr) == 0) {
436 a = &attr;
437 pthread_attr_setstacksize(a, stacksz);
439 if(pthread_create(&curr->pt, a, clientthread, curr) != 0)
440 dolog("pthread_create failed. OOM?\n");
441 if(a) pthread_attr_destroy(&attr);