/*-
 * Copyright (c) 1983, 1988, 1989, 1993
 *	The Regents of the University of California.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by the University of
 *	California, Berkeley and its contributors.
 * 4. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/*
 * remote login server:
 *	\0
 *	remuser\0
 *	locuser\0
 *	terminal_type/speed\0
 *	data
 */

#include "bsd_locl.h"

RCSID("$Id: rlogind.c,v 1.103 1998/04/04 13:12:48 assar Exp $");

extern int __check_rhosts_file;

char *INSECURE_MESSAGE =
"\r\n*** Connection not encrypted! Communication may be eavesdropped. ***"
"\r\n*** Use telnet or rlogin -x instead! ***\r\n";

#ifndef NOENCRYPTION
char *SECURE_MESSAGE =
"This rlogin session is using DES encryption for all transmissions.\r\n";
#else
#define	SECURE_MESSAGE INSECURE_MESSAGE
#endif

AUTH_DAT	*kdata;
KTEXT		ticket;
u_char		auth_buf[sizeof(AUTH_DAT)];
u_char		tick_buf[sizeof(KTEXT_ST)];
Key_schedule	schedule;
int		doencrypt, retval, use_kerberos, vacuous;

#define		ARGSTR			"Daip:lnkvxL:"

char	*env[2];
#define	NMAX 30
char	lusername[NMAX+1], rusername[NMAX+1];
static	char term[64] = "TERM=";
#define	ENVSIZE	(sizeof("TERM=")-1)	/* skip null for concatenation */
int	keepalive = 1;
int	check_all = 0;
int     no_delay = 0;

struct	passwd *pwd;

static const char *new_login = _PATH_LOGIN;

static void	doit (int, struct sockaddr_in *);
static int	control (int, char *, int);
static void	protocol (int, int);
static RETSIGTYPE cleanup (int);
void	fatal (int, const char *, int);
static int	do_rlogin (struct sockaddr_in *);
static void	setup_term (int);
static int	do_krb_login (struct sockaddr_in *);
static void	usage (void);

static int
readstream(int p, char *ibuf, int bufsize)
{
#ifndef HAVE_GETMSG
    return read(p, ibuf, bufsize);
#else
    static int flowison = -1;  /* current state of flow: -1 is unknown */
    static struct strbuf strbufc, strbufd;
    static unsigned char ctlbuf[BUFSIZ];
    static int use_read = 1;

    int flags = 0;
    int ret;
    struct termios tsp;

    struct iocblk ip;
    char vstop, vstart;
    int ixon;
    int newflow;

    if (use_read)
	{
	    ret = read(p, ibuf, bufsize);
	    if (ret < 0 && errno == EBADMSG)
		use_read = 0;
	    else
		return ret;
	}

    strbufc.maxlen = BUFSIZ;
    strbufc.buf = (char *)ctlbuf;
    strbufd.maxlen = bufsize-1;
    strbufd.len = 0;
    strbufd.buf = ibuf+1;
    ibuf[0] = 0;

    ret = getmsg(p, &strbufc, &strbufd, &flags);
    if (ret < 0)  /* error of some sort -- probably EAGAIN */
	return(-1);

    if (strbufc.len <= 0 || ctlbuf[0] == M_DATA) {
	/* data message */
	if (strbufd.len > 0) {			/* real data */
	    return(strbufd.len + 1);	/* count header char */
	} else {
	    /* nothing there */
	    errno = EAGAIN;
	    return(-1);
	}
    }

    /*
     * It's a control message.  Return 1, to look at the flag we set
     */

    switch (ctlbuf[0]) {
    case M_FLUSH:
	if (ibuf[1] & FLUSHW)
	    ibuf[0] = TIOCPKT_FLUSHWRITE;
	return(1);

    case M_IOCTL:
	memcpy(&ip, (ibuf+1), sizeof(ip));

	switch (ip.ioc_cmd) {
#ifdef TCSETS
	case TCSETS:
	case TCSETSW:
	case TCSETSF:
	    memcpy(&tsp,
		   (ibuf+1 + sizeof(struct iocblk)),
		   sizeof(tsp));
	    vstop = tsp.c_cc[VSTOP];
	    vstart = tsp.c_cc[VSTART];
	    ixon = tsp.c_iflag & IXON;
	    break;
#endif
	default:
	    errno = EAGAIN;
	    return(-1);
	}

	newflow =  (ixon && (vstart == 021) && (vstop == 023)) ? 1 : 0;
	if (newflow != flowison) {  /* it's a change */
	    flowison = newflow;
	    ibuf[0] = newflow ? TIOCPKT_DOSTOP : TIOCPKT_NOSTOP;
	    return(1);
	}
    }

    /* nothing worth doing anything about */
    errno = EAGAIN;
    return(-1);
#endif
}

