smb2_server: move struct msghdr to smbd_smb2_send_queue
[samba.git] / source3 / smbd / smb2_server.c
index 042f343b0ca0832e7656a220044c716fb010a5fc..55b383072e6a3cf1bca466127fed71c978578e3b 100644 (file)
@@ -62,26 +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 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.
@@ -92,75 +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,
                .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,
                /*
@@ -174,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)
@@ -229,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)
 {
@@ -261,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;
 }
@@ -310,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;
@@ -325,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);
 
@@ -575,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;
@@ -617,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) {
@@ -792,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;
@@ -1643,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");
        }
@@ -1848,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;
@@ -2170,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.
         */
@@ -2976,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) {
                /*
@@ -3188,7 +3262,7 @@ 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);
@@ -3350,7 +3424,7 @@ skipped_signing:
                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);
        }
 
@@ -3942,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)
 {
@@ -3981,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);
@@ -4307,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;
@@ -4461,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;
        }
 
@@ -4473,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;
 
@@ -4506,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);
 
@@ -4648,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;
@@ -4662,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)) {
                        /*
@@ -4730,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;
@@ -4753,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;
        }
 
        /*
@@ -4791,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.
@@ -4891,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;
        }
@@ -4943,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:
 
@@ -4955,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);
@@ -4987,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;
 
@@ -5032,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,