s3: only include smb profiling where needed.
[nivanova/samba-autobuild/.git] / source3 / smbd / smb2_server.c
index b697cb924b2cb668d5447601fc10e25af71c4055..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 "../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",
@@ -47,7 +52,7 @@ static const char *smb2_names[] = {
 
 const char *smb2_opcode_name(uint16_t opcode)
 {
-       if (opcode >= 0x12) {
+       if (opcode > 0x12) {
                return "Bad SMB2 opcode";
        }
        return smb2_names[opcode];
@@ -107,8 +112,16 @@ static NTSTATUS smbd_initialize_smb2(struct smbd_server_connection *sconn)
        }
        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;
+       }
 
-       ret = tstream_bsd_existing_socket(sconn, smbd_server_fd(),
+       ret = tstream_bsd_existing_socket(sconn, sconn->sock,
                                          &sconn->smb2.stream);
        if (ret == -1) {
                status = map_nt_error_from_unix(errno);
@@ -116,7 +129,7 @@ static NTSTATUS smbd_initialize_smb2(struct smbd_server_connection *sconn)
        }
 
        /* Ensure child is set to non-blocking mode */
-       set_blocking(smbd_server_fd(),false);
+       set_blocking(sconn->sock, false);
        return NT_STATUS_OK;
 }
 
@@ -153,10 +166,6 @@ static int smbd_smb2_request_parent_destructor(struct smbd_smb2_request **req)
 
 static int smbd_smb2_request_destructor(struct smbd_smb2_request *req)
 {
-       if (req->out.vector) {
-               DLIST_REMOVE(req->sconn->smb2.requests, req);
-       }
-
        if (req->parent) {
                *req->parent = NULL;
                talloc_free(req->mem_pool);
@@ -171,7 +180,12 @@ static struct smbd_smb2_request *smbd_smb2_request_allocate(TALLOC_CTX *mem_ctx)
        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;
        }
@@ -280,14 +294,70 @@ static NTSTATUS smbd_smb2_request_create(struct smbd_server_connection *sconn,
        return NT_STATUS_OK;
 }
 
-static NTSTATUS smbd_smb2_request_validate(struct smbd_smb2_request *req,
-                               uint16_t *p_creds_requested)
+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;
        int idx;
        bool compound_related = false;
 
-       *p_creds_requested = 0;
        count = req->in.vector_count;
 
        if (count < 4) {
@@ -296,7 +366,6 @@ static NTSTATUS smbd_smb2_request_validate(struct smbd_smb2_request *req,
        }
 
        for (idx=1; idx < count; idx += 3) {
-               uint16_t creds_requested = 0;
                const uint8_t *inhdr = NULL;
                uint32_t flags;
 
@@ -310,16 +379,13 @@ 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;
                }
 
-               creds_requested = SVAL(inhdr, SMB2_HDR_CREDIT);
-               if (*p_creds_requested + creds_requested < creds_requested) {
-                       *p_creds_requested = 65535;
-               } else {
-                       *p_creds_requested += creds_requested;
+               if (!smb2_validate_message_id(req->sconn, inhdr)) {
+                       return NT_STATUS_INVALID_PARAMETER;
                }
 
                flags = IVAL(inhdr, SMB2_HDR_FLAGS);
@@ -370,14 +436,63 @@ static NTSTATUS smbd_smb2_request_validate(struct smbd_smb2_request *req,
        return NT_STATUS_OK;
 }
 
-static NTSTATUS smbd_smb2_request_setup_out(struct smbd_smb2_request *req, uint16_t creds)
+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;
        int count;
        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;
        }
@@ -395,15 +510,16 @@ static NTSTATUS smbd_smb2_request_setup_out(struct smbd_smb2_request *req, uint1
                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;
                }
@@ -427,7 +543,6 @@ static NTSTATUS smbd_smb2_request_setup_out(struct smbd_smb2_request *req, uint1
                      NT_STATUS_V(NT_STATUS_INTERNAL_ERROR));
                SSVAL(outhdr, SMB2_HDR_OPCODE,
                      SVAL(inhdr, SMB2_HDR_OPCODE));
-               SSVAL(outhdr, SMB2_HDR_CREDIT,          creds);
                SIVAL(outhdr, SMB2_HDR_FLAGS,
                      IVAL(inhdr, SMB2_HDR_FLAGS) | SMB2_HDR_FLAG_REDIRECT);
                SIVAL(outhdr, SMB2_HDR_NEXT_COMMAND,    next_command_ofs);
@@ -467,23 +582,71 @@ void smbd_server_connection_terminate_ex(struct smbd_server_connection *sconn,
        exit_server_cleanly(reason);
 }
 
-static bool dup_smb2_vec(struct iovec *dstvec,
-                       const struct iovec *srcvec,
-                       int offset)
+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. */
 
-       if (srcvec[offset].iov_len &&
-                       srcvec[offset].iov_base) {
-               dstvec[offset].iov_base = talloc_memdup(dstvec,
-                                       srcvec[offset].iov_base,
-                                       srcvec[offset].iov_len);
-               if (!dstvec[offset].iov_base) {
+       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;
                }
-               dstvec[offset].iov_len = srcvec[offset].iov_len;
+               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 {
-               dstvec[offset].iov_base = NULL;
-               dstvec[offset].iov_len = 0;
+               outvec[2].iov_base = NULL;
+               outvec[2].iov_len = 0;
        }
        return true;
 }
@@ -506,7 +669,7 @@ static struct smbd_smb2_request *dup_smb2_req(const struct smbd_smb2_request *re
        newreq->async = false;
        newreq->cancelled = false;
 
-       outvec = talloc_array(newreq, struct iovec, count);
+       outvec = talloc_zero_array(newreq, struct iovec, count);
        if (!outvec) {
                TALLOC_FREE(newreq);
                return NULL;
@@ -518,16 +681,20 @@ static struct smbd_smb2_request *dup_smb2_req(const struct smbd_smb2_request *re
        outvec[0].iov_base = newreq->out.nbt_hdr;
        outvec[0].iov_len = 4;
        memcpy(newreq->out.nbt_hdr, req->out.nbt_hdr, 4);
-               
-       for (i = 1; i < count; i++) {
-               if (!dup_smb2_vec(outvec,
-                               req->out.vector,
-                               i)) {
-                       TALLOC_FREE(newreq);
-                       return NULL;
+
+       /* 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);
 
@@ -558,10 +725,13 @@ static NTSTATUS smb2_send_async_interim_response(const struct smbd_smb2_request
 
        /* Step back to the previous reply. */
        i = nreq->current_idx - 3;
-       outhdr = nreq->out.vector[i].iov_base;
+       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;
@@ -640,6 +810,9 @@ NTSTATUS smbd_smb2_request_pending_queue(struct smbd_smb2_request *req,
                return NT_STATUS_OK;
        }
 
+       req->subreq = subreq;
+       subreq = NULL;
+
        if (req->async) {
                /* We're already async. */
                return NT_STATUS_OK;
@@ -651,14 +824,11 @@ NTSTATUS smbd_smb2_request_pending_queue(struct smbd_smb2_request *req,
                 * request chain. This is not allowed.
                 * Cancel the outstanding request.
                 */
-               tevent_req_cancel(subreq);
+               tevent_req_cancel(req->subreq);
                return smbd_smb2_request_error(req,
                        NT_STATUS_INSUFFICIENT_RESOURCES);
        }
 
-       req->subreq = subreq;
-       subreq = NULL;
-
        if (DEBUGLEVEL >= 10) {
                dbgtext("smbd_smb2_request_pending_queue: req->current_idx = %u\n",
                        (unsigned int)req->current_idx );
@@ -677,11 +847,22 @@ NTSTATUS smbd_smb2_request_pending_queue(struct smbd_smb2_request *req,
                }
        }
 
+       /* 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);
+       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;
@@ -699,15 +880,15 @@ NTSTATUS smbd_smb2_request_pending_queue(struct smbd_smb2_request *req,
 
        smb2_setup_nbt_length(state->vector, 3);
 
-       hdr = state->vector[1].iov_base;
-       body = state->vector[2].iov_base;
+       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));
-       SSVAL(hdr, SMB2_HDR_CREDIT, 5);
+
        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);
@@ -724,6 +905,13 @@ NTSTATUS smbd_smb2_request_pending_queue(struct smbd_smb2_request *req,
        /* 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);
@@ -769,39 +957,21 @@ NTSTATUS smbd_smb2_request_pending_queue(struct smbd_smb2_request *req,
        smb2_setup_nbt_length(req->in.vector, 4);
 
        /* Now recreate the out.vectors. */
-       outvec = talloc_array(req, struct iovec, 4);
+       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);
 
-       outvec[1].iov_base = talloc_memdup(outvec,
-                               req->out.vector[i].iov_base,
-                               SMB2_HDR_BODY + 8);
-       if (!outvec[1].iov_base) {
+       if (!dup_smb2_vec3(outvec, &outvec[1], &req->out.vector[i])) {
                return NT_STATUS_NO_MEMORY;
        }
-       outvec[1].iov_len = SMB2_HDR_BODY;
-
-       outvec[2].iov_base = ((uint8_t *)outvec[1].iov_base) +
-                               SMB2_HDR_BODY;
-       outvec[2].iov_len = 8;
-
-       if (req->out.vector[i+2].iov_base &&
-                       req->out.vector[i+2].iov_len) {
-               outvec[3].iov_base = talloc_memdup(outvec,
-                                       req->out.vector[i+2].iov_base,
-                                       req->out.vector[i+2].iov_len);
-               if (!outvec[3].iov_base) {
-                       return NT_STATUS_NO_MEMORY;
-               }
-               outvec[3].iov_len = req->out.vector[i+2].iov_len;
-       } else {
-               outvec[3].iov_base = NULL;
-               outvec[3].iov_len = 0;
-       }
 
        TALLOC_FREE(req->out.vector);
 
@@ -852,6 +1022,7 @@ static NTSTATUS smbd_smb2_request_process_cancel(struct smbd_smb2_request *req)
         * 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) {
@@ -886,14 +1057,12 @@ static NTSTATUS smbd_smb2_request_process_cancel(struct smbd_smb2_request *req)
                        smb2_opcode_name((uint16_t)IVAL(inhdr, SMB2_HDR_OPCODE)),
                         (unsigned long long)found_id ));
                tevent_req_cancel(cur->subreq);
-               TALLOC_FREE(cur->subreq);
-               TALLOC_FREE(cur);
        }
 
        return NT_STATUS_OK;
 }
 
-static NTSTATUS smbd_smb2_request_dispatch(struct smbd_smb2_request *req)
+NTSTATUS smbd_smb2_request_dispatch(struct smbd_smb2_request *req)
 {
        const uint8_t *inhdr;
        int i = req->current_idx;
@@ -903,6 +1072,7 @@ static NTSTATUS smbd_smb2_request_dispatch(struct smbd_smb2_request *req)
        NTSTATUS status;
        NTSTATUS session_status;
        uint32_t allowed_flags;
+       NTSTATUS return_value;
 
        inhdr = (const uint8_t *)req->in.vector[i].iov_base;
 
@@ -960,189 +1130,322 @@ 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;
                }
-               return smbd_smb2_request_process_tcon(req);
+
+               {
+                       START_PROFILE(smb2_tcon);
+                       return_value = smbd_smb2_request_process_tcon(req);
+                       END_PROFILE(smb2_tcon);
+               }
+               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;
+               }
+
+               {
+                       START_PROFILE(smb2_flush);
+                       return_value = smbd_smb2_request_process_flush(req);
+                       END_PROFILE(smb2_flush);
                }
-               return smbd_smb2_request_process_flush(req);
+               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;
+               }
+
+               {
+                       START_PROFILE(smb2_read);
+                       return_value = smbd_smb2_request_process_read(req);
+                       END_PROFILE(smb2_read);
                }
-               return smbd_smb2_request_process_read(req);
+               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;
+               }
+
+               {
+                       START_PROFILE(smb2_write);
+                       return_value = smbd_smb2_request_process_write(req);
+                       END_PROFILE(smb2_write);
                }
-               return smbd_smb2_request_process_write(req);
+               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;
                }
-               return smbd_smb2_request_process_lock(req);
+
+               {
+                       START_PROFILE(smb2_lock);
+                       return_value = smbd_smb2_request_process_lock(req);
+                       END_PROFILE(smb2_lock);
+               }
+               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;
                }
