pidl/python: Allow passing in UTF8 string objects as well as unicode
[sfrench/samba-autobuild/.git] / source3 / libads / sasl.c
index a73545f8e59384aeb65d5ecf9f47cdab46b12cca..051fc961d9bc50a31a4989eb43e0fe94b120b46e 100644 (file)
 */
 
 #include "includes.h"
+#include "../libcli/auth/spnego.h"
+#include "../libcli/auth/ntlmssp.h"
+#include "ads.h"
+#include "smb_krb5.h"
 
 #ifdef HAVE_LDAP
 
+static ADS_STATUS ads_sasl_ntlmssp_wrap(ADS_STRUCT *ads, uint8 *buf, uint32 len)
+{
+       struct ntlmssp_state *ntlmssp_state =
+               (struct ntlmssp_state *)ads->ldap.wrap_private_data;
+       ADS_STATUS status;
+       NTSTATUS nt_status;
+       DATA_BLOB sig;
+       TALLOC_CTX *frame;
+       uint8 *dptr = ads->ldap.out.buf + (4 + NTLMSSP_SIG_SIZE);
+
+       frame = talloc_stackframe();
+       /* copy the data to the right location */
+       memcpy(dptr, buf, len);
+
+       /* create the signature and may encrypt the data */
+       if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_SEAL) {
+               nt_status = ntlmssp_seal_packet(ntlmssp_state,
+                                               frame,
+                                               dptr, len,
+                                               dptr, len,
+                                               &sig);
+       } else {
+               nt_status = ntlmssp_sign_packet(ntlmssp_state,
+                                               frame,
+                                               dptr, len,
+                                               dptr, len,
+                                               &sig);
+       }
+       status = ADS_ERROR_NT(nt_status);
+       if (!ADS_ERR_OK(status)) return status;
+
+       /* copy the signature to the right location */
+       memcpy(ads->ldap.out.buf + 4,
+              sig.data, NTLMSSP_SIG_SIZE);
+
+       TALLOC_FREE(frame);
+
+       /* set how many bytes must be written to the underlying socket */
+       ads->ldap.out.left = 4 + NTLMSSP_SIG_SIZE + len;
+
+       return ADS_SUCCESS;
+}
+
+static ADS_STATUS ads_sasl_ntlmssp_unwrap(ADS_STRUCT *ads)
+{
+       struct ntlmssp_state *ntlmssp_state =
+               (struct ntlmssp_state *)ads->ldap.wrap_private_data;
+       ADS_STATUS status;
+       NTSTATUS nt_status;
+       DATA_BLOB sig;
+       uint8 *dptr = ads->ldap.in.buf + (4 + NTLMSSP_SIG_SIZE);
+       uint32 dlen = ads->ldap.in.ofs - (4 + NTLMSSP_SIG_SIZE);
+
+       /* wrap the signature into a DATA_BLOB */
+       sig = data_blob_const(ads->ldap.in.buf + 4, NTLMSSP_SIG_SIZE);
+
+       /* verify the signature and maybe decrypt the data */
+       if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_SEAL) {
+               nt_status = ntlmssp_unseal_packet(ntlmssp_state,
+                                                 dptr, dlen,
+                                                 dptr, dlen,
+                                                 &sig);
+       } else {
+               nt_status = ntlmssp_check_packet(ntlmssp_state,
+                                                dptr, dlen,
+                                                dptr, dlen,
+                                                &sig);
+       }
+       status = ADS_ERROR_NT(nt_status);
+       if (!ADS_ERR_OK(status)) return status;
+
+       /* set the amount of bytes for the upper layer and set the ofs to the data */
+       ads->ldap.in.left       = dlen;
+       ads->ldap.in.ofs        = 4 + NTLMSSP_SIG_SIZE;
+
+       return ADS_SUCCESS;
+}
+
+static void ads_sasl_ntlmssp_disconnect(ADS_STRUCT *ads)
+{
+       struct ntlmssp_state *ntlmssp_state =
+               (struct ntlmssp_state *)ads->ldap.wrap_private_data;
+
+       TALLOC_FREE(ntlmssp_state);
+
+       ads->ldap.wrap_ops = NULL;
+       ads->ldap.wrap_private_data = NULL;
+}
+
+static const struct ads_saslwrap_ops ads_sasl_ntlmssp_ops = {
+       .name           = "ntlmssp",
+       .wrap           = ads_sasl_ntlmssp_wrap,
+       .unwrap         = ads_sasl_ntlmssp_unwrap,
+       .disconnect     = ads_sasl_ntlmssp_disconnect
+};
+
 /* 
    perform a LDAP/SASL/SPNEGO/NTLMSSP bind (just how many layers can
    we fit on one socket??)
@@ -34,11 +134,18 @@ static ADS_STATUS ads_sasl_spnego_ntlmssp_bind(ADS_STRUCT *ads)
        struct berval cred, *scred = NULL;
        int rc;
        NTSTATUS nt_status;
+       ADS_STATUS status;
        int turn = 1;
+       uint32 features = 0;
 
        struct ntlmssp_state *ntlmssp_state;
 
-       if (!NT_STATUS_IS_OK(nt_status = ntlmssp_client_start(&ntlmssp_state))) {
+       nt_status = ntlmssp_client_start(NULL,
+                                        global_myname(),
+                                        lp_workgroup(),
+                                        lp_client_ntlmv2_auth(),
+                                        &ntlmssp_state);
+       if (!NT_STATUS_IS_OK(nt_status)) {
                return ADS_ERROR_NT(nt_status);
        }
        ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_SIGN;
@@ -53,6 +160,28 @@ static ADS_STATUS ads_sasl_spnego_ntlmssp_bind(ADS_STRUCT *ads)
                return ADS_ERROR_NT(nt_status);
        }
 
+       switch (ads->ldap.wrap_type) {
+       case ADS_SASLWRAP_TYPE_SEAL:
+               features = NTLMSSP_FEATURE_SIGN | NTLMSSP_FEATURE_SEAL;
+               break;
+       case ADS_SASLWRAP_TYPE_SIGN:
+               if (ads->auth.flags & ADS_AUTH_SASL_FORCE) {
+                       features = NTLMSSP_FEATURE_SIGN;
+               } else {
+                       /*
+                        * windows servers are broken with sign only,
+                        * so we need to use seal here too
+                        */
+                       features = NTLMSSP_FEATURE_SIGN | NTLMSSP_FEATURE_SEAL;
+                       ads->ldap.wrap_type = ADS_SASLWRAP_TYPE_SEAL;
+               }
+               break;
+       case ADS_SASLWRAP_TYPE_PLAIN:
+               break;
+       }
+
+       ntlmssp_want_feature(ntlmssp_state, features);
+
        blob_in = data_blob_null;
 
        do {
@@ -63,11 +192,13 @@ static ADS_STATUS ads_sasl_spnego_ntlmssp_bind(ADS_STRUCT *ads)
                     || NT_STATUS_IS_OK(nt_status))
                    && blob_out.length) {
                        if (turn == 1) {
+                               const char *OIDs_ntlm[] = {OID_NTLMSSP, NULL};
                                /* and wrap it in a SPNEGO wrapper */
-                               msg1 = gen_negTokenInit(OID_NTLMSSP, blob_out);
+                               msg1 = spnego_gen_negTokenInit(talloc_tos(),
+                                               OIDs_ntlm, &blob_out, NULL);
                        } else {
                                /* wrap it in SPNEGO */
-                               msg1 = spnego_gen_auth(blob_out);
+                               msg1 = spnego_gen_auth(talloc_tos(), blob_out);
                        }
 
                        data_blob_free(&blob_out);
@@ -82,7 +213,7 @@ static ADS_STATUS ads_sasl_spnego_ntlmssp_bind(ADS_STRUCT *ads)
                                        ber_bvfree(scred);
                                }
 
