/*
 * $Id: rlogin.c,v 4.26 91/06/26 10:40:18 probe Exp $
 */

#ifndef lint
static char rcsid_rlogin_c[] = "$Header: /afs/rel-eng.athena.mit.edu/project/release/current/source/athena/athena.lib/kerberos/appl/bsd/RCS/rlogin.c,v 4.26 91/06/26 10:40:18 probe Exp $";
#endif lint

/*
 * Copyright (c) 1983 The Regents of the University of California.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms are permitted
 * provided that the above copyright notice and this paragraph are
 * duplicated in all such forms and that any documentation,
 * advertising materials, and other materials related to such
 * distribution and use acknowledge that the software was developed
 * by the University of California, Berkeley.  The name of the
 * University may not be used to endorse or promote products derived
 * from this software without specific prior written permission.
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
 * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 */

#ifndef lint
char copyright[] =
"@(#) Copyright (c) 1983 The Regents of the University of California.\n\
 All rights reserved.\n";
#endif /* not lint */

#ifndef lint
static char sccsid[] = "@(#)rlogin.c	5.12 (Berkeley) 9/19/88";
#endif /* not lint */

/*
 * rlogin - remote login
 */

#ifdef _AIX
#undef _BSD
#endif
	
#include <sys/types.h>
#include <sys/param.h>
#include <sys/ioctl.h>
#include <sys/errno.h>
#include <sys/file.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/wait.h>

#include <netinet/in.h>

#include <stdio.h>
#include <strings.h>
#include <errno.h>
#include <pwd.h>
#include <signal.h>
#include <setjmp.h>
#include <netdb.h>
#ifdef KERBEROS
#include <krb.h>
#endif

#ifdef POSIX
#include <termios.h>
#ifdef i386
#include <termio.h>
#endif

struct termios deftty;

#else /* !POSIX */
#include <sgtty.h>
#endif

# ifndef TIOCPKT_WINDOW
# define TIOCPKT_WINDOW 0x80
# endif TIOCPKT_WINDOW

/* concession to sun */
# ifndef SIGUSR1
# define SIGUSR1 30
# endif SIGUSR1

extern char *malloc(), *getenv();
extern struct passwd *getpwuid();

char	*name;
int	rem;
char	cmdchar = '~';
#ifdef ATHENA
int	eight = 1;		/* Default to 8 bit transmission */
int	no_local_escape = 0;
int	null_local_username = 0;
int	flow = 1;			/* Default is to allow flow
					   control at the local terminal */
int	flowcontrol;			/* Since emacs can alter the
					   flow control characteristics
					   of a session we need a
					   variable to keep track of
					   the original characteristics */
int	confirm = 0;			/* ask if ~. is given before dying. */
#else
int	eight;
#endif
int	litout = 0;
char	*speeds[] =
    { "0", "50", "75", "110", "134", "150", "200", "300",
      "600", "1200", "1800", "2400", "4800", "9600", "19200", "38400" };
char	term[256] = "network";
extern	int errno;
int	lostpeer();
int	dosigwinch = 0;
#ifndef sigmask
#define sigmask(m)	(1 << ((m)-1))
#endif
#ifdef NO_WINSIZE
struct winsize {
	unsigned short ws_row, ws_col;
	unsigned short ws_xpixel, ws_ypixel;
};
#endif
struct	winsize winsize;
int	sigwinch(), oob();
#ifdef ATHENA
char	*host;				/* external, so it can be
					   reached from confirm_death() */
#endif
#ifdef KERBEROS
void try_normal();
char krb_realm[REALM_SZ];
#ifndef NOENCRYPTION
int encrypt = 0;
#else /* NOENCRYPTION */
#define des_read read
#define des_write write
#endif /* NOENCRYPTION */
CREDENTIALS cred;
Key_schedule schedule;
MSG_DAT msg_data;
struct sockaddr_in local, foreign;
#ifndef UCB_RLOGIN
#define	UCB_RLOGIN	"/usr/ucb/rlogin"
#endif
#else /* !KERBEROS */
#define des_read read
#define des_write write
#endif /* KERBEROS */

/*
 * The following routine provides compatibility (such as it is)
 * between 4.2BSD Suns and others.  Suns have only a `ttysize',
 * so we convert it to a winsize.
 */
#ifdef TIOCGWINSZ
#define get_window_size(fd, wp)	ioctl(fd, TIOCGWINSZ, wp)
#else
int
get_window_size(fd, wp)
	int fd;
	struct winsize *wp;
{
	struct ttysize ts;
	int error;

	if ((error = ioctl(0, TIOCGSIZE, &ts)) != 0)
		return (error);
	wp->ws_row = ts.ts_lines;
	wp->ws_col = ts.ts_cols;
	wp->ws_xpixel = 0;
	wp->ws_ypixel = 0;
	return (0);
}
#endif /* TIOCGWINSZ */

