]> git.ipfire.org Git - thirdparty/krb5.git/commitdiff
Revise the password quality pluggable interface to match the project
authorGreg Hudson <ghudson@mit.edu>
Sat, 28 Aug 2010 22:39:40 +0000 (22:39 +0000)
committerGreg Hudson <ghudson@mit.edu>
Sat, 28 Aug 2010 22:39:40 +0000 (22:39 +0000)
page:

* Modules receive the policy name but not the policy object.
* Enforcement of password policy is out of the interface's scope.
* Built-in modules are: empty, dict, hesiod, princ.
* The consumer API loader takes care of open/close, so there is only
  a wrapper function for check.

The project page is at:
http://k5wiki.kerberos.org/wiki/Projects/Password_quality_pluggable_interface

git-svn-id: svn://anonsvn.mit.edu/krb5/branches/plugins2@24266 dc483132-0cff-0310-8789-dd5450dbe970

src/include/krb5/pwqual_plugin.h
src/lib/kadm5/server_internal.h
src/lib/kadm5/srv/Makefile.in
src/lib/kadm5/srv/pwqual.c
src/lib/kadm5/srv/pwqual_dict.c
src/lib/kadm5/srv/pwqual_empty.c [moved from src/lib/kadm5/srv/pwqual_policy.c with 58% similarity]
src/lib/kadm5/srv/pwqual_hesiod.c [new file with mode: 0644]
src/lib/kadm5/srv/pwqual_princ.c [new file with mode: 0644]
src/lib/kadm5/srv/server_misc.c

index 120ed5fdafad68663dcfcb88ebe247f406893920..0613705be4c1ac679a00141eab031aae568a4f29 100644 (file)
@@ -51,8 +51,6 @@
 
 #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;
