/*
 * skeysh - an skey-only login program with all rlogin, options and tty stuff
 * ripped out. It is to be called as a login shell, and relies on the login
 * program to set up initial tty settings and utmp information. -- Wietse
 */

/*
 * Copyright (c) 1980, 1987, 1988 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) 1980, 1987, 1988 The Regents of the University of California.\n\
 All rights reserved.\n";
#endif /* not lint */

#ifndef lint
static char sccsid[] = "@(#)login.c	5.32.1.1 (Berkeley) 1/28/89";
#endif /* not lint */

#include "sys_defs.h"

#include <stdlib.h>
#include <unistd.h>

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

#ifdef HAS_UTMPX
#include <utmpx.h>
#else /* HAS_UTMPX */
#include <utmp.h>
#endif /* HAS_UTMPX */
#ifndef UT_NAMESIZE
#define UT_NAMESIZE	sizeof(((struct UTMP_STRUCT *)0)->ut_name)
#endif /* UT_NAMESIZE */

#include <signal.h>
#ifdef USE_OUR_LASTLOG_H
#include "lastlog.h"
#else
#ifndef UTMP_DECLARES_LASTLOG
#include <lastlog.h>
#endif
#endif
#include <errno.h>
#ifndef NO_TTYENT
#include <ttyent.h>
#endif /* NO_TTYENT */
#include <syslog.h>
#include <grp.h>
#include <pwd.h>
#include <setjmp.h>
#include <stdio.h>
#include <string.h>

#ifndef O_RDWR
#include <fcntl.h>
#endif

#ifdef SYSV_SHADOW
#include <shadow.h>
#include "sysv_shadow.h"
#endif /* SYSV_SHADOW */
#ifdef SYSV_LOGINDEFS
#include "sysv_default.h"
#endif /* SYSV_LOGINDEFS */

#ifndef TTYGRPNAME
#define	TTYGRPNAME	"tty"		/* name of group to own ttys */
#endif

#ifdef HAS_PATHS_H
#include <paths.h>			/* Use system version if present */
#else
#include "paths.h"			/* Customize this file first! */
#endif
#define	_PATH_MOTDFILE	"/etc/motd"
#define	_PATH_HUSHLOGIN	".hushlogin"

/* Ultrix syslog(3) has no facility stuff. */
#ifndef LOG_AUTH
#define LOG_AUTH	0
#define LOG_ODELAY	0
#endif

/*
 * This bounds the time given to login.  Not a define so it can
 * be patched on machines where it's too small.
 */
int	timeout = 300;

struct	passwd *pwd;
int	failures;
char	*hostname, *username, *tty;