main(argc, argv)
	int argc;
	char **argv;
{
#ifdef ATHENA
  	char *cp = (char *) NULL;
#else
	char *host, *cp;
#endif
#ifdef POSIX
	struct termios ttyb;
#else
	struct sgttyb ttyb;
#endif
	struct passwd *pwd;
	struct servent *sp;
	int uid, options = 0, oldmask;
	int on = 1;
#ifdef KERBEROS
	KTEXT_ST ticket;
	char phost[MAX_HSTNM];	/* principal hostname, for Kerberos only */
	char **orig_argv = argv;
	int sock;
	long authopts;
#endif

	host = rindex(argv[0], '/');
	if (host)
		host++;
	else
		host = argv[0];
	argv++, --argc;
#ifdef KERBEROS
	krb_realm[0] = '\0';
#endif
	if (!strcmp(host, "rlogin"))
		host = *argv++, --argc;
#ifdef KERBEROS
	strcpy(phost,krb_get_phost(host));
#endif
another:
	if (argc > 0 && !strcmp(*argv, "-d")) {
		argv++, argc--;
		options |= SO_DEBUG;
		goto another;
	}
#ifdef ATHENA
	if (argc > 0 && !strcmp(*argv, "-c")) {
		confirm = 1;
		argv++; argc--;
		goto another;
		}
	if (argc > 0 && !strcmp(*argv, "-a")) {	   /* ask -- make remote */
		argv++; argc--;			/* machine ask for password */
		null_local_username = 1;	/* by giving null local user */
		goto another;			/* id */
	}
	if (argc > 0 && !strcmp(*argv, "-t")) {
		argv++; argc--;
		if (argc == 0) goto usage;
		cp = *argv++; argc--;
		goto another;
	}
	if (argc > 0 && !strcmp(*argv, "-n")) {
		no_local_escape = 1;
		argv++, argc--;
		goto another;
	}
	if (argc > 0 && !strcmp(*argv, "-7")) {  /* Pass only 7 bits */
		eight = 0;
		argv++, argc--;
		goto another;
	}
	if (argc > 0 && !strcmp(*argv, "-noflow")) {
		flow = 0;		/* Turn off local flow control so
					   that ^S can be passed to emacs. */
		argv++, argc--;
		goto another;
	}
#endif
	if (argc > 0 && !strcmp(*argv, "-l")) {
		argv++, argc--;
		if (argc == 0)
			goto usage;
		name = *argv++; argc--;
		goto another;
	}
	if (argc > 0 && !strncmp(*argv, "-e", 2)) {
		cmdchar = argv[0][2];
		argv++, argc--;
		goto another;
	}
	if (argc > 0 && !strcmp(*argv, "-8")) {
		eight = 1;
		argv++, argc--;
		goto another;
	}
	if (argc > 0 && !strcmp(*argv, "-L")) {
		litout = 1;
		argv++, argc--;
		goto another;
	}
#ifdef KERBEROS
	if (argc > 0 && !strcmp(*argv, "-k")) {
	        argv++, argc--;
		if (argc == 0) {
		  fprintf(stderr, "rlogin: -k flag must be followed with a realm name.\n");
		  exit (1);
		}
		strncpy(krb_realm, *argv, REALM_SZ);
		argv++, argc--;
		goto another;
	}
#ifndef NOENCRYPTION
	if (argc > 0 && !strcmp(*argv, "-x")) {
		encrypt++;
		argv++, argc--;
		goto another;
	}
#endif
#endif /* KERBEROS */
	if (host == 0)
		goto usage;
	if (argc > 0)
		goto usage;
	pwd = getpwuid(getuid());
	if (pwd == 0) {
		fprintf(stderr, "Who are you?\n");
		exit(1);
	}
#ifdef KERBEROS
	/*
	 * if there is an entry in /etc/services for Kerberos login,
	 * attempt to login with Kerberos. 
	 * If we fail at any step,  use the standard rlogin
	 */
#ifndef NOENCRYPTION
	if (encrypt)
		sp = getservbyname("eklogin","tcp");
	else
#endif /* NOENCRYPTION */
		sp = getservbyname("klogin","tcp");
	if (sp == 0) {
#ifdef NOENCRYPTION
		fprintf(stderr, "rlogin: klogin/tcp: unknown service\n");
#else
		fprintf(stderr, "rlogin: %s/tcp: unknown service\n",
			encrypt ? "eklogin" : "klogin");
#endif /* NOENCRYPTION */
		
		try_normal(orig_argv);
	}
#else
	sp = getservbyname("login", "tcp");
	if (sp == 0) {
		fprintf(stderr, "rlogin: login/tcp: unknown service\n");
		exit(2);
	}
#endif /* KERBEROS */
#ifdef ATHENA
	if (cp == (char *) NULL) cp = getenv("TERM");
#else
	cp = getenv("TERM");
#endif
	if (cp)
		(void) strcpy(term, cp);
#ifdef POSIX
	if (tcgetattr(0, &ttyb) == 0) {
		(void) strcat(term, "/");
		(void) strcat(term, speeds[ttyb.c_cflag & CBAUD]);
	}
#else
	if (ioctl(0, TIOCGETP, &ttyb) == 0) {
		(void) strcat(term, "/");
		(void) strcat(term, speeds[ttyb.sg_ospeed]);
	}
#endif
	(void) get_window_size(0, &winsize);
	(void) signal(SIGPIPE, lostpeer);
	/* will use SIGUSR1 for window size hack, so hold it off */
	oldmask = sigblock(sigmask(SIGURG) | sigmask(SIGUSR1));

#ifdef KERBEROS
	rem=KSUCCESS;
#ifndef NOENCRYPTION
	if (encrypt) {
		authopts = KOPT_DO_MUTUAL;
	} else
#endif /* NOENCRYPTION */
	{
#ifdef ATHENA_COMPAT
		authopts = KOPT_DO_OLDSTYLE;
#else
		authopts = 0L;
#endif
	}
	rem = kcmd(&sock, &host, sp->s_port,
#ifdef ATHENA
		   null_local_username ? NULL : pwd->pw_name,
#else
		   pwd->pw_name,
#endif
		   name ? name : pwd->pw_name, term,
		   0, &ticket, "rcmd", krb_realm,
		   &cred, schedule, &msg_data, &local, &foreign,
		   authopts);
	if (rem != KSUCCESS) {
	    if (rem == KDC_PR_UNKNOWN) /* assume the foreign principal
					    isn't registered */
		fprintf(stderr, "%s: Host %s isn't registered for Kerberos rlogin service\n",
			orig_argv[0], host);
	    else
		fprintf(stderr,
			"%s: Kerberos rcmd failed: %s.\n",
			orig_argv[0],
			(rem == -1) ? "rcmd protocol failure" :
			krb_err_txt[rem]);
	    rem = -1;
	}
	if (rem == -1)
		try_normal(orig_argv);
	rem = sock;
#else
        rem = rcmd(&host, sp->s_port,
#ifdef ATHENA
		   null_local_username ? NULL : pwd->pw_name,
#else
		   pwd->pw_name,
#endif
	    name ? name : pwd->pw_name, term, 0);
        if (rem < 0)
                exit(1);
#endif /* KERBEROS */
	/* we need to do the SETOWN here so that we get the SIGURG
	   registered if the URG data come in early, before the reader() gets
	   to do this for real (otherwise, the signal is never generated
	   by the kernel).  We block it above, so when it gets unblocked
	   it will get processed by the reader().
	   There is a possibility that the signal will get delivered to both
	   writer and reader, but that is harmless, since the writer reflects
	   it to the reader, and the oob() processing code in the reader will
	   work properly even if it is called when no oob() data is present.
	 */
	(void) fcntl(rem, F_SETOWN, getpid());
	if (options & SO_DEBUG &&
	    setsockopt(rem, SOL_SOCKET, SO_DEBUG, &on, sizeof (on)) < 0)
		perror("rlogin: setsockopt (SO_DEBUG)");
	uid = getuid();
	if (setuid(uid) < 0) {
		perror("rlogin: setuid");
		exit(1);
	}
#ifdef ATHENA
	flowcontrol = flow;  /* Set up really correct non-volatile variable */
#endif
	doit(oldmask);
	/*NOTREACHED*/
usage:
#ifdef KERBEROS
#ifdef ATHENA
	fprintf (stderr,
"usage: rlogin host [-option] [-option...] [-k realm ] [-t ttytype] [-l username]\n");
#ifdef NOENCRYPTION
    	fprintf (stderr, "     where option is e, 7, 8, noflow, n, a, or c\n");
#else /* !NOENCRYPTION */
    	fprintf (stderr, "     where option is e, 7, 8, noflow, n, a, x, or c\n");
#endif /* NOENCRYPTION */
#else /* !ATHENA */
#ifdef NOENCRYPTION
	fprintf (stderr,
"usage: rlogin host [ -ex ] [-k realm ] [-l username] [ -8 ] [ -L ]\n");
#else /* !NOENCRYPTION */
	fprintf (stderr,
"usage: rlogin host [ -ex ] [ -x ] [-k realm ] [-l username] [ -8 ] [ -L ]\n");
#endif /* NOENCRYPTION */
#endif /* ATHENA */
#else /* !KERBEROS */
#ifdef ATHENA
	fprintf (stderr,
"usage: rlogin host [-option] [-option...] [-t ttytype] [-l username]\n");
    	fprintf (stderr, "     where option is e, 7, 8, noflow, n, a, or c\n");
#else !ATHENA
	fprintf(stderr,
	    "usage: rlogin host [ -ex ] [ -l username ] [ -8 ] [ -L ]\n");
#endif /* ATHENA */
#endif /* KERBEROS */
	exit(1);
}

