TODO: s3-libads: Pass cli_credentials to ads_sasl_spnego_gensec_bind()
[metze/samba/wip.git] / source3 / libads / sasl.c
index 4436551d88d44b2b63743dcb1bc877a44068e6cb..841f24c6487106e4de33620309c8ec3d86a89a6f 100644 (file)
 */
 
 #include "includes.h"
-
-#ifdef HAVE_LDAP
-
-static ADS_STATUS ads_sasl_ntlmssp_wrap(ADS_STRUCT *ads, uint8 *buf, uint32 len)
+#include "../libcli/auth/spnego.h"
+#include "auth/credentials/credentials.h"
+#include "auth/gensec/gensec.h"
+#include "auth_generic.h"
+#include "ads.h"
+#include "smb_krb5.h"
+#include "system/gssapi.h"
+#include "lib/param/param.h"
+#include "krb5_env.h"
+
+static struct cli_credentials *ads_sasl_creds_init(TALLOC_CTX *mem_ctx,
+                                                  const char *username,
+                                                  const char *password,
+                                                  const char *domain,
+                                                  const char *realm,
+                                                  enum credentials_use_kerberos krb5_state)
 {
-       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.out.buf + (4 + NTLMSSP_SIG_SIZE);
-
-       /* 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,
-                                               dptr, len,
-                                               dptr, len,
-                                               &sig);
-       } else {
-               nt_status = ntlmssp_sign_packet(ntlmssp_state,
-                                               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);
-
-       data_blob_free(&sig);
-
-       /* set how many bytes must be written to the underlying socket */
-       ads->ldap.out.left = 4 + NTLMSSP_SIG_SIZE + len;
+       struct loadparm_context *lp_ctx;
+       struct cli_credentials *creds;
+       bool ok;
 
-       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);
+       creds = cli_credentials_init(mem_ctx);
+       if (creds == NULL) {
+               return NULL;
        }
