mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

neil_a_wilson
30.26.2007 282288d61ff6180e6798948e3aaa49271e306e70
Update the server to provide an idle time limit configuration option that can
be used to terminate client connections that have been idle for too long. This
can be controlled on a server-wide default level using the
ds-cfg-idle-time-limit configuration attribute in the cn=config entry, but it
can also be overridden on a per-user level with the ds-rlim-idle-time-limit
operational attribute in the user's entry.

Note that while the idle time limit support is server wide, it needs help from
the connection handler to provide an idle time for client connections. At this
time, only the LDAP connection handler provides this capability. It is not
appropriate for internal connections, and it is not seen as important for JMX
connections at this time (although such support could be added in the future if
the need arises).

OpenDS Issue Number: 118
2 files added
10 files modified
819 ■■■■■ changed files
opends/resource/config/config.ldif 2 ●●●●● patch | view | raw | blame | history
opends/resource/schema/02-config.ldif 9 ●●●● patch | view | raw | blame | history
opends/src/admin/defn/org/opends/server/admin/std/GlobalConfiguration.xml 22 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/api/ClientConnection.java 55 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/config/ConfigConstants.java 9 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/CoreConfigManager.java 2 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/DirectoryServer.java 34 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/IdleTimeLimitThread.java 193 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/messages/CoreMessages.java 71 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/protocols/ldap/LDAPClientConnection.java 51 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendWorkflowElement.java 96 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/core/IdleTimeLimitTestCase.java 275 ●●●●● patch | view | raw | blame | history
opends/resource/config/config.ldif
@@ -47,6 +47,7 @@
ds-cfg-reject-unauthenticated-requests: false
ds-cfg-default-password-policy: cn=Default Password Policy,cn=Password Policies,cn=config
ds-cfg-return-bind-error-messages: false
ds-cfg-idle-time-limit: 0 seconds
ds-cfg-allowed-task: org.opends.server.tasks.AddSchemaFileTask
ds-cfg-allowed-task: org.opends.server.tasks.BackupTask
ds-cfg-allowed-task: org.opends.server.tasks.DisconnectClientTask
@@ -1380,6 +1381,7 @@
ds-cfg-alternate-bind-dn: cn=Directory Manager
ds-rlim-size-limit: 0
ds-rlim-time-limit: 0
ds-rlim-idle-time-limit: 0
ds-rlim-lookthrough-limit: 0
ds-pwp-password-policy-dn: cn=Root Password Policy,cn=Password Policies,cn=config
opends/resource/schema/02-config.ldif
@@ -1547,6 +1547,12 @@
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'OpenDS Directory Server' )
attributeTypes: ( 1.3.6.1.4.1.26027.1.1.462 NAME 'ds-cfg-ssl-cipher-suite'
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'OpenDS Directory Server' )
attributeTypes: ( 1.3.6.1.4.1.26027.1.1.463 NAME 'ds-cfg-idle-time-limit'
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE
  X-ORIGIN 'OpenDS Directory Server' )
attributeTypes: ( 1.3.6.1.4.1.26027.1.1.464 NAME 'ds-rlim-idle-time-limit'
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE USAGE directoryOperation
  X-ORIGIN 'OpenDS Directory Server' )
objectClasses: ( 1.3.6.1.4.1.26027.1.2.1
  NAME 'ds-cfg-access-control-handler' SUP top STRUCTURAL
  MUST ( cn $ ds-cfg-acl-handler-class $ ds-cfg-acl-handler-enabled )
@@ -1764,7 +1770,8 @@
  ds-cfg-reject-unauthenticated-requests  $
  ds-cfg-bind-with-dn-requires-password $ ds-cfg-lookthrough-limit $
  ds-cfg-smtp-server $ ds-cfg-allowed-task $ ds-cfg-disabled-privilege $
  ds-cfg-return-bind-error-messages ) X-ORIGIN 'OpenDS Directory Server' )
  ds-cfg-return-bind-error-messages $ ds-cfg-idle-time-limit )
  X-ORIGIN 'OpenDS Directory Server' )
