added server side SMB2 signing
[kai/samba.git] / source4 / libcli / smb2 / transport.c
index e87c7a68c02dd64481d9d16900c29b52e0b6f6d4..a9a9efb3aac3bb51f5f3a969567ccbf071b0b7a5 100644 (file)
@@ -7,7 +7,7 @@
    
    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
-   the Free Software Foundation; either version 2 of the License, or
+   the Free Software Foundation; either version 3 of the License, or
    (at your option) any later version.
    
    This program is distributed in the hope that it will be useful,
    GNU General Public License for more details.
    
    You should have received a copy of the GNU General Public License
-   along with this program; if not, write to the Free Software
-   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
 #include "includes.h"
 #include "libcli/raw/libcliraw.h"
+#include "libcli/raw/raw_proto.h"
 #include "libcli/smb2/smb2.h"
+#include "libcli/smb2/smb2_calls.h"
 #include "lib/socket/socket.h"
 #include "lib/events/events.h"
 #include "lib/stream/packet.h"
-#include "include/dlinklist.h"
+#include "lib/util/dlinklist.h"
 
 
 /*
@@ -50,10 +51,9 @@ static void smb2_transport_event_handler(struct event_context *ev,
 /*
   destroy a transport
  */
-static int transport_destructor(void *ptr)
+static int transport_destructor(struct smb2_transport *transport)
 {
-       struct smb2_transport *transport = ptr;
-       smb2_transport_dead(transport);
+       smb2_transport_dead(transport, NT_STATUS_LOCAL_DISCONNECT);
        return 0;
 }
 
@@ -65,7 +65,7 @@ static void smb2_transport_error(void *private, NTSTATUS status)
 {
        struct smb2_transport *transport = talloc_get_type(private, 
                                                           struct smb2_transport);
-       smb2_transport_dead(transport);
+       smb2_transport_dead(transport, status);
 }
 
 static NTSTATUS smb2_transport_finish_recv(void *private, DATA_BLOB blob);