main(argc, argv)
	int argc;
	char **argv;
{
	extern char **environ;
	char *tz, *term, *getenv();
	struct group *gr;
	register char *p;
	int ask, cnt;
	int quietlog;
	void timedout();
	int last_fd;
	void hungup();
	char *salt, *ttyn, *pp = 0;
	char tbuf[MAXPATHLEN + 2];
	char *ttyname();
	time_t time();
	/* Disable core dumps with cleartext or shadow passwords. */
#ifdef RLIMIT_CORE
	struct rlimit old_core_limit;
	struct rlimit new_core_limit;
#endif
	char *skey_getpass(), *skey_crypt();
	char *utmp_host();
#ifdef SYSV_LOGINDEFS
	int mask;
	int maxtrys;
#endif /* SYSV_LOGINDEFS */
#ifdef ENV_REMOTEHOST
	char *remotehost;
#endif
#ifdef ENV_REMOTEUSER
	char *remoteuser;
#endif

#ifdef RLIMIT_CORE
	/* Disable core dumps with cleartext or shadow passwords. */
	getrlimit(RLIMIT_CORE, &old_core_limit);
	new_core_limit.rlim_cur = 0;
	new_core_limit.rlim_max = old_core_limit.rlim_max;
	setrlimit(RLIMIT_CORE, &new_core_limit);
#endif

	/* Do this before NIS+ or other library routines open files. */
	for (cnt = open_limit(); cnt > 2; cnt--)
		close(cnt);

	openlog("skeysh", LOG_ODELAY | LOG_PID, FACILITY);

#ifdef SYSV_LOGINDEFS
	/* Read defaults file and set the login timeout period. */
	sysv_defaults();
	timeout = atoi(default_timeout);
	maxtrys = atoi(default_maxtrys);
	if (sscanf(default_umask, "%o", &mask) != 1 || (mask & ~0777))
		syslog(LOG_WARNING, "bad umask default: %s", default_umask);
	else
		umask(mask);
#endif /* SYSV_LOGINDEFS */

	(void)signal(SIGALRM, timedout);
	(void)alarm((u_int)timeout);
	(void)signal(SIGHUP, hungup);
	(void)signal(SIGQUIT, SIG_IGN);
	(void)signal(SIGINT, SIG_IGN);

	/*
	 * Some sanity checks, to keep people from shooting themselves into
	 * the foot. This program does not do any damage when not invoked as
	 * login shell. After all, they can always invoke the login command
	 * directly.
	 */
	if (geteuid()) {
		printf("skeysh: Insufficient privilege.\n");
		sleepexit(1);
	}
	if (argv[0][0] != '-') {
		printf("skeysh: Must be run as login shell.\n");
		sleepexit(1);
	}
	if (ttyslot() <= 0) {
		printf("skeysh: No utmp entry for this terminal.\n");
		sleepexit(1);
	}

	/*
	 * This program has *no* options.
	 */
	argc--;
	argv++;

	/*
	 * Figure out if we should ask for the username or not. The name
	 * may be given on the command line.
	 */
	if (*argv) {
		username = *argv;
		ask = 0;
	} else
		ask = 1;

	/*
	 * Determine the tty name. BSD takes the basename, SYSV4 takes
	 * whatever remains after stripping the "/dev/" prefix. The code
	 * below should produce sensible results in either environment.
	 */
	ttyn = ttyname(0);
	if (ttyn == NULL || *ttyn == '\0')
		ttyn = "/dev/tty??";
	if (tty = strchr(ttyn + 1, '/'))
		++tty;
	else
		tty = ttyn;

	/*
	 * Extract hostname from all possible types of utmp files.
	 */
	hostname = utmp_host();

	for (cnt = 0;; ask = 1) {

		if (ask) {
			getloginname();
		}
		/*
		 * Note if trying multiple user names;
		 * log failures for previous user name,
		 * but don't bother logging one failure
		 * for nonexistent name (mistyped username).
		 */
		if (failures && strcmp(tbuf, username)) {
			if (failures > (pwd ? 0 : 1))
				badlogin(tbuf);
			failures = 0;
		}
		(void)strcpy(tbuf, username);
		if (pwd = getpwnam(username))
			salt = pwd->pw_passwd;
		else
			salt = "xx";

		/*
		 * If no pre-authentication and a password exists
		 * for this user, prompt for one and verify it.
		 */
		pp = skey_getpass("Password:", pwd, 1);
		p = skey_crypt(pp, salt, pwd, 0);

		if (pwd && !strcmp(p, pwd->pw_passwd))
			break;

		printf("Login incorrect\n");
		failures++;
#ifdef SYSV_LOGINDEFS
		/* max number of attemps and delays taken from defaults file */
		if (++cnt >= maxtrys) {
			badlogin(username);
			sleepexit(1);
		}
		sleep(atoi(default_sleep));
#else /* SYSV_LOGINDEFS */
		/* we allow 10 tries, but after 3 we start backing off */
		if (++cnt > 3) {
			if (cnt >= 10) {
				badlogin(username);
				sleepexit(1);
			}
			sleep((u_int)((cnt - 3) * 5));
		}
#endif /* SYSV_LOGINDEFS */
	}

	/* committed to login -- turn off timeout */
	(void)alarm((u_int)0);

	/*
	 * If valid so far and root is logging in, see if root logins on
	 * this terminal are permitted.
	 */
	if (pwd->pw_uid == 0 && !rootterm(tty)) {
		if (hostname)
			syslog(LOG_NOTICE, "ROOT LOGIN REFUSED FROM %.100s",
			    hostname);
		else
			syslog(LOG_NOTICE, "ROOT LOGIN REFUSED ON %s", tty);
		printf("Login incorrect\n");
		sleepexit(1);
	}

	/*
	 * Syslog each successful login, so we don't have to watch hundreds
	 * of wtmp or lastlogin files.
	 */
	if (hostname) {
		syslog(LOG_INFO, "login from %.100s as %s", hostname, pwd->pw_name);
	} else {
		syslog(LOG_INFO, "login on %s as %s", tty, pwd->pw_name);
	}

	/*
	 * Update the utmp files, either BSD or SYSV style.
	 */
#ifdef SYSV_UTMP
	if (UTMP_LOGIN(tty, username, hostname ? hostname : "") != 0) {
		printf("No utmpx entry.  You must exec \"login\" from the lowest level \"sh\".\n");
		sleepexit(0);
	}
#else /* SYSV_UTMP */
	{
		struct utmp utmp;

		memset((char *)&utmp, 0, sizeof(utmp));
		(void)time(&utmp.ut_time);
		strncpy(utmp.ut_name, username, sizeof(utmp.ut_name));
		if (hostname)
			strncpy(utmp.ut_host, hostname, sizeof(utmp.ut_host));
		strncpy(utmp.ut_line, tty, sizeof(utmp.ut_line));
		login(&utmp);
	}
#endif /* SYSV_UTMP */

#ifdef	HAS_SETLOGIN
	setlogin(pwd->pw_name);
#endif

	/*
	 * Open the lastlogin file before we give away root privileges.
	 * Before printing the last login time we must know if the
	 * ~/.hushlogin file exists. However, the home directory may be
	 * remote, so that we can enter it only after changing identity.
	 * By opening the lastlogin file in advance, we can still update
	 * it after we have dropped root privileges.
	 */
#ifdef IRIX_LASTLOGIN
	{
	char lastfile[BUFSIZ];
	sprintf(lastfile, "%s/%s", _PATH_LASTLOG, pwd->pw_name);
	last_fd = open(lastfile, O_CREAT | O_RDWR, 0644);
	fchmod(last_fd, 0644);
	}
#else /* IRIX_LASTLOGIN */
	last_fd = open(_PATH_LASTLOG, O_RDWR, 0);
#endif /* IRIX_LASTLOGIN */

	/*
	 * Set device protections, depending on what terminal the
	 * user is logged in. This feature is used on Suns to give
	 * console users better privacy.
	 */
	login_fbtab(tty, pwd->pw_uid, pwd->pw_gid);

	(void)chown(ttyn, pwd->pw_uid,
	    (gr = getgrnam(TTYGRPNAME)) ? gr->gr_gid : pwd->pw_gid);
	(void)chmod(ttyn, 0620);

	/* Give up root privileges: no way back from here. */

	if (setgid(pwd->pw_gid)) {
		printf("skeysh: bad gid: %d\n", pwd->pw_gid);
		sleepexit(0);
	}

	initgroups(username, pwd->pw_gid);

	if (setuid(pwd->pw_uid)) {
		printf("skeysh: bad uid: %d\n", pwd->pw_uid);
		sleepexit(0);
	}

	/*
	 * The home directory may be remote, so we enter it after the
	 * change of identity is complete. Only then we should test for
	 * the existence of a .hushlogin file. The lastlogin file was
	 * was opened while we were still root, so we can still update
	 * the time of last login.
	 */
	if (chdir(pwd->pw_dir) < 0) {
		printf("No directory %s!\n", pwd->pw_dir);
		if (chdir("/"))
			exit(0);
		pwd->pw_dir = "/";
		printf("Logging in with home = \"/\".\n");
	}
	quietlog = access(_PATH_HUSHLOGIN, F_OK) == 0;
	dolastlog(quietlog, last_fd);

	if (*pwd->pw_shell == '\0')
		pwd->pw_shell = _PATH_BSHELL;

	/*
	 * Set up a somewhat censored environment. It is too dangerous for
	 * set-uid program to preserve much across a change of userid.
	 */
	tz = getenv("TZ");
	term = getenv("TERM");
#ifdef ENV_REMOTEHOST
	remotehost = getenv("REMOTEHOST");
#endif
#ifdef ENV_REMOTEUSER
	remoteuser = getenv("REMOTEUSER");
#endif
	if (environ)
		environ[0] = 0;
	(void)setenv("HOME", pwd->pw_dir, 1);
	(void)setenv("SHELL", pwd->pw_shell, 1);
	(void)setenv("USER", pwd->pw_name, 1);
	sprintf(tbuf, "%s/%s", _PATH_MAILDIR, pwd->pw_name);
	setenv("MAIL", tbuf, 1);
#ifdef SYSV_LOGINDEFS
	(void)setenv("PATH", pwd->pw_uid ? default_path : default_supath, 0);
#else /* SYSV_LOGINDEFS */
	(void)setenv("PATH", _PATH_DEFPATH, 0);
#endif /* SYSV_LOGINDEFS */
	if (tz)
		(void)setenv("TZ", tz, 1);
	if (term)
		(void)setenv("TERM", term, 1);
#ifdef ENV_REMOTEHOST
	if (remotehost)
		(void)setenv("REMOTEHOST", remotehost, 1);
#endif
#ifdef ENV_REMOTEUSER
	if (remoteuser)
		(void)setenv("REMOTEUSER", remoteuser, 1);
#endif
#ifdef ENV_LOGNAME
	(void)setenv("LOGNAME", pwd->pw_name, 1);
#endif

	if (pwd->pw_uid == 0)
		if (hostname)
			syslog(LOG_NOTICE, "ROOT LOGIN ON %.100s FROM %s",
			    tty, hostname);
		else
			syslog(LOG_NOTICE, "ROOT LOGIN ON %s", tty);

#ifndef NO_MOTD
	/*
	 * Optionally show the message of the day. System V login leaves
	 * motd and mail stuff up to the shell startup file.
	 */
	if (!quietlog) {
		struct stat st;

		motd();
		(void)sprintf(tbuf, "%s/%s", _PATH_MAILDIR, pwd->pw_name);
		if (stat(tbuf, &st) == 0 && st.st_size != 0)
			printf("You have %smail.\n",
			    (st.st_mtime > st.st_atime) ? "new " : "");
	}
#endif /* NO_MOTD */

	tbuf[0] = '-';
	strcpy(tbuf + 1, (p = strrchr(pwd->pw_shell, '/')) ?
	    p + 1 : pwd->pw_shell);
#ifdef RLIMIT_CORE
	/* Re-enable core dumps. */
	setrlimit(RLIMIT_CORE, &old_core_limit);
#endif
	signal(SIGQUIT, SIG_DFL);
	signal(SIGINT, SIG_DFL);
	execlp(pwd->pw_shell, tbuf, 0);
	fprintf(stderr, "skeysh: no shell: ");
	perror(pwd->pw_shell);
	sleepexit(0);
}

