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