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