1 /* $OpenBSD: last.c,v 1.52 2019/06/28 13:35:01 deraadt Exp $ */ 2 /* $NetBSD: last.c,v 1.6 1994/12/24 16:49:02 cgd Exp $ */ 3 4 /* 5 * Copyright (c) 1987, 1993, 1994 6 * The Regents of the University of California. All rights reserved. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 1. Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in the 15 * documentation and/or other materials provided with the distribution. 16 * 3. Neither the name of the University nor the names of its contributors 17 * may be used to endorse or promote products derived from this software 18 * without specific prior written permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 21 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 24 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 26 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 29 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30 * SUCH DAMAGE. 31 */ 32 33 #include <sys/stat.h> 34 35 #include <ctype.h> 36 #include <err.h> 37 #include <fcntl.h> 38 #include <libgen.h> 39 #include <paths.h> 40 #include <signal.h> 41 #include <stdio.h> 42 #include <stdlib.h> 43 #include <string.h> 44 #include <time.h> 45 #include <unistd.h> 46 #include <limits.h> 47 #include <utmp.h> 48 49 #define NO 0 /* false/no */ 50 #define YES 1 /* true/yes */ 51 #define ATOI2(ar) ((ar)[0] - '0') * 10 + ((ar)[1] - '0'); (ar) += 2; 52 53 static struct utmp buf[1024]; /* utmp read buffer */ 54 55 struct arg { 56 char *name; /* argument */ 57 #define HOST_TYPE -2 58 #define TTY_TYPE -3 59 #define USER_TYPE -4 60 int type; /* type of arg */ 61 struct arg *next; /* linked list pointer */ 62 } *arglist; 63 64 struct ttytab { 65 time_t logout; /* log out time */ 66 char tty[UT_LINESIZE + 1]; /* terminal name */ 67 struct ttytab*next; /* linked list pointer */ 68 } *ttylist; 69 70 static time_t currentout; /* current logout value */ 71 static long maxrec = -1; /* records to display */ 72 static char *file = _PATH_WTMP; /* wtmp file */ 73 static int fulltime = 0; /* Display seconds? */ 74 static time_t snaptime = 0; /* report only at this time */ 75 static int calculate = 0; 76 static int seconds = 0; 77 78 void addarg(int, char *); 79 struct ttytab *addtty(char *); 80 void hostconv(char *); 81 void onintr(int); 82 char *ttyconv(char *); 83 time_t dateconv(char *); 84 int want(struct utmp *, int); 85 void wtmp(void); 86 void checkargs(void); 87 void print_entry(const struct utmp *); 88 void usage(void); 89 90 #define NAME_WIDTH 9 91 #define HOST_WIDTH 24 92 93 #define SECSPERDAY (24 * 60 * 60) 94 95 int 96 main(int argc, char *argv[]) 97 { 98 const char *errstr; 99 int ch, lastch = '\0', newarg = 1, prevoptind = 1; 100 101 while ((ch = getopt(argc, argv, "0123456789cf:h:n:st:d:T")) != -1) { 102 switch (ch) { 103 case '0': case '1': case '2': case '3': case '4': 104 case '5': case '6': case '7': case '8': case '9': 105 /* 106 * kludge: last was originally designed to take 107 * a number after a dash. 108 */ 109 if (newarg || !isdigit(lastch)) 110 maxrec = 0; 111 else if (maxrec > INT_MAX / 10) 112 usage(); 113 maxrec = (maxrec * 10) + (ch - '0'); 114 break; 115 case 'c': 116 calculate = 1; 117 break; 118 case 'f': 119 file = optarg; 120 break; 121 case 'h': 122 hostconv(optarg); 123 addarg(HOST_TYPE, optarg); 124 break; 125 case 'n': 126 maxrec = strtonum(optarg, 0, LONG_MAX, &errstr); 127 if (errstr != NULL) 128 errx(1, "number of lines is %s: %s", errstr, 129 optarg); 130 if (maxrec == 0) 131 exit(0); 132 break; 133 case 's': 134 seconds = 1; 135 break; 136 case 't': 137 addarg(TTY_TYPE, ttyconv(optarg)); 138 break; 139 case 'd': 140 snaptime = dateconv(optarg); 141 break; 142 case 'T': 143 fulltime = 1; 144 break; 145 default: 146 usage(); 147 } 148 lastch = ch; 149 newarg = optind != prevoptind; 150 prevoptind = optind; 151 } 152 if (maxrec == 0) 153 exit(0); 154 155 if (unveil(file, "r") == -1) 156 err(1, "unveil"); 157 if (pledge("stdio rpath", NULL) == -1) 158 err(1, "pledge"); 159 160 if (argc) { 161 setvbuf(stdout, NULL, _IOLBF, 0); 162 for (argv += optind; *argv; ++argv) { 163 #define COMPATIBILITY 164 #ifdef COMPATIBILITY 165 /* code to allow "last p5" to work */ 166 addarg(TTY_TYPE, ttyconv(*argv)); 167 #endif 168 addarg(USER_TYPE, *argv); 169 } 170 } 171 172 checkargs(); 173 wtmp(); 174 exit(0); 175 } 176 177 /* 178 * if snaptime is set, print warning if usernames, or -t or -h 179 * flags are also provided 180 */ 181 void 182 checkargs(void) 183 { 184 int ttyflag = 0; 185 struct arg *step; 186 187 if (!snaptime || !arglist) 188 return; 189 190 for (step = arglist; step; step = step->next) 191 switch (step->type) { 192 case HOST_TYPE: 193 (void)fprintf(stderr, 194 "Warning: Ignoring hostname flag\n"); 195 break; 196 case TTY_TYPE: 197 if (!ttyflag) { /* don't print this twice */ 198 (void)fprintf(stderr, 199 "Warning: Ignoring tty flag\n"); 200 ttyflag = 1; 201 } 202 break; 203 case USER_TYPE: 204 (void)fprintf(stderr, 205 "Warning: Ignoring username[s]\n"); 206 break; 207 default: 208 break; 209 /* PRINT NOTHING */ 210 } 211 } 212 213 void 214 print_entry(const struct utmp *bp) 215 { 216 printf("%-*.*s %-*.*s %-*.*s ", 217 NAME_WIDTH, UT_NAMESIZE, bp->ut_name, 218 UT_LINESIZE, UT_LINESIZE, bp->ut_line, 219 HOST_WIDTH, UT_HOSTSIZE, bp->ut_host); 220 221 if (seconds) 222 printf("%lld", (long long)bp->ut_time); 223 else { 224 struct tm *tm; 225 226 tm = localtime(&bp->ut_time); 227 if (tm == NULL) { 228 /* bogus entry? format as epoch time... */ 229 printf("%lld", (long long)bp->ut_time); 230 } else { 231 char tim[40]; 232 233 strftime(tim, sizeof tim, 234 fulltime ? "%a %b %d %H:%M:%S" : "%a %b %d %H:%M", 235 tm); 236 printf("%s", tim); 237 } 238 } 239 } 240 241 242 /* 243 * read through the wtmp file 244 */ 245 void 246 wtmp(void) 247 { 248 time_t delta, total = 0; 249 int timesize, wfd, snapfound = 0; 250 char *ct, *crmsg = "invalid"; 251 struct utmp *bp; 252 struct stat stb; 253 ssize_t bytes; 254 off_t bl; 255 struct ttytab *T; 256 257 if ((wfd = open(file, O_RDONLY, 0)) == -1 || fstat(wfd, &stb) == -1) 258 err(1, "%s", file); 259 bl = (stb.st_size + sizeof(buf) - 1) / sizeof(buf); 260 261 if (fulltime) 262 timesize = 8; /* HH:MM:SS */ 263 else 264 timesize = 5; /* HH:MM */ 265 266 (void)time(&buf[0].ut_time); 267 (void)signal(SIGINT, onintr); 268 (void)signal(SIGQUIT, onintr); 269 270 while (--bl >= 0) { 271 if (lseek(wfd, bl * sizeof(buf), SEEK_SET) == -1 || 272 (bytes = read(wfd, buf, sizeof(buf))) == -1) 273 err(1, "%s", file); 274 for (bp = &buf[bytes / sizeof(buf[0]) - 1]; bp >= buf; --bp) { 275 /* 276 * if the terminal line is '~', the machine stopped. 277 * see utmp(5) for more info. 278 */ 279 if (bp->ut_line[0] == '~' && !bp->ut_line[1]) { 280 /* everybody just logged out */ 281 for (T = ttylist; T; T = T->next) 282 T->logout = -bp->ut_time; 283 currentout = -bp->ut_time; 284 crmsg = strncmp(bp->ut_name, "shutdown", 285 UT_NAMESIZE) ? "crash" : "shutdown"; 286 287 /* 288 * if we're in snapshot mode, we want to 289 * exit if this shutdown/reboot appears 290 * while we we are tracking the active 291 * range 292 */ 293 if (snaptime && snapfound) { 294 close(wfd); 295 return; 296 } 297 298 /* 299 * don't print shutdown/reboot entries 300 * unless flagged for 301 */ 302 if (want(bp, NO)) { 303 print_entry(bp); 304 printf("\n"); 305 if (maxrec != -1 && !--maxrec) { 306 close(wfd); 307 return; 308 } 309 } 310 continue; 311 } 312 313 /* 314 * if the line is '{' or '|', date got set; see 315 * utmp(5) for more info. 316 */ 317 if ((bp->ut_line[0] == '{' || bp->ut_line[0] == '|') && 318 !bp->ut_line[1]) { 319 if (want(bp, NO)) { 320 print_entry(bp); 321 printf("\n"); 322 if (maxrec && !--maxrec) { 323 close(wfd); 324 return; 325 } 326 } 327 continue; 328 } 329 330 /* find associated tty */ 331 for (T = ttylist;; T = T->next) { 332 if (!T) { 333 /* add new one */ 334 T = addtty(bp->ut_line); 335 break; 336 } 337 if (!strncmp(T->tty, bp->ut_line, UT_LINESIZE)) 338 break; 339 } 340 341 /* 342 * print record if not in snapshot mode and wanted 343 * or in snapshot mode and in snapshot range 344 */ 345 if (bp->ut_name[0] && 346 ((want(bp, YES)) || (bp->ut_time < snaptime && 347 (T->logout > snaptime || !T->logout || 348 T->logout < 0)))) { 349 snapfound = 1; 350 print_entry(bp); 351 printf(" "); 352 353 if (!T->logout) 354 puts(" still logged in"); 355 else { 356 if (T->logout < 0) { 357 T->logout = -T->logout; 358 printf("- %s", crmsg); 359 } else { 360 if (seconds) 361 printf("- %lld", 362 (long long)T->logout); 363 else 364 printf("- %*.*s", 365 timesize, timesize, 366 ctime(&T->logout)+11); 367 } 368 delta = T->logout - bp->ut_time; 369 if (seconds) 370 printf(" (%lld)\n", 371 (long long)delta); 372 else { 373 if (delta < SECSPERDAY) 374 printf(" (%*.*s)\n", 375 timesize, timesize, 376 asctime(gmtime(&delta))+11); 377 else 378 printf(" (%lld+%*.*s)\n", 379 (long long)delta / SECSPERDAY, 380 timesize, timesize, 381 asctime(gmtime(&delta))+11); 382 } 383 if (calculate) 384 total += delta; 385 } 386 if (maxrec != -1 && !--maxrec) { 387 close(wfd); 388 return; 389 } 390 } 391 T->logout = bp->ut_time; 392 } 393 } 394 close(wfd); 395 if (calculate) { 396 if ((total / SECSPERDAY) > 0) { 397 int days = (total / SECSPERDAY); 398 total -= (days * SECSPERDAY); 399 400 printf("\nTotal time: %d days, %*.*s\n", 401 days, timesize, timesize, 402 asctime(gmtime(&total))+11); 403 } else 404 printf("\nTotal time: %*.*s\n", 405 timesize, timesize, 406 asctime(gmtime(&total))+11); 407 } 408 ct = ctime(&buf[0].ut_time); 409 printf("\n%s begins %10.10s %*.*s %4.4s\n", basename(file), ct, 410 timesize, timesize, ct + 11, ct + 20); 411 } 412 413 /* 414 * see if want this entry 415 */ 416 int 417 want(struct utmp *bp, int check) 418 { 419 struct arg *step; 420 421 if (check) { 422 /* 423 * some entries, such as ftp and uucp, will 424 * include process name plus id; exclude entries 425 * that start with 'console' and 'tty' from 426 * having the process id stripped. 427 */ 428 if ((strncmp(bp->ut_line, "console", strlen("console")) != 0) && 429 (strncmp(bp->ut_line, "tty", strlen("tty")) != 0)) { 430 char *s; 431 for (s = bp->ut_line; 432 *s != '\0' && !isdigit((unsigned char)*s); s++) 433 ; 434 *s = '\0'; 435 } 436 } 437 438 if (snaptime) /* if snaptime is set, return NO */ 439 return (NO); 440 441 if (!arglist) 442 return (YES); 443 444 for (step = arglist; step; step = step->next) 445 switch (step->type) { 446 case HOST_TYPE: 447 if (!strncasecmp(step->name, bp->ut_host, UT_HOSTSIZE)) 448 return (YES); 449 break; 450 case TTY_TYPE: 451 if (!strncmp(step->name, bp->ut_line, UT_LINESIZE)) 452 return (YES); 453 break; 454 case USER_TYPE: 455 if (!strncmp(step->name, bp->ut_name, UT_NAMESIZE)) 456 return (YES); 457 break; 458 } 459 460 return (NO); 461 } 462 463 /* 464 * add an entry to a linked list of arguments 465 */ 466 void 467 addarg(int type, char *arg) 468 { 469 struct arg *cur; 470 471 if (!(cur = malloc((u_int)sizeof(struct arg)))) 472 err(1, "malloc failure"); 473 cur->next = arglist; 474 cur->type = type; 475 cur->name = arg; 476 arglist = cur; 477 } 478 479 /* 480 * add an entry to a linked list of ttys 481 */ 482 struct ttytab * 483 addtty(char *ttyname) 484 { 485 struct ttytab *cur; 486 487 if (!(cur = malloc((u_int)sizeof(struct ttytab)))) 488 err(1, "malloc failure"); 489 cur->next = ttylist; 490 cur->logout = currentout; 491 memmove(cur->tty, ttyname, UT_LINESIZE); 492 return (ttylist = cur); 493 } 494 495 /* 496 * convert the hostname to search pattern; if the supplied host name 497 * has a domain attached that is the same as the current domain, rip 498 * off the domain suffix since that's what login(1) does. 499 */ 500 void 501 hostconv(char *arg) 502 { 503 static char *hostdot, name[HOST_NAME_MAX+1]; 504 static int first = 1; 505 char *argdot; 506 507 if (!(argdot = strchr(arg, '.'))) 508 return; 509 if (first) { 510 first = 0; 511 if (gethostname(name, sizeof(name))) 512 err(1, "gethostname"); 513 hostdot = strchr(name, '.'); 514 } 515 if (hostdot && !strcasecmp(hostdot, argdot)) 516 *argdot = '\0'; 517 } 518 519 /* 520 * convert tty to correct name. 521 */ 522 char * 523 ttyconv(char *arg) 524 { 525 size_t len = 8; 526 char *mval; 527 528 /* 529 * kludge -- we assume that all tty's end with 530 * a two character suffix. 531 */ 532 if (strlen(arg) == 2) { 533 /* either 6 for "ttyxx" or 8 for "console" */ 534 if (!(mval = malloc(len))) 535 err(1, "malloc failure"); 536 if (!strcmp(arg, "co")) 537 (void)strlcpy(mval, "console", len); 538 else 539 snprintf(mval, len, "tty%s", arg); 540 return (mval); 541 } 542 if (!strncmp(arg, _PATH_DEV, sizeof(_PATH_DEV) - 1)) 543 return (arg + 5); 544 return (arg); 545 } 546 547 /* 548 * Convert the snapshot time in command line given in the format 549 * [[[CC]YY]MMDD]hhmm[.SS]] to a time_t. 550 * Derived from atime_arg1() in usr.bin/touch/touch.c 551 */ 552 time_t 553 dateconv(char *arg) 554 { 555 time_t timet; 556 struct tm *t; 557 int yearset; 558 char *p; 559 560 /* Start with the current time. */ 561 if (time(&timet) == -1) 562 err(1, "time"); 563 if ((t = localtime(&timet)) == NULL) 564 err(1, "localtime"); 565 566 /* [[[CC]YY]MMDD]hhmm[.SS] */ 567 if ((p = strchr(arg, '.')) == NULL) 568 t->tm_sec = 0; /* Seconds defaults to 0. */ 569 else { 570 if (strlen(p + 1) != 2) 571 goto terr; 572 *p++ = '\0'; 573 t->tm_sec = ATOI2(p); 574 } 575 576 yearset = 0; 577 switch (strlen(arg)) { 578 case 12: /* CCYYMMDDhhmm */ 579 t->tm_year = ATOI2(arg); 580 t->tm_year *= 100; 581 yearset = 1; 582 /* FALLTHROUGH */ 583 case 10: /* YYMMDDhhmm */ 584 if (yearset) { 585 yearset = ATOI2(arg); 586 t->tm_year += yearset; 587 } else { 588 yearset = ATOI2(arg); 589 if (yearset < 69) 590 t->tm_year = yearset + 2000; 591 else 592 t->tm_year = yearset + 1900; 593 } 594 t->tm_year -= 1900; /* Convert to UNIX time. */ 595 /* FALLTHROUGH */ 596 case 8: /* MMDDhhmm */ 597 t->tm_mon = ATOI2(arg); 598 --t->tm_mon; /* Convert from 01-12 to 00-11 */ 599 t->tm_mday = ATOI2(arg); 600 t->tm_hour = ATOI2(arg); 601 t->tm_min = ATOI2(arg); 602 break; 603 case 4: /* hhmm */ 604 t->tm_hour = ATOI2(arg); 605 t->tm_min = ATOI2(arg); 606 break; 607 default: 608 goto terr; 609 } 610 t->tm_isdst = -1; /* Figure out DST. */ 611 timet = mktime(t); 612 if (timet == -1) 613 terr: errx(1, "out of range or illegal time specification: " 614 "[[[CC]YY]MMDD]hhmm[.SS]"); 615 return (timet); 616 } 617 618 619 /* 620 * on interrupt, we inform the user how far we've gotten 621 */ 622 void 623 onintr(int signo) 624 { 625 char str[1024], *ct, ctbuf[26]; 626 627 ct = ctime_r(&buf[0].ut_time, ctbuf); 628 snprintf(str, sizeof str, "\ninterrupted %10.10s %8.8s \n", 629 ct, ct + 11); 630 write(STDOUT_FILENO, str, strlen(str)); 631 if (signo == SIGINT) 632 _exit(1); 633 } 634 635 void 636 usage(void) 637 { 638 extern char *__progname; 639 640 fprintf(stderr, 641 "usage: %s [-csT] [-d date] [-f file] [-h host]" 642 " [-n number] [-t tty] [user ...]\n", __progname); 643 exit(1); 644 } 645