]> granicus.if.org Git - shadow/blobdiff - src/login.c
* src/su.c: Extract export of environment from main().
[shadow] / src / login.c
index 90f0ca259742a3e48c07643bdf6c5623f0635bb4..c24fc860d7cfb4f75b790acefed37c4eeab9d93b 100644 (file)
@@ -2,7 +2,7 @@
  * Copyright (c) 1989 - 1994, Julianne Frances Haugh
  * Copyright (c) 1996 - 2001, Marek Michałkiewicz
  * Copyright (c) 2001 - 2006, Tomasz Kłoczko
- * Copyright (c) 2007 - 2008, Nicolas François
+ * Copyright (c) 2007 - 2010, Nicolas François
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
 
 #include <errno.h>
 #include <grp.h>
+#ifndef USE_PAM
 #include <lastlog.h>
-#ifdef UT_ADDR
-#include <netdb.h>
-#endif
+#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"
+
 #ifdef USE_PAM
 #include "pam_defs.h"
 
@@ -68,47 +70,40 @@ static pam_handle_t *pamh = NULL;
 
 #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 *hostname = "";
+const char *Prog;
 
-static struct passwd pwent;
+static const char *hostname = "";
+static /*@null@*/ /*@only@*/char *username = NULL;
+static int reason = PW_LOGIN;
 
-#if HAVE_UTMPX_H
-extern struct utmpx utxent;
-struct utmpx failent;
-#else
-struct utmp failent;
-#endif
-extern struct utmp utent;
-
-struct lastlog lastlog;
+#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
+#else                          /* RLOGIN */
 #define rflg false
-#endif
+#endif                         /* !RLOGIN */
 static bool hflg = false;
 static bool preauth_flag = false;
 
-/*
- * Global variables.
- */
-
-static char *Prog;
 static bool amroot;
-static int timeout;
+static unsigned int timeout;
 
 /*
  * External identifiers.
@@ -129,13 +124,20 @@ extern char **environ;
 /* local function prototypes */
 static void usage (void);
 static void setup_tty (void);
-static void check_flags (int, char *const *);
+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,
+                         /*@null@*/const struct utmp *utent);
 
 #ifndef USE_PAM
 static struct faillog faillog;
 
 static void bad_time_notify (void);
-static void check_nologin (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);
@@ -158,7 +160,7 @@ static void usage (void)
        fprintf (stderr, _("       %s [-p] [-h host] [-f name]\n"), Prog);
 #ifdef RLOGIN
        fprintf (stderr, _("       %s [-p] -r host\n"), Prog);
-#endif
+#endif                         /* RLOGIN */
        exit (1);
 }
 
