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