forked from krb5/krb5-anonsvn
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Proof of concept code for a candidate plugin framework.
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
Showing
21 changed files
with
1,483 additions
and
361 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.