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*/
60static 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
70struct 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*/
83long KRB5_CALLCONV
84krb5int_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*/
189static long
190krb5int_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
245long KRB5_CALLCONV
246krb5int_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
252long KRB5_CALLCONV
253krb5int_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
265void KRB5_CALLCONV
266krb5int_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
304static long
305krb5int_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
315static long
316krb5int_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
336static void
337krb5int_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
357static void
358krb5int_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
370static long
371krb5int_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
418long KRB5_CALLCONV
419krb5int_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
534void KRB5_CALLCONV
535krb5int_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
547void KRB5_CALLCONV
548krb5int_free_plugin_dir_data (void **ptrs)
549{
550    /* Nothing special to be done per pointer.  */
551    free(ptrs);
552}
553
554long KRB5_CALLCONV
555krb5int_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
606void KRB5_CALLCONV
607krb5int_free_plugin_dir_func (void (**ptrs)(void))
608{
609    /* Nothing special to be done per pointer.  */
610    free(ptrs);
611}
612
613long KRB5_CALLCONV
614krb5int_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