]> eyrie.org Git - kerberos/krb5-strength.git/commitdiff
Add new plugin API for MIT Kerberos
authorRuss Allbery <rra@stanford.edu>
Thu, 7 Jan 2010 04:50:08 +0000 (20:50 -0800)
committerRuss Allbery <rra@stanford.edu>
Thu, 7 Jan 2010 04:50:32 +0000 (20:50 -0800)
Add a new plugin API for MIT Kerberos modelled after the plugin API
used for other MIT Kerberos plugins.  Thanks to Marcus Watts for
substantial research and contributions to the interface design.

.gitignore
LICENSE
Makefile.am
NEWS
configure.ac
plugin/mit.c [new file with mode: 0644]
tests/TESTS
tests/mit/plugin-t [new file with mode: 0755]
tests/mit/plugin.c [new file with mode: 0644]

index 809d90506c78a9a4f5868125bb9b3316444054b1..5e8bdffe28fa4ba820d864525c364c3f1aa4f528 100644 (file)
@@ -16,6 +16,7 @@
 /tests/data/.placeholder
 /tests/data/dictionary.*
 /tests/heimdal/plugin
+/tests/mit/plugin
 /tests/portable/asprintf-t
 /tests/portable/snprintf-t
 /tests/portable/strlcat-t
diff --git a/LICENSE b/LICENSE
index 2027e803084a47c687f3333b524fbeb82ee8e609..9a1296c3c58c5950d6dc6fc9399ac3911aa0721f 100644 (file)
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
 The krb5-strength package as a whole is:
 
-  Copyright 2006, 2007, 2009 Board of Trustees, Leland Stanford Jr.
+  Copyright 2006, 2007, 2009, 2010 Board of Trustees, Leland Stanford Jr.
   University.  All rights reserved.
 
 and released under the following license:
index eeab66f6adcd8b1becda4e70888a2d8acef0e68b..3ef328530bde878b25d7d129e68d56c97c034e22 100644 (file)
@@ -1,15 +1,16 @@
 # Automake makefile for krb5-strength.
 #
 # Written by Russ Allbery <rra@stanford.edu>
-# Copyright 2007, 2009 Board of Trustees, Leland Stanford Jr. University
+# Copyright 2007, 2009, 2010 Board of Trustees, Leland Stanford Jr. University
 #
 # See LICENSE for licensing terms.
 
 AUTOMAKE_OPTIONS = foreign subdir-objects
 ACLOCAL_AMFLAGS = -I m4
-EXTRA_DIST = autogen cracklib/HISTORY cracklib/LICENCE cracklib/README     \
-       cracklib/mkdict patches/README patches/mit-krb5-1.4.4 tests/TESTS   \
-       tests/heimdal/external-t tests/heimdal/plugin-t tests/tap/libtap.sh
+EXTRA_DIST = autogen cracklib/HISTORY cracklib/LICENCE cracklib/README    \
+       cracklib/mkdict patches/README patches/mit-krb5-1.4.4 tests/TESTS  \
+       tests/heimdal/external-t tests/heimdal/plugin-t tests/mit/plugin-t \
+       tests/tap/libtap.sh
 
 # Do this globally.  Everything needs to find the Kerberos headers and
 # libraries.
@@ -34,7 +35,7 @@ portable_libportable_la_LIBADD = $(LTLIBOBJS)
 
 # Rules for building the password strength plugin.
 module_LTLIBRARIES = plugin/passwd_strength.la
-plugin_passwd_strength_la_SOURCES = plugin/api.c plugin/heimdal.c
+plugin_passwd_strength_la_SOURCES = plugin/api.c plugin/heimdal.c plugin/mit.c
 plugin_passwd_strength_la_LDFLAGS = -module -avoid-version
 plugin_passwd_strength_la_LIBADD = cracklib/libcracklib.la \
        portable/libportable.la
@@ -67,9 +68,9 @@ warnings:
        $(MAKE) V=0 CFLAGS='$(WARNINGS)' $(check_PROGRAMS)
 
 # The bits below are for the test suite, not for the main package.
