/*
 *	Copyright 1984, 1985 by the Regents of the University of
 *	California and by Gregory Glenn Minshall.
 *
 *	Permission to use, copy, modify, and distribute these
 *	programs and their documentation for any purpose and
 *	without fee is hereby granted, provided that this
 *	copyright and permission appear on all copies and
 *	supporting documentation, the name of the Regents of
 *	the University of California not be used in advertising
 *	or publicity pertaining to distribution of the programs
 *	without specific prior permission, and notice be given in
 *	supporting documentation that copying and distribution is
 *	by permission of the Regents of the University of California
 *	and by Gregory Glenn Minshall.  Neither the Regents of the
 *	University of California nor Gregory Glenn Minshall make
 *	representations about the suitability of this software
 *	for any purpose.  It is provided "as is" without
 *	express or implied warranty.
 */


#if	defined(DOSCCS) && !defined(lint)
static char sccsid[] = "@(#)tn3270.c	2.8\t1/1/94";
#endif

/*
 * User telnet program, specially modified for tn3270.
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/time.h>

#include <netinet/in.h>
#include <arpa/inet.h>

#define	TELOPTS
#include <arpa/telnet.h>

#include <stdio.h>
#include <ctype.h>
#include <errno.h>
#include <signal.h>
#include <setjmp.h>
#include <netdb.h>

#define	strip(x)	((x)&0177)
#define min(x,y)	((x<y)? x:y)

#ifndef pdp11
#define IBUFSIZ	8*BUFSIZ
static char	Ibuf[IBUFSIZ], *Ifrontp = Ibuf, *Ibackp = Ibuf;
#else
	/* Move Ibuf onto stack to use last segment. */
#define IBUFSIZ	3*BUFSIZ
static char	*Ibuf, *Ifrontp, *Ibackp;
#endif
static char	ttyobuf[BUFSIZ], *tfrontp = ttyobuf, *tbackp = ttyobuf;
static char	netobuf[BUFSIZ], *nfrontp = netobuf, *nbackp = netobuf;

static char	SbBuffer[100], *pSb = SbBuffer;
#define Sb_Option SbBuffer[0]
#define Sb_Command SbBuffer[1]


static char	hisopts[256];
static char	myopts[256];

static char	doopt[] = { IAC, DO, '%', 'c', 0 };
static char	dont[] = { IAC, DONT, '%', 'c', 0 };
static char	will[] = { IAC, WILL, '%', 'c', 0 };
static char	wont[] = { IAC, WONT, '%', 'c', 0 };
static char	sb_terminal[] = { IAC, SB,
			TELOPT_TTYPE, TELQUAL_IS,
			'I', 'B', 'M', '-', '3', '2', '7', '7', '-', '2',
			IAC, SE };

/* The following is a real, live, global. */

/* The point of HaveInput is to give a hint to the terminal output processor
 * that some input from some source (network or terminal) has come in.
 */

int HaveInput = 1;	/* we have received input in the not too distant past */


static int	connected;
static int	SentTerminalType = 0;		/* returned sb_terminal to other? */
static int	In3270 = 0;			/* we are in 3270 binary mode */
static int	ISend = 0;			/* trying to send network data in */
static int	ForceMode = -1;			/* for debugging */
static int	net;
static int	showoptions = 0;
static int	debug = 0;
static int	crmod = 0;
static int	printnet = 0;
static FILE	*NetTrace;
static char	*prompt;
static char	escape = CTRL(]);

static char	line[200];
static int	margc;
static char	*margv[20];

static jmp_buf	toplevel;
static jmp_buf	peerdied;

extern	int errno;

int	quit(), suspend();
static int	tn(), bye(), help();
static int	setescape(), status(), toggle(), setoptions();
static int	setcrmod(), setdebug(), SetPrintNet();

#define HELPINDENT (sizeof ("connect"))

struct cmd {
	char	*name;		/* command name */
	char	*help;		/* help string */
	int	(*handler)();	/* routine which executes command */
	int	dohelp;		/* Should we give general help information? */
};

static char	openhelp[] =	"connect to a site";
static char	closehelp[] =	"close current connection";
static char	quithelp[] =	"exit telnet";
static char	zhelp[] =	"suspend telnet";
static char	debughelp[] =	"toggle debugging";
static char	escapehelp[] =	"set escape character";
static char	statushelp[] =	"print status information";
static char	helphelp[] =	"print help information";
static char	optionshelp[] =	"toggle viewing of options processing";
static char	crmodhelp[] =	"toggle mapping of received carriage returns";
static char	printnethelp[] = "print out raw data to/from net";

