This version fixes ECHOing problems, discovers 4.2 (brain-damaged)
[unix-history] / usr / src / libexec / telnetd / telnetd.c
index 51c274d..0a58886 100644 (file)
@@ -1,6 +1,18 @@
+/*
+ * Copyright (c) 1983 Regents of the University of California.
+ * All rights reserved.  The Berkeley software License Agreement
+ * specifies the terms and conditions for redistribution.
+ */
+
 #ifndef lint
 #ifndef lint
-static char sccsid[] = "@(#)telnetd.c  4.29 (Berkeley) %G%";
-#endif
+char copyright[] =
+"@(#) Copyright (c) 1983 Regents of the University of California.\n\
+ All rights reserved.\n";
+#endif not lint
+
+#ifndef lint
+static char sccsid[] = "@(#)telnetd.c  5.14 (Berkeley) %G%";
+#endif not lint
 
 /*
  * Stripped-down telnet server.
 
 /*
  * Stripped-down telnet server.
@@ -8,6 +20,9 @@ static char sccsid[] = "@(#)telnetd.c  4.29 (Berkeley) %G%";
 #include <sys/types.h>
 #include <sys/socket.h>
 #include <sys/wait.h>
 #include <sys/types.h>
 #include <sys/socket.h>
 #include <sys/wait.h>
+#include <sys/file.h>
+#include <sys/stat.h>
+#include <sys/time.h>
 
 #include <netinet/in.h>
 
 
 #include <netinet/in.h>
 
@@ -19,10 +34,16 @@ static      char sccsid[] = "@(#)telnetd.c  4.29 (Berkeley) %G%";
 #include <sgtty.h>
 #include <netdb.h>
 #include <syslog.h>
 #include <sgtty.h>
 #include <netdb.h>
 #include <syslog.h>
+#include <ctype.h>
 
 #define        BELL    '\07'
 
 #define        BELL    '\07'
-#define BANNER "\r\n\r\n4.2 BSD UNIX (%s)\r\n\r\r\n\r%s"
+#define BANNER "\r\n\r\n4.3 BSD UNIX (%s)\r\n\r\r\n\r%s"
 
 
+#define        OPT_DONT        0               /* don't do this option */
+#define        OPT_WONT        0               /* won't do this option */
+#define        OPT_DO          1               /* do this option */
+#define        OPT_WILL        1               /* will do this option */
+#define        OPT_ALWAYS_LOOK 2               /* special case for echo */
 char   hisopts[256];
 char   myopts[256];
 
 char   hisopts[256];
 char   myopts[256];
 
@@ -35,23 +56,99 @@ char        wont[] = { IAC, WONT, '%', 'c', 0 };
  * I/O data buffers, pointers, and counters.
  */
 char   ptyibuf[BUFSIZ], *ptyip = ptyibuf;
  * I/O data buffers, pointers, and counters.
  */
 char   ptyibuf[BUFSIZ], *ptyip = ptyibuf;
+
 char   ptyobuf[BUFSIZ], *pfrontp = ptyobuf, *pbackp = ptyobuf;
 char   ptyobuf[BUFSIZ], *pfrontp = ptyobuf, *pbackp = ptyobuf;
+
 char   netibuf[BUFSIZ], *netip = netibuf;
 char   netibuf[BUFSIZ], *netip = netibuf;
+#define        NIACCUM(c)      {   *netip++ = c; \
+                           ncc++; \
+                       }
+
 char   netobuf[BUFSIZ], *nfrontp = netobuf, *nbackp = netobuf;
 char   netobuf[BUFSIZ], *nfrontp = netobuf, *nbackp = netobuf;
+char   *neturg = 0;            /* one past last bye of urgent data */
+       /* the remote system seems to NOT be an old 4.2 */
+int    not42 = 1;
+
+
+char   subbuffer[100], *subpointer, *subend;   /* buffer for sub-options */
+#define        SB_CLEAR()      subpointer = subbuffer;
+#define        SB_TERM()       subend = subpointer;
+#define        SB_ACCUM(c)     if (subpointer < (subbuffer+sizeof subbuffer)) { \
+                               *subpointer++ = (c); \
+                       }
+
 int    pcc, ncc;
 
 int    pty, net;
 int    inter;
 extern char **environ;
 extern int errno;
 int    pcc, ncc;
 
 int    pty, net;
 int    inter;
 extern char **environ;
 extern int errno;
-char   line[] = "/dev/ptyp0";
+char   *line;
+int    SYNCHing = 0;           /* we are in TELNET SYNCH mode */
+/*
+ * The following are some clocks used to decide how to interpret
+ * the relationship between various variables.
+ */
 
 
+struct {
+    int
+       system,                 /* what the current time is */
+       echotoggle,             /* last time user entered echo character */
+       modenegotiated,         /* last time operating mode negotiated */
+       didnetreceive,          /* last time we read data from network */
+       gotDM;                  /* when did we last see a data mark */
+} clocks;
+
+#define        settimer(x)     clocks.x = clocks.system++
+\f
 main(argc, argv)
        char *argv[];
 {
        struct sockaddr_in from;
        int on = 1, fromlen;
 
 main(argc, argv)
        char *argv[];
 {
        struct sockaddr_in from;
        int on = 1, fromlen;
 
+#if    defined(DEBUG)
+       {
+           int s, ns, foo;
+           struct servent *sp;
+           static struct sockaddr_in sin = { AF_INET };
+
+           sp = getservbyname("telnet", "tcp");
+           if (sp == 0) {
+                   fprintf(stderr, "telnetd: tcp/telnet: unknown service\n");
+                   exit(1);
+           }
+           sin.sin_port = sp->s_port;
+           argc--, argv++;
+           if (argc > 0) {
+                   sin.sin_port = atoi(*argv);
+                   sin.sin_port = htons((u_short)sin.sin_port);
+           }
+
+           s = socket(AF_INET, SOCK_STREAM, 0);
+           if (s < 0) {
+                   perror("telnetd: socket");;
+                   exit(1);
+           }
+           if (bind(s, &sin, sizeof sin) < 0) {
+               perror("bind");
+               exit(1);
+           }
+           if (listen(s, 1) < 0) {
+               perror("listen");
+               exit(1);
+           }
+           foo = sizeof sin;
+           ns = accept(s, &sin, &foo);
+           if (ns < 0) {
+               perror("accept");
+               exit(1);
+           }
+           dup2(ns, 0);
+           close(s);
+       }
+#endif /* defined(DEBUG) */
+       openlog("telnetd", LOG_PID | LOG_ODELAY, LOG_DAEMON);
        fromlen = sizeof (from);
        if (getpeername(0, &from, &fromlen) < 0) {
                fprintf(stderr, "%s: ", argv[0]);
        fromlen = sizeof (from);
        if (getpeername(0, &from, &fromlen) < 0) {
                fprintf(stderr, "%s: ", argv[0]);
@@ -59,13 +156,35 @@ main(argc, argv)
                _exit(1);
        }
        if (setsockopt(0, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof (on)) < 0) {
                _exit(1);
        }
        if (setsockopt(0, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof (on)) < 0) {
-               openlog(argv[0], LOG_PID, 0);
                syslog(LOG_WARNING, "setsockopt (SO_KEEPALIVE): %m");
        }
        doit(0, &from);
 }
 
                syslog(LOG_WARNING, "setsockopt (SO_KEEPALIVE): %m");
        }
        doit(0, &from);
 }
 
