From 9310472ba321483b2143c6d6920e99c323009b0b Mon Sep 17 00:00:00 2001
From: Matthew Swift <matthew.swift@forgerock.com>
Date: Thu, 16 Feb 2012 15:48:56 +0000
Subject: [PATCH] Additional fix for OPENDJ-420: Rare SSLExceptions while handling LDAPS connections and big LDAP searches

---
 opendj-sdk/opends/src/server/org/opends/server/extensions/SASLByteChannel.java |  148 +++++++++++++++++++++++++------------------------
 opendj-sdk/opends/src/server/org/opends/server/extensions/TLSByteChannel.java  |   30 +++++++--
 2 files changed, 98 insertions(+), 80 deletions(-)

diff --git a/opendj-sdk/opends/src/server/org/opends/server/extensions/SASLByteChannel.java b/opendj-sdk/opends/src/server/org/opends/server/extensions/SASLByteChannel.java
index bf6ece9..d24ac6e 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/extensions/SASLByteChannel.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/extensions/SASLByteChannel.java
@@ -163,8 +163,8 @@
           if (sendWrappedBuffer.capacity() < wrappedDataBytes.length + 4)
           {
             // Resize the send buffer.
-            sendWrappedBuffer = ByteBuffer
-                .allocate(wrappedDataBytes.length + 4);
+            sendWrappedBuffer =
+                ByteBuffer.allocate(wrappedDataBytes.length + 4);
           }
           sendWrappedBuffer.clear();
           sendWrappedBuffer.putInt(wrappedDataBytes.length);
@@ -185,82 +185,85 @@
     // Attempt to read and unwrap the next SASL packet.
     private int doRecvAndUnwrap() throws IOException
     {
-      // Read the encoded packet length first.
-      if (recvWrappedLength < 0)
+      // Read SASL packets until some unwrapped data is produced or no more
+      // data is available on the underlying channel.
+      while (true)
       {
-        final int read = channel.read(recvWrappedLengthBuffer);
-        if (read <= 0)
-        {
-          // No data read or end of stream.
-          return read;
-        }
-
-        if (recvWrappedLengthBuffer.hasRemaining())
-        {
-          // Unable to read the length, so no data available yet.
-          return 0;
-        }
-
-        // Decode the length and reset the length buffer.
-        recvWrappedLengthBuffer.flip();
-        recvWrappedLength = recvWrappedLengthBuffer.getInt();
-        recvWrappedLengthBuffer.clear();
-
-        // Check that the length is valid.
-        if (recvWrappedLength > recvWrappedBufferMaximumSize)
-        {
-          throw new IOException(
-              "Client sent a SASL packet specifying a length "
-                  + recvWrappedLength
-                  + " which exceeds the negotiated limit of "
-                  + recvWrappedBufferMaximumSize);
-        }
-
+        // Read the wrapped packet length first.
         if (recvWrappedLength < 0)
         {
-          throw new IOException(
-              "Client sent a SASL packet specifying a negative length "
-                  + recvWrappedLength);
+          // The channel read may only partially fill the buffer due to
+          // buffering in the underlying channel layer (e.g. SSL layer), so
+          // repeatedly read until the length has been read or we are sure
+          // that we are unable to proceed.
+          while (recvWrappedLengthBuffer.hasRemaining())
+          {
+            final int read = channel.read(recvWrappedLengthBuffer);
+            if (read <= 0)
+            {
+              // Not enough data available or end of stream.
+              return read;
+            }
+          }
+
+          // Decode the length and reset the length buffer.
+          recvWrappedLengthBuffer.flip();
+          recvWrappedLength = recvWrappedLengthBuffer.getInt();
+          recvWrappedLengthBuffer.clear();
+
+          // Check that the length is valid.
+          if (recvWrappedLength > recvWrappedBufferMaximumSize)
+          {
+            throw new IOException(
+                "Client sent a SASL packet specifying a length "
+                    + recvWrappedLength
+                    + " which exceeds the negotiated limit of "
+                    + recvWrappedBufferMaximumSize);
+          }
+
+          if (recvWrappedLength < 0)
+          {
+            throw new IOException(
+                "Client sent a SASL packet specifying a negative length "
+                    + recvWrappedLength);
+          }
+
+          // Prepare the recv buffer for reading.
+          recvWrappedBuffer.clear();
+          recvWrappedBuffer.limit(recvWrappedLength);
         }
 
-        // Prepare the recv buffer for reading.
-        recvWrappedBuffer.clear();
-        recvWrappedBuffer.limit(recvWrappedLength);
+        // Read the wrapped packet data.
+
+        // The channel read may only partially fill the buffer due to
+        // buffering in the underlying channel layer (e.g. SSL layer), so
+        // repeatedly read until the data has been read or we are sure
+        // that we are unable to proceed.
+        while (recvWrappedBuffer.hasRemaining())
+        {
+          final int read = channel.read(recvWrappedBuffer);
+          if (read <= 0)
+          {
+            // Not enough data available or end of stream.
+            return read;
+          }
+        }
+
+        // The complete packet has been read, so unwrap it.
+        recvWrappedBuffer.flip();
+        final byte[] unwrappedDataBytes = saslContext.unwrap(
+            recvWrappedBuffer.array(), 0, recvWrappedLength);
+        recvWrappedLength = -1;
+
+        // Only return the unwrapped data if it was non-empty, otherwise try to
+        // read another SASL packet.
+        if (unwrappedDataBytes.length > 0)
+        {
+          recvUnwrappedBuffer = ByteBuffer.wrap(unwrappedDataBytes);
+          return recvUnwrappedBuffer.remaining();
+        }
       }
-
-      // Read wrapped data.
-      final int read = channel.read(recvWrappedBuffer);
-      if (read <= 0)
-      {
-        // No data read or end of stream.
-        return read;
-      }
-
-      if (recvWrappedBuffer.hasRemaining())
-      {
-        // Unable to read the full packet, so no data available yet.
-        return 0;
-      }
-
-      // The complete packet has been read, so unwrap it.
-      recvWrappedBuffer.flip();
-      final byte[] unwrappedDataBytes = saslContext.unwrap(
-          recvWrappedBuffer.array(), 0, recvWrappedLength);
-      recvWrappedLength = -1;
-
-      if (recvUnwrappedBuffer.capacity() < unwrappedDataBytes.length)
-      {
-        // Resize the recv buffer (this shouldn't ever happen).
-        recvUnwrappedBuffer = ByteBuffer.allocate(unwrappedDataBytes.length);
-      }
-
-      recvUnwrappedBuffer.clear();
-      recvUnwrappedBuffer.put(unwrappedDataBytes);
-      recvUnwrappedBuffer.flip();
-
-      return recvUnwrappedBuffer.remaining();
     }
