2 * Test for the Heimdal shared module API.
4 * Written by Russ Allbery <eagle@eyrie.org>
5 * Copyright 2023 Russ Allbery <eagle@eyrie.org>
6 * Copyright 2009, 2013-2014
7 * The Board of Trustees of the Leland Stanford Junior University
9 * SPDX-License-Identifier: MIT
13 #include <portable/krb5.h>
14 #include <portable/system.h>
18 #ifdef HAVE_KADM5_KADM5_PWCHECK_H
19 # include <kadm5/kadm5-pwcheck.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_KADM5_KADM5_PWCHECK_H
43 * If we're not building with Heimdal, we can't run this test and much of the
44 * test won't even compile. Replace this test with a small program that just
50 skip_all("not built against Heimdal libraries");
57 * Loads the Heimdal password change plugin and tests that its metadata is
58 * correct. Returns a pointer to the kadm5_pw_policy_verifier struct or bails
59 * on failure to load the plugin. Stores the handle in the last argument so
60 * that the caller can free the handle at the end of the test suite.
62 static struct kadm5_pw_policy_verifier *
63 load_plugin(void **handle)
66 struct kadm5_pw_policy_verifier *verifier;
68 /* Load the module. */
69 path = test_file_path("../plugin/.libs/strength.so");
71 bail("cannot find plugin");
72 *handle = dlopen(path, RTLD_NOW);
74 bail("cannot dlopen %s: %s", path, dlerror());
75 test_file_path_free(path);
77 /* Find the dispatch table and do a basic sanity check. */
78 verifier = dlsym(*handle, "kadm5_password_verifier");
80 bail("cannot get kadm5_password_verifier symbol: %s", dlerror());
81 if (verifier->funcs == NULL || verifier->funcs[0].func == NULL)
82 bail("no verifier functions in module");
84 /* Verify the metadata. */
85 is_string("krb5-strength", verifier->name, "Module name");
86 is_string("Russ Allbery", verifier->vendor, "Module vendor");
87 is_int(KADM5_PASSWD_VERSION_V1, verifier->version, "Module version");
88 is_string("krb5-strength", verifier->funcs[0].name,
89 "Module function name");
90 ok(verifier->funcs[1].name == NULL, "Only one function in module");
92 /* Return the dispatch table. */
98 * Given the dispatch table and a test case, call out to the password strength
99 * checking module and check the results.
102 is_password_test(const struct kadm5_pw_policy_verifier *verifier,
103 const struct password_test *test)
106 krb5_principal princ;
107 krb5_error_code code;
110 char error[BUFSIZ] = "";
112 /* Obtain a Kerberos context to use for parsing principal names. */
113 code = krb5_init_context(&ctx);
115 bail_krb5(ctx, code, "cannot initialize Kerberos context");
117 /* Translate the test data into the form that the verifier expects. */
118 code = krb5_parse_name(ctx, test->principal, &princ);
120 bail_krb5(ctx, code, "cannot parse principal %s", test->principal);
121 password.data = (char *) test->password;
122 password.length = strlen(test->password);
124 /* Call the verifier. */
125 result = (verifier->funcs[0].func)(ctx, princ, &password, NULL, error,
128 /* Heimdal only returns 0 or 1, so translate the expected code. */
129 is_int(test->code == 0 ? 0 : 1, result, "%s (status)", test->name);
130 is_string(test->error == NULL ? "" : test->error, error, "%s (error)",
133 /* Free data structures. */
134 krb5_free_principal(ctx, princ);
135 krb5_free_context(ctx);
142 char *path, *krb5_config, *krb5_config_empty, *tmpdir;
143 char *setup_argv[12];
145 struct kadm5_pw_policy_verifier *verifier;
149 * Calculate how many tests we have. There are five tests for the module
150 * metadata and two tests per password test. We run the principal tests
151 * three times, once each with CrackLib, CDB, and SQLite.
153 count = ARRAY_SIZE(cracklib_tests);
154 count += 2 * ARRAY_SIZE(length_tests);
155 count += ARRAY_SIZE(cdb_tests);
156 count += ARRAY_SIZE(sqlite_tests);
157 count += ARRAY_SIZE(classes_tests);
158 count += ARRAY_SIZE(letter_tests);
159 count += ARRAY_SIZE(principal_tests) * 3;
162 /* Start with the krb5.conf that contains no dictionary configuration. */
163 path = test_file_path("data/krb5.conf");
165 bail("cannot find data/krb5.conf in the test suite");
166 basprintf(&krb5_config_empty, "KRB5_CONFIG=%s", path);
167 putenv(krb5_config_empty);
169 /* Load the plugin. */
170 verifier = load_plugin(&handle);
172 /* Set up our krb5.conf with a basic configuration. */
173 setup_argv[0] = test_file_path("data/make-krb5-conf");
174 if (setup_argv[0] == NULL)
175 bail("cannot find data/make-krb5-conf in the test suite");
176 tmpdir = test_tmpdir();
177 setup_argv[1] = path;
178 setup_argv[2] = tmpdir;
179 setup_argv[3] = NULL;
180 run_setup((const char **) setup_argv);
182 /* Point KRB5_CONFIG at the newly-generated krb5.conf file. */
183 basprintf(&krb5_config, "KRB5_CONFIG=%s/krb5.conf", tmpdir);
185 free(krb5_config_empty);
187 /* Run principal tests. */
188 for (i = 0; i < ARRAY_SIZE(principal_tests); i++)
189 is_password_test(verifier, &principal_tests[i]);
191 # ifdef HAVE_CRACKLIB
193 /* Add CrackLib tests. */
194 setup_argv[3] = (char *) "password_dictionary";
195 basprintf(&setup_argv[4], "%s/data/dictionary", getenv("BUILD"));
196 setup_argv[5] = NULL;
197 run_setup((const char **) setup_argv);
199 /* Now, run all of the tests. */
200 for (i = 0; i < ARRAY_SIZE(cracklib_tests); i++) {
201 # ifdef HAVE_SYSTEM_CRACKLIB
202 if (cracklib_tests[i].skip_for_system_cracklib) {
203 skip_block(2, "not built with embedded CrackLib");
207 is_password_test(verifier, &cracklib_tests[i]);
211 * Add length restrictions and a maximum length for CrackLib. This should
212 * reject passwords as too short, but let through a password that's
213 * actually in the CrackLib dictionary.
215 setup_argv[5] = (char *) "minimum_length";
216 setup_argv[6] = (char *) "12";
217 setup_argv[7] = (char *) "cracklib_maxlen";
218 setup_argv[8] = (char *) "11";
219 setup_argv[9] = NULL;
220 run_setup((const char **) setup_argv);
222 /* Run the length tests. */
223 for (i = 0; i < ARRAY_SIZE(length_tests); i++)
224 is_password_test(verifier, &length_tests[i]);
226 /* Free the memory allocated for the CrackLib test. */
231 /* Otherwise, mark the CrackLib tests as skipped. */
232 count = ARRAY_SIZE(cracklib_tests) + ARRAY_SIZE(length_tests);
233 skip_block(count * 2, "not built with CDB support");
235 # endif /* !HAVE_CRACKLIB */
237 /* Add simple character class restrictions. */
238 setup_argv[3] = (char *) "minimum_different";
239 setup_argv[4] = (char *) "8";
240 setup_argv[5] = (char *) "require_ascii_printable";
241 setup_argv[6] = (char *) "true";
242 setup_argv[7] = (char *) "require_non_letter";
243 setup_argv[8] = (char *) "true";
244 setup_argv[9] = NULL;
245 run_setup((const char **) setup_argv);
247 /* Run the simple character class tests. */
248 for (i = 0; i < ARRAY_SIZE(letter_tests); i++)
249 is_password_test(verifier, &letter_tests[i]);
251 /* Add complex character class restrictions and remove the dictionary. */
252 setup_argv[3] = (char *) "require_classes";
253 setup_argv[4] = (char *) "8-19:lower,upper 8-15:digit 8-11:symbol 24-24:3";
254 setup_argv[5] = NULL;
255 run_setup((const char **) setup_argv);
257 /* Run the simple character class tests. */
258 for (i = 0; i < ARRAY_SIZE(classes_tests); i++)
259 is_password_test(verifier, &classes_tests[i]);
261 /* Try the length checks again with no dictionary at all. */
262 setup_argv[3] = (char *) "minimum_length";
263 setup_argv[4] = (char *) "12";
264 setup_argv[5] = NULL;
265 run_setup((const char **) setup_argv);
267 /* Run the length tests. */
268 for (i = 0; i < ARRAY_SIZE(length_tests); i++)
269 is_password_test(verifier, &length_tests[i]);
273 /* If built with CDB, set up krb5.conf to use a CDB dictionary instead. */
274 setup_argv[3] = (char *) "password_dictionary_cdb";
275 setup_argv[4] = test_file_path("data/wordlist.cdb");
276 if (setup_argv[4] == NULL)
277 bail("cannot find data/wordlist.cdb in the test suite");
278 setup_argv[5] = NULL;
279 run_setup((const char **) setup_argv);
280 test_file_path_free(setup_argv[4]);
282 /* Run the CDB tests. */
283 for (i = 0; i < ARRAY_SIZE(cdb_tests); i++)
284 is_password_test(verifier, &cdb_tests[i]);
285 for (i = 0; i < ARRAY_SIZE(principal_tests); i++)
286 is_password_test(verifier, &principal_tests[i]);
288 # else /* !HAVE_CDB */
290 /* Otherwise, mark the CDB tests as skipped. */
291 count = ARRAY_SIZE(cdb_tests) + ARRAY_SIZE(principal_tests);
292 skip_block(count * 2, "not built with CDB support");
294 # endif /* !HAVE_CDB */
299 * If built with SQLite, set up krb5.conf to use a SQLite dictionary
302 setup_argv[3] = (char *) "password_dictionary_sqlite";
303 setup_argv[4] = test_file_path("data/wordlist.sqlite");
304 if (setup_argv[4] == NULL)
305 bail("cannot find data/wordlist.sqlite in the test suite");
306 setup_argv[5] = NULL;
307 run_setup((const char **) setup_argv);
308 test_file_path_free(setup_argv[0]);
309 test_file_path_free(setup_argv[4]);
310 test_file_path_free(path);
312 /* Run the SQLite tests. */
313 for (i = 0; i < ARRAY_SIZE(sqlite_tests); i++)
314 is_password_test(verifier, &sqlite_tests[i]);
315 for (i = 0; i < ARRAY_SIZE(principal_tests); i++)
316 is_password_test(verifier, &principal_tests[i]);
318 # else /* !HAVE_SQLITE3 */
320 /* Otherwise, mark the SQLite tests as skipped. */
321 count = ARRAY_SIZE(sqlite_tests) + ARRAY_SIZE(principal_tests);
322 skip_block(count * 2, "not built with SQLite support");
324 # endif /* !HAVE_SQLITE3 */
326 /* Manually clean up after the results of make-krb5-conf. */
327 basprintf(&path, "%s/krb5.conf", tmpdir);
330 test_tmpdir_free(tmpdir);
332 /* Close down the module. */
333 if (dlclose(handle) != 0)
334 bail("cannot close plugin: %s", dlerror());
336 /* Keep valgrind clean by freeing environmental memory. */
337 putenv((char *) "KRB5_CONFIG=");
342 #endif /* HAVE_KADM5_KADM5_PWCHECK_H */