]> granicus.if.org Git - shadow/blobdiff - src/login.c
* src/passwd.c: Overflow when computing the number of days based
[shadow] / src / login.c
index a77ca756cf0c6434d7e4294aaf57e3721604cdfd..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,33 +70,27 @@ 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
  */
-char *Prog;
+const char *Prog;
 
 static const char *hostname = "";
-static char *username = NULL;
+static /*@null@*/ /*@only@*/char *username = NULL;
 static int reason = PW_LOGIN;
 
-static struct passwd pwent;
-
-#if HAVE_UTMPX_H
-extern struct utmpx utxent;
-struct utmpx failent;
-#else
-struct utmp failent;
-#endif
-extern struct utmp utent;
-
-struct lastlog lastlog;
+#ifndef USE_PAM
+static struct lastlog ll;
+#endif                         /* !USE_PAM */
 static bool pflg = false;
 static bool fflg = false;
 
@@ -107,7 +103,7 @@ static bool hflg = false;
 static bool preauth_flag = false;
 
 static bool amroot;
-static int timeout;
+static unsigned int timeout;
 
 /*
  * External identifiers.
@@ -128,13 +124,20 @@ extern char **environ;
 /* local function prototypes */
 static void usage (void);
 static void setup_tty (void);
-static void process_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);
@@ -165,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);
+       }
 }
 
 
@@ -195,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;
 
