From 5d378a280f74405fccbadbfb28e1066613c76fd8 Mon Sep 17 00:00:00 2001 From: Andrew Tridgell Date: Sat, 8 Dec 2001 11:18:56 +0000 Subject: added internal sasl/gssapi code. This means we are no longer dependent on cyrus-sasl which makes the code much less fragile. Also added code to auto-determine the server name or realm (This used to be commit 435fdf276a79c2a517adcd7726933aeef3fa924b) --- source3/libads/ads_struct.c | 49 ++---------- source3/libads/kerberos.c | 24 ++++-- source3/libads/ldap.c | 138 ++++++++++++++++---------------- source3/libads/sasl.c | 186 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 279 insertions(+), 118 deletions(-) create mode 100644 source3/libads/sasl.c (limited to 'source3/libads') diff --git a/source3/libads/ads_struct.c b/source3/libads/ads_struct.c index 15cbb328e8..72f2a32e64 100644 --- a/source3/libads/ads_struct.c +++ b/source3/libads/ads_struct.c @@ -22,7 +22,7 @@ #include "includes.h" -static char *ads_build_dn(const char *realm) +char *ads_build_dn(const char *realm) { char *p, *r; int numdots = 0; @@ -54,46 +54,6 @@ static char *ads_build_dn(const char *realm) return ret; } -#ifdef HAVE_KRB5 - -/* - get the default relm from krb5.conf -*/ -static char *get_default_realm(ADS_STRUCT *ads) -{ - BOOL ret; - krb5_context context; - char *realm; - - ret = krb5_init_context(&context); - if (ret) { - DEBUG(1,("krb5_init_context failed (%s)\n", error_message(ret))); - return NULL; - } - - ret = krb5_get_default_realm(context, &realm); - if (ret) { - DEBUG(1,("krb5_get_default_realm failed (%s)\n", error_message(ret))); - krb5_free_context(context); - return NULL; - } else { - DEBUG(5,("krb5_get_default_realm got (%s)\n", realm)); - } - krb5_free_context(context); - - return realm; -} - -#else -static char *get_default_realm(ADS_STRUCT *ads) -{ - /* We can't do this if we don't have krb5, - but save linking nightmares */ - DEBUG(5,("get_default_realm: not compiled with krb5.\n")); - return NULL; -} - -#endif #ifdef HAVE_LDAP /* @@ -151,11 +111,10 @@ ADS_STRUCT *ads_init(const char *realm, if (!ads->realm) { ads->realm = strdup(lp_realm()); if (!ads->realm[0]) { - ads->realm = get_default_realm(ads); + SAFE_FREE(ads->realm); } - if (!ads->realm) ads->realm = strdup(""); } - if (!ads->bind_path) { + if (!ads->bind_path && ads->realm) { ads->bind_path = ads_build_dn(ads->realm); } if (!ads->ldap_server) { @@ -183,9 +142,11 @@ void ads_destroy(ADS_STRUCT **ads) #endif SAFE_FREE((*ads)->realm); SAFE_FREE((*ads)->ldap_server); + SAFE_FREE((*ads)->ldap_server_name); SAFE_FREE((*ads)->kdc_server); SAFE_FREE((*ads)->bind_path); SAFE_FREE((*ads)->password); + SAFE_FREE((*ads)->user_name); ZERO_STRUCTP(*ads); SAFE_FREE(*ads); } diff --git a/source3/libads/kerberos.c b/source3/libads/kerberos.c index 19e8ffdc00..521fe0d5eb 100644 --- a/source3/libads/kerberos.c +++ b/source3/libads/kerberos.c @@ -85,19 +85,29 @@ int ads_kinit_password(ADS_STRUCT *ads) { char *s; int ret; - extern pstring global_myname; - fstring myname; + char *ccache; + + ccache = lock_path("winbindd_ccache"); /* we don't want this to affect the users ccache */ - setenv("KRB5CCNAME", lock_path("winbindd_ccache"), 1); + setenv("KRB5CCNAME", ccache, 1); - fstrcpy(myname, global_myname); - strlower(myname); - asprintf(&s, "HOST/%s@%s", global_myname, ads->realm); + unlink(ccache); + + if (!ads->user_name) { + /* by default use the machine account */ + extern pstring global_myname; + fstring myname; + fstrcpy(myname, global_myname); + strlower(myname); + asprintf(&ads->user_name, "HOST/%s", global_myname); + } + asprintf(&s, "%s@%s", ads->user_name, ads->realm); ret = kerberos_kinit_password(s, ads->password); free(s); if (ret) { - DEBUG(1,("kerberos_kinit_password failed: %s\n", error_message(ret))); + DEBUG(1,("kerberos_kinit_password %s failed: %s\n", + s, error_message(ret))); } return ret; } diff --git a/source3/libads/ldap.c b/source3/libads/ldap.c index a7c9265b18..b716966c1b 100644 --- a/source3/libads/ldap.c +++ b/source3/libads/ldap.c @@ -35,25 +35,6 @@ char *ads_errstr(int rc) return ldap_err2string(rc); } -/* - this is a minimal interact function, just enough for SASL to talk - GSSAPI/kerberos to W2K - Error handling is a bit of a problem. I can't see how to get Cyrus-sasl - to give sensible errors -*/ -static int sasl_interact(LDAP *ld,unsigned flags,void *defaults,void *in) -{ - sasl_interact_t *interact = in; - - while (interact->id != SASL_CB_LIST_END) { - interact->result = strdup(""); - interact->len = strlen(interact->result); - interact++; - } - - return LDAP_SUCCESS; -} - /* connect to the LDAP server */ @@ -68,67 +49,38 @@ int ads_connect(ADS_STRUCT *ads) if (!ads->ld) { return LDAP_SERVER_DOWN; } + if (!ads_server_info(ads)) { + DEBUG(1,("Failed to get ldap server info\n")); + return LDAP_SERVER_DOWN; + } + ldap_set_option(ads->ld, LDAP_OPT_PROTOCOL_VERSION, &version); if (ads->password) { - /* the machine acct password might have changed */ - free(ads->password); - ads->password = secrets_fetch_machine_password(); ads_kinit_password(ads); } - rc = ldap_sasl_interactive_bind_s(ads->ld, NULL, NULL, NULL, NULL, - LDAP_SASL_QUIET, - sasl_interact, NULL); - + rc = ads_sasl_bind(ads); return rc; } /* - a wrapper around ldap_search_s that retries depending on the error code - this is supposed to catch dropped connections and auto-reconnect + do a search with a timeout */ -int ads_search_retry(ADS_STRUCT *ads, const char *bind_path, int scope, const char *exp, - const char **attrs, void **res) +int ads_do_search(ADS_STRUCT *ads, const char *bind_path, int scope, const char *exp, + const char **attrs, void **res) { struct timeval timeout; - int rc = -1, rc2; - int count = 3; - - if (!ads->ld && - time(NULL) - ads->last_attempt < ADS_RECONNECT_TIME) { - return LDAP_SERVER_DOWN; - } - while (count--) { - *res = NULL; - timeout.tv_sec = ADS_SEARCH_TIMEOUT; - timeout.tv_usec = 0; - if (ads->ld) { - rc = ldap_search_ext_s(ads->ld, bind_path, scope, exp, attrs, 0, NULL, NULL, - &timeout, LDAP_NO_LIMIT, (LDAPMessage **)res); - if (rc == 0) return rc; - } + timeout.tv_sec = ADS_SEARCH_TIMEOUT; + timeout.tv_usec = 0; + *res = NULL; - if (*res) ads_msgfree(ads, *res); - *res = NULL; - DEBUG(1,("Reopening ads connection after error %s\n", ads_errstr(rc))); - if (ads->ld) { - /* we should unbind here, but that seems to trigger openldap bugs :( - ldap_unbind(ads->ld); - */ - } - ads->ld = NULL; - rc2 = ads_connect(ads); - if (rc2) { - DEBUG(1,("ads_search_retry: failed to reconnect (%s)\n", ads_errstr(rc))); - return rc2; - } - } - DEBUG(1,("ads reopen failed after error %s\n", ads_errstr(rc))); - return rc; + return ldap_search_ext_s(ads->ld, + bind_path, scope, + exp, attrs, 0, NULL, NULL, + &timeout, LDAP_NO_LIMIT, (LDAPMessage **)res); } - /* do a general ADS search */ @@ -136,7 +88,8 @@ int ads_search(ADS_STRUCT *ads, void **res, const char *exp, const char **attrs) { - return ads_search_retry(ads, ads->bind_path, LDAP_SCOPE_SUBTREE, exp, attrs, res); + return ads_do_search(ads, ads->bind_path, LDAP_SCOPE_SUBTREE, + exp, attrs, res); } /* @@ -146,7 +99,7 @@ int ads_search_dn(ADS_STRUCT *ads, void **res, const char *dn, const char **attrs) { - return ads_search_retry(ads, dn, LDAP_SCOPE_BASE, "(objectclass=*)", attrs, res); + return ads_do_search(ads, dn, LDAP_SCOPE_BASE, "(objectclass=*)", attrs, res); } /* @@ -586,7 +539,7 @@ BOOL ads_USN(ADS_STRUCT *ads, uint32 *usn) void *res; BOOL ret; - rc = ads_search_retry(ads, "", LDAP_SCOPE_BASE, "(objectclass=*)", attrs, &res); + rc = ads_do_search(ads, "", LDAP_SCOPE_BASE, "(objectclass=*)", attrs, &res); if (rc || ads_count_replies(ads, res) != 1) return False; ret = ads_pull_uint32(ads, res, "highestCommittedUSN", usn); ads_msgfree(ads, res); @@ -594,5 +547,56 @@ BOOL ads_USN(ADS_STRUCT *ads, uint32 *usn) } +/* find the servers name and realm - this can be done before authentication + The ldapServiceName field on w2k looks like this: + vnet3.home.samba.org:win2000-vnet3$@VNET3.HOME.SAMBA.ORG +*/ +BOOL ads_server_info(ADS_STRUCT *ads) +{ + const char *attrs[] = {"ldapServiceName", NULL}; + int rc; + void *res; + char **values; + char *ret, *p; + + rc = ads_do_search(ads, "", LDAP_SCOPE_BASE, "(objectclass=*)", attrs, &res); + if (rc || ads_count_replies(ads, res) != 1) return False; + + values = ldap_get_values(ads->ld, res, "ldapServiceName"); + + if (!values || !values[0]) return False; + + p = strchr(values[0], ':'); + if (!p) { + ldap_value_free(values); + ldap_msgfree(res); + return False; + } + + SAFE_FREE(ads->ldap_server_name); + + ads->ldap_server_name = strdup(p+1); + p = strchr(ads->ldap_server_name, '$'); + if (!p || p[1] != '@') { + ldap_value_free(values); + ldap_msgfree(res); + SAFE_FREE(ads->ldap_server_name); + return False; + } + + *p = 0; + + /* in case the realm isn't configured in smb.conf */ + if (!ads->realm || !ads->realm[0]) { + SAFE_FREE(ads->realm); + SAFE_FREE(ads->bind_path); + ads->realm = strdup(p+2); + ads->bind_path = ads_build_dn(ads->realm); + } + + DEBUG(3,("got ldap server name %s@%s\n", ret, ads->realm)); + + return True; +} #endif diff --git a/source3/libads/sasl.c b/source3/libads/sasl.c new file mode 100644 index 0000000000..dd948b5d4d --- /dev/null +++ b/source3/libads/sasl.c @@ -0,0 +1,186 @@ +/* + Unix SMB/Netbios implementation. + Version 3.0 + ads sasl code + Copyright (C) Andrew Tridgell 2001 + + 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; either version 2 of the License, or + (at your option) any later version. + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "includes.h" + +#ifdef HAVE_ADS + +#if USE_CYRUS_SASL +/* + this is a minimal interact function, just enough for SASL to talk + GSSAPI/kerberos to W2K + Error handling is a bit of a problem. I can't see how to get Cyrus-sasl + to give sensible errors +*/ +static int sasl_interact(LDAP *ld,unsigned flags,void *defaults,void *in) +{ + sasl_interact_t *interact = in; + + while (interact->id != SASL_CB_LIST_END) { + interact->result = strdup(""); + interact->len = strlen(interact->result); + interact++; + } + + return LDAP_SUCCESS; +} +#endif + + +#define MAX_GSS_PASSES 3 + +/* this performs a SASL/gssapi bind + we avoid using cyrus-sasl to make Samba more robust. cyrus-sasl + is very dependent on correctly configured DNS whereas + this routine is much less fragile + see RFC2078 for details +*/ +int ads_sasl_gssapi_bind(ADS_STRUCT *ads) +{ + int rc, minor_status; + gss_name_t serv_name; + gss_buffer_desc input_name; + gss_ctx_id_t context_handle; + gss_OID mech_type = GSS_C_NULL_OID; + gss_buffer_desc output_token, input_token; + OM_uint32 ret_flags, conf_state; + struct berval cred; + struct berval *scred; + int i=0; + int gss_rc; + uint8 *p; + uint32 max_msg_size; + char *sname; + + asprintf(&sname, "ldap@%s.%s", ads->ldap_server_name, ads->realm); + + input_name.value = sname; + input_name.length = strlen(input_name.value); + + rc = gss_import_name(&minor_status,&input_name,gss_nt_service_name, &serv_name); + + free(sname); + + context_handle = GSS_C_NO_CONTEXT; + + input_token.value = NULL; + input_token.length = 0; + + for (i=0; i < MAX_GSS_PASSES; i++) { + gss_rc = gss_init_sec_context(&minor_status, + GSS_C_NO_CREDENTIAL, + &context_handle, + serv_name, + mech_type, + GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG, + 0, + NULL, + &input_token, + NULL, + &output_token, + &ret_flags, + NULL); + + if (input_token.value) { + gss_release_buffer(&minor_status, &input_token); + } + + if (gss_rc && gss_rc != GSS_S_CONTINUE_NEEDED) goto failed; + + cred.bv_val = output_token.value; + cred.bv_len = output_token.length; + + rc = ldap_sasl_bind_s(ads->ld, NULL, "GSSAPI", &cred, NULL, NULL, + &scred); + + if (output_token.value) { + gss_release_buffer(&minor_status, &output_token); + } + + if (scred) { + input_token.value = scred->bv_val; + input_token.length = scred->bv_len; + } else { + input_token.value = NULL; + input_token.length = 0; + } + + if (gss_rc != GSS_S_CONTINUE_NEEDED) break; + } + + gss_release_name(&minor_status, &serv_name); + + gss_rc = gss_unwrap(&minor_status,context_handle,&input_token,&output_token, + &conf_state,NULL); + + gss_release_buffer(&minor_status, &input_token); + + p = (uint8 *)output_token.value; + + max_msg_size = (p[1]<<16) | (p[2]<<8) | p[3]; + + gss_release_buffer(&minor_status, &output_token); + + output_token.value = malloc(strlen(ads->bind_path) + 8); + p = output_token.value; + + *p++ = 1; /* no sign or seal */ + /* choose the same size as the server gave us */ + *p++ = max_msg_size>>16; + *p++ = max_msg_size>>8; + *p++ = max_msg_size; + snprintf(p, strlen(ads->bind_path)+1, "dn:%s", ads->bind_path); + p += strlen(ads->bind_path); + + output_token.length = strlen(ads->bind_path) + 8; + + gss_rc = gss_wrap(&minor_status, context_handle,0,GSS_C_QOP_DEFAULT, + &output_token, &conf_state, + &input_token); + + free(output_token.value); + + cred.bv_val = input_token.value; + cred.bv_len = input_token.length; + + rc = ldap_sasl_bind_s(ads->ld, NULL, "GSSAPI", &cred, NULL, NULL, + &scred); + + gss_release_buffer(&minor_status, &input_token); + return rc; + +failed: + return gss_rc; +} + +int ads_sasl_bind(ADS_STRUCT *ads) +{ +#if USE_CYRUS_SASL + return ldap_sasl_interactive_bind_s(ads->ld, NULL, NULL, NULL, NULL, + LDAP_SASL_QUIET, + sasl_interact, NULL); +#else + return ads_sasl_gssapi_bind(ads); +#endif +} + +#endif + -- cgit