diff options
Diffstat (limited to 'src/util/support/plugins.c')
| -rw-r--r-- | src/util/support/plugins.c | 787 | 
1 files changed, 787 insertions, 0 deletions
| diff --git a/src/util/support/plugins.c b/src/util/support/plugins.c new file mode 100644 index 000000000000..b0bb2ada8755 --- /dev/null +++ b/src/util/support/plugins.c @@ -0,0 +1,787 @@ +/* -*- 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 <dlfcn.h> +#endif +#include <sys/types.h> +#ifdef HAVE_SYS_STAT_H +#include <sys/stat.h> +#endif +#ifdef HAVE_SYS_PARAM_H +#include <sys/param.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#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 + +#if USE_DLOPEN && USE_CFBUNDLE +#include <CoreFoundation/CoreFoundation.h> + +/* 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 <stdarg.h> +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 <dirent.h> +#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 <sys/ndir.h> +#elif HAVE_SYS_DIR_H +# include <sys/dir.h> +#elif HAVE_NDIR_H +# include <ndir.h> +#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 (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; +} | 
