From b1e3b0ccdaa423b68ef6fa2fee67d3e09990985f Mon Sep 17 00:00:00 2001
From: neil_a_wilson <neil_a_wilson@localhost>
Date: Mon, 24 Sep 2007 23:15:46 +0000
Subject: [PATCH] Perform a significant refactoring of the local backend workflow element classes so that they use smaller method sizes, which allows the VM to better optimize the code.  Also, move the code for processing each type of operation into the class for the associated operation rather than keeping it all in the LocalBackendWorkflowElement class.

---
 opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendBindOperation.java | 1260 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 1,256 insertions(+), 4 deletions(-)

diff --git a/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendBindOperation.java b/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendBindOperation.java
index e101a13..4e209a7 100644
--- a/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendBindOperation.java
+++ b/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendBindOperation.java
@@ -26,21 +26,146 @@
  */
 package org.opends.server.workflowelement.localbackend;
 
+
+
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.concurrent.locks.Lock;
+
+import org.opends.messages.Message;
+import org.opends.server.admin.std.meta.PasswordPolicyCfgDefn;
+import org.opends.server.api.Backend;
+import org.opends.server.api.ClientConnection;
+import org.opends.server.api.SASLMechanismHandler;
+import org.opends.server.api.plugin.PostOperationPluginResult;
+import org.opends.server.api.plugin.PreOperationPluginResult;
+import org.opends.server.controls.AuthorizationIdentityResponseControl;
+import org.opends.server.controls.PasswordExpiredControl;
+import org.opends.server.controls.PasswordExpiringControl;
+import org.opends.server.controls.PasswordPolicyErrorType;
+import org.opends.server.controls.PasswordPolicyResponseControl;
+import org.opends.server.controls.PasswordPolicyWarningType;
+import org.opends.server.core.AccessControlConfigManager;
 import org.opends.server.core.BindOperation;
 import org.opends.server.core.BindOperationWrapper;
+import org.opends.server.core.DirectoryServer;
+import org.opends.server.core.PasswordPolicy;
+import org.opends.server.core.PasswordPolicyState;
+import org.opends.server.core.PluginConfigManager;
+import org.opends.server.loggers.debug.DebugTracer;
+import org.opends.server.types.AccountStatusNotification;
+import org.opends.server.types.AccountStatusNotificationType;
+import org.opends.server.types.Attribute;
+import org.opends.server.types.AttributeType;
+import org.opends.server.types.AttributeValue;
+import org.opends.server.types.AuthenticationInfo;
+import org.opends.server.types.ByteString;
+import org.opends.server.types.Control;
+import org.opends.server.types.DebugLogLevel;
+import org.opends.server.types.DirectoryException;
+import org.opends.server.types.DN;
+import org.opends.server.types.Entry;
+import org.opends.server.types.LockManager;
+import org.opends.server.types.ResultCode;
+import org.opends.server.types.WritabilityMode;
 import org.opends.server.types.operation.PostOperationBindOperation;
 import org.opends.server.types.operation.PostResponseBindOperation;
 import org.opends.server.types.operation.PreOperationBindOperation;
 
+import static org.opends.messages.CoreMessages.*;
+import static org.opends.server.config.ConfigConstants.*;
+import static org.opends.server.loggers.ErrorLogger.*;
+import static org.opends.server.loggers.debug.DebugLogger.*;
+import static org.opends.server.util.ServerConstants.*;
+import static org.opends.server.util.StaticUtils.*;
+
+
+
 /**
  * This class defines an operation used to bind against the Directory Server,
  * with the bound user entry within a local backend.
  */
