# See LICENSE for licensing terms.
ACLOCAL_AMFLAGS = -I m4
-EXTRA_DIST = .gitignore LICENSE autogen patches/README \
- patches/heimdal-1.3.1 tests/README tests/TESTS \
- tests/data/empty.conf tests/data/krb5.conf tests/data/queue.conf \
- tests/docs/pod-spelling-t tests/docs/pod-t tests/tap/libtap.sh \
+EXTRA_DIST = .gitignore LICENSE autogen patches/README \
+ patches/heimdal-1.3.1 tests/README tests/TESTS \
+ tests/data/default.conf tests/data/empty.conf \
+ tests/data/make-krb5-conf tests/data/queue.conf \
+ tests/docs/pod-spelling-t tests/docs/pod-t tests/tap/libtap.sh \
tests/util/xmalloc-t tools/krb5-sync.pod
+# Everything in the package needs to be able to find the Kerberos headers
+# and libraries.
AM_CPPFLAGS = $(KRB5_CPPFLAGS)
+AM_LDFLAGS = $(KRB5_LDFLAGS)
noinst_LTLIBRARIES = portable/libportable.la util/libutil.la
portable_libportable_la_SOURCES = portable/dummy.c portable/kadmin.h \
portable/krb5-extra.c portable/krb5.h portable/macros.h \
portable/stdbool.h portable/system.h
-portable_libportable_la_LDFLAGS = $(KRB5_LDFLAGS)
portable_libportable_la_LIBADD = $(LTLIBOBJS) $(KRB5_LIBS)
util_libutil_la_SOURCES = util/macros.h util/messages-krb5.c \
util/messages-krb5.h util/messages.c util/messages.h util/xmalloc.c \
plugin_krb5_sync_la_CPPFLAGS = $(KADM5SRV_CPPFLAGS) $(LDAP_CPPFLAGS) \
$(AM_CPPFLAGS)
plugin_krb5_sync_la_LDFLAGS = -module -avoid-version $(KADM5SRV_LDFLAGS) \
- $(LDAP_LDFLAGS) $(KRB5_LDFLAGS)
+ $(LDAP_LDFLAGS) $(AM_LDFLAGS)
plugin_krb5_sync_la_LIBADD = portable/libportable.la $(KADM5SRV_LIBS) \
$(LDAP_LIBS) $(KRB5_LIBS)
sbin_PROGRAMS = tools/krb5-sync
tools_krb5_sync_SOURCES = tools/krb5-sync.c $(plugin_krb5_sync_la_SOURCES)
tools_krb5_sync_CPPFLAGS = $(KADM5SRV_CPPFLAGS) $(LDAP_CPPFLAGS) $(AM_CPPFLAGS)
-tools_krb5_sync_LDFLAGS = $(KADM5SRV_LDFLAGS) $(LDAP_LDFLAGS) $(KRB5_LDFLAGS)
+tools_krb5_sync_LDFLAGS = $(KADM5SRV_LDFLAGS) $(LDAP_LDFLAGS) $(AM_LDFLAGS)
tools_krb5_sync_LDADD = portable/libportable.la util/libutil.la \
$(KADM5SRV_LIBS) $(LDAP_LIBS) $(KRB5_LIBS)
-DBUILD='"$(abs_top_builddir)/tests"'
tests_tap_libtap_a_CPPFLAGS = -I$(abs_top_srcdir)/tests
tests_tap_libtap_a_SOURCES = tests/tap/basic.c tests/tap/basic.h \
- tests/tap/macros.h tests/tap/messages.c tests/tap/messages.h \
- tests/tap/process.c tests/tap/process.h tests/tap/string.c \
- tests/tap/string.h
+ tests/tap/kerberos.c tests/tap/kerberos.h tests/tap/macros.h \
+ tests/tap/messages.c tests/tap/messages.h tests/tap/process.c \
+ tests/tap/process.h tests/tap/string.c tests/tap/string.h
# All of the test programs.
tests_plugin_heimdal_t_LDADD = tests/tap/libtap.a portable/libportable.la \
- $(DL_LIBS)
+ $(KRB5_LIBS) $(DL_LIBS)
tests_plugin_mit_t_LDADD = tests/tap/libtap.a portable/libportable.la \
- $(DL_LIBS)
+ $(KRB5_LIBS) $(DL_LIBS)
tests_plugin_queue_only_t_SOURCES = tests/plugin/queue-only-t.c \
$(plugin_krb5_sync_la_SOURCES)
tests_plugin_queue_only_t_CPPFLAGS = $(KADM5SRV_CPPFLAGS) $(LDAP_CPPFLAGS) \
$(AM_CPPFLAGS)
tests_plugin_queue_only_t_LDFLAGS = $(KADM5SRV_LDFLAGS) $(LDAP_LDFLAGS) \
- $(KRB5_LDFLAGS)
+ $(AM_LDFLAGS)
tests_plugin_queue_only_t_LDADD = tests/tap/libtap.a portable/libportable.la \
$(KADM5SRV_LIBS) $(LDAP_LIBS) $(KRB5_LIBS)
tests_plugin_queuing_t_SOURCES = tests/plugin/queuing-t.c \
tests_plugin_queuing_t_CPPFLAGS = $(KADM5SRV_CPPFLAGS) $(LDAP_CPPFLAGS) \
$(AM_CPPFLAGS)
tests_plugin_queuing_t_LDFLAGS = $(KADM5SRV_LDFLAGS) $(LDAP_LDFLAGS) \
- $(KRB5_LDFLAGS)
+ $(AM_LDFLAGS)
tests_plugin_queuing_t_LDADD = tests/tap/libtap.a portable/libportable.la \
$(KADM5SRV_LIBS) $(LDAP_LIBS) $(KRB5_LIBS)
tests_portable_asprintf_t_SOURCES = tests/portable/asprintf-t.c \
tests_portable_snprintf_t_SOURCES = tests/portable/snprintf-t.c \
tests/portable/snprintf.c
tests_portable_snprintf_t_LDADD = tests/tap/libtap.a portable/libportable.la
-tests_util_messages_krb5_t_LDFLAGS = $(KRB5_LDFLAGS)
tests_util_messages_krb5_t_LDADD = tests/tap/libtap.a util/libutil.la \
portable/libportable.la $(KRB5_LIBS)
tests_util_messages_t_LDADD = tests/tap/libtap.a util/libutil.la \
RRA_LIB_KRB5
RRA_LIB_KRB5_SWITCH
AC_CHECK_HEADERS([kadm5/kadm5_err.h krb5/kadm5_hook_plugin.h])
-AC_CHECK_FUNCS([krb5_get_init_creds_opt_alloc \
+AC_CHECK_FUNCS([krb5_free_default_realm \
+ krb5_free_string \
+ krb5_get_init_creds_opt_alloc \
krb5_get_init_creds_opt_set_default_flags \
krb5_principal_get_comp_string \
krb5_principal_get_num_comp \
dnl RRA_LIB_KRB5_SWITCH to set CPPFLAGS, LDFLAGS, and LIBS to include the
dnl Kerberos libraries, saving the current values first, and
dnl RRA_LIB_KRB5_RESTORE to restore those settings to before the last
-dnl RRA_LIB_KRB5_SWITCH. HAVE_KERBEROS will always be defined if RRA_LIB_KRB5
-dnl is used.
+dnl RRA_LIB_KRB5_SWITCH. HAVE_KRB5 will always be defined if RRA_LIB_KRB5 is
+dnl used.
dnl
dnl If KRB5_CPPFLAGS, KRB5_LDFLAGS, or KRB5_LIBS are set before calling these
dnl macros, their values will be added to whatever the macros discover.
dnl
dnl Provides the RRA_LIB_KRB5_OPTIONAL macro, which should be used if Kerberos
-dnl support is optional. This macro will still always set the substitution
-dnl variables, but they'll be empty unless --with-krb5 is given. Also,
-dnl HAVE_KERBEROS will be defined if --with-krb5 is given and
-dnl $rra_use_kerberos will be set to "true".
+dnl support is optional. In this case, Kerberos libraries are mandatory if
+dnl --with-krb5 is given, and will not be probed for if --without-krb5 is
+dnl given. Otherwise, they'll be probed for but will not be required.
+dnl Defines HAVE_KRB5 and sets rra_use_KRB5 to true if the libraries are
+dnl found. The substitution variables will always be set, but they will be
+dnl empty unless Kerberos libraries are found and the user did not disable
+dnl Kerberos support.
dnl
dnl Sets the Automake conditional KRB5_USES_COM_ERR saying whether we use
dnl com_err, since if we're also linking with AFS libraries, we may have to
dnl change library ordering in that case.
dnl
-dnl Depends on RRA_ENABLE_REDUCED_DEPENDS and RRA_SET_LDFLAGS.
+dnl Depends on RRA_KRB5_CONFIG, RRA_ENABLE_REDUCED_DEPENDS, and
+dnl RRA_SET_LDFLAGS.
dnl
dnl Also provides RRA_FUNC_KRB5_GET_INIT_CREDS_OPT_FREE_ARGS, which checks
dnl whether krb5_get_init_creds_opt_free takes one argument or two. Defines
dnl package, available at <http://www.eyrie.org/~eagle/software/rra-c-util/>.
dnl
dnl Written by Russ Allbery <eagle@eyrie.org>
-dnl Copyright 2005, 2006, 2007, 2008, 2009, 2010, 2011
+dnl Copyright 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2013
dnl The Board of Trustees of the Leland Stanford Junior University
dnl
dnl This file is free software; the authors give unlimited permission to copy
dnl and/or distribute it, with or without modifications, as long as this
dnl notice is preserved.
+dnl Ignore Automake conditionals if not using Automake.
+m4_define_default([AM_CONDITIONAL], [:])
+
dnl Headers to include when probing for Kerberos library properties.
AC_DEFUN([RRA_INCLUDES_KRB5], [[
#if HAVE_KRB5_H
[AS_IF([test x"$rra_krb5_root" != x/usr],
[KRB5_CPPFLAGS="-I${rra_krb5_root}/include"])])])])
+dnl Check for a header using a file existence check rather than using
+dnl AC_CHECK_HEADERS. This is used if there were arguments to configure
+dnl specifying the Kerberos header path, since we may have one header in the
+dnl default include path and another under our explicitly-configured Kerberos
+dnl location.
+AC_DEFUN([_RRA_LIB_KRB5_CHECK_HEADER],
+[AC_MSG_CHECKING([for $1])
+ AS_IF([test -f "${rra_krb5_incroot}/$1"],
+ [AC_DEFINE_UNQUOTED(AS_TR_CPP([HAVE_$1]), [1],
+ [Define to 1 if you have the <$1> header file.])
+ AC_MSG_RESULT([yes])],
+ [AC_MSG_RESULT([no])])])
+
dnl Does the appropriate library checks for reduced-dependency Kerberos
dnl linkage. The single argument, if true, says to fail if Kerberos could not
dnl be found.
[AS_IF([test x"$1" = xtrue],
[AC_MSG_ERROR([cannot find usable Kerberos library])])])
LIBS="$KRB5_LIBS $LIBS"
- AC_CHECK_HEADERS([krb5.h krb5/krb5.h])
+ AS_IF([test x"$rra_krb5_incroot" = x],
+ [AC_CHECK_HEADERS([krb5.h krb5/krb5.h])],
+ [_RRA_LIB_KRB5_CHECK_HEADER([krb5.h])
+ _RRA_LIB_KRB5_CHECK_HEADER([krb5/krb5.h])])
AC_CHECK_FUNCS([krb5_get_error_message],
[AC_CHECK_FUNCS([krb5_free_error_message])],
[AC_CHECK_FUNCS([krb5_get_error_string], [],
[RRA_INCLUDES_KRB5])],
[AC_CHECK_LIB([com_err], [com_err],
[KRB5_LIBS="$KRB5_LIBS -lcom_err"],
- [AC_MSG_ERROR([cannot find usable com_err library])])
+ [AS_IF([test x"$1" = xtrue],
+ [AC_MSG_ERROR([cannot find usable com_err library])],
+ [KRB5_LIBS=""])])
AC_CHECK_HEADERS([et/com_err.h])])])])])
RRA_LIB_KRB5_RESTORE])
[AC_CHECK_LIB([nsl], [socket], [LIBS="-lnsl -lsocket $LIBS"], [],
[-lsocket])])
AC_SEARCH_LIBS([crypt], [crypt])
- AC_SEARCH_LIBS([rk_simple_execve], [roken])
+ AC_SEARCH_LIBS([roken_concat], [roken])
rra_krb5_extra="$LIBS"
LIBS="$rra_krb5_save_LIBS"
AC_CHECK_LIB([krb5], [krb5_init_context],
[$rra_krb5_extra])],
[-lasn1 -lcom_err -lcrypto $rra_krb5_extra])
LIBS="$KRB5_LIBS $LIBS"
- AC_CHECK_HEADERS([krb5.h krb5/krb5.h])
+ AS_IF([test x"$rra_krb5_incroot" = x],
+ [AC_CHECK_HEADERS([krb5.h krb5/krb5.h])],
+ [_RRA_LIB_KRB5_CHECK_HEADER([krb5.h])
+ _RRA_LIB_KRB5_CHECK_HEADER([krb5/krb5.h])])
AC_CHECK_FUNCS([krb5_get_error_message],
[AC_CHECK_FUNCS([krb5_free_error_message])],
[AC_CHECK_FUNCS([krb5_get_error_string], [],
[RRA_KRB5_CONFIG([${rra_krb5_root}], [krb5], [KRB5],
[_RRA_LIB_KRB5_CHECK([$1])
RRA_LIB_KRB5_SWITCH
- AC_CHECK_HEADERS([krb5.h krb5/krb5.h])
+ AS_IF([test x"$rra_krb5_incroot" = x],
+ [AC_CHECK_HEADERS([krb5.h krb5/krb5.h])],
+ [_RRA_LIB_KRB5_CHECK_HEADER([krb5.h])
+ _RRA_LIB_KRB5_CHECK_HEADER([krb5/krb5.h])])
AC_CHECK_FUNCS([krb5_get_error_message],
[AC_CHECK_FUNCS([krb5_free_error_message])],
[AC_CHECK_FUNCS([krb5_get_error_string], [],
dnl The core of the library checking, shared between RRA_LIB_KRB5 and
dnl RRA_LIB_KRB5_OPTIONAL. The single argument, if "true", says to fail if
-dnl Kerberos could not be found.
+dnl Kerberos could not be found. Set up rra_krb5_incroot for later header
+dnl checking.
AC_DEFUN([_RRA_LIB_KRB5_INTERNAL],
[AC_REQUIRE([RRA_ENABLE_REDUCED_DEPENDS])
+ rra_krb5_incroot=
+ AS_IF([test x"$rra_krb5_includedir" != x],
+ [rra_krb5_incroot="$rra_krb5_includedir"],
+ [AS_IF([test x"$rra_krb5_root" != x],
+ [rra_krb5_incroot="${rra_krb5_root}/include"])])
AS_IF([test x"$rra_reduced_depends" = xtrue],
[_RRA_LIB_KRB5_PATHS
_RRA_LIB_KRB5_REDUCED([$1])],
[_RRA_LIB_KRB5_PATHS
_RRA_LIB_KRB5_MANUAL([$1])])])
rra_krb5_uses_com_err=false
- AS_CASE([$LIBS], [*-lcom_err*], [rra_krb5_uses_com_err=true])
- AM_CONDITIONAL([KRB5_USES_COM_ERR], [test x"$rra_krb5_uses_com_err" = xtrue])])
+ AS_CASE([$KRB5_LIBS], [*-lcom_err*], [rra_krb5_uses_com_err=true])
+ AM_CONDITIONAL([KRB5_USES_COM_ERR],
+ [test x"$rra_krb5_uses_com_err" = xtrue])])
dnl The main macro for packages with mandatory Kerberos support.
AC_DEFUN([RRA_LIB_KRB5],
[rra_krb5_root=
rra_krb5_libdir=
rra_krb5_includedir=
- rra_use_kerberos=true
+ rra_use_KRB5=true
AC_SUBST([KRB5_CPPFLAGS])
AC_SUBST([KRB5_LDFLAGS])
AC_SUBST([KRB5_LIBS])
[AS_IF([test x"$withval" != xyes && test x"$withval" != xno],
[rra_krb5_libdir="$withval"])])
_RRA_LIB_KRB5_INTERNAL([true])
- AC_DEFINE([HAVE_KERBEROS], 1, [Define to enable Kerberos features.])])
+ AC_DEFINE([HAVE_KRB5], 1, [Define to enable Kerberos features.])])
dnl The main macro for packages with optional Kerberos support.
AC_DEFUN([RRA_LIB_KRB5_OPTIONAL],
[rra_krb5_root=
rra_krb5_libdir=
rra_krb5_includedir=
- rra_use_kerberos=
+ rra_use_KRB5=
AC_SUBST([KRB5_CPPFLAGS])
AC_SUBST([KRB5_LDFLAGS])
AC_SUBST([KRB5_LIBS])
[AS_HELP_STRING([--with-krb5@<:@=DIR@:>@],
[Location of Kerberos headers and libraries])],
[AS_IF([test x"$withval" = xno],
- [rra_use_kerberos=false],
+ [rra_use_KRB5=false],
[AS_IF([test x"$withval" != xyes], [rra_krb5_root="$withval"])
- rra_use_kerberos=true])])
+ rra_use_KRB5=true])])
AC_ARG_WITH([krb5-include],
[AS_HELP_STRING([--with-krb5-include=DIR],
[Location of Kerberos headers])],
[AS_IF([test x"$withval" != xyes && test x"$withval" != xno],
[rra_krb5_libdir="$withval"])])
- AS_IF([test x"$rra_use_kerberos" != xfalse],
- [AS_IF([test x"$rra_use_kerberos" = xtrue],
+ AS_IF([test x"$rra_use_KRB5" != xfalse],
+ [AS_IF([test x"$rra_use_KRB5" = xtrue],
[_RRA_LIB_KRB5_INTERNAL([true])],
[_RRA_LIB_KRB5_INTERNAL([false])])],
[AM_CONDITIONAL([KRB5_USES_COM_ERR], [false])])
AS_IF([test x"$KRB5_LIBS" != x],
- [AC_DEFINE([HAVE_KERBEROS], 1, [Define to enable Kerberos features.])])])
+ [rra_use_KRB5=true
+ AC_DEFINE([HAVE_KRB5], 1, [Define to enable Kerberos features.])])])
dnl Source used by RRA_FUNC_KRB5_GET_INIT_CREDS_OPT_FREE_ARGS.
AC_DEFUN([_RRA_FUNC_KRB5_OPT_FREE_ARGS_SOURCE], [RRA_INCLUDES_KRB5] [[
--- /dev/null
+#!/bin/sh
+#
+# Generate a krb5.conf file with an [appdefault] krb5-strength section that
+# contains all the key/value pairs given on the command line. This script is
+# used by C tests to set up the environment.
+#
+# Written by Russ Allbery <rra@stanford.edu>
+# Copyright 2009, 2013
+# The Board of Trustees of the Leland Stanford Junior University
+#
+# See LICENSE for licensing terms.
+
+set -e
+
+# Command-line arguments are the source krb5.conf template, the directory into
+# which to write the resulting krb5.conf file, and then any number of key and
+# value pairs to put into krb5.conf.
+source="$1"
+tmpdir="$2"
+if [ -z "$tmpdir" ] ; then
+ echo 'Syntax: make-krb5-conf <source> <tmpdir> <key> <value> ...' >&2
+ exit 1
+fi
+shift
+shift
+
+# Copy over the template and ensure it's writable.
+cp "$source" "$tmpdir"/krb5.conf
+chmod 644 "$tmpdir"/krb5.conf
+
+# Add the appdefaults section.
+cat <<EOF >>"$tmpdir"/krb5.conf
+
+[appdefaults]
+ krb5-strength = {
+EOF
+
+# Add the key-value pairs.
+while [ -n "$1" ] ; do
+ echo " $1 = $2" >>"$tmpdir"/krb5.conf
+ shift
+ shift
+done
+
+# End the appdefaults section.
+echo ' }' >>"$tmpdir"/krb5.conf
+
+# Done.
+exit 0
/*
* Tests for the Heimdal module API.
*
+ * This just checks that we can call all of the functions and that they return
+ * appropriate error messages for a non-existent queue. We don't try to do
+ * any end-to-end testing of the functionality.
+ *
* Written by Russ Allbery <eagle@eyrie.org>
* Copyright 2012, 2013
* The Board of Trustees of the Leland Stanford Junior University
*/
#include <config.h>
+#include <portable/kadmin.h>
#include <portable/krb5.h>
#include <portable/system.h>
#include <dlfcn.h>
#include <errno.h>
-#include <kadm5/admin.h>
-#ifdef HAVE_KADM5_KADM5_ERR_H
-# include <kadm5/kadm5_err.h>
-#endif
#include <tests/tap/basic.h>
+#include <tests/tap/kerberos.h>
#include <tests/tap/string.h>
/*
int
main(void)
{
- char *krb5conf, *env, *plugin;
+ char *path, *krb5_config, *plugin;
krb5_error_code code;
krb5_context ctx;
krb5_principal princ;
const char *message;
char *wanted;
- krb5conf = test_file_path("data/default.conf");
- if (krb5conf == NULL)
- bail("cannot find tests/data/krb5.conf");
- basprintf(&env, "KRB5_CONFIG=%s", krb5conf);
- if (putenv(env) < 0)
+ /* Set up the default krb5.conf file. */
+ path = test_file_path("data/default.conf");
+ if (path == NULL)
+ bail("cannot find data/default.conf in the test suite");
+ basprintf(&krb5_config, "KRB5_CONFIG=%s", path);
+ if (putenv(krb5_config) < 0)
sysbail("cannot set KRB5CCNAME");
+
+ /* Obtain a Kerberos context. */
code = krb5_init_context(&ctx);
if (code != 0)
- bail("cannot create Kerberos context (%d)", (int) code);
+ bail_krb5(ctx, code, "cannot initialize Kerberos context");
+
+ /* Parse a test principal into a krb5_principal structure. */
code = krb5_parse_name(ctx, "test@EXAMPLE.COM", &princ);
if (code != 0)
- bail("cannot parse principal: %s", krb5_get_error_message(ctx, code));
+ bail_krb5(ctx, code, "cannot parse principal test@EXAMPLE.COM");
/*
- * We assume that the plugin is available as:
- *
- * BUILD/../plugin/.libs/krb5_sync.so
- *
- * since we don't want to embed Libtool's libtldl just to run a test. If
- * that's not correct for the local platform, we skip this test.
+ * Load the module. We assume that the plugin is available as
+ * krb5_sync.so in a .libs directory since we don't want to embed
+ * Libtool's libtldl just to run a test. If that's not correct for the
+ * local platform, we skip this test.
*/
plugin = test_file_path("../plugin/.libs/krb5_sync.so");
if (plugin == NULL)
skip_all("unknown plugin naming scheme");
-
- plan(15);
-
- /* Load the module and find the correct symbol. */
handle = dlopen(plugin, RTLD_NOW);
if (handle == NULL)
- diag("dlopen of %s failed: %s", plugin, dlerror());
- ok(handle != NULL, "dlopen succeeds");
- if (handle == NULL)
- ok(false, "dlsym succeeds");
- else {
- hook = dlsym(handle, "kadm5_hook_v0");
- ok(hook != NULL, "dlsym succeeds");
- }
+ bail("cannot dlopen %s: %s", plugin, dlerror());
+ test_file_path_free(plugin);
- /* Check metadata. */
+ /* Find the dispatch table and do a basic sanity check. */
+ hook = dlsym(handle, "kadm5_hook_v0");
if (hook == NULL)
- ok_block(3, false, "No symbol in plugin");
- else {
- is_string("krb5-sync", hook->name, "Correct name");
- is_int(KADM5_HOOK_VERSION_V0, hook->version, "Correct version");
- is_string("Russ Allbery", hook->vendor, "Correct vendor");
- }
+ bail("cannot get kadm5_hook_v0 symbol: %s", dlerror());
+ if (hook->init == NULL)
+ bail("no init function found in module");
+
+ /* No more skipping, so now we can report a plan. */
+ plan(13);
+
+ /* Verify the metadata. */
+ is_string("krb5-sync", hook->name, "Module name");
+ is_string("Russ Allbery", hook->vendor, "Module vendor");
+ is_int(KADM5_HOOK_VERSION_V0, hook->version, "Module version");
/*
- * Call the functions, all of which should fail with errors about queuing
- * since the queue doesn't exist. This verifies that the symbols are all
- * there and that the arguments are basically correct.
+ * Call init and chpass, which should fail with errors about queuing since
+ * the queue doesn't exist.
*/
- if (hook == NULL)
- ok_block(8, false, "No symbol in plugin");
- else {
- basprintf(&wanted, "cannot open lock file queue/.lock: %s",
- strerror(ENOENT));
- is_int(0, hook->init(ctx, &config), "init");
- ok(config != NULL, "...and config is not NULL");
- code = hook->chpass(ctx, config, KADM5_HOOK_STAGE_PRECOMMIT, princ,
- "test");
- is_int(ENOENT, code, "chpass");
- message = krb5_get_error_message(ctx, code);
- is_string(wanted, message, "...with correct error message");
- krb5_free_error_message(ctx, message);
-
- /* Test chpass with a NULL password. */
- code = hook->chpass(ctx, config, KADM5_HOOK_STAGE_PRECOMMIT, princ,
- NULL);
- is_int(0, code, "chpass with NULL password");
-
- /*
- * Everything in the entity is ignored except the principal and
- * attributes, so don't bother to fake much up here.
- */
- memset(&entity, 0, sizeof(entity));
- entity.principal = princ;
- entity.attributes = KRB5_KDB_DISALLOW_ALL_TIX;
- code = hook->create(ctx, config, KADM5_HOOK_STAGE_PRECOMMIT, &entity,
- 0, "test");
- is_int(ENOENT, code, "create");
- message = krb5_get_error_message(ctx, code);
- is_string(wanted, message, "...with correct error message");
- krb5_free_error_message(ctx, message);
- code = hook->modify(ctx, config, KADM5_HOOK_STAGE_POSTCOMMIT, &entity,
- KADM5_ATTRIBUTES);
- is_int(ENOENT, code, "modify");
- message = krb5_get_error_message(ctx, code);
- is_string(wanted, message, "...with correct error message");
- krb5_free_error_message(ctx, message);
-
- /* Test create with a NULL password. */
- code = hook->create(ctx, config, KADM5_HOOK_STAGE_PRECOMMIT, &entity,
- 0, NULL);
- is_int(0, code, "create with NULL password");
-
- /* Close down the module. */
- hook->fini(ctx, config);
- free(wanted);
- }
+ basprintf(&wanted, "cannot open lock file queue/.lock: %s",
+ strerror(ENOENT));
+ is_int(0, hook->init(ctx, &config), "init");
+ ok(config != NULL, "...and config is not NULL");
+ code = hook->chpass(ctx, config, KADM5_HOOK_STAGE_PRECOMMIT, princ,
+ "test");
+ is_int(ENOENT, code, "chpass");
+ message = krb5_get_error_message(ctx, code);
+ is_string(wanted, message, "...with correct error message");
+ krb5_free_error_message(ctx, message);
+
+ /* Test chpass with a NULL password, which should do nothing. */
+ code = hook->chpass(ctx, config, KADM5_HOOK_STAGE_PRECOMMIT, princ, NULL);
+ is_int(0, code, "chpass with NULL password");
+
+ /*
+ * Set up an entry for creating an account. Everything in the entity is
+ * ignored except the principal and attributes, so don't bother to fake
+ * much up here.
+ */
+ memset(&entity, 0, sizeof(entity));
+ entity.principal = princ;
+ entity.attributes = KRB5_KDB_DISALLOW_ALL_TIX;
+
+ /* Test creation with no queue directory. */
+ code = hook->create(ctx, config, KADM5_HOOK_STAGE_PRECOMMIT, &entity, 0,
+ "test");
+ is_int(ENOENT, code, "create");
+ message = krb5_get_error_message(ctx, code);
+ is_string(wanted, message, "...with correct error message");
+ krb5_free_error_message(ctx, message);
+
+ /* Test disabling with no queue directory. */
+ code = hook->modify(ctx, config, KADM5_HOOK_STAGE_POSTCOMMIT, &entity,
+ KADM5_ATTRIBUTES);
+ is_int(ENOENT, code, "modify");
+ message = krb5_get_error_message(ctx, code);
+ is_string(wanted, message, "...with correct error message");
+ krb5_free_error_message(ctx, message);
+
+ /* Test creation with a NULL password, which should do nothing. */
+ code = hook->create(ctx, config, KADM5_HOOK_STAGE_PRECOMMIT, &entity, 0,
+ NULL);
+ is_int(0, code, "create with NULL password");
+
+ /* Close down the module. */
+ hook->fini(ctx, config);
+ free(wanted);
/* Clean up. */
krb5_free_principal(ctx, princ);
krb5_free_context(ctx);
- test_file_path_free(krb5conf);
- free(env);
+ test_file_path_free(path);
+ free(krb5_config);
return 0;
}
/*
* Tests for the MIT Kerberos module API.
*
+ * This just checks that we can call all of the functions and that they return
+ * appropriate error messages for a non-existent queue. We don't try to do
+ * any end-to-end testing of the functionality.
+ *
* Written by Russ Allbery <eagle@eyrie.org>
* Copyright 2012, 2013
* The Board of Trustees of the Leland Stanford Junior University
*/
#include <config.h>
+#include <portable/kadmin.h>
#include <portable/krb5.h>
#include <portable/system.h>
#include <dlfcn.h>
#include <errno.h>
-#include <kadm5/admin.h>
-#ifdef HAVE_KADM5_KADM5_ERR_H
-# include <kadm5/kadm5_err.h>
-#endif
#ifdef HAVE_KRB5_KADM5_HOOK_PLUGIN_H
# include <krb5/kadm5_hook_plugin.h>
#endif
#include <tests/tap/basic.h>
+#include <tests/tap/kerberos.h>
#include <tests/tap/string.h>
int
main(void)
{
- char *krb5conf, *env, *plugin;
+ char *path, *krb5_config, *plugin;
krb5_error_code code;
krb5_context ctx;
krb5_principal princ;
void *handle = NULL;
- krb5_error_code (*callback)(krb5_context, int, int, krb5_plugin_vtable);
- kadm5_hook_vftable_1 hook;
+ krb5_error_code (*init)(krb5_context, int, int, krb5_plugin_vtable);
+ kadm5_hook_vftable_1 vtable;
kadm5_hook_modinfo *data = NULL;
kadm5_principal_ent_rec entity;
const char *message;
char *wanted;
- krb5conf = test_file_path("data/default.conf");
- if (krb5conf == NULL)
- bail("cannot find tests/data/krb5.conf");
- basprintf(&env, "KRB5_CONFIG=%s", krb5conf);
- if (putenv(env) < 0)
+ /* Set up the default krb5.conf file. */
+ path = test_file_path("data/default.conf");
+ if (path == NULL)
+ bail("cannot find data/default.conf in the test suite");
+ basprintf(&krb5_config, "KRB5_CONFIG=%s", path);
+ if (putenv(krb5_config) < 0)
sysbail("cannot set KRB5CCNAME");
+
+ /* Obtain a Kerberos context. */
code = krb5_init_context(&ctx);
if (code != 0)
- bail("cannot create Kerberos context (%d)", (int) code);
+ bail_krb5(ctx, code, "cannot initialize Kerberos context");
+
+ /* Parse a test principal into a krb5_principal structure. */
code = krb5_parse_name(ctx, "test@EXAMPLE.COM", &princ);
if (code != 0)
- bail("cannot parse principal: %s", krb5_get_error_message(ctx, code));
+ bail_krb5(ctx, code, "cannot parse principal test@EXAMPLE.COM");
/*
- * We assume that the plugin is available as:
- *
- * BUILD/../plugin/.libs/krb5_sync.so
- *
- * since we don't want to embed Libtool's libtldl just to run a test. If
- * that's not correct for the local platform, we skip this test.
+ * Load the module. We assume that the plugin is available as
+ * krb5_sync.so in a .libs directory since we don't want to embed
+ * Libtool's libtldl just to run a test. If that's not correct for the
+ * local platform, we skip this test.
*/
plugin = test_file_path("../plugin/.libs/krb5_sync.so");
if (plugin == NULL)
skip_all("unknown plugin naming scheme");
-
- plan(14);
-
- /* Load the module and find the correct symbol. */
handle = dlopen(plugin, RTLD_NOW);
if (handle == NULL)
- diag("dlopen of %s failed: %s", plugin, dlerror());
- ok(handle != NULL, "dlopen succeeds");
- if (handle == NULL)
- ok(false, "dlsym succeeds");
- else {
- callback = dlsym(handle, "kadm5_hook_krb5_sync_initvt");
- ok(callback != NULL, "dlsym succeeds");
- }
-
- /* Call the callback function and get the vtable. */
- memset(&hook, 0, sizeof(hook));
- if (handle == NULL || callback == NULL)
- ok(false, "callback succeeds");
- else {
- code = callback(ctx, 1, 0, (krb5_plugin_vtable) &hook);
- if (code != 0)
- diag("kadm5_hook_krb5_sync_initvt failed: %s",
- krb5_get_error_message(ctx, code));
- ok(code == 0, "kadm5_hook_krb5_sync_initvt succeeds");
- }
-
- /* Check metadata. */
- is_string("krb5_sync", hook.name, "Hook name is correct");
+ bail("cannot dlopen %s: %s", plugin, dlerror());
+ test_file_path_free(plugin);
+
+ /* Find the entry point function. */
+ init = dlsym(handle, "kadm5_hook_krb5_sync_initvt");
+ if (init == NULL)
+ bail("cannot get kadm5_hook_krb5_sync_initvt symbol: %s", dlerror());
+
+ /* No more skipping, so now we can report a plan. */
+ plan(12);
+
+ /* Test for correct results when requesting the wrong API version. */
+ code = init(ctx, 2, 0, (krb5_plugin_vtable) &vtable);
+ is_int(code, KRB5_PLUGIN_VER_NOTSUPP,
+ "Correct status for bad major API version");
+
+ /* Call that function properly to get the vtable. */
+ memset(&vtable, 0, sizeof(vtable));
+ code = init(ctx, 1, 0, (krb5_plugin_vtable) &vtable);
+ if (code != 0)
+ bail_krb5(ctx, code, "cannot obtain module vtable");
+
+ /* Check that all of the expected vtable entries are present. */
+ if (vtable.init == NULL || vtable.fini == NULL || vtable.chpass == NULL
+ || vtable.create == NULL || vtable.modify == NULL)
+ bail("missing function in module vtable");
+
+ /* Verify the metadata. */
+ is_string("krb5_sync", vtable.name, "Hook name is correct");
+
+ /*
+ * Call the chpass function, which should fail with errors about queuing
+ * since the queue doesn't exist.
+ */
+ basprintf(&wanted, "cannot open lock file queue/.lock: %s",
+ strerror(ENOENT));
+ is_int(0, vtable.init(ctx, &data), "init");
+ ok(data != NULL, "...and data is not NULL");
+ code = vtable.chpass(ctx, data, KADM5_HOOK_STAGE_PRECOMMIT, princ, false,
+ 0, NULL, "test");
+ is_int(ENOENT, code, "chpass");
+ message = krb5_get_error_message(ctx, code);
+ is_string(wanted, message, "...with correct error message");
+ krb5_free_error_message(ctx, message);
+
+ /* Test chpass with a NULL password, which should do nothing. */
+ code = vtable.chpass(ctx, data, KADM5_HOOK_STAGE_PRECOMMIT, princ, false,
+ 0, NULL, NULL);
+ is_int(0, code, "chpass with NULL password");
/*
- * Call the functions, all of which should fail with errors about queuing
- * since the queue doesn't exist. This verifies that the symbols are all
- * there and that the arguments are basically correct.
+ * Set up an entry for creating an account. Everything in the entity is
+ * ignored except the principal and attributes, so don't bother to fake
+ * much up here.
*/
- if (hook.name == NULL)
- ok_block(8, false, "No vtable");
- else {
- basprintf(&wanted, "cannot open lock file queue/.lock: %s",
- strerror(ENOENT));
- is_int(0, hook.init(ctx, &data), "init");
- ok(data != NULL, "...and data is not NULL");
- code = hook.chpass(ctx, data, KADM5_HOOK_STAGE_PRECOMMIT, princ,
- false, 0, NULL, "test");
- is_int(ENOENT, code, "chpass");
- message = krb5_get_error_message(ctx, code);
- is_string(wanted, message, "...with correct error message");
- krb5_free_error_message(ctx, message);
-
- /* Test chpass with a NULL password. */
- code = hook.chpass(ctx, data, KADM5_HOOK_STAGE_PRECOMMIT, princ,
- false, 0, NULL, NULL);
- is_int(0, code, "chpass with NULL password");
-
- /*
- * Everything in the entity is ignored except the principal and
- * attributes, so don't bother to fake much up here.
- */
- memset(&entity, 0, sizeof(entity));
- entity.principal = princ;
- entity.attributes = KRB5_KDB_DISALLOW_ALL_TIX;
- code = hook.create(ctx, data, KADM5_HOOK_STAGE_PRECOMMIT, &entity,
- 0, 0, NULL, "test");
- is_int(ENOENT, code, "create");
- message = krb5_get_error_message(ctx, code);
- is_string(wanted, message, "...with correct error message");
- krb5_free_error_message(ctx, message);
- code = hook.modify(ctx, data, KADM5_HOOK_STAGE_POSTCOMMIT, &entity,
- KADM5_ATTRIBUTES);
- is_int(ENOENT, code, "modify");
- message = krb5_get_error_message(ctx, code);
- is_string(wanted, message, "...with correct error message");
- krb5_free_error_message(ctx, message);
-
- /* Test create with a NULL password. */
- code = hook.create(ctx, data, KADM5_HOOK_STAGE_PRECOMMIT, &entity, 0,
- 0, NULL, NULL);
- is_int(0, code, "create with NUL password");
-
- /* Close down the module. */
- hook.fini(ctx, data);
- free(wanted);
- }
+ memset(&entity, 0, sizeof(entity));
+ entity.principal = princ;
+ entity.attributes = KRB5_KDB_DISALLOW_ALL_TIX;
+
+ /* Test creation with no queue directory. */
+ code = vtable.create(ctx, data, KADM5_HOOK_STAGE_PRECOMMIT, &entity, 0, 0,
+ NULL, "test");
+ is_int(ENOENT, code, "create");
+ message = krb5_get_error_message(ctx, code);
+ is_string(wanted, message, "...with correct error message");
+ krb5_free_error_message(ctx, message);
+
+ /* Test disabling with no queue directory. */
+ code = vtable.modify(ctx, data, KADM5_HOOK_STAGE_POSTCOMMIT, &entity,
+ KADM5_ATTRIBUTES);
+ is_int(ENOENT, code, "modify");
+ message = krb5_get_error_message(ctx, code);
+ is_string(wanted, message, "...with correct error message");
+ krb5_free_error_message(ctx, message);
+
+ /* Test creation with a NULL password, which should do nothing. */
+ code = vtable.create(ctx, data, KADM5_HOOK_STAGE_PRECOMMIT, &entity, 0, 0,
+ NULL, NULL);
+ is_int(0, code, "create with NULL password");
+
+ /* Close down the module. */
+ vtable.fini(ctx, data);
+ free(wanted);
/* Clean up. */
krb5_free_principal(ctx, princ);
krb5_free_context(ctx);
- test_file_path_free(krb5conf);
- free(env);
+ test_file_path_free(path);
+ free(krb5_config);
return 0;
}
--- /dev/null
+/*
+ * Utility functions for tests that use Kerberos.
+ *
+ * The core function is kerberos_setup, which loads Kerberos test
+ * configuration and returns a struct of information. It also supports
+ * obtaining initial tickets from the configured keytab and setting up
+ * KRB5CCNAME and KRB5_KTNAME if a Kerberos keytab is present. Also included
+ * are utility functions for setting up a krb5.conf file and reporting
+ * Kerberos errors or warnings during testing.
+ *
+ * Some of the functionality here is only available if the Kerberos libraries
+ * are available.
+ *
+ * The canonical version of this file is maintained in the rra-c-util package,
+ * which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>.
+ *
+ * Written by Russ Allbery <eagle@eyrie.org>
+ * Copyright 2006, 2007, 2009, 2010, 2011, 2012, 2013
+ * The Board of Trustees of the Leland Stanford Junior University
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <config.h>
+#ifdef HAVE_KRB5
+# include <portable/krb5.h>
+#endif
+#include <portable/system.h>
+
+#include <sys/stat.h>
+
+#include <tests/tap/basic.h>
+#include <tests/tap/kerberos.h>
+#include <tests/tap/process.h>
+#include <tests/tap/string.h>
+
+/*
+ * Disable the requirement that format strings be literals, since it's easier
+ * to handle the possible patterns for kinit commands as an array.
+ */
+#pragma GCC diagnostic ignored "-Wformat-nonliteral"
+
+
+/*
+ * These variables hold the allocated configuration struct, the environment to
+ * point to a different Kerberos ticket cache, keytab, and configuration file,
+ * and the temporary directories used. We store them so that we can free them
+ * on exit for cleaner valgrind output, making it easier to find real memory
+ * leaks in the tested programs.
+ */
+static struct kerberos_config *config = NULL;
+static char *krb5ccname = NULL;
+static char *krb5_ktname = NULL;
+static char *krb5_config = NULL;
+static char *tmpdir_ticket = NULL;
+static char *tmpdir_conf = NULL;
+
+
+/*
+ * Obtain Kerberos tickets and fill in the principal config entry.
+ *
+ * There are two implementations of this function, one if we have native
+ * Kerberos libraries available and one if we don't. Uses keytab to obtain
+ * credentials, and fills in the cache member of the provided config struct.
+ */
+#ifdef HAVE_KRB5
+
+static void
+kerberos_kinit(void)
+{
+ char *name, *krbtgt;
+ krb5_error_code code;
+ krb5_context ctx;
+ krb5_ccache ccache;
+ krb5_principal kprinc;
+ krb5_keytab keytab;
+ krb5_get_init_creds_opt *opts;
+ krb5_creds creds;
+ const char *realm;
+
+ /*
+ * Determine the principal corresponding to that keytab. We copy the
+ * memory to ensure that it's allocated in the right memory domain on
+ * systems where that may matter (like Windows).
+ */
+ code = krb5_init_context(&ctx);
+ if (code != 0)
+ bail_krb5(ctx, code, "error initializing Kerberos");
+ kprinc = kerberos_keytab_principal(ctx, config->keytab);
+ code = krb5_unparse_name(ctx, kprinc, &name);
+ if (code != 0)
+ bail_krb5(ctx, code, "error unparsing name");
+ krb5_free_principal(ctx, kprinc);
+ config->principal = bstrdup(name);
+ krb5_free_unparsed_name(ctx, name);
+
+ /* Now do the Kerberos initialization. */
+ code = krb5_cc_default(ctx, &ccache);
+ if (code != 0)
+ bail_krb5(ctx, code, "error setting ticket cache");
+ code = krb5_parse_name(ctx, config->principal, &kprinc);
+ if (code != 0)
+ bail_krb5(ctx, code, "error parsing principal %s", config->principal);
+ realm = krb5_principal_get_realm(ctx, kprinc);
+ basprintf(&krbtgt, "krbtgt/%s@%s", realm, realm);
+ code = krb5_kt_resolve(ctx, config->keytab, &keytab);
+ if (code != 0)
+ bail_krb5(ctx, code, "cannot open keytab %s", config->keytab);
+ code = krb5_get_init_creds_opt_alloc(ctx, &opts);
+ if (code != 0)
+ bail_krb5(ctx, code, "cannot allocate credential options");
+ krb5_get_init_creds_opt_set_default_flags(ctx, NULL, realm, opts);
+ krb5_get_init_creds_opt_set_forwardable(opts, 0);
+ krb5_get_init_creds_opt_set_proxiable(opts, 0);
+ code = krb5_get_init_creds_keytab(ctx, &creds, kprinc, keytab, 0, krbtgt,
+ opts);
+ if (code != 0)
+ bail_krb5(ctx, code, "cannot get Kerberos tickets");
+ code = krb5_cc_initialize(ctx, ccache, kprinc);
+ if (code != 0)
+ bail_krb5(ctx, code, "error initializing ticket cache");
+ code = krb5_cc_store_cred(ctx, ccache, &creds);
+ if (code != 0)
+ bail_krb5(ctx, code, "error storing credentials");
+ krb5_cc_close(ctx, ccache);
+ krb5_free_cred_contents(ctx, &creds);
+ krb5_kt_close(ctx, keytab);
+ krb5_free_principal(ctx, kprinc);
+ krb5_get_init_creds_opt_free(ctx, opts);
+ krb5_free_context(ctx);
+ free(krbtgt);
+}
+
+#else /* !HAVE_KRB5 */
+
+static void
+kerberos_kinit(void)
+{
+ static const char * const format[] = {
+ "kinit --no-afslog -k -t %s %s >/dev/null 2>&1 </dev/null",
+ "kinit -k -t %s %s >/dev/null 2>&1 </dev/null",
+ "kinit -t %s %s >/dev/null 2>&1 </dev/null",
+ "kinit -k -K %s %s >/dev/null 2>&1 </dev/null"
+ };
+ FILE *file;
+ char *path;
+ char principal[BUFSIZ], *command;
+ size_t i;
+ int status;
+
+ /* Read the principal corresponding to the keytab. */
+ path = test_file_path("config/principal");
+ if (path == NULL) {
+ test_file_path_free(config->keytab);
+ config->keytab = NULL;
+ return;
+ }
+ file = fopen(path, "r");
+ if (file == NULL) {
+ test_file_path_free(path);
+ return;
+ }
+ test_file_path_free(path);
+ if (fgets(principal, sizeof(principal), file) == NULL)
+ bail("cannot read %s", path);
+ fclose(file);
+ if (principal[strlen(principal) - 1] != '\n')
+ bail("no newline in %s", path);
+ principal[strlen(principal) - 1] = '\0';
+ config->principal = bstrdup(principal);
+
+ /* Now do the Kerberos initialization. */
+ for (i = 0; i < ARRAY_SIZE(format); i++) {
+ basprintf(&command, format[i], config->keytab, principal);
+ status = system(command);
+ free(command);
+ if (status != -1 && WEXITSTATUS(status) == 0)
+ break;
+ }
+ if (status == -1 || WEXITSTATUS(status) != 0)
+ bail("cannot get Kerberos tickets");
+}
+
+#endif /* !HAVE_KRB5 */
+
+
+/*
+ * Clean up at the end of a test. This removes the ticket cache and resets
+ * and frees the memory allocated for the environment variables so that
+ * valgrind output on test suites is cleaner.
+ */
+void
+kerberos_cleanup(void)
+{
+ char *path;
+
+ if (tmpdir_ticket != NULL) {
+ basprintf(&path, "%s/krb5cc_test", tmpdir_ticket);
+ unlink(path);
+ free(path);
+ test_tmpdir_free(tmpdir_ticket);
+ tmpdir_ticket = NULL;
+ }
+ if (config != NULL) {
+ if (config->keytab != NULL) {
+ test_file_path_free(config->keytab);
+ free(config->principal);
+ free(config->cache);
+ }
+ if (config->userprinc != NULL) {
+ free(config->userprinc);
+ free(config->username);
+ free(config->password);
+ }
+ free(config);
+ config = NULL;
+ }
+ if (krb5ccname != NULL) {
+ putenv((char *) "KRB5CCNAME=");
+ free(krb5ccname);
+ krb5ccname = NULL;
+ }
+ if (krb5_ktname != NULL) {
+ putenv((char *) "KRB5_KTNAME=");
+ free(krb5_ktname);
+ krb5_ktname = NULL;
+ }
+}
+
+
+/*
+ * Obtain Kerberos tickets for the principal specified in config/principal
+ * using the keytab specified in config/keytab, both of which are presumed to
+ * be in tests in either the build or the source tree. Also sets KRB5_KTNAME
+ * and KRB5CCNAME.
+ *
+ * Returns the contents of config/principal in newly allocated memory or NULL
+ * if Kerberos tests are apparently not configured. If Kerberos tests are
+ * configured but something else fails, calls bail.
+ */
+struct kerberos_config *
+kerberos_setup(enum kerberos_needs needs)
+{
+ char *path;
+ char buffer[BUFSIZ];
+ FILE *file = NULL;
+
+ /* If we were called before, clean up after the previous run. */
+ if (config != NULL)
+ kerberos_cleanup();
+ config = bcalloc(1, sizeof(struct kerberos_config));
+
+ /*
+ * If we have a config/keytab file, set the KRB5CCNAME and KRB5_KTNAME
+ * environment variables and obtain initial tickets.
+ */
+ config->keytab = test_file_path("config/keytab");
+ if (config->keytab == NULL) {
+ if (needs == TAP_KRB_NEEDS_KEYTAB || needs == TAP_KRB_NEEDS_BOTH)
+ skip_all("Kerberos tests not configured");
+ } else {
+ tmpdir_ticket = test_tmpdir();
+ basprintf(&config->cache, "%s/krb5cc_test", tmpdir_ticket);
+ basprintf(&krb5ccname, "KRB5CCNAME=%s/krb5cc_test", tmpdir_ticket);
+ basprintf(&krb5_ktname, "KRB5_KTNAME=%s", config->keytab);
+ putenv(krb5ccname);
+ putenv(krb5_ktname);
+ kerberos_kinit();
+ }
+
+ /*
+ * If we have a config/password file, read it and fill out the relevant
+ * members of our config struct.
+ */
+ path = test_file_path("config/password");
+ if (path != NULL)
+ file = fopen(path, "r");
+ if (file == NULL) {
+ if (needs == TAP_KRB_NEEDS_PASSWORD || needs == TAP_KRB_NEEDS_BOTH)
+ skip_all("Kerberos tests not configured");
+ } else {
+ if (fgets(buffer, sizeof(buffer), file) == NULL)
+ bail("cannot read %s", path);
+ if (buffer[strlen(buffer) - 1] != '\n')
+ bail("no newline in %s", path);
+ buffer[strlen(buffer) - 1] = '\0';
+ config->userprinc = bstrdup(buffer);
+ if (fgets(buffer, sizeof(buffer), file) == NULL)
+ bail("cannot read password from %s", path);
+ fclose(file);
+ if (buffer[strlen(buffer) - 1] != '\n')
+ bail("password too long in %s", path);
+ buffer[strlen(buffer) - 1] = '\0';
+ config->password = bstrdup(buffer);
+
+ /*
+ * Strip the realm from the principal and set realm and username.
+ * This is not strictly correct; it doesn't cope with escaped @-signs
+ * or enterprise names.
+ */
+ config->username = bstrdup(config->userprinc);
+ config->realm = strchr(config->username, '@');
+ if (config->realm == NULL)
+ bail("test principal has no realm");
+ *config->realm = '\0';
+ config->realm++;
+ }
+ if (path != NULL)
+ test_file_path_free(path);
+
+ /*
+ * Register the cleanup function as an atexit handler so that the caller
+ * doesn't have to worry about cleanup.
+ */
+ if (atexit(kerberos_cleanup) != 0)
+ sysdiag("cannot register cleanup function");
+
+ /* Return the configuration. */
+ return config;
+}
+
+
+/*
+ * Clean up the krb5.conf file generated by kerberos_generate_conf and free
+ * the memory used to set the environment variable. This doesn't fail if the
+ * file and variable are already gone, allowing it to be harmlessly run
+ * multiple times.
+ *
+ * Normally called via an atexit handler.
+ */
+void
+kerberos_cleanup_conf(void)
+{
+ char *path;
+
+ if (tmpdir_conf != NULL) {
+ basprintf(&path, "%s/krb5.conf", tmpdir_conf);
+ unlink(path);
+ free(path);
+ test_tmpdir_free(tmpdir_conf);
+ tmpdir_conf = NULL;
+ }
+ putenv((char *) "KRB5_CONFIG=");
+ if (krb5_config != NULL) {
+ free(krb5_config);
+ krb5_config = NULL;
+ }
+}
+
+
+/*
+ * Generate a krb5.conf file for testing and set KRB5_CONFIG to point to it.
+ * The [appdefaults] section will be stripped out and the default realm will
+ * be set to the realm specified, if not NULL. This will use config/krb5.conf
+ * in preference, so users can configure the tests by creating that file if
+ * the system file isn't suitable.
+ *
+ * Depends on data/generate-krb5-conf being present in the test suite.
+ */
+void
+kerberos_generate_conf(const char *realm)
+{
+ char *path;
+ const char *argv[3];
+
+ if (tmpdir_conf != NULL)
+ kerberos_cleanup_conf();
+ path = test_file_path("data/generate-krb5-conf");
+ if (path == NULL)
+ bail("cannot find generate-krb5-conf");
+ argv[0] = path;
+ argv[1] = realm;
+ argv[2] = NULL;
+ run_setup(argv);
+ test_file_path_free(path);
+ tmpdir_conf = test_tmpdir();
+ basprintf(&krb5_config, "KRB5_CONFIG=%s/krb5.conf", tmpdir_conf);
+ putenv(krb5_config);
+ if (atexit(kerberos_cleanup_conf) != 0)
+ sysdiag("cannot register cleanup function");
+}
+
+
+/*
+ * The remaining functions in this file are only available if Kerberos
+ * libraries are available.
+ */
+#ifdef HAVE_KRB5
+
+
+/*
+ * Report a Kerberos error and bail out.
+ */
+void
+bail_krb5(krb5_context ctx, krb5_error_code code, const char *format, ...)
+{
+ const char *k5_msg = NULL;
+ char *message;
+ va_list args;
+
+ if (ctx != NULL)
+ k5_msg = krb5_get_error_message(ctx, code);
+ va_start(args, format);
+ bvasprintf(&message, format, args);
+ va_end(args);
+ if (k5_msg == NULL)
+ bail("%s", message);
+ else
+ bail("%s: %s", message, k5_msg);
+}
+
+
+/*
+ * Report a Kerberos error as a diagnostic to stderr.
+ */
+void
+diag_krb5(krb5_context ctx, krb5_error_code code, const char *format, ...)
+{
+ const char *k5_msg = NULL;
+ char *message;
+ va_list args;
+
+ if (ctx != NULL)
+ k5_msg = krb5_get_error_message(ctx, code);
+ va_start(args, format);
+ bvasprintf(&message, format, args);
+ va_end(args);
+ if (k5_msg == NULL)
+ diag("%s", message);
+ else
+ diag("%s: %s", message, k5_msg);
+ free(message);
+ if (k5_msg != NULL)
+ krb5_free_error_message(ctx, k5_msg);
+}
+
+
+/*
+ * Find the principal of the first entry of a keytab and return it. The
+ * caller is responsible for freeing the result with krb5_free_principal.
+ * Exit on error.
+ */
+krb5_principal
+kerberos_keytab_principal(krb5_context ctx, const char *path)
+{
+ krb5_keytab keytab;
+ krb5_kt_cursor cursor;
+ krb5_keytab_entry entry;
+ krb5_principal princ;
+ krb5_error_code status;
+
+ status = krb5_kt_resolve(ctx, path, &keytab);
+ if (status != 0)
+ bail_krb5(ctx, status, "error opening %s", path);
+ status = krb5_kt_start_seq_get(ctx, keytab, &cursor);
+ if (status != 0)
+ bail_krb5(ctx, status, "error reading %s", path);
+ status = krb5_kt_next_entry(ctx, keytab, &entry, &cursor);
+ if (status == 0) {
+ status = krb5_copy_principal(ctx, entry.principal, &princ);
+ if (status != 0)
+ bail_krb5(ctx, status, "error copying principal from %s", path);
+ krb5_kt_free_entry(ctx, &entry);
+ }
+ if (status != 0)
+ bail("no principal found in keytab file %s", path);
+ krb5_kt_end_seq_get(ctx, keytab, &cursor);
+ krb5_kt_close(ctx, keytab);
+ return princ;
+}
+
+#endif /* HAVE_KRB5 */
--- /dev/null
+/*
+ * Utility functions for tests that use Kerberos.
+ *
+ * The canonical version of this file is maintained in the rra-c-util package,
+ * which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>.
+ *
+ * Written by Russ Allbery <eagle@eyrie.org>
+ * Copyright 2006, 2007, 2009, 2011, 2012, 2013
+ * The Board of Trustees of the Leland Stanford Junior University
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef TAP_KERBEROS_H
+#define TAP_KERBEROS_H 1
+
+#include <config.h>
+#include <tests/tap/macros.h>
+
+#ifdef HAVE_KRB5
+# include <portable/krb5.h>
+#endif
+
+/* Holds the information parsed from the Kerberos test configuration. */
+struct kerberos_config {
+ char *keytab; /* Path to the keytab. */
+ char *principal; /* Principal whose keys are in the keytab. */
+ char *cache; /* Path to the Kerberos ticket cache. */
+ char *userprinc; /* The fully-qualified principal. */
+ char *username; /* The local (non-realm) part of principal. */
+ char *realm; /* The realm part of the principal. */
+ char *password; /* The password. */
+};
+
+/*
+ * Whether to skip all tests (by calling skip_all) in kerberos_setup if
+ * certain configuration information isn't available.
+ */
+enum kerberos_needs {
+ TAP_KRB_NEEDS_NONE = 0x00,
+ TAP_KRB_NEEDS_KEYTAB = 0x01,
+ TAP_KRB_NEEDS_PASSWORD = 0x02,
+ TAP_KRB_NEEDS_BOTH = 0x01 | 0x02
+};
+
+BEGIN_DECLS
+
+/*
+ * Set up Kerberos, returning the test configuration information. This
+ * obtains Kerberos tickets from config/keytab, if one is present, and stores
+ * them in a Kerberos ticket cache, sets KRB5_KTNAME and KRB5CCNAME. It also
+ * loads the principal and password from config/password, if it exists, and
+ * stores the principal, password, username, and realm in the returned struct.
+ *
+ * If there is no config/keytab file, KRB5_KTNAME and KRB5CCNAME won't be set
+ * and the keytab field will be NULL. If there is no config/password file,
+ * the principal field will be NULL. If the files exist but loading them
+ * fails, or authentication fails, kerberos_setup calls bail.
+ *
+ * kerberos_cleanup will be set up to run from an atexit handler. This means
+ * that any child processes that should not remove the Kerberos ticket cache
+ * should call _exit instead of exit. The principal will be automatically
+ * freed when kerberos_cleanup is called or if kerberos_setup is called again.
+ * The caller doesn't need to worry about it.
+ */
+struct kerberos_config *kerberos_setup(enum kerberos_needs)
+ __attribute__((__malloc__));
+void kerberos_cleanup(void);
+
+/*
+ * Generate a krb5.conf file for testing and set KRB5_CONFIG to point to it.
+ * The [appdefaults] section will be stripped out and the default realm will
+ * be set to the realm specified, if not NULL. This will use config/krb5.conf
+ * in preference, so users can configure the tests by creating that file if
+ * the system file isn't suitable.
+ *
+ * Depends on data/generate-krb5-conf being present in the test suite.
+ *
+ * kerberos_cleanup_conf will clean up after this function, but usually
+ * doesn't need to be called directly since it's registered as an atexit
+ * handler.
+ */
+void kerberos_generate_conf(const char *realm);
+void kerberos_cleanup_conf(void);
+
+/* Thes interfaces are only available with native Kerberos support. */
+#ifdef HAVE_KRB5
+
+/* Bail out with an error, appending the Kerberos error message. */
+void bail_krb5(krb5_context, krb5_error_code, const char *format, ...)
+ __attribute__((__noreturn__, __nonnull__, __format__(printf, 3, 4)));
+
+/* Report a diagnostic with Kerberos error to stderr prefixed with #. */
+void diag_krb5(krb5_context, krb5_error_code, const char *format, ...)
+ __attribute__((__nonnull__, __format__(printf, 3, 4)));
+
+/*
+ * Given a Kerberos context and the path to a keytab, retrieve the principal
+ * for the first entry in the keytab and return it. Calls bail on failure.
+ * The returned principal should be freed with krb5_free_principal.
+ */
+krb5_principal kerberos_keytab_principal(krb5_context, const char *path)
+ __attribute__((__nonnull__));
+
+#endif /* HAVE_KRB5 */
+
+END_DECLS
+
+#endif /* !TAP_MESSAGES_H */