BSD 4_4_Lite1 release
[unix-history] / usr / src / libexec / telnetd / state.c
index edb6436..2d327a5 100644 (file)
@@ -1,46 +1,71 @@
 /*
 /*
- * Copyright (c) 1989 Regents of the University of California.
- * All rights reserved.
+ * Copyright (c) 1989, 1993
+ *     The Regents of the University of California.  All rights reserved.
  *
  *
- * Redistribution and use in source and binary forms are permitted
- * provided that the above copyright notice and this paragraph are
- * duplicated in all such forms and that any documentation,
- * advertising materials, and other materials related to such
- * distribution and use acknowledge that the software was developed
- * by the University of California, Berkeley.  The name of the
- * University may not be used to endorse or promote products derived
- * from this software without specific prior written permission.
- * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
- * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *     This product includes software developed by the University of
+ *     California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
  */
 
 #ifndef lint
  */
 
 #ifndef lint
-static char sccsid[] = "@(#)state.c    5.4 (Berkeley) %G%";
+static char sccsid[] = "@(#)state.c    8.2 (Berkeley) 12/15/93";
 #endif /* not lint */
 
 #include "telnetd.h"
 #endif /* not lint */
 
 #include "telnetd.h"
+#if    defined(AUTHENTICATION)
+#include <libtelnet/auth.h>
+#endif
 
 
-char   doopt[] = { IAC, DO, '%', 'c', 0 };
-char   dont[] = { IAC, DONT, '%', 'c', 0 };
-char   will[] = { IAC, WILL, '%', 'c', 0 };
-char   wont[] = { IAC, WONT, '%', 'c', 0 };
+unsigned char  doopt[] = { IAC, DO, '%', 'c', 0 };
+unsigned char  dont[] = { IAC, DONT, '%', 'c', 0 };
+unsigned char  will[] = { IAC, WILL, '%', 'c', 0 };
+unsigned char  wont[] = { IAC, WONT, '%', 'c', 0 };
 int    not42 = 1;
 
 /*
  * Buffer for sub-options, and macros
  * for suboptions buffer manipulations
  */
 int    not42 = 1;
 
 /*
  * Buffer for sub-options, and macros
  * for suboptions buffer manipulations
  */
-char   subbuffer[100], *subpointer= subbuffer, *subend= subbuffer;
+unsigned char subbuffer[512], *subpointer= subbuffer, *subend= subbuffer;
 
 
-#define        SB_CLEAR()      subpointer = subbuffer;
+#define        SB_CLEAR()      subpointer = subbuffer
 #define        SB_TERM()       { subend = subpointer; SB_CLEAR(); }
 #define        SB_ACCUM(c)     if (subpointer < (subbuffer+sizeof subbuffer)) { \
                                *subpointer++ = (c); \
                        }
 #define        SB_GET()        ((*subpointer++)&0xff)
 #define        SB_EOF()        (subpointer >= subend)
 #define        SB_TERM()       { subend = subpointer; SB_CLEAR(); }
 #define        SB_ACCUM(c)     if (subpointer < (subbuffer+sizeof subbuffer)) { \
                                *subpointer++ = (c); \
                        }
 #define        SB_GET()        ((*subpointer++)&0xff)
 #define        SB_EOF()        (subpointer >= subend)
+#define        SB_LEN()        (subend - subpointer)
 
 
+#ifdef ENV_HACK
+unsigned char *subsave;
+#define SB_SAVE()      subsave = subpointer;
+#define        SB_RESTORE()    subpointer = subsave;
+#endif
 
 
 /*
 
 
 /*
@@ -56,11 +81,12 @@ char        subbuffer[100], *subpointer= subbuffer, *subend= subbuffer;
 #define        TS_DO           7       /* do " */
 #define        TS_DONT         8       /* dont " */
 
 #define        TS_DO           7       /* do " */
 #define        TS_DONT         8       /* dont " */
 
