s3: only include smb profiling where needed.
[nivanova/samba-autobuild/.git] / source3 / smbd / smb2_server.c
index c889555a1cbaf5c37196c4ea0f8c6da8c657d84a..0004e7ca8caf781cc831f491979a258f26ce4a99 100644 (file)
@@ -3,6 +3,7 @@
    Core SMB2 server
 
    Copyright (C) Stefan Metzmacher 2009
+   Copyright (C) Jeremy Allison 2010
 
    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
 */
 
 #include "includes.h"
+#include "smbd/smbd.h"
 #include "smbd/globals.h"
-#include "../source4/libcli/smb2/smb2_constants.h"
+#include "../libcli/smb/smb_common.h"
 #include "../lib/tsocket/tsocket.h"
+#include "smbprofile.h"
+
+#define OUTVEC_ALLOC_SIZE (SMB2_HDR_BODY + 9)
+
+static const char *smb2_names[] = {
+       "SMB2_NEGPROT",
+       "SMB2_SESSSETUP",
+       "SMB2_LOGOFF",
+       "SMB2_TCON",
+       "SMB2_TDIS",
+       "SMB2_CREATE",
+       "SMB2_CLOSE",
+       "SMB2_FLUSH",
+       "SMB2_READ",
+       "SMB2_WRITE",
+       "SMB2_LOCK",
+       "SMB2_IOCTL",
+       "SMB2_CANCEL",
+       "SMB2_KEEPALIVE",
+       "SMB2_FIND",
+       "SMB2_NOTIFY",
+       "SMB2_GETINFO",
+       "SMB2_SETINFO",
+       "SMB2_BREAK"
+};
+
+const char *smb2_opcode_name(uint16_t opcode)
+{
+       if (opcode > 0x12) {
+               return "Bad SMB2 opcode";
+       }
+       return smb2_names[opcode];
+}
+
+static void print_req_vectors(struct smbd_smb2_request *req)
+{
+       int i;
+
+       for (i = 0; i < req->in.vector_count; i++) {
+               dbgtext("\treq->in.vector[%u].iov_len = %u\n",
+                       (unsigned int)i,
+                       (unsigned int)req->in.vector[i].iov_len);
+       }
+       for (i = 0; i < req->out.vector_count; i++) {
+               dbgtext("\treq->out.vector[%u].iov_len = %u\n",
+                       (unsigned int)i,
+                       (unsigned int)req->out.vector[i].iov_len);
+       }
+}
 
 bool smbd_is_smb2_header(const uint8_t *inbuf, size_t size)
 {
@@ -36,41 +87,49 @@ bool smbd_is_smb2_header(const uint8_t *inbuf, size_t size)
        return true;
 }
 