#ifdef ATHENA
int
confirm_death ()
{
	char hostname[33];
	char input;
	int answer;
	if (!confirm) return (1);	/* no confirm, just die */

	if (gethostname (hostname, sizeof(hostname)-1) != 0)
		strcpy (hostname, "???");
	else
		hostname[sizeof(hostname)-1] = '\0';

	fprintf (stderr, "\r\nKill session on %s from %s (y/n)?  ",
			 host, hostname);
	fflush (stderr);
	if (read(0, &input, 1) != 1)
		answer = EOF;	/* read from stdin */
	else
		answer = (int) input;
	fprintf (stderr, "%c\r\n", answer);
	fflush (stderr);
	return (answer == 'y' || answer == 'Y' || answer == EOF ||
		answer == 4);	/* control-D */
}
#endif /* ATHENA */

#define CRLF "\r\n"

int	child;
int	catchild();
int	copytochild(), writeroob();

#ifdef TIOCGLTC
/*
 * POSIX 1003.1-1988 does not define a 'suspend' character.
 * POSIX 1003.1-1990 does define an optional VSUSP but not a VDSUSP character.
 * Some termio implementations (A/UX, Ultrix 4.2) include both.
 *
 * However, since this is all derived from the BSD ioctl() and ltchars
 * concept, all these implementations generally also allow for the BSD-style
 * ioctl().  So we'll simplify the problem by only testing for the ioctl().
 */
