/* File MSNTND.C
 * Telnet driver
 *
 * Copyright (C) 1985, 1993, Trustees of Columbia University in the 
 * City of New York.  Permission is granted to any individual or institution
 * to use this software as long as it is not sold for profit.  This copyright
 * notice must be retained.  This software may not be included in commercial
 * products without written permission of Columbia University.
 *
 * Written for MS-DOS Kermit by Joe R. Doupnik, 
 *  Utah State University, jrd@cc.usu.edu, jrd@usu.Bitnet,
 *  and by Frank da Cruz, Columbia Univ., fdc@watsun.cc.columbia.edu.
 * With earlier contributions by Erick Engelke of the University of
 *  Waterloo, Waterloo, Ontario, Canada.
 *
 * Last edit
 * 16 June 1993 v3.13
 */
#include "msntcp.h"
#include "msnlib.h"

#define	MAXSESSIONS 6
#define MSGBUFLEN 512

/* TCP/IP Telnet negotiation support code */
#define IAC     255
#define DONT    254
#define DO      253
#define WONT    252
#define WILL    251
#define SB      250
#define BREAK   243
#define SE      240

#define TELOPT_ECHO     1
#define TELOPT_SGA      3
#define TELOPT_STATUS   5
#define TELOPT_TTYPE    24
#define TELOPT_NAWS	31
#define NTELOPTS        24

#define BAPICON		0xa0	/* 3Com BAPI, connect to port */
#define BAPIDISC	0xa1	/* 3Com BAPI, disconnect */
#define BAPIWRITE	0xa4	/* 3Com BAPI, write block */
#define BAPIREAD	0xa5	/* 3Com BAPI, read block */
#define BAPIBRK		0xa6	/* 3Com BAPI, send short break */
#define BAPISTAT	0xa7	/* 3Com BAPI, read status (# chars avail) */
#define BAPIHERE	0xaf	/* 3Com BAPI, presence check */
#define BAPIEECM	0xb0	/* 3Com BAPI, enable/disable ECM char */
#define BAPIECM		0xb1	/* 3Com BAPI, trap Enter Command Mode char */
#define BAPIPING	0xb2	/* Send Ping, Kermit extension of BAPI */
#define BAPISTAT_SUC	0	/* function successful */
#define BAPISTAT_NCW	1	/* no character written */
#define BAPISTAT_NCR	2	/* no character read */
#define BAPISTAT_NSS	3	/* no such session */
#define BAPISTAT_NOS	7	/* session aborted */
#define BAPISTAT_NSF	9	/* no such function */


#define TSBUFSIZ 41
static byte sb[TSBUFSIZ];	/* Buffer for subnegotiations */

static byte *termtype;		/* Telnet, used in negotiations */
static int sgaflg = 0;		/* Telnet SGA flag */
static int dosga  = 0;		/* Telnet 1 if I sent DO SGA from tn_ini() */
static int wttflg = 0;		/* Telnet Will Termtype flag */
static int wnawsflg = 0;	/* Telnet Will NAWS option flag */
static ttinccnt = 0;		/* Telnet count of chars in read socket */
static int in_IAC = 0;		/* non-zero when doing Telnet Options*/
int echo = 1;			/* Telnet echo, default on, but hate it */

struct	{
	word ident;
	char *name;
	} termarray[]=		/* terminal type names, from mssdef.h */
		{
		{0,"UNKNOWN"}, {1,"H-19"}, {2,"VT52"}, {4,"VT100"},
		{8,"VT102"}, {0x10,"VT220"}, {0x20,"VT320"}, {0x40,"TEK4014"},
		{0x80,"VIP7809"}, {0x100, "PT200"}, {0x200, "D463"},
		{0x400, "D470"}, {0xff,"UNKNOWN"}
		};

