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