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