/* SUPDUP Server
 *
 *	Written Jan. 84 by David Bridgham.  The organization and some of
 *  the code was taken from the telnet server written by Berkeley for 4.2.
 *
 * Changes in the versions
 * -----------------------
 * 1.0 -	The first release.
 * 1.1 -	Fixed the termcap entries to all have
 *		exactly three octal digits after the \.
 * 1.2 -	Changed supdupd to be started by inetd.
 * 1.3 -	Got rid of wrapped hack and made meta keys
 * 		set the high bit instead of prefixing escape.
 * 1.4 -	Added support for terminfo as well as termcap.
 * 1.5 -	Added TTYLOC Romkey's stuff.
 * 1.6 -	Understand (and ignore) most of the ITS ITP codes
 */

/* Define TERMINFO if want terminfo support.
 * Note: TERMCAP should be optional too.
 */

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <sys/file.h>

#include <netinet/in.h>

#include <arpa/telnet.h>
#include "supdupd.h"

#include <stdio.h>
#include <signal.h>
#include <errno.h>
#include <sgtty.h>
#include <netdb.h>

#ifdef	TERMINFO
# include <term.h>
# undef		CUR
# define	CUR
#endif	TERMINFO

#ifdef TTYLOC
# include <ttyloc.h>
#endif

#define	BELL	'\07'
/* MIT */
#define BANNER	"%s %s"
/* MIT */

#define	SBANNER	"Supdup 1.6.  Bugs to bug-supdup@borax.mit.edu"

/*
 * I/O data buffers, pointers, and counters.
 */
char	ptyibuf[BUFSIZ], *ptyip = ptyibuf;
char	ptyobuf[BUFSIZ], *pfrontp = ptyobuf, *pbackp = ptyobuf;
char	netibuf[BUFSIZ], *netip = netibuf;
char	netobuf[BUFSIZ], *nfrontp = netobuf, *nbackp = netobuf;
int	pcc, ncc;

int	pty, net;
int	inter;
int	reapchild();
extern	char **environ;
extern	int errno;
char	line[] = "/dev/ptyp0";
int	currcol, currline;	/* Current cursor position */
char ttyopt[6]; /* These variables are set at initial connection time */
short ttyrol;	/* How much the terminal scrolls by */
short tcmxv;	/* Number of lines */
short tcmxh;	/* Number of columns */

#ifdef DEBUG
int debug_file = 0;
#endif DEBUG

struct	sockaddr_in sin = { AF_INET };

main(argc, argv)
	char *argv[];
{
	struct sockaddr_in from;
	int fromlen;

	fromlen = sizeof (from);
	if (getpeername(0, &from, &fromlen) < 0) {
		fprintf(stderr, "%s: ", argv[0]);
		perror("getpeername");
		_exit(1);
	}

/* MIT */
#ifdef KEEPALIVE
	if (setsockopt(0, SOL_SOCKET, SO_KEEPALIVE, 0, 0) < 0) {
		fprintf(stderr, "%s: ", argv[0]);
		perror("setsockopt (SO_KEEPALIVE)");
	}
#endif KEEPALIVE
/* MIT */
	doit(0, &from);
}

char	termcap[512];

#ifdef	TERMINFO
char	terminfo[64];
#endif	TERMINFO

char	*envinit[] = { "TERM=supdup",
			termcap,
#ifdef	TERMINFO
			terminfo,
#endif	TERMINFO
			0 };

int	cleanup();

/*
 * Get a pty, scan input lines.
 */
doit(f, who)
	int f;
	struct sockaddr_in *who;
{
	char *cp = line, *host, *ntoa();
	int i, p, cc, t;
	struct sgttyb b;
	struct hostent *hp;

