Skip to content

Commit

Permalink
Proof of concept code for a candidate plugin framework.
Browse files Browse the repository at this point in the history
git-svn-id: svn://anonsvn.mit.edu/krb5/branches/plugins2@24203 dc483132-0cff-0310-8789-dd5450dbe970
  • Loading branch information
ghudson committed Jul 22, 2010
1 parent 80fa051 commit 936432f
Show file tree
Hide file tree
Showing 21 changed files with 1,483 additions and 361 deletions.
175 changes: 175 additions & 0 deletions README.BRANCH
@@ -0,0 +1,175 @@
This branch demonstrates a possible krb5 plugin infrastructure.

----- Design -----

The design decisions made in this infrastructure are:

1. Configuration for plugin module discovery and filtering

Built-in modules are automatically discoverable after being registered
by the consumer. Dynamic modules must be explicitly configured in
order to be discoverable. The discoverable set of modules can be
filtered by either enabling a specific set of modules by name, or
disabling modules by name.

The profile schema used in this branch is:

[preauth]
interfacename = {
# May take multiple values; only named plugins will be enabled.
enable_only = name

# May take multiple values; named plugins will be disabled.
disable = name

# Establishes a mapping from a module name to a dynamic object.
module = modname:pathname
}

The expectation is that the profile library will gain include-file
support so that mappings for dynamic objects can be specified in
profile fragments which are owned by the OS packages which implement
the module. Filtering rules are expected to be specified in the main
krb5.conf by the system administrator.

2. Consumer-facing API

Each pluggable interface has a corresponding consumer-facing API. The
API consists of:

* An interface-specific handle type, encapsulating the type of a
plugin module and possibly a resource instantiating that type (e.g a
keytab identifier).

* An interface-specific loader function, which creates a handle or a
list of handles. Lists of handles would be used by one-to-many
pluggable interfaces where the consumer wants to consult all
available modules.

3. Producer-facing dynamic module ABI

A dynamic object can implement one or more plugin modules. To
implement a plugin module, a dynamic object exports a symbol named
<interfacename>_<modulename>_init.

Module init functions accept as arguments a krb5_context, a major
version, a minor version, and a pointer to a caller-allocated vtable
(passed as an abstract type). Major versions correspond to complete
revisions of the vtable, while minor versions indicate extensions of a
vtable type.

Based on the major version, the init function casts the vtable pointer
to the correct interface-specific type, and then fills in fields of
that vtable, stopping as indicated by the minor version.

4. Framework

The following functions are used by interface-specific loader
functions:

* k5_plugin_load: Given a numeric interface ID and a module name,
return the init function for the named module.

* k5_plugin_load_all: Given a numeric interface ID, return the init
functions of all modules for that interface.

The following function is used by pluggable interface consumers:

* k5_plugin_register: Registers a built-in plugin module under a
specified interface ID and plugin name.

----- Branch walkthrough -----

The domain-independent framework code lives in:

* include/k5-int.h -- framework declarations and context fields
* lib/krb5/krb/plugin.c -- the framework implementation
* lib/krb5/krb/init_ctx.c -- krb5_free_context addition

The framework is demonstrated with a password quality pluggable
interface used by libkadm5srv. The code for this interface lives in:

* lib/kadm5/server_internal.h -- declarations for consumer API
* lib/kadm5/srv/pwqual.c -- consumer API implementation
* lib/kadm5/srv/pwqual_dict.c -- built-in module using dictionary
* lib/kadm5/srv/pwqual_policy.c -- built-in module using policy
* lib/kadm5/srv/server_misc.c -- consumer logic
* lib/kadm5/srv/server_dict.c -- removed (logic moved to pwqual_dict.c)
* lib/kadm5/srv/svr_principal.c -- some call sites adjusted
* lib/kadm5/srv/server_init.c -- some call sites adjusted

