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