#ifndef lint
static	char *sccsid = "@(#)login.c	4.33 (Berkeley) 83/09/02";
#endif

/*
 * login [ name ]
 * login -r hostname (for rlogind)
 * login -h hostname (for telnetd, etc.)
 */

/*
 * 8-12-85 SAJ@PREP
 *   Timeout is now 3 minutes. 
 *
 * 7-15-84
 * Hacked by cjl@prep to do:
 *   If environment variable TERM is supdup, and there is an environment
 *     variable TERMCAP then pass TERM and TERMCAP in new environment.
 *     This is useful for SUPDUP connections.
 *   If TERM is supdup, then get ttyloc from /tmp, adn use it as the remote
 *     host name. Also put it in env var TTYLOC.
 *   If the file /usr/adm/log exists, 
 *     then a line of text is appended to the file
 *     whenever a login is attempted.  Useful for checking if net randoms
 *     from far away are trying to hack the system.
 *
 * Hacked by saj@prep 7/84
 *   If the file /etc/sstab exists, then it is a table of safe chaosnet sites.
 *     Passwords are not required when logging from safe sites.
 *     Format of safe site file:
 *       Each line has in octal <subnet-number>,<host-number>
 *       <host-number> of 0 means all hosts on that subnet.
 *       For example, 26,0 means all hosts on subnet 26 octal are safe.
 * Makefile has to also be changed so login is linked with -lhosts
 */

#include <sys/param.h>
#include <sys/quota.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/resource.h>

#include <ctype.h>
#include <sgtty.h>
#include <utmp.h>
#include <signal.h>
#include <pwd.h>
#include <stdio.h>
#include <lastlog.h>
#include <errno.h>
#define	BSD42

#ifdef	CHAOS
#include <sys/chaos.h>
#endif	CHAOS

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

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

#define NMAX	sizeof(utmp.ut_name)

#define	FALSE	0
#define	TRUE	-1

char	nolog[] =	"/etc/nologin";
char	qlog[]  =	".hushlogin";
char	securetty[] =	"/etc/securetty";
char	maildir[30] =	"/usr/spool/mail/";
char	lastlog[] =	"/usr/adm/lastlog";
#ifdef	CHAOS
char	sstab[] =	"/etc/sstab";
#endif
char	logf[] =	"/usr/adm/log";
struct	passwd nouser = {"", "nope", -1, -1, -1, "", "", "", "" };
struct	sgttyb ttyb;
struct	utmp utmp;
char	minusnam[16] = "-";
/*
 * This bounds the time given to login.  We initialize it here
 * so it can be patched on machines where it's too small.
 */
int	timeout = 180;    /* 3 minutes */

char	homedir[64] = "HOME=";
char	shell[64] = "SHELL=";
char	term[64] = "TERM=";
char	user[20] = "USER=";
#ifndef	TTYLOC
char	ttyloc[64] = "TTYLOC=";
#endif

/* NOTE WELL extra zeros at the end of envinit
   they are for possible TERMCAP and TTYLOC */
char	*envinit[] =
    { homedir, shell, "PATH=:/usr/ucb:/bin:/usr/bin:/usr/local/bin",
      term, user, 0, 0, 0 };

struct	passwd *pwd;
struct	passwd *getpwnam();
char	*strcat(), *rindex(), *index();
int	setpwent();
int	timedout();
char	*ttyname();
char	*crypt();
char	*getpass();
char	*stypeof();
extern	char **environ;
extern	int errno;

struct	tchars tc = {
	CINTR, CQUIT, CSTART, CSTOP, CEOT, CBRK
};
struct	ltchars ltc = {
	CSUSP, CDSUSP, CRPRNT, CFLUSH, CWERASE, CLNEXT
};

int	rflag;
char	rusername[NMAX+1], lusername[NMAX+1];
char	rpassword[NMAX+1];
char	name[NMAX+1];
char	*rhost;

char	*getenv();

