]> eyrie.org Git - kerberos/krb5-strength.git/blob - tests/plugin/heimdal-t.c
New upstream version 3.3
[kerberos/krb5-strength.git] / tests / plugin / heimdal-t.c
1 /*
2  * Test for the Heimdal shared module API.
3  *
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
8  *
9  * SPDX-License-Identifier: MIT
10  */
11
12 #include <config.h>
13 #include <portable/krb5.h>
14 #include <portable/system.h>
15
16 #include <dlfcn.h>
17 #include <errno.h>
18 #ifdef HAVE_KADM5_KADM5_PWCHECK_H
19 #    include <kadm5/kadm5-pwcheck.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_KADM5_KADM5_PWCHECK_H
42 /*
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
45  * calls skip_all.
46  */
47 int
48 main(void)
49 {
50     skip_all("not built against Heimdal libraries");
51     return 0;
52 }
53
54 #else
55
56 /*
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.
61  */
62 static struct kadm5_pw_policy_verifier *
63 load_plugin(void **handle)
64 {
65     char *path;
66     struct kadm5_pw_policy_verifier *verifier;
67
68     /* Load the module. */
69     path = test_file_path("../plugin/.libs/strength.so");
70     if (path == NULL)
71         bail("cannot find plugin");
72     *handle = dlopen(path, RTLD_NOW);
73     if (*handle == NULL)
74         bail("cannot dlopen %s: %s", path, dlerror());
75     test_file_path_free(path);
76
77     /* Find the dispatch table and do a basic sanity check. */
78     verifier = dlsym(*handle, "kadm5_password_verifier");
79     if (verifier == NULL)
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");
83
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");
91
92     /* Return the dispatch table. */
93     return verifier;
94 }
95
96
97 /*
98  * Given the dispatch table and a test case, call out to the password strength
99  * checking module and check the results.
100  */
101 static void
102 is_password_test(const struct kadm5_pw_policy_verifier *verifier,
103                  const struct password_test *test)
104 {
105     krb5_context ctx;
106     krb5_principal princ;
107     krb5_error_code code;
108     krb5_data password;
109     int result;
110     char error[BUFSIZ] = "";
111
112     /* Obtain a Kerberos context to use for parsing principal names. */
113     code = krb5_init_context(&ctx);
114     if (code != 0)
115         bail_krb5(ctx, code, "cannot initialize Kerberos context");
116
117     /* Translate the test data into the form that the verifier expects. */
118     code = krb5_parse_name(ctx, test->principal, &princ);
119     if (code != 0)
120         bail_krb5(ctx, code, "cannot parse principal %s", test->principal);
121     password.data = (char *) test->password;
122     password.length = strlen(test->password);
123
124     /* Call the verifier. */
125     result = (verifier->funcs[0].func)(ctx, princ, &password, NULL, error,
126                                        sizeof(error));
127
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)",
131               test->name);
132
133     /* Free data structures. */
134     krb5_free_principal(ctx, princ);
135     krb5_free_context(ctx);
136 }
137
138
139 int
140 main(void)
141 {
142     char *path, *krb5_config, *krb5_config_empty, *tmpdir;
143     char *setup_argv[12];
144     size_t i, count;
145     struct kadm5_pw_policy_verifier *verifier;
146     void *handle;
147
148     /*
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.
152      */
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;
160     plan(5 + count * 2);
161
162     /* Start with the krb5.conf that contains no dictionary configuration. */
163     path = test_file_path("data/krb5.conf");
164     if (path == NULL)
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);
168
169     /* Load the plugin. */
170     verifier = load_plugin(&handle);
171
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);
181
182     /* Point KRB5_CONFIG at the newly-generated krb5.conf file. */
183     basprintf(&krb5_config, "KRB5_CONFIG=%s/krb5.conf", tmpdir);
184     putenv(krb5_config);
185     free(krb5_config_empty);
186
187     /* Run principal tests. */
188     for (i = 0; i < ARRAY_SIZE(principal_tests); i++)
189         is_password_test(verifier, &principal_tests[i]);
190
191 #    ifdef HAVE_CRACKLIB
192
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);
198
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");
204             continue;
205         }
206 #        endif
207         is_password_test(verifier, &cracklib_tests[i]);
208     }
209
210     /*
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.
214      */
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);
221
222     /* Run the length tests. */
223     for (i = 0; i < ARRAY_SIZE(length_tests); i++)
224         is_password_test(verifier, &length_tests[i]);
225
226     /* Free the memory allocated for the CrackLib test. */
227     free(setup_argv[4]);
228
229 #    else
230
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");
234
235 #    endif /* !HAVE_CRACKLIB */
236
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);
246
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]);
250
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);
256
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]);
260
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);
266
267     /* Run the length tests. */
268     for (i = 0; i < ARRAY_SIZE(length_tests); i++)
269         is_password_test(verifier, &length_tests[i]);
270
271 #    ifdef HAVE_CDB
272
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]);
281
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]);
287
288 #    else /* !HAVE_CDB */
289
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");
293
294 #    endif /* !HAVE_CDB */
295
296 #    ifdef HAVE_SQLITE3
297
298     /*
299      * If built with SQLite, set up krb5.conf to use a SQLite dictionary
300      * instead.
301      */
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);
311
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]);
317
318 #    else /* !HAVE_SQLITE3 */
319
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");
323
324 #    endif /* !HAVE_SQLITE3 */
325
326     /* Manually clean up after the results of make-krb5-conf. */
327     basprintf(&path, "%s/krb5.conf", tmpdir);
328     unlink(path);
329     free(path);
330     test_tmpdir_free(tmpdir);
331
332     /* Close down the module. */
333     if (dlclose(handle) != 0)
334         bail("cannot close plugin: %s", dlerror());
335
336     /* Keep valgrind clean by freeing environmental memory. */
337     putenv((char *) "KRB5_CONFIG=");
338     free(krb5_config);
339     return 0;
340 }
341
342 #endif /* HAVE_KADM5_KADM5_PWCHECK_H */