-char   *envinit[] = { "TERM=network", 0 };
+
+/*
+ * Get()
+ *
+ *     Return next character from file descriptor.
+ *
+ *     This is not meant to be very efficient, since it is only
+ * run during startup.
+ */
+
+Get(f)
+int    f;              /* the file descriptor */
+{
+    char       input;
+
+    if (read(f, &input, 1) != 1) {
+       syslog(LOG_ERR, "read: %m\n");
+       exit(1);
+    }
+    return input&0xff;
+}
+
+char   *terminaltype;
+char   *envinit[2];
 int    cleanup();
 
 /*
 int    cleanup();
 
 /*
@@ -75,30 +194,137 @@ doit(f, who)
        int f;
        struct sockaddr_in *who;
 {
        int f;
        struct sockaddr_in *who;
 {
-       char *cp = line, *host, *ntoa();
-       int i, p, cc, t;
+       char *host, *inet_ntoa();
+       int i, p, t;
        struct sgttyb b;
        struct hostent *hp;
        struct sgttyb b;
        struct hostent *hp;
+       int c;
+       int gotterminaltype = 0;
 
 
-       for (i = 0; i < 16; i++) {
-               cp[strlen("/dev/ptyp")] = "0123456789abcdef"[i];
-               p = open(cp, 2);
-               if (p > 0)
-                       goto gotpty;
+       /*
+        * Try to get a terminal type from the foreign host.
+        */
+
+       {
+           static char sbuf[] = { IAC, DO, TELOPT_TTYPE };
+
+           terminaltype = 0;
+           if (write(f, sbuf, sizeof sbuf) == -1) {
+               syslog(LOG_ERR, "write sbuf: %m\n");
+               exit(1);
+           }
+           for (;;) {          /* ugly, but we are VERY early */
+               while ((c = Get(f)) != IAC) {
+                   NIACCUM(c);
+               }
+               if ((c = Get(f)) == WILL) {
+                   if ((c = Get(f)) == TELOPT_TTYPE) {
+                       static char sbbuf[] = { IAC, SB, TELOPT_TTYPE,
+                                                       TELQUAL_SEND, IAC, SE };
+                       if (write(f, sbbuf, sizeof sbbuf) == -1) {
+                           syslog(LOG_ERR, "write sbbuf: %m\n");
+                           exit(1);
+                       }
+                       break;
+                   } else {
+                       NIACCUM(IAC);
+                       NIACCUM(WILL);
+                       NIACCUM(c);
+                   }
+               } else if (c == WONT) {
+                   if ((c = Get(f)) == TELOPT_TTYPE) {
+                       terminaltype = "TERM=network";
+                       break;
+                   } else {
+                       NIACCUM(IAC);
+                       NIACCUM(WONT);
+                       NIACCUM(c);
+                   }
+               } else {
+                   NIACCUM(IAC);
+                   NIACCUM(c);
+               }
+           }
+           if (!terminaltype) {
+               for (;;) {
+                   while ((c = Get(f)) != IAC) {
+                       NIACCUM(c);
+                   }
+                   if ((c = Get(f)) != SB) {
+                       NIACCUM(IAC);
+                       NIACCUM(c);
+                   } else if ((c = Get(f)) != TELOPT_TTYPE) {
+                       NIACCUM(IAC);
+                       NIACCUM(SB);
+                       NIACCUM(c);
+                   } else if ((c = Get(f)) != TELQUAL_IS) {
+                       NIACCUM(IAC);
+                       NIACCUM(SB);
+                       NIACCUM(TELOPT_TTYPE);
+                       NIACCUM(c);
+                   } else {            /* Yaaaay! */
+                       static char terminalname[5+41] = "TERM=";
+
+                       terminaltype = terminalname+strlen(terminalname);
+
+                       while (terminaltype <
+                                   (terminalname + sizeof terminalname-1)) {
+                           if ((c = Get(f)) == IAC) {
+                               if ((c = Get(f)) == SE) {
+                                   break;              /* done */
+                               } else {
+                                   *terminaltype++ = IAC;      /* ? */
+                                   if (isupper(c)) {
+                                       c = tolower(c);
+                                   }
+                                   *terminaltype++ = c;
+                               }
+                           } else {
+                               if (isupper(c)) {
+                                   c = tolower(c);
+                               }
+                               *terminaltype++ = c;    /* accumulate name */
+                           }
+                       }
+                       *terminaltype = 0;
+                       terminaltype = terminalname;
+                       gotterminaltype = 1;
+                       break;
+                   }
+               }
+           }
+           envinit[0] = terminaltype;
+           envinit[1] = 0;
+       }
+
+       for (c = 'p'; c <= 's'; c++) {
+               struct stat stb;
+
+               line = "/dev/ptyXX";
+               line[strlen("/dev/pty")] = c;
+               line[strlen("/dev/ptyp")] = '0';
+               if (stat(line, &stb) < 0)
+                       break;
+               for (i = 0; i < 16; i++) {
+                       line[strlen("/dev/ptyp")] = "0123456789abcdef"[i];
+                       p = open(line, 2);
+                       if (p > 0)
+                               goto gotpty;
+               }
        }
        fatal(f, "All network ports in use");
        /*NOTREACHED*/
 gotpty:
        dup2(f, 0);
        }
        fatal(f, "All network ports in use");
        /*NOTREACHED*/
 gotpty:
        dup2(f, 0);