main(argc, argv)
	char *argv[];
{
	register char *namep;
	int t, f, c, i;
	int invalid, quietlog;
	FILE *nlfd;
	char *ttyn,*td;
	int ldisc = 0, zero = 0, safeflag = 0;
	char *host = NULL;

	signal(SIGALRM, timedout);
	alarm(timeout);
	signal(SIGQUIT, SIG_IGN);
	signal(SIGINT, SIG_IGN);
	setpriority(PRIO_PROCESS, 0, 0);
	quota(Q_SETUID, 0, 0, 0);
	/*
	 * -r is used by rlogind to cause the autologin protocol;
	 * -h is used by other servers to pass the name of the
	 * remote host to login so that it may be placed in utmp and wtmp
	 */
	ttyn = ttyname(0);
	if (ttyn==(char *)0)
		ttyn = "/dev/tty??";
	if (argc > 1) {
		if (strcmp(argv[1], "-r") == 0) {
#ifdef	CHAOS
			if (getuid() == 0) safeflag = safe(host = argv[2]);
#endif
			rflag = doremotelogin(argv[2]);
			SCPYN(utmp.ut_host, argv[2]);
			argc = 0;
		} else if (strcmp(argv[1], "-h") == 0 && getuid() == 0) {
#ifdef	CHAOS
			safeflag = safe(host = argv[2]);
#endif	CHAOS
			SCPYN(utmp.ut_host, argv[2]);
			argc = 0;
		} else if (isdigit(*(ttyn + sizeof("/dev/tty")-1)) ||
			   !strcmp(ttyn,"/dev/console")) {
#ifdef LOCAL_SAFE
			safeflag = -1;
#endif
		} 
	}
	i = LCRTBS|LCRTERA|LCRTKIL|LCTLECH|LDECCTQ;
	ioctl(0, TIOCLSET, &i);
	ioctl(0, TIOCNXCL, 0);
	ioctl(0, FIONBIO, &zero);
	ioctl(0, FIOASYNC, &zero);
	ioctl(0, TIOCGETP, &ttyb);
	/*
	 * If talking to an rlogin process,
	 * propagate the terminal type and
	 * baud rate across the network.
	 */
	if (rflag)
		doremoteterm(term, &ttyb);
	ioctl(0, TIOCSLTC, &ltc);
	ioctl(0, TIOCSETC, &tc);
	ioctl(0, TIOCSETP, &ttyb);
	for (t = getdtablesize(); t > 3; t--)
		close(t);
	do {
		ldisc = 0;
		ioctl(0, TIOCSETD, &ldisc);
		invalid = FALSE;
		SCPYN(utmp.ut_name, "");
		/*
		 * Name specified, take it.
		 */
		if (argc > 1) {
			SCPYN(utmp.ut_name, argv[1]);
			argc = 0;
		}
		/*
		 * If remote login take given name,
		 * otherwise prompt user for something.
		 */
		if (rflag) {
			SCPYN(utmp.ut_name, lusername);
			/* autologin failed, prompt for passwd */
			if (rflag == -1)
				rflag = 0;
		} else
			getloginname(&utmp);
		if (!strcmp(pwd->pw_shell, "/bin/csh")) {
			ldisc = NTTYDISC;
			ioctl(0, TIOCSETD, &ldisc);
		}
		/*
		 * If no remote login authentication and
		 * a password exists for this user, prompt
		 * for one and verify it.
 		 * But not if safe site and user isn't root. -cjl
		 */
		if (!rflag && 
		    (pwd == &nouser || pwd->pw_uid == 0 || 
		     ((*pwd->pw_passwd != '\0') && !safeflag))) {
			char *pp;

			setpriority(PRIO_PROCESS, 0, -4);
			pp = getpass("Password:");
			namep = crypt(pp, pwd->pw_passwd);
			setpriority(PRIO_PROCESS, 0, 0);
			if (*pwd->pw_passwd != '\0') {
 			    if (strcmp(namep, pwd->pw_passwd)) {
				invalid = TRUE;
				logmsg("bad password",ttyn,host,pwd->pw_name,
					safeflag);
			    };
			};
		};
		/*
		 * If user not super-user, check for logins disabled.
		 */
		if (pwd->pw_uid != 0 && (nlfd = fopen(nolog, "r")) > 0) {
			while ((c = getc(nlfd)) != EOF)
				putchar(c);
			fflush(stdout);
			logmsg("nonroot logins disabled",ttyn,host,
			       pwd->pw_name,safeflag);
			sleep(5);
			exit(0);
		}
		/*
		 * If valid so far and root is logging in,
		 * see if root logins on this terminal are permitted.
		 */
		if (!invalid && pwd->pw_uid == 0 &&
		    !rootterm(ttyn+sizeof("/dev/")-1)) {
			logerr("ROOT LOGIN REFUSED %s",
			    ttyn+sizeof("/dev/")-1);
			logmsg("root logins disabled",ttyn,host,
			       pwd->pw_name,safeflag);
			invalid = TRUE;
		}
		if (invalid) {
			printf("Login incorrect\n");
			if (ttyn[sizeof("/dev/tty")-1] == 'd')
				logerr("BADDIALUP %s %s",
				    ttyn+sizeof("/dev/")-1, utmp.ut_name);
		}
		if (*pwd->pw_shell == '\0')
			pwd->pw_shell = "/bin/sh";
		i = strlen(pwd->pw_shell);
		if (chdir(pwd->pw_dir) < 0 && !invalid ) {
			if (chdir("/") < 0) {
				printf("No directory!\n");
				invalid = TRUE;
			} else {
				printf("No directory! %s\n",
				   "Logging in with home=/");
				pwd->pw_dir = "/";
			}
		}
		/*
		 * Remote login invalid must have been because
		 * of a restriction of some sort, no extra chances.
		 */
		if (rflag && invalid)
			exit(1);
	} while (invalid);
/* committed to login turn off timeout */
	alarm(0);

	logmsg("good login",ttyn,host,pwd->pw_name,safeflag);
	if (quota(Q_SETUID, pwd->pw_uid, 0, 0) < 0) {
		if (errno == EUSERS)
			printf("%s.\n%s.\n",
			   "Too many users logged on already",
			   "Try again later");
		else if (errno == EPROCLIM)
			printf("You have too many processes running.\n");
		else
			perror("setuid");
		sleep(5);
		exit(0);
	}
	time(&utmp.ut_time);
	t = ttyslot();
#ifndef	TTYLOC
	/* if the terminal is a supdup terminal, try to use ttylocs */
	if ((td = (char *) getenv("TERM")) != NULL && 
	  strcmp("supdup", td) == 0) {
		char fn[40],ttyl[40],**ep;
		sprintf(fn,"/tmp/%s.ttyloc",rindex(ttyn, '/')+1);
		if ((f = open(fn,0)) >= 0) {
			read(f,ttyl,sizeof(ttyl)-1);
			SCPYN(utmp.ut_host,ttyl);
			strcat(ttyloc,ttyl);
			close(f);
			unlink(fn);
			for (ep = envinit; *ep; ep++);
			*ep = ttyloc;
		}
	}
#endif
	if (t > 0 && (f = open("/etc/utmp", 1)) >= 0) {
		lseek(f, (long)(t*sizeof(utmp)), 0);
		SCPYN(utmp.ut_line, rindex(ttyn, '/')+1);
		write(f, (char *)&utmp, sizeof(utmp));
		close(f);
	}
	if (t > 0 && (f = open("/usr/adm/wtmp", 1)) >= 0) {
		lseek(f, 0L, 2);
		write(f, (char *)&utmp, sizeof(utmp));
		close(f);
	}
#ifdef	TTYLOC
	do_ttyloc();
#endif
	quietlog = access(qlog, 0) == 0;
	if ((f = open(lastlog, 2)) >= 0) {
		struct lastlog ll;

		lseek(f, (long)pwd->pw_uid * 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, (long)pwd->pw_uid * sizeof (struct lastlog), 0);
		time(&ll.ll_time);
		SCPYN(ll.ll_line, rindex(ttyn, '/')+1);
		SCPYN(ll.ll_host, utmp.ut_host);
		write(f, (char *) &ll, sizeof ll);
		close(f);
	}
	chown(ttyn, pwd->pw_uid, pwd->pw_gid);
	chmod(ttyn, 0622);
	setgid(pwd->pw_gid);
	strncpy(name, utmp.ut_name, NMAX);
	name[NMAX] = '\0';
	initgroups(name, pwd->pw_gid);
	quota(Q_DOWARN, pwd->pw_uid, (dev_t)-1, 0);
	setuid(pwd->pw_uid);
	strncat(homedir, pwd->pw_dir, sizeof(homedir)-6);
	strncat(shell, pwd->pw_shell, sizeof(shell)-7);
	if (term[strlen("TERM=")] == 0)
		strncat(term, stypeof(ttyn), sizeof(term)-6);
	strncat(user, pwd->pw_name, sizeof(user)-6);
	environ = envinit;
	if ((namep = rindex(pwd->pw_shell, '/')) == NULL)
		namep = pwd->pw_shell;
	else
		namep++;
	strcat(minusnam, namep);
	umask(022);
	if (ttyn[sizeof("/dev/tty")-1] == 'd')
		logerr("DIALUP %s %s",
		    ttyn+sizeof("/dev/")-1, pwd->pw_name);
	if (!quietlog) {
		showmotd();
		strcat(maildir, pwd->pw_name);
		if (access(maildir,4)==0) {
			struct stat statb;
			stat(maildir, &statb);
			if (statb.st_size)
				printf("You have mail.\n");
		}
	}
	signal(SIGALRM, SIG_DFL);
	signal(SIGQUIT, SIG_DFL);
	signal(SIGINT, SIG_DFL);
	signal(SIGTSTP, SIG_IGN);
	execlp(pwd->pw_shell, minusnam, 0);
	perror(pwd->pw_shell);
	printf("No shell\n");
	exit(0);
}

