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*/
Tprintf(const char * fmt,...)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
krb5int_open_plugin(const char * filepath,struct plugin_file_handle ** h,struct errinfo * ep)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
krb5int_get_plugin_sym(struct plugin_file_handle * h,const char * csymname,int isfunc,void ** ptr,struct errinfo * ep)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
krb5int_get_plugin_data(struct plugin_file_handle * h,const char * csymname,void ** ptr,struct errinfo * ep)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
krb5int_get_plugin_func(struct plugin_file_handle * h,const char * csymname,void (** ptr)(),struct errinfo * ep)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
krb5int_close_plugin(struct plugin_file_handle * h)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
krb5int_plugin_file_handle_array_init(struct plugin_file_handle *** harray)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
krb5int_plugin_file_handle_array_add(struct plugin_file_handle *** harray,int * count,struct plugin_file_handle * p)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
krb5int_plugin_file_handle_array_free(struct plugin_file_handle ** harray)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
krb5int_free_plugin_filenames(char ** filenames)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
krb5int_get_plugin_filenames(const char * const * filebases,char *** filenames)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
krb5int_open_plugin_dirs(const char * const * dirnames,const char * const * filebases,struct plugin_dir_handle * dirhandle,struct errinfo * ep)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
krb5int_close_plugin_dirs(struct plugin_dir_handle * dirhandle)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
krb5int_free_plugin_dir_data(void ** ptrs)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
krb5int_get_plugin_dir_data(struct plugin_dir_handle * dirhandle,const char * symname,void *** ptrs,struct errinfo * ep)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
krb5int_free_plugin_dir_func(void (** ptrs)(void))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
krb5int_get_plugin_dir_func(struct plugin_dir_handle * dirhandle,const char * symname,void (*** ptrs)(void),struct errinfo * ep)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