1/*	$NetBSD: dlz_example.c,v 1.2.8.1 2012/06/05 21:15:40 bouyer 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/* Id */
20
21/*
22 * This provides a very simple example of an external loadable DLZ
23 * driver, with update support.
24 */
25
26#include <stdio.h>
27#include <stdlib.h>
28#include <string.h>
29#include <stdarg.h>
30#include <stdint.h>
31
32#include "dlz_minimal.h"
33
34#ifdef WIN32
35#define STRTOK_R(a, b, c)	strtok_s(a, b, c)
36#elif defined(_REENTRANT)
37#define STRTOK_R(a, b, c)       strtok_r(a, b, c)
38#else
39#define STRTOK_R(a, b, c)       strtok(a, b)
40#endif
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	isc_boolean_t 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 isc_boolean_t
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 (ISC_TRUE);
77		}
78	}
79	return (ISC_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,
87	 const char *name, const char *type, dns_ttl_t ttl, const char *data)
88{
89	int i;
90	isc_boolean_t single = single_valued(type);
91	int first_empty = -1;
92
93	for (i = 0; i < MAX_RECORDS; i++) {
94		if (first_empty == -1 && strlen(list[i].name) == 0U) {
95			first_empty = i;
96		}
97		if (strcasecmp(list[i].name, name) != 0)
98			continue;
99		if (strcasecmp(list[i].type, type) != 0)
100			continue;
101		if (!single && strcasecmp(list[i].data, data) != 0)
102			continue;
103		break;
104	}
105	if (i == MAX_RECORDS && first_empty != -1) {
106		i = first_empty;
107	}
108	if (i == MAX_RECORDS) {
109		state->log(ISC_LOG_ERROR, "dlz_example: out of record space");
110		return (ISC_R_FAILURE);
111	}
112	strcpy(list[i].name, name);
113	strcpy(list[i].type, type);
114	strcpy(list[i].data, data);
115	list[i].ttl = ttl;
116	return (ISC_R_SUCCESS);
117}
118
119/*
120 * Delete a record from a list
121 */
122static isc_result_t
123del_name(struct dlz_example_data *state, struct record *list,
124	 const char *name, const char *type, dns_ttl_t ttl,
125	 const char *data)
126{
127	int i;
128
129	UNUSED(state);
130
131	for (i = 0; i < MAX_RECORDS; i++) {
132		if (strcasecmp(name, list[i].name) == 0 &&
133		    strcasecmp(type, list[i].type) == 0 &&
134		    strcasecmp(data, list[i].data) == 0 &&
135		    ttl == list[i].ttl) {
136			break;
137		}
138	}
139	if (i == MAX_RECORDS) {
140		return (ISC_R_NOTFOUND);
141	}
142	memset(&list[i], 0, sizeof(struct record));
143	return (ISC_R_SUCCESS);
144}
145
146static isc_result_t
147fmt_address(isc_sockaddr_t *addr, char *buffer, size_t size) {
148	char addr_buf[100];
149	const char *ret;
150	uint16_t port = 0;
151
152	switch (addr->type.sa.sa_family) {
153	case AF_INET:
154		port = ntohs(addr->type.sin.sin_port);
155		ret = inet_ntop(AF_INET, &addr->type.sin.sin_addr, addr_buf,
156				sizeof(addr_buf));
157		break;
158	case AF_INET6:
159		port = ntohs(addr->type.sin6.sin6_port);
160		ret = inet_ntop(AF_INET6, &addr->type.sin6.sin6_addr, addr_buf,
161				sizeof(addr_buf));
162		break;
163	default:
164		return (ISC_R_FAILURE);
165	}
166
167	if (ret == NULL)
168		return (ISC_R_FAILURE);
169
170	snprintf(buffer, size, "%s#%u", addr_buf, port);
171	return (ISC_R_SUCCESS);
172}
173
174/*
175 * Return the version of the API
176 */
177int
178dlz_version(unsigned int *flags) {
179	UNUSED(flags);
180	return (DLZ_DLOPEN_VERSION);
181}
182
183/*
184 * Remember a helper function from the bind9 dlz_dlopen driver
185 */
186static void
187b9_add_helper(struct dlz_example_data *state,
188	      const char *helper_name, void *ptr)
189{
190	if (strcmp(helper_name, "log") == 0)
191		state->log = (log_t *)ptr;
192	if (strcmp(helper_name, "putrr") == 0)
193		state->putrr = (dns_sdlz_putrr_t *)ptr;
194	if (strcmp(helper_name, "putnamedrr") == 0)
195		state->putnamedrr = (dns_sdlz_putnamedrr_t *)ptr;
196	if (strcmp(helper_name, "writeable_zone") == 0)
197		state->writeable_zone = (dns_dlz_writeablezone_t *)ptr;
198}
199
200
201/*
202 * Called to initialize the driver
203 */
204isc_result_t
205dlz_create(const char *dlzname, unsigned int argc, char *argv[],
206	   void **dbdata, ...)
207{
208	struct dlz_example_data *state;
209	const char *helper_name;
210	va_list ap;
211	char soa_data[200];
212
213	UNUSED(dlzname);
214
215	state = calloc(1, sizeof(struct dlz_example_data));
216	if (state == NULL)
217		return (ISC_R_NOMEMORY);
218
219	/* Fill in the helper functions */
220	va_start(ap, dbdata);
221	while ((helper_name = va_arg(ap, const char *)) != NULL) {
222		b9_add_helper(state, helper_name, va_arg(ap, void*));
223	}
224	va_end(ap);
225
226	if (argc < 2) {
227		state->log(ISC_LOG_ERROR,
228			   "dlz_example: please specify a zone name");
229		return (ISC_R_FAILURE);
230	}
231
232	state->zone_name = strdup(argv[1]);
233
234	sprintf(soa_data, "%s hostmaster.%s 123 900 600 86400 3600",
235		state->zone_name, state->zone_name);
236
237	add_name(state, &state->current[0], state->zone_name,
238		 "soa", 3600, soa_data);
239	add_name(state, &state->current[0], state->zone_name,
240		 "ns", 3600, state->zone_name);
241	add_name(state, &state->current[0], state->zone_name,
242		 "a", 1800, "10.53.0.1");
243
244	state->log(ISC_LOG_INFO,
245		   "dlz_example: started for zone %s",
246		   state->zone_name);
247
248	*dbdata = state;
249	return (ISC_R_SUCCESS);
250}
251
252/*
253 * Shut down the backend
254 */
255void
256dlz_destroy(void *dbdata) {
257	struct dlz_example_data *state = (struct dlz_example_data *)dbdata;
258
259	state->log(ISC_LOG_INFO,
260		   "dlz_example: shutting down zone %s",
261		   state->zone_name);
262	free(state->zone_name);
263	free(state);
264}
265
266
267/*
268 * See if we handle a given zone
269 */
270isc_result_t
271dlz_findzonedb(void *dbdata, const char *name) {
272	struct dlz_example_data *state = (struct dlz_example_data *)dbdata;
273
274	if (strcasecmp(state->zone_name, name) == 0)
275		return (ISC_R_SUCCESS);
276
277	return (ISC_R_NOTFOUND);
278}
279
280/*
281 * Look up one record in the sample database.
282 *
283 * If the queryname is "source-addr", we add a TXT record containing
284 * the address of the client; this demonstrates the use of 'methods'
285 * and 'clientinfo'.
286 */
287isc_result_t
288dlz_lookup(const char *zone, const char *name, void *dbdata,
289	   dns_sdlzlookup_t *lookup, dns_clientinfomethods_t *methods,
290	   dns_clientinfo_t *clientinfo)
291{
292	isc_result_t result;
293	struct dlz_example_data *state = (struct dlz_example_data *)dbdata;
294	isc_boolean_t found = ISC_FALSE;
295	isc_sockaddr_t *src;
296	char full_name[100];
297	int i;
298
299	UNUSED(zone);
300
301	if (strcmp(name, "@") == 0)
302		strcpy(full_name, state->zone_name);
303	else
304		sprintf(full_name, "%s.%s", name, state->zone_name);
305
306	if (strcmp(name, "source-addr") == 0) {
307		char buf[100];
308		strcpy(buf, "unknown");
309		if (methods != NULL &&
310		    methods->version - methods->age >=
311			    DNS_CLIENTINFOMETHODS_VERSION)
312		{
313			methods->sourceip(clientinfo, &src);
314			fmt_address(src, buf, sizeof(buf));
315		}
316
317		fprintf(stderr, "connection from: %s\n", buf);
318
319		found = ISC_TRUE;
320		result = state->putrr(lookup, "TXT", 0, buf);
321		if (result != ISC_R_SUCCESS)
322			return (result);
323	}
324
325	for (i = 0; i < MAX_RECORDS; i++) {
326		if (strcasecmp(state->current[i].name, full_name) == 0) {
327			found = ISC_TRUE;
328			result = state->putrr(lookup, state->current[i].type,
329					      state->current[i].ttl,
330					      state->current[i].data);
331			if (result != ISC_R_SUCCESS)
332				return (result);
333		}
334	}
335
336	if (!found)
337		return (ISC_R_NOTFOUND);
338
339	return (ISC_R_SUCCESS);
340}
341
342
343/*
344 * See if a zone transfer is allowed
345 */
346isc_result_t
347dlz_allowzonexfr(void *dbdata, const char *name, const char *client) {
348	UNUSED(client);
349
350	/* Just say yes for all our zones */
351	return (dlz_findzonedb(dbdata, name));
352}
353
354/*
355 * Perform a zone transfer
356 */
357isc_result_t
358dlz_allnodes(const char *zone, void *dbdata, dns_sdlzallnodes_t *allnodes) {
359	struct dlz_example_data *state = (struct dlz_example_data *)dbdata;
360	int i;
361
362	UNUSED(zone);
363
364	for (i = 0; i < MAX_RECORDS; i++) {
365		isc_result_t result;
366		if (strlen(state->current[i].name) == 0U) {
367			continue;
368		}
369		result = state->putnamedrr(allnodes, state->current[i].name,
370					   state->current[i].type,
371					   state->current[i].ttl,
372					   state->current[i].data);
373		if (result != ISC_R_SUCCESS)
374			return (result);
375	}
376
377	return (ISC_R_SUCCESS);
378}
379
380
381/*
382 * Start a transaction
383 */
384isc_result_t
385dlz_newversion(const char *zone, void *dbdata, void **versionp) {
386	struct dlz_example_data *state = (struct dlz_example_data *)dbdata;
387
388	if (state->transaction_started) {
389		state->log(ISC_LOG_INFO,
390			   "dlz_example: transaction already "
391			   "started for zone %s", zone);
392		return (ISC_R_FAILURE);
393	}
394
395	state->transaction_started = ISC_TRUE;
396	*versionp = (void *) &state->transaction_started;
397
398	return (ISC_R_SUCCESS);
399}
400
401/*
402 * End a transaction
403 */
404void
405dlz_closeversion(const char *zone, isc_boolean_t commit,
406		 void *dbdata, void **versionp)
407{
408	struct dlz_example_data *state = (struct dlz_example_data *)dbdata;
409
410	if (!state->transaction_started) {
411		state->log(ISC_LOG_INFO,
412			   "dlz_example: transaction not started for zone %s",
413			   zone);
414		*versionp = NULL;
415		return;
416	}
417
418	state->transaction_started = ISC_FALSE;
419
420	*versionp = NULL;
421
422	if (commit) {
423		int i;
424		state->log(ISC_LOG_INFO,
425			   "dlz_example: committing transaction on zone %s",
426			   zone);
427		for (i = 0; i < MAX_RECORDS; i++) {
428			if (strlen(state->adds[i].name) > 0U) {
429				add_name(state, &state->current[0],
430					 state->adds[i].name,
431					 state->adds[i].type,
432					 state->adds[i].ttl,
433					 state->adds[i].data);
434			}
435		}
436		for (i = 0; i < MAX_RECORDS; i++) {
437			if (strlen(state->deletes[i].name) > 0U) {
438				del_name(state, &state->current[0],
439					 state->deletes[i].name,
440					 state->deletes[i].type,
441					 state->deletes[i].ttl,
442					 state->deletes[i].data);
443			}
444		}
445	} else {
446		state->log(ISC_LOG_INFO,
447			   "dlz_example: cancelling transaction on zone %s",
448			   zone);
449	}
450	memset(state->adds, 0, sizeof(state->adds));
451	memset(state->deletes, 0, sizeof(state->deletes));
452}
453
454
455/*
456 * Configure a writeable zone
457 */
458isc_result_t
459dlz_configure(dns_view_t *view, void *dbdata) {
460	struct dlz_example_data *state = (struct dlz_example_data *)dbdata;
461	isc_result_t result;
462
463
464	state->log(ISC_LOG_INFO, "dlz_example: starting configure");
465	if (state->writeable_zone == NULL) {
466		state->log(ISC_LOG_INFO,
467			   "dlz_example: no writeable_zone method available");
468		return (ISC_R_FAILURE);
469	}
470
471	result = state->writeable_zone(view, state->zone_name);
472	if (result != ISC_R_SUCCESS) {
473		state->log(ISC_LOG_ERROR,
474			   "dlz_example: failed to configure zone %s",
475			   state->zone_name);
476		return (result);
477	}
478
479	state->log(ISC_LOG_INFO,
480		   "dlz_example: configured writeable zone %s",
481		   state->zone_name);
482	return (ISC_R_SUCCESS);
483}
484
485/*
486 * Authorize a zone update
487 */
488isc_boolean_t
489dlz_ssumatch(const char *signer, const char *name, const char *tcpaddr,
490	     const char *type, const char *key, uint32_t keydatalen,
491	     unsigned char *keydata, void *dbdata)
492{
493	struct dlz_example_data *state = (struct dlz_example_data *)dbdata;
494
495	UNUSED(tcpaddr);
496	UNUSED(type);
497	UNUSED(key);
498	UNUSED(keydatalen);
499	UNUSED(keydata);
500
501	if (strncmp(name, "deny.", 5) == 0) {
502		state->log(ISC_LOG_INFO,
503			   "dlz_example: denying update of name=%s by %s",
504			   name, signer);
505		return (ISC_FALSE);
506	}
507	state->log(ISC_LOG_INFO,
508		   "dlz_example: allowing update of name=%s by %s",
509		   name, signer);
510	return (ISC_TRUE);
511}
512
513
514static isc_result_t
515modrdataset(struct dlz_example_data *state, const char *name,
516	    const char *rdatastr, struct record *list)
517{
518	char *full_name, *dclass, *type, *data, *ttlstr;
519	char *buf = strdup(rdatastr);
520	isc_result_t result;
521#if defined(WIN32) || defined(_REENTRANT)
522	char *saveptr = NULL;
523#endif
524
525	/*
526	 * The format is:
527	 * FULLNAME\tTTL\tDCLASS\tTYPE\tDATA
528	 *
529	 * The DATA field is space separated, and is in the data format
530	 * for the type used by dig
531	 */
532
533	full_name = STRTOK_R(buf, "\t", &saveptr);
534	if (full_name == NULL)
535		return (ISC_R_FAILURE);
536
537	ttlstr = STRTOK_R(NULL, "\t", &saveptr);
538	if (ttlstr == NULL)
539		return (ISC_R_FAILURE);
540
541	dclass = STRTOK_R(NULL, "\t", &saveptr);
542	if (dclass == NULL)
543		return (ISC_R_FAILURE);
544
545	type = STRTOK_R(NULL, "\t", &saveptr);
546	if (type == NULL)
547		return (ISC_R_FAILURE);
548
549	data = STRTOK_R(NULL, "\t", &saveptr);
550	if (data == NULL)
551		return (ISC_R_FAILURE);
552
553	result = add_name(state, list, name, type,
554			  strtoul(ttlstr, NULL, 10), data);
555	free(buf);
556	return (result);
557}
558
559
560isc_result_t
561dlz_addrdataset(const char *name, const char *rdatastr,
562		void *dbdata, void *version)
563{
564	struct dlz_example_data *state = (struct dlz_example_data *)dbdata;
565
566	if (version != (void *) &state->transaction_started)
567		return (ISC_R_FAILURE);
568
569	state->log(ISC_LOG_INFO,
570		   "dlz_example: adding rdataset %s '%s'",
571		   name, rdatastr);
572
573	return (modrdataset(state, name, rdatastr, &state->adds[0]));
574}
575
576isc_result_t
577dlz_subrdataset(const char *name, const char *rdatastr,
578		void *dbdata, void *version)
579{
580	struct dlz_example_data *state = (struct dlz_example_data *)dbdata;
581
582	if (version != (void *) &state->transaction_started)
583		return (ISC_R_FAILURE);
584
585	state->log(ISC_LOG_INFO,
586		   "dlz_example: subtracting rdataset %s '%s'",
587		   name, rdatastr);
588
589	return (modrdataset(state, name, rdatastr, &state->deletes[0]));
590}
591
592
593isc_result_t
594dlz_delrdataset(const char *name, const char *type,
595		void *dbdata, void *version)
596{
597	struct dlz_example_data *state = (struct dlz_example_data *)dbdata;
598
599	if (version != (void *) &state->transaction_started)
600		return (ISC_R_FAILURE);
601
602	state->log(ISC_LOG_INFO,
603		   "dlz_example: deleting rdataset %s of type %s",
604		   name, type);
605
606	return (ISC_R_SUCCESS);
607}
608