svn rev #24203: branches/plugins2/ pwqual_combo/ src/include/ src/include/krb5/ ...

ghudson@MIT.EDU ghudson at MIT.EDU
Wed Jul 21 23:13:38 EDT 2010


http://src.mit.edu/fisheye/changelog/krb5/?cs=24203
Commit By: ghudson
Log Message:
Proof of concept code for a candidate plugin framework.



Changed Files:
A   branches/plugins2/README.BRANCH
A   branches/plugins2/pwqual_combo/
A   branches/plugins2/pwqual_combo/Makefile
A   branches/plugins2/pwqual_combo/combo.c
U   branches/plugins2/src/include/Makefile.in
U   branches/plugins2/src/include/k5-int.h
A   branches/plugins2/src/include/krb5/plugin.h
A   branches/plugins2/src/include/krb5/pwqual_plugin.h
U   branches/plugins2/src/lib/kadm5/server_internal.h
U   branches/plugins2/src/lib/kadm5/srv/Makefile.in
U   branches/plugins2/src/lib/kadm5/srv/libkadm5srv_mit.exports
A   branches/plugins2/src/lib/kadm5/srv/pwqual.c
A   branches/plugins2/src/lib/kadm5/srv/pwqual_dict.c
A   branches/plugins2/src/lib/kadm5/srv/pwqual_policy.c
D   branches/plugins2/src/lib/kadm5/srv/server_dict.c
U   branches/plugins2/src/lib/kadm5/srv/server_init.c
U   branches/plugins2/src/lib/kadm5/srv/server_misc.c
U   branches/plugins2/src/lib/kadm5/srv/svr_principal.c
U   branches/plugins2/src/lib/krb5/krb/Makefile.in
U   branches/plugins2/src/lib/krb5/krb/init_ctx.c
A   branches/plugins2/src/lib/krb5/krb/plugin.c
U   branches/plugins2/src/lib/krb5/libkrb5.exports
Added: branches/plugins2/README.BRANCH
===================================================================
--- branches/plugins2/README.BRANCH	                        (rev 0)
+++ branches/plugins2/README.BRANCH	2010-07-22 03:13:38 UTC (rev 24203)
@@ -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.

Added: branches/plugins2/pwqual_combo/Makefile
===================================================================
--- branches/plugins2/pwqual_combo/Makefile	                        (rev 0)
+++ branches/plugins2/pwqual_combo/Makefile	2010-07-22 03:13:38 UTC (rev 24203)
@@ -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

Added: branches/plugins2/pwqual_combo/combo.c
===================================================================
--- branches/plugins2/pwqual_combo/combo.c	                        (rev 0)
+++ branches/plugins2/pwqual_combo/combo.c	2010-07-22 03:13:38 UTC (rev 24203)
@@ -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;
+}

Modified: branches/plugins2/src/include/Makefile.in
===================================================================
--- branches/plugins2/src/include/Makefile.in	2010-07-22 03:03:15 UTC (rev 24202)
+++ branches/plugins2/src/include/Makefile.in	2010-07-22 03:13:38 UTC (rev 24203)
@@ -136,6 +136,8 @@
 	$(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
 

Modified: branches/plugins2/src/include/k5-int.h
===================================================================
--- branches/plugins2/src/include/k5-int.h	2010-07-22 03:03:15 UTC (rev 24202)
+++ branches/plugins2/src/include/k5-int.h	2010-07-22 03:13:38 UTC (rev 24203)
@@ -168,6 +168,7 @@
  */
 #include <errno.h>
 #include "krb5.h"
+#include <krb5/plugin.h>
 #include "profile.h"
 
 #include "port-sockets.h"
@@ -205,12 +206,14 @@
 #define KRB5_CONF_DEFAULT_PRINCIPAL_EXPIRATION   "default_principal_expiration"
 #define KRB5_CONF_DEFAULT_PRINCIPAL_FLAGS        "default_principal_flags"
 #define KRB5_CONF_DICT_FILE                   "dict_file"
+#define KRB5_CONF_DISABLE                     "disable"
 #define KRB5_CONF_DISABLE_LAST_SUCCESS        "disable_last_success"
 #define KRB5_CONF_DISABLE_LOCKOUT             "disable_lockout"
 #define KRB5_CONF_DNS_LOOKUP_KDC              "dns_lookup_kdc"
 #define KRB5_CONF_DNS_LOOKUP_REALM            "dns_lookup_realm"
 #define KRB5_CONF_DNS_FALLBACK                "dns_fallback"
 #define KRB5_CONF_DOMAIN_REALM                "domain_realm"
+#define KRB5_CONF_ENABLE_ONLY                 "enable_only"
 #define KRB5_CONF_EXTRA_ADDRESSES             "extra_addresses"
 #define KRB5_CONF_FORWARDABLE                 "forwardable"
 #define KRB5_CONF_HOST_BASED_SERVICES         "host_based_services"
@@ -245,9 +248,11 @@
 #define KRB5_CONF_MASTER_KDC                  "master_kdc"
 #define KRB5_CONF_MAX_LIFE                    "max_life"
 #define KRB5_CONF_MAX_RENEWABLE_LIFE          "max_renewable_life"
+#define KRB5_CONF_MODULE                      "module"
 #define KRB5_CONF_NOADDRESSES                 "noaddresses"
 #define KRB5_CONF_NO_HOST_REFERRAL            "no_host_referral"
 #define KRB5_CONF_PERMITTED_ENCTYPES          "permitted_enctypes"
+#define KRB5_CONF_PLUGINS                     "plugins"
 #define KRB5_CONF_PREAUTH_MODULE_DIR          "preauth_module_dir"
 #define KRB5_CONF_PREFERRED_PREAUTH_TYPES     "preferred_preauth_types"
 #define KRB5_CONF_PROXIABLE                   "proxiable"
@@ -1424,6 +1429,56 @@
                             krb5_authdata_context context, const char *module,
                             void *ptr);
 
+/* Plugin framework */
+
+/*
+ * A linked list entry mapping a module name to a module init function.  The
+ * entry may also include a dynamic object handle so that it can be released
+ * when the context is destroyed.
+ */
+struct plugin_mapping {
+    char *modname;
+    krb5_plugin_init_fn module;
+    struct plugin_file_handle *dyn_handle;
+    struct plugin_mapping *next;
+};
+
+/* Holds krb5_context information about each pluggable interface. */
+struct plugin_interface {
+    struct plugin_mapping *modules;
+    krb5_boolean configured;
+};
+
+/* A list of plugin interface IDs.  Make sure to increment
+ * PLUGIN_NUM_INTERFACES when a new interface is added. */
+#define PLUGIN_INTERFACE_PWQUAL 0
+#define PLUGIN_NUM_INTERFACES   1
+
+/* Retrieve the plugin module of type interface_id and name modname,
+ * storing the result into module. */
+krb5_error_code
+k5_plugin_load(krb5_context context, int interface_id, const char *modname,
+               krb5_plugin_init_fn *module);
+
+/* Retrieve all plugin modules of type interface_id, storing the result
+ * into modules.  Free the result with k5_plugin_free_handles. */
+krb5_error_code
+k5_plugin_load_all(krb5_context context, int interface_id,
+                   krb5_plugin_init_fn **modules);
+
+/* Release a module list allocated by k5_plugin_load_all. */
+void
+k5_plugin_free_modules(krb5_context context, krb5_plugin_init_fn *modules);
+
+/* Register a plugin module of type interface_id and name modname. */
+krb5_error_code
+k5_plugin_register(krb5_context context, int interface_id, const char *modname,
+                   krb5_plugin_init_fn module);
+
+/* Destroy the module state within context; used by krb5_free_context. */
+void
+k5_plugin_free_context(krb5_context context);
+
 struct _kdb5_dal_handle;        /* private, in kdb5.h */
 typedef struct _kdb5_dal_handle kdb5_dal_handle;
 struct _kdb_log_context;
