Fix for CVE-2009-2906.
[samba.git] / source / smbd / process.c
index 48a6d18bc92c6432c909079536b8b8e1824c5699..c53bfda2219a6be756479b7afb09e1adff590c02 100644 (file)
 
 #include "includes.h"
 
-extern struct auth_context *negprot_global_auth_context;
 extern int smb_echo_count;
 
-const int total_buffer_size = (BUFFER_SIZE + LARGE_WRITEX_HDR_SIZE + SAFETY_MARGIN);
-static enum smb_read_errors smb_read_error = SMB_READ_OK;
-
 /*
  * Size of data we can send to client. Set
  *  by the client for all protocols above CORE.
@@ -45,19 +41,52 @@ extern int max_send;
 
 /* Accessor function for smb_read_error for smbd functions. */
 
-enum smb_read_errors *get_srv_read_error(void)
+/****************************************************************************
+ Send an smb to a fd.
+****************************************************************************/
+
+bool srv_send_smb(int fd, char *buffer, bool do_encrypt)
 {
-       return &smb_read_error;
+       size_t len;
+       size_t nwritten=0;
+       ssize_t ret;
+       char *buf_out = buffer;
+
+       /* Sign the outgoing packet if required. */
+       srv_calculate_sign_mac(buf_out);
+
+       if (do_encrypt) {
+               NTSTATUS status = srv_encrypt_buffer(buffer, &buf_out);
+               if (!NT_STATUS_IS_OK(status)) {
+                       DEBUG(0, ("send_smb: SMB encryption failed "
+                               "on outgoing packet! Error %s\n",
+                               nt_errstr(status) ));
+                       return false;
+               }
+       }
+
+       len = smb_len(buf_out) + 4;
+
+       while (nwritten < len) {
+               ret = write_data(fd,buf_out+nwritten,len - nwritten);
+               if (ret <= 0) {
+                       DEBUG(0,("Error writing %d bytes to client. %d. (%s)\n",
+                               (int)len,(int)ret, strerror(errno) ));
+                       srv_free_enc_buffer(buf_out);
+                       return false;
+               }
+               nwritten += ret;
+       }
+
+       srv_free_enc_buffer(buf_out);
+       return true;
 }
 
 /*******************************************************************
  Setup the word count and byte count for a smb message.
- copying the '0xFF X X X' bytes from incoming
- buffer (so we copy any encryption context).
 ********************************************************************/
 
-int srv_set_message(const char *frombuf,
-                        char *buf,
+int srv_set_message(char *buf,
                         int num_words,
                         int num_bytes,
                         bool zero)
@@ -67,22 +96,14 @@ int srv_set_message(const char *frombuf,
        }
        SCVAL(buf,smb_wct,num_words);
        SSVAL(buf,smb_vwv + num_words*SIZEOFWORD,num_bytes);
-       _smb_setlen(buf,(smb_size + num_words*2 + num_bytes - 4));
-       if (buf != frombuf) {
-               memcpy(buf+4, frombuf+4, 4);
-       }
+       smb_setlen(buf,(smb_size + num_words*2 + num_bytes - 4));
        return (smb_size + num_words*2 + num_bytes);
 }
 
-static bool valid_smb_header(const char *inbuf)
+static bool valid_smb_header(const uint8_t *inbuf)
 {
-       if (srv_encryption_on()) {
-               uint16_t enc_num;
-               NTSTATUS status = get_enc_ctx_num(inbuf, &enc_num);
-               if (!NT_STATUS_IS_OK(status)) {
-                       return false;
-               }
-               return (enc_num == 0);
+       if (is_encrypted_packet(inbuf)) {
+               return true;
        }
        return (strncmp(smb_base(inbuf),"\377SMB",4) == 0);
 }
@@ -99,51 +120,19 @@ static bool valid_packet_size(size_t len)
        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.
-                        */
-
-                       cond_set_smb_read_error(get_srv_read_error(),
-                                               SMB_READ_ERROR);
-                       return false;
-               }
+               return false;
        }
        return true;
 }
 