static struct cmd cmdtab[] = {
	{ "open",	openhelp,	tn, 1 },
	{ "close",	closehelp,	bye, 1 },
	{ "quit",	quithelp,	quit, 1 },
	{ "z",		zhelp,		suspend, 1 },
	{ "suspend",	zhelp,		suspend, 0 },
	{ "escape",	escapehelp,	setescape, 0 },
	{ "status",	statushelp,	status, 1 },
	{ "options",	optionshelp,	setoptions, 0 },
	{ "crmod",	crmodhelp,	setcrmod, 0 },
	{ "debug",	debughelp,	setdebug, 0 },
	{ "printnet",	printnethelp,	SetPrintNet, 0 },
	{ "?",		helphelp,	help, 1 },
	{ "help",	helphelp,	help, 0 },
	0
};

static struct sockaddr_in sin;

static int	intr(), deadpeer(), inputAvailable();
static char	*control();
static struct	cmd *getcmd();
static struct	servent *sp;

static struct	tchars otc;
static struct	ltchars oltc;
static struct	sgttyb ottyb;

main(argc, argv)
	int argc;
	char *argv[];
{
#ifdef pdp11
	char	SIbuf[IBUFSIZ];

	Ibuf = Ifrontp = Ibackp = SIbuf;
	setbuf(stdout, NULL);
#endif
	ioctl(0, TIOCGETP, (char *)&ottyb);
	ioctl(0, TIOCGETC, (char *)&otc);
	ioctl(0, TIOCGLTC, (char *)&oltc);
	sp = getservbyname("telnet", "tcp");
	if (sp == 0) {
		ExitString(stderr, "telnet: tcp/telnet: unknown service\n", 1);
	}
	NetTrace = stdout;
	prompt = argv[0];
	if (argc > 1 && !strcmp(argv[1], "-d")) {
		debug = SO_DEBUG, argv++, argc--;
	}
	if (argc > 1 && !strcmp(argv[1], "-n")) {
	    argv++;
	    argc--;
	    if (argc > 1) {		/* get file name */
		NetTrace = fopen(argv[1], "w");
		argv++;
		argc--;
		if (NetTrace == NULL) {
		    NetTrace = stdout;
		}
	    }
	}
	if (argc != 1) {
		if (setjmp(toplevel) != 0)
			Exit(0);
		tn(argc, argv);
	}
	setjmp(toplevel);
	for (;;)
		command(1);
}

static char	*hostname;
static char	hnamebuf[32];

static
tn(argc, argv)
	int argc;
	char *argv[];
{
	struct hostent *host;
	char *strcpy();

	if (connected) {
		printf("?Already connected to %s\n", hostname);
		return;
	}
	if (argc < 2) {
		(void) strcpy(line, "Connect ");
		printf("(to) ");
		gets(&line[strlen(line)]);
		makeargv();
		argc = margc;
		argv = margv;
	}
	if (argc > 3) {
		printf("usage: %s host-name [port]\n", argv[0]);
		return;
	}
	host = gethostbyname(argv[1]);
	if (host) {
		sin.sin_family = host->h_addrtype;
		bcopy(host->h_addr, (caddr_t)&sin.sin_addr, host->h_length);
		hostname = host->h_name;
	} else {
		sin.sin_family = AF_INET;
		sin.sin_addr.s_addr = inet_addr(argv[1]);
		if (sin.sin_addr.s_addr == -1) {
			printf("%s: unknown host\n", argv[1]);
			return;
		}
		(void) strcpy(hnamebuf, argv[1]);
		hostname = hnamebuf;
	}
	sin.sin_port = sp->s_port;
	if (argc == 3) {
		sin.sin_port = atoi(argv[2]);
		sin.sin_port = htons(sin.sin_port);
	}
	net = socket(AF_INET, SOCK_STREAM, 0);
	if (net < 0) {
		perror("telnet: socket");
		return;
	}
	if (debug && setsockopt(net, SOL_SOCKET, SO_DEBUG, 0, 0) < 0)
		perror("setsockopt (SO_DEBUG)");
	signal(SIGINT, intr);
	signal(SIGPIPE, deadpeer);
	printf("Trying...\n");
	if (connect(net, (struct sockaddr *)&sin, sizeof (sin)) < 0) {
		perror("telnet: connect");
		signal(SIGINT, SIG_DFL);
		return;
	}
	connected++;
	call(status, "status", 0);
	if (setjmp(peerdied) == 0)
		telnet();
	if (In3270) {
	    Stop3270(1);
	}
	ExitString(stderr, "Connection closed by foreign host.\n", 1);
}

/*
 * Print status about the connection.
 */
