]> eyrie.org Git - kerberos/krb5-strength.git/blob - tests/tap/kerberos.c
Change CrackLib tests for system CrackLib
[kerberos/krb5-strength.git] / tests / tap / kerberos.c
1 /*
2  * Utility functions for tests that use Kerberos.
3  *
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.
10  *
11  * Some of the functionality here is only available if the Kerberos libraries
12  * are available.
13  *
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/>.
16  *
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
20  *
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:
27  *
28  * The above copyright notice and this permission notice shall be included in
29  * all copies or substantial portions of the Software.
30  *
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.
38  */
39
40 #include <config.h>
41 #ifdef HAVE_KRB5
42 # include <portable/krb5.h>
43 #endif
44 #include <portable/system.h>
45
46 #include <sys/stat.h>
47
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>
53
54 /*
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.
57  */
58 #if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ > 2) || defined(__clang__)
59 # pragma GCC diagnostic ignored "-Wformat-nonliteral"
60 #endif
61
62
63 /*
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.
69  */
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;
76
77
78 /*
79  * Obtain Kerberos tickets and fill in the principal config entry.
80  *
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.
84  */
85 #ifdef HAVE_KRB5
86
87 static void
88 kerberos_kinit(void)
89 {
90     char *name, *krbtgt;
91     krb5_error_code code;
92     krb5_context ctx;
93     krb5_ccache ccache;
94     krb5_principal kprinc;
95     krb5_keytab keytab;
96     krb5_get_init_creds_opt *opts;
97     krb5_creds creds;
98     const char *realm;
99
100     /*
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).
104      */
105     code = krb5_init_context(&ctx);
106     if (code != 0)
107         bail_krb5(ctx, code, "error initializing Kerberos");
108     kprinc = kerberos_keytab_principal(ctx, config->keytab);
109     code = krb5_unparse_name(ctx, kprinc, &name);
110     if (code != 0)
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);
115
116     /* Now do the Kerberos initialization. */
117     code = krb5_cc_default(ctx, &ccache);
118     if (code != 0)
119         bail_krb5(ctx, code, "error setting ticket cache");
120     code = krb5_parse_name(ctx, config->principal, &kprinc);
121     if (code != 0)
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);
126     if (code != 0)
127         bail_krb5(ctx, code, "cannot open keytab %s", config->keytab);
128     code = krb5_get_init_creds_opt_alloc(ctx, &opts);
129     if (code != 0)
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,
135                                       opts);
136     if (code != 0)
137         bail_krb5(ctx, code, "cannot get Kerberos tickets");
138     code = krb5_cc_initialize(ctx, ccache, kprinc);
139     if (code != 0)
140         bail_krb5(ctx, code, "error initializing ticket cache");
141     code = krb5_cc_store_cred(ctx, ccache, &creds);
142     if (code != 0)
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);
150     free(krbtgt);
151 }
152
153 #else /* !HAVE_KRB5 */
154
155 static void
156 kerberos_kinit(void)
157 {
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"
163     };
164     FILE *file;
165     char *path;
166     char principal[BUFSIZ], *command;
167     size_t i;
168     int status;
169
170     /* Read the principal corresponding to the keytab. */
171     path = test_file_path("config/principal");
172     if (path == NULL) {
173         test_file_path_free(config->keytab);
174         config->keytab = NULL;
175         return;
176     }
177     file = fopen(path, "r");
178     if (file == NULL) {
179         test_file_path_free(path);
180         return;
181     }
182     test_file_path_free(path);
183     if (fgets(principal, sizeof(principal), file) == NULL)
184         bail("cannot read %s", path);
185     fclose(file);
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);
190
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);
195         free(command);
196         if (status != -1 && WEXITSTATUS(status) == 0)
197             break;
198     }
199     if (status == -1 || WEXITSTATUS(status) != 0)
200         bail("cannot get Kerberos tickets");
201 }
202
203 #endif /* !HAVE_KRB5 */
204
205
206 /*
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.
211  */
212 static void
213 kerberos_free(void)
214 {
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);
220         free(config->cache);
221         free(config->userprinc);
222         free(config->username);
223         free(config->password);
224         free(config->pkinit_principal);
225         free(config->pkinit_cert);
226         free(config);
227         config = NULL;
228     }
229     if (krb5ccname != NULL) {
230         putenv((char *) "KRB5CCNAME=");
231         free(krb5ccname);
232         krb5ccname = NULL;
233     }
234     if (krb5_ktname != NULL) {
235         putenv((char *) "KRB5_KTNAME=");
236         free(krb5_ktname);
237         krb5_ktname = NULL;
238     }
239 }
240
241
242 /*
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.
247  */
248 void
249 kerberos_cleanup(void)
250 {
251     char *path;
252
253     if (tmpdir_ticket != NULL) {
254         basprintf(&path, "%s/krb5cc_test", tmpdir_ticket);
255         unlink(path);
256         free(path);
257     }
258     kerberos_free();
259 }
260
261
262 /*
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.
267  */
268 static void
269 kerberos_cleanup_handler(int success UNUSED, int primary)
270 {
271     if (primary)
272         kerberos_cleanup();
273     else
274         kerberos_free();
275 }
276
277
278 /*
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
282  * and KRB5CCNAME.
283  *
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.
287  */
288 struct kerberos_config *
289 kerberos_setup(enum kerberos_needs needs)
290 {
291     char *path;
292     char buffer[BUFSIZ];
293     FILE *file = NULL;
294
295     /* If we were called before, clean up after the previous run. */
296     if (config != NULL)
297         kerberos_cleanup();
298     config = bcalloc(1, sizeof(struct kerberos_config));
299
300     /*
301      * If we have a config/keytab file, set the KRB5CCNAME and KRB5_KTNAME
302      * environment variables and obtain initial tickets.
303      */
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");
308     } else {
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);
313         putenv(krb5ccname);
314         putenv(krb5_ktname);
315         kerberos_kinit();
316     }
317
318     /*
319      * If we have a config/password file, read it and fill out the relevant
320      * members of our config struct.
321      */
322     path = test_file_path("config/password");
323     if (path != NULL)
324         file = fopen(path, "r");
325     if (file == NULL) {
326         if (needs == TAP_KRB_NEEDS_PASSWORD || needs == TAP_KRB_NEEDS_BOTH)
327             skip_all("Kerberos tests not configured");
328     } else {
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);
337         fclose(file);
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);
342
343         /*
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.
347          */
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';
353         config->realm++;
354     }
355     test_file_path_free(path);
356
357     /*
358      * If we have PKINIT configuration, read it and fill out the relevant
359      * members of our config struct.
360      */
361     path = test_file_path("config/pkinit-principal");
362     if (path != NULL)
363         file = fopen(path, "r");
364     if (file != NULL) {
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';
370         fclose(file);
371         test_file_path_free(path);
372         path = test_file_path("config/pkinit-cert");
373         if (path != NULL) {
374             config->pkinit_principal = bstrdup(buffer);
375             config->pkinit_cert = bstrdup(path);
376         }
377     }
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");
381
382     /*
383      * Register the cleanup function so that the caller doesn't have to do
384      * explicit cleanup.
385      */
386     test_cleanup_register(kerberos_cleanup_handler);
387
388     /* Return the configuration. */
389     return config;
390 }
391
392
393 /*
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
397  * multiple times.
398  *
399  * Normally called via an atexit handler.
400  */
401 void
402 kerberos_cleanup_conf(void)
403 {
404     char *path;
405
406     if (tmpdir_conf != NULL) {
407         basprintf(&path, "%s/krb5.conf", tmpdir_conf);
408         unlink(path);
409         free(path);
410         test_tmpdir_free(tmpdir_conf);
411         tmpdir_conf = NULL;
412     }
413     putenv((char *) "KRB5_CONFIG=");
414     free(krb5_config);
415     krb5_config = NULL;
416 }
417
418
419 /*
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.
425  *
426  * Depends on data/generate-krb5-conf being present in the test suite.
427  */
428 void
429 kerberos_generate_conf(const char *realm)
430 {
431     char *path;
432     const char *argv[3];
433
434     if (tmpdir_conf != NULL)
435         kerberos_cleanup_conf();
436     path = test_file_path("data/generate-krb5-conf");
437     if (path == NULL)
438         bail("cannot find generate-krb5-conf");
439     argv[0] = path;
440     argv[1] = realm;
441     argv[2] = NULL;
442     run_setup(argv);
443     test_file_path_free(path);
444     tmpdir_conf = test_tmpdir();
445     basprintf(&krb5_config, "KRB5_CONFIG=%s/krb5.conf", tmpdir_conf);
446     putenv(krb5_config);
447     if (atexit(kerberos_cleanup_conf) != 0)
448         sysdiag("cannot register cleanup function");
449 }
450
451
452 /*
453  * The remaining functions in this file are only available if Kerberos
454  * libraries are available.
455  */
456 #ifdef HAVE_KRB5
457
458
459 /*
460  * Report a Kerberos error and bail out.
461  */
462 void
463 bail_krb5(krb5_context ctx, krb5_error_code code, const char *format, ...)
464 {
465     const char *k5_msg = NULL;
466     char *message;
467     va_list args;
468
469     if (ctx != NULL)
470         k5_msg = krb5_get_error_message(ctx, code);
471     va_start(args, format);
472     bvasprintf(&message, format, args);
473     va_end(args);
474     if (k5_msg == NULL)
475         bail("%s", message);
476     else
477         bail("%s: %s", message, k5_msg);
478 }
479
480
481 /*
482  * Report a Kerberos error as a diagnostic to stderr.
483  */
484 void
485 diag_krb5(krb5_context ctx, krb5_error_code code, const char *format, ...)
486 {
487     const char *k5_msg = NULL;
488     char *message;
489     va_list args;
490
491     if (ctx != NULL)
492         k5_msg = krb5_get_error_message(ctx, code);
493     va_start(args, format);
494     bvasprintf(&message, format, args);
495     va_end(args);
496     if (k5_msg == NULL)
497         diag("%s", message);
498     else
499         diag("%s: %s", message, k5_msg);
500     free(message);
501     if (k5_msg != NULL)
502         krb5_free_error_message(ctx, k5_msg);
503 }
504
505
506 /*
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.
509  * Exit on error.
510  */
511 krb5_principal
512 kerberos_keytab_principal(krb5_context ctx, const char *path)
513 {
514     krb5_keytab keytab;
515     krb5_kt_cursor cursor;
516     krb5_keytab_entry entry;
517     krb5_principal princ;
518     krb5_error_code status;
519
520     status = krb5_kt_resolve(ctx, path, &keytab);
521     if (status != 0)
522         bail_krb5(ctx, status, "error opening %s", path);
523     status = krb5_kt_start_seq_get(ctx, keytab, &cursor);
524     if (status != 0)
525         bail_krb5(ctx, status, "error reading %s", path);
526     status = krb5_kt_next_entry(ctx, keytab, &entry, &cursor);
527     if (status == 0) {
528         status = krb5_copy_principal(ctx, entry.principal, &princ);
529         if (status != 0)
530             bail_krb5(ctx, status, "error copying principal from %s", path);
531         krb5_kt_free_entry(ctx, &entry);
532     }
533     if (status != 0)
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);
537     return princ;
538 }
539
540 #endif /* HAVE_KRB5 */