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