do not dereference NULL pointer modules if we do not have dlopen
[heimdal.git] / lib / krb5 / plugin.c
blobf97c513af2069d20b78da9cedd4eac25ef291b4c
1 /*
2 * Copyright (c) 2006 - 2007 Kungliga Tekniska Högskolan
3 * (Royal Institute of Technology, Stockholm, Sweden).
4 * All rights reserved.
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
17 * 3. Neither the name of the Institute nor the names of its contributors
18 * may be used to endorse or promote products derived from this software
19 * without specific prior written permission.
21 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 * SUCH DAMAGE.
34 #include "krb5_locl.h"
36 #ifdef HAVE_DLFCN_H
37 #include <dlfcn.h>
38 #endif
39 #include <dirent.h>
41 struct krb5_plugin {
42 void *symbol;
43 struct krb5_plugin *next;
46 struct plugin {
47 enum { DSO, SYMBOL } type;
48 union {
49 struct {
50 char *path;
51 void *dsohandle;
52 } dso;
53 struct {
54 enum krb5_plugin_type type;
55 char *name;
56 char *symbol;
57 } symbol;
58 } u;
59 struct plugin *next;
62 static HEIMDAL_MUTEX plugin_mutex = HEIMDAL_MUTEX_INITIALIZER;
63 static struct plugin *registered = NULL;
65 /**
66 * Register a plugin symbol name of specific type.
67 * @param context a Keberos context
68 * @param type type of plugin symbol
69 * @param name name of plugin symbol
70 * @param symbol a pointer to the named symbol
71 * @return In case of error a non zero error com_err error is returned
72 * and the Kerberos error string is set.
74 * @ingroup krb5_support
77 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
78 krb5_plugin_register(krb5_context context,
79 enum krb5_plugin_type type,
80 const char *name,
81 void *symbol)
83 struct plugin *e;
85 HEIMDAL_MUTEX_lock(&plugin_mutex);
87 /* check for duplicates */
88 for (e = registered; e != NULL; e = e->next) {
89 if (e->type == SYMBOL &&
90 strcmp(e->u.symbol.name, name) == 0 &&
91 e->u.symbol.type == type && e->u.symbol.symbol == symbol) {
92 HEIMDAL_MUTEX_unlock(&plugin_mutex);
93 return 0;
97 e = calloc(1, sizeof(*e));
98 if (e == NULL) {
99 HEIMDAL_MUTEX_unlock(&plugin_mutex);
100 krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
101 return ENOMEM;
103 e->type = SYMBOL;
104 e->u.symbol.type = type;
105 e->u.symbol.name = strdup(name);
106 if (e->u.symbol.name == NULL) {
107 HEIMDAL_MUTEX_unlock(&plugin_mutex);
108 free(e);
109 krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
110 return ENOMEM;
112 e->u.symbol.symbol = symbol;
114 e->next = registered;
115 registered = e;
116 HEIMDAL_MUTEX_unlock(&plugin_mutex);
118 return 0;
121 static krb5_error_code
122 add_symbol(krb5_context context, struct krb5_plugin **list, void *symbol)
124 struct krb5_plugin *e;
126 e = calloc(1, sizeof(*e));
127 if (e == NULL) {
128 krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
129 return ENOMEM;
131 e->symbol = symbol;
132 e->next = *list;
133 *list = e;
134 return 0;
137 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
138 _krb5_plugin_find(krb5_context context,
139 enum krb5_plugin_type type,
140 const char *name,
141 struct krb5_plugin **list)
143 struct plugin *e;
144 krb5_error_code ret;
146 *list = NULL;
148 HEIMDAL_MUTEX_lock(&plugin_mutex);
150 for (ret = 0, e = registered; e != NULL; e = e->next) {
151 switch(e->type) {
152 case DSO: {
153 void *sym;
154 if (e->u.dso.dsohandle == NULL)
155 continue;
156 sym = dlsym(e->u.dso.dsohandle, name);
157 if (sym)
158 ret = add_symbol(context, list, sym);
159 break;
161 case SYMBOL:
162 if (strcmp(e->u.symbol.name, name) == 0 && e->u.symbol.type == type)
163 ret = add_symbol(context, list, e->u.symbol.symbol);
164 break;
166 if (ret) {
167 _krb5_plugin_free(*list);
168 *list = NULL;
172 HEIMDAL_MUTEX_unlock(&plugin_mutex);
173 if (ret)
174 return ret;
176 if (*list == NULL) {
177 krb5_set_error_message(context, ENOENT, "Did not find a plugin for %s", name);
178 return ENOENT;
181 return 0;
184 KRB5_LIB_FUNCTION void KRB5_LIB_CALL
185 _krb5_plugin_free(struct krb5_plugin *list)
187 struct krb5_plugin *next;
188 while (list) {
189 next = list->next;
190 free(list);
191 list = next;
195 * module - dict of {
196 * ModuleName = [
197 * plugin = object{
198 * array = { ptr, ctx }
204 static heim_dict_t modules;
206 struct plugin2 {
207 heim_string_t path;
208 void *dsohandle;
209 heim_dict_t names;
212 #ifdef HAVE_DLOPEN
214 static void
215 plug_dealloc(void *ptr)
217 struct plugin2 *p = ptr;
218 heim_release(p->path);
219 heim_release(p->names);
220 if (p->dsohandle)
221 dlclose(p->dsohandle);
224 static char *
225 resolve_origin(const char *di)
227 #ifdef HAVE_DLADDR
228 Dl_info dl_info;
229 const char *dname;
230 char *path, *p;
231 #endif
233 if (strncmp(di, "$ORIGIN/", sizeof("$ORIGIN/") - 1) &&
234 strcmp(di, "$ORIGIN"))
235 return strdup(di);
237 #ifndef HAVE_DLADDR
238 return strdup(LIBDIR "/plugin/krb5");
239 #else /* !HAVE_DLADDR */
240 di += sizeof("$ORIGIN") - 1;
242 if (dladdr(_krb5_load_plugins, &dl_info) == 0)
243 return strdup(LIBDIR "/plugin/krb5");
245 dname = dl_info.dli_fname;
246 #ifdef _WIN32
247 p = strrchr(dname, '\\');
248 if (p == NULL)
249 #endif
250 p = strrchr(dname, '/');
251 if (p) {
252 if (asprintf(&path, "%.*s%s", (int) (p - dname), dname, di) == -1)
253 return NULL;
254 } else {
255 if (asprintf(&path, "%s%s", dname, di) == -1)
256 return NULL;
259 return path;
260 #endif /* !HAVE_DLADDR */
263 #endif /* HAVE_DLOPEN */
266 * Load plugins (new system) for the given module @name (typically
267 * "krb5") from the given directory @paths.
269 * Inputs:
271 * @context A krb5_context
272 * @name Name of plugin module (typically "krb5")
273 * @paths Array of directory paths where to look
275 KRB5_LIB_FUNCTION void KRB5_LIB_CALL
276 _krb5_load_plugins(krb5_context context, const char *name, const char **paths)
278 #ifdef HAVE_DLOPEN
279 heim_string_t s = heim_string_create(name);
280 heim_dict_t module;
281 struct dirent *entry;
282 krb5_error_code ret;
283 const char **di;
284 char *dirname = NULL;
285 DIR *d;
286 #ifdef _WIN32
287 const char * plugin_prefix;
288 size_t plugin_prefix_len;
290 if (asprintf(&plugin_prefix, "plugin_%s_", name) == -1)
291 return;
292 plugin_prefix_len = (plugin_prefix ? strlen(plugin_prefix) : 0);
293 #endif
295 HEIMDAL_MUTEX_lock(&plugin_mutex);
297 if (modules == NULL) {
298 modules = heim_dict_create(11);
299 if (modules == NULL) {
300 HEIMDAL_MUTEX_unlock(&plugin_mutex);
301 return;
305 module = heim_dict_copy_value(modules, s);
306 if (module == NULL) {
307 module = heim_dict_create(11);
308 if (module == NULL) {
309 HEIMDAL_MUTEX_unlock(&plugin_mutex);
310 heim_release(s);
311 return;
313 heim_dict_set_value(modules, s, module);
315 heim_release(s);
317 for (di = paths; *di != NULL; di++) {
318 free(dirname);
319 dirname = resolve_origin(*di);
320 if (dirname == NULL)
321 continue;
322 d = opendir(dirname);
323 if (d == NULL)
324 continue;
325 rk_cloexec_dir(d);
327 while ((entry = readdir(d)) != NULL) {
328 char *n = entry->d_name;
329 char *path = NULL;
330 heim_string_t spath;
331 struct plugin2 *p;
333 /* skip . and .. */
334 if (n[0] == '.' && (n[1] == '\0' || (n[1] == '.' && n[2] == '\0')))
335 continue;
337 ret = 0;
338 #ifdef _WIN32
340 * On Windows, plugins must be loaded from the same directory as
341 * heimdal.dll (typically the assembly directory) and must have
342 * the name form "plugin_<module>_<name>.dll".
345 char *ext;
347 if (strnicmp(n, plugin_prefix, plugin_prefix_len))
348 continue;
349 ext = strrchr(n, '.');
350 if (ext == NULL || stricmp(ext, ".dll"))
351 continue;
353 ret = asprintf(&path, "%s\\%s", dirname, n);
354 if (ret < 0 || path == NULL)
355 continue;
357 #endif
358 #ifdef __APPLE__
359 { /* support loading bundles on MacOS */
360 size_t len = strlen(n);
361 if (len > 7 && strcmp(&n[len - 7], ".bundle") == 0)
362 ret = asprintf(&path, "%s/%s/Contents/MacOS/%.*s", dirname, n, (int)(len - 7), n);
364 #endif
365 if (ret < 0 || path == NULL)
366 ret = asprintf(&path, "%s/%s", dirname, n);
368 if (ret < 0 || path == NULL)
369 continue;
371 spath = heim_string_create(n);
372 if (spath == NULL) {
373 free(path);
374 continue;
377 /* check if already cached */
378 p = heim_dict_copy_value(module, spath);
379 if (p == NULL) {
380 p = heim_alloc(sizeof(*p), "krb5-plugin", plug_dealloc);
381 if (p)
382 p->dsohandle = dlopen(path, RTLD_LOCAL|RTLD_LAZY);
384 if (p && p->dsohandle) {
385 p->path = heim_retain(spath);
386 p->names = heim_dict_create(11);
387 heim_dict_set_value(module, spath, p);
390 heim_release(p);
391 heim_release(spath);
392 free(path);
394 closedir(d);
396 free(dirname);
397 HEIMDAL_MUTEX_unlock(&plugin_mutex);
398 heim_release(module);
399 #ifdef _WIN32
400 if (plugin_prefix)
401 free(plugin_prefix);
402 #endif
403 #endif /* HAVE_DLOPEN */
407 * Unload plugins (new system)
409 KRB5_LIB_FUNCTION void KRB5_LIB_CALL
410 _krb5_unload_plugins(krb5_context context, const char *name)
412 HEIMDAL_MUTEX_lock(&plugin_mutex);
413 heim_release(modules);
414 modules = NULL;
415 HEIMDAL_MUTEX_unlock(&plugin_mutex);
422 struct common_plugin_method {
423 int version;
424 krb5_error_code (*init)(krb5_context, void **);
425 void (*fini)(void *);
428 struct plug {
429 void *dataptr;
430 void *ctx;
433 static void
434 plug_free(void *ptr)
436 struct plug *pl = ptr;
437 if (pl->dataptr) {
438 struct common_plugin_method *cpm = pl->dataptr;
439 cpm->fini(pl->ctx);
443 struct iter_ctx {
444 krb5_context context;
445 heim_string_t n;
446 const char *name;
447 int min_version;
448 int flags;
449 heim_array_t result;
450 krb5_error_code (KRB5_LIB_CALL *func)(krb5_context, const void *, void *, void *);
451 void *userctx;
452 krb5_error_code ret;
455 static void
456 search_modules(heim_object_t key, heim_object_t value, void *ctx)
458 struct iter_ctx *s = ctx;
459 struct plugin2 *p = value;
460 struct plug *pl = heim_dict_copy_value(p->names, s->n);
461 struct common_plugin_method *cpm;
463 if (pl == NULL) {
464 if (p->dsohandle == NULL)
465 return;
467 pl = heim_alloc(sizeof(*pl), "struct-plug", plug_free);
469 cpm = pl->dataptr = dlsym(p->dsohandle, s->name);
470 if (cpm) {
471 int ret;
473 ret = cpm->init(s->context, &pl->ctx);
474 if (ret)
475 cpm = pl->dataptr = NULL;
477 heim_dict_set_value(p->names, s->n, pl);
478 } else {
479 cpm = pl->dataptr;
482 if (cpm && cpm->version >= s->min_version)
483 heim_array_append_value(s->result, pl);
484 heim_release(pl);
487 static void
488 eval_results(heim_object_t value, void *ctx, int *stop)
490 struct plug *pl = value;
491 struct iter_ctx *s = ctx;
493 if (s->ret != KRB5_PLUGIN_NO_HANDLE)
494 return;
496 s->ret = s->func(s->context, pl->dataptr, pl->ctx, s->userctx);
497 if (s->ret != KRB5_PLUGIN_NO_HANDLE
498 && !(s->flags & KRB5_PLUGIN_INVOKE_ALL))
499 *stop = 1;
503 * Run plugins for the given @module (e.g., "krb5") and @name (e.g.,
504 * "kuserok"). Specifically, the @func is invoked once per-plugin with
505 * four arguments: the @context, the plugin symbol value (a pointer to a
506 * struct whose first three fields are the same as struct common_plugin_method),
507 * a context value produced by the plugin's init method, and @userctx.
509 * @func should unpack arguments for a plugin function and invoke it
510 * with arguments taken from @userctx. @func should save plugin
511 * outputs, if any, in @userctx.
513 * All loaded and registered plugins are invoked via @func until @func
514 * returns something other than KRB5_PLUGIN_NO_HANDLE. Plugins that
515 * have nothing to do for the given arguments should return
516 * KRB5_PLUGIN_NO_HANDLE.
518 * Inputs:
520 * @context A krb5_context
521 * @module Name of module (typically "krb5")
522 * @name Name of pluggable interface (e.g., "kuserok")
523 * @min_version Lowest acceptable plugin minor version number
524 * @flags Flags (none defined at this time)
525 * @userctx Callback data for the callback function @func
526 * @func A callback function, invoked once per-plugin
528 * Outputs: None, other than the return value and such outputs as are
529 * gathered by @func.
531 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
532 _krb5_plugin_run_f(krb5_context context,
533 const char *module,
534 const char *name,
535 int min_version,
536 int flags,
537 void *userctx,
538 krb5_error_code (KRB5_LIB_CALL *func)(krb5_context, const void *, void *, void *))
540 heim_string_t m = heim_string_create(module);
541 heim_dict_t dict = NULL;
542 void *plug_ctx;
543 struct common_plugin_method *cpm;
544 struct iter_ctx s;
545 struct krb5_plugin *registered_plugins = NULL;
546 struct krb5_plugin *p;
548 /* Get registered plugins */
549 (void) _krb5_plugin_find(context, SYMBOL, name, &registered_plugins);
551 HEIMDAL_MUTEX_lock(&plugin_mutex);
553 s.context = context;
554 s.name = name;
555 s.n = heim_string_create(name);
556 s.flags = flags;
557 s.min_version = min_version;
558 s.result = heim_array_create();
559 s.func = func;
560 s.userctx = userctx;
561 s.ret = KRB5_PLUGIN_NO_HANDLE;
563 /* Get loaded plugins */
564 if (modules)
565 dict = heim_dict_copy_value(modules, m);
567 heim_release(m);
569 /* Add loaded plugins to s.result array */
570 if (dict)
571 heim_dict_iterate_f(dict, &s, search_modules);
573 /* We don't need to hold plugin_mutex during plugin invocation */
574 HEIMDAL_MUTEX_unlock(&plugin_mutex);
576 /* Invoke registered plugins (old system) */
577 for (p = registered_plugins; p; p = p->next) {
579 * XXX This is the wrong way to handle registered plugins, as we
580 * call init/fini on each invocation! We do this because we
581 * have nowhere in the struct plugin registered list to store
582 * the context allocated by the plugin's init function. (But at
583 * least we do call init/fini!)
585 * What we should do is adapt the old plugin system to the new
586 * one and change how we register plugins so that we use the new
587 * struct plug to keep track of their context structures, that
588 * way we can init once, invoke many times, then fini.
590 cpm = (struct common_plugin_method *)p->symbol;
591 s.ret = cpm->init(context, &plug_ctx);
592 if (s.ret)
593 continue;
594 s.ret = s.func(s.context, p->symbol, plug_ctx, s.userctx);
595 cpm->fini(plug_ctx);
596 if (s.ret != KRB5_PLUGIN_NO_HANDLE &&
597 !(flags & KRB5_PLUGIN_INVOKE_ALL))
598 break;
600 _krb5_plugin_free(registered_plugins);
602 /* Invoke loaded plugins (new system) */
603 if (s.ret == KRB5_PLUGIN_NO_HANDLE)
604 heim_array_iterate_f(s.result, &s, eval_results);
606 heim_release(s.result);
607 heim_release(s.n);
608 if (dict)
609 heim_release(dict);
611 return s.ret;