-static ssize_t read_packet_remainder(int fd,
-                                       char *buffer,
-                                       unsigned int timeout,
-                                       ssize_t len)
+static NTSTATUS 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,
-                                               get_srv_read_error());
-       } else {
-               ret = read_data(fd, buffer, len, get_srv_read_error());
+       if (len <= 0) {
+               return NT_STATUS_OK;
        }
 
-       if (ret != len) {
-               cond_set_smb_read_error(get_srv_read_error(),
-                                       SMB_READ_ERROR);
-               return -1;
-       }
-
-       return len;
+       return read_socket_with_timeout(fd, buffer, len, len, timeout, NULL);
 }
 
 /****************************************************************************
@@ -162,39 +151,29 @@ static ssize_t read_packet_remainder(int fd,
                                (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)
+static NTSTATUS 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_t *len_ret)
 {
        /* Size of a WRITEX call (+4 byte len). */
        char writeX_header[4 + STANDARD_WRITE_AND_X_HEADER_SIZE];
        ssize_t len = smb_len_large(lenbuf); /* Could be a UNIX large writeX. */
        ssize_t toread;
-       ssize_t ret;
+       NTSTATUS status;
 
-       memcpy(writeX_header, lenbuf, sizeof(lenbuf));
+       memcpy(writeX_header, lenbuf, 4);
 
-       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,
-                                       get_srv_read_error());
-       } else {
-               ret = read_data(fd,
-                               writeX_header+4,
-                               STANDARD_WRITE_AND_X_HEADER_SIZE,
-                               get_srv_read_error());
-       }
+       status = read_socket_with_timeout(
+               fd, writeX_header + 4,
+               STANDARD_WRITE_AND_X_HEADER_SIZE,
+               STANDARD_WRITE_AND_X_HEADER_SIZE,
+               timeout, NULL);
 
