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