]> git.ipfire.org Git - thirdparty/util-linux.git/blame - login-utils/last.c
taskset: Accept 0 pid for current process
[thirdparty/util-linux.git] / login-utils / last.c
CommitLineData
6dbe3af9 1/*
4d1d1233 2 * last(1) from sysvinit project, merged into util-linux in Aug 2013.
6dbe3af9 3 *
4d1d1233
KZ
4 * Copyright (C) 1991-2004 Miquel van Smoorenburg.
5 * Copyright (C) 2013 Ondrej Oprala <ooprala@redhat.com>
77079432 6 * Karel Zak <kzak@redhat.com>
6dbe3af9 7 *
4d1d1233
KZ
8 * Re-implementation of the 'last' command, this time for Linux. Yes I know
9 * there is BSD last, but I just felt like writing this. No thanks :-). Also,
10 * this version gives lots more info (especially with -x)
ce602720 11 *
ce602720 12 *
4d1d1233
KZ
13 * This program is free software; you can redistribute it and/or modify
14 * it under the terms of the GNU General Public License as published by
15 * the Free Software Foundation; either version 2 of the License, or
16 * (at your option) any later version.
ba0c9030 17 *
4d1d1233
KZ
18 * This program is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU General Public License for more details.
ce602720 22 *
4d1d1233 23 * You should have received a copy of the GNU General Public License
762f295a 24 * along with this program. If not, see <https://gnu.org/licenses/>.
ba0c9030 25 */
7eda085c 26#include <sys/types.h>
ce602720 27#include <sys/stat.h>
9d76c6fb 28#include <fcntl.h>
6dbe3af9 29#include <time.h>
6dbe3af9 30#include <stdio.h>
ce602720 31#include <ctype.h>
b4b919fe 32#include <utmpx.h>
404fa3f9 33#include <pwd.h>
6dbe3af9
KZ
34#include <stdlib.h>
35#include <unistd.h>
ce602720
OO
36#include <string.h>
37#include <signal.h>
38#include <getopt.h>
7eda085c 39#include <netinet/in.h>
ce602720 40#include <netdb.h>
6dbe3af9 41#include <arpa/inet.h>
c6ba16c6 42#include <libgen.h>
7eda085c 43
293e0c90
KZ
44#include "c.h"
45#include "nls.h"
2ea747eb 46#include "optutils.h"
4c7dfea7 47#include "pathnames.h"
a9f789e5
KZ
48#include "xalloc.h"
49#include "closestream.h"
2e58cd40 50#include "carefulputc.h"
1bd68aab 51#include "strutils.h"
60f25dea 52#include "timeutils.h"
cd2876d2 53#include "monotonic.h"
adcd2c32 54#include "fileutils.h"
293e0c90 55
2cdbf06f
EV
56#ifdef FUZZ_TARGET
57#include "fuzz.h"
58#endif
59
ce602720 60#ifndef SHUTDOWN_TIME
4d1d1233 61# define SHUTDOWN_TIME 254
6dbe3af9 62#endif
ce602720 63
83484375
SK
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
45f11b6b
SK
72#ifndef LAST_TIMESTAMP_LEN
73# define LAST_TIMESTAMP_LEN 32
74#endif
75
ce602720
OO
76#define UCHUNKSIZE 16384 /* How much we read at once. */
77
83484375 78struct last_control {
63d79371
ZJS
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 */
83484375
SK
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
0e5218ac 92 struct timeval boot_time; /* system boot time */
83484375
SK
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 */
45f11b6b 96 unsigned int time_fmt; /* time format */
5d7df6b7 97 char separator; /* output separator */
937f0509
JS
98
99 bool fullnames_mode;
83484375
SK
100};
101
ce602720
OO
102/* Double linked list of struct utmp's */
103struct utmplist {
b4b919fe 104 struct utmpx ut;
4d1d1233
KZ
105 struct utmplist *next;
106 struct utmplist *prev;
ce602720 107};
ce602720
OO
108
109/* Types of listing */
e843d047
SK
110enum {
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. */
9d0b73a6 116 R_REBOOT_CRASH, /* Reboot record without matching shutdown */
e843d047
SK
117 R_PHANTOM, /* No logout record but session is stale. */
118 R_TIMECHANGE /* NEW_TIME or OLD_TIME */
119};
ce602720 120
45f11b6b
SK
121enum {
122 LAST_TIMEFTM_NONE = 0,
c7eb14d3
KZ
123 LAST_TIMEFTM_SHORT,
124 LAST_TIMEFTM_CTIME,
125 LAST_TIMEFTM_ISO8601,
126
127 LAST_TIMEFTM_HHMM, /* non-public */
45f11b6b
SK
128};
129
c87e4fbb
KZ
130struct last_timefmt {
131 const char *name;
c7eb14d3
KZ
132 int in_len; /* log-in */
133 int in_fmt;
134 int out_len; /* log-out */
135 int out_fmt;
45f11b6b
SK
136};
137
c87e4fbb 138static struct last_timefmt timefmts[] = {
c7eb14d3
KZ
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",
9fd0a7a9
WP
156 .in_len = 25,
157 .out_len = 27,
c7eb14d3
KZ
158 .in_fmt = LAST_TIMEFTM_ISO8601,
159 .out_fmt = LAST_TIMEFTM_ISO8601
160 }
45f11b6b
SK
161};
162
ce602720 163/* Global variables */
751947b6 164static unsigned int recsdone; /* Number of records listed */
77079432 165static time_t lastdate; /* Last date we've seen */
2e81d998 166static time_t currentdate; /* date when we started processing the file */
ce602720 167
2cdbf06f 168#ifndef FUZZ_TARGET
45f11b6b 169/* --time-format=option parser */
eb2306e6 170static int which_time_format(const char *s)
45f11b6b 171{
c87e4fbb
KZ
172 size_t i;
173
174 for (i = 0; i < ARRAY_SIZE(timefmts); i++) {
eb2306e6 175 if (strcmp(timefmts[i].name, s) == 0)
c87e4fbb
KZ
176 return i;
177 }
eb2306e6 178 errx(EXIT_FAILURE, _("unknown time format: %s"), s);
45f11b6b 179}
2cdbf06f 180#endif
45f11b6b 181
ce602720
OO
182/*
183 * Read one utmp entry, return in new format.
184 * Automatically reposition file pointer.
185 */
b4b919fe 186static int uread(FILE *fp, struct utmpx *u, int *quit, const char *filename)
ce602720
OO
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 */
b4b919fe 199 return fread(u, sizeof(struct utmpx), 1, fp);
ce602720
OO
200 }
201
202 if (u == NULL) {
203 /*
204 * Initialize and position.
205 */
b4b919fe 206 utsize = sizeof(struct utmpx);
ce602720
OO
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) {
744c7fec 213 warn(_("seek on %s failed"), filename);
ce602720
OO
214 return 0;
215 }
216 bpos = (int)(fpos - o);
217 if (fread(buf, bpos, 1, fp) != 1) {
744c7fec 218 warn(_("cannot read %s"), filename);
ce602720
OO
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) {
b4b919fe 230 memcpy(u, buf + bpos, sizeof(struct utmpx));
ce602720 231 return 1;
6dbe3af9 232 }
db797f24 233
ce602720
OO
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) {
744c7fec 247 warn(_("seek on %s failed"), filename);
ce602720
OO
248 return 0;
249 }
250
251 /*
252 * Read another UCHUNKSIZE bytes.
253 */
254 if (fread(buf, UCHUNKSIZE, 1, fp) != 1) {
744c7fec 255 warn(_("cannot read %s"), filename);
ce602720
OO
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
b4b919fe 266 memcpy(u, tmp, sizeof(struct utmpx));
ce602720
OO
267
268 return 1;
6dbe3af9
KZ
269}
270
2cdbf06f 271#ifndef FUZZ_TARGET
6dbe3af9 272/*
ce602720 273 * SIGINT handler
6dbe3af9 274 */
ce602720
OO
275static void int_handler(int sig __attribute__((unused)))
276{
32457b5e 277 ul_sig_err(EXIT_FAILURE, "Interrupted");
ce602720 278}
8e5cf69f 279
ce602720
OO
280/*
281 * SIGQUIT handler
282 */
283static void quit_handler(int sig __attribute__((unused)))
284{
32457b5e 285 ul_sig_warn("Interrupted");
ce602720
OO
286 signal(SIGQUIT, quit_handler);
287}
2cdbf06f 288#endif
6dbe3af9 289
ce602720
OO
290/*
291 * Lookup a host with DNS.
292 */
293static 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;
6dbe3af9 300
ce602720 301 flags = useip ? NI_NUMERICHOST : 0;
6dbe3af9 302
ce602720
OO
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);
c07ebfa1 329 }
ce602720
OO
330
331 return getnameinfo(sa, salen, result, size, NULL, 0, flags);
6dbe3af9
KZ
332}
333
c7eb14d3 334static int time_formatter(int fmt, char *dst, size_t dlen, time_t *when)
45f11b6b 335{
45f11b6b
SK
336 int ret = 0;
337
c7eb14d3 338 switch (fmt) {
45f11b6b
SK
339 case LAST_TIMEFTM_NONE:
340 *dst = 0;
341 break;
c7eb14d3
KZ
342 case LAST_TIMEFTM_HHMM:
343 {
3160589d
SK
344 struct tm tm;
345
346 localtime_r(when, &tm);
347 if (!snprintf(dst, dlen, "%02d:%02d", tm.tm_hour, tm.tm_min))
c7eb14d3 348 ret = -1;
45f11b6b 349 break;
c7eb14d3
KZ
350 }
351 case LAST_TIMEFTM_CTIME:
3160589d
SK
352 {
353 char buf[CTIME_BUFSIZ];
354
75822efb 355 if (!ctime_r(when, buf)) {
356 ret = -1;
357 break;
358 }
3160589d 359 snprintf(dst, dlen, "%s", buf);
78acfddd 360 ret = rtrim_whitespace((unsigned char *) dst);
45f11b6b 361 break;
3160589d 362 }
45f11b6b 363 case LAST_TIMEFTM_ISO8601:
4111bb3a 364 ret = strtime_iso(when, ISO_TIMESTAMP_T, dst, dlen);
45f11b6b
SK
365 break;
366 default:
367 abort();
368 }
369 return ret;
370}
371
97bbb5e4
SK
372/*
373 * Remove trailing spaces from a string.
374 */
375static 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
6dbe3af9 389/*
ce602720 390 * Show one line of information on screen
6dbe3af9 391 */
b4b919fe 392static int list(const struct last_control *ctl, struct utmpx *p, time_t logout_time, int what)
ce602720 393{
2e81d998 394 time_t secs, utmp_time;
45f11b6b
SK
395 char logintime[LAST_TIMESTAMP_LEN];
396 char logouttime[LAST_TIMESTAMP_LEN];
397 char length[LAST_TIMESTAMP_LEN];
ce602720 398 char final[512];
a924b400 399 char utline[sizeof(p->ut_line) + 1];
ce602720 400 char domain[256];
ce602720
OO
401 int mins, hours, days;
402 int r, len;
c87e4fbb 403 struct last_timefmt *fmt;
6dbe3af9 404
ce602720
OO
405 /*
406 * uucp and ftp have special-type entries
407 */
0675f52a 408 mem2strcpy(utline, p->ut_line, sizeof(p->ut_line), sizeof(utline));
ce602720
OO
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 /*
0e65dcde 415 * Is this something we want to show?
ce602720 416 */
83484375 417 if (ctl->show) {
328734c0 418 char **walk;
83484375 419 for (walk = ctl->show; *walk; walk++) {
a924b400 420 if (strncmp(p->ut_user, *walk, sizeof(p->ut_user)) == 0 ||
ce602720
OO
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;
7eda085c 426 }
ce602720
OO
427
428 /*
429 * Calculate times
430 */
c7eb14d3
KZ
431 fmt = &timefmts[ctl->time_fmt];
432
55771f54 433 utmp_time = p->ut_tv.tv_sec;
37f534a6 434
d4ce90d4
SK
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 }
c7eb14d3
KZ
441
442 /* log-in time */
443 if (time_formatter(fmt->in_fmt, logintime,
444 sizeof(logintime), &utmp_time) < 0)
45f11b6b
SK
445 errx(EXIT_FAILURE, _("preallocation size exceeded"));
446
c7eb14d3 447 /* log-out time */
e1787b1a 448 secs = logout_time - utmp_time; /* Under strange circumstances, secs < 0 can happen */
ce602720
OO
449 mins = (secs / 60) % 60;
450 hours = (secs / 3600) % 24;
451 days = secs / 86400;
31d28e09 452
c4482f7b
KZ
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
2e81d998 458 if (logout_time == currentdate) {
c7eb14d3 459 if (ctl->time_fmt > LAST_TIMEFTM_SHORT) {
79fb7e18 460 snprintf(logouttime, sizeof(logouttime), " still running");
d3108db1
RM
461 length[0] = 0;
462 } else {
79fb7e18
KZ
463 snprintf(logouttime, sizeof(logouttime), " still");
464 snprintf(length, sizeof(length), "running");
31d28e09 465 }
c7eb14d3 466 } else if (days) {
79fb7e18 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 */
e1787b1a 468 } else if (hours) {
79fb7e18 469 snprintf(length, sizeof(length), " (%02d:%02d)", hours, abs(mins)); /* mins always shown as positive (w/o minus sign!) even if secs < 0 */
62f3e715 470 } else if (secs >= 0) {
79fb7e18 471 snprintf(length, sizeof(length), " (%02d:%02d)", hours, mins);
c7eb14d3 472 } else {
79fb7e18 473 snprintf(length, sizeof(length), " (-00:%02d)", abs(mins)); /* mins always shown as positive (w/o minus sign!) even if secs < 0 */
c7eb14d3 474 }
ce602720
OO
475
476 switch(what) {
477 case R_CRASH:
9d0b73a6 478 case R_REBOOT_CRASH:
79fb7e18 479 snprintf(logouttime, sizeof(logouttime), "- crash");
ce602720
OO
480 break;
481 case R_DOWN:
79fb7e18 482 snprintf(logouttime, sizeof(logouttime), "- down ");
ce602720
OO
483 break;
484 case R_NOW:
c7eb14d3 485 if (ctl->time_fmt > LAST_TIMEFTM_SHORT) {
79fb7e18 486 snprintf(logouttime, sizeof(logouttime), " still logged in");
4fcbfaa8
RM
487 length[0] = 0;
488 } else {
79fb7e18
KZ
489 snprintf(logouttime, sizeof(logouttime), " still");
490 snprintf(length, sizeof(length), "logged in");
ce602720 491 }
6dbe3af9 492 break;
ce602720 493 case R_PHANTOM:
c7eb14d3 494 if (ctl->time_fmt > LAST_TIMEFTM_SHORT) {
79fb7e18 495 snprintf(logouttime, sizeof(logouttime), " gone - no logout");
4fcbfaa8 496 length[0] = 0;
c7eb14d3 497 } else if (ctl->time_fmt == LAST_TIMEFTM_SHORT) {
79fb7e18
KZ
498 snprintf(logouttime, sizeof(logouttime), " gone");
499 snprintf(length, sizeof(length), "- no logout");
6e69f1ee
RM
500 } else {
501 logouttime[0] = 0;
79fb7e18 502 snprintf(length, sizeof(length), "no logout");
ce602720
OO
503 }
504 break;
ce602720
OO
505 case R_TIMECHANGE:
506 logouttime[0] = 0;
507 length[0] = 0;
6dbe3af9 508 break;
ce602720 509 case R_NORMAL:
c7eb14d3 510 case R_REBOOT:
6dbe3af9 511 break;
5cae90f9
SK
512 default:
513 abort();
ce602720
OO
514 }
515
516 /*
517 * Look up host with DNS if needed.
518 */
519 r = -1;
83484375 520 if (ctl->usedns || ctl->useip)
f2ff0adf 521 r = dns_lookup(domain, sizeof(domain), ctl->useip, (int32_t*)p->ut_addr_v6);
22fbfdb8
KZ
522 if (r < 0)
523 mem2strcpy(domain, p->ut_host, sizeof(p->ut_host), sizeof(domain));
c87e4fbb 524
937f0509
JS
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
83484375
SK
532 if (ctl->showhost) {
533 if (!ctl->altlist) {
937f0509
JS
534
535 if (!ctl->fullnames_mode && (strnlen(domain, sizeof(domain)) > ctl->domain_len))
536 domain[ctl->domain_len-1] = '*';
537
ce602720 538 len = snprintf(final, sizeof(final),
5d7df6b7
TD
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);
ce602720
OO
544 } else {
545 len = snprintf(final, sizeof(final),
5d7df6b7
TD
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);
5818a4aa 550 }
ce602720
OO
551 } else
552 len = snprintf(final, sizeof(final),
5d7df6b7
TD
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);
ce602720
OO
557
558#if defined(__GLIBC__)
559# if (__GLIBC__ == 2) && (__GLIBC_MINOR__ == 0)
560 final[sizeof(final)-1] = '\0';
561# endif
562#endif
6dbe3af9 563
97bbb5e4 564 trim_trailing_spaces(final);
ce602720
OO
565 /*
566 * Print out "final" string safely.
567 */
aa13246a 568 fputs_careful(final, stdout, '*', false, 0);
ce602720
OO
569
570 if (len < 0 || (size_t)len >= sizeof(final))
571 putchar('\n');
572
573 recsdone++;
83484375 574 if (ctl->maxrecs && ctl->maxrecs <= recsdone)
ce602720
OO
575 return 1;
576
577 return 0;
6dbe3af9
KZ
578}
579
2cdbf06f 580#ifndef FUZZ_TARGET
86be6a32 581static void __attribute__((__noreturn__)) usage(const struct last_control *ctl)
ce602720 582{
86be6a32 583 FILE *out = stdout;
293e0c90
KZ
584 fputs(USAGE_HEADER, out);
585 fprintf(out, _(
b8672754 586 " %s [options] [<username>...] [<tty>...]\n"), program_invocation_short_name);
293e0c90 587
451dbcfa
BS
588 fputs(USAGE_SEPARATOR, out);
589 fputs(_("Show a listing of last logged in users.\n"), out);
590
293e0c90
KZ
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,
f06abd22 596 _(" -f, --file <file> use a specific file instead of %s\n"), ctl->lastb ? _PATH_BTMP : _PATH_WTMP);
293e0c90
KZ
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);
25f43bcc 600 fputs(_(" -p, --present <time> display who were present at the specified time\n"), out);
293e0c90 601 fputs(_(" -R, --nohostname don't display the hostname field\n"), out);
dd50168b
SK
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);
25f43bcc 604 fputs(_(" -T, --tab-separated use tabs as delimiters\n"), out);
09af3db4
BS
605 fputs(_(" --time-format <format> show timestamps in the specified <format>:\n"
606 " notime|short|full|iso\n"), out);
25f43bcc
BS
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);
293e0c90
KZ
609
610 fputs(USAGE_SEPARATOR, out);
bad4c729
MY
611 fprintf(out, USAGE_HELP_OPTIONS(22));
612 fprintf(out, USAGE_MAN_TAIL("last(1)"));
293e0c90
KZ
613
614 exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
6dbe3af9 615}
2cdbf06f 616#endif
6dbe3af9 617
b4b919fe 618static int is_phantom(const struct last_control *ctl, struct utmpx *ut)
404fa3f9
SK
619{
620 struct passwd *pw;
f64ddc95 621 char path[sizeof(ut->ut_line) + 16];
4b646f01 622 char user[sizeof(ut->ut_user) + 1];
0e5218ac 623 int ret = 0;
404fa3f9 624
55771f54 625 if (ut->ut_tv.tv_sec < ctl->boot_time.tv_sec)
0e5218ac 626 return 1;
4b646f01
KZ
627
628 mem2strcpy(user, ut->ut_user, sizeof(ut->ut_user), sizeof(user));
629 pw = getpwnam(user);
404fa3f9
SK
630 if (!pw)
631 return 1;
10a0a9cb 632 snprintf(path, sizeof(path), "/proc/%u/loginuid", ut->ut_pid);
8813da1e
SK
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;
10a0a9cb 646 char utline[sizeof(ut->ut_line) + 1];
2e118e70 647
10a0a9cb
KZ
648 mem2strcpy(utline, ut->ut_line, sizeof(ut->ut_line), sizeof(utline));
649
650 snprintf(path, sizeof(path), "/dev/%s", utline);
8813da1e
SK
651 if (stat(path, &st))
652 return 1;
653 if (pw->pw_uid != st.st_uid)
654 return 1;
655 }
404fa3f9
SK
656 return ret;
657}
293e0c90 658
744c7fec
KZ
659static void process_wtmp_file(const struct last_control *ctl,
660 const char *filename)
ce602720 661{
9e930041 662 FILE *fp; /* File pointer of wtmp file */
77079432 663
b4b919fe 664 struct utmpx ut; /* Current utmp entry */
da1a8ed0 665 struct utmplist *ulist = NULL; /* All entries */
77079432
KZ
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 */
77079432 679
2cdbf06f 680#ifndef FUZZ_TARGET
77079432 681 time(&lastdown);
2cdbf06f
EV
682#else
683 lastdown = 1596001948;
684#endif
77079432
KZ
685 /*
686 * Fill in 'lastdate'
687 */
2e81d998 688 lastdate = currentdate = lastrch = lastdown;
ce602720 689
2cdbf06f 690#ifndef FUZZ_TARGET
77079432
KZ
691 /*
692 * Install signal handlers
693 */
694 signal(SIGINT, int_handler);
695 signal(SIGQUIT, quit_handler);
2cdbf06f 696#endif
ce602720 697
77079432
KZ
698 /*
699 * Open the utmp file
700 */
da1a8ed0
KZ
701 if ((fp = fopen(filename, "r")) == NULL)
702 err(EXIT_FAILURE, _("cannot open %s"), filename);
6dbe3af9
KZ
703
704 /*
77079432 705 * Optimize the buffer size.
ce602720 706 */
77079432
KZ
707 setvbuf(fp, NULL, _IOFBF, UCHUNKSIZE);
708
ce602720 709 /*
77079432 710 * Read first structure to capture the time field
6dbe3af9 711 */
744c7fec 712 if (uread(fp, &ut, NULL, filename) == 1)
55771f54 713 begintime = ut.ut_tv.tv_sec;
ce602720 714 else {
d879e052 715 if (fstat(fileno(fp), &st) != 0)
da1a8ed0 716 err(EXIT_FAILURE, _("stat of %s failed"), filename);
77079432
KZ
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 */
744c7fec 725 uread(fp, NULL, NULL, filename);
77079432
KZ
726
727 /*
728 * Read struct after struct backwards from the file.
729 */
730 while (!quit) {
731
744c7fec 732 if (uread(fp, &ut, &quit, filename) != 1)
77079432
KZ
733 break;
734
55771f54 735 if (ctl->since && ut.ut_tv.tv_sec < ctl->since)
dd50168b
SK
736 continue;
737
55771f54 738 if (ctl->until && ctl->until < ut.ut_tv.tv_sec)
77079432
KZ
739 continue;
740
55771f54 741 lastdate = ut.ut_tv.tv_sec;
77079432 742
83484375 743 if (ctl->lastb) {
55771f54 744 quit = list(ctl, &ut, ut.ut_tv.tv_sec, R_NORMAL);
77079432
KZ
745 continue;
746 }
747
ce602720 748 /*
77079432 749 * Set ut_type to the correct type.
ce602720 750 */
77079432
KZ
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*/
ce602720 760 /*
77079432
KZ
761 * For stupid old applications that don't fill in
762 * ut_type correctly.
ce602720 763 */
77079432
KZ
764 else {
765 if (ut.ut_type != DEAD_PROCESS &&
cfa7fe89 766 ut.ut_user[0] && ut.ut_line[0] &&
6cd00432 767 strncmp(ut.ut_user, "LOGIN", 5) != 0)
77079432
KZ
768 ut.ut_type = USER_PROCESS;
769 /*
770 * Even worse, applications that write ghost
771 * entries: ut_type set to USER_PROCESS but
cfa7fe89 772 * empty ut_user...
77079432 773 */
cfa7fe89 774 if (ut.ut_user[0] == 0)
77079432
KZ
775 ut.ut_type = DEAD_PROCESS;
776
777 /*
778 * Clock changes.
779 */
6cd00432 780 if (strncmp(ut.ut_user, "date", 4) == 0) {
77079432
KZ
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 }
6dbe3af9 786 }
ce602720 787#endif
77079432 788 switch (ut.ut_type) {
ce602720 789 case SHUTDOWN_TIME:
83484375 790 if (ctl->extended) {
ce602720 791 strcpy(ut.ut_line, "system down");
83484375 792 quit = list(ctl, &ut, lastboot, R_NORMAL);
ce602720 793 }
55771f54 794 lastdown = lastrch = ut.ut_tv.tv_sec;
ce602720
OO
795 down = 1;
796 break;
797 case OLD_TIME:
798 case NEW_TIME:
83484375 799 if (ctl->extended) {
ce602720
OO
800 strcpy(ut.ut_line,
801 ut.ut_type == NEW_TIME ? "new time" :
802 "old time");
83484375 803 quit = list(ctl, &ut, lastdown, R_TIMECHANGE);
ce602720
OO
804 }
805 break;
806 case BOOT_TIME:
807 strcpy(ut.ut_line, "system boot");
9d0b73a6
TR
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);
55771f54 812 lastboot = ut.ut_tv.tv_sec;
ce602720
OO
813 down = 1;
814 break;
815 case RUN_LVL:
816 x = ut.ut_pid & 255;
83484375 817 if (ctl->extended) {
79fb7e18 818 snprintf(ut.ut_line, sizeof(ut.ut_line), "(to lvl %c)", x);
83484375 819 quit = list(ctl, &ut, lastrch, R_NORMAL);
ce602720
OO
820 }
821 if (x == '0' || x == '6') {
55771f54 822 lastdown = ut.ut_tv.tv_sec;
ce602720
OO
823 down = 1;
824 ut.ut_type = SHUTDOWN_TIME;
825 }
55771f54 826 lastrch = ut.ut_tv.tv_sec;
ce602720 827 break;
6dbe3af9 828
ce602720
OO
829 case USER_PROCESS:
830 /*
77079432
KZ
831 * This was a login - show the first matching
832 * logout record and delete all records with
833 * the same ut_line.
ce602720
OO
834 */
835 c = 0;
da1a8ed0 836 for (p = ulist; p; p = next) {
ce602720
OO
837 next = p->next;
838 if (strncmp(p->ut.ut_line, ut.ut_line,
a924b400 839 sizeof(ut.ut_line)) == 0) {
ce602720
OO
840 /* Show it */
841 if (c == 0) {
55771f54 842 quit = list(ctl, &ut, p->ut.ut_tv.tv_sec, R_NORMAL);
ce602720
OO
843 c = 1;
844 }
da1a8ed0
KZ
845 if (p->next)
846 p->next->prev = p->prev;
ce602720
OO
847 if (p->prev)
848 p->prev->next = p->next;
849 else
da1a8ed0 850 ulist = p->next;
ce602720
OO
851 free(p);
852 }
853 }
854 /*
77079432
KZ
855 * Not found? Then crashed, down, still
856 * logged in, or missing logout record.
ce602720
OO
857 */
858 if (c == 0) {
ee24e58f 859 if (!lastboot) {
ce602720
OO
860 c = R_NOW;
861 /* Is process still alive? */
0e5218ac 862 if (is_phantom(ctl, &ut))
ce602720
OO
863 c = R_PHANTOM;
864 } else
865 c = whydown;
83484375 866 quit = list(ctl, &ut, lastboot, c);
ce602720 867 }
b77025b6 868 FALLTHROUGH;
ce602720
OO
869
870 case DEAD_PROCESS:
871 /*
77079432
KZ
872 * Just store the data if it is
873 * interesting enough.
ce602720
OO
874 */
875 if (ut.ut_line[0] == 0)
876 break;
a9f789e5 877 p = xmalloc(sizeof(struct utmplist));
b4b919fe 878 memcpy(&p->ut, &ut, sizeof(struct utmpx));
da1a8ed0 879 p->next = ulist;
ce602720 880 p->prev = NULL;
da1a8ed0
KZ
881 if (ulist)
882 ulist->prev = p;
883 ulist = p;
ce602720
OO
884 break;
885
5cae90f9
SK
886 case EMPTY:
887 case INIT_PROCESS:
888 case LOGIN_PROCESS:
b4b919fe 889#ifdef ACCOUNTING
5cae90f9 890 case ACCOUNTING:
b4b919fe 891#endif
5cae90f9
SK
892 /* ignored ut_types */
893 break;
894
895 default:
9e930041 896 warnx("unrecognized ut_type: %d", ut.ut_type);
ce602720 897 }
77079432
KZ
898
899 /*
900 * If we saw a shutdown/reboot record we can remove
da1a8ed0 901 * the entire current ulist.
77079432
KZ
902 */
903 if (down) {
55771f54 904 lastboot = ut.ut_tv.tv_sec;
77079432 905 whydown = (ut.ut_type == SHUTDOWN_TIME) ? R_DOWN : R_CRASH;
da1a8ed0 906 for (p = ulist; p; p = next) {
77079432
KZ
907 next = p->next;
908 free(p);
909 }
da1a8ed0 910 ulist = NULL;
77079432
KZ
911 down = 0;
912 }
ce602720 913 }
ce602720 914
78acfddd
SK
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);
c6ba16c6
RM
925 free(tmp);
926 }
78acfddd 927
77079432 928 fclose(fp);
da1a8ed0
KZ
929
930 for (p = ulist; p; p = next) {
bce5163b
SK
931 next = p->next;
932 free(p);
933 }
7ec78229
SK
934}
935
2cdbf06f 936#ifdef FUZZ_TARGET
cd588d66
KZ
937# include "all-io.h"
938
2cdbf06f
EV
939int 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;
2cdbf06f 952
adcd2c32 953 fd = mkstemp_cloexec(name);
cd588d66 954 if (fd < 0)
adcd2c32 955 err(EXIT_FAILURE, "mkstemp() failed");
cd588d66
KZ
956 if (write_all(fd, data, size) != 0)
957 err(EXIT_FAILURE, "write() failed");
2cdbf06f
EV
958
959 process_wtmp_file(&ctl, name);
960
961 close(fd);
962 unlink(name);
963
964 return 0;
965}
966#else
7ec78229
SK
967int main(int argc, char **argv)
968{
83484375
SK
969 struct last_control ctl = {
970 .showhost = TRUE,
971 .name_len = LAST_LOGIN_LEN,
c7eb14d3 972 .time_fmt = LAST_TIMEFTM_SHORT,
937f0509
JS
973 .domain_len = LAST_DOMAIN_LEN,
974 .fullnames_mode = false,
83484375 975 };
744c7fec
KZ
976 char **files = NULL;
977 size_t i, nfiles = 0;
83484375 978 int c;
3a4ae395 979 usec_t p;
7ec78229 980
45f11b6b
SK
981 enum {
982 OPT_TIME_FORMAT = CHAR_MAX + 1
983 };
7ec78229
SK
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' },
dd50168b 991 { "since", required_argument, NULL, 's' },
7ec78229 992 { "until", required_argument, NULL, 't' },
37f534a6 993 { "present", required_argument, NULL, 'p' },
7ec78229
SK
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' },
5d7df6b7 999 { "tab-separated", no_argument, NULL, 'T' },
45f11b6b 1000 { "time-format", required_argument, NULL, OPT_TIME_FORMAT },
7ec78229
SK
1001 { NULL, 0, NULL, 0 }
1002 };
a7349ee3 1003 static const ul_excl_t excl[] = { /* rows and cols in ASCII order */
2ea747eb
KZ
1004 { 'F', OPT_TIME_FORMAT }, /* fulltime, time-format */
1005 { 0 }
1006 };
1007 int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
7ec78229
SK
1008
1009 setlocale(LC_ALL, "");
1010 bindtextdomain(PACKAGE, LOCALEDIR);
1011 textdomain(PACKAGE);
2c308875 1012 close_stdout_atexit();
f06abd22
SK
1013 /*
1014 * Which file do we want to read?
1015 */
1016 ctl.lastb = strcmp(program_invocation_short_name, "lastb") == 0 ? 1 : 0;
5d7df6b7 1017 ctl.separator = ' ';
7ec78229 1018 while ((c = getopt_long(argc, argv,
5d7df6b7 1019 "hVf:n:RxadFit:p:s:T0123456789w", long_opts, NULL)) != -1) {
2ea747eb
KZ
1020
1021 err_exclusive_options(c, long_opts, excl, excl_st);
1022
7ec78229
SK
1023 switch(c) {
1024 case 'h':
86be6a32 1025 usage(&ctl);
7ec78229
SK
1026 break;
1027 case 'V':
2c308875 1028 print_version(EXIT_SUCCESS);
7ec78229 1029 case 'R':
ee24e58f 1030 ctl.showhost = 0;
7ec78229
SK
1031 break;
1032 case 'x':
ee24e58f 1033 ctl.extended = 1;
7ec78229
SK
1034 break;
1035 case 'n':
83484375 1036 ctl.maxrecs = strtos32_or_err(optarg, _("failed to parse number"));
7ec78229
SK
1037 break;
1038 case 'f':
744c7fec
KZ
1039 if (!files)
1040 files = xmalloc(sizeof(char *) * argc);
1041 files[nfiles++] = xstrdup(optarg);
7ec78229
SK
1042 break;
1043 case 'd':
ee24e58f 1044 ctl.usedns = 1;
7ec78229
SK
1045 break;
1046 case 'i':
ee24e58f 1047 ctl.useip = 1;
7ec78229
SK
1048 break;
1049 case 'a':
ee24e58f 1050 ctl.altlist = 1;
7ec78229
SK
1051 break;
1052 case 'F':
c7eb14d3 1053 ctl.time_fmt = LAST_TIMEFTM_CTIME;
7ec78229 1054 break;
37f534a6 1055 case 'p':
35435649 1056 if (ul_parse_timestamp(optarg, &p) < 0)
37f534a6 1057 errx(EXIT_FAILURE, _("invalid time value \"%s\""), optarg);
83484375 1058 ctl.present = (time_t) (p / 1000000);
37f534a6 1059 break;
dd50168b 1060 case 's':
35435649 1061 if (ul_parse_timestamp(optarg, &p) < 0)
dd50168b 1062 errx(EXIT_FAILURE, _("invalid time value \"%s\""), optarg);
83484375 1063 ctl.since = (time_t) (p / 1000000);
dd50168b 1064 break;
7ec78229 1065 case 't':
35435649 1066 if (ul_parse_timestamp(optarg, &p) < 0)
7ec78229 1067 errx(EXIT_FAILURE, _("invalid time value \"%s\""), optarg);
83484375 1068 ctl.until = (time_t) (p / 1000000);
7ec78229
SK
1069 break;
1070 case 'w':
937f0509 1071 ctl.fullnames_mode = true;
020061e5
TW
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);
7ec78229
SK
1076 break;
1077 case '0': case '1': case '2': case '3': case '4':
1078 case '5': case '6': case '7': case '8': case '9':
83484375 1079 ctl.maxrecs = 10 * ctl.maxrecs + c - '0';
7ec78229 1080 break;
45f11b6b
SK
1081 case OPT_TIME_FORMAT:
1082 ctl.time_fmt = which_time_format(optarg);
45f11b6b 1083 break;
5d7df6b7
TD
1084 case 'T':
1085 ctl.separator = '\t';
1086 break;
7ec78229 1087 default:
677ec86c 1088 errtryhelp(EXIT_FAILURE);
7ec78229
SK
1089 }
1090 }
1091
1092 if (optind < argc)
83484375 1093 ctl.show = argv + optind;
7ec78229 1094
744c7fec
KZ
1095 if (!files) {
1096 files = xmalloc(sizeof(char *));
1097 files[nfiles++] = xstrdup(ctl.lastb ? _PATH_BTMP : _PATH_WTMP);
7ec78229
SK
1098 }
1099
744c7fec 1100 for (i = 0; i < nfiles; i++) {
0e5218ac 1101 get_boot_time(&ctl.boot_time);
744c7fec
KZ
1102 process_wtmp_file(&ctl, files[i]);
1103 free(files[i]);
7ec78229 1104 }
744c7fec 1105 free(files);
77079432 1106 return EXIT_SUCCESS;
6dbe3af9 1107}
2cdbf06f 1108#endif