@@ -65,12 +63,14 @@ typedef krb5_error_code
 (*krb5_pwqual_open_fn)(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. */
+/*
+ * Mandatory: Check a password for the principal princ, which has an associated
+ * password policy named policy_name (or no associated policy if policy_name is
+ * NULL).  Return an error if the password check fails.
+ */
 typedef krb5_error_code
 (*krb5_pwqual_check_fn)(krb5_context context, krb5_pwqual_moddata data,
-                        const char *password, kadm5_policy_ent_t policy,
+                        const char *password, const char *policy_name,
                         krb5_principal princ);
 
 /* Optional: Release resources used by module data. */
index ceab3c965d1e554dd3cff7fd79ae3d93402ed883..076c18fc0aa4c054338077a1a8fc8eefb55d7dd9 100644 (file)
@@ -158,32 +158,23 @@ 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. */
+/* Load all available password quality plugin modules, bind each module to the
+ * realm's dictionary file, 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);
+k5_pwqual_load(krb5_context context, pwqual_handle **handles,
+               const char *dict_file);
 
-/* Release a handle list allocated by k5_pwqual_load.  All modules must have
- * been closed by the caller. */
-krb5_error_code
+/* Release a handle list allocated by k5_pwqual_load. */
+void
 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. */
+/* Check a password using a password quality plugin module. */
 krb5_error_code
 k5_pwqual_check(krb5_context context, pwqual_handle handle,
-                const char *password, kadm5_policy_ent_t policy,
+                const char *password, const char *policy_name,
                 krb5_principal princ);
 
-/* Release the memory used by a password quality plugin. */
-void
-k5_pwqual_close(krb5_context context, pwqual_handle handle);
-
 /*** initvt functions for built-in password quality modules ***/
 
 /* The dict module checks passwords against the realm's dictionary. */
@@ -191,9 +182,20 @@ krb5_error_code
 pwqual_dict_initvt(krb5_context context, int maj_ver, int min_ver,
                    krb5_plugin_vtable vtable);
 
-/* The policy module enforces password policy constraints. */
+/* The empty module rejects empty passwords (even with no password policy). */
+krb5_error_code
+pwqual_empty_initvt(krb5_context context, int maj_ver, int min_ver,
+                    krb5_plugin_vtable vtable);
+
+/* The hesiod module checks passwords against GECOS fields from Hesiod passwd
+ * information (only if the tree was built with Hesiod support). */
 krb5_error_code
-pwqual_policy_initvt(krb5_context context, int maj_ver, int min_ver,
+pwqual_hesiod_initvt(krb5_context context, int maj_ver, int min_ver,
                      krb5_plugin_vtable vtable);
 
+/* The princ module checks passwords against principal components. */
+krb5_error_code
+pwqual_princ_initvt(krb5_context context, int maj_ver, int min_ver,
+                    krb5_plugin_vtable vtable);
+
 #endif /* __KADM5_SERVER_INTERNAL_H__ */
index 6000d73b6ac3d9351e9b217153d4e7349ecd4bbc..a513035d56ff1e762709198c04af1cd33bd4120e 100644 (file)
@@ -29,7 +29,9 @@ RELDIR=kadm5/srv
 
 SRCS = $(srcdir)/pwqual.c \
        $(srcdir)/pwqual_dict.c \
-       $(srcdir)/pwqual_policy.c \
+       $(srcdir)/pwqual_empty.c \
+       $(srcdir)/pwqual_hesiod.c \
+       $(srcdir)/pwqual_princ.c \
        $(srcdir)/svr_policy.c \
        $(srcdir)/svr_principal.c \
        $(srcdir)/server_acl.c \
@@ -42,7 +44,9 @@ SRCS =        $(srcdir)/pwqual.c \
 
 OBJS = pwqual.$(OBJEXT) \
        pwqual_dict.$(OBJEXT) \
-       pwqual_policy.$(OBJECT) \
+       pwqual_empty.$(OBJEXT) \
+       pwqual_hesiod.$(OBJEXT) \
+       pwqual_princ.$(OBJEXT) \
        svr_policy.$(OBJEXT) \
        svr_principal.$(OBJEXT) \
        server_acl.$(OBJEXT) \
@@ -56,7 +60,9 @@ OBJS =        pwqual.$(OBJEXT) \
 STLIBOBJS = \
        pwqual.o \
        pwqual_dict.o \
-       pwqual_policy.o \
+       pwqual_empty.o \
+       pwqual_hesiod.o \
+       pwqual_princ.o \
        svr_policy.o \
        svr_principal.o \
        server_acl.o \
index c40f74fb85df13b17aa4f86fb0694ebcd8563650..cc92612d05679ba427fe573870bac9bcc4aa22ca 100644 (file)
@@ -25,7 +25,7 @@
  * or implied warranty.
  *
  *
- * Consumer interface for password quality plugins
+ * Consumer interface for password quality plugins.
  */
 
 #include "k5-int.h"
@@ -38,7 +38,8 @@ struct pwqual_handle_st {
 };
 
 krb5_error_code
-k5_pwqual_load(krb5_context context, pwqual_handle **handles)
+k5_pwqual_load(krb5_context context, pwqual_handle **handles,
+               const char *dict_file)
 {
     krb5_error_code ret;
     krb5_plugin_initvt_fn *modules = NULL, *mod;
@@ -55,60 +56,60 @@ k5_pwqual_load(krb5_context context, pwqual_handle **handles)
     if (list == NULL)
         goto cleanup;
 
-    /* For each module, allocate a handle and initialize its vtable.  Skip
-     * modules which don't successfully initialize. */
+    /* For each module, allocate a handle, initialize its vtable, and bind the
+     * dictionary filename. */
     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
+        if (ret != 0) {         /* Failed vtable init is non-fatal. */
             free(handle);
+            continue;
+        }
+        handle->data = NULL;
+        if (handle->vt.open != NULL) {
+            ret = handle->vt.open(context, dict_file, &handle->data);
+            if (ret != 0)       /* Failed dictionary binding is fatal. */
+                goto cleanup;
+        }
+        list[count++] = handle;
+        list[count] = NULL;
+        handle = NULL;
     }
+    list[count] = NULL;
 
     *handles = list;
     list = NULL;
 
 cleanup:
+    free(handle);
     k5_plugin_free_modules(context, modules);
     k5_pwqual_free_handles(context, list);
     return ret;
 }
 
-krb5_error_code
+void
 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);
-}
+    pwqual_handle *hp, handle;
 
