/*
 * SPDX-FileCopyrightText: 1989 - 1994, Julianne Frances Haugh
 * SPDX-FileCopyrightText: 1996 - 2001, Marek Michałkiewicz
 * SPDX-FileCopyrightText: 2001 - 2006, Tomasz Kłoczko
 * SPDX-FileCopyrightText: 2007 - 2012, Nicolas François
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include <config.h>

#ident "$Id$"

#include <errno.h>
#include <grp.h>
#ifndef USE_PAM
#include <lastlog.h>
#endif				/* !USE_PAM */
#include <pwd.h>
#include <signal.h>
#include <stdio.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <assert.h>
#include "defines.h"
#include "faillog.h"
#include "failure.h"
#include "getdef.h"
#include "prototypes.h"
#include "pwauth.h"
/*@-exitarg@*/
#include "exitcodes.h"
#include "shadowlog.h"

#ifdef USE_PAM
#include "pam_defs.h"

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))); \
	(void) pam_end(pamh, retcode); \
	exit(1); \
   }
#define PAM_END { retcode = pam_close_session(pamh,0); \
		(void) pam_end(pamh,retcode); }

#endif				/* USE_PAM */

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

/*
 * Global variables
 */
const char *Prog;

static const char *hostname = "";
static /*@null@*/ /*@only@*/char *username = NULL;
static int reason = PW_LOGIN;

#ifndef USE_PAM
static struct lastlog ll;
#endif				/* !USE_PAM */
static bool pflg = false;
static bool fflg = false;

#ifdef RLOGIN
static bool rflg = false;
#else				/* RLOGIN */
#define rflg false
#endif				/* !RLOGIN */
static bool hflg = false;
static bool preauth_flag = false;

static bool amroot;
static char tmsg[256];

/*
 * External identifiers.
 */

extern char **newenvp;
extern size_t newenvc;
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 process_flags (int argc, char *const *argv);
static /*@observer@*/const char *get_failent_user (/*@returned@*/const char *user);
static void update_utmp (const char *user,
                         const char *tty,
                         const char *host,
#ifdef USE_UTMPX
                         /*@null@*/const struct utmpx *utent
#else
                         /*@null@*/const struct utmp *utent
#endif
			);

#ifndef USE_PAM
static struct faillog faillog;

static void bad_time_notify (void);
static void check_nologin (bool login_to_root);
#else
static void get_pam_user (char **ptr_pam_user);
#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				/* RLOGIN */
	exit (1);
}

