/*
 * Copyright 1989 - 1994, Julianne Frances Haugh
 * 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. Neither the name of Julianne F. Haugh 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 JULIE HAUGH 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 JULIE HAUGH 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.
 */

#include <config.h>

#include "rcsid.h"
RCSID (PKG_VER "$Id: login.c,v 1.59 2005/06/20 09:36:27 kloczek Exp $")
#include "prototypes.h"
#include "defines.h"
#include <sys/stat.h>
#include <stdio.h>
#include <errno.h>
#include <pwd.h>
#include <grp.h>
#include <signal.h>
#include <lastlog.h>
#include "faillog.h"
#include "failure.h"
#include "pwauth.h"
#include "getdef.h"
#ifdef UT_ADDR
#include <netdb.h>
#endif
#ifdef USE_PAM
#include "pam_defs.h"
static const struct pam_conv conv = {
	misc_conv,
	NULL
};

static pam_handle_t *pamh = NULL;

#define PAM_FAIL_CHECK if (retcode != PAM_SUCCESS) { \
	fprintf(stderr,"\n%s\n",pam_strerror(pamh, retcode)); \
	syslog(LOG_ERR,"%s",pam_strerror(pamh, retcode)); \
	pam_end(pamh, retcode); exit(1); \
   }
#define PAM_END { retcode = pam_close_session(pamh,0); \
		pam_end(pamh,retcode); }

#endif				/* USE_PAM */

/*
 * Needed for MkLinux DR1/2/2.1 - J.
 */
#ifndef LASTLOG_FILE
#define LASTLOG_FILE "/var/log/lastlog"
#endif

const char *hostname = "";

static struct passwd pwent;

#if HAVE_UTMPX_H
extern struct utmpx utxent;
struct utmpx failent;
#else
struct utmp failent;
#endif
extern struct utmp utent;

struct lastlog lastlog;
static int pflg = 0;
static int fflg = 0;

#ifdef RLOGIN
static int rflg = 0;
#else
#define rflg 0
#endif
static int hflg = 0;
static int preauth_flag = 0;

/*
 * Global variables.
 */

static char *Prog;
static int amroot;
static int timeout;

/*
 * External identifiers.
 */

extern char **newenvp;
extern size_t newenvc;

extern void dolastlog (struct lastlog *, const struct passwd *,
		       const char *, const char *);

extern char **environ;

#ifndef	ALARM
#define	ALARM	60
#endif

#ifndef	RETRIES
#define	RETRIES	3
#endif

/* local function prototypes */
static void usage (void);
static void setup_tty (void);
static void check_flags (int, char *const *);

#ifndef USE_PAM
extern int login_access (const char *, const char *);

static struct faillog faillog;

static void bad_time_notify (void);
static void check_nologin (void);
#endif

static void init_env (void);
static RETSIGTYPE alarm_handler (int);

/*
 * usage - print login command usage and exit
 *
 * login [ name ]
 * login -r hostname	(for rlogind)
 * login -h hostname	(for telnetd, etc.)
 * login -f name	(for pre-authenticated login: datakit, xterm, etc.)
 */

static void usage (void)
{
	fprintf (stderr, _("Usage: %s [-p] [name]\n"), Prog);
	if (!amroot)
		exit (1);
	fprintf (stderr, _("       %s [-p] [-h host] [-f name]\n"), Prog);
#ifdef RLOGIN
	fprintf (stderr, _("       %s [-p] -r host\n"), Prog);
#endif
	exit (1);
}

static void setup_tty (void)
{
	TERMIO termio;

	GTTY (0, &termio);	/* get terminal characteristics */

	/*
	 * Add your favorite terminal modes here ...
	 */

	termio.c_lflag |= ISIG | ICANON | ECHO | ECHOE;
	termio.c_iflag |= ICRNL;

	/* leave these values unchanged if not specified in login.defs */
	termio.c_cc[VERASE] = getdef_num ("ERASECHAR", termio.c_cc[VERASE]);
	termio.c_cc[VKILL] = getdef_num ("KILLCHAR", termio.c_cc[VKILL]);

	/*
	 * ttymon invocation prefers this, but these settings won't come into
	 * effect after the first username login 
	 */

	STTY (0, &termio);
}


