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