]> eyrie.org Git - kerberos/kstart.git/commitdiff
Add support for kafs
authorRuss Allbery <eagle@eyrie.org>
Mon, 30 Aug 2021 05:05:44 +0000 (22:05 -0700)
committerRuss Allbery <eagle@eyrie.org>
Mon, 30 Aug 2021 05:05:44 +0000 (22:05 -0700)
Add support for kafs to the -t options of k5start and krenew.  If AFS
setpag support is not compiled in or AFS is not available, but the
Linux kafs kernel module is available, do not error out when -t is
specified and continue to run the external program.  If libkeyutils is
found at compile time and a command was specified on the command line,
create a new session keyring before running the external command to
isolate its AFS credentials from the calling process.  Thanks to Bill
MacAllister, David Howells, and Jeffrey Altman for proposed code and
implementation discussion.

Makefile.am
NEWS
README
README.md
commands/framework.c
configure.ac
docs/docknot.yaml
docs/k5start.pod
docs/krenew.pod
m4/keyutils.m4 [new file with mode: 0644]
m4/lib-helper.m4 [new file with mode: 0644]

index 58122bad41bd2b64eb9525c8b5f1084454af6ceb..8c4bc8a9d228d7308f310074c16f184e8dc8a426 100644 (file)
@@ -74,14 +74,18 @@ endif
 bin_PROGRAMS = commands/k5start commands/krenew
 commands_k5start_SOURCES = commands/framework.c commands/internal.h \
        commands/k5start.c
-commands_k5start_LDFLAGS = $(KRB5_LDFLAGS) $(KAFS_LDFLAGS)
+commands_k5start_CPPFLAGS = $(LIBKEYUTILS_CPPFLAGS) $(AM_CPPFLAGS)
+commands_k5start_LDFLAGS = $(KRB5_LDFLAGS) $(KAFS_LDFLAGS) \
+       $(LIBKEYUTILS_LDFLAGS)
 commands_k5start_LDADD = $(LIBKAFS) util/libutil.a portable/libportable.a \
-       $(K5START_LIBS)
+       $(K5START_LIBS) $(LIBKEYUTILS_LIBS)
 commands_krenew_SOURCES = commands/framework.c commands/internal.h \
        commands/krenew.c
-commands_krenew_LDFLAGS = $(KRB5_LDFLAGS) $(KAFS_LDFLAGS)
+commands_krenew_CPPFLAGS = $(LIBKEYUTILS_CPPFLAGS) $(AM_CPPFLAGS)
+commands_krenew_LDFLAGS = $(KRB5_LDFLAGS) $(KAFS_LDFLAGS) \
+       $(LIBKEYUTILS_LDFLAGS)
 commands_krenew_LDADD = $(LIBKAFS) util/libutil.a portable/libportable.a \
-       $(K5START_LIBS)
+       $(K5START_LIBS) $(LIBKEYUTILS_LIBS)
 dist_man_MANS = docs/k5start.1 docs/krenew.1
 
 DISTCLEANFILES = config.h.in~ tests/data/.placeholder
diff --git a/NEWS b/NEWS
index c4833fb86e254f5dfd395fe2fbfa6291b2142414..18cc30be227b2ab9e99c720af13a3a70c768a856 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -2,6 +2,16 @@
 
 kstart 4.3 (unreleased)
 
+    Add support for kafs to the -t options of k5start and krenew.  If AFS
+    setpag support is not compiled in or AFS is not available, but the
+    Linux kafs kernel module is available, do not error out when -t is
+    specified and continue to run the external program.  If libkeyutils is
+    found at compile time and a command was specified on the command line,
+    create a new session keyring before running the external command to
+    isolate its AFS credentials from the calling process.  Thanks to Bill
+    MacAllister, David Howells, and Jeffrey Altman for proposed code and
+    implementation discussion.
+
     If the process run by k5start or krenew is killed by a signal, k5start
     or krenew now exits with status 128 plus the killing signal rather
     than zero.  This avoids the caller of k5start or krenew thinking the