extern  byte FAR * bapiadr;	/* Far address of local Telnet client's buf */
extern	int bapireq, bapiret;	/* count of chars in/out */
extern	longword my_ip_addr, sin_mask; /* binary of our IP, netmask */
extern	longword ipbcast;	/* binary IP of broadcast */
extern	byte * def_domain;	/* default domain string */
extern	byte * hostname;	/* our name from BootP server */
static	longword host;		/* binary IP of host */
int	doslevel = 1;		/* operating at DOS level if != 0 */
word	sock_delay = 30;
static	tcp_Socket *s;		/* ptr to active socket */
int	msgcnt;			/* count of chars in message buffer */
byte	msgbuf[MSGBUFLEN+1];	/* message buffer for client */

extern	int hookvect(void);
extern	int unhookvect(void);
extern	int kecho(int);
extern	void readback(void);
int	session_close(int);
int	session_rotate(int);
int	tn_ini(void);
int	ttinc(void);
int	tn_doop(word);
int	send_iac(byte, int);
int	tn_sttyp(void);
int	tn_snaws(void);
int	subnegotiate(void);
void	optdebug(byte, int);

extern byte kmyip[];			/* our IP number */
extern byte knetmask[];			/* our netmask */
extern byte kdomain[];			/* our domain */
extern byte kgateway[];			/* our gateway */
extern byte kns1[];			/* our nameserver #1 */
extern byte kns2[];			/* our nameserver #2 */
extern byte kbcast[];			/* broadcast address pattern */
extern byte khost[];			/* remote host name/IP # */
extern word kport;			/* remote host TCP port */
extern word kserver;			/* if Kermit is in server mode */
extern byte ktttype[];			/* user term type override string */
extern word kterm;			/* terminal type index, from mssdef.h*/
extern byte kterm_lines;		/* terminal screen height */
extern byte kterm_cols;			/* terminal screen width */
extern byte kbtpserver[];		/* IP of Bootp host answering query */
extern byte kdebug;			/* non-zero if debug mode is active */

static struct sessioninfo
	{
	tcp_Socket socketdata;		/* current socket storage area */
	int sessionid;			/* identifying session */
	int tn_inited;			/* Telnet Options initied */
	int echo;			/* local echo state (0=remote) */
	} session[MAXSESSIONS];

static unsigned num_sessions = 0;		/* qty of active sessions */
static int active = -1;				/* ident of active session */
	
