aboutsummaryrefslogtreecommitdiff
path: root/crypto/heimdal/appl/telnet/telnetd/state.c
diff options
context:
space:
mode:
Diffstat (limited to 'crypto/heimdal/appl/telnet/telnetd/state.c')
-rw-r--r--crypto/heimdal/appl/telnet/telnetd/state.c1356
1 files changed, 1356 insertions, 0 deletions
diff --git a/crypto/heimdal/appl/telnet/telnetd/state.c b/crypto/heimdal/appl/telnet/telnetd/state.c
new file mode 100644
index 000000000000..80b90ea2d309
--- /dev/null
+++ b/crypto/heimdal/appl/telnet/telnetd/state.c
@@ -0,0 +1,1356 @@
+/*
+ * 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. 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.
+ */
+
+#include "telnetd.h"
+
+RCSID("$Id: state.c,v 1.13 1999/05/13 23:12:50 assar Exp $");
+
+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[2048], *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:
+ syslog(LOG_ERR, "telnetd: panic state=%d\n", state);
+ 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);
+}
+
+#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();});
+}