1 /* $NetBSD: dlz.c,v 1.7 2022/09/23 12:15:29 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/once.h>
64 #include <isc/rwlock.h>
65 #include <isc/string.h>
66 #include <isc/util.h>
67
68 #include <dns/db.h>
69 #include <dns/dlz.h>
70 #include <dns/fixedname.h>
71 #include <dns/log.h>
72 #include <dns/master.h>
73 #include <dns/ssu.h>
74 #include <dns/zone.h>
75
76 /***
77 *** Supported DLZ DB Implementations Registry
78 ***/
79
80 static ISC_LIST(dns_dlzimplementation_t) dlz_implementations;
81 static isc_rwlock_t dlz_implock;
82 static isc_once_t once = ISC_ONCE_INIT;
83
84 static void
dlz_initialize(void)85 dlz_initialize(void) {
86 isc_rwlock_init(&dlz_implock, 0, 0);
87 ISC_LIST_INIT(dlz_implementations);
88 }
89
90 /*%
91 * Searches the dlz_implementations list for a driver matching name.
92 */
93 static dns_dlzimplementation_t *
dlz_impfind(const char * name)94 dlz_impfind(const char *name) {
95 dns_dlzimplementation_t *imp;
96
97 for (imp = ISC_LIST_HEAD(dlz_implementations); imp != NULL;
98 imp = ISC_LIST_NEXT(imp, link))
99 {
100 if (strcasecmp(name, imp->name) == 0) {
101 return (imp);
102 }
103 }
104 return (NULL);
105 }
106
107 /***
108 *** Basic DLZ Methods
109 ***/
110
111 isc_result_t
dns_dlzallowzonexfr(dns_view_t * view,const dns_name_t * name,const isc_sockaddr_t * clientaddr,dns_db_t ** dbp)112 dns_dlzallowzonexfr(dns_view_t *view, const dns_name_t *name,
113 const isc_sockaddr_t *clientaddr, dns_db_t **dbp) {
114 isc_result_t result = ISC_R_NOTFOUND;
115 dns_dlzallowzonexfr_t allowzonexfr;
116 dns_dlzdb_t *dlzdb;
117
118 /*
119 * Performs checks to make sure data is as we expect it to be.
120 */
121 REQUIRE(name != NULL);
122 REQUIRE(dbp != NULL && *dbp == NULL);
123
124 /*
125 * Find a driver in which the zone exists and transfer is supported
126 */
127 for (dlzdb = ISC_LIST_HEAD(view->dlz_searched); dlzdb != NULL;
128 dlzdb = ISC_LIST_NEXT(dlzdb, link))
129 {
130 REQUIRE(DNS_DLZ_VALID(dlzdb));
131
132 allowzonexfr = dlzdb->implementation->methods->allowzonexfr;
133 result = (*allowzonexfr)(dlzdb->implementation->driverarg,
134 dlzdb->dbdata, dlzdb->mctx,
135 view->rdclass, name, clientaddr, dbp);
136
137 /*
138 * In these cases, we found the right database. Non-success
139 * result codes indicate the zone might not transfer.
140 */
141 switch (result) {
142 case ISC_R_SUCCESS:
143 case ISC_R_NOPERM:
144 case ISC_R_DEFAULT:
145 return (result);
146 default:
147 break;
148 }
149 }
150
151 if (result == ISC_R_NOTIMPLEMENTED) {
152 result = ISC_R_NOTFOUND;
153 }
154
155 return (result);
156 }
157
158 isc_result_t
dns_dlzcreate(isc_mem_t * mctx,const char * dlzname,const char * drivername,unsigned int argc,char * argv[],dns_dlzdb_t ** dbp)159 dns_dlzcreate(isc_mem_t *mctx, const char *dlzname, const char *drivername,
160 unsigned int argc, char *argv[], dns_dlzdb_t **dbp) {
161 dns_dlzimplementation_t *impinfo;
162 isc_result_t result;
163 dns_dlzdb_t *db = NULL;
164
165 /*
166 * initialize the dlz_implementations list, this is guaranteed
167 * to only really happen once.
168 */
169 RUNTIME_CHECK(isc_once_do(&once, dlz_initialize) == ISC_R_SUCCESS);
170
171 /*
172 * Performs checks to make sure data is as we expect it to be.
173 */
174 REQUIRE(dbp != NULL && *dbp == NULL);
175 REQUIRE(dlzname != NULL);
176 REQUIRE(drivername != NULL);
177 REQUIRE(mctx != NULL);
178
179 /* write log message */
180 isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DLZ,
181 ISC_LOG_INFO, "Loading '%s' using driver %s", dlzname,
182 drivername);
183
184 /* lock the dlz_implementations list so we can search it. */
185 RWLOCK(&dlz_implock, isc_rwlocktype_read);
186
187 /* search for the driver implementation */
188 impinfo = dlz_impfind(drivername);
189 if (impinfo == NULL) {
190 RWUNLOCK(&dlz_implock, isc_rwlocktype_read);
191
192 isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
193 DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
194 "unsupported DLZ database driver '%s'."
195 " %s not loaded.",
196 drivername, dlzname);
197
198 return (ISC_R_NOTFOUND);
199 }
200
201 /* Allocate memory to hold the DLZ database driver */
202 db = isc_mem_get(mctx, sizeof(dns_dlzdb_t));
203
204 /* Make sure memory region is set to all 0's */
205 memset(db, 0, sizeof(dns_dlzdb_t));
206
207 ISC_LINK_INIT(db, link);
208 db->implementation = impinfo;
209 if (dlzname != NULL) {
210 db->dlzname = isc_mem_strdup(mctx, dlzname);
211 }
212
213 /* Create a new database using implementation 'drivername'. */
214 result = ((impinfo->methods->create)(mctx, dlzname, argc, argv,
215 impinfo->driverarg, &db->dbdata));
216
217 /* mark the DLZ driver as valid */
218 if (result == ISC_R_SUCCESS) {
219 RWUNLOCK(&dlz_implock, isc_rwlocktype_read);
220 db->magic = DNS_DLZ_MAGIC;
221 isc_mem_attach(mctx, &db->mctx);
222 isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
223 DNS_LOGMODULE_DLZ, ISC_LOG_DEBUG(2),
224 "DLZ driver loaded successfully.");
225 *dbp = db;
226 return (ISC_R_SUCCESS);
227 } else {
228 isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
229 DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
230 "DLZ driver failed to load.");
231 }
232
233 /* impinfo->methods->create failed. */
234 RWUNLOCK(&dlz_implock, isc_rwlocktype_read);
235 isc_mem_free(mctx, db->dlzname);
236 isc_mem_put(mctx, db, sizeof(dns_dlzdb_t));
237 return (result);
238 }
239
240 void
dns_dlzdestroy(dns_dlzdb_t ** dbp)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(dns_dlzdb_t));
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
dns_dlzregister(const char * drivername,const dns_dlzmethods_t * methods,void * driverarg,isc_mem_t * mctx,dns_dlzimplementation_t ** dlzimp)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 RUNTIME_CHECK(isc_once_do(&once, dlz_initialize) == ISC_R_SUCCESS);
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(dns_dlzimplementation_t));
324
325 /* Make sure memory region is set to all 0's */
326 memset(dlz_imp, 0, sizeof(dns_dlzimplementation_t));
327
328 /* Store the data passed into this method */
329 dlz_imp->name = drivername;
330 dlz_imp->methods = methods;
331 dlz_imp->mctx = NULL;
332 dlz_imp->driverarg = driverarg;
333
334 /* attach the new dlz_implementation object to a memory context */
335 isc_mem_attach(mctx, &dlz_imp->mctx);
336
337 /*
338 * prepare the dlz_implementation object to be put in a list,
339 * and append it to the list
340 */
341 ISC_LINK_INIT(dlz_imp, link);
342 ISC_LIST_APPEND(dlz_implementations, dlz_imp, link);
343
344 /* Unlock the dlz_implementations list. */
345 RWUNLOCK(&dlz_implock, isc_rwlocktype_write);
346
347 /* Pass back the dlz_implementation that we created. */
348 *dlzimp = dlz_imp;
349
350 return (ISC_R_SUCCESS);
351 }
352
353 /*%
354 * Tokenize the string "s" into whitespace-separated words,
355 * return the number of words in '*argcp' and an array
356 * of pointers to the words in '*argvp'. The caller
357 * must free the array using isc_mem_put(). The string
358 * is modified in-place.
359 */
360 isc_result_t
dns_dlzstrtoargv(isc_mem_t * mctx,char * s,unsigned int * argcp,char *** argvp)361 dns_dlzstrtoargv(isc_mem_t *mctx, char *s, unsigned int *argcp, char ***argvp) {
362 return (isc_commandline_strtoargv(mctx, s, argcp, argvp, 0));
363 }
364
365 /*%
366 * Unregisters a DLZ driver. This basically just removes the dlz
367 * driver from the list of available drivers in the dlz_implementations list.
368 */
369 void
dns_dlzunregister(dns_dlzimplementation_t ** dlzimp)370 dns_dlzunregister(dns_dlzimplementation_t **dlzimp) {
371 dns_dlzimplementation_t *dlz_imp;
372
373 /* Write debugging message to log */
374 isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DLZ,
375 ISC_LOG_DEBUG(2), "Unregistering DLZ driver.");
376
377 /*
378 * Performs checks to make sure data is as we expect it to be.
379 */
380 REQUIRE(dlzimp != NULL && *dlzimp != NULL);
381
382 /*
383 * initialize the dlz_implementations list, this is guaranteed
384 * to only really happen once.
385 */
386 RUNTIME_CHECK(isc_once_do(&once, dlz_initialize) == ISC_R_SUCCESS);
387
388 dlz_imp = *dlzimp;
389
390 /* lock the dlz_implementations list so we can modify it. */
391 RWLOCK(&dlz_implock, isc_rwlocktype_write);
392
393 /* remove the dlz_implementation object from the list */
394 ISC_LIST_UNLINK(dlz_implementations, dlz_imp, link);
395
396 /*
397 * Return the memory back to the available memory pool and
398 * remove it from the memory context.
399 */
400 isc_mem_putanddetach(&dlz_imp->mctx, dlz_imp, sizeof(*dlz_imp));
401
402 /* Unlock the dlz_implementations list. */
403 RWUNLOCK(&dlz_implock, isc_rwlocktype_write);
404 }
405
406 /*
407 * Create a writeable DLZ zone. This can be called by DLZ drivers
408 * during configure() to create a zone that can be updated. The zone
409 * type is set to dns_zone_dlz, which is equivalent to a master zone
410 *
411 * This function uses a callback setup in dns_dlzconfigure() to call
412 * into the server zone code to setup the remaining pieces of server
413 * specific functionality on the zone
414 */
415 isc_result_t
dns_dlz_writeablezone(dns_view_t * view,dns_dlzdb_t * dlzdb,const char * zone_name)416 dns_dlz_writeablezone(dns_view_t *view, dns_dlzdb_t *dlzdb,
417 const char *zone_name) {
418 dns_zone_t *zone = NULL;
419 dns_zone_t *dupzone = NULL;
420 isc_result_t result;
421 isc_buffer_t buffer;
422 dns_fixedname_t fixorigin;
423 dns_name_t *origin;
424
425 REQUIRE(DNS_DLZ_VALID(dlzdb));
426
427 REQUIRE(dlzdb->configure_callback != NULL);
428
429 isc_buffer_constinit(&buffer, zone_name, strlen(zone_name));
430 isc_buffer_add(&buffer, strlen(zone_name));
431 dns_fixedname_init(&fixorigin);
432 result = dns_name_fromtext(dns_fixedname_name(&fixorigin), &buffer,
433 dns_rootname, 0, NULL);
434 if (result != ISC_R_SUCCESS) {
435 goto cleanup;
436 }
437 origin = dns_fixedname_name(&fixorigin);
438
439 if (!dlzdb->search) {
440 isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
441 DNS_LOGMODULE_DLZ, ISC_LOG_WARNING,
442 "DLZ %s has 'search no;', but attempted to "
443 "register writeable zone %s.",
444 dlzdb->dlzname, zone_name);
445 result = ISC_R_SUCCESS;
446 goto cleanup;
447 }
448
449 /* See if the zone already exists */
450 result = dns_view_findzone(view, origin, &dupzone);
451 if (result == ISC_R_SUCCESS) {
452 dns_zone_detach(&dupzone);
453 result = ISC_R_EXISTS;
454 goto cleanup;
455 }
456 INSIST(dupzone == NULL);
457
458 /* Create it */
459 result = dns_zone_create(&zone, view->mctx);
460 if (result != ISC_R_SUCCESS) {
461 goto cleanup;
462 }
463 result = dns_zone_setorigin(zone, origin);
464 if (result != ISC_R_SUCCESS) {
465 goto cleanup;
466 }
467 dns_zone_setview(zone, view);
468
469 dns_zone_setadded(zone, true);
470
471 if (dlzdb->ssutable == NULL) {
472 result = dns_ssutable_createdlz(dlzdb->mctx, &dlzdb->ssutable,
473 dlzdb);
474 if (result != ISC_R_SUCCESS) {
475 goto cleanup;
476 }
477 }
478 dns_zone_setssutable(zone, dlzdb->ssutable);
479
480 result = dlzdb->configure_callback(view, dlzdb, zone);
481 if (result != ISC_R_SUCCESS) {
482 goto cleanup;
483 }
484
485 result = dns_view_addzone(view, zone);
486
487 cleanup:
488 if (zone != NULL) {
489 dns_zone_detach(&zone);
490 }
491
492 return (result);
493 }
494
495 /*%
496 * Configure a DLZ driver. This is optional, and if supplied gives
497 * the backend an opportunity to configure parameters related to DLZ.
498 */
499 isc_result_t
dns_dlzconfigure(dns_view_t * view,dns_dlzdb_t * dlzdb,dlzconfigure_callback_t callback)500 dns_dlzconfigure(dns_view_t *view, dns_dlzdb_t *dlzdb,
501 dlzconfigure_callback_t callback) {
502 dns_dlzimplementation_t *impl;
503 isc_result_t result;
504
505 REQUIRE(DNS_DLZ_VALID(dlzdb));
506 REQUIRE(dlzdb->implementation != NULL);
507
508 impl = dlzdb->implementation;
509
510 if (impl->methods->configure == NULL) {
511 return (ISC_R_SUCCESS);
512 }
513
514 dlzdb->configure_callback = callback;
515
516 result = impl->methods->configure(impl->driverarg, dlzdb->dbdata, view,
517 dlzdb);
518 return (result);
519 }
520
521 bool
dns_dlz_ssumatch(dns_dlzdb_t * dlzdatabase,const dns_name_t * signer,const dns_name_t * name,const isc_netaddr_t * tcpaddr,dns_rdatatype_t type,const dst_key_t * key)522 dns_dlz_ssumatch(dns_dlzdb_t *dlzdatabase, const dns_name_t *signer,
523 const dns_name_t *name, const isc_netaddr_t *tcpaddr,
524 dns_rdatatype_t type, const dst_key_t *key) {
525 dns_dlzimplementation_t *impl;
526 bool r;
527
528 REQUIRE(dlzdatabase != NULL);
529 REQUIRE(dlzdatabase->implementation != NULL);
530 REQUIRE(dlzdatabase->implementation->methods != NULL);
531 impl = dlzdatabase->implementation;
532
533 if (impl->methods->ssumatch == NULL) {
534 isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
535 DNS_LOGMODULE_DLZ, ISC_LOG_INFO,
536 "No ssumatch method for DLZ database");
537 return (false);
538 }
539
540 r = impl->methods->ssumatch(signer, name, tcpaddr, type, key,
541 impl->driverarg, dlzdatabase->dbdata);
542 return (r);
543 }
544