s3:libsmb: don't finish the gensec handshake for guest logins
[samba.git] / source3 / libsmb / cliconnect.c
index 36970cc4dbd644dcf7e6f5e33e4064e59cd9afa5..c4ac605396cd67253b9a2a256f2d988211c76e06 100644 (file)
 #include "../libcli/auth/libcli_auth.h"
 #include "../libcli/auth/spnego.h"
 #include "smb_krb5.h"
-#include "../auth/ntlmssp/ntlmssp.h"
+#include "auth/credentials/credentials.h"
+#include "auth/gensec/gensec.h"
+#include "auth/ntlmssp/ntlmssp.h"
+#include "auth_generic.h"
 #include "libads/kerberos_proto.h"
 #include "krb5_env.h"
 #include "../lib/util/tevent_ntstatus.h"
@@ -34,7 +37,6 @@
 #include "libsmb/nmblib.h"
 #include "librpc/ndr/libndr.h"
 #include "../libcli/smb/smbXcli_base.h"
-#include "smb2cli.h"
 
 #define STAR_SMBSERVER "*SMBSERVER"
 
@@ -240,7 +242,7 @@ static void cli_session_setup_lanman2_done(struct tevent_req *subreq)
        p = bytes;
 
        cli_state_set_uid(state->cli, SVAL(inhdr, HDR_UID));