-       if (ret != STANDARD_WRITE_AND_X_HEADER_SIZE) {
-               cond_set_smb_read_error(get_srv_read_error(),
-                                       SMB_READ_ERROR);
-               return -1;
+       if (!NT_STATUS_IS_OK(status)) {
+               return status;
        }
 
        /*
@@ -202,7 +181,7 @@ ssize_t receive_smb_raw_talloc_partial_read(TALLOC_CTX *mem_ctx,
         * valid writeX call.
         */
 
-       if (is_valid_writeX_buffer(writeX_header)) {
+       if (is_valid_writeX_buffer((uint8_t *)writeX_header)) {
                /*
                 * If the data offset is beyond what
                 * we've read, drain the extra bytes.
@@ -233,19 +212,17 @@ ssize_t receive_smb_raw_talloc_partial_read(TALLOC_CTX *mem_ctx,
                if (*buffer == NULL) {
                        DEBUG(0, ("Could not allocate inbuf of length %d\n",
                                  (int)sizeof(writeX_header)));
-                       cond_set_smb_read_error(get_srv_read_error(),
-                                               SMB_READ_ERROR);
-                       return -1;
+                       return NT_STATUS_NO_MEMORY;
                }
 
                /* Work out the remaining bytes. */
                *p_unread = len - STANDARD_WRITE_AND_X_HEADER_SIZE;
-
-               return newlen + 4;
+               *len_ret = newlen + 4;
+               return NT_STATUS_OK;
        }
 
        if (!valid_packet_size(len)) {
-               return -1;
+               return NT_STATUS_INVALID_PARAMETER;
        }
 
        /*
@@ -258,9 +235,7 @@ ssize_t receive_smb_raw_talloc_partial_read(TALLOC_CTX *mem_ctx,
        if (*buffer == NULL) {
                DEBUG(0, ("Could not allocate inbuf of length %d\n",
                          (int)len+4));
-               cond_set_smb_read_error(get_srv_read_error(),
-                                       SMB_READ_ERROR);
-               return -1;
+               return NT_STATUS_NO_MEMORY;
        }
 
        /* Copy in what we already read. */
@@ -270,61 +245,49 @@ ssize_t receive_smb_raw_talloc_partial_read(TALLOC_CTX *mem_ctx,
        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;
+               status = read_packet_remainder(
+                       fd, (*buffer) + 4 + STANDARD_WRITE_AND_X_HEADER_SIZE,
+                       timeout, toread);
+
+               if (!NT_STATUS_IS_OK(status)) {
+                       DEBUG(10, ("receive_smb_raw_talloc_partial_read: %s\n",
+                                  nt_errstr(status)));
+                       return status;
                }
        }
 
-       return len + 4;
+       *len_ret = len + 4;
+       return NT_STATUS_OK;
 }
 
-static ssize_t receive_smb_raw_talloc(TALLOC_CTX *mem_ctx,
-                                       int fd,
-                                       char **buffer,
-                                       unsigned int timeout,
-                                       size_t *p_unread)
+static NTSTATUS receive_smb_raw_talloc(TALLOC_CTX *mem_ctx, int fd,
+                                      char **buffer, unsigned int timeout,
+                                      size_t *p_unread, size_t *plen)
 {
        char lenbuf[4];
-       ssize_t len,ret;
+       size_t len;
        int min_recv_size = lp_min_receive_file_size();
+       NTSTATUS status;
 
-       set_smb_read_error(get_srv_read_error(),SMB_READ_OK);
        *p_unread = 0;
 
-       len = read_smb_length_return_keepalive(fd, lenbuf,
-                       timeout, get_srv_read_error());
-       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.
-                */
-
-               cond_set_smb_read_error(get_srv_read_error(),SMB_READ_ERROR);
-               return -1;
+       status = read_smb_length_return_keepalive(fd, lenbuf, timeout, &len);
+       if (!NT_STATUS_IS_OK(status)) {
+               DEBUG(10, ("receive_smb_raw: %s\n", nt_errstr(status)));
+               return status;
        }
 
-       if (CVAL(lenbuf,0) != SMBkeepalive &&
+       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(mem_ctx,
-                                                       lenbuf,
-                                                       fd,
-                                                       buffer,
-                                                       timeout,
-                                                       p_unread);
+               return receive_smb_raw_talloc_partial_read(
+                       mem_ctx, lenbuf, fd, buffer, timeout, p_unread, plen);
        }
 
        if (!valid_packet_size(len)) {
-               return -1;
+               return NT_STATUS_INVALID_PARAMETER;
        }
 
        /*
@@ -336,52 +299,56 @@ static ssize_t receive_smb_raw_talloc(TALLOC_CTX *mem_ctx,
        if (*buffer == NULL) {
                DEBUG(0, ("Could not allocate inbuf of length %d\n",
                          (int)len+4));
-               cond_set_smb_read_error(get_srv_read_error(),SMB_READ_ERROR);
-               return -1;
+               return NT_STATUS_NO_MEMORY;
        }
 
        memcpy(*buffer, lenbuf, sizeof(lenbuf));
 
-       ret = read_packet_remainder(fd, (*buffer)+4, timeout, len);
-       if (ret != len) {
-               return -1;
+       status = read_packet_remainder(fd, (*buffer)+4, timeout, len);
+       if (!NT_STATUS_IS_OK(status)) {
+               return status;
        }
 
-       return len + 4;
+       *plen = len + 4;
+       return NT_STATUS_OK;
 }
 
-ssize_t receive_smb_talloc(TALLOC_CTX *mem_ctx, int fd, char **buffer,
-                          unsigned int timeout, size_t *p_unread)
+static NTSTATUS receive_smb_talloc(TALLOC_CTX *mem_ctx,        int fd,
+                                  char **buffer, unsigned int timeout,
+                                  size_t *p_unread, bool *p_encrypted,
+                                  size_t *p_len)
 {
-       ssize_t len;
+       size_t len = 0;
+       NTSTATUS status;
 
-       len = receive_smb_raw_talloc(mem_ctx, fd, buffer, timeout, p_unread);
+       *p_encrypted = false;
 
-       if (len < 0) {
-               return -1;
+       status = receive_smb_raw_talloc(mem_ctx, fd, buffer, timeout,
+                                       p_unread, &len);
+       if (!NT_STATUS_IS_OK(status)) {
+               return status;
        }
 
-       if (srv_encryption_on()) {
-               NTSTATUS status = srv_decrypt_buffer(*buffer);
+       if (is_encrypted_packet((uint8_t *)*buffer)) {
+               status = srv_decrypt_buffer(*buffer);
                if (!NT_STATUS_IS_OK(status)) {
                        DEBUG(0, ("receive_smb_talloc: SMB decryption failed on "
                                "incoming packet! Error %s\n",
                                nt_errstr(status) ));
-                       cond_set_smb_read_error(get_srv_read_error(),
-                                       SMB_READ_BAD_DECRYPT);
-                       return -1;
+                       return status;
                }
+               *p_encrypted = true;
        }
 
        /* Check the incoming SMB signature. */
        if (!srv_check_sign_mac(*buffer, true)) {
                DEBUG(0, ("receive_smb: SMB Signature verification failed on "
                          "incoming packet!\n"));
-               cond_set_smb_read_error(get_srv_read_error(),SMB_READ_BAD_SIG);
-               return -1;
+               return NT_STATUS_INVALID_NETWORK_RESPONSE;
        }
 
-       return len;
+       *p_len = len;
+       return NT_STATUS_OK;
 }
 
 /*
@@ -390,7 +357,8 @@ ssize_t receive_smb_talloc(TALLOC_CTX *mem_ctx, int fd, char **buffer,
 
 void init_smb_request(struct smb_request *req,
                        const uint8 *inbuf,
-                       size_t unread_bytes)
+                       size_t unread_bytes,
+                       bool encrypted)
 {
        size_t req_size = smb_len(inbuf) + 4;
        /* Ensure we have at least smb_size bytes. */
@@ -406,6 +374,8 @@ void init_smb_request(struct smb_request *req,
        req->tid    = SVAL(inbuf, smb_tid);
        req->wct    = CVAL(inbuf, smb_wct);
        req->unread_bytes = unread_bytes;
+       req->encrypted = encrypted;
+       req->conn = conn_find(req->tid);
 
        /* Ensure we have at least wct words and 2 bytes of bcc. */
        if (smb_size + req->wct*2 > req_size) {
@@ -463,6 +433,8 @@ static bool push_queued_message(struct smb_request *req,
 
        msg->request_time = request_time;
        msg->end_time = end_time;
+       msg->encrypted = req->encrypted;
+       msg->processed = false;
 
        if (private_data) {
                msg->private_data = data_blob_talloc(msg, private_data,
@@ -518,6 +490,16 @@ void schedule_deferred_open_smb_message(uint16 mid)
                DEBUG(10,("schedule_deferred_open_smb_message: [%d] msg_mid = %u\n", i++,
                        (unsigned int)msg_mid ));
                if (mid == msg_mid) {
+
+                       if (pml->processed) {
+                               /* A processed message should not be
+                                * rescheduled. */
+                               DEBUG(0,("schedule_deferred_open_smb_message: LOGIC ERROR "
+                                       "message mid %u was already processed\n",
+                                       (unsigned int)msg_mid ));
+                               continue;
+                       }
+
                        DEBUG(10,("schedule_deferred_open_smb_message: scheduling mid %u\n",
                                mid ));
                        pml->end_time.tv_sec = 0;
@@ -532,7 +514,7 @@ void schedule_deferred_open_smb_message(uint16 mid)
 }
 
 /****************************************************************************
- Return true if this mid is on the deferred queue.
+ Return true if this mid is on the deferred queue and was not yet processed.
 ****************************************************************************/
 
 bool open_was_deferred(uint16 mid)
@@ -540,7 +522,7 @@ bool open_was_deferred(uint16 mid)
        struct pending_message_list *pml;
 
        for (pml = deferred_open_queue; pml; pml = pml->next) {
-               if (SVAL(pml->buf.data,smb_mid) == mid) {
+               if (SVAL(pml->buf.data,smb_mid) == mid && !pml->processed) {
                        return True;
                }
        }
@@ -734,20 +716,18 @@ 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,
-                               size_t *p_unread)
+static NTSTATUS receive_message_or_smb(TALLOC_CTX *mem_ctx, char **buffer,
+                                      size_t *buffer_len, int timeout,
+                                      size_t *p_unread, bool *p_encrypted)
 {
        fd_set r_fds, w_fds;
        int selrtn;
        struct timeval to;
        int maxfd = 0;
-       ssize_t len;
+       size_t len = 0;
+       NTSTATUS status;
 
        *p_unread = 0;
-       set_smb_read_error(get_srv_read_error(),SMB_READ_OK);
 
  again:
 
@@ -801,15 +781,19 @@ static bool receive_message_or_smb(TALLOC_CTX *mem_ctx,
                                                        msg->buf.length);
                        if (*buffer == NULL) {
                                DEBUG(0, ("talloc failed\n"));
-                               set_smb_read_error(get_srv_read_error(),SMB_READ_ERROR);
-                               return False;
+                               return NT_STATUS_NO_MEMORY;
                        }
                        *buffer_len = msg->buf.length;
+                       *p_encrypted = msg->encrypted;
 
                        /* We leave this message on the queue so the open code can
                           know this is a retry. */
                        DEBUG(5,("receive_message_or_smb: returning deferred open smb message.\n"));
-                       return True;
+
+                       /* Mark the message as processed so this is not
+                        * re-processed in error. */
+                       msg->processed = true;
+                       return NT_STATUS_OK;
                }
        }
 
@@ -895,14 +879,12 @@ static bool receive_message_or_smb(TALLOC_CTX *mem_ctx,
        /* Check if error */
        if (selrtn == -1) {
                /* something is wrong. Maybe the socket is dead? */
-               set_smb_read_error(get_srv_read_error(),SMB_READ_ERROR);
-               return False;
-       } 
-    
+               return map_nt_error_from_unix(errno);
+       }
+
        /* Did we timeout ? */
        if (selrtn == 0) {
-               set_smb_read_error(get_srv_read_error(),SMB_READ_TIMEOUT);
-               return False;
+               return NT_STATUS_IO_TIMEOUT;
        }
 
        /*
@@ -921,15 +903,25 @@ static bool receive_message_or_smb(TALLOC_CTX *mem_ctx,
                goto again;
        }
 
-       len = receive_smb_talloc(mem_ctx, smbd_server_fd(), buffer, 0, p_unread);
+       /*
+        * We've just woken up from a protentially long select sleep.
+        * Ensure we process local messages as we need to synchronously
+        * process any messages from other smbd's to avoid file rename race
+        * conditions. This call is cheap if there are no messages waiting.
+        * JRA.
+        */
+       message_dispatch(smbd_messaging_context());
 
-       if (len == -1) {
-               return False;
+       status = receive_smb_talloc(mem_ctx, smbd_server_fd(), buffer, 0,
+                                   p_unread, p_encrypted, &len);
+
+       if (!NT_STATUS_IS_OK(status)) {
+               return status;
        }
 
-       *buffer_len = (size_t)len;
+       *buffer_len = len;
 
-       return True;
+       return NT_STATUS_OK;
 }
 
 /*
@@ -1001,7 +993,7 @@ force write permissions on print services.
 */
 static const struct smb_message_struct {
        const char *name;
-       void (*fn_new)(connection_struct *conn, struct smb_request *req);
+       void (*fn_new)(struct smb_request *req);
        int flags;
 } smb_messages[256] = {
 
@@ -1056,7 +1048,7 @@ static const struct smb_message_struct {
 /* 0x30 */ { NULL, NULL, 0 },
 /* 0x31 */ { NULL, NULL, 0 },
 /* 0x32 */ { "SMBtrans2",reply_trans2, AS_USER | CAN_IPC },
-/* 0x33 */ { "SMBtranss2",reply_transs2, AS_USER},
+/* 0x33 */ { "SMBtranss2",reply_transs2, AS_USER | CAN_IPC},
 /* 0x34 */ { "SMBfindclose",reply_findclose,AS_USER},
 /* 0x35 */ { "SMBfindnclose",reply_findnclose,AS_USER},
 /* 0x36 */ { NULL, NULL, 0 },
@@ -1276,8 +1268,10 @@ void reply_outbuf(struct smb_request *req, uint8 num_words, uint32 num_bytes)
        if ((num_bytes > 0xffffff)
            || ((num_bytes + smb_size + num_words*2) > 0xffffff)) {
                char *msg;
-               asprintf(&msg, "num_bytes too large: %u",
-                        (unsigned)num_bytes);
+               if (asprintf(&msg, "num_bytes too large: %u",
+                            (unsigned)num_bytes) == -1) {
+                       msg = CONST_DISCARD(char *, "num_bytes too large");
+               }
                smb_panic(msg);
        }
 
@@ -1288,8 +1282,7 @@ void reply_outbuf(struct smb_request *req, uint8 num_words, uint32 num_bytes)
        }
 
        construct_reply_common((char *)req->inbuf, (char *)req->outbuf);
-       srv_set_message((const char *)req->inbuf,
-                       (char *)req->outbuf, num_words, num_bytes, false);
+       srv_set_message((char *)req->outbuf, num_words, num_bytes, false);
        /*
         * Zero out the word area, the caller has to take care of the bcc area
         * himself
@@ -1316,9 +1309,8 @@ static void smb_dump(const char *name, int type, const char *data, ssize_t len)
 
        if (len < 4) len = smb_len(data)+4;
        for (i=1;i<100;i++) {
-               asprintf(&fname, "/tmp/%s.%d.%s", name, i,
-                               type ? "req" : "resp");
-               if (!fname) {
+               if (asprintf(&fname, "/tmp/%s.%d.%s", name, i,
+                            type ? "req" : "resp") == -1) {
                        return;
                }
                fd = open(fname, O_WRONLY|O_CREAT|O_EXCL, 0644);
@@ -1347,11 +1339,11 @@ static void smb_dump(const char *name, int type, const char *data, ssize_t len)
  find.
 ****************************************************************************/
 
-static void switch_message(uint8 type, struct smb_request *req, int size)
+static connection_struct *switch_message(uint8 type, struct smb_request *req, int size)
 {
        int flags;
        uint16 session_tag;
-       connection_struct *conn;
+       connection_struct *conn = NULL;
 
        static uint16 last_session_tag = UID_FIELD_INVALID;
 
@@ -1359,7 +1351,7 @@ static void switch_message(uint8 type, struct smb_request *req, int size)
 
        /* Make sure this is an SMB packet. smb_size contains NetBIOS header
         * so subtract 4 from it. */
-       if (!valid_smb_header((const char *)req->inbuf)
+       if (!valid_smb_header(req->inbuf)
            || (size < (smb_size - 4))) {
                DEBUG(2,("Non-SMB packet of length %d. Terminating server\n",
                         smb_len(req->inbuf)));
@@ -1370,7 +1362,7 @@ static void switch_message(uint8 type, struct smb_request *req, int size)
                DEBUG(0,("Unknown message type %d!\n",type));
                smb_dump("Unknown", 1, (char *)req->inbuf, size);
                reply_unknown_new(req, type);
-               return;
+               return NULL;
        }
 
        flags = smb_messages[type].flags;
@@ -1378,7 +1370,7 @@ static void switch_message(uint8 type, struct smb_request *req, int size)
        /* In share mode security we must ignore the vuid. */
        session_tag = (lp_security() == SEC_SHARE)
                ? UID_FIELD_INVALID : req->vuid;
-       conn = conn_find(req->tid);
+       conn = req->conn;
 
        DEBUG(3,("switch message %s (pid %d) conn 0x%lx\n", smb_fn_name(type),
                 (int)sys_getpid(), (unsigned long)conn));
@@ -1423,12 +1415,12 @@ static void switch_message(uint8 type, struct smb_request *req, int size)
                        } else {
                                reply_doserror(req, ERRSRV, ERRinvnid);
                        }
-                       return;
+                       return NULL;
                }
 
                if (!change_to_user(conn,session_tag)) {
                        reply_nterror(req, NT_STATUS_DOS(ERRSRV, ERRbaduid));
-                       return;
+                       return conn;
                }
 
                /* All NEED_WRITE and CAN_IPC flags must also have AS_USER. */
@@ -1436,13 +1428,13 @@ static void switch_message(uint8 type, struct smb_request *req, int size)
                /* Does it need write permission? */
                if ((flags & NEED_WRITE) && !CAN_WRITE(conn)) {
                        reply_nterror(req, NT_STATUS_MEDIA_WRITE_PROTECTED);
-                       return;
+                       return conn;
                }
 
                /* IPC services are limited */
                if (IS_IPC(conn) && !(flags & CAN_IPC)) {
                        reply_doserror(req, ERRSRV,ERRaccess);
-                       return;
+                       return conn;
                }
        } else {
                /* This call needs to be run as root */
@@ -1451,21 +1443,24 @@ static void switch_message(uint8 type, struct smb_request *req, int size)
 
        /* load service specific parameters */
        if (conn) {
+               if (req->encrypted) {
+                       conn->encrypted_tid = true;
+                       /* 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) {
+                               exit_server_cleanly("encryption required "
+                                       "on connection");
+                               return conn;
+                       }
+               }
+
                if (!set_current_service(conn,SVAL(req->inbuf,smb_flg),
                                         (flags & (AS_USER|DO_CHDIR)
                                          ?True:False))) {
                        reply_doserror(req, ERRSRV, ERRaccess);
-                       return;
-               }
-
-               if (conn->encrypt_level == Required && SVAL(req->inbuf,4) != 0x45FF ) {
-                       /* An encrypted packet has 0xFF 'E' at offset 4
-                        * which is little endian 0x45FF */
-                       uint8 com = CVAL(req->inbuf,smb_com);
-                       if (com != SMBtrans2 && com != SMBtranss2) {
-                               reply_nterror(req, NT_STATUS_ACCESS_DENIED);
-                               return;
-                       }
+                       return conn;
                }
                conn->num_smb_operations++;
        }
@@ -1476,19 +1471,22 @@ static void switch_message(uint8 type, struct smb_request *req, int size)
                !check_access(smbd_server_fd(), lp_hostsallow(-1),
                              lp_hostsdeny(-1)))) {
                reply_doserror(req, ERRSRV, ERRaccess);
-               return;
+               return conn;
        }
 
-       smb_messages[type].fn_new(conn, req);
+       smb_messages[type].fn_new(req);
+       return req->conn;
 }
 
 /****************************************************************************
  Construct a reply to the incoming packet.
 ****************************************************************************/
 
-static void construct_reply(char *inbuf, int size, size_t unread_bytes)
+static void construct_reply(char *inbuf, int size, size_t unread_bytes, bool encrypted)
 {
+       struct pending_message_list *pml = NULL;
        uint8 type = CVAL(inbuf,smb_com);
+       connection_struct *conn;
        struct smb_request *req;
 
        chain_size = 0;
@@ -1498,9 +1496,16 @@ static void construct_reply(char *inbuf, int size, size_t unread_bytes)
        if (!(req = talloc(talloc_tos(), struct smb_request))) {
                smb_panic("could not allocate smb_request");
        }
-       init_smb_request(req, (uint8 *)inbuf, unread_bytes);
+       init_smb_request(req, (uint8 *)inbuf, unread_bytes, encrypted);
+
+       conn = switch_message(type, req, size);
 
-       switch_message(type, req, size);
+       /* If this was a deferred message and it's still there and
+        * was processed, remove it. */
+       pml = get_open_deferred_message(req->mid);
+       if (pml && pml->processed) {
+               remove_deferred_open_smb_message(req->mid);
+       }
 
        if (req->unread_bytes) {
                /* writeX failed. drain socket. */
@@ -1519,8 +1524,10 @@ static void construct_reply(char *inbuf, int size, size_t unread_bytes)
                show_msg((char *)req->outbuf);
        }
 
-       if (!send_smb(smbd_server_fd(), (char *)req->outbuf)) {
-               exit_server_cleanly("construct_reply: send_smb failed.");
+       if (!srv_send_smb(smbd_server_fd(),
+                       (char *)req->outbuf,
+                       IS_CONN_ENCRYPTED(conn)||req->encrypted)) {
+               exit_server_cleanly("construct_reply: srv_send_smb failed.");
        }
 
        TALLOC_FREE(req);
@@ -1532,7 +1539,7 @@ static void construct_reply(char *inbuf, int size, size_t unread_bytes)
  Process an smb from the client
 ****************************************************************************/
 
-static void process_smb(char *inbuf, size_t nread, size_t unread_bytes)
+static void process_smb(char *inbuf, size_t nread, size_t unread_bytes, bool encrypted)
 {
        static int trans_num;
        int msg_type = CVAL(inbuf,0);
@@ -1553,7 +1560,7 @@ static void process_smb(char *inbuf, size_t nread, size_t unread_bytes)
                        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)send_smb(smbd_server_fd(),(char *)buf);
+                       (void)srv_send_smb(smbd_server_fd(),(char *)buf,false);
                        exit_server_cleanly("connection denied");
                }
        }
@@ -1574,7 +1581,7 @@ static void process_smb(char *inbuf, size_t nread, size_t unread_bytes)
 
        show_msg(inbuf);
 
-       construct_reply(inbuf,nread,unread_bytes);
+       construct_reply(inbuf,nread,unread_bytes,encrypted);
 
        trans_num++;
 }
@@ -1611,7 +1618,7 @@ void remove_from_common_flags2(uint32 v)
 
 void construct_reply_common(const char *inbuf, char *outbuf)
 {
-       srv_set_message(inbuf,outbuf,0,0,false);
+       srv_set_message(outbuf,0,0,false);
        
        SCVAL(outbuf,smb_com,CVAL(inbuf,smb_com));
        SIVAL(outbuf,smb_rcls,0);
@@ -1652,6 +1659,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;
@@ -1690,12 +1698,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
@@ -1734,7 +1743,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,0);
+       init_smb_request(req2, (uint8 *)inbuf2,0, req->encrypted);
 
        /* process the request */
        switch_message(smb_com2, req2, new_size);
@@ -1803,17 +1812,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
@@ -1824,6 +1833,12 @@ void chain_reply(struct smb_request *req)
        SAFE_FREE(caller_output);
        TALLOC_FREE(req2);
 
+       /*
+        * Reset the chain_size for our caller's offset calculations
+        */
+
+       chain_size -= (outsize_padded - smb_wct);
+
        return;
 }
 
@@ -1901,27 +1916,11 @@ void check_reload(time_t t)
  Process any timeout housekeeping. Return False if the caller should exit.
 ****************************************************************************/
 
-static bool timeout_processing(int *select_timeout,
+static void timeout_processing(int *select_timeout,
                               time_t *last_timeout_processing_time)
 {
        time_t t;
 
-       if (*get_srv_read_error() == SMB_READ_EOF) {
-               DEBUG(3,("timeout_processing: End of file from client (client has disconnected).\n"));
-               return false;
-       }
-
-       if (*get_srv_read_error() == SMB_READ_ERROR) {
-               DEBUG(3,("timeout_processing: receive_smb error (%s) Exiting\n",
-                       strerror(errno)));
-               return false;
-       }
-
-       if (*get_srv_read_error() == SMB_READ_BAD_SIG) {
-               DEBUG(3,("timeout_processing: receive_smb error bad smb signature. Exiting\n"));
-               return false;
-       }
-
        *last_timeout_processing_time = t = time(NULL);
 
        /* become root again if waiting */
@@ -1937,6 +1936,7 @@ static bool timeout_processing(int *select_timeout,
 
                unsigned char trust_passwd_hash[16];
                time_t lct;
+               void *lock;
 
                /*
                 * We're in domain level security, and the code that
@@ -1948,17 +1948,19 @@ static bool timeout_processing(int *select_timeout,
                 * First, open the machine password file with an exclusive lock.
                 */
 
-               if (secrets_lock_trust_account_password(lp_workgroup(), True) == False) {
+               lock = secrets_get_trust_account_lock(NULL, lp_workgroup());
+
+               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 True;
+                       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()));
-                       secrets_lock_trust_account_password(lp_workgroup(), False);
-                       return True;
+                       TALLOC_FREE(lock);
+                       return;
                }
 
                /*
@@ -1967,15 +1969,15 @@ machine %s in domain %s.\n", global_myname(), lp_workgroup()));
 
                if(t < lct + lp_machine_password_timeout()) {
                        global_machine_password_needs_changing = False;
-                       secrets_lock_trust_account_password(lp_workgroup(), False);
-                       return True;
+                       TALLOC_FREE(lock);
+                       return;
                }
 
                /* always just contact the PDC here */
     
                change_trust_account_password( lp_workgroup(), NULL);
                global_machine_password_needs_changing = False;
-               secrets_lock_trust_account_password(lp_workgroup(), False);
+               TALLOC_FREE(lock);
        }
 
        /* update printer queue caches if necessary */
@@ -2000,7 +2002,7 @@ machine %s in domain %s.\n", global_myname(), lp_workgroup()));
 
        *select_timeout = setup_select_timeout();
 
-       return True;
+       return;
 }
 
 /****************************************************************************
@@ -2018,27 +2020,44 @@ void smbd_process(void)
        while (True) {
                int select_timeout = setup_select_timeout();
                int num_echos;
-               char *inbuf;
-               size_t inbuf_len;
-               TALLOC_CTX *frame = talloc_stackframe();
+               char *inbuf = NULL;
+               size_t inbuf_len = 0;
+               bool encrypted = false;
+               TALLOC_CTX *frame = talloc_stackframe_pool(8192);
 
                errno = 0;
 
                /* Did someone ask for immediate checks on things like blocking locks ? */
                if (select_timeout == 0) {
-                       if(!timeout_processing(&select_timeout,
-                                              &last_timeout_processing_time))
-                               return;
+                       timeout_processing(&select_timeout,
+                                          &last_timeout_processing_time);
                        num_smbs = 0; /* Reset smb counter. */
                }
 
                run_events(smbd_event_context(), 0, NULL, NULL);
 