/*VARARGS*/
static
status()
{
	if (connected)
		printf("Connected to %s.\n", hostname);
	else
		printf("No connection.\n");
	/*printf("Escape character is '%s'.\n", control(escape));*/
	fflush(stdout);
}

static
makeargv()
{
	register char *cp;
	register char **argp = margv;

	margc = 0;
	for (cp = line; *cp;) {
		while (isspace(*cp))
			cp++;
		if (*cp == '\0')
			break;
		*argp++ = cp;
		margc += 1;
		while (*cp != '\0' && !isspace(*cp))
			cp++;
		if (*cp == '\0')
			break;
		*cp++ = '\0';
	}
	*argp++ = 0;
}

/*VARARGS*/
suspend()
{
	register int save;

	save = mode(0);
	kill(0, SIGTSTP);
	/* reget parameters in case they were changed */
	ioctl(0, TIOCGETP, (char *)&ottyb);
	ioctl(0, TIOCGETC, (char *)&otc);
	ioctl(0, TIOCGLTC, (char *)&oltc);
	(void) mode(save);
}

/*VARARGS*/
static
bye()
{
	register char *op;

	(void) mode(0);
	if (connected) {
		shutdown(net, 2);
		printf("Connection closed.\n");
		close(net);
		connected = 0;
		/* reset his options */
		for (op = hisopts; op < &hisopts[256]; op++)
			*op = 0;
	}
}

/*VARARGS*/
quit()
{
	call(bye, "bye", 0);
	Exit(0);
}

/*
 * Help command.
 */
static
help(argc, argv)
	int argc;
	char *argv[];
{
	register struct cmd *c;

	if (argc == 1) {
		printf("Commands may be abbreviated.  Commands are:\n\n");
		for (c = cmdtab; c->name; c++)
			if (c->dohelp) {
			    printf("%-*s\t%s\n", HELPINDENT, c->name, c->help);
			}
		return;
	}
	while (--argc > 0) {
		register char *arg;
		arg = *++argv;
		c = getcmd(arg);
		if (c == (struct cmd *)-1)
			printf("?Ambiguous help command %s\n", arg);
		else if (c == (struct cmd *)0)
			printf("?Invalid help command %s\n", arg);
		else
			printf("%s\n", c->help);
	}
}

/*
 * Call routine with argc, argv set from args (terminated by 0).
 * VARARGS2
 */
static
call(routine, args)
	int (*routine)();
	int args;
{
	register int *argp;
	register int argc;

	for (argc = 0, argp = &args; *argp++ != 0; argc++)
		;
	(*routine)(argc, &args);
}

static struct	tchars notc =	{ -1, -1, -1, -1, -1, -1 };
static struct	ltchars noltc =	{ -1, -1, -1, -1, -1, -1 };

mode(f)
	register int f;
{
	static int prevmode = 0;
	struct tchars *tc, tc3;
	struct ltchars *ltc;
	struct sgttyb sb;
	int onoff, old;

	if (prevmode == f)
		return (f);
	old = prevmode;
	prevmode = f;
	sb = ottyb;
	if (ForceMode != -1) {
		f = ForceMode;
		ForceMode = -1;
	}
	switch (f) {

	case 0:
		onoff = 0;
		tc = &otc;
		ltc = &oltc;
		break;

	case 1:		/* the rawest */
	case 2:		/* allows for local echoing, newline mapping */
	case 3:		/* like 1, but with XON/XOFF */

		sb.sg_flags |= CBREAK;
		if ((f == 1) || (f == 3)) {
		    sb.sg_flags &= ~(ECHO|CRMOD);
		} else {
		    sb.sg_flags |= ECHO|CRMOD;
		}
		sb.sg_erase = sb.sg_kill = -1;
		if (f == 3) {
		    tc = &tc3;
		    tc3 = notc;
			/* get XON, XOFF characters */
		    tc3.t_startc = otc.t_startc;
		    tc3.t_stopc = otc.t_stopc;
		} else {
		    tc = &notc;
		}
		ltc = &noltc;
		onoff = 1;
		break;

	default:
		return(old);
	}
	ioctl(fileno(stdin), TIOCSLTC, (char *)ltc);
	ioctl(fileno(stdin), TIOCSETC, (char *)tc);
	ioctl(fileno(stdin), TIOCSETP, (char *)&sb);
	ioctl(fileno(stdin), FIONBIO, (char *)&onoff);
	ioctl(fileno(stdout), FIONBIO, (char *)&onoff);
	ioctl(fileno(stdin), FIOASYNC, (char *)&onoff);
	return (old);
}

static char	sibuf[BUFSIZ], *sbp;
static char	tibuf[BUFSIZ], *tbp;
static int	scc, tcc;
static int tin, tout;			/* file descriptors */