There is also a sample dynamic plugin implementation in the directory
pwqual_combo (at the top level, not under src). This code simulates a
third-party plugin and so uses its own (not very good) build system.
The module rejects passwords which are combinations of two words from
the dictionary file.

----- Trying out the code -----

These steps demonstrate the functioning of the code.

1. Build the branch normally and install it somewhere.

2. cd into the pwqual_combo directory and build it with "make
-I/path/to/install/include". The Makefile probably only works on
Linux-based operating systems.

3. Go back to the main build directory and run "make testrealm" to
create a functioning environment.

4. Add the following configuration to testdir/krb5.master.conf to make
pwqual_combo discoverable:

[plugins]
pwqual = {
module = combo:/path/to/pwqual_combo.so
}

5. Create a file /tmp/dict containing the lines "books" and "sharks".
In the realm definition for KRBTEST.COM in krb5.master.conf, add
the setting "dict_file = /tmp/dict".

6. Run kadmin.local and create a policy with "addpol -minlength 4
testpolicy". Associated it with the principal user with "modprinc
-policy testpolicy user".

7. Inside kadmin.local, try some password change with "cpw user". You
should be able to see that all three password quality modules are
functioning: you won't be able to set passwords shorter than four
characters long (the policy module), or the passwords "books" or
"sharks" (the dict module), or passwords named "sharksbooks" or
"bookssharks" (the combo module).

8. Quit out of kadmin.local and edit testdir/krb5.master.conf again.
Play with the filtering rules by adding, alongside the "module"
directive, one or more assignments for enable_only and/or disable.
For instance, if you disable the policy module, you should find that
(upon restarting kadmin.local) you can set passwords shorter than four
characters again.

----- What's wrong with this branch -----

The krb5 code on this branch is mostly complete, but as a
demonstration branch it is not perfect. Problems include:

* In some cases (marked by XXX comments), overly vague error codes are
returned where new error codes should have been created. This is
because the krb5 trunk's krb5 error table is currently full, and
rectifying that problem is out of scope for the branch.

* Opening and closing password quality plugins should perhaps be
hidden by the password quality consumer API--that is, the open
method should be invoked by the loader, and the close method by
k5_pwqual_free_handles. Currently the responsibility for invoking
these methods rests with the consumer code in server_misc.c.

* At Tom's suggestion, new internal functions with external linkage
are using the prefix "k5_" instead of "krb5int_". This practice
should be validated by the dev community (and perhaps made uniform
in the code base, although that would result in a lot of churn) or
abandoned.

* The decisions about what is a typedef and what is a simple structure
type are kind of haphazard, erring on the side of using typedefs.

* The Hesiod support in server_misc.c was ripped out.
14 changes: 14 additions & 0 deletions pwqual_combo/Makefile
@@ -0,0 +1,14 @@
# Dummy non-portable build system for sample password quality plugin.

INCLUDES = -I/path/to/krb5/prefix/include

all: pwqual_combo.so

pwqual_combo.so: combo.so
gcc -shared -fPIC -o $@ combo.so

combo.so: combo.c
gcc -fPIC -DSHARED $(INCLUDES) -g -c combo.c -o $@

clean:
rm -f combo.so pwqual_combo.so
183 changes: 183 additions & 0 deletions pwqual_combo/combo.c
@@ -0,0 +1,183 @@
/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/*
* Copyright 1993 OpenVision Technologies, Inc., All Rights Reserved
*
* 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.
*
*
* Sample password quality plugin which checks for dictionary word combos
*/


#include <krb5.h>
#include <krb5/pwqual_plugin.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>

typedef struct combo_moddata_st {
char **word_list; /* list of word pointers */
char *word_block; /* actual word data */
size_t word_count; /* number of words */
} *combo_moddata;

static int
word_compare(const void *s1, const void *s2)
{
return (strcasecmp(*(const char **)s1, *(const char **)s2));
}

