1 /* $NetBSD: dlz_dlopen_driver.c,v 1.6 2014/12/10 04:37:52 christos Exp $ */ 2 3 /* 4 * Copyright (C) 2011-2014 Internet Systems Consortium, Inc. ("ISC") 5 * 6 * Permission to use, copy, modify, and/or distribute this software for any 7 * purpose with or without fee is hereby granted, provided that the above 8 * copyright notice and this permission notice appear in all copies. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH 11 * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 12 * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, 13 * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 14 * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE 15 * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 16 * PERFORMANCE OF THIS SOFTWARE. 17 */ 18 19 /* Id */ 20 21 #include <config.h> 22 23 #include <stdio.h> 24 #include <string.h> 25 #include <stdlib.h> 26 #include <dlfcn.h> 27 28 #include <dns/log.h> 29 #include <dns/result.h> 30 #include <dns/dlz_dlopen.h> 31 32 #include <isc/mem.h> 33 #include <isc/print.h> 34 #include <isc/result.h> 35 #include <isc/util.h> 36 37 #include <named/globals.h> 38 39 #include <dlz/dlz_dlopen_driver.h> 40 41 #ifdef ISC_DLZ_DLOPEN 42 static dns_sdlzimplementation_t *dlz_dlopen = NULL; 43 44 45 typedef struct dlopen_data { 46 isc_mem_t *mctx; 47 char *dl_path; 48 char *dlzname; 49 void *dl_handle; 50 void *dbdata; 51 unsigned int flags; 52 isc_mutex_t lock; 53 int version; 54 isc_boolean_t in_configure; 55 56 dlz_dlopen_version_t *dlz_version; 57 dlz_dlopen_create_t *dlz_create; 58 dlz_dlopen_findzonedb_t *dlz_findzonedb; 59 dlz_dlopen_lookup_t *dlz_lookup; 60 dlz_dlopen_authority_t *dlz_authority; 61 dlz_dlopen_allnodes_t *dlz_allnodes; 62 dlz_dlopen_allowzonexfr_t *dlz_allowzonexfr; 63 dlz_dlopen_newversion_t *dlz_newversion; 64 dlz_dlopen_closeversion_t *dlz_closeversion; 65 dlz_dlopen_configure_t *dlz_configure; 66 dlz_dlopen_ssumatch_t *dlz_ssumatch; 67 dlz_dlopen_addrdataset_t *dlz_addrdataset; 68 dlz_dlopen_subrdataset_t *dlz_subrdataset; 69 dlz_dlopen_delrdataset_t *dlz_delrdataset; 70 dlz_dlopen_destroy_t *dlz_destroy; 71 } dlopen_data_t; 72 73 /* Modules can choose whether they are lock-safe or not. */ 74 #define MAYBE_LOCK(cd) \ 75 do { \ 76 if ((cd->flags & DNS_SDLZFLAG_THREADSAFE) == 0 && \ 77 cd->in_configure == ISC_FALSE) \ 78 LOCK(&cd->lock); \ 79 } while (/*CONSTCOND*/0) 80 81 #define MAYBE_UNLOCK(cd) \ 82 do { \ 83 if ((cd->flags & DNS_SDLZFLAG_THREADSAFE) == 0 && \ 84 cd->in_configure == ISC_FALSE) \ 85 UNLOCK(&cd->lock); \ 86 } while (0) 87 88 /* 89 * Log a message at the given level. 90 */ 91 static void dlopen_log(int level, const char *fmt, ...) 92 { 93 va_list ap; 94 va_start(ap, fmt); 95 isc_log_vwrite(dns_lctx, DNS_LOGCATEGORY_DATABASE, 96 DNS_LOGMODULE_DLZ, ISC_LOG_DEBUG(level), 97 fmt, ap); 98 va_end(ap); 99 } 100 101 /* 102 * SDLZ methods 103 */ 104 105 static isc_result_t 106 dlopen_dlz_allnodes(const char *zone, void *driverarg, void *dbdata, 107 dns_sdlzallnodes_t *allnodes) 108 { 109 dlopen_data_t *cd = (dlopen_data_t *) dbdata; 110 isc_result_t result; 111 112 113 UNUSED(driverarg); 114 115 if (cd->dlz_allnodes == NULL) { 116 return (ISC_R_NOPERM); 117 } 118 119 MAYBE_LOCK(cd); 120 result = cd->dlz_allnodes(zone, cd->dbdata, allnodes); 121 MAYBE_UNLOCK(cd); 122 return (result); 123 } 124 125 126 static isc_result_t 127 dlopen_dlz_allowzonexfr(void *driverarg, void *dbdata, const char *name, 128 const char *client) 129 { 130 dlopen_data_t *cd = (dlopen_data_t *) dbdata; 131 isc_result_t result; 132 133 UNUSED(driverarg); 134 135 136 if (cd->dlz_allowzonexfr == NULL) { 137 return (ISC_R_NOPERM); 138 } 139 140 MAYBE_LOCK(cd); 141 result = cd->dlz_allowzonexfr(cd->dbdata, name, client); 142 MAYBE_UNLOCK(cd); 143 return (result); 144 } 145 146 static isc_result_t 147 dlopen_dlz_authority(const char *zone, void *driverarg, void *dbdata, 148 dns_sdlzlookup_t *lookup) 149 { 150 dlopen_data_t *cd = (dlopen_data_t *) dbdata; 151 isc_result_t result; 152 153 UNUSED(driverarg); 154 155 if (cd->dlz_authority == NULL) { 156 return (ISC_R_NOTIMPLEMENTED); 157 } 158 159 MAYBE_LOCK(cd); 160 result = cd->dlz_authority(zone, cd->dbdata, lookup); 161 MAYBE_UNLOCK(cd); 162 return (result); 163 } 164 165 static isc_result_t 166 dlopen_dlz_findzonedb(void *driverarg, void *dbdata, const char *name, 167 dns_clientinfomethods_t *methods, 168 dns_clientinfo_t *clientinfo) 169 { 170 dlopen_data_t *cd = (dlopen_data_t *) dbdata; 171 isc_result_t result; 172 173 UNUSED(driverarg); 174 175 MAYBE_LOCK(cd); 176 result = cd->dlz_findzonedb(cd->dbdata, name, methods, clientinfo); 177 MAYBE_UNLOCK(cd); 178 return (result); 179 } 180 181 182 static isc_result_t 183 dlopen_dlz_lookup(const char *zone, const char *name, void *driverarg, 184 void *dbdata, dns_sdlzlookup_t *lookup, 185 dns_clientinfomethods_t *methods, 186 dns_clientinfo_t *clientinfo) 187 { 188 dlopen_data_t *cd = (dlopen_data_t *) dbdata; 189 isc_result_t result; 190 191 UNUSED(driverarg); 192 193 MAYBE_LOCK(cd); 194 result = cd->dlz_lookup(zone, name, cd->dbdata, lookup, 195 methods, clientinfo); 196 MAYBE_UNLOCK(cd); 197 return (result); 198 } 199 200 /* 201 * Load a symbol from the library 202 */ 203 static void * 204 dl_load_symbol(dlopen_data_t *cd, const char *symbol, isc_boolean_t mandatory) { 205 void *ptr = dlsym(cd->dl_handle, symbol); 206 if (ptr == NULL && mandatory) { 207 dlopen_log(ISC_LOG_ERROR, 208 "dlz_dlopen: library '%s' is missing " 209 "required symbol '%s'", cd->dl_path, symbol); 210 } 211 return (ptr); 212 } 213 214 /* 215 * Called at startup for each dlopen zone in named.conf 216 */ 217 static isc_result_t 218 dlopen_dlz_create(const char *dlzname, unsigned int argc, char *argv[], 219 void *driverarg, void **dbdata) 220 { 221 dlopen_data_t *cd; 222 isc_mem_t *mctx = NULL; 223 isc_result_t result = ISC_R_FAILURE; 224 int dlopen_flags = 0; 225 226 UNUSED(driverarg); 227 228 if (argc < 2) { 229 dlopen_log(ISC_LOG_ERROR, 230 "dlz_dlopen driver for '%s' needs a path to " 231 "the shared library", dlzname); 232 return (ISC_R_FAILURE); 233 } 234 235 result = isc_mem_create(0, 0, &mctx); 236 if (result != ISC_R_SUCCESS) 237 return (result); 238 239 cd = isc_mem_get(mctx, sizeof(*cd)); 240 if (cd == NULL) { 241 isc_mem_destroy(&mctx); 242 return (ISC_R_NOMEMORY); 243 } 244 memset(cd, 0, sizeof(*cd)); 245 246 cd->mctx = mctx; 247 248 cd->dl_path = isc_mem_strdup(cd->mctx, argv[1]); 249 if (cd->dl_path == NULL) { 250 result = ISC_R_NOMEMORY; 251 goto failed; 252 } 253 254 cd->dlzname = isc_mem_strdup(cd->mctx, dlzname); 255 if (cd->dlzname == NULL) { 256 result = ISC_R_NOMEMORY; 257 goto failed; 258 } 259 260 /* Initialize the lock */ 261 result = isc_mutex_init(&cd->lock); 262 if (result != ISC_R_SUCCESS) 263 goto failed; 264 265 /* Open the library */ 266 dlopen_flags = RTLD_NOW|RTLD_GLOBAL; 267 268 #ifdef RTLD_DEEPBIND 269 /* 270 * If RTLD_DEEPBIND is available then use it. This can avoid 271 * issues with a module using a different version of a system 272 * library than one that bind9 uses. For example, bind9 may link 273 * to MIT kerberos, but the module may use Heimdal. If we don't 274 * use RTLD_DEEPBIND then we could end up with Heimdal functions 275 * calling MIT functions, which leads to bizarre results (usually 276 * a segfault). 277 */ 278 dlopen_flags |= RTLD_DEEPBIND; 279 #endif 280 281 cd->dl_handle = dlopen(cd->dl_path, dlopen_flags); 282 if (cd->dl_handle == NULL) { 283 dlopen_log(ISC_LOG_ERROR, 284 "dlz_dlopen failed to open library '%s' - %s", 285 cd->dl_path, dlerror()); 286 result = ISC_R_FAILURE; 287 goto failed; 288 } 289 290 /* Find the symbols */ 291 cd->dlz_version = (dlz_dlopen_version_t *) 292 dl_load_symbol(cd, "dlz_version", ISC_TRUE); 293 cd->dlz_create = (dlz_dlopen_create_t *) 294 dl_load_symbol(cd, "dlz_create", ISC_TRUE); 295 cd->dlz_lookup = (dlz_dlopen_lookup_t *) 296 dl_load_symbol(cd, "dlz_lookup", ISC_TRUE); 297 cd->dlz_findzonedb = (dlz_dlopen_findzonedb_t *) 298 dl_load_symbol(cd, "dlz_findzonedb", ISC_TRUE); 299 300 if (cd->dlz_create == NULL || 301 cd->dlz_version == NULL || 302 cd->dlz_lookup == NULL || 303 cd->dlz_findzonedb == NULL) 304 { 305 /* We're missing a required symbol */ 306 result = ISC_R_FAILURE; 307 goto failed; 308 } 309 310 cd->dlz_allowzonexfr = (dlz_dlopen_allowzonexfr_t *) 311 dl_load_symbol(cd, "dlz_allowzonexfr", ISC_FALSE); 312 cd->dlz_allnodes = (dlz_dlopen_allnodes_t *) 313 dl_load_symbol(cd, "dlz_allnodes", 314 ISC_TF(cd->dlz_allowzonexfr != NULL)); 315 cd->dlz_authority = (dlz_dlopen_authority_t *) 316 dl_load_symbol(cd, "dlz_authority", ISC_FALSE); 317 cd->dlz_newversion = (dlz_dlopen_newversion_t *) 318 dl_load_symbol(cd, "dlz_newversion", ISC_FALSE); 319 cd->dlz_closeversion = (dlz_dlopen_closeversion_t *) 320 dl_load_symbol(cd, "dlz_closeversion", 321 ISC_TF(cd->dlz_newversion != NULL)); 322 cd->dlz_configure = (dlz_dlopen_configure_t *) 323 dl_load_symbol(cd, "dlz_configure", ISC_FALSE); 324 cd->dlz_ssumatch = (dlz_dlopen_ssumatch_t *) 325 dl_load_symbol(cd, "dlz_ssumatch", ISC_FALSE); 326 cd->dlz_addrdataset = (dlz_dlopen_addrdataset_t *) 327 dl_load_symbol(cd, "dlz_addrdataset", ISC_FALSE); 328 cd->dlz_subrdataset = (dlz_dlopen_subrdataset_t *) 329 dl_load_symbol(cd, "dlz_subrdataset", ISC_FALSE); 330 cd->dlz_delrdataset = (dlz_dlopen_delrdataset_t *) 331 dl_load_symbol(cd, "dlz_delrdataset", ISC_FALSE); 332 cd->dlz_destroy = (dlz_dlopen_destroy_t *) 333 dl_load_symbol(cd, "dlz_destroy", ISC_FALSE); 334 335 /* Check the version of the API is the same */ 336 cd->version = cd->dlz_version(&cd->flags); 337 if (cd->version < (DLZ_DLOPEN_VERSION - DLZ_DLOPEN_AGE) || 338 cd->version > DLZ_DLOPEN_VERSION) 339 { 340 dlopen_log(ISC_LOG_ERROR, 341 "dlz_dlopen: %s: incorrect driver API version %d, " 342 "requires %d", 343 cd->dl_path, cd->version, DLZ_DLOPEN_VERSION); 344 result = ISC_R_FAILURE; 345 goto failed; 346 } 347 348 /* 349 * Call the library's create function. Note that this is an 350 * extended version of dlz create, with the addition of 351 * named function pointers for helper functions that the 352 * driver will need. This avoids the need for the backend to 353 * link the BIND9 libraries 354 */ 355 MAYBE_LOCK(cd); 356 result = cd->dlz_create(dlzname, argc-1, argv+1, 357 &cd->dbdata, 358 "log", dlopen_log, 359 "putrr", dns_sdlz_putrr, 360 "putnamedrr", dns_sdlz_putnamedrr, 361 "writeable_zone", dns_dlz_writeablezone, 362 NULL); 363 MAYBE_UNLOCK(cd); 364 if (result != ISC_R_SUCCESS) 365 goto failed; 366 367 *dbdata = cd; 368 369 return (ISC_R_SUCCESS); 370 371 failed: 372 dlopen_log(ISC_LOG_ERROR, "dlz_dlopen of '%s' failed", dlzname); 373 if (cd->dl_path != NULL) 374 isc_mem_free(mctx, cd->dl_path); 375 if (cd->dlzname != NULL) 376 isc_mem_free(mctx, cd->dlzname); 377 if (dlopen_flags != 0) 378 (void) isc_mutex_destroy(&cd->lock); 379 #ifdef HAVE_DLCLOSE 380 if (cd->dl_handle) 381 dlclose(cd->dl_handle); 382 #endif 383 isc_mem_put(mctx, cd, sizeof(*cd)); 384 isc_mem_destroy(&mctx); 385 return (result); 386 } 387 388 /* 389 * Called when bind is shutting down 390 */ 391 static void 392 dlopen_dlz_destroy(void *driverarg, void *dbdata) { 393 dlopen_data_t *cd = (dlopen_data_t *) dbdata; 394 isc_mem_t *mctx; 395 396 UNUSED(driverarg); 397 398 if (cd->dlz_destroy) { 399 MAYBE_LOCK(cd); 400 cd->dlz_destroy(cd->dbdata); 401 MAYBE_UNLOCK(cd); 402 } 403 404 if (cd->dl_path) 405 isc_mem_free(cd->mctx, cd->dl_path); 406 if (cd->dlzname) 407 isc_mem_free(cd->mctx, cd->dlzname); 408 409 #ifdef HAVE_DLCLOSE 410 if (cd->dl_handle) 411 dlclose(cd->dl_handle); 412 #endif 413 414 (void) isc_mutex_destroy(&cd->lock); 415 416 mctx = cd->mctx; 417 isc_mem_put(mctx, cd, sizeof(*cd)); 418 isc_mem_destroy(&mctx); 419 } 420 421 /* 422 * Called to start a transaction 423 */ 424 static isc_result_t 425 dlopen_dlz_newversion(const char *zone, void *driverarg, void *dbdata, 426 void **versionp) 427 { 428 dlopen_data_t *cd = (dlopen_data_t *) dbdata; 429 isc_result_t result; 430 431 UNUSED(driverarg); 432 433 if (cd->dlz_newversion == NULL) 434 return (ISC_R_NOTIMPLEMENTED); 435 436 MAYBE_LOCK(cd); 437 result = cd->dlz_newversion(zone, cd->dbdata, versionp); 438 MAYBE_UNLOCK(cd); 439 return (result); 440 } 441 442 /* 443 * Called to end a transaction 444 */ 445 static void 446 dlopen_dlz_closeversion(const char *zone, isc_boolean_t commit, 447 void *driverarg, void *dbdata, void **versionp) 448 { 449 dlopen_data_t *cd = (dlopen_data_t *) dbdata; 450 451 UNUSED(driverarg); 452 453 if (cd->dlz_newversion == NULL) { 454 *versionp = NULL; 455 return; 456 } 457 458 MAYBE_LOCK(cd); 459 cd->dlz_closeversion(zone, commit, cd->dbdata, versionp); 460 MAYBE_UNLOCK(cd); 461 } 462 463 /* 464 * Called on startup to configure any writeable zones 465 */ 466 static isc_result_t 467 dlopen_dlz_configure(dns_view_t *view, dns_dlzdb_t *dlzdb, 468 void *driverarg, void *dbdata) 469 { 470 dlopen_data_t *cd = (dlopen_data_t *) dbdata; 471 isc_result_t result; 472 473 UNUSED(driverarg); 474 475 if (cd->dlz_configure == NULL) 476 return (ISC_R_SUCCESS); 477 478 MAYBE_LOCK(cd); 479 cd->in_configure = ISC_TRUE; 480 result = cd->dlz_configure(view, dlzdb, cd->dbdata); 481 cd->in_configure = ISC_FALSE; 482 MAYBE_UNLOCK(cd); 483 484 return (result); 485 } 486 487 488 /* 489 * Check for authority to change a name 490 */ 491 static isc_boolean_t 492 dlopen_dlz_ssumatch(const char *signer, const char *name, const char *tcpaddr, 493 const char *type, const char *key, isc_uint32_t keydatalen, 494 unsigned char *keydata, void *driverarg, void *dbdata) 495 { 496 dlopen_data_t *cd = (dlopen_data_t *) dbdata; 497 isc_boolean_t ret; 498 499 UNUSED(driverarg); 500 501 if (cd->dlz_ssumatch == NULL) 502 return (ISC_FALSE); 503 504 MAYBE_LOCK(cd); 505 ret = cd->dlz_ssumatch(signer, name, tcpaddr, type, key, keydatalen, 506 keydata, cd->dbdata); 507 MAYBE_UNLOCK(cd); 508 509 return (ret); 510 } 511 512 513 /* 514 * Add an rdataset 515 */ 516 static isc_result_t 517 dlopen_dlz_addrdataset(const char *name, const char *rdatastr, 518 void *driverarg, void *dbdata, void *version) 519 { 520 dlopen_data_t *cd = (dlopen_data_t *) dbdata; 521 isc_result_t result; 522 523 UNUSED(driverarg); 524 525 if (cd->dlz_addrdataset == NULL) 526 return (ISC_R_NOTIMPLEMENTED); 527 528 MAYBE_LOCK(cd); 529 result = cd->dlz_addrdataset(name, rdatastr, cd->dbdata, version); 530 MAYBE_UNLOCK(cd); 531 532 return (result); 533 } 534 535 /* 536 * Subtract an rdataset 537 */ 538 static isc_result_t 539 dlopen_dlz_subrdataset(const char *name, const char *rdatastr, 540 void *driverarg, void *dbdata, void *version) 541 { 542 dlopen_data_t *cd = (dlopen_data_t *) dbdata; 543 isc_result_t result; 544 545 UNUSED(driverarg); 546 547 if (cd->dlz_subrdataset == NULL) 548 return (ISC_R_NOTIMPLEMENTED); 549 550 MAYBE_LOCK(cd); 551 result = cd->dlz_subrdataset(name, rdatastr, cd->dbdata, version); 552 MAYBE_UNLOCK(cd); 553 554 return (result); 555 } 556 557 /* 558 delete a rdataset 559 */ 560 static isc_result_t 561 dlopen_dlz_delrdataset(const char *name, const char *type, 562 void *driverarg, void *dbdata, void *version) 563 { 564 dlopen_data_t *cd = (dlopen_data_t *) dbdata; 565 isc_result_t result; 566 567 UNUSED(driverarg); 568 569 if (cd->dlz_delrdataset == NULL) 570 return (ISC_R_NOTIMPLEMENTED); 571 572 MAYBE_LOCK(cd); 573 result = cd->dlz_delrdataset(name, type, cd->dbdata, version); 574 MAYBE_UNLOCK(cd); 575 576 return (result); 577 } 578 579 580 static dns_sdlzmethods_t dlz_dlopen_methods = { 581 dlopen_dlz_create, 582 dlopen_dlz_destroy, 583 dlopen_dlz_findzonedb, 584 dlopen_dlz_lookup, 585 dlopen_dlz_authority, 586 dlopen_dlz_allnodes, 587 dlopen_dlz_allowzonexfr, 588 dlopen_dlz_newversion, 589 dlopen_dlz_closeversion, 590 dlopen_dlz_configure, 591 dlopen_dlz_ssumatch, 592 dlopen_dlz_addrdataset, 593 dlopen_dlz_subrdataset, 594 dlopen_dlz_delrdataset 595 }; 596 #endif 597 598 /* 599 * Register driver with BIND 600 */ 601 isc_result_t 602 dlz_dlopen_init(isc_mem_t *mctx) { 603 #ifndef ISC_DLZ_DLOPEN 604 UNUSED(mctx); 605 return (ISC_R_NOTIMPLEMENTED); 606 #else 607 isc_result_t result; 608 609 dlopen_log(2, "Registering DLZ_dlopen driver"); 610 611 result = dns_sdlzregister("dlopen", &dlz_dlopen_methods, NULL, 612 DNS_SDLZFLAG_RELATIVEOWNER | 613 DNS_SDLZFLAG_RELATIVERDATA | 614 DNS_SDLZFLAG_THREADSAFE, 615 mctx, &dlz_dlopen); 616 617 if (result != ISC_R_SUCCESS) { 618 UNEXPECTED_ERROR(__FILE__, __LINE__, 619 "dns_sdlzregister() failed: %s", 620 isc_result_totext(result)); 621 result = ISC_R_UNEXPECTED; 622 } 623 624 return (result); 625 #endif 626 } 627 628 629 /* 630 * Unregister the driver 631 */ 632 void 633 dlz_dlopen_clear(void) { 634 #ifdef ISC_DLZ_DLOPEN 635 dlopen_log(2, "Unregistering DLZ_dlopen driver"); 636 if (dlz_dlopen != NULL) 637 dns_sdlzunregister(&dlz_dlopen); 638 #endif 639 } 640