Password quality pluggable interface
authorGreg Hudson <ghudson@mit.edu>
Wed, 1 Sep 2010 16:40:22 +0000 (16:40 +0000)
committerGreg Hudson <ghudson@mit.edu>
Wed, 1 Sep 2010 16:40:22 +0000 (16:40 +0000)
Merge branches/plugins2 to trunk.  Adds a password quality pluggable
interface described in this project page:

http://k5wiki.kerberos.org/wiki/Projects/Password_quality_pluggable_interface

ticket: 6765

git-svn-id: svn://anonsvn.mit.edu/krb5/trunk@24284 dc483132-0cff-0310-8789-dd5450dbe970

23 files changed:
doc/admin.texinfo
doc/krb5conf.texinfo
pwqual_combo/Makefile [new file with mode: 0644]
pwqual_combo/combo.c [new file with mode: 0644]
src/config-files/krb5.conf.M
src/include/Makefile.in
src/include/k5-int.h
src/include/krb5/pwqual_plugin.h [new file with mode: 0644]
src/lib/kadm5/kadm_err.et
src/lib/kadm5/server_internal.h
src/lib/kadm5/srv/Makefile.in
src/lib/kadm5/srv/libkadm5srv_mit.exports
src/lib/kadm5/srv/pwqual.c [new file with mode: 0644]
src/lib/kadm5/srv/pwqual_dict.c [new file with mode: 0644]
src/lib/kadm5/srv/pwqual_empty.c [new file with mode: 0644]
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_dict.c [deleted file]
src/lib/kadm5/srv/server_init.c
src/lib/kadm5/srv/server_misc.c
src/lib/kadm5/srv/svr_principal.c
src/lib/krb5/krb/Makefile.in
src/lib/krb5/krb/plugin.c

index 5da912768df86466590a20e80e944c58bdc0c5ce..fd1be4279e66478bc1608c8213ce28ea2638590b 100644 (file)
@@ -410,6 +410,7 @@ salt.  The supported values for salts are as follows.
 * capaths::                     
 * dbdefaults::                  
 * dbmodules::                   
+* plugins::
 * pkinit client options::
 * Sample krb5.conf File::       
 @end menu
@@ -1042,7 +1043,7 @@ This LDAP specific tag indicates the list of LDAP servers that the Kerberos serv
 This LDAP specific tag indicates the number of connections to be maintained per LDAP server. This value is used if the number of connections per LDAP server are not mentioned in the configuration section under [dbmodules]. The default value is 5.
 @end table
 
-@node dbmodules, pkinit client options, dbdefaults, krb5.conf
+@node dbmodules, plugins, dbdefaults, krb5.conf
 @subsection [dbmodules]
 
 Contains database specific parameters used by the database library. Each tag in the [dbmodules] section of the file names a configuration section for database specific parameters that can be referred to by a realm. The value of the tag is a subsection where the relations in that subsection define the database specific parameters.
@@ -1090,7 +1091,65 @@ This LDAP specific tags indicates the number of connections to be maintained per
 
 @end table
 