@@ -166,23 +168,53 @@ static void setup_tty (void)
 {
        TERMIO termio;
 
-       GTTY (0, &termio);      /* get terminal characteristics */
+       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;
+               /*
+                * 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]);
+#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
 
-       /*
-        * ttymon invocation prefers this, but these settings won't come into
-        * effect after the first username login 
-        */
-       STTY (0, &termio);
+               /* 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);
+       }
 }
 
 
@@ -196,7 +228,7 @@ static void bad_time_notify (void)
        (void) fflush (stdout);
 }
 
-static void check_nologin (void)
+static void check_nologin (bool login_to_root)
 {
        char *fname;
 
@@ -210,7 +242,6 @@ static void check_nologin (void)
        fname = getdef_str ("NOLOGINS_FILE");
        if ((NULL != fname) && (access (fname, F_OK) == 0)) {
                FILE *nlfp;
-               int c;
 
                /*
                 * Cat the file if it can be opened, otherwise just
@@ -218,6 +249,7 @@ static void check_nologin (void)
                 */
                nlfp = fopen (fname, "r");
                if (NULL != nlfp) {
+                       int c;
                        while ((c = getc (nlfp)) != EOF) {
                                if (c == '\n') {
                                        (void) putchar ('\r');
@@ -235,18 +267,19 @@ static void check_nologin (void)
                 * gets to login.
                 */
 
-               if (pwent.pw_uid != 0) {
+               if (!login_to_root) {
                        closelog ();
                        exit (0);
                }
-               puts (_("\n[Disconnect bypassed -- root login allowed.]"));
+               (void) puts (_("\n[Disconnect bypassed -- root login allowed.]"));
        }
 }
 #endif                         /* !USE_PAM */
 
-static void check_flags (int argc, char *const *argv)
+static void process_flags (int argc, char *const *argv)
 {
        int arg;
+       int flag;
 
        /*
         * Check the flags for proper form. Every argument starting with
@@ -261,6 +294,76 @@ static void check_flags (int argc, char *const *argv)
                        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 ();
+       }
+
 }
 
 
@@ -313,10 +416,86 @@ static void init_env (void)
 
 static RETSIGTYPE alarm_handler (unused int sig)
 {
-       fprintf (stderr, _("\nLogin timed out after %d seconds.\n"), timeout);
+       fprintf (stderr, _("\nLogin timed out after %u seconds.\n"), timeout);
        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,
+                         /*@null@*/const struct utmp *utent)
+{
+       struct utmp  *ut  = prepare_utmp  (user, tty, host, utent);
+#ifdef USE_UTMPX
+       struct utmpx *utx = prepare_utmpx (user, tty, host, utent);
+#endif                         /* USE_UTMPX */
+
+       (void) setutmp  (ut);   /* make entry in the utmp & wtmp files */
+       free (ut);
+
+#ifdef USE_UTMPX
+       (void) setutmpx (utx);  /* make entry in the utmpx & wtmpx files */
+       free (utx);
+#endif                         /* USE_UTMPX */
+}
 
 /*
  * login - create a new login session for a user
@@ -337,38 +516,34 @@ static RETSIGTYPE alarm_handler (unused int sig)
  */
 int main (int argc, char **argv)
 {
-       char username[32];
+       const char *tmptty;
        char tty[BUFSIZ];
 
 #ifdef RLOGIN
        char term[128] = "";
-#endif
+#endif                         /* RLOGIN */
 #if defined(HAVE_STRFTIME) && !defined(USE_PAM)
        char ptime[80];
 #endif
-       int reason = PW_LOGIN;
-       int delay;
-       int retries;
-       bool failed;
-       int flag;
+       unsigned int delay;
+       unsigned int retries;
        bool subroot = false;
 #ifndef USE_PAM
        bool is_console;
 #endif
        int err;
        const char *cp;
-       char *tmp;
+       const char *tmp;
        char fromhost[512];
-       struct passwd *pwd;
+       struct passwd *pwd = NULL;
        char **envp = environ;
-       static char temp_pw[2];
-       static char temp_shell[] = "/bin/sh";
+       const char *failent_user;
+       /*@null@*/struct utmp *utent;
 
 #ifdef USE_PAM
        int retcode;
        pid_t child;
-       char *pam_user;
-       char **ptr_pam_user = &pam_user;
+       char *pam_user = NULL;
 #else
        struct spwd *spwd = NULL;
 #endif
@@ -384,77 +559,21 @@ int main (int argc, char **argv)
 
        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 'd':
-                       /* "-d device" ignored for compatibility */
-                       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 = true;
-                       if (optarg) {
-                               STRFCPY (username, optarg);
-                       }
-                       break;
-               case 'h':
-                       hflg = true;
-                       hostname = optarg;
-                       reason = PW_TELNET;
-                       break;
-#ifdef RLOGIN
-               case 'r':
-                       rflg = true;
-                       hostname = optarg;
-                       reason = PW_RLOGIN;
-                       break;
-#endif
-               case 'p':
-                       pflg = true;
-                       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);
+       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
@@ -462,424 +581,399 @@ int main (int argc, char **argv)
         * but users must "exec login" which will use the existing utmp
         * entry (will not overwrite remote hostname).  --marekm
         */
-       checkutmp (!amroot);
-       STRFCPY (tty, utent.ut_line);
+       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) {
-#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
+                * Add remote hostname to the environment. I think
+                * (not sure) I saw it once on Irix.  --marekm
                 */
-               he = gethostbyname (hostname);
-               if (NULL != he) {
-                       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 = true;
-               }
-               if (hflg) {
-                       reason = PW_RLOGIN;
-               }
+               addenv ("REMOTEHOST", hostname);
+       }
+       if (fflg) {
+               preauth_flag = true;
+       }
+       if (hflg) {
+               reason = PW_RLOGIN;
+       }
 #ifdef RLOGIN
-               if (   rflg
-                   && do_rlogin (hostname, username, sizeof username,
-                                 term, sizeof term)) {
+       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
+       }
+#endif                         /* RLOGIN */
 
-               OPENLOG ("login");
+       OPENLOG ("login");
 
-               setup_tty ();
+       setup_tty ();
 
 #ifndef USE_PAM
-               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);
+       (void) umask (getdef_num ("UMASK", GETDEF_DEFAULT_UMASK));
 
-                       if (limit != -1) {
-                               set_filesize_limit (limit);
-                       }
+       {
+               /* 
+                * 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++;
-                       }
+       /*
+        * 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
-               {
-                       /* preserve TERM from getty */
-                       if (!pflg) {
-                               tmp = getenv ("TERM");
-                               if (NULL != tmp) {
-                                       addenv ("TERM", tmp);
-                               }
+       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 ();
+       init_env ();
 
-               if (optind < argc) {    /* get the user name */
-                       if (rflg || (fflg && ('\0' != username[0]))) {
-                               usage ();
-                       }
+       if (optind < argc) {    /* now set command line variables */
+               set_env (argc - optind, &argv[optind]);
+       }
 
-                       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;
+#ifdef HAVE_STRUCT_UTMP_UT_HOST
+       } else if ((NULL != utent) && ('\0' != utent->ut_host[0])) {
+               cp = utent->ut_host;
+#endif                         /* HAVE_STRUCT_UTMP_UT_HOST */
+       } else {
+               cp = "";
+       }
 
-               if (rflg || hflg) {
-                       cp = hostname;
-               } else {
-                       /* FIXME: What is the priority:
-                        *        UT_HOST or HAVE_UTMPX_H? */
-#ifdef UT_HOST
-                       if ('\0' != utent.ut_host[0]) {
-                               cp = utent.ut_host;
-                       } else
-#endif
-#if HAVE_UTMPX_H
-                       if ('\0' != utxent.ut_host[0]) {
-                               cp = utxent.ut_host;
-                       } else
-#endif
-                       {
-                               cp = "";
-                       }
-               }
+       if ('\0' != *cp) {
+               snprintf (fromhost, sizeof fromhost,
+                         " on '%.100s' from '%.200s'", tty, cp);
+       } else {
+               snprintf (fromhost, sizeof fromhost,
+                         " on '%.100s'", tty);
+       }
 
-               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 */
+       (void) signal (SIGALRM, alarm_handler);
+       timeout = getdef_unum ("LOGIN_TIMEOUT", ALARM);
+       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);
+       }
 
-             top:
-               /* only allow ALARM sec. for login */
-               (void) signal (SIGALRM, alarm_handler);
-               timeout = getdef_num ("LOGIN_TIMEOUT", ALARM);
-               if (timeout > 0) {
-                       alarm (timeout);
+       /*
+        * 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));
                }
 
-               environ = newenvp;      /* make new environment active */
-               delay = getdef_num ("FAIL_DELAY", 1);
-               retries = getdef_num ("LOGIN_RETRIES", RETRIES);
+               retcode = pam_set_item (pamh, PAM_USER_PROMPT, loginprompt);
+               PAM_FAIL_CHECK;
 
-#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);
+               /* 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;
                }
 
                /*
-                * 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.
+                * 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_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, then the user has already been authenticated */
-               if (!fflg || (getuid () != 0)) {
-                       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 {
-                               snprintf (loginprompt,
-                                         sizeof (loginprompt), _("login: "));
+               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_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 */
-                       pam_get_item (pamh, PAM_USER,
-                                     (const void **)ptr_pam_user);
-                       if (pam_user[0] == '\0') {
-                               pam_set_item (pamh, PAM_USER, NULL);
+                       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;
                        }
 
-                       /*
-                        * 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) {
-                         const char *failent_user;
-                         failed = false;
-
-                         failcount++;
-                         if (delay > 0) {
-                           retcode = pam_fail_delay(pamh, 1000000*delay);
-                         }
-
-                         retcode = pam_authenticate (pamh, 0);
-
-                         pam_get_item (pamh, PAM_USER,
-                                       (const void **) ptr_pam_user);
-
-                         if ((NULL != pam_user) && ('\0' != pam_user[0])) {
-                           pwd = xgetpwnam(pam_user);
-                           if (NULL != pwd) {
-                             pwent = *pwd;
-                             failent_user = pwent.pw_name;
-                           } else {
-                             if (   getdef_bool("LOG_UNKFAIL_ENAB")
-                                 && (NULL != pam_user)) {
-                               failent_user = pam_user;
-                             } else {
-                               failent_user = "UNKNOWN";
-                             }
-                           }
-                         } else {
-                           pwd = NULL;
-                           failent_user = "UNKNOWN";
-                         }
-
-                         if (retcode == PAM_MAXTRIES || failcount >= retries) {
-                           SYSLOG ((LOG_NOTICE,
-                                   "TOO MANY LOGIN TRIES (%d)%s FOR `%s'",
-                                   failcount, fromhost, failent_user));
-                           fprintf(stderr,
-                                   _("Maximum number of tries exceeded (%d)\n"),
-                                   failcount);
-                           PAM_END;
-                           exit(0);
-                         } else if (retcode == PAM_ABORT) {
-                           /* Serious problems, quit now */
-                           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 (%d)%s FOR `%s', %s",
-                                  failcount, fromhost, failent_user,
-                                  pam_strerror (pamh, retcode)));
-                           failed = true;
-                         }
-
-                         if (!failed) {
-                           break;
-                         }
+                       if (!failed) {
+                               break;
+                       }
 
 #ifdef WITH_AUDIT
-                               {
-                                       struct passwd *pw;
-                                       char buf[64];
-
-                                       audit_fd = audit_open ();
-                                       /* local, no need for xgetpwnam */
-                                       pw = getpwnam (username);
-                                       if (NULL != pw) {
-                                               snprintf (buf, sizeof (buf),
-                                                         "uid=%lu",
-                                                   (unsigned long) pw->pw_uid);
-                                               audit_log_user_message
-                                                   (audit_fd, AUDIT_USER_LOGIN,
-                                                    buf, hostname, NULL,
-                                                    tty, 0);
-                                       } else {
-                                               snprintf (buf, sizeof (buf),
-                                                         "acct=%s", username);
-                                               audit_log_user_message
-                                                   (audit_fd, AUDIT_USER_LOGIN,
-                                                    buf, hostname, NULL,
-                                                    tty, 0);
-                                       }
-                                       close (audit_fd);
-                               }
+                       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 */
 
-                         fprintf (stderr, "\nLogin incorrect\n");
-
-                         /* Let's give it another go around */
-                         pam_set_item (pamh, PAM_USER, NULL);
-                       }
-
-                       /* We don't get here unless they were authenticated above */
-                       alarm (0);
-                       retcode = pam_acct_mgmt (pamh, 0);
-
-                       if (retcode == PAM_NEW_AUTHTOK_REQD) {
-                               retcode =
-                                   pam_chauthtok (pamh,
-                                                  PAM_CHANGE_EXPIRED_AUTHTOK);
+                       (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;
                }
 
-               /* 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 **)ptr_pam_user);
-               pwd = xgetpwnam (pam_user);
-               if (NULL == pwd) {
-                       SYSLOG ((LOG_ERR, "xgetpwnam(%s) failed",
-                                getdef_bool ("LOG_UNKFAIL_ENAB") ?
-                                pam_user : "UNKNOWN"));
-                       exit (1);
-               }
-
-               if (fflg) {
-                       retcode = pam_acct_mgmt (pamh, 0);
-                       PAM_FAIL_CHECK;
-               }
+               /* We don't get here unless they were authenticated above */
+               (void) alarm (0);
+       }
 
-               if (setup_groups (pwd) != 0) {
-                       exit (1);
-               }
+       /* 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 = pam_user;
+       failent_user = get_failent_user (username);
 
-               pwent = *pwd;
+       pwd = xgetpwnam (username);
+       if (NULL == pwd) {
+               SYSLOG ((LOG_ERR, "cannot find user %s", failent_user));
+               exit (1);
+       }
 
-               retcode = pam_setcred (pamh, PAM_ESTABLISH_CRED);
-               PAM_FAIL_CHECK;
+       /* 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_open_session (pamh,
-                                           hushed (&pwent) ? PAM_SILENT : 0);
-               PAM_FAIL_CHECK;
+       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 */
-                       failed = false; /* haven't failed authentication yet */
-                       if ('\0' == username[0]) {      /* need to get a login id */
-                               if (subroot) {
-                                       closelog ();
-                                       exit (1);
-                               }
-                               preauth_flag = false;
-                               login_prompt (_("\n%s login: "), username,
-                                             sizeof username);
+       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;
                        }
-#endif                         /* ! USE_PAM */
+               }
+               /* Get the username to be used to log failures */
+               failent_user = get_failent_user (username);
 
-#ifdef USE_PAM
-               pwd = xgetpwnam (pam_user);
-               if (NULL == pwd) {
-                       pwent.pw_name = pam_user;
-#else
                pwd = xgetpwnam (username);
                if (NULL == pwd) {
-                       pwent.pw_name = username;
-#endif
-                       strcpy (temp_pw, "!");
-                       pwent.pw_passwd = temp_pw;
-                       pwent.pw_shell = temp_shell;
-
                        preauth_flag = false;
                        failed = true;
                } else {
-                       pwent = *pwd;
+                       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;
+                       }
                }
-#ifndef USE_PAM
-               spwd = NULL;
-               if (   (NULL != pwd)
-                   && (strcmp (pwd->pw_passwd, SHADOW_PASSWD_STRING) == 0)) {
-                       /* !USE_PAM, no need for xgetspnam */
-                       spwd = getspnam (username);
+
+               if (strcmp (user_passwd, SHADOW_PASSWD_STRING) == 0) {
+                       spwd = xgetspnam (username);
                        if (NULL != spwd) {
-                               pwent.pw_passwd = spwd->sp_pwdp;
+                               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));
+                                        "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 = true;
-               }
-
                /*
                 * The -r and -f flags provide a name which has already
                 * been authenticated by some server.
@@ -888,20 +982,12 @@ int main (int argc, char **argv)
                        goto auth_ok;
                }
 
-               if (pw_auth (pwent.pw_passwd, username,
-                            reason, (char *) 0) == 0) {
+               if (pw_auth (user_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",
-                        (   (NULL != pwd)
-                         || getdef_bool ("LOG_UNKFAIL_ENAB")) ?
-                        username : "UNKNOWN", fromhost));
+               SYSLOG ((LOG_WARN, "invalid password for '%s' %s",
+                        failent_user, fromhost));
                failed = true;
 
              auth_ok:
@@ -911,24 +997,24 @@ int main (int argc, char **argv)
                 * authenticated and so on.
                 */
                if (   !failed
-                   && (NULL != pwent.pw_name)
-                   && (0 == pwent.pw_uid)
+                   && (NULL != pwd)
+                   && (0 == pwd->pw_uid)
                    && !is_console) {
                        SYSLOG ((LOG_CRIT, "ILLEGAL ROOT LOGIN %s", fromhost));
                        failed = true;
                }
                if (   !failed
-                   && !login_access (username, *hostname ? hostname : tty)) {
-                       SYSLOG ((LOG_WARN, "LOGIN `%s' REFUSED %s",
-                                username, fromhost));
+                   && !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 (pwent.pw_uid, &faillog, failed)) {
+                   && !failcheck (pwd->pw_uid, &faillog, failed)) {
                        SYSLOG ((LOG_CRIT,
-                                "exceeded failure limit for `%s' %s",
-                                username, fromhost));
+                                "exceeded failure limit for '%s' %s",
+                                username, fromhost));
                        failed = true;
                }
                if (!failed) {
@@ -937,48 +1023,32 @@ int main (int argc, char **argv)
 
                /* don't log non-existent users */
                if ((NULL != pwd) && getdef_bool ("FAILLOG_ENAB")) {
-                       failure (pwent.pw_uid, tty, &faillog);
+                       failure (pwd->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 (NULL != 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));
-                       failent.ut_type = USER_PROCESS;
-                       failtmp (&failent);
+#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);
                }
-               memzero (username, sizeof username);
 
                retries--;
                if (retries <= 0) {
                        SYSLOG ((LOG_CRIT, "REPEATED login failures%s",
-                                fromhost));
+                                fromhost));
                }
