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