-       cp[strlen("/dev/")] = 't';
-       t = open("/dev/tty", 2);
+       line[strlen("/dev/")] = 't';
+       t = open("/dev/tty", O_RDWR);
        if (t >= 0) {
                ioctl(t, TIOCNOTTY, 0);
                close(t);
        }
        if (t >= 0) {
                ioctl(t, TIOCNOTTY, 0);
                close(t);
        }
-       t = open(cp, 2);
+       t = open(line, O_RDWR);
        if (t < 0)
        if (t < 0)
-               fatalperror(f, cp, errno);
+               fatalperror(f, line, errno);
        ioctl(t, TIOCGETP, &b);
        b.sg_flags = CRMOD|XTABS|ANYP;
        ioctl(t, TIOCSETP, &b);
        ioctl(t, TIOCGETP, &b);
        b.sg_flags = CRMOD|XTABS|ANYP;
        ioctl(t, TIOCSETP, &b);
@@ -110,7 +336,7 @@ gotpty:
        if (hp)
                host = hp->h_name;
        else
        if (hp)
                host = hp->h_name;
        else
-               host = ntoa(who->sin_addr);
+               host = inet_ntoa(who->sin_addr);
        if ((i = fork()) < 0)
                fatalperror(f, "fork", errno);
        if (i)
        if ((i = fork()) < 0)
                fatalperror(f, "fork", errno);
        if (i)
@@ -122,7 +348,12 @@ gotpty:
        dup2(t, 2);
        close(t);
        environ = envinit;
        dup2(t, 2);
        close(t);
        environ = envinit;
-       execl("/bin/login", "login", "-h", host, 0);
+       /*
+        * -h : pass on name of host.
+        * -p : don't clobber the environment (so terminal type stays set).
+        */
+       execl("/bin/login", "login", "-h", host,
+                                       gotterminaltype ? "-p" : 0, 0);
        fatalperror(f, "/bin/login", errno);
        /*NOTREACHED*/
 }
        fatalperror(f, "/bin/login", errno);
        /*NOTREACHED*/
 }
@@ -133,7 +364,7 @@ fatal(f, msg)
 {
        char buf[BUFSIZ];
 
 {
        char buf[BUFSIZ];
 
-       (void) sprintf(buf, "telnetd: %s.\n", msg);
+       (void) sprintf(buf, "telnetd: %s.\r\n", msg);
        (void) write(f, buf, strlen(buf));
        exit(1);
 }
        (void) write(f, buf, strlen(buf));
        exit(1);
 }
@@ -146,10 +377,39 @@ fatalperror(f, msg, errno)
        char buf[BUFSIZ];
        extern char *sys_errlist[];
 
        char buf[BUFSIZ];
        extern char *sys_errlist[];
 
-       (void) sprintf(buf, "%s: %s", msg, sys_errlist[errno]);
+       (void) sprintf(buf, "%s: %s\r\n", msg, sys_errlist[errno]);
        fatal(f, buf);
 }
 
        fatal(f, buf);
 }
 
+
+/*
+ * Check a descriptor to see if out of band data exists on it.
+ */
+
+
+stilloob(s)
+int    s;              /* socket number */
+{
+    static struct timeval timeout = { 0 };
+    fd_set     excepts;
+    int value;
+
+    do {
+       FD_ZERO(&excepts);
+       FD_SET(s, &excepts);
+       value = select(s+1, (fd_set *)0, (fd_set *)0, &excepts, &timeout);
+    } while ((value == -1) && (errno = EINTR));
+
+    if (value < 0) {
+       fatalperror(pty, "select", errno);
+    }
+    if (FD_ISSET(s, &excepts)) {
+       return 1;
+    } else {
+       return 0;
+    }
+}
+\f
 /*
  * Main loop.  Select from pty and network, and
  * hand data to telnet receiver finite state machine.
 /*
  * Main loop.  Select from pty and network, and
  * hand data to telnet receiver finite state machine.
@@ -162,62 +422,165 @@ telnet(f, p)
        net = f, pty = p;
        ioctl(f, FIONBIO, &on);
        ioctl(p, FIONBIO, &on);
        net = f, pty = p;
        ioctl(f, FIONBIO, &on);
        ioctl(p, FIONBIO, &on);
+#if    defined(SO_OOBINLINE)
+       setsockopt(net, SOL_SOCKET, SO_OOBINLINE, &on, sizeof on);
+#endif /* defined(SO_OOBINLINE) */
        signal(SIGTSTP, SIG_IGN);
        signal(SIGCHLD, cleanup);
        signal(SIGTSTP, SIG_IGN);
        signal(SIGCHLD, cleanup);
+       setpgrp(0, 0);
 
        /*
 
        /*
-        * Request to do remote echo.
+        * Request to do remote echo and to suppress go ahead.
         */
        dooption(TELOPT_ECHO);
         */
        dooption(TELOPT_ECHO);
-       myopts[TELOPT_ECHO] = 1;
+       dooption(TELOPT_SGA);
+       /*
+        * Is the client side a 4.2 (NOT 4.3) system?  We need to know this
+        * because 4.2 clients are unable to deal with TCP urgent data.
+        *
+        * To find out, we send out a "DO ECHO".  If the remote system
+        * answers "WILL ECHO" it is probably a 4.2 client, and we note
+        * that fact ("WILL ECHO" ==> that the client will echo what
+        * WE, the server, sends it; it does NOT mean that the client will
+        * echo the terminal input).
+        */
+       sprintf(nfrontp, doopt, TELOPT_ECHO);
+       nfrontp += sizeof doopt-2;
+       hisopts[TELOPT_ECHO] = OPT_ALWAYS_LOOK;
+
        /*
         * Show banner that getty never gave.
         */
        gethostname(hostname, sizeof (hostname));
        sprintf(nfrontp, BANNER, hostname, "");
        nfrontp += strlen(nfrontp);
        /*
         * Show banner that getty never gave.
         */
        gethostname(hostname, sizeof (hostname));
        sprintf(nfrontp, BANNER, hostname, "");
        nfrontp += strlen(nfrontp);
