Coverage Report

Created: 2026-09-01 06:54

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/util-linux/login-utils/last.c
Line
Count
Source
1
/*
2
 * last(1) from sysvinit project, merged into util-linux in Aug 2013.
3
 *
4
 * Copyright (C) 1991-2004 Miquel van Smoorenburg.
5
 * Copyright (C) 2013      Ondrej Oprala <ooprala@redhat.com>
6
 *                         Karel Zak <kzak@redhat.com>
7
 *
8
 * Re-implementation of the 'last' command, this time for Linux. Yes I know
9
 * there is BSD last, but I just felt like writing this. No thanks :-).  Also,
10
 * this version gives lots more info (especially with -x)
11
 *
12
 *
13
 * This program is free software; you can redistribute it and/or modify
14
 * it under the terms of the GNU General Public License as published by
15
 * the Free Software Foundation; either version 2 of the License, or
16
 * (at your option) any later version.
17
 *
18
 * This program is distributed in the hope that it will be useful,
19
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21
 * GNU General Public License for more details.
22
 *
23
 * You should have received a copy of the GNU General Public License
24
 * along with this program.  If not, see <https://gnu.org/licenses/>.
25
 */
26
#include <sys/types.h>
27
#include <sys/stat.h>
28
#include <fcntl.h>
29
#include <time.h>
30
#include <stdio.h>
31
#include <ctype.h>
32
#include <utmpx.h>
33
#include <pwd.h>
34
#include <stdlib.h>
35
#include <unistd.h>
36
#include <string.h>
37
#include <signal.h>
38
#include <getopt.h>
39
#include <netinet/in.h>
40
#include <netdb.h>
41
#include <arpa/inet.h>
42
#include <libgen.h>
43
44
#include "c.h"
45
#include "nls.h"
46
#include "optutils.h"
47
#include "pathnames.h"
48
#include "xalloc.h"
49
#include "closestream.h"
50
#include "carefulputc.h"
51
#include "strutils.h"
52
#include "timeutils.h"
53
#include "monotonic.h"
54
#include "fileutils.h"
55
56
#ifdef FUZZ_TARGET
57
#include "fuzz.h"
58
#endif
59
60
#ifndef SHUTDOWN_TIME
61
0
# define SHUTDOWN_TIME 254
62
#endif
63
64
#ifndef LAST_LOGIN_LEN
65
0
# define LAST_LOGIN_LEN 8
66
#endif
67
68
#ifndef LAST_DOMAIN_LEN
69
0
# define LAST_DOMAIN_LEN 16
70
#endif
71
72
#ifndef LAST_TIMESTAMP_LEN
73
# define LAST_TIMESTAMP_LEN 32
74
#endif
75
76
0
#define UCHUNKSIZE  16384  /* How much we read at once. */
77
78
struct last_control {
79
  bool lastb, /* Is this command 'lastb' */
80
       extended,  /* Lots of info */
81
       showhost,  /* Show hostname */
82
       altlist, /* Hostname at the end */
83
       usedns,  /* Use DNS to lookup the hostname */
84
       useip; /* Print IP address in number format */
85
86
  unsigned int maxrecs; /* Maximum number of records to list */
87
88
  char **show;    /* Match search list */
89
90
  struct timeval boot_time; /* system boot time */
91
  time_t since;   /* at what time to start displaying the file */
92
  time_t until;   /* at what time to stop displaying the file */
93
  time_t present;   /* who where present at time_t */
94
  unsigned int time_fmt;  /* time format */
95
  char separator;        /* output separator */
96
97
  bool fullnames_mode;
98
};
99
100
/* Double linked list of struct utmp's */
101
struct utmplist {
102
  struct utmpx ut;
103
  struct utmplist *next;
104
  struct utmplist *prev;
105
};
106
107
/* Types of listing */
108
enum {
109
  R_CRASH = 1,  /* No logout record, system boot in between */
110
  R_DOWN,   /* System brought down in decent way */
111
  R_NORMAL, /* Normal */
112
  R_NOW,    /* Still logged in */
113
  R_REBOOT, /* Reboot record. */
114
  R_REBOOT_CRASH, /* Reboot record without matching shutdown */
115
  R_PHANTOM,  /* No logout record but session is stale. */
116
  R_TIMECHANGE  /* NEW_TIME or OLD_TIME */
117
};
118
119
enum {
120
  LAST_TIMEFTM_NONE = 0,
121
  LAST_TIMEFTM_SHORT,
122
  LAST_TIMEFTM_CTIME,
123
  LAST_TIMEFTM_ISO8601,
124
125
  LAST_TIMEFTM_HHMM,  /* non-public */
126
};
127
128
struct last_timefmt {
129
  const char *name;
130
  int in_len; /* log-in */
131
  int in_fmt;
132
  int out_len;  /* log-out */
133
  int out_fmt;
134
};
135
136
static struct last_timefmt timefmts[] = {
137
  [LAST_TIMEFTM_NONE] = { .name = "notime" },
138
  [LAST_TIMEFTM_SHORT] = {
139
    .name    = "short",
140
    .in_len  = 16,
141
    .out_len = 7,
142
    .in_fmt  = LAST_TIMEFTM_CTIME,
143
    .out_fmt = LAST_TIMEFTM_HHMM
144
  },
145
  [LAST_TIMEFTM_CTIME] = {
146
    .name    = "full",
147
    .in_len  = 24,
148
    .out_len = 26,
149
    .in_fmt  = LAST_TIMEFTM_CTIME,
150
    .out_fmt = LAST_TIMEFTM_CTIME
151
  },
152
  [LAST_TIMEFTM_ISO8601] = {
153
    .name    = "iso",
154
    .in_len  = 25,
155
    .out_len = 27,
156
    .in_fmt  = LAST_TIMEFTM_ISO8601,
157
    .out_fmt = LAST_TIMEFTM_ISO8601
158
  }
159
};
160
161
/* Global variables */
162
static unsigned int recsdone; /* Number of records listed */
163
static time_t lastdate;   /* Last date we've seen */
164
static time_t currentdate;  /* date when we started processing the file */
165
166
#ifndef FUZZ_TARGET
167
/* --time-format=option parser */
168
static int which_time_format(const char *s)
169
{
170
  size_t i;
171
172
  for (i = 0; i < ARRAY_SIZE(timefmts); i++) {
173
    if (strcmp(timefmts[i].name, s) == 0)
174
      return i;
175
  }
176
  errx(EXIT_FAILURE, _("unknown time format: %s"), s);
177
}
178
#endif
179
180
/*
181
 *  Read one utmp entry, return in new format.
182
 *  Automatically reposition file pointer.
183
 */