#ifdef HAVE_UTMPX_H
static int
logout(const char *line)
{
    struct utmpx utmpx, *utxp;
    int ret = 1;

    setutxent ();
    memset(&utmpx, 0, sizeof(utmpx));
    utmpx.ut_type = USER_PROCESS;
    strncpy(utmpx.ut_line, line, sizeof(utmpx.ut_line));
    utxp = getutxline(&utmpx);
    if (utxp) {
	strcpy(utxp->ut_user, "");
	utxp->ut_type = DEAD_PROCESS;
#ifdef HAVE_UT_EXIT
#ifdef _STRUCT___EXIT_STATUS
	utxp->ut_exit.__e_termination = 0;
	utxp->ut_exit.__e_exit = 0;
#elif defined(__osf__) /* XXX */
	utxp->ut_exit.ut_termination = 0;
	utxp->ut_exit.ut_exit = 0;
#else	
	utxp->ut_exit.e_termination = 0;
	utxp->ut_exit.e_exit = 0;
#endif
#endif
	gettimeofday(&utxp->ut_tv, NULL);
	pututxline(utxp);
#ifdef WTMPX_FILE
	updwtmpx(WTMPX_FILE, utxp);
#else
	ret = 0;
#endif
    }
    endutxent();
    return ret;
}
#else
static int
logout(const char *line)
{
    FILE *fp;
    struct utmp ut;
    int rval;

    if (!(fp = fopen(_PATH_UTMP, "r+")))
	return(0);
    rval = 1;
    while (fread(&ut, sizeof(struct utmp), 1, fp) == 1) {
	if (!ut.ut_name[0] ||
	    strncmp(ut.ut_line, line, sizeof(ut.ut_line)))
	    continue;
	memset(ut.ut_name, 0, sizeof(ut.ut_name));
#ifdef HAVE_UT_HOST
	memset(ut.ut_host, 0, sizeof(ut.ut_host));
#endif
#ifdef HAVE_UT_TYPE
	ut.ut_type = DEAD_PROCESS;
#endif
#ifdef HAVE_UT_EXIT
#ifdef _STRUCT___EXIT_STATUS
	ut.ut_exit.__e_termination = 0;
	ut.ut_exit.__e_exit = 0;
#elif defined(__osf__) /* XXX */
	ut.ut_exit.ut_termination = 0;
	ut.ut_exit.ut_exit = 0;
#else	
	ut.ut_exit.e_termination = 0;
	ut.ut_exit.e_exit = 0;
#endif
#endif
	time(&ut.ut_time);
	fseek(fp, (long)-sizeof(struct utmp), SEEK_CUR);
	fwrite(&ut, sizeof(struct utmp), 1, fp);
	fseek(fp, (long)0, SEEK_CUR);
	rval = 0;
    }
    fclose(fp);
    return(rval);
}
#endif

