/*
 * $Id: login.c,v 1.79 1997/11/02 02:42:54 ghudson Exp $
 */

#ifndef lint
static char *rcsid = "$Id: login.c,v 1.79 1997/11/02 02:42:54 ghudson Exp $";
#endif

/*
 * Copyright (c) 1980 Regents of the University of California.
 * All rights reserved.  The Berkeley software License Agreement
 * specifies the terms and conditions for redistribution.
 */

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

#ifndef lint
static char sccsid[] = "@(#)login.c	5.15 (Berkeley) 4/12/86";
#endif

/*
 * login [ name ]
 * login -r hostname (for rlogind)
 * login -k hostname (for Kerberos rlogind with password access)
 * login -K hostname (for Kerberos rlogind with restricted access)
 * login -h hostname (for telnetd, etc.)
 */

#include <AL/AL.h>
#include <sys/types.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <unistd.h>
#include <dirent.h>
#include <termios.h>
#include <limits.h>
#include <pwd.h>
#include <netdb.h>
#include <netinet/in.h>
#include <krb.h>
#include <grp.h>
#if defined(BSD4_4) || defined(linux)
#include <paths.h>
#endif
#ifndef BSD4_4
#include <lastlog.h>
#endif
#ifndef INITTAB
#include <ttyent.h>
#endif
#include <syslog.h>
#if defined(sun) && defined(__SVR4)
#include <crypt.h>
#endif

/* Operating system tests for features. */

#if defined(BSD) || defined(ultrix)
/* BSD systems store a username in the kernel. */
#define HAVE_SETLOGIN
#endif

#ifdef ultrix
#define USE_QUOTA
#endif

/* Macros and includes based on results of above conclusions. */

#ifndef HAVE_SETLOGIN
/* Just pretend to succeed. */
#define setlogin(x) 0
#endif

#ifdef USE_QUOTA
#ifdef BSD4_4
#include <ufs/ufs/quota.h>
#else
#include <sys/quota.h>
#endif
#endif

/* Constants which may or may not be defined by the system include files. */

#ifndef LOGNAME_MAX
#define LOGNAME_MAX 8
#endif

#ifndef _PASSWORD_LEN
#define _PASSWORD_LEN	128
#endif

#ifndef _PATH_MAILDIR
#define _PATH_MAILDIR "/usr/spool/mail/"
#endif
#ifndef _PATH_LASTLOG
#define _PATH_LASTLOG "/usr/adm/lastlog"
#endif

#ifndef CINTR
#define CINTR '\003'
#endif
#ifndef CQUIT
#define CQUIT '\034'
#endif
#ifndef CERASE
#define CERASE '\177'
#endif
#ifndef CKILL
#define CKILL '\021'
#endif
#ifndef CEOF
#define CEOF '\004'
#endif

#ifndef MAXBSIZE
#define MAXBSIZE 1024
#endif

#ifndef KRB_REALM
#define KRB_REALM	"ATHENA.MIT.EDU"
#endif

#ifndef FALSE
#define	FALSE	0
#define	TRUE	-1
#endif

/* Our own stuff. */

#define TTYGRPNAME	"tty"		/* name of group to own ttys */
#define TTYGID(gid)	tty_gid(gid)	/* gid that owns all ttys */

#define	SCMPN(a, b)	strncmp(a, b, sizeof(a))
#define	SCPYN(a, b)	strncpy(a, b, sizeof(a))

#define KRB_ENVIRON	"KRBTKFILE" /* Ticket file environment variable */
#define KRB_TK_DIR	"/tmp/tkt_" /* Where to put the ticket */
#define KRBTKLIFETIME	DEFAULT_TKT_LIFE

#define PROTOTYPE_DIR	"/usr/athena/lib/prototype_tmpuser" /* Temp files */
#define TEMP_DIR_PERM	0755	/* Permission on temporary directories */

#define START_UID	200	/* start assigning arbitrary UID's here */
#define MIT_GID		101	/* standard primary group "mit" */

#define PATH_NOLOGIN	"/etc/nologin"
#define PATH_HUSHLOGIN	".hushlogin"
#define PATH_REGISTER	"/usr/etc/go_register" /* XXX */
#define PATH_MOTD	"/etc/motd"
#define PATH_GETMOTD	"/usr/athena/bin/get_message"

#define TIMEOUT		60

extern char *optarg;
extern int optind;

static void getloginname(char *buf);
static void timedout(void);
static void catch(void);
static int rootterm(char *tty);
static void showmotd(void);
static char *stypeof(char *ttyid);
static int doremotelogin(char *host, char *username, int usernamelen,
			 char *term, int termlen);
static int dokerberoslogin(char *host, char *username, int usernamelen,
			   char *term, int termlen);
