summaryrefslogtreecommitdiff
path: root/source4/libcli/smb2
diff options
context:
space:
mode:
Diffstat (limited to 'source4/libcli/smb2')
-rw-r--r--source4/libcli/smb2/cancel.c6
-rw-r--r--source4/libcli/smb2/config.mk2
-rw-r--r--source4/libcli/smb2/connect.c54
-rw-r--r--source4/libcli/smb2/create.c5
-rw-r--r--source4/libcli/smb2/notify.c6
-rw-r--r--source4/libcli/smb2/session.c17
-rw-r--r--source4/libcli/smb2/signing.c165
-rw-r--r--source4/libcli/smb2/smb2.h22
-rw-r--r--source4/libcli/smb2/transport.c49
9 files changed, 286 insertions, 40 deletions
diff --git a/source4/libcli/smb2/cancel.c b/source4/libcli/smb2/cancel.c
index 80127feea5..65f02187c1 100644
--- a/source4/libcli/smb2/cancel.c
+++ b/source4/libcli/smb2/cancel.c
@@ -61,10 +61,10 @@ NTSTATUS smb2_cancel(struct smb2_request *r)
SSVAL(c->out.body, 0x02, 0);
- old_timeout = c->transport->options.timeout;
- c->transport->options.timeout = 0;
+ old_timeout = c->transport->options.request_timeout;
+ c->transport->options.request_timeout = 0;
smb2_transport_send(c);
- c->transport->options.timeout = old_timeout;
+ c->transport->options.request_timeout = old_timeout;
if (c->state == SMB2_REQUEST_ERROR) {
status = c->status;
diff --git a/source4/libcli/smb2/config.mk b/source4/libcli/smb2/config.mk
index 00b6305def..322bca1416 100644
--- a/source4/libcli/smb2/config.mk
+++ b/source4/libcli/smb2/config.mk
@@ -5,6 +5,6 @@ LIBCLI_SMB2_OBJ_FILES = $(addprefix $(libclisrcdir)/smb2/, \
transport.o request.o negprot.o session.o tcon.o \
create.o close.o connect.o getinfo.o write.o read.o \
setinfo.o find.o ioctl.o logoff.o tdis.o flush.o \
- lock.o notify.o cancel.o keepalive.o break.o util.o)
+ lock.o notify.o cancel.o keepalive.o break.o util.o signing.o)
$(eval $(call proto_header_template,$(libclisrcdir)/smb2/smb2_proto.h,$(LIBCLI_SMB2_OBJ_FILES:.o=.c)))
diff --git a/source4/libcli/smb2/connect.c b/source4/libcli/smb2/connect.c
index eabfa410ad..cdb5e3b5d4 100644
--- a/source4/libcli/smb2/connect.c
+++ b/source4/libcli/smb2/connect.c
@@ -33,6 +33,7 @@ struct smb2_connect_state {
struct resolve_context *resolve_ctx;
const char *host;
const char *share;
+ struct smbcli_options options;
struct smb2_negprot negprot;
struct smb2_tree_connect tcon;
struct smb2_session *session;
@@ -103,6 +104,34 @@ static void continue_negprot(struct smb2_request *req)
transport->negotiate.system_time = state->negprot.out.system_time;
transport->negotiate.server_start_time = state->negprot.out.server_start_time;
+ transport->negotiate.security_mode = state->negprot.out.security_mode;
+
+ switch (transport->options.signing) {
+ case SMB_SIGNING_OFF:
+ if (transport->negotiate.security_mode & SMB2_NEGOTIATE_SIGNING_REQUIRED) {
+ composite_error(c, NT_STATUS_ACCESS_DENIED);
+ return;
+ }
+ transport->signing.doing_signing = false;
+ break;
+ case SMB_SIGNING_SUPPORTED:
+ case SMB_SIGNING_AUTO:
+ if (transport->negotiate.security_mode & SMB2_NEGOTIATE_SIGNING_REQUIRED) {
+ transport->signing.doing_signing = true;
+ } else {
+ transport->signing.doing_signing = false;
+ }
+ break;
+ case SMB_SIGNING_REQUIRED:
+ if (transport->negotiate.security_mode & SMB2_NEGOTIATE_SIGNING_ENABLED) {
+ transport->signing.doing_signing = true;
+ } else {
+ composite_error(c, NT_STATUS_ACCESS_DENIED);
+ return;
+ }
+ break;
+ }
+
state->session = smb2_session_init(transport, global_loadparm, state, true);
if (composite_nomem(state->session, c)) return;
@@ -129,12 +158,24 @@ static void continue_socket(struct composite_context *creq)
c->status = smbcli_sock_connect_recv(creq, state, &sock);
if (!composite_is_ok(c)) return;
- transport = smb2_transport_init(sock, state);
+ transport = smb2_transport_init(sock, state, &state->options);
if (composite_nomem(transport, c)) return;
ZERO_STRUCT(state->negprot);
state->negprot.in.dialect_count = 2;
- state->negprot.in.security_mode = 0;
+ switch (transport->options.signing) {
+ case SMB_SIGNING_OFF:
+ state->negprot.in.security_mode = 0;
+ break;
+ case SMB_SIGNING_SUPPORTED:
+ case SMB_SIGNING_AUTO:
+ state->negprot.in.security_mode = SMB2_NEGOTIATE_SIGNING_ENABLED;
+ break;
+ case SMB_SIGNING_REQUIRED:
+ state->negprot.in.security_mode =
+ SMB2_NEGOTIATE_SIGNING_ENABLED | SMB2_NEGOTIATE_SIGNING_REQUIRED;
+ break;
+ }
state->negprot.in.capabilities = 0;
unix_to_nt_time(&state->negprot.in.start_time, time(NULL));
dialects[0] = 0;
@@ -178,7 +219,8 @@ struct composite_context *smb2_connect_send(TALLOC_CTX *mem_ctx,
const char *share,
struct resolve_context *resolve_ctx,
struct cli_credentials *credentials,
- struct event_context *ev)
+ struct event_context *ev,
+ struct smbcli_options *options)
{
struct composite_context *c;
struct smb2_connect_state *state;
@@ -193,6 +235,7 @@ struct composite_context *smb2_connect_send(TALLOC_CTX *mem_ctx,
c->private_data = state;
state->credentials = credentials;
+ state->options = *options;
state->host = talloc_strdup(c, host);
if (composite_nomem(state->host, c)) return c;
state->share = talloc_strdup(c, share);
@@ -232,10 +275,11 @@ NTSTATUS smb2_connect(TALLOC_CTX *mem_ctx,
struct resolve_context *resolve_ctx,
struct cli_credentials *credentials,
struct smb2_tree **tree,
- struct event_context *ev)
+ struct event_context *ev,
+ struct smbcli_options *options)
{
struct composite_context *c = smb2_connect_send(mem_ctx, host, share,
resolve_ctx,
- credentials, ev);
+ credentials, ev, options);
return smb2_connect_recv(c, mem_ctx, tree);
}
diff --git a/source4/libcli/smb2/create.c b/source4/libcli/smb2/create.c
index bff0a1587d..8a40e56a00 100644
--- a/source4/libcli/smb2/create.c
+++ b/source4/libcli/smb2/create.c
@@ -387,12 +387,13 @@ NTSTATUS smb2_create_recv(struct smb2_request *req, TALLOC_CTX *mem_ctx, struct
/* pull out the parsed blobs */
for (i=0;i<io->out.blobs.num_blobs;i++) {
if (strcmp(io->out.blobs.blobs[i].tag, SMB2_CREATE_TAG_MXAC) == 0) {
- /* why 8 bytes not 4?? */
+ /* TODO: this also contains a status field in
+ first 4 bytes */
if (io->out.blobs.blobs[i].data.length != 8) {
smb2_request_destroy(req);
return NT_STATUS_INVALID_NETWORK_RESPONSE;
}
- io->out.maximal_access = IVAL(io->out.blobs.blobs[i].data.data, 0);
+ io->out.maximal_access = IVAL(io->out.blobs.blobs[i].data.data, 4);
}
if (strcmp(io->out.blobs.blobs[i].tag, SMB2_CREATE_TAG_QFID) == 0) {
if (io->out.blobs.blobs[i].data.length != 32) {
diff --git a/source4/libcli/smb2/notify.c b/source4/libcli/smb2/notify.c
index 096d790a31..ef7341cae8 100644
--- a/source4/libcli/smb2/notify.c
+++ b/source4/libcli/smb2/notify.c
@@ -44,10 +44,10 @@ struct smb2_request *smb2_notify_send(struct smb2_tree *tree, struct smb2_notify
SIVAL(req->out.body, 0x18, io->in.completion_filter);
SIVAL(req->out.body, 0x1C, io->in.unknown);
- old_timeout = req->transport->options.timeout;
- req->transport->options.timeout = 0;
+ old_timeout = req->transport->options.request_timeout;
+ req->transport->options.request_timeout = 0;
smb2_transport_send(req);
- req->transport->options.timeout = old_timeout;
+ req->transport->options.request_timeout = old_timeout;
return req;
}
diff --git a/source4/libcli/smb2/session.c b/source4/libcli/smb2/session.c
index 29af6652f2..54915d8535 100644
--- a/source4/libcli/smb2/session.c
+++ b/source4/libcli/smb2/session.c
@@ -164,8 +164,8 @@ static void session_request_handler(struct smb2_request *req)
session_key_err = gensec_session_key(session->gensec, &session_key);
if (NT_STATUS_IS_OK(session_key_err)) {
- session->session_key = session_key;
- }
+ session->transport->signing.session_key = session_key;
+ }
}
session->uid = state->io.out.uid;
@@ -187,6 +187,14 @@ static void session_request_handler(struct smb2_request *req)
return;
}
+ if (session->transport->signing.doing_signing) {
+ c->status = smb2_start_signing(session->transport);
+ if (!NT_STATUS_IS_OK(c->status)) {
+ composite_error(c, c->status);
+ return;
+ }
+ }
+
composite_done(c);
}
@@ -208,7 +216,10 @@ struct composite_context *smb2_session_setup_spnego_send(struct smb2_session *se
ZERO_STRUCT(state->io);
state->io.in.vc_number = 0;
- state->io.in.security_mode = 0;
+ if (session->transport->signing.doing_signing) {
+ state->io.in.security_mode =
+ SMB2_NEGOTIATE_SIGNING_ENABLED | SMB2_NEGOTIATE_SIGNING_REQUIRED;
+ }
state->io.in.capabilities = 0;
state->io.in.channel = 0;
state->io.in.previous_sessionid = 0;
diff --git a/source4/libcli/smb2/signing.c b/source4/libcli/smb2/signing.c
new file mode 100644
index 0000000000..01f7576134
--- /dev/null
+++ b/source4/libcli/smb2/signing.c
@@ -0,0 +1,165 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ SMB2 Signing Code
+
+ Copyright (C) Andrew Tridgell <tridge@samba.org> 2008
+
+ 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 3 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, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "libcli/raw/libcliraw.h"
+#include "libcli/smb2/smb2.h"
+#include "libcli/smb2/smb2_calls.h"
+#include "heimdal/lib/hcrypto/sha.h"
+
+/*
+ NOTE: this code does not yet interoperate with the windows SMB2
+ implementation. We are waiting on feedback on the docs to find out
+ why
+ */
+
+
+/*
+ setup signing on a transport
+ */
+NTSTATUS smb2_start_signing(struct smb2_transport *transport)
+{
+ if (transport->signing.session_key.length != 16) {
+ DEBUG(2,("Wrong session key length %u for SMB2 signing\n",
+ (unsigned)transport->signing.session_key.length));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ transport->signing.signing_started = true;
+ return NT_STATUS_OK;
+}
+
+/*
+ sign an outgoing message
+ */
+NTSTATUS smb2_sign_message(struct smb2_request *req)
+{
+ struct smb2_request_buffer *buf = &req->out;
+ uint64_t session_id;
+ SHA256_CTX m;
+ uint8_t res[32];
+
+ if (!req->transport->signing.doing_signing ||
+ !req->transport->signing.signing_started) {
+ return NT_STATUS_OK;
+ }
+
+ if (buf->size < NBT_HDR_SIZE + SMB2_HDR_SIGNATURE + 16) {
+ /* can't sign non-SMB2 messages */
+ return NT_STATUS_OK;
+ }
+
+ session_id = BVAL(buf->hdr, SMB2_HDR_SESSION_ID);
+ if (session_id == 0) {
+ /* we don't sign messages with a zero session_id. See
+ MS-SMB2 3.2.4.1.1 */
+ return NT_STATUS_OK;
+ }
+
+ if (req->transport->signing.session_key.length != 16) {
+ DEBUG(2,("Wrong session key length %u for SMB2 signing\n",
+ (unsigned)req->transport->signing.session_key.length));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ memset(buf->hdr + SMB2_HDR_SIGNATURE, 0, 16);
+
+ SIVAL(buf->hdr, SMB2_HDR_FLAGS, IVAL(buf->hdr, SMB2_HDR_FLAGS) | SMB2_HDR_FLAG_SIGNED);
+
+ ZERO_STRUCT(m);
+ SHA256_Init(&m);
+ SHA256_Update(&m, req->transport->signing.session_key.data,
+ req->transport->signing.session_key.length);
+ SHA256_Update(&m, buf->buffer+NBT_HDR_SIZE, buf->size-NBT_HDR_SIZE);
+ SHA256_Final(res, &m);
+
+ DEBUG(5,("signed SMB2 message of size %u\n", (unsigned)buf->size - NBT_HDR_SIZE));
+
+ memcpy(buf->hdr + SMB2_HDR_SIGNATURE, res, 16);
+
+ if (DEBUGLVL(5)) {
+ /* check our own signature */
+ smb2_check_signature(req->transport, buf->buffer, buf->size);
+ }
+
+ return NT_STATUS_OK;
+}
+
+/*
+ check an incoming signature
+ */
+NTSTATUS smb2_check_signature(struct smb2_transport *transport,
+ uint8_t *buffer, uint_t length)
+{
+ uint64_t session_id;
+ SHA256_CTX m;
+ uint8_t res[SHA256_DIGEST_LENGTH];
+ uint8_t sig[16];
+
+ if (!transport->signing.signing_started ||
+ !transport->signing.doing_signing) {
+ return NT_STATUS_OK;
+ }
+
+ if (length < NBT_HDR_SIZE + SMB2_HDR_SIGNATURE + 16) {
+ /* can't check non-SMB2 messages */
+ return NT_STATUS_OK;
+ }
+
+ session_id = BVAL(buffer+NBT_HDR_SIZE, SMB2_HDR_SESSION_ID);
+ if (session_id == 0) {
+ /* don't sign messages with a zero session_id. See
+ MS-SMB2 3.2.4.1.1 */
+ return NT_STATUS_OK;
+ }
+
+ if (transport->signing.session_key.length == 0) {
+ /* we don't have the session key yet */
+ return NT_STATUS_OK;
+ }
+
+ if (transport->signing.session_key.length != 16) {
+ DEBUG(2,("Wrong session key length %u for SMB2 signing\n",
+ (unsigned)transport->signing.session_key.length));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ memcpy(sig, buffer+NBT_HDR_SIZE+SMB2_HDR_SIGNATURE, 16);
+
+ memset(buffer + NBT_HDR_SIZE + SMB2_HDR_SIGNATURE, 0, 16);
+
+ ZERO_STRUCT(m);
+ SHA256_Init(&m);
+ SHA256_Update(&m, transport->signing.session_key.data, 16);
+ SHA256_Update(&m, buffer+NBT_HDR_SIZE, length-NBT_HDR_SIZE);
+ SHA256_Final(res, &m);
+
+ memcpy(buffer+NBT_HDR_SIZE+SMB2_HDR_SIGNATURE, sig, 16);
+
+ if (memcmp(res, sig, 16) != 0) {
+ DEBUG(0,("Bad SMB2 signature for message of size %u\n", length));
+ dump_data(0, sig, 16);
+ dump_data(0, res, 16);
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ return NT_STATUS_OK;
+}
diff --git a/source4/libcli/smb2/smb2.h b/source4/libcli/smb2/smb2.h
index b55da05e21..0903509528 100644
--- a/source4/libcli/smb2/smb2.h
+++ b/source4/libcli/smb2/smb2.h
@@ -23,20 +23,24 @@
#define __LIBCLI_SMB2_SMB2_H__
#include "libcli/raw/request.h"
+#include "libcli/raw/libcliraw.h"
struct smb2_handle;
-struct smb2_options {
- uint32_t timeout;
+struct smb2_signing_context {
+ bool doing_signing;
+ bool signing_started;
+ DATA_BLOB session_key;
};
/*
- information returned from the negotiate response
+ information returned from the negotiate process
*/
struct smb2_negotiate {
DATA_BLOB secblob;
NTTIME system_time;
NTTIME server_start_time;
+ uint16_t security_mode;
};
/* this is the context for the smb2 transport layer */
@@ -44,7 +48,6 @@ struct smb2_transport {
/* socket level info */
struct smbcli_socket *socket;
- struct smb2_options options;
struct smb2_negotiate negotiate;
/* next seqnum to allocate */
@@ -74,6 +77,9 @@ struct smb2_transport {
/* private data passed to the oplock handler */
void *private_data;
} oplock;
+
+ struct smbcli_options options;
+ struct smb2_signing_context signing;
};
@@ -92,7 +98,6 @@ struct smb2_session {
struct smb2_transport *transport;
struct gensec_security *gensec;
uint64_t uid;
- DATA_BLOB session_key;
};
@@ -193,6 +198,13 @@ struct smb2_request {
#define SMB2_HDR_SIGNATURE 0x30 /* 16 bytes */
#define SMB2_HDR_BODY 0x40
+/* header flags */
+#define SMB2_HDR_FLAG_REDIRECT 0x01
+#define SMB2_HDR_FLAG_ASYNC 0x02
+#define SMB2_HDR_FLAG_CHAINED 0x04
+#define SMB2_HDR_FLAG_SIGNED 0x08
+#define SMB2_HDR_FLAG_DFS 0x10000000
+
/* SMB2 opcodes */
#define SMB2_OP_NEGPROT 0x00
#define SMB2_OP_SESSSETUP 0x01
diff --git a/source4/libcli/smb2/transport.c b/source4/libcli/smb2/transport.c
index 8eb60a06f1..561b6e528e 100644
--- a/source4/libcli/smb2/transport.c
+++ b/source4/libcli/smb2/transport.c
@@ -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);
@@ -112,8 +114,6 @@ struct smb2_transport *smb2_transport_init(struct smbcli_socket *sock,
talloc_set_destructor(transport, transport_destructor);
- transport->options.timeout = 30;
-
return transport;
}
@@ -140,27 +140,24 @@ void smb2_transport_dead(struct smb2_transport *transport, NTSTATUS status)
}
}
-static bool smb2_handle_oplock_break(struct smb2_transport *transport,
- const DATA_BLOB *blob)
+static NTSTATUS smb2_handle_oplock_break(struct smb2_transport *transport,
+ const DATA_BLOB *blob)
{
uint8_t *hdr;
uint16_t opcode;
- uint64_t seqnum;
hdr = blob->data+NBT_HDR_SIZE;
if (blob->length < (SMB2_MIN_SIZE+0x18)) {
DEBUG(1,("Discarding smb2 oplock reply of size %u\n",
- blob->length));
- return false;
+ (unsigned)blob->length));
+ return NT_STATUS_INVALID_NETWORK_RESPONSE;
}
opcode = SVAL(hdr, SMB2_HDR_OPCODE);
- seqnum = BVAL(hdr, SMB2_HDR_MESSAGE_ID);
- if ((opcode != SMB2_OP_BREAK) ||
- (seqnum != UINT64_MAX)) {
- return false;
+ if (opcode != SMB2_OP_BREAK) {
+ return NT_STATUS_INVALID_NETWORK_RESPONSE;
}
if (transport->oplock.handler) {
@@ -173,9 +170,11 @@ static bool smb2_handle_oplock_break(struct smb2_transport *transport,
transport->oplock.handler(transport, &h, level,
transport->oplock.private_data);
+ } else {
+ DEBUG(5,("Got SMB2 oplock break with no handler\n"));
}
- return true;
+ return NT_STATUS_OK;
}
/*
@@ -194,6 +193,7 @@ static NTSTATUS smb2_transport_finish_recv(void *private, DATA_BLOB blob)
uint16_t buffer_code;
uint32_t dynamic_size;
uint32_t i;
+ NTSTATUS status;
buffer = blob.data;
len = blob.length;
@@ -205,14 +205,20 @@ static NTSTATUS smb2_transport_finish_recv(void *private, DATA_BLOB blob)
goto error;
}
- if (smb2_handle_oplock_break(transport, &blob)) {
+ status = smb2_check_signature(transport, buffer, len);
+ if (!NT_STATUS_IS_OK(status)) {
talloc_free(buffer);
- return NT_STATUS_OK;
+ return status;
}
-
+
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->seqnum == seqnum) break;
@@ -340,6 +346,13 @@ void smb2_transport_send(struct smb2_request *req)
return;
}
+ status = smb2_sign_message(req);
+ 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)) {
@@ -352,9 +365,9 @@ 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);
}