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