static void getstr(char *buf, int cnt, char *err);
static int doremoteterm(char *term, struct termios *tp);
static void dofork(ALsession session);
static gid_t tty_gid(gid_t default_gid);
static void getlongpass(char *prompt, char *pbuf, int pbuflen);
static void init_wgfile(void);
static int verify_krb_tgt(char *realm);

static int rflag = 0, kflag = 0, Kflag = 0, usererr = -1, stopmotd = 0;
#ifdef USE_SETPAG
static int pagflag = FALSE;
#endif
static AUTH_DAT *kdata = NULL;
static struct winsize win = { 0, 0, 0, 0 };

static struct speeds {
    char *name;
    int  value;
} speeds[] = {
    {"50",   B50},	{"75",    B75},		{"110",   B110},
    {"134",  B134},	{"150",   B150},	{"200",   B200},
    {"300",  B300},	{"600",   B600},	{"1200",  B1200},
    {"1800", B1800},	{"2400",  B2400},	{"4800",  B4800},
    {"9600", B9600},	{"19200", B19200},	{"38400", B38400},
    {NULL}
};

int main(int argc, char **argv)
{
    static char tkbuf[256], homebuf[256], shellbuf[256], termbuf[256];
    static char userbuf[256];
    int ch;
    int pflag = 0, hflag = 0, t, f, c, invalid, quietlog, code, flags;
    int ldisc = 0, zero = 0, i, j;
    int krbval;
    pid_t forkval;
    long salt;
    char *ttyn, *tty, saltc[2], *hostname = NULL, *namep, **envnew;
    char realm[REALM_SZ], minusnam[16] = "-", username[LOGNAME_MAX + 1];
    char pp[_PASSWORD_LEN + 1], pp2[_PASSWORD_LEN + 1], term[64];
    char mailbuf[128];
    FILE *nlfp;
    struct termios tio;
    struct sigaction act;
    ALsessionStruct session;
    ALut ut;
    ALflag_t session_flags = ALflagNone;

    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;
    act.sa_handler = timedout;
    sigaction(SIGALRM, &act, NULL);
    alarm(TIMEOUT);
    act.sa_handler = SIG_IGN;
    sigaction(SIGQUIT, &act, NULL);
    sigaction(SIGINT, &act, NULL);
    nice(-nice(0));			/* Set process priority to 0. */
    umask(0);
#ifdef USE_QUOTA
    quota(Q_SETUID, 0, 0, 0);
#endif

    memset(username, 0, sizeof(username));
    while ((ch = getopt(argc, argv, "h:k:K:pr:")) != EOF) {
	switch (ch) {
	case 'r':
	    if (rflag || kflag || Kflag || hflag) {
		printf("Only one of -r -k -K or -h allowed\n");
		exit(1);
	    }
	    rflag = 1;
	    usererr = doremotelogin(optarg, username, sizeof(username),
				    term, sizeof(term));
	    hostname = optarg;
	    break;
	case 'k':
	    if (rflag || kflag || Kflag || hflag) {
		printf("Only one of -r -k -K or -h allowed\n");
		exit(1);
	    }
	    kflag = 1;
	    usererr = dokerberoslogin(optarg, username, sizeof(username),
				      term, sizeof(term));
	    hostname = optarg;
	    break;
	case 'K':
	    if (rflag || kflag || Kflag || hflag) {
		printf("Only one of -r -k -K or -h allowed\n");
		exit(1);
	    }
	    Kflag = 1;
	    usererr = dokerberoslogin(optarg, username, sizeof(username),
				      term, sizeof(term));
	    hostname = optarg;
	    break;
	case 'h':
	    if (getuid() != 0) {
		printf("Attempted to use -h while not root\n");
		exit(1);
	    }
	    if (rflag || kflag || Kflag || hflag) {
		printf("Only one of -r -k -K or -h allowed\n");
		exit(1);
	    }
	    hflag = 1;
	    hostname = optarg;
	    break;
	case 'p':
	    pflag = 1;
	    break;
	}
    }
    argc -= optind;
    argv += optind;
    if (argc > 0)
	SCPYN(username, argv[0]);

    flags = fcntl(0, F_GETFL, NULL);
    flags &= ~O_NONBLOCK;
    fcntl(0, F_SETFL, flags);
#ifdef TIOCNXCL
    ioctl(0, TIOCNXCL, 0);
#endif
#ifdef FIOASYNC
    ioctl(0, FIOASYNC, &zero);
#endif

    /*
     * If talking to an rlogin process,
     * propagate the terminal type and
     * baud rate across the network.
     */

    tcgetattr(0, &tio);
    
    if (rflag || kflag || Kflag)
	doremoteterm(term, &tio); 
    tio.c_iflag |= (BRKINT|ICRNL);
    tio.c_oflag |= (OPOST|ONLCR);
    tio.c_cflag |= (CREAD|HUPCL);
    tio.c_lflag |= (ICANON|ISIG|IEXTEN|ECHO|ECHOE|ECHOK);
#ifdef ultrix
    tio.c_oflag |= TAB3;
#endif
    tio.c_cc[VINTR] = CINTR;
    tio.c_cc[VQUIT] = CQUIT;
    tio.c_cc[VERASE] = CERASE;
    tio.c_cc[VKILL] = CKILL;
    tio.c_cc[VEOF] = CEOF;

    tcsetattr(0, TCSANOW, &tio);

    for (t = sysconf(_SC_OPEN_MAX); t > 2; t--)
	close(t);
    ttyn = ttyname(0);
    if (ttyn == (char *)0 || *ttyn == '\0')
	ttyn = "/dev/tty??";
    tty = strrchr(ttyn, '/');
    if (tty == NULL)
	tty = ttyn;
    else
	tty++;

    openlog("login", 0, LOG_AUTH);

    t = 0;
    invalid = FALSE;
    do {
	ldisc = 0;
	ioctl(0, TIOCSETD, &ldisc);

	/* Ask for a username if none specified. */
	if (!*username || invalid) {
	    getloginname(username);
	    if (*username == '-') {
		puts("login names may not start with '-'.");
		invalid = TRUE;
		continue;
	    }
	}

	invalid = TRUE;

	if (rflag || kflag || Kflag || hflag)
	    session_flags |= ALisRemoteSession;
	code = ALsetUser(&session, username, session_flags);
	if (ALisError(code)) {
	    getlongpass("Password:", pp, sizeof(pp));
	    goto leavethis;
	}

	if (usererr != -1 || !*ALpw_passwd(&session)) {
	    invalid = FALSE;
	    goto leavethis;
	}

	/* Set up the ticket file environment variable */
	sprintf(tkbuf, "%s=%s%s", KRB_ENVIRON, KRB_TK_DIR,
		strrchr(ttyn, '/') + 1);
	putenv(tkbuf);
	unlink(tkbuf + strlen(KRB_ENVIRON) + 1);
	    
	nice(-4);
	getlongpass("Password:", pp, sizeof(pp));

	/* Modifications for Kerberos authentication -- asp */
	SCPYN(pp2, pp);
	pp[8]='\0';
	if (!ALisTrue(&session, ALdidGetHesiodPasswd)) {
	    namep = crypt(pp, ALpw_passwd(&session));
	} else {
	    salt = 9 * getpid();
	    saltc[0] = salt & 077;
	    saltc[1] = (salt>>6) & 077;
	    for (i = 0; i < 2; i++) {
		c = saltc[i] + '.';
		if (c > '9')
		    c += 7;
		if (c > 'Z')
		    c += 6;
		saltc[i] = c;
	    }
	    ALpw_passwd(&session) = namep = crypt(pp, saltc);
	}
			    
	bzero(pp, sizeof(pp));
	nice(4);

	if (ALpw_uid(&session) != 0) { 
#ifdef USE_SETPAG
	    /* We only call setpag() for non-root users */
	    setpag();
	    pagflag = TRUE;
#endif
#if defined(SKEY) && defined(__NetBSD__)
	    if (strcasecmp(pp2, "s/key") == 0) {
		if (skey_haskey(username) != 0)
		    fprintf(stderr, "You have no s/key.\n");
		else if (skey_authenticate(username) != 0)
		    fprintf(stderr, "s/key password incorrect.\n");
		else
		    invalid = FALSE;
		goto noerrormsg;
	    }
#endif /* SKEY && __NetBSD__ */
	    /* if not root, get Kerberos tickets */
	    if (krb_get_lrealm(realm, 1) != KSUCCESS)
		SCPYN(realm, KRB_REALM);
	    krbval = krb_get_pw_in_tkt(username, "", realm,
				       "krbtgt", realm, KRBTKLIFETIME, pp2);
	    bzero(pp2, sizeof(pp2));
	    switch (krbval) {
	    case INTK_OK:
		ALflagSet(&session, ALhaveAuthentication);
		alarm(0);	/* Authentic, so don't time out. */
		if (verify_krb_tgt(realm) < 0) {
		    /* Oops.  He tried to fool us.  Tsk, tsk. */
		    fprintf(stderr, "Failed to verify Kerberos TGT.\n");
		    goto noerrormsg;
		}
		chown(getenv(KRB_ENVIRON), ALpw_uid(&session),
		      ALpw_gid(&session));
		/* If we already have a homedir, use it.
		 * Otherwise, try to attach.  If that fails,
		 * try to create. */
		code = ALgetHomedir(&session);
		if (code != 0) {
		    com_err(NULL, code, "while attaching your homedir.");
		    if (ALisError(code))
			goto noerrormsg;
		}
		invalid = FALSE;
		break;

	    case KDC_NULL_KEY:
		if (!ALisTrue(&session, ALdidGetHesiodPasswd))
		    goto good_anyway;

		/* tell the user to go register with kerberos */

		if (ALaddPasswdEntry(&session) < 0) {
		    com_err(NULL, code, "while adding your passwd entry.");
		    if (ALisError(code))
			goto noerrormsg;
		}
			
		alarm(0);	/* won't be logging in anyway */

		forkval = fork();
		if (forkval < 0) {
		    perror("Forking for registration program");
		    ALremovePasswdEntry(&session);
		    sleep(3);
		    exit(1);
		}
		if (forkval == 0) {
		    /* run the passwd program as the user */
		    setuid(ALpw_uid(&session));

		    execl(PATH_REGISTER, PATH_REGISTER, username, 0);
		    perror("Executing registration program");
		    sleep(2);
		    exit(1);
		}
		while(wait(NULL) != forkval);
		ALremovePasswdEntry(&session);
		exit(0);

	    case INTK_W_NOTALL:
		/* Not fatal, but print error message. */
		invalid = FALSE;
		ALflagSet(&session, ALhaveAuthentication);
		fprintf(stderr, "Kerberos error: %s\n", krb_err_txt[krbval]);
		goto leavethis;
	    /* The following errors should be printed and are fatal. */
	    case KDC_PR_UNKNOWN:
	    case KDC_PR_N_UNIQUE:
		if (!ALisTrue(&session, ALdidGetHesiodPasswd))
		    goto good_anyway;
	    case INTK_BADPW:
	    default:
		goto leavethis;
	    }
	} else { /* root logging in or inhibited; check password */
	    bzero(pp2, sizeof(pp2));
	} 
good_anyway:
	/* if password is good, user is good */
	invalid = invalid && strcmp(namep, ALpw_passwd(&session));

leavethis:
	if (invalid)
	    printf("Login incorrect\n");

noerrormsg:
	/*
	 * If our uid < 0, we must be a bogus user.
	 */
	if (ALpw_uid(&session) < 0) {
	    fprintf(stderr, "Bogus UID %d\n", ALpw_uid(&session));
	    invalid = TRUE;
	}
	/*
	 * If user not super-user, check for logins disabled.
	 */
	if (ALpw_uid(&session) != 0) {
	    nlfp = fopen(PATH_NOLOGIN, "r");
	    if (nlfp) {
		while ((c = getc(nlfp)) != EOF)
		    putchar(c);
		fflush(stdout);
		if (ALisTrue(&session, ALhaveAuthentication))
		    dest_tkt();
		sleep(5);
		exit(0);
	    }
	}
	/*
	 * If valid so far and root is logging in,
	 * see if root logins on this terminal are permitted.
	 */
	if (!invalid && ALpw_uid(&session) == 0 && !rootterm(tty)) {
	    if (hostname) {
		syslog(LOG_CRIT, "ROOT LOGIN REFUSED ON %s FROM %s",
		       tty, hostname);
	    } else {
		syslog(LOG_CRIT, "ROOT LOGIN REFUSED ON %s", tty);
	    }
	    fprintf(stderr, "Root logins are refused on this terminal.\n");
	    invalid = TRUE;
	}
	if (invalid && ++t >= 5) {
	    if (hostname) {
		syslog(LOG_CRIT, "REPEATED LOGIN FAILURES ON %s FROM %s, %s",
		       tty, hostname, username);
	    } else {
		syslog(LOG_CRIT, "REPEATED LOGIN FAILURES ON %s, %s",
		       tty, username);
	    }
	    tcgetattr(0, &tio);
	    tio.c_cflag &= ~HUPCL;
	    tcsetattr(0, TCSANOW, &tio);
	    sleep(10);
	    exit(1);
	}
	if (!invalid && *ALpw_shell(&session) == '\0')
	    ALpw_shell(&session) = "/bin/sh";

	/* 
	  The effective uid is used under AFS for access.
	  NFS uses euid and uid for access checking
	 */
	seteuid(ALpw_uid(&session));
	if (!invalid && chdir(ALpw_dir(&session)) < 0) {
	    if (chdir("/") < 0) {
		printf("No directory!\n");
		invalid = TRUE;
	    } else {
		puts("No directory!  Logging in with home=/\n");
		ALpw_dir(&session) = "/";
	    }
	}
	seteuid(getuid());

	/*
	 * Remote login invalid must have been because
	 * of a restriction of some sort, no extra chances.
	 */
	if (!usererr && invalid)
	    exit(1);

    } while (invalid);

    /* Committed to login; turn off timeout */
    alarm(0);
    if (!ALisTrue(&session, ALhaveAuthentication))
	puts("Warning: no Kerberos tickets obtained.");

#ifdef USE_QUOTA
    if (quota(Q_SETUID, ALpw_uid(&session), 0, 0) < 0 && errno != EINVAL) {
	if (errno == EUSERS)
	    printf("Too many users logged on already.\nTry again later.\n");
	else if (errno == EPROCLIM)
	    printf("You have too many processes running.\n");
	else
	    perror("quota (Q_SETUID)");
	sleep(5);
	if (ALisTrue(&session, ALhaveAuthentication))
		dest_tkt();
	exit(0);
    }
#endif

    /* Start the Athena session. */
    code = ALaddPasswdEntry(&session);
    if (ALisError(code)) {
	if (code == ALerrNOREMOTE)
	    printf("Login incorrect\n");
	else
	    com_err(NULL, code, "(%s)", ALcontext(&session));
	exit(1);
    }
    code = ALgetHomedir(&session);
    if (code != 0) {
	com_err(&session, code, "(%s)", ALcontext(&session));
	if (ALisError(code)) {
	    ALend(&session);
	    exit(1);
	}
    }
    code = ALaddToGroupsFile(&session);
    if (code != 0)
	com_err("Warning", code, "(%s)", ALcontext(&session));
    ut.host = (hostname) ? hostname : "";
    ALsetUtmpInfo(&session, ALutHOST, &ut);
    code = ALputUtmp(&session);
    if (code != 0)
	com_err("Warning", code, "(%s)", ALcontext(&session));

    quietlog = (access(PATH_HUSHLOGIN, F_OK) == 0);
    if ((f = open(_PATH_LASTLOG, O_RDWR)) >= 0) {
	struct lastlog ll;
	lseek(f, ALpw_uid(&session) * sizeof (struct lastlog), 0);
	if (read(f, (char *) &ll, sizeof ll) == sizeof ll &&
	    ll.ll_time != 0 && !quietlog) {
	    printf("Last login: %.*s ",
		   24-5, (char *)ctime(&ll.ll_time));
	    if (*ll.ll_host != '\0')
	    printf("from %.*s\n",
		   sizeof (ll.ll_host), ll.ll_host);
	    else
	    printf("on %.*s\n",
		   sizeof (ll.ll_line), ll.ll_line);
	}
	lseek(f, ALpw_uid(&session) * sizeof (struct lastlog), 0);
	time(&ll.ll_time);
	SCPYN(ll.ll_line, tty);
	SCPYN(ll.ll_host, (hostname) ? hostname : "");
	write(f, (char *) &ll, sizeof ll);
	close(f);
    }
    chown(ttyn, ALpw_uid(&session), TTYGID(ALpw_gid(&session)));

    if (!hflag && !rflag && !pflag && !kflag && !Kflag)		/* XXX */
	ioctl(0, TIOCSWINSZ, &win);
    chmod(ttyn, 0620);
    init_wgfile();

    /* Fork so that we can call kdestroy, notification server */
    dofork(&session);
	
    setgid(ALpw_gid(&session));
    initgroups(username, ALpw_gid(&session));
#ifdef USE_QUOTA
    quota(Q_DOWARN, ALpw_uid(&session), (dev_t)-1, 0);
#endif

    if (setlogin(ALpw_name(&session)) < 0)
	syslog(LOG_ERR, "setlogin() failure: %m");

    /* This call MUST succeed */
    if(setuid(ALpw_uid(&session)) < 0) {
	perror("setuid");
	ALend(&session);
	if (ALisTrue(&session, ALhaveAuthentication))
		dest_tkt();
	exit(1);
    }
    chdir(ALpw_dir(&session));

    sprintf(homebuf, "HOME=%s", ALpw_dir(&session));
    putenv(homebuf);
    sprintf(shellbuf, "SHELL=%s", ALpw_shell(&session));
    putenv(shellbuf);
    if (!*term)
	SCPYN(term, stypeof(tty));
    sprintf(termbuf, "TERM=%s", term);
    if (!getenv("TERM"))
	putenv(termbuf);
    sprintf(userbuf, "USER=%s", ALpw_name(&session));
    putenv(userbuf);
    putenv("PATH=/usr/athena/bin:/bin/athena:/usr/ucb:/bin:/usr/bin");
#ifdef ultrix
    putenv("hosttype=decmips");
#endif
#if defined(sun) && defined(__SVR4)
    putenv("hosttype=sun4");
#endif
#ifdef linux
    putenv("hosttype=linux");
#endif
#ifdef __NetBSD__
    putenv("hosttype=inbsd");
#endif

    namep = strrchr(ALpw_shell(&session), '/');
    namep = (namep == NULL) ? ALpw_shell(&session) : namep + 1;
    strcat(minusnam, namep);
    if (tty[sizeof("tty") - 1] == 'd')
	syslog(LOG_INFO, "DIALUP %s, %s", tty, ALpw_name(&session));
    if (ALpw_uid(&session) == 0) {
	if (hostname) {
	    if (kdata) {
		syslog(LOG_NOTICE, "ROOT LOGIN via Kerberos from %s",
		       hostname);
		syslog(LOG_NOTICE, "     (name=%s, instance=%s, realm=%s).",
		       kdata->pname, kdata->pinst, kdata->prealm);
	    } else {
		syslog(LOG_NOTICE, "ROOT LOGIN %s FROM %s", tty, hostname);
	    }
	} else {
	    if (kdata) {
		syslog(LOG_NOTICE, "ROOT LOGIN via Kerberos %s ", tty);
		syslog(LOG_NOTICE, "     (name=%s, instance=%s, realm=%s).",
		       kdata->pname, kdata->pinst, kdata->prealm);
	    } else {
		syslog(LOG_NOTICE, "ROOT LOGIN %s", tty);
	    }
	}
    }
    if (!quietlog) {
	struct stat st;

	showmotd();
	sprintf(mailbuf, "%s%s", _PATH_MAILDIR, ALpw_name(&session));
	if (stat(mailbuf, &st) == 0 && st.st_size != 0) {
	    printf("You have %smail.\n",
		   (st.st_mtime > st.st_atime) ? "new " : "");
	}
    }
#ifdef USE_QUOTA
    switch(forkval = fork()) {
    case -1:
	printf("Unable to fork to run quota.\n");
	break;
    case 0:
	execlp(QUOTAWARN, "quota", 0);
	exit(1);
	/* NOTREACHED */
    default:
	while(wait(0) != forkval) ;
	break;
    }
#endif
    act.sa_handler = SIG_DFL;
    sigaction(SIGALRM, &act, NULL);
    sigaction(SIGQUIT, &act, NULL);
    sigaction(SIGINT, &act, NULL);
    act.sa_handler = SIG_IGN;
    sigaction(SIGTSTP, &act, NULL);
    execlp(ALpw_shell(&session), minusnam, 0);
    perror(ALpw_shell(&session));
    printf("No shell\n");
    return 0;
}

