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  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 /*
26  * Copyright 2014 Nexenta Systems, Inc.  All rights reserved.
27  */
28 
29 /*
30  * Common code and structures used by name-service-switch "files" backends.
31  */
32 
33 /*
34  * An implementation that used mmap() sensibly would be a wonderful thing,
35  *   but this here is just yer standard fgets() thang.
36  */
37 
38 #include "files_common.h"
39 #include <stdio.h>
40 #include <stdlib.h>
41 #include <string.h>
42 #include <ctype.h>
43 #include <fcntl.h>
44 #include <poll.h>
45 #include <unistd.h>
46 #include <sys/stat.h>
47 #include <sys/mman.h>
48 
49 /*ARGSUSED*/
50 nss_status_t
51 _nss_files_setent(be, dummy)
52 	files_backend_ptr_t	be;
53 	void			*dummy;
54 {
55 	if (be->f == 0) {
56 		if (be->filename == 0) {
57 			/* Backend isn't initialized properly? */
58 			return (NSS_UNAVAIL);
59 		}
60 		if ((be->f = fopen(be->filename, "rF")) == 0) {
61 			return (NSS_UNAVAIL);
62 		}
63 	} else {
64 		rewind(be->f);
65 	}
66 	return (NSS_SUCCESS);
67 }
68 
69 /*ARGSUSED*/
70 nss_status_t
71 _nss_files_endent(be, dummy)
72 	files_backend_ptr_t	be;
73 	void			*dummy;
74 {
75 	if (be->f != 0) {
76 		(void) fclose(be->f);
77 		be->f = 0;
78 	}
79 	if (be->buf != 0) {
80 		free(be->buf);
81 		be->buf = 0;
82 	}
83 	return (NSS_SUCCESS);
84 }
85 
86 /*
87  * This routine reads a line, including the processing of continuation
88  * characters.  It always leaves (or inserts) \n\0 at the end of the line.
89  * It returns the length of the line read, excluding the \n\0.  Who's idea
90  * was this?
91  * Returns -1 on EOF.
92  *
93  * Note that since each concurrent call to _nss_files_read_line has
94  * it's own FILE pointer, we can use getc_unlocked w/o difficulties,
95  * a substantial performance win.
96  */
97 int
98 _nss_files_read_line(f, buffer, buflen)
99 	FILE			*f;
100 	char			*buffer;
101 	int			buflen;
102 {
103 	int			linelen;	/* 1st unused slot in buffer */
104 	int			c;
105 
106 	/*CONSTCOND*/
107 	while (1) {
108 		linelen = 0;
109 		while (linelen < buflen - 1) {	/* "- 1" saves room for \n\0 */
110 			switch (c = getc_unlocked(f)) {
111 			case EOF:
112 				if (linelen == 0 ||
113 				    buffer[linelen - 1] == '\\') {
114 					return (-1);
115 				} else {
116 					buffer[linelen    ] = '\n';
117 					buffer[linelen + 1] = '\0';
118 					return (linelen);
119 				}
120 			case '\n':
121 				if (linelen > 0 &&
122 				    buffer[linelen - 1] == '\\') {
123 					--linelen;  /* remove the '\\' */
124 				} else {
125 					buffer[linelen    ] = '\n';
126 					buffer[linelen + 1] = '\0';
127 					return (linelen);
128 				}
129 				break;
130 			default:
131 				buffer[linelen++] = c;
132 			}
133 		}
134 		/* Buffer overflow -- eat rest of line and loop again */
135 		/* ===> Should syslog() */
136 		do {
137 			c = getc_unlocked(f);
138 			if (c == EOF) {
139 				return (-1);
140 			}
141 		} while (c != '\n');
142 	}
143 	/*NOTREACHED*/
144 }
145 
146 /*
147  * used only for getgroupbymem() now.
148  */
149 nss_status_t
150 _nss_files_do_all(be, args, filter, func)
151 	files_backend_ptr_t	be;
152 	void			*args;
153 	const char		*filter;
154 	files_do_all_func_t	func;
155 {
156 	long			grlen;
157 	char			*buffer;
158 	int			buflen;
159 	nss_status_t		res;
160 
161 	if (be->buf == 0) {
162 		if ((grlen = sysconf(_SC_GETGR_R_SIZE_MAX)) > 0)
163 			be->minbuf = grlen;
164 		if ((be->buf = malloc(be->minbuf)) == 0)
165 			return (NSS_UNAVAIL);
166 	}
167 	buffer = be->buf;
168 	buflen = be->minbuf;
169 
170 	if ((res = _nss_files_setent(be, 0)) != NSS_SUCCESS) {
171 		return (res);
172 	}
173 
174 	res = NSS_NOTFOUND;
175 
176 	do {
177 		int		linelen;
178 
179 		if ((linelen = _nss_files_read_line(be->f, buffer,
180 		    buflen)) < 0) {
181 			/* End of file */
182 			break;
183 		}
184 		if (filter != 0 && strstr(buffer, filter) == 0) {
185 			/*
186 			 * Optimization:  if the entry doesn't contain the
187 			 *   filter string then it can't be the entry we want,
188 			 *   so don't bother looking more closely at it.
189 			 */
190 			continue;
191 		}
192 		res = (*func)(buffer, linelen, args);
193 
194 	} while (res == NSS_NOTFOUND);
195 
196 	(void) _nss_files_endent(be, 0);
197 	return (res);
198 }
199 
200 /*
201  * Could implement this as an iterator function on top of _nss_files_do_all(),
202  *   but the shared code is small enough that it'd be pretty silly.
203  */
204 nss_status_t
205 _nss_files_XY_all(be, args, netdb, filter, check)
206 	files_backend_ptr_t	be;
207 	nss_XbyY_args_t		*args;
208 	int			netdb;		/* whether it uses netdb */
209 						/* format or not */
210 	const char		*filter;	/* advisory, to speed up */
211 						/* string search */
212 	files_XY_check_func	check;	/* NULL means one-shot, for getXXent */
213 {
214 	char			*r;
215 	nss_status_t		res;
216 	int	parsestat;
217 	int (*func)();
218 
219 	if (filter != NULL && *filter == '\0')
220 		return (NSS_NOTFOUND);
221 	if (be->buf == 0 || (be->minbuf < args->buf.buflen)) {
222 		if (be->minbuf < args->buf.buflen) {
223 			if (be->buf == 0) {
224 				be->minbuf = args->buf.buflen;
225 			} else if (
226 			    (r = realloc(be->buf, args->buf.buflen)) != NULL) {
227 				be->buf = r;
228 				be->minbuf = args->buf.buflen;
229 			}
230 		}
231 		if (be->buf == 0 &&
232 			(be->buf = malloc(be->minbuf)) == 0)
233 				return (NSS_UNAVAIL);
234 	}
235 
236 	if (check != 0 || be->f == 0) {
237 		if ((res = _nss_files_setent(be, 0)) != NSS_SUCCESS) {
238 			return (res);
239 		}
240 	}
241 
242 	res = NSS_NOTFOUND;
243 
244 	/*CONSTCOND*/
245 	while (1) {
246 		char		*instr	= be->buf;
247 		int		linelen;
248 
249 		if ((linelen = _nss_files_read_line(be->f, instr,
250 		    be->minbuf)) < 0) {
251 			/* End of file */
252 			args->returnval = 0;
253 			args->returnlen = 0;
254 			break;
255 		}
256 		if (filter != 0 && strstr(instr, filter) == 0) {
257 			/*
258 			 * Optimization:  if the entry doesn't contain the
259 			 *   filter string then it can't be the entry we want,
260 			 *   so don't bother looking more closely at it.
261 			 */
262 			continue;
263 		}
264 		if (netdb) {
265 			char		*first;
266 			char		*last;
267 
268 			if ((last = strchr(instr, '#')) == 0) {
269 				last = instr + linelen;
270 			}
271 			*last-- = '\0';		/* Nuke '\n' or #comment */
272 
273 			/*
274 			 * Skip leading whitespace.  Normally there isn't
275 			 *   any, so it's not worth calling strspn().
276 			 */
277 			for (first = instr;  isspace(*first);  first++) {
278 				;
279 			}
280 			if (*first == '\0') {
281 				continue;
282 			}
283 			/*
284 			 * Found something non-blank on the line.  Skip back
285 			 * over any trailing whitespace;  since we know
286 			 * there's non-whitespace earlier in the line,
287 			 * checking for termination is easy.
288 			 */
289 			while (isspace(*last)) {
290 				--last;
291 			}
292 
293 			linelen = last - first + 1;
294 			if (first != instr) {
295 					instr = first;
296 			}
297 		}
298 
299 		args->returnval = 0;
300 		args->returnlen = 0;
301 
302 		if (check != NULL && (*check)(args, instr, linelen) == 0)
303 			continue;
304 
305 		parsestat = NSS_STR_PARSE_SUCCESS;
306 		if (be->filename != NULL) {
307 			/*
308 			 * Special case for passwd and group wherein we
309 			 * replace uids/gids > MAXUID by ID_NOBODY
310 			 * because files backend does not support
311 			 * ephemeral ids.
312 			 */
313 			if (strcmp(be->filename, PF_PATH) == 0)
314 				parsestat = validate_passwd_ids(instr,
315 				    &linelen, be->minbuf, 2);
316 			else if (strcmp(be->filename, GF_PATH) == 0)
317 				parsestat = validate_group_ids(instr,
318 				    &linelen, be->minbuf, 2, check);
319 		}
320 
321 		if (parsestat == NSS_STR_PARSE_SUCCESS) {
322 			func = args->str2ent;
323 			parsestat = (*func)(instr, linelen, args->buf.result,
324 			    args->buf.buffer, args->buf.buflen);
325 		}
326 
327 		if (parsestat == NSS_STR_PARSE_SUCCESS) {
328 			args->returnval = (args->buf.result != NULL)?
329 					args->buf.result : args->buf.buffer;
330 			args->returnlen = linelen;
331 			res = NSS_SUCCESS;
332 			break;
333 		} else if (parsestat == NSS_STR_PARSE_ERANGE) {
334 			args->erange = 1;
335 			break;
336 		} /* else if (parsestat == NSS_STR_PARSE_PARSE) don't care ! */
337 	}
338 
339 	/*
340 	 * stayopen is set to 0 by default in order to close the opened
341 	 * file.  Some applications may break if it is set to 1.
342 	 */
343 	if (check != 0 && !args->stayopen) {
344 		(void) _nss_files_endent(be, 0);
345 	}
346 
347 	return (res);
348 }
349 
350 /*
351  * File hashing support.  Critical for sites with large (e.g. 1000+ lines)
352  * /etc/passwd or /etc/group files.  Currently only used by getpw*() and
353  * getgr*() routines, but any files backend can use this stuff.
354  */
355 static void
356 _nss_files_hash_destroy(files_hash_t *fhp)
357 {
358 	free(fhp->fh_table);
359 	fhp->fh_table = NULL;
360 	free(fhp->fh_line);
361 	fhp->fh_line = NULL;
362 	free(fhp->fh_file_start);
363 	fhp->fh_file_start = NULL;
364 }
365 #ifdef PIC
366 /*
367  * It turns out the hashing stuff really needs to be disabled for processes
368  * other than the nscd; the consumption of swap space and memory is otherwise
369  * unacceptable when the nscd is killed w/ a large passwd file (4M) active.
370  * See 4031930 for details.
371  * So we just use this psuedo function to enable the hashing feature.  Since
372  * this function name is private, we just create a function w/ the name
373  *  __nss_use_files_hash in the nscd itself and everyone else uses the old
374  * interface.
375  * We also disable hashing for .a executables to avoid problems with large
376  * files....
377  */
378 
379 #pragma weak __nss_use_files_hash
380 
381 extern void  __nss_use_files_hash(void);
382 #endif /* pic */
383 
384 /*ARGSUSED*/
385 nss_status_t
386 _nss_files_XY_hash(files_backend_ptr_t be, nss_XbyY_args_t *args,
387 	int netdb, files_hash_t *fhp, int hashop, files_XY_check_func check)
388 {
389 	/* LINTED E_FUNC_VAR_UNUSED */
390 	int fd, retries, ht, stat;
391 	/* LINTED E_FUNC_VAR_UNUSED */
392 	uint_t hash, line, f;
393 	/* LINTED E_FUNC_VAR_UNUSED */
394 	files_hashent_t *hp, *htab;
395 	/* LINTED E_FUNC_VAR_UNUSED */
396 	char *cp, *first, *last;
397 	/* LINTED E_FUNC_VAR_UNUSED */
398 	nss_XbyY_args_t xargs;
399 	/* LINTED E_FUNC_VAR_UNUSED */
400 	struct stat64 st;
401 
402 #ifndef PIC
403 	return (_nss_files_XY_all(be, args, netdb, 0, check));
404 }
405 #else
406 	if (__nss_use_files_hash == 0)
407 		return (_nss_files_XY_all(be, args, netdb, 0, check));
408 
409 	mutex_lock(&fhp->fh_lock);
410 retry:
411 	retries = 100;
412 	while (stat64(be->filename, &st) < 0) {
413 		/*
414 		 * This can happen only in two cases: Either the file is
415 		 * completely missing and we were not able to read it yet
416 		 * (fh_table is NULL), or there is some brief period when the
417 		 * file is being modified/renamed.  Keep trying until things
418 		 * settle down, but eventually give up.
419 		 */
420 		if (fhp->fh_table == NULL || --retries == 0)
421 			goto unavail;
422 		poll(0, 0, 100);
423 	}
424 
425 	if (st.st_mtim.tv_sec == fhp->fh_mtime.tv_sec &&
426 	    st.st_mtim.tv_nsec == fhp->fh_mtime.tv_nsec &&
427 	    fhp->fh_table != NULL) {
428 		htab = &fhp->fh_table[hashop * fhp->fh_size];
429 		hash = fhp->fh_hash_func[hashop](args, 1, NULL, 0);
430 		for (hp = htab[hash % fhp->fh_size].h_first; hp != NULL;
431 		    hp = hp->h_next) {
432 			if (hp->h_hash != hash)
433 				continue;
434 			line = hp - htab;
435 			if ((*check)(args, fhp->fh_line[line].l_start,
436 					fhp->fh_line[line].l_len) == 0)
437 				continue;
438 
439 			if (be->filename != NULL) {
440 				stat = NSS_STR_PARSE_SUCCESS;
441 				if (strcmp(be->filename, PF_PATH) == 0)
442 					stat = validate_passwd_ids(
443 					    fhp->fh_line[line].l_start,
444 					    &fhp->fh_line[line].l_len,
445 					    fhp->fh_line[line].l_len + 1,
446 					    1);
447 				else if (strcmp(be->filename, GF_PATH) == 0)
448 					stat = validate_group_ids(
449 					    fhp->fh_line[line].l_start,
450 					    &fhp->fh_line[line].l_len,
451 					    fhp->fh_line[line].l_len + 1,
452 					    1, check);
453 				if (stat != NSS_STR_PARSE_SUCCESS) {
454 					if (stat == NSS_STR_PARSE_ERANGE)
455 						args->erange = 1;
456 					continue;
457 				}
458 			}
459 
460 			if ((*args->str2ent)(fhp->fh_line[line].l_start,
461 			    fhp->fh_line[line].l_len, args->buf.result,
462 			    args->buf.buffer, args->buf.buflen) ==
463 			    NSS_STR_PARSE_SUCCESS) {
464 				args->returnval = (args->buf.result)?
465 					args->buf.result:args->buf.buffer;
466 				args->returnlen = fhp->fh_line[line].l_len;
467 				mutex_unlock(&fhp->fh_lock);
468 				return (NSS_SUCCESS);
469 			} else {
470 				args->erange = 1;
471 			}
472 		}
473 		args->returnval = 0;
474 		args->returnlen = 0;
475 		mutex_unlock(&fhp->fh_lock);
476 		return (NSS_NOTFOUND);
477 	}
478 
479 	_nss_files_hash_destroy(fhp);
480 
481 	if (st.st_size > SSIZE_MAX)
482 		goto unavail;
483 
484 	if ((fhp->fh_file_start = malloc((ssize_t)st.st_size + 1)) == NULL)
485 		goto unavail;
486 
487 	if ((fd = open(be->filename, O_RDONLY)) < 0)
488 		goto unavail;
489 
490 	if (read(fd, fhp->fh_file_start, (ssize_t)st.st_size) !=
491 	    (ssize_t)st.st_size) {
492 		close(fd);
493 		goto retry;
494 	}
495 
496 	close(fd);
497 
498 	fhp->fh_file_end = fhp->fh_file_start + (off_t)st.st_size;
499 	*fhp->fh_file_end = '\n';
500 	fhp->fh_mtime = st.st_mtim;
501 
502 	/*
503 	 * If the file changed since we read it, or if it's less than
504 	 * 1-2 seconds old, don't trust it; its modification may still
505 	 * be in progress.  The latter is a heuristic hack to minimize
506 	 * the likelihood of damage if someone modifies /etc/mumble
507 	 * directly (as opposed to editing and renaming a temp file).
508 	 *
509 	 * Note: the cast to u_int is there in case (1) someone rdated
510 	 * the system backwards since the last modification of /etc/mumble
511 	 * or (2) this is a diskless client whose time is badly out of sync
512 	 * with its server.  The 1-2 second age hack doesn't cover these
513 	 * cases -- oh well.
514 	 */
515 	if (stat64(be->filename, &st) < 0 ||
516 	    st.st_mtim.tv_sec != fhp->fh_mtime.tv_sec ||
517 	    st.st_mtim.tv_nsec != fhp->fh_mtime.tv_nsec ||
518 	    (uint_t)(time(0) - st.st_mtim.tv_sec + 2) < 4) {
519 		poll(0, 0, 1000);
520 		goto retry;
521 	}
522 
523 	line = 1;
524 	for (cp = fhp->fh_file_start; cp < fhp->fh_file_end; cp++)
525 		if (*cp == '\n')
526 			line++;
527 
528 	for (f = 2; f * f <= line; f++) {	/* find next largest prime */
529 		if (line % f == 0) {
530 			f = 1;
531 			line++;
532 		}
533 	}
534 
535 	fhp->fh_size = line;
536 	fhp->fh_line = malloc(line * sizeof (files_linetab_t));
537 	fhp->fh_table = calloc(line * fhp->fh_nhtab, sizeof (files_hashent_t));
538 	if (fhp->fh_line == NULL || fhp->fh_table == NULL)
539 		goto unavail;
540 
541 	line = 0;
542 	cp = fhp->fh_file_start;
543 	while (cp < fhp->fh_file_end) {
544 		first = cp;
545 		while (*cp != '\n')
546 			cp++;
547 		if (cp > first && *(cp - 1) == '\\') {
548 			memmove(first + 2, first, cp - first - 1);
549 			cp = first + 2;
550 			continue;
551 		}
552 		last = cp;
553 		*cp++ = '\0';
554 		if (netdb) {
555 			if ((last = strchr(first, '#')) == 0)
556 				last = cp - 1;
557 			*last-- = '\0';		/* nuke '\n' or #comment */
558 			while (isspace(*first))	/* nuke leading whitespace */
559 				first++;
560 			if (*first == '\0')	/* skip content-free lines */
561 				continue;
562 			while (isspace(*last))	/* nuke trailing whitespace */
563 				--last;
564 			*++last = '\0';
565 		}
566 		for (ht = 0; ht < fhp->fh_nhtab; ht++) {
567 			hp = &fhp->fh_table[ht * fhp->fh_size + line];
568 			hp->h_hash = fhp->fh_hash_func[ht](&xargs, 0, first,
569 					last - first);
570 		}
571 		fhp->fh_line[line].l_start = first;
572 		fhp->fh_line[line++].l_len = last - first;
573 	}
574 
575 	/*
576 	 * Populate the hash tables in reverse order so that the hash chains
577 	 * end up in forward order.  This ensures that hashed lookups find
578 	 * things in the same order that a linear search of the file would.
579 	 * This is essential in cases where there could be multiple matches.
580 	 * For example: until 2.7, root and smtp both had uid 0; but we
581 	 * certainly wouldn't want getpwuid(0) to return smtp.
582 	 */
583 	for (ht = 0; ht < fhp->fh_nhtab; ht++) {
584 		htab = &fhp->fh_table[ht * fhp->fh_size];
585 		for (hp = &htab[line - 1]; hp >= htab; hp--) {
586 			uint_t bucket = hp->h_hash % fhp->fh_size;
587 			hp->h_next = htab[bucket].h_first;
588 			htab[bucket].h_first = hp;
589 		}
590 	}
591 
592 	goto retry;
593 
594 unavail:
595 	_nss_files_hash_destroy(fhp);
596 	mutex_unlock(&fhp->fh_lock);
597 	return (NSS_UNAVAIL);
598 }
599 #endif /* PIC */
600 
601 nss_status_t
602 _nss_files_getent_rigid(be, a)
603 	files_backend_ptr_t	be;
604 	void			*a;
605 {
606 	nss_XbyY_args_t		*args = (nss_XbyY_args_t *)a;
607 
608 	return (_nss_files_XY_all(be, args, 0, 0, 0));
609 }
610 
611 nss_status_t
612 _nss_files_getent_netdb(be, a)
613 	files_backend_ptr_t	be;
614 	void			*a;
615 {
616 	nss_XbyY_args_t		*args = (nss_XbyY_args_t *)a;
617 
618 	return (_nss_files_XY_all(be, args, 1, 0, 0));
619 }
620 
621 /*ARGSUSED*/
622 nss_status_t
623 _nss_files_destr(be, dummy)
624 	files_backend_ptr_t	be;
625 	void			*dummy;
626 {
627 	if (be != 0) {
628 		if (be->f != 0) {
629 			(void) _nss_files_endent(be, 0);
630 		}
631 		if (be->hashinfo != NULL) {
632 			(void) mutex_lock(&be->hashinfo->fh_lock);
633 			if (--be->hashinfo->fh_refcnt == 0)
634 				_nss_files_hash_destroy(be->hashinfo);
635 			(void) mutex_unlock(&be->hashinfo->fh_lock);
636 		}
637 		free(be);
638 	}
639 	return (NSS_SUCCESS);	/* In case anyone is dumb enough to check */
640 }
641 
642 nss_backend_t *
643 _nss_files_constr(ops, n_ops, filename, min_bufsize, fhp)
644 	files_backend_op_t	ops[];
645 	int			n_ops;
646 	const char		*filename;
647 	int			min_bufsize;
648 	files_hash_t		*fhp;
649 {
650 	files_backend_ptr_t	be;
651 
652 	if ((be = (files_backend_ptr_t)malloc(sizeof (*be))) == 0) {
653 		return (0);
654 	}
655 	be->ops		= ops;
656 	be->n_ops	= n_ops;
657 	be->filename	= filename;
658 	be->minbuf	= min_bufsize;
659 	be->f		= 0;
660 	be->buf		= 0;
661 	be->hashinfo	= fhp;
662 
663 	if (fhp != NULL) {
664 		(void) mutex_lock(&fhp->fh_lock);
665 		fhp->fh_refcnt++;
666 		(void) mutex_unlock(&fhp->fh_lock);
667 	}
668 
669 	return ((nss_backend_t *)be);
670 }
671 
672 int
673 _nss_files_check_name_colon(nss_XbyY_args_t *argp, const char *line,
674 	int linelen)
675 {
676 	const char	*linep, *limit;
677 	const char	*keyp = argp->key.name;
678 
679 	linep = line;
680 	limit = line + linelen;
681 	while (*keyp && linep < limit && *keyp == *linep) {
682 		keyp++;
683 		linep++;
684 	}
685 	return (linep < limit && *keyp == '\0' && *linep == ':');
686 }
687 
688 /*
689  * This routine is used to parse lines of the form:
690  * 	name number aliases
691  * It returns 1 if the key in argp matches any one of the
692  * names in the line, otherwise 0
693  * Used by rpc, networks, protocols
694  */
695 int
696 _nss_files_check_name_aliases(nss_XbyY_args_t *argp, const char *line,
697 	int linelen)
698 {
699 	const char	*limit, *linep, *keyp;
700 
701 	linep = line;
702 	limit = line + linelen;
703 	keyp = argp->key.name;
704 
705 	/* compare name */
706 	while (*keyp && linep < limit && !isspace(*linep) && *keyp == *linep) {
707 		keyp++;
708 		linep++;
709 	}
710 	if (*keyp == '\0' && linep < limit && isspace(*linep))
711 		return (1);
712 	/* skip remainder of the name, if any */
713 	while (linep < limit && !isspace(*linep))
714 		linep++;
715 	/* skip the delimiting spaces */
716 	while (linep < limit && isspace(*linep))
717 		linep++;
718 	/* compare with the aliases */
719 	while (linep < limit) {
720 		/*
721 		 * 1st pass: skip number
722 		 * Other passes: skip remainder of the alias name, if any
723 		 */
724 		while (linep < limit && !isspace(*linep))
725 			linep++;
726 		/* skip the delimiting spaces */
727 		while (linep < limit && isspace(*linep))
728 			linep++;
729 		/* compare with the alias name */
730 		keyp = argp->key.name;
731 		while (*keyp && linep < limit && !isspace(*linep) &&
732 		    *keyp == *linep) {
733 			keyp++;
734 			linep++;
735 		}
736 		if (*keyp == '\0' && (linep == limit || isspace(*linep)))
737 			return (1);
738 	}
739 	return (0);
740 }
741