/* * nsplitd.c * --------- * * $Id: nsplitd.c,v 2.6 1998/09/17 14:28:37 sde1000 Exp $ * * Copyright (c) 1995, University of Cambridge Computer Laboratory, * Copyright (c) 1995, Richard Black, All Rights Reserved. * * * A complete re-implementation of DME's nsplitd for use from inetd * */ /* The basic stream comes in (via inetd) and we then conenct to * somewhere else providing a loop-through service, except we offer * two other ports for connection - one of which gets a second channel * using the top bit to distinguish, and the other is a master control * port (normally used for gdb) which gets complete exclusive access * for its duration. * * Originally designed for multiplexing a xwcons/telnet with a gdb * post-mortem debugging session. * * Here is a picture: * * port0 (from inetd) * 8-bit connection / * made by us <----> nsplitd <-----gdbport (default port0+2) * to host:port/tcp |\ * | port1 (default port0+1) * \ * control (default port0+3) * * If port1 is explicitly disabled (through a command-line option) then * port0 becomes 8-bit clean. */ /* * N.B.: We do NOT support 8 bit stdin/stdout usage on a * /dev/... because to do that right involves much messing with ioctl * and TIOC... etc. If you want to do that sort of thing then the * right way to do it is to chain this onto wconsd (which does know * about and understand all the ioctl and TIOC grief). */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef FALSE #define FALSE 0 #endif #ifndef TRUE #define TRUE 1 #endif #ifndef LOG_DAEMON #define LOG_DAEMON 0 #endif #define DB(x) /* ((x), fflush(stderr)) */ extern char *optarg; extern int optind, opterr, optopt; static char *prog_name; static void usage(void) { fprintf(stderr, "This program (%s) should be run via inetd (tcp)\n\n", prog_name); fprintf(stderr, "usage: %s [-h][-g]" "[-c][-8] host:service\n", prog_name); exit(1); } static void fault(char *format, ...) { va_list ap; char logbuf[1024]; va_start(ap, format); fprintf(stderr, "%s: ", prog_name); vfprintf(stderr, format, ap); fflush(stderr); va_end(ap); /* XXX This is a bit dubious, but there is no vsyslog */ va_start(ap, format); vsnprintf(logbuf, sizeof(logbuf), format, ap); syslog(LOG_ERR, logbuf); va_end(ap); exit(1); } static int getservice(char *name, unsigned short *port) { struct servent *se; if (!name) return -1; if (isdigit(name[0])) *port = atoi(name); else { if (!(se = getservbyname(name, "tcp"))) return -1; *port = ntohs(se->s_port); } return 0; } /* * connect_host: connect to ("name", "port") */ static int connect_host (char *name, unsigned int port) { int fd; struct hostent *hostent; struct sockaddr_in sin; int on; if ((fd = socket (AF_INET, SOCK_STREAM, 0)) < 0) fault("socket"); if (!(hostent = gethostbyname(name))) fault("gethostbyname: %s: %s\n", name, strerror(errno)); memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET; sin.sin_port = htons (port); memcpy(&sin.sin_addr.s_addr, hostent->h_addr, sizeof(struct in_addr)); if (connect(fd, (struct sockaddr *) &sin, sizeof (sin)) < 0) fault("connect: %s:%u: %s\n", name, port, strerror(errno)); on = 1; if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &on, sizeof (on)) < 0) syslog(LOG_WARNING, "setsockopt (TCP_NODELAY): %m"); on = 1; if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof (on)) < 0) syslog(LOG_WARNING, "setsockopt (SO_KEEPALIVE): %m"); return fd; } /* * open a tcp socket and start listening for connections on it */ static int startlistening(unsigned short port) { int fd, on; struct sockaddr_in sin; if ((fd = socket (AF_INET, SOCK_STREAM, 0)) < 0) fault("socket"); on = 1; if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof (on)) < 0) syslog(LOG_WARNING, "setsockopt (SO_REUSEADDR): %m"); memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET; sin.sin_port = htons (port); sin.sin_addr.s_addr = INADDR_ANY; if (bind(fd, &sin, sizeof(sin)) < 0) fault("bind: %u: %s\n", port, strerror(errno)); if (listen(fd, 1) < 0) fault("listen: %s\n", strerror(errno)); return fd; } static void noblock(int fd) { int on=1; if (ioctl(fd, FIONBIO, &on) < 0) fault("ioctl: FIONBIO: %s\n", strerror(errno)); } /* You might not believe this, but fd_sets don't have to be a 32-bit * integer. In particular, in glibc2 it is an array of unsigned * longs. Hence, this hacked up FD_SET_rjb() that works out if it * would have been a nop. */ #define FD_SET_rjb(fd, setp) \ do { \ if ((fd) != 32) \ FD_SET((fd), (setp)); \ } while(0) #define FD_ISSET_rjb(fd, setp) (((fd) != 32)? FD_ISSET((fd), (setp)) : 0) #define MAXSIZE 256 /* ----------------------------------------------------------------- * The main bit of the algorithm. Note we use 32 to mean not connected * because this gives us 1<<32 == 0. We could have done this one * character at a time, but that would have been very inefficient and * not the unix way. */ static int debug; static void doit(int actl, int acto, int lish, int lisg, int lisc) { int acth, actg, actc; int gdbmode = FALSE; char gibuf[MAXSIZE], oibuf[MAXSIZE]; char libuf[MAXSIZE], lobuf[MAXSIZE]; char hibuf[MAXSIZE], hobuf[MAXSIZE]; char ctlbuf[MAXSIZE]; fd_set rdfs, wrfs, exfs; int gicc, oicc, licc, locc, hicc, hocc, ctlcc; char *giptr, *oiptr, *liptr, *loptr, *hiptr, *hoptr; int rc, fromlen; struct sockaddr_in from; gicc = oicc = licc = locc = hicc = hocc = ctlcc = 0; acth = actg = actc = 32; /* XXX yummy */ noblock(actl); noblock(acto); for(;;) { FD_ZERO(&rdfs); FD_ZERO(&wrfs); FD_ZERO(&exfs); /* always take input from the control port (if it's connected) */ FD_SET_rjb(actc, &rdfs); if (gdbmode) { if (oicc) FD_SET_rjb(actg, &wrfs); else FD_SET_rjb(acto, &rdfs); if (gicc) FD_SET_rjb(acto, &wrfs); else FD_SET_rjb(actg, &rdfs); } else { /* There is no such thing as oibuf because its been split into * lobuf and hobuf */ if (locc || hocc) { if (locc) FD_SET_rjb(actl, &wrfs); if (hocc) FD_SET_rjb(acth, &wrfs); } else FD_SET_rjb(acto, &rdfs); if (licc) FD_SET_rjb(acto, &wrfs); else FD_SET_rjb(actl, &rdfs); if (hicc) FD_SET_rjb(acto, &wrfs); else FD_SET_rjb(acth, &rdfs); } if (acth == 32 && lish>=0) FD_SET_rjb(lish, &rdfs); if (actg == 32) FD_SET_rjb(lisg, &rdfs); if (actc == 32) FD_SET_rjb(lisc, &rdfs); /* now make exfs the union of the read and write fd sets, plus * "actl" */ { int i; exfs = rdfs; for(i=0; i<32; i++) /* XXX we only copy fd numbers up to 31 */ if (FD_ISSET(i, &wrfs)) FD_SET_rjb(i, &exfs); FD_SET_rjb(actl, &exfs); } /* XXX AND: can't print something of type fd_set as %x - it * might be an array */ DB(fprintf(stderr, "%s: before select: %08x %08x %08x\n", prog_name, rdfs, wrfs, exfs)); if (select(32, &rdfs, &wrfs, &exfs, NULL) < 0) fault("select: %s\n", strerror(errno)); DB(fprintf(stderr, "%s: after select: %08x %08x %08x\n", prog_name, rdfs, wrfs, exfs)); /* XXX it appears that a non-blocking socket may not show up * correctly in exfs but instead goes readable with no data in * it. Thus we check for zero and goto the appropriate close * method. */ /* Deal with exceptions */ if (FD_ISSET_rjb(actg, &exfs)) { exfs_actg: close(actg); gdbmode = FALSE; oicc = 0; oiptr = oibuf; actg = 32; continue; /* because assumptions changed */ } if (FD_ISSET_rjb(acth, &exfs)) { exfs_acth: close(acth); hicc = hocc = 0; hiptr = hibuf; hoptr = hibuf; acth = 32; continue; /* because assumptions changed */ } if (FD_ISSET_rjb(actl, &exfs) || FD_ISSET_rjb(acto, &exfs)) { exfs_actl: exfs_acto: /* Thats all folks ... */ break; } if (FD_ISSET_rjb(actc, &exfs)) { exfs_ctl: close(actc); actc = 32; ctlcc = 0; continue; } /* Deal with reading */ if (FD_ISSET_rjb(acto, &rdfs)) { if ((oicc = read(acto, oiptr = oibuf, MAXSIZE)) < 0) fault("read acto: %d: %s\n", oicc, strerror(errno)); if (!oicc) goto exfs_acto; if (!gdbmode) { int t; assert((locc == 0) && (hocc == 0)); loptr = lobuf; hoptr = hobuf; if (lish>=0) { for(t=0; t=0 && (FD_ISSET_rjb(lish, &rdfs))) { fromlen = sizeof(from); if ((acth = accept(lish, &from, &fromlen)) < 0) { syslog(LOG_WARNING, "accept: %m"); acth = 32; } else { noblock(acth); hicc = hocc = 0; syslog(LOG_INFO, "highbit client peer is %s:%u\n", inet_ntoa(from.sin_addr), ntohs(from.sin_port)); } } if ((actg == 32) && (FD_ISSET_rjb(lisg, &rdfs))) { fromlen = sizeof(from); if ((actg = accept(lisg, &from, &fromlen)) < 0) { syslog(LOG_WARNING, "accept: %m"); actg = 32; } else { noblock(actg); gicc = 0; gdbmode = TRUE; syslog(LOG_INFO, "gdb client peer is %s:%u\n", inet_ntoa(from.sin_addr), ntohs(from.sin_port)); } } if ((actc == 32) && (FD_ISSET_rjb(lisc, &rdfs))) { fromlen = sizeof(from); if ((actc = accept(lisc, &from, &fromlen)) < 0) { syslog(LOG_WARNING, "accept (ctl): %m"); actc = 32; } else { noblock(actc); syslog(LOG_INFO, "ctl client peer is %s:%u\n", inet_ntoa(from.sin_addr), ntohs(from.sin_port)); } } /* Back to top of loop */ } /* We are bailing because one of the primary connections has gone * away. We close these all explicitly here because that way the * timeout on reusing the port numbers is smnaller. */ close(acth); close(actg); /* XXX AND: why are we closing all these "character counts" ?? */ close(gicc); close(oicc); close(licc); close(locc); close(hicc); close(hocc); } /* * ------------------------------------------------------------ */ int main(int argc, char **argv) { /* In general, suffix "l" is low channel, "h" is high channel, "g" * is gdb channel, "c" is control channel and "o" is output channel. */ struct sockaddr_in from; int infd = 0, outfd; unsigned short portl, porth, portg, portc, porto; int on = 1, c; char *outname, *outservice; int fromlen; int lish, lisg, lisc; #if 0 FILE *newerr; #endif /* 0 */ prog_name = argv[0]; if (isatty(infd)) usage(); /* Here, then not just a simple idiot. */ signal(SIGPIPE, SIG_IGN); openlog(prog_name, LOG_PID, LOG_DAEMON); fromlen = sizeof(from); if (getsockname(infd, &from, &fromlen) < 0) fault("getsockname: %s", strerror(errno)); if ((fromlen != sizeof(from)) || (from.sin_family != AF_INET)) fault("not an inet socket (family=%d)\n", from.sin_family); portl = ntohs(from.sin_port); porth = portl+1; portg = porth+1; portc = portg+1; fromlen = sizeof(from); if (getpeername(infd, &from, &fromlen) < 0) fault("getpeername: %s", strerror(errno)); if ((fromlen != sizeof(from)) || (from.sin_family != AF_INET)) fault("not an inet socket (family=%d)\n", from.sin_family); syslog(LOG_INFO, "on port %u peer is %s:%u\n", portl, inet_ntoa(from.sin_addr), ntohs(from.sin_port)); if (setsockopt(infd, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof (on)) < 0) syslog(LOG_WARNING, "setsockopt (SO_KEEPALIVE): %m"); /* from here on, we map stderr to output on the connection so we can * report errors to the remote user. */ #if 0 if (!(newerr = fdopen(infd, "w"))) syslog(LOG_WARNING, "fdopen: %m"); else *stderr = *newerr; #endif while((c = getopt(argc, argv, "d8h:g:c:")) != EOF) { switch(c) { case 'd': debug++; break; case 'h': /* high bit port */ if (getservice(optarg, &porth) < 0) fault("getservice failed (high port '%s')\n", optarg); break; case 'g': /* gdb port */ if (getservice(optarg, &portg) < 0) fault("getservice failed (gdb port '%s')\n", optarg); break; case 'c': /* control port */ if (getservice(optarg, &portc) < 0) fault("getservice failed (control port '%s')\n", optarg); break; case '8': /* 8-bit clean; no high port */ porth=0; break; default: fault("bad argument list!\n"); } } if (argc != optind + 1) fault("unparsed arguments (%d!=%d)\n", argc, optind+1); outname = argv[optind]; if (!(outservice = strchr(outname, ':'))) fault("output arg '%s' doesn't contain ':'\n", outname); *outservice++ = 0; if (getservice(outservice, &porto) < 0) fault("getservice failed (output port '%s')\n", outservice); /* Time to start the sockets */ if (porth) { lish = startlistening(porth); } else { lish = -1; } lisg = startlistening(portg); lisc = startlistening(portc); outfd = connect_host(outname, porto); doit(infd, outfd, lish, lisg, lisc); syslog(LOG_INFO, "terminating normally\n"); fclose(stderr); closelog(); exit(0); } /* End $Id: nsplitd.c,v 2.6 1998/09/17 14:28:37 sde1000 Exp $ */