mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

Matthew Swift
08.59.2012 88be99a38d4f02a6227ef5a2b514f77c6f28e524
Preparation work for OPENDJ-420: Rare SSLExceptions while handling LDAPS connections and big LDAP searches

Reformat and clean up code.
2 files modified
1522 ■■■■■ changed files
opends/src/server/org/opends/server/extensions/SASLByteChannel.java 708 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/extensions/TLSByteChannel.java 814 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/extensions/SASLByteChannel.java
@@ -28,354 +28,452 @@
package org.opends.server.extensions;
import java.nio.channels.ByteChannel;
import java.security.cert.Certificate;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ByteChannel;
import java.security.cert.Certificate;
import javax.security.sasl.Sasl;
import javax.security.sasl.SaslException;
import org.opends.server.api.ClientConnection;
/**
 * This class implements a SASL byte channel that can be used during
 * confidentiality and integrity.
 *
 */
public class
SASLByteChannel implements ByteChannel, ConnectionSecurityProvider {
public class SASLByteChannel implements ByteChannel, ConnectionSecurityProvider
{
    // The client connection associated with this provider.
    private ClientConnection connection;
  /**
   * Return a SASL byte channel instance created using the specified parameters.
   *
   * @param c
   *          A client connection associated with the instance.
   * @param name
   *          The name of the instance (SASL mechanism name).
   * @param context
   *          A SASL context associated with the instance.
   * @return A SASL byte channel.
   */
  public static SASLByteChannel getSASLByteChannel(final ClientConnection c,
      final String name, final SASLContext context)
  {
    return new SASLByteChannel(c, name, context);
  }
    // The SASL context associated with the provider
    private SASLContext saslContext;
    // The byte channel associated with this provider.
    private RedirectingByteChannel channel;
    // The number of bytes in the length buffer.
    private static final int lengthSize = 4;
  // The SASL context associated with the provider
  private SASLContext saslContext;
    //Length of the buffer.
    private int bufLength;
  // The byte channel associated with this provider.
  private final RedirectingByteChannel channel;
    // The SASL mechanism name.
    private String name;
  // The number of bytes in the length buffer.
  private static final int lengthSize = 4;
    //Buffers used in reading and decoding (unwrap)
    private ByteBuffer readBuffer, decodeBuffer;
  // Length of the buffer.
  private int bufLength;
    //How many bytes of the subsequent buffer is needed to complete a partially
    //read buffer.
    private int neededBytes = 0;
  // The SASL mechanism name.
  private final String name;
    //Used to not reset the buffer length size because the first 4 bytes of a
    //buffer are not size bytes.
    private boolean reading = false;
  // Buffers used in reading and decoding (unwrap)
  private final ByteBuffer readBuffer, decodeBuffer;
    /**
     * Create a SASL byte channel with the specified parameters
     * that is capable of processing a confidentiality/integrity SASL
     * connection.
     *
     * @param connection
     *          The client connection to read/write the bytes.
     * @param name
     *          The SASL mechanism name.
     * @param saslContext
     *          The SASL context to process the data through.
     */
    private SASLByteChannel(ClientConnection connection, String name,
        SASLContext saslContext) {
      this.connection = connection;
      this.name = name;
      this.saslContext = saslContext;
      this.channel = connection.getChannel();
      this.readBuffer = ByteBuffer.allocate(connection.getAppBufferSize());
      this.decodeBuffer =
                ByteBuffer.allocate(connection.getAppBufferSize() + lengthSize);
  // How many bytes of the subsequent buffer is needed to complete a partially
  // read buffer.
  private int neededBytes = 0;
  // Used to not reset the buffer length size because the first 4 bytes of a
  // buffer are not size bytes.
  private boolean reading = false;
  /**
   * Create a SASL byte channel with the specified parameters that is capable of
   * processing a confidentiality/integrity SASL connection.
   *
   * @param connection
   *          The client connection to read/write the bytes.
   * @param name
   *          The SASL mechanism name.
   * @param saslContext
   *          The SASL context to process the data through.
   */
  private SASLByteChannel(final ClientConnection connection, final String name,
      final SASLContext saslContext)
  {
    this.name = name;
    this.saslContext = saslContext;
    this.channel = connection.getChannel();
    this.readBuffer = ByteBuffer.allocate(connection.getAppBufferSize());
    this.decodeBuffer = ByteBuffer.allocate(connection.getAppBufferSize()
        + lengthSize);
  }
  /**
   * {@inheritDoc}
   */
  @Override
  public synchronized void close() throws IOException
  {
    saslContext.dispose();
    saslContext = null;
  }
  /**
   * {@inheritDoc}
   */
  @Override
  public int getAppBufSize()
  {
    return saslContext.getBufSize(Sasl.MAX_BUFFER);
  }
  /**
   * {@inheritDoc}
   */
  @Override
  public Certificate[] getClientCertificateChain()
  {
    return new Certificate[0];
  }
  /**
   * {@inheritDoc}
   */
  @Override
  public String getName()
  {
    return name;
  }
  /**
   * {@inheritDoc}
   */
  @Override
  public int getSSF()
  {
    return saslContext.getSSF();
  }
  /**
   * {@inheritDoc}
   */
  @Override
  public boolean isOpen()
  {
    return saslContext != null;
  }
  /**
   * {@inheritDoc}
   */
  @Override
  public boolean isSecure()
  {
    return true;
  }
  /**
   * {@inheritDoc}
   */
  @Override
  public synchronized int read(final ByteBuffer clearDst) throws IOException
  {
    int bytesToRead = lengthSize;
    if (reading)
    {
      bytesToRead = neededBytes;
    }
    /**
     * Return a SASL byte channel instance created using the specified
     * parameters.
     *
     * @param c A client connection associated with the instance.
     * @param name The name of the instance (SASL mechanism name).
     * @param context A SASL context associated with the instance.
     * @return A SASL byte channel.
     */
    public static SASLByteChannel
    getSASLByteChannel(ClientConnection c, String name,
                          SASLContext context) {
          return new SASLByteChannel(c, name, context);
    final int readResult = readAll(readBuffer, bytesToRead);
    if (readResult == -1)
    {
      return -1;
    }
    /**
     * Finish processing a previous, partially read buffer using some, or, all
     * of the bytes of the current buffer.
     *
     */
    private int processPartial(int readResult, ByteBuffer clearDst)
    throws IOException {
    // The previous buffer read was not complete, the current
    // buffer completes it.
    if (neededBytes > 0 && readResult > 0)
    {
      return (processPartial(readResult, clearDst));
    }
    if (readResult == 0 && !reading)
    {
      return 0;
    }
    if (!reading)
    {
      bufLength = getBufLength(readBuffer);
    }
    reading = false;
    // The buffer length is greater than what is there, save what is there,
    // figure out how much more is needed and return.
    if (bufLength > readBuffer.position())
    {
      neededBytes = bufLength - readBuffer.position() + lengthSize;
      readBuffer.flip();
      //Use all of the bytes of the current buffer and read some more.
      if(neededBytes > readResult) {
        neededBytes -= readResult;
        decodeBuffer.put(readBuffer);
        readBuffer.clear();
        reading = false;
        return 0;
      }
      //Use a portion of the current buffer.
      for(;neededBytes > 0;neededBytes--) {
        decodeBuffer.put(readBuffer.get());
      }
      //Unwrap the now completed buffer.
      byte[] inBytes = decodeBuffer.array();
      byte[]clearBytes = saslContext.unwrap(inBytes, lengthSize, bufLength);
      clearDst.put(clearBytes);
      decodeBuffer.put(readBuffer);
      readBuffer.clear();
      return 0;
    }
    else
    {
      readBuffer.flip();
      decodeBuffer.put(readBuffer);
      final byte[] inBytes = decodeBuffer.array();
      final byte[] clearBytes = saslContext.unwrap(inBytes, lengthSize,
          bufLength);
      decodeBuffer.clear();
      readBuffer.compact();
      //If the read buffer has bytes, these are a new buffer. Reset the
      //buffer length to the new value.
      if(readBuffer.position() != 0) {
        bufLength = getBufLength(readBuffer);
        reading = true;
      } else
        reading=false;
      clearDst.put(clearBytes);
      readBuffer.clear();
      return clearDst.position();
    }
  }
    /**
     * Read from the socket channel into the specified byte buffer at least
     * the number of bytes specified in the total parameter.
     *
     * @param byteBuf
     *          The byte buffer to put the bytes in.
     * @param total
     *          The total number of bytes to read from the socket
     *          channel.
     * @return The number of bytes read, 0 or -1.
     * @throws IOException
     *           If an error occurred reading the socket channel.
     */
    private int readAll(ByteBuffer byteBuf, int total) throws IOException
    {
      while (channel.isOpen() && total > 0) {
        int count = channel.read(byteBuf);
        if (count == -1) return -1;
        if (count == 0) return 0;
        total -= count;
      }
      if (total > 0)
        return -1;
      else
        return byteBuf.position();
    }
    /**
     * Return the clear buffer length as determined by processing the
     * first 4 bytes of the specified buffer.
     *
     * @param byteBuf
     *          The buffer to examine the first 4 bytes of.
     * @return The size of the clear buffer.
     */
    private int getBufLength(ByteBuffer byteBuf)
    {
      int answer = 0;
      for (int i = 0; i < lengthSize; i++)
      {
        byte b = byteBuf.get(i);
        answer <<= 8;
        answer |= ((int) b & 0xff);
      }
      return answer;
    }
    /**
     * {@inheritDoc}
     */
  /**
   * {@inheritDoc}
   */
  @Override
    public synchronized int read(ByteBuffer clearDst) throws IOException {
      int bytesToRead = lengthSize;
      if(reading)
        bytesToRead = neededBytes;
      int readResult = readAll(readBuffer, bytesToRead);
      if (readResult == -1)
        return -1;
      //The previous buffer read was not complete, the current
      //buffer completes it.
      if(neededBytes > 0 && readResult > 0)
          return(processPartial(readResult, clearDst));
      if(readResult == 0 && !reading) return 0;
      if(!reading) {
        bufLength = getBufLength(readBuffer);
  public ByteChannel wrapChannel(final ByteChannel channel)
  {
    return this;
  }
  /**
   * {@inheritDoc}
   */
  @Override
  public synchronized int write(final ByteBuffer clearSrc) throws IOException
  {
    final int sendBufSize = getAppBufSize();
    final int srcLen = clearSrc.remaining();
    final ByteBuffer sendBuffer = ByteBuffer.allocate(sendBufSize);
    if (srcLen > sendBufSize)
    {
      final int oldPos = clearSrc.position();
      int curPos = oldPos;
      int curLimit = oldPos + sendBufSize;
      while (curPos < srcLen)
      {
        clearSrc.position(curPos);
        clearSrc.limit(curLimit);
        sendBuffer.put(clearSrc);
        writeChannel(wrap(sendBuffer.array(), clearSrc.remaining()));
        curPos = curLimit;
        curLimit = Math.min(srcLen, curPos + sendBufSize);
      }
      reading=false;
      //The buffer length is greater than what is there, save what is there,
      //figure out how much more is needed and return.
      if(bufLength > readBuffer.position()) {
        neededBytes = bufLength - readBuffer.position() + lengthSize;
        readBuffer.flip();
        decodeBuffer.put(readBuffer);
        readBuffer.clear();
      return srcLen;
    }
    else
    {
      sendBuffer.put(clearSrc);
      return writeChannel(wrap(sendBuffer.array(), srcLen));
    }
  }
  /**
   * Return the clear buffer length as determined by processing the first 4
   * bytes of the specified buffer.
   *
   * @param byteBuf
   *          The buffer to examine the first 4 bytes of.
   * @return The size of the clear buffer.
   */
  private int getBufLength(final ByteBuffer byteBuf)
  {
    int answer = 0;
    for (int i = 0; i < lengthSize; i++)
    {
      final byte b = byteBuf.get(i);
      answer <<= 8;
      answer |= (b & 0xff);
    }
    return answer;
  }
  /**
   * Finish processing a previous, partially read buffer using some, or, all of
   * the bytes of the current buffer.
   */
  private int processPartial(final int readResult, final ByteBuffer clearDst)
      throws IOException
  {
    readBuffer.flip();
    // Use all of the bytes of the current buffer and read some more.
    if (neededBytes > readResult)
    {
      neededBytes -= readResult;
      decodeBuffer.put(readBuffer);
      readBuffer.clear();
      reading = false;
      return 0;
    }
    // Use a portion of the current buffer.
    for (; neededBytes > 0; neededBytes--)
    {
      decodeBuffer.put(readBuffer.get());
    }
    // Unwrap the now completed buffer.
    final byte[] inBytes = decodeBuffer.array();
    final byte[] clearBytes = saslContext
        .unwrap(inBytes, lengthSize, bufLength);
    clearDst.put(clearBytes);
    decodeBuffer.clear();
    readBuffer.compact();
    // If the read buffer has bytes, these are a new buffer. Reset the
    // buffer length to the new value.
    if (readBuffer.position() != 0)
    {
      bufLength = getBufLength(readBuffer);
      reading = true;
    }
    else
    {
      reading = false;
    }
    return clearDst.position();
  }
  /**
   * Read from the socket channel into the specified byte buffer at least the
   * number of bytes specified in the total parameter.
   *
   * @param byteBuf
   *          The byte buffer to put the bytes in.
   * @param total
   *          The total number of bytes to read from the socket channel.
   * @return The number of bytes read, 0 or -1.
   * @throws IOException
   *           If an error occurred reading the socket channel.
   */
  private int readAll(final ByteBuffer byteBuf, int total) throws IOException
  {
    while (channel.isOpen() && total > 0)
    {
      final int count = channel.read(byteBuf);
      if (count == -1)
      {
        return -1;
      }
      if (count == 0)
      {
        return 0;
      } else {
        readBuffer.flip();
        decodeBuffer.put(readBuffer);
        byte[] inBytes = decodeBuffer.array();
        byte[]clearBytes = saslContext.unwrap(inBytes, lengthSize, bufLength);
        decodeBuffer.clear();
        clearDst.put(clearBytes);
        readBuffer.clear();
      }
      return clearDst.position();
      total -= count;
    }
    /**
     * Writes the specified len parameter into the buffer in a form that
     * can be sent over a network to the client.
     *
     * @param buf
     *          The buffer to hold the length bytes.
     * @param len
     *          The length to encode.
     */
    private void writeBufLen(byte[] buf, int len)
    if (total > 0)
    {
      for (int i = 3; i >= 0; i--)
      {
        buf[i] = (byte) (len & 0xff);
        len >>>= 8;
      }
      return -1;
    }
    /**
     * Creates a buffer suitable to send to the client using the
     * specified clear byte array  and length of the bytes to
     * wrap.
     *
     * @param clearBytes
     *          The clear byte array to send to the client.
     * @param len
     *          The length of the bytes to wrap in the byte array.
     * @throws SaslException
     *           If the wrap of the bytes fails.
     */
    private ByteBuffer wrap(byte[] clearBytes, int len) throws IOException {
      byte[] wrapBytes = saslContext.wrap(clearBytes, 0, len);
      byte[] outBytes = new byte[wrapBytes.length + lengthSize];
      writeBufLen(outBytes, wrapBytes.length);
      System.arraycopy(wrapBytes, 0, outBytes, lengthSize, wrapBytes.length);
      return ByteBuffer.wrap(outBytes);
    else
    {
      return byteBuf.position();
    }
  }
    /**
     * {@inheritDoc}
     */
  @Override
    public synchronized int write(ByteBuffer clearSrc) throws IOException {
        int sendBufSize = getAppBufSize();
        int srcLen = clearSrc.remaining();
        ByteBuffer sendBuffer = ByteBuffer.allocate(sendBufSize);
        if (srcLen > sendBufSize) {
            int oldPos = clearSrc.position();
            int curPos = oldPos;
            int curLimit = oldPos + sendBufSize;
            while (curPos < srcLen) {
                clearSrc.position(curPos);
                clearSrc.limit(curLimit);
                sendBuffer.put(clearSrc);
                writeChannel(wrap(sendBuffer.array(), clearSrc.remaining()));
                curPos = curLimit;
                curLimit = Math.min(srcLen, curPos + sendBufSize);
            }
            return srcLen;
        } else {
            sendBuffer.put(clearSrc);
            return writeChannel(wrap(sendBuffer.array() ,srcLen));
        }
  /**
   * Creates a buffer suitable to send to the client using the specified clear
   * byte array and length of the bytes to wrap.
   *
   * @param clearBytes
   *          The clear byte array to send to the client.
   * @param len
   *          The length of the bytes to wrap in the byte array.
   * @throws SaslException
   *           If the wrap of the bytes fails.
   */
  private ByteBuffer wrap(final byte[] clearBytes, final int len)
      throws SaslException
  {
    final byte[] wrapBytes = saslContext.wrap(clearBytes, 0, len);
    final byte[] outBytes = new byte[wrapBytes.length + lengthSize];
    writeBufLen(outBytes, wrapBytes.length);
    System.arraycopy(wrapBytes, 0, outBytes, lengthSize, wrapBytes.length);
    return ByteBuffer.wrap(outBytes);
  }
  /**
   * Writes the specified len parameter into the buffer in a form that can be
   * sent over a network to the client.
   *
   * @param buf
   *          The buffer to hold the length bytes.
   * @param len
   *          The length to encode.
   */
  private void writeBufLen(final byte[] buf, int len)
  {
    for (int i = 3; i >= 0; i--)
    {
      buf[i] = (byte) (len & 0xff);
      len >>>= 8;
    }
  }
    /**
     * Write the specified byte buffer to the socket channel.
     *
     * @param buffer
     *          The byte buffer to write to the socket channel.
     * @return {@code true} if the byte buffer was successfully written
     *         to the socket channel, or, {@code false} if not.
     */
    private int writeChannel(ByteBuffer buffer) throws IOException {
        return channel.write(buffer);
      }
    /**
     * {@inheritDoc}
     */
  @Override
    public synchronized void close() throws IOException {
        saslContext.dispose();
        saslContext=null;
    }
    /**
     * {@inheritDoc}
     */
  @Override
    public boolean isOpen() {
        return saslContext != null;
    }
    /**
     * {@inheritDoc}
     */
  @Override
    public int getAppBufSize() {
        return saslContext.getBufSize(Sasl.MAX_BUFFER);
    }
    /**
     * {@inheritDoc}
     */
  @Override
    public Certificate[] getClientCertificateChain() {
        return new Certificate[0];
    }
    /**
     * {@inheritDoc}
     */
  @Override
    public int getSSF() {
        return saslContext.getSSF();
    }
    /**
     * {@inheritDoc}
     */
  @Override
    public ByteChannel wrapChannel(ByteChannel channel) {
        return this;
    }
    /**
     * {@inheritDoc}
     */
  @Override
    public String getName() {
        return name;
    }
    /**
     * {@inheritDoc}
     */
  @Override
    public boolean isSecure() {
        return true;
    }
  /**
   * Write the specified byte buffer to the socket channel.
   *
   * @param buffer
   *          The byte buffer to write to the socket channel.
   * @return {@code true} if the byte buffer was successfully written to the
   *         socket channel, or, {@code false} if not.
   */
  private int writeChannel(final ByteBuffer buffer) throws IOException
  {
    return channel.write(buffer);
  }
}
opends/src/server/org/opends/server/extensions/TLSByteChannel.java
@@ -23,9 +23,12 @@
 *
 *
 *      Copyright 2008-2009 Sun Microsystems, Inc.
 *      Portions copyright 2012 ForgeRock AS.
 */
package org.opends.server.extensions;
import static org.opends.server.loggers.debug.DebugLogger.debugEnabled;
import static org.opends.server.loggers.debug.DebugLogger.getTracer;
@@ -45,359 +48,506 @@
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.types.DebugLogLevel;
/**
 * A class that provides a TLS byte channel implementation.
 *
 */
public class TLSByteChannel implements
                                ByteChannel, ConnectionSecurityProvider {
public class TLSByteChannel implements ByteChannel, ConnectionSecurityProvider
{
  private static final DebugTracer TRACER = getTracer();
    private static final DebugTracer TRACER = getTracer();
  private final ByteChannel socketChannel;
    private final ClientConnection connection;
    private final ByteChannel socketChannel;
  private final SSLEngine sslEngine;
    private final SSLEngine sslEngine;
  // read copy to buffer
  private final ByteBuffer appData;
    //read copy to buffer
    private final ByteBuffer appData;
    //read encrypted
    private final ByteBuffer appNetData;
  // read encrypted
  private final ByteBuffer appNetData;
    //Write encrypted
    private final ByteBuffer netData, tempData;
    private final int sslBufferSize, appBufSize;
    private boolean reading = false;
  // Write encrypted
  private final ByteBuffer netData;
  private final ByteBuffer tempData;
    //Map of cipher phrases to effective key size (bits). Taken from the
    //following RFCs: 5289, 4346, 3268,4132 and 4162.
    private static final Map<String, Integer> cipherMap;
  private final int sslBufferSize;
  private final int appBufSize;
    static {
        cipherMap = new LinkedHashMap<String, Integer>();
        cipherMap.put("_WITH_AES_256_CBC_", new Integer(256));
        cipherMap.put("_WITH_CAMELLIA_256_CBC_", new Integer(256));
        cipherMap.put("_WITH_AES_256_GCM_", new Integer(256));
        cipherMap.put("_WITH_3DES_EDE_CBC_", new Integer(112));
        cipherMap.put("_WITH_AES_128_GCM_", new Integer(128));
        cipherMap.put("_WITH_SEED_CBC_", new Integer(128));
        cipherMap.put("_WITH_CAMELLIA_128_CBC_", new Integer(128));
        cipherMap.put("_WITH_AES_128_CBC_", new Integer(128));
        cipherMap.put("_WITH_IDEA_CBC_", new Integer(128));
        cipherMap.put("_WITH_DES_CBC_", new Integer(56));
        cipherMap.put("_WITH_RC2_CBC_40_", new Integer(40));
        cipherMap.put("_WITH_RC4_40_", new Integer(40));
        cipherMap.put("_WITH_DES40_CBC_", new Integer(40));
        cipherMap.put("_WITH_NULL_", new Integer(0));
    };
  private boolean reading = false;
    private TLSByteChannel(LDAPConnectionHandlerCfg config, ClientConnection c,
        ByteChannel socketChannel, SSLContext sslContext)  {
  // Map of cipher phrases to effective key size (bits). Taken from the
  // following RFCs: 5289, 4346, 3268,4132 and 4162.
  private static final Map<String, Integer> cipherMap;
  static
  {
    cipherMap = new LinkedHashMap<String, Integer>();
    cipherMap.put("_WITH_AES_256_CBC_", new Integer(256));
    cipherMap.put("_WITH_CAMELLIA_256_CBC_", new Integer(256));
    cipherMap.put("_WITH_AES_256_GCM_", new Integer(256));
    cipherMap.put("_WITH_3DES_EDE_CBC_", new Integer(112));
    cipherMap.put("_WITH_AES_128_GCM_", new Integer(128));
    cipherMap.put("_WITH_SEED_CBC_", new Integer(128));
    cipherMap.put("_WITH_CAMELLIA_128_CBC_", new Integer(128));
    cipherMap.put("_WITH_AES_128_CBC_", new Integer(128));
    cipherMap.put("_WITH_IDEA_CBC_", new Integer(128));
    cipherMap.put("_WITH_DES_CBC_", new Integer(56));
    cipherMap.put("_WITH_RC2_CBC_40_", new Integer(40));
    cipherMap.put("_WITH_RC4_40_", new Integer(40));
    cipherMap.put("_WITH_DES40_CBC_", new Integer(40));
    cipherMap.put("_WITH_NULL_", new Integer(0));
  };
        this.socketChannel = socketChannel;
        this.connection = c;
      // getHostName could potentially be very expensive and could block
      // the connection handler for several minutes. (See issue 4229)
      // Accepting new connections should be done in a seperate thread to
      // avoid blocking new connections. Just remove for now to prevent
      // potential DoS attacks. SSL sessions will not be reused and some
      // cipher suites (such as Kerberos) will not work.
      //String hostName = socketChannel.socket().getInetAddress().getHostName();
      //int port = socketChannel.socket().getPort();
      //sslEngine = sslContext.createSSLEngine(hostName, port);
        sslEngine = sslContext.createSSLEngine();
        sslEngine.setUseClientMode(false);
        Set<String> protocols = config.getSSLProtocol();
        if (!protocols.isEmpty())
            sslEngine.setEnabledProtocols(protocols.toArray(new String[0]));
        Set<String> ciphers = config.getSSLCipherSuite();
        if (!ciphers.isEmpty())
            sslEngine.setEnabledCipherSuites(ciphers.toArray(new String[0]));
        switch (config.getSSLClientAuthPolicy()) {
        case DISABLED:
            sslEngine.setNeedClientAuth(false);
            sslEngine.setWantClientAuth(false);
            break;
        case REQUIRED:
            sslEngine.setWantClientAuth(true);
            sslEngine.setNeedClientAuth(true);
            break;
        case OPTIONAL:
        default:
            sslEngine.setNeedClientAuth(false);
            sslEngine.setWantClientAuth(true);
            break;
  /**
   * Create an TLS byte channel instance using the specified LDAP connection
   * configuration, client connection, SSL context and socket channel
   * parameters.
   *
   * @param config
   *          The LDAP connection configuration.
   * @param c
   *          The client connection.
   * @param sslContext
   *          The SSL context.
   * @param socketChannel
   *          The socket channel.
   * @return A TLS capable byte channel.
   */
  public static TLSByteChannel getTLSByteChannel(
      final LDAPConnectionHandlerCfg config, final ClientConnection c,
      final SSLContext sslContext, final ByteChannel socketChannel)
  {
    return new TLSByteChannel(config, c, socketChannel, sslContext);
  }
  private TLSByteChannel(final LDAPConnectionHandlerCfg config,
      final ClientConnection c, final ByteChannel socketChannel,
      final SSLContext sslContext)
  {
    this.socketChannel = socketChannel;
    // getHostName could potentially be very expensive and could block
    // the connection handler for several minutes. (See issue 4229)
    // Accepting new connections should be done in a seperate thread to
    // avoid blocking new connections. Just remove for now to prevent
    // potential DoS attacks. SSL sessions will not be reused and some
    // cipher suites (such as Kerberos) will not work.
    // String hostName = socketChannel.socket().getInetAddress().getHostName();
    // int port = socketChannel.socket().getPort();
    // sslEngine = sslContext.createSSLEngine(hostName, port);
    sslEngine = sslContext.createSSLEngine();
    sslEngine.setUseClientMode(false);
    final Set<String> protocols = config.getSSLProtocol();
    if (!protocols.isEmpty())
    {
      sslEngine.setEnabledProtocols(protocols.toArray(new String[0]));
    }
    final Set<String> ciphers = config.getSSLCipherSuite();
    if (!ciphers.isEmpty())
    {
      sslEngine.setEnabledCipherSuites(ciphers.toArray(new String[0]));
    }
    switch (config.getSSLClientAuthPolicy())
    {
    case DISABLED:
      sslEngine.setNeedClientAuth(false);
      sslEngine.setWantClientAuth(false);
      break;
    case REQUIRED:
      sslEngine.setWantClientAuth(true);
      sslEngine.setNeedClientAuth(true);
      break;
    case OPTIONAL:
    default:
      sslEngine.setNeedClientAuth(false);
      sslEngine.setWantClientAuth(true);
      break;
    }
    final SSLSession sslSession = sslEngine.getSession();
    sslBufferSize = sslSession.getPacketBufferSize();
    appBufSize = sslSession.getApplicationBufferSize();
    appNetData = ByteBuffer.allocate(sslBufferSize);
    netData = ByteBuffer.allocate(sslBufferSize);
    appData = ByteBuffer.allocate(sslSession.getApplicationBufferSize());
    tempData = ByteBuffer.allocate(sslSession.getApplicationBufferSize());
  }
  /**
   * {@inheritDoc}
   */
  public synchronized void close() throws IOException
  {
    sslEngine.closeInbound();
    sslEngine.closeOutbound();
    final SSLEngineResult.HandshakeStatus hsStatus = sslEngine
        .getHandshakeStatus();
    if (hsStatus != SSLEngineResult.HandshakeStatus.FINISHED
        && hsStatus != SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING)
    {
      doHandshakeWrite(hsStatus);
    }
  }
  /**
   * {@inheritDoc}
   */
  public int getAppBufSize()
  {
    return appBufSize;
  }
  /**
   * {@inheritDoc}
   */
  public Certificate[] getClientCertificateChain()
  {
    try
    {
      return sslEngine.getSession().getPeerCertificates();
    }
    catch (final SSLPeerUnverifiedException e)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
      return new Certificate[0];
    }
  }
  /**
   * {@inheritDoc}
   */
  public String getName()
  {
    return "TLS";
  }
  /**
   * {@inheritDoc}
   */
  public int getSSF()
  {
    int cipherKeySSF = 0;
    final String cipherString = sslEngine.getSession().getCipherSuite();
    for (final Map.Entry<String, Integer> mapEntry : cipherMap.entrySet())
    {
      if (cipherString.indexOf(mapEntry.getKey()) >= 0)
      {
        cipherKeySSF = mapEntry.getValue().intValue();
        break;
      }
    }
    return cipherKeySSF;
  }
  /**
   * {@inheritDoc}
   */
  public boolean isOpen()
  {
    if (sslEngine.isInboundDone() || sslEngine.isOutboundDone())
    {
      return false;
    }
    return true;
  }
  /**
   * {@inheritDoc}
   */
  public boolean isSecure()
  {
    return true;
  }
  /**
   * {@inheritDoc}
   */
  public synchronized int read(final ByteBuffer clearBuffer) throws IOException
  {
    SSLEngineResult.HandshakeStatus hsStatus;
    if (!reading)
    {
      appNetData.clear();
    }
    else
    {
      reading = false;
    }
    if (!socketChannel.isOpen())
    {
      return -1;
    }
    if (sslEngine.isInboundDone())
    {
      return -1;
    }
    do
    {
      final int wrappedBytes = socketChannel.read(appNetData);
      appNetData.flip();
      if (wrappedBytes == -1)
      {
        return -1;
      }
      hsStatus = sslEngine.getHandshakeStatus();
      if (hsStatus == SSLEngineResult.HandshakeStatus.NEED_TASK
          || hsStatus == SSLEngineResult.HandshakeStatus.NEED_WRAP)
      {
        doHandshakeRead(hsStatus);
      }
      if (wrappedBytes == 0)
      {
        return 0;
      }
      while (appNetData.hasRemaining())
      {
        appData.clear();
        final SSLEngineResult res = sslEngine.unwrap(appNetData, appData);
        appData.flip();
        if (res.getStatus() == SSLEngineResult.Status.BUFFER_UNDERFLOW)
        {
          appNetData.compact();
          reading = true;
          break;
        }
        SSLSession sslSession = sslEngine.getSession();
        sslBufferSize = sslSession.getPacketBufferSize();
        appBufSize = sslSession.getApplicationBufferSize();
        appNetData = ByteBuffer.allocate(sslBufferSize);
        netData = ByteBuffer.allocate(sslBufferSize);
        appData = ByteBuffer.allocate(sslSession.getApplicationBufferSize());
        tempData = ByteBuffer.allocate(sslSession.getApplicationBufferSize());
    }
    /**
     * {@inheritDoc}
     */
    public int getAppBufSize() {
        return appBufSize;
    }
    /**
     * Create an TLS byte channel instance using the specified LDAP connection
     * configuration, client connection, SSL context and socket channel
     * parameters.
     *
     * @param config The LDAP connection configuration.
     * @param c The client connection.
     * @param sslContext The SSL context.
     * @param socketChannel The socket channel.
     * @return A TLS capable byte channel.
     */
    public static TLSByteChannel
    getTLSByteChannel(LDAPConnectionHandlerCfg config, ClientConnection c,
                        SSLContext sslContext, ByteChannel socketChannel) {
        return new TLSByteChannel(config, c, socketChannel, sslContext);
    }
    private SSLEngineResult.HandshakeStatus doTasks() {
        Runnable task;
        while ((task = sslEngine.getDelegatedTask()) != null)
            task.run();
        return sslEngine.getHandshakeStatus();
    }
    private void doHandshakeRead(SSLEngineResult.HandshakeStatus hsStatus)
    throws IOException {
        do {
            doHandshakeOp(hsStatus);
            hsStatus = sslEngine.getHandshakeStatus();
        } while (hsStatus == SSLEngineResult.HandshakeStatus.NEED_WRAP ||
                hsStatus == SSLEngineResult.HandshakeStatus.NEED_TASK);
    }
    private void  doHandshakeOp(SSLEngineResult.HandshakeStatus hsStatus)
    throws IOException {
        SSLEngineResult res;
        switch (hsStatus) {
        case NEED_TASK:
            hsStatus = doTasks();
            break;
        case NEED_WRAP:
            tempData.clear();
            netData.clear();
            res = sslEngine.wrap(tempData, netData);
            hsStatus = res.getHandshakeStatus();
            netData.flip();
            while(netData.hasRemaining()) {
                socketChannel.write(netData);
            }
            hsStatus = sslEngine.getHandshakeStatus();
            return;
        default:
            return;
        else if (res.getStatus() != SSLEngineResult.Status.OK)
        {
          return -1;
        }
    }
    /**
     * {@inheritDoc}
     */
    public synchronized int read(ByteBuffer clearBuffer) throws IOException {
        SSLEngineResult.HandshakeStatus hsStatus;
        if(!reading)
          appNetData.clear();
        else
          reading = false;
        if(!socketChannel.isOpen())
            return -1;
        if(sslEngine.isInboundDone())
            return -1;
        do {
            int wrappedBytes = socketChannel.read(appNetData);
            appNetData.flip();
            if(wrappedBytes == -1) {
                return -1;
            }
            hsStatus = sslEngine.getHandshakeStatus();
            if (hsStatus == SSLEngineResult.HandshakeStatus.NEED_TASK ||
                    hsStatus == SSLEngineResult.HandshakeStatus.NEED_WRAP)
                doHandshakeRead(hsStatus);
            if(wrappedBytes == 0)
              return 0;
            while (appNetData.hasRemaining()) {
                appData.clear();
                SSLEngineResult res = sslEngine.unwrap(appNetData, appData);
                appData.flip();
                if(res.getStatus() == SSLEngineResult.Status.BUFFER_UNDERFLOW) {
                  appNetData.compact();
                  reading = true;
                  break;
                } else  if(res.getStatus() != SSLEngineResult.Status.OK)
                    return -1;
                hsStatus = sslEngine.getHandshakeStatus();
                if (hsStatus == SSLEngineResult.HandshakeStatus.NEED_TASK ||
                        hsStatus == SSLEngineResult.HandshakeStatus.NEED_WRAP)
                    doHandshakeOp(hsStatus);
                clearBuffer.put(appData);
            }
            hsStatus = sslEngine.getHandshakeStatus();
        } while (hsStatus == SSLEngineResult.HandshakeStatus.NEED_TASK ||
                 hsStatus == SSLEngineResult.HandshakeStatus.NEED_WRAP );
        return clearBuffer.position();
    }
    /**
     * {@inheritDoc}
     */
    public synchronized void close() throws IOException {
        sslEngine.closeInbound();
        sslEngine.closeOutbound();
        SSLEngineResult.HandshakeStatus hsStatus =
                                      sslEngine.getHandshakeStatus();
        if(hsStatus != SSLEngineResult.HandshakeStatus.FINISHED &&
           hsStatus !=  SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING)
            doHandshakeWrite(hsStatus);
    }
    /**
     * {@inheritDoc}
     */
    public boolean isOpen() {
        if(sslEngine.isInboundDone() || sslEngine.isOutboundDone())
          return false;
        return true;
    }
    /**
     * {@inheritDoc}
     */
    public int getSSF() {
        int cipherKeySSF = 0;
        String cipherString = sslEngine.getSession().getCipherSuite();
        for(Map.Entry<String, Integer> mapEntry : cipherMap.entrySet()) {
            if(cipherString.indexOf(mapEntry.getKey()) >= 0) {
                cipherKeySSF = mapEntry.getValue().intValue();
                break;
            }
        }
        return cipherKeySSF;
    }
    /**
     * {@inheritDoc}
     */
    public  Certificate[] getClientCertificateChain() {
        try {
          return sslEngine.getSession().getPeerCertificates();
        }
        catch (SSLPeerUnverifiedException e) {
          if (debugEnabled()) {
            TRACER.debugCaught(DebugLogLevel.ERROR, e);
          }
          return new Certificate[0];
        }
    }
    private void doHandshakeUnwrap() throws IOException {
        netData.clear();
        tempData.clear();
        int bytesRead = socketChannel.read(netData);
        if (bytesRead <= 0)
            throw new ClosedChannelException();
         else
          sslEngine.unwrap(netData, tempData);
    }
    private void
    doHandshakeWrite(SSLEngineResult.HandshakeStatus hsStatus)
    throws IOException {
        do {
            if(hsStatus == SSLEngineResult.HandshakeStatus.NEED_UNWRAP) {
                doHandshakeUnwrap();
            } else
               doHandshakeOp(hsStatus);
            hsStatus = sslEngine.getHandshakeStatus();
        } while (hsStatus == SSLEngineResult.HandshakeStatus.NEED_WRAP ||
                hsStatus == SSLEngineResult.HandshakeStatus.NEED_TASK ||
                hsStatus == SSLEngineResult.HandshakeStatus.NEED_UNWRAP);
    }
    /**
     * {@inheritDoc}
     */
    public synchronized int write(ByteBuffer clearData) throws IOException {
        if(!socketChannel.isOpen() || sslEngine.isOutboundDone()) {
            throw new ClosedChannelException();
        }
        int originalPosition = clearData.position();
        int originalLimit = clearData.limit();
        int length = originalLimit - originalPosition;
        if (length > sslBufferSize) {
            int pos = originalPosition;
            int lim = originalPosition + sslBufferSize;
            while (pos < originalLimit) {
                clearData.position(pos);
                clearData.limit(lim);
                writeInternal(clearData);
                pos = lim;
                lim = Math.min(originalLimit, pos + sslBufferSize);
            }
            return length;
        }  else {
            return writeInternal(clearData);
        }
    }
    private int writeInternal(ByteBuffer clearData) throws IOException {
        int totBytesSent = 0;
        SSLEngineResult.HandshakeStatus hsStatus;
        hsStatus = sslEngine.getHandshakeStatus();
        if (hsStatus == SSLEngineResult.HandshakeStatus.NEED_TASK ||
                hsStatus == SSLEngineResult.HandshakeStatus.NEED_WRAP ||
                hsStatus == SSLEngineResult.HandshakeStatus.NEED_UNWRAP)
            doHandshakeWrite(hsStatus);
        while(clearData.hasRemaining()) {
            netData.clear();
            SSLEngineResult res = sslEngine.wrap(clearData, netData);
            netData.flip();
            if(netData.remaining() == 0)
            {
              // wrap didn't produce any data from our clear buffer.
              // Throw exception to prevent looping.
              throw new SSLException("SSLEngine.wrap produced 0 bytes");
            }
            if(res.getStatus() != SSLEngineResult.Status.OK)
                throw new ClosedChannelException();
            if (hsStatus == SSLEngineResult.HandshakeStatus.NEED_TASK ||
                    hsStatus == SSLEngineResult.HandshakeStatus.NEED_WRAP ||
                    hsStatus == SSLEngineResult.HandshakeStatus.NEED_UNWRAP)
                doHandshakeWrite(hsStatus);
            totBytesSent += socketChannel.write(netData);
        if (hsStatus == SSLEngineResult.HandshakeStatus.NEED_TASK
            || hsStatus == SSLEngineResult.HandshakeStatus.NEED_WRAP)
        {
          doHandshakeOp(hsStatus);
        }
        return totBytesSent;
        clearBuffer.put(appData);
      }
      hsStatus = sslEngine.getHandshakeStatus();
    }
    while (hsStatus == SSLEngineResult.HandshakeStatus.NEED_TASK
        || hsStatus == SSLEngineResult.HandshakeStatus.NEED_WRAP);
    return clearBuffer.position();
  }
  /**
   * {@inheritDoc}
   */
  public ByteChannel wrapChannel(final ByteChannel channel)
  {
    return this;
  }
  /**
   * {@inheritDoc}
   */
  public synchronized int write(final ByteBuffer clearData) throws IOException
  {
    if (!socketChannel.isOpen() || sslEngine.isOutboundDone())
    {
      throw new ClosedChannelException();
    }
    /**
     * {@inheritDoc}
     */
    public ByteChannel wrapChannel(ByteChannel channel) {
        return this;
    final int originalPosition = clearData.position();
    final int originalLimit = clearData.limit();
    final int length = originalLimit - originalPosition;
    if (length > sslBufferSize)
    {
      int pos = originalPosition;
      int lim = originalPosition + sslBufferSize;
      while (pos < originalLimit)
      {
        clearData.position(pos);
        clearData.limit(lim);
        writeInternal(clearData);
        pos = lim;
        lim = Math.min(originalLimit, pos + sslBufferSize);
      }
      return length;
    }
    else
    {
      return writeInternal(clearData);
    }
  }
  private void doHandshakeOp(SSLEngineResult.HandshakeStatus hsStatus)
      throws IOException
  {
    SSLEngineResult res;
    switch (hsStatus)
    {
    case NEED_TASK:
      hsStatus = doTasks();
      break;
    case NEED_WRAP:
      tempData.clear();
      netData.clear();
      res = sslEngine.wrap(tempData, netData);
      hsStatus = res.getHandshakeStatus();
      netData.flip();
      while (netData.hasRemaining())
      {
        socketChannel.write(netData);
      }
      hsStatus = sslEngine.getHandshakeStatus();
      return;
    default:
      return;
    }
  }
  private void doHandshakeRead(SSLEngineResult.HandshakeStatus hsStatus)
      throws IOException
  {
    do
    {
      doHandshakeOp(hsStatus);
      hsStatus = sslEngine.getHandshakeStatus();
    }
    while (hsStatus == SSLEngineResult.HandshakeStatus.NEED_WRAP
        || hsStatus == SSLEngineResult.HandshakeStatus.NEED_TASK);
  }
  private void doHandshakeUnwrap() throws IOException
  {
    netData.clear();
    tempData.clear();
    final int bytesRead = socketChannel.read(netData);
    if (bytesRead <= 0)
    {
      throw new ClosedChannelException();
    }
    else
    {
      sslEngine.unwrap(netData, tempData);
    }
  }
  private void doHandshakeWrite(SSLEngineResult.HandshakeStatus hsStatus)
      throws IOException
  {
    do
    {
      if (hsStatus == SSLEngineResult.HandshakeStatus.NEED_UNWRAP)
      {
        doHandshakeUnwrap();
      }
      else
      {
        doHandshakeOp(hsStatus);
      }
      hsStatus = sslEngine.getHandshakeStatus();
    }
    while (hsStatus == SSLEngineResult.HandshakeStatus.NEED_WRAP
        || hsStatus == SSLEngineResult.HandshakeStatus.NEED_TASK
        || hsStatus == SSLEngineResult.HandshakeStatus.NEED_UNWRAP);
  }
  private SSLEngineResult.HandshakeStatus doTasks()
  {
    Runnable task;
    while ((task = sslEngine.getDelegatedTask()) != null)
    {
      task.run();
    }
    return sslEngine.getHandshakeStatus();
  }
  private int writeInternal(final ByteBuffer clearData) throws IOException
  {
    int totBytesSent = 0;
    SSLEngineResult.HandshakeStatus hsStatus;
    hsStatus = sslEngine.getHandshakeStatus();
    if (hsStatus == SSLEngineResult.HandshakeStatus.NEED_TASK
        || hsStatus == SSLEngineResult.HandshakeStatus.NEED_WRAP
        || hsStatus == SSLEngineResult.HandshakeStatus.NEED_UNWRAP)
    {
      doHandshakeWrite(hsStatus);
    }
    /**
     * {@inheritDoc}
     */
    public String getName() {
        return "TLS";
    }
    while (clearData.hasRemaining())
    {
      netData.clear();
      final SSLEngineResult res = sslEngine.wrap(clearData, netData);
      netData.flip();
      if (netData.remaining() == 0)
      {
        // wrap didn't produce any data from our clear buffer.
        // Throw exception to prevent looping.
        throw new SSLException("SSLEngine.wrap produced 0 bytes");
      }
    /**
     * {@inheritDoc}
     */
    public boolean isSecure() {
        return true;
      if (res.getStatus() != SSLEngineResult.Status.OK)
      {
        throw new ClosedChannelException();
      }
      if (hsStatus == SSLEngineResult.HandshakeStatus.NEED_TASK
          || hsStatus == SSLEngineResult.HandshakeStatus.NEED_WRAP
          || hsStatus == SSLEngineResult.HandshakeStatus.NEED_UNWRAP)
      {
        doHandshakeWrite(hsStatus);
      }
      totBytesSent += socketChannel.write(netData);
    }
    return totBytesSent;
  }
}