@@ -209,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
@@ -217,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');
@@ -234,11 +267,11 @@ 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 */
@@ -316,6 +349,7 @@ static void process_flags (int argc, char *const *argv)
         *  Get the user name.
         */
        if (optind < argc) {
+               assert (NULL == username);
                username = xstrdup (argv[optind]);
                strzero (argv[optind]);
                ++optind;
@@ -382,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
@@ -406,6 +516,7 @@ static RETSIGTYPE alarm_handler (unused int sig)
  */
 int main (int argc, char **argv)
 {
+       const char *tmptty;
        char tty[BUFSIZ];
 
 #ifdef RLOGIN
@@ -414,29 +525,25 @@ int main (int argc, char **argv)
 #if defined(HAVE_STRFTIME) && !defined(USE_PAM)
        char ptime[80];
 #endif
-       int delay;
-       int retries;
-       bool failed;
+       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;
-#ifndef USE_PAM
-       static char temp_pw[2];
-       static char temp_shell[] = "/bin/sh";
-#endif
+       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
@@ -455,12 +562,18 @@ int main (int argc, char **argv)
        amroot = (getuid () == 0);
        Prog = Basename (argv[0]);
 
+       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
@@ -468,51 +581,29 @@ 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
-                */
-               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;
        }
@@ -521,8 +612,10 @@ int main (int argc, char **argv)
        }
 #ifdef RLOGIN
        if (rflg) {
-               username = malloc (32 * sizeof (char));
-               if (do_rlogin (hostname, username, 32, term, sizeof term)) {
+               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);
@@ -536,7 +629,7 @@ int main (int argc, char **argv)
        setup_tty ();
 
 #ifndef USE_PAM
-       umask (getdef_num ("UMASK", GETDEF_DEFAULT_UMASK));
+       (void) umask (getdef_num ("UMASK", GETDEF_DEFAULT_UMASK));
 
        {
                /* 
@@ -587,22 +680,12 @@ int main (int argc, char **argv)
 
        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 {
-               /* 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 = "";
-               }
+               cp = "";
        }
 
        if ('\0' != *cp) {
@@ -616,14 +699,14 @@ int main (int argc, char **argv)
       top:
        /* only allow ALARM sec. for login */
        (void) signal (SIGALRM, alarm_handler);
-       timeout = getdef_num ("LOGIN_TIMEOUT", ALARM);
+       timeout = getdef_unum ("LOGIN_TIMEOUT", ALARM);
        if (timeout > 0) {
-               alarm (timeout);
+               (void) alarm (timeout);
        }
 
        environ = newenvp;      /* make new environment active */
-       delay = getdef_num ("FAIL_DELAY", 1);
-       retries = getdef_num ("LOGIN_RETRIES", RETRIES);
+       delay   = getdef_unum ("FAIL_DELAY", 1);
+       retries = getdef_unum ("LOGIN_RETRIES", RETRIES);
 
 #ifdef USE_PAM
        retcode = pam_start ("login", username, &conv, &pamh);
@@ -640,6 +723,9 @@ int main (int argc, char **argv)
         * 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;
@@ -650,8 +736,8 @@ int main (int argc, char **argv)
        PAM_FAIL_CHECK;
 #endif
        /* if fflg, then the user has already been authenticated */
-       if (!fflg || (getuid () != 0)) {
-               int failcount = 0;
+       if (!fflg) {
+               unsigned int failcount = 0;
                char hostn[256];
                char loginprompt[256];  /* That's one hell of a prompt :) */
 
@@ -661,8 +747,8 @@ int main (int argc, char **argv)
                                  sizeof (loginprompt),
                                  _("%s login: "), hostn);
                } else {
-                       snprintf (loginprompt,
-                                 sizeof (loginprompt), _("login: "));
+                       strncpy (loginprompt, _("login: "),
+                                sizeof (loginprompt));
                }
 
                retcode = pam_set_item (pamh, PAM_USER_PROMPT, loginprompt);
@@ -670,9 +756,8 @@ int main (int argc, char **argv)
 
                /* if we didn't get a user on the command line,
                   set it to NULL */
-               retcode = pam_get_item (pamh, PAM_USER, (const void **)ptr_pam_user);
-               PAM_FAIL_CHECK;
-               if (pam_user[0] == '\0') {
+               get_pam_user (&pam_user);
+               if ((NULL != pam_user) && ('\0' == pam_user[0])) {
                        retcode = pam_set_item (pamh, PAM_USER, NULL);
                        PAM_FAIL_CHECK;
                }
@@ -687,8 +772,7 @@ int main (int argc, char **argv)
                 */
                failcount = 0;
                while (true) {
-                       const char *failent_user;
-                       failed = false;
+                       bool failed = false;
 
                        failcount++;
 #ifdef HAS_PAM_FAIL_DELAY
@@ -700,49 +784,26 @@ int main (int argc, char **argv)
 
                        retcode = pam_authenticate (pamh, 0);
 
-                       {
-                               int saved_retcode = retcode;
-                               retcode = pam_get_item (pamh, PAM_USER,
-                                                       (const void **) ptr_pam_user);
-                               PAM_FAIL_CHECK;
-                               retcode = saved_retcode;
-                       }
-
-                       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";
-                       }
+                       get_pam_user (&pam_user);
+                       failent_user = get_failent_user (pam_user);
 
                        if (retcode == PAM_MAXTRIES) {
                                SYSLOG ((LOG_NOTICE,
-                                        "TOO MANY LOGIN TRIES (%d)%s FOR '%s'",
+                                        "TOO MANY LOGIN TRIES (%u)%s FOR '%s'",
                                         failcount, fromhost, failent_user));
                                fprintf(stderr,
-                                       _("Maximum number of tries exceeded (%d)\n"),
+                                       _("Maximum number of tries exceeded (%u)\n"),
                                        failcount);
                                PAM_END;
                                exit(0);
                        } else if (retcode == PAM_ABORT) {
                                /* Serious problems, quit now */
-                               fputs (_("login: abort requested by PAM\n"),stderr);
+                               (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 (%d)%s FOR '%s', %s",
+                               SYSLOG ((LOG_NOTICE,"FAILED LOGIN (%u)%s FOR '%s', %s",
                                         failcount, fromhost, failent_user,
                                         pam_strerror (pamh, retcode)));
                                failed = true;
@@ -767,14 +828,15 @@ int main (int argc, char **argv)
                        close (audit_fd);
 #endif                         /* WITH_AUDIT */
 
-                       fprintf (stderr, "\nLogin incorrect\n");
+                       (void) puts ("");
+                       (void) puts (_("Login incorrect"));
 
                        if (failcount >= retries) {
                                SYSLOG ((LOG_NOTICE,
-                                        "TOO MANY LOGIN TRIES (%d)%s FOR '%s'",
+                                        "TOO MANY LOGIN TRIES (%u)%s FOR '%s'",
                                         failcount, fromhost, failent_user));
                                fprintf(stderr,
-                                       _("Maximum number of tries exceeded (%d)\n"),
+                                       _("Maximum number of tries exceeded (%u)\n"),
                                        failcount);
                                PAM_END;
                                exit(0);
@@ -790,53 +852,74 @@ int main (int argc, char **argv)
                }
 
                /* 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) alarm (0);
+       }
 
-               PAM_FAIL_CHECK;
+       /* 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.
+        * 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.
         */
-       retcode = pam_get_item (pamh, PAM_USER, (const void **)ptr_pam_user);
-       PAM_FAIL_CHECK;
+       get_pam_user (&pam_user);
        if (NULL != username) {
                free (username);
        }
-       username = xstrdup (pam_user);
+       username = pam_user;
+       failent_user = get_failent_user (username);
 
        pwd = xgetpwnam (username);
        if (NULL == pwd) {
-               SYSLOG ((LOG_ERR, "xgetpwnam(%s) failed",
-                        getdef_bool ("LOG_UNKFAIL_ENAB") ?
-                        username : "UNKNOWN"));
+               SYSLOG ((LOG_ERR, "cannot find user %s", failent_user));
                exit (1);
        }
 
-       if (fflg) {
-               retcode = pam_acct_mgmt (pamh, 0);
-               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);
        }
 
-       pwent = *pwd;
-
        retcode = pam_setcred (pamh, PAM_ESTABLISH_CRED);
        PAM_FAIL_CHECK;
-
-       retcode = pam_open_session (pamh, hushed (&pwent) ? PAM_SILENT : 0);
-       PAM_FAIL_CHECK;
+       /* 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) {
@@ -844,54 +927,53 @@ int main (int argc, char **argv)
                                exit (1);
                        }
                        preauth_flag = false;
-                       username = malloc (32);
-                       login_prompt (_("\n%s login: "), username, 32);
+                       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) {
+                       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) {
-                       pwent.pw_name = username;
-                       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;
+                       }
                }
 
-               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));
                        }
                }
 
-               /*
-                * 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.
@@ -900,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));
+                        failent_user, fromhost));
                failed = true;
 
              auth_ok:
@@ -923,21 +997,21 @@ 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)) {
+                   && !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));
@@ -949,40 +1023,24 @@ 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);
                }
 
                retries--;
@@ -998,7 +1056,7 @@ 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);
                }
 
@@ -1015,10 +1073,10 @@ int main (int argc, char **argv)
                 * 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)) {
@@ -1027,8 +1085,10 @@ int main (int argc, char **argv)
                }
        }                       /* while (true) */
 #endif                         /* ! USE_PAM */
+       assert (NULL != username);
+       assert (NULL != pwd);
 
-       alarm (0);              /* turn off alarm clock */
+       (void) alarm (0);               /* turn off alarm clock */
 
 #ifndef USE_PAM                        /* PAM does this */
        /*
@@ -1037,7 +1097,7 @@ 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))) {
+           && !isttytime (username, tty, time ((time_t *) 0))) {
                SYSLOG ((LOG_WARN, "invalid login time for '%s'%s",
                         username, fromhost));
                closelog ();
@@ -1045,17 +1105,16 @@ int main (int argc, char **argv)
                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 */
        }
 
-       setutmp (username, tty, hostname);      /* make entry in utmp & wtmp files */
-       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 */
@@ -1072,7 +1131,7 @@ int main (int argc, char **argv)
                                AUDIT_USER_LOGIN,
                                NULL,    /* Prog. name */
                                "login",
-                               pwd->pw_name,
+                               username,
                                AUDIT_NO_ID,
                                hostname,
                                NULL,    /* addr */
@@ -1083,31 +1142,38 @@ int main (int argc, char **argv)
 
 #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 (&pwent);
+       chown_tty (pwd);
 
 #ifdef USE_PAM
        /*
@@ -1133,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
        {
@@ -1169,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
@@ -1183,32 +1270,30 @@ 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));
                        }
                }
                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");
@@ -1221,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 */
@@ -1232,13 +1314,7 @@ 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")) {
                SYSLOG ((LOG_INFO, "'%s' logged in %s", username, fromhost));
@@ -1246,13 +1322,12 @@ int main (int argc, char **argv)
        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);
 }