/*
 * Select from tty and network...
 */
static
telnet()
{
    int on = 1;
    int negativePid = -getpid();
    int schedValue;

    (void) mode(2);
    ioctl(net, FIONBIO, (char *)&on);
    ioctl(net, FIOASYNC, (char *)&on);	/* hear about input */
    ioctl(net, SIOCSPGRP, (char *)&negativePid);	/* set my pid */
    tin = fileno(stdin);
    tout = fileno(stdout);

    for (;;) {
	while (schedValue = Scheduler(0)) {
	    if (schedValue == -1) {
		(void) mode(0);
		return;
	    }
	}
		/* If there is data waiting to go out to terminal, don't
		 * schedule any more data for the terminal.
		 */
	if (tfrontp-tbackp || !In3270) {
	    schedValue = 1;
	} else {
	    schedValue = DoTerminalOutput();
	}
	if (schedValue) {
	    if (Scheduler(1) == -1) {
		(void) mode(0);
		return;
	    }
	}
    }
}


/* Loop around once. */

static
Scheduler(block)
int block;		/* should we block in the select? */
{
    register int c;
    fd_set ibits, obits;
		/* One wants to be a bit careful about setting returnValue
		 * to one, since a one implies we did some useful work,
		 * and therefore probably won't be called to block next
		 * time.
		 */
    int returnValue = 0;
    static struct timeval TimeValue = {0};

    FD_ZERO(&ibits);
    FD_ZERO(&obits);

    if (!In3270) {
	if (nfrontp - nbackp)
		FD_SET(net, &obits);
	else if (tcc == 0) {
		FD_SET(tin, &ibits);
	}
	if (tfrontp - tbackp)
		FD_SET(tout, &obits);
	else if (!ISend)
		FD_SET(net, &ibits);
    } else {
	if (nfrontp - nbackp) {	/* something for network? */
	    FD_SET(net, &obits);	/* yes - wait for space */
	}
	if (tcc == 0) {		/* any pending tty input? */
	    FD_SET(tin, &ibits);	/* no, look for new input */
	}
	if (tfrontp-tbackp) {	/* any pending tty output? */
	    FD_SET(tout, &obits);	/* yes - wait for space */
	}
	if (!ISend) {		/* any pending net input? */
	    FD_SET(net, &ibits);	/* no, look for new input */
	}
    }
    if (scc < 0 && tcc < 0) {
	    return(-1);
    }
    if (HaveInput) {		/* Reprime SIGIO handler if appropriate */
	HaveInput = 0;
	signal(SIGIO, inputAvailable);
    }
    select(16, &ibits, &obits, (fd_set *) 0,
				(block)? (struct timeval *)0:&TimeValue);

#define FD_NEXT(p) (ffs((p)->fds_bits[0])-1)   /* Until there's a better way */

    if (FD_NEXT(&ibits) < 0 && FD_NEXT(&obits) < 0 && block) {
		    /* I don't like this, does it ever happen? */
	    printf("sleep(5) from tn3270, after select\n");
	    sleep(5);
	    return(0);
    }

    /*
     * Something to read from the network...
     */
    if (FD_ISSET(net, &ibits)) {
	    scc = read(net, sibuf, sizeof (sibuf));
	    if (scc < 0 && errno == EWOULDBLOCK)
		    scc = 0;
	    else {
		    if (scc <= 0)
			    return(-1);
		    sbp = sibuf;
		    if (printnet) {
			    Dump('<', sbp, scc);
		    }
		    returnValue = 1;		/* did something useful */
	    }
    }

    /*
     * Something to read from the tty...
     */
    if (FD_ISSET(tin, &ibits)) {
	    tcc = read(tin, tibuf, sizeof tibuf);
	    if (tcc < 0 && errno == EWOULDBLOCK)
		    tcc = 0;
	    else {
		    if (tcc <= 0)
			    return(-1);
		    tbp = tibuf;
		    returnValue = 1;		/* did something useful */
	    }
    }

    if (tcc > 0) {
	if (In3270) {
	    c = DataFromTerminal(tbp, tcc);
	    if (c) {
		returnValue = 1;		/* did something useful */
	    }
	    tcc -= c;
	    tbp += c;
	} else {
	    returnValue = 1;		/* did something useful */
	    while (tcc > 0) {
		if ((&netobuf[BUFSIZ] - nfrontp) < 2)
			break;
		c = *tbp++ & 0377, tcc--;
		if (strip(c) == escape) {
			command(0);
			tcc = 0;
			break;
		}
		if (c == IAC)
			*nfrontp++ = c;
		*nfrontp++ = c;
	    }
	}
    }
    if (FD_ISSET(net, &obits) && (c = (int) (nfrontp - nbackp)) > 0) {
	netflush();
	if (c != (int) (nfrontp-nbackp)) {
	    returnValue = 1;
	}
    }
    if (scc > 0) {
	if (Ifrontp+scc >= Ibuf+IBUFSIZ) {
	    if (Ibackp != Ibuf) {	/* do some copying */
		bcopy(Ibackp, Ibuf, Ifrontp-Ibackp);
		Ifrontp -= (Ibackp-Ibuf);
		Ibackp = Ibuf;
	    }
	}
	if (Ifrontp+scc < Ibuf+IBUFSIZ) {
	    returnValue = 1;		/* doing something useful */
	    telrcv();
	}		/* Else - we may never recover */
    }
    if (FD_ISSET(tout, &obits) && (c = (int) (tfrontp - tbackp)) > 0) {
	ttyflush();
	if (c != (int) (tfrontp-tbackp)) {
	    returnValue = 1;
	}
    }
    if (In3270 && (c = (int) (Ifrontp-Ibackp))) {
	    Ibackp += DataFromNetwork(Ibackp, Ifrontp-Ibackp, ISend);
	    if (c != (int) (Ifrontp-Ibackp)) {
		returnValue = 1;
	    }
	    if (Ibackp == Ifrontp) {
		    Ibackp = Ifrontp = Ibuf;
		    ISend = 0;	/* take data from network */
	    }
    }
    return(returnValue);				/* good return */
}

