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 2006 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 (*