1/*
2 * Copyright (C) 2011-2013  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$ */
18
19#include <config.h>
20
21#include <stdio.h>
22#include <string.h>
23#include <stdlib.h>
24#include <dlfcn.h>
25
26#include <dns/log.h>
27#include <dns/result.h>
28#include <dns/dlz_dlopen.h>
29
30#include <isc/mem.h>
31#include <isc/print.h>
32#include <isc/result.h>
33#include <isc/util.h>
34
35#include <named/globals.h>
36
37#include <dlz/dlz_dlopen_driver.h>
38
39#ifdef ISC_DLZ_DLOPEN
40static dns_sdlzimplementation_t *dlz_dlopen = NULL;
41
42
43typedef struct dlopen_data {
44	isc_mem_t *mctx;
45	char *dl_path;
46	char *dlzname;
47	void *dl_handle;
48	void *dbdata;
49	unsigned int flags;
50	isc_mutex_t lock;
51	int version;
52	isc_boolean_t in_configure;
53
54	dlz_dlopen_version_t *dlz_version;
55	dlz_dlopen_create_t *dlz_create;
56	dlz_dlopen_findzonedb_t *dlz_findzonedb;
57	dlz_dlopen_lookup_t *dlz_lookup;
58	dlz_dlopen_authority_t *dlz_authority;
59	dlz_dlopen_allnodes_t *dlz_allnodes;
60	dlz_dlopen_allowzonexfr_t *dlz_allowzonexfr;
61	dlz_dlopen_newversion_t *dlz_newversion;
62	dlz_dlopen_closeversion_t *dlz_closeversion;
63	dlz_dlopen_configure_t *dlz_configure;
64	dlz_dlopen_ssumatch_t *dlz_ssumatch;
65	dlz_dlopen_addrdataset_t *dlz_addrdataset;
66	dlz_dlopen_subrdataset_t *dlz_subrdataset;
67	dlz_dlopen_delrdataset_t *dlz_delrdataset;
68	dlz_dlopen_destroy_t *dlz_destroy;
69} dlopen_data_t;
70
71/* Modules can choose whether they are lock-safe or not. */
72#define MAYBE_LOCK(cd) \
73	do { \
74		if ((cd->flags & DNS_SDLZFLAG_THREADSAFE) == 0 && \
75		    cd->in_configure == ISC_FALSE) \
76			LOCK(&cd->lock); \
77	} while (0)
78
79#define MAYBE_UNLOCK(cd) \
80	do { \
81		if ((cd->flags & DNS_SDLZFLAG_THREADSAFE) == 0 && \
82		    cd->in_configure == ISC_FALSE) \
83			UNLOCK(&cd->lock); \
84	} while (0)
85
86/*
87 * Log a message at the given level.
88 */
89static void dlopen_log(int level, const char *fmt, ...)
90{
91	va_list ap;
92	va_start(ap, fmt);
93	isc_log_vwrite(dns_lctx, DNS_LOGCATEGORY_DATABASE,
94		       DNS_LOGMODULE_DLZ, ISC_LOG_DEBUG(level),
95		       fmt, ap);
96	va_end(ap);
97}
98
99/*
100 * SDLZ methods
101 */
102
103static isc_result_t
104dlopen_dlz_allnodes(const char *zone, void *driverarg, void *dbdata,
105		    dns_sdlzallnodes_t *allnodes)
106{
107	dlopen_data_t *cd = (dlopen_data_t *) dbdata;
108	isc_result_t result;
109
110
111	UNUSED(driverarg);
112
113	if (cd->dlz_allnodes == NULL) {
114		return (ISC_R_NOPERM);
115	}
116
117	MAYBE_LOCK(cd);
118	result = cd->dlz_allnodes(zone, cd->dbdata, allnodes);
119	MAYBE_UNLOCK(cd);
120	return (result);
121}
122
123
124static isc_result_t
125dlopen_dlz_allowzonexfr(void *driverarg, void *dbdata, const char *name,
126			const char *client)
127{
128	dlopen_data_t *cd = (dlopen_data_t *) dbdata;
129	isc_result_t result;
130
131	UNUSED(driverarg);
132
133
134	if (cd->dlz_allowzonexfr == NULL) {
135		return (ISC_R_NOPERM);
136	}
137
138	MAYBE_LOCK(cd);
139	result = cd->dlz_allowzonexfr(cd->dbdata, name, client);
140	MAYBE_UNLOCK(cd);
141	return (result);
142}
143
144static isc_result_t
145dlopen_dlz_authority(const char *zone, void *driverarg, void *dbdata,
146		     dns_sdlzlookup_t *lookup)
147{
148	dlopen_data_t *cd = (dlopen_data_t *) dbdata;
149	isc_result_t result;
150
151	UNUSED(driverarg);
152
153	if (cd->dlz_authority == NULL) {
154		return (ISC_R_NOTIMPLEMENTED);
155	}
156
157	MAYBE_LOCK(cd);
158	result = cd->dlz_authority(zone, cd->dbdata, lookup);
159	MAYBE_UNLOCK(cd);
160	return (result);
161}
162
163static isc_result_t
164dlopen_dlz_findzonedb(void *driverarg, void *dbdata, const char *name)
165{
166	dlopen_data_t *cd = (dlopen_data_t *) dbdata;
167	isc_result_t result;
168
169	UNUSED(driverarg);
170
171	MAYBE_LOCK(cd);
172	result = cd->dlz_findzonedb(cd->dbdata, name);
173	MAYBE_UNLOCK(cd);
174	return (result);
175}
176
177
178static isc_result_t
179dlopen_dlz_lookup(const char *zone, const char *name, void *driverarg,
180		  void *dbdata, dns_sdlzlookup_t *lookup,
181		  dns_clientinfomethods_t *methods,
182		  dns_clientinfo_t *clientinfo)
183{
184	dlopen_data_t *cd = (dlopen_data_t *) dbdata;
185	isc_result_t result;
186
187	UNUSED(driverarg);
188
189	MAYBE_LOCK(cd);
190	result = cd->dlz_lookup(zone, name, cd->dbdata, lookup,
191				methods, clientinfo);
192	MAYBE_UNLOCK(cd);
193	return (result);
194}
195
196/*
197 * Load a symbol from the library
198 */
199static void *
200dl_load_symbol(dlopen_data_t *cd, const char *symbol, isc_boolean_t mandatory) {
201	void *ptr = dlsym(cd->dl_handle, symbol);
202	if (ptr == NULL && mandatory) {
203		dlopen_log(ISC_LOG_ERROR,
204			   "dlz_dlopen: library '%s' is missing "
205			   "required symbol '%s'", cd->dl_path, symbol);
206	}
207	return (ptr);
208}
209
210/*
211 * Called at startup for each dlopen zone in named.conf
212 */
213static isc_result_t
214dlopen_dlz_create(const char *dlzname, unsigned int argc, char *argv[],
215		  void *driverarg, void **dbdata)
216{
217	dlopen_data_t *cd;
218	isc_mem_t *mctx = NULL;
219	isc_result_t result = ISC_R_FAILURE;
220	int dlopen_flags = 0;
221
222	UNUSED(driverarg);
223
224	if (argc < 2) {
225		dlopen_log(ISC_LOG_ERROR,
226			   "dlz_dlopen driver for '%s' needs a path to "
227			   "the shared library", dlzname);
228		return (ISC_R_FAILURE);
229	}
230
231	result = isc_mem_create(0, 0, &mctx);
232	if (result != ISC_R_SUCCESS)
233		return (result);
234
235	cd = isc_mem_get(mctx, sizeof(*cd));
236	if (cd == NULL) {
237		isc_mem_destroy(&mctx);
238		return (ISC_R_NOMEMORY);
239	}
240	memset(cd, 0, sizeof(*cd));
241
242	cd->mctx = mctx;
243
244	cd->dl_path = isc_mem_strdup(cd->mctx, argv[1]);
245	if (cd->dl_path == NULL) {
246		goto failed;
247	}
248
249	cd->dlzname = isc_mem_strdup(cd->mctx, dlzname);
250	if (cd->dlzname == NULL) {
251		goto failed;
252	}
253
254	/* Initialize the lock */
255	result = isc_mutex_init(&cd->lock);
256	if (result != ISC_R_SUCCESS)
257		goto failed;
258
259	/* Open the library */
260	dlopen_flags = RTLD_NOW|RTLD_GLOBAL;
261
262#ifdef RTLD_DEEPBIND
263	/*
264	 * If RTLD_DEEPBIND is available then use it. This can avoid
265	 * issues with a module using a different version of a system
266	 * library than one that bind9 uses. For example, bind9 may link
267	 * to MIT kerberos, but the module may use Heimdal. If we don't
268	 * use RTLD_DEEPBIND then we could end up with Heimdal functions
269	 * calling MIT functions, which leads to bizarre results (usually
270	 * a segfault).
271	 */
272	dlopen_flags |= RTLD_DEEPBIND;
273#endif
274
275	cd->dl_handle = dlopen(cd->dl_path, dlopen_flags);
276	if (cd->dl_handle == NULL) {
277		dlopen_log(ISC_LOG_ERROR,
278			   "dlz_dlopen failed to open library '%s' - %s",
279			   cd->dl_path, dlerror());
280		goto failed;
281	}
282
283	/* Find the symbols */
284	cd->dlz_version = (dlz_dlopen_version_t *)
285		dl_load_symbol(cd, "dlz_version", ISC_TRUE);
286	cd->dlz_create = (dlz_dlopen_create_t *)
287		dl_load_symbol(cd, "dlz_create", ISC_TRUE);
288	cd->dlz_lookup = (dlz_dlopen_lookup_t *)
289		dl_load_symbol(cd, "dlz_lookup", ISC_TRUE);
290	cd->dlz_findzonedb = (dlz_dlopen_findzonedb_t *)
291		dl_load_symbol(cd, "dlz_findzonedb", ISC_TRUE);
292
293	if (cd->dlz_create == NULL ||
294	    cd->dlz_lookup == NULL ||
295	    cd->dlz_findzonedb == NULL)
296	{
297		/* We're missing a required symbol */
298		goto failed;
299	}
300
301	cd->dlz_allowzonexfr = (dlz_dlopen_allowzonexfr_t *)
302		dl_load_symbol(cd, "dlz_allowzonexfr", ISC_FALSE);
303	cd->dlz_allnodes = (dlz_dlopen_allnodes_t *)
304		dl_load_symbol(cd, "dlz_allnodes",
305			       ISC_TF(cd->dlz_allowzonexfr != NULL));
306	cd->dlz_authority = (dlz_dlopen_authority_t *)
307		dl_load_symbol(cd, "dlz_authority", ISC_FALSE);
308	cd->dlz_newversion = (dlz_dlopen_newversion_t *)
309		dl_load_symbol(cd, "dlz_newversion", ISC_FALSE);
310	cd->dlz_closeversion = (dlz_dlopen_closeversion_t *)
311		dl_load_symbol(cd, "dlz_closeversion",
312			       ISC_TF(cd->dlz_newversion != NULL));
313	cd->dlz_configure = (dlz_dlopen_configure_t *)
314		dl_load_symbol(cd, "dlz_configure", ISC_FALSE);
315	cd->dlz_ssumatch = (dlz_dlopen_ssumatch_t *)
316		dl_load_symbol(cd, "dlz_ssumatch", ISC_FALSE);
317	cd->dlz_addrdataset = (dlz_dlopen_addrdataset_t *)
318		dl_load_symbol(cd, "dlz_addrdataset", ISC_FALSE);
319	cd->dlz_subrdataset = (dlz_dlopen_subrdataset_t *)
320		dl_load_symbol(cd, "dlz_subrdataset", ISC_FALSE);
321	cd->dlz_delrdataset = (dlz_dlopen_delrdataset_t *)
322		dl_load_symbol(cd, "dlz_delrdataset", ISC_FALSE);
323	cd->dlz_destroy = (dlz_dlopen_destroy_t *)
324		dl_load_symbol(cd, "dlz_destroy", ISC_FALSE);
325
326	/* Check the version of the API is the same */
327	cd->version = cd->dlz_version(&cd->flags);
328	if (cd->version != DLZ_DLOPEN_VERSION) {
329		dlopen_log(ISC_LOG_ERROR,
330			   "dlz_dlopen: incorrect version %d "
331			   "should be %d in '%s'",
332			   cd->version, DLZ_DLOPEN_VERSION, cd->dl_path);
333		goto failed;
334	}
335
336	/*
337	 * Call the library's create function. Note that this is an
338	 * extended version of dlz create, with the addition of
339	 * named function pointers for helper functions that the
340	 * driver will need. This avoids the need for the backend to
341	 * link the BIND9 libraries
342	 */
343	MAYBE_LOCK(cd);
344	result = cd->dlz_create(dlzname, argc-1, argv+1,
345				&cd->dbdata,
346				"log", dlopen_log,
347				"putrr", dns_sdlz_putrr,
348				"putnamedrr", dns_sdlz_putnamedrr,
349				"writeable_zone", dns_dlz_writeablezone,
350				NULL);
351	MAYBE_UNLOCK(cd);
352	if (result != ISC_R_SUCCESS)
353		goto failed;
354
355	*dbdata = cd;
356
357	return (ISC_R_SUCCESS);
358
359failed:
360	dlopen_log(ISC_LOG_ERROR, "dlz_dlopen of '%s' failed", dlzname);
361	if (cd->dl_path != NULL)
362		isc_mem_free(mctx, cd->dl_path);
363	if (cd->dlzname != NULL)
364		isc_mem_free(mctx, cd->dlzname);
365	if (dlopen_flags != 0)
366		(void) isc_mutex_destroy(&cd->lock);
367#ifdef HAVE_DLCLOSE
368	if (cd->dl_handle)
369		dlclose(cd->dl_handle);
370#endif
371	isc_mem_put(mctx, cd, sizeof(*cd));
372	isc_mem_destroy(&mctx);
373	return (result);
374}
375
376
377/*
378 * Called when bind is shutting down
379 */
380static void
381dlopen_dlz_destroy(void *driverarg, void *dbdata) {
382	dlopen_data_t *cd = (dlopen_data_t *) dbdata;
383	isc_mem_t *mctx;
384
385	UNUSED(driverarg);
386
387	if (cd->dlz_destroy) {
388		MAYBE_LOCK(cd);
389		cd->dlz_destroy(cd->dbdata);
390		MAYBE_UNLOCK(cd);
391	}
392
393	if (cd->dl_path)
394		isc_mem_free(cd->mctx, cd->dl_path);
395	if (cd->dlzname)
396		isc_mem_free(cd->mctx, cd->dlzname);
397
398#ifdef HAVE_DLCLOSE
399	if (cd->dl_handle)
400		dlclose(cd->dl_handle);
401#endif
402
403	(void) isc_mutex_destroy(&cd->lock);
404
405	mctx = cd->mctx;
406	isc_mem_put(mctx, cd, sizeof(*cd));
407	isc_mem_destroy(&mctx);
408}
409
410/*
411 * Called to start a transaction
412 */
413static isc_result_t
414dlopen_dlz_newversion(const char *zone, void *driverarg, void *dbdata,
415		      void **versionp)
416{
417	dlopen_data_t *cd = (dlopen_data_t *) dbdata;
418	isc_result_t result;
419
420	UNUSED(driverarg);
421
422	if (cd->dlz_newversion == NULL)
423		return (ISC_R_NOTIMPLEMENTED);
424
425	MAYBE_LOCK(cd);
426	result = cd->dlz_newversion(zone, cd->dbdata, versionp);
427	MAYBE_UNLOCK(cd);
428	return (result);
429}
430
431/*
432 * Called to end a transaction
433 */
434static void
435dlopen_dlz_closeversion(const char *zone, isc_boolean_t commit,
436			void *driverarg, void *dbdata, void **versionp)
437{
438	dlopen_data_t *cd = (dlopen_data_t *) dbdata;
439
440	UNUSED(driverarg);
441
442	if (cd->dlz_newversion == NULL) {
443		*versionp = NULL;
444		return;
445	}
446
447	MAYBE_LOCK(cd);
448	cd->dlz_closeversion(zone, commit, cd->dbdata, versionp);
449	MAYBE_UNLOCK(cd);
450}
451
452/*
453 * Called on startup to configure any writeable zones
454 */
455static isc_result_t
456dlopen_dlz_configure(dns_view_t *view, void *driverarg, void *dbdata) {
457	dlopen_data_t *cd = (dlopen_data_t *) dbdata;
458	isc_result_t result;
459
460	UNUSED(driverarg);
461
462	if (cd->dlz_configure == NULL)
463		return (ISC_R_SUCCESS);
464
465	MAYBE_LOCK(cd);
466	cd->in_configure = ISC_TRUE;
467	result = cd->dlz_configure(view, cd->dbdata);
468	cd->in_configure = ISC_FALSE;
469	MAYBE_UNLOCK(cd);
470
471	return (result);
472}
473
474
475/*
476 * Check for authority to change a name
477 */
478static isc_boolean_t
479dlopen_dlz_ssumatch(const char *signer, const char *name, const char *tcpaddr,
480		    const char *type, const char *key, isc_uint32_t keydatalen,
481		    unsigned char *keydata, void *driverarg, void *dbdata)
482{
483	dlopen_data_t *cd = (dlopen_data_t *) dbdata;
484	isc_boolean_t ret;
485
486	UNUSED(driverarg);
487
488	if (cd->dlz_ssumatch == NULL)
489		return (ISC_FALSE);
490
491	MAYBE_LOCK(cd);
492	ret = cd->dlz_ssumatch(signer, name, tcpaddr, type, key, keydatalen,
493			       keydata, cd->dbdata);
494	MAYBE_UNLOCK(cd);
495
496	return (ret);
497}
498
499
500/*
501 * Add an rdataset
502 */
503static isc_result_t
504dlopen_dlz_addrdataset(const char *name, const char *rdatastr,
505		       void *driverarg, void *dbdata, void *version)
506{
507	dlopen_data_t *cd = (dlopen_data_t *) dbdata;
508	isc_result_t result;
509
510	UNUSED(driverarg);
511
512	if (cd->dlz_addrdataset == NULL)
513		return (ISC_R_NOTIMPLEMENTED);
514
515	MAYBE_LOCK(cd);
516	result = cd->dlz_addrdataset(name, rdatastr, cd->dbdata, version);
517	MAYBE_UNLOCK(cd);
518
519	return (result);
520}
521
522/*
523 * Subtract an rdataset
524 */
525static isc_result_t
526dlopen_dlz_subrdataset(const char *name, const char *rdatastr,
527		       void *driverarg, void *dbdata, void *version)
528{
529	dlopen_data_t *cd = (dlopen_data_t *) dbdata;
530	isc_result_t result;
531
532	UNUSED(driverarg);
533
534	if (cd->dlz_subrdataset == NULL)
535		return (ISC_R_NOTIMPLEMENTED);
536
537	MAYBE_LOCK(cd);
538	result = cd->dlz_subrdataset(name, rdatastr, cd->dbdata, version);
539	MAYBE_UNLOCK(cd);
540
541	return (result);
542}
543
544/*
545  delete a rdataset
546 */
547static isc_result_t
548dlopen_dlz_delrdataset(const char *name, const char *type,
549		       void *driverarg, void *dbdata, void *version)
550{
551	dlopen_data_t *cd = (dlopen_data_t *) dbdata;
552	isc_result_t result;
553
554	UNUSED(driverarg);
555
556	if (cd->dlz_delrdataset == NULL)
557		return (ISC_R_NOTIMPLEMENTED);
558
559	MAYBE_LOCK(cd);
560	result = cd->dlz_delrdataset(name, type, cd->dbdata, version);
561	MAYBE_UNLOCK(cd);
562
563	return (result);
564}
565
566
567static dns_sdlzmethods_t dlz_dlopen_methods = {
568	dlopen_dlz_create,
569	dlopen_dlz_destroy,
570	dlopen_dlz_findzonedb,
571	dlopen_dlz_lookup,
572	dlopen_dlz_authority,
573	dlopen_dlz_allnodes,
574	dlopen_dlz_allowzonexfr,
575	dlopen_dlz_newversion,
576	dlopen_dlz_closeversion,
577	dlopen_dlz_configure,
578	dlopen_dlz_ssumatch,
579	dlopen_dlz_addrdataset,
580	dlopen_dlz_subrdataset,
581	dlopen_dlz_delrdataset
582};
583#endif
584
585/*
586 * Register driver with BIND
587 */
588isc_result_t
589dlz_dlopen_init(isc_mem_t *mctx) {
590#ifndef ISC_DLZ_DLOPEN
591	UNUSED(mctx);
592	return (ISC_R_NOTIMPLEMENTED);
593#else
594	isc_result_t result;
595
596	dlopen_log(2, "Registering DLZ_dlopen driver");
597
598	result = dns_sdlzregister("dlopen", &dlz_dlopen_methods, NULL,
599				  DNS_SDLZFLAG_RELATIVEOWNER |
600				  DNS_SDLZFLAG_THREADSAFE,
601				  mctx, &dlz_dlopen);
602
603	if (result != ISC_R_SUCCESS) {
604		UNEXPECTED_ERROR(__FILE__, __LINE__,
605				 "dns_sdlzregister() failed: %s",
606				 isc_result_totext(result));
607		result = ISC_R_UNEXPECTED;
608	}
609
610	return (result);
611#endif
612}
613
614
615/*
616 * Unregister the driver
617 */
618void
619dlz_dlopen_clear(void) {
620#ifdef ISC_DLZ_DLOPEN
621	dlopen_log(2, "Unregistering DLZ_dlopen driver");
622	if (dlz_dlopen != NULL)
623		dns_sdlzunregister(&dlz_dlopen);
624#endif
625}
626