-static NTSTATUS smbd_initialize_smb2(struct smbd_server_connection *conn)
+static NTSTATUS smbd_initialize_smb2(struct smbd_server_connection *sconn)
 {
        NTSTATUS status;
        int ret;
 
-       TALLOC_FREE(conn->smb1.fde);
+       TALLOC_FREE(sconn->smb1.fde);
 
-       conn->smb2.event_ctx = smbd_event_context();
+       sconn->smb2.event_ctx = smbd_event_context();
 
-       conn->smb2.recv_queue = tevent_queue_create(conn, "smb2 recv queue");
-       if (conn->smb2.recv_queue == NULL) {
+       sconn->smb2.recv_queue = tevent_queue_create(sconn, "smb2 recv queue");
+       if (sconn->smb2.recv_queue == NULL) {
                return NT_STATUS_NO_MEMORY;
        }
 
-       conn->smb2.send_queue = tevent_queue_create(conn, "smb2 send queue");
-       if (conn->smb2.send_queue == NULL) {
+       sconn->smb2.send_queue = tevent_queue_create(sconn, "smb2 send queue");
+       if (sconn->smb2.send_queue == NULL) {
                return NT_STATUS_NO_MEMORY;
        }
 
-       conn->smb2.sessions.idtree = idr_init(conn);
-       if (conn->smb2.sessions.idtree == NULL) {
+       sconn->smb2.sessions.idtree = idr_init(sconn);
+       if (sconn->smb2.sessions.idtree == NULL) {
+               return NT_STATUS_NO_MEMORY;
+       }
+       sconn->smb2.sessions.limit = 0x0000FFFE;
+       sconn->smb2.sessions.list = NULL;
+       sconn->smb2.seqnum_low = 0;
+       sconn->smb2.credits_granted = 0;
+       sconn->smb2.max_credits = lp_smb2_max_credits();
+       sconn->smb2.credits_bitmap = bitmap_talloc(sconn,
+                       DEFAULT_SMB2_MAX_CREDIT_BITMAP_FACTOR*sconn->smb2.max_credits);
+       if (sconn->smb2.credits_bitmap == NULL) {
                return NT_STATUS_NO_MEMORY;
        }
-       conn->smb2.sessions.limit = 0x0000FFFE;
-       conn->smb2.sessions.list = NULL;
 
-       ret = tstream_bsd_existing_socket(conn, smbd_server_fd(),
-                                         &conn->smb2.stream);
+       ret = tstream_bsd_existing_socket(sconn, sconn->sock,
+                                         &sconn->smb2.stream);
        if (ret == -1) {
                status = map_nt_error_from_unix(errno);
                return status;
        }
 
        /* Ensure child is set to non-blocking mode */
-       set_blocking(smbd_server_fd(),false);
+       set_blocking(sconn->sock, false);
        return NT_STATUS_OK;
 }
 
@@ -95,11 +154,67 @@ static void smb2_setup_nbt_length(struct iovec *vector, int count)
        _smb2_setlen(vector[0].iov_base, len);
 }
 
-static NTSTATUS smbd_smb2_request_create(struct smbd_server_connection *conn,
+static int smbd_smb2_request_parent_destructor(struct smbd_smb2_request **req)
+{
+       if (*req) {
+               (*req)->parent = NULL;
+               (*req)->mem_pool = NULL;
+       }
+
+       return 0;
+}
+
+static int smbd_smb2_request_destructor(struct smbd_smb2_request *req)
+{
+       if (req->parent) {
+               *req->parent = NULL;
+               talloc_free(req->mem_pool);
+       }
+
+       return 0;
+}
+
+static struct smbd_smb2_request *smbd_smb2_request_allocate(TALLOC_CTX *mem_ctx)
+{
+       TALLOC_CTX *mem_pool;
+       struct smbd_smb2_request **parent;
+       struct smbd_smb2_request *req;
+
+#if 0
+       /* Enable this to find subtle valgrind errors. */
+       mem_pool = talloc_init("smbd_smb2_request_allocate");
+#else
+       mem_pool = talloc_pool(mem_ctx, 8192);
+#endif
+       if (mem_pool == NULL) {
+               return NULL;
+       }
+
+       parent = talloc(mem_pool, struct smbd_smb2_request *);
+       if (parent == NULL) {
+               talloc_free(mem_pool);
+               return NULL;
+       }
+
+       req = talloc_zero(parent, struct smbd_smb2_request);
+       if (req == NULL) {
+               talloc_free(mem_pool);
+               return NULL;
+       }
+       *parent         = req;
+       req->mem_pool   = mem_pool;
+       req->parent     = parent;
+
+       talloc_set_destructor(parent, smbd_smb2_request_parent_destructor);
+       talloc_set_destructor(req, smbd_smb2_request_destructor);
+
+       return req;
+}
+
+static NTSTATUS smbd_smb2_request_create(struct smbd_server_connection *sconn,
                                         const uint8_t *inbuf, size_t size,
                                         struct smbd_smb2_request **_req)
 {
-       TALLOC_CTX *mem_pool;
        struct smbd_smb2_request *req;
        uint32_t protocol_version;
        const uint8_t *inhdr = NULL;
@@ -135,24 +250,17 @@ static NTSTATUS smbd_smb2_request_create(struct smbd_server_connection *conn,
                return NT_STATUS_INVALID_PARAMETER;
        }
 
-       mem_pool = talloc_pool(conn, 8192);
-       if (mem_pool == NULL) {
-               return NT_STATUS_NO_MEMORY;
-       }
-
-       req = talloc_zero(mem_pool, struct smbd_smb2_request);
+       req = smbd_smb2_request_allocate(sconn);
        if (req == NULL) {
-               talloc_free(mem_pool);
                return NT_STATUS_NO_MEMORY;
        }
-       req->mem_pool   = mem_pool;
-       req->conn       = conn;
+       req->sconn = sconn;
 
        talloc_steal(req, inbuf);
 
        req->in.vector = talloc_array(req, struct iovec, 4);
        if (req->in.vector == NULL) {
-               talloc_free(mem_pool);
+               TALLOC_FREE(req);
                return NT_STATUS_NO_MEMORY;
        }
        req->in.vector_count = 4;
@@ -186,6 +294,64 @@ static NTSTATUS smbd_smb2_request_create(struct smbd_server_connection *conn,
        return NT_STATUS_OK;
 }
 
+static bool smb2_validate_message_id(struct smbd_server_connection *sconn,
+                               const uint8_t *inhdr)
+{
+       uint64_t message_id = BVAL(inhdr, SMB2_HDR_MESSAGE_ID);
+       struct bitmap *credits_bm = sconn->smb2.credits_bitmap;
+       uint16_t opcode = IVAL(inhdr, SMB2_HDR_OPCODE);
+       unsigned int bitmap_offset;
+
+       if (opcode == SMB2_OP_CANCEL) {
+               /* SMB2_CANCEL requests by definition resend messageids. */
+               return true;
+       }
+
+       if (message_id < sconn->smb2.seqnum_low ||
+                       message_id > (sconn->smb2.seqnum_low +
+                       (sconn->smb2.max_credits * DEFAULT_SMB2_MAX_CREDIT_BITMAP_FACTOR))) {
+               DEBUG(0,("smb2_validate_message_id: bad message_id "
+                       "%llu (low = %llu, max = %lu)\n",
+                       (unsigned long long)message_id,
+                       (unsigned long long)sconn->smb2.seqnum_low,
+                       (unsigned long)sconn->smb2.max_credits ));
+               return false;
+       }
+
+       /* client just used a credit. */
+       SMB_ASSERT(sconn->smb2.credits_granted > 0);
+       sconn->smb2.credits_granted -= 1;
+
+       /* Mark the message_id as seen in the bitmap. */
+       bitmap_offset = (unsigned int)(message_id %
+                       (uint64_t)(sconn->smb2.max_credits * DEFAULT_SMB2_MAX_CREDIT_BITMAP_FACTOR));
+       if (bitmap_query(credits_bm, bitmap_offset)) {
+               DEBUG(0,("smb2_validate_message_id: duplicate message_id "
+                       "%llu (bm offset %u)\n",
+                       (unsigned long long)message_id,
+                       bitmap_offset));
+               return false;
+       }
+       bitmap_set(credits_bm, bitmap_offset);
+
+       if (message_id == sconn->smb2.seqnum_low + 1) {
+               /* Move the window forward by all the message_id's
+                  already seen. */
+               while (bitmap_query(credits_bm, bitmap_offset)) {
+                       DEBUG(10,("smb2_validate_message_id: clearing "
+                               "id %llu (position %u) from bitmap\n",
+                               (unsigned long long)(sconn->smb2.seqnum_low + 1),
+                               bitmap_offset ));
+                       bitmap_clear(credits_bm, bitmap_offset);
+                       sconn->smb2.seqnum_low += 1;
+                       bitmap_offset = (bitmap_offset + 1) %
+                               (sconn->smb2.max_credits * DEFAULT_SMB2_MAX_CREDIT_BITMAP_FACTOR);
+               }
+       }
+
+       return true;
+}
+
 static NTSTATUS smbd_smb2_request_validate(struct smbd_smb2_request *req)
 {
        int count;
@@ -213,11 +379,15 @@ static NTSTATUS smbd_smb2_request_validate(struct smbd_smb2_request *req)
 
                inhdr = (const uint8_t *)req->in.vector[idx].iov_base;
 
-               /* setup the SMB2 header */
+               /* Check the SMB2 header */
                if (IVAL(inhdr, SMB2_HDR_PROTOCOL_ID) != SMB2_MAGIC) {
                        return NT_STATUS_INVALID_PARAMETER;
                }
 
+               if (!smb2_validate_message_id(req->sconn, inhdr)) {
+                       return NT_STATUS_INVALID_PARAMETER;
+               }
+
                flags = IVAL(inhdr, SMB2_HDR_FLAGS);
                if (idx == 1) {
                        /*
@@ -266,6 +436,55 @@ static NTSTATUS smbd_smb2_request_validate(struct smbd_smb2_request *req)
        return NT_STATUS_OK;
 }
 
+static void smb2_set_operation_credit(struct smbd_server_connection *sconn,
+                       const struct iovec *in_vector,
+                       struct iovec *out_vector)
+{
+       uint8_t *outhdr = (uint8_t *)out_vector->iov_base;
+       uint16_t credits_requested = 0;
+       uint16_t credits_granted = 0;
+
+       if (in_vector != NULL) {
+               const uint8_t *inhdr = (const uint8_t *)in_vector->iov_base;
+               credits_requested = SVAL(inhdr, SMB2_HDR_CREDIT);
+       }
+
+       SMB_ASSERT(sconn->smb2.max_credits >= sconn->smb2.credits_granted);
+
+       /* Remember what we gave out. */
+       credits_granted = MIN(credits_requested, (sconn->smb2.max_credits -
+               sconn->smb2.credits_granted));
+
+       if (credits_granted == 0 && sconn->smb2.credits_granted == 0) {
+               /* First negprot packet, or ensure the client credits can
+                  never drop to zero. */
+               credits_granted = 1;
+       }
+
+       SSVAL(outhdr, SMB2_HDR_CREDIT, credits_granted);
+       sconn->smb2.credits_granted += credits_granted;
+
+       DEBUG(10,("smb2_set_operation_credit: requested %u, "
+               "granted %u, total granted %u\n",
+               (unsigned int)credits_requested,
+               (unsigned int)credits_granted,
+               (unsigned int)sconn->smb2.credits_granted ));
+}
+
+static void smb2_calculate_credits(const struct smbd_smb2_request *inreq,
+                               struct smbd_smb2_request *outreq)
+{
+       int count, idx;
+
+       count = outreq->out.vector_count;
+
+       for (idx=1; idx < count; idx += 3) {
+               smb2_set_operation_credit(outreq->sconn,
+                       &inreq->in.vector[idx],
+                       &outreq->out.vector[idx]);
+       }
+}
+
 static NTSTATUS smbd_smb2_request_setup_out(struct smbd_smb2_request *req)
 {
        struct iovec *vector;
@@ -273,7 +492,7 @@ static NTSTATUS smbd_smb2_request_setup_out(struct smbd_smb2_request *req)
        int idx;
 
        count = req->in.vector_count;
-       vector = talloc_array(req, struct iovec, count);
+       vector = talloc_zero_array(req, struct iovec, count);
        if (vector == NULL) {
                return NT_STATUS_NO_MEMORY;
        }
@@ -291,15 +510,16 @@ static NTSTATUS smbd_smb2_request_setup_out(struct smbd_smb2_request *req)
                struct iovec *current = &vector[idx];
 
                if ((idx + 3) < count) {
-                       /* we have a next command */
-                       next_command_ofs = SMB2_HDR_BODY + 8;
+                       /* we have a next command -
+                        * setup for the error case. */
+                       next_command_ofs = SMB2_HDR_BODY + 9;
                }
 
                inhdr = (const uint8_t *)req->in.vector[idx].iov_base;
                in_flags = IVAL(inhdr, SMB2_HDR_FLAGS);
 
-               outhdr = talloc_array(vector, uint8_t,
-                                     SMB2_HDR_BODY + 8);
+               outhdr = talloc_zero_array(vector, uint8_t,
+                                     OUTVEC_ALLOC_SIZE);
                if (outhdr == NULL) {
                        return NT_STATUS_NO_MEMORY;
                }
@@ -323,8 +543,6 @@ static NTSTATUS smbd_smb2_request_setup_out(struct smbd_smb2_request *req)
                      NT_STATUS_V(NT_STATUS_INTERNAL_ERROR));
                SSVAL(outhdr, SMB2_HDR_OPCODE,
                      SVAL(inhdr, SMB2_HDR_OPCODE));
-               /* Make up a number for now... JRA. FIXME ! FIXME !*/
-               SSVAL(outhdr, SMB2_HDR_CREDIT,          20);
                SIVAL(outhdr, SMB2_HDR_FLAGS,
                      IVAL(inhdr, SMB2_HDR_FLAGS) | SMB2_HDR_FLAG_REDIRECT);
                SIVAL(outhdr, SMB2_HDR_NEXT_COMMAND,    next_command_ofs);
@@ -350,6 +568,8 @@ static NTSTATUS smbd_smb2_request_setup_out(struct smbd_smb2_request *req)
        /* setup the length of the NBT packet */
        smb2_setup_nbt_length(req->out.vector, req->out.vector_count);
 
+       DLIST_ADD_END(req->sconn->smb2.requests, req, struct smbd_smb2_request *);
+
        return NT_STATUS_OK;
 }
 
@@ -362,14 +582,497 @@ void smbd_server_connection_terminate_ex(struct smbd_server_connection *sconn,
        exit_server_cleanly(reason);
 }
 
-static NTSTATUS smbd_smb2_request_dispatch(struct smbd_smb2_request *req)
+static bool dup_smb2_vec3(TALLOC_CTX *ctx,
+                       struct iovec *outvec,
+                       const struct iovec *srcvec)
+{
+       /* vec[0] is always boilerplate and must
+        * be allocated with size OUTVEC_ALLOC_SIZE. */
+
+       outvec[0].iov_base = talloc_memdup(ctx,
+                               srcvec[0].iov_base,
+                               OUTVEC_ALLOC_SIZE);
+       if (!outvec[0].iov_base) {
+               return false;
+       }
+       outvec[0].iov_len = SMB2_HDR_BODY;
+
+       /*
+        * If this is a "standard" vec[1] of length 8,
+        * pointing to srcvec[0].iov_base + SMB2_HDR_BODY,
+        * then duplicate this. Else use talloc_memdup().
+        */
+
+       if (srcvec[1].iov_len == 8 &&
+                       srcvec[1].iov_base ==
+                               ((uint8_t *)srcvec[0].iov_base) +
+                                       SMB2_HDR_BODY) {
+               outvec[1].iov_base = ((uint8_t *)outvec[1].iov_base) +
+                                       SMB2_HDR_BODY;
+               outvec[1].iov_len = 8;
+       } else {
+               outvec[1].iov_base = talloc_memdup(ctx,
+                               srcvec[1].iov_base,
+                               srcvec[1].iov_len);
+               if (!outvec[1].iov_base) {
+                       return false;
+               }
+               outvec[1].iov_len = srcvec[1].iov_len;
+       }
+
+       /*
+        * If this is a "standard" vec[2] of length 1,
+        * pointing to srcvec[0].iov_base + (OUTVEC_ALLOC_SIZE - 1)
+        * then duplicate this. Else use talloc_memdup().
+        */
+
+       if (srcvec[2].iov_base &&
+                       srcvec[2].iov_len) {
+               if (srcvec[2].iov_base ==
+                               ((uint8_t *)srcvec[0].iov_base) +
+                                       (OUTVEC_ALLOC_SIZE - 1) &&
+                               srcvec[2].iov_len == 1) {
+                       /* Common SMB2 error packet case. */
+                       outvec[2].iov_base = ((uint8_t *)outvec[0].iov_base) +
+                               (OUTVEC_ALLOC_SIZE - 1);
+               } else {
+                       outvec[2].iov_base = talloc_memdup(ctx,
+                                       srcvec[2].iov_base,
+                                       srcvec[2].iov_len);
+                       if (!outvec[2].iov_base) {
+                               return false;
+                       }
+               }
+               outvec[2].iov_len = srcvec[2].iov_len;
+       } else {
+               outvec[2].iov_base = NULL;
+               outvec[2].iov_len = 0;
+       }
+       return true;
+}
+
+static struct smbd_smb2_request *dup_smb2_req(const struct smbd_smb2_request *req)
+{
+       struct smbd_smb2_request *newreq = NULL;
+       struct iovec *outvec = NULL;
+       int count = req->out.vector_count;
+       int i;
+
+       newreq = smbd_smb2_request_allocate(req->sconn);
+       if (!newreq) {
+               return NULL;
+       }
+
+       newreq->sconn = req->sconn;
+       newreq->do_signing = req->do_signing;
+       newreq->current_idx = req->current_idx;
+       newreq->async = false;
+       newreq->cancelled = false;
+
+       outvec = talloc_zero_array(newreq, struct iovec, count);
+       if (!outvec) {
+               TALLOC_FREE(newreq);
+               return NULL;
+       }
+       newreq->out.vector = outvec;
+       newreq->out.vector_count = count;
+
+       /* Setup the outvec's identically to req. */
+       outvec[0].iov_base = newreq->out.nbt_hdr;
+       outvec[0].iov_len = 4;
+       memcpy(newreq->out.nbt_hdr, req->out.nbt_hdr, 4);
+
+       /* Setup the vectors identically to the ones in req. */
+       for (i = 1; i < count; i += 3) {
+               if (!dup_smb2_vec3(outvec, &outvec[i], &req->out.vector[i])) {
+                       break;
+               }
+       }
+
+       if (i < count) {
+               /* Alloc failed. */
+               TALLOC_FREE(newreq);
+               return NULL;
+       }
+
+       smb2_setup_nbt_length(newreq->out.vector,
+               newreq->out.vector_count);
+
+       return newreq;
+}
+
+static void smbd_smb2_request_writev_done(struct tevent_req *subreq);
+
+static NTSTATUS smb2_send_async_interim_response(const struct smbd_smb2_request *req)
+{
+       int i = 0;
+       uint8_t *outhdr = NULL;
+       struct smbd_smb2_request *nreq = NULL;
+
+       /* Create a new smb2 request we'll use
+          for the interim return. */
+       nreq = dup_smb2_req(req);
+       if (!nreq) {
+               return NT_STATUS_NO_MEMORY;
+       }
+
+       /* Lose the last 3 out vectors. They're the
+          ones we'll be using for the async reply. */
+       nreq->out.vector_count -= 3;
+
+       smb2_setup_nbt_length(nreq->out.vector,
+               nreq->out.vector_count);
+
+       /* Step back to the previous reply. */
+       i = nreq->current_idx - 3;
+       outhdr = (uint8_t *)nreq->out.vector[i].iov_base;
+       /* And end the chain. */
+       SIVAL(outhdr, SMB2_HDR_NEXT_COMMAND, 0);
+
+       /* Calculate outgoing credits */
+       smb2_calculate_credits(req, nreq);
+
+       /* Re-sign if needed. */
+       if (nreq->do_signing) {
+               NTSTATUS status;
+               status = smb2_signing_sign_pdu(nreq->session->session_key,
+                                       &nreq->out.vector[i], 3);
+               if (!NT_STATUS_IS_OK(status)) {
+                       return status;
+               }
+       }
+       if (DEBUGLEVEL >= 10) {
+               dbgtext("smb2_send_async_interim_response: nreq->current_idx = %u\n",
+                       (unsigned int)nreq->current_idx );
+               dbgtext("smb2_send_async_interim_response: returning %u vectors\n",
+                       (unsigned int)nreq->out.vector_count );
+               print_req_vectors(nreq);
+       }
+       nreq->subreq = tstream_writev_queue_send(nreq,
+                                       nreq->sconn->smb2.event_ctx,
+                                       nreq->sconn->smb2.stream,
+                                       nreq->sconn->smb2.send_queue,
+                                       nreq->out.vector,
+                                       nreq->out.vector_count);
+
+       if (nreq->subreq == NULL) {
+               return NT_STATUS_NO_MEMORY;
+       }
+
+       tevent_req_set_callback(nreq->subreq,
+                       smbd_smb2_request_writev_done,
+                       nreq);
+
+       return NT_STATUS_OK;
+}
+
+struct smbd_smb2_request_pending_state {
+        struct smbd_server_connection *sconn;
+        uint8_t buf[4 + SMB2_HDR_BODY + 0x08 + 1];
+        struct iovec vector[3];
+};
+
+static void smbd_smb2_request_pending_writev_done(struct tevent_req *subreq)
+{
+       struct smbd_smb2_request_pending_state *state =
+               tevent_req_callback_data(subreq,
+                       struct smbd_smb2_request_pending_state);
+       struct smbd_server_connection *sconn = state->sconn;
+       int ret;
+       int sys_errno;
+
+       ret = tstream_writev_queue_recv(subreq, &sys_errno);
+       TALLOC_FREE(subreq);
+       if (ret == -1) {
+               NTSTATUS status = map_nt_error_from_unix(sys_errno);
+               smbd_server_connection_terminate(sconn, nt_errstr(status));
+               return;
+       }
+
+       TALLOC_FREE(state);
+}
+
+NTSTATUS smbd_smb2_request_pending_queue(struct smbd_smb2_request *req,
+                                        struct tevent_req *subreq)
+{
+       NTSTATUS status;
+       struct smbd_smb2_request_pending_state *state = NULL;
+       int i = req->current_idx;
+       uint8_t *reqhdr = NULL;
+       uint8_t *hdr = NULL;
+       uint8_t *body = NULL;
+       uint32_t flags = 0;
+       uint64_t message_id = 0;
+       uint64_t async_id = 0;
+       struct iovec *outvec = NULL;
+
+       if (!tevent_req_is_in_progress(subreq)) {
+               return NT_STATUS_OK;
+       }
+
+       req->subreq = subreq;
+       subreq = NULL;
+
+       if (req->async) {
+               /* We're already async. */
+               return NT_STATUS_OK;
+       }
+
+       if (req->in.vector_count > i + 3) {
+               /*
+                * We're trying to go async in a compound
+                * request chain. This is not allowed.
+                * Cancel the outstanding request.
+                */
+               tevent_req_cancel(req->subreq);
+               return smbd_smb2_request_error(req,
+                       NT_STATUS_INSUFFICIENT_RESOURCES);
+       }
+
+       if (DEBUGLEVEL >= 10) {
+               dbgtext("smbd_smb2_request_pending_queue: req->current_idx = %u\n",
+                       (unsigned int)req->current_idx );
+               print_req_vectors(req);
+       }
+
+       if (req->out.vector_count > 4) {
+               /* This is a compound reply. We
+                * must do an interim response
+                * followed by the async response
+                * to match W2K8R2.
+                */
+               status = smb2_send_async_interim_response(req);
+               if (!NT_STATUS_IS_OK(status)) {
+                       return status;
+               }
+       }
+
+       /* Don't return an intermediate packet on a pipe read/write. */
+       if (req->tcon && req->tcon->compat_conn && IS_IPC(req->tcon->compat_conn)) {
+               return NT_STATUS_OK;
+       }
+
+       reqhdr = (uint8_t *)req->out.vector[i].iov_base;
+       flags = (IVAL(reqhdr, SMB2_HDR_FLAGS) & ~SMB2_HDR_FLAG_CHAINED);
+       message_id = BVAL(reqhdr, SMB2_HDR_MESSAGE_ID);
+       async_id = message_id; /* keep it simple for now... */
+
+       /*
+        * What we send is identical to a smbd_smb2_request_error
+        * packet with an error status of STATUS_PENDING. Make use
+        * of this fact sometime when refactoring. JRA.
+        */
+
+       state = talloc_zero(req->sconn, struct smbd_smb2_request_pending_state);
+       if (state == NULL) {
+               return NT_STATUS_NO_MEMORY;
+       }
+       state->sconn = req->sconn;
+
+       state->vector[0].iov_base = (void *)state->buf;
+       state->vector[0].iov_len = 4;
+
+       state->vector[1].iov_base = state->buf + 4;
+       state->vector[1].iov_len = SMB2_HDR_BODY;
+
+       state->vector[2].iov_base = state->buf + 4 + SMB2_HDR_BODY;
+       state->vector[2].iov_len = 9;
+
+       smb2_setup_nbt_length(state->vector, 3);
+
+       hdr = (uint8_t *)state->vector[1].iov_base;
+       body = (uint8_t *)state->vector[2].iov_base;
+
+       SIVAL(hdr, SMB2_HDR_PROTOCOL_ID, SMB2_MAGIC);
+       SSVAL(hdr, SMB2_HDR_LENGTH, SMB2_HDR_BODY);
+       SSVAL(hdr, SMB2_HDR_EPOCH, 0);
+       SIVAL(hdr, SMB2_HDR_STATUS, NT_STATUS_V(STATUS_PENDING));
+       SSVAL(hdr, SMB2_HDR_OPCODE, SVAL(reqhdr, SMB2_HDR_OPCODE));
+
+       SIVAL(hdr, SMB2_HDR_FLAGS, flags | SMB2_HDR_FLAG_ASYNC);
+       SIVAL(hdr, SMB2_HDR_NEXT_COMMAND, 0);
+       SBVAL(hdr, SMB2_HDR_MESSAGE_ID, message_id);
+       SBVAL(hdr, SMB2_HDR_PID, async_id);
+       SBVAL(hdr, SMB2_HDR_SESSION_ID,
+               BVAL(reqhdr, SMB2_HDR_SESSION_ID));
+       memset(hdr+SMB2_HDR_SIGNATURE, 0, 16);
+
+       SSVAL(body, 0x00, 0x08 + 1);
+
+       SCVAL(body, 0x02, 0);
+       SCVAL(body, 0x03, 0);
+       SIVAL(body, 0x04, 0);
+       /* Match W2K8R2... */
+       SCVAL(body, 0x08, 0x21);
+
+       /* Ensure we correctly go through crediting. Grant
+          the credits now, and zero credits on the final
+          response. */
+       smb2_set_operation_credit(req->sconn,
+                       &req->in.vector[i],
+                       &state->vector[1]);
+
+       if (req->do_signing) {
+               status = smb2_signing_sign_pdu(req->session->session_key,
+                                       state->vector, 3);
+               if (!NT_STATUS_IS_OK(status)) {
+                       return status;
+               }
+       }
+
+       subreq = tstream_writev_queue_send(state,
+                                       req->sconn->smb2.event_ctx,
+                                       req->sconn->smb2.stream,
+                                       req->sconn->smb2.send_queue,
+                                       state->vector,
+                                       3);
+
+       if (subreq == NULL) {
+               return NT_STATUS_NO_MEMORY;
+       }
+
+       tevent_req_set_callback(subreq,
+                       smbd_smb2_request_pending_writev_done,
+                       state);
+
+       /* Note we're going async with this request. */
+       req->async = true;
+
+       /*
+        * Now manipulate req so that the outstanding async request
+        * is the only one left in the struct smbd_smb2_request.
+        */
+
+       if (req->current_idx == 1) {
+               /* There was only one. */
+               goto out;
+       }
+
+       /* Re-arrange the in.vectors. */
+       req->in.vector[1] = req->in.vector[i];
+       req->in.vector[2] = req->in.vector[i+1];
+       req->in.vector[3] = req->in.vector[i+2];
+       req->in.vector_count = 4;
+       /* Reset the new in size. */
+       smb2_setup_nbt_length(req->in.vector, 4);
+
+       /* Now recreate the out.vectors. */
+       outvec = talloc_zero_array(req, struct iovec, 4);
+       if (!outvec) {
+               return NT_STATUS_NO_MEMORY;
+       }
+
+       /* 0 is always boilerplate and must
+        * be of size 4 for the length field. */
+
+       outvec[0].iov_base = req->out.nbt_hdr;
+       outvec[0].iov_len = 4;
+       SIVAL(req->out.nbt_hdr, 0, 0);
+
+       if (!dup_smb2_vec3(outvec, &outvec[1], &req->out.vector[i])) {
+               return NT_STATUS_NO_MEMORY;
+       }
+
+       TALLOC_FREE(req->out.vector);
+
+       req->out.vector = outvec;
+
+       req->current_idx = 1;
+       req->out.vector_count = 4;
+
+  out:
+
+       smb2_setup_nbt_length(req->out.vector,
+               req->out.vector_count);
+
+       /* Ensure our final reply matches the interim one. */
+       reqhdr = (uint8_t *)req->out.vector[1].iov_base;
+       SIVAL(reqhdr, SMB2_HDR_FLAGS, flags | SMB2_HDR_FLAG_ASYNC);
+       SBVAL(reqhdr, SMB2_HDR_PID, async_id);
+
+       {
+               const uint8_t *inhdr =
+                       (const uint8_t *)req->in.vector[1].iov_base;
+               DEBUG(10,("smbd_smb2_request_pending_queue: opcode[%s] mid %llu "
+                       "going async\n",
+                       smb2_opcode_name((uint16_t)IVAL(inhdr, SMB2_HDR_OPCODE)),
+                       (unsigned long long)async_id ));
+       }
+       return NT_STATUS_OK;
+}
+
+static NTSTATUS smbd_smb2_request_process_cancel(struct smbd_smb2_request *req)
+{
+       struct smbd_server_connection *sconn = req->sconn;
+       struct smbd_smb2_request *cur;
+       const uint8_t *inhdr;
+       int i = req->current_idx;
+       uint32_t flags;
+       uint64_t search_message_id;
+       uint64_t search_async_id;
+       uint64_t found_id;
+
+       inhdr = (const uint8_t *)req->in.vector[i].iov_base;
+
+       flags = IVAL(inhdr, SMB2_HDR_FLAGS);
+       search_message_id = BVAL(inhdr, SMB2_HDR_MESSAGE_ID);
+       search_async_id = BVAL(inhdr, SMB2_HDR_PID);
+
+       /*
+        * we don't need the request anymore
+        * cancel requests never have a response
+        */
+       DLIST_REMOVE(req->sconn->smb2.requests, req);
+       TALLOC_FREE(req);
+
+       for (cur = sconn->smb2.requests; cur; cur = cur->next) {
+               const uint8_t *outhdr;
+               uint64_t message_id;
+               uint64_t async_id;
+
+               i = cur->current_idx;
+
+               outhdr = (const uint8_t *)cur->out.vector[i].iov_base;
+
+               message_id = BVAL(outhdr, SMB2_HDR_MESSAGE_ID);
+               async_id = BVAL(outhdr, SMB2_HDR_PID);
+
+               if (flags & SMB2_HDR_FLAG_ASYNC) {
+                       if (search_async_id == async_id) {
+                               found_id = async_id;
+                               break;
+                       }
+               } else {
+                       if (search_message_id == message_id) {
+                               found_id = message_id;
+                               break;
+                       }
+               }
+       }
+
+       if (cur && cur->subreq) {
+               inhdr = (const uint8_t *)cur->in.vector[i].iov_base;
+               DEBUG(10,("smbd_smb2_request_process_cancel: attempting to "
+                       "cancel opcode[%s] mid %llu\n",
+                       smb2_opcode_name((uint16_t)IVAL(inhdr, SMB2_HDR_OPCODE)),
+                        (unsigned long long)found_id ));
+               tevent_req_cancel(cur->subreq);
+       }
+
+       return NT_STATUS_OK;
+}
+
+NTSTATUS smbd_smb2_request_dispatch(struct smbd_smb2_request *req)
 {
        const uint8_t *inhdr;
        int i = req->current_idx;
        uint16_t opcode;
        uint32_t flags;
+       uint64_t mid;
        NTSTATUS status;
        NTSTATUS session_status;
+       uint32_t allowed_flags;
+       NTSTATUS return_value;
 
        inhdr = (const uint8_t *)req->in.vector[i].iov_base;
 
@@ -377,16 +1080,20 @@ static NTSTATUS smbd_smb2_request_dispatch(struct smbd_smb2_request *req)
 
        flags = IVAL(inhdr, SMB2_HDR_FLAGS);
        opcode = IVAL(inhdr, SMB2_HDR_OPCODE);
-       DEBUG(10,("smbd_smb2_request_dispatch: opcode[%u]\n", opcode));
-
-#define TMP_SMB2_ALLOWED_FLAGS ( \
-       SMB2_HDR_FLAG_CHAINED | \
-       SMB2_HDR_FLAG_SIGNED | \
-       SMB2_HDR_FLAG_DFS)
-       if ((flags & ~TMP_SMB2_ALLOWED_FLAGS) != 0) {
+       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));
+
+       allowed_flags = SMB2_HDR_FLAG_CHAINED |
+                       SMB2_HDR_FLAG_SIGNED |
+                       SMB2_HDR_FLAG_DFS;
+       if (opcode == SMB2_OP_CANCEL) {
+               allowed_flags |= SMB2_HDR_FLAG_ASYNC;
+       }
+       if ((flags & ~allowed_flags) != 0) {
                return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
        }
-#undef TMP_SMB2_ALLOWED_FLAGS
 
        session_status = smbd_smb2_request_check_session(req);
 
@@ -423,178 +1130,353 @@ static NTSTATUS smbd_smb2_request_dispatch(struct smbd_smb2_request *req)
 
        switch (opcode) {
        case SMB2_OP_NEGPROT:
-               return smbd_smb2_request_process_negprot(req);
+               {
+                       START_PROFILE(smb2_negprot);
+                       return_value = smbd_smb2_request_process_negprot(req);
+                       END_PROFILE(smb2_negprot);
+               }
+               break;
 
        case SMB2_OP_SESSSETUP:
-               return smbd_smb2_request_process_sesssetup(req);
+               {
+                       START_PROFILE(smb2_sesssetup);
+                       return_value = smbd_smb2_request_process_sesssetup(req);
+                       END_PROFILE(smb2_sesssetup);
+               }
+               break;
 
        case SMB2_OP_LOGOFF:
                if (!NT_STATUS_IS_OK(session_status)) {
-                       return smbd_smb2_request_error(req, session_status);
+                       return_value = smbd_smb2_request_error(req, session_status);
+                       break;
                }
-               return smbd_smb2_request_process_logoff(req);
+
+               {
+                       START_PROFILE(smb2_logoff);
+                       return_value = smbd_smb2_request_process_logoff(req);
+                       END_PROFILE(smb2_logoff);
+               }
+               break;
 
        case SMB2_OP_TCON:
                if (!NT_STATUS_IS_OK(session_status)) {
-                       return smbd_smb2_request_error(req, session_status);
+                       return_value = smbd_smb2_request_error(req, session_status);
+                       break;
                }
                status = smbd_smb2_request_check_session(req);
                if (!NT_STATUS_IS_OK(status)) {
-                       return smbd_smb2_request_error(req, status);
+                       return_value = smbd_smb2_request_error(req, status);
+                       break;
+               }
+
+               {
+                       START_PROFILE(smb2_tcon);
+                       return_value = smbd_smb2_request_process_tcon(req);
+                       END_PROFILE(smb2_tcon);
                }
-               return smbd_smb2_request_process_tcon(req);
+               break;
 
        case SMB2_OP_TDIS:
                if (!NT_STATUS_IS_OK(session_status)) {
-                       return smbd_smb2_request_error(req, session_status);
+                       return_value = smbd_smb2_request_error(req, session_status);
+                       break;
                }
                status = smbd_smb2_request_check_tcon(req);
                if (!NT_STATUS_IS_OK(status)) {
-                       return smbd_smb2_request_error(req, status);
+                       return_value = smbd_smb2_request_error(req, status);
+                       break;
                }
-               return smbd_smb2_request_process_tdis(req);
+
+               {
+                       START_PROFILE(smb2_tdis);
+                       return_value = smbd_smb2_request_process_tdis(req);
+                       END_PROFILE(smb2_tdis);
+               }
+               break;
 
        case SMB2_OP_CREATE:
                if (!NT_STATUS_IS_OK(session_status)) {
-                       return smbd_smb2_request_error(req, session_status);
+                       return_value = smbd_smb2_request_error(req, session_status);
+                       break;
                }
                status = smbd_smb2_request_check_tcon(req);
                if (!NT_STATUS_IS_OK(status)) {
-                       return smbd_smb2_request_error(req, status);
+                       return_value = smbd_smb2_request_error(req, status);
+                       break;
                }
-               return smbd_smb2_request_process_create(req);
+
+               {
+                       START_PROFILE(smb2_create);
+                       return_value = smbd_smb2_request_process_create(req);
+                       END_PROFILE(smb2_create);
+               }
+               break;
 
        case SMB2_OP_CLOSE:
                if (!NT_STATUS_IS_OK(session_status)) {
-                       return smbd_smb2_request_error(req, session_status);
+                       return_value = smbd_smb2_request_error(req, session_status);
+                       break;
                }
                status = smbd_smb2_request_check_tcon(req);
                if (!NT_STATUS_IS_OK(status)) {
-                       return smbd_smb2_request_error(req, status);
+                       return_value = smbd_smb2_request_error(req, status);
+                       break;
                }
-               return smbd_smb2_request_process_close(req);
+
+               {
+                       START_PROFILE(smb2_close);
+                       return_value = smbd_smb2_request_process_close(req);
+                       END_PROFILE(smb2_close);
+               }
+               break;
 
        case SMB2_OP_FLUSH:
                if (!NT_STATUS_IS_OK(session_status)) {
-                       return smbd_smb2_request_error(req, session_status);
+                       return_value = smbd_smb2_request_error(req, session_status);
+                       break;
                }
                status = smbd_smb2_request_check_tcon(req);
                if (!NT_STATUS_IS_OK(status)) {
-                       return smbd_smb2_request_error(req, status);
+                       return_value = smbd_smb2_request_error(req, status);
+                       break;
                }
-               return smbd_smb2_request_process_flush(req);
+
+               {
+                       START_PROFILE(smb2_flush);
+                       return_value = smbd_smb2_request_process_flush(req);
+                       END_PROFILE(smb2_flush);
+               }
+               break;
 
        case SMB2_OP_READ:
                if (!NT_STATUS_IS_OK(session_status)) {
-                       return smbd_smb2_request_error(req, session_status);
+                       return_value = smbd_smb2_request_error(req, session_status);
+                       break;
                }
                status = smbd_smb2_request_check_tcon(req);
                if (!NT_STATUS_IS_OK(status)) {
-                       return smbd_smb2_request_error(req, status);
+                       return_value = smbd_smb2_request_error(req, status);
+                       break;
                }
-               return smbd_smb2_request_process_read(req);
+
+               {
+                       START_PROFILE(smb2_read);
+                       return_value = smbd_smb2_request_process_read(req);
+                       END_PROFILE(smb2_read);
+               }
+               break;
 
        case SMB2_OP_WRITE:
                if (!NT_STATUS_IS_OK(session_status)) {
-                       return smbd_smb2_request_error(req, session_status);
+                       return_value = smbd_smb2_request_error(req, session_status);
+                       break;
                }
                status = smbd_smb2_request_check_tcon(req);
                if (!NT_STATUS_IS_OK(status)) {
-                       return smbd_smb2_request_error(req, status);
+                       return_value = smbd_smb2_request_error(req, status);
+                       break;
                }
-               return smbd_smb2_request_process_write(req);
+
+               {
+                       START_PROFILE(smb2_write);
+                       return_value = smbd_smb2_request_process_write(req);
+                       END_PROFILE(smb2_write);
+               }
+               break;
 
        case SMB2_OP_LOCK:
                if (!NT_STATUS_IS_OK(session_status)) {
-                       return smbd_smb2_request_error(req, session_status);
+                       /* Too ugly to live ? JRA. */
+                       if (NT_STATUS_EQUAL(session_status,NT_STATUS_USER_SESSION_DELETED)) {
+                               session_status = NT_STATUS_FILE_CLOSED;
+                       }
+                       return_value = smbd_smb2_request_error(req, session_status);
+                       break;
                }
                status = smbd_smb2_request_check_tcon(req);
                if (!NT_STATUS_IS_OK(status)) {
-                       return smbd_smb2_request_error(req, status);
+                       /* Too ugly to live ? JRA. */
+                       if (NT_STATUS_EQUAL(status,NT_STATUS_NETWORK_NAME_DELETED)) {
+                               status = NT_STATUS_FILE_CLOSED;
+                       }
+                       return_value = smbd_smb2_request_error(req, status);
+                       break;
+               }
+
+               {
+                       START_PROFILE(smb2_lock);
+                       return_value = smbd_smb2_request_process_lock(req);
+                       END_PROFILE(smb2_lock);
                }
-               return smbd_smb2_request_error(req, NT_STATUS_NOT_IMPLEMENTED);
+               break;
 
        case SMB2_OP_IOCTL:
                if (!NT_STATUS_IS_OK(session_status)) {
-                       return smbd_smb2_request_error(req, session_status);
+                       return_value = smbd_smb2_request_error(req, session_status);
+                       break;
                }
                status = smbd_smb2_request_check_tcon(req);
                if (!NT_STATUS_IS_OK(status)) {
-                       return smbd_smb2_request_error(req, status);
+                       return_value = smbd_smb2_request_error(req, status);
+                       break;
+               }
+
+               {
+                       START_PROFILE(smb2_ioctl);
+                       return_value = smbd_smb2_request_process_ioctl(req);
+                       END_PROFILE(smb2_ioctl);
                }
-               return smbd_smb2_request_process_ioctl(req);
+               break;
 
        case SMB2_OP_CANCEL:
-               return smbd_smb2_request_error(req, NT_STATUS_NOT_IMPLEMENTED);
+               {
+                       START_PROFILE(smb2_cancel);
+                       return_value = smbd_smb2_request_process_cancel(req);
+                       END_PROFILE(smb2_cancel);
+               }
+               break;
 
        case SMB2_OP_KEEPALIVE:
-               return smbd_smb2_request_process_keepalive(req);
+               {START_PROFILE(smb2_keepalive);
+               return_value = smbd_smb2_request_process_keepalive(req);
+               END_PROFILE(smb2_keepalive);}
+               break;
 
        case SMB2_OP_FIND:
                if (!NT_STATUS_IS_OK(session_status)) {
-                       return smbd_smb2_request_error(req, session_status);
+                       return_value = smbd_smb2_request_error(req, session_status);
+                       break;
                }
                status = smbd_smb2_request_check_tcon(req);
                if (!NT_STATUS_IS_OK(status)) {
-                       return smbd_smb2_request_error(req, status);
+                       return_value = smbd_smb2_request_error(req, status);
+                       break;
                }
-               return smbd_smb2_request_error(req, NT_STATUS_NOT_IMPLEMENTED);
+
+               {
+                       START_PROFILE(smb2_find);
+                       return_value = smbd_smb2_request_process_find(req);
+                       END_PROFILE(smb2_find);
+               }
+               break;
 
        case SMB2_OP_NOTIFY:
                if (!NT_STATUS_IS_OK(session_status)) {
-                       return smbd_smb2_request_error(req, session_status);
+                       return_value = smbd_smb2_request_error(req, session_status);
+                       break;
                }
                status = smbd_smb2_request_check_tcon(req);
                if (!NT_STATUS_IS_OK(status)) {
-                       return smbd_smb2_request_error(req, status);
+                       return_value = smbd_smb2_request_error(req, status);
+                       break;
                }
-               return smbd_smb2_request_error(req, NT_STATUS_NOT_IMPLEMENTED);
+
+               {
+                       START_PROFILE(smb2_notify);
+                       return_value = smbd_smb2_request_process_notify(req);
+                       END_PROFILE(smb2_notify);
+               }
+               break;
 
        case SMB2_OP_GETINFO:
                if (!NT_STATUS_IS_OK(session_status)) {
-                       return smbd_smb2_request_error(req, session_status);
+                       return_value = smbd_smb2_request_error(req, session_status);
+                       break;
                }
                status = smbd_smb2_request_check_tcon(req);
                if (!NT_STATUS_IS_OK(status)) {
-                       return smbd_smb2_request_error(req, status);
+                       return_value = smbd_smb2_request_error(req, status);
+                       break;
                }
-               return smbd_smb2_request_error(req, NT_STATUS_NOT_IMPLEMENTED);
+
+               {
+                       START_PROFILE(smb2_getinfo);
+                       return_value = smbd_smb2_request_process_getinfo(req);
+                       END_PROFILE(smb2_getinfo);
+               }
+               break;
 
        case SMB2_OP_SETINFO:
                if (!NT_STATUS_IS_OK(session_status)) {
-                       return smbd_smb2_request_error(req, session_status);
+                       return_value = smbd_smb2_request_error(req, session_status);
+                       break;
                }
                status = smbd_smb2_request_check_tcon(req);
                if (!NT_STATUS_IS_OK(status)) {
-                       return smbd_smb2_request_error(req, status);
+                       return_value = smbd_smb2_request_error(req, status);
+                       break;
                }
-               return smbd_smb2_request_error(req, NT_STATUS_NOT_IMPLEMENTED);
+
+               {
+                       START_PROFILE(smb2_setinfo);
+                       return_value = smbd_smb2_request_process_setinfo(req);
+                       END_PROFILE(smb2_setinfo);
+               }
+               break;
 
        case SMB2_OP_BREAK:
                if (!NT_STATUS_IS_OK(session_status)) {
-                       return smbd_smb2_request_error(req, session_status);
+                       return_value = smbd_smb2_request_error(req, session_status);
+                       break;
                }
                status = smbd_smb2_request_check_tcon(req);
                if (!NT_STATUS_IS_OK(status)) {
-                       return smbd_smb2_request_error(req, status);
+                       return_value = smbd_smb2_request_error(req, status);
+                       break;
                }
-               return smbd_smb2_request_error(req, NT_STATUS_NOT_IMPLEMENTED);
-       }
 
-       return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
-}
+               {
+                       START_PROFILE(smb2_break);
+                       return_value = smbd_smb2_request_process_break(req);
+                       END_PROFILE(smb2_break);
+               }
+               break;
 
-static void smbd_smb2_request_dispatch_compound(struct tevent_req *subreq);
-static void smbd_smb2_request_writev_done(struct tevent_req *subreq);
+       default:
+               return_value = smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
+               break;
+       }
+       return return_value;
+}
 
 static NTSTATUS smbd_smb2_request_reply(struct smbd_smb2_request *req)
 {
        struct tevent_req *subreq;
+       int i = req->current_idx;
+
+       req->subreq = NULL;
+
+       req->current_idx += 3;
+
+       if (req->current_idx < req->out.vector_count) {
+               /*
+                * We must process the remaining compound
+                * SMB2 requests before any new incoming SMB2
+                * requests. This is because incoming SMB2
+                * requests may include a cancel for a
+                * compound request we haven't processed
+                * yet.
+                */
+               struct tevent_immediate *im = tevent_create_immediate(req);
+               if (!im) {
+                       return NT_STATUS_NO_MEMORY;
+               }
+               tevent_schedule_immediate(im,
+                                       req->sconn->smb2.event_ctx,
+                                       smbd_smb2_request_dispatch_immediate,
+                                       req);
+               return NT_STATUS_OK;
+       }
 
        smb2_setup_nbt_length(req->out.vector, req->out.vector_count);
 
+       /* Set credit for this operation (zero credits if this
+          is a final reply for an async operation). */
+       smb2_set_operation_credit(req->sconn,
+                       req->async ? NULL : &req->in.vector[i],
+                       &req->out.vector[i]);
+
        if (req->do_signing) {
-               int i = req->current_idx;
                NTSTATUS status;
                status = smb2_signing_sign_pdu(req->session->session_key,
                                               &req->out.vector[i], 3);
@@ -603,53 +1485,59 @@ static NTSTATUS smbd_smb2_request_reply(struct smbd_smb2_request *req)
                }
        }
 
