xref: /netbsd-src/crypto/external/bsd/heimdal/dist/lib/krb5/plugin.c (revision afab4e300d3a9fb07dd8c80daf53d0feb3345706)
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, &registered_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