1/*	$NetBSD: dlz_example.c,v 1.7 2024/02/21 22:51:47 christos Exp $	*/
2
3/*
4 * Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
5 *
6 * SPDX-License-Identifier: ISC
7 *
8 * Permission to use, copy, modify, and/or distribute this software for any
9 * purpose with or without fee is hereby granted, provided that the above
10 * copyright notice and this permission notice appear in all copies.
11 *
12 * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
13 * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
14 * AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
15 * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
16 * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
17 * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
18 * PERFORMANCE OF THIS SOFTWARE.
19 */
20
21/*
22 * This provides a very simple example of an external loadable DLZ
23 * driver, with update support.
24 */
25
26#include <inttypes.h>
27#include <stdarg.h>
28#include <stdbool.h>
29#include <stdio.h>
30#include <stdlib.h>
31#include <string.h>
32
33#include "../modules/include/dlz_minimal.h"
34
35#define CHECK(x)                             \
36	do {                                 \
37		result = (x);                \
38		if (result != ISC_R_SUCCESS) \
39			goto failure;        \
40	} while (0)
41
42/* For this simple example, use fixed sized strings */
43struct record {
44	char name[100];
45	char type[10];
46	char data[200];
47	dns_ttl_t ttl;
48};
49
50#define MAX_RECORDS 100
51
52struct dlz_example_data {
53	char *zone_name;
54
55	/* An example driver doesn't need good memory management :-) */
56	struct record current[MAX_RECORDS];
57	struct record adds[MAX_RECORDS];
58	struct record deletes[MAX_RECORDS];
59
60	bool transaction_started;
61
62	/* Helper functions from the dlz_dlopen driver */
63	log_t *log;
64	dns_sdlz_putrr_t *putrr;
65	dns_sdlz_putnamedrr_t *putnamedrr;
66	dns_dlz_writeablezone_t *writeable_zone;
67};
68
69static bool
70single_valued(const char *type) {
71	const char *single[] = { "soa", "cname", NULL };
72	int i;
73
74	for (i = 0; single[i]; i++) {
75		if (strcasecmp(single[i], type) == 0) {
76			return (true);
77		}
78	}
79	return (false);
80}
81
82/*
83 * Add a record to a list
84 */
85static isc_result_t
86add_name(struct dlz_example_data *state, struct record *list, const char *name,
87	 const char *type, dns_ttl_t ttl, const char *data) {
88	int i;
89	bool single = single_valued(type);
90	int first_empty = -1;
91
92	for (i = 0; i < MAX_RECORDS; i++) {
93		if (first_empty == -1 && strlen(list[i].name) == 0U) {
94			first_empty = i;
95		}
96		if (strcasecmp(list[i].name, name) != 0) {
97			continue;
98		}
99		if (strcasecmp(list[i].type, type) != 0) {
100			continue;
101		}
102		if (!single && strcasecmp(list[i].data, data) != 0) {
103			continue;
104		}
105		break;
106	}
107	if (i == MAX_RECORDS && first_empty != -1) {
108		i = first_empty;
109	}
110	if (i == MAX_RECORDS) {
111		if (state->log != NULL) {
112			state->log(ISC_LOG_ERROR, "dlz_example: out of record "
113						  "space");
114		}
115		return (ISC_R_FAILURE);
116	}
117
118	if (strlen(name) >= sizeof(list[i].name) ||
119	    strlen(type) >= sizeof(list[i].type) ||
120	    strlen(data) >= sizeof(list[i].data))
121	{
122		return (ISC_R_NOSPACE);
123	}
124
125	strncpy(list[i].name, name, sizeof(list[i].name));
126	list[i].name[sizeof(list[i].name) - 1] = '\0';
127
128	strncpy(list[i].type, type, sizeof(list[i].type));
129	list[i].type[sizeof(list[i].type) - 1] = '\0';
130
131	strncpy(list[i].data, data, sizeof(list[i].data));
132	list[i].data[sizeof(list[i].data) - 1] = '\0';
133
134	list[i].ttl = ttl;
135
136	return (ISC_R_SUCCESS);
137}
138
139/*
140 * Delete a record from a list
141 */
142static isc_result_t
143del_name(struct dlz_example_data *state, struct record *list, const char *name,
144	 const char *type, dns_ttl_t ttl, const char *data) {
145	int i;
146
147	UNUSED(state);
148
149	for (i = 0; i < MAX_RECORDS; i++) {
150		if (strcasecmp(name, list[i].name) == 0 &&
151		    strcasecmp(type, list[i].type) == 0 &&
152		    strcasecmp(data, list[i].data) == 0 && ttl == list[i].ttl)
153		{
154			break;
155		}
156	}
157	if (i == MAX_RECORDS) {
158		return (ISC_R_NOTFOUND);
159	}
160	memset(&list[i], 0, sizeof(struct record));
161	return (ISC_R_SUCCESS);
162}
163
164static isc_result_t
165fmt_address(isc_sockaddr_t *addr, char *buffer, size_t size) {
166	char addr_buf[100];
167	const char *ret;
168	uint16_t port = 0;
169
170	switch (addr->type.sa.sa_family) {
171	case AF_INET:
172		port = ntohs(addr->type.sin.sin_port);
173		ret = inet_ntop(AF_INET, &addr->type.sin.sin_addr, addr_buf,
174				sizeof(addr_buf));
175		break;
176	case AF_INET6:
177		port = ntohs(addr->type.sin6.sin6_port);
178		ret = inet_ntop(AF_INET6, &addr->type.sin6.sin6_addr, addr_buf,
179				sizeof(addr_buf));
180		break;
181	default:
182		return (ISC_R_FAILURE);
183	}
184
185	if (ret == NULL) {
186		return (ISC_R_FAILURE);
187	}
188
189	snprintf(buffer, size, "%s#%u", addr_buf, port);
190	return (ISC_R_SUCCESS);
191}
192
193/*
194 * Return the version of the API
195 */
196int
197dlz_version(unsigned int *flags) {
198	UNUSED(flags);
199	return (DLZ_DLOPEN_VERSION);
200}
201
202/*
203 * Remember a helper function from the bind9 dlz_dlopen driver
204 */
205static void
206b9_add_helper(struct dlz_example_data *state, const char *helper_name,
207	      void *ptr) {
208	if (strcmp(helper_name, "log") == 0) {
209		state->log = (log_t *)ptr;
210	}
211	if (strcmp(helper_name, "putrr") == 0) {
212		state->putrr = (dns_sdlz_putrr_t *)ptr;
213	}
214	if (strcmp(helper_name, "putnamedrr") == 0) {
215		state->putnamedrr = (dns_sdlz_putnamedrr_t *)ptr;
216	}
217	if (strcmp(helper_name, "writeable_zone") == 0) {
218		state->writeable_zone = (dns_dlz_writeablezone_t *)ptr;
219	}
220}
221
222/*
223 * Called to initialize the driver
224 */
225isc_result_t
226dlz_create(const char *dlzname, unsigned int argc, char *argv[], void **dbdata,
227	   ...) {
228	struct dlz_example_data *state;
229	const char *helper_name;
230	va_list ap;
231	char soa_data[200];
232	const char *extra;
233	isc_result_t result;
234	int n;
235
236	UNUSED(dlzname);
237
238	state = calloc(1, sizeof(struct dlz_example_data));
239	if (state == NULL) {
240		return (ISC_R_NOMEMORY);
241	}
242
243	/* Fill in the helper functions */
244	va_start(ap, dbdata);
245	while ((helper_name = va_arg(ap, const char *)) != NULL) {
246		b9_add_helper(state, helper_name, va_arg(ap, void *));
247	}
248	va_end(ap);
249
250	if (argc < 2 || argv[1][0] == '\0') {
251		if (state->log != NULL) {
252			state->log(ISC_LOG_ERROR, "dlz_example: please specify "
253						  "a zone name");
254		}
255		dlz_destroy(state);
256		return (ISC_R_FAILURE);
257	}
258
259	/* Ensure zone name is absolute */
260	state->zone_name = malloc(strlen(argv[1]) + 2);
261	if (state->zone_name == NULL) {
262		free(state);
263		return (ISC_R_NOMEMORY);
264	}
265	if (argv[1][strlen(argv[1]) - 1] == '.') {
266		strcpy(state->zone_name, argv[1]);
267	} else {
268		sprintf(state->zone_name, "%s.", argv[1]);
269	}
270
271	if (strcmp(state->zone_name, ".") == 0) {
272		extra = ".root";
273	} else {
274		extra = ".";
275	}
276
277	n = sprintf(soa_data, "%s hostmaster%s%s 123 900 600 86400 3600",
278		    state->zone_name, extra, state->zone_name);
279
280	if (n < 0) {
281		CHECK(ISC_R_FAILURE);
282	}
283	if ((unsigned)n >= sizeof(soa_data)) {
284		CHECK(ISC_R_NOSPACE);
285	}
286
287	add_name(state, &state->current[0], state->zone_name, "soa", 3600,
288		 soa_data);
289	add_name(state, &state->current[0], state->zone_name, "ns", 3600,
290		 state->zone_name);
291	add_name(state, &state->current[0], state->zone_name, "a", 1800,
292		 "10.53.0.1");
293
294	if (state->log != NULL) {
295		state->log(ISC_LOG_INFO, "dlz_example: started for zone %s",
296			   state->zone_name);
297	}
298
299	*dbdata = state;
300	return (ISC_R_SUCCESS);
301
302failure:
303	free(state);
304	return (result);
305}
306
307/*
308 * Shut down the backend
309 */
310void
311dlz_destroy(void *dbdata) {
312	struct dlz_example_data *state = (struct dlz_example_data *)dbdata;
313
314	if (state->log != NULL) {
315		state->log(ISC_LOG_INFO, "dlz_example: shutting down zone %s",
316			   state->zone_name);
317	}
318	free(state->zone_name);
319	free(state);
320}
321
322/*
323 * See if we handle a given zone
324 */
325isc_result_t
326dlz_findzonedb(void *dbdata, const char *name, dns_clientinfomethods_t *methods,
327	       dns_clientinfo_t *clientinfo) {
328	struct dlz_example_data *state = (struct dlz_example_data *)dbdata;
329	isc_sockaddr_t *src;
330	char addrbuf[100];
331	char absolute[1024];
332
333	strcpy(addrbuf, "unknown");
334	if (methods != NULL && methods->sourceip != NULL &&
335	    methods->version - methods->age <= DNS_CLIENTINFOMETHODS_VERSION &&
336	    DNS_CLIENTINFOMETHODS_VERSION <= methods->version)
337	{
338		methods->sourceip(clientinfo, &src);
339		fmt_address(src, addrbuf, sizeof(addrbuf));
340	}
341	state->log(ISC_LOG_INFO, "dlz_example: findzonedb connection from: %s",
342		   addrbuf);
343
344	state->log(ISC_LOG_INFO,
345		   "dlz_example: dlz_findzonedb called with name '%s' "
346		   "in zone DB '%s'",
347		   name, state->zone_name);
348
349	/*
350	 * Returning ISC_R_NOTFOUND will cause the query logic to
351	 * check the database for parent names, looking for zone cuts.
352	 *
353	 * Returning ISC_R_NOMORE prevents the query logic from doing
354	 * this; it will move onto the next database after a single query.
355	 */
356	if (strcasecmp(name, "test.example.com") == 0) {
357		return (ISC_R_NOMORE);
358	}
359
360	/*
361	 * For example.net, only return ISC_R_NOMORE when queried
362	 * from 10.53.0.1.
363	 */
364	if (strcasecmp(name, "test.example.net") == 0 &&
365	    strncmp(addrbuf, "10.53.0.1", 9) == 0)
366	{
367		return (ISC_R_NOMORE);
368	}
369
370	if (strcasecmp(state->zone_name, name) == 0) {
371		return (ISC_R_SUCCESS);
372	}
373
374	snprintf(absolute, sizeof(absolute), "%s.", name);
375	if (strcasecmp(state->zone_name, absolute) == 0) {
376		return (ISC_R_SUCCESS);
377	}
378
379	return (ISC_R_NOTFOUND);
380}
381
382/*
383 * Look up one record in the sample database.
384 *
385 * If the queryname is "source-addr", send back a TXT record containing
386 * the address of the client; this demonstrates the use of 'methods'
387 * and 'clientinfo'.
388 *
389 * If the queryname is "too-long", send back a TXT record that's too long
390 * to process; this should result in a SERVFAIL when queried.
391 */
392isc_result_t
393dlz_lookup(const char *zone, const char *name, void *dbdata,
394	   dns_sdlzlookup_t *lookup, dns_clientinfomethods_t *methods,
395	   dns_clientinfo_t *clientinfo) {
396	isc_result_t result;
397	struct dlz_example_data *state = (struct dlz_example_data *)dbdata;
398	bool found = false;
399	void *dbversion = NULL;
400	isc_sockaddr_t *src;
401	char full_name[256];
402	char buf[512];
403	int i;
404
405	UNUSED(zone);
406
407	if (state->putrr == NULL) {
408		return (ISC_R_NOTIMPLEMENTED);
409	}
410
411	if (strcmp(name, "@") == 0) {
412		strncpy(full_name, state->zone_name, 255);
413		full_name[255] = '\0';
414	} else {
415		snprintf(full_name, 255, "%s.%s", name, state->zone_name);
416	}
417
418	/*
419	 * If we need to know the database version (as set in
420	 * the 'newversion' dlz function) we can pick it up from the
421	 * clientinfo.
422	 *
423	 * This allows a lookup to query the correct version of the DNS
424	 * data, if the DLZ can differentiate between versions.
425	 *
426	 * For example, if a new database transaction is created by
427	 * 'newversion', the lookup should query within the same
428	 * transaction scope if it can.
429	 *
430	 * If the DLZ only operates on 'live' data, then version
431	 * wouldn't necessarily be needed.
432	 */
433	if (clientinfo != NULL && clientinfo->version >= 2) {
434		dbversion = clientinfo->dbversion;
435		if (dbversion != NULL && *(bool *)dbversion) {
436			state->log(ISC_LOG_INFO, "dlz_example: lookup against "
437						 "live "
438						 "transaction\n");
439		}
440	}
441
442	if (strcmp(name, "source-addr") == 0) {
443		char ecsbuf[DNS_ECS_FORMATSIZE] = "not supported";
444		strncpy(buf, "unknown", sizeof(buf));
445		if (methods != NULL && methods->sourceip != NULL &&
446		    (methods->version - methods->age <=
447		     DNS_CLIENTINFOMETHODS_VERSION) &&
448		    DNS_CLIENTINFOMETHODS_VERSION <= methods->version)
449		{
450			methods->sourceip(clientinfo, &src);
451			fmt_address(src, buf, sizeof(buf));
452		}
453		if (clientinfo != NULL && clientinfo->version >= 3) {
454			if (clientinfo->ecs.addr.family != AF_UNSPEC) {
455				dns_ecs_format(&clientinfo->ecs, ecsbuf,
456					       sizeof(ecsbuf));
457			} else {
458				snprintf(ecsbuf, sizeof(ecsbuf), "%s",
459					 "not present");
460			}
461		}
462		i = strlen(buf);
463		snprintf(buf + i, sizeof(buf) - i - 1, " ECS %s", ecsbuf);
464
465		state->log(ISC_LOG_INFO,
466			   "dlz_example: lookup connection from: %s", buf);
467
468		found = true;
469		result = state->putrr(lookup, "TXT", 0, buf);
470		/* We could also generate a CNAME RR:
471		snprintf(buf, sizeof(buf), "%s.redirect.example.", ecsbuf);
472		result = state->putrr(lookup, "CNAME", 0, buf); */
473		if (result != ISC_R_SUCCESS) {
474			return (result);
475		}
476	}
477
478	if (strcmp(name, "too-long") == 0) {
479		for (i = 0; i < 511; i++) {
480			buf[i] = 'x';
481		}
482		buf[i] = '\0';
483		found = true;
484		result = state->putrr(lookup, "TXT", 0, buf);
485		if (result != ISC_R_SUCCESS) {
486			return (result);
487		}
488	}
489
490	for (i = 0; i < MAX_RECORDS; i++) {
491		if (strcasecmp(state->current[i].name, full_name) == 0) {
492			found = true;
493			result = state->putrr(lookup, state->current[i].type,
494					      state->current[i].ttl,
495					      state->current[i].data);
496			if (result != ISC_R_SUCCESS) {
497				return (result);
498			}
499		}
500	}
501
502	if (!found) {
503		return (ISC_R_NOTFOUND);
504	}
505
506	return (ISC_R_SUCCESS);
507}
508
509/*
510 * See if a zone transfer is allowed
511 */
512isc_result_t
513dlz_allowzonexfr(void *dbdata, const char *name, const char *client) {
514	UNUSED(client);
515
516	/* Just say yes for all our zones */
517	return (dlz_findzonedb(dbdata, name, NULL, NULL));
518}
519
520/*
521 * Perform a zone transfer
522 */
523isc_result_t
524dlz_allnodes(const char *zone, void *dbdata, dns_sdlzallnodes_t *allnodes) {
525	struct dlz_example_data *state = (struct dlz_example_data *)dbdata;
526	int i;
527
528	UNUSED(zone);
529
530	if (state->putnamedrr == NULL) {
531		return (ISC_R_NOTIMPLEMENTED);
532	}
533
534	for (i = 0; i < MAX_RECORDS; i++) {
535		isc_result_t result;
536		if (strlen(state->current[i].name) == 0U) {
537			continue;
538		}
539		result = state->putnamedrr(allnodes, state->current[i].name,
540					   state->current[i].type,
541					   state->current[i].ttl,
542					   state->current[i].data);
543		if (result != ISC_R_SUCCESS) {
544			return (result);
545		}
546	}
547
548	return (ISC_R_SUCCESS);
549}
550
551/*
552 * Start a transaction
553 */
554isc_result_t
555dlz_newversion(const char *zone, void *dbdata, void **versionp) {
556	struct dlz_example_data *state = (struct dlz_example_data *)dbdata;
557
558	if (state->transaction_started) {
559		if (state->log != NULL) {
560			state->log(ISC_LOG_INFO,
561				   "dlz_example: transaction already "
562				   "started for zone %s",
563				   zone);
564		}
565		return (ISC_R_FAILURE);
566	}
567
568	state->transaction_started = true;
569	*versionp = (void *)&state->transaction_started;
570
571	return (ISC_R_SUCCESS);
572}
573
574/*
575 * End a transaction
576 */
577void
578dlz_closeversion(const char *zone, bool commit, void *dbdata, void **versionp) {
579	struct dlz_example_data *state = (struct dlz_example_data *)dbdata;
580
581	if (!state->transaction_started) {
582		if (state->log != NULL) {
583			state->log(ISC_LOG_INFO,
584				   "dlz_example: transaction not "
585				   "started for zone %s",
586				   zone);
587		}
588		*versionp = NULL;
589		return;
590	}
591
592	state->transaction_started = false;
593
594	*versionp = NULL;
595
596	if (commit) {
597		int i;
598		if (state->log != NULL) {
599			state->log(ISC_LOG_INFO,
600				   "dlz_example: committing "
601				   "transaction on zone %s",
602				   zone);
603		}
604		for (i = 0; i < MAX_RECORDS; i++) {
605			if (strlen(state->deletes[i].name) > 0U) {
606				(void)del_name(state, &state->current[0],
607					       state->deletes[i].name,
608					       state->deletes[i].type,
609					       state->deletes[i].ttl,
610					       state->deletes[i].data);
611			}
612		}
613		for (i = 0; i < MAX_RECORDS; i++) {
614			if (strlen(state->adds[i].name) > 0U) {
615				(void)add_name(state, &state->current[0],
616					       state->adds[i].name,
617					       state->adds[i].type,
618					       state->adds[i].ttl,
619					       state->adds[i].data);
620			}
621		}
622	} else {
623		if (state->log != NULL) {
624			state->log(ISC_LOG_INFO,
625				   "dlz_example: cancelling "
626				   "transaction on zone %s",
627				   zone);
628		}
629	}
630	memset(state->adds, 0, sizeof(state->adds));
631	memset(state->deletes, 0, sizeof(state->deletes));
632}
633
634/*
635 * Configure a writeable zone
636 */
637isc_result_t
638dlz_configure(dns_view_t *view, dns_dlzdb_t *dlzdb, void *dbdata) {
639	struct dlz_example_data *state = (struct dlz_example_data *)dbdata;
640	isc_result_t result;
641
642	if (state->log != NULL) {
643		state->log(ISC_LOG_INFO, "dlz_example: starting configure");
644	}
645
646	if (state->writeable_zone == NULL) {
647		if (state->log != NULL) {
648			state->log(ISC_LOG_INFO, "dlz_example: no "
649						 "writeable_zone method "
650						 "available");
651		}
652		return (ISC_R_FAILURE);
653	}
654
655	result = state->writeable_zone(view, dlzdb, state->zone_name);
656	if (result != ISC_R_SUCCESS) {
657		if (state->log != NULL) {
658			state->log(ISC_LOG_ERROR,
659				   "dlz_example: failed to "
660				   "configure zone %s",
661				   state->zone_name);
662		}
663		return (result);
664	}
665
666	if (state->log != NULL) {
667		state->log(ISC_LOG_INFO,
668			   "dlz_example: configured writeable "
669			   "zone %s",
670			   state->zone_name);
671	}
672	return (ISC_R_SUCCESS);
673}
674
675/*
676 * Authorize a zone update
677 */
678bool
679dlz_ssumatch(const char *signer, const char *name, const char *tcpaddr,
680	     const char *type, const char *key, uint32_t keydatalen,
681	     unsigned char *keydata, void *dbdata) {
682	struct dlz_example_data *state = (struct dlz_example_data *)dbdata;
683
684	UNUSED(tcpaddr);
685	UNUSED(type);
686	UNUSED(key);
687	UNUSED(keydatalen);
688	UNUSED(keydata);
689
690	if (strncmp(name, "deny.", 5) == 0) {
691		if (state->log != NULL) {
692			state->log(ISC_LOG_INFO,
693				   "dlz_example: denying update "
694				   "of name=%s by %s",
695				   name, signer);
696		}
697		return (false);
698	}
699	if (state->log != NULL) {
700		state->log(ISC_LOG_INFO,
701			   "dlz_example: allowing update of "
702			   "name=%s by %s",
703			   name, signer);
704	}
705	return (true);
706}
707
708static isc_result_t
709modrdataset(struct dlz_example_data *state, const char *name,
710	    const char *rdatastr, struct record *list) {
711	char *full_name, *dclass, *type, *data, *ttlstr, *buf;
712	char absolute[1024];
713	isc_result_t result;
714	char *saveptr = NULL;
715
716	buf = strdup(rdatastr);
717	if (buf == NULL) {
718		return (ISC_R_FAILURE);
719	}
720
721	/*
722	 * The format is:
723	 * FULLNAME\tTTL\tDCLASS\tTYPE\tDATA
724	 *
725	 * The DATA field is space separated, and is in the data format
726	 * for the type used by dig
727	 */
728
729	full_name = strtok_r(buf, "\t", &saveptr);
730	if (full_name == NULL) {
731		goto error;
732	}
733
734	ttlstr = strtok_r(NULL, "\t", &saveptr);
735	if (ttlstr == NULL) {
736		goto error;
737	}
738
739	dclass = strtok_r(NULL, "\t", &saveptr);
740	if (dclass == NULL) {
741		goto error;
742	}
743
744	type = strtok_r(NULL, "\t", &saveptr);
745	if (type == NULL) {
746		goto error;
747	}
748
749	data = strtok_r(NULL, "\t", &saveptr);
750	if (data == NULL) {
751		goto error;
752	}
753
754	if (name[strlen(name) - 1] != '.') {
755		snprintf(absolute, sizeof(absolute), "%s.", name);
756		name = absolute;
757	}
758
759	result = add_name(state, list, name, type, strtoul(ttlstr, NULL, 10),
760			  data);
761	free(buf);
762	return (result);
763
764error:
765	free(buf);
766	return (ISC_R_FAILURE);
767}
768
769isc_result_t
770dlz_addrdataset(const char *name, const char *rdatastr, void *dbdata,
771		void *version) {
772	struct dlz_example_data *state = (struct dlz_example_data *)dbdata;
773
774	if (version != (void *)&state->transaction_started) {
775		return (ISC_R_FAILURE);
776	}
777
778	if (state->log != NULL) {
779		state->log(ISC_LOG_INFO, "dlz_example: adding rdataset %s '%s'",
780			   name, rdatastr);
781	}
782
783	return (modrdataset(state, name, rdatastr, &state->adds[0]));
784}
785
786isc_result_t
787dlz_subrdataset(const char *name, const char *rdatastr, void *dbdata,
788		void *version) {
789	struct dlz_example_data *state = (struct dlz_example_data *)dbdata;
790
791	if (version != (void *)&state->transaction_started) {
792		return (ISC_R_FAILURE);
793	}
794
795	if (state->log != NULL) {
796		state->log(ISC_LOG_INFO,
797			   "dlz_example: subtracting rdataset "
798			   "%s '%s'",
799			   name, rdatastr);
800	}
801
802	return (modrdataset(state, name, rdatastr, &state->deletes[0]));
803}
804
805isc_result_t
806dlz_delrdataset(const char *name, const char *type, void *dbdata,
807		void *version) {
808	struct dlz_example_data *state = (struct dlz_example_data *)dbdata;
809
810	if (version != (void *)&state->transaction_started) {
811		return (ISC_R_FAILURE);
812	}
813
814	if (state->log != NULL) {
815		state->log(ISC_LOG_INFO,
816			   "dlz_example: deleting rdataset %s "
817			   "of type %s",
818			   name, type);
819	}
820
821	return (ISC_R_SUCCESS);
822}
823