openenvutils/telnetserver/src/state.c
changeset 0 2e3d3ce01487
child 4 0fdb7f6b0309
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/openenvutils/telnetserver/src/state.c	Tue Feb 02 10:12:00 2010 +0200
@@ -0,0 +1,1369 @@
+// state.c
+//
+// © Portions Copyright (c) Symbian Software Ltd 2007. 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, 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. 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.
+ */
+
+#include "telnetd.h"
+#ifdef __SYMBIAN32__
+#ifdef __ARMCC__
+// Turn off the 'extended constant initialiser used' warning
+#pragma diag_suppress 177
+#endif//__ARMCC__
+#endif//__SYMBIAN32__
+
+#ifndef __SYMBIAN32__
+__RCSID("$Heimdal: state.c,v 1.14.12.1 2004/06/21 08:21:58 lha Exp $"
+        "$NetBSD: state.c,v 1.3 2004/09/14 08:08:20 lha Exp $");
+#endif
+
+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
+ */
+unsigned char subbuffer[1024*64], *subpointer= subbuffer, *subend= 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_LEN()	(subend - subpointer)
+
+#ifdef	ENV_HACK
+unsigned char *subsave;
+#define SB_SAVE()	subsave = subpointer;
+#define	SB_RESTORE()	subpointer = subsave;
+#endif
+
+
+/*
+ * State for recv fsm
+ */
+#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_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_DONT		8	/* dont -''- */
+
+void
+telrcv(void)
+{
+    int c;
+    static int state = TS_DATA;
+
+    while (ncc > 0) {
+	if ((&ptyobuf[BUFSIZ] - pfrontp) < 2)
+	    break;
+	c = *netip++ & 0377, ncc--;
+#ifdef ENCRYPTION
+	if (decrypt_input)
+	    c = (*decrypt_input)(c);
+#endif
+	switch (state) {
+
+	case TS_CR:
+	    state = TS_DATA;
+	    /* Strip off \n or \0 after a \r */
+	    if ((c == 0) || (c == '\n')) {
+		break;
+	    }
+	    /* FALL THROUGH */
+
+	case TS_DATA:
+	    if (c == IAC) {
+		state = TS_IAC;
+		break;
+	    }
+	    /*
+	     * We now map \r\n ==> \r for pragmatic reasons.
+	     * Many client implementations send \r\n when
+	     * the user hits the CarriageReturn key.
+	     *
+	     * We USED to 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 ((c == '\r') && his_state_is_wont(TELOPT_BINARY)) {
+		int nc = *netip;
+#ifdef ENCRYPTION
+		if (decrypt_input)
+		    nc = (*decrypt_input)(nc & 0xff);
+#endif
+		{
+#ifdef ENCRYPTION
+		    if (decrypt_input)
+			(void)(*decrypt_input)(-1);
+#endif
+		    state = TS_CR;
+		}
+	    }
+	    *pfrontp++ = c;
+	    break;
+
+	case TS_IAC:
+	gotiac:			switch (c) {
+
+	    /*
+	     * Send the process on the pty side an
+	     * interrupt.  Do this with a NULL or
+	     * interrupt char; depending on the tty mode.
+	     */
+	case IP:
+	    DIAG(TD_OPTIONS,
+		 printoption("td: recv IAC", c));
+	    interrupt();
+	    break;
+
+	case BREAK:
+	    DIAG(TD_OPTIONS,
+		 printoption("td: recv IAC", c));
+	    sendbrk();
+	    break;
+
+	    /*
+	     * Are You There?
+	     */
+	case AYT:
+	    DIAG(TD_OPTIONS,
+		 printoption("td: recv IAC", c));
+	    recv_ayt();
+	    break;
+
+	    /*
+	     * Abort Output
+	     */
+	case AO:
+	    {
+		DIAG(TD_OPTIONS,
+		     printoption("td: recv IAC", c));
+		ptyflush();	/* half-hearted */
+		init_termbuf();
+
+		if (slctab[SLC_AO].sptr &&
+		    *slctab[SLC_AO].sptr != (cc_t)(_POSIX_VDISABLE)) {
+		    *pfrontp++ =
+			(unsigned char)*slctab[SLC_AO].sptr;
+		}
+
+		netclear();	/* clear buffer back */
+		output_data ("%c%c", IAC, DM);
+		neturg = nfrontp-1; /* off by one XXX */
+		DIAG(TD_OPTIONS,
+		     printoption("td: send IAC", DM));
+		break;
+	    }
+
+	/*
+	 * Erase Character and
+	 * Erase Line
+	 */
+	case EC:
+	case EL:
+	    {
+		cc_t ch;
+
+		DIAG(TD_OPTIONS,
+		     printoption("td: recv IAC", c));
+		ptyflush();	/* half-hearted */
+		init_termbuf();
+		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;
+	    }
+
+	/*
+	 * Check for urgent data...
+	 */
+	case DM:
+	    DIAG(TD_OPTIONS,
+		 printoption("td: recv IAC", c));
+	    SYNCHing = stilloob(net);
+	    settimer(gotDM);
+	    break;
+
+
+	    /*
+	     * Begin option subnegotiation...
+	     */
+	case SB:
+	    state = TS_SB;
+	    SB_CLEAR();
+	    continue;
+
+	case WILL:
+	    state = TS_WILL;
+	    continue;
+
+	case WONT:
+	    state = TS_WONT;
+	    continue;
+
+	case DO:
+	    state = TS_DO;
+	    continue;
+
+	case DONT:
+	    state = TS_DONT;
+	    continue;
+	case EOR:
+	    if (his_state_is_will(TELOPT_EOR))
+		doeof();
+	    break;
+
+	    /*
+	     * Handle RFC 10xx Telnet linemode option additions
+	     * to command stream (EOF, SUSP, ABORT).
+	     */
+	case xEOF:
+	    doeof();
+	    break;
+
+	case SUSP:
+	    sendsusp();
+	    break;
+
+	case ABORT:
+	    sendbrk();
+	    break;
+
+	case IAC:
+	    *pfrontp++ = c;
+	    break;
+	}
+	state = TS_DATA;
+	break;
+
+	case TS_SB:
+	    if (c == IAC) {
+		state = TS_SE;
+	    } else {
+		SB_ACCUM(c);
+	    }
+	    break;
+
+	case TS_SE:
+	    if (c != SE) {
+		if (c != IAC) {
+		    /*
+		     * bad form of suboption negotiation.
+		     * handle it in such a way as to avoid
+		     * damage to local state.  Parse
+		     * suboption buffer found so far,
+		     * then treat remaining stream as
+		     * another command sequence.
+		     */
+
+		    /* for DIAGNOSTICS */
+		    SB_ACCUM(IAC);
+		    SB_ACCUM(c);
+		    subpointer -= 2;
+
+		    SB_TERM();
+		    suboption();
+		    state = TS_IAC;
+		    goto gotiac;
+		}
+		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;
+	    }
+	    break;
+
+	case TS_WILL:
+	    willoption(c);
+	    state = TS_DATA;
+	    continue;
+
+	case TS_WONT:
+	    wontoption(c);
+	    if (c==TELOPT_ENCRYPT && his_do_dont_is_changing(TELOPT_ENCRYPT) )
+                dontoption(c);
+	    state = TS_DATA;
+	    continue;
+
+	case TS_DO:
+	    dooption(c);
+	    state = TS_DATA;
+	    continue;
+
+	case TS_DONT:
+	    dontoption(c);
+	    state = TS_DATA;
+	    continue;
+
+	default:
+#ifndef __SYMBIAN32__	
+	    syslog(LOG_ERR, "telnetd: panic state=%d\n", state); 
+#endif 	    
+	    printf("telnetd: panic state=%d\n", state);
+	    exit(1);
+	}
+    }
+}  /* end of telrcv */
+
+/*
+ * The will/wont/do/dont state machines are based on Dave Borman's
+ * Telnet option processing state machine.
+ *
+ * These correspond to the following states:
+ *	my_state = the last negotiated state
+ *	want_state = what I want the state to go to
+ *	want_resp = how many requests I have sent
+ * All state defaults are negative, and resp defaults to 0.
+ *
+ * When initiating a request to change state to new_state:
+ *
+ * if ((want_resp == 0 && new_state == my_state) || want_state == new_state) {
+ *	do nothing;
+ * } else {
+ *	want_state = new_state;
+ *	send new_state;
+ *	want_resp++;
+ * }
+ *
+ * When receiving new_state:
+ *
+ * if (want_resp) {
+ *	want_resp--;
+ *	if (want_resp && (new_state == my_state))
+ *		want_resp--;
+ * }
+ * if ((want_resp == 0) && (new_state != want_state)) {
+ *	if (ok_to_switch_to new_state)
+ *		want_state = new_state;
+ *	else
+ *		want_resp++;
+ *	send want_state;
+ * }
+ * my_state = new_state;
+ *
+ * Note that new_state is implied in these functions by the function itself.
+ * will and do imply positive new_state, wont and dont imply negative.
+ *
+ * Finally, there is one catch.  If we send a negative response to a
+ * positive request, my_state will be the positive while want_state will
+ * remain negative.  my_state will revert to negative when the negative
+ * acknowlegment arrives from the peer.  Thus, my_state generally tells
+ * us not only the last negotiated state, but also tells us what the peer
+ * wants to be doing as well.  It is important to understand this difference
+ * as we may wish to be processing data streams based on our desired state
+ * (want_state) or based on what the peer thinks the state is (my_state).
+ *
+ * This all works fine because if the peer sends a positive request, the data
+ * that we receive prior to negative acknowlegment will probably be affected
+ * by the positive state, and we can process it as such (if we can; if we
+ * can't then it really doesn't matter).  If it is that important, then the
+ * peer probably should be buffering until this option state negotiation
+ * is complete.
+ *
+ */
+void
+send_do(int option, int init)
+{
+    if (init) {
+	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
+	 * that we sent a DONT, so that we can send more DOs if
+	 * we want to.
+	 */
+	if (option == TELOPT_TM)
+	    set_his_want_state_wont(option);
+	else
+	    set_his_want_state_will(option);
+	do_dont_resp[option]++;
+    }
+    output_data((const char *)doopt, option);
+
+    DIAG(TD_OPTIONS, printoption("td: send do", option));
+}
+
+#ifdef	AUTHENTICATION
+extern void auth_request(void);
+#endif
+#ifdef	ENCRYPTION
+extern void encrypt_send_support();
+#endif
+
+void
+willoption(int option)
+{
+    int changeok = 0;
+    void (*func)() = 0;
+
+    /*
+     * 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] && his_state_is_will(option))
+	    do_dont_resp[option]--;
+    }
+    if (do_dont_resp[option] == 0) {
+	if (his_want_state_is_wont(option)) {
+	    switch (option) {
+
+	    case TELOPT_BINARY:
+		init_termbuf();
+		tty_binaryin(1);
+		set_termbuf();
+		changeok++;
+		break;
+
+	    case TELOPT_ECHO:
+		/*
+		 * See comments below for more info.
+		 */
+		not42 = 0;	/* looks like a 4.2 system */
+		break;
+
+	    case TELOPT_TM:
+		/*
+		 * We never respond to a WILL TM, and
+		 * we leave the state WONT.
+		 */
+		return;
+
+	    case TELOPT_LFLOW:
+		/*
+		 * If we are going to support flow control
+		 * option, then don't worry peer that we can't
+		 * change the flow control characters.
+		 */
+		slctab[SLC_XON].defset.flag &= ~SLC_LEVELBITS;
+		slctab[SLC_XON].defset.flag |= SLC_DEFAULT;
+		slctab[SLC_XOFF].defset.flag &= ~SLC_LEVELBITS;
+		slctab[SLC_XOFF].defset.flag |= SLC_DEFAULT;
+	    case TELOPT_TTYPE:
+	    case TELOPT_SGA:
+	    case TELOPT_NAWS:
+	    case TELOPT_TSPEED:
+	    case TELOPT_XDISPLOC:
+	    case TELOPT_NEW_ENVIRON:
+	    case TELOPT_OLD_ENVIRON:
+		changeok++;
+		break;
+
+
+#ifdef	AUTHENTICATION
+	    case TELOPT_AUTHENTICATION:
+		func = auth_request;
+		changeok++;
+		break;
+#endif
+
+#ifdef	ENCRYPTION
+	    case TELOPT_ENCRYPT:
+		func = encrypt_send_support;
+		changeok++;
+		break;
+#endif
+			
+	    default:
+		break;
+	    }
+	    if (changeok) {
+		set_his_want_state_will(option);
+		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	AUTHENTICATION
+	    case TELOPT_AUTHENTICATION:
+		func = auth_request;
+		break;
+#endif
+
+#ifdef	ENCRYPTION
+	    case TELOPT_ENCRYPT:
+		func = encrypt_send_support;
+		break;
+#endif
+
+	    case TELOPT_LFLOW:
+		func = flowstat;
+		break;
+	    }
+	}
+    }
+    set_his_state_will(option);
+    if (func)
+	(*func)();
+}  /* end of willoption */
+
+void
+send_dont(int option, int init)
+{
+    if (init) {
+	if ((do_dont_resp[option] == 0 && his_state_is_wont(option)) ||
+	    his_want_state_is_wont(option))
+	    return;
+	set_his_want_state_wont(option);
+	do_dont_resp[option]++;
+    }
+    output_data((const char *)dont, option);
+
+    DIAG(TD_OPTIONS, printoption("td: send dont", option));
+}
+
+void
+wontoption(int option)
+{
+    /*
+     * 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] && his_state_is_wont(option))
+	    do_dont_resp[option]--;
+    }
+    if (do_dont_resp[option] == 0) {
+	if (his_want_state_is_will(option)) {
+	    /* it is always ok to change to negative state */
+	    switch (option) {
+	    case TELOPT_ECHO:
+		not42 = 1; /* doesn't seem to be a 4.2 system */
+		break;
+
+	    case TELOPT_BINARY:
+		init_termbuf();
+		tty_binaryin(0);
+		set_termbuf();
+		break;
+
+	    case TELOPT_TM:
+		/*
+		 * If we get a WONT TM, and had sent a DO TM,
+		 * don't respond with a DONT TM, just leave it
+		 * as is.  Short circut the state machine to
+		 * achive this.
+		 */
+		set_his_want_state_wont(TELOPT_TM);
+		return;
+
+	    case TELOPT_LFLOW:
+		/*
+		 * If we are not going to support flow control
+		 * option, then let peer know that we can't
+		 * change the flow control characters.
+		 */
+		slctab[SLC_XON].defset.flag &= ~SLC_LEVELBITS;
+		slctab[SLC_XON].defset.flag |= SLC_CANTCHANGE;
+		slctab[SLC_XOFF].defset.flag &= ~SLC_LEVELBITS;
+		slctab[SLC_XOFF].defset.flag |= SLC_CANTCHANGE;
+		break;
+
+#ifdef 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;
+	    }
+	    set_his_want_state_wont(option);
+	    if (his_state_is_will(option))
+		send_dont(option, 0);
+	} else {
+	    switch (option) {
+	    case TELOPT_TM:
+		break;
+
+#ifdef AUTHENTICATION
+	    case TELOPT_AUTHENTICATION:
+		auth_finished(0, AUTH_REJECT);
+		break;
+#endif
+	    default:
+		break;
+	    }
+	}
+    }
+    set_his_state_wont(option);
+
+}  /* end of wontoption */
+
+void
+send_will(int option, int init)
+{
+    if (init) {
+	if ((will_wont_resp[option] == 0 && my_state_is_will(option))||
+	    my_want_state_is_will(option))
+	    return;
+	set_my_want_state_will(option);
+	will_wont_resp[option]++;
+    }
+    output_data ((const char *)will, option);
+
+    DIAG(TD_OPTIONS, printoption("td: send will", option));
+}
+
+/*
+ * 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;
+
+void
+dooption(int option)
+{
+    int changeok = 0;
+
+    /*
+     * 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] && my_state_is_will(option))
+	    will_wont_resp[option]--;
+    }
+    if ((will_wont_resp[option] == 0) && (my_want_state_is_wont(option))) {
+	switch (option) {
+	case TELOPT_ECHO:
+	    {
+		init_termbuf();
+		tty_setecho(1);
+		set_termbuf();
+	    }
+	changeok++;
+	break;
+
+	case TELOPT_BINARY:
+	    init_termbuf();
+	    tty_binaryout(1);
+	    set_termbuf();
+	    changeok++;
+	    break;
+
+	case TELOPT_SGA:
+	    turn_on_sga = 0;
+	    changeok++;
+	    break;
+
+	case TELOPT_STATUS:
+	    changeok++;
+	    break;
+
+	case TELOPT_TM:
+	    /*
+	     * Special case for TM.  We send a WILL, but
+	     * pretend we sent a WONT.
+	     */
+	    send_will(option, 0);
+	    set_my_want_state_wont(option);
+	    set_my_state_wont(option);
+	    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);
+	    netflush();
+	    cleanup(0);
+	    /* NOT REACHED */
+	    break;
+
+#ifdef ENCRYPTION
+	case TELOPT_ENCRYPT:
+	    changeok++;
+	    break;
+#endif
+	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) {
+	    set_my_want_state_will(option);
+	    send_will(option, 0);
+	} else {
+	    will_wont_resp[option]++;
+	    send_wont(option, 0);
+	}
+    }
+    set_my_state_will(option);
+
+}  /* end of dooption */
+
+void
+send_wont(int option, int init)
+{
+    if (init) {
+	if ((will_wont_resp[option] == 0 && my_state_is_wont(option)) ||
+	    my_want_state_is_wont(option))
+	    return;
+	set_my_want_state_wont(option);
+	will_wont_resp[option]++;
+    }
+    output_data ((const char *)wont, option);
+
+    DIAG(TD_OPTIONS, printoption("td: send wont", option));
+}
+
+void
+dontoption(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] && my_state_is_wont(option))
+	    will_wont_resp[option]--;
+    }
+    if ((will_wont_resp[option] == 0) && (my_want_state_is_will(option))) {
+	switch (option) {
+	case TELOPT_BINARY:
+	    init_termbuf();
+	    tty_binaryout(0);
+	    set_termbuf();
+	    break;
+
+	case TELOPT_ECHO:	/* we should stop echoing */
+	    {
+		init_termbuf();
+		tty_setecho(0);
+		set_termbuf();
+	    }
+	break;
+
+	case TELOPT_SGA:
+	    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;
+
+	default:
+	    break;
+	}
+
+	set_my_want_state_wont(option);
+	if (my_state_is_will(option))
+	    send_wont(option, 0);
+    }
+    set_my_state_wont(option);
+
+}  /* 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()
+ *
+ *	Look at the sub-option buffer, and try to be helpful to the other
+ * side.
+ *
+ *	Currently we recognize:
+ *
+ *	Terminal type is
+ *	Linemode
+ *	Window size
+ *	Terminal speed
+ */
+void
+suboption(void)
+{
+    int subchar;
+
+    DIAG(TD_OPTIONS, {netflush(); printsub('<', subpointer, SB_LEN()+2);});
+
+    subchar = SB_GET();
+    switch (subchar) {
+    case TELOPT_TSPEED: {
+	int xspeed, rspeed;
+
+	if (his_state_is_wont(TELOPT_TSPEED))	/* Ignore if option disabled */
+	    break;
+
+	settimer(tspeedsubopt);
+
+	if (SB_EOF() || SB_GET() != TELQUAL_IS)
+	    return;
+
+	xspeed = atoi((char *)subpointer);
+
+	while (SB_GET() != ',' && !SB_EOF()){}
+	
+	if (SB_EOF())
+	    return;
+
+	rspeed = atoi((char *)subpointer);
+	clientstat(TELOPT_TSPEED, xspeed, rspeed);
+
+	break;
+
+    }  /* end of case TELOPT_TSPEED */
+
+    case TELOPT_TTYPE: {		/* Yaaaay! */
+	static char terminalname[41];
+
+	if (his_state_is_wont(TELOPT_TTYPE))	/* Ignore if option disabled */
+	    break;
+	settimer(ttypesubopt);
+
+	if (SB_EOF() || SB_GET() != TELQUAL_IS) {
+	    return;		/* ??? XXX but, this is the most robust */
+	}
+
+	terminaltype = terminalname;
+
+	while ((terminaltype < (terminalname + sizeof terminalname-1)) &&
+	       !SB_EOF()) {
+	    int c;
+
+	    c = SB_GET();
+	    if (isupper(c)) {
+		c = tolower(c);
+	    }
+	    *terminaltype++ = c;    /* accumulate name */
+	}
+	*terminaltype = 0;
+	terminaltype = terminalname;
+	break;
+    }  /* end of case TELOPT_TTYPE */
+
+    case TELOPT_NAWS: {
+	int xwinsize, ywinsize;
+
+	if (his_state_is_wont(TELOPT_NAWS))	/* Ignore if option disabled */
+	    break;
+
+	if (SB_EOF())
+	    return;
+	xwinsize = SB_GET() << 8;
+	if (SB_EOF())
+	    return;
+	xwinsize |= SB_GET();
+	if (SB_EOF())
+	    return;
+	ywinsize = SB_GET() << 8;
+	if (SB_EOF())
+	    return;
+	ywinsize |= SB_GET();
+	clientstat(TELOPT_NAWS, xwinsize, ywinsize);
+
+	break;
+
+    }  /* end of case TELOPT_NAWS */
+
+    case TELOPT_STATUS: {
+	int mode;
+
+	if (SB_EOF())
+	    break;
+	mode = SB_GET();
+	switch (mode) {
+	case TELQUAL_SEND:
+	    if (my_state_is_will(TELOPT_STATUS))
+		send_status();
+	    break;
+
+	case TELQUAL_IS:
+	    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';
+	setenv("DISPLAY", (char *)subpointer, 1);
+	break;
+    }  /* end of case TELOPT_XDISPLOC */
+
+#ifdef	TELOPT_NEW_ENVIRON
+    case TELOPT_NEW_ENVIRON:
+#endif
+    case TELOPT_OLD_ENVIRON: {
+	int c;
+	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) {
+		    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, {
+			    output_data("ENVIRON VALUE and VAR are reversed!\r\n");
+			});
+
+		    }
+		}
+		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)
+		    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)
+	    setenv(varp, valp, 1);
+	else
+	    unsetenv(varp);
+	break;
+    }  /* end of case TELOPT_NEW_ENVIRON */
+#ifdef 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
+
+    default:
+	break;
+    }  /* end of switch */
+
+}  /* end of suboption */
+
+void
+doclientstat(void)
+{
+    clientstat(TELOPT_LINEMODE, WILL, 0);
+}
+
+#undef ADD
+#define	ADD(c)	 *ncp++ = c
+#define	ADD_DATA(c) { *ncp++ = c; if (c == SE || c == IAC) *ncp++ = c; }
+
+void
+send_status(void)
+{
+    unsigned char statusbuf[256];
+    unsigned char *ncp;
+    unsigned char i;
+
+    ncp = statusbuf;
+
+    netflush();	/* get rid of anything waiting to go out */
+
+    ADD(IAC);
+    ADD(SB);
+    ADD(TELOPT_STATUS);
+    ADD(TELQUAL_IS);
+
+    /*
+     * 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 (his_want_state_is_will(i)) {
+	    ADD(DO);
+	    ADD_DATA(i);
+	}
+    }
+
+    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(IAC);
+    ADD(SE);
+
+    writenet(statusbuf, ncp - statusbuf);
+    netflush();	/* Send it on its way */
+
+    DIAG(TD_OPTIONS,
+	 {printsub('>', statusbuf, ncp - statusbuf); netflush();});
+}