	for (i = 0; i < 16; i++) {
		cp[strlen("/dev/ptyp")] = "0123456789abcdef"[i];
		p = open(cp, O_RDWR, 0);
		if (p > 0)
			goto gotpty;
	}
	fatal(f, "All network ports in use");
	/*NOTREACHED*/
gotpty:
	dup2(f, 0);
	cp[strlen("/dev/")] = 't';
	t = open("/dev/tty", O_RDWR, 0);
	if (t >= 0) {
		ioctl(t, TIOCNOTTY, 0);
		close(t);
	}
	t = open(cp, O_RDWR, 0);
	if (t < 0)
		fatalperror(f, cp, errno);
	ioctl(t, TIOCGETP, &b);
#ifdef DEBUG
	cp[1] = 't', cp[2] = 'm', cp[3] = 'p';
	debug_file = open(cp, O_WRONLY | O_CREAT, 0777);
	if (debug_file < 0)
	  fatalperror(f, cp, errno);
#endif
	/* MIT */
	b.sg_ispeed = B9600;
	b.sg_ospeed = B9600;
	/* MIT */
	b.sg_flags = XTABS|ANYP;	/* punted CRMOD */
	ioctl(t, TIOCSETP, &b);
	ioctl(p, TIOCGETP, &b);
	b.sg_flags &= ~ECHO;
	ioctl(p, TIOCSETP, &b);
	sup_options(f);
	hp = gethostbyaddr(&who->sin_addr, sizeof (struct in_addr),
		who->sin_family);
	if (hp)
		host = hp->h_name;
	else
		host = ntoa(who->sin_addr);
	if ((i = fork()) < 0)
		fatalperror(f, "fork", errno);
	if (i)
		supdup(f, p);
	close(f);
	close(p);
	dup2(t, 0);
	dup2(t, 1);
	dup2(t, 2);
	close(t);
	environ = envinit;
	execl("/etc/supduplogin", "login", "-h", host, 0);
	fatalperror(2, "/etc/supduplogin", errno);
	/*NOTREACHED*/
}

fatal(f, msg)
	int f;
	char *msg;
{
	char buf[BUFSIZ];

	(void) sprintf(buf, "supdupd: %s.\r\n", msg);
	(void) write(f, buf, strlen(buf));
	exit(1);
}

fatalperror(f, msg, errno)
	int f;
	char *msg;
	int errno;
{
	char buf[BUFSIZ];
	extern char *sys_errlist[];

	(void) sprintf(buf, "%s: %s", msg, sys_errlist[errno]);
	fatal(f, buf);
}

/*
 * Main loop.  Select from pty and network, and
 * hand data to supdup receiver finite state machine.
 */
supdup(f, p)
{
	int on = 1;
	char hostname[32];

	net = f, pty = p;
	ioctl(f, FIONBIO, &on);
	ioctl(p, FIONBIO, &on);
	signal(SIGTSTP, SIG_IGN);
	signal(SIGCHLD, cleanup);
	mode(ECHO|CRMOD, 0);

	/*
	 * Print supdup banner.
	 */
	sprintf(nfrontp, SBANNER);
	nfrontp += strlen(nfrontp);
	*nfrontp++ = TDNOP;
	netflush();
	sleep(1);
	
	/*
	 * Show banner that getty never gave.
	 */
	*nfrontp++ = TDCLR;
	gethostname(hostname, sizeof (hostname));
	sprintf(nfrontp, BANNER, hostname, "");
	currline = 0;
	currcol = strlen(nfrontp);
	nfrontp += strlen(nfrontp);
	for (;;) {
	    int ibits = 0, obits = 0;
	    register int c;

	    /*
	     * Never look for input if there's still
	     * stuff in the corresponding output buffer
	     */
	    if ((nfrontp - nbackp) || pcc > 0)
	        obits |= (1 << f);
	    else
	        ibits |= (1 << p);
	    if ((pfrontp - pbackp) || ncc > 0)
	        obits |= (1 << p);
	    else
	        ibits |= (1 << f);
	    if (ncc < 0 && pcc < 0)
	        break;
	    select(16, &ibits, &obits, 0, 0);
	    if (ibits == 0 && obits == 0) {
		sleep(5);
		continue;
	    }

	    /*
	     * Something to read from the network...
	     */
	    if (ibits & (1 << f)) {
		ncc = read(f, netibuf, BUFSIZ);
		if (ncc < 0 && errno == EWOULDBLOCK)
		    ncc = 0;
		else {
		    if (ncc <= 0)
		        break;
		    netip = netibuf;
		}
	    }

	    /*
	     * Something to read from the pty...
	     */
	    if (ibits & (1 << p)) {
		pcc = read(p, ptyibuf, BUFSIZ);
		if (pcc < 0 && errno == EWOULDBLOCK)
		    pcc = 0;
		else {
		    if (pcc <= 0)
		        break;
		    ptyip = ptyibuf;
#ifdef DEBUG
		    write(debug_file, ptyibuf, pcc);
#endif
		}
	    }

	    if (pcc > 0)
	        supxmit();
	    if ((obits & (1 << f)) && (nfrontp - nbackp) > 0)
	        netflush();
	    if (ncc > 0)
	        suprcv();
	    if ((obits & (1 << p)) && (pfrontp - pbackp) > 0)
	        ptyflush();
	}
	cleanup();
}
	
