Fix bug #6009 - Setting "min receivefile size = 1" breaks writes.
[ira/wip.git] / source3 / smbd / process.c
index 71e38634b70ace96f0212d74f9bc56b242d86c9b..67e6067b260667e3c64e615cb184799396a14772 100644 (file)
@@ -20,8 +20,6 @@
 
 #include "includes.h"
 
-extern int smb_echo_count;
-
 /*
  * Size of data we can send to client. Set
  *  by the client for all protocols above CORE.
@@ -39,6 +37,9 @@ SIG_ATOMIC_T got_sig_term = 0;
 extern bool global_machine_password_needs_changing;
 extern int max_send;
 
+static void construct_reply_common(struct smb_request *req, const char *inbuf,
+                                  char *outbuf);
+
 /* Accessor function for smb_read_error for smbd functions. */
 
 /****************************************************************************
@@ -105,7 +106,11 @@ static bool valid_smb_header(const uint8_t *inbuf)
        if (is_encrypted_packet(inbuf)) {
                return true;
        }
-       return (strncmp(smb_base(inbuf),"\377SMB",4) == 0);
+       /*
+        * This used to be (strncmp(smb_base(inbuf),"\377SMB",4) == 0)
+        * but it just looks weird to call strncmp for this one.
+        */
+       return (IVAL(smb_base(inbuf), 0) == 0x424D53FF);
 }
 
 /* Socket functions for smbd packet processing. */
