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