/*
 * State for xmit fsm
 */
#define	XS_DATA		0	/* base state */
#define	XS_ESCAPE	1	/* supdup commands are escaped in /etc/termcap */
#define	XS_MVV		2	/* getting vertical position of TDMV0 */
#define	XS_MVH		3	/* getting horizontal position of TDMV0 */
#define	XS_CRLF		4	/* got \r looking for \n */
#define	XS_ILINE	5	/* waiting for number of lines to insert */
#define	XS_DLINE	6	/* waiting for number of lines to delete */
#define	XS_ICHAR	7	/* waiting for number of chars to insert */
#define	XS_DCHAR	8	/* waiting for number of chars to delete */

supxmit()
{
	static int	state = XS_DATA;
	register int	c;

	while (pcc > 0) {
	    if ((&netobuf[BUFSIZ] - nfrontp) < 4)
	        return;
	    c = *ptyip++ & 0377, pcc--;
/*	    echo_char(c); /* for debugging */
	    switch (state) {
	    case XS_DATA:
		switch (c) {

/* -- I don't see the need for this.  Mly
		case SUPDUP_ESCAPE:
		    *nfrontp++ = c;
		    *nfrontp++ = c;
		    break;
*/
		case KLUDGE_ESCAPE:
		    state = XS_ESCAPE;
		    break;

		case '\007':
		    *nfrontp++ = TDBEL;
		    break;

		case '\t':
		    currcol = (currcol + 8) & ~7;
		    if (currcol > tcmxh) currcol = tcmxh;
		    *nfrontp++ = TDMV0;
		    *nfrontp++ = currline;
		    *nfrontp++ = currcol;
		    break;

		case '\b':
		    if (currcol > 0) currcol--;
		    *nfrontp++ = TDMV0;
		    *nfrontp++ = currline;
		    *nfrontp++ = currcol;
		    break;
		    
		case '\r':
		    state = XS_CRLF;
		    if ((pcc) && ((*ptyip & 0377) == '\n'))
		        break;
		    currcol = 0;
		    *nfrontp++ = TDMV0;
		    *nfrontp++ = currline;
		    *nfrontp++ = 0;
		    break;

		case '\n':
		    if (currline < tcmxv) {
			*nfrontp++ = TDMV0;
			*nfrontp++ = ++currline;
			*nfrontp++ = currcol;
		    }
		    else {
			if (!TOROL) {
			    currline = 0;
			    *nfrontp++ = TDMV0;
			    *nfrontp++ = 0;
			    *nfrontp++ = currcol;
			    *nfrontp++ = TDEOL;
			}
			/* this is not the right thing, but
			 * what else can I do? */

			else {
			    currcol = 0;
			    *nfrontp++ = TDCRL;
			}
		    }
		    break;

		case '\0':	/* throw away nulls. Something is being
				 * a twit and padding even though it
				 * is not wanted! */
		    break;
		default:
		    if (currcol <= tcmxh) currcol++;
		    else {	/* push back char and do newline */
			ptyip--; pcc++;
			goto do_newline;
		    }
		    *nfrontp++ = c;
		    break;
		}
		break;

	    case XS_ESCAPE:
		c += 0176;
		switch (c) {

		case TDCRL:
		    state = XS_DATA;
do_newline:	    currcol = 0;
		    if (currline >= tcmxv) {
			if (!TOROL) {
			    currline = 0;
			    *nfrontp++ = TDMV0;
			    *nfrontp++ = 0;
			    *nfrontp++ = 0;
			    *nfrontp++ = TDEOL;
			}
			else *nfrontp++ = TDCRL;
			break;
		    }
		    else {
			*nfrontp++ = TDCRL;
			currline++;
		    }
		    break;

		case TDFS:
		    currcol++;
		    state = XS_DATA;
		    *nfrontp++ = c;
		    break;

		case TDMV0:
		    state = XS_MVV;
		    *nfrontp++ = c;
		    break;

		case TDCLR:
		    currcol = 0; currline = 0;
		    state = XS_DATA;
		    *nfrontp++ = c;
		    break;

		case TDLF:	/* this is not in the SUPDUP spec */
		    state = XS_DATA;
		    if (currline < tcmxv) currline++;
		    *nfrontp++ = TDMV0;
		    *nfrontp++ = currline;
		    *nfrontp++ = currcol;
		    break;

		case TDUP:
		    state = XS_DATA;
		    if (currline > 0) currline--;
		    *nfrontp++ = TDMV0;
		    *nfrontp++ = currline;
		    *nfrontp++ = currcol;
		    break;

		case TDILP:
		    state = XS_ILINE;
		    currcol = 0;
		    *nfrontp++ = c;
		    break;

		case TDDLP:
		    state = XS_DLINE;
		    currcol = 0;
		    *nfrontp++ = c;
		    break;

		case TDICP:
		    state = XS_ICHAR;
		    *nfrontp++ = c;
		    break;
		    
		case TDDCP:
		    state = XS_DCHAR;
		    *nfrontp++ = c;
		    break;
				
		case KLUDGE_ESCAPE: /* Really shouldn't get two */
		    break;	    /* of these but it's happening */

		default:
		    state = XS_DATA;
		    *nfrontp++ = c;
		    break;

		}
		break;

	    case XS_MVV:
		state = XS_MVH;
		c-= ' ';
		currline = c;
		*nfrontp++ = c;
		break;

	    case XS_MVH:
		state = XS_DATA;
		c -= ' ';
		currcol = c;
		*nfrontp++ = c;
		break;

	    /* The newline algorithm is as follows:
	     *	A \r causes the cursor to be sent to the
	     * beginning of the line unless there is a \n
	     * immediately following on the input.
	     *	If the next character is a \n then a
	     * %TDCRL is sent.
	     *	A lone \n causes the cursor to be moved
	     * straight down one line wrapping if necessary
	     * or doing a %TDCRL if at the bottom of the screen
	     * and it is a scrolling terminal.
	     */
	    case XS_CRLF:
		state = XS_DATA;
		if (c == '\n') {
		    currcol = 0;
		    if (currline >= tcmxv) {
			if (!TOROL) {
			    currline = 0;
			    *nfrontp++ = TDMV0;
			    *nfrontp++ = 0;
			    *nfrontp++ = 0;
			    *nfrontp++ = TDEOL;
			}
			else *nfrontp++ = TDCRL;
			break;
		    }
		    else {
			*nfrontp++ = TDCRL;
			currline++;
		    }
		}
		else {
		    ptyip--; pcc++; /* push back character */
		}
		break;

	    case XS_ILINE:
		state = XS_DATA;
		while (pcc > 3 && (ptyip[0] & 0377) == '\177' &&
				  (ptyip[1] & 0377) == '\025' &&
		       	          (ptyip[2] & 0377) == '\001') {
		       c++;
		       pcc -= 3;
		       ptyip += 3;
		   }
		*nfrontp++ = c;
		*nfrontp++ = TDMV0;
		*nfrontp++ = currline;
		*nfrontp++ = currcol;
		break;

	    case XS_DLINE:
		state = XS_DATA;
		while (pcc > 3 && (ptyip[0] & 0377) == '\177' &&
				  (ptyip[1] & 0377) == '\026' &&
				  (ptyip[2] & 0377) == '\001') {
		       c++;
		       pcc -= 3;
		       ptyip += 3;
		   }
		*nfrontp++ = c;
		*nfrontp++ = TDMV0;
		*nfrontp++ = currline;
		*nfrontp++ = currcol;
		break;
		
	    case XS_ICHAR:
	    case XS_DCHAR:
		state = XS_DATA;
		*nfrontp++ = c;
		break;

	    default:
		state = XS_DATA;
		break;
	    }
	}
}

