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