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