]> eyrie.org Git - kerberos/krb5-strength.git/blob - tests/plugin/mit-t.c
Restructure MIT test code to allow for dlclose
[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
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 an arrays
30  * named cdb_tests, cracklib_tests, and principal_tests.
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
39
40 #ifndef HAVE_KRB5_PWQUAL_PLUGIN_H
41 /*
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.
45  */
46 int
47 main(void)
48 {
49     skip_all("not built against MIT libraries");
50     return 0;
51 }
52
53 #else
54
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,
57                                                krb5_plugin_vtable);
58
59
60 /*
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.
65  */
66 static krb5_pwqual_vtable
67 load_plugin(krb5_context ctx, void **handle)
68 {
69     char *path;
70     krb5_error_code code;
71     krb5_pwqual_vtable vtable = NULL;
72     krb5_error_code (*init)(krb5_context, int, int, krb5_plugin_vtable);
73
74     /* Load the module. */
75     path = test_file_path("../plugin/.libs/strength.so");
76     if (path == NULL)
77         bail("cannot find plugin");
78     *handle = dlopen(path, RTLD_NOW);
79     if (*handle == NULL)
80         bail("cannot dlopen %s: %s", path, dlerror());
81     test_file_path_free(path);
82
83     /* Find the entry point function. */
84     init = dlsym(*handle, "pwqual_strength_initvt");
85     if (init == NULL)
86         bail("cannot get pwqual_strength_initvt symbol: %s", dlerror());
87
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");
92
93     /* Call that function properly to get the vtable. */
94     vtable = bmalloc(sizeof(*vtable));
95     code = init(ctx, 1, 1, (krb5_plugin_vtable) vtable);
96     if (code != 0)
97         bail_krb5(ctx, code, "cannot obtain module vtable");
98
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");
102
103     /* Verify the metadata. */
104     is_string("krb5-strength", vtable->name, "Module name");
105
106     /* Return the vtable. */
107     return vtable;
108 }
109
110
111 /*
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
114  * results.
115  */
116 static void
117 is_password_test(krb5_context ctx, const krb5_pwqual_vtable vtable,
118                  krb5_pwqual_moddata data, const struct password_test *test)
119 {
120     krb5_principal princ;
121     krb5_error_code code;
122     const char *error;
123
124     /* Translate the principal into a krb5_principal. */
125     code = krb5_parse_name(ctx, test->principal, &princ);
126     if (code != 0)
127         bail_krb5(ctx, code, "cannot parse principal %s", test->principal);
128
129     /* Call the verifier. */
130     code = vtable->check(ctx, data, test->password, NULL, princ, NULL);
131
132     /* Check the results against the test data. */
133     is_int(test->code, code, "%s (status)", test->name);
134     if (code == 0)
135         is_string(test->error, NULL, "%s (error)", test->name);
136     else {
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);
140     }
141
142     /* Free the parsed principal. */
143     krb5_free_principal(ctx, princ);
144 }
145
146
147 int
148 main(void)
149 {
150     char *path, *dictionary, *krb5_config, *krb5_config_empty, *tmpdir;
151     char *setup_argv[10];
152     const char*build;
153     size_t i, count;
154     krb5_context ctx;
155     krb5_pwqual_vtable vtable;
156     krb5_pwqual_moddata data;
157     krb5_error_code code;
158     void *handle;
159
160     /*
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
163      * password test.
164      *
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.
168      */
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);
176
177     /* Start with the krb5.conf that contains no dictionary configuration. */
178     path = test_file_path("data/krb5.conf");
179     if (path == NULL)
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);
183
184     /* Obtain a Kerberos context with that krb5.conf file. */
185     code = krb5_init_context(&ctx);
186     if (code != 0)
187         bail_krb5(ctx, code, "cannot initialize Kerberos context");
188
189     /* Load the plugin. */
190     vtable = load_plugin(ctx, &handle);
191
192     /* Initialize the plugin with a CrackLib dictionary. */
193     build = getenv("BUILD");
194     if (build == NULL)
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)");
199     if (code != 0)
200         bail("cannot continue after plugin initialization failure");
201
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]);
207
208     /* Close that initialization of the plugin and destroy that context. */
209     vtable->close(ctx, data);
210     krb5_free_context(ctx);
211     ctx = NULL;
212
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);
224
225     /* Point KRB5_CONFIG at the newly-generated krb5.conf file. */
226     basprintf(&krb5_config, "KRB5_CONFIG=%s/krb5.conf", tmpdir);
227     putenv(krb5_config);
228     free(krb5_config_empty);
229
230     /* Obtain a new Kerberos context with that krb5.conf file. */
231     krb5_free_context(ctx);
232     code = krb5_init_context(&ctx);
233     if (code != 0)
234         bail_krb5(ctx, code, "cannot initialize Kerberos context");
235
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)");
239     if (code != 0)
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);
244
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);
252
253     /* Obtain a new Kerberos context with that krb5.conf file. */
254     krb5_free_context(ctx);
255     code = krb5_init_context(&ctx);
256     if (code != 0)
257         bail_krb5(ctx, code, "cannot initialize Kerberos context");
258
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)");
262     if (code != 0)
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);
267
268     /*
269      * Add complex character class configuration to krb5.conf but drop
270      * the dictionary configuration.
271      */
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);
276
277     /* Obtain a new Kerberos context with that krb5.conf file. */
278     krb5_free_context(ctx);
279     code = krb5_init_context(&ctx);
280     if (code != 0)
281         bail_krb5(ctx, code, "cannot initialize Kerberos context");
282
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)");
286     if (code != 0)
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);
291
292     /*
293      * Add length restrictions.  This should only do length checks without any
294      * dictionary checks.
295      */
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);
300
301     /* Obtain a new Kerberos context with that krb5.conf file. */
302     krb5_free_context(ctx);
303     code = krb5_init_context(&ctx);
304     if (code != 0)
305         bail_krb5(ctx, code, "cannot initialize Kerberos context");
306
307     /* Run all of the length tests. */
308     code = vtable->open(ctx, NULL, &data);
309     is_int(0, code, "Plugin initialization (length)");
310     if (code != 0)
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);
315
316 #ifdef HAVE_CDB
317
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);
329
330     /* Obtain a new Kerberos context with that krb5.conf file. */
331     krb5_free_context(ctx);
332     code = krb5_init_context(&ctx);
333     if (code != 0)
334         bail_krb5(ctx, code, "cannot initialize Kerberos context");
335
336     /* Run the CDB and principal tests. */
337     code = vtable->open(ctx, NULL, &data);
338     is_int(0, code, "Plugin initialization (CDB dictionary)");
339     if (code != 0)
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);
346
347 #else /* !HAVE_CDB */
348
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");
352
353 #endif /* !HAVE_CDB */
354
355     /* Manually clean up after the results of make-krb5-conf. */
356     basprintf(&path, "%s/krb5.conf", tmpdir);
357     unlink(path);
358     free(path);
359     test_tmpdir_free(tmpdir);
360
361     /* Close down the module. */
362     if (dlclose(handle) != 0)
363         bail("cannot close plugin: %s", dlerror());
364
365     /* Keep valgrind clean by freeing all memory. */
366     test_file_path_free(dictionary);
367     krb5_free_context(ctx);
368     free(vtable);
369     putenv((char *) "KRB5_CONFIG=");
370     free(krb5_config);
371     return 0;
372 }
373
374 #endif /* HAVE_KRB5_PWQUAL_PLUGIN_H */