#ifndef HAVE_LOGWTMP
static void
logwtmp(const char *line, const char *name, const char *host)
{
    struct utmp ut;
    struct stat buf;
    int fd;

    memset (&ut, 0, sizeof(ut));
    if ((fd = open(_PATH_WTMP, O_WRONLY|O_APPEND, 0)) < 0)
	return;
    if (!fstat(fd, &buf)) {
	strncpy(ut.ut_line, line, sizeof(ut.ut_line));
	strncpy(ut.ut_name, name, sizeof(ut.ut_name));
#ifdef HAVE_UT_HOST
	strncpy(ut.ut_host, host, sizeof(ut.ut_host));
#endif
#ifdef HAVE_UT_PID
	ut.ut_pid = getpid();
#endif
#ifdef HAVE_UT_TYPE
	if(name[0])
	    ut.ut_type = USER_PROCESS;
	else
	    ut.ut_type = DEAD_PROCESS;
#endif
	time(&ut.ut_time);
	if (write(fd, &ut, sizeof(struct utmp)) !=
	    sizeof(struct utmp))
	    ftruncate(fd, buf.st_size);
    }
    close(fd);
}
#endif

int
main(int argc, char **argv)
{
    struct sockaddr_in from;
    int ch, fromlen, on;
    int interactive = 0;
    int portnum = 0;

    set_progname(argv[0]);

    openlog("rlogind", LOG_PID | LOG_CONS, LOG_AUTH);

    opterr = 0;
    while ((ch = getopt(argc, argv, ARGSTR)) != EOF)
	switch (ch) {
	case 'D':
	    no_delay = 1;
	    break;
	case 'a':
	    break;
	case 'i':
	    interactive = 1;
	    break;
	case 'p':
	    portnum = htons(atoi(optarg));
	    break;
	case 'l':
	    __check_rhosts_file = 0;
	    break;
	case 'n':
	    keepalive = 0;
	    break;
	case 'k':
	    use_kerberos = 1;
	    break;
	case 'v':
	    vacuous = 1;
	    break;
	case 'x':
	    doencrypt = 1;
	    break;
	case 'L':
	    new_login = optarg;
	    break;
	case '?':
	default:
	    usage();
	    break;
	}
    argc -= optind;
    argv += optind;

    if (use_kerberos && vacuous) {
	usage();
	fatal(STDERR_FILENO, "only one of -k and -v allowed", 0);
    }
    if (interactive) {
	if(portnum == 0)
	    portnum = get_login_port (use_kerberos, doencrypt);
	mini_inetd (portnum);
    }

    fromlen = sizeof (from);
    if (getpeername(0, (struct sockaddr *)&from, &fromlen) < 0) {
	syslog(LOG_ERR,"Can't get peer name of remote host: %m");
	fatal(STDERR_FILENO, "Can't get peer name of remote host", 1);
    }
    on = 1;
#ifdef HAVE_SETSOCKOPT
#ifdef SO_KEEPALIVE
    if (keepalive &&
	setsockopt(0, SOL_SOCKET, SO_KEEPALIVE, (void *)&on,
		   sizeof (on)) < 0)
	syslog(LOG_WARNING, "setsockopt (SO_KEEPALIVE): %m");
#endif
#ifdef TCP_NODELAY
    if (no_delay &&
	setsockopt(0, IPPROTO_TCP, TCP_NODELAY, (void *)&on,
		   sizeof(on)) < 0)
	syslog(LOG_WARNING, "setsockopt (TCP_NODELAY): %m");
#endif

#ifdef IP_TOS
    on = IPTOS_LOWDELAY;
    if (setsockopt(0, IPPROTO_IP, IP_TOS, (void *)&on, sizeof(int)) < 0)
	syslog(LOG_WARNING, "setsockopt (IP_TOS): %m");
#endif
#endif /* HAVE_SETSOCKOPT */
    doit(0, &from);
    return 0;
}

int	child;
int	netf;
char	line[MaxPathLen];
int	confirmed;

struct winsize win = { 0, 0, 0, 0 };


