convert the LDAP/SASL code to use GSS-SPNEGO if possible
authorAndrew Tridgell <tridge@samba.org>
Fri, 30 Aug 2002 06:59:57 +0000 (06:59 +0000)
committerAndrew Tridgell <tridge@samba.org>
Fri, 30 Aug 2002 06:59:57 +0000 (06:59 +0000)
we now do this:

- look for suported SASL mechanisms on the LDAP server
- choose GSS-SPNEGO if possible
- within GSS-SPNEGO choose KRB5 if we can do a kinit
- otherwise use NTLMSSP

This change also means that we no longer rely on having a gssapi
library to do ADS.

todo:
- add TLS/SSL support over LDAP
- change to using LDAP/SSL for password change in ADS
(This used to be commit b04e91f660d3b26d23044075d4a7e707eb41462d)

source3/include/includes.h
source3/libads/ldap.c
source3/libads/sasl.c
source3/libsmb/cliconnect.c
source3/libsmb/clikrb5.c
source3/libsmb/clispnego.c
source3/utils/net_ads.c

index f1c8c50df432962d4d36317983035dbb4bf95d4f..544487f273d0421e4ef95c11f6f56e55a3481527 100644 (file)
 
 #if HAVE_GSSAPI_GSSAPI_H
 #include <gssapi/gssapi.h>
-#else
-#undef HAVE_KRB5
 #endif
 
 #if HAVE_GSSAPI_GSSAPI_GENERIC_H
 #include <gssapi/gssapi_generic.h>
-#else
-#undef HAVE_KRB5
 #endif
 
 /* we support ADS if we have krb5 and ldap libs */
-#if defined(HAVE_KRB5) && defined(HAVE_LDAP) && defined(HAVE_GSSAPI)
+#if defined(HAVE_KRB5) && defined(HAVE_LDAP)
 #define HAVE_ADS
 #endif
 
index f0c4ad9040cbec286bb3e2cbe5ef9ff4b745d16b..2cfbedc6d4ea9ec007f2dc3bf0fca8027772fdc8 100644 (file)
@@ -226,7 +226,7 @@ ADS_STATUS ads_connect(ADS_STRUCT *ads)
        /* try via DNS */
        if (ads_try_dns(ads)) {
                goto got_connection;
-               }
+       }
 
        /* try via netbios lookups */
        if (!lp_disable_netbios() && ads_try_netbios(ads)) {
@@ -274,11 +274,6 @@ got_connection:
        }
 #endif
 
-       if (ads->auth.password) {
-               if ((code = ads_kinit_password(ads)))
-                       return ADS_ERROR_KRB5(code);
-       }
-
        if (ads->auth.no_bind) {
                return ADS_SUCCESS;
        }
index 81dedb0a81e7c257c2fd27bef5fcad5157c6c634..12a5722319feff66b9f9dd321679ae6c902bfc07 100644 (file)
 
 #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
+/* 
+   perform a LDAP/SASL/SPNEGO/NTLMSSP bind (just how many layers can
+   we fit on one socket??)
 */
