diff options
Diffstat (limited to 'pam-util/logging.c')
| -rw-r--r-- | pam-util/logging.c | 345 |
1 files changed, 345 insertions, 0 deletions
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 */ |
