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