-@node pkinit client options, Sample krb5.conf File, dbmodules, krb5.conf
+@node plugins, pkinit client options, dbmodules, krb5.conf
+
+@menu
+* pwqual interface::             
+@end menu
+
+Tags in the [plugins] section can be used to register dynamic plugin
+modules and to turn modules on and off.  Not every krb5 pluggable
+interface uses the [plugins] section; the ones that do are documented
+here.
+
+Each pluggable interface corresponds to a subsection of [plugins].
+All subsections support the same tags:
+
+@table @b
+@itemx module
+This tag may have multiple values.  Each value is a string of the form
+"modulename:pathname", which causes the shared object located at
+pathname to be registered as a dynamic module named modulename for the
+pluggable interface.  If pathname is not an absolute path, it will be
+treated as relative to the "krb5/plugins" subdirectory of the krb5
+library directory.
+
+@itemx enable_only
+This tag may have multiple values.  If there are values for this tag,
+then only the named modules will be enabled for the pluggable
+interface.
+
+@itemx disable
+This tag may have multiple values.  If there are values for this tag,
+then the named modules will be disabled for the pluggable interface.
+@end table
+
+The following subsections are currently supported within the [plugins]
+section:
+
+@node pwqual interface, , plugins, plugins
+
+The pwqual subsection controls modules for the password quality
+interface, which is used to reject weak passwords when passwords are
+changed.  In addition to any registered dynamic modules, the following
+built-in modules exist (and may be disabled with the disable tag):
+
+@table @b
+@itemx dict
+Checks against the realm dictionary file
+
+@itemx empty
+Rejects empty passwords
+
+@itemx hesiod
+Checks against user information stored in Hesiod (only if Kerberos was
+built with Hesiod support)
+
+@itemx princ
+Checks against components of the principal name
+@end table
+
+@node pkinit client options, Sample krb5.conf File, plugins, krb5.conf
 @subsection pkinit options
 
 @menu
index 9114350619e34409df9d86a4057748b120f769f8..0b4b2d43778531ecbb7790221f7020d35b64e6b3 100644 (file)
@@ -89,6 +89,10 @@ client to determine the intermediate realms which may be used in
 cross-realm authentication.  It is also used by the end-service when
 checking the transited field for trusted intermediate realms.
 
+@itemx plugins
+Contains tags to register dynamic plugin modules and to turn modules on
+and off.
+
 @ignore
 this doesn't seem to be used
 @itemx kdc
diff --git a/pwqual_combo/Makefile b/pwqual_combo/Makefile
new file mode 100644 (file)
index 0000000..ee7905b
--- /dev/null
@@ -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
diff --git a/pwqual_combo/combo.c b/pwqual_combo/combo.c
new file mode 100644 (file)
index 0000000..1a52ef4
--- /dev/null
@@ -0,0 +1,187 @@
+/* -*- 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, const char *policy_name,
+            krb5_principal princ, const char **languages)
+{
+    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) {
+                krb5_set_error_message(context, KADM5_PASS_Q_DICT,
+                                       "Password may not be a pair of "
+                                       "dictionary words");
+                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_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->open = combo_open;
+    vt->check = combo_check;
+    vt->close = combo_close;
+    return 0;
+}
index db3305f5980f9de9855603793accdb8557258426..2995aa2bef73af2e04f5d34c3a3fe304e418f874 100644 (file)
@@ -110,6 +110,9 @@ Contains default values for database specific parameters.
 
 .IP [dbmodules]
 Contains database specific parameters used by the database library.
+
+.ip [plugins]
+Contains plugin module registration and filtering parameters.
 .PP 
 Each of these sections will be covered in more details in the following
 sections.
@@ -682,6 +685,59 @@ is whitespace-separated. The LDAP server is specified by a LDAP URI.
 .IP ldap_conns_per_server
 This LDAP specific tag indicates the number of connections to be maintained per
 LDAP server.
+
+.SH PLUGINS SECTION
+
+Tags in the [plugins] section can be used to register dynamic plugin
+modules and to turn modules on and off.  Not every krb5 pluggable
+interface uses the [plugins] section; the ones that do are documented
+here.
+
+.PP
+Each pluggable interface corresponds to a subsection of [plugins].
+All subsections support the same tags:
+
+.IP module
+This tag may have multiple values.  Each value is a string of the form
+"modulename:pathname", which causes the shared object located at
+pathname to be registered as a dynamic module named modulename for the
+pluggable interface.  If pathname is not an absolute path, it will be
+treated as relative to the "krb5/plugins" subdirectory of the krb5
+library directory.
+
+.IP enable_only
+This tag may have multiple values.  If there are values for this tag,
+then only the named modules will be enabled for the pluggable
+interface.
+
+.IP disable
+This tag may have multiple values.  If there are values for this tag,
+then the named modules will be disabled for the pluggable interface.
+
+.PP
+The following subsections are currently supported within the [plugins]
+section:
+
+.SS pwqual interface
+
+The pwqual subsection controls modules for the password quality
+interface, which is used to reject weak passwords when passwords are
+changed.  In addition to any registered dynamic modules, the following
+built-in modules exist (and may be disabled with the disable tag):
+
+.IP dict
+Checks against the realm dictionary file
+
+.IP empty
+Rejects empty passwords
+
+.IP hesiod
+Checks against user information stored in Hesiod (only if Kerberos was
+built with Hesiod support)
+
+.IP princ
+Checks against components of the principal name
+
 .SH FILES 
 /etc/krb5.conf
 .SH SEE ALSO
index 930712d18e9cf0cab2441b85fe456bdd0dd42a2c..5c178a24af9cdaf05f2594b849f2f8f606f29bca 100644 (file)
@@ -138,6 +138,7 @@ install-headers-unix install:: krb5/krb5.h profile.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
 
index ec701c1dfe9cbbf54c67b7287d3aafe36a66d42c..4b2fdd25dbf94bab749be202d27745ceda8f44f0 100644 (file)
@@ -1516,7 +1516,8 @@ struct plugin_interface {
 
 /* A list of plugin interface IDs.  Make sure to increment
  * PLUGIN_NUM_INTERFACES when a new interface is added. */