/* this runs at Kermit task level */
int
main(void)					/* start TCP from Kermit */
{
	int status = 0;
	register byte *p;
	register int i;

	doslevel = 1;			/* say at DOS level here */
	if (num_sessions == 0)		/* then initialize the system */
	{
	my_ip_addr = 0L;
	def_domain = kdomain;		/* define our domain */
	host = 0L;		/* BELONGS IN SESSION, but is global */
	for (i = 0; i < MAXSESSIONS; i++)	/* init sessioninfo array */
		session[i].sessionid = -1;

	if (hookvect() == 0)
		{
		outs("\r\n Hooking vectors failed");
		goto anyerr;
		}
	
	s = NULL;			/* no TCP socket yet */
	if (tcp_init() == 0)		/* init TCP code */
		{
		outs("\r\n Unable to initialize TCP/IP system, quitting");
		goto anyerr;
		}

		/* set Ethernet broadcast to all 1's or all 0's */
	ipbcast = resolve(kbcast);	/* IP broadcast address */
	bootphost = ipbcast;		/* set Bootp to this IP too */

	if ((p = strchr(kmyip, ' ')) != NULL)	/* have a space */
		*p = '\0';			/* terminate on the space */

	if (stricmp(kmyip, "bootp") == 0)
		{
		if (dobootp() != 0)	/* use BOOTP for Internet address */
			{
			outs("\r\n BOOTP query failed. Quitting.");
			goto anyerr;
			}
		ntoa(kmyip, my_ip_addr);
		if (sin_mask != 0L)
			ntoa(knetmask, sin_mask);
		if (arp_rpt_gateway(0) != 0L)
			ntoa(kgateway, arp_rpt_gateway(0));
		if (last_nameserver > 0) 
			ntoa(kns1, def_nameservers[0]);
		if (last_nameserver > 1)
			ntoa(kns2, def_nameservers[1]);
		if (kdomain[0] == '\0' && hostname[0] != '\0')
						/* construct domain */
			if (p = strchr(hostname, '.')) /* find dot */
				strcpy(kdomain, &p[1]);
		readback();	/* readback these to Kermit main */
		}
	else
		{
		if (stricmp(kmyip, "rarp") == 0)
			{
			my_ip_addr = 0L;	/* clear our IP for RARP */
			if (pkt_rarp_init() == 0)
				goto anyerr;	/* no RARP handle */

			if (do_rarp() == 0) 	/* use RARP */
				{
				outs("\r\n RARP query failed.");
				goto anyerr;
				}
			ntoa(kmyip, my_ip_addr);
			readback();
			}
		else 				/* convert to 32 bit binary */
			my_ip_addr = resolve(kmyip);
		}

	if (my_ip_addr == 0L)
    		{			/* something drastically wrong */
		outs("\r\n Cannot understand my IP address, terminating");
		goto anyerr;
		}

	if ((sin_mask = resolve(knetmask)) == 0L)
	    	{			/* something drastically wrong */
		outs("\r\n Bad network submask, terminating");
		goto anyerr;
		}

	if (stricmp(kns1, "unknown"))
		add_server(&last_nameserver, MAX_NAMESERVERS, def_nameservers,
			resolve(kns1));
	if (stricmp(kns2, "unknown"))
		add_server(&last_nameserver, MAX_NAMESERVERS, def_nameservers,
		resolve(kns2));

	if (stricmp(kgateway, "unknown"))
		arp_add_gateway(kgateway, 0L);

	}	/* end of initial system setup */

/* starting a new session */

	if (num_sessions >= MAXSESSIONS)
		{
		outs("\nAll sessions are in use. Sorry\n");
		return (-1);		/* say can't do another, fail */
		}

	status = -1;
	for (i = 0; i < MAXSESSIONS; i++)	/* find free ident */
		if (session[i].sessionid < 0)
			{
			s = &session[i].socketdata;	/* active socket */
			session[i].sessionid = i;	/* ident */
			status = i;
			active = i;		/* identify active session */
			session[i].echo = echo;	/* Telnet echo from mainpgm */
			session[i].tn_inited = 0; /* do Telnet Options init */
			num_sessions++;	/* say have another active session */
			break;
			}

	if (status == -1)
		goto sock_err;		/* report bad news and exit */
	status = 0;

	if (ktttype[0] != '\0')		/* if user override given */
		termtype = ktttype;
	else				/* get termtype from real use */
		{
		for (i = 0; termarray[i].ident != 0xff; i++)
    			if (termarray[i].ident == kterm)	/* match */
				break;
		termtype = termarray[i].name;
		}

	/* kserver is set by main body SERVER command. If khost[0] is '*'
	then also behave as a server/listener for any main body mode. */

	if ((khost[0] == '*') || (kserver != 0))
		{
		host = 0L;		/* force no host IP at this stage */
		tcp_listen(s, kport, host, 0, 0); /* post a listen */
		}
	else					/* normal client mode */
		{
		outs("\r\n Resolving address of host ");
		outs(khost); outs(" ...");
		if ((host = resolve(khost)) == 0)
			{
			outs( "\r\n Cannot resolve address of host ");
			outs(khost);
			goto anyerr;
			}
		outs("\r\n");		/* get clean screen line */
		}

	if (host == my_ip_addr)
		{
		outs("\r\n Cannot talk to myself, sorry.");
		goto anyerr;
		}

	if ((khost[0] == '*') || (kserver != 0)) /* if server */
		{
		doslevel = 0;			/* end of DOS level i/o */
		if (kserver == 0)
			outs("\r\n Operating as a Telnet server. Waiting...");
		}
	else					/* we are a client */
		{
		if (tcp_open(s, 0, host, kport) == 0)
			{
			outs("\r\n Unable to contact the host.");
			outs("\r\n The host may be down or");
			outs(" a gateway may be needed.");
			goto anyerr;
			}
		sock_wait_established(s, sock_delay, NULL, &status);
		}

	if (sock_mode(s, TCP_MODE_NAGLE) == 0)
		{
		outs("\r\n Unable to set socket mode");
		goto anyerr;
		}
	doslevel = 0;			/* end of DOS level i/o */
	return (active); 		/* return handle of this session */


sock_err:
	switch (status)
		{
		case 1 : outs("\r\n Session is closed");
			break;
		case -1:/* outs("\r\n Cannot start a connection");*/
			break;
		}

anyerr:	session_close(active);
	if (num_sessions == 0)
		{
		tcp_shutdown();
		eth_release();			/* do absolutely */
		unhookvect();
		doslevel = 1;			/* at DOS level i/o */
		}
	return (-1);				/* say have stopped */
}

