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