diff --git a/include/lookup_ldap.h b/include/lookup_ldap.h
index 5b5c475..d6a754d 100644
--- a/include/lookup_ldap.h
+++ b/include/lookup_ldap.h
@@ -67,10 +67,10 @@ struct lookup_context {
char *user;
char *secret;
char *client_princ;
+ char *client_cc;
int kinit_done;
int kinit_successful;
#ifdef WITH_SASL
- krb5_principal krb5_client_princ;
krb5_context krb5ctxt;
krb5_ccache krb5_ccache;
sasl_conn_t *sasl_conn;
diff --git a/modules/cyrus-sasl.c b/modules/cyrus-sasl.c
index 303b7f2..f24f46b 100644
--- a/modules/cyrus-sasl.c
+++ b/modules/cyrus-sasl.c
@@ -72,6 +72,7 @@
*/
static const char *krb5ccenv = "KRB5CCNAME";
static const char *krb5ccval = "MEMORY:_autofstkt";
+static const char *default_client = "autofsclient";
static pthread_mutex_t krb5cc_mutex = PTHREAD_MUTEX_INITIALIZER;
static unsigned int krb5cc_in_use = 0;
@@ -376,7 +377,7 @@ int
sasl_do_kinit(unsigned logopt, struct lookup_context *ctxt)
{
krb5_error_code ret;
- krb5_principal tgs_princ, krb5_client_princ = ctxt->krb5_client_princ;
+ krb5_principal tgs_princ, krb5_client_princ;
krb5_creds my_creds;
char *tgs_name;
int status;
@@ -386,8 +387,8 @@ sasl_do_kinit(unsigned logopt, struct lookup_context *ctxt)
ctxt->kinit_done = 1;
debug(logopt,
- "initializing kerberos ticket: client principal %s ",
- ctxt->client_princ ? ctxt->client_princ : "autofsclient");
+ "initializing kerberos ticket: client principal %s",
+ ctxt->client_princ ? ctxt->client_princ : default_client);
ret = krb5_init_context(&ctxt->krb5ctxt);
if (ret) {
@@ -424,13 +425,12 @@ sasl_do_kinit(unsigned logopt, struct lookup_context *ctxt)
"calling krb5_sname_to_principal using defaults");
ret = krb5_sname_to_principal(ctxt->krb5ctxt, NULL,
- "autofsclient", KRB5_NT_SRV_HST,
+ default_client, KRB5_NT_SRV_HST,
&krb5_client_princ);
if (ret) {
error(logopt,
"krb5_sname_to_principal failed for "
- "%s with error %d",
- ctxt->client_princ ? "" : "autofsclient", ret);
+ "%s with error %d", default_client, ret);
goto out_cleanup_cc;
}
@@ -441,11 +441,11 @@ sasl_do_kinit(unsigned logopt, struct lookup_context *ctxt)
debug(logopt,
"krb5_unparse_name failed with error %d",
ret);
- goto out_cleanup_cc;
+ goto out_cleanup_client_princ;
}
debug(logopt,
- "principal used for authentication: \"%s\"", tmp_name);
+ "principal used for authentication: %s", tmp_name);
krb5_free_unparsed_name(ctxt->krb5ctxt, tmp_name);
}
@@ -461,14 +461,14 @@ sasl_do_kinit(unsigned logopt, struct lookup_context *ctxt)
if (ret) {
error(logopt,
"krb5_build_principal failed with error %d", ret);
- goto out_cleanup_cc;
+ goto out_cleanup_client_princ;
}
ret = krb5_unparse_name(ctxt->krb5ctxt, tgs_princ, &tgs_name);
if (ret) {
error(logopt, "krb5_unparse_name failed with error %d",
ret);
- goto out_cleanup_cc;
+ goto out_cleanup_client_princ;
}
debug(logopt, "Using tgs name %s", tgs_name);
@@ -486,7 +486,6 @@ sasl_do_kinit(unsigned logopt, struct lookup_context *ctxt)
goto out_cleanup_unparse;
}
-
status = pthread_mutex_lock(&krb5cc_mutex);
if (status)
fatal(status);
@@ -503,7 +502,7 @@ sasl_do_kinit(unsigned logopt, struct lookup_context *ctxt)
if (ret) {
error(logopt,
"krb5_cc_initialize failed with error %d", ret);
- goto out_cleanup_unparse;
+ goto out_cleanup_creds;
}
/* and store credentials for that principal */
@@ -511,26 +510,34 @@ sasl_do_kinit(unsigned logopt, struct lookup_context *ctxt)
if (ret) {
error(logopt,
"krb5_cc_store_cred failed with error %d", ret);
- goto out_cleanup_unparse;
+ goto out_cleanup_creds;
}
/* finally, set the environment variable to point to our
* credentials cache */
if (setenv(krb5ccenv, krb5ccval, 1) != 0) {
error(logopt, "setenv failed with %d", errno);
- goto out_cleanup_unparse;
+ goto out_cleanup_creds;
}
ctxt->kinit_successful = 1;
debug(logopt, "Kerberos authentication was successful!");
krb5_free_unparsed_name(ctxt->krb5ctxt, tgs_name);
+ krb5_free_cred_contents(ctxt->krb5ctxt, &my_creds);
+ krb5_free_principal(ctxt->krb5ctxt, tgs_princ);
+ krb5_free_principal(ctxt->krb5ctxt, krb5_client_princ);
return 0;
-out_cleanup_unparse:
+out_cleanup_creds:
krb5cc_in_use--;
+ krb5_free_cred_contents(ctxt->krb5ctxt, &my_creds);
+out_cleanup_unparse:
+ krb5_free_principal(ctxt->krb5ctxt, tgs_princ);
krb5_free_unparsed_name(ctxt->krb5ctxt, tgs_name);
+out_cleanup_client_princ:
+ krb5_free_principal(ctxt->krb5ctxt, krb5_client_princ);
out_cleanup_cc:
status = pthread_mutex_lock(&krb5cc_mutex);
if (status)
@@ -554,6 +561,152 @@ out_cleanup_cc:
}
/*
+ * Check a client given external credential cache.
+ *
+ * Returns 0 upon success. ctxt->kinit_done and ctxt->kinit_successful
+ * are set for cleanup purposes. The krb5 context and ccache entries in
+ * the lookup_context are also filled in.
+ *
+ * Upon failure, -1 is returned.
+ */
+int
+sasl_do_kinit_ext_cc(unsigned logopt, struct lookup_context *ctxt)
+{
+ krb5_principal def_princ;
+ krb5_principal krb5_client_princ;
+ krb5_error_code ret;
+ char *cc_princ, *client_princ;
+
+ if (ctxt->kinit_done)
+ return 0;
+ ctxt->kinit_done = 1;
+
+ debug(logopt,
+ "using external credential cache for auth: client principal %s",
+ ctxt->client_princ ? ctxt->client_princ : default_client);
+
+ ret = krb5_init_context(&ctxt->krb5ctxt);
+ if (ret) {
+ error(logopt, "krb5_init_context failed with %d", ret);
+ return -1;
+ }
+
+ ret = krb5_cc_resolve(ctxt->krb5ctxt, ctxt->client_cc, &ctxt->krb5_ccache);
+ if (ret) {
+ error(logopt, "krb5_cc_resolve failed with error %d",
+ ret);
+ krb5_cc_close(ctxt->krb5ctxt, ctxt->krb5_ccache);
+ krb5_free_context(ctxt->krb5ctxt);
+ return -1;
+ }
+
+ ret = krb5_cc_get_principal(ctxt->krb5ctxt, ctxt->krb5_ccache, &def_princ);
+ if (ret) {
+ error(logopt, "krb5_cc_get_principal failed with error %d", ret);
+ krb5_cc_close(ctxt->krb5ctxt, ctxt->krb5_ccache);
+ krb5_free_context(ctxt->krb5ctxt);
+ return -1;
+ }
+
+ ret = krb5_unparse_name(ctxt->krb5ctxt, def_princ, &cc_princ);
+ if (ret) {
+ error(logopt, "krb5_unparse_name failed with error %d", ret);
+ krb5_free_principal(ctxt->krb5ctxt, def_princ);
+ krb5_cc_close(ctxt->krb5ctxt, ctxt->krb5_ccache);
+ krb5_free_context(ctxt->krb5ctxt);
+ return -1;
+ }
+
+ debug(logopt, "external credential cache default principal %s", cc_princ);
+
+ /*
+ * If the principal isn't set in the config construct the default
+ * so we can check against the default principal of the external
+ * cred cache.
+ */
+ if (ctxt->client_princ)
+ client_princ = ctxt->client_princ;
+ else {
+ debug(logopt,
+ "calling krb5_sname_to_principal using defaults");
+
+ ret = krb5_sname_to_principal(ctxt->krb5ctxt, NULL,
+ default_client, KRB5_NT_SRV_HST,
+ &krb5_client_princ);
+ if (ret) {
+ error(logopt,
+ "krb5_sname_to_principal failed for "
+ "%s with error %d", default_client, ret);
+ krb5_free_principal(ctxt->krb5ctxt, def_princ);
+ krb5_cc_close(ctxt->krb5ctxt, ctxt->krb5_ccache);
+ krb5_free_context(ctxt->krb5ctxt);
+ return -1;
+ }
+
+
+ ret = krb5_unparse_name(ctxt->krb5ctxt,
+ krb5_client_princ, &client_princ);
+ if (ret) {
+ debug(logopt,
+ "krb5_unparse_name failed with error %d",
+ ret);
+ krb5_free_principal(ctxt->krb5ctxt, krb5_client_princ);
+ krb5_free_principal(ctxt->krb5ctxt, def_princ);
+ krb5_cc_close(ctxt->krb5ctxt, ctxt->krb5_ccache);
+ krb5_free_context(ctxt->krb5ctxt);
+ return -1;
+ }
+
+ debug(logopt,
+ "principal used for authentication: %s", client_princ);
+
+ krb5_free_principal(ctxt->krb5ctxt, krb5_client_princ);
+ }
+
+ /*
+ * Check if the principal to be used matches the default principal in
+ * the external cred cache.
+ */
+ if (strcmp(cc_princ, client_princ)) {
+ error(logopt,
+ "configured client principal %s ",
+ ctxt->client_princ);
+ error(logopt,
+ "external credential cache default principal %s",
+ cc_princ);
+ error(logopt,
+ "cannot use credential cache, external "
+ "default principal does not match configured "
+ "principal");
+ if (!ctxt->client_princ)
+ krb5_free_unparsed_name(ctxt->krb5ctxt, client_princ);
+ krb5_free_unparsed_name(ctxt->krb5ctxt, cc_princ);
+ krb5_free_principal(ctxt->krb5ctxt, def_princ);
+ krb5_cc_close(ctxt->krb5ctxt, ctxt->krb5_ccache);
+ krb5_free_context(ctxt->krb5ctxt);
+ return -1;
+ }
+
+ if (!ctxt->client_princ)
+ krb5_free_unparsed_name(ctxt->krb5ctxt, client_princ);
+ krb5_free_unparsed_name(ctxt->krb5ctxt, cc_princ);
+ krb5_free_principal(ctxt->krb5ctxt, def_princ);
+
+ /* Set the environment variable to point to the external cred cache */
+ if (setenv(krb5ccenv, ctxt->client_cc, 1) != 0) {
+ error(logopt, "setenv failed with %d", errno);
+ krb5_cc_close(ctxt->krb5ctxt, ctxt->krb5_ccache);
+ krb5_free_context(ctxt->krb5ctxt);
+ return -1;
+ }
+ ctxt->kinit_successful = 1;
+
+ debug(logopt, "Kerberos authentication was successful!");
+
+ return 0;
+}
+
+/*
* Attempt to bind to the ldap server using a given authentication
* mechanism. ldap should be a properly initialzed ldap pointer.
*
@@ -570,7 +723,11 @@ sasl_bind_mech(unsigned logopt, LDAP *ldap, struct lookup_context *ctxt, const c
int result;
if (!strncmp(mech, "GSSAPI", 6)) {
- if (sasl_do_kinit(logopt, ctxt) != 0)
+ if (ctxt->client_cc)
+ result = sasl_do_kinit_ext_cc(logopt, ctxt);
+ else
+ result = sasl_do_kinit(logopt, ctxt);
+ if (result != 0)
return NULL;
}
@@ -774,7 +931,7 @@ autofs_sasl_done(struct lookup_context *ctxt)
if (status)
fatal(status);
- if (--krb5cc_in_use)
+ if (--krb5cc_in_use || ctxt->client_cc)
ret = krb5_cc_close(ctxt->krb5ctxt, ctxt->krb5_ccache);
else
ret = krb5_cc_destroy(ctxt->krb5ctxt, ctxt->krb5_ccache);
diff --git a/modules/lookup_ldap.c b/modules/lookup_ldap.c
index 93f0477..13fbff7 100644
--- a/modules/lookup_ldap.c
+++ b/modules/lookup_ldap.c
@@ -651,7 +651,7 @@ int parse_ldap_config(unsigned logopt, struct lookup_context *ctxt)
xmlNodePtr root = NULL;
char *authrequired, *auth_conf, *authtype;
char *user = NULL, *secret = NULL;
- char *client_princ = NULL;
+ char *client_princ = NULL, *client_cc = NULL;
char *usetls, *tlsrequired;
authtype = user = secret = NULL;
@@ -840,6 +840,7 @@ int parse_ldap_config(unsigned logopt, struct lookup_context *ctxt)
* client. The default is "autofsclient/hostname@REALM".
*/
(void)get_property(logopt, root, "clientprinc", &client_princ);
+ (void)get_property(logopt, root, "credentialcache", &client_cc);
ctxt->auth_conf = auth_conf;
ctxt->use_tls = use_tls;
@@ -851,6 +852,7 @@ int parse_ldap_config(unsigned logopt, struct lookup_context *ctxt)
ctxt->user = user;
ctxt->secret = secret;
ctxt->client_princ = client_princ;
+ ctxt->client_cc = client_cc;
debug(logopt, MODPREFIX
"ldap authentication configured with the following options:");
@@ -863,9 +865,10 @@ int parse_ldap_config(unsigned logopt, struct lookup_context *ctxt)
debug(logopt, MODPREFIX
"user: %s, "
"secret: %s, "
- "client principal: %s",
+ "client principal: %s "
+ "credential cache: %s",
user, secret ? "specified" : "unspecified",
- client_princ);
+ client_princ, client_cc);
out:
xmlFreeDoc(doc);
@@ -1128,6 +1131,8 @@ static void free_context(struct lookup_context *ctxt)
free(ctxt->secret);
if (ctxt->client_princ)
free(ctxt->client_princ);
+ if (ctxt->client_cc)
+ free(ctxt->client_cc);
if (ctxt->mapname)
free(ctxt->mapname);
if (ctxt->qdn)
diff --git a/samples/autofs_ldap_auth.conf b/samples/autofs_ldap_auth.conf
index e10d1ea..a1f60c0 100644
--- a/samples/autofs_ldap_auth.conf
+++ b/samples/autofs_ldap_auth.conf
@@ -56,6 +56,11 @@ clientprinc - When using GSSAPI authentication, this attribute is
consulted to determine the principal name to use when
authenticating to the directory server. By default, this will
be set to "autofsclient/<fqdn>@<REALM>.
+
+credentialcache - When using GSSAPI authentication, this attribute
+ can be used to specify an externally configured credential
+ cache that is used during authentication. By default, autofs
+ will setup a memory based credential cache.
-->
<autofs_ldap_sasl_conf