-public class LocalBackendBindOperation extends BindOperationWrapper
-  implements PreOperationBindOperation,
-             PostOperationBindOperation,
-             PostResponseBindOperation
+public class LocalBackendBindOperation
+       extends BindOperationWrapper
+       implements PreOperationBindOperation, PostOperationBindOperation,
+                  PostResponseBindOperation
 {
+  /**
+   * The tracer object for the debug logger.
+   */
+  private static final DebugTracer TRACER = getTracer();
+
+
+
+  // The backend in which the bind operation should be processed.
+  private Backend backend;
+
+  // Indicates whether the bind response should include the first warning for an
+  // upcoming password expiration.
+  private boolean isFirstWarning;
+
+  // Indicates whether this bind is using a grace login for the user.
+  private boolean isGraceLogin;
+
+  // Indicates whether the user must change his/her password before doing
+  // anything else.
+  private boolean mustChangePassword;
+
+  // Indicates whether the user requested the password policy control.
+  private boolean pwPolicyControlRequested;
+
+  // Indicates whether the server should return the authorization ID as a
+  // control in the bind response.
+  private boolean returnAuthzID;
+
+  // Indicates whether to skip post-operation plugin processing.
+  private boolean skipPostOperation;
+
+  // The client connection associated with this bind operation.
+  private ClientConnection clientConnection;
+
+  // The bind DN provided by the client.
+  private DN bindDN;
+
+  // The entry of the user that successfully authenticated during processing for
+  // this bind operation.
+  private Entry authenticatedUserEntry;
+
+  // The lookthrough limit that should be enforced for the user.
+  private int lookthroughLimit;
+
+  // The value to use for the password policy warning.
+  private int pwPolicyWarningValue;
+
+  // The size limit that should be enforced for the user.
+  private int sizeLimit;
+
+  // The time limit that should be enforced for the user.
+  private int timeLimit;
+
+  // The idle time limit that should be enforced for the user.
+  private long idleTimeLimit;
+
+  // The password policy that applies to the user.
+  private PasswordPolicy policy;
+
+  // The password policy state for the user.
+  private PasswordPolicyState pwPolicyState;
+
+  // The password policy error type for this bind operation.
+  private PasswordPolicyErrorType pwPolicyErrorType;
+
+  // The password policy warning type for this bind operation.
+  private PasswordPolicyWarningType pwPolicyWarningType;
+
+  // The plugin config manager for the Directory Server.
+  private PluginConfigManager pluginConfigManager;
+
+  // The SASL mechanism used for this bind operation.
+  private String saslMechanism;
+
+
 
   /**
    * Creates a new operation that may be used to bind where
@@ -54,4 +179,1131 @@
     LocalBackendWorkflowElement.attachLocalOperation (bind, this);
   }
 
+
+
+  /**
+   * Process this bind operation in a local backend.
+   *
+   * @param  backend  The backend in which the bind operation should be
+   *                  processed.
+   */
+  void processLocalBind(Backend backend)
+  {
+    this.backend = backend;
+
+    // Initialize a number of variables for use during the bind processing.
+    clientConnection         = getClientConnection();
+    returnAuthzID            = false;
+    skipPostOperation        = false;
+    sizeLimit                = DirectoryServer.getSizeLimit();
+    timeLimit                = DirectoryServer.getTimeLimit();
+    lookthroughLimit         = DirectoryServer.getLookthroughLimit();
+    idleTimeLimit            = DirectoryServer.getIdleTimeLimit();
+    bindDN                   = getBindDN();
+    saslMechanism            = getSASLMechanism();
+    pwPolicyState            = null;
+    pwPolicyErrorType        = null;
+    pwPolicyControlRequested = false;
+    isGraceLogin             = false;
+    isFirstWarning           = false;
+    mustChangePassword       = false;
+    pwPolicyWarningType      = null;
+    pwPolicyWarningValue     = -1 ;
+    authenticatedUserEntry   = null;
+    pluginConfigManager      = DirectoryServer.getPluginConfigManager();
+
+
+    // Create a labeled block of code that we can break out of if a problem is
+    // detected.
+bindProcessing:
+    {
+      // Check to see if the client has permission to perform the
+      // bind.
+
+      // FIXME: for now assume that this will check all permission
+      // pertinent to the operation. This includes any controls
+      // specified.
+      if (! AccessControlConfigManager.getInstance().getAccessControlHandler().
+                 isAllowed(this))
+      {
+        setResultCode(ResultCode.INVALID_CREDENTIALS);
+        setAuthFailureReason(ERR_BIND_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS.get(
+                                  String.valueOf(bindDN)));
+        skipPostOperation = true;
+        break bindProcessing;
+      }
+
+      // Check to see if there are any controls in the request.  If so, then see
+      // if there is any special processing required.
+      try
+      {
+        handleRequestControls();
+      }
+      catch (DirectoryException de)
+      {
+        if (debugEnabled())
+        {
+          TRACER.debugCaught(DebugLogLevel.ERROR, de);
+        }
+
+        setResponseData(de);
+        break bindProcessing;
+      }
+
+
+      // Check to see if this is a simple bind or a SASL bind and process
+      // accordingly.
+      switch (getAuthenticationType())
+      {
+        case SIMPLE:
+          try
+          {
+            if (! processSimpleBind())
+            {
+              return;
+            }
+          }
+          catch (DirectoryException de)
+          {
+            if (debugEnabled())
+            {
+              TRACER.debugCaught(DebugLogLevel.ERROR, de);
+            }
+
+            if (de.getResultCode() == ResultCode.INVALID_CREDENTIALS)
+            {
+              setResultCode(ResultCode.INVALID_CREDENTIALS);
+              setAuthFailureReason(de.getMessageObject());
+            }
+            else
+            {
+              setResponseData(de);
+            }
+            break bindProcessing;
+          }
+          break;
+
+
+        case SASL:
+          try
+          {
+            if (! processSASLBind())
+            {
+              return;
+            }
+          }
+          catch (DirectoryException de)
+          {
+            if (debugEnabled())
+            {
+              TRACER.debugCaught(DebugLogLevel.ERROR, de);
+            }
+
+            if (de.getResultCode() == ResultCode.INVALID_CREDENTIALS)
+            {
+              setResultCode(ResultCode.INVALID_CREDENTIALS);
+              setAuthFailureReason(de.getMessageObject());
+            }
+            else
+            {
+              setResponseData(de);
+            }
+            break bindProcessing;
+          }
+          break;
+
+
+        default:
+          // Send a protocol error response to the client and disconnect.
+          // NYI
+          return;
+      }
+    }
+
+
+    // Update the user's account with any password policy changes that may be
+    // required.
+    try
+    {
+      if (pwPolicyState != null)
+      {
+        pwPolicyState.updateUserEntry();
+      }
+    }
+    catch (DirectoryException de)
+    {
+      if (debugEnabled())
+      {
+        TRACER.debugCaught(DebugLogLevel.ERROR, de);
+      }
+
+      setResponseData(de);
+    }
+
+
+    // Invoke the post-operation bind plugins.
+    if (! skipPostOperation)
+    {
+      PostOperationPluginResult postOpResult =
+           pluginConfigManager.invokePostOperationBindPlugins(this);
+      if (postOpResult.connectionTerminated())
+      {
+        // There's no point in continuing with anything.  Log the result
+        // and return.
+        setResultCode(ResultCode.CANCELED);
+        appendErrorMessage(ERR_CANCELED_BY_PREOP_DISCONNECT.get());
+        setProcessingStopTime();
+        return;
+      }
+    }
+
+
+    // Update the authentication information for the user.
+    AuthenticationInfo authInfo = getAuthenticationInfo();
+    if ((getResultCode() == ResultCode.SUCCESS) && (authInfo != null))
+    {
+      authenticatedUserEntry = authInfo.getAuthenticationEntry();
+      clientConnection.setAuthenticationInfo(authInfo);
+      clientConnection.setSizeLimit(sizeLimit);
+      clientConnection.setTimeLimit(timeLimit);
+      clientConnection.setIdleTimeLimit(idleTimeLimit);
+      clientConnection.setLookthroughLimit(lookthroughLimit);
+      clientConnection.setMustChangePassword(mustChangePassword);
+
+      if (returnAuthzID)
+      {
+        addResponseControl(new AuthorizationIdentityResponseControl(
+                                    authInfo.getAuthorizationDN()));
+      }
+    }
+
+
+    // See if we need to send a password policy control to the client.  If so,
+    // then add it to the response.
+    if (getResultCode() == ResultCode.SUCCESS)
+    {
+      if (pwPolicyControlRequested)
+      {
+        PasswordPolicyResponseControl pwpControl =
+             new PasswordPolicyResponseControl(pwPolicyWarningType,
+                                               pwPolicyWarningValue,
+                                               pwPolicyErrorType);
+        addResponseControl(pwpControl);
+      }
+      else
+      {
+        if (pwPolicyErrorType == PasswordPolicyErrorType.PASSWORD_EXPIRED)
+        {
+          addResponseControl(new PasswordExpiredControl());
+        }
+        else if (pwPolicyWarningType ==
+                 PasswordPolicyWarningType.TIME_BEFORE_EXPIRATION)
+        {
+          addResponseControl(new PasswordExpiringControl(pwPolicyWarningValue));
+        }
+      }
+    }
+    else
+    {
+      if (pwPolicyControlRequested)
+      {
+        PasswordPolicyResponseControl pwpControl =
+             new PasswordPolicyResponseControl(pwPolicyWarningType,
+                                               pwPolicyWarningValue,
+                                               pwPolicyErrorType);
+        addResponseControl(pwpControl);
+      }
+      else
+      {
+        if (pwPolicyErrorType == PasswordPolicyErrorType.PASSWORD_EXPIRED)
+        {
+          addResponseControl(new PasswordExpiredControl());
+        }
+      }
+    }
+
+    // Stop the processing timer.
+    setProcessingStopTime();
+  }
+
+
+
+  /**
+   * Handles request control processing for this bind operation.
+   *
+   * @throws  DirectoryException  If there is a problem with any of the
+   *                              controls.
+   */
+  private void handleRequestControls()
+          throws DirectoryException
+  {
+    List<Control> requestControls = getRequestControls();
+    if ((requestControls != null) && (! requestControls.isEmpty()))
+    {
+      for (int i=0; i < requestControls.size(); i++)
+      {
+        Control c   = requestControls.get(i);
+        String  oid = c.getOID();
+
+        if (! AccessControlConfigManager.getInstance().
+                 getAccessControlHandler(). isAllowed(bindDN, this, c))
+        {
+          skipPostOperation = true;
+          throw new DirectoryException(ResultCode.INSUFFICIENT_ACCESS_RIGHTS,
+                         ERR_CONTROL_INSUFFICIENT_ACCESS_RIGHTS.get(oid));
+        }
+
+        if (oid.equals(OID_AUTHZID_REQUEST))
+        {
+          returnAuthzID = true;
+        }
+        else if (oid.equals(OID_PASSWORD_POLICY_CONTROL))
+        {
+          pwPolicyControlRequested = true;
+        }
+
+        // NYI -- Add support for additional controls.
+
+        else if (c.isCritical())
+        {
+          throw new DirectoryException(
+                         ResultCode.UNAVAILABLE_CRITICAL_EXTENSION,
+                         ERR_BIND_UNSUPPORTED_CRITICAL_CONTROL.get(oid));
+        }
+      }
+    }
+  }
+
+
+
+  /**
+   * Performs the processing necessary for a simple bind operation.
+   *
+   * @return  {@code true} if processing should continue for the operation, or
+   *          {@code false} if not.
+   *
+   * @throws  DirectoryException  If a problem occurs that should cause the bind
+   *                              operation to fail.
+   */
+  private boolean processSimpleBind()
+          throws DirectoryException
+  {
+    // See if this is an anonymous bind.  If so, then determine whether
+    // to allow it.
+    ByteString simplePassword = getSimplePassword();
+    if ((simplePassword == null) || (simplePassword.value().length == 0))
+    {
+      return processAnonymousSimpleBind();
+    }
+
+    // See if the bind DN is actually one of the alternate root DNs
+    // defined in the server.  If so, then replace it with the actual DN
+    // for that user.
+    DN actualRootDN = DirectoryServer.getActualRootBindDN(bindDN);
+    if (actualRootDN != null)
+    {
+      bindDN = actualRootDN;
+    }
+
+    // Get the user entry based on the bind DN.  If it does not exist,
+    // then fail.
+    Lock userLock = null;
+    for (int i=0; i < 3; i++)
+    {
+      userLock = LockManager.lockRead(bindDN);
+      if (userLock != null)
+      {
+        break;
+      }
+    }
+
+    if (userLock == null)
+    {
+      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
+                                   ERR_BIND_OPERATION_CANNOT_LOCK_USER.get(
+                                        String.valueOf(bindDN)));
+    }
+
+    try
+    {
+      Entry userEntry;
+      try
+      {
+        userEntry = backend.getEntry(bindDN);
+      }
+      catch (DirectoryException de)
+      {
+        if (debugEnabled())
+        {
+          TRACER.debugCaught(DebugLogLevel.ERROR, de);
+        }
+
+        userEntry = null;
+        throw new DirectoryException(ResultCode.INVALID_CREDENTIALS,
+                                     de.getMessageObject());
+      }
+
+      if (userEntry == null)
+      {
+        throw new DirectoryException(ResultCode.INVALID_CREDENTIALS,
+                                     ERR_BIND_OPERATION_UNKNOWN_USER.get(
+                                          String.valueOf(bindDN)));
+      }
+      else
+      {
+        setUserEntryDN(userEntry.getDN());
+      }
+
+
+      // Check to see if the user has a password.  If not, then fail.
+      // FIXME -- We need to have a way to enable/disable debugging.
+      pwPolicyState = new PasswordPolicyState(userEntry, false, false);
+      policy = pwPolicyState.getPolicy();
+      AttributeType  pwType = policy.getPasswordAttribute();
+
+      List<Attribute> pwAttr = userEntry.getAttribute(pwType);
+      if ((pwAttr == null) || (pwAttr.isEmpty()))
+      {
+        throw new DirectoryException(ResultCode.INVALID_CREDENTIALS,
+                                     ERR_BIND_OPERATION_NO_PASSWORD.get(
+                                          String.valueOf(bindDN)));
+      }
+
+
+      // Perform a number of password policy state checks for the user.
+      checkPasswordPolicyState(userEntry, null);
+
+
+      // Invoke the pre-operation bind plugins.
+      PreOperationPluginResult preOpResult =
+           pluginConfigManager.invokePreOperationBindPlugins(this);
+      if (preOpResult.connectionTerminated())
+      {
+        // There's no point in continuing with anything.  Log the result
+        // and return.
+        setResultCode(ResultCode.CANCELED);
+        appendErrorMessage(ERR_CANCELED_BY_PREOP_DISCONNECT.get());
+        setProcessingStopTime();
+        return false;
+      }
+      else if (preOpResult.sendResponseImmediately()  ||
+               preOpResult.skipCoreProcessing())
+      {
+        skipPostOperation = false;
+        return true;
+      }
+
+
+      // Determine whether the provided password matches any of the stored
+      // passwords for the user.
+      if (pwPolicyState.passwordMatches(simplePassword))
+      {
+        setResultCode(ResultCode.SUCCESS);
+
+        boolean isRoot = DirectoryServer.isRootDN(userEntry.getDN());
+        if (DirectoryServer.lockdownMode() && (! isRoot))
+        {
+          throw new DirectoryException(ResultCode.INVALID_CREDENTIALS,
+                                       ERR_BIND_REJECTED_LOCKDOWN_MODE.get());
+        }
+        setAuthenticationInfo(new AuthenticationInfo(userEntry,
+                                                     simplePassword,
+                                                     isRoot));
+
+
+        // Set resource limits for the authenticated user.
+        setResourceLimits(userEntry);
+
+
+        // Perform any remaining processing for a successful simple
+        // authentication.
+        pwPolicyState.handleDeprecatedStorageSchemes(simplePassword);
+        pwPolicyState.clearFailureLockout();
+
+        if (isFirstWarning)
+        {
+          pwPolicyState.setWarnedTime();
+
+          int numSeconds = pwPolicyState.getSecondsUntilExpiration();
+          Message m = WARN_BIND_PASSWORD_EXPIRING.get(
+                           secondsToTimeString(numSeconds));
+
+          pwPolicyState.generateAccountStatusNotification(
+               AccountStatusNotificationType.PASSWORD_EXPIRING, userEntry, m,
+               AccountStatusNotification.createProperties(pwPolicyState,
+                     false, numSeconds, null, null));
+        }
+
+        if (isGraceLogin)
+        {
+          pwPolicyState.updateGraceLoginTimes();
+        }
+
+        pwPolicyState.setLastLoginTime();
+      }
+      else
+      {
+        setResultCode(ResultCode.INVALID_CREDENTIALS);
+        setAuthFailureReason(ERR_BIND_OPERATION_WRONG_PASSWORD.get());
+
+        if (policy.getLockoutFailureCount() > 0)
+        {
+          pwPolicyState.updateAuthFailureTimes();
+          if (pwPolicyState.lockedDueToFailures())
+          {
+            AccountStatusNotificationType notificationType;
+            Message m;
+
+            boolean tempLocked;
+            int lockoutDuration = pwPolicyState.getSecondsUntilUnlock();
+            if (lockoutDuration > -1)
+            {
+              notificationType = AccountStatusNotificationType.
+                                      ACCOUNT_TEMPORARILY_LOCKED;
+              tempLocked = true;
+
+              m = ERR_BIND_ACCOUNT_TEMPORARILY_LOCKED.get(
+                       secondsToTimeString(lockoutDuration));
+            }
+            else
+            {
+              notificationType = AccountStatusNotificationType.
+                                      ACCOUNT_PERMANENTLY_LOCKED;
+              tempLocked = false;
+
+              m = ERR_BIND_ACCOUNT_PERMANENTLY_LOCKED.get();
+            }
+
+            pwPolicyState.generateAccountStatusNotification(
+                 notificationType, userEntry, m,
+                 AccountStatusNotification.createProperties(pwPolicyState,
+                       tempLocked, -1, null, null));
+          }
+        }
+      }
+
+      return true;
+    }
+    finally
+    {
+      // No matter what, make sure to unlock the user's entry.
+      LockManager.unlock(bindDN, userLock);
+    }
+  }
+
+
+
+  /**
+   * Performs the processing necessary for an anonymous simple bind.
+   *
+   * @throws  DirectoryException  If a problem occurs that should cause the bind
+   *                              operation to fail.
+   */
+  private boolean processAnonymousSimpleBind()
+          throws DirectoryException
+  {
+    // If the server is in lockdown mode, then fail.
+    if (DirectoryServer.lockdownMode())
+    {
+      throw new DirectoryException(ResultCode.INVALID_CREDENTIALS,
+                                   ERR_BIND_REJECTED_LOCKDOWN_MODE.get());
+    }
+
+    // If there is a bind DN, then see whether that is acceptable.
+    if (DirectoryServer.bindWithDNRequiresPassword() &&
+        ((bindDN != null) && (! bindDN.isNullDN())))
+    {
+      throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
+                                   ERR_BIND_DN_BUT_NO_PASSWORD.get());
+    }
+
+
+    // Invoke the pre-operation bind plugins.
+    PreOperationPluginResult preOpResult =
+         pluginConfigManager.invokePreOperationBindPlugins(this);
+    if (preOpResult.connectionTerminated())
+    {
+      // There's no point in continuing with anything.  Log the result
+      // and return.
+      setResultCode(ResultCode.CANCELED);
+      appendErrorMessage(ERR_CANCELED_BY_PREOP_DISCONNECT.get());
+      setProcessingStopTime();
+      return false;
+    }
+    else if (preOpResult.sendResponseImmediately() ||
+             preOpResult.skipCoreProcessing())
+    {
+      skipPostOperation = true;
+      return true;
+    }
+
+    setResultCode(ResultCode.SUCCESS);
+    setAuthenticationInfo(new AuthenticationInfo());
+    return true;
+  }
+
+
+
+  /**
+   * Performs the processing necessary for a SASL bind operation.
+   *
+   * @return  {@code true} if processing should continue for the operation, or
+   *          {@code false} if not.
+   *
+   * @throws  DirectoryException  If a problem occurs that should cause the bind
+   *                              operation to fail.
+   */
+  private boolean processSASLBind()
+          throws DirectoryException
+  {
+    // Get the appropriate authentication handler for this request based
+    // on the SASL mechanism.  If there is none, then fail.
+    SASLMechanismHandler saslHandler =
+         DirectoryServer.getSASLMechanismHandler(saslMechanism);
+    if (saslHandler == null)
+    {
+      throw new DirectoryException(ResultCode.AUTH_METHOD_NOT_SUPPORTED,
+                     ERR_BIND_OPERATION_UNKNOWN_SASL_MECHANISM.get(
+                          saslMechanism));
+    }
+
+
+    // Check to see if the client has sufficient permission to perform the bind.
+    // NYI
+
+
+    // Invoke the pre-operation bind plugins.
+    PreOperationPluginResult preOpResult =
+         pluginConfigManager.invokePreOperationBindPlugins(this);
+    if (preOpResult.connectionTerminated())
+    {
+      // There's no point in continuing with anything.  Log the result
+      // and return.
+      setResultCode(ResultCode.CANCELED);
+      appendErrorMessage(ERR_CANCELED_BY_PREOP_DISCONNECT.get());
+      setProcessingStopTime();
+      return false;
+    }
+    else if (preOpResult.sendResponseImmediately() ||
+             preOpResult.skipCoreProcessing())
+    {
+      skipPostOperation = false;
+      return true;
+    }
+
+    // Actually process the SASL bind.
+    saslHandler.processSASLBind(this);
+
+
+    // If the server is operating in lockdown mode, then we will need to
+    // ensure that the authentication was successful and performed as a
+    // root user to continue.
+    Entry saslAuthUserEntry = getSASLAuthUserEntry();
+    if (DirectoryServer.lockdownMode())
+    {
+      ResultCode resultCode = getResultCode();
+      if (resultCode != ResultCode.SASL_BIND_IN_PROGRESS)
+      {
+        if ((resultCode != ResultCode.SUCCESS) ||
+            (saslAuthUserEntry == null) ||
+            (! DirectoryServer.isRootDN(saslAuthUserEntry.getDN())))
+        {
+          throw new DirectoryException(ResultCode.INVALID_CREDENTIALS,
+                                       ERR_BIND_REJECTED_LOCKDOWN_MODE.get());
+        }
+      }
+    }
+
+    // Create the password policy state object.
+    if (saslAuthUserEntry == null)
+    {
+      pwPolicyState = null;
+    }
+    else
+    {
+      // FIXME -- Need to have a way to enable debugging.
+      pwPolicyState = new PasswordPolicyState(saslAuthUserEntry, false,
+                                              false);
+      policy = pwPolicyState.getPolicy();
+      setUserEntryDN(saslAuthUserEntry.getDN());
+
+
+      // Perform password policy checks that will need to be completed
+      // regardless of whether the authentication was successful.
+      checkPasswordPolicyState(saslAuthUserEntry, saslHandler);
+    }
+
+
+    // Determine whether the authentication was successful and perform
+    // any remaining password policy processing accordingly.
+    ResultCode resultCode = getResultCode();
+    if (resultCode == ResultCode.SUCCESS)
+    {
+      if (pwPolicyState != null)
+      {
+        if (saslHandler.isPasswordBased(saslMechanism) &&
+            pwPolicyState.mustChangePassword())
+        {
+          mustChangePassword = true;
+        }
+
+        if (isFirstWarning)
+        {
+          pwPolicyState.setWarnedTime();
+
+          int numSeconds = pwPolicyState.getSecondsUntilExpiration();
+          Message m = WARN_BIND_PASSWORD_EXPIRING.get(
+                                 secondsToTimeString(numSeconds));
+
+          pwPolicyState.generateAccountStatusNotification(
+               AccountStatusNotificationType.PASSWORD_EXPIRING,
+               saslAuthUserEntry, m,
+               AccountStatusNotification.createProperties(pwPolicyState,
+                     false, numSeconds, null, null));
+        }
+
+        if (isGraceLogin)
+        {
+          pwPolicyState.updateGraceLoginTimes();
+        }
+
+        pwPolicyState.setLastLoginTime();
+
+
+        // Set appropriate resource limits for the user.
+        setResourceLimits(saslAuthUserEntry);
+      }
+    }
+    else if (resultCode == ResultCode.SASL_BIND_IN_PROGRESS)
+    {
+      // FIXME -- Is any special processing needed here?
+      return false;
+    }
+    else
+    {
+      if (pwPolicyState != null)
+      {
+        if (saslHandler.isPasswordBased(saslMechanism))
+        {
+
+          if (pwPolicyState.getPolicy().getLockoutFailureCount() > 0)
+          {
+            pwPolicyState.updateAuthFailureTimes();
+            if (pwPolicyState.lockedDueToFailures())
+            {
+              AccountStatusNotificationType notificationType;
+              boolean tempLocked;
+              Message m;
+
+              int lockoutDuration = pwPolicyState.getSecondsUntilUnlock();
+              if (lockoutDuration > -1)
+              {
+                notificationType = AccountStatusNotificationType.
+                                        ACCOUNT_TEMPORARILY_LOCKED;
+                tempLocked = true;
+                m = ERR_BIND_ACCOUNT_TEMPORARILY_LOCKED.get(
+                         secondsToTimeString(lockoutDuration));
+              }
+              else
+              {
+                notificationType =
+                     AccountStatusNotificationType.ACCOUNT_PERMANENTLY_LOCKED;
+                tempLocked = false;
+                m = ERR_BIND_ACCOUNT_PERMANENTLY_LOCKED.get();
+              }
+
+              pwPolicyState.generateAccountStatusNotification(
+                   notificationType, saslAuthUserEntry, m,
+                   AccountStatusNotification.createProperties(
+                        pwPolicyState, tempLocked, -1, null, null));
+            }
+          }
+        }
+      }
+    }
+
+    return true;
+  }
+
+
+
+  /**
+   * Validates a number of password policy state constraints for the user.
+   *
+   * @param  userEntry    The entry for the user that is authenticating.
+   * @param  saslHandler  The SASL mechanism handler if this is a SASL bind, or
+   *                      {@code null} for a simple bind.
+   *
+   * @throws  DirectoryException  If a problem occurs that should cause the bind
+   *                              to fail.
+   */
+  private void checkPasswordPolicyState(Entry userEntry,
+                                        SASLMechanismHandler saslHandler)
+          throws DirectoryException
+  {
+    boolean isSASLBind = (saslHandler != null);
+
+    // If the password policy is configured to track authentication failures or
+    // keep the last login time and the associated backend is disabled, then we
+    // may need to reject the bind immediately.
+    if ((policy.getStateUpdateFailurePolicy() ==
+         PasswordPolicyCfgDefn.StateUpdateFailurePolicy.PROACTIVE) &&
+        ((policy.getLockoutFailureCount() > 0) ||
+         ((policy.getLastLoginTimeAttribute() != null) &&
+          (policy.getLastLoginTimeFormat() != null))) &&
+        ((DirectoryServer.getWritabilityMode() == WritabilityMode.DISABLED) ||
+         (backend.getWritabilityMode() == WritabilityMode.DISABLED)))
+    {
+      // This policy isn't applicable to root users, so if it's a root
+      // user then ignore it.
+      if (! DirectoryServer.isRootDN(userEntry.getDN()))
+      {
+        throw new DirectoryException(ResultCode.INVALID_CREDENTIALS,
+                       ERR_BIND_OPERATION_WRITABILITY_DISABLED.get(
+                            String.valueOf(userEntry.getDN())));
+      }
+    }
+
+
+    // Check to see if the authentication must be done in a secure
+    // manner.  If so, then the client connection must be secure.
+    if (policy.requireSecureAuthentication() && (! clientConnection.isSecure()))
+    {
+      if (isSASLBind)
+      {
+        if (! saslHandler.isSecure(saslMechanism))
+        {
+          throw new DirectoryException(ResultCode.INVALID_CREDENTIALS,
+                         ERR_BIND_OPERATION_INSECURE_SASL_BIND.get(
+                              saslMechanism,
+                              String.valueOf(userEntry.getDN())));
+        }
+      }
+      else
+      {
+        throw new DirectoryException(ResultCode.INVALID_CREDENTIALS,
+                       ERR_BIND_OPERATION_INSECURE_SIMPLE_BIND.get(
+                            String.valueOf(userEntry.getDN())));
+      }
+    }
+
+
+    // Check to see if the user is administratively disabled or locked.
+    if (pwPolicyState.isDisabled())
+    {
+      throw new DirectoryException(ResultCode.INVALID_CREDENTIALS,
+                                   ERR_BIND_OPERATION_ACCOUNT_DISABLED.get(
+                                        String.valueOf(userEntry.getDN())));
+    }
+    else if (pwPolicyState.isAccountExpired())
+    {
+      Message m = ERR_BIND_OPERATION_ACCOUNT_EXPIRED.get(
+                       String.valueOf(userEntry.getDN()));
+      pwPolicyState.generateAccountStatusNotification(
+           AccountStatusNotificationType.ACCOUNT_EXPIRED, userEntry, m,
+           AccountStatusNotification.createProperties(pwPolicyState,
+                 false, -1, null, null));
+
+      throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, m);
+    }
+    else if (pwPolicyState.lockedDueToFailures())
+    {
+      if (pwPolicyErrorType == null)
+      {
+        pwPolicyErrorType = PasswordPolicyErrorType.ACCOUNT_LOCKED;
+      }
+
+      throw new DirectoryException(ResultCode.INVALID_CREDENTIALS,
+                     ERR_BIND_OPERATION_ACCOUNT_FAILURE_LOCKED.get(
+                          String.valueOf(userEntry.getDN())));
+    }
+    else if (pwPolicyState.lockedDueToIdleInterval())
+    {
+      Message m = ERR_BIND_OPERATION_ACCOUNT_IDLE_LOCKED.get(
+              String.valueOf(userEntry.getDN()));
+
+      if (pwPolicyErrorType == null)
+      {
+        pwPolicyErrorType = PasswordPolicyErrorType.ACCOUNT_LOCKED;
+      }
+
+      pwPolicyState.generateAccountStatusNotification(
+           AccountStatusNotificationType.ACCOUNT_IDLE_LOCKED, userEntry, m,
+           AccountStatusNotification.createProperties(pwPolicyState, false, -1,
+                                                      null, null));
+
+      throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, m);
+    }
+
+
+    // If it's a simple bind, or if it's a password-based SASL bind, then
+    // perform a number of password-based checks.
+    if ((! isSASLBind) || saslHandler.isPasswordBased(saslMechanism))
+    {
+      // Check to see if the account is locked due to the maximum reset age.
+      if (pwPolicyState.lockedDueToMaximumResetAge())
+      {
+        Message m = ERR_BIND_OPERATION_ACCOUNT_RESET_LOCKED.get(
+                         String.valueOf(userEntry.getDN()));
+
+        if (pwPolicyErrorType == null)
+        {
+          pwPolicyErrorType = PasswordPolicyErrorType.ACCOUNT_LOCKED;
+        }
+
+        pwPolicyState.generateAccountStatusNotification(
+             AccountStatusNotificationType.ACCOUNT_RESET_LOCKED, userEntry, m,
+             AccountStatusNotification.createProperties(pwPolicyState, false,
+                                                        -1, null, null));
+
+        throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, m);
+      }
+
+
+      // Determine whether the password is expired, or whether the user
+      // should be warned about an upcoming expiration.
+      if (pwPolicyState.isPasswordExpired())
+      {
+        if (pwPolicyErrorType == null)
+        {
+          pwPolicyErrorType = PasswordPolicyErrorType.PASSWORD_EXPIRED;
+        }
+
+        int maxGraceLogins = policy.getGraceLoginCount();
+        if ((maxGraceLogins > 0) && pwPolicyState.mayUseGraceLogin())
+        {
+          List<Long> graceLoginTimes = pwPolicyState.getGraceLoginTimes();
+          if ((graceLoginTimes == null) ||
+              (graceLoginTimes.size() < maxGraceLogins))
+          {
+            isGraceLogin       = true;
+            mustChangePassword = true;
+
+            if (pwPolicyWarningType == null)
+            {
+              pwPolicyWarningType =
+                   PasswordPolicyWarningType.GRACE_LOGINS_REMAINING;
+              pwPolicyWarningValue = maxGraceLogins -
+                                     (graceLoginTimes.size() + 1);
+            }
+          }
+          else
+          {
+            Message m = ERR_BIND_OPERATION_PASSWORD_EXPIRED.get(
+                             String.valueOf(userEntry.getDN()));
+
+            pwPolicyState.generateAccountStatusNotification(
+                 AccountStatusNotificationType.PASSWORD_EXPIRED, userEntry, m,
+                 AccountStatusNotification.createProperties(pwPolicyState,
+                                                            false, -1, null,
+                                                            null));
+
+            throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, m);
+          }
+        }
+        else
+        {
+          Message m = ERR_BIND_OPERATION_PASSWORD_EXPIRED.get(
+                           String.valueOf(userEntry.getDN()));
+
+          pwPolicyState.generateAccountStatusNotification(
+               AccountStatusNotificationType.PASSWORD_EXPIRED, userEntry, m,
+               AccountStatusNotification.createProperties(pwPolicyState, false,
+                                                          -1, null, null));
+
+          throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, m);
+        }
+      }
+      else if (pwPolicyState.shouldWarn())
+      {
+        int numSeconds = pwPolicyState.getSecondsUntilExpiration();
+
+        if (pwPolicyWarningType == null)
+        {
+          pwPolicyWarningType =
+               PasswordPolicyWarningType.TIME_BEFORE_EXPIRATION;
+          pwPolicyWarningValue = numSeconds;
+        }
+
+        isFirstWarning = pwPolicyState.isFirstWarning();
+      }
+
+
+      // Check to see if the user's password has been reset.
+      if (pwPolicyState.mustChangePassword())
+      {
+        mustChangePassword = true;
+
+        if (pwPolicyErrorType == null)
+        {
+          pwPolicyErrorType = PasswordPolicyErrorType.CHANGE_AFTER_RESET;
+        }
+      }
+    }
+  }
+
+
+
+  /**
+   * Sets resource limits for the authenticated user.
+   *
+   * @param  userEntry  The entry for the authenticated user.
+   */
+  private void setResourceLimits(Entry userEntry)
+  {
+    // See if the user's entry contains a custom size limit.
+    AttributeType attrType =
+         DirectoryServer.getAttributeType(OP_ATTR_USER_SIZE_LIMIT, true);
+    List<Attribute> 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())
+        {
+          logError(WARN_BIND_MULTIPLE_USER_SIZE_LIMITS.get(
+                        String.valueOf(userEntry.getDN())));
+        }
+        else
+        {
+          try
+          {
+            sizeLimit = Integer.parseInt(v.getStringValue());
+          }
+          catch (Exception e)
+          {
+            if (debugEnabled())
+            {
+              TRACER.debugCaught(DebugLogLevel.ERROR, e);
+            }
+
+            logError(WARN_BIND_CANNOT_PROCESS_USER_SIZE_LIMIT.get(
+                          v.getStringValue(),
+                          String.valueOf(userEntry.getDN())));
+          }
+        }
+      }
+    }
+
+
+    // See if the user's entry contains a custom time limit.
+    attrType = DirectoryServer.getAttributeType(OP_ATTR_USER_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())
+        {
+          logError(WARN_BIND_MULTIPLE_USER_TIME_LIMITS.get(
+                        String.valueOf(userEntry.getDN())));
+        }
+        else
+        {
+          try
+          {
+            timeLimit = Integer.parseInt(v.getStringValue());
+          }
+          catch (Exception e)
+          {
+            if (debugEnabled())
+            {
+              TRACER.debugCaught(DebugLogLevel.ERROR, e);
+            }
+
+            logError(WARN_BIND_CANNOT_PROCESS_USER_TIME_LIMIT.get(
+                          v.getStringValue(),
+                          String.valueOf(userEntry.getDN())));
+          }
+        }
+      }
+    }
+
+
+    // 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())
+        {
+          logError(WARN_BIND_MULTIPLE_USER_IDLE_TIME_LIMITS.get(
+                        String.valueOf(userEntry.getDN())));
+        }
+        else
+        {
+          try
+          {
+            idleTimeLimit = 1000L * Long.parseLong(v.getStringValue());
+          }
+          catch (Exception e)
+          {
+            if (debugEnabled())
+            {
+              TRACER.debugCaught(DebugLogLevel.ERROR, e);
+            }
+
+            logError(WARN_BIND_CANNOT_PROCESS_USER_IDLE_TIME_LIMIT.get(
+                          v.getStringValue(),
+                          String.valueOf(userEntry.getDN())));
+          }
+        }
+      }
+    }
+
+
+    // See if the user's entry contains a custom lookthrough limit.
+    attrType = DirectoryServer.getAttributeType(OP_ATTR_USER_LOOKTHROUGH_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())
+        {
+          logError(WARN_BIND_MULTIPLE_USER_LOOKTHROUGH_LIMITS.get(
+                        String.valueOf(userEntry.getDN())));
+        }
+        else
+        {
+          try
+          {
+            lookthroughLimit = Integer.parseInt(v.getStringValue());
+          }
+          catch (Exception e)
+          {
+            if (debugEnabled())
+            {
+              TRACER.debugCaught(DebugLogLevel.ERROR, e);
+            }
+
+            logError(WARN_BIND_CANNOT_PROCESS_USER_LOOKTHROUGH_LIMIT.get(
+                          v.getStringValue(),
+                          String.valueOf(userEntry.getDN())));
+          }
+        }
+      }
+    }
+  }
 }
+

--
Gitblit v1.10.0