static krb5_error_code
init_dict(combo_moddata dict, const char *dict_file)
{
int fd;
size_t len, i;
char *p, *t;
struct stat sb;

if (dict_file == NULL)
return 0;
if ((fd = open(dict_file, O_RDONLY)) == -1)
return (errno == ENOENT) ? 0 : errno;
if (fstat(fd, &sb) == -1) {
close(fd);
return errno;
}
if ((dict->word_block = malloc(sb.st_size + 1)) == NULL)
return ENOMEM;
if (read(fd, dict->word_block, sb.st_size) != sb.st_size)
return errno;
(void) close(fd);
dict->word_block[sb.st_size] = '\0';

p = dict->word_block;
len = sb.st_size;
while(len > 0 && (t = memchr(p, '\n', len)) != NULL) {
*t = '\0';
len -= t - p + 1;
p = t + 1;
dict->word_count++;
}
if ((dict->word_list = malloc(dict->word_count * sizeof(char *))) == NULL)
return ENOMEM;
p = dict->word_block;
for (i = 0; i < dict->word_count; i++) {
dict->word_list[i] = p;
p += strlen(p) + 1;
}
qsort(dict->word_list, dict->word_count, sizeof(char *), word_compare);
return 0;
}

static void
destroy_dict(combo_moddata dict)
{
if (dict == NULL)
return;
free(dict->word_list);
free(dict->word_block);
free(dict);
return;
}

static krb5_error_code
combo_open(krb5_context context, const char *dict_file,
krb5_pwqual_moddata *data)
{
krb5_error_code ret;
combo_moddata dict;

*data = NULL;

/* Allocate and initialize a dictionary structure. */
dict = malloc(sizeof(*dict));
if (dict == NULL)
return ENOMEM;
dict->word_list = NULL;
dict->word_block = NULL;
dict->word_count = 0;

/* Fill in the dictionary structure with data from dict_file. */
ret = init_dict(dict, dict_file);
if (ret != 0) {
destroy_dict(dict);
return ret;
}

*data = (krb5_pwqual_moddata)dict;
return 0;
}

static krb5_error_code
combo_check(krb5_context context, krb5_pwqual_moddata data,
const char *password, kadm5_policy_ent_t policy,
krb5_principal princ)
{
combo_moddata dict = (combo_moddata)data;
size_t i, j, len, pwlen;
const char *remainder;

if (dict->word_list == NULL)
return 0;

pwlen = strlen(password);
for (i = 0; i < dict->word_count; i++) {
len = strlen(dict->word_list[i]);
if (len >= pwlen)
continue;
if (strncasecmp(password, dict->word_list[i], len) != 0)
continue;
remainder = password + len;
for (i = 0; i < dict->word_count; i++) {
if (strcasecmp(remainder, dict->word_list[i]) == 0)
return KADM5_PASS_Q_DICT;
}
}

return 0;
}

static void
combo_close(krb5_context context, krb5_pwqual_moddata data)
{
destroy_dict((combo_moddata)data);
}

krb5_error_code
pwqual_combo_init(krb5_context context, int maj_ver, int min_ver,
krb5_plugin_vtable vtable)
{
krb5_pwqual_vtable vt;

if (maj_ver != 1)
return EINVAL; /* XXX create error code */
vt = (krb5_pwqual_vtable)vtable;
vt->open = combo_open;
vt->check = combo_check;
vt->close = combo_close;
return 0;
}
2 changes: 2 additions & 0 deletions src/include/Makefile.in
Expand Up @@ -136,6 +136,8 @@ 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) $(srcdir)/krb5/pwqual_plugin.h $(DESTDIR)$(KRB5_INCDIR)$(S)krb5$(S)pwqual_plugin.h
$(INSTALL_DATA) profile.h $(DESTDIR)$(KRB5_INCDIR)$(S)profile.h
$(INSTALL_DATA) $(srcdir)/gssapi.h $(DESTDIR)$(KRB5_INCDIR)$(S)gssapi.h

Expand Down

0 comments on commit 936432f

Please sign in to comment.