/* Copyright (C) 2007 Howard Wilkinson
Copyright (C) 2007 Markus Moeller
Copyright (C) 2007 Luke Howard
This file is part of the nss_ldap library
Contributed by Howard Wilkinson <howard@cohtech.com>, 2007.
Derived from original version by Markus Moeller <huaraz@moeller.plus.com>
The nss_ldap library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public License as
published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version.
The nss_ldap library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public
License along with the nss_ldap library; see the file COPYING.LIB. If not,
write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA.
*/
static char rcsId[] =
"$Id: ldap-init-krb5-cache.c,v 2.1 2007/10/01 00:12:06 lukeh Exp $";
/*
* This file implementes the management of the Kerberos Credential Cache
* for SASL connections.
*
* The init function implements a finite state machine which is designed to
* manage the various configuration states that the Kerberos environment
* can be in.
*/
/*
* INIT: Initial state when first called and also state after a reset.
* Calls krb5_cache_setup which populates the environment
* => REFRESH - Try to load some credentials first
* RUNNING: Credentials have been loaded and are current.
* => EXPIRED - Cached credentials have expired
* => REFRESH - Cached credentials are expiring
* => INIT - escape path for inconsistent data
* (should never happen)
* RENEW: Credentials are expiring and have not been externally refreshed
* => ERROR - Autorenew is not on so this is an error state
* => EXPIRED - Cached credentials expired during renewal
* => RUNNING - Cached credentials have been successfully renewed
* EXPIRED: Credentials have expired acquire new ones.
* This only works if a keytab is configured and useable.
* => ERROR - No available keytab or keytab is not useable
* => RUNNING - New credentials acquired
* REFRESH: Reload credentials from the configured credential cache
* => RUNNING - New credentials loaded and not expired
* => RENEW - New credentials loaded but are expiring
* (only happens if autorenew is on)
* => EXPIRED - New credentials loaded have expired
* or are expiring and autorenew is off
* => ERROR - Inconsistent credentials state
* => ACQUIRE - Cannot load any usable credentials from a cache
* Try for a keytab if one if configured
* ACQUIRE: No credentials loaded acquire new ones - (See EXPIRED)
* ERROR: Call reset and then return failure from this attempt
* => REFRESH
*/
/*
* The code is written to allow and external program to supply credentials
* in the environment. This program can refresh/renew the credentials
* periodically and we will use these.
* As an alternative this code can renew externally provided credentials
* if necessary - autorenew must be turned on.
* Finally if provided with a keytab this code will acquire credentials
* from a KDC
*/
/*
* 31st July 2007 - NO ATTEMPT HAS BEEN MADE TO MAKE
* THIS CODE THREAD SAFE AT THIS TIME
*/
#include "config.h"
#ifdef HAVE_LBER_H
#include <lber.h>
#endif
#ifdef HAVE_LDAP_H
#include <ldap.h>
#endif
#include "ldap-nss.h"
#ifdef CONFIGURE_KRB5_KEYTAB
#include <krb5.h>
#include <unistd.h>
#include <stdlib.h>
#include <libgen.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <syslog.h>
#include <time.h>
#ifndef HEIMDAL
#include <profile.h>
#endif
#ifdef HEIMDAL
#define error_message(code) krb5_get_err_text(context,code)
#endif
#include <sys/types.h>
#include <assert.h>
#include <gssapi/gssapi.h>
#include <gssapi/gssapi_krb5.h>
#define MAX_RENEW_TIME "365d"
#define KT_PATH_MAX 256
#ifndef HEIMDAL
typedef struct _profile_t *profile_t;
#endif
/* State machine items */
typedef enum
{
KRB5_CACHE_INIT = 0, /* First time through or has been reset */
KRB5_CACHE_RUNNING, /* Valid non-expired credentials loaded */
KRB5_CACHE_RENEW, /* Valid about to expire credentials loaded */
KRB5_CACHE_EXPIRED, /* Valid expired credentials loaded */
KRB5_CACHE_REFRESH, /* No credentials loaded */
KRB5_CACHE_ACQUIRE, /* Acquire new credentials from KDC */
KRB5_CACHE_ERROR /* Cannot get any credentials (this time) */
} krb5_cache_state;
/* Run the state machine from here */
static krb5_cache_state cache_state = KRB5_CACHE_INIT;
/* Track our Effective UID incase it is changing as we run */
static uid_t __euid = -1;
static uid_t euid = -1;
static krb5_context context = NULL;
static krb5_creds *creds = NULL;
#ifdef HEIMDAL
static krb5_creds creds2;
#endif
static krb5_principal principal = NULL;
static krb5_ccache cc = NULL;
static krb5_deltat skew = 0;
static char *ccname = NULL;
static char *ktname = NULL;
static char *saslid = NULL;
static int autorenew = 0;
#define credsOK(__c__) \
((__c__ != NULL) && ((__c__->times.endtime - time(0)) > (2*skew)))
#define credsEXPIRING(__c__) \
((__c__ != NULL) \
&& (((__c__->times.endtime - time(0)) <= (2*skew)) \
&& ((__c__->times.endtime - time(0)) > skew)))
#define credsEXPIRED(__c__) \
((__c__ == NULL) \
|| (((__c__->times.renew_till - time(0)) <= (2*skew)) \
|| ((__c__->times.endtime - time(0)) <= skew)))
static int
krb5_cache_reset (ldap_config_t * config)
{
debug ("==> krb5_cache_reset");
if (creds != NULL)
{
free ((void *) creds);
creds = NULL;
}
if (context != NULL)
{
if (principal != NULL)
{
krb5_free_principal (context, principal);
principal = NULL;
}
if (cc != NULL)
{
krb5_cc_close (context, cc);
cc = NULL;
}
krb5_free_context (context);
context = NULL;
}
skew = 0;
autorenew = 0;
if (ccname != NULL)
{
free ((void *) ccname);
ccname = NULL;
}
if (ktname != NULL)
{
free ((void *) ktname);
ktname = NULL;
}
if (saslid != NULL)
{
free ((void *) saslid);
saslid = NULL;
}
cache_state = KRB5_CACHE_INIT;
debug ("<== krb5_cache_reset");
return (0);
}
static int
krb5_cache_kt_is_accessible (char *__ktname)
{
krb5_error_code code = 0;
krb5_keytab __keytab;
debug ("==> krb5_cache_kt_is_accessible: ktname %s", __ktname);
assert (context != NULL);
if (!(code = krb5_kt_resolve (context, __ktname, &__keytab)))
{
debug ("==> krb5_cache_kt_is_accessible: resolved ktname %s - %s",
__ktname, krb5_kt_get_type (context, __keytab));
if (strcmp ("FILE", krb5_kt_get_type (context, __keytab)) == 0)
{
debug ("==> krb5_cache_kt_is_accessible: kt type = FILE");
uid_t ruid = getuid ();
gid_t rgid = getgid ();
gid_t egid = getegid ();
char buf[KT_PATH_MAX];
if (ruid != euid)
{
setreuid (euid, ruid);
}
if (rgid != egid)
{
setregid (egid, rgid);
}
krb5_kt_get_name (context, __keytab, buf, KT_PATH_MAX);
debug ("==> krb5_cache_kt_is_accessible: kt_get_name gives %s",
buf);
code = access (buf, R_OK);
if (ruid != euid)
{
setreuid (ruid, euid);
}
if (rgid != rgid)
{
setregid (rgid, egid);
}
}
krb5_kt_close (context, __keytab);
}
debug ("<== krb5_cache_kt_is_accessible: returns %s(%d)",
error_message (code), (int) code);
return (code == 0);
}
static char *
krb5_cache_get_ktname (ldap_config_t * config)
{
char *__ktname = NULL;
debug ("==> krb5_cache_get_ktname");
{
char *rootktname = ((euid == 0 && config->ldc_krb5_rootusekeytab
&& config->ldc_rootusesasl
&& config->ldc_krb5_rootkeytabname)
? config->ldc_krb5_rootkeytabname : NULL);
char *userktname = ((((config->ldc_usesasl && config->ldc_krb5_usekeytab)
|| (euid == 0 && config->ldc_rootusesasl
&& config->ldc_krb5_rootusekeytab))
&& config->ldc_krb5_keytabname)
? config->ldc_krb5_keytabname : NULL);
char *envktname = ((((config->ldc_usesasl && config->ldc_krb5_usekeytab)
|| (euid == 0 && config->ldc_rootusesasl
&& config->ldc_krb5_rootusekeytab))
&& getenv ("KRB5_KTNAME"))
? getenv ("KRB5_KTNAME") : NULL);
char *defktname = NULL;
if ((config->ldc_usesasl && config->ldc_krb5_usekeytab)
|| (euid == 0 && config->ldc_rootusesasl
&& config->ldc_krb5_rootusekeytab))
{
char buf[KT_PATH_MAX];
debug ("==> krb5_cache_get_ktname: get default keytab name");
krb5_kt_default_name (context, buf, KT_PATH_MAX);
defktname = strdup (buf);
}
debug
("==> krb5_cache_get_ktname: rootktname = %s, userktname = %s, envktname = %s, defktname = %s",
(rootktname) ? rootktname : "NULL", (userktname) ? userktname : "NULL",
(envktname) ? envktname : "NULL", (defktname) ? defktname : "NULL");
__ktname =
((rootktname
&& krb5_cache_kt_is_accessible (rootktname)) ? rootktname
: (userktname
&& krb5_cache_kt_is_accessible (userktname)) ? userktname
: (envktname
&& krb5_cache_kt_is_accessible (envktname)) ? envktname : (defktname
&&
krb5_cache_kt_is_accessible
(defktname))
? defktname : NULL);
}
debug ("<== krb5_cache_get_ktname: returns %s",
(__ktname) ? __ktname : "NULL");
return __ktname;
}
static int
krb5_cache_cc_is_accessible (char *__ccname, int writeable)
{
krb5_error_code code = 0;
krb5_ccache __cc;
debug ("==> krb5_cache_cc_is_accessible: ccname %s, writeable %d",
__ccname, writeable);
assert (context != NULL);
if (!(code = krb5_cc_resolve (context, __ccname, &__cc)))
{
debug ("==> krb5_cache_cc_is_accessible: resolved ccname %s - %s",
__ccname, krb5_cc_get_type (context, __cc));
if ((strcmp ("FILE", krb5_cc_get_type (context, __cc)) == 0)
|| (strcmp ("WRFILE", krb5_cc_get_type (context, __cc)) == 0))
{
int mode = R_OK;
uid_t ruid = getuid ();
gid_t rgid = getgid ();
gid_t egid = getegid ();
if (writeable)
{
mode = mode | W_OK;
}
if (ruid != euid)
{
setreuid (euid, ruid);
}
if (rgid != egid)
{
setregid (egid, rgid);
}
if ((code = access (krb5_cc_get_name (context, __cc), F_OK)))
{
debug
("==> krb5_cache_cc_is_accessible: cache file not accessible %s(%d)",
strerror (errno), errno);
if (errno == EACCES)
{ /* File does not exist */
if (writeable)
{
/* Check that path exists */
char *x__ccname =
strdup (krb5_cc_get_name (context, cc));
char *x__ccdir = dirname (x__ccname);
code = access (x__ccdir, mode | X_OK);
free ((void *) x__ccname);
}
}
}
else
{
code = access (krb5_cc_get_name (context, __cc), mode);
}
if (ruid != euid)
{
setreuid (ruid, euid);
}
if (rgid != rgid)
{
setregid (rgid, egid);
}
}
krb5_cc_close (context, __cc);
}
debug ("<== krb5_cache_cc_is_accessible: returns %s(%d)",
error_message (code), code);
return (code == 0);
}
static char *
krb5_cache_get_ccname (ldap_config_t * config)
{
char *__ccname = NULL;
debug ("==> krb5_cache_get_ccname");
{
char *rootccname = ((euid == 0
&& config->ldc_rootusesasl
&& config->ldc_krb5_rootccname)
? config->ldc_krb5_rootccname : NULL);
char *userccname = (((config->ldc_usesasl
|| (euid == 0 && config->ldc_rootusesasl))
&& config->ldc_krb5_ccname)
? config->ldc_krb5_ccname : NULL);
char *envccname = (((config->ldc_usesasl
|| (euid == 0 && config->ldc_rootusesasl))
&& getenv ("KRB5CCNAME"))
? getenv ("KRB5CCNAME") : NULL);
char *defccname = (((config->ldc_usesasl
|| (euid == 0 && config->ldc_rootusesasl))
&& (char *) krb5_cc_default_name (context))
? (char *) krb5_cc_default_name (context) : NULL);
int writeable = (autorenew
|| config->ldc_krb5_usekeytab
|| (euid == 0 && config->ldc_krb5_rootusekeytab));
debug
("==> krb5_cache_get_ccname: rootccname = %s, userccname = %s, envccname = %s, defccname = %s",
(rootccname) ? rootccname : "NULL", (userccname) ? userccname : "NULL",
(envccname) ? envccname : "NULL", (defccname) ? defccname : "NULL");
__ccname =
((rootccname
&& krb5_cache_cc_is_accessible (rootccname,
writeable)) ? rootccname : (userccname
&&
krb5_cache_cc_is_accessible
(userccname,
writeable))
? userccname : (envccname
&& krb5_cache_cc_is_accessible (envccname,
writeable)) ? envccname
: (defccname
&& krb5_cache_cc_is_accessible (defccname,
writeable)) ? defccname : NULL);
if (__ccname == NULL
&& (config->ldc_krb5_usekeytab
|| (euid == 0 && config->ldc_krb5_rootusekeytab)))
__ccname = "MEMORY:store_creds";
}
debug ("<== krb5_cache_get_ccname: returns ccname = %s",
(__ccname) ? __ccname : "NULL");
return ((__ccname) ? strdup (__ccname) : "NULL");
}
static char *
krb5_cache_get_saslid (ldap_config_t * config)
{
char *__saslid = ((euid = 0 && config->ldc_rootusesasl
&& config->ldc_rootsaslid)
? config->ldc_rootsaslid
: (config->ldc_usesasl && config->ldc_saslid)
? config->ldc_saslid : NULL);
if (__saslid == NULL)
{
int retval;
char hostname[HOST_NAME_MAX];
debug ("==> krb5_cache_get_saslid: get default principal name");
errno = 0;
retval = gethostname (hostname, HOST_NAME_MAX);
if (!retval)
{
hostname[HOST_NAME_MAX] = '\0';
__saslid = malloc (sizeof (hostname) + 6);
strcpy (__saslid, "host/");
strcat (__saslid, hostname);
debug ("==> krb5_cache_get_saslid: set principal name %s",
__saslid);
}
else
{
syslog (LOG_ERR, "nss_ldap: %s while resolving hostname",
strerror (errno));
debug ("==> krb5_cache_get_saslid: %s while resolving hostname",
strerror (errno));
}
}
else
{
__saslid = strdup (__saslid);
}
return __saslid;
}
static krb5_principal
krb5_cache_get_principal (ldap_config_t * config)
{
krb5_error_code code = 0;
krb5_principal __principal;
assert (saslid != NULL);
if ((code = krb5_parse_name (context, saslid, &__principal)))
{
syslog (LOG_ERR, "nss_ldap: %s(%d) while parsing principal name %s",
error_message (code), (int) code, saslid);
debug
("==> krb5_cache_get_principal: %s(%d) while parsing principal name %s",
error_message (code), (int) code, saslid);
return (NULL);
}
return __principal;
}
/* Set up to manage the credentials cache */
static int
krb5_cache_setup (ldap_config_t * config)
{
krb5_error_code code = 0;
#ifndef HEIMDAL
profile_t profile;
#endif
debug ("==> krb5_cache_setup");
if (context == NULL)
{
if ((code = krb5_init_context (&context)))
{
syslog (LOG_ERR,
"nss_ldap: %s(%d) while initialising Kerberos library",
error_message (code), (int) code);
debug
("<== krb5_cache_setup: %s(%d) while initialising Kerberos library",
error_message (code), (int) code);
return (code);
}
}
#ifndef HEIMDAL
if ((code = krb5_get_profile (context, &profile)))
{
syslog (LOG_ERR, "nss_ldap: %s(%d) while getting profile",
error_message (code), (int) code);
debug ("<== krb5_cache_setup: %s(%d) while getting profile",
error_message (code), (int) code);
return (code);
}
if ((code = profile_get_integer (profile,
"libdefaults",
"clockskew", 0, 5 * 60, &skew)))
{
syslog (LOG_ERR, "nss_ldap: %s(%d) while getting clockskew",
error_message (code), (int) code);
debug ("<== krb5_cache_setup: %s(%d) while getting clockskew",
error_message (code), (int) code);
return (code);
}
profile_release (profile);
#else
skew = context->max_skew;
#endif
ccname = krb5_cache_get_ccname (config);
debug ("==> krb5_cache_setup: credential cache name %s",
ccname ? ccname : "NULL");
cache_state = KRB5_CACHE_REFRESH;
autorenew = (config->ldc_krb5_autorenew
|| (euid == 0 && config->ldc_krb5_rootautorenew));
debug ("<== krb5_cache_setup");
return (0);
}
static void
krb5_cache_setup_creds (ldap_config_t * context)
{
debug ("==> krb5_cache_setup_creds");
if (creds == NULL)
{
creds = malloc (sizeof (*creds));
assert (creds != NULL);
}
memset (creds, 0, sizeof (*creds));
debug ("<== krb5_cache_setup_creds");
}
/* (Re)load the credentials cache into our local data */
static int
krb5_cache_refresh (ldap_config_t * config)
{
krb5_error_code code = 0;
debug ("==> krb5_cache_refresh");
if ((code = krb5_cc_resolve (context, ccname, &cc)))
{
debug ("==> krb5_cache_refresh: cache %s cannot be resolved",
ccname ? ccname : "NULL");
}
else if ((code = krb5_cc_get_principal (context, cc, &principal)))
{
debug ("==> krb5_cache_refresh: cannot get principal from cache %s",
ccname ? ccname : "NULL");
}
else
{
debug ("==> krb5_cache_refresh: found existing cache %s",
ccname ? ccname : "NULL");
/* Use the principal name from the cache rather than preconfigured saslid */
char *principal_name = NULL;
if ((code = krb5_unparse_name (context, principal, &principal_name)))
{
debug
("==> krb5_cache_refresh: cannot unparse principal from cache %s",
ccname ? ccname : "NULL");
}
else
{
krb5_cc_cursor cursor;
debug ("==> krb5_cache_refresh: cache %s principal %s",
ccname ? ccname : "NULL",
principal_name ? principal_name : "NULL");
if ((code = krb5_cc_start_seq_get (context, cc, &cursor)))
{
debug
("==> krb5_cache_refresh: cache %s credentials not usable",
ccname ? ccname : "NULL");
}
else
{
krb5_cache_setup_creds (config);
do
{
if (!
(code =
krb5_cc_next_cred (context, cc, &cursor, creds)))
{
debug ("==> krb5_cache_refresh: retrieved creds");
if (credsOK (creds))
{
debug
("==> krb5_cache_refresh: creds are OK --> RUNNING");
/* Reloaded cache is fine */
cache_state = KRB5_CACHE_RUNNING;
}
else
{
if (credsEXPIRING (creds))
{
debug
("==> krb5_cache_refresh: creds are EXPIRING");
/* Reloaded cache will expire shortly */
if (autorenew)
{
debug ("==> krb5_cache_refresh: --> RENEW");
cache_state = KRB5_CACHE_RENEW;
}
else
{
debug
("==> krb5_cache_refresh: --> EXPIRED");
cache_state = KRB5_CACHE_EXPIRED;
}
}
else if (credsEXPIRED (creds))
{
debug
("==> krb5_cache_refresh: creds have EXPIRED --> EXPIRED");
/* Reload cache has expired */
cache_state = KRB5_CACHE_EXPIRED;
}
else
{ /* Should never happen */
debug
("==> krb5_cache_refresh: creds in weird state --> ERROR");
cache_state = KRB5_CACHE_ERROR;
}
krb5_free_cred_contents (context, creds);
}
}
}
while (!code && cache_state == KRB5_CACHE_REFRESH);
if ((code = krb5_cc_end_seq_get (context, cc, &cursor)))
{
debug
("==> krb5_cache_refresh: cache %s scan failed to end cleanly",
ccname ? ccname : "NULL");
}
}
krb5_free_unparsed_name (context, principal_name);
}
}
if (principal != NULL)
{
krb5_free_principal (context, principal);
principal = NULL;
}
if (cc != NULL)
{
if ((code = krb5_cc_close (context, cc)))
{
debug ("==> krb5_cache_refresh: cache %s close failed",
ccname ? ccname : "NULL");
}
cc = NULL;
}
if (cache_state == KRB5_CACHE_REFRESH)
{
debug ("==> krb5_cache_refresh: stuck in refresh --> ACQUIRE");
cache_state = KRB5_CACHE_ACQUIRE; /* Try for a keytab */
}
debug ("<== krb5_cache_refresh");
return (code);
}
/* Renew an expired credentials cache */
static int
krb5_cache_renew (ldap_config_t * config)
{
krb5_error_code code = 0;
#ifdef HEIMDAL
krb5_kdc_flags flags;
krb5_realm *client_realm;
#endif
debug ("==> krb5_cache_renew");
if (!autorenew)
{
/*
* We should never get here
* as the refresh code should only enter renew
* if autorenew is true
*/
debug
("==> krb5_cache_renew: renew called with autorenew off --> ERROR");
cache_state = KRB5_CACHE_ERROR;
return (1);
}
assert (creds != NULL); /* Refresh or acquire will have set this up */
#ifndef HEIMDAL
/* renew ticket */
/* Overwrites contents of creds no storage allocation happening */
if ((code = krb5_get_renewed_creds (context, creds, principal, cc, NULL)))
{
debug ("==> krb5_cache_renew: failed to renew creds %s(%d)",
error_message (code), (int) code);
#else
/* renew ticket */
flags.i = 0;
flags.b.renewable = flags.b.renew = 1;
if ((code = krb5_cc_get_principal (context, cc, &creds2.client)))
{
syslog (LOG_ERR,
"nss_ldap: %s(%d) while getting principal from credential cache",
error_message (code), (int) code);
debug
("==> krb5_cache_renew: %s(%d) while getting principal from credentials cache",
error_message (code), (int) code);
cache_state = KRB5_CACHE_REFRESH;
return (code);
}
client_realm = krb5_princ_realm (context, creds2.client);
if ((code = krb5_make_principal (context, &creds2.server, *client_realm,
KRB5_TGS_NAME, *client_realm, NULL)))
{
syslog (LOG_ERR, "nss_ldap: %s(%d) while getting krbtgt principal",
error_message (code), (int) code);
debug ("==> krb5_cache_renew: %s(%d) while getting krbtgt principal",
error_message (code), (int) code);
cache_state = KRB5_CACHE_REFRESH;
return (code);
}
/* I think there is a potential storage leak here as creds is written to */
/* Need to check Heimdal code to see if it will overwrite or replace memory */
if ((code = krb5_get_kdc_cred (context,
cc, flags, NULL, NULL, &creds2, &creds)))
{
debug ("==> krb5_cache_renew: failed to get creds from kdc %s(%d)",
error_message (code), (int) code);
#endif
if (code == KRB5KRB_AP_ERR_TKT_EXPIRED)
{
/* this can happen because of clock skew */
debug
("<== krb5_cache_renew: ticket has expired because of clock skey --> EXPIRED");
cache_state = KRB5_CACHE_EXPIRED;
return (0);
}
else
{
syslog (LOG_ERR, "nss_ldap: %s(%d) while renewing credentials",
error_message (code), (int) code);
debug ("==> krb5_cache_renew: %s(%d) while renewing credentials",
error_message (code), (int) code);
cache_state = KRB5_CACHE_REFRESH;
return (code);
}
}
cache_state = KRB5_CACHE_RUNNING;
debug ("<== krb5_cache_renew: renewed creds --> RUNNING");
return (0);
}
/* Initialise the credentials cache from a keytab */
static int
krb5_cache_acquire (ldap_config_t * config)
{
krb5_error_code code = 0;
krb5_keytab keytab = NULL;
debug ("==> krb5_cache_acquire");
/*
* We have not managed to find any credentials.
* If a keytab is configured then try using that
*/
assert (context != NULL);
/* use keytab to fill cache */
if ((ktname == NULL) && ((ktname = krb5_cache_get_ktname (config)) == NULL))
{
debug ("==> krb5_cache_acquire: no usable keytab");
code = 1;
}
else if ((code = krb5_kt_resolve (context, ktname, &keytab)))
{
syslog (LOG_ERR, "nss_ldap: %s(%d) while resolving keytab filename %s",
error_message (code), (int) code, ktname);
debug
("==> krb5_cache_acquire: %s(%d) while resolving keytab filename %s",
error_message (code), (int) code, ktname);
}
else
{
krb5_get_init_creds_opt options;
krb5_deltat rlife;
if (saslid == NULL)
{
saslid = krb5_cache_get_saslid (config);
}
debug ("==> krb5_cache_acquire: saslid = %s",
(saslid) ? saslid : "NULL");
if (saslid && principal == NULL)
{
principal = krb5_cache_get_principal (config);
if (principal == NULL)
{
debug ("<== krb5_cache_acquire: no valid principal --> ERROR");
cache_state = KRB5_CACHE_ERROR;
return (1);
}
}
krb5_get_init_creds_opt_init (&options);
if ((code = krb5_string_to_deltat (MAX_RENEW_TIME, &rlife))
|| (rlife == 0))
{
syslog (LOG_ERR,
"nss_ldap: %s(%d) while setting renew lifetime value to %s",
error_message (code), (int) code, MAX_RENEW_TIME);
debug
("==> krb5_cache_acquire: %s(%d) while setting renew lifetime value to %s",
error_message (code), (int) code, MAX_RENEW_TIME);
code = (code == 0) ? 1 : code;
}
else
{
krb5_get_init_creds_opt_set_renew_life (&options, rlife);
debug ("==> krb5_cache_acquire: get credentials from keytab");
krb5_cache_setup_creds (config);
if ((code = krb5_get_init_creds_keytab (context,
creds,
principal,
keytab,
0,
NULL,
&options))
&& (code != EEXIST))
{
/* Failed to initialise credentials from keytab */
syslog (LOG_ERR,
"nss_ldap: %s(%d) while initialising credentials from keytab",
error_message (code), (int) code);
debug
("==> krb5_cache_acquire get credentials from keytab failed %s(%d)",
error_message (code), (int) code);
debug
("==> krb5_cache_acquire try refreshing from credential cache");
if ((code = krb5_cache_refresh (config)))
{
debug
("==> krb5_cache_acquire: cache credentials not usable");
free ((void *) creds);
creds = NULL;
}
else if (cache_state == KRB5_CACHE_ACQUIRE)
code = 1;
}
else
{
/* We have a set of credentials we now need to save them */
if ((code = krb5_cc_resolve (context, ccname, &cc)))
{
syslog (LOG_ERR,
"nss_ldap: %s(%d) while resolving credential cache",
error_message (code), (int) code);
debug
("==>krb5_cache_acquire: %s(%d) while resolving credential cache",
error_message (code), (int) code);
}
else if ((code = krb5_cc_initialize (context, cc, principal))
&& (code != EEXIST))
{
/* Failed to initialize the cache try to use a default one instead */
syslog (LOG_ERR,
"nss_ldap: %s(%d) while initializing credential cache",
error_message (code), (int) code);
debug
("==> krb5_cache_acquire: initializing credential cache failed %s(%d)",
error_message (code), (int) code);
}
else
{
debug
("==> krb5_cache_acquire store credentials in cache file");
if ((code = krb5_cc_store_cred (context, cc, creds)))
{
syslog (LOG_ERR,
"nss_ldap: %s(%d) while storing credentials",
error_message (code), (int) code);
debug
("==> krb5_cache_acquire: %s(%d) while storing credentials",
error_message (code), (int) code);
}
else
{
if (!creds->times.starttime)
creds->times.starttime = creds->times.authtime;
debug ("==> krb5_cache_acquire: got new credentials");
cache_state = KRB5_CACHE_RUNNING;
}
if (cc != NULL)
{
if ((code = krb5_cc_close (context, cc)))
{
debug
("==> krb5_cache_acquire: cache %s close failed",
ccname ? ccname : "NULL");
}
}
}
}
}
if (keytab != NULL)
{
krb5_kt_close (context, keytab);
}
}
if (code)
{
debug ("<== krb5_cache_acquire: --> ERROR");
cache_state = KRB5_CACHE_ERROR;
}
else
{
debug ("<== krb5_cache_acquire");
}
return (code);
}
int
do_init_krb5_cache (ldap_config_t * config)
{
krb5_error_code code = 0;
debug ("==> do_init_krb5_cache %s %s %s",
config->ldc_krb5_keytabname ? config->ldc_krb5_keytabname : "NULL",
config->ldc_krb5_ccname ? config->ldc_krb5_ccname : "NULL",
config->ldc_saslid ? config->ldc_saslid : "NULL");
euid = geteuid ();
/* Check to see if we are using sasl, if not then return as nothing to do */
if (!(config->ldc_usesasl || (euid == 0 && config->ldc_rootusesasl)))
{
return (1); /* Should we signal success? */
}
/* Check to see if we have swapped user since we were last called */
if (__euid != euid)
{ /* Could be first call but clear everything out anyway */
krb5_cache_reset (config);
__euid = euid;
}
/*
* If we do not have any credentials
* or they are expired or they are about to expire
* then try to load a new set from the credentials cache
* - this may have been renewed by
* some other process or thread so check this first.
*/
do
{
switch (cache_state)
{
case KRB5_CACHE_INIT:
code = krb5_cache_setup (config);
debug
("==> do_init_krb5_cache ktname = %s, ccname = %s, saslid = %s, euid = %d",
ktname ? ktname : "NULL", ccname ? ccname : "NULL",
saslid ? saslid : "NULL", euid);
break;
case KRB5_CACHE_RUNNING:
/*
* If we have credentials
* and they are not expired or about to expire then OK!
*/
if (credsOK (creds))
{
debug ("==> do_init_krb5_cache: return 0");
return (0);
}
if (credsEXPIRED (creds))
{
cache_state = KRB5_CACHE_EXPIRED;
}
else if (credsEXPIRING (creds))
{
cache_state = KRB5_CACHE_REFRESH;
}
else
{
/* Should not get here if things are OK so start again */
code = krb5_cache_reset (config);
cache_state = KRB5_CACHE_INIT;
}
break;
case KRB5_CACHE_RENEW:
code = krb5_cache_renew (config);
break;
case KRB5_CACHE_EXPIRED:
code = krb5_cache_acquire (config);
break;
case KRB5_CACHE_REFRESH:
code = krb5_cache_refresh (config);
break;
case KRB5_CACHE_ACQUIRE:
code = krb5_cache_acquire (config);
break;
case KRB5_CACHE_ERROR:
/*
* Can't do anything while in ERROR state.
* ?? Wait for a few seconds to have passed and try again ??
* Otherwise how do we break out of this loop
* - reset will make same sequence happen if this is a hard problem
*/
code = krb5_cache_reset (config);
break;
default:
break;
}
if (code)
{
debug ("==> do_init_krb5_cache: return %d", (int) code);
return (code);
}
}
while (1);
/*NOTREACHED*/
debug ("==> do_init_krb5_cache: reinit ticket loop exit failure");
return (1);
}
static char *saveccname = NULL;
/* This is shared into the environment so be careful */
#ifdef CONFIGURE_KRB5_CCNAME_ENV
static char envbuf[256];
#endif
int
do_select_krb5_cache (ldap_config_t * config)
{
int result = 0;
if (cache_state != KRB5_CACHE_RUNNING)
{
result = do_init_krb5_cache (config);
}
if (ccname != NULL)
{
#ifdef CONFIGURE_KRB5_CCNAME_ENV
char tmpbuf[256];
char *oldccname = getenv ("KRB5CCNAME");
if (saveccname != NULL)
{
free ((void *) saveccname);
saveccname = NULL;
}
if (oldccname != NULL)
{
strncpy (tmpbuf, oldccname, sizeof (tmpbuf));
tmpbuf[sizeof (tmpbuf) - 1] = '\0';
saveccname = (char *) malloc (strlen (tmpbuf) + 1);
strcpy (saveccname, tmpbuf);
}
snprintf (envbuf, sizeof (envbuf), "KRBCCNAME=%s", ccname);
putenv (envbuf);
#elif defined(CONFIGURE_KRB5_CCNAME_GSSAPI)
OM_uint32 retval = 0;
if (gss_krb5_ccache_name (&retval,
(const char *) ccname,
(const char **) &saveccname) !=
GSS_S_COMPLETE)
{
debug
("==> do_select_krb5_cache: unable to set default credential cache");
result = -1;
}
#endif
}
return result;
}
int
do_restore_krb5_cache (ldap_config_t * config)
{
int result = 0;
if (saveccname != NULL)
{
#ifdef CONFIGURE_KRB5_CCNAME_ENV
snprintf (envbuf, sizeof (envbuf), "KRB5CCNAME=%s", saveccname);
putenv (envbuf);
free ((void *) saveccname);
#elif defined(CONFIGURE_KRB5_CCNAME_GSSAPI)
OM_uint32 retval = 0;
if (gss_krb5_ccache_name (&retval, (const char *) saveccname, NULL) !=
GSS_S_COMPLETE)
{
debug
("==> do_restore_krb5_cache: unable to restore default credential cache");
result = -1;
}
#endif
saveccname = NULL;
}
return result;
}
#endif /* CONFIGURE_KRB5_KEYTAB */