@@ -74,7 +74,8 @@ static NTSTATUS smb2_transport_finish_recv(void *private, DATA_BLOB blob);
   create a transport structure based on an established socket
 */
 struct smb2_transport *smb2_transport_init(struct smbcli_socket *sock,
-                                          TALLOC_CTX *parent_ctx)
+                                          TALLOC_CTX *parent_ctx,
+                                          struct smbcli_options *options)
 {
        struct smb2_transport *transport;
 
@@ -82,6 +83,7 @@ struct smb2_transport *smb2_transport_init(struct smbcli_socket *sock,
        if (!transport) return NULL;
 
        transport->socket = talloc_steal(transport, sock);
+       transport->options = *options;
 
        /* setup the stream -> packet parser */
        transport->packet = packet_init(transport);
@@ -107,27 +109,30 @@ struct smb2_transport *smb2_transport_init(struct smbcli_socket *sock,
                                                    smb2_transport_event_handler,
                                                    transport);
 
-       packet_set_serialise(transport->packet, transport->socket->event.fde);
+       packet_set_fde(transport->packet, transport->socket->event.fde);
+       packet_set_serialise(transport->packet);
 
        talloc_set_destructor(transport, transport_destructor);
 
-       transport->options.timeout = 30;
-
        return transport;
 }
 
 /*
   mark the transport as dead
 */
-void smb2_transport_dead(struct smb2_transport *transport)
+void smb2_transport_dead(struct smb2_transport *transport, NTSTATUS status)
 {
        smbcli_sock_dead(transport->socket);
 
+       if (NT_STATUS_EQUAL(NT_STATUS_UNSUCCESSFUL, status)) {
+               status = NT_STATUS_UNEXPECTED_NETWORK_ERROR;
+       }
+
        /* kill all pending receives */
        while (transport->pending_recv) {
                struct smb2_request *req = transport->pending_recv;
                req->state = SMB2_REQUEST_ERROR;
-               req->status = NT_STATUS_NET_WRITE_FAULT;
+               req->status = status;
                DLIST_REMOVE(transport->pending_recv, req);
                if (req->async.fn) {
                        req->async.fn(req);
@@ -135,6 +140,43 @@ void smb2_transport_dead(struct smb2_transport *transport)
        }
 }
 
+static NTSTATUS smb2_handle_oplock_break(struct smb2_transport *transport,
+                                        const DATA_BLOB *blob)
+{
+       uint8_t *hdr;
+       uint16_t opcode;
+
+       hdr = blob->data+NBT_HDR_SIZE;
+
+       if (blob->length < (SMB2_MIN_SIZE+0x18)) {
+               DEBUG(1,("Discarding smb2 oplock reply of size %u\n",
+                        (unsigned)blob->length));
+               return NT_STATUS_INVALID_NETWORK_RESPONSE;
+       }
+
+       opcode  = SVAL(hdr, SMB2_HDR_OPCODE);
+
+       if (opcode != SMB2_OP_BREAK) {
+               return NT_STATUS_INVALID_NETWORK_RESPONSE;
+       }
+
+       if (transport->oplock.handler) {
+               uint8_t *body = hdr+SMB2_HDR_BODY;
+               struct smb2_handle h;
+               uint8_t level;
+
+               level = CVAL(body, 0x02);
+               smb2_pull_handle(body+0x08, &h);
+
+               transport->oplock.handler(transport, &h, level,
+                                         transport->oplock.private_data);
+       } else {
+               DEBUG(5,("Got SMB2 oplock break with no handler\n"));
+       }
+
+       return NT_STATUS_OK;
+}
+
 /*
   we have a full request in our receive buffer - match it to a pending request
   and process
@@ -147,6 +189,11 @@ static NTSTATUS smb2_transport_finish_recv(void *private, DATA_BLOB blob)
        int len;
        struct smb2_request *req = NULL;
        uint64_t seqnum;
+       uint32_t flags;
+       uint16_t buffer_code;
+       uint32_t dynamic_size;
+       uint32_t i;
+       NTSTATUS status;
 
        buffer = blob.data;
        len = blob.length;
@@ -158,7 +205,13 @@ static NTSTATUS smb2_transport_finish_recv(void *private, DATA_BLOB blob)
                goto error;
        }
 
-       seqnum = BVAL(hdr, SMB2_HDR_SEQNUM);
+       flags   = IVAL(hdr, SMB2_HDR_FLAGS);
+       seqnum  = BVAL(hdr, SMB2_HDR_MESSAGE_ID);
+
+       /* see MS-SMB2 3.2.5.19 */
+       if (seqnum == UINT64_MAX) {
+               return smb2_handle_oplock_break(transport, &blob);
+       }
 
        /* match the incoming request against the list of pending requests */
        for (req=transport->pending_recv; req; req=req->next) {
@@ -167,7 +220,7 @@ static NTSTATUS smb2_transport_finish_recv(void *private, DATA_BLOB blob)
 
        if (!req) {
                DEBUG(1,("Discarding unmatched reply with seqnum 0x%llx op %d\n", 
-                        seqnum, SVAL(hdr, SMB2_HDR_OPCODE)));
+                        (long long)seqnum, SVAL(hdr, SMB2_HDR_OPCODE)));
                goto error;
        }
 
@@ -180,10 +233,48 @@ static NTSTATUS smb2_transport_finish_recv(void *private, DATA_BLOB blob)
        req->in.hdr       = hdr;
        req->in.body      = hdr+SMB2_HDR_BODY;
        req->in.body_size = req->in.size - (SMB2_HDR_BODY+NBT_HDR_SIZE);
-       req->in.ptr       = req->in.body;
        req->status       = NT_STATUS(IVAL(hdr, SMB2_HDR_STATUS));
 
-       DEBUG(2, ("SMB2 RECV seqnum=0x%llx\n", req->seqnum));
+       if (transport->signing.signing_started &&
+           transport->signing.doing_signing) {
+               status = smb2_check_signature(&req->in, 
+                                             transport->signing.session_key);
+               if (!NT_STATUS_IS_OK(status)) {
+                       /* the spec says to ignore packets with a bad signature */
+                       talloc_free(buffer);
+                       return status;
+               }
+       }
+       
+
+       if (NT_STATUS_EQUAL(req->status, STATUS_PENDING)) {
+               if (flags & 0x00000002) {
+                       req->cancel.can_cancel = true;
+                       req->cancel.pending_id = IVAL(hdr, SMB2_HDR_PID);
+                       for (i=0; i< req->cancel.do_cancel; i++) {
+                               smb2_cancel(req);
+                       }
+               }
+               talloc_free(buffer);
+               return NT_STATUS_OK;
+       }
+
+       buffer_code = SVAL(req->in.body, 0);
+       req->in.body_fixed = (buffer_code & ~1);
+       req->in.dynamic = NULL;
+       dynamic_size = req->in.body_size - req->in.body_fixed;
+       if (dynamic_size != 0 && (buffer_code & 1)) {
+               req->in.dynamic = req->in.body + req->in.body_fixed;
+               if (smb2_oob(&req->in, req->in.dynamic, dynamic_size)) {
+                       DEBUG(1,("SMB2 request invalid dynamic size 0x%x\n", 
+                                dynamic_size));
+                       goto error;
+               }
+       }
+
+       smb2_setup_bufinfo(req);
+
+       DEBUG(2, ("SMB2 RECV seqnum=0x%llx\n", (long long)req->seqnum));
        dump_data(5, req->in.body, req->in.body_size);
 
        /* if this request has an async handler then call that to
@@ -232,9 +323,8 @@ static void smb2_timeout_handler(struct event_context *ev, struct timed_event *t
 /*
   destroy a request
 */
-static int smb2_request_destructor(void *ptr)
+static int smb2_request_destructor(struct smb2_request *req)
 {
-       struct smb2_request *req = talloc_get_type(ptr, struct smb2_request);
        if (req->state == SMB2_REQUEST_RECV) {
                DLIST_REMOVE(req->transport->pending_recv, req);
        }
@@ -250,9 +340,9 @@ void smb2_transport_send(struct smb2_request *req)
        DATA_BLOB blob;
        NTSTATUS status;
 
-       _smb_setlen(req->out.buffer, req->out.size - NBT_HDR_SIZE);
+       _smb2_setlen(req->out.buffer, req->out.size - NBT_HDR_SIZE);
 
-       DEBUG(2, ("SMB2 send seqnum=0x%llx\n", req->seqnum));
+       DEBUG(2, ("SMB2 send seqnum=0x%llx\n", (long long)req->seqnum));
        dump_data(5, req->out.body, req->out.body_size);
 
        /* check if the transport is dead */
@@ -262,6 +352,17 @@ void smb2_transport_send(struct smb2_request *req)
                return;
        }
 
+       /* possibly sign the message */
+       if (req->transport->signing.doing_signing &&
+           req->transport->signing.signing_started) {
+               status = smb2_sign_message(&req->out, req->transport->signing.session_key);
+               if (!NT_STATUS_IS_OK(status)) {
+                       req->state = SMB2_REQUEST_ERROR;
+                       req->status = status;
+                       return;
+               }
+       }
+       
        blob = data_blob_const(req->out.buffer, req->out.size);
        status = packet_send(req->transport->packet, blob);
        if (!NT_STATUS_IS_OK(status)) {
@@ -274,11 +375,47 @@ void smb2_transport_send(struct smb2_request *req)
        DLIST_ADD(req->transport->pending_recv, req);
 
        /* add a timeout */
-       if (req->transport->options.timeout) {
+       if (req->transport->options.request_timeout) {
                event_add_timed(req->transport->socket->event.ctx, req, 
-                               timeval_current_ofs(req->transport->options.timeout, 0), 
+                               timeval_current_ofs(req->transport->options.request_timeout, 0), 
                                smb2_timeout_handler, req);
        }
 
        talloc_set_destructor(req, smb2_request_destructor);
 }
+
+static void idle_handler(struct event_context *ev, 
+                        struct timed_event *te, struct timeval t, void *private)
+{
+       struct smb2_transport *transport = talloc_get_type(private,
+                                                          struct smb2_transport);
+       struct timeval next = timeval_add(&t, 0, transport->idle.period);
+       transport->socket->event.te = event_add_timed(transport->socket->event.ctx, 
+                                                     transport,
+                                                     next,
+                                                     idle_handler, transport);
+       transport->idle.func(transport, transport->idle.private);
+}
+
+/*
+  setup the idle handler for a transport
+  the period is in microseconds
+*/
+void smb2_transport_idle_handler(struct smb2_transport *transport, 
+                                void (*idle_func)(struct smb2_transport *, void *),
+                                uint64_t period,
+                                void *private)
+{
+       transport->idle.func = idle_func;
+       transport->idle.private = private;
+       transport->idle.period = period;
+
+       if (transport->socket->event.te != NULL) {
+               talloc_free(transport->socket->event.te);
+       }
+
+       transport->socket->event.te = event_add_timed(transport->socket->event.ctx, 
+                                                     transport,
+                                                     timeval_current_ofs(0, period),
+                                                     idle_handler, transport);
+}