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