1 | /*************************************************************************** |
2 | * _ _ ____ _ |
3 | * Project ___| | | | _ \| | |
4 | * / __| | | | |_) | | |
5 | * | (__| |_| | _ <| |___ |
6 | * \___|\___/|_| \_\_____| |
7 | * |
8 | * Copyright (C) 1998 - 2021, Daniel Stenberg, <daniel@haxx.se>, et al. |
9 | * |
10 | * This software is licensed as described in the file COPYING, which |
11 | * you should have received as part of this distribution. The terms |
12 | * are also available at https://curl.se/docs/copyright.html. |
13 | * |
14 | * You may opt to use, copy, modify, merge, publish, distribute and/or sell |
15 | * copies of the Software, and permit persons to whom the Software is |
16 | * furnished to do so, under the terms of the COPYING file. |
17 | * |
18 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY |
19 | * KIND, either express or implied. |
20 | * |
21 | ***************************************************************************/ |
22 | #include "server_setup.h" |
23 | #include <stdlib.h> |
24 | |
25 | /* Function |
26 | * |
27 | * Accepts a TCP connection on a custom port (IPv4 or IPv6). Connects to a |
28 | * given addr + port backend (that is NOT extracted form the client's |
29 | * request). The backend server default to connect to can be set with |
30 | * --backend and --backendport. |
31 | * |
32 | * Read commands from FILE (set with --config). The commands control how to |
33 | * act and is reset to defaults each client TCP connect. |
34 | * |
35 | * Config file keywords: |
36 | * |
37 | * "version [number: 5]" - requires the communication to use this version. |
38 | * "nmethods_min [number: 1]" - the minimum numberf NMETHODS the client must |
39 | * state |
40 | * "nmethods_max [number: 3]" - the minimum numberf NMETHODS the client must |
41 | * state |
42 | * "user [string]" - the user name that must match (if method is 2) |
43 | * "password [string]" - the password that must match (if method is 2) |
44 | * "backend [IPv4]" - numerical IPv4 address of backend to connect to |
45 | * "backendport [number:0]" - TCP port of backend to connect to. 0 means use |
46 | the client's specified port number. |
47 | * "method [number: 0]" - connect method to respond with: |
48 | * 0 - no auth |
49 | * 1 - GSSAPI (not supported) |
50 | * 2 - user + password |
51 | * "response [number]" - the decimal number to respond to a connect |
52 | * SOCKS5: 0 is OK, SOCKS4: 90 is ok |
53 | * |
54 | */ |
55 | |
56 | /* based on sockfilt.c */ |
57 | |
58 | #ifdef HAVE_SIGNAL_H |
59 | #include <signal.h> |
60 | #endif |
61 | #ifdef HAVE_NETINET_IN_H |
62 | #include <netinet/in.h> |
63 | #endif |
64 | #ifdef HAVE_NETINET_IN6_H |
65 | #include <netinet/in6.h> |
66 | #endif |
67 | #ifdef HAVE_ARPA_INET_H |
68 | #include <arpa/inet.h> |
69 | #endif |
70 | #ifdef HAVE_NETDB_H |
71 | #include <netdb.h> |
72 | #endif |
73 | |
74 | #define ENABLE_CURLX_PRINTF |
75 | /* make the curlx header define all printf() functions to use the curlx_* |
76 | versions instead */ |
77 | #include "curlx.h" /* from the private lib dir */ |
78 | #include "getpart.h" |
79 | #include "inet_pton.h" |
80 | #include "util.h" |
81 | #include "server_sockaddr.h" |
82 | #include "warnless.h" |
83 | |
84 | /* include memdebug.h last */ |
85 | #include "memdebug.h" |
86 | |
87 | #ifdef USE_WINSOCK |
88 | #undef EINTR |
89 | #define EINTR 4 /* errno.h value */ |
90 | #undef EAGAIN |
91 | #define EAGAIN 11 /* errno.h value */ |
92 | #undef ENOMEM |
93 | #define ENOMEM 12 /* errno.h value */ |
94 | #undef EINVAL |
95 | #define EINVAL 22 /* errno.h value */ |
96 | #endif |
97 | |
98 | #define DEFAULT_PORT 8905 |
99 | |
100 | #ifndef DEFAULT_LOGFILE |
101 | #define DEFAULT_LOGFILE "log/socksd.log" |
102 | #endif |
103 | |
104 | #ifndef DEFAULT_CONFIG |
105 | #define DEFAULT_CONFIG "socksd.config" |
106 | #endif |
107 | |
108 | static const char *backendaddr = "127.0.0.1" ; |
109 | static unsigned short backendport = 0; /* default is use client's */ |
110 | |
111 | struct configurable { |
112 | unsigned char version; /* initial version byte in the request must match |
113 | this */ |
114 | unsigned char nmethods_min; /* minimum number of nmethods to expect */ |
115 | unsigned char nmethods_max; /* maximum number of nmethods to expect */ |
116 | unsigned char responseversion; |
117 | unsigned char responsemethod; |
118 | unsigned char reqcmd; |
119 | unsigned char connectrep; |
120 | unsigned short port; /* backend port */ |
121 | char addr[32]; /* backend IPv4 numerical */ |
122 | char user[256]; |
123 | char password[256]; |
124 | }; |
125 | |
126 | #define CONFIG_VERSION 5 |
127 | #define CONFIG_NMETHODS_MIN 1 /* unauth, gssapi, auth */ |
128 | #define CONFIG_NMETHODS_MAX 3 |
129 | #define CONFIG_RESPONSEVERSION CONFIG_VERSION |
130 | #define CONFIG_RESPONSEMETHOD 0 /* no auth */ |
131 | #define CONFIG_REQCMD 1 /* CONNECT */ |
132 | #define CONFIG_PORT backendport |
133 | #define CONFIG_ADDR backendaddr |
134 | #define CONFIG_CONNECTREP 0 |
135 | |
136 | static struct configurable config; |
137 | |
138 | const char *serverlogfile = DEFAULT_LOGFILE; |
139 | static const char *configfile = DEFAULT_CONFIG; |
140 | |
141 | #ifdef ENABLE_IPV6 |
142 | static bool use_ipv6 = FALSE; |
143 | #endif |
144 | static const char *ipv_inuse = "IPv4" ; |
145 | static unsigned short port = DEFAULT_PORT; |
146 | |
147 | static void resetdefaults(void) |
148 | { |
149 | logmsg("Reset to defaults" ); |
150 | config.version = CONFIG_VERSION; |
151 | config.nmethods_min = CONFIG_NMETHODS_MIN; |
152 | config.nmethods_max = CONFIG_NMETHODS_MAX; |
153 | config.responseversion = CONFIG_RESPONSEVERSION; |
154 | config.responsemethod = CONFIG_RESPONSEMETHOD; |
155 | config.reqcmd = CONFIG_REQCMD; |
156 | config.connectrep = CONFIG_CONNECTREP; |
157 | config.port = CONFIG_PORT; |
158 | strcpy(config.addr, CONFIG_ADDR); |
159 | strcpy(config.user, "user" ); |
160 | strcpy(config.password, "password" ); |
161 | } |
162 | |
163 | static unsigned char byteval(char *value) |
164 | { |
165 | unsigned long num = strtoul(value, NULL, 10); |
166 | return num & 0xff; |
167 | } |
168 | |
169 | static unsigned short shortval(char *value) |
170 | { |
171 | unsigned long num = strtoul(value, NULL, 10); |
172 | return num & 0xffff; |
173 | } |
174 | |
175 | static void getconfig(void) |
176 | { |
177 | FILE *fp = fopen(configfile, FOPEN_READTEXT); |
178 | resetdefaults(); |
179 | if(fp) { |
180 | char buffer[512]; |
181 | logmsg("parse config file" ); |
182 | while(fgets(buffer, sizeof(buffer), fp)) { |
183 | char key[32]; |
184 | char value[32]; |
185 | if(2 == sscanf(buffer, "%31s %31s" , key, value)) { |
186 | if(!strcmp(key, "version" )) { |
187 | config.version = byteval(value); |
188 | logmsg("version [%d] set" , config.version); |
189 | } |
190 | else if(!strcmp(key, "nmethods_min" )) { |
191 | config.nmethods_min = byteval(value); |
192 | logmsg("nmethods_min [%d] set" , config.nmethods_min); |
193 | } |
194 | else if(!strcmp(key, "nmethods_max" )) { |
195 | config.nmethods_max = byteval(value); |
196 | logmsg("nmethods_max [%d] set" , config.nmethods_max); |
197 | } |
198 | else if(!strcmp(key, "backend" )) { |
199 | strcpy(config.addr, value); |
200 | logmsg("backend [%s] set" , config.addr); |
201 | } |
202 | else if(!strcmp(key, "backendport" )) { |
203 | config.port = shortval(value); |
204 | logmsg("backendport [%d] set" , config.port); |
205 | } |
206 | else if(!strcmp(key, "user" )) { |
207 | strcpy(config.user, value); |
208 | logmsg("user [%s] set" , config.user); |
209 | } |
210 | else if(!strcmp(key, "password" )) { |
211 | strcpy(config.password, value); |
212 | logmsg("password [%s] set" , config.password); |
213 | } |
214 | /* Methods: |
215 | o X'00' NO AUTHENTICATION REQUIRED |
216 | o X'01' GSSAPI |
217 | o X'02' USERNAME/PASSWORD |
218 | */ |
219 | else if(!strcmp(key, "method" )) { |
220 | config.responsemethod = byteval(value); |
221 | logmsg("method [%d] set" , config.responsemethod); |
222 | } |
223 | else if(!strcmp(key, "response" )) { |
224 | config.connectrep = byteval(value); |
225 | logmsg("response [%d] set" , config.connectrep); |
226 | } |
227 | } |
228 | } |
229 | fclose(fp); |
230 | } |
231 | } |
232 | |
233 | static void loghex(unsigned char *buffer, ssize_t len) |
234 | { |
235 | char data[1200]; |
236 | ssize_t i; |
237 | unsigned char *ptr = buffer; |
238 | char *optr = data; |
239 | ssize_t width = 0; |
240 | int left = sizeof(data); |
241 | |
242 | for(i = 0; i<len && (left >= 0); i++) { |
243 | msnprintf(optr, left, "%02x" , ptr[i]); |
244 | width += 2; |
245 | optr += 2; |
246 | left -= 2; |
247 | } |
248 | if(width) |
249 | logmsg("'%s'" , data); |
250 | } |
251 | |
252 | /* RFC 1928, SOCKS5 byte index */ |
253 | #define SOCKS5_VERSION 0 |
254 | #define SOCKS5_NMETHODS 1 /* number of methods that is listed */ |
255 | |
256 | /* in the request: */ |
257 | #define SOCKS5_REQCMD 1 |
258 | #define SOCKS5_RESERVED 2 |
259 | #define SOCKS5_ATYP 3 |
260 | #define SOCKS5_DSTADDR 4 |
261 | |
262 | /* connect response */ |
263 | #define SOCKS5_REP 1 |
264 | #define SOCKS5_BNDADDR 4 |
265 | |
266 | /* auth request */ |
267 | #define SOCKS5_ULEN 1 |
268 | #define SOCKS5_UNAME 2 |
269 | |
270 | #define SOCKS4_CD 1 |
271 | #define SOCKS4_DSTPORT 2 |
272 | |
273 | /* connect to a given IPv4 address, not the one asked for */ |
274 | static curl_socket_t socksconnect(unsigned short connectport, |
275 | const char *connectaddr) |
276 | { |
277 | int rc; |
278 | srvr_sockaddr_union_t me; |
279 | curl_socket_t sock = socket(AF_INET, SOCK_STREAM, 0); |
280 | if(sock == CURL_SOCKET_BAD) |
281 | return CURL_SOCKET_BAD; |
282 | memset(&me.sa4, 0, sizeof(me.sa4)); |
283 | me.sa4.sin_family = AF_INET; |
284 | me.sa4.sin_port = htons(connectport); |
285 | me.sa4.sin_addr.s_addr = INADDR_ANY; |
286 | Curl_inet_pton(AF_INET, connectaddr, &me.sa4.sin_addr); |
287 | |
288 | rc = connect(sock, &me.sa, sizeof(me.sa4)); |
289 | |
290 | if(rc) { |
291 | int error = SOCKERRNO; |
292 | logmsg("Error connecting to %s:%hu: (%d) %s" , |
293 | connectaddr, connectport, error, strerror(error)); |
294 | return CURL_SOCKET_BAD; |
295 | } |
296 | logmsg("Connected fine to %s:%d" , connectaddr, connectport); |
297 | return sock; |
298 | } |
299 | |
300 | static curl_socket_t socks4(curl_socket_t fd, |
301 | unsigned char *buffer, |
302 | ssize_t rc) |
303 | { |
304 | unsigned char response[256 + 16]; |
305 | curl_socket_t connfd; |
306 | unsigned char cd; |
307 | unsigned short s4port; |
308 | |
309 | if(buffer[SOCKS4_CD] != 1) { |
310 | logmsg("SOCKS4 CD is not 1: %d" , buffer[SOCKS4_CD]); |
311 | return CURL_SOCKET_BAD; |
312 | } |
313 | if(rc < 9) { |
314 | logmsg("SOCKS4 connect message too short: %d" , rc); |
315 | return CURL_SOCKET_BAD; |
316 | } |
317 | if(!config.port) |
318 | s4port = (unsigned short)((buffer[SOCKS4_DSTPORT]<<8) | |
319 | (buffer[SOCKS4_DSTPORT + 1])); |
320 | else |
321 | s4port = config.port; |
322 | |
323 | connfd = socksconnect(s4port, config.addr); |
324 | if(connfd == CURL_SOCKET_BAD) { |
325 | /* failed */ |
326 | cd = 91; |
327 | } |
328 | else { |
329 | /* success */ |
330 | cd = 90; |
331 | } |
332 | response[0] = 0; /* reply version 0 */ |
333 | response[1] = cd; /* result */ |
334 | /* copy port and address from connect request */ |
335 | memcpy(&response[2], &buffer[SOCKS4_DSTPORT], 6); |
336 | rc = (send)(fd, (char *)response, 8, 0); |
337 | if(rc != 8) { |
338 | logmsg("Sending SOCKS4 response failed!" ); |
339 | return CURL_SOCKET_BAD; |
340 | } |
341 | logmsg("Sent %d bytes" , rc); |
342 | loghex(response, rc); |
343 | |
344 | if(cd == 90) |
345 | /* now do the transfer */ |
346 | return connfd; |
347 | |
348 | if(connfd != CURL_SOCKET_BAD) |
349 | sclose(connfd); |
350 | |
351 | return CURL_SOCKET_BAD; |
352 | } |
353 | |
354 | static curl_socket_t sockit(curl_socket_t fd) |
355 | { |
356 | unsigned char buffer[256 + 16]; |
357 | unsigned char response[256 + 16]; |
358 | ssize_t rc; |
359 | unsigned char len; |
360 | unsigned char type; |
361 | unsigned char rep = 0; |
362 | unsigned char *address; |
363 | unsigned short socksport; |
364 | curl_socket_t connfd = CURL_SOCKET_BAD; |
365 | unsigned short s5port; |
366 | |
367 | getconfig(); |
368 | |
369 | rc = recv(fd, (char *)buffer, sizeof(buffer), 0); |
370 | |
371 | logmsg("READ %d bytes" , rc); |
372 | loghex(buffer, rc); |
373 | |
374 | if(buffer[SOCKS5_VERSION] == 4) |
375 | return socks4(fd, buffer, rc); |
376 | |
377 | if(buffer[SOCKS5_VERSION] != config.version) { |
378 | logmsg("VERSION byte not %d" , config.version); |
379 | return CURL_SOCKET_BAD; |
380 | } |
381 | if((buffer[SOCKS5_NMETHODS] < config.nmethods_min) || |
382 | (buffer[SOCKS5_NMETHODS] > config.nmethods_max)) { |
383 | logmsg("NMETHODS byte not within %d - %d " , |
384 | config.nmethods_min, config.nmethods_max); |
385 | return CURL_SOCKET_BAD; |
386 | } |
387 | /* after NMETHODS follows that many bytes listing the methods the client |
388 | says it supports */ |
389 | if(rc != (buffer[SOCKS5_NMETHODS] + 2)) { |
390 | logmsg("Expected %d bytes, got %d" , buffer[SOCKS5_NMETHODS] + 2, rc); |
391 | return CURL_SOCKET_BAD; |
392 | } |
393 | logmsg("Incoming request deemed fine!" ); |
394 | |
395 | /* respond with two bytes: VERSION + METHOD */ |
396 | response[0] = config.responseversion; |
397 | response[1] = config.responsemethod; |
398 | rc = (send)(fd, (char *)response, 2, 0); |
399 | if(rc != 2) { |
400 | logmsg("Sending response failed!" ); |
401 | return CURL_SOCKET_BAD; |
402 | } |
403 | logmsg("Sent %d bytes" , rc); |
404 | loghex(response, rc); |
405 | |
406 | /* expect the request or auth */ |
407 | rc = recv(fd, (char *)buffer, sizeof(buffer), 0); |
408 | |
409 | logmsg("READ %d bytes" , rc); |
410 | loghex(buffer, rc); |
411 | |
412 | if(config.responsemethod == 2) { |
413 | /* RFC 1929 authentication |
414 | +----+------+----------+------+----------+ |
415 | |VER | ULEN | UNAME | PLEN | PASSWD | |
416 | +----+------+----------+------+----------+ |
417 | | 1 | 1 | 1 to 255 | 1 | 1 to 255 | |
418 | +----+------+----------+------+----------+ |
419 | */ |
420 | unsigned char ulen; |
421 | unsigned char plen; |
422 | bool login = TRUE; |
423 | if(rc < 5) { |
424 | logmsg("Too short auth input: %d" , rc); |
425 | return CURL_SOCKET_BAD; |
426 | } |
427 | if(buffer[SOCKS5_VERSION] != 1) { |
428 | logmsg("Auth VERSION byte not 1, got %d" , buffer[SOCKS5_VERSION]); |
429 | return CURL_SOCKET_BAD; |
430 | } |
431 | ulen = buffer[SOCKS5_ULEN]; |
432 | if(rc < 4 + ulen) { |
433 | logmsg("Too short packet for username: %d" , rc); |
434 | return CURL_SOCKET_BAD; |
435 | } |
436 | plen = buffer[SOCKS5_ULEN + ulen + 1]; |
437 | if(rc < 3 + ulen + plen) { |
438 | logmsg("Too short packet for ulen %d plen %d: %d" , ulen, plen, rc); |
439 | return CURL_SOCKET_BAD; |
440 | } |
441 | if((ulen != strlen(config.user)) || |
442 | (plen != strlen(config.password)) || |
443 | memcmp(&buffer[SOCKS5_UNAME], config.user, ulen) || |
444 | memcmp(&buffer[SOCKS5_UNAME + ulen + 1], config.password, plen)) { |
445 | /* no match! */ |
446 | logmsg("mismatched credentials!" ); |
447 | login = FALSE; |
448 | } |
449 | response[0] = 1; |
450 | response[1] = login ? 0 : 1; |
451 | rc = (send)(fd, (char *)response, 2, 0); |
452 | if(rc != 2) { |
453 | logmsg("Sending auth response failed!" ); |
454 | return CURL_SOCKET_BAD; |
455 | } |
456 | logmsg("Sent %d bytes" , rc); |
457 | loghex(response, rc); |
458 | if(!login) |
459 | return CURL_SOCKET_BAD; |
460 | |
461 | /* expect the request */ |
462 | rc = recv(fd, (char *)buffer, sizeof(buffer), 0); |
463 | |
464 | logmsg("READ %d bytes" , rc); |
465 | loghex(buffer, rc); |
466 | } |
467 | if(rc < 6) { |
468 | logmsg("Too short for request: %d" , rc); |
469 | return CURL_SOCKET_BAD; |
470 | } |
471 | |
472 | if(buffer[SOCKS5_VERSION] != config.version) { |
473 | logmsg("Request VERSION byte not %d" , config.version); |
474 | return CURL_SOCKET_BAD; |
475 | } |
476 | /* 1 == CONNECT */ |
477 | if(buffer[SOCKS5_REQCMD] != config.reqcmd) { |
478 | logmsg("Request COMMAND byte not %d" , config.reqcmd); |
479 | return CURL_SOCKET_BAD; |
480 | } |
481 | /* reserved, should be zero */ |
482 | if(buffer[SOCKS5_RESERVED]) { |
483 | logmsg("Request COMMAND byte not %d" , config.reqcmd); |
484 | return CURL_SOCKET_BAD; |
485 | } |
486 | /* ATYP: |
487 | o IP V4 address: X'01' |
488 | o DOMAINNAME: X'03' |
489 | o IP V6 address: X'04' |
490 | */ |
491 | type = buffer[SOCKS5_ATYP]; |
492 | address = &buffer[SOCKS5_DSTADDR]; |
493 | switch(type) { |
494 | case 1: |
495 | /* 4 bytes IPv4 address */ |
496 | len = 4; |
497 | break; |
498 | case 3: |
499 | /* The first octet of the address field contains the number of octets of |
500 | name that follow */ |
501 | len = buffer[SOCKS5_DSTADDR]; |
502 | len++; |
503 | break; |
504 | case 4: |
505 | /* 16 bytes IPv6 address */ |
506 | len = 16; |
507 | break; |
508 | default: |
509 | logmsg("Unknown ATYP %d" , type); |
510 | return CURL_SOCKET_BAD; |
511 | } |
512 | if(rc < (4 + len + 2)) { |
513 | logmsg("Request too short: %d, expected %d" , rc, 4 + len + 2); |
514 | return CURL_SOCKET_BAD; |
515 | } |
516 | |
517 | if(!config.port) { |
518 | unsigned char *portp = &buffer[SOCKS5_DSTADDR + len]; |
519 | s5port = (unsigned short)((portp[0]<<8) | (portp[1])); |
520 | } |
521 | else |
522 | s5port = config.port; |
523 | |
524 | if(!config.connectrep) |
525 | connfd = socksconnect(s5port, config.addr); |
526 | |
527 | if(connfd == CURL_SOCKET_BAD) { |
528 | /* failed */ |
529 | rep = 1; |
530 | } |
531 | else { |
532 | rep = config.connectrep; |
533 | } |
534 | |
535 | /* */ |
536 | response[SOCKS5_VERSION] = config.responseversion; |
537 | |
538 | /* |
539 | o REP Reply field: |
540 | o X'00' succeeded |
541 | o X'01' general SOCKS server failure |
542 | o X'02' connection not allowed by ruleset |
543 | o X'03' Network unreachable |
544 | o X'04' Host unreachable |
545 | o X'05' Connection refused |
546 | o X'06' TTL expired |
547 | o X'07' Command not supported |
548 | o X'08' Address type not supported |
549 | o X'09' to X'FF' unassigned |
550 | */ |
551 | response[SOCKS5_REP] = rep; |
552 | response[SOCKS5_RESERVED] = 0; /* must be zero */ |
553 | response[SOCKS5_ATYP] = type; /* address type */ |
554 | |
555 | /* mirror back the original addr + port */ |
556 | |
557 | /* address or hostname */ |
558 | memcpy(&response[SOCKS5_BNDADDR], address, len); |
559 | |
560 | /* port number */ |
561 | memcpy(&response[SOCKS5_BNDADDR + len], |
562 | &buffer[SOCKS5_DSTADDR + len], sizeof(socksport)); |
563 | |
564 | rc = (send)(fd, (char *)response, len + 6, 0); |
565 | if(rc != (len + 6)) { |
566 | logmsg("Sending connect response failed!" ); |
567 | return CURL_SOCKET_BAD; |
568 | } |
569 | logmsg("Sent %d bytes" , rc); |
570 | loghex(response, rc); |
571 | |
572 | if(!rep) |
573 | return connfd; |
574 | |
575 | if(connfd != CURL_SOCKET_BAD) |
576 | sclose(connfd); |
577 | |
578 | return CURL_SOCKET_BAD; |
579 | } |
580 | |
581 | struct perclient { |
582 | size_t fromremote; |
583 | size_t fromclient; |
584 | curl_socket_t remotefd; |
585 | curl_socket_t clientfd; |
586 | bool used; |
587 | }; |
588 | |
589 | /* return non-zero when transfer is done */ |
590 | static int tunnel(struct perclient *cp, fd_set *fds) |
591 | { |
592 | ssize_t nread; |
593 | ssize_t nwrite; |
594 | char buffer[512]; |
595 | if(FD_ISSET(cp->clientfd, fds)) { |
596 | /* read from client, send to remote */ |
597 | nread = recv(cp->clientfd, buffer, sizeof(buffer), 0); |
598 | if(nread > 0) { |
599 | nwrite = send(cp->remotefd, (char *)buffer, |
600 | (SEND_TYPE_ARG3)nread, 0); |
601 | if(nwrite != nread) |
602 | return 1; |
603 | cp->fromclient += nwrite; |
604 | } |
605 | else |
606 | return 1; |
607 | } |
608 | if(FD_ISSET(cp->remotefd, fds)) { |
609 | /* read from remote, send to client */ |
610 | nread = recv(cp->remotefd, buffer, sizeof(buffer), 0); |
611 | if(nread > 0) { |
612 | nwrite = send(cp->clientfd, (char *)buffer, |
613 | (SEND_TYPE_ARG3)nread, 0); |
614 | if(nwrite != nread) |
615 | return 1; |
616 | cp->fromremote += nwrite; |
617 | } |
618 | else |
619 | return 1; |
620 | } |
621 | return 0; |
622 | } |
623 | |
624 | /* |
625 | sockfdp is a pointer to an established stream or CURL_SOCKET_BAD |
626 | |
627 | if sockfd is CURL_SOCKET_BAD, listendfd is a listening socket we must |
628 | accept() |
629 | */ |
630 | static bool incoming(curl_socket_t listenfd) |
631 | { |
632 | fd_set fds_read; |
633 | fd_set fds_write; |
634 | fd_set fds_err; |
635 | int clients = 0; /* connected clients */ |
636 | struct perclient c[2]; |
637 | |
638 | memset(c, 0, sizeof(c)); |
639 | if(got_exit_signal) { |
640 | logmsg("signalled to die, exiting..." ); |
641 | return FALSE; |
642 | } |
643 | |
644 | #ifdef HAVE_GETPPID |
645 | /* As a last resort, quit if socks5 process becomes orphan. */ |
646 | if(getppid() <= 1) { |
647 | logmsg("process becomes orphan, exiting" ); |
648 | return FALSE; |
649 | } |
650 | #endif |
651 | |
652 | do { |
653 | int i; |
654 | ssize_t rc; |
655 | int error = 0; |
656 | curl_socket_t sockfd = listenfd; |
657 | int maxfd = (int)sockfd; |
658 | |
659 | FD_ZERO(&fds_read); |
660 | FD_ZERO(&fds_write); |
661 | FD_ZERO(&fds_err); |
662 | |
663 | /* there's always a socket to wait for */ |
664 | FD_SET(sockfd, &fds_read); |
665 | |
666 | for(i = 0; i < 2; i++) { |
667 | if(c[i].used) { |
668 | curl_socket_t fd = c[i].clientfd; |
669 | FD_SET(fd, &fds_read); |
670 | if((int)fd > maxfd) |
671 | maxfd = (int)fd; |
672 | fd = c[i].remotefd; |
673 | FD_SET(fd, &fds_read); |
674 | if((int)fd > maxfd) |
675 | maxfd = (int)fd; |
676 | } |
677 | } |
678 | |
679 | do { |
680 | /* select() blocking behavior call on blocking descriptors please */ |
681 | rc = select(maxfd + 1, &fds_read, &fds_write, &fds_err, NULL); |
682 | if(got_exit_signal) { |
683 | logmsg("signalled to die, exiting..." ); |
684 | return FALSE; |
685 | } |
686 | } while((rc == -1) && ((error = errno) == EINTR)); |
687 | |
688 | if(rc < 0) { |
689 | logmsg("select() failed with error: (%d) %s" , |
690 | error, strerror(error)); |
691 | return FALSE; |
692 | } |
693 | |
694 | if((clients < 2) && FD_ISSET(sockfd, &fds_read)) { |
695 | curl_socket_t newfd = accept(sockfd, NULL, NULL); |
696 | if(CURL_SOCKET_BAD == newfd) { |
697 | error = SOCKERRNO; |
698 | logmsg("accept(%d, NULL, NULL) failed with error: (%d) %s" , |
699 | sockfd, error, strerror(error)); |
700 | } |
701 | else { |
702 | curl_socket_t remotefd; |
703 | logmsg("====> Client connect, fd %d. Read config from %s" , |
704 | newfd, configfile); |
705 | remotefd = sockit(newfd); /* SOCKS until done */ |
706 | if(remotefd == CURL_SOCKET_BAD) { |
707 | logmsg("====> Client disconnect" ); |
708 | sclose(newfd); |
709 | } |
710 | else { |
711 | struct perclient *cp = &c[0]; |
712 | logmsg("====> Tunnel transfer" ); |
713 | |
714 | if(c[0].used) |
715 | cp = &c[1]; |
716 | cp->fromremote = 0; |
717 | cp->fromclient = 0; |
718 | cp->clientfd = newfd; |
719 | cp->remotefd = remotefd; |
720 | cp->used = TRUE; |
721 | clients++; |
722 | } |
723 | |
724 | } |
725 | } |
726 | for(i = 0; i < 2; i++) { |
727 | struct perclient *cp = &c[i]; |
728 | if(cp->used) { |
729 | if(tunnel(cp, &fds_read)) { |
730 | logmsg("SOCKS transfer completed. Bytes: < %zu > %zu" , |
731 | cp->fromremote, cp->fromclient); |
732 | sclose(cp->clientfd); |
733 | sclose(cp->remotefd); |
734 | cp->used = FALSE; |
735 | clients--; |
736 | } |
737 | } |
738 | } |
739 | } while(clients); |
740 | |
741 | return TRUE; |
742 | } |
743 | |
744 | static curl_socket_t sockdaemon(curl_socket_t sock, |
745 | unsigned short *listenport) |
746 | { |
747 | /* passive daemon style */ |
748 | srvr_sockaddr_union_t listener; |
749 | int flag; |
750 | int rc; |
751 | int totdelay = 0; |
752 | int maxretr = 10; |
753 | int delay = 20; |
754 | int attempt = 0; |
755 | int error = 0; |
756 | |
757 | do { |
758 | attempt++; |
759 | flag = 1; |
760 | rc = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, |
761 | (void *)&flag, sizeof(flag)); |
762 | if(rc) { |
763 | error = SOCKERRNO; |
764 | logmsg("setsockopt(SO_REUSEADDR) failed with error: (%d) %s" , |
765 | error, strerror(error)); |
766 | if(maxretr) { |
767 | rc = wait_ms(delay); |
768 | if(rc) { |
769 | /* should not happen */ |
770 | error = errno; |
771 | logmsg("wait_ms() failed with error: (%d) %s" , |
772 | error, strerror(error)); |
773 | sclose(sock); |
774 | return CURL_SOCKET_BAD; |
775 | } |
776 | if(got_exit_signal) { |
777 | logmsg("signalled to die, exiting..." ); |
778 | sclose(sock); |
779 | return CURL_SOCKET_BAD; |
780 | } |
781 | totdelay += delay; |
782 | delay *= 2; /* double the sleep for next attempt */ |
783 | } |
784 | } |
785 | } while(rc && maxretr--); |
786 | |
787 | if(rc) { |
788 | logmsg("setsockopt(SO_REUSEADDR) failed %d times in %d ms. Error: (%d) %s" , |
789 | attempt, totdelay, error, strerror(error)); |
790 | logmsg("Continuing anyway..." ); |
791 | } |
792 | |
793 | /* When the specified listener port is zero, it is actually a |
794 | request to let the system choose a non-zero available port. */ |
795 | |
796 | #ifdef ENABLE_IPV6 |
797 | if(!use_ipv6) { |
798 | #endif |
799 | memset(&listener.sa4, 0, sizeof(listener.sa4)); |
800 | listener.sa4.sin_family = AF_INET; |
801 | listener.sa4.sin_addr.s_addr = INADDR_ANY; |
802 | listener.sa4.sin_port = htons(*listenport); |
803 | rc = bind(sock, &listener.sa, sizeof(listener.sa4)); |
804 | #ifdef ENABLE_IPV6 |
805 | } |
806 | else { |
807 | memset(&listener.sa6, 0, sizeof(listener.sa6)); |
808 | listener.sa6.sin6_family = AF_INET6; |
809 | listener.sa6.sin6_addr = in6addr_any; |
810 | listener.sa6.sin6_port = htons(*listenport); |
811 | rc = bind(sock, &listener.sa, sizeof(listener.sa6)); |
812 | } |
813 | #endif /* ENABLE_IPV6 */ |
814 | if(rc) { |
815 | error = SOCKERRNO; |
816 | logmsg("Error binding socket on port %hu: (%d) %s" , |
817 | *listenport, error, strerror(error)); |
818 | sclose(sock); |
819 | return CURL_SOCKET_BAD; |
820 | } |
821 | |
822 | if(!*listenport) { |
823 | /* The system was supposed to choose a port number, figure out which |
824 | port we actually got and update the listener port value with it. */ |
825 | curl_socklen_t la_size; |
826 | srvr_sockaddr_union_t localaddr; |
827 | #ifdef ENABLE_IPV6 |
828 | if(!use_ipv6) |
829 | #endif |
830 | la_size = sizeof(localaddr.sa4); |
831 | #ifdef ENABLE_IPV6 |
832 | else |
833 | la_size = sizeof(localaddr.sa6); |
834 | #endif |
835 | memset(&localaddr.sa, 0, (size_t)la_size); |
836 | if(getsockname(sock, &localaddr.sa, &la_size) < 0) { |
837 | error = SOCKERRNO; |
838 | logmsg("getsockname() failed with error: (%d) %s" , |
839 | error, strerror(error)); |
840 | sclose(sock); |
841 | return CURL_SOCKET_BAD; |
842 | } |
843 | switch(localaddr.sa.sa_family) { |
844 | case AF_INET: |
845 | *listenport = ntohs(localaddr.sa4.sin_port); |
846 | break; |
847 | #ifdef ENABLE_IPV6 |
848 | case AF_INET6: |
849 | *listenport = ntohs(localaddr.sa6.sin6_port); |
850 | break; |
851 | #endif |
852 | default: |
853 | break; |
854 | } |
855 | if(!*listenport) { |
856 | /* Real failure, listener port shall not be zero beyond this point. */ |
857 | logmsg("Apparently getsockname() succeeded, with listener port zero." ); |
858 | logmsg("A valid reason for this failure is a binary built without" ); |
859 | logmsg("proper network library linkage. This might not be the only" ); |
860 | logmsg("reason, but double check it before anything else." ); |
861 | sclose(sock); |
862 | return CURL_SOCKET_BAD; |
863 | } |
864 | } |
865 | |
866 | /* start accepting connections */ |
867 | rc = listen(sock, 5); |
868 | if(0 != rc) { |
869 | error = SOCKERRNO; |
870 | logmsg("listen(%d, 5) failed with error: (%d) %s" , |
871 | sock, error, strerror(error)); |
872 | sclose(sock); |
873 | return CURL_SOCKET_BAD; |
874 | } |
875 | |
876 | return sock; |
877 | } |
878 | |
879 | |
880 | int main(int argc, char *argv[]) |
881 | { |
882 | curl_socket_t sock = CURL_SOCKET_BAD; |
883 | curl_socket_t msgsock = CURL_SOCKET_BAD; |
884 | int wrotepidfile = 0; |
885 | int wroteportfile = 0; |
886 | const char *pidname = ".socksd.pid" ; |
887 | const char *portname = NULL; /* none by default */ |
888 | bool juggle_again; |
889 | int error; |
890 | int arg = 1; |
891 | |
892 | while(argc>arg) { |
893 | if(!strcmp("--version" , argv[arg])) { |
894 | printf("socksd IPv4%s\n" , |
895 | #ifdef ENABLE_IPV6 |
896 | "/IPv6" |
897 | #else |
898 | "" |
899 | #endif |
900 | ); |
901 | return 0; |
902 | } |
903 | else if(!strcmp("--pidfile" , argv[arg])) { |
904 | arg++; |
905 | if(argc>arg) |
906 | pidname = argv[arg++]; |
907 | } |
908 | else if(!strcmp("--portfile" , argv[arg])) { |
909 | arg++; |
910 | if(argc>arg) |
911 | portname = argv[arg++]; |
912 | } |
913 | else if(!strcmp("--config" , argv[arg])) { |
914 | arg++; |
915 | if(argc>arg) |
916 | configfile = argv[arg++]; |
917 | } |
918 | else if(!strcmp("--backend" , argv[arg])) { |
919 | arg++; |
920 | if(argc>arg) |
921 | backendaddr = argv[arg++]; |
922 | } |
923 | else if(!strcmp("--backendport" , argv[arg])) { |
924 | arg++; |
925 | if(argc>arg) |
926 | backendport = (unsigned short)atoi(argv[arg++]); |
927 | } |
928 | else if(!strcmp("--logfile" , argv[arg])) { |
929 | arg++; |
930 | if(argc>arg) |
931 | serverlogfile = argv[arg++]; |
932 | } |
933 | else if(!strcmp("--ipv6" , argv[arg])) { |
934 | #ifdef ENABLE_IPV6 |
935 | ipv_inuse = "IPv6" ; |
936 | use_ipv6 = TRUE; |
937 | #endif |
938 | arg++; |
939 | } |
940 | else if(!strcmp("--ipv4" , argv[arg])) { |
941 | /* for completeness, we support this option as well */ |
942 | #ifdef ENABLE_IPV6 |
943 | ipv_inuse = "IPv4" ; |
944 | use_ipv6 = FALSE; |
945 | #endif |
946 | arg++; |
947 | } |
948 | else if(!strcmp("--port" , argv[arg])) { |
949 | arg++; |
950 | if(argc>arg) { |
951 | char *endptr; |
952 | unsigned long ulnum = strtoul(argv[arg], &endptr, 10); |
953 | port = curlx_ultous(ulnum); |
954 | arg++; |
955 | } |
956 | } |
957 | else { |
958 | puts("Usage: socksd [option]\n" |
959 | " --backend [ipv4 addr]\n" |
960 | " --backendport [TCP port]\n" |
961 | " --config [file]\n" |
962 | " --version\n" |
963 | " --logfile [file]\n" |
964 | " --pidfile [file]\n" |
965 | " --portfile [file]\n" |
966 | " --ipv4\n" |
967 | " --ipv6\n" |
968 | " --bindonly\n" |
969 | " --port [port]\n" ); |
970 | return 0; |
971 | } |
972 | } |
973 | |
974 | #ifdef WIN32 |
975 | win32_init(); |
976 | atexit(win32_cleanup); |
977 | |
978 | setmode(fileno(stdin), O_BINARY); |
979 | setmode(fileno(stdout), O_BINARY); |
980 | setmode(fileno(stderr), O_BINARY); |
981 | #endif |
982 | |
983 | install_signal_handlers(false); |
984 | |
985 | #ifdef ENABLE_IPV6 |
986 | if(!use_ipv6) |
987 | #endif |
988 | sock = socket(AF_INET, SOCK_STREAM, 0); |
989 | #ifdef ENABLE_IPV6 |
990 | else |
991 | sock = socket(AF_INET6, SOCK_STREAM, 0); |
992 | #endif |
993 | |
994 | if(CURL_SOCKET_BAD == sock) { |
995 | error = SOCKERRNO; |
996 | logmsg("Error creating socket: (%d) %s" , |
997 | error, strerror(error)); |
998 | goto socks5_cleanup; |
999 | } |
1000 | |
1001 | { |
1002 | /* passive daemon style */ |
1003 | sock = sockdaemon(sock, &port); |
1004 | if(CURL_SOCKET_BAD == sock) { |
1005 | goto socks5_cleanup; |
1006 | } |
1007 | msgsock = CURL_SOCKET_BAD; /* no stream socket yet */ |
1008 | } |
1009 | |
1010 | logmsg("Running %s version" , ipv_inuse); |
1011 | logmsg("Listening on port %hu" , port); |
1012 | |
1013 | wrotepidfile = write_pidfile(pidname); |
1014 | if(!wrotepidfile) { |
1015 | goto socks5_cleanup; |
1016 | } |
1017 | |
1018 | if(portname) { |
1019 | wroteportfile = write_portfile(portname, port); |
1020 | if(!wroteportfile) { |
1021 | goto socks5_cleanup; |
1022 | } |
1023 | } |
1024 | |
1025 | do { |
1026 | juggle_again = incoming(sock); |
1027 | } while(juggle_again); |
1028 | |
1029 | socks5_cleanup: |
1030 | |
1031 | if((msgsock != sock) && (msgsock != CURL_SOCKET_BAD)) |
1032 | sclose(msgsock); |
1033 | |
1034 | if(sock != CURL_SOCKET_BAD) |
1035 | sclose(sock); |
1036 | |
1037 | if(wrotepidfile) |
1038 | unlink(pidname); |
1039 | if(wroteportfile) |
1040 | unlink(portname); |
1041 | |
1042 | restore_signal_handlers(false); |
1043 | |
1044 | if(got_exit_signal) { |
1045 | logmsg("============> socksd exits with signal (%d)" , exit_signal); |
1046 | /* |
1047 | * To properly set the return status of the process we |
1048 | * must raise the same signal SIGINT or SIGTERM that we |
1049 | * caught and let the old handler take care of it. |
1050 | */ |
1051 | raise(exit_signal); |
1052 | } |
1053 | |
1054 | logmsg("============> socksd quits" ); |
1055 | return 0; |
1056 | } |
1057 | |