Coverage Report

Created: 2025-11-24 06:42

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