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