objectClasses: ( 1.3.6.1.4.1.26027.1.2.41 NAME 'ds-cfg-root-dn' SUP top
  AUXILIARY MAY ds-cfg-alternate-bind-dn X-ORIGIN 'OpenDS Directory Server' )
objectClasses: ( 1.3.6.1.4.1.26027.1.2.42 NAME 'ds-cfg-root-dse'
opends/src/admin/defn/org/opends/server/admin/std/GlobalConfiguration.xml
@@ -693,5 +693,27 @@
    </adm:profile>
  </adm:property>
  <adm:property name="idle-time-limit" mandatory="false" multi-valued="false">
    <adm:synopsis>
      Specifies the maximum lenght of time that a client connection may remain
      established since its last completed operation.  A value of "0 seconds"
      indicates that no idle time limit will be enforced.
    </adm:synopsis>
    <adm:default-behavior>
      <adm:defined>
        <adm:value>0 seconds</adm:value>
      </adm:defined>
    </adm:default-behavior>
    <adm:syntax>
      <adm:duration base-unit="ms" lower-limit="0"/>
    </adm:syntax>
    <adm:profile name="ldap">
      <ldap:attribute>
        <ldap:oid>1.3.6.1.4.1.26027.1.1.463</ldap:oid>
        <ldap:name>ds-cfg-idle-time-limit</ldap:name>
      </ldap:attribute>
    </adm:profile>
  </adm:property>
</adm:managed-object>
opends/src/server/org/opends/server/api/ClientConnection.java
@@ -108,6 +108,9 @@
  // The time that this client connection was established.
  private long connectTime;
  // The idle time limit for this client connection.
  private long idleTimeLimit;
  // The opaque information used for storing intermediate state
  // information needed across multi-stage SASL binds.
  private Object saslAuthState;
@@ -138,6 +141,7 @@
    persistentSearches = new CopyOnWriteArrayList<PersistentSearch>();
    sizeLimit          = DirectoryServer.getSizeLimit();
    timeLimit          = DirectoryServer.getTimeLimit();
    idleTimeLimit      = DirectoryServer.getIdleTimeLimit();
    lookthroughLimit   = DirectoryServer.getLookthroughLimit();
    finalized          = false;
    privileges         = new HashSet<Privilege>();
@@ -1274,6 +1278,39 @@
  /**
   * Retrieves the maximum length of time in milliseconds that this
   * client connection will be allowed to remain idle before it should
   * be disconnected.
   *
   * @return  The maximum length of time in milliseconds that this
   *          client connection will be allowed to remain idle before
   *          it should be disconnected.
   */
  public final long getIdleTimeLimit()
  {
    return idleTimeLimit;
  }
  /**
   * Specifies the maximum length of time in milliseconds that this
   * client connection will be allowed to remain idle before it should
   * be disconnected.
   *
   * @param  idleTimeLimit  The maximum length of time in milliseconds
   *                        that this client connection will be
   *                        allowed to remain idle before it should be
   *                        disconnected.
   */
  public void setIdleTimeLimit(long idleTimeLimit)
  {
    this.idleTimeLimit = idleTimeLimit;
  }
  /**
   * Retrieves the default maximum number of entries that should
   * checked for matches during a search.
   *
@@ -1575,5 +1612,23 @@
    this.networkGroup = networkGroup;
  }
  /**
   * Retrieves the length of time in milliseconds that this client
   * connection has been idle.
   * <BR><BR>
   * Note that the default implementation will always return zero.
   * Subclasses associated with connection handlers should override
   * this method if they wish to provided idle time limit
   * functionality.
   *
   * @return  The length of time in milliseconds that this client
   *          connection has been idle.
   */
  public long getIdleTime()
  {
    return 0L;
  }
}
opends/src/server/org/opends/server/config/ConfigConstants.java
@@ -3541,6 +3541,15 @@
  /**
   * The name of the operational attribute that may be included in user entries
   * to specify an idle time limit to be applied for that user.
   */
  public static final String OP_ATTR_USER_IDLE_TIME_LIMIT =
      NAME_PREFIX_RLIM + "idle-time-limit";
  /**
   * The name of the operational attribute that may be included in user
   * entries to specify a size limit to be applied for that user.
   */
