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