From 9639c4639671baa1c1d6abeef99c6e2943a41342 Mon Sep 17 00:00:00 2001 From: Russ Allbery Date: Wed, 6 Jan 2010 20:50:08 -0800 Subject: [PATCH] Add new plugin API for MIT Kerberos 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 | 1 + LICENSE | 2 +- Makefile.am | 19 ++++--- NEWS | 4 ++ configure.ac | 4 +- plugin/mit.c | 120 +++++++++++++++++++++++++++++++++++++++++++ tests/TESTS | 1 + tests/mit/plugin-t | 101 ++++++++++++++++++++++++++++++++++++ tests/mit/plugin.c | 125 +++++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 366 insertions(+), 11 deletions(-) create mode 100644 plugin/mit.c create mode 100755 tests/mit/plugin-t create mode 100644 tests/mit/plugin.c diff --git a/.gitignore b/.gitignore index 809d905..5e8bdff 100644 --- a/.gitignore +++ b/.gitignore @@ -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 2027e80..9a1296c 100644 --- 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: diff --git a/Makefile.am b/Makefile.am index eeab66f..3ef3285 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,15 +1,16 @@ # Automake makefile for krb5-strength. # # Written by Russ Allbery -# 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 c30e5bd..dee8a85 100644 --- 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 diff --git a/configure.ac b/configure.ac index e3bc005..d71a9b1 100644 --- a/configure.ac +++ b/configure.ac @@ -1,7 +1,7 @@ dnl Process this file with autoconf to produce a configure script. dnl dnl Written by Russ Allbery -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 ]) RRA_LIB_KRB5_RESTORE diff --git a/plugin/mit.c b/plugin/mit.c new file mode 100644 index 0000000..bc67177 --- /dev/null +++ b/plugin/mit.c @@ -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 + * Copyright 2010 Board of Trustees, Leland Stanford Jr. Unversity + * + * See LICENSE for licensing terms. + */ + +#include +#include + +#include +#include + +#include + +/* 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 +# 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 */ diff --git a/tests/TESTS b/tests/TESTS index 0469963..da142a5 100644 --- a/tests/TESTS +++ b/tests/TESTS @@ -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 index 0000000..8c8f0c2 --- /dev/null +++ b/tests/mit/plugin-t @@ -0,0 +1,101 @@ +#!/bin/sh +# +# Test suite wrapper for the MIT Kerberos shared module API. +# +# Written by Russ Allbery +# 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 <> ./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 index 0000000..587b03f --- /dev/null +++ b/tests/mit/plugin.c @@ -0,0 +1,125 @@ +/* + * Test for the MIT Kerberos shared module API. + * + * Written by Russ Allbery + * Copyright 2010 Board of Trustees, Leland Stanford Jr. University + * + * See LICENSE for licensing terms. + */ + +#include +#include + +#include +#include +#include + +/* Allow for a build without the plugin header. */ +# ifdef HAVE_KRB5_PWCHECK_PLUGIN_H +# include +# 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); +} -- 2.39.2