-static int sasl_interact(LDAP *ld,unsigned flags,void *defaults,void *in)
+static ADS_STATUS ads_sasl_spnego_ntlmssp_bind(ADS_STRUCT *ads)
 {
-       sasl_interact_t *interact = in;
+       const char *mechs[] = {OID_NTLMSSP, NULL};
+       DATA_BLOB msg1;
+       DATA_BLOB blob, chal1, chal2, auth;
+       uint8 challenge[8];
+       uint8 nthash[24], lmhash[24], sess_key[16];
+       uint32 neg_flags;
+       struct berval cred, *scred;
+       ADS_STATUS status;
+       extern pstring global_myname;
+       int rc;
+
+       neg_flags = NTLMSSP_NEGOTIATE_UNICODE | 
+               NTLMSSP_NEGOTIATE_128 | 
+               NTLMSSP_NEGOTIATE_NTLM;
+
+       memset(sess_key, 0, 16);
 
-       while (interact->id != SASL_CB_LIST_END) {
-               interact->result = strdup("");
-               interact->len = strlen(interact->result);
-               interact++;
+       /* generate the ntlmssp negotiate packet */
+       msrpc_gen(&blob, "CddB",
+                 "NTLMSSP",
+                 NTLMSSP_NEGOTIATE,
+                 neg_flags,
+                 sess_key, 16);
+
+       /* and wrap it in a SPNEGO wrapper */
+       msg1 = gen_negTokenTarg(mechs, blob);
+       data_blob_free(&blob);
+
+       cred.bv_val = msg1.data;
+       cred.bv_len = msg1.length;
+
+       rc = ldap_sasl_bind_s(ads->ld, NULL, "GSS-SPNEGO", &cred, NULL, NULL, &scred);
+       if (rc != LDAP_SASL_BIND_IN_PROGRESS) {
+               status = ADS_ERROR(rc);
+               goto failed;
        }
-       
-       return LDAP_SUCCESS;
+
+       blob = data_blob(scred->bv_val, scred->bv_len);
+
+       /* the server gives us back two challenges */
+       if (!spnego_parse_challenge(blob, &chal1, &chal2)) {
+               DEBUG(3,("Failed to parse challenges\n"));
+               status = ADS_ERROR(LDAP_OPERATIONS_ERROR);
+               goto failed;
+       }
+
+       data_blob_free(&blob);
+
+       /* encrypt the password with the challenge */
+       memcpy(challenge, chal1.data + 24, 8);
+       SMBencrypt(ads->auth.password, challenge,lmhash);
+       SMBNTencrypt(ads->auth.password, challenge,nthash);
+
+       data_blob_free(&chal1);
+       data_blob_free(&chal2);
+
+       /* this generates the actual auth packet */
+       msrpc_gen(&blob, "CdBBUUUBd", 
+                 "NTLMSSP", 
+                 NTLMSSP_AUTH, 
+                 lmhash, 24,
+                 nthash, 24,
+                 lp_workgroup(), 
+                 ads->auth.user_name, 
+                 global_myname,
+                 sess_key, 16,
+                 neg_flags);
+
+       /* wrap it in SPNEGO */
+       auth = spnego_gen_auth(blob);
+
+       data_blob_free(&blob);
+
+       /* now send the auth packet and we should be done */
+       cred.bv_val = auth.data;
+       cred.bv_len = auth.length;
+
+       rc = ldap_sasl_bind_s(ads->ld, NULL, "GSS-SPNEGO", &cred, NULL, NULL, &scred);
+
+       return ADS_ERROR(rc);
+
+failed:
+       return status;
+}
+
+/* 
+   perform a LDAP/SASL/SPNEGO/KRB5 bind
+*/
+static ADS_STATUS ads_sasl_spnego_krb5_bind(ADS_STRUCT *ads, const char *principal)
+{
+       DATA_BLOB blob;
+       struct berval cred, *scred;
+       int rc;
+
+       blob = spnego_gen_negTokenTarg(principal);
+
+       if (!blob.data) {
+               return ADS_ERROR(LDAP_OPERATIONS_ERROR);
+       }
+
+       /* now send the auth packet and we should be done */
+       cred.bv_val = blob.data;
+       cred.bv_len = blob.length;
+
+       rc = ldap_sasl_bind_s(ads->ld, NULL, "GSS-SPNEGO", &cred, NULL, NULL, &scred);
+
+       data_blob_free(&blob);
+
+       return ADS_ERROR(rc);
 }
+
+/* 
+   this performs a SASL/SPNEGO bind
+*/
+static ADS_STATUS ads_sasl_spnego_bind(ADS_STRUCT *ads)
+{
+       struct berval *scred;
+       int rc, i;
+       ADS_STATUS status;
+       DATA_BLOB blob;
+       char *principal;
+       char *OIDs[ASN1_MAX_OIDS];
+       BOOL got_kerberos_mechanism = False;
+
+       rc = ldap_sasl_bind_s(ads->ld, NULL, "GSS-SPNEGO", NULL, NULL, NULL, &scred);
+
+       if (rc != LDAP_SASL_BIND_IN_PROGRESS) {
+               status = ADS_ERROR(rc);
+               goto failed;
+       }
+
+       blob = data_blob(scred->bv_val, scred->bv_len);
+
+#if 0
+       file_save("sasl_spnego.dat", blob.data, blob.length);
 #endif
 
+       /* the server sent us the first part of the SPNEGO exchange in the negprot 
+          reply */
+       if (!spnego_parse_negTokenInit(blob, OIDs, &principal)) {
+               data_blob_free(&blob);
+               status = ADS_ERROR(LDAP_OPERATIONS_ERROR);
+               goto failed;
+       }
+       data_blob_free(&blob);
+
+       /* make sure the server understands kerberos */
+       for (i=0;OIDs[i];i++) {
+               DEBUG(3,("got OID=%s\n", OIDs[i]));
+               if (strcmp(OIDs[i], OID_KERBEROS5_OLD) == 0 ||
+                   strcmp(OIDs[i], OID_KERBEROS5) == 0) {
+                       got_kerberos_mechanism = True;
+               }
+               free(OIDs[i]);
+       }
+       DEBUG(3,("got principal=%s\n", principal));
 
+       if (got_kerberos_mechanism && ads_kinit_password(ads) == 0) {
+               return ads_sasl_spnego_krb5_bind(ads, principal);
+       }
+
+       /* lets do NTLMSSP ... this has the big advantage that we don't need
+          to sync clocks, and we don't rely on special versions of the krb5 
+          library for HMAC_MD4 encryption */
+       return ads_sasl_spnego_ntlmssp_bind(ads);
+
+failed:
+       return status;
+}
+
+#ifdef HAVE_GSSAPI
 #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
+   see RFC2078 and RFC2222 for details
 */
-ADS_STATUS ads_sasl_gssapi_bind(ADS_STRUCT *ads)
+static ADS_STATUS ads_sasl_gssapi_bind(ADS_STRUCT *ads)
 {
        int minor_status;
        gss_name_t serv_name;
@@ -68,6 +223,7 @@ ADS_STATUS ads_sasl_gssapi_bind(ADS_STRUCT *ads)
        uint8 *p;
        uint32 max_msg_size;
        char *sname;
+       unsigned sec_layer;
        ADS_STATUS status;
        krb5_principal principal;
        krb5_context ctx;
@@ -159,22 +315,25 @@ ADS_STATUS ads_sasl_gssapi_bind(ADS_STRUCT *ads)
 
        p = (uint8 *)output_token.value;
 
+       file_save("sasl_gssapi.dat", output_token.value, output_token.length);
+
        max_msg_size = (p[1]<<16) | (p[2]<<8) | p[3];
+       sec_layer = *p;
 
        gss_release_buffer(&minor_status, &output_token);
 
        output_token.value = malloc(strlen(ads->config.bind_path) + 8);
        p = output_token.value;
 
-       *p++ = 1; /* no sign or seal */
+       *p++ = 1; /* no sign & seal selection */
        /* 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->config.bind_path)+4, "dn:%s", ads->config.bind_path);
-       p += strlen(ads->config.bind_path);
+       p += strlen(p);
 
-       output_token.length = strlen(ads->config.bind_path) + 8;
+       output_token.length = PTR_DIFF(p, output_token.value);
 
        gss_rc = gss_wrap(&minor_status, context_handle,0,GSS_C_QOP_DEFAULT,
                          &output_token, &conf_state,
@@ -198,18 +357,51 @@ ADS_STATUS ads_sasl_gssapi_bind(ADS_STRUCT *ads)
 failed:
        return status;
 }
+#endif
+
+/* mapping between SASL mechanisms and functions */
+static struct {
+       const char *name;
+       ADS_STATUS (*fn)(ADS_STRUCT *);
+} sasl_mechanisms[] = {
+       {"GSS-SPNEGO", ads_sasl_spnego_bind},
+#ifdef HAVE_GSSAPI
+       {"GSSAPI", ads_sasl_gssapi_bind}, /* doesn't work with .NET RC1. No idea why */
+#endif
+       {NULL, NULL}
+};
 
 ADS_STATUS ads_sasl_bind(ADS_STRUCT *ads)
 {
-#if USE_CYRUS_SASL
-       int rc;
-       rc = ldap_sasl_interactive_bind_s(ads->ld, NULL, NULL, NULL, NULL, 
-                                         LDAP_SASL_QUIET,
-                                         sasl_interact, NULL);
-       return ADS_ERROR(rc);
-#else
-       return ads_sasl_gssapi_bind(ads);
-#endif
+       const char *attrs[] = {"supportedSASLMechanisms", NULL};
+       char **values;
+       ADS_STATUS status;
+       int i, j;
+       void *res;
+
+       /* get a list of supported SASL mechanisms */
+       status = ads_do_search(ads, "", LDAP_SCOPE_BASE, "(objectclass=*)", attrs, &res);
+       if (!ADS_ERR_OK(status)) return status;
+
+       values = ldap_get_values(ads->ld, res, "supportedSASLMechanisms");
+
+       /* try our supported mechanisms in order */
+       for (i=0;sasl_mechanisms[i].name;i++) {
+               /* see if the server supports it */
+               for (j=0;values && values[j];j++) {
+                       if (strcmp(values[j], sasl_mechanisms[i].name) == 0) {
+                               DEBUG(4,("Found SASL mechanism %s\n", values[j]));
+                               status = sasl_mechanisms[i].fn(ads);
+                               ldap_value_free(values);
+                               ldap_msgfree(res);
+                               return status;
+                       }
+               }
+       }
+
+       ldap_value_free(values);
+       ldap_msgfree(res);
+       return ADS_ERROR(LDAP_AUTH_METHOD_NOT_SUPPORTED);
 }
 
 #endif
index 0d033c9b596a8f8326ca42f378ddd789ea8afb4d..e9b2b7b32ef489714af319b5cd88b09fc1ec3aa7 100644 (file)
@@ -446,7 +446,7 @@ static BOOL cli_session_setup_kerberos(struct cli_state *cli, char *principal, c
        DEBUG(2,("Doing kerberos session setup\n"));
 
        /* generate the encapsulated kerberos5 ticket */
-       negTokenTarg = spnego_gen_negTokenTarg(cli, principal);
+       negTokenTarg = spnego_gen_negTokenTarg(principal);
 
        if (!negTokenTarg.data) return False;
 
@@ -572,14 +572,14 @@ static BOOL cli_session_setup_spnego(struct cli_state *cli, char *user,
 {
        char *principal;
        char *OIDs[ASN1_MAX_OIDS];
-       uint8 guid[16];
        int i;
        BOOL got_kerberos_mechanism = False;
+       DATA_BLOB blob;
 
        DEBUG(2,("Doing spnego session setup (blob length=%d)\n", cli->secblob.length));
 
        /* the server might not even do spnego */
-       if (cli->secblob.length == 16) {
+       if (cli->secblob.length <= 16) {
                DEBUG(3,("server didn't supply a full spnego negprot\n"));
                goto ntlmssp;
        }
@@ -588,11 +588,16 @@ static BOOL cli_session_setup_spnego(struct cli_state *cli, char *user,
        file_save("negprot.dat", cli->secblob.data, cli->secblob.length);
 #endif
 
+       /* there is 16 bytes of GUID before the real spnego packet starts */
+       blob = data_blob(cli->secblob.data+16, cli->secblob.length-16);
+
        /* the server sent us the first part of the SPNEGO exchange in the negprot 
           reply */
-       if (!spnego_parse_negTokenInit(cli->secblob, guid, OIDs, &principal)) {
+       if (!spnego_parse_negTokenInit(blob, OIDs, &principal)) {
+               data_blob_free(&blob);
                return False;
        }
+       data_blob_free(&blob);
 
        /* make sure the server understands kerberos */
        for (i=0;OIDs[i];i++) {
index 685c4a25e0400a30b13b4b3fe7c172da120aad4b..955a93285c771d93513b0e1aa9f59f77d289bce2 100644 (file)
 
 #include "includes.h"
 
+#ifndef ENCTYPE_ARCFOUR_HMAC
+#define ENCTYPE_ARCFOUR_HMAC   0x0017
+#endif
+
 #ifdef HAVE_KRB5
 /*
   we can't use krb5_mk_req because w2k wants the service to be in a particular format
@@ -94,7 +98,9 @@ DATA_BLOB krb5_get_ticket(char *principal)
        krb5_context context;
        krb5_auth_context auth_context = NULL;
        DATA_BLOB ret;
-       krb5_enctype enc_types[] = {ENCTYPE_DES_CBC_MD5, ENCTYPE_NULL};
+       krb5_enctype enc_types[] = {ENCTYPE_ARCFOUR_HMAC, 
+                                   ENCTYPE_DES_CBC_MD5, 
+                                   ENCTYPE_NULL};
 
        retval = krb5_init_context(&context);
        if (retval) {
index bc4d0ca348ab9eb077d023b65b4899904da6332a..1eeae8b1717699dd9d1d16a758bd10dc36a58ef8 100644 (file)
@@ -79,7 +79,6 @@ DATA_BLOB spnego_gen_negTokenInit(uint8 guid[16],
   OIDs (the mechanisms) and a principal name string 
 */
 BOOL spnego_parse_negTokenInit(DATA_BLOB blob,
-                              uint8 guid[16], 
                               char *OIDs[ASN1_MAX_OIDS], 
                               char **principal)
 {
@@ -89,7 +88,6 @@ BOOL spnego_parse_negTokenInit(DATA_BLOB blob,
 
        asn1_load(&data, blob);
 
-       asn1_read(&data, guid, 16);
        asn1_start_tag(&data,ASN1_APPLICATION(0));
        asn1_check_OID(&data,OID_SPNEGO);
        asn1_start_tag(&data,ASN1_CONTEXT(0));
@@ -279,7 +277,7 @@ BOOL spnego_parse_krb5_wrap(DATA_BLOB blob, DATA_BLOB *ticket)
    generate a SPNEGO negTokenTarg packet, ready for a EXTENDED_SECURITY
    kerberos session setup 
 */
-DATA_BLOB spnego_gen_negTokenTarg(struct cli_state *cli, char *principal)
+DATA_BLOB spnego_gen_negTokenTarg(const char *principal)
 {
        DATA_BLOB tkt, tkt_wrapped, targ;
        const char *krb_mechs[] = {OID_KERBEROS5_OLD, OID_NTLMSSP, NULL};
index 16450c5b2994c30bb6ed6946c420a8dd3eb2cbc3..eb1c3fe0592252867de5803f38dd2fe213016068 100644 (file)
@@ -653,6 +653,10 @@ int net_ads_join(int argc, const char **argv)
                return -1;
        }
 
+       if (ads_kinit_password(ads)) {
+               return -1;
+       }
+
        rc = ads_set_machine_password(ads, global_myname, password);
        if (!ADS_ERR_OK(rc)) {
                d_printf("ads_set_machine_password: %s\n", ads_errstr(rc));