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}