struct	ltchars defltc;
struct	ltchars noltc =	{ -1, -1, -1, -1, -1, -1 };
#endif

#ifndef POSIX
int	defflags, tabflag;
int	deflflags;
char	deferase, defkill;
struct	tchars deftc;
struct	tchars notc =	{ -1, -1, -1, -1, -1, -1 };
#endif

doit(oldmask)
{
	int exit();
#ifdef POSIX
	(void) tcgetattr(0, &deftty);
#ifdef TIOCGLTC
	(void) ioctl(0, TIOCGLTC, (char *)&defltc);
#endif
#else
	struct sgttyb sb;

	(void) ioctl(0, TIOCGETP, (char *)&sb);
	defflags = sb.sg_flags;
	tabflag = defflags & TBDELAY;
	defflags &= ECHO | CRMOD;
	deferase = sb.sg_erase;
	defkill = sb.sg_kill;
	(void) ioctl(0, TIOCLGET, (char *)&deflflags);
	(void) ioctl(0, TIOCGETC, (char *)&deftc);
	notc.t_startc = deftc.t_startc;
	notc.t_stopc = deftc.t_stopc;
	(void) ioctl(0, TIOCGLTC, (char *)&defltc);
#endif
	(void) signal(SIGINT, SIG_IGN);
	setsignal(SIGHUP, exit);
	setsignal(SIGQUIT, exit);
	child = fork();
	if (child == -1) {
		perror("rlogin: fork");
		done(1);
	}
	if (child == 0) {
		mode(1);
		if (reader(oldmask) == 0) {
			prf("Connection closed.");
			exit(0);
		}
		sleep(1);
		prf("\007Connection closed.");
		exit(3);
	}

	/*
	 * We may still own the socket, and may have a pending SIGURG
	 * (or might receive one soon) that we really want to send to
	 * the reader.  Set a trap that simply copies such signals to
	 * the child.
	 */
	(void) signal(SIGURG, copytochild);
	(void) signal(SIGUSR1, writeroob);
	(void) sigsetmask(oldmask);
	(void) signal(SIGCHLD, catchild);
	writer();
	prf("Closed connection.");
	done(0);
}

/*
 * Trap a signal, unless it is being ignored.
 */
setsignal(sig, act)
	int sig, (*act)();
{
	int omask = sigblock(sigmask(sig));

	if (signal(sig, act) == SIG_IGN)
		(void) signal(sig, SIG_IGN);
	(void) sigsetmask(omask);
}

done(status)
	int status;
{
	int w;

	mode(0);
	if (child > 0) {
		/* make sure catchild does not snap it up */
		(void) signal(SIGCHLD, SIG_DFL);
		if (kill(child, SIGKILL) >= 0) {
#if 1 /* def POSIX */
			while ((w = wait(0)) > 0 && w != child)
				/*void*/;
#else
			while ((w = wait((union wait *)0)) > 0 && w != child)
				/*void*/;
#endif
		}
	}
	exit(status);
}

/*
 * Copy SIGURGs to the child process.
 */