#ifndef USE_PAM
/*
 * Tell the user that this is not the right time to login at this tty
 */
static void bad_time_notify (void)
{
	printf (_("Invalid login time\n"));
	fflush (stdout);
}

static void check_nologin (void)
{
	char *fname;

	/*
	 * Check to see if system is turned off for non-root users.
	 * This would be useful to prevent users from logging in
	 * during system maintenance. We make sure the message comes
	 * out for root so she knows to remove the file if she's
	 * forgotten about it ...
	 */

	fname = getdef_str ("NOLOGINS_FILE");
	if (fname != NULL && access (fname, F_OK) == 0) {
		FILE *nlfp;
		int c;

		/*
		 * Cat the file if it can be opened, otherwise just
		 * print a default message
		 */

		if ((nlfp = fopen (fname, "r"))) {
			while ((c = getc (nlfp)) != EOF) {
				if (c == '\n')
					putchar ('\r');

				putchar (c);
			}
			fflush (stdout);
			fclose (nlfp);
		} else
			printf (_("\nSystem closed for routine maintenance\n"));
		/*
		 * Non-root users must exit. Root gets the message, but
		 * gets to login.
		 */

		if (pwent.pw_uid != 0) {
			closelog ();
			exit (0);
		}
		printf (_("\n[Disconnect bypassed -- root login allowed.]\n"));
	}
}
#endif				/* !USE_PAM */

static void check_flags (int argc, char *const *argv)
{
	int arg;

	/*
	 * Check the flags for proper form. Every argument starting with
	 * "-" must be exactly two characters long. This closes all the
	 * clever rlogin, telnet, and getty holes.
	 */
	for (arg = 1; arg < argc; arg++) {
		if (argv[arg][0] == '-' && strlen (argv[arg]) > 2)
			usage ();
	}
}


static void init_env (void)
{
	char *cp, *tmp;

	if ((tmp = getenv ("LANG"))) {
		addenv ("LANG", tmp);
	}

	/*
	 * Add the timezone environmental variable so that time functions
	 * work correctly.
	 */

	if ((tmp = getenv ("TZ"))) {
		addenv ("TZ", tmp);
	} else if ((cp = getdef_str ("ENV_TZ")))
		addenv (*cp == '/' ? tz (cp) : cp, NULL);

	/* 
	 * Add the clock frequency so that profiling commands work
	 * correctly.
	 */

	if ((tmp = getenv ("HZ"))) {
		addenv ("HZ", tmp);
	} else if ((cp = getdef_str ("ENV_HZ")))
		addenv (cp, NULL);
}


static RETSIGTYPE alarm_handler (int sig)
{
	fprintf (stderr, _("\nLogin timed out after %d seconds.\n"), timeout);
	exit (0);
}


/*
 * login - create a new login session for a user
 *
 *	login is typically called by getty as the second step of a
 *	new user session. getty is responsible for setting the line
 *	characteristics to a reasonable set of values and getting
 *	the name of the user to be logged in. login may also be
 *	called to create a new user session on a pty for a variety
 *	of reasons, such as X servers or network logins.
 *
 *	the flags which login supports are
 *	
 *	-p - preserve the environment
 *	-r - perform autologin protocol for rlogin
 *	-f - do not perform authentication, user is preauthenticated
 *	-h - the name of the remote host
 */

