diff --git a/src/include/Makefile.in b/src/include/Makefile.in index 651bd3df83..930712d18e 100644 --- a/src/include/Makefile.in +++ b/src/include/Makefile.in @@ -137,6 +137,7 @@ install-headers-unix install:: krb5/krb5.h profile.h $(INSTALL_DATA) $(srcdir)/kdb.h $(DESTDIR)$(KRB5_INCDIR)$(S)kdb.h $(INSTALL_DATA) krb5/krb5.h $(DESTDIR)$(KRB5_INCDIR)$(S)krb5$(S)krb5.h $(INSTALL_DATA) $(srcdir)/krb5/locate_plugin.h $(DESTDIR)$(KRB5_INCDIR)$(S)krb5$(S)locate_plugin.h + $(INSTALL_DATA) $(srcdir)/krb5/plugin.h $(DESTDIR)$(KRB5_INCDIR)$(S)krb5$(S)plugin.h $(INSTALL_DATA) profile.h $(DESTDIR)$(KRB5_INCDIR)$(S)profile.h $(INSTALL_DATA) $(srcdir)/gssapi.h $(DESTDIR)$(KRB5_INCDIR)$(S)gssapi.h diff --git a/src/include/k5-int.h b/src/include/k5-int.h index bb078c0705..ec701c1dfe 100644 --- a/src/include/k5-int.h +++ b/src/include/k5-int.h @@ -168,6 +168,7 @@ typedef INT64_TYPE krb5_int64; */ #include #include "krb5.h" +#include #include "profile.h" #include "port-sockets.h" @@ -205,12 +206,14 @@ typedef INT64_TYPE krb5_int64; #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" @@ -245,9 +248,11 @@ typedef INT64_TYPE krb5_int64; #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" @@ -1426,6 +1431,118 @@ krb5_authdata_free_internal(krb5_context kcontext, 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 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 __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; @@ -1480,6 +1597,8 @@ struct _krb5_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 */ diff --git a/src/include/krb5/plugin.h b/src/include/krb5/plugin.h new file mode 100644 index 0000000000..f43406ab8e --- /dev/null +++ b/src/include/krb5/plugin.h @@ -0,0 +1,48 @@ +/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* + * + * + * 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. + * + * + * Generic declarations for dynamic modules implementing krb5 plugin modules. + */ + +#ifndef KRB5_PLUGIN_H +#define KRB5_PLUGIN_H + +/* krb5_plugin_vtable is an abstract type. Module initvt functions will cast + * it to the appropriate interface-specific vtable type. */ +typedef struct krb5_plugin_vtable_st *krb5_plugin_vtable; + +/* + * krb5_plugin_initvt_fn is the type of all module initvt functions. Based on + * the maj_ver argument, the initvt function should cast vtable to the + * appropriate type and then fill it in. If a vtable has been expanded, + * min_ver indicates which version of the vtable is being filled in. + */ +typedef krb5_error_code +(*krb5_plugin_initvt_fn)(krb5_context context, int maj_ver, int min_ver, + krb5_plugin_vtable vtable); + +#endif /* KRB5_PLUGIN_H */ diff --git a/src/lib/krb5/error_tables/k5e1_err.et b/src/lib/krb5/error_tables/k5e1_err.et index 6d5621001a..7fe4cc6fc4 100644 --- a/src/lib/krb5/error_tables/k5e1_err.et +++ b/src/lib/krb5/error_tables/k5e1_err.et @@ -30,4 +30,8 @@ # error_table k5e1 +error_code KRB5_PLUGIN_VER_NOTSUPP, "Plugin does not support interface version" +error_code KRB5_PLUGIN_BAD_MODULE_SPEC, "Invalid module specifier" +error_code KRB5_PLUGIN_NAME_NOTFOUND, "Plugin module name not found" + end diff --git a/src/lib/krb5/krb/Makefile.in b/src/lib/krb5/krb/Makefile.in index 895d44478a..1b4d879677 100644 --- a/src/lib/krb5/krb/Makefile.in +++ b/src/lib/krb5/krb/Makefile.in @@ -74,6 +74,7 @@ STLIBOBJS= \ pac.o \ pac_sign.o \ parse.o \ + plugin.o \ pr_to_salt.o \ preauth2.o \ gic_opt_set_pa.o \ @@ -173,6 +174,7 @@ OBJS= $(OUTPRE)addr_comp.$(OBJEXT) \ $(OUTPRE)pac.$(OBJEXT) \ $(OUTPRE)pac_sign.$(OBJEXT) \ $(OUTPRE)parse.$(OBJEXT) \ + $(OUTPRE)plugin.$(OBJEXT) \ $(OUTPRE)pr_to_salt.$(OBJEXT) \ $(OUTPRE)preauth2.$(OBJEXT) \ $(OUTPRE)gic_opt_set_pa.$(OBJEXT) \ @@ -273,6 +275,7 @@ SRCS= $(srcdir)/addr_comp.c \ $(srcdir)/pac.c \ $(srcdir)/pac_sign.c \ $(srcdir)/parse.c \ + $(srcdir)/plugin.c \ $(srcdir)/pr_to_salt.c \ $(srcdir)/preauth2.c \ $(srcdir)/gic_opt_set_pa.c \ diff --git a/src/lib/krb5/krb/init_ctx.c b/src/lib/krb5/krb/init_ctx.c index e7419f5d4a..c5975f19a6 100644 --- a/src/lib/krb5/krb/init_ctx.c +++ b/src/lib/krb5/krb/init_ctx.c @@ -273,6 +273,8 @@ krb5_free_context(krb5_context ctx) ctx->trace_callback(ctx, NULL, ctx->trace_callback_data); #endif + k5_plugin_free_context(ctx); + ctx->magic = 0; free(ctx); } diff --git a/src/lib/krb5/krb/plugin.c b/src/lib/krb5/krb/plugin.c new file mode 100644 index 0000000000..aa7452edcd --- /dev/null +++ b/src/lib/krb5/krb/plugin.c @@ -0,0 +1,368 @@ +/* -*- 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; + } +} diff --git a/src/lib/krb5/libkrb5.exports b/src/lib/krb5/libkrb5.exports index af661edcc5..57e5c45de2 100644 --- a/src/lib/krb5/libkrb5.exports +++ b/src/lib/krb5/libkrb5.exports @@ -104,6 +104,10 @@ initialize_kdb5_error_table initialize_krb5_error_table initialize_kv5m_error_table initialize_prof_error_table +k5_plugin_free_modules +k5_plugin_load +k5_plugin_load_all +k5_plugin_register krb524_convert_creds_kdc krb524_init_ets krb5_425_conv_principal