]> eyrie.org Git - kerberos/krb5-strength.git/commitdiff
Add support for checking against a CDB database
authorRuss Allbery <rra@stanford.edu>
Tue, 1 Oct 2013 06:15:51 +0000 (23:15 -0700)
committerRuss Allbery <rra@stanford.edu>
Tue, 1 Oct 2013 06:15:51 +0000 (23:15 -0700)
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.

17 files changed:
.gitignore
Makefile.am
NEWS
README
autogen
configure.ac
m4/cdb.m4 [new file with mode: 0644]
m4/lib-helper.m4 [new file with mode: 0644]
plugin/api.c
plugin/api.h
plugin/cdb.c [new file with mode: 0644]
tests/data/cdb.json [new file with mode: 0644]
tests/data/make-krb5-conf
tests/data/password-tests.h
tests/heimdal/external-t
tests/heimdal/plugin-t.c
tests/mit/plugin-t.c

index 4ec5414a65cd60400aca17ec8ff9a1ff6d4d8df4..b7018c4e541585734a2152a3248f1fdedbd0fe4e 100644 (file)
 /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
index a1e3ca9d557d80d88f265f5e9ebcc5484ac8f35e..e1292ad0e5673c7501782cdd0725135d38447293 100644 (file)
@@ -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 d6da64862dd1443859011943c0df5f15f4dd1583..1bb32663cf7b4d6b0d8dea22e3c61c84415b01ac 100644 (file)
--- 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 faa3fab6a97a232dbfc8872d0781d951f35f9d31..f36ebb52956ef02e041cd003cfe958aec5a737e8 100644 (file)
--- 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 6a9a8f0a4fe965b8918c65f4889ca44150c716c2..1137b00aaebb4b7c8a93bc9500644e3ce0dd4918 100755 (executable)
--- 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
index 09792d129b7ac848f6556ddd59824578f9abe0e4..32f5b958669c61719d6a4f15c498b5413fe37bda 100644 (file)
@@ -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 (file)
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 <rra@stanford.edu>
+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 (file)
index 0000000..aa53385
--- /dev/null
@@ -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 <http://www.eyrie.org/~eagle/software/rra-c-util/>.
+dnl
+dnl Written by Russ Allbery <rra@stanford.edu>
+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-<library> 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"])])])
index dbd6f34884d8daf4ad5b9b363d0142ad7fa36483..273a48b11ac82935432a0a10b8e9fb160d9916fb 100644 (file)
 #include <portable/krb5.h>
 #include <portable/system.h>
 
+#ifdef HAVE_CDB_H
+# include <cdb.h>
+#endif
 #include <ctype.h>
 #include <errno.h>
+#include <fcntl.h>
+#include <sys/stat.h>
 
 #include <plugin/api.h>
 #include <util/macros.h>
 # 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);
     }
index 0a7668e67655ebb6ca88f2cd5ae10ccbba6065b4..827cf86834c44149912b30cc6b0f73513ee0fd58 100644 (file)
 #include <config.h>
 #include <portable/krb5.h>
 
+#ifdef HAVE_CDB_H
+# include <cdb.h>
+#endif
+
 #ifdef HAVE_KRB5_PWQUAL_PLUGIN_H
 # include <krb5/pwqual_plugin.h>
 #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 (file)
index 0000000..02f349c
--- /dev/null
@@ -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 <rra@stanford.edu>
+ * Copyright 2013
+ *     The Board of Trustees of the Leland Stanford Junior University
+ *
+ * See LICENSE for licensing terms.
+ */
+
+#include <config.h>
+#include <portable/kadmin.h>
+#include <portable/krb5.h>
+#include <portable/system.h>
+
+#ifdef HAVE_CDB_H
+# include <cdb.h>
+#endif
+#include <errno.h>
+
+#include <plugin/api.h>
+#include <util/macros.h>
+
+/* 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 (file)
index 0000000..7dd1cd3
--- /dev/null
@@ -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
+    }
+]
index 59abf6f441c1311d5844af27e8d97106b7173110..baf4187642a15ff71efe53125639c3a3f0c4b886 100755 (executable)
@@ -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 <<EOF >>"$tmpdir"/krb5.conf
 
 [appdefaults]
     krb5-strength = {
-        password_dictionary = $dict
+        $setting = $dict
     }
 EOF
 
index e4f850e37f0fcf1f927c81a47aa4526ad4b850fc..6de74a91eb092423a9589ec7f507f2a62b80b461 100644 (file)
@@ -11,6 +11,9 @@
  * See LICENSE for licensing terms.
  */
 
+#ifndef TESTS_DATA_PASSWORD_TESTS_H
+#define TESTS_DATA_PASSWORD_TESTS_H 1
+
 #include <config.h>
 #include <portable/kadmin.h>
 #include <portable/krb5.h>
@@ -22,3 +25,5 @@ struct password_test {
     krb5_error_code code;
     const char *error;
 };
+
+#endif /* !TESTS_DATA_PASSWORD_TESTS_H */
index 66d4761864def5d7c5b82e54e34a0e507cad1864..4eaf519d7b15c5435676957dc59d5221755c1b1e 100755 (executable)
@@ -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';
index 44153febe13d1ded7cfc6f62df49b8574c923200..12e4424222a5aafa5001f84670dc8bea035e7494 100644 (file)
@@ -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 <tests/data/cdb.c>
 #include <tests/data/cracklib.c>
 
 
@@ -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);
index 5cf4d5d6b7083cfcb1f556699320ff0c464186c7..f192ac0c13627c680f19123655f594419272c3f4 100644 (file)
 #include <util/macros.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 <tests/data/cdb.c>
 #include <tests/data/cracklib.c>
 
 
@@ -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=");