+
                /*
                 * If this was a passwordless account and we get here, login
                 * was denied (securetty, faillog, etc.). There was no
@@ -986,29 +1056,40 @@ int main (int argc, char **argv)
                 * guys won't see that the passwordless account exists at
                 * all).  --marekm
                 */
-               if (pwent.pw_passwd[0] == '\0') {
+               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) {
-                       sleep (delay);
+                       (void) sleep (delay);
                }
 
-               puts (_("Login incorrect"));
+               (void) puts (_("Login incorrect"));
 
                /* allow only one attempt with -r or -f */
                if (rflg || fflg || (retries <= 0)) {
                        closelog ();
                        exit (1);
                }
-       }                       /* while (1) */
+       }                       /* while (true) */
 #endif                         /* ! USE_PAM */
-       alarm (0);              /* turn off alarm clock */
+       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
@@ -1016,29 +1097,24 @@ int main (int argc, char **argv)
         * 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));
+           && !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 ();
+       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 */
        }
 
-#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 */
+       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 */
@@ -1050,45 +1126,54 @@ int main (int argc, char **argv)
        }
 
 #ifdef WITH_AUDIT
-       {
-               char buf[32];
-
-               audit_fd = audit_open ();
-               snprintf (buf, sizeof (buf), "uid=%lu",
-                         (unsigned long) pwd->pw_uid);
-               audit_log_user_message (audit_fd, AUDIT_USER_LOGIN,
-                                       buf, hostname, NULL, tty, 1);
-               close (audit_fd);
-       }
+       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")) {     /* give last login and log this one */
-               dolastlog (&lastlog, &pwent, utent.ut_line, hostname);
+               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. expire() closes password files,
-        * and changes to the user in the child before executing the passwd
-        * program.  --marekm
+        * don't have access to /etc/shadow.
         */
