Merge branch 'master' of ssh://git.samba.org/data/git/samba
[ira/wip.git] / source4 / libcli / smb2 / transport.c
index e87c7a68c02dd64481d9d16900c29b52e0b6f6d4..e112544c62159354d308343a6738e6033b9350f3 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"
 
 
 /*
   an event has happened on the socket
 */
-static void smb2_transport_event_handler(struct event_context *ev, 
-                                        struct fd_event *fde, 
-                                        uint16_t flags, void *private)
+static void smb2_transport_event_handler(struct tevent_context *ev, 
+                                        struct tevent_fd *fde, 
+                                        uint16_t flags, void *private_data)
 {
-       struct smb2_transport *transport = talloc_get_type(private,
+       struct smb2_transport *transport = talloc_get_type(private_data,
                                                           struct smb2_transport);
        if (flags & EVENT_FD_READ) {
                packet_recv(transport->packet);
@@ -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;
 }
 
@@ -61,20 +61,21 @@ static int transport_destructor(void *ptr)
 /*
   handle receive errors
 */
-static void smb2_transport_error(void *private, NTSTATUS status)
+static void smb2_transport_error(void *private_data, NTSTATUS status)
 {
-       struct smb2_transport *transport = talloc_get_type(private
+       struct smb2_transport *transport = talloc_get_type(private_data,
                                                           struct smb2_transport);
-       smb2_transport_dead(transport);
+       smb2_transport_dead(transport, status);
 }
 
-static NTSTATUS smb2_transport_finish_recv(void *private, DATA_BLOB blob);
+static NTSTATUS smb2_transport_finish_recv(void *private_data, 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,18 +140,60 @@ 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
  */
-static NTSTATUS smb2_transport_finish_recv(void *private, DATA_BLOB blob)
+static NTSTATUS smb2_transport_finish_recv(void *private_data, DATA_BLOB blob)
 {
-       struct smb2_transport *transport = talloc_get_type(private
+       struct smb2_transport *transport = talloc_get_type(private_data,
                                                             struct smb2_transport);
        uint8_t *buffer, *hdr;
        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,45 @@ 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 ((flags & SMB2_HDR_FLAG_ASYNC) &&
+           NT_STATUS_EQUAL(req->status, STATUS_PENDING)) {
+               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;
+       }
+
+       if (req->session && req->session->signing_active) {
+               status = smb2_check_signature(&req->in, 
+                                             req->session->session_key);
+               if (!NT_STATUS_IS_OK(status)) {
+                       /* the spec says to ignore packets with a bad signature */
+                       talloc_free(buffer);
+                       return status;
+               }
+       }
+
+       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
@@ -213,10 +301,10 @@ error:
 /*
   handle timeouts of individual smb requests
 */
-static void smb2_timeout_handler(struct event_context *ev, struct timed_event *te, 
-                                struct timeval t, void *private)
+static void smb2_timeout_handler(struct tevent_context *ev, struct tevent_timer *te, 
+                                struct timeval t, void *private_data)
 {
-       struct smb2_request *req = talloc_get_type(private, struct smb2_request);
+       struct smb2_request *req = talloc_get_type(private_data, struct smb2_request);
 
        if (req->state == SMB2_REQUEST_RECV) {
                DLIST_REMOVE(req->transport->pending_recv, req);
@@ -232,9 +320,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 +337,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 +349,16 @@ void smb2_transport_send(struct smb2_request *req)
                return;
        }
 
+       /* possibly sign the message */
+       if (req->session && req->session->signing_active) {
+               status = smb2_sign_message(&req->out, req->session->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 +371,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 tevent_context *ev, 
+                        struct tevent_timer *te, struct timeval t, void *private_data)
+{
+       struct smb2_transport *transport = talloc_get_type(private_data,
+                                                          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_data);
+}
+
+/*
+  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_data)
+{
+       transport->idle.func = idle_func;
+       transport->idle.private_data = private_data;
+       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);
+}