-       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;
-
-       ntlmssp_end(&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??)
-*/
-static ADS_STATUS ads_sasl_spnego_ntlmssp_bind(ADS_STRUCT *ads)
-{
-       DATA_BLOB msg1 = data_blob_null;
-       DATA_BLOB blob = data_blob_null;
-       DATA_BLOB blob_in = data_blob_null;
-       DATA_BLOB blob_out = data_blob_null;
-       struct berval cred, *scred = NULL;
-       int rc;
-       NTSTATUS nt_status;
-       int turn = 1;
-       uint32 features = 0;
-
-       struct ntlmssp_state *ntlmssp_state;
-
-       if (!NT_STATUS_IS_OK(nt_status = ntlmssp_client_start(&ntlmssp_state))) {
-               return ADS_ERROR_NT(nt_status);
+       lp_ctx = loadparm_init_s3(creds, loadparm_s3_helpers());
+       if (lp_ctx == NULL) {
+               goto fail;
        }
-       ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_SIGN;
+       cli_credentials_set_conf(creds, lp_ctx);
 
-       if (!NT_STATUS_IS_OK(nt_status = ntlmssp_set_username(ntlmssp_state, ads->auth.user_name))) {
-               return ADS_ERROR_NT(nt_status);
-       }
-       if (!NT_STATUS_IS_OK(nt_status = ntlmssp_set_domain(ntlmssp_state, ads->auth.realm))) {
-               return ADS_ERROR_NT(nt_status);
-       }
-       if (!NT_STATUS_IS_OK(nt_status = ntlmssp_set_password(ntlmssp_state, ads->auth.password))) {
-               return ADS_ERROR_NT(nt_status);
+       if (username != NULL) {
+               ok = cli_credentials_set_username(creds,
+                                                 username,
+                                                 CRED_SPECIFIED);
+               if (!ok) {
+                       goto fail;
+               }
        }
 
-       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;
+       if (password != NULL) {
+               ok = cli_credentials_set_password(creds,
+                                                 password,
+                                                 CRED_SPECIFIED);
+               if (!ok) {
+                       goto fail;
                }
-               break;
-       case ADS_SASLWRAP_TYPE_PLAIN:
-               break;
        }
 
-       ntlmssp_want_feature(ntlmssp_state, features);
-
-       blob_in = data_blob_null;
-
-       do {
-               nt_status = ntlmssp_update(ntlmssp_state, 
-                                          blob_in, &blob_out);
-               data_blob_free(&blob_in);
-               if ((NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED) 
-                    || NT_STATUS_IS_OK(nt_status))
-                   && blob_out.length) {
-                       if (turn == 1) {
-                               /* and wrap it in a SPNEGO wrapper */
-                               msg1 = gen_negTokenInit(OID_NTLMSSP, blob_out);
-                       } else {
-                               /* wrap it in SPNEGO */
-                               msg1 = spnego_gen_auth(blob_out);
-                       }
-
-                       data_blob_free(&blob_out);
-
-                       cred.bv_val = (char *)msg1.data;
-                       cred.bv_len = msg1.length;
-                       scred = NULL;
-                       rc = ldap_sasl_bind_s(ads->ldap.ld, NULL, "GSS-SPNEGO", &cred, NULL, NULL, &scred);
-                       data_blob_free(&msg1);
-                       if ((rc != LDAP_SASL_BIND_IN_PROGRESS) && (rc != 0)) {
-                               if (scred) {
-                                       ber_bvfree(scred);
-                               }
-
-                               ntlmssp_end(&ntlmssp_state);
-                               return ADS_ERROR(rc);
-                       }
-                       if (scred) {
-                               blob = data_blob(scred->bv_val, scred->bv_len);
-                               ber_bvfree(scred);
-                       } else {
-                               blob = data_blob_null;
-                       }
-
-               } else {
-
-                       ntlmssp_end(&ntlmssp_state);
-                       data_blob_free(&blob_out);
-                       return ADS_ERROR_NT(nt_status);
+       if (domain != NULL) {
+               ok = cli_credentials_set_domain(creds,
+                                               domain,
+                                               CRED_SPECIFIED);
+               if (!ok) {
+                       goto fail;
                }
-               
-               if ((turn == 1) && 
-                   (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, 
-                                                   &tmp_blob)) {
-
-                               ntlmssp_end(&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, 
-                                                       &blob_in)) {
-
-                               ntlmssp_end(&ntlmssp_state);
-                               data_blob_free(&blob);
-                               DEBUG(3,("Failed to parse auth response\n"));
-                               return ADS_ERROR_NT(NT_STATUS_INVALID_PARAMETER);
-                       }
+       }
+
+       if (realm != NULL) {
+               ok = cli_credentials_set_realm(creds,
+                                              realm,
+                                              CRED_SPECIFIED);
+               if (!ok) {
+                       goto fail;
                }
-               data_blob_free(&blob);
-               data_blob_free(&blob_out);
-               turn++;
-       } while (rc == LDAP_SASL_BIND_IN_PROGRESS && !NT_STATUS_IS_OK(nt_status));
-       
-       /* we have a reference conter on ntlmssp_state, if we are signing
-          then the state will be kept by the signing engine */
-
-       if (ads->ldap.wrap_type > ADS_SASLWRAP_TYPE_PLAIN) {
-               ads->ldap.out.min = 4;
-               ads->ldap.out.max = 0x0FFFFFFF - NTLMSSP_SIG_SIZE;
-               ads->ldap.out.sig_size = NTLMSSP_SIG_SIZE;
-               ads->ldap.in.min = 4;
-               ads->ldap.in.max = 0x0FFFFFFF;
-               ads_setup_sasl_wrapping(ads, &ads_sasl_ntlmssp_ops, ntlmssp_state);
-       } else {
-               ntlmssp_end(&ntlmssp_state);
        }
 
-       return ADS_ERROR(rc);
-}
+       cli_credentials_set_kerberos_state(creds, krb5_state);
 
-#ifdef HAVE_GSSAPI
-static ADS_STATUS ads_sasl_gssapi_wrap(ADS_STRUCT *ads, uint8 *buf, uint32 len)
-{
-       gss_ctx_id_t context_handle = 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;
+       return creds;
+fail:
+       TALLOC_FREE(creds);
+       return NULL;
+}
 
-       unwrapped.value         = buf;
-       unwrapped.length        = len;
+#ifdef HAVE_LDAP
 
-       /* for now request sign and seal */
-       conf_req_flag   = (ads->ldap.wrap_type == ADS_SASLWRAP_TYPE_SEAL);
+static ADS_STATUS ads_sasl_gensec_wrap(struct ads_saslwrap *wrap,
+                                      uint8_t *buf, uint32_t len)
+{
+       struct gensec_security *gensec_security =
+               talloc_get_type_abort(wrap->wrap_private_data,
+               struct gensec_security);
+       NTSTATUS nt_status;
+       DATA_BLOB unwrapped, wrapped;
+       TALLOC_CTX *frame = talloc_stackframe();
 
-       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;
+       unwrapped = data_blob_const(buf, len);
 
-       if (conf_req_flag && conf_state == 0) {
-               return ADS_ERROR_NT(NT_STATUS_ACCESS_DENIED);
+       nt_status = gensec_wrap(gensec_security, frame, &unwrapped, &wrapped);
+       if (!NT_STATUS_IS_OK(nt_status)) {
+               TALLOC_FREE(frame);
+               return ADS_ERROR_NT(nt_status);
        }
 
-       if ((ads->ldap.out.size - 4) < wrapped.length) {
+       if ((wrap->out.size - 4) < wrapped.length) {
+               TALLOC_FREE(frame);
                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);
+       memcpy(wrap->out.buf + 4, wrapped.data, wrapped.length);
 
        /* set how many bytes must be written to the underlying socket */
-       ads->ldap.out.left = 4 + wrapped.length;
+       wrap->out.left = 4 + wrapped.length;
 
-       gss_release_buffer(&minor_status, &wrapped);
+       TALLOC_FREE(frame);
 
        return ADS_SUCCESS;
 }
 
-static ADS_STATUS ads_sasl_gssapi_unwrap(ADS_STRUCT *ads)
+static ADS_STATUS ads_sasl_gensec_unwrap(struct ads_saslwrap *wrap)
 {
-       gss_ctx_id_t context_handle = 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;
+       struct gensec_security *gensec_security =
+               talloc_get_type_abort(wrap->wrap_private_data,
+               struct gensec_security);
+       NTSTATUS nt_status;
+       DATA_BLOB unwrapped, wrapped;
+       TALLOC_CTX *frame = talloc_stackframe();
 
-       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;
+       wrapped = data_blob_const(wrap->in.buf + 4, wrap->in.ofs - 4);
 
-       if (ads->ldap.wrap_type == ADS_SASLWRAP_TYPE_SEAL && conf_state == 0) {
-               return ADS_ERROR_NT(NT_STATUS_ACCESS_DENIED);
+       nt_status = gensec_unwrap(gensec_security, frame, &wrapped, &unwrapped);
+       if (!NT_STATUS_IS_OK(nt_status)) {
+               TALLOC_FREE(frame);
+               return ADS_ERROR_NT(nt_status);
        }
 
-       if (wrapped.length < wrapped.length) {
+       if (wrapped.length < unwrapped.length) {
+               TALLOC_FREE(frame);
                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);
+       memcpy(wrap->in.buf + 4, unwrapped.data, unwrapped.length);
 
        /* set how many bytes must be written to the underlying socket */
-       ads->ldap.in.left       = unwrapped.length;
-       ads->ldap.in.ofs        = 4;
+       wrap->in.left   = unwrapped.length;
+       wrap->in.ofs    = 4;
 
-       gss_release_buffer(&minor_status, &unwrapped);
+       TALLOC_FREE(frame);
 
        return ADS_SUCCESS;
 }
 
-static void ads_sasl_gssapi_disconnect(ADS_STRUCT *ads)
+static void ads_sasl_gensec_disconnect(struct ads_saslwrap *wrap)
 {
-       gss_ctx_id_t context_handle = ads->ldap.wrap_private_data;
-       uint32 minor_status;
+       struct gensec_security *gensec_security =
+               talloc_get_type_abort(wrap->wrap_private_data,
+               struct gensec_security);
 
-       gss_delete_sec_context(&minor_status, &context_handle, GSS_C_NO_BUFFER);
+       TALLOC_FREE(gensec_security);
 
-       ads->ldap.wrap_ops = NULL;
-       ads->ldap.wrap_private_data = NULL;
+       wrap->wrap_ops = NULL;
+       wrap->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
+static const struct ads_saslwrap_ops ads_sasl_gensec_ops = {
+       .name           = "gensec",
+       .wrap           = ads_sasl_gensec_wrap,
+       .unwrap         = ads_sasl_gensec_unwrap,
+       .disconnect     = ads_sasl_gensec_disconnect
 };
 
 /* 
-   perform a LDAP/SASL/SPNEGO/GSSKRB5 bind
+   perform a LDAP/SASL/SPNEGO/{NTLMSSP,KRB5} bind (just how many layers can
+   we fit on one socket??)
 */
-static ADS_STATUS ads_sasl_spnego_gsskrb5_bind(ADS_STRUCT *ads, const gss_name_t serv_name)
+static ADS_STATUS ads_sasl_spnego_gensec_bind(ADS_STRUCT *ads,
+                               const char *sasl,
+                               struct cli_credentials *creds,
+                               const char *target_service,
+                               const char *target_hostname,
+                               const DATA_BLOB server_blob)
 {
+       DATA_BLOB blob_in = data_blob_null;
+       DATA_BLOB blob_out = data_blob_null;
+       int rc;
+       NTSTATUS nt_status;
        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) {
+       struct auth_generic_state *auth_generic_state;
+       bool use_spnego_principal = lp_client_use_spnego_principal();
+       const char *sasl_list[] = { sasl, NULL };
+       enum credentials_use_kerberos krb5_state;
+       NTTIME end_nt_time;
+       struct ads_saslwrap *wrap = &ads->ldap_wrap_data;
+
+       nt_status = auth_generic_client_prepare(NULL, &auth_generic_state);
+       if (!NT_STATUS_IS_OK(nt_status)) {
+               return ADS_ERROR_NT(nt_status);
+       }
+
+       nt_status = auth_generic_set_creds(auth_generic_state, creds);
+       if (!NT_STATUS_IS_OK(nt_status)) {
+               return ADS_ERROR_NT(nt_status);
+       }
+
+       if (server_blob.length == 0) {
+               use_spnego_principal = false;
+       }
+
+       krb5_state = cli_credentials_get_kerberos_state(creds);
+       if (krb5_state == CRED_DONT_USE_KERBEROS) {
+               use_spnego_principal = false;
+       }
+
+       if (target_service != NULL) {
+               nt_status = gensec_set_target_service(
+                                       auth_generic_state->gensec_security,
+                                       target_service);
+               if (!NT_STATUS_IS_OK(nt_status)) {
+                       return ADS_ERROR_NT(nt_status);
+               }
+       }
+
+       if (target_hostname != NULL) {
+               nt_status = gensec_set_target_hostname(
+                                       auth_generic_state->gensec_security,
+                                       target_hostname);
+               if (!NT_STATUS_IS_OK(nt_status)) {
+                       return ADS_ERROR_NT(nt_status);
+               }
+       }
+
+       if (target_service != NULL && target_hostname != NULL) {
+               use_spnego_principal = false;
+       }
+
+       switch (wrap->wrap_type) {
        case ADS_SASLWRAP_TYPE_SEAL:
-               req_flags |= GSS_C_INTEG_FLAG | GSS_C_CONF_FLAG;
+               gensec_want_feature(auth_generic_state->gensec_security, GENSEC_FEATURE_SIGN);
+               gensec_want_feature(auth_generic_state->gensec_security, GENSEC_FEATURE_SEAL);
                break;
        case ADS_SASLWRAP_TYPE_SIGN:
-               req_flags |= GSS_C_INTEG_FLAG;
+               if (ads->auth.flags & ADS_AUTH_SASL_FORCE) {
+                       gensec_want_feature(auth_generic_state->gensec_security, GENSEC_FEATURE_SIGN);
+               } else {
+                       /*
+                        * windows servers are broken with sign only,
+                        * so we let the NTLMSSP backend to seal here,
+                        * via GENSEC_FEATURE_LDAP_STYLE.
+                        */
+                       gensec_want_feature(auth_generic_state->gensec_security, GENSEC_FEATURE_SIGN);
+                       gensec_want_feature(auth_generic_state->gensec_security, GENSEC_FEATURE_LDAP_STYLE);
+               }
                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;
+       nt_status = auth_generic_client_start_by_sasl(auth_generic_state,
+                                                     sasl_list);
+       if (!NT_STATUS_IS_OK(nt_status)) {
+               return ADS_ERROR_NT(nt_status);
        }
 
-       /*
-        * 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;
+       rc = LDAP_SASL_BIND_IN_PROGRESS;
+       nt_status = NT_STATUS_MORE_PROCESSING_REQUIRED;
+       if (use_spnego_principal) {
+               blob_in = data_blob_dup_talloc(talloc_tos(), server_blob);
+               if (blob_in.length == 0) {
+                       TALLOC_FREE(auth_generic_state);
+                       return ADS_ERROR_NT(NT_STATUS_NO_MEMORY);
+               }
+       } else {
+               blob_in = data_blob_null;
+       }
+       blob_out = data_blob_null;
 
-       } 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;
+       while (true) {
+               struct berval cred, *scred = NULL;
 
-       } 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;
+               nt_status = gensec_update(auth_generic_state->gensec_security,
+                                         talloc_tos(), blob_in, &blob_out);
+               data_blob_free(&blob_in);
+               if (!NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED)
+                   && !NT_STATUS_IS_OK(nt_status))
+               {
+                       TALLOC_FREE(auth_generic_state);
+                       data_blob_free(&blob_out);
+                       return ADS_ERROR_NT(nt_status);
                }
 
-               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;
+               if (NT_STATUS_IS_OK(nt_status) && rc == 0 && blob_out.length == 0) {
+                       break;
                }
 
-               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;
-       
-       }
+               cred.bv_val = (char *)blob_out.data;
+               cred.bv_len = blob_out.length;
+               scred = NULL;
+               rc = ldap_sasl_bind_s(ads->ldap.ld, NULL, sasl, &cred, NULL, NULL, &scred);
+               data_blob_free(&blob_out);
+               if ((rc != LDAP_SASL_BIND_IN_PROGRESS) && (rc != 0)) {
+                       if (scred) {
+                               ber_bvfree(scred);
+                       }
 
-       /* and wrap that in a shiny SPNEGO wrapper */
-       unwrapped = data_blob_const(output_token.value, output_token.length);
-       wrapped = gen_negTokenTarg(spnego_mechs, unwrapped);
-       gss_release_buffer(&minor_status, &output_token);
-       if (unwrapped.length > wrapped.length) {
-               status = ADS_ERROR_NT(NT_STATUS_NO_MEMORY);
-               goto failed;
+                       TALLOC_FREE(auth_generic_state);
+                       return ADS_ERROR(rc);
+               }
+               if (scred) {
+                       blob_in = data_blob_talloc(talloc_tos(),
+                                                  scred->bv_val,
+                                                  scred->bv_len);
+                       if (blob_in.length != scred->bv_len) {
+                               ber_bvfree(scred);
+                               TALLOC_FREE(auth_generic_state);
+                               return ADS_ERROR_NT(NT_STATUS_NO_MEMORY);
+                       }
+                       ber_bvfree(scred);
+               } else {
+                       blob_in = data_blob_null;
+               }
+               if (NT_STATUS_IS_OK(nt_status) && rc == 0 && blob_in.length == 0) {
+                       break;
+               }
        }
 
-       cred.bv_val = (char *)wrapped.data;
-       cred.bv_len = wrapped.length;
+       data_blob_free(&blob_in);
+       data_blob_free(&blob_out);
 
-       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 (wrap->wrap_type >= ADS_SASLWRAP_TYPE_SEAL) {
+               bool ok;
 
-       if (scred) {
-               wrapped = data_blob_const(scred->bv_val, scred->bv_len);
-       } else {
-               wrapped = data_blob_null;
-       }
-
-       ok = spnego_parse_auth_response(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;
-       }
+               ok = gensec_have_feature(auth_generic_state->gensec_security,
+                                        GENSEC_FEATURE_SEAL);
+               if (!ok) {
+                       DEBUG(0,("The gensec feature sealing request, but unavailable\n"));
+                       TALLOC_FREE(auth_generic_state);
+                       return ADS_ERROR_NT(NT_STATUS_INVALID_NETWORK_RESPONSE);
+               }
 
-       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;
-       }
+               ok = gensec_have_feature(auth_generic_state->gensec_security,
+                                        GENSEC_FEATURE_SIGN);
+               if (!ok) {
+                       DEBUG(0,("The gensec feature signing request, but unavailable\n"));
+                       TALLOC_FREE(auth_generic_state);
+                       return ADS_ERROR_NT(NT_STATUS_INVALID_NETWORK_RESPONSE);
+               }
 
-       gss_release_buffer(&minor_status, &output_token);
+       } else if (wrap->wrap_type >= ADS_SASLWRAP_TYPE_SIGN) {
+               bool ok;
 
-       /*
-        * 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);
+               ok = gensec_have_feature(auth_generic_state->gensec_security,
+                                        GENSEC_FEATURE_SIGN);
+               if (!ok) {
+                       DEBUG(0,("The gensec feature signing request, but unavailable\n"));
+                       TALLOC_FREE(auth_generic_state);
+                       return ADS_ERROR_NT(NT_STATUS_INVALID_NETWORK_RESPONSE);
+               }
+       }
 
-       if (req_tmp != ret_tmp) {
-               /* everythings fine... */
-               status = ADS_ERROR_NT(NT_STATUS_INVALID_NETWORK_RESPONSE);
-               goto failed;
+       ads->auth.tgs_expire = LONG_MAX;
+       end_nt_time = gensec_expire_time(auth_generic_state->gensec_security);
+       if (end_nt_time != GENSEC_EXPIRE_TIME_INFINITY) {
+               struct timeval tv;
+               nttime_to_timeval(&tv, end_nt_time);
+               ads->auth.tgs_expire = tv.tv_sec;
        }
 
-       if (ads->ldap.wrap_type > ADS_SASLWRAP_TYPE_PLAIN) {
-               uint32 max_msg_size = 0x0A000000;
+       if (wrap->wrap_type > ADS_SASLWRAP_TYPE_PLAIN) {
+               size_t max_wrapped =
+                       gensec_max_wrapped_size(auth_generic_state->gensec_security);
+               wrap->out.max_unwrapped =
+                       gensec_max_input_size(auth_generic_state->gensec_security);
 
-               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);
-               if (gss_rc) {
-                       status = ADS_ERROR_GSS(gss_rc, minor_status);
-                       goto failed;
+               wrap->out.sig_size = max_wrapped - wrap->out.max_unwrapped;
+               /*
+                * Note that we have to truncate this to 0x2C
+                * (taken from a capture with LDAP unbind), as the
+                * signature size is not constant for Kerberos with
+                * arcfour-hmac-md5.
+                */
+               wrap->in.min_wrapped = MIN(wrap->out.sig_size, 0x2C);
+               wrap->in.max_wrapped = ADS_SASL_WRAPPING_IN_MAX_WRAPPED;
+               status = ads_setup_sasl_wrapping(wrap, ads->ldap.ld,
+                                                &ads_sasl_gensec_ops,
+                                                auth_generic_state->gensec_security);
+               if (!ADS_ERR_OK(status)) {
+                       DEBUG(0, ("ads_setup_sasl_wrapping() failed: %s\n",
+                               ads_errstr(status)));
+                       TALLOC_FREE(auth_generic_state);
+                       return status;
                }
-
-               ads->ldap.out.min = 4;
-               ads->ldap.out.sig_size = max_msg_size - ads->ldap.out.max;
-               ads->ldap.in.min = 4;
-               ads->ldap.in.max = max_msg_size;
-               ads_setup_sasl_wrapping(ads, &ads_sasl_gssapi_ops, context_handle);
-               /* make sure we don't free context_handle */
-               context_handle = GSS_C_NO_CONTEXT;
+               /* Only keep the gensec_security element around long-term */
+               talloc_steal(NULL, auth_generic_state->gensec_security);
        }
+       TALLOC_FREE(auth_generic_state);
 
-failed:
-       if (context_handle != GSS_C_NO_CONTEXT)
-               gss_delete_sec_context(&minor_status, &context_handle, GSS_C_NO_BUFFER);
-       return status;
+       return ADS_ERROR(rc);
 }
 
-#endif
-
 #ifdef HAVE_KRB5
 struct ads_service_principal {
-        krb5_context ctx;
-        char *string;
-        krb5_principal principal;
-#ifdef HAVE_GSSAPI
-        gss_name_t name;
+       char *service;
+       char *hostname;
+       char *string;
+#ifdef HAVE_KRB5
+       gss_name_t name;
 #endif
 };
 
 static void ads_free_service_principal(struct ads_service_principal *p)
 {
+       SAFE_FREE(p->service);
+       SAFE_FREE(p->hostname);
        SAFE_FREE(p->string);
 
-#ifdef HAVE_GSSAPI
+#ifdef HAVE_KRB5
        if (p->name) {
-               uint32 minor_status;
+               uint32_t minor_status;
                gss_release_name(&minor_status, &p->name);
        }
 #endif
-       if (p->principal) {
-               krb5_free_principal(p->ctx, p->principal);
-       }
-
-       if (p->ctx) {
-               krb5_free_context(p->ctx);
-       }
-
        ZERO_STRUCTP(p);
 }
 
-static ADS_STATUS ads_generate_service_principal(ADS_STRUCT *ads,
-                                                const char *given_principal,
-                                                struct ads_service_principal *p)
+static ADS_STATUS ads_guess_target(ADS_STRUCT *ads,
+                                  char **service,
+                                  char **hostname,
+                                  char **principal)
 {
-       ADS_STATUS status;
-       krb5_enctype enc_types[] = {
-#ifdef ENCTYPE_ARCFOUR_HMAC
-                       ENCTYPE_ARCFOUR_HMAC,
-#endif
-                       ENCTYPE_DES_CBC_MD5,
-                       ENCTYPE_NULL};
-#ifdef HAVE_GSSAPI
-       gss_buffer_desc input_name;
-       gss_OID_desc nt_principal = 
-       {10, CONST_DISCARD(char *, "\052\206\110\206\367\022\001\002\002\002")};
-       uint32 minor_status;
-       int gss_rc;
-#endif
+       ADS_STATUS status = ADS_ERROR(LDAP_NO_MEMORY);
+       char *princ = NULL;
+       TALLOC_CTX *frame;
+       char *server = NULL;
+       char *realm = NULL;
+       int rc;
 
-       ZERO_STRUCTP(p);
+       frame = talloc_stackframe();
+       if (frame == NULL) {
+               return ADS_ERROR(LDAP_NO_MEMORY);
+       }
 
-       /* 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 (given_principal) {
-               p->string = SMB_STRDUP(given_principal);
-               if (!p->string) {
-                       return ADS_ERROR(LDAP_NO_MEMORY);
+       if (ads->server.realm && ads->server.ldap_server) {
+               server = strlower_talloc(frame, ads->server.ldap_server);
+               if (server == NULL) {
+                       goto out;
                }
-       } else 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);
+               realm = strupper_talloc(frame, ads->server.realm);
+               if (realm == NULL) {
+                       goto out;
                }
 
-               strlower_m(server);
-               strupper_m(server_realm);
-               asprintf(&p->string, "ldap/%s@%s", server, server_realm);
+               /*
+                * If we got a name which is bigger than a NetBIOS name,
+                * but isn't a FQDN, create one.
+                */
+               if (strlen(server) > 15 && strstr(server, ".") == NULL) {
+                       char *dnsdomain;
 
-               SAFE_FREE(server);
-               SAFE_FREE(server_realm);
+                       dnsdomain = strlower_talloc(frame, ads->server.realm);
+                       if (dnsdomain == NULL) {
+                               goto out;
+                       }
 
-               if (!p->string) {
-                       return ADS_ERROR(LDAP_NO_MEMORY);
+                       server = talloc_asprintf(frame,
+                                                "%s.%s",
+                                                server, dnsdomain);
+                       if (server == NULL) {
+                               goto out;
+                       }
                }
        } else if (ads->config.realm && ads->config.ldap_server_name) {
-               char *server, *server_realm;
-
-               server = SMB_STRDUP(ads->config.ldap_server_name);
-               server_realm = SMB_STRDUP(ads->config.realm);
+               server = strlower_talloc(frame, ads->config.ldap_server_name);
+               if (server == NULL) {
+                       goto out;
+               }
 
-               if (!server || !server_realm) {
-                       return ADS_ERROR(LDAP_NO_MEMORY);
+               realm = strupper_talloc(frame, ads->config.realm);
+               if (realm == NULL) {
+                       goto out;
                }
 
-               strlower_m(server);
-               strupper_m(server_realm);
-               asprintf(&p->string, "ldap/%s@%s", server, server_realm);
+               /*
+                * If we got a name which is bigger than a NetBIOS name,
+                * but isn't a FQDN, create one.
+                */
+               if (strlen(server) > 15 && strstr(server, ".") == NULL) {
+                       char *dnsdomain;
 
-               SAFE_FREE(server);
-               SAFE_FREE(server_realm);
+                       dnsdomain = strlower_talloc(frame, ads->server.realm);
+                       if (dnsdomain == NULL) {
+                               goto out;
+                       }
 
-               if (!p->string) {
-                       return ADS_ERROR(LDAP_NO_MEMORY);
+                       server = talloc_asprintf(frame,
+                                                "%s.%s",
+                                                server, dnsdomain);
+                       if (server == NULL) {
+                               goto out;
+                       }
                }
        }
 
-       initialize_krb5_error_table();
-       status = ADS_ERROR_KRB5(krb5_init_context(&p->ctx));
-       if (!ADS_ERR_OK(status)) {
-               ads_free_service_principal(p);
-               return status;
+       if (server == NULL || realm == NULL) {
+               goto out;
        }
-       status = ADS_ERROR_KRB5(krb5_set_default_tgs_ktypes(p->ctx, enc_types));
-       if (!ADS_ERR_OK(status)) {
-               ads_free_service_principal(p);
-               return status;
+
+       *service = SMB_STRDUP("ldap");
+       if (*service == NULL) {
+               status = ADS_ERROR(LDAP_PARAM_ERROR);
+               goto out;
        }
-       status = ADS_ERROR_KRB5(smb_krb5_parse_name(p->ctx, p->string, &p->principal));
-       if (!ADS_ERR_OK(status)) {
-               ads_free_service_principal(p);
-               return status;
+       *hostname = SMB_STRDUP(server);
+       if (*hostname == NULL) {
+               SAFE_FREE(*service);
+               status = ADS_ERROR(LDAP_PARAM_ERROR);
+               goto out;
        }
-
-#ifdef HAVE_GSSAPI
-       /*
-        * 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.
-        *
-        * That's the reason for principal not beeing a local var in this function
-        */
-       input_name.value = &p->principal;
-       input_name.length = sizeof(p->principal);
-
-       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);
+       rc = asprintf(&princ, "ldap/%s@%s", server, realm);
+       if (rc == -1 || princ == NULL) {
+               SAFE_FREE(*service);
+               SAFE_FREE(*hostname);
+               status = ADS_ERROR(LDAP_PARAM_ERROR);
+               goto out;
        }
-#endif
 
+       *principal = princ;
+
+       status = ADS_SUCCESS;
+out:
+       TALLOC_FREE(frame);
        return status;
 }
 
-/* 
-   perform a LDAP/SASL/SPNEGO/KRB5 bind
-*/
-static ADS_STATUS ads_sasl_spnego_rawkrb5_bind(ADS_STRUCT *ads, const char *principal)
+static ADS_STATUS ads_generate_service_principal(ADS_STRUCT *ads,
+                                                struct ads_service_principal *p)
 {
-       DATA_BLOB blob = data_blob_null;
-       struct berval cred, *scred = NULL;
-       DATA_BLOB session_key = data_blob_null;
-       int rc;
-
-       if (ads->ldap.wrap_type > ADS_SASLWRAP_TYPE_PLAIN) {
-               return ADS_ERROR_NT(NT_STATUS_NOT_SUPPORTED);
-       }
+       ADS_STATUS status;
+#ifdef HAVE_KRB5
+       gss_buffer_desc input_name;
+       /* GSS_KRB5_NT_PRINCIPAL_NAME */
+       gss_OID_desc nt_principal =
+       {10, discard_const_p(char, "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02\x01")};
+       uint32_t minor_status;
+       int gss_rc;
+#endif
 
-       rc = spnego_gen_negTokenTarg(principal, ads->auth.time_offset, &blob, &session_key, 0,
-                                    &ads->auth.tgs_expire);
+       ZERO_STRUCTP(p);
 
-       if (rc) {
-               return ADS_ERROR_KRB5(rc);
+       status = ads_guess_target(ads,
+                                 &p->service,
+                                 &p->hostname,
+                                 &p->string);
+       if (!ADS_ERR_OK(status)) {
+               return status;
        }
 
-       /* now send the auth packet and we should be done */
-       cred.bv_val = (char *)blob.data;
-       cred.bv_len = blob.length;
-
-       rc = ldap_sasl_bind_s(ads->ldap.ld, NULL, "GSS-SPNEGO", &cred, NULL, NULL, &scred);
-
-       data_blob_free(&blob);
-       data_blob_free(&session_key);
-       if(scred)
-               ber_bvfree(scred);
-
-       return ADS_ERROR(rc);
-}
+#ifdef HAVE_KRB5
+       input_name.value = p->string;
+       input_name.length = strlen(p->string);
 
-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);
+       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_sasl_spnego_rawkrb5_bind(ads, p->string);
+
+       return ADS_SUCCESS;
 }
-#endif
+
+#endif /* HAVE_KRB5 */
 
 /* 
    this performs a SASL/SPNEGO bind
 */
 static ADS_STATUS ads_sasl_spnego_bind(ADS_STRUCT *ads)
 {
-       struct berval *scred=NULL;
-       int rc, i;
+       TALLOC_CTX *frame = talloc_stackframe();
+       struct ads_service_principal p = {0};
        ADS_STATUS status;
-       DATA_BLOB blob;
-       char *given_principal = NULL;
-       char *OIDs[ASN1_MAX_OIDS];
-#ifdef HAVE_KRB5
-       BOOL got_kerberos_mechanism = False;
-#endif
+       enum credentials_use_kerberos krb5_state;
+       struct cli_credentials *creds;
 
-       rc = ldap_sasl_bind_s(ads->ldap.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);
-
-       ber_bvfree(scred);
-
-#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, &given_principal)) {
-               data_blob_free(&blob);
-               status = ADS_ERROR(LDAP_OPERATIONS_ERROR);
-               goto failed;
+       if (ads->auth.flags & ADS_AUTH_DISABLE_KERBEROS) {
+               krb5_state = CRED_DONT_USE_KERBEROS;
+       } else if (ads->auth.flags & ADS_AUTH_ALLOW_NTLMSSP) {
+               krb5_state = CRED_AUTO_USE_KERBEROS;
+       } else {
+               krb5_state = CRED_MUST_USE_KERBEROS;
        }
-       data_blob_free(&blob);
 
-       /* make sure the server understands kerberos */
-       for (i=0;OIDs[i];i++) {
-               DEBUG(3,("ads_sasl_spnego_bind: got OID=%s\n", OIDs[i]));
-#ifdef HAVE_KRB5
-               if (strcmp(OIDs[i], OID_KERBEROS5_OLD) == 0 ||
-                   strcmp(OIDs[i], OID_KERBEROS5) == 0) {
-                       got_kerberos_mechanism = True;
-               }
-#endif
-               free(OIDs[i]);
+       status = ads_generate_service_principal(ads, &p);
+       if (!ADS_ERR_OK(status)) {
+               goto done;
        }
-       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) 
-       {
-               struct ads_service_principal p;
-
-               status = ads_generate_service_principal(ads, given_principal, &p);
-               SAFE_FREE(given_principal);
-               if (!ADS_ERR_OK(status)) {
-                       return status;
-               }
-
-               status = ads_sasl_spnego_krb5_bind(ads, &p);
-               if (ADS_ERR_OK(status)) {
-                       ads_free_service_principal(&p);
-                       return status;
-               }
-
-               DEBUG(10,("ads_sasl_spnego_krb5_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_spnego_krb5_bind(ads, &p);
-               }
-
-               ads_free_service_principal(&p);
-
-               /* only fallback to NTLMSSP if allowed */
-               if (ADS_ERR_OK(status) || 
-                   !(ads->auth.flags & ADS_AUTH_ALLOW_NTLMSSP)) {
-                       return status;
-               }
-       } else
-#endif
-       {
-               SAFE_FREE(given_principal);
+       creds = ads_sasl_creds_init(frame,
+                                   ads->auth.user_name,
+                                   ads->auth.password,
+                                   ads->auth.realm,
+                                   ads->auth.realm,
+                                   krb5_state);
+       if (creds == NULL) {
+               status = ADS_ERROR_SYSTEM(ENOMEM);
+               goto done;
        }
 
-       /* 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);
+       status = ads_sasl_spnego_gensec_bind(ads,
+                                            "GSS-SPNEGO",
+                                            creds,
+                                            p.service,
+                                            p.hostname,
+                                            data_blob_null);
 
-failed:
+done:
+       if (!ADS_ERR_OK(status)) {
+               DEBUG(1,("ads_sasl_spnego_gensec_bind(GSS-SPNEGO) failed "
+                        "for %s/%s with user[%s] realm=[%s]: %s\n",
+                         p.service, p.hostname,
+                         ads->auth.user_name,
+                         ads->auth.realm,
+                         ads_errstr(status)));
+       }
+       ads_free_service_principal(&p);
+       TALLOC_FREE(frame);
        return status;
 }
 
-#ifdef HAVE_GSSAPI
-#define MAX_GSS_PASSES 3
+#ifdef HAVE_KRB5
 
-/* 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 and RFC2222 for details
-*/
 static ADS_STATUS ads_sasl_gssapi_bind(ADS_STRUCT *ads)
 {
-       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 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;
+       TALLOC_CTX *frame = talloc_stackframe();
        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);
-
-       /*
-        * 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.
-        */
-
-       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;
-       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;
-       }
+       struct ads_service_principal p;
+       struct cli_credentials *creds;
 
-       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,
-                                         req_flags,
-                                         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) {
-                       status = ADS_ERROR_GSS(gss_rc, minor_status);
-                       goto failed;
-               }
-
-               cred.bv_val = (char *)output_token.value;
-               cred.bv_len = output_token.length;
-
-               rc = ldap_sasl_bind_s(ads->ldap.ld, NULL, "GSSAPI", &cred, NULL, NULL, 
-                                     &scred);
-               if (rc != LDAP_SASL_BIND_IN_PROGRESS) {
-                       status = ADS_ERROR(rc);
-                       goto failed;
-               }
-
-               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 == 0) break;
-       }
-
-       gss_rc = gss_unwrap(&minor_status,context_handle,&input_token,&output_token,
-                           &conf_state,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
-       file_save("sasl_gssapi.dat", output_token.value, output_token.length);
-#endif
-
-       if (p) {
-               max_msg_size = (p[1]<<16) | (p[2]<<8) | p[3];
-       }
-
-       gss_release_buffer(&minor_status, &output_token);
-
-       output_token.length = 4;
-       output_token.value = SMB_MALLOC(output_token.length);
-       p = (uint8 *)output_token.value;
-
-       *p++ = ads->ldap.wrap_type;
-       /* choose the same size as the server gave us */
-       *p++ = max_msg_size>>16;
-       *p++ = max_msg_size>>8;
-       *p++ = max_msg_size;
-       /*
-        * we used to add sprintf("dn:%s", ads->config.bind_path) here.
-        * but using ads->config.bind_path is the wrong! It should be
-        * the DN of the user object!
-        *
-        * w2k3 gives an error when we send an incorrect DN, but sending nothing
-        * is ok and matches the information flow used in GSS-SPNEGO.
-        */
-
-       gss_rc = gss_wrap(&minor_status, context_handle,0,GSS_C_QOP_DEFAULT,
-                         &output_token, &conf_state,
-                         &input_token);
-       if (gss_rc) {
-               status = ADS_ERROR_GSS(gss_rc, minor_status);
-               goto failed;
-       }
-
-       free(output_token.value);
-
-       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);
+       status = ads_generate_service_principal(ads, &p);
        if (!ADS_ERR_OK(status)) {
-               goto failed;
-       }
-
-       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);
-               if (gss_rc) {
-                       status = ADS_ERROR_GSS(gss_rc, minor_status);
-                       goto failed;
-               }
-
-               ads->ldap.out.min = 4;
-               ads->ldap.out.sig_size = max_msg_size - ads->ldap.out.max;
-               ads->ldap.in.min = 4;
-               ads->ldap.in.max = max_msg_size;
-               ads_setup_sasl_wrapping(ads, &ads_sasl_gssapi_ops, context_handle);
-               /* 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);
+               goto done;
+       }
+
+       creds = ads_sasl_creds_init(frame,
+                                   ads->auth.user_name,
+                                   ads->auth.password,
+                                   ads->auth.realm,
+                                   ads->auth.realm,
+                                   CRED_MUST_USE_KERBEROS);
+       if (creds == NULL) {
+               status = ADS_ERROR_SYSTEM(ENOMEM);
+               goto done;
+       }
+
+       status = ads_sasl_spnego_gensec_bind(ads,
+                                            "GSSAPI",
+                                            creds,
+                                            p.service,
+                                            p.hostname,
+                                            data_blob_null);
+
+done:
+       ads_free_service_principal(&p);
+       TALLOC_FREE(frame);
        return status;
 }
-#endif /* HAVE_GGSAPI */
+
+#endif /* HAVE_KRB5 */
 
 /* mapping between SASL mechanisms and functions */
 static struct {
@@ -1140,7 +687,7 @@ static struct {
        ADS_STATUS (*fn)(ADS_STRUCT *);
 } sasl_mechanisms[] = {
        {"GSS-SPNEGO", ads_sasl_spnego_bind},
-#ifdef HAVE_GSSAPI
+#ifdef HAVE_KRB5
        {"GSSAPI", ads_sasl_gssapi_bind}, /* doesn't work with .NET RC1. No idea why */
 #endif
        {NULL, NULL}
@@ -1153,6 +700,7 @@ ADS_STATUS ads_sasl_bind(ADS_STRUCT *ads)
        ADS_STATUS status;
        int i, j;
        LDAPMessage *res;
+       struct ads_saslwrap *wrap = &ads->ldap_wrap_data;
 
        /* get a list of supported SASL mechanisms */
        status = ads_do_search(ads, "", LDAP_SCOPE_BASE, "(objectclass=*)", attrs, &res);
@@ -1161,11 +709,11 @@ 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;
+               wrap->wrap_type = ADS_SASLWRAP_TYPE_SEAL;
        } else if (ads->auth.flags & ADS_AUTH_SASL_SIGN) {
-               ads->ldap.wrap_type = ADS_SASLWRAP_TYPE_SIGN;
+               wrap->wrap_type = ADS_SASLWRAP_TYPE_SIGN;
        } else {
-               ads->ldap.wrap_type = ADS_SASLWRAP_TYPE_PLAIN;
+               wrap->wrap_type = ADS_SASLWRAP_TYPE_PLAIN;
        }
 
        /* try our supported mechanisms in order */
@@ -1174,7 +722,17 @@ ADS_STATUS ads_sasl_bind(ADS_STRUCT *ads)
                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 &&
+                                   wrap->wrap_type == ADS_SASLWRAP_TYPE_PLAIN)
+                               {
+                                       DEBUG(3,("SASL bin got LDAP_STRONG_AUTH_REQUIRED "
+                                                "retrying with signing enabled\n"));
+                                       wrap->wrap_type = ADS_SASLWRAP_TYPE_SIGN;
+                                       goto retry;
+                               }
                                ldap_value_free(values);
                                ldap_msgfree(res);
                                return status;