static void setup_tty (void)
{
	TERMIO termio;

	if (GTTY (0, &termio) == 0) {	/* get terminal characteristics */
		int erasechar;
		int killchar;

		/*
		 * Add your favorite terminal modes here ...
		 */
		termio.c_lflag |= ISIG | ICANON | ECHO | ECHOE;
		termio.c_iflag |= ICRNL;

#if defined(ECHOKE) && defined(ECHOCTL)
		termio.c_lflag |= ECHOKE | ECHOCTL;
#endif
#if defined(ECHOPRT) && defined(NOFLSH) && defined(TOSTOP)
		termio.c_lflag &= ~(ECHOPRT | NOFLSH | TOSTOP);
#endif
#ifdef ONLCR
		termio.c_oflag |= ONLCR;
#endif

		/* leave these values unchanged if not specified in login.defs */
		erasechar = getdef_num ("ERASECHAR", (int) termio.c_cc[VERASE]);
		killchar = getdef_num ("KILLCHAR", (int) termio.c_cc[VKILL]);
		termio.c_cc[VERASE] = (cc_t) erasechar;
		termio.c_cc[VKILL] = (cc_t) killchar;
		/* Make sure the values were valid.
		 * getdef_num cannot validate this.
		 */
		if (erasechar != (int) termio.c_cc[VERASE]) {
			fprintf (stderr,
			         _("configuration error - cannot parse %s value: '%d'"),
			         "ERASECHAR", erasechar);
			exit (1);
		}
		if (killchar != (int) termio.c_cc[VKILL]) {
			fprintf (stderr,
			         _("configuration error - cannot parse %s value: '%d'"),
			         "KILLCHAR", killchar);
			exit (1);
		}

		/*
		 * ttymon invocation prefers this, but these settings
		 * won't come into effect after the first username login
		 */
		(void) 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)
{
	(void) puts (_("Invalid login time"));
	(void) fflush (stdout);
}

static void check_nologin (bool login_to_root)
{
	const 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 ((NULL != fname) && (access (fname, F_OK) == 0)) {
		FILE *nlfp;

		/*
		 * Cat the file if it can be opened, otherwise just
		 * print a default message
		 */
		nlfp = fopen (fname, "r");
		if (NULL != nlfp) {
			int c;
			while ((c = getc (nlfp)) != EOF) {
				if (c == '\n') {
					(void) putchar ('\r');
				}

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

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

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

	/*
	 * 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 ();
		}
		if (strcmp(argv[arg], "--") == 0) {
			break; /* stop checking on a "--" */
		}
	}

	/*
	 * Process options.
	 */
	while ((flag = getopt (argc, argv, "d:fh:pr:")) != EOF) {
		switch (flag) {
		case 'd':
			/* "-d device" ignored for compatibility */
			break;
		case 'f':
			fflg = true;
			break;
		case 'h':
			hflg = true;
			hostname = optarg;
			reason = PW_TELNET;
			break;
#ifdef	RLOGIN
		case 'r':
			rflg = true;
			hostname = optarg;
			reason = PW_RLOGIN;
			break;
#endif				/* RLOGIN */
		case 'p':
			pflg = true;
			break;
		default:
			usage ();
		}
	}

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

	if (rflg && (hflg || fflg)) {
		usage ();
	}
#endif				/* RLOGIN */

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

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

	/*
	 *  Get the user name.
	 */
	if (optind < argc) {
		assert (NULL == username);
		username = xstrdup (argv[optind]);
		strzero (argv[optind]);
		++optind;
	}

#ifdef	RLOGIN
	if (rflg && (NULL != username)) {
		usage ();
	}
#endif				/* RLOGIN */
	if (fflg && (NULL == username)) {
		usage ();
	}

}


static void init_env (void)
{
#ifndef USE_PAM
	const char *cp;
#endif
	char *tmp;

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

	/*
	 * Add the timezone environmental variable so that time functions
	 * work correctly.
	 */
	tmp = getenv ("TZ");
	if (NULL != tmp) {
		addenv ("TZ", tmp);
	}
#ifndef USE_PAM
	else {
		cp = getdef_str ("ENV_TZ");
		if (NULL != cp) {
			addenv (('/' == *cp) ? tz (cp) : cp, NULL);
		}
	}
#endif				/* !USE_PAM */
	/*
	 * Add the clock frequency so that profiling commands work
	 * correctly.
	 */
	tmp = getenv ("HZ");
	if (NULL != tmp) {
		addenv ("HZ", tmp);
	}
#ifndef USE_PAM
	else {
		cp = getdef_str ("ENV_HZ");
		if (NULL != cp) {
			addenv (cp, NULL);
		}
	}
#endif				/* !USE_PAM */
}


static RETSIGTYPE alarm_handler (unused int sig)
{
	write (STDERR_FILENO, tmsg, strlen (tmsg));
	_exit (0);
}

#ifdef USE_PAM
/*
 * get_pam_user - Get the username according to PAM
 *
 * ptr_pam_user shall point to a malloc'ed string (or NULL).
 */
static void get_pam_user (char **ptr_pam_user)
{
	int retcode;
	void *ptr_user;

	assert (NULL != ptr_pam_user);

	retcode = pam_get_item (pamh, PAM_USER, (const void **)&ptr_user);
	PAM_FAIL_CHECK;

	if (NULL != *ptr_pam_user) {
		free (*ptr_pam_user);
	}
	if (NULL != ptr_user) {
		*ptr_pam_user = xstrdup ((const char *)ptr_user);
	} else {
		*ptr_pam_user = NULL;
	}
}
#endif

/*
 * get_failent_user - Return a string that can be used to log failure
 *                    from an user.
 *
 * This will be either the user argument, or "UNKNOWN".
 *
 * It is quite common to mistyped the password for username, and passwords
 * should not be logged.
 */
static /*@observer@*/const char *get_failent_user (/*@returned@*/const char *user)
{
	const char *failent_user = "UNKNOWN";
	bool log_unkfail_enab = getdef_bool("LOG_UNKFAIL_ENAB");

	if ((NULL != user) && ('\0' != user[0])) {
		if (   log_unkfail_enab
		    || (getpwnam (user) != NULL)) {
			failent_user = user;
		}
	}

	return failent_user;
}

/*
 * update_utmp - Update or create an utmp entry in utmp, wtmp, utmpw, and
 *               wtmpx
 *
 *	utent should be the utmp entry returned by get_current_utmp (or
 *	NULL).
 */
static void update_utmp (const char *user,
                         const char *tty,
                         const char *host,
#ifdef USE_UTMPX
                         /*@null@*/const struct utmpx *utent
#else
                         /*@null@*/const struct utmp *utent
#endif
			 )
{
#ifdef USE_UTMPX
	struct utmpx *utx = prepare_utmpx (user, tty, host, utent);
#else
	struct utmp  *ut  = prepare_utmp  (user, tty, host, utent);
#endif				/* USE_UTMPX */

#ifndef USE_UTMPX
	(void) setutmp  (ut);	/* make entry in the utmp & wtmp files */
	free (ut);
#else
	(void) setutmpx (utx);	/* make entry in the utmpx & wtmpx files */
	free (utx);
#endif				/* USE_UTMPX */
}

/*
 * 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)
{
	const char *tmptty;
	char tty[BUFSIZ];

#ifdef RLOGIN
	char term[128] = "";
#endif				/* RLOGIN */
#if !defined(USE_PAM)
	char ptime[80];
#endif
	unsigned int delay;
	unsigned int retries;
	bool subroot = false;
#ifndef USE_PAM
	bool is_console;
#endif
	int err;
	unsigned int timeout;
	const char *cp;
	const char *tmp;
	char fromhost[512];
	struct passwd *pwd = NULL;
	char **envp = environ;
	const char *failent_user;
#ifdef USE_UTMPX
	/*@null@*/struct utmpx *utent;
#else
	/*@null@*/struct utmp *utent;
#endif

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

	sanitize_env ();

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

	initenv ();

	amroot = (getuid () == 0);
	Prog = Basename (argv[0]);
	log_set_progname(Prog);
	log_set_logfd(stderr);

	if (geteuid() != 0) {
		fprintf (stderr, _("%s: Cannot possibly work without effective root\n"), Prog);
		exit (1);
	}

	process_flags (argc, argv);

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

	utent = get_current_utmp ();
	/*
	 * 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
	 */
	if (!amroot && (NULL == utent)) {
		(void) puts (_("No utmp entry.  You must exec \"login\" from the lowest level \"sh\""));
		exit (1);
	}
	/* NOTE: utent might be NULL afterwards */

	tmptty = ttyname (0);
	if (NULL == tmptty) {
		tmptty = "UNKNOWN";
	}
	STRFCPY (tty, tmptty);

#ifndef USE_PAM
	is_console = console (tty);
#endif

	if (rflg || hflg) {
		/*
		 * Add remote hostname to the environment. I think
		 * (not sure) I saw it once on Irix.  --marekm
		 */
		addenv ("REMOTEHOST", hostname);
	}
	if (fflg) {
		preauth_flag = true;
	}
	if (hflg) {
		reason = PW_RLOGIN;
	}
#ifdef RLOGIN
	if (rflg) {
		assert (NULL == username);
		username = xmalloc (USER_NAME_MAX_LENGTH + 1);
		username[USER_NAME_MAX_LENGTH] = '\0';
		if (do_rlogin (hostname, username, USER_NAME_MAX_LENGTH, term, sizeof term)) {
			preauth_flag = true;
		} else {
			free (username);
			username = NULL;
		}
	}
#endif				/* RLOGIN */

	OPENLOG ("login");

	setup_tty ();

#ifndef USE_PAM
	(void) umask (getdef_num ("UMASK", GETDEF_DEFAULT_UMASK));

	{
		/*
		 * 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);
		}
	}

#endif
	/*
	 * The entire environment will be preserved if the -p flag
	 * is used.
	 */
	if (pflg) {
		while (NULL != *envp) {	/* add inherited environment, */
			addenv (*envp, NULL); /* some variables change later */
			envp++;
		}
	}

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

	init_env ();

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

	if (rflg || hflg) {
		cp = hostname;
#if defined(HAVE_STRUCT_UTMP_UT_HOST) || defined(USE_UTMPX)
	} else if ((NULL != utent) && ('\0' != utent->ut_host[0])) {
		cp = utent->ut_host;
#endif				/* HAVE_STRUCT_UTMP_UT_HOST */
	} else {
		cp = "";
	}

	if ('\0' != *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 */
	timeout = getdef_unum ("LOGIN_TIMEOUT", ALARM);
	snprintf (tmsg, sizeof tmsg,
	          _("\nLogin timed out after %u seconds.\n"), timeout);
	(void) signal (SIGALRM, alarm_handler);
	if (timeout > 0) {
		(void) alarm (timeout);
	}

	environ = newenvp;	/* make new environment active */
	delay   = getdef_unum ("FAIL_DELAY", 1);
	retries = getdef_unum ("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.
	 *
	 * PAM_RHOST and PAM_TTY are used for authentication, only use
	 * information coming from login or from the caller (e.g. no utmp)
	 */
	retcode = pam_set_item (pamh, PAM_RHOST, hostname);
	PAM_FAIL_CHECK;
	retcode = pam_set_item (pamh, PAM_TTY, tty);
	PAM_FAIL_CHECK;
#ifdef HAS_PAM_FAIL_DELAY
	retcode = pam_fail_delay (pamh, 1000000 * delay);
	PAM_FAIL_CHECK;
#endif
	/* if fflg, then the user has already been authenticated */
	if (!fflg) {
		unsigned int failcount = 0;
		char hostn[256];
		char loginprompt[256];	/* That's one hell of a prompt :) */

		/* Make the login prompt look like we want it */
		if (gethostname (hostn, sizeof (hostn)) == 0) {
			snprintf (loginprompt,
			          sizeof (loginprompt),
			          _("%s login: "), hostn);
		} else {
			strncpy (loginprompt, _("login: "),
			         sizeof (loginprompt));
		}

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

		/* if we didn't get a user on the command line,
		   set it to NULL */
		get_pam_user (&pam_user);
		if ((NULL != pam_user) && ('\0' == pam_user[0])) {
			retcode = pam_set_item (pamh, PAM_USER, NULL);
			PAM_FAIL_CHECK;
		}

		/*
		 * 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?
		 */
		failcount = 0;
		while (true) {
			bool failed = false;

			failcount++;
#ifdef HAS_PAM_FAIL_DELAY
			if (delay > 0) {
				retcode = pam_fail_delay(pamh, 1000000*delay);
				PAM_FAIL_CHECK;
			}
#endif

			retcode = pam_authenticate (pamh, 0);

			get_pam_user (&pam_user);
			failent_user = get_failent_user (pam_user);

			if (retcode == PAM_MAXTRIES) {
				SYSLOG ((LOG_NOTICE,
				         "TOO MANY LOGIN TRIES (%u)%s FOR '%s'",
				         failcount, fromhost, failent_user));
				fprintf (stderr,
				         _("Maximum number of tries exceeded (%u)\n"),
				         failcount);
				PAM_END;
				exit(0);
			} else if (retcode == PAM_ABORT) {
				/* Serious problems, quit now */
				(void) fputs (_("login: abort requested by PAM\n"), stderr);
				SYSLOG ((LOG_ERR,"PAM_ABORT returned from pam_authenticate()"));
				PAM_END;
				exit(99);
			} else if (retcode != PAM_SUCCESS) {
				SYSLOG ((LOG_NOTICE,"FAILED LOGIN (%u)%s FOR '%s', %s",
				         failcount, fromhost, failent_user,
				         pam_strerror (pamh, retcode)));
				failed = true;
			}

			if (!failed) {
				break;
			}

#ifdef WITH_AUDIT
			audit_fd = audit_open ();
			audit_log_acct_message (audit_fd,
			                        AUDIT_USER_LOGIN,
			                        NULL,    /* Prog. name */
			                        "login",
			                        failent_user,
			                        AUDIT_NO_ID,
			                        hostname,
			                        NULL,    /* addr */
			                        tty,
			                        0);      /* result */
			close (audit_fd);
#endif				/* WITH_AUDIT */

			(void) puts ("");
			(void) puts (_("Login incorrect"));

			if (failcount >= retries) {
				SYSLOG ((LOG_NOTICE,
				         "TOO MANY LOGIN TRIES (%u)%s FOR '%s'",
				         failcount, fromhost, failent_user));
				fprintf (stderr,
				         _("Maximum number of tries exceeded (%u)\n"),
				         failcount);
				PAM_END;
				exit(0);
			}

			/*
			 * Let's give it another go around.
			 * Even if a username was given on the command
			 * line, prompt again for the username.
			 */
			retcode = pam_set_item (pamh, PAM_USER, NULL);
			PAM_FAIL_CHECK;
		}

		/* We don't get here unless they were authenticated above */
		(void) alarm (0);
	}

	/* Check the account validity */
	retcode = pam_acct_mgmt (pamh, 0);
	if (retcode == PAM_NEW_AUTHTOK_REQD) {
		retcode = pam_chauthtok (pamh, PAM_CHANGE_EXPIRED_AUTHTOK);
	}
	PAM_FAIL_CHECK;

	/* Open the PAM session */
	get_pam_user (&pam_user);
	retcode = pam_open_session (pamh, hushed (pam_user) ? PAM_SILENT : 0);
	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.
	 *
	 * From now on, we will discard changes of the user (PAM_USER) by
	 * PAM APIs.
	 */
	get_pam_user (&pam_user);
	if (NULL != username) {
		free (username);
	}
	username = xstrdup (pam_user);
	failent_user = get_failent_user (username);

	pwd = xgetpwnam (username);
	if (NULL == pwd) {
		SYSLOG ((LOG_ERR, "cannot find user %s", failent_user));
		fprintf (stderr,
		         _("Cannot find user (%s)\n"),
		         username);
		exit (1);
	}

	/* This set up the process credential (group) and initialize the
	 * supplementary group access list.
	 * This has to be done before pam_setcred
	 */
	if (setup_groups (pwd) != 0) {
		exit (1);
	}

	retcode = pam_setcred (pamh, PAM_ESTABLISH_CRED);
	PAM_FAIL_CHECK;
	/* NOTE: If pam_setcred changes PAM_USER, this will not be taken
	 * into account.
	 */

#else				/* ! USE_PAM */
	while (true) {	/* repeatedly get login/password pairs */
		bool failed;
		/* user_passwd is always a pointer to this constant string
		 * or a passwd or shadow password that will be memzero by
		 * pw_free / spw_free.
		 * Do not free() user_passwd. */
		const char *user_passwd = "!";

		/* Do some cleanup to avoid keeping entries we do not need
		 * anymore. */
		if (NULL != pwd) {
			pw_free (pwd);
			pwd = NULL;
		}
		if (NULL != spwd) {
			spw_free (spwd);
			spwd = NULL;
		}

		failed = false;	/* haven't failed authentication yet */
		if (NULL == username) {	/* need to get a login id */
			if (subroot) {
				closelog ();
				exit (1);
			}
			preauth_flag = false;
			username = xmalloc (USER_NAME_MAX_LENGTH + 1);
			username[USER_NAME_MAX_LENGTH] = '\0';
			login_prompt (_("\n%s login: "), username, USER_NAME_MAX_LENGTH);

			if ('\0' == username[0]) {
				/* Prompt for a new login */
				free (username);
				username = NULL;
				continue;
			}
		}
		/* Get the username to be used to log failures */
		failent_user = get_failent_user (username);

		pwd = xgetpwnam (username);
		if (NULL == pwd) {
			preauth_flag = false;
			failed = true;
		} else {
			user_passwd = pwd->pw_passwd;
			/*
			 * If the encrypted password begins with a "!",
			 * the account is locked and the user cannot
			 * login, even if they have been
			 * "pre-authenticated."
			 */
			if (   ('!' == user_passwd[0])
			    || ('*' == user_passwd[0])) {
				failed = true;
			}

			if (strcmp (user_passwd, "") == 0) {
				char *prevent_no_auth = getdef_str("PREVENT_NO_AUTH");
				if (prevent_no_auth == NULL) {
					prevent_no_auth = "superuser";
				}
				if (strcmp(prevent_no_auth, "yes") == 0) {
					failed = true;
				} else if ((pwd->pw_uid == 0)
					&& (strcmp(prevent_no_auth, "superuser") == 0)) {
					failed = true;
				}
			}
		}

		if (strcmp (user_passwd, SHADOW_PASSWD_STRING) == 0) {
			spwd = xgetspnam (username);
			if (NULL != spwd) {
				user_passwd = spwd->sp_pwdp;
			} else {
				/* The user exists in passwd, but not in
				 * shadow. SHADOW_PASSWD_STRING indicates
				 * that the password shall be in shadow.
				 */
				SYSLOG ((LOG_WARN,
				         "no shadow password for '%s'%s",
				         username, fromhost));
			}
		}

		/*
		 * 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 (user_passwd, username, reason, (char *) 0) == 0) {
			goto auth_ok;
		}

		SYSLOG ((LOG_WARN, "invalid password for '%s' %s",
		         failent_user, fromhost));
		failed = true;

	      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
		    && (NULL != pwd)
		    && (0 == pwd->pw_uid)
		    && !is_console) {
			SYSLOG ((LOG_CRIT, "ILLEGAL ROOT LOGIN %s", fromhost));
			failed = true;
		}
		if (   !failed
		    && !login_access (username, ('\0' != *hostname) ? hostname : tty)) {
			SYSLOG ((LOG_WARN, "LOGIN '%s' REFUSED %s",
			         username, fromhost));
			failed = true;
		}
		if (   (NULL != pwd)
		    && getdef_bool ("FAILLOG_ENAB")
		    && !failcheck (pwd->pw_uid, &faillog, failed)) {
			SYSLOG ((LOG_CRIT,
			         "exceeded failure limit for '%s' %s",
			         username, fromhost));
			failed = true;
		}
		if (!failed) {
			break;
		}

		/* don't log non-existent users */
		if ((NULL != pwd) && getdef_bool ("FAILLOG_ENAB")) {
			failure (pwd->pw_uid, tty, &faillog);
		}
		if (getdef_str ("FTMP_FILE") != NULL) {
#ifdef USE_UTMPX
			struct utmpx *failent =
				prepare_utmpx (failent_user,
				               tty,
			/* FIXME: or fromhost? */hostname,
				               utent);
#else				/* !USE_UTMPX */
			struct utmp *failent =
				prepare_utmp (failent_user,
				              tty,
				              hostname,
				              utent);
#endif				/* !USE_UTMPX */
			failtmp (failent_user, failent);
			free (failent);
		}

		retries--;
		if (retries <= 0) {
			SYSLOG ((LOG_CRIT, "REPEATED login failures%s",
			         fromhost));
		}

		/*
		 * 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 (user_passwd[0] == '\0') {
			pw_auth ("!", username, reason, (char *) 0);
		}

		/*
		 * Authentication of this user failed.
		 * The username must be confirmed in the next try.
		 */
		free (username);
		username = NULL;

		/*
		 * 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) {
			(void) sleep (delay);
		}

		(void) puts (_("Login incorrect"));

		/* allow only one attempt with -r or -f */
		if (rflg || fflg || (retries <= 0)) {
			closelog ();
			exit (1);
		}
	}			/* while (true) */
#endif				/* ! USE_PAM */
	assert (NULL != username);
	assert (NULL != pwd);

	(void) 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 (username, tty, time ((time_t *) 0))) {
		SYSLOG ((LOG_WARN, "invalid login time for '%s'%s",
		         username, fromhost));
		closelog ();
		bad_time_notify ();
		exit (1);
	}

	check_nologin (pwd->pw_uid == 0);
#endif

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

	if (pwd->pw_shell[0] == '*') {	/* subsystem root */
		pwd->pw_shell++;	/* skip the '*' */
		subsystem (pwd);	/* figure out what to execute */
		subroot = true;	/* 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 */
	}

#ifdef WITH_AUDIT
	audit_fd = audit_open ();
	audit_log_acct_message (audit_fd,
	                        AUDIT_USER_LOGIN,
	                        NULL,    /* Prog. name */
	                        "login",
	                        username,
	                        AUDIT_NO_ID,
	                        hostname,
	                        NULL,    /* addr */
	                        tty,
	                        1);      /* result */
	close (audit_fd);
#endif				/* WITH_AUDIT */

#ifndef USE_PAM			/* pam_lastlog handles this */
	if (   getdef_bool ("LASTLOG_ENAB")
	    && pwd->pw_uid <= (uid_t) getdef_ulong ("LASTLOG_UID_MAX", 0xFFFFFFFFUL)) {
		/* give last login and log this one */
		dolastlog (&ll, pwd, tty, 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.
	 */
	if (NULL != spwd) {		/* check for age of password */
		if (expire (pwd, spwd)) {
			/* The user updated her password, get the new
			 * entries.
			 * Use the x variants because we need to keep the
			 * entry for a long time, and there might be other
			 * getxxyyy in between.
			 */
			pw_free (pwd);
			pwd = xgetpwnam (username);
			if (NULL == pwd) {
				SYSLOG ((LOG_ERR,
				         "cannot find user %s after update of expired password",
				         username));
				exit (1);
			}
			spw_free (spwd);
			spwd = xgetspnam (username);
		}
	}
	setup_limits (pwd);	/* nice, ulimit etc. */
#endif				/* ! USE_PAM */
	chown_tty (pwd);

#ifdef USE_PAM
	/*
	 * We must fork before setuid() because we need to call
	 * pam_close_session() as root.
	 */
	(void) signal (SIGINT, SIG_IGN);
	child = fork ();
	if (child < 0) {
		/* error in fork() */
		fprintf (stderr, _("%s: failure forking: %s"),
		         Prog, strerror (errno));
		PAM_END;
		exit (0);
	} else if (child != 0) {
		/*
		 * parent - wait for child to finish, then cleanup
		 * session
		 */
		wait (NULL);
		PAM_END;
		exit (0);
	}
	/* child */
#endif

	/* If we were init, we need to start a new session */
	if (getppid() == 1) {
		setsid();
		if (ioctl(0, TIOCSCTTY, 1) != 0) {
			fprintf (stderr, _("TIOCSCTTY failed on %s"), tty);
		}
	}

	/*
	 * The utmp entry needs to be updated to indicate the new status
	 * of the session, the new PID and SID.
	 */
	update_utmp (username, tty, hostname, utent);

	/* The pwd and spwd entries for the user have been copied.
	 *
	 * Close all the files so that unauthorized access won't occur.
	 */
	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

	/* Drop root privileges */
#ifndef USE_PAM
	if (setup_uid_gid (pwd, is_console))
#else
	/* The group privileges were already dropped.
	 * See setup_groups() above.
	 */
	if (change_uid (pwd))
#endif
	{
		exit (1);
	}

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

#ifdef USE_PAM
	{
		const char *const *env;

		env = (const char *const *) pam_getenvlist (pamh);
		while ((NULL != env) && (NULL != *env)) {
			addenv (*env, NULL);
			env++;
		}
	}
	(void) pam_end (pamh, PAM_SUCCESS | PAM_DATA_SILENT);
#endif

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

	if (!hushed (username)) {
		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")
		    && (0 != faillog.fail_cnt)) {
			failprint (&faillog);
			/* Reset the lockout times if logged in */
			if (   (0 != faillog.fail_max)
			    && (faillog.fail_cnt >= faillog.fail_max)) {
				(void) puts (_("Warning: login re-enabled after temporary lockout."));
				SYSLOG ((LOG_WARN,
				         "login '%s' re-enabled after temporary lockout (%d failures)",
				         username, (int) faillog.fail_cnt));
			}
		}
		if (   getdef_bool ("LASTLOG_ENAB")
		    && pwd->pw_uid <= (uid_t) getdef_ulong ("LASTLOG_UID_MAX", 0xFFFFFFFFUL)
		    && (ll.ll_time != 0)) {
			time_t ll_time = ll.ll_time;

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

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

	ttytype (tty);

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

	if (0 == pwd->pw_uid) {
		SYSLOG ((LOG_NOTICE, "ROOT LOGIN %s", fromhost));
	} else if (getdef_bool ("LOG_OK_LOGINS")) {
		SYSLOG ((LOG_INFO, "'%s' logged in %s", username, fromhost));
	}
	closelog ();
	tmp = getdef_str ("FAKE_SHELL");
	if (NULL != tmp) {
		err = shell (tmp, pwd->pw_shell, newenvp); /* fake shell */
	} else {
		/* exec the shell finally */
		err = shell (pwd->pw_shell, (char *) 0, newenvp);
	}

	return ((err == ENOENT) ? E_CMD_NOTFOUND : E_CMD_NOEXEC);
}