+
+       /*
+        * Call telrcv() once to pick up anything received during
+        * terminal type negotiation.
+        */
+       telrcv();
+
        for (;;) {
        for (;;) {
-               int ibits = 0, obits = 0;
+               fd_set ibits, obits, xbits;
                register int c;
 
                register int c;
 
+               if (ncc < 0 && pcc < 0)
+                       break;
+
+               FD_ZERO(&ibits);
+               FD_ZERO(&obits);
+               FD_ZERO(&xbits);
                /*
                 * Never look for input if there's still
                 * stuff in the corresponding output buffer
                 */
                /*
                 * Never look for input if there's still
                 * stuff in the corresponding output buffer
                 */
-               if (nfrontp - nbackp)
-                       obits |= (1 << f);
-               else
-                       ibits |= (1 << p);
-               if (pfrontp - pbackp)
-                       obits |= (1 << p);
-               else
-                       ibits |= (1 << f);
-               if (ncc < 0 && pcc < 0)
-                       break;
-               select(16, &ibits, &obits, 0, 0);
-               if (ibits == 0 && obits == 0) {
+               if (nfrontp - nbackp || pcc > 0) {
+                       FD_SET(f, &obits);
+               } else {
+                       FD_SET(p, &ibits);
+               }
+               if (pfrontp - pbackp || ncc > 0) {
+                       FD_SET(p, &obits);
+               } else {
+                       FD_SET(f, &ibits);
+               }
+               if (!SYNCHing) {
+                       FD_SET(f, &xbits);
+               }
+               if ((c = select(16, &ibits, &obits, &xbits,
+                                               (struct timeval *)0)) < 1) {
+                       if (c == -1) {
+                               if (errno == EINTR) {
+                                       continue;
+                               }
+                       }
                        sleep(5);
                        continue;
                }
 
                        sleep(5);
                        continue;
                }
 
+               /*
+                * Any urgent data?
+                */
+               if (FD_ISSET(net, &xbits)) {
+                   SYNCHing = 1;
+               }
+
                /*
                 * Something to read from the network...
                 */
                /*
                 * Something to read from the network...
                 */
-               if (ibits & (1 << f)) {
-                       ncc = read(f, netibuf, BUFSIZ);
-                       if (ncc < 0 && errno == EWOULDBLOCK)
-                               ncc = 0;
-                       else {
-                               if (ncc <= 0)
-                                       break;
-                               netip = netibuf;
+               if (FD_ISSET(net, &ibits)) {
+#if    !defined(SO_OOBINLINE)
+                       /*
+                        * In 4.2 (and some early 4.3) systems, the
+                        * OOB indication and data handling in the kernel
+                        * is such that if two separate TCP Urgent requests
+                        * come in, one byte of TCP data will be overlaid.
+                        * This is fatal for Telnet, but we try to live
+                        * with it.
+                        *
+                        * In addition, in 4.2 (and...), a special protocol
+                        * is needed to pick up the TCP Urgent data in
+                        * the correct sequence.
+                        *
+                        * What we do is:  if we think we are in urgent
+                        * mode, we look to see if we are "at the mark".
+                        * If we are, we do an OOB receive.  If we run
+                        * this twice, we will do the OOB receive twice,
+                        * but the second will fail, since the second
+                        * time we were "at the mark", but there wasn't
+                        * any data there (the kernel doesn't reset
+                        * "at the mark" until we do a normal read).
+                        * Once we've read the OOB data, we go ahead
+                        * and do normal reads.
+                        *
+                        * There is also another problem, which is that
+                        * since the OOB byte we read doesn't put us
+                        * out of OOB state, and since that byte is most
+                        * likely the TELNET DM (data mark), we would
+                        * stay in the TELNET SYNCH (SYNCHing) state.
+                        * So, clocks to the rescue.  If we've "just"
+                        * received a DM, then we test for the
+                        * presence of OOB data when the receive OOB
+                        * fails (and AFTER we did the normal mode read
+                        * to clear "at the mark").
+                        */
+                   if (SYNCHing) {
+                       int atmark;
+
+                       ioctl(net, SIOCATMARK, (char *)&atmark);
+                       if (atmark) {
+                           ncc = recv(net, netibuf, sizeof (netibuf), MSG_OOB);
+                           if ((ncc == -1) && (errno == EINVAL)) {
+                               ncc = read(net, netibuf, sizeof (netibuf));
+                               if (clocks.didnetreceive < clocks.gotDM) {
+                                   SYNCHing = stilloob(net);
+                               }
+                           }
+                       } else {
+                           ncc = read(net, netibuf, sizeof (netibuf));
                        }
                        }
+                   } else {
+                       ncc = read(net, netibuf, sizeof (netibuf));
+                   }
+                   settimer(didnetreceive);
+#else  /* !defined(SO_OOBINLINE)) */
+                   ncc = read(net, netibuf, sizeof (netibuf));
+#endif /* !defined(SO_OOBINLINE)) */
+                   if (ncc < 0 && errno == EWOULDBLOCK)
+                       ncc = 0;
+                   else {
+                       if (ncc <= 0) {
+                           break;
+                       }
+                       netip = netibuf;
+                   }
                }
 
                /*
                 * Something to read from the pty...
                 */
                }
 
                /*
                 * Something to read from the pty...
                 */
-               if (ibits & (1 << p)) {
+               if (FD_ISSET(p, &ibits)) {
                        pcc = read(p, ptyibuf, BUFSIZ);
                        if (pcc < 0 && errno == EWOULDBLOCK)
                                pcc = 0;
                        pcc = read(p, ptyibuf, BUFSIZ);
                        if (pcc < 0 && errno == EWOULDBLOCK)
                                pcc = 0;
@@ -235,12 +598,19 @@ telnet(f, p)
                        if (c == IAC)
                                *nfrontp++ = c;
                        *nfrontp++ = c;
                        if (c == IAC)
                                *nfrontp++ = c;
                        *nfrontp++ = c;
+                       if (c == '\r') {
+                               if (pcc > 0 && ((*ptyip & 0377) == '\n')) {
+                                       *nfrontp++ = *ptyip++ & 0377;
+                                       pcc--;
+                               } else
+                                       *nfrontp++ = '\0';
+                       }
                }
                }
-               if ((obits & (1 << f)) && (nfrontp - nbackp) > 0)
+               if (FD_ISSET(f, &obits) && (nfrontp - nbackp) > 0)
                        netflush();
                if (ncc > 0)
                        telrcv();
                        netflush();
                if (ncc > 0)
                        telrcv();
-               if ((obits & (1 << p)) && (pfrontp - pbackp) > 0)
+               if (FD_ISSET(p, &obits) && (pfrontp - pbackp) > 0)
                        ptyflush();
        }
        cleanup();
                        ptyflush();
        }
        cleanup();
