123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403 |
- /* tcpsvd.c - TCP(UDP)/IP service daemon
- *
- * Copyright 2013 Ashwini Kumar <ak.ashwini@gmail.com>
- * Copyright 2013 Sandeep Sharma <sandeep.jack2756@gmail.com>
- * Copyright 2013 Kyungwan Han <asura321@gmail.com>
- *
- * No Standard.
- USE_TCPSVD(NEWTOY(tcpsvd, "^<3c#=30<1C:b#=20<0u:l:hEv", TOYFLAG_USR|TOYFLAG_BIN))
- USE_TCPSVD(OLDTOY(udpsvd, tcpsvd, TOYFLAG_USR|TOYFLAG_BIN))
- config TCPSVD
- bool "tcpsvd"
- default n
- depends on TOYBOX_FORK
- help
- usage: tcpsvd [-hEv] [-c N] [-C N[:MSG]] [-b N] [-u User] [-l Name] IP Port Prog
- usage: udpsvd [-hEv] [-c N] [-u User] [-l Name] IP Port Prog
-
- Create TCP/UDP socket, bind to IP:PORT and listen for incoming connection.
- Run PROG for each connection.
- IP IP to listen on, 0 = all
- PORT Port to listen on
- PROG ARGS Program to run
- -l NAME Local hostname (else looks up local hostname in DNS)
- -u USER[:GRP] Change to user/group after bind
- -c N Handle up to N (> 0) connections simultaneously
- -b N (TCP Only) Allow a backlog of approximately N TCP SYNs
- -C N[:MSG] (TCP Only) Allow only up to N (> 0) connections from the same IP
- New connections from this IP address are closed
- immediately. MSG is written to the peer before close
- -h Look up peer's hostname
- -E Don't set up environment variables
- -v Verbose
- */
- #define FOR_tcpsvd
- #include "toys.h"
- GLOBALS(
- char *name;
- char *user;
- long bn;
- char *nmsg;
- long cn;
- int maxc;
- int count_all;
- int udp;
- )
- struct list_pid {
- struct list_pid *next;
- char *ip;
- int pid;
- };
- struct list {
- struct list* next;
- char *d;
- int count;
- };
- struct hashed {
- struct list *head;
- };
- #define HASH_NR 256
- struct hashed h[HASH_NR];
- struct list_pid *pids = NULL;
- // convert IP address to string.
- static char *sock_to_address(struct sockaddr *sock, int flags)
- {
- char hbuf[NI_MAXHOST] = {0,};
- char sbuf[NI_MAXSERV] = {0,};
- int status = 0;
- socklen_t len = sizeof(struct sockaddr_in6);
- if (!(status = getnameinfo(sock, len, hbuf, sizeof(hbuf), sbuf,
- sizeof(sbuf), flags))) {
- if (flags & NI_NUMERICSERV) return xmprintf("%s:%s",hbuf, sbuf);
- return xmprintf("%s",hbuf);
- }
- error_exit("getnameinfo: %s", gai_strerror(status));
- }
- // Insert pid, ip and fd in the list.
- static void insert(struct list_pid **l, int pid, char *addr)
- {
- struct list_pid *newnode = xmalloc(sizeof(struct list_pid));
- newnode->pid = pid;
- newnode->ip = addr;
- newnode->next = NULL;
- if (!*l) *l = newnode;
- else {
- newnode->next = (*l);
- *l = newnode;
- }
- }
- // Hashing of IP address.
- static int haship( char *addr)
- {
- uint32_t ip[8] = {0,};
- int count = 0, i = 0;
- if (!addr) error_exit("NULL ip");
- while (i < strlen(addr)) {
- while (addr[i] && (addr[i] != ':') && (addr[i] != '.')) {
- ip[count] = ip[count]*10 + (addr[i]-'0');
- i++;
- }
- if (i >= strlen(addr)) break;
- count++;
- i++;
- }
- return (ip[0]^ip[1]^ip[2]^ip[3]^ip[4]^ip[5]^ip[6]^ip[7])%HASH_NR;
- }
- // Remove a node from the list.
- static char *delete(struct list_pid **pids, int pid)
- {
- struct list_pid *prev, *free_node, *head = *pids;
- char *ip = NULL;
-
- if (!head) return NULL;
- prev = free_node = NULL;
- while (head) {
- if (head->pid == pid) {
- ip = head->ip;
- free_node = head;
- if (!prev) *pids = head->next;
- else prev->next = head->next;
- free(free_node);
- return ip;
- }
- prev = head;
- head = head->next;
- }
- return NULL;
- }
- // decrement the ref count fora connection, if count reches ZERO then remove the node
- static void remove_connection(char *ip)
- {
- struct list *head, *prev = NULL, *free_node = NULL;
- int hash = haship(ip);
- head = h[hash].head;
- while (head) {
- if (!strcmp(ip, head->d)) {
- head->count--;
- free_node = head;
- if (!head->count) {
- if (!prev) h[hash].head = head->next;
- else prev->next = head->next;
- free(free_node);
- }
- break;
- }
- prev = head;
- head = head->next;
- }
- free(ip);
- }
- // Handler function.
- static void handle_exit(int sig)
- {
- int status;
- pid_t pid_n = wait(&status);
- if (pid_n <= 0) return;
- char *ip = delete(&pids, pid_n);
- if (!ip) return;
- remove_connection(ip);
- TT.count_all--;
- if (toys.optflags & FLAG_v) {
- if (WIFEXITED(status))
- xprintf("%s: end %d exit %d\n",toys.which->name, pid_n, WEXITSTATUS(status));
- else if (WIFSIGNALED(status))
- xprintf("%s: end %d signaled %d\n",toys.which->name, pid_n, WTERMSIG(status));
- if (TT.cn > 1) xprintf("%s: status %d/%d\n",toys.which->name, TT.count_all, TT.cn);
- }
- }
- // Grab uid and gid
- static void get_uidgid(uid_t *uid, gid_t *gid, char *ug)
- {
- struct passwd *pass = NULL;
- struct group *grp = NULL;
- char *user = NULL, *group = NULL;
- unsigned int n;
- user = ug;
- group = strchr(ug,':');
- if (group) {
- *group = '\0';
- group++;
- }
- if (!(pass = getpwnam(user))) {
- n = atolx_range(user, 0, INT_MAX);
- if (!(pass = getpwuid(n))) perror_exit("Invalid user '%s'", user);
- }
- *uid = pass->pw_uid;
- *gid = pass->pw_gid;
- if (group) {
- if (!(grp = getgrnam(group))) {
- n = atolx_range(group, 0, INT_MAX);
- if (!(grp = getgrgid(n))) perror_exit("Invalid group '%s'",group);
- }
- }
- if (grp) *gid = grp->gr_gid;
- }
- // Bind socket.
- static int create_bind_sock(char *host, struct sockaddr *haddr)
- {
- struct addrinfo hints, *res = NULL, *rp;
- int sockfd, ret, set = 1;
- char *ptr;
- unsigned long port;
- errno = 0;
- port = strtoul(toys.optargs[1], &ptr, 10);
- if (errno || port > 65535)
- error_exit("Invalid port, Range is [0-65535]");
- if (*ptr) ptr = toys.optargs[1];
- else {
- sprintf(toybuf, "%lu", port);
- ptr = toybuf;
- }
- memset(&hints, 0, sizeof hints);
- hints.ai_family = AF_UNSPEC;
- hints.ai_socktype = ((TT.udp) ?SOCK_DGRAM : SOCK_STREAM);
- if ((ret = getaddrinfo(host, ptr, &hints, &res)))
- perror_exit("%s", gai_strerror(ret));
- for (rp = res; rp; rp = rp->ai_next)
- if ( (rp->ai_family == AF_INET) || (rp->ai_family == AF_INET6)) break;
- if (!rp) error_exit("Invalid IP %s", host);
- sockfd = xsocket(rp->ai_family, TT.udp ?SOCK_DGRAM :SOCK_STREAM, 0);
- setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &set, sizeof(set));
- if (TT.udp) setsockopt(sockfd, IPPROTO_IP, IP_PKTINFO, &set, sizeof(set));
- xbind(sockfd, rp->ai_addr, rp->ai_addrlen);
- if(haddr) memcpy(haddr, rp->ai_addr, rp->ai_addrlen);
- freeaddrinfo(res);
- return sockfd;
- }
- static void handle_signal(int sig)
- {
- if (toys.optflags & FLAG_v) xprintf("got signal %d, exit\n", sig);
- raise(sig);
- _exit(sig + 128); //should not reach here
- }
- void tcpsvd_main(void)
- {
- uid_t uid = 0;
- gid_t gid = 0;
- pid_t pid;
- char haddr[sizeof(struct sockaddr_in6)];
- struct list *head, *newnode;
- int hash, fd, newfd, j;
- char *ptr = NULL, *addr, *server, buf[sizeof(struct sockaddr_in6)];
- socklen_t len = sizeof(buf);
- TT.udp = (*toys.which->name == 'u');
- if (TT.udp) toys.optflags &= ~FLAG_C;
- memset(buf, 0, len);
- if (toys.optflags & FLAG_C) {
- if ((ptr = strchr(TT.nmsg, ':'))) {
- *ptr = '\0';
- ptr++;
- }
- TT.maxc = atolx_range(TT.nmsg, 1, INT_MAX);
- }
-
- fd = create_bind_sock(toys.optargs[0], (struct sockaddr*)&haddr);
- if(toys.optflags & FLAG_u) {
- get_uidgid(&uid, &gid, TT.user);
- setuid(uid);
- setgid(gid);
- }
- if (!TT.udp && (listen(fd, TT.bn) < 0)) perror_exit("Listen failed");
- server = sock_to_address((struct sockaddr*)&haddr, NI_NUMERICHOST|NI_NUMERICSERV);
- if (toys.optflags & FLAG_v) {
- if (toys.optflags & FLAG_u)
- xprintf("%s: listening on %s, starting, uid %u, gid %u\n"
- ,toys.which->name, server, uid, gid);
- else
- xprintf("%s: listening on %s, starting\n", toys.which->name, server);
- }
- for (j = 0; j < HASH_NR; j++) h[j].head = NULL;
- sigatexit(handle_signal);
- signal(SIGCHLD, handle_exit);
- while (1) {
- if (TT.count_all < TT.cn) {
- if (TT.udp) {
- if(recvfrom(fd, NULL, 0, MSG_PEEK, (struct sockaddr *)buf, &len) < 0)
- perror_exit("recvfrom");
- newfd = fd;
- } else {
- newfd = accept(fd, (struct sockaddr *)buf, &len);
- if (newfd < 0) perror_exit("Error on accept");
- }
- } else {
- sigset_t ss;
- sigemptyset(&ss);
- sigsuspend(&ss);
- continue;
- }
- TT.count_all++;
- addr = sock_to_address((struct sockaddr*)buf, NI_NUMERICHOST);
- hash = haship(addr);
- if (toys.optflags & FLAG_C) {
- for (head = h[hash].head; head; head = head->next)
- if (!strcmp(head->d, addr)) break;
- if (head && head->count >= TT.maxc) {
- if (ptr) write(newfd, ptr, strlen(ptr)+1);
- close(newfd);
- TT.count_all--;
- continue;
- }
- }
- newnode = (struct list*)xzalloc(sizeof(struct list));
- newnode->d = addr;
- for (head = h[hash].head; head; head = head->next) {
- if (!strcmp(addr, head->d)) {
- head->count++;
- free(newnode);
- break;
- }
- }
- if (!head) {
- newnode->next = h[hash].head;
- h[hash].head = newnode;
- h[hash].head->count++;
- }
- if (!(pid = xfork())) {
- char *serv = NULL, *clie = NULL;
- char *client = sock_to_address((struct sockaddr*)buf, NI_NUMERICHOST | NI_NUMERICSERV);
- if (toys.optflags & FLAG_h) { //lookup name
- if (toys.optflags & FLAG_l) serv = xstrdup(TT.name);
- else serv = sock_to_address((struct sockaddr*)&haddr, 0);
- clie = sock_to_address((struct sockaddr*)buf, 0);
- }
- if (!(toys.optflags & FLAG_E)) {
- setenv("PROTO", TT.udp ?"UDP" :"TCP", 1);
- setenv("PROTOLOCALADDR", server, 1);
- setenv("PROTOREMOTEADDR", client, 1);
- if (toys.optflags & FLAG_h) {
- setenv("PROTOLOCALHOST", serv, 1);
- setenv("PROTOREMOTEHOST", clie, 1);
- }
- if (!TT.udp) {
- char max_c[32];
- sprintf(max_c, "%d", TT.maxc);
- setenv("TCPCONCURRENCY", max_c, 1); //Not valid for udp
- }
- }
- if (toys.optflags & FLAG_v) {
- xprintf("%s: start %d %s-%s",toys.which->name, getpid(), server, client);
- if (toys.optflags & FLAG_h) xprintf(" (%s-%s)", serv, clie);
- xputc('\n');
- if (TT.cn > 1)
- xprintf("%s: status %d/%d\n",toys.which->name, TT.count_all, TT.cn);
- }
- free(client);
- if (toys.optflags & FLAG_h) {
- free(serv);
- free(clie);
- }
- if (TT.udp) xconnect(newfd, (struct sockaddr *)buf, sizeof(buf));
- close(0);
- close(1);
- dup2(newfd, 0);
- dup2(newfd, 1);
- xexec(toys.optargs+2); //skip IP PORT
- } else {
- insert(&pids, pid, addr);
- xclose(newfd); //close and reopen for next client.
- if (TT.udp) fd = create_bind_sock(toys.optargs[0],
- (struct sockaddr*)&haddr);
- }
- } //while(1)
- }
|