-               while (!receive_message_or_smb(NULL, &inbuf, &inbuf_len,
-                                              select_timeout, &unread_bytes)) {
-                       if(!timeout_processing(&select_timeout,
-                                              &last_timeout_processing_time))
-                               return;
+               while (True) {
+                       NTSTATUS status;
+
+                       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;
+                       }
+
+                       DEBUG(3, ("receive_message_or_smb failed: %s, "
+                                 "exiting\n", nt_errstr(status)));
+                       return;
+
                        num_smbs = 0; /* Reset smb counter. */
                }
 
@@ -2054,13 +2073,13 @@ void smbd_process(void)
                 */
                num_echos = smb_echo_count;
 
-               process_smb(inbuf, inbuf_len, unread_bytes);
+               process_smb(inbuf, inbuf_len, unread_bytes, encrypted);
 
                TALLOC_FREE(inbuf);
 
                if (smb_echo_count != num_echos) {
-                       if(!timeout_processing( &select_timeout, &last_timeout_processing_time))
-                               return;
+                       timeout_processing(&select_timeout,
+                                          &last_timeout_processing_time);
                        num_smbs = 0; /* Reset smb counter. */
                }
 
@@ -2076,10 +2095,9 @@ void smbd_process(void)
                if ((num_smbs % 200) == 0) {
                        time_t new_check_time = time(NULL);
                        if(new_check_time - last_timeout_processing_time >= (select_timeout/1000)) {
-                               if(!timeout_processing(
-                                          &select_timeout,
-                                          &last_timeout_processing_time))
-                                       return;
+                               timeout_processing(
+                                       &select_timeout,
+                                       &last_timeout_processing_time);
                                num_smbs = 0; /* Reset smb counter. */
                                last_timeout_processing_time = new_check_time; /* Reset time. */
                        }