@@ -252,8 +622,8 @@ telnet(f, p)
 #define        TS_DATA         0       /* base state */
 #define        TS_IAC          1       /* look for double IAC's */
 #define        TS_CR           2       /* CR-LF ->'s CR */
 #define        TS_DATA         0       /* base state */
 #define        TS_IAC          1       /* look for double IAC's */
 #define        TS_CR           2       /* CR-LF ->'s CR */
-#define        TS_BEGINNEG     3       /* throw away begin's... */
-#define        TS_ENDNEG       4       /* ...end's (suboption negotiation) */
+#define        TS_SB           3       /* throw away begin's... */
+#define        TS_SE           4       /* ...end's (suboption negotiation) */
 #define        TS_WILL         5       /* will option negotiation */
 #define        TS_WONT         6       /* wont " */
 #define        TS_DO           7       /* do " */
 #define        TS_WILL         5       /* will option negotiation */
 #define        TS_WONT         6       /* wont " */
 #define        TS_DO           7       /* do " */
@@ -263,7 +633,6 @@ telrcv()
 {
        register int c;
        static int state = TS_DATA;
 {
        register int c;
        static int state = TS_DATA;
-       struct sgttyb b;
 
        while (ncc > 0) {
                if ((&ptyobuf[BUFSIZ] - pfrontp) < 2)
 
        while (ncc > 0) {
                if ((&ptyobuf[BUFSIZ] - pfrontp) < 2)
@@ -271,6 +640,13 @@ telrcv()
                c = *netip++ & 0377, ncc--;
                switch (state) {
 
                c = *netip++ & 0377, ncc--;
                switch (state) {
 
+               case TS_CR:
+                       state = TS_DATA;
+                       if ((c == 0) || (c == '\n')) {
+                               break;
+                       }
+                       /* FALL THROUGH */
+
                case TS_DATA:
                        if (c == IAC) {
                                state = TS_IAC;
                case TS_DATA:
                        if (c == IAC) {
                                state = TS_IAC;
@@ -278,15 +654,22 @@ telrcv()
                        }
                        if (inter > 0)
                                break;
                        }
                        if (inter > 0)
                                break;
+                       /*
+                        * We map \r\n ==> \n, since \r\n says
+                        * that we want to be in column 1 of the next
+                        * printable line, and \n is the standard
+                        * unix way of saying that (\r is only good
+                        * if CRMOD is set, which it normally is).
+                        */
+                       if ((myopts[TELOPT_BINARY] == OPT_DONT) && c == '\r') {
+                               if ((ncc > 0) && ('\n' == *netip)) {
+                                       netip++; ncc--;
+                                       c = '\n';
+                               } else {
+                                       state = TS_CR;
+                               }
+                       }
                        *pfrontp++ = c;
                        *pfrontp++ = c;
-                       if (!myopts[TELOPT_BINARY] && c == '\r')
-                               state = TS_CR;
-                       break;
-
-               case TS_CR:
-                       if (c && c != '\n')
-                               *pfrontp++ = c;
-                       state = TS_DATA;
                        break;
 
                case TS_IAC:
                        break;
 
                case TS_IAC:
@@ -297,48 +680,89 @@ telrcv()
                         * interrupt.  Do this with a NULL or
                         * interrupt char; depending on the tty mode.
                         */
                         * interrupt.  Do this with a NULL or
                         * interrupt char; depending on the tty mode.
                         */
-                       case BREAK:
                        case IP:
                                interrupt();
                                break;
 
                        case IP:
                                interrupt();
                                break;
 
+                       case BREAK:
+                               sendbrk();
+                               break;
+
                        /*
                         * Are You There?
                         */
                        case AYT:
                        /*
                         * Are You There?
                         */
                        case AYT:
-                               *pfrontp++ = BELL;
+                               strcpy(nfrontp, "\r\n[Yes]\r\n");
+                               nfrontp += 9;
                                break;
 
                                break;
 
+                       /*
+                        * Abort Output
+                        */
+                       case AO: {
+                                       struct ltchars tmpltc;
+
+                                       ptyflush();     /* half-hearted */
+                                       ioctl(pty, TIOCGLTC, &tmpltc);
+                                       if (tmpltc.t_flushc != '\377') {
+                                               *pfrontp++ = tmpltc.t_flushc;
+                                       }
+                                       netclear();     /* clear buffer back */
+                                       *nfrontp++ = IAC;
+                                       *nfrontp++ = DM;
+                                       neturg = nfrontp-1; /* off by one XXX */
+                                       break;
+                               }
+
                        /*
                         * Erase Character and
                         * Erase Line
                         */
                        case EC:
                        /*
                         * Erase Character and
                         * Erase Line
                         */
                        case EC:
-                       case EL:
-                               ptyflush();     /* half-hearted */
-                               ioctl(pty, TIOCGETP, &b);
-                               *pfrontp++ = (c == EC) ?
-                                       b.sg_erase : b.sg_kill;
-                               break;
+                       case EL: {
+                                       struct sgttyb b;
+                                       char ch;
+
+                                       ptyflush();     /* half-hearted */
+                                       ioctl(pty, TIOCGETP, &b);
+                                       ch = (c == EC) ?
+                                               b.sg_erase : b.sg_kill;
+                                       if (ch != '\377') {
+                                               *pfrontp++ = ch;
+                                       }
+                                       break;
+                               }
 
                        /*
                         * Check for urgent data...
                         */
                        case DM:
 
                        /*
                         * Check for urgent data...
                         */
                        case DM:
+                               SYNCHing = stilloob(net);
+                               settimer(gotDM);
                                break;
 
                                break;
 
+
                        /*
                         * Begin option subnegotiation...
                         */
                        case SB:
                        /*
                         * Begin option subnegotiation...
                         */
                        case SB:
-                               state = TS_BEGINNEG;
+                               state = TS_SB;
                                continue;
 
                        case WILL:
                                continue;
 
                        case WILL:
+                               state = TS_WILL;
+                               continue;
+
                        case WONT:
                        case WONT:
+                               state = TS_WONT;
+                               continue;
+
                        case DO:
                        case DO:
+                               state = TS_DO;
+                               continue;
+
                        case DONT:
                        case DONT:
-                               state = TS_WILL + (c - WILL);
+                               state = TS_DONT;
                                continue;
 
                        case IAC:
                                continue;
 
                        case IAC:
@@ -348,38 +772,49 @@ telrcv()
                        state = TS_DATA;
                        break;
 
                        state = TS_DATA;
                        break;
 
-               case TS_BEGINNEG:
-                       if (c == IAC)
-                               state = TS_ENDNEG;
+               case TS_SB:
+                       if (c == IAC) {
+                               state = TS_SE;
+                       } else {
+                               SB_ACCUM(c);
+                       }
                        break;
 
                        break;
 
-               case TS_ENDNEG:
-                       state = c == SE ? TS_DATA : TS_BEGINNEG;
+               case TS_SE:
+                       if (c != SE) {
+                               if (c != IAC) {
+                                       SB_ACCUM(IAC);
+                               }
+                               SB_ACCUM(c);
+                               state = TS_SB;
+                       } else {
+                               SB_TERM();
+                               suboption();    /* handle sub-option */
+                               state = TS_DATA;
+                       }
                        break;
 
                case TS_WILL:
                        break;
 
                case TS_WILL:
-                       if (!hisopts[c])
+                       if (hisopts[c] != OPT_WILL)
                                willoption(c);
                        state = TS_DATA;
                        continue;
 
                case TS_WONT:
                                willoption(c);
                        state = TS_DATA;
                        continue;
 
                case TS_WONT:
-                       if (hisopts[c])
+                       if (hisopts[c] != OPT_WONT)
                                wontoption(c);
                        state = TS_DATA;
                        continue;
 
                case TS_DO:
                                wontoption(c);
                        state = TS_DATA;
                        continue;
 
                case TS_DO:
-                       if (!myopts[c])
+                       if (myopts[c] != OPT_DO)
                                dooption(c);
                        state = TS_DATA;
                        continue;
 
                case TS_DONT:
                                dooption(c);
                        state = TS_DATA;
                        continue;
 
                case TS_DONT:
-                       if (myopts[c]) {
-                               myopts[c] = 0;
-                               sprintf(nfrontp, wont, c);
-                               nfrontp += sizeof (wont) - 2;
+                       if (myopts[c] != OPT_DONT) {
+                               dontoption(c);
                        }
                        state = TS_DATA;
                        continue;
                        }
                        state = TS_DATA;
                        continue;
@@ -400,15 +835,24 @@ willoption(option)
 
        case TELOPT_BINARY:
                mode(RAW, 0);
 
        case TELOPT_BINARY:
                mode(RAW, 0);
-               goto common;
+               fmt = doopt;
+               break;
 
        case TELOPT_ECHO:
 
        case TELOPT_ECHO:
-               mode(0, ECHO|CRMOD);
-               /*FALL THRU*/
+               not42 = 0;              /* looks like a 4.2 system */
+               /*
+                * Now, in a 4.2 system, to break them out of ECHOing
+                * (to the terminal) mode, we need to send a "WILL ECHO".
+                * Kludge upon kludge!
+                */
+               if (myopts[TELOPT_ECHO] == OPT_DO) {
+                   dooption(TELOPT_ECHO);
+               }
+               fmt = dont;
+               break;
 
 
+       case TELOPT_TTYPE:
        case TELOPT_SGA:
        case TELOPT_SGA:
-       common:
-               hisopts[option] = 1;
                fmt = doopt;
                break;
 
                fmt = doopt;
                break;
 
@@ -420,6 +864,11 @@ willoption(option)
                fmt = dont;
                break;
        }
                fmt = dont;
                break;
        }
+       if (fmt == doopt) {
+               hisopts[option] = OPT_WILL;
+       } else {
+               hisopts[option] = OPT_WONT;
+       }
        sprintf(nfrontp, fmt, option);
        nfrontp += sizeof (dont) - 2;
 }
        sprintf(nfrontp, fmt, option);
        nfrontp += sizeof (dont) - 2;
 }
@@ -430,24 +879,16 @@ wontoption(option)
        char *fmt;
 
        switch (option) {
        char *fmt;
 
        switch (option) {
-
        case TELOPT_ECHO:
        case TELOPT_ECHO:
-               mode(ECHO|CRMOD, 0);
-               goto common;
+               not42 = 1;              /* doesn't seem to be a 4.2 system */
+               break;
 
        case TELOPT_BINARY:
                mode(0, RAW);
 
        case TELOPT_BINARY:
                mode(0, RAW);
-               /*FALL THRU*/
-
-       case TELOPT_SGA:
-       common:
-               hisopts[option] = 0;
-               fmt = dont;
                break;
                break;
-
-       default:
-               fmt = dont;
        }
        }
+       fmt = dont;
+       hisopts[option] = OPT_WONT;
        sprintf(nfrontp, fmt, option);
        nfrontp += sizeof (doopt) - 2;
 }
        sprintf(nfrontp, fmt, option);
        nfrontp += sizeof (doopt) - 2;
 }
