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
ldapdb_find(struct ldapdb_entry * stack,const void * index,size_t size)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
ldapdb_insert(struct ldapdb_entry ** stack,struct ldapdb_entry * item)99 static void ldapdb_insert(struct ldapdb_entry **stack,
100 struct ldapdb_entry *item) {
101 item->next = *stack;
102 *stack = item;
103 }
104
ldapdb_lock(int what)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 **
ldapdb_getconn(struct ldapdb_data * data)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
ldapdb_bind(struct ldapdb_data * data,LDAP ** ldp)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
ldapdb_search(const char * zone,const char * name,void * dbdata,void * retdata,dns_clientinfomethods_t * methods,dns_clientinfo_t * clientinfo)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
ldapdb_lookup(const char * zone,const char * name,void * dbdata,dns_sdblookup_t * lookup,dns_clientinfomethods_t * methods,dns_clientinfo_t * clientinfo)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
ldapdb_lookup(const char * zone,const char * name,void * dbdata,dns_sdblookup_t * lookup)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
ldapdb_allnodes(const char * zone,void * dbdata,dns_sdballnodes_t * allnodes)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 *
unhex(char * in)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
parseextensions(char * extensions,struct ldapdb_data * data)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
free_data(struct ldapdb_data * data)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
ldapdb_create(const char * zone,int argc,char ** argv,void * driverdata,void ** dbdata)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
ldapdb_destroy(const char * zone,void * driverdata,void ** dbdata)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
ldapdb_init(void)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
ldapdb_clear(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