xref: /titanic_44/usr/src/cmd/cmd-inet/usr.bin/finger.c (revision 0dc2366f7b9f9f36e10909b1e95edbf2a261c2ac)
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