command(top)
	int top;
{
	register struct cmd *c;
	int oldmode;

	oldmode = mode(0);
	if (!top)
		putchar('\n');
	else
		signal(SIGINT, SIG_DFL);
	for (;;) {
		printf("%s> ", prompt);
		if (gets(line) == 0) {
			if (feof(stdin)) {
				clearerr(stdin);
				putchar('\n');
			}
			break;
		}
		if (line[0] == 0)
			break;
		makeargv();
		c = getcmd(margv[0]);
		if (c == (struct cmd *)-1) {
			printf("?Ambiguous command\n");
			continue;
		}
		if (c == 0) {
			printf("?Invalid command\n");
			continue;
		}
		(*c->handler)(margc, margv);
		if (c->handler != help)
			break;
	}
	if (!top) {
		if (!connected)
			longjmp(toplevel, 1);
		(void) mode(oldmode);
	}
}

/*
 * Telnet receiver states for fsm
 */
#define	TS_DATA		0
#define	TS_IAC		1
#define	TS_WILL		2
#define	TS_WONT		3
#define	TS_DO		4
#define	TS_DONT		5
#define TS_SB		6	/* in sub-negotiation */
#define	TS_SE		7	/* coming out of sub-negotiation */

#define SB_ACCUM(c)	{*pSb = c;	/* accumulate character */ \
			if (pSb >= SbBuffer+sizeof (SbBuffer)) { \
				/* can't accept any more */ \
				pSb = SbBuffer; \
			} \
			pSb++;}