184
static int uread(FILE *fp, struct utmpx *u,  int *quit, const char *filename)
185
0
{
186
0
  static int utsize;
187
0
  static char buf[UCHUNKSIZE];
188
0
  char tmp[1024];
189
0
  static off_t fpos;
190
0
  static int bpos;
191
0
  off_t o;
192
193
0
  if (quit == NULL && u != NULL) {
194
    /*
195
     *  Normal read.
196
     */
197
0
    return fread(u, sizeof(struct utmpx), 1, fp);
198
0
  }
199
200
0
  if (u == NULL) {
201
    /*
202
     *  Initialize and position.
203
     */
204
0
    utsize = sizeof(struct utmpx);
205
0
    fseeko(fp, 0, SEEK_END);
206
0
    fpos = ftello(fp);
207
0
    if (fpos == 0)
208
0
      return 0;
209
0
    o = ((fpos - 1) / UCHUNKSIZE) * UCHUNKSIZE;
210
0
    if (fseeko(fp, o, SEEK_SET) < 0) {
211
0
      warn(_("seek on %s failed"), filename);
212
0
      return 0;
213
0
    }
214
0
    bpos = (int)(fpos - o);
215
0
    if (fread(buf, bpos, 1, fp) != 1) {
216
0
      warn(_("cannot read %s"), filename);
217
0
      return 0;
218
0
    }
219
0
    fpos = o;
220
0
    return 1;
221
0
  }
222
223
  /*
224
   *  Read one struct. From the buffer if possible.
225
   */
226
0
  bpos -= utsize;
227
0
  if (bpos >= 0) {
228
0
    memcpy(u, buf + bpos, sizeof(struct utmpx));
229
0
    return 1;
230
0
  }
231
232
  /*
233
   *  Oops we went "below" the buffer. We should be able to
234
   *  seek back UCHUNKSIZE bytes.
235
   */
236
0
  fpos -= UCHUNKSIZE;
237
0
  if (fpos < 0)
238
0
    return 0;
239
240
  /*
241
   *  Copy whatever is left in the buffer.
242
   */
243
0
  memcpy(tmp + (-bpos), buf, utsize + bpos);
244
0
  if (fseeko(fp, fpos, SEEK_SET) < 0) {
245
0
    warn(_("seek on %s failed"), filename);
246
0
    return 0;
247
0
  }
248
249
  /*
250
   *  Read another UCHUNKSIZE bytes.
251
   */
252
0
  if (fread(buf, UCHUNKSIZE, 1, fp) != 1) {
253
0
    warn(_("cannot read %s"), filename);
254
0
    return 0;
255
0
  }
256
257
  /*
258
   *  The end of the UCHUNKSIZE byte buffer should be the first
259
   *  few bytes of the current struct utmp.
260
   */
261
0
  memcpy(tmp, buf + UCHUNKSIZE + bpos, -bpos);
262
0
  bpos += UCHUNKSIZE;
263
264
0
  memcpy(u, tmp, sizeof(struct utmpx));
265
266
0
  return 1;
267
0
}
268
269
#ifndef FUZZ_TARGET
270
/*
271
 *  SIGINT handler
272
 */
273
static void int_handler(int sig __attribute__((unused)))
274
{
275
  ul_sig_err(EXIT_FAILURE, "Interrupted");
276
}
277
278
/*
279
 *  SIGQUIT handler
280
 */
281
static void quit_handler(int sig __attribute__((unused)))
282
{
283
  ul_sig_warn("Interrupted");
284
  signal(SIGQUIT, quit_handler);
285
}
286
#endif
287
288
/*
289
 *  Lookup a host with DNS.
290
 */
