]> git.ipfire.org Git - thirdparty/util-linux.git/blob - login-utils/last.c
Merge branch 'PR/libsmartcols-cell-data' of github.com:karelzak/util-linux-work
[thirdparty/util-linux.git] / login-utils / last.c
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, write to the Free Software
25 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
26 */
27 #include <sys/types.h>
28 #include <sys/stat.h>
29 #include <fcntl.h>
30 #include <time.h>
31 #include <stdio.h>
32 #include <ctype.h>
33 #include <utmpx.h>
34 #include <pwd.h>
35 #include <stdlib.h>
36 #include <unistd.h>
37 #include <string.h>
38 #include <signal.h>
39 #include <getopt.h>
40 #include <netinet/in.h>
41 #include <netdb.h>
42 #include <arpa/inet.h>
43 #include <libgen.h>
44
45 #include "c.h"
46 #include "nls.h"
47 #include "optutils.h"
48 #include "pathnames.h"
49 #include "xalloc.h"
50 #include "closestream.h"
51 #include "carefulputc.h"
52 #include "strutils.h"
53 #include "timeutils.h"
54 #include "monotonic.h"
55 #include "fileutils.h"
56
57 #ifdef FUZZ_TARGET
58 #include "fuzz.h"
59 #endif
60
61 #ifndef SHUTDOWN_TIME
62 # define SHUTDOWN_TIME 254
63 #endif
64
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
73 #ifndef LAST_TIMESTAMP_LEN
74 # define LAST_TIMESTAMP_LEN 32
75 #endif
76
77 #define UCHUNKSIZE 16384 /* How much we read at once. */
78
79 struct last_control {
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 */
85 useip :1; /* Print IP address in number format */
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
93 struct timeval boot_time; /* system boot time */
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 */
97 unsigned int time_fmt; /* time format */
98 char separator; /* output separator */
99 };
100
101 /* Double linked list of struct utmp's */
102 struct utmplist {
103 struct utmpx ut;
104 struct utmplist *next;
105 struct utmplist *prev;
106 };
107
108 /* Types of listing */
109 enum {
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. */
115 R_REBOOT_CRASH, /* Reboot record without matching shutdown */
116 R_PHANTOM, /* No logout record but session is stale. */
117 R_TIMECHANGE /* NEW_TIME or OLD_TIME */
118 };
119
120 enum {
121 LAST_TIMEFTM_NONE = 0,
122 LAST_TIMEFTM_SHORT,
123 LAST_TIMEFTM_CTIME,
124 LAST_TIMEFTM_ISO8601,
125
126 LAST_TIMEFTM_HHMM, /* non-public */
127 };
128
129 struct last_timefmt {
130 const char *name;
131 int in_len; /* log-in */
132 int in_fmt;
133 int out_len; /* log-out */
134 int out_fmt;
135 };
136
137 static struct last_timefmt timefmts[] = {
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",
155 .in_len = 25,
156 .out_len = 27,
157 .in_fmt = LAST_TIMEFTM_ISO8601,
158 .out_fmt = LAST_TIMEFTM_ISO8601
159 }
160 };
161
162 /* Global variables */
163 static unsigned int recsdone; /* Number of records listed */
164 static time_t lastdate; /* Last date we've seen */
165 static time_t currentdate; /* date when we started processing the file */
166
167 #ifndef FUZZ_TARGET
168 /* --time-format=option parser */
169 static int which_time_format(const char *s)
170 {
171 size_t i;
172
173 for (i = 0; i < ARRAY_SIZE(timefmts); i++) {
174 if (strcmp(timefmts[i].name, s) == 0)
175 return i;
176 }
177 errx(EXIT_FAILURE, _("unknown time format: %s"), s);
178 }
179 #endif
180
181 /*
182 * Read one utmp entry, return in new format.
183 * Automatically reposition file pointer.
184 */
185 static int uread(FILE *fp, struct utmpx *u, int *quit, const char *filename)
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 */
198 return fread(u, sizeof(struct utmpx), 1, fp);
199 }
200
201 if (u == NULL) {
202 /*
203 * Initialize and position.
204 */
205 utsize = sizeof(struct utmpx);
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) {
212 warn(_("seek on %s failed"), filename);
213 return 0;
214 }
215 bpos = (int)(fpos - o);
216 if (fread(buf, bpos, 1, fp) != 1) {
217 warn(_("cannot read %s"), filename);
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) {
229 memcpy(u, buf + bpos, sizeof(struct utmpx));
230 return 1;
231 }
232
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) {
246 warn(_("seek on %s failed"), filename);
247 return 0;
248 }
249
250 /*
251 * Read another UCHUNKSIZE bytes.
252 */
253 if (fread(buf, UCHUNKSIZE, 1, fp) != 1) {
254 warn(_("cannot read %s"), filename);
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
265 memcpy(u, tmp, sizeof(struct utmpx));
266
267 return 1;
268 }
269
270 #ifndef FUZZ_TARGET
271 /*
272 * SIGINT handler
273 */
274 static void int_handler(int sig __attribute__((unused)))
275 {
276 ul_sig_err(EXIT_FAILURE, "Interrupted");
277 }
278
279 /*
280 * SIGQUIT handler
281 */
282 static void quit_handler(int sig __attribute__((unused)))
283 {
284 ul_sig_warn("Interrupted");
285 signal(SIGQUIT, quit_handler);
286 }
287 #endif
288
289 /*
290 * Lookup a host with DNS.
291 */
292 static 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;
299
300 flags = useip ? NI_NUMERICHOST : 0;
301
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);
328 }
329
330 return getnameinfo(sa, salen, result, size, NULL, 0, flags);
331 }
332
333 static int time_formatter(int fmt, char *dst, size_t dlen, time_t *when)
334 {
335 int ret = 0;
336
337 switch (fmt) {
338 case LAST_TIMEFTM_NONE:
339 *dst = 0;
340 break;
341 case LAST_TIMEFTM_HHMM:
342 {
343 struct tm tm;
344
345 localtime_r(when, &tm);
346 if (!snprintf(dst, dlen, "%02d:%02d", tm.tm_hour, tm.tm_min))
347 ret = -1;
348 break;
349 }
350 case LAST_TIMEFTM_CTIME:
351 {
352 char buf[CTIME_BUFSIZ];
353
354 ctime_r(when, buf);
355 snprintf(dst, dlen, "%s", buf);
356 ret = rtrim_whitespace((unsigned char *) dst);
357 break;
358 }
359 case LAST_TIMEFTM_ISO8601:
360 ret = strtime_iso(when, ISO_TIMESTAMP_T, dst, dlen);
361 break;
362 default:
363 abort();
364 }
365 return ret;
366 }
367
368 /*
369 * Remove trailing spaces from a string.
370 */
371 static 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
385 /*
386 * Show one line of information on screen
387 */
388 static int list(const struct last_control *ctl, struct utmpx *p, time_t logout_time, int what)
389 {
390 time_t secs, utmp_time;
391 char logintime[LAST_TIMESTAMP_LEN];
392 char logouttime[LAST_TIMESTAMP_LEN];
393 char length[LAST_TIMESTAMP_LEN];
394 char final[512];
395 char utline[sizeof(p->ut_line) + 1];
396 char domain[256];
397 int mins, hours, days;
398 int r, len;
399 struct last_timefmt *fmt;
400
401 /*
402 * uucp and ftp have special-type entries
403 */
404 mem2strcpy(utline, p->ut_line, sizeof(p->ut_line), sizeof(utline));
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 /*
411 * Is this something we want to show?
412 */
413 if (ctl->show) {
414 char **walk;
415 for (walk = ctl->show; *walk; walk++) {
416 if (strncmp(p->ut_user, *walk, sizeof(p->ut_user)) == 0 ||
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;
422 }
423
424 /*
425 * Calculate times
426 */
427 fmt = &timefmts[ctl->time_fmt];
428
429 utmp_time = p->ut_tv.tv_sec;
430
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 }
437
438 /* log-in time */
439 if (time_formatter(fmt->in_fmt, logintime,
440 sizeof(logintime), &utmp_time) < 0)
441 errx(EXIT_FAILURE, _("preallocation size exceeded"));
442
443 /* log-out time */
444 secs = logout_time - utmp_time; /* Under strange circumstances, secs < 0 can happen */
445 mins = (secs / 60) % 60;
446 hours = (secs / 3600) % 24;
447 days = secs / 86400;
448
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
454 if (logout_time == currentdate) {
455 if (ctl->time_fmt > LAST_TIMEFTM_SHORT) {
456 snprintf(logouttime, sizeof(logouttime), " still running");
457 length[0] = 0;
458 } else {
459 snprintf(logouttime, sizeof(logouttime), " still");
460 snprintf(length, sizeof(length), "running");
461 }
462 } else if (days) {
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 */
464 } else if (hours) {
465 snprintf(length, sizeof(length), " (%02d:%02d)", hours, abs(mins)); /* mins always shown as positive (w/o minus sign!) even if secs < 0 */
466 } else if (secs >= 0) {
467 snprintf(length, sizeof(length), " (%02d:%02d)", hours, mins);
468 } else {
469 snprintf(length, sizeof(length), " (-00:%02d)", abs(mins)); /* mins always shown as positive (w/o minus sign!) even if secs < 0 */
470 }
471
472 switch(what) {
473 case R_CRASH:
474 case R_REBOOT_CRASH:
475 snprintf(logouttime, sizeof(logouttime), "- crash");
476 break;
477 case R_DOWN:
478 snprintf(logouttime, sizeof(logouttime), "- down ");
479 break;
480 case R_NOW:
481 if (ctl->time_fmt > LAST_TIMEFTM_SHORT) {
482 snprintf(logouttime, sizeof(logouttime), " still logged in");
483 length[0] = 0;
484 } else {
485 snprintf(logouttime, sizeof(logouttime), " still");
486 snprintf(length, sizeof(length), "logged in");
487 }
488 break;
489 case R_PHANTOM:
490 if (ctl->time_fmt > LAST_TIMEFTM_SHORT) {
491 snprintf(logouttime, sizeof(logouttime), " gone - no logout");
492 length[0] = 0;
493 } else if (ctl->time_fmt == LAST_TIMEFTM_SHORT) {
494 snprintf(logouttime, sizeof(logouttime), " gone");
495 snprintf(length, sizeof(length), "- no logout");
496 } else {
497 logouttime[0] = 0;
498 snprintf(length, sizeof(length), "no logout");
499 }
500 break;
501 case R_TIMECHANGE:
502 logouttime[0] = 0;
503 length[0] = 0;
504 break;
505 case R_NORMAL:
506 case R_REBOOT:
507 break;
508 default:
509 abort();
510 }
511
512 /*
513 * Look up host with DNS if needed.
514 */
515 r = -1;
516 if (ctl->usedns || ctl->useip)
517 r = dns_lookup(domain, sizeof(domain), ctl->useip, (int32_t*)p->ut_addr_v6);
518 if (r < 0)
519 mem2strcpy(domain, p->ut_host, sizeof(p->ut_host), sizeof(domain));
520
521 if (ctl->showhost) {
522 if (!ctl->altlist) {
523 len = snprintf(final, sizeof(final),
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);
529 } else {
530 len = snprintf(final, sizeof(final),
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);
535 }
536 } else
537 len = snprintf(final, sizeof(final),
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);
542
543 #if defined(__GLIBC__)
544 # if (__GLIBC__ == 2) && (__GLIBC_MINOR__ == 0)
545 final[sizeof(final)-1] = '\0';
546 # endif
547 #endif
548
549 trim_trailing_spaces(final);
550 /*
551 * Print out "final" string safely.
552 */
553 fputs_careful(final, stdout, '*', false, 0);
554
555 if (len < 0 || (size_t)len >= sizeof(final))
556 putchar('\n');
557
558 recsdone++;
559 if (ctl->maxrecs && ctl->maxrecs <= recsdone)
560 return 1;
561
562 return 0;
563 }
564
565 #ifndef FUZZ_TARGET
566 static void __attribute__((__noreturn__)) usage(const struct last_control *ctl)
567 {
568 FILE *out = stdout;
569 fputs(USAGE_HEADER, out);
570 fprintf(out, _(
571 " %s [options] [<username>...] [<tty>...]\n"), program_invocation_short_name);
572
573 fputs(USAGE_SEPARATOR, out);
574 fputs(_("Show a listing of last logged in users.\n"), out);
575
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,
581 _(" -f, --file <file> use a specific file instead of %s\n"), ctl->lastb ? _PATH_BTMP : _PATH_WTMP);
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);
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);
588 fputs(_(" -T, --tab-separated use tabs as delimiters\n"), out);
589 fputs(_(" -p, --present <time> display who were present at the specified time\n"), out);
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);
592 fputs(_(" --time-format <format> show timestamps in the specified <format>:\n"
593 " notime|short|full|iso\n"), out);
594
595 fputs(USAGE_SEPARATOR, out);
596 fprintf(out, USAGE_HELP_OPTIONS(22));
597 fprintf(out, USAGE_MAN_TAIL("last(1)"));
598
599 exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
600 }
601 #endif
602
603 static int is_phantom(const struct last_control *ctl, struct utmpx *ut)
604 {
605 struct passwd *pw;
606 char path[sizeof(ut->ut_line) + 16];
607 char user[sizeof(ut->ut_user) + 1];
608 int ret = 0;
609
610 if (ut->ut_tv.tv_sec < ctl->boot_time.tv_sec)
611 return 1;
612
613 mem2strcpy(user, ut->ut_user, sizeof(ut->ut_user), sizeof(user));
614 pw = getpwnam(user);
615 if (!pw)
616 return 1;
617 snprintf(path, sizeof(path), "/proc/%u/loginuid", ut->ut_pid);
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;
631 char utline[sizeof(ut->ut_line) + 1];
632
633 mem2strcpy(utline, ut->ut_line, sizeof(ut->ut_line), sizeof(utline));
634
635 snprintf(path, sizeof(path), "/dev/%s", utline);
636 if (stat(path, &st))
637 return 1;
638 if (pw->pw_uid != st.st_uid)
639 return 1;
640 }
641 return ret;
642 }
643
644 static void process_wtmp_file(const struct last_control *ctl,
645 const char *filename)
646 {
647 FILE *fp; /* File pointer of wtmp file */
648
649 struct utmpx ut; /* Current utmp entry */
650 struct utmplist *ulist = NULL; /* All entries */
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 */
664
665 #ifndef FUZZ_TARGET
666 time(&lastdown);
667 #else
668 lastdown = 1596001948;
669 #endif
670 /*
671 * Fill in 'lastdate'
672 */
673 lastdate = currentdate = lastrch = lastdown;
674
675 #ifndef FUZZ_TARGET
676 /*
677 * Install signal handlers
678 */
679 signal(SIGINT, int_handler);
680 signal(SIGQUIT, quit_handler);
681 #endif
682
683 /*
684 * Open the utmp file
685 */
686 if ((fp = fopen(filename, "r")) == NULL)
687 err(EXIT_FAILURE, _("cannot open %s"), filename);
688
689 /*
690 * Optimize the buffer size.
691 */
692 setvbuf(fp, NULL, _IOFBF, UCHUNKSIZE);
693
694 /*
695 * Read first structure to capture the time field
696 */
697 if (uread(fp, &ut, NULL, filename) == 1)
698 begintime = ut.ut_tv.tv_sec;
699 else {
700 if (fstat(fileno(fp), &st) != 0)
701 err(EXIT_FAILURE, _("stat of %s failed"), filename);
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 */
710 uread(fp, NULL, NULL, filename);
711
712 /*
713 * Read struct after struct backwards from the file.
714 */
715 while (!quit) {
716
717 if (uread(fp, &ut, &quit, filename) != 1)
718 break;
719
720 if (ctl->since && ut.ut_tv.tv_sec < ctl->since)
721 continue;
722
723 if (ctl->until && ctl->until < ut.ut_tv.tv_sec)
724 continue;
725
726 lastdate = ut.ut_tv.tv_sec;
727
728 if (ctl->lastb) {
729 quit = list(ctl, &ut, ut.ut_tv.tv_sec, R_NORMAL);
730 continue;
731 }
732
733 /*
734 * Set ut_type to the correct type.
735 */
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*/
745 /*
746 * For stupid old applications that don't fill in
747 * ut_type correctly.
748 */
749 else {
750 if (ut.ut_type != DEAD_PROCESS &&
751 ut.ut_user[0] && ut.ut_line[0] &&
752 strncmp(ut.ut_user, "LOGIN", 5) != 0)
753 ut.ut_type = USER_PROCESS;
754 /*
755 * Even worse, applications that write ghost
756 * entries: ut_type set to USER_PROCESS but
757 * empty ut_user...
758 */
759 if (ut.ut_user[0] == 0)
760 ut.ut_type = DEAD_PROCESS;
761
762 /*
763 * Clock changes.
764 */
765 if (strncmp(ut.ut_user, "date", 4) == 0) {
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 }
771 }
772 #endif
773 switch (ut.ut_type) {
774 case SHUTDOWN_TIME:
775 if (ctl->extended) {
776 strcpy(ut.ut_line, "system down");
777 quit = list(ctl, &ut, lastboot, R_NORMAL);
778 }
779 lastdown = lastrch = ut.ut_tv.tv_sec;
780 down = 1;
781 break;
782 case OLD_TIME:
783 case NEW_TIME:
784 if (ctl->extended) {
785 strcpy(ut.ut_line,
786 ut.ut_type == NEW_TIME ? "new time" :
787 "old time");
788 quit = list(ctl, &ut, lastdown, R_TIMECHANGE);
789 }
790 break;
791 case BOOT_TIME:
792 strcpy(ut.ut_line, "system boot");
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);
797 lastboot = ut.ut_tv.tv_sec;
798 down = 1;
799 break;
800 case RUN_LVL:
801 x = ut.ut_pid & 255;
802 if (ctl->extended) {
803 snprintf(ut.ut_line, sizeof(ut.ut_line), "(to lvl %c)", x);
804 quit = list(ctl, &ut, lastrch, R_NORMAL);
805 }
806 if (x == '0' || x == '6') {
807 lastdown = ut.ut_tv.tv_sec;
808 down = 1;
809 ut.ut_type = SHUTDOWN_TIME;
810 }
811 lastrch = ut.ut_tv.tv_sec;
812 break;
813
814 case USER_PROCESS:
815 /*
816 * This was a login - show the first matching
817 * logout record and delete all records with
818 * the same ut_line.
819 */
820 c = 0;
821 for (p = ulist; p; p = next) {
822 next = p->next;
823 if (strncmp(p->ut.ut_line, ut.ut_line,
824 sizeof(ut.ut_line)) == 0) {
825 /* Show it */
826 if (c == 0) {
827 quit = list(ctl, &ut, p->ut.ut_tv.tv_sec, R_NORMAL);
828 c = 1;
829 }
830 if (p->next)
831 p->next->prev = p->prev;
832 if (p->prev)
833 p->prev->next = p->next;
834 else
835 ulist = p->next;
836 free(p);
837 }
838 }
839 /*
840 * Not found? Then crashed, down, still
841 * logged in, or missing logout record.
842 */
843 if (c == 0) {
844 if (!lastboot) {
845 c = R_NOW;
846 /* Is process still alive? */
847 if (is_phantom(ctl, &ut))
848 c = R_PHANTOM;
849 } else
850 c = whydown;
851 quit = list(ctl, &ut, lastboot, c);
852 }
853 /* fallthrough */
854
855 case DEAD_PROCESS:
856 /*
857 * Just store the data if it is
858 * interesting enough.
859 */
860 if (ut.ut_line[0] == 0)
861 break;
862 p = xmalloc(sizeof(struct utmplist));
863 memcpy(&p->ut, &ut, sizeof(struct utmpx));
864 p->next = ulist;
865 p->prev = NULL;
866 if (ulist)
867 ulist->prev = p;
868 ulist = p;
869 break;
870
871 case EMPTY:
872 case INIT_PROCESS:
873 case LOGIN_PROCESS:
874 #ifdef ACCOUNTING
875 case ACCOUNTING:
876 #endif
877 /* ignored ut_types */
878 break;
879
880 default:
881 warnx("unrecognized ut_type: %d", ut.ut_type);
882 }
883
884 /*
885 * If we saw a shutdown/reboot record we can remove
886 * the entire current ulist.
887 */
888 if (down) {
889 lastboot = ut.ut_tv.tv_sec;
890 whydown = (ut.ut_type == SHUTDOWN_TIME) ? R_DOWN : R_CRASH;
891 for (p = ulist; p; p = next) {
892 next = p->next;
893 free(p);
894 }
895 ulist = NULL;
896 down = 0;
897 }
898 }
899
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);
910 free(tmp);
911 }
912
913 fclose(fp);
914
915 for (p = ulist; p; p = next) {
916 next = p->next;
917 free(p);
918 }
919 }
920
921 #ifdef FUZZ_TARGET
922 # include "all-io.h"
923
924 int 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;
937
938 fd = mkstemp_cloexec(name);
939 if (fd < 0)
940 err(EXIT_FAILURE, "mkstemp() failed");
941 if (write_all(fd, data, size) != 0)
942 err(EXIT_FAILURE, "write() failed");
943
944 process_wtmp_file(&ctl, name);
945
946 close(fd);
947 unlink(name);
948
949 return 0;
950 }
951 #else
952 int main(int argc, char **argv)
953 {
954 struct last_control ctl = {
955 .showhost = TRUE,
956 .name_len = LAST_LOGIN_LEN,
957 .time_fmt = LAST_TIMEFTM_SHORT,
958 .domain_len = LAST_DOMAIN_LEN
959 };
960 char **files = NULL;
961 size_t i, nfiles = 0;
962 int c;
963 usec_t p;
964
965 enum {
966 OPT_TIME_FORMAT = CHAR_MAX + 1
967 };
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' },
975 { "since", required_argument, NULL, 's' },
976 { "until", required_argument, NULL, 't' },
977 { "present", required_argument, NULL, 'p' },
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' },
983 { "tab-separated", no_argument, NULL, 'T' },
984 { "time-format", required_argument, NULL, OPT_TIME_FORMAT },
985 { NULL, 0, NULL, 0 }
986 };
987 static const ul_excl_t excl[] = { /* rows and cols in ASCII order */
988 { 'F', OPT_TIME_FORMAT }, /* fulltime, time-format */
989 { 0 }
990 };
991 int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
992
993 setlocale(LC_ALL, "");
994 bindtextdomain(PACKAGE, LOCALEDIR);
995 textdomain(PACKAGE);
996 close_stdout_atexit();
997 /*
998 * Which file do we want to read?
999 */
1000 ctl.lastb = strcmp(program_invocation_short_name, "lastb") == 0 ? 1 : 0;
1001 ctl.separator = ' ';
1002 while ((c = getopt_long(argc, argv,
1003 "hVf:n:RxadFit:p:s:T0123456789w", long_opts, NULL)) != -1) {
1004
1005 err_exclusive_options(c, long_opts, excl, excl_st);
1006
1007 switch(c) {
1008 case 'h':
1009 usage(&ctl);
1010 break;
1011 case 'V':
1012 print_version(EXIT_SUCCESS);
1013 case 'R':
1014 ctl.showhost = 0;
1015 break;
1016 case 'x':
1017 ctl.extended = 1;
1018 break;
1019 case 'n':
1020 ctl.maxrecs = strtos32_or_err(optarg, _("failed to parse number"));
1021 break;
1022 case 'f':
1023 if (!files)
1024 files = xmalloc(sizeof(char *) * argc);
1025 files[nfiles++] = xstrdup(optarg);
1026 break;
1027 case 'd':
1028 ctl.usedns = 1;
1029 break;
1030 case 'i':
1031 ctl.useip = 1;
1032 break;
1033 case 'a':
1034 ctl.altlist = 1;
1035 break;
1036 case 'F':
1037 ctl.time_fmt = LAST_TIMEFTM_CTIME;
1038 break;
1039 case 'p':
1040 if (parse_timestamp(optarg, &p) < 0)
1041 errx(EXIT_FAILURE, _("invalid time value \"%s\""), optarg);
1042 ctl.present = (time_t) (p / 1000000);
1043 break;
1044 case 's':
1045 if (parse_timestamp(optarg, &p) < 0)
1046 errx(EXIT_FAILURE, _("invalid time value \"%s\""), optarg);
1047 ctl.since = (time_t) (p / 1000000);
1048 break;
1049 case 't':
1050 if (parse_timestamp(optarg, &p) < 0)
1051 errx(EXIT_FAILURE, _("invalid time value \"%s\""), optarg);
1052 ctl.until = (time_t) (p / 1000000);
1053 break;
1054 case 'w':
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);
1059 break;
1060 case '0': case '1': case '2': case '3': case '4':
1061 case '5': case '6': case '7': case '8': case '9':
1062 ctl.maxrecs = 10 * ctl.maxrecs + c - '0';
1063 break;
1064 case OPT_TIME_FORMAT:
1065 ctl.time_fmt = which_time_format(optarg);
1066 break;
1067 case 'T':
1068 ctl.separator = '\t';
1069 break;
1070 default:
1071 errtryhelp(EXIT_FAILURE);
1072 }
1073 }
1074
1075 if (optind < argc)
1076 ctl.show = argv + optind;
1077
1078 if (!files) {
1079 files = xmalloc(sizeof(char *));
1080 files[nfiles++] = xstrdup(ctl.lastb ? _PATH_BTMP : _PATH_WTMP);
1081 }
1082
1083 for (i = 0; i < nfiles; i++) {
1084 get_boot_time(&ctl.boot_time);
1085 process_wtmp_file(&ctl, files[i]);
1086 free(files[i]);
1087 }
1088 free(files);
1089 return EXIT_SUCCESS;
1090 }
1091 #endif