static
telrcv()
{
	register int c;
	register char *Sbp;
	register int Scc;
	static int state = TS_DATA;

	while (scc > 0) {
		c = *sbp++ & 0377, scc--;
		switch (state) {

		case TS_DATA:
			if (c == IAC) {
			    state = TS_IAC;
			    continue;
			}
				/* We optimize this loop, since it is
				 * where we spend 99% of this routine.
				 */
			if (In3270) {
			    *Ifrontp++ = c;
			    Sbp = sbp;
			    Scc = scc;
			    while (Scc > 0) {
				c = *Sbp++ & 0377, Scc--;
				if (c == IAC) {
				    state = TS_IAC;
				    break;
				}
				*Ifrontp++ = c;
			    }
			    sbp = Sbp;
			    scc = Scc;
			} else {
			    *tfrontp++ = c;
			    /*
			     * This hack is needed since we can't set
			     * CRMOD on output only.  Machines like MULTICS
			     * like to send \r without \n; since we must
			     * turn off CRMOD to get proper input, the mapping
			     * is done here (sigh).
			     */
			    if (c == '\r' && crmod && !In3270)
				    *tfrontp++ = '\n';
			}
			continue;


		case TS_IAC:
			switch (c) {
			
			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 DM:
				outputPurge();
				break;

			case NOP:
			case GA:
				break;

			case SB:
				state = TS_SB;
				pSb = SbBuffer;	/* where to collect */
				continue;

			case EOR:
				if (In3270) {
				    Ibackp += DataFromNetwork(Ibackp,
						Ifrontp-Ibackp, 1);
				    if (Ibackp == Ifrontp) {
					Ibackp = Ifrontp = Ibuf;
					ISend = 0;	/* should have been! */
				    } else {
					ISend = 1;
				    }
				}
				break;

			case IAC:
				if (In3270) {
				    *Ifrontp++ = IAC;
				} else {
				    *tfrontp++ = IAC;
				}
				break;

			default:
				break;
			}
			state = TS_DATA;
			continue;

		case TS_WILL:
			printoption("RCVD", will, c, !hisopts[c]);
			if (!hisopts[c])
				willoption(c);
			state = TS_DATA;
			continue;

		case TS_WONT:
			printoption("RCVD", wont, c, hisopts[c]);
			if (hisopts[c])
				wontoption(c);
			state = TS_DATA;
			continue;

		case TS_DO:
			printoption("RCVD", doopt, c, !myopts[c]);
			if (!myopts[c])
				dooption(c);
			state = TS_DATA;
			continue;

		case TS_DONT:
			printoption("RCVD", dont, c, myopts[c]);
			if (myopts[c]) {
				myopts[c] = 0;
				if (c == TELOPT_BINARY) {
				    SetIn3270();
				}
				sprintf(nfrontp, wont, c);
				nfrontp += sizeof (wont) - 2;
				printoption("SENT", wont, c);
			}
			state = TS_DATA;
			continue;
		case TS_SB:
			if (c == IAC) {
				state = TS_SE;
				continue;
			}
			SB_ACCUM(c);
			continue;
		case TS_SE:
		    if (c != SE) {
			if (c != IAC) {
			    SB_ACCUM(IAC);
			}
			SB_ACCUM(c);
			state = TS_SB;
		    } else {
			/* this is the end of the sub negotiation */
			/* we only allow a termtype, send, sub */
			if ((Sb_Option != TELOPT_TTYPE) ||
				(Sb_Command != TELQUAL_SEND)) {
			    /* what to do? XXX */
			} else {
			    /* send our type */
			    SentTerminalType = 1;
			    SetIn3270();
			    bcopy(sb_terminal, nfrontp, sizeof sb_terminal);
			    nfrontp += sizeof sb_terminal;
			    printoption("SENT", sb_terminal,
					TELOPT_TTYPE);
			}
			state = TS_DATA;
		    }
		}
	}
}

static
willoption(option)
	int option;
{
	char *fmt;

	switch (option) {

	case TELOPT_ECHO:
		(void) mode(1);

	case TELOPT_BINARY:
		hisopts[option] = 1;
		SetIn3270();
		fmt = doopt;
		break;

	case TELOPT_EOR:
	case TELOPT_SGA:
		hisopts[option] = 1;
		fmt = doopt;
		break;

	case TELOPT_TM:
		fmt = dont;
		break;

	default:
		fmt = dont;
		break;
	}
	sprintf(nfrontp, fmt, option);
	nfrontp += sizeof (dont) - 2;
	printoption("SENT", fmt, option);
}

static
wontoption(option)
	int option;
{
	char *fmt;

	switch (option) {

	case TELOPT_BINARY:
		hisopts[option] = 0;
		SetIn3270();
		fmt = doopt;
		break;

	case TELOPT_ECHO:
		(void) mode(2);

	case TELOPT_SGA:
		hisopts[option] = 0;
		fmt = dont;
		break;

	default:
		fmt = dont;
	}
	sprintf(nfrontp, fmt, option);
	nfrontp += sizeof (doopt) - 2;
	printoption("SENT", fmt, option);
}

static
dooption(option)
	int option;
{
	char *fmt;

	switch (option) {

	case TELOPT_TTYPE:
	case TELOPT_BINARY:
		myopts[option] = 1;
		SetIn3270();
		fmt = will;
		break;

	case TELOPT_TM:
		fmt = wont;
		break;

	case TELOPT_ECHO:
		(void) mode(2);
		fmt = will;
		hisopts[option] = 0;
		break;

	case TELOPT_EOR:
	case TELOPT_SGA:
		fmt = will;
		break;

	default:
		fmt = wont;
		break;
	}
	sprintf(nfrontp, fmt, option);
	nfrontp += (sizeof dont)-2;
	printoption("SENT", fmt, option);
}

static
SetIn3270()
{
    if (SentTerminalType && myopts[TELOPT_BINARY] && hisopts[TELOPT_BINARY]) {
	if (!In3270) {
	    In3270 = 1;
	    OptInit();		/* initialize mappings */
	    /* initialize terminal key mapping */
	    (void) DataFromTerminal(ttyobuf, 0);
	    (void) mode(3);
	}
    } else {
	if (In3270) {
	    Stop3270(1);
	    In3270 = 0;
	    (void) mode(2);
	}
    }
}

