2 * Test for the MIT Kerberos shared module API.
4 * Written by Russ Allbery <eagle@eyrie.org>
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 an arrays
30 * named cdb_tests, cracklib_tests, and principal_tests.
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>
40 #ifndef HAVE_KRB5_PWQUAL_PLUGIN_H
42 * If we're not building with MIT Kerberos, we can't run this test and much of
43 * the test won't even compile. Replace this test with a small program that
44 * just calls skip_all.
49 skip_all("not built against MIT libraries");
55 /* The public symbol that we load and call to get the vtable. */
56 typedef krb5_error_code pwqual_strength_initvt(krb5_context, int, int,
61 * Loads the Heimdal password change plugin and tests that its metadata is
62 * correct. Returns a pointer to the kadm5_pw_policy_verifier struct or bails
63 * on failure to load the plugin. Stores the handle from dlopen in its second
64 * argument for a later clean shutdown.
66 static krb5_pwqual_vtable
67 load_plugin(krb5_context ctx, void **handle)
71 krb5_pwqual_vtable vtable = NULL;
72 krb5_error_code (*init)(krb5_context, int, int, krb5_plugin_vtable);
74 /* Load the module. */
75 path = test_file_path("../plugin/.libs/strength.so");
77 bail("cannot find plugin");
78 *handle = dlopen(path, RTLD_NOW);
80 bail("cannot dlopen %s: %s", path, dlerror());
81 test_file_path_free(path);
83 /* Find the entry point function. */
84 init = dlsym(*handle, "pwqual_strength_initvt");
86 bail("cannot get pwqual_strength_initvt symbol: %s", dlerror());
88 /* Test for correct results when requesting the wrong API version. */
89 code = init(ctx, 2, 0, (krb5_plugin_vtable) vtable);
90 is_int(code, KRB5_PLUGIN_VER_NOTSUPP,
91 "Correct status for bad major API version");
93 /* Call that function properly to get the vtable. */
94 vtable = bmalloc(sizeof(*vtable));
95 code = init(ctx, 1, 1, (krb5_plugin_vtable) vtable);
97 bail_krb5(ctx, code, "cannot obtain module vtable");
99 /* Check that all of the vtable entries are present. */
100 if (vtable->open == NULL || vtable->check == NULL || vtable->close == NULL)
101 bail("missing function in module vtable");
103 /* Verify the metadata. */
104 is_string("krb5-strength", vtable->name, "Module name");
106 /* Return the vtable. */
112 * Given a Kerberos context, the dispatch table, the module data, and a test
113 * case, call out to the password strength checking module and check the
117 is_password_test(krb5_context ctx, const krb5_pwqual_vtable vtable,
118 krb5_pwqual_moddata data, const struct password_test *test)
120 krb5_principal princ;
121 krb5_error_code code;
124 /* Translate the principal into a krb5_principal. */
125 code = krb5_parse_name(ctx, test->principal, &princ);
127 bail_krb5(ctx, code, "cannot parse principal %s", test->principal);
129 /* Call the verifier. */
130 code = vtable->check(ctx, data, test->password, NULL, princ, NULL);
132 /* Check the results against the test data. */
133 is_int(test->code, code, "%s (status)", test->name);
135 is_string(test->error, NULL, "%s (error)", test->name);
137 error = krb5_get_error_message(ctx, code);
138 is_string(test->error, error, "%s (error)", test->name);
139 krb5_free_error_message(ctx, error);
142 /* Free the parsed principal. */
143 krb5_free_principal(ctx, princ);
150 char *path, *dictionary, *krb5_config, *krb5_config_empty, *tmpdir;
151 char *setup_argv[10];
155 krb5_pwqual_vtable vtable;
156 krb5_pwqual_moddata data;
157 krb5_error_code code;
161 * Calculate how many tests we have. There are two tests for the module
162 * metadata, six more tests for initializing the plugin, and two tests per
165 * We run all the CrackLib tests twice, once with an explicit dictionary
166 * path and once from krb5.conf configuration. We run the principal tests
167 * with both CrackLib and CDB configurations.
169 count = 2 * ARRAY_SIZE(cracklib_tests);
170 count += ARRAY_SIZE(cdb_tests);
171 count += ARRAY_SIZE(classes_tests);
172 count += ARRAY_SIZE(length_tests);
173 count += ARRAY_SIZE(letter_tests);
174 count += 2 * ARRAY_SIZE(principal_tests);
175 plan(2 + 6 + count * 2);
177 /* Start with the krb5.conf that contains no dictionary configuration. */
178 path = test_file_path("data/krb5.conf");
180 bail("cannot find data/krb5.conf in the test suite");
181 basprintf(&krb5_config_empty, "KRB5_CONFIG=%s", path);
182 putenv(krb5_config_empty);
184 /* Obtain a Kerberos context with that krb5.conf file. */
185 code = krb5_init_context(&ctx);
187 bail_krb5(ctx, code, "cannot initialize Kerberos context");
189 /* Load the plugin. */
190 vtable = load_plugin(ctx, &handle);
192 /* Initialize the plugin with a CrackLib dictionary. */
193 build = getenv("BUILD");
195 bail("BUILD not set in the environment");
196 basprintf(&dictionary, "%s/data/dictionary", build);
197 code = vtable->open(ctx, dictionary, &data);
198 is_int(0, code, "Plugin initialization (explicit dictionary)");
200 bail("cannot continue after plugin initialization failure");
202 /* Now, run all of the tests, with principal tests. */
203 for (i = 0; i < ARRAY_SIZE(cracklib_tests); i++)
204 is_password_test(ctx, vtable, data, &cracklib_tests[i]);
205 for (i = 0; i < ARRAY_SIZE(principal_tests); i++)
206 is_password_test(ctx, vtable, data, &principal_tests[i]);
208 /* Close that initialization of the plugin and destroy that context. */
209 vtable->close(ctx, data);
210 krb5_free_context(ctx);
213 /* Set up our krb5.conf with the dictionary configuration. */
214 tmpdir = test_tmpdir();
215 setup_argv[0] = test_file_path("data/make-krb5-conf");
216 if (setup_argv[0] == NULL)
217 bail("cannot find data/make-krb5-conf in the test suite");
218 setup_argv[1] = path;
219 setup_argv[2] = tmpdir;
220 setup_argv[3] = (char *) "password_dictionary";
221 setup_argv[4] = dictionary;
222 setup_argv[5] = NULL;
223 run_setup((const char **) setup_argv);
225 /* Point KRB5_CONFIG at the newly-generated krb5.conf file. */
226 basprintf(&krb5_config, "KRB5_CONFIG=%s/krb5.conf", tmpdir);
228 free(krb5_config_empty);
230 /* Obtain a new Kerberos context with that krb5.conf file. */
231 krb5_free_context(ctx);
232 code = krb5_init_context(&ctx);
234 bail_krb5(ctx, code, "cannot initialize Kerberos context");
236 /* Run all of the tests again. No need to re-run principal tests. */
237 code = vtable->open(ctx, NULL, &data);
238 is_int(0, code, "Plugin initialization (krb5.conf dictionary)");
240 bail("cannot continue after plugin initialization failure");
241 for (i = 0; i < ARRAY_SIZE(cracklib_tests); i++)
242 is_password_test(ctx, vtable, data, &cracklib_tests[i]);
243 vtable->close(ctx, data);
245 /* Add simple character class configuration to krb5.conf. */
246 setup_argv[5] = (char *) "require_ascii_printable";
247 setup_argv[6] = (char *) "true";
248 setup_argv[7] = (char *) "require_non_letter";
249 setup_argv[8] = (char *) "true";
250 setup_argv[9] = NULL;
251 run_setup((const char **) setup_argv);
253 /* Obtain a new Kerberos context with that krb5.conf file. */
254 krb5_free_context(ctx);
255 code = krb5_init_context(&ctx);
257 bail_krb5(ctx, code, "cannot initialize Kerberos context");
259 /* Run all the simple character class tests. */
260 code = vtable->open(ctx, NULL, &data);
261 is_int(0, code, "Plugin initialization (simple character class)");
263 bail("cannot continue after plugin initialization failure");
264 for (i = 0; i < ARRAY_SIZE(letter_tests); i++)
265 is_password_test(ctx, vtable, data, &letter_tests[i]);
266 vtable->close(ctx, data);
269 * Add complex character class configuration to krb5.conf but drop
270 * the dictionary configuration.
272 setup_argv[3] = (char *) "require_classes";
273 setup_argv[4] = (char *) "8-19:lower,upper 8-15:digit 8-11:symbol";
274 setup_argv[5] = NULL;
275 run_setup((const char **) setup_argv);
277 /* Obtain a new Kerberos context with that krb5.conf file. */
278 krb5_free_context(ctx);
279 code = krb5_init_context(&ctx);
281 bail_krb5(ctx, code, "cannot initialize Kerberos context");
283 /* Run all the complex character class tests. */
284 code = vtable->open(ctx, NULL, &data);
285 is_int(0, code, "Plugin initialization (complex character class)");
287 bail("cannot continue after plugin initialization failure");
288 for (i = 0; i < ARRAY_SIZE(classes_tests); i++)
289 is_password_test(ctx, vtable, data, &classes_tests[i]);
290 vtable->close(ctx, data);
293 * Add length restrictions. This should only do length checks without any
296 setup_argv[3] = (char *) "minimum_length";
297 setup_argv[4] = (char *) "12";
298 setup_argv[5] = NULL;
299 run_setup((const char **) setup_argv);
301 /* Obtain a new Kerberos context with that krb5.conf file. */
302 krb5_free_context(ctx);
303 code = krb5_init_context(&ctx);
305 bail_krb5(ctx, code, "cannot initialize Kerberos context");
307 /* Run all of the length tests. */
308 code = vtable->open(ctx, NULL, &data);
309 is_int(0, code, "Plugin initialization (length)");
311 bail("cannot continue after plugin initialization failure");
312 for (i = 0; i < ARRAY_SIZE(length_tests); i++)
313 is_password_test(ctx, vtable, data, &length_tests[i]);
314 vtable->close(ctx, data);
318 /* If built with CDB, set up krb5.conf to use a CDB dictionary instead. */
319 test_file_path_free(dictionary);
320 dictionary = test_file_path("data/wordlist.cdb");
321 if (dictionary == NULL)
322 bail("cannot find data/wordlist.cdb in the test suite");
323 setup_argv[3] = (char *) "password_dictionary_cdb";
324 setup_argv[4] = dictionary;
325 setup_argv[5] = NULL;
326 run_setup((const char **) setup_argv);
327 test_file_path_free(setup_argv[0]);
328 test_file_path_free(path);
330 /* Obtain a new Kerberos context with that krb5.conf file. */
331 krb5_free_context(ctx);
332 code = krb5_init_context(&ctx);
334 bail_krb5(ctx, code, "cannot initialize Kerberos context");
336 /* Run the CDB and principal tests. */
337 code = vtable->open(ctx, NULL, &data);
338 is_int(0, code, "Plugin initialization (CDB dictionary)");
340 bail("cannot continue after plugin initialization failure");
341 for (i = 0; i < ARRAY_SIZE(cdb_tests); i++)
342 is_password_test(ctx, vtable, data, &cdb_tests[i]);
343 for (i = 0; i < ARRAY_SIZE(principal_tests); i++)
344 is_password_test(ctx, vtable, data, &principal_tests[i]);
345 vtable->close(ctx, data);
347 #else /* !HAVE_CDB */
349 /* Otherwise, mark the CDB tests as skipped. */
350 count = ARRAY_SIZE(cdb_tests) + ARRAY_SIZE(principal_tests);
351 skip_block(count * 2 + 1, "not built with CDB support");
353 #endif /* !HAVE_CDB */
355 /* Manually clean up after the results of make-krb5-conf. */
356 basprintf(&path, "%s/krb5.conf", tmpdir);
359 test_tmpdir_free(tmpdir);
361 /* Close down the module. */
362 if (dlclose(handle) != 0)
363 bail("cannot close plugin: %s", dlerror());
365 /* Keep valgrind clean by freeing all memory. */
366 test_file_path_free(dictionary);
367 krb5_free_context(ctx);
369 putenv((char *) "KRB5_CONFIG=");
374 #endif /* HAVE_KRB5_PWQUAL_PLUGIN_H */