1 /*	$NetBSD: ldapdb.c,v 1.5 2015/07/08 17:28:56 christos Exp $	*/
2 
3 /*
4  * ldapdb.c version 1.0-beta
5  *
6  * Copyright (C) 2002, 2004 Stig Venaas
7  *
8  * Permission to use, copy, modify, and distribute this software for any
9  * purpose with or without fee is hereby granted, provided that the above
10  * copyright notice and this permission notice appear in all copies.
11  *
12  * Contributors: Jeremy C. McDermond
13  */
14 
15 /*
16  * If you want to use TLS, uncomment the define below
17  */
18 /* #define LDAPDB_TLS */
19 
20 /*
21  * If you are using an old LDAP API uncomment the define below. Only do this
22  * if you know what you're doing or get compilation errors on ldap_memfree().
23  * This also forces LDAPv2.
24  */
25 /* #define LDAPDB_RFC1823API */
26 
27 /* Using LDAPv3 by default, change this if you want v2 */
28 #ifndef LDAPDB_LDAP_VERSION
29 #define LDAPDB_LDAP_VERSION 3
30 #endif
31 
32 #include <config.h>
33 
34 #include <string.h>
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <ctype.h>
38 
39 #include <isc/mem.h>
40 #include <isc/print.h>
41 #include <isc/result.h>
42 #include <isc/util.h>
43 #include <isc/thread.h>
44 
45 #include <dns/sdb.h>
46 
47 #include <named/globals.h>
48 #include <named/log.h>
49 
50 #include <ldap.h>
51 #include "ldapdb.h"
52 
53 /*
54  * A simple database driver for LDAP
55  */
56 
57 /* enough for name with 8 labels of max length */
58 #define MAXNAMELEN 519
59 
60 static dns_sdbimplementation_t *ldapdb = NULL;
61 
62 struct ldapdb_data {
63 	char *hostport;
64 	char *hostname;
65 	int portno;
66 	char *base;
67 	int defaultttl;
68 	char *filterall;
69 	int filteralllen;
70 	char *filterone;
71 	int filteronelen;
72 	char *filtername;
73 	char *bindname;
74 	char *bindpw;
75 #ifdef LDAPDB_TLS
76 	int tls;
77 #endif
78 };
79 
80 /* used by ldapdb_getconn */
81 
82 struct ldapdb_entry {
83 	void *index;
84 	size_t size;
85 	void *data;
86 	struct ldapdb_entry *next;
87 };
88 
89 static struct ldapdb_entry *ldapdb_find(struct ldapdb_entry *stack,
90 					const void *index, size_t size) {
91 	while (stack != NULL) {
92 		if (stack->size == size && !memcmp(stack->index, index, size))
93 			return stack;
94 		stack = stack->next;
95 	}
96 	return NULL;
97 }
98 
99 static void ldapdb_insert(struct ldapdb_entry **stack,
100 			  struct ldapdb_entry *item) {
101 	item->next = *stack;
102 	*stack = item;
103 }
104 
105 static void ldapdb_lock(int what) {
106 	static isc_mutex_t lock;
107 
108 	switch (what) {
109 	case 0:
110 		isc_mutex_init(&lock);
111 		break;
112 	case 1:
113 		LOCK(&lock);
114 		break;
115 	case -1:
116 		UNLOCK(&lock);
117 		break;
118 	}
119 }
120 
121 /* data == NULL means cleanup */
122 static LDAP **
123 ldapdb_getconn(struct ldapdb_data *data)
124 {
125 	static struct ldapdb_entry *allthreadsdata = NULL;
126 	struct ldapdb_entry *threaddata, *conndata;
127 	unsigned long threadid;
128 
129 	if (data == NULL) {
130 		/* cleanup */
131 		/* lock out other threads */
132 		ldapdb_lock(1);
133 		while (allthreadsdata != NULL) {
134 			threaddata = allthreadsdata;
135 			free(threaddata->index);
136 			while (threaddata->data != NULL) {
137 				conndata = threaddata->data;
138 				if (conndata->data != NULL)
139 					ldap_unbind((LDAP *)conndata->data);
140 				threaddata->data = conndata->next;
141 				free(conndata);
142 			}
143 			allthreadsdata = threaddata->next;
144 			free(threaddata);
145 		}
146 		ldapdb_lock(-1);
147 		return (NULL);
148 	}
149 
150 	/* look for connection data for current thread */
151 	threadid = isc_thread_self();
152 	threaddata = ldapdb_find(allthreadsdata, &threadid, sizeof(threadid));
153 	if (threaddata == NULL) {
154 		/* no data for this thread, create empty connection list */
155 		threaddata = malloc(sizeof(*threaddata));
156 		if (threaddata == NULL)
157 			return (NULL);
158 		threaddata->index = malloc(sizeof(threadid));
159 		if (threaddata->index == NULL) {
160 			free(threaddata);
161 			return (NULL);
162 		}
163 		*(unsigned long *)threaddata->index = threadid;
164 		threaddata->size = sizeof(threadid);
165 		threaddata->data = NULL;
166 
167 		/* need to lock out other threads here */
168 		ldapdb_lock(1);
169 		ldapdb_insert(&allthreadsdata, threaddata);
170 		ldapdb_lock(-1);
171 	}
172 
173 	/* threaddata points at the connection list for current thread */
174 	/* look for existing connection to our server */
175 	conndata = ldapdb_find((struct ldapdb_entry *)threaddata->data,
176 			       data->hostport, strlen(data->hostport));
177 	if (conndata == NULL) {
178 		/* no connection data structure for this server, create one */
179 		conndata = malloc(sizeof(*conndata));
180 		if (conndata == NULL)
181 			return (NULL);
182 		conndata->index = data->hostport;
183 		conndata->size = strlen(data->hostport);
184 		conndata->data = NULL;
185 		ldapdb_insert((struct ldapdb_entry **)&threaddata->data,
186 			      conndata);
187 	}
188 
189 	return (LDAP **)&conndata->data;
190 }
191 
192 static void
193 ldapdb_bind(struct ldapdb_data *data, LDAP **ldp)
194 {
195 #ifndef LDAPDB_RFC1823API
196 	const int ver = LDAPDB_LDAP_VERSION;
197 #endif
198 
199 	if (*ldp != NULL)
200 		ldap_unbind(*ldp);
201 	*ldp = ldap_open(data->hostname, data->portno);
202 	if (*ldp == NULL)
203 		return;
204 
205 #ifndef LDAPDB_RFC1823API
206 	ldap_set_option(*ldp, LDAP_OPT_PROTOCOL_VERSION, &ver);
207 #endif
208 
209 #ifdef LDAPDB_TLS
210 	if (data->tls) {
211 		ldap_start_tls_s(*ldp, NULL, NULL);
212 	}
213 #endif
214 
215 	if (ldap_simple_bind_s(*ldp, data->bindname, data->bindpw) != LDAP_SUCCESS) {
216 		ldap_unbind(*ldp);
217 		*ldp = NULL;
218 	}
219 }
220 
221 #ifdef DNS_CLIENTINFO_VERSION
222 static isc_result_t
223 ldapdb_search(const char *zone, const char *name, void *dbdata, void *retdata,
224 	      dns_clientinfomethods_t *methods, dns_clientinfo_t *clientinfo)
225 #else
226 static isc_result_t
227 ldapdb_search(const char *zone, const char *name, void *dbdata, void *retdata,
228 	      void *methods, void *clientinfo)
229 #endif /* DNS_CLIENTINFO_VERSION */
230 {
231 	struct ldapdb_data *data = dbdata;
232 	isc_result_t result = ISC_R_NOTFOUND;
233 	LDAP **ldp;
234 	LDAPMessage *res, *e;
235 	char *fltr, *a, **vals = NULL, **names = NULL;
236 	char type[64];
237 #ifdef LDAPDB_RFC1823API
238 	void *ptr;
239 #else
240 	BerElement *ptr;
241 #endif
242 	int i, j, errno, msgid;
243 
244 	UNUSED(methods);
245 	UNUSED(clientinfo);
246 
247 	ldp = ldapdb_getconn(data);
248 	if (ldp == NULL)
249 		return (ISC_R_FAILURE);
250 	if (*ldp == NULL) {
251 		ldapdb_bind(data, ldp);
252 		if (*ldp == NULL) {
253 			isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_SERVER, ISC_LOG_ERROR,
254 				      "LDAP sdb zone '%s': bind failed", zone);
255 			return (ISC_R_FAILURE);
256 		}
257 	}
258 
259 	if (name == NULL) {
260 		fltr = data->filterall;
261 	} else {
262 		if (strlen(name) > MAXNAMELEN) {
263 			isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_SERVER, ISC_LOG_ERROR,
264                                       "LDAP sdb zone '%s': name %s too long", zone, name);
265 			return (ISC_R_FAILURE);
266 		}
267 		sprintf(data->filtername, "%s))", name);
268 		fltr = data->filterone;
269 	}
270 
271 	msgid = ldap_search(*ldp, data->base, LDAP_SCOPE_SUBTREE, fltr, NULL, 0);
272 	if (msgid == -1) {
273 		ldapdb_bind(data, ldp);
274 		if (*ldp != NULL)
275 			msgid = ldap_search(*ldp, data->base, LDAP_SCOPE_SUBTREE, fltr, NULL, 0);
276 	}
277 
278 	if (*ldp == NULL || msgid == -1) {
279 		isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_SERVER, ISC_LOG_ERROR,
280 			      "LDAP sdb zone '%s': search failed, filter %s", zone, fltr);
281 		return (ISC_R_FAILURE);
282 	}
283 
284 	/* Get the records one by one as they arrive and return them to bind */
285 	while ((errno = ldap_result(*ldp, msgid, 0, NULL, &res)) != LDAP_RES_SEARCH_RESULT ) {
286 		LDAP *ld = *ldp;
287 		int ttl = data->defaultttl;
288 
289 		/* not supporting continuation references at present */
290 		if (errno != LDAP_RES_SEARCH_ENTRY) {
291 			isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_SERVER, ISC_LOG_ERROR,
292 				      "LDAP sdb zone '%s': ldap_result returned %d", zone, errno);
293 			ldap_msgfree(res);
294 			return (ISC_R_FAILURE);
295                 }
296 
297 		/* only one entry per result message */
298 		e = ldap_first_entry(ld, res);
299 		if (e == NULL) {
300 			ldap_msgfree(res);
301 			isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_SERVER, ISC_LOG_ERROR,
302 				      "LDAP sdb zone '%s': ldap_first_entry failed", zone);
303 			return (ISC_R_FAILURE);
304                 }
305 
306 		if (name == NULL) {
307 			names = ldap_get_values(ld, e, "relativeDomainName");
308 			if (names == NULL)
309 				continue;
310 		}
311 
312 		vals = ldap_get_values(ld, e, "dNSTTL");
313 		if (vals != NULL) {
314 			ttl = atoi(vals[0]);
315 			ldap_value_free(vals);
316 		}
317 
318 		for (a = ldap_first_attribute(ld, e, &ptr); a != NULL; a = ldap_next_attribute(ld, e, ptr)) {
319 			char *s;
320 
321 			for (s = a; *s; s++)
322 				*s = toupper(*s);
323 			s = strstr(a, "RECORD");
324 			if ((s == NULL) || (s == a) || (s - a >= (signed int)sizeof(type))) {
325 #ifndef LDAPDB_RFC1823API
326 				ldap_memfree(a);
327 #endif
328 				continue;
329 			}
330 
331 			strncpy(type, a, s - a);
332 			type[s - a] = '\0';
333 			vals = ldap_get_values(ld, e, a);
334 			if (vals != NULL) {
335 				for (i = 0; vals[i] != NULL; i++) {
336 					if (name != NULL) {
337 						result = dns_sdb_putrr(retdata, type, ttl, vals[i]);
338 					} else {
339 						for (j = 0; names[j] != NULL; j++) {
340 							result = dns_sdb_putnamedrr(retdata, names[j], type, ttl, vals[i]);
341 							if (result != ISC_R_SUCCESS)
342 								break;
343 						}
344 					}
345 ;					if (result != ISC_R_SUCCESS) {
346 						isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_SERVER, ISC_LOG_ERROR,
347 							      "LDAP sdb zone '%s': dns_sdb_put... failed for %s", zone, vals[i]);
348 						ldap_value_free(vals);
349 #ifndef LDAPDB_RFC1823API
350 						ldap_memfree(a);
351 						if (ptr != NULL)
352 							ber_free(ptr, 0);
353 #endif
354 						if (name == NULL)
355 							ldap_value_free(names);
356 						ldap_msgfree(res);
357 						return (ISC_R_FAILURE);
358 					}
359 				}
360 				ldap_value_free(vals);
361 			}
362 #ifndef LDAPDB_RFC1823API
363 			ldap_memfree(a);
364 #endif
365 		}
366 #ifndef LDAPDB_RFC1823API
367 		if (ptr != NULL)
368 			ber_free(ptr, 0);
369 #endif
370 		if (name == NULL)
371 			ldap_value_free(names);
372 
373 		/* free this result */
374 		ldap_msgfree(res);
375 	}
376 
377 	/* free final result */
378 	ldap_msgfree(res);
379         return (result);
380 }
381 
382 
383 /* callback routines */
384 #ifdef DNS_CLIENTINFO_VERSION
385 static isc_result_t
386 ldapdb_lookup(const char *zone, const char *name, void *dbdata,
387 	      dns_sdblookup_t *lookup, dns_clientinfomethods_t *methods,
388 	      dns_clientinfo_t *clientinfo)
389 {
390 	UNUSED(methods);
391 	UNUSED(clientinfo);
392 	return (ldapdb_search(zone, name, dbdata, lookup, NULL, NULL));
393 }
394 #else
395 static isc_result_t
396 ldapdb_lookup(const char *zone, const char *name, void *dbdata,
397 	      dns_sdblookup_t *lookup)
398 {
399 	return (ldapdb_search(zone, name, dbdata, lookup, methods,
400 			      clientinfo));
401 }
402 #endif /* DNS_CLIENTINFO_VERSION */
403 
404 static isc_result_t
405 ldapdb_allnodes(const char *zone, void *dbdata,
406 		dns_sdballnodes_t *allnodes)
407 {
408 	return (ldapdb_search(zone, NULL, dbdata, allnodes, NULL, NULL));
409 }
410 
411 static char *
412 unhex(char *in)
413 {
414 	static const char hexdigits[] = "0123456789abcdef";
415 	char *p, *s = in;
416 	int d1, d2;
417 
418 	while ((s = strchr(s, '%'))) {
419 		if (!(s[1] && s[2]))
420 			return NULL;
421 		if ((p = strchr(hexdigits, tolower(s[1]))) == NULL)
422 			return NULL;
423 		d1 = p - hexdigits;
424 		if ((p = strchr(hexdigits, tolower(s[2]))) == NULL)
425 			return NULL;
426 		d2 = p - hexdigits;
427 		*s++ = d1 << 4 | d2;
428 		memmove(s, s + 2, strlen(s) - 1);
429 	}
430 	return in;
431 }
432 
433 /* returns 0 for ok, -1 for bad syntax, -2 for unknown critical extension */
434 static int
435 parseextensions(char *extensions, struct ldapdb_data *data)
436 {
437 	char *s, *next, *name, *value;
438 	int critical;
439 
440 	while (extensions != NULL) {
441 		s = strchr(extensions, ',');
442 		if (s != NULL) {
443 			*s++ = '\0';
444 			next = s;
445 		} else {
446 			next = NULL;
447 		}
448 
449 		if (*extensions != '\0') {
450 			s = strchr(extensions, '=');
451 			if (s != NULL) {
452 				*s++ = '\0';
453 				value = *s != '\0' ? s : NULL;
454 			} else {
455 				value = NULL;
456 			}
457 			name = extensions;
458 
459 			critical = *name == '!';
460 			if (critical) {
461 				name++;
462 			}
463 			if (*name == '\0') {
464 				return -1;
465 			}
466 
467 			if (!strcasecmp(name, "bindname")) {
468 				data->bindname = value;
469 			} else if (!strcasecmp(name, "x-bindpw")) {
470 				data->bindpw = value;
471 #ifdef LDAPDB_TLS
472 			} else if (!strcasecmp(name, "x-tls")) {
473 				data->tls = value == NULL || !strcasecmp(value, "true");
474 #endif
475 			} else if (critical) {
476 				return -2;
477 			}
478 		}
479 		extensions = next;
480 	}
481 	return 0;
482 }
483 
484 static void
485 free_data(struct ldapdb_data *data)
486 {
487 	if (data->hostport != NULL)
488 		isc_mem_free(ns_g_mctx, data->hostport);
489 	if (data->hostname != NULL)
490 		isc_mem_free(ns_g_mctx, data->hostname);
491 	if (data->filterall != NULL)
492 		isc_mem_put(ns_g_mctx, data->filterall, data->filteralllen);
493 	if (data->filterone != NULL)
494 		isc_mem_put(ns_g_mctx, data->filterone, data->filteronelen);
495         isc_mem_put(ns_g_mctx, data, sizeof(struct ldapdb_data));
496 }
497 
498 
499 static isc_result_t
500 ldapdb_create(const char *zone, int argc, char **argv,
501 	      void *driverdata, void **dbdata)
502 {
503 	struct ldapdb_data *data;
504 	char *s, *filter = NULL, *extensions = NULL;
505 	int defaultttl;
506 
507 	UNUSED(driverdata);
508 
509 	/* we assume that only one thread will call create at a time */
510 	/* want to do this only once for all instances */
511 
512 	if ((argc < 2)
513 	    || (argv[0] != strstr( argv[0], "ldap://"))
514 	    || ((defaultttl = atoi(argv[1])) < 1))
515                 return (ISC_R_FAILURE);
516         data = isc_mem_get(ns_g_mctx, sizeof(struct ldapdb_data));
517         if (data == NULL)
518                 return (ISC_R_NOMEMORY);
519 
520 	memset(data, 0, sizeof(struct ldapdb_data));
521 	data->hostport = isc_mem_strdup(ns_g_mctx, argv[0] + strlen("ldap://"));
522 	if (data->hostport == NULL) {
523 		free_data(data);
524 		return (ISC_R_NOMEMORY);
525 	}
526 
527 	data->defaultttl = defaultttl;
528 
529 	s = strchr(data->hostport, '/');
530 	if (s != NULL) {
531 		*s++ = '\0';
532 		data->base = s;
533 		/* attrs, scope, filter etc? */
534 		s = strchr(s, '?');
535 		if (s != NULL) {
536 			*s++ = '\0';
537 			/* ignore attributes */
538 			s = strchr(s, '?');
539 			if (s != NULL) {
540 				*s++ = '\0';
541 				/* ignore scope */
542 				s = strchr(s, '?');
543 				if (s != NULL) {
544 					*s++ = '\0';
545 					/* filter */
546 					filter = s;
547 					s = strchr(s, '?');
548 					if (s != NULL) {
549 						*s++ = '\0';
550 						/* extensions */
551 						extensions = s;
552 						s = strchr(s, '?');
553 						if (s != NULL) {
554 							*s++ = '\0';
555 						}
556 						if (*extensions == '\0') {
557 							extensions = NULL;
558 						}
559 					}
560 					if (*filter == '\0') {
561 						filter = NULL;
562 					}
563 				}
564 			}
565 		}
566 		if (*data->base == '\0') {
567 			data->base = NULL;
568 		}
569 	}
570 
571 	/* parse extensions */
572 	if (extensions != NULL) {
573 		int err;
574 
575 		err = parseextensions(extensions, data);
576 		if (err < 0) {
577 			/* err should be -1 or -2 */
578 			free_data(data);
579 			if (err == -1) {
580 				isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_SERVER, ISC_LOG_ERROR,
581 					      "LDAP sdb zone '%s': URL: extension syntax error", zone);
582 			} else if (err == -2) {
583 				isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_SERVER, ISC_LOG_ERROR,
584 					      "LDAP sdb zone '%s': URL: unknown critical extension", zone);
585 			}
586 			return (ISC_R_FAILURE);
587 		}
588 	}
589 
590 	if ((data->base != NULL && unhex(data->base) == NULL) ||
591 	    (filter != NULL && unhex(filter) == NULL) ||
592 	    (data->bindname != NULL && unhex(data->bindname) == NULL) ||
593 	    (data->bindpw != NULL && unhex(data->bindpw) == NULL)) {
594 		free_data(data);
595 		isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_SERVER, ISC_LOG_ERROR,
596 			      "LDAP sdb zone '%s': URL: bad hex values", zone);
597 		return (ISC_R_FAILURE);
598 	}
599 
600 	/* compute filterall and filterone once and for all */
601 	if (filter == NULL) {
602 		data->filteralllen = strlen(zone) + strlen("(zoneName=)") + 1;
603 		data->filteronelen = strlen(zone) + strlen("(&(zoneName=)(relativeDomainName=))") + MAXNAMELEN + 1;
604 	} else {
605 		data->filteralllen = strlen(filter) + strlen(zone) + strlen("(&(zoneName=))") + 1;
606 		data->filteronelen = strlen(filter) + strlen(zone) + strlen("(&(zoneName=)(relativeDomainName=))") + MAXNAMELEN + 1;
607 	}
608 
609 	data->filterall = isc_mem_get(ns_g_mctx, data->filteralllen);
610 	if (data->filterall == NULL) {
611 		free_data(data);
612 		return (ISC_R_NOMEMORY);
613 	}
614 	data->filterone = isc_mem_get(ns_g_mctx, data->filteronelen);
615 	if (data->filterone == NULL) {
616 		free_data(data);
617 		return (ISC_R_NOMEMORY);
618 	}
619 
620 	if (filter == NULL) {
621 		sprintf(data->filterall, "(zoneName=%s)", zone);
622 		sprintf(data->filterone, "(&(zoneName=%s)(relativeDomainName=", zone);
623 	} else {
624 		sprintf(data->filterall, "(&%s(zoneName=%s))", filter, zone);
625 		sprintf(data->filterone, "(&%s(zoneName=%s)(relativeDomainName=", filter, zone);
626 	}
627 	data->filtername = data->filterone + strlen(data->filterone);
628 
629 	/* support URLs with literal IPv6 addresses */
630 	data->hostname = isc_mem_strdup(ns_g_mctx, data->hostport + (*data->hostport == '[' ? 1 : 0));
631 	if (data->hostname == NULL) {
632 		free_data(data);
633 		return (ISC_R_NOMEMORY);
634 	}
635 
636 	if (*data->hostport == '[' &&
637 	    (s = strchr(data->hostname, ']')) != NULL )
638 		*s++ = '\0';
639 	else
640 		s = data->hostname;
641 	s = strchr(s, ':');
642 	if (s != NULL) {
643 		*s++ = '\0';
644 		data->portno = atoi(s);
645 	} else
646 		data->portno = LDAP_PORT;
647 
648 	*dbdata = data;
649 	return (ISC_R_SUCCESS);
650 }
651 
652 static void
653 ldapdb_destroy(const char *zone, void *driverdata, void **dbdata) {
654 	struct ldapdb_data *data = *dbdata;
655 
656         UNUSED(zone);
657         UNUSED(driverdata);
658 
659 	free_data(data);
660 }
661 
662 static dns_sdbmethods_t ldapdb_methods = {
663 	ldapdb_lookup,
664 	NULL, /* authority */
665 	ldapdb_allnodes,
666 	ldapdb_create,
667 	ldapdb_destroy,
668 	NULL /* lookup2 */
669 };
670 
671 /* Wrapper around dns_sdb_register() */
672 isc_result_t
673 ldapdb_init(void) {
674 	unsigned int flags =
675 		DNS_SDBFLAG_RELATIVEOWNER |
676 		DNS_SDBFLAG_RELATIVERDATA |
677 		DNS_SDBFLAG_THREADSAFE;
678 
679 	ldapdb_lock(0);
680 	return (dns_sdb_register("ldap", &ldapdb_methods, NULL, flags,
681 				 ns_g_mctx, &ldapdb));
682 }
683 
684 /* Wrapper around dns_sdb_unregister() */
685 void
686 ldapdb_clear(void) {
687 	if (ldapdb != NULL) {
688 		/* clean up thread data */
689 		ldapdb_getconn(NULL);
690 		dns_sdb_unregister(&ldapdb);
691 	}
692 }
693