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