copytochild()
{

	(void) kill(child, SIGURG);
}

/*
 * This is called when the reader process gets the out-of-band (urgent)
 * request to turn on the window-changing protocol.
 */
writeroob()
{

	if (dosigwinch == 0) {
		sendwindow();
		(void) signal(SIGWINCH, sigwinch);
	}
	dosigwinch = 1;
}

catchild()
{
	int pid;
#ifdef POSIX
	int status;
#else
	union wait status;
#endif

again:
	pid = wait3(&status, WNOHANG|WUNTRACED, (struct rusage *)0);
	if (pid == 0)
		return;
	/*
	 * if the child (reader) dies, just quit
	 */
#ifdef POSIX
	if (pid < 0 || pid == child && !WIFSTOPPED(status))
		done(status);
#else
	if (pid < 0 || pid == child && !WIFSTOPPED(status))
		done((int)(status.w_termsig | status.w_retcode));
#endif
	goto again;
}

/*
 * writer: write to remote: 0 -> line.
 * ~.	terminate
 * ~^Z	suspend rlogin process.
 * ~^Y  suspend rlogin process, but leave reader alone.
 */
writer()
{
	char c;
	register int n;
	register int bol = 1;			/* beginning of line */
	register int local = 0;

#ifdef ultrix
	fd_set waitread;

	/* we need to wait until the reader() has set up the terminal, else
	   the read() below may block and not unblock when the terminal
	   state is reset.
	   */
	for (;;) {
	    FD_ZERO(&waitread);
	    FD_SET(0, &waitread);
	    n = select(1, &waitread, 0, 0, 0, 0);
	    if (n < 0 && errno == EINTR)
		continue;
	    if (n > 0)
		break;
	    else
		if (n < 0) {
		    perror("select");
		    break;
		}
	}
#endif /* ultrix */
	for (;;) {
		n = read(0, &c, 1);
		if (n <= 0) {
			if (n < 0 && errno == EINTR)
				continue;
			break;
		}
		/*
		 * If we're at the beginning of the line
		 * and recognize a command character, then
		 * we echo locally.  Otherwise, characters
		 * are echo'd remotely.  If the command
		 * character is doubled, this acts as a 
		 * force and local echo is suppressed.
		 */
		if (bol) {
			bol = 0;
			if (c == cmdchar) {
				bol = 0;
				local = 1;
				continue;
			}
		} else if (local) {
			local = 0;
#ifdef POSIX
			if (c == '.' || c == deftty.c_cc[VEOF])
#else
			if (c == '.' || c == deftc.t_eofc)
#endif
			{
#ifdef ATHENA
			    if (confirm_death())
#endif
			    {
				echo(c);
				break;
			    }
			}
#ifdef TIOCGLTC
			if ((c == defltc.t_suspc || c == defltc.t_dsuspc)
#ifdef ATHENA
  				&& !no_local_escape) {
#else
				) {
#endif
				bol = 1;
				echo(c);
				stop(c);
				continue;
			}
#endif
			if (c != cmdchar)
				(void) des_write(rem, &cmdchar, 1);
		}
		if (des_write(rem, &c, 1) == 0) {
			prf("line gone");
			break;
		}
#ifdef POSIX
		bol = (c == deftty.c_cc[VKILL] ||
		       c == deftty.c_cc[VINTR] ||
		       c == '\r' || c == '\n');
#ifdef TIOCGLTC
		if (!bol)
			bol = (c == defltc.t_suspc);
#endif
#else /* !POSIX */
		bol = c == defkill || c == deftc.t_eofc ||
		    c == deftc.t_intrc || c == defltc.t_suspc ||
		    c == '\r' || c == '\n';
#endif
	}
}

echo(c)
register char c;
{
	char buf[8];
	register char *p = buf;

	c &= 0177;
	*p++ = cmdchar;
	if (c < ' ') {
		*p++ = '^';
		*p++ = c + '@';
	} else if (c == 0177) {
		*p++ = '^';
		*p++ = '?';
	} else
		*p++ = c;
	*p++ = '\r';
	*p++ = '\n';
	(void) write(1, buf, p - buf);
}

stop(cmdc)
	char cmdc;
{
	mode(0);
	(void) signal(SIGCHLD, SIG_IGN);
#ifdef TIOCGLTC
	(void) kill(cmdc == defltc.t_suspc ? 0 : getpid(), SIGTSTP);
#endif
	(void) signal(SIGCHLD, catchild);
	mode(1);
	sigwinch();			/* check for size changes */
}

sigwinch()
{
	struct winsize ws;

	if (dosigwinch && get_window_size(0, &ws) == 0 &&
	    bcmp(&ws, &winsize, sizeof (ws))) {
		winsize = ws;
		sendwindow();
	}
}

/*
 * Send the window size to the server via the magic escape
 */
