Add new parameter, "min receivefile size" (by default set
authorJeremy Allison <jra@samba.org>
Tue, 30 Oct 2007 23:22:24 +0000 (16:22 -0700)
committerJeremy Allison <jra@samba.org>
Tue, 30 Oct 2007 23:22:24 +0000 (16:22 -0700)
to zero). If non-zero, writeX calls greater than this
value will be left in the socket buffer for later handling
with recvfile (or userspace equivalent). Definition of
recvfile for your system is left as an exercise for
the reader (I'm working on getting splice working :-).
Jeremy.
(This used to be commit 11c03b75ddbcb6e36b231bb40a1773d1c550621c)

source3/include/smb.h
source3/lib/recvfile.c
source3/lib/util_sock.c
source3/param/loadparm.c
source3/smbd/blocking.c
source3/smbd/fileio.c
source3/smbd/notify.c
source3/smbd/process.c
source3/smbd/reply.c
source3/smbd/server.c
source3/smbd/vfs.c

index 4c51acf6f41080572f608c3d8b0f9cb47aa66ec6..303f7606d3d35f5552f0ae95c02bc2ed15e56f8e 100644 (file)
@@ -675,6 +675,7 @@ struct smb_request {
        uint8  wct;
        const uint8 *inbuf;
        uint8 *outbuf;
+       size_t unread_bytes;
 };
 
 /* Defines for the sent_oplock_break field above. */
index cd9fb12716a900bdc3ff4fd1134d38d7047e684b..9d77f94a2913d3e10b57dfc965b897a3517d7db5 100644 (file)
@@ -125,13 +125,49 @@ static ssize_t default_sys_recvfile(int fromfd,
 }
 
 #if defined(HAVE_SPLICE_SYSCALL)
+
+#ifdef JRA_SPLICE_TEST
+#include <linux/unistd.h>
+#include <sys/syscall.h>
+
+#define __NR_splice             313
+_syscall6( long, splice,
+               int, fromfd,
+               loff_t *, fromoffset,
+               int, tofd,
+               loff_t *, tooffset,
+               size_t, count,
+               unsigned int, flags);
+#endif
+
 ssize_t sys_recvfile(int fromfd,
                        int tofd,
                        SMB_OFF_T offset,
                        size_t count)
 {
-       errno = ENOSYS
-       return -1;
+       size_t total = 0;
+
+       if (count == 0) {
+               return 0;
+       }
+
+       while (total < count) {
+               ssize_t ret = splice(fromfd,
+                                       NULL,
+                                       tofd,
+                                       &offset,
+                                       count,
+                                       0);
+               if (ret == -1) {
+                       if (errno != EINTR) {
+                               return -1;
+                       }
+                       continue;
+               }
+               total += ret;
+               count -= ret;
+       }
+       return total;
 }
 #else
 
index e66bd5f15a11fce09a69e8fb2b376df52b9d26dd..bbcbcacb4ab22d5a739d41a773dabd00ccd21b02 100644 (file)
@@ -1113,7 +1113,7 @@ bool send_keepalive(int client)
  Timeout is in milliseconds.
 ****************************************************************************/
 
-static ssize_t read_smb_length_return_keepalive(int fd,
+ssize_t read_smb_length_return_keepalive(int fd,
                                                char *inbuf,
                                                unsigned int timeout)
 {
@@ -1260,86 +1260,6 @@ ssize_t receive_smb_raw(int fd,
        return len;
 }
 
-static ssize_t receive_smb_raw_talloc(TALLOC_CTX *mem_ctx, int fd,
-                                     char **buffer, unsigned int timeout)
-{
-       char lenbuf[4];
-       ssize_t len,ret;
-
-       smb_read_error = 0;
-
-       len = read_smb_length_return_keepalive(fd, lenbuf, timeout);
-       if (len < 0) {
-               DEBUG(10,("receive_smb_raw: length < 0!\n"));
-
-               /*
-                * Correct fix. smb_read_error may have already been
-                * set. Only set it here if not already set. Global
-                * variables still suck :-). JRA.
-                */
-
-               if (smb_read_error == 0)
-                       smb_read_error = READ_ERROR;
-               return -1;
-       }
-
-       /*
-        * A WRITEX with CAP_LARGE_WRITEX can be 64k worth of data plus 65 bytes
-        * of header. Don't print the error if this fits.... JRA.
-        */
-
-       if (len > (BUFFER_SIZE + LARGE_WRITEX_HDR_SIZE)) {
-               DEBUG(0,("Invalid packet length! (%lu bytes).\n",
-                                       (unsigned long)len));
-               if (len > BUFFER_SIZE + (SAFETY_MARGIN/2)) {
-
-                       /*
-                        * Correct fix. smb_read_error may have already been
-                        * set. Only set it here if not already set. Global
-                        * variables still suck :-). JRA.
-                        */
-
-                       if (smb_read_error == 0)
-                               smb_read_error = READ_ERROR;
-                       return -1;
-               }
-       }
-
-       /*
-        * The +4 here can't wrap, we've checked the length above already.
-        */
-
-       *buffer = TALLOC_ARRAY(mem_ctx, char, len+4);
-
-       if (*buffer == NULL) {
-               DEBUG(0, ("Could not allocate inbuf of length %d\n",
-                         (int)len+4));
-               if (smb_read_error == 0)
-                       smb_read_error = READ_ERROR;
-               return -1;
-       }
-
-       memcpy(*buffer, lenbuf, sizeof(lenbuf));
-
-       if(len > 0) {
-               if (timeout > 0) {
-                       ret = read_socket_with_timeout(fd,(*buffer)+4, len,
-                                                      len, timeout);
-               } else {
-                       ret = read_data(fd, (*buffer)+4, len);
-               }
-
-               if (ret != len) {
-                       if (smb_read_error == 0) {
-                               smb_read_error = READ_ERROR;
-                       }
-                       return -1;
-               }
-       }
-
-       return len + 4;
-}
-
 /****************************************************************************
  Wrapper for receive_smb_raw().
  Checks the MAC on signed packets.
@@ -1364,30 +1284,6 @@ bool receive_smb(int fd, char *buffer, unsigned int timeout)
        return true;
 }
 
-ssize_t receive_smb_talloc(TALLOC_CTX *mem_ctx, int fd, char **buffer,
-                          unsigned int timeout)
-{
-       ssize_t len;
-
-       len = receive_smb_raw_talloc(mem_ctx, fd, buffer, timeout);
-
-       if (len < 0) {
-               return -1;
-       }
-
-       /* Check the incoming SMB signature. */
-       if (!srv_check_sign_mac(*buffer, true)) {
-               DEBUG(0, ("receive_smb: SMB Signature verification failed on "
-                         "incoming packet!\n"));
-               if (smb_read_error == 0) {
-                       smb_read_error = READ_BAD_SIG;
-               }
-               return -1;
-       }
-
-       return len;
-}
-
 /****************************************************************************
  Send an smb to a fd.
 ****************************************************************************/
index dcec6bce897861672a2c83e2f81224178bfa88ef..a5b264756795ef6ada87b6c0f62cd4bc2625bf9c 100644 (file)
@@ -331,6 +331,7 @@ typedef struct {
 
        bool bResetOnZeroVC;
        int iKeepalive;
+       int iminreceivefile;
        param_opt_struct *param_opt;
 } global;
 
@@ -998,6 +999,7 @@ static struct parm_struct parm_table[] = {
        {"max protocol", P_ENUM, P_GLOBAL, &Globals.maxprotocol, NULL, enum_protocol, FLAG_ADVANCED}, 
        {"protocol", P_ENUM, P_GLOBAL, &Globals.maxprotocol, NULL, enum_protocol, FLAG_ADVANCED}, 
        {"min protocol", P_ENUM, P_GLOBAL, &Globals.minprotocol, NULL, enum_protocol, FLAG_ADVANCED}, 
+       {"min receivefile size", P_INTEGER, P_GLOBAL, &Globals.iminreceivefile, NULL, NULL, FLAG_ADVANCED}, 
        {"read raw", P_BOOL, P_GLOBAL, &Globals.bReadRaw, NULL, NULL, FLAG_ADVANCED}, 
        {"write raw", P_BOOL, P_GLOBAL, &Globals.bWriteRaw, NULL, NULL, FLAG_ADVANCED}, 
        {"disable netbios", P_BOOL, P_GLOBAL, &Globals.bDisableNetbios, NULL, NULL, FLAG_ADVANCED}, 
@@ -1708,6 +1710,8 @@ static void init_globals(bool first_time_only)
 
        /* By default no shares out of the registry */
        Globals.bRegistryShares = False;
+
+       Globals.iminreceivefile = 0;
 }
 
 /*******************************************************************
@@ -2165,6 +2169,7 @@ FN_GLOBAL_INTEGER(lp_algorithmic_rid_base, &Globals.AlgorithmicRidBase)
 FN_GLOBAL_INTEGER(lp_name_cache_timeout, &Globals.name_cache_timeout)
 FN_GLOBAL_INTEGER(lp_client_signing, &Globals.client_signing)
 FN_GLOBAL_INTEGER(lp_server_signing, &Globals.server_signing)
+FN_GLOBAL_INTEGER(lp_min_receive_file_size, &Globals.iminreceivefile);
 FN_GLOBAL_INTEGER(lp_client_ldap_sasl_wrapping, &Globals.client_ldap_sasl_wrapping)
 
 /* local prototypes */
index 66a61449a1d39546bef188e191479a78a5a713a2..0078bb7d13dd11226af0c513c6800539df01dede 100644 (file)
@@ -259,7 +259,7 @@ static void reply_lockingX_success(blocking_lock_record *blr)
                smb_panic("Could not allocate smb_request");
        }
 
-       init_smb_request(req, (uint8 *)blr->inbuf);
+       init_smb_request(req, (uint8 *)blr->inbuf, 0);
        reply_outbuf(req, 2, 0);
 
        /*
@@ -531,7 +531,7 @@ static bool process_trans2(blocking_lock_record *blr)
                return True;
        }
 
-       init_smb_request(req, (uint8 *)blr->inbuf);
+       init_smb_request(req, (uint8 *)blr->inbuf, 0);
 
        SCVAL(req->inbuf, smb_com, SMBtrans2);
        SSVAL(params,0,0);
index 14056ddc803b99e7bee63365d8aac690fdcd0c5f..9e296f2204ed7e0945d31abb4f85723e476c0f7f 100644 (file)
@@ -116,12 +116,16 @@ static unsigned int allocated_write_caches;
  *Really* write to a file.
 ****************************************************************************/
 
-static ssize_t real_write_file(files_struct *fsp,const char *data, SMB_OFF_T pos, size_t n)
+static ssize_t real_write_file(struct smb_request *req,
+                               files_struct *fsp,
+                               const char *data,
+                               SMB_OFF_T pos,
+                               size_t n)
 {
        ssize_t ret;
 
         if (pos == -1) {
-                ret = vfs_write_data(fsp, data, n);
+                ret = vfs_write_data(req, fsp, data, n);
         } else {
                fsp->fh->pos = pos;
                if (pos && lp_strict_allocate(SNUM(fsp->conn))) {
@@ -129,7 +133,7 @@ static ssize_t real_write_file(files_struct *fsp,const char *data, SMB_OFF_T pos
                                return -1;
                        }
                }
-                ret = vfs_pwrite_data(fsp, data, n, pos);
+                ret = vfs_pwrite_data(req, fsp, data, n, pos);
        }
 
        DEBUG(10,("real_write_file (%s): pos = %.0f, size = %lu, returned %ld\n",
@@ -191,11 +195,15 @@ static int wcp_file_size_change(files_struct *fsp)
  Write to a file.
 ****************************************************************************/
 
-ssize_t write_file(files_struct *fsp, const char *data, SMB_OFF_T pos, size_t n)
+ssize_t write_file(struct smb_request *req,
+                       files_struct *fsp,
+                       const char *data,
+                       SMB_OFF_T pos,
+                       size_t n)
 {
        write_cache *wcp = fsp->wcp;
        ssize_t total_written = 0;
-       int write_path = -1; 
+       int write_path = -1;
 
        if (fsp->print_file) {
                fstring sharename;
@@ -234,8 +242,8 @@ ssize_t write_file(files_struct *fsp, const char *data, SMB_OFF_T pos, size_t n)
                        if (EXCLUSIVE_OPLOCK_TYPE(fsp->oplock_type) && !wcp) {
                                setup_write_cache(fsp, st.st_size);
                                wcp = fsp->wcp;
-                       } 
-               }  
+                       }
+               }
        }
 
 #ifdef WITH_PROFILE
@@ -280,9 +288,18 @@ nonop=%u allocated=%u active=%u direct=%u perfect=%u readhits=%u\n",
        }
 #endif
 
+       if (wcp && req->unread_bytes) {
+               /* If we're using receivefile don't
+                * deal with a write cache.
+                */
+               flush_write_cache(fsp, WRITE_FLUSH);
+               delete_write_cache(fsp);
+               wcp = NULL;
+       }
+
        if(!wcp) {
                DO_PROFILE_INC(writecache_direct_writes);
-               total_written = real_write_file(fsp, data, pos, n);
+               total_written = real_write_file(req, fsp, data, pos, n);
                return total_written;
        }
 
@@ -291,7 +308,7 @@ nonop=%u allocated=%u active=%u direct=%u perfect=%u readhits=%u\n",
 
        fsp->fh->pos = pos + n;
 
-       /* 
+       /*
         * If we have active cache and it isn't contiguous then we flush.
         * NOTE: There is a small problem with running out of disk ....
         */
@@ -610,7 +627,7 @@ len = %u\n",fsp->fh->fd, (double)pos, (unsigned int)n, (double)wcp->offset, (uns
                        if ( n <= wcp->alloc_size && n > wcp->data_size) {
                                cache_flush_needed = True;
                        } else {
-                               ssize_t ret = real_write_file(fsp, data, pos, n);
+                               ssize_t ret = real_write_file(NULL,fsp, data, pos, n);
 
                                /*
                                 * If the write overlaps the entire cache, then
@@ -657,7 +674,7 @@ n = %u, wcp->offset=%.0f, wcp->data_size=%u\n",
         */
 
        if (n > wcp->alloc_size ) {
-               ssize_t ret = real_write_file(fsp, data, pos, n);
+               ssize_t ret = real_write_file(NULL,fsp, data, pos, n);
                if (ret == -1) {
                        return -1;
                }
@@ -828,7 +845,7 @@ ssize_t flush_write_cache(files_struct *fsp, enum flush_reason_enum reason)
        }
 #endif
 
-       ret = real_write_file(fsp, wcp->data, wcp->offset, data_size);
+       ret = real_write_file(NULL, fsp, wcp->data, wcp->offset, data_size);
 
        /*
         * Ensure file size if kept up to date if write extends file.
index ecb7d9dce8ed700e45e1658682278d2c8ac7a2fc..0dd7fbb20e912ce8be7d45048a5dfe0ec8f0ede1 100644 (file)
@@ -189,7 +189,7 @@ void change_notify_reply(const uint8 *request_buf, uint32 max_param,
        smb_setlen((char *)tmp_request, smb_size);
        SCVAL(tmp_request, smb_wct, 0);
 
-       init_smb_request(req, tmp_request);
+       init_smb_request(req, tmp_request,0);
 
        send_nt_replies(req, NT_STATUS_OK, prs_data_p(&ps),
                        prs_offset(&ps), NULL, 0);
index ed1bf762e99c96b1851f4754edd3522d99223a99..1c8d8a6e7693b49f8fe4f669bd87a65a031fe64f 100644 (file)
@@ -25,7 +25,7 @@ extern int smb_echo_count;
 
 const int total_buffer_size = (BUFFER_SIZE + LARGE_WRITEX_HDR_SIZE + SAFETY_MARGIN);
 
-/* 
+/*
  * Size of data we can send to client. Set
  *  by the client for all protocols above CORE.
  *  Set by us for CORE protocol.
@@ -48,7 +48,9 @@ extern int max_send;
  * Initialize a struct smb_request from an inbuf
  */
 
-void init_smb_request(struct smb_request *req, const uint8 *inbuf)
+void init_smb_request(struct smb_request *req,
+                       const uint8 *inbuf,
+                       size_t unread_bytes)
 {
        size_t req_size = smb_len(inbuf) + 4;
        /* Ensure we have at least smb_size bytes. */
@@ -63,6 +65,8 @@ void init_smb_request(struct smb_request *req, const uint8 *inbuf)
        req->vuid   = SVAL(inbuf, smb_uid);
        req->tid    = SVAL(inbuf, smb_tid);
        req->wct    = CVAL(inbuf, smb_wct);
+       req->unread_bytes = unread_bytes;
+
        /* Ensure we have at least wct words and 2 bytes of bcc. */
        if (smb_size + req->wct*2 > req_size) {
                DEBUG(0,("init_smb_request: invalid wct number %u (size %u)\n",
@@ -231,6 +235,14 @@ bool push_deferred_smb_message(struct smb_request *req,
 {
        struct timeval end_time;
 
+       if (req->unread_bytes) {
+               DEBUG(0,("push_deferred_smb_message: logic error ! "
+                       "unread_bytes = %u\n",
+                       (unsigned int)req->unread_bytes ));
+               smb_panic("push_deferred_smb_message: "
+                       "logic error unread_bytes != 0" );
+       }
+
        end_time = timeval_sum(&request_time, &timeout);
 
        DEBUG(10,("push_deferred_open_smb_message: pushing message len %u mid %u "
@@ -382,8 +394,11 @@ static int select_on_fd(int fd, int maxfd, fd_set *fds)
 The timeout is in milliseconds
 ****************************************************************************/
 
-static bool receive_message_or_smb(TALLOC_CTX *mem_ctx, char **buffer,
-                                  size_t *buffer_len, int timeout)
+static bool receive_message_or_smb(TALLOC_CTX *mem_ctx,
+                               char **buffer,
+                               size_t *buffer_len,
+                               int timeout,
+                               size_t *p_unread)
 {
        fd_set r_fds, w_fds;
        int selrtn;
@@ -391,6 +406,7 @@ static bool receive_message_or_smb(TALLOC_CTX *mem_ctx, char **buffer,
        int maxfd = 0;
        ssize_t len;
 
+       *p_unread = 0;
        smb_read_error = 0;
 
  again:
@@ -565,7 +581,7 @@ static bool receive_message_or_smb(TALLOC_CTX *mem_ctx, char **buffer,
                goto again;
        }
 
-       len = receive_smb_talloc(mem_ctx, smbd_server_fd(), buffer, 0);
+       len = receive_smb_talloc(mem_ctx, smbd_server_fd(), buffer, 0, p_unread);
 
        if (len == -1) {
                return False;
@@ -1115,7 +1131,7 @@ static void switch_message(uint8 type, struct smb_request *req, int size)
  Construct a reply to the incoming packet.
 ****************************************************************************/
 
-static void construct_reply(char *inbuf, int size)
+static void construct_reply(char *inbuf, int size, size_t unread_bytes)
 {
        uint8 type = CVAL(inbuf,smb_com);
        struct smb_request *req;
@@ -1127,10 +1143,19 @@ static void construct_reply(char *inbuf, int size)
        if (!(req = talloc(talloc_tos(), struct smb_request))) {
                smb_panic("could not allocate smb_request");
        }
-       init_smb_request(req, (uint8 *)inbuf);
+       init_smb_request(req, (uint8 *)inbuf, unread_bytes);
 
        switch_message(type, req, size);
 
+       if (req->unread_bytes) {
+               /* writeX failed. drain socket. */
+               if (drain_socket(smbd_server_fd(), req->unread_bytes) !=
+                               req->unread_bytes) {
+                       smb_panic("failed to drain pending bytes");
+               }
+               req->unread_bytes = 0;
+       }
+
        if (req->outbuf == NULL) {
                return;
        }
@@ -1152,7 +1177,7 @@ static void construct_reply(char *inbuf, int size)
  Process an smb from the client
 ****************************************************************************/
 
-static void process_smb(char *inbuf, size_t nread)
+static void process_smb(char *inbuf, size_t nread, size_t unread_bytes)
 {
        static int trans_num;
        int msg_type = CVAL(inbuf,0);
@@ -1176,7 +1201,9 @@ static void process_smb(char *inbuf, size_t nread)
 
        DEBUG( 6, ( "got message type 0x%x of len 0x%x\n", msg_type,
                    smb_len(inbuf) ) );
-       DEBUG( 3, ( "Transaction %d of length %d\n", trans_num, (int)nread ) );
+       DEBUG( 3, ( "Transaction %d of length %d (%u toread)\n", trans_num,
+                               (int)nread,
+                               (unsigned int)unread_bytes ));
 
        if (msg_type != 0) {
                /*
@@ -1188,8 +1215,8 @@ static void process_smb(char *inbuf, size_t nread)
 
        show_msg(inbuf);
 
-       construct_reply(inbuf,nread);
-      
+       construct_reply(inbuf,nread,unread_bytes);
+
        trans_num++;
 }
 
@@ -1348,7 +1375,7 @@ void chain_reply(struct smb_request *req)
        if (!(req2 = talloc(talloc_tos(), struct smb_request))) {
                smb_panic("could not allocate smb_request");
        }
-       init_smb_request(req2, (uint8 *)inbuf2);
+       init_smb_request(req2, (uint8 *)inbuf2,0);
 
        /* process the request */
        switch_message(smb_com2, req2, new_size);
@@ -1625,6 +1652,7 @@ void smbd_process(void)
 {
        time_t last_timeout_processing_time = time(NULL);
        unsigned int num_smbs = 0;
+       size_t unread_bytes = 0;
 
        max_recv = MIN(lp_maxxmit(),BUFFER_SIZE);
 
@@ -1635,8 +1663,8 @@ void smbd_process(void)
                size_t inbuf_len;
                TALLOC_CTX *frame = talloc_stackframe();
 
-               errno = 0;      
-               
+               errno = 0;
+
                /* Did someone ask for immediate checks on things like blocking locks ? */
                if (select_timeout == 0) {
                        if(!timeout_processing(&select_timeout,
@@ -1648,7 +1676,7 @@ void smbd_process(void)
                run_events(smbd_event_context(), 0, NULL, NULL);
 
                while (!receive_message_or_smb(NULL, &inbuf, &inbuf_len,
-                                              select_timeout)) {
+                                              select_timeout, &unread_bytes)) {
                        if(!timeout_processing(&select_timeout,
                                               &last_timeout_processing_time))
                                return;
@@ -1664,10 +1692,10 @@ void smbd_process(void)
                 * faster than the select timeout, thus starving out the
                 * essential processing (change notify, blocking locks) that
                 * the timeout code does. JRA.
-                */ 
+                */
                num_echos = smb_echo_count;
 
-               process_smb(inbuf, inbuf_len);
+               process_smb(inbuf, inbuf_len, unread_bytes);
 
                TALLOC_FREE(inbuf);
 
index 38ce797eeb26bf80a2eff9ee562c54f8f3854ce2..4c1ed56d4ff554cf1654c9a35fce65b2fa9edd43 100644 (file)
@@ -3494,7 +3494,7 @@ void reply_writebraw(connection_struct *conn, struct smb_request *req)
        }
 
        if (numtowrite>0) {
-               nwritten = write_file(fsp,data,startpos,numtowrite);
+               nwritten = write_file(req,fsp,data,startpos,numtowrite);
        }
 
        DEBUG(3,("reply_writebraw: initial write fnum=%d start=%.0f num=%d "
@@ -3572,7 +3572,7 @@ void reply_writebraw(connection_struct *conn, struct smb_request *req)
                        exit_server_cleanly("secondary writebraw failed");
                }
 
-               nwritten = write_file(fsp,buf+4,startpos+nwritten,numtowrite);
+               nwritten = write_file(req,fsp,buf+4,startpos+nwritten,numtowrite);
                if (nwritten == -1) {
                        TALLOC_FREE(buf);
                        reply_unixerror(req, ERRHRD, ERRdiskfull);
@@ -3686,7 +3686,7 @@ void reply_writeunlock(connection_struct *conn, struct smb_request *req)
        if(numtowrite == 0) {
                nwritten = 0;
        } else {
-               nwritten = write_file(fsp,data,startpos,numtowrite);
+               nwritten = write_file(req,fsp,data,startpos,numtowrite);
        }
   
        status = sync_file(conn, fsp, False /* write through */);
@@ -3808,7 +3808,7 @@ void reply_write(connection_struct *conn, struct smb_request *req)
                        return;
                }
        } else
-               nwritten = write_file(fsp,data,startpos,numtowrite);
+               nwritten = write_file(req,fsp,data,startpos,numtowrite);
   
        status = sync_file(conn, fsp, False);
        if (!NT_STATUS_IS_OK(status)) {
@@ -3840,6 +3840,64 @@ void reply_write(connection_struct *conn, struct smb_request *req)
        return;
 }
 
+/****************************************************************************
+ Ensure a buffer is a valid writeX for recvfile purposes.
+****************************************************************************/
+
+#define STANDARD_WRITE_AND_X_HEADER_SIZE (smb_size - 4 + /* basic header */ \
+                                               (2*14) + /* word count (including bcc) */ \
+                                               1 /* pad byte */)
+
+bool is_valid_writeX_buffer(char *inbuf)
+{
+       size_t numtowrite;
+       connection_struct *conn = NULL;
+       unsigned int doff = 0;
+       size_t len = smb_len(inbuf);
+
+       if (CVAL(inbuf,smb_com) != SMBwriteX ||
+                       CVAL(inbuf,smb_vwv0) != 0xFF ||
+                       CVAL(inbuf,smb_wct) != 14) {
+               return false;
+       }
+       conn = conn_find(SVAL(inbuf, smb_tid));
+       if (conn == NULL) {
+               return false;
+       }
+       if (IS_IPC(conn)) {
+               return false;
+       }
+       numtowrite = SVAL(inbuf,smb_vwv10);
+       numtowrite |= ((((size_t)SVAL(inbuf,smb_vwv9)) & 1 )<<16);
+       if (numtowrite == 0) {
+               return false;
+       }
+       /* Ensure the sizes match up. */
+       doff = SVAL(inbuf,smb_vwv11);
+
+       if (doff < STANDARD_WRITE_AND_X_HEADER_SIZE) {
+               /* no pad byte...old smbclient :-( */
+               return false;
+       }
+
+       if (len - doff != numtowrite) {
+               DEBUG(10,("is_valid_writeX_buffer: doff mismatch "
+                       "len = %u, doff = %u, numtowrite = %u\n",
+                       (unsigned int)len,
+                       (unsigned int)doff,
+                       (unsigned int)numtowrite ));
+               return false;
+       }
+
+       DEBUG(10,("is_valid_writeX_buffer: true "
+               "len = %u, doff = %u, numtowrite = %u\n",
+               (unsigned int)len,
+               (unsigned int)doff,
+               (unsigned int)numtowrite ));
+
+       return true;
+}
+
 /****************************************************************************
  Reply to a write and X.
 ****************************************************************************/
@@ -3875,10 +3933,18 @@ void reply_write_and_X(connection_struct *conn, struct smb_request *req)
                numtowrite |= ((((size_t)SVAL(req->inbuf,smb_vwv9)) & 1 )<<16);
        }
 
-       if(smb_doff > smblen || (smb_doff + numtowrite > smblen)) {
-               reply_doserror(req, ERRDOS, ERRbadmem);
-               END_PROFILE(SMBwriteX);
-               return;
+       if (req->unread_bytes) {
+               if (numtowrite != req->unread_bytes) {
+                       reply_doserror(req, ERRDOS, ERRbadmem);
+                       END_PROFILE(SMBwriteX);
+                       return;
+               }
+       } else {
+               if (smb_doff > smblen || smb_doff + numtowrite > smblen) {
+                       reply_doserror(req, ERRDOS, ERRbadmem);
+                       END_PROFILE(SMBwriteX);
+                       return;
+               }
        }
 
        /* If it's an IPC, pass off the pipe handler. */
@@ -3947,15 +4013,16 @@ void reply_write_and_X(connection_struct *conn, struct smb_request *req)
                nwritten = 0;
        } else {
 
-               if (schedule_aio_write_and_X(conn, req, fsp, data, startpos,
-                                            numtowrite)) {
+               if (req->unread_bytes == 0 &&
+                               schedule_aio_write_and_X(conn, req, fsp, data,
+                                                       startpos, numtowrite)) {
                        END_PROFILE(SMBwriteX);
                        return;
                }
 
-               nwritten = write_file(fsp,data,startpos,numtowrite);
+               nwritten = write_file(req,fsp,data,startpos,numtowrite);
        }
-  
+
        if(((nwritten == 0) && (numtowrite != 0))||(nwritten < 0)) {
                reply_unixerror(req, ERRHRD, ERRdiskfull);
                END_PROFILE(SMBwriteX);
@@ -4264,7 +4331,7 @@ void reply_writeclose(connection_struct *conn, struct smb_request *req)
                return;
        }
   
-       nwritten = write_file(fsp,data,startpos,numtowrite);
+       nwritten = write_file(req,fsp,data,startpos,numtowrite);
 
        set_filetime(conn, fsp->fsp_name, mtime);
   
@@ -4726,7 +4793,7 @@ void reply_printwrite(connection_struct *conn, struct smb_request *req)
 
        data = smb_buf(req->inbuf) + 3;
 
-       if (write_file(fsp,data,-1,numtowrite) != numtowrite) {
+       if (write_file(req,fsp,data,-1,numtowrite) != numtowrite) {
                reply_unixerror(req, ERRHRD, ERRdiskfull);
                END_PROFILE(SMBsplwr);
                return;
index b9ab7ef7ac9562476ac64dcb6ba31202a495f421..25e2d2cb5e5706c78d6dedcc3f29a2d83a4e9f34 100644 (file)
@@ -1,20 +1,22 @@
-/* 
+/*
    Unix SMB/CIFS implementation.
    Main SMB server routines
    Copyright (C) Andrew Tridgell               1992-1998
    Copyright (C) Martin Pool                   2002
    Copyright (C) Jelmer Vernooij               2002-2003
-   
+   Copyright (C) Volker Lendecke               1993-2007
+   Copyright (C) Jeremy Allison                        1993-2007
+
    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 3 of the License, or
    (at your option) any later version.
-   
+
    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.
-   
+
    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
@@ -37,6 +39,8 @@ extern SIG_ATOMIC_T got_sig_term;
 extern SIG_ATOMIC_T reload_after_sighup;
 static SIG_ATOMIC_T got_sig_cld;
 
+extern int smb_read_error;
+
 #ifdef WITH_DFS
 extern int dcelogin_atmost_once;
 #endif /* WITH_DFS */
@@ -60,6 +64,293 @@ static void smbd_set_server_fd(int fd)
        client_setfd(fd);
 }
 
+/* Socket functions for smbd packet processing. */
+
+static bool valid_packet_size(len)
+{
+       /*
+        * A WRITEX with CAP_LARGE_WRITEX can be 64k worth of data plus 65 bytes
+        * of header. Don't print the error if this fits.... JRA.
+        */
+
+       if (len > (BUFFER_SIZE + LARGE_WRITEX_HDR_SIZE)) {
+               DEBUG(0,("Invalid packet length! (%lu bytes).\n",
+                                       (unsigned long)len));
+               if (len > BUFFER_SIZE + (SAFETY_MARGIN/2)) {
+
+                       /*
+                        * Correct fix. smb_read_error may have already been
+                        * set. Only set it here if not already set. Global
+                        * variables still suck :-). JRA.
+                        */
+
+                       if (smb_read_error == 0)
+                               smb_read_error = READ_ERROR;
+                       return false;
+               }
+       }
+       return true;
+}
+
+static ssize_t read_packet_remainder(int fd,
+                                       char *buffer,
+                                       unsigned int timeout,
+                                       ssize_t len)
+{
+       ssize_t ret;
+
+       if(len <= 0) {
+               return len;
+       }
+
+       if (timeout > 0) {
+               ret = read_socket_with_timeout(fd,
+                                               buffer,
+                                               len,
+                                               len,
+                                               timeout);
+       } else {
+               ret = read_data(fd, buffer, len);
+       }
+
+       if (ret != len) {
+               if (smb_read_error == 0) {
+                       smb_read_error = READ_ERROR;
+               }
+               return -1;
+       }
+
+       return len;
+}
+
+/****************************************************************************
+ Attempt a zerocopy writeX read. We know here that len > smb_size-4
+****************************************************************************/
+
+/*
+ * Unfortunately, earlier versions of smbclient/libsmbclient
+ * don't send this "standard" writeX header. I've fixed this
+ * for 3.2 but we'll use the old method with earlier versions.
+ * Windows and CIFSFS at least use this standard size. Not
+ * sure about MacOSX.
+ */
+
+#define STANDARD_WRITE_AND_X_HEADER_SIZE (smb_size - 4 + /* basic header */ \
+                               (2*14) + /* word count (including bcc) */ \
+                               1 /* pad byte */)
+
+ssize_t receive_smb_raw_talloc_partial_read(TALLOC_CTX *mem_ctx,
+                                       const char lenbuf[4],
+                                       int fd,
+                                       char **buffer,
+                                       unsigned int timeout,
+                                       size_t *p_unread)
+{
+       /* Size of a WRITEX call (+4 byte len). */
+       char writeX_header[4 + STANDARD_WRITE_AND_X_HEADER_SIZE];
+       ssize_t len = smb_len(lenbuf);
+       ssize_t toread;
+       ssize_t ret;
+
+       memcpy(writeX_header, lenbuf, sizeof(lenbuf));
+
+       if (timeout > 0) {
+               ret = read_socket_with_timeout(fd,
+                                       writeX_header + 4,
+                                       STANDARD_WRITE_AND_X_HEADER_SIZE,
+                                       STANDARD_WRITE_AND_X_HEADER_SIZE,
+                                       timeout);
+       } else {
+               ret = read_data(fd,
+                               writeX_header+4,
+                               STANDARD_WRITE_AND_X_HEADER_SIZE);
+       }
+
+       if (ret != STANDARD_WRITE_AND_X_HEADER_SIZE) {
+               if (smb_read_error == 0) {
+                       smb_read_error = READ_ERROR;
+               }
+               return -1;
+       }
+
+       /*
+        * Ok - now try and see if this is a possible
+        * valid writeX call.
+        */
+
+       if (is_valid_writeX_buffer(writeX_header)) {
+               /*
+                * If the data offset is beyond what
+                * we've read, drain the extra bytes.
+                */
+               uint16_t doff = SVAL(writeX_header,smb_vwv11);
+               ssize_t newlen;
+
+               if (doff > STANDARD_WRITE_AND_X_HEADER_SIZE) {
+                       size_t drain = doff - STANDARD_WRITE_AND_X_HEADER_SIZE;
+                       if (drain_socket(smbd_server_fd(), drain) != drain) {
+                               smb_panic("receive_smb_raw_talloc_partial_read:"
+                                       " failed to drain pending bytes");
+                       }
+               } else {
+                       doff = STANDARD_WRITE_AND_X_HEADER_SIZE;
+               }
+
+               /* Spoof down the length and null out the bcc. */
+               set_message_bcc(writeX_header, 0);
+               newlen = smb_len(writeX_header);
+
+               /* Copy the header we've written. */
+
+               *buffer = TALLOC_MEMDUP(mem_ctx,
+                               writeX_header,
+                               sizeof(writeX_header));
+
+               if (*buffer == NULL) {
+                       DEBUG(0, ("Could not allocate inbuf of length %d\n",
+                                 (int)sizeof(writeX_header)));
+                       if (smb_read_error == 0)
+                               smb_read_error = READ_ERROR;
+                       return -1;
+               }
+
+               /* Work out the remaining bytes. */
+               *p_unread = len - STANDARD_WRITE_AND_X_HEADER_SIZE;
+
+               return newlen + 4;
+       }
+
+       if (!valid_packet_size(len)) {
+               return -1;
+       }
+
+       /*
+        * Not a valid writeX call. Just do the standard
+        * talloc and return.
+        */
+
+       *buffer = TALLOC_ARRAY(mem_ctx, char, len+4);
+
+       if (*buffer == NULL) {
+               DEBUG(0, ("Could not allocate inbuf of length %d\n",
+                         (int)len+4));
+               if (smb_read_error == 0)
+                       smb_read_error = READ_ERROR;
+               return -1;
+       }
+
+       /* Copy in what we already read. */
+       memcpy(*buffer,
+               writeX_header,
+               4 + STANDARD_WRITE_AND_X_HEADER_SIZE);
+       toread = len - STANDARD_WRITE_AND_X_HEADER_SIZE;
+
+       if(toread > 0) {
+               ret = read_packet_remainder(fd,
+                       (*buffer) + 4 + STANDARD_WRITE_AND_X_HEADER_SIZE,
+                                       timeout,
+                                       toread);
+               if (ret != toread) {
+                       return -1;
+               }
+       }
+
+       return len + 4;
+}
+
+static ssize_t receive_smb_raw_talloc(TALLOC_CTX *mem_ctx,
+                                       int fd,
+                                       char **buffer,
+                                       unsigned int timeout,
+                                       size_t *p_unread)
+{
+       char lenbuf[4];
+       ssize_t len,ret;
+       int min_recv_size = lp_min_receive_file_size();
+
+       smb_read_error = 0;
+       *p_unread = 0;
+
+       len = read_smb_length_return_keepalive(fd, lenbuf, timeout);
+       if (len < 0) {
+               DEBUG(10,("receive_smb_raw: length < 0!\n"));
+
+               /*
+                * Correct fix. smb_read_error may have already been
+                * set. Only set it here if not already set. Global
+                * variables still suck :-). JRA.
+                */
+
+               if (smb_read_error == 0)
+                       smb_read_error = READ_ERROR;
+               return -1;
+       }
+
+       if (CVAL(lenbuf,0) != SMBkeepalive &&
+                       min_recv_size &&
+                       len > min_recv_size &&
+                       !srv_is_signing_active()) {
+
+               return receive_smb_raw_talloc_partial_read(mem_ctx,
+                                                       lenbuf,
+                                                       fd,
+                                                       buffer,
+                                                       timeout,
+                                                       p_unread);
+       }
+
+       if (!valid_packet_size(len)) {
+               return -1;
+       }
+
+       /*
+        * The +4 here can't wrap, we've checked the length above already.
+        */
+
+       *buffer = TALLOC_ARRAY(mem_ctx, char, len+4);
+
+       if (*buffer == NULL) {
+               DEBUG(0, ("Could not allocate inbuf of length %d\n",
+                         (int)len+4));
+               if (smb_read_error == 0)
+                       smb_read_error = READ_ERROR;
+               return -1;
+       }
+
+       memcpy(*buffer, lenbuf, sizeof(lenbuf));
+
+       ret = read_packet_remainder(fd, (*buffer)+4, timeout, len);
+       if (ret != len) {
+               return -1;
+       }
+
+       return len + 4;
+}
+
+ssize_t receive_smb_talloc(TALLOC_CTX *mem_ctx, int fd, char **buffer,
+                          unsigned int timeout, size_t *p_unread)
+{
+       ssize_t len;
+
+       len = receive_smb_raw_talloc(mem_ctx, fd, buffer, timeout, p_unread);
+
+       if (len < 0) {
+               return -1;
+       }
+
+       /* Check the incoming SMB signature. */
+       if (!srv_check_sign_mac(*buffer, true)) {
+               DEBUG(0, ("receive_smb: SMB Signature verification failed on "
+                         "incoming packet!\n"));
+               if (smb_read_error == 0) {
+                       smb_read_error = READ_BAD_SIG;
+               }
+               return -1;
+       }
+
+       return len;
+}
+
 struct event_context *smbd_event_context(void)
 {
        static struct event_context *ctx;
index e862710b6c4ae9a5c1f1e576d3d7d48568e20ee5..c1c1939153db957bf32f81a0823f2e2ab3d7e3a5 100644 (file)
@@ -418,11 +418,24 @@ ssize_t vfs_pread_data(files_struct *fsp, char *buf,
  Write data to a fd on the vfs.
 ****************************************************************************/
 
-ssize_t vfs_write_data(files_struct *fsp,const char *buffer,size_t N)
+ssize_t vfs_write_data(struct smb_request *req,
+                       files_struct *fsp,
+                       const char *buffer,
+                       size_t N)
 {
        size_t total=0;
        ssize_t ret;
 
+       if (req && req->unread_bytes) {
+               SMB_ASSERT(req->unread_bytes == N);
+               req->unread_bytes = 0;
+               return SMB_VFS_RECVFILE(smbd_server_fd(),
+                                       fsp,
+                                       fsp->fh->fd,
+                                       (SMB_OFF_T)-1,
+                                       N);
+       }
+
        while (total < N) {
                ret = SMB_VFS_WRITE(fsp,fsp->fh->fd,buffer + total,N - total);
 
@@ -436,12 +449,25 @@ ssize_t vfs_write_data(files_struct *fsp,const char *buffer,size_t N)
        return (ssize_t)total;
 }
 
-ssize_t vfs_pwrite_data(files_struct *fsp,const char *buffer,
-                size_t N, SMB_OFF_T offset)
+ssize_t vfs_pwrite_data(struct smb_request *req,
+                       files_struct *fsp,
+                       const char *buffer,
+                       size_t N,
+                       SMB_OFF_T offset)
 {
        size_t total=0;
        ssize_t ret;
 
+       if (req && req->unread_bytes) {
+               SMB_ASSERT(req->unread_bytes == N);
+               req->unread_bytes = 0;
+               return SMB_VFS_RECVFILE(smbd_server_fd(),
+                                       fsp,
+                                       fsp->fh->fd,
+                                       offset,
+                                       N);
+       }
+
        while (total < N) {
                ret = SMB_VFS_PWRITE(fsp, fsp->fh->fd, buffer + total,
                                 N - total, offset + total);