| /* 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 |
| 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)); |
| if ((bind(sockfd, rp->ai_addr, rp->ai_addrlen)) < 0) perror_exit("Bind failed"); |
| 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 && (connect(newfd, (struct sockaddr *)buf, sizeof(buf)) < 0)) |
| perror_exit("connect"); |
| |
| 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) |
| } |