/* this runs at Kermit task level */
int
exit(int value)				/* stop TCP from Kermit */
{
	int i;

	doslevel = 1;			/* tell i/o routines this is DOS */
	for (i = 0; i < MAXSESSIONS; i++)
		session_close(i);	/* close session, free buffers */
	num_sessions = 0;
	tcp_shutdown();			/* force the issue if necessary */
	eth_release();			/* do absolutely */
	unhookvect();
	return (-1);
}

/* return the int of the next available session after session "ident", or
   -1 if none. Do not actually change sessions. 
*/
int
session_rotate(int ident)
{	
	register int i, j;
	for (i = 1; i <= MAXSESSIONS; i++)
		{
		j = (i + ident) % MAXSESSIONS;	/* modulo maxsessions */
		if (session[j].sessionid != -1)
			return (j);
		}
	return (-1);
}

/* Change to session ident "ident" and active to "ident", return "active"
   if that session is valid, else  return -1.
*/
int
session_change(int ident)
{					/* change active session to ident */

	if (ident < 0 || ident >= MAXSESSIONS)
		return (-1);

	if (session[ident].sessionid < 0)
		return (-1);			/* inactive session */

	s = &session[ident].socketdata;		/* watch mid-stream stuff */
	kecho(echo = session[ident].echo);	/* update Kermit main body */
	return (active = ident);		/* ident of active session */
}

/* Close session "ident". Return ident of next available session, if any,
   without actually changing sessions. Returns -1 if no more sessions or
   if ident is out of legal range or if the session is already closed.
*/
int
session_close(int ident)	/* close a particular, ident, session */
{
	if (ident >= 0 && ident < MAXSESSIONS &&
		session[ident].sessionid >= 0)		/* graceful close */
		{
		sock_close(&session[ident].socketdata);
			/* free receive and send socket data buffers */
			/* this is pushing the tcp timeout a bit much */
		free(session[ident].socketdata.rdata);
		free(session[ident].socketdata.sdata);
					/* clear pointers to same */
		session[ident].socketdata.rdata = NULL; 
		session[ident].socketdata.sdata = NULL;
		session[ident].sessionid = -1;		/* marked closed */
		session[ident].tn_inited = 0;		/* not inited */
		if (num_sessions > 0) num_sessions--;/* qty active sessions */
		}
				/* activate, rtn next available session */
	return (session_change(session_rotate(ident)));
}

/* This is called by the main body of Kermit to transfer data. It returns
   the transfer status as a BAPI valued int. */