static void
doit(int f, struct sockaddr_in *fromp)
{
    int master, pid, on = 1;
    int authenticated = 0;
    char hostname[2 * MaxHostNameLen + 1];
    char c;

    alarm(60);
    read(f, &c, 1);

    if (c != 0)
	exit(1);
    if (vacuous)
	fatal(f, "Remote host requires Kerberos authentication", 0);

    alarm(0);
    inaddr2str (fromp->sin_addr, hostname, sizeof(hostname));

    if (use_kerberos) {
	retval = do_krb_login(fromp);
	if (retval == 0)
	    authenticated++;
	else if (retval > 0)
	    fatal(f, krb_get_err_text(retval), 0);
	write(f, &c, 1);
	confirmed = 1;		/* we sent the null! */
    } else {
	fromp->sin_port = ntohs((u_short)fromp->sin_port);
	if (fromp->sin_family != AF_INET ||
	    fromp->sin_port >= IPPORT_RESERVED ||
	    fromp->sin_port < IPPORT_RESERVED/2) {
	    syslog(LOG_NOTICE, "Connection from %s on illegal port",
		   inet_ntoa(fromp->sin_addr));
	    fatal(f, "Permission denied", 0);
	}
	ip_options_and_die (0, fromp);
	if (do_rlogin(fromp) == 0)
	    authenticated++;
    }
    if (confirmed == 0) {
	write(f, "", 1);
	confirmed = 1;		/* we sent the null! */
    }
#ifndef NOENCRYPTION
    if (doencrypt)
	des_enc_write(f, SECURE_MESSAGE,
		      strlen(SECURE_MESSAGE),
		      schedule, &kdata->session);
    else
#endif
	write(f, INSECURE_MESSAGE, strlen(INSECURE_MESSAGE));
    netf = f;

    pid = forkpty(&master, line, NULL, NULL);
    if (pid < 0) {
	if (errno == ENOENT)
	    fatal(f, "Out of ptys", 0);
	else
	    fatal(f, "Forkpty", 1);
    }
    if (pid == 0) {
	if (f > 2)	/* f should always be 0, but... */
	    close(f);
	setup_term(0);
	if (lusername[0] == '-'){
	    syslog(LOG_ERR, "tried to pass user \"%s\" to login",
		   lusername);
	    fatal(STDERR_FILENO, "invalid user", 0);
	}
	if (authenticated) {
	    if (use_kerberos && (pwd->pw_uid == 0))
		syslog(LOG_INFO|LOG_AUTH,
		       "ROOT Kerberos login from %s on %s\n",
		       krb_unparse_name_long(kdata->pname, 
					     kdata->pinst, 
					     kdata->prealm), 
		       hostname);
		    
	    execl(new_login, "login", "-p",
		  "-h", hostname, "-f", "--", lusername, 0);
	} else
	    execl(new_login, "login", "-p",
		  "-h", hostname, "--", lusername, 0);
	fatal(STDERR_FILENO, new_login, 1);
	/*NOTREACHED*/
    }
    /*
     * If encrypted, don't turn on NBIO or the des read/write
     * routines will croak.
     */

    if (!doencrypt)
	ioctl(f, FIONBIO, &on);
    ioctl(master, FIONBIO, &on);
    ioctl(master, TIOCPKT, &on);
#ifdef SIGTSTP
    signal(SIGTSTP, SIG_IGN);
#endif
    signal(SIGCHLD, cleanup);
    setsid();
    protocol(f, master);
    signal(SIGCHLD, SIG_IGN);
    cleanup(0);
}

const char	magic[2] = { 0377, 0377 };

/*
 * Handle a "control" request (signaled by magic being present)
 * in the data stream.  For now, we are only willing to handle
 * window size changes.
 */
static int
control(int master, char *cp, int n)
{
    struct winsize w;
    char *p;
    u_int32_t tmp;

    if (n < 4 + 4 * sizeof (u_int16_t) || cp[2] != 's' || cp[3] != 's')
	return (0);
#ifdef TIOCSWINSZ
    p = cp + 4;
    p += krb_get_int(p, &tmp, 2, 0);
    w.ws_row = tmp;
    p += krb_get_int(p, &tmp, 2, 0);
    w.ws_col = tmp;

    p += krb_get_int(p, &tmp, 2, 0);
#ifdef HAVE_WS_XPIXEL
    w.ws_xpixel = tmp;
#endif
    p += krb_get_int(p, &tmp, 2, 0);
#ifdef HAVE_WS_YPIXEL
    w.ws_ypixel = tmp;
#endif
    ioctl(master, TIOCSWINSZ, &w);
#endif
    return p - cp;
}

