Home | History | Annotate | Download | only in support
      1 /*
      2  * util/support/plugins.c
      3  *
      4  * Copyright 2006 by the Massachusetts Institute of Technology.
      5  * All Rights Reserved.
      6  *
      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.
     11  *
     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.
     25  *
     26  *
     27  * Plugin module support, and shims around dlopen/whatever.
     28  */
     29 
     30 /*
     31  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
     32  * Use is subject to license terms.
     33  */
     34 
     35 
     36 #include "k5-plugin.h"
     37 #if USE_DLOPEN
     38 #include <dlfcn.h>
     39 #endif
     40 #if USE_CFBUNDLE
     41 #include <CoreFoundation/CoreFoundation.h>
     42 #endif
     43 #include <stdio.h>
     44 #include <sys/types.h>
     45 #ifdef HAVE_SYS_STAT_H
     46 #include <sys/stat.h>
     47 #endif
     48 #ifdef HAVE_SYS_PARAM_H
     49 #include <sys/param.h>
     50 #endif
     51 #include <errno.h>
     52 #include <stdlib.h>
     53 #include <string.h>
     54 #ifdef HAVE_UNISTD_H
     55 #include <unistd.h>
     56 #endif
     57 
     58 #include <stdarg.h>
     59 /*ARGSUSED*/
     60 static void Tprintf (const char *fmt, ...)
     61 {
     62 #ifdef DEBUG
     63     va_list va;
     64     va_start (va, fmt);
     65     vfprintf (stderr, fmt, va);
     66     va_end (va);
     67 #endif
     68 }
     69 
     70 struct plugin_file_handle {
     71 #if USE_DLOPEN
     72     void *dlhandle;
     73 #endif
     74 #if USE_CFBUNDLE
     75     CFBundleRef bundle;
     76 #endif
     77 #if !defined (USE_DLOPEN) && !defined (USE_CFBUNDLE)
     78     char dummy;
     79 #endif
     80 };
     81 
     82 /*ARGSUSED2*/
     83 long KRB5_CALLCONV
     84 krb5int_open_plugin (const char *filepath, struct plugin_file_handle **h, struct errinfo *ep)
     85 {
     86     long err = 0;
     87     struct stat statbuf;
     88     struct plugin_file_handle *htmp = NULL;
     89     int got_plugin = 0;
     90 
     91     if (!err) {
     92         if (stat (filepath, &statbuf) < 0) {
     93             Tprintf ("stat(%s): %s\n", filepath, strerror (errno));
     94             err = errno;
     95         }
     96     }
     97 
     98     if (!err) {
     99         htmp = calloc (1, sizeof (*htmp)); /* calloc initializes ptrs to NULL */
    100         if (htmp == NULL) { err = errno; }
    101     }
    102 
    103 #if USE_DLOPEN
    104     if (!err && (statbuf.st_mode & S_IFMT) == S_IFREG) {
    105         void *handle = NULL;
    106 #ifdef RTLD_GROUP
    107 #define PLUGIN_DLOPEN_FLAGS (RTLD_NOW | RTLD_LOCAL | RTLD_GROUP)
    108 #else
    109 #define PLUGIN_DLOPEN_FLAGS (RTLD_NOW | RTLD_LOCAL)
    110 #endif
    111 
    112         if (!err) {
    113             handle = dlopen(filepath, PLUGIN_DLOPEN_FLAGS);
    114             if (handle == NULL) {
    115                 const char *e = dlerror();
    116                 Tprintf ("dlopen(%s): %s\n", filepath, e);
    117                 err = ENOENT; /* XXX */
    118 		krb5int_set_error (ep, err, "%s", e);
    119             }
    120         }
    121 
    122         if (!err) {
    123             got_plugin = 1;
    124             htmp->dlhandle = handle;
    125             handle = NULL;
    126         }
    127 
    128         if (handle != NULL) { dlclose (handle); }
    129     }
    130 #endif
    131 
    132 #if USE_CFBUNDLE
    133     if (!err && (statbuf.st_mode & S_IFMT) == S_IFDIR) {
    134         CFStringRef pluginPath = NULL;
    135         CFURLRef pluginURL = NULL;
    136         CFBundleRef pluginBundle = NULL;
    137 
    138         if (!err) {
    139             pluginPath = CFStringCreateWithCString (kCFAllocatorDefault, filepath,
    140                                                     kCFStringEncodingASCII);
    141             if (pluginPath == NULL) { err = ENOMEM; }
    142         }
    143 
    144         if (!err) {
    145             pluginURL = CFURLCreateWithFileSystemPath (kCFAllocatorDefault, pluginPath,
    146                                                        kCFURLPOSIXPathStyle, true);
    147             if (pluginURL == NULL) { err = ENOMEM; }
    148         }
    149 
    150         if (!err) {
    151             pluginBundle = CFBundleCreate (kCFAllocatorDefault, pluginURL);
    152             if (pluginBundle == NULL) { err = ENOENT; } /* XXX need better error */
    153         }
    154 
    155         if (!err) {
    156             if (!CFBundleIsExecutableLoaded (pluginBundle)) {
    157                 int loaded = CFBundleLoadExecutable (pluginBundle);
    158                 if (!loaded) { err = ENOENT; }  /* XXX need better error */
    159             }
    160         }
    161 
    162         if (!err) {
    163             got_plugin = 1;
    164             htmp->bundle = pluginBundle;
    165             pluginBundle = NULL;  /* htmp->bundle takes ownership */
    166         }
    167 
    168         if (pluginBundle != NULL) { CFRelease (pluginBundle); }
    169         if (pluginURL    != NULL) { CFRelease (pluginURL); }
    170         if (pluginPath   != NULL) { CFRelease (pluginPath); }
    171     }
    172 #endif
    173 
    174     if (!err && !got_plugin) {
    175         err = ENOENT;  /* no plugin or no way to load plugins */
    176     }
    177 
    178     if (!err) {
    179         *h = htmp;
    180         htmp = NULL;  /* h takes ownership */
    181     }
    182 
    183     if (htmp != NULL) { free (htmp); }
    184 
    185     return err;
    186 }
    187 
    188 /*ARGSUSED*/
    189 static long
    190 krb5int_get_plugin_sym (struct plugin_file_handle *h,
    191                         const char *csymname, int isfunc, void **ptr,
    192 			struct errinfo *ep)
    193 {
    194     long err = 0;
    195     void *sym = NULL;
    196 
    197 #if USE_DLOPEN
    198     if (!err && !sym && (h->dlhandle != NULL)) {
    199         /* XXX Do we need to add a leading "_" to the symbol name on any
    200         modern platforms?  */
    201         sym = dlsym (h->dlhandle, csymname);
    202         if (sym == NULL) {
    203             const char *e = dlerror (); /* XXX copy and save away */
    204             Tprintf ("dlsym(%s): %s\n", csymname, e);
    205             err = ENOENT; /* XXX */
    206 	    krb5int_set_error(ep, err, "%s", e);
    207         }
    208     }
    209 #endif
    210 
    211 #if USE_CFBUNDLE
    212     if (!err && !sym && (h->bundle != NULL)) {
    213         CFStringRef cfsymname = NULL;
    214 
    215         if (!err) {
    216             cfsymname = CFStringCreateWithCString (kCFAllocatorDefault, csymname,
    217                                                    kCFStringEncodingASCII);
    218             if (cfsymname == NULL) { err = ENOMEM; }
    219         }
    220 
    221         if (!err) {
    222             if (isfunc) {
    223                 sym = CFBundleGetFunctionPointerForName (h->bundle, cfsymname);
    224             } else {
    225                 sym = CFBundleGetDataPointerForName (h->bundle, cfsymname);
    226             }
    227             if (sym == NULL) { err = ENOENT; }  /* XXX */
    228         }
    229 
    230         if (cfsymname != NULL) { CFRelease (cfsymname); }
    231     }
    232 #endif
    233 
    234     if (!err && (sym == NULL)) {
    235         err = ENOENT;  /* unimplemented */
    236     }
    237 
    238     if (!err) {
    239         *ptr = sym;
    240     }
    241 
    242     return err;
    243 }
    244 
    245 long KRB5_CALLCONV
    246 krb5int_get_plugin_data (struct plugin_file_handle *h, const char *csymname,
    247 			 void **ptr, struct errinfo *ep)
    248 {
    249     return krb5int_get_plugin_sym (h, csymname, 0, ptr, ep);
    250 }
    251 
    252 long KRB5_CALLCONV
    253 krb5int_get_plugin_func (struct plugin_file_handle *h, const char *csymname,
    254 			 void (**ptr)(), struct errinfo *ep)
    255 {
    256     void *dptr = NULL;
    257     long err = krb5int_get_plugin_sym (h, csymname, 1, &dptr, ep);
    258     if (!err) {
    259         /* Cast function pointers to avoid code duplication */
    260         *ptr = (void (*)()) dptr;
    261     }
    262     return err;
    263 }
    264 
    265 void KRB5_CALLCONV
    266 krb5int_close_plugin (struct plugin_file_handle *h)
    267 {
    268 #if USE_DLOPEN
    269     if (h->dlhandle != NULL) { dlclose(h->dlhandle); }
    270 #endif
    271 #if USE_CFBUNDLE
    272     /* Do not call CFBundleUnloadExecutable because it's not ref counted.
    273      * CFRelease will unload the bundle if the internal refcount goes to zero. */
    274     if (h->bundle != NULL) { CFRelease (h->bundle); }
    275 #endif
    276     free (h);
    277 }
    278 
    279 /* autoconf docs suggest using this preference order */
    280 #if HAVE_DIRENT_H || USE_DIRENT_H
    281 #include <dirent.h>
    282 #define NAMELEN(D) strlen((D)->d_name)
    283 #else
    284 #define dirent direct
    285 #define NAMELEN(D) ((D)->d->namlen)
    286 #if HAVE_SYS_NDIR_H
    287 # include <sys/ndir.h>
    288 #elif HAVE_SYS_DIR_H
    289 # include <sys/dir.h>
    290 #elif HAVE_NDIR_H
    291 # include <ndir.h>
    292 #endif
    293 #endif
    294 
    295 
    296 #ifdef HAVE_STRERROR_R
    297 #define ERRSTR(ERR, BUF) \
    298     (strerror_r (ERR, BUF, sizeof(BUF)) == 0 ? BUF : strerror (ERR))
    299 #else
    300 #define ERRSTR(ERR, BUF) \
    301     (strerror (ERR))
    302 #endif
    303 
    304 static long
    305 krb5int_plugin_file_handle_array_init (struct plugin_file_handle ***harray)
    306 {
    307     long err = 0;
    308 
    309     *harray = calloc (1, sizeof (**harray)); /* calloc initializes to NULL */
    310     if (*harray == NULL) { err = errno; }
    311 
    312     return err;
    313 }
    314 
    315 static long
    316 krb5int_plugin_file_handle_array_add (struct plugin_file_handle ***harray, int *count,
    317                                       struct plugin_file_handle *p)
    318 {
    319     long err = 0;
    320     struct plugin_file_handle **newharray = NULL;
    321     int newcount = *count + 1;
    322 
    323     newharray = realloc (*harray, ((newcount + 1) * sizeof (**harray))); /* +1 for NULL */
    324     if (newharray == NULL) {
    325         err = errno;
    326     } else {
    327         newharray[newcount - 1] = p;
    328         newharray[newcount] = NULL;
    329 	*count = newcount;
    330         *harray = newharray;
    331     }
    332 
    333     return err;
    334 }
    335 
    336 static void
    337 krb5int_plugin_file_handle_array_free (struct plugin_file_handle **harray)
    338 {
    339     if (harray != NULL) {
    340         int i;
    341         for (i = 0; harray[i] != NULL; i++) {
    342             krb5int_close_plugin (harray[i]);
    343         }
    344         free (harray);
    345     }
    346 }
    347 
    348 #if TARGET_OS_MAC
    349 #define FILEEXTS { "", ".bundle", ".so", NULL }
    350 #elif defined(_WIN32)
    351 #define FILEEXTS  { "", ".dll", NULL }
    352 #else
    353 #define FILEEXTS  { "", ".so", NULL }
    354 #endif
    355 
    356 
    357 static void
    358 krb5int_free_plugin_filenames (char **filenames)
    359 {
    360     if (filenames != NULL) {
    361         int i;
    362         for (i = 0; filenames[i] != NULL; i++) {
    363             free (filenames[i]);
    364         }
    365         free (filenames);
    366     }
    367 }
    368 
    369 
    370 static long
    371 krb5int_get_plugin_filenames (const char * const *filebases, char ***filenames)
    372 {
    373     long err = 0;
    374     static const char *const fileexts[] = FILEEXTS;
    375     char **tempnames = NULL;
    376     int i;
    377 
    378     if (!err) {
    379         size_t count = 0;
    380         for (i = 0; filebases[i] != NULL; i++, count++);
    381         for (i = 0; fileexts[i] != NULL; i++, count++);
    382         tempnames = calloc (count, sizeof (char *));
    383         if (tempnames == NULL) { err = errno; }
    384     }
    385 
    386     if (!err) {
    387         int j;
    388         for (i = 0; !err && (filebases[i] != NULL); i++) {
    389             size_t baselen = strlen (filebases[i]);
    390             for (j = 0; !err && (fileexts[j] != NULL); j++) {
    391                 size_t len = baselen + strlen (fileexts[j]) + 2; /* '.' + NULL */
    392                 tempnames[i+j] = malloc (len * sizeof (char));
    393                 if (tempnames[i+j] == NULL) {
    394                     err = errno;
    395                 } else {
    396 		    /*LINTED*/
    397                     sprintf (tempnames[i+j], "%s%s", filebases[i], fileexts[j]);
    398                 }
    399             }
    400         }
    401     }
    402 
    403     if (!err) {
    404         *filenames = tempnames;
    405         tempnames = NULL;
    406     }
    407 
    408     if (tempnames != NULL) { krb5int_free_plugin_filenames (tempnames); }
    409 
    410     return err;
    411 }
    412 
    413 
    414 /* Takes a NULL-terminated list of directories.  If filebases is NULL, filebases is ignored
    415  * all plugins in the directories are loaded.  If filebases is a NULL-terminated array of names,
    416  * only plugins in the directories with those name (plus any platform extension) are loaded. */
    417 
    418 long KRB5_CALLCONV
    419 krb5int_open_plugin_dirs (const char * const *dirnames,
    420                           const char * const *filebases,
    421 			  struct plugin_dir_handle *dirhandle,
    422                           struct errinfo *ep)
    423 {
    424     long err = 0;
    425     struct plugin_file_handle **h = NULL;
    426     int count = 0;
    427     char **filenames = NULL;
    428     int i;
    429 
    430     if (!err) {
    431         err = krb5int_plugin_file_handle_array_init (&h);
    432     }
    433 
    434     if (!err && (filebases != NULL)) {
    435 	err = krb5int_get_plugin_filenames (filebases, &filenames);
    436     }
    437 
    438     for (i = 0; !err && dirnames[i] != NULL; i++) {
    439 	size_t dirnamelen = strlen (dirnames[i]) + 1; /* '/' */
    440         if (filenames != NULL) {
    441             /* load plugins with names from filenames from each directory */
    442             int j;
    443 
    444             for (j = 0; !err && filenames[j] != NULL; j++) {
    445                 struct plugin_file_handle *handle = NULL;
    446 		char *filepath = NULL;
    447 
    448 		if (!err) {
    449 		    filepath = malloc (dirnamelen + strlen (filenames[j]) + 1); /* NULL */
    450 		    if (filepath == NULL) {
    451 			err = errno;
    452 		    } else {
    453 			/*LINTED*/
    454 			sprintf (filepath, "%s/%s", dirnames[i], filenames[j]);
    455 		    }
    456 		}
    457 
    458                 if (krb5int_open_plugin (filepath, &handle, ep) == 0) {
    459                     err = krb5int_plugin_file_handle_array_add (&h, &count, handle);
    460                     if (!err) { handle = NULL; }  /* h takes ownership */
    461                 }
    462 
    463 		if (filepath != NULL) { free (filepath); }
    464 		if (handle   != NULL) { krb5int_close_plugin (handle); }
    465             }
    466         } else {
    467             /* load all plugins in each directory */
    468 #ifndef _WIN32
    469 	    DIR *dir = opendir (dirnames[i]);
    470 
    471             while (dir != NULL && !err) {
    472                 struct dirent *d = NULL;
    473                 char *filepath = NULL;
    474                 struct plugin_file_handle *handle = NULL;
    475                 int len;
    476 
    477                 d = readdir (dir);
    478                 if (d == NULL) { break; }
    479 
    480                 if ((strcmp (d->d_name, ".") == 0) ||
    481                     (strcmp (d->d_name, "..") == 0)) {
    482                     continue;
    483                 }
    484 
    485 		/* Solaris Kerberos: Only open files with a .so extension */
    486 		len = NAMELEN (d);
    487 		if (len < 3 || strcmp(".so", d->d_name + len - 3 ) != 0)
    488 			continue;
    489 
    490 		if (!err) {
    491 		    filepath = malloc (dirnamelen + len + 1); /* NULL */
    492 		    if (filepath == NULL) {
    493 			err = errno;
    494 		    } else {
    495 			/*LINTED*/
    496 			sprintf (filepath, "%s/%*s", dirnames[i], len, d->d_name);
    497 		    }
    498 		}
    499 
    500                 if (!err) {
    501                     if (krb5int_open_plugin (filepath, &handle, ep) == 0) {
    502                         err = krb5int_plugin_file_handle_array_add (&h, &count, handle);
    503                         if (!err) { handle = NULL; }  /* h takes ownership */
    504                     }
    505                 }
    506 
    507                 if (filepath  != NULL) { free (filepath); }
    508                 if (handle    != NULL) { krb5int_close_plugin (handle); }
    509             }
    510 
    511             if (dir != NULL) { closedir (dir); }
    512 #else
    513 	    /* Until a Windows implementation of this code is implemented */
    514 	    err = ENOENT;
    515 #endif /* _WIN32 */
    516         }
    517     }
    518 
    519     if (err == ENOENT) {
    520         err = 0;  /* ran out of plugins -- do nothing */
    521     }
    522 
    523     if (!err) {
    524         dirhandle->files = h;
    525         h = NULL;  /* dirhandle->files takes ownership */
    526     }
    527 
    528     if (filenames != NULL) { krb5int_free_plugin_filenames (filenames); }
    529     if (h         != NULL) { krb5int_plugin_file_handle_array_free (h); }
    530 
    531     return err;
    532 }
    533 
    534 void KRB5_CALLCONV
    535 krb5int_close_plugin_dirs (struct plugin_dir_handle *dirhandle)
    536 {
    537     if (dirhandle->files != NULL) {
    538         int i;
    539         for (i = 0; dirhandle->files[i] != NULL; i++) {
    540             krb5int_close_plugin (dirhandle->files[i]);
    541         }
    542         free (dirhandle->files);
    543         dirhandle->files = NULL;
    544     }
    545 }
    546 
    547 void KRB5_CALLCONV
    548 krb5int_free_plugin_dir_data (void **ptrs)
    549 {
    550     /* Nothing special to be done per pointer.  */
    551     free(ptrs);
    552 }
    553 
    554 long KRB5_CALLCONV
    555 krb5int_get_plugin_dir_data (struct plugin_dir_handle *dirhandle,
    556 			     const char *symname,
    557 			     void ***ptrs,
    558 			     struct errinfo *ep)
    559 {
    560     long err = 0;
    561     void **p = NULL;
    562     int count = 0;
    563 
    564     /* XXX Do we need to add a leading "_" to the symbol name on any
    565        modern platforms?  */
    566 
    567     Tprintf("get_plugin_data_sym(%s)\n", symname);
    568 
    569     if (!err) {
    570         p = calloc (1, sizeof (*p)); /* calloc initializes to NULL */
    571         if (p == NULL) { err = errno; }
    572     }
    573 
    574     if (!err && (dirhandle != NULL) && (dirhandle->files != NULL)) {
    575         int i = 0;
    576 
    577         for (i = 0; !err && (dirhandle->files[i] != NULL); i++) {
    578             void *sym = NULL;
    579 
    580             if (krb5int_get_plugin_data (dirhandle->files[i], symname, &sym, ep) == 0) {
    581                 void **newp = NULL;
    582 
    583                 count++;
    584                 newp = realloc (p, ((count + 1) * sizeof (*p))); /* +1 for NULL */
    585                 if (newp == NULL) {
    586                     err = errno;
    587                 } else {
    588                     p = newp;
    589                     p[count - 1] = sym;
    590                     p[count] = NULL;
    591                 }
    592             }
    593         }
    594     }
    595 
    596     if (!err) {
    597         *ptrs = p;
    598         p = NULL; /* ptrs takes ownership */
    599     }
    600 
    601     if (p != NULL) { free (p); }
    602 
    603     return err;
    604 }
    605 
    606 void KRB5_CALLCONV
    607 krb5int_free_plugin_dir_func (void (**ptrs)(void))
    608 {
    609     /* Nothing special to be done per pointer.  */
    610     free(ptrs);
    611 }
    612 
    613 long KRB5_CALLCONV
    614 krb5int_get_plugin_dir_func (struct plugin_dir_handle *dirhandle,
    615 			     const char *symname,
    616 			     void (***ptrs)(void),
    617 			     struct errinfo *ep)
    618 {
    619     long err = 0;
    620     void (**p)() = NULL;
    621     int count = 0;
    622 
    623     /* XXX Do we need to add a leading "_" to the symbol name on any
    624         modern platforms?  */
    625 
    626     Tprintf("get_plugin_data_sym(%s)\n", symname);
    627 
    628     if (!err) {
    629         p = calloc (1, sizeof (*p)); /* calloc initializes to NULL */
    630         if (p == NULL) { err = errno; }
    631     }
    632 
    633     if (!err && (dirhandle != NULL) && (dirhandle->files != NULL)) {
    634         int i = 0;
    635 
    636         for (i = 0; !err && (dirhandle->files[i] != NULL); i++) {
    637             void (*sym)() = NULL;
    638 
    639             if (krb5int_get_plugin_func (dirhandle->files[i], symname, &sym, ep) == 0) {
    640                 void (**newp)() = NULL;
    641 
    642                 count++;
    643                 newp = realloc (p, ((count + 1) * sizeof (*p))); /* +1 for NULL */
    644                 if (newp == NULL) {
    645                     err = errno;
    646                 } else {
    647                     p = newp;
    648                     p[count - 1] = sym;
    649                     p[count] = NULL;
    650                 }
    651             }
    652         }
    653     }
    654 
    655     if (!err) {
    656         *ptrs = p;
    657         p = NULL; /* ptrs takes ownership */
    658     }
    659 
    660     if (p != NULL) { free (p); }
    661 
    662     return err;
    663 }
    664