]> eyrie.org Git - kerberos/krb5-strength.git/blob - tests/plugin/mit-t.c
Change CrackLib tests for system CrackLib
[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 2010, 2013, 2014
6  *     The Board of Trustees of the Leland Stanford Junior University
7  *
8  * See LICENSE for licensing terms.
9  */
10
11 #include <config.h>
12 #include <portable/kadmin.h>
13 #include <portable/krb5.h>
14 #include <portable/system.h>
15
16 #include <dlfcn.h>
17 #include <errno.h>
18 #ifdef HAVE_KRB5_PWQUAL_PLUGIN_H
19 # include <krb5/pwqual_plugin.h>
20 #endif
21
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>
27
28 /*
29  * The password test data, generated from the JSON source.  Defines arrays
30  * named *_tests, where * is the file name without the ".c" suffix.
31  */
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>
39
40
41 #ifndef HAVE_KRB5_PWQUAL_PLUGIN_H
42 /*
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.
46  */
47 int
48 main(void)
49 {
50     skip_all("not built against MIT libraries");
51     return 0;
52 }
53
54 #else
55
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,
58                                                krb5_plugin_vtable);
59
60
61 /*
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.
66  */
67 static krb5_pwqual_vtable
68 load_plugin(krb5_context ctx, void **handle)
69 {
70     char *path;
71     krb5_error_code code;
72     krb5_pwqual_vtable vtable = NULL;
73     krb5_error_code (*init)(krb5_context, int, int, krb5_plugin_vtable);
74
75     /* Load the module. */
76     path = test_file_path("../plugin/.libs/strength.so");
77     if (path == NULL)
78         bail("cannot find plugin");
79     *handle = dlopen(path, RTLD_NOW);
80     if (*handle == NULL)
81         bail("cannot dlopen %s: %s", path, dlerror());
82     test_file_path_free(path);
83
84     /* Find the entry point function. */
85     init = dlsym(*handle, "pwqual_strength_initvt");
86     if (init == NULL)
87         bail("cannot get pwqual_strength_initvt symbol: %s", dlerror());
88
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");
93
94     /* Call that function properly to get the vtable. */
95     vtable = bmalloc(sizeof(*vtable));
96     code = init(ctx, 1, 1, (krb5_plugin_vtable) vtable);
97     if (code != 0)
98         bail_krb5(ctx, code, "cannot obtain module vtable");
99
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");
103
104     /* Verify the metadata. */
105     is_string("krb5-strength", vtable->name, "Module name");
106
107     /* Return the vtable. */
108     return vtable;
109 }
110
111
112 /*
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
115  * results.
116  */
117 static void
118 is_password_test(krb5_context ctx, const krb5_pwqual_vtable vtable,
119                  krb5_pwqual_moddata data, const struct password_test *test)
120 {
121     krb5_principal princ;
122     krb5_error_code code;
123     const char *error;
124
125     /* Translate the principal into a krb5_principal. */
126     code = krb5_parse_name(ctx, test->principal, &princ);
127     if (code != 0)
128         bail_krb5(ctx, code, "cannot parse principal %s", test->principal);
129
130     /* Call the verifier. */
131     code = vtable->check(ctx, data, test->password, NULL, princ, NULL);
132
133     /* Check the results against the test data. */
134     is_int(test->code, code, "%s (status)", test->name);
135     if (code == 0)
136         is_string(test->error, NULL, "%s (error)", test->name);
137     else {
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);
141     }
142
143     /* Free the parsed principal. */
144     krb5_free_principal(ctx, princ);
145 }
146
147
148 int
149 main(void)
150 {
151     char *path, *dictionary, *krb5_config, *krb5_config_empty, *tmpdir;
152     char *setup_argv[12];
153     const char*build;
154     size_t i, count;
155     krb5_context ctx;
156     krb5_pwqual_vtable vtable;
157     krb5_pwqual_moddata data;
158     krb5_error_code code;
159     void *handle;
160
161     /*
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
164      * per password test.
165      *
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.
169      */
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);
178
179     /* Start with the krb5.conf that contains no dictionary configuration. */
180     path = test_file_path("data/krb5.conf");
181     if (path == NULL)
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);
185
186     /* Obtain a Kerberos context with that krb5.conf file. */
187     code = krb5_init_context(&ctx);
188     if (code != 0)
189         bail_krb5(ctx, code, "cannot initialize Kerberos context");
190
191     /* Load the plugin. */
192     vtable = load_plugin(ctx, &handle);
193
194     /* Initialize the plugin with a CrackLib dictionary. */
195     build = getenv("BUILD");
196     if (build == NULL)
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)");
201     if (code != 0)
202         bail("cannot continue after plugin initialization failure");
203
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]);
209
210     /* Close that initialization of the plugin and destroy that context. */
211     vtable->close(ctx, data);
212     krb5_free_context(ctx);
213     ctx = NULL;
214
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);
226
227     /* Point KRB5_CONFIG at the newly-generated krb5.conf file. */
228     basprintf(&krb5_config, "KRB5_CONFIG=%s/krb5.conf", tmpdir);
229     putenv(krb5_config);
230     free(krb5_config_empty);
231
232     /* Obtain a new Kerberos context with that krb5.conf file. */
233     krb5_free_context(ctx);
234     code = krb5_init_context(&ctx);
235     if (code != 0)
236         bail_krb5(ctx, code, "cannot initialize Kerberos context");
237
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)");
241     if (code != 0)
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);
246
247     /*
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.
251      */
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);
258
259     /* Obtain a new Kerberos context with that krb5.conf file. */
260     krb5_free_context(ctx);
261     code = krb5_init_context(&ctx);
262     if (code != 0)
263         bail_krb5(ctx, code, "cannot initialize Kerberos context");
264
265     /* Run all of the length tests. */
266     code = vtable->open(ctx, NULL, &data);
267     is_int(0, code, "Plugin initialization (length)");
268     if (code != 0)
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);
273
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);
283
284     /* Obtain a new Kerberos context with that krb5.conf file. */
285     krb5_free_context(ctx);
286     code = krb5_init_context(&ctx);
287     if (code != 0)
288         bail_krb5(ctx, code, "cannot initialize Kerberos context");
289
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)");
293     if (code != 0)
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);
298
299     /*
300      * Add complex character class configuration to krb5.conf but drop
301      * the dictionary configuration.
302      */
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);
307
308     /* Obtain a new Kerberos context with that krb5.conf file. */
309     krb5_free_context(ctx);
310     code = krb5_init_context(&ctx);
311     if (code != 0)
312         bail_krb5(ctx, code, "cannot initialize Kerberos context");
313
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)");
317     if (code != 0)
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);
322
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);
328
329     /* Obtain a new Kerberos context with that krb5.conf file. */
330     krb5_free_context(ctx);
331     code = krb5_init_context(&ctx);
332     if (code != 0)
333         bail_krb5(ctx, code, "cannot initialize Kerberos context");
334
335     /* Run all of the length tests. */
336     code = vtable->open(ctx, NULL, &data);
337     is_int(0, code, "Plugin initialization (length)");
338     if (code != 0)
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);
343
344 #ifdef HAVE_CDB
345
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);
355
356     /* Obtain a new Kerberos context with that krb5.conf file. */
357     krb5_free_context(ctx);
358     code = krb5_init_context(&ctx);
359     if (code != 0)
360         bail_krb5(ctx, code, "cannot initialize Kerberos context");
361
362     /* Run the CDB and principal tests. */
363     code = vtable->open(ctx, NULL, &data);
364     is_int(0, code, "Plugin initialization (CDB dictionary)");
365     if (code != 0)
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);
372
373 #else /* !HAVE_CDB */
374
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");
378
379 #endif /* !HAVE_CDB */
380
381 #ifdef HAVE_SQLITE
382
383     /*
384      * If built with SQLite, set up krb5.conf to use a SQLite dictionary
385      * instead.
386      */
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);
397
398     /* Obtain a new Kerberos context with that krb5.conf file. */
399     krb5_free_context(ctx);
400     code = krb5_init_context(&ctx);
401     if (code != 0)
402         bail_krb5(ctx, code, "cannot initialize Kerberos context");
403
404     /* Run the SQLite and principal tests. */
405     code = vtable->open(ctx, NULL, &data);
406     is_int(0, code, "Plugin initialization (SQLite dictionary)");
407     if (code != 0)
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);
414
415 #else /* !HAVE_SQLITE */
416
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");
420
421 #endif /* !HAVE_SQLITE */
422
423     /* Manually clean up after the results of make-krb5-conf. */
424     basprintf(&path, "%s/krb5.conf", tmpdir);
425     unlink(path);
426     free(path);
427     test_tmpdir_free(tmpdir);
428
429     /* Close down the module. */
430     if (dlclose(handle) != 0)
431         bail("cannot close plugin: %s", dlerror());
432
433     /* Keep valgrind clean by freeing all memory. */
434     test_file_path_free(dictionary);
435     krb5_free_context(ctx);
436     free(vtable);
437     putenv((char *) "KRB5_CONFIG=");
438     free(krb5_config);
439     return 0;
440 }
441
442 #endif /* HAVE_KRB5_PWQUAL_PLUGIN_H */