static
void
send_oob(int fd, char c)
{
    static char last_oob = 0xFF;

#if (SunOS == 5) || defined(__hpux)
    /*
     * PSoriasis and HP-UX always send TIOCPKT_DOSTOP at startup so we
     * can avoid sending OOB data and thus not break on Linux by merging
     * TIOCPKT_DOSTOP into the first TIOCPKT_WINDOW.
     */
    static int oob_kludge = 2;
    if (oob_kludge == 2)
	{
	    oob_kludge--;		/* First time send nothing */
	    return;
	}
    else if (oob_kludge == 1)
	{
	    oob_kludge--;		/* Second time merge TIOCPKT_WINDOW */
	    c |= TIOCPKT_WINDOW;
	}
#endif

#define	pkcontrol(c) ((c)&(TIOCPKT_FLUSHWRITE|TIOCPKT_NOSTOP|TIOCPKT_DOSTOP))
    c = pkcontrol(c);
    /* Multiple OOB data breaks on Linux, avoid it when possible. */
    if (c != last_oob)
	send(fd, &c, 1, MSG_OOB);
    last_oob = c;
}

/*
 * rlogin "protocol" machine.
 */
static void
protocol(int f, int master)
{
    char pibuf[1024+1], fibuf[1024], *pbp, *fbp;
    int pcc = 0, fcc = 0;
    int cc, nfd, n;
    char cntl;
    unsigned char oob_queue = 0;

#ifdef SIGTTOU
    /*
     * Must ignore SIGTTOU, otherwise we'll stop
     * when we try and set slave pty's window shape
     * (our controlling tty is the master pty).
     */
    signal(SIGTTOU, SIG_IGN);
#endif

    send_oob(f, TIOCPKT_WINDOW); /* indicate new rlogin */

    if (f > master)
	nfd = f + 1;
    else
	nfd = master + 1;
    if (nfd > FD_SETSIZE) {
	syslog(LOG_ERR, "select mask too small, increase FD_SETSIZE");
	fatal(f, "internal error (select mask too small)", 0);
    }
    for (;;) {
	fd_set ibits, obits, ebits, *omask;

	FD_ZERO(&ebits);
	FD_ZERO(&ibits);
	FD_ZERO(&obits);
	omask = (fd_set *)NULL;
	if (fcc) {
	    FD_SET(master, &obits);
	    omask = &obits;
	} else
	    FD_SET(f, &ibits);
	if (pcc >= 0)
	    if (pcc) {
		FD_SET(f, &obits);
		omask = &obits;
	    } else
		FD_SET(master, &ibits);
	FD_SET(master, &ebits);
	if ((n = select(nfd, &ibits, omask, &ebits, 0)) < 0) {
	    if (errno == EINTR)
		continue;
	    fatal(f, "select", 1);
	}
	if (n == 0) {
	    /* shouldn't happen... */
	    sleep(5);
	    continue;
	}
	if (FD_ISSET(master, &ebits)) {
	    cc = readstream(master, &cntl, 1);
	    if (cc == 1 && pkcontrol(cntl)) {
#if 0				/* Kludge around */
		send_oob(f, cntl);
#endif
		oob_queue = cntl;
		if (cntl & TIOCPKT_FLUSHWRITE) {
		    pcc = 0;
		    FD_CLR(master, &ibits);
		}
	    }
	}
	if (FD_ISSET(f, &ibits)) {
#ifndef NOENCRYPTION
	    if (doencrypt)
		fcc = des_enc_read(f, fibuf,
				   sizeof(fibuf),
				   schedule, &kdata->session);
	    else
#endif
		fcc = read(f, fibuf, sizeof(fibuf));
	    if (fcc < 0 && errno == EWOULDBLOCK)
		fcc = 0;
	    else {
		char *cp;
		int left, n;

		if (fcc <= 0)
		    break;
		fbp = fibuf;

	    top:
		for (cp = fibuf; cp < fibuf+fcc-1; cp++)
		    if (cp[0] == magic[0] &&
			cp[1] == magic[1]) {
			left = fcc - (cp-fibuf);
			n = control(master, cp, left);
			if (n) {
			    left -= n;
			    if (left > 0)
				memmove(cp, cp+n, left);
			    fcc -= n;
			    goto top; /* n^2 */
			}
		    }
		FD_SET(master, &obits);		/* try write */
	    }
	}

	if (FD_ISSET(master, &obits) && fcc > 0) {
	    cc = write(master, fbp, fcc);
	    if (cc > 0) {
		fcc -= cc;
		fbp += cc;
	    }
	}

	if (FD_ISSET(master, &ibits)) {
	    pcc = readstream(master, pibuf, sizeof (pibuf));
	    pbp = pibuf;
	    if (pcc < 0 && errno == EWOULDBLOCK)
		pcc = 0;
	    else if (pcc <= 0)
		break;
	    else if (pibuf[0] == 0) {
		pbp++, pcc--;
		if (!doencrypt)
		    FD_SET(f, &obits);	/* try write */
	    } else {
		if (pkcontrol(pibuf[0])) {
		    oob_queue = pibuf[0];
#if 0				/* Kludge around */
		    send_oob(f, pibuf[0]);
#endif
		}
		pcc = 0;
	    }
	}
	if ((FD_ISSET(f, &obits)) && pcc > 0) {
#ifndef NOENCRYPTION
	    if (doencrypt)
		cc = des_enc_write(f, pbp, pcc, schedule, &kdata->session);
	    else
#endif
		cc = write(f, pbp, pcc);
	    if (cc < 0 && errno == EWOULDBLOCK) {
		/*
		 * This happens when we try write after read
		 * from p, but some old kernels balk at large
		 * writes even when select returns true.
		 */
		if (!FD_ISSET(master, &ibits))
		    sleep(5);
		continue;
	    }
	    if (cc > 0) {
		pcc -= cc;
		pbp += cc;
		/* Only send urg data when normal data
		 * has just been sent.
		 * Linux has deep problems with more
		 * than one byte of OOB data.
		 */
		if (oob_queue) {
		    send_oob (f, oob_queue);
		    oob_queue = 0;
		}
	    }
	}
    }
}