-check_PROGRAMS = tests/heimdal/plugin tests/portable/asprintf-t        \
-       tests/portable/snprintf-t tests/portable/strlcat-t      \
-       tests/portable/strlcpy-t tests/runtests
+check_PROGRAMS = tests/heimdal/plugin tests/mit/plugin                  \
+       tests/portable/asprintf-t tests/portable/snprintf-t              \
+       tests/portable/strlcat-t tests/portable/strlcpy-t tests/runtests
 tests_runtests_CPPFLAGS = -DSOURCE='"$(abs_top_srcdir)/tests"' \
        -DBUILD='"$(abs_top_builddir)/tests"'
 check_LIBRARIES = tests/tap/libtap.a
@@ -78,6 +79,8 @@ tests_tap_libtap_a_SOURCES = tests/tap/basic.c tests/tap/basic.h
 
 tests_heimdal_plugin_SOURCES = tests/heimdal/plugin.c
 tests_heimdal_plugin_LDADD = portable/libportable.la $(KRB5_LIBS) $(DL_LIBS)
+tests_mit_plugin_SOURCES = tests/mit/plugin.c
+tests_mit_plugin_LDADD = portable/libportable.la $(KRB5_LIBS) $(DL_LIBS)
 tests_portable_asprintf_t_SOURCES = tests/portable/asprintf-t.c \
        tests/portable/asprintf.c
 tests_portable_asprintf_t_LDADD = tests/tap/libtap.a portable/libportable.la
diff --git a/NEWS b/NEWS
index c30e5bdba64ca8b12d2a00e1a8a911e8c1f32bd0..dee8a8533744e951fbe6a4f65da0f0bb326bdb35 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -9,6 +9,10 @@ krb5-strength 1.0 (unreleased)
     dynamically loaded password strength checking API and can be used as a
     Heimdal kadmin plugin.
 
+    Add a new plugin API for MIT Kerberos modelled after the plugin API
+    used for other MIT Kerberos plugins.  Thanks to Marcus Watts for
+    substantial research and contributions to the interface design.
+
     Fixed the data format written by the included packer program to add
     enough nul bytes at the end of the data.  Previously, there was not
     enough trailing nul bytes for the expected input format, leading to
index e3bc005c5e93f2ac94b3f2cc0d4786d7af1f7e0c..d71a9b1530b409bd14f1e8c2746feff80b0b30df 100644 (file)
@@ -1,7 +1,7 @@
 dnl Process this file with autoconf to produce a configure script.
 dnl
 dnl Written by Russ Allbery <rra@stanford.edu>
-dnl Copyright 2006, 2007, 2009
+dnl Copyright 2006, 2007, 2009, 2010
 dnl     Board of Trustees, Leland Stanford Jr. University
 dnl
 dnl See LICENSE for licensing terms.
@@ -36,7 +36,7 @@ AC_SUBST([DL_LIBS])
 RRA_LIB_KRB5
 
 RRA_LIB_KRB5_SWITCH
