Add a new implementation of chain_reply
authorVolker Lendecke <vl@samba.org>
Mon, 10 Nov 2008 09:01:26 +0000 (10:01 +0100)
committerVolker Lendecke <vl@samba.org>
Fri, 16 Jan 2009 12:00:28 +0000 (13:00 +0100)
This the global variable "orig_inbuf" in the old chain_reply code. This global
variable was one of the reasons why we had the silly restriction to not allow
async requests within a request chain.

source3/include/smb.h
source3/smbd/blocking.c
source3/smbd/process.c

index 4d7d4b2f385f04dadd13a10c47ace1977fb81abb..3c727bafbf85d00b74838414ee2c7850a375b109 100644 (file)
@@ -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. */
index cccc5ce727af6c477ef517ed5ee3b50a175b33ee..9936fb219fce01c88271aa9a220a9b476c0d686c 100644 (file)
@@ -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);
 }
 
index 60e58aac225bd741dbe25dd190f35010bca48c1f..3547bfcc3a78487e01f090448acbacb16e32b2d6 100644 (file)
@@ -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.
 ****************************************************************************/