s4:smb_server/smb2: use helper variable smb2srv_chain_reply()
[amitay/samba.git] / source4 / smb_server / smb2 / receive.c
index 31b7f72ccaaa534b4d9bdf2682a836f120f0365c..b8aba71aaa777c4b3748cd1c88e54b50156ca826 100644 (file)
 #include "libcli/smb2/smb2.h"
 #include "libcli/smb2/smb2_calls.h"
 #include "smb_server/smb_server.h"
-#include "smb_server/service_smb_proto.h"
 #include "smb_server/smb2/smb2_server.h"
 #include "smbd/service_stream.h"
 #include "lib/stream/packet.h"
 #include "ntvfs/ntvfs.h"
 #include "param/param.h"
+#include "auth/auth.h"
+
+
+/* fill in the bufinfo */
+void smb2srv_setup_bufinfo(struct smb2srv_request *req)
+{
+       req->in.bufinfo.mem_ctx    = req;
+       req->in.bufinfo.flags      = BUFINFO_FLAG_UNICODE | BUFINFO_FLAG_SMB2;
+       req->in.bufinfo.align_base = req->in.buffer;
+       if (req->in.dynamic) {
+               req->in.bufinfo.data       = req->in.dynamic;
+               req->in.bufinfo.data_size  = req->in.body_size - req->in.body_fixed;
+       } else {
+               req->in.bufinfo.data       = NULL;
+               req->in.bufinfo.data_size  = 0;
+       }
+}
 
 static int smb2srv_request_destructor(struct smb2srv_request *req)
 {
@@ -53,22 +69,33 @@ struct smb2srv_request *smb2srv_init_request(struct smbsrv_connection *smb_conn)
 
        req->smb_conn = smb_conn;
 
+       req->chained_session_id = UINT64_MAX;
+       req->chained_tree_id = UINT32_MAX;
+
        talloc_set_destructor(req, smb2srv_request_destructor);
 
        return req;
 }
 
 NTSTATUS smb2srv_setup_reply(struct smb2srv_request *req, uint16_t body_fixed_size,
-                            BOOL body_dynamic_present, uint32_t body_dynamic_size)
+                            bool body_dynamic_present, uint32_t body_dynamic_size)
 {
-       uint32_t flags = 0x00000001;
+       uint32_t flags = IVAL(req->in.hdr, SMB2_HDR_FLAGS);
        uint32_t pid = IVAL(req->in.hdr, SMB2_HDR_PID);
        uint32_t tid = IVAL(req->in.hdr, SMB2_HDR_TID);
+       uint16_t credits = SVAL(req->in.hdr, SMB2_HDR_CREDIT);
+
+       if (credits == 0) {
+               credits = 1;
+       }
+
+       flags |= SMB2_HDR_FLAG_REDIRECT;
 
        if (req->pending_id) {
-               flags |= 0x00000002;
+               flags |= SMB2_HDR_FLAG_ASYNC;
                pid = req->pending_id;
                tid = 0;
+               credits = 0;
        }
 
        if (body_dynamic_present) {
@@ -94,17 +121,19 @@ NTSTATUS smb2srv_setup_reply(struct smb2srv_request *req, uint16_t body_fixed_si
 
        SIVAL(req->out.hdr, 0,                          SMB2_MAGIC);
        SSVAL(req->out.hdr, SMB2_HDR_LENGTH,            SMB2_HDR_BODY);
-       SSVAL(req->out.hdr, SMB2_HDR_PAD1,              0);
+       SSVAL(req->out.hdr, SMB2_HDR_CREDIT_CHARGE,
+             SVAL(req->in.hdr, SMB2_HDR_CREDIT_CHARGE));
        SIVAL(req->out.hdr, SMB2_HDR_STATUS,            NT_STATUS_V(req->status));
        SSVAL(req->out.hdr, SMB2_HDR_OPCODE,            SVAL(req->in.hdr, SMB2_HDR_OPCODE));
-       SSVAL(req->out.hdr, SMB2_HDR_UNKNOWN1,          0x0001);
+       SSVAL(req->out.hdr, SMB2_HDR_CREDIT,            credits);
        SIVAL(req->out.hdr, SMB2_HDR_FLAGS,             flags);
-       SIVAL(req->out.hdr, SMB2_HDR_CHAIN_OFFSET,      0);
-       SBVAL(req->out.hdr, SMB2_HDR_SEQNUM,            req->seqnum);
+       SIVAL(req->out.hdr, SMB2_HDR_NEXT_COMMAND,      0);
+       SBVAL(req->out.hdr, SMB2_HDR_MESSAGE_ID,        req->seqnum);
        SIVAL(req->out.hdr, SMB2_HDR_PID,               pid);
        SIVAL(req->out.hdr, SMB2_HDR_TID,               tid);
-       SBVAL(req->out.hdr, SMB2_HDR_UID,               BVAL(req->in.hdr, SMB2_HDR_UID));
-       memset(req->out.hdr+SMB2_HDR_SIG, 0, 16);
+       SBVAL(req->out.hdr, SMB2_HDR_SESSION_ID,        BVAL(req->in.hdr, SMB2_HDR_SESSION_ID));
+       memcpy(req->out.hdr+SMB2_HDR_SIGNATURE,
+              req->in.hdr+SMB2_HDR_SIGNATURE, 16);
 
        /* set the length of the fixed body part and +1 if there's a dynamic part also */
        SSVAL(req->out.body, 0, body_fixed_size + (body_dynamic_size?1:0));
@@ -126,33 +155,38 @@ static NTSTATUS smb2srv_reply(struct smb2srv_request *req);
 static void smb2srv_chain_reply(struct smb2srv_request *p_req)
 {
        NTSTATUS status;
+       struct smbsrv_connection *smb_conn = p_req->smb_conn;
        struct smb2srv_request *req;
        uint32_t chain_offset;
        uint32_t protocol_version;
        uint16_t buffer_code;
        uint32_t dynamic_size;
+       uint32_t flags;
+       uint32_t last_hdr_offset;
+
+       last_hdr_offset = p_req->in.hdr - p_req->in.buffer;
 
        chain_offset = p_req->chain_offset;
        p_req->chain_offset = 0;
 
-       if (p_req->in.size < (NBT_HDR_SIZE + chain_offset + SMB2_MIN_SIZE)) {
-               DEBUG(2,("Invalid SMB2 chained packet at offset 0x%X\n",
-                       chain_offset));
-               smbsrv_terminate_connection(p_req->smb_conn, "Invalid SMB2 chained packet");
+       if (p_req->in.size < (last_hdr_offset + chain_offset + SMB2_MIN_SIZE_NO_BODY)) {
+               DEBUG(2,("Invalid SMB2 chained packet at offset 0x%X from last hdr 0x%X\n",
+                       chain_offset, last_hdr_offset));
+               smbsrv_terminate_connection(smb_conn, "Invalid SMB2 chained packet");
                return;
        }
 
-       protocol_version = IVAL(p_req->in.buffer, NBT_HDR_SIZE + chain_offset);
+       protocol_version = IVAL(p_req->in.buffer, last_hdr_offset + chain_offset);
        if (protocol_version != SMB2_MAGIC) {
                DEBUG(2,("Invalid SMB chained packet: protocol prefix: 0x%08X\n",
                         protocol_version));
-               smbsrv_terminate_connection(p_req->smb_conn, "NON-SMB2 chained packet");
+               smbsrv_terminate_connection(smb_conn, "NON-SMB2 chained packet");
                return;
        }
 
-       req = smb2srv_init_request(p_req->smb_conn);
+       req = smb2srv_init_request(smb_conn);
        if (!req) {
-               smbsrv_terminate_connection(p_req->smb_conn, "SMB2 chained packet - no memory");
+               smbsrv_terminate_connection(smb_conn, "SMB2 chained packet - no memory");
                return;
        }
 
@@ -161,11 +195,24 @@ static void smb2srv_chain_reply(struct smb2srv_request *p_req)
        req->request_time       = p_req->request_time;
        req->in.allocated       = req->in.size;
 
-       req->in.hdr             = req->in.buffer+ NBT_HDR_SIZE + chain_offset;
+       req->in.hdr             = req->in.buffer+ last_hdr_offset + chain_offset;
        req->in.body            = req->in.hdr   + SMB2_HDR_BODY;
-       req->in.body_size       = req->in.size  - (NBT_HDR_SIZE+ chain_offset + SMB2_HDR_BODY);
+       req->in.body_size       = req->in.size  - (last_hdr_offset+ chain_offset + SMB2_HDR_BODY);
        req->in.dynamic         = NULL;
 
+       req->seqnum             = BVAL(req->in.hdr, SMB2_HDR_MESSAGE_ID);
+
+       if (req->in.body_size < 2) {
+               /* error handling for this is different for negprot to 
+                  other packet types */
+               uint16_t opcode = SVAL(req->in.hdr, SMB2_HDR_OPCODE);
+               if (opcode == SMB2_OP_NEGPROT) {
+                       smbsrv_terminate_connection(smb_conn, "Bad body size in SMB2 negprot");
+               } else {
+                       smb2srv_send_error(req, NT_STATUS_INVALID_PARAMETER);
+               }
+       }
+
        buffer_code             = SVAL(req->in.body, 0);
        req->in.body_fixed      = (buffer_code & ~1);
        dynamic_size            = req->in.body_size - req->in.body_fixed;
@@ -180,11 +227,19 @@ static void smb2srv_chain_reply(struct smb2srv_request *p_req)
                }
        }
 
-       if (p_req->chained_file_handle) {
-               memcpy(req->_chained_file_handle,
-                      p_req->_chained_file_handle,
-                      sizeof(req->_chained_file_handle));
-               req->chained_file_handle = req->_chained_file_handle;
+       smb2srv_setup_bufinfo(req);
+
+       flags = IVAL(req->in.hdr, SMB2_HDR_FLAGS);
+       if (flags & SMB2_HDR_FLAG_CHAINED) {
+               if (p_req->chained_file_handle) {
+                       memcpy(req->_chained_file_handle,
+                              p_req->_chained_file_handle,
+                              sizeof(req->_chained_file_handle));
+                       req->chained_file_handle = req->_chained_file_handle;
+               }
+               req->chained_session_id = p_req->chained_session_id;
+               req->chained_tree_id = p_req->chained_tree_id;
+               req->chain_status = p_req->chain_status;
        }
 
        /* 
@@ -194,7 +249,7 @@ static void smb2srv_chain_reply(struct smb2srv_request *p_req)
 
        status = smb2srv_reply(req);
        if (!NT_STATUS_IS_OK(status)) {
-               smbsrv_terminate_connection(req->smb_conn, nt_errstr(status));
+               smbsrv_terminate_connection(smb_conn, nt_errstr(status));
                talloc_free(req);
                return;
        }
@@ -212,9 +267,20 @@ void smb2srv_send_reply(struct smb2srv_request *req)
        }
 
        if (req->out.size > NBT_HDR_SIZE) {
-               _smb2_setlen(req->out.buffer, req->out.size - NBT_HDR_SIZE);
+               _smb_setlen_tcp(req->out.buffer, req->out.size - NBT_HDR_SIZE);
+       }
+
+       /* if signing is active on the session then sign the packet */
+       if (req->is_signed) {
+               status = smb2_sign_message(&req->out, 
+                                          req->session->session_info->session_key);
+               if (!NT_STATUS_IS_OK(status)) {
+                       smbsrv_terminate_connection(req->smb_conn, nt_errstr(status));
+                       return;
+               }               
        }
 
+
        blob = data_blob_const(req->out.buffer, req->out.size);
        status = packet_send(req->smb_conn->packet, blob);
        if (!NT_STATUS_IS_OK(status)) {
@@ -237,7 +303,7 @@ void smb2srv_send_error(struct smb2srv_request *req, NTSTATUS error)
                return;
        }
 
-       status = smb2srv_setup_reply(req, 8, True, 0);
+       status = smb2srv_setup_reply(req, 8, true, 0);
        if (!NT_STATUS_IS_OK(status)) {
                smbsrv_terminate_connection(req->smb_conn, nt_errstr(status));
                talloc_free(req);
@@ -249,6 +315,8 @@ void smb2srv_send_error(struct smb2srv_request *req, NTSTATUS error)
        SSVAL(req->out.body, 0x02, 0);
        SIVAL(req->out.body, 0x04, 0);
 
+       req->chain_status = NT_STATUS_INVALID_PARAMETER;
+
        smb2srv_send_reply(req);
 }
 
@@ -257,19 +325,67 @@ static NTSTATUS smb2srv_reply(struct smb2srv_request *req)
        uint16_t opcode;
        uint32_t tid;
        uint64_t uid;
+       uint32_t flags;
 
+       if (SVAL(req->in.hdr, SMB2_HDR_LENGTH) != SMB2_HDR_BODY) {
+               smbsrv_terminate_connection(req->smb_conn, "Invalid SMB2 header length");
+               return NT_STATUS_INVALID_PARAMETER;
+       }
        opcode                  = SVAL(req->in.hdr, SMB2_HDR_OPCODE);
-       req->chain_offset       = IVAL(req->in.hdr, SMB2_HDR_CHAIN_OFFSET);
-       req->seqnum             = BVAL(req->in.hdr, SMB2_HDR_SEQNUM);
+       req->chain_offset       = IVAL(req->in.hdr, SMB2_HDR_NEXT_COMMAND);
+       req->seqnum             = BVAL(req->in.hdr, SMB2_HDR_MESSAGE_ID);
        tid                     = IVAL(req->in.hdr, SMB2_HDR_TID);
-       uid                     = BVAL(req->in.hdr, SMB2_HDR_UID);
+       uid                     = BVAL(req->in.hdr, SMB2_HDR_SESSION_ID);
+       flags                   = IVAL(req->in.hdr, SMB2_HDR_FLAGS);
+
+       if (opcode != SMB2_OP_CANCEL &&
+           req->smb_conn->highest_smb2_seqnum != 0 &&
+           req->seqnum <= req->smb_conn->highest_smb2_seqnum) {
+               smbsrv_terminate_connection(req->smb_conn, "Invalid SMB2 sequence number");
+               return NT_STATUS_INVALID_PARAMETER;
+       }
+       if (opcode != SMB2_OP_CANCEL) {
+               req->smb_conn->highest_smb2_seqnum = req->seqnum;
+       }
+
+       if (flags & SMB2_HDR_FLAG_CHAINED) {
+               uid = req->chained_session_id;
+               tid = req->chained_tree_id;
+       }
 
        req->session    = smbsrv_session_find(req->smb_conn, uid, req->request_time);
        req->tcon       = smbsrv_smb2_tcon_find(req->session, tid, req->request_time);
 
+       req->chained_session_id = uid;
+       req->chained_tree_id = tid;
+
        errno = 0;
 
-       /* TODO: check the seqnum */
+       /* supporting signing is mandatory in SMB2, and is per-packet. So we 
+          should check the signature on any incoming packet that is signed, and 
+          should give a signed reply to any signed request */
+       if (flags & SMB2_HDR_FLAG_SIGNED) {
+               NTSTATUS status;
+
+               if (!req->session) goto nosession;
+
+               req->is_signed = true;
+               status = smb2_check_signature(&req->in, 
+                                             req->session->session_info->session_key);
+               if (!NT_STATUS_IS_OK(status)) {
+                       smb2srv_send_error(req, status);
+                       return NT_STATUS_OK;                    
+               }
+       } else if (req->session && req->session->smb2_signing.active) {
+               /* we require signing and this request was not signed */
+               smb2srv_send_error(req, NT_STATUS_ACCESS_DENIED);
+               return NT_STATUS_OK;                                    
+       }
+
+       if (!NT_STATUS_IS_OK(req->chain_status)) {
+               smb2srv_send_error(req, req->chain_status);
+               return NT_STATUS_OK;
+       }
 
        switch (opcode) {
        case SMB2_OP_NEGPROT:
@@ -371,14 +487,15 @@ notcon:
        return NT_STATUS_OK;
 }
 
-NTSTATUS smbsrv_recv_smb2_request(void *private, DATA_BLOB blob)
+NTSTATUS smbsrv_recv_smb2_request(void *private_data, DATA_BLOB blob)
 {
-       struct smbsrv_connection *smb_conn = talloc_get_type(private, struct smbsrv_connection);
+       struct smbsrv_connection *smb_conn = talloc_get_type(private_data, struct smbsrv_connection);
        struct smb2srv_request *req;
        struct timeval cur_time = timeval_current();
        uint32_t protocol_version;
        uint16_t buffer_code;
        uint32_t dynamic_size;
+       uint32_t flags;
 
        smb_conn->statistics.last_request_time = cur_time;
 
@@ -389,7 +506,7 @@ NTSTATUS smbsrv_recv_smb2_request(void *private, DATA_BLOB blob)
                return NT_STATUS_OK;
        }
 
-       if (blob.length < (NBT_HDR_SIZE + SMB2_MIN_SIZE)) {
+       if (blob.length < (NBT_HDR_SIZE + SMB2_MIN_SIZE_NO_BODY)) {
                DEBUG(2,("Invalid SMB2 packet length count %ld\n", (long)blob.length));
                smbsrv_terminate_connection(smb_conn, "Invalid SMB2 packet");
                return NT_STATUS_OK;
@@ -416,6 +533,21 @@ NTSTATUS smbsrv_recv_smb2_request(void *private, DATA_BLOB blob)
        req->in.body_size       = req->in.size  - (SMB2_HDR_BODY+NBT_HDR_SIZE);
        req->in.dynamic         = NULL;
 
+       req->seqnum             = BVAL(req->in.hdr, SMB2_HDR_MESSAGE_ID);
+
+       if (req->in.body_size < 2) {
+               /* error handling for this is different for negprot to 
+                  other packet types */
+               uint16_t opcode = SVAL(req->in.hdr, SMB2_HDR_OPCODE);
+               if (opcode == SMB2_OP_NEGPROT) {
+                       smbsrv_terminate_connection(req->smb_conn, "Bad body size in SMB2 negprot");                    
+                       return NT_STATUS_OK;
+               } else {
+                       smb2srv_send_error(req, NT_STATUS_INVALID_PARAMETER);
+                       return NT_STATUS_OK;
+               }
+       }
+
        buffer_code             = SVAL(req->in.body, 0);
        req->in.body_fixed      = (buffer_code & ~1);
        dynamic_size            = req->in.body_size - req->in.body_fixed;
@@ -430,11 +562,19 @@ NTSTATUS smbsrv_recv_smb2_request(void *private, DATA_BLOB blob)
                }
        }
 
+       smb2srv_setup_bufinfo(req);
+
        /* 
         * TODO: - make sure the length field is 64
         *       - make sure it's a request
         */
 
+       flags = IVAL(req->in.hdr, SMB2_HDR_FLAGS);
+       /* the first request should never have the related flag set */
+       if (flags & SMB2_HDR_FLAG_CHAINED) {
+               req->chain_status = NT_STATUS_INVALID_PARAMETER;
+       }
+
        return smb2srv_reply(req);
 }
 
@@ -450,12 +590,24 @@ static NTSTATUS smb2srv_init_pending(struct smbsrv_connection *smb_conn)
 
 NTSTATUS smb2srv_queue_pending(struct smb2srv_request *req)
 {
+       NTSTATUS status;
+       bool signing_used = false;
        int id;
+       uint16_t credits = SVAL(req->in.hdr, SMB2_HDR_CREDIT);
+
+       if (credits == 0) {
+               credits = 1;
+       }
 
        if (req->pending_id) {
                return NT_STATUS_INTERNAL_ERROR;
        }
 
+       if (req->smb_conn->connection->event.fde == NULL) {
+               /* the socket has been destroyed - no point trying to send an error! */
+               return NT_STATUS_REMOTE_DISCONNECT;
+       }
+
        id = idr_get_new_above(req->smb_conn->requests2.idtree_req, req, 
                               1, req->smb_conn->requests2.idtree_limit);
        if (id == -1) {
@@ -466,9 +618,30 @@ NTSTATUS smb2srv_queue_pending(struct smb2srv_request *req)
        req->pending_id = id;
 
        talloc_set_destructor(req, smb2srv_request_deny_destructor);
-       smb2srv_send_error(req, STATUS_PENDING);
-       talloc_set_destructor(req, smb2srv_request_destructor);
 
+       status = smb2srv_setup_reply(req, 8, true, 0);
+       if (!NT_STATUS_IS_OK(status)) {
+               return status;
+       }
+
+       SIVAL(req->out.hdr, SMB2_HDR_STATUS, NT_STATUS_V(STATUS_PENDING));
+       SSVAL(req->out.hdr, SMB2_HDR_CREDIT, credits);
+
+       SSVAL(req->out.body, 0x02, 0);
+       SIVAL(req->out.body, 0x04, 0);
+
+       /* if the real reply will be signed set the signed flags, but don't sign */
+       if (req->is_signed) {
+               SIVAL(req->out.hdr, SMB2_HDR_FLAGS, IVAL(req->out.hdr, SMB2_HDR_FLAGS) | SMB2_HDR_FLAG_SIGNED);
+               signing_used = req->is_signed;
+               req->is_signed = false;
+       }
+
+       smb2srv_send_reply(req);
+
+       req->is_signed = signing_used;
+
+       talloc_set_destructor(req, smb2srv_request_destructor);
        return NT_STATUS_OK;
 }
 
@@ -484,7 +657,7 @@ void smb2srv_cancel_recv(struct smb2srv_request *req)
        flags           = IVAL(req->in.hdr, SMB2_HDR_FLAGS);
        pending_id      = IVAL(req->in.hdr, SMB2_HDR_PID);
 
-       if (!(flags & 0x00000002)) {
+       if (!(flags & SMB2_HDR_FLAG_ASYNC)) {
                /* TODO: what to do here? */
                goto done;
        }
@@ -516,12 +689,11 @@ NTSTATUS smbsrv_init_smb2_connection(struct smbsrv_connection *smb_conn)
 
        /* this is the size that w2k uses, and it appears to be important for
           good performance */
-       smb_conn->negotiate.max_recv = lp_max_xmit(global_loadparm);
+       smb_conn->negotiate.max_recv = lpcfg_max_xmit(smb_conn->lp_ctx);
 
        smb_conn->negotiate.zone_offset = get_time_zone(time(NULL));
 
-       smb_conn->config.security = SEC_USER;
-       smb_conn->config.nt_status_support = True;
+       smb_conn->config.nt_status_support = true;
 
        status = smbsrv_init_sessions(smb_conn, UINT64_MAX);
        NT_STATUS_NOT_OK_RETURN(status);