diff --git a/README b/README
index c2a6d76b9aabb20cbf766f97647eda07735dbed5..6f67f2725bf55b1de915a6a4375cb8656657b013 100644 (file)
--- a/README
+++ b/README
@@ -113,6 +113,14 @@ BUILDING AND INSTALLATION
   authentication when running a specific command and when aklog is being
   run.
 
+  When using the Linux kafs module, the correct way to isolate kafs
+  credentials is to create a new session keyring rather than a new PAG.
+  This requires the libkeyutils library.  configure will attempt to
+  discover that library automatically and link with it by default.  Pass
+  the --with-libkeyutils, --with-libkeyutils-include, or
+  --with-libkeyutils-lib options to configure to specify a different path
+  to that library, or set the LIBKEYUTILS_* environment variables.
+
   Normally, configure will use krb5-config to determine the flags to use
   to compile with your Kerberos libraries.  To specify a particular
   krb5-config script to use, either set the PATH_KRB5_CONFIG environment
index 00c1e1dc5c4477a0fe7980423f77017a3e50c969..b0a00aefa54d10d9dd3aa5884c31b7520d85d7ca 100644 (file)
--- a/README.md
+++ b/README.md
@@ -120,6 +120,14 @@ When enabled, k5start and krenew will always create a new PAG before
 authentication when running a specific command and when aklog is being
 run.
 
+When using the Linux kafs module, the correct way to isolate kafs
+credentials is to create a new session keyring rather than a new PAG.
+This requires the libkeyutils library.  `configure` will attempt to
+discover that library automatically and link with it by default.  Pass the
+`--with-libkeyutils`, `--with-libkeyutils-include`, or
+`--with-libkeyutils-lib` options to `configure` to specify a different
+path to that library, or set the `LIBKEYUTILS_*` environment variables.
+
 Normally, configure will use `krb5-config` to determine the flags to use
 to compile with your Kerberos libraries.  To specify a particular
 `krb5-config` script to use, either set the `PATH_KRB5_CONFIG` environment
index 8a452027329dbfaa611166a5a029b5d0edc7fe4e..ad4c6c20b316c5308835e021fc1b1ce4f4bd416b 100644 (file)
 #include <portable/system.h>
 
 #include <errno.h>
+#ifdef HAVE_LIBKEYUTILS
+#    include <keyutils.h>
+#endif
 #include <signal.h>
+#include <sys/stat.h>
 #ifdef HAVE_SYS_TIME_H
 #    include <sys/time.h>
 #endif
@@ -276,6 +280,54 @@ add_handler(krb5_context ctx, struct config *config, void (*handler)(int),
 }
 
 
