1/*	$NetBSD: dlz.c,v 1.8 2024/02/21 22:52:06 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
80static ISC_LIST(dns_dlzimplementation_t) dlz_implementations;
81static isc_rwlock_t dlz_implock;
82static isc_once_t once = ISC_ONCE_INIT;
83
84static void
85dlz_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 */
93static dns_dlzimplementation_t *
94dlz_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
111isc_result_t
112dns_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
158isc_result_t
159dns_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
240void
241dns_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 */
275isc_result_t
276dns_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 */
360isc_result_t
361dns_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 */
369void
370dns_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 primary 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 */
415isc_result_t
416dns_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		dns_ssutable_createdlz(dlzdb->mctx, &dlzdb->ssutable, dlzdb);
473	}
474	dns_zone_setssutable(zone, dlzdb->ssutable);
475
476	result = dlzdb->configure_callback(view, dlzdb, zone);
477	if (result != ISC_R_SUCCESS) {
478		goto cleanup;
479	}
480
481	result = dns_view_addzone(view, zone);
482
483cleanup:
484	if (zone != NULL) {
485		dns_zone_detach(&zone);
486	}
487
488	return (result);
489}
490
491/*%
492 * Configure a DLZ driver. This is optional, and if supplied gives
493 * the backend an opportunity to configure parameters related to DLZ.
494 */
495isc_result_t
496dns_dlzconfigure(dns_view_t *view, dns_dlzdb_t *dlzdb,
497		 dlzconfigure_callback_t callback) {
498	dns_dlzimplementation_t *impl;
499	isc_result_t result;
500
501	REQUIRE(DNS_DLZ_VALID(dlzdb));
502	REQUIRE(dlzdb->implementation != NULL);
503
504	impl = dlzdb->implementation;
505
506	if (impl->methods->configure == NULL) {
507		return (ISC_R_SUCCESS);
508	}
509
510	dlzdb->configure_callback = callback;
511
512	result = impl->methods->configure(impl->driverarg, dlzdb->dbdata, view,
513					  dlzdb);
514	return (result);
515}
516
517bool
518dns_dlz_ssumatch(dns_dlzdb_t *dlzdatabase, const dns_name_t *signer,
519		 const dns_name_t *name, const isc_netaddr_t *tcpaddr,
520		 dns_rdatatype_t type, const dst_key_t *key) {
521	dns_dlzimplementation_t *impl;
522	bool r;
523
524	REQUIRE(dlzdatabase != NULL);
525	REQUIRE(dlzdatabase->implementation != NULL);
526	REQUIRE(dlzdatabase->implementation->methods != NULL);
527	impl = dlzdatabase->implementation;
528
529	if (impl->methods->ssumatch == NULL) {
530		isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
531			      DNS_LOGMODULE_DLZ, ISC_LOG_INFO,
532			      "No ssumatch method for DLZ database");
533		return (false);
534	}
535
536	r = impl->methods->ssumatch(signer, name, tcpaddr, type, key,
537				    impl->driverarg, dlzdatabase->dbdata);
538	return (r);
539}
540