static void getloginname(char *buf)
{
    char *namep;
    int c;

    *buf = 0;
    while (*buf == 0) {
	namep = buf;
	printf("login: "); 
	while ((c = getchar()) != '\n') {
	    if (c == ' ')
		c = '_';
	    if (c == EOF)
		exit(0);
	    if (namep < buf + LOGNAME_MAX)
		*namep++ = c;
	}
    }
    *namep = 0;
}

static void timedout()
{
    printf("Login timed out after %d seconds\n", TIMEOUT);
    exit(0);
}

static void catch()
{
    struct sigaction act;

    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;
    act.sa_handler = SIG_IGN;
    sigaction(SIGINT, &act, NULL);
    stopmotd = 1;
}

static int rootterm(char *tty)
{

#ifndef INITTAB
    struct ttyent *t;

    if ((t = getttynam(tty)) != NULL) {
	if (t->ty_status & TTY_SECURE)
	    return (1);
    }
    return (0);
#else 
    /* This is moot when /etc/inittab is used - there is no
       per tty resource available */
    return (1);
#endif
}

static void showmotd()
{
    FILE *mf;
    int forkval, c;
    struct sigaction act;

    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;
    act.sa_handler = catch;
    sigaction(SIGINT, &act, NULL);
    if (forkval = fork()) { /* parent */
	if (forkval < 0) {
	    perror("forking for motd service");
	    sleep(3);
	} else {
	    while (wait(0) != forkval)
		;
	}
    } else {
	if ((mf = fopen(PATH_MOTD, "r")) != NULL) {
	    while ((c = getc(mf)) != EOF && stopmotd == 0)
		putchar(c);
	    fclose(mf);
	}
	if (execlp(PATH_GETMOTD, PATH_GETMOTD, "-login", 0) < 0)
	    exit(0);
    }
    act.sa_handler = SIG_IGN;
    sigaction(SIGINT, &act, NULL);
}

