]> eyrie.org Git - kerberos/krb5-strength.git/blob - tests/plugin/mit-t.c
New upstream version 3.2
[kerberos/krb5-strength.git] / tests / plugin / mit-t.c
1 /*
2  * Test for the MIT Kerberos shared module API.
3  *
4  * Written by Russ Allbery <eagle@eyrie.org>
5  * Copyright 2017, 2020 Russ Allbery <eagle@eyrie.org>
6  * Copyright 2010, 2013-2014
7  *     The Board of Trustees of the Leland Stanford Junior University
8  *
9  * SPDX-License-Identifier: MIT
10  */
11
12 #include <config.h>
13 #include <portable/kadmin.h>
14 #include <portable/krb5.h>
15 #include <portable/system.h>
16
17 #include <dlfcn.h>
18 #include <errno.h>
19 #ifdef HAVE_KRB5_PWQUAL_PLUGIN_H
20 #    include <krb5/pwqual_plugin.h>
21 #endif
22
23 #include <tests/tap/basic.h>
24 #include <tests/tap/kerberos.h>
25 #include <tests/tap/process.h>
26 #include <tests/tap/string.h>
27 #include <util/macros.h>
28
29 /*
30  * The password test data, generated from the JSON source.  Defines arrays
31  * named *_tests, where * is the file name without the ".c" suffix.
32  */
33 #include <tests/data/passwords/cdb.c>
34 #include <tests/data/passwords/classes.c>
35 #include <tests/data/passwords/cracklib.c>
36 #include <tests/data/passwords/length.c>
37 #include <tests/data/passwords/letter.c>
38 #include <tests/data/passwords/principal.c>
39 #include <tests/data/passwords/sqlite.c>
40
41
42 #ifndef HAVE_KRB5_PWQUAL_PLUGIN_H
43 /*
44  * If we're not building with MIT Kerberos, we can't run this test and much of
45  * the test won't even compile.  Replace this test with a small program that
46  * just calls skip_all.
47  */
48 int
49 main(void)
50 {
51     skip_all("not built against MIT libraries");
52     return 0;
53 }
54
55 #else
56
57 /* The public symbol that we load and call to get the vtable. */
58 typedef krb5_error_code pwqual_strength_initvt(krb5_context, int, int,
59                                                krb5_plugin_vtable);
60
61
62 /*
63  * Loads the Heimdal password change plugin and tests that its metadata is
64  * correct.  Returns a pointer to the kadm5_pw_policy_verifier struct or bails
65  * on failure to load the plugin.  Stores the handle from dlopen in its second
66  * argument for a later clean shutdown.
67  */
68 static krb5_pwqual_vtable
69 load_plugin(krb5_context ctx, void **handle)
70 {
71     char *path;
72     krb5_error_code code;
73     krb5_pwqual_vtable vtable = NULL;
74     pwqual_strength_initvt *init;
75
76     /* Load the module. */
77     path = test_file_path("../plugin/.libs/strength.so");
78     if (path == NULL)
79         bail("cannot find plugin");
80     *handle = dlopen(path, RTLD_NOW);
81     if (*handle == NULL)
82         bail("cannot dlopen %s: %s", path, dlerror());
83     test_file_path_free(path);
84
85     /* Find the entry point function. */
86     init = (pwqual_strength_initvt *) dlsym(*handle, "pwqual_strength_initvt");
87     if (init == NULL)
88         bail("cannot get pwqual_strength_initvt symbol: %s", dlerror());
89
90     /* Test for correct results when requesting the wrong API version. */
91     code = init(ctx, 2, 0, (krb5_plugin_vtable) vtable);
92     is_int(code, KRB5_PLUGIN_VER_NOTSUPP,
93            "Correct status for bad major API version");
94
95     /* Call that function properly to get the vtable. */
96     vtable = bmalloc(sizeof(*vtable));
97     code = init(ctx, 1, 1, (krb5_plugin_vtable) vtable);
98     if (code != 0)
99         bail_krb5(ctx, code, "cannot obtain module vtable");
100
101     /* Check that all of the vtable entries are present. */
102     if (vtable->open == NULL || vtable->check == NULL || vtable->close == NULL)
103         bail("missing function in module vtable");
104
105     /* Verify the metadata. */
106     is_string("krb5-strength", vtable->name, "Module name");
107
108     /* Return the vtable. */
109     return vtable;
110 }
111
112
113 /*
114  * Given a Kerberos context, the dispatch table, the module data, and a test
115  * case, call out to the password strength checking module and check the
116  * results.
117  */
118 static void
119 is_password_test(krb5_context ctx, const krb5_pwqual_vtable vtable,
120                  krb5_pwqual_moddata data, const struct password_test *test)
121 {
122     krb5_principal princ;
123     krb5_error_code code;
124     const char *error;
125
126     /* Translate the principal into a krb5_principal. */
127     code = krb5_parse_name(ctx, test->principal, &princ);
128     if (code != 0)
129         bail_krb5(ctx, code, "cannot parse principal %s", test->principal);
130
131     /* Call the verifier. */
132     code = vtable->check(ctx, data, test->password, NULL, princ, NULL);
133
134     /* Check the results against the test data. */
135     is_int(test->code, code, "%s (status)", test->name);
136     if (code == 0)
137         is_string(test->error, NULL, "%s (error)", test->name);
138     else {
139         error = krb5_get_error_message(ctx, code);
140         is_string(test->error, error, "%s (error)", test->name);
141         krb5_free_error_message(ctx, error);
142     }
143
144     /* Free the parsed principal. */
145     krb5_free_principal(ctx, princ);
146 }
147
148
149 int
150 main(void)
151 {
152     char *path, *dictionary, *krb5_config, *krb5_config_empty, *tmpdir;
153     char *setup_argv[12];
154     const char *build;
155     size_t i, count;
156     krb5_context ctx;
157     krb5_pwqual_vtable vtable;
158     krb5_pwqual_moddata data;
159     krb5_error_code code;
160     void *handle;
161
162     /*
163      * Calculate how many tests we have.  There are two tests for the module
164      * metadata, seven more tests for initializing the plugin, and two tests
165      * per password test.
166      *
167      * We run all the CrackLib tests twice, once with an explicit dictionary
168      * path and once from krb5.conf configuration.  We run the principal tests
169      * with CrackLib, CDB, and SQLite configurations.
170      */
171     count = 2 * ARRAY_SIZE(cracklib_tests);
172     count += 2 * ARRAY_SIZE(length_tests);
173     count += ARRAY_SIZE(cdb_tests);
174     count += ARRAY_SIZE(sqlite_tests);
175     count += ARRAY_SIZE(classes_tests);
176     count += ARRAY_SIZE(letter_tests);
177     count += 3 * ARRAY_SIZE(principal_tests);
178     plan(2 + 8 + count * 2);
179
180     /* Start with the krb5.conf that contains no dictionary configuration. */
181     path = test_file_path("data/krb5.conf");
182     if (path == NULL)
183         bail("cannot find data/krb5.conf in the test suite");
184     basprintf(&krb5_config_empty, "KRB5_CONFIG=%s", path);
185     putenv(krb5_config_empty);
186
187     /* Obtain a Kerberos context with that krb5.conf file. */
188     code = krb5_init_context(&ctx);
189     if (code != 0)
190         bail_krb5(ctx, code, "cannot initialize Kerberos context");
191
192     /* Load the plugin. */
193     vtable = load_plugin(ctx, &handle);
194
195     /* Initialize the plugin with a CrackLib dictionary. */
196     build = getenv("BUILD");
197     if (build == NULL)
198         bail("BUILD not set in the environment");
199     basprintf(&dictionary, "%s/data/dictionary", build);
200     code = vtable->open(ctx, dictionary, &data);
201     is_int(0, code, "Plugin initialization (explicit dictionary)");
202     if (code != 0)
203         bail("cannot continue after plugin initialization failure");
204
205     /* Run the principal tests. */
206     for (i = 0; i < ARRAY_SIZE(principal_tests); i++)
207         is_password_test(ctx, vtable, data, &principal_tests[i]);
208
209 #    ifdef HAVE_CRACKLIB
210     /*
211      * Run the CrackLib tests if CrackLib is available, otherwise skip them.
212      * If built with the system CrackLib, skip tests that are marked as only
213      * working with the tougher rules of our embedded CrackLib.
214      */
215     for (i = 0; i < ARRAY_SIZE(cracklib_tests); i++) {
216 #        ifdef HAVE_SYSTEM_CRACKLIB
217         if (cracklib_tests[i].skip_for_system_cracklib) {
218             skip_block(2, "not built with embedded CrackLib");
219             continue;
220         }
221 #        endif
222         is_password_test(ctx, vtable, data, &cracklib_tests[i]);
223     }
224 #    else
225     count = ARRAY_SIZE(cracklib_tests);
226     skip_block(count * 2, "not built with CrackLib support");
227 #    endif
228
229     /* Close that initialization of the plugin and destroy that context. */
230     vtable->close(ctx, data);
231     krb5_free_context(ctx);
232     ctx = NULL;
233
234     /* Set up our krb5.conf with a base configuration. */
235     tmpdir = test_tmpdir();
236     setup_argv[0] = test_file_path("data/make-krb5-conf");
237     if (setup_argv[0] == NULL)
238         bail("cannot find data/make-krb5-conf in the test suite");
239     setup_argv[1] = path;
240     setup_argv[2] = tmpdir;
241
242     /* Point KRB5_CONFIG at the newly-generated krb5.conf file. */
243     basprintf(&krb5_config, "KRB5_CONFIG=%s/krb5.conf", tmpdir);
244     putenv(krb5_config);
245     free(krb5_config_empty);
246
247 #    ifdef HAVE_CRACKLIB
248
249     /* Add CrackLib configuration. */
250     setup_argv[3] = (char *) "password_dictionary";
251     setup_argv[4] = dictionary;
252     setup_argv[5] = NULL;
253     run_setup((const char **) setup_argv);
254
255     /* Obtain a new Kerberos context with that krb5.conf file. */
256     krb5_free_context(ctx);
257     code = krb5_init_context(&ctx);
258     if (code != 0)
259         bail_krb5(ctx, code, "cannot initialize Kerberos context");
260
261     /* Run all of the tests again.  No need to re-run principal tests. */
262     code = vtable->open(ctx, NULL, &data);
263     is_int(0, code, "Plugin initialization (krb5.conf dictionary)");
264     if (code != 0)
265         bail("cannot continue after plugin initialization failure");
266     for (i = 0; i < ARRAY_SIZE(cracklib_tests); i++) {
267 #        ifdef HAVE_SYSTEM_CRACKLIB
268         if (cracklib_tests[i].skip_for_system_cracklib) {
269             skip_block(2, "not built with embedded CrackLib");
270             continue;
271         }
272 #        endif
273         is_password_test(ctx, vtable, data, &cracklib_tests[i]);
274     }
275     vtable->close(ctx, data);
276
277     /*
278      * Add length restrictions and a maximum length for CrackLib.  This should
279      * reject passwords as too short, but let through a password that's
280      * actually in the CrackLib dictionary.
281      */
282     setup_argv[5] = (char *) "minimum_length";
283     setup_argv[6] = (char *) "12";
284     setup_argv[7] = (char *) "cracklib_maxlen";
285     setup_argv[8] = (char *) "11";
286     setup_argv[9] = NULL;
287     run_setup((const char **) setup_argv);
288
289     /* Obtain a new Kerberos context with that krb5.conf file. */
290     krb5_free_context(ctx);
291     code = krb5_init_context(&ctx);
292     if (code != 0)
293         bail_krb5(ctx, code, "cannot initialize Kerberos context");
294
295     /* Run all of the length tests. */
296     code = vtable->open(ctx, NULL, &data);
297     is_int(0, code, "Plugin initialization (length)");
298     if (code != 0)
299         bail("cannot continue after plugin initialization failure");
300     for (i = 0; i < ARRAY_SIZE(length_tests); i++)
301         is_password_test(ctx, vtable, data, &length_tests[i]);
302     vtable->close(ctx, data);
303
304 #    else
305
306     /* Otherwise mark the CrackLib tests as skipped. */
307     count = ARRAY_SIZE(cracklib_tests) + ARRAY_SIZE(length_tests);
308     skip_block(count * 2 + 2, "not built with CrackLib support");
309
310 #    endif /* !HAVE_CRACKLIB */
311
312     /* Switch to simple character class configuration in krb5.conf. */
313     setup_argv[3] = (char *) "minimum_different";
314     setup_argv[4] = (char *) "8";
315     setup_argv[5] = (char *) "require_ascii_printable";
316     setup_argv[6] = (char *) "true";
317     setup_argv[7] = (char *) "require_non_letter";
318     setup_argv[8] = (char *) "true";
319     setup_argv[9] = NULL;
320     run_setup((const char **) setup_argv);
321
322     /* Obtain a new Kerberos context with that krb5.conf file. */
323     krb5_free_context(ctx);
324     code = krb5_init_context(&ctx);
325     if (code != 0)
326         bail_krb5(ctx, code, "cannot initialize Kerberos context");
327
328     /* Run all the simple character class tests. */
329     code = vtable->open(ctx, NULL, &data);
330     is_int(0, code, "Plugin initialization (simple character class)");
331     if (code != 0)
332         bail("cannot continue after plugin initialization failure");
333     for (i = 0; i < ARRAY_SIZE(letter_tests); i++)
334         is_password_test(ctx, vtable, data, &letter_tests[i]);
335     vtable->close(ctx, data);
336
337     /* Add complex character class configuration to krb5.conf. */
338     setup_argv[3] = (char *) "require_classes";
339     setup_argv[4] = (char *) "8-19:lower,upper 8-15:digit 8-11:symbol 24-24:3";
340     setup_argv[5] = NULL;
341     run_setup((const char **) setup_argv);
342
343     /* Obtain a new Kerberos context with that krb5.conf file. */
344     krb5_free_context(ctx);
345     code = krb5_init_context(&ctx);
346     if (code != 0)
347         bail_krb5(ctx, code, "cannot initialize Kerberos context");
348
349     /* Run all the complex character class tests. */
350     code = vtable->open(ctx, NULL, &data);
351     is_int(0, code, "Plugin initialization (complex character class)");
352     if (code != 0)
353         bail_krb5(ctx, code, "plugin initialization failure");
354     for (i = 0; i < ARRAY_SIZE(classes_tests); i++)
355         is_password_test(ctx, vtable, data, &classes_tests[i]);
356     vtable->close(ctx, data);
357
358     /* Re-run the length restriction checks with no dictionary at all. */
359     setup_argv[3] = (char *) "minimum_length";
360     setup_argv[4] = (char *) "12";
361     setup_argv[5] = NULL;
362     run_setup((const char **) setup_argv);
363
364     /* Obtain a new Kerberos context with that krb5.conf file. */
365     krb5_free_context(ctx);
366     code = krb5_init_context(&ctx);
367     if (code != 0)
368         bail_krb5(ctx, code, "cannot initialize Kerberos context");
369
370     /* Run all of the length tests. */
371     code = vtable->open(ctx, NULL, &data);
372     is_int(0, code, "Plugin initialization (length)");
373     if (code != 0)
374         bail("cannot continue after plugin initialization failure");
375     for (i = 0; i < ARRAY_SIZE(length_tests); i++)
376         is_password_test(ctx, vtable, data, &length_tests[i]);
377     vtable->close(ctx, data);
378
379 #    ifdef HAVE_CDB
380
381     /* If built with CDB, set up krb5.conf to use a CDB dictionary instead. */
382     test_file_path_free(dictionary);
383     dictionary = test_file_path("data/wordlist.cdb");
384     if (dictionary == NULL)
385         bail("cannot find data/wordlist.cdb in the test suite");
386     setup_argv[3] = (char *) "password_dictionary_cdb";
387     setup_argv[4] = dictionary;
388     setup_argv[5] = NULL;
389     run_setup((const char **) setup_argv);
390
391     /* Obtain a new Kerberos context with that krb5.conf file. */
392     krb5_free_context(ctx);
393     code = krb5_init_context(&ctx);
394     if (code != 0)
395         bail_krb5(ctx, code, "cannot initialize Kerberos context");
396
397     /* Run the CDB and principal tests. */
398     code = vtable->open(ctx, NULL, &data);
399     is_int(0, code, "Plugin initialization (CDB dictionary)");
400     if (code != 0)
401         bail("cannot continue after plugin initialization failure");
402     for (i = 0; i < ARRAY_SIZE(cdb_tests); i++)
403         is_password_test(ctx, vtable, data, &cdb_tests[i]);
404     for (i = 0; i < ARRAY_SIZE(principal_tests); i++)
405         is_password_test(ctx, vtable, data, &principal_tests[i]);
406     vtable->close(ctx, data);
407
408 #    else /* !HAVE_CDB */
409
410     /* Otherwise, mark the CDB tests as skipped. */
411     count = ARRAY_SIZE(cdb_tests) + ARRAY_SIZE(principal_tests);
412     skip_block(count * 2 + 1, "not built with CDB support");
413
414 #    endif /* !HAVE_CDB */
415
416 #    ifdef HAVE_SQLITE
417
418     /*
419      * If built with SQLite, set up krb5.conf to use a SQLite dictionary
420      * instead.
421      */
422     test_file_path_free(dictionary);
423     dictionary = test_file_path("data/wordlist.sqlite");
424     if (dictionary == NULL)
425         bail("cannot find data/wordlist.sqlite in the test suite");
426     setup_argv[3] = (char *) "password_dictionary_sqlite";
427     setup_argv[4] = dictionary;
428     setup_argv[5] = NULL;
429     run_setup((const char **) setup_argv);
430     test_file_path_free(setup_argv[0]);
431     test_file_path_free(path);
432
433     /* Obtain a new Kerberos context with that krb5.conf file. */
434     krb5_free_context(ctx);
435     code = krb5_init_context(&ctx);
436     if (code != 0)
437         bail_krb5(ctx, code, "cannot initialize Kerberos context");
438
439     /* Run the SQLite and principal tests. */
440     code = vtable->open(ctx, NULL, &data);
441     is_int(0, code, "Plugin initialization (SQLite dictionary)");
442     if (code != 0)
443         bail("cannot continue after plugin initialization failure");
444     for (i = 0; i < ARRAY_SIZE(sqlite_tests); i++)
445         is_password_test(ctx, vtable, data, &sqlite_tests[i]);
446     for (i = 0; i < ARRAY_SIZE(principal_tests); i++)
447         is_password_test(ctx, vtable, data, &principal_tests[i]);
448     vtable->close(ctx, data);
449
450 #    else /* !HAVE_SQLITE */
451
452     /* Otherwise, mark the SQLite tests as skipped. */
453     count = ARRAY_SIZE(sqlite_tests) + ARRAY_SIZE(principal_tests);
454     skip_block(count * 2 + 1, "not built with SQLite support");
455
456 #    endif /* !HAVE_SQLITE */
457
458     /* Manually clean up after the results of make-krb5-conf. */
459     basprintf(&path, "%s/krb5.conf", tmpdir);
460     unlink(path);
461     free(path);
462     test_tmpdir_free(tmpdir);
463
464     /* Close down the module. */
465     if (dlclose(handle) != 0)
466         bail("cannot close plugin: %s", dlerror());
467
468     /* Keep valgrind clean by freeing all memory. */
469     test_file_path_free(dictionary);
470     krb5_free_context(ctx);
471     free(vtable);
472     putenv((char *) "KRB5_CONFIG=");
473     free(krb5_config);
474     return 0;
475 }
476
477 #endif /* HAVE_KRB5_PWQUAL_PLUGIN_H */