Home | History | Annotate | Line # | Download | only in krb5
      1 /*	$NetBSD: plugin.c,v 1.3 2023/06/19 21:41:44 christos Exp $	*/
      2 
      3 /*
      4  * Copyright (c) 2006 - 2007 Kungliga Tekniska Hgskolan
      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
     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
    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
    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
    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
    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 *
    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
    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
    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
    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
    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
    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
    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