001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions Copyright [year] [name of copyright owner]".
013 *
014 * Copyright 2006-2010 Sun Microsystems, Inc.
015 * Portions Copyright 2010-2016 ForgeRock AS.
016 */
017package org.opends.server.protocols.ldap;
018
019import static org.opends.messages.CoreMessages.*;
020import static org.opends.messages.ProtocolMessages.*;
021import static org.opends.server.core.DirectoryServer.*;
022import static org.opends.server.loggers.AccessLogger.*;
023import static org.opends.server.protocols.ldap.LDAPConstants.*;
024import static org.opends.server.util.ServerConstants.*;
025import static org.opends.server.util.StaticUtils.*;
026
027import java.io.Closeable;
028import java.io.IOException;
029import java.net.InetAddress;
030import java.net.Socket;
031import java.nio.ByteBuffer;
032import java.nio.channels.ByteChannel;
033import java.nio.channels.ClosedChannelException;
034import java.nio.channels.SelectionKey;
035import java.nio.channels.Selector;
036import java.nio.channels.SocketChannel;
037import java.security.cert.Certificate;
038import java.util.Collection;
039import java.util.Iterator;
040import java.util.List;
041import java.util.concurrent.ConcurrentHashMap;
042import java.util.concurrent.atomic.AtomicLong;
043import java.util.concurrent.atomic.AtomicReference;
044import java.util.concurrent.locks.Lock;
045import java.util.concurrent.locks.ReentrantLock;
046
047import javax.net.ssl.SSLException;
048
049import org.forgerock.i18n.LocalizableMessage;
050import org.forgerock.i18n.LocalizableMessageBuilder;
051import org.forgerock.i18n.slf4j.LocalizedLogger;
052import org.forgerock.opendj.io.ASN1;
053import org.forgerock.opendj.io.ASN1Writer;
054import org.forgerock.opendj.ldap.ByteString;
055import org.forgerock.opendj.ldap.ByteStringBuilder;
056import org.forgerock.opendj.ldap.DN;
057import org.forgerock.opendj.ldap.ResultCode;
058import org.opends.server.api.ClientConnection;
059import org.opends.server.api.ConnectionHandler;
060import org.opends.server.core.AbandonOperationBasis;
061import org.opends.server.core.AddOperationBasis;
062import org.opends.server.core.BindOperationBasis;
063import org.opends.server.core.CompareOperationBasis;
064import org.opends.server.core.DeleteOperationBasis;
065import org.opends.server.core.DirectoryServer;
066import org.opends.server.core.ExtendedOperationBasis;
067import org.opends.server.core.ModifyDNOperationBasis;
068import org.opends.server.core.ModifyOperationBasis;
069import org.opends.server.core.PersistentSearch;
070import org.opends.server.core.PluginConfigManager;
071import org.opends.server.core.SearchOperation;
072import org.opends.server.core.SearchOperationBasis;
073import org.opends.server.core.UnbindOperationBasis;
074import org.opends.server.extensions.ConnectionSecurityProvider;
075import org.opends.server.extensions.RedirectingByteChannel;
076import org.opends.server.extensions.TLSByteChannel;
077import org.opends.server.extensions.TLSCapableConnection;
078import org.opends.server.types.AuthenticationType;
079import org.opends.server.types.CancelRequest;
080import org.opends.server.types.CancelResult;
081import org.opends.server.types.Control;
082import org.opends.server.types.DirectoryException;
083import org.opends.server.types.DisconnectReason;
084import org.opends.server.types.IntermediateResponse;
085import org.opends.server.types.Operation;
086import org.opends.server.types.OperationType;
087import org.opends.server.types.SearchResultEntry;
088import org.opends.server.types.SearchResultReference;
089import org.opends.server.util.StaticUtils;
090import org.opends.server.util.TimeThread;
091
092/**
093 * This class defines an LDAP client connection, which is a type of
094 * client connection that will be accepted by an instance of the LDAP
095 * connection handler and have its requests decoded by an LDAP request
096 * handler.
097 */
098public final class LDAPClientConnection extends ClientConnection implements
099    TLSCapableConnection
100{
101  /**
102   * A runnable whose task is to close down all IO related channels
103   * associated with a client connection after a small delay.
104   */
105  private static final class ConnectionFinalizerJob implements Runnable
106  {
107    /** The client connection ASN1 reader. */
108    private final ASN1ByteChannelReader asn1Reader;
109
110    /** The client connection socket channel. */
111    private final SocketChannel socketChannel;
112
113    /** Creates a new connection finalizer job. */
114    private ConnectionFinalizerJob(ASN1ByteChannelReader asn1Reader,
115        SocketChannel socketChannel)
116    {
117      this.asn1Reader = asn1Reader;
118      this.socketChannel = socketChannel;
119    }
120
121    @Override
122    public void run()
123    {
124      try
125      {
126        asn1Reader.close();
127      }
128      catch (Exception e)
129      {
130        // In general, we don't care about any exception that might be
131        // thrown here.
132        logger.traceException(e);
133      }
134
135      try
136      {
137        socketChannel.close();
138      }
139      catch (Exception e)
140      {
141        // In general, we don't care about any exception that might be
142        // thrown here.
143        logger.traceException(e);
144      }
145    }
146  }
147
148  /**
149   * Channel that writes the contents of the provided buffer to the client,
150   * throwing an exception if the write is unsuccessful for too
151   * long (e.g., if the client is unresponsive or there is a network
152   * problem). If possible, it will attempt to use the selector returned
153   * by the {@code ClientConnection.getWriteSelector} method, but it is
154   * capable of working even if that method returns {@code null}. <BR>
155   *
156   * Note that the original position and limit values will not be
157   * preserved, so if that is important to the caller, then it should
158   * record them before calling this method and restore them after it
159   * returns.
160   */
161  private class TimeoutWriteByteChannel implements ByteChannel
162  {
163    /** Synchronize concurrent writes to the same connection. */
164    private final Lock writeLock = new ReentrantLock();
165
166    @Override
167    public int read(ByteBuffer byteBuffer) throws IOException
168    {
169      int bytesRead = clientChannel.read(byteBuffer);
170      if (bytesRead > 0 && keepStats)
171      {
172        statTracker.updateBytesRead(bytesRead);
173      }
174      return bytesRead;
175    }
176
177    @Override
178    public boolean isOpen()
179    {
180      return clientChannel.isOpen();
181    }
182
183    @Override
184    public void close() throws IOException
185    {
186      clientChannel.close();
187    }
188
189    @Override
190    public int write(ByteBuffer byteBuffer) throws IOException
191    {
192      writeLock.lock();
193      try
194      {
195        int bytesToWrite = byteBuffer.remaining();
196        int bytesWritten = clientChannel.write(byteBuffer);
197        if (bytesWritten > 0 && keepStats)
198        {
199          statTracker.updateBytesWritten(bytesWritten);
200        }
201        if (!byteBuffer.hasRemaining())
202        {
203          return bytesToWrite;
204        }
205
206        long startTime = System.currentTimeMillis();
207        long waitTime = getMaxBlockedWriteTimeLimit();
208        if (waitTime <= 0)
209        {
210          // We won't support an infinite time limit, so fall back to using
211          // five minutes, which is a very long timeout given that we're
212          // blocking a worker thread.
213          waitTime = 300000L;
214        }
215        long stopTime = startTime + waitTime;
216
217        Selector selector = getWriteSelector();
218        if (selector == null)
219        {
220          // The client connection does not provide a selector, so we'll
221          // fall back to a more inefficient way that will work without a
222          // selector.
223          while (byteBuffer.hasRemaining()
224              && System.currentTimeMillis() < stopTime)
225          {
226            bytesWritten = clientChannel.write(byteBuffer);
227            if (bytesWritten < 0)
228            {
229              // The client connection has been closed.
230              throw new ClosedChannelException();
231            }
232            if (bytesWritten > 0 && keepStats)
233            {
234              statTracker.updateBytesWritten(bytesWritten);
235            }
236          }
237
238          if (byteBuffer.hasRemaining())
239          {
240            // If we've gotten here, then the write timed out.
241            throw new ClosedChannelException();
242          }
243
244          return bytesToWrite;
245        }
246
247        // Register with the selector for handling write operations.
248        SelectionKey key = clientChannel.register(selector,
249            SelectionKey.OP_WRITE);
250        try
251        {
252          selector.select(waitTime);
253          while (byteBuffer.hasRemaining())
254          {
255            long currentTime = System.currentTimeMillis();
256            if (currentTime >= stopTime)
257            {
258              // We've been blocked for too long.
259              throw new ClosedChannelException();
260            }
261            waitTime = stopTime - currentTime;
262
263            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
264            while (iterator.hasNext())
265            {
266              SelectionKey k = iterator.next();
267              if (k.isWritable())
268              {
269                bytesWritten = clientChannel.write(byteBuffer);
270                if (bytesWritten < 0)
271                {
272                  // The client connection has been closed.
273                  throw new ClosedChannelException();
274                }
275                if (bytesWritten > 0 && keepStats)
276                {
277                  statTracker.updateBytesWritten(bytesWritten);
278                }
279
280                iterator.remove();
281              }
282            }
283
284            if (byteBuffer.hasRemaining())
285            {
286              selector.select(waitTime);
287            }
288          }
289
290          return bytesToWrite;
291        }
292        finally
293        {
294          if (key.isValid())
295          {
296            key.cancel();
297            selector.selectNow();
298          }
299        }
300      }
301      finally
302      {
303        writeLock.unlock();
304      }
305    }
306  }
307
308  /** The tracer object for the debug logger. */
309  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
310
311  /** Thread local ASN1Writer and buffer. */
312  private static final class ASN1WriterHolder implements Closeable
313  {
314    private final ASN1Writer writer;
315    private final ByteStringBuilder buffer;
316    private final int maxBufferSize;
317
318    private ASN1WriterHolder()
319    {
320      this.buffer = new ByteStringBuilder();
321      this.maxBufferSize = getMaxInternalBufferSize();
322      this.writer = ASN1.getWriter(buffer, maxBufferSize);
323    }
324
325    @Override
326    public void close() throws IOException
327    {
328      StaticUtils.close(writer);
329      buffer.clearAndTruncate(maxBufferSize, maxBufferSize);
330    }
331  }
332
333  /** Cached ASN1 writer: a thread can only write to one connection at a time. */
334  private static final ThreadLocal<ASN1WriterHolder> ASN1_WRITER_CACHE =
335      new ThreadLocal<ASN1WriterHolder>()
336  {
337    @Override
338    protected ASN1WriterHolder initialValue()
339    {
340      return new ASN1WriterHolder();
341    }
342  };
343
344  private ASN1WriterHolder getASN1Writer()
345  {
346    ASN1WriterHolder holder = ASN1_WRITER_CACHE.get();
347    if (holder.maxBufferSize != getMaxInternalBufferSize())
348    {
349      // Setting has changed, so recreate the holder.
350      holder = new ASN1WriterHolder();
351      ASN1_WRITER_CACHE.set(holder);
352    }
353    return holder;
354  }
355
356  /** The time that the last operation was completed. */
357  private final AtomicLong lastCompletionTime;
358  /** The next operation ID that should be used for this connection. */
359  private final AtomicLong nextOperationID;
360  /** The selector that may be used for write operations. */
361  private final AtomicReference<Selector> writeSelector;
362
363  /**
364   * Indicates whether the Directory Server believes this connection to be valid
365   * and available for communication.
366   */
367  private volatile boolean connectionValid;
368
369  /**
370   * Indicates whether this connection is about to be closed. This will be used
371   * to prevent accepting new requests while a disconnect is in progress.
372   */
373  private boolean disconnectRequested;
374
375  /**
376   * Indicates whether the connection should keep statistics regarding the
377   * operations that it is performing.
378   */
379  private final boolean keepStats;
380
381  /** The set of all operations currently in progress on this connection. */
382  private final ConcurrentHashMap<Integer, Operation> operationsInProgress;
383
384  /**
385   * The number of operations performed on this connection. Used to compare with
386   * the resource limits of the network group.
387   */
388  private final AtomicLong operationsPerformed;
389
390  /** The port on the client from which this connection originated. */
391  private final int clientPort;
392  /** The LDAP version that the client is using to communicate with the server. */
393  private int ldapVersion;
394  /** The port on the server to which this client has connected. */
395  private final int serverPort;
396
397  /** The reference to the connection handler that accepted this connection. */
398  private final LDAPConnectionHandler connectionHandler;
399  /** The statistics tracker associated with this client connection. */
400  private final LDAPStatistics statTracker;
401  private final boolean useNanoTime;
402
403  /** The connection ID assigned to this connection. */
404  private final long connectionID;
405
406  /** The lock used to provide threadsafe access to the set of operations in progress. */
407  private final Object opsInProgressLock;
408
409  /** The socket channel with which this client connection is associated. */
410  private final SocketChannel clientChannel;
411  /** The byte channel used for blocking writes with time out. */
412  private final ByteChannel timeoutClientChannel;
413
414  /** The string representation of the address of the client. */
415  private final String clientAddress;
416  /** The name of the protocol that the client is using to communicate with the server. */
417  private final String protocol;
418  /** The string representation of the address of the server to which the client has connected. */
419  private final String serverAddress;
420
421  private final ASN1ByteChannelReader asn1Reader;
422  private final int bufferSize;
423  private final RedirectingByteChannel saslChannel;
424  private final RedirectingByteChannel tlsChannel;
425  private volatile ConnectionSecurityProvider saslActiveProvider;
426  private volatile ConnectionSecurityProvider tlsActiveProvider;
427  private volatile ConnectionSecurityProvider saslPendingProvider;
428  private volatile ConnectionSecurityProvider tlsPendingProvider;
429
430  /**
431   * Creates a new LDAP client connection with the provided information.
432   *
433   * @param connectionHandler
434   *          The connection handler that accepted this connection.
435   * @param clientChannel
436   *          The socket channel that may be used to communicate with
437   *          the client.
438   * @param  protocol String representing the protocol (LDAP or LDAP+SSL).
439   * @throws DirectoryException If SSL initialisation fails.
440   */
441  LDAPClientConnection(LDAPConnectionHandler connectionHandler,
442      SocketChannel clientChannel, String protocol) throws DirectoryException
443  {
444    this.connectionHandler = connectionHandler;
445    this.clientChannel = clientChannel;
446    timeoutClientChannel = new TimeoutWriteByteChannel();
447    opsInProgressLock = new Object();
448    ldapVersion = 3;
449    lastCompletionTime = new AtomicLong(TimeThread.getTime());
450    nextOperationID = new AtomicLong(0);
451    connectionValid = true;
452    disconnectRequested = false;
453    operationsInProgress = new ConcurrentHashMap<>();
454    operationsPerformed = new AtomicLong(0);
455    keepStats = connectionHandler.keepStats();
456    this.protocol = protocol;
457    writeSelector = new AtomicReference<>();
458
459    final Socket socket = clientChannel.socket();
460    clientAddress = socket.getInetAddress().getHostAddress();
461    clientPort = socket.getPort();
462    serverAddress = socket.getLocalAddress().getHostAddress();
463    serverPort = socket.getLocalPort();
464
465    statTracker = this.connectionHandler.getStatTracker();
466    if (keepStats)
467    {
468      statTracker.updateConnect();
469      this.useNanoTime=DirectoryServer.getUseNanoTime();
470    }
471    else
472    {
473      this.useNanoTime = false;
474    }
475
476    bufferSize = connectionHandler.getBufferSize();
477
478    tlsChannel = RedirectingByteChannel.getRedirectingByteChannel(timeoutClientChannel);
479    saslChannel = RedirectingByteChannel.getRedirectingByteChannel(tlsChannel);
480    this.asn1Reader = new ASN1ByteChannelReader(saslChannel, bufferSize, connectionHandler.getMaxRequestSize());
481
482    if (connectionHandler.useSSL())
483    {
484      enableSSL(connectionHandler.getTLSByteChannel(timeoutClientChannel));
485    }
486
487    connectionID = DirectoryServer.newConnectionAccepted(this);
488  }
489
490  /**
491   * Retrieves the connection ID assigned to this connection.
492   *
493   * @return The connection ID assigned to this connection.
494   */
495  @Override
496  public long getConnectionID()
497  {
498    return connectionID;
499  }
500
501  /**
502   * Retrieves the connection handler that accepted this client
503   * connection.
504   *
505   * @return The connection handler that accepted this client
506   *         connection.
507   */
508  @Override
509  public ConnectionHandler<?> getConnectionHandler()
510  {
511    return connectionHandler;
512  }
513
514  /**
515   * Retrieves the socket channel that can be used to communicate with
516   * the client.
517   *
518   * @return The socket channel that can be used to communicate with the
519   *         client.
520   */
521  @Override
522  public SocketChannel getSocketChannel()
523  {
524    return clientChannel;
525  }
526
527  /**
528   * Retrieves the protocol that the client is using to communicate with
529   * the Directory Server.
530   *
531   * @return The protocol that the client is using to communicate with
532   *         the Directory Server.
533   */
534  @Override
535  public String getProtocol()
536  {
537    return protocol;
538  }
539
540  /**
541   * Retrieves a string representation of the address of the client.
542   *
543   * @return A string representation of the address of the client.
544   */
545  @Override
546  public String getClientAddress()
547  {
548    return clientAddress;
549  }
550
551  /**
552   * Retrieves the port number for this connection on the client system.
553   *
554   * @return The port number for this connection on the client system.
555   */
556  @Override
557  public int getClientPort()
558  {
559    return clientPort;
560  }
561
562  /**
563   * Retrieves a string representation of the address on the server to
564   * which the client connected.
565   *
566   * @return A string representation of the address on the server to
567   *         which the client connected.
568   */
569  @Override
570  public String getServerAddress()
571  {
572    return serverAddress;
573  }
574
575  /**
576   * Retrieves the port number for this connection on the server system.
577   *
578   * @return The port number for this connection on the server system.
579   */
580  @Override
581  public int getServerPort()
582  {
583    return serverPort;
584  }
585
586  /**
587   * Retrieves the <CODE>java.net.InetAddress</CODE> associated with the
588   * remote client system.
589   *
590   * @return The <CODE>java.net.InetAddress</CODE> associated with the
591   *         remote client system. It may be <CODE>null</CODE> if the
592   *         client is not connected over an IP-based connection.
593   */
594  @Override
595  public InetAddress getRemoteAddress()
596  {
597    return clientChannel.socket().getInetAddress();
598  }
599
600  /**
601   * Retrieves the <CODE>java.net.InetAddress</CODE> for the Directory
602   * Server system to which the client has established the connection.
603   *
604   * @return The <CODE>java.net.InetAddress</CODE> for the Directory
605   *         Server system to which the client has established the
606   *         connection. It may be <CODE>null</CODE> if the client is
607   *         not connected over an IP-based connection.
608   */
609  @Override
610  public InetAddress getLocalAddress()
611  {
612    return clientChannel.socket().getLocalAddress();
613  }
614
615  @Override
616  public boolean isConnectionValid()
617  {
618    return this.connectionValid;
619  }
620
621  /**
622   * Indicates whether this client connection is currently using a
623   * secure mechanism to communicate with the server. Note that this may
624   * change over time based on operations performed by the client or
625   * server (e.g., it may go from <CODE>false</CODE> to
626   * <CODE>true</CODE> if the client uses the StartTLS extended
627   * operation).
628   *
629   * @return <CODE>true</CODE> if the client connection is currently
630   *         using a secure mechanism to communicate with the server, or
631   *         <CODE>false</CODE> if not.
632   */
633  @Override
634  public boolean isSecure()
635  {
636    boolean secure = false;
637    if (tlsActiveProvider != null)
638    {
639      secure = tlsActiveProvider.isSecure();
640    }
641    if (!secure && saslActiveProvider != null)
642    {
643      secure = saslActiveProvider.isSecure();
644    }
645    return secure;
646  }
647
648  /**
649   * Sends a response to the client based on the information in the
650   * provided operation.
651   *
652   * @param operation
653   *          The operation for which to send the response.
654   */
655  @Override
656  public void sendResponse(Operation operation)
657  {
658    // Since this is the final response for this operation, we can go
659    // ahead and remove it from the "operations in progress" list. It
660    // can't be canceled after this point, and this will avoid potential
661    // race conditions in which the client immediately sends another
662    // request with the same message ID as was used for this operation.
663
664    if (keepStats) {
665        long time;
666        if (useNanoTime) {
667            time = operation.getProcessingNanoTime();
668        } else {
669            time = operation.getProcessingTime();
670        }
671        this.statTracker.updateOperationMonitoringData(
672                operation.getOperationType(),
673                time);
674    }
675
676    // Avoid sending the response if one has already been sent. This may happen
677    // if operation processing encounters a run-time exception after sending the
678    // response: the worker thread exception handling code will attempt to send
679    // an error result to the client indicating that a problem occurred.
680    if (removeOperationInProgress(operation.getMessageID()))
681    {
682      LDAPMessage message = operationToResponseLDAPMessage(operation);
683      if (message != null)
684      {
685        sendLDAPMessage(message);
686      }
687    }
688  }
689
690  /**
691   * Retrieves an LDAPMessage containing a response generated from the
692   * provided operation.
693   *
694   * @param operation
695   *          The operation to use to generate the response LDAPMessage.
696   * @return An LDAPMessage containing a response generated from the
697   *         provided operation.
698   */
699  private LDAPMessage operationToResponseLDAPMessage(Operation operation)
700  {
701    ResultCode resultCode = operation.getResultCode();
702    if (resultCode == null)
703    {
704      // This must mean that the operation has either not yet completed
705      // or that it completed without a result for some reason. In any
706      // case, log a message and set the response to "operations error".
707      logger.error(ERR_LDAP_CLIENT_SEND_RESPONSE_NO_RESULT_CODE, operation.getOperationType(),
708          operation.getConnectionID(), operation.getOperationID());
709      resultCode = DirectoryServer.getServerErrorResultCode();
710    }
711
712    LocalizableMessageBuilder errorMessage = operation.getErrorMessage();
713    DN matchedDN = operation.getMatchedDN();
714
715    // Referrals are not allowed for LDAPv2 clients.
716    List<String> referralURLs;
717    if (ldapVersion == 2)
718    {
719      referralURLs = null;
720
721      if (resultCode == ResultCode.REFERRAL)
722      {
723        resultCode = ResultCode.CONSTRAINT_VIOLATION;
724        errorMessage.append(ERR_LDAPV2_REFERRAL_RESULT_CHANGED.get());
725      }
726
727      List<String> opReferrals = operation.getReferralURLs();
728      if (opReferrals != null && !opReferrals.isEmpty())
729      {
730        StringBuilder referralsStr = new StringBuilder();
731        Iterator<String> iterator = opReferrals.iterator();
732        referralsStr.append(iterator.next());
733
734        while (iterator.hasNext())
735        {
736          referralsStr.append(", ");
737          referralsStr.append(iterator.next());
738        }
739
740        errorMessage.append(ERR_LDAPV2_REFERRALS_OMITTED.get(referralsStr));
741      }
742    }
743    else
744    {
745      referralURLs = operation.getReferralURLs();
746    }
747
748    ProtocolOp protocolOp;
749    switch (operation.getOperationType())
750    {
751    case ADD:
752      protocolOp =
753          new AddResponseProtocolOp(resultCode.intValue(),
754              errorMessage.toMessage(), matchedDN, referralURLs);
755      break;
756    case BIND:
757      ByteString serverSASLCredentials =
758          ((BindOperationBasis) operation).getServerSASLCredentials();
759      protocolOp =
760          new BindResponseProtocolOp(resultCode.intValue(),
761              errorMessage.toMessage(), matchedDN, referralURLs,
762              serverSASLCredentials);
763      break;
764    case COMPARE:
765      protocolOp =
766          new CompareResponseProtocolOp(resultCode.intValue(),
767              errorMessage.toMessage(), matchedDN, referralURLs);
768      break;
769    case DELETE:
770      protocolOp =
771          new DeleteResponseProtocolOp(resultCode.intValue(),
772              errorMessage.toMessage(), matchedDN, referralURLs);
773      break;
774    case EXTENDED:
775      // If this an LDAPv2 client, then we can't send this.
776      if (ldapVersion == 2)
777      {
778        logger.error(ERR_LDAPV2_SKIPPING_EXTENDED_RESPONSE,
779            getConnectionID(), operation.getOperationID(), operation);
780        return null;
781      }
782
783      ExtendedOperationBasis extOp = (ExtendedOperationBasis) operation;
784      protocolOp =
785          new ExtendedResponseProtocolOp(resultCode.intValue(),
786              errorMessage.toMessage(), matchedDN, referralURLs, extOp
787                  .getResponseOID(), extOp.getResponseValue());
788      break;
789    case MODIFY:
790      protocolOp =
791          new ModifyResponseProtocolOp(resultCode.intValue(),
792              errorMessage.toMessage(), matchedDN, referralURLs);
793      break;
794    case MODIFY_DN:
795      protocolOp =
796          new ModifyDNResponseProtocolOp(resultCode.intValue(),
797              errorMessage.toMessage(), matchedDN, referralURLs);
798      break;
799    case SEARCH:
800      protocolOp =
801          new SearchResultDoneProtocolOp(resultCode.intValue(),
802              errorMessage.toMessage(), matchedDN, referralURLs);
803      break;
804    default:
805      // This must be a type of operation that doesn't have a response.
806      // This shouldn't happen, so log a message and return.
807      logger.error(ERR_LDAP_CLIENT_SEND_RESPONSE_INVALID_OP, operation.getOperationType(), getConnectionID(),
808          operation.getOperationID(), operation);
809      return null;
810    }
811
812    // Controls are not allowed for LDAPv2 clients.
813    List<Control> controls;
814    if (ldapVersion == 2)
815    {
816      controls = null;
817    }
818    else
819    {
820      controls = operation.getResponseControls();
821    }
822
823    return new LDAPMessage(operation.getMessageID(), protocolOp,
824        controls);
825  }
826
827  /**
828   * Sends the provided search result entry to the client.
829   *
830   * @param searchOperation
831   *          The search operation with which the entry is associated.
832   * @param searchEntry
833   *          The search result entry to be sent to the client.
834   */
835  @Override
836  public void sendSearchEntry(SearchOperation searchOperation,
837      SearchResultEntry searchEntry)
838  {
839    SearchResultEntryProtocolOp protocolOp =
840        new SearchResultEntryProtocolOp(searchEntry, ldapVersion);
841
842    sendLDAPMessage(new LDAPMessage(searchOperation.getMessageID(),
843        protocolOp, searchEntry.getControls()));
844  }
845
846  /**
847   * Sends the provided search result reference to the client.
848   *
849   * @param searchOperation
850   *          The search operation with which the reference is
851   *          associated.
852   * @param searchReference
853   *          The search result reference to be sent to the client.
854   * @return <CODE>true</CODE> if the client is able to accept
855   *         referrals, or <CODE>false</CODE> if the client cannot
856   *         handle referrals and no more attempts should be made to
857   *         send them for the associated search operation.
858   */
859  @Override
860  public boolean sendSearchReference(SearchOperation searchOperation,
861      SearchResultReference searchReference)
862  {
863    // Make sure this is not an LDAPv2 client. If it is, then they can't
864    // see referrals so we'll not send anything. Also, throw an
865    // exception so that the core server will know not to try sending
866    // any more referrals to this client for the rest of the operation.
867    if (ldapVersion == 2)
868    {
869      logger.error(ERR_LDAPV2_SKIPPING_SEARCH_REFERENCE, getConnectionID(),
870              searchOperation.getOperationID(), searchReference);
871      return false;
872    }
873
874    SearchResultReferenceProtocolOp protocolOp =
875        new SearchResultReferenceProtocolOp(searchReference);
876
877    sendLDAPMessage(new LDAPMessage(searchOperation.getMessageID(),
878        protocolOp, searchReference.getControls()));
879    return true;
880  }
881
882  /**
883   * Sends the provided intermediate response message to the client.
884   *
885   * @param intermediateResponse
886   *          The intermediate response message to be sent.
887   * @return <CODE>true</CODE> if processing on the associated operation
888   *         should continue, or <CODE>false</CODE> if not.
889   */
890  @Override
891  protected boolean sendIntermediateResponseMessage(
892      IntermediateResponse intermediateResponse)
893  {
894    IntermediateResponseProtocolOp protocolOp =
895        new IntermediateResponseProtocolOp(intermediateResponse
896            .getOID(), intermediateResponse.getValue());
897
898    Operation operation = intermediateResponse.getOperation();
899
900    LDAPMessage message =
901        new LDAPMessage(operation.getMessageID(), protocolOp,
902            intermediateResponse.getControls());
903    sendLDAPMessage(message);
904
905    // The only reason we shouldn't continue processing is if the
906    // connection is closed.
907    return connectionValid;
908  }
909
910  /**
911   * Sends the provided LDAP message to the client.
912   *
913   * @param message
914   *          The LDAP message to send to the client.
915   */
916  private void sendLDAPMessage(LDAPMessage message)
917  {
918    // Use a thread local writer.
919    final ASN1WriterHolder holder = getASN1Writer();
920    try
921    {
922      message.write(holder.writer);
923      holder.buffer.copyTo(saslChannel);
924
925      if (logger.isTraceEnabled())
926      {
927        logger.trace("LDAPMessage=%s", message);
928      }
929
930      if (keepStats)
931      {
932        statTracker.updateMessageWritten(message);
933      }
934    }
935    catch (ClosedChannelException e)
936    {
937      logger.traceException(e);
938      disconnect(DisconnectReason.IO_ERROR, false,
939          ERR_IO_ERROR_ON_CLIENT_CONNECTION.get(getExceptionMessage(e)));
940      return;
941    }
942    catch (Exception e)
943    {
944      logger.traceException(e);
945      disconnect(DisconnectReason.SERVER_ERROR, false,
946          ERR_UNEXPECTED_EXCEPTION_ON_CLIENT_CONNECTION.get(getExceptionMessage(e)));
947      return;
948    }
949    finally
950    {
951      // Clear and reset all of the internal buffers ready for the next usage.
952      // The ASN1Writer is based on a ByteStringBuilder so closing will cause
953      // the internal buffers to be resized if needed.
954      close(holder);
955    }
956 }
957
958  /**
959   * Closes the connection to the client, optionally sending it a
960   * message indicating the reason for the closure. Note that the
961   * ability to send a notice of disconnection may not be available for
962   * all protocols or under all circumstances.
963   *
964   * @param disconnectReason
965   *          The disconnect reason that provides the generic cause for
966   *          the disconnect.
967   * @param sendNotification
968   *          Indicates whether to try to provide notification to the
969   *          client that the connection will be closed.
970   * @param message
971   *          The message to include in the disconnect notification
972   *          response. It may be <CODE>null</CODE> if no message is to
973   *          be sent.
974   */
975  @Override
976  public void disconnect(DisconnectReason disconnectReason,
977      boolean sendNotification, LocalizableMessage message)
978  {
979    // Set a flag indicating that the connection is being terminated so
980    // that no new requests will be accepted. Also cancel all operations
981    // in progress.
982    synchronized (opsInProgressLock)
983    {
984      // If we are already in the middle of a disconnect, then don't
985      // do anything.
986      if (disconnectRequested)
987      {
988        return;
989      }
990
991      disconnectRequested = true;
992    }
993
994    if (keepStats)
995    {
996      statTracker.updateDisconnect();
997    }
998
999    if (connectionID >= 0)
1000    {
1001      DirectoryServer.connectionClosed(this);
1002    }
1003
1004    // Indicate that this connection is no longer valid.
1005    connectionValid = false;
1006
1007    final LocalizableMessage cancelMessage;
1008    if (message != null)
1009    {
1010      cancelMessage = new LocalizableMessageBuilder()
1011          .append(disconnectReason.getClosureMessage())
1012          .append(": ")
1013          .append(message)
1014          .toMessage();
1015    }
1016    else
1017    {
1018      cancelMessage = disconnectReason.getClosureMessage();
1019    }
1020    cancelAllOperations(new CancelRequest(true, cancelMessage));
1021    finalizeConnectionInternal();
1022
1023    // If there is a write selector for this connection, then close it.
1024    Selector selector = writeSelector.get();
1025    close(selector);
1026
1027    // See if we should send a notification to the client. If so, then
1028    // construct and send a notice of disconnection unsolicited
1029    // response. Note that we cannot send this notification to an LDAPv2 client.
1030    if (sendNotification && ldapVersion != 2)
1031    {
1032      try
1033      {
1034        int resultCode = toResultCode(disconnectReason);
1035        LocalizableMessage errMsg = message != null ? message : INFO_LDAP_CLIENT_GENERIC_NOTICE_OF_DISCONNECTION.get();
1036
1037        ExtendedResponseProtocolOp notificationOp =
1038            new ExtendedResponseProtocolOp(resultCode, errMsg, null,
1039                null, OID_NOTICE_OF_DISCONNECTION, null);
1040
1041        sendLDAPMessage(new LDAPMessage(0, notificationOp, null));
1042      }
1043      catch (Exception e)
1044      {
1045        // NYI -- Log a message indicating that we couldn't send the
1046        // notice of disconnection.
1047        logger.traceException(e);
1048      }
1049    }
1050
1051    // Enqueue the connection channels for closing by the finalizer.
1052    Runnable r = new ConnectionFinalizerJob(asn1Reader, clientChannel);
1053    connectionHandler.registerConnectionFinalizer(r);
1054
1055    // NYI -- Deregister the client connection from any server components that
1056    // might know about it.
1057
1058    logDisconnect(this, disconnectReason, message);
1059
1060    try
1061    {
1062      PluginConfigManager pluginManager = DirectoryServer.getPluginConfigManager();
1063      pluginManager.invokePostDisconnectPlugins(this, disconnectReason, message);
1064    }
1065    catch (Exception e)
1066    {
1067      logger.traceException(e);
1068    }
1069  }
1070
1071  private int toResultCode(DisconnectReason disconnectReason)
1072  {
1073    switch (disconnectReason)
1074    {
1075    case PROTOCOL_ERROR:
1076      return LDAPResultCode.PROTOCOL_ERROR;
1077    case SERVER_SHUTDOWN:
1078      return LDAPResultCode.UNAVAILABLE;
1079    case SERVER_ERROR:
1080      return DirectoryServer.getServerErrorResultCode().intValue();
1081    case ADMIN_LIMIT_EXCEEDED:
1082    case IDLE_TIME_LIMIT_EXCEEDED:
1083    case MAX_REQUEST_SIZE_EXCEEDED:
1084    case IO_TIMEOUT:
1085      return LDAPResultCode.ADMIN_LIMIT_EXCEEDED;
1086    case CONNECTION_REJECTED:
1087      return LDAPResultCode.CONSTRAINT_VIOLATION;
1088    case INVALID_CREDENTIALS:
1089      return LDAPResultCode.INVALID_CREDENTIALS;
1090    default:
1091      return LDAPResultCode.OTHER;
1092    }
1093  }
1094
1095  /**
1096   * Retrieves the set of operations in progress for this client
1097   * connection. This list must not be altered by any caller.
1098   *
1099   * @return The set of operations in progress for this client
1100   *         connection.
1101   */
1102  @Override
1103  public Collection<Operation> getOperationsInProgress()
1104  {
1105    return operationsInProgress.values();
1106  }
1107
1108  /**
1109   * Retrieves the operation in progress with the specified message ID.
1110   *
1111   * @param messageID
1112   *          The message ID for the operation to retrieve.
1113   * @return The operation in progress with the specified message ID, or
1114   *         <CODE>null</CODE> if no such operation could be found.
1115   */
1116  @Override
1117  public Operation getOperationInProgress(int messageID)
1118  {
1119    return operationsInProgress.get(messageID);
1120  }
1121
1122  /**
1123   * Adds the provided operation to the set of operations in progress
1124   * for this client connection.
1125   *
1126   * @param operation
1127   *          The operation to add to the set of operations in progress
1128   *          for this client connection.
1129   * @throws DirectoryException
1130   *           If the operation is not added for some reason (e.g., the
1131   *           client already has reached the maximum allowed concurrent
1132   *           requests).
1133   */
1134  private void addOperationInProgress(Operation operation)
1135      throws DirectoryException
1136  {
1137    int messageID = operation.getMessageID();
1138
1139    // We need to grab a lock to ensure that no one else can add
1140    // operations to the queue while we are performing some preliminary
1141    // checks.
1142    try
1143    {
1144      synchronized (opsInProgressLock)
1145      {
1146        // If we're already in the process of disconnecting the client,
1147        // then reject the operation.
1148        if (disconnectRequested)
1149        {
1150          LocalizableMessage message = WARN_CLIENT_DISCONNECT_IN_PROGRESS.get();
1151          throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
1152              message);
1153        }
1154
1155        // Add the operation to the list of operations in progress for
1156        // this connection.
1157        Operation op = operationsInProgress.putIfAbsent(messageID, operation);
1158
1159        // See if there is already an operation in progress with the
1160        // same message ID. If so, then we can't allow it.
1161        if (op != null)
1162        {
1163          LocalizableMessage message =
1164            WARN_LDAP_CLIENT_DUPLICATE_MESSAGE_ID.get(messageID);
1165          throw new DirectoryException(ResultCode.PROTOCOL_ERROR,
1166              message);
1167        }
1168      }
1169
1170      // Try to add the operation to the work queue,
1171      // or run it synchronously (typically for the administration
1172      // connector)
1173      connectionHandler.getQueueingStrategy().enqueueRequest(
1174          operation);
1175    }
1176    catch (DirectoryException de)
1177    {
1178      logger.traceException(de);
1179
1180      operationsInProgress.remove(messageID);
1181      lastCompletionTime.set(TimeThread.getTime());
1182
1183      throw de;
1184    }
1185    catch (Exception e)
1186    {
1187      logger.traceException(e);
1188
1189      LocalizableMessage message =
1190        WARN_LDAP_CLIENT_CANNOT_ENQUEUE.get(getExceptionMessage(e));
1191      throw new DirectoryException(DirectoryServer
1192          .getServerErrorResultCode(), message, e);
1193    }
1194  }
1195
1196  /**
1197   * Removes the provided operation from the set of operations in
1198   * progress for this client connection. Note that this does not make
1199   * any attempt to cancel any processing that may already be in
1200   * progress for the operation.
1201   *
1202   * @param messageID
1203   *          The message ID of the operation to remove from the set of
1204   *          operations in progress.
1205   * @return <CODE>true</CODE> if the operation was found and removed
1206   *         from the set of operations in progress, or
1207   *         <CODE>false</CODE> if not.
1208   */
1209  @Override
1210  public boolean removeOperationInProgress(int messageID)
1211  {
1212    Operation operation = operationsInProgress.remove(messageID);
1213    if (operation == null)
1214    {
1215      return false;
1216    }
1217
1218    if (operation.getOperationType() == OperationType.ABANDON
1219        && keepStats
1220        && operation.getResultCode() == ResultCode.CANCELLED)
1221    {
1222      statTracker.updateAbandonedOperation();
1223    }
1224
1225    lastCompletionTime.set(TimeThread.getTime());
1226    return true;
1227  }
1228
1229  /**
1230   * Attempts to cancel the specified operation.
1231   *
1232   * @param messageID
1233   *          The message ID of the operation to cancel.
1234   * @param cancelRequest
1235   *          An object providing additional information about how the
1236   *          cancel should be processed.
1237   * @return A cancel result that either indicates that the cancel was
1238   *         successful or provides a reason that it was not.
1239   */
1240  @Override
1241  public CancelResult cancelOperation(int messageID,
1242      CancelRequest cancelRequest)
1243  {
1244    Operation op = operationsInProgress.get(messageID);
1245    if (op != null)
1246    {
1247      return op.cancel(cancelRequest);
1248    }
1249
1250    // See if the operation is in the list of persistent searches.
1251    for (PersistentSearch ps : getPersistentSearches())
1252    {
1253      if (ps.getMessageID() == messageID)
1254      {
1255        // We only need to find the first persistent search
1256        // associated with the provided message ID. The persistent search
1257        // will ensure that all other related persistent searches are cancelled.
1258        return ps.cancel();
1259      }
1260    }
1261    return new CancelResult(ResultCode.NO_SUCH_OPERATION, null);
1262  }
1263
1264  /**
1265   * Attempts to cancel all operations in progress on this connection.
1266   *
1267   * @param cancelRequest
1268   *          An object providing additional information about how the
1269   *          cancel should be processed.
1270   */
1271  @Override
1272  public void cancelAllOperations(CancelRequest cancelRequest)
1273  {
1274    // Make sure that no one can add any new operations.
1275    synchronized (opsInProgressLock)
1276    {
1277      try
1278      {
1279        for (Operation o : operationsInProgress.values())
1280        {
1281          try
1282          {
1283            o.abort(cancelRequest);
1284
1285            // TODO: Assume its cancelled?
1286            if (keepStats)
1287            {
1288              statTracker.updateAbandonedOperation();
1289            }
1290          }
1291          catch (Exception e)
1292          {
1293            logger.traceException(e);
1294          }
1295        }
1296
1297        if (!operationsInProgress.isEmpty()
1298            || !getPersistentSearches().isEmpty())
1299        {
1300          lastCompletionTime.set(TimeThread.getTime());
1301        }
1302
1303        operationsInProgress.clear();
1304
1305        for (PersistentSearch persistentSearch : getPersistentSearches())
1306        {
1307          persistentSearch.cancel();
1308        }
1309      }
1310      catch (Exception e)
1311      {
1312        logger.traceException(e);
1313      }
1314    }
1315  }
1316
1317  /**
1318   * Attempts to cancel all operations in progress on this connection
1319   * except the operation with the specified message ID.
1320   *
1321   * @param cancelRequest
1322   *          An object providing additional information about how the
1323   *          cancel should be processed.
1324   * @param messageID
1325   *          The message ID of the operation that should not be
1326   *          canceled.
1327   */
1328  @Override
1329  public void cancelAllOperationsExcept(CancelRequest cancelRequest,
1330      int messageID)
1331  {
1332    // Make sure that no one can add any new operations.
1333    synchronized (opsInProgressLock)
1334    {
1335      try
1336      {
1337        for (int msgID : operationsInProgress.keySet())
1338        {
1339          if (msgID == messageID)
1340          {
1341            continue;
1342          }
1343
1344          Operation o = operationsInProgress.get(msgID);
1345          if (o != null)
1346          {
1347            try
1348            {
1349              o.abort(cancelRequest);
1350
1351              // TODO: Assume its cancelled?
1352              if (keepStats)
1353              {
1354                statTracker.updateAbandonedOperation();
1355              }
1356            }
1357            catch (Exception e)
1358            {
1359              logger.traceException(e);
1360            }
1361          }
1362
1363          operationsInProgress.remove(msgID);
1364          lastCompletionTime.set(TimeThread.getTime());
1365        }
1366
1367        for (PersistentSearch persistentSearch : getPersistentSearches())
1368        {
1369          if (persistentSearch.getMessageID() == messageID)
1370          {
1371            continue;
1372          }
1373
1374          persistentSearch.cancel();
1375          lastCompletionTime.set(TimeThread.getTime());
1376        }
1377      }
1378      catch (Exception e)
1379      {
1380        logger.traceException(e);
1381      }
1382    }
1383  }
1384
1385  @Override
1386  public Selector getWriteSelector()
1387  {
1388    Selector selector = writeSelector.get();
1389    if (selector == null)
1390    {
1391      try
1392      {
1393        selector = Selector.open();
1394        if (!writeSelector.compareAndSet(null, selector))
1395        {
1396          selector.close();
1397          selector = writeSelector.get();
1398        }
1399      }
1400      catch (Exception e)
1401      {
1402        logger.traceException(e);
1403      }
1404    }
1405
1406    return selector;
1407  }
1408
1409  @Override
1410  public long getMaxBlockedWriteTimeLimit()
1411  {
1412    return connectionHandler.getMaxBlockedWriteTimeLimit();
1413  }
1414
1415  /**
1416   * Returns the total number of operations initiated on this
1417   * connection.
1418   *
1419   * @return the total number of operations on this connection
1420   */
1421  @Override
1422  public long getNumberOfOperations()
1423  {
1424    return operationsPerformed.get();
1425  }
1426
1427  /**
1428   * Returns the ASN1 reader for this connection.
1429   *
1430   * @return the ASN1 reader for this connection
1431   */
1432  ASN1ByteChannelReader getASN1Reader()
1433  {
1434    return asn1Reader;
1435  }
1436
1437  /**
1438   * Process data read.
1439   *
1440   * @return number of bytes read if this connection is still valid
1441   *         or negative integer to indicate an error otherwise
1442   */
1443  int processDataRead()
1444  {
1445    if (bindInProgress.get() || startTLSInProgress.get())
1446    {
1447      // We should wait for the bind or startTLS to finish before
1448      // reading any more data off the socket.
1449      return 0;
1450    }
1451
1452    try
1453    {
1454      int result = asn1Reader.processChannelData();
1455      if (result < 0)
1456      {
1457        // The connection has been closed by the client. Disconnect
1458        // and return.
1459        disconnect(DisconnectReason.CLIENT_DISCONNECT, false, null);
1460        return -1;
1461      }
1462      return result;
1463    }
1464    catch (Exception e)
1465    {
1466      logger.traceException(e);
1467
1468      if (asn1Reader.hasRemainingData() || e instanceof SSLException)
1469      {
1470        // The connection failed, but there was an unread partial message so
1471        // interpret this as an IO error.
1472        LocalizableMessage m = ERR_LDAP_CLIENT_IO_ERROR_DURING_READ.get(e);
1473        disconnect(DisconnectReason.IO_ERROR, true, m);
1474      }
1475      else
1476      {
1477        // The connection failed and there was no unread data, so interpret this
1478        // as indicating that the client aborted (reset) the connection. This
1479        // happens when a client configures closes a connection which has been
1480        // configured with SO_LINGER set to 0.
1481        LocalizableMessage m = ERR_LDAP_CLIENT_IO_ERROR_BEFORE_READ.get();
1482        disconnect(DisconnectReason.CLIENT_DISCONNECT, true, m);
1483      }
1484
1485      return -1;
1486    }
1487  }
1488
1489  /**
1490   * Processes the provided LDAP message read from the client and takes
1491   * whatever action is appropriate. For most requests, this will
1492   * include placing the operation in the work queue. Certain requests
1493   * (in particular, abandons and unbinds) will be processed directly.
1494   *
1495   * @param message
1496   *          The LDAP message to process.
1497   * @return <CODE>true</CODE> if the appropriate action was taken for
1498   *         the request, or <CODE>false</CODE> if there was a fatal
1499   *         error and the client has been disconnected as a result, or
1500   *         if the client unbound from the server.
1501   */
1502  boolean processLDAPMessage(LDAPMessage message)
1503  {
1504    if (keepStats)
1505    {
1506      statTracker.updateMessageRead(message);
1507    }
1508    operationsPerformed.getAndIncrement();
1509
1510    List<Control> opControls = message.getControls();
1511
1512    // FIXME -- See if there is a bind in progress. If so, then deny
1513    // most kinds of operations.
1514
1515    // Figure out what type of operation we're dealing with based on the
1516    // LDAP message. Abandon and unbind requests will be processed here.
1517    // All other types of requests will be encapsulated into operations
1518    // and append into the work queue to be picked up by a worker
1519    // thread. Any other kinds of LDAP messages (e.g., response
1520    // messages) are illegal and will result in the connection being
1521    // terminated.
1522    try
1523    {
1524      if (bindInProgress.get())
1525      {
1526        throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, ERR_ENQUEUE_BIND_IN_PROGRESS.get());
1527      }
1528      else if (startTLSInProgress.get())
1529      {
1530        throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, ERR_ENQUEUE_STARTTLS_IN_PROGRESS.get());
1531      }
1532      else if (saslBindInProgress.get() && message.getProtocolOpType() != OP_TYPE_BIND_REQUEST)
1533      {
1534        throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, ERR_ENQUEUE_SASLBIND_IN_PROGRESS.get());
1535      }
1536
1537      boolean result;
1538      switch (message.getProtocolOpType())
1539      {
1540      case OP_TYPE_ABANDON_REQUEST:
1541        return processAbandonRequest(message, opControls);
1542      case OP_TYPE_ADD_REQUEST:
1543        return processAddRequest(message, opControls);
1544      case OP_TYPE_BIND_REQUEST:
1545        boolean isSaslBind = message.getBindRequestProtocolOp().getAuthenticationType() == AuthenticationType.SASL;
1546        bindInProgress.set(true);
1547        if (isSaslBind)
1548        {
1549          saslBindInProgress.set(true);
1550        }
1551        result = processBindRequest(message, opControls);
1552        if(!result)
1553        {
1554          bindInProgress.set(false);
1555          if (isSaslBind)
1556          {
1557            saslBindInProgress.set(false);
1558          }
1559        }
1560        return result;
1561      case OP_TYPE_COMPARE_REQUEST:
1562        return processCompareRequest(message, opControls);
1563      case OP_TYPE_DELETE_REQUEST:
1564        return processDeleteRequest(message, opControls);
1565      case OP_TYPE_EXTENDED_REQUEST:
1566        boolean isStartTlsRequest = OID_START_TLS_REQUEST.equals(message.getExtendedRequestProtocolOp().getOID());
1567        if (isStartTlsRequest)
1568        {
1569          startTLSInProgress.set(true);
1570        }
1571        result = processExtendedRequest(message, opControls);
1572        if (!result && isStartTlsRequest)
1573        {
1574          startTLSInProgress.set(false);
1575        }
1576        return result;
1577      case OP_TYPE_MODIFY_REQUEST:
1578        return processModifyRequest(message, opControls);
1579      case OP_TYPE_MODIFY_DN_REQUEST:
1580        return processModifyDNRequest(message, opControls);
1581      case OP_TYPE_SEARCH_REQUEST:
1582        return processSearchRequest(message, opControls);
1583      case OP_TYPE_UNBIND_REQUEST:
1584        return processUnbindRequest(message, opControls);
1585      default:
1586        LocalizableMessage msg =
1587            ERR_LDAP_DISCONNECT_DUE_TO_INVALID_REQUEST_TYPE.get(message
1588                .getProtocolOpName(), message.getMessageID());
1589        disconnect(DisconnectReason.PROTOCOL_ERROR, true, msg);
1590        return false;
1591      }
1592    }
1593    catch (Exception e)
1594    {
1595      logger.traceException(e);
1596
1597      LocalizableMessage msg =
1598          ERR_LDAP_DISCONNECT_DUE_TO_PROCESSING_FAILURE.get(message
1599              .getProtocolOpName(), message.getMessageID(), e);
1600      disconnect(DisconnectReason.SERVER_ERROR, true, msg);
1601      return false;
1602    }
1603  }
1604
1605  /**
1606   * Processes the provided LDAP message as an abandon request.
1607   *
1608   * @param message
1609   *          The LDAP message containing the abandon request to
1610   *          process.
1611   * @param controls
1612   *          The set of pre-decoded request controls contained in the
1613   *          message.
1614   * @return <CODE>true</CODE> if the request was processed
1615   *         successfully, or <CODE>false</CODE> if not and the
1616   *         connection has been closed as a result (it is the
1617   *         responsibility of this method to close the connection).
1618   */
1619  private boolean processAbandonRequest(LDAPMessage message, List<Control> controls)
1620  {
1621    if (ldapVersion == 2 && !controls.isEmpty())
1622    {
1623      disconnectControlsNotAllowed();
1624      return false;
1625    }
1626
1627    // Create the abandon operation and add it into the work queue.
1628    AbandonRequestProtocolOp protocolOp =
1629        message.getAbandonRequestProtocolOp();
1630    AbandonOperationBasis abandonOp =
1631        new AbandonOperationBasis(this, nextOperationID
1632            .getAndIncrement(), message.getMessageID(), controls,
1633            protocolOp.getIDToAbandon());
1634
1635    try
1636    {
1637      addOperationInProgress(abandonOp);
1638    }
1639    catch (DirectoryException de)
1640    {
1641      logger.traceException(de);
1642
1643      // Don't send an error response since abandon operations
1644      // don't have a response.
1645    }
1646
1647    return connectionValid;
1648  }
1649
1650  /**
1651   * Processes the provided LDAP message as an add request.
1652   *
1653   * @param message
1654   *          The LDAP message containing the add request to process.
1655   * @param controls
1656   *          The set of pre-decoded request controls contained in the
1657   *          message.
1658   * @return <CODE>true</CODE> if the request was processed
1659   *         successfully, or <CODE>false</CODE> if not and the
1660   *         connection has been closed as a result (it is the
1661   *         responsibility of this method to close the connection).
1662   */
1663  private boolean processAddRequest(LDAPMessage message, List<Control> controls)
1664  {
1665    if (ldapVersion == 2 && !controls.isEmpty())
1666    {
1667      // LDAPv2 clients aren't allowed to send controls.
1668      AddResponseProtocolOp responseOp =
1669          new AddResponseProtocolOp(LDAPResultCode.PROTOCOL_ERROR,
1670              ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get());
1671      sendLDAPMessage(message, responseOp);
1672      disconnectControlsNotAllowed();
1673      return false;
1674    }
1675
1676    // Create the add operation and add it into the work queue.
1677    AddRequestProtocolOp protocolOp = message.getAddRequestProtocolOp();
1678    AddOperationBasis addOp =
1679        new AddOperationBasis(this, nextOperationID.getAndIncrement(),
1680            message.getMessageID(), controls, protocolOp.getDN(),
1681            protocolOp.getAttributes());
1682
1683    try
1684    {
1685      addOperationInProgress(addOp);
1686    }
1687    catch (DirectoryException de)
1688    {
1689      logger.traceException(de);
1690
1691      AddResponseProtocolOp responseOp =
1692          new AddResponseProtocolOp(de.getResultCode().intValue(),
1693              de.getMessageObject(), de.getMatchedDN(), de
1694                  .getReferralURLs());
1695
1696      sendLDAPMessage(new LDAPMessage(message.getMessageID(),
1697          responseOp, addOp.getResponseControls()));
1698    }
1699
1700    return connectionValid;
1701  }
1702
1703  private void sendLDAPMessage(LDAPMessage message, ProtocolOp responseOp)
1704  {
1705    sendLDAPMessage(new LDAPMessage(message.getMessageID(), responseOp));
1706  }
1707
1708  private void disconnectControlsNotAllowed()
1709  {
1710    disconnect(DisconnectReason.PROTOCOL_ERROR, false, ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get());
1711  }
1712
1713  /**
1714   * Processes the provided LDAP message as a bind request.
1715   *
1716   * @param message
1717   *          The LDAP message containing the bind request to process.
1718   * @param controls
1719   *          The set of pre-decoded request controls contained in the
1720   *          message.
1721   * @return <CODE>true</CODE> if the request was processed
1722   *         successfully, or <CODE>false</CODE> if not and the
1723   *         connection has been closed as a result (it is the
1724   *         responsibility of this method to close the connection).
1725   */
1726  private boolean processBindRequest(LDAPMessage message,
1727      List<Control> controls)
1728  {
1729    BindRequestProtocolOp protocolOp =
1730        message.getBindRequestProtocolOp();
1731
1732    // See if this is an LDAPv2 bind request, and if so whether that
1733    // should be allowed.
1734    String versionString;
1735    switch (ldapVersion = protocolOp.getProtocolVersion())
1736    {
1737    case 2:
1738      versionString = "2";
1739
1740      if (!connectionHandler.allowLDAPv2())
1741      {
1742        BindResponseProtocolOp responseOp =
1743            new BindResponseProtocolOp(
1744                LDAPResultCode.PROTOCOL_ERROR,
1745                ERR_LDAPV2_CLIENTS_NOT_ALLOWED.get());
1746        sendLDAPMessage(new LDAPMessage(message.getMessageID(),
1747            responseOp));
1748        disconnect(DisconnectReason.PROTOCOL_ERROR, false,
1749            ERR_LDAPV2_CLIENTS_NOT_ALLOWED.get());
1750        return false;
1751      }
1752
1753      if (!controls.isEmpty())
1754      {
1755        // LDAPv2 clients aren't allowed to send controls.
1756        BindResponseProtocolOp responseOp =
1757            new BindResponseProtocolOp(LDAPResultCode.PROTOCOL_ERROR,
1758                ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get());
1759        sendLDAPMessage(message, responseOp);
1760        disconnectControlsNotAllowed();
1761        return false;
1762      }
1763
1764      break;
1765    case 3:
1766      versionString = "3";
1767      break;
1768    default:
1769      // Unsupported protocol version. RFC4511 states that we MUST send
1770      // a protocol error back to the client.
1771      BindResponseProtocolOp responseOp =
1772          new BindResponseProtocolOp(LDAPResultCode.PROTOCOL_ERROR,
1773              ERR_LDAP_UNSUPPORTED_PROTOCOL_VERSION.get(ldapVersion));
1774      sendLDAPMessage(new LDAPMessage(message.getMessageID(),
1775          responseOp));
1776      disconnect(DisconnectReason.PROTOCOL_ERROR, false,
1777          ERR_LDAP_UNSUPPORTED_PROTOCOL_VERSION.get(ldapVersion));
1778      return false;
1779    }
1780
1781    ByteString bindDN = protocolOp.getDN();
1782
1783    BindOperationBasis bindOp;
1784    switch (protocolOp.getAuthenticationType())
1785    {
1786    case SIMPLE:
1787      bindOp =
1788          new BindOperationBasis(this, nextOperationID
1789              .getAndIncrement(), message.getMessageID(), controls,
1790              versionString, bindDN, protocolOp.getSimplePassword());
1791      break;
1792    case SASL:
1793      bindOp =
1794          new BindOperationBasis(this, nextOperationID
1795              .getAndIncrement(), message.getMessageID(), controls,
1796              versionString, bindDN, protocolOp.getSASLMechanism(),
1797              protocolOp.getSASLCredentials());
1798      break;
1799    default:
1800      // This is an invalid authentication type, and therefore a
1801      // protocol error. As per RFC 2251, a protocol error in a bind
1802      // request must result in terminating the connection.
1803      LocalizableMessage msg =
1804          ERR_LDAP_INVALID_BIND_AUTH_TYPE.get(message.getMessageID(),
1805              protocolOp.getAuthenticationType());
1806      disconnect(DisconnectReason.PROTOCOL_ERROR, true, msg);
1807      return false;
1808    }
1809
1810    // Add the operation into the work queue.
1811    try
1812    {
1813      addOperationInProgress(bindOp);
1814    }
1815    catch (DirectoryException de)
1816    {
1817      logger.traceException(de);
1818
1819      BindResponseProtocolOp responseOp =
1820          new BindResponseProtocolOp(de.getResultCode().intValue(),
1821              de.getMessageObject(), de.getMatchedDN(), de
1822                  .getReferralURLs());
1823
1824      sendLDAPMessage(new LDAPMessage(message.getMessageID(),
1825          responseOp, bindOp.getResponseControls()));
1826
1827      // If it was a protocol error, then terminate the connection.
1828      if (de.getResultCode() == ResultCode.PROTOCOL_ERROR)
1829      {
1830        LocalizableMessage msg =
1831            ERR_LDAP_DISCONNECT_DUE_TO_BIND_PROTOCOL_ERROR.get(message
1832                .getMessageID(), de.getMessageObject());
1833        disconnect(DisconnectReason.PROTOCOL_ERROR, true, msg);
1834      }
1835    }
1836
1837    return connectionValid;
1838  }
1839
1840  /**
1841   * Processes the provided LDAP message as a compare request.
1842   *
1843   * @param message
1844   *          The LDAP message containing the compare request to
1845   *          process.
1846   * @param controls
1847   *          The set of pre-decoded request controls contained in the
1848   *          message.
1849   * @return <CODE>true</CODE> if the request was processed
1850   *         successfully, or <CODE>false</CODE> if not and the
1851   *         connection has been closed as a result (it is the
1852   *         responsibility of this method to close the connection).
1853   */
1854  private boolean processCompareRequest(LDAPMessage message, List<Control> controls)
1855  {
1856    if (ldapVersion == 2 && !controls.isEmpty())
1857    {
1858      // LDAPv2 clients aren't allowed to send controls.
1859      CompareResponseProtocolOp responseOp =
1860          new CompareResponseProtocolOp(LDAPResultCode.PROTOCOL_ERROR,
1861              ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get());
1862      sendLDAPMessage(message, responseOp);
1863      disconnectControlsNotAllowed();
1864      return false;
1865    }
1866
1867    CompareRequestProtocolOp protocolOp =
1868        message.getCompareRequestProtocolOp();
1869    CompareOperationBasis compareOp =
1870        new CompareOperationBasis(this, nextOperationID
1871            .getAndIncrement(), message.getMessageID(), controls,
1872            protocolOp.getDN(), protocolOp.getAttributeType(),
1873            protocolOp.getAssertionValue());
1874
1875    // Add the operation into the work queue.
1876    try
1877    {
1878      addOperationInProgress(compareOp);
1879    }
1880    catch (DirectoryException de)
1881    {
1882      logger.traceException(de);
1883
1884      CompareResponseProtocolOp responseOp =
1885          new CompareResponseProtocolOp(de.getResultCode().intValue(),
1886              de.getMessageObject(), de.getMatchedDN(), de.getReferralURLs());
1887
1888      sendLDAPMessage(new LDAPMessage(message.getMessageID(),
1889          responseOp, compareOp.getResponseControls()));
1890    }
1891
1892    return connectionValid;
1893  }
1894
1895  /**
1896   * Processes the provided LDAP message as a delete request.
1897   *
1898   * @param message
1899   *          The LDAP message containing the delete request to process.
1900   * @param controls
1901   *          The set of pre-decoded request controls contained in the
1902   *          message.
1903   * @return <CODE>true</CODE> if the request was processed
1904   *         successfully, or <CODE>false</CODE> if not and the
1905   *         connection has been closed as a result (it is the
1906   *         responsibility of this method to close the connection).
1907   */
1908  private boolean processDeleteRequest(LDAPMessage message, List<Control> controls)
1909  {
1910    if (ldapVersion == 2 && !controls.isEmpty())
1911    {
1912      // LDAPv2 clients aren't allowed to send controls.
1913      DeleteResponseProtocolOp responseOp =
1914          new DeleteResponseProtocolOp(LDAPResultCode.PROTOCOL_ERROR,
1915              ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get());
1916      sendLDAPMessage(message, responseOp);
1917      disconnectControlsNotAllowed();
1918      return false;
1919    }
1920
1921    DeleteRequestProtocolOp protocolOp =
1922        message.getDeleteRequestProtocolOp();
1923    DeleteOperationBasis deleteOp =
1924        new DeleteOperationBasis(this, nextOperationID
1925            .getAndIncrement(), message.getMessageID(), controls,
1926            protocolOp.getDN());
1927
1928    // Add the operation into the work queue.
1929    try
1930    {
1931      addOperationInProgress(deleteOp);
1932    }
1933    catch (DirectoryException de)
1934    {
1935      logger.traceException(de);
1936
1937      DeleteResponseProtocolOp responseOp =
1938          new DeleteResponseProtocolOp(
1939              de.getResultCode().intValue(), de.getMessageObject(),
1940              de.getMatchedDN(), de.getReferralURLs());
1941
1942      sendLDAPMessage(new LDAPMessage(message.getMessageID(),
1943          responseOp, deleteOp.getResponseControls()));
1944    }
1945
1946    return connectionValid;
1947  }
1948
1949  /**
1950   * Processes the provided LDAP message as an extended request.
1951   *
1952   * @param message
1953   *          The LDAP message containing the extended request to
1954   *          process.
1955   * @param controls
1956   *          The set of pre-decoded request controls contained in the
1957   *          message.
1958   * @return <CODE>true</CODE> if the request was processed
1959   *         successfully, or <CODE>false</CODE> if not and the
1960   *         connection has been closed as a result (it is the
1961   *         responsibility of this method to close the connection).
1962   */
1963  private boolean processExtendedRequest(LDAPMessage message,
1964      List<Control> controls)
1965  {
1966    // See if this is an LDAPv2 client. If it is, then they should not
1967    // be issuing extended requests. We can't send a response that we
1968    // can be sure they can understand, so we have no choice but to
1969    // close the connection.
1970    if (ldapVersion == 2)
1971    {
1972      LocalizableMessage msg =
1973          ERR_LDAPV2_EXTENDED_REQUEST_NOT_ALLOWED.get(
1974              getConnectionID(), message.getMessageID());
1975      logger.error(msg);
1976      disconnect(DisconnectReason.PROTOCOL_ERROR, false, msg);
1977      return false;
1978    }
1979
1980    // FIXME -- Do we need to handle certain types of request here?
1981    // -- StartTLS requests
1982    // -- Cancel requests
1983
1984    ExtendedRequestProtocolOp protocolOp =
1985        message.getExtendedRequestProtocolOp();
1986    ExtendedOperationBasis extendedOp =
1987        new ExtendedOperationBasis(this, nextOperationID
1988            .getAndIncrement(), message.getMessageID(), controls,
1989            protocolOp.getOID(), protocolOp.getValue());
1990
1991    // Add the operation into the work queue.
1992    try
1993    {
1994      addOperationInProgress(extendedOp);
1995    }
1996    catch (DirectoryException de)
1997    {
1998      logger.traceException(de);
1999
2000      ExtendedResponseProtocolOp responseOp =
2001          new ExtendedResponseProtocolOp(de.getResultCode().intValue(),
2002              de.getMessageObject(), de.getMatchedDN(), de.getReferralURLs());
2003
2004      sendLDAPMessage(new LDAPMessage(message.getMessageID(),
2005          responseOp, extendedOp.getResponseControls()));
2006    }
2007
2008    return connectionValid;
2009  }
2010
2011  /**
2012   * Processes the provided LDAP message as a modify request.
2013   *
2014   * @param message
2015   *          The LDAP message containing the modify request to process.
2016   * @param controls
2017   *          The set of pre-decoded request controls contained in the
2018   *          message.
2019   * @return <CODE>true</CODE> if the request was processed
2020   *         successfully, or <CODE>false</CODE> if not and the
2021   *         connection has been closed as a result (it is the
2022   *         responsibility of this method to close the connection).
2023   */
2024  private boolean processModifyRequest(LDAPMessage message, List<Control> controls)
2025  {
2026    if (ldapVersion == 2 && !controls.isEmpty())
2027    {
2028      // LDAPv2 clients aren't allowed to send controls.
2029      ModifyResponseProtocolOp responseOp =
2030          new ModifyResponseProtocolOp(LDAPResultCode.PROTOCOL_ERROR,
2031              ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get());
2032      sendLDAPMessage(message, responseOp);
2033      disconnectControlsNotAllowed();
2034      return false;
2035    }
2036
2037    ModifyRequestProtocolOp protocolOp =
2038        message.getModifyRequestProtocolOp();
2039    ModifyOperationBasis modifyOp =
2040        new ModifyOperationBasis(this, nextOperationID
2041            .getAndIncrement(), message.getMessageID(), controls,
2042            protocolOp.getDN(), protocolOp.getModifications());
2043
2044    // Add the operation into the work queue.
2045    try
2046    {
2047      addOperationInProgress(modifyOp);
2048    }
2049    catch (DirectoryException de)
2050    {
2051      logger.traceException(de);
2052
2053      ModifyResponseProtocolOp responseOp =
2054          new ModifyResponseProtocolOp(
2055              de.getResultCode().intValue(), de.getMessageObject(),
2056              de.getMatchedDN(), de.getReferralURLs());
2057
2058      sendLDAPMessage(new LDAPMessage(message.getMessageID(),
2059          responseOp, modifyOp.getResponseControls()));
2060    }
2061
2062    return connectionValid;
2063  }
2064
2065  /**
2066   * Processes the provided LDAP message as a modify DN request.
2067   *
2068   * @param message
2069   *          The LDAP message containing the modify DN request to
2070   *          process.
2071   * @param controls
2072   *          The set of pre-decoded request controls contained in the
2073   *          message.
2074   * @return <CODE>true</CODE> if the request was processed
2075   *         successfully, or <CODE>false</CODE> if not and the
2076   *         connection has been closed as a result (it is the
2077   *         responsibility of this method to close the connection).
2078   */
2079  private boolean processModifyDNRequest(LDAPMessage message, List<Control> controls)
2080  {
2081    if (ldapVersion == 2 && !controls.isEmpty())
2082    {
2083      // LDAPv2 clients aren't allowed to send controls.
2084      ModifyDNResponseProtocolOp responseOp =
2085          new ModifyDNResponseProtocolOp(LDAPResultCode.PROTOCOL_ERROR,
2086              ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get());
2087      sendLDAPMessage(message, responseOp);
2088      disconnectControlsNotAllowed();
2089      return false;
2090    }
2091
2092    ModifyDNRequestProtocolOp protocolOp =
2093        message.getModifyDNRequestProtocolOp();
2094    ModifyDNOperationBasis modifyDNOp =
2095        new ModifyDNOperationBasis(this, nextOperationID
2096            .getAndIncrement(), message.getMessageID(), controls,
2097            protocolOp.getEntryDN(), protocolOp.getNewRDN(), protocolOp
2098                .deleteOldRDN(), protocolOp.getNewSuperior());
2099
2100    // Add the operation into the work queue.
2101    try
2102    {
2103      addOperationInProgress(modifyDNOp);
2104    }
2105    catch (DirectoryException de)
2106    {
2107      logger.traceException(de);
2108
2109      ModifyDNResponseProtocolOp responseOp =
2110          new ModifyDNResponseProtocolOp(de.getResultCode().intValue(),
2111              de.getMessageObject(), de.getMatchedDN(), de.getReferralURLs());
2112
2113      sendLDAPMessage(new LDAPMessage(message.getMessageID(),
2114          responseOp, modifyDNOp.getResponseControls()));
2115    }
2116
2117    return connectionValid;
2118  }
2119
2120  /**
2121   * Processes the provided LDAP message as a search request.
2122   *
2123   * @param message
2124   *          The LDAP message containing the search request to process.
2125   * @param controls
2126   *          The set of pre-decoded request controls contained in the
2127   *          message.
2128   * @return <CODE>true</CODE> if the request was processed
2129   *         successfully, or <CODE>false</CODE> if not and the
2130   *         connection has been closed as a result (it is the
2131   *         responsibility of this method to close the connection).
2132   */
2133  private boolean processSearchRequest(LDAPMessage message,
2134      List<Control> controls)
2135  {
2136    if (ldapVersion == 2 && !controls.isEmpty())
2137    {
2138      // LDAPv2 clients aren't allowed to send controls.
2139      SearchResultDoneProtocolOp responseOp =
2140          new SearchResultDoneProtocolOp(LDAPResultCode.PROTOCOL_ERROR,
2141              ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get());
2142      sendLDAPMessage(message, responseOp);
2143      disconnectControlsNotAllowed();
2144      return false;
2145    }
2146
2147    SearchRequestProtocolOp protocolOp =
2148        message.getSearchRequestProtocolOp();
2149    SearchOperationBasis searchOp =
2150        new SearchOperationBasis(this, nextOperationID
2151            .getAndIncrement(), message.getMessageID(), controls,
2152            protocolOp.getBaseDN(), protocolOp.getScope(), protocolOp
2153                .getDereferencePolicy(), protocolOp.getSizeLimit(),
2154            protocolOp.getTimeLimit(), protocolOp.getTypesOnly(),
2155            protocolOp.getFilter(), protocolOp.getAttributes());
2156
2157    // Add the operation into the work queue.
2158    try
2159    {
2160      addOperationInProgress(searchOp);
2161    }
2162    catch (DirectoryException de)
2163    {
2164      logger.traceException(de);
2165
2166      SearchResultDoneProtocolOp responseOp =
2167          new SearchResultDoneProtocolOp(de.getResultCode().intValue(),
2168              de.getMessageObject(), de.getMatchedDN(), de.getReferralURLs());
2169
2170      sendLDAPMessage(new LDAPMessage(message.getMessageID(),
2171          responseOp, searchOp.getResponseControls()));
2172    }
2173
2174    return connectionValid;
2175  }
2176
2177  /**
2178   * Processes the provided LDAP message as an unbind request.
2179   *
2180   * @param message
2181   *          The LDAP message containing the unbind request to process.
2182   * @param controls
2183   *          The set of pre-decoded request controls contained in the
2184   *          message.
2185   * @return <CODE>true</CODE> if the request was processed
2186   *         successfully, or <CODE>false</CODE> if not and the
2187   *         connection has been closed as a result (it is the
2188   *         responsibility of this method to close the connection).
2189   */
2190  private boolean processUnbindRequest(LDAPMessage message,
2191      List<Control> controls)
2192  {
2193    UnbindOperationBasis unbindOp =
2194        new UnbindOperationBasis(this, nextOperationID
2195            .getAndIncrement(), message.getMessageID(), controls);
2196
2197    unbindOp.run();
2198
2199    // The client connection will never be valid after an unbind.
2200    return false;
2201  }
2202
2203  @Override
2204  public String getMonitorSummary()
2205  {
2206    StringBuilder buffer = new StringBuilder();
2207    buffer.append("connID=\"");
2208    buffer.append(connectionID);
2209    buffer.append("\" connectTime=\"");
2210    buffer.append(getConnectTimeString());
2211    buffer.append("\" source=\"");
2212    buffer.append(clientAddress);
2213    buffer.append(":");
2214    buffer.append(clientPort);
2215    buffer.append("\" destination=\"");
2216    buffer.append(serverAddress);
2217    buffer.append(":");
2218    buffer.append(connectionHandler.getListenPort());
2219    buffer.append("\" ldapVersion=\"");
2220    buffer.append(ldapVersion);
2221    buffer.append("\" authDN=\"");
2222
2223    DN authDN = getAuthenticationInfo().getAuthenticationDN();
2224    if (authDN != null)
2225    {
2226      buffer.append(authDN);
2227    }
2228
2229    buffer.append("\" security=\"");
2230    if (isSecure())
2231    {
2232      if (tlsActiveProvider != null)
2233      {
2234        buffer.append(tlsActiveProvider.getName());
2235      }
2236      if (saslActiveProvider != null)
2237      {
2238        if (tlsActiveProvider != null)
2239        {
2240          buffer.append(",");
2241        }
2242        buffer.append(saslActiveProvider.getName());
2243      }
2244    }
2245    else
2246    {
2247      buffer.append("none");
2248    }
2249
2250    buffer.append("\" opsInProgress=\"");
2251    buffer.append(operationsInProgress.size());
2252    buffer.append("\"");
2253
2254    int countPSearch = getPersistentSearches().size();
2255    if (countPSearch > 0)
2256    {
2257      buffer.append(" persistentSearches=\"");
2258      buffer.append(countPSearch);
2259      buffer.append("\"");
2260    }
2261    return buffer.toString();
2262  }
2263
2264  /**
2265   * Appends a string representation of this client connection to the
2266   * provided buffer.
2267   *
2268   * @param buffer
2269   *          The buffer to which the information should be appended.
2270   */
2271  @Override
2272  public void toString(StringBuilder buffer)
2273  {
2274    buffer.append("LDAP client connection from ");
2275    buffer.append(clientAddress);
2276    buffer.append(":");
2277    buffer.append(clientPort);
2278    buffer.append(" to ");
2279    buffer.append(serverAddress);
2280    buffer.append(":");
2281    buffer.append(serverPort);
2282  }
2283
2284  @Override
2285  public boolean prepareTLS(LocalizableMessageBuilder unavailableReason)
2286  {
2287    if (tlsActiveProvider != null)
2288    {
2289      unavailableReason.append(ERR_LDAP_TLS_EXISTING_SECURITY_PROVIDER
2290          .get(tlsActiveProvider.getName()));
2291      return false;
2292    }
2293    // Make sure that the connection handler allows the use of the
2294    // StartTLS operation.
2295    if (!connectionHandler.allowStartTLS())
2296    {
2297      unavailableReason.append(ERR_LDAP_TLS_STARTTLS_NOT_ALLOWED.get());
2298      return false;
2299    }
2300    try
2301    {
2302      TLSByteChannel tlsByteChannel =
2303          connectionHandler.getTLSByteChannel(timeoutClientChannel);
2304      setTLSPendingProvider(tlsByteChannel);
2305    }
2306    catch (DirectoryException de)
2307    {
2308      logger.traceException(de);
2309      unavailableReason.append(ERR_LDAP_TLS_CANNOT_CREATE_TLS_PROVIDER
2310          .get(stackTraceToSingleLineString(de)));
2311      return false;
2312    }
2313    return true;
2314  }
2315
2316  /**
2317   * Retrieves the length of time in milliseconds that this client
2318   * connection has been idle. <BR>
2319   * <BR>
2320   * Note that the default implementation will always return zero.
2321   * Subclasses associated with connection handlers should override this
2322   * method if they wish to provided idle time limit functionality.
2323   *
2324   * @return The length of time in milliseconds that this client
2325   *         connection has been idle.
2326   */
2327  @Override
2328  public long getIdleTime()
2329  {
2330    if (operationsInProgress.isEmpty()
2331        && getPersistentSearches().isEmpty())
2332    {
2333      return TimeThread.getTime() - lastCompletionTime.get();
2334    }
2335    else
2336    {
2337      // There's at least one operation in progress, so it's not idle.
2338      return 0L;
2339    }
2340  }
2341
2342  /**
2343   * Set the connection provider that is not in use yet. Used in TLS
2344   * negotiation when a clear response is needed before the connection
2345   * provider is active.
2346   *
2347   * @param provider
2348   *          The provider that needs to be activated.
2349   */
2350  public void setTLSPendingProvider(ConnectionSecurityProvider provider)
2351  {
2352    tlsPendingProvider = provider;
2353  }
2354
2355  /**
2356   * Set the connection provider that is not in use. Used in SASL
2357   * negotiation when a clear response is needed before the connection
2358   * provider is active.
2359   *
2360   * @param provider
2361   *          The provider that needs to be activated.
2362   */
2363  public void setSASLPendingProvider(ConnectionSecurityProvider provider)
2364  {
2365    saslPendingProvider = provider;
2366  }
2367
2368  /** Enable the provider that is inactive. */
2369  private void enableTLS()
2370  {
2371    tlsActiveProvider = tlsPendingProvider;
2372    tlsChannel.redirect(tlsPendingProvider);
2373    tlsPendingProvider = null;
2374  }
2375
2376  /**
2377   * Set the security provider to the specified provider.
2378   *
2379   * @param sslProvider
2380   *          The provider to set the security provider to.
2381   */
2382  private void enableSSL(ConnectionSecurityProvider sslProvider)
2383  {
2384    tlsActiveProvider = sslProvider;
2385    tlsChannel.redirect(sslProvider);
2386  }
2387
2388  /** Enable the SASL provider that is currently inactive or pending. */
2389  private void enableSASL()
2390  {
2391    saslActiveProvider = saslPendingProvider;
2392    saslChannel.redirect(saslPendingProvider);
2393    saslPendingProvider = null;
2394  }
2395
2396  /**
2397   * Return the certificate chain array associated with a connection.
2398   *
2399   * @return The array of certificates associated with a connection.
2400   */
2401  public Certificate[] getClientCertificateChain()
2402  {
2403    if (tlsActiveProvider != null)
2404    {
2405      return tlsActiveProvider.getClientCertificateChain();
2406    }
2407    if (saslActiveProvider != null)
2408    {
2409      return saslActiveProvider.getClientCertificateChain();
2410    }
2411    return new Certificate[0];
2412  }
2413
2414  /**
2415   * Retrieves the TLS redirecting byte channel used in a LDAP client
2416   * connection.
2417   *
2418   * @return The TLS redirecting byte channel.
2419   */
2420   @Override
2421   public ByteChannel getChannel() {
2422     return this.tlsChannel;
2423   }
2424
2425  @Override
2426  public int getSSF()
2427  {
2428    int tlsSSF = tlsActiveProvider != null ? tlsActiveProvider.getSSF() : 0;
2429    int saslSSF = saslActiveProvider != null ? saslActiveProvider.getSSF() : 0;
2430    return Math.max(tlsSSF, saslSSF);
2431  }
2432
2433  @Override
2434  public void finishBind()
2435  {
2436    if (this.saslPendingProvider != null)
2437    {
2438      enableSASL();
2439    }
2440
2441    super.finishBind();
2442  }
2443
2444  @Override
2445  public void finishStartTLS()
2446  {
2447    if(this.tlsPendingProvider != null)
2448    {
2449      enableTLS();
2450    }
2451
2452    super.finishStartTLS();
2453  }
2454}