/*
 * State for recv fsm
 */
#define RS_DATA		0	/* base state */
#define	RS_ITP		1	/* recieved ITP_ESCAPE (034) */
#define	RS_META		2	/* got meta bits for character */
#define	RS_SUPDUP	3	/* got SUPDUP_ESCAPE (300) */
#define	RS_LOCATION	4	/* recieving location string */
#define RS_IGNORE_1	5	/* ignore next char */
#define RS_CURSORPOS_V	6	/* reading vertical (1st) cursorpos */
#define RS_CURSORPOS_H	7	/* reading horizontal (2nd) cursorpos */

suprcv()
{
	register int	c;
	int		meta_bit;
	int		meta_char;
	static int	state = RS_DATA;

	while (ncc > 0) {
	    if ((&ptyobuf[BUFSIZ] - pfrontp) < 2)
	      return;
	    c = *netip++ & 0377, ncc--;
	    meta_bit = 0;
	    switch (state) {
		
	    case RS_DATA:
		if (c == ITP_ESCAPE) {
		    state = RS_ITP;
		    break;
		}
		if (c == SUPDUP_ESCAPE) {
		    state = RS_SUPDUP;
		    break;
		}
		*pfrontp++ = c;
		break;

	    case RS_ITP:
		if (c & 0100) {
		    meta_char = c;
		    state = RS_META;
		    break;
		}
		switch (c) {
		case ITP_ESCAPE:
		    *pfrontp++ = c;
		    state = RS_DATA;
		    break;
		case ITP_CURSORPOS:
		    state = RS_CURSORPOS_V;
		    break;
		case ITP_FLOW_CONTROL_INCREASE:
		    /* Ignore this for the time being */
		    /*  Increase buffer size by <next-character> */
		    state = RS_IGNORE_1;
		    break;
		case ITP_PIATY:
		    /* ITS %piaty -- says that screen is messed up.
		       Unix gives no way to tell programs about this,
		       so ignore it. (Conceivably we could send through a
		        c-L character.. UGH)
		     */
		case ITP_FLOW_CONTROL_START:
		    /* Ignore this for the time being */
		    /*   -- User wants to hack flow control */
		case ITP_FLOW_CONTROL_END:
		case ITP_STOP_OUTPUT:
		case ITP_RESTART_OUTPUT:
		default: /* wtf? */
		    state = RS_DATA;
		}
		break;
	    case RS_META:
		state = RS_DATA;
		c = ITP_CHAR(meta_char, c);

		if (c == ITP_HELP)
		    c = 037;	/* hack for help key */
		else
		  {
		      if (c & ITP_MTA)
			meta_bit = 1;

		      if (c & ITP_CTL)
			  c = ASCII_PART(c & ASCII_CTL_MASK);
		      else
			  c = ASCII_PART(c);

		      if (meta_bit) c |= 0200;
		  }
		*pfrontp++ = c;
		break;

	    case RS_SUPDUP:
		switch (c) {

		case SUPDUP_LOGOUT:
		    cleanup();

		case SUPDUP_LOCATION:
		    state = RS_LOCATION;
		    break;

		default:
		    state = RS_DATA;
		    break;
		}
		break;

	    case RS_LOCATION:
#ifdef TTYLOC
		{
		    static struct ttyloc location;
		    static int i = 0;

		    location.ttyloc[i++] = c;
		    if (c == 0) {
			i = 0;
			strncpy(location.ttyname, line+5,
				MAX_TTYNAME_LEN);
			ttyloc_change(&location, TTYLOC_FILE);
			state = RS_DATA;
		    }
		    break;
		}
#else
		if (c == 0)
		  state = RS_DATA;
		break;
#endif
	    case RS_IGNORE_1:
		state = RS_DATA;
		break;
	    case RS_CURSORPOS_V:
		currcol = c;
		state = RS_CURSORPOS_H;
		break;
	    case RS_CURSORPOS_H:
		currline = c;
		state = RS_DATA;
		break;
	    }
	}
}

