diff --git a/source3/smbd/globals.h b/source3/smbd/globals.h index 864143d..951d352 100644 --- a/source3/smbd/globals.h +++ b/source3/smbd/globals.h @@ -360,6 +360,8 @@ struct smbd_smb2_request { int current_idx; bool do_signing; + bool async; + bool cancelled; struct files_struct *compat_chain_fsp; diff --git a/source3/smbd/smb2_notify.c b/source3/smbd/smb2_notify.c index 8fa0744..3d60ffb 100644 --- a/source3/smbd/smb2_notify.c +++ b/source3/smbd/smb2_notify.c @@ -108,6 +108,14 @@ static void smbd_smb2_request_notify_done(struct tevent_req *subreq) NTSTATUS status; NTSTATUS error; /* transport error */ + if (req->cancelled) { + const uint8_t *inhdr = (const uint8_t *)req->in.vector[i].iov_base; + uint64_t mid = BVAL(inhdr, SMB2_HDR_MESSAGE_ID); + DEBUG(10,("smbd_smb2_request_notify_done: cancelled mid %llu\n", + (unsigned long long)mid )); + return; + } + status = smbd_smb2_notify_recv(subreq, req, &out_output_buffer); @@ -364,6 +372,8 @@ static bool smbd_smb2_notify_cancel(struct tevent_req *req) smbd_notify_cancel_by_smbreq(state->smb2req->sconn, state->smbreq); + state->smb2req->cancelled = true; + tevent_req_done(req); return true; } diff --git a/source3/smbd/smb2_server.c b/source3/smbd/smb2_server.c index f5e3765..16c8082 100644 --- a/source3/smbd/smb2_server.c +++ b/source3/smbd/smb2_server.c @@ -23,6 +23,36 @@ #include "../libcli/smb/smb_common.h" #include "../lib/tsocket/tsocket.h" +static const char *smb2_names[] = { + "SMB2_NEGPROT", + "SMB2_SESSSETUP", + "SMB2_LOGOFF", + "SMB2_TCON", + "SMB2_TDIS", + "SMB2_CREATE", + "SMB2_CLOSE", + "SMB2_FLUSH", + "SMB2_READ", + "SMB2_WRITE", + "SMB2_LOCK", + "SMB2_IOCTL", + "SMB2_CANCEL", + "SMB2_KEEPALIVE", + "SMB2_FIND", + "SMB2_NOTIFY", + "SMB2_GETINFO", + "SMB2_SETINFO", + "SMB2_BREAK" +}; + +const char *smb2_opcode_name(uint16_t opcode) +{ + if (opcode >= 0x12) { + return "Bad SMB2 opcode"; + } + return smb2_names[opcode]; +} + bool smbd_is_smb2_header(const uint8_t *inbuf, size_t size) { if (size < (4 + SMB2_HDR_BODY)) { @@ -421,34 +451,124 @@ void smbd_server_connection_terminate_ex(struct smbd_server_connection *sconn, exit_server_cleanly(reason); } -struct smbd_smb2_request_pending_state { - struct smbd_server_connection *sconn; - uint8_t buf[4 + SMB2_HDR_BODY + 0x08]; - struct iovec vector; -}; +static struct smbd_smb2_request *dup_smb2_req(struct smbd_smb2_request *req) +{ + struct smbd_smb2_request *newreq = NULL; + struct iovec *outvec = NULL; + int count = req->out.vector_count; + int i; + + newreq = smbd_smb2_request_allocate(req->sconn); + if (!newreq) { + return NULL; + } + + newreq->sconn = req->sconn; + newreq->do_signing = req->do_signing; + newreq->current_idx = req->current_idx; + newreq->async = false; + newreq->cancelled = false; + + outvec = talloc_array(newreq, struct iovec, count); + if (!outvec) { + goto fail; + } + newreq->out.vector = outvec; + newreq->out.vector_count = count; + + /* Setup the outvec's identically to req. */ + outvec[0].iov_base = newreq->out.nbt_hdr; + outvec[0].iov_len = 4; + memcpy(newreq->out.nbt_hdr, req->out.nbt_hdr, 4); + + for (i = 1; i < count; i += 3) { + uint8_t *outhdr = NULL; + uint8_t *outbody = NULL; + + outhdr = talloc_memdup(outvec, + req->out.vector[i].iov_base, + SMB2_HDR_BODY + 8); + if (!outhdr) { + goto fail; + } + outbody = outhdr + SMB2_HDR_BODY; + + outvec[i].iov_base = (void *)outhdr; + outvec[i].iov_len = SMB2_HDR_BODY; + + outvec[i+1].iov_base = (void *)outbody; + outvec[i+1].iov_len = 8; + + if (req->out.vector[i+2].iov_len && + req->out.vector[i+2].iov_base) { + outvec[i+2].iov_base = talloc_memdup(outvec, + req->out.vector[i+2].iov_base, + req->out.vector[i+2].iov_len); + if (!outvec[i+2].iov_base) { + goto fail; + } + outvec[i+2].iov_len = req->out.vector[i+2].iov_len; + } else { + outvec[i+2].iov_base = NULL; + outvec[i+2].iov_len = 0; + } + } -static void smbd_smb2_request_pending_writev_done(struct tevent_req *subreq); + smb2_setup_nbt_length(newreq->out.vector, + newreq->out.vector_count); + + return newreq; + + fail: + + TALLOC_FREE(newreq); + return NULL; +} + +static void smbd_smb2_request_writev_done(struct tevent_req *subreq); NTSTATUS smbd_smb2_request_pending_queue(struct smbd_smb2_request *req, struct tevent_req *subreq) { - struct smbd_smb2_request_pending_state *state; - uint8_t *outhdr; int i = req->current_idx; - uint32_t flags; - uint64_t message_id; - uint64_t async_id; - uint8_t *hdr; - uint8_t *body; + struct smbd_smb2_request *nreq = NULL; + uint8_t *outhdr = NULL; + uint8_t *outbody = NULL; + uint32_t flags = 0; + uint64_t message_id = 0; + uint64_t async_id = 0; + struct iovec *outvec = NULL; if (!tevent_req_is_in_progress(subreq)) { return NT_STATUS_OK; } + if (req->async) { + /* We're already async. */ + return NT_STATUS_OK; + } + + if (req->in.vector_count > i + 3) { + /* + * We're trying to go async in a compound + * request chain. This is not allowed. + * Cancel the outstanding request. + */ + tevent_req_cancel(subreq); + return smbd_smb2_request_error(req, + NT_STATUS_INSUFFICIENT_RESOURCES); + } + req->subreq = subreq; subreq = NULL; - outhdr = (uint8_t *)req->out.vector[i].iov_base; + /* Create a new smb2 request we'll use to return. */ + nreq = dup_smb2_req(req); + if (!nreq) { + return NT_STATUS_NO_MEMORY; + } + + outhdr = (uint8_t *)nreq->out.vector[i].iov_base; flags = IVAL(outhdr, SMB2_HDR_FLAGS); message_id = BVAL(outhdr, SMB2_HDR_MESSAGE_ID); @@ -456,78 +576,125 @@ NTSTATUS smbd_smb2_request_pending_queue(struct smbd_smb2_request *req, async_id = message_id; /* keep it simple for now... */ SIVAL(outhdr, SMB2_HDR_FLAGS, flags | SMB2_HDR_FLAG_ASYNC); SBVAL(outhdr, SMB2_HDR_PID, async_id); + SIVAL(outhdr, SMB2_HDR_STATUS, NT_STATUS_V(STATUS_PENDING)); - /* TODO: add a paramter to delay this */ - state = talloc(req->sconn, struct smbd_smb2_request_pending_state); - if (state == NULL) { + outbody = outhdr + SMB2_HDR_BODY; + + /* Setup the same as smbd_smb2_request_error_ex() */ + nreq->out.vector[i+1].iov_base = (void *)outbody; + nreq->out.vector[i+1].iov_len = 8; + nreq->out.vector[i+2].iov_base = NULL; + nreq->out.vector[i+2].iov_len = 0; + + smb2_setup_nbt_length(nreq->out.vector, + nreq->out.vector_count); + + if (nreq->do_signing) { + NTSTATUS status; + status = smb2_signing_sign_pdu(nreq->session->session_key, + &nreq->out.vector[i], 3); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + } + + nreq->subreq = tstream_writev_queue_send(nreq, + nreq->sconn->smb2.event_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; } - state->sconn = req->sconn; - state->vector.iov_base = (void *)state->buf; - state->vector.iov_len = sizeof(state->buf); + tevent_req_set_callback(nreq->subreq, + smbd_smb2_request_writev_done, + nreq); - _smb2_setlen(state->buf, sizeof(state->buf) - 4); - hdr = state->buf + 4; - body = hdr + SMB2_HDR_BODY; + /* Note we're going async with this request. */ + req->async = true; - SIVAL(hdr, SMB2_HDR_PROTOCOL_ID, SMB2_MAGIC); - 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(outhdr, SMB2_HDR_OPCODE)); - SSVAL(hdr, SMB2_HDR_CREDIT, 1); - SIVAL(hdr, SMB2_HDR_FLAGS, - IVAL(outhdr, SMB2_HDR_FLAGS)); - SIVAL(hdr, SMB2_HDR_NEXT_COMMAND, 0); - SBVAL(hdr, SMB2_HDR_MESSAGE_ID, - BVAL(outhdr, SMB2_HDR_MESSAGE_ID)); - SBVAL(hdr, SMB2_HDR_PID, - BVAL(outhdr, SMB2_HDR_PID)); - SBVAL(hdr, SMB2_HDR_SESSION_ID, - BVAL(outhdr, SMB2_HDR_SESSION_ID)); - memset(hdr+SMB2_HDR_SIGNATURE, 0, 16); + /* + * Now manipulate req so that the outstanding async request + * is the only one left in the struct smbd_smb2_request. + */ - SSVAL(body, 0x00, 0x08 + 1); + if (req->current_idx == 1) { + /* There was only one. */ + goto out; + } - SCVAL(body, 0x02, 0); - SCVAL(body, 0x03, 0); - SIVAL(body, 0x04, 0); + /* 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); - subreq = tstream_writev_queue_send(state, - req->sconn->smb2.event_ctx, - req->sconn->smb2.stream, - req->sconn->smb2.send_queue, - &state->vector, 1); - if (subreq == NULL) { + /* Now recreate the out.vectors. */ + outvec = talloc_array(req, struct iovec, 4); + if (!outvec) { return NT_STATUS_NO_MEMORY; } - tevent_req_set_callback(subreq, - smbd_smb2_request_pending_writev_done, - state); + outvec[0].iov_base = req->out.nbt_hdr; + outvec[0].iov_len = 4; + SIVAL(req->out.nbt_hdr, 0, 0); - return NT_STATUS_OK; -} + outvec[1].iov_base = talloc_memdup(outvec, + req->out.vector[i].iov_base, + SMB2_HDR_BODY + 8); + if (!outvec[1].iov_base) { + return NT_STATUS_NO_MEMORY; + } + outvec[1].iov_len = SMB2_HDR_BODY; -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; + outvec[2].iov_base = ((uint8_t *)outvec[1].iov_base) + + SMB2_HDR_BODY; + outvec[2].iov_len = 8; - 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 (req->out.vector[i+2].iov_base && + req->out.vector[i+2].iov_len) { + outvec[3].iov_base = talloc_memdup(outvec, + req->out.vector[i+2].iov_base, + req->out.vector[i+2].iov_len); + if (!outvec[3].iov_base) { + return NT_STATUS_NO_MEMORY; + } + outvec[3].iov_len = req->out.vector[i+2].iov_len; + } else { + outvec[3].iov_base = NULL; + outvec[3].iov_len = 0; } - TALLOC_FREE(state); + 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. */ + outhdr = (uint8_t *)req->out.vector[1].iov_base; + SIVAL(outhdr, SMB2_HDR_FLAGS, flags | SMB2_HDR_FLAG_ASYNC); + SBVAL(outhdr, SMB2_HDR_PID, async_id); + + { + const uint8_t *inhdr = + (const uint8_t *)req->in.vector[i].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) @@ -539,6 +706,7 @@ static NTSTATUS smbd_smb2_request_process_cancel(struct smbd_smb2_request *req) uint32_t flags; uint64_t search_message_id; uint64_t search_async_id; + uint64_t found_id; inhdr = (const uint8_t *)req->in.vector[i].iov_base; @@ -566,17 +734,26 @@ static NTSTATUS smbd_smb2_request_process_cancel(struct smbd_smb2_request *req) if (flags & SMB2_HDR_FLAG_ASYNC) { if (search_async_id == async_id) { + found_id = async_id; break; } } else { if (search_message_id == message_id) { + found_id = message_id; break; } } } if (cur && cur->subreq) { + inhdr = (const uint8_t *)cur->in.vector[i].iov_base; + 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)), + (unsigned long long)found_id )); tevent_req_cancel(cur->subreq); + TALLOC_FREE(cur->subreq); + TALLOC_FREE(cur); } return NT_STATUS_OK; @@ -588,6 +765,7 @@ static NTSTATUS smbd_smb2_request_dispatch(struct smbd_smb2_request *req) int i = req->current_idx; uint16_t opcode; uint32_t flags; + uint64_t mid; NTSTATUS status; NTSTATUS session_status; uint32_t allowed_flags; @@ -598,7 +776,10 @@ static NTSTATUS smbd_smb2_request_dispatch(struct smbd_smb2_request *req) flags = IVAL(inhdr, SMB2_HDR_FLAGS); opcode = IVAL(inhdr, SMB2_HDR_OPCODE); - DEBUG(10,("smbd_smb2_request_dispatch: opcode[%u]\n", opcode)); + mid = BVAL(inhdr, SMB2_HDR_MESSAGE_ID); + DEBUG(10,("smbd_smb2_request_dispatch: opcode[%s] mid = %llu\n", + smb2_opcode_name(opcode), + (unsigned long long)mid)); allowed_flags = SMB2_HDR_FLAG_CHAINED | SMB2_HDR_FLAG_SIGNED | @@ -806,8 +987,9 @@ static NTSTATUS smbd_smb2_request_dispatch(struct smbd_smb2_request *req) return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER); } -static void smbd_smb2_request_dispatch_compound(struct tevent_req *subreq); -static void smbd_smb2_request_writev_done(struct tevent_req *subreq); +static void smbd_smb2_request_dispatch_compound(struct tevent_context *ctx, + struct tevent_immediate *im, + void *private_data); static NTSTATUS smbd_smb2_request_reply(struct smbd_smb2_request *req) { @@ -830,17 +1012,22 @@ static NTSTATUS smbd_smb2_request_reply(struct smbd_smb2_request *req) req->current_idx += 3; if (req->current_idx < req->out.vector_count) { - struct timeval zero = timeval_zero(); - subreq = tevent_wakeup_send(req, - req->sconn->smb2.event_ctx, - zero); - if (subreq == NULL) { + /* + * We must process the remaining compound + * SMB2 requests before any new incoming SMB2 + * requests. This is because incoming SMB2 + * requests may include a cancel for a + * compound request we haven't processed + * yet. + */ + struct tevent_immediate *im = tevent_create_immediate(req); + if (!im) { return NT_STATUS_NO_MEMORY; } - tevent_req_set_callback(subreq, + tevent_schedule_immediate(im, + req->sconn->smb2.event_ctx, smbd_smb2_request_dispatch_compound, req); - return NT_STATUS_OK; } @@ -858,15 +1045,16 @@ static NTSTATUS smbd_smb2_request_reply(struct smbd_smb2_request *req) return NT_STATUS_OK; } -static void smbd_smb2_request_dispatch_compound(struct tevent_req *subreq) +static void smbd_smb2_request_dispatch_compound(struct tevent_context *ctx, + struct tevent_immediate *im, + void *private_data) { - struct smbd_smb2_request *req = tevent_req_callback_data(subreq, + struct smbd_smb2_request *req = talloc_get_type_abort(private_data, struct smbd_smb2_request); struct smbd_server_connection *sconn = req->sconn; NTSTATUS status; - tevent_wakeup_recv(subreq); - TALLOC_FREE(subreq); + TALLOC_FREE(im); DEBUG(10,("smbd_smb2_request_dispatch_compound: idx[%d] of %d vectors\n", req->current_idx, req->in.vector_count)); diff --git a/source3/smbd/smb2_sesssetup.c b/source3/smbd/smb2_sesssetup.c index b3ea3fa..c79a443 100644 --- a/source3/smbd/smb2_sesssetup.c +++ b/source3/smbd/smb2_sesssetup.c @@ -349,16 +349,23 @@ NTSTATUS smbd_smb2_request_check_session(struct smbd_smb2_request *req) in_session_id = BVAL(inhdr, SMB2_HDR_SESSION_ID); - if (i > 2 && in_session_id == (0xFFFFFFFFFFFFFFFFLL)) { - /* - * Chained request - fill in session_id from - * the previous request out.vector[].iov_base. - * We can't modify the inhdr here as we have - * yet to check signing. - */ - outhdr = (const uint8_t *)req->out.vector[i-3].iov_base; - in_session_id = BVAL(outhdr, SMB2_HDR_SESSION_ID); - chained_fixup = true; + if (in_session_id == (0xFFFFFFFFFFFFFFFFLL)) { + if (req->async) { + /* + * async request - fill in session_id from + * already setup request out.vector[].iov_base. + */ + outhdr = (const uint8_t *)req->out.vector[i].iov_base; + in_session_id = BVAL(outhdr, SMB2_HDR_SESSION_ID); + } else if (i > 2) { + /* + * Chained request - fill in session_id from + * the previous request out.vector[].iov_base. + */ + outhdr = (const uint8_t *)req->out.vector[i-3].iov_base; + in_session_id = BVAL(outhdr, SMB2_HDR_SESSION_ID); + chained_fixup = true; + } } /* lookup an existing session */ diff --git a/source3/smbd/smb2_tcon.c b/source3/smbd/smb2_tcon.c index 3eb9da2..c3e46ee 100644 --- a/source3/smbd/smb2_tcon.c +++ b/source3/smbd/smb2_tcon.c @@ -231,15 +231,24 @@ NTSTATUS smbd_smb2_request_check_tcon(struct smbd_smb2_request *req) in_tid = IVAL(inhdr, SMB2_HDR_TID); - if (i > 2 && in_tid == (0xFFFFFFFF)) { - /* - * Chained request - fill in tid from - * the previous request out.vector[].iov_base. - */ - outhdr = (const uint8_t *)req->out.vector[i-3].iov_base; - in_tid = IVAL(outhdr, SMB2_HDR_TID); - chained_fixup = true; - } + if (in_tid == (0xFFFFFFFF)) { + if (req->async) { + /* + * async request - fill in tid from + * already setup out.vector[].iov_base. + */ + outhdr = (const uint8_t *)req->out.vector[i].iov_base; + in_tid = IVAL(outhdr, SMB2_HDR_TID); + } else if (i > 2) { + /* + * Chained request - fill in tid from + * the previous request out.vector[].iov_base. + */ + outhdr = (const uint8_t *)req->out.vector[i-3].iov_base; + in_tid = IVAL(outhdr, SMB2_HDR_TID); + chained_fixup = true; + } + } /* lookup an existing session */ p = idr_find(req->session->tcons.idtree, in_tid);