-       req->current_idx += 3;
-
-       if (req->current_idx < req->out.vector_count) {
-               struct timeval zero = timeval_zero();
-               subreq = tevent_wakeup_send(req,
-                                           req->conn->smb2.event_ctx,
-                                           zero);
-               if (subreq == NULL) {
-                       return NT_STATUS_NO_MEMORY;
-               }
-               tevent_req_set_callback(subreq,
-                                       smbd_smb2_request_dispatch_compound,
-                                       req);
+       if (DEBUGLEVEL >= 10) {
+               dbgtext("smbd_smb2_request_reply: sending...\n");
+               print_req_vectors(req);
+       }
 
-               return NT_STATUS_OK;
+       /* I am a sick, sick man... :-). Sendfile hack ... JRA. */
+       if (req->out.vector_count == 4 &&
+                       req->out.vector[3].iov_base == NULL &&
+                       req->out.vector[3].iov_len != 0) {
+               /* Dynamic part is NULL. Chop it off,
+                  We're going to send it via sendfile. */
+               req->out.vector_count -= 1;
        }
 
        subreq = tstream_writev_queue_send(req,
-                                          req->conn->smb2.event_ctx,
-                                          req->conn->smb2.stream,
-                                          req->conn->smb2.send_queue,
+                                          req->sconn->smb2.event_ctx,
+                                          req->sconn->smb2.stream,
+                                          req->sconn->smb2.send_queue,
                                           req->out.vector,
                                           req->out.vector_count);
        if (subreq == NULL) {
                return NT_STATUS_NO_MEMORY;
        }
        tevent_req_set_callback(subreq, smbd_smb2_request_writev_done, req);
+       /*
+        * We're done with this request -
+        * move it off the "being processed" queue.
+        */
+       DLIST_REMOVE(req->sconn->smb2.requests, req);
 
        return NT_STATUS_OK;
 }
 