+/*
+ * Probe to see if the Linux kafs subsystem is available.
+ */
+static bool
+has_kafs(void)
+{
+    struct stat st;
+    int rval, saved_errno;
+
+    saved_errno = errno;
+    rval = stat("/proc/fs/afs", &st);
+    errno = saved_errno;
+    return rval == 0;
+}
+
+
+/*
+ * Create a session keyring and link it to the user keyring.  This is done
+ * when using kafs since running aklog will change the current session
+ * keyring, and we don't want to clobber the keyring of our caller because we
+ * may be using different credentials.
+ *
+ * If libkeyutils is not available, do nothing silently and the caller's
+ * keyring will get clobbered.
+ */
+#ifdef HAVE_LIBKEYUTILS
+static void
+create_keyring(krb5_context ctx, struct config *config)
+{
+    key_serial_t key;
+
+    key = keyctl_join_session_keyring(NULL);
+    if (key < 0) {
+        syswarn("cannot create new session keyring");
+        exit_cleanup(ctx, config, 1);
+    }
+    if (keyctl_link(KEY_SPEC_USER_KEYRING, key) < 0)
+        syswarn("cannot link session keyring to user keyring");
+}
+#else
+static void
+create_keyring(krb5_context ctx UNUSED, struct config *config UNUSED)
+{
+    return;
+}
+#endif
+
+
 /*
  * The primary entry point of the framework.  Both k5start and krenew call
  * this function after setting up the options and configuration to do the real
@@ -302,8 +354,10 @@ run_framework(krb5_context ctx, struct config *config)
     }
 
     /*
-     * If built with setpag support and we're running a command, create the
-     * new PAG now before the first authentication.
+     * If built with setpag support, or if kafs is available, and we're
+     * running a command, create the new PAG now before the first
+     * authentication.  This prevents us from clobbering our caller's AFS
+     * credentials.
      */
     if (config->command != NULL && config->do_aklog) {
         if (k_hasafs()) {
@@ -311,6 +365,8 @@ run_framework(krb5_context ctx, struct config *config)
                 syswarn("unable to create PAG");
                 exit_cleanup(ctx, config, 1);
             }
+        } else if (has_kafs()) {
+            create_keyring(ctx, config);
         } else {
             warn("cannot create PAG: AFS support is not available");
             exit_cleanup(ctx, config, 1);
index 1ba88c1e491278f46466a0dd26815bf1d14f2eb9..f61b6b1998447370d182c566085ec12a158e4bd2 100644 (file)
@@ -21,6 +21,7 @@ AM_INIT_AUTOMAKE([1.11 check-news foreign silent-rules subdir-objects
 AM_MAINTAINER_MODE
 
 dnl Detect unexpanded macros.
+m4_pattern_forbid([^PKG_])
 m4_pattern_forbid([^_?RRA_])
 
 AC_PROG_CC
@@ -70,6 +71,9 @@ AC_ARG_ENABLE([setpag],
     [AS_IF([test x"$enableval" != xno], [RRA_LIB_KAFS])])
 AM_CONDITIONAL([NEED_KAFS], [test x"$rra_build_kafs" = xtrue])
 
+dnl Check if libkeyutils is available, used for kafs support.
+RRA_LIB_KEYUTILS_OPTIONAL
+
 dnl Other portability checks.
 AC_HEADER_STDBOOL
 AC_CHECK_HEADERS([strings.h sys/bitypes.h sys/select.h sys/time.h syslog.h])
index 421c2b7bc294d0781e47f68833f1f40b3071614e..255ded1eca8768c3bd347758c9d5e98aec487140 100644 (file)
@@ -59,6 +59,14 @@ build:
     When enabled, k5start and krenew will always create a new PAG before
     authentication when running a specific command and when aklog is being
     run.
+
+    When using the Linux kafs module, the correct way to isolate kafs
+    credentials is to create a new session keyring rather than a new PAG.
+    This requires the libkeyutils library.  `configure` will attempt to
+    discover that library automatically and link with it by default.  Pass the
+    `--with-libkeyutils`, `--with-libkeyutils-include`, or
+    `--with-libkeyutils-lib` options to `configure` to specify a different
+    path to that library, or set the `LIBKEYUTILS_*` environment variables.
   reduced_depends: true
   type: Autoconf
 distribution:
index e3a10829e2e5f135370802c4d4a492fa0ff82606..1b3f7d5a9670d20a37814e8edabcc1e7b1766f12 100644 (file)
@@ -2,7 +2,7 @@
 -abFhLnPqstvx keytab username kinit LDAP aklog HUP ALRM KRB5CCNAME AFS PAG
 init AKLOG kstart krenew afslog Bense Allbery Navid Golpayegani
 forwardable proxiable designator Ctrl-C backoff FSFAP
-SPDX-License-Identifier
+SPDX-License-Identifier kafs keyring libkeyutils
 
 =head1 NAME
 
@@ -317,14 +317,28 @@ risk.  You normally want to use a keytab and the B<-f> option instead.
 
 =item B<-t>
 
-Run an external program after getting a ticket.  The default use of this
-is to run B<aklog> to get a token.  If the environment variable KINIT_PROG
-is set, it overrides the compiled-in default.
+Run an external program after getting a ticket.  The intended use of this
+is to run B<aklog> to get a token.  If the environment variable AKLOG (or
+KINIT_PROG for backward compatibility) is set, it overrides the
+compiled-in default.
 
-If B<k5start> has been built with AFS setpag() support and a command was
-given on the command line, B<k5start> will create a new PAG before
-obtaining AFS tokens.  Otherwise, it will obtain tokens in the current
-PAG.
+If a command was given on the command line, B<k5start> will attempt to
+isolate the AFS credentials for that command from the invoking process.
+There are two possible ways in which this is done.
+
+First, if B<k5start> has been built with AFS setpag() support and AFS is
+available, B<k5start> will create a new PAG before running the external
+program.
+
+Otherwise, if either B<k5start> was not built with AFS setpag() support or
+AFS is not available, but the Linux kafs module is available and
+B<k5start> was built with libkeyutils support, it will create a new
+session keyring and link it to the current user keyring before running the
+external program.
+
+If neither of these conditions are true, B<k5start> will run the external
+program without doing any credential isolation, which may also affect the
+credentials of the invoking process.
 
 =item B<-U>
 
index 85fa8341a8f1b99f80213da8c7750f7d345517d0..98d1b1bbf73dba7e06137464894a23d52e87252c 100644 (file)
@@ -1,7 +1,7 @@
 =for stopwords
 -abhiLstvx aklog AFS OpenSSH PAG HUP ALRM KRB5CCNAME AKLOG kstart afslog
 Allbery Bense designator krenew Ctrl-C SIGHUP backoff FSFAP
-SPDX-License-Identifier
+SPDX-License-Identifier kafs keyring libkeyutils
 
 =head1 NAME
 
@@ -228,14 +228,28 @@ command to keep running without Kerberos tickets.
 
 =item B<-t>
 
-Run an external program after getting a ticket.  The default use of this
-is to run B<aklog> to get a token.  If the environment variable KINIT_PROG
-is set, it overrides the compiled-in default.
+Run an external program after getting a ticket.  The intended use of this
+is to run B<aklog> to get a token.  If the environment variable AKLOG (or
+KINIT_PROG for backward compatibility) is set, it overrides the
+compiled-in default.
 
-If B<krenew> has been built with AFS setpag() support and a command was
-given on the command line, B<krenew> will create a new PAG before
-obtaining AFS tokens.  Otherwise, it will obtain tokens in the current
-PAG.
+If a command was given on the command line, B<krenew> will attempt to
+isolate the AFS credentials for that command from the invoking process.
+There are two possible ways in which this is done.
+
+First, if B<krenew> has been built with AFS setpag() support and AFS is
+available, B<krenew> will create a new PAG before running the external
+program.
+
+Otherwise, if either B<krenew> was not built with AFS setpag() support or
+AFS is not available, but the Linux kafs module is available and B<krenew>
+was built with libkeyutils support, it will create a new session keyring
+and link it to the current user keyring before running the external
+program.
+
+If neither of these conditions are true, B<krenew> will run the external
+program without doing any credential isolation, which may also affect the
+credentials of the invoking process.
 
 =item B<-v>
 
diff --git a/m4/keyutils.m4 b/m4/keyutils.m4
new file mode 100644 (file)
index 0000000..d894013
--- /dev/null
@@ -0,0 +1,76 @@
+dnl Find the compiler and linker flags for libkeyutils.
+dnl
+dnl Finds the compiler and linker flags for linking with the libkeyutils
+dnl library.  Provides the --with-libkeyutils, --with-libkeyutils-lib, and
+dnl --with-libkeyutils-include configure options to specify non-standard paths
+dnl to the libkeyutils libraries or header files.
+dnl
+dnl Provides the macros RRA_LIB_KEYUTILS and RRA_LIB_KEYUTILS_OPTIONAL and
+dnl sets the substitution variables LIBKEYUTILS_CPPFLAGS, LIBKEYUTILS_LDFLAGS,
+dnl and LIBKEYUTILS_LIBS.  Also provides RRA_LIB_KEYUTILS_SWITCH to set
+dnl CPPFLAGS, LDFLAGS, and LIBS to include the libkeyutils libraries, saving
+dnl the current values first, and RRA_LIB_KEYUTILS_RESTORE to restore those
+dnl settings to before the last RRA_LIB_KEYUTILS_SWITCH.  Defines
+dnl HAVE_LIBKEYUTILS if libkeyutils is found.  If it isn't found, the
+dnl substitution variables will be empty.
+dnl
+dnl Depends on the lib-helper.m4 framework.
+dnl
+dnl The canonical version of this file is maintained in the rra-c-util
+dnl package, available at <https://www.eyrie.org/~eagle/software/rra-c-util/>.
+dnl
+dnl Copyright 2021 Russ Allbery <eagle@eyrie.org>
+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
+dnl SPDX-License-Identifier: FSFULLR
+
+dnl Save the current CPPFLAGS, LDFLAGS, and LIBS settings and switch to
+dnl versions that include the libevent flags.  Used as a wrapper, with
+dnl RRA_LIB_LIBEVENT_RESTORE, around tests.
+AC_DEFUN([RRA_LIB_KEYUTILS_SWITCH], [RRA_LIB_HELPER_SWITCH([LIBKEYUTILS])])
+
+dnl Restore CPPFLAGS, LDFLAGS, and LIBS to their previous values before
+dnl RRA_LIB_LIBEVENT_SWITCH was called.
+AC_DEFUN([RRA_LIB_KEYUTILS_RESTORE], [RRA_LIB_HELPER_RESTORE([LIBKEYUTILS])])
+
+dnl Checks if libkeyutils is present.  The single argument, if "true", says to
+dnl fail if the libkeyutils library could not be found.  Prefer probing with
+dnl pkg-config if available and the --with flags were not given.
+AC_DEFUN([_RRA_LIB_KEYUTILS_INTERNAL],
+[AC_REQUIRE([RRA_ENABLE_REDUCED_DEPENDS])
+ RRA_LIB_HELPER_PATHS([LIBKEYUTILS])
+ AS_IF([test x"$LIBKEYUTILS_CPPFLAGS" = x && test x"$LIBKEYUTILS_LDFLAGS" = x],
+    [PKG_CHECK_EXISTS([libkeyutils],
+        [PKG_CHECK_MODULES([LIBKEYUTILS], [libkeyutils])
+         LIBKEYUTILS_CPPFLAGS="$LIBKEYUTILS_CFLAGS"])])
+ AS_IF([test x"$LIBKEYUTILS_LIBS" = x],
+    [RRA_LIB_KEYUTILS_SWITCH
+     LIBS=
+     AC_SEARCH_LIBS([keyctl_join_session_keyring], [keyutils],
+        [LIBKEYUTILS_LIBS="$LIBS"],
+        [AS_IF([test x"$1" = xtrue],
+            [AC_MSG_ERROR([cannot find usable libkeyutils library])])])
+     RRA_LIB_KEYUTILS_RESTORE])])
+
+dnl The main macro for packages with mandatory libkeyutils support.
+AC_DEFUN([RRA_LIB_KEYUTILS],
+[RRA_LIB_HELPER_VAR_INIT([LIBKEYUTILS])
+ RRA_LIB_HELPER_WITH([libkeyutils], [libkeyutils], [LIBKEYUTILS])
+ _RRA_LIB_KEYUTILS_INTERNAL([true])
+ rra_use_LIBKEYUTILS=true
+ AC_DEFINE([HAVE_LIBKEYUTILS], 1, [Define if libkeyutils is available.])])
+
+dnl The main macro for packages with optional libkeyutils support.
+AC_DEFUN([RRA_LIB_KEYUTILS_OPTIONAL],
+[RRA_LIB_HELPER_VAR_INIT([LIBKEYUTILS])
+ RRA_LIB_HELPER_WITH_OPTIONAL([libkeyutils], [libkeyutils], [LIBKEYUTILS])
+ AS_IF([test x"$rra_use_LIBKEYUTILS" != xfalse],
+    [AS_IF([test x"$rra_use_LIBKEYUTILS" = xtrue],
+        [_RRA_LIB_KEYUTILS_INTERNAL([true])],
+        [_RRA_LIB_KEYUTILS_INTERNAL([false])])])
+ AS_IF([test x"$LIBKEYUTILS_LIBS" != x],
+    [rra_use_LIBKEYUTILS=true
+     AC_DEFINE([HAVE_LIBKEYUTILS], 1, [Define if libkeyutils is available.])])])
diff --git a/m4/lib-helper.m4 b/m4/lib-helper.m4
new file mode 100644 (file)
index 0000000..481122b
--- /dev/null
@@ -0,0 +1,149 @@
+dnl Helper functions to manage compiler variables.
+dnl
+dnl These are a wide variety of helper macros to make it easier to construct
+dnl standard macros to probe for a library and to set library-specific
+dnl CPPFLAGS, LDFLAGS, and LIBS shell substitution variables.  Most of them
+dnl take as one of the arguments the prefix string to use for variables, which
+dnl is usually something like "KRB5" or "GSSAPI".
+dnl
+dnl Depends on RRA_SET_LDFLAGS.
+dnl
+dnl The canonical version of this file is maintained in the rra-c-util
+dnl package, available at <https://www.eyrie.org/~eagle/software/rra-c-util/>.
+dnl
+dnl Written by Russ Allbery <eagle@eyrie.org>
+dnl Copyright 2018 Russ Allbery <eagle@eyrie.org>
+dnl Copyright 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
+dnl SPDX-License-Identifier: FSFULLR
+
+dnl Add the library flags to the default compiler flags and then remove them.
+dnl
+dnl To use these macros, pass the prefix string used for the variables as the
+dnl only argument.  For example, to use these for a library with KRB5 as a
+dnl prefix, one would use:
+dnl
+dnl     AC_DEFUN([RRA_LIB_KRB5_SWITCH], [RRA_LIB_HELPER_SWITCH([KRB5])])
+dnl     AC_DEFUN([RRA_LIB_KRB5_RESTORE], [RRA_LIB_HELPER_RESTORE([KRB5])])
+dnl
+dnl Then, wrap checks for library features with RRA_LIB_KRB5_SWITCH and
+dnl RRA_LIB_KRB5_RESTORE.
+AC_DEFUN([RRA_LIB_HELPER_SWITCH],
+[rra_$1[]_save_CPPFLAGS="$CPPFLAGS"
+ rra_$1[]_save_LDFLAGS="$LDFLAGS"
+ rra_$1[]_save_LIBS="$LIBS"
+ CPPFLAGS="$$1[]_CPPFLAGS $CPPFLAGS"
+ LDFLAGS="$$1[]_LDFLAGS $LDFLAGS"
+ LIBS="$$1[]_LIBS $LIBS"])
+
+AC_DEFUN([RRA_LIB_HELPER_RESTORE],
+[CPPFLAGS="$rra_$1[]_save_CPPFLAGS"
+ LDFLAGS="$rra_$1[]_save_LDFLAGS"
+ LIBS="$rra_$1[]_save_LIBS"])
+
+dnl Given _root, _libdir, and _includedir variables set for a library (set by
+dnl RRA_LIB_HELPER_WITH*), set the LDFLAGS and CPPFLAGS variables for that
+dnl library accordingly.  Takes the variable prefix as the only argument.
+AC_DEFUN([RRA_LIB_HELPER_PATHS],
+[AS_IF([test x"$rra_$1[]_libdir" != x],
+    [$1[]_LDFLAGS="-L$rra_$1[]_libdir"],
+    [AS_IF([test x"$rra_$1[]_root" != x],
+        [RRA_SET_LDFLAGS([$1][_LDFLAGS], [${rra_$1[]_root}])])])
+ AS_IF([test x"$rra_$1[]_includedir" != x],
+    [$1[]_CPPFLAGS="-I$rra_$1[]_includedir"],
+    [AS_IF([test x"$rra_$1[]_root" != x],
+        [AS_IF([test x"$rra_$1[]_root" != x/usr],
+            [$1[]_CPPFLAGS="-I${rra_$1[]_root}/include"])])])])
+
+dnl Check whether a library works.  This is used as a sanity check on the
+dnl results of *-config shell scripts.  Takes four arguments; the first, if
+dnl "true", says that a working library is mandatory and errors out if it
+dnl doesn't.  The second is the variable prefix.  The third is a function to
+dnl look for that should be in the libraries.  The fourth is the
+dnl human-readable name of the library for error messages.
+AC_DEFUN([RRA_LIB_HELPER_CHECK],
+[RRA_LIB_HELPER_SWITCH([$2])
+ AC_CHECK_FUNC([$3], [],
+    [AS_IF([test x"$1" = xtrue],
+        [AC_MSG_FAILURE([unable to link with $4 library])])
+     $2[]_CPPFLAGS=
+     $2[]_LDFLAGS=
+     $2[]_LIBS=])
+ RRA_LIB_HELPER_RESTORE([$2])])
+
+dnl Initialize the variables used by a library probe and set the appropriate
+dnl ones as substitution variables.  Takes the library variable prefix as its
+dnl only argument.
+AC_DEFUN([RRA_LIB_HELPER_VAR_INIT],
+[rra_$1[]_root=
+ rra_$1[]_libdir=
+ rra_$1[]_includedir=
+ rra_use_$1=
+ $1[]_CPPFLAGS=
+ $1[]_LDFLAGS=
+ $1[]_LIBS=
+ AC_SUBST([$1][_CPPFLAGS])
+ AC_SUBST([$1][_LDFLAGS])
+ AC_SUBST([$1][_LIBS])])
+
+dnl Unset all of the variables used by a library probe.  Used with the
+dnl _OPTIONAL versions of header probes when a header or library wasn't found
+dnl and therefore the library isn't usable.
+AC_DEFUN([RRA_LIB_HELPER_VAR_CLEAR],
+[$1[]_CPPFLAGS=
+ $1[]_LDFLAGS=
+ $1[]_LIBS=])
+
+dnl Handles --with options for a non-optional library.  First argument is the
+dnl base for the switch names.  Second argument is the short description.
+dnl Third argument is the variable prefix.  The variables set are used by
+dnl RRA_LIB_HELPER_PATHS.
+AC_DEFUN([RRA_LIB_HELPER_WITH],
+[AC_ARG_WITH([$1],
+    [AS_HELP_STRING([--with-][$1][=DIR],
+        [Location of $2 headers and libraries])],
+    [AS_IF([test x"$withval" != xyes && test x"$withval" != xno],
+        [rra_$3[]_root="$withval"])])
+ AC_ARG_WITH([$1][-include],
+    [AS_HELP_STRING([--with-][$1][-include=DIR],
+        [Location of $2 headers])],
+    [AS_IF([test x"$withval" != xyes && test x"$withval" != xno],
+        [rra_$3[]_includedir="$withval"])])
+ AC_ARG_WITH([$1][-lib],
+    [AS_HELP_STRING([--with-][$1][-lib=DIR],
+        [Location of $2 libraries])],
+    [AS_IF([test x"$withval" != xyes && test x"$withval" != xno],
+        [rra_$3[]_libdir="$withval"])])])
+
+dnl Handles --with options for an optional library, so --with-<library> can
+dnl cause the checks to be skipped entirely or become mandatory.  Sets an
+dnl rra_use_PREFIX variable to true or false if the library is explicitly
+dnl enabled or disabled.
+dnl
+dnl First argument is the base for the switch names.  Second argument is the
+dnl short description.  Third argument is the variable prefix.
+dnl
+dnl The variables set are used by RRA_LIB_HELPER_PATHS.
+AC_DEFUN([RRA_LIB_HELPER_WITH_OPTIONAL],
+[AC_ARG_WITH([$1],
+    [AS_HELP_STRING([--with-][$1][@<:@=DIR@:>@],
+        [Location of $2 headers and libraries])],
+    [AS_IF([test x"$withval" = xno],
+        [rra_use_$3=false],
+        [AS_IF([test x"$withval" != xyes], [rra_$3[]_root="$withval"])
+         rra_use_$3=true])])
+ AC_ARG_WITH([$1][-include],
+    [AS_HELP_STRING([--with-][$1][-include=DIR],
+        [Location of $2 headers])],
+    [AS_IF([test x"$withval" != xyes && test x"$withval" != xno],
+        [rra_$3[]_includedir="$withval"])])
+ AC_ARG_WITH([$1][-lib],
+    [AS_HELP_STRING([--with-][$1][-lib=DIR],
+        [Location of $2 libraries])],
+    [AS_IF([test x"$withval" != xyes && test x"$withval" != xno],
+        [rra_$3[]_libdir="$withval"])])])