logmsg(msg,ttyn,host,uname,safe)
char *msg,*ttyn,*host,*uname;
int safe;
{
  long t;
  char tim[25];
  FILE *logfile = fopen(logf,"a");
  FILE *console = fopen("/dev/console","a");
  char buf[512];

  if (logfile != NULL) {
    t = time(0);
    strncpy(tim,ctime(&t),24);
    tim[24] = (char) 0;
    sprintf(buf,"%s login: \"%s\" %s %s %s %s",
      tim,msg,ttyn+sizeof("/dev/")-1,
      host ? host : "local",uname,
      safe ? "(safe-site)" : "(unsafe-site)");
    fprintf(logfile,"%s\n",buf);
    fprintf(console,"%s\r\n",buf);
    fclose(logfile);
    fclose(console);
  }
}

#ifdef CHAOS
safe(hostname)
char *hostname;
{
	short chaddr;
	unsigned char host,subnet;
	char hn[128];
	struct chstatus foo;
	int tabsubnet, tabhost;
	FILE *fp;
	
	strcpy(hn,hostname);
	if (index(hn,'.') != NULL) *index(hn,'.') = '\000';
	if (!(chaddr = chaos_addr(hn, 0))) return(0);
	subnet = (chaddr>>8)&0177;
	host = chaddr&0177;

	/* determine whether the remote host is safe */
	if ((fp = fopen("/etc/sstab","r")) == NULL) return(0);
	while (fscanf(fp,"%o,%o\n",&tabsubnet,&tabhost)!=EOF) {
		if (((host==tabhost)&&(subnet==tabsubnet)) ||
		    ((tabhost==0)&&(subnet==tabsubnet))){
			fclose(fp);
		return(-1);
		};
	};
	fclose(fp);
	return(0);
};
#endif	CHAOS

