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,
.need_session = true,
.need_tcon = true,
.fileid_ofs = 0x10,
+ .modify = true,
},{
_OP(SMB2_OP_BREAK),
.need_session = true,
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 NT_STATUS_INVALID_PARAMETER_MIX;
}
- DLIST_ADD_END(xconn->smb2.requests, req, struct smbd_smb2_request *);
+ DLIST_ADD_END(xconn->smb2.requests, req);
return NT_STATUS_OK;
}
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);
}
nreq->queue_entry.mem_ctx = nreq;
nreq->queue_entry.vector = nreq->out.vector;
nreq->queue_entry.count = nreq->out.vector_count;
- DLIST_ADD_END(xconn->smb2.send_queue, &nreq->queue_entry, NULL);
+ DLIST_ADD_END(xconn->smb2.send_queue, &nreq->queue_entry);
xconn->smb2.send_queue_len++;
status = smbd_smb2_flush_send_queue(xconn);
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
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(xconn);
search_message_id = BVAL(inhdr, SMB2_HDR_MESSAGE_ID);
search_async_id = BVAL(inhdr, SMB2_HDR_PID);
+ /*
+ * We don't need the request anymore cancel requests never
+ * have a response.
+ *
+ * We defer the TALLOC_FREE(req) to the caller.
+ */
+ DLIST_REMOVE(xconn->smb2.requests, req);
+
for (cur = xconn->smb2.requests; cur; cur = cur->next) {
const uint8_t *outhdr;
uint64_t message_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;
}
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 *xconn = req->xconn;
session_status = smbd_smb2_request_check_session(req);
x = req->session;
if (x != NULL) {
- signing_required = x->global->signing_required;
- encryption_desired = x->encryption_desired;
- encryption_required = x->global->encryption_required;
+ 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 (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);
}
if (!NT_STATUS_IS_OK(status)) {
return smbd_smb2_request_error(req, status);
}
- if (req->tcon->encryption_desired) {
+ if (req->tcon->global->encryption_flags & SMBXSRV_ENCRYPTION_DESIRED) {
encryption_desired = true;
}
- if (req->tcon->global->encryption_required) {
+ if (req->tcon->global->encryption_flags & SMBXSRV_ENCRYPTION_REQUIRED) {
encryption_required = true;
}
if (encryption_required && !req->was_encrypted) {
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 */
/*
* 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);
*/
- DLIST_REMOVE(xconn->smb2.requests, req);
TALLOC_FREE(req);
break;
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 smbXsrv_connection *xconn = req->xconn;
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) &&
req->queue_entry.mem_ctx = req;
req->queue_entry.vector = req->out.vector;
req->queue_entry.count = req->out.vector_count;
- DLIST_ADD_END(xconn->smb2.send_queue, &req->queue_entry, NULL);
+ DLIST_ADD_END(xconn->smb2.send_queue, &req->queue_entry);
xconn->smb2.send_queue_len++;
status = smbd_smb2_flush_send_queue(xconn);
if (session != NULL) {
session_wire_id = session->global->session_wire_id;
- do_encryption = session->encryption_desired;
- if (tcon->encryption_desired) {
+ do_encryption = session->global->encryption_flags & SMBXSRV_ENCRYPTION_DESIRED;
+ if (tcon->global->encryption_flags & SMBXSRV_ENCRYPTION_DESIRED) {
do_encryption = true;
}
}
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(xconn);
int ret;
int err;
bool retry;
+ NTSTATUS status;
if (xconn->smb2.send_queue == NULL) {
TEVENT_FD_NOT_WRITEABLE(xconn->transport.fde);
bool ok;
if (e->sendfile_header != NULL) {
- NTSTATUS status = NT_STATUS_INTERNAL_ERROR;
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;
}
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;
}