sendwindow()
{
	char obuf[4 + sizeof (struct winsize)];
	struct winsize *wp = (struct winsize *)(obuf+4);

	obuf[0] = 0377;
	obuf[1] = 0377;
	obuf[2] = 's';
	obuf[3] = 's';
	wp->ws_row = htons(winsize.ws_row);
	wp->ws_col = htons(winsize.ws_col);
	wp->ws_xpixel = htons(winsize.ws_xpixel);
	wp->ws_ypixel = htons(winsize.ws_ypixel);
	(void) des_write(rem, obuf, sizeof(obuf));
}

/*
 * reader: read from remote: line -> 1
 */
#define	READING	1
#define	WRITING	2

char	rcvbuf[8 * 1024];
int	rcvcnt;
int	rcvstate;
int	ppid;
jmp_buf	rcvtop;

oob()
{
	int atmark, n;
	int rcvd = 0;
	char waste[BUFSIZ], mark;
#ifdef POSIX
	struct termios tty;
#else
	int out = FWRITE;
	struct sgttyb sb;
#endif

	while (recv(rem, &mark, 1, MSG_OOB) < 0)
		switch (errno) {
		
		case EWOULDBLOCK:
			/*
			 * Urgent data not here yet.
			 * It may not be possible to send it yet
			 * if we are blocked for output
			 * and our input buffer is full.
			 */
			if (rcvcnt < sizeof(rcvbuf)) {
				n = read(rem, rcvbuf + rcvcnt,
					sizeof(rcvbuf) - rcvcnt);
				if (n <= 0)
					return;
				rcvd += n;
			} else {
				n = read(rem, waste, sizeof(waste));
				if (n <= 0)
					return;
			}
			continue;
				
		default:
			return;
	}
	if (mark & TIOCPKT_WINDOW) {
		/*
		 * Let server know about window size changes
		 */
		(void) kill(ppid, SIGUSR1);
	}
#ifdef POSIX
	if (!eight && (mark & TIOCPKT_NOSTOP)) {
		(void) tcgetattr(0, &tty);
		tty.c_iflag &= ~IXON;
		(void) tcsetattr(0, TCSADRAIN, &tty);
	}
	if (!eight && (mark & TIOCPKT_DOSTOP)) {
		(void) tcgetattr(0, &tty);
		tty.c_iflag |= IXON;
		(void) tcsetattr(0, TCSADRAIN, &tty);
	}
#else
	if (!eight && (mark & TIOCPKT_NOSTOP)) {
		(void) ioctl(0, TIOCGETP, (char *)&sb);
		sb.sg_flags &= ~CBREAK;
		sb.sg_flags |= RAW;
		(void) ioctl(0, TIOCSETN, (char *)&sb);
		notc.t_stopc = -1;
		notc.t_startc = -1;
		(void) ioctl(0, TIOCSETC, (char *)&notc);
	}
	if (!eight && (mark & TIOCPKT_DOSTOP)) {
		(void) ioctl(0, TIOCGETP, (char *)&sb);
		sb.sg_flags &= ~RAW;
		sb.sg_flags |= CBREAK;
		(void) ioctl(0, TIOCSETN, (char *)&sb);
		notc.t_stopc = deftc.t_stopc;
		notc.t_startc = deftc.t_startc;
		(void) ioctl(0, TIOCSETC, (char *)&notc);
	}
#endif
	if (mark & TIOCPKT_FLUSHWRITE) {
#ifdef POSIX
		(void) tcflush(1, TCOFLUSH);
#else
		(void) ioctl(1, TIOCFLUSH, (char *)&out);
#endif
		for (;;) {
			if (ioctl(rem, SIOCATMARK, &atmark) < 0) {
				perror("ioctl");
				break;
			}
			if (atmark)
				break;
			n = read(rem, waste, sizeof (waste));
			if (n <= 0)
				break;
		}
		/*
		 * Don't want any pending data to be output,
		 * so clear the recv buffer.
		 * If we were hanging on a write when interrupted,
		 * don't want it to restart.  If we were reading,
		 * restart anyway.
		 */
		rcvcnt = 0;
		longjmp(rcvtop, 1);
	}

	/*
	 * oob does not do FLUSHREAD (alas!)
	 */

	/*
	 * If we filled the receive buffer while a read was pending,
	 * longjmp to the top to restart appropriately.  Don't abort
	 * a pending write, however, or we won't know how much was written.
	 */
	if (rcvd && rcvstate == READING)
		longjmp(rcvtop, 1);
}

/*
 * reader: read from remote: line -> 1
 */
