#include "../librpc/gen_ndr/krb5pac.h"
#include "auth.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_io_handler(struct smbd_server_connection *sconn,
+ uint16_t fde_flags);
+
#define OUTVEC_ALLOC_SIZE (SMB2_HDR_BODY + 9)
static const struct smbd_smb2_dispatch_table {
bool need_session;
bool need_tcon;
bool as_root;
+ uint16_t fileid_ofs;
+ bool allow_invalid_fileid;
} smbd_smb2_table[] = {
#define _OP(o) .opcode = o, .name = #o
{
_OP(SMB2_OP_CLOSE),
.need_session = true,
.need_tcon = true,
+ .fileid_ofs = 0x08,
},{
_OP(SMB2_OP_FLUSH),
.need_session = true,
.need_tcon = true,
+ .fileid_ofs = 0x08,
},{
_OP(SMB2_OP_READ),
.need_session = true,
.need_tcon = true,
+ .fileid_ofs = 0x10,
},{
_OP(SMB2_OP_WRITE),
.need_session = true,
.need_tcon = true,
+ .fileid_ofs = 0x10,
},{
_OP(SMB2_OP_LOCK),
.need_session = true,
.need_tcon = true,
+ .fileid_ofs = 0x08,
},{
_OP(SMB2_OP_IOCTL),
.need_session = true,
.need_tcon = true,
+ .fileid_ofs = 0x08,
+ .allow_invalid_fileid = true,
},{
_OP(SMB2_OP_CANCEL),
.as_root = true,
_OP(SMB2_OP_FIND),
.need_session = true,
.need_tcon = true,
+ .fileid_ofs = 0x08,
},{
_OP(SMB2_OP_NOTIFY),
.need_session = true,
.need_tcon = true,
+ .fileid_ofs = 0x08,
},{
_OP(SMB2_OP_GETINFO),
.need_session = true,
.need_tcon = true,
+ .fileid_ofs = 0x18,
},{
_OP(SMB2_OP_SETINFO),
.need_session = true,
.need_tcon = true,
+ .fileid_ofs = 0x10,
},{
_OP(SMB2_OP_BREAK),
.need_session = true,
.need_tcon = true,
+ /*
+ * we do not set
+ * .fileid_ofs here
+ * as LEASE breaks does not
+ * have a file id
+ */
}
};
return ret;
}
-static void print_req_vectors(struct smbd_smb2_request *req)
+static void print_req_vectors(const struct smbd_smb2_request *req)
{
int i;
static NTSTATUS smbd_initialize_smb2(struct smbd_server_connection *sconn)
{
- NTSTATUS status;
- int ret;
-
TALLOC_FREE(sconn->smb1.fde);
- sconn->smb2.recv_queue = tevent_queue_create(sconn, "smb2 recv queue");
- if (sconn->smb2.recv_queue == NULL) {
- return NT_STATUS_NO_MEMORY;
- }
-
- sconn->smb2.send_queue = tevent_queue_create(sconn, "smb2 send queue");
- if (sconn->smb2.send_queue == NULL) {
- return NT_STATUS_NO_MEMORY;
- }
+ sconn->smb2.send_queue = NULL;
sconn->smb2.seqnum_low = 0;
sconn->smb2.seqnum_range = 1;
return NT_STATUS_NO_MEMORY;
}
- ret = tstream_bsd_existing_socket(sconn, sconn->sock,
- &sconn->smb2.stream);
- if (ret == -1) {
- status = map_nt_error_from_unix(errno);
- return status;
+ sconn->smb2.fde = tevent_add_fd(sconn->ev_ctx,
+ sconn,
+ sconn->sock,
+ TEVENT_FD_READ,
+ smbd_smb2_connection_handler,
+ sconn);
+ if (sconn->smb2.fde == NULL) {
+ return NT_STATUS_NO_MEMORY;
}
/* Ensure child is set to non-blocking mode */
_smb2_setlen(vector[0].iov_base, len);
}
+static int smbd_smb2_request_destructor(struct smbd_smb2_request *req)
+{
+ data_blob_clear_free(&req->first_key);
+ data_blob_clear_free(&req->last_key);
+ return 0;
+}
+
static struct smbd_smb2_request *smbd_smb2_request_allocate(TALLOC_CTX *mem_ctx)
{
TALLOC_CTX *mem_pool;
/* Enable this to find subtle valgrind errors. */
mem_pool = talloc_init("smbd_smb2_request_allocate");
#else
- mem_pool = talloc_pool(mem_ctx, 8192);
+ mem_pool = talloc_tos();
#endif
if (mem_pool == NULL) {
return NULL;
return NULL;
}
talloc_reparent(mem_pool, mem_ctx, req);
+#if 0
TALLOC_FREE(mem_pool);
+#endif
req->last_session_id = UINT64_MAX;
req->last_tid = UINT32_MAX;
+ talloc_set_destructor(req, smbd_smb2_request_destructor);
+
return req;
}
int num_iov = 1;
size_t taken = 0;
uint8_t *first_hdr = buf;
+ size_t verified_buflen = 0;
+ uint8_t *tf = NULL;
+ size_t tf_len = 0;
/*
* Note: index '0' is reserved for the transport protocol
uint8_t *dyn = NULL;
struct iovec *iov_tmp;
+ if (verified_buflen > taken) {
+ len = verified_buflen - taken;
+ } else {
+ tf = NULL;
+ tf_len = 0;
+ }
+
+ if (len < 4) {
+ DEBUG(10, ("%d bytes left, expected at least %d\n",
+ (int)len, 4));
+ goto inval;
+ }
+ if (IVAL(hdr, 0) == SMB2_TF_MAGIC) {
+ struct smbXsrv_session *s = NULL;
+ uint64_t uid;
+ struct iovec tf_iov[2];
+ NTSTATUS status;
+ size_t enc_len;
+
+ if (conn->protocol < PROTOCOL_SMB2_24) {
+ DEBUG(10, ("Got SMB2_TRANSFORM header, "
+ "but dialect[0x%04X] is used\n",
+ conn->smb2.server.dialect));
+ goto inval;
+ }
+
+ if (!(conn->smb2.server.capabilities & SMB2_CAP_ENCRYPTION)) {
+ DEBUG(10, ("Got SMB2_TRANSFORM header, "
+ "but not negotiated "
+ "client[0x%08X] server[0x%08X]\n",
+ conn->smb2.client.capabilities,
+ conn->smb2.server.capabilities));
+ goto inval;
+ }
+
+ if (len < SMB2_TF_HDR_SIZE) {
+ DEBUG(1, ("%d bytes left, expected at least %d\n",
+ (int)len, SMB2_TF_HDR_SIZE));
+ goto inval;
+ }
+ tf = hdr;
+ tf_len = SMB2_TF_HDR_SIZE;
+ taken += tf_len;
+
+ hdr = first_hdr + taken;
+ enc_len = IVAL(tf, SMB2_TF_MSG_SIZE);
+ uid = BVAL(tf, SMB2_TF_SESSION_ID);
+
+ if (len < SMB2_TF_HDR_SIZE + enc_len) {
+ DEBUG(1, ("%d bytes left, expected at least %d\n",
+ (int)len,
+ (int)(SMB2_TF_HDR_SIZE + enc_len)));
+ goto inval;
+ }
+
+ status = smb2srv_session_lookup(conn, uid, now, &s);
+ if (s == NULL) {
+ DEBUG(1, ("invalid session[%llu] in "
+ "SMB2_TRANSFORM header\n",
+ (unsigned long long)uid));
+ TALLOC_FREE(iov);
+ return NT_STATUS_USER_SESSION_DELETED;
+ }
+
+ tf_iov[0].iov_base = (void *)tf;
+ tf_iov[0].iov_len = tf_len;
+ tf_iov[1].iov_base = (void *)hdr;
+ tf_iov[1].iov_len = enc_len;
+
+ status = smb2_signing_decrypt_pdu(s->global->decryption_key,
+ conn->protocol,
+ tf_iov, 2);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(iov);
+ return status;
+ }
+
+ verified_buflen = taken + enc_len;
+ len = enc_len;
+ }
+
/*
* We need the header plus the body length field
*/
cur = &iov[num_iov];
num_iov += SMBD_SMB2_NUM_IOV_PER_REQ;
+ cur[SMBD_SMB2_TF_IOV_OFS].iov_base = tf;
+ cur[SMBD_SMB2_TF_IOV_OFS].iov_len = tf_len;
cur[SMBD_SMB2_HDR_IOV_OFS].iov_base = hdr;
cur[SMBD_SMB2_HDR_IOV_OFS].iov_len = SMB2_HDR_BODY;
cur[SMBD_SMB2_BODY_IOV_OFS].iov_base = body;
const uint8_t *inhdr)
{
uint64_t message_id = BVAL(inhdr, SMB2_HDR_MESSAGE_ID);
- uint16_t opcode = IVAL(inhdr, SMB2_HDR_OPCODE);
+ uint16_t opcode = SVAL(inhdr, SMB2_HDR_OPCODE);
uint16_t credit_charge = 1;
uint64_t i;
struct iovec *hdr = SMBD_SMB2_IDX_HDR_IOV(req,in,idx);
struct iovec *body = SMBD_SMB2_IDX_BODY_IOV(req,in,idx);
const uint8_t *inhdr = NULL;
- uint32_t flags;
if (hdr->iov_len != SMB2_HDR_BODY) {
return NT_STATUS_INVALID_PARAMETER;
if (!smb2_validate_message_id(req->sconn, inhdr)) {
return NT_STATUS_INVALID_PARAMETER;
}
-
- flags = IVAL(inhdr, SMB2_HDR_FLAGS);
- if (idx == 1) {
- /*
- * the 1st request should never have the
- * SMB2_HDR_FLAG_CHAINED flag set
- */
- if (flags & SMB2_HDR_FLAG_CHAINED) {
- req->next_status = NT_STATUS_INVALID_PARAMETER;
- return NT_STATUS_OK;
- }
- } else if (idx == 4) {
- /*
- * the 2nd request triggers related vs. unrelated
- * compounded requests
- */
- if (flags & SMB2_HDR_FLAG_CHAINED) {
- req->compound_related = true;
- }
- } else if (idx > 4) {
-#if 0
- /*
- * It seems the this tests are wrong
- * see the SMB2-COMPOUND test
- */
-
- /*
- * all other requests should match the 2nd one
- */
- if (flags & SMB2_HDR_FLAG_CHAINED) {
- if (!req->compound_related) {
- req->next_status =
- NT_STATUS_INVALID_PARAMETER;
- return NT_STATUS_OK;
- }
- } else {
- if (req->compound_related) {
- req->next_status =
- NT_STATUS_INVALID_PARAMETER;
- return NT_STATUS_OK;
- }
- }
-#endif
- }
}
return NT_STATUS_OK;
*
* Windows also starts with the 1/16th and then grants
* more later. I was only able to trigger higher
- * values, when using a verify high credit charge.
+ * values, when using a very high credit charge.
*
- * TODO: scale up depending one load, free memory
+ * TODO: scale up depending on load, free memory
* or other stuff.
* Maybe also on the relationship between number
* of requests and the used sequence number.
out_status = NT_STATUS(IVAL(outhdr, SMB2_HDR_STATUS));
SMB_ASSERT(sconn->smb2.max_credits >= sconn->smb2.credits_granted);
- SMB_ASSERT(sconn->smb2.max_credits >= credit_charge);
+
+ if (sconn->smb2.max_credits < credit_charge) {
+ smbd_server_connection_terminate(sconn,
+ "client error: credit charge > max credits\n");
+ return;
+ }
if (out_flags & SMB2_HDR_FLAG_ASYNC) {
/*
outbody = outhdr + SMB2_HDR_BODY;
+ /*
+ * SMBD_SMB2_TF_IOV_OFS might be used later
+ */
+ current[SMBD_SMB2_TF_IOV_OFS].iov_base = NULL;
+ current[SMBD_SMB2_TF_IOV_OFS].iov_len = 0;
+
current[SMBD_SMB2_HDR_IOV_OFS].iov_base = (void *)outhdr;
current[SMBD_SMB2_HDR_IOV_OFS].iov_len = SMB2_HDR_BODY;
exit_server_cleanly(reason);
}
-static bool dup_smb2_vec3(TALLOC_CTX *ctx,
+static bool dup_smb2_vec4(TALLOC_CTX *ctx,
struct iovec *outvec,
const struct iovec *srcvec)
{
- /* vec[0] is always boilerplate and must
+ const uint8_t *srctf;
+ size_t srctf_len;
+ const uint8_t *srchdr;
+ size_t srchdr_len;
+ const uint8_t *srcbody;
+ size_t srcbody_len;
+ const uint8_t *expected_srcbody;
+ const uint8_t *srcdyn;
+ size_t srcdyn_len;
+ const uint8_t *expected_srcdyn;
+ uint8_t *dsttf;
+ uint8_t *dsthdr;
+ uint8_t *dstbody;
+ uint8_t *dstdyn;
+
+ srctf = (const uint8_t *)srcvec[SMBD_SMB2_TF_IOV_OFS].iov_base;
+ srctf_len = srcvec[SMBD_SMB2_TF_IOV_OFS].iov_len;
+ srchdr = (const uint8_t *)srcvec[SMBD_SMB2_HDR_IOV_OFS].iov_base;
+ srchdr_len = srcvec[SMBD_SMB2_HDR_IOV_OFS].iov_len;
+ srcbody = (const uint8_t *)srcvec[SMBD_SMB2_BODY_IOV_OFS].iov_base;
+ srcbody_len = srcvec[SMBD_SMB2_BODY_IOV_OFS].iov_len;
+ expected_srcbody = srchdr + SMB2_HDR_BODY;
+ srcdyn = (const uint8_t *)srcvec[SMBD_SMB2_DYN_IOV_OFS].iov_base;
+ srcdyn_len = srcvec[SMBD_SMB2_DYN_IOV_OFS].iov_len;
+ expected_srcdyn = srcbody + 8;
+
+ if ((srctf_len != SMB2_TF_HDR_SIZE) && (srctf_len != 0)) {
+ return false;
+ }
+
+ if (srchdr_len != SMB2_HDR_BODY) {
+ return false;
+ }
+
+ if (srctf_len == SMB2_TF_HDR_SIZE) {
+ dsttf = talloc_memdup(ctx, srctf, SMB2_TF_HDR_SIZE);
+ if (dsttf == NULL) {
+ return false;
+ }
+ } else {
+ dsttf = NULL;
+ }
+ outvec[SMBD_SMB2_TF_IOV_OFS].iov_base = (void *)dsttf;
+ outvec[SMBD_SMB2_TF_IOV_OFS].iov_len = srctf_len;
+
+ /* vec[SMBD_SMB2_HDR_IOV_OFS] is always boilerplate and must
* be allocated with size OUTVEC_ALLOC_SIZE. */
- outvec[0].iov_base = talloc_memdup(ctx,
- srcvec[0].iov_base,
- OUTVEC_ALLOC_SIZE);
- if (!outvec[0].iov_base) {
+ dsthdr = talloc_memdup(ctx, srchdr, OUTVEC_ALLOC_SIZE);
+ if (dsthdr == NULL) {
return false;
}
- outvec[0].iov_len = SMB2_HDR_BODY;
+ outvec[SMBD_SMB2_HDR_IOV_OFS].iov_base = (void *)dsthdr;
+ outvec[SMBD_SMB2_HDR_IOV_OFS].iov_len = SMB2_HDR_BODY;
/*
- * If this is a "standard" vec[1] of length 8,
- * pointing to srcvec[0].iov_base + SMB2_HDR_BODY,
+ * If this is a "standard" vec[SMBD_SMB2_BOFY_IOV_OFS] of length 8,
+ * pointing to srcvec[SMBD_SMB2_HDR_IOV_OFS].iov_base + SMB2_HDR_BODY,
* then duplicate this. Else use talloc_memdup().
*/
- if (srcvec[1].iov_len == 8 &&
- srcvec[1].iov_base ==
- ((uint8_t *)srcvec[0].iov_base) +
- SMB2_HDR_BODY) {
- outvec[1].iov_base = ((uint8_t *)outvec[0].iov_base) +
- SMB2_HDR_BODY;
- outvec[1].iov_len = 8;
+ if ((srcbody == expected_srcbody) && (srcbody_len == 8)) {
+ dstbody = dsthdr + SMB2_HDR_BODY;
} else {
- outvec[1].iov_base = talloc_memdup(ctx,
- srcvec[1].iov_base,
- srcvec[1].iov_len);
- if (!outvec[1].iov_base) {
+ dstbody = talloc_memdup(ctx, srcbody, srcbody_len);
+ if (dstbody == NULL) {
return false;
}
- outvec[1].iov_len = srcvec[1].iov_len;
}
+ outvec[SMBD_SMB2_BODY_IOV_OFS].iov_base = (void *)dstbody;
+ outvec[SMBD_SMB2_BODY_IOV_OFS].iov_len = srcbody_len;
/*
- * If this is a "standard" vec[2] of length 1,
- * pointing to srcvec[0].iov_base + (OUTVEC_ALLOC_SIZE - 1)
+ * If this is a "standard" vec[SMBD_SMB2_DYN_IOV_OFS] of length 1,
+ * pointing to
+ * srcvec[SMBD_SMB2_HDR_IOV_OFS].iov_base + 8
* then duplicate this. Else use talloc_memdup().
*/
- if (srcvec[2].iov_base &&
- srcvec[2].iov_len) {
- if (srcvec[2].iov_base ==
- ((uint8_t *)srcvec[0].iov_base) +
- (OUTVEC_ALLOC_SIZE - 1) &&
- srcvec[2].iov_len == 1) {
- /* Common SMB2 error packet case. */
- outvec[2].iov_base = ((uint8_t *)outvec[0].iov_base) +
- (OUTVEC_ALLOC_SIZE - 1);
- } else {
- outvec[2].iov_base = talloc_memdup(ctx,
- srcvec[2].iov_base,
- srcvec[2].iov_len);
- if (!outvec[2].iov_base) {
- return false;
- }
- }
- outvec[2].iov_len = srcvec[2].iov_len;
+ if ((srcdyn == expected_srcdyn) && (srcdyn_len == 1)) {
+ dstdyn = dsthdr + SMB2_HDR_BODY + 8;
+ } else if (srcdyn == NULL) {
+ dstdyn = NULL;
} else {
- outvec[2].iov_base = NULL;
- outvec[2].iov_len = 0;
+ dstdyn = talloc_memdup(ctx, srcdyn, srcdyn_len);
+ if (dstdyn == NULL) {
+ return false;
+ }
}
+ outvec[SMBD_SMB2_DYN_IOV_OFS].iov_base = (void *)dstdyn;
+ outvec[SMBD_SMB2_DYN_IOV_OFS].iov_len = srcdyn_len;
+
return true;
}
newreq->sconn = req->sconn;
newreq->session = req->session;
+ newreq->do_encryption = req->do_encryption;
newreq->do_signing = req->do_signing;
newreq->current_idx = req->current_idx;
/* Setup the vectors identically to the ones in req. */
for (i = 1; i < count; i += SMBD_SMB2_NUM_IOV_PER_REQ) {
- if (!dup_smb2_vec3(outvec, &outvec[i], &req->out.vector[i])) {
+ if (!dup_smb2_vec4(outvec, &outvec[i], &req->out.vector[i])) {
break;
}
}
return newreq;
}
-static void smbd_smb2_request_writev_done(struct tevent_req *subreq);
-
static NTSTATUS smb2_send_async_interim_response(const struct smbd_smb2_request *req)
{
- int i = 0;
+ struct smbd_server_connection *sconn = req->sconn;
+ struct smbXsrv_connection *conn = req->sconn->conn;
+ 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;
/* Create a new smb2 request we'll use
for the interim return. */
nreq->out.vector_count);
/* Step back to the previous reply. */
- i = nreq->current_idx - SMBD_SMB2_NUM_IOV_PER_REQ;
- outhdr = (uint8_t *)nreq->out.vector[i].iov_base;
+ nreq->current_idx -= SMBD_SMB2_NUM_IOV_PER_REQ;
+ firsttf = SMBD_SMB2_IDX_TF_IOV(nreq,out,first_idx);
+ outhdr_v = SMBD_SMB2_OUT_HDR_IOV(nreq);
+ outhdr = SMBD_SMB2_OUT_HDR_PTR(nreq);
/* And end the chain. */
SIVAL(outhdr, SMB2_HDR_NEXT_COMMAND, 0);
/* Calculate outgoing credits */
smb2_calculate_credits(req, nreq);
- /* Re-sign if needed. */
- if (nreq->do_signing) {
- NTSTATUS status;
- struct smbXsrv_session *x = nreq->session;
- struct smbXsrv_connection *conn = x->connection;
- DATA_BLOB signing_key = x->global->channels[0].signing_key;
-
- status = smb2_signing_sign_pdu(signing_key,
- conn->protocol,
- &nreq->out.vector[i],
- SMBD_SMB2_NUM_IOV_PER_REQ);
- if (!NT_STATUS_IS_OK(status)) {
- return status;
- }
- }
if (DEBUGLEVEL >= 10) {
dbgtext("smb2_send_async_interim_response: nreq->current_idx = %u\n",
(unsigned int)nreq->current_idx );
(unsigned int)nreq->out.vector_count );
print_req_vectors(nreq);
}
- nreq->subreq = tstream_writev_queue_send(nreq,
- nreq->sconn->ev_ctx,
- nreq->sconn->smb2.stream,
- nreq->sconn->smb2.send_queue,
- nreq->out.vector,
- nreq->out.vector_count);
- if (nreq->subreq == NULL) {
- return NT_STATUS_NO_MEMORY;
+ /*
+ * As we have changed the header (SMB2_HDR_NEXT_COMMAND),
+ * we need to sign/encrypt here with the last/first key we remembered
+ */
+ if (firsttf->iov_len == SMB2_TF_HDR_SIZE) {
+ status = smb2_signing_encrypt_pdu(req->first_key,
+ conn->protocol,
+ firsttf,
+ nreq->out.vector_count - first_idx);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ } else if (req->last_key.length > 0) {
+ status = smb2_signing_sign_pdu(req->last_key,
+ conn->protocol,
+ outhdr_v,
+ SMBD_SMB2_NUM_IOV_PER_REQ - 1);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
}
- tevent_req_set_callback(nreq->subreq,
- smbd_smb2_request_writev_done,
- nreq);
+ nreq->queue_entry.mem_ctx = nreq;
+ nreq->queue_entry.vector = nreq->out.vector;
+ nreq->queue_entry.count = nreq->out.vector_count;
+ DLIST_ADD_END(nreq->sconn->smb2.send_queue, &nreq->queue_entry, NULL);
+ nreq->sconn->smb2.send_queue_len++;
+
+ status = smbd_smb2_io_handler(sconn, TEVENT_FD_WRITE);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
return NT_STATUS_OK;
}
struct smbd_smb2_request_pending_state {
struct smbd_server_connection *sconn;
- uint8_t buf[4 + SMB2_HDR_BODY + 0x08 + 1];
- struct iovec vector[3];
+ 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];
};
-static void smbd_smb2_request_pending_writev_done(struct tevent_req *subreq)
-{
- struct smbd_smb2_request_pending_state *state =
- tevent_req_callback_data(subreq,
- struct smbd_smb2_request_pending_state);
- struct smbd_server_connection *sconn = state->sconn;
- int ret;
- int sys_errno;
-
- ret = tstream_writev_queue_recv(subreq, &sys_errno);
- TALLOC_FREE(subreq);
- if (ret == -1) {
- NTSTATUS status = map_nt_error_from_unix(sys_errno);
- smbd_server_connection_terminate(sconn, nt_errstr(status));
- return;
- }
-
- TALLOC_FREE(state);
-}
-
static void smbd_smb2_request_pending_timer(struct tevent_context *ev,
struct tevent_timer *te,
struct timeval current_time,
uint32_t defer_time)
{
NTSTATUS status;
- int i = req->current_idx;
struct timeval defer_endtime;
uint8_t *outhdr = NULL;
uint32_t flags;
if (!tevent_req_is_in_progress(subreq)) {
+ /*
+ * This is a performance optimization,
+ * it avoids one tevent_loop iteration,
+ * which means we avoid one
+ * talloc_stackframe_pool/talloc_free pair.
+ */
+ tevent_req_notify_callback(subreq);
return NT_STATUS_OK;
}
return NT_STATUS_OK;
}
- outhdr = (uint8_t *)req->out.vector[i].iov_base;
+ outhdr = SMBD_SMB2_OUT_HDR_PTR(req);
flags = IVAL(outhdr, SMB2_HDR_FLAGS);
if (flags & SMB2_HDR_FLAG_ASYNC) {
/* We're already async. */
return NT_STATUS_OK;
}
- if (req->in.vector_count > i + SMBD_SMB2_NUM_IOV_PER_REQ) {
+ 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 not allowed.
- * Cancel the outstanding request.
+ * 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.
*/
- tevent_req_cancel(req->subreq);
- return smbd_smb2_request_error(req,
- NT_STATUS_INSUFFICIENT_RESOURCES);
+ const uint8_t *inhdr = SMBD_SMB2_IN_HDR_PTR(req);
+
+ if (SVAL(inhdr, SMB2_HDR_OPCODE) != SMB2_OP_CREATE) {
+ /*
+ * Cancel the outstanding request.
+ */
+ bool ok = tevent_req_cancel(req->subreq);
+ if (ok) {
+ return NT_STATUS_OK;
+ }
+ TALLOC_FREE(req->subreq);
+ return smbd_smb2_request_error(req,
+ NT_STATUS_INTERNAL_ERROR);
+ }
}
if (DEBUGLEVEL >= 10) {
print_req_vectors(req);
}
- if (req->out.vector_count >= (2*SMBD_SMB2_NUM_IOV_PER_REQ)) {
+ if (req->current_idx > 1) {
/*
- * This is a compound reply. We
- * must do an interim response
- * followed by the async response
- * to match W2K8R2.
+ * We're going async in a compound
+ * chain after the first request has
+ * already been processed. Send an
+ * interim response containing the
+ * set of replies already generated.
*/
+ int idx = req->current_idx;
+
status = smb2_send_async_interim_response(req);
if (!NT_STATUS_IS_OK(status)) {
return status;
}
+ data_blob_clear_free(&req->first_key);
+
+ req->current_idx = 1;
/*
- * 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.
+ * Re-arrange the in.vectors to remove what
+ * we just sent.
*/
- 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;
+ memmove(&req->in.vector[1],
+ &req->in.vector[idx],
+ sizeof(req->in.vector[0])*(req->in.vector_count - idx));
+ req->in.vector_count = 1 + (req->in.vector_count - idx);
+
+ /* Re-arrange the out.vectors to match. */
+ memmove(&req->out.vector[1],
+ &req->out.vector[idx],
+ sizeof(req->out.vector[0])*(req->out.vector_count - idx));
+ req->out.vector_count = 1 + (req->out.vector_count - idx);
+
+ if (req->in.vector_count == 1 + SMBD_SMB2_NUM_IOV_PER_REQ) {
+ /*
+ * We only have one remaining request as
+ * we've processed everything else.
+ * This is no longer a compound request.
+ */
+ req->compound_related = false;
+ outhdr = SMBD_SMB2_OUT_HDR_PTR(req);
+ flags = (IVAL(outhdr, SMB2_HDR_FLAGS) & ~SMB2_HDR_FLAG_CHAINED);
+ SIVAL(outhdr, SMB2_HDR_FLAGS, flags);
}
-
- 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);
}
+ data_blob_clear_free(&req->last_key);
defer_endtime = timeval_current_ofs_usec(defer_time);
req->async_te = tevent_add_timer(req->sconn->ev_ctx,
struct smbd_smb2_request *req =
talloc_get_type_abort(private_data,
struct smbd_smb2_request);
+ struct smbd_server_connection *sconn = req->sconn;
struct smbd_smb2_request_pending_state *state = NULL;
uint8_t *outhdr = NULL;
const uint8_t *inhdr = NULL;
+ uint8_t *tf = NULL;
+ size_t tf_len = 0;
uint8_t *hdr = NULL;
uint8_t *body = NULL;
+ uint8_t *dyn = NULL;
uint32_t flags = 0;
+ uint64_t session_id = 0;
uint64_t message_id = 0;
+ uint64_t nonce_high = 0;
+ uint64_t nonce_low = 0;
uint64_t async_id = 0;
- struct tevent_req *subreq = NULL;
+ NTSTATUS status;
TALLOC_FREE(req->async_te);
outhdr = SMBD_SMB2_OUT_HDR_PTR(req);
flags = IVAL(outhdr, SMB2_HDR_FLAGS);
message_id = BVAL(outhdr, SMB2_HDR_MESSAGE_ID);
+ session_id = BVAL(outhdr, SMB2_HDR_SESSION_ID);
async_id = message_id; /* keep it simple for now... */
DEBUG(10,("smbd_smb2_request_pending_queue: opcode[%s] mid %llu "
"going async\n",
- smb2_opcode_name((uint16_t)IVAL(inhdr, SMB2_HDR_OPCODE)),
+ smb2_opcode_name(SVAL(inhdr, SMB2_HDR_OPCODE)),
(unsigned long long)async_id ));
/*
}
state->sconn = req->sconn;
- state->vector[0].iov_base = (void *)state->buf;
- state->vector[0].iov_len = 4;
+ tf = state->buf + NBT_HDR_SIZE;
+ tf_len = SMB2_TF_HDR_SIZE;
+
+ hdr = tf + SMB2_TF_HDR_SIZE;
+ body = hdr + SMB2_HDR_BODY;
+ dyn = body + 8;
- state->vector[1].iov_base = state->buf + 4;
- state->vector[1].iov_len = SMB2_HDR_BODY;
+ if (req->do_encryption) {
+ struct smbXsrv_session *x = req->session;
- state->vector[2].iov_base = state->buf + 4 + SMB2_HDR_BODY;
- state->vector[2].iov_len = 9;
+ nonce_high = x->nonce_high;
+ nonce_low = x->nonce_low;
- smb2_setup_nbt_length(state->vector, 3);
+ x->nonce_low += 1;
+ if (x->nonce_low == 0) {
+ x->nonce_low += 1;
+ x->nonce_high += 1;
+ }
+ }
- hdr = (uint8_t *)state->vector[1].iov_base;
- body = (uint8_t *)state->vector[2].iov_base;
+ SIVAL(tf, SMB2_TF_PROTOCOL_ID, SMB2_TF_MAGIC);
+ SBVAL(tf, SMB2_TF_NONCE+0, nonce_low);
+ SBVAL(tf, SMB2_TF_NONCE+8, nonce_high);
+ SBVAL(tf, SMB2_TF_SESSION_ID, session_id);
SIVAL(hdr, SMB2_HDR_PROTOCOL_ID, SMB2_MAGIC);
SSVAL(hdr, SMB2_HDR_LENGTH, SMB2_HDR_BODY);
SCVAL(body, 0x03, 0);
SIVAL(body, 0x04, 0);
/* Match W2K8R2... */
- SCVAL(body, 0x08, 0x21);
+ SCVAL(dyn, 0x00, 0x21);
+
+ state->vector[0].iov_base = (void *)state->buf;
+ state->vector[0].iov_len = NBT_HDR_SIZE;
+
+ if (req->do_encryption) {
+ state->vector[1+SMBD_SMB2_TF_IOV_OFS].iov_base = tf;
+ state->vector[1+SMBD_SMB2_TF_IOV_OFS].iov_len = tf_len;
+ } else {
+ state->vector[1+SMBD_SMB2_TF_IOV_OFS].iov_base = NULL;
+ state->vector[1+SMBD_SMB2_TF_IOV_OFS].iov_len = 0;
+ }
+
+ state->vector[1+SMBD_SMB2_HDR_IOV_OFS].iov_base = hdr;
+ state->vector[1+SMBD_SMB2_HDR_IOV_OFS].iov_len = SMB2_HDR_BODY;
+
+ state->vector[1+SMBD_SMB2_BODY_IOV_OFS].iov_base = body;
+ state->vector[1+SMBD_SMB2_BODY_IOV_OFS].iov_len = 8;
+
+ 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);
/* Ensure we correctly go through crediting. Grant
the credits now, and zero credits on the final
response. */
smb2_set_operation_credit(req->sconn,
SMBD_SMB2_IN_HDR_IOV(req),
- &state->vector[1]);
+ &state->vector[1+SMBD_SMB2_HDR_IOV_OFS]);
SIVAL(hdr, SMB2_HDR_FLAGS, flags | SMB2_HDR_FLAG_ASYNC);
- if (req->do_signing) {
- NTSTATUS status;
+ if (DEBUGLVL(10)) {
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(state->vector); i++) {
+ dbgtext("\tstate->vector[%u/%u].iov_len = %u\n",
+ (unsigned int)i,
+ (unsigned int)ARRAY_SIZE(state->vector),
+ (unsigned int)state->vector[i].iov_len);
+ }
+ }
+
+ if (req->do_encryption) {
+ struct smbXsrv_session *x = req->session;
+ struct smbXsrv_connection *conn = x->connection;
+ DATA_BLOB encryption_key = x->global->encryption_key;
+
+ status = smb2_signing_encrypt_pdu(encryption_key,
+ conn->protocol,
+ &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,
+ nt_errstr(status));
+ return;
+ }
+ } else if (req->do_signing) {
struct smbXsrv_session *x = req->session;
struct smbXsrv_connection *conn = x->connection;
DATA_BLOB signing_key = x->global->channels[0].signing_key;
status = smb2_signing_sign_pdu(signing_key,
- conn->protocol,
- &state->vector[1], 2);
+ conn->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,
nt_errstr(status));
}
}
- subreq = tstream_writev_queue_send(state,
- state->sconn->ev_ctx,
- state->sconn->smb2.stream,
- state->sconn->smb2.send_queue,
- state->vector,
- 3);
- if (subreq == NULL) {
- smbd_server_connection_terminate(state->sconn,
- nt_errstr(NT_STATUS_NO_MEMORY));
+ state->queue_entry.mem_ctx = state;
+ state->queue_entry.vector = state->vector;
+ state->queue_entry.count = ARRAY_SIZE(state->vector);
+ DLIST_ADD_END(sconn->smb2.send_queue, &state->queue_entry, NULL);
+ sconn->smb2.send_queue_len++;
+
+ status = smbd_smb2_io_handler(sconn, TEVENT_FD_WRITE);
+ if (!NT_STATUS_IS_OK(status)) {
+ smbd_server_connection_terminate(sconn,
+ nt_errstr(status));
return;
}
- tevent_req_set_callback(subreq,
- smbd_smb2_request_pending_writev_done,
- state);
}
static NTSTATUS smbd_smb2_request_process_cancel(struct smbd_smb2_request *req)
uint64_t message_id;
uint64_t async_id;
+ if (cur->compound_related) {
+ /*
+ * Never cancel anything in a compound request.
+ * Way too hard to deal with the result.
+ */
+ continue;
+ }
+
outhdr = SMBD_SMB2_OUT_HDR_PTR(cur);
message_id = BVAL(outhdr, SMB2_HDR_MESSAGE_ID);
inhdr = SMBD_SMB2_IN_HDR_PTR(cur);
DEBUG(10,("smbd_smb2_request_process_cancel: attempting to "
"cancel opcode[%s] mid %llu\n",
- smb2_opcode_name((uint16_t)IVAL(inhdr, SMB2_HDR_OPCODE)),
+ smb2_opcode_name(SVAL(inhdr, SMB2_HDR_OPCODE)),
(unsigned long long)found_id ));
tevent_req_cancel(cur->subreq);
}
in_tid = req->last_tid;
}
+ req->last_tid = 0;
+
status = smb2srv_tcon_lookup(req->session,
in_tid, now, &tcon);
if (!NT_STATUS_IS_OK(status)) {
inhdr = SMBD_SMB2_IN_HDR_PTR(req);
in_flags = IVAL(inhdr, SMB2_HDR_FLAGS);
- in_opcode = IVAL(inhdr, SMB2_HDR_OPCODE);
+ in_opcode = SVAL(inhdr, SMB2_HDR_OPCODE);
in_session_id = BVAL(inhdr, SMB2_HDR_SESSION_ID);
if (in_flags & SMB2_HDR_FLAG_CHAINED) {
in_session_id = req->last_session_id;
}
+ req->last_session_id = 0;
+
/* lookup an existing session */
status = smb2srv_session_lookup(req->sconn->conn,
in_session_id, now,
NTSTATUS smbd_smb2_request_verify_sizes(struct smbd_smb2_request *req,
size_t expected_body_size)
{
+ struct iovec *inhdr_v;
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;
+ int max_idx = req->in.vector_count - SMBD_SMB2_NUM_IOV_PER_REQ;
/*
* The following should be checked already.
*/
- if ((i+2) > req->in.vector_count) {
+ if (req->in.vector_count < SMBD_SMB2_NUM_IOV_PER_REQ) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ if (req->current_idx > max_idx) {
return NT_STATUS_INTERNAL_ERROR;
}
- if (req->in.vector[i+0].iov_len != SMB2_HDR_BODY) {
+
+ inhdr_v = SMBD_SMB2_IN_HDR_IOV(req);
+ if (inhdr_v->iov_len != SMB2_HDR_BODY) {
return NT_STATUS_INTERNAL_ERROR;
}
- if (req->in.vector[i+1].iov_len < 2) {
+ if (SMBD_SMB2_IN_BODY_LEN(req) < 2) {
return NT_STATUS_INTERNAL_ERROR;
}
- inhdr = (const uint8_t *)req->in.vector[i+0].iov_base;
+ inhdr = SMBD_SMB2_IN_HDR_PTR(req);
opcode = SVAL(inhdr, SMB2_HDR_OPCODE);
switch (opcode) {
* where the last byte might be in the
* dynamic section..
*/
- if (req->in.vector[i+1].iov_len != (expected_body_size & 0xFFFFFFFE)) {
+ if (SMBD_SMB2_IN_BODY_LEN(req) != (expected_body_size & 0xFFFFFFFE)) {
return NT_STATUS_INVALID_PARAMETER;
}
- if (req->in.vector[i+2].iov_len < min_dyn_size) {
+ if (SMBD_SMB2_IN_DYN_LEN(req) < min_dyn_size) {
return NT_STATUS_INVALID_PARAMETER;
}
- inbody = (const uint8_t *)req->in.vector[i+1].iov_base;
+ inbody = SMBD_SMB2_IN_BODY_PTR(req);
body_size = SVAL(inbody, 0x00);
if (body_size != expected_body_size) {
{
struct smbXsrv_connection *conn = req->sconn->conn;
const struct smbd_smb2_dispatch_table *call = NULL;
+ const struct iovec *intf_v = SMBD_SMB2_IN_TF_IOV(req);
const uint8_t *inhdr;
uint16_t opcode;
uint32_t flags;
NTSTATUS return_value;
struct smbXsrv_session *x = NULL;
bool signing_required = false;
+ bool encryption_required = false;
inhdr = SMBD_SMB2_IN_HDR_PTR(req);
/* TODO: verify more things */
flags = IVAL(inhdr, SMB2_HDR_FLAGS);
- opcode = IVAL(inhdr, SMB2_HDR_OPCODE);
+ opcode = SVAL(inhdr, SMB2_HDR_OPCODE);
mid = BVAL(inhdr, SMB2_HDR_MESSAGE_ID);
DEBUG(10,("smbd_smb2_request_dispatch: opcode[%s] mid = %llu\n",
smb2_opcode_name(opcode),
}
}
- call = smbd_smb2_call(opcode);
- if (call == NULL) {
- return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
- }
-
- allowed_flags = SMB2_HDR_FLAG_CHAINED |
- SMB2_HDR_FLAG_SIGNED |
- SMB2_HDR_FLAG_DFS;
- if (opcode == SMB2_OP_CANCEL) {
- allowed_flags |= SMB2_HDR_FLAG_ASYNC;
- }
- if ((flags & ~allowed_flags) != 0) {
- 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
*/
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) {
}
req->do_signing = false;
- if (flags & SMB2_HDR_FLAG_SIGNED) {
- DATA_BLOB signing_key;
-
- if (x == NULL) {
- return smbd_smb2_request_error(
- req, NT_STATUS_ACCESS_DENIED);
+ req->do_encryption = 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);
+
+ if (x != NULL && x->global->session_wire_id != tf_session_id) {
+ DEBUG(0,("smbd_smb2_request_dispatch: invalid session_id"
+ "in SMB2_HDR[%llu], SMB2_TF[%llu]\n",
+ (unsigned long long)x->global->session_wire_id,
+ (unsigned long long)tf_session_id));
+ /*
+ * TODO: windows allows this...
+ * should we drop the connection?
+ *
+ * For now we just return ACCESS_DENIED
+ * (Windows clients never trigger this)
+ * and wait for an update of [MS-SMB2].
+ */
+ return smbd_smb2_request_error(req,
+ NT_STATUS_ACCESS_DENIED);
}
- signing_key = x->global->channels[0].signing_key;
+ req->do_encryption = true;
+ }
- if (!NT_STATUS_IS_OK(session_status)) {
- return smbd_smb2_request_error(req, session_status);
- }
+ if (encryption_required && !req->do_encryption) {
+ return smbd_smb2_request_error(req,
+ NT_STATUS_ACCESS_DENIED);
+ }
- req->do_signing = true;
- status = smb2_signing_check_pdu(signing_key,
- conn->protocol,
- SMBD_SMB2_IN_HDR_IOV(req),
- SMBD_SMB2_NUM_IOV_PER_REQ);
- 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 (signing_required) {
- return smbd_smb2_request_error(req, NT_STATUS_ACCESS_DENIED);
+ call = smbd_smb2_call(opcode);
+ if (call == NULL) {
+ return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
+ }
+
+ allowed_flags = SMB2_HDR_FLAG_CHAINED |
+ SMB2_HDR_FLAG_SIGNED |
+ SMB2_HDR_FLAG_DFS;
+ if (opcode == SMB2_OP_CANCEL) {
+ allowed_flags |= SMB2_HDR_FLAG_ASYNC;
+ }
+ if ((flags & ~allowed_flags) != 0) {
+ return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
}
if (flags & SMB2_HDR_FLAG_CHAINED) {
/*
* This check is mostly for giving the correct error code
* for compounded requests.
- *
- * TODO: we may need to move this after the session
- * and tcon checks.
*/
- if (!NT_STATUS_IS_OK(req->next_status)) {
- return smbd_smb2_request_error(req, req->next_status);
+ if (!NT_STATUS_IS_OK(session_status)) {
+ return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
}
} else {
req->compat_chain_fsp = NULL;
}
- if (req->compound_related) {
- req->sconn->smb2.compound_related_in_progress = true;
- }
+ if (req->do_encryption) {
+ signing_required = false;
+ } else if (signing_required || (flags & SMB2_HDR_FLAG_SIGNED)) {
+ DATA_BLOB signing_key;
- if (call->need_session) {
- if (!NT_STATUS_IS_OK(session_status)) {
+ if (x == NULL) {
+ return smbd_smb2_request_error(
+ req, NT_STATUS_USER_SESSION_DELETED);
+ }
+
+ signing_key = x->global->channels[0].signing_key;
+
+ /*
+ * If we have a signing key, we should
+ * sign the response
+ */
+ if (signing_key.length > 0) {
+ req->do_signing = true;
+ }
+
+ status = smb2_signing_check_pdu(signing_key,
+ conn->protocol,
+ SMBD_SMB2_IN_HDR_IOV(req),
+ SMBD_SMB2_NUM_IOV_PER_REQ - 1);
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(req, status);
+ }
+
+ /*
+ * Now that we know the request was correctly signed
+ * we have to sign the response too.
+ */
+ req->do_signing = true;
+
+ if (!NT_STATUS_IS_OK(session_status)) {
+ return smbd_smb2_request_error(req, session_status);
+ }
+ } else if (opcode == SMB2_OP_CANCEL) {
+ /* Cancel requests are allowed to skip the signing */
+ } else if (signing_required) {
+ /*
+ * If signing is required we try to sign
+ * a possible error response
+ */
+ req->do_signing = true;
+ return smbd_smb2_request_error(req, NT_STATUS_ACCESS_DENIED);
+ }
+
+ if (flags & SMB2_HDR_FLAG_CHAINED) {
+ req->compound_related = true;
+ }
+
+ if (call->need_session) {
+ if (!NT_STATUS_IS_OK(session_status)) {
return smbd_smb2_request_error(req, session_status);
}
}
if (!NT_STATUS_IS_OK(status)) {
return smbd_smb2_request_error(req, status);
}
+ if (req->tcon->global->encryption_required) {
+ encryption_required = true;
+ }
+ if (encryption_required && !req->do_encryption) {
+ return smbd_smb2_request_error(req,
+ NT_STATUS_ACCESS_DENIED);
+ }
+ }
+
+ if (call->fileid_ofs != 0) {
+ size_t needed = call->fileid_ofs + 16;
+ const uint8_t *body = SMBD_SMB2_IN_BODY_PTR(req);
+ size_t body_size = SMBD_SMB2_IN_BODY_LEN(req);
+ uint64_t file_id_persistent;
+ uint64_t file_id_volatile;
+ struct files_struct *fsp;
+
+ SMB_ASSERT(call->need_tcon);
+
+ if (needed > body_size) {
+ return smbd_smb2_request_error(req,
+ NT_STATUS_INVALID_PARAMETER);
+ }
+
+ file_id_persistent = BVAL(body, call->fileid_ofs + 0);
+ file_id_volatile = BVAL(body, call->fileid_ofs + 8);
+
+ fsp = file_fsp_smb2(req, file_id_persistent, file_id_volatile);
+ if (fsp == NULL) {
+ if (!call->allow_invalid_fileid) {
+ return smbd_smb2_request_error(req,
+ NT_STATUS_FILE_CLOSED);
+ }
+
+ if (file_id_persistent != UINT64_MAX) {
+ return smbd_smb2_request_error(req,
+ NT_STATUS_FILE_CLOSED);
+ }
+ if (file_id_volatile != UINT64_MAX) {
+ return smbd_smb2_request_error(req,
+ NT_STATUS_FILE_CLOSED);
+ }
+ }
}
if (call->as_root) {
+ SMB_ASSERT(call->fileid_ofs == 0);
/* This call needs to be run as root */
change_to_root_user();
} else {
static NTSTATUS smbd_smb2_request_reply(struct smbd_smb2_request *req)
{
- struct tevent_req *subreq;
- int i = req->current_idx;
+ struct smbd_server_connection *sconn = req->sconn;
+ struct smbXsrv_connection *conn = req->sconn->conn;
+ 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;
req->subreq = NULL;
TALLOC_FREE(req->async_te);
+ if (req->do_encryption &&
+ (firsttf->iov_len == 0) &&
+ (req->first_key.length == 0) &&
+ (req->session != NULL) &&
+ (req->session->global->encryption_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;
+ }
+
+ /*
+ * We need to place the SMB2_TRANSFORM header before the
+ * first SMB2 header
+ */
+
+ /*
+ * we need to remember the encryption key
+ * and defer the signing/encryption until
+ * we are sure that we do not change
+ * the header again.
+ */
+ req->first_key = data_blob_dup_talloc(req, encryption_key);
+ if (req->first_key.data == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ tf = talloc_zero_array(req->out.vector, uint8_t,
+ SMB2_TF_HDR_SIZE);
+ if (tf == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ SIVAL(tf, SMB2_TF_PROTOCOL_ID, SMB2_TF_MAGIC);
+ SBVAL(tf, SMB2_TF_NONCE+0, nonce_low);
+ SBVAL(tf, SMB2_TF_NONCE+8, nonce_high);
+ SBVAL(tf, SMB2_TF_SESSION_ID, session_id);
+
+ firsttf->iov_base = (void *)tf;
+ firsttf->iov_len = SMB2_TF_HDR_SIZE;
+ }
+
+ if ((req->current_idx > SMBD_SMB2_NUM_IOV_PER_REQ) &&
+ (req->last_key.length > 0) &&
+ (firsttf->iov_len == 0))
+ {
+ int last_idx = req->current_idx - SMBD_SMB2_NUM_IOV_PER_REQ;
+ struct iovec *lasthdr = SMBD_SMB2_IDX_HDR_IOV(req,out,last_idx);
+
+ /*
+ * As we are sure the header of the last request in the
+ * compound chain will not change, we can to sign here
+ * with the last signing key we remembered.
+ */
+ status = smb2_signing_sign_pdu(req->last_key,
+ conn->protocol,
+ lasthdr,
+ SMBD_SMB2_NUM_IOV_PER_REQ - 1);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ }
+ data_blob_clear_free(&req->last_key);
+
req->current_idx += SMBD_SMB2_NUM_IOV_PER_REQ;
if (req->current_idx < req->out.vector_count) {
if (!im) {
return NT_STATUS_NO_MEMORY;
}
+
+ if (req->do_signing && firsttf->iov_len == 0) {
+ struct smbXsrv_session *x = req->session;
+ DATA_BLOB signing_key = x->global->channels[0].signing_key;
+
+ /*
+ * we need to remember the signing key
+ * and defer the signing until
+ * we are sure that we do not change
+ * the header again.
+ */
+ req->last_key = data_blob_dup_talloc(req, signing_key);
+ if (req->last_key.data == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
tevent_schedule_immediate(im,
req->sconn->ev_ctx,
smbd_smb2_request_dispatch_immediate,
}
if (req->compound_related) {
- req->sconn->smb2.compound_related_in_progress = false;
+ req->compound_related = false;
}
smb2_setup_nbt_length(req->out.vector, req->out.vector_count);
is a final reply for an async operation). */
smb2_calculate_credits(req, req);
- if (req->do_signing) {
- NTSTATUS status;
+ /*
+ * now check if we need to sign the current response
+ */
+ if (firsttf->iov_len == SMB2_TF_HDR_SIZE) {
+ status = smb2_signing_encrypt_pdu(req->first_key,
+ conn->protocol,
+ firsttf,
+ req->out.vector_count - first_idx);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ } else if (req->do_signing) {
struct smbXsrv_session *x = req->session;
- struct smbXsrv_connection *conn = x->connection;
DATA_BLOB signing_key = x->global->channels[0].signing_key;
status = smb2_signing_sign_pdu(signing_key,
conn->protocol,
- &req->out.vector[i],
- SMBD_SMB2_NUM_IOV_PER_REQ);
+ outhdr,
+ SMBD_SMB2_NUM_IOV_PER_REQ - 1);
if (!NT_STATUS_IS_OK(status)) {
return status;
}
}
-
- if (DEBUGLEVEL >= 10) {
- dbgtext("smbd_smb2_request_reply: sending...\n");
- print_req_vectors(req);
- }
+ data_blob_clear_free(&req->first_key);
/* 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) {
+ if (req->out.vector_count < (2*SMBD_SMB2_NUM_IOV_PER_REQ) &&
+ outdyn->iov_base == NULL && outdyn->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->ev_ctx,
- req->sconn->smb2.stream,
- req->sconn->smb2.send_queue,
- req->out.vector,
- req->out.vector_count);
- if (subreq == NULL) {
- return NT_STATUS_NO_MEMORY;
- }
- tevent_req_set_callback(subreq, smbd_smb2_request_writev_done, req);
/*
* We're done with this request -
* move it off the "being processed" queue.
*/
DLIST_REMOVE(req->sconn->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(req->sconn->smb2.send_queue, &req->queue_entry, NULL);
+ req->sconn->smb2.send_queue_len++;
+
+ status = smbd_smb2_io_handler(sconn, TEVENT_FD_WRITE);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
return NT_STATUS_OK;
}
}
}
-static void smbd_smb2_request_writev_done(struct tevent_req *subreq)
-{
- struct smbd_smb2_request *req = tevent_req_callback_data(subreq,
- struct smbd_smb2_request);
- 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) {
- 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,
NTSTATUS status,
DATA_BLOB body, DATA_BLOB *dyn,
const char *location)
{
uint8_t *outhdr;
- int i = req->current_idx;
+ struct iovec *outbody_v;
+ struct iovec *outdyn_v;
uint32_t next_command_ofs;
DEBUG(10,("smbd_smb2_request_done_ex: "
"idx[%d] status[%s] body[%u] dyn[%s:%u] at %s\n",
- i, nt_errstr(status), (unsigned int)body.length,
+ req->current_idx, nt_errstr(status), (unsigned int)body.length,
dyn ? "yes": "no",
(unsigned int)(dyn ? dyn->length : 0),
location));
return smbd_smb2_request_error(req, NT_STATUS_INTERNAL_ERROR);
}
- outhdr = (uint8_t *)req->out.vector[i].iov_base;
+ outhdr = SMBD_SMB2_OUT_HDR_PTR(req);
+ outbody_v = SMBD_SMB2_OUT_BODY_IOV(req);
+ outdyn_v = SMBD_SMB2_OUT_DYN_IOV(req);
next_command_ofs = IVAL(outhdr, SMB2_HDR_NEXT_COMMAND);
SIVAL(outhdr, SMB2_HDR_STATUS, NT_STATUS_V(status));
- req->out.vector[i+1].iov_base = (void *)body.data;
- req->out.vector[i+1].iov_len = body.length;
+ outbody_v->iov_base = (void *)body.data;
+ outbody_v->iov_len = body.length;
if (dyn) {
- req->out.vector[i+2].iov_base = (void *)dyn->data;
- req->out.vector[i+2].iov_len = dyn->length;
+ outdyn_v->iov_base = (void *)dyn->data;
+ outdyn_v->iov_len = dyn->length;
} else {
- req->out.vector[i+2].iov_base = NULL;
- req->out.vector[i+2].iov_len = 0;
+ outdyn_v->iov_base = NULL;
+ outdyn_v->iov_len = 0;
}
/* see if we need to recalculate the offset to the next response */
if (next_command_ofs > 0) {
next_command_ofs = SMB2_HDR_BODY;
- next_command_ofs += req->out.vector[i+1].iov_len;
- next_command_ofs += req->out.vector[i+2].iov_len;
+ next_command_ofs += SMBD_SMB2_OUT_BODY_LEN(req);
+ next_command_ofs += SMBD_SMB2_OUT_DYN_LEN(req);
}
if ((next_command_ofs % 8) != 0) {
size_t pad_size = 8 - (next_command_ofs % 8);
- if (req->out.vector[i+2].iov_len == 0) {
+ if (SMBD_SMB2_OUT_DYN_LEN(req) == 0) {
/*
* if the dyn buffer is empty
* we can use it to add padding
NT_STATUS_NO_MEMORY);
}
- req->out.vector[i+2].iov_base = (void *)pad;
- req->out.vector[i+2].iov_len = pad_size;
+ outdyn_v->iov_base = (void *)pad;
+ outdyn_v->iov_len = pad_size;
} else {
/*
* For now we copy the dynamic buffer
size_t new_size;
uint8_t *new_dyn;
- old_size = req->out.vector[i+2].iov_len;
- old_dyn = (uint8_t *)req->out.vector[i+2].iov_base;
+ old_size = SMBD_SMB2_OUT_DYN_LEN(req);
+ old_dyn = SMBD_SMB2_OUT_DYN_PTR(req);
new_size = old_size + pad_size;
new_dyn = talloc_zero_array(req->out.vector,
memcpy(new_dyn, old_dyn, old_size);
memset(new_dyn + old_size, 0, pad_size);
- req->out.vector[i+2].iov_base = (void *)new_dyn;
- req->out.vector[i+2].iov_len = new_size;
+ outdyn_v->iov_base = (void *)new_dyn;
+ outdyn_v->iov_len = new_size;
}
next_command_ofs += pad_size;
}
const char *location)
{
DATA_BLOB body;
- int i = req->current_idx;
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",
- i, nt_errstr(status), info ? " +info" : "",
+ req->current_idx, nt_errstr(status), info ? " +info" : "",
location));
+ if (unread_bytes) {
+ /* Recvfile error. Drain incoming socket. */
+ size_t ret;
+
+ errno = 0;
+ ret = drain_socket(req->sconn->sock, unread_bytes);
+ if (ret != unread_bytes) {
+ NTSTATUS error;
+
+ if (errno == 0) {
+ error = NT_STATUS_IO_DEVICE_ERROR;
+ } else {
+ error = map_nt_error_from_unix_common(errno);
+ }
+
+ DEBUG(2, ("Failed to drain %u bytes from SMB2 socket: "
+ "ret[%u] errno[%d] => %s\n",
+ (unsigned)unread_bytes,
+ (unsigned)ret, errno, nt_errstr(error)));
+ return error;
+ }
+ }
+
body.data = outhdr + SMB2_HDR_BODY;
body.length = 8;
SSVAL(body.data, 0, 9);
}
/*
- * if a request fails, all other remaining
- * compounded requests should fail too
+ * Note: Even if there is an error, continue to process the request.
+ * per MS-SMB2.
*/
- req->next_status = NT_STATUS_INVALID_PARAMETER;
return smbd_smb2_request_done_ex(req, status, body, info, __location__);
}
struct smbd_smb2_send_oplock_break_state {
struct smbd_server_connection *sconn;
- uint8_t buf[4 + SMB2_HDR_BODY + 0x18];
- struct iovec vector;
+ struct smbd_smb2_send_queue queue_entry;
+ uint8_t buf[NBT_HDR_SIZE + SMB2_TF_HDR_SIZE + SMB2_HDR_BODY + 0x18];
+ struct iovec vector[1+SMBD_SMB2_NUM_IOV_PER_REQ];
};
-static void smbd_smb2_oplock_break_writev_done(struct tevent_req *subreq);
-
NTSTATUS smbd_smb2_send_oplock_break(struct smbd_server_connection *sconn,
- uint64_t file_id_persistent,
- uint64_t file_id_volatile,
+ struct smbXsrv_session *session,
+ struct smbXsrv_tcon *tcon,
+ struct smbXsrv_open *op,
uint8_t oplock_level)
{
struct smbd_smb2_send_oplock_break_state *state;
- struct tevent_req *subreq;
+ struct smbXsrv_connection *conn = sconn->conn;
+ uint8_t *tf;
+ size_t tf_len;
uint8_t *hdr;
uint8_t *body;
+ size_t body_len;
+ uint8_t *dyn;
+ size_t dyn_len;
+ bool do_encryption = session->global->encryption_required;
+ uint64_t nonce_high = 0;
+ uint64_t nonce_low = 0;
+ NTSTATUS status;
+
+ if (tcon->global->encryption_required) {
+ do_encryption = true;
+ }
- state = talloc(sconn, struct smbd_smb2_send_oplock_break_state);
+ state = talloc_zero(sconn, struct smbd_smb2_send_oplock_break_state);
if (state == NULL) {
return NT_STATUS_NO_MEMORY;
}
state->sconn = sconn;
- state->vector.iov_base = (void *)state->buf;
- state->vector.iov_len = sizeof(state->buf);
-
- _smb2_setlen(state->buf, sizeof(state->buf) - 4);
- hdr = state->buf + 4;
+ tf = state->buf + NBT_HDR_SIZE;
+ tf_len = SMB2_TF_HDR_SIZE;
+ hdr = tf + tf_len;
body = hdr + SMB2_HDR_BODY;
+ body_len = 0x18;
+ dyn = body + body_len;
+ dyn_len = 0;
+
+ 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;
+ }
+ }
+
+ SIVAL(tf, SMB2_TF_PROTOCOL_ID, SMB2_TF_MAGIC);
+ SBVAL(tf, SMB2_TF_NONCE+0, nonce_low);
+ SBVAL(tf, SMB2_TF_NONCE+8, nonce_high);
+ SBVAL(tf, SMB2_TF_SESSION_ID, session->global->session_wire_id);
SIVAL(hdr, 0, SMB2_MAGIC);
SSVAL(hdr, SMB2_HDR_LENGTH, SMB2_HDR_BODY);
SBVAL(hdr, SMB2_HDR_SESSION_ID, 0);
memset(hdr+SMB2_HDR_SIGNATURE, 0, 16);
- SSVAL(body, 0x00, 0x18);
+ SSVAL(body, 0x00, body_len);
SCVAL(body, 0x02, oplock_level);
SCVAL(body, 0x03, 0); /* reserved */
SIVAL(body, 0x04, 0); /* reserved */
- SBVAL(body, 0x08, file_id_persistent);
- SBVAL(body, 0x10, file_id_volatile);
-
- subreq = tstream_writev_queue_send(state,
- sconn->ev_ctx,
- sconn->smb2.stream,
- sconn->smb2.send_queue,
- &state->vector, 1);
- if (subreq == NULL) {
- return NT_STATUS_NO_MEMORY;
- }
- tevent_req_set_callback(subreq,
- smbd_smb2_oplock_break_writev_done,
- state);
-
- return NT_STATUS_OK;
-}
+ SBVAL(body, 0x08, op->global->open_persistent_id);
+ SBVAL(body, 0x10, op->global->open_volatile_id);
-static void smbd_smb2_oplock_break_writev_done(struct tevent_req *subreq)
-{
- struct smbd_smb2_send_oplock_break_state *state =
- tevent_req_callback_data(subreq,
- struct smbd_smb2_send_oplock_break_state);
- struct smbd_server_connection *sconn = state->sconn;
- int ret;
- int sys_errno;
+ state->vector[0].iov_base = (void *)state->buf;
+ state->vector[0].iov_len = NBT_HDR_SIZE;
- ret = tstream_writev_queue_recv(subreq, &sys_errno);
- TALLOC_FREE(subreq);
- if (ret == -1) {
- NTSTATUS status = map_nt_error_from_unix(sys_errno);
- smbd_server_connection_terminate(sconn, nt_errstr(status));
- return;
+ if (do_encryption) {
+ state->vector[1+SMBD_SMB2_TF_IOV_OFS].iov_base = tf;
+ state->vector[1+SMBD_SMB2_TF_IOV_OFS].iov_len = tf_len;
+ } else {
+ state->vector[1+SMBD_SMB2_TF_IOV_OFS].iov_base = NULL;
+ state->vector[1+SMBD_SMB2_TF_IOV_OFS].iov_len = 0;
}
- TALLOC_FREE(state);
-}
+ state->vector[1+SMBD_SMB2_HDR_IOV_OFS].iov_base = hdr;
+ state->vector[1+SMBD_SMB2_HDR_IOV_OFS].iov_len = SMB2_HDR_BODY;
-struct smbd_smb2_request_read_state {
- struct tevent_context *ev;
- struct smbd_server_connection *sconn;
- struct smbd_smb2_request *smb2_req;
- struct {
- uint8_t nbt[NBT_HDR_SIZE];
- bool done;
- } hdr;
- size_t pktlen;
- uint8_t *pktbuf;
-};
+ state->vector[1+SMBD_SMB2_BODY_IOV_OFS].iov_base = body;
+ state->vector[1+SMBD_SMB2_BODY_IOV_OFS].iov_len = body_len;
-static int smbd_smb2_request_next_vector(struct tstream_context *stream,
- void *private_data,
- TALLOC_CTX *mem_ctx,
- struct iovec **_vector,
- size_t *_count);
-static void smbd_smb2_request_read_done(struct tevent_req *subreq);
+ state->vector[1+SMBD_SMB2_DYN_IOV_OFS].iov_base = dyn;
+ state->vector[1+SMBD_SMB2_DYN_IOV_OFS].iov_len = dyn_len;
-static struct tevent_req *smbd_smb2_request_read_send(TALLOC_CTX *mem_ctx,
- struct tevent_context *ev,
- struct smbd_server_connection *sconn)
-{
- struct tevent_req *req;
- struct smbd_smb2_request_read_state *state;
- struct tevent_req *subreq;
+ smb2_setup_nbt_length(state->vector, 1 + SMBD_SMB2_NUM_IOV_PER_REQ);
- req = tevent_req_create(mem_ctx, &state,
- struct smbd_smb2_request_read_state);
- if (req == NULL) {
- return NULL;
- }
- state->ev = ev;
- state->sconn = sconn;
+ if (do_encryption) {
+ DATA_BLOB encryption_key = session->global->encryption_key;
- state->smb2_req = smbd_smb2_request_allocate(state);
- if (tevent_req_nomem(state->smb2_req, req)) {
- return tevent_req_post(req, ev);
+ status = smb2_signing_encrypt_pdu(encryption_key,
+ conn->protocol,
+ &state->vector[1+SMBD_SMB2_TF_IOV_OFS],
+ SMBD_SMB2_NUM_IOV_PER_REQ);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
}
- state->smb2_req->sconn = sconn;
- subreq = tstream_readv_pdu_queue_send(state->smb2_req,
- state->ev,
- state->sconn->smb2.stream,
- state->sconn->smb2.recv_queue,
- smbd_smb2_request_next_vector,
- state);
- if (tevent_req_nomem(subreq, req)) {
- return tevent_req_post(req, ev);
+ state->queue_entry.mem_ctx = state;
+ state->queue_entry.vector = state->vector;
+ state->queue_entry.count = ARRAY_SIZE(state->vector);
+ DLIST_ADD_END(state->sconn->smb2.send_queue, &state->queue_entry, NULL);
+ state->sconn->smb2.send_queue_len++;
+
+ status = smbd_smb2_io_handler(sconn, TEVENT_FD_WRITE);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
}
- tevent_req_set_callback(subreq, smbd_smb2_request_read_done, req);
- return req;
+ return NT_STATUS_OK;
}
-static int smbd_smb2_request_next_vector(struct tstream_context *stream,
- void *private_data,
- TALLOC_CTX *mem_ctx,
- struct iovec **_vector,
- size_t *_count)
+static size_t get_min_receive_file_size(struct smbd_smb2_request *smb2_req)
{
- struct smbd_smb2_request_read_state *state =
- talloc_get_type_abort(private_data,
- struct smbd_smb2_request_read_state);
- struct iovec *vector;
-
- if (state->pktlen > 0) {
- /* if there're no remaining bytes, we're done */
- *_vector = NULL;
- *_count = 0;
+ if (smb2_req->do_signing) {
return 0;
}
-
- if (!state->hdr.done) {
- /*
- * first we need to get the NBT header
- */
- vector = talloc_array(mem_ctx, struct iovec, 1);
- if (vector == NULL) {
- return -1;
- }
-
- vector[0].iov_base = (void *)state->hdr.nbt;
- vector[0].iov_len = NBT_HDR_SIZE;
-
- *_vector = vector;
- *_count = 1;
-
- state->hdr.done = true;
+ if (smb2_req->do_encryption) {
return 0;
}
+ return (size_t)lp_min_receive_file_size();
+}
- /*
- * Now we analyze the NBT header
- */
- state->pktlen = smb2_len(state->hdr.nbt);
+static bool is_smb2_recvfile_write(struct smbd_smb2_request_read_state *state)
+{
+ uint32_t flags;
- if (state->pktlen == 0) {
- /* if there're no remaining bytes, we're done */
- *_vector = NULL;
- *_count = 0;
- return 0;
+ if (IVAL(state->pktbuf, 0) == SMB2_TF_MAGIC) {
+ /* Transform header. Cannot recvfile. */
+ return false;
}
-
- state->pktbuf = talloc_array(state->smb2_req, uint8_t, state->pktlen);
- if (state->pktbuf == NULL) {
- return -1;
+ if (IVAL(state->pktbuf, 0) != SMB2_MAGIC) {
+ /* Not SMB2. Normal error path will cope. */
+ return false;
}
-
- vector = talloc_array(mem_ctx, struct iovec, 1);
- if (vector == NULL) {
- return -1;
+ if (SVAL(state->pktbuf, 4) != SMB2_HDR_BODY) {
+ /* Not SMB2. Normal error path will cope. */
+ return false;
}
-
- vector[0].iov_base = (void *)state->pktbuf;
- vector[0].iov_len = state->pktlen;
-
- *_vector = vector;
- *_count = 1;
- return 0;
-}
-
-static void smbd_smb2_request_read_done(struct tevent_req *subreq)
-{
- struct tevent_req *req =
- tevent_req_callback_data(subreq,
- struct tevent_req);
- struct smbd_smb2_request_read_state *state =
- tevent_req_data(req,
- struct smbd_smb2_request_read_state);
- int ret;
- int sys_errno;
- NTSTATUS status;
- NTTIME now;
-
- ret = tstream_readv_pdu_queue_recv(subreq, &sys_errno);
- TALLOC_FREE(subreq);
- if (ret == -1) {
- status = map_nt_error_from_unix(sys_errno);
- tevent_req_nterror(req, status);
- return;
+ if (SVAL(state->pktbuf, SMB2_HDR_OPCODE) != SMB2_OP_WRITE) {
+ /* Needs to be a WRITE. */
+ return false;
}
-
- if (state->hdr.nbt[0] != 0x00) {
- DEBUG(1,("smbd_smb2_request_read_done: ignore NBT[0x%02X] msg\n",
- state->hdr.nbt[0]));
-
- ZERO_STRUCT(state->hdr);
- TALLOC_FREE(state->pktbuf);
- state->pktlen = 0;
-
- subreq = tstream_readv_pdu_queue_send(state->smb2_req,
- state->ev,
- state->sconn->smb2.stream,
- state->sconn->smb2.recv_queue,
- smbd_smb2_request_next_vector,
- state);
- if (tevent_req_nomem(subreq, req)) {
- return;
- }
- tevent_req_set_callback(subreq, smbd_smb2_request_read_done, req);
- return;
+ if (IVAL(state->pktbuf, SMB2_HDR_NEXT_COMMAND) != 0) {
+ /* Chained. Cannot recvfile. */
+ return false;
}
-
- state->smb2_req->request_time = timeval_current();
- now = timeval_to_nttime(&state->smb2_req->request_time);
-
- status = smbd_smb2_inbuf_parse_compound(state->smb2_req->sconn->conn,
- now,
- state->pktbuf,
- state->pktlen,
- state->smb2_req,
- &state->smb2_req->in.vector,
- &state->smb2_req->in.vector_count);
- if (tevent_req_nterror(req, status)) {
- return;
+ flags = IVAL(state->pktbuf, SMB2_HDR_FLAGS);
+ if (flags & SMB2_HDR_FLAG_CHAINED) {
+ /* Chained. Cannot recvfile. */
+ return false;
}
-
- state->smb2_req->current_idx = 1;
-
- tevent_req_done(req);
-}
-
-static NTSTATUS smbd_smb2_request_read_recv(struct tevent_req *req,
- TALLOC_CTX *mem_ctx,
- struct smbd_smb2_request **_smb2_req)
-{
- struct smbd_smb2_request_read_state *state =
- tevent_req_data(req,
- struct smbd_smb2_request_read_state);
- NTSTATUS status;
-
- if (tevent_req_is_nterror(req, &status)) {
- tevent_req_received(req);
- return status;
+ if (flags & SMB2_HDR_FLAG_SIGNED) {
+ /* Signed. Cannot recvfile. */
+ return false;
}
- *_smb2_req = talloc_move(mem_ctx, &state->smb2_req);
- tevent_req_received(req);
- return NT_STATUS_OK;
-}
+ DEBUG(10,("Doing recvfile write len = %u\n",
+ (unsigned int)(state->pktlen -
+ SMBD_SMB2_SHORT_RECEIVEFILE_WRITE_LEN)));
-static void smbd_smb2_request_incoming(struct tevent_req *subreq);
+ return true;
+}
static NTSTATUS smbd_smb2_request_next_incoming(struct smbd_server_connection *sconn)
{
+ struct smbd_smb2_request_read_state *state = &sconn->smb2.request_read_state;
size_t max_send_queue_len;
size_t cur_send_queue_len;
- struct tevent_req *subreq;
- if (sconn->smb2.compound_related_in_progress) {
+ if (state->req != NULL) {
/*
- * 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
+ * if there is already a tstream_readv_pdu
* 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);
+ cur_send_queue_len = sconn->smb2.send_queue_len;
if (cur_send_queue_len > max_send_queue_len) {
/*
}
/* ask for the next request */
- subreq = smbd_smb2_request_read_send(sconn, sconn->ev_ctx, sconn);
- if (subreq == NULL) {
+ ZERO_STRUCTP(state);
+ state->req = smbd_smb2_request_allocate(sconn);
+ if (state->req == NULL) {
return NT_STATUS_NO_MEMORY;
}
- tevent_req_set_callback(subreq, smbd_smb2_request_incoming, sconn);
+ state->req->sconn = sconn;
+ state->min_recv_size = get_min_receive_file_size(state->req);
+
+ TEVENT_FD_READABLE(sconn->smb2.fde);
return NT_STATUS_OK;
}
sconn->num_requests++;
}
-static void smbd_smb2_request_incoming(struct tevent_req *subreq)
+static int socket_error_from_errno(int ret,
+ int sys_errno,
+ bool *retry)
{
- struct smbd_server_connection *sconn = tevent_req_callback_data(subreq,
- struct smbd_server_connection);
- NTSTATUS status;
+ *retry = false;
+
+ if (ret >= 0) {
+ return 0;
+ }
+
+ if (ret != -1) {
+ return EIO;
+ }
+
+ if (sys_errno == 0) {
+ return EIO;
+ }
+
+ if (sys_errno == EINTR) {
+ *retry = true;
+ return sys_errno;
+ }
+
+ if (sys_errno == EINPROGRESS) {
+ *retry = true;
+ return sys_errno;
+ }
+
+ if (sys_errno == EAGAIN) {
+ *retry = true;
+ return sys_errno;
+ }
+
+ /* ENOMEM is retryable on Solaris/illumos, and possibly other systems. */
+ if (sys_errno == ENOMEM) {
+ *retry = true;
+ return sys_errno;
+ }
+
+#ifdef EWOULDBLOCK
+ if (sys_errno == EWOULDBLOCK) {
+ *retry = true;
+ return sys_errno;
+ }
+#endif
+
+ return sys_errno;
+}
+
+static NTSTATUS smbd_smb2_flush_send_queue(struct smbd_server_connection *sconn)
+{
+ int ret;
+ int err;
+ bool retry;
+
+ if (sconn->smb2.send_queue == NULL) {
+ TEVENT_FD_NOT_WRITEABLE(sconn->smb2.fde);
+ return NT_STATUS_OK;
+ }
+
+ while (sconn->smb2.send_queue != NULL) {
+ struct smbd_smb2_send_queue *e = sconn->smb2.send_queue;
+
+ if (e->sendfile_header != NULL) {
+ size_t size = 0;
+ size_t i = 0;
+ uint8_t *buf;
+
+ for (i=0; i < e->count; i++) {
+ size += e->vector[i].iov_len;
+ }
+
+ buf = talloc_array(e->mem_ctx, uint8_t, size);
+ if (buf == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ size = 0;
+ for (i=0; i < e->count; i++) {
+ memcpy(buf+size,
+ e->vector[i].iov_base,
+ e->vector[i].iov_len);
+ size += e->vector[i].iov_len;
+ }
+
+ e->sendfile_header->data = buf;
+ e->sendfile_header->length = size;
+ e->count = 0;
+
+ sconn->smb2.send_queue_len--;
+ DLIST_REMOVE(sconn->smb2.send_queue, e);
+ talloc_free(e->mem_ctx);
+ continue;
+ }
+
+ ret = writev(sconn->sock, e->vector, e->count);
+ if (ret == 0) {
+ /* propagate end of file */
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ err = socket_error_from_errno(ret, errno, &retry);
+ if (retry) {
+ /* retry later */
+ TEVENT_FD_WRITEABLE(sconn->smb2.fde);
+ return NT_STATUS_OK;
+ }
+ 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;
+ }
+
+ if (e->count > 0) {
+ /* we have more to write */
+ TEVENT_FD_WRITEABLE(sconn->smb2.fde);
+ return NT_STATUS_OK;
+ }
+
+ sconn->smb2.send_queue_len--;
+ DLIST_REMOVE(sconn->smb2.send_queue, e);
+ talloc_free(e->mem_ctx);
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS smbd_smb2_io_handler(struct smbd_server_connection *sconn,
+ uint16_t fde_flags)
+{
+ struct smbd_smb2_request_read_state *state = &sconn->smb2.request_read_state;
struct smbd_smb2_request *req = NULL;
+ size_t min_recvfile_size = UINT32_MAX;
+ int ret;
+ int err;
+ bool retry;
+ NTSTATUS status;
+ NTTIME now;
- status = smbd_smb2_request_read_recv(subreq, sconn, &req);
- TALLOC_FREE(subreq);
+ if (fde_flags & TEVENT_FD_WRITE) {
+ status = smbd_smb2_flush_send_queue(sconn);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ }
+
+ if (!(fde_flags & TEVENT_FD_READ)) {
+ return NT_STATUS_OK;
+ }
+
+ if (state->req == NULL) {
+ TEVENT_FD_NOT_READABLE(sconn->smb2.fde);
+ return NT_STATUS_OK;
+ }
+
+again:
+ if (!state->hdr.done) {
+ state->hdr.done = true;
+
+ state->vector.iov_base = (void *)state->hdr.nbt;
+ state->vector.iov_len = NBT_HDR_SIZE;
+ }
+
+ ret = readv(sconn->sock, &state->vector, 1);
+ if (ret == 0) {
+ /* propagate end of file */
+ return NT_STATUS_END_OF_FILE;
+ }
+ err = socket_error_from_errno(ret, errno, &retry);
+ if (retry) {
+ /* retry later */
+ TEVENT_FD_READABLE(sconn->smb2.fde);
+ return NT_STATUS_OK;
+ }
+ if (err != 0) {
+ return map_nt_error_from_unix_common(err);
+ }
+
+ if (ret < state->vector.iov_len) {
+ uint8_t *base;
+ base = (uint8_t *)state->vector.iov_base;
+ base += ret;
+ state->vector.iov_base = (void *)base;
+ state->vector.iov_len -= ret;
+ /* we have more to read */
+ TEVENT_FD_READABLE(sconn->smb2.fde);
+ return NT_STATUS_OK;
+ }
+
+ if (state->pktlen > 0) {
+ if (state->doing_receivefile && !is_smb2_recvfile_write(state)) {
+ /*
+ * Not a possible receivefile write.
+ * Read the rest of the data.
+ */
+ state->doing_receivefile = false;
+ state->vector.iov_base = (void *)(state->pktbuf +
+ SMBD_SMB2_SHORT_RECEIVEFILE_WRITE_LEN);
+ state->vector.iov_len = (state->pktlen -
+ SMBD_SMB2_SHORT_RECEIVEFILE_WRITE_LEN);
+ goto again;
+ }
+
+ /*
+ * Either this is a receivefile write so we've
+ * done a short read, or if not we have all the data.
+ */
+ goto got_full;
+ }
+
+ /*
+ * Now we analyze the NBT header
+ */
+ state->pktlen = smb2_len(state->hdr.nbt);
+ if (state->pktlen == 0) {
+ goto got_full;
+ }
+
+ state->pktbuf = talloc_array(state->req, uint8_t, state->pktlen);
+ if (state->pktbuf == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ state->vector.iov_base = (void *)state->pktbuf;
+
+ if (state->min_recv_size != 0) {
+ min_recvfile_size = SMBD_SMB2_SHORT_RECEIVEFILE_WRITE_LEN;
+ min_recvfile_size += state->min_recv_size;
+ }
+
+ if (state->pktlen > min_recvfile_size) {
+ /*
+ * Might be a receivefile write. Read the SMB2 HEADER +
+ * SMB2_WRITE header first. Set 'doing_receivefile'
+ * as we're *attempting* receivefile write. If this
+ * turns out not to be a SMB2_WRITE request or otherwise
+ * not suitable then we'll just read the rest of the data
+ * the next time this function is called.
+ */
+ state->vector.iov_len = SMBD_SMB2_SHORT_RECEIVEFILE_WRITE_LEN;
+ state->doing_receivefile = true;
+ } else {
+ state->vector.iov_len = state->pktlen;
+ }
+
+ goto again;
+
+got_full:
+
+ if (state->hdr.nbt[0] != 0x00) {
+ DEBUG(1,("ignore NBT[0x%02X] msg\n",
+ state->hdr.nbt[0]));
+
+ req = state->req;
+ ZERO_STRUCTP(state);
+ state->req = req;
+ state->min_recv_size = get_min_receive_file_size(state->req);
+ req = NULL;
+ goto again;
+ }
+
+ req = state->req;
+ state->req = NULL;
+
+ req->request_time = timeval_current();
+ now = timeval_to_nttime(&req->request_time);
+
+ status = smbd_smb2_inbuf_parse_compound(req->sconn->conn,
+ now,
+ state->pktbuf,
+ state->pktlen,
+ req,
+ &req->in.vector,
+ &req->in.vector_count);
if (!NT_STATUS_IS_OK(status)) {
- DEBUG(2,("smbd_smb2_request_incoming: client read error %s\n",
- nt_errstr(status)));
- smbd_server_connection_terminate(sconn, nt_errstr(status));
- return;
+ return status;
}
- DEBUG(10,("smbd_smb2_request_incoming: idx[%d] of %d vectors\n",
+ if (state->doing_receivefile) {
+ req->smb1req = talloc_zero(req, struct smb_request);
+ if (req->smb1req == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ req->smb1req->unread_bytes =
+ state->pktlen - SMBD_SMB2_SHORT_RECEIVEFILE_WRITE_LEN;
+ }
+
+ ZERO_STRUCTP(state);
+
+ req->current_idx = 1;
+
+ DEBUG(10,("smbd_smb2_request idx[%d] of %d vectors\n",
req->current_idx, req->in.vector_count));
status = smbd_smb2_request_validate(req);
if (!NT_STATUS_IS_OK(status)) {
- smbd_server_connection_terminate(sconn, nt_errstr(status));
- return;
+ return status;
}
status = smbd_smb2_request_setup_out(req);
if (!NT_STATUS_IS_OK(status)) {
- smbd_server_connection_terminate(sconn, nt_errstr(status));
- return;
+ return status;
}
status = smbd_smb2_request_dispatch(req);
if (!NT_STATUS_IS_OK(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;
+ return status;
}
sconn->num_requests++;
change_to_root_user();
check_log_size();
}
+
+ status = smbd_smb2_request_next_incoming(sconn);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static void smbd_smb2_connection_handler(struct tevent_context *ev,
+ struct tevent_fd *fde,
+ uint16_t flags,
+ void *private_data)
+{
+ struct smbd_server_connection *sconn =
+ talloc_get_type_abort(private_data,
+ struct smbd_server_connection);
+ NTSTATUS status;
+
+ status = smbd_smb2_io_handler(sconn, flags);
+ if (!NT_STATUS_IS_OK(status)) {
+ smbd_server_connection_terminate(sconn, nt_errstr(status));
+ return;
+ }
}