int
serial_handler(word cmd)
{
	int i, cmdstatus;
	register int ch;
	byte FAR * bp;
	extern int session_change(int);

	if (session[active].tn_inited == 0)	/* if not initialized yet */
		if (tn_ini() == -1)	/* init Telnet negotiations */
			return (BAPISTAT_NOS);	/* fatal error, quit */

	tcp_tick(s);			/* catch up on packet reading */

	cmdstatus = BAPISTAT_SUC;	/* success so far */
	switch (cmd)			/* cmd is function code */
		{
		case BAPIWRITE:		/* write a block, bapireq chars */
			if (khost[0] == '*' && s->state != tcp_StateESTAB)
				{
				bapiret = bapireq; /* discard output and */
				break;	/* send nothing until client appears*/
				}
			if (session[active].echo)
				if (sock_flushnext(s) == 0) /* send next now */
					cmdstatus = BAPISTAT_NOS; /* no sess*/
			if (s->state == tcp_StateESTAB)
				bapiret = sock_write(s, bapiadr, bapireq);
			else 
				{
				cmdstatus = BAPISTAT_NOS; /* no session */
				bapiret = bapireq;	/* discard data */
				break;
				}

		/* if terminal serving with no local echoing do echo here */
			if (session[active].echo == 0 && 
					khost[0] == '*' && kserver == 0)
				{		 /* echo to us */
				i = bapiret > MSGBUFLEN-msgcnt? 
						MSGBUFLEN-msgcnt: bapiret;
				bcopyff(bapiadr, msgbuf, i);
				outsn(msgbuf, i);
				}
			break;

		case BAPIREAD:		/* read block, count of bapireq */
						/* is IAC present? */
			i = fstchr(s->rdata, s->rdatalen, IAC);
			bapiret = 0;

			if (i < 0)
				{
				cmdstatus = BAPISTAT_NCR;/* nothing present */
				break;
				}
			if (i > 0 && in_IAC == 0)  /* read up to IAC */
				{
				if (i > bapireq) i = bapireq;
 				bapiret = sock_fastread(s, bapiadr, i);

		/* if terminal serving with local echoing then echo to host */
				if (session[active].echo != 0 && 
						khost[0] == '*' && 
						kserver == 0)
					sock_write(s, bapiadr, bapiret);
				break;
				}

			bp = bapiadr;	/* use slow reading for Options */
			ttinccnt = 0;	/* setup to read from buffer */
				/* state is here, IAC style, vs fast mode */
			in_IAC = 1;
			for (i = 0; i < bapireq; i++)
				{
				if ((ch = ttinc()) == -1)
					break;		/* no char */
				ch &= 0xff;
		        	if ( ch == IAC )  /* Telnet options intro */
					{		/* do Options */
					if ((ch = ttinc()) == -1)
						break;	/* do more later */
					if ((ch &= 0xff) != IAC)
						{   /* IAC IAC means IAC */
						tn_doop(ch); /* negotiate */
						continue;
						}
					}
				in_IAC = 0; 		/* end of mode */
				*bp++ = (byte) (ch & 0xff);
				bapiret++;
				}
			session[active].echo = echo;
			break;

		case BAPIBRK:			/* send BREAK */
			sock_putc(s, IAC);	/* char to socket buffer */
			sock_putc(s, BREAK);
			if (sock_flush(s) == 0)	/* send it now, with push */
				cmdstatus = BAPISTAT_NOS; /* no session */
			break;

		case BAPIPING:			/* Ping current host */
			do_ping(khost, host);
			break;

		case BAPISTAT:		/* check read status (chars avail) */
			bapiret = sock_dataready(s); /* # chars available */
 			break;

		case BAPIDISC:			/* close this connection */
			session_close(active);
			/* fall through to do session rotate */
		case BAPIECM:
			i = session_rotate(active);	/* get new ident */
			if (i != -1)			/* if exists */
				i = session_change(i);	/* make active */
			bapiret = 0;	/* no chars processed */
			return (i);	/* New session or -1, special */

		default: cmdstatus = BAPISTAT_NSF;/* unsupported function */
			break;
		}

	if ((sock_dataready(s) == 0) && (msgcnt == 0) &&  
		(s->sisopen == SOCKET_CLOSED))	/* no data and no session */
			return (BAPISTAT_NOS);	/* means exit session */
	else
		return (cmdstatus);		/* stuff is yet unread */
}

/* ttinc   - destructively read char from socket buffer, return -1 if fail */

int 
ttinc(void)
{
	byte ch;
	
	if (ttinccnt <= 0)			/* if no known chars */
		{
		tcp_tick(s);			/* read another packet */
		ttinccnt = sock_dataready(s); /* count available, if any */
		}
	if (sock_fastread(s, &ch, 1) != 0)	/* qty chars returned */
		{
		ttinccnt--;			/* one less char in socket */
		return (0xff & ch);
		}
	return (-1);
}

