diff --git a/source3/smbd/aio.c b/source3/smbd/aio.c index 3f553eb..e8be408 100644 --- a/source3/smbd/aio.c +++ b/source3/smbd/aio.c @@ -861,6 +861,11 @@ NTSTATUS schedule_aio_smb2_write(connection_struct *conn, return NT_STATUS_RETRY; } + if (smbreq->unread_bytes) { + /* Can't do async with recvfile. */ + return NT_STATUS_RETRY; + } + if (!(aio_ex = create_aio_extra(smbreq->smb2req, fsp, 0))) { return NT_STATUS_NO_MEMORY; } diff --git a/source3/smbd/globals.h b/source3/smbd/globals.h index 3461bc5..f075a6d 100644 --- a/source3/smbd/globals.h +++ b/source3/smbd/globals.h @@ -247,6 +247,7 @@ NTSTATUS smbd_smb2_request_pending_queue(struct smbd_smb2_request *req, uint32_t defer_time); struct smb_request *smbd_smb2_fake_smb_request(struct smbd_smb2_request *req); +size_t smbd_smb2_unread_bytes(struct smbd_smb2_request *req); void remove_smb2_chained_fsp(files_struct *fsp); NTSTATUS smbd_smb2_request_verify_creditcharge(struct smbd_smb2_request *req, @@ -545,6 +546,8 @@ struct smbd_smb2_request { #define SMBD_SMB2_OUT_DYN_PTR(req) (uint8_t *)(SMBD_SMB2_OUT_DYN_IOV(req)->iov_base) #define SMBD_SMB2_OUT_DYN_LEN(req) (SMBD_SMB2_OUT_DYN_IOV(req)->iov_len) +#define SMBD_SMB2_SHORT_RECEIVEFILE_WRITE_LEN (SMB2_HDR_BODY + 0x30) + struct { /* * vector[0] TRANSPORT HEADER (empty) diff --git a/source3/smbd/smb2_glue.c b/source3/smbd/smb2_glue.c index 1b2b4dd..23d6ce5 100644 --- a/source3/smbd/smb2_glue.c +++ b/source3/smbd/smb2_glue.c @@ -28,9 +28,13 @@ struct smb_request *smbd_smb2_fake_smb_request(struct smbd_smb2_request *req) struct smb_request *smbreq; const uint8_t *inhdr = SMBD_SMB2_IN_HDR_PTR(req); - smbreq = talloc_zero(req, struct smb_request); - if (smbreq == NULL) { - return NULL; + if (req->smb1req) { + smbreq = req->smb1req; + } else { + smbreq = talloc_zero(req, struct smb_request); + if (smbreq == NULL) { + return NULL; + } } smbreq->request_time = req->request_time; @@ -55,6 +59,18 @@ struct smb_request *smbd_smb2_fake_smb_request(struct smbd_smb2_request *req) } /********************************************************* + Are there unread bytes for recvfile ? +*********************************************************/ + +size_t smbd_smb2_unread_bytes(struct smbd_smb2_request *req) +{ + if (req->smb1req && req->smb1req->unread_bytes) { + return req->smb1req->unread_bytes; + } + return 0; +} + +/********************************************************* Called from file_free() to remove any chained fsp pointers. *********************************************************/ diff --git a/source3/smbd/smb2_server.c b/source3/smbd/smb2_server.c index f486236..327696b 100644 --- a/source3/smbd/smb2_server.c +++ b/source3/smbd/smb2_server.c @@ -2621,11 +2621,20 @@ NTSTATUS smbd_smb2_request_error_ex(struct smbd_smb2_request *req, { DATA_BLOB body; uint8_t *outhdr = SMBD_SMB2_OUT_HDR_PTR(req); + size_t unread_bytes = smbd_smb2_unread_bytes(req); DEBUG(10,("smbd_smb2_request_error_ex: idx[%d] status[%s] |%s| at %s\n", req->current_idx, nt_errstr(status), info ? " +info" : "", location)); + if (unread_bytes) { + /* Recvfile error. Drain incoming socket. */ + if (drain_socket(req->sconn->sock, unread_bytes) != + unread_bytes) { + smb_panic("Failed to drain SMB2 socket\n"); + } + } + body.data = outhdr + SMB2_HDR_BODY; body.length = 8; SSVAL(body.data, 0, 9); @@ -2821,6 +2830,8 @@ struct smbd_smb2_request_read_state { uint8_t nbt[NBT_HDR_SIZE]; bool done; } hdr; + bool doing_receivefile; + size_t min_recv_size; size_t pktlen; uint8_t *pktbuf; }; @@ -2832,6 +2843,17 @@ static int smbd_smb2_request_next_vector(struct tstream_context *stream, size_t *_count); static void smbd_smb2_request_read_done(struct tevent_req *subreq); +static size_t get_min_receive_file_size(struct smbd_smb2_request *smb2_req) +{ + if (smb2_req->do_signing) { + return 0; + } + if (smb2_req->do_encryption) { + return 0; + } + return (size_t)lp_min_receive_file_size(); +} + static struct tevent_req *smbd_smb2_request_read_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct smbd_server_connection *sconn) @@ -2853,6 +2875,7 @@ static struct tevent_req *smbd_smb2_request_read_send(TALLOC_CTX *mem_ctx, return tevent_req_post(req, ev); } state->smb2_req->sconn = sconn; + state->min_recv_size = get_min_receive_file_size(state->smb2_req); subreq = tstream_readv_pdu_queue_send(state->smb2_req, state->ev, @@ -2868,6 +2891,44 @@ static struct tevent_req *smbd_smb2_request_read_send(TALLOC_CTX *mem_ctx, return req; } +static bool is_smb2_recvfile_write(struct smbd_smb2_request_read_state *state) +{ + if (IVAL(state->pktbuf, 0) == SMB2_TF_MAGIC) { + /* Transform header. Cannot recvfile. */ + return false; + } + + if (IVAL(state->pktbuf, 0) != SMB2_MAGIC) { + /* Not SMB2. Normal error path will cope. */ + return false; + } + if (SVAL(state->pktbuf, 4) != SMB2_HDR_BODY) { + /* Not SMB2. Normal error path will cope. */ + return false; + } + if (IVAL(state->pktbuf, SMB2_HDR_NEXT_COMMAND) != 0) { + /* Chained. Cannot recvfile. */ + return false; + } + if (IVAL(state->pktbuf, SMB2_HDR_FLAGS) & + (SMB2_HDR_FLAG_CHAINED| + SMB2_HDR_FLAG_SIGNED)) { + /* Signed or chained. Cannot recvfile. */ + return false; + } + + if (SVAL(state->pktbuf, SMB2_HDR_OPCODE) != SMB2_OP_WRITE) { + /* Needs to be a WRITE. */ + return false; + } + + DEBUG(10,("Doing recvfile write len = %u!\n", + (unsigned int)(state->pktlen - + SMBD_SMB2_SHORT_RECEIVEFILE_WRITE_LEN))); + + return true; +} + static int smbd_smb2_request_next_vector(struct tstream_context *stream, void *private_data, TALLOC_CTX *mem_ctx, @@ -2877,12 +2938,36 @@ static int smbd_smb2_request_next_vector(struct tstream_context *stream, struct smbd_smb2_request_read_state *state = talloc_get_type_abort(private_data, struct smbd_smb2_request_read_state); - struct iovec *vector; + struct iovec *vector = NULL; if (state->pktlen > 0) { - /* if there're no remaining bytes, we're done */ - *_vector = NULL; - *_count = 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; + vector = talloc_array(mem_ctx, struct iovec, 1); + if (vector == NULL) { + return -1; + } + vector[0].iov_base = (void *)(state->pktbuf + + SMBD_SMB2_SHORT_RECEIVEFILE_WRITE_LEN); + vector[0].iov_len = (state->pktlen - + SMBD_SMB2_SHORT_RECEIVEFILE_WRITE_LEN); + *_vector = vector; + *_count = 1; + } else { + /* + * Either this is a receivefile write so we've + * done a short read, or if not we have all the data. + * Either way, we're done and the caller will handle + * and short read case by looking at the + * state->doing_receivefile value. + */ + *_vector = NULL; + *_count = 0; + } return 0; } @@ -2928,7 +3013,22 @@ static int smbd_smb2_request_next_vector(struct tstream_context *stream, } vector[0].iov_base = (void *)state->pktbuf; - vector[0].iov_len = state->pktlen; + if (state->min_recv_size && + state->pktlen > SMBD_SMB2_SHORT_RECEIVEFILE_WRITE_LEN + + state->min_recv_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. + */ + vector[0].iov_len = SMBD_SMB2_SHORT_RECEIVEFILE_WRITE_LEN; + state->doing_receivefile = true; + } else { + vector[0].iov_len = state->pktlen; + } *_vector = vector; *_count = 1; @@ -2991,6 +3091,15 @@ static void smbd_smb2_request_read_done(struct tevent_req *subreq) return; } + if (state->doing_receivefile) { + state->smb2_req->smb1req = talloc_zero(req, struct smb_request); + if (tevent_req_nomem(state->smb2_req->smb1req, req)) { + return; + } + state->smb2_req->smb1req->unread_bytes = + state->pktlen - SMBD_SMB2_SHORT_RECEIVEFILE_WRITE_LEN; + } + state->smb2_req->current_idx = 1; tevent_req_done(req); diff --git a/source3/smbd/smb2_write.c b/source3/smbd/smb2_write.c index f9cfbfc..bad8c17 100644 --- a/source3/smbd/smb2_write.c +++ b/source3/smbd/smb2_write.c @@ -49,6 +49,7 @@ NTSTATUS smbd_smb2_request_process_write(struct smbd_smb2_request *req) struct files_struct *in_fsp; uint32_t in_flags; struct tevent_req *subreq; + size_t unread_bytes; status = smbd_smb2_request_verify_sizes(req, 0x31); if (!NT_STATUS_IS_OK(status)) { @@ -67,7 +68,17 @@ NTSTATUS smbd_smb2_request_process_write(struct smbd_smb2_request *req) return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER); } - if (in_data_length > SMBD_SMB2_IN_DYN_LEN(req)) { + unread_bytes = smbd_smb2_unread_bytes(req); + if (unread_bytes) { + /* + * RECVFILE code path. Ensure we have the + * correct number of bytes left in the socket + * buffers. + */ + if (in_data_length != unread_bytes) { + return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER); + } + } else if (in_data_length > SMBD_SMB2_IN_DYN_LEN(req)) { return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER); }