/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ /* util/support/plugins.c - Plugin module support functions */ /* * Copyright 2006, 2008 by the Massachusetts Institute of Technology. * All Rights Reserved. * * Export of this software from the United States of America may * require a specific license from the United States Government. * It is the responsibility of any person or organization contemplating * export to obtain such a license before exporting. * * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and * distribute this software and its documentation for any purpose and * without fee is hereby granted, provided that the above copyright * notice appear in all copies and that both that copyright notice and * this permission notice appear in supporting documentation, and that * the name of M.I.T. not be used in advertising or publicity pertaining * to distribution of the software without specific, written prior * permission. Furthermore if you modify this software you must label * your software as modified software and not distribute it in such a * fashion that it might be confused with the original M.I.T. software. * M.I.T. makes no representations about the suitability of * this software for any purpose. It is provided "as is" without express * or implied warranty. */ #include "k5-platform.h" #include "k5-plugin.h" #if USE_DLOPEN #include #endif #include #ifdef HAVE_SYS_STAT_H #include #endif #ifdef HAVE_SYS_PARAM_H #include #endif #ifdef HAVE_UNISTD_H #include #endif #if USE_DLOPEN #ifdef RTLD_GROUP #define GROUP RTLD_GROUP #else #define GROUP 0 #endif #ifdef RTLD_NODELETE #define NODELETE RTLD_NODELETE #else #define NODELETE 0 #endif #define PLUGIN_DLOPEN_FLAGS (RTLD_NOW | RTLD_LOCAL | GROUP | NODELETE) #endif /* * glibc bug 11941, fixed in release 2.25, can cause an assertion failure in * dlclose() on process exit. Our workaround is to leak dlopen() handles * (which doesn't typically manifest in leak detection tools because the * handles are still reachable via a global table in libdl). Because we * dlopen() with RTLD_NODELETE, we weren't going to unload the plugin objects * anyway. */ #ifdef __GLIBC_PREREQ #if ! __GLIBC_PREREQ(2, 25) #define dlclose(x) #endif #endif #if USE_DLOPEN && USE_CFBUNDLE #include /* Currently CoreFoundation only exists on the Mac so we just use * pthreads directly to avoid creating empty function calls on other * platforms. If a thread initializer ever gets created in the common * plugin code, move this there */ static pthread_mutex_t krb5int_bundle_mutex = PTHREAD_MUTEX_INITIALIZER; #endif #include static void Tprintf (const char *fmt, ...) { #ifdef DEBUG va_list va; va_start (va, fmt); vfprintf (stderr, fmt, va); va_end (va); #endif } struct plugin_file_handle { #if USE_DLOPEN void *dlhandle; #endif #ifdef _WIN32 HMODULE hinstPlugin; #endif #if !defined (USE_DLOPEN) && !defined (_WIN32) char dummy; #endif }; #ifdef _WIN32 struct dirent { long d_ino; /* inode (always 1 in WIN32) */ off_t d_off; /* offset to this dirent */ unsigned short d_reclen; /* length of d_name */ char d_name[_MAX_FNAME+1]; /* filename (null terminated) */ }; typedef struct { intptr_t handle; /* _findfirst/_findnext handle */ short offset; /* offset into directory */ short finished; /* 1 if there are not more files */ struct _finddata_t fileinfo;/* from _findfirst/_findnext */ char *dir; /* the dir we are reading */ struct dirent dent; /* the dirent to return */ } DIR; DIR * opendir(const char *dir) { DIR *dp; char *filespec; intptr_t handle; int index; filespec = malloc(strlen(dir) + 2 + 1); strcpy(filespec, dir); index = strlen(filespec) - 1; if (index >= 0 && (filespec[index] == '/' || filespec[index] == '\\')) filespec[index] = '\0'; strcat(filespec, "/*"); dp = (DIR *)malloc(sizeof(DIR)); dp->offset = 0; dp->finished = 0; dp->dir = strdup(dir); if ((handle = _findfirst(filespec, &(dp->fileinfo))) < 0) { if (errno == ENOENT) dp->finished = 1; else { free(filespec); free(dp->dir); free(dp); return NULL; } } dp->handle = handle; free(filespec); return dp; } struct dirent * readdir(DIR *dp) { if (!dp || dp->finished) return NULL; if (dp->offset != 0) { if (_findnext(dp->handle, &(dp->fileinfo)) < 0) { dp->finished = 1; return NULL; } } dp->offset++; strncpy(dp->dent.d_name, dp->fileinfo.name, _MAX_FNAME); dp->dent.d_ino = 1; dp->dent.d_reclen = (unsigned short)strlen(dp->dent.d_name); dp->dent.d_off = dp->offset; return &(dp->dent); } int closedir(DIR *dp) { if (!dp) return 0; _findclose(dp->handle); free(dp->dir); free(dp); return 0; } #endif long KRB5_CALLCONV krb5int_open_plugin (const char *filepath, struct plugin_file_handle **h, struct errinfo *ep) { long err = 0; struct stat statbuf; struct plugin_file_handle *htmp = NULL; int got_plugin = 0; if (!err) { if (stat (filepath, &statbuf) < 0) { err = errno; Tprintf ("stat(%s): %s\n", filepath, strerror (err)); k5_set_error(ep, err, _("unable to find plugin [%s]: %s"), filepath, strerror(err)); } } if (!err) { htmp = calloc (1, sizeof (*htmp)); /* calloc initializes ptrs to NULL */ if (htmp == NULL) { err = ENOMEM; } } #if USE_DLOPEN if (!err && ((statbuf.st_mode & S_IFMT) == S_IFREG #if USE_CFBUNDLE || (statbuf.st_mode & S_IFMT) == S_IFDIR #endif /* USE_CFBUNDLE */ )) { void *handle = NULL; #if USE_CFBUNDLE char executablepath[MAXPATHLEN]; if ((statbuf.st_mode & S_IFMT) == S_IFDIR) { int lock_err = 0; CFStringRef pluginString = NULL; CFURLRef pluginURL = NULL; CFBundleRef pluginBundle = NULL; CFURLRef executableURL = NULL; /* Lock around CoreFoundation calls since objects are refcounted * and the refcounts are not thread-safe. Using pthreads directly * because this code is Mac-specific */ lock_err = pthread_mutex_lock(&krb5int_bundle_mutex); if (lock_err) { err = lock_err; } if (!err) { pluginString = CFStringCreateWithCString (kCFAllocatorDefault, filepath, kCFStringEncodingASCII); if (pluginString == NULL) { err = ENOMEM; } } if (!err) { pluginURL = CFURLCreateWithFileSystemPath (kCFAllocatorDefault, pluginString, kCFURLPOSIXPathStyle, true); if (pluginURL == NULL) { err = ENOMEM; } } if (!err) { pluginBundle = CFBundleCreate (kCFAllocatorDefault, pluginURL); if (pluginBundle == NULL) { err = ENOENT; } /* XXX need better error */ } if (!err) { executableURL = CFBundleCopyExecutableURL (pluginBundle); if (executableURL == NULL) { err = ENOMEM; } } if (!err) { if (!CFURLGetFileSystemRepresentation (executableURL, true, /* absolute */ (UInt8 *)executablepath, sizeof (executablepath))) { err = ENOMEM; } } if (!err) { /* override the path the caller passed in */ filepath = executablepath; } if (executableURL != NULL) { CFRelease (executableURL); } if (pluginBundle != NULL) { CFRelease (pluginBundle); } if (pluginURL != NULL) { CFRelease (pluginURL); } if (pluginString != NULL) { CFRelease (pluginString); } /* unlock after CFRelease calls since they modify refcounts */ if (!lock_err) { pthread_mutex_unlock (&krb5int_bundle_mutex); } } #endif /* USE_CFBUNDLE */ if (!err) { handle = dlopen(filepath, PLUGIN_DLOPEN_FLAGS); if (handle == NULL) { const char *e = dlerror(); if (e == NULL) e = _("unknown failure"); Tprintf ("dlopen(%s): %s\n", filepath, e); err = ENOENT; /* XXX */ k5_set_error(ep, err, _("unable to load plugin [%s]: %s"), filepath, e); } } if (!err) { got_plugin = 1; htmp->dlhandle = handle; handle = NULL; } if (handle != NULL) { dlclose (handle); } } #endif /* USE_DLOPEN */ #ifdef _WIN32 if (!err && (statbuf.st_mode & S_IFMT) == S_IFREG) { HMODULE handle = NULL; handle = LoadLibrary(filepath); if (handle == NULL) { Tprintf ("Unable to load dll: %s\n", filepath); err = ENOENT; /* XXX */ k5_set_error(ep, err, _("unable to load DLL [%s]"), filepath); } if (!err) { got_plugin = 1; htmp->hinstPlugin = handle; handle = NULL; } if (handle != NULL) FreeLibrary(handle); } #endif if (!err && !got_plugin) { err = ENOENT; /* no plugin or no way to load plugins */ k5_set_error(ep, err, _("plugin unavailable: %s"), strerror(err)); } if (!err) { *h = htmp; htmp = NULL; /* h takes ownership */ } free(htmp); return err; } static long krb5int_get_plugin_sym (struct plugin_file_handle *h, const char *csymname, int isfunc, void **ptr, struct errinfo *ep) { long err = 0; void *sym = NULL; #if USE_DLOPEN if (!err && !sym && (h->dlhandle != NULL)) { /* XXX Do we need to add a leading "_" to the symbol name on any modern platforms? */ sym = dlsym (h->dlhandle, csymname); if (sym == NULL) { const char *e = dlerror (); /* XXX copy and save away */ if (e == NULL) e = "unknown failure"; Tprintf ("dlsym(%s): %s\n", csymname, e); err = ENOENT; /* XXX */ k5_set_error(ep, err, "%s", e); } } #endif #ifdef _WIN32 LPVOID lpMsgBuf; DWORD dw; if (!err && !sym && (h->hinstPlugin != NULL)) { sym = GetProcAddress(h->hinstPlugin, csymname); if (sym == NULL) { const char *e = "unable to get dll symbol"; /* XXX copy and save away */ Tprintf ("GetProcAddress(%s): %i\n", csymname, GetLastError()); err = ENOENT; /* XXX */ k5_set_error(ep, err, "%s", e); dw = GetLastError(); if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, dw, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &lpMsgBuf, 0, NULL )) { fprintf (stderr, "unable to get dll symbol, %s\n", (LPCTSTR)lpMsgBuf); LocalFree(lpMsgBuf); } } } #endif if (!err && (sym == NULL)) { err = ENOENT; /* unimplemented */ } if (!err) { *ptr = sym; } return err; } long KRB5_CALLCONV krb5int_get_plugin_data (struct plugin_file_handle *h, const char *csymname, void **ptr, struct errinfo *ep) { return krb5int_get_plugin_sym (h, csymname, 0, ptr, ep); } long KRB5_CALLCONV krb5int_get_plugin_func (struct plugin_file_handle *h, const char *csymname, void (**ptr)(), struct errinfo *ep) { void *dptr = NULL; long err = krb5int_get_plugin_sym (h, csymname, 1, &dptr, ep); if (!err) { /* Cast function pointers to avoid code duplication */ *ptr = (void (*)()) dptr; } return err; } void KRB5_CALLCONV krb5int_close_plugin (struct plugin_file_handle *h) { #if USE_DLOPEN if (h->dlhandle != NULL) { dlclose(h->dlhandle); } #endif #ifdef _WIN32 if (h->hinstPlugin != NULL) { FreeLibrary(h->hinstPlugin); } #endif free (h); } /* autoconf docs suggest using this preference order */ #if HAVE_DIRENT_H || USE_DIRENT_H #include #define NAMELEN(D) strlen((D)->d_name) #else #ifndef _WIN32 #define dirent direct #define NAMELEN(D) ((D)->d->namlen) #else #define NAMELEN(D) strlen((D)->d_name) #endif #if HAVE_SYS_NDIR_H # include #elif HAVE_SYS_DIR_H # include #elif HAVE_NDIR_H # include #endif #endif static long krb5int_plugin_file_handle_array_init (struct plugin_file_handle ***harray) { long err = 0; *harray = calloc (1, sizeof (**harray)); /* calloc initializes to NULL */ if (*harray == NULL) { err = ENOMEM; } return err; } static long krb5int_plugin_file_handle_array_add (struct plugin_file_handle ***harray, size_t *count, struct plugin_file_handle *p) { long err = 0; struct plugin_file_handle **newharray = NULL; size_t newcount = *count + 1; newharray = realloc (*harray, ((newcount + 1) * sizeof (**harray))); /* +1 for NULL */ if (newharray == NULL) { err = ENOMEM; } else { newharray[newcount - 1] = p; newharray[newcount] = NULL; *count = newcount; *harray = newharray; } return err; } static void krb5int_plugin_file_handle_array_free (struct plugin_file_handle **harray) { if (harray != NULL) { int i; for (i = 0; harray[i] != NULL; i++) { krb5int_close_plugin (harray[i]); } free (harray); } } #if TARGET_OS_MAC #define FILEEXTS { "", ".bundle", ".dylib", ".so", NULL } #elif defined(_WIN32) #define FILEEXTS { "", ".dll", NULL } #else #define FILEEXTS { "", ".so", NULL } #endif static void krb5int_free_plugin_filenames (char **filenames) { if (filenames != NULL) { int i; for (i = 0; filenames[i] != NULL; i++) { free (filenames[i]); } free (filenames); } } static long krb5int_get_plugin_filenames (const char * const *filebases, char ***filenames) { long err = 0; static const char *const fileexts[] = FILEEXTS; char **tempnames = NULL; size_t bases_count = 0; size_t exts_count = 0; size_t i; if (!filebases) { err = EINVAL; } if (!filenames) { err = EINVAL; } if (!err) { for (i = 0; filebases[i]; i++) { bases_count++; } for (i = 0; fileexts[i]; i++) { exts_count++; } tempnames = calloc ((bases_count * exts_count)+1, sizeof (char *)); if (!tempnames) { err = ENOMEM; } } if (!err) { size_t j; for (i = 0; !err && filebases[i]; i++) { for (j = 0; !err && fileexts[j]; j++) { if (asprintf(&tempnames[(i*exts_count)+j], "%s%s", filebases[i], fileexts[j]) < 0) { tempnames[(i*exts_count)+j] = NULL; err = ENOMEM; } } } tempnames[bases_count * exts_count] = NULL; /* NUL-terminate */ } if (!err) { *filenames = tempnames; tempnames = NULL; } krb5int_free_plugin_filenames(tempnames); return err; } /* Takes a NULL-terminated list of directories. If filebases is NULL, filebases is ignored * all plugins in the directories are loaded. If filebases is a NULL-terminated array of names, * only plugins in the directories with those name (plus any platform extension) are loaded. */ long KRB5_CALLCONV krb5int_open_plugin_dirs (const char * const *dirnames, const char * const *filebases, struct plugin_dir_handle *dirhandle, struct errinfo *ep) { long err = 0; struct plugin_file_handle **h = NULL; size_t count = 0; char **filenames = NULL; int i; if (!err) { err = krb5int_plugin_file_handle_array_init (&h); } if (!err && (filebases != NULL)) { err = krb5int_get_plugin_filenames (filebases, &filenames); } for (i = 0; !err && dirnames[i] != NULL; i++) { if (filenames != NULL) { /* load plugins with names from filenames from each directory */ int j; for (j = 0; !err && filenames[j] != NULL; j++) { struct plugin_file_handle *handle = NULL; char *filepath = NULL; if (!err) { if (asprintf(&filepath, "%s/%s", dirnames[i], filenames[j]) < 0) { filepath = NULL; err = ENOMEM; } } if (!err && krb5int_open_plugin(filepath, &handle, ep) == 0) { err = krb5int_plugin_file_handle_array_add (&h, &count, handle); if (!err) handle = NULL; /* h takes ownership */ } free(filepath); if (handle != NULL) { krb5int_close_plugin (handle); } } } else { /* load all plugins in each directory */ DIR *dir = opendir (dirnames[i]); while (dir != NULL && !err) { struct dirent *d = NULL; char *filepath = NULL; struct plugin_file_handle *handle = NULL; d = readdir (dir); if (d == NULL) { break; } if ((strcmp (d->d_name, ".") == 0) || (strcmp (d->d_name, "..") == 0)) { continue; } if (!err) { int len = NAMELEN (d); if (asprintf(&filepath, "%s/%*s", dirnames[i], len, d->d_name) < 0) { filepath = NULL; err = ENOMEM; } } if (!err) { if (krb5int_open_plugin (filepath, &handle, ep) == 0) { err = krb5int_plugin_file_handle_array_add (&h, &count, handle); if (!err) { handle = NULL; } /* h takes ownership */ } } free(filepath); if (handle != NULL) { krb5int_close_plugin (handle); } } if (dir != NULL) { closedir (dir); } } } if (err == ENOENT) { err = 0; /* ran out of plugins -- do nothing */ } if (!err) { dirhandle->files = h; h = NULL; /* dirhandle->files takes ownership */ } if (filenames != NULL) { krb5int_free_plugin_filenames (filenames); } if (h != NULL) { krb5int_plugin_file_handle_array_free (h); } return err; } void KRB5_CALLCONV krb5int_close_plugin_dirs (struct plugin_dir_handle *dirhandle) { if (dirhandle->files != NULL) { int i; for (i = 0; dirhandle->files[i] != NULL; i++) { krb5int_close_plugin (dirhandle->files[i]); } free (dirhandle->files); dirhandle->files = NULL; } } void KRB5_CALLCONV krb5int_free_plugin_dir_data (void **ptrs) { /* Nothing special to be done per pointer. */ free(ptrs); } long KRB5_CALLCONV krb5int_get_plugin_dir_data (struct plugin_dir_handle *dirhandle, const char *symname, void ***ptrs, struct errinfo *ep) { long err = 0; void **p = NULL; size_t count = 0; /* XXX Do we need to add a leading "_" to the symbol name on any modern platforms? */ Tprintf("get_plugin_data_sym(%s)\n", symname); if (!err) { p = calloc (1, sizeof (*p)); /* calloc initializes to NULL */ if (p == NULL) { err = ENOMEM; } } if (!err && (dirhandle != NULL) && (dirhandle->files != NULL)) { int i = 0; for (i = 0; !err && (dirhandle->files[i] != NULL); i++) { void *sym = NULL; if (krb5int_get_plugin_data (dirhandle->files[i], symname, &sym, ep) == 0) { void **newp = NULL; count++; newp = realloc (p, ((count + 1) * sizeof (*p))); /* +1 for NULL */ if (newp == NULL) { err = ENOMEM; } else { p = newp; p[count - 1] = sym; p[count] = NULL; } } } } if (!err) { *ptrs = p; p = NULL; /* ptrs takes ownership */ } free(p); return err; } void KRB5_CALLCONV krb5int_free_plugin_dir_func (void (**ptrs)(void)) { /* Nothing special to be done per pointer. */ free(ptrs); } long KRB5_CALLCONV krb5int_get_plugin_dir_func (struct plugin_dir_handle *dirhandle, const char *symname, void (***ptrs)(void), struct errinfo *ep) { long err = 0; void (**p)() = NULL; size_t count = 0; /* XXX Do we need to add a leading "_" to the symbol name on any modern platforms? */ Tprintf("get_plugin_data_sym(%s)\n", symname); if (!err) { p = calloc (1, sizeof (*p)); /* calloc initializes to NULL */ if (p == NULL) { err = ENOMEM; } } if (!err && (dirhandle != NULL) && (dirhandle->files != NULL)) { int i = 0; for (i = 0; !err && (dirhandle->files[i] != NULL); i++) { void (*sym)() = NULL; if (krb5int_get_plugin_func (dirhandle->files[i], symname, &sym, ep) == 0) { void (**newp)() = NULL; count++; newp = realloc (p, ((count + 1) * sizeof (*p))); /* +1 for NULL */ if (newp == NULL) { err = ENOMEM; } else { p = newp; p[count - 1] = sym; p[count] = NULL; } } } } if (!err) { *ptrs = p; p = NULL; /* ptrs takes ownership */ } free(p); return err; }