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