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