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
_nss_files_setent(be,dummy)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
_nss_files_endent(be,dummy)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
_nss_files_read_line(f,buffer,buflen)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
_nss_files_do_all(be,args,filter,func)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
_nss_files_XY_all(be,args,netdb,filter,check)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
_nss_files_hash_destroy(files_hash_t * fhp)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
_nss_files_XY_hash(files_backend_ptr_t be,nss_XbyY_args_t * args,int netdb,files_hash_t * fhp,int hashop,files_XY_check_func check)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
_nss_files_getent_rigid(be,a)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
_nss_files_getent_netdb(be,a)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
_nss_files_destr(be,dummy)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 *
_nss_files_constr(ops,n_ops,filename,min_bufsize,fhp)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
_nss_files_check_name_colon(nss_XbyY_args_t * argp,const char * line,int linelen)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
_nss_files_check_name_aliases(nss_XbyY_args_t * argp,const char * line,int linelen)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