opends/src/server/org/opends/server/core/CoreConfigManager.java
@@ -337,6 +337,8 @@
    DirectoryServer.setReturnBindErrorMessages(
         globalConfig.isReturnBindErrorMessages());
    DirectoryServer.setIdleTimeLimit(globalConfig.getIdleTimeLimit());
  }
opends/src/server/org/opends/server/core/DirectoryServer.java
@@ -463,6 +463,9 @@
  // The number of connections currently established to the server.
  private long currentConnections;
  // The idle time limit for the server.
  private long idleTimeLimit;
  // The maximum number of connections that will be allowed at any given time.
  private long maxAllowedConnections;
@@ -731,6 +734,7 @@
    directoryServer.allowedTasks = new LinkedHashSet<String>(0);
    directoryServer.disabledPrivileges = new LinkedHashSet<Privilege>(0);
    directoryServer.returnBindErrorMessages = false;
    directoryServer.idleTimeLimit = 0L;
  }
@@ -1204,6 +1208,7 @@
      if (startConnectionHandlers)
      {
        startConnectionHandlers();
        new IdleTimeLimitThread().start();
      }
@@ -7526,6 +7531,35 @@
  /**
   * Retrieves the maximum length of time in milliseconds that client
   * connections should be allowed to remain idle without being disconnected.
   *
   * @return  The maximum length of time in milliseconds that client connections
   *          should be allowed to remain idle without being disconnected.
   */
  public static long getIdleTimeLimit()
  {
    return directoryServer.idleTimeLimit;
  }
  /**
   * Specifies the maximum length of time in milliseconds that client
   * connections should be allowed to remain idle without being disconnected.
   *
   * @param  idleTimeLimit  The maximum length of time in milliseconds that
   *                        client connections should be allowed to remain idle
   *                        without being disconnected.
   */
  public static void setIdleTimeLimit(long idleTimeLimit)
  {
    directoryServer.idleTimeLimit = idleTimeLimit;
  }
  /**
   * Registers the provided backup task listener with the Directory Server.
   *
   * @param  listener  The backup task listener to register with the Directory
opends/src/server/org/opends/server/core/IdleTimeLimitThread.java
New file
@@ -0,0 +1,193 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.core;
import org.opends.server.admin.std.server.ConnectionHandlerCfg;
import org.opends.server.api.ClientConnection;
import org.opends.server.api.ConnectionHandler;
import org.opends.server.api.DirectoryThread;
import org.opends.server.api.ServerShutdownListener;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.types.DebugLogLevel;
import org.opends.server.types.ErrorLogCategory;
import org.opends.server.types.ErrorLogSeverity;
import org.opends.server.types.DisconnectReason;
import static org.opends.server.loggers.ErrorLogger.*;
import static org.opends.server.loggers.debug.DebugLogger.*;
import static org.opends.server.messages.CoreMessages.*;
import static org.opends.server.messages.MessageHandler.*;
import static org.opends.server.util.StaticUtils.*;
/**
 * This class defines a thread that will be used to teriminate client
 * connections if they have been idle for too long.
 */
