2 * Test for the MIT Kerberos shared module API.
4 * Written by Russ Allbery <rra@stanford.edu>
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 generic_tests.
32 #include <tests/data/passwords/cdb.c>
33 #include <tests/data/passwords/class.c>
34 #include <tests/data/passwords/cracklib.c>
35 #include <tests/data/passwords/generic.c>
36 #include <tests/data/passwords/length.c>
39 #ifndef HAVE_KRB5_PWQUAL_PLUGIN_H
41 * If we're not building with MIT Kerberos, we can't run this test and much of
42 * the test won't even compile. Replace this test with a small program that
43 * just calls skip_all.
48 skip_all("not built against MIT libraries");
54 /* The public symbol that we load and call to get the vtable. */
55 typedef krb5_error_code pwqual_strength_initvt(krb5_context, int, int,
60 * Loads the Heimdal password change plugin and tests that its metadata is
61 * correct. Returns a pointer to the kadm5_pw_policy_verifier struct or bails
62 * on failure to load the plugin.
64 static krb5_pwqual_vtable
65 load_plugin(krb5_context ctx)
70 krb5_pwqual_vtable vtable = NULL;
71 krb5_error_code (*init)(krb5_context, int, int, krb5_plugin_vtable);
73 /* Load the module. */
74 path = test_file_path("../plugin/.libs/strength.so");
76 bail("cannot find plugin");
77 handle = dlopen(path, RTLD_NOW);
79 bail("cannot dlopen %s: %s", path, dlerror());
80 test_file_path_free(path);
82 /* Find the entry point function. */
83 init = dlsym(handle, "pwqual_strength_initvt");
85 bail("cannot get pwqual_strength_initvt symbol: %s", dlerror());
87 /* Test for correct results when requesting the wrong API version. */
88 code = init(ctx, 2, 0, (krb5_plugin_vtable) vtable);
89 is_int(code, KRB5_PLUGIN_VER_NOTSUPP,
90 "Correct status for bad major API version");
92 /* Call that function properly to get the vtable. */
93 vtable = bmalloc(sizeof(*vtable));
94 code = init(ctx, 1, 1, (krb5_plugin_vtable) vtable);
96 bail_krb5(ctx, code, "cannot obtain module vtable");
98 /* Check that all of the vtable entries are present. */
99 if (vtable->open == NULL || vtable->check == NULL || vtable->close == NULL)
100 bail("missing function in module vtable");
102 /* Verify the metadata. */
103 is_string("krb5-strength", vtable->name, "Module name");
105 /* Return the vtable. */
111 * Given a Kerberos context, the dispatch table, the module data, and a test
112 * case, call out to the password strength checking module and check the
116 is_password_test(krb5_context ctx, const krb5_pwqual_vtable vtable,
117 krb5_pwqual_moddata data, const struct password_test *test)
119 krb5_principal princ;
120 krb5_error_code code;
123 /* Translate the principal into a krb5_principal. */
124 code = krb5_parse_name(ctx, test->principal, &princ);
126 bail_krb5(ctx, code, "cannot parse principal %s", test->principal);
128 /* Call the verifier. */
129 code = vtable->check(ctx, data, test->password, NULL, princ, NULL);
131 /* Check the results against the test data. */
132 is_int(test->code, code, "%s (status)", test->name);
134 is_string(test->error, NULL, "%s (error)", test->name);
136 error = krb5_get_error_message(ctx, code);
137 is_string(test->error, error, "%s (error)", test->name);
138 krb5_free_error_message(ctx, error);
141 /* Free the parsed principal. */
142 krb5_free_principal(ctx, princ);
149 char *path, *dictionary, *krb5_config, *krb5_config_empty, *tmpdir;
150 char *setup_argv[10];
154 krb5_pwqual_vtable vtable;
155 krb5_pwqual_moddata data;
156 krb5_error_code code;
159 * Calculate how many tests we have. There are two tests for the module
160 * metadata, six more tests for initializing the plugin, one test for
161 * initialization without a valid dictionary, and two tests per password
164 * We run all the CrackLib and generic tests twice, once with an explicit
165 * dictionary path and once from krb5.conf configuration. We run the
166 * generic tests with both CrackLib and CDB configurations.
168 count = 2 * ARRAY_SIZE(cracklib_tests);
169 count += ARRAY_SIZE(cdb_tests);
170 count += ARRAY_SIZE(class_tests);
171 count += ARRAY_SIZE(length_tests);
172 count += 2 * ARRAY_SIZE(generic_tests);
173 plan(2 + 6 + count * 2);
175 /* Start with the krb5.conf that contains no dictionary configuration. */
176 path = test_file_path("data/krb5.conf");
178 bail("cannot find data/krb5.conf in the test suite");
179 basprintf(&krb5_config_empty, "KRB5_CONFIG=%s", path);
180 putenv(krb5_config_empty);
182 /* Obtain a Kerberos context with that krb5.conf file. */
183 code = krb5_init_context(&ctx);
185 bail_krb5(ctx, code, "cannot initialize Kerberos context");
187 /* Load and initialize the plugin without a dictionary. */
188 vtable = load_plugin(ctx);
189 code = vtable->open(ctx, NULL, &data);
190 is_int(KADM5_MISSING_CONF_PARAMS, code,
191 "Plugin initialization (no dictionary)");
193 /* Initialize the plugin again with the correct dictionary. */
194 build = getenv("BUILD");
196 bail("BUILD not set in the environment");
197 basprintf(&dictionary, "%s/data/dictionary", build);
198 code = vtable->open(ctx, dictionary, &data);
199 is_int(0, code, "Plugin initialization (explicit dictionary)");
201 bail("cannot continue after plugin initialization failure");
203 /* Now, run all of the tests, with generic tests. */
204 for (i = 0; i < ARRAY_SIZE(cracklib_tests); i++)
205 is_password_test(ctx, vtable, data, &cracklib_tests[i]);
206 for (i = 0; i < ARRAY_SIZE(generic_tests); i++)
207 is_password_test(ctx, vtable, data, &generic_tests[i]);
209 /* Close that initialization of the plugin and destroy that context. */
210 vtable->close(ctx, data);
211 krb5_free_context(ctx);
214 /* Set up our krb5.conf with the dictionary configuration. */
215 tmpdir = test_tmpdir();
216 setup_argv[0] = test_file_path("data/make-krb5-conf");
217 if (setup_argv[0] == NULL)
218 bail("cannot find data/make-krb5-conf in the test suite");
219 setup_argv[1] = path;
220 setup_argv[2] = tmpdir;
221 setup_argv[3] = (char *) "password_dictionary";
222 setup_argv[4] = dictionary;
223 setup_argv[5] = NULL;
224 run_setup((const char **) setup_argv);
226 /* Point KRB5_CONFIG at the newly-generated krb5.conf file. */
227 basprintf(&krb5_config, "KRB5_CONFIG=%s/krb5.conf", tmpdir);
229 free(krb5_config_empty);
231 /* Obtain a new Kerberos context with that krb5.conf file. */
232 krb5_free_context(ctx);
233 code = krb5_init_context(&ctx);
235 bail_krb5(ctx, code, "cannot initialize Kerberos context");
237 /* Run all of the tests again. No need to re-run generic tests. */
238 code = vtable->open(ctx, NULL, &data);
239 is_int(0, code, "Plugin initialization (krb5.conf dictionary)");
241 bail("cannot continue after plugin initialization failure");
242 for (i = 0; i < ARRAY_SIZE(cracklib_tests); i++)
243 is_password_test(ctx, vtable, data, &cracklib_tests[i]);
244 vtable->close(ctx, data);
246 /* Add character class configuration to krb5.conf. */
247 setup_argv[5] = (char *) "require_ascii_printable";
248 setup_argv[6] = (char *) "true";
249 setup_argv[7] = (char *) "require_non_letter";
250 setup_argv[8] = (char *) "true";
251 setup_argv[9] = NULL;
252 run_setup((const char **) setup_argv);
254 /* Obtain a new Kerberos context with that krb5.conf file. */
255 krb5_free_context(ctx);
256 code = krb5_init_context(&ctx);
258 bail_krb5(ctx, code, "cannot initialize Kerberos context");
260 /* Run all the character class tests. */
261 code = vtable->open(ctx, NULL, &data);
262 is_int(0, code, "Plugin initialization (character class)");
264 bail("cannot continue after plugin initialization failure");
265 for (i = 0; i < ARRAY_SIZE(class_tests); i++)
266 is_password_test(ctx, vtable, data, &class_tests[i]);
267 vtable->close(ctx, data);
269 /* Add minimum length configuration to krb5.conf. */
270 setup_argv[5] = (char *) "minimum_length";
271 setup_argv[6] = (char *) "12";
272 setup_argv[7] = NULL;
273 run_setup((const char **) setup_argv);
275 /* Obtain a new Kerberos context with that krb5.conf file. */
276 krb5_free_context(ctx);
277 code = krb5_init_context(&ctx);
279 bail_krb5(ctx, code, "cannot initialize Kerberos context");
281 /* Run all of the length tests. */
282 code = vtable->open(ctx, NULL, &data);
283 is_int(0, code, "Plugin initialization (length)");
285 bail("cannot continue after plugin initialization failure");
286 for (i = 0; i < ARRAY_SIZE(length_tests); i++)
287 is_password_test(ctx, vtable, data, &length_tests[i]);
288 vtable->close(ctx, data);
292 /* If built with CDB, set up krb5.conf to use a CDB dictionary instead. */
294 dictionary = test_file_path("data/wordlist.cdb");
295 if (dictionary == NULL)
296 bail("cannot find data/wordlist.cdb in the test suite");
297 setup_argv[3] = (char *) "password_dictionary_cdb";
298 setup_argv[4] = dictionary;
299 setup_argv[5] = NULL;
300 run_setup((const char **) setup_argv);
301 test_file_path_free(setup_argv[0]);
302 test_file_path_free(path);
304 /* Obtain a new Kerberos context with that krb5.conf file. */
305 krb5_free_context(ctx);
306 code = krb5_init_context(&ctx);
308 bail_krb5(ctx, code, "cannot initialize Kerberos context");
310 /* Run the CDB and generic tests. */
311 code = vtable->open(ctx, NULL, &data);
312 is_int(0, code, "Plugin initialization (CDB dictionary)");
314 bail("cannot continue after plugin initialization failure");
315 for (i = 0; i < ARRAY_SIZE(cdb_tests); i++)
316 is_password_test(ctx, vtable, data, &cdb_tests[i]);
317 for (i = 0; i < ARRAY_SIZE(generic_tests); i++)
318 is_password_test(ctx, vtable, data, &generic_tests[i]);
319 vtable->close(ctx, data);
321 #else /* !HAVE_CDB */
323 /* Otherwise, mark the CDB tests as skipped. */
324 count = ARRAY_SIZE(cdb_tests) + ARRAY_SIZE(generic_tests);
325 skip_block(count * 2 + 1, "not built with CDB support");
327 #endif /* !HAVE_CDB */
329 /* Manually clean up after the results of make-krb5-conf. */
330 basprintf(&path, "%s/krb5.conf", tmpdir);
333 test_tmpdir_free(tmpdir);
335 /* Keep valgrind clean by freeing all memory. */
336 test_file_path_free(dictionary);
337 krb5_free_context(ctx);
339 putenv((char *) "KRB5_CONFIG=");
344 #endif /* HAVE_KRB5_PWQUAL_PLUGIN_H */