/* Initialize a telnet connection */
/* Returns -1 on error, 0 is ok */

int 
tn_ini(void)
{
	sgaflg = 0;			/* SGA flag starts out this way */
	wttflg = 0;			/* Did not send WILL TERM TYPE yet. */
	wnawsflg = 0;			/* Did not send NAWS info yet */
	dosga  = 0;			/* Did not send DO SGA yet. */
	in_IAC = 0;			/* haven't done IAC reception yet */
	ttinccnt = 0;			/* count of chars in read socket */
	session[active].tn_inited = 1;	/* say we are doing this proc */
	kecho(echo = 1); 		/* start with echo to ourselves */
				/* if not server or not Telnet port */
	if (khost[0] == '*' || kserver != 0 || 	kport != 23)
			return (0);		/* don't go first */
	if (send_iac( WILL, TELOPT_TTYPE )) return( -1 );
	if (send_iac( WILL, TELOPT_NAWS)) return(-1);
	if (send_iac( DO, TELOPT_SGA )) return( -1 );
	wttflg = 1;			/* Remember I offered TTYPE */
	wnawsflg = 1;			/* Remember I offered NAWS */
	dosga = 1;			/* Remember I sent DO SGA */
	return(0);
}

/*
 * send_iac - send interupt character and pertanent stuff
 *	    - return 0 on success
 */

int
send_iac(byte cmd, int opt)
{
	byte io_data[3];

	io_data[0] = IAC;
	io_data[1] = cmd;
	io_data[2] = (byte)(opt & 0xff);
	if (sock_fastwrite(s, io_data, 3) == 0)
		return (1);		/* failed to write */
	if (kdebug != 0)
		{
		outs("Opt send ");
		optdebug(cmd, opt);
		if (cmd != SB) outs("\r\n");
		}

	tcp_tick(s);
	return (0);
}

/*
 * Process in-band Telnet negotiation characters from the remote host.
 * Call with the telnet IAC character and the current duplex setting
 * (0 = remote echo, 1 = local echo).
 * Returns:
 *  -1 on success or char 0x255 (IAC) when IAC is the first char read here.
 */

