diff options
author | Cy Schubert <cy@FreeBSD.org> | 2025-04-17 02:13:41 +0000 |
---|---|---|
committer | Cy Schubert <cy@FreeBSD.org> | 2025-05-27 16:20:06 +0000 |
commit | 24f0b4ca2d565cdbb4fe7839ff28320706bf2386 (patch) | |
tree | bc9ce87edb73f767f5580887d0fc8c643b9d7a49 /pam-util |
Diffstat (limited to 'pam-util')
-rw-r--r-- | pam-util/args.c | 105 | ||||
-rw-r--r-- | pam-util/args.h | 84 | ||||
-rw-r--r-- | pam-util/logging.c | 345 | ||||
-rw-r--r-- | pam-util/logging.h | 131 | ||||
-rw-r--r-- | pam-util/options.c | 720 | ||||
-rw-r--r-- | pam-util/options.h | 205 | ||||
-rw-r--r-- | pam-util/vector.c | 289 | ||||
-rw-r--r-- | pam-util/vector.h | 120 |
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 */ |