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 (Exception e)
989    {
990      logger.traceException(e);
991
992      // FIXME -- Log a message or something
993      disconnect(DisconnectReason.SERVER_ERROR, false, null);
994      return;
995    }
996    finally
997    {
998      // Clear and reset all of the internal buffers ready for the next usage.
999      // The ASN1Writer is based on a ByteStringBuilder so closing will cause
1000      // the internal buffers to be resized if needed.
1001      close(holder);
1002    }
1003 }
1004
1005
1006
1007  /**
1008   * Closes the connection to the client, optionally sending it a
1009   * message indicating the reason for the closure. Note that the
1010   * ability to send a notice of disconnection may not be available for
1011   * all protocols or under all circumstances.
1012   *
1013   * @param disconnectReason
1014   *          The disconnect reason that provides the generic cause for
1015   *          the disconnect.
1016   * @param sendNotification
1017   *          Indicates whether to try to provide notification to the
1018   *          client that the connection will be closed.
1019   * @param message
1020   *          The message to include in the disconnect notification
1021   *          response. It may be <CODE>null</CODE> if no message is to
1022   *          be sent.
1023   */
1024  @Override
1025  public void disconnect(DisconnectReason disconnectReason,
1026      boolean sendNotification, LocalizableMessage message)
1027  {
1028    // Set a flag indicating that the connection is being terminated so
1029    // that no new requests will be accepted. Also cancel all operations
1030    // in progress.
1031    synchronized (opsInProgressLock)
1032    {
1033      // If we are already in the middle of a disconnect, then don't
1034      // do anything.
1035      if (disconnectRequested)
1036      {
1037        return;
1038      }
1039
1040      disconnectRequested = true;
1041    }
1042
1043    if (keepStats)
1044    {
1045      statTracker.updateDisconnect();
1046    }
1047
1048    if (connectionID >= 0)
1049    {
1050      DirectoryServer.connectionClosed(this);
1051    }
1052
1053    // Indicate that this connection is no longer valid.
1054    connectionValid = false;
1055
1056    if (message != null)
1057    {
1058      LocalizableMessageBuilder msgBuilder = new LocalizableMessageBuilder();
1059      msgBuilder.append(disconnectReason.getClosureMessage());
1060      msgBuilder.append(": ");
1061      msgBuilder.append(message);
1062      cancelAllOperations(new CancelRequest(true, msgBuilder
1063          .toMessage()));
1064    }
1065    else
1066    {
1067      cancelAllOperations(new CancelRequest(true, disconnectReason
1068          .getClosureMessage()));
1069    }
1070    finalizeConnectionInternal();
1071
1072    // If there is a write selector for this connection, then close it.
1073    Selector selector = writeSelector.get();
1074    close(selector);
1075
1076    // See if we should send a notification to the client. If so, then
1077    // construct and send a notice of disconnection unsolicited
1078    // response. Note that we cannot send this notification to an LDAPv2 client.
1079    if (sendNotification && ldapVersion != 2)
1080    {
1081      try
1082      {
1083        int resultCode;
1084        switch (disconnectReason)
1085        {
1086        case PROTOCOL_ERROR:
1087          resultCode = LDAPResultCode.PROTOCOL_ERROR;
1088          break;
1089        case SERVER_SHUTDOWN:
1090          resultCode = LDAPResultCode.UNAVAILABLE;
1091          break;
1092        case SERVER_ERROR:
1093          resultCode = DirectoryServer.getServerErrorResultCode().intValue();
1094          break;
1095        case ADMIN_LIMIT_EXCEEDED:
1096        case IDLE_TIME_LIMIT_EXCEEDED:
1097        case MAX_REQUEST_SIZE_EXCEEDED:
1098        case IO_TIMEOUT:
1099          resultCode = LDAPResultCode.ADMIN_LIMIT_EXCEEDED;
1100          break;
1101        case CONNECTION_REJECTED:
1102          resultCode = LDAPResultCode.CONSTRAINT_VIOLATION;
1103          break;
1104        case INVALID_CREDENTIALS:
1105          resultCode = LDAPResultCode.INVALID_CREDENTIALS;
1106          break;
1107        default:
1108          resultCode = LDAPResultCode.OTHER;
1109          break;
1110        }
1111
1112        LocalizableMessage errMsg;
1113        if (message == null)
1114        {
1115          errMsg =
1116              INFO_LDAP_CLIENT_GENERIC_NOTICE_OF_DISCONNECTION.get();
1117        }
1118        else
1119        {
1120          errMsg = message;
1121        }
1122
1123        ExtendedResponseProtocolOp notificationOp =
1124            new ExtendedResponseProtocolOp(resultCode, errMsg, null,
1125                null, OID_NOTICE_OF_DISCONNECTION, null);
1126
1127        sendLDAPMessage(new LDAPMessage(0, notificationOp, null));
1128      }
1129      catch (Exception e)
1130      {
1131        // NYI -- Log a message indicating that we couldn't send the
1132        // notice of disconnection.
1133        logger.traceException(e);
1134      }
1135    }
1136
1137    // Enqueue the connection channels for closing by the finalizer.
1138    Runnable r = new ConnectionFinalizerJob(asn1Reader, clientChannel);
1139    connectionHandler.registerConnectionFinalizer(r);
1140
1141    // NYI -- Deregister the client connection from any server components that
1142    // might know about it.
1143
1144    // Log a disconnect message.
1145    logDisconnect(this, disconnectReason, message);
1146
1147    try
1148    {
1149      PluginConfigManager pluginManager =
1150          DirectoryServer.getPluginConfigManager();
1151      pluginManager.invokePostDisconnectPlugins(this, disconnectReason,
1152          message);
1153    }
1154    catch (Exception e)
1155    {
1156      logger.traceException(e);
1157    }
1158  }
1159
1160
1161
1162  /**
1163   * Retrieves the set of operations in progress for this client
1164   * connection. This list must not be altered by any caller.
1165   *
1166   * @return The set of operations in progress for this client
1167   *         connection.
1168   */
1169  @Override
1170  public Collection<Operation> getOperationsInProgress()
1171  {
1172    return operationsInProgress.values();
1173  }
1174
1175
1176
1177  /**
1178   * Retrieves the operation in progress with the specified message ID.
1179   *
1180   * @param messageID
1181   *          The message ID for the operation to retrieve.
1182   * @return The operation in progress with the specified message ID, or
1183   *         <CODE>null</CODE> if no such operation could be found.
1184   */
1185  @Override
1186  public Operation getOperationInProgress(int messageID)
1187  {
1188    return operationsInProgress.get(messageID);
1189  }
1190
1191
1192
1193  /**
1194   * Adds the provided operation to the set of operations in progress
1195   * for this client connection.
1196   *
1197   * @param operation
1198   *          The operation to add to the set of operations in progress
1199   *          for this client connection.
1200   * @throws DirectoryException
1201   *           If the operation is not added for some reason (e.g., the
1202   *           client already has reached the maximum allowed concurrent
1203   *           requests).
1204   */
1205  private void addOperationInProgress(Operation operation)
1206      throws DirectoryException
1207  {
1208    int messageID = operation.getMessageID();
1209
1210    // We need to grab a lock to ensure that no one else can add
1211    // operations to the queue while we are performing some preliminary
1212    // checks.
1213    try
1214    {
1215      synchronized (opsInProgressLock)
1216      {
1217        // If we're already in the process of disconnecting the client,
1218        // then reject the operation.
1219        if (disconnectRequested)
1220        {
1221          LocalizableMessage message = WARN_CLIENT_DISCONNECT_IN_PROGRESS.get();
1222          throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
1223              message);
1224        }
1225
1226        // Add the operation to the list of operations in progress for
1227        // this connection.
1228        Operation op = operationsInProgress.putIfAbsent(messageID, operation);
1229
1230        // See if there is already an operation in progress with the
1231        // same message ID. If so, then we can't allow it.
1232        if (op != null)
1233        {
1234          LocalizableMessage message =
1235            WARN_LDAP_CLIENT_DUPLICATE_MESSAGE_ID.get(messageID);
1236          throw new DirectoryException(ResultCode.PROTOCOL_ERROR,
1237              message);
1238        }
1239      }
1240
1241      // Try to add the operation to the work queue,
1242      // or run it synchronously (typically for the administration
1243      // connector)
1244      connectionHandler.getQueueingStrategy().enqueueRequest(
1245          operation);
1246    }
1247    catch (DirectoryException de)
1248    {
1249      logger.traceException(de);
1250
1251      operationsInProgress.remove(messageID);
1252      lastCompletionTime.set(TimeThread.getTime());
1253
1254      throw de;
1255    }
1256    catch (Exception e)
1257    {
1258      logger.traceException(e);
1259
1260      LocalizableMessage message =
1261        WARN_LDAP_CLIENT_CANNOT_ENQUEUE.get(getExceptionMessage(e));
1262      throw new DirectoryException(DirectoryServer
1263          .getServerErrorResultCode(), message, e);
1264    }
1265  }
1266
1267
1268
1269  /**
1270   * Removes the provided operation from the set of operations in
1271   * progress for this client connection. Note that this does not make
1272   * any attempt to cancel any processing that may already be in
1273   * progress for the operation.
1274   *
1275   * @param messageID
1276   *          The message ID of the operation to remove from the set of
1277   *          operations in progress.
1278   * @return <CODE>true</CODE> if the operation was found and removed
1279   *         from the set of operations in progress, or
1280   *         <CODE>false</CODE> if not.
1281   */
1282  @Override
1283  public boolean removeOperationInProgress(int messageID)
1284  {
1285    Operation operation = operationsInProgress.remove(messageID);
1286    if (operation == null)
1287    {
1288      return false;
1289    }
1290
1291    if (operation.getOperationType() == OperationType.ABANDON
1292        && keepStats
1293        && operation.getResultCode() == ResultCode.CANCELLED)
1294    {
1295      statTracker.updateAbandonedOperation();
1296    }
1297
1298    lastCompletionTime.set(TimeThread.getTime());
1299    return true;
1300  }
1301
1302
1303
1304  /**
1305   * Attempts to cancel the specified operation.
1306   *
1307   * @param messageID
1308   *          The message ID of the operation to cancel.
1309   * @param cancelRequest
1310   *          An object providing additional information about how the
1311   *          cancel should be processed.
1312   * @return A cancel result that either indicates that the cancel was
1313   *         successful or provides a reason that it was not.
1314   */
1315  @Override
1316  public CancelResult cancelOperation(int messageID,
1317      CancelRequest cancelRequest)
1318  {
1319    Operation op = operationsInProgress.get(messageID);
1320    if (op == null)
1321    {
1322      // See if the operation is in the list of persistent searches.
1323      for (PersistentSearch ps : getPersistentSearches())
1324      {
1325        if (ps.getMessageID() == messageID)
1326        {
1327          // We only need to find the first persistent search
1328          // associated with the provided message ID. The persistent
1329          // search will ensure that all other related persistent
1330          // searches are cancelled.
1331          return ps.cancel();
1332        }
1333      }
1334
1335      return new CancelResult(ResultCode.NO_SUCH_OPERATION, null);
1336    }
1337    else
1338    {
1339      return op.cancel(cancelRequest);
1340    }
1341  }
1342
1343
1344
1345  /**
1346   * Attempts to cancel all operations in progress on this connection.
1347   *
1348   * @param cancelRequest
1349   *          An object providing additional information about how the
1350   *          cancel should be processed.
1351   */
1352  @Override
1353  public void cancelAllOperations(CancelRequest cancelRequest)
1354  {
1355    // Make sure that no one can add any new operations.
1356    synchronized (opsInProgressLock)
1357    {
1358      try
1359      {
1360        for (Operation o : operationsInProgress.values())
1361        {
1362          try
1363          {
1364            o.abort(cancelRequest);
1365
1366            // TODO: Assume its cancelled?
1367            if (keepStats)
1368            {
1369              statTracker.updateAbandonedOperation();
1370            }
1371          }
1372          catch (Exception e)
1373          {
1374            logger.traceException(e);
1375          }
1376        }
1377
1378        if (!operationsInProgress.isEmpty()
1379            || !getPersistentSearches().isEmpty())
1380        {
1381          lastCompletionTime.set(TimeThread.getTime());
1382        }
1383
1384        operationsInProgress.clear();
1385
1386        for (PersistentSearch persistentSearch : getPersistentSearches())
1387        {
1388          persistentSearch.cancel();
1389        }
1390      }
1391      catch (Exception e)
1392      {
1393        logger.traceException(e);
1394      }
1395    }
1396  }
1397
1398
1399
1400  /**
1401   * Attempts to cancel all operations in progress on this connection
1402   * except the operation with the specified message ID.
1403   *
1404   * @param cancelRequest
1405   *          An object providing additional information about how the
1406   *          cancel should be processed.
1407   * @param messageID
1408   *          The message ID of the operation that should not be
1409   *          canceled.
1410   */
1411  @Override
1412  public void cancelAllOperationsExcept(CancelRequest cancelRequest,
1413      int messageID)
1414  {
1415    // Make sure that no one can add any new operations.
1416    synchronized (opsInProgressLock)
1417    {
1418      try
1419      {
1420        for (int msgID : operationsInProgress.keySet())
1421        {
1422          if (msgID == messageID)
1423          {
1424            continue;
1425          }
1426
1427          Operation o = operationsInProgress.get(msgID);
1428          if (o != null)
1429          {
1430            try
1431            {
1432              o.abort(cancelRequest);
1433
1434              // TODO: Assume its cancelled?
1435              if (keepStats)
1436              {
1437                statTracker.updateAbandonedOperation();
1438              }
1439            }
1440            catch (Exception e)
1441            {
1442              logger.traceException(e);
1443            }
1444          }
1445
1446          operationsInProgress.remove(msgID);
1447          lastCompletionTime.set(TimeThread.getTime());
1448        }
1449
1450        for (PersistentSearch persistentSearch : getPersistentSearches())
1451        {
1452          if (persistentSearch.getMessageID() == messageID)
1453          {
1454            continue;
1455          }
1456
1457          persistentSearch.cancel();
1458          lastCompletionTime.set(TimeThread.getTime());
1459        }
1460      }
1461      catch (Exception e)
1462      {
1463        logger.traceException(e);
1464      }
1465    }
1466  }
1467
1468
1469
1470  /** {@inheritDoc} */
1471  @Override
1472  public Selector getWriteSelector()
1473  {
1474    Selector selector = writeSelector.get();
1475    if (selector == null)
1476    {
1477      try
1478      {
1479        selector = Selector.open();
1480        if (!writeSelector.compareAndSet(null, selector))
1481        {
1482          selector.close();
1483          selector = writeSelector.get();
1484        }
1485      }
1486      catch (Exception e)
1487      {
1488        logger.traceException(e);
1489      }
1490    }
1491
1492    return selector;
1493  }
1494
1495
1496
1497  /** {@inheritDoc} */
1498  @Override
1499  public long getMaxBlockedWriteTimeLimit()
1500  {
1501    return connectionHandler.getMaxBlockedWriteTimeLimit();
1502  }
1503
1504
1505
1506  /**
1507   * Returns the total number of operations initiated on this
1508   * connection.
1509   *
1510   * @return the total number of operations on this connection
1511   */
1512  @Override
1513  public long getNumberOfOperations()
1514  {
1515    return operationsPerformed.get();
1516  }
1517
1518
1519
1520  /**
1521   * Returns the ASN1 reader for this connection.
1522   *
1523   * @return the ASN1 reader for this connection
1524   */
1525  ASN1ByteChannelReader getASN1Reader()
1526  {
1527    return asn1Reader;
1528  }
1529
1530
1531
1532  /**
1533   * Process data read.
1534   *
1535   * @return number of bytes read if this connection is still valid
1536   *         or negative integer to indicate an error otherwise
1537   */
1538  int processDataRead()
1539  {
1540    if (bindOrStartTLSInProgress.get())
1541    {
1542      // We should wait for the bind or startTLS to finish before
1543      // reading any more data off the socket.
1544      return 0;
1545    }
1546
1547    try
1548    {
1549      int result = asn1Reader.processChannelData();
1550      if (result < 0)
1551      {
1552        // The connection has been closed by the client. Disconnect
1553        // and return.
1554        disconnect(DisconnectReason.CLIENT_DISCONNECT, false, null);
1555        return -1;
1556      }
1557      return result;
1558    }
1559    catch (Exception e)
1560    {
1561      logger.traceException(e);
1562
1563      if (asn1Reader.hasRemainingData() || e instanceof SSLException)
1564      {
1565        // The connection failed, but there was an unread partial message so
1566        // interpret this as an IO error.
1567        LocalizableMessage m = ERR_LDAP_CLIENT_IO_ERROR_DURING_READ.get(e);
1568        disconnect(DisconnectReason.IO_ERROR, true, m);
1569      }
1570      else
1571      {
1572        // The connection failed and there was no unread data, so interpret this
1573        // as indicating that the client aborted (reset) the connection. This
1574        // happens when a client configures closes a connection which has been
1575        // configured with SO_LINGER set to 0.
1576        LocalizableMessage m = ERR_LDAP_CLIENT_IO_ERROR_BEFORE_READ.get();
1577        disconnect(DisconnectReason.CLIENT_DISCONNECT, true, m);
1578      }
1579
1580      return -1;
1581    }
1582  }
1583
1584
1585
1586  /**
1587   * Processes the provided LDAP message read from the client and takes
1588   * whatever action is appropriate. For most requests, this will
1589   * include placing the operation in the work queue. Certain requests
1590   * (in particular, abandons and unbinds) will be processed directly.
1591   *
1592   * @param message
1593   *          The LDAP message to process.
1594   * @return <CODE>true</CODE> if the appropriate action was taken for
1595   *         the request, or <CODE>false</CODE> if there was a fatal
1596   *         error and the client has been disconnected as a result, or
1597   *         if the client unbound from the server.
1598   */
1599  boolean processLDAPMessage(LDAPMessage message)
1600  {
1601    if (keepStats)
1602    {
1603      statTracker.updateMessageRead(message);
1604    }
1605    operationsPerformed.getAndIncrement();
1606
1607    List<Control> opControls = message.getControls();
1608
1609    // FIXME -- See if there is a bind in progress. If so, then deny
1610    // most kinds of operations.
1611
1612    // Figure out what type of operation we're dealing with based on the
1613    // LDAP message. Abandon and unbind requests will be processed here.
1614    // All other types of requests will be encapsulated into operations
1615    // and append into the work queue to be picked up by a worker
1616    // thread. Any other kinds of LDAP messages (e.g., response
1617    // messages) are illegal and will result in the connection being
1618    // terminated.
1619    try
1620    {
1621      if(bindOrStartTLSInProgress.get() ||
1622          (saslBindInProgress.get() &&
1623              message.getProtocolOpType() != OP_TYPE_BIND_REQUEST))
1624      {
1625        throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
1626            ERR_ENQUEUE_BIND_IN_PROGRESS.get());
1627      }
1628
1629      boolean result;
1630      switch (message.getProtocolOpType())
1631      {
1632      case OP_TYPE_ABANDON_REQUEST:
1633        result = processAbandonRequest(message, opControls);
1634        return result;
1635      case OP_TYPE_ADD_REQUEST:
1636        result = processAddRequest(message, opControls);
1637        return result;
1638      case OP_TYPE_BIND_REQUEST:
1639        bindOrStartTLSInProgress.set(true);
1640        if(message.getBindRequestProtocolOp().
1641            getAuthenticationType() == AuthenticationType.SASL)
1642        {
1643          saslBindInProgress.set(true);
1644        }
1645        result = processBindRequest(message, opControls);
1646        if(!result)
1647        {
1648          bindOrStartTLSInProgress.set(false);
1649          if(message.getBindRequestProtocolOp().
1650              getAuthenticationType() == AuthenticationType.SASL)
1651          {
1652            saslBindInProgress.set(false);
1653          }
1654        }
1655        return result;
1656      case OP_TYPE_COMPARE_REQUEST:
1657        result = processCompareRequest(message, opControls);
1658        return result;
1659      case OP_TYPE_DELETE_REQUEST:
1660        result = processDeleteRequest(message, opControls);
1661        return result;
1662      case OP_TYPE_EXTENDED_REQUEST:
1663        if(message.getExtendedRequestProtocolOp().getOID().equals(
1664            OID_START_TLS_REQUEST))
1665        {
1666          bindOrStartTLSInProgress.set(true);
1667        }
1668        result = processExtendedRequest(message, opControls);
1669        if(!result &&
1670            message.getExtendedRequestProtocolOp().getOID().equals(
1671                OID_START_TLS_REQUEST))
1672        {
1673          bindOrStartTLSInProgress.set(false);
1674        }
1675        return result;
1676      case OP_TYPE_MODIFY_REQUEST:
1677        result = processModifyRequest(message, opControls);
1678        return result;
1679      case OP_TYPE_MODIFY_DN_REQUEST:
1680        result = processModifyDNRequest(message, opControls);
1681        return result;
1682      case OP_TYPE_SEARCH_REQUEST:
1683        result = processSearchRequest(message, opControls);
1684        return result;
1685      case OP_TYPE_UNBIND_REQUEST:
1686        result = processUnbindRequest(message, opControls);
1687        return result;
1688      default:
1689        LocalizableMessage msg =
1690            ERR_LDAP_DISCONNECT_DUE_TO_INVALID_REQUEST_TYPE.get(message
1691                .getProtocolOpName(), message.getMessageID());
1692        disconnect(DisconnectReason.PROTOCOL_ERROR, true, msg);
1693        return false;
1694      }
1695    }
1696    catch (Exception e)
1697    {
1698      logger.traceException(e);
1699
1700      LocalizableMessage msg =
1701          ERR_LDAP_DISCONNECT_DUE_TO_PROCESSING_FAILURE.get(message
1702              .getProtocolOpName(), message.getMessageID(), e);
1703      disconnect(DisconnectReason.SERVER_ERROR, true, msg);
1704      return false;
1705    }
1706  }
1707
1708
1709
1710  /**
1711   * Processes the provided LDAP message as an abandon request.
1712   *
1713   * @param message
1714   *          The LDAP message containing the abandon request to
1715   *          process.
1716   * @param controls
1717   *          The set of pre-decoded request controls contained in the
1718   *          message.
1719   * @return <CODE>true</CODE> if the request was processed
1720   *         successfully, or <CODE>false</CODE> if not and the
1721   *         connection has been closed as a result (it is the
1722   *         responsibility of this method to close the connection).
1723   */
1724  private boolean processAbandonRequest(LDAPMessage message, List<Control> controls)
1725  {
1726    if (ldapVersion == 2 && controls != null && !controls.isEmpty())
1727    {
1728      // LDAPv2 clients aren't allowed to send controls.
1729      disconnect(DisconnectReason.PROTOCOL_ERROR, false,
1730          ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get());
1731      return false;
1732    }
1733
1734    // Create the abandon operation and add it into the work queue.
1735    AbandonRequestProtocolOp protocolOp =
1736        message.getAbandonRequestProtocolOp();
1737    AbandonOperationBasis abandonOp =
1738        new AbandonOperationBasis(this, nextOperationID
1739            .getAndIncrement(), message.getMessageID(), controls,
1740            protocolOp.getIDToAbandon());
1741
1742    try
1743    {
1744      addOperationInProgress(abandonOp);
1745    }
1746    catch (DirectoryException de)
1747    {
1748      logger.traceException(de);
1749
1750      // Don't send an error response since abandon operations
1751      // don't have a response.
1752    }
1753
1754    return connectionValid;
1755  }
1756
1757
1758
1759  /**
1760   * Processes the provided LDAP message as an add request.
1761   *
1762   * @param message
1763   *          The LDAP message containing the add request to process.
1764   * @param controls
1765   *          The set of pre-decoded request controls contained in the
1766   *          message.
1767   * @return <CODE>true</CODE> if the request was processed
1768   *         successfully, or <CODE>false</CODE> if not and the
1769   *         connection has been closed as a result (it is the
1770   *         responsibility of this method to close the connection).
1771   */
1772  private boolean processAddRequest(LDAPMessage message, List<Control> controls)
1773  {
1774    if (ldapVersion == 2 && controls != null && !controls.isEmpty())
1775    {
1776      // LDAPv2 clients aren't allowed to send controls.
1777      AddResponseProtocolOp responseOp =
1778          new AddResponseProtocolOp(LDAPResultCode.PROTOCOL_ERROR,
1779              ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get());
1780      sendLDAPMessage(new LDAPMessage(message.getMessageID(),
1781          responseOp));
1782      disconnect(DisconnectReason.PROTOCOL_ERROR, false,
1783          ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get());
1784      return false;
1785    }
1786
1787    // Create the add operation and add it into the work queue.
1788    AddRequestProtocolOp protocolOp = message.getAddRequestProtocolOp();
1789    AddOperationBasis addOp =
1790        new AddOperationBasis(this, nextOperationID.getAndIncrement(),
1791            message.getMessageID(), controls, protocolOp.getDN(),
1792            protocolOp.getAttributes());
1793
1794    try
1795    {
1796      addOperationInProgress(addOp);
1797    }
1798    catch (DirectoryException de)
1799    {
1800      logger.traceException(de);
1801
1802      AddResponseProtocolOp responseOp =
1803          new AddResponseProtocolOp(de.getResultCode().intValue(),
1804              de.getMessageObject(), de.getMatchedDN(), de
1805                  .getReferralURLs());
1806
1807      sendLDAPMessage(new LDAPMessage(message.getMessageID(),
1808          responseOp, addOp.getResponseControls()));
1809    }
1810
1811    return connectionValid;
1812  }
1813
1814
1815
1816  /**
1817   * Processes the provided LDAP message as a bind request.
1818   *
1819   * @param message
1820   *          The LDAP message containing the bind request to process.
1821   * @param controls
1822   *          The set of pre-decoded request controls contained in the
1823   *          message.
1824   * @return <CODE>true</CODE> if the request was processed
1825   *         successfully, or <CODE>false</CODE> if not and the
1826   *         connection has been closed as a result (it is the
1827   *         responsibility of this method to close the connection).
1828   */
1829  private boolean processBindRequest(LDAPMessage message,
1830      List<Control> controls)
1831  {
1832    BindRequestProtocolOp protocolOp =
1833        message.getBindRequestProtocolOp();
1834
1835    // See if this is an LDAPv2 bind request, and if so whether that
1836    // should be allowed.
1837    String versionString;
1838    switch (ldapVersion = protocolOp.getProtocolVersion())
1839    {
1840    case 2:
1841      versionString = "2";
1842
1843      if (!connectionHandler.allowLDAPv2())
1844      {
1845        BindResponseProtocolOp responseOp =
1846            new BindResponseProtocolOp(
1847                LDAPResultCode.PROTOCOL_ERROR,
1848                ERR_LDAPV2_CLIENTS_NOT_ALLOWED.get());
1849        sendLDAPMessage(new LDAPMessage(message.getMessageID(),
1850            responseOp));
1851        disconnect(DisconnectReason.PROTOCOL_ERROR, false,
1852            ERR_LDAPV2_CLIENTS_NOT_ALLOWED.get());
1853        return false;
1854      }
1855
1856      if (controls != null && !controls.isEmpty())
1857      {
1858        // LDAPv2 clients aren't allowed to send controls.
1859        BindResponseProtocolOp responseOp =
1860            new BindResponseProtocolOp(LDAPResultCode.PROTOCOL_ERROR,
1861                ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get());
1862        sendLDAPMessage(new LDAPMessage(message.getMessageID(),
1863            responseOp));
1864        disconnect(DisconnectReason.PROTOCOL_ERROR, false,
1865            ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get());
1866        return false;
1867      }
1868
1869      break;
1870    case 3:
1871      versionString = "3";
1872      break;
1873    default:
1874      // Unsupported protocol version. RFC4511 states that we MUST send
1875      // a protocol error back to the client.
1876      BindResponseProtocolOp responseOp =
1877          new BindResponseProtocolOp(LDAPResultCode.PROTOCOL_ERROR,
1878              ERR_LDAP_UNSUPPORTED_PROTOCOL_VERSION.get(ldapVersion));
1879      sendLDAPMessage(new LDAPMessage(message.getMessageID(),
1880          responseOp));
1881      disconnect(DisconnectReason.PROTOCOL_ERROR, false,
1882          ERR_LDAP_UNSUPPORTED_PROTOCOL_VERSION.get(ldapVersion));
1883      return false;
1884    }
1885
1886    ByteString bindDN = protocolOp.getDN();
1887
1888    BindOperationBasis bindOp;
1889    switch (protocolOp.getAuthenticationType())
1890    {
1891    case SIMPLE:
1892      bindOp =
1893          new BindOperationBasis(this, nextOperationID
1894              .getAndIncrement(), message.getMessageID(), controls,
1895              versionString, bindDN, protocolOp.getSimplePassword());
1896      break;
1897    case SASL:
1898      bindOp =
1899          new BindOperationBasis(this, nextOperationID
1900              .getAndIncrement(), message.getMessageID(), controls,
1901              versionString, bindDN, protocolOp.getSASLMechanism(),
1902              protocolOp.getSASLCredentials());
1903      break;
1904    default:
1905      // This is an invalid authentication type, and therefore a
1906      // protocol error. As per RFC 2251, a protocol error in a bind
1907      // request must result in terminating the connection.
1908      LocalizableMessage msg =
1909          ERR_LDAP_INVALID_BIND_AUTH_TYPE.get(message.getMessageID(),
1910              protocolOp.getAuthenticationType());
1911      disconnect(DisconnectReason.PROTOCOL_ERROR, true, msg);
1912      return false;
1913    }
1914
1915    // Add the operation into the work queue.
1916    try
1917    {
1918      addOperationInProgress(bindOp);
1919    }
1920    catch (DirectoryException de)
1921    {
1922      logger.traceException(de);
1923
1924      BindResponseProtocolOp responseOp =
1925          new BindResponseProtocolOp(de.getResultCode().intValue(),
1926              de.getMessageObject(), de.getMatchedDN(), de
1927                  .getReferralURLs());
1928
1929      sendLDAPMessage(new LDAPMessage(message.getMessageID(),
1930          responseOp, bindOp.getResponseControls()));
1931
1932      // If it was a protocol error, then terminate the connection.
1933      if (de.getResultCode() == ResultCode.PROTOCOL_ERROR)
1934      {
1935        LocalizableMessage msg =
1936            ERR_LDAP_DISCONNECT_DUE_TO_BIND_PROTOCOL_ERROR.get(message
1937                .getMessageID(), de.getMessageObject());
1938        disconnect(DisconnectReason.PROTOCOL_ERROR, true, msg);
1939      }
1940    }
1941
1942    return connectionValid;
1943  }
1944
1945
1946
1947  /**
1948   * Processes the provided LDAP message as a compare request.
1949   *
1950   * @param message
1951   *          The LDAP message containing the compare request to
1952   *          process.
1953   * @param controls
1954   *          The set of pre-decoded request controls contained in the
1955   *          message.
1956   * @return <CODE>true</CODE> if the request was processed
1957   *         successfully, or <CODE>false</CODE> if not and the
1958   *         connection has been closed as a result (it is the
1959   *         responsibility of this method to close the connection).
1960   */
1961  private boolean processCompareRequest(LDAPMessage message, List<Control> controls)
1962  {
1963    if (ldapVersion == 2 && controls != null && !controls.isEmpty())
1964    {
1965      // LDAPv2 clients aren't allowed to send controls.
1966      CompareResponseProtocolOp responseOp =
1967          new CompareResponseProtocolOp(LDAPResultCode.PROTOCOL_ERROR,
1968              ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get());
1969      sendLDAPMessage(new LDAPMessage(message.getMessageID(),
1970          responseOp));
1971      disconnect(DisconnectReason.PROTOCOL_ERROR, false,
1972          ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get());
1973      return false;
1974    }
1975
1976    CompareRequestProtocolOp protocolOp =
1977        message.getCompareRequestProtocolOp();
1978    CompareOperationBasis compareOp =
1979        new CompareOperationBasis(this, nextOperationID
1980            .getAndIncrement(), message.getMessageID(), controls,
1981            protocolOp.getDN(), protocolOp.getAttributeType(),
1982            protocolOp.getAssertionValue());
1983
1984    // Add the operation into the work queue.
1985    try
1986    {
1987      addOperationInProgress(compareOp);
1988    }
1989    catch (DirectoryException de)
1990    {
1991      logger.traceException(de);
1992
1993      CompareResponseProtocolOp responseOp =
1994          new CompareResponseProtocolOp(de.getResultCode().intValue(),
1995              de.getMessageObject(), de.getMatchedDN(), de.getReferralURLs());
1996
1997      sendLDAPMessage(new LDAPMessage(message.getMessageID(),
1998          responseOp, compareOp.getResponseControls()));
1999    }
2000
2001    return connectionValid;
2002  }
2003
2004
2005
2006  /**
2007   * Processes the provided LDAP message as a delete request.
2008   *
2009   * @param message
2010   *          The LDAP message containing the delete request to process.
2011   * @param controls
2012   *          The set of pre-decoded request controls contained in the
2013   *          message.
2014   * @return <CODE>true</CODE> if the request was processed
2015   *         successfully, or <CODE>false</CODE> if not and the
2016   *         connection has been closed as a result (it is the
2017   *         responsibility of this method to close the connection).
2018   */
2019  private boolean processDeleteRequest(LDAPMessage message, List<Control> controls)
2020  {
2021    if (ldapVersion == 2 && controls != null && !controls.isEmpty())
2022    {
2023      // LDAPv2 clients aren't allowed to send controls.
2024      DeleteResponseProtocolOp responseOp =
2025          new DeleteResponseProtocolOp(LDAPResultCode.PROTOCOL_ERROR,
2026              ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get());
2027      sendLDAPMessage(new LDAPMessage(message.getMessageID(),
2028          responseOp));
2029      disconnect(DisconnectReason.PROTOCOL_ERROR, false,
2030          ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get());
2031      return false;
2032    }
2033
2034    DeleteRequestProtocolOp protocolOp =
2035        message.getDeleteRequestProtocolOp();
2036    DeleteOperationBasis deleteOp =
2037        new DeleteOperationBasis(this, nextOperationID
2038            .getAndIncrement(), message.getMessageID(), controls,
2039            protocolOp.getDN());
2040
2041    // Add the operation into the work queue.
2042    try
2043    {
2044      addOperationInProgress(deleteOp);
2045    }
2046    catch (DirectoryException de)
2047    {
2048      logger.traceException(de);
2049
2050      DeleteResponseProtocolOp responseOp =
2051          new DeleteResponseProtocolOp(
2052              de.getResultCode().intValue(), de.getMessageObject(),
2053              de.getMatchedDN(), de.getReferralURLs());
2054
2055      sendLDAPMessage(new LDAPMessage(message.getMessageID(),
2056          responseOp, deleteOp.getResponseControls()));
2057    }
2058
2059    return connectionValid;
2060  }
2061
2062
2063
2064  /**
2065   * Processes the provided LDAP message as an extended request.
2066   *
2067   * @param message
2068   *          The LDAP message containing the extended request to
2069   *          process.
2070   * @param controls
2071   *          The set of pre-decoded request controls contained in the
2072   *          message.
2073   * @return <CODE>true</CODE> if the request was processed
2074   *         successfully, or <CODE>false</CODE> if not and the
2075   *         connection has been closed as a result (it is the
2076   *         responsibility of this method to close the connection).
2077   */
2078  private boolean processExtendedRequest(LDAPMessage message,
2079      List<Control> controls)
2080  {
2081    // See if this is an LDAPv2 client. If it is, then they should not
2082    // be issuing extended requests. We can't send a response that we
2083    // can be sure they can understand, so we have no choice but to
2084    // close the connection.
2085    if (ldapVersion == 2)
2086    {
2087      LocalizableMessage msg =
2088          ERR_LDAPV2_EXTENDED_REQUEST_NOT_ALLOWED.get(
2089              getConnectionID(), message.getMessageID());
2090      logger.error(msg);
2091      disconnect(DisconnectReason.PROTOCOL_ERROR, false, msg);
2092      return false;
2093    }
2094
2095    // FIXME -- Do we need to handle certain types of request here?
2096    // -- StartTLS requests
2097    // -- Cancel requests
2098
2099    ExtendedRequestProtocolOp protocolOp =
2100        message.getExtendedRequestProtocolOp();
2101    ExtendedOperationBasis extendedOp =
2102        new ExtendedOperationBasis(this, nextOperationID
2103            .getAndIncrement(), message.getMessageID(), controls,
2104            protocolOp.getOID(), protocolOp.getValue());
2105
2106    // Add the operation into the work queue.
2107    try
2108    {
2109      addOperationInProgress(extendedOp);
2110    }
2111    catch (DirectoryException de)
2112    {
2113      logger.traceException(de);
2114
2115      ExtendedResponseProtocolOp responseOp =
2116          new ExtendedResponseProtocolOp(de.getResultCode().intValue(),
2117              de.getMessageObject(), de.getMatchedDN(), de.getReferralURLs());
2118
2119      sendLDAPMessage(new LDAPMessage(message.getMessageID(),
2120          responseOp, extendedOp.getResponseControls()));
2121    }
2122
2123    return connectionValid;
2124  }
2125
2126
2127
2128  /**
2129   * Processes the provided LDAP message as a modify request.
2130   *
2131   * @param message
2132   *          The LDAP message containing the modify request to process.
2133   * @param controls
2134   *          The set of pre-decoded request controls contained in the
2135   *          message.
2136   * @return <CODE>true</CODE> if the request was processed
2137   *         successfully, or <CODE>false</CODE> if not and the
2138   *         connection has been closed as a result (it is the
2139   *         responsibility of this method to close the connection).
2140   */
2141  private boolean processModifyRequest(LDAPMessage message, List<Control> controls)
2142  {
2143    if (ldapVersion == 2 && controls != null && !controls.isEmpty())
2144    {
2145      // LDAPv2 clients aren't allowed to send controls.
2146      ModifyResponseProtocolOp responseOp =
2147          new ModifyResponseProtocolOp(LDAPResultCode.PROTOCOL_ERROR,
2148              ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get());
2149      sendLDAPMessage(new LDAPMessage(message.getMessageID(),
2150          responseOp));
2151      disconnect(DisconnectReason.PROTOCOL_ERROR, false,
2152          ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get());
2153      return false;
2154    }
2155
2156    ModifyRequestProtocolOp protocolOp =
2157        message.getModifyRequestProtocolOp();
2158    ModifyOperationBasis modifyOp =
2159        new ModifyOperationBasis(this, nextOperationID
2160            .getAndIncrement(), message.getMessageID(), controls,
2161            protocolOp.getDN(), protocolOp.getModifications());
2162
2163    // Add the operation into the work queue.
2164    try
2165    {
2166      addOperationInProgress(modifyOp);
2167    }
2168    catch (DirectoryException de)
2169    {
2170      logger.traceException(de);
2171
2172      ModifyResponseProtocolOp responseOp =
2173          new ModifyResponseProtocolOp(
2174              de.getResultCode().intValue(), de.getMessageObject(),
2175              de.getMatchedDN(), de.getReferralURLs());
2176
2177      sendLDAPMessage(new LDAPMessage(message.getMessageID(),
2178          responseOp, modifyOp.getResponseControls()));
2179    }
2180
2181    return connectionValid;
2182  }
2183
2184
2185
2186  /**
2187   * Processes the provided LDAP message as a modify DN request.
2188   *
2189   * @param message
2190   *          The LDAP message containing the modify DN request to
2191   *          process.
2192   * @param controls
2193   *          The set of pre-decoded request controls contained in the
2194   *          message.
2195   * @return <CODE>true</CODE> if the request was processed
2196   *         successfully, or <CODE>false</CODE> if not and the
2197   *         connection has been closed as a result (it is the
2198   *         responsibility of this method to close the connection).
2199   */
2200  private boolean processModifyDNRequest(LDAPMessage message, List<Control> controls)
2201  {
2202    if (ldapVersion == 2 && controls != null && !controls.isEmpty())
2203    {
2204      // LDAPv2 clients aren't allowed to send controls.
2205      ModifyDNResponseProtocolOp responseOp =
2206          new ModifyDNResponseProtocolOp(LDAPResultCode.PROTOCOL_ERROR,
2207              ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get());
2208      sendLDAPMessage(new LDAPMessage(message.getMessageID(),
2209          responseOp));
2210      disconnect(DisconnectReason.PROTOCOL_ERROR, false,
2211          ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get());
2212      return false;
2213    }
2214
2215    ModifyDNRequestProtocolOp protocolOp =
2216        message.getModifyDNRequestProtocolOp();
2217    ModifyDNOperationBasis modifyDNOp =
2218        new ModifyDNOperationBasis(this, nextOperationID
2219            .getAndIncrement(), message.getMessageID(), controls,
2220            protocolOp.getEntryDN(), protocolOp.getNewRDN(), protocolOp
2221                .deleteOldRDN(), protocolOp.getNewSuperior());
2222
2223    // Add the operation into the work queue.
2224    try
2225    {
2226      addOperationInProgress(modifyDNOp);
2227    }
2228    catch (DirectoryException de)
2229    {
2230      logger.traceException(de);
2231
2232      ModifyDNResponseProtocolOp responseOp =
2233          new ModifyDNResponseProtocolOp(de.getResultCode().intValue(),
2234              de.getMessageObject(), de.getMatchedDN(), de.getReferralURLs());
2235
2236      sendLDAPMessage(new LDAPMessage(message.getMessageID(),
2237          responseOp, modifyDNOp.getResponseControls()));
2238    }
2239
2240    return connectionValid;
2241  }
2242
2243
2244
2245  /**
2246   * Processes the provided LDAP message as a search request.
2247   *
2248   * @param message
2249   *          The LDAP message containing the search request to process.
2250   * @param controls
2251   *          The set of pre-decoded request controls contained in the
2252   *          message.
2253   * @return <CODE>true</CODE> if the request was processed
2254   *         successfully, or <CODE>false</CODE> if not and the
2255   *         connection has been closed as a result (it is the
2256   *         responsibility of this method to close the connection).
2257   */
2258  private boolean processSearchRequest(LDAPMessage message,
2259      List<Control> controls)
2260  {
2261    if (ldapVersion == 2 && controls != null && !controls.isEmpty())
2262    {
2263      // LDAPv2 clients aren't allowed to send controls.
2264      SearchResultDoneProtocolOp responseOp =
2265          new SearchResultDoneProtocolOp(LDAPResultCode.PROTOCOL_ERROR,
2266              ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get());
2267      sendLDAPMessage(new LDAPMessage(message.getMessageID(),
2268          responseOp));
2269      disconnect(DisconnectReason.PROTOCOL_ERROR, false,
2270          ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get());
2271      return false;
2272    }
2273
2274    SearchRequestProtocolOp protocolOp =
2275        message.getSearchRequestProtocolOp();
2276    SearchOperationBasis searchOp =
2277        new SearchOperationBasis(this, nextOperationID
2278            .getAndIncrement(), message.getMessageID(), controls,
2279            protocolOp.getBaseDN(), protocolOp.getScope(), protocolOp
2280                .getDereferencePolicy(), protocolOp.getSizeLimit(),
2281            protocolOp.getTimeLimit(), protocolOp.getTypesOnly(),
2282            protocolOp.getFilter(), protocolOp.getAttributes());
2283
2284    // Add the operation into the work queue.
2285    try
2286    {
2287      addOperationInProgress(searchOp);
2288    }
2289    catch (DirectoryException de)
2290    {
2291      logger.traceException(de);
2292
2293      SearchResultDoneProtocolOp responseOp =
2294          new SearchResultDoneProtocolOp(de.getResultCode().intValue(),
2295              de.getMessageObject(), de.getMatchedDN(), de.getReferralURLs());
2296
2297      sendLDAPMessage(new LDAPMessage(message.getMessageID(),
2298          responseOp, searchOp.getResponseControls()));
2299    }
2300
2301    return connectionValid;
2302  }
2303
2304
2305
2306  /**
2307   * Processes the provided LDAP message as an unbind request.
2308   *
2309   * @param message
2310   *          The LDAP message containing the unbind request to process.
2311   * @param controls
2312   *          The set of pre-decoded request controls contained in the
2313   *          message.
2314   * @return <CODE>true</CODE> if the request was processed
2315   *         successfully, or <CODE>false</CODE> if not and the
2316   *         connection has been closed as a result (it is the
2317   *         responsibility of this method to close the connection).
2318   */
2319  private boolean processUnbindRequest(LDAPMessage message,
2320      List<Control> controls)
2321  {
2322    UnbindOperationBasis unbindOp =
2323        new UnbindOperationBasis(this, nextOperationID
2324            .getAndIncrement(), message.getMessageID(), controls);
2325
2326    unbindOp.run();
2327
2328    // The client connection will never be valid after an unbind.
2329    return false;
2330  }
2331
2332
2333
2334  /** {@inheritDoc} */
2335  @Override
2336  public String getMonitorSummary()
2337  {
2338    StringBuilder buffer = new StringBuilder();
2339    buffer.append("connID=\"");
2340    buffer.append(connectionID);
2341    buffer.append("\" connectTime=\"");
2342    buffer.append(getConnectTimeString());
2343    buffer.append("\" source=\"");
2344    buffer.append(clientAddress);
2345    buffer.append(":");
2346    buffer.append(clientPort);
2347    buffer.append("\" destination=\"");
2348    buffer.append(serverAddress);
2349    buffer.append(":");
2350    buffer.append(connectionHandler.getListenPort());
2351    buffer.append("\" ldapVersion=\"");
2352    buffer.append(ldapVersion);
2353    buffer.append("\" authDN=\"");
2354
2355    DN authDN = getAuthenticationInfo().getAuthenticationDN();
2356    if (authDN != null)
2357    {
2358      authDN.toString(buffer);
2359    }
2360
2361    buffer.append("\" security=\"");
2362    if (isSecure())
2363    {
2364      if (tlsActiveProvider != null)
2365      {
2366        buffer.append(tlsActiveProvider.getName());
2367      }
2368      if (saslActiveProvider != null)
2369      {
2370        if (tlsActiveProvider != null)
2371        {
2372          buffer.append(",");
2373        }
2374        buffer.append(saslActiveProvider.getName());
2375      }
2376    }
2377    else
2378    {
2379      buffer.append("none");
2380    }
2381
2382    buffer.append("\" opsInProgress=\"");
2383    buffer.append(operationsInProgress.size());
2384    buffer.append("\"");
2385
2386    int countPSearch = getPersistentSearches().size();
2387    if (countPSearch > 0)
2388    {
2389      buffer.append(" persistentSearches=\"");
2390      buffer.append(countPSearch);
2391      buffer.append("\"");
2392    }
2393    return buffer.toString();
2394  }
2395
2396
2397
2398  /**
2399   * Appends a string representation of this client connection to the
2400   * provided buffer.
2401   *
2402   * @param buffer
2403   *          The buffer to which the information should be appended.
2404   */
2405  @Override
2406  public void toString(StringBuilder buffer)
2407  {
2408    buffer.append("LDAP client connection from ");
2409    buffer.append(clientAddress);
2410    buffer.append(":");
2411    buffer.append(clientPort);
2412    buffer.append(" to ");
2413    buffer.append(serverAddress);
2414    buffer.append(":");
2415    buffer.append(serverPort);
2416  }
2417
2418
2419
2420  /** {@inheritDoc} */
2421  @Override
2422  public boolean prepareTLS(LocalizableMessageBuilder unavailableReason)
2423  {
2424    if (tlsActiveProvider != null)
2425    {
2426      unavailableReason.append(ERR_LDAP_TLS_EXISTING_SECURITY_PROVIDER
2427          .get(tlsActiveProvider.getName()));
2428      return false;
2429    }
2430    // Make sure that the connection handler allows the use of the
2431    // StartTLS operation.
2432    if (!connectionHandler.allowStartTLS())
2433    {
2434      unavailableReason.append(ERR_LDAP_TLS_STARTTLS_NOT_ALLOWED.get());
2435      return false;
2436    }
2437    try
2438    {
2439      TLSByteChannel tlsByteChannel =
2440          connectionHandler.getTLSByteChannel(timeoutClientChannel);
2441      setTLSPendingProvider(tlsByteChannel);
2442    }
2443    catch (DirectoryException de)
2444    {
2445      logger.traceException(de);
2446      unavailableReason.append(ERR_LDAP_TLS_CANNOT_CREATE_TLS_PROVIDER
2447          .get(stackTraceToSingleLineString(de)));
2448      return false;
2449    }
2450    return true;
2451  }
2452
2453
2454
2455  /**
2456   * Retrieves the length of time in milliseconds that this client
2457   * connection has been idle. <BR>
2458   * <BR>
2459   * Note that the default implementation will always return zero.
2460   * Subclasses associated with connection handlers should override this
2461   * method if they wish to provided idle time limit functionality.
2462   *
2463   * @return The length of time in milliseconds that this client
2464   *         connection has been idle.
2465   */
2466  @Override
2467  public long getIdleTime()
2468  {
2469    if (operationsInProgress.isEmpty()
2470        && getPersistentSearches().isEmpty())
2471    {
2472      return TimeThread.getTime() - lastCompletionTime.get();
2473    }
2474    else
2475    {
2476      // There's at least one operation in progress, so it's not idle.
2477      return 0L;
2478    }
2479  }
2480
2481
2482
2483  /**
2484   * Set the connection provider that is not in use yet. Used in TLS
2485   * negotiation when a clear response is needed before the connection
2486   * provider is active.
2487   *
2488   * @param provider
2489   *          The provider that needs to be activated.
2490   */
2491  public void setTLSPendingProvider(ConnectionSecurityProvider provider)
2492  {
2493    tlsPendingProvider = provider;
2494  }
2495
2496
2497
2498  /**
2499   * Set the connection provider that is not in use. Used in SASL
2500   * negotiation when a clear response is needed before the connection
2501   * provider is active.
2502   *
2503   * @param provider
2504   *          The provider that needs to be activated.
2505   */
2506  public void setSASLPendingProvider(ConnectionSecurityProvider provider)
2507  {
2508    saslPendingProvider = provider;
2509  }
2510
2511
2512
2513  /**
2514   * Enable the provider that is inactive.
2515   */
2516  private void enableTLS()
2517  {
2518    tlsActiveProvider = tlsPendingProvider;
2519    tlsChannel.redirect(tlsPendingProvider);
2520    tlsPendingProvider = null;
2521  }
2522
2523
2524
2525  /**
2526   * Set the security provider to the specified provider.
2527   *
2528   * @param sslProvider
2529   *          The provider to set the security provider to.
2530   */
2531  private void enableSSL(ConnectionSecurityProvider sslProvider)
2532  {
2533    tlsActiveProvider = sslProvider;
2534    tlsChannel.redirect(sslProvider);
2535  }
2536
2537
2538
2539  /**
2540   * Enable the SASL provider that is currently inactive or pending.
2541   */
2542  private void enableSASL()
2543  {
2544    saslActiveProvider = saslPendingProvider;
2545    saslChannel.redirect(saslPendingProvider);
2546    saslPendingProvider = null;
2547  }
2548
2549
2550
2551  /**
2552   * Return the certificate chain array associated with a connection.
2553   *
2554   * @return The array of certificates associated with a connection.
2555   */
2556  public Certificate[] getClientCertificateChain()
2557  {
2558    if (tlsActiveProvider != null)
2559    {
2560      return tlsActiveProvider.getClientCertificateChain();
2561    }
2562    if (saslActiveProvider != null)
2563    {
2564      return saslActiveProvider.getClientCertificateChain();
2565    }
2566    return new Certificate[0];
2567  }
2568
2569
2570
2571  /**
2572   * Retrieves the TLS redirecting byte channel used in a LDAP client
2573   * connection.
2574   *
2575   * @return The TLS redirecting byte channel.
2576   */
2577   @Override
2578   public ByteChannel getChannel() {
2579     return this.tlsChannel;
2580   }
2581
2582
2583
2584  /** {@inheritDoc} */
2585  @Override
2586  public int getSSF()
2587  {
2588    int tlsSSF = tlsActiveProvider != null ? tlsActiveProvider.getSSF() : 0;
2589    int saslSSF = saslActiveProvider != null ? saslActiveProvider.getSSF() : 0;
2590    return Math.max(tlsSSF, saslSSF);
2591  }
2592
2593
2594
2595  /** {@inheritDoc} */
2596  @Override
2597  public void finishBindOrStartTLS()
2598  {
2599    if(this.tlsPendingProvider != null)
2600    {
2601      enableTLS();
2602    }
2603
2604    if (this.saslPendingProvider != null)
2605    {
2606      enableSASL();
2607    }
2608
2609    super.finishBindOrStartTLS();
2610  }
2611}