1 /*
2 * Copyright (C) 2011-2014 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
40 static dns_sdlzimplementation_t *dlz_dlopen = NULL;
41
42
43 typedef 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 */
dlopen_log(int level,const char * fmt,...)89 static 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
103 static isc_result_t
dlopen_dlz_allnodes(const char * zone,void * driverarg,void * dbdata,dns_sdlzallnodes_t * allnodes)104 dlopen_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
124 static isc_result_t
dlopen_dlz_allowzonexfr(void * driverarg,void * dbdata,const char * name,const char * client)125 dlopen_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
144 static isc_result_t
dlopen_dlz_authority(const char * zone,void * driverarg,void * dbdata,dns_sdlzlookup_t * lookup)145 dlopen_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
163 static isc_result_t
dlopen_dlz_findzonedb(void * driverarg,void * dbdata,const char * name)164 dlopen_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
178 static isc_result_t
dlopen_dlz_lookup(const char * zone,const char * name,void * driverarg,void * dbdata,dns_sdlzlookup_t * lookup,dns_clientinfomethods_t * methods,dns_clientinfo_t * clientinfo)179 dlopen_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 */
199 static void *
dl_load_symbol(dlopen_data_t * cd,const char * symbol,isc_boolean_t mandatory)200 dl_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 */
213 static isc_result_t
dlopen_dlz_create(const char * dlzname,unsigned int argc,char * argv[],void * driverarg,void ** dbdata)214 dlopen_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 result = ISC_R_NOMEMORY;
247 goto failed;
248 }
249
250 cd->dlzname = isc_mem_strdup(cd->mctx, dlzname);
251 if (cd->dlzname == NULL) {
252 result = ISC_R_NOMEMORY;
253 goto failed;
254 }
255
256 /* Initialize the lock */
257 result = isc_mutex_init(&cd->lock);
258 if (result != ISC_R_SUCCESS)
259 goto failed;
260
261 /* Open the library */
262 dlopen_flags = RTLD_NOW|RTLD_GLOBAL;
263
264 #ifdef RTLD_DEEPBIND
265 /*
266 * If RTLD_DEEPBIND is available then use it. This can avoid
267 * issues with a module using a different version of a system
268 * library than one that bind9 uses. For example, bind9 may link
269 * to MIT kerberos, but the module may use Heimdal. If we don't
270 * use RTLD_DEEPBIND then we could end up with Heimdal functions
271 * calling MIT functions, which leads to bizarre results (usually
272 * a segfault).
273 */
274 dlopen_flags |= RTLD_DEEPBIND;
275 #endif
276
277 cd->dl_handle = dlopen(cd->dl_path, dlopen_flags);
278 if (cd->dl_handle == NULL) {
279 dlopen_log(ISC_LOG_ERROR,
280 "dlz_dlopen failed to open library '%s' - %s",
281 cd->dl_path, dlerror());
282 result = ISC_R_FAILURE;
283 goto failed;
284 }
285
286 /* Find the symbols */
287 cd->dlz_version = (dlz_dlopen_version_t *)
288 dl_load_symbol(cd, "dlz_version", ISC_TRUE);
289 cd->dlz_create = (dlz_dlopen_create_t *)
290 dl_load_symbol(cd, "dlz_create", ISC_TRUE);
291 cd->dlz_lookup = (dlz_dlopen_lookup_t *)
292 dl_load_symbol(cd, "dlz_lookup", ISC_TRUE);
293 cd->dlz_findzonedb = (dlz_dlopen_findzonedb_t *)
294 dl_load_symbol(cd, "dlz_findzonedb", ISC_TRUE);
295
296 if (cd->dlz_create == NULL ||
297 cd->dlz_lookup == NULL ||
298 cd->dlz_findzonedb == NULL)
299 {
300 /* We're missing a required symbol */
301 result = ISC_R_FAILURE;
302 goto failed;
303 }
304
305 cd->dlz_allowzonexfr = (dlz_dlopen_allowzonexfr_t *)
306 dl_load_symbol(cd, "dlz_allowzonexfr", ISC_FALSE);
307 cd->dlz_allnodes = (dlz_dlopen_allnodes_t *)
308 dl_load_symbol(cd, "dlz_allnodes",
309 ISC_TF(cd->dlz_allowzonexfr != NULL));
310 cd->dlz_authority = (dlz_dlopen_authority_t *)
311 dl_load_symbol(cd, "dlz_authority", ISC_FALSE);
312 cd->dlz_newversion = (dlz_dlopen_newversion_t *)
313 dl_load_symbol(cd, "dlz_newversion", ISC_FALSE);
314 cd->dlz_closeversion = (dlz_dlopen_closeversion_t *)
315 dl_load_symbol(cd, "dlz_closeversion",
316 ISC_TF(cd->dlz_newversion != NULL));
317 cd->dlz_configure = (dlz_dlopen_configure_t *)
318 dl_load_symbol(cd, "dlz_configure", ISC_FALSE);
319 cd->dlz_ssumatch = (dlz_dlopen_ssumatch_t *)
320 dl_load_symbol(cd, "dlz_ssumatch", ISC_FALSE);
321 cd->dlz_addrdataset = (dlz_dlopen_addrdataset_t *)
322 dl_load_symbol(cd, "dlz_addrdataset", ISC_FALSE);
323 cd->dlz_subrdataset = (dlz_dlopen_subrdataset_t *)
324 dl_load_symbol(cd, "dlz_subrdataset", ISC_FALSE);
325 cd->dlz_delrdataset = (dlz_dlopen_delrdataset_t *)
326 dl_load_symbol(cd, "dlz_delrdataset", ISC_FALSE);
327 cd->dlz_destroy = (dlz_dlopen_destroy_t *)
328 dl_load_symbol(cd, "dlz_destroy", ISC_FALSE);
329
330 /* Check the version of the API is the same */
331 cd->version = cd->dlz_version(&cd->flags);
332 if (cd->version != DLZ_DLOPEN_VERSION) {
333 dlopen_log(ISC_LOG_ERROR,
334 "dlz_dlopen: incorrect version %d "
335 "should be %d in '%s'",
336 cd->version, DLZ_DLOPEN_VERSION, cd->dl_path);
337 result = ISC_R_FAILURE;
338 goto failed;
339 }
340
341 /*
342 * Call the library's create function. Note that this is an
343 * extended version of dlz create, with the addition of
344 * named function pointers for helper functions that the
345 * driver will need. This avoids the need for the backend to
346 * link the BIND9 libraries
347 */
348 MAYBE_LOCK(cd);
349 result = cd->dlz_create(dlzname, argc-1, argv+1,
350 &cd->dbdata,
351 "log", dlopen_log,
352 "putrr", dns_sdlz_putrr,
353 "putnamedrr", dns_sdlz_putnamedrr,
354 "writeable_zone", dns_dlz_writeablezone,
355 NULL);
356 MAYBE_UNLOCK(cd);
357 if (result != ISC_R_SUCCESS)
358 goto failed;
359
360 *dbdata = cd;
361
362 return (ISC_R_SUCCESS);
363
364 failed:
365 dlopen_log(ISC_LOG_ERROR, "dlz_dlopen of '%s' failed", dlzname);
366 if (cd->dl_path != NULL)
367 isc_mem_free(mctx, cd->dl_path);
368 if (cd->dlzname != NULL)
369 isc_mem_free(mctx, cd->dlzname);
370 if (dlopen_flags != 0)
371 (void) isc_mutex_destroy(&cd->lock);
372 #ifdef HAVE_DLCLOSE
373 if (cd->dl_handle)
374 dlclose(cd->dl_handle);
375 #endif
376 isc_mem_put(mctx, cd, sizeof(*cd));
377 isc_mem_destroy(&mctx);
378 return (result);
379 }
380
381 /*
382 * Called when bind is shutting down
383 */
384 static void
dlopen_dlz_destroy(void * driverarg,void * dbdata)385 dlopen_dlz_destroy(void *driverarg, void *dbdata) {
386 dlopen_data_t *cd = (dlopen_data_t *) dbdata;
387 isc_mem_t *mctx;
388
389 UNUSED(driverarg);
390
391 if (cd->dlz_destroy) {
392 MAYBE_LOCK(cd);
393 cd->dlz_destroy(cd->dbdata);
394 MAYBE_UNLOCK(cd);
395 }
396
397 if (cd->dl_path)
398 isc_mem_free(cd->mctx, cd->dl_path);
399 if (cd->dlzname)
400 isc_mem_free(cd->mctx, cd->dlzname);
401
402 #ifdef HAVE_DLCLOSE
403 if (cd->dl_handle)
404 dlclose(cd->dl_handle);
405 #endif
406
407 (void) isc_mutex_destroy(&cd->lock);
408
409 mctx = cd->mctx;
410 isc_mem_put(mctx, cd, sizeof(*cd));
411 isc_mem_destroy(&mctx);
412 }
413
414 /*
415 * Called to start a transaction
416 */
417 static isc_result_t
dlopen_dlz_newversion(const char * zone,void * driverarg,void * dbdata,void ** versionp)418 dlopen_dlz_newversion(const char *zone, void *driverarg, void *dbdata,
419 void **versionp)
420 {
421 dlopen_data_t *cd = (dlopen_data_t *) dbdata;
422 isc_result_t result;
423
424 UNUSED(driverarg);
425
426 if (cd->dlz_newversion == NULL)
427 return (ISC_R_NOTIMPLEMENTED);
428
429 MAYBE_LOCK(cd);
430 result = cd->dlz_newversion(zone, cd->dbdata, versionp);
431 MAYBE_UNLOCK(cd);
432 return (result);
433 }
434
435 /*
436 * Called to end a transaction
437 */
438 static void
dlopen_dlz_closeversion(const char * zone,isc_boolean_t commit,void * driverarg,void * dbdata,void ** versionp)439 dlopen_dlz_closeversion(const char *zone, isc_boolean_t commit,
440 void *driverarg, void *dbdata, void **versionp)
441 {
442 dlopen_data_t *cd = (dlopen_data_t *) dbdata;
443
444 UNUSED(driverarg);
445
446 if (cd->dlz_newversion == NULL) {
447 *versionp = NULL;
448 return;
449 }
450
451 MAYBE_LOCK(cd);
452 cd->dlz_closeversion(zone, commit, cd->dbdata, versionp);
453 MAYBE_UNLOCK(cd);
454 }
455
456 /*
457 * Called on startup to configure any writeable zones
458 */
459 static isc_result_t
dlopen_dlz_configure(dns_view_t * view,void * driverarg,void * dbdata)460 dlopen_dlz_configure(dns_view_t *view, void *driverarg, void *dbdata) {
461 dlopen_data_t *cd = (dlopen_data_t *) dbdata;
462 isc_result_t result;
463
464 UNUSED(driverarg);
465
466 if (cd->dlz_configure == NULL)
467 return (ISC_R_SUCCESS);
468
469 MAYBE_LOCK(cd);
470 cd->in_configure = ISC_TRUE;
471 result = cd->dlz_configure(view, cd->dbdata);
472 cd->in_configure = ISC_FALSE;
473 MAYBE_UNLOCK(cd);
474
475 return (result);
476 }
477
478
479 /*
480 * Check for authority to change a name
481 */
482 static isc_boolean_t
dlopen_dlz_ssumatch(const char * signer,const char * name,const char * tcpaddr,const char * type,const char * key,isc_uint32_t keydatalen,unsigned char * keydata,void * driverarg,void * dbdata)483 dlopen_dlz_ssumatch(const char *signer, const char *name, const char *tcpaddr,
484 const char *type, const char *key, isc_uint32_t keydatalen,
485 unsigned char *keydata, void *driverarg, void *dbdata)
486 {
487 dlopen_data_t *cd = (dlopen_data_t *) dbdata;
488 isc_boolean_t ret;
489
490 UNUSED(driverarg);
491
492 if (cd->dlz_ssumatch == NULL)
493 return (ISC_FALSE);
494
495 MAYBE_LOCK(cd);
496 ret = cd->dlz_ssumatch(signer, name, tcpaddr, type, key, keydatalen,
497 keydata, cd->dbdata);
498 MAYBE_UNLOCK(cd);
499
500 return (ret);
501 }
502
503
504 /*
505 * Add an rdataset
506 */
507 static isc_result_t
dlopen_dlz_addrdataset(const char * name,const char * rdatastr,void * driverarg,void * dbdata,void * version)508 dlopen_dlz_addrdataset(const char *name, const char *rdatastr,
509 void *driverarg, void *dbdata, void *version)
510 {
511 dlopen_data_t *cd = (dlopen_data_t *) dbdata;
512 isc_result_t result;
513
514 UNUSED(driverarg);
515
516 if (cd->dlz_addrdataset == NULL)
517 return (ISC_R_NOTIMPLEMENTED);
518
519 MAYBE_LOCK(cd);
520 result = cd->dlz_addrdataset(name, rdatastr, cd->dbdata, version);
521 MAYBE_UNLOCK(cd);
522
523 return (result);
524 }
525
526 /*
527 * Subtract an rdataset
528 */
529 static isc_result_t
dlopen_dlz_subrdataset(const char * name,const char * rdatastr,void * driverarg,void * dbdata,void * version)530 dlopen_dlz_subrdataset(const char *name, const char *rdatastr,
531 void *driverarg, void *dbdata, void *version)
532 {
533 dlopen_data_t *cd = (dlopen_data_t *) dbdata;
534 isc_result_t result;
535
536 UNUSED(driverarg);
537
538 if (cd->dlz_subrdataset == NULL)
539 return (ISC_R_NOTIMPLEMENTED);
540
541 MAYBE_LOCK(cd);
542 result = cd->dlz_subrdataset(name, rdatastr, cd->dbdata, version);
543 MAYBE_UNLOCK(cd);
544
545 return (result);
546 }
547
548 /*
549 delete a rdataset
550 */
551 static isc_result_t
dlopen_dlz_delrdataset(const char * name,const char * type,void * driverarg,void * dbdata,void * version)552 dlopen_dlz_delrdataset(const char *name, const char *type,
553 void *driverarg, void *dbdata, void *version)
554 {
555 dlopen_data_t *cd = (dlopen_data_t *) dbdata;
556 isc_result_t result;
557
558 UNUSED(driverarg);
559
560 if (cd->dlz_delrdataset == NULL)
561 return (ISC_R_NOTIMPLEMENTED);
562
563 MAYBE_LOCK(cd);
564 result = cd->dlz_delrdataset(name, type, cd->dbdata, version);
565 MAYBE_UNLOCK(cd);
566
567 return (result);
568 }
569
570
571 static dns_sdlzmethods_t dlz_dlopen_methods = {
572 dlopen_dlz_create,
573 dlopen_dlz_destroy,
574 dlopen_dlz_findzonedb,
575 dlopen_dlz_lookup,
576 dlopen_dlz_authority,
577 dlopen_dlz_allnodes,
578 dlopen_dlz_allowzonexfr,
579 dlopen_dlz_newversion,
580 dlopen_dlz_closeversion,
581 dlopen_dlz_configure,
582 dlopen_dlz_ssumatch,
583 dlopen_dlz_addrdataset,
584 dlopen_dlz_subrdataset,
585 dlopen_dlz_delrdataset
586 };
587 #endif
588
589 /*
590 * Register driver with BIND
591 */
592 isc_result_t
dlz_dlopen_init(isc_mem_t * mctx)593 dlz_dlopen_init(isc_mem_t *mctx) {
594 #ifndef ISC_DLZ_DLOPEN
595 UNUSED(mctx);
596 return (ISC_R_NOTIMPLEMENTED);
597 #else
598 isc_result_t result;
599
600 dlopen_log(2, "Registering DLZ_dlopen driver");
601
602 result = dns_sdlzregister("dlopen", &dlz_dlopen_methods, NULL,
603 DNS_SDLZFLAG_RELATIVEOWNER |
604 DNS_SDLZFLAG_THREADSAFE,
605 mctx, &dlz_dlopen);
606
607 if (result != ISC_R_SUCCESS) {
608 UNEXPECTED_ERROR(__FILE__, __LINE__,
609 "dns_sdlzregister() failed: %s",
610 isc_result_totext(result));
611 result = ISC_R_UNEXPECTED;
612 }
613
614 return (result);
615 #endif
616 }
617
618
619 /*
620 * Unregister the driver
621 */
622 void
dlz_dlopen_clear(void)623 dlz_dlopen_clear(void) {
624 #ifdef ISC_DLZ_DLOPEN
625 dlopen_log(2, "Unregistering DLZ_dlopen driver");
626 if (dlz_dlopen != NULL)
627 dns_sdlzunregister(&dlz_dlopen);
628 #endif
629 }
630