static char *stypeof(char *ttyid)
{
#ifdef INITTAB
    return ("vt100");
#else
    struct ttyent *t;

    if (ttyid == NULL || (t = getttynam(ttyid)) == NULL)
	return ("su");
    return (t->ty_type);
#endif
}

static int doremotelogin(char *host, char *username, int usernamelen,
			 char *term, int termsize)
{
    struct passwd *pwd;
    char rusername[LOGNAME_MAX + 1];

    getstr(rusername, sizeof(rusername), "Remote user");
    getstr(username, usernamelen, "Local user");
    getstr(term, termsize, "Terminal type");
    if (getuid())
	return(-1);
    pwd = getpwnam(username);
    if (!pwd)
	return(-1);
    return(ruserok(host, (pwd->pw_uid == 0), rusername, username));
}

static int dokerberoslogin(char *host, char *username, int usernamelen,
			   char *term, int termsize)
{
    int rc;
    struct hostent *hp = gethostbyname(host);
    struct sockaddr_in sin;

    /*
     * Kerberos autologin protocol.
     */

    memset(&sin, 0, sizeof(sin));

    if (hp) {
	memcpy(&sin.sin_addr, hp->h_addr, sizeof(sin.sin_addr));
    } else {
	/*
	 * No host addr prevents auth, so
	 * punt krb and require password
	 */
	if (Kflag)
	    goto paranoid;
	else
	    return(-1);
    }

    kdata = (AUTH_DAT *) malloc(sizeof(AUTH_DAT));
    if (rc = GetKerberosData(0, sin.sin_addr, kdata, "rcmd")) {
	printf("Kerberos rlogin failed: %s\r\n",krb_err_txt[rc]);
	if (Kflag) {
paranoid:
	    /*
	     * Paranoid hosts, such as a Kerberos server, specify the
	     * Klogind daemon to disallow even password access here.
	     */
	    printf("Sorry, you must have Kerberos authentication to access this host.\r\n");
	    exit(1);
	}
    }
    getstr(username, usernamelen, "Local user");
    getstr(term, termsize, "Terminal type");
    if (getuid())
	return (-1);
    if (getpwnam(username) == NULL)
	return (-1);

    /*
     * if Kerberos login failed because of an error in GetKerberosData,
     * return the indication of a bad attempt.  User will be prompted
     * for a password.  We CAN'T check the .rhost file, because we need 
     * the remote username to do that, and the remote username is in the 
     * Kerberos ticket.  This affects ONLY the case where there is Kerberos 
     * on both ends, but Kerberos fails on the server end. 
     */
    if (rc)
	return (-1);

    rc = kuserok(kdata, username);
    if (rc) {
	printf("login: %s has not given you permission to login without a password.\r\n", username);
	if (Kflag)
	    exit(1);
	return (-1);
    }
    return (0);
}