-#define PLUGIN_NUM_INTERFACES   0
+#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. */
diff --git a/src/include/krb5/pwqual_plugin.h b/src/include/krb5/pwqual_plugin.h
new file mode 100644 (file)
index 0000000..403bb11
--- /dev/null
@@ -0,0 +1,109 @@
+/* -*- 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.
+ *
+ * The password quality pluggable interface currently has only one supported
+ * major version, which is 1.  Major version 1 has a current minor version
+ * number of 1.
+ *
+ * Password quality plugin modules should define a function named
+ * pwqual_<modulename>_initvt, matching the signature:
+ *
+ *   krb5_error_code
+ *   pwqual_modname_initvt(krb5_context context, int maj_ver, int min_ver,
+ *                         krb5_plugin_vtable vtable);
+ *
+ * The initvt function should:
+ *
+ * - Check that the supplied maj_ver number is supported by the module, or
+ *   return KRB5_PLUGIN_VER_NOTSUPP if it is not.
+ *
+ * - Cast the vtable pointer as appropriate for maj_ver:
+ *     maj_ver == 1: Cast to krb5_pwqual_vtable
+ *
+ * - Initialize the methods of the vtable, stopping as appropriate for the
+ *   supplied min_ver.  Optional methods may be left uninitialized.
+ *
+ * Memory for the vtable is allocated by the caller, not by the module.
+ */
+
+#ifndef KRB5_PWQUAL_PLUGIN_H
+#define KRB5_PWQUAL_PLUGIN_H
+
+#include <krb5/krb5.h>
+#include <krb5/plugin.h>
+#include <kadm5/admin.h>
+
+/* An abstract type for password quality module data. */
+typedef struct krb5_pwqual_moddata_st *krb5_pwqual_moddata;
+
+/*** Method type declarations ***/
+
+/* Optional: Initialize module data.  dictfile is the realm's configured
+ * dictionary filename. */
+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, which has an associated
+ * password policy named policy_name (or no associated policy if policy_name is
+ * NULL).  The parameter languages, if not NULL, contains a null-terminated
+ * list of client-specified language tags as defined in RFC 5646.  The method
+ * should return one of the following errors if the password fails quality
+ * standards:
+ *
+ * - KADM5_PASS_Q_TOOSHORT: password should be longer
+ * - KADM5_PASS_Q_CLASS:    password must have more character classes
+ * - KADM5_PASS_Q_DICT:     password contains dictionary words
+ * - KADM5_PASS_Q_GENERIC:  unspecified quality failure
+ *
+ * The module should also set an extended error message with
+ * krb5_set_error_message().  The message may be localized according to one of
+ * the language tags in languages.
+ */
+typedef krb5_error_code
+(*krb5_pwqual_check_fn)(krb5_context context, krb5_pwqual_moddata data,
+                        const char *password, const char *policy_name,
+                        krb5_principal princ, const char **languages);
+
+/* Optional: Release resources used by module data. */
+typedef void
+(*krb5_pwqual_close_fn)(krb5_context context, krb5_pwqual_moddata data);
+
+/*** vtable declarations **/
+
+/* Password quality plugin vtable for major version 1. */
+typedef struct krb5_pwqual_vtable_st {
+    krb5_pwqual_open_fn open;
+    krb5_pwqual_check_fn check;
+    krb5_pwqual_close_fn close;
+    /* Minor version 1 ends here. */
+} *krb5_pwqual_vtable;
+
+#endif /* KRB5_PWQUAL_PLUGIN_H */
index a6086b1119b763f6ac69774c57f4b39aeb9821c0..5530ccafa5b64f0d7b34e1d41db09b88de76ce06 100644 (file)
@@ -61,4 +61,5 @@ error_code KADM5_SETKEY3_ETYPE_MISMATCH, "Mismatched enctypes for setkey3"
 error_code KADM5_MISSING_KRB5_CONF_PARAMS, "Missing parameters in krb5.conf required for kadmin client"
 error_code KADM5_XDR_FAILURE,          "XDR encoding error"
 error_code KADM5_CANT_RESOLVE, "Cannot resolve network address for admin server in requested realm"
