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 2006-2010 Sun Microsystems, Inc. 025 * Portions Copyright 2014-2015 ForgeRock AS 026 */ 027package org.opends.server.protocols.ldap; 028 029import static org.opends.messages.ProtocolMessages.*; 030import static org.opends.server.loggers.AccessLogger.logConnect; 031import static org.opends.server.util.StaticUtils.*; 032 033import java.io.IOException; 034import java.nio.channels.CancelledKeyException; 035import java.nio.channels.SelectionKey; 036import java.nio.channels.Selector; 037import java.nio.channels.SocketChannel; 038import java.util.ArrayList; 039import java.util.Collection; 040import java.util.Iterator; 041 042import java.util.LinkedList; 043import java.util.List; 044import org.forgerock.i18n.LocalizableMessage; 045import org.opends.server.api.DirectoryThread; 046import org.opends.server.api.ServerShutdownListener; 047import org.opends.server.core.DirectoryServer; 048import org.forgerock.i18n.slf4j.LocalizedLogger; 049import org.forgerock.opendj.io.ASN1Reader; 050import org.forgerock.opendj.ldap.DecodeException; 051import org.opends.server.types.DisconnectReason; 052import org.opends.server.types.InitializationException; 053import org.opends.server.types.LDAPException; 054 055/** 056 * This class defines an LDAP request handler, which is associated with an LDAP 057 * connection handler and is responsible for reading and decoding any requests 058 * that LDAP clients may send to the server. Multiple request handlers may be 059 * used in conjunction with a single connection handler for better performance 060 * and scalability. 061 */ 062public class LDAPRequestHandler 063 extends DirectoryThread 064 implements ServerShutdownListener 065{ 066 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 067 068 /** Indicates whether the Directory Server is in the process of shutting down. */ 069 private volatile boolean shutdownRequested; 070 071 /** The current set of selection keys. */ 072 private volatile SelectionKey[] keys = new SelectionKey[0]; 073 074 /** 075 * The queue that will be used to hold the set of pending connections that 076 * need to be registered with the selector. 077 * TODO: revisit, see Issue 4202. 078 */ 079 private List<LDAPClientConnection> pendingConnections = new LinkedList<>(); 080 081 /** Lock object for synchronizing access to the pending connections queue. */ 082 private final Object pendingConnectionsLock = new Object(); 083 084 /** The list of connections ready for request processing. */ 085 private LinkedList<LDAPClientConnection> readyConnections = new LinkedList<>(); 086 087 /** The selector that will be used to monitor the client connections. */ 088 private final Selector selector; 089 090 /** The name to use for this request handler. */ 091 private final String handlerName; 092 093 094 095 /** 096 * Creates a new LDAP request handler that will be associated with the 097 * provided connection handler. 098 * 099 * @param connectionHandler The LDAP connection handler with which this 100 * request handler is associated. 101 * @param requestHandlerID The integer value that may be used to distinguish 102 * this request handler from others associated with 103 * the same connection handler. 104 * @throws InitializationException If a problem occurs while initializing 105 * this request handler. 106 */ 107 public LDAPRequestHandler(LDAPConnectionHandler connectionHandler, 108 int requestHandlerID) 109 throws InitializationException 110 { 111 super("LDAP Request Handler " + requestHandlerID + 112 " for connection handler " + connectionHandler); 113 114 115 handlerName = getName(); 116 117 try 118 { 119 selector = Selector.open(); 120 } 121 catch (Exception e) 122 { 123 logger.traceException(e); 124 125 LocalizableMessage message = ERR_LDAP_REQHANDLER_OPEN_SELECTOR_FAILED.get(handlerName, e); 126 throw new InitializationException(message, e); 127 } 128 129 try 130 { 131 // Check to see if we get an error while trying to perform a select. If 132 // we do, then it's likely CR 6322825 and the server won't be able to 133 // handle LDAP requests in its current state. 134 selector.selectNow(); 135 } 136 catch (IOException ioe) 137 { 138 StackTraceElement[] stackElements = ioe.getStackTrace(); 139 if (stackElements != null && stackElements.length > 0) 140 { 141 StackTraceElement ste = stackElements[0]; 142 if (ste.getClassName().equals("sun.nio.ch.DevPollArrayWrapper") 143 && ste.getMethodName().contains("poll") 144 && ioe.getMessage().equalsIgnoreCase("Invalid argument")) 145 { 146 LocalizableMessage message = ERR_LDAP_REQHANDLER_DETECTED_JVM_ISSUE_CR6322825.get(ioe); 147 throw new InitializationException(message, ioe); 148 } 149 } 150 } 151 } 152 153 154 155 /** 156 * Operates in a loop, waiting for client requests to arrive and ensuring that 157 * they are processed properly. 158 */ 159 @Override 160 public void run() 161 { 162 // Operate in a loop until the server shuts down. Each time through the 163 // loop, check for new requests, then check for new connections. 164 while (!shutdownRequested) 165 { 166 LDAPClientConnection readyConnection = null; 167 while ((readyConnection = readyConnections.poll()) != null) 168 { 169 try 170 { 171 ASN1Reader asn1Reader = readyConnection.getASN1Reader(); 172 boolean ldapMessageProcessed = false; 173 while (true) 174 { 175 if (asn1Reader.elementAvailable()) 176 { 177 if (!ldapMessageProcessed) 178 { 179 if (readyConnection.processLDAPMessage( 180 LDAPReader.readMessage(asn1Reader))) 181 { 182 ldapMessageProcessed = true; 183 } 184 else 185 { 186 break; 187 } 188 } 189 else 190 { 191 readyConnections.add(readyConnection); 192 break; 193 } 194 } 195 else 196 { 197 if (readyConnection.processDataRead() <= 0) 198 { 199 break; 200 } 201 } 202 } 203 } 204 catch (DecodeException | LDAPException e) 205 { 206 logger.traceException(e); 207 readyConnection.disconnect(DisconnectReason.PROTOCOL_ERROR, true, 208 e.getMessageObject()); 209 } 210 catch (Exception e) 211 { 212 logger.traceException(e); 213 readyConnection.disconnect(DisconnectReason.PROTOCOL_ERROR, true, 214 LocalizableMessage.raw(e.toString())); 215 } 216 } 217 218 // Check to see if we have any pending connections that need to be 219 // registered with the selector. 220 List<LDAPClientConnection> tmp = null; 221 synchronized (pendingConnectionsLock) 222 { 223 if (!pendingConnections.isEmpty()) 224 { 225 tmp = pendingConnections; 226 pendingConnections = new LinkedList<>(); 227 } 228 } 229 230 if (tmp != null) 231 { 232 for (LDAPClientConnection c : tmp) 233 { 234 try 235 { 236 SocketChannel socketChannel = c.getSocketChannel(); 237 socketChannel.configureBlocking(false); 238 socketChannel.register(selector, SelectionKey.OP_READ, c); 239 logConnect(c); 240 } 241 catch (Exception e) 242 { 243 logger.traceException(e); 244 245 c.disconnect(DisconnectReason.SERVER_ERROR, true, 246 ERR_LDAP_REQHANDLER_CANNOT_REGISTER.get(handlerName, e)); 247 } 248 } 249 } 250 251 // Create a copy of the selection keys which can be used in a 252 // thread-safe manner by getClientConnections. This copy is only 253 // updated once per loop, so may not be accurate. 254 keys = selector.keys().toArray(new SelectionKey[0]); 255 256 int selectedKeys = 0; 257 try 258 { 259 // We timeout every second so that we can refresh the key list. 260 selectedKeys = selector.select(1000); 261 } 262 catch (Exception e) 263 { 264 logger.traceException(e); 265 266 // FIXME -- Should we do something else with this? 267 } 268 269 if (shutdownRequested) 270 { 271 // Avoid further processing and disconnect all clients. 272 break; 273 } 274 275 if (selectedKeys > 0) 276 { 277 Iterator<SelectionKey> iterator = selector.selectedKeys().iterator(); 278 while (iterator.hasNext()) 279 { 280 SelectionKey key = iterator.next(); 281 282 try 283 { 284 if (key.isReadable()) 285 { 286 LDAPClientConnection clientConnection = null; 287 288 try 289 { 290 clientConnection = (LDAPClientConnection) key.attachment(); 291 292 try 293 { 294 int readResult = clientConnection.processDataRead(); 295 if (readResult < 0) 296 { 297 key.cancel(); 298 } 299 if (readResult > 0) { 300 readyConnections.add(clientConnection); 301 } 302 } 303 catch (Exception e) 304 { 305 logger.traceException(e); 306 307 // Some other error occurred while we were trying to read data 308 // from the client. 309 // FIXME -- Should we log this? 310 key.cancel(); 311 clientConnection.disconnect(DisconnectReason.SERVER_ERROR, 312 false, null); 313 } 314 } 315 catch (Exception e) 316 { 317 logger.traceException(e); 318 319 // We got some other kind of error. If nothing else, cancel the 320 // key, but if the client connection is available then 321 // disconnect it as well. 322 key.cancel(); 323 324 if (clientConnection != null) 325 { 326 clientConnection.disconnect(DisconnectReason.SERVER_ERROR, 327 false, null); 328 } 329 } 330 } 331 else if (! key.isValid()) 332 { 333 key.cancel(); 334 } 335 } 336 catch (CancelledKeyException cke) 337 { 338 logger.traceException(cke); 339 340 // This could happen if a connection was closed between the time 341 // that select returned and the time that we try to access the 342 // associated channel. If that was the case, we don't need to do 343 // anything. 344 } 345 catch (Exception e) 346 { 347 logger.traceException(e); 348 349 // This should not happen, and it would have caused our reader 350 // thread to die. Log a severe error. 351 logger.error(ERR_LDAP_REQHANDLER_UNEXPECTED_SELECT_EXCEPTION, getName(), getExceptionMessage(e)); 352 } 353 finally 354 { 355 if (!key.isValid()) 356 { 357 // Help GC - release the connection. 358 key.attach(null); 359 } 360 361 iterator.remove(); 362 } 363 } 364 } 365 } 366 367 // Disconnect all active connections. 368 SelectionKey[] keyArray = selector.keys().toArray(new SelectionKey[0]); 369 for (SelectionKey key : keyArray) 370 { 371 LDAPClientConnection c = (LDAPClientConnection) key.attachment(); 372 373 try 374 { 375 key.channel().close(); 376 } 377 catch (Exception e) 378 { 379 logger.traceException(e); 380 } 381 382 try 383 { 384 key.cancel(); 385 } 386 catch (Exception e) 387 { 388 logger.traceException(e); 389 } 390 391 try 392 { 393 c.disconnect(DisconnectReason.SERVER_SHUTDOWN, true, 394 ERR_LDAP_REQHANDLER_DEREGISTER_DUE_TO_SHUTDOWN.get()); 395 } 396 catch (Exception e) 397 { 398 logger.traceException(e); 399 } 400 } 401 402 // Disconnect all pending connections. 403 synchronized (pendingConnectionsLock) 404 { 405 for (LDAPClientConnection c : pendingConnections) 406 { 407 try 408 { 409 c.disconnect(DisconnectReason.SERVER_SHUTDOWN, true, 410 ERR_LDAP_REQHANDLER_DEREGISTER_DUE_TO_SHUTDOWN.get()); 411 } 412 catch (Exception e) 413 { 414 logger.traceException(e); 415 } 416 } 417 } 418 } 419 420 421 422 /** 423 * Registers the provided client connection with this request 424 * handler so that any requests received from that client will be 425 * processed. 426 * 427 * @param clientConnection 428 * The client connection to be registered with this request 429 * handler. 430 * @return <CODE>true</CODE> if the client connection was properly 431 * registered with this request handler, or 432 * <CODE>false</CODE> if not. 433 */ 434 public boolean registerClient(LDAPClientConnection clientConnection) 435 { 436 // FIXME -- Need to check if the maximum client limit has been reached. 437 438 439 // If the server is in the process of shutting down, then we don't want to 440 // accept it. 441 if (shutdownRequested) 442 { 443 clientConnection.disconnect(DisconnectReason.SERVER_SHUTDOWN, true, 444 ERR_LDAP_REQHANDLER_REJECT_DUE_TO_SHUTDOWN.get()); 445 return false; 446 } 447 448 // Try to add the new connection to the queue. If it succeeds, then wake 449 // up the selector so it will be picked up right away. Otherwise, 450 // disconnect the client. 451 synchronized (pendingConnectionsLock) 452 { 453 pendingConnections.add(clientConnection); 454 } 455 456 selector.wakeup(); 457 return true; 458 } 459 460 461 462 /** 463 * Retrieves the set of all client connections that are currently registered 464 * with this request handler. 465 * 466 * @return The set of all client connections that are currently registered 467 * with this request handler. 468 */ 469 public Collection<LDAPClientConnection> getClientConnections() 470 { 471 ArrayList<LDAPClientConnection> connList = new ArrayList<>(keys.length); 472 for (SelectionKey key : keys) 473 { 474 LDAPClientConnection c = (LDAPClientConnection) key.attachment(); 475 476 // If the client has disconnected the attachment may be null. 477 if (c != null) 478 { 479 connList.add(c); 480 } 481 } 482 483 return connList; 484 } 485 486 487 488 /** 489 * Retrieves the human-readable name for this shutdown listener. 490 * 491 * @return The human-readable name for this shutdown listener. 492 */ 493 public String getShutdownListenerName() 494 { 495 return handlerName; 496 } 497 498 499 500 /** 501 * Causes this request handler to register itself as a shutdown listener with 502 * the Directory Server. This must be called if the connection handler is 503 * shut down without closing all associated connections, otherwise the thread 504 * would not be stopped by the server. 505 */ 506 public void registerShutdownListener() 507 { 508 DirectoryServer.registerShutdownListener(this); 509 } 510 511 512 513 /** 514 * Indicates that the Directory Server has received a request to stop running 515 * and that this shutdown listener should take any action necessary to prepare 516 * for it. 517 * 518 * @param reason The human-readable reason for the shutdown. 519 */ 520 public void processServerShutdown(LocalizableMessage reason) 521 { 522 shutdownRequested = true; 523 selector.wakeup(); 524 } 525} 526