@@ -465,14 +906,15 @@ dooption(option)
 
        case TELOPT_ECHO:
                mode(ECHO|CRMOD, 0);
 
        case TELOPT_ECHO:
                mode(ECHO|CRMOD, 0);
-               goto common;
+               fmt = will;
+               break;
 
        case TELOPT_BINARY:
                mode(RAW, 0);
 
        case TELOPT_BINARY:
                mode(RAW, 0);
-               /*FALL THRU*/
+               fmt = will;
+               break;
 
        case TELOPT_SGA:
 
        case TELOPT_SGA:
-       common:
                fmt = will;
                break;
 
                fmt = will;
                break;
 
@@ -480,10 +922,58 @@ dooption(option)
                fmt = wont;
                break;
        }
                fmt = wont;
                break;
        }
+       if (fmt == will) {
+           myopts[option] = OPT_DO;
+       } else {
+           myopts[option] = OPT_DONT;
+       }
        sprintf(nfrontp, fmt, option);
        nfrontp += sizeof (doopt) - 2;
 }
 
        sprintf(nfrontp, fmt, option);
        nfrontp += sizeof (doopt) - 2;
 }
 
+
+dontoption(option)
+int option;
+{
+    char *fmt;
+
+    switch (option) {
+    case TELOPT_ECHO:          /* we should stop echoing */
+       mode(0, ECHO|CRMOD);
+       fmt = wont;
+       break;
+    default:
+       fmt = wont;
+       break;
+    }
+    if (fmt = wont) {
+       myopts[option] = OPT_DONT;
+    } else {
+       myopts[option] = OPT_DO;
+    }
+    sprintf(nfrontp, fmt, option);
+    nfrontp += sizeof (wont) - 2;
+}
+
+/*
+ * suboption()
+ *
+ *     Look at the sub-option buffer, and try to be helpful to the other
+ * side.
+ *
+ *     Currently we recognize:
+ *
+ *     (nothing - we only do terminal type at start-up time)
+ */
+
+suboption()
+{
+    switch (subbuffer[0]&0xff) {
+    default:
+       ;
+    }
+}
+
 mode(on, off)
        int on, off;
 {
 mode(on, off)
        int on, off;
 {
@@ -516,6 +1006,26 @@ interrupt()
                '\177' : tchars.t_intrc;
 }
 
                '\177' : tchars.t_intrc;
 }
 