@@ -1478,6 +1533,8 @@
 
     krb5_trace_callback trace_callback;
     void *trace_callback_data;
+
+    struct plugin_interface plugins[PLUGIN_NUM_INTERFACES];
 };
 
 /* could be used in a table to find an etype and initialize a block */

Added: branches/plugins2/src/include/krb5/plugin.h
===================================================================
--- branches/plugins2/src/include/krb5/plugin.h	                        (rev 0)
+++ branches/plugins2/src/include/krb5/plugin.h	2010-07-22 03:13:38 UTC (rev 24203)
@@ -0,0 +1,48 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ * <krb5/plugin.h>
+ *
+ * Copyright (C) 2010 by the Massachusetts Institute of Technology.
+ * All rights reserved.
+ *
+ * Export of this software from the United States of America may
+ *   require a specific license from the United States Government.
+ *   It is the responsibility of any person or organization contemplating
+ *   export to obtain such a license before exporting.
+ *
+ * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
+ * distribute this software and its documentation for any purpose and
+ * without fee is hereby granted, provided that the above copyright
+ * notice appear in all copies and that both that copyright notice and
+ * this permission notice appear in supporting documentation, and that
+ * the name of M.I.T. not be used in advertising or publicity pertaining
+ * to distribution of the software without specific, written prior
+ * permission.  Furthermore if you modify this software you must label
+ * your software as modified software and not distribute it in such a
+ * fashion that it might be confused with the original M.I.T. software.
+ * M.I.T. makes no representations about the suitability of
+ * this software for any purpose.  It is provided "as is" without express
+ * or implied warranty.
+ *
+ *
+ * Generic declarations for dynamic modules implementing krb5 plugin modules.
+ */
+
+#ifndef KRB5_PLUGIN_H
+#define KRB5_PLUGIN_H
+
+/* krb5_plugin_vtable is an abstract type.  Module init functions will cast it
+ * to the appropriate interface-specific vtable type. */
+typedef struct krb5_plugin_vtable_st *krb5_plugin_vtable;
+
+/*
+  krb5_plugin_init_fn is the type of a module init function.  Based on the
+ * maj_ver argument, the init function should cast vtable to the appropriate
+ * type and then fill it in.  If a vtable has been expanded, min_ver indicates
+ * which version of the vtable is being filled in.
+ */
+typedef krb5_error_code
+(*krb5_plugin_init_fn)(krb5_context context, int maj_ver, int min_ver,
+                       krb5_plugin_vtable vtable);
+
+#endif /* KRB5_PLUGIN_H */

Added: branches/plugins2/src/include/krb5/pwqual_plugin.h
===================================================================
--- branches/plugins2/src/include/krb5/pwqual_plugin.h	                        (rev 0)
+++ branches/plugins2/src/include/krb5/pwqual_plugin.h	2010-07-22 03:13:38 UTC (rev 24203)
@@ -0,0 +1,60 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ * prototype/prototype.h
+ *
+ * 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.
+ *
+ *
+ * Declarations for password quality plugin module implementors.
+ */
+
+#ifndef KRB5_PWQUAL_PLUGIN_H
+#define KRB5_PWQUAL_PLUGIN_H
+
+#include <krb5/krb5.h>
+#include <krb5/plugin.h>
+#include <kadm5/admin.h>
+#include <kdb.h>
+
+/* An abstract type for password quality module data. */
+typedef struct krb5_pwqual_moddata_st *krb5_pwqual_moddata;
+
+/* Password quality plugin vtable for major version 1. */
+typedef struct krb5_pwqual_vtable_st {
+    /* Optional: Initialize module data.  dictfile is the realm's configured
+     * dictionary filename. */
+    krb5_error_code (*open)(krb5_context context, const char *dict_file,
+                            krb5_pwqual_moddata *data);
+
+    /* Mandatory: Check a password for the principal princ, possibly making use
+     * of the password policy given by policy.  Return an error if the password
+     * check fails. */
+    krb5_error_code (*check)(krb5_context context, krb5_pwqual_moddata data,
+			     const char *password, kadm5_policy_ent_t policy,
+			     krb5_principal princ);
+
+    /* Optional: Release resources used by module data. */
+    void (*close)(krb5_context context, krb5_pwqual_moddata data);
+} *krb5_pwqual_vtable;
+
+#endif /* KRB5_PWQUAL_PLUGIN_H */

Modified: branches/plugins2/src/lib/kadm5/server_internal.h
===================================================================
--- branches/plugins2/src/lib/kadm5/server_internal.h	2010-07-22 03:03:15 UTC (rev 24202)
+++ branches/plugins2/src/lib/kadm5/server_internal.h	2010-07-22 03:13:38 UTC (rev 24203)
@@ -22,6 +22,7 @@
 #include    <errno.h>
 #include    <kdb.h>
 #include    <kadm5/admin.h>
+#include    <krb5/plugin.h>
 #include    "admin_internal.h"
 
 /*
@@ -33,6 +34,9 @@
  */
 #define INITIAL_HIST_KVNO 2
 
+/* A pwqual_handle represents a password quality plugin module. */
+typedef struct pwqual_handle_st *pwqual_handle;
+
 typedef struct _kadm5_server_handle_t {
     krb5_ui_4       magic_number;
     krb5_ui_4       struct_version;
@@ -42,6 +46,7 @@
     kadm5_config_params  params;
     struct _kadm5_server_handle_t *lhandle;
     char **db_args;
+    pwqual_handle   *qual_handles;
 } kadm5_server_handle_rec, *kadm5_server_handle_t;
 
 #define OSA_ADB_PRINC_VERSION_1  0x12345C01
@@ -65,8 +70,7 @@
 kadm5_ret_t    adb_policy_init(kadm5_server_handle_t handle);
 kadm5_ret_t    adb_policy_close(kadm5_server_handle_t handle);
 kadm5_ret_t    passwd_check(kadm5_server_handle_t handle,
-                            char *pass, int use_policy,
-                            kadm5_policy_ent_t policy,
+                            const char *pass, kadm5_policy_ent_t policy,
                             krb5_principal principal);
 kadm5_ret_t    principal_exists(krb5_principal principal);
 krb5_error_code     kdb_init_master(kadm5_server_handle_t handle,
@@ -90,9 +94,8 @@
                                    void (*iter_fct)(void *, krb5_principal),
                                    void *data);
 
