smb2_server: move struct msghdr to smbd_smb2_send_queue
[samba.git] / source3 / smbd / smb2_server.c
index 991a336855a7481d194e8d872a5386cd66341166..55b383072e6a3cf1bca466127fed71c978578e3b 100644 (file)
@@ -23,6 +23,8 @@
 #include "system/network.h"
 #include "smbd/smbd.h"
 #include "smbd/globals.h"
+#include "smbd/smbXsrv_open.h"
+#include "lib/param/param.h"
 #include "../libcli/smb/smb_common.h"
 #include "../lib/tsocket/tsocket.h"
 #include "../lib/util/tevent_ntstatus.h"
@@ -32,6 +34,7 @@
 #include "lib/util/iov_buf.h"
 #include "auth.h"
 #include "libcli/smb/smbXcli_base.h"
+#include "source3/lib/substitute.h"
 
 #if defined(LINUX)
 /* SIOCOUTQ TIOCOUTQ are the same */
@@ -59,27 +62,24 @@ static NTSTATUS smbd_smb2_flush_send_queue(struct smbXsrv_connection *xconn);
 
 static const struct smbd_smb2_dispatch_table {
        uint16_t opcode;
-       const char *name;
-       bool need_session;
-       bool need_tcon;
-       bool as_root;
        uint16_t fileid_ofs;
-       bool allow_invalid_fileid;
-       bool modify;
+       bool need_session : 1;
+       bool need_tcon : 1;
+       bool as_root : 1;
+       bool modify : 1;
 } smbd_smb2_table[] = {
-#define _OP(o) .opcode = o, .name = #o
        {
-               _OP(SMB2_OP_NEGPROT),
+               .opcode = SMB2_OP_NEGPROT,
                .as_root = true,
        },{
-               _OP(SMB2_OP_SESSSETUP),
+               .opcode = SMB2_OP_SESSSETUP,
                .as_root = true,
        },{
-               _OP(SMB2_OP_LOGOFF),
+               .opcode = SMB2_OP_LOGOFF,
                .need_session = true,
                .as_root = true,
        },{
-               _OP(SMB2_OP_TCON),
+               .opcode = SMB2_OP_TCON,
                .need_session = true,
                /*
                 * This call needs to be run as root.
@@ -90,76 +90,74 @@ static const struct smbd_smb2_dispatch_table {
                 */
                .as_root = true,
        },{
-               _OP(SMB2_OP_TDIS),
+               .opcode = SMB2_OP_TDIS,
                .need_session = true,
                .need_tcon = true,
                .as_root = true,
        },{
-               _OP(SMB2_OP_CREATE),
+               .opcode = SMB2_OP_CREATE,
                .need_session = true,
                .need_tcon = true,
        },{
-               _OP(SMB2_OP_CLOSE),
+               .opcode = SMB2_OP_CLOSE,
                .need_session = true,
                .need_tcon = true,
                .fileid_ofs = 0x08,
        },{
-               _OP(SMB2_OP_FLUSH),
+               .opcode = SMB2_OP_FLUSH,
                .need_session = true,
                .need_tcon = true,
                .fileid_ofs = 0x08,
        },{
-               _OP(SMB2_OP_READ),
+               .opcode = SMB2_OP_READ,
                .need_session = true,
                .need_tcon = true,
                .fileid_ofs = 0x10,
        },{
-               _OP(SMB2_OP_WRITE),
+               .opcode = SMB2_OP_WRITE,
                .need_session = true,
                .need_tcon = true,
                .fileid_ofs = 0x10,
                .modify = true,
        },{
-               _OP(SMB2_OP_LOCK),
+               .opcode = SMB2_OP_LOCK,
                .need_session = true,
                .need_tcon = true,
                .fileid_ofs = 0x08,
        },{
-               _OP(SMB2_OP_IOCTL),
+               .opcode = SMB2_OP_IOCTL,
                .need_session = true,
                .need_tcon = true,
                .fileid_ofs = 0x08,
-               .allow_invalid_fileid = true,
                .modify = true,
        },{
-               _OP(SMB2_OP_CANCEL),
+               .opcode = SMB2_OP_CANCEL,
                .as_root = true,
        },{
-               _OP(SMB2_OP_KEEPALIVE),
-               .as_root = true,
+               .opcode = SMB2_OP_KEEPALIVE,
        },{
-               _OP(SMB2_OP_QUERY_DIRECTORY),
+               .opcode = SMB2_OP_QUERY_DIRECTORY,
                .need_session = true,
                .need_tcon = true,
                .fileid_ofs = 0x08,
        },{
-               _OP(SMB2_OP_NOTIFY),
+               .opcode = SMB2_OP_NOTIFY,
                .need_session = true,
                .need_tcon = true,
                .fileid_ofs = 0x08,
        },{
-               _OP(SMB2_OP_GETINFO),
+               .opcode = SMB2_OP_GETINFO,
                .need_session = true,
                .need_tcon = true,
                .fileid_ofs = 0x18,
        },{
-               _OP(SMB2_OP_SETINFO),
+               .opcode = SMB2_OP_SETINFO,
                .need_session = true,
                .need_tcon = true,
                .fileid_ofs = 0x10,
                .modify = true,
        },{
-               _OP(SMB2_OP_BREAK),
+               .opcode = SMB2_OP_BREAK,
                .need_session = true,
                .need_tcon = true,
                /*
@@ -173,10 +171,70 @@ static const struct smbd_smb2_dispatch_table {
 
 const char *smb2_opcode_name(uint16_t opcode)
 {
-       if (opcode >= ARRAY_SIZE(smbd_smb2_table)) {
-               return "Bad SMB2 opcode";
+       const char *result = "Bad SMB2 opcode";
+
+       switch (opcode) {
+       case SMB2_OP_NEGPROT:
+               result = "SMB2_OP_NEGPROT";
+               break;
+       case SMB2_OP_SESSSETUP:
+               result = "SMB2_OP_SESSSETUP";
+               break;
+       case SMB2_OP_LOGOFF:
+               result = "SMB2_OP_LOGOFF";
+               break;
+       case SMB2_OP_TCON:
+               result = "SMB2_OP_TCON";
+               break;
+       case SMB2_OP_TDIS:
+               result = "SMB2_OP_TDIS";
+               break;
+       case SMB2_OP_CREATE:
+               result = "SMB2_OP_CREATE";
+               break;
+       case SMB2_OP_CLOSE:
+               result = "SMB2_OP_CLOSE";
+               break;
+       case SMB2_OP_FLUSH:
+               result = "SMB2_OP_FLUSH";
+               break;
+       case SMB2_OP_READ:
+               result = "SMB2_OP_READ";
+               break;
+       case SMB2_OP_WRITE:
+               result = "SMB2_OP_WRITE";
+               break;
+       case SMB2_OP_LOCK:
+               result = "SMB2_OP_LOCK";
+               break;
+       case SMB2_OP_IOCTL:
+               result = "SMB2_OP_IOCTL";
+               break;
+       case SMB2_OP_CANCEL:
+               result = "SMB2_OP_CANCEL";
+               break;
+       case SMB2_OP_KEEPALIVE:
+               result = "SMB2_OP_KEEPALIVE";
+               break;
+       case SMB2_OP_QUERY_DIRECTORY:
+               result = "SMB2_OP_QUERY_DIRECTORY";
+               break;
+       case SMB2_OP_NOTIFY:
+               result = "SMB2_OP_NOTIFY";
+               break;
+       case SMB2_OP_GETINFO:
+               result = "SMB2_OP_GETINFO";
+               break;
+       case SMB2_OP_SETINFO:
+               result = "SMB2_OP_SETINFO";
+               break;
+       case SMB2_OP_BREAK:
+               result = "SMB2_OP_BREAK";
+               break;
+       default:
+               break;
        }
-       return smbd_smb2_table[opcode].name;
+       return result;
 }
 
 static const struct smbd_smb2_dispatch_table *smbd_smb2_call(uint16_t opcode)
@@ -228,6 +286,12 @@ bool smbd_smb2_is_compound(const struct smbd_smb2_request *req)
        return req->in.vector_count >= (2*SMBD_SMB2_NUM_IOV_PER_REQ);
 }
 
+bool smbd_smb2_is_last_in_compound(const struct smbd_smb2_request *req)
+{
+       return (req->current_idx + SMBD_SMB2_NUM_IOV_PER_REQ ==
+               req->in.vector_count);
+}
+
 static NTSTATUS smbd_initialize_smb2(struct smbXsrv_connection *xconn,
                                     uint64_t expected_seq_low)
 {
@@ -260,11 +324,23 @@ static NTSTATUS smbd_initialize_smb2(struct smbXsrv_connection *xconn,
        }
        tevent_fd_set_auto_close(xconn->transport.fde);
 
-       /* Ensure child is set to non-blocking mode */
+       /*
+        * Ensure child is set to non-blocking mode,
+        * unless the system supports MSG_DONTWAIT,
+        * if MSG_DONTWAIT is available we should force
+        * blocking mode.
+        */
+#ifdef MSG_DONTWAIT
+       rc = set_blocking(xconn->transport.sock, true);
+       if (rc < 0) {
+               return NT_STATUS_INTERNAL_ERROR;
+       }
+#else
        rc = set_blocking(xconn->transport.sock, false);
        if (rc < 0) {
                return NT_STATUS_INTERNAL_ERROR;
        }
+#endif
 
        return NT_STATUS_OK;
 }
@@ -309,7 +385,7 @@ void smb2_request_set_async_internal(struct smbd_smb2_request *req,
        req->async_internal = async_internal;
 }
 
-static struct smbd_smb2_request *smbd_smb2_request_allocate(TALLOC_CTX *mem_ctx)
+static struct smbd_smb2_request *smbd_smb2_request_allocate(struct smbXsrv_connection *xconn)
 {
        TALLOC_CTX *mem_pool;
        struct smbd_smb2_request *req;
@@ -324,18 +400,21 @@ static struct smbd_smb2_request *smbd_smb2_request_allocate(TALLOC_CTX *mem_ctx)
                return NULL;
        }
 
-       req = talloc_zero(mem_pool, struct smbd_smb2_request);
+       req = talloc(mem_pool, struct smbd_smb2_request);
        if (req == NULL) {
                talloc_free(mem_pool);
                return NULL;
        }
-       talloc_reparent(mem_pool, mem_ctx, req);
+       talloc_reparent(mem_pool, xconn, req);
 #if 0
        TALLOC_FREE(mem_pool);
 #endif
-
-       req->last_session_id = UINT64_MAX;
-       req->last_tid = UINT32_MAX;
+       *req = (struct smbd_smb2_request) {
+               .sconn = xconn->client->sconn,
+               .xconn = xconn,
+               .last_session_id = UINT64_MAX,
+               .last_tid = UINT32_MAX,
+       };
 
        talloc_set_destructor(req, smbd_smb2_request_destructor);
 
@@ -574,7 +653,6 @@ static NTSTATUS smbd_smb2_request_create(struct smbXsrv_connection *xconn,
                                         const uint8_t *_inpdu, size_t size,
                                         struct smbd_smb2_request **_req)
 {
-       struct smbd_server_connection *sconn = xconn->client->sconn;
        struct smbd_smb2_request *req;
        uint32_t protocol_version;
        uint8_t *inpdu = NULL;
@@ -616,8 +694,6 @@ static NTSTATUS smbd_smb2_request_create(struct smbXsrv_connection *xconn,
        if (req == NULL) {
                return NT_STATUS_NO_MEMORY;
        }
-       req->sconn = sconn;
-       req->xconn = xconn;
 
        inpdu = talloc_memdup(req, _inpdu, size);
        if (inpdu == NULL) {
@@ -791,7 +867,7 @@ static bool smb2_validate_message_id(struct smbXsrv_connection *xconn,
                }
        }
 
-       /* substract used credits */
+       /* subtract used credits */
        xconn->smb2.credits.granted -= credit_charge;
 
        return true;
@@ -1132,6 +1208,11 @@ bool smbXsrv_server_multi_channel_enabled(void)
        bool enabled = lp_server_multi_channel_support();
 #ifndef __ALLOW_MULTI_CHANNEL_SUPPORT
        bool forced = false;
+       struct loadparm_context *lp_ctx = loadparm_init_s3(NULL, loadparm_s3_helpers());
+       bool unspecified = lpcfg_parm_is_unspecified(lp_ctx, "server multi channel support");
+       if (unspecified) {
+               enabled = false;
+       }
        /*
         * If we don't have support from the kernel
         * to ask for the un-acked number of bytes
@@ -1147,6 +1228,7 @@ bool smbXsrv_server_multi_channel_enabled(void)
                        "https://bugzilla.samba.org/show_bug.cgi?id=11897\n"));
                enabled = false;
        }
+       TALLOC_FREE(lp_ctx);
 #endif /* ! __ALLOW_MULTI_CHANNEL_SUPPORT */
        return enabled;
 }
@@ -1636,6 +1718,7 @@ static void smbd_server_connection_terminate_done(struct tevent_req *subreq)
        NTSTATUS status;
 
        status = smbXsrv_connection_shutdown_recv(subreq);
+       TALLOC_FREE(subreq);
        if (!NT_STATUS_IS_OK(status)) {
                exit_server("smbXsrv_connection_shutdown_recv failed");
        }
@@ -1841,8 +1924,6 @@ static struct smbd_smb2_request *dup_smb2_req(const struct smbd_smb2_request *re
                return NULL;
        }
 
-       newreq->sconn = req->sconn;
-       newreq->xconn = req->xconn;
        newreq->session = req->session;
        newreq->do_encryption = req->do_encryption;
        newreq->do_signing = req->do_signing;
@@ -2119,19 +2200,27 @@ NTSTATUS smbd_smb2_request_pending_queue(struct smbd_smb2_request *req,
 
 static
 struct smb2_signing_key *smbd_smb2_signing_key(struct smbXsrv_session *session,
-                                              struct smbXsrv_connection *xconn)
+                                              struct smbXsrv_connection *xconn,
+                                              bool *_has_channel)
 {
        struct smbXsrv_channel_global0 *c = NULL;
        NTSTATUS status;
        struct smb2_signing_key *key = NULL;
+       bool has_channel = false;
 
        status = smbXsrv_session_find_channel(session, xconn, &c);
        if (NT_STATUS_IS_OK(status)) {
                key = c->signing_key;
+               has_channel = true;
        }
 
        if (!smb2_signing_key_valid(key)) {
                key = session->global->signing_key;
+               has_channel = false;
+       }
+
+       if (_has_channel != NULL) {
+               *_has_channel = has_channel;
        }
 
        return key;
@@ -2155,7 +2244,7 @@ static NTSTATUS smb2_get_new_nonce(struct smbXsrv_session *session,
         * nonce wrap, or the security of the whole
         * communication and the keys is destroyed.
         * We must drop the connection once we have
-        * transfered too much data.
+        * transferred too much data.
         *
         * NOTE: We assume nonces greater than 8 bytes.
         */
@@ -2257,6 +2346,11 @@ static void smbd_smb2_request_pending_timer(struct tevent_context *ev,
        SIVAL(hdr, SMB2_HDR_STATUS, NT_STATUS_V(NT_STATUS_PENDING));
        SSVAL(hdr, SMB2_HDR_OPCODE, SVAL(outhdr, SMB2_HDR_OPCODE));
 
+       /*
+        * The STATUS_PENDING response has SMB2_HDR_FLAG_SIGNED
+        * clearedm, but echoes the signature field.
+        */
+       flags &= ~SMB2_HDR_FLAG_SIGNED;
        SIVAL(hdr, SMB2_HDR_FLAGS, flags);
        SIVAL(hdr, SMB2_HDR_NEXT_COMMAND, 0);
        SBVAL(hdr, SMB2_HDR_MESSAGE_ID, message_id);
@@ -2310,6 +2404,10 @@ static void smbd_smb2_request_pending_timer(struct tevent_context *ev,
                        SMBD_SMB2_IN_HDR_IOV(req),
                        &state->vector[1+SMBD_SMB2_HDR_IOV_OFS]);
 
+       /*
+        * We add SMB2_HDR_FLAG_ASYNC after smb2_set_operation_credit()
+        * as it reacts on it
+        */
        SIVAL(hdr, SMB2_HDR_FLAGS, flags | SMB2_HDR_FLAG_ASYNC);
 
        if (DEBUGLVL(10)) {
@@ -2335,19 +2433,6 @@ static void smbd_smb2_request_pending_timer(struct tevent_context *ev,
                                                nt_errstr(status));
                        return;
                }
-       } else if (req->do_signing) {
-               struct smbXsrv_session *x = req->session;
-               struct smb2_signing_key *signing_key =
-                       smbd_smb2_signing_key(x, xconn);
-
-               status = smb2_signing_sign_pdu(signing_key,
-                                       &state->vector[1+SMBD_SMB2_HDR_IOV_OFS],
-                                       SMBD_SMB2_NUM_IOV_PER_REQ - 1);
-               if (!NT_STATUS_IS_OK(status)) {
-                       smbd_server_connection_terminate(xconn,
-                                               nt_errstr(status));
-                       return;
-               }
        }
 
        state->queue_entry.mem_ctx = state;
@@ -2393,6 +2478,10 @@ static NTSTATUS smbd_smb2_request_process_cancel(struct smbd_smb2_request *req)
                uint64_t message_id;
                uint64_t async_id;
 
+               if (cur->session != req->session) {
+                       continue;
+               }
+
                if (cur->compound_related) {
                        /*
                         * Never cancel anything in a compound request.
@@ -2767,9 +2856,6 @@ static void smb2srv_update_crypto_flags(struct smbd_smb2_request *req,
                /* Unencrypted packet, can be signed */
                if (req->do_signing) {
                        sign_flag = SMBXSRV_PROCESSED_SIGNED_PACKET;
-               } else if (opcode == SMB2_OP_CANCEL) {
-                       /* Cancel requests are allowed to skip signing */
-                       sign_flag &= ~SMBXSRV_PROCESSED_UNSIGNED_PACKET;
                }
        }
 
@@ -2964,9 +3050,9 @@ NTSTATUS smbd_smb2_request_dispatch(struct smbd_smb2_request *req)
        flags = IVAL(inhdr, SMB2_HDR_FLAGS);
        opcode = SVAL(inhdr, SMB2_HDR_OPCODE);
        mid = BVAL(inhdr, SMB2_HDR_MESSAGE_ID);
-       DEBUG(10,("smbd_smb2_request_dispatch: opcode[%s] mid = %llu\n",
-               smb2_opcode_name(opcode),
-               (unsigned long long)mid));
+       DBG_DEBUG("opcode[%s] mid = %"PRIu64"\n",
+                 smb2_opcode_name(opcode),
+                 mid);
 
        if (xconn->protocol >= PROTOCOL_SMB2_02) {
                /*
@@ -3083,6 +3169,7 @@ NTSTATUS smbd_smb2_request_dispatch(struct smbd_smb2_request *req)
                signing_required = false;
        } else if (signing_required || (flags & SMB2_HDR_FLAG_SIGNED)) {
                struct smb2_signing_key *signing_key = NULL;
+               bool has_channel = false;
 
                if (x == NULL) {
                        /*
@@ -3104,19 +3191,46 @@ NTSTATUS smbd_smb2_request_dispatch(struct smbd_smb2_request *req)
                        return smbd_smb2_request_error(req, status);
                }
 
-               signing_key = smbd_smb2_signing_key(x, xconn);
+               signing_key = smbd_smb2_signing_key(x, xconn, &has_channel);
 
                /*
                 * If we have a signing key, we should
                 * sign the response
                 */
-               if (smb2_signing_key_valid(signing_key)) {
+               if (smb2_signing_key_valid(signing_key) && opcode != SMB2_OP_CANCEL) {
                        req->do_signing = true;
                }
 
                status = smb2_signing_check_pdu(signing_key,
                                                SMBD_SMB2_IN_HDR_IOV(req),
                                                SMBD_SMB2_NUM_IOV_PER_REQ - 1);
+               if (NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED) &&
+                   opcode == SMB2_OP_SESSSETUP && !has_channel &&
+                   NT_STATUS_IS_OK(session_status))
+               {
+                       if (!NT_STATUS_EQUAL(x->status, NT_STATUS_BAD_LOGON_SESSION_STATE)) {
+                               struct smbXsrv_session *session = NULL;
+                               NTSTATUS error;
+
+                               error = smb2srv_session_lookup_global(req->xconn->client,
+                                                                     x->global->session_wire_id,
+                                                                     req,
+                                                                     &session);
+                               if (!NT_STATUS_IS_OK(error)) {
+                                       return smbd_smb2_request_error(req, error);
+                               }
+
+                               /*
+                                * We fallback to a session of
+                                * another process in order to
+                                * get the signing correct.
+                                *
+                                * We don't set req->last_session_id here.
+                                */
+                               req->session = x = session;
+                       }
+                       goto skipped_signing;
+               }
                if (!NT_STATUS_IS_OK(status)) {
                        return smbd_smb2_request_error(req, status);
                }
@@ -3125,14 +3239,16 @@ NTSTATUS smbd_smb2_request_dispatch(struct smbd_smb2_request *req)
                 * Now that we know the request was correctly signed
                 * we have to sign the response too.
                 */
-               req->do_signing = true;
+               if (opcode != SMB2_OP_CANCEL) {
+                       req->do_signing = true;
+               }
 
                if (!NT_STATUS_IS_OK(session_status)) {
                        return smbd_smb2_request_error(req, session_status);
                }
-       } else if (opcode == SMB2_OP_CANCEL) {
-               /* Cancel requests are allowed to skip the signing */
-       } else if (opcode == SMB2_OP_IOCTL) {
+       }
+
+       if (opcode == SMB2_OP_IOCTL) {
                /*
                 * Some special IOCTL calls don't require
                 * file, tcon nor session.
@@ -3146,13 +3262,13 @@ NTSTATUS smbd_smb2_request_dispatch(struct smbd_smb2_request *req)
                 * checks below.
                 */
                static const struct smbd_smb2_dispatch_table _root_ioctl_call = {
-                       _OP(SMB2_OP_IOCTL),
+                       .opcode = SMB2_OP_IOCTL,
                        .as_root = true,
                };
                const uint8_t *body = SMBD_SMB2_IN_BODY_PTR(req);
                size_t body_size = SMBD_SMB2_IN_BODY_LEN(req);
                uint32_t in_ctl_code;
-               size_t needed = 4;
+               size_t needed = 8;
 
                if (needed > body_size) {
                        return smbd_smb2_request_error(req,
@@ -3167,16 +3283,17 @@ NTSTATUS smbd_smb2_request_dispatch(struct smbd_smb2_request *req)
                case FSCTL_SMBTORTURE_FORCE_UNACKED_TIMEOUT:
                        call = &_root_ioctl_call;
                        break;
+               case FSCTL_VALIDATE_NEGOTIATE_INFO:
+                       call = &_root_ioctl_call;
+                       break;
+               case FSCTL_QUERY_NETWORK_INTERFACE_INFO:
+                       call = &_root_ioctl_call;
+                       break;
                }
-       } else if (signing_required) {
-               /*
-                * If signing is required we try to sign
-                * a possible error response
-                */
-               req->do_signing = true;
-               return smbd_smb2_request_error(req, NT_STATUS_ACCESS_DENIED);
        }
 
+skipped_signing:
+
        if (flags & SMB2_HDR_FLAG_CHAINED) {
                req->compound_related = true;
        }
@@ -3281,16 +3398,12 @@ NTSTATUS smbd_smb2_request_dispatch(struct smbd_smb2_request *req)
                                return smbd_smb2_request_error(req,
                                                req->compound_create_err);
                        }
-                       if (!call->allow_invalid_fileid) {
-                               return smbd_smb2_request_error(req,
-                                               NT_STATUS_FILE_CLOSED);
-                       }
-
-                       if (file_id_persistent != UINT64_MAX) {
-                               return smbd_smb2_request_error(req,
-                                               NT_STATUS_FILE_CLOSED);
-                       }
-                       if (file_id_volatile != UINT64_MAX) {
+                       /*
+                        * smbd_smb2_request_process_ioctl()
+                        * has more checks in order to return more
+                        * detailed error codes...
+                        */
+                       if (opcode != SMB2_OP_IOCTL) {
                                return smbd_smb2_request_error(req,
                                                NT_STATUS_FILE_CLOSED);
                        }
@@ -3311,7 +3424,7 @@ NTSTATUS smbd_smb2_request_dispatch(struct smbd_smb2_request *req)
                SMB_ASSERT(call->fileid_ofs == 0);
                /* This call needs to be run as root */
                change_to_root_user();
-       } else {
+       } else if (opcode != SMB2_OP_KEEPALIVE) {
                SMB_ASSERT(call->need_tcon);
        }
 
@@ -3606,7 +3719,7 @@ static NTSTATUS smbd_smb2_request_reply(struct smbd_smb2_request *req)
                if (req->do_signing && firsttf->iov_len == 0) {
                        struct smbXsrv_session *x = req->session;
                        struct smb2_signing_key *signing_key =
-                               smbd_smb2_signing_key(x, xconn);
+                               smbd_smb2_signing_key(x, xconn, NULL);
 
                        /*
                         * we need to remember the signing key
@@ -3660,7 +3773,7 @@ static NTSTATUS smbd_smb2_request_reply(struct smbd_smb2_request *req)
        } else if (req->do_signing) {
                struct smbXsrv_session *x = req->session;
                struct smb2_signing_key *signing_key =
-                       smbd_smb2_signing_key(x, xconn);
+                       smbd_smb2_signing_key(x, xconn, NULL);
 
                status = smb2_signing_sign_pdu(signing_key,
                                               outhdr,
@@ -3903,6 +4016,7 @@ NTSTATUS smbd_smb2_request_done_ex(struct smbd_smb2_request *req,
 
 NTSTATUS smbd_smb2_request_error_ex(struct smbd_smb2_request *req,
                                    NTSTATUS status,
+                                   uint8_t error_context_count,
                                    DATA_BLOB *info,
                                    const char *location)
 {
@@ -3942,6 +4056,7 @@ NTSTATUS smbd_smb2_request_error_ex(struct smbd_smb2_request *req,
        body.data = outhdr + SMB2_HDR_BODY;
        body.length = 8;
        SSVAL(body.data, 0, 9);
+       SCVAL(body.data, 2, error_context_count);
 
        if (info) {
                SIVAL(body.data, 0x04, info->length);
@@ -4268,7 +4383,7 @@ static void smbXsrv_pending_break_done(struct tevent_req *subreq)
                status = smbXsrv_pending_break_submit(pb);
                if (NT_STATUS_EQUAL(status, NT_STATUS_ABANDONED)) {
                        /*
-                        * If there's no remaing connection
+                        * If there's no remaining connection
                         * there's no need to send a break again.
                         */
                        goto remove;
@@ -4422,7 +4537,7 @@ static bool is_smb2_recvfile_write(struct smbd_smb2_request_read_state *state)
        if (IS_PRINT(fsp->conn)) {
                return false;
        }
-       if (fsp->base_fsp != NULL) {
+       if (fsp_is_alternate_stream(fsp)) {
                return false;
        }
 
@@ -4434,8 +4549,8 @@ static bool is_smb2_recvfile_write(struct smbd_smb2_request_read_state *state)
 
 static NTSTATUS smbd_smb2_request_next_incoming(struct smbXsrv_connection *xconn)
 {
-       struct smbd_server_connection *sconn = xconn->client->sconn;
        struct smbd_smb2_request_read_state *state = &xconn->smb2.request_read_state;
+       struct smbd_smb2_request *req = NULL;
        size_t max_send_queue_len;
        size_t cur_send_queue_len;
 
@@ -4467,14 +4582,22 @@ static NTSTATUS smbd_smb2_request_next_incoming(struct smbXsrv_connection *xconn
        }
 
        /* ask for the next request */
-       ZERO_STRUCTP(state);
-       state->req = smbd_smb2_request_allocate(xconn);
-       if (state->req == NULL) {
+       req = smbd_smb2_request_allocate(xconn);
+       if (req == NULL) {
                return NT_STATUS_NO_MEMORY;
        }
-       state->req->sconn = sconn;
-       state->req->xconn = xconn;
-       state->min_recv_size = lp_min_receive_file_size();
+       *state = (struct smbd_smb2_request_read_state) {
+               .req = req,
+               .min_recv_size = lp_min_receive_file_size(),
+               ._vector = {
+                       [0] = (struct iovec) {
+                               .iov_base = (void *)state->hdr.nbt,
+                               .iov_len = NBT_HDR_SIZE,
+                       },
+               },
+               .vector = state->_vector,
+               .count = 1,
+       };
 
        TEVENT_FD_READABLE(xconn->transport.fde);
 
@@ -4609,7 +4732,40 @@ static int socket_error_from_errno(int ret,
        return sys_errno;
 }
 
-static NTSTATUS smbd_smb2_flush_send_queue(struct smbXsrv_connection *xconn)
+static NTSTATUS smbd_smb2_advance_send_queue(struct smbXsrv_connection *xconn,
+                                            struct smbd_smb2_send_queue **_e,
+                                            size_t n)
+{
+       struct smbd_smb2_send_queue *e = *_e;
+       bool ok;
+
+       xconn->ack.unacked_bytes += n;
+
+       ok = iov_advance(&e->vector, &e->count, n);
+       if (!ok) {
+               return NT_STATUS_INTERNAL_ERROR;
+       }
+
+       if (e->count > 0) {
+               return NT_STATUS_RETRY;
+       }
+
+       xconn->smb2.send_queue_len--;
+       DLIST_REMOVE(xconn->smb2.send_queue, e);
+
+       if (e->ack.req == NULL) {
+               *_e = NULL;
+               talloc_free(e->mem_ctx);
+               return NT_STATUS_OK;
+       }
+
+       e->ack.required_acked_bytes = xconn->ack.unacked_bytes;
+       DLIST_ADD_END(xconn->ack.queue, e);
+
+       return NT_STATUS_OK;
+}
+
+static NTSTATUS smbd_smb2_flush_with_sendmsg(struct smbXsrv_connection *xconn)
 {
        int ret;
        int err;
@@ -4623,8 +4779,7 @@ static NTSTATUS smbd_smb2_flush_send_queue(struct smbXsrv_connection *xconn)
 
        while (xconn->smb2.send_queue != NULL) {
                struct smbd_smb2_send_queue *e = xconn->smb2.send_queue;
-               bool ok;
-               struct msghdr msg;
+               unsigned sendmsg_flags = 0;
 
                if (!NT_STATUS_IS_OK(xconn->transport.status)) {
                        /*
@@ -4691,12 +4846,19 @@ static NTSTATUS smbd_smb2_flush_send_queue(struct smbXsrv_connection *xconn)
                        continue;
                }
 
-               msg = (struct msghdr) {
+               e->msg = (struct msghdr) {
                        .msg_iov = e->vector,
                        .msg_iovlen = e->count,
                };
 
-               ret = sendmsg(xconn->transport.sock, &msg, 0);
+#ifdef MSG_NOSIGNAL
+               sendmsg_flags |= MSG_NOSIGNAL;
+#endif
+#ifdef MSG_DONTWAIT
+               sendmsg_flags |= MSG_DONTWAIT;
+#endif
+
+               ret = sendmsg(xconn->transport.sock, &e->msg, sendmsg_flags);
                if (ret == 0) {
                        /* propagate end of file */
                        return NT_STATUS_INTERNAL_ERROR;
@@ -4714,29 +4876,29 @@ static NTSTATUS smbd_smb2_flush_send_queue(struct smbXsrv_connection *xconn)
                        return status;
                }
 
-               xconn->ack.unacked_bytes += ret;
-
-               ok = iov_advance(&e->vector, &e->count, ret);
-               if (!ok) {
-                       return NT_STATUS_INTERNAL_ERROR;
-               }
-
-               if (e->count > 0) {
-                       /* we have more to write */
+               status = smbd_smb2_advance_send_queue(xconn, &e, ret);
+               if (NT_STATUS_EQUAL(status, NT_STATUS_RETRY)) {
+                       /* retry later */
                        TEVENT_FD_WRITEABLE(xconn->transport.fde);
                        return NT_STATUS_OK;
                }
+               if (!NT_STATUS_IS_OK(status)) {
+                       smbXsrv_connection_disconnect_transport(xconn,
+                                                               status);
+                       return status;
+               }
+       }
 
-               xconn->smb2.send_queue_len--;
-               DLIST_REMOVE(xconn->smb2.send_queue, e);
+       return NT_STATUS_MORE_PROCESSING_REQUIRED;
+}
 
-               if (e->ack.req == NULL) {
-                       talloc_free(e->mem_ctx);
-                       continue;
-               }
+static NTSTATUS smbd_smb2_flush_send_queue(struct smbXsrv_connection *xconn)
+{
+       NTSTATUS status;
 
-               e->ack.required_acked_bytes = xconn->ack.unacked_bytes;
-               DLIST_ADD_END(xconn->ack.queue, e);
+       status = smbd_smb2_flush_with_sendmsg(xconn);
+       if (!NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
+               return status;
        }
 
        /*
@@ -4752,92 +4914,36 @@ static NTSTATUS smbd_smb2_flush_send_queue(struct smbXsrv_connection *xconn)
        return NT_STATUS_OK;
 }
 
-static NTSTATUS smbd_smb2_io_handler(struct smbXsrv_connection *xconn,
-                                    uint16_t fde_flags)
+static NTSTATUS smbd_smb2_advance_incoming(struct smbXsrv_connection *xconn, size_t n)
 {
        struct smbd_server_connection *sconn = xconn->client->sconn;
        struct smbd_smb2_request_read_state *state = &xconn->smb2.request_read_state;
        struct smbd_smb2_request *req = NULL;
        size_t min_recvfile_size = UINT32_MAX;
-       int ret;
-       int err;
-       bool retry;
        NTSTATUS status;
        NTTIME now;
-       struct msghdr msg;
-
-       if (!NT_STATUS_IS_OK(xconn->transport.status)) {
-               /*
-                * we're not supposed to do any io
-                */
-               TEVENT_FD_NOT_READABLE(xconn->transport.fde);
-               TEVENT_FD_NOT_WRITEABLE(xconn->transport.fde);
-               return NT_STATUS_OK;
-       }
-
-       if (fde_flags & TEVENT_FD_WRITE) {
-               status = smbd_smb2_flush_send_queue(xconn);
-               if (!NT_STATUS_IS_OK(status)) {
-                       return status;
-               }
-       }
-
-       if (!(fde_flags & TEVENT_FD_READ)) {
-               return NT_STATUS_OK;
-       }
+       bool ok;
 
-       if (state->req == NULL) {
-               TEVENT_FD_NOT_READABLE(xconn->transport.fde);
-               return NT_STATUS_OK;
+       ok = iov_advance(&state->vector, &state->count, n);
+       if (!ok) {
+               return NT_STATUS_INTERNAL_ERROR;
        }
 
-again:
-       if (!state->hdr.done) {
-               state->hdr.done = true;
-
-               state->vector.iov_base = (void *)state->hdr.nbt;
-               state->vector.iov_len = NBT_HDR_SIZE;
+       if (state->count > 0) {
+               return NT_STATUS_PENDING;
        }
 
-       msg = (struct msghdr) {
-               .msg_iov = &state->vector,
-               .msg_iovlen = 1,
-       };
-
-       ret = recvmsg(xconn->transport.sock, &msg, 0);
-       if (ret == 0) {
-               /* propagate end of file */
-               status = NT_STATUS_END_OF_FILE;
-               smbXsrv_connection_disconnect_transport(xconn,
-                                                       status);
-               return status;
-       }
-       err = socket_error_from_errno(ret, errno, &retry);
-       if (retry) {
-               /* retry later */
-               TEVENT_FD_READABLE(xconn->transport.fde);
-               return NT_STATUS_OK;
-       }
-       if (err != 0) {
-               status = map_nt_error_from_unix_common(err);
-               smbXsrv_connection_disconnect_transport(xconn,
-                                                       status);
-               return status;
-       }
+       if (state->pktlen > 0) {
+               if (!state->doing_receivefile) {
+                       /*
+                        * we have all the data.
+                        */
+                       goto got_full;
+               }
 
-       if (ret < state->vector.iov_len) {
-               uint8_t *base;
-               base = (uint8_t *)state->vector.iov_base;
-               base += ret;
-               state->vector.iov_base = (void *)base;
-               state->vector.iov_len -= ret;
-               /* we have more to read */
-               TEVENT_FD_READABLE(xconn->transport.fde);
-               return NT_STATUS_OK;
-       }
+               if (!is_smb2_recvfile_write(state)) {
+                       size_t ofs = state->pktlen;
 
-       if (state->pktlen > 0) {
-               if (state->doing_receivefile && !is_smb2_recvfile_write(state)) {
                        /*
                         * Not a possible receivefile write.
                         * Read the rest of the data.
@@ -4852,18 +4958,20 @@ again:
                                return NT_STATUS_NO_MEMORY;
                        }
 
-                       state->vector.iov_base = (void *)(state->pktbuf +
-                               state->pktlen);
-                       state->vector.iov_len = (state->pktfull -
-                               state->pktlen);
+                       state->_vector[0]  = (struct iovec) {
+                               .iov_base = (void *)(state->pktbuf + ofs),
+                               .iov_len = (state->pktfull - ofs),
+                       };
+                       state->vector = state->_vector;
+                       state->count = 1;
 
                        state->pktlen = state->pktfull;
-                       goto again;
+                       return NT_STATUS_RETRY;
                }
 
                /*
-                * Either this is a receivefile write so we've
-                * done a short read, or if not we have all the data.
+                * This is a receivefile write so we've
+                * done a short read.
                 */
                goto got_full;
        }
@@ -4904,10 +5012,14 @@ again:
                return NT_STATUS_NO_MEMORY;
        }
 
-       state->vector.iov_base = (void *)state->pktbuf;
-       state->vector.iov_len = state->pktlen;
+       state->_vector[0] = (struct iovec) {
+               .iov_base = (void *)state->pktbuf,
+               .iov_len = state->pktlen,
+       };
+       state->vector = state->_vector;
+       state->count = 1;
 
-       goto again;
+       return NT_STATUS_RETRY;
 
 got_full:
 
@@ -4916,15 +5028,22 @@ got_full:
                         state->hdr.nbt[0]));
 
                req = state->req;
-               ZERO_STRUCTP(state);
-               state->req = req;
-               state->min_recv_size = lp_min_receive_file_size();
-               req = NULL;
-               goto again;
+               *state = (struct smbd_smb2_request_read_state) {
+                       .req = req,
+                       .min_recv_size = lp_min_receive_file_size(),
+                       ._vector = {
+                               [0] = (struct iovec) {
+                                       .iov_base = (void *)state->hdr.nbt,
+                                       .iov_len = NBT_HDR_SIZE,
+                               },
+                       },
+                       .vector = state->_vector,
+                       .count = 1,
+               };
+               return NT_STATUS_RETRY;
        }
 
        req = state->req;
-       state->req = NULL;
 
        req->request_time = timeval_current();
        now = timeval_to_nttime(&req->request_time);
@@ -4948,7 +5067,9 @@ got_full:
                req->smb1req->unread_bytes = state->pktfull - state->pktlen;
        }
 
-       ZERO_STRUCTP(state);
+       *state = (struct smbd_smb2_request_read_state) {
+               .req = NULL,
+       };
 
        req->current_idx = 1;
 
@@ -4993,6 +5114,96 @@ got_full:
        return NT_STATUS_OK;
 }
 
+static NTSTATUS smbd_smb2_io_handler(struct smbXsrv_connection *xconn,
+                                    uint16_t fde_flags)
+{
+       struct smbd_smb2_request_read_state *state = &xconn->smb2.request_read_state;
+       unsigned recvmsg_flags = 0;
+       int ret;
+       int err;
+       bool retry;
+       NTSTATUS status;
+
+       if (!NT_STATUS_IS_OK(xconn->transport.status)) {
+               /*
+                * we're not supposed to do any io
+                */
+               TEVENT_FD_NOT_READABLE(xconn->transport.fde);
+               TEVENT_FD_NOT_WRITEABLE(xconn->transport.fde);
+               return NT_STATUS_OK;
+       }
+
+       if (fde_flags & TEVENT_FD_WRITE) {
+               status = smbd_smb2_flush_send_queue(xconn);
+               if (!NT_STATUS_IS_OK(status)) {
+                       return status;
+               }
+       }
+
+       if (!(fde_flags & TEVENT_FD_READ)) {
+               return NT_STATUS_OK;
+       }
+
+       if (state->req == NULL) {
+               TEVENT_FD_NOT_READABLE(xconn->transport.fde);
+               return NT_STATUS_OK;
+       }
+
+again:
+
+       state->msg = (struct msghdr) {
+               .msg_iov = state->vector,
+               .msg_iovlen = state->count,
+       };
+
+#ifdef MSG_NOSIGNAL
+       recvmsg_flags |= MSG_NOSIGNAL;
+#endif
+#ifdef MSG_DONTWAIT
+       recvmsg_flags |= MSG_DONTWAIT;
+#endif
+
+       ret = recvmsg(xconn->transport.sock, &state->msg, recvmsg_flags);
+       if (ret == 0) {
+               /* propagate end of file */
+               status = NT_STATUS_END_OF_FILE;
+               smbXsrv_connection_disconnect_transport(xconn,
+                                                       status);
+               return status;
+       }
+       err = socket_error_from_errno(ret, errno, &retry);
+       if (retry) {
+               /* retry later */
+               TEVENT_FD_READABLE(xconn->transport.fde);
+               return NT_STATUS_OK;
+       }
+       if (err != 0) {
+               status = map_nt_error_from_unix_common(err);
+               smbXsrv_connection_disconnect_transport(xconn,
+                                                       status);
+               return status;
+       }
+
+       status = smbd_smb2_advance_incoming(xconn, ret);
+       if (NT_STATUS_EQUAL(status, NT_STATUS_PENDING)) {
+               /* we have more to read */
+               TEVENT_FD_READABLE(xconn->transport.fde);
+               return NT_STATUS_OK;
+       }
+       if (NT_STATUS_EQUAL(status, NT_STATUS_RETRY)) {
+               /*
+                * smbd_smb2_advance_incoming setup a new vector
+                * that we should try to read immediately.
+                */
+               goto again;
+       }
+       if (!NT_STATUS_IS_OK(status)) {
+               return status;
+       }
+
+       return NT_STATUS_OK;
+}
+
 static void smbd_smb2_connection_handler(struct tevent_context *ev,
                                         struct tevent_fd *fde,
                                         uint16_t flags,