1 /* $NetBSD: dlz.c,v 1.9 2025/01/26 16:25:22 christos Exp $ */ 2 3 /* 4 * Copyright (C) Internet Systems Consortium, Inc. ("ISC") 5 * 6 * SPDX-License-Identifier: MPL-2.0 AND ISC 7 * 8 * This Source Code Form is subject to the terms of the Mozilla Public 9 * License, v. 2.0. If a copy of the MPL was not distributed with this 10 * file, you can obtain one at https://mozilla.org/MPL/2.0/. 11 * 12 * See the COPYRIGHT file distributed with this work for additional 13 * information regarding copyright ownership. 14 */ 15 16 /* 17 * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl. 18 * 19 * Permission to use, copy, modify, and distribute this software for any 20 * purpose with or without fee is hereby granted, provided that the 21 * above copyright notice and this permission notice appear in all 22 * copies. 23 * 24 * THE SOFTWARE IS PROVIDED "AS IS" AND STICHTING NLNET 25 * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL 26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL 27 * STICHTING NLNET BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR 28 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS 29 * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE 30 * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE 31 * USE OR PERFORMANCE OF THIS SOFTWARE. 32 * 33 * The development of Dynamically Loadable Zones (DLZ) for Bind 9 was 34 * conceived and contributed by Rob Butler. 35 * 36 * Permission to use, copy, modify, and distribute this software for any 37 * purpose with or without fee is hereby granted, provided that the 38 * above copyright notice and this permission notice appear in all 39 * copies. 40 * 41 * THE SOFTWARE IS PROVIDED "AS IS" AND ROB BUTLER 42 * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL 43 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL 44 * ROB BUTLER BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR 45 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS 46 * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE 47 * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE 48 * USE OR PERFORMANCE OF THIS SOFTWARE. 49 */ 50 51 /*! \file */ 52 53 /*** 54 *** Imports 55 ***/ 56 57 #include <stdbool.h> 58 59 #include <isc/buffer.h> 60 #include <isc/commandline.h> 61 #include <isc/magic.h> 62 #include <isc/mem.h> 63 #include <isc/netmgr.h> 64 #include <isc/once.h> 65 #include <isc/random.h> 66 #include <isc/rwlock.h> 67 #include <isc/string.h> 68 #include <isc/util.h> 69 70 #include <dns/db.h> 71 #include <dns/dlz.h> 72 #include <dns/fixedname.h> 73 #include <dns/log.h> 74 #include <dns/master.h> 75 #include <dns/ssu.h> 76 #include <dns/zone.h> 77 78 /*** 79 *** Supported DLZ DB Implementations Registry 80 ***/ 81 82 static ISC_LIST(dns_dlzimplementation_t) dlz_implementations; 83 static isc_rwlock_t dlz_implock; 84 static isc_once_t once = ISC_ONCE_INIT; 85 86 static void 87 dlz_initialize(void) { 88 isc_rwlock_init(&dlz_implock); 89 ISC_LIST_INIT(dlz_implementations); 90 } 91 92 /*% 93 * Searches the dlz_implementations list for a driver matching name. 94 */ 95 static dns_dlzimplementation_t * 96 dlz_impfind(const char *name) { 97 dns_dlzimplementation_t *imp; 98 99 for (imp = ISC_LIST_HEAD(dlz_implementations); imp != NULL; 100 imp = ISC_LIST_NEXT(imp, link)) 101 { 102 if (strcasecmp(name, imp->name) == 0) { 103 return imp; 104 } 105 } 106 return NULL; 107 } 108 109 /*** 110 *** Basic DLZ Methods 111 ***/ 112 113 isc_result_t 114 dns_dlzallowzonexfr(dns_view_t *view, const dns_name_t *name, 115 const isc_sockaddr_t *clientaddr, dns_db_t **dbp) { 116 isc_result_t result = ISC_R_NOTFOUND; 117 dns_dlzallowzonexfr_t allowzonexfr; 118 dns_dlzdb_t *dlzdb; 119 120 /* 121 * Performs checks to make sure data is as we expect it to be. 122 */ 123 REQUIRE(name != NULL); 124 REQUIRE(dbp != NULL && *dbp == NULL); 125 126 /* 127 * Find a driver in which the zone exists and transfer is supported 128 */ 129 for (dlzdb = ISC_LIST_HEAD(view->dlz_searched); dlzdb != NULL; 130 dlzdb = ISC_LIST_NEXT(dlzdb, link)) 131 { 132 REQUIRE(DNS_DLZ_VALID(dlzdb)); 133 134 allowzonexfr = dlzdb->implementation->methods->allowzonexfr; 135 result = (*allowzonexfr)(dlzdb->implementation->driverarg, 136 dlzdb->dbdata, dlzdb->mctx, 137 view->rdclass, name, clientaddr, dbp); 138 139 /* 140 * In these cases, we found the right database. Non-success 141 * result codes indicate the zone might not transfer. 142 */ 143 switch (result) { 144 case ISC_R_SUCCESS: 145 case ISC_R_NOPERM: 146 case ISC_R_DEFAULT: 147 return result; 148 default: 149 break; 150 } 151 } 152 153 if (result == ISC_R_NOTIMPLEMENTED) { 154 result = ISC_R_NOTFOUND; 155 } 156 157 return result; 158 } 159 160 isc_result_t 161 dns_dlzcreate(isc_mem_t *mctx, const char *dlzname, const char *drivername, 162 unsigned int argc, char *argv[], dns_dlzdb_t **dbp) { 163 dns_dlzimplementation_t *impinfo; 164 isc_result_t result; 165 dns_dlzdb_t *db = NULL; 166 167 /* 168 * initialize the dlz_implementations list, this is guaranteed 169 * to only really happen once. 170 */ 171 isc_once_do(&once, dlz_initialize); 172 173 /* 174 * Performs checks to make sure data is as we expect it to be. 175 */ 176 REQUIRE(dbp != NULL && *dbp == NULL); 177 REQUIRE(dlzname != NULL); 178 REQUIRE(drivername != NULL); 179 REQUIRE(mctx != NULL); 180 181 /* write log message */ 182 isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DLZ, 183 ISC_LOG_INFO, "Loading '%s' using driver %s", dlzname, 184 drivername); 185 186 /* lock the dlz_implementations list so we can search it. */ 187 RWLOCK(&dlz_implock, isc_rwlocktype_read); 188 189 /* search for the driver implementation */ 190 impinfo = dlz_impfind(drivername); 191 if (impinfo == NULL) { 192 RWUNLOCK(&dlz_implock, isc_rwlocktype_read); 193 194 isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, 195 DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, 196 "unsupported DLZ database driver '%s'." 197 " %s not loaded.", 198 drivername, dlzname); 199 200 return ISC_R_NOTFOUND; 201 } 202 203 /* Allocate memory to hold the DLZ database driver */ 204 db = isc_mem_get(mctx, sizeof(*db)); 205 *db = (dns_dlzdb_t){ 206 .implementation = impinfo, 207 }; 208 209 ISC_LINK_INIT(db, link); 210 if (dlzname != NULL) { 211 db->dlzname = isc_mem_strdup(mctx, dlzname); 212 } 213 214 /* Create a new database using implementation 'drivername'. */ 215 result = ((impinfo->methods->create)(mctx, dlzname, argc, argv, 216 impinfo->driverarg, &db->dbdata)); 217 218 RWUNLOCK(&dlz_implock, isc_rwlocktype_read); 219 /* mark the DLZ driver as valid */ 220 if (result != ISC_R_SUCCESS) { 221 goto failure; 222 } 223 224 db->magic = DNS_DLZ_MAGIC; 225 isc_mem_attach(mctx, &db->mctx); 226 isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DLZ, 227 ISC_LOG_DEBUG(2), "DLZ driver loaded successfully."); 228 *dbp = db; 229 return ISC_R_SUCCESS; 230 failure: 231 isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DLZ, 232 ISC_LOG_ERROR, "DLZ driver failed to load."); 233 234 /* impinfo->methods->create failed. */ 235 isc_mem_free(mctx, db->dlzname); 236 isc_mem_put(mctx, db, sizeof(*db)); 237 return result; 238 } 239 240 void 241 dns_dlzdestroy(dns_dlzdb_t **dbp) { 242 dns_dlzdestroy_t destroy; 243 dns_dlzdb_t *db; 244 245 /* Write debugging message to log */ 246 isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DLZ, 247 ISC_LOG_DEBUG(2), "Unloading DLZ driver."); 248 249 /* 250 * Perform checks to make sure data is as we expect it to be. 251 */ 252 REQUIRE(dbp != NULL && DNS_DLZ_VALID(*dbp)); 253 254 db = *dbp; 255 *dbp = NULL; 256 257 if (db->ssutable != NULL) { 258 dns_ssutable_detach(&db->ssutable); 259 } 260 261 /* call the drivers destroy method */ 262 if (db->dlzname != NULL) { 263 isc_mem_free(db->mctx, db->dlzname); 264 } 265 destroy = db->implementation->methods->destroy; 266 (*destroy)(db->implementation->driverarg, db->dbdata); 267 /* return memory and detach */ 268 isc_mem_putanddetach(&db->mctx, db, sizeof(*db)); 269 } 270 271 /*% 272 * Registers a DLZ driver. This basically just adds the dlz 273 * driver to the list of available drivers in the dlz_implementations list. 274 */ 275 isc_result_t 276 dns_dlzregister(const char *drivername, const dns_dlzmethods_t *methods, 277 void *driverarg, isc_mem_t *mctx, 278 dns_dlzimplementation_t **dlzimp) { 279 dns_dlzimplementation_t *dlz_imp; 280 281 /* Write debugging message to log */ 282 isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DLZ, 283 ISC_LOG_DEBUG(2), "Registering DLZ driver '%s'", 284 drivername); 285 286 /* 287 * Performs checks to make sure data is as we expect it to be. 288 */ 289 REQUIRE(drivername != NULL); 290 REQUIRE(methods != NULL); 291 REQUIRE(methods->create != NULL); 292 REQUIRE(methods->destroy != NULL); 293 REQUIRE(methods->findzone != NULL); 294 REQUIRE(mctx != NULL); 295 REQUIRE(dlzimp != NULL && *dlzimp == NULL); 296 297 /* 298 * initialize the dlz_implementations list, this is guaranteed 299 * to only really happen once. 300 */ 301 isc_once_do(&once, dlz_initialize); 302 303 /* lock the dlz_implementations list so we can modify it. */ 304 RWLOCK(&dlz_implock, isc_rwlocktype_write); 305 306 /* 307 * check that another already registered driver isn't using 308 * the same name 309 */ 310 dlz_imp = dlz_impfind(drivername); 311 if (dlz_imp != NULL) { 312 isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, 313 DNS_LOGMODULE_DLZ, ISC_LOG_DEBUG(2), 314 "DLZ Driver '%s' already registered", drivername); 315 RWUNLOCK(&dlz_implock, isc_rwlocktype_write); 316 return ISC_R_EXISTS; 317 } 318 319 /* 320 * Allocate memory for a dlz_implementation object. Error if 321 * we cannot. 322 */ 323 dlz_imp = isc_mem_get(mctx, sizeof(*dlz_imp)); 324 *dlz_imp = (dns_dlzimplementation_t){ 325 .name = drivername, 326 .methods = methods, 327 .driverarg = driverarg, 328 }; 329 330 /* attach the new dlz_implementation object to a memory context */ 331 isc_mem_attach(mctx, &dlz_imp->mctx); 332 333 /* 334 * prepare the dlz_implementation object to be put in a list, 335 * and append it to the list 336 */ 337 ISC_LINK_INIT(dlz_imp, link); 338 ISC_LIST_APPEND(dlz_implementations, dlz_imp, link); 339 340 /* Unlock the dlz_implementations list. */ 341 RWUNLOCK(&dlz_implock, isc_rwlocktype_write); 342 343 /* Pass back the dlz_implementation that we created. */ 344 *dlzimp = dlz_imp; 345 346 return ISC_R_SUCCESS; 347 } 348 349 /*% 350 * Tokenize the string "s" into whitespace-separated words, 351 * return the number of words in '*argcp' and an array 352 * of pointers to the words in '*argvp'. The caller 353 * must free the array using isc_mem_put(). The string 354 * is modified in-place. 355 */ 356 isc_result_t 357 dns_dlzstrtoargv(isc_mem_t *mctx, char *s, unsigned int *argcp, char ***argvp) { 358 return isc_commandline_strtoargv(mctx, s, argcp, argvp, 0); 359 } 360 361 /*% 362 * Unregisters a DLZ driver. This basically just removes the dlz 363 * driver from the list of available drivers in the dlz_implementations list. 364 */ 365 void 366 dns_dlzunregister(dns_dlzimplementation_t **dlzimp) { 367 dns_dlzimplementation_t *dlz_imp; 368 369 /* Write debugging message to log */ 370 isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DLZ, 371 ISC_LOG_DEBUG(2), "Unregistering DLZ driver."); 372 373 /* 374 * Performs checks to make sure data is as we expect it to be. 375 */ 376 REQUIRE(dlzimp != NULL && *dlzimp != NULL); 377 378 /* 379 * initialize the dlz_implementations list, this is guaranteed 380 * to only really happen once. 381 */ 382 isc_once_do(&once, dlz_initialize); 383 384 dlz_imp = *dlzimp; 385 386 /* lock the dlz_implementations list so we can modify it. */ 387 RWLOCK(&dlz_implock, isc_rwlocktype_write); 388 389 /* remove the dlz_implementation object from the list */ 390 ISC_LIST_UNLINK(dlz_implementations, dlz_imp, link); 391 392 /* 393 * Return the memory back to the available memory pool and 394 * remove it from the memory context. 395 */ 396 isc_mem_putanddetach(&dlz_imp->mctx, dlz_imp, sizeof(*dlz_imp)); 397 398 /* Unlock the dlz_implementations list. */ 399 RWUNLOCK(&dlz_implock, isc_rwlocktype_write); 400 } 401 402 /* 403 * Create a writeable DLZ zone. This can be called by DLZ drivers 404 * during configure() to create a zone that can be updated. The zone 405 * type is set to dns_zone_dlz, which is equivalent to a primary zone 406 * 407 * This function uses a callback setup in dns_dlzconfigure() to call 408 * into the server zone code to setup the remaining pieces of server 409 * specific functionality on the zone 410 */ 411 isc_result_t 412 dns_dlz_writeablezone(dns_view_t *view, dns_dlzdb_t *dlzdb, 413 const char *zone_name) { 414 dns_zone_t *zone = NULL; 415 dns_zone_t *dupzone = NULL; 416 isc_result_t result; 417 isc_buffer_t buffer; 418 dns_fixedname_t fixorigin; 419 dns_name_t *origin; 420 421 REQUIRE(DNS_DLZ_VALID(dlzdb)); 422 423 REQUIRE(dlzdb->configure_callback != NULL); 424 425 isc_buffer_constinit(&buffer, zone_name, strlen(zone_name)); 426 isc_buffer_add(&buffer, strlen(zone_name)); 427 dns_fixedname_init(&fixorigin); 428 result = dns_name_fromtext(dns_fixedname_name(&fixorigin), &buffer, 429 dns_rootname, 0, NULL); 430 if (result != ISC_R_SUCCESS) { 431 goto cleanup; 432 } 433 origin = dns_fixedname_name(&fixorigin); 434 435 if (!dlzdb->search) { 436 isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, 437 DNS_LOGMODULE_DLZ, ISC_LOG_WARNING, 438 "DLZ %s has 'search no;', but attempted to " 439 "register writeable zone %s.", 440 dlzdb->dlzname, zone_name); 441 result = ISC_R_SUCCESS; 442 goto cleanup; 443 } 444 445 /* See if the zone already exists */ 446 result = dns_view_findzone(view, origin, DNS_ZTFIND_EXACT, &dupzone); 447 if (result == ISC_R_SUCCESS) { 448 dns_zone_detach(&dupzone); 449 result = ISC_R_EXISTS; 450 goto cleanup; 451 } 452 INSIST(dupzone == NULL); 453 454 /* Create it */ 455 dns_zone_create(&zone, view->mctx, 0); 456 result = dns_zone_setorigin(zone, origin); 457 if (result != ISC_R_SUCCESS) { 458 goto cleanup; 459 } 460 dns_zone_setview(zone, view); 461 462 dns_zone_setadded(zone, true); 463 464 if (dlzdb->ssutable == NULL) { 465 dns_ssutable_createdlz(dlzdb->mctx, &dlzdb->ssutable, dlzdb); 466 } 467 dns_zone_setssutable(zone, dlzdb->ssutable); 468 469 result = dlzdb->configure_callback(view, dlzdb, zone); 470 if (result != ISC_R_SUCCESS) { 471 goto cleanup; 472 } 473 474 result = dns_view_addzone(view, zone); 475 476 cleanup: 477 if (zone != NULL) { 478 dns_zone_detach(&zone); 479 } 480 481 return result; 482 } 483 484 /*% 485 * Configure a DLZ driver. This is optional, and if supplied gives 486 * the backend an opportunity to configure parameters related to DLZ. 487 */ 488 isc_result_t 489 dns_dlzconfigure(dns_view_t *view, dns_dlzdb_t *dlzdb, 490 dlzconfigure_callback_t callback) { 491 dns_dlzimplementation_t *impl; 492 isc_result_t result; 493 494 REQUIRE(DNS_DLZ_VALID(dlzdb)); 495 REQUIRE(dlzdb->implementation != NULL); 496 497 impl = dlzdb->implementation; 498 499 if (impl->methods->configure == NULL) { 500 return ISC_R_SUCCESS; 501 } 502 503 dlzdb->configure_callback = callback; 504 505 result = impl->methods->configure(impl->driverarg, dlzdb->dbdata, view, 506 dlzdb); 507 return result; 508 } 509 510 bool 511 dns_dlz_ssumatch(dns_dlzdb_t *dlzdatabase, const dns_name_t *signer, 512 const dns_name_t *name, const isc_netaddr_t *tcpaddr, 513 dns_rdatatype_t type, const dst_key_t *key) { 514 dns_dlzimplementation_t *impl; 515 bool r; 516 517 REQUIRE(dlzdatabase != NULL); 518 REQUIRE(dlzdatabase->implementation != NULL); 519 REQUIRE(dlzdatabase->implementation->methods != NULL); 520 impl = dlzdatabase->implementation; 521 522 if (impl->methods->ssumatch == NULL) { 523 isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, 524 DNS_LOGMODULE_DLZ, ISC_LOG_INFO, 525 "No ssumatch method for DLZ database"); 526 return false; 527 } 528 529 r = impl->methods->ssumatch(signer, name, tcpaddr, type, key, 530 impl->driverarg, dlzdatabase->dbdata); 531 return r; 532 } 533