xref: /illumos-gate/usr/src/lib/libc/port/gen/getgrnam_r.c (revision 1a220b56b93ff1dc80855691548503117af4cc10)
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 2006 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 #pragma ident	"%Z%%M%	%I%	%E% SMI"
27 
28 #pragma weak endgrent = _endgrent
29 #pragma weak setgrent = _setgrent
30 
31 #pragma weak getgrnam_r = _getgrnam_r
32 #pragma weak getgrgid_r = _getgrgid_r
33 #pragma weak getgrent_r = _getgrent_r
34 #pragma weak fgetgrent_r = _fgetgrent_r
35 
36 #include "synonyms.h"
37 #include <mtlib.h>
38 #include <sys/types.h>
39 #include <grp.h>
40 #include <memory.h>
41 #include <nsswitch.h>
42 #include <nss_dbdefs.h>
43 #include <stdio.h>
44 #include <stdlib.h>
45 #include <string.h>
46 #include <synch.h>
47 #include <sys/param.h>
48 #include <sys/mman.h>
49 
50 extern int _getgroupsbymember(const char *, gid_t[], int, int);
51 int str2group(const char *, int, void *,
52 	char *, int);
53 
54 static DEFINE_NSS_DB_ROOT(db_root);
55 static DEFINE_NSS_GETENT(context);
56 
57 #define	USE_NETID_STR	"NETID_AUTHORITATIVE=TRUE"
58 
59 void
60 _nss_initf_group(nss_db_params_t *p)
61 {
62 	p->name	= NSS_DBNAM_GROUP;
63 	p->default_config = NSS_DEFCONF_GROUP;
64 }
65 
66 #include <getxby_door.h>
67 #include <sys/door.h>
68 
69 static struct group *
70 process_getgr(struct group *result, char *buffer, int buflen,
71     nsc_data_t *sptr, int ndata);
72 
73 struct group *
74 _uncached_getgrnam_r(const char *name, struct group *result, char *buffer,
75     int buflen);
76 
77 struct group *
78 _uncached_getgrgid_r(gid_t gid, struct group *result, char *buffer, int buflen);
79 
80 /*
81  * POSIX.1c Draft-6 version of the function getgrnam_r.
82  * It was implemented by Solaris 2.3.
83  */
84 struct group *
85 _getgrnam_r(const char *name, struct group *result, char *buffer, int buflen)
86 {
87 	/*
88 	 * allocate room on the stack for the nscd to return
89 	 * group and group member information
90 	 */
91 	union {
92 		nsc_data_t	s_d;
93 		char		s_b[8192];
94 	} space;
95 	nsc_data_t	*sptr;
96 	int		ndata;
97 	int		adata;
98 	struct group	*resptr = NULL;
99 
100 	if ((name == (const char *)NULL) ||
101 	    (strlen(name) >= (sizeof (space) - sizeof (nsc_data_t)))) {
102 		errno = ERANGE;
103 		return (NULL);
104 	}
105 
106 	ndata = sizeof (space);
107 	adata = strlen(name) + sizeof (nsc_call_t) + 1;
108 	space.s_d.nsc_call.nsc_callnumber = GETGRNAM;
109 	(void) strcpy(space.s_d.nsc_call.nsc_u.name, name);
110 	sptr = &space.s_d;
111 
112 	switch (_nsc_trydoorcall(&sptr, &ndata, &adata)) {
113 	case SUCCESS:	/* positive cache hit */
114 		break;
115 	case NOTFOUND:	/* negative cache hit */
116 		return (NULL);
117 	default:
118 		return ((struct group *)_uncached_getgrnam_r(name, result,
119 		    buffer, buflen));
120 	}
121 
122 	resptr = process_getgr(result, buffer, buflen, sptr, ndata);
123 
124 	/*
125 	 * check to see if doors reallocated memory underneath us
126 	 * if they did munmap the memory or suffer a memory leak
127 	 */
128 
129 	if (sptr != &space.s_d)
130 		munmap((void *)sptr, ndata);
131 
132 	return (resptr);
133 }
134 
135 /*
136  * POSIX.1c Draft-6 version of the function getgrgid_r.
137  * It was implemented by Solaris 2.3.
138  */
139 struct group *
140 _getgrgid_r(gid_t gid, struct group *result, char *buffer, int buflen)
141 {
142 	/*
143 	 * allocate room on the stack for the nscd to return
144 	 * group and group member information
145 	 */
146 	union {
147 		nsc_data_t	s_d;
148 		char		s_b[8192];
149 	} space;
150 	nsc_data_t	*sptr;
151 	int		ndata;
152 	int		adata;
153 	struct group	*resptr = NULL;
154 
155 	ndata = sizeof (space);
156 	adata = sizeof (nsc_call_t) + 1;
157 	space.s_d.nsc_call.nsc_callnumber = GETGRGID;
158 	space.s_d.nsc_call.nsc_u.gid = gid;
159 	sptr = &space.s_d;
160 
161 	switch (_nsc_trydoorcall(&sptr, &ndata, &adata)) {
162 	case SUCCESS:	/* positive cache hit */
163 		break;
164 	case NOTFOUND:	/* negative cache hit */
165 		return (NULL);
166 	default:
167 		return ((struct group *)_uncached_getgrgid_r(gid, result,
168 		    buffer, buflen));
169 	}
170 
171 	resptr = process_getgr(result, buffer, buflen, sptr, ndata);
172 
173 	/*
174 	 * check to see if doors reallocated memory underneath us
175 	 * if they did munmap the memory or suffer a memory leak
176 	 */
177 
178 	if (sptr != &space.s_d)
179 		munmap((void *)sptr, ndata);
180 
181 	return (resptr);
182 }
183 /*
184  *  This routine should be rewritten - there's no reason it
185  *  cannot be the same code for 32 and 64 bit w/ a bit of care.
186  */
187 /* ARGSUSED4 */
188 static struct group *
189 process_getgr(struct group *result, char *buffer, int buflen,
190     nsc_data_t *sptr, int ndata)
191 {
192 	int i;
193 	char *fixed;
194 #ifdef	_LP64
195 	char	*buffer32;
196 	char	**gr_mem32;
197 	uptr32_t	index;
198 	struct group	group64;
199 #endif	/*	_LP64	*/
200 
201 /* align buffer on a pointer boundry 4bytes in 32bits and 8 bytes in 64bits */
202 #ifdef	_LP64
203 	fixed = (char *)(((uintptr_t)buffer + 7) & ~7);
204 #else
205 	fixed = (char *)(((uintptr_t)buffer + 3) & ~3);
206 #endif	/*	_LP64	*/
207 
208 	if (buflen <= fixed - buffer) { /* watch out for wrap-around */
209 		errno = ERANGE;
210 		return (NULL);
211 	}
212 
213 	buflen -= fixed - buffer;
214 
215 	buffer = fixed;
216 
217 #ifdef	_LP64
218 	/*
219 	 * this is just a rationality check; we need to make
220 	 * sure that there's enough space for the gr_mem array
221 	 * as well... easiest place to do that is when we copy
222 	 * them in place.
223 	 */
224 
225 	if (sptr->nsc_ret.nsc_bufferbytesused +
226 		/*
227 		 * ^^^ number of bytes from nscd
228 		 */
229 	    (sizeof (char **)) +
230 		/*
231 		 * ^^^ we need 8 bytes for gr_mem
232 		 */
233 	    (sizeof (char *) - 1) -
234 		/*
235 		 * ^^^ plus space for pssibly fixing aligment of gr_mem
236 		 */
237 	    sizeof (group32_t)
238 		/*
239 		 * ^^^ because we don't put this in the usr buffer
240 		 */
241 	    > buflen) {
242 #else
243 	if (sptr->nsc_ret.nsc_bufferbytesused - sizeof (struct group)
244 	    > buflen) {
245 #endif	/*	_LP64	*/
246 		errno = ERANGE;
247 		return (NULL);
248 	}
249 
250 	if (sptr->nsc_ret.nsc_return_code != SUCCESS)
251 		return (NULL);
252 
253 /*
254  * ncsd is a 32bit application, so use 32bit data items if we are in 64bit mode
255  */
256 #ifdef	_LP64
257 
258 	(void) memcpy(buffer,
259 	    (sptr->nsc_ret.nsc_u.buff + sizeof (group32_t)),
260 	    (sptr->nsc_ret.nsc_bufferbytesused - sizeof (group32_t)));
261 
262 	group64.gr_name = (char *)(sptr->nsc_ret.nsc_u.grp.gr_name +
263 				(uintptr_t)buffer);
264 	group64.gr_passwd = (char *)(sptr->nsc_ret.nsc_u.grp.gr_passwd +
265 				(uintptr_t)buffer);
266 	group64.gr_gid = sptr->nsc_ret.nsc_u.grp.gr_gid;
267 
268 	group64.gr_mem = (char **)((uintptr_t)buffer +
269 			sptr->nsc_ret.nsc_bufferbytesused - sizeof (group32_t));
270 	group64.gr_mem = (char **)(((uintptr_t)group64.gr_mem + 7) & ~7);
271 
272 	gr_mem32 = (char **)(uintptr_t)sptr->nsc_ret.nsc_u.grp.gr_mem;
273 	buffer32 = buffer;
274 
275 	for (i = 0; ; i++) {
276 		index = *((uptr32_t *)
277 		    ((uintptr_t)gr_mem32 + (uintptr_t)buffer32));
278 
279 		/*
280 		 * make sure there's enough space to copy the pointer...
281 		 */
282 		if (&group64.gr_mem[i + 1] >
283 		    (char **)((uintptr_t)buffer + buflen)) {
284 			errno = ERANGE;
285 			return (NULL);
286 		}
287 
288 		if (index == 0)
289 			break;
290 
291 		group64.gr_mem[i] = (char *)(index + buffer);
292 		buffer32 += sizeof (uptr32_t);
293 
294 	}
295 	group64.gr_mem[i] = NULL;
296 
297 	*result = group64;
298 #else
299 
300 	(void) memcpy(buffer,
301 	    (sptr->nsc_ret.nsc_u.buff + sizeof (struct group)),
302 	    (sptr->nsc_ret.nsc_bufferbytesused - sizeof (struct group)));
303 
304 	sptr->nsc_ret.nsc_u.grp.gr_name += (uintptr_t)buffer;
305 	sptr->nsc_ret.nsc_u.grp.gr_passwd += (uintptr_t)buffer;
306 
307 	sptr->nsc_ret.nsc_u.grp.gr_mem =
308 	    (char **)((uintptr_t)sptr->nsc_ret.nsc_u.grp.gr_mem +
309 		(uintptr_t)buffer);
310 
311 	i = 0;
312 	while (sptr->nsc_ret.nsc_u.grp.gr_mem[i]) {
313 		sptr->nsc_ret.nsc_u.grp.gr_mem[i] += (uintptr_t)buffer;
314 		i++;
315 	}
316 
317 	*result = sptr->nsc_ret.nsc_u.grp;
318 
319 #endif	/*	_LP64	*/
320 
321 	return (result);
322 }
323 
324 struct group *
325 _uncached_getgrgid_r(gid_t gid, struct group *result, char *buffer,
326     int buflen)
327 {
328 	nss_XbyY_args_t arg;
329 
330 	NSS_XbyY_INIT(&arg, result, buffer, buflen, str2group);
331 	arg.key.gid = gid;
332 	(void) nss_search(&db_root, _nss_initf_group,
333 				NSS_DBOP_GROUP_BYGID, &arg);
334 	return ((struct group *)NSS_XbyY_FINI(&arg));
335 }
336 
337 /*
338  * POSIX.1c standard version of the function getgrgid_r.
339  * User gets it via static getgrgid_r from the header file.
340  */
341 int
342 __posix_getgrgid_r(gid_t gid, struct group *grp, char *buffer,
343     size_t bufsize, struct group **result)
344 {
345 	int nerrno = 0;
346 	int oerrno = errno;
347 
348 	errno = 0;
349 	if ((*result = _getgrgid_r(gid, grp, buffer, (uintptr_t)bufsize))
350 		== NULL) {
351 			nerrno = errno;
352 	}
353 	errno = oerrno;
354 	return (nerrno);
355 }
356 
357 extern struct group *
358 _getgrnam_r(const char *name, struct group *result, char *buffer,
359 	int buflen);
360 
361 struct group *
362 _uncached_getgrnam_r(const char *name, struct group *result, char *buffer,
363 	int buflen)
364 {
365 	nss_XbyY_args_t arg;
366 
367 	NSS_XbyY_INIT(&arg, result, buffer, buflen, str2group);
368 	arg.key.name = name;
369 	(void) nss_search(&db_root, _nss_initf_group,
370 			NSS_DBOP_GROUP_BYNAME, &arg);
371 	return ((struct group *)NSS_XbyY_FINI(&arg));
372 }
373 
374 /*
375  * POSIX.1c standard version of the function getgrnam_r.
376  * User gets it via static getgrnam_r from the header file.
377  */
378 int
379 __posix_getgrnam_r(const char *name, struct group *grp, char *buffer,
380     size_t bufsize, struct group **result)
381 {
382 	int nerrno = 0;
383 	int oerrno = errno;
384 
385 	if ((*result = _getgrnam_r(name, grp, buffer, (uintptr_t)bufsize))
386 		== NULL) {
387 			nerrno = errno;
388 	}
389 	errno = oerrno;
390 	return (nerrno);
391 }
392 
393 void
394 setgrent(void)
395 {
396 	nss_setent(&db_root, _nss_initf_group, &context);
397 }
398 
399 void
400 endgrent(void)
401 {
402 	nss_endent(&db_root, _nss_initf_group, &context);
403 	nss_delete(&db_root);
404 }
405 
406 struct group *
407 getgrent_r(struct group *result, char *buffer, int buflen)
408 {
409 	nss_XbyY_args_t arg;
410 	char		*nam;
411 
412 	/* In getXXent_r(), protect the unsuspecting caller from +/- entries */
413 
414 	do {
415 		NSS_XbyY_INIT(&arg, result, buffer, buflen, str2group);
416 		/* No key to fill in */
417 		(void) nss_getent(&db_root, _nss_initf_group, &context, &arg);
418 	} while (arg.returnval != 0 &&
419 		(nam = ((struct group *)arg.returnval)->gr_name) != 0 &&
420 		(*nam == '+' || *nam == '-'));
421 
422 	return ((struct group *)NSS_XbyY_FINI(&arg));
423 }
424 
425 struct group *
426 fgetgrent_r(FILE *f, struct group *result, char *buffer, int buflen)
427 {
428 	extern void	_nss_XbyY_fgets(FILE *, nss_XbyY_args_t *);
429 	nss_XbyY_args_t	arg;
430 
431 	/* ... but in fgetXXent_r, the caller deserves any +/- entry he gets */
432 
433 	/* No key to fill in */
434 	NSS_XbyY_INIT(&arg, result, buffer, buflen, str2group);
435 	_nss_XbyY_fgets(f, &arg);
436 	return ((struct group *)NSS_XbyY_FINI(&arg));
437 }
438 
439 /*
440  * _getgroupsbymember(uname, gid_array, maxgids, numgids):
441  *	Private interface for initgroups().  It returns the group ids of
442  *	groups of which the specified user is a member.
443  *
444  * Arguments:
445  *   username	Username of the putative member
446  *   gid_array	Space in which to return the gids.  The first [numgids]
447  *		elements are assumed to already contain valid gids.
448  *   maxgids	Maximum number of elements in gid_array.
449  *   numgids	Number of elements (normally 0 or 1) that already contain
450  *		valid gids.
451  * Return value:
452  *   number of valid gids in gid_array (may be zero)
453  *	or
454  *   -1 (and errno set appropriately) on errors (none currently defined)
455  */
456 
457 static nss_status_t process_cstr(const char *, int, struct nss_groupsbymem *);
458 
459 int
460 _getgroupsbymember(const char *username, gid_t gid_array[],
461     int maxgids, int numgids)
462 {
463 	struct nss_groupsbymem	arg;
464 	char defval[BUFSIZ];
465 	FILE *defl;
466 
467 	arg.username	= username;
468 	arg.gid_array	= gid_array;
469 	arg.maxgids	= maxgids;
470 	arg.numgids	= numgids;
471 	arg.str2ent	= str2group;
472 	arg.process_cstr = process_cstr;
473 
474 	/*
475 	 * The old value being provided here was 0, ie do the quick
476 	 * way.  Given that this was never actually used under NIS
477 	 * and had the wrong (now corrected) meaning for NIS+ we need
478 	 * to change the default to be 1 (TRUE) as we now need the
479 	 * admin to decided to use netid, setting NETID_AUTHORITATIVE
480 	 * in /etc/default/nss to TRUE gets us a value of 0 for
481 	 * force_slow_way - don't you just love double negatives ;-)
482 	 *
483 	 * We need to do this to preserve the behaviour seen when the
484 	 * admin makes no changes.
485 	 */
486 	arg.force_slow_way = 1;
487 
488 	/*
489 	 * The "easy" way to do /etc/default/nss is to use the defread()
490 	 * stuff from libcmd, but since we are in libc we don't want to
491 	 * link ourselfs against libcmd, so instead we just do it by hand
492 	 */
493 
494 	if ((defl = fopen(__NSW_DEFAULT_FILE, "rF")) != NULL) {
495 		while (fgets(defval, sizeof (defval), defl) != NULL) {
496 			if (strncmp(USE_NETID_STR, defval,
497 			    sizeof (USE_NETID_STR) - 1) == 0) {
498 				arg.force_slow_way = 0;
499 				break;
500 			}
501 		}
502 		(void) fclose(defl);
503 	}
504 
505 	(void) nss_search(&db_root, _nss_initf_group,
506 			NSS_DBOP_GROUP_BYMEMBER, &arg);
507 
508 #ifdef	undef
509 	/*
510 	 * Only do this if there's existing code somewhere that relies on
511 	 *   initgroups() doing an endgrent() -- most unlikely.
512 	 */
513 	endgrent();
514 #endif	/* undef */
515 
516 	return (arg.numgids);
517 }
518 
519 
520 static char *
521 gettok(char **nextpp, char sep)
522 {
523 	char	*p = *nextpp;
524 	char	*q = p;
525 	char	c;
526 
527 	if (p == 0)
528 		return (0);
529 
530 	while ((c = *q) != '\0' && c != sep)
531 		q++;
532 
533 	if (c == '\0')
534 		*nextpp = 0;
535 	else {
536 		*q++ = '\0';
537 		*nextpp = q;
538 	}
539 	return (p);
540 }
541 
542 /*
543  * Return values: 0 = success, 1 = parse error, 2 = erange ...
544  * The structure pointer passed in is a structure in the caller's space
545  * wherein the field pointers would be set to areas in the buffer if
546  * need be. instring and buffer should be separate areas.
547  */
548 int
549 str2group(const char *instr, int lenstr, void *ent, char *buffer, int buflen)
550 {
551 	struct group		*group	= (struct group *)ent;
552 	char			*p, *next;
553 	int			black_magic;	/* "+" or "-" entry */
554 	char			**memlist, **limit;
555 
556 	if (lenstr + 1 > buflen)
557 		return (NSS_STR_PARSE_ERANGE);
558 
559 	/*
560 	 * We copy the input string into the output buffer and
561 	 * operate on it in place.
562 	 */
563 	(void) memcpy(buffer, instr, lenstr);
564 	buffer[lenstr] = '\0';
565 
566 	next = buffer;
567 
568 	/*
569 	 * Parsers for passwd and group have always been pretty rigid;
570 	 * we wouldn't want to buck a Unix tradition
571 	 */
572 
573 	group->gr_name = p = gettok(&next, ':');
574 	if (*p == '\0') {
575 		/* Empty group-name;  not allowed */
576 		return (NSS_STR_PARSE_PARSE);
577 	}
578 
579 	/* Always return at least an empty gr_mem list */
580 	memlist	= (char **)ROUND_UP(buffer + lenstr + 1, sizeof (char *));
581 	limit	= (char **)ROUND_DOWN(buffer + buflen, sizeof (char *));
582 	*memlist = 0;
583 	group->gr_mem = memlist;
584 
585 	black_magic = (*p == '+' || *p == '-');
586 	if (black_magic) {
587 		/* Then the rest of the group entry is optional */
588 		group->gr_passwd = 0;
589 		group->gr_gid = 0;
590 	}
591 
592 	group->gr_passwd = p = gettok(&next, ':');
593 	if (p == 0) {
594 		if (black_magic)
595 			return (NSS_STR_PARSE_SUCCESS);
596 		else
597 			return (NSS_STR_PARSE_PARSE);
598 	}
599 
600 	p = next;					/* gid */
601 	if (p == 0 || *p == '\0') {
602 		if (black_magic)
603 			return (NSS_STR_PARSE_SUCCESS);
604 		else
605 			return (NSS_STR_PARSE_PARSE);
606 	}
607 	if (!black_magic) {
608 		group->gr_gid = (gid_t)strtol(p, &next, 10);
609 		if (next == p) {
610 			/* gid field should be nonempty */
611 			return (NSS_STR_PARSE_PARSE);
612 		}
613 		/*
614 		 * gids should be non-negative; anything else
615 		 * is administrative policy.
616 		 */
617 		if (group->gr_gid < 0)
618 			group->gr_gid = GID_NOBODY;
619 	}
620 	if (*next++ != ':') {
621 		/* Parse error, even for a '+' entry (which should have	*/
622 		/*   an empty gid field, since it's always overridden)	*/
623 		return (NSS_STR_PARSE_PARSE);
624 	}
625 
626 	/* === Could check and complain if there are any extra colons */
627 	while (memlist < limit) {
628 		p = gettok(&next, ',');
629 		if (p == 0 || *p == '\0') {
630 			*memlist = 0;
631 			/* Successfully parsed and stored */
632 			return (NSS_STR_PARSE_SUCCESS);
633 		}
634 		*memlist++ = p;
635 	}
636 	/* Out of space;  error even for black_magic */
637 	return (NSS_STR_PARSE_ERANGE);
638 }
639 
640 static nss_status_t
641 process_cstr(const char *instr, int instr_len, struct nss_groupsbymem *gbm)
642 {
643 	/*
644 	 * It's possible to do a much less inefficient version of this by
645 	 * selectively duplicating code from str2group().  For now,
646 	 * however, we'll take the easy way out and implement this on
647 	 * top of str2group().
648 	 */
649 
650 	const char		*username = gbm->username;
651 	nss_XbyY_buf_t		*buf;
652 	struct group		*grp;
653 	char			**memp;
654 	char			*mem;
655 	int	parsestat;
656 
657 	buf = _nss_XbyY_buf_alloc(sizeof (struct group), NSS_BUFLEN_GROUP);
658 	if (buf == 0)
659 		return (NSS_UNAVAIL);
660 
661 	grp = (struct group *)buf->result;
662 
663 	parsestat = (*gbm->str2ent)(instr, instr_len,
664 				    grp, buf->buffer, buf->buflen);
665 
666 	if (parsestat != NSS_STR_PARSE_SUCCESS) {
667 		_nss_XbyY_buf_free(buf);
668 		return (NSS_NOTFOUND);	/* === ? */
669 	}
670 
671 	if (grp->gr_mem) {
672 		for (memp = grp->gr_mem; (memp) && ((mem = *memp) != 0);
673 								memp++) {
674 			if (strcmp(mem, username) == 0) {
675 				gid_t	gid 	= grp->gr_gid;
676 				gid_t	*gidp	= gbm->gid_array;
677 				int	numgids	= gbm->numgids;
678 				int	i;
679 
680 				_nss_XbyY_buf_free(buf);
681 
682 				for (i = 0; i < numgids && *gidp != gid; i++,
683 								gidp++) {
684 					;
685 				}
686 				if (i >= numgids) {
687 					if (i >= gbm->maxgids) {
688 					/* Filled the array;  stop searching */
689 						return (NSS_SUCCESS);
690 					}
691 					*gidp = gid;
692 					gbm->numgids = numgids + 1;
693 				}
694 				return (NSS_NOTFOUND);	/* Explained in   */
695 							/* <nss_dbdefs.h> */
696 			}
697 		}
698 	}
699 	_nss_XbyY_buf_free(buf);
700 	return (NSS_NOTFOUND);
701 }
702