Now that the ldap code runs with the real and effective uid set to

0, it is not possible for the gssapi libs to find the user's krb5
credential cache file.  To work around this, we make a temporary
copy of the user's credential cache specified by KRB5CCNAME (opened
with the user's effective uid) and point gssapi to it.  To set the
credential cache file name, we dynamically look up gss_krb5_ccache_name()
and use it if available, otherwise fall back to setting KRB5CCNAME.
This commit is contained in:
Todd C. Miller
2013-07-28 17:06:43 -06:00
parent 39575aecf2
commit b1c8f0575b
2 changed files with 183 additions and 57 deletions

View File

@@ -43,6 +43,7 @@
# include <time.h>
#endif
#include <ctype.h>
#include <fcntl.h>
#include <pwd.h>
#include <grp.h>
#include <netinet/in.h>
@@ -62,17 +63,12 @@
# else
# include <sasl.h>
# endif
# if HAVE_GSS_KRB5_CCACHE_NAME
# if defined(HAVE_GSSAPI_GSSAPI_KRB5_H)
# include <gssapi/gssapi.h>
# include <gssapi/gssapi_krb5.h>
# elif defined(HAVE_GSSAPI_GSSAPI_H)
# include <gssapi/gssapi.h>
# else
# include <gssapi.h>
# endif
# ifdef HAVE_DLOPEN
# include <dlfcn.h>
# else
# include "compat/dlfcn.h"
# endif
#endif
#endif /* HAVE_LDAP_SASL_INTERACTIVE_BIND_S */
#include "sudoers.h"
#include "parse.h"
@@ -1441,6 +1437,42 @@ sudo_ldap_parse_keyword(const char *keyword, const char *value,
debug_return_bool(false);
}
#ifdef HAVE_LDAP_SASL_INTERACTIVE_BIND_S
static bool
sudo_check_krb5_ccname(const char *ccname)
{
int fd = -1;
debug_decl(sudo_check_krb5_ccname, SUDO_DEBUG_LDAP)
/* Strip off leading FILE: or WRFILE: prefix. */
switch (ccname[0]) {
case 'F':
case 'f':
if (strncasecmp(ccname, "FILE:", 5) == 0)
ccname += 5;
break;
case 'W':
case 'w':
if (strncasecmp(ccname, "WRFILE:", 7) == 0)
ccname += 7;
break;
}
/* Make sure credential cache is fully-qualified and exists. */
if (ccname[0] == '/')
fd = open(ccname, O_RDONLY|O_NONBLOCK, 0);
if (fd == -1) {
sudo_debug_printf(SUDO_DEBUG_WARN|SUDO_DEBUG_LINENO,
"unable to open krb5 credential cache: %s", ccname);
debug_return_bool(false);
}
close(fd);
sudo_debug_printf(SUDO_DEBUG_INFO,
"using krb5 credential cache: %s", ccname);
debug_return_bool(true);
}
#endif /* HAVE_LDAP_SASL_INTERACTIVE_BIND_S */
static bool
sudo_ldap_read_config(void)
{
@@ -1642,24 +1674,11 @@ sudo_ldap_read_config(void)
* Make sure we can open the file specified by krb5_ccname.
*/
if (ldap_conf.krb5_ccname != NULL) {
if (strncasecmp(ldap_conf.krb5_ccname, "FILE:", 5) == 0 ||
strncasecmp(ldap_conf.krb5_ccname, "WRFILE:", 7) == 0) {
value = ldap_conf.krb5_ccname +
(ldap_conf.krb5_ccname[4] == ':' ? 5 : 7);
if ((fp = fopen(value, "r")) != NULL) {
sudo_debug_printf(SUDO_DEBUG_INFO,
"using krb5 credential cache: %s", value);
fclose(fp);
} else {
/* Can't open it, just ignore the entry. */
sudo_debug_printf(SUDO_DEBUG_WARN|SUDO_DEBUG_LINENO,
"unable to open krb5 credential cache: %s", value);
efree(ldap_conf.krb5_ccname);
ldap_conf.krb5_ccname = NULL;
}
}
if (!sudo_check_krb5_ccname(ldap_conf.krb5_ccname))
ldap_conf.krb5_ccname = NULL;
}
#endif
debug_return_bool(true);
}
@@ -1982,17 +2001,104 @@ done:
}
#ifdef HAVE_LDAP_SASL_INTERACTIVE_BIND_S
static unsigned int (*sudo_gss_krb5_ccache_name)(unsigned int *minor_status, const char *name, const char **old_name);
static int
sudo_set_krb5_ccache_name(const char *name, const char **old_name)
{
int rc = 0;
unsigned int junk;
static bool initialized;
debug_decl(sudo_set_krb5_ccache_name, SUDO_DEBUG_LDAP)
if (!initialized) {
sudo_gss_krb5_ccache_name = dlsym(RTLD_DEFAULT, "gss_krb5_ccache_name");
initialized = true;
}
/*
* Try to use gss_krb5_ccache_name() if possible.
* We also need to set KRB5CCNAME since some LDAP libs may not use
* gss_krb5_ccache_name().
*/
if (sudo_gss_krb5_ccache_name != NULL) {
rc = sudo_gss_krb5_ccache_name(&junk, name, old_name);
} else {
/* No gss_krb5_ccache_name(), fall back on KRB5CCNAME. */
if (old_name != NULL)
*old_name = sudo_getenv("KRB5CCNAME");
}
if (name != NULL && *name != '\0')
sudo_setenv("KRB5CCNAME", name, true);
else
sudo_unsetenv("KRB5CCNAME");
debug_return_int(rc);
}
/*
* Make a copy of the credential cache file specified by KRB5CCNAME
* which must be readable by the user. The resulting cache file
* is root-owned and will be removed after authenticating via SASL.
*/
static char *
sudo_krb5_copy_cc_file(const char *old_ccname)
{
int ofd, nfd;
ssize_t off, nread, nwritten = -1;
static char new_ccname[sizeof(_PATH_TMP) + sizeof("sudocc_XXXXXXXX") - 1];
char buf[10240], *ret = NULL;
debug_decl(sudo_krb5_copy_cc_file, SUDO_DEBUG_LDAP)
/* Open credential cache as user to prevent stolen creds. */
set_perms(PERM_USER);
ofd = open(old_ccname, O_RDONLY|O_NONBLOCK);
restore_perms();
if (ofd != -1) {
(void) fcntl(ofd, F_SETFL, 0);
if (lock_file(ofd, SUDO_LOCK)) {
snprintf(new_ccname, sizeof(new_ccname), "%s%s",
_PATH_TMP, "sudocc_XXXXXXXX");
nfd = mkstemp(new_ccname);
if (nfd != -1) {
while ((nread = read(ofd, buf, sizeof(buf))) > 0) {
off = 0;
while ((nwritten = write(nfd, buf + off, nread - off)) != -1) {
off += nwritten;
}
if (nwritten == -1)
break;
}
close(nfd);
if (nread != -1 && nwritten != -1) {
ret = new_ccname; /* success! */
} else {
unlink(new_ccname); /* failed */
}
}
}
close(ofd);
}
debug_return_str(ret);
}
static int
sudo_ldap_sasl_interact(LDAP *ld, unsigned int flags, void *_auth_id,
void *_interact)
{
char *auth_id = (char *)_auth_id;
sasl_interact_t *interact = (sasl_interact_t *)_interact;
int rc = LDAP_SUCCESS;
debug_decl(sudo_ldap_sasl_interact, SUDO_DEBUG_LDAP)
for (; interact->id != SASL_CB_LIST_END; interact++) {
if (interact->id != SASL_CB_USER)
debug_return_int(LDAP_PARAM_ERROR);
if (interact->id != SASL_CB_USER) {
warningx("sudo_ldap_sasl_interact: unexpected interact id %lu",
interact->id);
rc = LDAP_PARAM_ERROR;
break;
}
if (auth_id != NULL)
interact->result = auth_id;
@@ -2003,14 +2109,19 @@ sudo_ldap_sasl_interact(LDAP *ld, unsigned int flags, void *_auth_id,
interact->len = strlen(interact->result);
#if SASL_VERSION_MAJOR < 2
interact->result = estrdup(interact->result);
interact->result = strdup(interact->result);
if (interact->result == NULL) {
rc = LDAP_NO_MEMORY;
break;
}
#endif /* SASL_VERSION_MAJOR < 2 */
DPRINTF2("sudo_ldap_sasl_interact: SASL_CB_USER %s",
(const char *)interact->result);
}
debug_return_int(LDAP_SUCCESS);
debug_return_int(rc);
}
#endif /* HAVE_LDAP_SASL_INTERACTIVE_BIND_S */
/*
* Set LDAP options from the specified options table
*/
@@ -2212,45 +2323,46 @@ static int
sudo_ldap_bind_s(LDAP *ld)
{
int rc;
#ifdef HAVE_LDAP_SASL_INTERACTIVE_BIND_S
const char *old_ccname = user_ccname;
# ifdef HAVE_GSS_KRB5_CCACHE_NAME
unsigned int status;
# endif
#endif
debug_decl(sudo_ldap_bind_s, SUDO_DEBUG_LDAP)
#ifdef HAVE_LDAP_SASL_INTERACTIVE_BIND_S
if (ldap_conf.rootuse_sasl == true ||
(ldap_conf.rootuse_sasl != false && ldap_conf.use_sasl == true)) {
const char *old_ccname = NULL;
const char *new_ccname = ldap_conf.krb5_ccname;
const char *tmp_ccname = NULL;
void *auth_id = ldap_conf.rootsasl_auth_id ?
ldap_conf.rootsasl_auth_id : ldap_conf.sasl_auth_id;
if (ldap_conf.krb5_ccname != NULL) {
# ifdef HAVE_GSS_KRB5_CCACHE_NAME
if (gss_krb5_ccache_name(&status, ldap_conf.krb5_ccname, &old_ccname)
!= GSS_S_COMPLETE) {
old_ccname = NULL;
/* Make temp copy of the user's credential cache as needed. */
if (ldap_conf.krb5_ccname == NULL && user_ccname != NULL)
new_ccname = tmp_ccname = sudo_krb5_copy_cc_file(user_ccname);
if (new_ccname != NULL) {
rc = sudo_set_krb5_ccache_name(new_ccname, &old_ccname);
if (rc == 0) {
sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
"set ccache name %s -> %s",
old_ccname ? old_ccname : "(none)", new_ccname);
} else {
sudo_debug_printf(SUDO_DEBUG_WARN|SUDO_DEBUG_LINENO,
"gss_krb5_ccache_name() failed: %d", status);
"gss_krb5_ccache_name() failed: %d", rc);
}
# else
sudo_setenv("KRB5CCNAME", ldap_conf.krb5_ccname, true);
# endif
}
rc = ldap_sasl_interactive_bind_s(ld, ldap_conf.binddn, "GSSAPI",
NULL, NULL, LDAP_SASL_QUIET, sudo_ldap_sasl_interact, auth_id);
if (ldap_conf.krb5_ccname != NULL) {
# ifdef HAVE_GSS_KRB5_CCACHE_NAME
if (gss_krb5_ccache_name(&status, old_ccname, NULL) != GSS_S_COMPLETE)
sudo_debug_printf(SUDO_DEBUG_WARN|SUDO_DEBUG_LINENO,
"gss_krb5_ccache_name() failed: %d", status);
# else
if (old_ccname != NULL)
sudo_setenv("KRB5CCNAME", old_ccname, true);
else
sudo_unsetenv("KRB5CCNAME");
# endif
if (new_ccname != NULL) {
rc = sudo_set_krb5_ccache_name(old_ccname, NULL);
if (rc == 0) {
sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
"restore ccache name %s -> %s", new_ccname, old_ccname);
} else {
sudo_debug_printf(SUDO_DEBUG_WARN|SUDO_DEBUG_LINENO,
"gss_krb5_ccache_name() failed: %d", rc);
}
/* Remove temporary copy of user's credential cache. */
if (tmp_ccname != NULL)
unlink(tmp_ccname);
}
if (rc != LDAP_SUCCESS) {
warningx("ldap_sasl_interactive_bind_s(): %s",