-static void smbd_smb2_request_dispatch_compound(struct tevent_req *subreq)
+void smbd_smb2_request_dispatch_immediate(struct tevent_context *ctx,
+                                       struct tevent_immediate *im,
+                                       void *private_data)
 {
-       struct smbd_smb2_request *req = tevent_req_callback_data(subreq,
+       struct smbd_smb2_request *req = talloc_get_type_abort(private_data,
                                        struct smbd_smb2_request);
-       struct smbd_server_connection *conn = req->conn;
+       struct smbd_server_connection *sconn = req->sconn;
        NTSTATUS status;
 
-       tevent_wakeup_recv(subreq);
-       TALLOC_FREE(subreq);
+       TALLOC_FREE(im);
 
-       DEBUG(10,("smbd_smb2_request_dispatch_compound: idx[%d] of %d vectors\n",
-                 req->current_idx, req->in.vector_count));
+       if (DEBUGLEVEL >= 10) {
+               DEBUG(10,("smbd_smb2_request_dispatch_immediate: idx[%d] of %d vectors\n",
+                       req->current_idx, req->in.vector_count));
+               print_req_vectors(req);
+       }
 
        status = smbd_smb2_request_dispatch(req);
        if (!NT_STATUS_IS_OK(status)) {
-               smbd_server_connection_terminate(conn, nt_errstr(status));
+               smbd_server_connection_terminate(sconn, nt_errstr(status));
                return;
        }
 }
@@ -658,62 +1546,20 @@ static void smbd_smb2_request_writev_done(struct tevent_req *subreq)
 {
        struct smbd_smb2_request *req = tevent_req_callback_data(subreq,
                                        struct smbd_smb2_request);
-       struct smbd_server_connection *conn = req->conn;
+       struct smbd_server_connection *sconn = req->sconn;
        int ret;
        int sys_errno;
-       TALLOC_CTX *mem_pool;
 
        ret = tstream_writev_queue_recv(subreq, &sys_errno);
        TALLOC_FREE(subreq);
+       TALLOC_FREE(req);
        if (ret == -1) {
                NTSTATUS status = map_nt_error_from_unix(sys_errno);
-               smbd_server_connection_terminate(conn, nt_errstr(status));
+               DEBUG(2,("smbd_smb2_request_writev_done: client write error %s\n",
+                       nt_errstr(status)));
+               smbd_server_connection_terminate(sconn, nt_errstr(status));
                return;
        }
-
-       mem_pool = req->mem_pool;
-       req = NULL;
-       talloc_free(mem_pool);
-}
-
-NTSTATUS smbd_smb2_request_error_ex(struct smbd_smb2_request *req,
-                                   NTSTATUS status,
-                                   DATA_BLOB *info,
-                                   const char *location)
-{
-       uint8_t *outhdr;
-       uint8_t *outbody;
-       int i = req->current_idx;
-
-       DEBUG(10,("smbd_smb2_request_error_ex: idx[%d] status[%s] |%s| at %s\n",
-                 i, nt_errstr(status), info ? " +info" : "",
-                 location));
-
-       outhdr = (uint8_t *)req->out.vector[i].iov_base;
-
-       SIVAL(outhdr, SMB2_HDR_STATUS, NT_STATUS_V(status));
-
-       outbody = outhdr + SMB2_HDR_BODY;
-
-       req->out.vector[i+1].iov_base = (void *)outbody;
-       req->out.vector[i+1].iov_len = 8;
-
-       if (info) {
-               SIVAL(outbody, 0x04, info->length);
-               req->out.vector[i+2].iov_base   = (void *)info->data;
-               req->out.vector[i+2].iov_len    = info->length;
-       } else {
-               req->out.vector[i+2].iov_base = NULL;
-               req->out.vector[i+2].iov_len = 0;
-       }
-
-       /*
-        * if a request fails, all other remaining
-        * compounded requests should fail too
-        */
-       req->next_status = NT_STATUS_INVALID_PARAMETER;
-
-       return smbd_smb2_request_reply(req);
 }
 
 NTSTATUS smbd_smb2_request_done_ex(struct smbd_smb2_request *req,
@@ -722,7 +1568,6 @@ NTSTATUS smbd_smb2_request_done_ex(struct smbd_smb2_request *req,
                                   const char *location)
 {
        uint8_t *outhdr;
-       uint8_t *outdyn;
        int i = req->current_idx;
        uint32_t next_command_ofs;
 
@@ -742,8 +1587,6 @@ NTSTATUS smbd_smb2_request_done_ex(struct smbd_smb2_request *req,
        }
 
        outhdr = (uint8_t *)req->out.vector[i].iov_base;
-       /* the fallback dynamic buffer */
-       outdyn = outhdr + SMB2_HDR_BODY + 8;
 
        next_command_ofs = IVAL(outhdr, SMB2_HDR_NEXT_COMMAND);
        SIVAL(outhdr, SMB2_HDR_STATUS, NT_STATUS_V(status));
@@ -798,7 +1641,7 @@ NTSTATUS smbd_smb2_request_done_ex(struct smbd_smb2_request *req,
                        old_dyn = (uint8_t *)req->out.vector[i+2].iov_base;
 
                        new_size = old_size + pad_size;
-                       new_dyn = talloc_array(req->out.vector,
+                       new_dyn = talloc_zero_array(req->out.vector,
                                               uint8_t, new_size);
                        if (new_dyn == NULL) {
                                return smbd_smb2_request_error(req,
@@ -810,8 +1653,6 @@ NTSTATUS smbd_smb2_request_done_ex(struct smbd_smb2_request *req,
 
                        req->out.vector[i+2].iov_base = (void *)new_dyn;
                        req->out.vector[i+2].iov_len = new_size;
-
-                       TALLOC_FREE(old_dyn);
                }
                next_command_ofs += pad_size;
        }
@@ -821,6 +1662,140 @@ NTSTATUS smbd_smb2_request_done_ex(struct smbd_smb2_request *req,
        return smbd_smb2_request_reply(req);
 }
 
+NTSTATUS smbd_smb2_request_error_ex(struct smbd_smb2_request *req,
+                                   NTSTATUS status,
+                                   DATA_BLOB *info,
+                                   const char *location)
+{
+       DATA_BLOB body;
+       int i = req->current_idx;
+       uint8_t *outhdr = (uint8_t *)req->out.vector[i].iov_base;
+
+       DEBUG(10,("smbd_smb2_request_error_ex: idx[%d] status[%s] |%s| at %s\n",
+                 i, nt_errstr(status), info ? " +info" : "",
+                 location));
+
+       body.data = outhdr + SMB2_HDR_BODY;
+       body.length = 8;
+       SSVAL(body.data, 0, 9);
+
+       if (info) {
+               SIVAL(body.data, 0x04, info->length);
+       } else {
+               /* Allocated size of req->out.vector[i].iov_base
+                * *MUST BE* OUTVEC_ALLOC_SIZE. So we have room for
+                * 1 byte without having to do an alloc.
+                */
+               info = talloc_zero_array(req->out.vector,
+                                       DATA_BLOB,
+                                       1);
+               if (!info) {
+                       return NT_STATUS_NO_MEMORY;
+               }
+               info->data = ((uint8_t *)outhdr) +
+                       OUTVEC_ALLOC_SIZE - 1;
+               info->length = 1;
+               SCVAL(info->data, 0, 0);
+       }
+
+       /*
+        * if a request fails, all other remaining
+        * compounded requests should fail too
+        */
+       req->next_status = NT_STATUS_INVALID_PARAMETER;
+
+       return smbd_smb2_request_done_ex(req, status, body, info, __location__);
+}
+
+
+struct smbd_smb2_send_oplock_break_state {
+       struct smbd_server_connection *sconn;
+       uint8_t buf[4 + SMB2_HDR_BODY + 0x18];
+       struct iovec vector;
+};
+
+static void smbd_smb2_oplock_break_writev_done(struct tevent_req *subreq);
+
+NTSTATUS smbd_smb2_send_oplock_break(struct smbd_server_connection *sconn,
+                                    uint64_t file_id_persistent,
+                                    uint64_t file_id_volatile,
+                                    uint8_t oplock_level)
+{
+       struct smbd_smb2_send_oplock_break_state *state;
+       struct tevent_req *subreq;
+       uint8_t *hdr;
+       uint8_t *body;
+
+       state = talloc(sconn, struct smbd_smb2_send_oplock_break_state);
+       if (state == NULL) {
+               return NT_STATUS_NO_MEMORY;
+       }
+       state->sconn = sconn;
+
+       state->vector.iov_base = (void *)state->buf;
+       state->vector.iov_len = sizeof(state->buf);
+
+       _smb2_setlen(state->buf, sizeof(state->buf) - 4);
+       hdr = state->buf + 4;
+       body = hdr + SMB2_HDR_BODY;
+
+       SIVAL(hdr, 0,                           SMB2_MAGIC);
+       SSVAL(hdr, SMB2_HDR_LENGTH,             SMB2_HDR_BODY);
+       SSVAL(hdr, SMB2_HDR_EPOCH,              0);
+       SIVAL(hdr, SMB2_HDR_STATUS,             0);
+       SSVAL(hdr, SMB2_HDR_OPCODE,             SMB2_OP_BREAK);
+       SSVAL(hdr, SMB2_HDR_CREDIT,             0);
+       SIVAL(hdr, SMB2_HDR_FLAGS,              SMB2_HDR_FLAG_REDIRECT);
+       SIVAL(hdr, SMB2_HDR_NEXT_COMMAND,       0);
+       SBVAL(hdr, SMB2_HDR_MESSAGE_ID,         UINT64_MAX);
+       SIVAL(hdr, SMB2_HDR_PID,                0);
+       SIVAL(hdr, SMB2_HDR_TID,                0);
+       SBVAL(hdr, SMB2_HDR_SESSION_ID,         0);
+       memset(hdr+SMB2_HDR_SIGNATURE, 0, 16);
+
+       SSVAL(body, 0x00, 0x18);
+
+       SCVAL(body, 0x02, oplock_level);
+       SCVAL(body, 0x03, 0);           /* reserved */
+       SIVAL(body, 0x04, 0);           /* reserved */
+       SBVAL(body, 0x08, file_id_persistent);
+       SBVAL(body, 0x10, file_id_volatile);
+
+       subreq = tstream_writev_queue_send(state,
+                                          sconn->smb2.event_ctx,
+                                          sconn->smb2.stream,
+                                          sconn->smb2.send_queue,
+                                          &state->vector, 1);
+       if (subreq == NULL) {
+               return NT_STATUS_NO_MEMORY;
+       }
+       tevent_req_set_callback(subreq,
+                               smbd_smb2_oplock_break_writev_done,
+                               state);
+
+       return NT_STATUS_OK;
+}
+
+static void smbd_smb2_oplock_break_writev_done(struct tevent_req *subreq)
+{
+       struct smbd_smb2_send_oplock_break_state *state =
+               tevent_req_callback_data(subreq,
+               struct smbd_smb2_send_oplock_break_state);
+       struct smbd_server_connection *sconn = state->sconn;
+       int ret;
+       int sys_errno;
+
+       ret = tstream_writev_queue_recv(subreq, &sys_errno);
+       TALLOC_FREE(subreq);
+       if (ret == -1) {
+               NTSTATUS status = map_nt_error_from_unix(sys_errno);
+               smbd_server_connection_terminate(sconn, nt_errstr(status));
+               return;
+       }
+
+       TALLOC_FREE(state);
+}
+
 struct smbd_smb2_request_read_state {
        size_t missing;
        bool asked_for_header;
@@ -836,12 +1811,11 @@ static void smbd_smb2_request_read_done(struct tevent_req *subreq);
 
 static struct tevent_req *smbd_smb2_request_read_send(TALLOC_CTX *mem_ctx,
                                        struct tevent_context *ev,
-                                       struct smbd_server_connection *conn)
+                                       struct smbd_server_connection *sconn)
 {
        struct tevent_req *req;
        struct smbd_smb2_request_read_state *state;
        struct tevent_req *subreq;
-       TALLOC_CTX *mem_pool;
 
        req = tevent_req_create(mem_ctx, &state,
                                struct smbd_smb2_request_read_state);
@@ -851,21 +1825,14 @@ static struct tevent_req *smbd_smb2_request_read_send(TALLOC_CTX *mem_ctx,
        state->missing = 0;
        state->asked_for_header = false;
 
-       mem_pool = talloc_pool(state, 8192);
-       if (tevent_req_nomem(mem_pool, req)) {
-               return tevent_req_post(req, ev);
-       }
-
-       state->smb2_req = talloc_zero(mem_pool, struct smbd_smb2_request);
+       state->smb2_req = smbd_smb2_request_allocate(state);
        if (tevent_req_nomem(state->smb2_req, req)) {
                return tevent_req_post(req, ev);
        }
+       state->smb2_req->sconn = sconn;
 
-       state->smb2_req->mem_pool       = mem_pool;
-       state->smb2_req->conn           = conn;
-
-       subreq = tstream_readv_pdu_queue_send(state, ev, conn->smb2.stream,
-                                             conn->smb2.recv_queue,
+       subreq = tstream_readv_pdu_queue_send(state, ev, sconn->smb2.stream,
+                                             sconn->smb2.recv_queue,
                                              smbd_smb2_request_next_vector,
                                              state);
        if (tevent_req_nomem(subreq, req)) {
@@ -1048,7 +2015,13 @@ static int smbd_smb2_request_next_vector(struct tstream_context *stream,
                                 * body and let the caller deal with the error
                                 */
                                invalid = true;
-                       } else if (body_size > (full_size - SMB2_HDR_BODY)) {
+                       }
+
+                       if ((body_size % 2) != 0) {
+                               body_size -= 1;
+                       }
+
+                       if (body_size > (full_size - SMB2_HDR_BODY)) {
                                /*
                                 * this is invalid, just return a zero
                                 * body and let the caller deal with the error
@@ -1059,11 +2032,7 @@ static int smbd_smb2_request_next_vector(struct tstream_context *stream,
 
                if (invalid) {
                        /* the caller should check this */
-                       body_size = 0;
-               }
-
-               if ((body_size % 2) != 0) {
-                       body_size -= 1;
+                       body_size = 2;
                }
 
                dyn_size = full_size - (SMB2_HDR_BODY + body_size);
@@ -1096,7 +2065,7 @@ static int smbd_smb2_request_next_vector(struct tstream_context *stream,
                 */
                memcpy(body, hdr + SMB2_HDR_BODY, 2);
                vector[0].iov_base = body + 2;
-               vector[0].iov_len = req->in.vector[idx].iov_len - 2;
+               vector[0].iov_len = body_size - 2;
 
                vector[1] = req->in.vector[idx+1];
 
@@ -1195,68 +2164,69 @@ static NTSTATUS smbd_smb2_request_read_recv(struct tevent_req *req,
 
 static void smbd_smb2_request_incoming(struct tevent_req *subreq);
 
-void smbd_smb2_first_negprot(struct smbd_server_connection *conn,
+void smbd_smb2_first_negprot(struct smbd_server_connection *sconn,
                             const uint8_t *inbuf, size_t size)
 {
        NTSTATUS status;
-       struct smbd_smb2_request *req;
+       struct smbd_smb2_request *req = NULL;
        struct tevent_req *subreq;
 
        DEBUG(10,("smbd_smb2_first_negprot: packet length %u\n",
                 (unsigned int)size));
 
-       status = smbd_initialize_smb2(conn);
+       status = smbd_initialize_smb2(sconn);
        if (!NT_STATUS_IS_OK(status)) {
-               smbd_server_connection_terminate(conn, nt_errstr(status));
+               smbd_server_connection_terminate(sconn, nt_errstr(status));
                return;
        }
 
-       status = smbd_smb2_request_create(conn, inbuf, size, &req);
+       status = smbd_smb2_request_create(sconn, inbuf, size, &req);
        if (!NT_STATUS_IS_OK(status)) {
-               smbd_server_connection_terminate(conn, nt_errstr(status));
+               smbd_server_connection_terminate(sconn, nt_errstr(status));
                return;
        }
 
        status = smbd_smb2_request_setup_out(req);
        if (!NT_STATUS_IS_OK(status)) {
-               smbd_server_connection_terminate(conn, nt_errstr(status));
+               smbd_server_connection_terminate(sconn, nt_errstr(status));
                return;
        }
 
        status = smbd_smb2_request_dispatch(req);
        if (!NT_STATUS_IS_OK(status)) {
-               smbd_server_connection_terminate(conn, nt_errstr(status));
+               smbd_server_connection_terminate(sconn, nt_errstr(status));
                return;
        }
 
        /* ask for the next request */
-       subreq = smbd_smb2_request_read_send(conn,conn->smb2.event_ctx, conn);
+       subreq = smbd_smb2_request_read_send(sconn, sconn->smb2.event_ctx, sconn);
        if (subreq == NULL) {
-               smbd_server_connection_terminate(conn, "no memory for reading");
+               smbd_server_connection_terminate(sconn, "no memory for reading");
                return;
        }
-       tevent_req_set_callback(subreq, smbd_smb2_request_incoming, conn);
+       tevent_req_set_callback(subreq, smbd_smb2_request_incoming, sconn);
 }
 
 static void smbd_smb2_request_incoming(struct tevent_req *subreq)
 {
-       struct smbd_server_connection *conn = tevent_req_callback_data(subreq,
-                                             struct smbd_server_connection);
+       struct smbd_server_connection *sconn = tevent_req_callback_data(subreq,
+                                              struct smbd_server_connection);
        NTSTATUS status;
-       struct smbd_smb2_request *req;
+       struct smbd_smb2_request *req = NULL;
 
-       status = smbd_smb2_request_read_recv(subreq, conn, &req);
+       status = smbd_smb2_request_read_recv(subreq, sconn, &req);
        TALLOC_FREE(subreq);
        if (!NT_STATUS_IS_OK(status)) {
-               smbd_server_connection_terminate(conn, nt_errstr(status));
+               DEBUG(2,("smbd_smb2_request_incoming: client read error %s\n",
+                       nt_errstr(status)));
+               smbd_server_connection_terminate(sconn, nt_errstr(status));
                return;
        }
 
        if (req->in.nbt_hdr[0] != 0x00) {
                DEBUG(1,("smbd_smb2_request_incoming: ignore NBT[0x%02X] msg\n",
                         req->in.nbt_hdr[0]));
-               talloc_free(req->mem_pool);
-               req = NULL;
+               TALLOC_FREE(req);
                goto next;
        }
 
@@ -1267,28 +2237,28 @@ static void smbd_smb2_request_incoming(struct tevent_req *subreq)
 
        status = smbd_smb2_request_validate(req);
        if (!NT_STATUS_IS_OK(status)) {
-               smbd_server_connection_terminate(conn, nt_errstr(status));
+               smbd_server_connection_terminate(sconn, nt_errstr(status));
                return;
        }
 
        status = smbd_smb2_request_setup_out(req);
        if (!NT_STATUS_IS_OK(status)) {
-               smbd_server_connection_terminate(conn, nt_errstr(status));
+               smbd_server_connection_terminate(sconn, nt_errstr(status));
                return;
        }
 
        status = smbd_smb2_request_dispatch(req);
        if (!NT_STATUS_IS_OK(status)) {
-               smbd_server_connection_terminate(conn, nt_errstr(status));
+               smbd_server_connection_terminate(sconn, nt_errstr(status));
                return;
        }
 
 next:
        /* ask for the next request (this constructs the main loop) */
-       subreq = smbd_smb2_request_read_send(conn,conn->smb2.event_ctx, conn);
+       subreq = smbd_smb2_request_read_send(sconn, sconn->smb2.event_ctx, sconn);
        if (subreq == NULL) {
-               smbd_server_connection_terminate(conn, "no memory for reading");
+               smbd_server_connection_terminate(sconn, "no memory for reading");
                return;
        }
-       tevent_req_set_callback(subreq, smbd_smb2_request_incoming, conn);
+       tevent_req_set_callback(subreq, smbd_smb2_request_incoming, sconn);
 }