-int                 init_dict(kadm5_config_params *);
-int                 find_word(const char *word);
-void                destroy_dict(void);
+kadm5_ret_t         init_pwqual(kadm5_server_handle_t handle);
+void                destroy_pwqual(kadm5_server_handle_t handle);
 
 /* XXX this ought to be in libkrb5.a, but isn't */
 kadm5_ret_t krb5_copy_key_data_contents(krb5_context context,
@@ -153,4 +156,44 @@
 void
 osa_free_princ_ent(osa_princ_ent_t val);
 
+/*** Password quality plugin consumer interface ***/
+
+/* Load the available password quality plugins and store the result into
+ * *handles.  Free the result with k5_pwqual_free_handles. */
+krb5_error_code
+k5_pwqual_load(krb5_context context, pwqual_handle **handles);
+
+/* Release a handle list allocated by k5_pwqual_load.  All modules must have
+ * been closed by the caller. */
+krb5_error_code
+k5_pwqual_free_handles(krb5_context context, pwqual_handle *handles);
+
+/* Initialize a password quality plugin, possibly using the realm's configured
+ * dictionary filename. */
+krb5_error_code
+k5_pwqual_open(krb5_context context, pwqual_handle handle,
+               const char *dict_file);
+
+/* Check a password using a password quality plugin. */
+krb5_error_code
+k5_pwqual_check(krb5_context context, pwqual_handle handle,
+                const char *password, kadm5_policy_ent_t policy,
+                krb5_principal princ);
+
+/* Release the memory used by a password quality plugin. */
+void
+k5_pwqual_close(krb5_context context, pwqual_handle handle);
+
+/*** Init functions for built-in password quality modules ***/
+
+/* The dict module checks passwords against the realm's dictionary. */
+krb5_error_code
+pwqual_dict_init(krb5_context context, int maj_ver, int min_ver,
+                 krb5_plugin_vtable vtable);
+
+/* The policy module enforces password policy constraints. */
+krb5_error_code
+pwqual_policy_init(krb5_context context, int maj_ver, int min_ver,
+                   krb5_plugin_vtable vtable);
+
 #endif /* __KADM5_SERVER_INTERNAL_H__ */

Modified: branches/plugins2/src/lib/kadm5/srv/Makefile.in
===================================================================
--- branches/plugins2/src/lib/kadm5/srv/Makefile.in	2010-07-22 03:03:15 UTC (rev 24202)
+++ branches/plugins2/src/lib/kadm5/srv/Makefile.in	2010-07-22 03:13:38 UTC (rev 24203)
@@ -27,36 +27,42 @@
 SHLIB_RDIRS=$(KRB5_LIBDIR)
 RELDIR=kadm5/srv
 
-SRCS =	$(srcdir)/svr_policy.c \
+SRCS =	$(srcdir)/pwqual.c \
+	$(srcdir)/pwqual_dict.c \
+	$(srcdir)/pwqual_policy.c \
+	$(srcdir)/svr_policy.c \
 	$(srcdir)/svr_principal.c \
 	$(srcdir)/server_acl.c \
 	$(srcdir)/server_kdb.c \
 	$(srcdir)/server_misc.c \
 	$(srcdir)/server_init.c \
-	$(srcdir)/server_dict.c \
 	$(srcdir)/svr_iters.c \
 	$(srcdir)/svr_chpass_util.c \
 	$(srcdir)/adb_xdr.c 
 
-OBJS =	svr_policy.$(OBJEXT) \
+OBJS =	pwqual.$(OBJEXT) \
+	pwqual_dict.$(OBJEXT) \
+	pwqual_policy.$(OBJECT) \
+	svr_policy.$(OBJEXT) \
 	svr_principal.$(OBJEXT) \
 	server_acl.$(OBJEXT) \
 	server_kdb.$(OBJEXT) \
 	server_misc.$(OBJEXT) \
 	server_init.$(OBJEXT) \
-	server_dict.$(OBJEXT) \
 	svr_iters.$(OBJEXT) \
 	svr_chpass_util.$(OBJEXT) \
 	adb_xdr.$(OBJEXT) 
 
 STLIBOBJS = \
+	pwqual.o \
+	pwqual_dict.o \
+	pwqual_policy.o \
 	svr_policy.o \
 	svr_principal.o \
 	server_acl.o \
 	server_kdb.o \
 	server_misc.o \
 	server_init.o \
-	server_dict.o \
 	svr_iters.o \
 	svr_chpass_util.o \
 	adb_xdr.o

Modified: branches/plugins2/src/lib/kadm5/srv/libkadm5srv_mit.exports
===================================================================
--- branches/plugins2/src/lib/kadm5/srv/libkadm5srv_mit.exports	2010-07-22 03:03:15 UTC (rev 24202)
+++ branches/plugins2/src/lib/kadm5/srv/libkadm5srv_mit.exports	2010-07-22 03:13:38 UTC (rev 24203)
@@ -7,10 +7,7 @@
 kadm5int_acl_init
 adb_policy_close
 adb_policy_init
-destroy_dict
-find_word
 hist_princ
-init_dict
 kadm5_set_use_password_server
 kadm5_chpass_principal
 kadm5_chpass_principal_3

Added: branches/plugins2/src/lib/kadm5/srv/pwqual.c
===================================================================
--- branches/plugins2/src/lib/kadm5/srv/pwqual.c	                        (rev 0)
+++ branches/plugins2/src/lib/kadm5/srv/pwqual.c	2010-07-22 03:13:38 UTC (rev 24203)
@@ -0,0 +1,114 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ * lib/kadm5/srv/pwqual.c
+ *
+ * Copyright (C) 2010 by the Massachusetts Institute of Technology.
+ * All rights reserved.
+ *
+ * Export of this software from the United States of America may
+ *   require a specific license from the United States Government.
+ *   It is the responsibility of any person or organization contemplating
+ *   export to obtain such a license before exporting.
+ *
+ * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
+ * distribute this software and its documentation for any purpose and
+ * without fee is hereby granted, provided that the above copyright
+ * notice appear in all copies and that both that copyright notice and
+ * this permission notice appear in supporting documentation, and that
+ * the name of M.I.T. not be used in advertising or publicity pertaining
+ * to distribution of the software without specific, written prior
+ * permission.  Furthermore if you modify this software you must label
+ * your software as modified software and not distribute it in such a
+ * fashion that it might be confused with the original M.I.T. software.
+ * M.I.T. makes no representations about the suitability of
+ * this software for any purpose.  It is provided "as is" without express
+ * or implied warranty.
+ *
+ *
+ * Consumer interface for password quality plugins
+ */
+
+#include "k5-int.h"
+#include "server_internal.h"
+#include <krb5/pwqual_plugin.h>
+
+struct pwqual_handle_st {
+    struct krb5_pwqual_vtable_st vt;
+    krb5_pwqual_moddata data;
+};
+
+krb5_error_code
+k5_pwqual_load(krb5_context context, pwqual_handle **handles)
+{
+    krb5_error_code ret;
+    krb5_plugin_init_fn *modules = NULL, *mod;
+    size_t count;
+    pwqual_handle *list = NULL, handle = NULL;
+
+    ret = k5_plugin_load_all(context, PLUGIN_INTERFACE_PWQUAL, &modules);
+    if (ret != 0)
+	goto cleanup;
+
+    /* Allocate a large enough list of handles. */
+    for (count = 0; modules[count] != NULL; count++);
+    list = k5alloc((count + 1) * sizeof(*list), &ret);
+    if (list == NULL)
+	goto cleanup;
+
+    /* For each module, allocate a handle and initialize its vtable.  Skip
+     * modules which don't successfully initialize. */
+    count = 0;
+    for (mod = modules; *mod != NULL; mod++) {
+	handle = k5alloc(sizeof(*handle), &ret);
+	if (handle == NULL)
+	    goto cleanup;
+	ret = (*mod)(context, 1, 1, (krb5_plugin_vtable)&handle->vt);
+	if (ret == 0)
+	    list[count++] = handle;
+	else
+	    free(handle);
+    }
+
+    *handles = list;
+    list = NULL;
+
+cleanup:
+    k5_plugin_free_modules(context, modules);
+    k5_pwqual_free_handles(context, list);
+    return ret;
+}
+
+krb5_error_code
+k5_pwqual_free_handles(krb5_context context, pwqual_handle *handles)
+{
+    /* It's the caller's responsibility to close each handle, so all of the
+     * module data should be freed by now, leaving only the list itself. */
+    free(handles);
+}
+
+krb5_error_code
+k5_pwqual_open(krb5_context context, pwqual_handle handle,
+               const char *dict_file)
+{
+    if (handle->data != NULL)
+	return EINVAL;
+    if (handle->vt.open == NULL)
+        return 0;
+    return handle->vt.open(context, dict_file, &handle->data);
+}
+
+krb5_error_code
+k5_pwqual_check(krb5_context context, pwqual_handle handle,
+		const char *password, kadm5_policy_ent_t policy,
+		krb5_principal princ)
+{
+    return handle->vt.check(context, handle->data, password, policy, princ);
+}
+
+void
+k5_pwqual_close(krb5_context context, pwqual_handle handle)
+{
+    if (handle->vt.close)
+        handle->vt.close(context, handle->data);
+    handle->data = NULL;
+}

Copied: branches/plugins2/src/lib/kadm5/srv/pwqual_dict.c (from rev 24200, trunk/src/lib/kadm5/srv/server_dict.c)
===================================================================
--- branches/plugins2/src/lib/kadm5/srv/pwqual_dict.c	                        (rev 0)
+++ branches/plugins2/src/lib/kadm5/srv/pwqual_dict.c	2010-07-22 03:13:38 UTC (rev 24203)
@@ -0,0 +1,242 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ * Copyright 1993 OpenVision Technologies, Inc., All Rights Reserved
+ *
+ * $Header$
+ */
+
+#if !defined(lint) && !defined(__CODECENTER__)
+static char *rcsid = "$Header$";
+#endif
+
+#include "k5-platform.h"
+#include <krb5/pwqual_plugin.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <kadm5/admin.h>
+#include "adm_proto.h"
+#include <syslog.h>
+#include "server_internal.h"
+
+typedef struct dict_moddata_st {
+    char **word_list;        /* list of word pointers */
+    char *word_block;        /* actual word data */
+    unsigned int word_count; /* number of words */
+} *dict_moddata;
+
+
+/*
+ * Function: word_compare
+ *
+ * Purpose: compare two words in the dictionary.
+ *
+ * Arguments:
+ *      w1              (input) pointer to first word
+ *      w2              (input) pointer to second word
+ *      <return value>  result of strcmp
+ *
+ * Requires:
+ *      w1 and w2 to point to valid memory
+ *
+ */
+
+static int
+word_compare(const void *s1, const void *s2)
+{
+    return (strcasecmp(*(const char **)s1, *(const char **)s2));
+}
+
+/*
+ * Function: init-dict
+ *
+ * Purpose: Initialize in memory word dictionary
+ *
+ * Arguments:
+ *          none
+ *          <return value> KADM5_OK on success errno on failure;
+ *                         (but success on ENOENT)
+ *
+ * Requires:
+ *      If WORDFILE exists, it must contain a list of words,
+ *      one word per-line.
+ *
+ * Effects:
+ *      If WORDFILE exists, it is read into memory sorted for future
+ * use.  If it does not exist, it syslogs an error message and returns
+ * success.
+ *
+ * Modifies:
+ *      word_list to point to a chunck of allocated memory containing
+ *      pointers to words
+ *      word_block to contain the dictionary.
+ *
+ */
+
+static int
+init_dict(dict_moddata dict, const char *dict_file)
+{
+    int fd;
+    size_t len, i;
+    char *p, *t;
+    struct stat sb;
+
+    if (dict_file == NULL) {
+        krb5_klog_syslog(LOG_INFO, "No dictionary file specified, continuing "
+                         "without one.");
+        return KADM5_OK;
+    }
+    if ((fd = open(dict_file, O_RDONLY)) == -1) {
+        if (errno == ENOENT) {
+            krb5_klog_syslog(LOG_ERR,
+                             "WARNING!  Cannot find dictionary file %s, "
+                             "continuing without one.", dict_file);
+            return KADM5_OK;
+        } else
+            return errno;
+    }
+    set_cloexec_fd(fd);
+    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 KADM5_OK;
+}
+
+/*
+ * Function: destroy_dict
+ *
+ * Purpose: destroy in-core copy of dictionary.
+ *
+ * Arguments:
+ *          none
+ *          <return value>  none
+ * Requires:
+ *          nothing
+ * Effects:
+ *      frees up memory occupied by word_list and word_block
+ *      sets count back to 0, and resets the pointers to NULL
+ *
+ * Modifies:
+ *      word_list, word_block, and word_count.
+ *
+ */
+
+static void
+destroy_dict(dict_moddata dict)
+{
+    if (dict == NULL)
+        return;
+    free(dict->word_list);
+    free(dict->word_block);
+    free(dict);
+    return;
+}
+
+/* Implement the password quality open method by reading in dict_file. */
+static krb5_error_code
+dict_open(krb5_context context, const char *dict_file,
+          krb5_pwqual_moddata *data)
+{
+    krb5_error_code ret;
+    dict_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;
+}
+
+/* Implement the password quality check method by checking the password
+ * against the dictionary, as well as against principal components. */
+static krb5_error_code
+dict_check(krb5_context context, krb5_pwqual_moddata data,
+           const char *password, kadm5_policy_ent_t policy,
+           krb5_principal princ)
+{
+    dict_moddata dict = (dict_moddata)data;
+    int i, n;
+    char *cp;
+
+    /* Don't check the dictionary for principals with no password policy. */
+    if (policy == NULL)
+        return 0;
+
+    /* Check against words in the dictionary if we successfully loaded one. */
+    if (dict->word_list != NULL &&
+        bsearch(&password, dict->word_list, dict->word_count, sizeof(char *),
+                word_compare) != NULL)
+        return KADM5_PASS_Q_DICT;
+
+    /* Check against components of the principal. */
+    n = krb5_princ_size(handle->context, princ);
+    cp = krb5_princ_realm(handle->context, princ)->data;
+    if (strcasecmp(cp, password) == 0)
+	return KADM5_PASS_Q_DICT;
+    for (i = 0; i < n; i++) {
+	cp = krb5_princ_component(handle->context, princ, i)->data;
+	if (strcasecmp(cp, password) == 0)
+	    return KADM5_PASS_Q_DICT;
+    }
+    return 0;
+}
+
+/* Implement the password quality close method. */
+static void
+dict_close(krb5_context context, krb5_pwqual_moddata data)
+{
+    destroy_dict((dict_moddata)data);
+}
+
+krb5_error_code
+pwqual_dict_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 = dict_open;
+    vt->check = dict_check;
+    vt->close = dict_close;
+    return 0;
+}

