r11662: the beginnings of a SMB2 client library. Very hackish, meant for experimentation
authorAndrew Tridgell <tridge@samba.org>
Fri, 11 Nov 2005 04:45:38 +0000 (04:45 +0000)
committerGerald (Jerry) Carter <jerry@samba.org>
Wed, 10 Oct 2007 18:46:09 +0000 (13:46 -0500)
(This used to be commit 68422dc73f6ea51bf906f3db223ae8abf077aba1)

source4/libcli/config.mk
source4/libcli/smb2/config.mk [new file with mode: 0644]
source4/libcli/smb2/negprot.c [new file with mode: 0644]
source4/libcli/smb2/request.c [new file with mode: 0644]
source4/libcli/smb2/smb2.h [new file with mode: 0644]
source4/libcli/smb2/transport.c [new file with mode: 0644]

index ea93b12a03c685814d2189bc7dfaaf0ab40dc4d5..8417d770b616c1669f6be5cbc625eef2f841b607 100644 (file)
@@ -122,3 +122,5 @@ OBJ_FILES = raw/rawfile.o \
                raw/rawdate.o \
                raw/rawlpq.o
 REQUIRED_SUBSYSTEMS = LIBPACKET
+
+include smb2/config.mk
diff --git a/source4/libcli/smb2/config.mk b/source4/libcli/smb2/config.mk
new file mode 100644 (file)
index 0000000..9840876
--- /dev/null
@@ -0,0 +1,6 @@
+[SUBSYSTEM::LIBCLI_SMB2]
+OBJ_FILES = \
+       transport.o \
+       request.o \
+       negprot.o
+REQUIRED_SUBSYSTEMS = LIBCLI_RAW LIBPACKET
diff --git a/source4/libcli/smb2/negprot.c b/source4/libcli/smb2/negprot.c
new file mode 100644 (file)
index 0000000..6b35373
--- /dev/null
@@ -0,0 +1,82 @@
+/* 
+   Unix SMB/CIFS implementation.
+
+   SMB2 client negprot handling
+
+   Copyright (C) Andrew Tridgell 2005
+   
+   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
+   (at your option) any later version.
+   
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   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.
+*/
+
+#include "includes.h"
+#include "libcli/raw/libcliraw.h"
+#include "libcli/smb2/smb2.h"
+
+/*
+  send a negprot request
+*/
+struct smb2_request *smb2_negprot_send(struct smb2_transport *transport)
+{
+       struct smb2_request *req;
+
+       req = smb2_request_init(transport, SMB2_OP_NEGPROT, 0x26);
+       if (req == NULL) return NULL;
+
+       memset(req->out.body, 0, 0x26);
+       SIVAL(req->out.body, 0, 0x00010024); /* unknown */
+
+       smb2_transport_send(req);
+
+       return req;
+}
+
+/*
+  recv a negprot reply
+*/
+NTSTATUS smb2_negprot_recv(struct smb2_request *req)
+{
+       NTTIME t1, t2;
+       DATA_BLOB secblob;
+       struct GUID guid;
+       NTSTATUS status;
+
+       if (!smb2_request_receive(req) || 
+           smb2_request_is_error(req)) {
+               return smb2_request_destroy(req);
+       }
+
+       t1 = smbcli_pull_nttime(req->in.body, 0x28);
+       t2 = smbcli_pull_nttime(req->in.body, 0x30);
+
+       secblob = smb2_pull_blob(req, req->in.body+0x40, req->in.body_size - 0x40);
+       status  = smb2_pull_guid(req, req->in.body+0x08, &guid);
+       NT_STATUS_NOT_OK_RETURN(status);
+
+       printf("Negprot reply:\n");
+       printf("t1  =%s\n", nt_time_string(req, t1));
+       printf("t2  =%s\n", nt_time_string(req, t2));
+       printf("guid=%s\n", GUID_string(req, &guid));
+
+       return smb2_request_destroy(req);
+}
+
+/*
+  sync negprot request
+*/
+NTSTATUS smb2_negprot(struct smb2_transport *transport)
+{
+       struct smb2_request *req = smb2_negprot_send(transport);
+       return smb2_negprot_recv(req);
+}
diff --git a/source4/libcli/smb2/request.c b/source4/libcli/smb2/request.c
new file mode 100644 (file)
index 0000000..1b2dc5e
--- /dev/null
@@ -0,0 +1,171 @@
+/* 
+   Unix SMB/CIFS implementation.
+
+   SMB2 client request handling
+
+   Copyright (C) Andrew Tridgell 2005
+   
+   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
+   (at your option) any later version.
+   
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   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.
+*/
+
+#include "includes.h"
+#include "libcli/raw/libcliraw.h"
+#include "libcli/smb2/smb2.h"
+#include "include/dlinklist.h"
+#include "lib/events/events.h"
+#include "librpc/gen_ndr/ndr_misc.h"
+
+/*
+  initialise a smb2 request
+*/
+struct smb2_request *smb2_request_init(struct smb2_transport *transport, 
+                                      uint16_t opcode, uint32_t body_size)
+{
+       struct smb2_request *req;
+
+       req = talloc(transport, struct smb2_request);
+       if (req == NULL) return NULL;
+
+       req->state     = SMB2_REQUEST_INIT;
+       req->transport = transport;
+       req->seqnum    = transport->seqnum++;
+       req->status    = NT_STATUS_OK;
+       req->async.fn  = NULL;
+       req->next = req->prev = NULL;
+
+       ZERO_STRUCT(req->in);
+       
+       req->out.allocated = SMB2_HDR_BODY+NBT_HDR_SIZE+body_size;
+       req->out.buffer    = talloc_size(req, req->out.allocated);
+       if (req->out.buffer == NULL) {
+               talloc_free(req);
+               return NULL;
+       }
+
+       req->out.size      = SMB2_HDR_BODY+NBT_HDR_SIZE + body_size;
+       req->out.hdr       = req->out.buffer + NBT_HDR_SIZE;
+       req->out.body      = req->out.hdr + SMB2_HDR_BODY;
+       req->out.body_size = body_size;
+       req->out.ptr       = req->out.body;
+
+       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);
+       SIVAL(req->out.hdr, SMB2_HDR_STATUS,  0);
+       SSVAL(req->out.hdr, SMB2_HDR_OPCODE,  opcode);
+       SSVAL(req->out.hdr, SMB2_HDR_PAD2,    0);
+       SIVAL(req->out.hdr, SMB2_HDR_FLAGS,   0);
+       SIVAL(req->out.hdr, SMB2_HDR_UNKNOWN, 0);
+       SBVAL(req->out.hdr, SMB2_HDR_SEQNUM,  req->seqnum);
+       SIVAL(req->out.hdr, SMB2_HDR_PID,     0);
+       SIVAL(req->out.hdr, SMB2_HDR_TID,     0);
+       SIVAL(req->out.hdr, SMB2_HDR_UID,     0);
+       SIVAL(req->out.hdr, SMB2_HDR_UID2,    0);
+       memset(req->out.hdr+SMB2_HDR_SIG, 0, 16);
+
+       return req;
+}
+
+/* destroy a request structure and return final status */
+NTSTATUS smb2_request_destroy(struct smb2_request *req)
+{
+       NTSTATUS status;
+
+       /* this is the error code we give the application for when a
+          _send() call fails completely */
+       if (!req) return NT_STATUS_UNSUCCESSFUL;
+
+       if (req->transport) {
+               /* remove it from the list of pending requests (a null op if
+                  its not in the list) */
+               DLIST_REMOVE(req->transport->pending_recv, req);
+       }
+
+       if (req->state == SMBCLI_REQUEST_ERROR &&
+           NT_STATUS_IS_OK(req->status)) {
+               req->status = NT_STATUS_INTERNAL_ERROR;
+       }
+
+       status = req->status;
+       talloc_free(req);
+       return status;
+}
+
+/*
+  receive a response to a packet
+*/
+BOOL smb2_request_receive(struct smb2_request *req)
+{
+       /* req can be NULL when a send has failed. This eliminates lots of NULL
+          checks in each module */
+       if (!req) return False;
+
+       /* keep receiving packets until this one is replied to */
+       while (req->state <= SMB2_REQUEST_RECV) {
+               if (event_loop_once(req->transport->socket->event.ctx) != 0) {
+                       return False;
+               }
+       }
+
+       return req->state == SMB2_REQUEST_DONE;
+}
+
+/* Return true if the last packet was in error */
+BOOL smb2_request_is_error(struct smb2_request *req)
+{
+       return NT_STATUS_IS_ERR(req->status);
+}
+
+/*
+  check if a range in the reply body is out of bounds
+*/
+BOOL smb2_oob(struct smb2_request *req, const uint8_t *ptr, uint_t size)
+{
+       /* be careful with wraparound! */
+       if (ptr < req->in.body ||
+           ptr >= req->in.body + req->in.body_size ||
+           size > req->in.body_size ||
+           ptr + size > req->in.body + req->in.body_size) {
+               return True;
+       }
+       return False;
+}
+
+/*
+  pull a data blob from the body of a reply
+*/
+DATA_BLOB smb2_pull_blob(struct smb2_request *req, uint8_t *ptr, uint_t size)
+{
+       if (smb2_oob(req, ptr, size)) {
+               return data_blob(NULL, 0);
+       }
+       return data_blob_talloc(req, ptr, size);
+}
+
+/*
+  pull a guid from the reply body
+*/
+NTSTATUS smb2_pull_guid(struct smb2_request *req, uint8_t *ptr, struct GUID *guid)
+{
+       NTSTATUS status;
+       DATA_BLOB blob = smb2_pull_blob(req, ptr, 16);
+       if (blob.data == NULL) {
+               return NT_STATUS_BUFFER_TOO_SMALL;
+       }
+       status = ndr_pull_struct_blob(&blob, req, guid, 
+                                     (ndr_pull_flags_fn_t)ndr_pull_GUID);
+       data_blob_free(&blob);
+       return status;
+}
diff --git a/source4/libcli/smb2/smb2.h b/source4/libcli/smb2/smb2.h
new file mode 100644 (file)
index 0000000..2e01159
--- /dev/null
@@ -0,0 +1,154 @@
+/* 
+   Unix SMB/CIFS implementation.
+
+   SMB2 client library header
+
+   Copyright (C) Andrew Tridgell 2005
+   
+   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
+   (at your option) any later version.
+   
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   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.
+*/
+
+struct smb2_options {
+       uint32_t timeout;
+};
+
+/*
+  information returned from the negotiate response
+*/
+struct smb2_negotiate {
+       DATA_BLOB secblob;
+       
+};
+
+/* this is the context for the smb2 transport layer */
+struct smb2_transport {
+       /* socket level info */
+       struct smbcli_socket *socket;
+
+       struct smb2_options options;
+       struct smb2_negotiate negotiate;
+
+       /* next seqnum to allocate */
+       uint64_t seqnum;
+
+       /* a list of requests that are pending for receive on this
+          connection */
+       struct smb2_request *pending_recv;
+
+       /* context of the stream -> packet parser */
+       struct packet_context *packet;
+};
+
+
+struct smb2_request_buffer {
+       /* the raw SMB2 buffer, including the 4 byte length header */
+       uint8_t *buffer;
+       
+       /* the size of the raw buffer, including 4 byte header */
+       uint_t size;
+       
+       /* how much has been allocated - on reply the buffer is over-allocated to 
+          prevent too many realloc() calls 
+       */
+       uint_t allocated;
+       
+       /* the start of the SMB2 header - this is always buffer+4 */
+       uint8_t *hdr;
+       
+       /* the packet body */
+       uint8_t *body;
+       uint_t body_size;
+       
+       /* ptr is used as a moving pointer into the data area
+        * of the packet. The reason its here and not a local
+        * variable in each function is that when a realloc of
+        * a send packet is done we need to move this
+        * pointer */
+       uint8_t *ptr;
+};
+
+
+/*
+  a client request moves between the following 4 states.
+*/
+enum smb2_request_state {SMB2_REQUEST_INIT, /* we are creating the request */
+                       SMB2_REQUEST_RECV, /* we are waiting for a matching reply */
+                       SMB2_REQUEST_DONE, /* the request is finished */
+                       SMB2_REQUEST_ERROR}; /* a packet or transport level error has occurred */
+
+/* the context for a single SMB2 request */
+struct smb2_request {
+       /* allow a request to be part of a list of requests */
+       struct smb2_request *next, *prev;
+
+       /* each request is in one of 3 possible states */
+       enum smb2_request_state state;
+       
+       struct smb2_transport *transport;
+
+       uint64_t seqnum;
+
+       /* the NT status for this request. Set by packet receive code
+          or code detecting error. */
+       NTSTATUS status;
+       
+       struct smb2_request_buffer in;
+       struct smb2_request_buffer out;
+
+       /* information on what to do with a reply when it is received
+          asyncronously. If this is not setup when a reply is received then
+          the reply is discarded
+
+          The private pointer is private to the caller of the client
+          library (the application), not private to the library
+       */
+       struct {
+               void (*fn)(struct smb2_request *);
+               void *private;
+       } async;
+};
+
+
+#define SMB2_MIN_SIZE 0x40
+
+/* offsets into header elements */
+#define SMB2_HDR_LENGTH  0x04
+#define SMB2_HDR_PAD1    0x06
+#define SMB2_HDR_STATUS  0x08
+#define SMB2_HDR_OPCODE  0x0c
+#define SMB2_HDR_PAD2    0x0e
+#define SMB2_HDR_FLAGS   0x10
+#define SMB2_HDR_UNKNOWN 0x14
+#define SMB2_HDR_SEQNUM  0x18
+#define SMB2_HDR_PID     0x20
+#define SMB2_HDR_TID     0x24
+#define SMB2_HDR_UID     0x28
+#define SMB2_HDR_UID2    0x2c /* whats this? */
+#define SMB2_HDR_SIG     0x30 /* guess ... */
+#define SMB2_HDR_BODY    0x40
+
+/* SMB2 opcodes */
+#define SMB2_OP_NEGPROT   0x00
+#define SMB2_OP_SESSSETUP 0x01
+#define SMB2_OP_TCON      0x03
+#define SMB2_OP_TDIS      0x04
+#define SMB2_OP_CREATE    0x05
+#define SMB2_OP_CLOSE     0x06
+#define SMB2_OP_READ      0x08
+#define SMB2_OP_WRITE     0x09
+#define SMB2_OP_FIND      0x0e
+
+#define SMB2_MAGIC 0x424D53FE /* 0xFE 'S' 'M' 'B' */
+
diff --git a/source4/libcli/smb2/transport.c b/source4/libcli/smb2/transport.c
new file mode 100644 (file)
index 0000000..a178b35
--- /dev/null
@@ -0,0 +1,274 @@
+/* 
+   Unix SMB/CIFS implementation.
+
+   SMB2 client transport context management functions
+
+   Copyright (C) Andrew Tridgell 2005
+   
+   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
+   (at your option) any later version.
+   
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   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.
+*/
+
+#include "includes.h"
+#include "libcli/raw/libcliraw.h"
+#include "libcli/smb2/smb2.h"
+#include "lib/socket/socket.h"
+#include "lib/events/events.h"
+#include "lib/stream/packet.h"
+#include "include/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)
+{
+       struct smb2_transport *transport = talloc_get_type(private,
+                                                          struct smb2_transport);
+       if (flags & EVENT_FD_READ) {
+               packet_recv(transport->packet);
+               return;
+       }
+       if (flags & EVENT_FD_WRITE) {
+               packet_queue_run(transport->packet);
+       }
+}
+
+/*
+  destroy a transport
+ */
+static int transport_destructor(void *ptr)
+{
+       struct smb2_transport *transport = ptr;
+       smb2_transport_dead(transport);
+       return 0;
+}
+
+
+/*
+  handle receive errors
+*/
+static void smb2_transport_error(void *private, NTSTATUS status)
+{
+       struct smb2_transport *transport = talloc_get_type(private, 
+                                                          struct smb2_transport);
+       smb2_transport_dead(transport);
+}
+
+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)
+{
+       struct smb2_transport *transport;
+
+       transport = talloc_zero(parent_ctx, struct smb2_transport);
+       if (!transport) return NULL;
+
+       transport->socket = talloc_steal(transport, sock);
+
+       /* setup the stream -> packet parser */
+       transport->packet = packet_init(transport);
+       if (transport->packet == NULL) {
+               talloc_free(transport);
+               return NULL;
+       }
+       packet_set_private(transport->packet, transport);
+       packet_set_socket(transport->packet, transport->socket->sock);
+       packet_set_callback(transport->packet, smb2_transport_finish_recv);
+       packet_set_full_request(transport->packet, packet_full_request_nbt);
+       packet_set_error_handler(transport->packet, smb2_transport_error);
+       packet_set_event_context(transport->packet, transport->socket->event.ctx);
+       packet_set_nofree(transport->packet);
+
+       /* take over event handling from the socket layer - it only
+          handles events up until we are connected */
+       talloc_free(transport->socket->event.fde);
+       transport->socket->event.fde = event_add_fd(transport->socket->event.ctx,
+                                                   transport->socket,
+                                                   socket_get_fd(transport->socket->sock),
+                                                   EVENT_FD_READ,
+                                                   smb2_transport_event_handler,
+                                                   transport);
+
+       packet_set_serialise(transport->packet, transport->socket->event.fde);
+
+       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)
+{
+       smbcli_sock_dead(transport->socket);
+
+       /* 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;
+               DLIST_REMOVE(transport->pending_recv, req);
+               if (req->async.fn) {
+                       req->async.fn(req);
+               }
+       }
+}
+
+/*
+  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)
+{
+       struct smb2_transport *transport = talloc_get_type(private, 
+                                                            struct smb2_transport);
+       uint8_t *buffer, *hdr;
+       int len;
+       struct smb2_request *req;
+       uint64_t seqnum;
+
+       buffer = blob.data;
+       len = blob.length;
+
+       hdr = buffer+NBT_HDR_SIZE;
+
+       if (len < SMB2_MIN_SIZE) {
+               DEBUG(1,("Discarding smb2 reply of size %d\n", len));
+               goto error;
+       }
+
+       seqnum = BVAL(hdr, SMB2_HDR_SEQNUM);
+
+       /* match the incoming request against the list of pending requests */
+       for (req=transport->pending_recv; req; req=req->next) {
+               if (req->seqnum == seqnum) break;
+       }
+
+       if (!req) {
+               DEBUG(1,("Discarding unmatched reply with seqnum 0x%llx op %d\n", 
+                        seqnum, SVAL(hdr, SMB2_HDR_OPCODE)));
+               goto error;
+       }
+
+       /* fill in the 'in' portion of the matching request */
+       req->in.buffer = buffer;
+       talloc_steal(req, buffer);
+       req->in.size = len;
+       req->in.allocated = req->in.size;
+
+       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));
+
+       /* if this request has an async handler then call that to
+          notify that the reply has been received. This might destroy
+          the request so it must happen last */
+       DLIST_REMOVE(transport->pending_recv, req);
+       req->state = SMB2_REQUEST_DONE;
+       if (req->async.fn) {
+               req->async.fn(req);
+       }
+       return NT_STATUS_OK;
+
+error:
+       if (req) {
+               DLIST_REMOVE(transport->pending_recv, req);
+               req->state = SMB2_REQUEST_ERROR;
+       }
+       dump_data(0, blob.data, blob.length);
+       data_blob_free(&blob);
+       return NT_STATUS_UNSUCCESSFUL;
+}
+
+/*
+  handle timeouts of individual smb requests
+*/
+static void smb2_timeout_handler(struct event_context *ev, struct timed_event *te, 
+                                  struct timeval t, void *private)
+{
+       struct smb2_request *req = talloc_get_type(private, struct smb2_request);
+
+       if (req->state == SMB2_REQUEST_RECV) {
+               DLIST_REMOVE(req->transport->pending_recv, req);
+       }
+       req->status = NT_STATUS_IO_TIMEOUT;
+       req->state = SMB2_REQUEST_ERROR;
+       if (req->async.fn) {
+               req->async.fn(req);
+       }
+}
+
+
+/*
+  destroy a request
+*/
+static int smb2_request_destructor(void *ptr)
+{
+       struct smb2_request *req = talloc_get_type(ptr, struct smb2_request);
+       if (req->state == SMB2_REQUEST_RECV) {
+               DLIST_REMOVE(req->transport->pending_recv, req);
+       }
+       return 0;
+}
+
+
+/*
+  put a request into the send queue
+*/
+void smb2_transport_send(struct smb2_request *req)
+{
+       DATA_BLOB blob;
+       NTSTATUS status;
+
+       _smb_setlen(req->out.buffer, req->out.size - NBT_HDR_SIZE);
+
+       /* check if the transport is dead */
+       if (req->transport->socket->sock == NULL) {
+               req->state = SMB2_REQUEST_ERROR;
+               req->status = NT_STATUS_NET_WRITE_FAULT;
+               return;
+       }
+
+       blob = data_blob_const(req->out.buffer, req->out.size);
+       status = packet_send(req->transport->packet, blob);
+       if (!NT_STATUS_IS_OK(status)) {
+               req->state = SMB2_REQUEST_ERROR;
+               req->status = status;
+               return;
+       }
+
+       req->state = SMB2_REQUEST_RECV;
+       DLIST_ADD(req->transport->pending_recv, req);
+
+       /* add a timeout */
+       if (req->transport->options.timeout) {
+               event_add_timed(req->transport->socket->event.ctx, req, 
+                               timeval_current_ofs(req->transport->options.timeout, 0), 
+                               smb2_timeout_handler, req);
+       }
+
+       talloc_set_destructor(req, smb2_request_destructor);
+}