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