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