reader(oldmask)
	int oldmask;
{
#if (defined(BSD) && BSD >= 43) || defined(ultrix) || defined(_IBMR2)
	int pid = getpid();
#else
	int pid = -getpid();
#endif
	int n, remaining;
	char *bufp = rcvbuf;

	(void) signal(SIGTTOU, SIG_IGN);
	(void) signal(SIGURG, oob);
	ppid = getppid();
	(void) fcntl(rem, F_SETOWN, pid);
	(void) setjmp(rcvtop);
	(void) sigsetmask(oldmask);
	for (;;) {
		while ((remaining = rcvcnt - (bufp - rcvbuf)) > 0) {
			rcvstate = WRITING;
			n = write(1, bufp, remaining);
			if (n < 0) {
				if (errno != EINTR)
					return (-1);
				continue;
			}
			bufp += n;
		}
		bufp = rcvbuf;
		rcvcnt = 0;
		rcvstate = READING;
		rcvcnt = des_read(rem, rcvbuf, sizeof (rcvbuf));
		if (rcvcnt == 0)
			return (0);
		if (rcvcnt < 0) {
			if (errno == EINTR)
				continue;
			perror("read");
			return (-1);
		}
	}
}

mode(f)
	int f;
{
#ifdef POSIX
	struct termios newtty;

	switch(f) {
	case 0:
#ifdef TIOCGLTC
		(void) ioctl(0, TIOCSLTC, (char *)&defltc);
#endif
		(void) tcsetattr(0, TCSADRAIN, &deftty);
		break;
	case 1:
		(void) tcgetattr(0, &newtty);
		
		newtty.c_lflag &= ~(ICANON|ISIG|ECHO);
#ifdef ATHENA
		if (!flow)
#else
		if (eight)
#endif
		{
			newtty.c_lflag &= ~(ICANON|ISIG|ECHO);
			newtty.c_iflag &= ~(BRKINT|INLCR|ICRNL|ISTRIP);
			newtty.c_iflag |=  (IXON|IXANY);
			newtty.c_oflag &= ~(OPOST);
		} else {
			newtty.c_lflag &= ~(ICANON|ISIG|ECHO);
			newtty.c_iflag &= ~(INLCR|ICRNL);
			newtty.c_iflag |=  (BRKINT|ISTRIP|IXON|IXANY);
			newtty.c_oflag &= ~(ONLCR|ONOCR);
			newtty.c_oflag |=  (OPOST);
		}
		/* preserve tab delays, but turn off XTABS */
		if ((newtty.c_oflag & TABDLY) == TAB3)
			newtty.c_oflag &= ~TABDLY;

		if (litout)
			newtty.c_oflag &= ~OPOST;

		newtty.c_cc[VMIN] = 1;
		newtty.c_cc[VTIME] = 0;
		(void) tcsetattr(0, TCSADRAIN, &newtty);
#ifdef TIOCGLTC
		/* Do this after the tcsetattr() in case this version
		 * of termio supports the VSUSP or VDSUSP characters */
		(void) ioctl(0, TIOCSLTC, (char *)&noltc);
#endif
		break;
	default:
		return;
		/* NOTREACHED */
	}
#else
	struct tchars *tc;
	struct ltchars *ltc;
	struct sgttyb sb;
	int	lflags;

	(void) ioctl(0, TIOCGETP, (char *)&sb);
	(void) ioctl(0, TIOCLGET, (char *)&lflags);
	switch (f) {

	case 0:
		sb.sg_flags &= ~(CBREAK|RAW|TBDELAY);
		sb.sg_flags |= defflags|tabflag;
		tc = &deftc;
		ltc = &defltc;
		sb.sg_kill = defkill;
		sb.sg_erase = deferase;
		lflags = deflflags;
		break;

	case 1:
#ifdef ATHENA
  		sb.sg_flags &= ~(CBREAK|RAW);
  		sb.sg_flags |= (!flow ? RAW : CBREAK);
#else
		sb.sg_flags |= (eight ? RAW : CBREAK);
#endif
		sb.sg_flags &= ~defflags;
		/* preserve tab delays, but turn off XTABS */
		if ((sb.sg_flags & TBDELAY) == XTABS)
			sb.sg_flags &= ~TBDELAY;
		tc = &notc;
		ltc = &noltc;
		sb.sg_kill = sb.sg_erase = -1;
		if (litout)
			lflags |= LLITOUT;
#if defined(ATHENA) && defined(LPASS8)
		if (eight)
			lflags |= LPASS8;
#endif
		break;

	default:
		return;
	}
	(void) ioctl(0, TIOCSLTC, (char *)ltc);
	(void) ioctl(0, TIOCSETC, (char *)tc);
	(void) ioctl(0, TIOCSETN, (char *)&sb);
	(void) ioctl(0, TIOCLSET, (char *)&lflags);
#endif /* !POSIX */
}

/*VARARGS*/
prf(f, a1, a2, a3, a4, a5)
	char *f;
{

	fprintf(stderr, f, a1, a2, a3, a4, a5);
	fprintf(stderr, CRLF);
}

