aboutsummaryrefslogtreecommitdiff
path: root/pam-util
diff options
context:
space:
mode:
authorCy Schubert <cy@FreeBSD.org>2025-04-17 02:13:41 +0000
committerCy Schubert <cy@FreeBSD.org>2025-05-27 16:20:06 +0000
commit24f0b4ca2d565cdbb4fe7839ff28320706bf2386 (patch)
treebc9ce87edb73f767f5580887d0fc8c643b9d7a49 /pam-util
Diffstat (limited to 'pam-util')
-rw-r--r--pam-util/args.c105
-rw-r--r--pam-util/args.h84
-rw-r--r--pam-util/logging.c345
-rw-r--r--pam-util/logging.h131
-rw-r--r--pam-util/options.c720
-rw-r--r--pam-util/options.h205
-rw-r--r--pam-util/vector.c289
-rw-r--r--pam-util/vector.h120
8 files changed, 1999 insertions, 0 deletions
diff --git a/pam-util/args.c b/pam-util/args.c
new file mode 100644
index 000000000000..293988b8cd1a
--- /dev/null
+++ b/pam-util/args.c
@@ -0,0 +1,105 @@
+/*
+ * Constructor and destructor for PAM data.
+ *
+ * The PAM utility functions often need an initial argument that encapsulates
+ * the PAM handle, some configuration information, and possibly a Kerberos
+ * context. This implements a constructor and destructor for that data
+ * structure.
+ *
+ * The individual PAM modules should provide a definition of the pam_config
+ * struct appropriate to that module. None of the PAM utility functions need
+ * to know what that configuration struct looks like, and it must be freed
+ * before calling putil_args_free().
+ *
+ * The canonical version of this file is maintained in the rra-c-util package,
+ * which can be found at <https://www.eyrie.org/~eagle/software/rra-c-util/>.
+ *
+ * Written by Russ Allbery <eagle@eyrie.org>
+ * Copyright 2010, 2012-2014
+ * 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.
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#include <config.h>
+#ifdef HAVE_KRB5
+# include <portable/krb5.h>
+#endif
+#include <portable/pam.h>
+#include <portable/system.h>
+
+#include <errno.h>
+
+#include <pam-util/args.h>
+#include <pam-util/logging.h>
+
+
+/*
+ * Allocate a new pam_args struct and return it, or NULL on memory allocation
+ * or Kerberos initialization failure. If HAVE_KRB5 is defined, we also
+ * allocate a Kerberos context.
+ */
+struct pam_args *
+putil_args_new(pam_handle_t *pamh, int flags)
+{
+ struct pam_args *args;
+#ifdef HAVE_KRB5
+ krb5_error_code status;
+#endif
+
+ args = calloc(1, sizeof(struct pam_args));
+ if (args == NULL) {
+ putil_crit(NULL, "cannot allocate memory: %s", strerror(errno));
+ return NULL;
+ }
+ args->pamh = pamh;
+ args->silent = ((flags & PAM_SILENT) == PAM_SILENT);
+
+#ifdef HAVE_KRB5
+ if (issetugid())
+ status = krb5_init_secure_context(&args->ctx);
+ else
+ status = krb5_init_context(&args->ctx);
+ if (status != 0) {
+ putil_err_krb5(args, status, "cannot create Kerberos context");
+ free(args);
+ return NULL;
+ }
+#endif /* HAVE_KRB5 */
+ return args;
+}
+
+
+/*
+ * Free a pam_args struct. The config member must be freed separately.
+ */
+void
+putil_args_free(struct pam_args *args)
+{
+ if (args == NULL)
+ return;
+#ifdef HAVE_KRB5
+ free(args->realm);
+ if (args->ctx != NULL)
+ krb5_free_context(args->ctx);
+#endif
+ free(args);
+}
diff --git a/pam-util/args.h b/pam-util/args.h
new file mode 100644
index 000000000000..79b5d046ab60
--- /dev/null
+++ b/pam-util/args.h
@@ -0,0 +1,84 @@
+/*
+ * Standard structure for PAM data.
+ *
+ * The PAM utility functions often need an initial argument that encapsulates
+ * the PAM handle, some configuration information, and possibly a Kerberos
+ * context. This header provides a standard structure definition.
+ *
+ * The individual PAM modules should provide a definition of the pam_config
+ * struct appropriate to that module. None of the PAM utility functions need
+ * to know what that configuration struct looks like.
+ *
+ * The canonical version of this file is maintained in the rra-c-util package,
+ * which can be found at <https://www.eyrie.org/~eagle/software/rra-c-util/>.
+ *
+ * Written by Russ Allbery <eagle@eyrie.org>
+ * Copyright 2010, 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.
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#ifndef PAM_UTIL_ARGS_H
+#define PAM_UTIL_ARGS_H 1
+
+#include <config.h>
+#ifdef HAVE_KRB5
+# include <portable/krb5.h>
+#endif
+#include <portable/pam.h>
+#include <portable/stdbool.h>
+
+/* Opaque struct from the PAM utility perspective. */
+struct pam_config;
+
+struct pam_args {
+ pam_handle_t *pamh; /* Pointer back to the PAM handle. */
+ struct pam_config *config; /* Per-module PAM configuration. */
+ bool debug; /* Log debugging information. */
+ bool silent; /* Do not pass text to the application. */
+ const char *user; /* User being authenticated. */
+
+#ifdef HAVE_KRB5
+ krb5_context ctx; /* Context for Kerberos operations. */
+ char *realm; /* Kerberos realm for configuration. */
+#endif
+};
+
+BEGIN_DECLS
+
+/* Default to a hidden visibility for all internal functions. */
+#pragma GCC visibility push(hidden)
+
+/*
+ * Allocate and free the pam_args struct. We assume that user is a pointer to
+ * a string maintained elsewhere and don't free it here. config must be freed
+ * separately by the caller.
+ */
+struct pam_args *putil_args_new(pam_handle_t *, int flags);
+void putil_args_free(struct pam_args *);
+
+/* Undo default visibility change. */
+#pragma GCC visibility pop
+
+END_DECLS
+
+#endif /* !PAM_UTIL_ARGS_H */
diff --git a/pam-util/logging.c b/pam-util/logging.c
new file mode 100644
index 000000000000..460993315870
--- /dev/null
+++ b/pam-util/logging.c
@@ -0,0 +1,345 @@
+/*
+ * Logging functions for PAM modules.
+ *
+ * Logs errors and debugging messages from PAM modules. The debug versions
+ * only log anything if debugging was enabled; the crit and err versions
+ * always log.
+ *
+ * The canonical version of this file is maintained in the rra-c-util package,
+ * which can be found at <https://www.eyrie.org/~eagle/software/rra-c-util/>.
+ *
+ * Written by Russ Allbery <eagle@eyrie.org>
+ * Copyright 2015, 2018, 2020 Russ Allbery <eagle@eyrie.org>
+ * Copyright 2005-2007, 2009-2010, 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.
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#include <config.h>
+#ifdef HAVE_KRB5
+# include <portable/krb5.h>
+#endif
+#include <portable/pam.h>
+#include <portable/system.h>
+
+#include <syslog.h>
+
+#include <pam-util/args.h>
+#include <pam-util/logging.h>
+
+#ifndef LOG_AUTHPRIV
+# define LOG_AUTHPRIV LOG_AUTH
+#endif
+
+/* Used for iterating through arrays. */
+#define ARRAY_SIZE(array) (sizeof(array) / sizeof((array)[0]))
+
+/*
+ * Mappings of PAM flags to symbolic names for logging when entering a PAM
+ * module function.
+ */
+static const struct {
+ int flag;
+ const char *name;
+} FLAGS[] = {
+ /* clang-format off */
+ {PAM_CHANGE_EXPIRED_AUTHTOK, "expired" },
+ {PAM_DELETE_CRED, "delete" },
+ {PAM_DISALLOW_NULL_AUTHTOK, "nonull" },
+ {PAM_ESTABLISH_CRED, "establish"},
+ {PAM_PRELIM_CHECK, "prelim" },
+ {PAM_REFRESH_CRED, "refresh" },
+ {PAM_REINITIALIZE_CRED, "reinit" },
+ {PAM_SILENT, "silent" },
+ {PAM_UPDATE_AUTHTOK, "update" },
+ /* clang-format on */
+};
+
+
+/*
+ * Utility function to format a message into newly allocated memory, reporting
+ * an error via syslog if vasprintf fails.
+ */
+static char *__attribute__((__format__(printf, 1, 0)))
+format(const char *fmt, va_list args)
+{
+ char *msg;
+
+ if (vasprintf(&msg, fmt, args) < 0) {
+ syslog(LOG_CRIT | LOG_AUTHPRIV, "vasprintf failed: %m");
+ return NULL;
+ }
+ return msg;
+}
+
+
+/*
+ * Log wrapper function that adds the user. Log a message with the given
+ * priority, prefixed by (user <user>) with the account name being
+ * authenticated if known.
+ */
+static void __attribute__((__format__(printf, 3, 0)))
+log_vplain(struct pam_args *pargs, int priority, const char *fmt, va_list args)
+{
+ char *msg;
+
+ if (priority == LOG_DEBUG && (pargs == NULL || !pargs->debug))
+ return;
+ if (pargs != NULL && pargs->user != NULL) {
+ msg = format(fmt, args);
+ if (msg == NULL)
+ return;
+ pam_syslog(pargs->pamh, priority, "(user %s) %s", pargs->user, msg);
+ free(msg);
+ } else if (pargs != NULL) {
+ pam_vsyslog(pargs->pamh, priority, fmt, args);
+ } else {
+ msg = format(fmt, args);
+ if (msg == NULL)
+ return;
+ syslog(priority | LOG_AUTHPRIV, "%s", msg);
+ free(msg);
+ }
+}
+
+
+/*
+ * Wrapper around log_vplain with variadic arguments.
+ */
+static void __attribute__((__format__(printf, 3, 4)))
+log_plain(struct pam_args *pargs, int priority, const char *fmt, ...)
+{
+ va_list args;
+
+ va_start(args, fmt);
+ log_vplain(pargs, priority, fmt, args);
+ va_end(args);
+}
+
+
+/*
+ * Log wrapper function for reporting a PAM error. Log a message with the
+ * given priority, prefixed by (user <user>) with the account name being
+ * authenticated if known, followed by a colon and the formatted PAM error.
+ * However, do not include the colon and the PAM error if the PAM status is
+ * PAM_SUCCESS.
+ */
+static void __attribute__((__format__(printf, 4, 0)))
+log_pam(struct pam_args *pargs, int priority, int status, const char *fmt,
+ va_list args)
+{
+ char *msg;
+
+ if (priority == LOG_DEBUG && (pargs == NULL || !pargs->debug))
+ return;
+ msg = format(fmt, args);
+ if (msg == NULL)
+ return;
+ if (pargs == NULL)
+ log_plain(NULL, priority, "%s", msg);
+ else if (status == PAM_SUCCESS)
+ log_plain(pargs, priority, "%s", msg);
+ else
+ log_plain(pargs, priority, "%s: %s", msg,
+ pam_strerror(pargs->pamh, status));
+ free(msg);
+}
+
+
+/*
+ * The public interfaces. For each common log level (crit, err, and debug),
+ * generate a putil_<level> function and one for _pam. Do this with the
+ * preprocessor to save duplicate code.
+ */
+/* clang-format off */
+#define LOG_FUNCTION(level, priority) \
+ void __attribute__((__format__(printf, 2, 3))) \
+ putil_ ## level(struct pam_args *pargs, const char *fmt, ...) \
+ { \
+ va_list args; \
+ \
+ va_start(args, fmt); \
+ log_vplain(pargs, priority, fmt, args); \
+ va_end(args); \
+ } \
+ void __attribute__((__format__(printf, 3, 4))) \
+ putil_ ## level ## _pam(struct pam_args *pargs, int status, \
+ const char *fmt, ...) \
+ { \
+ va_list args; \
+ \
+ va_start(args, fmt); \
+ log_pam(pargs, priority, status, fmt, args); \
+ va_end(args); \
+ }
+LOG_FUNCTION(crit, LOG_CRIT)
+LOG_FUNCTION(err, LOG_ERR)
+LOG_FUNCTION(notice, LOG_NOTICE)
+LOG_FUNCTION(debug, LOG_DEBUG)
+/* clang-format on */
+
+
+/*
+ * Report entry into a function. Takes the PAM arguments, the function name,
+ * and the flags and maps the flags to symbolic names.
+ */
+void
+putil_log_entry(struct pam_args *pargs, const char *func, int flags)
+{
+ size_t i, length, offset;
+ char *out = NULL, *nout;
+
+ if (!pargs->debug)
+ return;
+ if (flags != 0)
+ for (i = 0; i < ARRAY_SIZE(FLAGS); i++) {
+ if (!(flags & FLAGS[i].flag))
+ continue;
+ if (out == NULL) {
+ out = strdup(FLAGS[i].name);
+ if (out == NULL)
+ break;
+ } else {
+ length = strlen(FLAGS[i].name);
+ nout = realloc(out, strlen(out) + length + 2);
+ if (nout == NULL) {
+ free(out);
+ out = NULL;
+ break;
+ }
+ out = nout;
+ offset = strlen(out);
+ out[offset] = '|';
+ memcpy(out + offset + 1, FLAGS[i].name, length);
+ out[offset + 1 + length] = '\0';
+ }
+ }
+ if (out == NULL)
+ pam_syslog(pargs->pamh, LOG_DEBUG, "%s: entry", func);
+ else {
+ pam_syslog(pargs->pamh, LOG_DEBUG, "%s: entry (%s)", func, out);
+ free(out);
+ }
+}
+
+
+/*
+ * Report an authentication failure. This is a separate function since we
+ * want to include various PAM metadata in the log message and put it in a
+ * standard format. The format here is modeled after the pam_unix
+ * authentication failure message from Linux PAM.
+ */
+void __attribute__((__format__(printf, 2, 3)))
+putil_log_failure(struct pam_args *pargs, const char *fmt, ...)
+{
+ char *msg;
+ va_list args;
+ const char *ruser = NULL;
+ const char *rhost = NULL;
+ const char *tty = NULL;
+ const char *name = NULL;
+
+ if (pargs->user != NULL)
+ name = pargs->user;
+ va_start(args, fmt);
+ msg = format(fmt, args);
+ va_end(args);
+ if (msg == NULL)
+ return;
+ pam_get_item(pargs->pamh, PAM_RUSER, (PAM_CONST void **) &ruser);
+ pam_get_item(pargs->pamh, PAM_RHOST, (PAM_CONST void **) &rhost);
+ pam_get_item(pargs->pamh, PAM_TTY, (PAM_CONST void **) &tty);
+
+ /* clang-format off */
+ pam_syslog(pargs->pamh, LOG_NOTICE, "%s; logname=%s uid=%ld euid=%ld"
+ " tty=%s ruser=%s rhost=%s", msg,
+ (name != NULL) ? name : "",
+ (long) getuid(), (long) geteuid(),
+ (tty != NULL) ? tty : "",
+ (ruser != NULL) ? ruser : "",
+ (rhost != NULL) ? rhost : "");
+ /* clang-format on */
+
+ free(msg);
+}
+
+
+/*
+ * Below are the additional logging functions enabled if built with Kerberos
+ * support, used to report Kerberos errors.
+ */
+#ifdef HAVE_KRB5
+
+
+/*
+ * Log wrapper function for reporting a Kerberos error. Log a message with
+ * the given priority, prefixed by (user <user>) with the account name being
+ * authenticated if known, followed by a colon and the formatted Kerberos
+ * error.
+ */
+__attribute__((__format__(printf, 4, 0))) static void
+log_krb5(struct pam_args *pargs, int priority, int status, const char *fmt,
+ va_list args)
+{
+ char *msg;
+ const char *k5_msg = NULL;
+
+ if (priority == LOG_DEBUG && (pargs == NULL || !pargs->debug))
+ return;
+ msg = format(fmt, args);
+ if (msg == NULL)
+ return;
+ if (pargs != NULL && pargs->ctx != NULL) {
+ k5_msg = krb5_get_error_message(pargs->ctx, status);
+ log_plain(pargs, priority, "%s: %s", msg, k5_msg);
+ } else {
+ log_plain(pargs, priority, "%s", msg);
+ }
+ free(msg);
+ if (k5_msg != NULL)
+ krb5_free_error_message(pargs->ctx, k5_msg);
+}
+
+
+/*
+ * The public interfaces. Do this with the preprocessor to save duplicate
+ * code.
+ */
+/* clang-format off */
+#define LOG_FUNCTION_KRB5(level, priority) \
+ void __attribute__((__format__(printf, 3, 4))) \
+ putil_ ## level ## _krb5(struct pam_args *pargs, int status, \
+ const char *fmt, ...) \
+ { \
+ va_list args; \
+ \
+ va_start(args, fmt); \
+ log_krb5(pargs, priority, status, fmt, args); \
+ va_end(args); \
+ }
+LOG_FUNCTION_KRB5(crit, LOG_CRIT)
+LOG_FUNCTION_KRB5(err, LOG_ERR)
+LOG_FUNCTION_KRB5(notice, LOG_NOTICE)
+LOG_FUNCTION_KRB5(debug, LOG_DEBUG)
+/* clang-format on */
+
+#endif /* HAVE_KRB5 */
diff --git a/pam-util/logging.h b/pam-util/logging.h
new file mode 100644
index 000000000000..bf95ea520ae2
--- /dev/null
+++ b/pam-util/logging.h
@@ -0,0 +1,131 @@
+/*
+ * Interface to standard PAM logging.
+ *
+ * The canonical version of this file is maintained in the rra-c-util package,
+ * which can be found at <https://www.eyrie.org/~eagle/software/rra-c-util/>.
+ *
+ * Written by Russ Allbery <eagle@eyrie.org>
+ * Copyright 2020 Russ Allbery <eagle@eyrie.org>
+ * Copyright 2006-2010, 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.
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#ifndef PAM_UTIL_LOGGING_H
+#define PAM_UTIL_LOGGING_H 1
+
+#include <config.h>
+#include <portable/macros.h>
+#ifdef HAVE_KRB5
+# include <portable/krb5.h>
+#endif
+#include <portable/pam.h>
+
+#include <stddef.h>
+#include <syslog.h>
+
+/* Forward declarations to avoid extra includes. */
+struct pam_args;
+
+BEGIN_DECLS
+
+/* Default to a hidden visibility for all internal functions. */
+#pragma GCC visibility push(hidden)
+
+/*
+ * Error reporting and debugging functions. For each log level, there are two
+ * functions. The _log function just prints out the message it's given. The
+ * _log_pam function does the same but appends the pam_strerror results for
+ * the provided status code if it is not PAM_SUCCESS.
+ */
+void putil_crit(struct pam_args *, const char *, ...)
+ __attribute__((__format__(printf, 2, 3)));
+void putil_crit_pam(struct pam_args *, int, const char *, ...)
+ __attribute__((__format__(printf, 3, 4)));
+void putil_err(struct pam_args *, const char *, ...)
+ __attribute__((__format__(printf, 2, 3)));
+void putil_err_pam(struct pam_args *, int, const char *, ...)
+ __attribute__((__format__(printf, 3, 4)));
+void putil_notice(struct pam_args *, const char *, ...)
+ __attribute__((__format__(printf, 2, 3)));
+void putil_notice_pam(struct pam_args *, int, const char *, ...)
+ __attribute__((__format__(printf, 3, 4)));
+void putil_debug(struct pam_args *, const char *, ...)
+ __attribute__((__format__(printf, 2, 3)));
+void putil_debug_pam(struct pam_args *, int, const char *, ...)
+ __attribute__((__format__(printf, 3, 4)));
+
+/*
+ * The Kerberos versions of the PAM logging and debugging functions, which
+ * report the last Kerberos error. These are only available if built with
+ * Kerberos support.
+ */
+#ifdef HAVE_KRB5
+void putil_crit_krb5(struct pam_args *, int, const char *, ...)
+ __attribute__((__format__(printf, 3, 4)));
+void putil_err_krb5(struct pam_args *, int, const char *, ...)
+ __attribute__((__format__(printf, 3, 4)));
+void putil_notice_krb5(struct pam_args *, int, const char *, ...)
+ __attribute__((__format__(printf, 3, 4)));
+void putil_debug_krb5(struct pam_args *, int, const char *, ...)
+ __attribute__((__format__(printf, 3, 4)));
+#endif
+
+/* Log entry to a PAM function. */
+void putil_log_entry(struct pam_args *, const char *, int flags)
+ __attribute__((__nonnull__));
+
+/* Log an authentication failure. */
+void putil_log_failure(struct pam_args *, const char *, ...)
+ __attribute__((__nonnull__, __format__(printf, 2, 3)));
+
+/* Undo default visibility change. */
+#pragma GCC visibility pop
+
+END_DECLS
+
+/* __func__ is C99, but not provided by all implementations. */
+#if (__STDC_VERSION__ < 199901L) && !defined(__func__)
+# if (__GNUC__ >= 2)
+# define __func__ __FUNCTION__
+# else
+# define __func__ "<unknown>"
+# endif
+#endif
+
+/* Macros to record entry and exit from the main PAM functions. */
+#define ENTRY(args, flags) \
+ do { \
+ if (args->debug) \
+ putil_log_entry((args), __func__, (flags)); \
+ } while (0)
+#define EXIT(args, pamret) \
+ do { \
+ if (args != NULL && args->debug) \
+ pam_syslog( \
+ (args)->pamh, LOG_DEBUG, "%s: exit (%s)", __func__, \
+ ((pamret) == PAM_SUCCESS) \
+ ? "success" \
+ : (((pamret) == PAM_IGNORE) ? "ignore" : "failure")); \
+ } while (0)
+
+#endif /* !PAM_UTIL_LOGGING_H */
diff --git a/pam-util/options.c b/pam-util/options.c
new file mode 100644
index 000000000000..052e528a5be4
--- /dev/null
+++ b/pam-util/options.c
@@ -0,0 +1,720 @@
+/*
+ * Parse PAM options into a struct.
+ *
+ * Given a struct in which to store options and a specification for what
+ * options go where, parse both the PAM configuration options and any options
+ * from a Kerberos krb5.conf file and fill out the struct.
+ *
+ * The canonical version of this file is maintained in the rra-c-util package,
+ * which can be found at <https://www.eyrie.org/~eagle/software/rra-c-util/>.
+ *
+ * Written by Russ Allbery <eagle@eyrie.org>
+ * Copyright 2020 Russ Allbery <eagle@eyrie.org>
+ * Copyright 2006-2008, 2010-2011, 2013-2014
+ * 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.
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#include <config.h>
+#ifdef HAVE_KRB5
+# include <portable/krb5.h>
+#endif
+#include <portable/system.h>
+
+#include <errno.h>
+
+#include <pam-util/args.h>
+#include <pam-util/logging.h>
+#include <pam-util/options.h>
+#include <pam-util/vector.h>
+
+/* Used for unused parameters to silence gcc warnings. */
+#define UNUSED __attribute__((__unused__))
+
+/*
+ * Macros used to resolve a void * pointer to the configuration struct and an
+ * offset into a pointer to the appropriate type. Scary violations of the C
+ * type system lurk here.
+ */
+/* clang-format off */
+#define CONF_BOOL(c, o) (bool *) (void *)((char *) (c) + (o))
+#define CONF_NUMBER(c, o) (long *) (void *)((char *) (c) + (o))
+#define CONF_STRING(c, o) (char **) (void *)((char *) (c) + (o))
+#define CONF_LIST(c, o) (struct vector **)(void *)((char *) (c) + (o))
+/* clang-format on */
+
+/*
+ * We can only process times properly if we have Kerberos. If not, they fall
+ * back to longs and we convert them as numbers.
+ */
+/* clang-format off */
+#ifdef HAVE_KRB5
+# define CONF_TIME(c, o) (krb5_deltat *)(void *)((char *) (c) + (o))
+#else
+# define CONF_TIME(c, o) (long *) (void *)((char *) (c) + (o))
+#endif
+/* clang-format on */
+
+
+/*
+ * Set a vector argument to its default. This needs to do a deep copy of the
+ * vector so that we can safely free it when freeing the configuration. Takes
+ * the PAM argument struct, the pointer in which to store the vector, and the
+ * default vector. Returns true if the default was set correctly and false on
+ * memory allocation failure, which is also reported with putil_crit().
+ */
+static bool
+copy_default_list(struct pam_args *args, struct vector **setting,
+ const struct vector *defval)
+{
+ struct vector *result = NULL;
+
+ *setting = NULL;
+ if (defval != NULL && defval->strings != NULL) {
+ result = vector_copy(defval);
+ if (result == NULL) {
+ putil_crit(args, "cannot allocate memory: %s", strerror(errno));
+ return false;
+ }
+ *setting = result;
+ }
+ return true;
+}
+
+
+/*
+ * Set a vector argument to a default based on a string. Takes the PAM
+ * argument struct,t he pointer into which to store the vector, and the
+ * default string. Returns true if the default was set correctly and false on
+ * memory allocation failure, which is also reported with putil_crit().
+ */
+static bool
+default_list_string(struct pam_args *args, struct vector **setting,
+ const char *defval)
+{
+ struct vector *result = NULL;
+
+ *setting = NULL;
+ if (defval != NULL) {
+ result = vector_split_multi(defval, " \t,", NULL);
+ if (result == NULL) {
+ putil_crit(args, "cannot allocate memory: %s", strerror(errno));
+ return false;
+ }
+ *setting = result;
+ }
+ return true;
+}
+
+
+/*
+ * Set the defaults for the PAM configuration. Takes the PAM arguments, an
+ * option table defined as above, and the number of entries in the table. The
+ * config member of the args struct must already be allocated. Returns true
+ * on success and false on error (generally out of memory). Errors will
+ * already be reported using putil_crit().
+ *
+ * This function must be called before either putil_args_krb5() or
+ * putil_args_parse(), since neither of those functions set defaults.
+ */
+bool
+putil_args_defaults(struct pam_args *args, const struct option options[],
+ size_t optlen)
+{
+ size_t opt;
+
+ for (opt = 0; opt < optlen; opt++) {
+ bool *bp;
+ long *lp;
+#ifdef HAVE_KRB5
+ krb5_deltat *tp;
+#else
+ long *tp;
+#endif
+ char **sp;
+ struct vector **vp;
+
+ switch (options[opt].type) {
+ case TYPE_BOOLEAN:
+ bp = CONF_BOOL(args->config, options[opt].location);
+ *bp = options[opt].defaults.boolean;
+ break;
+ case TYPE_NUMBER:
+ lp = CONF_NUMBER(args->config, options[opt].location);
+ *lp = options[opt].defaults.number;
+ break;
+ case TYPE_TIME:
+ tp = CONF_TIME(args->config, options[opt].location);
+ *tp = (krb5_deltat) options[opt].defaults.number;
+ break;
+ case TYPE_STRING:
+ sp = CONF_STRING(args->config, options[opt].location);
+ if (options[opt].defaults.string == NULL)
+ *sp = NULL;
+ else {
+ *sp = strdup(options[opt].defaults.string);
+ if (*sp == NULL) {
+ putil_crit(args, "cannot allocate memory: %s",
+ strerror(errno));
+ return false;
+ }
+ }
+ break;
+ case TYPE_LIST:
+ vp = CONF_LIST(args->config, options[opt].location);
+ if (!copy_default_list(args, vp, options[opt].defaults.list))
+ return false;
+ break;
+ case TYPE_STRLIST:
+ vp = CONF_LIST(args->config, options[opt].location);
+ if (!default_list_string(args, vp, options[opt].defaults.string))
+ return false;
+ break;
+ }
+ }
+ return true;
+}
+
+
+#ifdef HAVE_KRB5
+/*
+ * Load a boolean option from Kerberos appdefaults. Takes the PAM argument
+ * struct, the section name, the realm, the option, and the result location.
+ *
+ * The stupidity of rewriting the realm argument into a krb5_data is required
+ * by MIT Kerberos.
+ */
+static void
+default_boolean(struct pam_args *args, const char *section, const char *realm,
+ const char *opt, bool *result)
+{
+ int tmp;
+# ifdef HAVE_KRB5_REALM
+ krb5_const_realm rdata = realm;
+# else
+ krb5_data realm_struct;
+ const krb5_data *rdata;
+
+ if (realm == NULL)
+ rdata = NULL;
+ else {
+ rdata = &realm_struct;
+ realm_struct.magic = KV5M_DATA;
+ realm_struct.data = (void *) realm;
+ realm_struct.length = (unsigned int) strlen(realm);
+ }
+# endif
+
+ /*
+ * The MIT version of krb5_appdefault_boolean takes an int * and the
+ * Heimdal version takes a krb5_boolean *, so hope that Heimdal always
+ * defines krb5_boolean to int or this will require more portability work.
+ */
+ krb5_appdefault_boolean(args->ctx, section, rdata, opt, *result, &tmp);
+ *result = tmp;
+}
+
+
+/*
+ * Load a number option from Kerberos appdefaults. Takes the PAM argument
+ * struct, the section name, the realm, the option, and the result location.
+ * The native interface doesn't support numbers, so we actually read a string
+ * and then convert.
+ */
+static void
+default_number(struct pam_args *args, const char *section, const char *realm,
+ const char *opt, long *result)
+{
+ char *tmp = NULL;
+ char *end;
+ long value;
+# ifdef HAVE_KRB5_REALM
+ krb5_const_realm rdata = realm;
+# else
+ krb5_data realm_struct;
+ const krb5_data *rdata;
+
+ if (realm == NULL)
+ rdata = NULL;
+ else {
+ rdata = &realm_struct;
+ realm_struct.magic = KV5M_DATA;
+ realm_struct.data = (void *) realm;
+ realm_struct.length = (unsigned int) strlen(realm);
+ }
+# endif
+
+ krb5_appdefault_string(args->ctx, section, rdata, opt, "", &tmp);
+ if (tmp != NULL && tmp[0] != '\0') {
+ errno = 0;
+ value = strtol(tmp, &end, 10);
+ if (errno != 0 || *end != '\0')
+ putil_err(args, "invalid number in krb5.conf setting for %s: %s",
+ opt, tmp);
+ else
+ *result = value;
+ }
+ free(tmp);
+}
+
+
+/*
+ * Load a time option from Kerberos appdefaults. Takes the PAM argument
+ * struct, the section name, the realm, the option, and the result location.
+ * The native interface doesn't support numbers, so we actually read a string
+ * and then convert using krb5_string_to_deltat.
+ */
+static void
+default_time(struct pam_args *args, const char *section, const char *realm,
+ const char *opt, krb5_deltat *result)
+{
+ char *tmp = NULL;
+ krb5_deltat value;
+ krb5_error_code retval;
+# ifdef HAVE_KRB5_REALM
+ krb5_const_realm rdata = realm;
+# else
+ krb5_data realm_struct;
+ const krb5_data *rdata;
+
+ if (realm == NULL)
+ rdata = NULL;
+ else {
+ rdata = &realm_struct;
+ realm_struct.magic = KV5M_DATA;
+ realm_struct.data = (void *) realm;
+ realm_struct.length = (unsigned int) strlen(realm);
+ }
+# endif
+
+ krb5_appdefault_string(args->ctx, section, rdata, opt, "", &tmp);
+ if (tmp != NULL && tmp[0] != '\0') {
+ retval = krb5_string_to_deltat(tmp, &value);
+ if (retval != 0)
+ putil_err(args, "invalid time in krb5.conf setting for %s: %s",
+ opt, tmp);
+ else
+ *result = value;
+ }
+ free(tmp);
+}
+
+
+/*
+ * Load a string option from Kerberos appdefaults. Takes the PAM argument
+ * struct, the section name, the realm, the option, and the result location.
+ *
+ * This requires an annoying workaround because one cannot specify a default
+ * value of NULL with MIT Kerberos, since MIT Kerberos unconditionally calls
+ * strdup on the default value. There's also no way to determine if memory
+ * allocation failed while parsing or while setting the default value, so we
+ * don't return an error code.
+ */
+static void
+default_string(struct pam_args *args, const char *section, const char *realm,
+ const char *opt, char **result)
+{
+ char *value = NULL;
+# ifdef HAVE_KRB5_REALM
+ krb5_const_realm rdata = realm;
+# else
+ krb5_data realm_struct;
+ const krb5_data *rdata;
+
+ if (realm == NULL)
+ rdata = NULL;
+ else {
+ rdata = &realm_struct;
+ realm_struct.magic = KV5M_DATA;
+ realm_struct.data = (void *) realm;
+ realm_struct.length = (unsigned int) strlen(realm);
+ }
+# endif
+
+ krb5_appdefault_string(args->ctx, section, rdata, opt, "", &value);
+ if (value != NULL) {
+ if (value[0] == '\0')
+ free(value);
+ else {
+ if (*result != NULL)
+ free(*result);
+ *result = value;
+ }
+ }
+}
+
+
+/*
+ * Load a list option from Kerberos appdefaults. Takes the PAM arguments, the
+ * context, the section name, the realm, the option, and the result location.
+ *
+ * We may fail here due to memory allocation problems, in which case we return
+ * false to indicate that PAM setup should abort.
+ */
+static bool
+default_list(struct pam_args *args, const char *section, const char *realm,
+ const char *opt, struct vector **result)
+{
+ char *tmp = NULL;
+ struct vector *value;
+
+ default_string(args, section, realm, opt, &tmp);
+ if (tmp != NULL) {
+ value = vector_split_multi(tmp, " \t,", NULL);
+ if (value == NULL) {
+ free(tmp);
+ putil_crit(args, "cannot allocate vector: %s", strerror(errno));
+ return false;
+ }
+ if (*result != NULL)
+ vector_free(*result);
+ *result = value;
+ free(tmp);
+ }
+ return true;
+}
+
+
+/*
+ * The public interface for getting configuration information from krb5.conf.
+ * Takes the PAM arguments, the krb5.conf section, the options specification,
+ * and the number of options in the options table. The config member of the
+ * args struct must already be allocated. Iterate through the option list
+ * and, for every option where krb5_config is true, see if it's set in the
+ * Kerberos configuration.
+ *
+ * This looks obviously slow, but there haven't been any reports of problems
+ * and there's no better interface. But if you wonder where the cycles in
+ * your computer are getting wasted, well, here's one place.
+ */
+bool
+putil_args_krb5(struct pam_args *args, const char *section,
+ const struct option options[], size_t optlen)
+{
+ size_t i;
+ char *realm;
+ bool free_realm = false;
+
+ /* Having no local realm may be intentional, so don't report an error. */
+ if (args->realm != NULL)
+ realm = args->realm;
+ else {
+ if (krb5_get_default_realm(args->ctx, &realm) < 0)
+ realm = NULL;
+ else
+ free_realm = true;
+ }
+ for (i = 0; i < optlen; i++) {
+ const struct option *opt = &options[i];
+
+ if (!opt->krb5_config)
+ continue;
+ switch (opt->type) {
+ case TYPE_BOOLEAN:
+ default_boolean(args, section, realm, opt->name,
+ CONF_BOOL(args->config, opt->location));
+ break;
+ case TYPE_NUMBER:
+ default_number(args, section, realm, opt->name,
+ CONF_NUMBER(args->config, opt->location));
+ break;
+ case TYPE_TIME:
+ default_time(args, section, realm, opt->name,
+ CONF_TIME(args->config, opt->location));
+ break;
+ case TYPE_STRING:
+ default_string(args, section, realm, opt->name,
+ CONF_STRING(args->config, opt->location));
+ break;
+ case TYPE_LIST:
+ case TYPE_STRLIST:
+ if (!default_list(args, section, realm, opt->name,
+ CONF_LIST(args->config, opt->location)))
+ return false;
+ break;
+ }
+ }
+ if (free_realm)
+ krb5_free_default_realm(args->ctx, realm);
+ return true;
+}
+
+#else /* !HAVE_KRB5 */
+
+/*
+ * Stub function for getting configuration information from krb5.conf used
+ * when the PAM module is not built with Kerberos support so that the function
+ * can be called unconditionally.
+ */
+bool
+putil_args_krb5(struct pam_args *args UNUSED, const char *section UNUSED,
+ const struct option options[] UNUSED, size_t optlen UNUSED)
+{
+ return true;
+}
+
+#endif /* !HAVE_KRB5 */
+
+
+/*
+ * bsearch comparison function for finding PAM arguments in an array of struct
+ * options. We only compare up to the first '=' in the key so that we don't
+ * have to munge the string before searching.
+ */
+static int
+option_compare(const void *key, const void *member)
+{
+ const char *string = key;
+ const struct option *option = member;
+ const char *p;
+ size_t length;
+ int result;
+
+ p = strchr(string, '=');
+ if (p == NULL)
+ return strcmp(string, option->name);
+ else {
+ length = (size_t)(p - string);
+ if (length == 0)
+ return -1;
+ result = strncmp(string, option->name, length);
+ if (result == 0 && strlen(option->name) > length)
+ return -1;
+ return result;
+ }
+}
+
+
+/*
+ * Given a PAM argument, convert the value portion of the argument to a
+ * boolean and store it in the provided location. If the value is missing,
+ * that's equivalent to a true value. If the value is invalid, report an
+ * error and leave the location unchanged.
+ */
+static void
+convert_boolean(struct pam_args *args, const char *arg, bool *setting)
+{
+ const char *value;
+
+ value = strchr(arg, '=');
+ if (value == NULL)
+ *setting = true;
+ else {
+ value++;
+ /* clang-format off */
+ if ( strcasecmp(value, "true") == 0
+ || strcasecmp(value, "yes") == 0
+ || strcasecmp(value, "on") == 0
+ || strcmp (value, "1") == 0)
+ *setting = true;
+ else if ( strcasecmp(value, "false") == 0
+ || strcasecmp(value, "no") == 0
+ || strcasecmp(value, "off") == 0
+ || strcmp (value, "0") == 0)
+ *setting = false;
+ else
+ putil_err(args, "invalid boolean in setting: %s", arg);
+ /* clang-format on */
+ }
+}
+
+
+/*
+ * Given a PAM argument, convert the value portion of the argument to a number
+ * and store it in the provided location. If the value is missing or isn't a
+ * number, report an error and leave the location unchanged.
+ */
+static void
+convert_number(struct pam_args *args, const char *arg, long *setting)
+{
+ const char *value;
+ char *end;
+ long result;
+
+ value = strchr(arg, '=');
+ if (value == NULL || value[1] == '\0') {
+ putil_err(args, "value missing for option %s", arg);
+ return;
+ }
+ errno = 0;
+ result = strtol(value + 1, &end, 10);
+ if (errno != 0 || *end != '\0') {
+ putil_err(args, "invalid number in setting: %s", arg);
+ return;
+ }
+ *setting = result;
+}
+
+
+/*
+ * Given a PAM argument, convert the value portion of the argument from a
+ * Kerberos time string to a krb5_deltat and store it in the provided
+ * location. If the value is missing or isn't a number, report an error and
+ * leave the location unchanged.
+ */
+#ifdef HAVE_KRB5
+static void
+convert_time(struct pam_args *args, const char *arg, krb5_deltat *setting)
+{
+ const char *value;
+ krb5_deltat result;
+ krb5_error_code retval;
+
+ value = strchr(arg, '=');
+ if (value == NULL || value[1] == '\0') {
+ putil_err(args, "value missing for option %s", arg);
+ return;
+ }
+ retval = krb5_string_to_deltat((char *) value + 1, &result);
+ if (retval != 0)
+ putil_err(args, "bad time value in setting: %s", arg);
+ else
+ *setting = result;
+}
+
+#else /* HAVE_KRB5 */
+
+static void
+convert_time(struct pam_args *args, const char *arg, long *setting)
+{
+ convert_number(args, arg, setting);
+}
+
+#endif /* !HAVE_KRB5 */
+
+
+/*
+ * Given a PAM argument, convert the value portion of the argument to a string
+ * and store it in the provided location. If the value is missing, report an
+ * error and leave the location unchanged, returning true since that's a
+ * non-fatal error. If memory allocation fails, return false, since PAM setup
+ * should abort.
+ */
+static bool
+convert_string(struct pam_args *args, const char *arg, char **setting)
+{
+ const char *value;
+ char *result;
+
+ value = strchr(arg, '=');
+ if (value == NULL) {
+ putil_err(args, "value missing for option %s", arg);
+ return true;
+ }
+ result = strdup(value + 1);
+ if (result == NULL) {
+ putil_crit(args, "cannot allocate memory: %s", strerror(errno));
+ return false;
+ }
+ free(*setting);
+ *setting = result;
+ return true;
+}
+
+
+/*
+ * Given a PAM argument, convert the value portion of the argument to a vector
+ * and store it in the provided location. If the value is missing, report an
+ * error and leave the location unchanged, returning true since that's a
+ * non-fatal error. If memory allocation fails, return false, since PAM setup
+ * should abort.
+ */
+static bool
+convert_list(struct pam_args *args, const char *arg, struct vector **setting)
+{
+ const char *value;
+ struct vector *result;
+
+ value = strchr(arg, '=');
+ if (value == NULL) {
+ putil_err(args, "value missing for option %s", arg);
+ return true;
+ }
+ result = vector_split_multi(value + 1, " \t,", NULL);
+ if (result == NULL) {
+ putil_crit(args, "cannot allocate vector: %s", strerror(errno));
+ return false;
+ }
+ vector_free(*setting);
+ *setting = result;
+ return true;
+}
+
+
+/*
+ * Parse the PAM arguments. Takes the PAM argument struct, the argument count
+ * and vector, the option table, and the number of elements in the option
+ * table. The config member of the args struct must already be allocated.
+ * Returns true on success and false on error. An error return should be
+ * considered fatal. Report errors using putil_crit(). Unknown options will
+ * also be diagnosed (to syslog at LOG_ERR using putil_err()), but are not
+ * considered fatal errors and will still return true.
+ *
+ * If options should be retrieved from krb5.conf, call putil_args_krb5()
+ * first, before calling this function.
+ */
+bool
+putil_args_parse(struct pam_args *args, int argc, const char *argv[],
+ const struct option options[], size_t optlen)
+{
+ int i;
+ const struct option *option;
+
+ /*
+ * Second pass: find each option we were given and set the corresponding
+ * configuration parameter.
+ */
+ for (i = 0; i < argc; i++) {
+ option = bsearch(argv[i], options, optlen, sizeof(struct option),
+ option_compare);
+ if (option == NULL) {
+ putil_err(args, "unknown option %s", argv[i]);
+ continue;
+ }
+ switch (option->type) {
+ case TYPE_BOOLEAN:
+ convert_boolean(args, argv[i],
+ CONF_BOOL(args->config, option->location));
+ break;
+ case TYPE_NUMBER:
+ convert_number(args, argv[i],
+ CONF_NUMBER(args->config, option->location));
+ break;
+ case TYPE_TIME:
+ convert_time(args, argv[i],
+ CONF_TIME(args->config, option->location));
+ break;
+ case TYPE_STRING:
+ if (!convert_string(args, argv[i],
+ CONF_STRING(args->config, option->location)))
+ return false;
+ break;
+ case TYPE_LIST:
+ case TYPE_STRLIST:
+ if (!convert_list(args, argv[i],
+ CONF_LIST(args->config, option->location)))
+ return false;
+ break;
+ }
+ }
+ return true;
+}
diff --git a/pam-util/options.h b/pam-util/options.h
new file mode 100644
index 000000000000..062d095e8e7e
--- /dev/null
+++ b/pam-util/options.h
@@ -0,0 +1,205 @@
+/*
+ * Interface to PAM option parsing.
+ *
+ * This interface defines a lot of macros and types with very short names, and
+ * hence without a lot of namespace protection. It should be included only in
+ * the file that's doing the option parsing and not elsewhere to remove the
+ * risk of clashes.
+ *
+ * The canonical version of this file is maintained in the rra-c-util package,
+ * which can be found at <https://www.eyrie.org/~eagle/software/rra-c-util/>.
+ *
+ * Written by Russ Allbery <eagle@eyrie.org>
+ * Copyright 2020 Russ Allbery <eagle@eyrie.org>
+ * Copyright 2010-2011, 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.
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#ifndef PAM_UTIL_OPTIONS_H
+#define PAM_UTIL_OPTIONS_H 1
+
+#include <config.h>
+#ifdef HAVE_KRB5
+# include <portable/krb5.h>
+#endif
+#include <portable/macros.h>
+#include <portable/stdbool.h>
+
+#include <stddef.h>
+
+/* Forward declarations to avoid additional includes. */
+struct vector;
+
+/*
+ * The types of configuration values possible. STRLIST is a list data type
+ * that takes its default from a string value instead of a vector. For
+ * STRLIST, the default string value will be turned into a vector by splitting
+ * on comma, space, and tab. (This is the same as would be done with the
+ * value of a PAM setting when the target variable type is a list.)
+ */
+enum type
+{
+ TYPE_BOOLEAN,
+ TYPE_NUMBER,
+ TYPE_TIME,
+ TYPE_STRING,
+ TYPE_LIST,
+ TYPE_STRLIST
+};
+
+/*
+ * Each configuration option is defined by a struct option. This specifies
+ * the name of the option, its offset into the configuration struct, whether
+ * it can be specified in a krb5.conf file, its type, and its default value if
+ * not set. Note that PAM configuration options are specified as strings, so
+ * there's no native way of representing a list argument. List values are
+ * always initialized by splitting a string on whitespace or commas.
+ *
+ * The default value should really be a union, but you can't initialize unions
+ * properly in C in a static initializer without C99 named initializer
+ * support, which we can't (yet) assume. So use a struct instead, and
+ * initialize all the members, even though we'll only care about one of them.
+ *
+ * Note that numbers set in the configuration struct created by this interface
+ * must be longs, not ints. There is currently no provision for unsigned
+ * numbers.
+ *
+ * Times take their default from defaults.number. The difference between time
+ * and number is in the parsing of a user-supplied value and the type of the
+ * stored attribute.
+ */
+struct option {
+ const char *name;
+ size_t location;
+ bool krb5_config;
+ enum type type;
+ struct {
+ bool boolean;
+ long number;
+ const char *string;
+ const struct vector *list;
+ } defaults;
+};
+
+/*
+ * The following macros are helpers to make it easier to define the table that
+ * specifies how to convert the configuration into a struct. They provide an
+ * initializer for the type and default fields.
+ */
+/* clang-format off */
+#define BOOL(def) TYPE_BOOLEAN, { (def), 0, NULL, NULL }
+#define NUMBER(def) TYPE_NUMBER, { 0, (def), NULL, NULL }
+#define TIME(def) TYPE_TIME, { 0, (def), NULL, NULL }
+#define STRING(def) TYPE_STRING, { 0, 0, (def), NULL }
+#define LIST(def) TYPE_LIST, { 0, 0, NULL, (def) }
+#define STRLIST(def) TYPE_STRLIST, { 0, 0, (def), NULL }
+/* clang-format on */
+
+/*
+ * The user of this file should also define a macro of the following form:
+ *
+ * #define K(name) (#name), offsetof(struct pam_config, name)
+ *
+ * Then, the definition of the necessary table for building the configuration
+ * will look something like this:
+ *
+ * const struct option options[] = {
+ * { K(aklog_homedir), true, BOOL (false) },
+ * { K(cells), true, LIST (NULL) },
+ * { K(debug), false, BOOL (false) },
+ * { K(minimum_uid), true, NUMBER (0) },
+ * { K(program), true, STRING (NULL) },
+ * };
+ *
+ * which provides a nice, succinct syntax for creating the table. The options
+ * MUST be in sorted order, since the options parsing code does a binary
+ * search.
+ */
+
+BEGIN_DECLS
+
+/* Default to a hidden visibility for all internal functions. */
+#pragma GCC visibility push(hidden)
+
+/*
+ * Set the defaults for the PAM configuration. Takes the PAM arguments, an
+ * option table defined as above, and the number of entries in the table. The
+ * config member of the args struct must already be allocated. Returns true
+ * on success and false on error (generally out of memory). Errors will
+ * already be reported using putil_crit().
+ *
+ * This function must be called before either putil_args_krb5() or
+ * putil_args_parse(), since neither of those functions set defaults.
+ */
+bool putil_args_defaults(struct pam_args *, const struct option options[],
+ size_t optlen) __attribute__((__nonnull__));
+
+/*
+ * Fill out options from krb5.conf. Takes the PAM args structure, the name of
+ * the section for the software being configured, an option table defined as
+ * above, and the number of entries in the table. The config member of the
+ * args struct must already be allocated. Only those options whose
+ * krb5_config attribute is true will be considered.
+ *
+ * This code automatically checks for configuration settings scoped to the
+ * local realm, so the default realm should be set before calling this
+ * function. If that's done based on a configuration option, one may need to
+ * pre-parse the configuration options.
+ *
+ * Returns true on success and false on an error. An error return should be
+ * considered fatal. Errors will already be reported using putil_crit*() or
+ * putil_err*() as appropriate. If Kerberos is not available, returns without
+ * doing anything.
+ *
+ * putil_args_defaults() should be called before this function.
+ */
+bool putil_args_krb5(struct pam_args *, const char *section,
+ const struct option options[], size_t optlen)
+ __attribute__((__nonnull__));
+
+/*
+ * Parse the PAM arguments and fill out the provided struct. Takes the PAM
+ * arguments, the argument count and vector, an option table defined as above,
+ * and the number of entries in the table. The config member of the args
+ * struct must already be allocated. Returns true on success and false on
+ * error. An error return should be considered fatal. Errors will already be
+ * reported using putil_crit(). Unknown options will also be diagnosed (to
+ * syslog at LOG_ERR using putil_err()), but are not considered fatal errors
+ * and will still return true.
+ *
+ * The krb5_config option of the option configuration is ignored by this
+ * function. If options should be retrieved from krb5.conf, call
+ * putil_args_krb5() first, before calling this function.
+ *
+ * putil_args_defaults() should be called before this function.
+ */
+bool putil_args_parse(struct pam_args *, int argc, const char *argv[],
+ const struct option options[], size_t optlen)
+ __attribute__((__nonnull__));
+
+/* Undo default visibility change. */
+#pragma GCC visibility pop
+
+END_DECLS
+
+#endif /* !PAM_UTIL_OPTIONS_H */
diff --git a/pam-util/vector.c b/pam-util/vector.c
new file mode 100644
index 000000000000..012a9aef24a3
--- /dev/null
+++ b/pam-util/vector.c
@@ -0,0 +1,289 @@
+/*
+ * Vector handling (counted lists of char *'s).
+ *
+ * A vector is a table for handling a list of strings with less overhead than
+ * linked list. The intention is for vectors, once allocated, to be reused;
+ * this saves on memory allocations once the array of char *'s reaches a
+ * stable size.
+ *
+ * This is based on the util/vector.c library, but that library uses xmalloc
+ * routines to exit the program if memory allocation fails. This is a
+ * modified version of the vector library that instead returns false on
+ * failure to allocate memory, allowing the caller to do appropriate recovery.
+ *
+ * Vectors require list of strings, not arbitrary binary data, and cannot
+ * handle data elements containing nul characters.
+ *
+ * Only the portions of the vector library used by PAM modules are
+ * implemented.
+ *
+ * The canonical version of this file is maintained in the rra-c-util package,
+ * which can be found at <https://www.eyrie.org/~eagle/software/rra-c-util/>.
+ *
+ * Written by Russ Allbery <eagle@eyrie.org>
+ * Copyright 2017-2018 Russ Allbery <eagle@eyrie.org>
+ * Copyright 2010-2011, 2014
+ * The Board of Trustees of the Leland Stanford Junior University
+ *
+ * Copying and distribution of this file, with or without modification, are
+ * permitted in any medium without royalty provided the copyright notice and
+ * this notice are preserved. This file is offered as-is, without any
+ * warranty.
+ *
+ * SPDX-License-Identifier: FSFAP
+ */
+
+#include <config.h>
+#include <portable/system.h>
+
+#include <pam-util/vector.h>
+
+
+/*
+ * Allocate a new, empty vector. Returns NULL if memory allocation fails.
+ */
+struct vector *
+vector_new(void)
+{
+ struct vector *vector;
+
+ vector = calloc(1, sizeof(struct vector));
+ vector->allocated = 1;
+ vector->strings = calloc(1, sizeof(char *));
+ return vector;
+}
+
+
+/*
+ * Allocate a new vector that's a copy of an existing vector. Returns NULL if
+ * memory allocation fails.
+ */
+struct vector *
+vector_copy(const struct vector *old)
+{
+ struct vector *vector;
+ size_t i;
+
+ vector = vector_new();
+ if (!vector_resize(vector, old->count)) {
+ vector_free(vector);
+ return NULL;
+ }
+ vector->count = old->count;
+ for (i = 0; i < old->count; i++) {
+ vector->strings[i] = strdup(old->strings[i]);
+ if (vector->strings[i] == NULL) {
+ vector_free(vector);
+ return NULL;
+ }
+ }
+ return vector;
+}
+
+
+/*
+ * Resize a vector (using reallocarray to resize the table). Return false if
+ * memory allocation fails.
+ */
+bool
+vector_resize(struct vector *vector, size_t size)
+{
+ size_t i;
+ char **strings;
+
+ if (vector->count > size) {
+ for (i = size; i < vector->count; i++)
+ free(vector->strings[i]);
+ vector->count = size;
+ }
+ if (size == 0)
+ size = 1;
+ strings = reallocarray(vector->strings, size, sizeof(char *));
+ if (strings == NULL)
+ return false;
+ vector->strings = strings;
+ vector->allocated = size;
+ return true;
+}
+
+
+/*
+ * Add a new string to the vector, resizing the vector as necessary. The
+ * vector is resized an element at a time; if a lot of resizes are expected,
+ * vector_resize should be called explicitly with a more suitable size.
+ * Return false if memory allocation fails.
+ */
+bool
+vector_add(struct vector *vector, const char *string)
+{
+ size_t next = vector->count;
+
+ if (vector->count == vector->allocated)
+ if (!vector_resize(vector, vector->allocated + 1))
+ return false;
+ vector->strings[next] = strdup(string);
+ if (vector->strings[next] == NULL)
+ return false;
+ vector->count++;
+ return true;
+}
+
+
+/*
+ * Empty a vector but keep the allocated memory for the pointer table.
+ */
+void
+vector_clear(struct vector *vector)
+{
+ size_t i;
+
+ for (i = 0; i < vector->count; i++)
+ if (vector->strings[i] != NULL)
+ free(vector->strings[i]);
+ vector->count = 0;
+}
+
+
+/*
+ * Free a vector completely.
+ */
+void
+vector_free(struct vector *vector)
+{
+ if (vector == NULL)
+ return;
+ vector_clear(vector);
+ free(vector->strings);
+ free(vector);
+}
+
+
+/*
+ * Given a vector that we may be reusing, clear it out. If the first argument
+ * is NULL, allocate a new vector. Used by vector_split*. Returns NULL if
+ * memory allocation fails.
+ */
+static struct vector *
+vector_reuse(struct vector *vector)
+{
+ if (vector == NULL)
+ return vector_new();
+ else {
+ vector_clear(vector);
+ return vector;
+ }
+}
+
+
+/*
+ * Given a string and a set of separators expressed as a string, count the
+ * number of strings that it will split into when splitting on those
+ * separators.
+ */
+static size_t
+split_multi_count(const char *string, const char *seps)
+{
+ const char *p;
+ size_t count;
+
+ if (*string == '\0')
+ return 0;
+ for (count = 1, p = string + 1; *p != '\0'; p++)
+ if (strchr(seps, *p) != NULL && strchr(seps, p[-1]) == NULL)
+ count++;
+
+ /*
+ * If the string ends in separators, we've overestimated the number of
+ * strings by one.
+ */
+ if (strchr(seps, p[-1]) != NULL)
+ count--;
+ return count;
+}
+
+
+/*
+ * Given a string, split it at any of the provided separators to form a
+ * vector, copying each string segment. If the third argument isn't NULL,
+ * reuse that vector; otherwise, allocate a new one. Any number of
+ * consecutive separators are considered a single separator. Returns NULL on
+ * memory allocation failure, after which the provided vector may only have
+ * partial results.
+ */
+struct vector *
+vector_split_multi(const char *string, const char *seps, struct vector *vector)
+{
+ const char *p, *start;
+ size_t i, count;
+ bool created = false;
+
+ if (vector == NULL)
+ created = true;
+ vector = vector_reuse(vector);
+ if (vector == NULL)
+ return NULL;
+
+ count = split_multi_count(string, seps);
+ if (vector->allocated < count && !vector_resize(vector, count))
+ goto fail;
+
+ vector->count = 0;
+ for (start = string, p = string, i = 0; *p != '\0'; p++)
+ if (strchr(seps, *p) != NULL) {
+ if (start != p) {
+ vector->strings[i] = strndup(start, (size_t)(p - start));
+ if (vector->strings[i] == NULL)
+ goto fail;
+ i++;
+ vector->count++;
+ }
+ start = p + 1;
+ }
+ if (start != p) {
+ vector->strings[i] = strndup(start, (size_t)(p - start));
+ if (vector->strings[i] == NULL)
+ goto fail;
+ vector->count++;
+ }
+ return vector;
+
+fail:
+ if (created)
+ vector_free(vector);
+ return NULL;
+}
+
+
+/*
+ * Given a vector and a path to a program, exec that program with the vector
+ * as its arguments. This requires adding a NULL terminator to the vector and
+ * casting it appropriately. Returns 0 on success and -1 on error, like exec
+ * does.
+ */
+int
+vector_exec(const char *path, struct vector *vector)
+{
+ if (vector->allocated == vector->count)
+ if (!vector_resize(vector, vector->count + 1))
+ return -1;
+ vector->strings[vector->count] = NULL;
+ return execv(path, (char *const *) vector->strings);
+}
+
+
+/*
+ * Given a vector, a path to a program, and the environment, exec that program
+ * with the vector as its arguments and the given environment. This requires
+ * adding a NULL terminator to the vector and casting it appropriately.
+ * Returns 0 on success and -1 on error, like exec does.
+ */
+int
+vector_exec_env(const char *path, struct vector *vector,
+ const char *const env[])
+{
+ if (vector->allocated == vector->count)
+ if (!vector_resize(vector, vector->count + 1))
+ return -1;
+ vector->strings[vector->count] = NULL;
+ return execve(path, (char *const *) vector->strings, (char *const *) env);
+}
diff --git a/pam-util/vector.h b/pam-util/vector.h
new file mode 100644
index 000000000000..351c53f8d40b
--- /dev/null
+++ b/pam-util/vector.h
@@ -0,0 +1,120 @@
+/*
+ * Prototypes for vector handling.
+ *
+ * A vector is a list of strings, with dynamic resizing of the list as new
+ * strings are added and support for various operations on strings (such as
+ * splitting them on delimiters).
+ *
+ * Vectors require list of strings, not arbitrary binary data, and cannot
+ * handle data elements containing nul characters.
+ *
+ * This is based on the util/vector.c library, but that library uses xmalloc
+ * routines to exit the program if memory allocation fails. This is a
+ * modified version of the vector library that instead returns false on
+ * failure to allocate memory, allowing the caller to do appropriate recovery.
+ *
+ * Only the portions of the vector library used by PAM modules are
+ * implemented.
+ *
+ * The canonical version of this file is maintained in the rra-c-util package,
+ * which can be found at <https://www.eyrie.org/~eagle/software/rra-c-util/>.
+ *
+ * Written by Russ Allbery <eagle@eyrie.org>
+ * Copyright 2010-2011, 2014
+ * The Board of Trustees of the Leland Stanford Junior University
+ *
+ * Copying and distribution of this file, with or without modification, are
+ * permitted in any medium without royalty provided the copyright notice and
+ * this notice are preserved. This file is offered as-is, without any
+ * warranty.
+ *
+ * SPDX-License-Identifier: FSFAP
+ */
+
+#ifndef PAM_UTIL_VECTOR_H
+#define PAM_UTIL_VECTOR_H 1
+
+#include <config.h>
+#include <portable/macros.h>
+#include <portable/stdbool.h>
+
+#include <stddef.h>
+
+struct vector {
+ size_t count;
+ size_t allocated;
+ char **strings;
+};
+
+BEGIN_DECLS
+
+/* Default to a hidden visibility for all util functions. */
+#pragma GCC visibility push(hidden)
+
+/* Create a new, empty vector. Returns NULL on memory allocation failure. */
+struct vector *vector_new(void) __attribute__((__malloc__));
+
+/*
+ * Create a new vector that's a copy of an existing vector. Returns NULL on
+ * memory allocation failure.
+ */
+struct vector *vector_copy(const struct vector *)
+ __attribute__((__malloc__, __nonnull__));
+
+/*
+ * Add a string to a vector. Resizes the vector if necessary. Returns false
+ * on failure to allocate memory.
+ */
+bool vector_add(struct vector *, const char *string)
+ __attribute__((__nonnull__));
+
+/*
+ * Resize the array of strings to hold size entries. Saves reallocation work
+ * in vector_add if it's known in advance how many entries there will be.
+ * Returns false on failure to allocate memory.
+ */
+bool vector_resize(struct vector *, size_t size) __attribute__((__nonnull__));
+
+/*
+ * Reset the number of elements to zero, freeing all of the strings for a
+ * regular vector, but not freeing the strings array (to cut down on memory
+ * allocations if the vector will be reused).
+ */
+void vector_clear(struct vector *) __attribute__((__nonnull__));
+
+/* Free the vector and all resources allocated for it. */
+void vector_free(struct vector *);
+
+/*
+ * Split functions build a vector from a string. vector_split_multi splits on
+ * a set of characters. If the vector argument is NULL, a new vector is
+ * allocated; otherwise, the provided one is reused. Returns NULL on memory
+ * allocation failure, after which the provided vector may have been modified
+ * to only have partial results.
+ *
+ * Empty strings will yield zero-length vectors. Adjacent delimiters are
+ * treated as a single delimiter by vector_split_multi. Any leading or
+ * trailing delimiters are ignored, so this function will never create
+ * zero-length strings (similar to the behavior of strtok).
+ */
+struct vector *vector_split_multi(const char *string, const char *seps,
+ struct vector *)
+ __attribute__((__nonnull__(1, 2)));
+
+/*
+ * Exec the given program with the vector as its arguments. Return behavior
+ * is the same as execv. Note the argument order is different than the other
+ * vector functions (but the same as execv). The vector_exec_env variant
+ * calls execve and passes in the environment for the program.
+ */
+int vector_exec(const char *path, struct vector *)
+ __attribute__((__nonnull__));
+int vector_exec_env(const char *path, struct vector *, const char *const env[])
+ __attribute__((__nonnull__));
+
+/* Undo default visibility change. */
+#pragma GCC visibility pop
+
+END_DECLS
+
+#endif /* UTIL_VECTOR_H */