getloginname()
{
	register int ch;
	register char *p;
	static char nbuf[UT_NAMESIZE + 1];

	for (;;) {
		printf("Username: ");
		for (p = nbuf; (ch = getchar()) != '\n'; ) {
			if (ch == EOF) {
				badlogin(username);
				exit(0);
			}
			if (p < nbuf + UT_NAMESIZE)
				*p++ = ch;
		}
		if (p > nbuf)
			if (nbuf[0] == '-')
				fprintf(stderr,
				    "login names may not start with '-'.\n");
			else {
				*p = '\0';
				username = nbuf;
				break;
			}
	}
}

void timedout()
{
	fprintf(stderr, "Login timed out after %d seconds\n", timeout);
	exit(0);
}

void hungup()
{
	close(0);       /* force EOF */
}

rootterm(ttyn)
	char *ttyn;
{
#ifdef NO_SECURE_TTY
	return (1);
#else
#ifdef NO_TTYENT
#ifdef SYSV_LOGINDEFS
	return (default_console == 0 || strcmp(default_console, ttyname(0)) == 0);
#else
	return (strcmp(_PATH_CONSOLE, ttyname(0)) == 0);
#endif
#else /* NO_TTYENT */
	struct ttyent *t;

	return((t = getttynam(ttyn)) && t->ty_status&TTY_SECURE);
#endif /* NO_TTYENT */
#endif /* NO_SECURE_TTY */
}

