diff options
-rw-r--r-- | source3/include/smb.h | 9 | ||||
-rw-r--r-- | source3/smbd/blocking.c | 6 | ||||
-rw-r--r-- | source3/smbd/process.c | 217 |
3 files changed, 226 insertions, 6 deletions
diff --git a/source3/include/smb.h b/source3/include/smb.h index 4d7d4b2f38..3c727bafbf 100644 --- a/source3/include/smb.h +++ b/source3/include/smb.h @@ -627,7 +627,16 @@ struct smb_request { size_t unread_bytes; bool encrypted; connection_struct *conn; + + /* + * Chained request handling + */ struct files_struct *chain_fsp; + + /* + * Here we collect the outbufs from the chain handlers + */ + uint8_t *chain_outbuf; }; /* Defines for the sent_oplock_break field above. */ diff --git a/source3/smbd/blocking.c b/source3/smbd/blocking.c index cccc5ce727..9936fb219f 100644 --- a/source3/smbd/blocking.c +++ b/source3/smbd/blocking.c @@ -238,12 +238,6 @@ static void reply_lockingX_success(blocking_lock_record *blr) */ chain_reply(blr->req); - - if (!srv_send_smb(smbd_server_fd(), (char *)blr->req->outbuf, - IS_CONN_ENCRYPTED(blr->fsp->conn))) { - exit_server_cleanly("send_blocking_reply: srv_send_smb failed."); - } - TALLOC_FREE(blr->req->outbuf); } diff --git a/source3/smbd/process.c b/source3/smbd/process.c index 60e58aac22..3547bfcc3a 100644 --- a/source3/smbd/process.c +++ b/source3/smbd/process.c @@ -372,6 +372,7 @@ void init_smb_request(struct smb_request *req, req->encrypted = encrypted; req->conn = conn_find(req->tid); req->chain_fsp = NULL; + req->chain_outbuf = NULL; /* Ensure we have at least wct words and 2 bytes of bcc. */ if (smb_size + req->wct*2 > req_size) { @@ -1613,6 +1614,8 @@ void construct_reply_common_req(struct smb_request *req, char *outbuf) Construct a chained reply and add it to the already made reply ****************************************************************************/ +#if 0 + void chain_reply(struct smb_request *req) { /* @@ -1817,6 +1820,220 @@ void chain_reply(struct smb_request *req) return; } +#else + +/* + * Hack around reply_nterror & friends not being aware of chained requests, + * generating illegal (i.e. wct==0) chain replies. + */ + +static void fixup_chain_error_packet(struct smb_request *req) +{ + uint8_t *outbuf = req->outbuf; + req->outbuf = NULL; + reply_outbuf(req, 2, 0); + memcpy(req->outbuf, outbuf, smb_wct); + TALLOC_FREE(outbuf); + SCVAL(req->outbuf, smb_vwv0, 0xff); +} + +void chain_reply(struct smb_request *req) +{ + size_t smblen = smb_len(req->inbuf); + size_t already_used, length_needed; + uint8_t chain_cmd; + uint32_t chain_offset; /* uint32_t to avoid overflow */ + + uint8_t wct; + uint16_t *vwv; + uint16_t buflen; + uint8_t *buf; + + if (IVAL(req->outbuf, smb_rcls) != 0) { + fixup_chain_error_packet(req); + } + + /* + * Any of the AndX requests and replies have at least a wct of + * 2. vwv[0] is the next command, vwv[1] is the offset from the + * beginning of the SMB header to the next wct field. + * + * None of the AndX requests put anything valuable in vwv[0] and [1], + * so we can overwrite it here to form the chain. + */ + + if ((req->wct < 2) || (CVAL(req->outbuf, smb_wct) < 2)) { + goto error; + } + + /* + * Here we assume that this is the end of the chain. For that we need + * to set "next command" to 0xff and the offset to 0. If we later find + * more commands in the chain, this will be overwritten again. + */ + + SCVAL(req->outbuf, smb_vwv0, 0xff); + SCVAL(req->outbuf, smb_vwv0+1, 0); + SSVAL(req->outbuf, smb_vwv1, 0); + + if (req->chain_outbuf == NULL) { + /* + * In req->chain_outbuf we collect all the replies. Start the + * chain by copying in the first reply. + */ + req->chain_outbuf = req->outbuf; + req->outbuf = NULL; + } else { + if (!smb_splice_chain(&req->chain_outbuf, + CVAL(req->outbuf, smb_com), + CVAL(req->outbuf, smb_wct), + (uint16_t *)(req->outbuf + smb_vwv), + 0, smb_buflen(req->outbuf), + (uint8_t *)smb_buf(req->outbuf))) { + goto error; + } + TALLOC_FREE(req->outbuf); + } + + /* + * We use the old request's vwv field to grab the next chained command + * and offset into the chained fields. + */ + + chain_cmd = CVAL(req->vwv+0, 0); + chain_offset = SVAL(req->vwv+1, 0); + + if (chain_cmd == 0xff) { + /* + * End of chain, no more requests from the client. So ship the + * replies. + */ + smb_setlen((char *)(req->chain_outbuf), + talloc_get_size(req->chain_outbuf) - 4); + if (!srv_send_smb(smbd_server_fd(), (char *)req->chain_outbuf, + IS_CONN_ENCRYPTED(req->conn) + ||req->encrypted)) { + exit_server_cleanly("chain_reply: srv_send_smb " + "failed."); + } + return; + } + + /* + * Check if the client tries to fool us. The request so far uses the + * space to the end of the byte buffer in the request just + * processed. The chain_offset can't point into that area. If that was + * the case, we could end up with an endless processing of the chain, + * we would always handle the same request. + */ + + already_used = PTR_DIFF(req->buf+req->buflen, smb_base(req->inbuf)); + if (chain_offset < already_used) { + goto error; + } + + /* + * Next check: Make sure the chain offset does not point beyond the + * overall smb request length. + */ + + length_needed = chain_offset+1; /* wct */ + if (length_needed > smblen) { + goto error; + } + + /* + * Now comes the pointer magic. Goal here is to set up req->vwv and + * req->buf correctly again to be able to call the subsequent + * switch_message(). The chain offset (the former vwv[1]) points at + * the new wct field. + */ + + wct = CVAL(smb_base(req->inbuf), chain_offset); + + /* + * Next consistency check: Make the new vwv array fits in the overall + * smb request. + */ + + length_needed += (wct+1)*sizeof(uint16_t); /* vwv+buflen */ + if (length_needed > smblen) { + goto error; + } + vwv = (uint16_t *)(smb_base(req->inbuf) + chain_offset + 1); + + /* + * Now grab the new byte buffer.... + */ + + buflen = SVAL(vwv+wct, 0); + + /* + * .. and check that it fits. + */ + + length_needed += buflen; + if (length_needed > smblen) { + goto error; + } + buf = (uint8_t *)(vwv+wct+1); + + req->cmd = chain_cmd; + req->wct = wct; + req->vwv = vwv; + req->buflen = buflen; + req->buf = buf; + + switch_message(chain_cmd, req, smblen); + + if (req->outbuf == NULL) { + /* + * This happens if the chained command has suspended itself or + * if it has called srv_send_smb() itself. + */ + return; + } + + /* + * We end up here if the chained command was not itself chained or + * suspended, but for example a close() command. We now need to splice + * the chained commands' outbuf into the already built up chain_outbuf + * and ship the result. + */ + goto done; + + error: + /* + * We end up here if there's any error in the chain syntax. Report a + * DOS error, just like Windows does. + */ + reply_nterror(req, NT_STATUS_DOS(ERRSRV, ERRerror)); + fixup_chain_error_packet(req); + + done: + if (!smb_splice_chain(&req->chain_outbuf, + CVAL(req->outbuf, smb_com), + CVAL(req->outbuf, smb_wct), + (uint16_t *)(req->outbuf + smb_vwv), + 0, smb_buflen(req->outbuf), + (uint8_t *)smb_buf(req->outbuf))) { + exit_server_cleanly("chain_reply: smb_splice_chain failed\n"); + } + TALLOC_FREE(req->outbuf); + + smb_setlen((char *)(req->chain_outbuf), + talloc_get_size(req->chain_outbuf) - 4); + + show_msg((char *)(req->chain_outbuf)); + + if (!srv_send_smb(smbd_server_fd(), (char *)req->chain_outbuf, + IS_CONN_ENCRYPTED(req->conn)||req->encrypted)) { + exit_server_cleanly("construct_reply: srv_send_smb failed."); + } +} + +#endif + /**************************************************************************** Check if services need reloading. ****************************************************************************/ |