_krb5_load_plugins: Windows naming rules
[heimdal.git] / lib / krb5 / plugin.c
blob0521971b57ef7cf26f92ab9855844286acd98e83
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
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 *p = '\0';
252 if (asprintf(&path, "%s%s", dname, di) == -1)
253 return NULL;
254 return path;
255 #endif
260 * Load plugins (new system) for the given module @name (typically
261 * "krb5") from the given directory @paths.
263 * Inputs:
265 * @context A krb5_context
266 * @name Name of plugin module (typically "krb5")
267 * @paths Array of directory paths where to look
269 KRB5_LIB_FUNCTION void KRB5_LIB_CALL
270 _krb5_load_plugins(krb5_context context, const char *name, const char **paths)
272 #ifdef HAVE_DLOPEN
273 heim_string_t s = heim_string_create(name);
274 heim_dict_t module;
275 struct dirent *entry;
276 krb5_error_code ret;
277 const char **di;
278 char *dirname = NULL;
279 DIR *d;
280 #ifdef _WIN32
281 const char * plugin_prefix;
282 size_t plugin_prefix_len;
284 if (asprintf(&plugin_prefix, "plugin_%s_", name) == -1)
285 return;
286 plugin_prefix_len = (plugin_prefix ? strlen(plugin_prefix) : 0);
287 #endif
289 HEIMDAL_MUTEX_lock(&plugin_mutex);
291 if (modules == NULL) {
292 modules = heim_dict_create(11);
293 if (modules == NULL) {
294 HEIMDAL_MUTEX_unlock(&plugin_mutex);
295 return;
299 module = heim_dict_copy_value(modules, s);
300 if (module == NULL) {
301 module = heim_dict_create(11);
302 if (module == NULL) {
303 HEIMDAL_MUTEX_unlock(&plugin_mutex);
304 heim_release(s);
305 return;
307 heim_dict_set_value(modules, s, module);
309 heim_release(s);
311 for (di = paths; *di != NULL; di++) {
312 free(dirname);
313 dirname = resolve_origin(*di);
314 if (dirname == NULL)
315 continue;
316 d = opendir(dirname);
317 if (d == NULL)
318 continue;
319 rk_cloexec_dir(d);
321 while ((entry = readdir(d)) != NULL) {
322 char *n = entry->d_name;
323 char *path = NULL;
324 heim_string_t spath;
325 struct plugin2 *p;
327 /* skip . and .. */
328 if (n[0] == '.' && (n[1] == '\0' || (n[1] == '.' && n[2] == '\0')))
329 continue;
331 #ifdef _WIN32
333 * On Windows, plugins must be loaded from the same directory as
334 * heimdal.dll (typically the assembly directory) and must have
335 * the name form "plugin_<module>_<name>.dll".
338 char *ext;
340 if (strnicmp(n, plugin_prefix, plugin_prefix_len))
341 continue;
342 ext = strrchr(n, '.');
343 if (ext == NULL || stricmp(ext, ".dll"))
344 continue;
346 #endif
348 ret = 0;
349 #ifdef __APPLE__
350 { /* support loading bundles on MacOS */
351 size_t len = strlen(n);
352 if (len > 7 && strcmp(&n[len - 7], ".bundle") == 0)
353 ret = asprintf(&path, "%s/%s/Contents/MacOS/%.*s", dirname, n, (int)(len - 7), n);
355 #endif
356 if (ret < 0 || path == NULL)
357 ret = asprintf(&path, "%s/%s", dirname, n);
359 if (ret < 0 || path == NULL)
360 continue;
362 spath = heim_string_create(n);
363 if (spath == NULL) {
364 free(path);
365 continue;
368 /* check if already cached */
369 p = heim_dict_copy_value(module, spath);
370 if (p == NULL) {
371 p = heim_alloc(sizeof(*p), "krb5-plugin", plug_dealloc);
372 if (p)
373 p->dsohandle = dlopen(path, RTLD_LOCAL|RTLD_LAZY);
375 if (p && p->dsohandle) {
376 p->path = heim_retain(spath);
377 p->names = heim_dict_create(11);
378 heim_dict_set_value(module, spath, p);
381 heim_release(p);
382 heim_release(spath);
383 free(path);
385 closedir(d);
387 free(dirname);
388 HEIMDAL_MUTEX_unlock(&plugin_mutex);
389 heim_release(module);
390 #ifdef _WIN32
391 if (plugin_prefix)
392 free(plugin_prefix);
393 #endif
394 #endif /* HAVE_DLOPEN */
398 * Unload plugins (new system)
400 KRB5_LIB_FUNCTION void KRB5_LIB_CALL
401 _krb5_unload_plugins(krb5_context context, const char *name)
403 HEIMDAL_MUTEX_lock(&plugin_mutex);
404 heim_release(modules);
405 modules = NULL;
406 HEIMDAL_MUTEX_unlock(&plugin_mutex);
413 struct common_plugin_method {
414 int version;
415 krb5_error_code (*init)(krb5_context, void **);
416 void (*fini)(void *);
419 struct plug {
420 void *dataptr;
421 void *ctx;
424 static void
425 plug_free(void *ptr)
427 struct plug *pl = ptr;
428 if (pl->dataptr) {
429 struct common_plugin_method *cpm = pl->dataptr;
430 cpm->fini(pl->ctx);
434 struct iter_ctx {
435 krb5_context context;
436 heim_string_t n;
437 const char *name;
438 int min_version;
439 int flags;
440 heim_array_t result;
441 krb5_error_code (KRB5_LIB_CALL *func)(krb5_context, const void *, void *, void *);
442 void *userctx;
443 krb5_error_code ret;
446 static void
447 search_modules(heim_object_t key, heim_object_t value, void *ctx)
449 struct iter_ctx *s = ctx;
450 struct plugin2 *p = value;
451 struct plug *pl = heim_dict_copy_value(p->names, s->n);
452 struct common_plugin_method *cpm;
454 if (pl == NULL) {
455 if (p->dsohandle == NULL)
456 return;
458 pl = heim_alloc(sizeof(*pl), "struct-plug", plug_free);
460 cpm = pl->dataptr = dlsym(p->dsohandle, s->name);
461 if (cpm) {
462 int ret;
464 ret = cpm->init(s->context, &pl->ctx);
465 if (ret)
466 cpm = pl->dataptr = NULL;
468 heim_dict_set_value(p->names, s->n, pl);
469 } else {
470 cpm = pl->dataptr;
473 if (cpm && cpm->version >= s->min_version)
474 heim_array_append_value(s->result, pl);
475 heim_release(pl);
478 static void
479 eval_results(heim_object_t value, void *ctx, int *stop)
481 struct plug *pl = value;
482 struct iter_ctx *s = ctx;
484 if (s->ret != KRB5_PLUGIN_NO_HANDLE)
485 return;
487 s->ret = s->func(s->context, pl->dataptr, pl->ctx, s->userctx);
488 if (s->ret != KRB5_PLUGIN_NO_HANDLE
489 && !(s->flags & KRB5_PLUGIN_INVOKE_ALL))
490 *stop = 1;
494 * Run plugins for the given @module (e.g., "krb5") and @name (e.g.,
495 * "kuserok"). Specifically, the @func is invoked once per-plugin with
496 * four arguments: the @context, the plugin symbol value (a pointer to a
497 * struct whose first three fields are the same as struct common_plugin_method),
498 * a context value produced by the plugin's init method, and @userctx.
500 * @func should unpack arguments for a plugin function and invoke it
501 * with arguments taken from @userctx. @func should save plugin
502 * outputs, if any, in @userctx.
504 * All loaded and registered plugins are invoked via @func until @func
505 * returns something other than KRB5_PLUGIN_NO_HANDLE. Plugins that
506 * have nothing to do for the given arguments should return
507 * KRB5_PLUGIN_NO_HANDLE.
509 * Inputs:
511 * @context A krb5_context
512 * @module Name of module (typically "krb5")
513 * @name Name of pluggable interface (e.g., "kuserok")
514 * @min_version Lowest acceptable plugin minor version number
515 * @flags Flags (none defined at this time)
516 * @userctx Callback data for the callback function @func
517 * @func A callback function, invoked once per-plugin
519 * Outputs: None, other than the return value and such outputs as are
520 * gathered by @func.
522 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
523 _krb5_plugin_run_f(krb5_context context,
524 const char *module,
525 const char *name,
526 int min_version,
527 int flags,
528 void *userctx,
529 krb5_error_code (KRB5_LIB_CALL *func)(krb5_context, const void *, void *, void *))
531 heim_string_t m = heim_string_create(module);
532 heim_dict_t dict;
533 void *plug_ctx;
534 struct common_plugin_method *cpm;
535 struct iter_ctx s;
536 struct krb5_plugin *registered_plugins = NULL;
537 struct krb5_plugin *p;
539 /* Get registered plugins */
540 (void) _krb5_plugin_find(context, SYMBOL, name, &registered_plugins);
542 HEIMDAL_MUTEX_lock(&plugin_mutex);
544 s.context = context;
545 s.name = name;
546 s.n = heim_string_create(name);
547 s.flags = flags;
548 s.min_version = min_version;
549 s.result = heim_array_create();
550 s.func = func;
551 s.userctx = userctx;
552 s.ret = KRB5_PLUGIN_NO_HANDLE;
554 /* Get loaded plugins */
555 dict = heim_dict_copy_value(modules, m);
556 heim_release(m);
558 /* Add loaded plugins to s.result array */
559 if (dict)
560 heim_dict_iterate_f(dict, &s, search_modules);
562 /* We don't need to hold plugin_mutex during plugin invocation */
563 HEIMDAL_MUTEX_unlock(&plugin_mutex);
565 /* Invoke registered plugins (old system) */
566 for (p = registered_plugins; p; p = p->next) {
568 * XXX This is the wrong way to handle registered plugins, as we
569 * call init/fini on each invocation! We do this because we
570 * have nowhere in the struct plugin registered list to store
571 * the context allocated by the plugin's init function. (But at
572 * least we do call init/fini!)
574 * What we should do is adapt the old plugin system to the new
575 * one and change how we register plugins so that we use the new
576 * struct plug to keep track of their context structures, that
577 * way we can init once, invoke many times, then fini.
579 cpm = (struct common_plugin_method *)p->symbol;
580 s.ret = cpm->init(context, &plug_ctx);
581 if (s.ret)
582 continue;
583 s.ret = s.func(s.context, p->symbol, plug_ctx, s.userctx);
584 cpm->fini(plug_ctx);
585 if (s.ret != KRB5_PLUGIN_NO_HANDLE &&
586 !(flags & KRB5_PLUGIN_INVOKE_ALL))
587 break;
589 _krb5_plugin_free(registered_plugins);
591 /* Invoke loaded plugins (new system) */
592 if (s.ret == KRB5_PLUGIN_NO_HANDLE)
593 heim_array_iterate_f(s.result, &s, eval_results);
595 heim_release(s.result);
596 heim_release(s.n);
597 heim_release(dict);
599 return s.ret;