1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /* util/support/plugins.c - Plugin module support functions */
4 * Copyright 2006, 2008 by the Massachusetts Institute of Technology.
7 * Export of this software from the United States of America may
8 * require a specific license from the United States Government.
9 * It is the responsibility of any person or organization contemplating
10 * export to obtain such a license before exporting.
12 * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
13 * distribute this software and its documentation for any purpose and
14 * without fee is hereby granted, provided that the above copyright
15 * notice appear in all copies and that both that copyright notice and
16 * this permission notice appear in supporting documentation, and that
17 * the name of M.I.T. not be used in advertising or publicity pertaining
18 * to distribution of the software without specific, written prior
19 * permission. Furthermore if you modify this software you must label
20 * your software as modified software and not distribute it in such a
21 * fashion that it might be confused with the original M.I.T. software.
22 * M.I.T. makes no representations about the suitability of
23 * this software for any purpose. It is provided "as is" without express
24 * or implied warranty.
27 #include "k5-platform.h"
28 #include "k5-plugin.h"
32 #include <sys/types.h>
33 #ifdef HAVE_SYS_STAT_H
36 #ifdef HAVE_SYS_PARAM_H
37 #include <sys/param.h>
45 #define GROUP RTLD_GROUP
50 #define NODELETE RTLD_NODELETE
54 #define PLUGIN_DLOPEN_FLAGS (RTLD_NOW | RTLD_LOCAL | GROUP | NODELETE)
58 * glibc bug 11941, fixed in release 2.25, can cause an assertion failure in
59 * dlclose() on process exit. Our workaround is to leak dlopen() handles
60 * (which doesn't typically manifest in leak detection tools because the
61 * handles are still reachable via a global table in libdl). Because we
62 * dlopen() with RTLD_NODELETE, we weren't going to unload the plugin objects
66 #if ! __GLIBC_PREREQ(2, 25)
71 #if USE_DLOPEN && USE_CFBUNDLE
72 #include <CoreFoundation/CoreFoundation.h>
74 /* Currently CoreFoundation only exists on the Mac so we just use
75 * pthreads directly to avoid creating empty function calls on other
76 * platforms. If a thread initializer ever gets created in the common
77 * plugin code, move this there */
78 static pthread_mutex_t krb5int_bundle_mutex = PTHREAD_MUTEX_INITIALIZER;
82 static void Tprintf (const char *fmt, ...)
87 vfprintf (stderr, fmt, va);
92 struct plugin_file_handle {
99 #if !defined (USE_DLOPEN) && !defined (_WIN32)
106 long d_ino; /* inode (always 1 in WIN32) */
107 off_t d_off; /* offset to this dirent */
108 unsigned short d_reclen; /* length of d_name */
109 char d_name[_MAX_FNAME+1]; /* filename (null terminated) */
113 intptr_t handle; /* _findfirst/_findnext handle */
114 short offset; /* offset into directory */
115 short finished; /* 1 if there are not more files */
116 struct _finddata_t fileinfo;/* from _findfirst/_findnext */
117 char *dir; /* the dir we are reading */
118 struct dirent dent; /* the dirent to return */
121 DIR * opendir(const char *dir)
128 filespec = malloc(strlen(dir) + 2 + 1);
129 strcpy(filespec, dir);
130 index = strlen(filespec) - 1;
131 if (index >= 0 && (filespec[index] == '/' || filespec[index] == '\\'))
132 filespec[index] = '\0';
133 strcat(filespec, "/*");
135 dp = (DIR *)malloc(sizeof(DIR));
138 dp->dir = strdup(dir);
140 if ((handle = _findfirst(filespec, &(dp->fileinfo))) < 0) {
157 struct dirent * readdir(DIR *dp)
159 if (!dp || dp->finished) return NULL;
161 if (dp->offset != 0) {
162 if (_findnext(dp->handle, &(dp->fileinfo)) < 0) {
169 strncpy(dp->dent.d_name, dp->fileinfo.name, _MAX_FNAME);
171 dp->dent.d_reclen = (unsigned short)strlen(dp->dent.d_name);
172 dp->dent.d_off = dp->offset;
177 int closedir(DIR *dp)
180 _findclose(dp->handle);
189 krb5int_open_plugin (const char *filepath, struct plugin_file_handle **h, struct errinfo *ep)
192 struct plugin_file_handle *htmp = NULL;
194 #if defined(USE_CFBUNDLE) || defined(_WIN32)
198 if (stat (filepath, &statbuf) < 0) {
200 Tprintf ("stat(%s): %s\n", filepath, strerror (err));
201 k5_set_error(ep, err, _("unable to find plugin [%s]: %s"),
202 filepath, strerror(err));
208 htmp = calloc (1, sizeof (*htmp)); /* calloc initializes ptrs to NULL */
209 if (htmp == NULL) { err = ENOMEM; }
215 && ((statbuf.st_mode & S_IFMT) == S_IFREG
216 || (statbuf.st_mode & S_IFMT) == S_IFDIR)
217 #endif /* USE_CFBUNDLE */
222 char executablepath[MAXPATHLEN];
224 if ((statbuf.st_mode & S_IFMT) == S_IFDIR) {
226 CFStringRef pluginString = NULL;
227 CFURLRef pluginURL = NULL;
228 CFBundleRef pluginBundle = NULL;
229 CFURLRef executableURL = NULL;
231 /* Lock around CoreFoundation calls since objects are refcounted
232 * and the refcounts are not thread-safe. Using pthreads directly
233 * because this code is Mac-specific */
234 lock_err = pthread_mutex_lock(&krb5int_bundle_mutex);
235 if (lock_err) { err = lock_err; }
238 pluginString = CFStringCreateWithCString (kCFAllocatorDefault,
240 kCFStringEncodingASCII);
241 if (pluginString == NULL) { err = ENOMEM; }
245 pluginURL = CFURLCreateWithFileSystemPath (kCFAllocatorDefault,
247 kCFURLPOSIXPathStyle,
249 if (pluginURL == NULL) { err = ENOMEM; }
253 pluginBundle = CFBundleCreate (kCFAllocatorDefault, pluginURL);
254 if (pluginBundle == NULL) { err = ENOENT; } /* XXX need better error */
258 executableURL = CFBundleCopyExecutableURL (pluginBundle);
259 if (executableURL == NULL) { err = ENOMEM; }
263 if (!CFURLGetFileSystemRepresentation (executableURL,
265 (UInt8 *)executablepath,
266 sizeof (executablepath))) {
272 /* override the path the caller passed in */
273 filepath = executablepath;
276 if (executableURL != NULL) { CFRelease (executableURL); }
277 if (pluginBundle != NULL) { CFRelease (pluginBundle); }
278 if (pluginURL != NULL) { CFRelease (pluginURL); }
279 if (pluginString != NULL) { CFRelease (pluginString); }
281 /* unlock after CFRelease calls since they modify refcounts */
282 if (!lock_err) { pthread_mutex_unlock (&krb5int_bundle_mutex); }
284 #endif /* USE_CFBUNDLE */
287 handle = dlopen(filepath, PLUGIN_DLOPEN_FLAGS);
288 if (handle == NULL) {
289 const char *e = dlerror();
291 e = _("unknown failure");
292 Tprintf ("dlopen(%s): %s\n", filepath, e);
293 err = ENOENT; /* XXX */
294 k5_set_error(ep, err, _("unable to load plugin [%s]: %s"),
301 htmp->dlhandle = handle;
305 if (handle != NULL) { dlclose (handle); }
307 #endif /* USE_DLOPEN */
310 if (!err && (statbuf.st_mode & S_IFMT) == S_IFREG) {
311 HMODULE handle = NULL;
313 handle = LoadLibrary(filepath);
314 if (handle == NULL) {
315 Tprintf ("Unable to load dll: %s\n", filepath);
316 err = ENOENT; /* XXX */
317 k5_set_error(ep, err, _("unable to load DLL [%s]"), filepath);
322 htmp->hinstPlugin = handle;
331 if (!err && !got_plugin) {
332 err = ENOENT; /* no plugin or no way to load plugins */
333 k5_set_error(ep, err, _("plugin unavailable: %s"), strerror(err));
338 htmp = NULL; /* h takes ownership */
347 krb5int_get_plugin_sym (struct plugin_file_handle *h,
348 const char *csymname, int isfunc, void **ptr,
355 if (!err && !sym && (h->dlhandle != NULL)) {
356 /* XXX Do we need to add a leading "_" to the symbol name on any
358 sym = dlsym (h->dlhandle, csymname);
360 const char *e = dlerror (); /* XXX copy and save away */
362 e = "unknown failure";
363 Tprintf ("dlsym(%s): %s\n", csymname, e);
364 err = ENOENT; /* XXX */
365 k5_set_error(ep, err, "%s", e);
374 if (!err && !sym && (h->hinstPlugin != NULL)) {
375 sym = GetProcAddress(h->hinstPlugin, csymname);
377 const char *e = "unable to get dll symbol"; /* XXX copy and save away */
378 Tprintf ("GetProcAddress(%s): %i\n", csymname, GetLastError());
379 err = ENOENT; /* XXX */
380 k5_set_error(ep, err, "%s", e);
383 if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
384 FORMAT_MESSAGE_FROM_SYSTEM,
387 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
391 fprintf (stderr, "unable to get dll symbol, %s\n", (LPCTSTR)lpMsgBuf);
398 if (!err && (sym == NULL)) {
399 err = ENOENT; /* unimplemented */
410 krb5int_get_plugin_data (struct plugin_file_handle *h, const char *csymname,
411 void **ptr, struct errinfo *ep)
413 return krb5int_get_plugin_sym (h, csymname, 0, ptr, ep);
417 krb5int_get_plugin_func (struct plugin_file_handle *h, const char *csymname,
418 void (**ptr)(), struct errinfo *ep)
421 long err = krb5int_get_plugin_sym (h, csymname, 1, &dptr, ep);
423 /* Cast function pointers to avoid code duplication */
424 *ptr = (void (*)()) dptr;
430 krb5int_close_plugin (struct plugin_file_handle *h)
433 if (h->dlhandle != NULL) { dlclose(h->dlhandle); }
436 if (h->hinstPlugin != NULL) { FreeLibrary(h->hinstPlugin); }
441 /* autoconf docs suggest using this preference order */
442 #if HAVE_DIRENT_H || USE_DIRENT_H
444 #define NAMELEN(D) strlen((D)->d_name)
447 #define dirent direct
448 #define NAMELEN(D) ((D)->d->namlen)
450 #define NAMELEN(D) strlen((D)->d_name)
453 # include <sys/ndir.h>
455 # include <sys/dir.h>
462 krb5int_plugin_file_handle_array_init (struct plugin_file_handle ***harray)
466 *harray = calloc (1, sizeof (**harray)); /* calloc initializes to NULL */
467 if (*harray == NULL) { err = ENOMEM; }
473 krb5int_plugin_file_handle_array_add (struct plugin_file_handle ***harray, size_t *count,
474 struct plugin_file_handle *p)
477 struct plugin_file_handle **newharray = NULL;
478 size_t newcount = *count + 1;
480 newharray = realloc (*harray, ((newcount + 1) * sizeof (**harray))); /* +1 for NULL */
481 if (newharray == NULL) {
484 newharray[newcount - 1] = p;
485 newharray[newcount] = NULL;
494 krb5int_plugin_file_handle_array_free (struct plugin_file_handle **harray)
496 if (harray != NULL) {
498 for (i = 0; harray[i] != NULL; i++) {
499 krb5int_close_plugin (harray[i]);
506 #define FILEEXTS { "", ".bundle", ".dylib", ".so", NULL }
507 #elif defined(_WIN32)
508 #define FILEEXTS { "", ".dll", NULL }
510 #define FILEEXTS { "", ".so", NULL }
515 krb5int_free_plugin_filenames (char **filenames)
517 if (filenames != NULL) {
519 for (i = 0; filenames[i] != NULL; i++) {
528 krb5int_get_plugin_filenames (const char * const *filebases, char ***filenames)
531 static const char *const fileexts[] = FILEEXTS;
532 char **tempnames = NULL;
533 size_t bases_count = 0;
534 size_t exts_count = 0;
537 if (!filebases) { err = EINVAL; }
538 if (!filenames) { err = EINVAL; }
541 for (i = 0; filebases[i]; i++) { bases_count++; }
542 for (i = 0; fileexts[i]; i++) { exts_count++; }
543 tempnames = calloc ((bases_count * exts_count)+1, sizeof (char *));
544 if (!tempnames) { err = ENOMEM; }
549 for (i = 0; !err && filebases[i]; i++) {
550 for (j = 0; !err && fileexts[j]; j++) {
551 if (asprintf(&tempnames[(i*exts_count)+j], "%s%s",
552 filebases[i], fileexts[j]) < 0) {
553 tempnames[(i*exts_count)+j] = NULL;
558 tempnames[bases_count * exts_count] = NULL; /* NUL-terminate */
562 *filenames = tempnames;
566 krb5int_free_plugin_filenames(tempnames);
572 /* Takes a NULL-terminated list of directories. If filebases is NULL, filebases is ignored
573 * all plugins in the directories are loaded. If filebases is a NULL-terminated array of names,
574 * only plugins in the directories with those name (plus any platform extension) are loaded. */
577 krb5int_open_plugin_dirs (const char * const *dirnames,
578 const char * const *filebases,
579 struct plugin_dir_handle *dirhandle,
583 struct plugin_file_handle **h = NULL;
585 char **filenames = NULL;
589 err = krb5int_plugin_file_handle_array_init (&h);
592 if (!err && (filebases != NULL)) {
593 err = krb5int_get_plugin_filenames (filebases, &filenames);
596 for (i = 0; !err && dirnames[i] != NULL; i++) {
597 if (filenames != NULL) {
598 /* load plugins with names from filenames from each directory */
601 for (j = 0; !err && filenames[j] != NULL; j++) {
602 struct plugin_file_handle *handle = NULL;
603 char *filepath = NULL;
606 if (asprintf(&filepath, "%s/%s", dirnames[i], filenames[j]) < 0) {
612 if (!err && krb5int_open_plugin(filepath, &handle, ep) == 0) {
613 err = krb5int_plugin_file_handle_array_add (&h, &count, handle);
615 handle = NULL; /* h takes ownership */
619 if (handle != NULL) { krb5int_close_plugin (handle); }
622 /* load all plugins in each directory */
623 DIR *dir = opendir (dirnames[i]);
625 while (dir != NULL && !err) {
626 struct dirent *d = NULL;
627 char *filepath = NULL;
628 struct plugin_file_handle *handle = NULL;
631 if (d == NULL) { break; }
633 if ((strcmp (d->d_name, ".") == 0) ||
634 (strcmp (d->d_name, "..") == 0)) {
639 int len = NAMELEN (d);
640 if (asprintf(&filepath, "%s/%*s", dirnames[i], len, d->d_name) < 0) {
647 if (krb5int_open_plugin (filepath, &handle, ep) == 0) {
648 err = krb5int_plugin_file_handle_array_add (&h, &count, handle);
649 if (!err) { handle = NULL; } /* h takes ownership */
654 if (handle != NULL) { krb5int_close_plugin (handle); }
657 if (dir != NULL) { closedir (dir); }
662 err = 0; /* ran out of plugins -- do nothing */
666 dirhandle->files = h;
667 h = NULL; /* dirhandle->files takes ownership */
670 if (filenames != NULL) { krb5int_free_plugin_filenames (filenames); }
671 if (h != NULL) { krb5int_plugin_file_handle_array_free (h); }
677 krb5int_close_plugin_dirs (struct plugin_dir_handle *dirhandle)
679 if (dirhandle->files != NULL) {
681 for (i = 0; dirhandle->files[i] != NULL; i++) {
682 krb5int_close_plugin (dirhandle->files[i]);
684 free (dirhandle->files);
685 dirhandle->files = NULL;
690 krb5int_free_plugin_dir_data (void **ptrs)
692 /* Nothing special to be done per pointer. */
697 krb5int_get_plugin_dir_data (struct plugin_dir_handle *dirhandle,
706 /* XXX Do we need to add a leading "_" to the symbol name on any
709 Tprintf("get_plugin_data_sym(%s)\n", symname);
712 p = calloc (1, sizeof (*p)); /* calloc initializes to NULL */
713 if (p == NULL) { err = ENOMEM; }
716 if (!err && (dirhandle != NULL) && (dirhandle->files != NULL)) {
719 for (i = 0; !err && (dirhandle->files[i] != NULL); i++) {
722 if (krb5int_get_plugin_data (dirhandle->files[i], symname, &sym, ep) == 0) {
726 newp = realloc (p, ((count + 1) * sizeof (*p))); /* +1 for NULL */
740 p = NULL; /* ptrs takes ownership */
749 krb5int_free_plugin_dir_func (void (**ptrs)(void))
751 /* Nothing special to be done per pointer. */
756 krb5int_get_plugin_dir_func (struct plugin_dir_handle *dirhandle,
758 void (***ptrs)(void),
765 /* XXX Do we need to add a leading "_" to the symbol name on any
768 Tprintf("get_plugin_data_sym(%s)\n", symname);
771 p = calloc (1, sizeof (*p)); /* calloc initializes to NULL */
772 if (p == NULL) { err = ENOMEM; }
775 if (!err && (dirhandle != NULL) && (dirhandle->files != NULL)) {
778 for (i = 0; !err && (dirhandle->files[i] != NULL); i++) {
779 void (*sym)() = NULL;
781 if (krb5int_get_plugin_func (dirhandle->files[i], symname, &sym, ep) == 0) {
782 void (**newp)() = NULL;
785 newp = realloc (p, ((count + 1) * sizeof (*p))); /* +1 for NULL */
799 p = NULL; /* ptrs takes ownership */