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