getloginname(up)
	register struct utmp *up;
{
	register char *namep;
	char c;

	while (up->ut_name[0] == '\0') {
		namep = up->ut_name;
		printf("login: ");
		while ((c = getchar()) != '\n') {
			if (c == ' ')
				c = '_';
			if (c == EOF)
				exit(0);
			if (namep < up->ut_name+NMAX)
				*namep++ = c;
		}
	}
	strncpy(lusername, up->ut_name, NMAX);
	lusername[NMAX] = 0;
	setpwent();
	if ((pwd = getpwnam(lusername)) == NULL)
		pwd = &nouser;
	endpwent();
}

timedout()
{

	printf("Login timed out after %d seconds\n", timeout);
	exit(0);
}

int	stopmotd;
catch()
{

	signal(SIGINT, SIG_IGN);
	stopmotd++;
}

rootterm(tty)
	char *tty;
{
	register FILE *fd;
	char buf[100];

	if ((fd = fopen(securetty, "r")) == NULL)
		return(1);
	while (fgets(buf, sizeof buf, fd) != NULL) {
		buf[strlen(buf)-1] = '\0';
		if (strcmp(tty, buf) == 0) {
			fclose(fd);
			return(1);
		}
	}
	fclose(fd);
	return(0);
}

showmotd()
{
	FILE *mf;
	register c;

	signal(SIGINT, catch);
	if ((mf = fopen("/etc/motd","r")) != NULL) {
		while ((c = getc(mf)) != EOF && stopmotd == 0)
			putchar(c);
		fclose(mf);
	}
	signal(SIGINT, SIG_IGN);
}

