From f115d00fa6c65b82180e5b0717b8628a1b890645 Mon Sep 17 00:00:00 2001 From: Russ Allbery Date: Mon, 30 Sep 2013 23:15:51 -0700 Subject: [PATCH] Add support for checking against a CDB database Add support for building with TinyCDB and then checking passwords against a CDB database. There is a new password_dictionary_cdb krb5.conf configuration setting that configures a CDB directory to use. The tests with a CDB dictionary are much simpler: passwords are rejected if found in the dictionary either literally, with one or two characters removed from the start or end, or with one character removed from both the start and the end. Both a CrackLib and a CDB dictionary can be specified to check both dictionaries. --- .gitignore | 2 + Makefile.am | 23 ++-- NEWS | 11 ++ README | 59 ++++++++--- autogen | 4 + configure.ac | 1 + m4/cdb.m4 | 60 +++++++++++ m4/lib-helper.m4 | 138 ++++++++++++++++++++++++ plugin/api.c | 203 ++++++++++++++++++++++++++++-------- plugin/api.h | 16 +++ plugin/cdb.c | 154 +++++++++++++++++++++++++++ tests/data/cdb.json | 63 +++++++++++ tests/data/make-krb5-conf | 9 +- tests/data/password-tests.h | 5 + tests/heimdal/external-t | 85 +++++++++++---- tests/heimdal/plugin-t.c | 33 ++++-- tests/mit/plugin-t.c | 48 +++++++-- 17 files changed, 812 insertions(+), 102 deletions(-) create mode 100644 m4/cdb.m4 create mode 100644 m4/lib-helper.m4 create mode 100644 plugin/cdb.c create mode 100644 tests/data/cdb.json diff --git a/.gitignore b/.gitignore index 4ec5414..b7018c4 100644 --- a/.gitignore +++ b/.gitignore @@ -19,8 +19,10 @@ /m4/lt~obsolete.m4 /stamp-h1 /tests/data/.placeholder +/tests/data/cdb.c /tests/data/cracklib.c /tests/data/dictionary.* +/tests/data/wordlist.cdb /tests/heimdal/plugin-t /tests/mit/plugin-t /tests/portable/asprintf-t diff --git a/Makefile.am b/Makefile.am index a1e3ca9..e1292ad 100644 --- a/Makefile.am +++ b/Makefile.am @@ -15,10 +15,10 @@ EXTRA_DIST = .gitignore LICENSE autogen cracklib/HISTORY cracklib/LICENCE \ tests/tap/libtap.sh tests/tools/cdbmake-wordlist # Do this globally. Everything needs to find the Kerberos headers and -# libraries, and if we're using the system CrackLib, add its location -# unconditionally as well. -AM_CPPFLAGS = $(CRACKLIB_CPPFLAGS) $(KRB5_CPPFLAGS) -AM_LDFLAGS = $(CRACKLIB_LDFLAGS) $(KRB5_LDFLAGS) +# libraries, and if we're using the system CrackLib or libcdb, add its +# location unconditionally as well. +AM_CPPFLAGS = $(CRACKLIB_CPPFLAGS) $(KRB5_CPPFLAGS) $(CDB_CPPFLAGS) +AM_LDFLAGS = $(CRACKLIB_LDFLAGS) $(KRB5_LDFLAGS) $(CDB_LDFLAGS) # Put the module into /usr/local/lib/kadmind by default, relative to --libdir. moduledir = $(libdir)/kadmind @@ -49,20 +49,21 @@ endif # 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/mit.c +plugin_passwd_strength_la_SOURCES = plugin/api.c plugin/cdb.c \ + plugin/heimdal.c plugin/mit.c plugin_passwd_strength_la_LDFLAGS = -module -avoid-version if EMBEDDED_CRACKLIB plugin_passwd_strength_la_LIBADD = cracklib/libcracklib.la else plugin_passwd_strength_la_LIBADD = $(CRACKLIB_LIBS) endif -plugin_passwd_strength_la_LIBADD += portable/libportable.la $(KRB5_LIBS) +plugin_passwd_strength_la_LIBADD += portable/libportable.la $(KRB5_LIBS) \ + $(CDB_LIBS) # The Heimdal external-check program. bin_PROGRAMS = external/heimdal-strength external_heimdal_strength_CFLAGS = $(AM_CFLAGS) -external_heimdal_strength_SOURCES = plugin/api.c plugin/api.h \ +external_heimdal_strength_SOURCES = plugin/api.c plugin/api.h plugin/cdb.c \ external/heimdal-strength.c if EMBEDDED_CRACKLIB external_heimdal_strength_LDADD = cracklib/libcracklib.la @@ -70,7 +71,7 @@ else external_heimdal_strength_LDADD = $(CRACKLIB_LIBS) endif external_heimdal_strength_LDADD += util/libutil.a portable/libportable.la \ - $(KRB5_LIBS) + $(KRB5_LIBS) $(CDB_LIBS) # Other tools. dist_bin_SCRIPTS = tools/cdbmake-wordlist @@ -131,10 +132,10 @@ tests_tap_libtap_a_SOURCES = tests/tap/basic.c tests/tap/basic.h \ # The actual test programs. tests_heimdal_plugin_t_CPPFLAGS = $(KRB5_CPPFLAGS) tests_heimdal_plugin_t_LDADD = tests/tap/libtap.a portable/libportable.la \ - $(KRB5_LIBS) $(DL_LIBS) + $(KRB5_LIBS) $(CDB_LIBS) $(DL_LIBS) tests_mit_plugin_t_CPPFLAGS = $(KRB5_CPPFLAGS) tests_mit_plugin_t_LDADD = tests/tap/libtap.a portable/libportable.la \ - $(KRB5_LIBS) $(DL_LIBS) + $(KRB5_LIBS) $(CDB_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 d6da648..1bb3266 100644 --- a/NEWS +++ b/NEWS @@ -7,6 +7,17 @@ krb5-strength 2.0 (unreleased) and MIT. Drop the patch for MIT Kerberos 1.4 (and hence support for versions of MIT Kerberos prior to 1.9). + Add support for building with TinyCDB and then checking passwords + against a CDB database. There is a new password_dictionary_cdb + krb5.conf configuration setting that configures a CDB directory to + use. The tests with a CDB dictionary are much simpler: passwords are + rejected if found in the dictionary either literally, with one or two + characters removed from the start or end, or with one character + removed from both the start and the end. Both a CrackLib and a CDB + dictionary can be specified to check both dictionaries. A new + cdbmake-wordlist utility (written in Perl) is included to ease the + process of creating a CDB database from a simple word list. + The plugin now sets the Kerberos error message in the context to pass error information, resulting in higher-quality error reporting in the MIT Kerberos plugin. diff --git a/README b/README index faa3fab..f36ebb5 100644 --- a/README +++ b/README @@ -76,13 +76,21 @@ REQUIREMENTS well-behaved for use inside plugins or libraries. If using a system CrackLib library, use version 2.8.22 or later to avoid these problems. + You can also optionally build against the TinyCDB library, which + provides support for simpler and faster password checking against a CDB + dictionary file. Building a CDB dictionary with cdbmake-wordlist + (included) requires Perl 5.006 or later and the CDB utility that comes + with TinyCDB. + For this module to be effective for either Heimdal or MIT Kerberos, you will also need to construct a dictionary. The mkdict and packer utilities to build a CrackLib dictionary from a word list are included in this toolkit but not installed by default. You can run them out of the cracklib directory after building. You can also use the utilities that come with the stock CrackLib package (often already packaged in a - Linux distribution); the database format is compatible. + Linux distribution); the database format is compatible. For building a + CDB dictionary, use the provided cdbmake-wordlist program. The CDB + utility must be on your PATH. For a word list to use as source for the dictionary, you can use /usr/share/dict/words if it's available on your system, but it would be @@ -119,6 +127,11 @@ COMPILING AND INSTALLING directory where CrackLib was installed, or separately set the include and library path with --with-cracklib-include and --with-cracklib-lib. + krb5-strength will automatically build with TinyCDB if it is found. To + specify the installation path of TinyCDB, use --with-tinycdb. You can + also separately set the include and library path with + --with-tinycdb-include and --with-tinycdb-lib. + Normally, configure will use krb5-config to determine the flags to use to compile with your Kerberos libraries. If krb5-config isn't found, it will look for the standard Kerberos libraries in locations already @@ -163,10 +176,13 @@ COMPILING AND INSTALLING CONFIGURATION - First, build and install a CrackLib dictionary as described above. This - dictionary will consist of three files, one each ending in *.hwm, *.pwd, - and *.pwi. Install those files somewhere on your system. Then, follow - the relevant instructions below for either Heimdal or MIT Kerberos. + First, build and install either a CrackLib dictionary as described in + REQUIREMENTS above, or build a CDB dictionary with cdbmake-wordlist. + (Or both.) The CrackLib dictionary will consist of three files, one + each ending in *.hwm, *.pwd, and *.pwi. The CDB dictionary will consist + of a single file ending in *.cdb. Install those files somewhere on your + system. Then, follow the relevant instructions below for either Heimdal + or MIT Kerberos. Heimdal @@ -180,11 +196,15 @@ CONFIGURATION file is located): krb5-strength = { - password_dictionary = /usr/local/lib/kadmind/dictionary + password_dictionary = /path/to/cracklib/dictionary + password_dictionary_cdb = /path/to/cdb/dictionary.cdb } - The provided path should be the full path to the dictionary files, - omitting the trailing *.hwm, *.pwd, and *.pwi extensions. + The first setting configures a CrackLib dictionary and the second a CDB + dictionary. The provided path should be the full path to the dictionary + files, omitting the trailing *.hwm, *.pwd, and *.pwi extensions for the + CrackLib dictionary. You can use either or both settings. If you use + both, CrackLib will be checked first, and then CDB. Then, for the external password checking program, add a new section (or modify the existing [password_quality] section) to look like the @@ -234,22 +254,35 @@ CONFIGURATION [appdefaults] section: krb5-strength = { - password_dictionary = /path/to/cracklib/dictionary + password_dictionary = /path/to/cracklib/dictionary + password_dictionary_cdb = /path/to/cdb/dictionary.cdb } + The first setting configures a CrackLib dictionary and the second a CDB + dictionary. The provided path should be the full path to the dictionary + files, omitting the trailing *.hwm, *.pwd, and *.pwi extensions for the + CrackLib dictionary. You can use either or both settings. If you use + both, CrackLib will be checked first, and then CDB. + The second option is to use the normal dict_path setting. In the [realms] section of your krb5.conf kdc.conf, under the appropriate realm or realms, specify the path to the dictionary: dict_file = /path/to/cracklib/dictionary - The provided path should be the full path to the dictionary files, - omitting the trailing *.hwm, *.pwd, or *.pwi extension. However, be - aware that, if you use this approach, you will probably want to disable - the built-in standard dict pwqual plugin by adding the line: + This will be taken as a CrackLib dictionary path, the same as the + setting for password_dictionary above. The provided path should be the + full path to the dictionary files, omitting the trailing *.hwm, *.pwd, + or *.pwi extension. However, be aware that, if you use this approach, + you will probably want to disable the built-in standard dict pwqual + plugin by adding the line: disable = dict to the pwqual block of the [plugins] section as shown above. Otherwise, it will also try to load a dictionary at the same path to do simple dictionary matching. + + You can also mix and match these settings, by using dict_path for the + CrackLib dictionary path and krb5.conf for the CDB dictionary path. + There is no way to specify a CDB dictionary via the dict_path setting. diff --git a/autogen b/autogen index 6a9a8f0..1137b00 100755 --- a/autogen +++ b/autogen @@ -16,3 +16,7 @@ pod2man --release="$version" --center='krb5-strength' \ # Generate the C version of our password test data. tests/data/make-c-data tests/data/cracklib.json > tests/data/cracklib.c +tests/data/make-c-data tests/data/cdb.json > tests/data/cdb.c + +# Generate the CDB database from the test wordlist for plugin tests. +tools/cdbmake-wordlist tests/data/wordlist diff --git a/configure.ac b/configure.ac index 09792d1..32f5b95 100644 --- a/configure.ac +++ b/configure.ac @@ -27,6 +27,7 @@ AM_DISABLE_STATIC AC_PROG_LIBTOOL dnl External libraries. +RRA_LIB_CDB_OPTIONAL RRA_LIB_CRACKLIB RRA_LIB_KRB5 RRA_LIB_KRB5_SWITCH diff --git a/m4/cdb.m4 b/m4/cdb.m4 new file mode 100644 index 0000000..cb8c1f7 --- /dev/null +++ b/m4/cdb.m4 @@ -0,0 +1,60 @@ +dnl Find the compiler and linker flags for libcdb. +dnl +dnl Finds the compiler and linker flags for linking with the libcdb library. +dnl Provides the --with-libcdb, --with-libcdb-lib, and --with-libcdb-include +dnl configure options to specify non-standard paths to libcdb libraries. +dnl +dnl Provides the macros RRA_LIB_CDB and RRA_LIB_CDB_OPTIONAL and sets the +dnl substitution variables CDB_CPPFLAGS, CDB_LDFLAGS, and CDB_LIBS. Also +dnl provides RRA_LIB_CDB_SWITCH to set CPPFLAGS, LDFLAGS, and LIBS to include +dnl the kadmin client libraries, saving the ecurrent values, and +dnl RRA_LIB_CDB_RESTORE to restore those settings to before the last +dnl RRA_LIB_CDB_SWITCH. Define HAVE_CDB if the library is found. +dnl +dnl Depends on RRA_SET_LDFLAGS. +dnl +dnl Written by Russ Allbery +dnl Copyright 2010, 2013 +dnl The Board of Trustees of the Leland Stanford Junior University +dnl +dnl This file is free software; the authors give unlimited permission to copy +dnl and/or distribute it, with or without modifications, as long as this +dnl notice is preserved. + +dnl Save the current CPPFLAGS, LDFLAGS, and LIBS settings and switch to +dnl versions that include the kadmin client flags. Used as a wrapper, with +dnl RRA_LIB_CDB_RESTORE, around tests. +AC_DEFUN([RRA_LIB_CDB_SWITCH], [RRA_LIB_HELPER_SWITCH([CDB])]) + +dnl Restore CPPFLAGS, LDFLAGS, and LIBS to their previous values (before +dnl RRA_LIB_CDB_SWITCH was called). +AC_DEFUN([RRA_LIB_CDB_RESTORE], [RRA_LIB_HELPER_RESTORE([CDB])]) + +dnl Checks if the libcdb library is present. The single argument, if "true", +dnl says to fail if the libcdb library could not be found. +AC_DEFUN([_RRA_LIB_CDB_INTERNAL], +[RRA_LIB_HELPER_PATHS([CDB]) + RRA_LIB_CDB_SWITCH + AC_CHECK_LIB([cdb], [cdb_init], [CDB_LIBS=-lcdb], + [AS_IF([test x"$1" = xtrue], + [AC_MSG_ERROR([cannot find usable TinyCDB library])])]) + AC_CHECK_HEADERS([cdb.h]) + RRA_LIB_CDB_RESTORE]) + +dnl The main macro for packages with mandatory kadmin client support. +AC_DEFUN([RRA_LIB_CDB], +[RRA_LIB_HELPER_VAR_INIT([CDB]) + RRA_LIB_HELPER_WITH([tinycdb], [TinyCDB], [CDB]) + _RRA_LIB_CDB_INTERNAL([true]) + AC_DEFINE([HAVE_CDB], 1, [Define if libcdb is available.])]) + +dnl The main macro for packages with optional kadmin client support. +AC_DEFUN([RRA_LIB_CDB_OPTIONAL], +[RRA_LIB_HELPER_VAR_INIT([CDB]) + RRA_LIB_HELPER_WITH_OPTIONAL([tinycdb], [TinyCDB], [CDB]) + AS_IF([test x"$rra_use_CDB" != xfalse], + [AS_IF([test x"$rra_use_CDB" = xtrue], + [_RRA_LIB_CDB_INTERNAL([true])], + [_RRA_LIB_CDB_INTERNAL([false])])]) + AS_IF([test x"$CDB_LIBS" != x], + [AC_DEFINE([HAVE_CDB], 1, [Define if libcdb is available.])])]) diff --git a/m4/lib-helper.m4 b/m4/lib-helper.m4 new file mode 100644 index 0000000..aa53385 --- /dev/null +++ b/m4/lib-helper.m4 @@ -0,0 +1,138 @@ +dnl Helper functions to manage compiler variables. +dnl +dnl These are a wide variety of helper macros to make it easier to construct +dnl standard macros to probe for a library and to set library-specific +dnl CPPFLAGS, LDFLAGS, and LIBS shell substitution variables. Most of them +dnl take as one of the arguments the prefix string to use for variables, which +dnl is usually something like "KRB5" or "GSSAPI". +dnl +dnl Depends on RRA_ENABLE_REDUCED_DEPENDS and RRA_SET_LDFLAGS. +dnl +dnl The canonical version of this file is maintained in the rra-c-util +dnl package, available at . +dnl +dnl Written by Russ Allbery +dnl Copyright 2011 +dnl The Board of Trustees of the Leland Stanford Junior University +dnl +dnl This file is free software; the authors give unlimited permission to copy +dnl and/or distribute it, with or without modifications, as long as this +dnl notice is preserved. + +dnl Add the library flags to the default compiler flags and then remove them. +dnl +dnl To use these macros, pass the prefix string used for the variables as the +dnl only argument. For example, to use these for a library with KRB5 as a +dnl prefix, one would use: +dnl +dnl AC_DEFUN([RRA_LIB_KRB5_SWITCH], [RRA_LIB_HELPER_SWITCH([KRB5])]) +dnl AC_DEFUN([RRA_LIB_KRB5_RESTORE], [RRA_LIB_HELPER_RESTORE([KRB5])]) +dnl +dnl Then, wrap checks for library features with RRA_LIB_KRB5_SWITCH and +dnl RRA_LIB_KRB5_RESTORE. +AC_DEFUN([RRA_LIB_HELPER_SWITCH], +[rra_$1[]_save_CPPFLAGS="$CPPFLAGS" + rra_$1[]_save_LDFLAGS="$LDFLAGS" + rra_$1[]_save_LIBS="$LIBS" + CPPFLAGS="$$1[]_CPPFLAGS $CPPFLAGS" + LDFLAGS="$$1[]_LDFLAGS $LDFLAGS" + LIBS="$$1[]_LIBS $LIBS"]) + +AC_DEFUN([RRA_LIB_HELPER_RESTORE], +[CPPFLAGS="$rra_$1[]_save_CPPFLAGS" + LDFLAGS="$rra_$1[]_save_LDFLAGS" + LIBS="$rra_$1[]_save_LIBS"]) + +dnl Given _root, _libdir, and _includedir variables set for a library (set by +dnl RRA_LIB_HELPER_WITH*), set the LDFLAGS and CPPFLAGS variables for that +dnl library accordingly. Takes the variable prefix as the only argument. +AC_DEFUN([RRA_LIB_HELPER_PATHS], +[AS_IF([test x"$rra_$1[]_libdir" != x], + [$1[]_LDFLAGS="-L$rra_$1[]_libdir"], + [AS_IF([test x"$rra_$1[]_root" != x], + [RRA_SET_LDFLAGS([$1][_LDFLAGS], [${rra_$1[]_root}])])]) + AS_IF([test x"$rra_$1[]_includedir" != x], + [$1[]_CPPFLAGS="-I$rra_$1[]_includedir"], + [AS_IF([test x"$rra_$1[]_root" != x], + [AS_IF([test x"$rra_$1[]_root" != x/usr], + [$1[]_CPPFLAGS="-I${rra_$1[]_root}/include"])])])]) + +dnl Check whether a library works. This is used as a sanity check on the +dnl results of *-config shell scripts. Takes four arguments; the first, if +dnl "true", says that a working library is mandatory and errors out if it +dnl doesn't. The second is the variable prefix. The third is a function to +dnl look for that should be in the libraries. The fourth is the +dnl human-readable name of the library for error messages. +AC_DEFUN([RRA_LIB_HELPER_CHECK], +[RRA_LIB_HELPER_SWITCH([$2]) + AC_CHECK_FUNC([$3], [], + [AS_IF([test x"$1" = xtrue], + [AC_MSG_FAILURE([unable to link with $4 library])]) + $2[]_CPPFLAGS= + $2[]_LDFLAGS= + $2[]_LIBS=]) + RRA_LIB_HELPER_RESTORE([$2])]) + +dnl Initialize the variables used by a library probe and set the appropriate +dnl ones as substitution variables. Takes the library variable prefix as its +dnl only argument. +AC_DEFUN([RRA_LIB_HELPER_VAR_INIT], +[rra_$1[]_root= + rra_$1[]_libdir= + rra_$1[]_includedir= + rra_use_$1= + $1[]_CPPFLAGS= + $1[]_LDFLAGS= + $1[]_LIBS= + AC_SUBST([$1][_CPPFLAGS]) + AC_SUBST([$1][_LDFLAGS]) + AC_SUBST([$1][_LIBS])]) + +dnl Handles --with options for a non-optional library. First argument is the +dnl base for the switch names. Second argument is the short description. +dnl Third argument is the variable prefix. The variables set are used by +dnl RRA_LIB_HELPER_PATHS. +AC_DEFUN([RRA_LIB_HELPER_WITH], +[AC_ARG_WITH([$1], + [AS_HELP_STRING([--with-][$1][=DIR], + [Location of $2 headers and libraries])], + [AS_IF([test x"$withval" != xyes && test x"$withval" != xno], + [rra_$3[]_root="$withval"])]) + AC_ARG_WITH([$1][-include], + [AS_HELP_STRING([--with-][$1][-include=DIR], + [Location of $2 headers])], + [AS_IF([test x"$withval" != xyes && test x"$withval" != xno], + [rra_$3[]_includedir="$withval"])]) + AC_ARG_WITH([$1][-lib], + [AS_HELP_STRING([--with-][$1][-lib=DIR], + [Location of $2 libraries])], + [AS_IF([test x"$withval" != xyes && test x"$withval" != xno], + [rra_$3[]_libdir="$withval"])])]) + +dnl Handles --with options for an optional library, so --with- can +dnl cause the checks to be skipped entirely or become mandatory. Sets an +dnl rra_use_PREFIX variable to true or false if the library is explicitly +dnl enabled or disabled. +dnl +dnl First argument is the base for the switch names. Second argument is the +dnl short description. Third argument is the variable prefix. +dnl +dnl The variables set are used by RRA_LIB_HELPER_PATHS. +AC_DEFUN([RRA_LIB_HELPER_WITH_OPTIONAL], +[AC_ARG_WITH([$1], + [AS_HELP_STRING([--with-][$1][@<:@=DIR@:>@], + [Location of $2 headers and libraries])], + [AS_IF([test x"$withval" = xno], + [rra_use_$3=false], + [AS_IF([test x"$withval" != yes], [rra_$3[]_root="$withval"]) + rra_use_$3=true])]) + AC_ARG_WITH([$1][-include], + [AS_HELP_STRING([--with-][$1][-include=DIR], + [Location of $2 headers])], + [AS_IF([test x"$withval" != xyes && test x"$withval" != xno], + [rra_$3[]_includedir="$withval"])]) + AC_ARG_WITH([$1][-lib], + [AS_HELP_STRING([--with-][$1][-lib=DIR], + [Location of $2 libraries])], + [AS_IF([test x"$withval" != xyes && test x"$withval" != xno], + [rra_$3[]_libdir="$withval"])])]) diff --git a/plugin/api.c b/plugin/api.c index dbd6f34..273a48b 100644 --- a/plugin/api.c +++ b/plugin/api.c @@ -18,8 +18,13 @@ #include #include +#ifdef HAVE_CDB_H +# include +#endif #include #include +#include +#include #include #include @@ -29,6 +34,12 @@ # define KADM5_PASS_Q_GENERIC KADM5_PASS_Q_DICT #endif +/* If not built with CDB support, provide some stubs. */ +#ifndef HAVE_CDB +# define pwcheck_check_cdb(c, d, p) 0 +# define pwcheck_close_cdb(c, d) /* empty */ +#endif + /* The public function exported by the cracklib library. */ extern char *FascistCheck(const char *password, const char *dict); @@ -95,6 +106,91 @@ default_string(krb5_context ctx, const char *section, const char *opt, } +/* + * Initialize the CrackLib dictionary. Ensure that the dictionary file exists + * and is readable and store the path in the module context. Returns 0 on + * success, non-zero on failure. + * + * The dictionary file should not include the trailing .pwd extension. + * Currently, we don't cope with a NULL dictionary path. + */ +static krb5_error_code +init_cracklib(krb5_context ctx, krb5_pwqual_moddata data) +{ + char *file; + int oerrno; + + /* Sanity-check the dictionary path. */ + if (asprintf(&file, "%s.pwd", data->dictionary) < 0) { + oerrno = errno; + krb5_set_error_message(ctx, oerrno, "cannot allocate memory: %s", + strerror(oerrno)); + return oerrno; + } + if (access(file, R_OK) != 0) { + oerrno = errno; + krb5_set_error_message(ctx, oerrno, "dictionary %s does not exist: %s", + file, strerror(oerrno)); + free(file); + return oerrno; + } + free(file); + return 0; +} + + +#ifdef HAVE_CDB +/* + * Initialize the CDB dictionary. Opens the dictionary and sets up the + * TinyCDB state. Returns 0 on success, non-zero on failure (and sets the + * error in the Kerberos context). If not built with CDB support, always + * returns an error. + */ +static krb5_error_code +init_cdb(krb5_context ctx, krb5_pwqual_moddata data, const char *database) +{ + krb5_error_code code; + + data->cdb_fd = open(database, O_RDONLY); + if (data->cdb_fd < 0) { + code = errno; + krb5_set_error_message(ctx, code, "cannot open dictionary %s: %s", + database, strerror(errno)); + return code; + } + if (cdb_init(&data->cdb, data->cdb_fd) < 0) { + code = errno; + krb5_set_error_message(ctx, code, + "cannot initialize dictionary %s: %s", + database, strerror(errno)); + close(data->cdb_fd); + data->cdb_fd = -1; + return code; + } + data->have_cdb = true; + return 0; +} + +#else + +/* + * Stub for init_cdb if not built with CDB support. + */ +static krb5_error_code +init_cdb(krb5_context ctx, krb5_pwqual_moddata data UNUSED, + const char *database UNUSED) +{ + krb5_error_code code; + + code = KADM5_BAD_SERVER_PARAMS; + krb5_set_error_message(ctx, code, + "CDB dictionary requested but not built with CDB support"); + return code; +} + +#endif + + /* * Initialize the module. Ensure that the dictionary file exists and is * readable and store the path in the module context. Returns 0 on success, @@ -106,60 +202,70 @@ default_string(krb5_context ctx, const char *section, const char *opt, */ krb5_error_code pwcheck_init(krb5_context ctx, const char *dictionary, - krb5_pwqual_moddata *data) + krb5_pwqual_moddata *moddata) { - char *file; - char *path = NULL; - int oerrno; + krb5_pwqual_moddata data = NULL; + char *cdb_path = NULL; + krb5_error_code code; + + /* Allocate our internal data. */ + data = calloc(1, sizeof(*data)); + if (data == NULL) { + code = errno; + krb5_set_error_message(ctx, code, "cannot allocate memory: %s", + strerror(code)); + return code; + } + data->cdb_fd = -1; /* Use dictionary if given, otherwise get from krb5.conf. */ if (dictionary == NULL) - default_string(ctx, "krb5-strength", "password_dictionary", &path); + default_string(ctx, "krb5-strength", "password_dictionary", + &data->dictionary); else { - path = strdup(dictionary); - if (path == NULL) { - oerrno = errno; - krb5_set_error_message(ctx, oerrno, "cannot allocate memory"); - return oerrno; + data->dictionary = strdup(dictionary); + if (data->dictionary == NULL) { + code = errno; + krb5_set_error_message(ctx, code, "cannot allocate memory"); + goto fail; } } + /* Get CDB dictionary path from krb5.conf. */ + default_string(ctx, "krb5-strength", "password_dictionary_cdb", &cdb_path); + /* If there is no dictionary, abort our setup with an error. */ - if (path == NULL) { - krb5_set_error_message(ctx, KADM5_MISSING_CONF_PARAMS, + if (data->dictionary == NULL && cdb_path == NULL) { + code = KADM5_MISSING_CONF_PARAMS; + krb5_set_error_message(ctx, code, "password_dictionary not configured in krb5.conf"); - return KADM5_MISSING_CONF_PARAMS; + goto fail; } - /* Sanity-check the dictionary path. */ - if (asprintf(&file, "%s.pwd", path) < 0) { - oerrno = errno; - krb5_set_error_message(ctx, oerrno, "cannot allocate memory: %s", - strerror(oerrno)); - free(path); - return oerrno; + /* If there is a CrackLib dictionary, initialize CrackLib. */ + if (data->dictionary != NULL) { + code = init_cracklib(ctx, data); + if (code != 0) + goto fail; } - if (access(file, R_OK) != 0) { - oerrno = errno; - krb5_set_error_message(ctx, oerrno, "dictionary %s does not exist: %s", - file, strerror(oerrno)); - free(path); - free(file); - return oerrno; - } - free(file); - /* Everything looks good. Allocate and store our internal data. */ - *data = malloc(sizeof(**data)); - if (*data == NULL) { - oerrno = errno; - krb5_set_error_message(ctx, oerrno, "cannot allocate memory: %s", - strerror(oerrno)); - free(path); - return oerrno; + /* If there is a CDB dictionary, initialize TinyCDB. */ + if (cdb_path != NULL) { + code = init_cdb(ctx, data, cdb_path); + if (code != 0) + goto fail; } - (*data)->dictionary = path; + + /* Initialized. Set moddata and return. */ + *moddata = data; return 0; + +fail: + if (data != NULL) + pwcheck_close(ctx, data); + free(cdb_path); + *moddata = NULL; + return code; } @@ -178,6 +284,7 @@ pwcheck_check(krb5_context ctx UNUSED, krb5_pwqual_moddata data, char c; int oerrno; const char *result; + krb5_error_code code; /* * We get the principal (in krb5_unparse_name format) from kadmind and we @@ -241,10 +348,21 @@ pwcheck_check(krb5_context ctx UNUSED, krb5_pwqual_moddata data, } } free(user); - result = FascistCheck(password, data->dictionary); - if (result != NULL) { - krb5_set_error_message(ctx, KADM5_PASS_Q_GENERIC, "%s", result); - return KADM5_PASS_Q_GENERIC; + + /* Check the password against CrackLib if it is configured. */ + if (data->dictionary != NULL) { + result = FascistCheck(password, data->dictionary); + if (result != NULL) { + krb5_set_error_message(ctx, KADM5_PASS_Q_GENERIC, "%s", result); + return KADM5_PASS_Q_GENERIC; + } + } + + /* Check the password against CDB if it is configured. */ + if (data->have_cdb) { + code = pwcheck_check_cdb(ctx, data, password); + if (code != 0) + return code; } return 0; } @@ -258,6 +376,7 @@ void pwcheck_close(krb5_context ctx UNUSED, krb5_pwqual_moddata data) { if (data != NULL) { + pwcheck_close_cdb(ctx, data); free(data->dictionary); free(data); } diff --git a/plugin/api.h b/plugin/api.h index 0a7668e..827cf86 100644 --- a/plugin/api.h +++ b/plugin/api.h @@ -16,6 +16,10 @@ #include #include +#ifdef HAVE_CDB_H +# include +#endif + #ifdef HAVE_KRB5_PWQUAL_PLUGIN_H # include #else @@ -29,6 +33,11 @@ typedef struct krb5_pwqual_moddata_st *krb5_pwqual_moddata; */ struct krb5_pwqual_moddata_st { char *dictionary; + bool have_cdb; + int cdb_fd; +#ifdef HAVE_CDB_H + struct cdb cdb; +#endif }; /* Initialize the plugin and set up configuration. */ @@ -42,7 +51,14 @@ krb5_error_code pwcheck_init(krb5_context, const char *dictionary, krb5_error_code pwcheck_check(krb5_context, krb5_pwqual_moddata, const char *password, const char *principal); +/* Check a password (and some permutations) against a CDB database. */ +krb5_error_code pwcheck_check_cdb(krb5_context, krb5_pwqual_moddata, + const char *password); + /* Finished checking passwords. Free internal data. */ void pwcheck_close(krb5_context, krb5_pwqual_moddata); +/* Free the subset of internal data used by the CDB module. */ +void pwcheck_close_cdb(krb5_context, krb5_pwqual_moddata); + #endif /* !PLUGIN_INTERNAL_H */ diff --git a/plugin/cdb.c b/plugin/cdb.c new file mode 100644 index 0000000..02f349c --- /dev/null +++ b/plugin/cdb.c @@ -0,0 +1,154 @@ +/* + * Check a CDB database for a password or some simple permutations. + * + * This file implements a much simpler variation on CrackLib checks intended + * for use with longer passwords where some of the CrackLib permutations don't + * make as much sense. A CDB database with passwords as keys is checked for + * the password and for variations with one character removed from the start + * or end, two characters removed from the start, two from the end, or one + * character from both start and end. + * + * Written by Russ Allbery + * Copyright 2013 + * The Board of Trustees of the Leland Stanford Junior University + * + * See LICENSE for licensing terms. + */ + +#include +#include +#include +#include + +#ifdef HAVE_CDB_H +# include +#endif +#include + +#include +#include + +/* Skip the rest of this file if CDB is not available. */ +#ifdef HAVE_CDB + +/* + * Macro used to make password checks more readable. Assumes that the found + * and fail labels are available for the abort cases of finding a password or + * failing to look it up. + */ +# define CHECK_PASSWORD(ctx, data, password) \ + do { \ + code = in_cdb_dictionary(ctx, data, password, &found); \ + if (code != 0) \ + goto fail; \ + if (found) \ + goto found; \ + } while (0) + + +/* + * Look up a password in CDB and set the found parameter to true if it is + * found, false otherwise. Returns a Kerberos status code, which will be 0 on + * success and something else on failure. + */ +static krb5_error_code +in_cdb_dictionary(krb5_context ctx, krb5_pwqual_moddata data, + const char *password, bool *found) +{ + int status, oerrno; + + status = cdb_find(&data->cdb, password, strlen(password)); + if (status < 0) { + oerrno = errno; + krb5_set_error_message(ctx, oerrno, "cannot query CDB database: %s", + strerror(oerrno)); + return oerrno; + } else { + *found = (status == 1); + return 0; + } +} + + +/* + * Given a password, try the various transformations that we want to apply and + * check for each of them in the dictionary. Returns a Kerberos status code, + * which will be KADM5_PASS_Q_DICT if the password was found in the + * dictionary. + */ +krb5_error_code +pwcheck_check_cdb(krb5_context ctx, krb5_pwqual_moddata data, + const char *password) +{ + krb5_error_code code; + bool found; + char *variant = NULL; + int oerrno; + + /* Check the basic password. */ + CHECK_PASSWORD(ctx, data, password); + + /* Check with one or two characters removed from the start. */ + if (password[0] != '\0') { + CHECK_PASSWORD(ctx, data, password + 1); + if (password[1] != '\0') + CHECK_PASSWORD(ctx, data, password + 2); + } + + /* + * Strip a character from the end and then check both that password and + * the one with a character taken from the start as well. + */ + if (strlen(password) > 0) { + variant = strdup(password); + if (variant == NULL) { + oerrno = errno; + krb5_set_error_message(ctx, oerrno, "cannot allocate memory: %s", + strerror(oerrno)); + return oerrno; + } + variant[strlen(variant) - 1] = '\0'; + CHECK_PASSWORD(ctx, data, variant); + if (variant[0] != '\0') + CHECK_PASSWORD(ctx, data, variant + 1); + + /* Check the password with two characters removed. */ + if (strlen(password) > 1) { + variant[strlen(variant) - 1] = '\0'; + CHECK_PASSWORD(ctx, data, variant); + } + free(variant); + } + + /* Password not found. */ + return 0; + +found: + /* We found the password or a variant in the dictionary. */ + if (variant != NULL) + free(variant); + krb5_set_error_message(ctx, KADM5_PASS_Q_DICT, + "it is based on a dictionary word"); + return KADM5_PASS_Q_DICT; + +fail: + /* Some sort of failure during CDB lookup. */ + if (variant != NULL) + free(variant); + return code; +} + + +/* + * Free internal TinyCDB state and close the CDB dictionary. + */ +void +pwcheck_close_cdb(krb5_context ctx UNUSED, krb5_pwqual_moddata data) +{ + if (data->have_cdb) + cdb_free(&data->cdb); + if (data->cdb_fd != -1) + close(data->cdb_fd); +} + +#endif /* HAVE_CDB */ diff --git a/tests/data/cdb.json b/tests/data/cdb.json new file mode 100644 index 0000000..7dd1cd3 --- /dev/null +++ b/tests/data/cdb.json @@ -0,0 +1,63 @@ +[ + { + "name": "good password", + "principal": "test@EXAMPLE.ORG", + "password": "known good password", + "code": 0 + }, + { + "name": "in dictionary", + "principal": "test@EXAMPLE.ORG", + "password": "password", + "code": "KADM5_PASS_Q_DICT", + "error": "it is based on a dictionary word" + }, + { + "name": "in dictionary (longer)", + "principal": "test@EXAMPLE.ORG", + "password": "bitterbane", + "code": "KADM5_PASS_Q_DICT", + "error": "it is based on a dictionary word" + }, + { + "name": "in dictionary (drop first)", + "principal": "test@EXAMPLE.ORG", + "password": "1bitterbane", + "code": "KADM5_PASS_Q_DICT", + "error": "it is based on a dictionary word" + }, + { + "name": "in dictionary (drop last)", + "principal": "test@EXAMPLE.ORG", + "password": "bitterbane1", + "code": "KADM5_PASS_Q_DICT", + "error": "it is based on a dictionary word" + }, + { + "name": "in dictionary (drop first two)", + "principal": "test@EXAMPLE.ORG", + "password": "abbitterbane", + "code": "KADM5_PASS_Q_DICT", + "error": "it is based on a dictionary word" + }, + { + "name": "in dictionary (drop last two)", + "principal": "test@EXAMPLE.ORG", + "password": "bitterbane12", + "code": "KADM5_PASS_Q_DICT", + "error": "it is based on a dictionary word" + }, + { + "name": "in dictionary (drop first and last)", + "principal": "test@EXAMPLE.ORG", + "password": "'bitterbane'", + "code": "KADM5_PASS_Q_DICT", + "error": "it is based on a dictionary word" + }, + { + "name": "dictionary with three characters", + "principal": "test@EXAMPLE.ORG", + "password": "bitterbane123", + "code": 0 + } +] diff --git a/tests/data/make-krb5-conf b/tests/data/make-krb5-conf index 59abf6f..baf4187 100755 --- a/tests/data/make-krb5-conf +++ b/tests/data/make-krb5-conf @@ -26,12 +26,19 @@ fi # Copy over the template. cp "$source" "$tmpdir"/krb5.conf +# Figure out whether we're testing the cdb or cracklib dictionary. +setting= +case "$dict" in + *.cdb) setting=password_dictionary_cdb ;; + *) setting=password_dictionary ;; +esac + # Add the appdefaults section. cat <>"$tmpdir"/krb5.conf [appdefaults] krb5-strength = { - password_dictionary = $dict + $setting = $dict } EOF diff --git a/tests/data/password-tests.h b/tests/data/password-tests.h index e4f850e..6de74a9 100644 --- a/tests/data/password-tests.h +++ b/tests/data/password-tests.h @@ -11,6 +11,9 @@ * See LICENSE for licensing terms. */ +#ifndef TESTS_DATA_PASSWORD_TESTS_H +#define TESTS_DATA_PASSWORD_TESTS_H 1 + #include #include #include @@ -22,3 +25,5 @@ struct password_test { krb5_error_code code; const char *error; }; + +#endif /* !TESTS_DATA_PASSWORD_TESTS_H */ diff --git a/tests/heimdal/external-t b/tests/heimdal/external-t index 66d4761..4eaf519 100755 --- a/tests/heimdal/external-t +++ b/tests/heimdal/external-t @@ -23,6 +23,35 @@ use_prereq('IPC::Run', 'run'); use_prereq('JSON'); use_prereq('Test::More', '0.87_01'); +# Run the newly-built heimdal-strength command and return the status, output, +# and error output as a list. +# +# $principal - Principal to pass to the command +# $password - Password to pass to the command +# +# Returns: The exit status, standard output, and standard error as a list +# Throws: Text exception on failure to run the test program +sub run_heimdal_strength { + my ($principal, $password) = @_; + + # Build the input to the strength checking program. + my $in = "principal: $principal\n"; + $in .= "new-password: $password\n"; + $in .= "end\n"; + + # Find the newly-built password checking program. + my $program = test_file_path('../external/heimdal-strength'); + + # Run the password strength checker. + my $out; + my $err; + run([$program, $principal], \$in, \$out, \$err); + my $status = ($? >> 8); + + # Return the results. + return ($status, $out, $err); +} + # Run the newly-built heimdal-strength command to check a password and reports # the results using Test::More. This uses the standard protocol for Heimdal # external password strength checking programs. @@ -38,20 +67,11 @@ use_prereq('Test::More', '0.87_01'); # Throws: Text exception on failure to run the test program sub check_password { my ($test_ref) = @_; + my $principal = $test_ref->{principal}; + my $password = $test_ref->{password}; - # Build the input to the strength checking program. - my $in = "principal: $test_ref->{principal}\n"; - $in .= "new-password: $test_ref->{password}\n"; - $in .= "end\n"; - - # Find the newly-built password checking program. - my $program = test_file_path('../external/heimdal-strength'); - - # Run the password strength checker. - my $out; - my $err; - run([$program, $test_ref->{principal}], \$in, \$out, \$err); - my $status = ($? >> 8); + # Run the heimdal-strength command. + my ($status, $out, $err) = run_heimdal_strength($principal, $password); chomp($out, $err); # Check the results. If there is an error in the password, it should come @@ -71,11 +91,15 @@ sub check_password { # Create a new krb5.conf file that includes password dictionary configuration # so that subsequent invocations of heimdal-strength can find the testing -# dictionary. +# dictionary. Takes the setting and the value of the setting to write. +# +# $setting - krb5.conf setting to add to [appdefaults] +# $dictionary - The dictionary file name, relative to tests/data # # Returns: Path to the new krb5.conf file # Throws: Text exception if the new krb5.conf file cannot be created sub create_krb5_conf { + my ($setting, $dictionary) = @_; my $old = test_file_path('data/krb5.conf'); my $tmpdir = $ENV{BUILD} ? "$ENV{BUILD}/tmp" : 'tests/tmp'; my $new = "$tmpdir/krb5.conf"; @@ -95,7 +119,7 @@ sub create_krb5_conf { [appdefaults] krb5-strength = { - password_dictionary = $datadir/dictionary + $setting = $datadir/$dictionary } __END__ or die "Cannot append to $new: $!\n"; @@ -123,11 +147,12 @@ sub load_password_tests { } # Load the password tests from JSON. -my $tests = load_password_tests(test_file_path('data/cracklib.json')); +my $cracklib_tests = load_password_tests(test_file_path('data/cracklib.json')); +my $cdb_tests = load_password_tests(test_file_path('data/cdb.json')); # We can now calculate our plan: three tests for each password test, with an # extra set for testing behavior when password_dictionary is not configured. -plan(tests => (scalar(@{$tests}) + 1) * 3); +plan(tests => (scalar(@{$cracklib_tests}) + scalar(@{$cdb_tests}) + 1) * 3); # Find our initial test krb5.conf file. local $ENV{KRB5_CONFIG} = test_file_path('data/krb5.conf'); @@ -146,14 +171,32 @@ my $test = { check_password($test); # Install the krb5.conf file with a configuration pointing to the test -# dictionary. -local $ENV{KRB5_CONFIG} = create_krb5_conf(); +# CrackLib dictionary. +my $krb5_conf = create_krb5_conf('password_dictionary', 'dictionary'); +local $ENV{KRB5_CONFIG} = $krb5_conf; -# Run the password tests from JSON. -for my $test (@{$tests}) { +# Run the CrackLib password tests from JSON. +for my $test (@{$cracklib_tests}) { check_password($test); } +# Install the krb5.conf file with configuration pointing to the CDB +# dictionary. +$krb5_conf = create_krb5_conf('password_dictionary_cdb', 'wordlist.cdb'); +local $ENV{KRB5_CONFIG} = $krb5_conf; + +# Check whether we were built with CDB support. If so, run those tests. +my ($status, $output, $err) = run_heimdal_strength('test', 'password'); +SKIP: { + skip('not built with CDB support', scalar(@{$cdb_tests}) * 3) + if ($status == 1 && $err =~ m{ not [ ] built [ ] with [ ] CDB }xms); + + # Run the CDB password tests from JSON. + for my $test (@{$cdb_tests}) { + check_password($test); + } +} + # Clean up our temporary krb5.conf file on any exit. END { my $tmpdir = $ENV{BUILD} ? "$ENV{BUILD}/tmp" : 'tests/tmp'; diff --git a/tests/heimdal/plugin-t.c b/tests/heimdal/plugin-t.c index 44153fe..12e4424 100644 --- a/tests/heimdal/plugin-t.c +++ b/tests/heimdal/plugin-t.c @@ -49,9 +49,10 @@ struct kadm5_pw_policy_verifier { #endif /* !HAVE_KADM5_PWCHECK_H */ /* - * The password test data, generated from the JSON source. Defines an array - * named cracklib_tests. + * The password test data, generated from the JSON source. Defines an arrays + * named cracklib_tests and CDB_tests. */ +#include #include @@ -164,7 +165,7 @@ main(void) * metadata, one more password test than the list of password tests we * have configured, and two tests per password test. */ - plan(5 + (ARRAY_SIZE(cracklib_tests) + 1) * 2); + plan(5 + (ARRAY_SIZE(cracklib_tests) + ARRAY_SIZE(cdb_tests) + 1) * 2); /* Start with the krb5.conf that contains no dictionary configuration. */ path = test_file_path("data/krb5.conf"); @@ -189,9 +190,6 @@ main(void) setup_argv[3] = tmpdir; setup_argv[4] = NULL; run_setup((const char **) setup_argv); - test_file_path_free(setup_argv[0]); - free(setup_argv[1]); - test_file_path_free(path); /* Point KRB5_CONFIG at the newly-generated krb5.conf file. */ basprintf(&krb5_config, "KRB5_CONFIG=%s/krb5.conf", tmpdir); @@ -202,6 +200,29 @@ main(void) for (i = 0; i < ARRAY_SIZE(cracklib_tests); i++) is_password_test(verifier, &cracklib_tests[i]); +#ifdef HAVE_CDB + + /* If built with CDB, set up krb5.conf to use a CDB dictionary instead. */ + free(setup_argv[1]); + setup_argv[1] = test_file_path("data/wordlist.cdb"); + if (setup_argv[1] == NULL) + bail("cannot find data/wordlist.cdb in the test suite"); + run_setup((const char **) setup_argv); + test_file_path_free(setup_argv[0]); + test_file_path_free(setup_argv[1]); + test_file_path_free(path); + + /* Run the CDB tests. */ + for (i = 0; i < ARRAY_SIZE(cdb_tests); i++) + is_password_test(verifier, &cdb_tests[i]); + +#else /* !HAVE_CDB */ + + /* Otherwise, mark the CDB tests as skipped. */ + skip_block(ARRAY_SIZE(cdb_tests) * 2, "not built with CDB support"); + +#endif /* !HAVE_CDB */ + /* Manually clean up after the results of make-krb5-conf. */ basprintf(&path, "%s/krb5.conf", tmpdir); unlink(path); diff --git a/tests/mit/plugin-t.c b/tests/mit/plugin-t.c index 5cf4d5d..f192ac0 100644 --- a/tests/mit/plugin-t.c +++ b/tests/mit/plugin-t.c @@ -26,9 +26,10 @@ #include /* - * The password test data, generated from the JSON source. Defines an array - * named cracklib_tests. + * The password test data, generated from the JSON source. Defines an arrays + * named cracklib_tests and cdb_tests. */ +#include #include @@ -153,12 +154,12 @@ main(void) /* * Calculate how many tests we have. There are two tests for the module - * metadata, three more tests for initializing the plugin, one test for + * metadata, four more tests for initializing the plugin, one test for * initialization without a valid dictionary, and two tests per password * test. We run all the cracklib tests twice, once with an explicit * dictionary path and once from krb5.conf configuration. */ - plan(2 + 3 + 2 * ARRAY_SIZE(cracklib_tests) * 2); + plan(2 + 4 + (2 * ARRAY_SIZE(cracklib_tests) + ARRAY_SIZE(cdb_tests)) * 2); /* Start with the krb5.conf that contains no dictionary configuration. */ path = test_file_path("data/krb5.conf"); @@ -205,8 +206,6 @@ main(void) setup_argv[3] = tmpdir; setup_argv[4] = NULL; run_setup((const char **) setup_argv); - test_file_path_free(setup_argv[0]); - test_file_path_free(path); /* Point KRB5_CONFIG at the newly-generated krb5.conf file. */ basprintf(&krb5_config, "KRB5_CONFIG=%s/krb5.conf", tmpdir); @@ -214,17 +213,50 @@ main(void) free(krb5_config_empty); /* Obtain a new Kerberos context with that krb5.conf file. */ + krb5_free_context(ctx); code = krb5_init_context(&ctx); if (code != 0) bail_krb5(ctx, code, "cannot initialize Kerberos context"); /* Run all of the tests again. */ - code = vtable->open(ctx, dictionary, &data); + code = vtable->open(ctx, NULL, &data); is_int(0, code, "Plugin initialization (krb5.conf dictionary)"); for (i = 0; i < ARRAY_SIZE(cracklib_tests); i++) is_password_test(ctx, vtable, data, &cracklib_tests[i]); vtable->close(ctx, data); +#ifdef HAVE_CDB + + /* If built with CDB, set up krb5.conf to use a CDB dictionary instead. */ + free(dictionary); + dictionary = test_file_path("data/wordlist.cdb"); + if (dictionary == NULL) + bail("cannot find data/wordlist.cdb in the test suite"); + setup_argv[1] = dictionary; + run_setup((const char **) setup_argv); + test_file_path_free(setup_argv[0]); + test_file_path_free(path); + + /* Obtain a new Kerberos context with that krb5.conf file. */ + krb5_free_context(ctx); + code = krb5_init_context(&ctx); + if (code != 0) + bail_krb5(ctx, code, "cannot initialize Kerberos context"); + + /* Run the CDB tests. */ + code = vtable->open(ctx, NULL, &data); + is_int(0, code, "Plugin initialization (CDB dictionary)"); + for (i = 0; i < ARRAY_SIZE(cdb_tests); i++) + is_password_test(ctx, vtable, data, &cdb_tests[i]); + vtable->close(ctx, data); + +#else /* !HAVE_CDB */ + + /* Otherwise, mark the CDB tests as skipped. */ + skip_block(ARRAY_SIZE(cdb_tests) * 2 + 1, "not built with CDB support"); + +#endif /* !HAVE_CDB */ + /* Manually clean up after the results of make-krb5-conf. */ basprintf(&path, "%s/krb5.conf", tmpdir); unlink(path); @@ -232,7 +264,7 @@ main(void) test_tmpdir_free(tmpdir); /* Keep valgrind clean by freeing all memory. */ - free(dictionary); + test_file_path_free(dictionary); krb5_free_context(ctx); free(vtable); putenv((char *) "KRB5_CONFIG="); -- 2.39.2