]> git.ipfire.org Git - thirdparty/util-linux.git/blobdiff - login-utils/last.c
last: fix wtmp user name buffer overflow [asan]
[thirdparty/util-linux.git] / login-utils / last.c
index 84ae264071d808423580be3bf882bb53fd6c9bfc..be744b079198ecf11ba05eed990c5f942bca18c0 100644 (file)
  */
 #include <sys/types.h>
 #include <sys/stat.h>
-#include <sys/fcntl.h>
+#include <fcntl.h>
 #include <time.h>
 #include <stdio.h>
 #include <ctype.h>
-#include <utmp.h>
+#include <utmpx.h>
+#include <pwd.h>
 #include <stdlib.h>
 #include <unistd.h>
 #include <string.h>
 #include <netinet/in.h>
 #include <netdb.h>
 #include <arpa/inet.h>
+#include <libgen.h>
 
 #include "c.h"
 #include "nls.h"
+#include "optutils.h"
 #include "pathnames.h"
 #include "xalloc.h"
 #include "closestream.h"
 #include "carefulputc.h"
 #include "strutils.h"
-#include "time-util.h"
+#include "timeutils.h"
+#include "monotonic.h"
 
 #ifndef SHUTDOWN_TIME
 # define SHUTDOWN_TIME 254
 # define LAST_DOMAIN_LEN 16
 #endif
 