-       if (spwd) {             /* check for age of password */
-               if (expire (&pwent, spwd)) {
-                       /* !USE_PAM, no need for xgetpwnam */
-                       pwd = getpwnam (username);
-                       /* !USE_PAM, no need for xgetspnam */
-                       spwd = getspnam (username);
-                       if (pwd) {
-                               pwent = *pwd;
+       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
+                        * getxxyy 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 (&pwent);  /* nice, ulimit etc. */
+       setup_limits (pwd);     /* nice, ulimit etc. */
 #endif                         /* ! USE_PAM */
-       chown_tty (tty, &pwent);
+       chown_tty (pwd);
 
 #ifdef USE_PAM
        /*
@@ -1100,7 +1185,7 @@ int main (int argc, char **argv)
        if (child < 0) {
                /* error in fork() */
                fprintf (stderr, _("%s: failure forking: %s"),
-                        Prog, strerror (errno));
+                        Prog, strerror (errno));
                PAM_END;
                exit (0);
        } else if (child != 0) {
@@ -1114,25 +1199,46 @@ int main (int argc, char **argv)
        }
        /* 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);
+                       fprintf (stderr, _("TIOCSCTTY failed on %s"), tty);
                }
        }
 
-       /* We call set_groups() above because this clobbers pam_groups.so */
+       /*
+        * 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 (&pwent, is_console))
+       if (setup_uid_gid (pwd, is_console))
 #else
-       if (change_uid (&pwent))
+       /* The group privileges were already dropped.
+        * See setup_groups() above.
+        */
+       if (change_uid (pwd))
 #endif
        {
                exit (1);
        }
 
-       setup_env (&pwent);     /* set env vars, cd to the home dir */
+       setup_env (pwd);        /* set env vars, cd to the home dir */
 
 #ifdef USE_PAM
        {
@@ -1150,7 +1256,7 @@ int main (int argc, char **argv)
        (void) bindtextdomain (PACKAGE, LOCALEDIR);
        (void) textdomain (PACKAGE);
 
-       if (!hushed (&pwent)) {
+       if (!hushed (username)) {
                addenv ("HUSHLOGIN=FALSE", NULL);
                /*
                 * pam_unix, pam_mail and pam_lastlog should take care of
@@ -1164,37 +1270,35 @@ int main (int argc, char **argv)
                        /* Reset the lockout times if logged in */
                        if (   (0 != faillog.fail_max)
                            && (faillog.fail_cnt >= faillog.fail_max)) {
-                               puts (_
-                                     ("Warning: login re-enabled after temporary lockout."));
+                               (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));
+                                        "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;
+                   && (ll.ll_time != 0)) {
+                       time_t ll_time = ll.ll_time;
 
 #ifdef HAVE_STRFTIME
-                       strftime (ptime, sizeof (ptime),
-                                 "%a %b %e %H:%M:%S %z %Y",
-                                 localtime (&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, lastlog.ll_line);
+                               ptime, ll.ll_line);
 #else
                        printf (_("Last login: %.19s on %s"),
-                               ctime (&ll_time), lastlog.ll_line);
+                               ctime (&ll_time), ll.ll_line);
 #endif
 #ifdef HAVE_LL_HOST            /* __linux__ || SUN4 */
-                       if ('\0' != lastlog.ll_host[0]) {
+                       if ('\0' != ll.ll_host[0]) {
                                printf (_(" from %.*s"),
-                                       (int) sizeof lastlog.
-                                       ll_host, lastlog.ll_host);
+                                       (int) sizeof ll.ll_host, ll.ll_host);
                        }
 #endif
                        printf (".\n");
                }
-               agecheck (&pwent, spwd);
+               agecheck (spwd);
 
                mailcheck ();   /* report on the status of mail */
 #endif                         /* !USE_PAM */
@@ -1202,10 +1306,7 @@ int main (int argc, char **argv)
                addenv ("HUSHLOGIN=TRUE", NULL);
        }
 
-       if (   (NULL != getdef_str ("TTYTYPE_FILE"))
-           && (NULL == getenv ("TERM"))) {
-               ttytype (tty);
-       }
+       ttytype (tty);
 
        (void) signal (SIGQUIT, SIG_DFL);       /* default quit signal */
        (void) signal (SIGTERM, SIG_DFL);       /* default terminate signal */
@@ -1213,31 +1314,20 @@ int main (int argc, char **argv)
        (void) signal (SIGHUP, SIG_DFL);        /* added this.  --marekm */
        (void) 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 (0 == pwent.pw_uid) {
+       if (0 == pwd->pw_uid) {
                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
+               SYSLOG ((LOG_INFO, "'%s' logged in %s", username, fromhost));
        }
        closelog ();
        tmp = getdef_str ("FAKE_SHELL");
        if (NULL != tmp) {
-               err = shell (tmp, pwent.pw_shell, newenvp); /* fake shell */
+               err = shell (tmp, pwd->pw_shell, newenvp); /* fake shell */
        } else {
                /* exec the shell finally */
-               err = shell (pwent.pw_shell, (char *) 0, newenvp);
+               err = shell (pwd->pw_shell, (char *) 0, newenvp);
        }
-       exit (err == ENOENT ? E_CMD_NOTFOUND : E_CMD_NOEXEC);
-       /* NOT REACHED */
-       return 0;
+
+       return ((err == ENOENT) ? E_CMD_NOTFOUND : E_CMD_NOEXEC);
 }