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 2013-2016 ForgeRock AS.
015 */
016package org.opends.server.protocols.http;
017
018import static org.opends.messages.ConfigMessages.WARN_CONFIG_LOGGER_NO_ACTIVE_HTTP_ACCESS_LOGGERS;
019import static org.opends.messages.ProtocolMessages.*;
020import static org.opends.server.util.ServerConstants.ALERT_DESCRIPTION_HTTP_CONNECTION_HANDLER_CONSECUTIVE_FAILURES;
021import static org.opends.server.util.ServerConstants.ALERT_TYPE_HTTP_CONNECTION_HANDLER_CONSECUTIVE_FAILURES;
022import static org.opends.server.util.StaticUtils.getExceptionMessage;
023import static org.opends.server.util.StaticUtils.isAddressInUse;
024import static org.opends.server.util.StaticUtils.stackTraceToSingleLineString;
025
026import javax.net.ssl.KeyManager;
027import javax.net.ssl.SSLContext;
028import javax.net.ssl.SSLEngine;
029
030import java.io.IOException;
031import java.net.InetAddress;
032import java.util.Arrays;
033import java.util.Collection;
034import java.util.Iterator;
035import java.util.LinkedHashMap;
036import java.util.LinkedList;
037import java.util.List;
038import java.util.Map;
039import java.util.Objects;
040import java.util.Set;
041import java.util.SortedSet;
042import java.util.TreeSet;
043import java.util.concurrent.ConcurrentHashMap;
044import java.util.concurrent.TimeUnit;
045
046import org.forgerock.http.servlet.HttpFrameworkServlet;
047import org.forgerock.i18n.LocalizableMessage;
048import org.forgerock.i18n.slf4j.LocalizedLogger;
049import org.forgerock.opendj.config.server.ConfigChangeResult;
050import org.forgerock.opendj.config.server.ConfigException;
051import org.forgerock.opendj.ldap.ResultCode;
052import org.glassfish.grizzly.http.HttpProbe;
053import org.glassfish.grizzly.http.server.HttpServer;
054import org.glassfish.grizzly.http.server.NetworkListener;
055import org.glassfish.grizzly.http.server.ServerConfiguration;
056import org.glassfish.grizzly.monitoring.MonitoringConfig;
057import org.glassfish.grizzly.nio.transport.TCPNIOTransport;
058import org.glassfish.grizzly.servlet.WebappContext;
059import org.glassfish.grizzly.ssl.SSLEngineConfigurator;
060import org.glassfish.grizzly.strategies.SameThreadIOStrategy;
061import org.glassfish.grizzly.utils.Charsets;
062import org.opends.server.admin.server.ConfigurationChangeListener;
063import org.opends.server.admin.std.server.ConnectionHandlerCfg;
064import org.opends.server.admin.std.server.HTTPConnectionHandlerCfg;
065import org.opends.server.api.AlertGenerator;
066import org.opends.server.api.ClientConnection;
067import org.opends.server.api.ConnectionHandler;
068import org.opends.server.api.KeyManagerProvider;
069import org.opends.server.api.ServerShutdownListener;
070import org.opends.server.api.TrustManagerProvider;
071import org.opends.server.core.DirectoryServer;
072import org.opends.server.core.ServerContext;
073import org.opends.server.extensions.NullKeyManagerProvider;
074import org.opends.server.extensions.NullTrustManagerProvider;
075import org.opends.server.loggers.HTTPAccessLogger;
076import org.opends.server.monitors.ClientConnectionMonitorProvider;
077import org.forgerock.opendj.ldap.DN;
078import org.opends.server.types.DirectoryException;
079import org.opends.server.types.HostPort;
080import org.opends.server.types.InitializationException;
081import org.opends.server.util.SelectableCertificateKeyManager;
082import org.opends.server.util.StaticUtils;
083
084/**
085 * This class defines a connection handler that will be used for communicating
086 * with clients over HTTP. The connection handler is responsible for
087 * starting/stopping the embedded web server.
088 */
089public class HTTPConnectionHandler extends ConnectionHandler<HTTPConnectionHandlerCfg>
090                                   implements ConfigurationChangeListener<HTTPConnectionHandlerCfg>,
091                                              ServerShutdownListener,
092                                              AlertGenerator
093{
094  /** The tracer object for the debug logger. */
095  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
096
097  /** Default friendly name for this connection handler. */
098  private static final String DEFAULT_FRIENDLY_NAME = "HTTP Connection Handler";
099
100  /** SSL instance name used in context creation. */
101  private static final String SSL_CONTEXT_INSTANCE_NAME = "TLS";
102
103  /** The initialization configuration. */
104  private HTTPConnectionHandlerCfg initConfig;
105
106  /** The current configuration. */
107  private HTTPConnectionHandlerCfg currentConfig;
108
109  /** Indicates whether the Directory Server is in the process of shutting down. */
110  private volatile boolean shutdownRequested;
111
112  /** Indicates whether this connection handler is enabled. */
113  private boolean enabled;
114
115  /** The set of listeners for this connection handler. */
116  private List<HostPort> listeners = new LinkedList<>();
117
118  /** The HTTP server embedded in OpenDJ. */
119  private HttpServer httpServer;
120
121  /** The HTTP probe that collects stats. */
122  private HTTPStatsProbe httpProbe;
123
124  /**
125   * Holds the current client connections. Using {@link ConcurrentHashMap} to
126   * ensure no concurrent reads/writes can happen and adds/removes are fast. We
127   * only use the keys, so it does not matter what value is put there.
128   */
129  private Map<ClientConnection, ClientConnection> clientConnections = new ConcurrentHashMap<>();
130
131  /** The set of statistics collected for this connection handler. */
132  private HTTPStatistics statTracker;
133
134  /** The client connection monitor provider associated with this connection handler. */
135  private ClientConnectionMonitorProvider connMonitor;
136
137  /** The unique name assigned to this connection handler. */
138  private String handlerName;
139
140  /** The protocol used by this connection handler. */
141  private String protocol;
142
143  /**
144   * The condition variable that will be used by the start method to wait for
145   * the socket port to be opened and ready to process requests before returning.
146   */
147  private final Object waitListen = new Object();
148
149  /** The friendly name of this connection handler. */
150  private String friendlyName;
151
152  /** The SSL engine configurator is used for obtaining default SSL parameters. */
153  private SSLEngineConfigurator sslEngineConfigurator;
154
155  private ServerContext serverContext;
156
157  /** Default constructor. It is invoked by reflection to create this {@link ConnectionHandler}. */
158  public HTTPConnectionHandler()
159  {
160    super(DEFAULT_FRIENDLY_NAME);
161  }
162
163  /**
164   * Returns whether unauthenticated HTTP requests are allowed. The server
165   * checks whether unauthenticated requests are allowed server-wide first then
166   * for the HTTP Connection Handler second.
167   *
168   * @return true if unauthenticated requests are allowed, false otherwise.
169   */
170  public boolean acceptUnauthenticatedRequests()
171  {
172    // The global setting overrides the more specific setting here.
173    return !DirectoryServer.rejectUnauthenticatedRequests() && !this.currentConfig.isAuthenticationRequired();
174  }
175
176  /**
177   * Registers a client connection to track it.
178   *
179   * @param clientConnection
180   *          the client connection to register
181   */
182  void addClientConnection(ClientConnection clientConnection)
183  {
184    clientConnections.put(clientConnection, clientConnection);
185  }
186
187  @Override
188  public ConfigChangeResult applyConfigurationChange(HTTPConnectionHandlerCfg config)
189  {
190    final ConfigChangeResult ccr = new ConfigChangeResult();
191
192    if (anyChangeRequiresRestart(config))
193    {
194      ccr.setAdminActionRequired(true);
195      ccr.addMessage(ERR_CONNHANDLER_CONFIG_CHANGES_REQUIRE_RESTART.get("HTTP"));
196    }
197
198    // Reconfigure SSL if needed.
199    try
200    {
201      configureSSL(config);
202    }
203    catch (DirectoryException e)
204    {
205      logger.traceException(e);
206      ccr.setResultCode(e.getResultCode());
207      ccr.addMessage(e.getMessageObject());
208      return ccr;
209    }
210
211    if (config.isEnabled() && this.currentConfig.isEnabled() && isListening())
212    {
213      // Server was running and will still be running if the "enabled" was flipped,
214      // leave it to the stop / start server to handle it.
215      if (!this.currentConfig.isKeepStats() && config.isKeepStats())
216      {
217        // It must now keep stats while it was not previously.
218        setHttpStatsProbe(this.httpServer);
219      }
220      else if (this.currentConfig.isKeepStats() && !config.isKeepStats() && this.httpProbe != null)
221      {
222        // It must NOT keep stats anymore.
223        getHttpConfig(this.httpServer).removeProbes(this.httpProbe);
224        this.httpProbe = null;
225      }
226    }
227
228    this.initConfig = config;
229    this.currentConfig = config;
230    this.enabled = this.currentConfig.isEnabled();
231
232    return ccr;
233  }
234
235  private boolean anyChangeRequiresRestart(HTTPConnectionHandlerCfg newCfg)
236  {
237    return !equals(newCfg.getListenPort(), initConfig.getListenPort())
238        || !Objects.equals(newCfg.getListenAddress(), initConfig.getListenAddress())
239        || !equals(newCfg.getMaxRequestSize(), currentConfig.getMaxRequestSize())
240        || !equals(newCfg.isAllowTCPReuseAddress(), currentConfig.isAllowTCPReuseAddress())
241        || !equals(newCfg.isUseTCPKeepAlive(), currentConfig.isUseTCPKeepAlive())
242        || !equals(newCfg.isUseTCPNoDelay(), currentConfig.isUseTCPNoDelay())
243        || !equals(newCfg.getMaxBlockedWriteTimeLimit(), currentConfig.getMaxBlockedWriteTimeLimit())
244        || !equals(newCfg.getBufferSize(), currentConfig.getBufferSize())
245        || !equals(newCfg.getAcceptBacklog(), currentConfig.getAcceptBacklog())
246        || !equals(newCfg.isUseSSL(), currentConfig.isUseSSL())
247        || !Objects.equals(newCfg.getKeyManagerProviderDN(), currentConfig.getKeyManagerProviderDN())
248        || !Objects.equals(newCfg.getSSLCertNickname(), currentConfig.getSSLCertNickname())
249        || !Objects.equals(newCfg.getTrustManagerProviderDN(), currentConfig.getTrustManagerProviderDN())
250        || !Objects.equals(newCfg.getSSLProtocol(), currentConfig.getSSLProtocol())
251        || !Objects.equals(newCfg.getSSLCipherSuite(), currentConfig.getSSLCipherSuite())
252        || !Objects.equals(newCfg.getSSLClientAuthPolicy(), currentConfig.getSSLClientAuthPolicy());
253  }
254
255  private boolean equals(long l1, long l2)
256  {
257    return l1 == l2;
258  }
259
260  private boolean equals(boolean b1, boolean b2)
261  {
262    return b1 == b2;
263  }
264
265  private void configureSSL(HTTPConnectionHandlerCfg config)
266      throws DirectoryException
267  {
268    protocol = config.isUseSSL() ? "HTTPS" : "HTTP";
269    if (config.isUseSSL())
270    {
271      sslEngineConfigurator = createSSLEngineConfigurator(config);
272    }
273    else
274    {
275      sslEngineConfigurator = null;
276    }
277  }
278
279  @Override
280  public void finalizeConnectionHandler(LocalizableMessage finalizeReason)
281  {
282    shutdownRequested = true;
283    // Unregister this as a change listener.
284    currentConfig.removeHTTPChangeListener(this);
285
286    if (connMonitor != null)
287    {
288      DirectoryServer.deregisterMonitorProvider(connMonitor);
289    }
290
291    if (statTracker != null)
292    {
293      DirectoryServer.deregisterMonitorProvider(statTracker);
294    }
295  }
296
297  @Override
298  public Map<String, String> getAlerts()
299  {
300    Map<String, String> alerts = new LinkedHashMap<>();
301
302    alerts.put(ALERT_TYPE_HTTP_CONNECTION_HANDLER_CONSECUTIVE_FAILURES,
303               ALERT_DESCRIPTION_HTTP_CONNECTION_HANDLER_CONSECUTIVE_FAILURES);
304
305    return alerts;
306  }
307
308  @Override
309  public String getClassName()
310  {
311    return HTTPConnectionHandler.class.getName();
312  }
313
314  @Override
315  public Collection<ClientConnection> getClientConnections()
316  {
317    return clientConnections.keySet();
318  }
319
320  @Override
321  public DN getComponentEntryDN()
322  {
323    return currentConfig.dn();
324  }
325
326  @Override
327  public String getConnectionHandlerName()
328  {
329    return handlerName;
330  }
331
332  /**
333   * Returns the current config of this connection handler.
334   *
335   * @return the current config of this connection handler
336   */
337  HTTPConnectionHandlerCfg getCurrentConfig()
338  {
339    return this.currentConfig;
340  }
341
342  @Override
343  public Collection<String> getEnabledSSLCipherSuites()
344  {
345    final SSLEngineConfigurator configurator = sslEngineConfigurator;
346    if (configurator != null)
347    {
348      return Arrays.asList(configurator.getEnabledCipherSuites());
349    }
350    return super.getEnabledSSLCipherSuites();
351  }
352
353  @Override
354  public Collection<String> getEnabledSSLProtocols()
355  {
356    final SSLEngineConfigurator configurator = sslEngineConfigurator;
357    if (configurator != null)
358    {
359      return Arrays.asList(configurator.getEnabledProtocols());
360    }
361    return super.getEnabledSSLProtocols();
362  }
363
364  @Override
365  public Collection<HostPort> getListeners()
366  {
367    return listeners;
368  }
369
370  /**
371   * Returns the listen port for this connection handler.
372   *
373   * @return the listen port for this connection handler.
374   */
375  int getListenPort()
376  {
377    return this.initConfig.getListenPort();
378  }
379
380  @Override
381  public String getProtocol()
382  {
383    return protocol;
384  }
385
386  /**
387   * Returns the SSL engine configured for this connection handler if SSL is
388   * enabled, null otherwise.
389   *
390   * @return the SSL engine if SSL is enabled, null otherwise
391   */
392  SSLEngine getSSLEngine()
393  {
394    return sslEngineConfigurator.createSSLEngine();
395  }
396
397  @Override
398  public String getShutdownListenerName()
399  {
400    return handlerName;
401  }
402
403  /**
404   * Retrieves the set of statistics maintained by this connection handler.
405   *
406   * @return The set of statistics maintained by this connection handler.
407   */
408  public HTTPStatistics getStatTracker()
409  {
410    return statTracker;
411  }
412
413  @Override
414  public void initializeConnectionHandler(ServerContext serverContext, HTTPConnectionHandlerCfg config)
415      throws ConfigException, InitializationException
416  {
417    this.serverContext = serverContext;
418    this.enabled = config.isEnabled();
419
420    if (friendlyName == null)
421    {
422      friendlyName = config.dn().rdn().getFirstAVA().getAttributeValue().toString();
423    }
424
425    int listenPort = config.getListenPort();
426    for (InetAddress a : config.getListenAddress())
427    {
428      listeners.add(new HostPort(a.getHostAddress(), listenPort));
429    }
430
431    handlerName = getHandlerName(config);
432
433    // Configure SSL if needed.
434    try
435    {
436      // This call may disable the connector if wrong SSL settings
437      configureSSL(config);
438    }
439    catch (DirectoryException e)
440    {
441      logger.traceException(e);
442      throw new InitializationException(e.getMessageObject());
443    }
444
445    // Create and register monitors.
446    statTracker = new HTTPStatistics(handlerName + " Statistics");
447    DirectoryServer.registerMonitorProvider(statTracker);
448
449    connMonitor = new ClientConnectionMonitorProvider(this);
450    DirectoryServer.registerMonitorProvider(connMonitor);
451
452    // Register this as a change listener.
453    config.addHTTPChangeListener(this);
454
455    this.initConfig = config;
456    this.currentConfig = config;
457  }
458
459  private String getHandlerName(HTTPConnectionHandlerCfg config)
460  {
461    StringBuilder nameBuffer = new StringBuilder();
462    nameBuffer.append(friendlyName);
463    for (InetAddress a : config.getListenAddress())
464    {
465      nameBuffer.append(" ");
466      nameBuffer.append(a.getHostAddress());
467    }
468    nameBuffer.append(" port ");
469    nameBuffer.append(config.getListenPort());
470    return nameBuffer.toString();
471  }
472
473  @Override
474  public boolean isConfigurationAcceptable(
475      ConnectionHandlerCfg configuration, List<LocalizableMessage> unacceptableReasons)
476  {
477    HTTPConnectionHandlerCfg config = (HTTPConnectionHandlerCfg) configuration;
478
479    if (currentConfig == null || (!this.enabled && config.isEnabled()))
480    {
481      // Attempt to bind to the listen port on all configured addresses to
482      // verify whether the connection handler will be able to start.
483      LocalizableMessage errorMessage = checkAnyListenAddressInUse(
484          config.getListenAddress(), config.getListenPort(), config.isAllowTCPReuseAddress(), config.dn());
485      if (errorMessage != null)
486      {
487        unacceptableReasons.add(errorMessage);
488        return false;
489      }
490    }
491
492    if (config.isEnabled() && config.isUseSSL())
493    {
494      try
495      {
496        createSSLEngineConfigurator(config);
497      }
498      catch (DirectoryException e)
499      {
500        logger.traceException(e);
501        unacceptableReasons.add(e.getMessageObject());
502        return false;
503      }
504    }
505
506    return true;
507  }
508
509  /**
510   * Checks whether any listen address is in use for the given port. The check
511   * is performed by binding to each address and port.
512   *
513   * @param listenAddresses
514   *          the listen {@link InetAddress} to test
515   * @param listenPort
516   *          the listen port to test
517   * @param allowReuseAddress
518   *          whether addresses can be reused
519   * @param configEntryDN
520   *          the configuration entry DN
521   * @return an error message if at least one of the address is already in use,
522   *         null otherwise.
523   */
524  private LocalizableMessage checkAnyListenAddressInUse(
525      Collection<InetAddress> listenAddresses, int listenPort, boolean allowReuseAddress, DN configEntryDN)
526  {
527    for (InetAddress a : listenAddresses)
528    {
529      try
530      {
531        if (isAddressInUse(a, listenPort, allowReuseAddress))
532        {
533          throw new IOException(ERR_CONNHANDLER_ADDRESS_INUSE.get().toString());
534        }
535      }
536      catch (IOException e)
537      {
538        logger.traceException(e);
539        return ERR_CONNHANDLER_CANNOT_BIND.get(
540            "HTTP", configEntryDN, a.getHostAddress(), listenPort, getExceptionMessage(e));
541      }
542    }
543    return null;
544  }
545
546  @Override
547  public boolean isConfigurationChangeAcceptable(
548      HTTPConnectionHandlerCfg configuration, List<LocalizableMessage> unacceptableReasons)
549  {
550    return isConfigurationAcceptable(configuration, unacceptableReasons);
551  }
552
553  /**
554   * Indicates whether this connection handler should maintain usage statistics.
555   *
556   * @return <CODE>true</CODE> if this connection handler should maintain usage
557   *         statistics, or <CODE>false</CODE> if not.
558   */
559  public boolean keepStats()
560  {
561    return currentConfig.isKeepStats();
562  }
563
564  @Override
565  public void processServerShutdown(LocalizableMessage reason)
566  {
567    shutdownRequested = true;
568  }
569
570  private boolean isListening()
571  {
572    return httpServer != null;
573  }
574
575  @Override
576  public void start()
577  {
578    // The Directory Server start process should only return when the connection handlers port
579    // are fully opened and working.
580    // The start method therefore needs to wait for the created thread too.
581    synchronized (waitListen)
582    {
583      super.start();
584
585      try
586      {
587        waitListen.wait();
588      }
589      catch (InterruptedException e)
590      {
591        // If something interrupted the start its probably better to return ASAP
592      }
593    }
594  }
595
596  /**
597   * Unregisters a client connection to stop tracking it.
598   *
599   * @param clientConnection
600   *          the client connection to unregister
601   */
602  void removeClientConnection(ClientConnection clientConnection)
603  {
604    clientConnections.remove(clientConnection);
605  }
606
607  @Override
608  public void run()
609  {
610    setName(handlerName);
611
612    boolean lastIterationFailed = false;
613    boolean starting = true;
614
615    while (!shutdownRequested)
616    {
617      // If this connection handler is not enabled, then just sleep for a bit and check again.
618      if (!this.enabled)
619      {
620        if (isListening())
621        {
622          stopHttpServer();
623        }
624
625        if (starting)
626        {
627          // This may happen if there was an initialisation error which led to disable the connector.
628          // The main thread is waiting for the connector to listen on its port, which will not occur yet,
629          // so notify here to allow the server startup to complete.
630          synchronized (waitListen)
631          {
632            starting = false;
633            waitListen.notify();
634          }
635        }
636
637        StaticUtils.sleep(1000);
638        continue;
639      }
640
641      if (isListening())
642      {
643        // If already listening, then sleep for a bit and check again.
644        StaticUtils.sleep(1000);
645        continue;
646      }
647
648      try
649      {
650        // At this point, the connection Handler either started correctly or failed
651        // to start but the start process should be notified and resume its work in any cases.
652        synchronized (waitListen)
653        {
654          waitListen.notify();
655        }
656
657        // If we have gotten here, then we are about to start listening
658        // for the first time since startup or since we were previously disabled.
659        // Start the embedded HTTP server
660        startHttpServer();
661        lastIterationFailed = false;
662      }
663      catch (Exception e)
664      {
665        // Clean up the messed up HTTP server
666        cleanUpHttpServer();
667
668        // Error + alert about the horked config
669        logger.traceException(e);
670        logger.error(
671            ERR_CONNHANDLER_CANNOT_ACCEPT_CONNECTION, friendlyName, currentConfig.dn(), getExceptionMessage(e));
672
673        if (lastIterationFailed)
674        {
675          // The last time through the accept loop we also encountered a failure.
676          // Rather than enter a potential infinite loop of failures,
677          // disable this acceptor and log an error.
678          LocalizableMessage message = ERR_CONNHANDLER_CONSECUTIVE_ACCEPT_FAILURES.get(
679              friendlyName, currentConfig.dn(), stackTraceToSingleLineString(e));
680          logger.error(message);
681
682          DirectoryServer.sendAlertNotification(this, ALERT_TYPE_HTTP_CONNECTION_HANDLER_CONSECUTIVE_FAILURES, message);
683          this.enabled = false;
684        }
685        else
686        {
687          lastIterationFailed = true;
688        }
689      }
690    }
691
692    // Initiate shutdown
693    stopHttpServer();
694  }
695
696  private void startHttpServer() throws Exception
697  {
698    if (HTTPAccessLogger.getHTTPAccessLogPublishers().isEmpty())
699    {
700      logger.warn(WARN_CONFIG_LOGGER_NO_ACTIVE_HTTP_ACCESS_LOGGERS);
701    }
702
703    this.httpServer = createHttpServer();
704
705    // Register servlet as default servlet and also able to serve REST requests
706    createAndRegisterServlet("OpenDJ Rest2LDAP servlet", "", "/*");
707
708    logger.trace("Starting HTTP server...");
709    this.httpServer.start();
710    logger.trace("HTTP server started");
711    logger.info(NOTE_CONNHANDLER_STARTED_LISTENING, handlerName);
712  }
713
714  private HttpServer createHttpServer()
715  {
716    final HttpServer server = new HttpServer();
717
718    final int requestSize = (int) currentConfig.getMaxRequestSize();
719    final ServerConfiguration serverConfig = server.getServerConfiguration();
720    serverConfig.setMaxBufferedPostSize(requestSize);
721    serverConfig.setMaxFormPostSize(requestSize);
722    serverConfig.setDefaultQueryEncoding(Charsets.UTF8_CHARSET);
723
724    if (keepStats())
725    {
726      setHttpStatsProbe(server);
727    }
728
729    // Configure the network listener
730    final NetworkListener listener = new NetworkListener(
731        "Rest2LDAP", NetworkListener.DEFAULT_NETWORK_HOST, initConfig.getListenPort());
732    server.addListener(listener);
733
734    // Configure the network transport
735    final TCPNIOTransport transport = listener.getTransport();
736    transport.setReuseAddress(currentConfig.isAllowTCPReuseAddress());
737    transport.setKeepAlive(currentConfig.isUseTCPKeepAlive());
738    transport.setTcpNoDelay(currentConfig.isUseTCPNoDelay());
739    transport.setWriteTimeout(currentConfig.getMaxBlockedWriteTimeLimit(), TimeUnit.MILLISECONDS);
740
741    final int bufferSize = (int) currentConfig.getBufferSize();
742    transport.setReadBufferSize(bufferSize);
743    transport.setWriteBufferSize(bufferSize);
744    transport.setIOStrategy(SameThreadIOStrategy.getInstance());
745
746    final int numRequestHandlers = getNumRequestHandlers(currentConfig.getNumRequestHandlers(), friendlyName);
747    transport.setSelectorRunnersCount(numRequestHandlers);
748    transport.setServerConnectionBackLog(currentConfig.getAcceptBacklog());
749
750    // Configure SSL
751    if (sslEngineConfigurator != null)
752    {
753      listener.setSecure(true);
754      listener.setSSLEngineConfig(sslEngineConfigurator);
755    }
756
757    return server;
758  }
759
760  private void setHttpStatsProbe(HttpServer server)
761  {
762    this.httpProbe = new HTTPStatsProbe(this.statTracker);
763    getHttpConfig(server).addProbes(this.httpProbe);
764  }
765
766  private MonitoringConfig<HttpProbe> getHttpConfig(HttpServer server)
767  {
768    return server.getServerConfiguration().getMonitoringConfig().getHttpConfig();
769  }
770
771  private void createAndRegisterServlet(final String servletName, final String... urlPatterns) throws Exception
772  {
773    // Create and deploy the Web app context
774    final WebappContext ctx = new WebappContext(servletName);
775    ctx.addServlet(servletName,
776        new HttpFrameworkServlet(new LdapHttpApplication(serverContext, this))).addMapping(urlPatterns);
777    ctx.deploy(this.httpServer);
778  }
779
780  private void stopHttpServer()
781  {
782    if (this.httpServer != null)
783    {
784      logger.trace("Stopping HTTP server...");
785      this.httpServer.shutdownNow();
786      cleanUpHttpServer();
787      logger.trace("HTTP server stopped");
788      logger.info(NOTE_CONNHANDLER_STOPPED_LISTENING, handlerName);
789    }
790  }
791
792  private void cleanUpHttpServer()
793  {
794    this.httpServer = null;
795    this.httpProbe = null;
796  }
797
798  @Override
799  public void toString(StringBuilder buffer)
800  {
801    buffer.append(handlerName);
802  }
803
804  private SSLEngineConfigurator createSSLEngineConfigurator(HTTPConnectionHandlerCfg config) throws DirectoryException
805  {
806    if (!config.isUseSSL())
807    {
808      return null;
809    }
810
811    try
812    {
813      SSLContext sslContext = createSSLContext(config);
814      SSLEngineConfigurator configurator = new SSLEngineConfigurator(sslContext);
815      configurator.setClientMode(false);
816
817      // configure with defaults from the JVM
818      final SSLEngine defaults = sslContext.createSSLEngine();
819      configurator.setEnabledProtocols(defaults.getEnabledProtocols());
820      configurator.setEnabledCipherSuites(defaults.getEnabledCipherSuites());
821
822      final Set<String> protocols = config.getSSLProtocol();
823      if (!protocols.isEmpty())
824      {
825        configurator.setEnabledProtocols(protocols.toArray(new String[protocols.size()]));
826      }
827
828      final Set<String> ciphers = config.getSSLCipherSuite();
829      if (!ciphers.isEmpty())
830      {
831        configurator.setEnabledCipherSuites(ciphers.toArray(new String[ciphers.size()]));
832      }
833
834      switch (config.getSSLClientAuthPolicy())
835      {
836      case DISABLED:
837        configurator.setNeedClientAuth(false);
838        configurator.setWantClientAuth(false);
839        break;
840      case REQUIRED:
841        configurator.setNeedClientAuth(true);
842        configurator.setWantClientAuth(true);
843        break;
844      case OPTIONAL:
845      default:
846        configurator.setNeedClientAuth(false);
847        configurator.setWantClientAuth(true);
848        break;
849      }
850
851      return configurator;
852    }
853    catch (Exception e)
854    {
855      logger.traceException(e);
856      ResultCode resCode = DirectoryServer.getServerErrorResultCode();
857      throw new DirectoryException(resCode, ERR_CONNHANDLER_SSL_CANNOT_INITIALIZE.get(getExceptionMessage(e)), e);
858    }
859  }
860
861  private SSLContext createSSLContext(HTTPConnectionHandlerCfg config) throws Exception
862  {
863    if (!config.isUseSSL())
864    {
865      return null;
866    }
867
868    DN keyMgrDN = config.getKeyManagerProviderDN();
869    KeyManagerProvider<?> keyManagerProvider = DirectoryServer.getKeyManagerProvider(keyMgrDN);
870    if (keyManagerProvider == null)
871    {
872      logger.error(ERR_NULL_KEY_PROVIDER_MANAGER, keyMgrDN, friendlyName);
873      logger.warn(INFO_DISABLE_CONNECTION, friendlyName);
874      keyManagerProvider = new NullKeyManagerProvider();
875      enabled = false;
876    }
877    else if (!keyManagerProvider.containsAtLeastOneKey())
878    {
879      logger.error(ERR_INVALID_KEYSTORE, friendlyName);
880      logger.warn(INFO_DISABLE_CONNECTION, friendlyName);
881      enabled = false;
882    }
883
884    final SortedSet<String> aliases = new TreeSet<>(config.getSSLCertNickname());
885    final KeyManager[] keyManagers;
886    if (aliases.isEmpty())
887    {
888      keyManagers = keyManagerProvider.getKeyManagers();
889    }
890    else
891    {
892      final Iterator<String> it = aliases.iterator();
893      while (it.hasNext())
894      {
895        if (!keyManagerProvider.containsKeyWithAlias(it.next()))
896        {
897          logger.error(ERR_KEYSTORE_DOES_NOT_CONTAIN_ALIAS, aliases, friendlyName);
898          it.remove();
899        }
900      }
901      if (aliases.isEmpty())
902      {
903        logger.warn(INFO_DISABLE_CONNECTION, friendlyName);
904        enabled = false;
905      }
906      keyManagers = SelectableCertificateKeyManager.wrap(keyManagerProvider.getKeyManagers(), aliases);
907    }
908
909    DN trustMgrDN = config.getTrustManagerProviderDN();
910    TrustManagerProvider<?> trustManagerProvider = DirectoryServer.getTrustManagerProvider(trustMgrDN);
911    if (trustManagerProvider == null)
912    {
913      trustManagerProvider = new NullTrustManagerProvider();
914    }
915
916    SSLContext sslContext = SSLContext.getInstance(SSL_CONTEXT_INSTANCE_NAME);
917    sslContext.init(keyManagers, trustManagerProvider.getTrustManagers(), null);
918    return sslContext;
919  }
920}