#undef	UNKNOWN
#define UNKNOWN "su"

char *
stypeof(ttyid)
	char *ttyid;
{
	static char typebuf[16];
	char buf[50];
	register FILE *f;
	register char *p, *t, *q;

	if (ttyid == NULL)
		return (UNKNOWN);
	/*
	 * CJL's kludge to make supdup win: If TERM is supdup,
	 * preserve TERM and TERMCAP. SUPDUP sets them up the right way.
	 */
	if ((t = (char *) getenv("TERM")) != NULL && strcmp("supdup", t) == 0) {
		if ((p = (char *) getenv("TERMCAP")) != NULL) {
			char **ep;
			for (ep = envinit; *ep; ep++);
			*ep = p-8; /* back the pointer up 8 bytes "TERMCAP=" */
		}
		return (t);
	}
	f = fopen("/etc/ttytype", "r");
	if (f == NULL)
		return (UNKNOWN);
	/* split off end of name */
	for (p = q = ttyid; *p != 0; p++)
		if (*p == '/')
			q = p + 1;

	/* scan the file */
	while (fgets(buf, sizeof buf, f) != NULL) {
		for (t = buf; *t != ' ' && *t != '\t'; t++)
			;
		*t++ = 0;
		while (*t == ' ' || *t == '\t')
			t++;
		for (p = t; *p > ' '; p++)
			;
		*p = 0;
		if (strcmp(q,t) == 0) {
			strcpy(typebuf, buf);
			fclose(f);
			return (typebuf);
		}
	}
	fclose (f);
	return (UNKNOWN);
}

