/* tcpsvd.c - TCP(UDP)/IP service daemon * * Copyright 2013 Ashwini Kumar * Copyright 2013 Sandeep Sharma * Copyright 2013 Kyungwan Han * * 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) }