static void getstr(char *buf, int cnt, char *err)
{
    int ocnt = cnt;
    char *obuf = buf;
    char c;

    do {
	if (read(0, &c, 1) != 1)
	    exit(1);
	if (--cnt < 0) {
	    fprintf(stderr, "%s '%.*s' too long, %d characters maximum.\r\n",
		    err, ocnt, obuf, ocnt-1);
	    exit(1);
	}
	*buf++ = c;
    } while (c != 0);
}

static int doremoteterm(char *term, struct termios *tp)
{
    char *cp = strchr(term, '/');
    struct speeds *cpp;
    char *speed;

    if (cp) {
	*cp++ = '\0';
	speed = cp;
	cp = strchr(speed, '/');
	if (cp)
	    *cp++ = '\0';
	for (cpp = speeds; cpp->name != NULL; cpp++) {
	    if (strcmp(cpp->name, speed) == 0) {
		cfsetispeed(tp, cpp->value);
		cfsetospeed(tp, cpp->value);
		break;
	    }
	}
    }
}

/*
 * This routine handles cleanup stuff, notification service, and the like.
 * It exits only in the child process.
 */
static void dofork(ALsession session)
{
    int child;
    struct sigaction act;

    if ((child=fork()) == 0)
	return; /* Child process */

    chdir("/");	/* Let's not keep the fs busy... */

    /* If we're the parent, watch the child until it dies */
    while(wait(0) != child)
	;

    /* Cleanup stuff */

    /* Send a SIGHUP to everything in the process group, but not us.
     * Originally included to support Zephyr over rlogin/telnet
     * connections, but it has some general use, since any personal
     * daemon can setpgrp(0, getpgrp(getppid())) before forking to be
     * sure of receiving a HUP when the user logs out.
     *
     * Note that we are assuming that the shell will set its process
     * group to its process id. Our csh does, anyway, and there is no
     * other way to reliably find out what that shell's pgrp is.
     */

    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;
    act.sa_handler = SIG_IGN;
    sigaction(SIGHUP, &act, NULL);
    if (kill(-child, SIGHUP) == -1) {
	/* EINVAL shouldn't happen (SIGHUP is a constant),
	 * ESRCH could, but we ignore it
	 * EPERM means something actually is wrong, so log it
	 * (in this case, the signal didn't get delivered but
	 * something might have wanted it...)
	 */
	if(errno == EPERM)
	    syslog(LOG_DEBUG,
		   "EPERM trying to kill login process group: child_pgrp %d",
		   child);
    }

    /* Run dest_tkt to destroy tickets */
    dest_tkt();		/* If this fails, we lose quietly */

#ifdef USE_SETPAG
    /* Destroy any AFS tokens */
    if (pagflag)
	ktc_ForgetAllTokens();
#endif

    ALend(session);

    /* Leave */
    exit(0);
}