mode(on, off)
	int on, off;
{
	struct sgttyb b;

	ptyflush();
	ioctl(pty, TIOCGETP, &b);
	b.sg_flags |= on;
	b.sg_flags &= ~off;
	ioctl(pty, TIOCSETP, &b);
}

ptyflush()
{
	int n;

	if ((n = pfrontp - pbackp) > 0)
		n = write(pty, pbackp, n);
	if (n < 0)
		return;
	pbackp += n;
	if (pbackp == pfrontp)
		pbackp = pfrontp = ptyobuf;
}

netflush()
{
	int n;

	if ((n = nfrontp - nbackp) > 0)
		n = write(net, nbackp, n);
	if (n < 0) {
		if (errno == EWOULDBLOCK)
			return;
		/* should blow this guy away... */
		return;
	}
	nbackp += n;
	if (nbackp == nfrontp)
		nbackp = nfrontp = netobuf;
}

cleanup()
{
#ifdef	TERMINFO
	clean_terminfo();
#endif	TERMINFO
	
	rmut();
	vhangup();	/* XXX */
	shutdown(net, 2);
	kill(0, SIGKILL);
	exit(1);
}

#ifdef	TERMINFO
/* Cleans up the files created for the TERMINFO stuff.
 */
clean_terminfo()
{
	char	dir[128];
	int	pid;

	pid = getpid();
	sprintf(dir, "/tmp/%d/s/supdup", pid);
	unlink(dir);
	sprintf(dir, "/tmp/%d/s", pid);
	rmdir(dir);
	sprintf(dir, "/tmp/%d", pid);
	rmdir(dir);
}