int 
tn_doop(word ch)
{					/* enter after reading IAC char */
    register int c, x;

    	if (ch < SB) return(0);		/* ch is not in range of Options */

	if ((x = ttinc()) == -1)	/* read Option character */
		return (-1);		/* nothing there */
	x &= 0xff;
	c = ch;				/* use register'd character */

	if (kdebug != 0)
		{
		outs("Opt recv ");
		optdebug((byte)c, x);
		if (c != SB) outs("\r\n");
		}

    switch (x) {
      case TELOPT_ECHO:                 /* ECHO negotiation */
	if (c == WILL)			/* Host says it will echo */
		{
		if (echo != 0)		/* reply only if change required */
			{
			send_iac(DO,x);	/* Please do */
			kecho(echo = 0); /* echo is from the other side */
			}
		break;
		}

        if (c == WONT)			/* Host says it won't echo */
		{
		if (echo == 0)			/* If we not echoing now */
			{
			send_iac(DONT,x);	/* agree to no host echo */
			kecho(echo = 1); 	/* do local echoing */
			}
		break;
		}

	if (c == DO)
		{			/* Host wants me to echo to it */
		send_iac(WONT,x);	/* I say I won't */
		break;
        	}
	break;				/* do not respond to DONT */

      case TELOPT_SGA:                  /* Suppress Go-Ahead */
	if (c == WONT)			/* Host says it won't sup go-aheads */
		{
		if (sgaflg == 0)
			send_iac(DONT, x);	/* acknowledge */
		sgaflg = 1;			/* suppress, remember */
		if (echo == 0)			/* if we're not echoing, */
			kecho(echo = 1);	/* switch to local echo */
		break;
		}

        if (c == WILL)			/* Host says it will use go aheads */
		{
		if (sgaflg || !dosga)		/* ACK only if necessary */
			{
			sgaflg = 0;		/* do go-aheads, remember */
			send_iac(DO,x);		/* this is a change, so ACK */
            		}
		break;
	        }
	break;				/* no response to other cases */

      case TELOPT_TTYPE:                /* Terminal Type */
        switch (c) {
          case DO:                      /* DO terminal type */
	    if (wttflg == 0) {		/* If I haven't said so before, */
		send_iac(WILL, x);	/* say I'll send it if asked */
		wttflg++;
	    }
		break;

          case SB:			/* enter subnegotiations */
	    if (wttflg == 0)
	    	break;			/* we have not been introduced yet */
	    if (subnegotiate() != 0)	/* successful negotiation */
		tn_sttyp();		/* report terminal type */
	    break;
	    	
          default:                      /* ignore other TTYPE Options */
		break;
        }				/* end of inner switch (c) */
	break;

	case TELOPT_NAWS:		/* terminal width and height */
        switch (c) {
          case DO:                      /* DO terminal type */
	    if (wnawsflg == 0) {	/* If I haven't said so before, */
		send_iac(WILL, x);	/* say I'll send it if asked */
		wnawsflg++;
	    }
		break;

          case SB:			/* enter subnegotiations */
	    if (wnawsflg == 0)
	    	break;			/* we have not been introduced yet */
	    if (subnegotiate() != 0)	/* successful negotiation */
		tn_snaws();		/* report screen size */
	    break;

          default:                      /* ignore other NAWS Options */
		break;
        }				/* end of inner switch (c) */
	break;


      default:				/* all other Options: refuse nicely */
	switch(c) {
          case WILL:                    /* You will? */
		send_iac(DONT,x);	/* Please don't */
		break;
          case DO:                      /* You want me to? */
		send_iac(WONT,x);	/* I won't */
		break;

          case DONT:
		send_iac(WONT,x);	/* I won't */
		break;

          case WONT:                    /* You won't? */
		break;			/* Good */

	  default:
	  	break;			/* unknown character, discard */
          }				/* end of default switch (c) */
        break;
    }					/* end switch (x) */
    return (-1);			/* say done with Telnet Options */
}

/* Perform Telnet Option subnegotiation. SB byte has been read. Consume
   through IAC SE. Return 1 if successful, else 0.
*/
int
subnegotiate(void)
{
	register word flag, y;
 	word n;

            n = flag = 0;               /* flag for when done reading SB */
            while (n < TSBUFSIZ)
	    	{			/* loop looking for IAC SE */
                if ((y = ttinc()) == -1)
			break;		/* nothing there */
		y &= 0xff;              /* make sure it's just 8 bits */
		sb[n++] = (byte) y;	/* save what we got in buffer */

		if (kdebug != 0)
			{
			if (y == SE) outs(" se\r\n");
			else
			if (y != IAC)
				{
				if (n == 1 && y == 1)
					outs(" send");
				else
					{
					outs(" \\x"); 
					outhex((byte)y);
					}
				}
			}

		if (y == IAC) 		/* If this is an IAC */
		    {
		    if (flag)		/* If previous char was IAC */
		    	{
			n--;		/* it's quoted, keep one IAC */
			flag = 0;	/* and turn off the flag. */
			}
		    else flag = 1;	/* Otherwise set the flag. */
		    }
		else if (flag)  	/* Something else following IAC */
			{
		    if (y != SE)	/* If not SE, it's a protocol error */
		      flag = 0;
		    break;
                	}		/* end of if (y == IAC) */
		}			/* end while */

	    if (flag == 0 || y == -1)	/* no option IAC SE */
	       return (0);		/* flag == 0 is invalid SB */

	    if ( *sb == 1 )		/* wants us to report option */
		return (1);		/* say can do report */
	    else
	    	return (0);
}

/* Telnet send terminal type */
/* Returns -1 on error, 0 on success */

