1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License, Version 1.0 only 6 * (the "License"). You may not use this file except in compliance 7 * with the License. 8 * 9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 10 * or http://www.opensolaris.org/os/licensing. 11 * See the License for the specific language governing permissions 12 * and limitations under the License. 13 * 14 * When distributing Covered Code, include this CDDL HEADER in each 15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 16 * If applicable, add the following below this CDDL HEADER, with the 17 * fields enclosed by brackets "[]" replaced with your own identifying 18 * information: Portions Copyright [yyyy] [name of copyright owner] 19 * 20 * CDDL HEADER END 21 */ 22 /* 23 * Copyright (c) 2017 Olaf Bohlen 24 * 25 * Copyright (c) 2013 Gary Mills 26 * 27 * Copyright 2004 Sun Microsystems, Inc. All rights reserved. 28 * Use is subject to license terms. 29 */ 30 31 /* 32 * Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T 33 * All Rights Reserved 34 */ 35 36 /* 37 * University Copyright- Copyright (c) 1982, 1986, 1988 38 * The Regents of the University of California 39 * All Rights Reserved 40 * 41 * University Acknowledgment- Portions of this document are derived from 42 * software developed by the University of California, Berkeley, and its 43 * contributors. 44 */ 45 46 /* 47 * last 48 */ 49 #include <sys/types.h> 50 #include <stdio.h> 51 #include <stdlib.h> 52 #include <unistd.h> 53 #include <strings.h> 54 #include <signal.h> 55 #include <sys/stat.h> 56 #include <pwd.h> 57 #include <fcntl.h> 58 #include <utmpx.h> 59 #include <locale.h> 60 #include <ctype.h> 61 62 /* 63 * Use the full lengths from utmpx for NMAX, LMAX and HMAX . 64 */ 65 #define NMAX (sizeof (((struct utmpx *)0)->ut_user)) 66 #define LMAX (sizeof (((struct utmpx *)0)->ut_line)) 67 #define HMAX (sizeof (((struct utmpx *)0)->ut_host)) 68 69 /* Print minimum field widths. */ 70 #define LOGIN_WIDTH 8 71 #define LINE_WIDTH 12 72 73 #define SECDAY (24*60*60) 74 #define CHUNK_SIZE 256 75 76 #define lineq(a, b) (strncmp(a, b, LMAX) == 0) 77 #define nameq(a, b) (strncmp(a, b, NMAX) == 0) 78 #define hosteq(a, b) (strncmp(a, b, HMAX) == 0) 79 #define linehostnameq(a, b, c, d) \ 80 (lineq(a, b)&&hosteq(a+LMAX+1, c)&&nameq(a+LMAX+HMAX+2, d)) 81 82 #define USAGE "usage: last [-n number] [-f filename] [-a ] [ -l ] [name |\ 83 tty] ...\n" 84 85 /* Beware: These are set in main() to exclude the executable name. */ 86 static char **argv; 87 static int argc; 88 static char **names; 89 static int names_num; 90 91 static struct utmpx buf[128]; 92 93 /* 94 * ttnames and logouts are allocated in the blocks of 95 * CHUNK_SIZE lines whenever needed. The count of the 96 * current size is maintained in the variable "lines" 97 * The variable bootxtime is used to hold the time of 98 * the last BOOT_TIME 99 * All elements of the logouts are initialised to bootxtime 100 * everytime the buffer is reallocated. 101 */ 102 103 static char **ttnames; 104 static time_t *logouts; 105 static time_t bootxtime; 106 static int lines; 107 static char timef[128]; 108 static char hostf[HMAX + 1]; 109 110 static char *strspl(char *, char *); 111 static void onintr(int); 112 static void reallocate_buffer(); 113 static void memory_alloc(int); 114 static int want(struct utmpx *, char **, char **); 115 static void record_time(time_t *, int *, int, struct utmpx *); 116 117 int 118 main(int ac, char **av) 119 { 120 int i, j; 121 int aflag = 0; 122 int lflag = 0; /* parameter -l, long format with seconds and years */ 123 int fpos; /* current position in time format buffer */ 124 int chrcnt; /* # of chars formatted by current sprintf */ 125 int bl, wtmp; 126 char *ct; 127 char *ut_host; 128 char *ut_user; 129 struct utmpx *bp; 130 time_t otime; 131 struct stat stb; 132 int print = 0; 133 char *crmsg = (char *)0; 134 long outrec = 0; 135 long maxrec = 0x7fffffffL; 136 char *wtmpfile = "/var/adm/wtmpx"; 137 size_t hostf_len; 138 139 (void) setlocale(LC_ALL, ""); 140 #if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */ 141 #define TEXT_DOMAIN "SYS_TEST" /* Use this only if it weren't. */ 142 #endif 143 (void) textdomain(TEXT_DOMAIN); 144 145 (void) time(&buf[0].ut_xtime); 146 ac--, av++; 147 argc = ac; 148 argv = av; 149 names = malloc(argc * sizeof (char *)); 150 if (names == NULL) { 151 perror("last"); 152 exit(2); 153 } 154 names_num = 0; 155 for (i = 0; i < argc; i++) { 156 if (argv[i][0] == '-') { 157 158 /* -[0-9]* sets max # records to print */ 159 if (isdigit(argv[i][1])) { 160 maxrec = atoi(argv[i]+1); 161 continue; 162 } 163 164 for (j = 1; argv[i][j] != '\0'; ++j) { 165 switch (argv[i][j]) { 166 167 /* -f name sets filename of wtmp file */ 168 case 'f': 169 if (argv[i][j+1] != '\0') { 170 wtmpfile = &argv[i][j+1]; 171 } else if (i+1 < argc) { 172 wtmpfile = argv[++i]; 173 } else { 174 (void) fprintf(stderr, 175 gettext("last: argument to " 176 "-f is missing\n")); 177 (void) fprintf(stderr, 178 gettext(USAGE)); 179 exit(1); 180 } 181 goto next_word; 182 183 /* -n number sets max # records to print */ 184 case 'n': { 185 char *arg; 186 187 if (argv[i][j+1] != '\0') { 188 arg = &argv[i][j+1]; 189 } else if (i+1 < argc) { 190 arg = argv[++i]; 191 } else { 192 (void) fprintf(stderr, 193 gettext("last: argument to " 194 "-n is missing\n")); 195 (void) fprintf(stderr, 196 gettext(USAGE)); 197 exit(1); 198 } 199 200 if (!isdigit(*arg)) { 201 (void) fprintf(stderr, 202 gettext("last: argument to " 203 "-n is not a number\n")); 204 (void) fprintf(stderr, 205 gettext(USAGE)); 206 exit(1); 207 } 208 maxrec = atoi(arg); 209 goto next_word; 210 } 211 212 /* -a displays hostname last on the line */ 213 case 'a': 214 aflag++; 215 break; 216 217 /* -l turns on long dates and times */ 218 case 'l': 219 lflag++; 220 break; 221 222 default: 223 (void) fprintf(stderr, gettext(USAGE)); 224 exit(1); 225 } 226 } 227 228 next_word: 229 continue; 230 } 231 232 if (strlen(argv[i]) > 2 || strcmp(argv[i], "~") == 0 || 233 getpwnam(argv[i]) != NULL) { 234 /* Not a tty number. */ 235 names[names_num] = argv[i]; 236 ++names_num; 237 } else { 238 /* tty number. Prepend "tty". */ 239 names[names_num] = strspl("tty", argv[i]); 240 ++names_num; 241 } 242 } 243 244 wtmp = open(wtmpfile, 0); 245 if (wtmp < 0) { 246 perror(wtmpfile); 247 exit(1); 248 } 249 (void) fstat(wtmp, &stb); 250 bl = (stb.st_size + sizeof (buf)-1) / sizeof (buf); 251 if (signal(SIGINT, SIG_IGN) != SIG_IGN) { 252 (void) signal(SIGINT, onintr); 253 (void) signal(SIGQUIT, onintr); 254 } 255 lines = CHUNK_SIZE; 256 ttnames = calloc(lines, sizeof (char *)); 257 logouts = calloc(lines, sizeof (time_t)); 258 if (ttnames == NULL || logouts == NULL) { 259 (void) fprintf(stderr, gettext("Out of memory \n ")); 260 exit(2); 261 } 262 for (bl--; bl >= 0; bl--) { 263 (void) lseek(wtmp, (off_t)(bl * sizeof (buf)), 0); 264 bp = &buf[read(wtmp, buf, sizeof (buf)) / sizeof (buf[0]) - 1]; 265 for (; bp >= buf; bp--) { 266 if (want(bp, &ut_host, &ut_user)) { 267 for (i = 0; i <= lines; i++) { 268 if (i == lines) 269 reallocate_buffer(); 270 if (ttnames[i] == NULL) { 271 memory_alloc(i); 272 /* 273 * LMAX+HMAX+NMAX+3 bytes have been 274 * allocated for ttnames[i]. 275 * If bp->ut_line is longer than LMAX, 276 * ut_host is longer than HMAX, 277 * and ut_user is longer than NMAX, 278 * truncate it to fit ttnames[i]. 279 */ 280 (void) strlcpy(ttnames[i], bp->ut_line, 281 LMAX+1); 282 (void) strlcpy(ttnames[i]+LMAX+1, 283 ut_host, HMAX+1); 284 (void) strlcpy(ttnames[i]+LMAX+HMAX+2, 285 ut_user, NMAX+1); 286 record_time(&otime, &print, 287 i, bp); 288 break; 289 } else if (linehostnameq(ttnames[i], 290 bp->ut_line, ut_host, ut_user)) { 291 record_time(&otime, 292 &print, i, bp); 293 break; 294 } 295 } 296 } 297 if (print) { 298 if (strncmp(bp->ut_line, "ftp", 3) == 0) 299 bp->ut_line[3] = '\0'; 300 if (strncmp(bp->ut_line, "uucp", 4) == 0) 301 bp->ut_line[4] = '\0'; 302 303 ct = ctime(&bp->ut_xtime); 304 (void) printf(gettext("%-*.*s %-*.*s "), 305 LOGIN_WIDTH, NMAX, bp->ut_name, 306 LINE_WIDTH, LMAX, bp->ut_line); 307 hostf_len = strlen(bp->ut_host); 308 (void) snprintf(hostf, sizeof (hostf), 309 "%-*.*s", hostf_len, hostf_len, 310 bp->ut_host); 311 /* write seconds and year if -l specified */ 312 if (lflag > 0) { 313 fpos = snprintf(timef, sizeof (timef), 314 "%10.10s %13.13s ", 315 ct, 11 + ct); 316 } else { 317 fpos = snprintf(timef, sizeof (timef), 318 "%10.10s %5.5s ", 319 ct, 11 + ct); 320 } 321 322 if (!lineq(bp->ut_line, "system boot") && 323 !lineq(bp->ut_line, "system down")) { 324 if (otime == 0 && 325 bp->ut_type == USER_PROCESS) { 326 327 if (fpos < sizeof (timef)) { 328 /* timef still has room */ 329 (void) snprintf(timef + fpos, sizeof (timef) - fpos, 330 gettext(" still logged in")); 331 } 332 333 } else { 334 time_t delta; 335 if (otime < 0) { 336 otime = -otime; 337 /* 338 * TRANSLATION_NOTE 339 * See other notes on "down" 340 * and "- %5.5s". 341 * "-" means "until". This 342 * is displayed after the 343 * starting time as in: 344 * 16:20 - down 345 * You probably don't want to 346 * translate this. Should you 347 * decide to translate this, 348 * translate "- %5.5s" too. 349 */ 350 351 if (fpos < sizeof (timef)) { 352 /* timef still has room */ 353 chrcnt = snprintf(timef + fpos, sizeof (timef) - fpos, 354 gettext("- %s"), crmsg); 355 fpos += chrcnt; 356 } 357 358 } else { 359 360 if (fpos < sizeof (timef)) { 361 /* timef still has room */ 362 if (lflag > 0) { 363 chrcnt = snprintf(timef + fpos, sizeof (timef) - fpos, 364 gettext("- %8.8s"), ctime(&otime) + 11); 365 } else { 366 chrcnt = snprintf(timef + fpos, sizeof (timef) - fpos, 367 gettext("- %5.5s"), ctime(&otime) + 11); 368 } 369 fpos += chrcnt; 370 } 371 372 } 373 delta = otime - bp->ut_xtime; 374 if (delta < SECDAY) { 375 376 if (fpos < sizeof (timef)) { 377 /* timef still has room */ 378 if (lflag > 0) { 379 (void) snprintf(timef + fpos, sizeof (timef) - fpos, 380 gettext(" (%8.8s)"), asctime(gmtime(&delta)) + 11); 381 } else { 382 (void) snprintf(timef + fpos, sizeof (timef) - fpos, 383 gettext(" (%5.5s)"), asctime(gmtime(&delta)) + 11); 384 } 385 386 } 387 388 } else { 389 390 if (fpos < sizeof (timef)) { 391 /* timef still has room */ 392 if (lflag > 0) { 393 (void) snprintf(timef + fpos, sizeof (timef) - fpos, 394 gettext(" (%ld+%8.8s)"), delta / SECDAY, 395 asctime(gmtime(&delta)) + 11); 396 } else { 397 (void) snprintf(timef + fpos, sizeof (timef) - fpos, 398 gettext(" (%ld+%5.5s)"), delta / SECDAY, 399 asctime(gmtime(&delta)) + 11); 400 } 401 } 402 403 } 404 } 405 } 406 if (lflag > 0) { 407 if (aflag) 408 (void) printf("%-.*s %-.*s\n", 409 strlen(timef), timef, 410 strlen(hostf), hostf); 411 else 412 (void) printf( 413 "%-16.16s %-.*s\n", hostf, 414 strlen(timef), timef); 415 } else { 416 if (aflag) 417 (void) printf( 418 "%-35.35s %-.*s\n", timef, 419 strlen(hostf), hostf); 420 else 421 (void) printf( 422 "%-16.16s %-.35s\n", hostf, 423 timef); 424 } 425 (void) fflush(stdout); 426 if (++outrec >= maxrec) 427 exit(0); 428 } 429 /* 430 * when the system is down or crashed. 431 */ 432 if (bp->ut_type == BOOT_TIME) { 433 for (i = 0; i < lines; i++) 434 logouts[i] = -bp->ut_xtime; 435 bootxtime = -bp->ut_xtime; 436 /* 437 * TRANSLATION_NOTE 438 * Translation of this "down " will replace 439 * the %s in "- %s". "down" is used instead 440 * of the real time session was ended, probably 441 * because the session ended by a sudden crash. 442 */ 443 crmsg = gettext("down "); 444 } 445 print = 0; /* reset the print flag */ 446 } 447 } 448 ct = ctime(&buf[0].ut_xtime); 449 if (lflag > 0) { 450 (void) printf(gettext("\nwtmp begins %10.10s %13.13s \n"), ct, 451 ct + 11); 452 } else { 453 (void) printf(gettext("\nwtmp begins %10.10s %5.5s \n"), ct, 454 ct + 11); 455 } 456 457 /* free() called to prevent lint warning about names */ 458 free(names); 459 460 return (0); 461 } 462 463 static void 464 reallocate_buffer() 465 { 466 int j; 467 static char **tmpttnames; 468 static time_t *tmplogouts; 469 470 lines += CHUNK_SIZE; 471 tmpttnames = realloc(ttnames, sizeof (char *)*lines); 472 tmplogouts = realloc(logouts, sizeof (time_t)*lines); 473 if (tmpttnames == NULL || tmplogouts == NULL) { 474 (void) fprintf(stderr, gettext("Out of memory \n")); 475 exit(2); 476 } else { 477 ttnames = tmpttnames; 478 logouts = tmplogouts; 479 } 480 for (j = lines-CHUNK_SIZE; j < lines; j++) { 481 ttnames[j] = NULL; 482 logouts[j] = bootxtime; 483 } 484 } 485 486 static void 487 memory_alloc(int i) 488 { 489 ttnames[i] = (char *)malloc(LMAX + HMAX + NMAX + 3); 490 if (ttnames[i] == NULL) { 491 (void) fprintf(stderr, gettext("Out of memory \n ")); 492 exit(2); 493 } 494 } 495 496 static void 497 onintr(int signo) 498 { 499 char *ct; 500 501 if (signo == SIGQUIT) 502 (void) signal(SIGQUIT, (void(*)())onintr); 503 ct = ctime(&buf[0].ut_xtime); 504 (void) printf(gettext("\ninterrupted %10.10s %5.5s \n"), ct, ct + 11); 505 (void) fflush(stdout); 506 if (signo == SIGINT) 507 exit(1); 508 } 509 510 static int 511 want(struct utmpx *bp, char **host, char **user) 512 { 513 char **name; 514 int i; 515 char *zerostr = "\0"; 516 517 *host = zerostr; *user = zerostr; 518 519 /* if ut_line = dtremote for the users who did dtremote login */ 520 if (strncmp(bp->ut_line, "dtremote", 8) == 0) { 521 *host = bp->ut_host; 522 *user = bp->ut_user; 523 } 524 /* if ut_line = dtlocal for the users who did a dtlocal login */ 525 else if (strncmp(bp->ut_line, "dtlocal", 7) == 0) { 526 *host = bp->ut_host; 527 *user = bp->ut_user; 528 } 529 /* 530 * Both dtremote and dtlocal can have multiple entries in 531 * /var/adm/wtmpx with these values, so the user and host 532 * entries are also checked 533 */ 534 if ((bp->ut_type == BOOT_TIME) || (bp->ut_type == DOWN_TIME)) 535 (void) strcpy(bp->ut_user, "reboot"); 536 537 if (bp->ut_type != USER_PROCESS && bp->ut_type != DEAD_PROCESS && 538 bp->ut_type != BOOT_TIME && bp->ut_type != DOWN_TIME) 539 return (0); 540 541 if (bp->ut_user[0] == '.') 542 return (0); 543 544 if (names_num == 0) { 545 if (bp->ut_line[0] != '\0') 546 return (1); 547 } else { 548 name = names; 549 for (i = 0; i < names_num; i++, name++) { 550 if (nameq(*name, bp->ut_name) || 551 lineq(*name, bp->ut_line) || 552 (lineq(*name, "ftp") && 553 (strncmp(bp->ut_line, "ftp", 3) == 0))) { 554 return (1); 555 } 556 } 557 } 558 return (0); 559 } 560 561 static char * 562 strspl(char *left, char *right) 563 { 564 size_t ressize = strlen(left) + strlen(right) + 1; 565 566 char *res = malloc(ressize); 567 568 if (res == NULL) { 569 perror("last"); 570 exit(2); 571 } 572 (void) strlcpy(res, left, ressize); 573 (void) strlcat(res, right, ressize); 574 return (res); 575 } 576 577 static void 578 record_time(time_t *otime, int *print, int i, struct utmpx *bp) 579 { 580 *otime = logouts[i]; 581 logouts[i] = bp->ut_xtime; 582 if ((bp->ut_type == USER_PROCESS && bp->ut_user[0] != '\0') || 583 (bp->ut_type == BOOT_TIME) || (bp->ut_type == DOWN_TIME)) 584 *print = 1; 585 else 586 *print = 0; 587 } 588