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