2 * Utility functions for tests that use Kerberos.
4 * The core function is kerberos_setup, which loads Kerberos test
5 * configuration and returns a struct of information. It also supports
6 * obtaining initial tickets from the configured keytab and setting up
7 * KRB5CCNAME and KRB5_KTNAME if a Kerberos keytab is present. Also included
8 * are utility functions for setting up a krb5.conf file and reporting
9 * Kerberos errors or warnings during testing.
11 * Some of the functionality here is only available if the Kerberos libraries
14 * The canonical version of this file is maintained in the rra-c-util package,
15 * which can be found at <https://www.eyrie.org/~eagle/software/rra-c-util/>.
17 * Written by Russ Allbery <eagle@eyrie.org>
18 * Copyright 2006, 2007, 2009, 2010, 2011, 2012, 2013, 2014
19 * The Board of Trustees of the Leland Stanford Junior University
21 * Permission is hereby granted, free of charge, to any person obtaining a
22 * copy of this software and associated documentation files (the "Software"),
23 * to deal in the Software without restriction, including without limitation
24 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
25 * and/or sell copies of the Software, and to permit persons to whom the
26 * Software is furnished to do so, subject to the following conditions:
28 * The above copyright notice and this permission notice shall be included in
29 * all copies or substantial portions of the Software.
31 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
32 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
33 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
34 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
35 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
36 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
37 * DEALINGS IN THE SOFTWARE.
42 # include <portable/krb5.h>
44 #include <portable/system.h>
48 #include <tests/tap/basic.h>
49 #include <tests/tap/kerberos.h>
50 #include <tests/tap/macros.h>
51 #include <tests/tap/process.h>
52 #include <tests/tap/string.h>
55 * Disable the requirement that format strings be literals, since it's easier
56 * to handle the possible patterns for kinit commands as an array.
58 #if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ > 2) || defined(__clang__)
59 # pragma GCC diagnostic ignored "-Wformat-nonliteral"
64 * These variables hold the allocated configuration struct, the environment to
65 * point to a different Kerberos ticket cache, keytab, and configuration file,
66 * and the temporary directories used. We store them so that we can free them
67 * on exit for cleaner valgrind output, making it easier to find real memory
68 * leaks in the tested programs.
70 static struct kerberos_config *config = NULL;
71 static char *krb5ccname = NULL;
72 static char *krb5_ktname = NULL;
73 static char *krb5_config = NULL;
74 static char *tmpdir_ticket = NULL;
75 static char *tmpdir_conf = NULL;
79 * Obtain Kerberos tickets and fill in the principal config entry.
81 * There are two implementations of this function, one if we have native
82 * Kerberos libraries available and one if we don't. Uses keytab to obtain
83 * credentials, and fills in the cache member of the provided config struct.
94 krb5_principal kprinc;
96 krb5_get_init_creds_opt *opts;
101 * Determine the principal corresponding to that keytab. We copy the
102 * memory to ensure that it's allocated in the right memory domain on
103 * systems where that may matter (like Windows).
105 code = krb5_init_context(&ctx);
107 bail_krb5(ctx, code, "error initializing Kerberos");
108 kprinc = kerberos_keytab_principal(ctx, config->keytab);
109 code = krb5_unparse_name(ctx, kprinc, &name);
111 bail_krb5(ctx, code, "error unparsing name");
112 krb5_free_principal(ctx, kprinc);
113 config->principal = bstrdup(name);
114 krb5_free_unparsed_name(ctx, name);
116 /* Now do the Kerberos initialization. */
117 code = krb5_cc_default(ctx, &ccache);
119 bail_krb5(ctx, code, "error setting ticket cache");
120 code = krb5_parse_name(ctx, config->principal, &kprinc);
122 bail_krb5(ctx, code, "error parsing principal %s", config->principal);
123 realm = krb5_principal_get_realm(ctx, kprinc);
124 basprintf(&krbtgt, "krbtgt/%s@%s", realm, realm);
125 code = krb5_kt_resolve(ctx, config->keytab, &keytab);
127 bail_krb5(ctx, code, "cannot open keytab %s", config->keytab);
128 code = krb5_get_init_creds_opt_alloc(ctx, &opts);
130 bail_krb5(ctx, code, "cannot allocate credential options");
131 krb5_get_init_creds_opt_set_default_flags(ctx, NULL, realm, opts);
132 krb5_get_init_creds_opt_set_forwardable(opts, 0);
133 krb5_get_init_creds_opt_set_proxiable(opts, 0);
134 code = krb5_get_init_creds_keytab(ctx, &creds, kprinc, keytab, 0, krbtgt,
137 bail_krb5(ctx, code, "cannot get Kerberos tickets");
138 code = krb5_cc_initialize(ctx, ccache, kprinc);
140 bail_krb5(ctx, code, "error initializing ticket cache");
141 code = krb5_cc_store_cred(ctx, ccache, &creds);
143 bail_krb5(ctx, code, "error storing credentials");
144 krb5_cc_close(ctx, ccache);
145 krb5_free_cred_contents(ctx, &creds);
146 krb5_kt_close(ctx, keytab);
147 krb5_free_principal(ctx, kprinc);
148 krb5_get_init_creds_opt_free(ctx, opts);
149 krb5_free_context(ctx);
153 #else /* !HAVE_KRB5 */
158 static const char * const format[] = {
159 "kinit --no-afslog -k -t %s %s >/dev/null 2>&1 </dev/null",
160 "kinit -k -t %s %s >/dev/null 2>&1 </dev/null",
161 "kinit -t %s %s >/dev/null 2>&1 </dev/null",
162 "kinit -k -K %s %s >/dev/null 2>&1 </dev/null"
166 char principal[BUFSIZ], *command;
170 /* Read the principal corresponding to the keytab. */
171 path = test_file_path("config/principal");
173 test_file_path_free(config->keytab);
174 config->keytab = NULL;
177 file = fopen(path, "r");
179 test_file_path_free(path);
182 test_file_path_free(path);
183 if (fgets(principal, sizeof(principal), file) == NULL)
184 bail("cannot read %s", path);
186 if (principal[strlen(principal) - 1] != '\n')
187 bail("no newline in %s", path);
188 principal[strlen(principal) - 1] = '\0';
189 config->principal = bstrdup(principal);
191 /* Now do the Kerberos initialization. */
192 for (i = 0; i < ARRAY_SIZE(format); i++) {
193 basprintf(&command, format[i], config->keytab, principal);
194 status = system(command);
196 if (status != -1 && WEXITSTATUS(status) == 0)
199 if (status == -1 || WEXITSTATUS(status) != 0)
200 bail("cannot get Kerberos tickets");
203 #endif /* !HAVE_KRB5 */
207 * Free all the memory associated with our Kerberos setup, but don't remove
208 * the ticket cache. This is used when cleaning up on exit from a non-primary
209 * process so that test programs that fork don't remove the ticket cache still
210 * used by the main program.
215 test_tmpdir_free(tmpdir_ticket);
216 tmpdir_ticket = NULL;
217 if (config != NULL) {
218 test_file_path_free(config->keytab);
219 free(config->principal);
221 free(config->userprinc);
222 free(config->username);
223 free(config->password);
224 free(config->pkinit_principal);
225 free(config->pkinit_cert);
229 if (krb5ccname != NULL) {
230 putenv((char *) "KRB5CCNAME=");
234 if (krb5_ktname != NULL) {
235 putenv((char *) "KRB5_KTNAME=");
243 * Clean up at the end of a test. This removes the ticket cache and resets
244 * and frees the memory allocated for the environment variables so that
245 * valgrind output on test suites is cleaner. Most of the work is done by
246 * kerberos_free, but this function also deletes the ticket cache.
249 kerberos_cleanup(void)
253 if (tmpdir_ticket != NULL) {
254 basprintf(&path, "%s/krb5cc_test", tmpdir_ticket);
263 * The cleanup handler for the TAP framework. Call kerberos_cleanup if we're
264 * in the primary process and kerberos_free if not. The first argument, which
265 * indicates whether the test succeeded or not, is ignored, since we need to
266 * do the same thing either way.
269 kerberos_cleanup_handler(int success UNUSED, int primary)
279 * Obtain Kerberos tickets for the principal specified in config/principal
280 * using the keytab specified in config/keytab, both of which are presumed to
281 * be in tests in either the build or the source tree. Also sets KRB5_KTNAME
284 * Returns the contents of config/principal in newly allocated memory or NULL
285 * if Kerberos tests are apparently not configured. If Kerberos tests are
286 * configured but something else fails, calls bail.
288 struct kerberos_config *
289 kerberos_setup(enum kerberos_needs needs)
295 /* If we were called before, clean up after the previous run. */
298 config = bcalloc(1, sizeof(struct kerberos_config));
301 * If we have a config/keytab file, set the KRB5CCNAME and KRB5_KTNAME
302 * environment variables and obtain initial tickets.
304 config->keytab = test_file_path("config/keytab");
305 if (config->keytab == NULL) {
306 if (needs == TAP_KRB_NEEDS_KEYTAB || needs == TAP_KRB_NEEDS_BOTH)
307 skip_all("Kerberos tests not configured");
309 tmpdir_ticket = test_tmpdir();
310 basprintf(&config->cache, "%s/krb5cc_test", tmpdir_ticket);
311 basprintf(&krb5ccname, "KRB5CCNAME=%s/krb5cc_test", tmpdir_ticket);
312 basprintf(&krb5_ktname, "KRB5_KTNAME=%s", config->keytab);
319 * If we have a config/password file, read it and fill out the relevant
320 * members of our config struct.
322 path = test_file_path("config/password");
324 file = fopen(path, "r");
326 if (needs == TAP_KRB_NEEDS_PASSWORD || needs == TAP_KRB_NEEDS_BOTH)
327 skip_all("Kerberos tests not configured");
329 if (fgets(buffer, sizeof(buffer), file) == NULL)
330 bail("cannot read %s", path);
331 if (buffer[strlen(buffer) - 1] != '\n')
332 bail("no newline in %s", path);
333 buffer[strlen(buffer) - 1] = '\0';
334 config->userprinc = bstrdup(buffer);
335 if (fgets(buffer, sizeof(buffer), file) == NULL)
336 bail("cannot read password from %s", path);
338 if (buffer[strlen(buffer) - 1] != '\n')
339 bail("password too long in %s", path);
340 buffer[strlen(buffer) - 1] = '\0';
341 config->password = bstrdup(buffer);
344 * Strip the realm from the principal and set realm and username.
345 * This is not strictly correct; it doesn't cope with escaped @-signs
346 * or enterprise names.
348 config->username = bstrdup(config->userprinc);
349 config->realm = strchr(config->username, '@');
350 if (config->realm == NULL)
351 bail("test principal has no realm");
352 *config->realm = '\0';
355 test_file_path_free(path);
358 * If we have PKINIT configuration, read it and fill out the relevant
359 * members of our config struct.
361 path = test_file_path("config/pkinit-principal");
363 file = fopen(path, "r");
365 if (fgets(buffer, sizeof(buffer), file) == NULL)
366 bail("cannot read %s", path);
367 if (buffer[strlen(buffer) - 1] != '\n')
368 bail("no newline in %s", path);
369 buffer[strlen(buffer) - 1] = '\0';
371 test_file_path_free(path);
372 path = test_file_path("config/pkinit-cert");
374 config->pkinit_principal = bstrdup(buffer);
375 config->pkinit_cert = bstrdup(path);
378 test_file_path_free(path);
379 if (config->pkinit_cert == NULL && (needs & TAP_KRB_NEEDS_PKINIT) != 0)
380 skip_all("PKINIT tests not configured");
383 * Register the cleanup function so that the caller doesn't have to do
386 test_cleanup_register(kerberos_cleanup_handler);
388 /* Return the configuration. */
394 * Clean up the krb5.conf file generated by kerberos_generate_conf and free
395 * the memory used to set the environment variable. This doesn't fail if the
396 * file and variable are already gone, allowing it to be harmlessly run
399 * Normally called via an atexit handler.
402 kerberos_cleanup_conf(void)
406 if (tmpdir_conf != NULL) {
407 basprintf(&path, "%s/krb5.conf", tmpdir_conf);
410 test_tmpdir_free(tmpdir_conf);
413 putenv((char *) "KRB5_CONFIG=");
420 * Generate a krb5.conf file for testing and set KRB5_CONFIG to point to it.
421 * The [appdefaults] section will be stripped out and the default realm will
422 * be set to the realm specified, if not NULL. This will use config/krb5.conf
423 * in preference, so users can configure the tests by creating that file if
424 * the system file isn't suitable.
426 * Depends on data/generate-krb5-conf being present in the test suite.
429 kerberos_generate_conf(const char *realm)
434 if (tmpdir_conf != NULL)
435 kerberos_cleanup_conf();
436 path = test_file_path("data/generate-krb5-conf");
438 bail("cannot find generate-krb5-conf");
443 test_file_path_free(path);
444 tmpdir_conf = test_tmpdir();
445 basprintf(&krb5_config, "KRB5_CONFIG=%s/krb5.conf", tmpdir_conf);
447 if (atexit(kerberos_cleanup_conf) != 0)
448 sysdiag("cannot register cleanup function");
453 * The remaining functions in this file are only available if Kerberos
454 * libraries are available.
460 * Report a Kerberos error and bail out.
463 bail_krb5(krb5_context ctx, krb5_error_code code, const char *format, ...)
465 const char *k5_msg = NULL;
470 k5_msg = krb5_get_error_message(ctx, code);
471 va_start(args, format);
472 bvasprintf(&message, format, args);
477 bail("%s: %s", message, k5_msg);
482 * Report a Kerberos error as a diagnostic to stderr.
485 diag_krb5(krb5_context ctx, krb5_error_code code, const char *format, ...)
487 const char *k5_msg = NULL;
492 k5_msg = krb5_get_error_message(ctx, code);
493 va_start(args, format);
494 bvasprintf(&message, format, args);
499 diag("%s: %s", message, k5_msg);
502 krb5_free_error_message(ctx, k5_msg);
507 * Find the principal of the first entry of a keytab and return it. The
508 * caller is responsible for freeing the result with krb5_free_principal.
512 kerberos_keytab_principal(krb5_context ctx, const char *path)
515 krb5_kt_cursor cursor;
516 krb5_keytab_entry entry;
517 krb5_principal princ;
518 krb5_error_code status;
520 status = krb5_kt_resolve(ctx, path, &keytab);
522 bail_krb5(ctx, status, "error opening %s", path);
523 status = krb5_kt_start_seq_get(ctx, keytab, &cursor);
525 bail_krb5(ctx, status, "error reading %s", path);
526 status = krb5_kt_next_entry(ctx, keytab, &entry, &cursor);
528 status = krb5_copy_principal(ctx, entry.principal, &princ);
530 bail_krb5(ctx, status, "error copying principal from %s", path);
531 krb5_kt_free_entry(ctx, &entry);
534 bail("no principal found in keytab file %s", path);
535 krb5_kt_end_seq_get(ctx, keytab, &cursor);
536 krb5_kt_close(ctx, keytab);
540 #endif /* HAVE_KRB5 */