summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSimo Sorce <ssorce@server.ipatest.com>2009-05-22 20:19:41 +0200
committerMartin Nagy <mnagy@redhat.com>2009-05-25 16:18:50 +0200
commit526c492627f0e14d04750569dcf0a2ff726d4b73 (patch)
tree61ebcd4db3192fcc5d974a7c72bc9e2ac563cadc
parent0bb0471612f709a360a9b20f2c792f72fe05a52d (diff)
downloadldap_driver-526c492627f0e14d04750569dcf0a2ff726d4b73.tar.gz
ldap_driver-526c492627f0e14d04750569dcf0a2ff726d4b73.tar.xz
ldap_driver-526c492627f0e14d04750569dcf0a2ff726d4b73.zip
Add basic support to get a tgt autonomously
Use mutex to serialize kinit. Reuse existing valid creedentials if any.
-rw-r--r--src/Makefile.am2
-rw-r--r--src/krb5_helper.c185
-rw-r--r--src/krb5_helper.h2
-rw-r--r--src/ldap_helper.c36
4 files changed, 225 insertions, 0 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
index 60af3ba..625d9da 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -4,6 +4,7 @@ bindplugindir=$(libdir)/bind
HDRS = \
acl.h \
cache.h \
+ krb5_helper.h \
ldap_convert.h \
ldap_helper.h \
log.h \
@@ -18,6 +19,7 @@ ldap_la_SOURCES = \
$(HDRS) \
acl.c \
cache.c \
+ krb5_helper.c \
ldap_convert.c \
ldap_driver.c \
ldap_helper.c \
diff --git a/src/krb5_helper.c b/src/krb5_helper.c
new file mode 100644
index 0000000..c69a947
--- /dev/null
+++ b/src/krb5_helper.c
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) Simo Sorce <ssorce@redhat.com> 2009
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; version 2 or later
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#define _BSD_SOURCE
+
+#include <isc/util.h>
+#include <string.h>
+#include <stdlib.h>
+#include <krb5.h>
+#include "util.h"
+#include "str.h"
+#include "log.h"
+
+#define DEFAULT_KEYTAB "FILE:/etc/named.keytab"
+#define MIN_TIME 300 /* 5 minutes */
+
+#define CHECK_KRB5(ctx, err, msg, ...) \
+ do { \
+ if (err) { \
+ log_error(msg " (%s)", ##__VA_ARGS__, \
+ krb5_get_error_message(ctx, err)); \
+ result = ISC_R_FAILURE; \
+ goto cleanup; \
+ } \
+ } while(0)
+
+static isc_result_t
+check_credentials(krb5_context context,
+ krb5_ccache ccache,
+ krb5_principal service)
+{
+ char *realm = NULL;
+ krb5_creds creds;
+ krb5_creds mcreds;
+ krb5_timestamp now;
+ krb5_error_code krberr;
+ isc_result_t result;
+
+ memset(&mcreds, 0, sizeof(mcreds));
+ memset(&creds, 0, sizeof(creds));
+
+ krberr = krb5_get_default_realm(context, &realm);
+ CHECK_KRB5(context, krberr, "Failed to retrieve default realm");
+
+ krberr = krb5_build_principal(context, &mcreds.server,
+ strlen(realm), realm,
+ "krbtgt", realm, NULL);
+ CHECK_KRB5(context, krberr, "Failed to build tgt principal");
+
+ /* krb5_cc_retrieve_cred filters on both server and client */
+ mcreds.client = service;
+
+ krberr = krb5_cc_retrieve_cred(context, ccache, 0, &mcreds, &creds);
+ if (krberr) {
+ log_debug(2, "Principal not found in cred cache (%s)",
+ krb5_get_error_message(context, krberr));
+ result = ISC_R_FAILURE;
+ goto cleanup;
+ }
+
+ krberr = krb5_timeofday(context, &now);
+ CHECK_KRB5(context, krberr, "Failed to get timeofday");
+
+ if (now > (creds.times.endtime + MIN_TIME)) {
+ log_debug(2, "Credentials expired");
+ result = ISC_R_FAILURE;
+ goto cleanup;
+ }
+
+ result = ISC_R_SUCCESS;
+
+cleanup:
+ krb5_free_cred_contents(context, &creds);
+ if (mcreds.server) krb5_free_principal(context, mcreds.server);
+ if (realm) krb5_free_default_realm(context, realm);
+ return result;
+}
+
+isc_result_t
+get_krb5_tgt(isc_mem_t *mctx, const char *principal, const char *keyfile)
+{
+ ld_string_t *ccname = NULL;
+ krb5_context context = NULL;
+ krb5_keytab keytab = NULL;
+ krb5_ccache ccache = NULL;
+ krb5_principal kprincpw;
+ krb5_creds my_creds;
+ krb5_get_init_creds_opt options;
+ krb5_error_code krberr;
+ isc_result_t result;
+ int ret;
+
+ REQUIRE(principal != NULL && principal[0] != '\0');
+
+ if (keyfile == NULL || keyfile[0] == '\0') {
+ log_debug(2, "Using default keytab file name: %s",
+ DEFAULT_KEYTAB);
+ keyfile = DEFAULT_KEYTAB;
+ } else {
+ if (strcmp(keyfile, "FILE:") != 0) {
+ log_error("Unknown keytab file name format, "
+ "missing leading 'FILE:' prefix");
+ return ISC_R_FAILURE;
+ }
+ }
+
+ krberr = krb5_init_context(&context);
+ if (krberr) {
+ log_error("Failed to init kerberos context");
+ return ISC_R_FAILURE;
+ }
+
+ /* get credentials cache */
+ CHECK(str_new(mctx, &ccname));
+ CHECK(str_sprintf(ccname, "MEMORY:_ld_krb5_cc_%s", principal));
+
+ ret = setenv("KRB5CCNAME", str_buf(ccname), 1);
+ if (ret == -1) {
+ log_error("Failed to set KRB5CCNAME environment variable");
+ result = ISC_R_FAILURE;
+ goto cleanup;
+ }
+
+ krberr = krb5_cc_resolve(context, str_buf(ccname), &ccache);
+ CHECK_KRB5(context, krberr,
+ "Failed to resolve ccache name %s", ccname);
+
+ /* get krb5_principal from string */
+ krberr = krb5_parse_name(context, principal, &kprincpw);
+ CHECK_KRB5(context, krberr,
+ "Failed to parse the principal name %s", principal);
+
+ /* check if we already have valid credentials */
+ result = check_credentials(context, ccache, kprincpw);
+ if (result == ISC_R_SUCCESS) {
+ log_debug(2, "Found valid cached credentials");
+ goto cleanup;
+ }
+
+ /* open keytab */
+ krberr = krb5_kt_resolve(context, keyfile, &keytab);
+ CHECK_KRB5(context, krberr,
+ "Failed to resolve keytab file %s", keyfile);
+
+ memset(&my_creds, 0, sizeof(my_creds));
+ memset(&options, 0, sizeof(options));
+
+ krb5_get_init_creds_opt_set_address_list(&options, NULL);
+ krb5_get_init_creds_opt_set_forwardable(&options, 0);
+ krb5_get_init_creds_opt_set_proxiable(&options, 0);
+
+ /* get tgt */
+ krberr = krb5_get_init_creds_keytab(context, &my_creds, kprincpw,
+ keytab, 0, NULL, &options);
+ CHECK_KRB5(context, krberr, "Failed to init credentials");
+
+ /* store credentials in cache */
+ krberr = krb5_cc_initialize(context, ccache, kprincpw);
+ CHECK_KRB5(context, krberr, "Failed to initialize ccache");
+
+ krberr = krb5_cc_store_cred(context, ccache, &my_creds);
+ CHECK_KRB5(context, krberr, "Failed to store ccache");
+
+ result = ISC_R_SUCCESS;
+
+cleanup:
+ if (ccname) str_destroy(&ccname);
+ if (keytab) krb5_kt_close(context, keytab);
+ if (context) krb5_free_context(context);
+ return result;
+}
diff --git a/src/krb5_helper.h b/src/krb5_helper.h
new file mode 100644
index 0000000..6e608f4
--- /dev/null
+++ b/src/krb5_helper.h
@@ -0,0 +1,2 @@
+isc_result_t
+get_krb5_tgt(isc_mem_t *mctx, const char *principal, const char *keyfile);
diff --git a/src/ldap_helper.c b/src/ldap_helper.c
index 2c32003..4b40f35 100644
--- a/src/ldap_helper.c
+++ b/src/ldap_helper.c
@@ -46,6 +46,7 @@
#include <strings.h>
#include "acl.h"
+#include "krb5_helper.h"
#include "ldap_convert.h"
#include "ldap_helper.h"
#include "log.h"
@@ -108,6 +109,9 @@ struct ldap_db {
isc_rwlock_t zone_rwlock;
dns_rbt_t *zone_names;
+ /* krb5 kinit mutex */
+ isc_mutex_t kinit_lock;
+
/* Settings. */
ld_string_t *uri;
ld_string_t *base;
@@ -119,6 +123,7 @@ struct ldap_db {
ld_string_t *sasl_mech;
ld_string_t *sasl_user;
ld_string_t *sasl_realm;
+ ld_string_t *krb5_keytab;
};
struct ldap_instance {
@@ -284,6 +289,7 @@ new_ldap_db(isc_mem_t *mctx, dns_view_t *view, ldap_db_t **ldap_dbp,
{ "sasl_mech", default_string("ANONYMOUS") },
{ "sasl_user", default_string("") },
{ "sasl_realm", default_string("") },
+ { "krb5_keytab", default_string("") },
end_of_settings
};
@@ -307,6 +313,8 @@ new_ldap_db(isc_mem_t *mctx, dns_view_t *view, ldap_db_t **ldap_dbp,
CHECK(isc_rwlock_init(&ldap_db->zone_rwlock, 0, 0));
CHECK(dns_rbt_create(mctx, string_deleter, mctx, &ldap_db->zone_names));
+ CHECK(isc_mutex_init(&ldap_db->kinit_lock));
+
CHECK(str_new(mctx, &auth_method_str));
CHECK(str_new(mctx, &ldap_db->uri));
CHECK(str_new(mctx, &ldap_db->base));
@@ -315,6 +323,7 @@ new_ldap_db(isc_mem_t *mctx, dns_view_t *view, ldap_db_t **ldap_dbp,
CHECK(str_new(mctx, &ldap_db->sasl_mech));
CHECK(str_new(mctx, &ldap_db->sasl_user));
CHECK(str_new(mctx, &ldap_db->sasl_realm));
+ CHECK(str_new(mctx, &ldap_db->krb5_keytab));
i = 0;
ldap_settings[i++].target = ldap_db->uri;
@@ -327,6 +336,7 @@ new_ldap_db(isc_mem_t *mctx, dns_view_t *view, ldap_db_t **ldap_dbp,
ldap_settings[i++].target = ldap_db->sasl_mech;
ldap_settings[i++].target = ldap_db->sasl_user;
ldap_settings[i++].target = ldap_db->sasl_realm;
+ ldap_settings[i++].target = ldap_db->krb5_keytab;
CHECK(set_settings(ldap_settings, argv));
@@ -353,6 +363,17 @@ new_ldap_db(isc_mem_t *mctx, dns_view_t *view, ldap_db_t **ldap_dbp,
goto cleanup;
}
+ /* check we have the right data when SASL/GSSAPI is selected */
+ if ((ldap_db->auth_method == AUTH_SASL) &&
+ (str_casecmp_char(ldap_db->sasl_mech, "GSSAPI") == 0)) {
+ if ((ldap_db->sasl_user == NULL) ||
+ (str_len(ldap_db->sasl_user) == 0)) {
+ log_error("Sasl mech GSSAPI defined but sasl_user is empty");
+ result = ISC_R_FAILURE;
+ goto cleanup;
+ }
+ }
+
CHECK(semaphore_init(&ldap_db->conn_semaphore, ldap_db->connections));
for (i = 0; i < ldap_db->connections; i++) {
@@ -399,11 +420,14 @@ destroy_ldap_db(ldap_db_t **ldap_dbp)
str_destroy(&ldap_db->sasl_mech);
str_destroy(&ldap_db->sasl_user);
str_destroy(&ldap_db->sasl_realm);
+ str_destroy(&ldap_db->krb5_keytab);
semaphore_destroy(&ldap_db->conn_semaphore);
/* commented out for now, causes named to hang */
//dns_view_detach(&ldap_db->view);
+ DESTROYLOCK(&ldap_db->kinit_lock);
+
dns_rbt_destroy(&ldap_db->zone_names);
isc_rwlock_destroy(&ldap_db->zone_rwlock);
@@ -1605,6 +1629,18 @@ ldap_reconnect(ldap_instance_t *ldap_inst)
ret = ldap_simple_bind_s(ldap_inst->handle, bind_dn, password);
break;
case AUTH_SASL:
+
+ if (strcmp(str_buf(ldap_db->sasl_mech), "GSSAPI") == 0) {
+ isc_result_t result;
+ LOCK(&ldap_db->kinit_lock);
+ result = get_krb5_tgt(ldap_db->mctx,
+ str_buf(ldap_db->sasl_user),
+ str_buf(ldap_db->krb5_keytab));
+ UNLOCK(&ldap_db->kinit_lock);
+ if (result != ISC_R_SUCCESS)
+ return result;
+ }
+
log_error("%s", str_buf(ldap_db->sasl_mech));
ret = ldap_sasl_interactive_bind_s(ldap_inst->handle, NULL,
str_buf(ldap_db->sasl_mech),