-               return smbd_smb2_request_process_ioctl(req);
+
+               {
+                       START_PROFILE(smb2_ioctl);
+                       return_value = smbd_smb2_request_process_ioctl(req);
+                       END_PROFILE(smb2_ioctl);
+               }
+               break;
 
        case SMB2_OP_CANCEL:
-               return smbd_smb2_request_process_cancel(req);
+               {
+                       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;
+               }
+
+               {
+                       START_PROFILE(smb2_find);
+                       return_value = smbd_smb2_request_process_find(req);
+                       END_PROFILE(smb2_find);
                }
-               return smbd_smb2_request_process_find(req);
+               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;
+               }
+
+               {
+                       START_PROFILE(smb2_notify);
+                       return_value = smbd_smb2_request_process_notify(req);
+                       END_PROFILE(smb2_notify);
                }
-               return smbd_smb2_request_process_notify(req);
+               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;
+               }
+
+               {
+                       START_PROFILE(smb2_getinfo);
+                       return_value = smbd_smb2_request_process_getinfo(req);
+                       END_PROFILE(smb2_getinfo);
                }
-               return smbd_smb2_request_process_getinfo(req);
+               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;
+               }
+
+               {
+                       START_PROFILE(smb2_setinfo);
+                       return_value = smbd_smb2_request_process_setinfo(req);
+                       END_PROFILE(smb2_setinfo);
                }