Added: branches/plugins2/src/lib/kadm5/srv/pwqual_policy.c
===================================================================
--- branches/plugins2/src/lib/kadm5/srv/pwqual_policy.c	                        (rev 0)
+++ branches/plugins2/src/lib/kadm5/srv/pwqual_policy.c	2010-07-22 03:13:38 UTC (rev 24203)
@@ -0,0 +1,81 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ * lib/kadm5/srv/pwqual_policy.c
+ *
+ * Copyright (C) 2010 by the Massachusetts Institute of Technology.
+ * All rights reserved.
+ *
+ * Export of this software from the United States of America may
+ *   require a specific license from the United States Government.
+ *   It is the responsibility of any person or organization contemplating
+ *   export to obtain such a license before exporting.
+ *
+ * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
+ * distribute this software and its documentation for any purpose and
+ * without fee is hereby granted, provided that the above copyright
+ * notice appear in all copies and that both that copyright notice and
+ * this permission notice appear in supporting documentation, and that
+ * the name of M.I.T. not be used in advertising or publicity pertaining
+ * to distribution of the software without specific, written prior
+ * permission.  Furthermore if you modify this software you must label
+ * your software as modified software and not distribute it in such a
+ * fashion that it might be confused with the original M.I.T. software.
+ * M.I.T. makes no representations about the suitability of
+ * this software for any purpose.  It is provided "as is" without express
+ * or implied warranty.
+ *
+ *
+ * Password quality module to enforce password policy
+ */
+
+#include "k5-platform.h"
+#include <krb5/pwqual_plugin.h>
+#include <kadm5/admin.h>
+#include <ctype.h>
+#include "server_internal.h"
+
+/* Implement the password quality check module. */
+static krb5_error_code
+policy_check(krb5_context context, krb5_pwqual_moddata data,
+	     const char *password, kadm5_policy_ent_t policy,
+	     krb5_principal princ)
+{
+    int nupper = 0, nlower = 0, ndigit = 0, npunct = 0, nspec = 0;
+    const char *s;
+    unsigned char c;
+
+    if (policy == NULL)
+	return (*password == '\0') ? KADM5_PASS_Q_TOOSHORT : 0;
+
+    if(strlen(password) < (size_t)policy->pw_min_length)
+	return KADM5_PASS_Q_TOOSHORT;
+    s = password;
+    while ((c = (unsigned char)*s++)) {
+	if (islower(c))
+	    nlower = 1;
+	else if (isupper(c))
+	    nupper = 1;
+	else if (isdigit(c))
+	    ndigit = 1;
+	else if (ispunct(c))
+	    npunct = 1;
+	else
+	    nspec = 1;
+    }
+    if ((nupper + nlower + ndigit + npunct + nspec) < policy->pw_min_classes)
+	return KADM5_PASS_Q_CLASS;
+    return 0;
+}
+
+krb5_error_code
+pwqual_policy_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->check = policy_check;
+    return 0;
+}