-                               ntlmssp_end(&ntlmssp_state);
+                               TALLOC_FREE(ntlmssp_state);
                                return ADS_ERROR(rc);
                        }
                        if (scred) {
@@ -94,7 +225,7 @@ static ADS_STATUS ads_sasl_spnego_ntlmssp_bind(ADS_STRUCT *ads)
 
                } else {
 
-                       ntlmssp_end(&ntlmssp_state);
+                       TALLOC_FREE(ntlmssp_state);
                        data_blob_free(&blob_out);
                        return ADS_ERROR_NT(nt_status);
                }
@@ -103,20 +234,20 @@ static ADS_STATUS ads_sasl_spnego_ntlmssp_bind(ADS_STRUCT *ads)
                    (rc == LDAP_SASL_BIND_IN_PROGRESS)) {
                        DATA_BLOB tmp_blob = data_blob_null;
                        /* the server might give us back two challenges */
-                       if (!spnego_parse_challenge(blob, &blob_in, 
+                       if (!spnego_parse_challenge(talloc_tos(), blob, &blob_in, 
                                                    &tmp_blob)) {
 
-                               ntlmssp_end(&ntlmssp_state);
+                               TALLOC_FREE(ntlmssp_state);
                                data_blob_free(&blob);
                                DEBUG(3,("Failed to parse challenges\n"));
                                return ADS_ERROR_NT(NT_STATUS_INVALID_PARAMETER);
                        }
                        data_blob_free(&tmp_blob);
                } else if (rc == LDAP_SASL_BIND_IN_PROGRESS) {
-                       if (!spnego_parse_auth_response(blob, nt_status, OID_NTLMSSP, 
+                       if (!spnego_parse_auth_response(talloc_tos(), blob, nt_status, OID_NTLMSSP, 
                                                        &blob_in)) {
 
-                               ntlmssp_end(&ntlmssp_state);
+                               TALLOC_FREE(ntlmssp_state);
                                data_blob_free(&blob);
                                DEBUG(3,("Failed to parse auth response\n"));
                                return ADS_ERROR_NT(NT_STATUS_INVALID_PARAMETER);
@@ -130,23 +261,456 @@ static ADS_STATUS ads_sasl_spnego_ntlmssp_bind(ADS_STRUCT *ads)
        /* we have a reference conter on ntlmssp_state, if we are signing
           then the state will be kept by the signing engine */
 
-       ntlmssp_end(&ntlmssp_state);
+       if (ads->ldap.wrap_type > ADS_SASLWRAP_TYPE_PLAIN) {
+               ads->ldap.out.max_unwrapped = ADS_SASL_WRAPPING_OUT_MAX_WRAPPED - NTLMSSP_SIG_SIZE;
+               ads->ldap.out.sig_size = NTLMSSP_SIG_SIZE;
+               ads->ldap.in.min_wrapped = ads->ldap.out.sig_size;
+               ads->ldap.in.max_wrapped = ADS_SASL_WRAPPING_IN_MAX_WRAPPED;
+               status = ads_setup_sasl_wrapping(ads, &ads_sasl_ntlmssp_ops, ntlmssp_state);
+               if (!ADS_ERR_OK(status)) {
+                       DEBUG(0, ("ads_setup_sasl_wrapping() failed: %s\n",
+                               ads_errstr(status)));
+                       TALLOC_FREE(ntlmssp_state);
+                       return status;
+               }
+       } else {
+               TALLOC_FREE(ntlmssp_state);
+       }
 
        return ADS_ERROR(rc);
 }
 
+#ifdef HAVE_GSSAPI
+static ADS_STATUS ads_sasl_gssapi_wrap(ADS_STRUCT *ads, uint8 *buf, uint32 len)
+{
+       gss_ctx_id_t context_handle = (gss_ctx_id_t)ads->ldap.wrap_private_data;
+       ADS_STATUS status;
+       int gss_rc;
+       uint32 minor_status;
+       gss_buffer_desc unwrapped, wrapped;
+       int conf_req_flag, conf_state;
+
+       unwrapped.value         = buf;
+       unwrapped.length        = len;
+
+       /* for now request sign and seal */
+       conf_req_flag   = (ads->ldap.wrap_type == ADS_SASLWRAP_TYPE_SEAL);
+
+       gss_rc = gss_wrap(&minor_status, context_handle,
+                         conf_req_flag, GSS_C_QOP_DEFAULT,
+                         &unwrapped, &conf_state,
+                         &wrapped);
+       status = ADS_ERROR_GSS(gss_rc, minor_status);
+       if (!ADS_ERR_OK(status)) return status;
+
+       if (conf_req_flag && conf_state == 0) {
+               return ADS_ERROR_NT(NT_STATUS_ACCESS_DENIED);
+       }
+
+       if ((ads->ldap.out.size - 4) < wrapped.length) {
+               return ADS_ERROR_NT(NT_STATUS_INTERNAL_ERROR);
+       }
+
+       /* copy the wrapped blob to the right location */
+       memcpy(ads->ldap.out.buf + 4, wrapped.value, wrapped.length);
+
+       /* set how many bytes must be written to the underlying socket */
+       ads->ldap.out.left = 4 + wrapped.length;
+
+       gss_release_buffer(&minor_status, &wrapped);
+
+       return ADS_SUCCESS;
+}
+
+static ADS_STATUS ads_sasl_gssapi_unwrap(ADS_STRUCT *ads)
+{
+       gss_ctx_id_t context_handle = (gss_ctx_id_t)ads->ldap.wrap_private_data;
+       ADS_STATUS status;
+       int gss_rc;
+       uint32 minor_status;
+       gss_buffer_desc unwrapped, wrapped;
+       int conf_state;
+
+       wrapped.value   = ads->ldap.in.buf + 4;
+       wrapped.length  = ads->ldap.in.ofs - 4;
+
+       gss_rc = gss_unwrap(&minor_status, context_handle,
+                           &wrapped, &unwrapped,
+                           &conf_state, GSS_C_QOP_DEFAULT);
+       status = ADS_ERROR_GSS(gss_rc, minor_status);
+       if (!ADS_ERR_OK(status)) return status;
+
+       if (ads->ldap.wrap_type == ADS_SASLWRAP_TYPE_SEAL && conf_state == 0) {
+               return ADS_ERROR_NT(NT_STATUS_ACCESS_DENIED);
+       }
+
+       if (wrapped.length < unwrapped.length) {
+               return ADS_ERROR_NT(NT_STATUS_INTERNAL_ERROR);
+       }
+
+       /* copy the wrapped blob to the right location */
+       memcpy(ads->ldap.in.buf + 4, unwrapped.value, unwrapped.length);
+
+       /* set how many bytes must be written to the underlying socket */
+       ads->ldap.in.left       = unwrapped.length;
+       ads->ldap.in.ofs        = 4;
+
+       gss_release_buffer(&minor_status, &unwrapped);
+
+       return ADS_SUCCESS;
+}
+
+static void ads_sasl_gssapi_disconnect(ADS_STRUCT *ads)
+{
+       gss_ctx_id_t context_handle = (gss_ctx_id_t)ads->ldap.wrap_private_data;
+       uint32 minor_status;
+
+       gss_delete_sec_context(&minor_status, &context_handle, GSS_C_NO_BUFFER);
+
+       ads->ldap.wrap_ops = NULL;
+       ads->ldap.wrap_private_data = NULL;
+}
+
+static const struct ads_saslwrap_ops ads_sasl_gssapi_ops = {
+       .name           = "gssapi",
+       .wrap           = ads_sasl_gssapi_wrap,
+       .unwrap         = ads_sasl_gssapi_unwrap,
+       .disconnect     = ads_sasl_gssapi_disconnect
+};
+
+/* 
+   perform a LDAP/SASL/SPNEGO/GSSKRB5 bind
+*/
+static ADS_STATUS ads_sasl_spnego_gsskrb5_bind(ADS_STRUCT *ads, const gss_name_t serv_name)
+{
+       ADS_STATUS status;
+       bool ok;
+       uint32 minor_status;
+       int gss_rc, rc;
+       gss_OID_desc krb5_mech_type =
+       {9, CONST_DISCARD(char *, "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02") };
+       gss_OID mech_type = &krb5_mech_type;
+       gss_OID actual_mech_type = GSS_C_NULL_OID;
+       const char *spnego_mechs[] = {OID_KERBEROS5_OLD, OID_KERBEROS5, OID_NTLMSSP, NULL};
+       gss_ctx_id_t context_handle = GSS_C_NO_CONTEXT;
+       gss_buffer_desc input_token, output_token;
+       uint32 req_flags, ret_flags;
+       uint32 req_tmp, ret_tmp;
+       DATA_BLOB unwrapped;
+       DATA_BLOB wrapped;
+       struct berval cred, *scred = NULL;
+
+       input_token.value = NULL;
+       input_token.length = 0;
+
+       req_flags = GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG;
+       switch (ads->ldap.wrap_type) {
+       case ADS_SASLWRAP_TYPE_SEAL:
+               req_flags |= GSS_C_INTEG_FLAG | GSS_C_CONF_FLAG;
+               break;
+       case ADS_SASLWRAP_TYPE_SIGN:
+               req_flags |= GSS_C_INTEG_FLAG;
+               break;
+       case ADS_SASLWRAP_TYPE_PLAIN:
+               break;
+       }
+
+       /* Note: here we explicit ask for the krb5 mech_type */
+       gss_rc = gss_init_sec_context(&minor_status,
+                                     GSS_C_NO_CREDENTIAL,
+                                     &context_handle,
+                                     serv_name,
+                                     mech_type,
+                                     req_flags,
+                                     0,
+                                     NULL,
+                                     &input_token,
+                                     &actual_mech_type,
+                                     &output_token,
+                                     &ret_flags,
+                                     NULL);
+       if (gss_rc && gss_rc != GSS_S_CONTINUE_NEEDED) {
+               status = ADS_ERROR_GSS(gss_rc, minor_status);
+               goto failed;
+       }
+
+       /*
+        * As some gssapi krb5 mech implementations
+        * automaticly add GSS_C_INTEG_FLAG and GSS_C_CONF_FLAG
+        * to req_flags internaly, it's not possible to
+        * use plain or signing only connection via
+        * the gssapi interface.
+        *
+        * Because of this we need to check it the ret_flags
+        * has more flags as req_flags and correct the value
+        * of ads->ldap.wrap_type.
+        *
+        * I ads->auth.flags has ADS_AUTH_SASL_FORCE
+        * we need to give an error.
+        */
+       req_tmp = req_flags & (GSS_C_INTEG_FLAG | GSS_C_CONF_FLAG);
+       ret_tmp = ret_flags & (GSS_C_INTEG_FLAG | GSS_C_CONF_FLAG);
+
+       if (req_tmp == ret_tmp) {
+               /* everythings fine... */
+
+       } else if (req_flags & GSS_C_CONF_FLAG) {
+               /*
+                * here we wanted sealing but didn't got it
+                * from the gssapi library
+                */
+               status = ADS_ERROR_NT(NT_STATUS_NOT_SUPPORTED);
+               goto failed;
+
+       } else if ((req_flags & GSS_C_INTEG_FLAG) &&
+                  !(ret_flags & GSS_C_INTEG_FLAG)) {
+               /*
+                * here we wanted siging but didn't got it
+                * from the gssapi library
+                */
+               status = ADS_ERROR_NT(NT_STATUS_NOT_SUPPORTED);
+               goto failed;
+
+       } else if (ret_flags & GSS_C_CONF_FLAG) {
+               /*
+                * here we didn't want sealing
+                * but the gssapi library forces it
+                * so correct the needed wrap_type if
+                * the caller didn't forced siging only
+                */
+               if (ads->auth.flags & ADS_AUTH_SASL_FORCE) {
+                       status = ADS_ERROR_NT(NT_STATUS_NOT_SUPPORTED);
+                       goto failed;
+               }
+
+               ads->ldap.wrap_type = ADS_SASLWRAP_TYPE_SEAL;
+               req_flags = ret_flags;
+
+       } else if (ret_flags & GSS_C_INTEG_FLAG) {
+               /*
+                * here we didn't want signing
+                * but the gssapi library forces it
+                * so correct the needed wrap_type if
+                * the caller didn't forced plain
+                */
+               if (ads->auth.flags & ADS_AUTH_SASL_FORCE) {
+                       status = ADS_ERROR_NT(NT_STATUS_NOT_SUPPORTED);
+                       goto failed;
+               }
+
+               ads->ldap.wrap_type = ADS_SASLWRAP_TYPE_SIGN;
+               req_flags = ret_flags;
+       } else {
+               /*
+                * This could (should?) not happen
+                */
+               status = ADS_ERROR_NT(NT_STATUS_INTERNAL_ERROR);
+               goto failed;
+       
+       }
+
+       /* and wrap that in a shiny SPNEGO wrapper */
+       unwrapped = data_blob_const(output_token.value, output_token.length);
+       wrapped = spnego_gen_negTokenInit(talloc_tos(),
+                       spnego_mechs, &unwrapped, NULL);
+       gss_release_buffer(&minor_status, &output_token);
+       if (unwrapped.length > wrapped.length) {
+               status = ADS_ERROR_NT(NT_STATUS_NO_MEMORY);
+               goto failed;
+       }
+
+       cred.bv_val = (char *)wrapped.data;
+       cred.bv_len = wrapped.length;
+
+       rc = ldap_sasl_bind_s(ads->ldap.ld, NULL, "GSS-SPNEGO", &cred, NULL, NULL, 
+                             &scred);
+       data_blob_free(&wrapped);
+       if (rc != LDAP_SUCCESS) {
+               status = ADS_ERROR(rc);
+               goto failed;
+       }
+
+       if (scred) {
+               wrapped = data_blob_const(scred->bv_val, scred->bv_len);
+       } else {
+               wrapped = data_blob_null;
+       }
+
+       ok = spnego_parse_auth_response(talloc_tos(), wrapped, NT_STATUS_OK,
+                                       OID_KERBEROS5_OLD,
+                                       &unwrapped);
+       if (scred) ber_bvfree(scred);
+       if (!ok) {
+               status = ADS_ERROR_NT(NT_STATUS_INVALID_NETWORK_RESPONSE);
+               goto failed;
+       }
+
+       input_token.value       = unwrapped.data;
+       input_token.length      = unwrapped.length;
+
+       /* 
+        * As we asked for mutal authentication
+        * we need to pass the servers response
+        * to gssapi
+        */
+       gss_rc = gss_init_sec_context(&minor_status,
+                                     GSS_C_NO_CREDENTIAL,
+                                     &context_handle,
+                                     serv_name,
+                                     mech_type,
+                                     req_flags,
+                                     0,
+                                     NULL,
+                                     &input_token,
+                                     &actual_mech_type,
+                                     &output_token,
+                                     &ret_flags,
+                                     NULL);
+       data_blob_free(&unwrapped);
+       if (gss_rc) {
+               status = ADS_ERROR_GSS(gss_rc, minor_status);
+               goto failed;
+       }
+
+       gss_release_buffer(&minor_status, &output_token);
+
+       /*
+        * If we the sign and seal options
+        * doesn't match after getting the response
+        * from the server, we don't want to use the connection
+        */
+       req_tmp = req_flags & (GSS_C_INTEG_FLAG | GSS_C_CONF_FLAG);
+       ret_tmp = ret_flags & (GSS_C_INTEG_FLAG | GSS_C_CONF_FLAG);
+
+       if (req_tmp != ret_tmp) {
+               /* everythings fine... */
+               status = ADS_ERROR_NT(NT_STATUS_INVALID_NETWORK_RESPONSE);
+               goto failed;
+       }
+
+       if (ads->ldap.wrap_type > ADS_SASLWRAP_TYPE_PLAIN) {
+               uint32 max_msg_size = ADS_SASL_WRAPPING_OUT_MAX_WRAPPED;
+
+               gss_rc = gss_wrap_size_limit(&minor_status, context_handle,
+                                            (ads->ldap.wrap_type == ADS_SASLWRAP_TYPE_SEAL),
+                                            GSS_C_QOP_DEFAULT,
+                                            max_msg_size, &ads->ldap.out.max_unwrapped);
+               if (gss_rc) {
+                       status = ADS_ERROR_GSS(gss_rc, minor_status);
+                       goto failed;
+               }
+
+               ads->ldap.out.sig_size = max_msg_size - ads->ldap.out.max_unwrapped;
+               ads->ldap.in.min_wrapped = 0x2C; /* taken from a capture with LDAP unbind */
+               ads->ldap.in.max_wrapped = max_msg_size;
+               status = ads_setup_sasl_wrapping(ads, &ads_sasl_gssapi_ops, context_handle);
+               if (!ADS_ERR_OK(status)) {
+                       DEBUG(0, ("ads_setup_sasl_wrapping() failed: %s\n",
+                               ads_errstr(status)));
+                       goto failed;
+               }
+               /* make sure we don't free context_handle */
+               context_handle = GSS_C_NO_CONTEXT;
+       }
+
+       status = ADS_SUCCESS;
+
+failed:
+       if (context_handle != GSS_C_NO_CONTEXT)
+               gss_delete_sec_context(&minor_status, &context_handle, GSS_C_NO_BUFFER);
+       return status;
+}
+
+#endif /* HAVE_GSSAPI */
+
 #ifdef HAVE_KRB5
+struct ads_service_principal {
+        char *string;
+#ifdef HAVE_GSSAPI
+        gss_name_t name;
+#endif
+};
+
+static void ads_free_service_principal(struct ads_service_principal *p)
+{
+       SAFE_FREE(p->string);
+
+#ifdef HAVE_GSSAPI
+       if (p->name) {
+               uint32 minor_status;
+               gss_release_name(&minor_status, &p->name);
+       }
+#endif
+       ZERO_STRUCTP(p);
+}
+
+static ADS_STATUS ads_generate_service_principal(ADS_STRUCT *ads,
+                                                const char *given_principal,
+                                                struct ads_service_principal *p)
+{
+       ADS_STATUS status;
+#ifdef HAVE_GSSAPI
+       gss_buffer_desc input_name;
+       /* GSS_KRB5_NT_PRINCIPAL_NAME */
+       gss_OID_desc nt_principal =
+       {10, CONST_DISCARD(char *, "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02\x01")};
+       uint32 minor_status;
+       int gss_rc;
+#endif
+
+       ZERO_STRUCTP(p);
+
+       /* I've seen a child Windows 2000 domain not send
+          the principal name back in the first round of
+          the SASL bind reply.  So we guess based on server
+          name and realm.  --jerry  */
+       /* Also try best guess when we get the w2k8 ignore
+          principal back - gd */
+
+       if (!given_principal ||
+           strequal(given_principal, ADS_IGNORE_PRINCIPAL)) {
+
+               status = ads_guess_service_principal(ads, &p->string);
+               if (!ADS_ERR_OK(status)) {
+                       return status;
+               }
+       } else {
+               p->string = SMB_STRDUP(given_principal);
+               if (!p->string) {
+                       return ADS_ERROR(LDAP_NO_MEMORY);
+               }
+       }
+
+#ifdef HAVE_GSSAPI
+       input_name.value = p->string;
+       input_name.length = strlen(p->string);
+
+       gss_rc = gss_import_name(&minor_status, &input_name, &nt_principal, &p->name);
+       if (gss_rc) {
+               ads_free_service_principal(p);
+               return ADS_ERROR_GSS(gss_rc, minor_status);
+       }
+#endif
+
+       return ADS_SUCCESS;
+}
+
 /* 
    perform a LDAP/SASL/SPNEGO/KRB5 bind
 */
-static ADS_STATUS ads_sasl_spnego_krb5_bind(ADS_STRUCT *ads, const char *principal)
+static ADS_STATUS ads_sasl_spnego_rawkrb5_bind(ADS_STRUCT *ads, const char *principal)
 {
        DATA_BLOB blob = data_blob_null;
        struct berval cred, *scred = NULL;
        DATA_BLOB session_key = data_blob_null;
        int rc;
 
-       rc = spnego_gen_negTokenTarg(principal, ads->auth.time_offset, &blob, &session_key, 0,
+       if (ads->ldap.wrap_type > ADS_SASLWRAP_TYPE_PLAIN) {
+               return ADS_ERROR_NT(NT_STATUS_NOT_SUPPORTED);
+       }
+
+       rc = spnego_gen_krb5_negTokenInit(talloc_tos(), principal,
+                                    ads->auth.time_offset, &blob, &session_key, 0,
                                     &ads->auth.tgs_expire);
 
        if (rc) {
@@ -166,7 +730,28 @@ static ADS_STATUS ads_sasl_spnego_krb5_bind(ADS_STRUCT *ads, const char *princip
 
        return ADS_ERROR(rc);
 }
+
+static ADS_STATUS ads_sasl_spnego_krb5_bind(ADS_STRUCT *ads,
+                                           struct ads_service_principal *p)
+{
+#ifdef HAVE_GSSAPI
+       /*
+        * we only use the gsskrb5 based implementation
+        * when sasl sign or seal is requested.
+        *
+        * This has the following reasons:
+        * - it's likely that the gssapi krb5 mech implementation
+        *   doesn't support to negotiate plain connections
+        * - the ads_sasl_spnego_rawkrb5_bind is more robust
+        *   against clock skew errors
+        */
+       if (ads->ldap.wrap_type > ADS_SASLWRAP_TYPE_PLAIN) {
+               return ads_sasl_spnego_gsskrb5_bind(ads, p->name);
+       }
 #endif
+       return ads_sasl_spnego_rawkrb5_bind(ads, p->string);
+}
+#endif /* HAVE_KRB5 */
 
 /* 
    this performs a SASL/SPNEGO bind
@@ -177,10 +762,10 @@ static ADS_STATUS ads_sasl_spnego_bind(ADS_STRUCT *ads)
        int rc, i;
        ADS_STATUS status;
        DATA_BLOB blob;
-       char *principal = NULL;
+       char *given_principal = NULL;
        char *OIDs[ASN1_MAX_OIDS];
 #ifdef HAVE_KRB5
-       BOOL got_kerberos_mechanism = False;
+       bool got_kerberos_mechanism = False;
 #endif
 
        rc = ldap_sasl_bind_s(ads->ldap.ld, NULL, "GSS-SPNEGO", NULL, NULL, NULL, &scred);
@@ -200,7 +785,7 @@ static ADS_STATUS ads_sasl_spnego_bind(ADS_STRUCT *ads)
 
        /* the server sent us the first part of the SPNEGO exchange in the negprot 
           reply */
-       if (!spnego_parse_negTokenInit(blob, OIDs, &principal)) {
+       if (!spnego_parse_negTokenInit(talloc_tos(), blob, OIDs, &given_principal, NULL)) {
                data_blob_free(&blob);
                status = ADS_ERROR(LDAP_OPERATIONS_ERROR);
                goto failed;
@@ -216,44 +801,25 @@ static ADS_STATUS ads_sasl_spnego_bind(ADS_STRUCT *ads)
                        got_kerberos_mechanism = True;
                }
 #endif
-               free(OIDs[i]);
+               talloc_free(OIDs[i]);
        }
-       DEBUG(3,("ads_sasl_spnego_bind: got server principal name = %s\n", principal));
+       DEBUG(3,("ads_sasl_spnego_bind: got server principal name = %s\n", given_principal));
 
 #ifdef HAVE_KRB5
        if (!(ads->auth.flags & ADS_AUTH_DISABLE_KERBEROS) &&
            got_kerberos_mechanism) 
        {
-               /* I've seen a child Windows 2000 domain not send 
-                  the principal name back in the first round of 
-                  the SASL bind reply.  So we guess based on server
-                  name and realm.  --jerry  */
-               if ( !principal ) {
-                       if ( ads->server.realm && ads->server.ldap_server ) {
-                               char *server, *server_realm;
-                               
-                               server = SMB_STRDUP( ads->server.ldap_server );
-                               server_realm = SMB_STRDUP( ads->server.realm );
-                               
-                               if ( !server || !server_realm )
-                                       return ADS_ERROR(LDAP_NO_MEMORY);
-
-                               strlower_m( server );
-                               strupper_m( server_realm );                             
-                               asprintf( &principal, "ldap/%s@%s", server, server_realm );
-
-                               SAFE_FREE( server );
-                               SAFE_FREE( server_realm );
-
-                               if ( !principal )
-                                       return ADS_ERROR(LDAP_NO_MEMORY);                               
-                       }
-                       
+               struct ads_service_principal p;
+
+               status = ads_generate_service_principal(ads, given_principal, &p);
+               TALLOC_FREE(given_principal);
+               if (!ADS_ERR_OK(status)) {
+                       return status;
                }
-               
-               status = ads_sasl_spnego_krb5_bind(ads, principal);
+
+               status = ads_sasl_spnego_krb5_bind(ads, &p);
                if (ADS_ERR_OK(status)) {
-                       SAFE_FREE(principal);
+                       ads_free_service_principal(&p);
                        return status;
                }
 
@@ -263,19 +829,26 @@ static ADS_STATUS ads_sasl_spnego_bind(ADS_STRUCT *ads)
                status = ADS_ERROR_KRB5(ads_kinit_password(ads)); 
 
                if (ADS_ERR_OK(status)) {
-                       status = ads_sasl_spnego_krb5_bind(ads, principal);
+                       status = ads_sasl_spnego_krb5_bind(ads, &p);
+                       if (!ADS_ERR_OK(status)) {
+                               DEBUG(0,("kinit succeeded but "
+                                       "ads_sasl_spnego_krb5_bind failed: %s\n",
+                                       ads_errstr(status)));
+                       }
                }
 
+               ads_free_service_principal(&p);
+
                /* only fallback to NTLMSSP if allowed */
                if (ADS_ERR_OK(status) || 
                    !(ads->auth.flags & ADS_AUTH_ALLOW_NTLMSSP)) {
-                       SAFE_FREE(principal);
                        return status;
                }
-       }
+       } else
 #endif
-
-       SAFE_FREE(principal);
+       {
+               TALLOC_FREE(given_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 
@@ -295,80 +868,32 @@ failed:
    this routine is much less fragile
    see RFC2078 and RFC2222 for details
 */
-static ADS_STATUS ads_sasl_gssapi_bind(ADS_STRUCT *ads)
+static ADS_STATUS ads_sasl_gssapi_do_bind(ADS_STRUCT *ads, const gss_name_t serv_name)
 {
        uint32 minor_status;
-       gss_name_t serv_name;
-       gss_buffer_desc input_name;
        gss_ctx_id_t context_handle = GSS_C_NO_CONTEXT;
        gss_OID mech_type = GSS_C_NULL_OID;
        gss_buffer_desc output_token, input_token;
-       uint32 ret_flags, conf_state;
+       uint32 req_flags, ret_flags;
+       int conf_state;
        struct berval cred;
        struct berval *scred = NULL;
        int i=0;
        int gss_rc, rc;
        uint8 *p;
-       uint32 max_msg_size = 0;
-       char *sname = NULL;
+       uint32 max_msg_size = ADS_SASL_WRAPPING_OUT_MAX_WRAPPED;
+       uint8 wrap_type = ADS_SASLWRAP_TYPE_PLAIN;
        ADS_STATUS status;
-       krb5_principal principal = NULL;
-       krb5_context ctx = NULL;
-       krb5_enctype enc_types[] = {
-#ifdef ENCTYPE_ARCFOUR_HMAC
-                       ENCTYPE_ARCFOUR_HMAC,
-#endif
-                       ENCTYPE_DES_CBC_MD5,
-                       ENCTYPE_NULL};
-       gss_OID_desc nt_principal = 
-       {10, CONST_DISCARD(char *, "\052\206\110\206\367\022\001\002\002\002")};
 
-       /* we need to fetch a service ticket as the ldap user in the
-          servers realm, regardless of our realm */
-       asprintf(&sname, "ldap/%s@%s", ads->config.ldap_server_name, ads->config.realm);
-
-       initialize_krb5_error_table();
-       status = ADS_ERROR_KRB5(krb5_init_context(&ctx));
-       if (!ADS_ERR_OK(status)) {
-               SAFE_FREE(sname);
-               return status;
-       }
-       status = ADS_ERROR_KRB5(krb5_set_default_tgs_ktypes(ctx, enc_types));
-       if (!ADS_ERR_OK(status)) {
-               SAFE_FREE(sname);
-               krb5_free_context(ctx); 
-               return status;
-       }
-       status = ADS_ERROR_KRB5(smb_krb5_parse_name(ctx, sname, &principal));
-       if (!ADS_ERR_OK(status)) {
-               SAFE_FREE(sname);
-               krb5_free_context(ctx); 
-               return status;
-       }
-
-       input_name.value = &principal;
-       input_name.length = sizeof(principal);
-
-       gss_rc = gss_import_name(&minor_status, &input_name, &nt_principal, &serv_name);
+       input_token.value = NULL;
+       input_token.length = 0;
 
        /*
-        * The MIT libraries have a *HORRIBLE* bug - input_value.value needs
-        * to point to the *address* of the krb5_principal, and the gss libraries
-        * to a shallow copy of the krb5_principal pointer - so we need to keep
-        * the krb5_principal around until we do the gss_release_name. MIT *SUCKS* !
-        * Just one more way in which MIT engineers screwed me over.... JRA.
+        * Note: here we always ask the gssapi for sign and seal
+        *       as this is negotiated later after the mutal
+        *       authentication
         */
-
-       SAFE_FREE(sname);
-
-       if (gss_rc) {
-               krb5_free_principal(ctx, principal);
-               krb5_free_context(ctx); 
-               return ADS_ERROR_GSS(gss_rc, minor_status);
-       }
-
-       input_token.value = NULL;
-       input_token.length = 0;
+       req_flags = GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG | GSS_C_INTEG_FLAG | GSS_C_CONF_FLAG;
 
        for (i=0; i < MAX_GSS_PASSES; i++) {
                gss_rc = gss_init_sec_context(&minor_status,
@@ -376,7 +901,7 @@ static ADS_STATUS ads_sasl_gssapi_bind(ADS_STRUCT *ads)
                                          &context_handle,
                                          serv_name,
                                          mech_type,
-                                         GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG,
+                                         req_flags,
                                          0,
                                          NULL,
                                          &input_token,
@@ -384,11 +909,10 @@ static ADS_STATUS ads_sasl_gssapi_bind(ADS_STRUCT *ads)
                                          &output_token,
                                          &ret_flags,
                                          NULL);
-
-               if (input_token.value) {
-                       gss_release_buffer(&minor_status, &input_token);
+               if (scred) {
+                       ber_bvfree(scred);
+                       scred = NULL;
                }
-
                if (gss_rc && gss_rc != GSS_S_CONTINUE_NEEDED) {
                        status = ADS_ERROR_GSS(gss_rc, minor_status);
                        goto failed;
@@ -420,14 +944,16 @@ static ADS_STATUS ads_sasl_gssapi_bind(ADS_STRUCT *ads)
        }
 
        gss_rc = gss_unwrap(&minor_status,context_handle,&input_token,&output_token,
-                           (int *)&conf_state,NULL);
+                           &conf_state,NULL);
+       if (scred) {
+               ber_bvfree(scred);
+               scred = NULL;
+       }
        if (gss_rc) {
                status = ADS_ERROR_GSS(gss_rc, minor_status);
                goto failed;
        }
 
-       gss_release_buffer(&minor_status, &input_token);
-
        p = (uint8 *)output_token.value;
 
 #if 0
@@ -435,20 +961,42 @@ static ADS_STATUS ads_sasl_gssapi_bind(ADS_STRUCT *ads)
 #endif
 
        if (p) {
-               max_msg_size = (p[1]<<16) | (p[2]<<8) | p[3];
+               wrap_type = CVAL(p,0);
+               SCVAL(p,0,0);
+               max_msg_size = RIVAL(p,0);
        }
 
        gss_release_buffer(&minor_status, &output_token);
 
+       if (!(wrap_type & ads->ldap.wrap_type)) {
+               /*
+                * the server doesn't supports the wrap
+                * type we want :-(
+                */
+               DEBUG(0,("The ldap sasl wrap type doesn't match wanted[%d] server[%d]\n",
+                       ads->ldap.wrap_type, wrap_type));
+               DEBUGADD(0,("You may want to set the 'client ldap sasl wrapping' option\n"));
+               status = ADS_ERROR_NT(NT_STATUS_NOT_SUPPORTED);
+               goto failed;
+       }
+
+       /* 0x58 is the minimum windows accepts */
+       if (max_msg_size < 0x58) {
+               max_msg_size = 0x58;
+       }
+
        output_token.length = 4;
        output_token.value = SMB_MALLOC(output_token.length);
+       if (!output_token.value) {
+               output_token.length = 0;
+               status = ADS_ERROR_NT(NT_STATUS_NO_MEMORY);
+               goto failed;
+       }
        p = (uint8 *)output_token.value;
 
-       *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;
+       RSIVAL(p,0,max_msg_size);
+       SCVAL(p,0,ads->ldap.wrap_type);
+
        /*
         * we used to add sprintf("dn:%s", ads->config.bind_path) here.
         * but using ads->config.bind_path is the wrong! It should be
@@ -459,37 +1007,95 @@ static ADS_STATUS ads_sasl_gssapi_bind(ADS_STRUCT *ads)
         */
 
        gss_rc = gss_wrap(&minor_status, context_handle,0,GSS_C_QOP_DEFAULT,
-                         &output_token, (int *)&conf_state,
-                         &input_token);
+                       &output_token, /* used as *input* here. */
+                       &conf_state,
+                       &input_token); /* Used as *output* here. */
        if (gss_rc) {
                status = ADS_ERROR_GSS(gss_rc, minor_status);
+               output_token.length = 0;
+               SAFE_FREE(output_token.value);
                goto failed;
        }
 
-       free(output_token.value);
+       /* We've finished with output_token. */
+       SAFE_FREE(output_token.value);
+       output_token.length = 0;
 
        cred.bv_val = (char *)input_token.value;
        cred.bv_len = input_token.length;
 
        rc = ldap_sasl_bind_s(ads->ldap.ld, NULL, "GSSAPI", &cred, NULL, NULL, 
                              &scred);
+       gss_release_buffer(&minor_status, &input_token);
        status = ADS_ERROR(rc);
+       if (!ADS_ERR_OK(status)) {
+               goto failed;
+       }
 
-       gss_release_buffer(&minor_status, &input_token);
+       if (ads->ldap.wrap_type > ADS_SASLWRAP_TYPE_PLAIN) {
+               gss_rc = gss_wrap_size_limit(&minor_status, context_handle,
+                                            (ads->ldap.wrap_type == ADS_SASLWRAP_TYPE_SEAL),
+                                            GSS_C_QOP_DEFAULT,
+                                            max_msg_size, &ads->ldap.out.max_unwrapped);
+               if (gss_rc) {
+                       status = ADS_ERROR_GSS(gss_rc, minor_status);
+                       goto failed;
+               }
+
+               ads->ldap.out.sig_size = max_msg_size - ads->ldap.out.max_unwrapped;
+               ads->ldap.in.min_wrapped = 0x2C; /* taken from a capture with LDAP unbind */
+               ads->ldap.in.max_wrapped = max_msg_size;
+               status = ads_setup_sasl_wrapping(ads, &ads_sasl_gssapi_ops, context_handle);
+               if (!ADS_ERR_OK(status)) {
+                       DEBUG(0, ("ads_setup_sasl_wrapping() failed: %s\n",
+                               ads_errstr(status)));
+                       goto failed;
+               }
+               /* make sure we don't free context_handle */
+               context_handle = GSS_C_NO_CONTEXT;
+       }
 
 failed:
 
-       gss_release_name(&minor_status, &serv_name);
        if (context_handle != GSS_C_NO_CONTEXT)
                gss_delete_sec_context(&minor_status, &context_handle, GSS_C_NO_BUFFER);
-       krb5_free_principal(ctx, principal);
-       krb5_free_context(ctx); 
 
        if(scred)
                ber_bvfree(scred);
        return status;
 }
-#endif /* HAVE_GGSAPI */
+
+static ADS_STATUS ads_sasl_gssapi_bind(ADS_STRUCT *ads)
+{
+       ADS_STATUS status;
+       struct ads_service_principal p;
+
+       status = ads_generate_service_principal(ads, NULL, &p);
+       if (!ADS_ERR_OK(status)) {
+               return status;
+       }
+
+       status = ads_sasl_gssapi_do_bind(ads, p.name);
+       if (ADS_ERR_OK(status)) {
+               ads_free_service_principal(&p);
+               return status;
+       }
+
+       DEBUG(10,("ads_sasl_gssapi_do_bind failed with: %s, "
+                 "calling kinit\n", ads_errstr(status)));
+
+       status = ADS_ERROR_KRB5(ads_kinit_password(ads));
+
+       if (ADS_ERR_OK(status)) {
+               status = ads_sasl_gssapi_do_bind(ads, p.name);
+       }
+
+       ads_free_service_principal(&p);
+
+       return status;
+}
+
+#endif /* HAVE_GSSAPI */
 
 /* mapping between SASL mechanisms and functions */
 static struct {
@@ -517,13 +1123,31 @@ ADS_STATUS ads_sasl_bind(ADS_STRUCT *ads)
 
        values = ldap_get_values(ads->ldap.ld, res, "supportedSASLMechanisms");
 
+       if (ads->auth.flags & ADS_AUTH_SASL_SEAL) {
+               ads->ldap.wrap_type = ADS_SASLWRAP_TYPE_SEAL;
+       } else if (ads->auth.flags & ADS_AUTH_SASL_SIGN) {
+               ads->ldap.wrap_type = ADS_SASLWRAP_TYPE_SIGN;
+       } else {
+               ads->ldap.wrap_type = ADS_SASLWRAP_TYPE_PLAIN;
+       }
+
        /* 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]));
+retry:
                                status = sasl_mechanisms[i].fn(ads);
+                               if (status.error_type == ENUM_ADS_ERROR_LDAP &&
+                                   status.err.rc == LDAP_STRONG_AUTH_REQUIRED &&
+                                   ads->ldap.wrap_type == ADS_SASLWRAP_TYPE_PLAIN)
+                               {
+                                       DEBUG(3,("SASL bin got LDAP_STRONG_AUTH_REQUIRED "
+                                                "retrying with signing enabled\n"));
+                                       ads->ldap.wrap_type = ADS_SASLWRAP_TYPE_SIGN;
+                                       goto retry;
+                               }
                                ldap_value_free(values);
                                ldap_msgfree(res);
                                return status;