From 526c492627f0e14d04750569dcf0a2ff726d4b73 Mon Sep 17 00:00:00 2001 From: Simo Sorce Date: Fri, 22 May 2009 20:19:41 +0200 Subject: Add basic support to get a tgt autonomously Use mutex to serialize kinit. Reuse existing valid creedentials if any. --- src/Makefile.am | 2 + src/krb5_helper.c | 185 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/krb5_helper.h | 2 + src/ldap_helper.c | 36 +++++++++++ 4 files changed, 225 insertions(+) create mode 100644 src/krb5_helper.c create mode 100644 src/krb5_helper.h 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 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 +#include +#include +#include +#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 #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), -- cgit