291
static int dns_lookup(char *result, int size, int useip, int32_t *a)
292
0
{
293
0
  struct sockaddr_in  sin;
294
0
  struct sockaddr_in6 sin6;
295
0
  struct sockaddr   *sa;
296
0
  int     salen, flags;
297
0
  int     mapped = 0;
298
299
0
  flags = useip ? NI_NUMERICHOST : 0;
300
301
  /*
302
   *  IPv4 or IPv6 ?
303
   *  1. If last 3 4bytes are 0, must be IPv4
304
   *  2. If IPv6 in IPv4, handle as IPv4
305
   *  3. Anything else is IPv6
306
   *
307
   *  Ugly.
308
   */
309
0
  if (a[0] == 0 && a[1] == 0 && a[2] == (int32_t)htonl (0xffff))
310
0
    mapped = 1;
311
312
0
  if (mapped || (a[1] == 0 && a[2] == 0 && a[3] == 0)) {
313
    /* IPv4 */
314
0
    sin.sin_family = AF_INET;
315
0
    sin.sin_port = 0;
316
0
    sin.sin_addr.s_addr = mapped ? a[3] : a[0];
317
0
    sa = (struct sockaddr *)&sin;
318
0
    salen = sizeof(sin);
319
0
  } else {
320
    /* IPv6 */
321
0
    memset(&sin6, 0, sizeof(sin6));
322
0
    sin6.sin6_family = AF_INET6;
323
0
    sin6.sin6_port = 0;
324
0
    memcpy(sin6.sin6_addr.s6_addr, a, 16);
325
0
    sa = (struct sockaddr *)&sin6;
326
0
    salen = sizeof(sin6);
327
0
  }
328
329
0
  return getnameinfo(sa, salen, result, size, NULL, 0, flags);
330
0
}
331
332
static int time_formatter(int fmt, char *dst, size_t dlen, time_t *when)
333
0
{
334
0
  int ret = 0;
335
336
0
  switch (fmt) {
337
0
  case LAST_TIMEFTM_NONE:
338
0
    *dst = 0;
339
0
    break;
340
0
  case LAST_TIMEFTM_HHMM:
341
0
  {
342
0
    struct tm tm;
343
344
0
    localtime_r(when, &tm);
345
0
    if (!snprintf(dst, dlen, "%02d:%02d", tm.tm_hour, tm.tm_min))
346
0
      ret = -1;
347
0
    break;
348
0
  }
349
0
  case LAST_TIMEFTM_CTIME:
350
0
  {
351
0
    char buf[CTIME_BUFSIZ];
352
353
0
    if (!ctime_r(when, buf)) {
354
0
      ret = -1;
355
0
      break;
356
0
    }
357
0
    snprintf(dst, dlen, "%s", buf);
358
0
    ret = rtrim_whitespace((unsigned char *) dst);
359
0
    break;
360
0
  }
361
0
  case LAST_TIMEFTM_ISO8601:
362
0
    ret = strtime_iso(when, ISO_TIMESTAMP_T, dst, dlen);
363
0
    break;
364
0
  default:
365
0
    abort();
366
0
  }
367
0
  return ret;
368
0
}
369
370
/*
371
 *  Remove trailing spaces from a string.
372
 */
373
static void trim_trailing_spaces(char *s)
374
0
{
375
0
  char *p;
376
377
0
  for (p = s; *p; ++p)
378
0
    continue;
379
0
  while (p > s && isspace(*--p))
380
0
    continue;
381
0
  if (p > s)
382
0
    ++p;
383
0
  *p++ = '\n';
384
0
  *p = '\0';
385
0
}
386
387
/*
388
 *  Show one line of information on screen
389
 */
