/*
* Test for the MIT Kerberos shared module API.
*
- * Written by Russ Allbery <rra@stanford.edu>
- * Copyright 2010, 2013
+ * Written by Russ Allbery <eagle@eyrie.org>
+ * Copyright 2010, 2013, 2014
* The Board of Trustees of the Leland Stanford Junior University
*
* See LICENSE for licensing terms.
#include <util/macros.h>
/*
- * The password test data, generated from the JSON source. Defines an arrays
- * named cdb_tests, cracklib_tests, and generic_tests.
+ * The password test data, generated from the JSON source. Defines arrays
+ * named *_tests, where * is the file name without the ".c" suffix.
*/
#include <tests/data/passwords/cdb.c>
-#include <tests/data/passwords/class.c>
+#include <tests/data/passwords/classes.c>
#include <tests/data/passwords/cracklib.c>
-#include <tests/data/passwords/generic.c>
#include <tests/data/passwords/length.c>
+#include <tests/data/passwords/letter.c>
+#include <tests/data/passwords/principal.c>
+#include <tests/data/passwords/sqlite.c>
#ifndef HAVE_KRB5_PWQUAL_PLUGIN_H
/*
* Loads the Heimdal password change plugin and tests that its metadata is
* correct. Returns a pointer to the kadm5_pw_policy_verifier struct or bails
- * on failure to load the plugin.
+ * on failure to load the plugin. Stores the handle from dlopen in its second
+ * argument for a later clean shutdown.
*/
static krb5_pwqual_vtable
-load_plugin(krb5_context ctx)
+load_plugin(krb5_context ctx, void **handle)
{
char *path;
- void *handle;
krb5_error_code code;
krb5_pwqual_vtable vtable = NULL;
krb5_error_code (*init)(krb5_context, int, int, krb5_plugin_vtable);
path = test_file_path("../plugin/.libs/strength.so");
if (path == NULL)
bail("cannot find plugin");
- handle = dlopen(path, RTLD_NOW);
- if (handle == NULL)
+ *handle = dlopen(path, RTLD_NOW);
+ if (*handle == NULL)
bail("cannot dlopen %s: %s", path, dlerror());
test_file_path_free(path);
/* Find the entry point function. */
- init = dlsym(handle, "pwqual_strength_initvt");
+ init = dlsym(*handle, "pwqual_strength_initvt");
if (init == NULL)
bail("cannot get pwqual_strength_initvt symbol: %s", dlerror());
main(void)
{
char *path, *dictionary, *krb5_config, *krb5_config_empty, *tmpdir;
- char *setup_argv[10];
+ char *setup_argv[12];
const char*build;
size_t i, count;
krb5_context ctx;
krb5_pwqual_vtable vtable;
krb5_pwqual_moddata data;
krb5_error_code code;
+ void *handle;
/*
* Calculate how many tests we have. There are two tests for the module
- * metadata, six more tests for initializing the plugin, one test for
- * initialization without a valid dictionary, and two tests per password
- * test.
+ * metadata, seven more tests for initializing the plugin, and two tests
+ * per password test.
*
- * We run all the CrackLib and generic tests twice, once with an explicit
- * dictionary path and once from krb5.conf configuration. We run the
- * generic tests with both CrackLib and CDB configurations.
+ * We run all the CrackLib tests twice, once with an explicit dictionary
+ * path and once from krb5.conf configuration. We run the principal tests
+ * with CrackLib, CDB, and SQLite configurations.
*/
count = 2 * ARRAY_SIZE(cracklib_tests);
+ count += 2 * ARRAY_SIZE(length_tests);
count += ARRAY_SIZE(cdb_tests);
- count += ARRAY_SIZE(class_tests);
- count += ARRAY_SIZE(length_tests);
- count += 2 * ARRAY_SIZE(generic_tests);
- plan(2 + 6 + count * 2);
+ count += ARRAY_SIZE(sqlite_tests);
+ count += ARRAY_SIZE(classes_tests);
+ count += ARRAY_SIZE(letter_tests);
+ count += 3 * ARRAY_SIZE(principal_tests);
+ plan(2 + 8 + count * 2);
/* Start with the krb5.conf that contains no dictionary configuration. */
path = test_file_path("data/krb5.conf");
if (code != 0)
bail_krb5(ctx, code, "cannot initialize Kerberos context");
- /* Load and initialize the plugin without a dictionary. */
- vtable = load_plugin(ctx);
- code = vtable->open(ctx, NULL, &data);
- is_int(KADM5_MISSING_CONF_PARAMS, code,
- "Plugin initialization (no dictionary)");
+ /* Load the plugin. */
+ vtable = load_plugin(ctx, &handle);
- /* Initialize the plugin again with the correct dictionary. */
+ /* Initialize the plugin with a CrackLib dictionary. */
build = getenv("BUILD");
if (build == NULL)
bail("BUILD not set in the environment");
if (code != 0)
bail("cannot continue after plugin initialization failure");
- /* Now, run all of the tests, with generic tests. */
+ /* Now, run all of the tests, with principal tests. */
for (i = 0; i < ARRAY_SIZE(cracklib_tests); i++)
is_password_test(ctx, vtable, data, &cracklib_tests[i]);
- for (i = 0; i < ARRAY_SIZE(generic_tests); i++)
- is_password_test(ctx, vtable, data, &generic_tests[i]);
+ for (i = 0; i < ARRAY_SIZE(principal_tests); i++)
+ is_password_test(ctx, vtable, data, &principal_tests[i]);
/* Close that initialization of the plugin and destroy that context. */
vtable->close(ctx, data);
if (code != 0)
bail_krb5(ctx, code, "cannot initialize Kerberos context");
- /* Run all of the tests again. No need to re-run generic tests. */
+ /* Run all of the tests again. No need to re-run principal tests. */
code = vtable->open(ctx, NULL, &data);
is_int(0, code, "Plugin initialization (krb5.conf dictionary)");
if (code != 0)
is_password_test(ctx, vtable, data, &cracklib_tests[i]);
vtable->close(ctx, data);
- /* Add character class configuration to krb5.conf. */
+ /*
+ * Add length restrictions and a maximum length for CrackLib. This should
+ * reject passwords as too short, but let through a password that's
+ * actually in the CrackLib dictionary.
+ */
+ setup_argv[5] = (char *) "minimum_length";
+ setup_argv[6] = (char *) "12";
+ setup_argv[7] = (char *) "cracklib_maxlen";
+ setup_argv[8] = (char *) "11";
+ setup_argv[9] = NULL;
+ run_setup((const char **) setup_argv);
+
+ /* 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 length tests. */
+ code = vtable->open(ctx, NULL, &data);
+ is_int(0, code, "Plugin initialization (length)");
+ if (code != 0)
+ bail("cannot continue after plugin initialization failure");
+ for (i = 0; i < ARRAY_SIZE(length_tests); i++)
+ is_password_test(ctx, vtable, data, &length_tests[i]);
+ vtable->close(ctx, data);
+
+ /* Add simple character class configuration to krb5.conf. */
+ setup_argv[3] = (char *) "minimum_different";
+ setup_argv[4] = (char *) "8";
setup_argv[5] = (char *) "require_ascii_printable";
setup_argv[6] = (char *) "true";
setup_argv[7] = (char *) "require_non_letter";
if (code != 0)
bail_krb5(ctx, code, "cannot initialize Kerberos context");
- /* Run all the character class tests. */
+ /* Run all the simple character class tests. */
code = vtable->open(ctx, NULL, &data);
- is_int(0, code, "Plugin initialization (character class)");
+ is_int(0, code, "Plugin initialization (simple character class)");
if (code != 0)
bail("cannot continue after plugin initialization failure");
- for (i = 0; i < ARRAY_SIZE(class_tests); i++)
- is_password_test(ctx, vtable, data, &class_tests[i]);
+ for (i = 0; i < ARRAY_SIZE(letter_tests); i++)
+ is_password_test(ctx, vtable, data, &letter_tests[i]);
vtable->close(ctx, data);
- /* Add minimum length configuration to krb5.conf. */
- setup_argv[5] = (char *) "minimum_length";
- setup_argv[6] = (char *) "12";
- setup_argv[7] = NULL;
+ /*
+ * Add complex character class configuration to krb5.conf but drop
+ * the dictionary configuration.
+ */
+ setup_argv[3] = (char *) "require_classes";
+ setup_argv[4] = (char *) "8-19:lower,upper 8-15:digit 8-11:symbol 24-24:3";
+ setup_argv[5] = NULL;
+ run_setup((const char **) setup_argv);
+
+ /* 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 the complex character class tests. */
+ code = vtable->open(ctx, NULL, &data);
+ is_int(0, code, "Plugin initialization (complex character class)");
+ if (code != 0)
+ bail_krb5(ctx, code, "plugin initialization failure");
+ for (i = 0; i < ARRAY_SIZE(classes_tests); i++)
+ is_password_test(ctx, vtable, data, &classes_tests[i]);
+ vtable->close(ctx, data);
+
+ /* Re-run the length restriction checks with no dictionary at all. */
+ setup_argv[3] = (char *) "minimum_length";
+ setup_argv[4] = (char *) "12";
+ setup_argv[5] = NULL;
run_setup((const char **) setup_argv);
/* Obtain a new Kerberos context with that krb5.conf file. */
#ifdef HAVE_CDB
/* If built with CDB, set up krb5.conf to use a CDB dictionary instead. */
- free(dictionary);
+ test_file_path_free(dictionary);
dictionary = test_file_path("data/wordlist.cdb");
if (dictionary == NULL)
bail("cannot find data/wordlist.cdb in the test suite");
setup_argv[4] = dictionary;
setup_argv[5] = NULL;
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);
if (code != 0)
bail_krb5(ctx, code, "cannot initialize Kerberos context");
- /* Run the CDB and generic tests. */
+ /* Run the CDB and principal tests. */
code = vtable->open(ctx, NULL, &data);
is_int(0, code, "Plugin initialization (CDB dictionary)");
if (code != 0)
bail("cannot continue after plugin initialization failure");
for (i = 0; i < ARRAY_SIZE(cdb_tests); i++)
is_password_test(ctx, vtable, data, &cdb_tests[i]);
- for (i = 0; i < ARRAY_SIZE(generic_tests); i++)
- is_password_test(ctx, vtable, data, &generic_tests[i]);
+ for (i = 0; i < ARRAY_SIZE(principal_tests); i++)
+ is_password_test(ctx, vtable, data, &principal_tests[i]);
vtable->close(ctx, data);
#else /* !HAVE_CDB */
/* Otherwise, mark the CDB tests as skipped. */
- count = ARRAY_SIZE(cdb_tests) + ARRAY_SIZE(generic_tests);
+ count = ARRAY_SIZE(cdb_tests) + ARRAY_SIZE(principal_tests);
skip_block(count * 2 + 1, "not built with CDB support");
#endif /* !HAVE_CDB */
+#ifdef HAVE_SQLITE
+
+ /*
+ * If built with SQLite, set up krb5.conf to use a SQLite dictionary
+ * instead.
+ */
+ test_file_path_free(dictionary);
+ dictionary = test_file_path("data/wordlist.sqlite");
+ if (dictionary == NULL)
+ bail("cannot find data/wordlist.sqlite in the test suite");
+ setup_argv[3] = (char *) "password_dictionary_sqlite";
+ setup_argv[4] = dictionary;
+ setup_argv[5] = NULL;
+ 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 SQLite and principal tests. */
+ code = vtable->open(ctx, NULL, &data);
+ is_int(0, code, "Plugin initialization (SQLite dictionary)");
+ if (code != 0)
+ bail("cannot continue after plugin initialization failure");
+ for (i = 0; i < ARRAY_SIZE(sqlite_tests); i++)
+ is_password_test(ctx, vtable, data, &sqlite_tests[i]);
+ for (i = 0; i < ARRAY_SIZE(principal_tests); i++)
+ is_password_test(ctx, vtable, data, &principal_tests[i]);
+ vtable->close(ctx, data);
+
+#else /* !HAVE_SQLITE */
+
+ /* Otherwise, mark the SQLite tests as skipped. */
+ count = ARRAY_SIZE(sqlite_tests) + ARRAY_SIZE(principal_tests);
+ skip_block(count * 2 + 1, "not built with SQLite support");
+
+#endif /* !HAVE_SQLITE */
+
/* Manually clean up after the results of make-krb5-conf. */
basprintf(&path, "%s/krb5.conf", tmpdir);
unlink(path);
free(path);
test_tmpdir_free(tmpdir);
+ /* Close down the module. */
+ if (dlclose(handle) != 0)
+ bail("cannot close plugin: %s", dlerror());
+
/* Keep valgrind clean by freeing all memory. */
test_file_path_free(dictionary);
krb5_free_context(ctx);