-       cli->is_guestlogin = ((SVAL(vwv+2, 0) & 1) != 0);
+       smb1cli_session_set_action(cli->smb1.session, SVAL(vwv+2, 0));
 
        status = smb_bytes_talloc_string(cli,
                                        inhdr,
@@ -448,7 +450,7 @@ static void cli_session_setup_guest_done(struct tevent_req *subreq)
        p = bytes;
 
        cli_state_set_uid(state->cli, SVAL(inhdr, HDR_UID));
-       cli->is_guestlogin = ((SVAL(vwv+2, 0) & 1) != 0);
+       smb1cli_session_set_action(cli->smb1.session, SVAL(vwv+2, 0));
 
        status = smb_bytes_talloc_string(cli,
                                        inhdr,
@@ -613,7 +615,7 @@ static void cli_session_setup_plain_done(struct tevent_req *subreq)
        p = bytes;
 
        cli_state_set_uid(state->cli, SVAL(inhdr, HDR_UID));
-       cli->is_guestlogin = ((SVAL(vwv+2, 0) & 1) != 0);
+       smb1cli_session_set_action(cli->smb1.session, SVAL(vwv+2, 0));
 
        status = smb_bytes_talloc_string(cli,
                                        inhdr,
@@ -930,7 +932,7 @@ static void cli_session_setup_nt1_done(struct tevent_req *subreq)
        p = bytes;
 
        cli_state_set_uid(state->cli, SVAL(inhdr, HDR_UID));
-       cli->is_guestlogin = ((SVAL(vwv+2, 0) & 1) != 0);
+       smb1cli_session_set_action(cli->smb1.session, SVAL(vwv+2, 0));
 
        status = smb_bytes_talloc_string(cli,
                                        inhdr,
@@ -1180,7 +1182,7 @@ static void cli_sesssetup_blob_done(struct tevent_req *subreq)
        state->inbuf = in;
        inhdr = in + NBT_HDR_SIZE;
        cli_state_set_uid(state->cli, SVAL(inhdr, HDR_UID));
-       cli->is_guestlogin = ((SVAL(vwv+2, 0) & 1) != 0);
+       smb1cli_session_set_action(cli->smb1.session, SVAL(vwv+2, 0));
 
        blob_length = SVAL(vwv+3, 0);
        if (blob_length > num_bytes) {
@@ -1288,370 +1290,439 @@ static void use_in_memory_ccache(void) {
        setenv(KRB5_ENV_CCNAME, "MEMORY:cliconnect", 1);
 }
 
+#endif /* HAVE_KRB5 */
+
 /****************************************************************************
- Do a spnego/kerberos encrypted session setup.
+ Do a spnego/NTLMSSP encrypted session setup.
 ****************************************************************************/
 
-struct cli_session_setup_kerberos_state {
+struct cli_session_setup_gensec_state {
+       struct tevent_context *ev;
        struct cli_state *cli;
-       DATA_BLOB negTokenTarg;
-       DATA_BLOB session_key_krb5;
-       ADS_STATUS ads_status;
+       struct auth_generic_state *auth_generic;
+       bool is_anonymous;
+       DATA_BLOB blob_in;
+       uint8_t *inbuf;
+       struct iovec *recv_iov;
+       DATA_BLOB blob_out;
+       bool local_ready;
+       bool remote_ready;
+       DATA_BLOB session_key;
 };
 
-static void cli_session_setup_kerberos_done(struct tevent_req *subreq);
+static int cli_session_setup_gensec_state_destructor(
+       struct cli_session_setup_gensec_state *state)
+{
+       TALLOC_FREE(state->auth_generic);
+       data_blob_clear_free(&state->session_key);
+       return 0;
+}
+
+static void cli_session_setup_gensec_local_next(struct tevent_req *req);
+static void cli_session_setup_gensec_local_done(struct tevent_req *subreq);
+static void cli_session_setup_gensec_remote_next(struct tevent_req *req);
+static void cli_session_setup_gensec_remote_done(struct tevent_req *subreq);
+static void cli_session_setup_gensec_ready(struct tevent_req *req);
 
-static struct tevent_req *cli_session_setup_kerberos_send(
+static struct tevent_req *cli_session_setup_gensec_send(
        TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct cli_state *cli,
-       const char *principal)
+       const char *user, const char *pass, const char *domain,
+       enum credentials_use_kerberos krb5_state,
+       const char *target_service,
+       const char *target_hostname,
+       const char *target_principal)
 {
-       struct tevent_req *req, *subreq;
-       struct cli_session_setup_kerberos_state *state;
-       int rc;
-
-       DEBUG(2,("Doing kerberos session setup\n"));
+       struct tevent_req *req;
+       struct cli_session_setup_gensec_state *state;
+       NTSTATUS status;
+       bool use_spnego_principal = lp_client_use_spnego_principal();
 
        req = tevent_req_create(mem_ctx, &state,
-                               struct cli_session_setup_kerberos_state);
+                               struct cli_session_setup_gensec_state);
        if (req == NULL) {
                return NULL;
        }
+       state->ev = ev;
        state->cli = cli;
-       state->ads_status = ADS_SUCCESS;
 
-       /*
-        * Ok, this is cheating: spnego_gen_krb5_negTokenInit can block if
-        * we have to acquire a ticket. To be fixed later :-)
-        */
-       rc = spnego_gen_krb5_negTokenInit(state, principal, 0, &state->negTokenTarg,
-                                    &state->session_key_krb5, 0, NULL, NULL);
-       if (rc) {
-               DEBUG(1, ("cli_session_setup_kerberos: "
-                         "spnego_gen_krb5_negTokenInit failed: %s\n",
-                         error_message(rc)));
-               state->ads_status = ADS_ERROR_KRB5(rc);
-               tevent_req_nterror(req, NT_STATUS_UNSUCCESSFUL);
-               return tevent_req_post(req, ev);
+       talloc_set_destructor(
+               state, cli_session_setup_gensec_state_destructor);
+
+       if (user == NULL || strlen(user) == 0) {
+               if (pass != NULL && strlen(pass) == 0) {
+                       /*
+                        * some callers pass "" as no password
+                        *
+                        * gensec only handles NULL as no password.
+                        */
+                       pass = NULL;
+               }
        }
 
-#if 0
-       file_save("negTokenTarg.dat", state->negTokenTarg.data,
-                 state->negTokenTarg.length);
-#endif
+       status = auth_generic_client_prepare(state, &state->auth_generic);
+       if (tevent_req_nterror(req, status)) {
+               return tevent_req_post(req, ev);
+       }
 
-       if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_SMB2_02) {
-               state->cli->smb2.session = smbXcli_session_create(cli,
-                                                                 cli->conn);
-               if (tevent_req_nomem(state->cli->smb2.session, req)) {
-                       return tevent_req_post(req, ev);
+       gensec_want_feature(state->auth_generic->gensec_security,
+                           GENSEC_FEATURE_SESSION_KEY);
+       if (cli->use_ccache) {
+               gensec_want_feature(state->auth_generic->gensec_security,
+                                   GENSEC_FEATURE_NTLM_CCACHE);
+               if (pass != NULL && strlen(pass) == 0) {
+                       /*
+                        * some callers pass "" as no password
+                        *
+                        * GENSEC_FEATURE_NTLM_CCACHE only handles
+                        * NULL as no password.
+                        */
+                       pass = NULL;
                }
        }
 
-       subreq = cli_sesssetup_blob_send(state, ev, cli, state->negTokenTarg);
-       if (tevent_req_nomem(subreq, req)) {
+       status = auth_generic_set_username(state->auth_generic, user);
+       if (tevent_req_nterror(req, status)) {
                return tevent_req_post(req, ev);
        }
-       tevent_req_set_callback(subreq, cli_session_setup_kerberos_done, req);
-       return req;
-}
 
-static void cli_session_setup_kerberos_done(struct tevent_req *subreq)
-{
-       struct tevent_req *req = tevent_req_callback_data(
-               subreq, struct tevent_req);
-       struct cli_session_setup_kerberos_state *state = tevent_req_data(
-               req, struct cli_session_setup_kerberos_state);
-       uint8_t *inbuf = NULL;
-       struct iovec *recv_iov = NULL;
-       NTSTATUS status;
+       status = auth_generic_set_domain(state->auth_generic, domain);
+       if (tevent_req_nterror(req, status)) {
+               return tevent_req_post(req, ev);
+       }
 
-       status = cli_sesssetup_blob_recv(subreq, state,
-                                        NULL, &inbuf, &recv_iov);
-       TALLOC_FREE(subreq);
-       if (!NT_STATUS_IS_OK(status)) {
-               tevent_req_nterror(req, status);
-               return;
+       if (cli->pw_nt_hash) {
+               struct samr_Password nt_hash;
+               size_t converted;
+               bool ok;
+
+               if (pass == NULL) {
+                       tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER_MIX);
+                       return tevent_req_post(req, ev);
+               }
+
+               converted = strhex_to_str((char *)nt_hash.hash,
+                                         sizeof(nt_hash.hash),
+                                         pass, strlen(pass));
+               if (converted != sizeof(nt_hash.hash)) {
+                       tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER_MIX);
+                       return tevent_req_post(req, ev);
+               }
+
+               ok = cli_credentials_set_nt_hash(state->auth_generic->credentials,
+                                                &nt_hash, CRED_SPECIFIED);
+               if (!ok) {
+                       tevent_req_oom(req);
+                       return tevent_req_post(req, ev);
+               }
+       } else {
+               status = auth_generic_set_password(state->auth_generic, pass);
+               if (tevent_req_nterror(req, status)) {
+                       return tevent_req_post(req, ev);
+               }
        }
 
-       if (smbXcli_conn_protocol(state->cli->conn) >= PROTOCOL_SMB2_02) {
-               struct smbXcli_session *session = state->cli->smb2.session;
-               status = smb2cli_session_set_session_key(session,
-                                               state->session_key_krb5,
-                                               recv_iov);
+       cli_credentials_set_kerberos_state(state->auth_generic->credentials,
+                                          krb5_state);
+
+       if (krb5_state == CRED_DONT_USE_KERBEROS) {
+               use_spnego_principal = false;
+       }
+
+       if (target_service != NULL) {
+               status = gensec_set_target_service(
+                               state->auth_generic->gensec_security,
+                               target_service);
                if (tevent_req_nterror(req, status)) {
-                       return;
+                       return tevent_req_post(req, ev);
                }
-       } else {
-               struct smbXcli_session *session = state->cli->smb1.session;
+       }
 
-               status = smb1cli_session_set_session_key(session,
-                                                        state->session_key_krb5);
+       if (target_hostname != NULL) {
+               status = gensec_set_target_hostname(
+                               state->auth_generic->gensec_security,
+                               target_hostname);
                if (tevent_req_nterror(req, status)) {
-                       return;
+                       return tevent_req_post(req, ev);
                }
+       }
 
-               if (smb1cli_conn_activate_signing(state->cli->conn, state->session_key_krb5,
-                                          data_blob_null)
-                   && !smb1cli_conn_check_signing(state->cli->conn, inbuf, 1)) {
-                       tevent_req_nterror(req, NT_STATUS_ACCESS_DENIED);
-                       return;
+       if (target_principal != NULL) {
+               status = gensec_set_target_principal(
+                               state->auth_generic->gensec_security,
+                               target_principal);
+               if (tevent_req_nterror(req, status)) {
+                       return tevent_req_post(req, ev);
                }
+               use_spnego_principal = false;
+       } else if (target_service != NULL && target_hostname != NULL) {
+               use_spnego_principal = false;
        }
 
-       tevent_req_done(req);
-}
+       if (use_spnego_principal) {
+               const DATA_BLOB *b;
+               b = smbXcli_conn_server_gss_blob(cli->conn);
+               if (b != NULL) {
+                       state->blob_in = *b;
+               }
+       }
 
-static ADS_STATUS cli_session_setup_kerberos_recv(struct tevent_req *req)
-{
-       struct cli_session_setup_kerberos_state *state = tevent_req_data(
-               req, struct cli_session_setup_kerberos_state);
-       NTSTATUS status;
+       state->is_anonymous = cli_credentials_is_anonymous(state->auth_generic->credentials);
 
-       if (tevent_req_is_nterror(req, &status)) {
-               return ADS_ERROR_NT(status);
+       status = auth_generic_client_start(state->auth_generic,
+                                          GENSEC_OID_SPNEGO);
+       if (tevent_req_nterror(req, status)) {
+               return tevent_req_post(req, ev);
        }
-       return state->ads_status;
-}
 
-#endif /* HAVE_KRB5 */
+       if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_SMB2_02) {
+               state->cli->smb2.session = smbXcli_session_create(cli,
+                                                                 cli->conn);
+               if (tevent_req_nomem(state->cli->smb2.session, req)) {
+                       return tevent_req_post(req, ev);
+               }
+       }
 
-/****************************************************************************
- Do a spnego/NTLMSSP encrypted session setup.
-****************************************************************************/
+       cli_session_setup_gensec_local_next(req);
+       if (!tevent_req_is_in_progress(req)) {
+               return tevent_req_post(req, ev);
+       }
 
-struct cli_session_setup_ntlmssp_state {
-       struct tevent_context *ev;
-       struct cli_state *cli;
-       struct ntlmssp_state *ntlmssp_state;
-       int turn;
-       DATA_BLOB blob_out;
-};
+       return req;
+}
 
-static int cli_session_setup_ntlmssp_state_destructor(
-       struct cli_session_setup_ntlmssp_state *state)
+static void cli_session_setup_gensec_local_next(struct tevent_req *req)
 {
-       if (state->ntlmssp_state != NULL) {
-               TALLOC_FREE(state->ntlmssp_state);
+       struct cli_session_setup_gensec_state *state =
+               tevent_req_data(req,
+               struct cli_session_setup_gensec_state);
+       struct tevent_req *subreq = NULL;
+
+       if (state->local_ready) {
+               tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE);
+               return;
        }
-       return 0;
-}
 
-static void cli_session_setup_ntlmssp_done(struct tevent_req *req);
+       subreq = gensec_update_send(state, state->ev,
+                       state->auth_generic->gensec_security,
+                       state->blob_in);
+       if (tevent_req_nomem(subreq, req)) {
+               return;
+       }
+       tevent_req_set_callback(subreq, cli_session_setup_gensec_local_done, req);
+}
 
-static struct tevent_req *cli_session_setup_ntlmssp_send(
-       TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct cli_state *cli,
-       const char *user, const char *pass, const char *domain)
+static void cli_session_setup_gensec_local_done(struct tevent_req *subreq)
 {
-       struct tevent_req *req, *subreq;
-       struct cli_session_setup_ntlmssp_state *state;
+       struct tevent_req *req =
+               tevent_req_callback_data(subreq,
+               struct tevent_req);
+       struct cli_session_setup_gensec_state *state =
+               tevent_req_data(req,
+               struct cli_session_setup_gensec_state);
        NTSTATUS status;
-       DATA_BLOB blob_out;
-       const char *OIDs_ntlm[] = {OID_NTLMSSP, NULL};
 
-       req = tevent_req_create(mem_ctx, &state,
-                               struct cli_session_setup_ntlmssp_state);
-       if (req == NULL) {
-               return NULL;
+       status = gensec_update_recv(subreq, state, &state->blob_out);
+       TALLOC_FREE(subreq);
+       state->blob_in = data_blob_null;
+       if (!NT_STATUS_IS_OK(status) &&
+           !NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED))
+       {
+               tevent_req_nterror(req, status);
+               return;
        }
-       state->ev = ev;
-       state->cli = cli;
-       state->turn = 1;
-
-       state->ntlmssp_state = NULL;
-       talloc_set_destructor(
-               state, cli_session_setup_ntlmssp_state_destructor);
 
-       status = ntlmssp_client_start(state,
-                                     lp_netbios_name(),
-                                     lp_workgroup(),
-                                     lp_client_ntlmv2_auth(),
-                                     &state->ntlmssp_state);
-       if (!NT_STATUS_IS_OK(status)) {
-               goto fail;
-       }
-       ntlmssp_want_feature(state->ntlmssp_state,
-                            NTLMSSP_FEATURE_SESSION_KEY);
-       if (cli->use_ccache) {
-               ntlmssp_want_feature(state->ntlmssp_state,
-                                    NTLMSSP_FEATURE_CCACHE);
-       }
-       status = ntlmssp_set_username(state->ntlmssp_state, user);
-       if (!NT_STATUS_IS_OK(status)) {
-               goto fail;
-       }
-       status = ntlmssp_set_domain(state->ntlmssp_state, domain);
-       if (!NT_STATUS_IS_OK(status)) {
-               goto fail;
-       }
-       if (cli->pw_nt_hash) {
-               status = ntlmssp_set_password_hash(state->ntlmssp_state, pass);
-       } else {
-               status = ntlmssp_set_password(state->ntlmssp_state, pass);
-       }
-       if (!NT_STATUS_IS_OK(status)) {
-               goto fail;
+       if (NT_STATUS_IS_OK(status)) {
+               state->local_ready = true;
        }
-       status = ntlmssp_update(state->ntlmssp_state, data_blob_null,
-                               &blob_out);
-       if (!NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
-               goto fail;
+
+       if (state->local_ready && state->remote_ready) {
+               cli_session_setup_gensec_ready(req);
+               return;
        }
 
-       state->blob_out = spnego_gen_negTokenInit(state, OIDs_ntlm, &blob_out, NULL);
-       data_blob_free(&blob_out);
+       cli_session_setup_gensec_remote_next(req);
+}
 
-       if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_SMB2_02) {
-               state->cli->smb2.session = smbXcli_session_create(cli,
-                                                                 cli->conn);
-               if (tevent_req_nomem(state->cli->smb2.session, req)) {
-                       return tevent_req_post(req, ev);
-               }
+static void cli_session_setup_gensec_remote_next(struct tevent_req *req)
+{
+       struct cli_session_setup_gensec_state *state =
+               tevent_req_data(req,
+               struct cli_session_setup_gensec_state);
+       struct tevent_req *subreq = NULL;
+
+       if (state->remote_ready) {
+               tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE);
+               return;
        }
 
-       subreq = cli_sesssetup_blob_send(state, ev, cli, state->blob_out);
+       subreq = cli_sesssetup_blob_send(state, state->ev,
+                                        state->cli, state->blob_out);
        if (tevent_req_nomem(subreq, req)) {
-               return tevent_req_post(req, ev);
+               return;
        }
-       tevent_req_set_callback(subreq, cli_session_setup_ntlmssp_done, req);
-       return req;
-fail:
-       tevent_req_nterror(req, status);
-       return tevent_req_post(req, ev);
+       tevent_req_set_callback(subreq,
+                               cli_session_setup_gensec_remote_done,
+                               req);
 }
 
-static void cli_session_setup_ntlmssp_done(struct tevent_req *subreq)
+static void cli_session_setup_gensec_remote_done(struct tevent_req *subreq)
 {
-       struct tevent_req *req = tevent_req_callback_data(
-               subreq, struct tevent_req);
-       struct cli_session_setup_ntlmssp_state *state = tevent_req_data(
-               req, struct cli_session_setup_ntlmssp_state);
-       DATA_BLOB blob_in, msg_in, blob_out;
-       uint8_t *inbuf = NULL;
-       struct iovec *recv_iov = NULL;
-       bool parse_ret;
+       struct tevent_req *req =
+               tevent_req_callback_data(subreq,
+               struct tevent_req);
+       struct cli_session_setup_gensec_state *state =
+               tevent_req_data(req,
+               struct cli_session_setup_gensec_state);
        NTSTATUS status;
 
-       status = cli_sesssetup_blob_recv(subreq, talloc_tos(), &blob_in,
-                                        &inbuf, &recv_iov);
+       TALLOC_FREE(state->inbuf);
+       TALLOC_FREE(state->recv_iov);
+
+       status = cli_sesssetup_blob_recv(subreq, state, &state->blob_in,
+                                        &state->inbuf, &state->recv_iov);
        TALLOC_FREE(subreq);
        data_blob_free(&state->blob_out);
+       if (!NT_STATUS_IS_OK(status) &&
+           !NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED))
+       {
+               tevent_req_nterror(req, status);
+               return;
+       }
 
        if (NT_STATUS_IS_OK(status)) {
-               if (state->cli->server_domain[0] == '\0') {
-                       TALLOC_FREE(state->cli->server_domain);
-                       state->cli->server_domain = talloc_strdup(state->cli,
-                                               state->ntlmssp_state->server.netbios_domain);
-                       if (state->cli->server_domain == NULL) {
-                               tevent_req_nterror(req, NT_STATUS_NO_MEMORY);
-                               return;
-                       }
-               }
+               struct smbXcli_session *session = NULL;
+               bool is_guest = false;
 
                if (smbXcli_conn_protocol(state->cli->conn) >= PROTOCOL_SMB2_02) {
-                       struct smbXcli_session *session = state->cli->smb2.session;
-
-                       if (ntlmssp_is_anonymous(state->ntlmssp_state)) {
-                               /*
-                                * Windows server does not set the
-                                * SMB2_SESSION_FLAG_IS_GUEST nor
-                                * SMB2_SESSION_FLAG_IS_NULL flag.
-                                *
-                                * This fix makes sure we do not try
-                                * to verify a signature on the final
-                                * session setup response.
-                                */
-                               TALLOC_FREE(state->ntlmssp_state);
-                               tevent_req_done(req);
-                               return;
-                       }
-
-                       status = smb2cli_session_set_session_key(session,
-                                               state->ntlmssp_state->session_key,
-                                               recv_iov);
-                       if (tevent_req_nterror(req, status)) {
-                               return;
-                       }
+                       session = state->cli->smb2.session;
                } else {
-                       struct smbXcli_session *session = state->cli->smb1.session;
-
-                       status = smb1cli_session_set_session_key(session,
-                                       state->ntlmssp_state->session_key);
-                       if (tevent_req_nterror(req, status)) {
-                               return;
-                       }
+                       session = state->cli->smb1.session;
+               }
 
-                       if (smb1cli_conn_activate_signing(
-                                   state->cli->conn, state->ntlmssp_state->session_key,
-                                   data_blob_null)
-                           && !smb1cli_conn_check_signing(state->cli->conn, inbuf, 1)) {
-                               tevent_req_nterror(req, NT_STATUS_ACCESS_DENIED);
-                               return;
-                       }
+               is_guest = smbXcli_session_is_guest(session);
+               if (is_guest) {
+                       /*
+                        * We can't finish the gensec handshake, we don't
+                        * have a negotiated session key.
+                        *
+                        * So just pretend we are completely done.
+                        */
+                       state->blob_in = data_blob_null;
+                       state->local_ready = true;
                }
-               TALLOC_FREE(state->ntlmssp_state);
-               tevent_req_done(req);
+
+               state->remote_ready = true;
+       }
+
+       if (state->local_ready && state->remote_ready) {
+               cli_session_setup_gensec_ready(req);
                return;
        }
-       if (!NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
-               tevent_req_nterror(req, status);
+
+       cli_session_setup_gensec_local_next(req);
+}
+
+static void cli_session_setup_gensec_ready(struct tevent_req *req)
+{
+       struct cli_session_setup_gensec_state *state =
+               tevent_req_data(req,
+               struct cli_session_setup_gensec_state);
+       const char *server_domain = NULL;
+       NTSTATUS status;
+
+       if (state->blob_in.length != 0) {
+               tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE);
                return;
        }
 
-       if (blob_in.length == 0) {
-               tevent_req_nterror(req, NT_STATUS_UNSUCCESSFUL);
+       if (state->blob_out.length != 0) {
+               tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE);
                return;
        }
 
-       if ((state->turn == 1)
-           && NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
-               DATA_BLOB tmp_blob = data_blob_null;
-               /* the server might give us back two challenges */
-               parse_ret = spnego_parse_challenge(state, blob_in, &msg_in,
-                                                  &tmp_blob);
-               data_blob_free(&tmp_blob);
-       } else {
-               parse_ret = spnego_parse_auth_response(state, blob_in, status,
-                                                      OID_NTLMSSP, &msg_in);
-       }
-       state->turn += 1;
-
-       if (!parse_ret) {
-               DEBUG(3,("Failed to parse auth response\n"));
-               if (NT_STATUS_IS_OK(status)
-                   || NT_STATUS_EQUAL(status,
-                                      NT_STATUS_MORE_PROCESSING_REQUIRED)) {
-                       tevent_req_nterror(
-                               req, NT_STATUS_INVALID_NETWORK_RESPONSE);
+       /*
+        * gensec_ntlmssp_server_domain() returns NULL
+        * if NTLMSSP is not used.
+        *
+        * We can remove this later
+        * and leave the server domain empty for SMB2 and above
+        * in future releases.
+        */
+       server_domain = gensec_ntlmssp_server_domain(
+                               state->auth_generic->gensec_security);
+
+       if (state->cli->server_domain[0] == '\0' && server_domain != NULL) {
+               TALLOC_FREE(state->cli->server_domain);
+               state->cli->server_domain = talloc_strdup(state->cli,
+                                       server_domain);
+               if (state->cli->server_domain == NULL) {
+                       tevent_req_nterror(req, NT_STATUS_NO_MEMORY);
                        return;
                }
        }
 
-       status = ntlmssp_update(state->ntlmssp_state, msg_in, &blob_out);
-
-       if (!NT_STATUS_IS_OK(status)
-           && !NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
-               TALLOC_FREE(state->ntlmssp_state);
-               tevent_req_nterror(req, status);
+       status = gensec_session_key(state->auth_generic->gensec_security,
+                                   state, &state->session_key);
+       if (tevent_req_nterror(req, status)) {
                return;
        }
 
-       state->blob_out = spnego_gen_auth(state, blob_out);
-       if (tevent_req_nomem(state->blob_out.data, req)) {
-               return;
-       }
+       if (smbXcli_conn_protocol(state->cli->conn) >= PROTOCOL_SMB2_02) {
+               struct smbXcli_session *session = state->cli->smb2.session;
 
-       subreq = cli_sesssetup_blob_send(state, state->ev, state->cli,
-                                        state->blob_out);
-       if (tevent_req_nomem(subreq, req)) {
-               return;
+               if (state->is_anonymous) {
+                       /*
+                        * Windows server does not set the
+                        * SMB2_SESSION_FLAG_IS_GUEST nor
+                        * SMB2_SESSION_FLAG_IS_NULL flag.
+                        *
+                        * This fix makes sure we do not try
+                        * to verify a signature on the final
+                        * session setup response.
+                        */
+                       tevent_req_done(req);
+                       return;
+               }
+
+               status = smb2cli_session_set_session_key(session,
+                                                        state->session_key,
+                                                        state->recv_iov);
+               if (tevent_req_nterror(req, status)) {
+                       return;
+               }
+       } else {
+               struct smbXcli_session *session = state->cli->smb1.session;
+               bool active;
+
+               status = smb1cli_session_set_session_key(session,
+                                                        state->session_key);
+               if (tevent_req_nterror(req, status)) {
+                       return;
+               }
+
+               active = smb1cli_conn_activate_signing(state->cli->conn,
+                                                      state->session_key,
+                                                      data_blob_null);
+               if (active) {
+                       bool ok;
+
+                       ok = smb1cli_conn_check_signing(state->cli->conn,
+                                                       state->inbuf, 1);
+                       if (!ok) {
+                               tevent_req_nterror(req, NT_STATUS_ACCESS_DENIED);
+                               return;
+                       }
+               }
        }
-       tevent_req_set_callback(subreq, cli_session_setup_ntlmssp_done, req);
+
+       tevent_req_done(req);
 }
 
-static NTSTATUS cli_session_setup_ntlmssp_recv(struct tevent_req *req)
+static NTSTATUS cli_session_setup_gensec_recv(struct tevent_req *req)
 {
-       struct cli_session_setup_ntlmssp_state *state = tevent_req_data(
-               req, struct cli_session_setup_ntlmssp_state);
+       struct cli_session_setup_gensec_state *state =
+               tevent_req_data(req,
+               struct cli_session_setup_gensec_state);
        NTSTATUS status;
 
        if (tevent_req_is_nterror(req, &status)) {
@@ -1670,7 +1741,7 @@ static char *cli_session_setup_get_principal(
        char *principal = NULL;
 
        if (!lp_client_use_spnego_principal() ||
-           strequal(principal, ADS_IGNORE_PRINCIPAL)) {
+           strequal(spnego_principal, ADS_IGNORE_PRINCIPAL)) {
                spnego_principal = NULL;
        }
        if (spnego_principal != NULL) {
@@ -1731,6 +1802,7 @@ static char *cli_session_setup_get_account(TALLOC_CTX *mem_ctx,
 struct cli_session_setup_spnego_state {
        struct tevent_context *ev;
        struct cli_state *cli;
+       const char *target_hostname;
        const char *user;
        const char *account;
        const char *pass;
@@ -1747,14 +1819,14 @@ static void cli_session_setup_spnego_done_ntlmssp(struct tevent_req *subreq);
 
 static struct tevent_req *cli_session_setup_spnego_send(
        TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct cli_state *cli,
-       const char *user, const char *pass, const char *user_domain,
-       const char *dest_realm)
+       const char *user, const char *pass, const char *user_domain)
 {
        struct tevent_req *req, *subreq;
        struct cli_session_setup_spnego_state *state;
        char *principal = NULL;
        char *OIDs[ASN1_MAX_OIDS];
        int i;
+       const char *dest_realm = cli_state_remote_realm(cli);
        const DATA_BLOB *server_blob;
        NTSTATUS status;
 
@@ -1775,6 +1847,7 @@ static struct tevent_req *cli_session_setup_spnego_send(
                return tevent_req_post(req, ev);
        }
 
+       state->target_hostname = smbXcli_conn_remote_name(cli->conn);
        server_blob = smbXcli_conn_server_gss_blob(cli->conn);
 
        DEBUG(3,("Doing spnego session setup (blob length=%lu)\n",
@@ -1829,9 +1902,13 @@ static struct tevent_req *cli_session_setup_spnego_send(
         * and do not store results */
 
        if (user && *user && cli->got_kerberos_mechanism && cli->use_kerberos) {
-               const char *remote_name = smbXcli_conn_remote_name(cli->conn);
                char *tmp;
 
+               tmp = cli_session_setup_get_principal(
+                       talloc_tos(), principal, state->target_hostname, dest_realm);
+               TALLOC_FREE(principal);
+               principal = tmp;
+
                if (pass && *pass) {
                        int ret;
 
@@ -1839,8 +1916,8 @@ static struct tevent_req *cli_session_setup_spnego_send(
                        ret = kerberos_kinit_password(user, pass, 0 /* no time correction for now */, NULL);
 
                        if (ret){
+                               DEBUG(0, ("Kinit for %s to access %s failed: %s\n", user, principal, error_message(ret)));
                                TALLOC_FREE(principal);
-                               DEBUG(0, ("Kinit failed: %s\n", error_message(ret)));
                                if (cli->fallback_after_kerberos)
                                        goto ntlmssp;
                                state->result = ADS_ERROR_KRB5(ret);
@@ -1849,14 +1926,12 @@ static struct tevent_req *cli_session_setup_spnego_send(
                        }
                }
 
-               tmp = cli_session_setup_get_principal(
-                       talloc_tos(), principal, remote_name, dest_realm);
-               TALLOC_FREE(principal);
-               principal = tmp;
-
                if (principal) {
-                       subreq = cli_session_setup_kerberos_send(
-                               state, ev, cli, principal);
+                       subreq = cli_session_setup_gensec_send(
+                               state, ev, cli,
+                               state->account, pass, user_domain,
+                               CRED_MUST_USE_KERBEROS,
+                               "cifs", state->target_hostname, principal);
                        if (tevent_req_nomem(subreq, req)) {
                                return tevent_req_post(req, ev);
                        }
@@ -1869,8 +1944,11 @@ static struct tevent_req *cli_session_setup_spnego_send(
 #endif
 
 ntlmssp:
-       subreq = cli_session_setup_ntlmssp_send(
-               state, ev, cli, state->account, pass, user_domain);
+       subreq = cli_session_setup_gensec_send(
+               state, state->ev, state->cli,
+               state->account, state->pass, state->user_domain,
+               CRED_DONT_USE_KERBEROS,
+               "cifs", state->target_hostname, NULL);
        if (tevent_req_nomem(subreq, req)) {
                return tevent_req_post(req, ev);
        }
@@ -1886,9 +1964,11 @@ static void cli_session_setup_spnego_done_krb(struct tevent_req *subreq)
                subreq, struct tevent_req);
        struct cli_session_setup_spnego_state *state = tevent_req_data(
                req, struct cli_session_setup_spnego_state);
+       NTSTATUS status;
 
-       state->result = cli_session_setup_kerberos_recv(subreq);
+       status = cli_session_setup_gensec_recv(subreq);
        TALLOC_FREE(subreq);
+       state->result = ADS_ERROR_NT(status);
 
        if (ADS_ERR_OK(state->result) ||
            !state->cli->fallback_after_kerberos) {
@@ -1896,9 +1976,11 @@ static void cli_session_setup_spnego_done_krb(struct tevent_req *subreq)
                return;
        }
 
-       subreq = cli_session_setup_ntlmssp_send(
-               state, state->ev, state->cli, state->account, state->pass,
-               state->user_domain);
+       subreq = cli_session_setup_gensec_send(
+               state, state->ev, state->cli,
+               state->account, state->pass, state->user_domain,
+               CRED_DONT_USE_KERBEROS,
+               "cifs", state->target_hostname, NULL);
        if (tevent_req_nomem(subreq, req)) {
                return;
        }
@@ -1915,7 +1997,7 @@ static void cli_session_setup_spnego_done_ntlmssp(struct tevent_req *subreq)
                req, struct cli_session_setup_spnego_state);
        NTSTATUS status;
 
-       status = cli_session_setup_ntlmssp_recv(subreq);
+       status = cli_session_setup_gensec_recv(subreq);
        TALLOC_FREE(subreq);
        state->result = ADS_ERROR_NT(status);
        tevent_req_done(req);
@@ -2029,10 +2111,8 @@ struct tevent_req *cli_session_setup_send(TALLOC_CTX *mem_ctx,
        }
 
        if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_SMB2_02) {
-               const char *remote_realm = cli_state_remote_realm(cli);
-
                subreq = cli_session_setup_spnego_send(
-                       state, ev, cli, user, pass, workgroup, remote_realm);
+                       state, ev, cli, user, pass, workgroup);
                if (tevent_req_nomem(subreq, req)) {
                        return tevent_req_post(req, ev);
                }
@@ -2092,10 +2172,8 @@ struct tevent_req *cli_session_setup_send(TALLOC_CTX *mem_ctx,
        /* if the server supports extended security then use SPNEGO */
 
        if (smb1cli_conn_capabilities(cli->conn) & CAP_EXTENDED_SECURITY) {
-               const char *remote_realm = cli_state_remote_realm(cli);
-
                subreq = cli_session_setup_spnego_send(
-                       state, ev, cli, user, pass, workgroup, remote_realm);
+                       state, ev, cli, user, pass, workgroup);
                if (tevent_req_nomem(subreq, req)) {
                        return tevent_req_post(req, ev);
                }
@@ -2104,6 +2182,17 @@ struct tevent_req *cli_session_setup_send(TALLOC_CTX *mem_ctx,
                return req;
        } else {
                /* otherwise do a NT1 style session setup */
+               if (lp_client_ntlmv2_auth() && lp_client_use_spnego()) {
+                       /*
+                        * Don't send an NTLMv2 response without NTLMSSP
+                        * if we want to use spnego support
+                        */
+                       DEBUG(1, ("Server does not support EXTENDED_SECURITY "
+                                 " but 'client use spnego = yes"
+                                 " and 'client ntlmv2 auth = yes'\n"));
+                       tevent_req_nterror(req, NT_STATUS_ACCESS_DENIED);
+                       return tevent_req_post(req, ev);
+               }
 
                subreq = cli_session_setup_nt1_send(
                        state, ev, cli, user, pass, passlen, ntpass, ntpasslen,
@@ -2245,7 +2334,7 @@ struct cli_ulogoff_state {
 
 static void cli_ulogoff_done(struct tevent_req *subreq);
 
-struct tevent_req *cli_ulogoff_send(TALLOC_CTX *mem_ctx,
+static struct tevent_req *cli_ulogoff_send(TALLOC_CTX *mem_ctx,
                                    struct tevent_context *ev,
                                    struct cli_state *cli)
 {
@@ -2288,7 +2377,7 @@ static void cli_ulogoff_done(struct tevent_req *subreq)
        tevent_req_done(req);
 }
 
-NTSTATUS cli_ulogoff_recv(struct tevent_req *req)
+static NTSTATUS cli_ulogoff_recv(struct tevent_req *req)
 {
        return tevent_req_simple_recv_ntstatus(req);
 }
@@ -2299,6 +2388,18 @@ NTSTATUS cli_ulogoff(struct cli_state *cli)
        struct tevent_req *req;
        NTSTATUS status = NT_STATUS_NO_MEMORY;
 
+       if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_SMB2_02) {
+               status = smb2cli_logoff(cli->conn,
+                                       cli->timeout,
+                                       cli->smb2.session);
+               if (!NT_STATUS_IS_OK(status)) {
+                       return status;
+               }
+               smb2cli_session_set_id_and_flags(cli->smb2.session,
+                                                UINT64_MAX, 0);
+               return NT_STATUS_OK;
+       }
+
        if (smbXcli_conn_has_async_calls(cli->conn)) {
                return NT_STATUS_INVALID_PARAMETER;
        }
@@ -2404,7 +2505,7 @@ struct tevent_req *cli_tcon_andx_create(TALLOC_CTX *mem_ctx,
                         * Non-encrypted passwords - convert to DOS codepage
                         * before using.
                         */
-                       tmp_pass = talloc_array(talloc_tos(), uint8, 0);
+                       tmp_pass = talloc_array(talloc_tos(), uint8_t, 0);
                        if (tevent_req_nomem(tmp_pass, req)) {
                                return tevent_req_post(req, ev);
                        }
@@ -2563,21 +2664,22 @@ static void cli_tcon_andx_done(struct tevent_req *subreq)
         * Avoids issues when connecting to Win9x boxes sharing files
         */
 
-       cli->dfsroot = false;
-
        if ((wct > 2) && (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_LANMAN2)) {
                optional_support = SVAL(vwv+2, 0);
        }
 
-       if (optional_support & SMB_SHARE_IN_DFS) {
-               cli->dfsroot = true;
-       }
-
        if (optional_support & SMB_EXTENDED_SIGNATURES) {
                smb1cli_session_protect_session_key(cli->smb1.session);
        }
 
-       cli_state_set_tid(cli, SVAL(inhdr, HDR_TID));
+       smb1cli_tcon_set_values(state->cli->smb1.tcon,
+                               SVAL(inhdr, HDR_TID),
+                               optional_support,
+                               0, /* maximal_access */
+                               0, /* guest_maximal_access */
+                               NULL, /* service */
+                               NULL); /* fs_type */
+
        tevent_req_done(req);
 }
 
@@ -2592,7 +2694,7 @@ NTSTATUS cli_tcon_andx(struct cli_state *cli, const char *share,
        TALLOC_CTX *frame = talloc_stackframe();
        struct tevent_context *ev;
        struct tevent_req *req;
-       NTSTATUS status = NT_STATUS_OK;
+       NTSTATUS status = NT_STATUS_NO_MEMORY;
 
        if (smbXcli_conn_has_async_calls(cli->conn)) {
                /*
@@ -2602,57 +2704,174 @@ NTSTATUS cli_tcon_andx(struct cli_state *cli, const char *share,
                goto fail;
        }
 
-       ev = samba_tevent_context_init(frame);
-       if (ev == NULL) {
-               status = NT_STATUS_NO_MEMORY;
-               goto fail;
+       ev = samba_tevent_context_init(frame);
+       if (ev == NULL) {
+               goto fail;
+       }
+
+       req = cli_tcon_andx_send(frame, ev, cli, share, dev, pass, passlen);
+       if (req == NULL) {
+               goto fail;
+       }
+
+       if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+               goto fail;
+       }
+
+       status = cli_tcon_andx_recv(req);
+ fail:
+       TALLOC_FREE(frame);
+       return status;
+}
+
+struct cli_tree_connect_state {
+       struct cli_state *cli;
+};
+
+static struct tevent_req *cli_raw_tcon_send(
+       TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct cli_state *cli,
+       const char *service, const char *pass, const char *dev);
+static NTSTATUS cli_raw_tcon_recv(struct tevent_req *req,
+                                 uint16_t *max_xmit, uint16_t *tid);
+
+static void cli_tree_connect_smb2_done(struct tevent_req *subreq);
+static void cli_tree_connect_andx_done(struct tevent_req *subreq);
+static void cli_tree_connect_raw_done(struct tevent_req *subreq);
+
+static struct tevent_req *cli_tree_connect_send(
+       TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct cli_state *cli,
+       const char *share, const char *dev, const char *pass, int passlen)
+{
+       struct tevent_req *req, *subreq;
+       struct cli_tree_connect_state *state;
+
+       req = tevent_req_create(mem_ctx, &state,
+                               struct cli_tree_connect_state);
+       if (req == NULL) {
+               return NULL;
+       }
+       state->cli = cli;
+
+       cli->share = talloc_strdup(cli, share);
+       if (tevent_req_nomem(cli->share, req)) {
+               return tevent_req_post(req, ev);
+       }
+
+       if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_SMB2_02) {
+               char *unc;
+
+               cli->smb2.tcon = smbXcli_tcon_create(cli);
+               if (tevent_req_nomem(cli->smb2.tcon, req)) {
+                       return tevent_req_post(req, ev);
+               }
+
+               unc = talloc_asprintf(state, "\\\\%s\\%s",
+                                     smbXcli_conn_remote_name(cli->conn),
+                                     share);
+               if (tevent_req_nomem(unc, req)) {
+                       return tevent_req_post(req, ev);
+               }
+
+               subreq = smb2cli_tcon_send(state, ev, cli->conn, cli->timeout,
+                                          cli->smb2.session, cli->smb2.tcon,
+                                          0, /* flags */
+                                          unc);
+               if (tevent_req_nomem(subreq, req)) {
+                       return tevent_req_post(req, ev);
+               }
+               tevent_req_set_callback(subreq, cli_tree_connect_smb2_done,
+                                       req);
+               return req;
+       }
+
+       if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_LANMAN1) {
+               subreq = cli_tcon_andx_send(state, ev, cli, share, dev,
+                                           pass, passlen);
+               if (tevent_req_nomem(subreq, req)) {
+                       return tevent_req_post(req, ev);
+               }
+               tevent_req_set_callback(subreq, cli_tree_connect_andx_done,
+                                       req);
+               return req;
+       }
+
+       subreq = cli_raw_tcon_send(state, ev, cli, share, pass, dev);
+       if (tevent_req_nomem(subreq, req)) {
+               return tevent_req_post(req, ev);
+       }
+       tevent_req_set_callback(subreq, cli_tree_connect_raw_done, req);
+
+       return req;
+}
+
+static void cli_tree_connect_smb2_done(struct tevent_req *subreq)
+{
+       tevent_req_simple_finish_ntstatus(
+               subreq, smb2cli_tcon_recv(subreq));
+}
+
+static void cli_tree_connect_andx_done(struct tevent_req *subreq)
+{
+       tevent_req_simple_finish_ntstatus(
+               subreq, cli_tcon_andx_recv(subreq));
+}
+
+static void cli_tree_connect_raw_done(struct tevent_req *subreq)
+{
+       struct tevent_req *req = tevent_req_callback_data(
+               subreq, struct tevent_req);
+       struct cli_tree_connect_state *state = tevent_req_data(
+               req, struct cli_tree_connect_state);
+       NTSTATUS status;
+       uint16_t max_xmit = 0;
+       uint16_t tid = 0;
+
+       status = cli_raw_tcon_recv(subreq, &max_xmit, &tid);
+       if (tevent_req_nterror(req, status)) {
+               return;
        }
 
-       req = cli_tcon_andx_send(frame, ev, cli, share, dev, pass, passlen);
-       if (req == NULL) {
-               status = NT_STATUS_NO_MEMORY;
-               goto fail;
-       }
+       smb1cli_tcon_set_values(state->cli->smb1.tcon,
+                               tid,
+                               0, /* optional_support */
+                               0, /* maximal_access */
+                               0, /* guest_maximal_access */
+                               NULL, /* service */
+                               NULL); /* fs_type */
 
-       if (!tevent_req_poll(req, ev)) {
-               status = map_nt_error_from_unix(errno);
-               goto fail;
-       }
+       tevent_req_done(req);
+}
 
-       status = cli_tcon_andx_recv(req);
- fail:
-       TALLOC_FREE(frame);
-       return status;
+static NTSTATUS cli_tree_connect_recv(struct tevent_req *req)
+{
+       return tevent_req_simple_recv_ntstatus(req);
 }
 
 NTSTATUS cli_tree_connect(struct cli_state *cli, const char *share,
                          const char *dev, const char *pass, int passlen)
 {
-       NTSTATUS status;
-       uint16_t max_xmit = 0;
-       uint16_t tid = 0;
+       struct tevent_context *ev;
+       struct tevent_req *req;
+       NTSTATUS status = NT_STATUS_NO_MEMORY;
 
-       cli->share = talloc_strdup(cli, share);
-       if (!cli->share) {
-               return NT_STATUS_NO_MEMORY;
+       if (smbXcli_conn_has_async_calls(cli->conn)) {
+               return NT_STATUS_INVALID_PARAMETER;
        }
-
-       if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_SMB2_02) {
-               return smb2cli_tcon(cli, share);
+       ev = samba_tevent_context_init(talloc_tos());
+       if (ev == NULL) {
+               goto fail;
        }
-
-       if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_LANMAN1) {
-               return cli_tcon_andx(cli, share, dev, pass, passlen);
+       req = cli_tree_connect_send(ev, ev, cli, share, dev, pass, passlen);
+       if (req == NULL) {
+               goto fail;
        }
-
-       status = cli_raw_tcon(cli, share, pass, dev, &max_xmit, &tid);
-       if (!NT_STATUS_IS_OK(status)) {
-               return status;
+       if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+               goto fail;
        }
-
-       cli_state_set_tid(cli, tid);
-
-       return NT_STATUS_OK;
+       status = cli_tree_connect_recv(req);
+fail:
+       TALLOC_FREE(ev);
+       return status;
 }
 
 /****************************************************************************
@@ -2665,7 +2884,7 @@ struct cli_tdis_state {
 
 static void cli_tdis_done(struct tevent_req *subreq);
 
-struct tevent_req *cli_tdis_send(TALLOC_CTX *mem_ctx,
+static struct tevent_req *cli_tdis_send(TALLOC_CTX *mem_ctx,
                                 struct tevent_context *ev,
                                 struct cli_state *cli)
 {
@@ -2704,7 +2923,7 @@ static void cli_tdis_done(struct tevent_req *subreq)
        tevent_req_done(req);
 }
 
-NTSTATUS cli_tdis_recv(struct tevent_req *req)
+static NTSTATUS cli_tdis_recv(struct tevent_req *req)
 {
        return tevent_req_simple_recv_ntstatus(req);
 }
@@ -2715,6 +2934,13 @@ NTSTATUS cli_tdis(struct cli_state *cli)
        struct tevent_req *req;
        NTSTATUS status = NT_STATUS_NO_MEMORY;
 
+       if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_SMB2_02) {
+               return smb2cli_tdis(cli->conn,
+                                   cli->timeout,
+                                   cli->smb2.session,
+                                   cli->smb2.tcon);
+       }
+
        if (smbXcli_conn_has_async_calls(cli->conn)) {
                return NT_STATUS_INVALID_PARAMETER;
        }
@@ -2758,6 +2984,7 @@ static struct tevent_req *cli_connect_sock_send(
        struct tevent_req *req, *subreq;
        struct cli_connect_sock_state *state;
        const char *prog;
+       struct sockaddr_storage *addrs;
        unsigned i, num_addrs;
        NTSTATUS status;
 
@@ -2781,7 +3008,6 @@ static struct tevent_req *cli_connect_sock_send(
        }
 
        if ((pss == NULL) || is_zero_addr(pss)) {
-               struct sockaddr_storage *addrs;
 
                /*
                 * Here we cheat. resolve_name_list is not async at all. So
@@ -2795,8 +3021,12 @@ static struct tevent_req *cli_connect_sock_send(
                        tevent_req_nterror(req, status);
                        return tevent_req_post(req, ev);
                }
-               pss = addrs;
        } else {
+               addrs = talloc_array(state, struct sockaddr_storage, 1);
+               if (tevent_req_nomem(addrs, req)) {
+                       return tevent_req_post(req, ev);
+               }
+               addrs[0] = *pss;
                num_addrs = 1;
        }
 
@@ -2819,7 +3049,7 @@ static struct tevent_req *cli_connect_sock_send(
        }
 
        subreq = smbsock_any_connect_send(
-               state, ev, pss, state->called_names, state->called_types,
+               state, ev, addrs, state->called_names, state->called_types,
                state->calling_names, NULL, num_addrs, port);
        if (tevent_req_nomem(subreq, req)) {
                return tevent_req_post(req, ev);
@@ -2878,21 +3108,29 @@ static struct tevent_req *cli_connect_nb_send(
 {
        struct tevent_req *req, *subreq;
        struct cli_connect_nb_state *state;
-       char *p;
 
        req = tevent_req_create(mem_ctx, &state, struct cli_connect_nb_state);
        if (req == NULL) {
                return NULL;
        }
-       state->desthost = host;
        state->signing_state = signing_state;
        state->flags = flags;
 
-       p = strchr(host, '#');
-       if (p != NULL) {
-               name_type = strtol(p+1, NULL, 16);
-               host = talloc_strndup(state, host, p - host);
-               if (tevent_req_nomem(host, req)) {
+       if (host != NULL) {
+               char *p = strchr(host, '#');
+
+               if (p != NULL) {
+                       name_type = strtol(p+1, NULL, 16);
+                       host = talloc_strndup(state, host, p - host);
+                       if (tevent_req_nomem(host, req)) {
+                               return tevent_req_post(req, ev);
+                       }
+               }
+
+               state->desthost = host;
+       } else {
+               state->desthost = print_canonical_sockaddr(state, dest_ss);
+               if (tevent_req_nomem(state->desthost, req)) {
                        return tevent_req_post(req, ev);
                }
        }
@@ -2913,7 +3151,7 @@ static void cli_connect_nb_done(struct tevent_req *subreq)
        struct cli_connect_nb_state *state = tevent_req_data(
                req, struct cli_connect_nb_state);
        NTSTATUS status;
-       int fd;
+       int fd = 0;
        uint16_t port;
 
        status = cli_connect_sock_recv(subreq, &fd, &port);
@@ -2977,6 +3215,8 @@ fail:
 struct cli_start_connection_state {
        struct tevent_context *ev;
        struct cli_state *cli;
+       int min_protocol;
+       int max_protocol;
 };
 
 static void cli_start_connection_connected(struct tevent_req *subreq);
@@ -3006,6 +3246,14 @@ static struct tevent_req *cli_start_connection_send(
        }
        state->ev = ev;
 
+       if (signing_state == SMB_SIGNING_IPC_DEFAULT) {
+               state->min_protocol = lp_client_ipc_min_protocol();
+               state->max_protocol = lp_client_ipc_max_protocol();
+       } else {
+               state->min_protocol = lp_client_min_protocol();
+               state->max_protocol = lp_client_max_protocol();
+       }
+
        subreq = cli_connect_nb_send(state, ev, dest_host, dest_ss, port,
                                     0x20, my_name, signing_state, flags);
        if (tevent_req_nomem(subreq, req)) {
@@ -3031,7 +3279,8 @@ static void cli_start_connection_connected(struct tevent_req *subreq)
 
        subreq = smbXcli_negprot_send(state, state->ev, state->cli->conn,
                                      state->cli->timeout,
-                                     PROTOCOL_CORE, PROTOCOL_NT1);
+                                     state->min_protocol,
+                                     state->max_protocol);
        if (tevent_req_nomem(subreq, req)) {
                return;
        }
@@ -3042,6 +3291,8 @@ static void cli_start_connection_done(struct tevent_req *subreq)
 {
        struct tevent_req *req = tevent_req_callback_data(
                subreq, struct tevent_req);
+       struct cli_start_connection_state *state = tevent_req_data(
+               req, struct cli_start_connection_state);
        NTSTATUS status;
 
        status = smbXcli_negprot_recv(subreq);
@@ -3049,6 +3300,13 @@ static void cli_start_connection_done(struct tevent_req *subreq)
        if (tevent_req_nterror(req, status)) {
                return;
        }
+
+       if (smbXcli_conn_protocol(state->cli->conn) >= PROTOCOL_SMB2_02) {
+               /* Ensure we ask for some initial credits. */
+               smb2cli_conn_set_max_credits(state->cli->conn,
+                                            DEFAULT_SMB2_MAX_CREDITS);
+       }
+
        tevent_req_done(req);
 }
 
@@ -3063,6 +3321,7 @@ static NTSTATUS cli_start_connection_recv(struct tevent_req *req,
                return status;
        }
        *output_cli = state->cli;
+
        return NT_STATUS_OK;
 }
 
@@ -3094,7 +3353,6 @@ fail:
        return status;
 }
 
-
 /**
    establishes a connection right up to doing tconX, password specified.
    @param output_cli A fully initialised cli structure, non-null only on success
@@ -3108,95 +3366,247 @@ fail:
    @param password User's password, unencrypted unix string.
 */
 
-NTSTATUS cli_full_connection(struct cli_state **output_cli, 
-                            const char *my_name, 
-                            const char *dest_host, 
-                            const struct sockaddr_storage *dest_ss, int port,
-                            const char *service, const char *service_type,
-                            const char *user, const char *domain, 
-                            const char *password, int flags,
-                            int signing_state)
+struct cli_full_connection_state {
+       struct tevent_context *ev;
+       const char *service;
+       const char *service_type;
+       const char *user;
+       const char *domain;
+       const char *password;
+       int pw_len;
+       int flags;
+       struct cli_state *cli;
+};
+
+static int cli_full_connection_state_destructor(
+       struct cli_full_connection_state *s);
+static void cli_full_connection_started(struct tevent_req *subreq);
+static void cli_full_connection_sess_set_up(struct tevent_req *subreq);
+static void cli_full_connection_done(struct tevent_req *subreq);
+
+struct tevent_req *cli_full_connection_send(
+       TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+       const char *my_name, const char *dest_host,
+       const struct sockaddr_storage *dest_ss, int port,
+       const char *service, const char *service_type,
+       const char *user, const char *domain,
+       const char *password, int flags, int signing_state)
 {
-       NTSTATUS nt_status;
-       struct cli_state *cli = NULL;
-       int pw_len = password ? strlen(password)+1 : 0;
+       struct tevent_req *req, *subreq;
+       struct cli_full_connection_state *state;
+
+       req = tevent_req_create(mem_ctx, &state,
+                               struct cli_full_connection_state);
+       if (req == NULL) {
+               return NULL;
+       }
+       talloc_set_destructor(state, cli_full_connection_state_destructor);
 
-       *output_cli = NULL;
+       state->ev = ev;
+       state->service = service;
+       state->service_type = service_type;
+       state->user = user;
+       state->domain = domain;
+       state->password = password;
+       state->flags = flags;
 
-       if (password == NULL) {
-               password = "";
+       state->pw_len = state->password ? strlen(state->password)+1 : 0;
+       if (state->password == NULL) {
+               state->password = "";
        }
 
-       nt_status = cli_start_connection(&cli, my_name, dest_host,
-                                        dest_ss, port, signing_state,
-                                        flags);
+       subreq = cli_start_connection_send(
+               state, ev, my_name, dest_host, dest_ss, port,
+               signing_state, flags);
+       if (tevent_req_nomem(subreq, req)) {
+               return tevent_req_post(req, ev);
+       }
+       tevent_req_set_callback(subreq, cli_full_connection_started, req);
+       return req;
+}
 
-       if (!NT_STATUS_IS_OK(nt_status)) {
-               return nt_status;
+static int cli_full_connection_state_destructor(
+       struct cli_full_connection_state *s)
+{
+       if (s->cli != NULL) {
+               cli_shutdown(s->cli);
+               s->cli = NULL;
        }
+       return 0;
+}
 
-       nt_status = cli_session_setup(cli, user, password, pw_len, password,
-                                     pw_len, domain);
-       if (!NT_STATUS_IS_OK(nt_status)) {
+static void cli_full_connection_started(struct tevent_req *subreq)
+{
+       struct tevent_req *req = tevent_req_callback_data(
+               subreq, struct tevent_req);
+       struct cli_full_connection_state *state = tevent_req_data(
+               req, struct cli_full_connection_state);
+       NTSTATUS status;
 
-               if (!(flags & CLI_FULL_CONNECTION_ANONYMOUS_FALLBACK)) {
-                       DEBUG(1,("failed session setup with %s\n",
-                                nt_errstr(nt_status)));
-                       cli_shutdown(cli);
-                       return nt_status;
-               }
+       status = cli_start_connection_recv(subreq, &state->cli);
+       TALLOC_FREE(subreq);
+       if (tevent_req_nterror(req, status)) {
+               return;
+       }
+       subreq = cli_session_setup_send(
+               state, state->ev, state->cli, state->user,
+               state->password, state->pw_len, state->password, state->pw_len,
+               state->domain);
+       if (tevent_req_nomem(subreq, req)) {
+               return;
+       }
+       tevent_req_set_callback(subreq, cli_full_connection_sess_set_up, req);
+}
+
+static void cli_full_connection_sess_set_up(struct tevent_req *subreq)
+{
+       struct tevent_req *req = tevent_req_callback_data(
+               subreq, struct tevent_req);
+       struct cli_full_connection_state *state = tevent_req_data(
+               req, struct cli_full_connection_state);
+       NTSTATUS status;
+
+       status = cli_session_setup_recv(subreq);
+       TALLOC_FREE(subreq);
+
+       if (!NT_STATUS_IS_OK(status) &&
+           (state->flags & CLI_FULL_CONNECTION_ANONYMOUS_FALLBACK)) {
 
-               nt_status = cli_session_setup(cli, "", "", 0, "", 0, domain);
-               if (!NT_STATUS_IS_OK(nt_status)) {
-                       DEBUG(1,("anonymous failed session setup with %s\n",
-                                nt_errstr(nt_status)));
-                       cli_shutdown(cli);
-                       return nt_status;
+               state->flags &= ~CLI_FULL_CONNECTION_ANONYMOUS_FALLBACK;
+
+               subreq = cli_session_setup_send(
+                       state, state->ev, state->cli, "", "", 0, "", 0,
+                       state->domain);
+               if (tevent_req_nomem(subreq, req)) {
+                       return;
                }
+               tevent_req_set_callback(
+                       subreq, cli_full_connection_sess_set_up, req);
+               return;
        }
 
-       if (service) {
-               nt_status = cli_tree_connect(cli, service, service_type,
-                                            password, pw_len);
-               if (!NT_STATUS_IS_OK(nt_status)) {
-                       DEBUG(1,("failed tcon_X with %s\n", nt_errstr(nt_status)));
-                       cli_shutdown(cli);
-                       if (NT_STATUS_IS_OK(nt_status)) {
-                               nt_status = NT_STATUS_UNSUCCESSFUL;
-                       }
-                       return nt_status;
+       if (tevent_req_nterror(req, status)) {
+               return;
+       }
+
+       if (state->service != NULL) {
+               subreq = cli_tree_connect_send(
+                       state, state->ev, state->cli,
+                       state->service, state->service_type,
+                       state->password, state->pw_len);
+               if (tevent_req_nomem(subreq, req)) {
+                       return;
                }
+               tevent_req_set_callback(subreq, cli_full_connection_done, req);
+               return;
+       }
+
+       status = cli_init_creds(state->cli, state->user, state->domain,
+                               state->password);
+       if (tevent_req_nterror(req, status)) {
+               return;
        }
+       tevent_req_done(req);
+}
+
+static void cli_full_connection_done(struct tevent_req *subreq)
+{
+       struct tevent_req *req = tevent_req_callback_data(
+               subreq, struct tevent_req);
+       struct cli_full_connection_state *state = tevent_req_data(
+               req, struct cli_full_connection_state);
+       NTSTATUS status;
 
-       nt_status = cli_init_creds(cli, user, domain, password);
-       if (!NT_STATUS_IS_OK(nt_status)) {
-               cli_shutdown(cli);
-               return nt_status;
+       status = cli_tree_connect_recv(subreq);
+       TALLOC_FREE(subreq);
+       if (tevent_req_nterror(req, status)) {
+               return;
+       }
+       status = cli_init_creds(state->cli, state->user, state->domain,
+                               state->password);
+       if (tevent_req_nterror(req, status)) {
+               return;
        }
+       tevent_req_done(req);
+}
+
+NTSTATUS cli_full_connection_recv(struct tevent_req *req,
+                                 struct cli_state **output_cli)
+{
+       struct cli_full_connection_state *state = tevent_req_data(
+               req, struct cli_full_connection_state);
+       NTSTATUS status;
 
-       *output_cli = cli;
+       if (tevent_req_is_nterror(req, &status)) {
+               return status;
+       }
+       *output_cli = state->cli;
+       talloc_set_destructor(state, NULL);
        return NT_STATUS_OK;
 }
 
+NTSTATUS cli_full_connection(struct cli_state **output_cli,
+                            const char *my_name,
+                            const char *dest_host,
+                            const struct sockaddr_storage *dest_ss, int port,
+                            const char *service, const char *service_type,
+                            const char *user, const char *domain,
+                            const char *password, int flags,
+                            int signing_state)
+{
+       struct tevent_context *ev;
+       struct tevent_req *req;
+       NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+       ev = samba_tevent_context_init(talloc_tos());
+       if (ev == NULL) {
+               goto fail;
+       }
+       req = cli_full_connection_send(
+               ev, ev, my_name, dest_host, dest_ss, port, service,
+               service_type, user, domain, password, flags, signing_state);
+       if (req == NULL) {
+               goto fail;
+       }
+       if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+               goto fail;
+       }
+       status = cli_full_connection_recv(req, output_cli);
+ fail:
+       TALLOC_FREE(ev);
+       return status;
+}
+
 /****************************************************************************
  Send an old style tcon.
 ****************************************************************************/
-NTSTATUS cli_raw_tcon(struct cli_state *cli, 
-                     const char *service, const char *pass, const char *dev,
-                     uint16 *max_xmit, uint16 *tid)
-{
-       struct tevent_req *req;
+struct cli_raw_tcon_state {
        uint16_t *ret_vwv;
+};
+
+static void cli_raw_tcon_done(struct tevent_req *subreq);
+
+static struct tevent_req *cli_raw_tcon_send(
+       TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct cli_state *cli,
+       const char *service, const char *pass, const char *dev)
+{
+       struct tevent_req *req, *subreq;
+       struct cli_raw_tcon_state *state;
        uint8_t *bytes;
-       NTSTATUS status;
+
+       req = tevent_req_create(mem_ctx, &state, struct cli_raw_tcon_state);
+       if (req == NULL) {
+               return NULL;
+       }
 
        if (!lp_client_plaintext_auth() && (*pass)) {
                DEBUG(1, ("Server requested PLAINTEXT password but 'client plaintext auth = no'"
                          " or 'client ntlmv2 auth = yes'\n"));
-               return NT_STATUS_ACCESS_DENIED;
+               tevent_req_nterror(req, NT_STATUS_ACCESS_DENIED);
+               return tevent_req_post(req, ev);
        }
 
-       bytes = talloc_array(talloc_tos(), uint8_t, 0);
+       bytes = talloc_array(state, uint8_t, 0);
        bytes = smb_bytes_push_bytes(bytes, 4, NULL, 0);
        bytes = smb_bytes_push_str(bytes, smbXcli_conn_use_unicode(cli->conn),
                                   service, strlen(service)+1, NULL);
@@ -3207,19 +3617,76 @@ NTSTATUS cli_raw_tcon(struct cli_state *cli,
        bytes = smb_bytes_push_str(bytes, smbXcli_conn_use_unicode(cli->conn),
                                   dev, strlen(dev)+1, NULL);
 
-       status = cli_smb(talloc_tos(), cli, SMBtcon, 0, 0, NULL,
-                        talloc_get_size(bytes), bytes, &req,
-                        2, NULL, &ret_vwv, NULL, NULL);
-       if (!NT_STATUS_IS_OK(status)) {
-               return status;
+       if (tevent_req_nomem(bytes, req)) {
+               return tevent_req_post(req, ev);
+       }
+
+       subreq = cli_smb_send(state, ev, cli, SMBtcon, 0, 0, NULL,
+                             talloc_get_size(bytes), bytes);
+       if (tevent_req_nomem(subreq, req)) {
+               return tevent_req_post(req, ev);
+       }
+       tevent_req_set_callback(subreq, cli_raw_tcon_done, req);
+       return req;
+}
+
+static void cli_raw_tcon_done(struct tevent_req *subreq)
+{
+       struct tevent_req *req = tevent_req_callback_data(
+               subreq, struct tevent_req);
+       struct cli_raw_tcon_state *state = tevent_req_data(
+               req, struct cli_raw_tcon_state);
+       NTSTATUS status;
+
+       status = cli_smb_recv(subreq, state, NULL, 2, NULL, &state->ret_vwv,
+                             NULL, NULL);
+       TALLOC_FREE(subreq);
+       if (tevent_req_nterror(req, status)) {
+               return;
        }
+       tevent_req_done(req);
+}
 
-       *max_xmit = SVAL(ret_vwv + 0, 0);
-       *tid = SVAL(ret_vwv + 1, 0);
+static NTSTATUS cli_raw_tcon_recv(struct tevent_req *req,
+                                 uint16_t *max_xmit, uint16_t *tid)
+{
+       struct cli_raw_tcon_state *state = tevent_req_data(
+               req, struct cli_raw_tcon_state);
+       NTSTATUS status;
 
+       if (tevent_req_is_nterror(req, &status)) {
+               return status;
+       }
+       *max_xmit = SVAL(state->ret_vwv + 0, 0);
+       *tid = SVAL(state->ret_vwv + 1, 0);
        return NT_STATUS_OK;
 }
 
+NTSTATUS cli_raw_tcon(struct cli_state *cli,
+                     const char *service, const char *pass, const char *dev,
+                     uint16_t *max_xmit, uint16_t *tid)
+{
+       struct tevent_context *ev;
+       struct tevent_req *req;
+       NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+       ev = samba_tevent_context_init(talloc_tos());
+       if (ev == NULL) {
+               goto fail;
+       }
+       req = cli_raw_tcon_send(ev, ev, cli, service, pass, dev);
+       if (req == NULL) {
+               goto fail;
+       }
+       if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+               goto fail;
+       }
+       status = cli_raw_tcon_recv(req, max_xmit, tid);
+fail:
+       TALLOC_FREE(ev);
+       return status;
+}
+
 /* Return a cli_state pointing at the IPC$ share for the given server */
 
 struct cli_state *get_ipc_connect(char *server,