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