public class IdleTimeLimitThread
       extends DirectoryThread
       implements ServerShutdownListener
{
  /**
   * The debug log tracer for this object.
   */
  private static final DebugTracer TRACER = getTracer();
  // Indicates whether a shutdown request has been received.
  private boolean shutdownRequested;
  /**
   * Creates a new instance of this idle time limit thread.
   */
  public IdleTimeLimitThread()
  {
    super("Idle Time Limit Thread");
    setDaemon(true);
    shutdownRequested = false;
    DirectoryServer.registerShutdownListener(this);
  }
  /**
   * Operates in a loop, teriminating any client connections that have been idle
   * for too long.
   */
  public void run()
  {
    int    disconnectMessageID = MSGID_IDLETIME_LIMIT_EXCEEDED;
    String disconnectMessage   = getMessage(disconnectMessageID);
    long sleepTime = 5000L;
    while (! shutdownRequested)
    {
      try
      {
        try
        {
          sleep(sleepTime);
        } catch (InterruptedException ie) {}
        sleepTime = 5000L;
        for (ConnectionHandler ch : DirectoryServer.getConnectionHandlers())
        {
          ConnectionHandler<? extends ConnectionHandlerCfg> connHandler =
               (ConnectionHandler<? extends ConnectionHandlerCfg>) ch;
          for (ClientConnection c : connHandler.getClientConnections())
          {
            long idleTime = c.getIdleTime();
            if (idleTime > 0)
            {
              long idleTimeLimit = c.getIdleTimeLimit();
              if (idleTimeLimit > 0)
              {
                if (idleTime > idleTimeLimit)
                {
                  if (debugEnabled())
                  {
                    TRACER.debugInfo("Terminating client connection " +
                                     c.getConnectionID() +
                                     " due to the idle time limit");
                  }
                  try
                  {
                    c.disconnect(DisconnectReason.IDLE_TIME_LIMIT_EXCEEDED,
                                 true, disconnectMessage, disconnectMessageID);
                  }
                  catch (Exception e)
                  {
                    if (debugEnabled())
                    {
                      TRACER.debugCaught(DebugLogLevel.ERROR, e);
                    }
                    int    msgID   = MSGID_IDLETIME_DISCONNECT_ERROR;
                    String message = getMessage(msgID, c.getConnectionID(),
                                          stackTraceToSingleLineString(e));
                    logError(ErrorLogCategory.CORE_SERVER,
                             ErrorLogSeverity.MILD_ERROR, message, msgID);
                  }
                }
                else
                {
                  long shouldSleepTime = idleTimeLimit - idleTime;
                  if (shouldSleepTime < sleepTime)
                  {
                    sleepTime = shouldSleepTime;
                  }
                }
              }
            }
          }
        }
      }
      catch (Exception e)
      {
        if (debugEnabled())
        {
          TRACER.debugCaught(DebugLogLevel.ERROR, e);
        }
        int    msgID   = MSGID_IDLETIME_UNEXPECTED_ERROR;
        String message = getMessage(msgID, stackTraceToSingleLineString(e));
        logError(ErrorLogCategory.CORE_SERVER, ErrorLogSeverity.SEVERE_ERROR,
                 message, msgID);
      }
    }
  }
  /**
   * {@inheritDoc}
   */
  public String getShutdownListenerName()
  {
    return "Idle Time Limit Thread";
  }
  /**
   * {@inheritDoc}
   */
  public void processServerShutdown(String reason)
  {
    shutdownRequested = true;
  }
}
opends/src/server/org/opends/server/messages/CoreMessages.java
@@ -6277,6 +6277,58 @@
  /**
   * The message ID for the message that will be used if a user entry contains
   * multiple values for the user-specific idle time limit attribute.  This
   * takes a single argument, which is the DN of the user entry.
   */
  public static final int MSGID_BIND_MULTIPLE_USER_IDLE_TIME_LIMITS =
       CATEGORY_MASK_CORE | SEVERITY_MASK_SEVERE_WARNING | 630;
  /**
   * The message ID for the message that will be used if an error occurs while
   * trying to parse a user-specific idle time limit value as an integer.  This
   * takes two arguments, which are the provided time limit value and the DN of
   * the user entry.
   */
  public static final int MSGID_BIND_CANNOT_PROCESS_USER_IDLE_TIME_LIMIT =
       CATEGORY_MASK_CORE | SEVERITY_MASK_SEVERE_WARNING | 631;
  /**
   * The message ID for the message that will be used to indicate that the idle
   * time limit has been exceeded for a client connection.  It does not take any
   * arguments.
   */
  public static final int MSGID_IDLETIME_LIMIT_EXCEEDED =
       CATEGORY_MASK_CORE | SEVERITY_MASK_INFORMATIONAL | 632;
  /**
   * The message ID for the message that will be used if an error occurred while
   * trying to terminate a client connection.  It takes two arguments, which are
   * the connection ID and a string representation of the exception that was
   * caught.
   */
  public static final int MSGID_IDLETIME_DISCONNECT_ERROR =
       CATEGORY_MASK_CORE | SEVERITY_MASK_MILD_ERROR | 632;
  /**
   * The message ID for the message that will be used if an unexpected error
   * occurred in the time thread.  It takes a single argument, which is a string
   * representation of the exception that was caught.
   */
  public static final int MSGID_IDLETIME_UNEXPECTED_ERROR =
       CATEGORY_MASK_CORE | SEVERITY_MASK_SEVERE_ERROR | 632;
  /**
   * Associates a set of generic messages with the message IDs defined
   * in this class.
   */