static gid_t tty_gid(gid_t default_gid)
{
    struct group *getgrnam(), *gr;
    int gid = default_gid;

    gr = getgrnam(TTYGRPNAME);
    if (gr != (struct group *) 0)
	gid = gr->gr_gid;

    endgrent();

    return (gid);
}

static void getlongpass(char *prompt, char *pbuf, int pbuflen)
{
    struct termios tio;
    tcflag_t flags;
    char *p;
    int c;
    FILE *fi;
    struct sigaction act, oact;

    if ((fi = fdopen(open("/dev/tty", 2), "r")) == NULL)
	fi = stdin;
    else
	setbuf(fi, (char *)NULL);
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;
    act.sa_handler = SIG_IGN;
    sigaction(SIGINT, &act, &oact);
    tcgetattr(fileno(fi), &tio);
    flags = tio.c_lflag;
    tio.c_lflag &= ~ECHO;
    tcsetattr(fileno(fi), TCSANOW, &tio);
    fprintf(stderr, "%s", prompt);
    fflush(stderr);
    for (p = pbuf; (c = getc(fi)) != '\n' && c != EOF;) {
	if (p < &pbuf[pbuflen - 1])
	    *p++ = c;
    }
    *p = '\0';
    fprintf(stderr, "\n");
    fflush(stderr);
    tio.c_lflag = flags;
    tcsetattr(fileno(fi), TCSANOW, &tio);
    sigaction(SIGINT, &oact, NULL);
    if (fi != stdin)
	fclose(fi);
}