+/*
+ * Send quit to process on other side of pty.
+ * If it is in raw mode, just write NULL;
+ * otherwise, write quit char.
+ */
+sendbrk()
+{
+       struct sgttyb b;
+       struct tchars tchars;
+
+       ptyflush();     /* half-hearted */
+       ioctl(pty, TIOCGETP, &b);
+       if (b.sg_flags & RAW) {
+               *pfrontp++ = '\0';
+               return;
+       }
+       *pfrontp++ = ioctl(pty, TIOCGETC, &tchars) < 0 ?
+               '\034' : tchars.t_quitc;
+}
+
 ptyflush()
 {
        int n;
 ptyflush()
 {
        int n;
@@ -528,22 +1038,154 @@ ptyflush()
        if (pbackp == pfrontp)
                pbackp = pfrontp = ptyobuf;
 }
        if (pbackp == pfrontp)
                pbackp = pfrontp = ptyobuf;
 }
+\f
+/*
+ * nextitem()
+ *
+ *     Return the address of the next "item" in the TELNET data
+ * stream.  This will be the address of the next character if
+ * the current address is a user data character, or it will
+ * be the address of the character following the TELNET command
+ * if the current address is a TELNET IAC ("I Am a Command")
+ * character.
+ */
+
+char *
+nextitem(current)
+char   *current;
+{
+    if ((*current&0xff) != IAC) {
+       return current+1;
+    }
+    switch (*(current+1)&0xff) {
+    case DO:
+    case DONT:
+    case WILL:
+    case WONT:
+       return current+3;
+    case SB:           /* loop forever looking for the SE */
+       {
+           register char *look = current+2;
+
+           for (;;) {
+               if ((*look++&0xff) == IAC) {
+                   if ((*look++&0xff) == SE) {
+                       return look;
+                   }
+               }
+           }
+       }
+    default:
+       return current+2;
+    }
+}
+
+
+/*
+ * netclear()
+ *
+ *     We are about to do a TELNET SYNCH operation.  Clear
+ * the path to the network.
+ *
+ *     Things are a bit tricky since we may have sent the first
+ * byte or so of a previous TELNET command into the network.
+ * So, we have to scan the network buffer from the beginning
+ * until we are up to where we want to be.
+ *
+ *     A side effect of what we do, just to keep things
+ * simple, is to clear the urgent data pointer.  The principal
+ * caller should be setting the urgent data pointer AFTER calling
+ * us in any case.
+ */
+
+netclear()
+{
+    register char *thisitem, *next;
+    char *good;
+#define        wewant(p)       ((nfrontp > p) && ((*p&0xff) == IAC) && \
+                               ((*(p+1)&0xff) != EC) && ((*(p+1)&0xff) != EL))
+
+    thisitem = netobuf;
+
+    while ((next = nextitem(thisitem)) <= nbackp) {
+       thisitem = next;
+    }
+
+    /* Now, thisitem is first before/at boundary. */
+
+    good = netobuf;    /* where the good bytes go */
+
+    while (nfrontp > thisitem) {
+       if (wewant(thisitem)) {
+           int length;
+
+           next = thisitem;
+           do {
+               next = nextitem(next);
+           } while (wewant(next) && (nfrontp > next));
+           length = next-thisitem;
+           bcopy(thisitem, good, length);
+           good += length;
+           thisitem = next;
+       } else {
+           thisitem = nextitem(thisitem);
+       }
+    }
+
+    nbackp = netobuf;
+    nfrontp = good;            /* next byte to be sent */
+    neturg = 0;
+}
+\f
+/*
+ *  netflush
+ *             Send as much data as possible to the network,
+ *     handling requests for urgent data.
+ */
+
 
 netflush()
 {
 
 netflush()
 {
-       int n;
+    int n;
 
 
-       if ((n = nfrontp - nbackp) > 0)
-               n = write(net, nbackp, n);
-       if (n < 0) {
-               if (errno == EWOULDBLOCK)
-                       return;
-               /* should blow this guy away... */
-               return;
+    if ((n = nfrontp - nbackp) > 0) {
+       /*
+        * if no urgent data, or if the other side appears to be an
+        * old 4.2 client (and thus unable to survive TCP urgent data),
+        * write the entire buffer in non-OOB mode.
+        */
+       if ((neturg == 0) || (not42 == 0)) {
+           n = write(net, nbackp, n);  /* normal write */
+       } else {
+           n = neturg - nbackp;
+           /*
+            * In 4.2 (and 4.3) systems, there is some question about
+            * what byte in a sendOOB operation is the "OOB" data.
+            * To make ourselves compatible, we only send ONE byte
+            * out of band, the one WE THINK should be OOB (though
+            * we really have more the TCP philosophy of urgent data
+            * rather than the Unix philosophy of OOB data).
+            */
+           if (n > 1) {
+               n = send(net, nbackp, n-1, 0);  /* send URGENT all by itself */
+           } else {
+               n = send(net, nbackp, n, MSG_OOB);      /* URGENT data */
+           }
        }
        }
-       nbackp += n;
-       if (nbackp == nfrontp)
-               nbackp = nfrontp = netobuf;
+    }
+    if (n < 0) {
+       if (errno == EWOULDBLOCK)
+           return;
+       /* should blow this guy away... */
+       return;
+    }
+    nbackp += n;
+    if (nbackp >= neturg) {
+       neturg = 0;
+    }
+    if (nbackp == nfrontp) {
+       nbackp = nfrontp = netobuf;
+    }
 }
 
 cleanup()
 }
 
 cleanup()