Modified: branches/plugins2/src/lib/kadm5/srv/server_init.c
===================================================================
--- branches/plugins2/src/lib/kadm5/srv/server_init.c	2010-07-22 03:03:15 UTC (rev 24202)
+++ branches/plugins2/src/lib/kadm5/srv/server_init.c	2010-07-22 03:13:38 UTC (rev 24203)
@@ -317,7 +317,7 @@
         return ret;
     }
 
-    ret = init_dict(&handle->params);
+    ret = init_pwqual(handle);
     if (ret) {
         krb5_db_fini(handle->context);
         krb5_free_principal(handle->context, handle->current_caller);
@@ -337,7 +337,7 @@
 
     CHECK_HANDLE(server_handle);
 
-    destroy_dict();
+    destroy_pwqual(handle);
 
     adb_policy_close(handle);
     krb5_db_fini(handle->context);

Modified: branches/plugins2/src/lib/kadm5/srv/server_misc.c
===================================================================
--- branches/plugins2/src/lib/kadm5/srv/server_misc.c	2010-07-22 03:03:15 UTC (rev 24202)
+++ branches/plugins2/src/lib/kadm5/srv/server_misc.c	2010-07-22 03:13:38 UTC (rev 24203)
@@ -13,10 +13,6 @@
 #include    <kdb.h>
 #include    <ctype.h>
 #include    <pwd.h>
-
-/* for strcasecmp */
-#include    <string.h>
-
 #include    "server_internal.h"
 
 kadm5_ret_t
@@ -37,147 +33,68 @@
     return KADM5_OK;
 }
 
-#ifdef HESIOD
-/* stolen from v4sever/kadm_funcs.c */
-static char *
-reverse(str)
-    char    *str;
+kadm5_ret_t
+init_pwqual(kadm5_server_handle_t handle)
 {
-    static char newstr[80];
-    char    *p, *q;
-    int     i;
+    krb5_error_code ret;
+    pwqual_handle *list, *h;
+    const char *dict_file = NULL;
 
-    i = strlen(str);
-    if (i >= sizeof(newstr))
-        i = sizeof(newstr)-1;
-    p = str+i-1;
-    q = newstr;
-    q[i]='\0';
-    for(; i > 0; i--)
-        *q++ = *p--;
+    ret = k5_plugin_register(handle->context, PLUGIN_INTERFACE_PWQUAL,
+                             "dict", pwqual_dict_init);
+    if (ret != 0)
+        return ret;
 
-    return(newstr);
-}
-#endif /* HESIOD */
+    ret = k5_plugin_register(handle->context, PLUGIN_INTERFACE_PWQUAL,
+                             "policy", pwqual_policy_init);
+    if (ret != 0)
+        return ret;
 
