and even more GLIBC feature test fun
[rofl0r-microsocks.git] / sockssrv.c
blobe26c04d6a8b2bf14a49b46a2ed082e3d52ad6304
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 #include <stdlib.h>
27 #include <string.h>
28 #include <stdio.h>
29 #include <pthread.h>
30 #include <signal.h>
31 #include <sys/select.h>
32 #include <arpa/inet.h>
33 #include <errno.h>
34 #include "server.h"
35 #include "sblist.h"
37 static const char* auth_user;
38 static const char* auth_pass;
39 static sblist* auth_ips;
40 static pthread_mutex_t auth_ips_mutex = PTHREAD_MUTEX_INITIALIZER;
42 enum socksstate {
43 SS_1_CONNECTED,
44 SS_2_NEED_AUTH, /* skipped if NO_AUTH method supported */
45 SS_3_AUTHED,
48 enum authmethod {
49 AM_NO_AUTH = 0,
50 AM_GSSAPI = 1,
51 AM_USERNAME = 2,
52 AM_INVALID = 0xFF
55 enum errorcode {
56 EC_SUCCESS = 0,
57 EC_GENERAL_FAILURE = 1,
58 EC_NOT_ALLOWED = 2,
59 EC_NET_UNREACHABLE = 3,
60 EC_HOST_UNREACHABLE = 4,
61 EC_CONN_REFUSED = 5,
62 EC_TTL_EXPIRED = 6,
63 EC_COMMAND_NOT_SUPPORTED = 7,
64 EC_ADDRESSTYPE_NOT_SUPPORTED = 8,
67 struct thread {
68 pthread_t pt;
69 struct client client;
70 enum socksstate state;
71 volatile int done;
74 #ifndef CONFIG_LOG
75 #define CONFIG_LOG 1
76 #endif
77 #if CONFIG_LOG
78 /* we log to stderr because it's not using line buffering, i.e. malloc which would need
79 locking when called from different threads. for the same reason we use dprintf,
80 which writes directly to an fd. */
81 #define dolog(...) dprintf(2, __VA_ARGS__)
82 #else
83 static void dolog(const char* fmt, ...) { }
84 #endif
86 static int connect_socks_target(unsigned char *buf, size_t n, struct client *client) {
87 if(n < 5) return -EC_GENERAL_FAILURE;
88 if(buf[0] != 5) return -EC_GENERAL_FAILURE;
89 if(buf[1] != 1) return -EC_COMMAND_NOT_SUPPORTED; /* we support only CONNECT method */
90 if(buf[2] != 0) return -EC_GENERAL_FAILURE; /* malformed packet */
92 int af = AF_INET;
93 size_t minlen = 4 + 4 + 2, l;
94 char namebuf[256];
95 struct addrinfo* remote;
97 switch(buf[3]) {
98 case 4: /* ipv6 */
99 af = AF_INET6;
100 minlen = 4 + 2 + 16;
101 /* fall through */
102 case 1: /* ipv4 */
103 if(n < minlen) return -EC_GENERAL_FAILURE;
104 if(namebuf != inet_ntop(af, buf+4, namebuf, sizeof namebuf))
105 return -EC_GENERAL_FAILURE; /* malformed or too long addr */
106 break;
107 case 3: /* dns name */
108 l = buf[4];
109 minlen = 4 + 2 + l + 1;
110 if(n < 4 + 2 + l + 1) return -EC_GENERAL_FAILURE;
111 memcpy(namebuf, buf+4+1, l);
112 namebuf[l] = 0;
113 break;
114 default:
115 return -EC_ADDRESSTYPE_NOT_SUPPORTED;
117 unsigned short port;
118 port = (buf[minlen-2] << 8) | buf[minlen-1];
119 if(resolve(namebuf, port, &remote)) return -9;
120 int fd = socket(remote->ai_addr->sa_family, SOCK_STREAM, 0);
121 if(fd == -1) {
122 eval_errno:
123 freeaddrinfo(remote);
124 switch(errno) {
125 case EPROTOTYPE:
126 case EPROTONOSUPPORT:
127 case EAFNOSUPPORT:
128 return -EC_ADDRESSTYPE_NOT_SUPPORTED;
129 case ECONNREFUSED:
130 return -EC_CONN_REFUSED;
131 case ENETDOWN:
132 case ENETUNREACH:
133 return -EC_NET_UNREACHABLE;
134 case EHOSTUNREACH:
135 return -EC_HOST_UNREACHABLE;
136 case EBADF:
137 default:
138 perror("socket/connect");
139 return -EC_GENERAL_FAILURE;
142 if(connect(fd, remote->ai_addr, remote->ai_addrlen) == -1)
143 goto eval_errno;
145 freeaddrinfo(remote);
146 if(CONFIG_LOG) {
147 char clientname[256];
148 af = client->addr.v4.sin_family;
149 void *ipdata = af == AF_INET ? (void*)&client->addr.v4.sin_addr : (void*)&client->addr.v6.sin6_addr;
150 inet_ntop(af, ipdata, clientname, sizeof clientname);
151 dolog("client[%d] %s: connected to %s:%d\n", client->fd, clientname, namebuf, port);
153 return fd;
156 static int is_authed(union sockaddr_union *client, union sockaddr_union *authedip) {
157 if(authedip->v4.sin_family == client->v4.sin_family) {
158 int af = authedip->v4.sin_family;
159 size_t cmpbytes = af == AF_INET ? 4 : 16;
160 void *cmp1 = af == AF_INET ? (void*)&client->v4.sin_addr : (void*)&client->v6.sin6_addr;
161 void *cmp2 = af == AF_INET ? (void*)&authedip->v4.sin_addr : (void*)&authedip->v6.sin6_addr;
162 if(!memcmp(cmp1, cmp2, cmpbytes)) return 1;
164 return 0;
167 static enum authmethod check_auth_method(unsigned char *buf, size_t n, struct client*client) {
168 if(buf[0] != 5) return AM_INVALID;
169 size_t idx = 1;
170 if(idx >= n ) return AM_INVALID;
171 int n_methods = buf[idx];
172 idx++;
173 while(idx < n && n_methods > 0) {
174 if(buf[idx] == AM_NO_AUTH) {
175 if(!auth_user) return AM_NO_AUTH;
176 else if(auth_ips) {
177 size_t i;
178 int authed = 0;
179 pthread_mutex_lock(&auth_ips_mutex);
180 for(i=0;i<sblist_getsize(auth_ips);i++) {
181 if((authed = is_authed(&client->addr, sblist_get(auth_ips, i))))
182 break;
184 pthread_mutex_unlock(&auth_ips_mutex);
185 if(authed) return AM_NO_AUTH;
187 } else if(buf[idx] == AM_USERNAME) {
188 if(auth_user) return AM_USERNAME;
190 idx++;
191 n_methods--;
193 return AM_INVALID;
196 static void add_auth_ip(struct client*client) {
197 pthread_mutex_lock(&auth_ips_mutex);
198 sblist_add(auth_ips, &client->addr);
199 pthread_mutex_unlock(&auth_ips_mutex);
202 static void send_auth_response(int fd, enum authmethod meth) {
203 unsigned char buf[2];
204 buf[0] = 5;
205 buf[1] = meth;
206 write(fd, buf, 2);
209 static void send_error(int fd, enum errorcode ec) {
210 /* position 4 contains ATYP, the address type, which is the same as used in the connect
211 request. we're lazy and return always IPV4 address type in errors. */
212 char buf[10] = { 5, ec, 0, 1 /*AT_IPV4*/, 0,0,0,0, 0,0 };
213 write(fd, buf, 10);
216 static void copyloop(int fd1, int fd2) {
217 int maxfd = fd2;
218 if(fd1 > fd2) maxfd = fd1;
219 fd_set fdsc, fds;
220 FD_ZERO(&fdsc);
221 FD_SET(fd1, &fdsc);
222 FD_SET(fd2, &fdsc);
224 while(1) {
225 memcpy(&fds, &fdsc, sizeof(fds));
226 /* inactive connections are reaped after 15 min to free resources.
227 usually programs send keep-alive packets so this should only happen
228 when a connection is really unused. */
229 struct timeval timeout = {.tv_sec = 60*15, .tv_usec = 0};
230 switch(select(maxfd+1, &fds, 0, 0, &timeout)) {
231 case 0:
232 send_error(fd1, EC_TTL_EXPIRED);
233 return;
234 case -1:
235 if(errno == EINTR) continue;
236 else perror("select");
237 return;
239 int infd = FD_ISSET(fd1, &fds) ? fd1 : fd2;
240 int outfd = infd == fd2 ? fd1 : fd2;
241 char buf[1024];
242 ssize_t sent = 0, n = read(infd, buf, sizeof buf);
243 if(n <= 0) return;
244 while(sent < n) {
245 ssize_t m = write(outfd, buf+sent, n-sent);
246 if(m < 0) return;
247 sent += m;
252 static enum errorcode check_credentials(unsigned char* buf, size_t n) {
253 if(n < 5) return EC_GENERAL_FAILURE;
254 if(buf[0] != 1) return EC_GENERAL_FAILURE;
255 unsigned ulen, plen;
256 ulen=buf[1];
257 if(n < 2 + ulen + 2) return EC_GENERAL_FAILURE;
258 plen=buf[2+ulen];
259 if(n < 2 + ulen + 1 + plen) return EC_GENERAL_FAILURE;
260 char user[256], pass[256];
261 memcpy(user, buf+2, ulen);
262 memcpy(pass, buf+2+ulen+1, plen);
263 if(!strcmp(user, auth_user) && !strcmp(pass, auth_pass)) return EC_SUCCESS;
264 return EC_NOT_ALLOWED;
267 static void* clientthread(void *data) {
268 struct thread *t = data;
269 t->state = SS_1_CONNECTED;
270 unsigned char buf[1024];
271 ssize_t n;
272 int ret;
273 int remotefd = -1;
274 enum authmethod am;
275 while((n = recv(t->client.fd, buf, sizeof buf, 0)) > 0) {
276 switch(t->state) {
277 case SS_1_CONNECTED:
278 am = check_auth_method(buf, n, &t->client);
279 if(am == AM_NO_AUTH) t->state = SS_3_AUTHED;
280 else if (am == AM_USERNAME) t->state = SS_2_NEED_AUTH;
281 send_auth_response(t->client.fd, am);
282 if(am == AM_INVALID) goto breakloop;
283 break;
284 case SS_2_NEED_AUTH:
285 ret = check_credentials(buf, n);
286 send_auth_response(t->client.fd, ret);
287 if(ret != EC_SUCCESS)
288 goto breakloop;
289 t->state = SS_3_AUTHED;
290 if(auth_ips) add_auth_ip(&t->client);
291 break;
292 case SS_3_AUTHED:
293 ret = connect_socks_target(buf, n, &t->client);
294 if(ret < 0) {
295 send_error(t->client.fd, ret*-1);
296 goto breakloop;
298 remotefd = ret;
299 send_error(t->client.fd, EC_SUCCESS);
300 copyloop(t->client.fd, remotefd);
301 goto breakloop;
305 breakloop:
307 if(remotefd != -1)
308 close(remotefd);
310 close(t->client.fd);
311 t->done = 1;
313 return 0;
316 static void collect(sblist *threads) {
317 size_t i;
318 for(i=0;i<sblist_getsize(threads);) {
319 struct thread* thread = *((struct thread**)sblist_get(threads, i));
320 if(thread->done) {
321 pthread_join(thread->pt, 0);
322 sblist_delete(threads, i);
323 free(thread);
324 } else
325 i++;
329 static int usage(void) {
330 dprintf(2,
331 "MicroSocks SOCKS5 Server\n"
332 "------------------------\n"
333 "usage: microsocks -1 -i listenip -p port -u user -P password\n"
334 "all arguments are optional.\n"
335 "by default listenip is 0.0.0.0 and port 1080.\n\n"
336 "option -1 activates auth_once mode: once a specific ip address\n"
337 "authed successfully with user/pass, it is added to a whitelist\n"
338 "and may use the proxy without auth.\n"
339 "this is handy for programs like firefox that don't support\n"
340 "user/pass auth. for it to work you'd basically make one connection\n"
341 "with another program that supports it, and then you can use firefox too.\n"
343 return 1;
346 /* prevent username and password from showing up in top. */
347 static void zero_arg(char *s) {
348 size_t i, l = strlen(s);
349 for(i=0;i<l;i++) s[i] = 0;
352 int main(int argc, char** argv) {
353 int c;
354 const char *listenip = "0.0.0.0";
355 unsigned port = 1080;
356 while((c = getopt(argc, argv, ":1i:p:u:P:")) != -1) {
357 switch(c) {
358 case '1':
359 auth_ips = sblist_new(sizeof(union sockaddr_union), 8);
360 break;
361 case 'u':
362 auth_user = strdup(optarg);
363 zero_arg(optarg);
364 break;
365 case 'P':
366 auth_pass = strdup(optarg);
367 zero_arg(optarg);
368 break;
369 case 'i':
370 listenip = optarg;
371 break;
372 case 'p':
373 port = atoi(optarg);
374 break;
375 case ':':
376 dprintf(2, "error: option -%c requires an operand\n", optopt);
377 case '?':
378 return usage();
381 if((auth_user && !auth_pass) || (!auth_user && auth_pass)) {
382 dprintf(2, "error: user and pass must be used together\n");
383 return 1;
385 if(auth_ips && !auth_pass) {
386 dprintf(2, "error: auth-once option must be used together with user/pass\n");
387 return 1;
389 signal(SIGPIPE, SIG_IGN);
390 struct server s;
391 sblist *threads = sblist_new(sizeof (struct thread*), 8);
392 if(server_setup(&s, listenip, port)) {
393 perror("server_setup");
394 return 1;
396 while(1) {
397 collect(threads);
398 struct client c;
399 struct thread *curr = malloc(sizeof (struct thread));
400 if(!curr) goto oom;
401 curr->done = 0;
402 if(server_waitclient(&s, &c)) continue;
403 curr->client = c;
404 if(!sblist_add(threads, &curr)) {
405 close(curr->client.fd);
406 free(curr);
407 oom:
408 dolog("rejecting connection due to OOM\n");
409 usleep(16); /* prevent 100% CPU usage in OOM situation */
410 continue;
412 pthread_attr_t *a = 0, attr;
413 if(pthread_attr_init(&attr) == 0) {
414 a = &attr;
415 pthread_attr_setstacksize(a, 8192); /* 4KB for us, 4KB for libc */
417 if(pthread_create(&curr->pt, a, clientthread, curr) != 0)
418 dolog("pthread_create failed. OOM?\n");
419 if(a) pthread_attr_destroy(&attr);