/* * serialconsole [-daemon ] * [-net ] * [-console] * [-speed baudrate] * [-lockspeed] * [-nodown] * [-log logfile] * [-script logfile] * [-addr ] * [-esc ] * [-noesc] * [-hex] * [-captive] * [-recap ] * [-parity ] * [-ro] * -port * * With -daemon, runs a daemon talking to , listening on * . By default, listens only on 127.0.0.1; -addr can be * given to specify the IP address, or "any" or "all" to specify * INADDR_ANY. -speed may be given to specify the initial baudrate * (the default is 9600). -lockspeed specifies that client requests * to change the baudrate are to be ignored. -nodown specifies that * client up/down requests are to be ignored. * * -console is somewhat like -daemon, except that instead of listening * to a specific serial port, it opens up a pty pair. When there is * at least one client connection, it sets the pty as console with * TIOCCONS, releasing it when there are no client connections. Recap * requests are ignored in this mode. * * -net is like -daemon, except that instead of talking to a tty * device, it opens a connection to port on address * (which can be anything acceptable to getaddrinfo(3)), communicating * with whatever that gives. Breaks and speed changes are ignored. * * Without -daemon, contacts an existing daemon on (with * similar defaulting for the IP address, but without INADDR_ANY). * -esc can be specified to control the escape character, which * defaults to ^], same as telnet; -noesc means no escape character * will be recognized. -recap can be specified to get the effect of * entering the command line and giving a recap command, then * returning to communication, immediately upon startup. -captive * disables the commands to exit, suspend, and run an external * program, but nothing else. -parity controls how the 0x80 bit is * treated. Two specifiers are given; the first one specifies how * characters received from the daemon are treated, the second how * typed characters are treated. Each specifier begins with a * character specifying what test is to be applied to received * characters to determine which ones to drop; this character can be * "n" to specify that no characters are to be dropped, "e" specifying * that only even parity is to be kept, "o" specifying that only odd * parity is to be kept, "0" specifying that only zero parity is to be * kept, or "1" specifying that only one parity is to be kept. * Following this is a second character specifying what is to be done * with the parity bit of the accepted characters before they are * output. This second character can be "n", indicating no * modification (accepted characters are output as they are received), * "e" indicating that parity is to be forced to even, "o" indicating * that parity is to be forced to odd, "0" indicating that parity is * to be forced to zero, or "1" indicating that parity is to be forced * to one. Thus, to list a few examples, "nn" produces an 8-bit-clean * channel; "n0" strips the high bit, "e0" accepts only even parity * and strips the parity bits, "on" accepts only odd parity and passes * it through. If -parity is not specified, "nn" is the default. * Note that the escape character is checked for on input before the * parity processing is applied. * * -log is used only with -daemon; it causes serialconsole to write a * detailed log of everything that happens to a logfile. If the * logfile already exists it is appended to. The logfile may be `-' * to write the log to serialconsole's stdout. * * -script is used only with -daemon; it causes serialconsole to write * a transcript of output data - data received by serialconsole from * the serial line - to a logfile. If the logfile already exists it * is appended to. The logfile may be `-' to write the transcript to * serialconsole's stdout. * * The difference between -log and -script lies in what is logged and * how. -script logs data in only one direction, and logs it * verbatim, whereas -log logs data in both directions, events such as * clients connecting and disconnecting, and writes nonprintable * characters as escape sequences. * * Using -daemon and -esc together is an error, as is using -addr any, * -addr all, or -log without -daemon. -addr can be given multiple * times; with -daemon, this specifies listening on multiple * addresses, whereas without -daemon, this specifies multiple * addresses to try until connection succeeds. -daemon and -recap * together is also an error. * * -hex specifies hex mode right from startup (see below). * * The escape language, where X is the escape character: * * X? Print a brief summary of the escape commands * X. Disconnect and exit * Xz Suspend * X# Generate a break * X (space) Ignore the escape character. * XxAB `Type' character 0xAB, overriding parity settings * X% Enter a command prompt, at which the following commands * are accepted: * quit * exit * Disconnect and exit. * z * suspend * Suspend. * break * Generate a break. * recap N * Re-output the last N bytes of output. * There is a limit on N. * cont * go * Resume communication. * vis * Toggle (and report) whether * nonprintable characters are displayed * as themselves or are printed as * multi-character escapes. * hex * Toggle (and report) hex mode. When hex * mode is on, every data byte from the * server is printed as three characters, * a space and two hex digits. (This is * like a stronger version of vis, above.) * speed N * Request that the daemon change the * baudrate on the serial line. (The * daemon may or may not pay attention.) * iparity xx * Sets the parity spec for typed * characters (see the -parity option, * above). * oparity xx * Sets the parity spec for characters * received from the server (see the * -parity option, above). * run cmdline * Run the given command line with stdin * and stdout connected to the serial * line. When it exits, a note is printed * and communication is resumed. * down * Request the daemon stop doing I/O on * the serial line, but hold it open. * This allows other programs to access * it, without losing the settings (eg, * baudrate) in use. (Speed changes are * honoured when down, as a convenient way * to set the baudrate.) * up * Request the daemon resume I/O on the * serial line, countering an earlier * `down'. * * Network protocol: * * On initial connection, each side sends a "magic number" string, * which is "xpjrafsbfmeoooekqkfulezydre", followed by a NUL, * followed by its version number (decimal ASCII), followed by * another NUL. Each side then decides whether it is willing to * continue communicating. If so, it sends a third NUL; if not, * it closes the connection. The version number sent is the * highest version the sending peer supports; if they differ, * communication (if it happens at all) will use the protocol * appropriate to lower of the two. * * The protocol described herein is version 1. * * Each data stream is a stream of "packets", wrapped in a simple * serializing protocol. All multi-octet values are sent * big-endian. The first octet of each packet describes what type * of packet it is and what its contents are: * * 0x00 Short data packet, contains one octet of data length, * followed by that many octets of normal serial-line * data. * * 0x01 Long data packet, contains two octets of data length * followed by that many octets of normal serial-line * data. * * 0x02 Break packet, contains no following data. This * represents a BREAK condition in the data stream and * occurs only in the client->server data stream. * * 0x03 Recap packet, contains two further octets containing a * count of octets of data which should be replayed. The * replayed data is sent using normal data packets. This * occurs only in the client->server data stream. * * 0x04 Flow control, contains one or more octets of data. * First octet specifies a subtype: * value C->S? S->C? Meaning * 0x00 Y N Stop sending * 0x01 Y N Resume sending * 0x02 N Y Ack 0x00 * 0x03 N Y Ack 0x01 * 0x04 Y N Set buffer size; includes two * further octets, which are the * buffer size. * 0x05 N Y This much data got dropped; * includes four octets of dropped * data octet count. * Every client has a flow control state maintained by the * server; when sending is stopped for a connection, data * for that connection is buffered up to some limit, with * old data discarded if the buffer gets full. When * sending is resumed, the buffered data is dumped. The * buffering limit can be set with the 0x04 subrequest. * The default buffer size is zero. * * 0x05 Speed-change packet, contains four further octets * containing a new baudrate. The server never sends * anything in response; it may or may not pay attention * to the request. * * 0x06 Up/down packet. Contains one further octet, which is * 0x00 for a `down' request and 0x01 for an `up'. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include extern const char *__progname; static const char magic[] = "xpjrafsbfmeoooekqkfulezydre"; static const int magic_len = 27; static const char *arg_daemondev; static int arg_console; static const char *arg_netaddr; static const char *arg_netport; static int daemon_mode; static const char *arg_logfile; static const char *arg_scriptfile; static int arg_speed; static int arg_lockspeed; static int arg_nodown; static int arg_nipaddress; static const char **arg_ipaddress; static const char *arg_escchar; static int arg_noesc; static int arg_captive; static const char *arg_portnum; static const char *arg_recap; static int arg_debug; static int arg_parity; static int arg_ro; #define P_ACC 0x0f #define P_MOD 0xf0 #define P_N 0x00 #define P_E 0x11 #define P_O 0x22 #define P_0 0x33 #define P_1 0x44 static int iparity = (P_N&P_ACC) | (P_N&P_MOD); static int oparity = (P_N&P_ACC) | (P_N&P_MOD); static int do_vis = 0; static int do_hex = 0; static volatile int want_dumpblocks = 0; #define MAGIC 0x9c88ce11 #define GUARD1 0x767dde24 #define GUARD2 0x4f5c2bf7 #define GUARD3 0xd73b6aca #define GUARD4 0xe18013b7 #define TRASH 0x82dcb924 /*fea9930a193bf30b*/ typedef struct header HEADER; typedef struct trailer TRAILER; struct header { unsigned long int magic; void *flink; void *blink; int nb; const char *file; int line; unsigned long int guard1; unsigned long int guard2; } ; struct trailer { unsigned long int guard3; unsigned long int guard4; } ; static void *root_f = 0; static void *root_b = 0; static void malpanic(const char *why) { fprintf(stderr,"malloc panic: %s\n",why); abort(); } static void link_in(HEADER *h, void *base) { h->flink = root_f; h->blink = 0; root_f = base; if (h->flink) { HEADER h2; bcopy(h->flink,&h2,sizeof(HEADER)); if (h2.magic != MAGIC) malpanic("link_in: bad magic number"); if (h2.guard1 != GUARD1) malpanic("link_in: guard 1 wrong"); if (h2.guard2 != GUARD2) malpanic("link_in: guard 2 wrong"); h2.blink = base; bcopy(&h2,h->flink,sizeof(HEADER)); } else { root_b = base; } } static void link_out(HEADER *h) { HEADER h2; if (h->flink == 0) { root_b = h->blink; } else { bcopy(h->flink,&h2,sizeof(HEADER)); if (h2.magic != MAGIC) malpanic("link_out: bad magic number"); if (h2.guard1 != GUARD1) malpanic("link_out: guard 1 wrong"); if (h2.guard2 != GUARD2) malpanic("link_out: guard 2 wrong"); h2.blink = h->blink; bcopy(&h2,h->flink,sizeof(HEADER)); } if (h->blink == 0) { root_f = h->flink; } else { bcopy(h->blink,&h2,sizeof(HEADER)); if (h2.magic != MAGIC) malpanic("link_out: bad magic number"); if (h2.guard1 != GUARD1) malpanic("link_out: guard 1 wrong"); if (h2.guard2 != GUARD2) malpanic("link_out: guard 2 wrong"); h2.flink = h->flink; bcopy(&h2,h->blink,sizeof(HEADER)); } } static void fill_with_trash(char *blk, int len) { static const int trashval = TRASH; int n; if (len <= sizeof(trashval)) { bcopy(&trashval,blk,len); return; } bcopy(&trashval,blk,sizeof(trashval)); n = sizeof(trashval); while (2*n <= len) { bcopy(blk,blk+n,n); n *= 2; } if (n < len) bcopy(blk,blk+n,len-n); } static void *sc_malloc(size_t nb, const char *file, int line) { char *x; HEADER h; TRAILER t; x = malloc(sizeof(HEADER)+nb+sizeof(TRAILER)); h.magic = MAGIC; h.nb = nb; h.file = file; h.line = line; h.guard1 = GUARD1; h.guard2 = GUARD2; t.guard3 = GUARD3; t.guard4 = GUARD4; link_in(&h,x); bcopy(&h,x,sizeof(HEADER)); bcopy(&t,x+sizeof(HEADER)+nb,sizeof(TRAILER)); fill_with_trash(x+sizeof(HEADER),nb); return(x+sizeof(HEADER)); } static void *sc_realloc(void *blk, size_t nb, const char *file, int line) { char *o; char *n; HEADER h; TRAILER t; if (blk) { o = blk; bcopy(o-sizeof(HEADER),&h,sizeof(HEADER)); if (h.magic != MAGIC) malpanic("realloc: bad magic number"); if (h.guard1 != GUARD1) malpanic("realloc: guard 1 wrong"); if (h.guard2 != GUARD2) malpanic("realloc: guard 2 wrong"); bcopy(o+h.nb,&t,sizeof(TRAILER)); if (t.guard3 != GUARD3) malpanic("realloc: guard 3 wrong"); if (t.guard4 != GUARD4) malpanic("realloc: guard 4 wrong"); h.magic = ~MAGIC; h.guard1 = ~GUARD1; h.guard2 = ~GUARD2; t.guard3 = ~GUARD3; t.guard4 = ~GUARD4; link_out(&h); bcopy(&h,o-sizeof(HEADER),sizeof(HEADER)); bcopy(&t,o+h.nb,sizeof(TRAILER)); n = realloc(o-sizeof(HEADER),sizeof(HEADER)+nb+sizeof(TRAILER)); } else { n = malloc(sizeof(HEADER)+nb+sizeof(TRAILER)); h.nb = 0; } if (nb > h.nb) fill_with_trash(n+sizeof(HEADER)+h.nb,nb-h.nb); h.magic = MAGIC; h.nb = nb; h.file = file; h.line = line; h.guard1 = GUARD1; h.guard2 = GUARD2; t.guard3 = GUARD3; t.guard4 = GUARD4; link_in(&h,n); bcopy(&h,n,sizeof(HEADER)); bcopy(&t,n+sizeof(HEADER)+nb,sizeof(TRAILER)); return(n+sizeof(HEADER)); } static void sc_free(void *blk) { char *o; HEADER h; TRAILER t; if (! blk) return; o = blk; bcopy(o-sizeof(HEADER),&h,sizeof(HEADER)); if (h.magic != MAGIC) malpanic("free: bad magic number"); if (h.guard1 != GUARD1) malpanic("free: guard 1 wrong"); if (h.guard2 != GUARD2) malpanic("free: guard 2 wrong"); bcopy(o+h.nb,&t,sizeof(TRAILER)); if (t.guard3 != GUARD3) malpanic("free: guard 3 wrong"); if (t.guard4 != GUARD4) malpanic("free: guard 4 wrong"); h.magic = ~MAGIC; h.guard1 = ~GUARD1; h.guard2 = ~GUARD2; t.guard3 = ~GUARD3; t.guard4 = ~GUARD4; link_out(&h); bcopy(&h,o-sizeof(HEADER),sizeof(HEADER)); bcopy(&t,o+h.nb,sizeof(TRAILER)); free(o-sizeof(HEADER)); } static void dumpblocks(void) { char *b; HEADER h; TRAILER t; void *desb; printf("========\n"); b = root_f; desb = 0; while (b) { bcopy(b,&h,sizeof(HEADER)); if (h.magic != MAGIC) malpanic("dumpblocks: bad magic number"); if (h.guard1 != GUARD1) malpanic("dumpblocks: guard 1 wrong"); if (h.guard2 != GUARD2) malpanic("dumpblocks: guard 2 wrong"); bcopy(b+sizeof(HEADER)+h.nb,&t,sizeof(TRAILER)); if (t.guard3 != GUARD3) malpanic("dumpblocks: guard 3 wrong"); if (t.guard4 != GUARD4) malpanic("dumpblocks: guard 4 wrong"); if (h.blink != desb) malpanic("dumpblocks: backlink wrong"); printf("[\"%s\", line %d: %d at %p]\n",h.file,h.line,h.nb,b+sizeof(HEADER)); desb = b; b = h.flink; } if (root_b != desb) { malpanic("dumpblocks: root backlink wrong"); } fflush(stdout); } #define malloc(x) sc_malloc((x),__FILE__,__LINE__) #define realloc(x,y) sc_realloc((x),(y),__FILE__,__LINE__) #define free(x) sc_free(x) static int addr_is_any(const char *s) { return(!strcmp(s,"all") || !strcmp(s,"any")); } static const char *setparity(int *parityp, const char *s) { int v[2]; int i; if (!s[0] || !s[1] || s[2]) return("length wrong"); for (i=0;i<2;i++) { switch (s[i]) { case 'n': v[i] = P_N; break; case 'e': v[i] = P_E; break; case 'o': v[i] = P_O; break; case '0': v[i] = P_0; break; case '1': v[i] = P_1; break; default: return("bad character"); break; } } *parityp = (v[0] & P_ACC) | (v[1] & P_MOD); return(0); } static void handleargs(int ac, char **av) { int errs; int skip; int nneed; errs = 0; skip = 0; for (ac--,av++;ac;ac--,av++) { if (skip > 0) { skip --; continue; } if (0) { needarg:; switch (nneed) { case 1: fprintf(stderr,"%s: %s needs another argument\n",__progname,av[0]); break; default: fprintf(stderr,"%s: %s needs %d more arguments\n",__progname,av[0],nneed); break; } errs ++; continue; } #define WANTARGS(n) do { nneed = (n); if ((skip+=nneed) >= ac) goto needarg; } while (0) if (! strcmp(av[0],"-daemon")) { WANTARGS(1); arg_daemondev = av[skip]; arg_console = 0; arg_netaddr = 0; daemon_mode = 1; } else if (! strcmp(av[0],"-console")) { arg_daemondev = 0; arg_console = 1; arg_netaddr = 0; daemon_mode = 1; } else if (! strcmp(av[0],"-net")) { WANTARGS(2); arg_daemondev = 0; arg_console = 0; arg_netaddr = av[skip-1]; arg_netport = av[skip]; daemon_mode = 1; } else if (! strcmp(av[0],"-log")) { WANTARGS(1); arg_logfile = av[skip]; } else if (! strcmp(av[0],"-script")) { WANTARGS(1); arg_scriptfile = av[skip]; } else if (! strcmp(av[0],"-speed")) { WANTARGS(1); arg_speed = atoi(av[skip]); } else if (! strcmp(av[0],"-addr")) { WANTARGS(1); arg_ipaddress = realloc(arg_ipaddress,(arg_nipaddress+1)*sizeof(char *)); arg_ipaddress[arg_nipaddress++] = av[skip]; } else if (!strcmp(av[0],"-esc")) { WANTARGS(1); arg_escchar = av[skip]; arg_noesc = 0; } else if (!strcmp(av[0],"-port")) { WANTARGS(1); arg_portnum = av[skip]; } else if (! strcmp(av[0],"-recap")) { WANTARGS(1); arg_recap = av[skip]; } else if (! strcmp(av[0],"-noesc")) { arg_noesc = 1; arg_escchar = 0; } else if (! strcmp(av[0],"-captive")) { arg_captive = 1; } else if (! strcmp(av[0],"-ro")) { arg_ro = 1; } else if (! strcmp(av[0],"-lockspeed")) { arg_lockspeed = 1; } else if (! strcmp(av[0],"-nodown")) { arg_nodown = 1; } else if (! strcmp(av[0],"-debug")) { arg_debug = 1; } else if (! strcmp(av[0],"-hex")) { do_hex = 1; } else if (! strcmp(av[0],"-parity")) { const char *err; WANTARGS(2); arg_parity = 1; err = setparity(&oparity,av[skip-1]); if (err) { fprintf(stderr,"%s: bad -parity argument (%s): %s\n",__progname,err,av[skip-1]); errs ++; } err = setparity(&iparity,av[skip]); if (err) { fprintf(stderr,"%s: bad -parity argument (%s): %s\n",__progname,err,av[skip]); errs ++; } } else if (av[0][0] == '-') { fprintf(stderr,"%s: unrecognized flag %s\n",__progname,av[0]); errs ++; } else { fprintf(stderr,"%s: unrecognized argument %s\n",__progname,av[0]); errs ++; } } if (errs) exit(1); if (daemon_mode) { if (arg_escchar) { fprintf(stderr,"%s: daemon operation inconsistent with -esc\n",__progname); errs ++; } if (arg_noesc) { fprintf(stderr,"%s: daemon operation inconsistent with -noesc\n",__progname); errs ++; } if (arg_captive) { fprintf(stderr,"%s: daemon operation inconsistent with -captive\n",__progname); errs ++; } if (arg_recap) { fprintf(stderr,"%s: daemon operation inconsistent with -recap\n",__progname); errs ++; } if (arg_parity) { fprintf(stderr,"%s: daemon operation inconsistent with -parity\n",__progname); errs ++; } if (do_hex) { fprintf(stderr,"%s: daemon operation inconsistent with -hex\n",__progname); errs ++; } } else { int i; if (arg_logfile) { fprintf(stderr,"%s: -log requires daemon operation\n",__progname); errs ++; } if (arg_scriptfile) { fprintf(stderr,"%s: -script requires daemon operation\n",__progname); errs ++; } for (i=0;i= v) { l ++; v *= 10; } return(l); } static void findpty(int *mfdp, int *sfdp) { int i; int mfd; int sfd; char ptyname[16]; for (i=0;;i++) { sprintf(&ptyname[0],"/dev/pty%c%x",'p'+(i/16),i%16); mfd = open(&ptyname[0],O_RDWR,0); if (mfd < 0) { int e; e = errno; if (e == ENOENT) { fprintf(stderr,"%s: all pseudo-ttys in use\n",__progname); exit(1); } continue; } ptyname[5] = 't'; /* sprintf(&ptyname[0],"/dev/tty%c%x",'p'+(i/16),i%16); */ sfd = open(&ptyname[0],O_RDWR,0); if (sfd < 0) { close(mfd); continue; } *mfdp = mfd; *sfdp = sfd; arg_daemondev = strdup(&ptyname[0]); return; } } static void nodelay(int s) { int val; val = 1; setsockopt(s,IPPROTO_TCP,TCP_NODELAY,&val,sizeof(val)); } static int net_connection(const char *a, const char *p) { struct addrinfo hints; struct addrinfo *ai0; struct addrinfo *ai; int e; int se; int s; hints.ai_flags = 0; 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(a,p,&hints,&ai0); if (e) { fprintf(stderr,"%s: %s/%s: %s\n",__progname,a,p,gai_strerror(e)); exit(1); } if (! ai0) { fprintf(stderr,"%s: %s/%s: successful lookup but no addresses?\n",__progname,a,p); exit(1); } se = 0; for (ai=ai0;ai;ai=ai->ai_next) { s = socket(ai->ai_family,ai->ai_socktype,ai->ai_protocol); if (s < 0) { if (se == 0) se = errno; continue; } se = -1; if (connect(s,ai->ai_addr,ai->ai_addrlen) < 0) { char hnbuf[NI_MAXHOST]; char pnbuf[NI_MAXSERV]; e = errno; if (getnameinfo(ai->ai_addr,ai->ai_addrlen,&hnbuf[0],NI_MAXHOST,&pnbuf[0],NI_MAXSERV,NI_NUMERICHOST|NI_NUMERICSERV)) { fprintf(stderr,"%s: %s/%s: connect failed [%s], can't get numeric hostname info [%s]\n",__progname,a,p,strerror(e),strerror(errno)); } else { if (ai0->ai_next) { fprintf(stderr,"%s: connect %s [%s/%s]: %s\n",__progname,a,&hnbuf[0],&pnbuf[0],strerror(e)); } else { fprintf(stderr,"%s: connect %s/%s: %s\n",__progname,&hnbuf[0],&pnbuf[0],strerror(e)); } } close(s); continue; } freeaddrinfo(ai0); nodelay(s); return(s); } if (se > 0) { fprintf(stderr,"%s: socket: %s\n",__progname,strerror(se)); } else if (se == 0) { fprintf(stderr,"%s: %s/%s: failure but no error?\n",__progname,a,p); } exit(1); } static void run_daemon(void) { int ttyfd; int ptyfd; int netfd; char *serstr; int *acc; struct in_addr *accaddr; int nacc; int i; int j; struct sockaddr_in sin; int sinlen; fd_set rfds; fd_set wfds; typedef struct conn CONN; struct conn { CONN *link; int state; #define CS_MAGIC 1 #define CS_VERSION 2 #define CS_CONFNUL 3 #define CS_DATA 4 #define CS_DEAD 5 int state_n; int pkt_len; int fd; unsigned char *pktbuf; int pktbn; int wbh; int wbt; int wbn; int xoff; int xofflim; char *xoffq; int xoffdrop; int xoffh; int xofft; int xoffn; struct sockaddr_in remaddr; unsigned char rbuf[8192]; char wbuf[70000]; } ; CONN *conns; char recap_ring[65536]; int recap_h; int recap_n; struct termios tstate; int logfd; FILE *logf; FILE *logmsgf; int log_intype; CONN *log_inconn; int log_col; /* types for log_intype, and first arg to write_log */ #define LOG_NONE 0 /* marker - none of the below */ #define LOG_RD 1 /* data read from tty: data, len */ #define LOG_WR 2 /* data written to tty: conn, data, len */ #define LOG_MSG 3 /* other messages: printf-fmt, printf-args */ int scriptfd; int line_down; int logmsgwrite(void *arg __attribute__((__unused__)), const char *data, int len) { int len0; const char *dp; int ll; if (len < 1) return(len); len0 = len; while (1) { dp = data; ll = len; while ((ll > 0) && (*dp != '\n')) { ll --; dp ++; } if (log_intype == LOG_NONE) { fprintf(logf,"*** "); log_intype = LOG_MSG; } if (ll == 0) { fwrite(data,1,len,logf); break; } fwrite(data,1,(dp+1)-data,logf); log_intype = LOG_NONE; data = dp + 1; len = ll - 1; if (len == 0) break; } return(len0); } void open_log(void) { if (! strcmp(arg_logfile,"-")) { logfd = dup(1); } else { logfd = open(arg_logfile,O_WRONLY|O_APPEND|O_CREAT,0666); } if (logfd < 0) { fprintf(stderr,"%s: can't write -log file %s: %s\n",__progname,arg_logfile,strerror(errno)); exit(1); } logf = fdopen(logfd,"a"); if (logf == 0) { fprintf(stderr,"%s: can't fdopen %s: %s\n",__progname,arg_logfile,strerror(errno)); exit(1); } logmsgf = fwopen(0,&logmsgwrite); if (logmsgf == 0) { fprintf(stderr,"%s: can't fwopen msg stream: %s\n",__progname,strerror(errno)); exit(1); } log_intype = LOG_NONE; } void open_script(void) { if (! strcmp(arg_scriptfile,"-")) { scriptfd = dup(1); } else { scriptfd = open(arg_scriptfile,O_WRONLY|O_APPEND|O_CREAT,0666); } if (scriptfd < 0) { fprintf(stderr,"%s: can't write -script file %s: %s\n",__progname,arg_logfile,strerror(errno)); exit(1); } } void log_chgtype(void) { if (log_intype != LOG_NONE) fprintf(logf,"\n"); log_intype = LOG_NONE; } void log_escaped_data(const void *data, int len) { const char *dp; unsigned char c; for (dp=data;len>0;len--,dp++) { if (log_col > 72) { fprintf(logf,"\n "); log_col = 4; } c = *dp; if ((c < 32) || ((c > 126) && (c < 160))) { char pc; switch (c) { case 7: pc = 'a'; break; case 8: pc = 'b'; break; case 9: pc = 't'; break; case 10: pc = 'n'; break; case 12: pc = 'f'; break; case 13: pc = 'r'; break; case 27: pc = 'e'; break; default: pc = 0; break; } if (pc) { putc('\\',logf); putc(pc,logf); log_col += 2; } else { if ((len > 1) && (dp[1] >= '0') && (dp[1] <= '9')) { fprintf(logf,"\\%03o",c); log_col += 4; } else { fprintf(logf,"\\%o",c); if (c < 8) log_col += 2; else if (c < 8*8) log_col += 3; else log_col += 4; } } } else if (c == '\\') { fprintf(logf,"\\\\"); log_col += 2; } else { putc(c,logf); log_col ++; } } } void write_log(int type, ...) { va_list ap; if (! arg_logfile) return; va_start(ap,type); switch (type) { case LOG_RD: { const void *data; int len; data = va_arg(ap,const void *); len = va_arg(ap,int); if (len > 0) { if (log_intype != LOG_RD) log_chgtype(); if (log_intype == LOG_NONE) { fprintf(logf,"<<< "); log_intype = LOG_RD; log_col = 4; } log_escaped_data(data,len); } } break; case LOG_WR: { CONN *c; const void *data; int len; c = va_arg(ap,CONN *); data = va_arg(ap,const void *); len = va_arg(ap,int); if (len > 0) { if ((log_intype != LOG_WR) || (log_inconn != c)) log_chgtype(); if (log_intype == LOG_NONE) { fprintf(logf,"%d >>> ",c->fd); log_intype = LOG_WR; log_col = decnlen(c->fd) + 5; } log_escaped_data(data,len); } } break; case LOG_MSG: { const char *fmt; fmt = va_arg(ap,const char *); if (log_intype != LOG_MSG) log_chgtype(); vfprintf(logmsgf,fmt,ap); fflush(logmsgf); } break; default: fprintf(stderr,"%s: bad type %d to write_log\n",__progname,type); abort(); break; } va_end(ap); fflush(logf); } void write_script(const void *data, int len) { if (arg_scriptfile) write(scriptfd,data,len); } void ensure_packet_len(CONN *c, int len) { if (len > c->pktbn) { c->pktbn = len; c->pktbuf = realloc(c->pktbuf,len); } } void conn_queue(CONN *c, const void *data, int len) { if (c->state == CS_DEAD) return; if (c->wbn+len > sizeof(c->wbuf)) { c->state = CS_DEAD; return; } if (c->wbh+len >= sizeof(c->wbuf)) { int n; n = sizeof(c->wbuf) - c->wbh; bcopy(data,&c->wbuf[c->wbh],n); if (len > n) bcopy(n+(const char *)data,&c->wbuf[0],len-n); c->wbh = len - n; } else { bcopy(data,&c->wbuf[c->wbh],len); c->wbh += len; } c->wbn += len; } void set_console(int wantit) { if (!arg_console || (ttyfd < 0)) return; ioctl(ttyfd,TIOCCONS,&wantit); } void acceptconn(int x) { int s; CONN *c; sinlen = sizeof(sin); s = accept(acc[x],(struct sockaddr *)&sin,&sinlen); if (s < 0) { if (errno == EINTR) return; fprintf(stderr,"%s: accept: %s\n",__progname,strerror(errno)); exit(1); } if (! conns) set_console(1); fcntl(s,F_SETFL,fcntl(s,F_GETFL,0)|O_NONBLOCK); nodelay(s); c = malloc(sizeof(CONN)); c->state = CS_MAGIC; c->state_n = 0; c->fd = s; c->pktbuf = 0; c->pktbn = 0; c->wbh = 0; c->wbt = 0; c->wbn = 0; c->xoff = 0; c->xofflim = 0; c->xoffq = 0; c->xoffh = 0; c->xofft = 0; c->xoffn = 0; c->remaddr = sin; c->link = conns; conns = c; conn_queue(c,&magic[0],magic_len+1); conn_queue(c,"1",2); } void conn_free(CONN *c) { free(c->xoffq); free(c->pktbuf); free(c); } void conn_write(CONN *c) { int n; int rv; n = sizeof(c->wbuf) - c->wbt; if (n > c->wbn) n = c->wbn; rv = write(c->fd,&c->wbuf[c->wbt],n); if (rv < 0) { if (errno == EINTR) return; fprintf(stderr,"%s: write: %s\n",__progname,strerror(errno)); c->state = CS_DEAD; return; } c->wbn -= rv; if (c->wbn == 0) { c->wbh = 0; c->wbt = 0; } else { c->wbt += rv; if (c->wbt == sizeof(c->wbuf)) c->wbt = 0; } } void client_break(CONN *c) { if (! line_down) { if (ttyfd < 0) { write_log(LOG_MSG,"BREAK, fd %d (ignored)\n",c->fd); } else { write_log(LOG_MSG,"BREAK, fd %d\n",c->fd); tcsendbreak(ttyfd,0); } } } void client_data(CONN *c, const void *data, int len) { if (! line_down) { write_log(LOG_WR,c,data,len); write((netfd>=0)?netfd:ttyfd,data,len); } } void conn_queue_data(CONN *c, int nblk, ...) { va_list ap; int i; int nb; char hdr[3]; va_start(ap,nblk); nb = 0; for (i=0;i> 8; hdr[2] = nb & 0xff; conn_queue(c,&hdr[0],3); } va_start(ap,nblk); for (i=0;i recap_n) len = recap_n; if (len < 1) return; if (len <= recap_h) { conn_queue_data(c,1,(const void *)&recap_ring[recap_h-len],len); } else { conn_queue_data(c,2,(const void *)&recap_ring[sizeof(recap_ring)-(len-recap_h)],len-recap_h, (const void *)&recap_ring[0],recap_h); } } void client_speedchg(CONN *c __attribute__((__unused__)), int new) { struct termios tmpstate; if (arg_lockspeed || (ttyfd < 0)) return; tmpstate = tstate; if (cfsetspeed(&tmpstate,new) < 0) return; if (tcsetattr(ttyfd,TCSANOW,&tmpstate) < 0) return; tstate = tmpstate; } void client_xonxoff(CONN *c, int onp) { if (onp) { if (! c->xoff) { conn_queue(c,"\x04\x03",2); return; } c->xoff = 0; if (c->xoffdrop) { unsigned char drop[6]; drop[0] = 0x04; drop[1] = 0x05; drop[2] = c->xoffdrop >> 24; drop[3] = (c->xoffdrop >> 16) & 0xff; drop[4] = (c->xoffdrop >> 8) & 0xff; drop[5] = c->xoffdrop & 0xff; conn_queue(c,&drop[0],6); } if (c->xoffn > 0) { if (c->xoffh > c->xofft) { conn_queue_data(c,1,(const void *)(c->xoffq+c->xofft),c->xoffh-c->xofft); } else { conn_queue_data(c,2,(const void *)(c->xoffq+c->xofft),c->xofflim-c->xofft, (const void *)c->xoffq,c->xoffh); } } c->xoff = 0; } else { conn_queue(c,"\x04\x02",2); if (c->xoff) return; c->xoff = 1; c->xoffdrop = 0; c->xoffh = 0; c->xofft = 0; c->xoffn = 0; } } void client_xonxoff_size(CONN *c, int size) { c->xoffdrop += c->xoffn; c->xofflim = size; c->xoffq = realloc(c->xoffq,c->xofflim); c->xoffh = 0; c->xofft = 0; c->xoffn = 0; } void conn_read(CONN *c) { int rv; int rx; rv = read(c->fd,&c->rbuf[0],sizeof(c->rbuf)); if (rv < 0) { if (errno == EAGAIN) return; if (errno == EINTR) return; fprintf(stderr,"%s: read: %s\n",__progname,strerror(errno)); c->state = CS_DEAD; return; } if (rv == 0) { c->state = CS_DEAD; return; } for (rx=0;rxstate) { case CS_DEAD: return; break; case CS_MAGIC: if (c->rbuf[rx] != magic[c->state_n]) { c->state = CS_DEAD; return; } c->state_n ++; if (c->state_n > magic_len) { c->state = CS_VERSION; c->state_n = 0; } break; case CS_VERSION: switch (c->rbuf[rx]) { case '\0': if (c->state_n != 1) { c->state = CS_DEAD; return; } c->state = CS_CONFNUL; c->state_n = 0; conn_queue(c,"",1); break; case '0': c->state_n *= 10; break; case '1': c->state_n = (c->state_n * 10) + 1; break; case '2': c->state_n = (c->state_n * 10) + 2; break; case '3': c->state_n = (c->state_n * 10) + 3; break; case '4': c->state_n = (c->state_n * 10) + 4; break; case '5': c->state_n = (c->state_n * 10) + 5; break; case '6': c->state_n = (c->state_n * 10) + 6; break; case '7': c->state_n = (c->state_n * 10) + 7; break; case '8': c->state_n = (c->state_n * 10) + 8; break; case '9': c->state_n = (c->state_n * 10) + 9; break; default: c->state = CS_DEAD; return; break; } break; case CS_CONFNUL: if (c->rbuf[rx] == 0) { c->state = CS_DATA; write_log(LOG_MSG,"New connection fd %d from %s\n",c->fd,inet_ntoa(c->remaddr.sin_addr)); } else { c->state = CS_DEAD; return; } break; case CS_DATA: if (c->state_n == 0) { switch (c->rbuf[rx]) { case 0x00: c->pkt_len = 2; break; case 0x01: c->pkt_len = 3; break; case 0x02: client_break(c); continue; break; case 0x03: c->pkt_len = 3; break; case 0x04: c->pkt_len = 2; break; case 0x05: c->pkt_len = 5; break; case 0x06: c->pkt_len = 2; break; default: c->state = CS_DEAD; return; break; } ensure_packet_len(c,c->pkt_len); c->pktbuf[0] = c->rbuf[rx]; c->state_n = 1; continue; } c->pktbuf[c->state_n++] = c->rbuf[rx]; if (c->state_n < c->pkt_len) continue; switch (c->pktbuf[0]) { case 0x00: if (c->pkt_len == 2) { c->pkt_len = 2 + c->pktbuf[1]; if (c->pkt_len == 2) { c->state_n = 0; } else { ensure_packet_len(c,c->pkt_len); } } else { client_data(c,c->pktbuf+2,c->pkt_len-2); c->state_n = 0; } break; case 0x01: if (c->pkt_len == 3) { c->pkt_len = 3 + (c->pktbuf[1] << 8) + c->pktbuf[2]; if (c->pkt_len == 3) { c->state_n = 0; } else { ensure_packet_len(c,c->pkt_len); } } else { client_data(c,c->pktbuf+3,c->pkt_len-3); c->state_n = 0; } break; case 0x03: client_recap(c,(c->pktbuf[1]<<8)+c->pktbuf[2]); write_log(LOG_MSG,"Recap, fd %d len %d\n",c->fd,(c->pktbuf[1]<<8)+c->pktbuf[2]); c->state_n = 0; break; case 0x04: switch (c->pktbuf[1]) { case 0x00: client_xonxoff(c,0); c->state_n = 0; break; case 0x01: client_xonxoff(c,1); c->state_n = 0; break; case 0x04: if (c->pkt_len == 2) { c->pkt_len = 4; ensure_packet_len(c,4); } else { client_xonxoff_size(c,(c->pktbuf[2]<<8)+c->pktbuf[3]); c->state_n = 0; } break; default: c->state = CS_DEAD; return; } break; case 0x05: { int newspeed; newspeed = (c->pktbuf[1]<<24) + (c->pktbuf[2]<<16) + (c->pktbuf[3]<<8) + c->pktbuf[4]; client_speedchg(c,newspeed); write_log(LOG_MSG,"Speed chg, fd %d speed %d\n",c->fd,newspeed); } c->state_n = 0; break; case 0x06: switch (c->pktbuf[1]) { case 0x00: if (! arg_nodown) { line_down = 1; fcntl(ttyfd,F_SETFL,fcntl(ttyfd,F_GETFL,0)&~O_NONBLOCK); write_log(LOG_MSG,"Down request, fd %d\n",c->fd); } else { write_log(LOG_MSG,"Down request ignored, fd %d\n",c->fd); } break; case 0x01: if (! arg_nodown) { line_down = 0; fcntl(ttyfd,F_SETFL,fcntl(ttyfd,F_GETFL,0)|O_NONBLOCK); write_log(LOG_MSG,"Up request, fd %d\n",c->fd); } else { write_log(LOG_MSG,"Up request ignored, fd %d\n",c->fd); } break; } c->state_n = 0; break; default: c->state = CS_DEAD; return; break; } break; } } } void client_output(CONN *c, const void *data, int len) { if (c->state != CS_DATA) return; if (c->xoff) { if (c->xofflim == 0) { c->xoffdrop += len; return; } if (c->xoffn+len > c->xofflim) { c->xoffdrop += c->xoffn + len - c->xofflim; if (len >= c->xofflim) { c->xoffh = 0; c->xofft = 0; c->xoffn = c->xofflim; bcopy((len-c->xofflim)+(const char *)data,c->xoffq,c->xofflim); return; } else { if (len < c->xofflim-c->xoffh) { bcopy(data,c->xoffq+c->xoffh,len); c->xoffh += len; } else { bcopy(data,c->xoffq+c->xoffh,c->xofflim-c->xoffh); bcopy((c->xofflim-c->xoffh)+(const char *)data,c->xoffq,len-(c->xofflim-c->xoffh)); c->xoffh += len - c->xofflim; } c->xofft = c->xoffh; c->xoffn = c->xofflim; } } else { if ((c->xofft != 0) || (c->xoffh != c->xoffn)) abort(); bcopy(data,c->xoffq+c->xoffh,len); c->xoffh += len; c->xoffn += len; if (c->xoffn == c->xofflim) c->xoffh = 0; } } else { conn_queue_data(c,1,(const void *)data,len); } } void tty_read(int fd) { int n; CONN *c; int ro; char buf[10240]; n = read(fd,&buf[0],sizeof(buf)); if (n < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return; break; } fprintf(stderr,"%s: %s read: %s\n",__progname,serstr,strerror(errno)); exit(1); } if (n < 1) { fprintf(stderr,"%s: %s read got EOF\n",__progname,serstr); exit(1); } write_log(LOG_RD,(const void *)&buf[0],n); write_script((const void *)&buf[0],n); for (c=conns;c;c=c->link) client_output(c,&buf[0],n); if (recap_h+n >= sizeof(recap_ring)) { ro = sizeof(recap_ring) - recap_h; bcopy(&buf[0],&recap_ring[recap_h],ro); recap_h = 0; } else { ro = 0; } if (n > ro) { bcopy(&buf[ro],&recap_ring[recap_h],n-ro); recap_h += n - ro; } recap_n += n; if (recap_n >= sizeof(recap_ring)) recap_n = sizeof(recap_ring); } ttyfd = -1; ptyfd = -1; netfd = -1; if (arg_console) { findpty(&ptyfd,&ttyfd); serstr = strdup("console pty"); } else if (arg_netaddr) { netfd = net_connection(arg_netaddr,arg_netport); asprintf(&serstr,"%s/%s",arg_netaddr,arg_netport); } else { ttyfd = open(arg_daemondev,O_RDWR|O_NONBLOCK,0); if (ttyfd < 0) { fprintf(stderr,"%s: %s: %s\n",__progname,arg_daemondev,strerror(errno)); exit(1); } serstr = strdup(arg_daemondev); fcntl(ttyfd,F_SETFL,fcntl(ttyfd,F_GETFL,0)&~O_NONBLOCK); } if (ttyfd >= 0) { if (tcgetattr(ttyfd,&tstate) < 0) { fprintf(stderr,"%s: tcgetattr %s: %s\n",__progname,arg_daemondev,strerror(errno)); exit(1); } cfmakeraw(&tstate); tstate.c_cflag &= ~HUPCL; tstate.c_cflag |= CLOCAL; if (cfsetspeed(&tstate,arg_speed?:9600) < 0) { fprintf(stderr,"%s: set speed on %s: %s\n",__progname,arg_daemondev,strerror(errno)); exit(1); } tstate.c_cc[VMIN] = 0; tstate.c_cc[VTIME] = 0; if (tcsetattr(ttyfd,TCSANOW,&tstate) < 0) { fprintf(stderr,"%s: tcsetattr %s: %s\n",__progname,arg_daemondev,strerror(errno)); exit(1); } if (tcflush(ttyfd,TCIOFLUSH) < 0) { fprintf(stderr,"%s: tcflush %s: %s\n",__progname,arg_daemondev,strerror(errno)); exit(1); } } line_down = 0; signal(SIGPIPE,SIG_IGN); if (arg_nipaddress == 0) { accaddr = malloc(sizeof(struct in_addr)); accaddr[0].s_addr = htonl(0x7f000001); nacc = 1; } else { int aax; int errs; errs = 0; aax = 0; accaddr = malloc(arg_nipaddress*sizeof(struct in_addr)); nacc = arg_nipaddress; for (i=0;ih_addrtype != AF_INET) { fprintf(stderr,"%s: %s: non-INET address\n",__progname,arg_ipaddress[i]); errs ++; } else if (hp->h_length != sizeof(struct in_addr)) { fprintf(stderr,"%s: %s: address length wrong\n",__progname,arg_ipaddress[i]); errs ++; } else { for (j=0;hp->h_addr_list[j];j++) ; if (j != 1) accaddr = realloc(accaddr,(nacc+(j-1))*sizeof(struct in_addr)); for (j--;j>=0;j--) { bcopy(hp->h_addr_list[j],&accaddr[aax++],sizeof(struct in_addr)); } } } } if (errs) exit(1); if (aax != nacc) abort(); } acc = malloc(nacc*sizeof(int)); for (i=0;i= 0) ? netfd : (ptyfd >= 0) ? ptyfd : ttyfd; if (! line_down) FD_SET(serfd,&rfds); for (i=0;istate == CS_DEAD) { write_log(LOG_MSG,"Connection dead, fd %d\n",c->fd); *cpp = c->link; close(c->fd); conn_free(c); if (! conns) set_console(0); continue; } FD_SET(c->fd,&rfds); if (c->wbn > 0) FD_SET(c->fd,&wfds); cpp = &c->link; } i = select(FD_SETSIZE,&rfds,&wfds,0,0); if (i < 0) { if (errno == EINTR) continue; fprintf(stderr,"%s: select: %s\n",__progname,strerror(errno)); exit(1); } if (FD_ISSET(serfd,&rfds)) tty_read(serfd); for (c=conns;c;c=c->link) { if (FD_ISSET(c->fd,&rfds)) conn_read(c); if (FD_ISSET(c->fd,&wfds)) conn_write(c); } for (i=0;i 127) && (c < 160)) { printf("\\%03o",c); } else { putchar(c); } } fflush(stdout); } static unsigned char par_tbl_e[256] = { 0,129,130, 3,132, 5, 6,135,136, 9, 10,139, 12,141,142, 15, 144, 17, 18,147, 20,149,150, 23, 24,153,154, 27,156, 29, 30,159, 160, 33, 34,163, 36,165,166, 39, 40,169,170, 43,172, 45, 46,175, 48,177,178, 51,180, 53, 54,183,184, 57, 58,187, 60,189,190, 63, 192, 65, 66,195, 68,197,198, 71, 72,201,202, 75,204, 77, 78,207, 80,209,210, 83,212, 85, 86,215,216, 89, 90,219, 92,221,222, 95, 96,225,226, 99,228,101,102,231,232,105,106,235,108,237,238,111, 240,113,114,243,116,245,246,119,120,249,250,123,252,125,126,255, 0,129,130, 3,132, 5, 6,135,136, 9, 10,139, 12,141,142, 15, 144, 17, 18,147, 20,149,150, 23, 24,153,154, 27,156, 29, 30,159, 160, 33, 34,163, 36,165,166, 39, 40,169,170, 43,172, 45, 46,175, 48,177,178, 51,180, 53, 54,183,184, 57, 58,187, 60,189,190, 63, 192, 65, 66,195, 68,197,198, 71, 72,201,202, 75,204, 77, 78,207, 80,209,210, 83,212, 85, 86,215,216, 89, 90,219, 92,221,222, 95, 96,225,226, 99,228,101,102,231,232,105,106,235,108,237,238,111, 240,113,114,243,116,245,246,119,120,249,250,123,252,125,126,255 }; static unsigned char par_tbl_o[256] = { 128, 1, 2,131, 4,133,134, 7, 8,137,138, 11,140, 13, 14,143, 16,145,146, 19,148, 21, 22,151,152, 25, 26,155, 28,157,158, 31, 32,161,162, 35,164, 37, 38,167,168, 41, 42,171, 44,173,174, 47, 176, 49, 50,179, 52,181,182, 55, 56,185,186, 59,188, 61, 62,191, 64,193,194, 67,196, 69, 70,199,200, 73, 74,203, 76,205,206, 79, 208, 81, 82,211, 84,213,214, 87, 88,217,218, 91,220, 93, 94,223, 224, 97, 98,227,100,229,230,103,104,233,234,107,236,109,110,239, 112,241,242,115,244,117,118,247,248,121,122,251,124,253,254,127, 128, 1, 2,131, 4,133,134, 7, 8,137,138, 11,140, 13, 14,143, 16,145,146, 19,148, 21, 22,151,152, 25, 26,155, 28,157,158, 31, 32,161,162, 35,164, 37, 38,167,168, 41, 42,171, 44,173,174, 47, 176, 49, 50,179, 52,181,182, 55, 56,185,186, 59,188, 61, 62,191, 64,193,194, 67,196, 69, 70,199,200, 73, 74,203, 76,205,206, 79, 208, 81, 82,211, 84,213,214, 87, 88,217,218, 91,220, 93, 94,223, 224, 97, 98,227,100,229,230,103,104,233,234,107,236,109,110,239, 112,241,242,115,244,117,118,247,248,121,122,251,124,253,254,127 }; static unsigned char par_tbl_0[256] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99,100,101,102,103,104,105,106,107,108,109,110,111, 112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99,100,101,102,103,104,105,106,107,108,109,110,111, 112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127 }; static unsigned char par_tbl_1[256] = { 128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143, 144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159, 160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175, 176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191, 192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207, 208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223, 224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239, 240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255, 128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143, 144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159, 160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175, 176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191, 192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207, 208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223, 224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239, 240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255 }; static int parity_accept(unsigned char c, int spec) { switch (spec & P_ACC) { case P_N & P_ACC: return(1); break; case P_E & P_ACC: return(c==par_tbl_e[c&0x7f]); break; case P_O & P_ACC: return(c==par_tbl_o[c&0x7f]); break; case P_0 & P_ACC: return((c&0x80)==0); break; case P_1 & P_ACC: return(c&0x80); break; } abort(); } static unsigned char parity_mod(unsigned char c, int spec) { switch (spec & P_MOD) { case P_N & P_MOD: return(c); break; case P_E & P_MOD: return(par_tbl_e[c&0x7f]); break; case P_O & P_MOD: return(par_tbl_o[c&0x7f]); break; case P_0 & P_MOD: return(par_tbl_0[c&0x7f]); break; case P_1 & P_MOD: return(par_tbl_1[c&0x7f]); break; } abort(); } static int hexvalue(char c) { switch (c) { case '0': return(0); break; case '1': return(1); break; case '2': return(2); break; case '3': return(3); break; case '4': return(4); break; case '5': return(5); break; case '6': return(6); break; case '7': return(7); break; case '8': return(8); break; case '9': return(9); break; case 'a': case 'A': return(10); break; case 'b': case 'B': return(11); break; case 'c': case 'C': return(12); break; case 'd': case 'D': return(13); break; case 'e': case 'E': return(14); break; case 'f': case 'F': return(15); break; } return(-1); } static void run_client(void) { struct termios otstate; struct termios tstate; struct pollfd pfds[3]; int npfds; int prv; int c; char escchar; char escname[3]; int state; #define S_DATA 1 #define S_ESC 2 #define S_CMD 3 #define S_EXIT 4 #define S_XOFF 5 #define S_HEXA 6 #define S_HEXB 7 #define S_PROG 8 #define S_DEADPROG 9 unsigned char hexchar; void (*xoff_fn)(void); unsigned char *pktbuf; int pktbn; int pktlen; int pktwant; int progpipe; int net_read(void *buf, int len) { int n; while (1) { n = read(c,buf,len); if (n < 0) { if (errno == EINTR) continue; fprintf(stderr,"%s: net read: %s\n",__progname,strerror(errno)); exit(1); } if (n == 0) { fprintf(stderr,"%s: net EOF\n",__progname); exit(1); } return(n); } } void do_magic_and_version(void) { char mbuf[sizeof(magic)]; int mx; int n; char ch; int v; write(c,&magic[0],magic_len+1); write(c,"1",2); mx = 0; while (mx < sizeof(mbuf)) { n = net_read(&mbuf[mx],sizeof(mbuf)-mx); if (bcmp(&mbuf[mx],&magic[mx],n)) { fprintf(stderr,"%s: protocol error: bad magic string\n",__progname); exit(1); } mx += n; } v = 0; while (1) { n = net_read(&ch,1); if (ch == '\0') break; v *= 10; switch (ch) { case '0': break; case '1': v += 1; break; case '2': v += 2; break; case '3': v += 3; break; case '4': v += 4; break; case '5': v += 5; break; case '6': v += 6; break; case '7': v += 7; break; case '8': v += 8; break; case '9': v += 9; break; default: fprintf(stderr,"%s: protocol error: bad version\n",__progname); exit(1); break; } } if (v != 1) { fprintf(stderr,"%s: unknown protocol version\n",__progname); exit(1); } write(c,"",1); net_read(&ch,1); } void tryconn(struct in_addr *a, const char *strform) { struct sockaddr_in sin; c = socket(AF_INET,SOCK_STREAM,0); if (c < 0) { fprintf(stderr,"%s: socket: %s\n",__progname,strerror(errno)); exit(1); } bzero(&sin,sizeof(sin)); sin.sin_family = AF_INET; sin.sin_port = htons(atoi(arg_portnum)); sin.sin_addr = *a; if (connect(c,(struct sockaddr *)&sin,sizeof(sin)) < 0) { fprintf(stderr,"%s: connect %s: %s\n",__progname,strform,strerror(errno)); close(c); c = -1; } nodelay(c); } void write_data(const void *data, int len) { unsigned char hdr[3]; if (len < 1) return; if (len < 256) { hdr[0] = 0x00; hdr[1] = len; write(c,&hdr[0],2); } else { hdr[0] = 0x01; hdr[1] = len >> 8; hdr[2] = len & 0xff; write(c,&hdr[0],3); } write(c,data,len); } void esc_dumphelp(void) { printf("\r\n"); printf("%s? print this help\r\n",&escname[0]); if (! arg_captive) { printf("%s. exit\r\n",&escname[0]); printf("%sz suspend\r\n",&escname[0]); } printf("%s# generate a BREAK\r\n",&escname[0]); printf("%s (space) do nothing (ignore the %s)\r\n",&escname[0],&escname[0]); printf("%sxAB `Type' character 0xAB\r\n",&escname[0]); printf("%s%% enter command-line mode\r\n",&escname[0]); printf("or instead,\r\n"); printf("%s%s send %s\r\n",&escname[0],&escname[0],&escname[0]); fflush(stdout); } void esc_dumpusage(void) { printf("[%s? for help]",&escname[0]); fflush(stdout); } void send_xoff(void) { write(c,"\x04\x00",2); } void send_xon(void) { write(c,"\x04\x01",2); } void suspend(void) { tcsetattr(0,TCSADRAIN,&otstate); kill(0,SIGTSTP); tcsetattr(0,TCSADRAIN,&tstate); send_xon(); state = S_DATA; } void read_stdin(void) { char rbuf[256]; int rv; int rx; int d0; int d1; int v; void end_d(void) { if ((d0 >= 0) && (d1 > d0)) { if (arg_ro) { write(1,"\7",1); } else { write_data(&rbuf[d0],d1-d0); } } d0 = -1; } void d_char(unsigned char c) { if (parity_accept(c,iparity)) rbuf[d1++] = parity_mod(c,iparity); } rv = read(0,&rbuf[0],sizeof(rbuf)); if (rv < 0) { if (errno == EINTR) return; fprintf(stderr,"%s: read stdin: %s\n",__progname,strerror(errno)); exit(1); } if (rv == 0) exit(0); /* probably lost a window */ d0 = -1; for (rx=0;rx= have) buf = realloc(buf,(have=len+16)+1); buf[len++] = ch; } } void request_recap(int n) { unsigned char pkt[3]; pkt[0] = 0x03; pkt[1] = n >> 8; pkt[2] = n & 0xff; write(c,&pkt[0],3); } void setup_run(const char *cmd) { int p[2]; const char *sh; pid_t kid; sh = getenv("SHELL"); if (! sh) sh = "/bin/sh"; if (socketpair(AF_LOCAL,SOCK_STREAM,0,&p[0]) < 0) { fprintf(stderr,"%s: socketpair: %s\n",__progname,strerror(errno)); exit(1); } kid = fork(); if (kid == 0) { close(p[1]); close(c); if (p[0] != 0) { dup2(p[0],0); close(p[0]); } dup2(0,1); execl(sh,sh,"-c",cmd,(char *)0); _exit(1); } close(p[0]); progpipe = p[1]; } void cmdline_loop(void) { int pending_recap; pending_recap = 0; tcsetattr(0,TCSADRAIN,&otstate); while (1) { char *line; printf("(serialconsole)> "); fflush(stdout); line = read_line(); if (!strcmp(line,"?") || !strcmp(line,"help")) { if (! arg_captive) printf("quit, z, "); printf("break, go, vis, hex, recap, speed, iparity, oparity"); if (! arg_captive) printf(", run"); printf("\n"); } else if (!arg_captive && (!strcmp(line,"quit") || !strcmp(line,"exit"))) { exit(0); } else if (!arg_captive && (!strcmp(line,"z") || !strcmp(line,"suspend"))) { kill(0,SIGTSTP); } else if (!strcmp(line,"break")) { if (arg_ro) { write(1,"\7",1); } else { write(c,"\x02",1); } } else if (!strcmp(line,"go") || !strcmp(line,"cont")) { break; } else if (!strncmp(line,"recap ",6)) { int n; n = atoi(line+6); if (n > 0) pending_recap = n; } else if (!strncmp(line,"speed ",6)) { int n; n = atoi(line+6); if (n > 0) { unsigned char buf[5]; buf[0] = 0x05; buf[1] = (n >> 24) & 0xff; buf[2] = (n >> 16) & 0xff; buf[3] = (n >> 8) & 0xff; buf[4] = n & 0xff; write(c,&buf[0],5); } } else if (!strncmp(line,"iparity ",8)) { const char *s; const char *err; s = line + 8; while (*s == ' ') s ++; err = setparity(&iparity,s); if (err) printf("Bad parity spec (%s)\n",err); } else if (!strncmp(line,"oparity ",8)) { const char *s; const char *err; s = line + 8; while (*s == ' ') s ++; err = setparity(&oparity,s); if (err) printf("Bad parity spec (%s)\n",err); } else if (!strncmp(line,"vis",3)) { do_vis = ! do_vis; printf("Nonprintable characters will %sbe escaped.\n",do_vis?"":"not "); } else if (!strncmp(line,"hex",3)) { do_hex = ! do_hex; printf("Output will %sbe displayed in hex.\n",do_hex?"":"not "); } else if (!arg_captive && !strncmp(line,"run",3)) { setup_run(line+3); tcsetattr(0,TCSADRAIN,&tstate); send_xon(); state = S_PROG; return; } else if (!strncmp(line,"down",4)) { unsigned char buf[2]; buf[0] = 0x06; buf[1] = 0x00; write(c,&buf[0],2); } else if (!strncmp(line,"up",2)) { unsigned char buf[2]; buf[0] = 0x06; buf[1] = 0x01; write(c,&buf[0],2); } else if (!strcmp(line,"")) { } else { printf("Unrecognized command - ? for a list\n"); } } tcsetattr(0,TCSADRAIN,&tstate); send_xon(); if (pending_recap > 0) request_recap(pending_recap); state = S_DATA; } void newpkt(void) { pktlen = 0; pktwant = 1; } void got_xoff_ack(void) { if (state == S_XOFF) { (*xoff_fn)(); } } void got_xon_ack(void) { } void got_xon_drop(int n) { if (n > 0) { printf("[dropped %d]",n); fflush(stdout); } } void read_net(void) { unsigned char rbuf[8192]; char outbuf[8192]; int outfill; int rv; int rx; void out_flush(void) { if (outfill > 0) { if ((state == S_PROG) || (state == S_DEADPROG)) { rv = send(progpipe,&outbuf[0],outfill,MSG_NOSIGNAL); if (rv < 0) state = S_DEADPROG; } else { out_vis(&outbuf[0],outfill); } } outfill = 0; } void out_data(const void *data, int len) { const unsigned char *dp; for (dp=data;len>0;dp++,len--) { if (! parity_accept(*dp,oparity)) continue; if (outfill+len > sizeof(outbuf)) out_flush(); outbuf[outfill++] = parity_mod(*dp,oparity); } } outfill = 0; rv = net_read(&rbuf[0],sizeof(rbuf)); for (rx=0;rx= pktbn) { pktbuf = realloc(pktbuf,pktbn=pktlen+64); } pktbuf[pktlen++] = rbuf[rx]; if (pktlen >= pktwant) { switch (pktbuf[0]) { case 0x00: switch (pktlen) { case 1: pktwant = 2; break; case 2: pktwant = 2 + pktbuf[1]; if (pktwant == 2) newpkt(); break; default: out_data(pktbuf+2,pktlen-2); newpkt(); break; } break; case 0x01: switch (pktlen) { case 1: pktwant = 3; break; case 3: pktwant = 3 + (pktbuf[1] << 8) + pktbuf[2]; if (pktwant == 3) newpkt(); break; default: out_data(pktbuf+3,pktlen-3); newpkt(); break; } break; case 0x04: switch (pktlen) { case 1: pktwant = 2; break; case 2: switch (pktbuf[1]) { case 0x02: got_xoff_ack(); newpkt(); break; case 0x03: got_xon_ack(); newpkt(); break; case 0x05: pktwant = 6; break; default: out_flush(); printf("[protocol error (bad flow control subtype %d) [rx=%d]]\r\n",pktbuf[1],rx); state = S_EXIT; return; } break; case 6: got_xon_drop((pktbuf[2]<<24)|(pktbuf[3]<<16)|(pktbuf[4]<<8)|pktbuf[5]); newpkt(); break; default: abort(); break; } break; default: out_flush(); printf("[protocol error (bad packet type %d) [rx=%d]]\r\n",pktbuf[0],rx); state = S_EXIT; return; break; } } } out_flush(); } void read_prog(void) { unsigned char rbuf[8192]; int rv; rv = read(progpipe,&rbuf[0],sizeof(rbuf)); if (rv <= 0) { state = S_DEADPROG; return; } write_data(&rbuf[0],rv); } c = -1; if (arg_nipaddress == 0) { struct in_addr a; a.s_addr = htonl(0x7f000001); /* 127.0.0.1 */ tryconn(&a,"127.0.0.1 (default)"); } else { struct in_addr a; int i; for (i=0;ih_addrtype != AF_INET) { fprintf(stderr,"%s: %s: non-INET address\n",__progname,arg_ipaddress[i]); exit(1); } else if (hp->h_length != sizeof(struct in_addr)) { fprintf(stderr,"%s: %s: address length wrong\n",__progname,arg_ipaddress[i]); exit(1); } else { nbuf = malloc(strlen(hp->h_name)+64); for (j=0;hp->h_addr_list[j];j++) { bcopy(hp->h_addr_list[j],&a,sizeof(struct in_addr)); sprintf(nbuf,"%s (%s)",hp->h_name,inet_ntoa(a)); tryconn(&a,nbuf); if (c >= 0) break; } } free(nbuf); } if (c >= 0) break; } } if (c < 0) exit(1); do_magic_and_version(); if (arg_recap) request_recap(atoi(arg_recap)); if (tcgetattr(0,&tstate) < 0) { fprintf(stderr,"%s: tcgetattr: %s\n",__progname,strerror(errno)); exit(1); } otstate = tstate; cfmakeraw(&tstate); tstate.c_cc[VMIN] = 0; tstate.c_cc[VTIME] = 0; if (tcsetattr(0,TCSADRAIN,&tstate) < 0) { fprintf(stderr,"%s: tcsetattr: %s\n",__progname,strerror(errno)); exit(1); } escchar = 29; /* ^] */ if (arg_escchar) { if (arg_escchar[0]) { if (arg_escchar[1]) { if (!strcasecmp(arg_escchar,"^@")) escchar = 0; else if (!strcasecmp(arg_escchar,"^a")) escchar = 1; else if (!strcasecmp(arg_escchar,"^b")) escchar = 2; else if (!strcasecmp(arg_escchar,"^c")) escchar = 3; else if (!strcasecmp(arg_escchar,"^d")) escchar = 4; else if (!strcasecmp(arg_escchar,"^e")) escchar = 5; else if (!strcasecmp(arg_escchar,"^f")) escchar = 6; else if (!strcasecmp(arg_escchar,"^g")) escchar = 7; else if (!strcasecmp(arg_escchar,"^h")) escchar = 8; else if (!strcasecmp(arg_escchar,"^i")) escchar = 9; else if (!strcasecmp(arg_escchar,"^j")) escchar = 10; else if (!strcasecmp(arg_escchar,"^k")) escchar = 11; else if (!strcasecmp(arg_escchar,"^l")) escchar = 12; else if (!strcasecmp(arg_escchar,"^m")) escchar = 13; else if (!strcasecmp(arg_escchar,"^n")) escchar = 14; else if (!strcasecmp(arg_escchar,"^o")) escchar = 15; else if (!strcasecmp(arg_escchar,"^p")) escchar = 16; else if (!strcasecmp(arg_escchar,"^q")) escchar = 17; else if (!strcasecmp(arg_escchar,"^r")) escchar = 18; else if (!strcasecmp(arg_escchar,"^s")) escchar = 19; else if (!strcasecmp(arg_escchar,"^t")) escchar = 20; else if (!strcasecmp(arg_escchar,"^u")) escchar = 21; else if (!strcasecmp(arg_escchar,"^v")) escchar = 22; else if (!strcasecmp(arg_escchar,"^w")) escchar = 23; else if (!strcasecmp(arg_escchar,"^x")) escchar = 24; else if (!strcasecmp(arg_escchar,"^y")) escchar = 25; else if (!strcasecmp(arg_escchar,"^z")) escchar = 26; else if (!strcasecmp(arg_escchar,"^[")) escchar = 27; else if (!strcasecmp(arg_escchar,"^\\")) escchar = 28; else if (!strcasecmp(arg_escchar,"^]")) escchar = 29; else if (!strcasecmp(arg_escchar,"^^")) escchar = 30; else if (!strcasecmp(arg_escchar,"^_")) escchar = 31; else if (!strcasecmp(arg_escchar,"sp")) escchar = 32; else if (!strcasecmp(arg_escchar,"space")) escchar = 32; else if (!strcasecmp(arg_escchar,"^?")) escchar = 127; else { fprintf(stderr,"%s: %s: unknown escape character name\n",__progname,arg_escchar); exit(1); } } else { escchar = arg_escchar[0]; } } } if (escchar < 32) { escname[0] = '^'; escname[1] = escchar ^ 64; escname[2] = '\0'; } else if (escchar == 127) { strcpy(&escname[0],"^?"); } else { escname[0] = escchar; escname[1] = '\0'; } state = S_DATA; pktbuf = 0; pktbn = 0; newpkt(); while (1) { if (want_dumpblocks) { want_dumpblocks = 0; dumpblocks(); continue; } switch (state) { case S_EXIT: tcsetattr(0,TCSADRAIN,&otstate); exit(0); break; case S_CMD: send_xoff(); state = S_XOFF; xoff_fn = &cmdline_loop; continue; break; case S_DEADPROG: close(progpipe); while (wait3(0,WNOHANG,0) > 0) ; printf("[command done]\r\n"); state = S_DATA; break; } pfds[0] = (struct pollfd){.fd=0,.events=POLLIN|POLLRDNORM}; pfds[1] = (struct pollfd){.fd=c,.events=POLLIN|POLLRDNORM}; npfds = 2; if (state == S_PROG) { pfds[2] = (struct pollfd){.fd=progpipe,.events=POLLIN|POLLRDNORM}; npfds = 3; } prv = poll(&pfds[0],npfds,INFTIM); if (prv < 0) { if (errno == EINTR) continue; fprintf(stderr,"%s: poll: %s\n",__progname,strerror(errno)); exit(1); } if (pfds[0].revents & (POLLIN|POLLRDNORM)) read_stdin(); if (pfds[1].revents & (POLLIN|POLLRDNORM)) read_net(); if ((npfds > 2) && (pfds[2].revents & (POLLIN|POLLRDNORM))) read_prog(); } } static void request_dumpblocks(int sig __attribute__((__unused__))) { want_dumpblocks = 1; } int main(int, char **); int main(int ac, char **av) { handleargs(ac,av); signal(SIGUSR1,request_dumpblocks); if (daemon_mode) { run_daemon(); } else { run_client(); } exit(0); }