+#ifndef LAST_TIMESTAMP_LEN
+# define LAST_TIMESTAMP_LEN 32
+#endif
+
 #define UCHUNKSIZE     16384   /* How much we read at once. */
 
 struct last_control {
-       char lastb;             /* Is this command 'lastb' */
-       char extended;          /* Lots of info */
-       char showhost;          /* Show hostname */
-       char altlist;           /* Hostname at the end */
-       char usedns;            /* Use DNS to lookup the hostname */
-       char useip;             /* Print IP address in number format */
-       char fulltime;          /* Print full dates and times */
+       unsigned int lastb :1,    /* Is this command 'lastb' */
+                    extended :1, /* Lots of info */
+                    showhost :1, /* Show hostname */
+                    altlist :1,  /* Hostname at the end */
+                    usedns :1,   /* Use DNS to lookup the hostname */
+                    useip :1;    /* Print IP address in number format */
 
        unsigned int name_len;  /* Number of login name characters to print */
        unsigned int domain_len; /* Number of domain name characters to print */
@@ -78,22 +85,19 @@ struct last_control {
 
        char **show;            /* Match search list */
 
-       char **altv;            /* Alternate wtmp files */
-       unsigned int altc;      /* Number of alternative files */
-       unsigned int alti;      /* Index number of the alternative file */
-
+       struct timeval boot_time; /* system boot time */
        time_t since;           /* at what time to start displaying the file */
        time_t until;           /* at what time to stop displaying the file */
        time_t present;         /* who where present at time_t */
+       unsigned int time_fmt;  /* time format */
 };
 
 /* Double linked list of struct utmp's */
 struct utmplist {
-       struct utmp ut;
+       struct utmpx ut;
        struct utmplist *next;
        struct utmplist *prev;
 };
-struct utmplist *utmplist = NULL;
 
 /* Types of listing */
 enum {
@@ -106,16 +110,70 @@ enum {
        R_TIMECHANGE    /* NEW_TIME or OLD_TIME */
 };
 
+enum {
+       LAST_TIMEFTM_NONE = 0,
+       LAST_TIMEFTM_SHORT,
+       LAST_TIMEFTM_CTIME,
+       LAST_TIMEFTM_ISO8601,
+
+       LAST_TIMEFTM_HHMM,      /* non-public */
+};
+
+struct last_timefmt {
+       const char *name;
+       int in_len;     /* log-in */
+       int in_fmt;
+       int out_len;    /* log-out */
+       int out_fmt;
+};
+
+static struct last_timefmt timefmts[] = {
+       [LAST_TIMEFTM_NONE] = { .name = "notime" },
+       [LAST_TIMEFTM_SHORT] = {
+               .name    = "short",
+               .in_len  = 16,
+               .out_len = 7,
+               .in_fmt  = LAST_TIMEFTM_CTIME,
+               .out_fmt = LAST_TIMEFTM_HHMM
+       },
+       [LAST_TIMEFTM_CTIME] = {
+               .name    = "full",
+               .in_len  = 24,
+               .out_len = 26,
+               .in_fmt  = LAST_TIMEFTM_CTIME,
+               .out_fmt = LAST_TIMEFTM_CTIME
+       },
+       [LAST_TIMEFTM_ISO8601] = {
+               .name    = "iso",
+               .in_len  = 25,
+               .out_len = 27,
+               .in_fmt  = LAST_TIMEFTM_ISO8601,
+               .out_fmt = LAST_TIMEFTM_ISO8601
+       }
+};
+
 /* Global variables */
 static unsigned int recsdone;  /* Number of records listed */
 static time_t lastdate;                /* Last date we've seen */
+static time_t currentdate;     /* date when we started processing the file */
+
+/* --time-format=option parser */
+static int which_time_format(const char *s)
+{
+       size_t i;
+
+       for (i = 0; i < ARRAY_SIZE(timefmts); i++) {
+               if (strcmp(timefmts[i].name, s) == 0)
+                       return i;
+       }
+       errx(EXIT_FAILURE, _("unknown time format: %s"), s);
+}
 
 /*
  *     Read one utmp entry, return in new format.
  *     Automatically reposition file pointer.
  */
-static int uread(const struct last_control *ctl, FILE *fp, struct utmp *u,
-                int *quit)
+static int uread(FILE *fp, struct utmpx *u,  int *quit, const char *filename)
 {
        static int utsize;
        static char buf[UCHUNKSIZE];
@@ -128,26 +186,26 @@ static int uread(const struct last_control *ctl, FILE *fp, struct utmp *u,
                /*
                 *      Normal read.
                 */
-               return fread(u, sizeof(struct utmp), 1, fp);
+               return fread(u, sizeof(struct utmpx), 1, fp);
        }
 
        if (u == NULL) {
                /*
                 *      Initialize and position.
                 */
-               utsize = sizeof(struct utmp);
+               utsize = sizeof(struct utmpx);
                fseeko(fp, 0, SEEK_END);
                fpos = ftello(fp);
                if (fpos == 0)
                        return 0;
                o = ((fpos - 1) / UCHUNKSIZE) * UCHUNKSIZE;
                if (fseeko(fp, o, SEEK_SET) < 0) {
-                       warn(_("seek failed: %s"), ctl->altv[ctl->alti]);
+                       warn(_("seek on %s failed"), filename);
                        return 0;
                }
                bpos = (int)(fpos - o);
                if (fread(buf, bpos, 1, fp) != 1) {
-                       warn(_("read failed: %s"), ctl->altv[ctl->alti]);
+                       warn(_("cannot read %s"), filename);
                        return 0;
                }
                fpos = o;
@@ -159,7 +217,7 @@ static int uread(const struct last_control *ctl, FILE *fp, struct utmp *u,
         */
        bpos -= utsize;
        if (bpos >= 0) {
-               memcpy(u, buf + bpos, sizeof(struct utmp));
+               memcpy(u, buf + bpos, sizeof(struct utmpx));
                return 1;
        }
 
@@ -176,7 +234,7 @@ static int uread(const struct last_control *ctl, FILE *fp, struct utmp *u,
         */
        memcpy(tmp + (-bpos), buf, utsize + bpos);
        if (fseeko(fp, fpos, SEEK_SET) < 0) {
-               warn(_("seek failed: %s"), ctl->altv[ctl->alti]);
+               warn(_("seek on %s failed"), filename);
                return 0;
        }
 
@@ -184,7 +242,7 @@ static int uread(const struct last_control *ctl, FILE *fp, struct utmp *u,
         *      Read another UCHUNKSIZE bytes.
         */
        if (fread(buf, UCHUNKSIZE, 1, fp) != 1) {
-               warn(_("read failed: %s"), ctl->altv[ctl->alti]);
+               warn(_("cannot read %s"), filename);
                return 0;
        }
 
@@ -195,7 +253,7 @@ static int uread(const struct last_control *ctl, FILE *fp, struct utmp *u,
        memcpy(tmp, buf + UCHUNKSIZE + bpos, -bpos);
        bpos += UCHUNKSIZE;
 
-       memcpy(u, tmp, sizeof(struct utmp));
+       memcpy(u, tmp, sizeof(struct utmpx));
 
        return 1;
 }
@@ -271,39 +329,85 @@ static int dns_lookup(char *result, int size, int useip, int32_t *a)
        return getnameinfo(sa, salen, result, size, NULL, 0, flags);
 }
 
+static int time_formatter(int fmt, char *dst, size_t dlen, time_t *when)
+{
+       int ret = 0;
+
+       switch (fmt) {
+       case LAST_TIMEFTM_NONE:
+               *dst = 0;
+               break;
+       case LAST_TIMEFTM_HHMM:
+       {
+               struct tm *tm = localtime(when);
+               if (!snprintf(dst, dlen, "%02d:%02d", tm->tm_hour, tm->tm_min))
+                       ret = -1;
+               break;
+       }
+       case LAST_TIMEFTM_CTIME:
+               snprintf(dst, dlen, "%s", ctime(when));
+               ret = rtrim_whitespace((unsigned char *) dst);
+               break;
+       case LAST_TIMEFTM_ISO8601:
+               ret = strtime_iso(when, ISO_TIMESTAMP_T, dst, dlen);
+               break;
+       default:
+               abort();
+       }
+       return ret;
+}
+
+/*
+ *     Remove trailing spaces from a string.
+ */
+static void trim_trailing_spaces(char *s)
+{
+       char *p;
+
+       for (p = s; *p; ++p)
+               continue;
+       while (p > s && isspace(*--p))
+               continue;
+       if (p > s)
+               ++p;
+       *p++ = '\n';
+       *p = '\0';
+}
+
 /*
  *     Show one line of information on screen
  */
-static int list(const struct last_control *ctl, struct utmp *p, time_t t, int what)
+static int list(const struct last_control *ctl, struct utmpx *p, time_t logout_time, int what)
 {
-       time_t          secs, tmp;
-       char            logintime[32];
-       char            logouttime[32];
-       char            length[32];
+       time_t          secs, utmp_time;
+       char            logintime[LAST_TIMESTAMP_LEN];
+       char            logouttime[LAST_TIMESTAMP_LEN];
+       char            length[LAST_TIMESTAMP_LEN];
        char            final[512];
-       char            utline[UT_LINESIZE+1];
+       char            utline[sizeof(p->ut_line) + 1];
        char            domain[256];
        char            *s;
        int             mins, hours, days;
        int             r, len;
+       struct last_timefmt *fmt;
 
        /*
         *      uucp and ftp have special-type entries
         */
        utline[0] = 0;
-       strncat(utline, p->ut_line, UT_LINESIZE);
+       strncat(utline, p->ut_line, sizeof(utline) - 1);
        if (strncmp(utline, "ftp", 3) == 0 && isdigit(utline[3]))
                utline[3] = 0;
        if (strncmp(utline, "uucp", 4) == 0 && isdigit(utline[4]))
                utline[4] = 0;
 
        /*
-        *      Is this something we wanna show?
+        *      Is this something we want to show?
         */
        if (ctl->show) {
                char **walk;
                for (walk = ctl->show; *walk; walk++) {
-                       if (strncmp(p->ut_name, *walk, UT_NAMESIZE) == 0 ||
+                       if (strncmp(p->ut_user, *walk, sizeof(p->ut_user)) == 0 ||
                            strcmp(utline, *walk) == 0 ||
                            (strncmp(utline, "tty", 3) == 0 &&
                             strcmp(utline + 3, *walk) == 0)) break;
@@ -314,27 +418,50 @@ static int list(const struct last_control *ctl, struct utmp *p, time_t t, int wh
        /*
         *      Calculate times
         */
-       tmp = (time_t)p->ut_time;
+       fmt = &timefmts[ctl->time_fmt];
 
-       if (ctl->present && (ctl->present < tmp || (0 < t && t < ctl->present)))
-               return 0;
+       utmp_time = p->ut_tv.tv_sec;
 
-       strcpy(logintime, ctime(&tmp));
-       if (ctl->fulltime)
-               sprintf(logouttime, "- %s", ctime(&t));
-       else {
-               logintime[16] = 0;
-               sprintf(logouttime, "- %s", ctime(&t) + 11);
-               logouttime[7] = 0;
+       if (ctl->present) {
+               if (ctl->present < utmp_time)
+                       return 0;
+               if (0 < logout_time && logout_time < ctl->present)
+                       return 0;
        }
-       secs = t - p->ut_time;
+
+       /* log-in time */
+       if (time_formatter(fmt->in_fmt, logintime,
+                          sizeof(logintime), &utmp_time) < 0)
+               errx(EXIT_FAILURE, _("preallocation size exceeded"));
+
+       /* log-out time */
+       secs  = logout_time - utmp_time; /* Under strange circumstances, secs < 0 can happen */
        mins  = (secs / 60) % 60;
        hours = (secs / 3600) % 24;
        days  = secs / 86400;
-       if (days)
-               sprintf(length, "(%d+%02d:%02d)", days, hours, mins);
-       else
-               sprintf(length, " (%02d:%02d)", hours, mins);
+
+       strcpy(logouttime, "- ");
+       if (time_formatter(fmt->out_fmt, logouttime + 2,
+                          sizeof(logouttime) - 2, &logout_time) < 0)
+               errx(EXIT_FAILURE, _("preallocation size exceeded"));
+
+       if (logout_time == currentdate) {
+               if (ctl->time_fmt > LAST_TIMEFTM_SHORT) {
+                       sprintf(logouttime, "  still running");
+                       length[0] = 0;
+               } else {
+                       sprintf(logouttime, "  still");
+                       sprintf(length, "running");
+               }
+       } else if (days) {
+               sprintf(length, "(%d+%02d:%02d)", days, abs(hours), abs(mins)); /* hours and mins always shown as positive (w/o minus sign!) even if secs < 0 */
+       } else if (hours) {
+               sprintf(length, " (%02d:%02d)", hours, abs(mins));  /* mins always shown as positive (w/o minus sign!) even if secs < 0 */
+       } else if (secs >= 0) {
+               sprintf(length, " (%02d:%02d)", hours, mins); 
+       } else {
+               sprintf(length, " (-00:%02d)", abs(mins));  /* mins always shown as positive (w/o minus sign!) even if secs < 0 */
+       }
 
        switch(what) {
                case R_CRASH:
@@ -344,30 +471,32 @@ static int list(const struct last_control *ctl, struct utmp *p, time_t t, int wh
                        sprintf(logouttime, "- down ");
                        break;
                case R_NOW:
-                       length[0] = 0;
-                       if (ctl->fulltime)
+                       if (ctl->time_fmt > LAST_TIMEFTM_SHORT) {
                                sprintf(logouttime, "  still logged in");
-                       else {
+                               length[0] = 0;
+                       } else {
                                sprintf(logouttime, "  still");
                                sprintf(length, "logged in");
                        }
                        break;
                case R_PHANTOM:
-                       length[0] = 0;
-                       if (ctl->fulltime)
+                       if (ctl->time_fmt > LAST_TIMEFTM_SHORT) {
                                sprintf(logouttime, "  gone - no logout");
-                       else {
+                               length[0] = 0;
+                       } else if (ctl->time_fmt == LAST_TIMEFTM_SHORT) {
                                sprintf(logouttime, "   gone");
                                sprintf(length, "- no logout");
+                       } else {
+                               logouttime[0] = 0;
+                               sprintf(length, "no logout");
                        }
                        break;
-               case R_REBOOT:
-                       break;
                case R_TIMECHANGE:
                        logouttime[0] = 0;
                        length[0] = 0;
                        break;
                case R_NORMAL:
+               case R_REBOOT:
                        break;
                default:
                        abort();
@@ -378,37 +507,31 @@ static int list(const struct last_control *ctl, struct utmp *p, time_t t, int wh
         */
        r = -1;
        if (ctl->usedns || ctl->useip)
-               r = dns_lookup(domain, sizeof(domain), ctl->useip, p->ut_addr_v6);
-       if (r < 0) {
-               len = UT_HOSTSIZE;
-               if (len >= (int)sizeof(domain)) len = sizeof(domain) - 1;
-               domain[0] = 0;
-               strncat(domain, p->ut_host, len);
-       }
+               r = dns_lookup(domain, sizeof(domain), ctl->useip, (int32_t*)p->ut_addr_v6);
+       if (r < 0)
+               mem2strcpy(domain, p->ut_host, sizeof(p->ut_host), sizeof(domain));
 
        if (ctl->showhost) {
                if (!ctl->altlist) {
                        len = snprintf(final, sizeof(final),
-                               ctl->fulltime ?
-                               "%-8.*s %-12.12s %-16.*s %-24.24s %-26.26s %-12.12s\n" :
-                               "%-8.*s %-12.12s %-16.*s %-16.16s %-7.7s %-12.12s\n",
-                               ctl->name_len, p->ut_name, utline,
-                               ctl->domain_len, domain, logintime, logouttime, length);
+                               "%-8.*s %-12.12s %-16.*s %-*.*s %-*.*s %s\n",
+                               ctl->name_len, p->ut_user, utline,
+                               ctl->domain_len, domain,
+                               fmt->in_len, fmt->in_len, logintime, fmt->out_len, fmt->out_len,
+                               logouttime, length);
                } else {
                        len = snprintf(final, sizeof(final),
-                               ctl->fulltime ?
-                               "%-8.*s %-12.12s %-24.24s %-26.26s %-12.12s %s\n" :
-                               "%-8.*s %-12.12s %-16.16s %-7.7s %-12.12s %s\n",
-                               ctl->name_len, p->ut_name, utline,
-                               logintime, logouttime, length, domain);
+                               "%-8.*s %-12.12s %-*.*s %-*.*s %-12.12s %s\n",
+                               ctl->name_len, p->ut_user, utline,
+                               fmt->in_len, fmt->in_len, logintime, fmt->out_len, fmt->out_len,
+                               logouttime, length, domain);
                }
        } else
                len = snprintf(final, sizeof(final),
-                       ctl->fulltime ?
-                       "%-8.*s %-12.12s %-24.24s %-26.26s %-12.12s\n" :
-                       "%-8.*s %-12.12s %-16.16s %-7.7s %-12.12s\n",
-                       ctl->name_len, p->ut_name, utline,
-                       logintime, logouttime, length);
+                       "%-8.*s %-12.12s %-*.*s %-*.*s %s\n",
+                       ctl->name_len, p->ut_user, utline,
+                       fmt->in_len, fmt->in_len, logintime, fmt->out_len, fmt->out_len,
+                       logouttime, length);
 
 #if defined(__GLIBC__)
 #  if (__GLIBC__ == 2) && (__GLIBC_MINOR__ == 0)
@@ -416,11 +539,12 @@ static int list(const struct last_control *ctl, struct utmp *p, time_t t, int wh
 #  endif
 #endif
 
+       trim_trailing_spaces(final);
        /*
         *      Print out "final" string safely.
         */
        for (s = final; *s; s++)
-               carefulputc(*s, stdout, '*');
+               fputc_careful(*s, stdout, '*');
 
        if (len < 0 || (size_t)len >= sizeof(final))
                putchar('\n');
@@ -433,42 +557,84 @@ static int list(const struct last_control *ctl, struct utmp *p, time_t t, int wh
 }
 
 
-static void __attribute__((__noreturn__)) usage(FILE *out)
+static void __attribute__((__noreturn__)) usage(const struct last_control *ctl)
 {
+       FILE *out = stdout;
        fputs(USAGE_HEADER, out);
        fprintf(out, _(
                " %s [options] [<username>...] [<tty>...]\n"), program_invocation_short_name);
 
+       fputs(USAGE_SEPARATOR, out);
+       fputs(_("Show a listing of last logged in users.\n"), out);
+
        fputs(USAGE_OPTIONS, out);
        fputs(_(" -<number>            how many lines to show\n"), out);
        fputs(_(" -a, --hostlast       display hostnames in the last column\n"), out);
        fputs(_(" -d, --dns            translate the IP number back into a hostname\n"), out);
        fprintf(out,
-             _(" -f, --file <file>    use a specific file instead of %s\n"), _PATH_WTMP);
+             _(" -f, --file <file>    use a specific file instead of %s\n"), ctl->lastb ? _PATH_BTMP : _PATH_WTMP);
        fputs(_(" -F, --fulltimes      print full login and logout times and dates\n"), out);
        fputs(_(" -i, --ip             display IP numbers in numbers-and-dots notation\n"), out);
        fputs(_(" -n, --limit <number> how many lines to show\n"), out);
        fputs(_(" -R, --nohostname     don't display the hostname field\n"), out);
        fputs(_(" -s, --since <time>   display the lines since the specified time\n"), out);
        fputs(_(" -t, --until <time>   display the lines until the specified time\n"), out);
-       fputs(_(" -p, --present <time> display who where present at the specified time\n"), out);
+       fputs(_(" -p, --present <time> display who were present at the specified time\n"), out);
        fputs(_(" -w, --fullnames      display full user and domain names\n"), out);
        fputs(_(" -x, --system         display system shutdown entries and run level changes\n"), out);
+       fputs(_("     --time-format <format>  show timestamps in the specified <format>:\n"
+               "                               notime|short|full|iso\n"), out);
 
        fputs(USAGE_SEPARATOR, out);
-       fputs(USAGE_HELP, out);
-       fputs(USAGE_VERSION, out);
-       fprintf(out, USAGE_MAN_TAIL("last(1)"));
+       printf(USAGE_HELP_OPTIONS(22));
+       printf(USAGE_MAN_TAIL("last(1)"));
 
        exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
 }
 
+static int is_phantom(const struct last_control *ctl, struct utmpx *ut)
+{
+       struct passwd *pw;
+       char path[sizeof(ut->ut_line) + 16];
+       int ret = 0;
+
+       if (ut->ut_tv.tv_sec < ctl->boot_time.tv_sec)
+               return 1;
+       ut->ut_user[__UT_NAMESIZE - 1] = '\0';
+       pw = getpwnam(ut->ut_user);
+       if (!pw)
+               return 1;
+       sprintf(path, "/proc/%u/loginuid", ut->ut_pid);
+       if (access(path, R_OK) == 0) {
+               unsigned int loginuid;
+               FILE *f = NULL;
+
+               if (!(f = fopen(path, "r")))
+                       return 1;
+               if (fscanf(f, "%u", &loginuid) != 1)
+                       ret = 1;
+               fclose(f);
+               if (!ret && pw->pw_uid != loginuid)
+                       return 1;
+       } else {
+               struct stat st;
 
-static void process_wtmp_file(const struct last_control *ctl)
+               sprintf(path, "/dev/%s", ut->ut_line);
+               if (stat(path, &st))
+                       return 1;
+               if (pw->pw_uid != st.st_uid)
+                       return 1;
+       }
+       return ret;
+}
+
+static void process_wtmp_file(const struct last_control *ctl,
+                             const char *filename)
 {
-       FILE *fp;               /* Filepointer of wtmp file */
+       FILE *fp;               /* File pointer of wtmp file */
 
-       struct utmp ut;         /* Current utmp entry */
+       struct utmpx ut;        /* Current utmp entry */
+       struct utmplist *ulist = NULL;  /* All entries */
        struct utmplist *p;     /* Pointer into utmplist */
        struct utmplist *next;  /* Pointer into utmplist */
 
@@ -484,12 +650,10 @@ static void process_wtmp_file(const struct last_control *ctl)
        int down = 0;           /* Down flag */
 
        time(&lastdown);
-       lastrch = lastdown;
-
        /*
         * Fill in 'lastdate'
         */
-       lastdate = lastdown;
+       lastdate = currentdate = lastrch = lastdown;
 
        /*
         * Install signal handlers
@@ -500,8 +664,8 @@ static void process_wtmp_file(const struct last_control *ctl)
        /*
         * Open the utmp file
         */
-       if ((fp = fopen(ctl->altv[ctl->alti], "r")) == NULL)
-               err(EXIT_FAILURE, _("cannot open %s"), ctl->altv[ctl->alti]);
+       if ((fp = fopen(filename, "r")) == NULL)
+               err(EXIT_FAILURE, _("cannot open %s"), filename);
 
        /*
         * Optimize the buffer size.
@@ -511,10 +675,11 @@ static void process_wtmp_file(const struct last_control *ctl)
        /*
         * Read first structure to capture the time field
         */
-       if (uread(ctl, fp, &ut, NULL) == 1)
-               begintime = ut.ut_time;
+       if (uread(fp, &ut, NULL, filename) == 1)
+               begintime = ut.ut_tv.tv_sec;
        else {
-               fstat(fileno(fp), &st);
+               if (fstat(fileno(fp), &st) != 0)
+                       err(EXIT_FAILURE, _("stat of %s failed"), filename);
                begintime = st.st_ctime;
                quit = 1;
        }
@@ -523,26 +688,26 @@ static void process_wtmp_file(const struct last_control *ctl)
         * Go to end of file minus one structure
         * and/or initialize utmp reading code.
         */
-       uread(ctl, fp, NULL, NULL);
+       uread(fp, NULL, NULL, filename);
 
        /*
         * Read struct after struct backwards from the file.
         */
        while (!quit) {
 
-               if (uread(ctl, fp, &ut, &quit) != 1)
+               if (uread(fp, &ut, &quit, filename) != 1)
                        break;
 
-               if (ctl->since && ut.ut_time < ctl->since)
+               if (ctl->since && ut.ut_tv.tv_sec < ctl->since)
                        continue;
 
-               if (ctl->until && ctl->until < ut.ut_time)
+               if (ctl->until && ctl->until < ut.ut_tv.tv_sec)
                        continue;
 
-               lastdate = ut.ut_time;
+               lastdate = ut.ut_tv.tv_sec;
 
                if (ctl->lastb) {
-                       quit = list(ctl, &ut, ut.ut_time, R_NORMAL);
+                       quit = list(ctl, &ut, ut.ut_tv.tv_sec, R_NORMAL);
                        continue;
                }
 
@@ -564,21 +729,21 @@ static void process_wtmp_file(const struct last_control *ctl)
                 */
                else {
                        if (ut.ut_type != DEAD_PROCESS &&
-                           ut.ut_name[0] && ut.ut_line[0] &&
-                           strcmp(ut.ut_name, "LOGIN") != 0)
+                           ut.ut_user[0] && ut.ut_line[0] &&
+                           strcmp(ut.ut_user, "LOGIN") != 0)
                                ut.ut_type = USER_PROCESS;
                        /*
                         * Even worse, applications that write ghost
                         * entries: ut_type set to USER_PROCESS but
-                        * empty ut_name...
+                        * empty ut_user...
                         */
-                       if (ut.ut_name[0] == 0)
+                       if (ut.ut_user[0] == 0)
                                ut.ut_type = DEAD_PROCESS;
 
                        /*
                         * Clock changes.
                         */
-                       if (strcmp(ut.ut_name, "date") == 0) {
+                       if (strcmp(ut.ut_user, "date") == 0) {
                                if (ut.ut_line[0] == '|')
                                        ut.ut_type = OLD_TIME;
                                if (ut.ut_line[0] == '{')
@@ -592,7 +757,7 @@ static void process_wtmp_file(const struct last_control *ctl)
                                strcpy(ut.ut_line, "system down");
                                quit = list(ctl, &ut, lastboot, R_NORMAL);
                        }
-                       lastdown = lastrch = ut.ut_time;
+                       lastdown = lastrch = ut.ut_tv.tv_sec;
                        down = 1;
                        break;
                case OLD_TIME:
@@ -607,7 +772,7 @@ static void process_wtmp_file(const struct last_control *ctl)
                case BOOT_TIME:
                        strcpy(ut.ut_line, "system boot");
                        quit = list(ctl, &ut, lastdown, R_REBOOT);
-                       lastboot = ut.ut_time;
+                       lastboot = ut.ut_tv.tv_sec;
                        down = 1;
                        break;
                case RUN_LVL:
@@ -617,11 +782,11 @@ static void process_wtmp_file(const struct last_control *ctl)
                                quit = list(ctl, &ut, lastrch, R_NORMAL);
                        }
                        if (x == '0' || x == '6') {
-                               lastdown = ut.ut_time;
+                               lastdown = ut.ut_tv.tv_sec;
                                down = 1;
                                ut.ut_type = SHUTDOWN_TIME;
                        }
-                       lastrch = ut.ut_time;
+                       lastrch = ut.ut_tv.tv_sec;
                        break;
 
                case USER_PROCESS:
@@ -631,20 +796,21 @@ static void process_wtmp_file(const struct last_control *ctl)
                         * the same ut_line.
                         */
                        c = 0;
-                       for (p = utmplist; p; p = next) {
+                       for (p = ulist; p; p = next) {
                                next = p->next;
                                if (strncmp(p->ut.ut_line, ut.ut_line,
-                                   UT_LINESIZE) == 0) {
+                                   sizeof(ut.ut_line)) == 0) {
                                        /* Show it */
                                        if (c == 0) {
-                                               quit = list(ctl, &ut, p->ut.ut_time, R_NORMAL);
+                                               quit = list(ctl, &ut, p->ut.ut_tv.tv_sec, R_NORMAL);
                                                c = 1;
                                        }
-                                       if (p->next) p->next->prev = p->prev;
+                                       if (p->next)
+                                               p->next->prev = p->prev;
                                        if (p->prev)
                                                p->prev->next = p->next;
                                        else
-                                               utmplist = p->next;
+                                               ulist = p->next;
                                        free(p);
                                }
                        }
@@ -653,18 +819,16 @@ static void process_wtmp_file(const struct last_control *ctl)
                         * logged in, or missing logout record.
                         */
                        if (c == 0) {
-                               if (lastboot == 0) {
+                               if (!lastboot) {
                                        c = R_NOW;
                                        /* Is process still alive? */
-                                       if (ut.ut_pid > 0 &&
-                                           kill(ut.ut_pid, 0) != 0 &&
-                                           errno == ESRCH)
+                                       if (is_phantom(ctl, &ut))
                                                c = R_PHANTOM;
                                } else
                                        c = whydown;
                                quit = list(ctl, &ut, lastboot, c);
                        }
-                       /* FALLTHRU */
+                       /* fallthrough */
 
                case DEAD_PROCESS:
                        /*
@@ -674,42 +838,62 @@ static void process_wtmp_file(const struct last_control *ctl)
                        if (ut.ut_line[0] == 0)
                                break;
                        p = xmalloc(sizeof(struct utmplist));
-                       memcpy(&p->ut, &ut, sizeof(struct utmp));
-                       p->next  = utmplist;
+                       memcpy(&p->ut, &ut, sizeof(struct utmpx));
+                       p->next  = ulist;
                        p->prev  = NULL;
-                       if (utmplist) utmplist->prev = p;
-                       utmplist = p;
+                       if (ulist)
+                               ulist->prev = p;
+                       ulist = p;
                        break;
 
                case EMPTY:
                case INIT_PROCESS:
                case LOGIN_PROCESS:
+#ifdef ACCOUNTING
                case ACCOUNTING:
+#endif
                        /* ignored ut_types */
                        break;
 
                default:
-                       warnx("unrecogized ut_type: %d", ut.ut_type);
+                       warnx("unrecognized ut_type: %d", ut.ut_type);
                }
 
                /*
                 * If we saw a shutdown/reboot record we can remove
-                * the entire current utmplist.
+                * the entire current ulist.
                 */
                if (down) {
-                       lastboot = ut.ut_time;
+                       lastboot = ut.ut_tv.tv_sec;
                        whydown = (ut.ut_type == SHUTDOWN_TIME) ? R_DOWN : R_CRASH;
-                       for (p = utmplist; p; p = next) {
+                       for (p = ulist; p; p = next) {
                                next = p->next;
                                free(p);
                        }
-                       utmplist = NULL;
+                       ulist = NULL;
                        down = 0;
                }
        }
 
-       printf(_("\n%s begins %s"), basename(ctl->altv[ctl->alti]), ctime(&begintime));
+       if (ctl->time_fmt != LAST_TIMEFTM_NONE) {
+               struct last_timefmt *fmt;
+               char timestr[LAST_TIMESTAMP_LEN];
+               char *tmp = xstrdup(filename);
+
+               fmt = &timefmts[ctl->time_fmt];
+               if (time_formatter(fmt->in_fmt, timestr,
+                                  sizeof(timestr), &begintime) < 0)
+                       errx(EXIT_FAILURE, _("preallocation size exceeded"));
+               printf(_("\n%s begins %s\n"), basename(tmp), timestr);
+               free(tmp);
+       }
+
        fclose(fp);
+
+       for (p = ulist; p; p = next) {
+               next = p->next;
+               free(p);
+       }
 }
 
 int main(int argc, char **argv)
@@ -717,11 +901,17 @@ int main(int argc, char **argv)
        struct last_control ctl = {
                .showhost = TRUE,
                .name_len = LAST_LOGIN_LEN,
+               .time_fmt = LAST_TIMEFTM_SHORT,
                .domain_len = LAST_DOMAIN_LEN
        };
+       char **files = NULL;
+       size_t i, nfiles = 0;
        int c;
        usec_t p;
 
+       enum {
+               OPT_TIME_FORMAT = CHAR_MAX + 1
+       };
        static const struct option long_opts[] = {
              { "limit",        required_argument, NULL, 'n' },
              { "help", no_argument,       NULL, 'h' },
@@ -737,48 +927,60 @@ int main(int argc, char **argv)
              { "ip",         no_argument,       NULL, 'i' },
              { "fulltimes",  no_argument,       NULL, 'F' },
              { "fullnames",  no_argument,       NULL, 'w' },
+             { "time-format", required_argument, NULL, OPT_TIME_FORMAT },
              { NULL, 0, NULL, 0 }
        };
+       static const ul_excl_t excl[] = {       /* rows and cols in ASCII order */
+               { 'F', OPT_TIME_FORMAT },       /* fulltime, time-format */
+               { 0 }
+       };
+       int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
 
        setlocale(LC_ALL, "");
        bindtextdomain(PACKAGE, LOCALEDIR);
        textdomain(PACKAGE);
        atexit(close_stdout);
-
+       /*
+        * Which file do we want to read?
+        */
+       ctl.lastb = strcmp(program_invocation_short_name, "lastb") == 0 ? 1 : 0;
        while ((c = getopt_long(argc, argv,
                        "hVf:n:RxadFit:p:s:0123456789w", long_opts, NULL)) != -1) {
+
+               err_exclusive_options(c, long_opts, excl, excl_st);
+
                switch(c) {
                case 'h':
-                       usage(stdout);
+                       usage(&ctl);
                        break;
                case 'V':
                        printf(UTIL_LINUX_VERSION);
                        return EXIT_SUCCESS;
                case 'R':
-                       ctl.showhost = FALSE;
+                       ctl.showhost = 0;
                        break;
                case 'x':
-                       ctl.extended = TRUE;
+                       ctl.extended = 1;
                        break;
                case 'n':
                        ctl.maxrecs = strtos32_or_err(optarg, _("failed to parse number"));
                        break;
                case 'f':
-                       if (!ctl.altv)
-                               ctl.altv = xmalloc(sizeof(char *) * argc);
-                       ctl.altv[ctl.altc++] = xstrdup(optarg);
+                       if (!files)
+                               files = xmalloc(sizeof(char *) * argc);
+                       files[nfiles++] = xstrdup(optarg);
                        break;
                case 'd':
-                       ctl.usedns = TRUE;
+                       ctl.usedns = 1;
                        break;
                case 'i':
-                       ctl.useip = TRUE;
+                       ctl.useip = 1;
                        break;
                case 'a':
-                       ctl.altlist = TRUE;
+                       ctl.altlist = 1;
                        break;
                case 'F':
-                       ctl.fulltime = TRUE;
+                       ctl.time_fmt = LAST_TIMEFTM_CTIME;
                        break;
                case 'p':
                        if (parse_timestamp(optarg, &p) < 0)
@@ -796,41 +998,36 @@ int main(int argc, char **argv)
                        ctl.until = (time_t) (p / 1000000);
                        break;
                case 'w':
-                       if (ctl.name_len < UT_NAMESIZE)
-                               ctl.name_len = UT_NAMESIZE;
-                       if (ctl.domain_len < UT_HOSTSIZE)
-                               ctl.domain_len = UT_HOSTSIZE;
+                       if (ctl.name_len < sizeof(((struct utmpx *) 0)->ut_user))
+                               ctl.name_len = sizeof(((struct utmpx *) 0)->ut_user);
+                       if (ctl.domain_len < sizeof(((struct utmpx *) 0)->ut_host))
+                               ctl.domain_len = sizeof(((struct utmpx *) 0)->ut_host);
                        break;
                case '0': case '1': case '2': case '3': case '4':
                case '5': case '6': case '7': case '8': case '9':
                        ctl.maxrecs = 10 * ctl.maxrecs + c - '0';
                        break;
-               default:
-                       usage(stderr);
+               case OPT_TIME_FORMAT:
+                       ctl.time_fmt = which_time_format(optarg);
                        break;
+               default:
+                       errtryhelp(EXIT_FAILURE);
                }
        }
 
        if (optind < argc)
                ctl.show = argv + optind;
 
-       /*
-        * Which file do we want to read?
-        */
-       ctl.lastb = !strcmp(program_invocation_short_name, "lastb");
-       if (!ctl.altc) {
-               ctl.altv = xmalloc(sizeof(char *));
-               if (ctl.lastb)
-                       ctl.altv[0] = xstrdup(_PATH_BTMP);
-               else
-                       ctl.altv[0] = xstrdup(_PATH_WTMP);
-               ctl.altc++;
+       if (!files) {
+               files = xmalloc(sizeof(char *));
+               files[nfiles++] = xstrdup(ctl.lastb ? _PATH_BTMP : _PATH_WTMP);
        }
 
-       for (; ctl.alti < ctl.altc; ctl.alti++) {
-               process_wtmp_file(&ctl);
-               free(ctl.altv[ctl.alti]);
+       for (i = 0; i < nfiles; i++) {
+               get_boot_time(&ctl.boot_time);
+               process_wtmp_file(&ctl, files[i]);
+               free(files[i]);
        }
-       free(ctl.altv);
+       free(files);
        return EXIT_SUCCESS;
 }