#include "smbprofile.h"
#include "../lib/util/bitmap.h"
#include "../librpc/gen_ndr/krb5pac.h"
+#include "lib/util/iov_buf.h"
#include "auth.h"
+#include "lib/crypto/sha512.h"
static void smbd_smb2_connection_handler(struct tevent_context *ev,
struct tevent_fd *fde,
uint16_t flags,
void *private_data);
-static NTSTATUS smbd_smb2_flush_send_queue(struct smbd_server_connection *sconn);
+static NTSTATUS smbd_smb2_flush_send_queue(struct smbXsrv_connection *xconn);
static const struct smbd_smb2_dispatch_table {
uint16_t opcode;
bool as_root;
uint16_t fileid_ofs;
bool allow_invalid_fileid;
+ bool modify;
} smbd_smb2_table[] = {
#define _OP(o) .opcode = o, .name = #o
{
.need_session = true,
.need_tcon = true,
.fileid_ofs = 0x10,
+ .modify = true,
},{
_OP(SMB2_OP_LOCK),
.need_session = true,
.need_tcon = true,
.fileid_ofs = 0x08,
.allow_invalid_fileid = true,
+ .modify = true,
},{
_OP(SMB2_OP_CANCEL),
.as_root = true,
_OP(SMB2_OP_KEEPALIVE),
.as_root = true,
},{
- _OP(SMB2_OP_FIND),
+ _OP(SMB2_OP_QUERY_DIRECTORY),
.need_session = true,
.need_tcon = true,
.fileid_ofs = 0x08,
.need_session = true,
.need_tcon = true,
.fileid_ofs = 0x10,
+ .modify = true,
},{
_OP(SMB2_OP_BREAK),
.need_session = true,
return true;
}
-static NTSTATUS smbd_initialize_smb2(struct smbd_server_connection *sconn)
+static NTSTATUS smbd_initialize_smb2(struct smbXsrv_connection *xconn,
+ uint64_t expected_seq_low)
{
- struct smbXsrv_connection *xconn = sconn->conn;
-
TALLOC_FREE(xconn->transport.fde);
- xconn->smb2.credits.seq_low = 0;
+ xconn->smb2.credits.seq_low = expected_seq_low;
xconn->smb2.credits.seq_range = 1;
xconn->smb2.credits.granted = 1;
xconn->smb2.credits.max = lp_smb2_max_credits();
return NT_STATUS_NO_MEMORY;
}
- xconn->transport.fde = tevent_add_fd(sconn->ev_ctx,
- sconn,
+ xconn->transport.fde = tevent_add_fd(xconn->ev_ctx,
+ xconn,
xconn->transport.sock,
TEVENT_FD_READ,
smbd_smb2_connection_handler,
- sconn);
+ xconn);
if (xconn->transport.fde == NULL) {
return NT_STATUS_NO_MEMORY;
}
buf[3] = (len)&0xFF; \
} while (0)
-static void smb2_setup_nbt_length(struct iovec *vector, int count)
+static bool smb2_setup_nbt_length(struct iovec *vector, int count)
{
- size_t len = 0;
- int i;
+ ssize_t len;
- for (i=1; i < count; i++) {
- len += vector[i].iov_len;
+ if (count == 0) {
+ return false;
+ }
+
+ len = iov_buflen(vector+1, count-1);
+
+ if ((len == -1) || (len > 0xFFFFFF)) {
+ return false;
}
_smb2_setlen(vector[0].iov_base, len);
+ return true;
}
static int smbd_smb2_request_destructor(struct smbd_smb2_request *req)
return 0;
}
+void smb2_request_set_async_internal(struct smbd_smb2_request *req,
+ bool async_internal)
+{
+ req->async_internal = async_internal;
+}
+
static struct smbd_smb2_request *smbd_smb2_request_allocate(TALLOC_CTX *mem_ctx)
{
TALLOC_CTX *mem_pool;
return req;
}
-static NTSTATUS smbd_smb2_inbuf_parse_compound(struct smbXsrv_connection *conn,
+static NTSTATUS smbd_smb2_inbuf_parse_compound(struct smbXsrv_connection *xconn,
NTTIME now,
uint8_t *buf,
size_t buflen,
NTSTATUS status;
size_t enc_len;
- if (conn->protocol < PROTOCOL_SMB2_24) {
+ if (xconn->protocol < PROTOCOL_SMB2_24) {
DEBUG(10, ("Got SMB2_TRANSFORM header, "
"but dialect[0x%04X] is used\n",
- conn->smb2.server.dialect));
+ xconn->smb2.server.dialect));
goto inval;
}
- if (!(conn->smb2.server.capabilities & SMB2_CAP_ENCRYPTION)) {
+ if (xconn->smb2.server.cipher == 0) {
DEBUG(10, ("Got SMB2_TRANSFORM header, "
"but not negotiated "
"client[0x%08X] server[0x%08X]\n",
- conn->smb2.client.capabilities,
- conn->smb2.server.capabilities));
+ xconn->smb2.client.capabilities,
+ xconn->smb2.server.capabilities));
goto inval;
}
goto inval;
}
- status = smb2srv_session_lookup(conn, uid, now, &s);
+ status = smb2srv_session_lookup_conn(xconn, uid, now,
+ &s);
if (s == NULL) {
DEBUG(1, ("invalid session[%llu] in "
"SMB2_TRANSFORM header\n",
tf_iov[1].iov_len = enc_len;
status = smb2_signing_decrypt_pdu(s->global->decryption_key,
- conn->protocol,
+ xconn->smb2.server.cipher,
tf_iov, 2);
if (!NT_STATUS_IS_OK(status)) {
TALLOC_FREE(iov_alloc);
return NT_STATUS_INVALID_PARAMETER;
}
-static NTSTATUS smbd_smb2_request_create(struct smbd_server_connection *sconn,
- uint8_t *inbuf, size_t size,
+static NTSTATUS smbd_smb2_request_create(struct smbXsrv_connection *xconn,
+ const uint8_t *_inpdu, size_t size,
struct smbd_smb2_request **_req)
{
+ struct smbd_server_connection *sconn = xconn->client->sconn;
struct smbd_smb2_request *req;
uint32_t protocol_version;
+ uint8_t *inpdu = NULL;
const uint8_t *inhdr = NULL;
uint16_t cmd;
uint32_t next_command_ofs;
NTSTATUS status;
NTTIME now;
- if (size < (4 + SMB2_HDR_BODY + 2)) {
+ if (size < (SMB2_HDR_BODY + 2)) {
DEBUG(0,("Invalid SMB2 packet length count %ld\n", (long)size));
return NT_STATUS_INVALID_PARAMETER;
}
- inhdr = inbuf + 4;
+ inhdr = _inpdu;
protocol_version = IVAL(inhdr, SMB2_HDR_PROTOCOL_ID);
if (protocol_version != SMB2_MAGIC) {
return NT_STATUS_INVALID_PARAMETER;
}
- req = smbd_smb2_request_allocate(sconn);
+ req = smbd_smb2_request_allocate(xconn);
if (req == NULL) {
return NT_STATUS_NO_MEMORY;
}
req->sconn = sconn;
+ req->xconn = xconn;
- talloc_steal(req, inbuf);
+ inpdu = talloc_memdup(req, _inpdu, size);
+ if (inpdu == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
req->request_time = timeval_current();
now = timeval_to_nttime(&req->request_time);
- status = smbd_smb2_inbuf_parse_compound(sconn->conn,
+ status = smbd_smb2_inbuf_parse_compound(xconn,
now,
- inbuf + NBT_HDR_SIZE,
- size - NBT_HDR_SIZE,
+ inpdu,
+ size,
req, &req->in.vector,
&req->in.vector_count);
if (!NT_STATUS_IS_OK(status)) {
return NT_STATUS_OK;
}
-static bool smb2_validate_sequence_number(struct smbd_server_connection *sconn,
+static bool smb2_validate_sequence_number(struct smbXsrv_connection *xconn,
uint64_t message_id, uint64_t seq_id)
{
- struct smbXsrv_connection *xconn = sconn->conn;
struct bitmap *credits_bm = xconn->smb2.credits.bitmap;
unsigned int offset;
uint64_t seq_tmp;
return true;
}
-static bool smb2_validate_message_id(struct smbd_server_connection *sconn,
- const uint8_t *inhdr)
+static bool smb2_validate_message_id(struct smbXsrv_connection *xconn,
+ const uint8_t *inhdr)
{
- struct smbXsrv_connection *xconn = sconn->conn;
uint64_t message_id = BVAL(inhdr, SMB2_HDR_MESSAGE_ID);
uint16_t opcode = SVAL(inhdr, SMB2_HDR_OPCODE);
uint16_t credit_charge = 1;
credit_charge,
(unsigned long long)id));
- ok = smb2_validate_sequence_number(sconn, message_id, id);
+ ok = smb2_validate_sequence_number(xconn, message_id, id);
if (!ok) {
return false;
}
return NT_STATUS_INVALID_PARAMETER;
}
- if (!smb2_validate_message_id(req->sconn, inhdr)) {
+ if (!smb2_validate_message_id(req->xconn, inhdr)) {
return NT_STATUS_INVALID_PARAMETER;
}
}
return NT_STATUS_OK;
}
-static void smb2_set_operation_credit(struct smbd_server_connection *sconn,
- const struct iovec *in_vector,
- struct iovec *out_vector)
+static void smb2_set_operation_credit(struct smbXsrv_connection *xconn,
+ const struct iovec *in_vector,
+ struct iovec *out_vector)
{
- struct smbXsrv_connection *xconn = sconn->conn;
const uint8_t *inhdr = (const uint8_t *)in_vector->iov_base;
uint8_t *outhdr = (uint8_t *)out_vector->iov_base;
uint16_t credit_charge = 1;
cmd = SVAL(inhdr, SMB2_HDR_OPCODE);
credits_requested = SVAL(inhdr, SMB2_HDR_CREDIT);
+ credits_requested = MAX(credits_requested, 1);
out_flags = IVAL(outhdr, SMB2_HDR_FLAGS);
out_status = NT_STATUS(IVAL(outhdr, SMB2_HDR_STATUS));
SMB_ASSERT(xconn->smb2.credits.max >= xconn->smb2.credits.granted);
if (xconn->smb2.credits.max < credit_charge) {
- smbd_server_connection_terminate(sconn,
+ smbd_server_connection_terminate(xconn,
"client error: credit charge > max credits\n");
return;
}
* credits on the final response.
*/
credits_granted = 0;
- } else if (credits_requested > 0) {
+ } else {
+ uint16_t additional_possible =
+ xconn->smb2.credits.max - credit_charge;
uint16_t additional_max = 0;
uint16_t additional_credits = credits_requested - 1;
break;
}
+ additional_max = MIN(additional_max, additional_possible);
additional_credits = MIN(additional_credits, additional_max);
credits_granted = credit_charge + additional_credits;
- } else if (xconn->smb2.credits.granted == 0) {
- /*
- * Make sure the client has always at least one credit
- */
- credits_granted = 1;
}
/*
struct iovec *outhdr_v = SMBD_SMB2_IDX_HDR_IOV(outreq,out,idx);
uint8_t *outhdr = (uint8_t *)outhdr_v->iov_base;
- smb2_set_operation_credit(outreq->sconn, inhdr_v, outhdr_v);
+ smb2_set_operation_credit(outreq->xconn, inhdr_v, outhdr_v);
/* To match Windows, count up what we
just granted. */
static NTSTATUS smbd_smb2_request_setup_out(struct smbd_smb2_request *req)
{
+ struct smbXsrv_connection *xconn = req->xconn;
TALLOC_CTX *mem_ctx;
struct iovec *vector;
int count;
int idx;
+ bool ok;
count = req->in.vector_count;
if (count <= ARRAY_SIZE(req->out._vector)) {
req->out.vector_count = count;
/* setup the length of the NBT packet */
- smb2_setup_nbt_length(req->out.vector, req->out.vector_count);
+ ok = smb2_setup_nbt_length(req->out.vector, req->out.vector_count);
+ if (!ok) {
+ return NT_STATUS_INVALID_PARAMETER_MIX;
+ }
- DLIST_ADD_END(req->sconn->smb2.requests, req, struct smbd_smb2_request *);
+ DLIST_ADD_END(xconn->smb2.requests, req);
return NT_STATUS_OK;
}
-void smbd_server_connection_terminate_ex(struct smbd_server_connection *sconn,
+void smbd_server_connection_terminate_ex(struct smbXsrv_connection *xconn,
const char *reason,
const char *location)
{
- DEBUG(10,("smbd_server_connection_terminate_ex: reason[%s] at %s\n",
- reason, location));
+ struct smbXsrv_client *client = xconn->client;
+
+ DEBUG(10,("smbd_server_connection_terminate_ex: conn[%s] reason[%s] at %s\n",
+ smbXsrv_connection_dbg(xconn), reason, location));
+
+ if (client->connections->next != NULL) {
+ /* TODO: cancel pending requests */
+ DLIST_REMOVE(client->connections, xconn);
+ TALLOC_FREE(xconn);
+ return;
+ }
+
+ /*
+ * The last connection was disconnected
+ */
exit_server_cleanly(reason);
}
struct iovec *outvec = NULL;
int count = req->out.vector_count;
int i;
+ bool ok;
- newreq = smbd_smb2_request_allocate(req->sconn);
+ newreq = smbd_smb2_request_allocate(req->xconn);
if (!newreq) {
return NULL;
}
newreq->sconn = req->sconn;
+ newreq->xconn = req->xconn;
newreq->session = req->session;
newreq->do_encryption = req->do_encryption;
newreq->do_signing = req->do_signing;
return NULL;
}
- smb2_setup_nbt_length(newreq->out.vector,
- newreq->out.vector_count);
+ ok = smb2_setup_nbt_length(newreq->out.vector,
+ newreq->out.vector_count);
+ if (!ok) {
+ TALLOC_FREE(newreq);
+ return NULL;
+ }
return newreq;
}
static NTSTATUS smb2_send_async_interim_response(const struct smbd_smb2_request *req)
{
- struct smbd_server_connection *sconn = req->sconn;
- struct smbXsrv_connection *conn = req->sconn->conn;
+ struct smbXsrv_connection *xconn = req->xconn;
int first_idx = 1;
struct iovec *firsttf = NULL;
struct iovec *outhdr_v = NULL;
uint8_t *outhdr = NULL;
struct smbd_smb2_request *nreq = NULL;
NTSTATUS status;
+ bool ok;
/* Create a new smb2 request we'll use
for the interim return. */
ones we'll be using for the async reply. */
nreq->out.vector_count -= SMBD_SMB2_NUM_IOV_PER_REQ;
- smb2_setup_nbt_length(nreq->out.vector,
- nreq->out.vector_count);
+ ok = smb2_setup_nbt_length(nreq->out.vector,
+ nreq->out.vector_count);
+ if (!ok) {
+ return NT_STATUS_INVALID_PARAMETER_MIX;
+ }
/* Step back to the previous reply. */
nreq->current_idx -= SMBD_SMB2_NUM_IOV_PER_REQ;
*/
if (firsttf->iov_len == SMB2_TF_HDR_SIZE) {
status = smb2_signing_encrypt_pdu(req->first_key,
- conn->protocol,
+ xconn->smb2.server.cipher,
firsttf,
nreq->out.vector_count - first_idx);
if (!NT_STATUS_IS_OK(status)) {
}
} else if (req->last_key.length > 0) {
status = smb2_signing_sign_pdu(req->last_key,
- conn->protocol,
+ xconn->protocol,
outhdr_v,
SMBD_SMB2_NUM_IOV_PER_REQ - 1);
if (!NT_STATUS_IS_OK(status)) {
nreq->queue_entry.mem_ctx = nreq;
nreq->queue_entry.vector = nreq->out.vector;
nreq->queue_entry.count = nreq->out.vector_count;
- DLIST_ADD_END(conn->smb2.send_queue, &nreq->queue_entry, NULL);
- conn->smb2.send_queue_len++;
+ DLIST_ADD_END(xconn->smb2.send_queue, &nreq->queue_entry);
+ xconn->smb2.send_queue_len++;
- status = smbd_smb2_flush_send_queue(sconn);
+ status = smbd_smb2_flush_send_queue(xconn);
if (!NT_STATUS_IS_OK(status)) {
return status;
}
}
struct smbd_smb2_request_pending_state {
- struct smbd_server_connection *sconn;
struct smbd_smb2_send_queue queue_entry;
uint8_t buf[NBT_HDR_SIZE + SMB2_TF_HDR_SIZE + SMB2_HDR_BODY + 0x08 + 1];
struct iovec vector[1 + SMBD_SMB2_NUM_IOV_PER_REQ];
return NT_STATUS_OK;
}
+ if (req->async_internal) {
+ /*
+ * An SMB2 request implementation wants to handle the request
+ * asynchronously "internally" while keeping synchronous
+ * behaviour for the SMB2 request. This means we don't send an
+ * interim response and we can allow processing of compound SMB2
+ * requests (cf the subsequent check) for all cases.
+ */
+ return NT_STATUS_OK;
+ }
+
if (req->in.vector_count > req->current_idx + SMBD_SMB2_NUM_IOV_PER_REQ) {
/*
- * We're trying to go async in a compound
- * request chain.
- * This is only allowed for opens that
- * cause an oplock break, otherwise it
- * is not allowed. See [MS-SMB2].pdf
- * note <194> on Section 3.3.5.2.7.
+ * We're trying to go async in a compound request
+ * chain. This is only allowed for opens that cause an
+ * oplock break or for the last operation in the
+ * chain, otherwise it is not allowed. See
+ * [MS-SMB2].pdf note <206> on Section 3.3.5.2.7.
*/
const uint8_t *inhdr = SMBD_SMB2_IN_HDR_PTR(req);
return NT_STATUS_OK;
}
+static DATA_BLOB smbd_smb2_signing_key(struct smbXsrv_session *session,
+ struct smbXsrv_connection *xconn)
+{
+ struct smbXsrv_channel_global0 *c = NULL;
+ NTSTATUS status;
+ DATA_BLOB key = data_blob_null;
+
+ status = smbXsrv_session_find_channel(session, xconn, &c);
+ if (NT_STATUS_IS_OK(status)) {
+ key = c->signing_key;
+ }
+
+ if (key.length == 0) {
+ key = session->global->signing_key;
+ }
+
+ return key;
+}
+
+static NTSTATUS smb2_get_new_nonce(struct smbXsrv_session *session,
+ uint64_t *new_nonce_high,
+ uint64_t *new_nonce_low)
+{
+ uint64_t nonce_high;
+ uint64_t nonce_low;
+
+ session->nonce_low += 1;
+ if (session->nonce_low == 0) {
+ session->nonce_low += 1;
+ session->nonce_high += 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 (session->nonce_high >= session->nonce_high_max) {
+ return NT_STATUS_ENCRYPTION_FAILED;
+ }
+
+ nonce_high = session->nonce_high_random;
+ nonce_high += session->nonce_high;
+ nonce_low = session->nonce_low;
+
+ *new_nonce_high = nonce_high;
+ *new_nonce_low = nonce_low;
+ return NT_STATUS_OK;
+}
+
static void smbd_smb2_request_pending_timer(struct tevent_context *ev,
struct tevent_timer *te,
struct timeval current_time,
struct smbd_smb2_request *req =
talloc_get_type_abort(private_data,
struct smbd_smb2_request);
- struct smbd_server_connection *sconn = req->sconn;
- struct smbXsrv_connection *xconn = sconn->conn;
+ struct smbXsrv_connection *xconn = req->xconn;
struct smbd_smb2_request_pending_state *state = NULL;
uint8_t *outhdr = NULL;
const uint8_t *inhdr = NULL;
uint64_t nonce_low = 0;
uint64_t async_id = 0;
NTSTATUS status;
+ bool ok;
TALLOC_FREE(req->async_te);
* of this fact sometime when refactoring. JRA.
*/
- state = talloc_zero(req->sconn, struct smbd_smb2_request_pending_state);
+ state = talloc_zero(req->xconn, struct smbd_smb2_request_pending_state);
if (state == NULL) {
- smbd_server_connection_terminate(req->sconn,
+ smbd_server_connection_terminate(xconn,
nt_errstr(NT_STATUS_NO_MEMORY));
return;
}
- state->sconn = req->sconn;
tf = state->buf + NBT_HDR_SIZE;
tf_len = SMB2_TF_HDR_SIZE;
dyn = body + 8;
if (req->do_encryption) {
- struct smbXsrv_session *x = req->session;
-
- nonce_high = x->nonce_high;
- nonce_low = x->nonce_low;
-
- x->nonce_low += 1;
- if (x->nonce_low == 0) {
- x->nonce_low += 1;
- x->nonce_high += 1;
+ status = smb2_get_new_nonce(req->session,
+ &nonce_high,
+ &nonce_low);
+ if (!NT_STATUS_IS_OK(status)) {
+ smbd_server_connection_terminate(xconn,
+ nt_errstr(status));
+ return;
}
}
state->vector[1+SMBD_SMB2_DYN_IOV_OFS].iov_base = dyn;
state->vector[1+SMBD_SMB2_DYN_IOV_OFS].iov_len = 1;
- smb2_setup_nbt_length(state->vector, 1 + SMBD_SMB2_NUM_IOV_PER_REQ);
+ ok = smb2_setup_nbt_length(state->vector,
+ 1 + SMBD_SMB2_NUM_IOV_PER_REQ);
+ if (!ok) {
+ smbd_server_connection_terminate(
+ xconn, nt_errstr(NT_STATUS_INTERNAL_ERROR));
+ return;
+ }
/* Ensure we correctly go through crediting. Grant
the credits now, and zero credits on the final
response. */
- smb2_set_operation_credit(req->sconn,
+ smb2_set_operation_credit(req->xconn,
SMBD_SMB2_IN_HDR_IOV(req),
&state->vector[1+SMBD_SMB2_HDR_IOV_OFS]);
DATA_BLOB encryption_key = x->global->encryption_key;
status = smb2_signing_encrypt_pdu(encryption_key,
- xconn->protocol,
+ xconn->smb2.server.cipher,
&state->vector[1+SMBD_SMB2_TF_IOV_OFS],
SMBD_SMB2_NUM_IOV_PER_REQ);
if (!NT_STATUS_IS_OK(status)) {
- smbd_server_connection_terminate(req->sconn,
+ smbd_server_connection_terminate(xconn,
nt_errstr(status));
return;
}
} else if (req->do_signing) {
struct smbXsrv_session *x = req->session;
- DATA_BLOB signing_key = x->global->channels[0].signing_key;
+ DATA_BLOB signing_key = smbd_smb2_signing_key(x, xconn);
status = smb2_signing_sign_pdu(signing_key,
xconn->protocol,
&state->vector[1+SMBD_SMB2_HDR_IOV_OFS],
SMBD_SMB2_NUM_IOV_PER_REQ - 1);
if (!NT_STATUS_IS_OK(status)) {
- smbd_server_connection_terminate(req->sconn,
+ smbd_server_connection_terminate(xconn,
nt_errstr(status));
return;
}
state->queue_entry.mem_ctx = state;
state->queue_entry.vector = state->vector;
state->queue_entry.count = ARRAY_SIZE(state->vector);
- DLIST_ADD_END(xconn->smb2.send_queue, &state->queue_entry, NULL);
+ DLIST_ADD_END(xconn->smb2.send_queue, &state->queue_entry);
xconn->smb2.send_queue_len++;
- status = smbd_smb2_flush_send_queue(sconn);
+ status = smbd_smb2_flush_send_queue(xconn);
if (!NT_STATUS_IS_OK(status)) {
- smbd_server_connection_terminate(sconn,
+ smbd_server_connection_terminate(xconn,
nt_errstr(status));
return;
}
static NTSTATUS smbd_smb2_request_process_cancel(struct smbd_smb2_request *req)
{
- struct smbd_server_connection *sconn = req->sconn;
+ struct smbXsrv_connection *xconn = req->xconn;
struct smbd_smb2_request *cur;
const uint8_t *inhdr;
uint32_t flags;
search_async_id = BVAL(inhdr, SMB2_HDR_PID);
/*
- * we don't need the request anymore
- * cancel requests never have a response
+ * We don't need the request anymore cancel requests never
+ * have a response.
+ *
+ * We defer the TALLOC_FREE(req) to the caller.
*/
- DLIST_REMOVE(req->sconn->smb2.requests, req);
- TALLOC_FREE(req);
+ DLIST_REMOVE(xconn->smb2.requests, req);
- for (cur = sconn->smb2.requests; cur; cur = cur->next) {
+ for (cur = xconn->smb2.requests; cur; cur = cur->next) {
const uint8_t *outhdr;
uint64_t message_id;
uint64_t async_id;
return NT_STATUS_ACCESS_DENIED;
}
- /* should we pass FLAG_CASELESS_PATHNAMES here? */
if (!set_current_service(tcon->compat, 0, true)) {
return NT_STATUS_ACCESS_DENIED;
}
req->last_session_id = 0;
- /* lookup an existing session */
- status = smb2srv_session_lookup(req->sconn->conn,
- in_session_id, now,
- &session);
+ /* look an existing session up */
+ switch (in_opcode) {
+ case SMB2_OP_SESSSETUP:
+ /*
+ * For a session bind request, we don't have the
+ * channel set up at this point yet, so we defer
+ * the verification that the connection belongs
+ * to the session to the session setup code, which
+ * can look at the session binding flags.
+ */
+ status = smb2srv_session_lookup_client(req->xconn->client,
+ in_session_id, now,
+ &session);
+ break;
+ default:
+ status = smb2srv_session_lookup_conn(req->xconn,
+ in_session_id, now,
+ &session);
+ break;
+ }
if (session) {
req->session = session;
req->last_session_id = in_session_id;
return NT_STATUS_INVALID_HANDLE;
}
- if (in_session_id != req->sconn->conn->last_session_id) {
- req->sconn->conn->last_session_id = in_session_id;
+ if (in_session_id != req->xconn->client->last_session_id) {
+ req->xconn->client->last_session_id = in_session_id;
set_current_user_info(session_info->unix_info->sanitized_username,
session_info->unix_info->unix_name,
session_info->info->domain_name);
NTSTATUS smbd_smb2_request_verify_creditcharge(struct smbd_smb2_request *req,
uint32_t data_length)
{
- struct smbXsrv_connection *xconn = req->sconn->conn;
+ struct smbXsrv_connection *xconn = req->xconn;
uint16_t needed_charge;
uint16_t credit_charge = 1;
const uint8_t *inhdr;
return NT_STATUS_OK;
}
+bool smbXsrv_is_encrypted(uint8_t encryption_flags)
+{
+ return (!(encryption_flags & SMBXSRV_PROCESSED_UNENCRYPTED_PACKET)
+ &&
+ (encryption_flags & (SMBXSRV_PROCESSED_ENCRYPTED_PACKET |
+ SMBXSRV_ENCRYPTION_DESIRED |
+ SMBXSRV_ENCRYPTION_REQUIRED)));
+}
+
+bool smbXsrv_is_partially_encrypted(uint8_t encryption_flags)
+{
+ return ((encryption_flags & SMBXSRV_PROCESSED_ENCRYPTED_PACKET) &&
+ (encryption_flags & SMBXSRV_PROCESSED_UNENCRYPTED_PACKET));
+}
+
+/* Set a flag if not already set, return true if set */
+bool smbXsrv_set_crypto_flag(uint8_t *flags, uint8_t flag)
+{
+ if ((flag == 0) || (*flags & flag)) {
+ return false;
+ }
+
+ *flags |= flag;
+ return true;
+}
+
+/*
+ * Update encryption state tracking flags, this can be used to
+ * determine whether whether the session or tcon is "encrypted".
+ */
+static void smb2srv_update_crypto_flags(struct smbd_smb2_request *req,
+ uint16_t opcode,
+ bool *update_session_globalp,
+ bool *update_tcon_globalp)
+{
+ /* Default: assume unecrypted and unsigned */
+ struct smbXsrv_session *session = req->session;
+ struct smbXsrv_tcon *tcon = req->tcon;
+ uint8_t encrypt_flag = SMBXSRV_PROCESSED_UNENCRYPTED_PACKET;
+ uint8_t sign_flag = SMBXSRV_PROCESSED_UNSIGNED_PACKET;
+ bool update_session = false;
+ bool update_tcon = false;
+
+ if (req->was_encrypted && req->do_encryption) {
+ encrypt_flag = SMBXSRV_PROCESSED_ENCRYPTED_PACKET;
+ sign_flag = SMBXSRV_PROCESSED_SIGNED_PACKET;
+ } else {
+ /* Unencrypted packet, can be signed */
+ if (req->do_signing) {
+ sign_flag = SMBXSRV_PROCESSED_SIGNED_PACKET;
+ } else if (opcode == SMB2_OP_CANCEL) {
+ /* Cancel requests are allowed to skip signing */
+ sign_flag &= ~SMBXSRV_PROCESSED_UNSIGNED_PACKET;
+ }
+ }
+
+ update_session |= smbXsrv_set_crypto_flag(
+ &session->global->encryption_flags, encrypt_flag);
+ update_session |= smbXsrv_set_crypto_flag(
+ &session->global->signing_flags, sign_flag);
+
+ if (tcon) {
+ update_tcon |= smbXsrv_set_crypto_flag(
+ &tcon->global->encryption_flags, encrypt_flag);
+ update_tcon |= smbXsrv_set_crypto_flag(
+ &tcon->global->signing_flags, sign_flag);
+ }
+
+ *update_session_globalp = update_session;
+ *update_tcon_globalp = update_tcon;
+ return;
+}
+
+bool smbXsrv_is_signed(uint8_t signing_flags)
+{
+ /*
+ * Signing is always enabled, so unless we got an unsigned
+ * packet and at least one signed packet that was not
+ * encrypted, the session or tcon is "signed".
+ */
+ return (!(signing_flags & SMBXSRV_PROCESSED_UNSIGNED_PACKET) &&
+ (signing_flags & SMBXSRV_PROCESSED_SIGNED_PACKET));
+}
+
+bool smbXsrv_is_partially_signed(uint8_t signing_flags)
+{
+ return ((signing_flags & SMBXSRV_PROCESSED_UNSIGNED_PACKET) &&
+ (signing_flags & SMBXSRV_PROCESSED_SIGNED_PACKET));
+}
+
+static NTSTATUS smbd_smb2_request_dispatch_update_counts(
+ struct smbd_smb2_request *req,
+ bool modify_call)
+{
+ struct smbXsrv_connection *xconn = req->xconn;
+ const uint8_t *inhdr;
+ uint16_t channel_sequence;
+ uint32_t flags;
+ int cmp;
+ struct smbXsrv_open *op;
+ bool update_open = false;
+ NTSTATUS status = NT_STATUS_OK;
+
+ req->request_counters_updated = false;
+
+ if (xconn->protocol < PROTOCOL_SMB2_22) {
+ return NT_STATUS_OK;
+ }
+
+ if (req->compat_chain_fsp == NULL) {
+ return NT_STATUS_OK;
+ }
+
+ op = req->compat_chain_fsp->op;
+ if (op == NULL) {
+ return NT_STATUS_OK;
+ }
+
+ inhdr = SMBD_SMB2_IN_HDR_PTR(req);
+ flags = IVAL(inhdr, SMB2_HDR_FLAGS);
+ channel_sequence = SVAL(inhdr, SMB2_HDR_CHANNEL_SEQUENCE);
+
+ cmp = channel_sequence - op->global->channel_sequence;
+
+ if (abs(cmp) > INT16_MAX) {
+ /*
+ * [MS-SMB2] 3.3.5.2.10 - Verifying the Channel Sequence Number:
+ *
+ * If the channel sequence number of the request and the one
+ * known to the server are not equal, the channel sequence
+ * number and outstanding request counts are only updated
+ * "... if the unsigned difference using 16-bit arithmetic
+ * between ChannelSequence and Open.ChannelSequence is less than
+ * or equal to 0x7FFF ...".
+ * Otherwise, an error is returned for the modifying
+ * calls write, set_info, and ioctl.
+ *
+ * There are currently two issues with the description:
+ *
+ * * For the other calls, the document seems to imply
+ * that processing continues without adapting the
+ * counters (if the sequence numbers are not equal).
+ *
+ * TODO: This needs clarification!
+ *
+ * * Also, the behaviour if the difference is larger
+ * than 0x7FFF is not clear. The document seems to
+ * imply that if such a difference is reached,
+ * the server starts to ignore the counters or
+ * in the case of the modifying calls, return errors.
+ *
+ * TODO: This needs clarification!
+ *
+ * At this point Samba tries to be a little more
+ * clever than the description in the MS-SMB2 document
+ * by heuristically detecting and properly treating
+ * a 16 bit overflow of the client-submitted sequence
+ * number:
+ *
+ * If the stored channel squence number is more than
+ * 0x7FFF larger than the one from the request, then
+ * the client-provided sequence number has likely
+ * overflown. We treat this case as valid instead
+ * of as failure.
+ *
+ * The MS-SMB2 behaviour would be setting cmp = -1.
+ */
+ cmp *= -1;
+ }
+
+ if (!(flags & SMB2_HDR_FLAG_REPLAY_OPERATION)) {
+ if (cmp == 0) {
+ op->request_count += 1;
+ req->request_counters_updated = true;
+ } else if (cmp > 0) {
+ op->pre_request_count += op->request_count;
+ op->request_count = 1;
+ op->global->channel_sequence = channel_sequence;
+ update_open = true;
+ req->request_counters_updated = true;
+ } else if (modify_call) {
+ return NT_STATUS_FILE_NOT_AVAILABLE;
+ }
+ } else {
+ if (cmp == 0 && op->pre_request_count == 0) {
+ op->request_count += 1;
+ req->request_counters_updated = true;
+ } else if (cmp > 0 && op->pre_request_count == 0) {
+ op->pre_request_count += op->request_count;
+ op->request_count = 1;
+ op->global->channel_sequence = channel_sequence;
+ update_open = true;
+ req->request_counters_updated = true;
+ } else if (modify_call) {
+ return NT_STATUS_FILE_NOT_AVAILABLE;
+ }
+ }
+
+ if (update_open) {
+ status = smbXsrv_open_update(op);
+ }
+
+ return status;
+}
+
NTSTATUS smbd_smb2_request_dispatch(struct smbd_smb2_request *req)
{
- struct smbXsrv_connection *conn = req->sconn->conn;
+ struct smbXsrv_connection *xconn = req->xconn;
const struct smbd_smb2_dispatch_table *call = NULL;
const struct iovec *intf_v = SMBD_SMB2_IN_TF_IOV(req);
const uint8_t *inhdr;
NTSTATUS return_value;
struct smbXsrv_session *x = NULL;
bool signing_required = false;
+ bool encryption_desired = false;
bool encryption_required = false;
inhdr = SMBD_SMB2_IN_HDR_PTR(req);
+ DO_PROFILE_INC(request);
+
/* TODO: verify more things */
flags = IVAL(inhdr, SMB2_HDR_FLAGS);
smb2_opcode_name(opcode),
(unsigned long long)mid));
- if (conn->protocol >= PROTOCOL_SMB2_02) {
+ if (xconn->protocol >= PROTOCOL_SMB2_02) {
/*
* once the protocol is negotiated
* SMB2_OP_NEGPROT is not allowed anymore
session_status = smbd_smb2_request_check_session(req);
x = req->session;
if (x != NULL) {
- signing_required = x->global->signing_required;
- encryption_required = x->global->encryption_required;
-
- if (opcode == SMB2_OP_SESSSETUP &&
- x->global->channels[0].signing_key.length) {
- signing_required = true;
- }
+ signing_required = x->global->signing_flags & SMBXSRV_SIGNING_REQUIRED;
+ encryption_desired = x->global->encryption_flags & SMBXSRV_ENCRYPTION_DESIRED;
+ encryption_required = x->global->encryption_flags & SMBXSRV_ENCRYPTION_REQUIRED;
}
+ req->async_internal = false;
req->do_signing = false;
req->do_encryption = false;
+ req->was_encrypted = false;
if (intf_v->iov_len == SMB2_TF_HDR_SIZE) {
const uint8_t *intf = SMBD_SMB2_IN_TF_PTR(req);
uint64_t tf_session_id = BVAL(intf, SMB2_TF_SESSION_ID);
NT_STATUS_ACCESS_DENIED);
}
- req->do_encryption = true;
+ req->was_encrypted = true;
}
- if (encryption_required && !req->do_encryption) {
+ if (encryption_required && !req->was_encrypted) {
return smbd_smb2_request_error(req,
NT_STATUS_ACCESS_DENIED);
}
allowed_flags = SMB2_HDR_FLAG_CHAINED |
SMB2_HDR_FLAG_SIGNED |
SMB2_HDR_FLAG_DFS;
+ if (xconn->protocol >= PROTOCOL_SMB3_11) {
+ allowed_flags |= SMB2_HDR_FLAG_PRIORITY_MASK;
+ }
+ if (opcode == SMB2_OP_NEGPROT) {
+ if (lp_server_max_protocol() >= PROTOCOL_SMB3_11) {
+ allowed_flags |= SMB2_HDR_FLAG_PRIORITY_MASK;
+ }
+ }
if (opcode == SMB2_OP_CANCEL) {
allowed_flags |= SMB2_HDR_FLAG_ASYNC;
}
+ if (xconn->protocol >= PROTOCOL_SMB2_22) {
+ allowed_flags |= SMB2_HDR_FLAG_REPLAY_OPERATION;
+ }
if ((flags & ~allowed_flags) != 0) {
return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
}
req->compat_chain_fsp = NULL;
}
- if (req->do_encryption) {
+ if (req->was_encrypted) {
signing_required = false;
} else if (signing_required || (flags & SMB2_HDR_FLAG_SIGNED)) {
- DATA_BLOB signing_key;
+ DATA_BLOB signing_key = data_blob_null;
if (x == NULL) {
/*
return smbd_smb2_request_error(req, status);
}
- signing_key = x->global->channels[0].signing_key;
+ signing_key = smbd_smb2_signing_key(x, xconn);
/*
* If we have a signing key, we should
}
status = smb2_signing_check_pdu(signing_key,
- conn->protocol,
+ xconn->protocol,
SMBD_SMB2_IN_HDR_IOV(req),
SMBD_SMB2_NUM_IOV_PER_REQ - 1);
if (!NT_STATUS_IS_OK(status)) {
if (!NT_STATUS_IS_OK(status)) {
return smbd_smb2_request_error(req, status);
}
- if (req->tcon->global->encryption_required) {
+ if (req->tcon->global->encryption_flags & SMBXSRV_ENCRYPTION_DESIRED) {
+ encryption_desired = true;
+ }
+ if (req->tcon->global->encryption_flags & SMBXSRV_ENCRYPTION_REQUIRED) {
encryption_required = true;
}
- if (encryption_required && !req->do_encryption) {
+ if (encryption_required && !req->was_encrypted) {
return smbd_smb2_request_error(req,
NT_STATUS_ACCESS_DENIED);
}
}
+ if (req->was_encrypted || encryption_desired) {
+ req->do_encryption = true;
+ }
+
+ if (req->session) {
+ bool update_session_global = false;
+ bool update_tcon_global = false;
+
+ smb2srv_update_crypto_flags(req, opcode,
+ &update_session_global,
+ &update_tcon_global);
+
+ if (update_session_global) {
+ status = smbXsrv_session_update(x);
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(req, status);
+ }
+ }
+ if (update_tcon_global) {
+ status = smbXsrv_tcon_update(req->tcon);
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(req, status);
+ }
+ }
+ }
+
if (call->fileid_ofs != 0) {
size_t needed = call->fileid_ofs + 16;
const uint8_t *body = SMBD_SMB2_IN_BODY_PTR(req);
}
}
+ status = smbd_smb2_request_dispatch_update_counts(req, call->modify);
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(req, status);
+ }
+
if (call->as_root) {
SMB_ASSERT(call->fileid_ofs == 0);
/* This call needs to be run as root */
SMB_ASSERT(call->need_tcon);
}
+#define _INBYTES(_r) \
+ iov_buflen(SMBD_SMB2_IN_HDR_IOV(_r), SMBD_SMB2_NUM_IOV_PER_REQ-1)
+
switch (opcode) {
case SMB2_OP_NEGPROT:
- {
- START_PROFILE(smb2_negprot);
- return_value = smbd_smb2_request_process_negprot(req);
- END_PROFILE(smb2_negprot);
- }
+ SMBPROFILE_IOBYTES_ASYNC_START(smb2_negprot, profile_p,
+ req->profile, _INBYTES(req));
+ return_value = smbd_smb2_request_process_negprot(req);
break;
case SMB2_OP_SESSSETUP:
- {
- START_PROFILE(smb2_sesssetup);
- return_value = smbd_smb2_request_process_sesssetup(req);
- END_PROFILE(smb2_sesssetup);
- }
+ SMBPROFILE_IOBYTES_ASYNC_START(smb2_sesssetup, profile_p,
+ req->profile, _INBYTES(req));
+ return_value = smbd_smb2_request_process_sesssetup(req);
break;
case SMB2_OP_LOGOFF:
- {
- START_PROFILE(smb2_logoff);
- return_value = smbd_smb2_request_process_logoff(req);
- END_PROFILE(smb2_logoff);
- }
+ SMBPROFILE_IOBYTES_ASYNC_START(smb2_logoff, profile_p,
+ req->profile, _INBYTES(req));
+ return_value = smbd_smb2_request_process_logoff(req);
break;
case SMB2_OP_TCON:
- {
- START_PROFILE(smb2_tcon);
- return_value = smbd_smb2_request_process_tcon(req);
- END_PROFILE(smb2_tcon);
- }
+ SMBPROFILE_IOBYTES_ASYNC_START(smb2_tcon, profile_p,
+ req->profile, _INBYTES(req));
+ return_value = smbd_smb2_request_process_tcon(req);
break;
case SMB2_OP_TDIS:
- {
- START_PROFILE(smb2_tdis);
- return_value = smbd_smb2_request_process_tdis(req);
- END_PROFILE(smb2_tdis);
- }
+ SMBPROFILE_IOBYTES_ASYNC_START(smb2_tdis, profile_p,
+ req->profile, _INBYTES(req));
+ return_value = smbd_smb2_request_process_tdis(req);
break;
case SMB2_OP_CREATE:
- {
- START_PROFILE(smb2_create);
- return_value = smbd_smb2_request_process_create(req);
- END_PROFILE(smb2_create);
+ if (req->subreq == NULL) {
+ SMBPROFILE_IOBYTES_ASYNC_START(smb2_create, profile_p,
+ req->profile, _INBYTES(req));
+ } else {
+ SMBPROFILE_IOBYTES_ASYNC_SET_BUSY(req->profile);
}
+ return_value = smbd_smb2_request_process_create(req);
break;
case SMB2_OP_CLOSE:
- {
- START_PROFILE(smb2_close);
- return_value = smbd_smb2_request_process_close(req);
- END_PROFILE(smb2_close);
- }
+ SMBPROFILE_IOBYTES_ASYNC_START(smb2_close, profile_p,
+ req->profile, _INBYTES(req));
+ return_value = smbd_smb2_request_process_close(req);
break;
case SMB2_OP_FLUSH:
- {
- START_PROFILE(smb2_flush);
- return_value = smbd_smb2_request_process_flush(req);
- END_PROFILE(smb2_flush);
- }
+ SMBPROFILE_IOBYTES_ASYNC_START(smb2_flush, profile_p,
+ req->profile, _INBYTES(req));
+ return_value = smbd_smb2_request_process_flush(req);
break;
case SMB2_OP_READ:
- {
- START_PROFILE(smb2_read);
- return_value = smbd_smb2_request_process_read(req);
- END_PROFILE(smb2_read);
- }
+ SMBPROFILE_IOBYTES_ASYNC_START(smb2_read, profile_p,
+ req->profile, _INBYTES(req));
+ return_value = smbd_smb2_request_process_read(req);
break;
case SMB2_OP_WRITE:
- {
- START_PROFILE(smb2_write);
- return_value = smbd_smb2_request_process_write(req);
- END_PROFILE(smb2_write);
- }
+ SMBPROFILE_IOBYTES_ASYNC_START(smb2_write, profile_p,
+ req->profile, _INBYTES(req));
+ return_value = smbd_smb2_request_process_write(req);
break;
case SMB2_OP_LOCK:
- {
- START_PROFILE(smb2_lock);
- return_value = smbd_smb2_request_process_lock(req);
- END_PROFILE(smb2_lock);
- }
+ SMBPROFILE_IOBYTES_ASYNC_START(smb2_lock, profile_p,
+ req->profile, _INBYTES(req));
+ return_value = smbd_smb2_request_process_lock(req);
break;
case SMB2_OP_IOCTL:
- {
- START_PROFILE(smb2_ioctl);
- return_value = smbd_smb2_request_process_ioctl(req);
- END_PROFILE(smb2_ioctl);
- }
+ SMBPROFILE_IOBYTES_ASYNC_START(smb2_ioctl, profile_p,
+ req->profile, _INBYTES(req));
+ return_value = smbd_smb2_request_process_ioctl(req);
break;
case SMB2_OP_CANCEL:
- {
- START_PROFILE(smb2_cancel);
- return_value = smbd_smb2_request_process_cancel(req);
- END_PROFILE(smb2_cancel);
- }
+ SMBPROFILE_IOBYTES_ASYNC_START(smb2_cancel, profile_p,
+ req->profile, _INBYTES(req));
+ return_value = smbd_smb2_request_process_cancel(req);
+ SMBPROFILE_IOBYTES_ASYNC_END(req->profile, 0);
+
+ /*
+ * We don't need the request anymore cancel requests never
+ * have a response.
+ *
+ * smbd_smb2_request_process_cancel() already called
+ * DLIST_REMOVE(xconn->smb2.requests, req);
+ */
+ TALLOC_FREE(req);
+
break;
case SMB2_OP_KEEPALIVE:
- {
- START_PROFILE(smb2_keepalive);
- return_value = smbd_smb2_request_process_keepalive(req);
- END_PROFILE(smb2_keepalive);
- }
+ SMBPROFILE_IOBYTES_ASYNC_START(smb2_keepalive, profile_p,
+ req->profile, _INBYTES(req));
+ return_value = smbd_smb2_request_process_keepalive(req);
break;
- case SMB2_OP_FIND:
- {
- START_PROFILE(smb2_find);
- return_value = smbd_smb2_request_process_find(req);
- END_PROFILE(smb2_find);
- }
+ case SMB2_OP_QUERY_DIRECTORY:
+ SMBPROFILE_IOBYTES_ASYNC_START(smb2_find, profile_p,
+ req->profile, _INBYTES(req));
+ return_value = smbd_smb2_request_process_query_directory(req);
break;
case SMB2_OP_NOTIFY:
- {
- START_PROFILE(smb2_notify);
- return_value = smbd_smb2_request_process_notify(req);
- END_PROFILE(smb2_notify);
- }
+ SMBPROFILE_IOBYTES_ASYNC_START(smb2_notify, profile_p,
+ req->profile, _INBYTES(req));
+ return_value = smbd_smb2_request_process_notify(req);
break;
case SMB2_OP_GETINFO:
- {
- START_PROFILE(smb2_getinfo);
- return_value = smbd_smb2_request_process_getinfo(req);
- END_PROFILE(smb2_getinfo);
- }
+ SMBPROFILE_IOBYTES_ASYNC_START(smb2_getinfo, profile_p,
+ req->profile, _INBYTES(req));
+ return_value = smbd_smb2_request_process_getinfo(req);
break;
case SMB2_OP_SETINFO:
- {
- START_PROFILE(smb2_setinfo);
- return_value = smbd_smb2_request_process_setinfo(req);
- END_PROFILE(smb2_setinfo);
- }
+ SMBPROFILE_IOBYTES_ASYNC_START(smb2_setinfo, profile_p,
+ req->profile, _INBYTES(req));
+ return_value = smbd_smb2_request_process_setinfo(req);
break;
case SMB2_OP_BREAK:
- {
- START_PROFILE(smb2_break);
- return_value = smbd_smb2_request_process_break(req);
- END_PROFILE(smb2_break);
- }
+ SMBPROFILE_IOBYTES_ASYNC_START(smb2_break, profile_p,
+ req->profile, _INBYTES(req));
+ return_value = smbd_smb2_request_process_break(req);
break;
default:
return return_value;
}
+static void smbd_smb2_request_reply_update_counts(struct smbd_smb2_request *req)
+{
+ struct smbXsrv_connection *xconn = req->xconn;
+ const uint8_t *inhdr;
+ uint16_t channel_sequence;
+ struct smbXsrv_open *op;
+
+ if (!req->request_counters_updated) {
+ return;
+ }
+
+ if (xconn->protocol < PROTOCOL_SMB2_22) {
+ return;
+ }
+
+ if (req->compat_chain_fsp == NULL) {
+ return;
+ }
+
+ op = req->compat_chain_fsp->op;
+ if (op == NULL) {
+ return;
+ }
+
+ inhdr = SMBD_SMB2_IN_HDR_PTR(req);
+ channel_sequence = SVAL(inhdr, SMB2_HDR_CHANNEL_SEQUENCE);
+
+ if (op->global->channel_sequence == channel_sequence) {
+ SMB_ASSERT(op->request_count > 0);
+ op->request_count -= 1;
+ } else {
+ SMB_ASSERT(op->pre_request_count > 0);
+ op->pre_request_count -= 1;
+ }
+}
+
static NTSTATUS smbd_smb2_request_reply(struct smbd_smb2_request *req)
{
- struct smbd_server_connection *sconn = req->sconn;
- struct smbXsrv_connection *conn = req->sconn->conn;
+ struct smbXsrv_connection *xconn = req->xconn;
int first_idx = 1;
struct iovec *firsttf = SMBD_SMB2_IDX_TF_IOV(req,out,first_idx);
struct iovec *outhdr = SMBD_SMB2_OUT_HDR_IOV(req);
struct iovec *outdyn = SMBD_SMB2_OUT_DYN_IOV(req);
NTSTATUS status;
+ bool ok;
req->subreq = NULL;
TALLOC_FREE(req->async_te);
+ /* MS-SMB2: 3.3.4.1 Sending Any Outgoing Message */
+ smbd_smb2_request_reply_update_counts(req);
+
if (req->do_encryption &&
(firsttf->iov_len == 0) &&
(req->first_key.length == 0) &&
DATA_BLOB encryption_key = req->session->global->encryption_key;
uint8_t *tf;
uint64_t session_id = req->session->global->session_wire_id;
- struct smbXsrv_session *x = req->session;
uint64_t nonce_high;
uint64_t nonce_low;
- nonce_high = x->nonce_high;
- nonce_low = x->nonce_low;
-
- x->nonce_low += 1;
- if (x->nonce_low == 0) {
- x->nonce_low += 1;
- x->nonce_high += 1;
+ status = smb2_get_new_nonce(req->session,
+ &nonce_high,
+ &nonce_low);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
}
/*
* with the last signing key we remembered.
*/
status = smb2_signing_sign_pdu(req->last_key,
- conn->protocol,
+ xconn->protocol,
lasthdr,
SMBD_SMB2_NUM_IOV_PER_REQ - 1);
if (!NT_STATUS_IS_OK(status)) {
data_blob_clear_free(&req->last_key);
}
+ SMBPROFILE_IOBYTES_ASYNC_END(req->profile,
+ iov_buflen(outhdr, SMBD_SMB2_NUM_IOV_PER_REQ-1));
+
req->current_idx += SMBD_SMB2_NUM_IOV_PER_REQ;
if (req->current_idx < req->out.vector_count) {
if (req->do_signing && firsttf->iov_len == 0) {
struct smbXsrv_session *x = req->session;
- DATA_BLOB signing_key = x->global->channels[0].signing_key;
+ DATA_BLOB signing_key = smbd_smb2_signing_key(x, xconn);
/*
* we need to remember the signing key
req->compound_related = false;
}
- smb2_setup_nbt_length(req->out.vector, req->out.vector_count);
+ ok = smb2_setup_nbt_length(req->out.vector, req->out.vector_count);
+ if (!ok) {
+ return NT_STATUS_INVALID_PARAMETER_MIX;
+ }
/* Set credit for these operations (zero credits if this
is a final reply for an async operation). */
*/
if (firsttf->iov_len == SMB2_TF_HDR_SIZE) {
status = smb2_signing_encrypt_pdu(req->first_key,
- conn->protocol,
+ xconn->smb2.server.cipher,
firsttf,
req->out.vector_count - first_idx);
if (!NT_STATUS_IS_OK(status)) {
}
} else if (req->do_signing) {
struct smbXsrv_session *x = req->session;
- DATA_BLOB signing_key = x->global->channels[0].signing_key;
+ DATA_BLOB signing_key = smbd_smb2_signing_key(x, xconn);
status = smb2_signing_sign_pdu(signing_key,
- conn->protocol,
+ xconn->protocol,
outhdr,
SMBD_SMB2_NUM_IOV_PER_REQ - 1);
if (!NT_STATUS_IS_OK(status)) {
data_blob_clear_free(&req->first_key);
}
+ if (req->preauth != NULL) {
+ struct hc_sha512state sctx;
+ int i;
+
+ samba_SHA512_Init(&sctx);
+ samba_SHA512_Update(&sctx, req->preauth->sha512_value,
+ sizeof(req->preauth->sha512_value));
+ for (i = 1; i < req->in.vector_count; i++) {
+ samba_SHA512_Update(&sctx,
+ req->in.vector[i].iov_base,
+ req->in.vector[i].iov_len);
+ }
+ samba_SHA512_Final(req->preauth->sha512_value, &sctx);
+
+ samba_SHA512_Init(&sctx);
+ samba_SHA512_Update(&sctx, req->preauth->sha512_value,
+ sizeof(req->preauth->sha512_value));
+ for (i = 1; i < req->out.vector_count; i++) {
+ samba_SHA512_Update(&sctx,
+ req->out.vector[i].iov_base,
+ req->out.vector[i].iov_len);
+ }
+ samba_SHA512_Final(req->preauth->sha512_value, &sctx);
+
+ req->preauth = NULL;
+ }
+
/* I am a sick, sick man... :-). Sendfile hack ... JRA. */
if (req->out.vector_count < (2*SMBD_SMB2_NUM_IOV_PER_REQ) &&
outdyn->iov_base == NULL && outdyn->iov_len != 0) {
* We're done with this request -
* move it off the "being processed" queue.
*/
- DLIST_REMOVE(req->sconn->smb2.requests, req);
+ DLIST_REMOVE(xconn->smb2.requests, req);
req->queue_entry.mem_ctx = req;
req->queue_entry.vector = req->out.vector;
req->queue_entry.count = req->out.vector_count;
- DLIST_ADD_END(conn->smb2.send_queue, &req->queue_entry, NULL);
- conn->smb2.send_queue_len++;
+ DLIST_ADD_END(xconn->smb2.send_queue, &req->queue_entry);
+ xconn->smb2.send_queue_len++;
- status = smbd_smb2_flush_send_queue(sconn);
+ status = smbd_smb2_flush_send_queue(xconn);
if (!NT_STATUS_IS_OK(status)) {
return status;
}
return NT_STATUS_OK;
}
-static NTSTATUS smbd_smb2_request_next_incoming(struct smbd_server_connection *sconn);
+static NTSTATUS smbd_smb2_request_next_incoming(struct smbXsrv_connection *xconn);
void smbd_smb2_request_dispatch_immediate(struct tevent_context *ctx,
struct tevent_immediate *im,
{
struct smbd_smb2_request *req = talloc_get_type_abort(private_data,
struct smbd_smb2_request);
- struct smbd_server_connection *sconn = req->sconn;
+ struct smbXsrv_connection *xconn = req->xconn;
NTSTATUS status;
TALLOC_FREE(im);
status = smbd_smb2_request_dispatch(req);
if (!NT_STATUS_IS_OK(status)) {
- smbd_server_connection_terminate(sconn, nt_errstr(status));
+ smbd_server_connection_terminate(xconn, nt_errstr(status));
return;
}
- status = smbd_smb2_request_next_incoming(sconn);
+ status = smbd_smb2_request_next_incoming(xconn);
if (!NT_STATUS_IS_OK(status)) {
- smbd_server_connection_terminate(sconn, nt_errstr(status));
+ smbd_server_connection_terminate(xconn, nt_errstr(status));
return;
}
}
outdyn_v->iov_len = 0;
}
- /* see if we need to recalculate the offset to the next response */
- if (next_command_ofs > 0) {
+ /*
+ * See if we need to recalculate the offset to the next response
+ *
+ * Note that all responses may require padding (including the very last
+ * one).
+ */
+ if (req->out.vector_count >= (2 * SMBD_SMB2_NUM_IOV_PER_REQ)) {
next_command_ofs = SMB2_HDR_BODY;
next_command_ofs += SMBD_SMB2_OUT_BODY_LEN(req);
next_command_ofs += SMBD_SMB2_OUT_DYN_LEN(req);
next_command_ofs += pad_size;
}
- SIVAL(outhdr, SMB2_HDR_NEXT_COMMAND, next_command_ofs);
-
+ if ((req->current_idx + SMBD_SMB2_NUM_IOV_PER_REQ) >= req->out.vector_count) {
+ SIVAL(outhdr, SMB2_HDR_NEXT_COMMAND, 0);
+ } else {
+ SIVAL(outhdr, SMB2_HDR_NEXT_COMMAND, next_command_ofs);
+ }
return smbd_smb2_request_reply(req);
}
DATA_BLOB *info,
const char *location)
{
- struct smbXsrv_connection *xconn = req->sconn->conn;
+ struct smbXsrv_connection *xconn = req->xconn;
DATA_BLOB body;
DATA_BLOB _dyn;
uint8_t *outhdr = SMBD_SMB2_OUT_HDR_PTR(req);
size_t unread_bytes = smbd_smb2_unread_bytes(req);
- DEBUG(10,("smbd_smb2_request_error_ex: idx[%d] status[%s] |%s| at %s\n",
- req->current_idx, nt_errstr(status), info ? " +info" : "",
- location));
+ DBG_NOTICE("smbd_smb2_request_error_ex: idx[%d] status[%s] |%s| "
+ "at %s\n", req->current_idx, nt_errstr(status),
+ info ? " +info" : "", location);
if (unread_bytes) {
/* Recvfile error. Drain incoming socket. */
struct smbd_smb2_send_break_state {
- struct smbd_server_connection *sconn;
struct smbd_smb2_send_queue queue_entry;
uint8_t nbt_hdr[NBT_HDR_SIZE];
uint8_t tf[SMB2_TF_HDR_SIZE];
uint8_t body[1];
};
-static NTSTATUS smbd_smb2_send_break(struct smbd_server_connection *sconn,
+static NTSTATUS smbd_smb2_send_break(struct smbXsrv_connection *xconn,
struct smbXsrv_session *session,
struct smbXsrv_tcon *tcon,
const uint8_t *body,
size_t body_len)
{
struct smbd_smb2_send_break_state *state;
- struct smbXsrv_connection *conn = sconn->conn;
- bool do_encryption = session->global->encryption_required;
+ bool do_encryption = false;
+ uint64_t session_wire_id = 0;
uint64_t nonce_high = 0;
uint64_t nonce_low = 0;
NTSTATUS status;
size_t statelen;
+ bool ok;
- if (tcon->global->encryption_required) {
- do_encryption = true;
+ if (session != NULL) {
+ session_wire_id = session->global->session_wire_id;
+ do_encryption = session->global->encryption_flags & SMBXSRV_ENCRYPTION_DESIRED;
+ if (tcon->global->encryption_flags & SMBXSRV_ENCRYPTION_DESIRED) {
+ do_encryption = true;
+ }
}
statelen = offsetof(struct smbd_smb2_send_break_state, body) +
body_len;
- state = talloc_zero_size(sconn, statelen);
+ state = talloc_zero_size(xconn, statelen);
if (state == NULL) {
return NT_STATUS_NO_MEMORY;
}
talloc_set_name_const(state, "struct smbd_smb2_send_break_state");
- state->sconn = sconn;
if (do_encryption) {
- nonce_high = session->nonce_high;
- nonce_low = session->nonce_low;
-
- session->nonce_low += 1;
- if (session->nonce_low == 0) {
- session->nonce_low += 1;
- session->nonce_high += 1;
+ status = smb2_get_new_nonce(session,
+ &nonce_high,
+ &nonce_low);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
}
}
SIVAL(state->tf, SMB2_TF_PROTOCOL_ID, SMB2_TF_MAGIC);
SBVAL(state->tf, SMB2_TF_NONCE+0, nonce_low);
SBVAL(state->tf, SMB2_TF_NONCE+8, nonce_high);
- SBVAL(state->tf, SMB2_TF_SESSION_ID, session->global->session_wire_id);
+ SBVAL(state->tf, SMB2_TF_SESSION_ID, session_wire_id);
SIVAL(state->hdr, 0, SMB2_MAGIC);
SSVAL(state->hdr, SMB2_HDR_LENGTH, SMB2_HDR_BODY);
* state->vector[1+SMBD_SMB2_DYN_IOV_OFS] is NULL by talloc_zero above
*/
- smb2_setup_nbt_length(state->vector, 1 + SMBD_SMB2_NUM_IOV_PER_REQ);
+ ok = smb2_setup_nbt_length(state->vector,
+ 1 + SMBD_SMB2_NUM_IOV_PER_REQ);
+ if (!ok) {
+ return NT_STATUS_INVALID_PARAMETER_MIX;
+ }
if (do_encryption) {
DATA_BLOB encryption_key = session->global->encryption_key;
status = smb2_signing_encrypt_pdu(encryption_key,
- conn->protocol,
+ xconn->smb2.server.cipher,
&state->vector[1+SMBD_SMB2_TF_IOV_OFS],
SMBD_SMB2_NUM_IOV_PER_REQ);
if (!NT_STATUS_IS_OK(status)) {
state->queue_entry.mem_ctx = state;
state->queue_entry.vector = state->vector;
state->queue_entry.count = ARRAY_SIZE(state->vector);
- DLIST_ADD_END(conn->smb2.send_queue, &state->queue_entry, NULL);
- conn->smb2.send_queue_len++;
+ DLIST_ADD_END(xconn->smb2.send_queue, &state->queue_entry);
+ xconn->smb2.send_queue_len++;
- status = smbd_smb2_flush_send_queue(sconn);
+ status = smbd_smb2_flush_send_queue(xconn);
if (!NT_STATUS_IS_OK(status)) {
return status;
}
return NT_STATUS_OK;
}
-NTSTATUS smbd_smb2_send_oplock_break(struct smbd_server_connection *sconn,
+NTSTATUS smbd_smb2_send_oplock_break(struct smbXsrv_connection *xconn,
struct smbXsrv_session *session,
struct smbXsrv_tcon *tcon,
struct smbXsrv_open *op,
SBVAL(body, 0x08, op->global->open_persistent_id);
SBVAL(body, 0x10, op->global->open_volatile_id);
- return smbd_smb2_send_break(sconn, session, tcon, body, sizeof(body));
+ return smbd_smb2_send_break(xconn, NULL, NULL, body, sizeof(body));
+}
+
+NTSTATUS smbd_smb2_send_lease_break(struct smbXsrv_connection *xconn,
+ uint16_t new_epoch,
+ uint32_t lease_flags,
+ struct smb2_lease_key *lease_key,
+ uint32_t current_lease_state,
+ uint32_t new_lease_state)
+{
+ uint8_t body[0x2c];
+
+ SSVAL(body, 0x00, sizeof(body));
+ SSVAL(body, 0x02, new_epoch);
+ SIVAL(body, 0x04, lease_flags);
+ SBVAL(body, 0x08, lease_key->data[0]);
+ SBVAL(body, 0x10, lease_key->data[1]);
+ SIVAL(body, 0x18, current_lease_state);
+ SIVAL(body, 0x1c, new_lease_state);
+ SIVAL(body, 0x20, 0); /* BreakReason, MUST be 0 */
+ SIVAL(body, 0x24, 0); /* AccessMaskHint, MUST be 0 */
+ SIVAL(body, 0x28, 0); /* ShareMaskHint, MUST be 0 */
+
+ return smbd_smb2_send_break(xconn, NULL, NULL, body, sizeof(body));
}
static bool is_smb2_recvfile_write(struct smbd_smb2_request_read_state *state)
file_id_persistent = BVAL(body, 0x10);
file_id_volatile = BVAL(body, 0x18);
- status = smb2srv_open_lookup(state->req->sconn->conn,
+ status = smb2srv_open_lookup(state->req->xconn,
file_id_persistent,
file_id_volatile,
0, /* now */
return true;
}
-static NTSTATUS smbd_smb2_request_next_incoming(struct smbd_server_connection *sconn)
+static NTSTATUS smbd_smb2_request_next_incoming(struct smbXsrv_connection *xconn)
{
- struct smbXsrv_connection *xconn = sconn->conn;
+ struct smbd_server_connection *sconn = xconn->client->sconn;
struct smbd_smb2_request_read_state *state = &xconn->smb2.request_read_state;
size_t max_send_queue_len;
size_t cur_send_queue_len;
/* ask for the next request */
ZERO_STRUCTP(state);
- state->req = smbd_smb2_request_allocate(sconn);
+ state->req = smbd_smb2_request_allocate(xconn);
if (state->req == NULL) {
return NT_STATUS_NO_MEMORY;
}
state->req->sconn = sconn;
+ state->req->xconn = xconn;
state->min_recv_size = lp_min_receive_file_size();
TEVENT_FD_READABLE(xconn->transport.fde);
return NT_STATUS_OK;
}
-void smbd_smb2_first_negprot(struct smbd_server_connection *sconn,
- uint8_t *inbuf, size_t size)
+void smbd_smb2_process_negprot(struct smbXsrv_connection *xconn,
+ uint64_t expected_seq_low,
+ const uint8_t *inpdu, size_t size)
{
+ struct smbd_server_connection *sconn = xconn->client->sconn;
NTSTATUS status;
struct smbd_smb2_request *req = NULL;
DEBUG(10,("smbd_smb2_first_negprot: packet length %u\n",
(unsigned int)size));
- status = smbd_initialize_smb2(sconn);
+ status = smbd_initialize_smb2(xconn, expected_seq_low);
if (!NT_STATUS_IS_OK(status)) {
- smbd_server_connection_terminate(sconn, nt_errstr(status));
+ smbd_server_connection_terminate(xconn, nt_errstr(status));
return;
}
- status = smbd_smb2_request_create(sconn, inbuf, size, &req);
+ status = smbd_smb2_request_create(xconn, inpdu, size, &req);
if (!NT_STATUS_IS_OK(status)) {
- smbd_server_connection_terminate(sconn, nt_errstr(status));
+ smbd_server_connection_terminate(xconn, nt_errstr(status));
return;
}
status = smbd_smb2_request_validate(req);
if (!NT_STATUS_IS_OK(status)) {
- smbd_server_connection_terminate(sconn, nt_errstr(status));
+ smbd_server_connection_terminate(xconn, nt_errstr(status));
return;
}
status = smbd_smb2_request_setup_out(req);
if (!NT_STATUS_IS_OK(status)) {
- smbd_server_connection_terminate(sconn, nt_errstr(status));
+ smbd_server_connection_terminate(xconn, nt_errstr(status));
return;
}
+#ifdef WITH_PROFILE
+ /*
+ * this was already counted at the SMB1 layer =>
+ * smbd_smb2_request_dispatch() should not count it twice.
+ */
+ if (profile_p->values.request_stats.count > 0) {
+ profile_p->values.request_stats.count--;
+ }
+#endif
status = smbd_smb2_request_dispatch(req);
if (!NT_STATUS_IS_OK(status)) {
- smbd_server_connection_terminate(sconn, nt_errstr(status));
+ smbd_server_connection_terminate(xconn, nt_errstr(status));
return;
}
- status = smbd_smb2_request_next_incoming(sconn);
+ status = smbd_smb2_request_next_incoming(xconn);
if (!NT_STATUS_IS_OK(status)) {
- smbd_server_connection_terminate(sconn, nt_errstr(status));
+ smbd_server_connection_terminate(xconn, nt_errstr(status));
return;
}
return sys_errno;
}
-static NTSTATUS smbd_smb2_flush_send_queue(struct smbd_server_connection *sconn)
+static NTSTATUS smbd_smb2_flush_send_queue(struct smbXsrv_connection *xconn)
{
- struct smbXsrv_connection *xconn = sconn->conn;
int ret;
int err;
bool retry;
+ NTSTATUS status;
if (xconn->smb2.send_queue == NULL) {
TEVENT_FD_NOT_WRITEABLE(xconn->transport.fde);
while (xconn->smb2.send_queue != NULL) {
struct smbd_smb2_send_queue *e = xconn->smb2.send_queue;
+ bool ok;
if (e->sendfile_header != NULL) {
size_t size = 0;
size_t i = 0;
uint8_t *buf;
+ status = NT_STATUS_INTERNAL_ERROR;
+
for (i=0; i < e->count; i++) {
size += e->vector[i].iov_len;
}
e->sendfile_header->data = buf;
e->sendfile_header->length = size;
+ e->sendfile_status = &status;
e->count = 0;
xconn->smb2.send_queue_len--;
* the destructor.
*/
talloc_free(e->mem_ctx);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
continue;
}
if (err != 0) {
return map_nt_error_from_unix_common(err);
}
- while (ret > 0) {
- if (ret < e->vector[0].iov_len) {
- uint8_t *base;
- base = (uint8_t *)e->vector[0].iov_base;
- base += ret;
- e->vector[0].iov_base = (void *)base;
- e->vector[0].iov_len -= ret;
- break;
- }
- ret -= e->vector[0].iov_len;
- e->vector += 1;
- e->count -= 1;
- }
- /*
- * there're maybe some empty vectors at the end
- * which we need to skip, otherwise we would get
- * ret == 0 from the readv() call and return EPIPE
- */
- while (e->count > 0) {
- if (e->vector[0].iov_len > 0) {
- break;
- }
- e->vector += 1;
- e->count -= 1;
+ ok = iov_advance(&e->vector, &e->count, ret);
+ if (!ok) {
+ return NT_STATUS_INTERNAL_ERROR;
}
if (e->count > 0) {
talloc_free(e->mem_ctx);
}
+ /*
+ * Restart reads if we were blocked on
+ * draining the send queue.
+ */
+
+ status = smbd_smb2_request_next_incoming(xconn);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
return NT_STATUS_OK;
}
-static NTSTATUS smbd_smb2_io_handler(struct smbd_server_connection *sconn,
+static NTSTATUS smbd_smb2_io_handler(struct smbXsrv_connection *xconn,
uint16_t fde_flags)
{
- struct smbXsrv_connection *xconn = sconn->conn;
+ struct smbd_server_connection *sconn = xconn->client->sconn;
struct smbd_smb2_request_read_state *state = &xconn->smb2.request_read_state;
struct smbd_smb2_request *req = NULL;
size_t min_recvfile_size = UINT32_MAX;
}
if (fde_flags & TEVENT_FD_WRITE) {
- status = smbd_smb2_flush_send_queue(sconn);
+ status = smbd_smb2_flush_send_queue(xconn);
if (!NT_STATUS_IS_OK(status)) {
return status;
}
req->request_time = timeval_current();
now = timeval_to_nttime(&req->request_time);
- status = smbd_smb2_inbuf_parse_compound(req->sconn->conn,
+ status = smbd_smb2_inbuf_parse_compound(xconn,
now,
state->pktbuf,
state->pktlen,
check_log_size();
}
- status = smbd_smb2_request_next_incoming(sconn);
+ status = smbd_smb2_request_next_incoming(xconn);
if (!NT_STATUS_IS_OK(status)) {
return status;
}
uint16_t flags,
void *private_data)
{
- struct smbd_server_connection *sconn =
+ struct smbXsrv_connection *xconn =
talloc_get_type_abort(private_data,
- struct smbd_server_connection);
+ struct smbXsrv_connection);
NTSTATUS status;
- status = smbd_smb2_io_handler(sconn, flags);
+ status = smbd_smb2_io_handler(xconn, flags);
if (!NT_STATUS_IS_OK(status)) {
- smbd_server_connection_terminate(sconn, nt_errstr(status));
+ smbd_server_connection_terminate(xconn, nt_errstr(status));
return;
}
}