-#if 0
-static int
-lower(str)
-    char    *str;
-{
-    register char   *cp;
-    int     effect=0;
+    ret = k5_pwqual_load(handle->context, &list);
+    if (ret != 0)
+        return ret;
 
-    for (cp = str; *cp; cp++) {
-        if (isupper(*cp)) {
-            *cp = tolower(*cp);
-            effect++;
+    if (handle->params.mask & KADM5_CONFIG_DICT_FILE)
+        dict_file = handle->params.dict_file;
+
+    for (h = list; *h != NULL; h++) {
+        ret = k5_pwqual_open(handle->context, *h, dict_file);
+        if (ret != 0) {
+            /* Close any previously opened modules and error out. */
+            for (; h > list; h--)
+                k5_pwqual_close(handle->context, *(h - 1));
+            k5_pwqual_free_handles(handle->context, list);
+            return ret;
         }
     }
-    return(effect);
+
+    handle->qual_handles = list;
+    return 0;
 }
-#endif
 
-#ifdef HESIOD
-static int
-str_check_gecos(gecos, pwstr)
-    char    *gecos;
-    char    *pwstr;
+/* Check a password against all available password quality plugin modules. */
+kadm5_ret_t
+passwd_check(kadm5_server_handle_t handle, const char *password,
+             kadm5_policy_ent_t policy, krb5_principal princ)
 {
-    char            *cp, *ncp, *tcp;
+    krb5_error_code ret;
+    pwqual_handle *h;
 
-    for (cp = gecos; *cp; ) {
-        /* Skip past punctuation */
-        for (; *cp; cp++)
-            if (isalnum(*cp))
-                break;
-        /* Skip to the end of the word */
-        for (ncp = cp; *ncp; ncp++)
-            if (!isalnum(*ncp) && *ncp != '\'')
-                break;
-        /* Delimit end of word */
-        if (*ncp)
-            *ncp++ = '\0';
-        /* Check word to see if it's the password */
-        if (*cp) {
-            if (!strcasecmp(pwstr, cp))
-                return 1;
-            tcp = reverse(cp);
-            if (!strcasecmp(pwstr, tcp))
-                return 1;
-            cp = ncp;
-        } else
-            break;
+    for (h = handle->qual_handles; *h != NULL; h++) {
+        ret = k5_pwqual_check(handle->context, *h, password, policy, princ);
+        if (ret != 0)
+            return ret;
     }
     return 0;
 }
-#endif /* HESIOD */
 
-/* some of this is stolen from gatekeeper ... */
-kadm5_ret_t
-passwd_check(kadm5_server_handle_t handle,
-             char *password, int use_policy, kadm5_policy_ent_t pol,
-             krb5_principal principal)
+void
+destroy_pwqual(kadm5_server_handle_t handle)
 {
-    int     nupper = 0,
-        nlower = 0,
-        ndigit = 0,
-        npunct = 0,
-        nspec = 0;
-    char    c, *s, *cp;
-#ifdef HESIOD
-    extern  struct passwd *hes_getpwnam();
-    struct  passwd *ent;
-#endif
+    pwqual_handle *h;
 
-    if(use_policy) {
-        if(strlen(password) < pol->pw_min_length)
-            return KADM5_PASS_Q_TOOSHORT;
-        s = password;
-        while ((c = *s++)) {
-            if (islower((unsigned char) c)) {
-                nlower = 1;
-                continue;
-            }
-            else if (isupper((unsigned char) c)) {
-                nupper = 1;
-                continue;
-            } else if (isdigit((unsigned char) c)) {
-                ndigit = 1;
-                continue;
-            } else if (ispunct((unsigned char) c)) {
-                npunct = 1;
-                continue;
-            } else {
-                nspec = 1;
-                continue;
-            }
-        }
-        if ((nupper + nlower + ndigit + npunct + nspec) < pol->pw_min_classes)
-            return KADM5_PASS_Q_CLASS;
-        if((find_word(password) == KADM5_OK))
-            return KADM5_PASS_Q_DICT;
-        else {
-            int i, n = krb5_princ_size(handle->context, principal);
-            cp = krb5_princ_realm(handle->context, principal)->data;
-            if (strcasecmp(cp, password) == 0)
-                return KADM5_PASS_Q_DICT;
-            for (i = 0; i < n ; i++) {
-                cp = krb5_princ_component(handle->context, principal, i)->data;
-                if (strcasecmp(cp, password) == 0)
-                    return KADM5_PASS_Q_DICT;
-#ifdef HESIOD
-                ent = hes_getpwnam(cp);
-                if (ent && ent->pw_gecos)
-                    if (str_check_gecos(ent->pw_gecos, password))
-                        return KADM5_PASS_Q_DICT; /* XXX new error code? */
-#endif
-            }
-            return KADM5_OK;
-        }
-    } else {
-        if (strlen(password) < 1)
-            return KADM5_PASS_Q_TOOSHORT;
-    }
-    return KADM5_OK;
+    for (h = handle->qual_handles; *h != NULL; h++)
+        k5_pwqual_close(handle->context, *h);
+    k5_pwqual_free_handles(handle->context, handle->qual_handles);
+    handle->qual_handles = NULL;
 }

Modified: branches/plugins2/src/lib/kadm5/srv/svr_principal.c
===================================================================
--- branches/plugins2/src/lib/kadm5/srv/svr_principal.c	2010-07-22 03:03:15 UTC (rev 24202)
+++ branches/plugins2/src/lib/kadm5/srv/svr_principal.c	2010-07-22 03:13:38 UTC (rev 24203)
@@ -292,7 +292,7 @@
         have_polent = TRUE;
     }
     if (password) {
-        ret = passwd_check(handle, password, have_polent, &polent,
+        ret = passwd_check(handle, password, have_polent ? &polent : NULL,
                            entry->principal);
         if (ret)
             goto cleanup;
@@ -1341,8 +1341,8 @@
         have_pol = 1;
     }
 
-    if ((ret = passwd_check(handle, password, adb.aux_attributes &
-                            KADM5_POLICY, &pol, principal)))
+    if ((ret = passwd_check(handle, password, have_pol ? &pol : NULL,
+                            principal)))
         goto done;
 
     ret = krb5_dbe_find_act_mkey(handle->context, master_keylist,

Modified: branches/plugins2/src/lib/krb5/krb/Makefile.in
===================================================================
--- branches/plugins2/src/lib/krb5/krb/Makefile.in	2010-07-22 03:03:15 UTC (rev 24202)
+++ branches/plugins2/src/lib/krb5/krb/Makefile.in	2010-07-22 03:13:38 UTC (rev 24203)
@@ -74,6 +74,7 @@
 	pac.o		\
 	pac_sign.o	\
 	parse.o		\
+	plugin.o	\
 	pr_to_salt.o	\
 	preauth2.o	\
 	gic_opt_set_pa.o	\
@@ -173,6 +174,7 @@
 	$(OUTPRE)pac.$(OBJEXT)		\
 	$(OUTPRE)pac_sign.$(OBJEXT)	\
 	$(OUTPRE)parse.$(OBJEXT)	\
+	$(OUTPRE)plugin.$(OBJEXT)	\
 	$(OUTPRE)pr_to_salt.$(OBJEXT)	\
 	$(OUTPRE)preauth2.$(OBJEXT)	\
 	$(OUTPRE)gic_opt_set_pa.$(OBJEXT)	\
@@ -273,6 +275,7 @@
 	$(srcdir)/pac.c		\
 	$(srcdir)/pac_sign.c	\
 	$(srcdir)/parse.c	\
+	$(srcdir)/plugin.c	\
 	$(srcdir)/pr_to_salt.c	\
 	$(srcdir)/preauth2.c	\
 	$(srcdir)/gic_opt_set_pa.c	\

Modified: branches/plugins2/src/lib/krb5/krb/init_ctx.c
===================================================================
--- branches/plugins2/src/lib/krb5/krb/init_ctx.c	2010-07-22 03:03:15 UTC (rev 24202)
+++ branches/plugins2/src/lib/krb5/krb/init_ctx.c	2010-07-22 03:13:38 UTC (rev 24203)
@@ -273,6 +273,8 @@
         ctx->trace_callback(ctx, NULL, ctx->trace_callback_data);
 #endif
 
+    k5_plugin_free_context(ctx);
+
     ctx->magic = 0;
     free(ctx);
 }

Added: branches/plugins2/src/lib/krb5/krb/plugin.c
===================================================================
--- branches/plugins2/src/lib/krb5/krb/plugin.c	                        (rev 0)
+++ branches/plugins2/src/lib/krb5/krb/plugin.c	2010-07-22 03:13:38 UTC (rev 24203)
@@ -0,0 +1,382 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ * lib/krb5/krb/plugin.c
+ *
+ * Copyright (C) 2010 by the Massachusetts Institute of Technology.
+ * All rights reserved.
+ *
+ * Export of this software from the United States of America may
+ *   require a specific license from the United States Government.
+ *   It is the responsibility of any person or organization contemplating
+ *   export to obtain such a license before exporting.
+ *
+ * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
+ * distribute this software and its documentation for any purpose and
+ * without fee is hereby granted, provided that the above copyright
+ * notice appear in all copies and that both that copyright notice and
+ * this permission notice appear in supporting documentation, and that
+ * the name of M.I.T. not be used in advertising or publicity pertaining
+ * to distribution of the software without specific, written prior
+ * permission.  Furthermore if you modify this software you must label
+ * your software as modified software and not distribute it in such a
+ * fashion that it might be confused with the original M.I.T. software.
+ * M.I.T. makes no representations about the suitability of
+ * this software for any purpose.  It is provided "as is" without express
+ * or implied warranty.
+ *
+ *
+ * Plugin framework functions
+ */
+
+#include "k5-int.h"
+
+const char *interface_names[PLUGIN_NUM_INTERFACES] = {
+    "pwqual"
+};
+
+/* Return the context's interface structure for id, or NULL if invalid. */
+static inline struct plugin_interface *
+get_interface(krb5_context context, int id)
+{
+    if (context == NULL || id < 0 || id >= PLUGIN_NUM_INTERFACES)
+	return NULL;
+    return &context->plugins[id];
+}
+
+/* Release the memory associated with the linked list entry map. */
+static void
+free_plugin_mapping(struct plugin_mapping *map)
+{
+    if (map == NULL)
+	return;
+    free(map->modname);
+    if (map->dyn_handle != NULL)
+	krb5int_close_plugin(map->dyn_handle);
+    free(map);
+}
+
+/*
+ * Register a mapping from modname to module.  On success, dyn_handle is
+ * remembered in the mapping and will be released when the mapping is
+ * overwritten or the context is destroyed.
+ */
+static krb5_error_code
+register_module(krb5_context context, struct plugin_interface *interface,
+		const char *modname, krb5_plugin_init_fn module,
+		struct plugin_file_handle *dyn_handle)
+{
+    struct plugin_mapping *map, **pmap;
+
+    /* If a mapping already exists for modname, remove it. */
+    for (pmap = &interface->modules; *pmap != NULL; pmap = &(*pmap)->next) {
+	map = *pmap;
+	if (strcmp(map->modname, modname) == 0) {
+	    *pmap = map->next;
+	    free_plugin_mapping(map);
+	    break;
+	}
+    }
+
+    /* Create a new mapping structure. */
+    map = malloc(sizeof(*map));
+    if (map == NULL)
+	return ENOMEM;
+    map->modname = strdup(modname);
+    if (map->modname == NULL) {
+	free(map);
+	return ENOMEM;
+    }
+    map->module = module;
+    map->dyn_handle = dyn_handle;
+
+    /* Chain it into the list. */
+    map->next = interface->modules;
+    interface->modules = map;
+    return 0;
+}
+
+/* Parse a profile module string of the form "modname:modpath" into its
+ * component parts. */
+static krb5_error_code
+parse_modstr(krb5_context context, const char *modstr,
+	     char **modname, char **modpath)
+{
+    const char *sep;
+    char *name = NULL, *path = NULL;
+
+    *modname = NULL;
+    *modpath = NULL;
+
+    sep = strchr(modstr, ':');
+    if (sep == NULL) {
+	krb5_set_error_message(context, EINVAL, "Invalid module string %s",
+			       modstr);
+	return EINVAL; /* XXX create specific error code */
+    }
+
+    /* Copy the module name. */
+    name = malloc(sep - modstr + 1);
+    if (name == NULL)
+	return ENOMEM;
+    memcpy(name, modstr, sep - modstr);
+    name[sep - modstr] = '\0';
+
+    /* Copy the module path. */
+    path = strdup(sep + 1);
+    if (path == NULL) {
+	free(name);
+	return ENOMEM;
+    }
+
+    *modname = name;
+    *modpath = path;
+    return 0;
+}
+
+/* Open a dynamic object at modpath, look up symname within it, and register
+ * the resulting init function as modname. */
+static krb5_error_code
+open_and_register(krb5_context context, struct plugin_interface *interface,
+		  const char *modname, const char *modpath,
+		  const char *symname)
+{
+    krb5_error_code ret;
+    struct plugin_file_handle *handle;
+    void (*init_fn)();
+
+    ret = krb5int_open_plugin(modpath, &handle, &context->err);
+    if (ret != 0)
+	return ret;
+
+    ret = krb5int_get_plugin_func(handle, symname, &init_fn, &context->err);
+    if (ret != 0) {
+	krb5int_close_plugin(handle);
+	return ret;
+    }
+
+    ret = register_module(context, interface, modname,
+			  (krb5_plugin_init_fn)init_fn, handle);
+    if (ret != 0)
+	krb5int_close_plugin(handle);
+    return ret;
+}
+
+/* Register the plugins given by the profile strings in modules. */
+static krb5_error_code
+register_dyn_modules(krb5_context context, struct plugin_interface *interface,
+		     const char *iname, char **modules)
+{
+    krb5_error_code ret;
+    char *modname = NULL, *modpath = NULL, *symname = NULL;
+
+    for (; *modules != NULL; modules++) {
+	ret = parse_modstr(context, *modules, &modname, &modpath);
+	if (ret != 0)
+	    return ret;
+	if (asprintf(&symname, "%s_%s_init", iname, modname) < 0) {
+	    free(modname);
+	    free(modpath);
+	    return ENOMEM;
+	}
+	/* XXX should errors here be fatal, or just ignore the module? */
+	ret = open_and_register(context, interface, modname, modpath, symname);
+	free(modname);
+	free(modpath);
+	free(symname);
+	if (ret != 0)
+	    return ret;
+    }
+    return 0;
+}
+
+/* Return true if value is found in list. */
+static krb5_boolean
+find_in_list(char **list, const char *value)
+{
+    for (; *list != NULL; list++) {
+	if (strcmp(*list, value) == 0)
+	    return TRUE;
+    }
+    return FALSE;
+}
+
+/* Remove any registered modules whose names are not present in enable. */
+static void
+filter_enable(krb5_context context, struct plugin_interface *interface,
+	      char **enable)
+{
+    struct plugin_mapping *map, **pmap;
+
+    pmap = &interface->modules;
+    while (*pmap != NULL) {
+	map = *pmap;
+	if (!find_in_list(enable, map->modname)) {
+	    *pmap = map->next;
+	    free_plugin_mapping(map);
+	} else
+	    pmap = &map->next;
+    }
+}
+
+/* Remove any registered modules whose names are present in disable. */
+static void
+filter_disable(krb5_context context, struct plugin_interface *interface,
+	       char **disable)
+{
+    struct plugin_mapping *map, **pmap;
+
+    pmap = &interface->modules;
+    while (*pmap != NULL) {
+	map = *pmap;
+	if (find_in_list(disable, map->modname)) {
+	    *pmap = map->next;
+	    free_plugin_mapping(map);
+	} else
+	    pmap = &map->next;
+    }
+}
+
+/* Ensure that a plugin interface is configured.  id is assumed to be valid. */
+static krb5_error_code
+configure_interface(krb5_context context, int id)
+{
+    krb5_error_code ret;
+    struct plugin_interface *interface = &context->plugins[id];
+    const char *iname = interface_names[id];
+    char **modules = NULL, **enable = NULL, **disable = NULL;
+    static const char *path[4];
+
+    if (interface->configured)
+	return 0;
+
+    /* Read the configuration variables for this interface. */
+    path[0] = KRB5_CONF_PLUGINS;
+    path[1] = iname;
+    path[2] = KRB5_CONF_MODULE;
+    path[3] = NULL;
+    ret = profile_get_values(context->profile, path, &modules);
+    if (ret != 0 && ret != PROF_NO_RELATION)
+        goto cleanup;
+    path[2] = KRB5_CONF_ENABLE_ONLY;
+    ret = profile_get_values(context->profile, path, &enable);
+    if (ret != 0 && ret != PROF_NO_RELATION)
+        goto cleanup;
+    path[2] = KRB5_CONF_DISABLE;
+    ret = profile_get_values(context->profile, path, &disable);
+    if (ret != 0 && ret != PROF_NO_RELATION)
+        goto cleanup;
+
+    if (modules != NULL) {
+	ret = register_dyn_modules(context, interface, iname, modules);
+	if (ret != 0)
+	    return ret;
+    }
+    if (enable != NULL)
+	filter_enable(context, interface, enable);
+    if (disable != NULL)
+	filter_disable(context, interface, disable);
+
+    ret = 0;
+cleanup:
+    profile_free_list(modules);
+    profile_free_list(enable);
+    profile_free_list(disable);
+    return ret;
+}
+
+krb5_error_code
+k5_plugin_load(krb5_context context, int interface_id, const char *modname,
+               krb5_plugin_init_fn *module)
+{
+    krb5_error_code ret;
+    struct plugin_interface *interface = get_interface(context, interface_id);
+    struct plugin_mapping *map;
+
+    if (interface == NULL)
+	return EINVAL;
+    ret = configure_interface(context, interface_id);
+    if (ret != 0)
+	return ret;
+    for (map = interface->modules; map != NULL; map = map->next) {
+	if (strcmp(map->modname, modname) == 0) {
+	    *module = map->module;
+	    return 0;
+	}
+    }
+    return ENOENT; /* XXX Create error code? */
+}
+
+krb5_error_code
+k5_plugin_load_all(krb5_context context, int interface_id,
+                   krb5_plugin_init_fn **modules)
+{
+    krb5_error_code ret;
+    struct plugin_interface *interface = get_interface(context, interface_id);
+    struct plugin_mapping *map;
+    krb5_plugin_init_fn *list;
+    size_t count;
+
+    if (interface == NULL)
+	return EINVAL;
+    ret = configure_interface(context, interface_id);
+    if (ret != 0)
+	return ret;
+
+    /* Count the modules and allocate a list to hold them. */
+    count = 0;
+    for (map = interface->modules; map != NULL; map = map->next)
+	count++;
+    list = malloc((count + 1) * sizeof(*list));
+    if (list == NULL)
+	return ENOMEM;
+
+    /* Place each module's init function into list. */
+    count = 0;
+    for (map = interface->modules; map != NULL; map = map->next)
+	list[count++] = map->module;
+    list[count] = NULL;
+
+    *modules = list;
+    return 0;
+}
+
+void
+k5_plugin_free_modules(krb5_context context, krb5_plugin_init_fn *modules)
+{
+    free(modules);
+}
+
+krb5_error_code
+k5_plugin_register(krb5_context context, int interface_id, const char *modname,
+                   krb5_plugin_init_fn module)
+{
+    struct plugin_interface *interface = get_interface(context, interface_id);
+
+    if (interface == NULL)
+	return EINVAL;
+
+    /* Disallow registering plugins after load.  We may need to reconsider
+     * this, but it simplifies the design. */
+    if (interface->configured)
+	return EINVAL;
+
+    return register_module(context, interface, modname, module, NULL);
+}
+
+void
+k5_plugin_free_context(krb5_context context)
+{
+    int i;
+    struct plugin_interface *interface;
+    struct plugin_mapping *map, *next;
+
+    for (i = 0; i < PLUGIN_NUM_INTERFACES; i++) {
+	interface = &context->plugins[i];
+	for (map = interface->modules; map != NULL; map = next) {
+	    next = map->next;
+	    free_plugin_mapping(map);
+	}
+	interface->modules = NULL;
+	interface->configured = FALSE;
+    }
+}

Modified: branches/plugins2/src/lib/krb5/libkrb5.exports
===================================================================
--- branches/plugins2/src/lib/krb5/libkrb5.exports	2010-07-22 03:03:15 UTC (rev 24202)
+++ branches/plugins2/src/lib/krb5/libkrb5.exports	2010-07-22 03:13:38 UTC (rev 24203)
@@ -104,6 +104,10 @@
 initialize_krb5_error_table
 initialize_kv5m_error_table
 initialize_prof_error_table
+k5_plugin_free_modules
+k5_plugin_load
+k5_plugin_load_all
+k5_plugin_register
 krb524_convert_creds_kdc
 krb524_init_ets
 krb5_425_conv_principal




More information about the cvs-krb5 mailing list