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);
/*
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;
}
/*
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;
if (!transport) return NULL;
transport->socket = talloc_steal(transport, sock);
+ transport->options = *options;
/* setup the stream -> packet parser */
transport->packet = packet_init(transport);
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);
}
}
+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;
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) {
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;
}
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
/*
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);
/*
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);
}
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 */
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)) {
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);
+}