xref: /freebsd/crypto/krb5/src/util/support/plugins.c (revision 7f2fe78b9dd5f51c821d771b63d2e096f6fd49e9)
1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /* util/support/plugins.c - Plugin module support functions */
3 /*
4  * Copyright 2006, 2008 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 #include "k5-platform.h"
28 #include "k5-plugin.h"
29 #if USE_DLOPEN
30 #include <dlfcn.h>
31 #endif
32 
33 #if USE_DLOPEN
34 #ifdef RTLD_GROUP
35 #define GROUP RTLD_GROUP
36 #else
37 #define GROUP 0
38 #endif
39 #ifdef RTLD_NODELETE
40 #define NODELETE RTLD_NODELETE
41 #else
42 #define NODELETE 0
43 #endif
44 #define PLUGIN_DLOPEN_FLAGS (RTLD_NOW | RTLD_LOCAL | GROUP | NODELETE)
45 #endif
46 
47 /*
48  * glibc bug 11941, fixed in release 2.25, can cause an assertion failure in
49  * dlclose() on process exit.  Our workaround is to leak dlopen() handles
50  * (which doesn't typically manifest in leak detection tools because the
51  * handles are still reachable via a global table in libdl).  Because we
52  * dlopen() with RTLD_NODELETE, we weren't going to unload the plugin objects
53  * anyway.
54  */
55 #ifdef __GLIBC_PREREQ
56 #if ! __GLIBC_PREREQ(2, 25)
57 #define dlclose(x)
58 #endif
59 #endif
60 
61 #include <stdarg.h>
Tprintf(const char * fmt,...)62 static void Tprintf (const char *fmt, ...)
63 {
64 #ifdef DEBUG
65     va_list va;
66     va_start (va, fmt);
67     vfprintf (stderr, fmt, va);
68     va_end (va);
69 #endif
70 }
71 
72 struct plugin_file_handle {
73 #if defined(USE_DLOPEN)
74     void *dlhandle;
75 #elif defined(_WIN32)
76     HMODULE module;
77 #else
78     char dummy;
79 #endif
80 };
81 
82 #if defined(USE_DLOPEN)
83 
84 static long
open_plugin_dlfcn(struct plugin_file_handle * h,const char * filename,struct errinfo * ep)85 open_plugin_dlfcn(struct plugin_file_handle *h, const char *filename,
86                   struct errinfo *ep)
87 {
88     const char *e;
89 
90     h->dlhandle = dlopen(filename, PLUGIN_DLOPEN_FLAGS);
91     if (h->dlhandle == NULL) {
92         e = dlerror();
93         if (e == NULL)
94             e = _("unknown failure");
95         Tprintf("dlopen(%s): %s\n", filename, e);
96         k5_set_error(ep, ENOENT, _("unable to load plugin [%s]: %s"),
97                      filename, e);
98         return ENOENT;
99     }
100     return 0;
101 }
102 #define open_plugin open_plugin_dlfcn
103 
104 static long
get_sym_dlfcn(struct plugin_file_handle * h,const char * csymname,void ** sym_out,struct errinfo * ep)105 get_sym_dlfcn(struct plugin_file_handle *h, const char *csymname,
106               void **sym_out, struct errinfo *ep)
107 {
108     const char *e;
109 
110     if (h->dlhandle == NULL)
111         return ENOENT;
112     *sym_out = dlsym(h->dlhandle, csymname);
113     if (*sym_out == NULL) {
114         e = dlerror();
115         if (e == NULL)
116             e = _("unknown failure");
117         Tprintf("dlsym(%s): %s\n", csymname, e);
118         k5_set_error(ep, ENOENT, "%s", e);
119         return ENOENT;
120     }
121     return 0;
122 }
123 #define get_sym get_sym_dlfcn
124 
125 static void
close_plugin_dlfcn(struct plugin_file_handle * h)126 close_plugin_dlfcn(struct plugin_file_handle *h)
127 {
128     if (h->dlhandle != NULL)
129         dlclose(h->dlhandle);
130 }
131 #define close_plugin close_plugin_dlfcn
132 
133 #elif defined(_WIN32)
134 
135 static long
open_plugin_win32(struct plugin_file_handle * h,const char * filename,struct errinfo * ep)136 open_plugin_win32(struct plugin_file_handle *h, const char *filename,
137                   struct errinfo *ep)
138 {
139     h->module = LoadLibrary(filename);
140     if (h == NULL) {
141         Tprintf("Unable to load dll: %s\n", filename);
142         k5_set_error(ep, ENOENT, _("unable to load DLL [%s]"), filename);
143         return ENOENT;
144     }
145     return 0;
146 }
147 #define open_plugin open_plugin_win32
148 
149 static long
get_sym_win32(struct plugin_file_handle * h,const char * csymname,void ** sym_out,struct errinfo * ep)150 get_sym_win32(struct plugin_file_handle *h, const char *csymname,
151               void **sym_out, struct errinfo *ep)
152 {
153     LPVOID lpMsgBuf;
154     DWORD dw;
155 
156     if (h->module == NULL)
157         return ENOENT;
158     *sym_out = GetProcAddress(h->module, csymname);
159     if (*sym_out == NULL) {
160         Tprintf("GetProcAddress(%s): %i\n", csymname, GetLastError());
161         dw = GetLastError();
162         if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
163                           FORMAT_MESSAGE_FROM_SYSTEM,
164                           NULL, dw, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
165                           (LPTSTR)&lpMsgBuf, 0, NULL)) {
166             k5_set_error(ep, ENOENT, _("unable to get DLL Symbol: %s"),
167                          (char *)lpMsgBuf);
168             LocalFree(lpMsgBuf);
169         }
170         return ENOENT;
171     }
172     return 0;
173 }
174 #define get_sym get_sym_win32
175 
176 static void
close_plugin_win32(struct plugin_file_handle * h)177 close_plugin_win32(struct plugin_file_handle *h)
178 {
179     if (h->module != NULL)
180         FreeLibrary(h->module);
181 }
182 #define close_plugin close_plugin_win32
183 
184 #else
185 
186 static long
open_plugin_dummy(struct plugin_file_handle * h,const char * filename,struct errinfo * ep)187 open_plugin_dummy(struct plugin_file_handle *h, const char *filename,
188                   struct errinfo *ep)
189 {
190     k5_set_error(ep, ENOENT, _("plugin loading unavailable"));
191     return ENOENT;
192 }
193 #define open_plugin open_plugin_dummy
194 
195 static long
get_sym_dummy(struct plugin_file_handle * h,const char * csymname,void ** sym_out,struct errinfo * ep)196 get_sym_dummy(struct plugin_file_handle *h, const char *csymname,
197               void **sym_out, struct errinfo *ep)
198 {
199     return ENOENT;
200 }
201 #define get_sym get_sym_dummy
202 
203 static void
close_plugin_dummy(struct plugin_file_handle * h)204 close_plugin_dummy(struct plugin_file_handle *h)
205 {
206 }
207 #define close_plugin close_plugin_dummy
208 
209 #endif
210 
211 long KRB5_CALLCONV
krb5int_open_plugin(const char * filename,struct plugin_file_handle ** handle_out,struct errinfo * ep)212 krb5int_open_plugin(const char *filename,
213                     struct plugin_file_handle **handle_out, struct errinfo *ep)
214 {
215     long ret;
216     struct plugin_file_handle *h;
217 
218     *handle_out = NULL;
219 
220     h = calloc(1, sizeof(*h));
221     if (h == NULL)
222         return ENOMEM;
223 
224     ret = open_plugin(h, filename, ep);
225     if (ret) {
226         free(h);
227         return ret;
228     }
229 
230     *handle_out = h;
231     return 0;
232 }
233 
234 long KRB5_CALLCONV
krb5int_get_plugin_data(struct plugin_file_handle * h,const char * csymname,void ** sym_out,struct errinfo * ep)235 krb5int_get_plugin_data(struct plugin_file_handle *h, const char *csymname,
236                         void **sym_out, struct errinfo *ep)
237 {
238     return get_sym(h, csymname, sym_out, ep);
239 }
240 
241 long KRB5_CALLCONV
krb5int_get_plugin_func(struct plugin_file_handle * h,const char * csymname,void (** sym_out)(),struct errinfo * ep)242 krb5int_get_plugin_func(struct plugin_file_handle *h, const char *csymname,
243                         void (**sym_out)(), struct errinfo *ep)
244 {
245     void *dptr = NULL;
246     long ret = get_sym(h, csymname, &dptr, ep);
247 
248     if (!ret)
249         *sym_out = (void (*)())dptr;
250     return ret;
251 }
252 
253 void KRB5_CALLCONV
krb5int_close_plugin(struct plugin_file_handle * h)254 krb5int_close_plugin (struct plugin_file_handle *h)
255 {
256     close_plugin(h);
257     free(h);
258 }
259 
260 static long
krb5int_plugin_file_handle_array_init(struct plugin_file_handle *** harray)261 krb5int_plugin_file_handle_array_init (struct plugin_file_handle ***harray)
262 {
263     long err = 0;
264 
265     *harray = calloc (1, sizeof (**harray)); /* calloc initializes to NULL */
266     if (*harray == NULL) { err = ENOMEM; }
267 
268     return err;
269 }
270 
271 static long
krb5int_plugin_file_handle_array_add(struct plugin_file_handle *** harray,size_t * count,struct plugin_file_handle * p)272 krb5int_plugin_file_handle_array_add (struct plugin_file_handle ***harray, size_t *count,
273                                       struct plugin_file_handle *p)
274 {
275     long err = 0;
276     struct plugin_file_handle **newharray = NULL;
277     size_t newcount = *count + 1;
278 
279     newharray = realloc (*harray, ((newcount + 1) * sizeof (**harray))); /* +1 for NULL */
280     if (newharray == NULL) {
281         err = ENOMEM;
282     } else {
283         newharray[newcount - 1] = p;
284         newharray[newcount] = NULL;
285         *count = newcount;
286         *harray = newharray;
287     }
288 
289     return err;
290 }
291 
292 static void
krb5int_plugin_file_handle_array_free(struct plugin_file_handle ** harray)293 krb5int_plugin_file_handle_array_free (struct plugin_file_handle **harray)
294 {
295     if (harray != NULL) {
296         int i;
297         for (i = 0; harray[i] != NULL; i++) {
298             krb5int_close_plugin (harray[i]);
299         }
300         free (harray);
301     }
302 }
303 
304 #if TARGET_OS_MAC
305 #define FILEEXTS { "", ".bundle", ".dylib", ".so", NULL }
306 #elif defined(_WIN32)
307 #define FILEEXTS  { "", ".dll", NULL }
308 #else
309 #define FILEEXTS  { "", ".so", NULL }
310 #endif
311 
312 
313 static void
krb5int_free_plugin_filenames(char ** filenames)314 krb5int_free_plugin_filenames (char **filenames)
315 {
316     if (filenames != NULL) {
317         int i;
318         for (i = 0; filenames[i] != NULL; i++) {
319             free (filenames[i]);
320         }
321         free (filenames);
322     }
323 }
324 
325 
326 static long
krb5int_get_plugin_filenames(const char * const * filebases,char *** filenames)327 krb5int_get_plugin_filenames (const char * const *filebases, char ***filenames)
328 {
329     long err = 0;
330     static const char *const fileexts[] = FILEEXTS;
331     char **tempnames = NULL;
332     size_t bases_count = 0;
333     size_t exts_count = 0;
334     size_t i;
335 
336     if (!filebases) { err = EINVAL; }
337     if (!filenames) { err = EINVAL; }
338 
339     if (!err) {
340         for (i = 0; filebases[i]; i++) { bases_count++; }
341         for (i = 0; fileexts[i]; i++) { exts_count++; }
342         tempnames = calloc ((bases_count * exts_count)+1, sizeof (char *));
343         if (!tempnames) { err = ENOMEM; }
344     }
345 
346     if (!err) {
347         size_t j;
348         for (i = 0; !err && filebases[i]; i++) {
349             for (j = 0; !err && fileexts[j]; j++) {
350                 if (asprintf(&tempnames[(i*exts_count)+j], "%s%s",
351                              filebases[i], fileexts[j]) < 0) {
352                     tempnames[(i*exts_count)+j] = NULL;
353                     err = ENOMEM;
354                 }
355             }
356         }
357         tempnames[bases_count * exts_count] = NULL; /* NUL-terminate */
358     }
359 
360     if (!err) {
361         *filenames = tempnames;
362         tempnames = NULL;
363     }
364 
365     krb5int_free_plugin_filenames(tempnames);
366 
367     return err;
368 }
369 
370 
371 /* Takes a NULL-terminated list of directories.  If filebases is NULL, filebases is ignored
372  * all plugins in the directories are loaded.  If filebases is a NULL-terminated array of names,
373  * only plugins in the directories with those name (plus any platform extension) are loaded. */
374 
375 long KRB5_CALLCONV
krb5int_open_plugin_dirs(const char * const * dirnames,const char * const * filebases,struct plugin_dir_handle * dirhandle,struct errinfo * ep)376 krb5int_open_plugin_dirs (const char * const *dirnames,
377                           const char * const *filebases,
378                           struct plugin_dir_handle *dirhandle,
379                           struct errinfo *ep)
380 {
381     long err = 0;
382     struct plugin_file_handle **h = NULL;
383     size_t count = 0;
384     char **filenames = NULL;
385     int i;
386 
387     if (!err) {
388         err = krb5int_plugin_file_handle_array_init (&h);
389     }
390 
391     if (!err && (filebases != NULL)) {
392         err = krb5int_get_plugin_filenames (filebases, &filenames);
393     }
394 
395     for (i = 0; !err && dirnames[i] != NULL; i++) {
396         if (filenames != NULL) {
397             /* load plugins with names from filenames from each directory */
398             int j;
399 
400             for (j = 0; !err && filenames[j] != NULL; j++) {
401                 struct plugin_file_handle *handle = NULL;
402                 char *filepath = NULL;
403 
404                 if (!err) {
405                     if (asprintf(&filepath, "%s/%s", dirnames[i], filenames[j]) < 0) {
406                         filepath = NULL;
407                         err = ENOMEM;
408                     }
409                 }
410 
411                 if (!err && krb5int_open_plugin(filepath, &handle, ep) == 0) {
412                     err = krb5int_plugin_file_handle_array_add (&h, &count, handle);
413                     if (!err)
414                         handle = NULL; /* h takes ownership */
415                 }
416 
417                 free(filepath);
418                 if (handle   != NULL) { krb5int_close_plugin (handle); }
419             }
420         } else {
421             char **fnames = NULL;
422             int j;
423 
424             err = k5_dir_filenames(dirnames[i], &fnames);
425             for (j = 0; !err && fnames[j] != NULL; j++) {
426                 char *filepath = NULL;
427                 struct plugin_file_handle *handle = NULL;
428 
429                 if (strcmp(fnames[j], ".") == 0 ||
430                     strcmp(fnames[j], "..") == 0)
431                     continue;
432 
433                 if (asprintf(&filepath, "%s/%s", dirnames[i], fnames[j]) < 0) {
434                     filepath = NULL;
435                     err = ENOMEM;
436                 }
437 
438                 if (!err && krb5int_open_plugin(filepath, &handle, ep) == 0) {
439                     err = krb5int_plugin_file_handle_array_add(&h, &count,
440                                                                handle);
441                     if (!err)
442                         handle = NULL;  /* h takes ownership */
443                 }
444 
445                 free(filepath);
446                 if (handle != NULL)
447                     krb5int_close_plugin(handle);
448             }
449 
450             k5_free_filenames(fnames);
451         }
452     }
453 
454     if (err == ENOENT) {
455         err = 0;  /* ran out of plugins -- do nothing */
456     }
457 
458     if (!err) {
459         dirhandle->files = h;
460         h = NULL;  /* dirhandle->files takes ownership */
461     }
462 
463     if (filenames != NULL) { krb5int_free_plugin_filenames (filenames); }
464     if (h         != NULL) { krb5int_plugin_file_handle_array_free (h); }
465 
466     return err;
467 }
468 
469 void KRB5_CALLCONV
krb5int_close_plugin_dirs(struct plugin_dir_handle * dirhandle)470 krb5int_close_plugin_dirs (struct plugin_dir_handle *dirhandle)
471 {
472     if (dirhandle->files != NULL) {
473         int i;
474         for (i = 0; dirhandle->files[i] != NULL; i++) {
475             krb5int_close_plugin (dirhandle->files[i]);
476         }
477         free (dirhandle->files);
478         dirhandle->files = NULL;
479     }
480 }
481 
482 void KRB5_CALLCONV
krb5int_free_plugin_dir_data(void ** ptrs)483 krb5int_free_plugin_dir_data (void **ptrs)
484 {
485     /* Nothing special to be done per pointer.  */
486     free(ptrs);
487 }
488 
489 long KRB5_CALLCONV
krb5int_get_plugin_dir_data(struct plugin_dir_handle * dirhandle,const char * symname,void *** ptrs,struct errinfo * ep)490 krb5int_get_plugin_dir_data (struct plugin_dir_handle *dirhandle,
491                              const char *symname,
492                              void ***ptrs,
493                              struct errinfo *ep)
494 {
495     long err = 0;
496     void **p = NULL;
497     size_t count = 0;
498 
499     /* XXX Do we need to add a leading "_" to the symbol name on any
500        modern platforms?  */
501 
502     Tprintf("get_plugin_data_sym(%s)\n", symname);
503 
504     if (!err) {
505         p = calloc (1, sizeof (*p)); /* calloc initializes to NULL */
506         if (p == NULL) { err = ENOMEM; }
507     }
508 
509     if (!err && (dirhandle != NULL) && (dirhandle->files != NULL)) {
510         int i = 0;
511 
512         for (i = 0; !err && (dirhandle->files[i] != NULL); i++) {
513             void *sym = NULL;
514 
515             if (krb5int_get_plugin_data (dirhandle->files[i], symname, &sym, ep) == 0) {
516                 void **newp = NULL;
517 
518                 count++;
519                 newp = realloc (p, ((count + 1) * sizeof (*p))); /* +1 for NULL */
520                 if (newp == NULL) {
521                     err = ENOMEM;
522                 } else {
523                     p = newp;
524                     p[count - 1] = sym;
525                     p[count] = NULL;
526                 }
527             }
528         }
529     }
530 
531     if (!err) {
532         *ptrs = p;
533         p = NULL; /* ptrs takes ownership */
534     }
535 
536     free(p);
537 
538     return err;
539 }
540 
541 void KRB5_CALLCONV
krb5int_free_plugin_dir_func(void (** ptrs)(void))542 krb5int_free_plugin_dir_func (void (**ptrs)(void))
543 {
544     /* Nothing special to be done per pointer.  */
545     free(ptrs);
546 }
547 
548 long KRB5_CALLCONV
krb5int_get_plugin_dir_func(struct plugin_dir_handle * dirhandle,const char * symname,void (*** ptrs)(void),struct errinfo * ep)549 krb5int_get_plugin_dir_func (struct plugin_dir_handle *dirhandle,
550                              const char *symname,
551                              void (***ptrs)(void),
552                              struct errinfo *ep)
553 {
554     long err = 0;
555     void (**p)() = NULL;
556     size_t count = 0;
557 
558     /* XXX Do we need to add a leading "_" to the symbol name on any
559        modern platforms?  */
560 
561     Tprintf("get_plugin_data_sym(%s)\n", symname);
562 
563     if (!err) {
564         p = calloc (1, sizeof (*p)); /* calloc initializes to NULL */
565         if (p == NULL) { err = ENOMEM; }
566     }
567 
568     if (!err && (dirhandle != NULL) && (dirhandle->files != NULL)) {
569         int i = 0;
570 
571         for (i = 0; !err && (dirhandle->files[i] != NULL); i++) {
572             void (*sym)() = NULL;
573 
574             if (krb5int_get_plugin_func (dirhandle->files[i], symname, &sym, ep) == 0) {
575                 void (**newp)() = NULL;
576 
577                 count++;
578                 newp = realloc (p, ((count + 1) * sizeof (*p))); /* +1 for NULL */
579                 if (newp == NULL) {
580                     err = ENOMEM;
581                 } else {
582                     p = newp;
583                     p[count - 1] = sym;
584                     p[count] = NULL;
585                 }
586             }
587         }
588     }
589 
590     if (!err) {
591         *ptrs = p;
592         p = NULL; /* ptrs takes ownership */
593     }
594 
595     free(p);
596 
597     return err;
598 }
599