/*
* 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 .
*
* Written by Russ Allbery
* Copyright 2015, 2018, 2020 Russ Allbery
* 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
#ifdef HAVE_KRB5
# include
#endif
#include
#include
#include
#include
#include
#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 ) 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 ) 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_ 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 ) 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 */