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