diff --git a/source3/smbd/aio.c b/source3/smbd/aio.c index 3f553eb..d41868c 100644 --- a/source3/smbd/aio.c +++ b/source3/smbd/aio.c @@ -833,6 +833,11 @@ NTSTATUS schedule_aio_smb2_write(connection_struct *conn, size_t min_aio_write_size = lp_aio_write_size(SNUM(conn)); struct tevent_req *req; + if (smbreq->unread_bytes) { + /* Can't do async with recvfile. */ + return NT_STATUS_RETRY; + } + if (fsp->base_fsp != NULL) { /* No AIO on streams yet */ DEBUG(10, ("AIO on streams not yet supported\n")); diff --git a/source3/smbd/globals.h b/source3/smbd/globals.h index 0d0ebcd..10448e5 100644 --- a/source3/smbd/globals.h +++ b/source3/smbd/globals.h @@ -245,6 +245,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, 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 d92302e..44301ac 100644 --- a/source3/smbd/smb2_server.c +++ b/source3/smbd/smb2_server.c @@ -1824,6 +1824,11 @@ NTSTATUS smbd_smb2_request_verify_sizes(struct smbd_smb2_request *req, case SMB2_OP_GETINFO: min_dyn_size = 0; break; + case SMB2_OP_WRITE: + if (req->smb1req && req->smb1req->unread_bytes) { + min_dyn_size = 0; + } + break; } /* @@ -2626,6 +2631,16 @@ NTSTATUS smbd_smb2_request_error_ex(struct smbd_smb2_request *req, req->current_idx, nt_errstr(status), info ? " +info" : "", location)); + if (req->smb1req && req->smb1req->unread_bytes) { + /* Recvfile error. Drain incoming socket. */ + if (drain_socket(req->sconn->sock, + req->smb1req->unread_bytes) != + req->smb1req->unread_bytes) + { + smb_panic("Failed to drain SMB2 socket\n"); + } + } + body.data = outhdr + SMB2_HDR_BODY; body.length = 8; SSVAL(body.data, 0, 9); @@ -2823,8 +2838,116 @@ struct smbd_smb2_request_read_state { } hdr; size_t pktlen; uint8_t *pktbuf; + bool smb2_hdr_read; + bool smb2_rest_read; }; +static bool smbd_is_smb2_recvfile_write( + struct smbd_smb2_request_read_state *state, + size_t dyn_size) +{ + int min_recv_size = lp_min_receive_file_size(); + const uint8_t *inhdr = state->pktbuf; + size_t next_command_ofs; + uint16_t opcode; + uint32_t in_flags; + uint32_t in_tid; + uint64_t in_session_id; + struct smbXsrv_tcon *tcon; + struct smbXsrv_session *session = NULL; + struct timeval request_time; + NTTIME now; + NTSTATUS status; + + if (min_recv_size == 0) { + /* recvfile not turned on. Nothing to do here. */ + DEBUG(10,("min receivefile size turned off\n")); + return false; + } + + if (IVAL(inhdr, SMB2_HDR_PROTOCOL_ID) != SMB2_MAGIC) { + /* Bad SMB2 header. Let the caller deal with it. */ + DEBUG(10,("bad SMB2 header\n")); + return false; + } + + opcode = SVAL(inhdr, SMB2_HDR_OPCODE); + + if (opcode != SMB2_OP_WRITE) { + /* Only do recvfile in SMB2_WRITE. */ + DEBUG(10,("not a SMB2_WRITE command [opcode=0x%02X]\n", opcode)); + return false; + } + + next_command_ofs = IVAL(inhdr, SMB2_HDR_NEXT_COMMAND); + + if (next_command_ofs != 0) { + /* Can't do recvfile on compound requests. */ + DEBUG(10,("can't do recvfile on compound requests " + "[next command not equal to 0]\n")); + return false; + } + + in_flags = IVAL(inhdr, SMB2_HDR_FLAGS); + + if (in_flags & SMB2_HDR_FLAG_CHAINED) { + /* Can't do recvfile on compound requests. */ + DEBUG(10,("can't do recvfile on compound requests " + "[flag SMB2_FLAGS_RELATED_OPERATIONS set]\n")); + return false; + } + + if (in_flags & SMB2_HDR_FLAG_SIGNED) { + /* Can't do recvfile on signed connection. */ + DEBUG(10,("can't do recvfile on signed connection\n")); + return false; + } + + if (dyn_size == 0 || dyn_size < min_recv_size) { + /* Bad write, or too small for us. Caller handle. */ + DEBUG(10,("bad write command or too small " + "[dyn_size=%lu, min_recv_size=%d]\n", dyn_size, min_recv_size)); + return false; + } + + /* + * can't do recvfile on IPC or print connections. + */ + request_time = timeval_current(); + now = timeval_to_nttime(&request_time); + in_session_id = BVAL(inhdr, SMB2_HDR_SESSION_ID); + status = smb2srv_session_lookup(state->sconn->conn, + in_session_id, now, &session); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(10,("can't look up this session ID [session ID=%ld]\n", + in_session_id)); + return false; + } + if (!session || !NT_STATUS_IS_OK(session->status)) { + DEBUG(10,("session ID %ld is invalid\n", in_session_id)); + return false; + } + + in_tid = IVAL(inhdr, SMB2_HDR_TID); + status = smb2srv_tcon_lookup(session, in_tid, now, &tcon); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(10,("can't look up this tree ID [tree ID=%d]\n", in_tid)); + return false; + } + + if (IS_IPC(tcon->compat) || IS_PRINT(tcon->compat)) { + DEBUG(10,("can't do recvfile on IPC or print connections\n")); + return false; + } + + /* + * OK, this is a recvfile we can handle on + * a file share. + */ + DEBUG(10,("SMB2_WRITE will be handled as recvfile\n")); + return true; +} + static int smbd_smb2_request_next_vector(struct tstream_context *stream, void *private_data, TALLOC_CTX *mem_ctx, @@ -2878,9 +3001,10 @@ static int smbd_smb2_request_next_vector(struct tstream_context *stream, talloc_get_type_abort(private_data, struct smbd_smb2_request_read_state); struct iovec *vector; + size_t pdulen; - if (state->pktlen > 0) { - /* if there're no remaining bytes, we're done */ + if (state->smb2_rest_read && state->pktlen > 0) { + /* all set for this packet, we're done */ *_vector = NULL; *_count = 0; return 0; @@ -2908,27 +3032,88 @@ static int smbd_smb2_request_next_vector(struct tstream_context *stream, /* * Now we analyze the NBT header */ - state->pktlen = smb2_len(state->hdr.nbt); - - if (state->pktlen == 0) { + pdulen = smb2_len(state->hdr.nbt); + if (pdulen == 0) { /* if there're no remaining bytes, we're done */ *_vector = NULL; *_count = 0; return 0; } - state->pktbuf = talloc_array(state->smb2_req, uint8_t, state->pktlen); - if (state->pktbuf == NULL) { - return -1; - } + /* + * allocate memory for vector to be returned + */ vector = talloc_array(mem_ctx, struct iovec, 1); if (vector == NULL) { return -1; } - vector[0].iov_base = (void *)state->pktbuf; - vector[0].iov_len = state->pktlen; + if (!state->smb2_hdr_read) { + /* + * SMB2 header not read yet, so... + * 1. allocate buffer for whole SMB2 packet + * 2. mark SMB2 header as read + * 3. return next vector (reading SMB2 header only) + */ + state->pktlen = pdulen; + state->pktbuf = talloc_array(state->smb2_req, uint8_t, state->pktlen); + if (state->pktbuf == NULL) { + return -1; + } + + state->smb2_hdr_read = true; + + /* + * read whole SMB2 header plus next 2 bytes + * (size of SMB2 command) and store at the + * beginning of PDU buffer + */ + vector[0].iov_base = (void *)state->pktbuf; + vector[0].iov_len = SMB2_HDR_BODY + 2; + } else { + /* + * SMB2 header has been read already, so... + * 1. check whether this is a SMB2_WRITE with recvfile turned on + * 2. if so, adjust packet length to be read + * 3. if not, proceed to read rest of the packet + * 4. return vector (either adjusted or not) + */ + + /* + * assuming this is a SMB2_WRITE, variable buffer length will be the + * whole PDU packet lenght minus the SMB2 and SMB2_WRITE headers + * together + */ + size_t cmd_size = SVAL(state->pktbuf, SMB2_HDR_BODY) & 0xFFFE; + size_t dyn_size = state->pktlen - (SMB2_HDR_BODY + cmd_size); + + if (smbd_is_smb2_recvfile_write(state, dyn_size)) { + /* + * adjust packet and allocate space + * for fake SMB1 request + */ + state->smb2_req->smb1req = talloc_zero(state->smb2_req, + struct smb_request); + if (!state->smb2_req->smb1req) { + return -1; + } + + state->smb2_req->smb1req->unread_bytes = dyn_size; + state->pktlen -= dyn_size; + + state->pktbuf = talloc_realloc(state->smb2_req, state->pktbuf, + uint8_t, state->pktlen); + if (state->pktbuf == NULL) { + return -1; + } + } + + state->smb2_rest_read = true; + + vector[0].iov_base = (void *)(state->pktbuf + SMB2_HDR_BODY + 2); + vector[0].iov_len = state->pktlen - SMB2_HDR_BODY - 2; + } *_vector = vector; *_count = 1; diff --git a/source3/smbd/smb2_write.c b/source3/smbd/smb2_write.c index f9cfbfc..6800070 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,21 @@ 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) { + DEBUG(2,("smbd_smb2_request_process_write : " + "data length in SMB2 packet does not match " + ":%s:PDU=0x%08X:guessed=0x%08X\n", __location__, + in_data_length, (unsigned int)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); }