/* login - log into the system			Author: Patrick van Kleef */

/* Original version by Patrick van Kleef.  History of modifications:
 *
 * Peter S. Housel   Jan. 1988
 *  - Set up $USER, $HOME and $TERM.
 *  - Set signals to SIG_DFL.
 *
 * Terrence W. Holm   June 1988
 *  - Allow a username as an optional argument.
 *  - Time out if a password is not typed within 60 seconds.
 *  - Perform a dummy delay after a bad username is entered.
 *  - Don't allow a login if "/etc/nologin" exists.
 *  - Cause a failure on bad "pw_shell" fields.
 *  - Record the login in "/usr/adm/wtmp".
 *
 * Peter S. Housel   Dec. 1988
 *  - Record the login in "/etc/utmp" also.
 *
 * F. van Kempen     June 1989
 *  - various patches for Minix V1.4a.
 *
 * F. van Kempen     September 1989
 *  - added login-failure administration (new utmp.h needed!).
 *  - support arguments in pw_shell field
 *  - adapted source text to MINIX Style Sheet
 *
 * F. van Kempen     October 1989
 *  - adapted to new utmp database.
 * F. van Kempen,    December 1989
 *  - fixed 'slot' assumption in wtmp()
 *  - fixed all MSS-stuff
 *  - adapted to POSIX (MINIX 1.5)
 * F. van Kempen,    January 1990
 *  - made all 'bad login accounting' optional by "#ifdef BADLOG".
 * F. van Kempen,    Februari 1990
 *  - fixed 'first argument' bug and added some casts.
 *
 * Andy Tanenbaum April 1990
 * - if /bin/sh cannot be located, try /usr/bin/sh
 *
 * Michael A. Temari	October 1990
 *  - handle more than single digit tty devices
 *
 * Philip Homburg - Feb 28 1992
 *  - use ttyname to get the name of a tty.
 *
 * Kees J. Bot - Feb 13 1993
 *  - putting out garbage.
 *  - added lastlog.
 *
 * Kees J. Bot - Feb 13 1993
 *  - supplementary groups.
 *
 * Kees J. Bot - Jan 3 1996
 *  - ported back to standard Minix.
 */

#include <sys/types.h>
#include <sys/stat.h>
#include <ttyent.h>
#include <errno.h>
#include <fcntl.h>
#include <pwd.h>
#include <grp.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <utmp.h>
#include <time.h>
#include <minix/minlib.h>

char PATH_UTMP[] = "/etc/utmp";			/* current logins */
char PATH_WTMP[] = "/usr/adm/wtmp";		/* login/logout history */
char PATH_LASTLOG[] = "/usr/adm/lastlog";	/* last login history */
char PATH_MOTD[] = "/etc/motd";			/* message of the day */

#define TTY_GID		4			/* group ID of ttys */

#define EXTRA_ENV	6

/* Crude indication of a tty being physically secure: */
#define securetty(dev)		((unsigned) ((dev) - 0x0400) < (unsigned) 8)

int time_out;
char *hostname;
char user[32];
char logname[35];
char home[128];
char shell[128];
char term[128];
char **env;
extern char **environ;

_PROTOTYPE(int main, (int argc, char **argv));
_PROTOTYPE(void wtmp, (char *user, int uid));
_PROTOTYPE(void show_file, (char *nam));
_PROTOTYPE(void Time_out, (int dummy));
_PROTOTYPE(void usage, (void));
_PROTOTYPE(void add2env, (char **env, char *entry, int replace));

