From ac293f85342a77777c2164a53c8ec43ddc1c1aed Mon Sep 17 00:00:00 2001 From: Andrew Tridgell Date: Fri, 11 Nov 2005 04:45:38 +0000 Subject: r11662: the beginnings of a SMB2 client library. Very hackish, meant for experimentation (This used to be commit 68422dc73f6ea51bf906f3db223ae8abf077aba1) --- source4/libcli/smb2/config.mk | 6 + source4/libcli/smb2/negprot.c | 82 ++++++++++++ source4/libcli/smb2/request.c | 171 +++++++++++++++++++++++++ source4/libcli/smb2/smb2.h | 154 ++++++++++++++++++++++ source4/libcli/smb2/transport.c | 274 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 687 insertions(+) create mode 100644 source4/libcli/smb2/config.mk create mode 100644 source4/libcli/smb2/negprot.c create mode 100644 source4/libcli/smb2/request.c create mode 100644 source4/libcli/smb2/smb2.h create mode 100644 source4/libcli/smb2/transport.c (limited to 'source4/libcli/smb2') diff --git a/source4/libcli/smb2/config.mk b/source4/libcli/smb2/config.mk new file mode 100644 index 0000000000..9840876c2f --- /dev/null +++ b/source4/libcli/smb2/config.mk @@ -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 index 0000000000..6b35373807 --- /dev/null +++ b/source4/libcli/smb2/negprot.c @@ -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 index 0000000000..1b2dc5e64c --- /dev/null +++ b/source4/libcli/smb2/request.c @@ -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 index 0000000000..2e01159355 --- /dev/null +++ b/source4/libcli/smb2/smb2.h @@ -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 index 0000000000..a178b35f93 --- /dev/null +++ b/source4/libcli/smb2/transport.c @@ -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); +} -- cgit