390
static int list(const struct last_control *ctl, struct utmpx *p, time_t logout_time, int what)
391
0
{
392
0
  time_t    secs, utmp_time;
393
0
  char    logintime[LAST_TIMESTAMP_LEN];
394
0
  char    logouttime[LAST_TIMESTAMP_LEN];
395
0
  char    length[LAST_TIMESTAMP_LEN];
396
0
  char    final[512];
397
0
  char    utline[sizeof(p->ut_line) + 1];
398
0
  char    domain[256];
399
0
  int   mins, hours, days;
400
0
  int   r, len;
401
0
  struct last_timefmt *fmt;
402
0
  int     name_len;
403
404
  /*
405
   *  uucp and ftp have special-type entries
406
   */
407
0
  mem2strcpy(utline, p->ut_line, sizeof(p->ut_line), sizeof(utline));
408
0
  if (strncmp(utline, "ftp", 3) == 0 && isdigit(utline[3]))
409
0
    utline[3] = 0;
410
0
  if (strncmp(utline, "uucp", 4) == 0 && isdigit(utline[4]))
411
0
    utline[4] = 0;
412
413
  /*
414
   *  Is this something we want to show?
415
   */
416
0
  if (ctl->show) {
417
0
    char **walk;
418
0
    for (walk = ctl->show; *walk; walk++) {
419
0
      if (strncmp(p->ut_user, *walk, sizeof(p->ut_user)) == 0 ||
420
0
          strcmp(utline, *walk) == 0 ||
421
0
          (strncmp(utline, "tty", 3) == 0 &&
422
0
           strcmp(utline + 3, *walk) == 0)) break;
423
0
    }
424
0
    if (*walk == NULL) return 0;
425
0
  }
426
427
  /*
428
   *  Calculate times
429
   */
430
0
  fmt = &timefmts[ctl->time_fmt];
431
432
0
  utmp_time = p->ut_tv.tv_sec;
433
434
0
  if (ctl->present) {
435
0
    if (ctl->present < utmp_time)
436
0
      return 0;
437
0
    if (0 < logout_time && logout_time < ctl->present)
438
0
      return 0;
439
0
  }
440
441
  /* log-in time */
442
0
  if (time_formatter(fmt->in_fmt, logintime,
443
0
         sizeof(logintime), &utmp_time) < 0)
444
0
    errx(EXIT_FAILURE, _("preallocation size exceeded"));
445
446
  /* log-out time */
447
0
  secs  = logout_time - utmp_time; /* Under strange circumstances, secs < 0 can happen */
448
0
  mins  = (secs / 60) % 60;
449
0
  hours = (secs / 3600) % 24;
450
0
  days  = secs / 86400;
451
452
0
  strcpy(logouttime, "- ");
453
0
  if (time_formatter(fmt->out_fmt, logouttime + 2,
454
0
         sizeof(logouttime) - 2, &logout_time) < 0)
455
0
    errx(EXIT_FAILURE, _("preallocation size exceeded"));
456
457
0
  if (logout_time == currentdate) {
458
0
    if (ctl->time_fmt > LAST_TIMEFTM_SHORT) {
459
0
      snprintf(logouttime, sizeof(logouttime), "  still running");
460
0
      length[0] = 0;
461
0
    } else {
462
0
      snprintf(logouttime, sizeof(logouttime), "  still");
463
0
      snprintf(length, sizeof(length), "running");
464
0
    }
465
0
  } else if (days) {
466
0
    snprintf(length, sizeof(length), "(%d+%02d:%02d)", days, abs(hours), abs(mins)); /* hours and mins always shown as positive (w/o minus sign!) even if secs < 0 */
467
0
  } else if (hours) {
468
0
    snprintf(length, sizeof(length), " (%02d:%02d)", hours, abs(mins));  /* mins always shown as positive (w/o minus sign!) even if secs < 0 */
469
0
  } else if (secs >= 0) {
470
0
    snprintf(length, sizeof(length), " (%02d:%02d)", hours, mins);
471
0
  } else {
472
0
    snprintf(length, sizeof(length), " (-00:%02d)", abs(mins));  /* mins always shown as positive (w/o minus sign!) even if secs < 0 */
473
0
  }
474
475
0
  switch(what) {
476
0
    case R_CRASH:
477
0
    case R_REBOOT_CRASH:
478
0
      snprintf(logouttime, sizeof(logouttime), "- crash");
479
0
      break;
480
0
    case R_DOWN:
481
0
      snprintf(logouttime, sizeof(logouttime), "- down ");
482
0
      break;
483
0
    case R_NOW:
484
0
      if (ctl->time_fmt > LAST_TIMEFTM_SHORT) {
485
0
        snprintf(logouttime, sizeof(logouttime), "  still logged in");
486
0
        length[0] = 0;
487
0
      } else {
488
0
        snprintf(logouttime, sizeof(logouttime), "  still");
489
0
        snprintf(length, sizeof(length), "logged in");
490
0
      }
491
0
      break;
492
0
    case R_PHANTOM:
493
0
      if (ctl->time_fmt > LAST_TIMEFTM_SHORT) {
494
0
        snprintf(logouttime, sizeof(logouttime), "  gone - no logout");
495
0
        length[0] = 0;
496
0
      } else if (ctl->time_fmt == LAST_TIMEFTM_SHORT) {
497
0
        snprintf(logouttime, sizeof(logouttime), "   gone");
498
0
        snprintf(length, sizeof(length), "- no logout");
499
0
      } else {
500
0
        logouttime[0] = 0;
501
0
        snprintf(length, sizeof(length), "no logout");
502
0
      }
503
0
      break;
504
0
    case R_TIMECHANGE:
505
0
      logouttime[0] = 0;
506
0
      length[0] = 0;
507
0
      break;
508
0
    case R_NORMAL:
509
0
    case R_REBOOT:
510
0
      break;
511
0
    default:
512
0
      abort();
513
0
  }
514
515
  /*
516
   *  Look up host with DNS if needed.
517
   */
518
0
  r = -1;
519
0
  if (ctl->usedns || ctl->useip)
520
0
    r = dns_lookup(domain, sizeof(domain), ctl->useip, (int32_t*)p->ut_addr_v6);
521
0
  if (r < 0)
522
0
    mem2strcpy(domain, p->ut_host, sizeof(p->ut_host), sizeof(domain));
523
524
  /*
525
   * set last displayed character to an asterisk when
526
   * user/domain/ip fields are to be truncated in non-fullnames mode
527
   */
528
0
  if (ctl->fullnames_mode)
529
0
    name_len = (int)sizeof_member(struct utmpx, ut_user);
530
0
  else {
531
0
    name_len = LAST_LOGIN_LEN;
532
0
    if (strnlen(p->ut_user, sizeof(p->ut_user)) > LAST_LOGIN_LEN)
533
0
      p->ut_user[LAST_LOGIN_LEN - 1] = '*';
534
0
  }
535
536
0
  if (ctl->showhost) {
537
0
    if (!ctl->altlist) {
538
0
      int domain_len;
539
540
0
      if (ctl->fullnames_mode)
541
0
        domain_len = (int)sizeof_member(struct utmpx, ut_host);
542
0
      else {
543
0
        domain_len = LAST_DOMAIN_LEN;
544
0
        if (strnlen(domain, sizeof(domain)) > LAST_DOMAIN_LEN)
545
0
          domain[LAST_DOMAIN_LEN - 1] = '*';
546
0
      }
547
548
0
      len = snprintf(final, sizeof(final),
549
0
        "%-8.*s%c%-12.12s%c%-16.*s%c%-*.*s%c%-*.*s%c%s\n",
550
0
        name_len, p->ut_user, ctl->separator, utline, ctl->separator,
551
0
        domain_len, domain, ctl->separator,
552
0
        fmt->in_len, fmt->in_len, logintime, ctl->separator, fmt->out_len, fmt->out_len,
553
0
        logouttime, ctl->separator, length);
554
0
    } else {
555
0
      len = snprintf(final, sizeof(final),
556
0
        "%-8.*s%c%-12.12s%c%-*.*s%c%-*.*s%c%-12.12s%c%s\n",
557
0
        name_len, p->ut_user, ctl->separator, utline, ctl->separator,
558
0
        fmt->in_len, fmt->in_len, logintime, ctl->separator, fmt->out_len, fmt->out_len,
559
0
        logouttime, ctl->separator, length, ctl->separator, domain);
560
0
    }
561
0
  } else
562
0
    len = snprintf(final, sizeof(final),
563
0
      "%-8.*s%c%-12.12s%c%-*.*s%c%-*.*s%c%s\n",
564
0
      name_len, p->ut_user, ctl->separator, utline, ctl->separator,
565
0
      fmt->in_len, fmt->in_len, logintime, ctl->separator, fmt->out_len, fmt->out_len,
566
0
      logouttime, ctl->separator, length);
567
568
0
#if defined(__GLIBC__)
569
#  if (__GLIBC__ == 2) && (__GLIBC_MINOR__ == 0)
570
  final[sizeof(final)-1] = '\0';
571
#  endif
572
0
#endif
573
574
0
  trim_trailing_spaces(final);
575
  /*
576
   *  Print out "final" string safely.
577
   */
578
0
  fputs_careful(final, stdout, '*', false, 0);
579
580
0
  if (len < 0 || (size_t)len >= sizeof(final))
581
0
    putchar('\n');
582
583
0
  recsdone++;
584
0
  if (ctl->maxrecs && ctl->maxrecs <= recsdone)
585
0
    return 1;
586
587
0
  return 0;
588
0
}
589
590
#ifndef FUZZ_TARGET
591
static void __attribute__((__noreturn__)) usage(const struct last_control *ctl)
592
{
593
  FILE *out = stdout;
594
  fputs(USAGE_HEADER, out);
595
  fprintf(out, _(
596
    " %s [options] [<username>...] [<tty>...]\n"), program_invocation_short_name);
597
598
  fputs(USAGE_SEPARATOR, out);
599
  fputs(_("Show a listing of last logged in users.\n"), out);
600
601
  fputs(USAGE_OPTIONS, out);
602
  fputs(_(" -<number>            how many lines to show\n"), out);
603
  fputs(_(" -a, --hostlast       display hostnames in the last column\n"), out);
604
  fputs(_(" -d, --dns            translate the IP number back into a hostname\n"), out);
605
  fprintf(out,
606
        _(" -f, --file <file>    use a specific file instead of %s\n"), ctl->lastb ? _PATH_BTMP : _PATH_WTMP);
607
  fputs(_(" -F, --fulltimes      print full login and logout times and dates\n"), out);
608
  fputs(_(" -i, --ip             display IP numbers in numbers-and-dots notation\n"), out);
609
  fputs(_(" -n, --limit <number> how many lines to show\n"), out);
610
  fputs(_(" -p, --present <time> display who were present at the specified time\n"), out);
611
  fputs(_(" -R, --nohostname     don't display the hostname field\n"), out);
612
  fputs(_(" -s, --since <time>   display the lines since the specified time\n"), out);
613
  fputs(_(" -t, --until <time>   display the lines until the specified time\n"), out);
614
  fputs(_(" -T, --tab-separated  use tabs as delimiters\n"), out);
615
  fputs(_("     --time-format <format>  show timestamps in the specified <format>:\n"
616
    "                               notime|short|full|iso\n"), out);
617
  fputs(_(" -w, --fullnames      display full user and domain names\n"), out);
618
  fputs(_(" -x, --system         display system shutdown entries and run level changes\n"), out);
619
620
  fputs(USAGE_SEPARATOR, out);
621
  fprintf(out, USAGE_HELP_OPTIONS(22));
622
  fprintf(out, USAGE_MAN_TAIL("last(1)"));
623
624
  exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
625
}
626
#endif
627
628
static int is_phantom(const struct last_control *ctl, struct utmpx *ut)
629
0
{
630
0
  struct passwd *pw;
631
0
  char path[sizeof(ut->ut_line) + 16];
632
0
  char user[sizeof(ut->ut_user) + 1];
633
0
  int ret = 0;
634
635
0
  if (ut->ut_tv.tv_sec < ctl->boot_time.tv_sec)
636
0
    return 1;
637
638
0
  mem2strcpy(user, ut->ut_user, sizeof(ut->ut_user), sizeof(user));
639
0
  pw = getpwnam(user);
640
0
  if (!pw)
641
0
    return 1;
642
0
  snprintf(path, sizeof(path), "/proc/%d/loginuid", ut->ut_pid);
643
0
  if (access(path, R_OK) == 0) {
644
0
    unsigned int loginuid;
645
0
    FILE *f = NULL;
646
647
0
    if (!(f = fopen(path, "r")))
648
0
      return 1;
649
0
    if (fscanf(f, "%u", &loginuid) != 1)
650
0
      ret = 1;
651
0
    fclose(f);
652
0
    if (!ret && loginuid != INVALID_UID && pw->pw_uid != loginuid)
653
0
      return 1;
654
0
  } else {
655
0
    struct stat st;
656
0
    char utline[sizeof(ut->ut_line) + 1];
657
658
0
    mem2strcpy(utline, ut->ut_line, sizeof(ut->ut_line), sizeof(utline));
659
660
0
    if (utline[0] != ':') {
661
0
      snprintf(path, sizeof(path), "/dev/%s", utline);
662
0
      if (stat(path, &st))
663
0
        return 1;
664
0
      if (pw->pw_uid != st.st_uid)
665
0
        return 1;
666
0
    }
667
0
  }
668
0
  return ret;
669
0
}
670
671
static void process_wtmp_file(const struct last_control *ctl,
672
            const char *filename)