-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);
+    if (handles == NULL)
+        return;
+    for (hp = handles; *hp != NULL; hp++) {
+        handle = *hp;
+        if (handle->vt.close != NULL)
+            handle->vt.close(context, handle->data);
+    }
+    free(handles);
 }
 
 krb5_error_code
 k5_pwqual_check(krb5_context context, pwqual_handle handle,
-                const char *password, kadm5_policy_ent_t policy,
+                const char *password, const char *policy_name,
                 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;
+    return handle->vt.check(context, handle->data, password, policy_name,
+                            princ);
 }
index 23b851848a63a490e87c68d1cb4ae836594e1f1a..18892a0e74db729b606f83393d11b36ab6a5fc22 100644 (file)
@@ -1,16 +1,38 @@
 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
 /*
+ * lib/kadm5/srv/pwqual_dict.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.
+ *
+ * Dictionary initialization and lookup code is (see top-level NOTICE file for
+ * license):
+ *
  * Copyright 1993 OpenVision Technologies, Inc., All Rights Reserved
  *
- * $Header$
+ *
+ * Password quality module to look up passwords within the realm dictionary.
  */
 
-/* Password quality module to look up paswords within the realm dictionary. */
-
-#if !defined(lint) && !defined(__CODECENTER__)
-static char *rcsid = "$Header$";
-#endif
-
 #include "k5-platform.h"
 #include <krb5/pwqual_plugin.h>
 #include <sys/types.h>
@@ -191,15 +213,13 @@ dict_open(krb5_context context, const char *dict_file,
  * 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,
+           const char *password, const char *policy_name,
            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)
+    if (policy_name == NULL)
         return 0;
 
     /* Check against words in the dictionary if we successfully loaded one. */
@@ -208,16 +228,6 @@ dict_check(krb5_context context, krb5_pwqual_moddata data,
                 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;
 }
 
similarity index 58%
rename from src/lib/kadm5/srv/pwqual_policy.c
rename to src/lib/kadm5/srv/pwqual_empty.c
index 4c7c5866a5c325435196fab8014e0f2ecae54c2f..ba25502581d9444ba5e79acea2a85bf133a7e2f5 100644 (file)
@@ -1,6 +1,6 @@
 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
 /*
- * lib/kadm5/srv/pwqual_policy.c
+ * lib/kadm5/srv/pwqual_empty.c
  *
  * Copyright (C) 2010 by the Massachusetts Institute of Technology.
  * All rights reserved.
  * or implied warranty.
  *
  *
- * Password quality module to enforce password policy
+ * Password quality module to reject empty passwords.
  */
 
 #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)
+empty_check(krb5_context context, krb5_pwqual_moddata data,
+            const char *password, const char *policy_name,
+            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)
+    /* Unlike other built-in modules, this one operates even for principals
+     * with no password policy. */
+    if (*password == '\0')
         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_initvt(krb5_context context, int maj_ver, int min_ver,
-                     krb5_plugin_vtable vtable)
+pwqual_empty_initvt(krb5_context context, int maj_ver, int min_ver,
+                    krb5_plugin_vtable vtable)
 {
     krb5_pwqual_vtable vt;
 
     if (maj_ver != 1)
         return KRB5_PLUGIN_VER_NOTSUPP;
     vt = (krb5_pwqual_vtable)vtable;
-    vt->check = policy_check;
+    vt->check = empty_check;
     return 0;
 }
