001/*
002 * CDDL HEADER START
003 *
004 * The contents of this file are subject to the terms of the
005 * Common Development and Distribution License, Version 1.0 only
006 * (the "License").  You may not use this file except in compliance
007 * with the License.
008 *
009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
010 * or http://forgerock.org/license/CDDLv1.0.html.
011 * See the License for the specific language governing permissions
012 * and limitations under the License.
013 *
014 * When distributing Covered Code, include this CDDL HEADER in each
015 * file and include the License file at legal-notices/CDDLv1_0.txt.
016 * If applicable, add the following below this CDDL HEADER, with the
017 * fields enclosed by brackets "[]" replaced with your own identifying
018 * information:
019 *      Portions Copyright [yyyy] [name of copyright owner]
020 *
021 * CDDL HEADER END
022 *
023 *
024 *      Copyright 2006-2010 Sun Microsystems, Inc.
025 *      Portions Copyright 2011-2015 ForgeRock AS
026 */
027package org.opends.server.protocols.ldap;
028
029import static org.opends.messages.ProtocolMessages.*;
030import static org.opends.server.util.ServerConstants.*;
031import static org.opends.server.util.StaticUtils.*;
032
033import java.io.IOException;
034import java.net.InetAddress;
035import java.net.InetSocketAddress;
036import java.net.SocketException;
037import java.nio.channels.*;
038import java.util.*;
039import java.util.concurrent.Executors;
040import java.util.concurrent.ScheduledExecutorService;
041import java.util.concurrent.TimeUnit;
042
043import javax.net.ssl.KeyManager;
044import javax.net.ssl.SSLContext;
045import javax.net.ssl.SSLEngine;
046
047import org.forgerock.i18n.LocalizableMessage;
048import org.forgerock.i18n.slf4j.LocalizedLogger;
049import org.forgerock.opendj.config.server.ConfigChangeResult;
050import org.forgerock.opendj.config.server.ConfigException;
051import org.forgerock.opendj.ldap.AddressMask;
052import org.forgerock.opendj.ldap.ResultCode;
053import org.opends.server.admin.server.ConfigurationChangeListener;
054import org.opends.server.admin.std.server.ConnectionHandlerCfg;
055import org.opends.server.admin.std.server.LDAPConnectionHandlerCfg;
056import org.opends.server.api.*;
057import org.opends.server.api.plugin.PluginResult;
058import org.opends.server.core.DirectoryServer;
059import org.opends.server.core.PluginConfigManager;
060import org.opends.server.core.QueueingStrategy;
061import org.opends.server.core.ServerContext;
062import org.opends.server.core.WorkQueueStrategy;
063import org.opends.server.extensions.NullKeyManagerProvider;
064import org.opends.server.extensions.NullTrustManagerProvider;
065import org.opends.server.extensions.TLSByteChannel;
066import org.opends.server.monitors.ClientConnectionMonitorProvider;
067import org.opends.server.types.*;
068import org.opends.server.util.SelectableCertificateKeyManager;
069import org.opends.server.util.StaticUtils;
070
071/**
072 * This class defines a connection handler that will be used for communicating
073 * with clients over LDAP. It is actually implemented in two parts: as a
074 * connection handler and one or more request handlers. The connection handler
075 * is responsible for accepting new connections and registering each of them
076 * with a request handler. The request handlers then are responsible for reading
077 * requests from the clients and parsing them as operations. A single request
078 * handler may be used, but having multiple handlers might provide better
079 * performance in a multi-CPU system.
080 */
081public final class LDAPConnectionHandler extends
082    ConnectionHandler<LDAPConnectionHandlerCfg> implements
083    ConfigurationChangeListener<LDAPConnectionHandlerCfg>,
084    ServerShutdownListener, AlertGenerator
085{
086
087  /**
088   * Task run periodically by the connection finalizer.
089   */
090  private final class ConnectionFinalizerRunnable implements Runnable
091  {
092    @Override
093    public void run()
094    {
095      if (!connectionFinalizerActiveJobQueue.isEmpty())
096      {
097        for (Runnable r : connectionFinalizerActiveJobQueue)
098        {
099          r.run();
100        }
101        connectionFinalizerActiveJobQueue.clear();
102      }
103
104      // Switch the lists.
105      synchronized (connectionFinalizerLock)
106      {
107        List<Runnable> tmp = connectionFinalizerActiveJobQueue;
108        connectionFinalizerActiveJobQueue = connectionFinalizerPendingJobQueue;
109        connectionFinalizerPendingJobQueue = tmp;
110      }
111
112    }
113  }
114  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
115
116  /**
117   * Default friendly name for the LDAP connection handler.
118   */
119  private static final String DEFAULT_FRIENDLY_NAME = "LDAP Connection Handler";
120
121  /** SSL instance name used in context creation. */
122  private static final String SSL_CONTEXT_INSTANCE_NAME = "TLS";
123
124  /** The current configuration state. */
125  private LDAPConnectionHandlerCfg currentConfig;
126
127  /* Properties that cannot be modified dynamically */
128
129  /** The set of addresses on which to listen for new connections. */
130  private Set<InetAddress> listenAddresses;
131
132  /** The port on which this connection handler should listen for requests. */
133  private int listenPort;
134
135  /** The SSL client auth policy used by this connection handler. */
136  private SSLClientAuthPolicy sslClientAuthPolicy;
137
138  /** The backlog that will be used for the accept queue. */
139  private int backlog;
140
141  /** Indicates whether to allow the reuse address socket option. */
142  private boolean allowReuseAddress;
143
144  /**
145   * The number of request handlers that should be used for this connection
146   * handler.
147   */
148  private int numRequestHandlers;
149
150  /**
151   * Indicates whether the Directory Server is in the process of shutting down.
152   */
153  private volatile boolean shutdownRequested;
154
155  /* Internal LDAP connection handler state */
156
157  /** Indicates whether this connection handler is enabled. */
158  private boolean enabled;
159
160  /** The set of clients that are explicitly allowed access to the server. */
161  private Collection<AddressMask> allowedClients;
162
163  /**
164   * The set of clients that have been explicitly denied access to the server.
165   */
166  private Collection<AddressMask> deniedClients;
167
168  /**
169   * The index to the request handler that will be used for the next connection
170   * accepted by the server.
171   */
172  private int requestHandlerIndex;
173
174  /** The set of listeners for this connection handler. */
175  private List<HostPort> listeners;
176
177  /**
178   * The set of request handlers that are associated with this connection
179   * handler.
180   */
181  private LDAPRequestHandler[] requestHandlers;
182
183  /** The set of statistics collected for this connection handler. */
184  private LDAPStatistics statTracker;
185
186  /**
187   * The client connection monitor provider associated with this connection
188   * handler.
189   */
190  private ClientConnectionMonitorProvider connMonitor;
191
192  /**
193   * The selector that will be used to multiplex connection acceptance across
194   * multiple sockets by a single thread.
195   */
196  private Selector selector;
197
198  /** The unique name assigned to this connection handler. */
199  private String handlerName;
200
201  /** The protocol used by this connection handler. */
202  private String protocol;
203
204  /** Queueing strategy. */
205  private final QueueingStrategy queueingStrategy;
206
207  /**
208   * The condition variable that will be used by the start method to wait for
209   * the socket port to be opened and ready to process requests before
210   * returning.
211   */
212  private final Object waitListen = new Object();
213
214  /** The friendly name of this connection handler. */
215  private String friendlyName;
216
217  /**
218   * SSL context.
219   *
220   * @see LDAPConnectionHandler#sslEngine
221   */
222  private SSLContext sslContext;
223
224  /** The SSL engine is used for obtaining default SSL parameters. */
225  private SSLEngine sslEngine;
226
227  /**
228   * Connection finalizer thread.
229   * <p>
230   * This thread is defers closing clients for approximately 100ms. This gives
231   * the client a chance to close the connection themselves before the server
232   * thus avoiding leaving the server side in the TIME WAIT state.
233   */
234  private final Object connectionFinalizerLock = new Object();
235  private ScheduledExecutorService connectionFinalizer;
236  private List<Runnable> connectionFinalizerActiveJobQueue;
237  private List<Runnable> connectionFinalizerPendingJobQueue;
238
239
240
241  /**
242   * Creates a new instance of this LDAP connection handler. It must be
243   * initialized before it may be used.
244   */
245  public LDAPConnectionHandler()
246  {
247    this(new WorkQueueStrategy(), null); // Use name from configuration.
248  }
249
250
251
252  /**
253   * Creates a new instance of this LDAP connection handler, using a queueing
254   * strategy. It must be initialized before it may be used.
255   *
256   * @param strategy
257   *          Request handling strategy.
258   * @param friendlyName
259   *          The name of of this connection handler, or {@code null} if the
260   *          name should be taken from the configuration.
261   */
262  public LDAPConnectionHandler(QueueingStrategy strategy, String friendlyName)
263  {
264    super(friendlyName != null ? friendlyName : DEFAULT_FRIENDLY_NAME
265        + " Thread");
266
267    this.friendlyName = friendlyName;
268    this.queueingStrategy = strategy;
269
270    // No real implementation is required. Do all the work in the
271    // initializeConnectionHandler method.
272  }
273
274
275
276  /**
277   * Indicates whether this connection handler should allow interaction with
278   * LDAPv2 clients.
279   *
280   * @return <CODE>true</CODE> if LDAPv2 is allowed, or <CODE>false</CODE> if
281   *         not.
282   */
283  public boolean allowLDAPv2()
284  {
285    return currentConfig.isAllowLDAPV2();
286  }
287
288
289
290  /**
291   * Indicates whether this connection handler should allow the use of the
292   * StartTLS extended operation.
293   *
294   * @return <CODE>true</CODE> if StartTLS is allowed, or <CODE>false</CODE> if
295   *         not.
296   */
297  public boolean allowStartTLS()
298  {
299    return currentConfig.isAllowStartTLS() && !currentConfig.isUseSSL();
300  }
301
302
303
304  /** {@inheritDoc} */
305  @Override
306  public ConfigChangeResult applyConfigurationChange(
307      LDAPConnectionHandlerCfg config)
308  {
309    final ConfigChangeResult ccr = new ConfigChangeResult();
310
311    // Note that the following properties cannot be modified:
312    //
313    // * listen port and addresses
314    // * use ssl
315    // * ssl policy
316    // * ssl cert nickname
317    // * accept backlog
318    // * tcp reuse address
319    // * num request handler
320
321    // Clear the stat tracker if LDAPv2 is being enabled.
322    if (currentConfig.isAllowLDAPV2() != config.isAllowLDAPV2()
323        && config.isAllowLDAPV2())
324    {
325      statTracker.clearStatistics();
326    }
327
328    // Apply the changes.
329    currentConfig = config;
330    enabled = config.isEnabled();
331    allowedClients = config.getAllowedClient();
332    deniedClients = config.getDeniedClient();
333
334    // Reconfigure SSL if needed.
335    try
336    {
337      configureSSL(config);
338    }
339    catch (DirectoryException e)
340    {
341      logger.traceException(e);
342      ccr.setResultCode(e.getResultCode());
343      ccr.addMessage(e.getMessageObject());
344      return ccr;
345    }
346
347    if (config.isAllowLDAPV2())
348    {
349      DirectoryServer.registerSupportedLDAPVersion(2, this);
350    }
351    else
352    {
353      DirectoryServer.deregisterSupportedLDAPVersion(2, this);
354    }
355
356    return ccr;
357  }
358
359  private void configureSSL(LDAPConnectionHandlerCfg config)
360      throws DirectoryException
361  {
362    protocol = config.isUseSSL() ? "LDAPS" : "LDAP";
363    if (config.isUseSSL() || config.isAllowStartTLS())
364    {
365      sslContext = createSSLContext(config);
366      sslEngine = createSSLEngine(config, sslContext);
367    }
368    else
369    {
370      sslContext = null;
371      sslEngine = null;
372    }
373  }
374
375
376  /** {@inheritDoc} */
377  @Override
378  public void finalizeConnectionHandler(LocalizableMessage finalizeReason)
379  {
380    shutdownRequested = true;
381    currentConfig.removeLDAPChangeListener(this);
382
383    if (connMonitor != null)
384    {
385      DirectoryServer.deregisterMonitorProvider(connMonitor);
386    }
387
388    if (statTracker != null)
389    {
390      DirectoryServer.deregisterMonitorProvider(statTracker);
391    }
392
393    DirectoryServer.deregisterSupportedLDAPVersion(2, this);
394    DirectoryServer.deregisterSupportedLDAPVersion(3, this);
395
396    try
397    {
398      selector.wakeup();
399    }
400    catch (Exception e)
401    {
402      logger.traceException(e);
403    }
404
405    for (LDAPRequestHandler requestHandler : requestHandlers)
406    {
407      requestHandler.processServerShutdown(finalizeReason);
408    }
409
410    // Shutdown the connection finalizer and ensure that any pending
411    // unclosed connections are closed.
412    synchronized (connectionFinalizerLock)
413    {
414      connectionFinalizer.shutdown();
415      connectionFinalizer = null;
416
417      Runnable r = new ConnectionFinalizerRunnable();
418      r.run(); // Flush active queue.
419      r.run(); // Flush pending queue.
420    }
421  }
422
423
424
425  /**
426   * Retrieves information about the set of alerts that this generator may
427   * produce. The map returned should be between the notification type for a
428   * particular notification and the human-readable description for that
429   * notification. This alert generator must not generate any alerts with types
430   * that are not contained in this list.
431   *
432   * @return Information about the set of alerts that this generator may
433   *         produce.
434   */
435  @Override
436  public Map<String, String> getAlerts()
437  {
438    Map<String, String> alerts = new LinkedHashMap<>();
439
440    alerts.put(ALERT_TYPE_LDAP_CONNECTION_HANDLER_CONSECUTIVE_FAILURES,
441        ALERT_DESCRIPTION_LDAP_CONNECTION_HANDLER_CONSECUTIVE_FAILURES);
442    alerts.put(ALERT_TYPE_LDAP_CONNECTION_HANDLER_UNCAUGHT_ERROR,
443        ALERT_DESCRIPTION_LDAP_CONNECTION_HANDLER_UNCAUGHT_ERROR);
444
445    return alerts;
446  }
447
448
449
450  /**
451   * Retrieves the fully-qualified name of the Java class for this alert
452   * generator implementation.
453   *
454   * @return The fully-qualified name of the Java class for this alert generator
455   *         implementation.
456   */
457  @Override
458  public String getClassName()
459  {
460    return LDAPConnectionHandler.class.getName();
461  }
462
463
464
465  /**
466   * Retrieves the set of active client connections that have been established
467   * through this connection handler.
468   *
469   * @return The set of active client connections that have been established
470   *         through this connection handler.
471   */
472  @Override
473  public Collection<ClientConnection> getClientConnections()
474  {
475    List<ClientConnection> connectionList = new LinkedList<>();
476    for (LDAPRequestHandler requestHandler : requestHandlers)
477    {
478      connectionList.addAll(requestHandler.getClientConnections());
479    }
480    return connectionList;
481  }
482
483
484
485  /**
486   * Retrieves the DN of the configuration entry with which this alert generator
487   * is associated.
488   *
489   * @return The DN of the configuration entry with which this alert generator
490   *         is associated.
491   */
492  @Override
493  public DN getComponentEntryDN()
494  {
495    return currentConfig.dn();
496  }
497
498
499
500  /** {@inheritDoc} */
501  @Override
502  public String getConnectionHandlerName()
503  {
504    return handlerName;
505  }
506
507
508
509  /** {@inheritDoc} */
510  @Override
511  public Collection<String> getEnabledSSLCipherSuites()
512  {
513    final SSLEngine engine = sslEngine;
514    if (engine != null)
515    {
516      return Arrays.asList(engine.getEnabledCipherSuites());
517    }
518    return super.getEnabledSSLCipherSuites();
519  }
520
521
522
523  /** {@inheritDoc} */
524  @Override
525  public Collection<String> getEnabledSSLProtocols()
526  {
527    final SSLEngine engine = sslEngine;
528    if (engine != null)
529    {
530      return Arrays.asList(engine.getEnabledProtocols());
531    }
532    return super.getEnabledSSLProtocols();
533  }
534
535
536
537  /** {@inheritDoc} */
538  @Override
539  public Collection<HostPort> getListeners()
540  {
541    return listeners;
542  }
543
544
545
546  /**
547   * Retrieves the port on which this connection handler is listening for client
548   * connections.
549   *
550   * @return The port on which this connection handler is listening for client
551   *         connections.
552   */
553  public int getListenPort()
554  {
555    return listenPort;
556  }
557
558
559
560  /**
561   * Retrieves the maximum length of time in milliseconds that attempts to write
562   * to LDAP client connections should be allowed to block.
563   *
564   * @return The maximum length of time in milliseconds that attempts to write
565   *         to LDAP client connections should be allowed to block, or zero if
566   *         there should not be any limit imposed.
567   */
568  public long getMaxBlockedWriteTimeLimit()
569  {
570    return currentConfig.getMaxBlockedWriteTimeLimit();
571  }
572
573
574
575  /**
576   * Retrieves the maximum ASN.1 element value length that will be allowed by
577   * this connection handler.
578   *
579   * @return The maximum ASN.1 element value length that will be allowed by this
580   *         connection handler.
581   */
582  public int getMaxRequestSize()
583  {
584    return (int) currentConfig.getMaxRequestSize();
585  }
586
587
588
589  /**
590   * Retrieves the size in bytes of the LDAP response message write buffer
591   * defined for this connection handler.
592   *
593   * @return The size in bytes of the LDAP response message write buffer.
594   */
595  public int getBufferSize()
596  {
597    return (int) currentConfig.getBufferSize();
598  }
599
600
601
602  /** {@inheritDoc} */
603  @Override
604  public String getProtocol()
605  {
606    return protocol;
607  }
608
609
610
611  /** {@inheritDoc} */
612  @Override
613  public String getShutdownListenerName()
614  {
615    return handlerName;
616  }
617
618
619
620  /**
621   * Retrieves the SSL client authentication policy for this connection handler.
622   *
623   * @return The SSL client authentication policy for this connection handler.
624   */
625  public SSLClientAuthPolicy getSSLClientAuthPolicy()
626  {
627    return sslClientAuthPolicy;
628  }
629
630
631
632  /**
633   * Retrieves the set of statistics maintained by this connection handler.
634   *
635   * @return The set of statistics maintained by this connection handler.
636   */
637  public LDAPStatistics getStatTracker()
638  {
639    return statTracker;
640  }
641
642
643
644  /** {@inheritDoc} */
645  @Override
646  public void initializeConnectionHandler(ServerContext serverContext, LDAPConnectionHandlerCfg config)
647      throws ConfigException, InitializationException
648  {
649    if (friendlyName == null)
650    {
651      friendlyName = config.dn().rdn().getAttributeValue(0).toString();
652    }
653
654    // Open the selector.
655    try
656    {
657      selector = Selector.open();
658    }
659    catch (Exception e)
660    {
661      logger.traceException(e);
662
663      LocalizableMessage message = ERR_LDAP_CONNHANDLER_OPEN_SELECTOR_FAILED.get(
664          config.dn(), stackTraceToSingleLineString(e));
665      throw new InitializationException(message, e);
666    }
667
668    // Save this configuration for future reference.
669    currentConfig = config;
670    enabled = config.isEnabled();
671    requestHandlerIndex = 0;
672    allowedClients = config.getAllowedClient();
673    deniedClients = config.getDeniedClient();
674
675    // Configure SSL if needed.
676    try
677    {
678      // This call may disable the connector if wrong SSL settings
679      configureSSL(config);
680    }
681    catch (DirectoryException e)
682    {
683      logger.traceException(e);
684      throw new InitializationException(e.getMessageObject());
685    }
686
687    // Save properties that cannot be dynamically modified.
688    allowReuseAddress = config.isAllowTCPReuseAddress();
689    backlog = config.getAcceptBacklog();
690    listenAddresses = config.getListenAddress();
691    listenPort = config.getListenPort();
692    numRequestHandlers =
693        getNumRequestHandlers(config.getNumRequestHandlers(), friendlyName);
694
695    // Construct a unique name for this connection handler, and put
696    // together the set of listeners.
697    listeners = new LinkedList<>();
698    StringBuilder nameBuffer = new StringBuilder();
699    nameBuffer.append(friendlyName);
700    for (InetAddress a : listenAddresses)
701    {
702      listeners.add(new HostPort(a.getHostAddress(), listenPort));
703      nameBuffer.append(" ");
704      nameBuffer.append(a.getHostAddress());
705    }
706    nameBuffer.append(" port ");
707    nameBuffer.append(listenPort);
708    handlerName = nameBuffer.toString();
709
710    // Attempt to bind to the listen port on all configured addresses to
711    // verify whether the connection handler will be able to start.
712    LocalizableMessage errorMessage =
713        checkAnyListenAddressInUse(listenAddresses, listenPort,
714            allowReuseAddress, config.dn());
715    if (errorMessage != null)
716    {
717      logger.error(errorMessage);
718      throw new InitializationException(errorMessage);
719    }
720
721    // Create a system property to store the LDAP(S) port the server is
722    // listening to. This information can be displayed with jinfo.
723    System.setProperty(protocol + "_port", String.valueOf(listenPort));
724
725    // Create and start a connection finalizer thread for this
726    // connection handler.
727    connectionFinalizer = Executors
728        .newSingleThreadScheduledExecutor(new DirectoryThread.Factory(
729            "LDAP Connection Finalizer for connection handler " + toString()));
730
731    connectionFinalizerActiveJobQueue = new ArrayList<>();
732    connectionFinalizerPendingJobQueue = new ArrayList<>();
733
734    connectionFinalizer.scheduleWithFixedDelay(
735        new ConnectionFinalizerRunnable(), 100, 100, TimeUnit.MILLISECONDS);
736
737    // Create and start the request handlers.
738    requestHandlers = new LDAPRequestHandler[numRequestHandlers];
739    for (int i = 0; i < numRequestHandlers; i++)
740    {
741      requestHandlers[i] = new LDAPRequestHandler(this, i);
742    }
743
744    for (int i = 0; i < numRequestHandlers; i++)
745    {
746      requestHandlers[i].start();
747    }
748
749    // Register the set of supported LDAP versions.
750    DirectoryServer.registerSupportedLDAPVersion(3, this);
751    if (config.isAllowLDAPV2())
752    {
753      DirectoryServer.registerSupportedLDAPVersion(2, this);
754    }
755
756    // Create and register monitors.
757    statTracker = new LDAPStatistics(handlerName + " Statistics");
758    DirectoryServer.registerMonitorProvider(statTracker);
759
760    connMonitor = new ClientConnectionMonitorProvider(this);
761    DirectoryServer.registerMonitorProvider(connMonitor);
762
763    // Register this as a change listener.
764    config.addLDAPChangeListener(this);
765  }
766
767
768
769  /** {@inheritDoc} */
770  @Override
771  public boolean isConfigurationAcceptable(ConnectionHandlerCfg configuration,
772      List<LocalizableMessage> unacceptableReasons)
773  {
774    LDAPConnectionHandlerCfg config = (LDAPConnectionHandlerCfg) configuration;
775
776    if (currentConfig == null
777        || (!currentConfig.isEnabled() && config.isEnabled()))
778    {
779      // Attempt to bind to the listen port on all configured addresses to
780      // verify whether the connection handler will be able to start.
781      LocalizableMessage errorMessage =
782          checkAnyListenAddressInUse(config.getListenAddress(), config
783              .getListenPort(), config.isAllowTCPReuseAddress(), config.dn());
784      if (errorMessage != null)
785      {
786        unacceptableReasons.add(errorMessage);
787        return false;
788      }
789    }
790
791    if (config.isEnabled()
792        // Check that the SSL configuration is valid.
793        && (config.isUseSSL() || config.isAllowStartTLS()))
794    {
795      try
796      {
797        createSSLEngine(config, createSSLContext(config));
798      }
799      catch (DirectoryException e)
800      {
801        logger.traceException(e);
802
803        unacceptableReasons.add(e.getMessageObject());
804        return false;
805      }
806    }
807
808    return true;
809  }
810
811  /**
812   * Checks whether any listen address is in use for the given port. The check
813   * is performed by binding to each address and port.
814   *
815   * @param listenAddresses
816   *          the listen {@link InetAddress} to test
817   * @param listenPort
818   *          the listen port to test
819   * @param allowReuseAddress
820   *          whether addresses can be reused
821   * @param configEntryDN
822   *          the configuration entry DN
823   * @return an error message if at least one of the address is already in use,
824   *         null otherwise.
825   */
826  private LocalizableMessage checkAnyListenAddressInUse(
827      Collection<InetAddress> listenAddresses, int listenPort,
828      boolean allowReuseAddress, DN configEntryDN)
829  {
830    for (InetAddress a : listenAddresses)
831    {
832      try
833      {
834        if (StaticUtils.isAddressInUse(a, listenPort, allowReuseAddress))
835        {
836          throw new IOException(ERR_CONNHANDLER_ADDRESS_INUSE.get().toString());
837        }
838      }
839      catch (IOException e)
840      {
841        logger.traceException(e);
842        return ERR_CONNHANDLER_CANNOT_BIND.get("LDAP", configEntryDN, a.getHostAddress(), listenPort,
843            getExceptionMessage(e));
844      }
845    }
846    return null;
847  }
848
849
850  /** {@inheritDoc} */
851  @Override
852  public boolean isConfigurationChangeAcceptable(
853      LDAPConnectionHandlerCfg config, List<LocalizableMessage> unacceptableReasons)
854  {
855    return isConfigurationAcceptable(config, unacceptableReasons);
856  }
857
858
859
860  /**
861   * Indicates whether this connection handler should maintain usage statistics.
862   *
863   * @return <CODE>true</CODE> if this connection handler should maintain usage
864   *         statistics, or <CODE>false</CODE> if not.
865   */
866  public boolean keepStats()
867  {
868    return currentConfig.isKeepStats();
869  }
870
871
872
873  /** {@inheritDoc} */
874  @Override
875  public void processServerShutdown(LocalizableMessage reason)
876  {
877    shutdownRequested = true;
878
879    try
880    {
881      for (LDAPRequestHandler requestHandler : requestHandlers)
882      {
883        try
884        {
885          requestHandler.processServerShutdown(reason);
886        }
887        catch (Exception ignored)
888        {
889        }
890      }
891    }
892    catch (Exception ignored)
893    {
894    }
895  }
896
897
898
899  /** {@inheritDoc} */
900  @Override
901  public void start()
902  {
903    // The Directory Server start process should only return
904    // when the connection handlers port are fully opened
905    // and working. The start method therefore needs to wait for
906    // the created thread to
907    synchronized (waitListen)
908    {
909      super.start();
910
911      try
912      {
913        waitListen.wait();
914      }
915      catch (InterruptedException e)
916      {
917        // If something interrupted the start its probably better
918        // to return ASAP.
919      }
920    }
921  }
922
923
924
925  /**
926   * Operates in a loop, accepting new connections and ensuring that requests on
927   * those connections are handled properly.
928   */
929  @Override
930  public void run()
931  {
932    setName(handlerName);
933    boolean listening = false;
934    boolean starting = true;
935
936    while (!shutdownRequested)
937    {
938      // If this connection handler is not enabled, then just sleep
939      // for a bit and check again.
940      if (!enabled)
941      {
942        if (listening)
943        {
944          cleanUpSelector();
945          listening = false;
946
947          logger.info(NOTE_CONNHANDLER_STOPPED_LISTENING, handlerName);
948        }
949
950        if (starting)
951        {
952          // This may happen if there was an initialisation error
953          // which led to disable the connector.
954          // The main thread is waiting for the connector to listen
955          // on its port, which will not occur yet,
956          // so notify here to allow the server startup to complete.
957          synchronized (waitListen)
958          {
959            starting = false;
960            waitListen.notify();
961          }
962        }
963
964        StaticUtils.sleep(1000);
965        continue;
966      }
967
968      // If we have gotten here, then we are about to start listening
969      // for the first time since startup or since we were previously
970      // disabled. Make sure to start with a clean selector and then
971      // create all the listeners.
972      try
973      {
974        cleanUpSelector();
975
976        int numRegistered = registerChannels();
977
978        // At this point, the connection Handler either started
979        // correctly or failed to start but the start process
980        // should be notified and resume its work in any cases.
981        synchronized (waitListen)
982        {
983          waitListen.notify();
984        }
985
986        // If none of the listeners were created successfully, then
987        // consider the connection handler disabled and require
988        // administrative action before trying again.
989        if (numRegistered == 0)
990        {
991          logger.error(ERR_LDAP_CONNHANDLER_NO_ACCEPTORS, currentConfig.dn());
992
993          enabled = false;
994          continue;
995        }
996
997        listening = true;
998
999        // Enter a loop, waiting for new connections to arrive and
1000        // then accepting them as they come in.
1001        boolean lastIterationFailed = false;
1002        while (enabled && !shutdownRequested)
1003        {
1004          try
1005          {
1006            serveIncomingConnections();
1007
1008            lastIterationFailed = false;
1009          }
1010          catch (Exception e)
1011          {
1012            logger.traceException(e);
1013            logger.error(ERR_CONNHANDLER_CANNOT_ACCEPT_CONNECTION, friendlyName,
1014                currentConfig.dn(), getExceptionMessage(e));
1015
1016            if (lastIterationFailed)
1017            {
1018              // The last time through the accept loop we also
1019              // encountered a failure. Rather than enter a potential
1020              // infinite loop of failures, disable this acceptor and
1021              // log an error.
1022              LocalizableMessage message =
1023                  ERR_CONNHANDLER_CONSECUTIVE_ACCEPT_FAILURES.get(friendlyName,
1024                      currentConfig.dn(), stackTraceToSingleLineString(e));
1025              logger.error(message);
1026
1027              DirectoryServer.sendAlertNotification(this,
1028                  ALERT_TYPE_LDAP_CONNECTION_HANDLER_CONSECUTIVE_FAILURES,
1029                  message);
1030
1031              cleanUpSelector();
1032              enabled = false;
1033            }
1034            else
1035            {
1036              lastIterationFailed = true;
1037            }
1038          }
1039        }
1040
1041        if (shutdownRequested)
1042        {
1043          cleanUpSelector();
1044          selector.close();
1045          listening = false;
1046          enabled = false;
1047        }
1048
1049      }
1050      catch (Exception e)
1051      {
1052        logger.traceException(e);
1053
1054        // This is very bad because we failed outside the loop. The
1055        // only thing we can do here is log a message, send an alert,
1056        // and disable the selector until an administrator can figure
1057        // out what's going on.
1058        LocalizableMessage message =
1059            ERR_LDAP_CONNHANDLER_UNCAUGHT_ERROR.get(currentConfig.dn(), stackTraceToSingleLineString(e));
1060        logger.error(message);
1061
1062        DirectoryServer.sendAlertNotification(this,
1063            ALERT_TYPE_LDAP_CONNECTION_HANDLER_UNCAUGHT_ERROR, message);
1064
1065        cleanUpSelector();
1066        enabled = false;
1067      }
1068    }
1069  }
1070
1071
1072  /**
1073   * Serves the incoming connections.
1074   *
1075   * @throws IOException
1076   * @throws DirectoryException
1077   */
1078  private void serveIncomingConnections() throws IOException, DirectoryException
1079  {
1080    int selectorState = selector.select();
1081
1082    // We can't rely on return value of select to determine if any keys
1083    // are ready.
1084    // see http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4850373
1085    for (Iterator<SelectionKey> iterator =
1086        selector.selectedKeys().iterator(); iterator.hasNext();)
1087    {
1088      SelectionKey key = iterator.next();
1089      iterator.remove();
1090      if (key.isAcceptable())
1091      {
1092        // Accept the new client connection.
1093        ServerSocketChannel serverChannel = (ServerSocketChannel) key
1094            .channel();
1095        SocketChannel clientChannel = serverChannel.accept();
1096        if (clientChannel != null)
1097        {
1098          acceptConnection(clientChannel);
1099        }
1100      }
1101
1102      if (selectorState == 0 && enabled && !shutdownRequested
1103          && logger.isTraceEnabled())
1104      {
1105        // Selected keys was non empty but select() returned 0.
1106        // Log warning and hope it blocks on the next select() call.
1107        logger.trace("Selector.select() returned 0. "
1108            + "Selected Keys: %d, Interest Ops: %d, Ready Ops: %d ",
1109            selector.selectedKeys().size(), key.interestOps(),
1110            key.readyOps());
1111      }
1112    }
1113  }
1114
1115
1116  /**
1117   * Open channels for each listen address and register them against this
1118   * ConnectionHandler's {@link Selector}.
1119   *
1120   * @return the number of successfully registered channel
1121   */
1122  private int registerChannels()
1123  {
1124    int numRegistered = 0;
1125    for (InetAddress a : listenAddresses)
1126    {
1127      try
1128      {
1129        ServerSocketChannel channel = ServerSocketChannel.open();
1130        channel.socket().setReuseAddress(allowReuseAddress);
1131        channel.socket()
1132            .bind(new InetSocketAddress(a, listenPort), backlog);
1133        channel.configureBlocking(false);
1134        channel.register(selector, SelectionKey.OP_ACCEPT);
1135        numRegistered++;
1136
1137        logger.info(NOTE_CONNHANDLER_STARTED_LISTENING, handlerName);
1138      }
1139      catch (Exception e)
1140      {
1141        logger.traceException(e);
1142
1143        logger.error(ERR_LDAP_CONNHANDLER_CREATE_CHANNEL_FAILED, currentConfig.dn(), a.getHostAddress(), listenPort,
1144            stackTraceToSingleLineString(e));
1145      }
1146    }
1147    return numRegistered;
1148  }
1149
1150
1151
1152  private void acceptConnection(SocketChannel clientChannel)
1153      throws DirectoryException
1154  {
1155    try
1156    {
1157      clientChannel.socket().setKeepAlive(currentConfig.isUseTCPKeepAlive());
1158      clientChannel.socket().setTcpNoDelay(currentConfig.isUseTCPNoDelay());
1159    }
1160    catch (SocketException se)
1161    {
1162      // TCP error occurred because connection reset/closed? In any case,
1163      // just close it and ignore.
1164      // See http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6378870
1165      close(clientChannel);
1166    }
1167
1168    // Check to see if the core server rejected the
1169    // connection (e.g., already too many connections
1170    // established).
1171    LDAPClientConnection clientConnection = new LDAPClientConnection(this,
1172        clientChannel, getProtocol());
1173    if (clientConnection.getConnectionID() < 0)
1174    {
1175      clientConnection.disconnect(DisconnectReason.ADMIN_LIMIT_EXCEEDED, true,
1176          ERR_CONNHANDLER_REJECTED_BY_SERVER.get());
1177      return;
1178    }
1179
1180    InetAddress clientAddr = clientConnection.getRemoteAddress();
1181    // Check to see if the client is on the denied list.
1182    // If so, then reject it immediately.
1183    if (!deniedClients.isEmpty()
1184        && AddressMask.matchesAny(deniedClients, clientAddr))
1185    {
1186      clientConnection.disconnect(DisconnectReason.CONNECTION_REJECTED,
1187          currentConfig.isSendRejectionNotice(), ERR_CONNHANDLER_DENIED_CLIENT
1188              .get(clientConnection.getClientHostPort(), clientConnection
1189                  .getServerHostPort()));
1190      return;
1191    }
1192    // Check to see if there is an allowed list and if
1193    // there is whether the client is on that list. If
1194    // not, then reject the connection.
1195    if (!allowedClients.isEmpty()
1196        && !AddressMask.matchesAny(allowedClients, clientAddr))
1197    {
1198      clientConnection.disconnect(DisconnectReason.CONNECTION_REJECTED,
1199          currentConfig.isSendRejectionNotice(),
1200          ERR_CONNHANDLER_DISALLOWED_CLIENT.get(clientConnection
1201              .getClientHostPort(), clientConnection.getServerHostPort()));
1202      return;
1203    }
1204
1205    // If we've gotten here, then we'll take the
1206    // connection so invoke the post-connect plugins and
1207    // register the client connection with a request
1208    // handler.
1209    try
1210    {
1211      PluginConfigManager pluginManager = DirectoryServer
1212          .getPluginConfigManager();
1213      PluginResult.PostConnect pluginResult = pluginManager
1214          .invokePostConnectPlugins(clientConnection);
1215      if (!pluginResult.continueProcessing())
1216      {
1217        clientConnection.disconnect(pluginResult.getDisconnectReason(),
1218            pluginResult.sendDisconnectNotification(),
1219            pluginResult.getErrorMessage());
1220        return;
1221      }
1222
1223      LDAPRequestHandler requestHandler =
1224          requestHandlers[requestHandlerIndex++];
1225      if (requestHandlerIndex >= numRequestHandlers)
1226      {
1227        requestHandlerIndex = 0;
1228      }
1229      requestHandler.registerClient(clientConnection);
1230    }
1231    catch (Exception e)
1232    {
1233      logger.traceException(e);
1234
1235      LocalizableMessage message =
1236          INFO_CONNHANDLER_UNABLE_TO_REGISTER_CLIENT.get(clientConnection
1237              .getClientHostPort(), clientConnection.getServerHostPort(),
1238              getExceptionMessage(e));
1239      logger.debug(message);
1240
1241      clientConnection.disconnect(DisconnectReason.SERVER_ERROR,
1242          currentConfig.isSendRejectionNotice(), message);
1243    }
1244  }
1245
1246
1247
1248  /**
1249   * Appends a string representation of this connection handler to the provided
1250   * buffer.
1251   *
1252   * @param buffer
1253   *          The buffer to which the information should be appended.
1254   */
1255  @Override
1256  public void toString(StringBuilder buffer)
1257  {
1258    buffer.append(handlerName);
1259  }
1260
1261
1262
1263  /**
1264   * Indicates whether this connection handler should use SSL to communicate
1265   * with clients.
1266   *
1267   * @return {@code true} if this connection handler should use SSL to
1268   *         communicate with clients, or {@code false} if not.
1269   */
1270  public boolean useSSL()
1271  {
1272    return currentConfig.isUseSSL();
1273  }
1274
1275
1276
1277  /**
1278   * Cleans up the contents of the selector, closing any server socket channels
1279   * that might be associated with it. Any connections that might have been
1280   * established through those channels should not be impacted.
1281   */
1282  private void cleanUpSelector()
1283  {
1284    try
1285    {
1286      for (SelectionKey key : selector.keys())
1287      {
1288        try
1289        {
1290          key.cancel();
1291        }
1292        catch (Exception e)
1293        {
1294          logger.traceException(e);
1295        }
1296
1297        try
1298        {
1299          key.channel().close();
1300        }
1301        catch (Exception e)
1302        {
1303          logger.traceException(e);
1304        }
1305      }
1306    }
1307    catch (Exception e)
1308    {
1309      logger.traceException(e);
1310    }
1311  }
1312
1313
1314
1315  /**
1316   * Get the queueing strategy.
1317   *
1318   * @return The queueing strategy.
1319   */
1320  public QueueingStrategy getQueueingStrategy()
1321  {
1322    return queueingStrategy;
1323  }
1324
1325
1326
1327  /**
1328   * Creates a TLS Byte Channel instance using the specified socket channel.
1329   *
1330   * @param channel
1331   *          The socket channel to use in the creation.
1332   * @return A TLS Byte Channel instance.
1333   * @throws DirectoryException
1334   *           If the channel cannot be created.
1335   */
1336  public TLSByteChannel getTLSByteChannel(ByteChannel channel)
1337      throws DirectoryException
1338  {
1339    SSLEngine sslEngine = createSSLEngine(currentConfig, sslContext);
1340    return new TLSByteChannel(channel, sslEngine);
1341  }
1342
1343
1344
1345  private SSLEngine createSSLEngine(LDAPConnectionHandlerCfg config,
1346      SSLContext sslContext) throws DirectoryException
1347  {
1348    try
1349    {
1350      SSLEngine sslEngine = sslContext.createSSLEngine();
1351      sslEngine.setUseClientMode(false);
1352
1353      final Set<String> protocols = config.getSSLProtocol();
1354      if (!protocols.isEmpty())
1355      {
1356        sslEngine.setEnabledProtocols(protocols.toArray(new String[0]));
1357      }
1358
1359      final Set<String> ciphers = config.getSSLCipherSuite();
1360      if (!ciphers.isEmpty())
1361      {
1362        sslEngine.setEnabledCipherSuites(ciphers.toArray(new String[0]));
1363      }
1364
1365      switch (config.getSSLClientAuthPolicy())
1366      {
1367      case DISABLED:
1368        sslEngine.setNeedClientAuth(false);
1369        sslEngine.setWantClientAuth(false);
1370        break;
1371      case REQUIRED:
1372        sslEngine.setWantClientAuth(true);
1373        sslEngine.setNeedClientAuth(true);
1374        break;
1375      case OPTIONAL:
1376      default:
1377        sslEngine.setNeedClientAuth(false);
1378        sslEngine.setWantClientAuth(true);
1379        break;
1380      }
1381
1382      return sslEngine;
1383    }
1384    catch (Exception e)
1385    {
1386      logger.traceException(e);
1387      ResultCode resCode = DirectoryServer.getServerErrorResultCode();
1388      LocalizableMessage message = ERR_CONNHANDLER_SSL_CANNOT_INITIALIZE
1389          .get(getExceptionMessage(e));
1390      throw new DirectoryException(resCode, message, e);
1391    }
1392  }
1393
1394
1395
1396  private void disableAndWarnIfUseSSL(LDAPConnectionHandlerCfg config)
1397  {
1398    if (config.isUseSSL())
1399    {
1400      logger.warn(INFO_DISABLE_CONNECTION, friendlyName);
1401      enabled = false;
1402    }
1403  }
1404
1405  private SSLContext createSSLContext(LDAPConnectionHandlerCfg config)
1406      throws DirectoryException
1407  {
1408    try
1409    {
1410      DN keyMgrDN = config.getKeyManagerProviderDN();
1411      KeyManagerProvider<?> keyManagerProvider = DirectoryServer
1412          .getKeyManagerProvider(keyMgrDN);
1413      if (keyManagerProvider == null)
1414      {
1415        logger.error(ERR_NULL_KEY_PROVIDER_MANAGER, keyMgrDN, friendlyName);
1416        disableAndWarnIfUseSSL(config);
1417        keyManagerProvider = new NullKeyManagerProvider();
1418        // The SSL connection is unusable without a key manager provider
1419      }
1420      else if (! keyManagerProvider.containsAtLeastOneKey())
1421      {
1422        logger.error(ERR_INVALID_KEYSTORE, friendlyName);
1423        disableAndWarnIfUseSSL(config);
1424      }
1425
1426      final SortedSet<String> aliases = new TreeSet<>(config.getSSLCertNickname());
1427      final KeyManager[] keyManagers;
1428      if (aliases.isEmpty())
1429      {
1430        keyManagers = keyManagerProvider.getKeyManagers();
1431      }
1432      else
1433      {
1434        final Iterator<String> it = aliases.iterator();
1435        while (it.hasNext())
1436        {
1437          if (!keyManagerProvider.containsKeyWithAlias(it.next()))
1438          {
1439            logger.error(ERR_KEYSTORE_DOES_NOT_CONTAIN_ALIAS, aliases, friendlyName);
1440            it.remove();
1441          }
1442        }
1443
1444        if (aliases.isEmpty())
1445        {
1446          disableAndWarnIfUseSSL(config);
1447        }
1448        keyManagers = SelectableCertificateKeyManager.wrap(keyManagerProvider.getKeyManagers(), aliases, friendlyName);
1449      }
1450
1451      DN trustMgrDN = config.getTrustManagerProviderDN();
1452      TrustManagerProvider<?> trustManagerProvider = DirectoryServer
1453          .getTrustManagerProvider(trustMgrDN);
1454      if (trustManagerProvider == null)
1455      {
1456        trustManagerProvider = new NullTrustManagerProvider();
1457      }
1458
1459      SSLContext sslContext = SSLContext.getInstance(SSL_CONTEXT_INSTANCE_NAME);
1460      sslContext.init(keyManagers, trustManagerProvider.getTrustManagers(),
1461          null);
1462      return sslContext;
1463    }
1464    catch (Exception e)
1465    {
1466      logger.traceException(e);
1467      ResultCode resCode = DirectoryServer.getServerErrorResultCode();
1468      LocalizableMessage message = ERR_CONNHANDLER_SSL_CANNOT_INITIALIZE
1469          .get(getExceptionMessage(e));
1470      throw new DirectoryException(resCode, message, e);
1471    }
1472  }
1473
1474  /**
1475   * Enqueue a connection finalizer which will be invoked after a short delay.
1476   *
1477   * @param r
1478   *          The connection finalizer runnable.
1479   */
1480  void registerConnectionFinalizer(Runnable r)
1481  {
1482    synchronized (connectionFinalizerLock)
1483    {
1484      if (connectionFinalizer != null)
1485      {
1486        connectionFinalizerPendingJobQueue.add(r);
1487      }
1488      else
1489      {
1490        // Already finalized - invoked immediately.
1491        r.run();
1492      }
1493    }
1494  }
1495
1496}