static RETSIGTYPE
cleanup(int signo)
{
    char *p = clean_ttyname (line);

    if (logout(p) == 0)
	logwtmp(p, "", "");
    chmod(line, 0666);
    chown(line, 0, 0);
    *p = 'p';
    chmod(line, 0666);
    chown(line, 0, 0);
    shutdown(netf, 2);
    signal(SIGHUP, SIG_IGN);
#ifdef HAVE_VHANGUP
    vhangup();
#endif /* HAVE_VHANGUP */
    exit(1);
}

void
fatal(int f, const char *msg, int syserr)
{
    int len;
    char buf[BUFSIZ], *bp = buf;

    /*
     * Prepend binary one to message if we haven't sent
     * the magic null as confirmation.
     */
    if (!confirmed)
	*bp++ = '\01';		/* error indicator */
    if (syserr)
	snprintf(bp, sizeof(buf) - (bp - buf),
		 "rlogind: %s: %s.\r\n",
		 msg, strerror(errno));
    else
	snprintf(bp, sizeof(buf) - (bp - buf),
		 "rlogind: %s.\r\n", msg);
    len = strlen(bp);
#ifndef NOENCRYPTION
    if (doencrypt)
	des_enc_write(f, buf, bp + len - buf, schedule, &kdata->session);
    else
#endif
	write(f, buf, bp + len - buf);
    exit(1);
}

static void
xgetstr(char *buf, int cnt, char *errmsg)
{
    char c;

    do {
	if (read(0, &c, 1) != 1)
	    exit(1);
	if (--cnt < 0)
	    fatal(STDOUT_FILENO, errmsg, 0);
	*buf++ = c;
    } while (c != 0);
}