@@ -7104,6 +7156,14 @@
                    "The user-specific time limit value %s contained in " +
                    "user entry %s could not be parsed as an integer.  The " +
                    "default server time limit will be used");
    registerMessage(MSGID_BIND_MULTIPLE_USER_IDLE_TIME_LIMITS,
                    "There are multiple user-specific idle time limit values " +
                    "contained in user entry %s.  The default server idle " +
                    "time limit will be used");
    registerMessage(MSGID_BIND_CANNOT_PROCESS_USER_IDLE_TIME_LIMIT,
                    "The user-specific idle time limit value %s contained in " +
                    "user entry %s could not be parsed as an integer.  The " +
                    "default server idle time limit will be used");
    registerMessage(MSGID_BIND_PASSWORD_EXPIRING,
                    "The user password is about to expire (time to " +
                    "expiration:  %s)");
@@ -8549,6 +8609,17 @@
    registerMessage(MSGID_ENTRYENCODECFG_INVALID_LENGTH,
                    "Unable to decode the provided entry encode " +
                    "configuration element because it has an invalid length");
    registerMessage(MSGID_IDLETIME_LIMIT_EXCEEDED,
                    "This connection has been teriminated because it has " +
                    "remained idle for too long");
    registerMessage(MSGID_IDLETIME_DISCONNECT_ERROR,
                    "An error occurred while attempting to disconnect " +
                    "client connection %d:  %s");
    registerMessage(MSGID_IDLETIME_UNEXPECTED_ERROR,
                    "An unexpected error occurred in the idle time limit " +
                    "thread:  %s");
  }
}
opends/src/server/org/opends/server/protocols/ldap/LDAPClientConnection.java
@@ -89,6 +89,7 @@
import org.opends.server.types.ResultCode;
import org.opends.server.types.SearchResultEntry;
import org.opends.server.types.SearchResultReference;
import org.opends.server.util.TimeThread;
@@ -108,6 +109,9 @@
  // The time that the last operation was completed.
  private AtomicLong lastCompletionTime;
  // The next operation ID that should be used for this connection.
  private AtomicLong nextOperationID;
@@ -240,6 +244,7 @@
    ldapVersion          = 3;
    requestHandler       = null;
    lastCompletionTime   = new AtomicLong(TimeThread.getTime());
    nextOperationID      = new AtomicLong(0);
    connectionValid      = true;
    disconnectRequested  = false;
@@ -1210,6 +1215,7 @@
      }
      operationsInProgress.remove(messageID);
      lastCompletionTime.set(TimeThread.getTime());
      throw de;
    }
@@ -1247,7 +1253,15 @@
  public boolean removeOperationInProgress(int messageID)
  {
    AbstractOperation operation = operationsInProgress.remove(messageID);
    return (operation != null);
    if (operation == null)
    {
      return false;
    }
    else
    {
      lastCompletionTime.set(TimeThread.getTime());
      return true;
    }
  }
@@ -1333,6 +1347,12 @@
        }
      }
      if (! (operationsInProgress.isEmpty() &&
             getPersistentSearches().isEmpty()))
      {
        lastCompletionTime.set(TimeThread.getTime());
      }
      operationsInProgress.clear();