diff --git a/src/lib/kadm5/srv/pwqual_hesiod.c b/src/lib/kadm5/srv/pwqual_hesiod.c
new file mode 100644 (file)
index 0000000..f8862a3
--- /dev/null
@@ -0,0 +1,133 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ * lib/kadm5/srv/pwqual_hesiod.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 check passwords against GECOS fields of Hesiod
+ * passwd information, if the tree is compiled with Hesiod support.
+ */
+
+#include "k5-platform.h"
+#include <krb5/pwqual_plugin.h>
+#include "server_internal.h"
+#include <ctype.h>
+
+#ifdef HESIOD
+#include <pwd.h>
+
+static char *
+reverse(char *str, char *newstr, size_t newstr_size)
+{
+    char *p, *q;
+    size_t i;
+
+    i = strlen(str);
+    if (i >= newstr_size)
+        i = newstr_size - 1;
+    p = str + i - 1;
+    q = newstr;
+    q[i] = '\0';
+    for (; i > 0; i--)
+        *q++ = *p--;
+
+    return newstr;
+}
+
+static int
+str_check_gecos(char *gecos, const char *pwstr)
+{
+    char *cp, *ncp, *tcp, revbuf[80];
+
+    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, revbuf, sizeof(revbuf));
+            if (!strcasecmp(pwstr, tcp))
+                return 1;
+            cp = ncp;
+        } else
+            break;
+    }
+    return 0;
+}
+#endif /* HESIOD */
+
+static krb5_error_code
+hesiod_check(krb5_context context, krb5_pwqual_moddata data,
+             const char *password, const char *policy_name,
+             krb5_principal princ)
+{
+#ifdef HESIOD
+    extern struct passwd *hes_getpwnam();
+    struct passwd *ent;
+    int i, n;
+    const char *cp;
+
+    /* Don't check for principals with no password policy. */
+    if (policy_name == NULL)
+        return 0;
+
+    n = krb5_princ_size(handle->context, princ);
+    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;
+        ent = hes_getpwnam(cp);
+        if (ent && ent->pw_gecos && str_check_gecos(ent->pw_gecos, password))
+            return KADM5_PASS_Q_DICT;
+    }
+#endif /* HESIOD */
+    return 0;
+}
+
+krb5_error_code
+pwqual_hesiod_initvt(krb5_context context, int maj_ver, int min_ver,
+                     krb5_plugin_vtable vtable)
+{
+    krb5_pwqual_vtable vt;
+
+    if (maj_ver != 1)
+        return KRB5_PLUGIN_VER_NOTSUPP;
+    vt = (krb5_pwqual_vtable)vtable;
+    vt->check = hesiod_check;
+    return 0;
+}
diff --git a/src/lib/kadm5/srv/pwqual_princ.c b/src/lib/kadm5/srv/pwqual_princ.c
new file mode 100644 (file)
index 0000000..06393a1
--- /dev/null
@@ -0,0 +1,72 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ * lib/kadm5/srv/pwqual_princ.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 check passwords against principal components.
+ */
+
+#include "k5-platform.h"
+#include <krb5/pwqual_plugin.h>
+#include "server_internal.h"
+
+static krb5_error_code
+princ_check(krb5_context context, krb5_pwqual_moddata data,
+            const char *password, const char *policy_name,
+            krb5_principal princ)
+{
+    int i, n;
+    char *cp;
+
+    /* Don't check for principals with no password policy. */
+    if (policy_name == NULL)
+        return 0;
+
+    /* 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;
+}
+
+krb5_error_code
+pwqual_princ_initvt(krb5_context context, int maj_ver, int min_ver,
+                   krb5_plugin_vtable vtable)
+{
+    krb5_pwqual_vtable vt;
+
+    if (maj_ver != 1)
+        return KRB5_PLUGIN_VER_NOTSUPP;
+    vt = (krb5_pwqual_vtable)vtable;
+    vt->check = princ_check;
+    return 0;
+}
index 9bea86f440049162df8f6ca76b2f8f7fc0076cde..93369f0002a7a35deb2ca5f569008dfa379ade05 100644 (file)
@@ -1,14 +1,34 @@
 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
 /*
+ * Copyright (C) 2010 by the Massachusetts Institute of Technology.
+ * All rights reserved.
+ *
+ * Export of this software from the United States of America may
+ *   require a specific license from the United States Government.
+ *   It is the responsibility of any person or organization contemplating
+ *   export to obtain such a license before exporting.
+ *
+ * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
+ * distribute this software and its documentation for any purpose and
+ * without fee is hereby granted, provided that the above copyright
+ * notice appear in all copies and that both that copyright notice and
+ * this permission notice appear in supporting documentation, and that
+ * the name of M.I.T. not be used in advertising or publicity pertaining
+ * to distribution of the software without specific, written prior
+ * permission.  Furthermore if you modify this software you must label
+ * your software as modified software and not distribute it in such a
+ * fashion that it might be confused with the original M.I.T. software.
+ * M.I.T. makes no representations about the suitability of
+ * this software for any purpose.  It is provided "as is" without express
+ * or implied warranty.
+ *
+ * check_against_policy code is originally (see top-level NOTICE file for
+ * license):
+ *
  * Copyright 1993 OpenVision Technologies, Inc., All Rights Reserved
  *
- * $Header$
  */
 
-#if !defined(lint) && !defined(__CODECENTER__)
-static char *rcsid = "$Header$";
-#endif
-
 #include    "k5-int.h"
 #include    <kdb.h>
 #include    <ctype.h>
@@ -37,51 +57,86 @@ kadm5_ret_t
 init_pwqual(kadm5_server_handle_t handle)
 {
     krb5_error_code ret;
-    pwqual_handle *list, *h;
+    pwqual_handle *list;
     const char *dict_file = NULL;
 
+    /* Register the built-in password quality modules. */
     ret = k5_plugin_register(handle->context, PLUGIN_INTERFACE_PWQUAL,
                              "dict", pwqual_dict_initvt);
     if (ret != 0)
         return ret;
-
     ret = k5_plugin_register(handle->context, PLUGIN_INTERFACE_PWQUAL,
-                             "policy", pwqual_policy_initvt);
+                             "empty", pwqual_empty_initvt);
     if (ret != 0)
         return ret;
-
-    ret = k5_pwqual_load(handle->context, &list);
+    ret = k5_plugin_register(handle->context, PLUGIN_INTERFACE_PWQUAL,
+                             "hesiod", pwqual_hesiod_initvt);
+    if (ret != 0)
+        return ret;
+    ret = k5_plugin_register(handle->context, PLUGIN_INTERFACE_PWQUAL,
+                             "princ", pwqual_princ_initvt);
     if (ret != 0)
         return ret;
 
+    /* Load all available password quality modules. */
     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;
-        }
-    }
+    ret = k5_pwqual_load(handle->context, &list, dict_file);
+    if (ret != 0)
+        return ret;
 
     handle->qual_handles = list;
     return 0;
 }
 
