opnsense-src/module/cache.c
Cy Schubert 24f0b4ca2d pam-krb5: Import/add pam-krb5 from eyeire.org
From https://www.eyrie.org/~eagle/software/pam-krb5/:

pam-krb5 provides a Kerberos PAM module that supports authentication,
user ticket cache handling, simple authorization (via .k5login or
checking Kerberos principals against local usernames), and password
changing. It can be configured through either options in the PAM
configuration itself or through entries in the system krb5.conf file,
and it tries to work around PAM implementation flaws in commonly-used
PAM-enabled applications such as OpenSSH and xdm. It supports both
PKINIT and FAST to the extent that the underlying Kerberos libraries
support these features.

The reason for this import is to provide an MIT KRB5 compatible
pam_krb5 PAM module. The existing pam_krb5 in FreeBS only works
with Heimdal.

Sponsored by:	The FreeBSD Foundation
2025-05-27 09:20:06 -07:00

185 lines
5.4 KiB
C

/*
* Ticket cache initialization.
*
* Provides functions for creating ticket caches, used by pam_authenticate,
* pam_setcred, and pam_chauthtok after changing an expired password.
*
* Copyright 2005-2009, 2014, 2020 Russ Allbery <eagle@eyrie.org>
* Copyright 2011-2012
* The Board of Trustees of the Leland Stanford Junior University
* Copyright 2005 Andres Salomon <dilinger@debian.org>
* Copyright 1999-2000 Frank Cusack <fcusack@fcusack.com>
*
* SPDX-License-Identifier: BSD-3-clause or GPL-1+
*/
#include <config.h>
#include <portable/krb5.h>
#include <portable/pam.h>
#include <portable/system.h>
#include <errno.h>
#include <module/internal.h>
#include <pam-util/args.h>
#include <pam-util/logging.h>
/*
* Get the name of a cache. Takes the name of the environment variable that
* should be set to indicate which cache to use, either the permanent cache
* (KRB5CCNAME) or the temporary cache (PAM_KRB5CCNAME).
*
* Treat an empty environment variable setting the same as if the variable
* was not set, since on FreeBSD we can't delete the environment variable,
* only set it to an empty value.
*/
const char *
pamk5_get_krb5ccname(struct pam_args *args, const char *key)
{
const char *name;
/* When refreshing a cache, we need to try the regular environment. */
name = pam_getenv(args->pamh, key);
if (name == NULL || *name == '\0')
name = getenv(key);
if (name == NULL || *name == '\0')
return NULL;
else
return name;
}
/*
* Put the ticket cache information into the environment. Takes the path and
* the environment variable to set, since this is used both for the permanent
* cache (KRB5CCNAME) and the temporary cache (PAM_KRB5CCNAME). Returns a PAM
* status code.
*/
int
pamk5_set_krb5ccname(struct pam_args *args, const char *name, const char *key)
{
char *env_name = NULL;
int pamret;
if (asprintf(&env_name, "%s=%s", key, name) < 0) {
putil_crit(args, "asprintf failed: %s", strerror(errno));
pamret = PAM_BUF_ERR;
goto done;
}
pamret = pam_putenv(args->pamh, env_name);
if (pamret != PAM_SUCCESS) {
putil_err_pam(args, pamret, "pam_putenv failed");
pamret = PAM_SERVICE_ERR;
goto done;
}
pamret = PAM_SUCCESS;
done:
free(env_name);
return pamret;
}
/*
* Given the template for a ticket cache name, initialize that file securely
* mkstemp. Returns a PAM success or error code.
*/
int
pamk5_cache_mkstemp(struct pam_args *args, char *template)
{
int ccfd, oerrno;
ccfd = mkstemp(template);
if (ccfd < 0) {
oerrno = errno;
putil_crit(args, "mkstemp(\"%s\") failed: %s", template,
strerror(errno));
errno = oerrno;
return PAM_SERVICE_ERR;
}
close(ccfd);
return PAM_SUCCESS;
}
/*
* Given a cache name and the initial credentials, initialize the cache, store
* the credentials in that cache, and return a pointer to the new cache in the
* cache argument. Returns a PAM success or error code.
*/
int
pamk5_cache_init(struct pam_args *args, const char *ccname, krb5_creds *creds,
krb5_ccache *cache)
{
struct context *ctx;
int retval;
if (args == NULL || args->config == NULL || args->config->ctx == NULL
|| args->config->ctx->context == NULL)
return PAM_SERVICE_ERR;
ctx = args->config->ctx;
retval = krb5_cc_resolve(ctx->context, ccname, cache);
if (retval != 0) {
putil_err_krb5(args, retval, "cannot resolve ticket cache %s", ccname);
retval = PAM_SERVICE_ERR;
goto done;
}
retval = krb5_cc_initialize(ctx->context, *cache, ctx->princ);
if (retval != 0) {
putil_err_krb5(args, retval, "cannot initialize ticket cache %s",
ccname);
retval = PAM_SERVICE_ERR;
goto done;
}
retval = krb5_cc_store_cred(ctx->context, *cache, creds);
if (retval != 0) {
putil_err_krb5(args, retval, "cannot store credentials in %s", ccname);
retval = PAM_SERVICE_ERR;
goto done;
}
done:
if (retval != PAM_SUCCESS && *cache != NULL) {
krb5_cc_destroy(ctx->context, *cache);
*cache = NULL;
}
return retval;
}
/*
* Initialize an internal ticket cache with a random name, store the given
* credentials in the cache, and store the cache in the context. Put the path
* in PAM_KRB5CCNAME where it can be picked up later by pam_setcred. Returns
* a PAM success or error code.
*/
int
pamk5_cache_init_random(struct pam_args *args, krb5_creds *creds)
{
char *cache_name = NULL;
const char *dir;
int pamret;
/* Store the obtained credentials in a temporary cache. */
dir = args->config->ccache_dir;
if (strncmp("FILE:", args->config->ccache_dir, strlen("FILE:")) == 0)
dir += strlen("FILE:");
if (asprintf(&cache_name, "%s/krb5cc_pam_XXXXXX", dir) < 0) {
putil_crit(args, "malloc failure: %s", strerror(errno));
return PAM_SERVICE_ERR;
}
pamret = pamk5_cache_mkstemp(args, cache_name);
if (pamret != PAM_SUCCESS)
goto done;
pamret =
pamk5_cache_init(args, cache_name, creds, &args->config->ctx->cache);
if (pamret != PAM_SUCCESS)
goto done;
putil_debug(args, "temporarily storing credentials in %s", cache_name);
pamret = pamk5_set_krb5ccname(args, cache_name, "PAM_KRB5CCNAME");
done:
free(cache_name);
return pamret;
}