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