xref: /freebsd/crypto/heimdal/lib/krb5/plugin.c (revision 123af6ec70016f5556da5972d4d63c7d175c06d3)
1 /*
2  * Copyright (c) 2006 - 2007 Kungliga Tekniska Högskolan
3  * (Royal Institute of Technology, Stockholm, Sweden).
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  *
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  *
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.
16  *
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.
20  *
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.
32  */
33 
34 #include "krb5_locl.h"
35 
36 #ifdef HAVE_DLFCN_H
37 #include <dlfcn.h>
38 #endif
39 #include <dirent.h>
40 
41 struct krb5_plugin {
42     void *symbol;
43     struct krb5_plugin *next;
44 };
45 
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;
60 };
61 
62 static HEIMDAL_MUTEX plugin_mutex = HEIMDAL_MUTEX_INITIALIZER;
63 static struct plugin *registered = NULL;
64 static int plugins_needs_scan = 1;
65 
66 static const char *sysplugin_dirs[] =  {
67     LIBDIR "/plugin/krb5",
68 #ifdef __APPLE__
69     "/System/Library/KerberosPlugins/KerberosFrameworkPlugins",
70 #endif
71     NULL
72 };
73 
74 /*
75  *
76  */
77 
78 void *
79 _krb5_plugin_get_symbol(struct krb5_plugin *p)
80 {
81     return p->symbol;
82 }
83 
84 struct krb5_plugin *
85 _krb5_plugin_get_next(struct krb5_plugin *p)
86 {
87     return p->next;
88 }
89 
90 /*
91  *
92  */
93 
94 #ifdef HAVE_DLOPEN
95 
96 static krb5_error_code
97 loadlib(krb5_context context, char *path)
98 {
99     struct plugin *e;
100 
101     e = calloc(1, sizeof(*e));
102     if (e == NULL) {
103 	krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
104 	free(path);
105 	return ENOMEM;
106     }
107 
108 #ifndef RTLD_LAZY
109 #define RTLD_LAZY 0
110 #endif
111 #ifndef RTLD_LOCAL
112 #define RTLD_LOCAL 0
113 #endif
114     e->type = DSO;
115     /* ignore error from dlopen, and just keep it as negative cache entry */
116     e->u.dso.dsohandle = dlopen(path, RTLD_LOCAL|RTLD_LAZY);
117     e->u.dso.path = path;
118 
119     e->next = registered;
120     registered = e;
121 
122     return 0;
123 }
124 #endif /* HAVE_DLOPEN */
125 
126 /**
127  * Register a plugin symbol name of specific type.
128  * @param context a Keberos context
129  * @param type type of plugin symbol
130  * @param name name of plugin symbol
131  * @param symbol a pointer to the named symbol
132  * @return In case of error a non zero error com_err error is returned
133  * and the Kerberos error string is set.
134  *
135  * @ingroup krb5_support
136  */
137 
138 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
139 krb5_plugin_register(krb5_context context,
140 		     enum krb5_plugin_type type,
141 		     const char *name,
142 		     void *symbol)
143 {
144     struct plugin *e;
145 
146     HEIMDAL_MUTEX_lock(&plugin_mutex);
147 
148     /* check for duplicates */
149     for (e = registered; e != NULL; e = e->next) {
150 	if (e->type == SYMBOL &&
151 	    strcmp(e->u.symbol.name, name) == 0 &&
152 	    e->u.symbol.type == type && e->u.symbol.symbol == symbol) {
153 	    HEIMDAL_MUTEX_unlock(&plugin_mutex);
154 	    return 0;
155 	}
156     }
157 
158     e = calloc(1, sizeof(*e));
159     if (e == NULL) {
160 	HEIMDAL_MUTEX_unlock(&plugin_mutex);
161 	krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
162 	return ENOMEM;
163     }
164     e->type = SYMBOL;
165     e->u.symbol.type = type;
166     e->u.symbol.name = strdup(name);
167     if (e->u.symbol.name == NULL) {
168 	HEIMDAL_MUTEX_unlock(&plugin_mutex);
169 	free(e);
170 	krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
171 	return ENOMEM;
172     }
173     e->u.symbol.symbol = symbol;
174 
175     e->next = registered;
176     registered = e;
177     HEIMDAL_MUTEX_unlock(&plugin_mutex);
178 
179     return 0;
180 }
181 
182 static int
183 is_valid_plugin_filename(const char * n)
184 {
185     if (n[0] == '.' && (n[1] == '\0' || (n[1] == '.' && n[2] == '\0')))
186         return 0;
187 
188 #ifdef _WIN32
189     /* On Windows, we only attempt to load .dll files as plug-ins. */
190     {
191         const char * ext;
192 
193         ext = strrchr(n, '.');
194         if (ext == NULL)
195             return 0;
196 
197         return !stricmp(ext, ".dll");
198     }
199 #else
200     return 1;
201 #endif
202 }
203 
204 static void
205 trim_trailing_slash(char * path)
206 {
207     size_t l;
208 
209     l = strlen(path);
210     while (l > 0 && (path[l - 1] == '/'
211 #ifdef BACKSLASH_PATH_DELIM
212                      || path[l - 1] == '\\'
213 #endif
214                )) {
215         path[--l] = '\0';
216     }
217 }
218 
219 static krb5_error_code
220 load_plugins(krb5_context context)
221 {
222     struct plugin *e;
223     krb5_error_code ret;
224     char **dirs = NULL, **di;
225     struct dirent *entry;
226     char *path;
227     DIR *d = NULL;
228 
229     if (!plugins_needs_scan)
230 	return 0;
231     plugins_needs_scan = 0;
232 
233 #ifdef HAVE_DLOPEN
234 
235     dirs = krb5_config_get_strings(context, NULL, "libdefaults",
236 				   "plugin_dir", NULL);
237     if (dirs == NULL)
238 	dirs = rk_UNCONST(sysplugin_dirs);
239 
240     for (di = dirs; *di != NULL; di++) {
241         char * dir = *di;
242 
243 #ifdef KRB5_USE_PATH_TOKENS
244         if (_krb5_expand_path_tokens(context, *di, &dir))
245             goto next_dir;
246 #endif
247 
248         trim_trailing_slash(dir);
249 
250         d = opendir(dir);
251 
252 	if (d == NULL)
253 	    goto next_dir;
254 
255 	rk_cloexec_dir(d);
256 
257 	while ((entry = readdir(d)) != NULL) {
258 	    char *n = entry->d_name;
259 
260 	    /* skip . and .. */
261             if (!is_valid_plugin_filename(n))
262 		continue;
263 
264 	    path = NULL;
265 	    ret = 0;
266 #ifdef __APPLE__
267 	    { /* support loading bundles on MacOS */
268 		size_t len = strlen(n);
269 		if (len > 7 && strcmp(&n[len - 7],  ".bundle") == 0)
270 		    ret = asprintf(&path, "%s/%s/Contents/MacOS/%.*s", dir, n, (int)(len - 7), n);
271 	    }
272 #endif
273 	    if (ret < 0 || path == NULL)
274 		ret = asprintf(&path, "%s/%s", dir, n);
275 
276 	    if (ret < 0 || path == NULL) {
277 		ret = ENOMEM;
278 		krb5_set_error_message(context, ret, "malloc: out of memory");
279 		return ret;
280 	    }
281 
282 	    /* check if already tried */
283 	    for (e = registered; e != NULL; e = e->next)
284 		if (e->type == DSO && strcmp(e->u.dso.path, path) == 0)
285 		    break;
286 	    if (e) {
287 		free(path);
288 	    } else {
289 		loadlib(context, path); /* store or frees path */
290 	    }
291 	}
292 	closedir(d);
293 
294     next_dir:
295         if (dir != *di)
296             free(dir);
297     }
298     if (dirs != rk_UNCONST(sysplugin_dirs))
299 	krb5_config_free_strings(dirs);
300 #endif /* HAVE_DLOPEN */
301     return 0;
302 }
303 
304 static krb5_error_code
305 add_symbol(krb5_context context, struct krb5_plugin **list, void *symbol)
306 {
307     struct krb5_plugin *e;
308 
309     e = calloc(1, sizeof(*e));
310     if (e == NULL) {
311 	krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
312 	return ENOMEM;
313     }
314     e->symbol = symbol;
315     e->next = *list;
316     *list = e;
317     return 0;
318 }
319 
320 krb5_error_code
321 _krb5_plugin_find(krb5_context context,
322 		  enum krb5_plugin_type type,
323 		  const char *name,
324 		  struct krb5_plugin **list)
325 {
326     struct plugin *e;
327     krb5_error_code ret;
328 
329     *list = NULL;
330 
331     HEIMDAL_MUTEX_lock(&plugin_mutex);
332 
333     load_plugins(context);
334 
335     for (ret = 0, e = registered; e != NULL; e = e->next) {
336 	switch(e->type) {
337 	case DSO: {
338 	    void *sym;
339 	    if (e->u.dso.dsohandle == NULL)
340 		continue;
341 	    sym = dlsym(e->u.dso.dsohandle, name);
342 	    if (sym)
343 		ret = add_symbol(context, list, sym);
344 	    break;
345 	}
346 	case SYMBOL:
347 	    if (strcmp(e->u.symbol.name, name) == 0 && e->u.symbol.type == type)
348 		ret = add_symbol(context, list, e->u.symbol.symbol);
349 	    break;
350 	}
351 	if (ret) {
352 	    _krb5_plugin_free(*list);
353 	    *list = NULL;
354 	}
355     }
356 
357     HEIMDAL_MUTEX_unlock(&plugin_mutex);
358     if (ret)
359 	return ret;
360 
361     if (*list == NULL) {
362 	krb5_set_error_message(context, ENOENT, "Did not find a plugin for %s", name);
363 	return ENOENT;
364     }
365 
366     return 0;
367 }
368 
369 void
370 _krb5_plugin_free(struct krb5_plugin *list)
371 {
372     struct krb5_plugin *next;
373     while (list) {
374 	next = list->next;
375 	free(list);
376 	list = next;
377     }
378 }
379 /*
380  * module - dict of {
381  *      ModuleName = [
382  *          plugin = object{
383  *              array = { ptr, ctx }
384  *          }
385  *      ]
386  * }
387  */
388 
389 static heim_dict_t modules;
390 
391 struct plugin2 {
392     heim_string_t path;
393     void *dsohandle;
394     heim_dict_t names;
395 };
396 
397 static void
398 plug_dealloc(void *ptr)
399 {
400     struct plugin2 *p = ptr;
401     heim_release(p->path);
402     heim_release(p->names);
403     if (p->dsohandle)
404 	dlclose(p->dsohandle);
405 }
406 
407 
408 void
409 _krb5_load_plugins(krb5_context context, const char *name, const char **paths)
410 {
411 #ifdef HAVE_DLOPEN
412     heim_string_t s = heim_string_create(name);
413     heim_dict_t module;
414     struct dirent *entry;
415     krb5_error_code ret;
416     const char **di;
417     DIR *d;
418 
419     HEIMDAL_MUTEX_lock(&plugin_mutex);
420 
421     if (modules == NULL) {
422 	modules = heim_dict_create(11);
423 	if (modules == NULL) {
424 	    HEIMDAL_MUTEX_unlock(&plugin_mutex);
425 	    return;
426 	}
427     }
428 
429     module = heim_dict_copy_value(modules, s);
430     if (module == NULL) {
431 	module = heim_dict_create(11);
432 	if (module == NULL) {
433 	    HEIMDAL_MUTEX_unlock(&plugin_mutex);
434 	    heim_release(s);
435 	    return;
436 	}
437 	heim_dict_add_value(modules, s, module);
438     }
439     heim_release(s);
440 
441     for (di = paths; *di != NULL; di++) {
442 	d = opendir(*di);
443 	if (d == NULL)
444 	    continue;
445 	rk_cloexec_dir(d);
446 
447 	while ((entry = readdir(d)) != NULL) {
448 	    char *n = entry->d_name;
449 	    char *path = NULL;
450 	    heim_string_t spath;
451 	    struct plugin2 *p;
452 
453 	    /* skip . and .. */
454 	    if (n[0] == '.' && (n[1] == '\0' || (n[1] == '.' && n[2] == '\0')))
455 		continue;
456 
457 	    ret = 0;
458 #ifdef __APPLE__
459 	    { /* support loading bundles on MacOS */
460 		size_t len = strlen(n);
461 		if (len > 7 && strcmp(&n[len - 7],  ".bundle") == 0)
462 		    ret = asprintf(&path, "%s/%s/Contents/MacOS/%.*s", *di, n, (int)(len - 7), n);
463 	    }
464 #endif
465 	    if (ret < 0 || path == NULL)
466 		ret = asprintf(&path, "%s/%s", *di, n);
467 
468 	    if (ret < 0 || path == NULL)
469 		continue;
470 
471 	    spath = heim_string_create(n);
472 	    if (spath == NULL) {
473 		free(path);
474 		continue;
475 	    }
476 
477 	    /* check if already cached */
478 	    p = heim_dict_copy_value(module, spath);
479 	    if (p == NULL) {
480 		p = heim_alloc(sizeof(*p), "krb5-plugin", plug_dealloc);
481 		if (p)
482 		    p->dsohandle = dlopen(path, RTLD_LOCAL|RTLD_LAZY);
483 
484 		if (p->dsohandle) {
485 		    p->path = heim_retain(spath);
486 		    p->names = heim_dict_create(11);
487 		    heim_dict_add_value(module, spath, p);
488 		}
489 	    }
490 	    heim_release(spath);
491 	    heim_release(p);
492 	    free(path);
493 	}
494 	closedir(d);
495     }
496     heim_release(module);
497     HEIMDAL_MUTEX_unlock(&plugin_mutex);
498 #endif /* HAVE_DLOPEN */
499 }
500 
501 void
502 _krb5_unload_plugins(krb5_context context, const char *name)
503 {
504     HEIMDAL_MUTEX_lock(&plugin_mutex);
505     heim_release(modules);
506     modules = NULL;
507     HEIMDAL_MUTEX_unlock(&plugin_mutex);
508 }
509 
510 /*
511  *
512  */
513 
514 struct common_plugin_method {
515     int			version;
516     krb5_error_code	(*init)(krb5_context, void **);
517     void		(*fini)(void *);
518 };
519 
520 struct plug {
521     void *dataptr;
522     void *ctx;
523 };
524 
525 static void
526 plug_free(void *ptr)
527 {
528     struct plug *pl = ptr;
529     if (pl->dataptr) {
530 	struct common_plugin_method *cpm = pl->dataptr;
531 	cpm->fini(pl->ctx);
532     }
533 }
534 
535 struct iter_ctx {
536     krb5_context context;
537     heim_string_t n;
538     const char *name;
539     int min_version;
540     heim_array_t result;
541     krb5_error_code (*func)(krb5_context, const void *, void *, void *);
542     void *userctx;
543     krb5_error_code ret;
544 };
545 
546 static void
547 search_modules(void *ctx, heim_object_t key, heim_object_t value)
548 {
549     struct iter_ctx *s = ctx;
550     struct plugin2 *p = value;
551     struct plug *pl = heim_dict_copy_value(p->names, s->n);
552     struct common_plugin_method *cpm;
553 
554     if (pl == NULL) {
555 	if (p->dsohandle == NULL)
556 	    return;
557 
558 	pl = heim_alloc(sizeof(*pl), "struct-plug", plug_free);
559 
560 	cpm = pl->dataptr = dlsym(p->dsohandle, s->name);
561 	if (cpm) {
562 	    int ret;
563 
564 	    ret = cpm->init(s->context, &pl->ctx);
565 	    if (ret)
566 		cpm = pl->dataptr = NULL;
567 	}
568 	heim_dict_add_value(p->names, s->n, pl);
569     } else {
570 	cpm = pl->dataptr;
571     }
572 
573     if (cpm && cpm->version >= s->min_version)
574 	heim_array_append_value(s->result, pl);
575 
576     heim_release(pl);
577 }
578 
579 static void
580 eval_results(heim_object_t value, void *ctx)
581 {
582     struct plug *pl = value;
583     struct iter_ctx *s = ctx;
584 
585     if (s->ret != KRB5_PLUGIN_NO_HANDLE)
586 	return;
587 
588     s->ret = s->func(s->context, pl->dataptr, pl->ctx, s->userctx);
589 }
590 
591 krb5_error_code
592 _krb5_plugin_run_f(krb5_context context,
593 		   const char *module,
594 		   const char *name,
595 		   int min_version,
596 		   int flags,
597 		   void *userctx,
598 		   krb5_error_code (*func)(krb5_context, const void *, void *, void *))
599 {
600     heim_string_t m = heim_string_create(module);
601     heim_dict_t dict;
602     struct iter_ctx s;
603 
604     HEIMDAL_MUTEX_lock(&plugin_mutex);
605 
606     dict = heim_dict_copy_value(modules, m);
607     heim_release(m);
608     if (dict == NULL) {
609 	HEIMDAL_MUTEX_unlock(&plugin_mutex);
610 	return KRB5_PLUGIN_NO_HANDLE;
611     }
612 
613     s.context = context;
614     s.name = name;
615     s.n = heim_string_create(name);
616     s.min_version = min_version;
617     s.result = heim_array_create();
618     s.func = func;
619     s.userctx = userctx;
620 
621     heim_dict_iterate_f(dict, search_modules, &s);
622 
623     heim_release(dict);
624 
625     HEIMDAL_MUTEX_unlock(&plugin_mutex);
626 
627     s.ret = KRB5_PLUGIN_NO_HANDLE;
628 
629     heim_array_iterate_f(s.result, eval_results, &s);
630 
631     heim_release(s.result);
632     heim_release(s.n);
633 
634     return s.ret;
635 }
636