@@ -552,7 +1194,6 @@ cleanup()
        rmut();
        vhangup();      /* XXX */
        shutdown(net, 2);
        rmut();
        vhangup();      /* XXX */
        shutdown(net, 2);
-       kill(0, SIGKILL);
        exit(1);
 }
 
        exit(1);
 }
 
@@ -560,38 +1201,50 @@ cleanup()
 
 struct utmp wtmp;
 char   wtmpf[] = "/usr/adm/wtmp";
 
 struct utmp wtmp;
 char   wtmpf[] = "/usr/adm/wtmp";
-char   utmp[] = "/etc/utmp";
-#define SCPYN(a, b)    strncpy(a, b, sizeof (a))
-#define SCMPN(a, b)    strncmp(a, b, sizeof (a))
+char   utmpf[] = "/etc/utmp";
+#define SCPYN(a, b)    strncpy(a, b, sizeof(a))
+#define SCMPN(a, b)    strncmp(a, b, sizeof(a))
 
 rmut()
 {
        register f;
        int found = 0;
 
 rmut()
 {
        register f;
        int found = 0;
+       struct utmp *u, *utmp;
+       int nutmp;
+       struct stat statbf;
 
 
-       f = open(utmp, 2);
+       f = open(utmpf, O_RDWR);
        if (f >= 0) {
        if (f >= 0) {
-               while(read(f, (char *)&wtmp, sizeof (wtmp)) == sizeof (wtmp)) {
-                       if (SCMPN(wtmp.ut_line, line+5) || wtmp.ut_name[0]==0)
-                               continue;
-                       lseek(f, -(long)sizeof (wtmp), 1);
-                       SCPYN(wtmp.ut_name, "");
-                       SCPYN(wtmp.ut_host, "");
-                       time(&wtmp.ut_time);
-                       write(f, (char *)&wtmp, sizeof (wtmp));
-                       found++;
+               fstat(f, &statbf);
+               utmp = (struct utmp *)malloc(statbf.st_size);
+               if (!utmp)
+                       syslog(LOG_ERR, "utmp malloc failed");
+               if (statbf.st_size && utmp) {
+                       nutmp = read(f, utmp, statbf.st_size);
+                       nutmp /= sizeof(struct utmp);
+               
+                       for (u = utmp ; u < &utmp[nutmp] ; u++) {
+                               if (SCMPN(u->ut_line, line+5) ||
+                                   u->ut_name[0]==0)
+                                       continue;
+                               lseek(f, ((long)u)-((long)utmp), L_SET);
+                               SCPYN(u->ut_name, "");
+                               SCPYN(u->ut_host, "");
+                               time(&u->ut_time);
+                               write(f, (char *)u, sizeof(wtmp));
+                               found++;
+                       }
                }
                close(f);
        }
        if (found) {
                }
                close(f);
        }
        if (found) {
-               f = open(wtmpf, 1);
+               f = open(wtmpf, O_WRONLY|O_APPEND);
                if (f >= 0) {
                        SCPYN(wtmp.ut_line, line+5);
                        SCPYN(wtmp.ut_name, "");
                        SCPYN(wtmp.ut_host, "");
                        time(&wtmp.ut_time);
                if (f >= 0) {
                        SCPYN(wtmp.ut_line, line+5);
                        SCPYN(wtmp.ut_name, "");
                        SCPYN(wtmp.ut_host, "");
                        time(&wtmp.ut_time);
-                       lseek(f, (long)0, 2);
-                       write(f, (char *)&wtmp, sizeof (wtmp));
+                       write(f, (char *)&wtmp, sizeof(wtmp));
                        close(f);
                }
        }
                        close(f);
                }
        }
@@ -601,20 +1254,3 @@ rmut()
        chmod(line, 0666);
        chown(line, 0, 0);
 }
        chmod(line, 0666);
        chown(line, 0, 0);
 }
-
-/*
- * Convert network-format internet address
- * to base 256 d.d.d.d representation.
- */
-char *
-ntoa(in)
-       struct in_addr in;
-{
-       static char b[18];
-       register char *p;
-
-       p = (char *)&in;
-#define        UC(b)   (((int)b)&0xff)
-       sprintf(b, "%d.%d.%d.%d", UC(p[0]), UC(p[1]), UC(p[2]), UC(p[3]));
-       return (b);
-}