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 34 #ifndef lint 35 static const char copyright[] = 36 "@(#) Copyright (c) 1987, 1993, 1994\n\ 37 The Regents of the University of California. All rights reserved.\n"; 38 #endif /* not lint */ 39 40 #ifndef lint 41 static const char sccsid[] = "@(#)last.c 8.2 (Berkeley) 4/2/94"; 42 #endif /* not lint */ 43 #include <sys/cdefs.h> 44 __FBSDID("$FreeBSD$"); 45 46 #include <sys/param.h> 47 #include <sys/stat.h> 48 49 #include <err.h> 50 #include <errno.h> 51 #include <fcntl.h> 52 #include <langinfo.h> 53 #include <locale.h> 54 #include <paths.h> 55 #include <signal.h> 56 #include <stdio.h> 57 #include <stdlib.h> 58 #include <string.h> 59 #include <time.h> 60 #include <timeconv.h> 61 #include <unistd.h> 62 #include <utmpx.h> 63 #include <sys/queue.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 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(idlisthead, idtab) idlist; 80 81 struct idtab { 82 time_t logout; /* log out time */ 83 char id[sizeof ((struct utmpx *)0)->ut_id]; /* identifier */ 84 LIST_ENTRY(idtab) list; 85 }; 86 87 static const char *crmsg; /* cause of last reboot */ 88 static time_t currentout; /* current logout value */ 89 static long maxrec; /* records to display */ 90 static const char *file = NULL; /* wtmp file */ 91 static int sflag = 0; /* show delta in seconds */ 92 static int width = 5; /* show seconds in delta */ 93 static int yflag; /* show year */ 94 static int d_first; 95 static int snapfound = 0; /* found snapshot entry? */ 96 static time_t snaptime; /* if != 0, we will only 97 * report users logged in 98 * at this snapshot time 99 */ 100 101 void addarg(int, char *); 102 time_t dateconv(char *); 103 void doentry(struct utmpx *); 104 void hostconv(char *); 105 void printentry(struct utmpx *, struct idtab *); 106 char *ttyconv(char *); 107 int want(struct utmpx *); 108 void usage(void); 109 void wtmp(void); 110 111 void 112 usage(void) 113 { 114 (void)fprintf(stderr, 115 "usage: last [-swy] [-d [[CC]YY][MMDD]hhmm[.SS]] [-f file] [-h host]\n" 116 " [-n maxrec] [-t tty] [user ...]\n"); 117 exit(1); 118 } 119 120 int 121 main(int argc, char *argv[]) 122 { 123 int ch; 124 char *p; 125 126 (void) setlocale(LC_TIME, ""); 127 d_first = (*nl_langinfo(D_MD_ORDER) == 'd'); 128 129 maxrec = -1; 130 snaptime = 0; 131 while ((ch = getopt(argc, argv, "0123456789d:f:h:n:st:wy")) != -1) 132 switch (ch) { 133 case '0': case '1': case '2': case '3': case '4': 134 case '5': case '6': case '7': case '8': case '9': 135 /* 136 * kludge: last was originally designed to take 137 * a number after a dash. 138 */ 139 if (maxrec == -1) { 140 p = strchr(argv[optind - 1], ch); 141 if (p == NULL) 142 p = strchr(argv[optind], ch); 143 maxrec = atol(p); 144 if (!maxrec) 145 exit(0); 146 } 147 break; 148 case 'd': 149 snaptime = dateconv(optarg); 150 break; 151 case 'f': 152 file = optarg; 153 break; 154 case 'h': 155 hostconv(optarg); 156 addarg(HOST_TYPE, optarg); 157 break; 158 case 'n': 159 errno = 0; 160 maxrec = strtol(optarg, &p, 10); 161 if (p == optarg || *p != '\0' || errno != 0 || 162 maxrec <= 0) 163 errx(1, "%s: bad line count", optarg); 164 break; 165 case 's': 166 sflag++; /* Show delta as seconds */ 167 break; 168 case 't': 169 addarg(TTY_TYPE, ttyconv(optarg)); 170 break; 171 case 'w': 172 width = 8; 173 break; 174 case 'y': 175 yflag++; 176 break; 177 case '?': 178 default: 179 usage(); 180 } 181 182 if (sflag && width == 8) usage(); 183 184 if (argc) { 185 setlinebuf(stdout); 186 for (argv += optind; *argv; ++argv) { 187 #define COMPATIBILITY 188 #ifdef COMPATIBILITY 189 /* code to allow "last p5" to work */ 190 addarg(TTY_TYPE, ttyconv(*argv)); 191 #endif 192 addarg(USER_TYPE, *argv); 193 } 194 } 195 wtmp(); 196 exit(0); 197 } 198 199 /* 200 * wtmp -- 201 * read through the wtmp file 202 */ 203 void 204 wtmp(void) 205 { 206 struct utmpx *buf = NULL; 207 struct utmpx *ut; 208 static unsigned int amount = 0; 209 time_t t; 210 char ct[80]; 211 struct tm *tm; 212 213 LIST_INIT(&idlist); 214 (void)time(&t); 215 216 /* Load the last entries from the file. */ 217 if (setutxdb(UTXDB_LOG, file) != 0) 218 err(1, "%s", file); 219 while ((ut = getutxent()) != NULL) { 220 if (amount % 128 == 0) { 221 buf = realloc(buf, (amount + 128) * sizeof *ut); 222 if (buf == NULL) 223 err(1, "realloc"); 224 } 225 memcpy(&buf[amount++], ut, sizeof *ut); 226 if (t > ut->ut_tv.tv_sec) 227 t = ut->ut_tv.tv_sec; 228 } 229 endutxent(); 230 231 /* Display them in reverse order. */ 232 while (amount > 0) 233 doentry(&buf[--amount]); 234 235 tm = localtime(&t); 236 (void) strftime(ct, sizeof(ct), "\nwtmp begins %+\n", tm); 237 printf("%s", ct); 238 } 239 240 /* 241 * doentry -- 242 * process a single wtmp entry 243 */ 244 void 245 doentry(struct utmpx *bp) 246 { 247 struct idtab *tt, *ttx; /* idlist entry */ 248 249 /* the machine stopped */ 250 if (bp->ut_type == BOOT_TIME || bp->ut_type == SHUTDOWN_TIME) { 251 /* everybody just logged out */ 252 for (tt = LIST_FIRST(&idlist); tt;) { 253 LIST_REMOVE(tt, list); 254 ttx = tt; 255 tt = LIST_NEXT(tt, list); 256 free(ttx); 257 } 258 currentout = -bp->ut_tv.tv_sec; 259 crmsg = bp->ut_type != SHUTDOWN_TIME ? 260 "crash" : "shutdown"; 261 /* 262 * if we're in snapshot mode, we want to exit if this 263 * shutdown/reboot appears while we we are tracking the 264 * active range 265 */ 266 if (snaptime && snapfound) 267 exit(0); 268 /* 269 * don't print shutdown/reboot entries unless flagged for 270 */ 271 if (!snaptime && want(bp)) 272 printentry(bp, NULL); 273 return; 274 } 275 /* date got set */ 276 if (bp->ut_type == OLD_TIME || bp->ut_type == NEW_TIME) { 277 if (want(bp) && !snaptime) 278 printentry(bp, NULL); 279 return; 280 } 281 282 if (bp->ut_type != USER_PROCESS && bp->ut_type != DEAD_PROCESS) 283 return; 284 285 /* find associated identifier */ 286 LIST_FOREACH(tt, &idlist, list) 287 if (!memcmp(tt->id, bp->ut_id, sizeof bp->ut_id)) 288 break; 289 290 if (tt == NULL) { 291 /* add new one */ 292 tt = malloc(sizeof(struct idtab)); 293 if (tt == NULL) 294 errx(1, "malloc failure"); 295 tt->logout = currentout; 296 memcpy(tt->id, bp->ut_id, sizeof bp->ut_id); 297 LIST_INSERT_HEAD(&idlist, tt, list); 298 } 299 300 /* 301 * print record if not in snapshot mode and wanted 302 * or in snapshot mode and in snapshot range 303 */ 304 if (bp->ut_type == USER_PROCESS && (want(bp) || 305 (bp->ut_tv.tv_sec < snaptime && 306 (tt->logout > snaptime || tt->logout < 1)))) { 307 snapfound = 1; 308 printentry(bp, tt); 309 } 310 tt->logout = bp->ut_tv.tv_sec; 311 } 312 313 /* 314 * printentry -- 315 * output an entry 316 * 317 * If `tt' is non-NULL, use it and `crmsg' to print the logout time or 318 * logout type (crash/shutdown) as appropriate. 319 */ 320 void 321 printentry(struct utmpx *bp, struct idtab *tt) 322 { 323 char ct[80]; 324 struct tm *tm; 325 time_t delta; /* time difference */ 326 time_t t; 327 328 if (maxrec != -1 && !maxrec--) 329 exit(0); 330 t = bp->ut_tv.tv_sec; 331 tm = localtime(&t); 332 (void) strftime(ct, sizeof(ct), d_first ? 333 (yflag ? "%a %e %b %Y %R" : "%a %e %b %R") : 334 (yflag ? "%a %b %e %Y %R" : "%a %b %e %R"), tm); 335 switch (bp->ut_type) { 336 case BOOT_TIME: 337 printf("%-42s", "boot time"); 338 break; 339 case SHUTDOWN_TIME: 340 printf("%-42s", "shutdown time"); 341 break; 342 case OLD_TIME: 343 printf("%-42s", "old time"); 344 break; 345 case NEW_TIME: 346 printf("%-42s", "new time"); 347 break; 348 case USER_PROCESS: 349 printf("%-10s %-8s %-22.22s", 350 bp->ut_user, bp->ut_line, bp->ut_host); 351 break; 352 } 353 printf(" %s%c", ct, tt == NULL ? '\n' : ' '); 354 if (tt == NULL) 355 return; 356 if (!tt->logout) { 357 puts(" still logged in"); 358 return; 359 } 360 if (tt->logout < 0) { 361 tt->logout = -tt->logout; 362 printf("- %s", crmsg); 363 } else { 364 tm = localtime(&tt->logout); 365 (void) strftime(ct, sizeof(ct), "%R", tm); 366 printf("- %s", ct); 367 } 368 delta = tt->logout - bp->ut_tv.tv_sec; 369 if (sflag) { 370 printf(" (%8ld)\n", (long)delta); 371 } else { 372 tm = gmtime(&delta); 373 (void) strftime(ct, sizeof(ct), width >= 8 ? "%T" : "%R", tm); 374 if (delta < 86400) 375 printf(" (%s)\n", ct); 376 else 377 printf(" (%ld+%s)\n", (long)delta / 86400, ct); 378 } 379 } 380 381 /* 382 * want -- 383 * see if want this entry 384 */ 385 int 386 want(struct utmpx *bp) 387 { 388 ARG *step; 389 390 if (snaptime) 391 return (NO); 392 393 if (!arglist) 394 return (YES); 395 396 for (step = arglist; step; step = step->next) 397 switch(step->type) { 398 case HOST_TYPE: 399 if (!strcasecmp(step->name, bp->ut_host)) 400 return (YES); 401 break; 402 case TTY_TYPE: 403 if (!strcmp(step->name, bp->ut_line)) 404 return (YES); 405 break; 406 case USER_TYPE: 407 if (!strcmp(step->name, bp->ut_user)) 408 return (YES); 409 break; 410 } 411 return (NO); 412 } 413 414 /* 415 * addarg -- 416 * add an entry to a linked list of arguments 417 */ 418 void 419 addarg(int type, char *arg) 420 { 421 ARG *cur; 422 423 if ((cur = malloc(sizeof(ARG))) == NULL) 424 errx(1, "malloc failure"); 425 cur->next = arglist; 426 cur->type = type; 427 cur->name = arg; 428 arglist = cur; 429 } 430 431 /* 432 * hostconv -- 433 * convert the hostname to search pattern; if the supplied host name 434 * has a domain attached that is the same as the current domain, rip 435 * off the domain suffix since that's what login(1) does. 436 */ 437 void 438 hostconv(char *arg) 439 { 440 static int first = 1; 441 static char *hostdot, name[MAXHOSTNAMELEN]; 442 char *argdot; 443 444 if (!(argdot = strchr(arg, '.'))) 445 return; 446 if (first) { 447 first = 0; 448 if (gethostname(name, sizeof(name))) 449 err(1, "gethostname"); 450 hostdot = strchr(name, '.'); 451 } 452 if (hostdot && !strcasecmp(hostdot, argdot)) 453 *argdot = '\0'; 454 } 455 456 /* 457 * ttyconv -- 458 * convert tty to correct name. 459 */ 460 char * 461 ttyconv(char *arg) 462 { 463 char *mval; 464 465 /* 466 * kludge -- we assume that all tty's end with 467 * a two character suffix. 468 */ 469 if (strlen(arg) == 2) { 470 /* either 6 for "ttyxx" or 8 for "console" */ 471 if ((mval = malloc(8)) == NULL) 472 errx(1, "malloc failure"); 473 if (!strcmp(arg, "co")) 474 (void)strcpy(mval, "console"); 475 else { 476 (void)strcpy(mval, "tty"); 477 (void)strcpy(mval + 3, arg); 478 } 479 return (mval); 480 } 481 if (!strncmp(arg, _PATH_DEV, sizeof(_PATH_DEV) - 1)) 482 return (arg + 5); 483 return (arg); 484 } 485 486 /* 487 * dateconv -- 488 * Convert the snapshot time in command line given in the format 489 * [[CC]YY]MMDDhhmm[.SS]] to a time_t. 490 * Derived from atime_arg1() in usr.bin/touch/touch.c 491 */ 492 time_t 493 dateconv(char *arg) 494 { 495 time_t timet; 496 struct tm *t; 497 int yearset; 498 char *p; 499 500 /* Start with the current time. */ 501 if (time(&timet) < 0) 502 err(1, "time"); 503 if ((t = localtime(&timet)) == NULL) 504 err(1, "localtime"); 505 506 /* [[CC]YY]MMDDhhmm[.SS] */ 507 if ((p = strchr(arg, '.')) == NULL) 508 t->tm_sec = 0; /* Seconds defaults to 0. */ 509 else { 510 if (strlen(p + 1) != 2) 511 goto terr; 512 *p++ = '\0'; 513 t->tm_sec = ATOI2(p); 514 } 515 516 yearset = 0; 517 switch (strlen(arg)) { 518 case 12: /* CCYYMMDDhhmm */ 519 t->tm_year = ATOI2(arg); 520 t->tm_year *= 100; 521 yearset = 1; 522 /* FALLTHROUGH */ 523 case 10: /* YYMMDDhhmm */ 524 if (yearset) { 525 yearset = ATOI2(arg); 526 t->tm_year += yearset; 527 } else { 528 yearset = ATOI2(arg); 529 if (yearset < 69) 530 t->tm_year = yearset + 2000; 531 else 532 t->tm_year = yearset + 1900; 533 } 534 t->tm_year -= 1900; /* Convert to UNIX time. */ 535 /* FALLTHROUGH */ 536 case 8: /* MMDDhhmm */ 537 t->tm_mon = ATOI2(arg); 538 --t->tm_mon; /* Convert from 01-12 to 00-11 */ 539 t->tm_mday = ATOI2(arg); 540 t->tm_hour = ATOI2(arg); 541 t->tm_min = ATOI2(arg); 542 break; 543 case 4: /* hhmm */ 544 t->tm_hour = ATOI2(arg); 545 t->tm_min = ATOI2(arg); 546 break; 547 default: 548 goto terr; 549 } 550 t->tm_isdst = -1; /* Figure out DST. */ 551 timet = mktime(t); 552 if (timet == -1) 553 terr: errx(1, 554 "out of range or illegal time specification: [[CC]YY]MMDDhhmm[.SS]"); 555 return timet; 556 } 557