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