@@ -1401,12 +1421,14 @@
        }
        operationsInProgress.remove(msgID);
        lastCompletionTime.set(TimeThread.getTime());
      }
      for (PersistentSearch persistentSearch : getPersistentSearches())
      {
        DirectoryServer.deregisterPersistentSearch(persistentSearch);
        lastCompletionTime.set(TimeThread.getTime());
      }
    }
    catch (Exception e)
@@ -2789,5 +2811,32 @@
  {
    return connectionHandler.getSSLServerCertNickname();
  }
  /**
   * Retrieves the length of time in milliseconds that this client
   * connection has been idle.
   * <BR><BR>
   * Note that the default implementation will always return zero.
   * Subclasses associated with connection handlers should override
   * this method if they wish to provided idle time limit
   * functionality.
   *
   * @return  The length of time in milliseconds that this client
   *          connection has been idle.
   */
  public long getIdleTime()
  {
    if (operationsInProgress.isEmpty() && getPersistentSearches().isEmpty())
    {
      return (TimeThread.getTime() - lastCompletionTime.get());
    }
    else
    {
      // There's at least one operation in progress, so it's not idle.
      return 0L;
    }
  }
}
opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendWorkflowElement.java
@@ -3091,6 +3091,7 @@
    int     sizeLimit        = DirectoryServer.getSizeLimit();
    int     timeLimit        = DirectoryServer.getTimeLimit();
    int     lookthroughLimit = DirectoryServer.getLookthroughLimit();
    long    idleTimeLimit    = DirectoryServer.getIdleTimeLimit();
    boolean skipPostOperation = false;
    // The password policy state information for this bind operation.
