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