+error_code KADM5_PASS_Q_GENERIC, "Unspecified password quality failure"
 end
index cc589fad23dcd6dcba7d11f4154ce396e79ed3f2..076c18fc0aa4c054338077a1a8fc8eefb55d7dd9 100644 (file)
@@ -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 @@ typedef struct _kadm5_server_handle_t {
     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 @@ typedef struct _osa_princ_ent_t {
 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 @@ krb5_error_code     kdb_iter_entry(kadm5_server_handle_t handle,
                                    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,46 @@ bool_t          xdr_osa_princ_ent_rec(XDR *xdrs, osa_princ_ent_t objp);
 void
 osa_free_princ_ent(osa_princ_ent_t val);
 
+/*** Password quality plugin consumer interface ***/
+
+/* 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,
+               const char *dict_file);
+
+/* Release a handle list allocated by k5_pwqual_load. */
+void
+k5_pwqual_free_handles(krb5_context context, pwqual_handle *handles);
+
+/* Check a password using a password quality plugin module. */
+krb5_error_code
+k5_pwqual_check(krb5_context context, pwqual_handle handle,
+                const char *password, const char *policy_name,
+                krb5_principal princ);
+
+/*** initvt functions for built-in password quality modules ***/
+
+/* The dict module checks passwords against the realm's dictionary. */
+krb5_error_code
+pwqual_dict_initvt(krb5_context context, int maj_ver, int min_ver,
+                   krb5_plugin_vtable vtable);
+
+/* 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_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 c7e0fac9b0a50c96e324ce35ffa23e434c097814..a513035d56ff1e762709198c04af1cd33bd4120e 100644 (file)
@@ -27,36 +27,48 @@ SHLIB_DIRS=-L$(TOPLIBD)
 SHLIB_RDIRS=$(KRB5_LIBDIR)
 RELDIR=kadm5/srv
 
-SRCS = $(srcdir)/svr_policy.c \
+SRCS = $(srcdir)/pwqual.c \
+       $(srcdir)/pwqual_dict.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 \
        $(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_empty.$(OBJEXT) \
+       pwqual_hesiod.$(OBJEXT) \
+       pwqual_princ.$(OBJEXT) \
+       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_empty.o \
+       pwqual_hesiod.o \
+       pwqual_princ.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
index 6da95bd7cebdc2e9b2b3e3e26317bea06aa7dce8..345957a139d02bb33ce5fef6a591a19ef50c7a73 100644 (file)
@@ -7,10 +7,7 @@ kadm5int_acl_impose_restrictions
 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
diff --git a/src/lib/kadm5/srv/pwqual.c b/src/lib/kadm5/srv/pwqual.c
new file mode 100644 (file)
index 0000000..646bfcb
--- /dev/null
@@ -0,0 +1,115 @@
+/* -*- 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,
+               const char *dict_file)
+{
+    krb5_error_code ret;
+    krb5_plugin_initvt_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, 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) {         /* 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;
+}
+
+void
+k5_pwqual_free_handles(krb5_context context, pwqual_handle *handles)
+{
+    pwqual_handle *hp, handle;
+
+    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, const char *policy_name,
+                krb5_principal princ)
+{
+    return handle->vt.check(context, handle->data, password, policy_name,
+                            princ, NULL);
+}
diff --git a/src/lib/kadm5/srv/pwqual_dict.c b/src/lib/kadm5/srv/pwqual_dict.c
new file mode 100644 (file)
index 0000000..2df9a8b
--- /dev/null
@@ -0,0 +1,254 @@
+/* -*- 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
+ *
+ *
+ * Password quality module to look up passwords within the realm dictionary.
+ */
+
+#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, const char *policy_name,
+           krb5_principal princ, const char **languages)
+{
+    dict_moddata dict = (dict_moddata)data;
+
+    /* Don't check the dictionary for principals with no password policy. */
+    if (policy_name == 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;
+
+    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_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->open = dict_open;
+    vt->check = dict_check;
+    vt->close = dict_close;
+    return 0;
+}
diff --git a/src/lib/kadm5/srv/pwqual_empty.c b/src/lib/kadm5/srv/pwqual_empty.c
new file mode 100644 (file)
index 0000000..df3505a
--- /dev/null
@@ -0,0 +1,61 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ * lib/kadm5/srv/pwqual_empty.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 reject empty passwords.
+ */
+
+#include "k5-platform.h"
+#include <krb5/pwqual_plugin.h>
+#include "server_internal.h"
+
+static krb5_error_code
+empty_check(krb5_context context, krb5_pwqual_moddata data,
+            const char *password, const char *policy_name,
+            krb5_principal princ, const char **languages)
+{
+    /* Unlike other built-in modules, this one operates even for principals
+     * with no password policy. */
+    if (*password == '\0') {
+        krb5_set_error_message(context, KADM5_PASS_Q_TOOSHORT,
+                               "Empty passwords are not allowed");
+        return KADM5_PASS_Q_TOOSHORT;
+    }
+    return 0;
+}
+
+krb5_error_code
+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 = 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..993992d
--- /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, const char **languages)
+{
+#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++) {
+        ent = hes_getpwnam(cp);
+        if (ent && ent->pw_gecos && str_check_gecos(ent->pw_gecos, password)) {
+            krb5_set_error_message(context, KADM5_PASS_Q_DICT,
+                                   "Password maynot match user information.");
+            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..dfe5f20
--- /dev/null
@@ -0,0 +1,75 @@
+/* -*- 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, const char **languages)
+{
+    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) {
+            krb5_set_error_message(context, KADM5_PASS_Q_DICT,
+                                   "Password may not match principal name");
+            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;
+}
diff --git a/src/lib/kadm5/srv/server_dict.c b/src/lib/kadm5/srv/server_dict.c
deleted file mode 100644 (file)
index 81cc5f9..0000000
+++ /dev/null
@@ -1,208 +0,0 @@
-/* -*- 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    <sys/types.h>
-#include    <sys/file.h>
-#include    <fcntl.h>
-#include    <sys/stat.h>
-#include    <unistd.h>
-#include <errno.h>
-#include    <kadm5/admin.h>
-#include    <stdlib.h>
-#include    <stdio.h>
-#include    <string.h>
-#ifdef HAVE_MEMORY_H
-#include    <memory.h>
-#endif
-#include    "adm_proto.h"
-#include    <syslog.h>
-#include    "server_internal.h"
-#include    "k5-platform.h"
-
-static char         **word_list = NULL;     /* list of word pointers */
-static char         *word_block = NULL;     /* actual word data */
-static unsigned int word_count = 0;         /* number of words */
-
-
-/*
- * 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.
- *
- */
-
-int init_dict(kadm5_config_params *params)
-{
-    int             fd,
-        len,
-        i;
-    char            *p,
-        *t;
-    struct  stat    sb;
-
-    if(word_list != NULL && word_block != NULL)
-        return KADM5_OK;
-    if (! (params->mask & KADM5_CONFIG_DICT_FILE)) {
-        krb5_klog_syslog(LOG_INFO, "No dictionary file specified, continuing "
-                         "without one.");
-        return KADM5_OK;
-    }
-    if ((fd = open(params->dict_file, O_RDONLY)) == -1) {
-        if (errno == ENOENT) {
-            krb5_klog_syslog(LOG_ERR,
-                             "WARNING!  Cannot find dictionary file %s, "
-                             "continuing without one.", params->dict_file);
-            return KADM5_OK;
-        } else
-            return errno;
-    }
-    set_cloexec_fd(fd);
-    if (fstat(fd, &sb) == -1) {
-        close(fd);
-        return errno;
-    }
-    if ((word_block = (char *) malloc(sb.st_size + 1)) == NULL)
-        return ENOMEM;
-    if (read(fd, word_block, sb.st_size) != sb.st_size)
-        return errno;
-    (void) close(fd);
-    word_block[sb.st_size] = '\0';
-
-    p = word_block;
-    len = sb.st_size;
-    while(len > 0 && (t = memchr(p, '\n', len)) != NULL) {
-        *t = '\0';
-        len -= t - p + 1;
-        p = t + 1;
-        word_count++;
-    }
-    if ((word_list = (char **) malloc(word_count * sizeof(char *))) == NULL)
-        return ENOMEM;
-    p = word_block;
-    for (i = 0; i < word_count; i++) {
-        word_list[i] = p;
-        p += strlen(p) + 1;
-    }
-    qsort(word_list, word_count, sizeof(char *), word_compare);
-    return KADM5_OK;
-}
-
-/*
- * Function: find_word
- *
- * Purpose: See if the specified word exists in the in-core dictionary
- *
- * Arguments:
- *      word            (input) word to search for.
- *      <return value>  WORD_NOT_FOUND if not in dictionary,
- *                      KADM5_OK if if found word
- *                      errno if init needs to be called and returns an
- *                      error
- *
- * Requires:
- *      word to be a null terminated string.
- *      That word_list and word_block besetup
- *
- * Effects:
- *      finds word in dictionary.
- * Modifies:
- *      nothing.
- *
- */
-
-int
-find_word(const char *word)
-{
-    char    **value;
-
-    if(word_list == NULL || word_block == NULL)
-        return WORD_NOT_FOUND;
-    if ((value = (char **) bsearch(&word, word_list, word_count, sizeof(char *),
-                                   word_compare)) == NULL)
-        return WORD_NOT_FOUND;
-    else
-        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.
- *
- */
-
-void
-destroy_dict(void)
-{
-    if(word_list) {
-        free(word_list);
-        word_list = NULL;
-    }
-    if(word_block) {
-        free(word_block);
-        word_block = NULL;
-    }
-    if(word_count)
-        word_count = 0;
-    return;
-}
index 557ef0ad46839ea68aa575bc10b59e8a50cb85d8..9ebc13eea6e76e363683915a1cbe765dc6bb3c5e 100644 (file)
@@ -317,7 +317,7 @@ kadm5_ret_t kadm5_init(krb5_context context, char *client_name, char *pass,
         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 @@ kadm5_ret_t kadm5_destroy(void *server_handle)
 
     CHECK_HANDLE(server_handle);
 
-    destroy_dict();
+    destroy_pwqual(handle);
 
     adb_policy_close(handle);
     krb5_db_fini(handle->context);
index 1faeb86b16f7d6ba42f235ae66fb7617bbc537dc..93369f0002a7a35deb2ca5f569008dfa379ade05 100644 (file)
@@ -1,22 +1,38 @@
 /* -*- 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>
 #include    <pwd.h>
-
-/* for strcasecmp */
-#include    <string.h>
-
 #include    "server_internal.h"
 
 kadm5_ret_t
@@ -37,147 +53,99 @@ adb_policy_close(kadm5_server_handle_t handle)
     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;
-
-    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--;
-
-    return(newstr);
+    krb5_error_code ret;
+    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,
+                             "empty", pwqual_empty_initvt);
+    if (ret != 0)
+        return ret;
+    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;
+    ret = k5_pwqual_load(handle->context, &list, dict_file);
+    if (ret != 0)
+        return ret;
+
+    handle->qual_handles = list;
+    return 0;
 }
-#endif /* HESIOD */
 
-#if 0
-static int
-lower(str)
-    char    *str;
+/* 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)
 {
-    register char   *cp;
-    int     effect=0;
-
-    for (cp = str; *cp; cp++) {
-        if (isupper(*cp)) {
-            *cp = tolower(*cp);
-            effect++;
-        }
+    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;
     }
-    return(effect);
+    nclasses = hasupper + haslower + hasdigit + haspunct + hasspec;
+    if (nclasses < pol->pw_min_classes)
+        return KADM5_PASS_Q_CLASS;
+    return KADM5_OK;
 }
-#endif
 
-#ifdef HESIOD
-static int
-str_check_gecos(gecos, pwstr)
-    char    *gecos;
-    char    *pwstr;
+/* 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)
 {
-    char            *cp, *ncp, *tcp;
-
-    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;
+    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, polname, 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
-
-    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;
+    k5_pwqual_free_handles(handle->context, handle->qual_handles);
+    handle->qual_handles = NULL;
 }
index 6b14d3ba6bd212e7f3dd7a7ab6cde93a4e861540..dc164064301e2ee3f8f0e83881993ca1ee48d72b 100644 (file)
@@ -292,7 +292,7 @@ kadm5_create_principal_3(void *server_handle,
         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 @@ kadm5_chpass_principal_3(void *server_handle,
         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,
index 4f3081428e49b219ecc3a825098462325fa18cce..0737a2e533236da12e40c65ea5a012093e67e415 100644 (file)
@@ -353,7 +353,7 @@ T_PAC_OBJS= t_pac.o pac.o pac_sign.o copy_data.o
 
 T_PRINC_OBJS= t_princ.o parse.o unparse.o
 
-T_ETYPES_OBJS= t_etypes.o init_ctx.o plugin.o etype_list.o
+T_ETYPES_OBJS= t_etypes.o init_ctx.o etype_list.o plugin.o
 
 t_walk_rtree: $(T_WALK_RTREE_OBJS) $(KRB5_BASE_DEPLIBS)
        $(CC_LINK) -o t_walk_rtree $(T_WALK_RTREE_OBJS) $(KRB5_BASE_LIBS)
index d868f331900e3d74c0fac30e527b0c7360cc4afd..277da2472205d759d128a802b17036efbf09b78a 100644 (file)
@@ -31,6 +31,7 @@
 #include "k5-int.h"
 
 const char *interface_names[PLUGIN_NUM_INTERFACES] = {
+    "pwqual"
 };
 
 /* Return the context's interface structure for id, or NULL if invalid. */