673
0
{
674
0
  FILE *fp;   /* File pointer of wtmp file */
675
676
0
  struct utmpx ut;  /* Current utmp entry */
677
0
  struct utmplist *ulist = NULL; /* All entries */
678
0
  struct utmplist *p; /* Pointer into utmplist */
679
0
  struct utmplist *next;  /* Pointer into utmplist */
680
681
0
  time_t lastboot = 0;  /* Last boottime */
682
0
  time_t lastrch = 0; /* Last run level change */
683
0
  time_t lastdown;  /* Last downtime */
684
0
  time_t begintime; /* When wtmp begins */
685
0
  int whydown = 0;  /* Why we went down: crash or shutdown */
686
687
0
  int c, x;   /* Scratch */
688
0
  struct stat st;   /* To stat the [uw]tmp file */
689
0
  int quit = 0;   /* Flag */
690
0
  int down = 0;   /* Down flag */
691
692
#ifndef FUZZ_TARGET
693
  time(&lastdown);
694
#else
695
0
  lastdown = 1596001948;
696
0
#endif
697
  /*
698
   * Fill in 'lastdate'
699
   */
700
0
  lastdate = currentdate = lastrch = lastdown;
701
702
#ifndef FUZZ_TARGET
703
  /*
704
   * Install signal handlers
705
   */
706
  signal(SIGINT, int_handler);
707
  signal(SIGQUIT, quit_handler);
708
#endif
709
710
  /*
711
   * Open the utmp file
712
   */
713
0
  if ((fp = fopen(filename, "r")) == NULL)
714
0
    err(EXIT_FAILURE, _("cannot open %s"), filename);
715
716
  /*
717
   * Optimize the buffer size.
718
   */
719
0
  setvbuf(fp, NULL, _IOFBF, UCHUNKSIZE);
720
721
  /*
722
   * Read first structure to capture the time field
723
   */
724
0
  if (uread(fp, &ut, NULL, filename) == 1)
725
0
    begintime = ut.ut_tv.tv_sec;
726
0
  else {
727
0
    if (fstat(fileno(fp), &st) != 0)
728
0
      err(EXIT_FAILURE, _("stat of %s failed"), filename);
729
0
    begintime = st.st_ctime;
730
0
    quit = 1;
731
0
  }
732
733
  /*
734
   * Go to end of file minus one structure
735
   * and/or initialize utmp reading code.
736
   */
737
0
  uread(fp, NULL, NULL, filename);
738
739
  /*
740
   * Read struct after struct backwards from the file.
741
   */
742
0
  while (!quit) {
743
744
0
    if (uread(fp, &ut, &quit, filename) != 1)
745
0
      break;
746
747
0
    if (ctl->since && ut.ut_tv.tv_sec < ctl->since)
748
0
      continue;
749
750
0
    if (ctl->until && ctl->until < ut.ut_tv.tv_sec)
751
0
      continue;
752
753
0
    lastdate = ut.ut_tv.tv_sec;
754
755
0
    if (ctl->lastb) {
756
0
      quit = list(ctl, &ut, ut.ut_tv.tv_sec, R_NORMAL);
757
0
      continue;
758
0
    }
759
760
    /*
761
     * Set ut_type to the correct type.
762
     */
763
0
    if (strncmp(ut.ut_line, "~", 1) == 0) {
764
0
      if (strncmp(ut.ut_user, "shutdown", 8) == 0)
765
0
        ut.ut_type = SHUTDOWN_TIME;
766
0
      else if (strncmp(ut.ut_user, "reboot", 6) == 0)
767
0
        ut.ut_type = BOOT_TIME;
768
0
      else if (strncmp(ut.ut_user, "runlevel", 8) == 0)
769
0
        ut.ut_type = RUN_LVL;
770
0
    }
771
0
#if 1 /*def COMPAT*/
772
    /*
773
     * For stupid old applications that don't fill in
774
     * ut_type correctly.
775
     */
776
0
    else {
777
0
      if (ut.ut_type != DEAD_PROCESS &&
778
0
          ut.ut_user[0] && ut.ut_line[0] &&
779
0
          strncmp(ut.ut_user, "LOGIN", 5) != 0)
780
0
        ut.ut_type = USER_PROCESS;
781
      /*
782
       * Even worse, applications that write ghost
783
       * entries: ut_type set to USER_PROCESS but
784
       * empty ut_user...
785
       */
786
0
      if (ut.ut_user[0] == 0)
787
0
        ut.ut_type = DEAD_PROCESS;
788
789
      /*
790
       * Clock changes.
791
       */
792
0
      if (strncmp(ut.ut_user, "date", 4) == 0) {
793
0
        if (ut.ut_line[0] == '|')
794
0
          ut.ut_type = OLD_TIME;
795
0
        if (ut.ut_line[0] == '{')
796
0
          ut.ut_type = NEW_TIME;
797
0
      }
798
0
    }
799
0
#endif
800
0
    switch (ut.ut_type) {
801
0
    case SHUTDOWN_TIME:
802
0
      if (ctl->extended) {
803
0
        strcpy(ut.ut_line, "system down");
804
0
        quit = list(ctl, &ut, lastboot, R_NORMAL);
805
0
      }
806
0
      lastdown = lastrch = ut.ut_tv.tv_sec;
807
0
      down = 1;
808
0
      break;
809
0
    case OLD_TIME:
810
0
    case NEW_TIME:
811
0
      if (ctl->extended) {
812
0
        strcpy(ut.ut_line,
813
0
        ut.ut_type == NEW_TIME ? "new time" :
814
0
          "old time");
815
0
        quit = list(ctl, &ut, lastdown, R_TIMECHANGE);
816
0
      }
817
0
      break;
818
0
    case BOOT_TIME:
819
0
      strcpy(ut.ut_line, "system boot");
820
0
      if (lastdown > lastboot && lastdown != currentdate)
821
0
        quit = list(ctl, &ut, lastboot, R_REBOOT_CRASH);
822
0
      else
823
0
        quit = list(ctl, &ut, lastdown, R_REBOOT);
824
0
      lastboot = ut.ut_tv.tv_sec;
825
0
      down = 1;
826
0
      break;
827
0
    case RUN_LVL:
828
0
      x = ut.ut_pid & 255;
829
0
      if (ctl->extended) {
830
0
        snprintf(ut.ut_line, sizeof(ut.ut_line), "(to lvl %c)", x);
831
0
        quit = list(ctl, &ut, lastrch, R_NORMAL);
832
0
      }
833
0
      if (x == '0' || x == '6') {
834
0
        lastdown = ut.ut_tv.tv_sec;
835
0
        down = 1;
836
0
        ut.ut_type = SHUTDOWN_TIME;
837
0
      }
838
0
      lastrch = ut.ut_tv.tv_sec;
839
0
      break;
840
841
0
    case USER_PROCESS:
842
      /*
843
       * This was a login - show the first matching
844
       * logout record and delete all records with
845
       * the same ut_line.
846
       */
847
0
      c = 0;
848
0
      for (p = ulist; p; p = next) {
849
0
        next = p->next;
850
0
        if (strncmp(p->ut.ut_line, ut.ut_line,
851
0
            sizeof(ut.ut_line)) == 0) {
852
          /* Show it */
853
0
          if (c == 0) {
854
0
            quit = list(ctl, &ut, p->ut.ut_tv.tv_sec, R_NORMAL);
855
0
            c = 1;
856
0
          }
857
0
          if (p->next)
858
0
            p->next->prev = p->prev;
859
0
          if (p->prev)
860
0
            p->prev->next = p->next;
861
0
          else
862
0
            ulist = p->next;
863
0
          free(p);
864
0
        }
865
0
      }
866
      /*
867
       * Not found? Then crashed, down, still
868
       * logged in, or missing logout record.
869
       */
870
0
      if (c == 0) {
871
0
        if (!lastboot) {
872
0
          c = R_NOW;
873
          /* Is process still alive? */
874
0
          if (is_phantom(ctl, &ut))
875
0
            c = R_PHANTOM;
876
0
        } else
877
0
          c = whydown;
878
0
        quit = list(ctl, &ut, lastboot, c);
879
0
      }
880
0
      FALLTHROUGH;
881
882
0
    case DEAD_PROCESS:
883
      /*
884
       * Just store the data if it is
885
       * interesting enough.
886
       */
887
0
      if (ut.ut_line[0] == 0)
888
0
        break;
889
0
      p = xmalloc(sizeof(struct utmplist));
890
0
      memcpy(&p->ut, &ut, sizeof(struct utmpx));
891
0
      p->next  = ulist;
892
0
      p->prev  = NULL;
893
0
      if (ulist)
894
0
        ulist->prev = p;
895
0
      ulist = p;
896
0
      break;
897
898
0
    case EMPTY:
899
0
    case INIT_PROCESS:
900
0
    case LOGIN_PROCESS:
901
0
#ifdef ACCOUNTING
902
0
    case ACCOUNTING:
903
0
#endif
904
      /* ignored ut_types */
905
0
      break;
906
907
0
    default:
908
0
      warnx("unrecognized ut_type: %d", ut.ut_type);
909
0
    }
910
911
    /*
912
     * If we saw a shutdown/reboot record we can remove
913
     * the entire current ulist.
914
     */
915
0
    if (down) {
916
0
      lastboot = ut.ut_tv.tv_sec;
917
0
      whydown = (ut.ut_type == SHUTDOWN_TIME) ? R_DOWN : R_CRASH;
918
0
      for (p = ulist; p; p = next) {
919
0
        next = p->next;
920
0
        free(p);
921
0
      }
922
0
      ulist = NULL;
923
0
      down = 0;
924
0
    }
925
0
  }
926
927
0
  if (ctl->time_fmt != LAST_TIMEFTM_NONE) {
928
0
    struct last_timefmt *fmt;
929
0
    char timestr[LAST_TIMESTAMP_LEN];
930
0
    char *tmp = xstrdup(filename);
931
932
0
    fmt = &timefmts[ctl->time_fmt];
933
0
    if (time_formatter(fmt->in_fmt, timestr,
934
0
           sizeof(timestr), &begintime) < 0)
935
0
      errx(EXIT_FAILURE, _("preallocation size exceeded"));
936
0
    printf(_("\n%s begins %s\n"), basename(tmp), timestr);
937
0
    free(tmp);
938
0
  }
939
940
0
  fclose(fp);
941
942
0
  for (p = ulist; p; p = next) {
943
0
    next = p->next;
944
0
    free(p);
945
0
  }
946
0
}
947
948
#ifdef FUZZ_TARGET
949
# include "all-io.h"
950
951
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
952
0
{
953
0
  struct last_control ctl = {
954
0
    .showhost = TRUE,
955
0
    .time_fmt = LAST_TIMEFTM_SHORT,
956
0
    .boot_time = {
957
0
      .tv_sec = 1595978419,
958
0
      .tv_usec = 816074
959
0
    }
960
0
  };
961
0
  char name[] = "/tmp/test-last-fuzz.XXXXXX";
962
0
  int fd;
963
964
0
  fd = mkstemp_cloexec(name);
965
0
  if (fd < 0)
966
0
    err(EXIT_FAILURE, "mkstemp() failed");
967
0
  if (ul_write_all(fd, data, size) != 0)
968
0
    err(EXIT_FAILURE, "write() failed");
969
970
0
  process_wtmp_file(&ctl, name);
971
972
0
  close(fd);
973
0
  unlink(name);
974
975
0
  return 0;
976
0
}
977
#else
978
struct number_buffer {
979
  char buffer[16];
980
  size_t pos;
981
};
982
983
/*
984
 * Parses number in buffer, stores the result in val, and resets buffer.
985
 *
986
 * Calls err if number does not fit into val.
987
 */