#endif	TERMINFO


#include <utmp.h>

struct	utmp wtmp;
char	wtmpf[]	= "/usr/adm/wtmp";
char	utmp[] = "/etc/utmp";
#define SCPYN(a, b)	strncpy(a, b, sizeof (a))
#define SCMPN(a, b)	strncmp(a, b, sizeof (a))

rmut()
{
	register f;
	int found = 0;

	f = open(utmp, O_RDWR, 0);
	if (f >= 0) {
		while(read(f, (char *)&wtmp, sizeof (wtmp)) == sizeof (wtmp)) {
			if (SCMPN(wtmp.ut_line, line+5) || wtmp.ut_name[0]==0)
				continue;
			lseek(f, -(long)sizeof (wtmp), 1);
			SCPYN(wtmp.ut_name, "");
			SCPYN(wtmp.ut_host, "");
			time(&wtmp.ut_time);
			write(f, (char *)&wtmp, sizeof (wtmp));
			found++;
		}
		close(f);
	}
	if (found) {
		f = open(wtmpf, O_WRONLY, 0);
		if (f >= 0) {
			SCPYN(wtmp.ut_line, line+5);
			SCPYN(wtmp.ut_name, "");
			SCPYN(wtmp.ut_host, "");
			time(&wtmp.ut_time);
			lseek(f, (long)0, 2);
			write(f, (char *)&wtmp, sizeof (wtmp));
			close(f);
		}
	}
	chmod(line, 0666);
	chown(line, 0, 0);
	line[strlen("/dev/")] = 'p';
	chmod(line, 0666);
	chown(line, 0, 0);
}

/*
 * Convert network-format internet address
 * to base 256 d.d.d.d representation.
 */
char *
ntoa(in)
	struct in_addr in;
{
	static char b[18];
	register char *p;

	p = (char *)&in;
#define	UC(b)	(((int)b)&0xff)
	sprintf(b, "%d.%d.%d.%d", UC(p[0]), UC(p[1]), UC(p[2]), UC(p[3]));
	return (b);
}

/* Read the 36 bit options from the net setting variables and create
 * the TERMCAP environment variable.
 */