/*
 * Set the escape character.
 */
static
setescape(argc, argv)
	int argc;
	char *argv[];
{
	register char *arg;
	char buf[50];

	if (argc > 2)
		arg = argv[1];
	else {
		printf("new escape character: ");
		gets(buf);
		arg = buf;
	}
	if (arg[0] != '\0')
		escape = arg[0];
	printf("Escape character is '%s'.\n", control(escape));
	fflush(stdout);
}

/*VARARGS*/
static
setoptions()
{

	showoptions = !showoptions;
	printf("%s show option processing.\n", showoptions ? "Will" : "Wont");
	fflush(stdout);
}

/*VARARGS*/
static
setcrmod()
{

	crmod = !crmod;
	printf("%s map carriage return on output.\n", crmod ? "Will" : "Wont");
	fflush(stdout);
}

/*VARARGS*/
static
setdebug()
{

	debug = !debug;
	printf("%s turn on socket level debugging.\n",
		debug ? "Will" : "Wont");
	fflush(stdout);
	if (debug && net > 0 && setsockopt(net, SOL_SOCKET, SO_DEBUG, 0, 0) < 0)
		perror("setsockopt (SO_DEBUG)");
}

/*VARARGS*/
static
SetPrintNet()
{

	printnet = !printnet;
	printf("%s turn on printing of raw network traffic.\n",
		printnet ? "Will" : "Wont");
}

/*
 * Construct a control character sequence
 * for a special character.
 */
static char *
control(c)
	register int c;
{
	static char buf[3];

	if (c == 0177)
		return ("^?");
	if (c >= 040) {
		buf[0] = c;
		buf[1] = 0;
	} else {
		buf[0] = '^';
		buf[1] = '@'+c;
		buf[2] = 0;
	}
	return (buf);
}

static struct cmd *
getcmd(name)
	register char *name;
{
	register char *p, *q;
	register struct cmd *c, *found;
	register int nmatches, longest;

	longest = 0;
	nmatches = 0;
	found = 0;
	for (c = cmdtab; p = c->name; c++) {
		for (q = name; *q == *p++; q++)
			if (*q == 0)		/* exact match? */
				return (c);
		if (!*q) {			/* the name was a prefix */
			if (q - name > longest) {
				longest = q - name;
				nmatches = 1;
				found = c;
			} else if (q - name == longest)
				nmatches++;
		}
	}
	if (nmatches > 1)
		return ((struct cmd *)-1);
	return (found);
}

static
deadpeer()
{
	(void) mode(0);
	longjmp(peerdied, -1);
}

static
intr()
{
	(void) mode(0);
	longjmp(toplevel, -1);
}

static
inputAvailable()
{
    HaveInput = 1;
}

/* outputPurge() - get rid of all output destined for terminal. */
outputPurge()
{
    ioctl(fileno(stdout), TIOCFLUSH, (char *)0);
    tbackp = tfrontp = ttyobuf;
}

ttyflush()
{
	int n;

	if ((n = tfrontp - tbackp) > 0)
		n = write(tout, tbackp, n);
	if (n < 0)
		return;
	tbackp += n;
	if (tbackp == tfrontp)
		tbackp = tfrontp = ttyobuf;
}

/* TtyChars() - returns the number of characters in the TTY buffer */
TtyChars()
{
    return(tfrontp-tbackp);
}

netflush()
{
	int n;

	if ((n = nfrontp - nbackp) > 0)
		n = write(net, nbackp, n);
	if (n < 0) {
		if (errno != ENOBUFS && errno != EWOULDBLOCK) {
			(void) mode(0);
			perror(hostname);
			close(net);
			longjmp(peerdied, -1);
			/*NOTREACHED*/
		}
		n = 0;
	}
	if (printnet) {
		Dump('>', nbackp, n);
	}
	nbackp += n;
	if (nbackp == nfrontp)
		nbackp = nfrontp = netobuf;
}

/* DataToNetwork - queue up some data to go to network.  When last byte is
    queued, we add on an IAC EOR sequence (so, don't call us until you
    want that done...)
 */

int
DataToNetwork(buffer, count)
register char	*buffer;		/* where the data is */
register int	count;			/* how much to send */
{
    register int c;
    int origCount;

    origCount = count;

    while (count) {
	if ((&netobuf[sizeof netobuf] - nfrontp) < 6) {
	    netflush();
	    if ((&netobuf[sizeof netobuf] - nfrontp) < 6) {
		break;
	    }
	}
	c = *buffer++;
	count--;
	if (c == IAC) {
	    *nfrontp++ = IAC;
	    *nfrontp++ = IAC;
	} else {
	    *nfrontp++ = c;
	}
    }

    if (!count) {
	*nfrontp++ = IAC;
	*nfrontp++ = EOR;
	netflush();		/* try to move along as quickly as ... */
    }
    return(origCount - count);
}

