*/
#include "includes.h"
+#include "smbd/smbd.h"
#include "smbd/globals.h"
#include "../libcli/smb/smb_common.h"
#include "../lib/tsocket/tsocket.h"
+#include "../lib/util/tevent_ntstatus.h"
+#include "smbprofile.h"
+#include "../lib/util/bitmap.h"
+#include "../librpc/gen_ndr/krb5pac.h"
+#include "auth.h"
#define OUTVEC_ALLOC_SIZE (SMB2_HDR_BODY + 9)
TALLOC_FREE(sconn->smb1.fde);
- sconn->smb2.event_ctx = smbd_event_context();
-
sconn->smb2.recv_queue = tevent_queue_create(sconn, "smb2 recv queue");
if (sconn->smb2.recv_queue == NULL) {
return NT_STATUS_NO_MEMORY;
sconn->smb2.seqnum_low = 0;
sconn->smb2.credits_granted = 0;
sconn->smb2.max_credits = lp_smb2_max_credits();
- sconn->smb2.credits_bitmap = bitmap_talloc(sconn, 2*sconn->smb2.max_credits);
+ sconn->smb2.credits_bitmap = bitmap_talloc(sconn,
+ DEFAULT_SMB2_MAX_CREDIT_BITMAP_FACTOR*sconn->smb2.max_credits);
if (sconn->smb2.credits_bitmap == NULL) {
return NT_STATUS_NO_MEMORY;
}
req->mem_pool = mem_pool;
req->parent = parent;
+ req->last_session_id = UINT64_MAX;
+ req->last_tid = UINT32_MAX;
+
talloc_set_destructor(parent, smbd_smb2_request_parent_destructor);
talloc_set_destructor(req, smbd_smb2_request_destructor);
memcpy(req->in.nbt_hdr, inbuf, 4);
ofs = 0;
- req->in.vector[0].iov_base = (void *)req->in.nbt_hdr;
+ req->in.vector[0].iov_base = discard_const_p(void, req->in.nbt_hdr);
req->in.vector[0].iov_len = 4;
ofs += req->in.vector[0].iov_len;
- req->in.vector[1].iov_base = (void *)(inbuf + ofs);
+ req->in.vector[1].iov_base = discard_const_p(void, (inbuf + ofs));
req->in.vector[1].iov_len = SMB2_HDR_BODY;
ofs += req->in.vector[1].iov_len;
- req->in.vector[2].iov_base = (void *)(inbuf + ofs);
+ req->in.vector[2].iov_base = discard_const_p(void, (inbuf + ofs));
req->in.vector[2].iov_len = SVAL(inbuf, ofs) & 0xFFFE;
ofs += req->in.vector[2].iov_len;
return NT_STATUS_INVALID_PARAMETER;
}
- req->in.vector[3].iov_base = (void *)(inbuf + ofs);
+ req->in.vector[3].iov_base = discard_const_p(void, (inbuf + ofs));
req->in.vector[3].iov_len = size - ofs;
ofs += req->in.vector[3].iov_len;
if (message_id < sconn->smb2.seqnum_low ||
message_id > (sconn->smb2.seqnum_low +
- (2*sconn->smb2.credits_granted))) {
+ (sconn->smb2.max_credits * DEFAULT_SMB2_MAX_CREDIT_BITMAP_FACTOR))) {
DEBUG(0,("smb2_validate_message_id: bad message_id "
- "%llu (low = %llu, granted = %lu)\n",
+ "%llu (low = %llu, max = %lu)\n",
(unsigned long long)message_id,
(unsigned long long)sconn->smb2.seqnum_low,
- (unsigned long)sconn->smb2.credits_granted ));
+ (unsigned long)sconn->smb2.max_credits ));
+ return false;
+ }
+
+ if (sconn->smb2.credits_granted == 0) {
+ DEBUG(0,("smb2_validate_message_id: client used more "
+ "credits than granted message_id (%llu)\n",
+ (unsigned long long)message_id));
return false;
}
/* client just used a credit. */
- SMB_ASSERT(sconn->smb2.credits_granted > 0);
sconn->smb2.credits_granted -= 1;
/* Mark the message_id as seen in the bitmap. */
bitmap_offset = (unsigned int)(message_id %
- (uint64_t)(sconn->smb2.max_credits * 2));
+ (uint64_t)(sconn->smb2.max_credits * DEFAULT_SMB2_MAX_CREDIT_BITMAP_FACTOR));
if (bitmap_query(credits_bm, bitmap_offset)) {
DEBUG(0,("smb2_validate_message_id: duplicate message_id "
"%llu (bm offset %u)\n",
bitmap_clear(credits_bm, bitmap_offset);
sconn->smb2.seqnum_low += 1;
bitmap_offset = (bitmap_offset + 1) %
- (sconn->smb2.max_credits * 2);
+ (sconn->smb2.max_credits * DEFAULT_SMB2_MAX_CREDIT_BITMAP_FACTOR);
}
}
{
int count;
int idx;
- bool compound_related = false;
count = req->in.vector_count;
* compounded requests
*/
if (flags & SMB2_HDR_FLAG_CHAINED) {
- compound_related = true;
+ req->compound_related = true;
}
} else if (idx > 4) {
#if 0
* all other requests should match the 2nd one
*/
if (flags & SMB2_HDR_FLAG_CHAINED) {
- if (!compound_related) {
+ if (!req->compound_related) {
req->next_status =
NT_STATUS_INVALID_PARAMETER;
return NT_STATUS_OK;
}
} else {
- if (compound_related) {
+ if (req->compound_related) {
req->next_status =
NT_STATUS_INVALID_PARAMETER;
return NT_STATUS_OK;
const struct iovec *in_vector,
struct iovec *out_vector)
{
- uint8_t *outhdr = out_vector->iov_base;
- uint16_t credits_requested = 0;
+ const uint8_t *inhdr = (const uint8_t *)in_vector->iov_base;
+ uint8_t *outhdr = (uint8_t *)out_vector->iov_base;
+ uint16_t credits_requested;
+ uint32_t out_flags;
uint16_t credits_granted = 0;
- if (in_vector != NULL) {
- const uint8_t *inhdr = (const uint8_t *)in_vector->iov_base;
- credits_requested = SVAL(inhdr, SMB2_HDR_CREDIT);
- }
+ credits_requested = SVAL(inhdr, SMB2_HDR_CREDIT);
+ out_flags = IVAL(outhdr, SMB2_HDR_FLAGS);
SMB_ASSERT(sconn->smb2.max_credits >= sconn->smb2.credits_granted);
- /* Remember what we gave out. */
- credits_granted = MIN(credits_requested, (sconn->smb2.max_credits -
- sconn->smb2.credits_granted));
+ if (out_flags & SMB2_HDR_FLAG_ASYNC) {
+ /*
+ * In case we already send an async interim
+ * response, we should not grant
+ * credits on the final response.
+ */
+ credits_requested = 0;
+ }
+
+ if (credits_requested) {
+ uint16_t modified_credits_requested;
+ uint32_t multiplier;
+
+ /*
+ * Split up max_credits into 1/16ths, and then scale
+ * the requested credits by how many 16ths have been
+ * currently granted. Less than 1/16th == grant all
+ * requested (100%), scale down as more have been
+ * granted. Never ask for less than 1 as the client
+ * asked for at least 1. JRA.
+ */
+
+ multiplier = 16 - (((sconn->smb2.credits_granted * 16) / sconn->smb2.max_credits) % 16);
+
+ modified_credits_requested = (multiplier * credits_requested) / 16;
+ if (modified_credits_requested == 0) {
+ modified_credits_requested = 1;
+ }
+
+ /* Remember what we gave out. */
+ credits_granted = MIN(modified_credits_requested,
+ (sconn->smb2.max_credits - sconn->smb2.credits_granted));
+ }
if (credits_granted == 0 && sconn->smb2.credits_granted == 0) {
/* First negprot packet, or ensure the client credits can
struct smbd_smb2_request *outreq)
{
int count, idx;
+ uint16_t total_credits = 0;
count = outreq->out.vector_count;
for (idx=1; idx < count; idx += 3) {
+ uint8_t *outhdr = (uint8_t *)outreq->out.vector[idx].iov_base;
smb2_set_operation_credit(outreq->sconn,
&inreq->in.vector[idx],
&outreq->out.vector[idx]);
+ /* To match Windows, count up what we
+ just granted. */
+ total_credits += SVAL(outhdr, SMB2_HDR_CREDIT);
+ /* Set to zero in all but the last reply. */
+ if (idx + 3 < count) {
+ SSVAL(outhdr, SMB2_HDR_CREDIT, 0);
+ } else {
+ SSVAL(outhdr, SMB2_HDR_CREDIT, total_credits);
+ }
}
}
for (idx=1; idx < count; idx += 3) {
const uint8_t *inhdr = NULL;
- uint32_t in_flags;
uint8_t *outhdr = NULL;
uint8_t *outbody = NULL;
uint32_t next_command_ofs = 0;
}
inhdr = (const uint8_t *)req->in.vector[idx].iov_base;
- in_flags = IVAL(inhdr, SMB2_HDR_FLAGS);
outhdr = talloc_zero_array(vector, uint8_t,
OUTVEC_ALLOC_SIZE);
/* setup the SMB2 header */
SIVAL(outhdr, SMB2_HDR_PROTOCOL_ID, SMB2_MAGIC);
SSVAL(outhdr, SMB2_HDR_LENGTH, SMB2_HDR_BODY);
- SSVAL(outhdr, SMB2_HDR_EPOCH, 0);
+ SSVAL(outhdr, SMB2_HDR_CREDIT_CHARGE,
+ SVAL(inhdr, SMB2_HDR_CREDIT_CHARGE));
SIVAL(outhdr, SMB2_HDR_STATUS,
NT_STATUS_V(NT_STATUS_INTERNAL_ERROR));
SSVAL(outhdr, SMB2_HDR_OPCODE,
IVAL(inhdr, SMB2_HDR_TID));
SBVAL(outhdr, SMB2_HDR_SESSION_ID,
BVAL(inhdr, SMB2_HDR_SESSION_ID));
- memset(outhdr + SMB2_HDR_SIGNATURE, 0, 16);
+ memcpy(outhdr + SMB2_HDR_SIGNATURE,
+ inhdr + SMB2_HDR_SIGNATURE, 16);
/* setup error body header */
SSVAL(outbody, 0x00, 0x08 + 1);
srcvec[1].iov_base ==
((uint8_t *)srcvec[0].iov_base) +
SMB2_HDR_BODY) {
- outvec[1].iov_base = ((uint8_t *)outvec[1].iov_base) +
+ outvec[1].iov_base = ((uint8_t *)outvec[0].iov_base) +
SMB2_HDR_BODY;
outvec[1].iov_len = 8;
} else {
}
newreq->sconn = req->sconn;
+ newreq->session = req->session;
newreq->do_signing = req->do_signing;
newreq->current_idx = req->current_idx;
- newreq->async = false;
- newreq->cancelled = false;
outvec = talloc_zero_array(newreq, struct iovec, count);
if (!outvec) {
print_req_vectors(nreq);
}
nreq->subreq = tstream_writev_queue_send(nreq,
- nreq->sconn->smb2.event_ctx,
+ nreq->sconn->ev_ctx,
nreq->sconn->smb2.stream,
nreq->sconn->smb2.send_queue,
nreq->out.vector,
TALLOC_FREE(state);
}
+static void smbd_smb2_request_pending_timer(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval current_time,
+ void *private_data);
+
NTSTATUS smbd_smb2_request_pending_queue(struct smbd_smb2_request *req,
- struct tevent_req *subreq)
+ struct tevent_req *subreq,
+ uint32_t defer_time)
{
NTSTATUS status;
- struct smbd_smb2_request_pending_state *state = NULL;
int i = req->current_idx;
- uint8_t *reqhdr = NULL;
- uint8_t *hdr = NULL;
- uint8_t *body = NULL;
- uint32_t flags = 0;
- uint64_t message_id = 0;
- uint64_t async_id = 0;
- struct iovec *outvec = NULL;
+ struct timeval defer_endtime;
+ uint8_t *outhdr = NULL;
+ uint32_t flags;
if (!tevent_req_is_in_progress(subreq)) {
return NT_STATUS_OK;
req->subreq = subreq;
subreq = NULL;
- if (req->async) {
+ if (req->async_te) {
+ /* We're already async. */
+ return NT_STATUS_OK;
+ }
+
+ outhdr = (uint8_t *)req->out.vector[i].iov_base;
+ flags = IVAL(outhdr, SMB2_HDR_FLAGS);
+ if (flags & SMB2_HDR_FLAG_ASYNC) {
/* We're already async. */
return NT_STATUS_OK;
}
}
if (req->out.vector_count > 4) {
+ struct iovec *outvec = NULL;
+
/* This is a compound reply. We
* must do an interim response
* followed by the async response
if (!NT_STATUS_IS_OK(status)) {
return status;
}
+
+ /*
+ * We're splitting off the last SMB2
+ * request in a compound set, and the
+ * smb2_send_async_interim_response()
+ * call above just sent all the replies
+ * for the previous SMB2 requests in
+ * this compound set. So we're no longer
+ * in the "compound_related_in_progress"
+ * state, and this is no longer a compound
+ * request.
+ */
+ req->compound_related = false;
+ req->sconn->smb2.compound_related_in_progress = false;
+
+ /* Re-arrange the in.vectors. */
+ req->in.vector[1] = req->in.vector[i];
+ req->in.vector[2] = req->in.vector[i+1];
+ req->in.vector[3] = req->in.vector[i+2];
+ req->in.vector_count = 4;
+
+ /* Reset the new in size. */
+ smb2_setup_nbt_length(req->in.vector, 4);
+
+ /* Now recreate the out.vectors. */
+ outvec = talloc_zero_array(req, struct iovec, 4);
+ if (!outvec) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* 0 is always boilerplate and must
+ * be of size 4 for the length field. */
+
+ outvec[0].iov_base = req->out.nbt_hdr;
+ outvec[0].iov_len = 4;
+ SIVAL(req->out.nbt_hdr, 0, 0);
+
+ if (!dup_smb2_vec3(outvec, &outvec[1], &req->out.vector[i])) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ TALLOC_FREE(req->out.vector);
+
+ req->out.vector = outvec;
+
+ req->current_idx = 1;
+ req->out.vector_count = 4;
+
+ outhdr = (uint8_t *)req->out.vector[1].iov_base;
+ flags = (IVAL(outhdr, SMB2_HDR_FLAGS) & ~SMB2_HDR_FLAG_CHAINED);
+ SIVAL(outhdr, SMB2_HDR_FLAGS, flags);
}
- /* Don't return an intermediate packet on a pipe read/write. */
- if (req->tcon && req->tcon->compat_conn && IS_IPC(req->tcon->compat_conn)) {
- return NT_STATUS_OK;
+ defer_endtime = timeval_current_ofs_usec(defer_time);
+ req->async_te = tevent_add_timer(req->sconn->ev_ctx,
+ req, defer_endtime,
+ smbd_smb2_request_pending_timer,
+ req);
+ if (req->async_te == NULL) {
+ return NT_STATUS_NO_MEMORY;
}
- reqhdr = (uint8_t *)req->out.vector[i].iov_base;
- flags = (IVAL(reqhdr, SMB2_HDR_FLAGS) & ~SMB2_HDR_FLAG_CHAINED);
- message_id = BVAL(reqhdr, SMB2_HDR_MESSAGE_ID);
+ return NT_STATUS_OK;
+}
+
+static void smbd_smb2_request_pending_timer(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval current_time,
+ void *private_data)
+{
+ struct smbd_smb2_request *req =
+ talloc_get_type_abort(private_data,
+ struct smbd_smb2_request);
+ struct smbd_smb2_request_pending_state *state = NULL;
+ int i = req->current_idx;
+ uint8_t *outhdr = NULL;
+ const uint8_t *inhdr = NULL;
+ uint8_t *hdr = NULL;
+ uint8_t *body = NULL;
+ uint32_t flags = 0;
+ uint64_t message_id = 0;
+ uint64_t async_id = 0;
+ struct tevent_req *subreq = NULL;
+
+ TALLOC_FREE(req->async_te);
+
+ /* Ensure our final reply matches the interim one. */
+ inhdr = (const uint8_t *)req->in.vector[1].iov_base;
+ outhdr = (uint8_t *)req->out.vector[1].iov_base;
+ flags = IVAL(outhdr, SMB2_HDR_FLAGS);
+ message_id = BVAL(outhdr, SMB2_HDR_MESSAGE_ID);
+
async_id = message_id; /* keep it simple for now... */
+ SIVAL(outhdr, SMB2_HDR_FLAGS, flags | SMB2_HDR_FLAG_ASYNC);
+ SBVAL(outhdr, SMB2_HDR_ASYNC_ID, async_id);
+
+ DEBUG(10,("smbd_smb2_request_pending_queue: opcode[%s] mid %llu "
+ "going async\n",
+ smb2_opcode_name((uint16_t)IVAL(inhdr, SMB2_HDR_OPCODE)),
+ (unsigned long long)async_id ));
+
/*
* What we send is identical to a smbd_smb2_request_error
* packet with an error status of STATUS_PENDING. Make use
state = talloc_zero(req->sconn, struct smbd_smb2_request_pending_state);
if (state == NULL) {
- return NT_STATUS_NO_MEMORY;
+ smbd_server_connection_terminate(req->sconn,
+ nt_errstr(NT_STATUS_NO_MEMORY));
+ return;
}
state->sconn = req->sconn;
SSVAL(hdr, SMB2_HDR_LENGTH, SMB2_HDR_BODY);
SSVAL(hdr, SMB2_HDR_EPOCH, 0);
SIVAL(hdr, SMB2_HDR_STATUS, NT_STATUS_V(STATUS_PENDING));
- SSVAL(hdr, SMB2_HDR_OPCODE, SVAL(reqhdr, SMB2_HDR_OPCODE));
+ SSVAL(hdr, SMB2_HDR_OPCODE, SVAL(outhdr, SMB2_HDR_OPCODE));
- SIVAL(hdr, SMB2_HDR_FLAGS, flags | SMB2_HDR_FLAG_ASYNC);
+ SIVAL(hdr, SMB2_HDR_FLAGS, flags);
SIVAL(hdr, SMB2_HDR_NEXT_COMMAND, 0);
SBVAL(hdr, SMB2_HDR_MESSAGE_ID, message_id);
SBVAL(hdr, SMB2_HDR_PID, async_id);
SBVAL(hdr, SMB2_HDR_SESSION_ID,
- BVAL(reqhdr, SMB2_HDR_SESSION_ID));
- memset(hdr+SMB2_HDR_SIGNATURE, 0, 16);
+ BVAL(outhdr, SMB2_HDR_SESSION_ID));
+ memcpy(hdr+SMB2_HDR_SIGNATURE,
+ outhdr+SMB2_HDR_SIGNATURE, 16);
SSVAL(body, 0x00, 0x08 + 1);
/* Match W2K8R2... */
SCVAL(body, 0x08, 0x21);
- /* Ensure we correctly go through crediting. */
+ /* Ensure we correctly go through crediting. Grant
+ the credits now, and zero credits on the final
+ response. */
smb2_set_operation_credit(req->sconn,
- NULL,
+ &req->in.vector[i],
&state->vector[1]);
+ SIVAL(hdr, SMB2_HDR_FLAGS, flags | SMB2_HDR_FLAG_ASYNC);
+
if (req->do_signing) {
+ NTSTATUS status;
+
status = smb2_signing_sign_pdu(req->session->session_key,
- state->vector, 3);
+ &state->vector[1], 2);
if (!NT_STATUS_IS_OK(status)) {
- return status;
+ smbd_server_connection_terminate(req->sconn,
+ nt_errstr(status));
+ return;
}
}
subreq = tstream_writev_queue_send(state,
- req->sconn->smb2.event_ctx,
- req->sconn->smb2.stream,
- req->sconn->smb2.send_queue,
+ state->sconn->ev_ctx,
+ state->sconn->smb2.stream,
+ state->sconn->smb2.send_queue,
state->vector,
3);
-
if (subreq == NULL) {
- return NT_STATUS_NO_MEMORY;
+ smbd_server_connection_terminate(state->sconn,
+ nt_errstr(NT_STATUS_NO_MEMORY));
+ return;
}
-
tevent_req_set_callback(subreq,
smbd_smb2_request_pending_writev_done,
state);
-
- /* Note we're going async with this request. */
- req->async = true;
-
- /*
- * Now manipulate req so that the outstanding async request
- * is the only one left in the struct smbd_smb2_request.
- */
-
- if (req->current_idx == 1) {
- /* There was only one. */
- goto out;
- }
-
- /* Re-arrange the in.vectors. */
- req->in.vector[1] = req->in.vector[i];
- req->in.vector[2] = req->in.vector[i+1];
- req->in.vector[3] = req->in.vector[i+2];
- req->in.vector_count = 4;
- /* Reset the new in size. */
- smb2_setup_nbt_length(req->in.vector, 4);
-
- /* Now recreate the out.vectors. */
- outvec = talloc_zero_array(req, struct iovec, 4);
- if (!outvec) {
- return NT_STATUS_NO_MEMORY;
- }
-
- /* 0 is always boilerplate and must
- * be of size 4 for the length field. */
-
- outvec[0].iov_base = req->out.nbt_hdr;
- outvec[0].iov_len = 4;
- SIVAL(req->out.nbt_hdr, 0, 0);
-
- if (!dup_smb2_vec3(outvec, &outvec[1], &req->out.vector[i])) {
- return NT_STATUS_NO_MEMORY;
- }
-
- TALLOC_FREE(req->out.vector);
-
- req->out.vector = outvec;
-
- req->current_idx = 1;
- req->out.vector_count = 4;
-
- out:
-
- smb2_setup_nbt_length(req->out.vector,
- req->out.vector_count);
-
- /* Ensure our final reply matches the interim one. */
- reqhdr = (uint8_t *)req->out.vector[1].iov_base;
- SIVAL(reqhdr, SMB2_HDR_FLAGS, flags | SMB2_HDR_FLAG_ASYNC);
- SBVAL(reqhdr, SMB2_HDR_PID, async_id);
-
- {
- const uint8_t *inhdr =
- (const uint8_t *)req->in.vector[1].iov_base;
- DEBUG(10,("smbd_smb2_request_pending_queue: opcode[%s] mid %llu "
- "going async\n",
- smb2_opcode_name((uint16_t)IVAL(inhdr, SMB2_HDR_OPCODE)),
- (unsigned long long)async_id ));
- }
- return NT_STATUS_OK;
}
static NTSTATUS smbd_smb2_request_process_cancel(struct smbd_smb2_request *req)
return NT_STATUS_OK;
}
+/*************************************************************
+ Ensure an incoming tid is a valid one for us to access.
+ Change to the associated uid credentials and chdir to the
+ valid tid directory.
+*************************************************************/
+
+static NTSTATUS smbd_smb2_request_check_tcon(struct smbd_smb2_request *req)
+{
+ const uint8_t *inhdr;
+ int i = req->current_idx;
+ uint32_t in_flags;
+ uint32_t in_tid;
+ void *p;
+ struct smbd_smb2_tcon *tcon;
+
+ req->tcon = NULL;
+
+ inhdr = (const uint8_t *)req->in.vector[i+0].iov_base;
+
+ in_flags = IVAL(inhdr, SMB2_HDR_FLAGS);
+ in_tid = IVAL(inhdr, SMB2_HDR_TID);
+
+ if (in_flags & SMB2_HDR_FLAG_CHAINED) {
+ in_tid = req->last_tid;
+ }
+
+ /* lookup an existing session */
+ p = idr_find(req->session->tcons.idtree, in_tid);
+ if (p == NULL) {
+ return NT_STATUS_NETWORK_NAME_DELETED;
+ }
+ tcon = talloc_get_type_abort(p, struct smbd_smb2_tcon);
+
+ if (!change_to_user(tcon->compat_conn,req->session->vuid)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ /* should we pass FLAG_CASELESS_PATHNAMES here? */
+ if (!set_current_service(tcon->compat_conn, 0, true)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ req->tcon = tcon;
+ req->last_tid = in_tid;
+
+ return NT_STATUS_OK;
+}
+
+/*************************************************************
+ Ensure an incoming session_id is a valid one for us to access.
+*************************************************************/
+
+static NTSTATUS smbd_smb2_request_check_session(struct smbd_smb2_request *req)
+{
+ const uint8_t *inhdr;
+ int i = req->current_idx;
+ uint32_t in_flags;
+ uint64_t in_session_id;
+ void *p;
+ struct smbd_smb2_session *session;
+
+ req->session = NULL;
+ req->tcon = NULL;
+
+ inhdr = (const uint8_t *)req->in.vector[i+0].iov_base;
+
+ in_flags = IVAL(inhdr, SMB2_HDR_FLAGS);
+ in_session_id = BVAL(inhdr, SMB2_HDR_SESSION_ID);
+
+ if (in_flags & SMB2_HDR_FLAG_CHAINED) {
+ in_session_id = req->last_session_id;
+ }
+
+ /* lookup an existing session */
+ p = idr_find(req->sconn->smb2.sessions.idtree, in_session_id);
+ if (p == NULL) {
+ return NT_STATUS_USER_SESSION_DELETED;
+ }
+ session = talloc_get_type_abort(p, struct smbd_smb2_session);
+
+ if (!NT_STATUS_IS_OK(session->status)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ set_current_user_info(session->session_info->unix_info->sanitized_username,
+ session->session_info->unix_info->unix_name,
+ session->session_info->info->domain_name);
+
+ req->session = session;
+ req->last_session_id = in_session_id;
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS smbd_smb2_request_verify_sizes(struct smbd_smb2_request *req,
+ size_t expected_body_size)
+{
+ const uint8_t *inhdr;
+ uint16_t opcode;
+ const uint8_t *inbody;
+ int i = req->current_idx;
+ size_t body_size;
+ size_t min_dyn_size = expected_body_size & 0x00000001;
+
+ /*
+ * The following should be checked already.
+ */
+ if ((i+2) > req->in.vector_count) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ if (req->in.vector[i+0].iov_len != SMB2_HDR_BODY) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ if (req->in.vector[i+1].iov_len < 2) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ inhdr = (const uint8_t *)req->in.vector[i+0].iov_base;
+ opcode = SVAL(inhdr, SMB2_HDR_OPCODE);
+
+ switch (opcode) {
+ case SMB2_OP_IOCTL:
+ case SMB2_OP_GETINFO:
+ min_dyn_size = 0;
+ break;
+ }
+
+ /*
+ * Now check the expected body size,
+ * where the last byte might be in the
+ * dynnamic section..
+ */
+ if (req->in.vector[i+1].iov_len != (expected_body_size & 0xFFFFFFFE)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ if (req->in.vector[i+2].iov_len < min_dyn_size) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ inbody = (const uint8_t *)req->in.vector[i+1].iov_base;
+
+ body_size = SVAL(inbody, 0x00);
+ if (body_size != expected_body_size) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ return NT_STATUS_OK;
+}
+
NTSTATUS smbd_smb2_request_dispatch(struct smbd_smb2_request *req)
{
const uint8_t *inhdr;
smb2_opcode_name(opcode),
(unsigned long long)mid));
+ if (get_Protocol() >= PROTOCOL_SMB2_02) {
+ /*
+ * once the protocol is negotiated
+ * SMB2_OP_NEGPROT is not allowed anymore
+ */
+ if (opcode == SMB2_OP_NEGPROT) {
+ /* drop the connection */
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ } else {
+ /*
+ * if the protocol is not negotiated yet
+ * only SMB2_OP_NEGPROT is allowed.
+ */
+ if (opcode != SMB2_OP_NEGPROT) {
+ /* drop the connection */
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ }
+
allowed_flags = SMB2_HDR_FLAG_CHAINED |
SMB2_HDR_FLAG_SIGNED |
SMB2_HDR_FLAG_DFS;
return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
}
+ /*
+ * Check if the client provided a valid session id,
+ * if so smbd_smb2_request_check_session() calls
+ * set_current_user_info().
+ *
+ * As some command don't require a valid session id
+ * we defer the check of the session_status
+ */
session_status = smbd_smb2_request_check_session(req);
req->do_signing = false;
if (!NT_STATUS_IS_OK(status)) {
return smbd_smb2_request_error(req, status);
}
+ } else if (opcode == SMB2_OP_CANCEL) {
+ /* Cancel requests are allowed to skip the signing */
} else if (req->session && req->session->do_signing) {
return smbd_smb2_request_error(req, NT_STATUS_ACCESS_DENIED);
}
req->compat_chain_fsp = NULL;
}
+ if (req->compound_related) {
+ req->sconn->smb2.compound_related_in_progress = true;
+ }
+
switch (opcode) {
case SMB2_OP_NEGPROT:
+ /* This call needs to be run as root */
+ change_to_root_user();
+
{
START_PROFILE(smb2_negprot);
return_value = smbd_smb2_request_process_negprot(req);
break;
case SMB2_OP_SESSSETUP:
+ /* This call needs to be run as root */
+ change_to_root_user();
+
{
START_PROFILE(smb2_sesssetup);
return_value = smbd_smb2_request_process_sesssetup(req);
break;
}
+ /* This call needs to be run as root */
+ change_to_root_user();
+
{
START_PROFILE(smb2_logoff);
return_value = smbd_smb2_request_process_logoff(req);
return_value = smbd_smb2_request_error(req, session_status);
break;
}
- status = smbd_smb2_request_check_session(req);
- if (!NT_STATUS_IS_OK(status)) {
- return_value = smbd_smb2_request_error(req, status);
- break;
- }
+
+ /*
+ * This call needs to be run as root.
+ *
+ * smbd_smb2_request_process_tcon()
+ * calls make_connection_snum(), which will call
+ * change_to_user(), when needed.
+ */
+ change_to_root_user();
{
START_PROFILE(smb2_tcon);
return_value = smbd_smb2_request_error(req, session_status);
break;
}
+ /*
+ * This call needs to be run as user.
+ *
+ * smbd_smb2_request_check_tcon()
+ * calls change_to_user() on success.
+ */
status = smbd_smb2_request_check_tcon(req);
if (!NT_STATUS_IS_OK(status)) {
return_value = smbd_smb2_request_error(req, status);
break;
}
+ /* This call needs to be run as root */
+ change_to_root_user();
+
{
START_PROFILE(smb2_tdis);
return_value = smbd_smb2_request_error(req, session_status);
break;
}
+ /*
+ * This call needs to be run as user.
+ *
+ * smbd_smb2_request_check_tcon()
+ * calls change_to_user() on success.
+ */
status = smbd_smb2_request_check_tcon(req);
if (!NT_STATUS_IS_OK(status)) {
return_value = smbd_smb2_request_error(req, status);
return_value = smbd_smb2_request_error(req, session_status);
break;
}
+ /*
+ * This call needs to be run as user.
+ *
+ * smbd_smb2_request_check_tcon()
+ * calls change_to_user() on success.
+ */
status = smbd_smb2_request_check_tcon(req);
if (!NT_STATUS_IS_OK(status)) {
return_value = smbd_smb2_request_error(req, status);
return_value = smbd_smb2_request_error(req, session_status);
break;
}
+ /*
+ * This call needs to be run as user.
+ *
+ * smbd_smb2_request_check_tcon()
+ * calls change_to_user() on success.
+ */
status = smbd_smb2_request_check_tcon(req);
if (!NT_STATUS_IS_OK(status)) {
return_value = smbd_smb2_request_error(req, status);
return_value = smbd_smb2_request_error(req, session_status);
break;
}
+ /*
+ * This call needs to be run as user.
+ *
+ * smbd_smb2_request_check_tcon()
+ * calls change_to_user() on success.
+ */
status = smbd_smb2_request_check_tcon(req);
if (!NT_STATUS_IS_OK(status)) {
return_value = smbd_smb2_request_error(req, status);
return_value = smbd_smb2_request_error(req, session_status);
break;
}
+ /*
+ * This call needs to be run as user.
+ *
+ * smbd_smb2_request_check_tcon()
+ * calls change_to_user() on success.
+ */
status = smbd_smb2_request_check_tcon(req);
if (!NT_STATUS_IS_OK(status)) {
return_value = smbd_smb2_request_error(req, status);
return_value = smbd_smb2_request_error(req, session_status);
break;
}
+ /*
+ * This call needs to be run as user.
+ *
+ * smbd_smb2_request_check_tcon()
+ * calls change_to_user() on success.
+ */
status = smbd_smb2_request_check_tcon(req);
if (!NT_STATUS_IS_OK(status)) {
/* Too ugly to live ? JRA. */
return_value = smbd_smb2_request_error(req, session_status);
break;
}
+ /*
+ * This call needs to be run as user.
+ *
+ * smbd_smb2_request_check_tcon()
+ * calls change_to_user() on success.
+ */
status = smbd_smb2_request_check_tcon(req);
if (!NT_STATUS_IS_OK(status)) {
return_value = smbd_smb2_request_error(req, status);
break;
case SMB2_OP_CANCEL:
+ /*
+ * This call needs to be run as root
+ *
+ * That is what we also do in the SMB1 case.
+ */
+ change_to_root_user();
+
{
START_PROFILE(smb2_cancel);
return_value = smbd_smb2_request_process_cancel(req);
break;
case SMB2_OP_KEEPALIVE:
- {START_PROFILE(smb2_keepalive);
- return_value = smbd_smb2_request_process_keepalive(req);
- END_PROFILE(smb2_keepalive);}
+ /* This call needs to be run as root */
+ change_to_root_user();
+
+ {
+ START_PROFILE(smb2_keepalive);
+ return_value = smbd_smb2_request_process_keepalive(req);
+ END_PROFILE(smb2_keepalive);
+ }
break;
case SMB2_OP_FIND:
return_value = smbd_smb2_request_error(req, session_status);
break;
}
+ /*
+ * This call needs to be run as user.
+ *
+ * smbd_smb2_request_check_tcon()
+ * calls change_to_user() on success.
+ */
status = smbd_smb2_request_check_tcon(req);
if (!NT_STATUS_IS_OK(status)) {
return_value = smbd_smb2_request_error(req, status);
return_value = smbd_smb2_request_error(req, session_status);
break;
}
+ /*
+ * This call needs to be run as user.
+ *
+ * smbd_smb2_request_check_tcon()
+ * calls change_to_user() on success.
+ */
status = smbd_smb2_request_check_tcon(req);
if (!NT_STATUS_IS_OK(status)) {
return_value = smbd_smb2_request_error(req, status);
return_value = smbd_smb2_request_error(req, session_status);
break;
}
+ /*
+ * This call needs to be run as user.
+ *
+ * smbd_smb2_request_check_tcon()
+ * calls change_to_user() on success.
+ */
status = smbd_smb2_request_check_tcon(req);
if (!NT_STATUS_IS_OK(status)) {
return_value = smbd_smb2_request_error(req, status);
return_value = smbd_smb2_request_error(req, session_status);
break;
}
+ /*
+ * This call needs to be run as user.
+ *
+ * smbd_smb2_request_check_tcon()
+ * calls change_to_user() on success.
+ */
status = smbd_smb2_request_check_tcon(req);
if (!NT_STATUS_IS_OK(status)) {
return_value = smbd_smb2_request_error(req, status);
return_value = smbd_smb2_request_error(req, session_status);
break;
}
+ /*
+ * This call needs to be run as user.
+ *
+ * smbd_smb2_request_check_tcon()
+ * calls change_to_user() on success.
+ */
status = smbd_smb2_request_check_tcon(req);
if (!NT_STATUS_IS_OK(status)) {
return_value = smbd_smb2_request_error(req, status);
int i = req->current_idx;
req->subreq = NULL;
-
- smb2_setup_nbt_length(req->out.vector, req->out.vector_count);
-
- /* Set credit for this operation. */
- smb2_set_operation_credit(req->sconn,
- &req->in.vector[i],
- &req->out.vector[i]);
-
- if (req->do_signing) {
- NTSTATUS status;
- status = smb2_signing_sign_pdu(req->session->session_key,
- &req->out.vector[i], 3);
- if (!NT_STATUS_IS_OK(status)) {
- return status;
- }
- }
+ TALLOC_FREE(req->async_te);
req->current_idx += 3;
return NT_STATUS_NO_MEMORY;
}
tevent_schedule_immediate(im,
- req->sconn->smb2.event_ctx,
+ req->sconn->ev_ctx,
smbd_smb2_request_dispatch_immediate,
req);
return NT_STATUS_OK;
}
+ if (req->compound_related) {
+ req->sconn->smb2.compound_related_in_progress = false;
+ }
+
+ smb2_setup_nbt_length(req->out.vector, req->out.vector_count);
+
+ /* Set credit for these operations (zero credits if this
+ is a final reply for an async operation). */
+ smb2_calculate_credits(req, req);
+
+ if (req->do_signing) {
+ NTSTATUS status;
+ status = smb2_signing_sign_pdu(req->session->session_key,
+ &req->out.vector[i], 3);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ }
+
if (DEBUGLEVEL >= 10) {
dbgtext("smbd_smb2_request_reply: sending...\n");
print_req_vectors(req);
}
+ /* I am a sick, sick man... :-). Sendfile hack ... JRA. */
+ if (req->out.vector_count == 4 &&
+ req->out.vector[3].iov_base == NULL &&
+ req->out.vector[3].iov_len != 0) {
+ /* Dynamic part is NULL. Chop it off,
+ We're going to send it via sendfile. */
+ req->out.vector_count -= 1;
+ }
+
subreq = tstream_writev_queue_send(req,
- req->sconn->smb2.event_ctx,
+ req->sconn->ev_ctx,
req->sconn->smb2.stream,
req->sconn->smb2.send_queue,
req->out.vector,
return NT_STATUS_OK;
}
+static NTSTATUS smbd_smb2_request_next_incoming(struct smbd_server_connection *sconn);
+
void smbd_smb2_request_dispatch_immediate(struct tevent_context *ctx,
struct tevent_immediate *im,
void *private_data)
smbd_server_connection_terminate(sconn, nt_errstr(status));
return;
}
+
+ status = smbd_smb2_request_next_incoming(sconn);
+ if (!NT_STATUS_IS_OK(status)) {
+ smbd_server_connection_terminate(sconn, nt_errstr(status));
+ return;
+ }
}
static void smbd_smb2_request_writev_done(struct tevent_req *subreq)
struct smbd_server_connection *sconn = req->sconn;
int ret;
int sys_errno;
+ NTSTATUS status;
ret = tstream_writev_queue_recv(subreq, &sys_errno);
TALLOC_FREE(subreq);
TALLOC_FREE(req);
if (ret == -1) {
- NTSTATUS status = map_nt_error_from_unix(sys_errno);
+ status = map_nt_error_from_unix(sys_errno);
DEBUG(2,("smbd_smb2_request_writev_done: client write error %s\n",
nt_errstr(status)));
smbd_server_connection_terminate(sconn, nt_errstr(status));
return;
}
+
+ status = smbd_smb2_request_next_incoming(sconn);
+ if (!NT_STATUS_IS_OK(status)) {
+ smbd_server_connection_terminate(sconn, nt_errstr(status));
+ return;
+ }
}
NTSTATUS smbd_smb2_request_done_ex(struct smbd_smb2_request *req,
SBVAL(body, 0x10, file_id_volatile);
subreq = tstream_writev_queue_send(state,
- sconn->smb2.event_ctx,
+ sconn->ev_ctx,
sconn->smb2.stream,
sconn->smb2.send_queue,
&state->vector, 1);
invalid = true;
}
- if ((body_size % 2) != 0) {
- body_size -= 1;
- }
+ /*
+ * Mask out the lowest bit, the "dynamic" part
+ * of body_size.
+ */
+ body_size &= ~1;
if (body_size > (full_size - SMB2_HDR_BODY)) {
/*
static void smbd_smb2_request_incoming(struct tevent_req *subreq);
+static NTSTATUS smbd_smb2_request_next_incoming(struct smbd_server_connection *sconn)
+{
+ size_t max_send_queue_len;
+ size_t cur_send_queue_len;
+ struct tevent_req *subreq;
+
+ if (sconn->smb2.compound_related_in_progress) {
+ /*
+ * Can't read another until the related
+ * compound is done.
+ */
+ return NT_STATUS_OK;
+ }
+
+ if (tevent_queue_length(sconn->smb2.recv_queue) > 0) {
+ /*
+ * if there is already a smbd_smb2_request_read
+ * pending, we are done.
+ */
+ return NT_STATUS_OK;
+ }
+
+ max_send_queue_len = MAX(1, sconn->smb2.max_credits/16);
+ cur_send_queue_len = tevent_queue_length(sconn->smb2.send_queue);
+
+ if (cur_send_queue_len > max_send_queue_len) {
+ /*
+ * if we have a lot of requests to send,
+ * we wait until they are on the wire until we
+ * ask for the next request.
+ */
+ return NT_STATUS_OK;
+ }
+
+ /* ask for the next request */
+ subreq = smbd_smb2_request_read_send(sconn, sconn->ev_ctx, sconn);
+ if (subreq == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ tevent_req_set_callback(subreq, smbd_smb2_request_incoming, sconn);
+
+ return NT_STATUS_OK;
+}
+
void smbd_smb2_first_negprot(struct smbd_server_connection *sconn,
const uint8_t *inbuf, size_t size)
{
NTSTATUS status;
struct smbd_smb2_request *req = NULL;
- struct tevent_req *subreq;
-
- if (lp_security() == SEC_SHARE) {
- DEBUG(2,("WARNING!!: \"security = share\" is deprecated for "
- "SMB2 servers. Mapping to \"security = user\" and "
- "\"map to guest = Bad User\"\n" ));
- lp_do_parameter(-1, "security", "user");
- lp_do_parameter(-1, "map to guest", "Bad User");
- }
DEBUG(10,("smbd_smb2_first_negprot: packet length %u\n",
(unsigned int)size));
return;
}
- /* ask for the next request */
- subreq = smbd_smb2_request_read_send(sconn, sconn->smb2.event_ctx, sconn);
- if (subreq == NULL) {
- smbd_server_connection_terminate(sconn, "no memory for reading");
+ status = smbd_smb2_request_next_incoming(sconn);
+ if (!NT_STATUS_IS_OK(status)) {
+ smbd_server_connection_terminate(sconn, nt_errstr(status));
return;
}
- tevent_req_set_callback(subreq, smbd_smb2_request_incoming, sconn);
+
+ sconn->num_requests++;
}
static void smbd_smb2_request_incoming(struct tevent_req *subreq)
}
next:
- /* ask for the next request (this constructs the main loop) */
- subreq = smbd_smb2_request_read_send(sconn, sconn->smb2.event_ctx, sconn);
- if (subreq == NULL) {
- smbd_server_connection_terminate(sconn, "no memory for reading");
+ status = smbd_smb2_request_next_incoming(sconn);
+ if (!NT_STATUS_IS_OK(status)) {
+ smbd_server_connection_terminate(sconn, nt_errstr(status));
return;
}
- tevent_req_set_callback(subreq, smbd_smb2_request_incoming, sconn);
+
+ sconn->num_requests++;
+
+ /* The timeout_processing function isn't run nearly
+ often enough to implement 'max log size' without
+ overrunning the size of the file by many megabytes.
+ This is especially true if we are running at debug
+ level 10. Checking every 50 SMB2s is a nice
+ tradeoff of performance vs log file size overrun. */
+
+ if ((sconn->num_requests % 50) == 0 &&
+ need_to_check_log_size()) {
+ change_to_root_user();
+ check_log_size();
+ }
}