summaryrefslogtreecommitdiff
path: root/src/lib/krb5/krb/plugin.c
diff options
context:
space:
mode:
authorCy Schubert <cy@FreeBSD.org>2017-07-07 17:03:42 +0000
committerCy Schubert <cy@FreeBSD.org>2017-07-07 17:03:42 +0000
commit33a9b234e7087f573ef08cd7318c6497ba08b439 (patch)
treed0ea40ad3bf5463a3c55795977c71bcb7d781b4b /src/lib/krb5/krb/plugin.c
Notes
Diffstat (limited to 'src/lib/krb5/krb/plugin.c')
-rw-r--r--src/lib/krb5/krb/plugin.c494
1 files changed, 494 insertions, 0 deletions
diff --git a/src/lib/krb5/krb/plugin.c b/src/lib/krb5/krb/plugin.c
new file mode 100644
index 000000000000..7d64b7c7eda9
--- /dev/null
+++ b/src/lib/krb5/krb/plugin.c
@@ -0,0 +1,494 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/* lib/krb5/krb/plugin.c - Plugin framework functions */
+/*
+ * Copyright (C) 2010 by the Massachusetts Institute of Technology.
+ * All rights reserved.
+ *
+ * Export of this software from the United States of America may
+ * require a specific license from the United States Government.
+ * It is the responsibility of any person or organization contemplating
+ * export to obtain such a license before exporting.
+ *
+ * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
+ * distribute this software and its documentation for any purpose and
+ * without fee is hereby granted, provided that the above copyright
+ * notice appear in all copies and that both that copyright notice and
+ * this permission notice appear in supporting documentation, and that
+ * the name of M.I.T. not be used in advertising or publicity pertaining
+ * to distribution of the software without specific, written prior
+ * permission. Furthermore if you modify this software you must label
+ * your software as modified software and not distribute it in such a
+ * fashion that it might be confused with the original M.I.T. software.
+ * M.I.T. makes no representations about the suitability of
+ * this software for any purpose. It is provided "as is" without express
+ * or implied warranty.
+ */
+
+#include "k5-int.h"
+
+/*
+ * A plugin_mapping structure maps a module name to a built-in or dynamic
+ * module. modname is always present; the other three fields can be in four
+ * different states:
+ *
+ * - If dyn_path and dyn_handle are null but module is set, the mapping is to a
+ * built-in module.
+ * - If dyn_path is set but dyn_handle and module are null, the mapping is to a
+ * dynamic module which hasn't been loaded yet.
+ * - If all three fields are set, the mapping is to a dynamic module which has
+ * been loaded and is ready to use.
+ * - If all three fields are null, the mapping is to a dynamic module which
+ * failed to load and should be ignored.
+ */
+struct plugin_mapping {
+ char *modname;
+ char *dyn_path;
+ struct plugin_file_handle *dyn_handle;
+ krb5_plugin_initvt_fn module;
+};
+
+const char *interface_names[] = {
+ "pwqual",
+ "kadm5_hook",
+ "clpreauth",
+ "kdcpreauth",
+ "ccselect",
+ "localauth",
+ "hostrealm",
+ "audit",
+ "tls",
+ "kdcauthdata"
+};
+
+/* Return the context's interface structure for id, or NULL if invalid. */
+static inline struct plugin_interface *
+get_interface(krb5_context context, int id)
+{
+ if (context == NULL || id < 0 || id >= PLUGIN_NUM_INTERFACES)
+ return NULL;
+ return &context->plugins[id];
+}
+
+/* Release the memory associated with the mapping list entry map. */
+static void
+free_plugin_mapping(struct plugin_mapping *map)
+{
+ if (map == NULL)
+ return;
+ free(map->modname);
+ free(map->dyn_path);
+ if (map->dyn_handle != NULL)
+ krb5int_close_plugin(map->dyn_handle);
+ free(map);
+}
+
+static void
+free_mapping_list(struct plugin_mapping **list)
+{
+ struct plugin_mapping **mp;
+
+ for (mp = list; mp != NULL && *mp != NULL; mp++)
+ free_plugin_mapping(*mp);
+ free(list);
+}
+
+/* Construct a plugin mapping object. path may be NULL (for a built-in
+ * module), or may be relative to the plugin base directory. */
+static krb5_error_code
+make_plugin_mapping(krb5_context context, const char *name, size_t namelen,
+ const char *path, krb5_plugin_initvt_fn module,
+ struct plugin_mapping **map_out)
+{
+ krb5_error_code ret;
+ struct plugin_mapping *map = NULL;
+
+ /* Create the mapping entry. */
+ map = k5alloc(sizeof(*map), &ret);
+ if (map == NULL)
+ return ret;
+
+ map->modname = k5memdup0(name, namelen, &ret);
+ if (map->modname == NULL)
+ goto oom;
+ if (path != NULL) {
+ if (k5_path_join(context->plugin_base_dir, path, &map->dyn_path))
+ goto oom;
+ }
+ map->module = module;
+ *map_out = map;
+ return 0;
+
+oom:
+ free_plugin_mapping(map);
+ return ENOMEM;
+}
+
+/*
+ * Register a mapping from modname to either dyn_path (for an auto-registered
+ * dynamic module) or to module (for a builtin module). dyn_path may be
+ * relative to the plugin base directory.
+ */
+static krb5_error_code
+register_module(krb5_context context, struct plugin_interface *interface,
+ const char *modname, const char *dyn_path,
+ krb5_plugin_initvt_fn module)
+{
+ struct plugin_mapping **list;
+ size_t count;
+
+ /* Allocate list space for another element and a terminator. */
+ list = interface->modules;
+ for (count = 0; list != NULL && list[count] != NULL; count++);
+ list = realloc(interface->modules, (count + 2) * sizeof(*list));
+ if (list == NULL)
+ return ENOMEM;
+ list[count] = list[count + 1] = NULL;
+ interface->modules = list;
+
+ /* Create a new mapping structure and add it to the list. */
+ return make_plugin_mapping(context, modname, strlen(modname), dyn_path,
+ module, &list[count]);
+}
+
+/* Parse a profile module string of the form "modname:modpath" into a mapping
+ * entry. */
+static krb5_error_code
+parse_modstr(krb5_context context, const char *modstr,
+ struct plugin_mapping **map_out)
+{
+ const char *sep;
+
+ *map_out = NULL;
+
+ sep = strchr(modstr, ':');
+ if (sep == NULL) {
+ k5_setmsg(context, KRB5_PLUGIN_BAD_MODULE_SPEC,
+ _("Invalid module specifier %s"), modstr);
+ return KRB5_PLUGIN_BAD_MODULE_SPEC;
+ }
+
+ return make_plugin_mapping(context, modstr, sep - modstr, sep + 1, NULL,
+ map_out);
+}
+
+/* Return true if value is found in list. */
+static krb5_boolean
+find_in_list(char **list, const char *value)
+{
+ for (; *list != NULL; list++) {
+ if (strcmp(*list, value) == 0)
+ return TRUE;
+ }
+ return FALSE;
+}
+
+/* Get the list of values for the profile variable varname in the section for
+ * interface id, or NULL if no values are set. */
+static krb5_error_code
+get_profile_var(krb5_context context, int id, const char *varname, char ***out)
+{
+ krb5_error_code ret;
+ const char *path[4];
+
+ *out = NULL;
+ path[0] = KRB5_CONF_PLUGINS;
+ path[1] = interface_names[id];
+ path[2] = varname;
+ path[3] = NULL;
+ ret = profile_get_values(context->profile, path, out);
+ return (ret == PROF_NO_RELATION) ? 0 : ret;
+}
+
+/* Expand *list_inout to contain the mappings from modstrs, followed by the
+ * existing built-in module mappings. */
+static krb5_error_code
+make_full_list(krb5_context context, char **modstrs,
+ struct plugin_mapping ***list_inout)
+{
+ krb5_error_code ret = 0;
+ size_t count, pos, i, j;
+ struct plugin_mapping **list, **mp;
+ char **mod;
+
+ /* Allocate space for all of the modules plus a null terminator. */
+ for (count = 0; modstrs[count] != NULL; count++);
+ for (mp = *list_inout; mp != NULL && *mp != NULL; mp++, count++);
+ list = calloc(count + 1, sizeof(*list));
+ if (list == NULL)
+ return ENOMEM;
+
+ /* Parse each profile module entry and store it in the list. */
+ for (mod = modstrs, pos = 0; *mod != NULL; mod++, pos++) {
+ ret = parse_modstr(context, *mod, &list[pos]);
+ if (ret != 0) {
+ free_mapping_list(list);
+ return ret;
+ }
+ }
+
+ /* Cannibalize the old list of built-in modules. */
+ for (mp = *list_inout; mp != NULL && *mp != NULL; mp++, pos++)
+ list[pos] = *mp;
+ assert(pos == count);
+
+ /* Filter out duplicates, preferring earlier entries to later ones. */
+ for (i = 0, pos = 0; i < count; i++) {
+ for (j = 0; j < pos; j++) {
+ if (strcmp(list[i]->modname, list[j]->modname) == 0) {
+ free_plugin_mapping(list[i]);
+ break;
+ }
+ }
+ if (j == pos)
+ list[pos++] = list[i];
+ }
+ list[pos] = NULL;
+
+ free(*list_inout);
+ *list_inout = list;
+ return 0;
+}
+
+/* Remove any entries from list which match values in disabled. */
+static void
+remove_disabled_modules(struct plugin_mapping **list, char **disable)
+{
+ struct plugin_mapping **in, **out;
+
+ out = list;
+ for (in = list; *in != NULL; in++) {
+ if (find_in_list(disable, (*in)->modname))
+ free_plugin_mapping(*in);
+ else
+ *out++ = *in;
+ }
+ *out = NULL;
+}
+
+/* Modify list to include only the entries matching strings in enable, in
+ * the order they are listed there. */
+static void
+filter_enabled_modules(struct plugin_mapping **list, char **enable)
+{
+ size_t count, i, pos = 0;
+ struct plugin_mapping *tmp;
+
+ /* Count the number of existing entries. */
+ for (count = 0; list[count] != NULL; count++);
+
+ /* For each string in enable, look for a matching module. */
+ for (; *enable != NULL; enable++) {
+ for (i = pos; i < count; i++) {
+ if (strcmp(list[i]->modname, *enable) == 0) {
+ /* Swap the matching module into the next result position. */
+ tmp = list[pos];
+ list[pos++] = list[i];
+ list[i] = tmp;
+ break;
+ }
+ }
+ }
+
+ /* Free all mappings which didn't match and terminate the list. */
+ for (i = pos; i < count; i++)
+ free_plugin_mapping(list[i]);
+ list[pos] = NULL;
+}
+
+/* Ensure that a plugin interface is configured. id must be valid. */
+static krb5_error_code
+configure_interface(krb5_context context, int id)
+{
+ krb5_error_code ret;
+ struct plugin_interface *interface = &context->plugins[id];
+ char **modstrs = NULL, **enable = NULL, **disable = NULL;
+
+ if (interface->configured)
+ return 0;
+
+ /* Detect consistency errors when plugin interfaces are added. */
+ assert(sizeof(interface_names) / sizeof(*interface_names) ==
+ PLUGIN_NUM_INTERFACES);
+
+ /* Get profile variables for this interface. */
+ ret = get_profile_var(context, id, KRB5_CONF_MODULE, &modstrs);
+ if (ret)
+ goto cleanup;
+ ret = get_profile_var(context, id, KRB5_CONF_DISABLE, &disable);
+ if (ret)
+ goto cleanup;
+ ret = get_profile_var(context, id, KRB5_CONF_ENABLE_ONLY, &enable);
+ if (ret)
+ goto cleanup;
+
+ /* Create the full list of dynamic and built-in modules. */
+ if (modstrs != NULL) {
+ ret = make_full_list(context, modstrs, &interface->modules);
+ if (ret)
+ goto cleanup;
+ }
+
+ /* Remove disabled modules. */
+ if (disable != NULL)
+ remove_disabled_modules(interface->modules, disable);
+
+ /* Filter and re-order the list according to enable-modules. */
+ if (enable != NULL)
+ filter_enabled_modules(interface->modules, enable);
+
+cleanup:
+ profile_free_list(modstrs);
+ profile_free_list(enable);
+ profile_free_list(disable);
+ return ret;
+}
+
+/* If map is for a dynamic module which hasn't been loaded yet, attempt to load
+ * it. Only try to load a module once. */
+static void
+load_if_needed(krb5_context context, struct plugin_mapping *map,
+ const char *iname)
+{
+ char *symname = NULL;
+ struct plugin_file_handle *handle = NULL;
+ void (*initvt_fn)();
+
+ if (map->module != NULL || map->dyn_path == NULL)
+ return;
+ if (asprintf(&symname, "%s_%s_initvt", iname, map->modname) < 0)
+ return;
+ if (krb5int_open_plugin(map->dyn_path, &handle, &context->err))
+ goto err;
+ if (krb5int_get_plugin_func(handle, symname, &initvt_fn, &context->err))
+ goto err;
+ free(symname);
+ map->dyn_handle = handle;
+ map->module = (krb5_plugin_initvt_fn)initvt_fn;
+ return;
+
+err:
+ /* Clean up, and also null out map->dyn_path so we don't try again. */
+ if (handle != NULL)
+ krb5int_close_plugin(handle);
+ free(symname);
+ free(map->dyn_path);
+ map->dyn_path = NULL;
+}
+
+krb5_error_code
+k5_plugin_load(krb5_context context, int interface_id, const char *modname,
+ krb5_plugin_initvt_fn *module)
+{
+ krb5_error_code ret;
+ struct plugin_interface *interface = get_interface(context, interface_id);
+ struct plugin_mapping **mp, *map;
+
+ if (interface == NULL)
+ return EINVAL;
+ ret = configure_interface(context, interface_id);
+ if (ret != 0)
+ return ret;
+ for (mp = interface->modules; mp != NULL && *mp != NULL; mp++) {
+ map = *mp;
+ if (strcmp(map->modname, modname) == 0) {
+ load_if_needed(context, map, interface_names[interface_id]);
+ if (map->module != NULL) {
+ *module = map->module;
+ return 0;
+ }
+ break;
+ }
+ }
+ k5_setmsg(context, KRB5_PLUGIN_NAME_NOTFOUND,
+ _("Could not find %s plugin module named '%s'"),
+ interface_names[interface_id], modname);
+ return KRB5_PLUGIN_NAME_NOTFOUND;
+}
+
+krb5_error_code
+k5_plugin_load_all(krb5_context context, int interface_id,
+ krb5_plugin_initvt_fn **modules)
+{
+ krb5_error_code ret;
+ struct plugin_interface *interface = get_interface(context, interface_id);
+ struct plugin_mapping **mp, *map;
+ krb5_plugin_initvt_fn *list;
+ size_t count;
+
+ if (interface == NULL)
+ return EINVAL;
+ ret = configure_interface(context, interface_id);
+ if (ret != 0)
+ return ret;
+
+ /* Count the modules and allocate a list to hold them. */
+ mp = interface->modules;
+ for (count = 0; mp != NULL && mp[count] != NULL; count++);
+ list = calloc(count + 1, sizeof(*list));
+ if (list == NULL)
+ return ENOMEM;
+
+ /* Place each module's initvt function into list. */
+ count = 0;
+ for (mp = interface->modules; mp != NULL && *mp != NULL; mp++) {
+ map = *mp;
+ load_if_needed(context, map, interface_names[interface_id]);
+ if (map->module != NULL)
+ list[count++] = map->module;
+ }
+
+ *modules = list;
+ return 0;
+}
+
+void
+k5_plugin_free_modules(krb5_context context, krb5_plugin_initvt_fn *modules)
+{
+ free(modules);
+}
+
+krb5_error_code
+k5_plugin_register(krb5_context context, int interface_id, const char *modname,
+ krb5_plugin_initvt_fn module)
+{
+ struct plugin_interface *interface = get_interface(context, interface_id);
+
+ if (interface == NULL)
+ return EINVAL;
+
+ /* Disallow registering plugins after load. We may need to reconsider
+ * this, but it simplifies the design. */
+ if (interface->configured)
+ return EINVAL;
+
+ return register_module(context, interface, modname, NULL, module);
+}
+
+krb5_error_code
+k5_plugin_register_dyn(krb5_context context, int interface_id,
+ const char *modname, const char *modsubdir)
+{
+ krb5_error_code ret;
+ struct plugin_interface *interface = get_interface(context, interface_id);
+ char *path;
+
+ /* Disallow registering plugins after load. */
+ if (interface == NULL || interface->configured)
+ return EINVAL;
+
+ if (asprintf(&path, "%s/%s%s", modsubdir, modname, PLUGIN_EXT) < 0)
+ return ENOMEM;
+ ret = register_module(context, interface, modname, path, NULL);
+ free(path);
+ return ret;
+}
+
+void
+k5_plugin_free_context(krb5_context context)
+{
+ int i;
+
+ for (i = 0; i < PLUGIN_NUM_INTERFACES; i++)
+ free_mapping_list(context->plugins[i].modules);
+ memset(context->plugins, 0, sizeof(context->plugins));
+}