@@ -164,7 +169,7 @@ static NTSTATUS receive_smb_raw_talloc_partial_read(TALLOC_CTX *mem_ctx,
        ssize_t toread;
        NTSTATUS status;
 
-       memcpy(writeX_header, lenbuf, sizeof(lenbuf));
+       memcpy(writeX_header, lenbuf, 4);
 
        status = read_socket_with_timeout(
                fd, writeX_header + 4,
@@ -279,7 +284,7 @@ static NTSTATUS receive_smb_raw_talloc(TALLOC_CTX *mem_ctx, int fd,
 
        if (CVAL(lenbuf,0) == 0 &&
                        min_recv_size &&
-                       smb_len_large(lenbuf) > min_recv_size && /* Could be a UNIX large writeX. */
+                       smb_len_large(lenbuf) > (min_recv_size + STANDARD_WRITE_AND_X_HEADER_SIZE) && /* Could be a UNIX large writeX. */
                        !srv_is_signing_active()) {
 
                return receive_smb_raw_talloc_partial_read(
@@ -367,15 +372,20 @@ void init_smb_request(struct smb_request *req,
                        (unsigned int)req_size ));
                exit_server_cleanly("Invalid SMB request");
        }
+       req->cmd    = CVAL(inbuf, smb_com);
        req->flags2 = SVAL(inbuf, smb_flg2);
        req->smbpid = SVAL(inbuf, smb_pid);
        req->mid    = SVAL(inbuf, smb_mid);
        req->vuid   = SVAL(inbuf, smb_uid);
        req->tid    = SVAL(inbuf, smb_tid);
        req->wct    = CVAL(inbuf, smb_wct);
+       req->vwv    = (uint16_t *)(inbuf+smb_vwv);
+       req->buflen = smb_buflen(inbuf);
+       req->buf    = (const uint8_t *)smb_buf(inbuf);
        req->unread_bytes = unread_bytes;
        req->encrypted = encrypted;
        req->conn = conn_find(req->tid);
+       req->chain_fsp = NULL;
 
        /* Ensure we have at least wct words and 2 bytes of bcc. */
        if (smb_size + req->wct*2 > req_size) {
@@ -385,15 +395,14 @@ void init_smb_request(struct smb_request *req,
                exit_server_cleanly("Invalid SMB request");
        }
        /* Ensure bcc is correct. */
-       if (((uint8 *)smb_buf(inbuf)) + smb_buflen(inbuf) > inbuf + req_size) {
+       if (((uint8 *)smb_buf(inbuf)) + req->buflen > inbuf + req_size) {
                DEBUG(0,("init_smb_request: invalid bcc number %u "
                        "(wct = %u, size %u)\n",
-                       (unsigned int)smb_buflen(inbuf),
+                       (unsigned int)req->buflen,
                        (unsigned int)req->wct,
                        (unsigned int)req_size));
                exit_server_cleanly("Invalid SMB request");
        }
-       req->inbuf  = inbuf;
        req->outbuf = NULL;
 }
 
@@ -706,7 +715,7 @@ The timeout is in milliseconds
 ****************************************************************************/
 
 static NTSTATUS receive_message_or_smb(TALLOC_CTX *mem_ctx, char **buffer,
-                                      size_t *buffer_len, int timeout,
+                                      size_t *buffer_len,
                                       size_t *p_unread, bool *p_encrypted)
 {
        fd_set r_fds, w_fds;
@@ -718,15 +727,8 @@ static NTSTATUS receive_message_or_smb(TALLOC_CTX *mem_ctx, char **buffer,
 
        *p_unread = 0;
 
- again:
-
-       if (timeout >= 0) {
-               to.tv_sec = timeout / 1000;
-               to.tv_usec = (timeout % 1000) * 1000;
-       } else {
-               to.tv_sec = SMBD_SELECT_TIMEOUT;
-               to.tv_usec = 0;
-       }
+       to.tv_sec = SMBD_SELECT_TIMEOUT;
+       to.tv_usec = 0;
 
        /*
         * Note that this call must be before processing any SMB
@@ -747,7 +749,7 @@ static NTSTATUS receive_message_or_smb(TALLOC_CTX *mem_ctx, char **buffer,
                        pop_message = True;
                } else {
                        struct timeval tv;
-                       SMB_BIG_INT tdif;
+                       int64_t tdif;
 
                        GetTimeOfDay(&tv);
                        tdif = usec_time_diff(&msg->end_time, &tv);
@@ -806,7 +808,7 @@ static NTSTATUS receive_message_or_smb(TALLOC_CTX *mem_ctx, char **buffer,
                 * the state of the flag in fds for the server file descriptor is
                 * indeterminate - we may have done I/O on it in the oplock processing. JRA.
                 */
-               goto again;
+               return NT_STATUS_RETRY;
        }
 
        /*
@@ -825,7 +827,7 @@ static NTSTATUS receive_message_or_smb(TALLOC_CTX *mem_ctx, char **buffer,
        if (timeval_is_zero(&to)) {
                /* Process a timed event now... */
                if (run_events(smbd_event_context(), 0, NULL, NULL)) {
-                       goto again;
+                       return NT_STATUS_RETRY;
                }
        }
        
@@ -844,7 +846,7 @@ static NTSTATUS receive_message_or_smb(TALLOC_CTX *mem_ctx, char **buffer,
        }
 
        if (run_events(smbd_event_context(), selrtn, &r_fds, &w_fds)) {
-               goto again;
+               return NT_STATUS_RETRY;
        }
 
        /* if we get EINTR then maybe we have received an oplock
@@ -858,7 +860,7 @@ static NTSTATUS receive_message_or_smb(TALLOC_CTX *mem_ctx, char **buffer,
                 * the state of the flag in fds for the server file descriptor is
                 * indeterminate - we may have done I/O on it in the oplock processing. JRA.
                 */
-               goto again;
+               return NT_STATUS_RETRY;
        }
 
        /* Check if error */
@@ -869,7 +871,7 @@ static NTSTATUS receive_message_or_smb(TALLOC_CTX *mem_ctx, char **buffer,
 
        /* Did we timeout ? */
        if (selrtn == 0) {
-               return NT_STATUS_IO_TIMEOUT;
+               return NT_STATUS_RETRY;
        }
 
        /*
@@ -885,7 +887,7 @@ static NTSTATUS receive_message_or_smb(TALLOC_CTX *mem_ctx, char **buffer,
                 * the state of the flag in fds for the server file descriptor is
                 * indeterminate - we may have done I/O on it in the oplock processing. JRA.
                 */
-               goto again;
+               return NT_STATUS_RETRY;
        }
 
        /*
@@ -978,7 +980,7 @@ force write permissions on print services.
 */
 static const struct smb_message_struct {
        const char *name;
-       void (*fn_new)(struct smb_request *req);
+       void (*fn)(struct smb_request *req);
        int flags;
 } smb_messages[256] = {
 
@@ -1245,7 +1247,9 @@ static const struct smb_message_struct {
  allocate and initialize a reply packet
 ********************************************************************/
 
-void reply_outbuf(struct smb_request *req, uint8 num_words, uint32 num_bytes)
+static bool create_outbuf(TALLOC_CTX *mem_ctx, struct smb_request *req,
+                         const char *inbuf, char **outbuf, uint8_t num_words,
+                         uint32_t num_bytes)
 {
        /*
          * Protect against integer wrap
@@ -1260,23 +1264,33 @@ void reply_outbuf(struct smb_request *req, uint8 num_words, uint32 num_bytes)
                smb_panic(msg);
        }
 
-       if (!(req->outbuf = TALLOC_ARRAY(
-                     req, uint8,
-                     smb_size + num_words*2 + num_bytes))) {
-               smb_panic("could not allocate output buffer\n");
+       *outbuf = TALLOC_ARRAY(mem_ctx, char,
+                              smb_size + num_words*2 + num_bytes);
+       if (*outbuf == NULL) {
+               return false;
        }
 
-       construct_reply_common((char *)req->inbuf, (char *)req->outbuf);
-       srv_set_message((char *)req->outbuf, num_words, num_bytes, false);
+       construct_reply_common(req, inbuf, *outbuf);
+       srv_set_message(*outbuf, num_words, num_bytes, false);
        /*
         * Zero out the word area, the caller has to take care of the bcc area
         * himself
         */
        if (num_words != 0) {
-               memset(req->outbuf + smb_vwv0, 0, num_words*2);
+               memset(*outbuf + smb_vwv0, 0, num_words*2);
        }
 
-       return;
+       return true;
+}
+
+void reply_outbuf(struct smb_request *req, uint8 num_words, uint32 num_bytes)
+{
+       char *outbuf;
+       if (!create_outbuf(req, req, (char *)req->inbuf, &outbuf, num_words,
+                          num_bytes)) {
+               smb_panic("could not allocate output buffer\n");
+       }
+       req->outbuf = (uint8_t *)outbuf;
 }
 
 
@@ -1343,7 +1357,7 @@ static connection_struct *switch_message(uint8 type, struct smb_request *req, in
                exit_server_cleanly("Non-SMB packet");
        }
 
-       if (smb_messages[type].fn_new == NULL) {
+       if (smb_messages[type].fn == NULL) {
                DEBUG(0,("Unknown message type %d!\n",type));
                smb_dump("Unknown", 1, (char *)req->inbuf, size);
                reply_unknown_new(req, type);
@@ -1411,6 +1425,7 @@ static connection_struct *switch_message(uint8 type, struct smb_request *req, in
 
                if (!change_to_user(conn,session_tag)) {
                        reply_nterror(req, NT_STATUS_DOS(ERRSRV, ERRbaduid));
+                       remove_deferred_open_smb_message(req->mid);
                        return conn;
                }
 
@@ -1439,8 +1454,7 @@ static connection_struct *switch_message(uint8 type, struct smb_request *req, in
                        /* encrypted required from now on. */
                        conn->encrypt_level = Required;
                } else if (ENCRYPTION_REQUIRED(conn)) {
-                       uint8 com = CVAL(req->inbuf,smb_com);
-                       if (com != SMBtrans2 && com != SMBtranss2) {
+                       if (req->cmd != SMBtrans2 && req->cmd != SMBtranss2) {
                                exit_server_cleanly("encryption required "
                                        "on connection");
                                return conn;
@@ -1465,7 +1479,7 @@ static connection_struct *switch_message(uint8 type, struct smb_request *req, in
                return conn;
        }
 
-       smb_messages[type].fn_new(req);
+       smb_messages[type].fn(req);
        return req->conn;
 }
 
@@ -1475,20 +1489,18 @@ static connection_struct *switch_message(uint8 type, struct smb_request *req, in
 
 static void construct_reply(char *inbuf, int size, size_t unread_bytes, bool encrypted)
 {
-       uint8 type = CVAL(inbuf,smb_com);
        connection_struct *conn;
        struct smb_request *req;
 
        chain_size = 0;
-       file_chain_reset();
-       reset_chain_p();
 
        if (!(req = talloc(talloc_tos(), struct smb_request))) {
                smb_panic("could not allocate smb_request");
        }
        init_smb_request(req, (uint8 *)inbuf, unread_bytes, encrypted);
+       req->inbuf  = (uint8_t *)talloc_move(req, &inbuf);
 
-       conn = switch_message(type, req, size);
+       conn = switch_message(req->cmd, req, size);
 
        if (req->unread_bytes) {
                /* writeX failed. drain socket. */
@@ -1529,25 +1541,6 @@ static void process_smb(char *inbuf, size_t nread, size_t unread_bytes, bool enc
 
        DO_PROFILE_INC(smb_count);
 
-       if (trans_num == 0) {
-               char addr[INET6_ADDRSTRLEN];
-
-               /* on the first packet, check the global hosts allow/ hosts
-               deny parameters before doing any parsing of the packet
-               passed to us by the client.  This prevents attacks on our
-               parsing code from hosts not in the hosts allow list */
-
-               if (!check_access(smbd_server_fd(), lp_hostsallow(-1),
-                                 lp_hostsdeny(-1))) {
-                       /* send a negative session response "not listening on calling name" */
-                       static unsigned char buf[5] = {0x83, 0, 0, 1, 0x81};
-                       DEBUG( 1, ( "Connection denied from %s\n",
-                               client_addr(get_client_fd(),addr,sizeof(addr)) ) );
-                       (void)srv_send_smb(smbd_server_fd(),(char *)buf,false);
-                       exit_server_cleanly("connection denied");
-               }
-       }
-
        DEBUG( 6, ( "got message type 0x%x of len 0x%x\n", msg_type,
                    smb_len(inbuf) ) );
        DEBUG( 3, ( "Transaction %d of length %d (%u toread)\n", trans_num,
@@ -1599,11 +1592,12 @@ void remove_from_common_flags2(uint32 v)
        common_flags2 &= ~v;
 }
 
-void construct_reply_common(const char *inbuf, char *outbuf)
+static void construct_reply_common(struct smb_request *req, const char *inbuf,
+                                  char *outbuf)
 {
        srv_set_message(outbuf,0,0,false);
        
-       SCVAL(outbuf,smb_com,CVAL(inbuf,smb_com));
+       SCVAL(outbuf, smb_com, req->cmd);
        SIVAL(outbuf,smb_rcls,0);
        SCVAL(outbuf,smb_flg, FLAG_REPLY | (CVAL(inbuf,smb_flg) & FLAG_CASELESS_PATHNAMES)); 
        SSVAL(outbuf,smb_flg2,
@@ -1617,6 +1611,11 @@ void construct_reply_common(const char *inbuf, char *outbuf)
        SSVAL(outbuf,smb_mid,SVAL(inbuf,smb_mid));
 }
 
+void construct_reply_common_req(struct smb_request *req, char *outbuf)
+{
+       construct_reply_common(req, (char *)req->inbuf, outbuf);
+}
+
 /****************************************************************************
  Construct a chained reply and add it to the already made reply
 ****************************************************************************/
@@ -1642,6 +1641,7 @@ void chain_reply(struct smb_request *req)
        char *outbuf = (char *)req->outbuf;
        size_t outsize = smb_len(outbuf) + 4;
        size_t outsize_padded;
+       size_t padding;
        size_t ofs, to_move;
 
        struct smb_request *req2;
@@ -1680,12 +1680,13 @@ void chain_reply(struct smb_request *req)
         */
 
        outsize_padded = (outsize + 3) & ~3;
+       padding = outsize_padded - outsize;
 
        /*
         * remember how much the caller added to the chain, only counting
         * stuff after the parameter words
         */
-       chain_size += outsize_padded - smb_wct;
+       chain_size += (outsize_padded - smb_wct);
 
        /*
         * work out pointers into the original packets. The
@@ -1725,6 +1726,8 @@ void chain_reply(struct smb_request *req)
                smb_panic("could not allocate smb_request");
        }
        init_smb_request(req2, (uint8 *)inbuf2,0, req->encrypted);
+       req2->inbuf = (uint8_t *)inbuf2;
+       req2->chain_fsp = req->chain_fsp;
 
        /* process the request */
        switch_message(smb_com2, req2, new_size);
@@ -1793,17 +1796,17 @@ void chain_reply(struct smb_request *req)
        SCVAL(outbuf, smb_vwv0, smb_com2);
        SSVAL(outbuf, smb_vwv1, chain_size + smb_wct - 4);
 
-       if (outsize_padded > outsize) {
+       if (padding != 0) {
 
                /*
                 * Due to padding we have some uninitialized bytes after the
                 * caller's output
                 */
 
-               memset(outbuf + outsize, 0, outsize_padded - outsize);
+               memset(outbuf + outsize, 0, padding);
        }
 
-       smb_setlen(outbuf, outsize2 + chain_size - 4);
+       smb_setlen(outbuf, outsize2 + caller_outputlen + padding - 4);
 
        /*
         * restore the saved data, being careful not to overwrite any data
@@ -1814,24 +1817,13 @@ void chain_reply(struct smb_request *req)
        SAFE_FREE(caller_output);
        TALLOC_FREE(req2);
 
-       return;
-}
-
-/****************************************************************************
- Setup the needed select timeout in milliseconds.
-****************************************************************************/
-
-static int setup_select_timeout(void)
-{
-       int select_timeout;
+       /*
+        * Reset the chain_size for our caller's offset calculations
+        */
 
-       select_timeout = SMBD_SELECT_TIMEOUT*1000;
+       chain_size -= (outsize_padded - smb_wct);
 
-       if (print_notify_messages_pending()) {
-               select_timeout = MIN(select_timeout, 1000);
-       }
-
-       return select_timeout;
+       return;
 }
 
 /****************************************************************************
@@ -1888,113 +1880,40 @@ void check_reload(time_t t)
 }
 
 /****************************************************************************
- Process any timeout housekeeping. Return False if the caller should exit.
+ Process commands from the client
 ****************************************************************************/
 
-static void timeout_processing(int *select_timeout,
-                              time_t *last_timeout_processing_time)
+void smbd_process(void)
 {
-       time_t t;
-
-       *last_timeout_processing_time = t = time(NULL);
-
-       /* become root again if waiting */
-       change_to_root_user();
-
-       /* check if we need to reload services */
-       check_reload(t);
-
-       if(global_machine_password_needs_changing && 
-                       /* for ADS we need to do a regular ADS password change, not a domain
-                                       password change */
-                       lp_security() == SEC_DOMAIN) {
-
-               unsigned char trust_passwd_hash[16];
-               time_t lct;
-               void *lock;
-
-               /*
-                * We're in domain level security, and the code that
-                * read the machine password flagged that the machine
-                * password needs changing.
-                */
-
-               /*
-                * First, open the machine password file with an exclusive lock.
-                */
+       unsigned int num_smbs = 0;
+       size_t unread_bytes = 0;
 
-               lock = secrets_get_trust_account_lock(NULL, lp_workgroup());
+       char addr[INET6_ADDRSTRLEN];
 
-               if (lock == NULL) {
-                       DEBUG(0,("process: unable to lock the machine account password for \
-machine %s in domain %s.\n", global_myname(), lp_workgroup() ));
-                       return;
-               }
-
-               if(!secrets_fetch_trust_account_password(lp_workgroup(), trust_passwd_hash, &lct, NULL)) {
-                       DEBUG(0,("process: unable to read the machine account password for \
-machine %s in domain %s.\n", global_myname(), lp_workgroup()));
-                       TALLOC_FREE(lock);
-                       return;
-               }
+       /*
+        * Before the first packet, check the global hosts allow/ hosts deny
+        * parameters before doing any parsing of packets passed to us by the
+        * client. This prevents attacks on our parsing code from hosts not in
+        * the hosts allow list.
+        */
 
+       if (!check_access(smbd_server_fd(), lp_hostsallow(-1),
+                         lp_hostsdeny(-1))) {
                /*
-                * Make sure someone else hasn't already done this.
+                * send a negative session response "not listening on calling
+                * name"
                 */
-
-               if(t < lct + lp_machine_password_timeout()) {
-                       global_machine_password_needs_changing = False;
-                       TALLOC_FREE(lock);
-                       return;
-               }
-
-               /* always just contact the PDC here */
-    
-               change_trust_account_password( lp_workgroup(), NULL);
-               global_machine_password_needs_changing = False;
-               TALLOC_FREE(lock);
+               unsigned char buf[5] = {0x83, 0, 0, 1, 0x81};
+               DEBUG( 1, ("Connection denied from %s\n",
+                          client_addr(get_client_fd(),addr,sizeof(addr)) ) );
+               (void)srv_send_smb(smbd_server_fd(),(char *)buf,false);
+               exit_server_cleanly("connection denied");
        }
 
-       /* update printer queue caches if necessary */
-  
-       update_monitored_printq_cache();
-  
-       /*
-        * Now we are root, check if the log files need pruning.
-        * Force a log file check.
-        */
-       force_check_log_size();
-       check_log_size();
-
-       /* Send any queued printer notify message to interested smbd's. */
-
-       print_notify_send_messages(smbd_messaging_context(), 0);
-
-       /*
-        * Modify the select timeout depending upon
-        * what we have remaining in our queues.
-        */
-
-       *select_timeout = setup_select_timeout();
-
-       return;
-}
-
-/****************************************************************************
- Process commands from the client
-****************************************************************************/
-
-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);
 
        while (True) {
-               int select_timeout = setup_select_timeout();
-               int num_echos;
+               NTSTATUS status;
                char *inbuf = NULL;
                size_t inbuf_len = 0;
                bool encrypted = false;
@@ -2002,82 +1921,26 @@ void smbd_process(void)
 
                errno = 0;
 
-               /* Did someone ask for immediate checks on things like blocking locks ? */
-               if (select_timeout == 0) {
-                       timeout_processing(&select_timeout,
-                                          &last_timeout_processing_time);
-                       num_smbs = 0; /* Reset smb counter. */
-               }
-
                run_events(smbd_event_context(), 0, NULL, NULL);
 
-               while (True) {
-                       NTSTATUS status;
+               status = NT_STATUS_RETRY;
 
+               while (NT_STATUS_EQUAL(status, NT_STATUS_RETRY)) {
                        status = receive_message_or_smb(
                                talloc_tos(), &inbuf, &inbuf_len,
-                               select_timeout, &unread_bytes, &encrypted);
-
-                       if (NT_STATUS_IS_OK(status)) {
-                               break;
-                       }
-
-                       if (NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT)) {
-                               timeout_processing(
-                                       &select_timeout,
-                                       &last_timeout_processing_time);
-                               continue;
-                       }
+                               &unread_bytes, &encrypted);
+               }
 
+               if (!NT_STATUS_IS_OK(status)) {
                        DEBUG(3, ("receive_message_or_smb failed: %s, "
                                  "exiting\n", nt_errstr(status)));
                        return;
-
-                       num_smbs = 0; /* Reset smb counter. */
                }
 
-
-               /*
-                * Ensure we do timeout processing if the SMB we just got was
-                * only an echo request. This allows us to set the select
-                * timeout in 'receive_message_or_smb()' to any value we like
-                * without worrying that the client will send echo requests
-                * 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, unread_bytes, encrypted);
 
-               TALLOC_FREE(inbuf);
-
-               if (smb_echo_count != num_echos) {
-                       timeout_processing(&select_timeout,
-                                          &last_timeout_processing_time);
-                       num_smbs = 0; /* Reset smb counter. */
-               }
-
                num_smbs++;
 
-               /*
-                * If we are getting smb requests in a constant stream
-                * with no echos, make sure we attempt timeout processing
-                * every select_timeout milliseconds - but only check for this
-                * every 200 smb requests.
-                */
-               
-               if ((num_smbs % 200) == 0) {
-                       time_t new_check_time = time(NULL);
-                       if(new_check_time - last_timeout_processing_time >= (select_timeout/1000)) {
-                               timeout_processing(
-                                       &select_timeout,
-                                       &last_timeout_processing_time);
-                               num_smbs = 0; /* Reset smb counter. */
-                               last_timeout_processing_time = new_check_time; /* Reset time. */
-                       }
-               }
-
                /* The timeout_processing function isn't run nearly
                   often enough to implement 'max log size' without
                   overrunning the size of the file by many megabytes.