Linux – Restoring login account blocked by inactivity

linuxuser-accounts

Login accounts have an inactivity timer set at 35 days. That is, /etc/pam.d/passwd-auth has a line

auth        required      pam_lastlog.so inactive=35

So I get lines like

sshd[29629]: pam_unix(sshd:account): account weaverw has expired (failed to change password)
sshd[29629]: pam_lastlog(sshd:account): user weaverw inactive for 71 days - denied

Question: what is the best way to re-activate these accounts? What the SA's have been doing is su over to the account, then exit, so it will have an entry in lastlog, but this seems inelegant.

Best Answer

To fix the pam_unix password expiration, edit /etc/shadow. It has the format [colon separated fields]:

username:passwd:number_of_days_since_pw_changed:...

and set the 3rd field to zero.


To fix the pam_lastlog it's a bit uglier. The control file is /var/log/lastlog. It's a compressed and/or binary format file.

It can be viewed with the lastlog utility. But [AFAICT] the utility provides no mechanism to change an individual entry.

It seems the recommended method is to null out the file. While this changes it for all users, this is less severe than the passwd expiry changes. cp /dev/null /var/log/lastlog will do this without disturbing the selinux permissions.

The usermod utility will reset the information for a single user but only when using the -u option to change the user's uid. Perhaps, used in conjunction with -o:

usermod -o -u <current_uid_of_user> <username>

At worst, do it in two commands, first setting uid to new unique uid, then set it back to the old one. For example, if the user's uid was 5001 and there was no uid 5500 in use, do:

usermod -u 5500 fred
usermod -u 5001 fred

If you really want to preserve most information in /var/log/lastlog and the above doesn't work, the shadow-utils source package has a way to do it ...

Here's the source snippet from useradd:

static void lastlog_reset (uid_t uid)
{
    struct lastlog ll;
    int fd;
    off_t offset_uid = (off_t) (sizeof ll) * uid;

    if (access (LASTLOG_FILE, F_OK) != 0) {
        return;
    }

    memzero (&ll, sizeof (ll));

    fd = open (LASTLOG_FILE, O_RDWR);
    if (   (-1 == fd)
        || (lseek (fd, offset_uid, SEEK_SET) != offset_uid)
        || (write (fd, &ll, sizeof (ll)) != (ssize_t) sizeof (ll))
        || (fsync (fd) != 0)
        || (close (fd) != 0)) {
        fprintf (stderr,
                 _("%s: failed to reset the lastlog entry of UID %lu: %s\n"),
                 Prog, (unsigned long) uid, strerror (errno));
        SYSLOG ((LOG_WARN, "failed to reset the lastlog entry of UID %lu", (unsigned long) uid));
        /* continue */
    }
}

Here's a snippet from usermod:

/*
 * update_lastlog - update the lastlog file
 *
 * Relocate the "lastlog" entries for the user. The old entry is
 * left alone in case the UID was shared. It doesn't hurt anything
 * to just leave it be.
 */
static void update_lastlog (void)
{
    struct lastlog ll;
    int fd;
    off_t off_uid = (off_t) user_id * sizeof ll;
    off_t off_newuid = (off_t) user_newid * sizeof ll;

    if (access (LASTLOG_FILE, F_OK) != 0) {
        return;
    }

    fd = open (LASTLOG_FILE, O_RDWR);

    if (-1 == fd) {
        fprintf (stderr,
                 _("%s: failed to copy the lastlog entry of user %lu to user %lu: %s\n"),
                 Prog, (unsigned long) user_id, (unsigned long) user_newid, strerror (errno));
        return;
    }

    if (   (lseek (fd, off_uid, SEEK_SET) == off_uid)
        && (read (fd, &ll, sizeof ll) == (ssize_t) sizeof ll)) {
        /* Copy the old entry to its new location */
        if (   (lseek (fd, off_newuid, SEEK_SET) != off_newuid)
            || (write (fd, &ll, sizeof ll) != (ssize_t) sizeof ll)
            || (fsync (fd) != 0)
            || (close (fd) != 0)) {
            fprintf (stderr,
                     _("%s: failed to copy the lastlog entry of user %lu to user %lu: %s\n"),
                     Prog, (unsigned long) user_id, (unsigned long) user_newid, strerror (errno));
        }
    } else {
        /* Assume lseek or read failed because there is
         * no entry for the old UID */

        /* Check if the new UID already has an entry */
        if (   (lseek (fd, off_newuid, SEEK_SET) == off_newuid)
            && (read (fd, &ll, sizeof ll) == (ssize_t) sizeof ll)) {
            /* Reset the new uid's lastlog entry */
            memzero (&ll, sizeof (ll));
            if (   (lseek (fd, off_newuid, SEEK_SET) != off_newuid)
                || (write (fd, &ll, sizeof ll) != (ssize_t) sizeof ll)
                || (fsync (fd) != 0)
                || (close (fd) != 0)) {
                fprintf (stderr,
                         _("%s: failed to copy the lastlog entry of user %lu to user %lu: %s\n"),
                         Prog, (unsigned long) user_id, (unsigned long) user_newid, strerror (errno));
            }
        } else {
            (void) close (fd);
        }
    }
}

The definition for struct lastlog comes from #include <lastlog.h> and will look like:

/* The structure describing an entry in the database of
   previous logins.  */
struct lastlog
  {
#ifdef __WORDSIZE_TIME64_COMPAT32
    int32_t ll_time;
#else
    __time_t ll_time;
#endif
    char ll_line[UT_LINESIZE];
    char ll_host[UT_HOSTSIZE];
  };