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