static void init_wgfile()
{
    char wgfile[16];
    static char wgbuf[32];

    strcpy(wgfile, "/tmp/wg.XXXXXX");
    mktemp(wgfile);
    sprintf(wgbuf, "WGFILE=%s", wgfile);
    putenv(wgbuf);
}

/*
 * Verify the Kerberos ticket-granting ticket just retrieved for the
 * user.  If the Kerberos server doesn't respond, assume the user is
 * trying to fake us out (since we DID just get a TGT from what is
 * supposedly our KDC).  If the rcmd.<host> service is unknown (i.e.,
 * the local srvtab doesn't have it), let her in.
 *
 * Returns 1 for confirmation, -1 for failure, 0 for uncertainty.
 */
static int verify_krb_tgt(char *realm)
{
    char local_hostname[MAXHOSTNAMELEN], phost[BUFSIZ];
    struct hostent *hp;
    KTEXT_ST ticket;
    AUTH_DAT authdata;
    unsigned long addr;
    static char rcmd[] = "rcmd";
    char key[8];
    int krbval, retval, have_keys;
    extern char *krb_get_phost();

    if (gethostname(local_hostname, sizeof(local_hostname)) == -1) {
	perror("cannot retrieve local hostname");
	return (-1);
    }
    strncpy(phost, krb_get_phost(local_hostname), sizeof(phost));
    phost[sizeof(phost) - 1] = 0;
    hp = gethostbyname(local_hostname);
    if (!hp) {
	perror ("cannot retrieve local host address");
	return (-1);
    }
    memcpy(&addr, hp->h_addr, sizeof (addr));
    /* Do we have rcmd.<host> keys? */
    have_keys = read_service_key(rcmd, phost, realm, 0, KEYFILE, key) ? 0 : 1;
    krbval = krb_mk_req(&ticket, rcmd, phost, realm, 0);
    if (krbval == KDC_PR_UNKNOWN) {
	/*
	 * Our rcmd.<host> principal isn't known -- just assume valid
	 * for now?  This is one case that the user _could_ fake out.
	 */
	return ((have_keys) ? -1 : 0);
    } else if (krbval != KSUCCESS) {
	printf("Unable to verify Kerberos TGT: %s\n", krb_err_txt[krbval]);
	syslog(LOG_NOTICE | LOG_AUTH, "Kerberos TGT bad: %s",
	       krb_err_txt[krbval]);
	return (-1);
    }
    /* got ticket, try to use it */
    krbval = krb_rd_req(&ticket, rcmd, phost, addr, &authdata, "");
    if (krbval != KSUCCESS) {
	if (krbval == RD_AP_UNDEC && !have_keys) {
	    retval = 0;
	} else {
	    retval = -1;
	    printf("Unable to verify rcmd ticket: %s\n", krb_err_txt[krbval]);
	}
	syslog(LOG_NOTICE | LOG_AUTH, "can't verify rcmd ticket: %s;%s\n",
	       krb_err_txt[krbval], retval ? "srvtab found, assuming failure"
	       : "no srvtab found, assuming success");
	goto EGRESS;
    }
    /*
     * The rcmd.<host> ticket has been received _and_ verified.
     */
    retval = 1;
    /* do cleanup and return */
EGRESS:
    memset(&ticket, 0, sizeof(ticket));
    memset(&authdata, 0, sizeof(authdata));
    return (retval);
}