-               return smbd_smb2_request_process_setinfo(req);
+               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_process_break(req);
-       }
 
-       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_context *ctx,
-                                       struct tevent_immediate *im,
-                                       void *private_data);
+       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;
 
-       smb2_setup_nbt_length(req->out.vector, req->out.vector_count);
-
-       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);
-               if (!NT_STATUS_IS_OK(status)) {
-                       return status;
-               }
-       }
-
        req->current_idx += 3;
 
        if (req->current_idx < req->out.vector_count) {
@@ -1160,16 +1463,42 @@ static NTSTATUS smbd_smb2_request_reply(struct smbd_smb2_request *req)
                }
                tevent_schedule_immediate(im,
                                        req->sconn->smb2.event_ctx,
-                                       smbd_smb2_request_dispatch_compound,
+                                       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) {
+               NTSTATUS status;
+               status = smb2_signing_sign_pdu(req->session->session_key,
+                                              &req->out.vector[i], 3);
+               if (!NT_STATUS_IS_OK(status)) {
+                       return status;
+               }
+       }
+
        if (DEBUGLEVEL >= 10) {
                dbgtext("smbd_smb2_request_reply: sending...\n");
                print_req_vectors(req);
        }
 
+       /* 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->sconn->smb2.event_ctx,
                                           req->sconn->smb2.stream,
@@ -1180,11 +1509,16 @@ static NTSTATUS smbd_smb2_request_reply(struct smbd_smb2_request *req)
                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_context *ctx,
+void smbd_smb2_request_dispatch_immediate(struct tevent_context *ctx,
                                        struct tevent_immediate *im,
                                        void *private_data)
 {
@@ -1196,7 +1530,7 @@ static void smbd_smb2_request_dispatch_compound(struct tevent_context *ctx,
        TALLOC_FREE(im);
 
        if (DEBUGLEVEL >= 10) {
-               DEBUG(10,("smbd_smb2_request_dispatch_compound: idx[%d] of %d vectors\n",
+               DEBUG(10,("smbd_smb2_request_dispatch_immediate: idx[%d] of %d vectors\n",
                        req->current_idx, req->in.vector_count));
                print_req_vectors(req);
        }
@@ -1228,53 +1562,12 @@ static void smbd_smb2_request_writev_done(struct tevent_req *subreq)
        }
 }
 
-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,
                                   NTSTATUS status,
                                   DATA_BLOB body, DATA_BLOB *dyn,
                                   const char *location)
 {
        uint8_t *outhdr;
-       uint8_t *outdyn;
        int i = req->current_idx;
        uint32_t next_command_ofs;
 
@@ -1294,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));
@@ -1350,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,
@@ -1362,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;
        }
@@ -1373,6 +1662,52 @@ 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];
@@ -1833,7 +2168,7 @@ 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",
@@ -1851,7 +2186,7 @@ void smbd_smb2_first_negprot(struct smbd_server_connection *sconn,
                return;
        }
 
-       status = smbd_smb2_request_setup_out(req, (uint16_t)lp_maxmux());
+       status = smbd_smb2_request_setup_out(req);
        if (!NT_STATUS_IS_OK(status)) {
                smbd_server_connection_terminate(sconn, nt_errstr(status));
                return;
@@ -1874,7 +2209,6 @@ void smbd_smb2_first_negprot(struct smbd_server_connection *sconn,
 
 static void smbd_smb2_request_incoming(struct tevent_req *subreq)
 {
-       uint16_t creds_requested = 0;
        struct smbd_server_connection *sconn = tevent_req_callback_data(subreq,
                                               struct smbd_server_connection);
        NTSTATUS status;
@@ -1901,13 +2235,13 @@ static void smbd_smb2_request_incoming(struct tevent_req *subreq)
        DEBUG(10,("smbd_smb2_request_incoming: idx[%d] of %d vectors\n",
                 req->current_idx, req->in.vector_count));
 
-       status = smbd_smb2_request_validate(req, &creds_requested);
+       status = smbd_smb2_request_validate(req);
        if (!NT_STATUS_IS_OK(status)) {
                smbd_server_connection_terminate(sconn, nt_errstr(status));
                return;
        }
 
-       status = smbd_smb2_request_setup_out(req, 5);
+       status = smbd_smb2_request_setup_out(req);
        if (!NT_STATUS_IS_OK(status)) {
                smbd_server_connection_terminate(sconn, nt_errstr(status));
                return;