+       void
 telrcv()
 {
        register int c;
        static int state = TS_DATA;
 telrcv()
 {
        register int c;
        static int state = TS_DATA;
-#ifdef CRAY2
+#if    defined(CRAY2) && defined(UNICOS5)
        char *opfrontp = pfrontp;
 #endif
 
        char *opfrontp = pfrontp;
 #endif
 
@@ -68,6 +94,10 @@ telrcv()
                if ((&ptyobuf[BUFSIZ] - pfrontp) < 2)
                        break;
                c = *netip++ & 0377, ncc--;
                if ((&ptyobuf[BUFSIZ] - pfrontp) < 2)
                        break;
                c = *netip++ & 0377, ncc--;
+#ifdef ENCRYPTION
+               if (decrypt_input)
+                       c = (*decrypt_input)(c);
+#endif /* ENCRYPTION */
                switch (state) {
 
                case TS_CR:
                switch (state) {
 
                case TS_CR:
@@ -94,15 +124,28 @@ telrcv()
                         * unix way of saying that (\r is only good
                         * if CRMOD is set, which it normally is).
                         */
                         * unix way of saying that (\r is only good
                         * if CRMOD is set, which it normally is).
                         */
-                       if ((c == '\r') && (hisopts[TELOPT_BINARY] == OPT_NO)) {
+                       if ((c == '\r') && his_state_is_wont(TELOPT_BINARY)) {
+                               int nc = *netip;
+#ifdef ENCRYPTION
+                               if (decrypt_input)
+                                       nc = (*decrypt_input)(nc & 0xff);
+#endif /* ENCRYPTION */
+#ifdef LINEMODE
                                /*
                                 * If we are operating in linemode,
                                 * convert to local end-of-line.
                                 */
                                /*
                                 * If we are operating in linemode,
                                 * convert to local end-of-line.
                                 */
-                               if ((linemode) && (ncc > 0)&&('\n' == *netip)) {
+                               if (linemode && (ncc > 0) && (('\n' == nc) ||
+                                        ((0 == nc) && tty_iscrnl())) ) {
                                        netip++; ncc--;
                                        c = '\n';
                                        netip++; ncc--;
                                        c = '\n';
-                               } else {
+                               } else
+#endif
+                               {
+#ifdef ENCRYPTION
+                                       if (decrypt_input)
+                                               (void)(*decrypt_input)(-1);
+#endif /* ENCRYPTION */
                                        state = TS_CR;
                                }
                        }
                                        state = TS_CR;
                                }
                        }
@@ -118,10 +161,14 @@ gotiac:                   switch (c) {
                         * interrupt char; depending on the tty mode.
                         */
                        case IP:
                         * interrupt char; depending on the tty mode.
                         */
                        case IP:
+                               DIAG(TD_OPTIONS,
+                                       printoption("td: recv IAC", c));
                                interrupt();
                                break;
 
                        case BREAK:
                                interrupt();
                                break;
 
                        case BREAK:
+                               DIAG(TD_OPTIONS,
+                                       printoption("td: recv IAC", c));
                                sendbrk();
                                break;
 
                                sendbrk();
                                break;
 
@@ -129,8 +176,9 @@ gotiac:                     switch (c) {
                         * Are You There?
                         */
                        case AYT:
                         * Are You There?
                         */
                        case AYT:
-                               (void) strcpy(nfrontp, "\r\n[Yes]\r\n");
-                               nfrontp += 9;
+                               DIAG(TD_OPTIONS,
+                                       printoption("td: recv IAC", c));
+                               recv_ayt();
                                break;
 
                        /*
                                break;
 
                        /*
@@ -138,18 +186,23 @@ gotiac:                   switch (c) {
                         */
                        case AO:
                            {
                         */
                        case AO:
                            {
+                               DIAG(TD_OPTIONS,
+                                       printoption("td: recv IAC", c));
                                ptyflush();     /* half-hearted */
                                init_termbuf();
 
                                if (slctab[SLC_AO].sptr &&
                                ptyflush();     /* half-hearted */
                                init_termbuf();
 
                                if (slctab[SLC_AO].sptr &&
-                                   *slctab[SLC_AO].sptr != '\377') {
-                                       *pfrontp++ = *slctab[SLC_AO].sptr;
+                                   *slctab[SLC_AO].sptr != (cc_t)(_POSIX_VDISABLE)) {
+                                   *pfrontp++ =
+                                       (unsigned char)*slctab[SLC_AO].sptr;
                                }
 
                                netclear();     /* clear buffer back */
                                *nfrontp++ = IAC;
                                *nfrontp++ = DM;
                                neturg = nfrontp-1; /* off by one XXX */
                                }
 
                                netclear();     /* clear buffer back */
                                *nfrontp++ = IAC;
                                *nfrontp++ = DM;
                                neturg = nfrontp-1; /* off by one XXX */
+                               DIAG(TD_OPTIONS,
+                                       printoption("td: send IAC", DM));
                                break;
                            }
 
                                break;
                            }
 
@@ -160,14 +213,18 @@ gotiac:                   switch (c) {
                        case EC:
                        case EL:
                            {
                        case EC:
                        case EL:
                            {
-                               unsigned char ch;
+                               cc_t ch;
 
 
+                               DIAG(TD_OPTIONS,
+                                       printoption("td: recv IAC", c));
                                ptyflush();     /* half-hearted */
                                init_termbuf();
                                ptyflush();     /* half-hearted */
                                init_termbuf();
-                               ch = (c == EC) ? *slctab[SLC_EC].sptr :
-                                                *slctab[SLC_EL].sptr;
-                               if (ch != '\377')
-                                       *pfrontp++ = ch;
+                               if (c == EC)
+                                       ch = *slctab[SLC_EC].sptr;
+                               else
+                                       ch = *slctab[SLC_EL].sptr;
+                               if (ch != (cc_t)(_POSIX_VDISABLE))
+                                       *pfrontp++ = (unsigned char)ch;
                                break;
                            }
 
                                break;
                            }
 
@@ -175,6 +232,8 @@ gotiac:                     switch (c) {
                         * Check for urgent data...
                         */
                        case DM:
                         * Check for urgent data...
                         */
                        case DM:
+                               DIAG(TD_OPTIONS,
+                                       printoption("td: recv IAC", c));
                                SYNCHing = stilloob(net);
                                settimer(gotDM);
                                break;
                                SYNCHing = stilloob(net);
                                settimer(gotDM);
                                break;
@@ -204,7 +263,7 @@ gotiac:                     switch (c) {
                                state = TS_DONT;
                                continue;
                        case EOR:
                                state = TS_DONT;
                                continue;
                        case EOR:
-                               if (hisopts[TELOPT_EOR])
+                               if (his_state_is_will(TELOPT_EOR))
                                        doeof();
                                break;
 
                                        doeof();
                                break;
 
@@ -250,6 +309,12 @@ gotiac:                    switch (c) {
                                         * then treat remaining stream as
                                         * another command sequence.
                                         */
                                         * then treat remaining stream as
                                         * another command sequence.
                                         */
+
+                                       /* for DIAGNOSTICS */
+                                       SB_ACCUM(IAC);
+                                       SB_ACCUM(c);
+                                       subpointer -= 2;
+
                                        SB_TERM();
                                        suboption();
                                        state = TS_IAC;
                                        SB_TERM();
                                        suboption();
                                        state = TS_IAC;
@@ -258,6 +323,11 @@ gotiac:                    switch (c) {
                                SB_ACCUM(c);
                                state = TS_SB;
                        } else {
                                SB_ACCUM(c);
                                state = TS_SB;
                        } else {
+                               /* for DIAGNOSTICS */
+                               SB_ACCUM(IAC);
+                               SB_ACCUM(SE);
+                               subpointer -= 2;
+
                                SB_TERM();
                                suboption();    /* handle sub-option */
                                state = TS_DATA;
                                SB_TERM();
                                suboption();    /* handle sub-option */
                                state = TS_DATA;
@@ -290,7 +360,7 @@ gotiac:                     switch (c) {
                        exit(1);
                }
        }
                        exit(1);
                }
        }
-#if    CRAY2
+#if    defined(CRAY2) && defined(UNICOS5)
        if (!linemode) {
                char    xptyobuf[BUFSIZ+NETSLOP];
                char    xbuf2[BUFSIZ];
        if (!linemode) {
                char    xptyobuf[BUFSIZ+NETSLOP];
                char    xbuf2[BUFSIZ];
@@ -304,20 +374,12 @@ gotiac:                   switch (c) {
                        if ((*nfrontp++ = *cp++) == IAC)
                                *nfrontp++ = IAC;
        }
                        if ((*nfrontp++ = *cp++) == IAC)
                                *nfrontp++ = IAC;
        }
-#endif /* CRAY2 */
+#endif /* defined(CRAY2) && defined(UNICOS5) */
 }  /* end of telrcv */
 
 /*
  * The will/wont/do/dont state machines are based on Dave Borman's
 }  /* end of telrcv */
 
 /*
  * The will/wont/do/dont state machines are based on Dave Borman's
- * Telnet option processing state machine.  We keep track of the full
- * state of the option negotiation with the following state variables
- *     myopts, hisopts - The last fully negotiated state for each
- *                     side of the connection.
- *     mywants, hiswants - The state we wish to be in after a completed
- *                     negotiation.  (hiswants is slightly misleading,
- *                     this is more precisely the state I want him to
- *                     be in.
- *     resp - We count the number of requests we have sent out.
+ * Telnet option processing state machine.
  *
  * These correspond to the following states:
  *     my_state = the last negotiated state
  *
  * These correspond to the following states:
  *     my_state = the last negotiated state
@@ -370,16 +432,14 @@ gotiac:                   switch (c) {
  * peer probably should be buffering until this option state negotiation
  * is complete.
  *
  * peer probably should be buffering until this option state negotiation
  * is complete.
  *
- * In processing options, request signifies whether this is a request
- * to send or a response.  request is true if this is a request to 
- * send generated locally.
  */
  */
+       void
 send_do(option, init)
        int option, init;
 {
        if (init) {
 send_do(option, init)
        int option, init;
 {
        if (init) {
-               if ((do_dont_resp[option] == 0 && hisopts[option] == OPT_YES) ||
-                   hiswants[option] == OPT_YES)
+               if ((do_dont_resp[option] == 0 && his_state_is_will(option)) ||
+                   his_want_state_is_will(option))
                        return;
                /*
                 * Special case for TELOPT_TM:  We send a DO, but pretend
                        return;
                /*
                 * Special case for TELOPT_TM:  We send a DO, but pretend
@@ -387,31 +447,47 @@ send_do(option, init)
                 * we want to.
                 */
                if (option == TELOPT_TM)
                 * we want to.
                 */
                if (option == TELOPT_TM)
-                       hiswants[option] = OPT_NO;
+                       set_his_want_state_wont(option);
                else
                else
-                       hiswants[option] = OPT_YES;
+                       set_his_want_state_will(option);
                do_dont_resp[option]++;
        }
                do_dont_resp[option]++;
        }
-       (void) sprintf(nfrontp, doopt, option);
+       (void) sprintf(nfrontp, (char *)doopt, option);
        nfrontp += sizeof (dont) - 2;
        nfrontp += sizeof (dont) - 2;
+
+       DIAG(TD_OPTIONS, printoption("td: send do", option));
 }
 
 }
 
+#ifdef AUTHENTICATION
+extern void auth_request();
+#endif
+#ifdef LINEMODE
+extern void doclientstat();
+#endif
+#ifdef ENCRYPTION
+extern void encrypt_send_support();
+#endif /* ENCRYPTION */
+
+       void
 willoption(option)
        int option;
 {
        int changeok = 0;
 willoption(option)
        int option;
 {
        int changeok = 0;
+       void (*func)() = 0;
 
        /*
         * process input from peer.
         */
 
 
        /*
         * process input from peer.
         */
 
+       DIAG(TD_OPTIONS, printoption("td: recv will", option));
+
        if (do_dont_resp[option]) {
                do_dont_resp[option]--;
        if (do_dont_resp[option]) {
                do_dont_resp[option]--;
-               if (do_dont_resp[option] && hisopts[option] == OPT_YES)
+               if (do_dont_resp[option] && his_state_is_will(option))
                        do_dont_resp[option]--;
        }
        if (do_dont_resp[option] == 0) {
                        do_dont_resp[option]--;
        }
        if (do_dont_resp[option] == 0) {
-           if (hiswants[option] != OPT_YES) {
+           if (his_want_state_is_wont(option)) {
                switch (option) {
 
                case TELOPT_BINARY:
                switch (option) {
 
                case TELOPT_BINARY:
@@ -422,40 +498,10 @@ willoption(option)
                        break;
 
                case TELOPT_ECHO:
                        break;
 
                case TELOPT_ECHO:
-                       not42 = 0;      /* looks like a 4.2 system */
-#ifdef notdef
-                       /*
-                        * Now, in a 4.2 system, to break them out of
-                        * ECHOing (to the terminal) mode, we need to
-                        * send a WILL ECHO.
-                        */
-                       if (myopts[TELOPT_ECHO] == OPT_YES) {
-                               send_will(TELOPT_ECHO, 1);
-                       }
-#else
-                       /*
-                        * "WILL ECHO".  Kludge upon kludge!
-                        * A 4.2 client is now echoing user input at
-                        * the tty.  This is probably undesireable and
-                        * it should be stopped.  The client will
-                        * respond WONT TM to the DO TM that we send to
-                        * check for kludge linemode.  When the WONT TM
-                        * arrives, linemode will be turned off and a
-                        * change propogated to the pty.  This change
-                        * will cause us to process the new pty state
-                        * in localstat(), which will notice that
-                        * linemode is off and send a WILL ECHO
-                        * so that we are properly in character mode and
-                        * all is well.
-                        */
-#endif
                        /*
                        /*
-                        * Fool the state machine into sending a don't.
-                        * This also allows the initial echo sending
-                        * code to break out of the loop that it is
-                        * in.  (Look in telnet())
+                        * See comments below for more info.
                         */
                         */
-                       hiswants[TELOPT_ECHO] = OPT_NO;
+                       not42 = 0;      /* looks like a 4.2 system */
                        break;
 
                case TELOPT_TM:
                        break;
 
                case TELOPT_TM:
@@ -484,11 +530,13 @@ willoption(option)
                                lmodetype = KLUDGE_LINEMODE;
                                clientstat(TELOPT_LINEMODE, WILL, 0);
                                send_wont(TELOPT_SGA, 1);
                                lmodetype = KLUDGE_LINEMODE;
                                clientstat(TELOPT_LINEMODE, WILL, 0);
                                send_wont(TELOPT_SGA, 1);
+                       } else if (lmodetype == NO_AUTOKLUDGE) {
+                               lmodetype = KLUDGE_OK;
                        }
 #endif /* defined(LINEMODE) && defined(KLUDGELINEMODE) */
                        /*
                         * We never respond to a WILL TM, and
                        }
 #endif /* defined(LINEMODE) && defined(KLUDGELINEMODE) */
                        /*
                         * We never respond to a WILL TM, and
-                        * we leave the state OPT_NO.
+                        * we leave the state WONT.
                         */
                        return;
 
                         */
                        return;
 
@@ -506,6 +554,9 @@ willoption(option)
                case TELOPT_SGA:
                case TELOPT_NAWS:
                case TELOPT_TSPEED:
                case TELOPT_SGA:
                case TELOPT_NAWS:
                case TELOPT_TSPEED:
+               case TELOPT_XDISPLOC:
+               case TELOPT_NEW_ENVIRON:
+               case TELOPT_OLD_ENVIRON:
                        changeok++;
                        break;
 
                        changeok++;
                        break;
 
@@ -517,56 +568,133 @@ willoption(option)
                         */
                        lmodetype = REAL_LINEMODE;
 # endif        /* KLUDGELINEMODE */
                         */
                        lmodetype = REAL_LINEMODE;
 # endif        /* KLUDGELINEMODE */
-                       clientstat(TELOPT_LINEMODE, WILL, 0);
+                       func = doclientstat;
                        changeok++;
                        break;
 #endif /* LINEMODE */
 
                        changeok++;
                        break;
 #endif /* LINEMODE */
 
+#ifdef AUTHENTICATION
+               case TELOPT_AUTHENTICATION:
+                       func = auth_request;
+                       changeok++;
+                       break;
+#endif
+
+#ifdef ENCRYPTION
+               case TELOPT_ENCRYPT:
+                       func = encrypt_send_support;
+                       changeok++;
+                       break;
+#endif /* ENCRYPTION */
+
                default:
                        break;
                }
                if (changeok) {
                default:
                        break;
                }
                if (changeok) {
-                       hiswants[option] = OPT_YES;
+                       set_his_want_state_will(option);
                        send_do(option, 0);
                } else {
                        do_dont_resp[option]++;
                        send_dont(option, 0);
                }
                        send_do(option, 0);
                } else {
                        do_dont_resp[option]++;
                        send_dont(option, 0);
                }
+           } else {
+               /*
+                * Option processing that should happen when
+                * we receive conformation of a change in
+                * state that we had requested.
+                */
+               switch (option) {
+               case TELOPT_ECHO:
+                       not42 = 0;      /* looks like a 4.2 system */
+                       /*
+                        * Egads, he responded "WILL ECHO".  Turn
+                        * it off right now!
+                        */
+                       send_dont(option, 1);
+                       /*
+                        * "WILL ECHO".  Kludge upon kludge!
+                        * A 4.2 client is now echoing user input at
+                        * the tty.  This is probably undesireable and
+                        * it should be stopped.  The client will
+                        * respond WONT TM to the DO TM that we send to
+                        * check for kludge linemode.  When the WONT TM
+                        * arrives, linemode will be turned off and a
+                        * change propogated to the pty.  This change
+                        * will cause us to process the new pty state
+                        * in localstat(), which will notice that
+                        * linemode is off and send a WILL ECHO
+                        * so that we are properly in character mode and
+                        * all is well.
+                        */
+                       break;
+#ifdef LINEMODE
+               case TELOPT_LINEMODE:
+# ifdef        KLUDGELINEMODE
+                       /*
+                        * Note client's desire to use linemode.
+                        */
+                       lmodetype = REAL_LINEMODE;
+# endif        /* KLUDGELINEMODE */
+                       func = doclientstat;
+                       break;
+#endif /* LINEMODE */
+
+#ifdef AUTHENTICATION
+               case TELOPT_AUTHENTICATION:
+                       func = auth_request;
+                       break;
+#endif
+
+#ifdef ENCRYPTION
+               case TELOPT_ENCRYPT:
+                       func = encrypt_send_support;
+                       break;
+#endif /* ENCRYPTION */
+               case TELOPT_LFLOW:
+                       func = flowstat;
+                       break;
+               }
            }
        }
            }
        }
-       hisopts[option] = OPT_YES;
+       set_his_state_will(option);
+       if (func)
+               (*func)();
 }  /* end of willoption */
 
 }  /* end of willoption */
 
+       void
 send_dont(option, init)
        int option, init;
 {
        if (init) {
 send_dont(option, init)
        int option, init;
 {
        if (init) {
-               if ((do_dont_resp[option] == 0 && hisopts[option] == OPT_NO) ||
-                   hiswants[option] == OPT_NO)
+               if ((do_dont_resp[option] == 0 && his_state_is_wont(option)) ||
+                   his_want_state_is_wont(option))
                        return;
                        return;
-               hiswants[option] = OPT_NO;
+               set_his_want_state_wont(option);
                do_dont_resp[option]++;
        }
                do_dont_resp[option]++;
        }
-       (void) sprintf(nfrontp, dont, option);
+       (void) sprintf(nfrontp, (char *)dont, option);
        nfrontp += sizeof (doopt) - 2;
        nfrontp += sizeof (doopt) - 2;
+
+       DIAG(TD_OPTIONS, printoption("td: send dont", option));
 }
 
 }
 
+       void
 wontoption(option)
        int option;
 {
 wontoption(option)
        int option;
 {
-       char *fmt = (char *)0;
-
        /*
         * Process client input.
         */
 
        /*
         * Process client input.
         */
 
+       DIAG(TD_OPTIONS, printoption("td: recv wont", option));
+
        if (do_dont_resp[option]) {
                do_dont_resp[option]--;
        if (do_dont_resp[option]) {
                do_dont_resp[option]--;
-               if (do_dont_resp[option] && hisopts[option] == OPT_NO)
+               if (do_dont_resp[option] && his_state_is_wont(option))
                        do_dont_resp[option]--;
        }
        if (do_dont_resp[option] == 0) {
                        do_dont_resp[option]--;
        }
        if (do_dont_resp[option] == 0) {
-           if (hiswants[option] != OPT_NO) {
+           if (his_want_state_is_will(option)) {
                /* it is always ok to change to negative state */
                switch (option) {
                case TELOPT_ECHO:
                /* it is always ok to change to negative state */
                switch (option) {
                case TELOPT_ECHO:
@@ -586,11 +714,13 @@ wontoption(option)
                         * If real linemode is supported, then client is
                         * asking to turn linemode off.
                         */
                         * If real linemode is supported, then client is
                         * asking to turn linemode off.
                         */
-                       if (lmodetype == REAL_LINEMODE)
+                       if (lmodetype != REAL_LINEMODE)
+                               break;
+                       lmodetype = KLUDGE_LINEMODE;
 # endif        /* KLUDGELINEMODE */
 # endif        /* KLUDGELINEMODE */
-                               clientstat(TELOPT_LINEMODE, WONT, 0);
+                       clientstat(TELOPT_LINEMODE, WONT, 0);
                        break;
                        break;
-#endif LINEMODE
+#endif /* LINEMODE */
 
                case TELOPT_TM:
                        /*
 
                case TELOPT_TM:
                        /*
@@ -599,7 +729,7 @@ wontoption(option)
                         * as is.  Short circut the state machine to
                         * achive this.
                         */
                         * as is.  Short circut the state machine to
                         * achive this.
                         */
-                       hiswants[TELOPT_TM] = OPT_NO;
+                       set_his_want_state_wont(TELOPT_TM);
                        return;
 
                case TELOPT_LFLOW:
                        return;
 
                case TELOPT_LFLOW:
@@ -614,46 +744,102 @@ wontoption(option)
                        slctab[SLC_XOFF].defset.flag |= SLC_CANTCHANGE;
                        break;
 
                        slctab[SLC_XOFF].defset.flag |= SLC_CANTCHANGE;
                        break;
 
+#if    defined(AUTHENTICATION)
+               case TELOPT_AUTHENTICATION:
+                       auth_finished(0, AUTH_REJECT);
+                       break;
+#endif
+
+               /*
+                * For options that we might spin waiting for
+                * sub-negotiation, if the client turns off the
+                * option rather than responding to the request,
+                * we have to treat it here as if we got a response
+                * to the sub-negotiation, (by updating the timers)
+                * so that we'll break out of the loop.
+                */
+               case TELOPT_TTYPE:
+                       settimer(ttypesubopt);
+                       break;
+
+               case TELOPT_TSPEED:
+                       settimer(tspeedsubopt);
+                       break;
+
+               case TELOPT_XDISPLOC:
+                       settimer(xdisplocsubopt);
+                       break;
+
+               case TELOPT_OLD_ENVIRON:
+                       settimer(oenvironsubopt);
+                       break;
+
+               case TELOPT_NEW_ENVIRON:
+                       settimer(environsubopt);
+                       break;
+
                default:
                        break;
                }
                default:
                        break;
                }
-               hiswants[option] = OPT_NO;
-               fmt = dont;
-               send_dont(option, 0);
+               set_his_want_state_wont(option);
+               if (his_state_is_will(option))
+                       send_dont(option, 0);
            } else {
                switch (option) {
                case TELOPT_TM:
 #if    defined(LINEMODE) && defined(KLUDGELINEMODE)
            } else {
                switch (option) {
                case TELOPT_TM:
 #if    defined(LINEMODE) && defined(KLUDGELINEMODE)
-                       if (lmodetype < REAL_LINEMODE) {
+                       if (lmodetype < NO_AUTOKLUDGE) {
                                lmodetype = NO_LINEMODE;
                                clientstat(TELOPT_LINEMODE, WONT, 0);
                                send_will(TELOPT_SGA, 1);
                                lmodetype = NO_LINEMODE;
                                clientstat(TELOPT_LINEMODE, WONT, 0);
                                send_will(TELOPT_SGA, 1);
-/*@*/                          send_will(TELOPT_ECHO, 1);
+                               send_will(TELOPT_ECHO, 1);
                        }
 #endif /* defined(LINEMODE) && defined(KLUDGELINEMODE) */
                        }
 #endif /* defined(LINEMODE) && defined(KLUDGELINEMODE) */
+                       break;
+
+#if    defined(AUTHENTICATION)
+               case TELOPT_AUTHENTICATION:
+                       auth_finished(0, AUTH_REJECT);
+                       break;
+#endif
                default:
                        break;
                }
            }
        }
                default:
                        break;
                }
            }
        }
-       hisopts[option] = OPT_NO;
+       set_his_state_wont(option);
 
 }  /* end of wontoption */
 
 
 }  /* end of wontoption */
 
+       void
 send_will(option, init)
        int option, init;
 {
        if (init) {
 send_will(option, init)
        int option, init;
 {
        if (init) {
-               if ((will_wont_resp[option] == 0 && myopts[option] == OPT_YES)||
-                   mywants[option] == OPT_YES)
+               if ((will_wont_resp[option] == 0 && my_state_is_will(option))||
+                   my_want_state_is_will(option))
                        return;
                        return;
-               mywants[option] = OPT_YES;
+               set_my_want_state_will(option);
                will_wont_resp[option]++;
        }
                will_wont_resp[option]++;
        }
-       (void) sprintf(nfrontp, will, option);
+       (void) sprintf(nfrontp, (char *)will, option);
        nfrontp += sizeof (doopt) - 2;
        nfrontp += sizeof (doopt) - 2;
+
+       DIAG(TD_OPTIONS, printoption("td: send will", option));
 }
 
 }
 
+#if    !defined(LINEMODE) || !defined(KLUDGELINEMODE)
+/*
+ * When we get a DONT SGA, we will try once to turn it
+ * back on.  If the other side responds DONT SGA, we
+ * leave it at that.  This is so that when we talk to
+ * clients that understand KLUDGELINEMODE but not LINEMODE,
+ * we'll keep them in char-at-a-time mode.
+ */
+int turn_on_sga = 0;
+#endif
+
+       void
 dooption(option)
        int option;
 {
 dooption(option)
        int option;
 {
@@ -663,23 +849,28 @@ dooption(option)
         * Process client input.
         */
 
         * Process client input.
         */
 
+       DIAG(TD_OPTIONS, printoption("td: recv do", option));
+
        if (will_wont_resp[option]) {
                will_wont_resp[option]--;
        if (will_wont_resp[option]) {
                will_wont_resp[option]--;
-               if (will_wont_resp[option] && myopts[option] == OPT_YES)
+               if (will_wont_resp[option] && my_state_is_will(option))
                        will_wont_resp[option]--;
        }
                        will_wont_resp[option]--;
        }
-       if ((will_wont_resp[option] == 0) && (mywants[option] != OPT_YES)) {
+       if ((will_wont_resp[option] == 0) && (my_want_state_is_wont(option))) {
                switch (option) {
                case TELOPT_ECHO:
 #ifdef LINEMODE
                switch (option) {
                case TELOPT_ECHO:
 #ifdef LINEMODE
-                       if (lmodetype == NO_LINEMODE) {
+# ifdef        KLUDGELINEMODE
+                       if (lmodetype == NO_LINEMODE)
+# else
+                       if (his_state_is_wont(TELOPT_LINEMODE))
+# endif
 #endif
 #endif
+                       {
                                init_termbuf();
                                tty_setecho(1);
                                set_termbuf();
                                init_termbuf();
                                tty_setecho(1);
                                set_termbuf();
-#ifdef LINEMODE
                        }
                        }
-#endif
                        changeok++;
                        break;
 
                        changeok++;
                        break;
 
@@ -714,6 +905,8 @@ dooption(option)
                                if (linemode)
                                        break;
                        }
                                if (linemode)
                                        break;
                        }
+#else
+                       turn_on_sga = 0;
 #endif /* defined(LINEMODE) && defined(KLUDGELINEMODE) */
                        changeok++;
                        break;
 #endif /* defined(LINEMODE) && defined(KLUDGELINEMODE) */
                        changeok++;
                        break;
@@ -728,56 +921,89 @@ dooption(option)
                         * pretend we sent a WONT.
                         */
                        send_will(option, 0);
                         * pretend we sent a WONT.
                         */
                        send_will(option, 0);
-                       mywants[option] = OPT_NO;
-                       myopts[option] = OPT_NO;
+                       set_my_want_state_wont(option);
+                       set_my_state_wont(option);
                        return;
 
                        return;
 
+               case TELOPT_LOGOUT:
+                       /*
+                        * When we get a LOGOUT option, respond
+                        * with a WILL LOGOUT, make sure that
+                        * it gets written out to the network,
+                        * and then just go away...
+                        */
+                       set_my_want_state_will(TELOPT_LOGOUT);
+                       send_will(TELOPT_LOGOUT, 0);
+                       set_my_state_will(TELOPT_LOGOUT);
+                       (void)netflush();
+                       cleanup(0);
+                       /* NOT REACHED */
+                       break;
+
+#ifdef ENCRYPTION
+               case TELOPT_ENCRYPT:
+                       changeok++;
+                       break;
+#endif /* ENCRYPTION */
                case TELOPT_LINEMODE:
                case TELOPT_TTYPE:
                case TELOPT_NAWS:
                case TELOPT_TSPEED:
                case TELOPT_LFLOW:
                case TELOPT_LINEMODE:
                case TELOPT_TTYPE:
                case TELOPT_NAWS:
                case TELOPT_TSPEED:
                case TELOPT_LFLOW:
+               case TELOPT_XDISPLOC:
+#ifdef TELOPT_ENVIRON
+               case TELOPT_NEW_ENVIRON:
+#endif
+               case TELOPT_OLD_ENVIRON:
                default:
                        break;
                }
                if (changeok) {
                default:
                        break;
                }
                if (changeok) {
-                       mywants[option] = OPT_YES;
+                       set_my_want_state_will(option);
                        send_will(option, 0);
                } else {
                        will_wont_resp[option]++;
                        send_wont(option, 0);
                }
        }
                        send_will(option, 0);
                } else {
                        will_wont_resp[option]++;
                        send_wont(option, 0);
                }
        }
-       myopts[option] = OPT_YES;
+       set_my_state_will(option);
 
 }  /* end of dooption */
 
 
 }  /* end of dooption */
 
+       void
 send_wont(option, init)
        int option, init;
 {
        if (init) {
 send_wont(option, init)
        int option, init;
 {
        if (init) {
-               if ((will_wont_resp[option] == 0 && myopts[option] == OPT_NO) ||
-                   mywants[option] == OPT_NO)
+               if ((will_wont_resp[option] == 0 && my_state_is_wont(option)) ||
+                   my_want_state_is_wont(option))
                        return;
                        return;
-               mywants[option] = OPT_NO;
+               set_my_want_state_wont(option);
                will_wont_resp[option]++;
        }
                will_wont_resp[option]++;
        }
-       (void) sprintf(nfrontp, wont, option);
+       (void) sprintf(nfrontp, (char *)wont, option);
        nfrontp += sizeof (wont) - 2;
        nfrontp += sizeof (wont) - 2;
+
+       DIAG(TD_OPTIONS, printoption("td: send wont", option));
 }
 
 }
 
+       void
 dontoption(option)
        int option;
 {
        /*
         * Process client input.
         */
 dontoption(option)
        int option;
 {
        /*
         * Process client input.
         */
+
+
+       DIAG(TD_OPTIONS, printoption("td: recv dont", option));
+
        if (will_wont_resp[option]) {
                will_wont_resp[option]--;
        if (will_wont_resp[option]) {
                will_wont_resp[option]--;
-               if (will_wont_resp[option] && myopts[option] == OPT_NO)
+               if (will_wont_resp[option] && my_state_is_wont(option))
                        will_wont_resp[option]--;
        }
                        will_wont_resp[option]--;
        }
-       if ((will_wont_resp[option] == 0) && (mywants[option] != OPT_NO)) {
+       if ((will_wont_resp[option] == 0) && (my_want_state_is_will(option))) {
                switch (option) {
                case TELOPT_BINARY:
                        init_termbuf();
                switch (option) {
                case TELOPT_BINARY:
                        init_termbuf();
@@ -787,14 +1013,18 @@ dontoption(option)
 
                case TELOPT_ECHO:       /* we should stop echoing */
 #ifdef LINEMODE
 
                case TELOPT_ECHO:       /* we should stop echoing */
 #ifdef LINEMODE
-                       if (lmodetype == NO_LINEMODE) {
+# ifdef        KLUDGELINEMODE
+                       if ((lmodetype != REAL_LINEMODE) &&
+                           (lmodetype != KLUDGE_LINEMODE))
+# else
+                       if (his_state_is_wont(TELOPT_LINEMODE))
+# endif
 #endif
 #endif
+                       {
                                init_termbuf();
                                tty_setecho(0);
                                set_termbuf();
                                init_termbuf();
                                tty_setecho(0);
                                set_termbuf();
-#ifdef LINEMODE
                        }
                        }
-#endif
                        break;
 
                case TELOPT_SGA:
                        break;
 
                case TELOPT_SGA:
@@ -804,11 +1034,13 @@ dontoption(option)
                         * must process an incoming do SGA for
                         * linemode purposes.
                         */
                         * must process an incoming do SGA for
                         * linemode purposes.
                         */
-                       if (lmodetype == KLUDGE_LINEMODE) {
+                       if ((lmodetype == KLUDGE_LINEMODE) ||
+                           (lmodetype == KLUDGE_OK)) {
                                /*
                                 * The client is asking us to turn
                                 * linemode on.
                                 */
                                /*
                                 * The client is asking us to turn
                                 * linemode on.
                                 */
+                               lmodetype = KLUDGE_LINEMODE;
                                clientstat(TELOPT_LINEMODE, WILL, 0);
                                /*
                                 * If we did not turn line mode on,
                                clientstat(TELOPT_LINEMODE, WILL, 0);
                                /*
                                 * If we did not turn line mode on,
@@ -817,19 +1049,37 @@ dontoption(option)
                                 * Gross.  Very Gross.
                                 */
                        }
                                 * Gross.  Very Gross.
                                 */
                        }
+                       break;
+#else
+                       set_my_want_state_wont(option);
+                       if (my_state_is_will(option))
+                               send_wont(option, 0);
+                       set_my_state_wont(option);
+                       if (turn_on_sga ^= 1)
+                               send_will(option, 1);
+                       return;
 #endif /* defined(LINEMODE) && defined(KLUDGELINEMODE) */
 
                default:
                        break;
                }
 
 #endif /* defined(LINEMODE) && defined(KLUDGELINEMODE) */
 
                default:
                        break;
                }
 
-               mywants[option] = OPT_NO;
-               send_wont(option, 0);
+               set_my_want_state_wont(option);
+               if (my_state_is_will(option))
+                       send_wont(option, 0);
        }
        }
-       myopts[option] = OPT_NO;
+       set_my_state_wont(option);
 
 }  /* end of dontoption */
 
 
 }  /* end of dontoption */
 
+#ifdef ENV_HACK
+int env_ovar = -1;
+int env_ovalue = -1;
+#else  /* ENV_HACK */
+# define env_ovar OLD_ENV_VAR
+# define env_ovalue OLD_ENV_VALUE
+#endif /* ENV_HACK */
+
 /*
  * suboption()
  *
 /*
  * suboption()
  *
@@ -843,16 +1093,19 @@ dontoption(option)
  *     Window size
  *     Terminal speed
  */
  *     Window size
  *     Terminal speed
  */
+       void
 suboption()
 {
     register int subchar;
 
 suboption()
 {
     register int subchar;
 
+    DIAG(TD_OPTIONS, {netflush(); printsub('<', subpointer, SB_LEN()+2);});
+
     subchar = SB_GET();
     switch (subchar) {
     case TELOPT_TSPEED: {
        register int xspeed, rspeed;
 
     subchar = SB_GET();
     switch (subchar) {
     case TELOPT_TSPEED: {
        register int xspeed, rspeed;
 
-       if (hisopts[TELOPT_TSPEED] == OPT_NO)   /* Ignore if option disabled */
+       if (his_state_is_wont(TELOPT_TSPEED))   /* Ignore if option disabled */
                break;
 
        settimer(tspeedsubopt);
                break;
 
        settimer(tspeedsubopt);
@@ -860,13 +1113,13 @@ suboption()
        if (SB_EOF() || SB_GET() != TELQUAL_IS)
                return;
 
        if (SB_EOF() || SB_GET() != TELQUAL_IS)
                return;
 
-       xspeed = atoi(subpointer);
+       xspeed = atoi((char *)subpointer);
 
        while (SB_GET() != ',' && !SB_EOF());
        if (SB_EOF())
                return;
 
 
        while (SB_GET() != ',' && !SB_EOF());
        if (SB_EOF())
                return;
 
-       rspeed = atoi(subpointer);
+       rspeed = atoi((char *)subpointer);
        clientstat(TELOPT_TSPEED, xspeed, rspeed);
 
        break;
        clientstat(TELOPT_TSPEED, xspeed, rspeed);
 
        break;
@@ -874,17 +1127,17 @@ suboption()
     }  /* end of case TELOPT_TSPEED */
 
     case TELOPT_TTYPE: {               /* Yaaaay! */
     }  /* end of case TELOPT_TSPEED */
 
     case TELOPT_TTYPE: {               /* Yaaaay! */
-       static char terminalname[5+41] = "TERM=";
+       static char terminalname[41];
 
 
-       if (hisopts[TELOPT_TTYPE] == OPT_NO)    /* Ignore if option disabled */
+       if (his_state_is_wont(TELOPT_TTYPE))    /* Ignore if option disabled */
                break;
        settimer(ttypesubopt);
 
                break;
        settimer(ttypesubopt);
 
-       if (SB_GET() != TELQUAL_IS) {
+       if (SB_EOF() || SB_GET() != TELQUAL_IS) {
            return;             /* ??? XXX but, this is the most robust */
        }
 
            return;             /* ??? XXX but, this is the most robust */
        }
 
-       terminaltype = terminalname+sizeof("TERM=")-1;
+       terminaltype = terminalname;
 
        while ((terminaltype < (terminalname + sizeof terminalname-1)) &&
                                                                    !SB_EOF()) {
 
        while ((terminaltype < (terminalname + sizeof terminalname-1)) &&
                                                                    !SB_EOF()) {
@@ -904,7 +1157,7 @@ suboption()
     case TELOPT_NAWS: {
        register int xwinsize, ywinsize;
 
     case TELOPT_NAWS: {
        register int xwinsize, ywinsize;
 
-       if (hisopts[TELOPT_NAWS] == OPT_NO)     /* Ignore if option disabled */
+       if (his_state_is_wont(TELOPT_NAWS))     /* Ignore if option disabled */
                break;
 
        if (SB_EOF())
                break;
 
        if (SB_EOF())
@@ -929,14 +1182,17 @@ suboption()
     case TELOPT_LINEMODE: {
        register int request;
 
     case TELOPT_LINEMODE: {
        register int request;
 
-       if (hisopts[TELOPT_LINEMODE] == OPT_NO) /* Ignore if option disabled */
+       if (his_state_is_wont(TELOPT_LINEMODE)) /* Ignore if option disabled */
                break;
        /*
         * Process linemode suboptions.
         */
                break;
        /*
         * Process linemode suboptions.
         */
-       if (SB_EOF()) break;  /* garbage was sent */
-       request = SB_GET();  /* get will/wont */
-       if (SB_EOF()) break;  /* another garbage check */
+       if (SB_EOF())
+           break;              /* garbage was sent */
+       request = SB_GET();     /* get will/wont */
+
+       if (SB_EOF())
+           break;              /* another garbage check */
 
        if (request == LM_SLC) {  /* SLC is not preceeded by WILL or WONT */
                /*
 
        if (request == LM_SLC) {  /* SLC is not preceeded by WILL or WONT */
                /*
@@ -944,13 +1200,18 @@ suboption()
                 */
                start_slc(1);
                do_opt_slc(subpointer, subend - subpointer);
                 */
                start_slc(1);
                do_opt_slc(subpointer, subend - subpointer);
-               end_slc(0);
-
+               (void) end_slc(0);
+               break;
        } else if (request == LM_MODE) {
        } else if (request == LM_MODE) {
+               if (SB_EOF())
+                   return;
                useeditmode = SB_GET();  /* get mode flag */
                clientstat(LM_MODE, 0, 0);
                useeditmode = SB_GET();  /* get mode flag */
                clientstat(LM_MODE, 0, 0);
+               break;
        }
 
        }
 
+       if (SB_EOF())
+           break;
        switch (SB_GET()) {  /* what suboption? */
        case LM_FORWARDMASK:
                /*
        switch (SB_GET()) {  /* what suboption? */
        case LM_FORWARDMASK:
                /*
@@ -962,16 +1223,18 @@ suboption()
        default:
                break;
        }
        default:
                break;
        }
-
+       break;
     }  /* end of case TELOPT_LINEMODE */
 #endif
     case TELOPT_STATUS: {
        int mode;
 
     }  /* end of case TELOPT_LINEMODE */
 #endif
     case TELOPT_STATUS: {
        int mode;
 
+       if (SB_EOF())
+           break;
        mode = SB_GET();
        switch (mode) {
        case TELQUAL_SEND:
        mode = SB_GET();
        switch (mode) {
        case TELQUAL_SEND:
-           if (myopts[TELOPT_STATUS] == OPT_YES)
+           if (my_state_is_will(TELOPT_STATUS))
                send_status();
            break;
 
                send_status();
            break;
 
@@ -981,7 +1244,269 @@ suboption()
        default:
            break;
        }
        default:
            break;
        }
-    }
+       break;
+    }  /* end of case TELOPT_STATUS */
+
+    case TELOPT_XDISPLOC: {
+       if (SB_EOF() || SB_GET() != TELQUAL_IS)
+               return;
+       settimer(xdisplocsubopt);
+       subpointer[SB_LEN()] = '\0';
+       (void)setenv("DISPLAY", (char *)subpointer, 1);
+       break;
+    }  /* end of case TELOPT_XDISPLOC */
+
+#ifdef TELOPT_NEW_ENVIRON
+    case TELOPT_NEW_ENVIRON:
+#endif
+    case TELOPT_OLD_ENVIRON: {
+       register int c;
+       register char *cp, *varp, *valp;
+
+       if (SB_EOF())
+               return;
+       c = SB_GET();
+       if (c == TELQUAL_IS) {
+               if (subchar == TELOPT_OLD_ENVIRON)
+                       settimer(oenvironsubopt);
+               else
+                       settimer(environsubopt);
+       } else if (c != TELQUAL_INFO) {
+               return;
+       }
+
+#ifdef TELOPT_NEW_ENVIRON
+       if (subchar == TELOPT_NEW_ENVIRON) {
+           while (!SB_EOF()) {
+               c = SB_GET();
+               if ((c == NEW_ENV_VAR) || (c == ENV_USERVAR))
+                       break;
+           }
+       } else
+#endif
+       {
+#ifdef ENV_HACK
+           /*
+            * We only want to do this if we haven't already decided
+            * whether or not the other side has its VALUE and VAR
+            * reversed.
+            */
+           if (env_ovar < 0) {
+               register int last = -1;         /* invalid value */
+               int empty = 0;
+               int got_var = 0, got_value = 0, got_uservar = 0;
+
+               /*
+                * The other side might have its VALUE and VAR values
+                * reversed.  To be interoperable, we need to determine
+                * which way it is.  If the first recognized character
+                * is a VAR or VALUE, then that will tell us what
+                * type of client it is.  If the fist recognized
+                * character is a USERVAR, then we continue scanning
+                * the suboption looking for two consecutive
+                * VAR or VALUE fields.  We should not get two
+                * consecutive VALUE fields, so finding two
+                * consecutive VALUE or VAR fields will tell us
+                * what the client is.
+                */
+               SB_SAVE();
+               while (!SB_EOF()) {
+                       c = SB_GET();
+                       switch(c) {
+                       case OLD_ENV_VAR:
+                               if (last < 0 || last == OLD_ENV_VAR
+                                   || (empty && (last == OLD_ENV_VALUE)))
+                                       goto env_ovar_ok;
+                               got_var++;
+                               last = OLD_ENV_VAR;
+                               break;
+                       case OLD_ENV_VALUE:
+                               if (last < 0 || last == OLD_ENV_VALUE
+                                   || (empty && (last == OLD_ENV_VAR)))
+                                       goto env_ovar_wrong;
+                               got_value++;
+                               last = OLD_ENV_VALUE;
+                               break;
+                       case ENV_USERVAR:
+                               /* count strings of USERVAR as one */
+                               if (last != ENV_USERVAR)
+                                       got_uservar++;
+                               if (empty) {
+                                       if (last == OLD_ENV_VALUE)
+                                               goto env_ovar_ok;
+                                       if (last == OLD_ENV_VAR)
+                                               goto env_ovar_wrong;
+                               }
+                               last = ENV_USERVAR;
+                               break;
+                       case ENV_ESC:
+                               if (!SB_EOF())
+                                       c = SB_GET();
+                               /* FALL THROUGH */
+                       default:
+                               empty = 0;
+                               continue;
+                       }
+                       empty = 1;
+               }
+               if (empty) {
+                       if (last == OLD_ENV_VALUE)
+                               goto env_ovar_ok;
+                       if (last == OLD_ENV_VAR)
+                               goto env_ovar_wrong;
+               }
+               /*
+                * Ok, the first thing was a USERVAR, and there
+                * are not two consecutive VAR or VALUE commands,
+                * and none of the VAR or VALUE commands are empty.
+                * If the client has sent us a well-formed option,
+                * then the number of VALUEs received should always
+                * be less than or equal to the number of VARs and
+                * USERVARs received.
+                *
+                * If we got exactly as many VALUEs as VARs and
+                * USERVARs, the client has the same definitions.
+                *
+                * If we got exactly as many VARs as VALUEs and
+                * USERVARS, the client has reversed definitions.
+                */
+               if (got_uservar + got_var == got_value) {
+           env_ovar_ok:
+                       env_ovar = OLD_ENV_VAR;
+                       env_ovalue = OLD_ENV_VALUE;
+               } else if (got_uservar + got_value == got_var) {
+           env_ovar_wrong:
+                       env_ovar = OLD_ENV_VALUE;
+                       env_ovalue = OLD_ENV_VAR;
+                       DIAG(TD_OPTIONS, {sprintf(nfrontp,
+                               "ENVIRON VALUE and VAR are reversed!\r\n");
+                               nfrontp += strlen(nfrontp);});
+
+               }
+           }
+           SB_RESTORE();
+#endif
+
+           while (!SB_EOF()) {
+               c = SB_GET();
+               if ((c == env_ovar) || (c == ENV_USERVAR))
+                       break;
+           }
+       }
+
+       if (SB_EOF())
+               return;
+
+       cp = varp = (char *)subpointer;
+       valp = 0;
+
+       while (!SB_EOF()) {
+               c = SB_GET();
+               if (subchar == TELOPT_OLD_ENVIRON) {
+                       if (c == env_ovar)
+                               c = NEW_ENV_VAR;
+                       else if (c == env_ovalue)
+                               c = NEW_ENV_VALUE;
+               }
+               switch (c) {
+
+               case NEW_ENV_VALUE:
+                       *cp = '\0';
+                       cp = valp = (char *)subpointer;
+                       break;
+
+               case NEW_ENV_VAR:
+               case ENV_USERVAR:
+                       *cp = '\0';
+                       if (valp)
+                               (void)setenv(varp, valp, 1);
+                       else
+                               unsetenv(varp);
+                       cp = varp = (char *)subpointer;
+                       valp = 0;
+                       break;
+
+               case ENV_ESC:
+                       if (SB_EOF())
+                               break;
+                       c = SB_GET();
+                       /* FALL THROUGH */
+               default:
+                       *cp++ = c;
+                       break;
+               }
+       }
+       *cp = '\0';
+       if (valp)
+               (void)setenv(varp, valp, 1);
+       else
+               unsetenv(varp);
+       break;
+    }  /* end of case TELOPT_NEW_ENVIRON */
+#if    defined(AUTHENTICATION)
+    case TELOPT_AUTHENTICATION:
+       if (SB_EOF())
+               break;
+       switch(SB_GET()) {
+       case TELQUAL_SEND:
+       case TELQUAL_REPLY:
+               /*
+                * These are sent by us and cannot be sent by
+                * the client.
+                */
+               break;
+       case TELQUAL_IS:
+               auth_is(subpointer, SB_LEN());
+               break;
+       case TELQUAL_NAME:
+               auth_name(subpointer, SB_LEN());
+               break;
+       }
+       break;
+#endif
+#ifdef ENCRYPTION
+    case TELOPT_ENCRYPT:
+       if (SB_EOF())
+               break;
+       switch(SB_GET()) {
+       case ENCRYPT_SUPPORT:
+               encrypt_support(subpointer, SB_LEN());
+               break;
+       case ENCRYPT_IS:
+               encrypt_is(subpointer, SB_LEN());
+               break;
+       case ENCRYPT_REPLY:
+               encrypt_reply(subpointer, SB_LEN());
+               break;
+       case ENCRYPT_START:
+               encrypt_start(subpointer, SB_LEN());
+               break;
+       case ENCRYPT_END:
+               encrypt_end();
+               break;
+       case ENCRYPT_REQSTART:
+               encrypt_request_start(subpointer, SB_LEN());
+               break;
+       case ENCRYPT_REQEND:
+               /*
+                * We can always send an REQEND so that we cannot
+                * get stuck encrypting.  We should only get this
+                * if we have been able to get in the correct mode
+                * anyhow.
+                */
+               encrypt_request_end();
+               break;
+       case ENCRYPT_ENC_KEYID:
+               encrypt_enc_keyid(subpointer, SB_LEN());
+               break;
+       case ENCRYPT_DEC_KEYID:
+               encrypt_dec_keyid(subpointer, SB_LEN());
+               break;
+       default:
+               break;
+       }
+       break;
+#endif /* ENCRYPTION */
 
     default:
        break;
 
     default:
        break;
@@ -989,13 +1514,20 @@ suboption()
 
 }  /* end of suboption */
 
 
 }  /* end of suboption */
 
+       void
+doclientstat()
+{
+       clientstat(TELOPT_LINEMODE, WILL, 0);
+}
+
 #define        ADD(c)   *ncp++ = c;
 #define        ADD_DATA(c) { *ncp++ = c; if (c == SE) *ncp++ = c; }
 #define        ADD(c)   *ncp++ = c;
 #define        ADD_DATA(c) { *ncp++ = c; if (c == SE) *ncp++ = c; }
+       void
 send_status()
 {
 send_status()
 {
-       char statusbuf[256];
-       register char *ncp;
-       register int i;
+       unsigned char statusbuf[256];
+       register unsigned char *ncp;
+       register unsigned char i;
 
        ncp = statusbuf;
 
 
        ncp = statusbuf;
 
@@ -1006,14 +1538,22 @@ send_status()
        ADD(TELOPT_STATUS);
        ADD(TELQUAL_IS);
 
        ADD(TELOPT_STATUS);
        ADD(TELQUAL_IS);
 
-       for (i = 0; i < NTELOPTS; i++) {
-               if (myopts[i] == OPT_YES) {
+       /*
+        * We check the want_state rather than the current state,
+        * because if we received a DO/WILL for an option that we
+        * don't support, and the other side didn't send a DONT/WONT
+        * in response to our WONT/DONT, then the "state" will be
+        * WILL/DO, and the "want_state" will be WONT/DONT.  We
+        * need to go by the latter.
+        */
+       for (i = 0; i < (unsigned char)NTELOPTS; i++) {
+               if (my_want_state_is_will(i)) {
                        ADD(WILL);
                        ADD_DATA(i);
                        if (i == IAC)
                                ADD(IAC);
                }
                        ADD(WILL);
                        ADD_DATA(i);
                        if (i == IAC)
                                ADD(IAC);
                }
-               if (hisopts[i] == OPT_YES) {
+               if (his_want_state_is_will(i)) {
                        ADD(DO);
                        ADD_DATA(i);
                        if (i == IAC)
                        ADD(DO);
                        ADD_DATA(i);
                        if (i == IAC)
@@ -1021,9 +1561,32 @@ send_status()
                }
        }
 
                }
        }
 
+       if (his_want_state_is_will(TELOPT_LFLOW)) {
+               ADD(SB);
+               ADD(TELOPT_LFLOW);
+               if (flowmode) {
+                       ADD(LFLOW_ON);
+               } else {
+                       ADD(LFLOW_OFF);
+               }
+               ADD(SE);
+
+               if (restartany >= 0) {
+                       ADD(SB)
+                       ADD(TELOPT_LFLOW);
+                       if (restartany) {
+                               ADD(LFLOW_RESTART_ANY);
+                       } else {
+                               ADD(LFLOW_RESTART_XON);
+                       }
+                       ADD(SE)
+                       ADD(SB);
+               }
+       }
+
 #ifdef LINEMODE
 #ifdef LINEMODE
-       if (hisopts[TELOPT_LINEMODE] == OPT_YES) {
-               char *cp, *cpe;
+       if (his_want_state_is_will(TELOPT_LINEMODE)) {
+               unsigned char *cp, *cpe;
                int len;
 
                ADD(SB);
                int len;
 
                ADD(SB);
@@ -1051,4 +1614,7 @@ send_status()
 
        writenet(statusbuf, ncp - statusbuf);
        netflush();     /* Send it on its way */
 
        writenet(statusbuf, ncp - statusbuf);
        netflush();     /* Send it on its way */
+
+       DIAG(TD_OPTIONS,
+               {printsub('>', statusbuf, ncp - statusbuf); netflush();});
 }
 }