1/* dlopen.c--Unix dlopen() dynamic loader interface
2 * Rob Siemborski
3 * Rob Earhart
4 * $Id: dlopen.c,v 1.7 2005/05/17 21:56:43 snsimon Exp $
5 */
6/*
7 * Copyright (c) 1998-2003 Carnegie Mellon University.  All rights reserved.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 *
13 * 1. Redistributions of source code must retain the above copyright
14 *    notice, this list of conditions and the following disclaimer.
15 *
16 * 2. Redistributions in binary form must reproduce the above copyright
17 *    notice, this list of conditions and the following disclaimer in
18 *    the documentation and/or other materials provided with the
19 *    distribution.
20 *
21 * 3. The name "Carnegie Mellon University" must not be used to
22 *    endorse or promote products derived from this software without
23 *    prior written permission. For permission or any other legal
24 *    details, please contact
25 *      Office of Technology Transfer
26 *      Carnegie Mellon University
27 *      5000 Forbes Avenue
28 *      Pittsburgh, PA  15213-3890
29 *      (412) 268-4387, fax: (412) 268-7395
30 *      tech-transfer@andrew.cmu.edu
31 *
32 * 4. Redistributions of any form whatsoever must retain the following
33 *    acknowledgment:
34 *    "This product includes software developed by Computing Services
35 *     at Carnegie Mellon University (http://www.cmu.edu/computing/)."
36 *
37 * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
38 * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
39 * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
40 * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
41 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
42 * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
43 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
44 */
45
46#include <config.h>
47#ifdef HAVE_DLFCN_H
48#include <dlfcn.h>
49#endif
50
51#include <stdlib.h>
52#include <errno.h>
53#include <stdio.h>
54#include <limits.h>
55
56#include <sasl.h>
57#include "saslint.h"
58
59#ifndef PIC
60#include <saslplug.h>
61#include "staticopen.h"
62#endif
63
64#ifdef DO_DLOPEN
65#if HAVE_DIRENT_H
66# include <dirent.h>
67# define NAMLEN(dirent) strlen((dirent)->d_name)
68#else /* HAVE_DIRENT_H */
69# define dirent direct
70# define NAMLEN(dirent) (dirent)->d_namlen
71# if HAVE_SYS_NDIR_H
72#  include <sys/ndir.h>
73# endif
74# if HAVE_SYS_DIR_H
75#  include <sys/dir.h>
76# endif
77# if HAVE_NDIR_H
78#  include <ndir.h>
79# endif
80#endif /* ! HAVE_DIRENT_H */
81
82#ifndef NAME_MAX
83# ifdef _POSIX_NAME_MAX
84#  define NAME_MAX _POSIX_NAME_MAX
85# else
86#  define NAME_MAX 16
87# endif
88#endif
89
90#if NAME_MAX < 8
91#  define NAME_MAX 8
92#endif
93
94#ifdef __hpux
95#ifndef HAVE_DLFCN_H
96#include <dl.h>
97
98typedef shl_t * dll_handle;
99typedef void * dll_func;
100
101dll_handle
102dlopen(char *fname, int mode)
103{
104    shl_t h = shl_load(fname, BIND_DEFERRED, 0L);
105    shl_t *hp = NULL;
106
107    if (h) {
108	hp = (shl_t *)malloc(sizeof (shl_t));
109	if (!hp) {
110	    shl_unload(h);
111	} else {
112	    *hp = h;
113	}
114    }
115
116    return (dll_handle)hp;
117}
118
119int
120dlclose(dll_handle hp)
121{
122    shl_t h;
123
124    if (hp != NULL) {
125	h = *((shl_t *)hp);
126	free(hp);
127	return shl_unload(h);
128    } else {
129	/* Return error */
130	return -1;
131    }
132}
133
134dll_func
135dlsym(dll_handle h, char *n)
136{
137    dll_func handle;
138
139    if (shl_findsym ((shl_t *)h, n, TYPE_PROCEDURE, &handle))
140	return NULL;
141
142    return (dll_func)handle;
143}
144
145char *dlerror()
146{
147    if (errno != 0) {
148	return strerror(errno);
149    }
150    return "Generic shared library error";
151}
152
153#endif /* HAVE_DLFCN_H */
154
155#ifdef __ia64
156#define SO_SUFFIX       ".so"
157#else
158#define SO_SUFFIX	".sl"
159#endif /* __ia64 */
160#elif defined(__APPLE__)
161/* APPLE: support both .plugin and .so */
162#define PLUGIN_SUFFIX	".plugin"
163#define SO_SUFFIX	".so"
164#else /* __APPLE__ */
165#define SO_SUFFIX	".so"
166#endif
167
168#define LA_SUFFIX       ".la"
169
170typedef struct lib_list
171{
172    struct lib_list *next;
173    void *library;
174} lib_list_t;
175
176static lib_list_t *lib_list_head = NULL;
177
178#endif /* DO_DLOPEN */
179
180int _sasl_locate_entry(void *library, const char *entryname,
181		       void **entry_point)
182{
183#ifdef DO_DLOPEN
184/* note that we still check for known problem systems in
185 * case we are cross-compiling */
186#if defined(DLSYM_NEEDS_UNDERSCORE) || (defined(__OpenBSD__) && !defined(__ELF__))
187    char adj_entryname[1024];
188#else
189#define adj_entryname entryname
190#endif
191
192    if(!entryname) {
193	_sasl_log(NULL, SASL_LOG_ERR,
194		  "no entryname in _sasl_locate_entry");
195	return SASL_BADPARAM;
196    }
197
198    if(!library) {
199	_sasl_log(NULL, SASL_LOG_ERR,
200		  "no library in _sasl_locate_entry");
201	return SASL_BADPARAM;
202    }
203
204    if(!entry_point) {
205	_sasl_log(NULL, SASL_LOG_ERR,
206		  "no entrypoint output pointer in _sasl_locate_entry");
207	return SASL_BADPARAM;
208    }
209
210#if defined(DLSYM_NEEDS_UNDERSCORE) || (defined(__OpenBSD__) && !defined(__ELF__))
211    snprintf(adj_entryname, sizeof adj_entryname, "_%s", entryname);
212#endif
213
214    *entry_point = NULL;
215    *entry_point = dlsym(library, adj_entryname);
216    if (*entry_point == NULL) {
217#if 0 /* This message appears to confuse people */
218	_sasl_log(NULL, SASL_LOG_DEBUG,
219		  "unable to get entry point %s: %s", adj_entryname,
220		  dlerror());
221#endif
222	return SASL_FAIL;
223    }
224
225    return SASL_OK;
226#else
227    return SASL_FAIL;
228#endif /* DO_DLOPEN */
229}
230
231#ifdef DO_DLOPEN
232
233static int _sasl_plugin_load(char *plugin, void *library,
234			     const char *entryname,
235			     int (*add_plugin)(const char *, void *))
236{
237    void *entry_point;
238    int result;
239
240    result = _sasl_locate_entry(library, entryname, &entry_point);
241    if(result == SASL_OK) {
242	result = add_plugin(plugin, entry_point);
243	if(result != SASL_OK)
244	    _sasl_log(NULL, SASL_LOG_DEBUG,
245		      "_sasl_plugin_load failed on %s for plugin: %s\n",
246		      entryname, plugin);
247    }
248
249    return result;
250}
251
252/* this returns the file to actually open.
253 *  out should be a buffer of size PATH_MAX
254 *  and may be the same as in. */
255
256/* We'll use a static buffer for speed unless someone complains */
257#define MAX_LINE 2048
258
259static int _parse_la(const char *prefix, const char *in, char *out)
260{
261    FILE *file;
262    size_t length;
263    int has64bit = 1; /* APPLE */
264    char line[MAX_LINE];
265    char *ntmp = NULL;
266
267    if(!in || !out || !prefix || out == in) return SASL_BADPARAM;
268
269    /* Set this so we can detect failure */
270    *out = '\0';
271
272    length = strlen(in);
273
274    if (strcmp(in + (length - strlen(LA_SUFFIX)), LA_SUFFIX)) {
275	if(!strcmp(in + (length - strlen(SO_SUFFIX)),SO_SUFFIX)) {
276	    /* check for a .la file */
277	    strcpy(line, prefix);
278	    strcat(line, in);
279	    length = strlen(line);
280	    *(line + (length - strlen(SO_SUFFIX))) = '\0';
281	    strcat(line, LA_SUFFIX);
282	    file = fopen(line, "r");
283	    if(file) {
284		/* We'll get it on the .la open */
285		fclose(file);
286		return SASL_FAIL;
287	    }
288	}
289	strcpy(out, prefix);
290	strcat(out, in);
291	return SASL_OK;
292    }
293
294    strcpy(line, prefix);
295    strcat(line, in);
296
297    file = fopen(line, "r");
298    if(!file) {
299	_sasl_log(NULL, SASL_LOG_WARN,
300		  "unable to open LA file: %s", line);
301	return SASL_FAIL;
302    }
303
304    while(!feof(file)) {
305	if(!fgets(line, MAX_LINE, file)) break;
306	if(line[strlen(line) - 1] != '\n') {
307	    _sasl_log(NULL, SASL_LOG_WARN,
308		      "LA file has too long of a line: %s", in);
309	    return SASL_BUFOVER;
310	}
311	if(line[0] == '\n' || line[0] == '#') continue;
312	if(!strncmp(line, "dlname=", sizeof("dlname=") - 1)) {
313	    /* We found the line with the name in it */
314	    char *end;
315	    char *start;
316	    size_t len;
317	    end = strrchr(line, '\'');
318	    if(!end) continue;
319	    start = &line[sizeof("dlname=")-1];
320	    len = strlen(start);
321	    if(len > 3 && start[0] == '\'') {
322		ntmp=&start[1];
323		*end='\0';
324		/* Do we have dlname="" ? */
325		if(ntmp == end) {
326		    _sasl_log(NULL, SASL_LOG_DEBUG,
327			      "dlname is empty in .la file: %s", in);
328		    return SASL_FAIL;
329		}
330		strcpy(out, prefix);
331		strcat(out, ntmp);
332	    }
333#if !__LP64__
334	    break;
335#endif
336	}
337#if __LP64__
338	else
339	if (!strncmp(line, "64bit=", sizeof("64bit=") - 1)) {
340		if (strstr(line, "no") != NULL) {
341			has64bit = 0;
342		}
343	}
344#endif
345    }
346    if(ferror(file) || *out == '\0') {
347	_sasl_log(NULL, SASL_LOG_WARN,
348		  "Error reading .la: %s\n", in);
349	fclose(file);
350	return SASL_FAIL;
351    }
352    fclose(file);
353
354#if __LP64__
355	if (has64bit == 0)
356		return SASL_FAIL;
357#endif
358
359    if(!(*out)) {
360	_sasl_log(NULL, SASL_LOG_WARN,
361		  "Could not find a dlname line in .la file: %s", in);
362	return SASL_FAIL;
363    }
364
365    return SASL_OK;
366}
367#endif /* DO_DLOPEN */
368
369/* loads a plugin library */
370int _sasl_get_plugin(const char *file,
371		     const sasl_callback_t *verifyfile_cb,
372		     void **libraryptr)
373{
374#ifdef DO_DLOPEN
375    int r = 0;
376    int flag;
377    void *library;
378    lib_list_t *newhead;
379
380    r = ((sasl_verifyfile_t *)(verifyfile_cb->proc))
381		    (verifyfile_cb->context, file, SASL_VRFY_PLUGIN);
382    if (r != SASL_OK) return r;
383
384#ifdef RTLD_NOW
385    flag = RTLD_NOW;
386#else
387    flag = 0;
388#endif
389
390    newhead = sasl_ALLOC(sizeof(lib_list_t));
391    if(!newhead) return SASL_NOMEM;
392
393    if (!(library = dlopen(file, flag))) {
394	_sasl_log(NULL, SASL_LOG_ERR,
395		  "unable to dlopen %s: %s", file, dlerror());
396	sasl_FREE(newhead);
397	return SASL_FAIL;
398    }
399
400    newhead->library = library;
401    newhead->next = lib_list_head;
402    lib_list_head = newhead;
403
404    *libraryptr = library;
405    return SASL_OK;
406#else
407    return SASL_FAIL;
408#endif /* DO_DLOPEN */
409}
410
411/* gets the list of mechanisms */
412int _sasl_load_plugins(const add_plugin_list_t *entrypoints,
413		       const sasl_callback_t *getpath_cb,
414		       const sasl_callback_t *verifyfile_cb)
415{
416    int result;
417    const add_plugin_list_t *cur_ep;
418#ifdef DO_DLOPEN
419    char str[PATH_MAX], tmp[PATH_MAX+2], prefix[PATH_MAX+2];
420				/* 1 for '/' 1 for trailing '\0' */
421    char c;
422    int pos;
423    const char *path=NULL;
424    int position;
425    DIR *dp;
426    struct dirent *dir;
427#endif
428#ifndef PIC
429    add_plugin_t *add_plugin;
430    _sasl_plug_type type;
431    _sasl_plug_rec *p;
432#endif
433
434    if (! entrypoints
435	|| ! getpath_cb
436	|| getpath_cb->id != SASL_CB_GETPATH
437	|| ! getpath_cb->proc
438	|| ! verifyfile_cb
439	|| verifyfile_cb->id != SASL_CB_VERIFYFILE
440	|| ! verifyfile_cb->proc)
441	return SASL_BADPARAM;
442
443#ifndef PIC
444    /* do all the static plugins first */
445
446    for(cur_ep = entrypoints; cur_ep->entryname; cur_ep++) {
447
448	/* What type of plugin are we looking for? */
449	if(!strcmp(cur_ep->entryname, "sasl_server_plug_init")) {
450	    type = SERVER;
451	    add_plugin = (add_plugin_t *)sasl_server_add_plugin;
452	} else if (!strcmp(cur_ep->entryname, "sasl_client_plug_init")) {
453	    type = CLIENT;
454	    add_plugin = (add_plugin_t *)sasl_client_add_plugin;
455	} else if (!strcmp(cur_ep->entryname, "sasl_auxprop_plug_init")) {
456	    type = AUXPROP;
457	    add_plugin = (add_plugin_t *)sasl_auxprop_add_plugin;
458	} else if (!strcmp(cur_ep->entryname, "sasl_canonuser_init")) {
459	    type = CANONUSER;
460	    add_plugin = (add_plugin_t *)sasl_canonuser_add_plugin;
461	} else {
462	    /* What are we looking for then? */
463	    return SASL_FAIL;
464	}
465	for (p=_sasl_static_plugins; p->type; p++) {
466	    if(type == p->type)
467	    	result = add_plugin(p->name, p->plug);
468	}
469    }
470#endif /* !PIC */
471
472/* only do the following if:
473 *
474 * we support dlopen()
475 *  AND we are not staticly compiled
476 *      OR we are staticly compiled and TRY_DLOPEN_WHEN_STATIC is defined
477 */
478#if defined(DO_DLOPEN) && (defined(PIC) || (!defined(PIC) && defined(TRY_DLOPEN_WHEN_STATIC)))
479    /* get the path to the plugins */
480    result = ((sasl_getpath_t *)(getpath_cb->proc))(getpath_cb->context,
481						    &path);
482    if (result != SASL_OK) return result;
483    if (! path) return SASL_FAIL;
484
485    if (strlen(path) >= PATH_MAX) { /* no you can't buffer overrun */
486	return SASL_FAIL;
487    }
488
489    position=0;
490    do {
491	pos=0;
492	do {
493	    c=path[position];
494	    position++;
495	    str[pos]=c;
496	    pos++;
497	} while ((c!=':') && (c!='=') && (c!=0));
498	str[pos-1]='\0';
499
500	strcpy(prefix,str);
501	strcat(prefix,"/");
502
503	if ((dp=opendir(str)) !=NULL) /* ignore errors */
504	{
505	    while ((dir=readdir(dp)) != NULL)
506	    {
507		size_t length;
508		void *library;
509		char *c;
510		char plugname[PATH_MAX];
511		char name[PATH_MAX];
512
513		length = NAMLEN(dir);
514		if (length < 4)
515		    continue; /* can not possibly be what we're looking for */
516
517		if (length + pos>=PATH_MAX) continue; /* too big */
518
519		if (strcmp(dir->d_name + (length - strlen(SO_SUFFIX)),
520			   SO_SUFFIX)
521#ifdef PLUGIN_SUFFIX
522		    && strcmp(dir->d_name + (length - strlen(PLUGIN_SUFFIX)),
523			   PLUGIN_SUFFIX)
524#endif
525		    && strcmp(dir->d_name + (length - strlen(LA_SUFFIX)),
526			   LA_SUFFIX))
527		    continue;
528
529		memcpy(name,dir->d_name,length);
530		name[length]='\0';
531
532		result = _parse_la(prefix, name, tmp);
533		if(result != SASL_OK)
534		    continue;
535
536		/* skip "lib" and cut off suffix --
537		   this only need be approximate */
538                /* APPLE: strlcpy */
539		strlcpy(plugname, (strncmp(name, "lib", 3) == 0) ? (name + 3) : name, sizeof(plugname));
540		c = strchr(plugname, (int)'.');
541		if(c) *c = '\0';
542
543		result = _sasl_get_plugin(tmp, verifyfile_cb, &library);
544
545		if(result != SASL_OK)
546		    continue;
547
548		for(cur_ep = entrypoints; cur_ep->entryname; cur_ep++) {
549			_sasl_plugin_load(plugname, library, cur_ep->entryname,
550					  cur_ep->add_plugin);
551			/* If this fails, it's not the end of the world */
552		}
553	    }
554
555	    closedir(dp);
556	} else {
557	    _sasl_log(NULL, SASL_LOG_DEBUG,
558		      "looking for plugins in '%s', failed to open directory, error: %s",
559		      str,
560		      strerror(errno));
561	}
562
563    } while ((c!='=') && (c!=0));
564#endif /* defined(DO_DLOPEN) && (!defined(PIC) || (defined(PIC) && defined(TRY_DLOPEN_WHEN_STATIC))) */
565
566    return SASL_OK;
567}
568
569/* APPLE: gets the list of mechanisms */
570int _sasl_load_plugins_alt(const add_plugin_list_t *entrypoints,
571		       const sasl_callback_t *getpath_cb,
572		       const sasl_callback_t *verifyfile_cb)
573{
574    int result;
575	int has_auxprop = 0;
576    const add_plugin_list_t *cur_ep;
577#ifdef DO_DLOPEN
578    char str[PATH_MAX], tmp[PATH_MAX+2], prefix[PATH_MAX+2];
579				/* 1 for '/' 1 for trailing '\0' */
580    char c;
581    int pos;
582    const char *path=NULL;
583    int position;
584    DIR *dp;
585    struct dirent *dir;
586#endif
587#ifndef PIC
588    add_plugin_t *add_plugin;
589    _sasl_plug_type type;
590    _sasl_plug_rec *p;
591#endif
592
593	if (! entrypoints
594		|| ! getpath_cb
595		|| getpath_cb->id != SASL_CB_GETPATH
596		|| ! getpath_cb->proc
597		|| ! verifyfile_cb
598		|| verifyfile_cb->id != SASL_CB_VERIFYFILE
599		|| ! verifyfile_cb->proc)
600		return SASL_BADPARAM;
601
602#ifndef PIC
603    /* do all the static plugins first */
604
605    for (cur_ep = entrypoints; cur_ep->entryname; cur_ep++) {
606		/* What type of plugin are we looking for? */
607		if(!strcmp(cur_ep->entryname, "sasl_server_plug_init")) {
608			type = SERVER;
609			add_plugin = (add_plugin_t *)sasl_server_add_plugin;
610		} else if (!strcmp(cur_ep->entryname, "sasl_client_plug_init")) {
611			type = CLIENT;
612			add_plugin = (add_plugin_t *)sasl_client_add_plugin;
613		} else if (!strcmp(cur_ep->entryname, "sasl_auxprop_plug_init")) {
614			type = AUXPROP;
615			add_plugin = (add_plugin_t *)sasl_auxprop_add_plugin_nolog;
616		} else if (!strcmp(cur_ep->entryname, "sasl_canonuser_init")) {
617			type = CANONUSER;
618			add_plugin = (add_plugin_t *)sasl_canonuser_add_plugin;
619		} else {
620			/* What are we looking for then? */
621			return SASL_FAIL;
622		}
623		for (p = _sasl_static_plugins; p->type; p++) {
624			if (type == p->type) {
625				result = add_plugin(p->name, p->plug);
626				if (result == SASL_OK && type == AUXPROP) {
627					has_auxprop = 1;
628				}
629			}
630		}
631    }
632#endif /* !PIC */
633
634/* only do the following if:
635 *
636 * we support dlopen()
637 *  AND we are not staticly compiled
638 *      OR we are staticly compiled and TRY_DLOPEN_WHEN_STATIC is defined
639 */
640#if defined(DO_DLOPEN) && (defined(PIC) || (!defined(PIC) && defined(TRY_DLOPEN_WHEN_STATIC)))
641    /* get the path to the plugins */
642    result = ((sasl_getpath_t *)(getpath_cb->proc))(getpath_cb->context,
643						    &path);
644    if (result != SASL_OK) return result;
645    if (! path) return SASL_FAIL;
646
647    if (strlen(path) >= PATH_MAX) { /* no you can't buffer overrun */
648		return SASL_FAIL;
649    }
650
651    position=0;
652    do {
653		pos=0;
654		do {
655			c=path[position];
656			position++;
657			str[pos]=c;
658			pos++;
659		} while ((c!=':') && (c!='=') && (c!=0));
660		str[pos-1]='\0';
661
662		strcpy(prefix,str);
663		strcat(prefix,"/");
664
665		if ((dp=opendir(str)) !=NULL) /* ignore errors */
666		{
667			while ((dir=readdir(dp)) != NULL)
668			{
669				size_t length;
670				void *library;
671				char *c;
672				char plugname[PATH_MAX];
673				char name[PATH_MAX];
674
675				length = NAMLEN(dir);
676				if (length < 4)
677					continue; /* can not possibly be what we're looking for */
678
679				if (length + pos>=PATH_MAX) continue; /* too big */
680
681				if (strcmp(dir->d_name + (length - strlen(SO_SUFFIX)),
682					   SO_SUFFIX)
683					&& strcmp(dir->d_name + (length - strlen(LA_SUFFIX)),
684					   LA_SUFFIX))
685					continue;
686
687				memcpy(name,dir->d_name,length);
688				name[length]='\0';
689
690				result = _parse_la(prefix, name, tmp);
691				if(result != SASL_OK)
692					continue;
693
694				/* skip "lib" and cut off suffix --
695				   this only need be approximate */
696				strlcpy(plugname, (strncmp(name, "lib", 3) == 0) ? (name + 3) : name, sizeof(plugname));
697				c = strchr(plugname, (int)'.');
698				if(c) *c = '\0';
699
700				result = _sasl_get_plugin(tmp, verifyfile_cb, &library);
701
702				if(result != SASL_OK)
703					continue;
704
705				for(cur_ep = entrypoints; cur_ep->entryname; cur_ep++) {
706					/* If this fails, it's not the end of the world */
707					result = _sasl_plugin_load(plugname, library, cur_ep->entryname,
708							  cur_ep->add_plugin);
709					if (result == SASL_OK && strcmp(cur_ep->entryname, "sasl_auxprop_plug_init") == 0)
710						has_auxprop = 1;
711				}
712			}
713
714			closedir(dp);
715		}
716	} while ((c!='=') && (c!=0));
717#endif /* defined(DO_DLOPEN) && (!defined(PIC) || (defined(PIC) && defined(TRY_DLOPEN_WHEN_STATIC))) */
718
719    return has_auxprop ? SASL_OK : SASL_NOMECH;
720}
721
722int
723_sasl_done_with_plugins(void)
724{
725#ifdef DO_DLOPEN
726    lib_list_t *libptr, *libptr_next;
727
728    for(libptr = lib_list_head; libptr; libptr = libptr_next) {
729	libptr_next = libptr->next;
730	if(libptr->library)
731	    dlclose(libptr->library);
732	sasl_FREE(libptr);
733    }
734
735    lib_list_head = NULL;
736#endif /* DO_DLOPEN */
737    return SASL_OK;
738}
739