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