#ifndef NO_MOTD /* message of the day stuff */

jmp_buf motdinterrupt;

motd()
{
	register int fd, nchars;
	void (*oldint)(), sigint();
	char tbuf[8192];

	if ((fd = open(_PATH_MOTDFILE, O_RDONLY, 0)) < 0)
		return;
	oldint = (void (*)()) signal(SIGINT, sigint);
	if (setjmp(motdinterrupt) == 0)
		while ((nchars = read(fd, tbuf, sizeof(tbuf))) > 0)
			(void)write(fileno(stdout), tbuf, nchars);
	(void)signal(SIGINT, oldint);
	(void)close(fd);
}

void sigint()
{
	longjmp(motdinterrupt, 1);
}

#endif /* !NO_MOTD */

dolastlog(quiet, fd)
	int quiet;
	int fd;
{
	struct lastlog ll;

	if (fd >= 0) {
#ifndef IRIX_LASTLOGIN
		(void)lseek(fd, (off_t)pwd->pw_uid * sizeof(ll), L_SET);
#endif
#ifdef SYSV_SHADOW
		if (read(fd, (char *)&ll, sizeof(ll)) == sizeof(ll) &&
		    ll.ll_time != 0) {
			if (pwd->pw_uid && spwd->sp_inact > 0
			    && ll.ll_time / DAY + spwd->sp_inact < DAY_NOW) {
				printf("Your account has been inactive too long.\n");
				sleepexit(1);
			}
			if (!quiet) {
				printf("Last login: %.*s ",
				    24-5, (char *)ctime(&ll.ll_time));
				if (*ll.ll_host != '\0') {
#ifdef IRIX_LASTLOGIN
				    if (strncmp(ll.ll_line, pwd->pw_name,
				      sizeof(ll.ll_line)))
					printf("from %.*s@%.*s\n",
					    sizeof(ll.ll_line), ll.ll_line,
					    sizeof(ll.ll_host), ll.ll_host);
				    else
#endif /* IRIX_LASTLOGIN */
					printf("from %.*s\n",
					    sizeof(ll.ll_host), ll.ll_host);
				} else
					printf("on %.*s\n",
					    sizeof(ll.ll_line), ll.ll_line);
			}
		}
#ifdef IRIX_LASTLOGIN
		(void)lseek(fd, (off_t)0, L_SET);
#else /* IRIX_LASTLOGIN */
		(void)lseek(fd, (off_t)pwd->pw_uid * sizeof(ll), L_SET);
#endif /* IRIX_LASTLOGIN */
#else /* SYSV_SHADOW */
		if (!quiet) {
			if (read(fd, (char *)&ll, sizeof(ll)) == sizeof(ll) &&
			    ll.ll_time != 0) {
				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);
			}
			(void)lseek(fd, (off_t)pwd->pw_uid * sizeof(ll), L_SET);
		}
#endif /* SYSV_SHADOW */
		memset((char *)&ll, 0, sizeof(ll));
		(void)time(&ll.ll_time);
#ifdef IRIX_LASTLOGIN
		if (hostname)
			strncpy(ll.ll_line, "UNKNOWN", sizeof(ll.ll_line));
		else
#endif /* IRIX_LASTLOGIN */
		strncpy(ll.ll_line, tty, sizeof(ll.ll_line));
		if (hostname)
			strncpy(ll.ll_host, hostname, sizeof(ll.ll_host));
		(void)write(fd, (char *)&ll, sizeof(ll));
		(void)close(fd);
	}
}

badlogin(name)
	char *name;
{
	if (failures < (pwd ? 1 : 2))
		return;
	if (hostname)
		syslog(LOG_NOTICE, "%d LOGIN FAILURE%s FROM %.100s, %s",
		    failures, failures > 1 ? "S" : "", hostname, name);
	else
		syslog(LOG_NOTICE, "%d LOGIN FAILURE%s ON %s, %s",
		    failures, failures > 1 ? "S" : "", tty, name);
}

sleepexit(eval)
	int eval;
{
	sleep((u_int)5);
	exit(eval);
}