-AC_CHECK_HEADERS([kadm5-pwcheck.h])
+AC_CHECK_HEADERS([kadm5-pwcheck.h krb5/pwcheck_plugin.h])
 AC_CHECK_TYPES([krb5_realm], , , [#include <krb5.h>])
 RRA_LIB_KRB5_RESTORE
 
diff --git a/plugin/mit.c b/plugin/mit.c
new file mode 100644 (file)
index 0000000..bc67177
--- /dev/null
@@ -0,0 +1,120 @@
+/*
+ * MIT Kerberos shared module API.
+ *
+ * This is the glue required for a Heimdal password quality check via a
+ * dynamically loaded module.  Retrieves the dictionary path from krb5.conf.
+ * This may change in later versions via a mechanism to pass profile
+ * information from kadmind to the plugin.
+ *
+ * Written by Russ Allbery <rra@stanford.edu>
+ * Copyright 2010 Board of Trustees, Leland Stanford Jr. Unversity
+ *
+ * See LICENSE for licensing terms.
+ */
+
+#include <config.h>
+#include <portable/system.h>
+
+#include <errno.h>
+#include <krb5.h>
+
+#include <plugin/api.h>
+
+/* Skip this entire file if building with Heimdal. */
+#ifndef HAVE_KRB5_REALM
+
+/* Used for unused parameters to silence gcc warnings. */
+#define UNUSED  __attribute__((__unused__))
+
+/* Allow for a build without the plugin header. */
+# ifdef HAVE_KRB5_PWCHECK_PLUGIN_H
+#  include <krb5/pwcheck_plugin.h>
+# else
+typedef struct krb5plugin_kadmin_pwcheck_ftable_v0 {
+    int minor_version;
+    krb5_error_code (*init)(krb5_context, void **);
+    void (*fini)(krb5_context, void *);
+    int (*check)(krb5_context, void *, krb5_const_principal,
+                 const krb5_data *password);
+} krb5plugin_kadmin_pwcheck_ftable_v0;
+# endif /* !HAVE_KRB5_PWCHECK_PLUGIN_H */
+
+
+/*
+ * Initialize the library.  We can't just call pwcheck_init, since currently
+ * kadmind doesn't tell us the dictionary path.  So first look up where the
+ * dictionary is, and then call pwcheck_init.
+ */
+static krb5_error_code
+init(krb5_context context, void **data)
+{
+    char *dictionary = NULL;
+
+    krb5_appdefault_string(context, "krb5-strength", NULL,
+                           "password_dictionary", "", &dictionary);
+    if (dictionary == NULL || dictionary[0] == '\0') {
+        krb5_set_error_message(context, KRB5_PLUGIN_OP_NOTSUPP,
+                               "password_dictionary not configured in"
+                               " krb5.conf");
+        return KRB5_PLUGIN_OP_NOTSUPP;
+    }
+    if (pwcheck_init(data, dictionary) != 0) {
+        krb5_set_error_message(context, errno, "Cannot initialize strength"
+                               " checking with dictionary %s: %s", dictionary,
+                               strerror(errno));
+        return errno;
+    }
+    return 0;
+}
+
+
+/*
+ * Check the password.  We need to transform the krb5_data struct and the
+ * principal passed us by kadmind into nul-terminated strings for our check.
+ */
+static krb5_error_code
+check(krb5_context context, void *data, krb5_const_principal princ,
+      const krb5_data *password)
+{
+    char *pastring;
+    char *name = NULL;
+    krb5_error_code status;
+    char message[BUFSIZ];
+
+    status = krb5_unparse_name(context, princ, &name);
+    if (status != 0)
+        return status;
+    pastring = malloc(password->length + 1);
+    if (pastring == NULL) {
+        status = errno;
+        krb5_set_error_message(context, status, "%s", strerror(status));
+        krb5_free_unparsed_name(context, name);
+        return status;
+    }
+    memcpy(pastring, password->data, password->length);
+    pastring[password->length] = '\0';
+    status = pwcheck_check(data, pastring, name, message, sizeof(message));
+    if (status != 0)
+        krb5_set_error_message(context, status, "%s", message);
+    free(pastring);
+    krb5_free_unparsed_name(context, name);
+    return status;
+}
+
+
+/*
+ * Shut down the library.
+ */
+static void
+fini(krb5_context context UNUSED, void *data)
+{
+    pwcheck_close(data);
+}
+
+
+/* The public symbol that MIT Kerberos looks for. */
+krb5plugin_kadmin_pwcheck_ftable_v0 kadmin_pwcheck_0 = {
+    0, init, fini, check
+};
+
+#endif /* !HAVE_KRB5_REALM */
index 0469963bac0aedc7ce1c7de8ea4afa16a4fe613d..da142a5e49d3857bdcaf41a81941ced951e5a6fc 100644 (file)
@@ -1,5 +1,6 @@
 heimdal/external
 heimdal/plugin
+mit/plugin
 portable/asprintf
 portable/snprintf
 portable/strlcat
diff --git a/tests/mit/plugin-t b/tests/mit/plugin-t
new file mode 100755 (executable)
index 0000000..8c8f0c2
--- /dev/null
@@ -0,0 +1,101 @@
+#!/bin/sh
+#
+# Test suite wrapper for the MIT Kerberos shared module API.
+#
+# Written by Russ Allbery <rra@stanford.edu>
+# Copyright 2009, 2010 Board of Trustees, Leland Stanford Jr. University
+#
+# See LICENSE for licensing terms.
+
+. "$SOURCE/tap/libtap.sh"
+cd "$BUILD/data"
+
+# Run the plugin program to check a password.  Takes the test description,
+# the principal, the password, the expected exit status, and the expected
+# output to standard error.
+ok_password () {
+    local desc princ password w_status w_stderr status stderr
+    desc="$1"
+    princ="$2"
+    password="$3"
+    w_status="$4"
+    w_stderr="$5"
+    stderr=`"$BUILD/mit/plugin" "$princ" "$password" 2>&1`
+    status="$?"
+    echo "# status: $status"
+    echo "# stderr: $stderr"
+    ok "$desc: status" [ "$status" -eq "$w_status" ]
+    ok "$desc: stderr" [ "$stderr" = "$w_stderr" ]
+}
+
+# We need a modified krb5.conf file to add the password_dictionary setting.
+# We first generate a modified copy of the krb5.conf file that doesn't contain
+# this setting so that we can test error handling.
+krb5conf=
+for p in /etc/krb5.conf /usr/local/etc/krb5.conf data/krb5.conf ; do
+    if [ -r "$p" ] ; then
+        krb5conf="$p"
+        sed -e '/^[    ]*password_dictionary[  ]*=/d' "$p" > ./krb5.conf
+        KRB5_CONFIG="./krb5.conf"
+        export KRB5_CONFIG
+        break
+    fi
+done
+if [ -z "$krb5conf" ] ; then
+    skip_all 'no krb5.conf found, put one in tests/data/krb5.conf'
+fi
+
+# Check whether we can run the test at all.
+"$BUILD/mit/plugin" 'test@EXAMPLE.COM' 'test' >/dev/null 2>&1
+if [ $? = 42 ] ; then
+    rm -f krb5.conf
+    skip_all 'not built against MIT Kerberos libraries'
+fi
+
+# Okay, we should be good to run the test suite.
+plan 28
+
+# We don't have a password_dictionary setting, so we should fail with an
+# initialization error.
+ok_password "no dictionary configured" 'test@EXAMPLE.ORG' 'password' 1 \
+    'password_dictionary not configured in krb5.conf'
+
+# Now add the password dictionary configuration.
+cat <<EOF >> ./krb5.conf
+
+[appdefaults]
+    krb5-strength = {
+        password_dictionary = $BUILD/data/dictionary
+    }
+
+EOF
+
+# Check the basic functionality.
+ok_password "good password" 'test@EXAMPLE.ORG' 'known good password' 0 ''
+ok_password "password in dictionary" 'test@EXAMPLE.ORG' 'password' 1 \
+    'it is based on a dictionary word'
+ok_password "password in dictionary" 'test@EXAMPLE.ORG' 'bitter' 1 \
+    'it is based on a dictionary word'
+ok_password "password in dictionary" 'test@EXAMPLE.ORG' 'rettib' 1 \
+    'it is based on a (reversed) dictionary word'
+ok_password "password too short" 'test@EXAMPLE.ORG' 'food' 1 \
+    "it is too short"
+ok_password "password way too short" 'test@EXAMPLE.ORG' 'foo' 1 \
+    "it's WAY too short"
+ok_password "password empty" 'test@EXAMPLE.ORG' '' 1 \
+    "it's WAY too short"
+ok_password "password all whitespace" 'test@EXAMPLE.ORG' '         ' 1 \
+    'it does not contain enough DIFFERENT characters'
+ok_password "password too simplistic" 'test@EXAMPLE.ORG' 'abcdefghi' 1 \
+    'it is too simplistic/systematic'
+ok_password "not enough characters" 'test@EXAMPLE.ORG' '22413411' 1 \
+    'it does not contain enough DIFFERENT characters'
+ok_password "password based on principal" 'someuser@EXAMPLE.ORG' 'someuser' \
+    1 'Password based on username'
+ok_password "password based on principal" 'someuser@EXAMPLE.ORG' 'resuemos' \
+    1 'Password based on username'
+ok_password "password is principal" 'test@EXAMPLE.ORG' 'test@EXAMPLE.ORG' \
+    1 'Password based on username'
+
+# Clean up.
+rm -f krb5.conf
diff --git a/tests/mit/plugin.c b/tests/mit/plugin.c
new file mode 100644 (file)
index 0000000..587b03f
--- /dev/null
@@ -0,0 +1,125 @@
+/*
+ * Test for the MIT Kerberos shared module API.
+ *
+ * Written by Russ Allbery <rra@stanford.edu>
+ * Copyright 2010 Board of Trustees, Leland Stanford Jr. University
+ *
+ * See LICENSE for licensing terms.
+ */
+
+#include <config.h>
+#include <portable/system.h>
+
+#include <dlfcn.h>
+#include <errno.h>
+#include <krb5.h>
+
+/* Allow for a build without the plugin header. */
+# ifdef HAVE_KRB5_PWCHECK_PLUGIN_H
+#  include <krb5/pwcheck_plugin.h>
+# else
+typedef struct krb5plugin_kadmin_pwcheck_ftable_v0 {
+    int minor_version;
+    krb5_error_code (*init)(krb5_context, void **);
+    void (*fini)(krb5_context, void *);
+    int (*check)(krb5_context, void *, krb5_const_principal,
+                 const krb5_data *password);
+} krb5plugin_kadmin_pwcheck_ftable_v0;
+# endif /* !HAVE_KRB5_PWCHECK_PLUGIN_H */
+
+
+/*
+ * Expects a principal and a password to check on the command line.  Loads the
+ * MIT Kerberos plugin, converts the input into the necessary format, calls
+ * the plugin, and reports the results.  Exits with a status matching the
+ * return value of the plugin function.
+ *
+ * We assume that the plugin is available as:
+ *
+ *     BUILD/../plugin/.libs/passwd_strength.so
+ *
+ * since we don't want to embed Libtool's libtldl just to run a test.
+ */
+int
+main(int argc, char *argv[])
+{
+    const char *build;
+    char *path;
+    size_t length;
+    krb5_context ctx;
+    krb5_principal princ;
+    krb5_data password;
+    krb5_error_code status;
+    void *handle, *data;
+    struct krb5plugin_kadmin_pwcheck_ftable_v0 *verifier;
+
+    /*
+     * If we're not building with MIT Kerberos, we can't run this test.  Exit
+     * with a special status to communicate this to the test wrapper.
+     */
+#ifdef HAVE_KRB5_REALM
+    exit(42);
+#endif
+
+    /* Build the path of the plugin. */
+    if (argc != 3) {
+        fprintf(stderr, "Wrong number of arguments\n");
+        exit(1);
+    }
+    build = getenv("BUILD");
+    if (build == NULL) {
+        fprintf(stderr, "No BUILD environment variable set\n");
+        exit(1);
+    }
+    length = strlen(build) + strlen("/../plugin/.libs/passwd_strength.so");
+    path = malloc(length + 1);
+    if (path == NULL) {
+        fprintf(stderr, "Cannot allocate memory: %s\n", strerror(errno));
+        exit(1);
+    }
+    strlcpy(path, build, length + 1);
+    strlcat(path, "/../plugin/.libs/passwd_strength.so", length + 1);
+
+    /* Initialize the data structures. */
+    status = krb5_init_context(&ctx);
+    if (status != 0) {
+        fprintf(stderr, "Cannot initialize Kerberos context\n");
+        exit(1);
+    }
+    status = krb5_parse_name(ctx, argv[1], &princ);
+    if (status != 0) {
+        fprintf(stderr, "Cannot parse principal name\n");
+        exit(1);
+    }
+    password.length = strlen(argv[2]);
+    password.data = argv[2];
+
+    /* Load the module and find the correct symbol. */
+    handle = dlopen(path, RTLD_NOW);
+    if (handle == NULL) {
+        fprintf(stderr, "Cannot dlopen %s: %s\n", path, dlerror());
+        exit(1);
+    }
+    verifier = dlsym(handle, "kadmin_pwcheck_0");
+    if (verifier == NULL) {
+        fprintf(stderr, "Cannot get kadmin_pwcheck_0 symbol: %s\n", dlerror());
+        exit(1);
+    }
+    if (verifier->minor_version != 0
+        || verifier->init == NULL
+        || verifier->check == NULL
+        || verifier->fini == NULL) {
+        fprintf(stderr, "Invalid metadata in plugin\n");
+        exit(1);
+    }
+    status = verifier->init(ctx, &data);
+    if (status != 0) {
+        fprintf(stderr, "%s\n", krb5_get_error_message(ctx, status));
+        exit(1);
+    }
+    status = verifier->check(ctx, data, princ, &password);
+    if (status != 0)
+        fprintf(stderr, "%s\n", krb5_get_error_message(ctx, status));
+    verifier->fini(ctx, data);
+    exit(status);
+}