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 (the "License").
   6  * You may not use this file except in compliance with the License.
   7  *
   8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
   9  * or http://www.opensolaris.org/os/licensing.
  10  * See the License for the specific language governing permissions
  11  * and limitations under the License.
  12  *
  13  * When distributing Covered Code, include this CDDL HEADER in each
  14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
  15  * If applicable, add the following below this CDDL HEADER, with the
  16  * fields enclosed by brackets "[]" replaced with your own identifying
  17  * information: Portions Copyright [yyyy] [name of copyright owner]
  18  *
  19  * CDDL HEADER END
  20  */
  21 
  22 /*
  23  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
  24  * Use is subject to license terms.
  25  */
  26 
  27 /*      Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */
  28 /*        All Rights Reserved   */
  29 
  30 /*
  31  * Copyright (c) 1982, 1986, 1988
  32  * The Regents of the University of California
  33  * All Rights Reserved
  34  *
  35  * Portions of this document are derived from
  36  * software developed by the University of California, Berkeley, and its
  37  * contributors.
  38  */
  39 
  40 /*
  41  * This is a finger program.  It prints out useful information about users
  42  * by digging it up from various system files.
  43  *
  44  * There are three output formats, all of which give login name, teletype
  45  * line number, and login time.  The short output format is reminiscent
  46  * of finger on ITS, and gives one line of information per user containing
  47  * in addition to the minimum basic requirements (MBR), the user's full name,
  48  * idle time and location.
  49  * The quick style output is UNIX who-like, giving only name, teletype and
  50  * login time.  Finally, the long style output give the same information
  51  * as the short (in more legible format), the home directory and shell
  52  * of the user, and, if it exits, a copy of the file .plan in the users
  53  * home directory.  Finger may be called with or without a list of people
  54  * to finger -- if no list is given, all the people currently logged in
  55  * are fingered.
  56  *
  57  * The program is validly called by one of the following:
  58  *
  59  *      finger                  {short form list of users}
  60  *      finger -l               {long form list of users}
  61  *      finger -b               {briefer long form list of users}
  62  *      finger -q               {quick list of users}
  63  *      finger -i               {quick list of users with idle times}
  64  *      finger -m               {matches arguments against only username}
  65  *      finger -f               {suppress header in non-long form}
  66  *      finger -p               {suppress printing of .plan file}
  67  *      finger -h               {suppress printing of .project file}
  68  *      finger -i               {forces "idle" output format}
  69  *      finger namelist         {long format list of specified users}
  70  *      finger -s namelist      {short format list of specified users}
  71  *      finger -w namelist      {narrow short format list of specified users}
  72  *
  73  * where 'namelist' is a list of users login names.
  74  * The other options can all be given after one '-', or each can have its
  75  * own '-'.  The -f option disables the printing of headers for short and
  76  * quick outputs.  The -b option briefens long format outputs.  The -p
  77  * option turns off plans for long format outputs.
  78  */
  79 
  80 #include <sys/types.h>
  81 #include <sys/stat.h>
  82 #include <utmpx.h>
  83 #include <sys/signal.h>
  84 #include <pwd.h>
  85 #include <stdio.h>
  86 #include <lastlog.h>
  87 #include <ctype.h>
  88 #include <sys/time.h>
  89 #include <time.h>
  90 #include <sys/socket.h>
  91 #include <netinet/in.h>
  92 #include <netdb.h>
  93 #include <locale.h>
  94 #include <sys/select.h>
  95 #include <stdlib.h>
  96 #include <strings.h>
  97 #include <fcntl.h>
  98 #include <curses.h>
  99 #include <unctrl.h>
 100 #include <maillock.h>
 101 #include <deflt.h>
 102 #include <unistd.h>
 103 #include <arpa/inet.h>
 104 #include <macros.h>
 105 
 106 static char gecos_ignore_c = '*';       /* ignore this in real name */
 107 static char gecos_sep_c = ',';          /* separator in pw_gecos field */
 108 static char gecos_samename = '&';   /* repeat login name in real name */
 109 
 110 #define TALKABLE        0220            /* tty is writable if this mode */
 111 
 112 #define NMAX    sizeof (((struct utmpx *)0)->ut_name)
 113 #define LMAX    sizeof (((struct utmpx *)0)->ut_line)
 114 #define HMAX    sizeof (((struct utmpx *)0)->ut_host)
 115 
 116 struct person {                         /* one for each person fingered */
 117         char *name;                     /* name */
 118         char tty[LMAX+1];               /* null terminated tty line */
 119         char host[HMAX+1];              /* null terminated remote host name */
 120         char *ttyloc;                   /* location of tty line, if any */
 121         time_t loginat;                 /* time of (last) login */
 122         time_t idletime;                /* how long idle (if logged in) */
 123         char *realname;                 /* pointer to full name */
 124         struct passwd *pwd;             /* structure of /etc/passwd stuff */
 125         char loggedin;                  /* person is logged in */
 126         char writable;                  /* tty is writable */
 127         char original;                  /* this is not a duplicate entry */
 128         struct person *link;            /* link to next person */
 129 };
 130 
 131 char LASTLOG[] = "/var/adm/lastlog";    /* last login info */
 132 char PLAN[] = "/.plan";                 /* what plan file is */
 133 char PROJ[] = "/.project";              /* what project file */
 134 
 135 int unbrief = 1;                        /* -b option default */
 136 int header = 1;                         /* -f option default */
 137 int hack = 1;                           /* -h option default */
 138 int idle = 0;                           /* -i option default */
 139 int large = 0;                          /* -l option default */
 140 int match = 1;                          /* -m option default */
 141 int plan = 1;                           /* -p option default */
 142 int unquick = 1;                        /* -q option default */
 143 int small = 0;                          /* -s option default */
 144 int wide = 1;                           /* -w option default */
 145 
 146 /*
 147  * RFC 1288 says that system administrators should have the option of
 148  * separately allowing ASCII characters less than 32 or greater than
 149  * 126.  The termpass variable keeps track of this.
 150  */
 151 char    defaultfile[] = "/etc/default/finger";
 152 char    passvar[] = "PASS=";
 153 int     termpass = 0;                   /* default is ASCII only */
 154 char *termopts[] = {
 155 #define TERM_LOW        0
 156         "low",
 157 #define TERM_HIGH       1
 158         "high",
 159         (char *)NULL
 160 };
 161 #define TS_LOW  (1 << TERM_LOW)           /* print characters less than 32 */
 162 #define TS_HIGH (1 << TERM_HIGH)  /* print characters greater than 126 */
 163 
 164 
 165 int unshort;
 166 FILE *lf;                               /* LASTLOG file pointer */
 167 struct person *person1;                 /* list of people */
 168 size_t nperson;                         /* number of people */
 169 time_t tloc;                            /* current time */
 170 
 171 char usagestr[] = "Usage: "
 172         "finger [-bfhilmpqsw] [name1 [name2 ...] ]\n";
 173 
 174 int AlreadyPrinted(uid_t uid);
 175 void AnyMail(char *name);
 176 void catfile(char *s, mode_t mode, int trunc_at_nl);
 177 void decode(struct person *pers);
 178 void doall(void);
 179 void donames(char **argv);
 180 void findidle(struct person *pers);
 181 void findwhen(struct person *pers);
 182 void fwclose(void);
 183 void fwopen(void);
 184 void initscreening(void);
 185 void ltimeprint(char *before, time_t *dt, char *after);
 186 int matchcmp(char *gname, char *login, char *given);
 187 int namecmp(char *name1, char *name2);
 188 int netfinger(char *name);
 189 void personprint(struct person *pers);
 190 void print(void);
 191 struct passwd *pwdcopy(const struct passwd *pfrom);
 192 void quickprint(struct person *pers);
 193 void shortprint(struct person *pers);
 194 void stimeprint(time_t *dt);
 195 void sort_by_username(void);
 196 
 197 
 198 int
 199 main(int argc, char **argv)
 200 {
 201         int c;
 202 
 203         (void) setlocale(LC_ALL, "");
 204         /* parse command line for (optional) arguments */
 205         while ((c = getopt(argc, argv, "bfhilmpqsw")) != EOF)
 206                         switch (c) {
 207                         case 'b':
 208                                 unbrief = 0;
 209                                 break;
 210                         case 'f':
 211                                 header = 0;
 212                                 break;
 213                         case 'h':
 214                                 hack = 0;
 215                                 break;
 216                         case 'i':
 217                                 idle = 1;
 218                                 unquick = 0;
 219                                 break;
 220                         case 'l':
 221                                 large = 1;
 222                                 break;
 223                         case 'm':
 224                                 match = 0;
 225                                 break;
 226                         case 'p':
 227                                 plan = 0;
 228                                 break;
 229                         case 'q':
 230                                 unquick = 0;
 231                                 break;
 232                         case 's':
 233                                 small = 1;
 234                                 break;
 235                         case 'w':
 236                                 wide = 0;
 237                                 break;
 238                         default:
 239                                 (void) fprintf(stderr, usagestr);
 240                                 exit(1);
 241                         }
 242         if (unquick || idle)
 243                 tloc = time(NULL);
 244 
 245         /* find out what filtering on .plan/.project files we should do */
 246         initscreening();
 247 
 248         /*
 249          * optind == argc means no names given
 250          */
 251         if (optind == argc)
 252                 doall();
 253         else
 254                 donames(&argv[optind]);
 255 
 256         sort_by_username();
 257 
 258         if (nperson > 0)
 259                 print();
 260         return (0);
 261         /* NOTREACHED */
 262 }
 263 
 264 void
 265 doall(void)
 266 {
 267         struct person *p;
 268         struct passwd *pw;
 269         struct utmpx *u;
 270         char name[NMAX + 1];
 271 
 272         unshort = large;
 273         setutxent();
 274         if (unquick) {
 275                 setpwent();
 276                 fwopen();
 277         }
 278         while ((u = getutxent()) != NULL) {
 279                 if (u->ut_name[0] == 0 ||
 280                     nonuserx(*u) ||
 281                     u->ut_type != USER_PROCESS)
 282                         continue;
 283                 if (person1 == NULL)
 284                         p = person1 = malloc(sizeof (*p));
 285                 else {
 286                         p->link = malloc(sizeof (*p));
 287                         p = p->link;
 288                 }
 289                 bcopy(u->ut_name, name, NMAX);
 290                 name[NMAX] = 0;
 291                 bcopy(u->ut_line, p->tty, LMAX);
 292                 p->tty[LMAX] = 0;
 293                 bcopy(u->ut_host, p->host, HMAX);
 294                 p->host[HMAX] = 0;
 295                 p->loginat = u->ut_tv.tv_sec;
 296                 p->pwd = NULL;
 297                 p->loggedin = 1;
 298                 if (unquick && (pw = getpwnam(name))) {
 299                         p->pwd = pwdcopy(pw);
 300                         decode(p);
 301                         p->name = p->pwd->pw_name;
 302                 } else
 303                         p->name = strdup(name);
 304                 p->ttyloc = NULL;
 305 
 306                 nperson++;
 307         }
 308         if (unquick) {
 309                 fwclose();
 310                 endpwent();
 311         }
 312         endutxent();
 313         if (nperson == 0) {
 314                 (void) printf("No one logged on\n");
 315                 return;
 316         }
 317         p->link = NULL;
 318 }
 319 
 320 void
 321 donames(char **argv)
 322 {
 323         struct person   *p;
 324         struct passwd   *pw;
 325         struct utmpx    *u;
 326 
 327         /*
 328          * get names from command line and check to see if they're
 329          * logged in
 330          */
 331         unshort = !small;
 332         for (; *argv != NULL; argv++) {
 333                 if (netfinger(*argv))
 334                         continue;
 335                 if (person1 == NULL)
 336                         p = person1 = malloc(sizeof (*p));
 337                 else {
 338                         p->link = malloc(sizeof (*p));
 339                         p = p->link;
 340                 }
 341                 p->name = *argv;
 342                 p->loggedin = 0;
 343                 p->original = 1;
 344                 p->pwd = NULL;
 345 
 346                 nperson++;
 347         }
 348         if (nperson == 0)
 349                 return;
 350         p->link = NULL;
 351         /*
 352          * if we are doing it, read /etc/passwd for the useful info
 353          */
 354         if (unquick) {
 355                 setpwent();
 356                 if (!match) {
 357                         for (p = person1; p != NULL; p = p->link) {
 358                                 if ((pw = getpwnam(p->name)) != NULL)
 359                                         p->pwd = pwdcopy(pw);
 360                         }
 361                 } else {
 362                         while ((pw = getpwent()) != NULL) {
 363                                 for (p = person1; p != NULL; p = p->link) {
 364                                         if (!p->original)
 365                                                 continue;
 366                                         if (strcmp(p->name, pw->pw_name) != 0 &&
 367                                             !matchcmp(pw->pw_gecos, pw->pw_name,
 368                                             p->name)) {
 369                                                 continue;
 370                                         }
 371                                         if (p->pwd == NULL) {
 372                                                 p->pwd = pwdcopy(pw);
 373                                         } else {
 374                                                 struct person *new;
 375                                                 /*
 376                                                  * Handle multiple login names.
 377                                                  * Insert new "duplicate" entry
 378                                                  * behind.
 379                                                  */
 380                                                 new = malloc(sizeof (*new));
 381                                                 new->pwd = pwdcopy(pw);
 382                                                 new->name = p->name;
 383                                                 new->original = 1;
 384                                                 new->loggedin = 0;
 385                                                 new->ttyloc = NULL;
 386                                                 new->link = p->link;
 387                                                 p->original = 0;
 388                                                 p->link = new;
 389                                                 p = new;
 390 
 391                                                 nperson++;
 392                                         }
 393                                 }
 394                         }
 395                 }
 396                 endpwent();
 397         }
 398         /* Now get login information */
 399         setutxent();
 400         while ((u = getutxent()) != NULL) {
 401                 if (u->ut_name[0] == 0 || u->ut_type != USER_PROCESS)
 402                         continue;
 403                 for (p = person1; p != NULL; p = p->link) {
 404                         p->ttyloc = NULL;
 405                         if (p->loggedin == 2)
 406                                 continue;
 407                         if (strncmp((p->pwd != NULL) ?
 408                             p->pwd->pw_name : p->name,
 409                             u->ut_name, NMAX) != 0)
 410                                 continue;
 411                         if (p->loggedin == 0) {
 412                                 bcopy(u->ut_line, p->tty, LMAX);
 413                                 p->tty[LMAX] = 0;
 414                                 bcopy(u->ut_host, p->host, HMAX);
 415                                 p->host[HMAX] = 0;
 416                                 p->loginat = u->ut_tv.tv_sec;
 417                                 p->loggedin = 1;
 418                         } else {        /* p->loggedin == 1 */
 419                                 struct person *new;
 420                                 new = malloc(sizeof (*new));
 421                                 new->name = p->name;
 422                                 bcopy(u->ut_line, new->tty, LMAX);
 423                                 new->tty[LMAX] = 0;
 424                                 bcopy(u->ut_host, new->host, HMAX);
 425                                 new->host[HMAX] = 0;
 426                                 new->loginat = u->ut_tv.tv_sec;
 427                                 new->pwd = p->pwd;
 428                                 new->loggedin = 1;
 429                                 new->original = 0;
 430                                 new->link = p->link;
 431                                 p->loggedin = 2;
 432                                 p->link = new;
 433                                 p = new;
 434 
 435                                 nperson++;
 436                         }
 437                 }
 438         }
 439         endutxent();
 440         if (unquick) {
 441                 fwopen();
 442                 for (p = person1; p != NULL; p = p->link)
 443                         decode(p);
 444                 fwclose();
 445         }
 446 }
 447 
 448 void
 449 print(void)
 450 {
 451         struct person *p;
 452         char *s;
 453 
 454         /*
 455          * print out what we got
 456          */
 457         if (header) {
 458                 if (unquick) {
 459                         if (!unshort) {
 460                                 if (wide) {
 461                                         (void) printf("Login       "
 462                                             "Name               TTY         "
 463                                             "Idle    When    Where\n");
 464                                 } else {
 465                                         (void) printf("Login    TTY Idle    "
 466                                             "When    Where\n");
 467                                 }
 468                         }
 469                 } else {
 470                         (void) printf("Login      TTY                When");
 471                         if (idle)
 472                                 (void) printf("             Idle");
 473                         (void) putchar('\n');
 474                 }
 475         }
 476         for (p = person1; p != NULL; p = p->link) {
 477                 if (!unquick) {
 478                         quickprint(p);
 479                         continue;
 480                 }
 481                 if (!unshort) {
 482                         shortprint(p);
 483                         continue;
 484                 }
 485                 personprint(p);
 486                 if (p->pwd != NULL && !AlreadyPrinted(p->pwd->pw_uid)) {
 487                         AnyMail(p->pwd->pw_name);
 488                         if (hack) {
 489                                 struct stat sbuf;
 490 
 491                                 s = malloc(strlen(p->pwd->pw_dir) +
 492                                     sizeof (PROJ));
 493                                 if (s != NULL) {
 494                                         (void) strcpy(s, p->pwd->pw_dir);
 495                                         (void) strcat(s, PROJ);
 496                                         if (stat(s, &sbuf) != -1 &&
 497                                             (S_ISREG(sbuf.st_mode) ||
 498                                             S_ISFIFO(sbuf.st_mode)) &&
 499                                             (sbuf.st_mode & S_IROTH)) {
 500                                                 (void) printf("Project: ");
 501                                                 catfile(s, sbuf.st_mode, 1);
 502                                                 (void) putchar('\n');
 503                                         }
 504                                         free(s);
 505                                 }
 506                         }
 507                         if (plan) {
 508                                 struct stat sbuf;
 509 
 510                                 s = malloc(strlen(p->pwd->pw_dir) +
 511                                     sizeof (PLAN));
 512                                 if (s != NULL) {
 513                                         (void) strcpy(s, p->pwd->pw_dir);
 514                                         (void) strcat(s, PLAN);
 515                                         if (stat(s, &sbuf) == -1 ||
 516                                             (!S_ISREG(sbuf.st_mode) &&
 517                                             !S_ISFIFO(sbuf.st_mode)) ||
 518                                             ((sbuf.st_mode & S_IROTH) == 0))
 519                                                 (void) printf("No Plan.\n");
 520                                         else {
 521                                                 (void) printf("Plan:\n");
 522                                                 catfile(s, sbuf.st_mode, 0);
 523                                         }
 524                                         free(s);
 525                                 }
 526                         }
 527                 }
 528                 if (p->link != NULL)
 529                         (void) putchar('\n');
 530         }
 531 }
 532 
 533 /*
 534  * Duplicate a pwd entry.
 535  * Note: Only the useful things (what the program currently uses) are copied.
 536  */
 537 struct passwd *
 538 pwdcopy(const struct passwd *pfrom)
 539 {
 540         struct passwd *pto;
 541 
 542         pto = malloc(sizeof (*pto));
 543         pto->pw_name = strdup(pfrom->pw_name);
 544         pto->pw_uid = pfrom->pw_uid;
 545         pto->pw_gecos = strdup(pfrom->pw_gecos);
 546         pto->pw_dir = strdup(pfrom->pw_dir);
 547         pto->pw_shell = strdup(pfrom->pw_shell);
 548         return (pto);
 549 }
 550 
 551 /*
 552  * print out information on quick format giving just name, tty, login time
 553  * and idle time if idle is set.
 554  */
 555 void
 556 quickprint(struct person *pers)
 557 {
 558         (void) printf("%-8.8s  ", pers->name);
 559         if (pers->loggedin) {
 560                 if (idle) {
 561                         findidle(pers);
 562                         (void) printf("%c%-12s %-16.16s",
 563                             pers->writable ? ' ' : '*',
 564                             pers->tty, ctime(&pers->loginat));
 565                         ltimeprint("   ", &pers->idletime, "");
 566                 } else {
 567                         (void) printf(" %-12s %-16.16s",
 568                             pers->tty, ctime(&pers->loginat));
 569                 }
 570                 (void) putchar('\n');
 571         } else {
 572                 (void) printf("          Not Logged In\n");
 573         }
 574 }
 575 
 576 /*
 577  * print out information in short format, giving login name, full name,
 578  * tty, idle time, login time, and host.
 579  */
 580 void
 581 shortprint(struct person *pers)
 582 {
 583         char *p;
 584 
 585         if (pers->pwd == NULL) {
 586                 (void) printf("%-15s       ???\n", pers->name);
 587                 return;
 588         }
 589         (void) printf("%-8s", pers->pwd->pw_name);
 590         if (wide) {
 591                 if (pers->realname != NULL) {
 592                         (void) printf(" %-20.20s", pers->realname);
 593                 } else {
 594                         (void) printf("        ???          ");
 595                 }
 596         }
 597         (void) putchar(' ');
 598         if (pers->loggedin && !pers->writable) {
 599                 (void) putchar('*');
 600         } else {
 601                 (void) putchar(' ');
 602         }
 603         if (*pers->tty) {
 604                 (void) printf("%-11.11s ", pers->tty);
 605         } else {
 606                 (void) printf("            ");  /* 12 spaces */
 607         }
 608         p = ctime(&pers->loginat);
 609         if (pers->loggedin) {
 610                 stimeprint(&pers->idletime);
 611                 (void) printf(" %3.3s %-5.5s ", p, p + 11);
 612         } else if (pers->loginat == 0) {
 613                 (void) printf(" < .  .  .  . >");
 614         } else if (tloc - pers->loginat >= 180 * 24 * 60 * 60) {
 615                 (void) printf(" <%-6.6s, %-4.4s>", p + 4, p + 20);
 616         } else {
 617                 (void) printf(" <%-12.12s>", p + 4);
 618         }
 619         if (*pers->host) {
 620                 (void) printf(" %-20.20s", pers->host);
 621         } else {
 622                 if (pers->ttyloc != NULL)
 623                         (void) printf(" %-20.20s", pers->ttyloc);
 624         }
 625         (void) putchar('\n');
 626 }
 627 
 628 
 629 /*
 630  * print out a person in long format giving all possible information.
 631  * directory and shell are inhibited if unbrief is clear.
 632  */
 633 void
 634 personprint(struct person *pers)
 635 {
 636         if (pers->pwd == NULL) {
 637                 (void) printf("Login name: %-10s\t\t\tIn real life: ???\n",
 638                     pers->name);
 639                 return;
 640         }
 641         (void) printf("Login name: %-10s", pers->pwd->pw_name);
 642         if (pers->loggedin && !pers->writable) {
 643                 (void) printf(" (messages off)  ");
 644         } else {
 645                 (void) printf("                 ");
 646         }
 647         if (pers->realname != NULL) {
 648                 (void) printf("In real life: %s", pers->realname);
 649         }
 650         if (unbrief) {
 651                 (void) printf("\nDirectory: %-25s", pers->pwd->pw_dir);
 652                 if (*pers->pwd->pw_shell)
 653                         (void) printf("\tShell: %-s", pers->pwd->pw_shell);
 654         }
 655         if (pers->loggedin) {
 656                 char *ep = ctime(&pers->loginat);
 657                 if (*pers->host) {
 658                         (void) printf("\nOn since %15.15s on %s from %s",
 659                             &ep[4], pers->tty, pers->host);
 660                         ltimeprint("\n", &pers->idletime, " Idle Time");
 661                 } else {
 662                         (void) printf("\nOn since %15.15s on %-12s",
 663                             &ep[4], pers->tty);
 664                         ltimeprint("\n", &pers->idletime, " Idle Time");
 665                 }
 666         } else if (pers->loginat == 0) {
 667                 (void) printf("\nNever logged in.");
 668         } else if (tloc - pers->loginat > 180 * 24 * 60 * 60) {
 669                 char *ep = ctime(&pers->loginat);
 670                 (void) printf("\nLast login %10.10s, %4.4s on %s",
 671                     ep, ep+20, pers->tty);
 672                 if (*pers->host) {
 673                         (void) printf(" from %s", pers->host);
 674                 }
 675         } else {
 676                 char *ep = ctime(&pers->loginat);
 677                 (void) printf("\nLast login %16.16s on %s", ep, pers->tty);
 678                 if (*pers->host) {
 679                         (void) printf(" from %s", pers->host);
 680                 }
 681         }
 682         (void) putchar('\n');
 683 }
 684 
 685 
 686 /*
 687  * decode the information in the gecos field of /etc/passwd
 688  */
 689 void
 690 decode(struct person *pers)
 691 {
 692         char buffer[256];
 693         char *bp, *gp, *lp;
 694 
 695         pers->realname = NULL;
 696         if (pers->pwd == NULL)
 697                 return;
 698         gp = pers->pwd->pw_gecos;
 699         bp = buffer;
 700 
 701         if (gecos_ignore_c != '\0' &&
 702             *gp == gecos_ignore_c) {
 703                 gp++;
 704         }
 705         while (*gp != '\0' &&
 706             *gp != gecos_sep_c) {                       /* name */
 707                 if (*gp == gecos_samename) {
 708                         lp = pers->pwd->pw_name;
 709                         if (islower(*lp))
 710                                 *bp++ = toupper(*lp++);
 711                         while (*bp++ = *lp++)
 712                                 ;
 713                         bp--;
 714                         gp++;
 715                 } else {
 716                         *bp++ = *gp++;
 717                 }
 718         }
 719         *bp++ = 0;
 720         if (bp > (buffer + 1))
 721                 pers->realname = strdup(buffer);
 722         if (pers->loggedin)
 723                 findidle(pers);
 724         else
 725                 findwhen(pers);
 726 }
 727 
 728 /*
 729  * find the last log in of a user by checking the LASTLOG file.
 730  * the entry is indexed by the uid, so this can only be done if
 731  * the uid is known (which it isn't in quick mode)
 732  */
 733 void
 734 fwopen(void)
 735 {
 736         if ((lf = fopen(LASTLOG, "r")) == NULL)
 737                 (void) fprintf(stderr, "finger: %s open error\n", LASTLOG);
 738 }
 739 
 740 void
 741 findwhen(struct person *pers)
 742 {
 743         struct lastlog ll;
 744 
 745         if (lf != NULL) {
 746                 if (fseeko(lf, (off_t)pers->pwd->pw_uid * (off_t)sizeof (ll),
 747                     SEEK_SET) == 0) {
 748                         if (fread((char *)&ll, sizeof (ll), 1, lf) == 1) {
 749                                 int l_max, h_max;
 750 
 751                                 l_max = min(LMAX, sizeof (ll.ll_line));
 752                                 h_max = min(HMAX, sizeof (ll.ll_host));
 753 
 754                                 bcopy(ll.ll_line, pers->tty, l_max);
 755                                 pers->tty[l_max] = '\0';
 756                                 bcopy(ll.ll_host, pers->host, h_max);
 757                                 pers->host[h_max] = '\0';
 758                                 pers->loginat = ll.ll_time;
 759                         } else {
 760                                 if (ferror(lf))
 761                                         (void) fprintf(stderr,
 762                                             "finger: %s read error\n", LASTLOG);
 763                                 pers->tty[0] = 0;
 764                                 pers->host[0] = 0;
 765                                 pers->loginat = 0L;
 766                         }
 767                 } else {
 768                         (void) fprintf(stderr, "finger: %s fseeko error\n",
 769                             LASTLOG);
 770                 }
 771         } else {
 772                 pers->tty[0] = 0;
 773                 pers->host[0] = 0;
 774                 pers->loginat = 0L;
 775         }
 776 }
 777 
 778 void
 779 fwclose(void)
 780 {
 781         if (lf != NULL)
 782                 (void) fclose(lf);
 783 }
 784 
 785 /*
 786  * find the idle time of a user by doing a stat on /dev/tty??,
 787  * where tty?? has been gotten from UTMPX_FILE, supposedly.
 788  */
 789 void
 790 findidle(struct person *pers)
 791 {
 792         struct stat ttystatus;
 793         struct stat inputdevstatus;
 794 #define TTYLEN (sizeof ("/dev/") - 1)
 795         static char buffer[TTYLEN + LMAX + 1] = "/dev/";
 796         time_t t;
 797         time_t lastinputtime;
 798 
 799         (void) strcpy(buffer + TTYLEN, pers->tty);
 800         buffer[TTYLEN+LMAX] = 0;
 801         if (stat(buffer, &ttystatus) < 0) {
 802                 (void) fprintf(stderr, "finger: Can't stat %s\n", buffer);
 803                 exit(4);
 804         }
 805         lastinputtime = ttystatus.st_atime;
 806         if (strcmp(pers->tty, "console") == 0) {
 807                 /*
 808                  * On the console, the user may be running a window system; if
 809                  * so, their activity will show up in the last-access times of
 810                  * "/dev/kbd" and "/dev/mouse", so take the minimum of the idle
 811                  * times on those two devices and "/dev/console" and treat that
 812                  * as the idle time.
 813                  */
 814                 if (stat("/dev/kbd", &inputdevstatus) == 0) {
 815                         if (lastinputtime < inputdevstatus.st_atime)
 816                                 lastinputtime = inputdevstatus.st_atime;
 817                 }
 818                 if (stat("/dev/mouse", &inputdevstatus) == 0) {
 819                         if (lastinputtime < inputdevstatus.st_atime)
 820                                 lastinputtime = inputdevstatus.st_atime;
 821                 }
 822         }
 823         t = time(NULL);
 824         if (t < lastinputtime)
 825                 pers->idletime = (time_t)0;
 826         else
 827                 pers->idletime = t - lastinputtime;
 828         pers->writable = (ttystatus.st_mode & TALKABLE) == TALKABLE;
 829 }
 830 
 831 /*
 832  * print idle time in short format; this program always prints 4 characters;
 833  * if the idle time is zero, it prints 4 blanks.
 834  */
 835 void
 836 stimeprint(time_t *dt)
 837 {
 838         struct tm *delta;
 839 
 840         delta = gmtime(dt);
 841         if (delta->tm_yday == 0)
 842                 if (delta->tm_hour == 0)
 843                         if (delta->tm_min == 0)
 844                                 (void) printf("    ");
 845                         else
 846                                 (void) printf("  %2d", delta->tm_min);
 847                 else
 848                         if (delta->tm_hour >= 10)
 849                                 (void) printf("%3d:", delta->tm_hour);
 850                         else
 851                                 (void) printf("%1d:%02d",
 852                                     delta->tm_hour, delta->tm_min);
 853         else
 854                 (void) printf("%3dd", delta->tm_yday);
 855 }
 856 
 857 /*
 858  * print idle time in long format with care being taken not to pluralize
 859  * 1 minutes or 1 hours or 1 days.
 860  * print "prefix" first.
 861  */
 862 void
 863 ltimeprint(char *before, time_t *dt, char *after)
 864 {
 865         struct tm *delta;
 866 
 867         delta = gmtime(dt);
 868         if (delta->tm_yday == 0 && delta->tm_hour == 0 && delta->tm_min == 0 &&
 869             delta->tm_sec <= 10)
 870                 return;
 871         (void) printf("%s", before);
 872         if (delta->tm_yday >= 10)
 873                 (void) printf("%d days", delta->tm_yday);
 874         else if (delta->tm_yday > 0)
 875                 (void) printf("%d day%s %d hour%s",
 876                     delta->tm_yday, delta->tm_yday == 1 ? "" : "s",
 877                     delta->tm_hour, delta->tm_hour == 1 ? "" : "s");
 878         else
 879                 if (delta->tm_hour >= 10)
 880                         (void) printf("%d hours", delta->tm_hour);
 881                 else if (delta->tm_hour > 0)
 882                         (void) printf("%d hour%s %d minute%s",
 883                             delta->tm_hour, delta->tm_hour == 1 ? "" : "s",
 884                             delta->tm_min, delta->tm_min == 1 ? "" : "s");
 885                 else
 886                         if (delta->tm_min >= 10)
 887                                 (void) printf("%2d minutes", delta->tm_min);
 888                         else if (delta->tm_min == 0)
 889                                 (void) printf("%2d seconds", delta->tm_sec);
 890                         else
 891                                 (void) printf("%d minute%s %d second%s",
 892                                     delta->tm_min,
 893                                     delta->tm_min == 1 ? "" : "s",
 894                                     delta->tm_sec,
 895                                     delta->tm_sec == 1 ? "" : "s");
 896         (void) printf("%s", after);
 897 }
 898 
 899 /*
 900  * The grammar of the pw_gecos field is sufficiently complex that the
 901  * best way to parse it is by using an explicit finite-state machine,
 902  * in which a table defines the rules of interpretation.
 903  *
 904  * Some special rules are necessary to handle the fact that names
 905  * may contain certain punctuation characters.  At this writing,
 906  * the possible punctuation characters are '.', '-', and '_'.
 907  *
 908  * Other rules are needed to account for characters that require special
 909  * processing when they appear in the pw_gecos field.  At present, there
 910  * are three such characters, with these default values and effects:
 911  *
 912  *    gecos_ignore_c   '*'    This character is ignored.
 913  *    gecos_sep_c      ','    Delimits displayed and nondisplayed contents.
 914  *    gecos_samename   '&'    Copies the login name into the output.
 915  *
 916  * As the program examines each successive character in the returned
 917  * pw_gecos value, it fetches (from the table) the FSM rule applicable
 918  * for that character in the current machine state, and thus determines
 919  * the next state.
 920  *
 921  * The possible states are:
 922  *    S0 start
 923  *    S1 in a word
 924  *    S2 not in a word
 925  *    S3 copy login name into output
 926  *    S4 end of GECOS field
 927  *
 928  * Here follows a depiction of the state transitions.
 929  *
 930  *
 931  *              gecos_ignore_c OR isspace OR any other character
 932  *                  +--+
 933  *                  |  |
 934  *                  |  V
 935  *                 +-----+
 936  *    NULL OR      | S0  |  isalpha OR isdigit
 937  * +---------------|start|------------------------+
 938  * |  gecos_sep_c  +-----+                        |     isalpha OR isdigit
 939  * |                |  |                          |   +---------------------+
 940  * |                |  |                          |   | OR '.' '-' '_'      |
 941  * |                |  |isspace                   |   |                     |
 942  * |                |  +-------+                  V   V                     |
 943  * |                |          |              +-----------+                 |
 944  * |                |          |              |    S1     |<--+             |
 945  * |                |          |              | in a word |   | isalpha OR  |
 946  * |                |          |              +-----------+   | isdigit OR  |
 947  * |                |          |               |  |  |  |     | '.' '-' '_' |
 948  * |                |    +----- ---------------+  |  |  +-----+             |
 949  * |                |    |     |                  |  |                      |
 950  * |                |    |     |   gecos_ignore_c |  |                      |
 951  * |                |    |     |   isspace        |  |                      |
 952  * |                |    |     |   ispunct/other  |  |                      |
 953  * |                |    |     |   any other char |  |                      |
 954  * |                |    |     |  +---------------+  |                      |
 955  * |                |    |     |  |                  |NULL OR gecos_sep_c   |
 956  * |                |    |     |  |                  +------------------+   |
 957  * |  gecos_samename|    |     V  V                                     |   |
 958  * |  +-------------+    |    +---------------+                         |   |
 959  * |  |                  |    |       S2      | isspace OR '.' '-' '_'  |   |
 960  * |  |  gecos_samename  |    | not in a word |<---------------------+  |   |
 961  * |  |  +---------------+    +---------------+ OR gecos_ignore_c    |  |   |
 962  * |  |  |                        |    ^  |  |  OR ispunct OR other  |  |   |
 963  * |  |  |                        |    |  |  |                       |  |   |
 964  * |  |  |  gecos_samename        |    |  |  +-----------------------+  |   |
 965  * |  |  |  +---------------------+    |  |                             |   |
 966  * |  |  |  |                          |  |                             |   |
 967  * |  |  |  |            gecos_ignore_c|  | NULL OR gecos_sep_c         |   |
 968  * |  |  |  |            gecos_samename|  +-----------------------+     |   |
 969  * |  |  |  |            ispunct/other |                          |     |   |
 970  * |  V  V  V            isspace       |                          |     |   |
 971  * | +-----------------+ any other char|                          |     |   |
 972  * | |      S3         |---------------+  isalpha OR isdigit OR   |     |   |
 973  * | |insert login name|------------------------------------------ ----- ---+
 974  * | +-----------------+                  '.' '-' '_'             |     |
 975  * |                |    NULL OR gecos_sep_c                      |     |
 976  * |                +------------------------------------------+  |     |
 977  * |                                                           |  |     |
 978  * |                                                           V  V     V
 979  * |                                                         +------------+
 980  * | NULL OR gecos_sep_c                                     |     S4     |
 981  * +-------------------------------------------------------->|end of gecos|<--+
 982  *                                                           +------------+   |
 983  *                                                                      | all |
 984  *                                                                      +-----+
 985  *
 986  *
 987  *  The transitions from the above diagram are summarized in
 988  *  the following table of target states, which is implemented
 989  *  in code as the gecos_fsm array.
 990  *
 991  * Input:
 992  *        +--gecos_ignore_c
 993  *        |    +--gecos_sep_c
 994  *        |    |    +--gecos_samename
 995  *        |    |    |    +--isalpha
 996  *        |    |    |    |    +--isdigit
 997  *        |    |    |    |    |      +--isspace
 998  *        |    |    |    |    |      |    +--punctuation possible in name
 999  *        |    |    |    |    |      |    |    +--other punctuation
1000  *        |    |    |    |    |      |    |    |    +--NULL character
1001  *        |    |    |    |    |      |    |    |    |    +--any other character
1002  *        |    |    |    |    |      |    |    |    |    |
1003  *        V    V    V    V    V      V    V    V    V    V
1004  * From: ---------------------------------------------------
1005  * S0   | S0 | S4 | S3 | S1 | S1 |   S0 | S1 | S2 | S4 | S0 |
1006  * S1   | S2 | S4 | S3 | S1 | S1 |   S2 | S1 | S2 | S4 | S2 |
1007  * S2   | S2 | S4 | S3 | S1 | S1 |   S2 | S2 | S2 | S4 | S2 |
1008  * S3   | S2 | S4 | S2 | S1 | S1 |   S2 | S1 | S2 | S4 | S2 |
1009  * S4   | S4 | S4 | S4 | S4 | S4 |   S4 | S4 | S4 | S4 | S4 |
1010  *
1011  */
1012 
1013 /*
1014  * Data types and structures for scanning the pw_gecos field.
1015  */
1016 typedef enum gecos_state {
1017         S0,             /* start */
1018         S1,             /* in a word */
1019         S2,             /* not in a word */
1020         S3,             /* copy login */
1021         S4              /* end of gecos */
1022 } gecos_state_t;
1023 
1024 #define GFSM_ROWS 5
1025 #define GFSM_COLS 10
1026 
1027 gecos_state_t gecos_fsm[GFSM_ROWS][GFSM_COLS] = {
1028         {S0, S4, S3, S1, S1,    S0, S1, S2, S4, S0},    /* S0 */
1029         {S2, S4, S3, S1, S1,    S2, S1, S2, S4, S2},    /* S1 */
1030         {S2, S4, S3, S1, S1,    S2, S2, S2, S4, S2},    /* S2 */
1031         {S2, S4, S2, S1, S1,    S2, S1, S2, S4, S2},    /* S3 */
1032         {S4, S4, S4, S4, S4,    S4, S4, S4, S4, S4}     /* S4 */
1033 };
1034 
1035 /*
1036  * Scan the pw_gecos field according to defined state table;
1037  * return the next state according the the rules.
1038  */
1039 gecos_state_t
1040 gecos_scan_state(gecos_state_t instate, char ch)
1041 {
1042         if (ch == gecos_ignore_c) {
1043                 return (gecos_fsm[instate][0]);
1044         } else if (ch == gecos_sep_c) {
1045                 return (gecos_fsm[instate][1]);
1046         } else if (ch == gecos_samename) {
1047                 return (gecos_fsm[instate][2]);
1048         } else if (isalpha(ch)) {
1049                 return (gecos_fsm[instate][3]);
1050         } else if (isdigit(ch)) {
1051                 return (gecos_fsm[instate][4]);
1052         } else if (isspace(ch)) {
1053                 return (gecos_fsm[instate][5]);
1054         } else if (ch == '.' || ch == '-' || ch == '_') {
1055                 return (gecos_fsm[instate][6]);
1056         } else if (ispunct(ch)) {
1057                 return (gecos_fsm[instate][7]);
1058         } else if (ch == '\0') {
1059                 return (gecos_fsm[instate][8]);
1060         }
1061         return (gecos_fsm[instate][9]);
1062 }
1063 
1064 
1065 /*
1066  * Compare the given argument, which is taken to be a username, with
1067  * the login name and with strings in the the pw_gecos field.
1068  */
1069 int
1070 matchcmp(char *gname, char *login, char *given)
1071 {
1072         char    buffer[100];
1073         char    *bp, *lp, *gp;
1074 
1075         gecos_state_t kstate = S0;
1076         gecos_state_t kstate_next = S0;
1077 
1078         if (*gname == '\0' && *given == '\0')
1079                 return (1);
1080 
1081         bp = buffer;
1082         gp = gname;
1083 
1084         do {
1085                 kstate_next = gecos_scan_state(kstate, *gp);
1086 
1087                 switch (kstate_next) {
1088 
1089                 case S0:
1090                         gp++;
1091                         break;
1092                 case S1:
1093                         if (bp < buffer + sizeof (buffer)) {
1094                                 *bp++ = *gp++;
1095                         }
1096                         break;
1097                 case S2:
1098                         if (kstate == S1 || kstate == S3) {
1099                                 *bp++ = ' ';
1100                         }
1101                         gp++;
1102                         break;
1103                 case S3:
1104                         lp = login;
1105                         do {
1106                                 *bp++ = *lp++;
1107                         } while (*bp != '\0' && bp < buffer + sizeof (buffer));
1108                         bp--;
1109                         break;
1110                 case S4:
1111                         *bp++ = '\0';
1112                         break;
1113                 default:
1114                         *bp++ = '\0';
1115                         break;
1116                 }
1117                 kstate = kstate_next;
1118 
1119         } while ((bp < buffer + sizeof (buffer)) && kstate != S4);
1120 
1121         gp = strtok(buffer, " ");
1122 
1123         while (gp != NULL) {
1124                 if (namecmp(gp, given) > 0) {
1125                         return (1);
1126                 }
1127                 gp = strtok(NULL, " ");
1128         }
1129         return (0);
1130 }
1131 
1132 /*
1133  * Perform the character-by-character comparison.
1134  * It is intended that "finger foo" should match "foo2", but an argument
1135  * consisting entirely of digits should not be matched too broadly.
1136  * Also, we do not want "finger foo123" to match "Mr. Foo" in the gecos.
1137  */
1138 int
1139 namecmp(char *name1, char *name2)
1140 {
1141         char c1, c2;
1142         boolean_t alphaseen = B_FALSE;
1143         boolean_t digitseen = B_FALSE;
1144 
1145         for (;;) {
1146                 c1 = *name1++;
1147                 if (isalpha(c1))
1148                         alphaseen = B_TRUE;
1149                 if (isdigit(c1))
1150                         digitseen = B_TRUE;
1151                 if (isupper(c1))
1152                         c1 = tolower(c1);
1153 
1154                 c2 = *name2++;
1155                 if (isupper(c2))
1156                         c2 = tolower(c2);
1157 
1158                 if (c1 != c2)
1159                         break;
1160                 if (c1 == '\0')
1161                         return (1);
1162         }
1163         if (!c1) {
1164                 for (name2--; isdigit(*name2); name2++)
1165                         ;
1166                 if (*name2 == '\0' && digitseen) {
1167                         return (1);
1168                 }
1169         } else if (!c2) {
1170                 for (name1--; isdigit(*name1); name1++)
1171                         ;
1172                 if (*name1 == '\0' && alphaseen) {
1173                         return (1);
1174                 }
1175         }
1176         return (0);
1177 }
1178 
1179 
1180 int
1181 netfinger(char *name)
1182 {
1183         char *host;
1184         struct hostent *hp;
1185         struct sockaddr_in6 sin6;
1186         struct in6_addr ipv6addr;
1187         struct in_addr ipv4addr;
1188         int s;
1189         FILE *f;
1190         int c;
1191         int lastc;
1192         char abuf[INET6_ADDRSTRLEN];
1193         int error_num;
1194 
1195         if (name == NULL)
1196                 return (0);
1197         host = strrchr(name, '@');
1198         if (host == NULL)
1199                 return (0);
1200         *host++ = 0;
1201 
1202         if ((hp = getipnodebyname(host, AF_INET6, AI_ALL | AI_ADDRCONFIG |
1203             AI_V4MAPPED, &error_num)) == NULL) {
1204                 if (error_num == TRY_AGAIN) {
1205                         (void) fprintf(stderr,
1206                             "unknown host: %s (try again later)\n", host);
1207                 } else {
1208                         (void) fprintf(stderr, "unknown host: %s\n", host);
1209                 }
1210                 return (1);
1211         }
1212 
1213         /*
1214          * If hp->h_name is a IPv4-mapped IPv6 literal, we'll convert it to
1215          * IPv4 literal address.
1216          */
1217         if ((inet_pton(AF_INET6, hp->h_name, &ipv6addr) > 0) &&
1218             IN6_IS_ADDR_V4MAPPED(&ipv6addr)) {
1219                 IN6_V4MAPPED_TO_INADDR(&ipv6addr, &ipv4addr);
1220                 (void) printf("[%s] ", inet_ntop(AF_INET, &ipv4addr, abuf,
1221                     sizeof (abuf)));
1222         } else {
1223                 (void) printf("[%s] ", hp->h_name);
1224         }
1225         bzero(&sin6, sizeof (sin6));
1226         sin6.sin6_family = hp->h_addrtype;
1227         bcopy(hp->h_addr_list[0], (char *)&sin6.sin6_addr, hp->h_length);
1228         sin6.sin6_port = htons(IPPORT_FINGER);
1229         s = socket(sin6.sin6_family, SOCK_STREAM, 0);
1230         if (s < 0) {
1231                 (void) fflush(stdout);
1232                 perror("socket");
1233                 freehostent(hp);
1234                 return (1);
1235         }
1236         while (connect(s, (struct sockaddr *)&sin6, sizeof (sin6)) < 0) {
1237 
1238                 if (hp && hp->h_addr_list[1]) {
1239 
1240                         hp->h_addr_list++;
1241                         bcopy(hp->h_addr_list[0],
1242                             (caddr_t)&sin6.sin6_addr, hp->h_length);
1243                         (void) close(s);
1244                         s = socket(sin6.sin6_family, SOCK_STREAM, 0);
1245                         if (s < 0) {
1246                                 (void) fflush(stdout);
1247                                 perror("socket");
1248                                 freehostent(hp);
1249                                 return (0);
1250                         }
1251                         continue;
1252                 }
1253 
1254                 (void) fflush(stdout);
1255                 perror("connect");
1256                 (void) close(s);
1257                 freehostent(hp);
1258                 return (1);
1259         }
1260         freehostent(hp);
1261         hp = NULL;
1262 
1263         (void) printf("\n");
1264         if (large)
1265                 (void) write(s, "/W ", 3);
1266         (void) write(s, name, strlen(name));
1267         (void) write(s, "\r\n", 2);
1268         f = fdopen(s, "r");
1269 
1270         lastc = '\n';
1271         while ((c = getc(f)) != EOF) {
1272                 /* map CRLF -> newline */
1273                 if ((lastc == '\r') && (c != '\n'))
1274                         /* print out saved CR */
1275                         (void) putchar('\r');
1276                 lastc = c;
1277                 if (c == '\r')
1278                         continue;
1279                 (void) putchar(c);
1280         }
1281 
1282         if (lastc != '\n')
1283                 (void) putchar('\n');
1284         (void) fclose(f);
1285         return (1);
1286 }
1287 
1288 /*
1289  *      AnyMail - takes a username (string pointer thereto), and
1290  *      prints on standard output whether there is any unread mail,
1291  *      and if so, how old it is.       (JCM@Shasta 15 March 80)
1292  */
1293 void
1294 AnyMail(char *name)
1295 {
1296         struct stat buf;                /* space for file status buffer */
1297         char *mbxdir = MAILDIR;         /* string with path preamble */
1298         char *mbxpath;                  /* space for entire pathname */
1299 
1300         char *timestr;
1301 
1302         mbxpath = malloc(strlen(name) + strlen(MAILDIR) + 1);
1303         if (mbxpath == NULL)
1304                 return;
1305 
1306         (void) strcpy(mbxpath, mbxdir); /* copy preamble into path name */
1307         (void) strcat(mbxpath, name);   /* concatenate user name to path */
1308 
1309         if (stat(mbxpath, &buf) == -1 || buf.st_size == 0) {
1310                 /* Mailbox is empty or nonexistent */
1311                 (void) printf("No unread mail\n");
1312         } else {
1313                 if (buf.st_mtime < buf.st_atime) {
1314                         /*
1315                          * No new mail since the last time the user read it.
1316                          */
1317                         (void) printf("Mail last read ");
1318                         (void) printf("%s", ctime(&buf.st_atime));
1319                 } else if (buf.st_mtime > buf.st_atime) {
1320                         /*
1321                          * New mail has definitely arrived since the last time
1322                          * mail was read.  mtime is the time the most recent
1323                          * message arrived; atime is either the time the oldest
1324                          * unread message arrived, or the last time the mail
1325                          * was read.
1326                          */
1327                         (void) printf("New mail received ");
1328                         timestr = ctime(&buf.st_mtime); /* time last modified */
1329                         timestr[24] = '\0';     /* suppress newline (ugh) */
1330                         (void) printf("%s", timestr);
1331                         (void) printf(";\n  unread since ");
1332                         (void) printf("%s", ctime(&buf.st_atime));
1333                 } else {
1334                         /*
1335                          * There is something in mailbox, but we can't really
1336                          * be sure whether it is mail held there by the user
1337                          * or a (single) new message that was placed in a newly
1338                          * recreated mailbox, so punt and call it "unread mail."
1339                          */
1340                         (void) printf("Unread mail since ");
1341                         (void) printf("%s", ctime(&buf.st_mtime));
1342                 }
1343         }
1344         free(mbxpath);
1345 }
1346 
1347 /*
1348  * return true iff we've already printed project/plan for this uid;
1349  * if not, enter this uid into table (so this function has a side-effect.)
1350  */
1351 #define PPMAX   4096            /* assume no more than 4096 logged-in users */
1352 uid_t   PlanPrinted[PPMAX+1];
1353 int     PPIndex = 0;            /* index of next unused table entry */
1354 
1355 int
1356 AlreadyPrinted(uid_t uid)
1357 {
1358         int i = 0;
1359 
1360         while (i++ < PPIndex) {
1361                 if (PlanPrinted[i] == uid)
1362                 return (1);
1363         }
1364         if (i < PPMAX) {
1365                 PlanPrinted[i] = uid;
1366                 PPIndex++;
1367         }
1368         return (0);
1369 }
1370 
1371 #define FIFOREADTIMEOUT (60)    /* read timeout on select */
1372 /* BEGIN CSTYLED */
1373 #define PRINT_CHAR(c)                                           \
1374         (                                                       \
1375                 ((termpass & TS_HIGH) && ((int)c) > 126) \
1376                 ||                                              \
1377                 (isascii((int)c) &&                             \
1378                          (isprint((int)c) || isspace((int)c))   \
1379                 )                                               \
1380                 ||                                              \
1381                 ((termpass & TS_LOW) && ((int)c) < 32)           \
1382         )
1383 /* END CSTYLED */
1384 
1385 
1386 void
1387 catfile(char *s, mode_t mode, int trunc_at_nl)
1388 {
1389         if (S_ISFIFO(mode)) {
1390                 int fd;
1391 
1392                 fd = open(s, O_RDONLY | O_NONBLOCK);
1393                 if (fd != -1) {
1394                         fd_set readfds, exceptfds;
1395                         struct timeval tv;
1396 
1397                         FD_ZERO(&readfds);
1398                         FD_ZERO(&exceptfds);
1399                         FD_SET(fd, &readfds);
1400                         FD_SET(fd, &exceptfds);
1401 
1402                         timerclear(&tv);
1403                         tv.tv_sec = FIFOREADTIMEOUT;
1404 
1405                         (void) fflush(stdout);
1406                         while (select(fd + 1, &readfds, (fd_set *) 0,
1407                             &exceptfds, &tv) != -1) {
1408                                 unsigned char buf[BUFSIZ];
1409                                 int nread;
1410 
1411                                 nread = read(fd, buf, sizeof (buf));
1412                                 if (nread > 0) {
1413                                         unsigned char *p;
1414 
1415                                         FD_SET(fd, &readfds);
1416                                         FD_SET(fd, &exceptfds);
1417                                         for (p = buf; p < buf + nread; p++) {
1418                                                 if (trunc_at_nl && *p == '\n')
1419                                                         goto out;
1420                                                 if (PRINT_CHAR(*p))
1421                                                         (void) putchar((int)*p);
1422                                                 else if (isascii(*p))
1423                                                         (void) fputs(unctrl(*p),
1424                                                             stdout);
1425                                         }
1426                                 } else
1427                                         break;
1428                         }
1429 out:
1430                         (void) close(fd);
1431                 }
1432         } else {
1433                 int c;
1434                 FILE *fp;
1435 
1436                 fp = fopen(s, "r");
1437                 if (fp) {
1438                         while ((c = getc(fp)) != EOF) {
1439                                 if (trunc_at_nl && c == '\n')
1440                                         break;
1441                                 if (PRINT_CHAR(c))
1442                                         (void) putchar((int)c);
1443                                 else
1444                                         if (isascii(c))
1445                                                 (void) fputs(unctrl(c), stdout);
1446                         }
1447                         (void) fclose(fp);
1448                 }
1449         }
1450 }
1451 
1452 
1453 void
1454 initscreening(void)
1455 {
1456         char *options, *value;
1457 
1458         if (defopen(defaultfile) == 0) {
1459                 char    *cp;
1460                 int     flags;
1461 
1462                 /*
1463                  * ignore case
1464                  */
1465                 flags = defcntl(DC_GETFLAGS, 0);
1466                 TURNOFF(flags, DC_CASE);
1467                 (void) defcntl(DC_SETFLAGS, flags);
1468 
1469                 if (cp = defread(passvar)) {
1470                         options = cp;
1471                         while (*options != '\0')
1472                                 switch (getsubopt(&options, termopts, &value)) {
1473                                 case TERM_LOW:
1474                                         termpass |= TS_LOW;
1475                                         break;
1476                                 case TERM_HIGH:
1477                                         termpass |= TS_HIGH;
1478                                         break;
1479                                 }
1480                 }
1481                 (void) defopen(NULL);   /* close default file */
1482         }
1483 }
1484 
1485 int
1486 person_compare(const void *p1, const void *p2)
1487 {
1488         const struct person *pp1 = *(struct person **)p1;
1489         const struct person *pp2 = *(struct person **)p2;
1490         int r;
1491 
1492         /*
1493          * Sort by username.
1494          */
1495         r = strcmp(pp1->name, pp2->name);
1496 
1497         if (r != 0)
1498                 return (r);
1499 
1500         /*
1501          * If usernames are the same, sort by idle time.
1502          */
1503         r = pp1->idletime - pp2->idletime;
1504 
1505         return (r);
1506 }
1507 
1508 void
1509 sort_by_username()
1510 {
1511         struct person **sortable, *loop;
1512         size_t i;
1513 
1514         sortable = malloc(sizeof (sortable[0]) * nperson);
1515 
1516         if (sortable == NULL)
1517                 return;
1518 
1519         for (i = 0, loop = person1; i < nperson; i++) {
1520                 struct person *next = loop->link;
1521 
1522                 sortable[i] = loop;
1523                 loop->link = NULL;
1524 
1525                 loop = next;
1526         }
1527 
1528         qsort(sortable, nperson, sizeof (sortable[0]), person_compare);
1529 
1530         for (i = 1; i < nperson; i++)
1531                 sortable[i-1]->link = sortable[i];
1532         person1 = sortable[0];
1533 
1534         free(sortable);
1535 }