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 int readResult = clientConnection.processDataRead(); 293 if (readResult < 0) 294 { 295 key.cancel(); 296 } 297 if (readResult > 0) { 298 readyConnections.add(clientConnection); 299 } 300 } 301 catch (Exception e) 302 { 303 logger.traceException(e); 304 305 // We got some other kind of error. If nothing else, cancel the 306 // key, but if the client connection is available then 307 // disconnect it as well. 308 key.cancel(); 309 310 if (clientConnection != null) 311 { 312 clientConnection.disconnect(DisconnectReason.SERVER_ERROR, false, 313 ERR_UNEXPECTED_EXCEPTION_ON_CLIENT_CONNECTION.get(getExceptionMessage(e))); 314 } 315 } 316 } 317 else if (! key.isValid()) 318 { 319 key.cancel(); 320 } 321 } 322 catch (CancelledKeyException cke) 323 { 324 logger.traceException(cke); 325 326 // This could happen if a connection was closed between the time 327 // that select returned and the time that we try to access the 328 // associated channel. If that was the case, we don't need to do 329 // anything. 330 } 331 catch (Exception e) 332 { 333 logger.traceException(e); 334 335 // This should not happen, and it would have caused our reader 336 // thread to die. Log a severe error. 337 logger.error(ERR_LDAP_REQHANDLER_UNEXPECTED_SELECT_EXCEPTION, getName(), getExceptionMessage(e)); 338 } 339 finally 340 { 341 if (!key.isValid()) 342 { 343 // Help GC - release the connection. 344 key.attach(null); 345 } 346 347 iterator.remove(); 348 } 349 } 350 } 351 } 352 353 // Disconnect all active connections. 354 SelectionKey[] keyArray = selector.keys().toArray(new SelectionKey[0]); 355 for (SelectionKey key : keyArray) 356 { 357 LDAPClientConnection c = (LDAPClientConnection) key.attachment(); 358 359 try 360 { 361 key.channel().close(); 362 } 363 catch (Exception e) 364 { 365 logger.traceException(e); 366 } 367 368 try 369 { 370 key.cancel(); 371 } 372 catch (Exception e) 373 { 374 logger.traceException(e); 375 } 376 377 try 378 { 379 c.disconnect(DisconnectReason.SERVER_SHUTDOWN, true, 380 ERR_LDAP_REQHANDLER_DEREGISTER_DUE_TO_SHUTDOWN.get()); 381 } 382 catch (Exception e) 383 { 384 logger.traceException(e); 385 } 386 } 387 388 // Disconnect all pending connections. 389 synchronized (pendingConnectionsLock) 390 { 391 for (LDAPClientConnection c : pendingConnections) 392 { 393 try 394 { 395 c.disconnect(DisconnectReason.SERVER_SHUTDOWN, true, 396 ERR_LDAP_REQHANDLER_DEREGISTER_DUE_TO_SHUTDOWN.get()); 397 } 398 catch (Exception e) 399 { 400 logger.traceException(e); 401 } 402 } 403 } 404 } 405 406 407 408 /** 409 * Registers the provided client connection with this request 410 * handler so that any requests received from that client will be 411 * processed. 412 * 413 * @param clientConnection 414 * The client connection to be registered with this request 415 * handler. 416 * @return <CODE>true</CODE> if the client connection was properly 417 * registered with this request handler, or 418 * <CODE>false</CODE> if not. 419 */ 420 public boolean registerClient(LDAPClientConnection clientConnection) 421 { 422 // FIXME -- Need to check if the maximum client limit has been reached. 423 424 425 // If the server is in the process of shutting down, then we don't want to 426 // accept it. 427 if (shutdownRequested) 428 { 429 clientConnection.disconnect(DisconnectReason.SERVER_SHUTDOWN, true, 430 ERR_LDAP_REQHANDLER_REJECT_DUE_TO_SHUTDOWN.get()); 431 return false; 432 } 433 434 // Try to add the new connection to the queue. If it succeeds, then wake 435 // up the selector so it will be picked up right away. Otherwise, 436 // disconnect the client. 437 synchronized (pendingConnectionsLock) 438 { 439 pendingConnections.add(clientConnection); 440 } 441 442 selector.wakeup(); 443 return true; 444 } 445 446 447 448 /** 449 * Retrieves the set of all client connections that are currently registered 450 * with this request handler. 451 * 452 * @return The set of all client connections that are currently registered 453 * with this request handler. 454 */ 455 public Collection<LDAPClientConnection> getClientConnections() 456 { 457 ArrayList<LDAPClientConnection> connList = new ArrayList<>(keys.length); 458 for (SelectionKey key : keys) 459 { 460 LDAPClientConnection c = (LDAPClientConnection) key.attachment(); 461 462 // If the client has disconnected the attachment may be null. 463 if (c != null) 464 { 465 connList.add(c); 466 } 467 } 468 469 return connList; 470 } 471 472 473 474 /** 475 * Retrieves the human-readable name for this shutdown listener. 476 * 477 * @return The human-readable name for this shutdown listener. 478 */ 479 public String getShutdownListenerName() 480 { 481 return handlerName; 482 } 483 484 485 486 /** 487 * Causes this request handler to register itself as a shutdown listener with 488 * the Directory Server. This must be called if the connection handler is 489 * shut down without closing all associated connections, otherwise the thread 490 * would not be stopped by the server. 491 */ 492 public void registerShutdownListener() 493 { 494 DirectoryServer.registerShutdownListener(this); 495 } 496 497 498 499 /** 500 * Indicates that the Directory Server has received a request to stop running 501 * and that this shutdown listener should take any action necessary to prepare 502 * for it. 503 * 504 * @param reason The human-readable reason for the shutdown. 505 */ 506 public void processServerShutdown(LocalizableMessage reason) 507 { 508 shutdownRequested = true; 509 selector.wakeup(); 510 } 511} 512