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}