]> eyrie.org Git - kerberos/krb5-sync.git/commitdiff
Start cleaning up module test suite
authorRuss Allbery <eagle@eyrie.org>
Thu, 5 Dec 2013 02:14:58 +0000 (18:14 -0800)
committerRuss Allbery <eagle@eyrie.org>
Thu, 5 Dec 2013 02:14:58 +0000 (18:14 -0800)
Import the Kerberos TAP functions from rra-c-util so that we can
use bail_krb5, and update the Kerberos Autoconf macros to match.
Add make-krb5-conf to build a configuration with known settings,
although it's not being used yet.  Clean up and comment the dynamic
loading tests and use bail more to avoid making the whole test
conditional.

Makefile.am
configure.ac
m4/krb5.m4
tests/data/make-krb5-conf [new file with mode: 0755]
tests/plugin/heimdal-t.c
tests/plugin/mit-t.c
tests/tap/kerberos.c [new file with mode: 0644]
tests/tap/kerberos.h [new file with mode: 0644]

index 52265d8916bbff1fa0a9a898a62525fd5e25befd..e40f43cd8709278b31e9641c7bf69505c4355a05 100644 (file)
@@ -7,19 +7,22 @@
 # 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 \
@@ -39,7 +42,7 @@ plugin_krb5_sync_la_SOURCES = plugin/ad.c plugin/config.c plugin/error.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)
 
@@ -47,7 +50,7 @@ plugin_krb5_sync_la_LIBADD = portable/libportable.la $(KADM5SRV_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)
 
@@ -96,21 +99,21 @@ tests_runtests_CPPFLAGS = -DSOURCE='"$(abs_top_srcdir)/tests"' \
        -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 \
@@ -118,7 +121,7 @@ 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 \
@@ -127,7 +130,6 @@ tests_portable_asprintf_t_LDADD = tests/tap/libtap.a portable/libportable.la
 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 \
index fec33d76410929f9831d7b84d41ee3019d787e9b..010db6d6100787e8f8e053d75b843527f06fe27f 100644 (file)
@@ -26,7 +26,9 @@ LT_INIT
 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 \
index b57f4e573a8d1cf4c36dd3c81ab8717e7252e615..950524f4069f28c62c51ecaaae8e68966f93a7bb 100644 (file)
@@ -11,23 +11,27 @@ dnl KRB5_CPPFLAGS, KRB5_LDFLAGS, and KRB5_LIBS.  Also provides
 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
@@ -40,13 +44,16 @@ dnl The canonical version of this file is maintained in the rra-c-util
 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
@@ -87,6 +94,19 @@ AC_DEFUN([_RRA_LIB_KRB5_PATHS],
         [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.
@@ -96,7 +116,10 @@ AC_DEFUN([_RRA_LIB_KRB5_REDUCED],
      [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], [],
@@ -108,7 +131,9 @@ AC_DEFUN([_RRA_LIB_KRB5_REDUCED],
                      [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])
 
@@ -126,7 +151,7 @@ AC_DEFUN([_RRA_LIB_KRB5_MANUAL],
     [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],
@@ -156,7 +181,10 @@ AC_DEFUN([_RRA_LIB_KRB5_MANUAL],
         [$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], [],
@@ -188,7 +216,10 @@ AC_DEFUN([_RRA_LIB_KRB5_CONFIG],
 [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], [],
@@ -203,9 +234,15 @@ AC_DEFUN([_RRA_LIB_KRB5_CONFIG],
 
 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])],
@@ -214,15 +251,16 @@ AC_DEFUN([_RRA_LIB_KRB5_INTERNAL],
         [_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])
@@ -243,14 +281,14 @@ AC_DEFUN([RRA_LIB_KRB5],
     [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])
@@ -259,9 +297,9 @@ AC_DEFUN([RRA_LIB_KRB5_OPTIONAL],
     [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])],
@@ -273,13 +311,14 @@ AC_DEFUN([RRA_LIB_KRB5_OPTIONAL],
     [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] [[
diff --git a/tests/data/make-krb5-conf b/tests/data/make-krb5-conf
new file mode 100755 (executable)
index 0000000..060a3d3
--- /dev/null
@@ -0,0 +1,49 @@
+#!/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
index ea9d8d7b523afa93d4f2b56c66409494ae915e7c..3874e28475d4484f16130494734c7c963356ecbd 100644 (file)
@@ -1,6 +1,10 @@
 /*
  * 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>
 
 /*
@@ -54,7 +56,7 @@ typedef struct kadm5_hook {
 int
 main(void)
 {
-    char *krb5conf, *env, *plugin;
+    char *path, *krb5_config, *plugin;
     krb5_error_code code;
     krb5_context ctx;
     krb5_principal princ;
@@ -65,112 +67,110 @@ main(void)
     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;
 }
index aac5cd0451da951ae51c26dc0ee87f8fc8d0664a..4480c90909a294b3745fb3ebc437989a951d1dc0 100644 (file)
@@ -1,6 +1,10 @@
 /*
  * 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>
 
 
@@ -39,131 +41,136 @@ main(void)
 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;
 }
 
diff --git a/tests/tap/kerberos.c b/tests/tap/kerberos.c
new file mode 100644 (file)
index 0000000..58315ee
--- /dev/null
@@ -0,0 +1,488 @@
+/*
+ * 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 */
diff --git a/tests/tap/kerberos.h b/tests/tap/kerberos.h
new file mode 100644 (file)
index 0000000..c34f891
--- /dev/null
@@ -0,0 +1,125 @@
+/*
+ * 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 */