#include "../lib/util/tevent_unix.h"
#include "lib/util/util_net.h"
#include "lib/util/dlinklist.h"
+#include "lib/util/iov_buf.h"
#include "../libcli/smb/smb_common.h"
#include "../libcli/smb/smb_seal.h"
#include "../libcli/smb/smb_signing.h"
#include "../libcli/smb/read_smb.h"
#include "smbXcli_base.h"
#include "librpc/ndr/libndr.h"
+#include "libcli/smb/smb2_negotiate_context.h"
+#include "lib/crypto/sha512.h"
+#include "lib/crypto/aes.h"
+#include "lib/crypto/aes_ccm_128.h"
+#include "lib/crypto/aes_gcm_128.h"
struct smbXcli_conn;
struct smbXcli_req;
struct smbXcli_tcon;
struct smbXcli_conn {
- int read_fd;
- int write_fd;
+ int sock_fd;
struct sockaddr_storage local_ss;
struct sockaddr_storage remote_ss;
const char *remote_name;
struct tevent_queue *outgoing;
struct tevent_req **pending;
struct tevent_req *read_smb_req;
+ struct tevent_req *suicide_req;
+ enum protocol_types min_protocol;
+ enum protocol_types max_protocol;
enum protocol_types protocol;
bool allow_signing;
bool desire_signing;
NTTIME system_time;
NTTIME start_time;
DATA_BLOB gss_blob;
+ uint16_t cipher;
} server;
uint64_t mid;
uint16_t cur_credits;
uint16_t max_credits;
+
+ uint32_t cc_chunk_len;
+ uint32_t cc_max_chunks;
+
+ uint8_t io_priority;
+
+ bool force_channel_sequence;
+
+ uint8_t preauth_sha512[64];
} smb2;
struct smbXcli_session *sessions;
bool should_encrypt;
DATA_BLOB encryption_key;
DATA_BLOB decryption_key;
+ uint64_t nonce_high_random;
+ uint64_t nonce_high_max;
uint64_t nonce_high;
uint64_t nonce_low;
uint16_t channel_sequence;
+ bool replay_active;
};
struct smbXcli_session {
struct {
uint16_t session_id;
+ uint16_t action;
DATA_BLOB application_key;
bool protected_key;
} smb1;
struct {
DATA_BLOB signing_key;
+ uint8_t preauth_sha512[64];
} smb2_channel;
/*
};
struct smbXcli_tcon {
+ bool is_smb1;
+ uint32_t fs_attributes;
+
struct {
uint16_t tcon_id;
uint16_t optional_support;
uint32_t flags;
uint32_t capabilities;
uint32_t maximal_access;
+ bool should_sign;
bool should_encrypt;
} smb2;
};
uint8_t *inbuf;
+ struct tevent_req *write_req;
+
struct {
/* Space for the header including the wct */
uint8_t hdr[HDR_VWV];
bool signing_skipped;
bool notify_async;
bool got_async;
+ uint16_t cancel_flags;
+ uint64_t cancel_mid;
+ uint64_t cancel_aid;
} smb2;
};
return NULL;
}
- conn->read_fd = fd;
- conn->write_fd = dup(fd);
- if (conn->write_fd == -1) {
- goto error;
- }
+ conn->sock_fd = fd;
conn->remote_name = talloc_strdup(conn, remote_name);
if (conn->remote_name == NULL) {
goto error;
}
-
ss = (void *)&conn->local_ss;
sa = (struct sockaddr *)ss;
sa_length = sizeof(conn->local_ss);
}
conn->pending = NULL;
+ conn->min_protocol = PROTOCOL_NONE;
+ conn->max_protocol = PROTOCOL_NONE;
conn->protocol = PROTOCOL_NONE;
switch (signing_state) {
conn->desire_signing = false;
conn->mandatory_signing = false;
break;
+ case SMB_SIGNING_DESIRED:
+ /* if the server desires it */
+ conn->allow_signing = true;
+ conn->desire_signing = true;
+ conn->mandatory_signing = false;
+ break;
+ case SMB_SIGNING_IPC_DEFAULT:
case SMB_SIGNING_REQUIRED:
/* always */
conn->allow_signing = true;
conn->smb2.cur_credits = 1;
conn->smb2.max_credits = 0;
+ conn->smb2.io_priority = 1;
+
+ /*
+ * Samba and Windows servers accept a maximum of 16 MiB with a maximum
+ * chunk length of 1 MiB.
+ */
+ conn->smb2.cc_chunk_len = 1024 * 1024;
+ conn->smb2.cc_max_chunks = 16;
talloc_set_destructor(conn, smbXcli_conn_destructor);
return conn;
error:
- if (conn->write_fd != -1) {
- close(conn->write_fd);
- }
TALLOC_FREE(conn);
return NULL;
}
return false;
}
- if (conn->read_fd == -1) {
+ if (conn->sock_fd == -1) {
return false;
}
return false;
}
+bool smbXcli_conn_signing_mandatory(struct smbXcli_conn *conn)
+{
+ return conn->mandatory_signing;
+}
+
+/*
+ * [MS-SMB] 2.2.2.3.5 - SMB1 support for passing through
+ * query/set commands to the file system
+ */
+bool smbXcli_conn_support_passthrough(struct smbXcli_conn *conn)
+{
+ if (conn->protocol >= PROTOCOL_SMB2_02) {
+ return true;
+ }
+
+ if (conn->smb1.capabilities & CAP_W2K_SMBS) {
+ return true;
+ }
+
+ return false;
+}
+
void smbXcli_conn_set_sockopt(struct smbXcli_conn *conn, const char *options)
{
- set_socket_options(conn->read_fd, options);
+ set_socket_options(conn->sock_fd, options);
}
const struct sockaddr_storage *smbXcli_conn_local_sockaddr(struct smbXcli_conn *conn)
return &conn->smb1.server.guid;
}
+bool smbXcli_conn_get_force_channel_sequence(struct smbXcli_conn *conn)
+{
+ return conn->smb2.force_channel_sequence;
+}
+
+void smbXcli_conn_set_force_channel_sequence(struct smbXcli_conn *conn,
+ bool v)
+{
+ conn->smb2.force_channel_sequence = v;
+}
+
struct smbXcli_conn_samba_suicide_state {
struct smbXcli_conn *conn;
struct iovec iov;
uint8_t buf[9];
+ struct tevent_req *write_req;
};
+static void smbXcli_conn_samba_suicide_cleanup(struct tevent_req *req,
+ enum tevent_req_state req_state);
static void smbXcli_conn_samba_suicide_done(struct tevent_req *subreq);
struct tevent_req *smbXcli_conn_samba_suicide_send(TALLOC_CTX *mem_ctx,
SCVAL(state->buf, 8, exitcode);
_smb_setlen_nbt(state->buf, sizeof(state->buf)-4);
+ if (conn->suicide_req != NULL) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+
state->iov.iov_base = state->buf;
state->iov.iov_len = sizeof(state->buf);
- subreq = writev_send(state, ev, conn->outgoing, conn->write_fd,
+ subreq = writev_send(state, ev, conn->outgoing, conn->sock_fd,
false, &state->iov, 1);
if (tevent_req_nomem(subreq, req)) {
return tevent_req_post(req, ev);
}
tevent_req_set_callback(subreq, smbXcli_conn_samba_suicide_done, req);
+ state->write_req = subreq;
+
+ tevent_req_set_cleanup_fn(req, smbXcli_conn_samba_suicide_cleanup);
+
+ /*
+ * We need to use tevent_req_defer_callback()
+ * in order to allow smbXcli_conn_disconnect()
+ * to do a safe cleanup.
+ */
+ tevent_req_defer_callback(req, ev);
+ conn->suicide_req = req;
+
return req;
}
+static void smbXcli_conn_samba_suicide_cleanup(struct tevent_req *req,
+ enum tevent_req_state req_state)
+{
+ struct smbXcli_conn_samba_suicide_state *state = tevent_req_data(
+ req, struct smbXcli_conn_samba_suicide_state);
+
+ TALLOC_FREE(state->write_req);
+
+ if (state->conn == NULL) {
+ return;
+ }
+
+ if (state->conn->suicide_req == req) {
+ state->conn->suicide_req = NULL;
+ }
+ state->conn = NULL;
+}
+
static void smbXcli_conn_samba_suicide_done(struct tevent_req *subreq)
{
struct tevent_req *req = tevent_req_callback_data(
ssize_t nwritten;
int err;
+ state->write_req = NULL;
+
nwritten = writev_recv(subreq, &err);
TALLOC_FREE(subreq);
if (nwritten == -1) {
+ /* here, we need to notify all pending requests */
NTSTATUS status = map_nt_error_from_unix_common(err);
smbXcli_conn_disconnect(state->conn, status);
return;
if (req == NULL) {
goto fail;
}
- ok = tevent_req_poll(req, ev);
+ ok = tevent_req_poll_ntstatus(req, ev, &status);
if (!ok) {
- status = map_nt_error_from_unix_common(errno);
goto fail;
}
status = smbXcli_conn_samba_suicide_recv(req);
bool smb1cli_conn_req_possible(struct smbXcli_conn *conn)
{
- size_t pending;
+ size_t pending = talloc_array_length(conn->pending);
uint16_t possible = conn->smb1.server.max_mux;
- pending = tevent_queue_length(conn->outgoing);
- if (pending >= possible) {
- return false;
- }
- pending += talloc_array_length(conn->pending);
if (pending >= possible) {
return false;
}
}
}
+static NTSTATUS smbXcli_req_cancel_write_req(struct tevent_req *req)
+{
+ struct smbXcli_req_state *state =
+ tevent_req_data(req,
+ struct smbXcli_req_state);
+ struct smbXcli_conn *conn = state->conn;
+ size_t num_pending = talloc_array_length(conn->pending);
+ ssize_t ret;
+ int err;
+ bool ok;
+
+ if (state->write_req == NULL) {
+ return NT_STATUS_OK;
+ }
+
+ /*
+ * Check if it's possible to cancel the request.
+ * If the result is true it's not too late.
+ * See writev_cancel().
+ */
+ ok = tevent_req_cancel(state->write_req);
+ if (ok) {
+ TALLOC_FREE(state->write_req);
+
+ if (conn->protocol >= PROTOCOL_SMB2_02) {
+ /*
+ * SMB2 has a sane signing state.
+ */
+ return NT_STATUS_OK;
+ }
+
+ if (num_pending > 1) {
+ /*
+ * We have more pending requests following us. This
+ * means the signing state will be broken for them.
+ *
+ * As a solution we could add the requests directly to
+ * our outgoing queue and do the signing in the trigger
+ * function and then use writev_send() without passing a
+ * queue. That way we'll only sign packets we're most
+ * likely send to the wire.
+ */
+ return NT_STATUS_REQUEST_OUT_OF_SEQUENCE;
+ }
+
+ /*
+ * If we're the only request that's
+ * pending, we're able to recover the signing
+ * state.
+ */
+ smb_signing_cancel_reply(conn->smb1.signing,
+ state->smb1.one_way_seqnum);
+ return NT_STATUS_OK;
+ }
+
+ ret = writev_recv(state->write_req, &err);
+ TALLOC_FREE(state->write_req);
+ if (ret == -1) {
+ return map_nt_error_from_unix_common(err);
+ }
+
+ return NT_STATUS_OK;
+}
+
void smbXcli_req_unset_pending(struct tevent_req *req)
{
struct smbXcli_req_state *state =
struct smbXcli_conn *conn = state->conn;
size_t num_pending = talloc_array_length(conn->pending);
size_t i;
+ NTSTATUS cancel_status;
+
+ cancel_status = smbXcli_req_cancel_write_req(req);
if (state->smb1.mid != 0) {
/*
* This is a [nt]trans[2] request which waits
* for more than one reply.
*/
+ if (!NT_STATUS_IS_OK(cancel_status)) {
+ /*
+ * If the write_req cancel didn't work
+ * we can't use the connection anymore.
+ */
+ smbXcli_conn_disconnect(conn, cancel_status);
+ return;
+ }
return;
}
- talloc_set_destructor(req, NULL);
+ tevent_req_set_cleanup_fn(req, NULL);
if (num_pending == 1) {
/*
* conn->pending. So if nothing is pending anymore, we need to
* delete the socket read fde.
*/
+ /* TODO: smbXcli_conn_cancel_read_req */
TALLOC_FREE(conn->pending);
conn->read_smb_req = NULL;
+
+ if (!NT_STATUS_IS_OK(cancel_status)) {
+ /*
+ * If the write_req cancel didn't work
+ * we can't use the connection anymore.
+ */
+ smbXcli_conn_disconnect(conn, cancel_status);
+ return;
+ }
return;
}
* right thing nevertheless, the point of this routine is to
* remove ourselves from conn->pending.
*/
+
+ if (!NT_STATUS_IS_OK(cancel_status)) {
+ /*
+ * If the write_req cancel didn't work
+ * we can't use the connection anymore.
+ */
+ smbXcli_conn_disconnect(conn, cancel_status);
+ return;
+ }
return;
}
*/
conn->pending = talloc_realloc(NULL, conn->pending, struct tevent_req *,
num_pending - 1);
+
+ if (!NT_STATUS_IS_OK(cancel_status)) {
+ /*
+ * If the write_req cancel didn't work
+ * we can't use the connection anymore.
+ */
+ smbXcli_conn_disconnect(conn, cancel_status);
+ return;
+ }
return;
}
-static int smbXcli_req_destructor(struct tevent_req *req)
+static void smbXcli_req_cleanup(struct tevent_req *req,
+ enum tevent_req_state req_state)
{
struct smbXcli_req_state *state =
tevent_req_data(req,
struct smbXcli_req_state);
+ struct smbXcli_conn *conn = state->conn;
+ NTSTATUS cancel_status;
- /*
- * Make sure we really remove it from
- * the pending array on destruction.
- */
- state->smb1.mid = 0;
- smbXcli_req_unset_pending(req);
- return 0;
+ switch (req_state) {
+ case TEVENT_REQ_RECEIVED:
+ /*
+ * Make sure we really remove it from
+ * the pending array on destruction.
+ *
+ * smbXcli_req_unset_pending() calls
+ * smbXcli_req_cancel_write_req() internal
+ */
+ state->smb1.mid = 0;
+ smbXcli_req_unset_pending(req);
+ return;
+ default:
+ cancel_status = smbXcli_req_cancel_write_req(req);
+ if (!NT_STATUS_IS_OK(cancel_status)) {
+ /*
+ * If the write_req cancel didn't work
+ * we can't use the connection anymore.
+ */
+ smbXcli_conn_disconnect(conn, cancel_status);
+ return;
+ }
+ return;
+ }
}
static bool smb1cli_req_cancel(struct tevent_req *req);
}
pending[num_pending] = req;
conn->pending = pending;
- talloc_set_destructor(req, smbXcli_req_destructor);
+ tevent_req_set_cleanup_fn(req, smbXcli_req_cleanup);
tevent_req_set_cancel_fn(req, smbXcli_req_cancel);
if (!smbXcli_conn_receive_next(conn)) {
*/
conn->read_smb_req = read_smb_send(conn->pending,
state->ev,
- conn->read_fd);
+ conn->sock_fd);
if (conn->read_smb_req == NULL) {
return false;
}
void smbXcli_conn_disconnect(struct smbXcli_conn *conn, NTSTATUS status)
{
struct smbXcli_session *session;
+ int sock_fd = conn->sock_fd;
tevent_queue_stop(conn->outgoing);
- if (conn->read_fd != -1) {
- close(conn->read_fd);
- }
- if (conn->write_fd != -1) {
- close(conn->write_fd);
- }
- conn->read_fd = -1;
- conn->write_fd = -1;
+ conn->sock_fd = -1;
session = conn->sessions;
if (talloc_array_length(conn->pending) == 0) {
smb2cli_session_increment_channel_sequence(session);
}
+ if (conn->suicide_req != NULL) {
+ /*
+ * smbXcli_conn_samba_suicide_send()
+ * used tevent_req_defer_callback() already.
+ */
+ if (!NT_STATUS_IS_OK(status)) {
+ tevent_req_nterror(conn->suicide_req, status);
+ }
+ conn->suicide_req = NULL;
+ }
+
/*
* Cancel all pending requests. We do not do a for-loop walking
* conn->pending because that array changes in
state = tevent_req_data(req, struct smbXcli_req_state);
if (state->smb1.chained_requests == NULL) {
+ bool in_progress;
+
/*
* We're dead. No point waiting for trans2
* replies.
continue;
}
+ in_progress = tevent_req_is_in_progress(req);
+ if (!in_progress) {
+ /*
+ * already finished
+ */
+ continue;
+ }
+
/*
* we need to defer the callback, because we may notify
* more then one caller.
num_chained = talloc_array_length(chain);
for (i=0; i<num_chained; i++) {
+ bool in_progress;
+
req = chain[i];
state = tevent_req_data(req, struct smbXcli_req_state);
continue;
}
+ in_progress = tevent_req_is_in_progress(req);
+ if (!in_progress) {
+ /*
+ * already finished
+ */
+ continue;
+ }
+
/*
* we need to defer the callback, because we may notify
* more than one caller.
}
TALLOC_FREE(chain);
}
+
+ if (sock_fd != -1) {
+ close(sock_fd);
+ }
}
/*
static size_t smbXcli_iov_len(const struct iovec *iov, int count)
{
- size_t result = 0;
- int i;
- for (i=0; i<count; i++) {
- result += iov[i].iov_len;
- }
- return result;
-}
-
-static uint8_t *smbXcli_iov_concat(TALLOC_CTX *mem_ctx,
- const struct iovec *iov,
- int count)
-{
- size_t len = smbXcli_iov_len(iov, count);
- size_t copied;
- uint8_t *buf;
- int i;
+ ssize_t ret = iov_buflen(iov, count);
- buf = talloc_array(mem_ctx, uint8_t, len);
- if (buf == NULL) {
- return NULL;
- }
- copied = 0;
- for (i=0; i<count; i++) {
- memcpy(buf+copied, iov[i].iov_base, iov[i].iov_len);
- copied += iov[i].iov_len;
- }
- return buf;
+ /* Ignore the overflow case for now ... */
+ return ret;
}
static void smb1cli_req_flags(enum protocol_types protocol,
uint16_t flags2 = 0;
uint16_t uid = 0;
uint16_t tid = 0;
+ ssize_t num_bytes;
if (iov_count > MAX_SMB_IOV) {
/*
if (tcon) {
tid = tcon->smb1.tcon_id;
+
+ if (tcon->fs_attributes & FILE_CASE_SENSITIVE_SEARCH) {
+ clear_flags |= FLAG_CASELESS_PATHNAMES;
+ } else {
+ /* Default setting, case insensitive. */
+ additional_flags |= FLAG_CASELESS_PATHNAMES;
+ }
+
+ if (smbXcli_conn_dfs_supported(conn) &&
+ smbXcli_tcon_is_dfs_share(tcon))
+ {
+ additional_flags2 |= FLAGS2_DFS_PATHNAMES;
+ }
}
state->smb1.recv_cmd = 0xFF;
state->smb1.vwv = vwv;
- SSVAL(state->smb1.bytecount_buf, 0, smbXcli_iov_len(bytes_iov, iov_count));
+ num_bytes = iov_buflen(bytes_iov, iov_count);
+ if (num_bytes == -1) {
+ /*
+ * I'd love to add a check for num_bytes<=UINT16_MAX here, but
+ * the smbclient->samba connections can lie and transfer more.
+ */
+ TALLOC_FREE(req);
+ return NULL;
+ }
+
+ SSVAL(state->smb1.bytecount_buf, 0, num_bytes);
state->smb1.iov[0].iov_base = (void *)state->length_hdr;
state->smb1.iov[0].iov_len = sizeof(state->length_hdr);
frame = talloc_stackframe();
- buf = smbXcli_iov_concat(frame, &iov[1], iov_count - 1);
+ buf = iov_concat(frame, &iov[1], iov_count - 1);
if (buf == NULL) {
return NT_STATUS_NO_MEMORY;
}
NTSTATUS status;
uint8_t cmd;
uint16_t mid;
+ ssize_t nbtlen;
if (!smbXcli_conn_is_connected(state->conn)) {
return NT_STATUS_CONNECTION_DISCONNECTED;
}
if (state->conn->protocol > PROTOCOL_NT1) {
+ DBG_ERR("called for dialect[%s] server[%s]\n",
+ smb_protocol_types_string(state->conn->protocol),
+ smbXcli_conn_remote_name(state->conn));
return NT_STATUS_REVISION_MISMATCH;
}
}
SSVAL(iov[1].iov_base, HDR_MID, mid);
- _smb_setlen_nbt(iov[0].iov_base, smbXcli_iov_len(&iov[1], iov_count-1));
+ nbtlen = iov_buflen(&iov[1], iov_count-1);
+ if ((nbtlen == -1) || (nbtlen > 0x1FFFF)) {
+ return NT_STATUS_INVALID_PARAMETER_MIX;
+ }
+
+ _smb_setlen_nbt(iov[0].iov_base, nbtlen);
status = smb1cli_conn_signv(state->conn, iov, iov_count,
&state->smb1.seqnum,
if (common_encryption_on(state->conn->smb1.trans_enc)) {
char *buf, *enc_buf;
- buf = (char *)smbXcli_iov_concat(talloc_tos(), iov, iov_count);
+ buf = (char *)iov_concat(talloc_tos(), iov, iov_count);
if (buf == NULL) {
return NT_STATUS_NO_MEMORY;
}
state->conn->dispatch_incoming = smb1cli_conn_dispatch_incoming;
}
+ if (!smbXcli_req_set_pending(req)) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
tevent_req_set_cancel_fn(req, smbXcli_req_cancel);
subreq = writev_send(state, state->ev, state->conn->outgoing,
- state->conn->write_fd, false, iov, iov_count);
+ state->conn->sock_fd, false, iov, iov_count);
if (subreq == NULL) {
return NT_STATUS_NO_MEMORY;
}
tevent_req_set_callback(subreq, smb1cli_req_writev_done, req);
+ state->write_req = subreq;
+
return NT_STATUS_OK;
}
ssize_t nwritten;
int err;
+ state->write_req = NULL;
+
nwritten = writev_recv(subreq, &err);
TALLOC_FREE(subreq);
if (nwritten == -1) {
+ /* here, we need to notify all pending requests */
NTSTATUS status = map_nt_error_from_unix_common(err);
smbXcli_conn_disconnect(state->conn, status);
return;
tevent_req_done(req);
return;
}
-
- if (!smbXcli_req_set_pending(req)) {
- tevent_req_nterror(req, NT_STATUS_NO_MEMORY);
- return;
- }
}
static void smbXcli_conn_received(struct tevent_req *subreq)
struct iovec **piov, int *pnum_iov)
{
struct iovec *iov;
- int num_iov;
+ size_t num_iov;
size_t buflen;
size_t taken;
size_t remaining;
struct iovec *iov = NULL;
struct iovec *this_iov;
NTSTATUS status;
- size_t nbt_len;
+ ssize_t nbt_len;
if (num_reqs == 1) {
return smb1cli_req_writev_submit(reqs[0], first_state,
chain_padding = next_padding;
}
- nbt_len = smbXcli_iov_len(&iov[1], iovlen-1);
- if (nbt_len > first_state->conn->smb1.max_xmit) {
+ nbt_len = iov_buflen(&iov[1], iovlen-1);
+ if ((nbt_len == -1) || (nbt_len > first_state->conn->smb1.max_xmit)) {
TALLOC_FREE(iov);
TALLOC_FREE(first_state->smb1.chained_requests);
return NT_STATUS_INVALID_PARAMETER_MIX;
|| (talloc_array_length(conn->pending) != 0));
}
+bool smbXcli_conn_dfs_supported(struct smbXcli_conn *conn)
+{
+ if (conn->protocol >= PROTOCOL_SMB2_02) {
+ return (smb2cli_conn_server_capabilities(conn) & SMB2_CAP_DFS);
+ }
+
+ return (smb1cli_conn_capabilities(conn) & CAP_DFS);
+}
+
bool smb2cli_conn_req_possible(struct smbXcli_conn *conn, uint32_t *max_dyn_len)
{
uint16_t credits = 1;
conn->smb2.max_credits = max_credits;
}
+uint16_t smb2cli_conn_get_cur_credits(struct smbXcli_conn *conn)
+{
+ return conn->smb2.cur_credits;
+}
+
+uint8_t smb2cli_conn_get_io_priority(struct smbXcli_conn *conn)
+{
+ if (conn->protocol < PROTOCOL_SMB3_11) {
+ return 0;
+ }
+
+ return conn->smb2.io_priority;
+}
+
+void smb2cli_conn_set_io_priority(struct smbXcli_conn *conn,
+ uint8_t io_priority)
+{
+ conn->smb2.io_priority = io_priority;
+}
+
+uint32_t smb2cli_conn_cc_chunk_len(struct smbXcli_conn *conn)
+{
+ return conn->smb2.cc_chunk_len;
+}
+
+void smb2cli_conn_set_cc_chunk_len(struct smbXcli_conn *conn,
+ uint32_t chunk_len)
+{
+ conn->smb2.cc_chunk_len = chunk_len;
+}
+
+uint32_t smb2cli_conn_cc_max_chunks(struct smbXcli_conn *conn)
+{
+ return conn->smb2.cc_max_chunks;
+}
+
+void smb2cli_conn_set_cc_max_chunks(struct smbXcli_conn *conn,
+ uint32_t max_chunks)
+{
+ conn->smb2.cc_max_chunks = max_chunks;
+}
+
static void smb2cli_req_cancel_done(struct tevent_req *subreq);
static bool smb2cli_req_cancel(struct tevent_req *req)
struct smbXcli_req_state *state =
tevent_req_data(req,
struct smbXcli_req_state);
- uint32_t flags = IVAL(state->smb2.hdr, SMB2_HDR_FLAGS);
- uint64_t mid = BVAL(state->smb2.hdr, SMB2_HDR_MESSAGE_ID);
- uint64_t aid = BVAL(state->smb2.hdr, SMB2_HDR_ASYNC_ID);
struct smbXcli_tcon *tcon = state->tcon;
struct smbXcli_session *session = state->session;
uint8_t *fixed = state->smb2.pad;
subreq = smb2cli_req_create(state, state->ev,
state->conn,
SMB2_OP_CANCEL,
- flags, 0,
+ 0, 0, /* flags */
0, /* timeout */
tcon, session,
fixed, fixed_len,
}
substate = tevent_req_data(subreq, struct smbXcli_req_state);
- /*
- * clear everything but the SMB2_HDR_FLAG_ASYNC flag
- * e.g. if SMB2_HDR_FLAG_CHAINED is set we get INVALID_PARAMETER back
- */
- flags &= SMB2_HDR_FLAG_ASYNC;
-
- if (flags & SMB2_HDR_FLAG_ASYNC) {
- mid = 0;
- }
-
- SIVAL(substate->smb2.hdr, SMB2_HDR_FLAGS, flags);
- SBVAL(substate->smb2.hdr, SMB2_HDR_MESSAGE_ID, mid);
- SBVAL(substate->smb2.hdr, SMB2_HDR_ASYNC_ID, aid);
+ SIVAL(substate->smb2.hdr, SMB2_HDR_FLAGS, state->smb2.cancel_flags);
+ SBVAL(substate->smb2.hdr, SMB2_HDR_MESSAGE_ID, state->smb2.cancel_mid);
+ SBVAL(substate->smb2.hdr, SMB2_HDR_ASYNC_ID, state->smb2.cancel_aid);
status = smb2cli_req_compound_submit(&subreq, 1);
if (!NT_STATUS_IS_OK(status)) {
uint32_t flags = 0;
uint32_t tid = 0;
uint64_t uid = 0;
- bool use_channel_sequence = false;
+ bool use_channel_sequence = conn->smb2.force_channel_sequence;
uint16_t channel_sequence = 0;
+ bool use_replay_flag = false;
req = tevent_req_create(mem_ctx, &state,
struct smbXcli_req_state);
use_channel_sequence = true;
}
+ if (smbXcli_conn_protocol(conn) >= PROTOCOL_SMB3_00) {
+ use_replay_flag = true;
+ }
+
+ if (smbXcli_conn_protocol(conn) >= PROTOCOL_SMB3_11) {
+ flags |= SMB2_PRIORITY_VALUE_TO_MASK(conn->smb2.io_priority);
+ }
+
if (session) {
uid = session->smb2->session_id;
channel_sequence = session->smb2->channel_sequence;
}
+ if (use_replay_flag && session->smb2->replay_active) {
+ additional_flags |= SMB2_HDR_FLAG_REPLAY_OPERATION;
+ }
+
state->smb2.should_sign = session->smb2->should_sign;
state->smb2.should_encrypt = session->smb2->should_encrypt;
if (cmd == SMB2_OP_SESSSETUP &&
- session->smb2->signing_key.length != 0) {
+ session->smb2_channel.signing_key.length == 0 &&
+ session->smb2->signing_key.length != 0)
+ {
+ /*
+ * a session bind needs to be signed
+ */
state->smb2.should_sign = true;
}
session->smb2_channel.signing_key.length == 0) {
state->smb2.should_encrypt = false;
}
+
+ if (additional_flags & SMB2_HDR_FLAG_SIGNED) {
+ if (session->smb2_channel.signing_key.length == 0) {
+ tevent_req_nterror(req, NT_STATUS_NO_USER_SESSION_KEY);
+ return req;
+ }
+
+ additional_flags &= ~SMB2_HDR_FLAG_SIGNED;
+ state->smb2.should_sign = true;
+ }
}
if (tcon) {
tid = tcon->smb2.tcon_id;
+ if (tcon->smb2.should_sign) {
+ state->smb2.should_sign = true;
+ }
if (tcon->smb2.should_encrypt) {
state->smb2.should_encrypt = true;
}
int tf_iov = -1;
const DATA_BLOB *encryption_key = NULL;
uint64_t encryption_session_id = 0;
+ uint64_t nonce_high = UINT64_MAX;
+ uint64_t nonce_low = UINT64_MAX;
/*
* 1 for the nbt length, optional TRANSFORM
encryption_session_id = state->session->smb2->session_id;
- tf_iov = num_iov;
- iov[num_iov].iov_base = state->smb2.transform;
- iov[num_iov].iov_len = sizeof(state->smb2.transform);
- num_iov += 1;
-
- SBVAL(state->smb2.transform, SMB2_TF_PROTOCOL_ID, SMB2_TF_MAGIC);
- SBVAL(state->smb2.transform, SMB2_TF_NONCE,
- state->session->smb2->nonce_low);
- SBVAL(state->smb2.transform, SMB2_TF_NONCE+8,
- state->session->smb2->nonce_high);
- SBVAL(state->smb2.transform, SMB2_TF_SESSION_ID,
- encryption_session_id);
-
state->session->smb2->nonce_low += 1;
if (state->session->smb2->nonce_low == 0) {
state->session->smb2->nonce_high += 1;
state->session->smb2->nonce_low += 1;
}
+ /*
+ * CCM and GCM algorithms must never have their
+ * nonce wrap, or the security of the whole
+ * communication and the keys is destroyed.
+ * We must drop the connection once we have
+ * transfered too much data.
+ *
+ * NOTE: We assume nonces greater than 8 bytes.
+ */
+ if (state->session->smb2->nonce_high >=
+ state->session->smb2->nonce_high_max)
+ {
+ return NT_STATUS_ENCRYPTION_FAILED;
+ }
+
+ nonce_high = state->session->smb2->nonce_high_random;
+ nonce_high += state->session->smb2->nonce_high;
+ nonce_low = state->session->smb2->nonce_low;
+
+ tf_iov = num_iov;
+ iov[num_iov].iov_base = state->smb2.transform;
+ iov[num_iov].iov_len = sizeof(state->smb2.transform);
+ num_iov += 1;
+
+ SBVAL(state->smb2.transform, SMB2_TF_PROTOCOL_ID, SMB2_TF_MAGIC);
+ SBVAL(state->smb2.transform, SMB2_TF_NONCE,
+ nonce_low);
+ SBVAL(state->smb2.transform, SMB2_TF_NONCE+8,
+ nonce_high);
+ SBVAL(state->smb2.transform, SMB2_TF_SESSION_ID,
+ encryption_session_id);
+
nbt_len += SMB2_TF_HDR_SIZE;
break;
}
SSVAL(state->smb2.hdr, SMB2_HDR_CREDIT, credits);
SBVAL(state->smb2.hdr, SMB2_HDR_MESSAGE_ID, mid);
+ state->smb2.cancel_flags = 0;
+ state->smb2.cancel_mid = mid;
+ state->smb2.cancel_aid = 0;
+
skip_credits:
if (state->session && encryption_key == NULL) {
/*
}
status = smb2_signing_encrypt_pdu(*encryption_key,
- state->conn->protocol,
+ state->conn->smb2.server.cipher,
&iov[tf_iov], num_iov - tf_iov);
if (!NT_STATUS_IS_OK(status)) {
return status;
}
subreq = writev_send(state, state->ev, state->conn->outgoing,
- state->conn->write_fd, false, iov, num_iov);
+ state->conn->sock_fd, false, iov, num_iov);
if (subreq == NULL) {
return NT_STATUS_NO_MEMORY;
}
tevent_req_set_callback(subreq, smb2cli_req_writev_done, reqs[0]);
+ state->write_req = subreq;
+
return NT_STATUS_OK;
}
ssize_t nwritten;
int err;
+ state->write_req = NULL;
+
nwritten = writev_recv(subreq, &err);
TALLOC_FREE(subreq);
if (nwritten == -1) {
}
}
+static struct smbXcli_session* smbXcli_session_by_uid(struct smbXcli_conn *conn,
+ uint64_t uid)
+{
+ struct smbXcli_session *s = conn->sessions;
+
+ for (; s; s = s->next) {
+ if (s->smb2->session_id != uid) {
+ continue;
+ }
+ break;
+ }
+
+ return s;
+}
+
static NTSTATUS smb2cli_inbuf_parse_compound(struct smbXcli_conn *conn,
uint8_t *buf,
size_t buflen,
TALLOC_CTX *mem_ctx,
- struct iovec **piov, int *pnum_iov)
+ struct iovec **piov,
+ size_t *pnum_iov)
{
struct iovec *iov;
int num_iov = 0;
goto inval;
}
- s = conn->sessions;
- for (; s; s = s->next) {
- if (s->smb2->session_id != uid) {
- continue;
- }
- break;
- }
-
+ s = smbXcli_session_by_uid(conn, uid);
if (s == NULL) {
DEBUG(10, ("unknown session_id %llu\n",
(unsigned long long)uid));
tf_iov[1].iov_len = enc_len;
status = smb2_signing_decrypt_pdu(s->smb2->decryption_key,
- conn->protocol,
+ conn->smb2.server.cipher,
tf_iov, 2);
if (!NT_STATUS_IS_OK(status)) {
TALLOC_FREE(iov);
{
struct tevent_req *req;
struct smbXcli_req_state *state = NULL;
- struct iovec *iov;
- int i, num_iov;
+ struct iovec *iov = NULL;
+ size_t i, num_iov = 0;
NTSTATUS status;
bool defer = true;
struct smbXcli_session *last_session = NULL;
}
state = tevent_req_data(req, struct smbXcli_req_state);
- state->smb2.got_async = false;
-
req_opcode = SVAL(state->smb2.hdr, SMB2_HDR_OPCODE);
if (opcode != req_opcode) {
return NT_STATUS_INVALID_NETWORK_RESPONSE;
NT_STATUS_EQUAL(status, STATUS_PENDING)) {
uint64_t async_id = BVAL(inhdr, SMB2_HDR_ASYNC_ID);
+ if (state->smb2.got_async) {
+ /* We only expect one STATUS_PENDING response */
+ return NT_STATUS_INVALID_NETWORK_RESPONSE;
+ }
+ state->smb2.got_async = true;
+
/*
* async interim responses are not signed,
* even if the SMB2_HDR_FLAG_SIGNED flag
* is set.
*/
- req_flags |= SMB2_HDR_FLAG_ASYNC;
- SBVAL(state->smb2.hdr, SMB2_HDR_FLAGS, req_flags);
- SBVAL(state->smb2.hdr, SMB2_HDR_ASYNC_ID, async_id);
+ state->smb2.cancel_flags = SMB2_HDR_FLAG_ASYNC;
+ state->smb2.cancel_mid = 0;
+ state->smb2.cancel_aid = async_id;
if (state->smb2.notify_async) {
- state->smb2.got_async = true;
tevent_req_defer_callback(req, state->ev);
tevent_req_notify_callback(req);
}
uint64_t uid = BVAL(inhdr, SMB2_HDR_SESSION_ID);
if (session == NULL) {
- struct smbXcli_session *s;
-
- s = state->conn->sessions;
- for (; s; s = s->next) {
- if (s->smb2->session_id != uid) {
- continue;
- }
-
- session = s;
- break;
- }
+ session = smbXcli_session_by_uid(state->conn,
+ uid);
}
if (session == NULL) {
}
}
if (signing_key) {
- int cmp;
- static const uint8_t zeros[16];
-
- cmp = memcmp(inhdr+SMB2_HDR_SIGNATURE,
- zeros,
- 16);
- if (cmp == 0) {
+ bool zero;
+ zero = all_zero(inhdr+SMB2_HDR_SIGNATURE, 16);
+ if (zero) {
state->smb2.signing_skipped = true;
signing_key = NULL;
}
*piov = NULL;
}
- if (state->smb2.got_async) {
+ if (tevent_req_is_in_progress(req) && state->smb2.got_async) {
return STATUS_PENDING;
}
return status;
}
+NTSTATUS smb2cli_req_get_sent_iov(struct tevent_req *req,
+ struct iovec *sent_iov)
+{
+ struct smbXcli_req_state *state =
+ tevent_req_data(req,
+ struct smbXcli_req_state);
+
+ if (tevent_req_is_in_progress(req)) {
+ return STATUS_PENDING;
+ }
+
+ sent_iov[0].iov_base = state->smb2.hdr;
+ sent_iov[0].iov_len = sizeof(state->smb2.hdr);
+
+ sent_iov[1].iov_base = discard_const(state->smb2.fixed);
+ sent_iov[1].iov_len = state->smb2.fixed_len;
+
+ if (state->smb2.dyn != NULL) {
+ sent_iov[2].iov_base = discard_const(state->smb2.dyn);
+ sent_iov[2].iov_len = state->smb2.dyn_len;
+ } else {
+ sent_iov[2].iov_base = NULL;
+ sent_iov[2].iov_len = 0;
+ }
+
+ return NT_STATUS_OK;
+}
+
static const struct {
enum protocol_types proto;
const char *smb1_name;
{PROTOCOL_SMB2_24, SMB2_DIALECT_REVISION_224},
{PROTOCOL_SMB3_00, SMB3_DIALECT_REVISION_300},
{PROTOCOL_SMB3_02, SMB3_DIALECT_REVISION_302},
+ {PROTOCOL_SMB3_10, SMB3_DIALECT_REVISION_310},
+ {PROTOCOL_SMB3_11, SMB3_DIALECT_REVISION_311},
};
struct smbXcli_negprot_state {
struct smbXcli_conn *conn;
struct tevent_context *ev;
uint32_t timeout_msec;
- enum protocol_types min_protocol;
- enum protocol_types max_protocol;
struct {
uint8_t fixed[36];
- uint8_t dyn[ARRAY_SIZE(smb2cli_prots)*2];
} smb2;
};
struct smbXcli_conn *conn,
uint32_t timeout_msec,
enum protocol_types min_protocol,
- enum protocol_types max_protocol)
+ enum protocol_types max_protocol,
+ uint16_t max_credits)
{
struct tevent_req *req, *subreq;
struct smbXcli_negprot_state *state;
state->conn = conn;
state->ev = ev;
state->timeout_msec = timeout_msec;
- state->min_protocol = min_protocol;
- state->max_protocol = max_protocol;
if (min_protocol == PROTOCOL_NONE) {
tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER_MIX);
return tevent_req_post(req, ev);
}
+ conn->min_protocol = min_protocol;
+ conn->max_protocol = max_protocol;
+ conn->protocol = PROTOCOL_NONE;
+
+ if (max_protocol >= PROTOCOL_SMB2_02) {
+ conn->smb2.max_credits = max_credits;
+ }
+
if ((min_protocol < PROTOCOL_SMB2_02) &&
(max_protocol < PROTOCOL_SMB2_02)) {
/*
uint8_t c = 2;
bool ok;
- if (smb1cli_prots[i].proto < state->min_protocol) {
+ if (smb1cli_prots[i].proto < state->conn->min_protocol) {
continue;
}
- if (smb1cli_prots[i].proto > state->max_protocol) {
+ if (smb1cli_prots[i].proto > state->conn->max_protocol) {
continue;
}
}
}
- smb1cli_req_flags(state->max_protocol,
+ smb1cli_req_flags(state->conn->max_protocol,
state->conn->smb1.client.capabilities,
SMBnegprot,
0, 0, &flags,
protnum = SVAL(vwv, 0);
for (i=0; i < ARRAY_SIZE(smb1cli_prots); i++) {
- if (smb1cli_prots[i].proto < state->min_protocol) {
+ if (smb1cli_prots[i].proto < state->conn->min_protocol) {
continue;
}
- if (smb1cli_prots[i].proto > state->max_protocol) {
+ if (smb1cli_prots[i].proto > state->conn->max_protocol) {
continue;
}
tevent_req_done(req);
}
+static size_t smbXcli_padding_helper(uint32_t offset, size_t n)
+{
+ if ((offset & (n-1)) == 0) return 0;
+ return n - (offset & (n-1));
+}
+
static struct tevent_req *smbXcli_negprot_smb2_subreq(struct smbXcli_negprot_state *state)
{
size_t i;
uint8_t *buf;
uint16_t dialect_count = 0;
+ DATA_BLOB dyn = data_blob_null;
- buf = state->smb2.dyn;
for (i=0; i < ARRAY_SIZE(smb2cli_prots); i++) {
- if (smb2cli_prots[i].proto < state->min_protocol) {
+ bool ok;
+ uint8_t val[2];
+
+ if (smb2cli_prots[i].proto < state->conn->min_protocol) {
continue;
}
- if (smb2cli_prots[i].proto > state->max_protocol) {
+ if (smb2cli_prots[i].proto > state->conn->max_protocol) {
continue;
}
- SSVAL(buf, dialect_count*2, smb2cli_prots[i].smb2_dialect);
+ SSVAL(val, 0, smb2cli_prots[i].smb2_dialect);
+
+ ok = data_blob_append(state, &dyn, val, sizeof(val));
+ if (!ok) {
+ return NULL;
+ }
+
dialect_count++;
}
SSVAL(buf, 2, dialect_count);
SSVAL(buf, 4, state->conn->smb2.client.security_mode);
SSVAL(buf, 6, 0); /* Reserved */
- if (state->max_protocol >= PROTOCOL_SMB2_22) {
+ if (state->conn->max_protocol >= PROTOCOL_SMB2_22) {
SIVAL(buf, 8, state->conn->smb2.client.capabilities);
} else {
SIVAL(buf, 8, 0); /* Capabilities */
}
- if (state->max_protocol >= PROTOCOL_SMB2_10) {
+ if (state->conn->max_protocol >= PROTOCOL_SMB2_10) {
NTSTATUS status;
DATA_BLOB blob;
} else {
memset(buf+12, 0, 16); /* ClientGuid */
}
- SBVAL(buf, 28, 0); /* ClientStartTime */
+
+ if (state->conn->max_protocol >= PROTOCOL_SMB3_10) {
+ NTSTATUS status;
+ struct smb2_negotiate_contexts c = { .num_contexts = 0, };
+ uint32_t offset;
+ DATA_BLOB b;
+ uint8_t p[38];
+ const uint8_t zeros[8] = {0, };
+ size_t pad;
+ bool ok;
+
+ SSVAL(p, 0, 1); /* HashAlgorithmCount */
+ SSVAL(p, 2, 32); /* SaltLength */
+ SSVAL(p, 4, SMB2_PREAUTH_INTEGRITY_SHA512);
+ generate_random_buffer(p + 6, 32);
+
+ b = data_blob_const(p, 38);
+ status = smb2_negotiate_context_add(state, &c,
+ SMB2_PREAUTH_INTEGRITY_CAPABILITIES, b);
+ if (!NT_STATUS_IS_OK(status)) {
+ return NULL;
+ }
+
+ SSVAL(p, 0, 2); /* ChiperCount */
+ /*
+ * For now we preferr CCM because our implementation
+ * is faster than GCM, see bug #11451.
+ */
+ SSVAL(p, 2, SMB2_ENCRYPTION_AES128_CCM);
+ SSVAL(p, 4, SMB2_ENCRYPTION_AES128_GCM);
+
+ b = data_blob_const(p, 6);
+ status = smb2_negotiate_context_add(state, &c,
+ SMB2_ENCRYPTION_CAPABILITIES, b);
+ if (!NT_STATUS_IS_OK(status)) {
+ return NULL;
+ }
+
+ status = smb2_negotiate_context_push(state, &b, c);
+ if (!NT_STATUS_IS_OK(status)) {
+ return NULL;
+ }
+
+ offset = SMB2_HDR_BODY + sizeof(state->smb2.fixed) + dyn.length;
+ pad = smbXcli_padding_helper(offset, 8);
+
+ ok = data_blob_append(state, &dyn, zeros, pad);
+ if (!ok) {
+ return NULL;
+ }
+ offset += pad;
+
+ ok = data_blob_append(state, &dyn, b.data, b.length);
+ if (!ok) {
+ return NULL;
+ }
+
+ SIVAL(buf, 28, offset); /* NegotiateContextOffset */
+ SSVAL(buf, 32, c.num_contexts); /* NegotiateContextCount */
+ SSVAL(buf, 34, 0); /* Reserved */
+ } else {
+ SBVAL(buf, 28, 0); /* Reserved/ClientStartTime */
+ }
return smb2cli_req_send(state, state->ev,
state->conn, SMB2_OP_NEGPROT,
state->timeout_msec,
NULL, NULL, /* tcon, session */
state->smb2.fixed, sizeof(state->smb2.fixed),
- state->smb2.dyn, dialect_count*2,
+ dyn.data, dyn.length,
UINT16_MAX); /* max_dyn_len */
}
uint8_t *body;
size_t i;
uint16_t dialect_revision;
+ struct smb2_negotiate_contexts c = { .num_contexts = 0, };
+ uint32_t negotiate_context_offset = 0;
+ uint16_t negotiate_context_count = 0;
+ DATA_BLOB negotiate_context_blob = data_blob_null;
+ size_t avail;
+ size_t ctx_ofs;
+ size_t needed;
+ struct smb2_negotiate_context *preauth = NULL;
+ uint16_t hash_count;
+ uint16_t salt_length;
+ uint16_t hash_selected;
+ struct hc_sha512state sctx;
+ struct smb2_negotiate_context *cipher = NULL;
+ struct iovec sent_iov[3];
static const struct smb2cli_req_expected_response expected[] = {
{
.status = NT_STATUS_OK,
status = smb2cli_req_recv(subreq, state, &iov,
expected, ARRAY_SIZE(expected));
- TALLOC_FREE(subreq);
if (tevent_req_nterror(req, status)) {
return;
}
dialect_revision = SVAL(body, 4);
for (i=0; i < ARRAY_SIZE(smb2cli_prots); i++) {
- if (smb2cli_prots[i].proto < state->min_protocol) {
+ if (smb2cli_prots[i].proto < state->conn->min_protocol) {
continue;
}
- if (smb2cli_prots[i].proto > state->max_protocol) {
+ if (smb2cli_prots[i].proto > state->conn->max_protocol) {
continue;
}
}
if (conn->protocol == PROTOCOL_NONE) {
- if (state->min_protocol >= PROTOCOL_SMB2_02) {
+ TALLOC_FREE(subreq);
+
+ if (state->conn->min_protocol >= PROTOCOL_SMB2_02) {
tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE);
return;
}
}
/* make sure we do not loop forever */
- state->min_protocol = PROTOCOL_SMB2_02;
+ state->conn->min_protocol = PROTOCOL_SMB2_02;
/*
* send a SMB2 negprot, in order to negotiate
}
conn->smb2.server.security_mode = SVAL(body, 2);
+ if (conn->protocol >= PROTOCOL_SMB3_10) {
+ negotiate_context_count = SVAL(body, 6);
+ }
blob = data_blob_const(body + 8, 16);
status = GUID_from_data_blob(&blob, &conn->smb2.server.guid);
return;
}
+ if (conn->protocol < PROTOCOL_SMB3_10) {
+ TALLOC_FREE(subreq);
+
+ if (conn->smb2.server.capabilities & SMB2_CAP_ENCRYPTION) {
+ conn->smb2.server.cipher = SMB2_ENCRYPTION_AES128_CCM;
+ }
+ tevent_req_done(req);
+ return;
+ }
+
+ /*
+ * Here we are now at SMB3_11, so encryption should be
+ * negotiated via context, not capabilities.
+ */
+
+ if (conn->smb2.server.capabilities & SMB2_CAP_ENCRYPTION) {
+ /*
+ * Server set SMB2_CAP_ENCRYPTION capability,
+ * but *SHOULD* not, not *MUST* not. Just mask it off.
+ * NetApp seems to do this:
+ * BUG: https://bugzilla.samba.org/show_bug.cgi?id=13009
+ */
+ conn->smb2.server.capabilities &= ~SMB2_CAP_ENCRYPTION;
+ }
+
+ negotiate_context_offset = IVAL(body, 60);
+ if (negotiate_context_offset < security_offset) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE);
+ return;
+ }
+
+ ctx_ofs = negotiate_context_offset - security_offset;
+ if (ctx_ofs > iov[2].iov_len) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE);
+ return;
+ }
+ avail = iov[2].iov_len - security_length;
+ needed = iov[2].iov_len - ctx_ofs;
+ if (needed > avail) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE);
+ return;
+ }
+
+ negotiate_context_blob.data = (uint8_t *)iov[2].iov_base;
+ negotiate_context_blob.length = iov[2].iov_len;
+
+ negotiate_context_blob.data += ctx_ofs;
+ negotiate_context_blob.length -= ctx_ofs;
+
+ status = smb2_negotiate_context_parse(state, negotiate_context_blob, &c);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ if (negotiate_context_count != c.num_contexts) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE);
+ return;
+ }
+
+ preauth = smb2_negotiate_context_find(&c,
+ SMB2_PREAUTH_INTEGRITY_CAPABILITIES);
+ if (preauth == NULL) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE);
+ return;
+ }
+
+ if (preauth->data.length < 6) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE);
+ return;
+ }
+
+ hash_count = SVAL(preauth->data.data, 0);
+ salt_length = SVAL(preauth->data.data, 2);
+ hash_selected = SVAL(preauth->data.data, 4);
+
+ if (hash_count != 1) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE);
+ return;
+ }
+
+ if (preauth->data.length != (6 + salt_length)) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE);
+ return;
+ }
+
+ if (hash_selected != SMB2_PREAUTH_INTEGRITY_SHA512) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE);
+ return;
+ }
+
+ cipher = smb2_negotiate_context_find(&c, SMB2_ENCRYPTION_CAPABILITIES);
+ if (cipher != NULL) {
+ uint16_t cipher_count;
+
+ if (cipher->data.length < 2) {
+ tevent_req_nterror(req,
+ NT_STATUS_INVALID_NETWORK_RESPONSE);
+ return;
+ }
+
+ cipher_count = SVAL(cipher->data.data, 0);
+
+ if (cipher_count > 1) {
+ tevent_req_nterror(req,
+ NT_STATUS_INVALID_NETWORK_RESPONSE);
+ return;
+ }
+
+ if (cipher->data.length != (2 + 2 * cipher_count)) {
+ tevent_req_nterror(req,
+ NT_STATUS_INVALID_NETWORK_RESPONSE);
+ return;
+ }
+
+ if (cipher_count == 1) {
+ uint16_t cipher_selected;
+
+ cipher_selected = SVAL(cipher->data.data, 2);
+
+ switch (cipher_selected) {
+ case SMB2_ENCRYPTION_AES128_GCM:
+ case SMB2_ENCRYPTION_AES128_CCM:
+ conn->smb2.server.cipher = cipher_selected;
+ break;
+ }
+ }
+ }
+
+ /* First we hash the request */
+ smb2cli_req_get_sent_iov(subreq, sent_iov);
+ samba_SHA512_Init(&sctx);
+ samba_SHA512_Update(&sctx, conn->smb2.preauth_sha512,
+ sizeof(conn->smb2.preauth_sha512));
+ for (i = 0; i < 3; i++) {
+ samba_SHA512_Update(&sctx, sent_iov[i].iov_base, sent_iov[i].iov_len);
+ }
+ samba_SHA512_Final(conn->smb2.preauth_sha512, &sctx);
+ TALLOC_FREE(subreq);
+
+ /* And now we hash the response */
+ samba_SHA512_Init(&sctx);
+ samba_SHA512_Update(&sctx, conn->smb2.preauth_sha512,
+ sizeof(conn->smb2.preauth_sha512));
+ for (i = 0; i < 3; i++) {
+ samba_SHA512_Update(&sctx, iov[i].iov_base, iov[i].iov_len);
+ }
+ samba_SHA512_Final(conn->smb2.preauth_sha512, &sctx);
+
tevent_req_done(req);
}
goto fail;
}
req = smbXcli_negprot_send(frame, ev, conn, timeout_msec,
- min_protocol, max_protocol);
+ min_protocol, max_protocol,
+ WINDOWS_CLIENT_PURE_SMB2_NEGPROT_INITIAL_CREDIT_ASK);
if (req == NULL) {
goto fail;
}
- ok = tevent_req_poll(req, ev);
+ ok = tevent_req_poll_ntstatus(req, ev, &status);
if (!ok) {
- status = map_nt_error_from_unix_common(errno);
goto fail;
}
status = smbXcli_negprot_recv(req);
return status;
}
-static int smbXcli_session_destructor(struct smbXcli_session *session)
-{
- if (session->conn == NULL) {
- return 0;
- }
+struct smb2cli_validate_negotiate_info_state {
+ struct smbXcli_conn *conn;
+ DATA_BLOB in_input_buffer;
+ DATA_BLOB in_output_buffer;
+ DATA_BLOB out_input_buffer;
+ DATA_BLOB out_output_buffer;
+ uint16_t dialect;
+};
- DLIST_REMOVE(session->conn->sessions, session);
- return 0;
-}
+static void smb2cli_validate_negotiate_info_done(struct tevent_req *subreq);
-struct smbXcli_session *smbXcli_session_create(TALLOC_CTX *mem_ctx,
- struct smbXcli_conn *conn)
+struct tevent_req *smb2cli_validate_negotiate_info_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbXcli_conn *conn,
+ uint32_t timeout_msec,
+ struct smbXcli_session *session,
+ struct smbXcli_tcon *tcon)
{
- struct smbXcli_session *session;
+ struct tevent_req *req;
+ struct smb2cli_validate_negotiate_info_state *state;
+ uint8_t *buf;
+ uint16_t dialect_count = 0;
+ struct tevent_req *subreq;
+ bool _save_should_sign;
+ size_t i;
- session = talloc_zero(mem_ctx, struct smbXcli_session);
- if (session == NULL) {
- return NULL;
- }
- session->smb2 = talloc_zero(session, struct smb2cli_session);
- if (session->smb2 == NULL) {
- talloc_free(session);
+ req = tevent_req_create(mem_ctx, &state,
+ struct smb2cli_validate_negotiate_info_state);
+ if (req == NULL) {
return NULL;
}
- talloc_set_destructor(session, smbXcli_session_destructor);
-
- DLIST_ADD_END(conn->sessions, session, struct smbXcli_session *);
- session->conn = conn;
+ state->conn = conn;
- return session;
-}
+ state->in_input_buffer = data_blob_talloc_zero(state,
+ 4 + 16 + 1 + 1 + 2);
+ if (tevent_req_nomem(state->in_input_buffer.data, req)) {
+ return tevent_req_post(req, ev);
+ }
+ buf = state->in_input_buffer.data;
-struct smbXcli_session *smbXcli_session_copy(TALLOC_CTX *mem_ctx,
- struct smbXcli_session *src)
-{
- struct smbXcli_session *session;
+ if (state->conn->max_protocol >= PROTOCOL_SMB2_22) {
+ SIVAL(buf, 0, conn->smb2.client.capabilities);
+ } else {
+ SIVAL(buf, 0, 0); /* Capabilities */
+ }
+ if (state->conn->max_protocol >= PROTOCOL_SMB2_10) {
+ NTSTATUS status;
+ DATA_BLOB blob;
- session = talloc_zero(mem_ctx, struct smbXcli_session);
- if (session == NULL) {
- return NULL;
+ status = GUID_to_ndr_blob(&conn->smb2.client.guid,
+ state, &blob);
+ if (!NT_STATUS_IS_OK(status)) {
+ return NULL;
+ }
+ memcpy(buf+4, blob.data, 16); /* ClientGuid */
+ } else {
+ memset(buf+4, 0, 16); /* ClientGuid */
}
- session->smb2 = talloc_zero(session, struct smb2cli_session);
- if (session->smb2 == NULL) {
- talloc_free(session);
- return NULL;
+ if (state->conn->min_protocol >= PROTOCOL_SMB2_02) {
+ SCVAL(buf, 20, conn->smb2.client.security_mode);
+ } else {
+ SCVAL(buf, 20, 0);
}
+ SCVAL(buf, 21, 0); /* reserved */
- session->conn = src->conn;
- *session->smb2 = *src->smb2;
+ for (i=0; i < ARRAY_SIZE(smb2cli_prots); i++) {
+ bool ok;
+ size_t ofs;
+
+ if (smb2cli_prots[i].proto < state->conn->min_protocol) {
+ continue;
+ }
+
+ if (smb2cli_prots[i].proto > state->conn->max_protocol) {
+ continue;
+ }
+
+ if (smb2cli_prots[i].proto == state->conn->protocol) {
+ state->dialect = smb2cli_prots[i].smb2_dialect;
+ }
+
+ ofs = state->in_input_buffer.length;
+ ok = data_blob_realloc(state, &state->in_input_buffer,
+ ofs + 2);
+ if (!ok) {
+ tevent_req_oom(req);
+ return tevent_req_post(req, ev);
+ }
+
+ buf = state->in_input_buffer.data;
+ SSVAL(buf, ofs, smb2cli_prots[i].smb2_dialect);
+
+ dialect_count++;
+ }
+ buf = state->in_input_buffer.data;
+ SSVAL(buf, 22, dialect_count);
+
+ _save_should_sign = smb2cli_tcon_is_signing_on(tcon);
+ smb2cli_tcon_should_sign(tcon, true);
+ subreq = smb2cli_ioctl_send(state, ev, conn,
+ timeout_msec, session, tcon,
+ UINT64_MAX, /* in_fid_persistent */
+ UINT64_MAX, /* in_fid_volatile */
+ FSCTL_VALIDATE_NEGOTIATE_INFO,
+ 0, /* in_max_input_length */
+ &state->in_input_buffer,
+ 24, /* in_max_output_length */
+ &state->in_output_buffer,
+ SMB2_IOCTL_FLAG_IS_FSCTL);
+ smb2cli_tcon_should_sign(tcon, _save_should_sign);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq,
+ smb2cli_validate_negotiate_info_done,
+ req);
+
+ return req;
+}
+
+static void smb2cli_validate_negotiate_info_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct smb2cli_validate_negotiate_info_state *state =
+ tevent_req_data(req,
+ struct smb2cli_validate_negotiate_info_state);
+ NTSTATUS status;
+ const uint8_t *buf;
+ uint32_t capabilities;
+ DATA_BLOB guid_blob;
+ struct GUID server_guid;
+ uint16_t security_mode;
+ uint16_t dialect;
+
+ status = smb2cli_ioctl_recv(subreq, state,
+ &state->out_input_buffer,
+ &state->out_output_buffer);
+ TALLOC_FREE(subreq);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_FILE_CLOSED)) {
+ /*
+ * The response was signed, but not supported
+ *
+ * Older Windows and Samba releases return
+ * NT_STATUS_FILE_CLOSED.
+ */
+ tevent_req_done(req);
+ return;
+ }
+ if (NT_STATUS_EQUAL(status, NT_STATUS_INVALID_DEVICE_REQUEST)) {
+ /*
+ * The response was signed, but not supported
+ *
+ * This is returned by the NTVFS based Samba 4.x file server
+ * for file shares.
+ */
+ tevent_req_done(req);
+ return;
+ }
+ if (NT_STATUS_EQUAL(status, NT_STATUS_FS_DRIVER_REQUIRED)) {
+ /*
+ * The response was signed, but not supported
+ *
+ * This is returned by the NTVFS based Samba 4.x file server
+ * for ipc shares.
+ */
+ tevent_req_done(req);
+ return;
+ }
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_SUPPORTED)) {
+ /*
+ * The response was signed, but not supported
+ *
+ * This might be returned by older Windows versions or by
+ * NetApp SMB server implementations.
+ *
+ * See
+ *
+ * https://blogs.msdn.microsoft.com/openspecification/2012/06/28/smb3-secure-dialect-negotiation/
+ *
+ */
+ tevent_req_done(req);
+ return;
+ }
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ if (state->out_output_buffer.length != 24) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE);
+ return;
+ }
+
+ buf = state->out_output_buffer.data;
+
+ capabilities = IVAL(buf, 0);
+ guid_blob = data_blob_const(buf + 4, 16);
+ status = GUID_from_data_blob(&guid_blob, &server_guid);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ security_mode = CVAL(buf, 20);
+ dialect = SVAL(buf, 22);
+
+ if (capabilities != state->conn->smb2.server.capabilities) {
+ tevent_req_nterror(req, NT_STATUS_ACCESS_DENIED);
+ return;
+ }
+
+ if (!GUID_equal(&server_guid, &state->conn->smb2.server.guid)) {
+ tevent_req_nterror(req, NT_STATUS_ACCESS_DENIED);
+ return;
+ }
+
+ if (security_mode != state->conn->smb2.server.security_mode) {
+ tevent_req_nterror(req, NT_STATUS_ACCESS_DENIED);
+ return;
+ }
+
+ if (dialect != state->dialect) {
+ tevent_req_nterror(req, NT_STATUS_ACCESS_DENIED);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+NTSTATUS smb2cli_validate_negotiate_info_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+static int smbXcli_session_destructor(struct smbXcli_session *session)
+{
+ if (session->conn == NULL) {
+ return 0;
+ }
+
+ DLIST_REMOVE(session->conn->sessions, session);
+ return 0;
+}
+
+struct smbXcli_session *smbXcli_session_create(TALLOC_CTX *mem_ctx,
+ struct smbXcli_conn *conn)
+{
+ struct smbXcli_session *session;
+
+ session = talloc_zero(mem_ctx, struct smbXcli_session);
+ if (session == NULL) {
+ return NULL;
+ }
+ session->smb2 = talloc_zero(session, struct smb2cli_session);
+ if (session->smb2 == NULL) {
+ talloc_free(session);
+ return NULL;
+ }
+ talloc_set_destructor(session, smbXcli_session_destructor);
+
+ DLIST_ADD_END(conn->sessions, session);
+ session->conn = conn;
+
+ memcpy(session->smb2_channel.preauth_sha512,
+ conn->smb2.preauth_sha512,
+ sizeof(session->smb2_channel.preauth_sha512));
+
+ return session;
+}
+
+struct smbXcli_session *smbXcli_session_copy(TALLOC_CTX *mem_ctx,
+ struct smbXcli_session *src)
+{
+ struct smbXcli_session *session;
+
+ session = talloc_zero(mem_ctx, struct smbXcli_session);
+ if (session == NULL) {
+ return NULL;
+ }
+ session->smb2 = talloc_zero(session, struct smb2cli_session);
+ if (session->smb2 == NULL) {
+ talloc_free(session);
+ return NULL;
+ }
+
+ session->conn = src->conn;
+ *session->smb2 = *src->smb2;
session->smb2_channel = src->smb2_channel;
session->disconnect_expired = src->disconnect_expired;
- DLIST_ADD_END(src->conn->sessions, session, struct smbXcli_session *);
+ DLIST_ADD_END(src->conn->sessions, session);
talloc_set_destructor(session, smbXcli_session_destructor);
return session;
}
+bool smbXcli_session_is_guest(struct smbXcli_session *session)
+{
+ if (session == NULL) {
+ return false;
+ }
+
+ if (session->conn == NULL) {
+ return false;
+ }
+
+ if (session->conn->mandatory_signing) {
+ return false;
+ }
+
+ if (session->conn->protocol >= PROTOCOL_SMB2_02) {
+ if (session->smb2->session_flags & SMB2_SESSION_FLAG_IS_GUEST) {
+ return true;
+ }
+ return false;
+ }
+
+ if (session->smb1.action & SMB_SETUP_GUEST) {
+ return true;
+ }
+
+ return false;
+}
+
+bool smbXcli_session_is_authenticated(struct smbXcli_session *session)
+{
+ const DATA_BLOB *application_key;
+
+ if (session == NULL) {
+ return false;
+ }
+
+ if (session->conn == NULL) {
+ return false;
+ }
+
+ /*
+ * If we have an application key we had a session key negotiated
+ * at auth time.
+ */
+ if (session->conn->protocol >= PROTOCOL_SMB2_02) {
+ application_key = &session->smb2->application_key;
+ } else {
+ application_key = &session->smb1.application_key;
+ }
+
+ if (application_key->length == 0) {
+ return false;
+ }
+
+ return true;
+}
NTSTATUS smbXcli_session_application_key(struct smbXcli_session *session,
TALLOC_CTX *mem_ctx,
session->smb1.session_id = session_id;
}
+void smb1cli_session_set_action(struct smbXcli_session *session,
+ uint16_t action)
+{
+ session->smb1.action = action;
+}
+
NTSTATUS smb1cli_session_set_session_key(struct smbXcli_session *session,
const DATA_BLOB _session_key)
{
if (conn->mandatory_signing) {
security_mode |= SMB2_NEGOTIATE_SIGNING_REQUIRED;
}
+ if (session->smb2->should_sign) {
+ security_mode |= SMB2_NEGOTIATE_SIGNING_REQUIRED;
+ }
return security_mode;
}
session->smb2->channel_sequence += 1;
}
+uint16_t smb2cli_session_reset_channel_sequence(struct smbXcli_session *session,
+ uint16_t channel_sequence)
+{
+ uint16_t prev_cs;
+
+ prev_cs = session->smb2->channel_sequence;
+ session->smb2->channel_sequence = channel_sequence;
+
+ return prev_cs;
+}
+
+uint16_t smb2cli_session_current_channel_sequence(struct smbXcli_session *session)
+{
+ return session->smb2->channel_sequence;
+}
+
+void smb2cli_session_start_replay(struct smbXcli_session *session)
+{
+ session->smb2->replay_active = true;
+}
+
+void smb2cli_session_stop_replay(struct smbXcli_session *session)
+{
+ session->smb2->replay_active = false;
+}
+
+NTSTATUS smb2cli_session_update_preauth(struct smbXcli_session *session,
+ const struct iovec *iov)
+{
+ struct hc_sha512state sctx;
+ size_t i;
+
+ if (session->conn == NULL) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ if (session->conn->protocol < PROTOCOL_SMB3_10) {
+ return NT_STATUS_OK;
+ }
+
+ if (session->smb2_channel.signing_key.length != 0) {
+ return NT_STATUS_OK;
+ }
+
+ samba_SHA512_Init(&sctx);
+ samba_SHA512_Update(&sctx, session->smb2_channel.preauth_sha512,
+ sizeof(session->smb2_channel.preauth_sha512));
+ for (i = 0; i < 3; i++) {
+ samba_SHA512_Update(&sctx, iov[i].iov_base, iov[i].iov_len);
+ }
+ samba_SHA512_Final(session->smb2_channel.preauth_sha512, &sctx);
+
+ return NT_STATUS_OK;
+}
+
NTSTATUS smb2cli_session_set_session_key(struct smbXcli_session *session,
const DATA_BLOB _session_key,
const struct iovec *recv_iov)
{
struct smbXcli_conn *conn = session->conn;
- uint16_t no_sign_flags;
+ uint16_t no_sign_flags = 0;
uint8_t session_key[16];
+ bool check_signature = true;
+ uint32_t hdr_flags;
NTSTATUS status;
+ struct _derivation {
+ DATA_BLOB label;
+ DATA_BLOB context;
+ };
+ struct {
+ struct _derivation signing;
+ struct _derivation encryption;
+ struct _derivation decryption;
+ struct _derivation application;
+ } derivation = { };
+ size_t nonce_size = 0;
if (conn == NULL) {
return NT_STATUS_INVALID_PARAMETER_MIX;
}
- no_sign_flags = SMB2_SESSION_FLAG_IS_GUEST | SMB2_SESSION_FLAG_IS_NULL;
+ if (recv_iov[0].iov_len != SMB2_HDR_BODY) {
+ return NT_STATUS_INVALID_PARAMETER_MIX;
+ }
+
+ if (!conn->mandatory_signing) {
+ /*
+ * only allow guest sessions without
+ * mandatory signing.
+ *
+ * If we try an authentication with username != ""
+ * and the server let us in without verifying the
+ * password we don't have a negotiated session key
+ * for signing.
+ */
+ no_sign_flags = SMB2_SESSION_FLAG_IS_GUEST;
+ }
if (session->smb2->session_flags & no_sign_flags) {
session->smb2->should_sign = false;
return NT_STATUS_INVALID_PARAMETER_MIX;
}
+ if (conn->protocol >= PROTOCOL_SMB3_10) {
+ struct _derivation *d;
+ DATA_BLOB p;
+
+ p = data_blob_const(session->smb2_channel.preauth_sha512,
+ sizeof(session->smb2_channel.preauth_sha512));
+
+ d = &derivation.signing;
+ d->label = data_blob_string_const_null("SMBSigningKey");
+ d->context = p;
+
+ d = &derivation.encryption;
+ d->label = data_blob_string_const_null("SMBC2SCipherKey");
+ d->context = p;
+
+ d = &derivation.decryption;
+ d->label = data_blob_string_const_null("SMBS2CCipherKey");
+ d->context = p;
+
+ d = &derivation.application;
+ d->label = data_blob_string_const_null("SMBAppKey");
+ d->context = p;
+
+ } else if (conn->protocol >= PROTOCOL_SMB2_24) {
+ struct _derivation *d;
+
+ d = &derivation.signing;
+ d->label = data_blob_string_const_null("SMB2AESCMAC");
+ d->context = data_blob_string_const_null("SmbSign");
+
+ d = &derivation.encryption;
+ d->label = data_blob_string_const_null("SMB2AESCCM");
+ d->context = data_blob_string_const_null("ServerIn ");
+
+ d = &derivation.decryption;
+ d->label = data_blob_string_const_null("SMB2AESCCM");
+ d->context = data_blob_string_const_null("ServerOut");
+
+ d = &derivation.application;
+ d->label = data_blob_string_const_null("SMB2APP");
+ d->context = data_blob_string_const_null("SmbRpc");
+ }
+
ZERO_STRUCT(session_key);
memcpy(session_key, _session_key.data,
MIN(_session_key.length, sizeof(session_key)));
}
if (conn->protocol >= PROTOCOL_SMB2_24) {
- const DATA_BLOB label = data_blob_string_const_null("SMB2AESCMAC");
- const DATA_BLOB context = data_blob_string_const_null("SmbSign");
+ struct _derivation *d = &derivation.signing;
smb2_key_derivation(session_key, sizeof(session_key),
- label.data, label.length,
- context.data, context.length,
+ d->label.data, d->label.length,
+ d->context.data, d->context.length,
session->smb2->signing_key.data);
}
}
if (conn->protocol >= PROTOCOL_SMB2_24) {
- const DATA_BLOB label = data_blob_string_const_null("SMB2AESCCM");
- const DATA_BLOB context = data_blob_string_const_null("ServerIn ");
+ struct _derivation *d = &derivation.encryption;
smb2_key_derivation(session_key, sizeof(session_key),
- label.data, label.length,
- context.data, context.length,
+ d->label.data, d->label.length,
+ d->context.data, d->context.length,
session->smb2->encryption_key.data);
}
}
if (conn->protocol >= PROTOCOL_SMB2_24) {
- const DATA_BLOB label = data_blob_string_const_null("SMB2AESCCM");
- const DATA_BLOB context = data_blob_string_const_null("ServerOut");
+ struct _derivation *d = &derivation.decryption;
smb2_key_derivation(session_key, sizeof(session_key),
- label.data, label.length,
- context.data, context.length,
+ d->label.data, d->label.length,
+ d->context.data, d->context.length,
session->smb2->decryption_key.data);
}
}
if (conn->protocol >= PROTOCOL_SMB2_24) {
- const DATA_BLOB label = data_blob_string_const_null("SMB2APP");
- const DATA_BLOB context = data_blob_string_const_null("SmbRpc");
+ struct _derivation *d = &derivation.application;
smb2_key_derivation(session_key, sizeof(session_key),
- label.data, label.length,
- context.data, context.length,
+ d->label.data, d->label.length,
+ d->context.data, d->context.length,
session->smb2->application_key.data);
}
ZERO_STRUCT(session_key);
return NT_STATUS_NO_MEMORY;
}
- status = smb2_signing_check_pdu(session->smb2_channel.signing_key,
- session->conn->protocol,
- recv_iov, 3);
- if (!NT_STATUS_IS_OK(status)) {
- return status;
+ check_signature = conn->mandatory_signing;
+
+ hdr_flags = IVAL(recv_iov[0].iov_base, SMB2_HDR_FLAGS);
+ if (hdr_flags & SMB2_HDR_FLAG_SIGNED) {
+ /*
+ * Sadly some vendors don't sign the
+ * final SMB2 session setup response
+ *
+ * At least Windows and Samba are always doing this
+ * if there's a session key available.
+ *
+ * We only check the signature if it's mandatory
+ * or SMB2_HDR_FLAG_SIGNED is provided.
+ */
+ check_signature = true;
+ }
+
+ if (conn->protocol >= PROTOCOL_SMB3_10) {
+ check_signature = true;
+ }
+
+ if (check_signature) {
+ status = smb2_signing_check_pdu(session->smb2_channel.signing_key,
+ session->conn->protocol,
+ recv_iov, 3);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
}
session->smb2->should_sign = false;
session->smb2->should_encrypt = false;
}
- if (!(conn->smb2.server.capabilities & SMB2_CAP_ENCRYPTION)) {
+ if (conn->smb2.server.cipher == 0) {
session->smb2->should_encrypt = false;
}
- generate_random_buffer((uint8_t *)&session->smb2->nonce_high,
- sizeof(session->smb2->nonce_high));
- session->smb2->nonce_low = 1;
+ /*
+ * CCM and GCM algorithms must never have their
+ * nonce wrap, or the security of the whole
+ * communication and the keys is destroyed.
+ * We must drop the connection once we have
+ * transfered too much data.
+ *
+ * NOTE: We assume nonces greater than 8 bytes.
+ */
+ generate_random_buffer((uint8_t *)&session->smb2->nonce_high_random,
+ sizeof(session->smb2->nonce_high_random));
+ switch (conn->smb2.server.cipher) {
+ case SMB2_ENCRYPTION_AES128_CCM:
+ nonce_size = AES_CCM_128_NONCE_SIZE;
+ break;
+ case SMB2_ENCRYPTION_AES128_GCM:
+ nonce_size = AES_GCM_128_IV_SIZE;
+ break;
+ default:
+ nonce_size = 0;
+ break;
+ }
+ session->smb2->nonce_high_max = SMB2_NONCE_HIGH_MAX(nonce_size);
+ session->smb2->nonce_high = 0;
+ session->smb2->nonce_low = 0;
return NT_STATUS_OK;
}
}
talloc_set_destructor(session2, smbXcli_session_destructor);
- DLIST_ADD_END(conn->sessions, session2, struct smbXcli_session *);
+ DLIST_ADD_END(conn->sessions, session2);
session2->conn = conn;
+ memcpy(session2->smb2_channel.preauth_sha512,
+ conn->smb2.preauth_sha512,
+ sizeof(session2->smb2_channel.preauth_sha512));
+
*_session2 = session2;
return NT_STATUS_OK;
}
struct smbXcli_conn *conn = session->conn;
uint8_t channel_key[16];
NTSTATUS status;
+ struct _derivation {
+ DATA_BLOB label;
+ DATA_BLOB context;
+ };
+ struct {
+ struct _derivation signing;
+ } derivation = { };
if (conn == NULL) {
return NT_STATUS_INVALID_PARAMETER_MIX;
return NT_STATUS_INVALID_PARAMETER_MIX;
}
+ if (conn->protocol >= PROTOCOL_SMB3_10) {
+ struct _derivation *d;
+ DATA_BLOB p;
+
+ p = data_blob_const(session->smb2_channel.preauth_sha512,
+ sizeof(session->smb2_channel.preauth_sha512));
+
+ d = &derivation.signing;
+ d->label = data_blob_string_const_null("SMBSigningKey");
+ d->context = p;
+ } else if (conn->protocol >= PROTOCOL_SMB2_24) {
+ struct _derivation *d;
+
+ d = &derivation.signing;
+ d->label = data_blob_string_const_null("SMB2AESCMAC");
+ d->context = data_blob_string_const_null("SmbSign");
+ }
+
ZERO_STRUCT(channel_key);
memcpy(channel_key, _channel_key.data,
MIN(_channel_key.length, sizeof(channel_key)));
}
if (conn->protocol >= PROTOCOL_SMB2_24) {
- const DATA_BLOB label = data_blob_string_const_null("SMB2AESCMAC");
- const DATA_BLOB context = data_blob_string_const_null("SmbSign");
+ struct _derivation *d = &derivation.signing;
smb2_key_derivation(channel_key, sizeof(channel_key),
- label.data, label.length,
- context.data, context.length,
+ d->label.data, d->label.length,
+ d->context.data, d->context.length,
session->smb2_channel.signing_key.data);
}
ZERO_STRUCT(channel_key);
NTSTATUS smb2cli_session_encryption_on(struct smbXcli_session *session)
{
+ if (!session->smb2->should_sign) {
+ /*
+ * We need required signing on the session
+ * in order to prevent man in the middle attacks.
+ */
+ return NT_STATUS_INVALID_PARAMETER_MIX;
+ }
+
if (session->smb2->should_encrypt) {
return NT_STATUS_OK;
}
return NT_STATUS_NOT_SUPPORTED;
}
- if (!(session->conn->smb2.server.capabilities & SMB2_CAP_ENCRYPTION)) {
+ if (session->conn->smb2.server.cipher == 0) {
return NT_STATUS_NOT_SUPPORTED;
}
return tcon;
}
+/*
+ * Return a deep structure copy of a struct smbXcli_tcon *
+ */
+
+struct smbXcli_tcon *smbXcli_tcon_copy(TALLOC_CTX *mem_ctx,
+ const struct smbXcli_tcon *tcon_in)
+{
+ struct smbXcli_tcon *tcon;
+
+ tcon = talloc_memdup(mem_ctx, tcon_in, sizeof(struct smbXcli_tcon));
+ if (tcon == NULL) {
+ return NULL;
+ }
+
+ /* Deal with the SMB1 strings. */
+ if (tcon_in->smb1.service != NULL) {
+ tcon->smb1.service = talloc_strdup(tcon, tcon_in->smb1.service);
+ if (tcon->smb1.service == NULL) {
+ TALLOC_FREE(tcon);
+ return NULL;
+ }
+ }
+ if (tcon->smb1.fs_type != NULL) {
+ tcon->smb1.fs_type = talloc_strdup(tcon, tcon_in->smb1.fs_type);
+ if (tcon->smb1.fs_type == NULL) {
+ TALLOC_FREE(tcon);
+ return NULL;
+ }
+ }
+ return tcon;
+}
+
+void smbXcli_tcon_set_fs_attributes(struct smbXcli_tcon *tcon,
+ uint32_t fs_attributes)
+{
+ tcon->fs_attributes = fs_attributes;
+}
+
+uint32_t smbXcli_tcon_get_fs_attributes(struct smbXcli_tcon *tcon)
+{
+ return tcon->fs_attributes;
+}
+
+bool smbXcli_tcon_is_dfs_share(struct smbXcli_tcon *tcon)
+{
+ if (tcon == NULL) {
+ return false;
+ }
+
+ if (tcon->is_smb1) {
+ if (tcon->smb1.optional_support & SMB_SHARE_IN_DFS) {
+ return true;
+ }
+
+ return false;
+ }
+
+ if (tcon->smb2.capabilities & SMB2_SHARE_CAP_DFS) {
+ return true;
+ }
+
+ return false;
+}
+
uint16_t smb1cli_tcon_current_id(struct smbXcli_tcon *tcon)
{
return tcon->smb1.tcon_id;
void smb1cli_tcon_set_id(struct smbXcli_tcon *tcon, uint16_t tcon_id)
{
+ tcon->is_smb1 = true;
tcon->smb1.tcon_id = tcon_id;
}
const char *service,
const char *fs_type)
{
+ tcon->is_smb1 = true;
+ tcon->fs_attributes = 0;
tcon->smb1.tcon_id = tcon_id;
tcon->smb1.optional_support = optional_support;
tcon->smb1.maximal_access = maximal_access;
return tcon->smb2.tcon_id;
}
+void smb2cli_tcon_set_id(struct smbXcli_tcon *tcon, uint32_t tcon_id)
+{
+ tcon->smb2.tcon_id = tcon_id;
+}
+
uint32_t smb2cli_tcon_capabilities(struct smbXcli_tcon *tcon)
{
return tcon->smb2.capabilities;
}
+uint32_t smb2cli_tcon_flags(struct smbXcli_tcon *tcon)
+{
+ return tcon->smb2.flags;
+}
+
void smb2cli_tcon_set_values(struct smbXcli_tcon *tcon,
struct smbXcli_session *session,
uint32_t tcon_id,
uint32_t capabilities,
uint32_t maximal_access)
{
+ tcon->is_smb1 = false;
+ tcon->fs_attributes = 0;
tcon->smb2.tcon_id = tcon_id;
tcon->smb2.type = type;
tcon->smb2.flags = flags;
tcon->smb2.capabilities = capabilities;
tcon->smb2.maximal_access = maximal_access;
+ tcon->smb2.should_sign = false;
tcon->smb2.should_encrypt = false;
if (session == NULL) {
return;
}
+ tcon->smb2.should_sign = session->smb2->should_sign;
tcon->smb2.should_encrypt = session->smb2->should_encrypt;
if (flags & SMB2_SHAREFLAG_ENCRYPT_DATA) {
tcon->smb2.should_encrypt = true;
}
}
+
+void smb2cli_tcon_should_sign(struct smbXcli_tcon *tcon,
+ bool should_sign)
+{
+ tcon->smb2.should_sign = should_sign;
+}
+
+bool smb2cli_tcon_is_signing_on(struct smbXcli_tcon *tcon)
+{
+ if (tcon->smb2.should_encrypt) {
+ return true;
+ }
+
+ return tcon->smb2.should_sign;
+}
+
+void smb2cli_tcon_should_encrypt(struct smbXcli_tcon *tcon,
+ bool should_encrypt)
+{
+ tcon->smb2.should_encrypt = should_encrypt;
+}
+
+bool smb2cli_tcon_is_encryption_on(struct smbXcli_tcon *tcon)
+{
+ return tcon->smb2.should_encrypt;
+}
+
+void smb2cli_conn_set_mid(struct smbXcli_conn *conn, uint64_t mid)
+{
+ conn->smb2.mid = mid;
+}
+
+uint64_t smb2cli_conn_get_mid(struct smbXcli_conn *conn)
+{
+ return conn->smb2.mid;
+}