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