1 /* $OpenBSD: last.c,v 1.50 2015/10/29 03:00:31 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 if (pledge("stdio rpath", NULL) == -1) 102 err(1, "pledge"); 103 104 while ((ch = getopt(argc, argv, "0123456789cf:h:n:st:d:T")) != -1) { 105 switch (ch) { 106 case '0': case '1': case '2': case '3': case '4': 107 case '5': case '6': case '7': case '8': case '9': 108 /* 109 * kludge: last was originally designed to take 110 * a number after a dash. 111 */ 112 if (newarg || !isdigit(lastch)) 113 maxrec = 0; 114 else if (maxrec > INT_MAX / 10) 115 usage(); 116 maxrec = (maxrec * 10) + (ch - '0'); 117 break; 118 case 'c': 119 calculate = 1; 120 break; 121 case 'f': 122 file = optarg; 123 break; 124 case 'h': 125 hostconv(optarg); 126 addarg(HOST_TYPE, optarg); 127 break; 128 case 'n': 129 maxrec = strtonum(optarg, 0, LONG_MAX, &errstr); 130 if (errstr != NULL) 131 errx(1, "number of lines is %s: %s", errstr, 132 optarg); 133 if (maxrec == 0) 134 exit(0); 135 break; 136 case 's': 137 seconds = 1; 138 break; 139 case 't': 140 addarg(TTY_TYPE, ttyconv(optarg)); 141 break; 142 case 'd': 143 snaptime = dateconv(optarg); 144 break; 145 case 'T': 146 fulltime = 1; 147 break; 148 default: 149 usage(); 150 } 151 lastch = ch; 152 newarg = optind != prevoptind; 153 prevoptind = optind; 154 } 155 if (maxrec == 0) 156 exit(0); 157 158 if (argc) { 159 setvbuf(stdout, NULL, _IOLBF, 0); 160 for (argv += optind; *argv; ++argv) { 161 #define COMPATIBILITY 162 #ifdef COMPATIBILITY 163 /* code to allow "last p5" to work */ 164 addarg(TTY_TYPE, ttyconv(*argv)); 165 #endif 166 addarg(USER_TYPE, *argv); 167 } 168 } 169 170 checkargs(); 171 wtmp(); 172 exit(0); 173 } 174 175 /* 176 * if snaptime is set, print warning if usernames, or -t or -h 177 * flags are also provided 178 */ 179 void 180 checkargs(void) 181 { 182 int ttyflag = 0; 183 struct arg *step; 184 185 if (!snaptime || !arglist) 186 return; 187 188 for (step = arglist; step; step = step->next) 189 switch (step->type) { 190 case HOST_TYPE: 191 (void)fprintf(stderr, 192 "Warning: Ignoring hostname flag\n"); 193 break; 194 case TTY_TYPE: 195 if (!ttyflag) { /* don't print this twice */ 196 (void)fprintf(stderr, 197 "Warning: Ignoring tty flag\n"); 198 ttyflag = 1; 199 } 200 break; 201 case USER_TYPE: 202 (void)fprintf(stderr, 203 "Warning: Ignoring username[s]\n"); 204 break; 205 default: 206 break; 207 /* PRINT NOTHING */ 208 } 209 } 210 211 void 212 print_entry(const struct utmp *bp) 213 { 214 printf("%-*.*s %-*.*s %-*.*s ", 215 NAME_WIDTH, UT_NAMESIZE, bp->ut_name, 216 UT_LINESIZE, UT_LINESIZE, bp->ut_line, 217 HOST_WIDTH, UT_HOSTSIZE, bp->ut_host); 218 219 if (seconds) 220 printf("%lld", (long long)bp->ut_time); 221 else { 222 struct tm *tm; 223 224 tm = localtime(&bp->ut_time); 225 if (tm == NULL) { 226 /* bogus entry? format as epoch time... */ 227 printf("%lld", (long long)bp->ut_time); 228 } else { 229 char tim[40]; 230 231 strftime(tim, sizeof tim, 232 fulltime ? "%a %b %d %H:%M:%S" : "%a %b %d %H:%M", 233 tm); 234 printf("%s", tim); 235 } 236 } 237 } 238 239 240 /* 241 * read through the wtmp file 242 */ 243 void 244 wtmp(void) 245 { 246 time_t delta, total = 0; 247 int timesize, wfd, snapfound = 0; 248 char *ct, *crmsg = "invalid"; 249 struct utmp *bp; 250 struct stat stb; 251 ssize_t bytes; 252 off_t bl; 253 struct ttytab *T; 254 255 if ((wfd = open(file, O_RDONLY, 0)) < 0 || fstat(wfd, &stb) == -1) 256 err(1, "%s", file); 257 bl = (stb.st_size + sizeof(buf) - 1) / sizeof(buf); 258 259 if (fulltime) 260 timesize = 8; /* HH:MM:SS */ 261 else 262 timesize = 5; /* HH:MM */ 263 264 (void)time(&buf[0].ut_time); 265 (void)signal(SIGINT, onintr); 266 (void)signal(SIGQUIT, onintr); 267 268 while (--bl >= 0) { 269 if (lseek(wfd, bl * sizeof(buf), SEEK_SET) == -1 || 270 (bytes = read(wfd, buf, sizeof(buf))) == -1) 271 err(1, "%s", file); 272 for (bp = &buf[bytes / sizeof(buf[0]) - 1]; bp >= buf; --bp) { 273 /* 274 * if the terminal line is '~', the machine stopped. 275 * see utmp(5) for more info. 276 */ 277 if (bp->ut_line[0] == '~' && !bp->ut_line[1]) { 278 /* everybody just logged out */ 279 for (T = ttylist; T; T = T->next) 280 T->logout = -bp->ut_time; 281 currentout = -bp->ut_time; 282 crmsg = strncmp(bp->ut_name, "shutdown", 283 UT_NAMESIZE) ? "crash" : "shutdown"; 284 285 /* 286 * if we're in snapshot mode, we want to 287 * exit if this shutdown/reboot appears 288 * while we we are tracking the active 289 * range 290 */ 291 if (snaptime && snapfound) { 292 close(wfd); 293 return; 294 } 295 296 /* 297 * don't print shutdown/reboot entries 298 * unless flagged for 299 */ 300 if (want(bp, NO)) { 301 print_entry(bp); 302 printf("\n"); 303 if (maxrec != -1 && !--maxrec) { 304 close(wfd); 305 return; 306 } 307 } 308 continue; 309 } 310 311 /* 312 * if the line is '{' or '|', date got set; see 313 * utmp(5) for more info. 314 */ 315 if ((bp->ut_line[0] == '{' || bp->ut_line[0] == '|') && 316 !bp->ut_line[1]) { 317 if (want(bp, NO)) { 318 print_entry(bp); 319 printf("\n"); 320 if (maxrec && !--maxrec) { 321 close(wfd); 322 return; 323 } 324 } 325 continue; 326 } 327 328 /* find associated tty */ 329 for (T = ttylist;; T = T->next) { 330 if (!T) { 331 /* add new one */ 332 T = addtty(bp->ut_line); 333 break; 334 } 335 if (!strncmp(T->tty, bp->ut_line, UT_LINESIZE)) 336 break; 337 } 338 339 /* 340 * print record if not in snapshot mode and wanted 341 * or in snapshot mode and in snapshot range 342 */ 343 if (bp->ut_name[0] && 344 ((want(bp, YES)) || (bp->ut_time < snaptime && 345 (T->logout > snaptime || !T->logout || 346 T->logout < 0)))) { 347 snapfound = 1; 348 print_entry(bp); 349 printf(" "); 350 351 if (!T->logout) 352 puts(" still logged in"); 353 else { 354 if (T->logout < 0) { 355 T->logout = -T->logout; 356 printf("- %s", crmsg); 357 } else { 358 if (seconds) 359 printf("- %lld", 360 (long long)T->logout); 361 else 362 printf("- %*.*s", 363 timesize, timesize, 364 ctime(&T->logout)+11); 365 } 366 delta = T->logout - bp->ut_time; 367 if (seconds) 368 printf(" (%lld)\n", 369 (long long)delta); 370 else { 371 if (delta < SECSPERDAY) 372 printf(" (%*.*s)\n", 373 timesize, timesize, 374 asctime(gmtime(&delta))+11); 375 else 376 printf(" (%lld+%*.*s)\n", 377 (long long)delta / SECSPERDAY, 378 timesize, timesize, 379 asctime(gmtime(&delta))+11); 380 } 381 if (calculate) 382 total += delta; 383 } 384 if (maxrec != -1 && !--maxrec) { 385 close(wfd); 386 return; 387 } 388 } 389 T->logout = bp->ut_time; 390 } 391 } 392 close(wfd); 393 if (calculate) { 394 if ((total / SECSPERDAY) > 0) { 395 int days = (total / SECSPERDAY); 396 total -= (days * SECSPERDAY); 397 398 printf("\nTotal time: %d days, %*.*s\n", 399 days, timesize, timesize, 400 asctime(gmtime(&total))+11); 401 } else 402 printf("\nTotal time: %*.*s\n", 403 timesize, timesize, 404 asctime(gmtime(&total))+11); 405 } 406 ct = ctime(&buf[0].ut_time); 407 printf("\n%s begins %10.10s %*.*s %4.4s\n", basename(file), ct, 408 timesize, timesize, ct + 11, ct + 20); 409 } 410 411 /* 412 * see if want this entry 413 */ 414 int 415 want(struct utmp *bp, int check) 416 { 417 struct arg *step; 418 419 if (check) { 420 /* 421 * some entries, such as ftp and uucp, will 422 * include process name plus id; exclude entries 423 * that start with 'console' and 'tty' from 424 * having the process id stripped. 425 */ 426 if ((strncmp(bp->ut_line, "console", strlen("console")) != 0) && 427 (strncmp(bp->ut_line, "tty", strlen("tty")) != 0)) { 428 char *s; 429 for (s = bp->ut_line; 430 *s != '\0' && !isdigit((unsigned char)*s); s++) 431 ; 432 *s = '\0'; 433 } 434 } 435 436 if (snaptime) /* if snaptime is set, return NO */ 437 return (NO); 438 439 if (!arglist) 440 return (YES); 441 442 for (step = arglist; step; step = step->next) 443 switch (step->type) { 444 case HOST_TYPE: 445 if (!strncasecmp(step->name, bp->ut_host, UT_HOSTSIZE)) 446 return (YES); 447 break; 448 case TTY_TYPE: 449 if (!strncmp(step->name, bp->ut_line, UT_LINESIZE)) 450 return (YES); 451 break; 452 case USER_TYPE: 453 if (!strncmp(step->name, bp->ut_name, UT_NAMESIZE)) 454 return (YES); 455 break; 456 } 457 458 return (NO); 459 } 460 461 /* 462 * add an entry to a linked list of arguments 463 */ 464 void 465 addarg(int type, char *arg) 466 { 467 struct arg *cur; 468 469 if (!(cur = malloc((u_int)sizeof(struct arg)))) 470 err(1, "malloc failure"); 471 cur->next = arglist; 472 cur->type = type; 473 cur->name = arg; 474 arglist = cur; 475 } 476 477 /* 478 * add an entry to a linked list of ttys 479 */ 480 struct ttytab * 481 addtty(char *ttyname) 482 { 483 struct ttytab *cur; 484 485 if (!(cur = malloc((u_int)sizeof(struct ttytab)))) 486 err(1, "malloc failure"); 487 cur->next = ttylist; 488 cur->logout = currentout; 489 memmove(cur->tty, ttyname, UT_LINESIZE); 490 return (ttylist = cur); 491 } 492 493 /* 494 * convert the hostname to search pattern; if the supplied host name 495 * has a domain attached that is the same as the current domain, rip 496 * off the domain suffix since that's what login(1) does. 497 */ 498 void 499 hostconv(char *arg) 500 { 501 static char *hostdot, name[HOST_NAME_MAX+1]; 502 static int first = 1; 503 char *argdot; 504 505 if (!(argdot = strchr(arg, '.'))) 506 return; 507 if (first) { 508 first = 0; 509 if (gethostname(name, sizeof(name))) 510 err(1, "gethostname"); 511 hostdot = strchr(name, '.'); 512 } 513 if (hostdot && !strcasecmp(hostdot, argdot)) 514 *argdot = '\0'; 515 } 516 517 /* 518 * convert tty to correct name. 519 */ 520 char * 521 ttyconv(char *arg) 522 { 523 size_t len = 8; 524 char *mval; 525 526 /* 527 * kludge -- we assume that all tty's end with 528 * a two character suffix. 529 */ 530 if (strlen(arg) == 2) { 531 /* either 6 for "ttyxx" or 8 for "console" */ 532 if (!(mval = malloc(len))) 533 err(1, "malloc failure"); 534 if (!strcmp(arg, "co")) 535 (void)strlcpy(mval, "console", len); 536 else 537 snprintf(mval, len, "tty%s", arg); 538 return (mval); 539 } 540 if (!strncmp(arg, _PATH_DEV, sizeof(_PATH_DEV) - 1)) 541 return (arg + 5); 542 return (arg); 543 } 544 545 /* 546 * Convert the snapshot time in command line given in the format 547 * [[[CC]YY]MMDD]hhmm[.SS]] to a time_t. 548 * Derived from atime_arg1() in usr.bin/touch/touch.c 549 */ 550 time_t 551 dateconv(char *arg) 552 { 553 time_t timet; 554 struct tm *t; 555 int yearset; 556 char *p; 557 558 /* Start with the current time. */ 559 if (time(&timet) < 0) 560 err(1, "time"); 561 if ((t = localtime(&timet)) == NULL) 562 err(1, "localtime"); 563 564 /* [[[CC]YY]MMDD]hhmm[.SS] */ 565 if ((p = strchr(arg, '.')) == NULL) 566 t->tm_sec = 0; /* Seconds defaults to 0. */ 567 else { 568 if (strlen(p + 1) != 2) 569 goto terr; 570 *p++ = '\0'; 571 t->tm_sec = ATOI2(p); 572 } 573 574 yearset = 0; 575 switch (strlen(arg)) { 576 case 12: /* CCYYMMDDhhmm */ 577 t->tm_year = ATOI2(arg); 578 t->tm_year *= 100; 579 yearset = 1; 580 /* FALLTHROUGH */ 581 case 10: /* YYMMDDhhmm */ 582 if (yearset) { 583 yearset = ATOI2(arg); 584 t->tm_year += yearset; 585 } else { 586 yearset = ATOI2(arg); 587 if (yearset < 69) 588 t->tm_year = yearset + 2000; 589 else 590 t->tm_year = yearset + 1900; 591 } 592 t->tm_year -= 1900; /* Convert to UNIX time. */ 593 /* FALLTHROUGH */ 594 case 8: /* MMDDhhmm */ 595 t->tm_mon = ATOI2(arg); 596 --t->tm_mon; /* Convert from 01-12 to 00-11 */ 597 t->tm_mday = ATOI2(arg); 598 t->tm_hour = ATOI2(arg); 599 t->tm_min = ATOI2(arg); 600 break; 601 case 4: /* hhmm */ 602 t->tm_hour = ATOI2(arg); 603 t->tm_min = ATOI2(arg); 604 break; 605 default: 606 goto terr; 607 } 608 t->tm_isdst = -1; /* Figure out DST. */ 609 timet = mktime(t); 610 if (timet == -1) 611 terr: errx(1, "out of range or illegal time specification: " 612 "[[[CC]YY]MMDD]hhmm[.SS]"); 613 return (timet); 614 } 615 616 617 /* 618 * on interrupt, we inform the user how far we've gotten 619 */ 620 void 621 onintr(int signo) 622 { 623 char str[1024], *ct, ctbuf[26]; 624 625 ct = ctime_r(&buf[0].ut_time, ctbuf); 626 snprintf(str, sizeof str, "\ninterrupted %10.10s %8.8s \n", 627 ct, ct + 11); 628 write(STDOUT_FILENO, str, strlen(str)); 629 if (signo == SIGINT) 630 _exit(1); 631 } 632 633 void 634 usage(void) 635 { 636 extern char *__progname; 637 638 fprintf(stderr, 639 "usage: %s [-csT] [-d date] [-f file] [-h host]" 640 " [-n number] [-t tty] [user ...]\n", __progname); 641 exit(1); 642 } 643