sup_options(net)
int net;
{
	char	temp[6];
	int	count;

	read(net, temp, 6);	/* Read count */
	count = -((-1 << 6) | temp[2]);
	if (count--) {
		read(net, temp, 6);	/* Discard TCTYP */
		if (count--) {
			read(net, ttyopt, 6);
			if (count--) {
				read(net, temp, 6);
				tcmxv = (temp[5] & 0377) | ((temp[4] & 0377) << 6);
				if (count--) {
					read(net, temp, 6);
					tcmxh = 1 + (temp[5] & 0377) | ((temp[4] & 0377) << 6);
					if (count--) {
						read(net, temp, 6);
						ttyrol = temp[5] & 077;
						while (count--) read(net, temp, 6);
					}
				}
			}
		}
	}
	sprintf(termcap, "TERMCAP=SD|supdup|SUPDUP virtual terminal:co#%d:li#%d:",
		tcmxh, tcmxv);
	strcat(termcap, "am:vb=\\177\\023:nd=\\177\\020:");
	strcat(termcap, "cl=\\177\\022:so=\\177\\031:se=\\177\\032:pt:");
	if (TOERS)
		strcat(termcap, "ce=\\177\\005:ec=\\177\\006:cd=\\177\\004:");
	if (TOMVB)
		strcat(termcap, "bs:");
	if (TOOVR)
		strcat(termcap, "os:");
	if (TOMVU) {
		strcat(termcap, "up=\\177\\041:cm=\\177\\021%+ %+ :");
		strcat(termcap, "do=\\177\\014:nl=\\177\\014:");
	}
	if (TOLID) {
		strcat(termcap, "al=\\177\\025\\001:dl=\\177\\026\\001:");
		strcat(termcap, "AL=\\177\\025%.:DL=\\177\\026%.:");
	}
	if (TOCID) {
		strcat(termcap, "mi:im=:ei=:ic=\\177\\027\\001:");
		strcat(termcap, "dc=\\177\\030\\001:dm=:ed=:");
		strcat(termcap, "IC=\\177\\027%.:DC=\\177\\030%.:");
	}
	if (!TOROL)
		strcat(termcap, "ns:");
	if (TOFCI)
		strcat(termcap, "km:MT:");

#ifdef	TERMINFO
	init_terminfo();
#endif	TERMINFO

	tcmxh--;	/* making these one less works better for later */
	tcmxv--;	/* computations */
}


#ifdef	TERMINFO

char names[] = "supdup|sd|supdup virtual terminal";
#define	MAGIC_NUM	0432

struct head {
	short	magic;
	short	name_len;
	short	bools_len;
	short	nums_len;
	short	str_len;
	short	strtab_len;
	} header;

unsigned char Booleans[BOOLCOUNT + 1];
short	Numbers[NUMCOUNT];
short	Strings[STRCOUNT];
char	String_Table[] = {
#define	FLASH_SCREEN	0
	'\177', '\023', '\0',
#define	CURSOR_RIGHT	3
	'\177', '\020', '\0',
#define	CLEAR_SCREEN	6
	'\177', '\022', '\0',
#define	ENTER_STANDOUT_MODE	9
	'\177', '\031', '\0',
#define	EXIT_STANDOUT_MODE	12
	'\177', '\032', '\0',
#define	CLR_EOL	15
	'\177', '\005', '\0',
#define	CLR_EOS	CLR_EOL + 3
	'\177', '\004', '\0',
#define	CURSOR_UP	CLR_EOS + 3
	'\177', '\041', '\0',
#define	CURSOR_DOWN	CURSOR_UP + 3
	'\177', '\014', '\0',
#define	NEWLINE	CURSOR_DOWN + 3
	'\177', '\011', '\0',	/* this does cursor_down (\014) in termcap */
#define	CURSOR_ADDRESS	NEWLINE + 3
	'\177', '\021',
		'%', 'p', '1', '%', '\'', ' ', '\'', '%', '+', '%', 'c',
		'%', 'p', '2', '%', '\'', ' ', '\'', '%', '+', '%', 'c', '\0',
#define	INSERT_LINE	CURSOR_ADDRESS + 25
	'\177', '\025', '\001', '\0',
#define	DELETE_LINE	INSERT_LINE + 4
	'\177', '\026', '\001', '\0',
#define	PARM_INSERT_LINE	DELETE_LINE + 4
	'\177', '\025', '%', 'p', '1', '%', 'c', '\0',
#define	PARM_DELETE_LINE	PARM_INSERT_LINE + 8
	'\177', '\026', '%', 'p', '1', '%', 'c', '\0',
#define	CURSOR_LEFT	PARM_DELETE_LINE + 8
	'', '\0',
#define	DELETE_CHARACTER	CURSOR_LEFT + 2
	'\177', '\030', '\001', '\0',
#define	INSERT_CHARACTER	DELETE_CHARACTER + 4
	'\177', '\027', '\001', '\0',
#define	PARM_DCH	INSERT_CHARACTER + 4
	'\177', '\030', '%', 'p', '1', '%', 'c', '\0',
#define	PARM_ICH	PARM_DCH + 4
	'\177', '\027', '%', 'p', '1', '%', 'c', '\0',
};

