1/*	$NetBSD: nsdispatch.c,v 1.37 2012/03/13 21:13:42 christos Exp $	*/
2
3/*-
4 * Copyright (c) 1997, 1998, 1999, 2004 The NetBSD Foundation, Inc.
5 * All rights reserved.
6 *
7 * This code is derived from software contributed to The NetBSD Foundation
8 * by Luke Mewburn; and by Jason R. Thorpe.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 *    notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 *    notice, this list of conditions and the following disclaimer in the
17 *    documentation and/or other materials provided with the distribution.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 * POSSIBILITY OF SUCH DAMAGE.
30 */
31
32/*-
33 * Copyright (c) 2003 Networks Associates Technology, Inc.
34 * All rights reserved.
35 *
36 * Portions of this software were developed for the FreeBSD Project by
37 * Jacques A. Vidrine, Safeport Network Services, and Network
38 * Associates Laboratories, the Security Research Division of Network
39 * Associates, Inc. under DARPA/SPAWAR contract N66001-01-C-8035
40 * ("CBOSS"), as part of the DARPA CHATS research program.
41 *
42 * Redistribution and use in source and binary forms, with or without
43 * modification, are permitted provided that the following conditions
44 * are met:
45 * 1. Redistributions of source code must retain the above copyright
46 *    notice, this list of conditions and the following disclaimer.
47 * 2. Redistributions in binary form must reproduce the above copyright
48 *    notice, this list of conditions and the following disclaimer in the
49 *    documentation and/or other materials provided with the distribution.
50 *
51 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
52 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
53 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
54 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
55 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
56 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
57 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
58 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
59 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
60 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
61 * SUCH DAMAGE.
62 */
63
64#include <sys/cdefs.h>
65#if defined(LIBC_SCCS) && !defined(lint)
66__RCSID("$NetBSD: nsdispatch.c,v 1.37 2012/03/13 21:13:42 christos Exp $");
67#endif /* LIBC_SCCS and not lint */
68
69#include <sys/types.h>
70#include <sys/param.h>
71#include <sys/stat.h>
72#include <sys/queue.h>
73
74#include <assert.h>
75#ifdef __ELF__
76#include <dlfcn.h>
77#endif /* __ELF__ */
78#include <fcntl.h>
79#define _NS_PRIVATE
80#include <nsswitch.h>
81#include <pthread.h>
82#include <stdarg.h>
83#include <stdio.h>
84#include <stdlib.h>
85#include <string.h>
86#include <unistd.h>
87
88#include <libutil.h>
89
90#define __isthreaded 1
91
92extern	FILE 	*_nsyyin;
93extern	int	 _nsyyparse(void);
94
95
96#ifdef __weak_alias
97__weak_alias(nsdispatch,_nsdispatch)
98#endif
99
100
101/*
102 * default sourcelist: `files'
103 */
104const ns_src __nsdefaultsrc[] = {
105	{ NSSRC_FILES,	NS_SUCCESS },
106	{ 0, 0 },
107};
108
109const ns_src __nsdefaultcompat[] = {
110	{ NSSRC_COMPAT,	NS_SUCCESS },
111	{ 0, 0 }
112};
113
114const ns_src __nsdefaultcompat_forceall[] = {
115	{ NSSRC_COMPAT,	NS_SUCCESS | NS_FORCEALL },
116	{ 0, 0 }
117};
118
119const ns_src __nsdefaultfiles[] = {
120	{ NSSRC_FILES,	NS_SUCCESS },
121	{ 0, 0 },
122};
123
124const ns_src __nsdefaultfiles_forceall[] = {
125	{ NSSRC_FILES,	NS_SUCCESS | NS_FORCEALL },
126	{ 0, 0 },
127};
128
129const ns_src __nsdefaultnis[] = {
130	{ NSSRC_NIS,	NS_SUCCESS },
131	{ 0, 0 }
132};
133
134const ns_src __nsdefaultnis_forceall[] = {
135	{ NSSRC_NIS,	NS_SUCCESS | NS_FORCEALL },
136	{ 0, 0 }
137};
138
139
140/* Database, source mappings. */
141static	u_int			 _nsmapsize;
142static	ns_dbt			*_nsmap;
143
144/* Nsswitch modules. */
145static	u_int			 _nsmodsize;
146static	ns_mod			*_nsmod;
147
148/* Placeholder for built-in modules' dlopen() handles. */
149static	void			*_nsbuiltin = &_nsbuiltin;
150
151#ifdef _REENTRANT
152/*
153 * Global nsswitch data structures are mostly read-only, but we update them
154 * when we read or re-read nsswitch.conf.
155 */
156static 	pthread_rwlock_t		_nslock;
157static	pthread_once_t			_nslockinit = PTHREAD_ONCE_INIT;
158
159static void lockinit()
160{
161	int result = pthread_rwlock_init(&_nslock, NULL);
162	assert(result == 0);
163}
164
165/*
166 * List of threads currently in nsdispatch().  We use this to detect
167 * recursive calls and avoid reloading configuration in such cases,
168 * which could cause deadlock.
169 */
170struct _ns_drec {
171	LIST_ENTRY(_ns_drec)	list;
172	pthread_t			thr;
173};
174static LIST_HEAD(, _ns_drec) _ns_drec = LIST_HEAD_INITIALIZER(&_ns_drec);
175static pthread_mutex_t _ns_drec_lock = PTHREAD_MUTEX_INITIALIZER;
176#endif /* _REENTRANT */
177
178
179/*
180 * Runtime determination of whether we are dynamically linked or not.
181 */
182#define	is_dynamic()		(0)	/* don't bother - switch to ELF! */
183
184
185/*
186 * size of dynamic array chunk for _nsmap and _nsmap[x].srclist (and other
187 * growing arrays).
188 */
189#define NSELEMSPERCHUNK		8
190
191/*
192 * Dynamically growable arrays are used for lists of databases, sources,
193 * and modules.  The following "vector" API is used to isolate the
194 * common operations.
195 */
196typedef void	(*_nsvect_free_elem)(void *);
197
198static void *
199_nsvect_append(const void *elem, void *vec, u_int *count, size_t esize)
200{
201	void	*p;
202
203	if ((*count % NSELEMSPERCHUNK) == 0) {
204		p = realloc(vec, (*count + NSELEMSPERCHUNK) * esize);
205		if (p == NULL)
206			return (NULL);
207		vec = p;
208	}
209	memmove((void *)(((uintptr_t)vec) + (*count * esize)), elem, esize);
210	(*count)++;
211	return (vec);
212}
213
214static void *
215_nsvect_elem(u_int i, void *vec, u_int count, size_t esize)
216{
217
218	if (i < count)
219		return ((void *)((uintptr_t)vec + (i * esize)));
220	else
221		return (NULL);
222}
223
224static void
225_nsvect_free(void *vec, u_int *count, size_t esize, _nsvect_free_elem free_elem)
226{
227	void	*elem;
228	u_int	 i;
229
230	for (i = 0; i < *count; i++) {
231		elem = _nsvect_elem(i, vec, *count, esize);
232		if (elem != NULL)
233			(*free_elem)(elem);
234	}
235	if (vec != NULL)
236		free(vec);
237	*count = 0;
238}
239#define	_NSVECT_FREE(v, c, s, f)					\
240do {									\
241	_nsvect_free((v), (c), (s), (f));				\
242	(v) = NULL;							\
243} while (/*CONSTCOND*/0)
244
245static int
246_nsdbtcmp(const void *a, const void *b)
247{
248
249	return (strcasecmp(((const ns_dbt *)a)->name,
250	    ((const ns_dbt *)b)->name));
251}
252
253static int
254_nsmodcmp(const void *a, const void *b)
255{
256
257	return (strcasecmp(((const ns_mod *)a)->name,
258	    ((const ns_mod *)b)->name));
259}
260
261static int
262_nsmtabcmp(const void *a, const void *b)
263{
264	int	cmp;
265
266	cmp = strcmp(((const ns_mtab *)a)->name,
267	    ((const ns_mtab *)b)->name);
268	if (cmp)
269		return (cmp);
270
271	return (strcasecmp(((const ns_mtab *)a)->database,
272	    ((const ns_mtab *)b)->database));
273}
274
275static void
276_nsmodfree(ns_mod *mod)
277{
278
279	free(__UNCONST(mod->name));
280	if (mod->handle == NULL)
281		return;
282	if (mod->unregister != NULL)
283		(*mod->unregister)(mod->mtab, mod->mtabsize);
284#ifdef __ELF__
285	if (mod->handle != _nsbuiltin)
286		(void) dlclose(mod->handle);
287#endif /* __ELF__ */
288}
289
290/*
291 * Load a built-in or dyanamically linked module.  If the `reg_fn'
292 * argument is non-NULL, assume a built-in module and use `reg_fn'
293 * to register it.  Otherwise, search for a dynamic nsswitch module.
294 */
295static int
296_nsloadmod(const char *source, nss_module_register_fn reg_fn)
297{
298#ifdef __ELF__
299	char	buf[PATH_MAX];
300#endif
301	ns_mod	mod, *new;
302
303	memset(&mod, 0, sizeof(mod));
304	mod.name = strdup(source);
305	if (mod.name == NULL)
306		return (-1);
307
308	if (reg_fn != NULL) {
309		/*
310		 * The placeholder is required, as a NULL handle
311		 * represents an invalid module.
312		 */
313		mod.handle = _nsbuiltin;
314	} else if (!is_dynamic()) {
315		goto out;
316	} else {
317#ifdef __ELF__
318		if (snprintf(buf, sizeof(buf), "nss_%s.so.%d", mod.name,
319		    NSS_MODULE_INTERFACE_VERSION) >= (int)sizeof(buf))
320			goto out;
321		mod.handle = dlopen(buf, RTLD_LOCAL | RTLD_LAZY);
322		if (mod.handle == NULL) {
323#ifdef _NSS_DEBUG
324			/*
325			 * This gets pretty annoying, since the built-in
326			 * sources are not yet modules.
327			 */
328			/* XXX log some error? */
329#endif
330			goto out;
331		}
332		reg_fn = (nss_module_register_fn) dlsym(mod.handle,
333		    "nss_module_register");
334		if (reg_fn == NULL) {
335			(void) dlclose(mod.handle);
336			mod.handle = NULL;
337			/* XXX log some error? */
338			goto out;
339		}
340#else /* ! __ELF__ */
341		mod.handle = NULL;
342#endif /* __ELF__ */
343	}
344	mod.mtab = (*reg_fn)(mod.name, &mod.mtabsize, &mod.unregister);
345	if (mod.mtab == NULL || mod.mtabsize == 0) {
346#ifdef __ELF__
347		if (mod.handle != _nsbuiltin)
348			(void) dlclose(mod.handle);
349#endif /* __ELF__ */
350		mod.handle = NULL;
351		/* XXX log some error? */
352		goto out;
353	}
354	if (mod.mtabsize > 1)
355		qsort(mod.mtab, mod.mtabsize, sizeof(mod.mtab[0]),
356		    _nsmtabcmp);
357 out:
358	new = _nsvect_append(&mod, _nsmod, &_nsmodsize, sizeof(*_nsmod));
359	if (new == NULL) {
360		_nsmodfree(&mod);
361		return (-1);
362	}
363	_nsmod = new;
364	/* _nsmodsize already incremented */
365
366	qsort(_nsmod, _nsmodsize, sizeof(*_nsmod), _nsmodcmp);
367	return (0);
368}
369
370static void
371_nsloadbuiltin(void)
372{
373
374	/* Do nothing, for now. */
375}
376
377int
378_nsdbtaddsrc(ns_dbt *dbt, const ns_src *src)
379{
380	void		*new;
381	const ns_mod	*mod;
382	ns_mod		 modkey;
383
384	assert(dbt != NULL);
385	assert(src != NULL);
386
387	new = _nsvect_append(src, dbt->srclist, &dbt->srclistsize,
388	    sizeof(*src));
389	if (new == NULL)
390		return (-1);
391	dbt->srclist = new;
392	/* dbt->srclistsize already incremented */
393
394	modkey.name = src->name;
395	mod = bsearch(&modkey, _nsmod, _nsmodsize, sizeof(*_nsmod), _nsmodcmp);
396	if (mod == NULL)
397		return (_nsloadmod(src->name, NULL));
398
399	return (0);
400}
401
402void
403_nsdbtdump(const ns_dbt *dbt)
404{
405	unsigned int	i;
406
407	assert(dbt != NULL);
408
409	printf("%s (%d source%s):", dbt->name, dbt->srclistsize,
410	    dbt->srclistsize == 1 ? "" : "s");
411	for (i = 0; i < dbt->srclistsize; i++) {
412		printf(" %s", dbt->srclist[i].name);
413		if (!(dbt->srclist[i].flags &
414		    (NS_UNAVAIL|NS_NOTFOUND|NS_TRYAGAIN)) &&
415		    (dbt->srclist[i].flags & NS_SUCCESS))
416			continue;
417		printf(" [");
418		if (!(dbt->srclist[i].flags & NS_SUCCESS))
419			printf(" SUCCESS=continue");
420		if (dbt->srclist[i].flags & NS_UNAVAIL)
421			printf(" UNAVAIL=return");
422		if (dbt->srclist[i].flags & NS_NOTFOUND)
423			printf(" NOTFOUND=return");
424		if (dbt->srclist[i].flags & NS_TRYAGAIN)
425			printf(" TRYAGAIN=return");
426		printf(" ]");
427	}
428	printf("\n");
429}
430
431static void
432_nssrclist_free(ns_src **src, u_int srclistsize)
433{
434	u_int	i;
435
436	for (i = 0; i < srclistsize; i++) {
437		if ((*src)[i].name != NULL)
438			free(__UNCONST((*src)[i].name));
439	}
440	free(*src);
441	*src = NULL;
442}
443
444static void
445_nsdbtfree(ns_dbt *dbt)
446{
447
448	_nssrclist_free(&dbt->srclist, dbt->srclistsize);
449	if (dbt->name != NULL)
450		free(__UNCONST(dbt->name));
451}
452
453int
454_nsdbtput(const ns_dbt *dbt)
455{
456	ns_dbt	*p;
457	void	*new;
458	u_int	i;
459
460	assert(dbt != NULL);
461
462	for (i = 0; i < _nsmapsize; i++) {
463		p = _nsvect_elem(i, _nsmap, _nsmapsize, sizeof(*_nsmap));
464		if (strcasecmp(dbt->name, p->name) == 0) {
465					/* overwrite existing entry */
466			if (p->srclist != NULL)
467				_nssrclist_free(&p->srclist, p->srclistsize);
468			memmove(p, dbt, sizeof(*dbt));
469			return (0);
470		}
471	}
472	new = _nsvect_append(dbt, _nsmap, &_nsmapsize, sizeof(*_nsmap));
473	if (new == NULL)
474		return (-1);
475	_nsmap = new;
476	/* _nsmapsize already incremented */
477
478	return (0);
479}
480
481/*
482 * This function is called each time nsdispatch() is called.  If this
483 * is the first call, or if the configuration has changed, (re-)prepare
484 * the global data used by NSS.
485 */
486static int
487_nsconfigure(void)
488{
489#ifdef _REENTRANT
490	static pthread_mutex_t	_nsconflock = PTHREAD_MUTEX_INITIALIZER;
491#endif
492	static time_t	_nsconfmod;
493	struct stat	statbuf;
494
495	pthread_mutex_lock(&_nsconflock);
496
497	if (stat(_PATH_NS_CONF, &statbuf) == -1) {
498		/*
499		 * No nsswitch.conf; just use whatever configuration we
500		 * currently have, or fall back on the defaults specified
501		 * by the caller.
502		 */
503		pthread_mutex_unlock(&_nsconflock);
504		return (0);
505	}
506
507	if (statbuf.st_mtime <= _nsconfmod) {
508		/* Internal state is up-to-date with nsswitch.conf. */
509		pthread_mutex_unlock(&_nsconflock);
510		return (0);
511	}
512
513	/*
514	 * Ok, we've decided we need to update the nsswitch configuration
515	 * structures.  Acquire a write-lock on _nslock while continuing
516	 * to hold _nsconflock.  Acquiring a write-lock blocks while
517	 * waiting for other threads already holding a read-lock to clear.
518	 * We hold _nsconflock for the duration, and update the time stamp
519	 * at the end of the update operation, at which time we release
520	 * both locks.
521	 */
522	pthread_rwlock_wrlock(&_nslock);
523
524	_nsyyin = fopen(_PATH_NS_CONF, "r");
525	if (_nsyyin == NULL) {
526		/*
527		 * Unable to open nsswitch.conf; behave as though the
528		 * stat() above failed.  Even though we have already
529		 * updated _nsconfmod, if the file reappears, the
530		 * mtime will change.
531		 */
532		goto out;
533	}
534
535	_NSVECT_FREE(_nsmap, &_nsmapsize, sizeof(*_nsmap),
536	    (_nsvect_free_elem) _nsdbtfree);
537	_NSVECT_FREE(_nsmod, &_nsmodsize, sizeof(*_nsmod),
538	    (_nsvect_free_elem) _nsmodfree);
539
540	_nsloadbuiltin();
541
542	_nsyyparse();
543	(void) fclose(_nsyyin);
544	if (_nsmapsize != 0)
545		qsort(_nsmap, _nsmapsize, sizeof(*_nsmap), _nsdbtcmp);
546
547	_nsconfmod = statbuf.st_mtime;
548
549 out:
550	pthread_rwlock_unlock(&_nslock);
551	pthread_mutex_unlock(&_nsconflock);
552	return (0);
553}
554
555static nss_method
556_nsmethod(const char *source, const char *database, const char *method,
557    const ns_dtab disp_tab[], void **cb_data)
558{
559	int	curdisp;
560	ns_mod	*mod, modkey;
561	ns_mtab	*mtab, mtabkey;
562
563	if (disp_tab != NULL) {
564		for (curdisp = 0; disp_tab[curdisp].src != NULL; curdisp++) {
565			if (strcasecmp(source, disp_tab[curdisp].src) == 0) {
566				*cb_data = disp_tab[curdisp].cb_data;
567				return (disp_tab[curdisp].callback);
568			}
569		}
570	}
571
572	modkey.name = source;
573	mod = bsearch(&modkey, _nsmod, _nsmodsize, sizeof(*_nsmod),
574	    _nsmodcmp);
575	if (mod != NULL && mod->handle != NULL) {
576		mtabkey.database = database;
577		mtabkey.name = method;
578		mtab = bsearch(&mtabkey, mod->mtab, mod->mtabsize,
579		    sizeof(mod->mtab[0]), _nsmtabcmp);
580		if (mtab != NULL) {
581			*cb_data = mtab->mdata;
582			return (mtab->method);
583		}
584	}
585
586	*cb_data = NULL;
587	return (NULL);
588}
589
590int
591/*ARGSUSED*/
592nsdispatch(void *retval, const ns_dtab disp_tab[], const char *database,
593	    const char *method, const ns_src defaults[], ...)
594{
595	static int	 _nsdispatching;
596#ifdef _REENTRANT
597	struct _ns_drec	 drec, *ldrec;
598#endif
599	va_list		 ap;
600	int		 i, result;
601	ns_dbt		 key;
602	const ns_dbt	*dbt;
603	const ns_src	*srclist;
604	int		 srclistsize;
605	nss_method	 cb;
606	void		*cb_data;
607
608	/* retval may be NULL */
609	/* disp_tab may be NULL */
610	assert(database != NULL);
611	assert(method != NULL);
612	assert(defaults != NULL);
613	if (database == NULL || method == NULL || defaults == NULL)
614		return (NS_UNAVAIL);
615
616	pthread_once(&_nslockinit, lockinit);
617
618	/*
619	 * In both the threaded and non-threaded cases, avoid reloading
620	 * the configuration if the current thread is already running
621	 * nsdispatch() (i.e. recursive call).
622	 *
623	 * In the non-threaded case, this avoids changing the data structures
624	 * while we're using them.
625	 *
626	 * In the threaded case, this avoids trying to take a write lock
627	 * while the current thread holds a read lock (which would result
628	 * in deadlock).
629	 */
630#ifdef _REENTRANT
631	if (__isthreaded) {
632		drec.thr = pthread_self();
633		pthread_mutex_lock(&_ns_drec_lock);
634		LIST_FOREACH(ldrec, &_ns_drec, list) {
635			if (ldrec->thr == drec.thr)
636				break;
637		}
638		LIST_INSERT_HEAD(&_ns_drec, &drec, list);
639		pthread_mutex_unlock(&_ns_drec_lock);
640		if (ldrec == NULL && _nsconfigure()) {
641			pthread_mutex_lock(&_ns_drec_lock);
642			LIST_REMOVE(&drec, list);
643			pthread_mutex_unlock(&_ns_drec_lock);
644			return (NS_UNAVAIL);
645		}
646	} else
647#endif /* _REENTRANT */
648
649	if (_nsdispatching++ == 0 && _nsconfigure()) {
650		_nsdispatching--;
651		return (NS_UNAVAIL);
652	}
653
654	pthread_rwlock_rdlock(&_nslock);
655
656	key.name = database;
657	dbt = bsearch(&key, _nsmap, _nsmapsize, sizeof(*_nsmap), _nsdbtcmp);
658	if (dbt != NULL) {
659		srclist = dbt->srclist;
660		srclistsize = dbt->srclistsize;
661	} else {
662		srclist = defaults;
663		srclistsize = 0;
664		while (srclist[srclistsize].name != NULL)
665			srclistsize++;
666	}
667	result = 0;
668
669	for (i = 0; i < srclistsize; i++) {
670		cb = _nsmethod(srclist[i].name, database, method,
671		    disp_tab, &cb_data);
672		result = 0;
673		if (cb != NULL) {
674			va_start(ap, defaults);
675			result = (*cb)(retval, cb_data, ap);
676			va_end(ap);
677			if (defaults[0].flags & NS_FORCEALL)
678				continue;
679			if (result & srclist[i].flags)
680				break;
681		}
682	}
683	result &= NS_STATUSMASK;	/* clear private flags in result */
684
685	pthread_rwlock_unlock(&_nslock);
686
687#ifdef _REENTRANT
688	if (__isthreaded) {
689		pthread_mutex_lock(&_ns_drec_lock);
690		LIST_REMOVE(&drec, list);
691		pthread_mutex_unlock(&_ns_drec_lock);
692	} else
693#endif /* _REENTRANT */
694		_nsdispatching--;
695
696	return (result ? result : NS_NOTFOUND);
697}
698