int main (int argc, char **argv)
{
	char username[32];
	char tty[BUFSIZ];

#ifdef RLOGIN
	char term[128] = "";
#endif
#if defined(HAVE_STRFTIME) && !defined(USE_PAM)
	char ptime[80];
#endif
	int reason = PW_LOGIN;
	int delay;
	int retries;
	int failed;
	int flag;
	int subroot = 0;
	int is_console;
	const char *cp;
	char *tmp;
	char fromhost[512];
	struct passwd *pwd;
	char **envp = environ;
	static char temp_pw[2];
	static char temp_shell[] = "/bin/sh";

#ifdef USE_PAM
	int retcode;
	pid_t child;
	char *pam_user;
#endif				/* USE_PAM */
#ifndef USE_PAM
	struct spwd *spwd = NULL;
#endif
	/*
	 * Some quick initialization.
	 */

	sanitize_env ();

	setlocale (LC_ALL, "");
	bindtextdomain (PACKAGE, LOCALEDIR);
	textdomain (PACKAGE);

	initenv ();

	username[0] = '\0';
	amroot = (getuid () == 0);
	Prog = Basename (argv[0]);

	check_flags (argc, argv);

	while ((flag = getopt (argc, argv, "d:f::h:pr:")) != EOF) {
		switch (flag) {
		case 'p':
			pflg++;
			break;
		case 'f':
			/*
			 * username must be a separate token
			 * (-f root, *not* -froot).  --marekm
			 *
			 * if -f has an arg, use that, else use the
			 * normal user name passed after all options
			 * --benc
			 */
			if (optarg != NULL && optarg != argv[optind - 1])
				usage ();
			fflg++;
			if (optarg)
				STRFCPY (username, optarg);
			break;
#ifdef	RLOGIN
		case 'r':
			rflg++;
			hostname = optarg;
			reason = PW_RLOGIN;
			break;
#endif
		case 'h':
			hflg++;
			hostname = optarg;
			reason = PW_TELNET;
			break;
		case 'd':
			/* "-d device" ignored for compatibility */
			break;
		default:
			usage ();
		}
	}

#ifdef RLOGIN
	/*
	 * Neither -h nor -f should be combined with -r.
	 */

	if (rflg && (hflg || fflg))
		usage ();
#endif

	/*
	 * Allow authentication bypass only if real UID is zero.
	 */

	if ((rflg || fflg || hflg) && !amroot) {
		fprintf (stderr, _("%s: Permission denied.\n"), Prog);
		exit (1);
	}

	if (!isatty (0) || !isatty (1) || !isatty (2))
		exit (1);	/* must be a terminal */

#if 0
	/*
	 * Get the utmp file entry and get the tty name from it. The
	 * current process ID must match the process ID in the utmp
	 * file if there are no additional flags on the command line.
	 */
	checkutmp (!rflg && !fflg && !hflg);
#else
	/*
	 * Be picky if run by normal users (possible if installed setuid
	 * root), but not if run by root. This way it still allows logins
	 * even if your getty is broken, or if something corrupts utmp,
	 * but users must "exec login" which will use the existing utmp
	 * entry (will not overwrite remote hostname).  --marekm
	 */
	checkutmp (!amroot);
#endif
	STRFCPY (tty, utent.ut_line);
	is_console = console (tty);

	if (rflg || hflg) {
#ifdef UT_ADDR
		struct hostent *he;

		/*
		 * Fill in the ut_addr field (remote login IP address). XXX
		 * - login from util-linux does it, but this is not the
		 * right place to do it. The program that starts login
		 * (telnetd, rlogind) knows the IP address, so it should
		 * create the utmp entry and fill in ut_addr. 
		 * gethostbyname() is not 100% reliable (the remote host may
		 * be unknown, etc.).  --marekm
		 */
		if ((he = gethostbyname (hostname))) {
			utent.ut_addr = *((int32_t *) (he->h_addr_list[0]));
#endif
#ifdef UT_HOST
			strncpy (utent.ut_host, hostname,
				 sizeof (utent.ut_host));
#endif
#if HAVE_UTMPX_H
			strncpy (utxent.ut_host, hostname,
				 sizeof (utxent.ut_host));
#endif
			/*
			 * Add remote hostname to the environment. I think
			 * (not sure) I saw it once on Irix.  --marekm
			 */
			addenv ("REMOTEHOST", hostname);
		}
#ifdef __linux__
		/*
		 * workaround for init/getty leaving junk in ut_host at least in
		 * some version of RedHat.  --marekm
		 */
		else if (amroot)
			memzero (utent.ut_host, sizeof utent.ut_host);
#endif
		if (fflg)
			preauth_flag++;
		if (hflg)
			reason = PW_RLOGIN;
#ifdef RLOGIN
		if (rflg
		    && do_rlogin (hostname, username, sizeof username,
				  term, sizeof term))
			preauth_flag++;
#endif

		OPENLOG ("login");

		setup_tty ();

		umask (getdef_num ("UMASK", 077));

		{
			/* 
			 * Use the ULIMIT in the login.defs file, and if
			 * there isn't one, use the default value. The
			 * user may have one for themselves, but otherwise,
			 * just take what you get.
			 */

			long limit = getdef_long ("ULIMIT", -1L);

			if (limit != -1)
				set_filesize_limit (limit);
		}

		/*
		 * The entire environment will be preserved if the -p flag
		 * is used.
		 */

		if (pflg)
			while (*envp)	/* add inherited environment, */
				addenv (*envp++, NULL);	/* some variables change later */

#ifdef RLOGIN
		if (term[0] != '\0')
			addenv ("TERM", term);
		else
#endif
			/* preserve TERM from getty */
		if (!pflg && (tmp = getenv ("TERM")))
			addenv ("TERM", tmp);

		init_env ();

		if (optind < argc) {	/* get the user name */
			if (rflg || (fflg && username[0]))
				usage ();

			STRFCPY (username, argv[optind]);
			strzero (argv[optind]);
			++optind;
		}
		if (optind < argc)	/* now set command line variables */
			set_env (argc - optind, &argv[optind]);

		if (rflg || hflg)
			cp = hostname;
		else
#ifdef	UT_HOST
		if (utent.ut_host[0])
			cp = utent.ut_host;
		else
#endif
#if HAVE_UTMPX_H
		if (utxent.ut_host[0])
			cp = utxent.ut_host;
		else
#endif
			cp = "";

		if (*cp)
			snprintf (fromhost, sizeof fromhost,
				  _(" on `%.100s' from `%.200s'"), tty, cp);
		else
			snprintf (fromhost, sizeof fromhost,
				  _(" on `%.100s'"), tty);

	      top:
		/* only allow ALARM sec. for login */
		signal (SIGALRM, alarm_handler);
		timeout = getdef_num ("LOGIN_TIMEOUT", ALARM);
		if (timeout > 0)
			alarm (timeout);

		environ = newenvp;	/* make new environment active */
		delay = getdef_num ("FAIL_DELAY", 1);
		retries = getdef_num ("LOGIN_RETRIES", RETRIES);

#ifdef USE_PAM
		retcode = pam_start ("login", username, &conv, &pamh);
		if (retcode != PAM_SUCCESS) {
			fprintf (stderr,
				 "login: PAM Failure, aborting: %s\n",
				 pam_strerror (pamh, retcode));
			syslog (LOG_ERR, "Couldn't initialize PAM: %s",
				pam_strerror (pamh, retcode));
			exit (99);
		}
		/*
		 * hostname & tty are either set to NULL or their correct values,
		 * depending on how much we know. We also set PAM's fail delay to
		 * ours.
		 */
		retcode = pam_set_item (pamh, PAM_RHOST, hostname);
		PAM_FAIL_CHECK;
		retcode = pam_set_item (pamh, PAM_TTY, tty);
		PAM_FAIL_CHECK;
#ifdef HAVE_PAM_FAIL_DELAY
		retcode = pam_fail_delay (pamh, 1000000 * delay);
		PAM_FAIL_CHECK;
#endif
		/* if fflg == 1, then the user has already been authenticated */
		if (!fflg || (getuid () != 0)) {
			int failcount = 0;
			char hostn[256];
			char login_prompt[256];	/* That's one hell of a prompt :) */

			/* Make the login prompt look like we want it */
			if (!gethostname (hostn, sizeof (hostn)))
				snprintf (login_prompt,
					  sizeof (login_prompt),
					  "%s login: ", hostn);
			else
				snprintf (login_prompt,
					  sizeof (login_prompt), "login: ");

			retcode =
			    pam_set_item (pamh, PAM_USER_PROMPT, login_prompt);
			PAM_FAIL_CHECK;

			/* if we didn't get a user on the command line,
			   set it to NULL */
			pam_get_item (pamh, PAM_USER,
				      (const void **) &pam_user);
			if (pam_user[0] == '\0')
				pam_set_item (pamh, PAM_USER, NULL);

			/*
			 * There may be better ways to deal with some of
			 * these conditions, but at least this way I don't
			 * think we'll be giving away information. Perhaps
			 * someday we can trust that all PAM modules will
			 * pay attention to failure count and get rid of
			 * MAX_LOGIN_TRIES?
			 */

			retcode = pam_authenticate (pamh, 0);
			while ((failcount++ < retries) &&
			       ((retcode == PAM_AUTH_ERR) ||
				(retcode == PAM_USER_UNKNOWN) ||
				(retcode == PAM_CRED_INSUFFICIENT) ||
				(retcode == PAM_AUTHINFO_UNAVAIL))) {
				pam_get_item (pamh, PAM_USER,
					      (const void **) &pam_user);
				syslog (LOG_NOTICE,
					"FAILED LOGIN %d FROM %s FOR %s, %s",
					failcount, hostname, pam_user,
					pam_strerror (pamh, retcode));
#ifdef HAVE_PAM_FAIL_DELAY
				pam_fail_delay (pamh, 1000000 * delay);
#endif
				fprintf (stderr, "Login incorrect\n\n");
				pam_set_item (pamh, PAM_USER, NULL);
				retcode = pam_authenticate (pamh, 0);
			}

			if (retcode != PAM_SUCCESS) {
				pam_get_item (pamh, PAM_USER,
					      (const void **) &pam_user);

				if (retcode == PAM_MAXTRIES)
					syslog (LOG_NOTICE,
						"TOO MANY LOGIN TRIES (%d) FROM %s FOR %s, %s",
						failcount, hostname,
						pam_user,
						pam_strerror (pamh, retcode));
				else
					syslog (LOG_NOTICE,
						"FAILED LOGIN SESSION FROM %s FOR %s, %s",
						hostname, pam_user,
						pam_strerror (pamh, retcode));

				fprintf (stderr, "\nLogin incorrect\n");
				pam_end (pamh, retcode);
				exit (0);
			}

			retcode = pam_acct_mgmt (pamh, 0);

			if (retcode == PAM_NEW_AUTHTOK_REQD) {
				retcode =
				    pam_chauthtok (pamh,
						   PAM_CHANGE_EXPIRED_AUTHTOK);
			}

			PAM_FAIL_CHECK;
		}

		/* Grab the user information out of the password file for future usage
		   First get the username that we are actually using, though.
		 */
		retcode =
		    pam_get_item (pamh, PAM_USER, (const void **) &pam_user);
		setpwent ();
		pwd = getpwnam (pam_user);

		if (fflg) {
			retcode = pam_acct_mgmt (pamh, 0);
			PAM_FAIL_CHECK;
		}

		if (!pwd || setup_groups (pwd))
			exit (1);
		else
			pwent = *pwd;

		retcode = pam_setcred (pamh, PAM_ESTABLISH_CRED);
		PAM_FAIL_CHECK;

		retcode = pam_open_session (pamh,
					    hushed (&pwent) ? PAM_SILENT : 0);
		PAM_FAIL_CHECK;

#else				/* ! USE_PAM */
		while (1) {	/* repeatedly get login/password pairs */
			failed = 0;	/* haven't failed authentication yet */
			if (!username[0]) {	/* need to get a login id */
				if (subroot) {
					closelog ();
					exit (1);
				}
				preauth_flag = 0;
				login_prompt (_("\n%s login: "), username,
					      sizeof username);
				continue;
			}
#endif				/* ! USE_PAM */

#ifdef USE_PAM
		if (!(pwd = getpwnam (pam_user))) {
			pwent.pw_name = pam_user;
#else
		if (!(pwd = getpwnam (username))) {
			pwent.pw_name = username;
#endif
			strcpy (temp_pw, "!");
			pwent.pw_passwd = temp_pw;
			pwent.pw_shell = temp_shell;

			preauth_flag = 0;
			failed = 1;
		} else {
			pwent = *pwd;
		}
#ifndef USE_PAM
		spwd = NULL;
		if (pwd && strcmp (pwd->pw_passwd, SHADOW_PASSWD_STRING) == 0) {
			spwd = getspnam (username);
			if (spwd)
				pwent.pw_passwd = spwd->sp_pwdp;
			else
				SYSLOG ((LOG_WARN,
					 "no shadow password for `%s'%s",
					 username, fromhost));
		}

		/*
		 * If the encrypted password begins with a "!", the account
		 * is locked and the user cannot login, even if they have
		 * been "pre-authenticated."
		 */
		if (pwent.pw_passwd[0] == '!' || pwent.pw_passwd[0] == '*')
			failed = 1;

		/*
		 * The -r and -f flags provide a name which has already
		 * been authenticated by some server.
		 */
		if (preauth_flag)
			goto auth_ok;

		if (pw_auth
		    (pwent.pw_passwd, username, reason, (char *) 0) == 0)
			goto auth_ok;

		/*
		 * Don't log unknown usernames - I mistyped the password for
		 * username at least once. Should probably use LOG_AUTHPRIV
		 * for those who really want to log them.  --marekm
		 */
		SYSLOG ((LOG_WARN, "invalid password for `%s' %s",
			 (pwd
			  || getdef_bool ("LOG_UNKFAIL_ENAB")) ?
			 username : "UNKNOWN", fromhost));
		failed = 1;

	      auth_ok:
		/*
		 * This is the point where all authenticated users wind up.
		 * If you reach this far, your password has been
		 * authenticated and so on.
		 */

		if (!failed && pwent.pw_name && pwent.pw_uid == 0
		    && !is_console) {
			SYSLOG ((LOG_CRIT, "ILLEGAL ROOT LOGIN %s", fromhost));
			failed = 1;
		}
		if (!failed
		    && !login_access (username, *hostname ? hostname : tty)) {
			SYSLOG ((LOG_WARN, "LOGIN `%s' REFUSED %s",
				 username, fromhost));
			failed = 1;
		}
		if (pwd && getdef_bool ("FAILLOG_ENAB") &&
		    !failcheck (pwent.pw_uid, &faillog, failed)) {
			SYSLOG ((LOG_CRIT,
				 "exceeded failure limit for `%s' %s",
				 username, fromhost));
			failed = 1;
		}
		if (!failed)
			break;

		/* don't log non-existent users */
		if (pwd && getdef_bool ("FAILLOG_ENAB"))
			failure (pwent.pw_uid, tty, &faillog);
		if (getdef_str ("FTMP_FILE") != NULL) {
			const char *failent_user;

#if HAVE_UTMPX_H
			failent = utxent;
			if (sizeof (failent.ut_tv) == sizeof (struct timeval))
				gettimeofday ((struct timeval *)
					      &failent.ut_tv, NULL);
			else {
				struct timeval tv;

				gettimeofday (&tv, NULL);
				failent.ut_tv.tv_sec = tv.tv_sec;
				failent.ut_tv.tv_usec = tv.tv_usec;
			}
#else
			failent = utent;
			failent.ut_time = time (NULL);
#endif
			if (pwd) {
				failent_user = pwent.pw_name;
			} else {
				if (getdef_bool ("LOG_UNKFAIL_ENAB"))
					failent_user = username;
				else
					failent_user = "UNKNOWN";
			}
			strncpy (failent.ut_user, failent_user,
				 sizeof (failent.ut_user));
#ifdef USER_PROCESS
			failent.ut_type = USER_PROCESS;
#endif
			failtmp (&failent);
		}
		memzero (username, sizeof username);

		if (--retries <= 0)
			SYSLOG ((LOG_CRIT, "REPEATED login failures%s",
				 fromhost));
#if 1
		/*
		 * If this was a passwordless account and we get here, login
		 * was denied (securetty, faillog, etc.). There was no
		 * password prompt, so do it now (will always fail - the bad
		 * guys won't see that the passwordless account exists at
		 * all).  --marekm
		 */

		if (pwent.pw_passwd[0] == '\0')
			pw_auth ("!", username, reason, (char *) 0);
#endif
		/*
		 * Wait a while (a la SVR4 /usr/bin/login) before attempting
		 * to login the user again. If the earlier alarm occurs
		 * before the sleep() below completes, login will exit.
		 */

		if (delay > 0)
			sleep (delay);

		puts (_("Login incorrect"));

		/* allow only one attempt with -r or -f */
		if (rflg || fflg || retries <= 0) {
			closelog ();
			exit (1);
		}
	}			/* while (1) */
#endif				/* ! USE_PAM */
	alarm (0);		/* turn off alarm clock */
#ifndef USE_PAM			/* PAM does this */
	/*
	 * porttime checks moved here, after the user has been
	 * authenticated. now prints a message, as suggested
	 * by Ivan Nejgebauer <ian@unsux.ns.ac.yu>.  --marekm
	 */
	if (getdef_bool ("PORTTIME_CHECKS_ENAB") &&
	    !isttytime (pwent.pw_name, tty, time ((time_t *) 0))) {
		SYSLOG ((LOG_WARN, "invalid login time for `%s'%s",
			 username, fromhost));
		closelog ();
		bad_time_notify ();
		exit (1);
	}

	check_nologin ();
#endif

	if (getenv ("IFS"))	/* don't export user IFS ... */
		addenv ("IFS= \t\n", NULL);	/* ... instead, set a safe IFS */

#ifdef USE_PAM
	setutmp (pam_user, tty, hostname);	/* make entry in utmp & wtmp files */
#else
	setutmp (username, tty, hostname);	/* make entry in utmp & wtmp files */
#endif
	if (pwent.pw_shell[0] == '*') {	/* subsystem root */
		pwent.pw_shell++;	/* skip the '*' */
		subsystem (&pwent);	/* figure out what to execute */
		subroot++;	/* say i was here again */
		endpwent ();	/* close all of the file which were */
		endgrent ();	/* open in the original rooted file */
		endspent ();	/* system. they will be re-opened */
#ifdef	SHADOWGRP
		endsgent ();	/* in the new rooted file system */
#endif
		goto top;	/* go do all this all over again */
	}
#ifndef USE_PAM			/* pam_lastlog handles this */
	if (getdef_bool ("LASTLOG_ENAB"))	/* give last login and log this one */
		dolastlog (&lastlog, &pwent, utent.ut_line, hostname);
#endif

#ifndef USE_PAM			/* PAM handles this as well */
	/*
	 * Have to do this while we still have root privileges, otherwise we
	 * don't have access to /etc/shadow. expire() closes password files,
	 * and changes to the user in the child before executing the passwd
	 * program.  --marekm
	 */
	if (spwd) {		/* check for age of password */
		if (expire (&pwent, spwd)) {
			pwd = getpwnam (username);
			spwd = getspnam (username);
			if (pwd)
				pwent = *pwd;
		}
	}
	setup_limits (&pwent);	/* nice, ulimit etc. */
#endif				/* ! USE_PAM */
	chown_tty (tty, &pwent);

#ifdef USE_PAM
	/*
	 * We must fork before setuid() because we need to call
	 * pam_close_session() as root.
	 *
	 * Note: not true in other (non-Linux) PAM implementations, where
	 * the parent process of login (init, telnetd, ...) is responsible
	 * for calling pam_close_session(). This avoids an extra process for
	 * each login. Maybe we should do this on Linux too? We let the
	 * admin configure whether they need to keep login around to close
	 * sessions.
	 */
	if (getdef_bool ("CLOSE_SESSIONS")) {
		signal (SIGINT, SIG_IGN);
		child = fork ();
		if (child < 0) {
			/* error in fork() */
			fprintf (stderr,
				 "login: failure forking: %s",
				 strerror (errno));
			PAM_END;
			exit (0);
		} else if (child) {
			/*
			 * parent - wait for child to finish, then cleanup
			 * session
			 */
			wait (NULL);
			PAM_END;
			exit (0);
		}
		/* child */
	}
#endif
	/* We call set_groups() above because this clobbers pam_groups.so */
#ifndef USE_PAM
	if (setup_uid_gid (&pwent, is_console))
#else
	if (change_uid (&pwent))
#endif
		exit (1);

	setup_env (&pwent);	/* set env vars, cd to the home dir */

#ifdef USE_PAM
	{
		const char *const *env;

		env = (const char *const *) pam_getenvlist (pamh);
		while (env && *env) {
			addenv (*env, NULL);
			env++;
		}
	}
#endif

	setlocale (LC_ALL, "");
	bindtextdomain (PACKAGE, LOCALEDIR);
	textdomain (PACKAGE);

	if (!hushed (&pwent)) {
		addenv ("HUSHLOGIN=FALSE", NULL);
		/*
		 * pam_unix, pam_mail and pam_lastlog should take care of
		 * this
		 */
#ifndef USE_PAM
		motd ();	/* print the message of the day */
		if (getdef_bool ("FAILLOG_ENAB")
		    && faillog.fail_cnt != 0) {
			failprint (&faillog);
			/* Reset the lockout times if logged in */
			if (faillog.fail_max &&
			    faillog.fail_cnt >= faillog.fail_max) {
				puts (_
				      ("Warning: login re-enabled after temporary lockout.\n"));
				SYSLOG ((LOG_WARN,
					 "login `%s' re-enabled after temporary lockout (%d failures)",
					 username, (int) faillog.fail_cnt));
			}
		}
		if (getdef_bool ("LASTLOG_ENAB")
		    && lastlog.ll_time != 0) {
			time_t ll_time = lastlog.ll_time;

#ifdef HAVE_STRFTIME
			strftime (ptime, sizeof (ptime),
				  "%a %b %e %H:%M:%S %z %Y",
				  localtime (&ll_time));
			printf (_("Last login: %s on %s"),
				ptime, lastlog.ll_line);
#else
			printf (_("Last login: %.19s on %s"),
				ctime (&ll_time), lastlog.ll_line);
#endif
#ifdef HAVE_LL_HOST		/* __linux__ || SUN4 */
			if (lastlog.ll_host[0])
				printf (_(" from %.*s"),
					(int) sizeof lastlog.
					ll_host, lastlog.ll_host);
#endif
			printf (".\n");
		}
		agecheck (&pwent, spwd);

		mailcheck ();	/* report on the status of mail */
#endif				/* !USE_PAM */
	} else
		addenv ("HUSHLOGIN=TRUE", NULL);

	if (getdef_str ("TTYTYPE_FILE") != NULL && getenv ("TERM") == NULL)
		ttytype (tty);

	signal (SIGQUIT, SIG_DFL);	/* default quit signal */
	signal (SIGTERM, SIG_DFL);	/* default terminate signal */
	signal (SIGALRM, SIG_DFL);	/* default alarm signal */
	signal (SIGHUP, SIG_DFL);	/* added this.  --marekm */
	signal (SIGINT, SIG_DFL);	/* default interrupt signal */

	endpwent ();		/* stop access to password file */
	endgrent ();		/* stop access to group file */
	endspent ();		/* stop access to shadow passwd file */
#ifdef	SHADOWGRP
	endsgent ();		/* stop access to shadow group file */
#endif
	if (pwent.pw_uid == 0)
		SYSLOG ((LOG_NOTICE, "ROOT LOGIN %s", fromhost));
	else if (getdef_bool ("LOG_OK_LOGINS"))
#ifdef USE_PAM
		SYSLOG ((LOG_INFO, "`%s' logged in %s", pam_user, fromhost));
#else
		SYSLOG ((LOG_INFO, "`%s' logged in %s", username, fromhost));
#endif
	closelog ();
	if ((tmp = getdef_str ("FAKE_SHELL")) != NULL) {
		shell (tmp, pwent.pw_shell);	/* fake shell */
	}
	shell (pwent.pw_shell, (char *) 0);	/* exec the shell finally. */
	/* NOT REACHED */
	return 0;
}
