*/
#include <errno.h>
#include "krb5.h"
+#include <krb5/plugin.h>
#include "profile.h"
#include "port-sockets.h"
#define KRB5_CONF_DEFAULT_PRINCIPAL_EXPIRATION "default_principal_expiration"
#define KRB5_CONF_DEFAULT_PRINCIPAL_FLAGS "default_principal_flags"
#define KRB5_CONF_DICT_FILE "dict_file"
+#define KRB5_CONF_DISABLE "disable"
#define KRB5_CONF_DISABLE_LAST_SUCCESS "disable_last_success"
#define KRB5_CONF_DISABLE_LOCKOUT "disable_lockout"
#define KRB5_CONF_DNS_LOOKUP_KDC "dns_lookup_kdc"
#define KRB5_CONF_DNS_LOOKUP_REALM "dns_lookup_realm"
#define KRB5_CONF_DNS_FALLBACK "dns_fallback"
#define KRB5_CONF_DOMAIN_REALM "domain_realm"
+#define KRB5_CONF_ENABLE_ONLY "enable_only"
#define KRB5_CONF_EXTRA_ADDRESSES "extra_addresses"
#define KRB5_CONF_FORWARDABLE "forwardable"
#define KRB5_CONF_HOST_BASED_SERVICES "host_based_services"
#define KRB5_CONF_MASTER_KDC "master_kdc"
#define KRB5_CONF_MAX_LIFE "max_life"
#define KRB5_CONF_MAX_RENEWABLE_LIFE "max_renewable_life"
+#define KRB5_CONF_MODULE "module"
#define KRB5_CONF_NOADDRESSES "noaddresses"
#define KRB5_CONF_NO_HOST_REFERRAL "no_host_referral"
#define KRB5_CONF_PERMITTED_ENCTYPES "permitted_enctypes"
+#define KRB5_CONF_PLUGINS "plugins"
#define KRB5_CONF_PREAUTH_MODULE_DIR "preauth_module_dir"
#define KRB5_CONF_PREFERRED_PREAUTH_TYPES "preferred_preauth_types"
#define KRB5_CONF_PROXIABLE "proxiable"
krb5_authdata_context context, const char *module,
void *ptr);
+/*** Plugin framework ***/
+
+/*
+ * This framework can be used to create pluggable interfaces. Not all existing
+ * pluggable interface use this framework, but new ones should. A new
+ * pluggable interface entails:
+ *
+ * - An interface ID definition in the list of #defines below.
+ *
+ * - A name in the interface_names array in lib/krb5/krb/plugins.c.
+ *
+ * - An installed public header file in include/krb5. The public header should
+ * include <krb5/plugin.h> and should declare a vtable structure for each
+ * supported major version of the interface.
+ *
+ * - A consumer API implementation, located within the code unit which makes
+ * use of the pluggable interface. The consumer API should consist of:
+ *
+ * . An interface-specific handle type which contains a vtable structure for
+ * the module (or a union of several such structures, if there are multiple
+ * supported major versions) and, optionally, resource data bound to the
+ * handle.
+ *
+ * . An interface-specific loader function which creates a handle or list of
+ * handles. A list of handles would be created if the interface is a
+ * one-to-many interface where the consumer wants to consult all available
+ * modules; a single handle would be created for an interface where the
+ * consumer wants to consult a specific module. The loader function should
+ * use k5_plugin_load or k5_plugin_load_all to produce one or a list of
+ * vtable initializer functions, and should use those functions to fill in
+ * the vtable structure for the module (if necessary, trying each supported
+ * major version starting from the most recent). The loader function can
+ * also bind resource data into the handle based on caller arguments, if
+ * appropriate.
+ *
+ * . For each plugin method, a wrapper function which accepts a krb5_context,
+ * a plugin handle, and the method arguments. Wrapper functions should
+ * invoke the method function contained in the handle's vtable.
+ *
+ * - Possibly, built-in implementations of the interface, also located within
+ * the code unit which makes use of the interface. Built-in implementations
+ * must be registered with k5_plugin_register before the first call to
+ * k5_plugin_load or k5_plugin_load_all.
+ *
+ * A pluggable interface should have one or more currently supported major
+ * versions, starting at 1. Each major version should have a current minor
+ * version, also starting at 1. If new methods are added to a vtable, the
+ * minor version should be incremented and the vtable stucture should document
+ * where each minor vtable version ends. If method signatures for a vtable are
+ * changed, the major version should be incremented.
+ *
+ * Plugin module implementations (either built-in or dynamically loaded) should
+ * define a function named <interfacename>_<modulename>_initvt, matching the
+ * signature of krb5_plugin_initvt_fn as declared in include/krb5/plugin.h.
+ * The initvt function should check the given maj_ver argument against its own
+ * supported major versions, cast the vtable pointer to the appropriate
+ * interface-specific vtable type, and fill in the vtable methods, stopping as
+ * appropriate for the given min_ver. Memory for the vtable structure is
+ * allocated by the caller, not by the module.
+ *
+ * Dynamic plugin modules are registered with the framework through the
+ * [plugins] section of the profile, as described in the admin documentation
+ * and krb5.conf man page.
+ */
+
+/*
+ * A linked list entry mapping a module name to a module initvt function. The
+ * entry may also include a dynamic object handle so that it can be released
+ * when the context is destroyed.
+ */
+struct plugin_mapping {
+ char *modname;
+ krb5_plugin_initvt_fn module;
+ struct plugin_file_handle *dyn_handle;
+ struct plugin_mapping *next;
+};
+
+/* Holds krb5_context information about each pluggable interface. */
+struct plugin_interface {
+ struct plugin_mapping *modules;
+ krb5_boolean configured;
+};
+
+/* A list of plugin interface IDs. Make sure to increment
+ * PLUGIN_NUM_INTERFACES when a new interface is added. */
+#define PLUGIN_NUM_INTERFACES 0
+
+/* Retrieve the plugin module of type interface_id and name modname,
+ * storing the result into module. */
+krb5_error_code
+k5_plugin_load(krb5_context context, int interface_id, const char *modname,
+ krb5_plugin_initvt_fn *module);
+
+/* Retrieve all plugin modules of type interface_id, storing the result
+ * into modules. Free the result with k5_plugin_free_handles. */
+krb5_error_code
+k5_plugin_load_all(krb5_context context, int interface_id,
+ krb5_plugin_initvt_fn **modules);
+
+/* Release a module list allocated by k5_plugin_load_all. */
+void
+k5_plugin_free_modules(krb5_context context, krb5_plugin_initvt_fn *modules);
+
+/* Register a plugin module of type interface_id and name modname. */
+krb5_error_code
+k5_plugin_register(krb5_context context, int interface_id, const char *modname,
+ krb5_plugin_initvt_fn module);
+
+/* Destroy the module state within context; used by krb5_free_context. */
+void
+k5_plugin_free_context(krb5_context context);
+
struct _kdb5_dal_handle; /* private, in kdb5.h */
typedef struct _kdb5_dal_handle kdb5_dal_handle;
struct _kdb_log_context;
krb5_trace_callback trace_callback;
void *trace_callback_data;
+
+ struct plugin_interface plugins[PLUGIN_NUM_INTERFACES];
};
/* could be used in a table to find an etype and initialize a block */
--- /dev/null
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ * lib/krb5/krb/plugin.c
+ *
+ * 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.
+ *
+ *
+ * Plugin framework functions
+ */
+
+#include "k5-int.h"
+
+const char *interface_names[PLUGIN_NUM_INTERFACES] = {
+};
+
+/* 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 linked list entry map. */
+static void
+free_plugin_mapping(struct plugin_mapping *map)
+{
+ if (map == NULL)
+ return;
+ free(map->modname);
+ if (map->dyn_handle != NULL)
+ krb5int_close_plugin(map->dyn_handle);
+ free(map);
+}
+
+/*
+ * Register a mapping from modname to module. On success, dyn_handle is
+ * remembered in the mapping and will be released when the mapping is
+ * overwritten or the context is destroyed.
+ */
+static krb5_error_code
+register_module(krb5_context context, struct plugin_interface *interface,
+ const char *modname, krb5_plugin_initvt_fn module,
+ struct plugin_file_handle *dyn_handle)
+{
+ struct plugin_mapping *map, **pmap;
+
+ /* If a mapping already exists for modname, remove it. */
+ for (pmap = &interface->modules; *pmap != NULL; pmap = &(*pmap)->next) {
+ map = *pmap;
+ if (strcmp(map->modname, modname) == 0) {
+ *pmap = map->next;
+ free_plugin_mapping(map);
+ break;
+ }
+ }
+
+ /* Create a new mapping structure. */
+ map = malloc(sizeof(*map));
+ if (map == NULL)
+ return ENOMEM;
+ map->modname = strdup(modname);
+ if (map->modname == NULL) {
+ free(map);
+ return ENOMEM;
+ }
+ map->module = module;
+ map->dyn_handle = dyn_handle;
+
+ /* Chain it into the list. */
+ map->next = interface->modules;
+ interface->modules = map;
+ return 0;
+}
+
+/* Parse a profile module string of the form "modname:modpath" into its
+ * component parts. */
+static krb5_error_code
+parse_modstr(krb5_context context, const char *modstr,
+ char **modname, char **modpath)
+{
+ const char *sep;
+ char *name = NULL, *path = NULL;
+
+ *modname = NULL;
+ *modpath = NULL;
+
+ sep = strchr(modstr, ':');
+ if (sep == NULL) {
+ krb5_set_error_message(context, KRB5_PLUGIN_BAD_MODULE_SPEC,
+ "Invalid module specifier %s", modstr);
+ return KRB5_PLUGIN_BAD_MODULE_SPEC;
+ }
+
+ /* Copy the module name. */
+ name = malloc(sep - modstr + 1);
+ if (name == NULL)
+ return ENOMEM;
+ memcpy(name, modstr, sep - modstr);
+ name[sep - modstr] = '\0';
+
+ /* Copy the module path. */
+ path = strdup(sep + 1);
+ if (path == NULL) {
+ free(name);
+ return ENOMEM;
+ }
+
+ *modname = name;
+ *modpath = path;
+ return 0;
+}
+
+/* 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;
+}
+
+/* Return true if module is not filtered out by enable or disable lists. */
+static krb5_boolean
+module_enabled(const char *modname, char **enable, char **disable)
+{
+ return ((enable == NULL || find_in_list(enable, modname)) &&
+ (disable == NULL || !find_in_list(disable, modname)));
+}
+
+/* Remove any registered modules whose names are filtered out. */
+static void
+filter_builtins(krb5_context context, struct plugin_interface *interface,
+ char **enable, char **disable)
+{
+ struct plugin_mapping *map, **pmap;
+
+ pmap = &interface->modules;
+ while (*pmap != NULL) {
+ map = *pmap;
+ if (!module_enabled(map->modname, enable, disable)) {
+ *pmap = map->next;
+ free_plugin_mapping(map);
+ } else
+ pmap = &map->next;
+ }
+}
+
+/* Register the plugin module given by the profile string mod. */
+static krb5_error_code
+register_dyn_module(krb5_context context, struct plugin_interface *interface,
+ const char *iname, const char *modstr, char **enable,
+ char **disable)
+{
+ krb5_error_code ret;
+ char *modname = NULL, *modpath = NULL, *symname = NULL;
+ struct plugin_file_handle *handle = NULL;
+ void (*initvt_fn)();
+
+ /* Parse out the module name and path, and make sure it is enabled. */
+ ret = parse_modstr(context, modstr, &modname, &modpath);
+ if (ret != 0)
+ goto cleanup;
+ if (!module_enabled(modname, enable, disable))
+ goto cleanup;
+
+ /* Construct the initvt symbol name for this interface and module. */
+ if (asprintf(&symname, "%s_%s_initvt", iname, modname) < 0) {
+ symname = NULL;
+ ret = ENOMEM;
+ goto cleanup;
+ }
+
+ /* Open the plugin and resolve the initvt symbol. */
+ ret = krb5int_open_plugin(modpath, &handle, &context->err);
+ if (ret != 0)
+ goto cleanup;
+ ret = krb5int_get_plugin_func(handle, symname, &initvt_fn, &context->err);
+ if (ret != 0)
+ goto cleanup;
+
+ /* Create a mapping for the module. */
+ ret = register_module(context, interface, modname,
+ (krb5_plugin_initvt_fn)initvt_fn, handle);
+ if (ret != 0)
+ goto cleanup;
+ handle = NULL; /* Now owned by the module mapping. */
+
+cleanup:
+ free(modname);
+ free(modpath);
+ free(symname);
+ if (handle != NULL)
+ krb5int_close_plugin(handle);
+ return ret;
+}
+
+/* Ensure that a plugin interface is configured. id is assumed to be valid. */
+static krb5_error_code
+configure_interface(krb5_context context, int id)
+{
+ krb5_error_code ret;
+ struct plugin_interface *interface = &context->plugins[id];
+ const char *iname = interface_names[id];
+ char **modules = NULL, **enable = NULL, **disable = NULL, **mod;
+ static const char *path[4];
+
+ if (interface->configured)
+ return 0;
+
+ /* Read the configuration variables for this interface. */
+ path[0] = KRB5_CONF_PLUGINS;
+ path[1] = iname;
+ path[2] = KRB5_CONF_MODULE;
+ path[3] = NULL;
+ ret = profile_get_values(context->profile, path, &modules);
+ if (ret != 0 && ret != PROF_NO_RELATION)
+ goto cleanup;
+ path[2] = KRB5_CONF_ENABLE_ONLY;
+ ret = profile_get_values(context->profile, path, &enable);
+ if (ret != 0 && ret != PROF_NO_RELATION)
+ goto cleanup;
+ path[2] = KRB5_CONF_DISABLE;
+ ret = profile_get_values(context->profile, path, &disable);
+ if (ret != 0 && ret != PROF_NO_RELATION)
+ goto cleanup;
+
+ /* Remove built-in modules which are filtered out by configuration. */
+ filter_builtins(context, interface, enable, disable);
+
+ /* Create mappings for dynamic modules which aren't filtered out. */
+ for (mod = modules; mod && *mod; mod++) {
+ ret = register_dyn_module(context, interface, iname, *mod,
+ enable, disable);
+ if (ret != 0)
+ return ret;
+ }
+
+ ret = 0;
+cleanup:
+ profile_free_list(modules);
+ profile_free_list(enable);
+ profile_free_list(disable);
+ return ret;
+}
+
+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 *map;
+
+ if (interface == NULL)
+ return EINVAL;
+ ret = configure_interface(context, interface_id);
+ if (ret != 0)
+ return ret;
+ for (map = interface->modules; map != NULL; map = map->next) {
+ if (strcmp(map->modname, modname) == 0) {
+ *module = map->module;
+ return 0;
+ }
+ }
+ krb5_set_error_message(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 *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. */
+ count = 0;
+ for (map = interface->modules; map != NULL; map = map->next)
+ count++;
+ list = malloc((count + 1) * sizeof(*list));
+ if (list == NULL)
+ return ENOMEM;
+
+ /* Place each module's initvt function into list. */
+ count = 0;
+ for (map = interface->modules; map != NULL; map = map->next)
+ list[count++] = map->module;
+ list[count] = NULL;
+
+ *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, module, NULL);
+}
+
+void
+k5_plugin_free_context(krb5_context context)
+{
+ int i;
+ struct plugin_interface *interface;
+ struct plugin_mapping *map, *next;
+
+ for (i = 0; i < PLUGIN_NUM_INTERFACES; i++) {
+ interface = &context->plugins[i];
+ for (map = interface->modules; map != NULL; map = next) {
+ next = map->next;
+ free_plugin_mapping(map);
+ }
+ interface->modules = NULL;
+ interface->configured = FALSE;
+ }
+}