#ifdef KERBEROS
void
try_normal(argv)
char **argv;
{
	register char *host;

#ifndef NOENCRYPTION
	if (encrypt)
		exit(1);
#endif
	fprintf(stderr,"trying normal rlogin (%s)\n",
		UCB_RLOGIN);
	fflush(stderr);

	host = rindex(argv[0], '/');
	if (host)
		host++;
	else
		host = argv[0];
	if (!strcmp(host, "rlogin"))
		argv++;

	execv(UCB_RLOGIN, argv);
	perror("exec");
	exit(1);
}

#ifndef NOENCRYPTION
char des_inbuf[10240];			/* needs to be > largest read size */
char des_outbuf[10240];			/* needs to be > largest write size */
char storage[10240];			/* storage for the decryption */
int nstored = 0;
char *store_ptr;

int
des_read(fd, buf, len)
int fd;
register char *buf;
int len;
{
	int nreturned = 0;
	long net_len, rd_len;
	int cc;

	if (!encrypt)
		return(read(fd, buf, len));

	if (nstored >= len) {
		bcopy(store_ptr, buf, len);
		store_ptr += len;
		nstored -= len;
		return(len);
	} else if (nstored) {
		bcopy(store_ptr, buf, nstored);
		nreturned += nstored;
		buf += nstored;
		len -= nstored;
		nstored = 0;
	}
	
	if ((cc = krb_net_read(fd, &net_len, sizeof(net_len))) != sizeof(net_len)) {
		/* XXX can't read enough, pipe
		   must have closed */
		return(0);
	}
	net_len = ntohl(net_len);
	if (net_len <= 0 || net_len > sizeof(des_inbuf)) {
		/* preposterous length; assume out-of-sync; only
		   recourse is to close connection, so return 0 */
		return(0);
	}
	/* the writer tells us how much real data we are getting, but
	   we need to read the pad bytes (8-byte boundary) */
	rd_len = roundup(net_len, 8);
	if ((cc = krb_net_read(fd, des_inbuf, rd_len)) != rd_len) {
		/* pipe must have closed, return 0 */
		return(0);
	}
	(void) pcbc_encrypt(des_inbuf,
			    storage,
			    (net_len < 8) ? 8 : net_len,
			    schedule,
			    cred.session,
			    DECRYPT);
	/* 
	 * when the cleartext block is < 8 bytes, it is "right-justified"
	 * in the block, so we need to adjust the pointer to the data
	 */
	if (net_len < 8)
		store_ptr = storage + 8 - net_len;
	else
		store_ptr = storage;

	nstored = net_len;
	if (nstored > len) {
		bcopy(store_ptr, buf, len);
		nreturned += len;
		store_ptr += len;
		nstored -= len;
	} else {
		bcopy(store_ptr, buf, nstored);
		nreturned += nstored;
		nstored = 0;
	}
	
	return(nreturned);
}

int
des_write(fd, buf, len)
int fd;
char *buf;
int len;
{
	long net_len;
	static int seeded = 0;
	static char garbage_buf[8];
	long garbage;

	if (!encrypt)
		return(write(fd, buf, len));

	/* 
	 * pcbc_encrypt outputs in 8-byte (64 bit) increments
	 *
	 * it zero-fills the cleartext to 8-byte padding,
	 * so if we have cleartext of < 8 bytes, we want
	 * to insert random garbage before it so that the ciphertext
	 * differs for each transmission of the same cleartext.
	 * if len < 8 - sizeof(long), sizeof(long) bytes of random
	 * garbage should be sufficient; leave the rest as-is in the buffer.
	 * if len > 8 - sizeof(long), just garbage fill the rest.
	 */

#define min(a,b) ((a < b) ? a : b)

	if (len < 8) {
		if (!seeded) {
			seeded = 1;
			srandom((int) time((long *)0));
		}
		garbage = random();
		/* insert random garbage */
		(void) bcopy(&garbage, garbage_buf, min(sizeof(long),8));

		/* this "right-justifies" the data in the buffer */
		(void) bcopy(buf, garbage_buf + 8 - len, len);
	}

	(void) pcbc_encrypt((len < 8) ? garbage_buf : buf,
			    des_outbuf,
			    (len < 8) ? 8 : len,
			    schedule,
			    cred.session,
			    ENCRYPT);

	/* tell the other end the real amount, but send an 8-byte padded
	   packet */
	net_len = htonl(len);
	(void) write(fd, &net_len, sizeof(net_len));
	(void) write(fd, des_outbuf, roundup(len,8));
	return(len);
}
#endif /* NOENCRYPTION */
#endif /* KERBEROS */
lostpeer()
{

	(void) signal(SIGPIPE, SIG_IGN);
	prf("\007Connection closed.");
	done(1);
}