-
   }
 
 
@@ -326,8 +329,7 @@
     sendUnwrappedBufferSize = saslContext.getMaxRawSendBufferSize();
 
     recvWrappedBuffer = ByteBuffer.allocate(recvWrappedBufferMaximumSize);
-    recvUnwrappedBuffer = ByteBuffer.allocate(recvWrappedBufferMaximumSize);
-    recvUnwrappedBuffer.flip(); // Initially nothing has been received.
+    recvUnwrappedBuffer = ByteBuffer.allocate(0);
     sendUnwrappedBytes = new byte[sendUnwrappedBufferSize];
     sendWrappedBuffer = ByteBuffer.allocate(sendUnwrappedBufferSize + 64);
   }
diff --git a/opendj-sdk/opends/src/server/org/opends/server/extensions/TLSByteChannel.java b/opendj-sdk/opends/src/server/org/opends/server/extensions/TLSByteChannel.java
index 9914582..1f0cf6e 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/extensions/TLSByteChannel.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/extensions/TLSByteChannel.java
@@ -245,15 +245,17 @@
       // Synchronize SSL unwrap with channel reads.
       synchronized (unwrapLock)
       {
-        // Repeat if there is underflow or overflow.
+        // Read SSL packets until some unwrapped data is produced or no more
+        // data is available on the underlying channel.
         boolean needRead = true;
+        int read = 0;
         while (true)
         {
           // Read wrapped data if needed.
           if (needRead)
           {
             recvWrappedBuffer.compact(); // Prepare for append.
-            final int read = channel.read(recvWrappedBuffer);
+            read = channel.read(recvWrappedBuffer);
             recvWrappedBuffer.flip(); // Restore for read.
             if (read < 0)
             {
@@ -288,11 +290,11 @@
             break; // Retry unwrap.
           case BUFFER_UNDERFLOW:
             // Not enough data was read. This either means that the inbound
-            // buffer was too small, or not enough data is available.
+            // buffer was too small, or not enough data was read.
             final int newPktSize = sslEngine.getSession().getPacketBufferSize();
             if (newPktSize > recvWrappedBuffer.capacity())
             {
-              // Buffer needs resizing.
+              // Increase the buffer size and reread.
               final ByteBuffer newRecvWrappedBuffer = ByteBuffer
                   .allocate(newPktSize);
               newRecvWrappedBuffer.put(recvWrappedBuffer);
@@ -300,11 +302,18 @@
               recvWrappedBuffer = newRecvWrappedBuffer;
               break;
             }
-            else
+            else if (read == 0)
             {
               // Not enough data is available to read a complete SSL packet.
               return 0;
             }
+            else
+            {
+              // The channel read may only partially fill the buffer due to
+              // buffering in the underlying channel layer, so try reading again
+              // until we are sure that we are unable to proceed.
+              break;
+            }
           case CLOSED:
             // Peer sent SSL close notification.
             sslEngine.closeInbound();
@@ -320,7 +329,7 @@
               // No application data was read, but if we are handshaking then
               // try to continue.
               if (result.getHandshakeStatus() == HandshakeStatus.NEED_UNWRAP
-                  && !recvWrappedBuffer.hasRemaining())
+                  && !recvWrappedBuffer.hasRemaining() && read == 0)
               {
                 // Not enough data is available to continue handshake.
                 return 0;
@@ -331,11 +340,18 @@
                 doHandshake(true /* isReading */);
               }
             }
-            else
+            else if (read == 0)
             {
               // No data available and not handshaking.
               return 0;
             }
+            else
+            {
+              // The channel read may only partially fill the buffer due to
+              // buffering in the underlying channel layer, so try reading again
+              // until we are sure that we are unable to proceed.
+              break;
+            }
           }
         }
       }

--
Gitblit v1.10.0