@@ -3705,6 +3706,53 @@
              }
              // See if the user's entry contains a custom idle time limit.
              attrType = DirectoryServer.getAttributeType(
                              OP_ATTR_USER_IDLE_TIME_LIMIT, true);
              attrList = userEntry.getAttribute(attrType);
              if ((attrList != null) && (attrList.size() == 1))
              {
                Attribute a = attrList.get(0);
                LinkedHashSet<AttributeValue>  values = a.getValues();
                Iterator<AttributeValue> iterator = values.iterator();
                if (iterator.hasNext())
                {
                  AttributeValue v = iterator.next();
                  if (iterator.hasNext())
                  {
                    int msgID = MSGID_BIND_MULTIPLE_USER_IDLE_TIME_LIMITS;
                    String message =
                         getMessage(msgID, String.valueOf(userEntry.getDN()));
                    logError(ErrorLogCategory.CORE_SERVER,
                             ErrorLogSeverity.SEVERE_WARNING, message, msgID);
                  }
                  else
                  {
                    try
                    {
                      idleTimeLimit =
                           1000L * Long.parseLong(v.getStringValue());
                    }
                    catch (Exception e)
                    {
                      if (debugEnabled())
                      {
                        TRACER.debugCaught(DebugLogLevel.ERROR, e);
                      }
                      int msgID =
                           MSGID_BIND_CANNOT_PROCESS_USER_IDLE_TIME_LIMIT;
                      String message =
                           getMessage(msgID, v.getStringValue(),
                                      String.valueOf(userEntry.getDN()));
                      logError(ErrorLogCategory.CORE_SERVER,
                               ErrorLogSeverity.SEVERE_WARNING, message, msgID);
                    }
                  }
                }
              }
              // See if the user's entry contains a custom lookthrough limit.
              attrType =
                   DirectoryServer.getAttributeType(
@@ -4272,6 +4320,53 @@
              }
              // See if the user's entry contains a custom idle time limit.
              attrType = DirectoryServer.getAttributeType(
                              OP_ATTR_USER_IDLE_TIME_LIMIT, true);
              attrList = saslAuthUserEntry.getAttribute(attrType);
              if ((attrList != null) && (attrList.size() == 1))
              {
                Attribute a = attrList.get(0);
                LinkedHashSet<AttributeValue>  values = a.getValues();
                Iterator<AttributeValue> iterator = values.iterator();
                if (iterator.hasNext())
                {
                  AttributeValue v = iterator.next();
                  if (iterator.hasNext())
                  {
                    int msgID = MSGID_BIND_MULTIPLE_USER_IDLE_TIME_LIMITS;
                    String message =
                         getMessage(msgID, String.valueOf(userDNString));
                    logError(ErrorLogCategory.CORE_SERVER,
                             ErrorLogSeverity.SEVERE_WARNING, message, msgID);
                  }
                  else
                  {
                    try
                    {
                      idleTimeLimit =
                           1000L * Long.parseLong(v.getStringValue());
                    }
                    catch (Exception e)
                    {
                      if (debugEnabled())
                      {
                        TRACER.debugCaught(DebugLogLevel.ERROR, e);
                      }
                      int msgID =
                           MSGID_BIND_CANNOT_PROCESS_USER_IDLE_TIME_LIMIT;
                      String message =
                           getMessage(msgID, v.getStringValue(),
                                      String.valueOf(userDNString));
                      logError(ErrorLogCategory.CORE_SERVER,
                               ErrorLogSeverity.SEVERE_WARNING, message, msgID);
                    }
                  }
                }
              }
              // See if the user's entry contains a custom lookthrough limit.
              attrType =
                   DirectoryServer.getAttributeType(
@@ -4422,6 +4517,7 @@
      clientConnection.setAuthenticationInfo(authInfo);
      clientConnection.setSizeLimit(sizeLimit);
      clientConnection.setTimeLimit(timeLimit);
      clientConnection.setIdleTimeLimit(idleTimeLimit);
      clientConnection.setLookthroughLimit(lookthroughLimit);
      clientConnection.setMustChangePassword(mustChangePassword);
opends/tests/unit-tests-testng/src/server/org/opends/server/core/IdleTimeLimitTestCase.java
New file
@@ -0,0 +1,275 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.core;
import java.net.Socket;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import org.opends.server.TestCaseUtils;
import org.opends.server.protocols.asn1.*;
import org.opends.server.protocols.ldap.*;
import org.opends.server.tools.dsconfig.DSConfig;
import static org.testng.Assert.*;
/**
 * A set of test cases that involve disconnecting clients due to the idle time
 * limit.
 */
public class IdleTimeLimitTestCase
       extends CoreTestCase
{
  /**
   * Ensures that the Directory Server is running.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @BeforeClass()
  public void startServer()
         throws Exception
  {
    TestCaseUtils.startServer();
  }
  /**
   * Tests the server-wide idle time limit for an anonymous connection.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(groups="slow")
  public void testServerWideAnonymousIdleTimeLimit()
         throws Exception
  {
    String[] args =
    {
      "-h", "127.0.0.1",
      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
      "-D", "cn=Directory Manager",
      "-w", "password",
      "set-global-configuration-prop",
      "--set", "idle-time-limit:5 seconds"
    };
    assertEquals(DSConfig.main(args, false, System.out, System.err), 0);
    Socket s = null;
    try
    {
      s = new Socket("127.0.0.1", TestCaseUtils.getServerLdapPort());
      ASN1Writer w = new ASN1Writer(s);
      ASN1Reader r = new ASN1Reader(s);
      r.setIOTimeout(60000);
      LDAPMessage m = LDAPMessage.decode(r.readElement().decodeAsSequence());
      ExtendedResponseProtocolOp extendedResponse =
           m.getExtendedResponseProtocolOp();
      assertEquals(extendedResponse.getOID(),
                   LDAPConstants.OID_NOTICE_OF_DISCONNECTION);
      assertNull(r.readElement());
    }
    finally
    {
      try
      {
        s.close();
      } catch (Exception e) {}
      args = new String[]
      {
        "-h", "127.0.0.1",
        "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
        "-D", "cn=Directory Manager",
        "-w", "password",
        "set-global-configuration-prop",
        "--set", "idle-time-limit:0 seconds"
      };
      assertEquals(DSConfig.main(args, false, System.out, System.err), 0);
    }
  }
  /**
   * Tests the server-wide idle time limit for an authenticated connection.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(groups="slow")
  public void testServerWideAuthenticatedIdleTimeLimit()
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
    TestCaseUtils.addEntry(
      "dn: uid=test.user,o=test",
      "objectClass: top",
      "objectClass: person",
      "objectClass: organizationalPerson",
      "objectClass: inetOrgPerson",
      "uid: test.user",
      "givenName: Test",
      "sn: User",
      "cn: Test User",
      "userPassword: password"
    );
    String[] args =
    {
      "-h", "127.0.0.1",
      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
      "-D", "cn=Directory Manager",
      "-w", "password",
      "set-global-configuration-prop",
      "--set", "idle-time-limit:5 seconds"
    };
    assertEquals(DSConfig.main(args, false, System.out, System.err), 0);
    Socket s = null;
    try
    {
      s = new Socket("127.0.0.1", TestCaseUtils.getServerLdapPort());
      ASN1Writer w = new ASN1Writer(s);
      ASN1Reader r = new ASN1Reader(s);
      r.setIOTimeout(60000);
      BindRequestProtocolOp bindRequest = new BindRequestProtocolOp(
           new ASN1OctetString("uid=test.user,o=test"), 3,
           new ASN1OctetString("password"));
      LDAPMessage m = new LDAPMessage(1, bindRequest);
      w.writeElement(m.encode());
      m = LDAPMessage.decode(r.readElement().decodeAsSequence());
      BindResponseProtocolOp bindResponse = m.getBindResponseProtocolOp();
      assertEquals(bindResponse.getResultCode(), 0);
      m = LDAPMessage.decode(r.readElement().decodeAsSequence());
      ExtendedResponseProtocolOp extendedResponse =
           m.getExtendedResponseProtocolOp();
      assertEquals(extendedResponse.getOID(),
                   LDAPConstants.OID_NOTICE_OF_DISCONNECTION);
      assertNull(r.readElement());
    }
    finally
    {
      try
      {
        s.close();
      } catch (Exception e) {}
      args = new String[]
      {
        "-h", "127.0.0.1",
        "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
        "-D", "cn=Directory Manager",
        "-w", "password",
        "set-global-configuration-prop",
        "--set", "idle-time-limit:0 seconds"
      };
      assertEquals(DSConfig.main(args, false, System.out, System.err), 0);
    }
  }
  /**
   * Tests a user-specific idle time limit.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(groups="slow")
  public void testUserSpecificIdleTimeLimit()
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
    TestCaseUtils.addEntry(
      "dn: uid=test.user,o=test",
      "objectClass: top",
      "objectClass: person",
      "objectClass: organizationalPerson",
      "objectClass: inetOrgPerson",
      "uid: test.user",
      "givenName: Test",
      "sn: User",
      "cn: Test User",
      "userPassword: password",
      "ds-rlim-idle-time-limit: 5"
    );
    Socket s = null;
    try
    {
      s = new Socket("127.0.0.1", TestCaseUtils.getServerLdapPort());
      ASN1Writer w = new ASN1Writer(s);
      ASN1Reader r = new ASN1Reader(s);
      r.setIOTimeout(60000);
      BindRequestProtocolOp bindRequest = new BindRequestProtocolOp(
           new ASN1OctetString("uid=test.user,o=test"), 3,
           new ASN1OctetString("password"));
      LDAPMessage m = new LDAPMessage(1, bindRequest);
      w.writeElement(m.encode());
      m = LDAPMessage.decode(r.readElement().decodeAsSequence());
      BindResponseProtocolOp bindResponse = m.getBindResponseProtocolOp();
      assertEquals(bindResponse.getResultCode(), 0);
      m = LDAPMessage.decode(r.readElement().decodeAsSequence());
      ExtendedResponseProtocolOp extendedResponse =
           m.getExtendedResponseProtocolOp();
      assertEquals(extendedResponse.getOID(),
                   LDAPConstants.OID_NOTICE_OF_DISCONNECTION);
      assertNull(r.readElement());
    }
    finally
    {
      try
      {
        s.close();
      } catch (Exception e) {}
    }
  }
}