void wtmp(user, uid)
char *user;			/* user name */
int uid;			/* user id */
{
  /* Make entries in /usr/adm/wtmp and /etc/utmp. */
  struct utmp entry;
  register int fd= -1;
  int lineno;
  int err = 0;
  char *what;

  /* First, read the current UTMP entry. we need some of its
   * parameters! (like PID, ID etc...).
   */
  what= "ttyslot()";
  lineno= ttyslot();
  if (lineno == 0) err= errno;	/* ttyslot failed */

  if (err == 0 && (fd = open(what = PATH_UTMP, O_RDONLY)) < 0) {
  	if (errno == ENOENT) return;
  	err= errno;
  }
  if (err == 0 && lseek(fd, (off_t) lineno * sizeof(entry), SEEK_SET) < 0)
  	err= errno;
  if (err == 0 && read(fd, (char *) &entry, sizeof(entry)) != sizeof(entry))
  	err= errno;
  if (fd >= 0) close(fd);

  /* Enter new fields. */
  strncpy(entry.ut_user, user, sizeof(entry.ut_user));
  if (hostname) strncpy(entry.ut_host, hostname, sizeof(entry.ut_host));

  if (entry.ut_pid == 0) entry.ut_pid = getpid();

  entry.ut_type = USER_PROCESS;		/* we are past login... */
  time(&entry.ut_time);

  /* Write a WTMP record. */
  if (err == 0) {
  	if ((fd = open(what = PATH_WTMP, O_WRONLY|O_APPEND)) < 0) {
  		if (errno != ENOENT) err= errno;
	} else {
		if (write(fd, (char *) &entry, sizeof(entry)) < 0) err= errno;
		close(fd);
	}
  }

  /* Rewrite the UTMP entry. */
  if (err == 0 && (fd = open(what = PATH_UTMP, O_WRONLY)) < 0)
	err= errno;
  if (err == 0 && lseek(fd, (off_t) lineno * sizeof(entry), SEEK_SET) < 0)
	err= errno;
  if (err == 0 && write(fd, (char *) &entry, sizeof(entry)) < 0)
	err= errno;
  if (fd >= 0) close(fd);

  /* Write the LASTLOG entry. */
  if (err == 0 && (fd = open(what = PATH_LASTLOG, O_WRONLY)) < 0) {
	if (errno == ENOENT) return;
	err= errno;
  }
  if (err == 0 && lseek(fd, (off_t) uid * sizeof(entry), SEEK_SET) < 0)
	err= errno;
  if (err == 0 && write(fd, (char *) &entry, sizeof(entry)) < 0)
	err= errno;
  if (fd >= 0) close(fd);

  if (err != 0) {
  	fprintf(stderr, "login: %s: %s\n", what, strerror(err));
  	return;
  }
}


void show_file(nam)
char *nam;
{
/* Read a textfile and show it on the desired terminal. */
  register int fd, len;
  char buf[80];

  if ((fd = open(nam, O_RDONLY)) > 0) {
	len = 1;
	while (len > 0) {
		len = read(fd, buf, 80);
		write(1, buf, len);
	}
	close(fd);
  }
}


