Home | History | Annotate | Download | only in usr.bin
      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 }
   1536