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