/* * Stripped-down form of nc. * * Usage: $0 PORT * Usage: $0 ADDR PORT * Usage: $0 -la PATH * Usage: $0 -lc PATH * Any of thse may have [-cmd PATH ARG...] appended. For the modes * that listen for connections, the -cmd can also be -server. * * Listens on the given PORT, or connects to the given ADDR and PORT * (all command-line addresses must be numeric), or listens and * accepts on (with -la) or connects to (with -lc) an AF_LOCAL socket * at PATH. Assuming the connection succeeds, passes data between * stdin and stdout and the connection. Exits only once each * directions reaches EOF. * * By default, uses getaddrinfo() and getnameinfo(), which can handle * v6 as well as v4. Build with -DV4ONLY to use inet_aton and * inet_ntoa instead, which are v4-only but more portable. * * Build with -DLINUX_WORKAROUND on Linux, to deal with some ways in * which it insists on being gratuitously incompatible with BSD. * * If -cmd is given, all further args are a program and args to run; * this means that, instead of stdin and stdout as described above, * data goes to the program's input and output. If -server is used * instead, forks a new process to handle each connection, with the * parent continuing to listen for more connections. * * Because this program is supposed to be maximally portable, we do a * number of things differently from usual. For example, we don't let * loose the full power of gcc+extensions and we deal with include * file dependency bugs by working around them rather than just * assuming they've been fixed. * * This program is in the public domain. */ #include #include #include #include #include #include #include #include #include #include #include #include #ifndef V4ONLY #include #if defined(LINUX_WORKAROUND) && !defined(INFTIM) #define INFTIM (-1) #endif #if defined(LINUX_WORKAROUND) && !defined(POLLRDNORM) #define POLLRDNORM 0 #endif #if defined(LINUX_WORKAROUND) && !defined(POLLWRNORM) #define POLLWRNORM 0 #endif #endif #ifdef LINUX_WORKAROUND #define SIG_FMT "signal %d" #define SIG_ARG(sig) (sig) #else #define SIG_FMT "%s (%s)" #define SIG_ARG(sig) sys_signame[(sig)],sys_siglist[(sig)] #endif extern const char *__progname; typedef struct proc PROC; struct proc { PROC *link; const char *tag; pid_t pid; } ; static int nullfd; static int net; static PROC *procs; static char **cmd; static int cmdn; static int servercmd = 0; static char *arg1 = 0; static char *arg2 = 0; static int local_acc = 0; static int local_conn = 0; static int in_fd; static int out_fd; static void usage(void) { fprintf(stderr,"Usage: %s port\n",__progname); fprintf(stderr,"Usage: %s addr port\n",__progname); fprintf(stderr,"Usage: %s -la path\n",__progname); fprintf(stderr,"Usage: %s -lc path\n",__progname); fprintf(stderr,"Any of the above may have [-cmd PATH ARGS...] appended.\n"); exit(1); } #ifdef V4ONLY #include static void open_ip_accept(const char *port) { struct sockaddr_in sin; struct sockaddr_in fsin; socklen_t flen; int s; s = socket(AF_INET,SOCK_STREAM,0); if (s < 0) { fprintf(stderr,"%s: socket: %s\n",__progname,strerror(errno)); exit(1); } bzero(&sin,sizeof(sin)); /* XXX API botch */ sin.sin_addr.s_addr = INADDR_ANY; sin.sin_port = htons(atoi(port)); sin.sin_family = AF_INET; #ifndef LINUX_WORKAROUND sin.sin_len = sizeof(sin); #endif if (bind(s,(void *)&sin,sizeof(sin)) < 0) { fprintf(stderr,"%s: bind (%d): %s\n", __progname,ntohs(sin.sin_port),strerror(errno)); exit(1); } if (listen(s,1) < 0) { fprintf(stderr,"%s: listen (%d): %s\n", __progname,ntohs(sin.sin_port),strerror(errno)); exit(1); } flen = sizeof(fsin); net = accept(s,(void *)&fsin,&flen); if (net < 0) { fprintf(stderr,"%s: accept (%d): %s\n", __progname,ntohs(sin.sin_port),strerror(errno)); exit(1); } close(s); } static void open_ip_connect(const char *addr, const char *port) { struct sockaddr_in sin; net = socket(AF_INET,SOCK_STREAM,0); if (net < 0) { fprintf(stderr,"%s: socket: %s\n",__progname,strerror(errno)); exit(1); } bzero(&sin,sizeof(sin)); /* XXX API botch */ if (! inet_aton(addr,&sin.sin_addr)) { fprintf(stderr,"%s: %s: bad address\n",__progname,addr); exit(1); } sin.sin_port = htons(atoi(port)); sin.sin_family = AF_INET; #ifndef LINUX_WORKAROUND sin.sin_len = sizeof(sin); #endif if (connect(net,(void *)&sin,sizeof(sin)) < 0) { fprintf(stderr,"%s: connect (%s/%d): %s\n", __progname,inet_ntoa(sin.sin_addr),ntohs(sin.sin_port),strerror(errno)); exit(1); } } #else #include static void open_ip_accept(const char *port) { struct addrinfo hints; struct addrinfo *ai0; struct addrinfo *ai; int e; int na; int *sv; struct pollfd *pfds; struct addrinfo **aiv; int x; int i; int j; struct sockaddr_storage fss; socklen_t fsslen; hints.ai_flags = AI_PASSIVE | AI_NUMERICHOST | AI_NUMERICSERV; hints.ai_family = PF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = 0; hints.ai_addrlen = 0; hints.ai_canonname = 0; hints.ai_addr = 0; hints.ai_next = 0; e = getaddrinfo(0,port,&hints,&ai0); if (e) { fprintf(stderr,"%s: %s: %s\n",__progname,port,gai_strerror(e)); exit(1); } if (! ai0) { fprintf(stderr,"%s: %s: lookup succeeded but returned no addresses\n", __progname,port); exit(1); } na = 0; for (ai=ai0;ai;ai=ai->ai_next) na ++; sv = malloc(na*sizeof(int)); pfds = malloc(na*sizeof(struct pollfd)); aiv = malloc(na*sizeof(struct addrinfo *)); x = 0; for (ai=ai0;ai;ai=ai->ai_next) { sv[x] = socket(ai->ai_family,ai->ai_socktype,ai->ai_protocol); if (sv[x] < 0) { fprintf(stderr,"%s: socket (AF %d): %s\n", __progname,ai->ai_family,strerror(errno)); continue; } if (bind(sv[x],ai->ai_addr,ai->ai_addrlen) < 0) { fprintf(stderr,"%s: bind (AF %d): %s\n", __progname,ai->ai_family,strerror(errno)); continue; } if (listen(sv[x],1) < 0) { fprintf(stderr,"%s: listen (AF %d): %s\n", __progname,ai->ai_family,strerror(errno)); continue; } aiv[x] = ai; x ++; } while (1) { for (i=x-1;i>=0;i--) { pfds[i].fd = sv[i]; pfds[i].events = POLLIN | POLLRDNORM; } if (poll(pfds,x,INFTIM) < 0) { if (errno == EINTR) continue; fprintf(stderr,"%s: poll: %s\n",__progname,strerror(errno)); exit(1); } for (i=x-1;i>=0;i--) { if (pfds[i].revents & (POLLIN|POLLRDNORM|POLLERR|POLLHUP|POLLNVAL)) { fsslen = sizeof(fss); net = accept(sv[i],(void *)&fss,&fsslen); if (net < 0) { fprintf(stderr,"%s: accept (%s, AF %d): %s\n", __progname,port,aiv[i]->ai_family,strerror(errno)); exit(1); } if (servercmd) { pid_t kid; kid = fork(); if (kid < 0) { fprintf(stderr,"%s: fork: %s\n",__progname,strerror(errno)); exit(1); } if (kid) { close(net); continue; } } for (j=x-1;j>=0;j--) close(sv[j]); free(sv); free(pfds); free(aiv); freeaddrinfo(ai0); return; } } } } static void open_ip_connect(const char *addr, const char *port) { struct addrinfo hints; struct addrinfo *ai0; struct addrinfo *ai; int e; char ihost[NI_MAXHOST]; char iserv[NI_MAXSERV]; hints.ai_flags = AI_NUMERICHOST | AI_NUMERICSERV; hints.ai_family = PF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = 0; hints.ai_addrlen = 0; hints.ai_canonname = 0; hints.ai_addr = 0; hints.ai_next = 0; e = getaddrinfo(addr,port,&hints,&ai0); if (e) { fprintf(stderr,"%s: %s/%s: %s\n",__progname,addr,port,gai_strerror(e)); exit(1); } if (! ai0) { fprintf(stderr,"%s: %s/%s: lookup succeeded but returned no addresses\n", __progname,addr,port); exit(1); } for (ai=ai0;ai;ai=ai->ai_next) { net = socket(ai->ai_family,ai->ai_socktype,ai->ai_protocol); if (net < 0) { fprintf(stderr,"%s: socket (AF %d): %s\n", __progname,ai->ai_family,strerror(errno)); continue; } if (connect(net,ai->ai_addr,ai->ai_addrlen) < 0) { e = errno; if (getnameinfo( ai->ai_addr, ai->ai_addrlen, &ihost[0], sizeof(ihost), &iserv[0], sizeof(iserv), NI_NUMERICHOST | NI_NUMERICSERV )) { fprintf(stderr,"%s: connect (can't get text: %s): %s\n", __progname,strerror(errno),strerror(e)); } else { fprintf(stderr,"%s: connect (%s/%s): %s\n", __progname,&ihost[0],&iserv[0],strerror(e)); } close(net); continue; } freeaddrinfo(ai0); return; } exit(1); } #endif static void open_local_accept(const char *path) { int s; int pl; int sl; struct sockaddr_un *s_un; struct sockaddr_un peer; socklen_t peerlen; pl = strlen(path); sl = sizeof(struct sockaddr_un) - sizeof(s_un->sun_path) + strlen(path) + 1; s_un = malloc(sl); s_un->sun_family = AF_LOCAL; #ifndef LINUX_WORKAROUND s_un->sun_len = sl; #endif strcpy(&s_un->sun_path[0],path); s = socket(AF_LOCAL,SOCK_STREAM,0); if (s < 0) { fprintf(stderr,"%s: socket (AF_LOCAL): %s\n",__progname,strerror(errno)); exit(1); } if (bind(s,(void *)s_un,sl) < 0) { fprintf(stderr,"%s: bind (to %s): %s\n",__progname,path,strerror(errno)); exit(1); } if (listen(s,1) < 0) { fprintf(stderr,"%s: listen (AF_LOCAL): %s\n",__progname,strerror(errno)); exit(1); } peerlen = sizeof(peer); net = accept(s,(void *)&peer,&peerlen); if (net < 0) { fprintf(stderr,"%s: accept (on %s): %s\n",__progname,path,strerror(errno)); exit(1); } } static void open_local_connect(const char *path) { int pl; int sl; struct sockaddr_un *s_un; pl = strlen(path); sl = sizeof(struct sockaddr_un) - sizeof(s_un->sun_path) + strlen(path) + 1; s_un = malloc(sl); s_un->sun_family = AF_LOCAL; #ifndef LINUX_WORKAROUND s_un->sun_len = sl; #endif strcpy(&s_un->sun_path[0],path); net = socket(AF_LOCAL,SOCK_STREAM,0); if (net < 0) { fprintf(stderr,"%s: socket (AF_LOCAL): %s\n",__progname,strerror(errno)); exit(1); } if (connect(net,(void *)s_un,sl) < 0) { fprintf(stderr,"%s: bind (to %s): %s\n",__progname,path,strerror(errno)); exit(1); } } static void save_proc(pid_t kid, const char *tag) { PROC *p; p = malloc(sizeof(PROC)); p->tag = tag; p->pid = kid; p->link = procs; procs = p; } static void forkproc(void (*fn)(void), const char *tag) { pid_t kid; fflush(0); kid = fork(); if (kid < 0) { fprintf(stderr,"%s: fork: %s\n",__progname,strerror(errno)); exit(1); } if (kid == 0) { (*fn)(); _exit(1); } save_proc(kid,tag); } static void waitprocs(void) { PROC *p; PROC **pp; pid_t kid; int status; int s; while (1) { if (! procs) break; kid = wait(&status); if (kid < 0) { if (errno == EINTR) continue; fprintf(stderr,"%s: wait: %s\n",__progname,strerror(errno)); exit(1); } pp = &procs; while ((p = *pp)) { if (p->pid == kid) { if (WIFEXITED(status)) { if (WEXITSTATUS(status) != 0) { fprintf(stderr,"%s: %s child: exit %d\n",__progname,p->tag,WEXITSTATUS(status)); } } else if (WIFSIGNALED(status)) { s = WTERMSIG(status); if ((s > 0) && (s < NSIG)) { fprintf(stderr,"%s: %s child: "SIG_FMT"%s\n",__progname,p->tag,SIG_ARG(s),WCOREDUMP(status)?" (core dumped)":""); } else { fprintf(stderr,"%s: %s child: signal %d%s\n",__progname,p->tag,s,WCOREDUMP(status)?" (core dumped)":""); } } else { fprintf(stderr,"%s: %s child: incomprehensible status %d\n",__progname,p->tag,status); } *pp = p->link; free(p); } else { pp = &p->link; } } } } static void x_to_y(int ffd, const char *ftag, int tfd, const char *ttag) { char buf[8192]; int r; int w; int o; while (1) { r = read(ffd,&buf[0],sizeof(buf)); if (r < 0) { if (errno == EINTR) continue; fprintf(stderr,"%s: read from %s: %s\n",__progname,ftag,strerror(errno)); exit(1); } if (r == 0) { shutdown(ffd,SHUT_RD); shutdown(tfd,SHUT_WR); exit(0); } o = 0; while (o < r) { w = write(tfd,&buf[o],r-o); if (w < 0) { if (errno == EINTR) continue; fprintf(stderr,"%s: write to %s: %s\n",__progname,ttag,strerror(errno)); exit(1); } o += w; } } } static void stdin_to_net(void) { close(out_fd); x_to_y(in_fd,"stdin",net,"net"); } static void net_to_stdout(void) { close(in_fd); x_to_y(net,"net",out_fd,"stdout"); } static void handleargs(int ac, char **av) { int skip; int errs; skip = 0; errs = 0; for (ac--,av++;ac;ac--,av++) { if (skip > 0) { skip --; continue; } if (**av != '-') { if (! arg1) { arg1 = *av; } else if (! arg2) { arg2 = *av; if (local_acc || local_conn) usage(); } else { fprintf(stderr,"%s: stray argument `%s'\n",__progname,*av); errs = 1; } continue; } if (!strcmp(*av,"-la")) { local_acc = 1; local_conn = 0; if (arg2) usage(); continue; } if (!strcmp(*av,"-lc")) { local_conn = 1; local_acc = 0; if (arg2) usage(); continue; } if (!strcmp(*av,"-cmd")) { cmd = av + 1; cmdn = ac - 1; if (cmdn < 1) usage(); servercmd = 0; break; } if (!strcmp(*av,"-server")) { cmd = av + 1; cmdn = ac - 1; if (cmdn < 1) usage(); servercmd = 1; break; } fprintf(stderr,"%s: unrecognized option `%s'\n",__progname,*av); errs = 1; } if (! arg1) usage(); if (errs) exit(1); } static void setup_cmd(void) { int ip[2]; int op[2]; pid_t kid; int xp[2]; int e; int n; if ( (socketpair(AF_LOCAL,SOCK_STREAM,0,&ip[0]) < 0) || (socketpair(AF_LOCAL,SOCK_STREAM,0,&op[0]) < 0) || (socketpair(AF_LOCAL,SOCK_STREAM,0,&xp[0]) < 0) ) { fprintf(stderr,"%s: socketpair: %s\n",__progname,strerror(errno)); exit(1); } fflush(0); kid = fork(); if (kid < 0) { fprintf(stderr,"%s: fork: %s\n",__progname,strerror(errno)); exit(1); } if (kid == 0) { close(ip[1]); close(op[0]); close(xp[1]); fcntl(xp[0],F_SETFD,1); close(net); dup2(ip[0],0); close(ip[0]); dup2(op[1],1); close(op[1]); execvp(cmd[0],cmd); e = errno; write(xp[0],&e,sizeof(e)); _exit(1); } close(ip[0]); close(op[1]); close(xp[0]); n = recv(xp[1],&e,sizeof(e),MSG_WAITALL); if (n < 0) { fprintf(stderr,"%s: exec protocol recv: %s\n",__progname,strerror(errno)); exit(1); } if (n == sizeof(e)) { fprintf(stderr,"%s: exec %s: %s\n",__progname,cmd[0],strerror(e)); exit(1); } if (n != 0) { fprintf(stderr,"%s: exec protocol recv: wanted %d or 0, got %d\n",__progname,(int)sizeof(e),n); exit(1); } close(xp[1]); in_fd = op[0]; out_fd = ip[1]; save_proc(kid,"command"); } static void init_stdio(void) { int fd; nullfd = open("/dev/null",O_RDWR,0); if (nullfd < 0) { fprintf(stderr,"%s: /dev/null: %s\n",__progname,strerror(errno)); exit(1); } while (nullfd < 3) { fd = dup(nullfd); if (fd < 0) { fprintf(stderr,"%s: dup: %s\n",__progname,strerror(errno)); exit(1); } nullfd = fd; } } int main(int, char **); int main(int ac, char **av) { handleargs(ac,av); init_stdio(); if (servercmd) signal(SIGCHLD,SIG_IGN); if (arg2) { open_ip_connect(arg1,arg2); } else if (local_acc) { open_local_accept(arg1); } else if (local_conn) { open_local_connect(arg1); } else { open_ip_accept(av[1]); } if (servercmd) signal(SIGCHLD,SIG_DFL); procs = 0; if (cmd) { setup_cmd(); } else { in_fd = 0; out_fd = 1; } forkproc(&stdin_to_net,"input->net"); forkproc(&net_to_stdout,"net->output"); close(net); dup2(nullfd,in_fd); dup2(nullfd,out_fd); waitprocs(); exit(0); }