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