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