diff options
Diffstat (limited to 'source4/libcli/smb2')
-rw-r--r-- | source4/libcli/smb2/cancel.c | 6 | ||||
-rw-r--r-- | source4/libcli/smb2/config.mk | 2 | ||||
-rw-r--r-- | source4/libcli/smb2/connect.c | 54 | ||||
-rw-r--r-- | source4/libcli/smb2/notify.c | 6 | ||||
-rw-r--r-- | source4/libcli/smb2/session.c | 17 | ||||
-rw-r--r-- | source4/libcli/smb2/signing.c | 165 | ||||
-rw-r--r-- | source4/libcli/smb2/smb2.h | 22 | ||||
-rw-r--r-- | source4/libcli/smb2/transport.c | 49 |
8 files changed, 283 insertions, 38 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/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); } |