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