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