doremotelogin(host)
	char *host;
{
	FILE *hostf;
	int first = 1;

	getstr(rusername, sizeof (rusername), "remuser");
	getstr(lusername, sizeof (lusername), "locuser");
	getstr(term+5, sizeof(term)-5, "Terminal type");
	if (getuid()) {
		pwd = &nouser;
		goto bad;
	}
	setpwent();
	pwd = getpwnam(lusername);
	endpwent();
	if (pwd == NULL) {
		pwd = &nouser;
		goto bad;
	}
	hostf = pwd->pw_uid ? fopen("/etc/hosts.equiv", "r") : 0;
again:
	if (hostf) {
		char ahost[32];

		while (fgets(ahost, sizeof (ahost), hostf)) {
			char *user;

			if ((user = index(ahost, '\n')) != 0)
				*user++ = '\0';
			if ((user = index(ahost, ' ')) != 0)
				*user++ = '\0';
			if (!strcmp(host, ahost) &&
			    !strcmp(rusername, user ? user : lusername)) {
				fclose(hostf);
				return (1);
			}
		}
		fclose(hostf);
	}
	if (first == 1) {
		char *rhosts = ".rhosts";
		struct stat sbuf;

		first = 0;
		if (chdir(pwd->pw_dir) < 0)
			goto again;
		if (lstat(rhosts, &sbuf) < 0)
			goto again;
		if ((sbuf.st_mode & S_IFMT) == S_IFLNK) {
			printf("login: .rhosts is a soft link.\r\n");
			goto bad;
		}
		hostf = fopen(rhosts, "r");
		fstat(fileno(hostf), &sbuf);
		if (sbuf.st_uid && sbuf.st_uid != pwd->pw_uid) {
			printf("login: Bad .rhosts ownership.\r\n");
			fclose(hostf);
			goto bad;
		}
		goto again;
	}
bad:
	return (-1);
}

getstr(buf, cnt, err)
	char *buf;
	int cnt;
	char *err;
{
	char c;

	do {
		if (read(0, &c, 1) != 1)
			exit(1);
		if (--cnt < 0) {
			printf("%s too long\r\n", err);
			exit(1);
		}
		*buf++ = c;
	} while (c != 0);
}

char	*speeds[] =
    { "0", "50", "75", "110", "134", "150", "200", "300",
      "600", "1200", "1800", "2400", "4800", "9600", "19200", "38400" };
#define	NSPEEDS	(sizeof (speeds) / sizeof (speeds[0]))

doremoteterm(term, tp)
	char *term;
	struct sgttyb *tp;
{
	char *cp = index(term, '/');
	register int i;

	if (cp) {
		*cp++ = 0;
		for (i = 0; i < NSPEEDS; i++)
			if (!strcmp(speeds[i], cp)) {
				tp->sg_ispeed = tp->sg_ospeed = i;
				break;
			}
	}
	tp->sg_flags = ECHO|CRMOD|ANYP|XTABS;
}

logerr(fmt, a1, a2, a3)
	char *fmt, *a1, *a2, *a3;
{
#ifdef LOGERR
	FILE *cons = fopen("/dev/console", "w");

	if (cons != NULL) {
		fprintf(cons, fmt, a1, a2, a3);
		fprintf(cons, "\n\r");
		fclose(cons);
	}
#endif
}
#ifdef	TTYLOC
/* If we're using ttyloc, then:
	if there is an environment variable called ttyloc, set the current
		location to that value.
	otherwise, set the current location to the default.
   we should probably do this by starting up ttyloc but we don't really
	want to spin off another process, so we steal the code.
*/

char *rindex();

do_ttyloc() {
	struct ttyloc loc;
	char *ttyn;
	char *tty;
	char *env_ttyloc;

	/* figure out our tty */
	ttyn = ttyname(0);

	/* if no name then give up already */
	if(ttyn == NULL)
		return;

	/* strip off the path */
	tty = rindex(ttyn, '/');
	if(tty == NULL)
		tty = ttyn;
	else tty++;

	strcpy(loc.ttyname, tty);

	/* is the TTYLOC environment variable defined?
		if so then set our location to that and we're done.
	*/
	env_ttyloc = getenv("TTYLOC");
	if(env_ttyloc != NULL) {
		strcpy(loc.ttyloc, env_ttyloc);
		ttyloc_change(&loc, TTYLOC_FILE);
		return;
		}

	/* if it's not, then try to set our default
	*/
	if(ttyloc_lookup(&loc, DEFAULT_TTYLOC_FILE)) {
		ttyloc_change(&loc, TTYLOC_FILE);
		return;
		}

	/* if there's no default, then just clear the location */
	strcpy(loc.ttyloc, "");
	ttyloc_change(&loc, TTYLOC_FILE);
	}
#endif