988
static void
989
number_parse(struct number_buffer *nb, unsigned int *val)
990
{
991
  if (nb->pos > 0) {
992
    nb->buffer[nb->pos] = '\0';
993
    *val = strtou32_or_err(nb->buffer, _("failed to parse number"));
994
    nb->pos = 0;
995
  }
996
}
997
998
int main(int argc, char **argv)
999
{
1000
  struct last_control ctl = {
1001
    .showhost = TRUE,
1002
    .time_fmt = LAST_TIMEFTM_SHORT,
1003
    .fullnames_mode = false,
1004
  };
1005
  const char **files = NULL;
1006
  struct number_buffer nb = {
1007
    .pos = 0
1008
  };
1009
  int numind;
1010
  size_t i, nfiles = 0;
1011
  int c;
1012
  usec_t p;
1013
1014
  enum {
1015
    OPT_TIME_FORMAT = CHAR_MAX + 1
1016
  };
1017
  static const struct option long_opts[] = {
1018
        { "limit",  required_argument, NULL, 'n' },
1019
        { "help", no_argument,       NULL, 'h' },
1020
        { "file",       required_argument, NULL, 'f' },
1021
        { "nohostname", no_argument,       NULL, 'R' },
1022
        { "version",    no_argument,       NULL, 'V' },
1023
        { "hostlast",   no_argument,       NULL, 'a' },
1024
        { "since",      required_argument, NULL, 's' },
1025
        { "until",      required_argument, NULL, 't' },
1026
        { "present",    required_argument, NULL, 'p' },
1027
        { "system",     no_argument,       NULL, 'x' },
1028
        { "dns",        no_argument,       NULL, 'd' },
1029
        { "ip",         no_argument,       NULL, 'i' },
1030
        { "fulltimes",  no_argument,       NULL, 'F' },
1031
        { "fullnames",  no_argument,       NULL, 'w' },
1032
        { "tab-separated",  no_argument,   NULL, 'T' },
1033
        { "time-format", required_argument, NULL, OPT_TIME_FORMAT },
1034
        { NULL, 0, NULL, 0 }
1035
  };
1036
  static const ul_excl_t excl[] = { /* rows and cols in ASCII order */
1037
    { 'F', OPT_TIME_FORMAT }, /* fulltime, time-format */
1038
    { 0 }
1039
  };
1040
  int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
1041
1042
  setlocale(LC_ALL, "");
1043
  bindtextdomain(PACKAGE, LOCALEDIR);
1044
  textdomain(PACKAGE);
1045
  close_stdout_atexit();
1046
  /*
1047
   * Which file do we want to read?
1048
   */
1049
  ctl.lastb = strcmp(program_invocation_short_name, "lastb") == 0 ? 1 : 0;
1050
  ctl.separator = ' ';
1051
  numind = optind;
1052
  while ((c = getopt_long(argc, argv,
1053
       "hVf:n:RxadFit:p:s:T0123456789w", long_opts, NULL)) != -1) {
1054
    int digit = 0;
1055
1056
    err_exclusive_options(c, long_opts, excl, excl_st);
1057
1058
    switch(c) {
1059
    case 'h':
1060
      usage(&ctl);
1061
      break;
1062
    case 'V':
1063
      print_version(EXIT_SUCCESS);
1064
    case 'R':
1065
      ctl.showhost = 0;
1066
      break;
1067
    case 'x':
1068
      ctl.extended = 1;
1069
      break;
1070
    case 'n':
1071
      ctl.maxrecs = strtou32_or_err(optarg, _("failed to parse number"));
1072
      break;
1073
    case 'f':
1074
      if (!files)
1075
        files = xmalloc(sizeof(char *) * argc);
1076
      files[nfiles++] = optarg;
1077
      break;
1078
    case 'd':
1079
      ctl.usedns = 1;
1080
      break;
1081
    case 'i':
1082
      ctl.useip = 1;
1083
      break;
1084
    case 'a':
1085
      ctl.altlist = 1;
1086
      break;
1087
    case 'F':
1088
      ctl.time_fmt = LAST_TIMEFTM_CTIME;
1089
      break;
1090
    case 'p':
1091
      if (ul_parse_timestamp(optarg, &p) < 0)
1092
        errx(EXIT_FAILURE, _("invalid time value \"%s\""), optarg);
1093
      ctl.present = (time_t) (p / USEC_PER_SEC);
1094
      break;
1095
    case 's':
1096
      if (ul_parse_timestamp(optarg, &p) < 0)
1097
        errx(EXIT_FAILURE, _("invalid time value \"%s\""), optarg);
1098
      ctl.since = (time_t) (p / USEC_PER_SEC);
1099
      break;
1100
    case 't':
1101
      if (ul_parse_timestamp(optarg, &p) < 0)
1102
        errx(EXIT_FAILURE, _("invalid time value \"%s\""), optarg);
1103
      ctl.until = (time_t) (p / USEC_PER_SEC);
1104
      break;
1105
    case 'w':
1106
      ctl.fullnames_mode = true;
1107
      break;
1108
    case '0': case '1': case '2': case '3': case '4':
1109
    case '5': case '6': case '7': case '8': case '9':
1110
      digit = 1;
1111
      if (c == '0' && nb.pos == 1 && nb.buffer[0] == '0')
1112
        ; /* keep only one leading zero */
1113
      else if (nb.pos < sizeof(nb.buffer) - 1)
1114
        nb.buffer[nb.pos++] = c;
1115
      break;
1116
    case OPT_TIME_FORMAT:
1117
      ctl.time_fmt = which_time_format(optarg);
1118
      break;
1119
    case 'T':
1120
      ctl.separator = '\t';
1121
      break;
1122
    default:
1123
      errtryhelp(EXIT_FAILURE);
1124
    }
1125
1126
    /* parsed no digit or reached end of argument? */
1127
    if (!digit || numind != optind) {
1128
      number_parse(&nb, &ctl.maxrecs);
1129
      numind = optind;
1130
    }
1131
  }
1132
1133
  number_parse(&nb, &ctl.maxrecs);
1134
1135
  if (optind < argc)
1136
    ctl.show = argv + optind;
1137
1138
  if (!files) {
1139
    files = xmalloc(sizeof(char *));
1140
    files[nfiles++] = ctl.lastb ? _PATH_BTMP : _PATH_WTMP;
1141
  }
1142
1143
  for (i = 0; i < nfiles; i++) {
1144
    get_boot_time(&ctl.boot_time);
1145
    process_wtmp_file(&ctl, files[i]);
1146
  }
1147
  free(files);
1148
  return EXIT_SUCCESS;
1149
}
1150
#endif