/* DataToTerminal - queue up some data to go to terminal. */

int
DataToTerminal(buffer, count)
register char	*buffer;		/* where the data is */
register int	count;			/* how much to send */
{
    int origCount;
    fd_set o;

    origCount = count;

    while (count) {
	if (tfrontp >= &ttyobuf[sizeof ttyobuf]) {
	    ttyflush();
	    while (tfrontp >= &ttyobuf[sizeof ttyobuf]) {
		FD_ZERO(&o);
		FD_SET(tout, &o);
		(void) select(tout+1, (fd_set *) 0, &o, (fd_set *) 0,
						(struct timeval *) 0);
		ttyflush();
	    }
	}
	*tfrontp++ = *buffer++;
	count--;
    }
    return(origCount - count);
}

/* EmptyTerminal - called to make sure that the terminal buffer is empty.
 *			Note that we consider the buffer to run all the
 *			way to the kernel (thus the select).
 */

void
EmptyTerminal()
{
    fd_set o;

    FD_ZERO(&o);
    FD_SET(tout, &o);

    if (tfrontp == tbackp) {
	(void) select(tout+1, (fd_set *) 0, &o, (fd_set *) 0,
			(struct timeval *) 0);	/* wait for TTLOWAT */
    } else {
	while (tfrontp != tbackp) {
	    ttyflush();
	    (void) select(tout+1, (fd_set *) 0, &o, (fd_set *) 0,
				(struct timeval *) 0);	/* wait for TTLOWAT */
	}
    }
}



/* StringToTerminal - output a null terminated string to the terminal */

int
StringToTerminal(s)
char *s;
{
    int count;

    count = strlen(s);
    if (count) {
	(void) DataToTerminal(s, count);	/* we know it always goes... */
    }
}


/* _putchar - output a single character to the terminal.  This name is so that
 *	curses(3x) can call us to send out data.
 */

_putchar(c)
char c;
{
    if (tfrontp >= &ttyobuf[sizeof ttyobuf]) {
	(void) DataToTerminal(&c, 1);
    } else {
	*tfrontp++ = c;		/* optimize if possible. */
    }
}

static
SetForExit()
{
    (void) mode(2);			/* switch modes to flush output */
    (void) mode(0);
    fflush(stdout);
    fflush(stderr);
    if (In3270) {
	Stop3270(0);
    }
    (void) mode(2);			/* make sure we go back to mode 0 */
    (void) mode(0);
}

static
Exit(returnCode)
int returnCode;
{
    SetForExit();
    exit(returnCode);
}

ExitString(file, string, returnCode)
FILE *file;
char *string;
int returnCode;
{
    SetForExit();
    fwrite(string, 1, strlen(string), file);
    exit(returnCode);
}

ExitPerror(string, returnCode)
char *string;
int returnCode;
{
    SetForExit();
    perror(string);
    exit(returnCode);
}


static
Dump(direction, buffer, length)
char	direction;
char	*buffer;
int	length;
{
#   define BYTES_PER_LINE	32
    char *pThis;
    int offset;

    offset = 0;

    while (length) {
	/* print one line */
	fprintf(NetTrace, "%c 0x%x\t", direction, offset);
	pThis = buffer;
	buffer = buffer+min(length, BYTES_PER_LINE);
	while (pThis < buffer) {
	    fprintf(NetTrace, "%.2x", (*pThis)&0xff);
	    pThis++;
	}
	fprintf(NetTrace, "\n");
	length -= BYTES_PER_LINE;
	offset += BYTES_PER_LINE;
	if (length < 0) {
	    return;
	}
	/* find next unique line */
    }
}



/*VARARGS*/
static
printoption(direction, fmt, option, what)
	char *direction, *fmt;
	int option, what;
{
	if (!showoptions)
		return;
	printf("%s ", direction);
	if (fmt == doopt)
		fmt = "do";
	else if (fmt == dont)
		fmt = "dont";
	else if (fmt == will)
		fmt = "will";
	else if (fmt == wont)
		fmt = "wont";
	else if (fmt == sb_terminal)
		fmt = "will (terminal)";
	else
		fmt = "???";
	if (option < TELOPT_SUPDUP)
		printf("%s %s", fmt, telopts[option]);
	else
		printf("%s %d", fmt, option);
	if (*direction == '<') {
		printf("\r\n");
		return;
	}
	printf(" (%s)\r\n", what ? "reply" : "don't reply");
}