static int
do_rlogin(struct sockaddr_in *dest)
{
    xgetstr(rusername, sizeof(rusername), "remuser too long");
    xgetstr(lusername, sizeof(lusername), "locuser too long");
    xgetstr(term+ENVSIZE, sizeof(term)-ENVSIZE, "Terminal type too long");

    pwd = k_getpwnam(lusername);
    if (pwd == NULL)
	return (-1);
    if (pwd->pw_uid == 0 && strcmp("root", lusername) != 0)
	{
	    syslog(LOG_ALERT, "NIS attack, user %s has uid 0", lusername);
	    return (-1);
	}
    return (iruserok(dest->sin_addr.s_addr,
		     (pwd->pw_uid == 0),
		     rusername,
		     lusername));
}

static void 
setup_term(int fd)
{
    char *cp = strchr(term+ENVSIZE, '/');
    char *speed;
    struct termios tt;

    tcgetattr(fd, &tt);
    if (cp) {
	int s;

	*cp++ = '\0';
	speed = cp;
	cp = strchr(speed, '/');
	if (cp)
	    *cp++ = '\0';
	s = int2speed_t (atoi (speed));
	if (s > 0) {
	    cfsetospeed (&tt, s);
	    cfsetispeed (&tt, s);
	}
    }

    tt.c_iflag &= ~INPCK;
    tt.c_iflag |= ICRNL|IXON;
    tt.c_oflag |= OPOST|ONLCR;
#ifdef TAB3
    tt.c_oflag |= TAB3;
#endif /* TAB3 */
#ifdef ONLRET
    tt.c_oflag &= ~ONLRET;
#endif /* ONLRET */
    tt.c_lflag |= (ECHO|ECHOE|ECHOK|ISIG|ICANON);
    tt.c_cflag &= ~PARENB;
    tt.c_cflag |= CS8;
    tt.c_cc[VMIN] = 1;
    tt.c_cc[VTIME] = 0;
    tt.c_cc[VEOF] = CEOF;
    tcsetattr(fd, TCSAFLUSH, &tt);

    env[0] = term;
    env[1] = 0;
    environ = env;
}

#define	VERSION_SIZE	9

/*
 * Do the remote kerberos login to the named host with the
 * given inet address
 *
 * Return 0 on valid authorization
 * Return -1 on valid authentication, no authorization
 * Return >0 for error conditions
 */
static int
do_krb_login(struct sockaddr_in *dest)
{
    int rc;
    char instance[INST_SZ], version[VERSION_SIZE];
    long authopts = 0L;	/* !mutual */
    struct sockaddr_in faddr;

    kdata = (AUTH_DAT *) auth_buf;
    ticket = (KTEXT) tick_buf;

    k_getsockinst(0, instance, sizeof(instance));

    if (doencrypt) {
	rc = sizeof(faddr);
	if (getsockname(0, (struct sockaddr *)&faddr, &rc))
	    return (-1);
	authopts = KOPT_DO_MUTUAL;
	rc = krb_recvauth(
			  authopts, 0,
			  ticket, "rcmd",
			  instance, dest, &faddr,
			  kdata, "", schedule, version);
	des_set_key(&kdata->session, schedule);

    } else
	rc = krb_recvauth(
			  authopts, 0,
			  ticket, "rcmd",
			  instance, dest, (struct sockaddr_in *) 0,
			  kdata, "", 0, version);

    if (rc != KSUCCESS)
	return (rc);

    xgetstr(lusername, sizeof(lusername), "locuser");
    /* get the "cmd" in the rcmd protocol */
    xgetstr(term+ENVSIZE, sizeof(term)-ENVSIZE, "Terminal type");

    pwd = k_getpwnam(lusername);
    if (pwd == NULL)
	return (-1);
    if (pwd->pw_uid == 0 && strcmp("root", lusername) != 0)
	{
	    syslog(LOG_ALERT, "NIS attack, user %s has uid 0", lusername);
	    return (-1);
	}

    /* returns nonzero for no access */
    if (kuserok(kdata, lusername) != 0)
	return (-1);

    return (0);

}

static void
usage(void)
{
    syslog(LOG_ERR,
	   "usage: rlogind [-Dailn] [-p port] [-x] [-L login] [-k | -v]");
    exit(1);
}