int 
tn_sttyp(void)
{                            /* Send telnet terminal type. */
    register byte *ttn;
    register int ttnl;		/* Name & length of terminal type. */

    ttn = termtype;		/* we already got this from environment */
    if ((*ttn == 0) || ((ttnl = strlen(ttn)) >= TSBUFSIZ)) {
        ttn = "UNKNOWN";
        ttnl = 7;
    }

    ttn = strcpy(&sb[1], ttn);		/* Copy to subnegotiation buffer */
	while (*ttn != NULL)
		{
		if (*ttn >= 'a' && *ttn <= 'z') *ttn += (byte)('A' - 'a');
		ttn++;
		}
    *sb    = 0;				/* 'is'... */
    *ttn++ = IAC;
    *ttn   = SE;

    send_iac((byte)SB, TELOPT_TTYPE);	/* Send: Terminal Type */
    sock_flushnext(s);			/* send on next write */
    sock_fastwrite(s, sb, ttnl + 3);
    if (kdebug != 0)
    	{
	int i;
	outs(" "); for (i = 0; i < ttnl; i++) outch(sb[i+1]);
	outs(" se\r\n");
	}
    return (0);
}

/* Send terminal width and height (characters). RFC 1073 */
int
tn_snaws(void)
{
	char sbuf[6] = {0,0, 0,0, (char)IAC, (char)SE};

	sbuf[1] = kterm_cols;
	sbuf[3] = kterm_lines;
	send_iac((byte)SB, TELOPT_NAWS);	/* Send: Terminal size */
	sock_flushnext(s);			/* send on next write */
	sock_fastwrite(s, sbuf, 6);
    	if (kdebug != 0)
    		{
		outs(" \\x"); 
		outhex(sbuf[1]);
		outs(" \\x"); 
		outhex(sbuf[3]);
		outs(" se\r\n");
		}
	return (0);
}

/* assist displaying of Telnet Options negotiation material */
void
optdebug(byte cmd, int option)
{
	switch (cmd)
		{
		case WILL: outs("will ");
				break;
		case WONT: outs("wont ");
				break;
		case DO: outs("do ");
				break;
		case DONT: outs("dont ");
				break;
		case SB: outs("sb ");
				break;
		default: outs("\\x "); outhex(cmd);
				break;
		}			/* end of switch c */
	switch (option)
		{
		case TELOPT_ECHO: outs("echo");
				break;
		case TELOPT_SGA: outs("sga");
				break;
		case TELOPT_TTYPE: outs("ttype");
				break;
		case TELOPT_NAWS: outs("naws");
				break;
		default: outs("\\x"); outhex((byte)option);
				break;
		}
}


/* Compose a nice greeting message for incoming Telnet connections. Called
   by tcp_handler() in the tcp_StateLISTEN section. It also notifies the
   local terminal emulator of the client's presence and address. */

void
server_hello(tcp_Socket *s)
{
	char hellomsg[MSGBUFLEN];		/* work buffer, keep short */
	register int len;

	strcpy(hellomsg,
		"\r\n Welcome to the MS-DOS Kermit Telnet server at [");
	ntoa(&hellomsg[strlen(hellomsg)], my_ip_addr);		/* our IP */
	strcat(hellomsg, "].\r\n");		/* as [dotted decimal] */
	if (kserver != 0)			/* if file serving */
		{
		strcat(hellomsg," Escape back to your Kermit prompt and");
		strcat(hellomsg," issue Kermit file server commands.\r\n\n");
		}
	else					/* if terminal emulating */
		{
		strcat(hellomsg,
			" You are talking to the terminal emulator,\r\n");
		strcat(hellomsg, " adjust local echoing accordingly.\r\n");
		}

		/* stuff string in socket buffer, adjust socket data length */
	bcopyff(hellomsg, &s->sdata[s->sdatalen], 
				len = strlen(hellomsg));
	s->sdatalen += len;
				/* tell main body the news */
	strcpy(hellomsg, "\r\n Connection starting from [");
	ntoa(&hellomsg[strlen(hellomsg)], s->hisaddr);	/* their IP */
	strcat(hellomsg, "].\r\n");
	outs(hellomsg);		/* send connection info to main body */
}
	
