1/*
2 * Copyright (C) 2010 Andrew Tridgell
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the
6 * above copyright notice and this permission notice appear in all
7 * copies.
8 *
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR
10 * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
11 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
12 * STICHTING NLNET BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
13 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
14 * 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
16 * USE OR 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 <stdbool.h>
28#include <stdint.h>
29#include <stdarg.h>
30
31#include "dlz_minimal.h"
32
33
34/* for this simple example, use fixed sized strings */
35struct record {
36	char name[100];
37	char type[10];
38	char data[200];
39	uint32_t ttl;
40};
41
42#define MAX_RECORDS 100
43
44struct dlz_example_data {
45	char *zone_name;
46
47	/* an example driver doesn't need good memory management :-) */
48	struct record current[MAX_RECORDS];
49	struct record adds[MAX_RECORDS];
50	struct record deletes[MAX_RECORDS];
51
52	bool transaction_started;
53
54	/* helper functions from the dlz_dlopen driver */
55	void (*log)(int level, const char *fmt, ...);
56	isc_result_t (*putrr)(dns_sdlzlookup_t *handle, const char *type,
57			      dns_ttl_t ttl, const char *data);
58	isc_result_t (*putnamedrr)(dns_sdlzlookup_t *handle, const char *name,
59				   const char *type, dns_ttl_t ttl, const char *data);
60	isc_result_t (*writeable_zone)(dns_view_t *view, const char *zone_name);
61};
62
63static bool single_valued(const char *type)
64{
65	const char *single[] = { "soa", "cname", NULL };
66	int i;
67	for (i=0; single[i]; i++) {
68		if (strcasecmp(single[i], type) == 0) {
69			return true;
70		}
71	}
72	return false;
73}
74
75/*
76  add a record to a list
77 */
78static isc_result_t add_name(struct dlz_example_data *state,
79			     struct record *list, const char *name, const char *type,
80			     uint32_t ttl, const char *data)
81{
82	int i;
83	bool single = single_valued(type);
84	int first_empty = -1;
85
86	for (i=0; i<MAX_RECORDS; i++) {
87		if (first_empty == -1 && strlen(list[i].name) == 0) {
88			first_empty = i;
89		}
90		if (strcasecmp(list[i].name, name) != 0)
91			continue;
92		if (strcasecmp(list[i].type, type) != 0)
93			continue;
94		if (!single && strcasecmp(list[i].data, data) != 0)
95			continue;
96		break;
97	}
98	if (i == MAX_RECORDS && first_empty != -1) {
99		i = first_empty;
100	}
101	if (i == MAX_RECORDS) {
102		state->log(ISC_LOG_ERROR, "dlz_example: out of record space");
103		return ISC_R_FAILURE;
104	}
105	strcpy(list[i].name, name);
106	strcpy(list[i].type, type);
107	strcpy(list[i].data, data);
108	list[i].ttl = ttl;
109	return ISC_R_SUCCESS;
110}
111
112/*
113  delete a record from a list
114 */
115static isc_result_t del_name(struct dlz_example_data *state,
116			     struct record *list, const char *name, const char *type,
117			     uint32_t ttl, const char *data)
118{
119	int i;
120	for (i=0; i<MAX_RECORDS; i++) {
121		if (strcasecmp(name, list[i].name) == 0 &&
122		    strcasecmp(type, list[i].type) == 0 &&
123		    strcasecmp(data, list[i].data) == 0 &&
124		    ttl == list[i].ttl) {
125			break;
126		}
127	}
128	if (i == MAX_RECORDS) {
129		return ISC_R_NOTFOUND;
130	}
131	memset(&list[i], 0, sizeof(struct record));
132	return ISC_R_SUCCESS;
133}
134
135
136
137/*
138  return the version of the API
139 */
140int dlz_version(unsigned int *flags)
141{
142	return DLZ_DLOPEN_VERSION;
143}
144
145/*
146   remember a helper function from the bind9 dlz_dlopen driver
147 */
148static void b9_add_helper(struct dlz_example_data *state, const char *helper_name, void *ptr)
149{
150	if (strcmp(helper_name, "log") == 0) {
151		state->log = ptr;
152	}
153	if (strcmp(helper_name, "putrr") == 0) {
154		state->putrr = ptr;
155	}
156	if (strcmp(helper_name, "putnamedrr") == 0) {
157		state->putnamedrr = ptr;
158	}
159	if (strcmp(helper_name, "writeable_zone") == 0) {
160		state->writeable_zone = ptr;
161	}
162}
163
164
165/*
166  called to initialise the driver
167 */
168isc_result_t dlz_create(const char *dlzname, unsigned int argc, char *argv[],
169			void **dbdata, ...)
170{
171	struct dlz_example_data *state;
172	const char *helper_name;
173	va_list ap;
174	char soa_data[200];
175
176	state = calloc(1, sizeof(struct dlz_example_data));
177	if (state == NULL) {
178		return ISC_R_NOMEMORY;
179	}
180
181	/* fill in the helper functions */
182	va_start(ap, dbdata);
183	while ((helper_name = va_arg(ap, const char *)) != NULL) {
184		b9_add_helper(state, helper_name, va_arg(ap, void*));
185	}
186	va_end(ap);
187
188	if (argc < 2) {
189		state->log(ISC_LOG_ERROR, "dlz_example: please specify a zone name");
190		return ISC_R_FAILURE;
191	}
192
193	state->zone_name = strdup(argv[1]);
194
195	sprintf(soa_data, "%s hostmaster.%s 123 900 600 86400 3600",
196		state->zone_name, state->zone_name);
197
198	add_name(state, &state->current[0], state->zone_name, "soa", 3600, soa_data);
199	add_name(state, &state->current[0], state->zone_name, "ns", 3600, state->zone_name);
200	add_name(state, &state->current[0], state->zone_name, "a", 1800, "10.53.0.1");
201
202	state->log(ISC_LOG_INFO, "dlz_example: started for zone %s", state->zone_name);
203
204	*dbdata = state;
205	return ISC_R_SUCCESS;
206}
207
208/*
209  shutdown the backend
210 */
211void dlz_destroy(void *dbdata)
212{
213	struct dlz_example_data *state = (struct dlz_example_data *)dbdata;
214	state->log(ISC_LOG_INFO, "dlz_example: shutting down zone %s", state->zone_name);
215	free(state->zone_name);
216	free(state);
217}
218
219
220/*
221  see if we handle a given zone
222 */
223isc_result_t dlz_findzonedb(void *dbdata, const char *name)
224{
225	struct dlz_example_data *state = (struct dlz_example_data *)dbdata;
226	if (strcasecmp(state->zone_name, name) == 0) {
227		return ISC_R_SUCCESS;
228	}
229	return ISC_R_NOTFOUND;
230}
231
232
233
234/*
235  lookup one record
236 */
237isc_result_t dlz_lookup(const char *zone, const char *name,
238			void *dbdata, dns_sdlzlookup_t *lookup)
239{
240	struct dlz_example_data *state = (struct dlz_example_data *)dbdata;
241	int i;
242	bool found = false;
243	char full_name[100];
244
245	if (strcmp(name, "@") == 0) {
246		strcpy(full_name, state->zone_name);
247	} else {
248		sprintf(full_name, "%s.%s", name, state->zone_name);
249	}
250	for (i=0; i<MAX_RECORDS; i++) {
251		if (strcasecmp(state->current[i].name, full_name) == 0) {
252			isc_result_t result;
253			found = true;
254			result = state->putrr(lookup, state->current[i].type,
255					      state->current[i].ttl, state->current[i].data);
256			if (result != ISC_R_SUCCESS) {
257				return result;
258			}
259		}
260	}
261	if (!found) {
262		return ISC_R_NOTFOUND;
263	}
264	return ISC_R_SUCCESS;
265}
266
267
268/*
269  see if a zone transfer is allowed
270 */
271isc_result_t dlz_allowzonexfr(void *dbdata, const char *name, const char *client)
272{
273	/* just say yes for all our zones */
274	return dlz_findzonedb(dbdata, name);
275}
276
277/*
278  perform a zone transfer
279 */
280isc_result_t dlz_allnodes(const char *zone, void *dbdata,
281			  dns_sdlzallnodes_t *allnodes)
282{
283	struct dlz_example_data *state = (struct dlz_example_data *)dbdata;
284	int i;
285
286	for (i=0; i<MAX_RECORDS; i++) {
287		isc_result_t result;
288		if (strlen(state->current[i].name) == 0) {
289			continue;
290		}
291		result = state->putnamedrr(allnodes, state->current[i].name, state->current[i].type,
292					   state->current[i].ttl, state->current[i].data);
293		if (result != ISC_R_SUCCESS) {
294			return result;
295		}
296	}
297
298	return ISC_R_SUCCESS;
299}
300
301
302/*
303  start a transaction
304 */
305isc_result_t dlz_newversion(const char *zone, void *dbdata, void **versionp)
306{
307	struct dlz_example_data *state = (struct dlz_example_data *)dbdata;
308
309	if (state->transaction_started) {
310		state->log(ISC_LOG_INFO, "dlz_example: transaction already started for zone %s", zone);
311		return ISC_R_FAILURE;
312	}
313
314	state->transaction_started = true;
315
316	*versionp = (void *) &state->transaction_started;
317
318	return ISC_R_SUCCESS;
319}
320
321/*
322  end a transaction
323 */
324void dlz_closeversion(const char *zone, isc_boolean_t commit, void *dbdata, void **versionp)
325{
326	struct dlz_example_data *state = (struct dlz_example_data *)dbdata;
327
328	if (!state->transaction_started) {
329		state->log(ISC_LOG_INFO, "dlz_example: transaction not started for zone %s", zone);
330		*versionp = NULL;
331		return;
332	}
333
334	state->transaction_started = false;
335
336	*versionp = NULL;
337
338	if (commit) {
339		int i;
340		state->log(ISC_LOG_INFO, "dlz_example: committing transaction on zone %s", zone);
341		for (i=0; i<MAX_RECORDS; i++) {
342			if (strlen(state->adds[i].name) > 0) {
343				add_name(state, &state->current[0],
344					 state->adds[i].name,
345					 state->adds[i].type,
346					 state->adds[i].ttl,
347					 state->adds[i].data);
348			}
349		}
350		for (i=0; i<MAX_RECORDS; i++) {
351			if (strlen(state->deletes[i].name) > 0) {
352				del_name(state, &state->current[0],
353					 state->deletes[i].name,
354					 state->deletes[i].type,
355					 state->deletes[i].ttl,
356					 state->deletes[i].data);
357			}
358		}
359	} else {
360		state->log(ISC_LOG_INFO, "dlz_example: cancelling transaction on zone %s", zone);
361	}
362	memset(state->adds, 0, sizeof(state->adds));
363	memset(state->deletes, 0, sizeof(state->deletes));
364}
365
366
367/*
368  configure a writeable zone
369 */
370isc_result_t dlz_configure(dns_view_t *view, void *dbdata)
371{
372	struct dlz_example_data *state = (struct dlz_example_data *)dbdata;
373	isc_result_t result;
374
375
376	state->log(ISC_LOG_INFO, "dlz_example: starting configure");
377	if (state->writeable_zone == NULL) {
378		state->log(ISC_LOG_INFO, "dlz_example: no writeable_zone method available");
379		return ISC_R_FAILURE;
380	}
381
382	result = state->writeable_zone(view, state->zone_name);
383	if (result != ISC_R_SUCCESS) {
384		state->log(ISC_LOG_ERROR, "dlz_example: failed to configure zone %s", state->zone_name);
385		return result;
386	}
387
388	state->log(ISC_LOG_INFO, "dlz_example: configured writeable zone %s", state->zone_name);
389	return ISC_R_SUCCESS;
390}
391
392/*
393  authorize a zone update
394 */
395isc_boolean_t dlz_ssumatch(const char *signer, const char *name, const char *tcpaddr,
396			   const char *type, const char *key, uint32_t keydatalen, uint8_t *keydata,
397			   void *dbdata)
398{
399	struct dlz_example_data *state = (struct dlz_example_data *)dbdata;
400	if (strncmp(name, "deny.", 5) == 0) {
401		state->log(ISC_LOG_INFO, "dlz_example: denying update of name=%s by %s",
402			   name, signer);
403		return false;
404	}
405	state->log(ISC_LOG_INFO, "dlz_example: allowing update of name=%s by %s",
406		   name, signer);
407	return true;
408}
409
410
411static isc_result_t modrdataset(struct dlz_example_data *state, const char *name, const char *rdatastr,
412				struct record *list)
413{
414	char *full_name, *dclass, *type, *data, *ttlstr;
415	char *buf = strdup(rdatastr);
416	isc_result_t result;
417	char *saveptr = NULL;
418
419	/*
420	  the format is:
421	  FULLNAME\tTTL\tDCLASS\tTYPE\tDATA
422
423	  The DATA field is space separated, and is in the data format
424	  for the type used by dig
425	 */
426
427	full_name = strtok_r(buf, "\t", &saveptr);
428	if (full_name == NULL) return ISC_R_FAILURE;
429	ttlstr    = strtok_r(NULL, "\t", &saveptr);
430	if (ttlstr == NULL) return ISC_R_FAILURE;
431	dclass    = strtok_r(NULL, "\t", &saveptr);
432	if (dclass == NULL) return ISC_R_FAILURE;
433	type      = strtok_r(NULL, "\t", &saveptr);
434	if (type == NULL) return ISC_R_FAILURE;
435	data	  = strtok_r(NULL, "\t", &saveptr);
436	if (data == NULL) return ISC_R_FAILURE;
437
438	result = add_name(state, list, name, type, strtoul(ttlstr, NULL, 10), data);
439	free(buf);
440	return result;
441}
442
443
444isc_result_t dlz_addrdataset(const char *name, const char *rdatastr, void *dbdata, void *version)
445{
446	struct dlz_example_data *state = (struct dlz_example_data *)dbdata;
447
448	if (version != (void *) &state->transaction_started) {
449		return ISC_R_FAILURE;
450	}
451
452	state->log(ISC_LOG_INFO, "dlz_example: adding rdataset %s '%s'", name, rdatastr);
453
454	return modrdataset(state, name, rdatastr, &state->adds[0]);
455}
456
457isc_result_t dlz_subrdataset(const char *name, const char *rdatastr, void *dbdata, void *version)
458{
459	struct dlz_example_data *state = (struct dlz_example_data *)dbdata;
460
461	if (version != (void *) &state->transaction_started) {
462		return ISC_R_FAILURE;
463	}
464
465	state->log(ISC_LOG_INFO, "dlz_example: subtracting rdataset %s '%s'", name, rdatastr);
466
467	return modrdataset(state, name, rdatastr, &state->deletes[0]);
468}
469
470
471isc_result_t dlz_delrdataset(const char *name, const char *type, void *dbdata, void *version)
472{
473	struct dlz_example_data *state = (struct dlz_example_data *)dbdata;
474
475	if (version != (void *) &state->transaction_started) {
476		return ISC_R_FAILURE;
477	}
478
479	state->log(ISC_LOG_INFO, "dlz_example: deleting rdataset %s of type %s", name, type);
480
481	return ISC_R_SUCCESS;
482}
483