xref: /freebsd/crypto/krb5/src/util/support/plugins.c (revision f1c4c3daccbaf3820f0e2224de53df12fc952fcc)
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)(void),struct errinfo * ep)242 krb5int_get_plugin_func(struct plugin_file_handle *h, const char *csymname,
243                         void (**sym_out)(void), 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 (*)(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     size_t i;
296 
297     if (harray != NULL) {
298         for (i = 0; harray[i] != NULL; i++) {
299             krb5int_close_plugin (harray[i]);
300         }
301         free (harray);
302     }
303 }
304 
305 #if TARGET_OS_MAC
306 #define FILEEXTS { "", ".bundle", ".dylib", ".so", NULL }
307 #elif defined(_WIN32)
308 #define FILEEXTS  { "", ".dll", NULL }
309 #else
310 #define FILEEXTS  { "", ".so", NULL }
311 #endif
312 
313 
314 static void
krb5int_free_plugin_filenames(char ** filenames)315 krb5int_free_plugin_filenames (char **filenames)
316 {
317     size_t i;
318 
319     if (filenames != NULL) {
320         for (i = 0; filenames[i] != NULL; i++) {
321             free (filenames[i]);
322         }
323         free (filenames);
324     }
325 }
326 
327 
328 static long
krb5int_get_plugin_filenames(const char * const * filebases,char *** filenames)329 krb5int_get_plugin_filenames (const char * const *filebases, char ***filenames)
330 {
331     long err = 0;
332     static const char *const fileexts[] = FILEEXTS;
333     char **tempnames = NULL;
334     size_t bases_count = 0;
335     size_t exts_count = 0;
336     size_t i;
337 
338     if (!filebases) { err = EINVAL; }
339     if (!filenames) { err = EINVAL; }
340 
341     if (!err) {
342         for (i = 0; filebases[i]; i++) { bases_count++; }
343         for (i = 0; fileexts[i]; i++) { exts_count++; }
344         tempnames = calloc ((bases_count * exts_count)+1, sizeof (char *));
345         if (!tempnames) { err = ENOMEM; }
346     }
347 
348     if (!err) {
349         size_t j;
350         for (i = 0; !err && filebases[i]; i++) {
351             for (j = 0; !err && fileexts[j]; j++) {
352                 if (asprintf(&tempnames[(i*exts_count)+j], "%s%s",
353                              filebases[i], fileexts[j]) < 0) {
354                     tempnames[(i*exts_count)+j] = NULL;
355                     err = ENOMEM;
356                 }
357             }
358         }
359         tempnames[bases_count * exts_count] = NULL; /* NUL-terminate */
360     }
361 
362     if (!err) {
363         *filenames = tempnames;
364         tempnames = NULL;
365     }
366 
367     krb5int_free_plugin_filenames(tempnames);
368 
369     return err;
370 }
371 
372 
373 /* Takes a NULL-terminated list of directories.  If filebases is NULL, filebases is ignored
374  * all plugins in the directories are loaded.  If filebases is a NULL-terminated array of names,
375  * only plugins in the directories with those name (plus any platform extension) are loaded. */
376 
377 long KRB5_CALLCONV
krb5int_open_plugin_dirs(const char * const * dirnames,const char * const * filebases,struct plugin_dir_handle * dirhandle,struct errinfo * ep)378 krb5int_open_plugin_dirs (const char * const *dirnames,
379                           const char * const *filebases,
380                           struct plugin_dir_handle *dirhandle,
381                           struct errinfo *ep)
382 {
383     long err = 0;
384     struct plugin_file_handle **h = NULL;
385     size_t count = 0;
386     char **filenames = NULL;
387     size_t i;
388 
389     if (!err) {
390         err = krb5int_plugin_file_handle_array_init (&h);
391     }
392 
393     if (!err && (filebases != NULL)) {
394         err = krb5int_get_plugin_filenames (filebases, &filenames);
395     }
396 
397     for (i = 0; !err && dirnames[i] != NULL; i++) {
398         if (filenames != NULL) {
399             /* load plugins with names from filenames from each directory */
400             size_t j;
401 
402             for (j = 0; !err && filenames[j] != NULL; j++) {
403                 struct plugin_file_handle *handle = NULL;
404                 char *filepath = NULL;
405 
406                 if (!err)
407                     err = k5_path_join(dirnames[i], filenames[j], &filepath);
408 
409                 if (!err && krb5int_open_plugin(filepath, &handle, ep) == 0) {
410                     err = krb5int_plugin_file_handle_array_add (&h, &count, handle);
411                     if (!err)
412                         handle = NULL; /* h takes ownership */
413                 }
414 
415                 free(filepath);
416                 if (handle   != NULL) { krb5int_close_plugin (handle); }
417             }
418         } else {
419             char **fnames = NULL;
420             size_t j;
421 
422             err = k5_dir_filenames(dirnames[i], &fnames);
423             for (j = 0; !err && fnames[j] != NULL; j++) {
424                 char *filepath = NULL;
425                 struct plugin_file_handle *handle = NULL;
426 
427                 if (strcmp(fnames[j], ".") == 0 ||
428                     strcmp(fnames[j], "..") == 0)
429                     continue;
430 
431                 err = k5_path_join(dirnames[i], fnames[j], &filepath);
432 
433                 if (!err && krb5int_open_plugin(filepath, &handle, ep) == 0) {
434                     err = krb5int_plugin_file_handle_array_add(&h, &count,
435                                                                handle);
436                     if (!err)
437                         handle = NULL;  /* h takes ownership */
438                 }
439 
440                 free(filepath);
441                 if (handle != NULL)
442                     krb5int_close_plugin(handle);
443             }
444 
445             k5_free_filenames(fnames);
446         }
447     }
448 
449     if (err == ENOENT) {
450         err = 0;  /* ran out of plugins -- do nothing */
451     }
452 
453     if (!err) {
454         dirhandle->files = h;
455         h = NULL;  /* dirhandle->files takes ownership */
456     }
457 
458     if (filenames != NULL) { krb5int_free_plugin_filenames (filenames); }
459     if (h         != NULL) { krb5int_plugin_file_handle_array_free (h); }
460 
461     return err;
462 }
463 
464 void KRB5_CALLCONV
krb5int_close_plugin_dirs(struct plugin_dir_handle * dirhandle)465 krb5int_close_plugin_dirs (struct plugin_dir_handle *dirhandle)
466 {
467     size_t i;
468 
469     if (dirhandle->files != NULL) {
470         for (i = 0; dirhandle->files[i] != NULL; i++) {
471             krb5int_close_plugin (dirhandle->files[i]);
472         }
473         free (dirhandle->files);
474         dirhandle->files = NULL;
475     }
476 }
477 
478 void KRB5_CALLCONV
krb5int_free_plugin_dir_data(void ** ptrs)479 krb5int_free_plugin_dir_data (void **ptrs)
480 {
481     /* Nothing special to be done per pointer.  */
482     free(ptrs);
483 }
484 
485 long KRB5_CALLCONV
krb5int_get_plugin_dir_data(struct plugin_dir_handle * dirhandle,const char * symname,void *** ptrs,struct errinfo * ep)486 krb5int_get_plugin_dir_data (struct plugin_dir_handle *dirhandle,
487                              const char *symname,
488                              void ***ptrs,
489                              struct errinfo *ep)
490 {
491     long err = 0;
492     void **p = NULL;
493     size_t count = 0;
494 
495     /* XXX Do we need to add a leading "_" to the symbol name on any
496        modern platforms?  */
497 
498     Tprintf("get_plugin_data_sym(%s)\n", symname);
499 
500     if (!err) {
501         p = calloc (1, sizeof (*p)); /* calloc initializes to NULL */
502         if (p == NULL) { err = ENOMEM; }
503     }
504 
505     if (!err && (dirhandle != NULL) && (dirhandle->files != NULL)) {
506         size_t i = 0;
507 
508         for (i = 0; !err && (dirhandle->files[i] != NULL); i++) {
509             void *sym = NULL;
510 
511             if (krb5int_get_plugin_data (dirhandle->files[i], symname, &sym, ep) == 0) {
512                 void **newp = NULL;
513 
514                 count++;
515                 newp = realloc (p, ((count + 1) * sizeof (*p))); /* +1 for NULL */
516                 if (newp == NULL) {
517                     err = ENOMEM;
518                 } else {
519                     p = newp;
520                     p[count - 1] = sym;
521                     p[count] = NULL;
522                 }
523             }
524         }
525     }
526 
527     if (!err) {
528         *ptrs = p;
529         p = NULL; /* ptrs takes ownership */
530     }
531 
532     free(p);
533 
534     return err;
535 }
536 
537 void KRB5_CALLCONV
krb5int_free_plugin_dir_func(void (** ptrs)(void))538 krb5int_free_plugin_dir_func (void (**ptrs)(void))
539 {
540     /* Nothing special to be done per pointer.  */
541     free(ptrs);
542 }
543 
544 long KRB5_CALLCONV
krb5int_get_plugin_dir_func(struct plugin_dir_handle * dirhandle,const char * symname,void (*** ptrs)(void),struct errinfo * ep)545 krb5int_get_plugin_dir_func (struct plugin_dir_handle *dirhandle,
546                              const char *symname,
547                              void (***ptrs)(void),
548                              struct errinfo *ep)
549 {
550     long err = 0;
551     void (**p)(void) = NULL;
552     size_t count = 0;
553 
554     /* XXX Do we need to add a leading "_" to the symbol name on any
555        modern platforms?  */
556 
557     Tprintf("get_plugin_data_sym(%s)\n", symname);
558 
559     if (!err) {
560         p = calloc (1, sizeof (*p)); /* calloc initializes to NULL */
561         if (p == NULL) { err = ENOMEM; }
562     }
563 
564     if (!err && (dirhandle != NULL) && (dirhandle->files != NULL)) {
565         size_t i = 0;
566 
567         for (i = 0; !err && (dirhandle->files[i] != NULL); i++) {
568             void (*sym)(void) = NULL;
569 
570             if (krb5int_get_plugin_func (dirhandle->files[i], symname, &sym, ep) == 0) {
571                 void (**newp)(void) = NULL;
572 
573                 count++;
574                 newp = realloc (p, ((count + 1) * sizeof (*p))); /* +1 for NULL */
575                 if (newp == NULL) {
576                     err = ENOMEM;
577                 } else {
578                     p = newp;
579                     p[count - 1] = sym;
580                     p[count] = NULL;
581                 }
582             }
583         }
584     }
585 
586     if (!err) {
587         *ptrs = p;
588         p = NULL; /* ptrs takes ownership */
589     }
590 
591     free(p);
592 
593     return err;
594 }
595