int main(argc, argv)
int argc;
char *argv[];
{
  char name[30];
  char *password, *cryptedpwd;
  char *tty_name;
  int n, ap, check_pw, bad, secure, i, envsiz;
  struct passwd *pwd;
  char *bp, *argx[8], **ep;	/* pw_shell arguments */
  char argx0[64];		/* argv[0] of the shell */
  char *sh = "/bin/sh";		/* sh/pw_shell field value */
  char *initialname;
  int c, f_flag, p_flag;
  char *h_arg;
  int authorized, preserv_env;
  struct ttyent *ttyp;
  struct stat ttystat;
  struct sigaction sa;

  /* Don't let QUIT dump core. */
  sigemptyset(&sa.sa_mask);
  sa.sa_flags = 0;
  sa.sa_handler = exit;
  sigaction(SIGQUIT, &sa, NULL);

  /* Parse options.  */
  f_flag= 0;
  p_flag= 0;
  h_arg= NULL;
  while ((c= getopt(argc, argv, "?fh:p")) != -1)
  {
	switch(c)
	{
	case 'f':
		if (f_flag)
			usage();
		f_flag= 1;
		break;
	case 'h':
		if (h_arg)
			usage();
		if (getuid() == 0)
			h_arg= optarg;
		break;
	case 'p':
		if (p_flag)
			usage();
		p_flag= 1;
		break;
	case '?':
		usage();
	default:
		fprintf(stderr, "login: getopt failed: '%c'\n", c);
		exit(1);
	}
  }
  if (optind < argc)
	initialname= argv[optind++];
  else
	initialname= NULL;
  if (optind != argc)
	usage();

  authorized= f_flag;
  hostname= h_arg;
  preserv_env= p_flag;

  /* Look up /dev/tty number. */
  tty_name= ttyname(0);
  if (tty_name == NULL)
  {
	write(1, "Unable to lookup tty name\n", 26);
	exit(1);
  }

  /* Get login name and passwd. */
  for (;;initialname= NULL) {
	if (initialname)
		strcpy(name, initialname);
	else {
		do {
			write(1, "login: ", 7);
			n = read(0, name, 30);
			if (n == 0) exit(1);
			if (n < 0)
			{
				if (errno != EINTR)
					fprintf(stderr,
						"login: read failed: %s\n",
							strerror(errno));
				exit(1);
			}
		} while (n < 2);
		name[n - 1] = 0;
	}

	/* Start timer running. */
	time_out = 0;
	sa.sa_handler = Time_out;
	sigaction(SIGALRM, &sa, NULL);
	alarm(60);


	/* Look up login/passwd. */
	pwd = getpwnam(name);

	check_pw = 1;			/* default is check password. */

	/* For now, only console is secure. */
	secure = fstat(0, &ttystat) == 0 && securetty(ttystat.st_rdev);

	if (pwd && authorized && initialname
			&& (pwd->pw_uid == getuid() || getuid() == 0)) {
		check_pw= 0;		/* Don't ask a password for
					 * pre-authorized users.
					 */
	} else
	if (pwd && secure && strcmp(crypt("", pwd->pw_passwd),
						pwd->pw_passwd) == 0) {
		check_pw= 0;		/* empty password */
	}

	if (check_pw) {
		password = getpass("Password:");

		if (time_out) exit(1);

		bad = 0;
		if (!pwd) bad = 1;
		if (!password) { password = ""; bad = 1; }
		if (!secure && pwd && strcmp(crypt("", pwd->pw_passwd),
					pwd->pw_passwd) == 0) bad = 1;

		cryptedpwd = bad ? "*" : pwd->pw_passwd;

		if (strcmp(crypt(password, cryptedpwd), cryptedpwd) != 0) {
			write(1, "Login incorrect\n", 16);
			continue;
		}
	}
	/* Check if the system is going down  */
	if (access("/etc/nologin", 0) == 0 && strcmp(name, "root") != 0) {
		write(1, "System going down\n\n", 19);
		continue;
	}

	/* Stop timer. */
	alarm(0);

	/* Write login record to /usr/adm/wtmp and /etc/utmp */
	wtmp(name, pwd->pw_uid);

	/* Create the argv[] array from the pw_shell field. */
	ap = 0;
	argx[ap++] = argx0;	/* "-sh" most likely */
	if (pwd->pw_shell[0]) {
		sh = pwd->pw_shell;
		bp = sh;
		while (*bp) {
			while (*bp && *bp != ' ' && *bp != '\t') bp++;
			if (*bp == ' ' || *bp == '\t') {
				*bp++ = '\0';	/* mark end of string */
				argx[ap++] = bp;
			}
		}
	} else
	argx[ap] = NULL;
	strcpy(argx0, "-");	/* most shells need it for their .profile */
	if ((bp= strrchr(sh, '/')) == NULL) bp = sh; else bp++;
	strncat(argx0, bp, sizeof(argx0) - 2);

	/* Set the environment */
	if (p_flag)
	{
		for (ep= environ; *ep; ep++)
			;
	}
	else
		ep= environ;

	envsiz= ep-environ;
	env= calloc(envsiz + EXTRA_ENV, sizeof(*env));
	if (env == NULL)
	{
		fprintf(stderr, "login: out of memory\n");
		exit(1);
	}
	for (i= 0; i<envsiz; i++)
		env[i]= environ[i];

	strcpy(user, "USER=");
	strcat(user, name);
	add2env(env, user, 1);
	strcpy(logname, "LOGNAME=");
	strcat(logname, name);
	add2env(env, logname, 1);
	strcpy(home, "HOME=");
	strcat(home, pwd->pw_dir);
	add2env(env, home, 1);
	strcpy(shell, "SHELL=");
	strcat(shell, sh);
	add2env(env, shell, 1);
	if ((ttyp = getttynam(tty_name + 5)) != NULL) {
		strcpy(term, "TERM=");
		strcat(term, ttyp->ty_type);
		add2env(env, term, 0);
	}

	/* Show the message-of-the-day. */
	show_file(PATH_MOTD);

	/* Assign the terminal to this user. */
	chown(tty_name, pwd->pw_uid, TTY_GID);
	chmod(tty_name, 0620);

	/* Change id. */
#if __minix_vmd
	initgroups(pwd->pw_name, pwd->pw_gid);
#endif
	setgid(pwd->pw_gid);
	setuid(pwd->pw_uid);

	/* cd $HOME */
	chdir(pwd->pw_dir);

	/* Reset signals to default values. */
	sa.sa_handler = SIG_DFL;
	for (n = 1; n <= _NSIG; ++n) sigaction(n, &sa, NULL);

	/* Execute the user's shell. */
	execve(sh, argx, env);

	if (pwd->pw_gid == 0) {
		/* Privileged user gets /bin/sh in times of crisis. */
		sh= "/bin/sh";
		argx[0]= "-sh";
		strcpy(shell, "SHELL=");
		strcat(shell, sh);
		execve(sh, argx, env);
	}
	write(1, "exec failure\n", 13);
	exit(1);
  }
  return(0);
}


void Time_out(dummy)
int dummy; /* to keep the compiler happy */
{
   write(2, "\r\nLogin timed out after 60 seconds\r\n", 36);
   time_out = 1;
}

void usage()
{
	fprintf(stderr, "Usage: login [-h hostname] [-f] [-p] [username]\n");
	exit(1);
}

void add2env(env, entry, replace)
char **env;
char *entry;
int replace;
{
/* Replace an environment variable with entry or add entry if the environment
 * variable doesn't exit yet. 
 */
	char *cp;
	int keylen;

	cp= strchr(entry, '=');
	keylen= cp-entry+1;

	for(; *env; env++)
	{
		if (strncmp(*env, entry, keylen) == 0) {
			if (!replace) return;		/* Don't replace */
			break;
		}
	}
	*env= entry;
}
