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