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