summaryrefslogtreecommitdiffstats
path: root/ldap/servers/slapd
diff options
context:
space:
mode:
authorRich Megginson <rmeggins@redhat.com>2008-11-12 17:42:37 +0000
committerRich Megginson <rmeggins@redhat.com>2008-11-12 17:42:37 +0000
commitf376efb4b33d7c9ba5c99dd487a773f6d1538766 (patch)
tree64c6bf3b449351b2141a5788d95d0e6b062ce050 /ldap/servers/slapd
parentb9dd50478ad4ad824da670c7f5053e2fecb14613 (diff)
downloadds-f376efb4b33d7c9ba5c99dd487a773f6d1538766.tar.gz
ds-f376efb4b33d7c9ba5c99dd487a773f6d1538766.tar.xz
ds-f376efb4b33d7c9ba5c99dd487a773f6d1538766.zip
Resolves: bug 469261
Bug Description: Support server-to-server SASL - kerberos improvements Reviewed by: ssorce (Thanks!) Fix Description: I made several improvements to the kerberos code at Simo's suggestion First look for the principal in the ccache. If not found, use the username if it does not look like a DN. If still not found, construct a principal using the krb5_sname_to_principal() function to construct "ldap/fqdn@REALM". Next, see if the credentials for this principal are still valid. In order to grab the credentials from the ccache, I needed to construct the server principal, which in this case is the TGS service principal (e.g. krbtgt/REALM@REALM). If the credentials are present and not expired, then the code assumes they are ok and does not acquire new credentials. If the credentials are expired or not found, the code will then use the keytab to authenticate. Based on more feedback from Simo, I made some additional changes: * Go ahead and reacquire the creds if they have expired or will expire in 30 seconds - this is not configurable but could be made to be - 30 seconds should be long enough so that the credentials will not expire by the time they are actually used deep in the ldap/sasl/gssapi/krb code, and short enough so that this won't cause unnecessary credential churn * Retry the bind in the case of Ticket expired. There is no way that I can see to get the actual error code - fortunately the extended ldap error message has this information Platforms tested: Fedora 8, Fedora 9 Flag Day: no Doc impact: oh yes
Diffstat (limited to 'ldap/servers/slapd')
-rw-r--r--ldap/servers/slapd/util.c291
1 files changed, 205 insertions, 86 deletions
diff --git a/ldap/servers/slapd/util.c b/ldap/servers/slapd/util.c
index 42aec6b7..43f93cb1 100644
--- a/ldap/servers/slapd/util.c
+++ b/ldap/servers/slapd/util.c
@@ -72,10 +72,6 @@
#define _CSEP '/'
#endif
-#ifdef HAVE_KRB5
-static void set_krb5_creds();
-#endif
-
static int special_np(unsigned char c)
{
@@ -1136,7 +1132,7 @@ slapi_ldap_bind(
if (msgidp) { /* let caller process result */
*msgidp = mymsgid;
} else { /* process results */
- rc = ldap_result(ld, mymsgid, LDAP_MSG_ALL, timeout, &result);
+ rc = ldap_result(ld, mymsgid, LDAP_MSG_ALL, timeout, &result);
if (-1 == rc) { /* error */
rc = ldap_get_lderrno(ld, NULL, NULL);
slapi_log_error(SLAPI_LOG_FATAL, "slapi_ldap_bind",
@@ -1219,6 +1215,16 @@ typedef struct {
char *realm;
} ldapSaslInteractVals;
+#ifdef HAVE_KRB5
+static void set_krb5_creds(
+ const char *authid,
+ const char *username,
+ const char *passwd,
+ const char *realm,
+ ldapSaslInteractVals *vals
+);
+#endif
+
static void *
ldap_sasl_set_interact_vals(LDAP *ld, const char *mech, const char *authid,
const char *username, const char *passwd,
@@ -1249,12 +1255,6 @@ ldap_sasl_set_interact_vals(LDAP *ld, const char *mech, const char *authid,
}
}
-#ifdef HAVE_KRB5
- if (mech && !strcmp(mech, "GSSAPI")) {
- username = NULL; /* get from krb creds */
- }
-#endif
-
if (username) { /* use explicit passed in value */
vals->username = slapi_ch_strdup(username);
} else { /* use option value if any */
@@ -1281,7 +1281,7 @@ ldap_sasl_set_interact_vals(LDAP *ld, const char *mech, const char *authid,
#ifdef HAVE_KRB5
if (mech && !strcmp(mech, "GSSAPI")) {
- set_krb5_creds();
+ set_krb5_creds(authid, username, passwd, realm, vals);
}
#endif /* HAVE_KRB5 */
@@ -1368,6 +1368,20 @@ ldap_sasl_interact_cb(LDAP *ld, unsigned flags, void *defaults, void *prompts)
return (LDAP_SUCCESS);
}
+/* figure out from the context and this error if we should
+ attempt to retry the bind */
+static int
+can_retry_bind(LDAP *ld, const char *mech, const char *bindid,
+ const char *creds, int rc, const char *errmsg)
+{
+ int localrc = 0;
+ if (errmsg && strstr(errmsg, "Ticket expired")) {
+ localrc = 1;
+ }
+
+ return localrc;
+}
+
int
slapd_ldap_sasl_interactive_bind(
LDAP *ld, /* ldap connection */
@@ -1380,22 +1394,36 @@ slapd_ldap_sasl_interactive_bind(
)
{
int rc = LDAP_SUCCESS;
- void *defaults = ldap_sasl_set_interact_vals(ld, mech, NULL, bindid,
- creds, NULL);
- /* have to first set the defaults used by the callback function */
- /* call the bind function */
- rc = ldap_sasl_interactive_bind_ext_s(ld, bindid, mech, serverctrls,
- NULL, LDAP_SASL_QUIET,
- ldap_sasl_interact_cb, defaults,
- returnedctrls);
- ldap_sasl_free_interact_vals(defaults);
- if (LDAP_SUCCESS != rc) {
- slapi_log_error(SLAPI_LOG_FATAL, "slapd_ldap_sasl_interactive_bind",
- "Error: could not perform interactive bind for id "
- "[%s] mech [%s]: error %d (%s)\n",
- bindid ? bindid : "(anon)",
- mech ? mech : "SIMPLE",
- rc, ldap_err2string(rc));
+ int tries = 0;
+
+ while (tries < 2) {
+ void *defaults = ldap_sasl_set_interact_vals(ld, mech, NULL, bindid,
+ creds, NULL);
+ /* have to first set the defaults used by the callback function */
+ /* call the bind function */
+ rc = ldap_sasl_interactive_bind_ext_s(ld, bindid, mech, serverctrls,
+ NULL, LDAP_SASL_QUIET,
+ ldap_sasl_interact_cb, defaults,
+ returnedctrls);
+ ldap_sasl_free_interact_vals(defaults);
+ if (LDAP_SUCCESS != rc) {
+ char *errmsg = NULL;
+ rc = ldap_get_lderrno(ld, NULL, &errmsg);
+ slapi_log_error(SLAPI_LOG_FATAL, "slapd_ldap_sasl_interactive_bind",
+ "Error: could not perform interactive bind for id "
+ "[%s] mech [%s]: error %d (%s) (%s)\n",
+ bindid ? bindid : "(anon)",
+ mech ? mech : "SIMPLE",
+ rc, ldap_err2string(rc), errmsg);
+ if (can_retry_bind(ld, mech, bindid, creds, rc, errmsg)) {
+ ; /* pass through to retry one time */
+ } else {
+ break; /* done - fail - cannot retry */
+ }
+ } else {
+ break; /* done - success */
+ }
+ tries++;
}
return rc;
@@ -1506,6 +1534,94 @@ cleanup:
return;
}
+static int
+looks_like_a_dn(const char *username)
+{
+ return (username && strchr(username, '='));
+}
+
+static int
+credentials_are_valid(
+ krb5_context ctx,
+ krb5_ccache cc,
+ krb5_principal princ,
+ const char *princ_name,
+ int *rc
+)
+{
+ char *logname = "credentials_are_valid";
+ int myrc = 0;
+ krb5_creds mcreds; /* match these values */
+ krb5_creds creds; /* returned creds */
+ char *tgs_princ_name = NULL;
+ krb5_timestamp currenttime;
+ int authtracelevel = SLAPI_LOG_SHELL; /* special auth tracing */
+ int realm_len;
+ char *realm_str;
+ int time_buffer = 30; /* seconds - go ahead and renew if creds are
+ about to expire */
+
+ memset(&mcreds, 0, sizeof(mcreds));
+ memset(&creds, 0, sizeof(creds));
+ *rc = 0;
+ if (!cc) {
+ /* ok - no error */
+ goto cleanup;
+ }
+
+ /* have to construct the tgs server principal in
+ order to set mcreds.server required in order
+ to use krb5_cc_retrieve_creds() */
+ /* get default realm first */
+ realm_len = krb5_princ_realm(ctx, princ)->length;
+ realm_str = krb5_princ_realm(ctx, princ)->data;
+ tgs_princ_name = slapi_ch_smprintf("%s/%*s@%*s", KRB5_TGS_NAME,
+ realm_len, realm_str,
+ realm_len, realm_str);
+
+ if ((*rc = krb5_parse_name(ctx, tgs_princ_name, &mcreds.server))) {
+ slapi_log_error(SLAPI_LOG_FATAL, logname,
+ "Could parse principal [%s]: %d (%s)\n",
+ tgs_princ_name, *rc, error_message(*rc));
+ goto cleanup;
+ }
+
+ mcreds.client = princ;
+ if ((*rc = krb5_cc_retrieve_cred(ctx, cc, 0, &mcreds, &creds))) {
+ if (*rc == KRB5_CC_NOTFOUND) {
+ /* ok - no creds for this princ in the cache */
+ *rc = 0;
+ }
+ goto cleanup;
+ }
+
+ /* have the creds - now look at the timestamp */
+ if ((*rc = krb5_timeofday(ctx, &currenttime))) {
+ slapi_log_error(SLAPI_LOG_FATAL, logname,
+ "Could not get current time: %d (%s)\n",
+ *rc, error_message(*rc));
+ goto cleanup;
+ }
+
+ if (currenttime > (creds.times.endtime + time_buffer)) {
+ slapi_log_error(authtracelevel, logname,
+ "Credentials for [%s] have expired or will soon "
+ "expire - now [%d] endtime [%d]\n", princ_name,
+ currenttime, creds.times.endtime);
+ goto cleanup;
+ }
+
+ myrc = 1; /* credentials are valid */
+cleanup:
+ krb5_free_cred_contents(ctx, &creds);
+ slapi_ch_free_string(&tgs_princ_name);
+ if (mcreds.server) {
+ krb5_free_principal(ctx, mcreds.server);
+ }
+
+ return myrc;
+}
+
/*
* This implementation assumes that we want to use the
* keytab from the default keytab env. var KRB5_KTNAME
@@ -1517,7 +1633,13 @@ cleanup:
* env var to point to those credentials.
*/
static void
-set_krb5_creds()
+set_krb5_creds(
+ const char *authid,
+ const char *username,
+ const char *passwd,
+ const char *realm,
+ ldapSaslInteractVals *vals
+)
{
char *logname = "set_krb5_creds";
const char *cc_type = "MEMORY"; /* keep cred cache in memory */
@@ -1526,11 +1648,8 @@ set_krb5_creds()
krb5_principal princ = NULL;
char *princ_name = NULL;
krb5_error_code rc = 0;
- krb5_error_code looprc = 0;
krb5_creds creds;
krb5_keytab kt = NULL;
- krb5_keytab_entry ktent;
- krb5_kt_cursor ktcur = NULL;
char *cc_name = NULL;
char ktname[MAX_KEYTAB_NAME_LEN];
static char cc_env_name[1024+32]; /* size from ccdefname.c */
@@ -1634,6 +1753,57 @@ set_krb5_creds()
goto cleanup;
}
+ /* need to figure out which principal to use
+ 1) use the one from the ccache
+ 2) use username
+ 3) construct one in the form ldap/fqdn@REALM
+ */
+ if (!princ && username && !looks_like_a_dn(username) &&
+ (rc = krb5_parse_name(ctx, username, &princ))) {
+ slapi_log_error(SLAPI_LOG_FATAL, logname,
+ "Error: could not convert [%s] into a kerberos "
+ "principal: %d (%s)\n", username,
+ rc, error_message(rc));
+ goto cleanup;
+ }
+
+ /* if still no principal, construct one */
+ if (!princ &&
+ (rc = krb5_sname_to_principal(ctx, NULL, "ldap",
+ KRB5_NT_SRV_HST, &princ))) {
+ slapi_log_error(SLAPI_LOG_FATAL, logname,
+ "Error: could not construct ldap service "
+ "principal: %d (%s)\n", rc, error_message(rc));
+ goto cleanup;
+ }
+
+ if ((rc = krb5_unparse_name(ctx, princ, &princ_name))) {
+ slapi_log_error(SLAPI_LOG_FATAL, logname,
+ "Unable to get name of principal: "
+ "%d (%s)\n", rc, error_message(rc));
+ goto cleanup;
+ }
+
+ slapi_log_error(authtracelevel, logname,
+ "Using principal named [%s]\n", princ_name);
+
+ /* grab the credentials from the ccache, if any -
+ if the credentials are still valid, we do not have
+ to authenticate again */
+ if (credentials_are_valid(ctx, cc, princ, princ_name, &rc)) {
+ slapi_log_error(authtracelevel, logname,
+ "Credentials for principal [%s] are still "
+ "valid - no auth is necessary.\n",
+ princ_name);
+ goto cleanup;
+ } else if (rc) { /* some error other than "there are no credentials" */
+ slapi_log_error(SLAPI_LOG_FATAL, logname,
+ "Unable to verify cached credentials for "
+ "principal [%s]: %d (%s)\n", princ_name,
+ rc, error_message(rc));
+ goto cleanup;
+ }
+
/* find our default keytab */
if ((rc = krb5_kt_default(ctx, &kt))) {
slapi_log_error(SLAPI_LOG_FATAL, logname,
@@ -1653,60 +1823,6 @@ set_krb5_creds()
slapi_log_error(authtracelevel, logname,
"Using keytab named [%s]\n", ktname);
- /* if there was no cache, or no principal in the cache, we look
- in the keytab */
- if (!princ) {
- /* just use the first principal in the keytab
- "first principals, clarice"
- */
- if ((rc = krb5_kt_start_seq_get(ctx, kt, &ktcur))) {
- slapi_log_error(SLAPI_LOG_FATAL, logname,
- "Unable to open keytab [%s] cursor: %d (%s)\n",
- ktname, rc, error_message(rc));
- goto cleanup;
- }
-
- memset(&ktent, 0, sizeof(ktent));
- while ((looprc = krb5_kt_next_entry(ctx, kt, &ktent, &ktcur)) == 0) {
- if ((looprc = krb5_unparse_name(ctx, ktent.principal,
- &princ_name))) {
- slapi_log_error(SLAPI_LOG_FATAL, logname,
- "Unable to get name from keytab [%s] "
- "principal: %d (%s)\n", ktname, looprc,
- error_message(looprc));
- break;
- }
- /* found one - make a copy to free later */
- if ((looprc = krb5_copy_principal(ctx, ktent.principal,
- &princ))) {
- slapi_log_error(SLAPI_LOG_FATAL, logname,
- "Unable to copy keytab [%s] principal [%s]: "
- "%d (%s)\n", ktname, princ_name, looprc,
- error_message(looprc));
- break;
- }
- slapi_log_error(authtracelevel, logname,
- "Using keytab principal [%s]\n", princ_name);
- break;
- }
-
- krb5_free_keytab_entry_contents(ctx, &ktent);
- memset(&ktent, 0, sizeof(ktent));
- if ((rc = krb5_kt_end_seq_get(ctx, kt, &ktcur))) {
- slapi_log_error(SLAPI_LOG_FATAL, logname,
- "Unable to close keytab [%s] cursor: %d (%s)\n",
- ktname, rc, error_message(rc));
- goto cleanup;
- }
-
- /* if we had an error in the loop above, just bail out
- after closing the keytab cursor and keytab */
- if (looprc) {
- rc = looprc;
- goto cleanup;
- }
- }
-
/* now do the actual kerberos authentication using
the keytab, and get the creds */
rc = krb5_get_init_creds_keytab(ctx, &creds, princ, kt,
@@ -1809,6 +1925,9 @@ set_krb5_creds()
cc_env_name);
}
+ /* use NULL as username */
+ slapi_ch_free_string(&vals->username);
+
cleanup:
krb5_free_unparsed_name(ctx, princ_name);
if (kt) { /* NULL not allowed */