Home | History | Annotate | Download | only in pending
      1 /* tcpsvd.c - TCP(UDP)/IP service daemon
      2  *
      3  * Copyright 2013 Ashwini Kumar <ak.ashwini (at) gmail.com>
      4  * Copyright 2013 Sandeep Sharma <sandeep.jack2756 (at) gmail.com>
      5  * Copyright 2013 Kyungwan Han <asura321 (at) gmail.com>
      6  *
      7  * No Standard.
      8 
      9 USE_TCPSVD(NEWTOY(tcpsvd, "^<3c#=30<1C:b#=20<0u:l:hEv", TOYFLAG_USR|TOYFLAG_BIN))
     10 USE_TCPSVD(OLDTOY(udpsvd, tcpsvd, TOYFLAG_USR|TOYFLAG_BIN))
     11 
     12 config TCPSVD
     13   bool "tcpsvd"
     14   default n
     15   depends on TOYBOX_FORK
     16   help
     17     usage: tcpsvd [-hEv] [-c N] [-C N[:MSG]] [-b N] [-u User] [-l Name] IP Port Prog
     18     usage: udpsvd [-hEv] [-c N] [-u User] [-l Name] IP Port Prog
     19 
     20     Create TCP/UDP socket, bind to IP:PORT and listen for incoming connection.
     21     Run PROG for each connection.
     22 
     23     IP            IP to listen on, 0 = all
     24     PORT          Port to listen on
     25     PROG ARGS     Program to run
     26     -l NAME       Local hostname (else looks up local hostname in DNS)
     27     -u USER[:GRP] Change to user/group after bind
     28     -c N          Handle up to N (> 0) connections simultaneously
     29     -b N          (TCP Only) Allow a backlog of approximately N TCP SYNs
     30     -C N[:MSG]    (TCP Only) Allow only up to N (> 0) connections from the same IP
     31                   New connections from this IP address are closed
     32                   immediately. MSG is written to the peer before close
     33     -h            Look up peer's hostname
     34     -E            Don't set up environment variables
     35     -v            Verbose
     36 */
     37 
     38 #define FOR_tcpsvd
     39 #include "toys.h"
     40 
     41 GLOBALS(
     42   char *name;
     43   char *user;
     44   long bn;
     45   char *nmsg;
     46   long cn;
     47 
     48   int maxc;
     49   int count_all;
     50   int udp;
     51 )
     52 
     53 struct list_pid {
     54   struct list_pid *next;
     55   char *ip;
     56   int pid;
     57 };
     58 
     59 struct list {
     60   struct list* next;
     61   char *d;
     62   int count;
     63 };
     64 
     65 struct hashed {
     66   struct list *head;
     67 };
     68 
     69 #define HASH_NR 256
     70 struct hashed h[HASH_NR];
     71 struct list_pid *pids = NULL;
     72 
     73 // convert IP address to string.
     74 static char *sock_to_address(struct sockaddr *sock, int flags)
     75 {
     76   char hbuf[NI_MAXHOST] = {0,};
     77   char sbuf[NI_MAXSERV] = {0,};
     78   int status = 0;
     79   socklen_t len = sizeof(struct sockaddr_in6);
     80 
     81   if (!(status = getnameinfo(sock, len, hbuf, sizeof(hbuf), sbuf,
     82           sizeof(sbuf), flags))) {
     83     if (flags & NI_NUMERICSERV) return xmprintf("%s:%s",hbuf, sbuf);
     84     return xmprintf("%s",hbuf);
     85   }
     86   error_exit("getnameinfo: %s", gai_strerror(status));
     87 }
     88 
     89 // Insert pid, ip and fd in the list.
     90 static void insert(struct list_pid **l, int pid, char *addr)
     91 {
     92   struct list_pid *newnode = xmalloc(sizeof(struct list_pid));
     93   newnode->pid = pid;
     94   newnode->ip = addr;
     95   newnode->next = NULL;
     96   if (!*l) *l = newnode;
     97   else {
     98     newnode->next = (*l);
     99    *l = newnode;
    100   }
    101 }
    102 
    103 // Hashing of IP address.
    104 static int haship( char *addr)
    105 {
    106   uint32_t ip[8] = {0,};
    107   int count = 0, i = 0;
    108 
    109   if (!addr) error_exit("NULL ip");
    110   while (i < strlen(addr)) {
    111     while (addr[i] && (addr[i] != ':') && (addr[i] != '.')) {
    112       ip[count] = ip[count]*10 + (addr[i]-'0');
    113       i++;
    114     }
    115     if (i >= strlen(addr)) break;
    116     count++;
    117     i++;
    118   }
    119   return (ip[0]^ip[1]^ip[2]^ip[3]^ip[4]^ip[5]^ip[6]^ip[7])%HASH_NR;
    120 }
    121 
    122 // Remove a node from the list.
    123 static char *delete(struct list_pid **pids, int pid)
    124 {
    125   struct list_pid *prev, *free_node, *head = *pids;
    126   char *ip = NULL;
    127 
    128   if (!head) return NULL;
    129   prev = free_node = NULL;
    130   while (head) {
    131     if (head->pid == pid) {
    132       ip = head->ip;
    133       free_node = head;
    134       if (!prev) *pids = head->next;
    135       else prev->next = head->next;
    136       free(free_node);
    137       return ip;
    138     }
    139     prev = head;
    140     head = head->next;
    141   }
    142   return NULL;
    143 }
    144 
    145 // decrement the ref count fora connection, if count reches ZERO then remove the node
    146 static void remove_connection(char *ip)
    147 {
    148   struct list *head, *prev = NULL, *free_node = NULL;
    149   int hash = haship(ip);
    150 
    151   head = h[hash].head;
    152   while (head) {
    153     if (!strcmp(ip, head->d)) {
    154       head->count--;
    155       free_node = head;
    156       if (!head->count) {
    157         if (!prev) h[hash].head = head->next;
    158         else prev->next = head->next;
    159         free(free_node);
    160       }
    161       break;
    162     }
    163     prev = head;
    164     head = head->next;
    165   }
    166   free(ip);
    167 }
    168 
    169 // Handler function.
    170 static void handle_exit(int sig)
    171 {
    172   int status;
    173   pid_t pid_n = wait(&status);
    174 
    175   if (pid_n <= 0) return;
    176   char *ip = delete(&pids, pid_n);
    177   if (!ip) return;
    178   remove_connection(ip);
    179   TT.count_all--;
    180   if (toys.optflags & FLAG_v) {
    181     if (WIFEXITED(status))
    182       xprintf("%s: end %d exit %d\n",toys.which->name, pid_n, WEXITSTATUS(status));
    183     else if (WIFSIGNALED(status))
    184       xprintf("%s: end %d signaled %d\n",toys.which->name, pid_n, WTERMSIG(status));
    185     if (TT.cn > 1) xprintf("%s: status %d/%d\n",toys.which->name, TT.count_all, TT.cn);
    186   }
    187 }
    188 
    189 // Grab uid and gid
    190 static void get_uidgid(uid_t *uid, gid_t *gid, char *ug)
    191 {
    192   struct passwd *pass = NULL;
    193   struct group *grp = NULL;
    194   char *user = NULL, *group = NULL;
    195   unsigned int n;
    196 
    197   user = ug;
    198   group = strchr(ug,':');
    199   if (group) {
    200     *group = '\0';
    201     group++;
    202   }
    203   if (!(pass = getpwnam(user))) {
    204     n = atolx_range(user, 0, INT_MAX);
    205     if (!(pass = getpwuid(n))) perror_exit("Invalid user '%s'", user);
    206   }
    207   *uid = pass->pw_uid;
    208   *gid = pass->pw_gid;
    209 
    210   if (group) {
    211     if (!(grp = getgrnam(group))) {
    212       n = atolx_range(group, 0, INT_MAX);
    213       if (!(grp = getgrgid(n))) perror_exit("Invalid group '%s'",group);
    214     }
    215   }
    216   if (grp) *gid = grp->gr_gid;
    217 }
    218 
    219 // Bind socket.
    220 static int create_bind_sock(char *host, struct sockaddr *haddr)
    221 {
    222   struct addrinfo hints, *res = NULL, *rp;
    223   int sockfd, ret, set = 1;
    224   char *ptr;
    225   unsigned long port;
    226 
    227   errno = 0;
    228   port = strtoul(toys.optargs[1], &ptr, 10);
    229   if (errno || port > 65535)
    230     error_exit("Invalid port, Range is [0-65535]");
    231   if (*ptr) ptr = toys.optargs[1];
    232   else {
    233     sprintf(toybuf, "%lu", port);
    234     ptr = toybuf;
    235   }
    236 
    237   memset(&hints, 0, sizeof hints);
    238   hints.ai_family = AF_UNSPEC;
    239   hints.ai_socktype = ((TT.udp) ?SOCK_DGRAM : SOCK_STREAM);
    240   if ((ret = getaddrinfo(host, ptr, &hints, &res)))
    241     perror_exit("%s", gai_strerror(ret));
    242 
    243   for (rp = res; rp; rp = rp->ai_next)
    244     if ( (rp->ai_family == AF_INET) || (rp->ai_family == AF_INET6)) break;
    245 
    246   if (!rp) error_exit("Invalid IP %s", host);
    247 
    248   sockfd = xsocket(rp->ai_family, TT.udp ?SOCK_DGRAM :SOCK_STREAM, 0);
    249   setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &set, sizeof(set));
    250   if (TT.udp) setsockopt(sockfd, IPPROTO_IP, IP_PKTINFO, &set, sizeof(set));
    251   if ((bind(sockfd, rp->ai_addr, rp->ai_addrlen)) < 0) perror_exit("Bind failed");
    252   if(haddr) memcpy(haddr, rp->ai_addr, rp->ai_addrlen);
    253   freeaddrinfo(res);
    254   return sockfd;
    255 }
    256 
    257 static void handle_signal(int sig)
    258 {
    259   if (toys.optflags & FLAG_v) xprintf("got signal %d, exit\n", sig);
    260   raise(sig);
    261   _exit(sig + 128); //should not reach here
    262 }
    263 
    264 void tcpsvd_main(void)
    265 {
    266   uid_t uid = 0;
    267   gid_t gid = 0;
    268   pid_t pid;
    269   char haddr[sizeof(struct sockaddr_in6)];
    270   struct list *head, *newnode;
    271   int hash, fd, newfd, j;
    272   char *ptr = NULL, *addr, *server, buf[sizeof(struct sockaddr_in6)];
    273   socklen_t len = sizeof(buf);
    274 
    275   TT.udp = (*toys.which->name == 'u');
    276   if (TT.udp) toys.optflags &= ~FLAG_C;
    277   memset(buf, 0, len);
    278   if (toys.optflags & FLAG_C) {
    279     if ((ptr = strchr(TT.nmsg, ':'))) {
    280       *ptr = '\0';
    281       ptr++;
    282     }
    283     TT.maxc = atolx_range(TT.nmsg, 1, INT_MAX);
    284   }
    285 
    286   fd = create_bind_sock(toys.optargs[0], (struct sockaddr*)&haddr);
    287   if(toys.optflags & FLAG_u) {
    288     get_uidgid(&uid, &gid, TT.user);
    289     setuid(uid);
    290     setgid(gid);
    291   }
    292 
    293   if (!TT.udp && (listen(fd, TT.bn) < 0)) perror_exit("Listen failed");
    294   server = sock_to_address((struct sockaddr*)&haddr, NI_NUMERICHOST|NI_NUMERICSERV);
    295   if (toys.optflags & FLAG_v) {
    296     if (toys.optflags & FLAG_u)
    297       xprintf("%s: listening on %s, starting, uid %u, gid %u\n"
    298           ,toys.which->name, server, uid, gid);
    299     else
    300       xprintf("%s: listening on %s, starting\n", toys.which->name, server);
    301   }
    302   for (j = 0; j < HASH_NR; j++) h[j].head = NULL;
    303   sigatexit(handle_signal);
    304   signal(SIGCHLD, handle_exit);
    305 
    306   while (1) {
    307     if (TT.count_all  < TT.cn) {
    308       if (TT.udp) {
    309         if(recvfrom(fd, NULL, 0, MSG_PEEK, (struct sockaddr *)buf, &len) < 0)
    310           perror_exit("recvfrom");
    311         newfd = fd;
    312       } else {
    313         newfd = accept(fd, (struct sockaddr *)buf, &len);
    314         if (newfd < 0) perror_exit("Error on accept");
    315       }
    316     } else {
    317       sigset_t ss;
    318       sigemptyset(&ss);
    319       sigsuspend(&ss);
    320       continue;
    321     }
    322     TT.count_all++;
    323     addr = sock_to_address((struct sockaddr*)buf, NI_NUMERICHOST);
    324 
    325     hash = haship(addr);
    326     if (toys.optflags & FLAG_C) {
    327       for (head = h[hash].head; head; head = head->next)
    328         if (!strcmp(head->d, addr)) break;
    329 
    330       if (head && head->count >= TT.maxc) {
    331         if (ptr) write(newfd, ptr, strlen(ptr)+1);
    332         close(newfd);
    333         TT.count_all--;
    334         continue;
    335       }
    336     }
    337 
    338     newnode = (struct list*)xzalloc(sizeof(struct list));
    339     newnode->d = addr;
    340     for (head = h[hash].head; head; head = head->next) {
    341       if (!strcmp(addr, head->d)) {
    342         head->count++;
    343         free(newnode);
    344         break;
    345       }
    346     }
    347 
    348     if (!head) {
    349       newnode->next = h[hash].head;
    350       h[hash].head = newnode;
    351       h[hash].head->count++;
    352     }
    353 
    354     if (!(pid = xfork())) {
    355       char *serv = NULL, *clie = NULL;
    356       char *client = sock_to_address((struct sockaddr*)buf, NI_NUMERICHOST | NI_NUMERICSERV);
    357       if (toys.optflags & FLAG_h) { //lookup name
    358         if (toys.optflags & FLAG_l) serv = xstrdup(TT.name);
    359         else serv = sock_to_address((struct sockaddr*)&haddr, 0);
    360         clie = sock_to_address((struct sockaddr*)buf, 0);
    361       }
    362 
    363       if (!(toys.optflags & FLAG_E)) {
    364         setenv("PROTO", TT.udp ?"UDP" :"TCP", 1);
    365         setenv("PROTOLOCALADDR", server, 1);
    366         setenv("PROTOREMOTEADDR", client, 1);
    367         if (toys.optflags & FLAG_h) {
    368           setenv("PROTOLOCALHOST", serv, 1);
    369           setenv("PROTOREMOTEHOST", clie, 1);
    370         }
    371         if (!TT.udp) {
    372           char max_c[32];
    373           sprintf(max_c, "%d", TT.maxc);
    374           setenv("TCPCONCURRENCY", max_c, 1); //Not valid for udp
    375         }
    376       }
    377       if (toys.optflags & FLAG_v) {
    378         xprintf("%s: start %d %s-%s",toys.which->name, getpid(), server, client);
    379         if (toys.optflags & FLAG_h) xprintf(" (%s-%s)", serv, clie);
    380         xputc('\n');
    381         if (TT.cn > 1)
    382           xprintf("%s: status %d/%d\n",toys.which->name, TT.count_all, TT.cn);
    383       }
    384       free(client);
    385       if (toys.optflags & FLAG_h) {
    386         free(serv);
    387         free(clie);
    388       }
    389       if (TT.udp && (connect(newfd, (struct sockaddr *)buf, sizeof(buf)) < 0))
    390           perror_exit("connect");
    391 
    392       close(0);
    393       close(1);
    394       dup2(newfd, 0);
    395       dup2(newfd, 1);
    396       xexec(toys.optargs+2); //skip IP PORT
    397     } else {
    398       insert(&pids, pid, addr);
    399       xclose(newfd); //close and reopen for next client.
    400       if (TT.udp) fd = create_bind_sock(toys.optargs[0],
    401           (struct sockaddr*)&haddr);
    402     }
    403   } //while(1)
    404 }
    405