Merge pull request #203 from sdigit/patch-1
[heimdal.git] / lib / krb5 / plugin.c
blob03f64000f2396faa3156a71cca8bcd27c207efbf
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 static void
213 plug_dealloc(void *ptr)
215 struct plugin2 *p = ptr;
216 heim_release(p->path);
217 heim_release(p->names);
218 if (p->dsohandle)
219 dlclose(p->dsohandle);
222 static char *
223 resolve_origin(const char *di)
225 #ifdef HAVE_DLADDR
226 Dl_info dl_info;
227 const char *dname;
228 char *path, *p;
229 #endif
231 if (strncmp(di, "$ORIGIN/", sizeof("$ORIGIN/") - 1) &&
232 strcmp(di, "$ORIGIN"))
233 return strdup(di);
235 #ifndef HAVE_DLADDR
236 return strdup(LIBDIR "/plugin/krb5");
237 #else /* !HAVE_DLADDR */
238 di += sizeof("$ORIGIN") - 1;
240 if (dladdr(_krb5_load_plugins, &dl_info) == 0)
241 return strdup(LIBDIR "/plugin/krb5");
243 dname = dl_info.dli_fname;
244 #ifdef _WIN32
245 p = strrchr(dname, '\\');
246 if (p == NULL)
247 #endif
248 p = strrchr(dname, '/');
249 if (p) {
250 if (asprintf(&path, "%.*s%s", (int) (p - dname), dname, di) == -1)
251 return NULL;
252 } else {
253 if (asprintf(&path, "%s%s", dname, di) == -1)
254 return NULL;
257 return path;
258 #endif /* !HAVE_DLADDR */
263 * Load plugins (new system) for the given module @name (typically
264 * "krb5") from the given directory @paths.
266 * Inputs:
268 * @context A krb5_context
269 * @name Name of plugin module (typically "krb5")
270 * @paths Array of directory paths where to look
272 KRB5_LIB_FUNCTION void KRB5_LIB_CALL
273 _krb5_load_plugins(krb5_context context, const char *name, const char **paths)
275 #ifdef HAVE_DLOPEN
276 heim_string_t s = heim_string_create(name);
277 heim_dict_t module;
278 struct dirent *entry;
279 krb5_error_code ret;
280 const char **di;
281 char *dirname = NULL;
282 DIR *d;
283 #ifdef _WIN32
284 const char * plugin_prefix;
285 size_t plugin_prefix_len;
287 if (asprintf(&plugin_prefix, "plugin_%s_", name) == -1)
288 return;
289 plugin_prefix_len = (plugin_prefix ? strlen(plugin_prefix) : 0);
290 #endif
292 HEIMDAL_MUTEX_lock(&plugin_mutex);
294 if (modules == NULL) {
295 modules = heim_dict_create(11);
296 if (modules == NULL) {
297 HEIMDAL_MUTEX_unlock(&plugin_mutex);
298 return;
302 module = heim_dict_copy_value(modules, s);
303 if (module == NULL) {
304 module = heim_dict_create(11);
305 if (module == NULL) {
306 HEIMDAL_MUTEX_unlock(&plugin_mutex);
307 heim_release(s);
308 return;
310 heim_dict_set_value(modules, s, module);
312 heim_release(s);
314 for (di = paths; *di != NULL; di++) {
315 free(dirname);
316 dirname = resolve_origin(*di);
317 if (dirname == NULL)
318 continue;
319 d = opendir(dirname);
320 if (d == NULL)
321 continue;
322 rk_cloexec_dir(d);
324 while ((entry = readdir(d)) != NULL) {
325 char *n = entry->d_name;
326 char *path = NULL;
327 heim_string_t spath;
328 struct plugin2 *p;
330 /* skip . and .. */
331 if (n[0] == '.' && (n[1] == '\0' || (n[1] == '.' && n[2] == '\0')))
332 continue;
334 ret = 0;
335 #ifdef _WIN32
337 * On Windows, plugins must be loaded from the same directory as
338 * heimdal.dll (typically the assembly directory) and must have
339 * the name form "plugin_<module>_<name>.dll".
342 char *ext;
344 if (strnicmp(n, plugin_prefix, plugin_prefix_len))
345 continue;
346 ext = strrchr(n, '.');
347 if (ext == NULL || stricmp(ext, ".dll"))
348 continue;
350 ret = asprintf(&path, "%s\\%s", dirname, n);
351 if (ret < 0 || path == NULL)
352 continue;
354 #endif
355 #ifdef __APPLE__
356 { /* support loading bundles on MacOS */
357 size_t len = strlen(n);
358 if (len > 7 && strcmp(&n[len - 7], ".bundle") == 0)
359 ret = asprintf(&path, "%s/%s/Contents/MacOS/%.*s", dirname, n, (int)(len - 7), n);
361 #endif
362 if (ret < 0 || path == NULL)
363 ret = asprintf(&path, "%s/%s", dirname, n);
365 if (ret < 0 || path == NULL)
366 continue;
368 spath = heim_string_create(n);
369 if (spath == NULL) {
370 free(path);
371 continue;
374 /* check if already cached */
375 p = heim_dict_copy_value(module, spath);
376 if (p == NULL) {
377 p = heim_alloc(sizeof(*p), "krb5-plugin", plug_dealloc);
378 if (p)
379 p->dsohandle = dlopen(path, RTLD_LOCAL|RTLD_LAZY);
381 if (p && p->dsohandle) {
382 p->path = heim_retain(spath);
383 p->names = heim_dict_create(11);
384 heim_dict_set_value(module, spath, p);
387 heim_release(p);
388 heim_release(spath);
389 free(path);
391 closedir(d);
393 free(dirname);
394 HEIMDAL_MUTEX_unlock(&plugin_mutex);
395 heim_release(module);
396 #ifdef _WIN32
397 if (plugin_prefix)
398 free(plugin_prefix);
399 #endif
400 #endif /* HAVE_DLOPEN */
404 * Unload plugins (new system)
406 KRB5_LIB_FUNCTION void KRB5_LIB_CALL
407 _krb5_unload_plugins(krb5_context context, const char *name)
409 HEIMDAL_MUTEX_lock(&plugin_mutex);
410 heim_release(modules);
411 modules = NULL;
412 HEIMDAL_MUTEX_unlock(&plugin_mutex);
419 struct common_plugin_method {
420 int version;
421 krb5_error_code (*init)(krb5_context, void **);
422 void (*fini)(void *);
425 struct plug {
426 void *dataptr;
427 void *ctx;
430 static void
431 plug_free(void *ptr)
433 struct plug *pl = ptr;
434 if (pl->dataptr) {
435 struct common_plugin_method *cpm = pl->dataptr;
436 cpm->fini(pl->ctx);
440 struct iter_ctx {
441 krb5_context context;
442 heim_string_t n;
443 const char *name;
444 int min_version;
445 int flags;
446 heim_array_t result;
447 krb5_error_code (KRB5_LIB_CALL *func)(krb5_context, const void *, void *, void *);
448 void *userctx;
449 krb5_error_code ret;
452 static void
453 search_modules(heim_object_t key, heim_object_t value, void *ctx)
455 struct iter_ctx *s = ctx;
456 struct plugin2 *p = value;
457 struct plug *pl = heim_dict_copy_value(p->names, s->n);
458 struct common_plugin_method *cpm;
460 if (pl == NULL) {
461 if (p->dsohandle == NULL)
462 return;
464 pl = heim_alloc(sizeof(*pl), "struct-plug", plug_free);
466 cpm = pl->dataptr = dlsym(p->dsohandle, s->name);
467 if (cpm) {
468 int ret;
470 ret = cpm->init(s->context, &pl->ctx);
471 if (ret)
472 cpm = pl->dataptr = NULL;
474 heim_dict_set_value(p->names, s->n, pl);
475 } else {
476 cpm = pl->dataptr;
479 if (cpm && cpm->version >= s->min_version)
480 heim_array_append_value(s->result, pl);
481 heim_release(pl);
484 static void
485 eval_results(heim_object_t value, void *ctx, int *stop)
487 struct plug *pl = value;
488 struct iter_ctx *s = ctx;
490 if (s->ret != KRB5_PLUGIN_NO_HANDLE)
491 return;
493 s->ret = s->func(s->context, pl->dataptr, pl->ctx, s->userctx);
494 if (s->ret != KRB5_PLUGIN_NO_HANDLE
495 && !(s->flags & KRB5_PLUGIN_INVOKE_ALL))
496 *stop = 1;
500 * Run plugins for the given @module (e.g., "krb5") and @name (e.g.,
501 * "kuserok"). Specifically, the @func is invoked once per-plugin with
502 * four arguments: the @context, the plugin symbol value (a pointer to a
503 * struct whose first three fields are the same as struct common_plugin_method),
504 * a context value produced by the plugin's init method, and @userctx.
506 * @func should unpack arguments for a plugin function and invoke it
507 * with arguments taken from @userctx. @func should save plugin
508 * outputs, if any, in @userctx.
510 * All loaded and registered plugins are invoked via @func until @func
511 * returns something other than KRB5_PLUGIN_NO_HANDLE. Plugins that
512 * have nothing to do for the given arguments should return
513 * KRB5_PLUGIN_NO_HANDLE.
515 * Inputs:
517 * @context A krb5_context
518 * @module Name of module (typically "krb5")
519 * @name Name of pluggable interface (e.g., "kuserok")
520 * @min_version Lowest acceptable plugin minor version number
521 * @flags Flags (none defined at this time)
522 * @userctx Callback data for the callback function @func
523 * @func A callback function, invoked once per-plugin
525 * Outputs: None, other than the return value and such outputs as are
526 * gathered by @func.
528 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
529 _krb5_plugin_run_f(krb5_context context,
530 const char *module,
531 const char *name,
532 int min_version,
533 int flags,
534 void *userctx,
535 krb5_error_code (KRB5_LIB_CALL *func)(krb5_context, const void *, void *, void *))
537 heim_string_t m = heim_string_create(module);
538 heim_dict_t dict;
539 void *plug_ctx;
540 struct common_plugin_method *cpm;
541 struct iter_ctx s;
542 struct krb5_plugin *registered_plugins = NULL;
543 struct krb5_plugin *p;
545 /* Get registered plugins */
546 (void) _krb5_plugin_find(context, SYMBOL, name, &registered_plugins);
548 HEIMDAL_MUTEX_lock(&plugin_mutex);
550 s.context = context;
551 s.name = name;
552 s.n = heim_string_create(name);
553 s.flags = flags;
554 s.min_version = min_version;
555 s.result = heim_array_create();
556 s.func = func;
557 s.userctx = userctx;
558 s.ret = KRB5_PLUGIN_NO_HANDLE;
560 /* Get loaded plugins */
561 dict = heim_dict_copy_value(modules, m);
562 heim_release(m);
564 /* Add loaded plugins to s.result array */
565 if (dict)
566 heim_dict_iterate_f(dict, &s, search_modules);
568 /* We don't need to hold plugin_mutex during plugin invocation */
569 HEIMDAL_MUTEX_unlock(&plugin_mutex);
571 /* Invoke registered plugins (old system) */
572 for (p = registered_plugins; p; p = p->next) {
574 * XXX This is the wrong way to handle registered plugins, as we
575 * call init/fini on each invocation! We do this because we
576 * have nowhere in the struct plugin registered list to store
577 * the context allocated by the plugin's init function. (But at
578 * least we do call init/fini!)
580 * What we should do is adapt the old plugin system to the new
581 * one and change how we register plugins so that we use the new
582 * struct plug to keep track of their context structures, that
583 * way we can init once, invoke many times, then fini.
585 cpm = (struct common_plugin_method *)p->symbol;
586 s.ret = cpm->init(context, &plug_ctx);
587 if (s.ret)
588 continue;
589 s.ret = s.func(s.context, p->symbol, plug_ctx, s.userctx);
590 cpm->fini(plug_ctx);
591 if (s.ret != KRB5_PLUGIN_NO_HANDLE &&
592 !(flags & KRB5_PLUGIN_INVOKE_ALL))
593 break;
595 _krb5_plugin_free(registered_plugins);
597 /* Invoke loaded plugins (new system) */
598 if (s.ret == KRB5_PLUGIN_NO_HANDLE)
599 heim_array_iterate_f(s.result, &s, eval_results);
601 heim_release(s.result);
602 heim_release(s.n);
603 heim_release(dict);
605 return s.ret;