#define	STRTABLEN	100

/* This routine sets up the files and environment variables for using the
 * TERMINFO data base.
 */
init_terminfo()
{
	register int i;
	int	file;
	int	pid;
	char	directory[128];

	pid = getpid();
	sprintf(directory, "/tmp/%d", pid);
	mkdir(directory, 0777);
	sprintf(terminfo, "TERMINFO=%s", directory);
	strcat(directory, "/s");
	mkdir(directory, 0777);
	strcat(directory, "/supdup");

	file = open(directory, O_WRONLY | O_CREAT, 0777);
	if (file <= 0) cleanup();

	for (i = 0; i < BOOLCOUNT + 1; i++)
		Booleans[i] = 0;
	for (i = 0; i < NUMCOUNT; i++)
		Numbers[i] = -1;
	for (i = 0; i < STRCOUNT; i++)
		Strings[i] = -1;

	header.magic = MAGIC_NUM;
	header.name_len = strlen(names) + 1;
	header.bools_len = BOOLCOUNT;
	header.nums_len = NUMCOUNT;
	header.str_len = STRCOUNT;
	header.strtab_len = STRTABLEN;

	lines = tcmxv;
	columns = tcmxh;
	auto_right_margin = 1;
	flash_screen = FLASH_SCREEN;
	cursor_right = CURSOR_RIGHT;
	clear_screen = CLEAR_SCREEN;
	enter_standout_mode = ENTER_STANDOUT_MODE;
	exit_standout_mode = EXIT_STANDOUT_MODE;
	init_tabs = 8;
	if (TOERS) {
		clr_eol = CLR_EOL;
		clr_eos = CLR_EOS;
	}
	if (TOMVB)
		cursor_left = CURSOR_LEFT;
	if (TOOVR)
		over_strike = 1;
	if (TOMVU) {
		cursor_up = CURSOR_UP;
		cursor_down = CURSOR_DOWN;
		newline = NEWLINE;
		cursor_address = CURSOR_ADDRESS;
	}
	if (TOLID) {
		insert_line = INSERT_LINE;
		delete_line = DELETE_LINE;
		parm_insert_line = PARM_INSERT_LINE;
		parm_delete_line = PARM_DELETE_LINE;
	}
	if (TOCID) {
		insert_character = INSERT_CHARACTER;
		delete_character = DELETE_CHARACTER;
		parm_dch = PARM_DCH;
		parm_ich = PARM_ICH;
	}
	if (TOFCI)
		has_meta_key = 1;

	write(file, &header, sizeof(struct head));
	write(file, names, header.name_len);
	write(file, Booleans, (header.name_len + header.bools_len) & 1 ?
				header.bools_len + 1 :
				header.bools_len);
	write(file, Numbers, 2 * header.nums_len);
	write(file, Strings, 2 * header.str_len);
	write(file, String_Table, header.strtab_len + 5);
	
}

#endif	TERMINFO


/* this routine is used for debugging only */
echo_char(c)
unsigned char	c;
{
	static int count = 0;

	if (count++ > 16) {
		printf("\n");
		count = 0;
	}
	if (c >= '\177') printf("\\%o ", c);
	else if (c < '\041') printf("\\%o ", c);
	else printf("%c ", c);
	fflush(stdout);
}