-/* Check a password against all available password quality plugin modules. */
+/* Check that a password meets the quality constraints given in pol. */
+static kadm5_ret_t
+check_against_policy(kadm5_server_handle_t handle, const char *password,
+                     kadm5_policy_ent_t pol)
+{
+    int hasupper = 0, haslower = 0, hasdigit = 0, haspunct = 0, hasspec = 0;
+    int c, nclasses;
+
+    /* Check against the policy's minimum length. */
+    if (strlen(password) < (size_t)pol->pw_min_length)
+        return KADM5_PASS_Q_TOOSHORT;
+
+    /* Check against the policy's minimum number of character classes. */
+    while ((c = (unsigned char)*password++) != '\0') {
+        if (islower(c))
+            haslower = 1;
+        else if (isupper(c))
+            hasupper = 1;
+        else if (isdigit(c))
+            hasdigit = 1;
+        else if (ispunct(c))
+            haspunct = 1;
+        else
+            hasspec = 1;
+    }
+    nclasses = hasupper + haslower + hasdigit + haspunct + hasspec;
+    if (nclasses < pol->pw_min_classes)
+        return KADM5_PASS_Q_CLASS;
+    return KADM5_OK;
+}
+
+/* Check a password against all available password quality plugin modules
+ * and against policy. */
 kadm5_ret_t
 passwd_check(kadm5_server_handle_t handle, const char *password,
              kadm5_policy_ent_t policy, krb5_principal princ)
 {
     krb5_error_code ret;
     pwqual_handle *h;
+    const char *polname = (policy == NULL) ? NULL : policy->policy;
 
+    if (policy != NULL) {
+        ret = check_against_policy(handle, password, policy);
+        if (ret != 0)
+            return ret;
+    }
     for (h = handle->qual_handles; *h != NULL; h++) {
-        ret = k5_pwqual_check(handle->context, *h, password, policy, princ);
+        ret = k5_pwqual_check(handle->context, *h, password, polname, princ);
         if (ret != 0)
             return ret;
     }
@@ -91,10 +146,6 @@ passwd_check(kadm5_server_handle_t handle, const char *password,
 void
 destroy_pwqual(kadm5_server_handle_t handle)
 {
-    pwqual_handle *h;
-
-    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;
 }