logo_kerberos.gif

Difference between revisions of "User:TomYu/Plugin support improvements"

From K5Wiki
Jump to: navigation, search
(copy project page)
 
 
(5 intermediate revisions by the same user not shown)
Line 1: Line 1:
{{project-early}}
 
  +
The MIT Kerberos implementation has a number of places where modular extensibility would facilitate development and deployment of new technologies. Developers of the core code and authors of extension code have an interest in making it easier for third parties to extend the code in a well-compartmentalized way. Administrators of Kerberos deployments and packagers of software have an interest in making it easy to deploy new capabilities in a simple way.
   
This page contains some notes about improving the plugin infrastructure, but does not make a specific proposal yet.
 
  +
== Priorities ==
   
Some possibly useful ideas at [http://www.drdobbs.com/cpp/204202899 Dr Dobb's]. It's rather biased toward C++, and we may disagree with some of the details.
 
  +
# Allow third parties to implement multiple plugin modules for each pluggable interface
  +
# Allow a plugin module to build as dynamic or built-in from the same source code
  +
# Allow third parties to more easily create new plugin modules
  +
# Provide a uniform method for configuring discovery of plugin modules
  +
# Improve readability of code that calls pluggable interfaces
  +
# Allow easier creation of new pluggable interfaces
  +
# Allow incremental transition of existing pluggable interfaces to the new framework
   
==Motivation ==
 
  +
== Deliverables for release 1.9 ==
   
Have you tried to write a new plugin for Kerberos? What was your experience? Your answer to this question is the motivation for this project.
 
  +
Create a plugin framework and pluggable interfaces that can support password strength and password synchronization plugin modules. These should support the capabilities of two existing extensions written by Russ Allbery -- krb5-strength and krb5-sync. The framework is subject to change in the future, so it doesn't have to accommmodate all eventualities, but we will have a goal of not painting ourselves into a corner with respect to reasonably plausible future requirements.
We set up our goal on creating a framework that
 
   
* Facilitates simple and clear additions of new plugin interfaces and new implementations of the existing plugins;
 
  +
== Requirements ==
* Handles both built-in and dynamic plugins;
 
* Allows multiple implementation of the same plugin interface;
 
* Provides uniform way to supply parameters for plugin configuration;
 
   
  +
* thread safety
  +
* minimal configuration required
  +
:* no explicit configuration needed to use built-in modules in their default modes
  +
:* no explicit configuration needed to use loadable modules if the files containing them are located in the default directory for that kind of module
  +
* allow changing of default directory base for loadable plugin modules of all pluggable interfaces
  +
* any resources allocated by a plugin module must be released by that module and by no other code
   
== Definitions ==
+
== Components ==
   
; pluggable interface: an (internal) interface that can be implemented by a third party. These can be one-to-one, or one-to-many. An example of one-to-one is the DAL, and an example of one-to-many is preauth.
 
  +
The plugin framework consists of a domain-independent (not specific to a particular pluggable interface) part and domain-specific parts. There will be a number of pluggable interfaces, each encapsulating a set of related capabilities in a way that allows third parties to implement the interfaces easily.
   
; module: a unit of code that implements a pluggable interface. It can be built in, or it can be dynamically loadable.
 
  +
; domain-independent:
:; built-in: a module whose executable code is located within the library shared object or executable program file, or behaves as if it were. (Dependent library shared objects of the calling library can contain "built-in" modules for the calling library.) The distinguishing feature of a built-in module is that, as part of program startup, the operating system automatically maps the executable code of the module into the address space of the process that calls it, without any explicit action by the library or program.
 
  +
:; plugin manager: generic plugin registry and support services
:; dynamically loaded: a module whose executable code is located within a file that is distinct from the library or program that calls it. The plugin support framework uses the runtime linker (or equivalent) to explicitly map the executable code of the module into the process address space.
 
  +
:; provider interface: the generic interface that a plugin module implements to interact with the plugin manager
  +
; domain-specific: this includes the pluggable interfaces
  +
:; consumer interface: the interface that a user of a plugin accesses
  +
:; provider interface: the interface that a plugin module implements to interact with the consumer
   
; discovery: process of enumerating what modules are available for a pluggable interface. Includes possible filtering of the raw discovered set.
 
  +
A consumer of a pluggable interface initializes the plugin manager, usually implicitly via <code>krb5_init_context()</code>, and uses the consumer interface to access the services of the plugin moudle. The bulk of the consumer interface is a set of ordinary C functions that takes an opaque plugin module handle as a parameter (which may be provided implicitly, depending on interface). Typically the consumer obtains an opaque plugin module handle by calling a domain-specific plugin loader, which is part of the consumer interface.
:* compiled-in
 
:* directory scan
 
:* explicit inclusion by configuration
 
:* explicit exclusion by configuration
 
   
; loading: the process of making modules available for calling. This can involve dynamically loading a module using the runtime linker, or it can involve registering a vtable provided by an application.
 
  +
The provider interface has both a domain-independent part and a domain-specific part. The plugin manager interacts with the domain-independent part of a plugin module, while the consumer interface implementation interacts with the domain-specific part of a plugin module. The same plugin module source code can be built as either a built-in or dynamically loaded module.
:* built-in
 
:* dynamic loading
 
:* application-registered
 
   
; selection: the process of a caller invoking one specific module from the set of loaded modules that implement an interface.
 
  +
* pluggable interface (PLIF)
  +
** consumer interface
  +
** provider interface
  +
* plugin manager (PLM)
   
  +
=== Pluggable interface ===
   
==Structure==
 
  +
A pluggable interface is an interface, possibly internal to a library, that can be implemented by a third party in a modular, well-compartmentalized manner. These implementations of pluggable interfaces are called plugin modules. Pluggable interfaces allow a consumer to use the capabilities of the interface without needing to be aware of the implementation details. In particular, a pluggable interface prevents the consumer from needing to know whether the module is a built-in or a dynamically loadable module. Pluggable interfaces can be one-to-one, or one-to-many. An example of one-to-one is the DAL, and an example of one-to-many is preauth.
[[Image:Slide_v2.jpg]]
 
   
==Participants==
 
  +
A pluggable interface has two parts: a consumer interface and a provider interface. Typically, library code implements the consumer interface, and application code or other library code calls the functions of the consumer interface. The provider interface is what the plugin module implements.
   
* Plugin Manager ('''PM''') - Abstract module that declares interface for operations that manage plugin configuration and registry services;
 
  +
==== Consumer interface ====
* Plugin Manager Implementation ('''PMI''') - Implements the operations for plugin configuration and registry services;
 
* Plugin Loader ('''PL''') - Abstract module that declares an interface for operations that create abstract plugin objects. It knows about:
 
:: Set of available implementations;
 
:: How to create them;
 
:: We might want to have a separate loader function for each pluggable interface, for type safety;
 
* Plugin Loader Implementation ('''PLI''') - Concrete implementation of PL;
 
* Plugin Interface ('''PI''') - Abstract module that declares plugin interface.
 
* Plugin Interface Implementation ('''PII''') - Concrete implementation of the plugin interface;
 
* Caller - Plugin caller.
 
   
  +
The consumer interface isolates the consumer from implementation details of the pluggable interface. The consumer does not generally need to know about whether a given module is built-in or dynamically loaded. The implementation of a consumer interface is essentially a glue layer, and can make use of domain-independent (not specific to any pluggable interface) capabilities of the plugin framework. The consumer might explicitly register a new plugin module that it implements: this capability is part of the plugin manager.
   
==Collaboration==
 
  +
A consumer of a pluggable interface uses an opaque handle, obtained from a loader function that is part of the pluggable interface, to call the methods of a plugin module. Each method of the consumer interface is an ordinary C function that takes the opaque handle either explicitly as its first argument or implicitly by some means such as a module name. Conceptually, these pluggable interface functions are wrapper functions that call through function pointers contained in the opaque plugin module handle object. (though this is not the only possible implementation)
   
* An instance ''pl_manager'' of a particular PMI is created as part of ''krb5_init_context'' operation at run-time (1). This instance will use methods specific to PMI to configure and register the desired plugin implementations;
 
  +
A handle can represent:
* The list of the desired plugin implementations is known to the specific PLI which is aggregated with PMI (2);
 
* PMI defers acquiring of plugin interfaces to PLI (3);
 
* The client uses ''pl_manager'' to invoke the desired plugin interface (4).
 
   
  +
* the plugin module itself
  +
* a resource to which the plugin module provides access (e.g., a ccache handle)
  +
* a set of plugin modules (e.g., the set of all available preauth mechanisms)
   
==User Interface==
 
  +
One rationale for using wrapper functions instead of having the consumer directly invoke methods through a function pointer is to make it easier for debuggers and analysis tools to recognize when a particular interface method is being called. (Function pointers might have identifier names that look nothing like the actual name of the function they point to, in addition to enabling confusing aliasing.)
   
; plhandle plugin_manager_get_service (plugin_manager* instance,const char*, const int);
 
  +
The loader function is specific to the pluggable interface. One reason is for type safety: there will be a distinct opaque handle type for each pluggable interface, allowing compile-time checking to catch some sorts of programming errors. Another reason is backward compatibility: it allows a pluggable interface to support plugin modules that implement an older provider interface.
; int plugin_manager_configure (plugin_manager* instance,const char*);
 
; void plugin_manager_start (plugin_manager* instance);
 
; void plugin_manager_stop (plugin_manager* instance);
 
   
; void get_loader_content (loader_handle handle, const char* container[]);
 
  +
==== Provider interface ====
; plhandle create_api (loader_handle handle, const char* plugin_name);
 
   
  +
A plugin module is a unit of code that implements the provider interface portion of a pluggable interface. Plugin modules can be built in or dynamically loaded. Several alternatives exist for the form of the provider interface, but some have significant advantages in allowing the plugin module to use identical source code for both built-in and loadable modules.
   
==Implementation==
 
  +
A built-in module is a module whose executable code is located within the library shared object or executable program file, or behaves as if it were. (While separate library shared objects that the calling library depends on can contain "built-in" modules for the calling library, this can cause problems with cyclic references.) The distinguishing characteristic of a built-in module is that, as part of program startup, the operating system automatically maps the executable code of the module into the address space of the process that calls it, without any explicit action by the library or program.
   
; Proof of concept code is available from ''plugins'' branch: ''svn+ssh://svn.mit.edu/krb5/branches/plugins''
 
  +
A dynamically loaded module is a module whose executable code is located within a file that is distinct from the library or program that calls it. The plugin support framework uses the runtime linker (or equivalent) to explicitly map the executable code of the module into the process address space. In POSIX systems, this is typically done using <code>dlopen()</code>.
   
; k5-int.h: ''plugin_manager *pl_manager'' is added to _krb5_context
 
  +
===== Loadable module provider interface =====
   
; PMI vs PLI: In general a particular PMI is tied to one or more PLIs. In its turn, each PLI is responsible for loading one or more plugins. The notion of the multiple plugins per PLI is justified by the convenience of the plugin grouping based on their relativity.
 
  +
The domain-independent part of the provider interface of a loadable module consists of a single exported function symbol, which denotes the vtable constructor function for that module. The signature of the constructor is specific to the pluggable interface the module implements. The contents of the vtable are also specific to the pluggable interface.
   
Implementation must be MT safe
 
  +
The constructor usually takes as arguments an interface version number, a pointer to a caller-allocated vtable structure for the interface, and the size of the structure (as an added precaution against version mismatches). The name of the function symbol is constructed from the name of the pluggable interface and the name of the plugin module. (Alternatively, all plugin modules implementing a given interface have the same name for the symbol, but see the section on built-in modules for why this might be a bad idea.)
   
  +
Although the caller (actually the plugin support code) allocates the vtable structure in the above description, one alternative is to have the module perform the allocation of the structure itself. This can cause problems if the module uses a different memory allocator than the caller.
   
==Consequences==
 
  +
Another alternative is to have the vtable constructor instead return a pointer to a compiled-in vtable. This might cause performance problems related to copy relocations.
   
The management details, such as configuration and loading, are hidden from the plugin writers and callers.
 
  +
===== Built-in module provider interface =====
   
* The responsibility of the writer of the new plugin interface (PI) is limited to the writing source and header as described at Template_1:
 
  +
A built-in module provides the same interface as a loadable module. In the alternative where we use same exported function symbol for each loadable module implementing a pluggable interface, the built-in modules will still need distinct prefixes for each vtable constructor function. This requires slightly different source code for a built-in module versus a loadable module, or a technique like using macros to do renaming.
   
''Template 1''
 
  +
===== Alternative: one symbol per method =====
 
#include <plugin_manager.h>
 
#include <k5-int.h>
 
typedef struct {
 
int version;
 
krb5_error_code (*fn1)(krb5_context);
 
} plugin_PLName;
 
 
krb5_error_code plugin_PLName_fn1(plhandle handle, krb5_context)
 
{
 
plugin_PLName* api = (plugin_PLName*) handle.api;
 
api->plugin_PLName_fn1(context);
 
return 0;
 
}
 
   
* The responsibility of the writer of the new plugin interface implementation (PII) is limited to the writing source and header as described at Template_2 and the notifying the plugin loader about its availability Template_3:
 
  +
Exporting one symbol per method in a loadable module is an alternative that might require less effort from a module author, as it does not require that a vtable be part of the provider interface. Built-in modules would probably still require a vtable (though that could be provided by the implementation of the consumer interface).
   
''Template 2''
 
  +
This requires the consumer interface implementation do to many more <code>dlsym()</code> operations or equivalent, which can be expensive on some platforms. (This is in addition to the relocation overhead, which would probably have to happen anyway.) Also, searching for a missing symbol could search through every dependency library of the loaded module, at least on some platforms. If the loadable module has many dependencies, or some of these dependencies have huge symbol tables, this could affect performance even more. Some object file formats lack a dynamic symbol hash table (Mach-O is one; some ECOFF or XCOFF file formats might also), making symbol lookups even more expensive.
 
static krb5_error_code
 
plugin_PLName_fn1(krb5_context ctx, krb5_data *data)
 
{ /*intended functionality */}
 
 
static void
 
plugin_PLName_cleanup(plugin_PLName* api)
 
{ /* do some cleanup */ }
 
 
static krb5_error_code
 
plugin_PLName_init(void)
 
{ /* do some init */ }
 
 
plhandle plugin_PLName_PLImpl_create()
 
{
 
plhandle handle;
 
plugin_PLName* api = malloc(sizeof(plugin_prng));
 
api->version = 0;
 
api->fn1 = plugin_PLName_fn1;
 
api->PLName_init = plugin_PLName_init;
 
api->PLName_cleanup = plugin_PLName_cleanup;
 
handle.api = api;
 
return handle;
 
}
 
   
''Template 3''
 
  +
===== Alternative: loader with fixed module set =====
 
static plugin_descr _table[] = {
 
{"plugin_PLImpl_PLName", plugin_PLName_PLImpl_create},
 
};
 
   
''For better readability some of the verification, clean-up, error handling code is omitted from the templates''
 
  +
This approach uses a slightly different definition for "loader". A loader is a function that acts like a plugin registry that knows about a fixed, predetermined set of plugin modules. Each module in this set may implement a different pluggable interface. Typically, a built-in loader function knows about all of the built-in modules, regardless of what interface each module implements. A collection of modules (possibly implementing different pluggable interfaces) can be combined in a single dynamically loaded shared object that only exports the loader function interface. To search for a dynamically loaded plugin module, the plugin framework would load each of these shared objects and call the loader function of each one until it located the desired module.
   
  +
=== Plugin manager ===
   
==Sample Code==
 
  +
The plugin manager (PLM) provides a set of generic support capabilities that are independent of individual pluggable interfaces. It centralizes the discovery process for plugin modules. Typically, consumers of pluggable interfaces do not call it directly, but instead call a loader function of the specific pluggable interface, which in turn calls the plugin manager.
   
===Plugin initialization===
 
  +
The <code>krb5_init_context()</code> functions will create and configure a plugin manager context that will exist in the krb5_context An consumer application may use <code>krb5_get_plm()</code> to retrieve the plugin manager context so it can register its own built-in plugin modules. Libraries such as libkadm5 register their own built-in plugin modules this way too.
The following is an example of the plugin initialization:
 
 
plugin_default_manager_get_instance(&plugin_mngr_instance);
 
plugin_manager_configure(ctx->pl_manager, conf_path);
 
plugin_manager_start(ctx->pl_manager);
 
   
===How to call plugins===
 
  +
The plugin manager locates plugin modules using both a numeric identifer that designates a pluggable interface and a string that names a module that implements that pluggable interface. The primary way to use the plugin manager is to query it for the vtable constructor for a specified module (or a set of vtable constructors for all modules of that interface).
The following is an example of invoking Password Quality plugin:
 
   
plhandle plugin_handle;
 
  +
As a lower-level interface that supports backward compatibility for older loadable-module provider interfaces, the plugin manager can create an opaque locator handle for a specified module. The plugin manager also supports looking up sets of locator handles (for all modules implementing a specified pluggable interface).
plugin_handle = plugin_manager_get_service(ctx->pl_manager, "plugin_pwd_qlty", PWD_QLTY_KRB);
 
plugin_pwd_qlty_check(ctx->plugin_manager, srv_handle, password, use_policy, pol, principal);
 
   
===How to construct plugin interface (PI)===
 
  +
Additional functions in the plugin manager interface allow for lookups of symbols in the plugin module (if supported by that module)
The following is an example of plugin interface called "password quality":
 
   
typedef struct {
 
  +
==== Registry of built-in modules ====
int version;
 
kadm5_ret_t (*pwd_qlty_init)(kadm5_server_handle_t);
 
void (*pwd_qlty_cleanup)();
 
kadm5_ret_t (*pwd_qlty_check)(kadm5_server_handle_t, char*, int, kadm5_policy_ent_t, krb5_principal);
 
} plugin_pwd_qlty;
 
 
kadm5_ret_t
 
plugin_pwd_qlty_check( plhandle handle, kadm5_server_handle_t srv_handle, char *password, int use_policy, kadm5_policy_ent_t pol, krb5_principal principal)
 
{
 
plugin_pwd_qlty* api = (plugin_pwd_qlty*) handle.api;
 
api->pwd_qlty_check(srv_handle, password, use_policy, pol, principal);
 
}
 
 
kadm5_ret_t
 
plugin_pwd_qlty_init(plhandle, kadm5_server_handle_t)
 
{ /* do some init */ }
 
 
void
 
plugin_pwd_qlty_cleanup(plhandle)
 
{ /* do some clean-up */ }
 
   
===How to construct plugin interface implementation (PII)===
 
  +
This registry keeps track of built-in modules. Typically, libkrb5 will initialize this with locators for all of the built-in modules that are linked into it. Applications can also register private built-in plugin modules using this registry.
Every PII should have only one public ''create'' function. For PII called "password quality krb" it would be ''plhandle plugin_pwd_qlty_krb_create()''.The following is an example of the this PII:
 
   
plhandle plugin_pwd_qlty_krb_create()
 
  +
==== Registry of loadable modules ====
{
 
plhandle handle;
 
plugin_pwd_qlty* api = malloc(sizeof(plugin_pwd_qlty));
 
api->version = 1;
 
api->pwd_qlty_init = plugin_pwd_qlty_init;
 
api->pwd_qlty_check = plugin_pwd_qlty_check;
 
api->pwd_qlty_cleanup = plugin_pwd_qlty_clean;
 
handle.api = api;
 
return handle;
 
}
 
 
static kadm5_ret_t
 
plugin_pwd_qlty_check(kadm5_server_handle_t srv_handle, char *password, int use_policy, kadm5_policy_ent_t pol, krb5_principal principal)
 
{ /* do some quality verification */ }
 
 
static kadm5_ret_t
 
plugin_pwd_qlty_init(kadm5_server_handle_t handle)
 
{ /* do some initialization */ }
 
 
static kadm5_ret_t
 
plugin_pwd_qlty_cleanup()
 
{ /* do some cleanup */ }
 
   
''For better readability some of the verification, clean-up, error handling code is omitted from the samples''
 
  +
This registry keeps track of a few additional items needed for locating loadable modules. This includes a base directory pathname to a directory tree of plugin modules. Each subdirectory of the base directory has a name that corresponds to the pluggable interface identifier for the modules that intended to be in that subdirectory. Caching of vtable constructors of previously-loaded dynamically loadable modules can occur.
   
  +
== Interactions ==
   
==Built-in Plugins==
 
  +
=== Startup ===
   
*; Examples: PreAuth, AuthData, Password Quality (see ''Current plugins'' section for details)
 
  +
The <code>krb5_init_context()</code> function initializes a plugin manager context that resides in the krb5_context. It registers libkrb5 built-in modules (from static tables), and reads location parameters of dynamically-loaded plugin modules (if any) from the <code>krb5.conf</code> (or other configuration profile), calling plugin manager methods to configure the locations of dynamically-loaded plugin modules.
   
*; Invoking multiple implementations of the same plugin: This is achieved by using the aimed ''plugin_id'''s in ''plugin_manager_get_service'' calls. For example, two specific implementations of the password quality plugins may be invoked in password check. Alternatively, one can use '''all available''' plugin implementations as it is done for preauthentication.
 
  +
=== Consumer ===
   
  +
The consumer calls the plugin loader function for the desired pluggable interface. The loader function calls the plugin manager to retrieve the vtable constructor function for the appropriate module and builds an opaque module handle to give to the consumer. The plugin manager looks up the vtable constructor to give to the loader function of the pluggable interface. The consumer calls wrapper functions of the pluggable interface, passing the opaque module handle, to access the capabilities of the pluggable interface.
   
==Dynamic Plugins==
 
  +
=== Adding new modules at run time ===
   
*; Examples: Audit, Password sync
 
  +
An application can register its own plugin modules at run time by calling the plugin manager function to update the registry.
   
*; Requirements:
 
  +
=== Backward compatibility ===
# The difference between dynamic and built-in plugins should be on the level of linkage. Their codebase should be identical.
 
# Plugin consumer should not have the knowledge about the type of plugins it is using (in terms of dynamic/built-in). On the caller side the code invoking plugin should stay the same for both dynamic and built-in plugins.
 
# Similar to built-in plugins, the plugin consumer may call the multiple implementations of the same plugin interface. For example, two implementations of the audit plugin may be used to handle password change and intruder lockout events.
 
# It is responsibility of the administrator to point to the location of the dynamic plugin as part of the configuration.
 
   
*; Creating dynamic plugin: The appropriate loader is linked with the set of the desired plugins resulting into the shared library. Details:
 
  +
Older dynamically loadable modules that don't conform to the "vtable constructor function" provider model can be handled with specialized loader functions. If a pluggable interface needs to support a legacy loadable module of this sort, it can contain a more elaborate loader function that uses the lower-level capabilities of the plugin manager to:
# One of the deliverables of the plugin framework is the loader file. Its responsibility is to load modules that are listed in its local table;
 
# The dynamic plugin creator must fill this table with the list of the desired plugins.
 
# The dynamic plugin creator then links the loader with the set of the desired plugins resulting into the shared library;
 
# Administrator points to the location of the dynamic plugin by setting ''plugin_loader_path'' configuration attribute to this library.
 
   
''The following is the example of the table in the loader:''
 
  +
* obtain an opaque locator handle for a module (or set of modules)
static plugin_descr plugin_dyn_factory_table[] = {
 
  +
* look up data or function symbols from a module locator handle (or set of locator handles)
{"plugin_pwd_qlty_DYN", plugin_pwd_qlty_DYN_create},
 
{"plugin_pwd_qlty_DYN_X", plugin_pwd_qlty_DYN_X_create},
 
{NULL,NULL}
 
};
 
   
  +
== Configuration ==
   
*; Invoking dynamic plugin: Similar to builtin plugins with the following additions. The option ''plugin_loader_path'' in the configuration file refers to the location of the dynamic plugin shared library and the option ''plugin_loader_type'' must be set to ''dynamic''.
 
  +
Configuration takes the form of a "plugins" section in the krb5.conf profile. The user can override the base plugin directory (which defaults to $prefix/lib/krb5/plugins) to allow for testing. The user can also add additional directories to search on a per-interface basis.
   
  +
<pre>
  +
[plugins]
  +
# override base plugin directory -- defaults to $prefix
  +
basedir = /usr/local/testinstall/lib/krb5/plugins
   
==Configuration==
 
  +
# config section for "pwqual" -- password quality interface
  +
pwqual = {
  +
dir = pwqual_foo_corp # adds a new directory to search
  +
disable = pwqual_bogus # disable a specific module
  +
disable = pwqual_bogus2 # disable another specific module
  +
modules = {
  +
# explicit file locations of user-installed modules
  +
pwqual_cracklib = /tmp/pwqual_cracklib.so
  +
pwqual_random_corp = /usr/local/pwqual_random_corp.so
  +
}
  +
}
   
===Configuration file attributes===
 
  +
# config section for "ccache" interface
:: ''plugin list'' - list of the desired plugins;
 
  +
ccache = {
:: ''plugin_api'' - name of the plugin interface;
 
  +
# ...
:: ''plugin_name'' - concrete plugin implementation;
 
  +
}
:: ''plugin_id'' - the numeric id that plugin implementation is associated with. Used by the caller;
 
  +
</pre>
:: ''plugin_loader_name'' - name of the loader the particular implementation is associated with;
 
:: ''plugin_loader_type'' - defines the plugin type: built-in or dynamic;
 
:: ''plugin_loader_path'' - path to the plugin dynamic library
 
   
===Example of the plugin section in krb5.conf===
 
  +
== Open questions ==
[plugins]
 
/* built-in plugin */
 
plugin_list = PQ1
 
PQ1 = {
 
plugin_api = plugin_pwd_qlty
 
plugin_loader_name = plugin_default_factory
 
plugin_loader_type = static
 
plugin_name = plugin_pwd_qlty_krb
 
plugin_id = 1
 
}
 
 
/* dynamic plugin */
 
plugin_list = PQ_DYN
 
PQ_DYN = {
 
plugin_api = plugin_pwd_qlty
 
plugin_floader_name = plugin_dyn_factory
 
plugin_loader_type = dynamic
 
plugin_name = plugin_pwd_qlty_DYN
 
plugin_loader_path = /path-to-the-lib/libplugin_dynamic.so
 
plugin_id = 33
 
}
 
   
===Alternatives===
 
  +
* Should the initial implementation include any of the backward compatibility support for older dynamically loadable modules?
The choice of plugin configuration format is a matter of taste. It may be presented in XML, YAML, JSON etc. One needs only an appropriate parser.
 
   
===Configuration change at run-time===
 
  +
== Details ==
(...)
 
   
  +
Deallocator functions, etc. omitted for conciseness.
   
==Directory Structure==
 
  +
'''NEEDS MORE DETAIL'''
   
*;src/plugin_core: plugin_manager.[ch], plugin_loader.[ch], impl/plugin_XXX_manager.[ch], impl/plugin_XXX_loader.[ch]
 
  +
=== Plugin manager details ===
*;src/plugins: pluginA/plugin_A.[ch], pluginA/plugin_A_implN/plugin_A_implN.[ch]
 
*;src/plugin_dynamic: plugin_dyn_factory.[ch]
 
   
  +
The following functions are meant to be used by a consumer of pluggable interfaces:
   
==Defaults==
 
  +
; <code>k5_plm_init</code>: initialize a plugin manager context
  +
; <code>k5_plm_fini</code>: shut down a plugin manager context
   
(...)
 
  +
; <code>k5_plm_add_vtfn</code>: register a vtable constructor (e.g., for a built-in module)
  +
; <code>k5_plm_set_basedir</code>: set the base directory for dynamic plugin modules
  +
; <code>k5_plm_set_plif_path</code>: set the search path for dynamic plugin modules of a specific interface
   
  +
The following functions are meant to be used by a loader function of a pluggable interface:
   
==Build process==
 
  +
; <code>k5_plm_get_vtfn</code>: get a vtable constructor
  +
; <code>k5_plm_get_vtfn_set</code>: get a set of vtable constructors
   
*; Options: DEBUG_PLUGINS - output more info for debugging
 
  +
The following functions are meant to be used by a loader function that needs to do lower-level operations (particularly for backward compatibility with older loadable modules). They are typically not implemented by registries that only track built-in modules:
   
  +
; <code>k5_plm_get_ploc</code>: retrieve a module locator handle
  +
; <code>k5_plm_get_ploc_set</code>: retrieve a set of module locator handles
  +
; <code>k5_ploc_get_vtfn</code>: get a vtable constructor function from a module locator
  +
; <code>k5_ploc_get_data</code>: get a data symbol from a module locator
  +
; <code>k5_ploc_get_func</code>: get a function symbol from a module locator
  +
; <code>k5_ploc_get_vtfn_set</code>: get a set of vtable constructor functions from a module locator set
  +
; <code>k5_ploc_get_data_set</code>: get a set of data symbol from a module locator set
  +
; <code>k5_ploc_get_func_set</code>: get a set of function symbol from a module locator set
   
  +
----
   
  +
== Old background ==
   
==Current plugins==
 
  +
for reference
  +
  +
=== Current plugins ===
   
 
We currently have the following plugin frameworks:
 
We currently have the following plugin frameworks:
Line 335: Line 235:
 
Plugin frameworks which are "not exposed" may still be productively used by vendor forks of the krb5 tree.
 
Plugin frameworks which are "not exposed" may still be productively used by vendor forks of the krb5 tree.
   
 
  +
=== Future plugins ===
==Future plugins==
 
   
 
The following areas are candidates for future plugin support:
 
The following areas are candidates for future plugin support:
Line 348: Line 247:
 
* password synchronization
 
* password synchronization
   
==Current support infrastructure==
+
=== Current support infrastructure ===
   
 
In libkrb5support, we have functions to facilitate loading plugins from shared objects. There is a set of functions to load individual plugins from named files and mechglue; these are currently used by the HDB bridge and GSS mechglue:
 
In libkrb5support, we have functions to facilitate loading plugins from shared objects. There is a set of functions to load individual plugins from named files and mechglue; these are currently used by the HDB bridge and GSS mechglue:
Line 366: Line 265:
 
* krb5int_free_plugin_dir_func - Free a list of function objects returned by krb5int_get_plugin_dir_func
 
* krb5int_free_plugin_dir_func - Free a list of function objects returned by krb5int_get_plugin_dir_func
   
==Problem areas==
+
=== Problem areas ===
   
 
* Every caller of krb5int_open_plugin_dirs specifies either no filebases (e.g. preauth plugins) or a single filebase (KDB plugins). Accepting and processing a list of filebases is probably needless complexity.
 
* Every caller of krb5int_open_plugin_dirs specifies either no filebases (e.g. preauth plugins) or a single filebase (KDB plugins). Accepting and processing a list of filebases is probably needless complexity.
Line 381: Line 280:
   
 
* In some scenarios such as embedded environments, it may be more useful to allow applications to supply plugin vtables via an API (as we do for keytabs and ccaches, though those APIs are not public) than to load them from shared objects in the filesystem.
 
* In some scenarios such as embedded environments, it may be more useful to allow applications to supply plugin vtables via an API (as we do for keytabs and ccaches, though those APIs are not public) than to load them from shared objects in the filesystem.
  +
  +
== Definitions ==
  +
  +
; pluggable interface: an (internal) interface that can be implemented by a third party. These can be one-to-one, or one-to-many. An example of one-to-one is the DAL, and an example of one-to-many is preauth.
  +
  +
; module: a unit of code that implements a pluggable interface. It can be built in, or it can be dynamically loadable.
  +
:; built-in: a module whose executable code is located within the library shared object or executable program file, or behaves as if it were. (While separate library shared objects that the calling library depends on can contain "built-in" modules for the calling library, this can cause problems with cyclic references.) The distinguishing characteristic of a built-in module is that, as part of program startup, the operating system automatically maps the executable code of the module into the address space of the process that calls it, without any explicit action by the library or program.
  +
:; dynamically loaded: a module whose executable code is located within a file that is distinct from the library or program that calls it. The plugin support framework uses the runtime linker (or equivalent) to explicitly map the executable code of the module into the process address space. In POSIX systems, this is typically done using <code>dlopen()</code>.
  +
  +
; discovery: process of enumerating what modules are available for a pluggable interface. Includes possible filtering of the raw discovered set.
  +
:* compiled-in
  +
:* directory scan
  +
:* explicit inclusion by configuration
  +
:* explicit exclusion by configuration
  +
  +
; loading: the process of making modules available for calling. This can involve dynamically loading a module using the runtime linker, or it can involve registering a vtable provided by an application.
  +
:* built-in
  +
:* dynamic loading
  +
:* application-registered
  +
  +
; selection: the process of a caller invoking one specific module from the set of loaded modules that implement an interface.
  +
  +
; consumer interface: the interface that a caller uses to access the services of a pluggable interface. Typically, but not always, the krb5 library implements the consumer interface.
  +
  +
; provider interface: the interface that a module author implements
  +
  +
== Links ==
  +
  +
Some possibly useful ideas at [http://www.drdobbs.com/cpp/204202899 Dr Dobb's]. It's rather biased toward C++, and we may disagree with some of the details. Among other things, it advocates a greater separation of the domain-specific interface of a plugin module from its domain-independent interface: plugin modules get passed a handle for the "registry services" of the plugin manager, and the modules must call those services to register their objects or methods.
  +
  +
A paper by Ulrich Drepper, [http://people.redhat.com/drepper/dsohowto.pdf How To Write Shared Libraries (PDF)], contains some useful information, mostly from a Linux / glibc perspective. It includes analysis of the performance implications of various aspects of using shared libraries.
  +
  +
The Sun [http://docs.sun.com/app/docs/doc/817-1984?l=en Linker and Libraries Guide] includes many useful details about the ELF object file format and the functioning of ELF shared libraries.

Latest revision as of 14:58, 3 August 2010

The MIT Kerberos implementation has a number of places where modular extensibility would facilitate development and deployment of new technologies. Developers of the core code and authors of extension code have an interest in making it easier for third parties to extend the code in a well-compartmentalized way. Administrators of Kerberos deployments and packagers of software have an interest in making it easy to deploy new capabilities in a simple way.

Priorities

  1. Allow third parties to implement multiple plugin modules for each pluggable interface
  2. Allow a plugin module to build as dynamic or built-in from the same source code
  3. Allow third parties to more easily create new plugin modules
  4. Provide a uniform method for configuring discovery of plugin modules
  5. Improve readability of code that calls pluggable interfaces
  6. Allow easier creation of new pluggable interfaces
  7. Allow incremental transition of existing pluggable interfaces to the new framework

Deliverables for release 1.9

Create a plugin framework and pluggable interfaces that can support password strength and password synchronization plugin modules. These should support the capabilities of two existing extensions written by Russ Allbery -- krb5-strength and krb5-sync. The framework is subject to change in the future, so it doesn't have to accommmodate all eventualities, but we will have a goal of not painting ourselves into a corner with respect to reasonably plausible future requirements.

Requirements

  • thread safety
  • minimal configuration required
  • no explicit configuration needed to use built-in modules in their default modes
  • no explicit configuration needed to use loadable modules if the files containing them are located in the default directory for that kind of module
  • allow changing of default directory base for loadable plugin modules of all pluggable interfaces
  • any resources allocated by a plugin module must be released by that module and by no other code

Components

The plugin framework consists of a domain-independent (not specific to a particular pluggable interface) part and domain-specific parts. There will be a number of pluggable interfaces, each encapsulating a set of related capabilities in a way that allows third parties to implement the interfaces easily.

domain-independent
plugin manager
generic plugin registry and support services
provider interface
the generic interface that a plugin module implements to interact with the plugin manager
domain-specific
this includes the pluggable interfaces
consumer interface
the interface that a user of a plugin accesses
provider interface
the interface that a plugin module implements to interact with the consumer

A consumer of a pluggable interface initializes the plugin manager, usually implicitly via krb5_init_context(), and uses the consumer interface to access the services of the plugin moudle. The bulk of the consumer interface is a set of ordinary C functions that takes an opaque plugin module handle as a parameter (which may be provided implicitly, depending on interface). Typically the consumer obtains an opaque plugin module handle by calling a domain-specific plugin loader, which is part of the consumer interface.

The provider interface has both a domain-independent part and a domain-specific part. The plugin manager interacts with the domain-independent part of a plugin module, while the consumer interface implementation interacts with the domain-specific part of a plugin module. The same plugin module source code can be built as either a built-in or dynamically loaded module.

  • pluggable interface (PLIF)
    • consumer interface
    • provider interface
  • plugin manager (PLM)

Pluggable interface

A pluggable interface is an interface, possibly internal to a library, that can be implemented by a third party in a modular, well-compartmentalized manner. These implementations of pluggable interfaces are called plugin modules. Pluggable interfaces allow a consumer to use the capabilities of the interface without needing to be aware of the implementation details. In particular, a pluggable interface prevents the consumer from needing to know whether the module is a built-in or a dynamically loadable module. Pluggable interfaces can be one-to-one, or one-to-many. An example of one-to-one is the DAL, and an example of one-to-many is preauth.

A pluggable interface has two parts: a consumer interface and a provider interface. Typically, library code implements the consumer interface, and application code or other library code calls the functions of the consumer interface. The provider interface is what the plugin module implements.

Consumer interface

The consumer interface isolates the consumer from implementation details of the pluggable interface. The consumer does not generally need to know about whether a given module is built-in or dynamically loaded. The implementation of a consumer interface is essentially a glue layer, and can make use of domain-independent (not specific to any pluggable interface) capabilities of the plugin framework. The consumer might explicitly register a new plugin module that it implements: this capability is part of the plugin manager.

A consumer of a pluggable interface uses an opaque handle, obtained from a loader function that is part of the pluggable interface, to call the methods of a plugin module. Each method of the consumer interface is an ordinary C function that takes the opaque handle either explicitly as its first argument or implicitly by some means such as a module name. Conceptually, these pluggable interface functions are wrapper functions that call through function pointers contained in the opaque plugin module handle object. (though this is not the only possible implementation)

A handle can represent:

  • the plugin module itself
  • a resource to which the plugin module provides access (e.g., a ccache handle)
  • a set of plugin modules (e.g., the set of all available preauth mechanisms)

One rationale for using wrapper functions instead of having the consumer directly invoke methods through a function pointer is to make it easier for debuggers and analysis tools to recognize when a particular interface method is being called. (Function pointers might have identifier names that look nothing like the actual name of the function they point to, in addition to enabling confusing aliasing.)

The loader function is specific to the pluggable interface. One reason is for type safety: there will be a distinct opaque handle type for each pluggable interface, allowing compile-time checking to catch some sorts of programming errors. Another reason is backward compatibility: it allows a pluggable interface to support plugin modules that implement an older provider interface.

Provider interface

A plugin module is a unit of code that implements the provider interface portion of a pluggable interface. Plugin modules can be built in or dynamically loaded. Several alternatives exist for the form of the provider interface, but some have significant advantages in allowing the plugin module to use identical source code for both built-in and loadable modules.

A built-in module is a module whose executable code is located within the library shared object or executable program file, or behaves as if it were. (While separate library shared objects that the calling library depends on can contain "built-in" modules for the calling library, this can cause problems with cyclic references.) The distinguishing characteristic of a built-in module is that, as part of program startup, the operating system automatically maps the executable code of the module into the address space of the process that calls it, without any explicit action by the library or program.

A dynamically loaded module is a module whose executable code is located within a file that is distinct from the library or program that calls it. The plugin support framework uses the runtime linker (or equivalent) to explicitly map the executable code of the module into the process address space. In POSIX systems, this is typically done using dlopen().

Loadable module provider interface

The domain-independent part of the provider interface of a loadable module consists of a single exported function symbol, which denotes the vtable constructor function for that module. The signature of the constructor is specific to the pluggable interface the module implements. The contents of the vtable are also specific to the pluggable interface.

The constructor usually takes as arguments an interface version number, a pointer to a caller-allocated vtable structure for the interface, and the size of the structure (as an added precaution against version mismatches). The name of the function symbol is constructed from the name of the pluggable interface and the name of the plugin module. (Alternatively, all plugin modules implementing a given interface have the same name for the symbol, but see the section on built-in modules for why this might be a bad idea.)

Although the caller (actually the plugin support code) allocates the vtable structure in the above description, one alternative is to have the module perform the allocation of the structure itself. This can cause problems if the module uses a different memory allocator than the caller.

Another alternative is to have the vtable constructor instead return a pointer to a compiled-in vtable. This might cause performance problems related to copy relocations.

Built-in module provider interface

A built-in module provides the same interface as a loadable module. In the alternative where we use same exported function symbol for each loadable module implementing a pluggable interface, the built-in modules will still need distinct prefixes for each vtable constructor function. This requires slightly different source code for a built-in module versus a loadable module, or a technique like using macros to do renaming.

Alternative: one symbol per method

Exporting one symbol per method in a loadable module is an alternative that might require less effort from a module author, as it does not require that a vtable be part of the provider interface. Built-in modules would probably still require a vtable (though that could be provided by the implementation of the consumer interface).

This requires the consumer interface implementation do to many more dlsym() operations or equivalent, which can be expensive on some platforms. (This is in addition to the relocation overhead, which would probably have to happen anyway.) Also, searching for a missing symbol could search through every dependency library of the loaded module, at least on some platforms. If the loadable module has many dependencies, or some of these dependencies have huge symbol tables, this could affect performance even more. Some object file formats lack a dynamic symbol hash table (Mach-O is one; some ECOFF or XCOFF file formats might also), making symbol lookups even more expensive.

Alternative: loader with fixed module set

This approach uses a slightly different definition for "loader". A loader is a function that acts like a plugin registry that knows about a fixed, predetermined set of plugin modules. Each module in this set may implement a different pluggable interface. Typically, a built-in loader function knows about all of the built-in modules, regardless of what interface each module implements. A collection of modules (possibly implementing different pluggable interfaces) can be combined in a single dynamically loaded shared object that only exports the loader function interface. To search for a dynamically loaded plugin module, the plugin framework would load each of these shared objects and call the loader function of each one until it located the desired module.

Plugin manager

The plugin manager (PLM) provides a set of generic support capabilities that are independent of individual pluggable interfaces. It centralizes the discovery process for plugin modules. Typically, consumers of pluggable interfaces do not call it directly, but instead call a loader function of the specific pluggable interface, which in turn calls the plugin manager.

The krb5_init_context() functions will create and configure a plugin manager context that will exist in the krb5_context An consumer application may use krb5_get_plm() to retrieve the plugin manager context so it can register its own built-in plugin modules. Libraries such as libkadm5 register their own built-in plugin modules this way too.

The plugin manager locates plugin modules using both a numeric identifer that designates a pluggable interface and a string that names a module that implements that pluggable interface. The primary way to use the plugin manager is to query it for the vtable constructor for a specified module (or a set of vtable constructors for all modules of that interface).

As a lower-level interface that supports backward compatibility for older loadable-module provider interfaces, the plugin manager can create an opaque locator handle for a specified module. The plugin manager also supports looking up sets of locator handles (for all modules implementing a specified pluggable interface).

Additional functions in the plugin manager interface allow for lookups of symbols in the plugin module (if supported by that module)

Registry of built-in modules

This registry keeps track of built-in modules. Typically, libkrb5 will initialize this with locators for all of the built-in modules that are linked into it. Applications can also register private built-in plugin modules using this registry.

Registry of loadable modules

This registry keeps track of a few additional items needed for locating loadable modules. This includes a base directory pathname to a directory tree of plugin modules. Each subdirectory of the base directory has a name that corresponds to the pluggable interface identifier for the modules that intended to be in that subdirectory. Caching of vtable constructors of previously-loaded dynamically loadable modules can occur.

Interactions

Startup

The krb5_init_context() function initializes a plugin manager context that resides in the krb5_context. It registers libkrb5 built-in modules (from static tables), and reads location parameters of dynamically-loaded plugin modules (if any) from the krb5.conf (or other configuration profile), calling plugin manager methods to configure the locations of dynamically-loaded plugin modules.

Consumer

The consumer calls the plugin loader function for the desired pluggable interface. The loader function calls the plugin manager to retrieve the vtable constructor function for the appropriate module and builds an opaque module handle to give to the consumer. The plugin manager looks up the vtable constructor to give to the loader function of the pluggable interface. The consumer calls wrapper functions of the pluggable interface, passing the opaque module handle, to access the capabilities of the pluggable interface.

Adding new modules at run time

An application can register its own plugin modules at run time by calling the plugin manager function to update the registry.

Backward compatibility

Older dynamically loadable modules that don't conform to the "vtable constructor function" provider model can be handled with specialized loader functions. If a pluggable interface needs to support a legacy loadable module of this sort, it can contain a more elaborate loader function that uses the lower-level capabilities of the plugin manager to:

  • obtain an opaque locator handle for a module (or set of modules)
  • look up data or function symbols from a module locator handle (or set of locator handles)

Configuration

Configuration takes the form of a "plugins" section in the krb5.conf profile. The user can override the base plugin directory (which defaults to $prefix/lib/krb5/plugins) to allow for testing. The user can also add additional directories to search on a per-interface basis.

[plugins]
    # override base plugin directory -- defaults to $prefix
    basedir = /usr/local/testinstall/lib/krb5/plugins

    # config section for "pwqual" -- password quality interface
    pwqual = {
        dir = pwqual_foo_corp   # adds a new directory to search
        disable = pwqual_bogus  # disable a specific module
        disable = pwqual_bogus2 # disable another specific module
        modules = {
            # explicit file locations of user-installed modules
            pwqual_cracklib = /tmp/pwqual_cracklib.so
            pwqual_random_corp = /usr/local/pwqual_random_corp.so
        }
    }

    # config section for "ccache" interface
    ccache = {
        # ...
    }

Open questions

  • Should the initial implementation include any of the backward compatibility support for older dynamically loadable modules?

Details

Deallocator functions, etc. omitted for conciseness.

NEEDS MORE DETAIL

Plugin manager details

The following functions are meant to be used by a consumer of pluggable interfaces:

k5_plm_init
initialize a plugin manager context
k5_plm_fini
shut down a plugin manager context
k5_plm_add_vtfn
register a vtable constructor (e.g., for a built-in module)
k5_plm_set_basedir
set the base directory for dynamic plugin modules
k5_plm_set_plif_path
set the search path for dynamic plugin modules of a specific interface

The following functions are meant to be used by a loader function of a pluggable interface:

k5_plm_get_vtfn
get a vtable constructor
k5_plm_get_vtfn_set
get a set of vtable constructors

The following functions are meant to be used by a loader function that needs to do lower-level operations (particularly for backward compatibility with older loadable modules). They are typically not implemented by registries that only track built-in modules:

k5_plm_get_ploc
retrieve a module locator handle
k5_plm_get_ploc_set
retrieve a set of module locator handles
k5_ploc_get_vtfn
get a vtable constructor function from a module locator
k5_ploc_get_data
get a data symbol from a module locator
k5_ploc_get_func
get a function symbol from a module locator
k5_ploc_get_vtfn_set
get a set of vtable constructor functions from a module locator set
k5_ploc_get_data_set
get a set of data symbol from a module locator set
k5_ploc_get_func_set
get a set of function symbol from a module locator set

Old background

for reference

Current plugins

We currently have the following plugin frameworks:

  • Preauth: All shared objects from profile-specified or installation directory are loaded. Two vtables are read from the shared objects, one for libkrb5 and one for the KDC. The preauth framework iterates over the module list invoking functions to generate or handle preauth data. Preauth vtable functions receive a callback function and data object which allow it to request information such as the expected enctype or FAST armor key for the request.
  • Authdata: Very similar to the preauth framework.
  • KDB: The profile specifies a database library name for each realm. Shared objects matching the library name are loaded from a profile-specified and installation directory; the first matching object with an appropriately-named vtable data object is used, and the rest are ignored. libkdb5 contains wrappers which invoke functions in the library's vtable, or (for some optional functions) default implementations if the vtable left the function pointer as NULL.
  • KDC location: All shared objects from an installation directory are located. A vtable is read from the shared objects. The KDC location framework iterates over each vtable and invokes a lookup function; modules can return success with a location, an error (which halts the location process), or a distinguished error code which passes control along to the next module or the built-in location mechanisms.
  • GSSAPI: The file /etc/gss/mechs can specify a list of mechanism OIDs and shared object filenames; filenames are taken as relative to an installation directory. Shared objects implementing mechanisms can export either a function returning a vtable, or can export each GSSAPI interface individually.

The following areas of functionality are virtualized but have no exposed plugin framework:

  • Serialization: Serialization table entries can be registered with krb5_register_serializer. Data objects are matched to table entries by magic number. The registration function is exported by libkrb5 and is named with the krb5_ prefix, but it and its associated structure are declared in k5-int.h rather than krb5.h. It is not used outside of libkrb5.
  • ccache: Very similar to serialization, except that ccache implementations are selected using a URL-style prefix in the ccache name.
  • keytab: Very similar to ccache, except that the keytab registration function is used outside of libkrb5 to register a "KDB keytab", which is used by kadmind to serve GSSRPC without requiring a keytab file containing the kadmin keys.
  • Replay cache: Very similar to ccache, except that the replay cache registration function is not used anywhere (even inside libkrb5).

Plugin frameworks which are "not exposed" may still be productively used by vendor forks of the krb5 tree.

Future plugins

The following areas are candidates for future plugin support:

  • PRNG
  • profile / configuration
  • DNS / host-realm mapping
  • password quality policy
  • lockout
  • audit
  • password synchronization

Current support infrastructure

In libkrb5support, we have functions to facilitate loading plugins from shared objects. There is a set of functions to load individual plugins from named files and mechglue; these are currently used by the HDB bridge and GSS mechglue:

  • krb5int_open_plugin - Create a plugin handle from a filename
  • krb5int_close_plugin - Close a plugin handle
  • krb5int_get_plugin_data - Retrieve a data object from a plugin handle by symbol name
  • krb5int_get_plugin_func - Retrieve a function object from a plugin handle by symbol name

There is another set of functions to scan a list of directories for plugins:

  • krb5int_open_plugin_dirs - Create a plugin dir handle from a list of directories and (optionally) filebases
  • krb5int_close_plugin_dirs - Close a plugin dir handle
  • krb5int_get_plugin_dir_data - Retrieve a list of data objects from a plugin dir handle by symbol name
  • krb5int_get_plugin_dir_func - Retrieve a list of function objects from a plugin dir handle by symbol name
  • krb5int_free_plugin_dir_data - Free a list of data objects returned by krb5int_get_plugin_dir_data
  • krb5int_free_plugin_dir_func - Free a list of function objects returned by krb5int_get_plugin_dir_func

Problem areas

  • Every caller of krb5int_open_plugin_dirs specifies either no filebases (e.g. preauth plugins) or a single filebase (KDB plugins). Accepting and processing a list of filebases is probably needless complexity.
  • Callers of krb5int_open_plugin_dirs have to know what directories to supply, which means they need to know the krb5 install root as well as the magic plugin area for OS X, and they need logic for reading a profile variable to determine the alternate plugin directory for the test suite (currently only implemented for KDB and preauth plugins).
  • In most uses of plugins, we read a data object containing a list of function pointers. This makes it mostly impossible to supply a plugin which works with multiple versions of krb5. If we instead read a function object which we invoked with a version number to retrieve the vtable, it would be possible (though perhaps awkward) to create a shared object which works with multiple versions.
  • We are somewhat schizophrenic about how plugins can access krb5 library functionality, and in particular internal symbols. Sometimes we call functions directly, sometimes we make use of a vtable passed into the plugin (e.g. the preauth_get_client_data_proc function), sometimes we use the accessor to invoke internal functions, and sometimes we call APIs or internal functions directly. Ideally we should have a consistent policy with a sound justification.
  • When measuring code coverage with gcov, we cannot use shared libraries; this means we need to link in-tree plugins statically into the libraries or programs which load them. We have an ad-hoc method to do this with KDB plugins, but not with other plugin types.
  • Administrators have an easier time writing scripts than creating linkable shared objects. In some cases it might yield a better administrator experience to create plugin interfaces via subprocesses than loading shared objects, although in many cases this might not be feasible.
  • In some scenarios such as embedded environments, it may be more useful to allow applications to supply plugin vtables via an API (as we do for keytabs and ccaches, though those APIs are not public) than to load them from shared objects in the filesystem.

Definitions

pluggable interface
an (internal) interface that can be implemented by a third party. These can be one-to-one, or one-to-many. An example of one-to-one is the DAL, and an example of one-to-many is preauth.
module
a unit of code that implements a pluggable interface. It can be built in, or it can be dynamically loadable.
built-in
a module whose executable code is located within the library shared object or executable program file, or behaves as if it were. (While separate library shared objects that the calling library depends on can contain "built-in" modules for the calling library, this can cause problems with cyclic references.) The distinguishing characteristic of a built-in module is that, as part of program startup, the operating system automatically maps the executable code of the module into the address space of the process that calls it, without any explicit action by the library or program.
dynamically loaded
a module whose executable code is located within a file that is distinct from the library or program that calls it. The plugin support framework uses the runtime linker (or equivalent) to explicitly map the executable code of the module into the process address space. In POSIX systems, this is typically done using dlopen().
discovery
process of enumerating what modules are available for a pluggable interface. Includes possible filtering of the raw discovered set.
  • compiled-in
  • directory scan
  • explicit inclusion by configuration
  • explicit exclusion by configuration
loading
the process of making modules available for calling. This can involve dynamically loading a module using the runtime linker, or it can involve registering a vtable provided by an application.
  • built-in
  • dynamic loading
  • application-registered
selection
the process of a caller invoking one specific module from the set of loaded modules that implement an interface.
consumer interface
the interface that a caller uses to access the services of a pluggable interface. Typically, but not always, the krb5 library implements the consumer interface.
provider interface
the interface that a module author implements

Links

Some possibly useful ideas at Dr Dobb's. It's rather biased toward C++, and we may disagree with some of the details. Among other things, it advocates a greater separation of the domain-specific interface of a plugin module from its domain-independent interface: plugin modules get passed a handle for the "registry services" of the plugin manager, and the modules must call those services to register their objects or methods.

A paper by Ulrich Drepper, How To Write Shared Libraries (PDF), contains some useful information, mostly from a Linux / glibc perspective. It includes analysis of the performance implications of various aspects of using shared libraries.

The Sun Linker and Libraries Guide includes many useful details about the ELF object file format and the functioning of ELF shared libraries.