2 * Test for the MIT Kerberos shared module API.
4 * Written by Russ Allbery <eagle@eyrie.org>
5 * Copyright 2010, 2013, 2014
6 * The Board of Trustees of the Leland Stanford Junior University
8 * See LICENSE for licensing terms.
12 #include <portable/kadmin.h>
13 #include <portable/krb5.h>
14 #include <portable/system.h>
18 #ifdef HAVE_KRB5_PWQUAL_PLUGIN_H
19 # include <krb5/pwqual_plugin.h>
22 #include <tests/tap/basic.h>
23 #include <tests/tap/kerberos.h>
24 #include <tests/tap/process.h>
25 #include <tests/tap/string.h>
26 #include <util/macros.h>
29 * The password test data, generated from the JSON source. Defines arrays
30 * named *_tests, where * is the file name without the ".c" suffix.
32 #include <tests/data/passwords/cdb.c>
33 #include <tests/data/passwords/classes.c>
34 #include <tests/data/passwords/cracklib.c>
35 #include <tests/data/passwords/length.c>
36 #include <tests/data/passwords/letter.c>
37 #include <tests/data/passwords/principal.c>
38 #include <tests/data/passwords/sqlite.c>
41 #ifndef HAVE_KRB5_PWQUAL_PLUGIN_H
43 * If we're not building with MIT Kerberos, we can't run this test and much of
44 * the test won't even compile. Replace this test with a small program that
45 * just calls skip_all.
50 skip_all("not built against MIT libraries");
56 /* The public symbol that we load and call to get the vtable. */
57 typedef krb5_error_code pwqual_strength_initvt(krb5_context, int, int,
62 * Loads the Heimdal password change plugin and tests that its metadata is
63 * correct. Returns a pointer to the kadm5_pw_policy_verifier struct or bails
64 * on failure to load the plugin. Stores the handle from dlopen in its second
65 * argument for a later clean shutdown.
67 static krb5_pwqual_vtable
68 load_plugin(krb5_context ctx, void **handle)
72 krb5_pwqual_vtable vtable = NULL;
73 krb5_error_code (*init)(krb5_context, int, int, krb5_plugin_vtable);
75 /* Load the module. */
76 path = test_file_path("../plugin/.libs/strength.so");
78 bail("cannot find plugin");
79 *handle = dlopen(path, RTLD_NOW);
81 bail("cannot dlopen %s: %s", path, dlerror());
82 test_file_path_free(path);
84 /* Find the entry point function. */
85 init = dlsym(*handle, "pwqual_strength_initvt");
87 bail("cannot get pwqual_strength_initvt symbol: %s", dlerror());
89 /* Test for correct results when requesting the wrong API version. */
90 code = init(ctx, 2, 0, (krb5_plugin_vtable) vtable);
91 is_int(code, KRB5_PLUGIN_VER_NOTSUPP,
92 "Correct status for bad major API version");
94 /* Call that function properly to get the vtable. */
95 vtable = bmalloc(sizeof(*vtable));
96 code = init(ctx, 1, 1, (krb5_plugin_vtable) vtable);
98 bail_krb5(ctx, code, "cannot obtain module vtable");
100 /* Check that all of the vtable entries are present. */
101 if (vtable->open == NULL || vtable->check == NULL || vtable->close == NULL)
102 bail("missing function in module vtable");
104 /* Verify the metadata. */
105 is_string("krb5-strength", vtable->name, "Module name");
107 /* Return the vtable. */
113 * Given a Kerberos context, the dispatch table, the module data, and a test
114 * case, call out to the password strength checking module and check the
118 is_password_test(krb5_context ctx, const krb5_pwqual_vtable vtable,
119 krb5_pwqual_moddata data, const struct password_test *test)
121 krb5_principal princ;
122 krb5_error_code code;
125 /* Translate the principal into a krb5_principal. */
126 code = krb5_parse_name(ctx, test->principal, &princ);
128 bail_krb5(ctx, code, "cannot parse principal %s", test->principal);
130 /* Call the verifier. */
131 code = vtable->check(ctx, data, test->password, NULL, princ, NULL);
133 /* Check the results against the test data. */
134 is_int(test->code, code, "%s (status)", test->name);
136 is_string(test->error, NULL, "%s (error)", test->name);
138 error = krb5_get_error_message(ctx, code);
139 is_string(test->error, error, "%s (error)", test->name);
140 krb5_free_error_message(ctx, error);
143 /* Free the parsed principal. */
144 krb5_free_principal(ctx, princ);
151 char *path, *dictionary, *krb5_config, *krb5_config_empty, *tmpdir;
152 char *setup_argv[12];
156 krb5_pwqual_vtable vtable;
157 krb5_pwqual_moddata data;
158 krb5_error_code code;
162 * Calculate how many tests we have. There are two tests for the module
163 * metadata, seven more tests for initializing the plugin, and two tests
166 * We run all the CrackLib tests twice, once with an explicit dictionary
167 * path and once from krb5.conf configuration. We run the principal tests
168 * with CrackLib, CDB, and SQLite configurations.
170 count = 2 * ARRAY_SIZE(cracklib_tests);
171 count += 2 * ARRAY_SIZE(length_tests);
172 count += ARRAY_SIZE(cdb_tests);
173 count += ARRAY_SIZE(sqlite_tests);
174 count += ARRAY_SIZE(classes_tests);
175 count += ARRAY_SIZE(letter_tests);
176 count += 3 * ARRAY_SIZE(principal_tests);
177 plan(2 + 8 + count * 2);
179 /* Start with the krb5.conf that contains no dictionary configuration. */
180 path = test_file_path("data/krb5.conf");
182 bail("cannot find data/krb5.conf in the test suite");
183 basprintf(&krb5_config_empty, "KRB5_CONFIG=%s", path);
184 putenv(krb5_config_empty);
186 /* Obtain a Kerberos context with that krb5.conf file. */
187 code = krb5_init_context(&ctx);
189 bail_krb5(ctx, code, "cannot initialize Kerberos context");
191 /* Load the plugin. */
192 vtable = load_plugin(ctx, &handle);
194 /* Initialize the plugin with a CrackLib dictionary. */
195 build = getenv("BUILD");
197 bail("BUILD not set in the environment");
198 basprintf(&dictionary, "%s/data/dictionary", build);
199 code = vtable->open(ctx, dictionary, &data);
200 is_int(0, code, "Plugin initialization (explicit dictionary)");
202 bail("cannot continue after plugin initialization failure");
204 /* Now, run all of the tests, with principal tests. */
205 for (i = 0; i < ARRAY_SIZE(cracklib_tests); i++)
206 is_password_test(ctx, vtable, data, &cracklib_tests[i]);
207 for (i = 0; i < ARRAY_SIZE(principal_tests); i++)
208 is_password_test(ctx, vtable, data, &principal_tests[i]);
210 /* Close that initialization of the plugin and destroy that context. */
211 vtable->close(ctx, data);
212 krb5_free_context(ctx);
215 /* Set up our krb5.conf with the dictionary configuration. */
216 tmpdir = test_tmpdir();
217 setup_argv[0] = test_file_path("data/make-krb5-conf");
218 if (setup_argv[0] == NULL)
219 bail("cannot find data/make-krb5-conf in the test suite");
220 setup_argv[1] = path;
221 setup_argv[2] = tmpdir;
222 setup_argv[3] = (char *) "password_dictionary";
223 setup_argv[4] = dictionary;
224 setup_argv[5] = NULL;
225 run_setup((const char **) setup_argv);
227 /* Point KRB5_CONFIG at the newly-generated krb5.conf file. */
228 basprintf(&krb5_config, "KRB5_CONFIG=%s/krb5.conf", tmpdir);
230 free(krb5_config_empty);
232 /* Obtain a new Kerberos context with that krb5.conf file. */
233 krb5_free_context(ctx);
234 code = krb5_init_context(&ctx);
236 bail_krb5(ctx, code, "cannot initialize Kerberos context");
238 /* Run all of the tests again. No need to re-run principal tests. */
239 code = vtable->open(ctx, NULL, &data);
240 is_int(0, code, "Plugin initialization (krb5.conf dictionary)");
242 bail("cannot continue after plugin initialization failure");
243 for (i = 0; i < ARRAY_SIZE(cracklib_tests); i++)
244 is_password_test(ctx, vtable, data, &cracklib_tests[i]);
245 vtable->close(ctx, data);
248 * Add length restrictions and a maximum length for CrackLib. This should
249 * reject passwords as too short, but let through a password that's
250 * actually in the CrackLib dictionary.
252 setup_argv[5] = (char *) "minimum_length";
253 setup_argv[6] = (char *) "12";
254 setup_argv[7] = (char *) "cracklib_maxlen";
255 setup_argv[8] = (char *) "11";
256 setup_argv[9] = NULL;
257 run_setup((const char **) setup_argv);
259 /* Obtain a new Kerberos context with that krb5.conf file. */
260 krb5_free_context(ctx);
261 code = krb5_init_context(&ctx);
263 bail_krb5(ctx, code, "cannot initialize Kerberos context");
265 /* Run all of the length tests. */
266 code = vtable->open(ctx, NULL, &data);
267 is_int(0, code, "Plugin initialization (length)");
269 bail("cannot continue after plugin initialization failure");
270 for (i = 0; i < ARRAY_SIZE(length_tests); i++)
271 is_password_test(ctx, vtable, data, &length_tests[i]);
272 vtable->close(ctx, data);
274 /* Add simple character class configuration to krb5.conf. */
275 setup_argv[3] = (char *) "minimum_different";
276 setup_argv[4] = (char *) "8";
277 setup_argv[5] = (char *) "require_ascii_printable";
278 setup_argv[6] = (char *) "true";
279 setup_argv[7] = (char *) "require_non_letter";
280 setup_argv[8] = (char *) "true";
281 setup_argv[9] = NULL;
282 run_setup((const char **) setup_argv);
284 /* Obtain a new Kerberos context with that krb5.conf file. */
285 krb5_free_context(ctx);
286 code = krb5_init_context(&ctx);
288 bail_krb5(ctx, code, "cannot initialize Kerberos context");
290 /* Run all the simple character class tests. */
291 code = vtable->open(ctx, NULL, &data);
292 is_int(0, code, "Plugin initialization (simple character class)");
294 bail("cannot continue after plugin initialization failure");
295 for (i = 0; i < ARRAY_SIZE(letter_tests); i++)
296 is_password_test(ctx, vtable, data, &letter_tests[i]);
297 vtable->close(ctx, data);
300 * Add complex character class configuration to krb5.conf but drop
301 * the dictionary configuration.
303 setup_argv[3] = (char *) "require_classes";
304 setup_argv[4] = (char *) "8-19:lower,upper 8-15:digit 8-11:symbol 24-24:3";
305 setup_argv[5] = NULL;
306 run_setup((const char **) setup_argv);
308 /* Obtain a new Kerberos context with that krb5.conf file. */
309 krb5_free_context(ctx);
310 code = krb5_init_context(&ctx);
312 bail_krb5(ctx, code, "cannot initialize Kerberos context");
314 /* Run all the complex character class tests. */
315 code = vtable->open(ctx, NULL, &data);
316 is_int(0, code, "Plugin initialization (complex character class)");
318 bail_krb5(ctx, code, "plugin initialization failure");
319 for (i = 0; i < ARRAY_SIZE(classes_tests); i++)
320 is_password_test(ctx, vtable, data, &classes_tests[i]);
321 vtable->close(ctx, data);
323 /* Re-run the length restriction checks with no dictionary at all. */
324 setup_argv[3] = (char *) "minimum_length";
325 setup_argv[4] = (char *) "12";
326 setup_argv[5] = NULL;
327 run_setup((const char **) setup_argv);
329 /* Obtain a new Kerberos context with that krb5.conf file. */
330 krb5_free_context(ctx);
331 code = krb5_init_context(&ctx);
333 bail_krb5(ctx, code, "cannot initialize Kerberos context");
335 /* Run all of the length tests. */
336 code = vtable->open(ctx, NULL, &data);
337 is_int(0, code, "Plugin initialization (length)");
339 bail("cannot continue after plugin initialization failure");
340 for (i = 0; i < ARRAY_SIZE(length_tests); i++)
341 is_password_test(ctx, vtable, data, &length_tests[i]);
342 vtable->close(ctx, data);
346 /* If built with CDB, set up krb5.conf to use a CDB dictionary instead. */
347 test_file_path_free(dictionary);
348 dictionary = test_file_path("data/wordlist.cdb");
349 if (dictionary == NULL)
350 bail("cannot find data/wordlist.cdb in the test suite");
351 setup_argv[3] = (char *) "password_dictionary_cdb";
352 setup_argv[4] = dictionary;
353 setup_argv[5] = NULL;
354 run_setup((const char **) setup_argv);
356 /* Obtain a new Kerberos context with that krb5.conf file. */
357 krb5_free_context(ctx);
358 code = krb5_init_context(&ctx);
360 bail_krb5(ctx, code, "cannot initialize Kerberos context");
362 /* Run the CDB and principal tests. */
363 code = vtable->open(ctx, NULL, &data);
364 is_int(0, code, "Plugin initialization (CDB dictionary)");
366 bail("cannot continue after plugin initialization failure");
367 for (i = 0; i < ARRAY_SIZE(cdb_tests); i++)
368 is_password_test(ctx, vtable, data, &cdb_tests[i]);
369 for (i = 0; i < ARRAY_SIZE(principal_tests); i++)
370 is_password_test(ctx, vtable, data, &principal_tests[i]);
371 vtable->close(ctx, data);
373 #else /* !HAVE_CDB */
375 /* Otherwise, mark the CDB tests as skipped. */
376 count = ARRAY_SIZE(cdb_tests) + ARRAY_SIZE(principal_tests);
377 skip_block(count * 2 + 1, "not built with CDB support");
379 #endif /* !HAVE_CDB */
384 * If built with SQLite, set up krb5.conf to use a SQLite dictionary
387 test_file_path_free(dictionary);
388 dictionary = test_file_path("data/wordlist.sqlite");
389 if (dictionary == NULL)
390 bail("cannot find data/wordlist.sqlite in the test suite");
391 setup_argv[3] = (char *) "password_dictionary_sqlite";
392 setup_argv[4] = dictionary;
393 setup_argv[5] = NULL;
394 run_setup((const char **) setup_argv);
395 test_file_path_free(setup_argv[0]);
396 test_file_path_free(path);
398 /* Obtain a new Kerberos context with that krb5.conf file. */
399 krb5_free_context(ctx);
400 code = krb5_init_context(&ctx);
402 bail_krb5(ctx, code, "cannot initialize Kerberos context");
404 /* Run the SQLite and principal tests. */
405 code = vtable->open(ctx, NULL, &data);
406 is_int(0, code, "Plugin initialization (SQLite dictionary)");
408 bail("cannot continue after plugin initialization failure");
409 for (i = 0; i < ARRAY_SIZE(sqlite_tests); i++)
410 is_password_test(ctx, vtable, data, &sqlite_tests[i]);
411 for (i = 0; i < ARRAY_SIZE(principal_tests); i++)
412 is_password_test(ctx, vtable, data, &principal_tests[i]);
413 vtable->close(ctx, data);
415 #else /* !HAVE_SQLITE */
417 /* Otherwise, mark the SQLite tests as skipped. */
418 count = ARRAY_SIZE(sqlite_tests) + ARRAY_SIZE(principal_tests);
419 skip_block(count * 2 + 1, "not built with SQLite support");
421 #endif /* !HAVE_SQLITE */
423 /* Manually clean up after the results of make-krb5-conf. */
424 basprintf(&path, "%s/krb5.conf", tmpdir);
427 test_tmpdir_free(tmpdir);
429 /* Close down the module. */
430 if (dlclose(handle) != 0)
431 bail("cannot close plugin: %s", dlerror());
433 /* Keep valgrind clean by freeing all memory. */
434 test_file_path_free(dictionary);
435 krb5_free_context(ctx);
437 putenv((char *) "KRB5_CONFIG=");
442 #endif /* HAVE_KRB5_PWQUAL_PLUGIN_H */