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

neil_a_wilson
25.15.2007 b1e3b0ccdaa423b68ef6fa2fee67d3e09990985f
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.
9 files modified
17058 ■■■■ changed files
opends/src/server/org/opends/server/workflowelement/WorkflowElement.java 31 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendAddOperation.java 1529 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendBindOperation.java 1258 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendCompareOperation.java 560 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendDeleteOperation.java 803 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendModifyDNOperation.java 1347 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendModifyOperation.java 2317 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendSearchOperation.java 628 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendWorkflowElement.java 8585 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/workflowelement/WorkflowElement.java
@@ -40,17 +40,14 @@
 */
public abstract class WorkflowElement
{
  // Indicates whether the workflow element encapsulates a private local
  // backend.
  private boolean isPrivate = false;
  // The workflow element identifier.
  private String workflowElementID = null;
  /**
   * Indicates whether the workflow element encapsulates a private
   * local backend.
   */
  protected boolean isPrivate = false;
  /**
   * Creates a new instance of the workflow element.
@@ -64,14 +61,14 @@
  }
  /**
   * Executes the workflow element for an operation.
   *
   * @param operation the operation to execute
   */
  public abstract void execute(
      Operation operation
      );
  public abstract void execute(Operation operation);
  /**
@@ -87,6 +84,21 @@
  }
  /**
   * Specifies whether the workflow element encapsulates a private local
   * backend.
   *
   * @param  isPrivate  Indicates whether the workflow element encapsulates a
   *                    private local backend.
   */
  protected void setPrivate(boolean isPrivate)
  {
    this.isPrivate = isPrivate;
  }
  /**
   * Provides the workflow element identifier.
   *
@@ -97,3 +109,4 @@
    return workflowElementID;
  }
}
opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendAddOperation.java
@@ -25,28 +25,43 @@
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.workflowelement.localbackend;
import org.opends.messages.Message;
import org.opends.messages.MessageBuilder;
import static org.opends.server.config.ConfigConstants.*;
import static org.opends.messages.CoreMessages.*;
import static org.opends.server.util.ServerConstants.*;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.locks.Lock;
import org.opends.messages.Message;
import org.opends.messages.MessageBuilder;
import org.opends.server.api.AttributeSyntax;
import org.opends.server.api.Backend;
import org.opends.server.api.ChangeNotificationListener;
import org.opends.server.api.ClientConnection;
import org.opends.server.api.PasswordStorageScheme;
import org.opends.server.api.PasswordValidator;
import org.opends.server.api.SynchronizationProvider;
import org.opends.server.api.plugin.PostOperationPluginResult;
import org.opends.server.api.plugin.PreOperationPluginResult;
import org.opends.server.controls.LDAPAssertionRequestControl;
import org.opends.server.controls.LDAPPostReadRequestControl;
import org.opends.server.controls.LDAPPostReadResponseControl;
import org.opends.server.controls.PasswordPolicyErrorType;
import org.opends.server.controls.PasswordPolicyResponseControl;
import org.opends.server.controls.ProxiedAuthV1Control;
import org.opends.server.controls.ProxiedAuthV2Control;
import org.opends.server.core.AccessControlConfigManager;
import org.opends.server.core.AddOperation;
import org.opends.server.core.AddOperationWrapper;
import org.opends.server.core.DirectoryServer;
import org.opends.server.core.PasswordPolicy;
import org.opends.server.core.PluginConfigManager;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.protocols.asn1.ASN1OctetString;
import org.opends.server.schema.AuthPasswordSyntax;
import org.opends.server.schema.BooleanSyntax;
@@ -55,31 +70,82 @@
import org.opends.server.types.AttributeType;
import org.opends.server.types.AttributeValue;
import org.opends.server.types.ByteString;
import org.opends.server.types.CancelledOperationException;
import org.opends.server.types.CancelResult;
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.LDAPException;
import org.opends.server.types.LockManager;
import org.opends.server.types.ObjectClass;
import org.opends.server.types.Privilege;
import org.opends.server.types.RDN;
import org.opends.server.types.ResultCode;
import org.opends.server.types.SearchFilter;
import org.opends.server.types.SearchResultEntry;
import org.opends.server.types.SynchronizationProviderResult;
import org.opends.server.types.operation.PostOperationAddOperation;
import org.opends.server.types.operation.PostResponseAddOperation;
import org.opends.server.types.operation.PreOperationAddOperation;
import org.opends.server.types.operation.PostSynchronizationAddOperation;
import org.opends.server.util.TimeThread;
import static org.opends.messages.CoreMessages.*;
import static org.opends.server.loggers.ErrorLogger.*;
import static org.opends.server.loggers.debug.DebugLogger.*;
import static org.opends.server.config.ConfigConstants.*;
import static org.opends.server.util.ServerConstants.*;
import static org.opends.server.util.StaticUtils.*;
/**
 * This class defines an operation used to add an entry in a local backend
 * of the Directory Server.
 */
public class LocalBackendAddOperation extends AddOperationWrapper
  implements PreOperationAddOperation,
             PostOperationAddOperation,
             PostResponseAddOperation,
             PostSynchronizationAddOperation
public class LocalBackendAddOperation
       extends AddOperationWrapper
       implements PreOperationAddOperation, PostOperationAddOperation,
                  PostResponseAddOperation, PostSynchronizationAddOperation
{
  /**
   * The tracer object for the debug logger.
   */
  private static final DebugTracer TRACER = getTracer();
  // The backend in which the entry is to be added.
  private Backend backend;
  // Indicates whether the request includes the LDAP no-op control.
  private boolean noOp;
  // Indicates whether to skip post-operation plugin processing.
  private boolean skipPostOperation;
  // The DN of the entry to be added.
  private DN entryDN;
  // The entry being added to the server.
  private Entry entry;
  // The post-read request control included in the request, if applicable.
  LDAPPostReadRequestControl postReadRequest;
  // The set of object classes for the entry to add.
  private Map<ObjectClass, String> objectClasses;
  // The set of operational attributes for the entry to add.
  private Map<AttributeType,List<Attribute>> operationalAttributes;
  // The set of user attributes for the entry to add.
  private Map<AttributeType,List<Attribute>> userAttributes;
  /**
   * Creates a new operation that may be used to add a new entry in a
   * local backend of the Directory Server.
@@ -89,10 +155,12 @@
  public LocalBackendAddOperation(AddOperation add)
  {
    super(add);
    LocalBackendWorkflowElement.attachLocalOperation (add, this);
  }
  /**
   * Retrieves the entry to be added to the server.  Note that this will not be
   * available to pre-parse plugins or during the conflict resolution portion of
@@ -106,34 +174,983 @@
    return entry;
  }
  /**
   * Sets the entry to be added to the server.
   * Process this add operation against a local backend.
   *
   * @param  entry - The entry to be added to the server, or <CODE>null</CODE>
   *                 if it is not yet available.
   * @param  backend  The backend in which the add operation should be
   *                  processed.
   */
  public final void setEntryToAdd(Entry entry){
    this.entry = entry;
  void processLocalAdd(Backend backend)
  {
    this.backend = backend;
    ClientConnection clientConnection = getClientConnection();
    // Get the plugin config manager that will be used for invoking plugins.
    PluginConfigManager pluginConfigManager =
         DirectoryServer.getPluginConfigManager();
    skipPostOperation = false;
    // Check for a request to cancel this operation.
    if (getCancelRequest() != null)
    {
      return;
  }
    // Create a labeled block of code that we can break out of if a problem is
    // detected.
addProcessing:
    {
      // Process the entry DN and set of attributes to convert them from their
      // raw forms as provided by the client to the forms required for the rest
      // of the add processing.
      entryDN = getEntryDN();
      if (entryDN == null)
      {
        break addProcessing;
      }
      objectClasses = getObjectClasses();
      userAttributes = getUserAttributes();
      operationalAttributes = getOperationalAttributes();
      if ((objectClasses == null ) || (userAttributes == null) ||
          (operationalAttributes == null))
      {
        break addProcessing;
      }
      // Check for a request to cancel this operation.
      if (getCancelRequest() != null)
      {
        return;
      }
      // Grab a read lock on the parent entry, if there is one.  We need to do
      // this to ensure that the parent is not deleted or renamed while this add
      // is in progress, and we could also need it to check the entry against
      // a DIT structure rule.
      Lock parentLock = null;
      Lock entryLock  = null;
      DN parentDN = entryDN.getParentDNInSuffix();
      try
      {
        parentLock = lockParent(parentDN);
      }
      catch (DirectoryException de)
      {
        if (debugEnabled())
        {
          TRACER.debugCaught(DebugLogLevel.ERROR, de);
        }
        setResponseData(de);
        break addProcessing;
      }
      try
      {
        // Check for a request to cancel this operation.
        if (getCancelRequest() != null)
        {
          return;
        }
        // Grab a write lock on the target entry.  We'll need to do this
        // eventually anyway, and we want to make sure that the two locks are
        // always released when exiting this method, no matter what.  Since
        // the entry shouldn't exist yet, locking earlier than necessary
        // shouldn't cause a problem.
        for (int i=0; i < 3; i++)
        {
          entryLock = LockManager.lockWrite(entryDN);
          if (entryLock != null)
          {
            break;
          }
        }
        if (entryLock == null)
        {
          setResultCode(DirectoryServer.getServerErrorResultCode());
          appendErrorMessage(ERR_ADD_CANNOT_LOCK_ENTRY.get(
                                  String.valueOf(entryDN)));
          skipPostOperation = true;
          break addProcessing;
        }
        // Invoke any conflict resolution processing that might be needed by the
        // synchronization provider.
        for (SynchronizationProvider provider :
             DirectoryServer.getSynchronizationProviders())
        {
          try
          {
            SynchronizationProviderResult result =
                 provider.handleConflictResolution(this);
            if (! result.continueOperationProcessing())
            {
              break addProcessing;
            }
          }
          catch (DirectoryException de)
          {
            if (debugEnabled())
            {
              TRACER.debugCaught(DebugLogLevel.ERROR, de);
            }
            logError(ERR_ADD_SYNCH_CONFLICT_RESOLUTION_FAILED.get(
                          getConnectionID(), getOperationID(),
                          getExceptionMessage(de)));
            setResponseData(de);
            break addProcessing;
          }
        }
        for (AttributeType at : userAttributes.keySet())
        {
          // If the attribute type is marked "NO-USER-MODIFICATION" then fail
          // unless this is an internal operation or is related to
          // synchronization in some way.
          // This must be done before running the password policy code
          // and any other code that may add attributes marked as
          // "NO-USER-MODIFICATION"
          //
          // Note that doing this checks at this time
          // of the processing does not make it possible for pre-parse plugins
          // to add NO-USER-MODIFICATION attributes to the entry.
          if (at.isNoUserModification())
          {
            if (! (isInternalOperation() || isSynchronizationOperation()))
            {
              setResultCode(ResultCode.UNWILLING_TO_PERFORM);
              appendErrorMessage(ERR_ADD_ATTR_IS_NO_USER_MOD.get(
                                      String.valueOf(entryDN),
                                      at.getNameOrOID()));
              break addProcessing;
            }
          }
        }
        for (AttributeType at : operationalAttributes.keySet())
        {
          if (at.isNoUserModification())
          {
            if (! (isInternalOperation() || isSynchronizationOperation()))
            {
              setResultCode(ResultCode.UNWILLING_TO_PERFORM);
              appendErrorMessage(ERR_ADD_ATTR_IS_NO_USER_MOD.get(
                                      String.valueOf(entryDN),
                                      at.getNameOrOID()));
              break addProcessing;
            }
          }
        }
        // Check to see if the entry already exists.  We do this before
        // checking whether the parent exists to ensure a referral entry
        // above the parent results in a correct referral.
        try
        {
          if (DirectoryServer.entryExists(entryDN))
          {
            setResultCode(ResultCode.ENTRY_ALREADY_EXISTS);
            appendErrorMessage(ERR_ADD_ENTRY_ALREADY_EXISTS.get(
                                    String.valueOf(entryDN)));
            break addProcessing;
          }
        }
        catch (DirectoryException de)
        {
          if (debugEnabled())
          {
            TRACER.debugCaught(DebugLogLevel.ERROR, de);
          }
          setResponseData(de);
          break addProcessing;
        }
        // Get the parent entry, if it exists.
        Entry parentEntry = null;
        if (parentDN != null)
        {
          try
          {
            parentEntry = DirectoryServer.getEntry(parentDN);
            if (parentEntry == null)
            {
              DN matchedDN = parentDN.getParentDNInSuffix();
              while (matchedDN != null)
              {
                try
                {
                  if (DirectoryServer.entryExists(matchedDN))
                  {
                    setMatchedDN(matchedDN);
                    break;
                  }
                }
                catch (Exception e)
                {
                  if (debugEnabled())
                  {
                    TRACER.debugCaught(DebugLogLevel.ERROR, e);
                  }
                  break;
                }
                matchedDN = matchedDN.getParentDNInSuffix();
              }
              // The parent doesn't exist, so this add can't be successful.
              setResultCode(ResultCode.NO_SUCH_OBJECT);
              appendErrorMessage(ERR_ADD_NO_PARENT.get(String.valueOf(entryDN),
                                      String.valueOf(parentDN)));
              break addProcessing;
            }
          }
          catch (DirectoryException de)
          {
            if (debugEnabled())
            {
              TRACER.debugCaught(DebugLogLevel.ERROR, de);
            }
            setResponseData(de);
            break addProcessing;
          }
        }
        // Check to make sure that all of the RDN attributes are included as
        // attribute values.  If not, then either add them or report an error.
        try
        {
          addRDNAttributesIfNecessary();
        }
        catch (DirectoryException de)
        {
          if (debugEnabled())
          {
            TRACER.debugCaught(DebugLogLevel.ERROR, de);
          }
          setResponseData(de);
          break addProcessing;
        }
        // Check to make sure that all objectclasses have their superior classes
        // listed in the entry.  If not, then add them.
        HashSet<ObjectClass> additionalClasses = null;
        for (ObjectClass oc : objectClasses.keySet())
        {
          ObjectClass superiorClass = oc.getSuperiorClass();
          if ((superiorClass != null) &&
              (! objectClasses.containsKey(superiorClass)))
          {
            if (additionalClasses == null)
            {
              additionalClasses = new HashSet<ObjectClass>();
            }
            additionalClasses.add(superiorClass);
          }
        }
        if (additionalClasses != null)
        {
          for (ObjectClass oc : additionalClasses)
          {
            addObjectClassChain(oc);
          }
        }
        // Create an entry object to encapsulate the set of attributes and
        // objectclasses.
        entry = new Entry(entryDN, objectClasses, userAttributes,
                          operationalAttributes);
        // Check to see if the entry includes a privilege specification.  If so,
        // then the requester must have the PRIVILEGE_CHANGE privilege.
        AttributeType privType =
             DirectoryServer.getAttributeType(OP_ATTR_PRIVILEGE_NAME, true);
        if (entry.hasAttribute(privType) &&
            (! clientConnection.hasPrivilege(Privilege.PRIVILEGE_CHANGE, this)))
        {
          appendErrorMessage(
               ERR_ADD_CHANGE_PRIVILEGE_INSUFFICIENT_PRIVILEGES.get());
          setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
          break addProcessing;
        }
        // If it's not a synchronization operation, then check
        // to see if the entry contains one or more passwords and if they
        // are valid in accordance with the password policies associated with
        // the user.  Also perform any encoding that might be required by
        // password storage schemes.
        if (! isSynchronizationOperation())
        {
          try
          {
            handlePasswordPolicy();
          }
          catch (DirectoryException de)
          {
            if (debugEnabled())
            {
              TRACER.debugCaught(DebugLogLevel.ERROR, de);
            }
            setResponseData(de);
            break addProcessing;
          }
        }
        // If the server is configured to check schema and the
        // operation is not a synchronization operation,
        // check to see if the entry is valid according to the server schema,
        // and also whether its attributes are valid according to their syntax.
        if ((DirectoryServer.checkSchema()) && (! isSynchronizationOperation()))
        {
          try
          {
            checkSchema(parentEntry);
          }
          catch (DirectoryException de)
          {
            if (debugEnabled())
            {
              TRACER.debugCaught(DebugLogLevel.ERROR, de);
            }
            setResponseData(de);
            break addProcessing;
          }
        }
        // Get the backend in which the add is to be performed.
        if (backend == null)
        {
          setResultCode(ResultCode.NO_SUCH_OBJECT);
          appendErrorMessage(Message.raw("No backend for entry " +
                                         entryDN.toString())); // TODO: i18n
          break addProcessing;
        }
        // Check to see if there are any controls in the request. If so, then
        // see if there is any special processing required.
        try
        {
          processControls(parentDN);
        }
        catch (DirectoryException de)
        {
          if (debugEnabled())
          {
            TRACER.debugCaught(DebugLogLevel.ERROR, de);
          }
          setResponseData(de);
          break addProcessing;
        }
        // Check to see if the client has permission to perform the add.
        // FIXME: for now assume that this will check all permission
        // pertinent to the operation. This includes proxy authorization
        // and any other controls specified.
        // FIXME: earlier checks to see if the entry already exists or
        // if the parent entry does not exist may have already exposed
        // sensitive information to the client.
        if (AccessControlConfigManager.getInstance().getAccessControlHandler().
                 isAllowed(this) == false)
        {
          setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
          appendErrorMessage(ERR_ADD_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS.get(
                                  String.valueOf(entryDN)));
          skipPostOperation = true;
          break addProcessing;
        }
        // Check for a request to cancel this operation.
        if (getCancelRequest() != null)
        {
          return;
        }
        // If the operation is not a synchronization operation,
        // Invoke the pre-operation add plugins.
        if (! isSynchronizationOperation())
        {
          PreOperationPluginResult preOpResult =
            pluginConfigManager.invokePreOperationAddPlugins(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());
            return;
          }
          else if (preOpResult.sendResponseImmediately())
          {
            skipPostOperation = true;
            break addProcessing;
          }
          else if (preOpResult.skipCoreProcessing())
          {
            skipPostOperation = false;
            break addProcessing;
          }
        }
        // Check for a request to cancel this operation.
        if (getCancelRequest() != null)
        {
          return;
        }
        // If it is not a private backend, then check to see if the server or
        // backend is operating in read-only mode.
        if (! backend.isPrivateBackend())
        {
          switch (DirectoryServer.getWritabilityMode())
          {
            case DISABLED:
              setResultCode(ResultCode.UNWILLING_TO_PERFORM);
              appendErrorMessage(ERR_ADD_SERVER_READONLY.get(
                                      String.valueOf(entryDN)));
              break addProcessing;
            case INTERNAL_ONLY:
              if (! (isInternalOperation() || isSynchronizationOperation()))
              {
                setResultCode(ResultCode.UNWILLING_TO_PERFORM);
                appendErrorMessage(ERR_ADD_SERVER_READONLY.get(
                                        String.valueOf(entryDN)));
                break addProcessing;
              }
              break;
          }
          switch (backend.getWritabilityMode())
          {
            case DISABLED:
              setResultCode(ResultCode.UNWILLING_TO_PERFORM);
              appendErrorMessage(ERR_ADD_BACKEND_READONLY.get(
                                      String.valueOf(entryDN)));
              break addProcessing;
            case INTERNAL_ONLY:
              if (! (isInternalOperation() || isSynchronizationOperation()))
              {
                setResultCode(ResultCode.UNWILLING_TO_PERFORM);
                appendErrorMessage(ERR_ADD_BACKEND_READONLY.get(
                                        String.valueOf(entryDN)));
                break addProcessing;
              }
              break;
          }
        }
        try
        {
          if (noOp)
          {
            appendErrorMessage(INFO_ADD_NOOP.get());
            setResultCode(ResultCode.NO_OPERATION);
          }
          else
          {
            for (SynchronizationProvider provider :
                 DirectoryServer.getSynchronizationProviders())
            {
              try
              {
                SynchronizationProviderResult result =
                     provider.doPreOperation(this);
                if (! result.continueOperationProcessing())
                {
                  break addProcessing;
                }
              }
              catch (DirectoryException de)
              {
                if (debugEnabled())
                {
                  TRACER.debugCaught(DebugLogLevel.ERROR, de);
                }
                logError(ERR_ADD_SYNCH_PREOP_FAILED.get(getConnectionID(),
                              getOperationID(), getExceptionMessage(de)));
                setResponseData(de);
                break addProcessing;
              }
            }
            backend.addEntry(entry, this);
          }
          if (postReadRequest != null)
          {
            addPostReadResponse();
          }
          if (! noOp)
          {
            setResultCode(ResultCode.SUCCESS);
          }
        }
        catch (DirectoryException de)
        {
          if (debugEnabled())
          {
            TRACER.debugCaught(DebugLogLevel.ERROR, de);
          }
          setResponseData(de);
          break addProcessing;
        }
        catch (CancelledOperationException coe)
        {
          if (debugEnabled())
          {
            TRACER.debugCaught(DebugLogLevel.ERROR, coe);
          }
          CancelResult cancelResult = coe.getCancelResult();
          setCancelResult(cancelResult);
          setResultCode(cancelResult.getResultCode());
          Message message = coe.getMessageObject();
          if ((message != null) && (message.length() > 0))
          {
            appendErrorMessage(message);
          }
          break addProcessing;
        }
      }
      finally
      {
        if (entryLock != null)
        {
          LockManager.unlock(entryDN, entryLock);
        }
        if (parentLock != null)
        {
          LockManager.unlock(parentDN, parentLock);
        }
        for (SynchronizationProvider provider :
             DirectoryServer.getSynchronizationProviders())
        {
          try
          {
            provider.doPostOperation(this);
          }
          catch (DirectoryException de)
          {
            if (debugEnabled())
            {
              TRACER.debugCaught(DebugLogLevel.ERROR, de);
            }
            logError(ERR_ADD_SYNCH_POSTOP_FAILED.get(getConnectionID(),
                          getOperationID(), getExceptionMessage(de)));
            setResponseData(de);
            break;
          }
        }
      }
    }
    // Indicate that it is now too late to attempt to cancel the operation.
    setCancelResult(CancelResult.TOO_LATE);
    // Invoke the post-operation or post-synchronization add plugins.
    if (isSynchronizationOperation())
    {
      if (getResultCode() == ResultCode.SUCCESS)
      {
        pluginConfigManager.invokePostSynchronizationAddPlugins(this);
      }
    }
    else if (! skipPostOperation)
    {
      // FIXME -- Should this also be done while holding the locks?
      PostOperationPluginResult postOpResult =
           pluginConfigManager.invokePostOperationAddPlugins(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());
        return;
      }
    }
    // Notify any change notification listeners that might be registered with
    // the server.
    if ((getResultCode() == ResultCode.SUCCESS) && (entry != null))
    {
      for (ChangeNotificationListener changeListener :
           DirectoryServer.getChangeNotificationListeners())
      {
        try
        {
          changeListener.handleAddOperation(this, entry);
        }
        catch (Exception e)
        {
          if (debugEnabled())
          {
            TRACER.debugCaught(DebugLogLevel.ERROR, e);
          }
          logError(ERR_ADD_ERROR_NOTIFYING_CHANGE_LISTENER.get(
                        getExceptionMessage(e)));
        }
      }
    }
  }
  /**
   * Acquire a read lock on the parent of the entry to add.
   *
   * @return  The acquired read lock.
   *
   * @throws  DirectoryException  If a problem occurs while attempting to
   *                              acquire the lock.
   */
  private Lock lockParent(DN parentDN)
          throws DirectoryException
  {
    Lock parentLock = null;
    if (parentDN == null)
    {
      // Either this entry is a suffix or doesn't belong in the directory.
      if (DirectoryServer.isNamingContext(entryDN))
      {
        // This is fine.  This entry is one of the configured suffixes.
        parentLock = null;
      }
      else if (entryDN.isNullDN())
      {
        // This is not fine.  The root DSE cannot be added.
        throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
                                     ERR_ADD_CANNOT_ADD_ROOT_DSE.get());
      }
      else
      {
        // The entry doesn't have a parent but isn't a suffix.  This is not
        // allowed.
        throw new DirectoryException(ResultCode.NO_SUCH_OBJECT,
                                     ERR_ADD_ENTRY_NOT_SUFFIX.get(
                                          String.valueOf(entryDN)));
      }
    }
    else
    {
      for (int i=0; i < 3; i++)
      {
        parentLock = LockManager.lockRead(parentDN);
        if (parentLock != null)
        {
          break;
        }
      }
      if (parentLock == null)
      {
        throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
                                     ERR_ADD_CANNOT_LOCK_PARENT.get(
                                          String.valueOf(entryDN),
                                          String.valueOf(parentDN)));
      }
    }
    return parentLock;
  }
  /**
   * Adds any missing RDN attributes to the entry.
   *
   * @throws  DirectoryException  If the entry is missing one or more RDN
   *                              attributes and the server is configured to
   *                              reject such entries.
   */
  private void addRDNAttributesIfNecessary()
          throws DirectoryException
  {
    RDN rdn = entryDN.getRDN();
    int numAVAs = rdn.getNumValues();
    for (int i=0; i < numAVAs; i++)
    {
      AttributeType  t = rdn.getAttributeType(i);
      AttributeValue v = rdn.getAttributeValue(i);
      String         n = rdn.getAttributeName(i);
      if (t.isOperational())
      {
        List<Attribute> attrList = operationalAttributes.get(t);
        if (attrList == null)
        {
          if (isSynchronizationOperation() ||
              DirectoryServer.addMissingRDNAttributes())
          {
            LinkedHashSet<AttributeValue> valueList =
                 new LinkedHashSet<AttributeValue>(1);
            valueList.add(v);
            attrList = new ArrayList<Attribute>();
            attrList.add(new Attribute(t, n, valueList));
            operationalAttributes.put(t, attrList);
          }
          else
          {
            throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
                                         ERR_ADD_MISSING_RDN_ATTRIBUTE.get(
                                              String.valueOf(entryDN), n));
          }
        }
        else
        {
          boolean found = false;
          for (Attribute a : attrList)
          {
            if (a.hasOptions())
            {
              continue;
            }
            else
            {
              if (! a.hasValue(v))
              {
                a.getValues().add(v);
              }
              found = true;
              break;
            }
          }
          if (! found)
          {
            if (isSynchronizationOperation() ||
                DirectoryServer.addMissingRDNAttributes())
            {
              LinkedHashSet<AttributeValue> valueList =
                   new LinkedHashSet<AttributeValue>(1);
              valueList.add(v);
              attrList.add(new Attribute(t, n, valueList));
            }
            else
            {
              throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
                                           ERR_ADD_MISSING_RDN_ATTRIBUTE.get(
                                                String.valueOf(entryDN), n));
            }
          }
        }
      }
      else
      {
        List<Attribute> attrList = userAttributes.get(t);
        if (attrList == null)
        {
          if (isSynchronizationOperation() ||
              DirectoryServer.addMissingRDNAttributes())
          {
            LinkedHashSet<AttributeValue> valueList =
                 new LinkedHashSet<AttributeValue>(1);
            valueList.add(v);
            attrList = new ArrayList<Attribute>();
            attrList.add(new Attribute(t, n, valueList));
            userAttributes.put(t, attrList);
          }
          else
          {
            throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
                                         ERR_ADD_MISSING_RDN_ATTRIBUTE.get(
                                              String.valueOf(entryDN),n));
          }
        }
        else
        {
          boolean found = false;
          for (Attribute a : attrList)
          {
            if (a.hasOptions())
            {
              continue;
            }
            else
            {
              if (! a.hasValue(v))
              {
                a.getValues().add(v);
              }
              found = true;
              break;
            }
          }
          if (! found)
          {
            if (isSynchronizationOperation() ||
                DirectoryServer.addMissingRDNAttributes())
            {
              LinkedHashSet<AttributeValue> valueList =
                   new LinkedHashSet<AttributeValue>(1);
              valueList.add(v);
              attrList.add(new Attribute(t, n, valueList));
            }
            else
            {
              throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
                                           ERR_ADD_MISSING_RDN_ATTRIBUTE.get(
                                                String.valueOf(entryDN),n));
            }
          }
        }
      }
    }
  }
  /**
   * Adds the provided objectClass to the entry, along with its superior classes
   * if appropriate.
   *
   * @param  objectClass  The objectclass to add to the entry.
   */
  public final void addObjectClassChain(ObjectClass objectClass)
  {
    Map<ObjectClass, String> objectClasses = getObjectClasses();
    if (objectClasses != null){
      if (! objectClasses.containsKey(objectClass))
      {
        objectClasses.put(objectClass, objectClass.getNameOrOID());
      }
      ObjectClass superiorClass = objectClass.getSuperiorClass();
      if ((superiorClass != null) &&
          (! objectClasses.containsKey(superiorClass)))
      {
        addObjectClassChain(superiorClass);
      }
    }
  }
  /**
   * Performs all password policy processing necessary for the provided add
   * operation.
   *
   * @param  passwordPolicy  The password policy associated with the entry to be
   *                         added.
   * @param  userEntry       The user entry being added.
   *
   * @throws  DirectoryException  If a problem occurs while performing password
   *                              policy processing for the add operation.
   */
  public final void handlePasswordPolicy(PasswordPolicy passwordPolicy,
                                         Entry userEntry)
  public final void handlePasswordPolicy()
         throws DirectoryException
  {
    // FIXME -- We need to check to see if the password policy subentry
    //          might be specified virtually rather than as a real
    //          attribute.
    PasswordPolicy passwordPolicy = null;
    List<Attribute> pwAttrList =
         entry.getAttribute(OP_ATTR_PWPOLICY_POLICY_DN);
    if ((pwAttrList != null) && (! pwAttrList.isEmpty()))
    {
      Attribute a = pwAttrList.get(0);
      LinkedHashSet<AttributeValue> valueSet = a.getValues();
      Iterator<AttributeValue> iterator = valueSet.iterator();
      if (iterator.hasNext())
      {
        DN policyDN;
        try
        {
          policyDN = DN.decode(iterator.next().getValue());
        }
        catch (DirectoryException de)
        {
          if (debugEnabled())
          {
            TRACER.debugCaught(DebugLogLevel.ERROR, de);
          }
          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
                                       ERR_ADD_INVALID_PWPOLICY_DN_SYNTAX.get(
                                            String.valueOf(entryDN),
                                           de.getMessageObject()));
        }
        passwordPolicy = DirectoryServer.getPasswordPolicy(policyDN);
        if (passwordPolicy == null)
        {
          throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
                                       ERR_ADD_NO_SUCH_PWPOLICY.get(
                                            String.valueOf(entryDN),
                                         String.valueOf(policyDN)));
        }
      }
    }
    if (passwordPolicy == null)
    {
      passwordPolicy = DirectoryServer.getDefaultPasswordPolicy();
    }
    // See if a password was specified.
    AttributeType passwordAttribute = passwordPolicy.getPasswordAttribute();
    List<Attribute> attrList = userEntry.getAttribute(passwordAttribute);
    List<Attribute> attrList = entry.getAttribute(passwordAttribute);
    if ((attrList == null) || attrList.isEmpty())
    {
      // The entry doesn't have a password, so no action is required.
@@ -237,7 +1254,7 @@
             passwordPolicy.getPasswordValidators().values())
        {
          if (! validator.passwordIsAcceptable(value, currentPasswords, this,
                                               userEntry, invalidReason))
                                               entry, invalidReason))
          {
            addPWPolicyControl(
                 PasswordPolicyErrorType.INSUFFICIENT_PASSWORD_QUALITY);
@@ -296,7 +1313,7 @@
                                      OP_ATTR_PWPOLICY_CHANGED_TIME,
                                      changedTimeValues));
    userEntry.putAttribute(changedTimeType, changedTimeList);
    entry.putAttribute(changedTimeType, changedTimeList);
    // If we should force change on add, then set the appropriate flag.
@@ -319,10 +1336,12 @@
      ArrayList<Attribute> resetList = new ArrayList<Attribute>(1);
      resetList.add(new Attribute(resetType, OP_ATTR_PWPOLICY_RESET_REQUIRED,
                                  resetValues));
      userEntry.putAttribute(resetType, resetList);
      entry.putAttribute(resetType, resetList);
    }
  }
  /**
   * Adds a password policy response control if the corresponding request
   * control was included.
@@ -341,28 +1360,462 @@
    }
  }
  /**
   * Adds the provided objectClass to the entry, along with its superior classes
   * if appropriate.
   * Verifies that the entry to be added conforms to the server schema.
   *
   * @param  objectClass  The objectclass to add to the entry.
   * @param  parentEntry  The parent of the entry to add.
   *
   * @throws  DirectoryException  If the entry violates the server schema
   *                              configuration.
   */
  public final void addObjectClassChain(ObjectClass objectClass)
  private void checkSchema(Entry parentEntry)
          throws DirectoryException
  {
    Map<ObjectClass, String> objectClasses = getObjectClasses();
    if (objectClasses != null){
      if (! objectClasses.containsKey(objectClass))
    MessageBuilder invalidReason = new MessageBuilder();
    if (! entry.conformsToSchema(parentEntry, true, true, true, invalidReason))
      {
        objectClasses.put(objectClass, objectClass.getNameOrOID());
      throw new DirectoryException(ResultCode.OBJECTCLASS_VIOLATION,
                                   invalidReason.toMessage());
      }
      ObjectClass superiorClass = objectClass.getSuperiorClass();
      if ((superiorClass != null) &&
          (! objectClasses.containsKey(superiorClass)))
    else
      {
        addObjectClassChain(superiorClass);
      switch (DirectoryServer.getSyntaxEnforcementPolicy())
      {
        case REJECT:
          invalidReason = new MessageBuilder();
          for (List<Attribute> attrList : userAttributes.values())
          {
            for (Attribute a : attrList)
            {
              AttributeSyntax syntax = a.getAttributeType().getSyntax();
              if (syntax != null)
              {
                for (AttributeValue v : a.getValues())
                {
                  if (! syntax.valueIsAcceptable(v.getValue(), invalidReason))
                  {
                    Message message = WARN_ADD_OP_INVALID_SYNTAX.get(
                                           String.valueOf(entryDN),
                                           String.valueOf(v.getStringValue()),
                                           String.valueOf(a.getName()),
                                           String.valueOf(invalidReason));
                    throw new DirectoryException(
                                   ResultCode.INVALID_ATTRIBUTE_SYNTAX,
                                   message);
                  }
                }
      }
    }
  }
          for (List<Attribute> attrList :
               operationalAttributes.values())
          {
            for (Attribute a : attrList)
            {
              AttributeSyntax syntax = a.getAttributeType().getSyntax();
              if (syntax != null)
              {
                for (AttributeValue v : a.getValues())
                {
                  if (! syntax.valueIsAcceptable(v.getValue(),
                                                 invalidReason))
                  {
                    Message message = WARN_ADD_OP_INVALID_SYNTAX.
                        get(String.valueOf(entryDN),
                            String.valueOf(v.getStringValue()),
                            String.valueOf(a.getName()),
                            String.valueOf(invalidReason));
                    throw new DirectoryException(
                                   ResultCode.INVALID_ATTRIBUTE_SYNTAX,
                                   message);
}
                }
              }
            }
          }
          break;
        case WARN:
          invalidReason = new MessageBuilder();
          for (List<Attribute> attrList : userAttributes.values())
          {
            for (Attribute a : attrList)
            {
              AttributeSyntax syntax = a.getAttributeType().getSyntax();
              if (syntax != null)
              {
                for (AttributeValue v : a.getValues())
                {
                  if (! syntax.valueIsAcceptable(v.getValue(),
                                                 invalidReason))
                  {
                    logError(WARN_ADD_OP_INVALID_SYNTAX.get(
                                  String.valueOf(entryDN),
                                  String.valueOf(v.getStringValue()),
                                  String.valueOf(a.getName()),
                                  String.valueOf(invalidReason)));
                  }
                }
              }
            }
          }
          for (List<Attribute> attrList : operationalAttributes.values())
          {
            for (Attribute a : attrList)
            {
              AttributeSyntax syntax = a.getAttributeType().getSyntax();
              if (syntax != null)
              {
                for (AttributeValue v : a.getValues())
                {
                  if (! syntax.valueIsAcceptable(v.getValue(),
                                                 invalidReason))
                  {
                    logError(WARN_ADD_OP_INVALID_SYNTAX.get(
                                  String.valueOf(entryDN),
                                  String.valueOf(v.getStringValue()),
                                  String.valueOf(a.getName()),
                                  String.valueOf(invalidReason)));
                  }
                }
              }
            }
          }
          break;
      }
    }
    // See if the entry contains any attributes or object classes marked
    // OBSOLETE.  If so, then reject the entry.
    for (AttributeType at : userAttributes.keySet())
    {
      if (at.isObsolete())
      {
        throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
                                     WARN_ADD_ATTR_IS_OBSOLETE.get(
                                          String.valueOf(entryDN),
                                          at.getNameOrOID()));
      }
    }
    for (AttributeType at : operationalAttributes.keySet())
    {
      if (at.isObsolete())
      {
        throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
                                     WARN_ADD_ATTR_IS_OBSOLETE.get(
                                          String.valueOf(entryDN),
                                          at.getNameOrOID()));
      }
    }
    for (ObjectClass oc : objectClasses.keySet())
    {
      if (oc.isObsolete())
      {
        throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
                                     WARN_ADD_OC_IS_OBSOLETE.get(
                                          String.valueOf(entryDN),
                                          oc.getNameOrOID()));
      }
    }
  }
  /**
   * Processes the set of controls contained in the add request.
   *
   * @param  parentDN  The DN of the parent of the entry to add.
   *
   * @throws  DirectoryException  If there is a problem with any of the
   *                              request controls.
   */
  private void processControls(DN parentDN)
          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(parentDN, this, c))
        {
          skipPostOperation = true;
          throw new DirectoryException(ResultCode.INSUFFICIENT_ACCESS_RIGHTS,
                         ERR_CONTROL_INSUFFICIENT_ACCESS_RIGHTS.get(oid));
        }
        if (oid.equals(OID_LDAP_ASSERTION))
        {
          LDAPAssertionRequestControl assertControl;
          if (c instanceof LDAPAssertionRequestControl)
          {
            assertControl = (LDAPAssertionRequestControl) c;
          }
          else
          {
            try
            {
              assertControl = LDAPAssertionRequestControl.decodeControl(c);
              requestControls.set(i, assertControl);
            }
            catch (LDAPException le)
            {
              if (debugEnabled())
              {
                TRACER.debugCaught(DebugLogLevel.ERROR, le);
              }
              throw new DirectoryException(
                             ResultCode.valueOf(le.getResultCode()),
                             le.getMessageObject());
            }
          }
          try
          {
            // FIXME -- We need to determine whether the current user has
            //          permission to make this determination.
            SearchFilter filter = assertControl.getSearchFilter();
            if (! filter.matchesEntry(entry))
            {
              throw new DirectoryException(ResultCode.ASSERTION_FAILED,
                                           ERR_ADD_ASSERTION_FAILED.get(
                                                String.valueOf(entryDN)));
            }
          }
          catch (DirectoryException de)
          {
            if (de.getResultCode() == ResultCode.ASSERTION_FAILED)
            {
              throw de;
            }
            if (debugEnabled())
            {
              TRACER.debugCaught(DebugLogLevel.ERROR, de);
            }
            throw new DirectoryException(ResultCode.PROTOCOL_ERROR,
                           ERR_ADD_CANNOT_PROCESS_ASSERTION_FILTER.get(
                                String.valueOf(entryDN),
                                de.getMessageObject()));
          }
        }
        else if (oid.equals(OID_LDAP_NOOP_OPENLDAP_ASSIGNED))
        {
          noOp = true;
        }
        else if (oid.equals(OID_LDAP_READENTRY_POSTREAD))
        {
          if (c instanceof LDAPPostReadRequestControl)
          {
            postReadRequest = (LDAPPostReadRequestControl) c;
          }
          else
          {
            try
            {
              postReadRequest = LDAPPostReadRequestControl.decodeControl(c);
              requestControls.set(i, postReadRequest);
            }
            catch (LDAPException le)
            {
              if (debugEnabled())
              {
                TRACER.debugCaught(DebugLogLevel.ERROR, le);
              }
              throw new DirectoryException(
                             ResultCode.valueOf(le.getResultCode()),
                             le.getMessageObject());
            }
          }
        }
        else if (oid.equals(OID_PROXIED_AUTH_V1))
        {
          // The requester must have the PROXIED_AUTH privilige in order to
          // be able to use this control.
          if (! getClientConnection().hasPrivilege(Privilege.PROXIED_AUTH,
                                                   this))
          {
            throw new DirectoryException(ResultCode.AUTHORIZATION_DENIED,
                           ERR_PROXYAUTH_INSUFFICIENT_PRIVILEGES.get());
          }
          ProxiedAuthV1Control proxyControl;
          if (c instanceof ProxiedAuthV1Control)
          {
            proxyControl = (ProxiedAuthV1Control) c;
          }
          else
          {
            try
            {
              proxyControl = ProxiedAuthV1Control.decodeControl(c);
            }
            catch (LDAPException le)
            {
              if (debugEnabled())
              {
                TRACER.debugCaught(DebugLogLevel.ERROR, le);
              }
              throw new DirectoryException(
                             ResultCode.valueOf(le.getResultCode()),
                             le.getMessageObject());
            }
          }
          Entry authorizationEntry = proxyControl.getAuthorizationEntry();
          setAuthorizationEntry(authorizationEntry);
          if (authorizationEntry == null)
          {
            setProxiedAuthorizationDN(DN.nullDN());
          }
          else
          {
            setProxiedAuthorizationDN(authorizationEntry.getDN());
          }
        }
        else if (oid.equals(OID_PROXIED_AUTH_V2))
        {
          // The requester must have the PROXIED_AUTH privilige in order to
          // be able to use this control.
          if (! getClientConnection().hasPrivilege(Privilege.PROXIED_AUTH,
                                                   this))
          {
            throw new DirectoryException(ResultCode.AUTHORIZATION_DENIED,
                           ERR_PROXYAUTH_INSUFFICIENT_PRIVILEGES.get());
          }
          ProxiedAuthV2Control proxyControl;
          if (c instanceof ProxiedAuthV2Control)
          {
            proxyControl = (ProxiedAuthV2Control) c;
          }
          else
          {
            try
            {
              proxyControl = ProxiedAuthV2Control.decodeControl(c);
            }
            catch (LDAPException le)
            {
              if (debugEnabled())
              {
                TRACER.debugCaught(DebugLogLevel.ERROR, le);
              }
              throw new DirectoryException(
                             ResultCode.valueOf(le.getResultCode()),
                             le.getMessageObject());
            }
          }
          Entry authorizationEntry = proxyControl.getAuthorizationEntry();
          setAuthorizationEntry(authorizationEntry);
          if (authorizationEntry == null)
          {
            setProxiedAuthorizationDN(DN.nullDN());
          }
          else
          {
            setProxiedAuthorizationDN(authorizationEntry.getDN());
          }
        }
        else if (oid.equals(OID_PASSWORD_POLICY_CONTROL))
        {
          // We don't need to do anything here because it's already handled
          // in LocalBackendAddOperation.handlePasswordPolicy().
        }
        // NYI -- Add support for additional controls.
        else if (c.isCritical())
        {
          if ((backend == null) || (! backend.supportsControl(oid)))
          {
            throw new DirectoryException(
                           ResultCode.UNAVAILABLE_CRITICAL_EXTENSION,
                           ERR_ADD_UNSUPPORTED_CRITICAL_CONTROL.get(
                                String.valueOf(entryDN), oid));
          }
        }
      }
    }
  }
  /**
   * Adds the post-read response control to the response.
   */
  private void addPostReadResponse()
  {
    Entry addedEntry = entry.duplicate(true);
    if (! postReadRequest.allowsAttribute(
               DirectoryServer.getObjectClassAttributeType()))
    {
      addedEntry.removeAttribute(DirectoryServer.getObjectClassAttributeType());
    }
    if (! postReadRequest.returnAllUserAttributes())
    {
      Iterator<AttributeType> iterator =
           addedEntry.getUserAttributes().keySet().iterator();
      while (iterator.hasNext())
      {
        AttributeType attrType = iterator.next();
        if (! postReadRequest.allowsAttribute(attrType))
        {
          iterator.remove();
        }
      }
    }
    if (! postReadRequest.returnAllOperationalAttributes())
    {
      Iterator<AttributeType> iterator =
           addedEntry.getOperationalAttributes().keySet().iterator();
      while (iterator.hasNext())
      {
        AttributeType attrType = iterator.next();
        if (! postReadRequest.allowsAttribute(attrType))
        {
          iterator.remove();
        }
      }
    }
    // FIXME -- Check access controls on the entry to see if it should
    //          be returned or if any attributes need to be stripped
    //          out..
    SearchResultEntry searchEntry = new SearchResultEntry(addedEntry);
    LDAPPostReadResponseControl responseControl =
         new LDAPPostReadResponseControl(postReadRequest.getOID(),
                                         postReadRequest.isCritical(),
                                         searchEntry);
    addResponseControl(responseControl);
  }
}
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,
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())));
          }
        }
      }
    }
  }
}
opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendCompareOperation.java
@@ -27,28 +27,82 @@
package org.opends.server.workflowelement.localbackend;
import java.util.HashSet;
import java.util.List;
import java.util.concurrent.locks.Lock;
import org.opends.server.api.Backend;
import org.opends.server.api.ClientConnection;
import org.opends.server.api.plugin.PostOperationPluginResult;
import org.opends.server.api.plugin.PreOperationPluginResult;
import org.opends.server.controls.LDAPAssertionRequestControl;
import org.opends.server.controls.ProxiedAuthV1Control;
import org.opends.server.controls.ProxiedAuthV2Control;
import org.opends.server.core.AccessControlConfigManager;
import org.opends.server.core.CompareOperation;
import org.opends.server.core.CompareOperationWrapper;
import org.opends.server.core.DirectoryServer;
import org.opends.server.core.PluginConfigManager;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.types.Attribute;
import org.opends.server.types.AttributeType;
import org.opends.server.types.AttributeValue;
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.LDAPException;
import org.opends.server.types.LockManager;
import org.opends.server.types.Privilege;
import org.opends.server.types.ResultCode;
import org.opends.server.types.SearchFilter;
import org.opends.server.types.operation.PostOperationCompareOperation;
import org.opends.server.types.operation.PostResponseCompareOperation;
import org.opends.server.types.operation.PreOperationCompareOperation;
import static org.opends.messages.CoreMessages.*;
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 that may be used to determine whether a
 * specified entry in the Directory Server contains a given attribute-value
 * pair.
 */
public class LocalBackendCompareOperation extends CompareOperationWrapper
    implements PreOperationCompareOperation,
               PostOperationCompareOperation,
public class LocalBackendCompareOperation
       extends CompareOperationWrapper
       implements PreOperationCompareOperation, PostOperationCompareOperation,
               PostResponseCompareOperation
{
  /**
   * The tracer object for the debug logger.
   */
  private static final DebugTracer TRACER = getTracer();
  // The backend in which the comparison is to be performed.
  private Backend backend;
  // Indicates whether to skip post-operation processing.
  private boolean skipPostOperation;
  // The client connection for this operation.
  private ClientConnection clientConnection;
  // The DN of the entry to compare.
  private DN entryDN;
  // The entry to be compared.
  private Entry entry = null;
  /**
   * Creates a new compare operation based on the provided compare operation.
   *
@@ -61,6 +115,7 @@
  }
  /**
   * Retrieves the entry to target with the compare operation.
   *
@@ -73,14 +128,505 @@
  }
  /**
   * Set the entry to target with the compare operation.
   * Process this compare operation in a local backend.
   *
   * @param  entry   The entry to target with the compare operation.
   * @param  backend  The backend in which the compare operation should be
   *                  processed.
   */
  public void setEntryToCompare(Entry entry)
  void processLocalCompare(Backend backend)
  {
    this.entry = entry;
    this.backend = backend;
    clientConnection  = getClientConnection();
    skipPostOperation = false;
    // Get the plugin config manager that will be used for invoking plugins.
    PluginConfigManager pluginConfigManager =
         DirectoryServer.getPluginConfigManager();
    // Get a reference to the client connection
    ClientConnection clientConnection = getClientConnection();
    // Check for a request to cancel this operation.
    if (getCancelRequest() != null)
    {
      return;
  }
    // Create a labeled block of code that we can break out of if a problem is
    // detected.
compareProcessing:
    {
      // Process the entry DN to convert it from the raw form to the form
      // required for the rest of the compare processing.
      entryDN = getEntryDN();
      if (entryDN == null)
      {
        skipPostOperation = true;
        break compareProcessing;
}
      // If the target entry is in the server configuration, then make sure the
      // requester has the CONFIG_READ privilege.
      if (DirectoryServer.getConfigHandler().handlesEntry(entryDN) &&
          (! clientConnection.hasPrivilege(Privilege.CONFIG_READ, this)))
      {
        appendErrorMessage(ERR_COMPARE_CONFIG_INSUFFICIENT_PRIVILEGES.get());
        setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
        skipPostOperation = true;
        break compareProcessing;
      }
      // Check for a request to cancel this operation.
      if (getCancelRequest() != null)
      {
        return;
      }
      // Grab a read lock on the entry.
      Lock readLock = null;
      for (int i=0; i < 3; i++)
      {
        readLock = LockManager.lockRead(entryDN);
        if (readLock != null)
        {
          break;
        }
      }
      if (readLock == null)
      {
        setResultCode(DirectoryServer.getServerErrorResultCode());
        appendErrorMessage(ERR_COMPARE_CANNOT_LOCK_ENTRY.get(
                                String.valueOf(entryDN)));
        skipPostOperation = true;
        break compareProcessing;
      }
      try
      {
        // Get the entry.  If it does not exist, then fail.
        try
        {
          entry = DirectoryServer.getEntry(entryDN);
          if (entry == null)
          {
            setResultCode(ResultCode.NO_SUCH_OBJECT);
            appendErrorMessage(
                    ERR_COMPARE_NO_SUCH_ENTRY.get(String.valueOf(entryDN)));
            // See if one of the entry's ancestors exists.
            DN parentDN = entryDN.getParentDNInSuffix();
            while (parentDN != null)
            {
              try
              {
                if (DirectoryServer.entryExists(parentDN))
                {
                  setMatchedDN(parentDN);
                  break;
                }
              }
              catch (Exception e)
              {
                if (debugEnabled())
                {
                  TRACER.debugCaught(DebugLogLevel.ERROR, e);
                }
                break;
              }
              parentDN = parentDN.getParentDNInSuffix();
            }
            break compareProcessing;
          }
        }
        catch (DirectoryException de)
        {
          if (debugEnabled())
          {
            TRACER.debugCaught(DebugLogLevel.ERROR, de);
          }
          setResultCode(de.getResultCode());
          appendErrorMessage(de.getMessageObject());
          break compareProcessing;
        }
        // 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 compareProcessing;
        }
        // Check to see if the client has permission to perform the
        // compare.
        // FIXME: for now assume that this will check all permission
        // pertinent to the operation. This includes proxy authorization
        // and any other controls specified.
        // FIXME: earlier checks to see if the entry already exists may
        // have already exposed sensitive information to the client.
        if (! AccessControlConfigManager.getInstance().
                   getAccessControlHandler().isAllowed(this))
        {
          setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
          appendErrorMessage(ERR_COMPARE_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS.get(
                                  String.valueOf(entryDN)));
          skipPostOperation = true;
          break compareProcessing;
        }
        // Check for a request to cancel this operation.
        if (getCancelRequest() != null)
        {
          return;
        }
        // Invoke the pre-operation compare plugins.
        PreOperationPluginResult preOpResult =
             pluginConfigManager.invokePreOperationComparePlugins(this);
        if (preOpResult.connectionTerminated())
        {
          // There's no point in continuing with anything.  Log the request and
          // result and return.
          setResultCode(ResultCode.CANCELED);
          appendErrorMessage(ERR_CANCELED_BY_PREOP_DISCONNECT.get());
          setProcessingStopTime();
          return;
        }
        else if (preOpResult.sendResponseImmediately())
        {
          skipPostOperation = true;
          break compareProcessing;
        }
        else if (preOpResult.skipCoreProcessing())
        {
          skipPostOperation = false;
          break compareProcessing;
        }
        // Get the base attribute type and set of options.
        String          baseName;
        HashSet<String> options;
        String rawAttributeType = getRawAttributeType();
        int             semicolonPos = rawAttributeType.indexOf(';');
        if (semicolonPos > 0)
        {
          baseName = toLowerCase(rawAttributeType.substring(0, semicolonPos));
          options = new HashSet<String>();
          int nextPos = rawAttributeType.indexOf(';', semicolonPos+1);
          while (nextPos > 0)
          {
            options.add(rawAttributeType.substring(semicolonPos+1, nextPos));
            semicolonPos = nextPos;
            nextPos = rawAttributeType.indexOf(';', semicolonPos+1);
          }
          options.add(rawAttributeType.substring(semicolonPos+1));
        }
        else
        {
          baseName = toLowerCase(rawAttributeType);
          options  = null;
        }
        // Actually perform the compare operation.
        AttributeType attrType = getAttributeType();
        if (attrType == null)
        {
          attrType = DirectoryServer.getAttributeType(baseName, true);
          setAttributeType(attrType);
        }
        List<Attribute> attrList = entry.getAttribute(attrType, options);
        if ((attrList == null) || attrList.isEmpty())
        {
          setResultCode(ResultCode.NO_SUCH_ATTRIBUTE);
          if (options == null)
          {
            appendErrorMessage(WARN_COMPARE_OP_NO_SUCH_ATTR.get(
                                    String.valueOf(entryDN), baseName));
          }
          else
          {
            appendErrorMessage(WARN_COMPARE_OP_NO_SUCH_ATTR_WITH_OPTIONS.get(
                                    String.valueOf(entryDN), baseName));
          }
        }
        else
        {
          AttributeValue value = new AttributeValue(attrType,
                                                    getAssertionValue());
          boolean matchFound = false;
          for (Attribute a : attrList)
          {
            if (a.hasValue(value))
            {
              matchFound = true;
              break;
            }
          }
          if (matchFound)
          {
            setResultCode(ResultCode.COMPARE_TRUE);
          }
          else
          {
            setResultCode(ResultCode.COMPARE_FALSE);
          }
        }
      }
      finally
      {
        LockManager.unlock(entryDN, readLock);
      }
    }
    // Check for a request to cancel this operation.
    if (getCancelRequest() != null)
    {
      return;
    }
    // Invoke the post-operation compare plugins.
    if (! skipPostOperation)
    {
      PostOperationPluginResult postOperationResult =
           pluginConfigManager.invokePostOperationComparePlugins(this);
      if (postOperationResult.connectionTerminated())
      {
        setResultCode(ResultCode.CANCELED);
        appendErrorMessage(ERR_CANCELED_BY_POSTOP_DISCONNECT.get());
        return;
      }
    }
  }
  /**
   * Performs any processing required for the controls included in the request.
   *
   * @throws  DirectoryException  If a problem occurs that should prevent the
   *                              operation from succeeding.
   */
  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(entryDN, this, c))
        {
          skipPostOperation = true;
          throw new DirectoryException(ResultCode.INSUFFICIENT_ACCESS_RIGHTS,
                         ERR_CONTROL_INSUFFICIENT_ACCESS_RIGHTS.get(oid));
        }
        if (oid.equals(OID_LDAP_ASSERTION))
        {
          LDAPAssertionRequestControl assertControl;
          if (c instanceof LDAPAssertionRequestControl)
          {
            assertControl = (LDAPAssertionRequestControl) c;
          }
          else
          {
            try
            {
              assertControl = LDAPAssertionRequestControl.decodeControl(c);
              requestControls.set(i, assertControl);
            }
            catch (LDAPException le)
            {
              if (debugEnabled())
              {
                TRACER.debugCaught(DebugLogLevel.ERROR, le);
              }
              throw new DirectoryException(
                             ResultCode.valueOf(le.getResultCode()),
                             le.getMessageObject());
            }
          }
          try
          {
            // FIXME -- We need to determine whether the current user has
            //          permission to make this determination.
            SearchFilter filter = assertControl.getSearchFilter();
            if (! filter.matchesEntry(entry))
            {
              throw new DirectoryException(ResultCode.ASSERTION_FAILED,
                                           ERR_COMPARE_ASSERTION_FAILED.get(
                                                String.valueOf(entryDN)));
            }
          }
          catch (DirectoryException de)
          {
            if (de.getResultCode() == ResultCode.ASSERTION_FAILED)
            {
              throw de;
            }
            if (debugEnabled())
            {
              TRACER.debugCaught(DebugLogLevel.ERROR, de);
            }
            throw new DirectoryException(ResultCode.PROTOCOL_ERROR,
                           ERR_COMPARE_CANNOT_PROCESS_ASSERTION_FILTER.get(
                                String.valueOf(entryDN),
                                de.getMessageObject()));
          }
        }
        else if (oid.equals(OID_PROXIED_AUTH_V1))
        {
          // The requester must have the PROXIED_AUTH privilige in order to
          // be able to use this control.
          if (! clientConnection.hasPrivilege(Privilege.PROXIED_AUTH, this))
          {
            throw new DirectoryException(ResultCode.AUTHORIZATION_DENIED,
                           ERR_PROXYAUTH_INSUFFICIENT_PRIVILEGES.get());
          }
          ProxiedAuthV1Control proxyControl;
          if (c instanceof ProxiedAuthV1Control)
          {
            proxyControl = (ProxiedAuthV1Control) c;
          }
          else
          {
            try
            {
              proxyControl = ProxiedAuthV1Control.decodeControl(c);
            }
            catch (LDAPException le)
            {
              if (debugEnabled())
              {
                TRACER.debugCaught(DebugLogLevel.ERROR, le);
              }
              throw new DirectoryException(
                             ResultCode.valueOf(le.getResultCode()),
                             le.getMessageObject());
            }
          }
          Entry authorizationEntry = proxyControl.getAuthorizationEntry();
          setAuthorizationEntry(authorizationEntry);
          if (authorizationEntry == null)
          {
            setProxiedAuthorizationDN(DN.nullDN());
          }
          else
          {
            setProxiedAuthorizationDN(authorizationEntry.getDN());
          }
        }
        else if (oid.equals(OID_PROXIED_AUTH_V2))
        {
          // The requester must have the PROXIED_AUTH privilige in order to
          // be able to use this control.
          if (! clientConnection.hasPrivilege(Privilege.PROXIED_AUTH, this))
          {
            throw new DirectoryException(ResultCode.AUTHORIZATION_DENIED,
                           ERR_PROXYAUTH_INSUFFICIENT_PRIVILEGES.get());
          }
          ProxiedAuthV2Control proxyControl;
          if (c instanceof ProxiedAuthV2Control)
          {
            proxyControl = (ProxiedAuthV2Control) c;
          }
          else
          {
            try
            {
              proxyControl = ProxiedAuthV2Control.decodeControl(c);
            }
            catch (LDAPException le)
            {
              if (debugEnabled())
              {
                TRACER.debugCaught(DebugLogLevel.ERROR, le);
              }
              throw new DirectoryException(
                             ResultCode.valueOf(le.getResultCode()),
                             le.getMessageObject());
            }
          }
          Entry authorizationEntry = proxyControl.getAuthorizationEntry();
          setAuthorizationEntry(authorizationEntry);
          if (authorizationEntry == null)
          {
            setProxiedAuthorizationDN(DN.nullDN());
          }
          else
          {
            setProxiedAuthorizationDN(authorizationEntry.getDN());
          }
        }
        // NYI -- Add support for additional controls.
        else if (c.isCritical())
        {
          if ((backend == null) || (! backend.supportsControl(oid)))
          {
            throw new DirectoryException(
                           ResultCode.UNAVAILABLE_CRITICAL_EXTENSION,
                           ERR_COMPARE_UNSUPPORTED_CRITICAL_CONTROL.get(
                                String.valueOf(entryDN), oid));
          }
        }
      }
    }
  }
}
opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendDeleteOperation.java
@@ -27,27 +27,97 @@
package org.opends.server.workflowelement.localbackend;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.locks.Lock;
import org.opends.messages.Message;
import org.opends.server.api.Backend;
import org.opends.server.api.ChangeNotificationListener;
import org.opends.server.api.ClientConnection;
import org.opends.server.api.SynchronizationProvider;
import org.opends.server.api.plugin.PostOperationPluginResult;
import org.opends.server.api.plugin.PreOperationPluginResult;
import org.opends.server.controls.LDAPAssertionRequestControl;
import org.opends.server.controls.LDAPPreReadRequestControl;
import org.opends.server.controls.LDAPPreReadResponseControl;
import org.opends.server.controls.ProxiedAuthV1Control;
import org.opends.server.controls.ProxiedAuthV2Control;
import org.opends.server.core.AccessControlConfigManager;
import org.opends.server.core.DeleteOperationWrapper;
import org.opends.server.core.DeleteOperation;
import org.opends.server.core.DirectoryServer;
import org.opends.server.core.PluginConfigManager;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.types.AttributeType;
import org.opends.server.types.CancelledOperationException;
import org.opends.server.types.CancelResult;
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.LDAPException;
import org.opends.server.types.LockManager;
import org.opends.server.types.Privilege;
import org.opends.server.types.ResultCode;
import org.opends.server.types.SearchFilter;
import org.opends.server.types.SearchResultEntry;
import org.opends.server.types.SynchronizationProviderResult;
import org.opends.server.types.operation.PostOperationDeleteOperation;
import org.opends.server.types.operation.PostResponseDeleteOperation;
import org.opends.server.types.operation.PreOperationDeleteOperation;
import org.opends.server.types.operation.PostSynchronizationDeleteOperation;
import static org.opends.messages.CoreMessages.*;
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 delete an entry in a local backend
 * of the Directory Server.
 */
public class LocalBackendDeleteOperation extends DeleteOperationWrapper
  implements PreOperationDeleteOperation,
             PostOperationDeleteOperation,
public class LocalBackendDeleteOperation
       extends DeleteOperationWrapper
       implements PreOperationDeleteOperation, PostOperationDeleteOperation,
             PostResponseDeleteOperation,
             PostSynchronizationDeleteOperation
{
  /**
   * The tracer object for the debug logger.
   */
  private static final DebugTracer TRACER = getTracer();
  // The backend in which the operation is to be processed.
  private Backend backend;
  // Indicates whether the LDAP no-op control has been requested.
  private boolean noOp;
  // Indicates whether to skip post-operation processing.
  private boolean skipPostOperation;
  // The client connection on which this operation was requested.
  private ClientConnection clientConnection;
  // The DN of the entry to be deleted.
  private DN entryDN;
  // The entry to be deleted.
  private Entry entry;
  // The pre-read request control included in the request, if applicable.
  private LDAPPreReadRequestControl preReadRequest;
  /**
   * Creates a new operation that may be used to delete an entry from a
   * local backend of the Directory Server.
@@ -61,6 +131,7 @@
  }
  /**
   * Retrieves the entry to be deleted.
   *
@@ -72,13 +143,731 @@
    return entry;
  }
  /**
   * Sets the entry to be deleted.
   * Process this delete operation in a local backend.
   *
   * @param  entry - The entry to be deleted
   * @param  backend  The backend in which the delete operation should be
   *                  processed.
   */
  public void setEntryToDelete(Entry entry){
    this.entry = entry;
  void processLocalDelete(Backend backend)
  {
    this.backend = backend;
    clientConnection = getClientConnection();
    // Get the plugin config manager that will be used for invoking plugins.
    PluginConfigManager pluginConfigManager =
         DirectoryServer.getPluginConfigManager();
    skipPostOperation = false;
    // Check for a request to cancel this operation.
    if (getCancelRequest() != null)
    {
      return;
  }
    // Create a labeled block of code that we can break out of if a problem is
    // detected.
deleteProcessing:
    {
      // Process the entry DN to convert it from its raw form as provided by the
      // client to the form required for the rest of the delete processing.
      entryDN = getEntryDN();
      if (entryDN == null){
        break deleteProcessing;
}
      // Grab a write lock on the entry.
      Lock entryLock = null;
      for (int i=0; i < 3; i++)
      {
        entryLock = LockManager.lockWrite(entryDN);
        if (entryLock != null)
        {
          break;
        }
      }
      if (entryLock == null)
      {
        setResultCode(DirectoryServer.getServerErrorResultCode());
        appendErrorMessage(ERR_DELETE_CANNOT_LOCK_ENTRY.get(
                                String.valueOf(entryDN)));
        break deleteProcessing;
      }
      try
      {
        // Get the entry to delete.  If it doesn't exist, then fail.
        try
        {
          entry = backend.getEntry(entryDN);
          if (entry == null)
          {
            setResultCode(ResultCode.NO_SUCH_OBJECT);
            appendErrorMessage(ERR_DELETE_NO_SUCH_ENTRY.get(
                                    String.valueOf(entryDN)));
            try
            {
              DN parentDN = entryDN.getParentDNInSuffix();
              while (parentDN != null)
              {
                if (DirectoryServer.entryExists(parentDN))
                {
                  setMatchedDN(parentDN);
                  break;
                }
                parentDN = parentDN.getParentDNInSuffix();
              }
            }
            catch (Exception e)
            {
              if (debugEnabled())
              {
                TRACER.debugCaught(DebugLogLevel.ERROR, e);
              }
            }
            break deleteProcessing;
          }
        }
        catch (DirectoryException de)
        {
          if (debugEnabled())
          {
            TRACER.debugCaught(DebugLogLevel.ERROR, de);
          }
          setResponseData(de);
          break deleteProcessing;
        }
        // Invoke any conflict resolution processing that might be needed by the
        // synchronization provider.
        for (SynchronizationProvider provider :
             DirectoryServer.getSynchronizationProviders())
        {
          try
          {
            SynchronizationProviderResult result =
                 provider.handleConflictResolution(this);
            if (! result.continueOperationProcessing())
            {
              break deleteProcessing;
            }
          }
          catch (DirectoryException de)
          {
            if (debugEnabled())
            {
              TRACER.debugCaught(DebugLogLevel.ERROR, de);
            }
            logError(ERR_DELETE_SYNCH_CONFLICT_RESOLUTION_FAILED.get(
                          getConnectionID(), getOperationID(),
                          getExceptionMessage(de)));
            setResponseData(de);
            break deleteProcessing;
          }
        }
        // Check to see if the client has permission to perform the
        // delete.
        // 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 deleteProcessing;
        }
        // FIXME: for now assume that this will check all permission
        // pertinent to the operation. This includes proxy authorization
        // and any other controls specified.
        // FIXME: earlier checks to see if the entry already exists may
        // have already exposed sensitive information to the client.
        if (! AccessControlConfigManager.getInstance().
                   getAccessControlHandler().isAllowed(this))
        {
          setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
          appendErrorMessage(ERR_DELETE_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS.get(
                                  String.valueOf(entryDN)));
          skipPostOperation = true;
          break deleteProcessing;
        }
        // Check for a request to cancel this operation.
        if (getCancelRequest() != null)
        {
          return;
        }
        // If the operation is not a synchronization operation,
        // invoke the pre-delete plugins.
        if (! isSynchronizationOperation())
        {
          PreOperationPluginResult preOpResult =
               pluginConfigManager.invokePreOperationDeletePlugins(this);
          if (preOpResult.connectionTerminated())
          {
            // There's no point in continuing with anything.  Log the request
            // and result and return.
            setResultCode(ResultCode.CANCELED);
            appendErrorMessage(ERR_CANCELED_BY_PREOP_DISCONNECT.get());
            setProcessingStopTime();
            return;
          }
          else if (preOpResult.sendResponseImmediately() ||
                   preOpResult.skipCoreProcessing())
          {
            skipPostOperation = true;
            break deleteProcessing;
          }
        }
        // Check for a request to cancel this operation.
        if (getCancelRequest() != null)
        {
          return;
        }
        // Get the backend to use for the delete.  If there is none, then fail.
        if (backend == null)
        {
          setResultCode(ResultCode.NO_SUCH_OBJECT);
          appendErrorMessage(ERR_DELETE_NO_SUCH_ENTRY.get(
                                  String.valueOf(entryDN)));
          break deleteProcessing;
        }
        // If it is not a private backend, then check to see if the server or
        // backend is operating in read-only mode.
        if (! backend.isPrivateBackend())
        {
          switch (DirectoryServer.getWritabilityMode())
          {
            case DISABLED:
              setResultCode(ResultCode.UNWILLING_TO_PERFORM);
              appendErrorMessage(ERR_DELETE_SERVER_READONLY.get(
                                      String.valueOf(entryDN)));
              break deleteProcessing;
            case INTERNAL_ONLY:
              if (! (isInternalOperation() || isSynchronizationOperation()))
              {
                setResultCode(ResultCode.UNWILLING_TO_PERFORM);
                appendErrorMessage(ERR_DELETE_SERVER_READONLY.get(
                                        String.valueOf(entryDN)));
                break deleteProcessing;
              }
          }
          switch (backend.getWritabilityMode())
          {
            case DISABLED:
              setResultCode(ResultCode.UNWILLING_TO_PERFORM);
              appendErrorMessage(ERR_DELETE_BACKEND_READONLY.get(
                                      String.valueOf(entryDN)));
              break deleteProcessing;
            case INTERNAL_ONLY:
              if (! (isInternalOperation() || isSynchronizationOperation()))
              {
                setResultCode(ResultCode.UNWILLING_TO_PERFORM);
                appendErrorMessage(ERR_DELETE_BACKEND_READONLY.get(
                                        String.valueOf(entryDN)));
                break deleteProcessing;
              }
          }
        }
        // The selected backend will have the responsibility of making sure that
        // the entry actually exists and does not have any children (or possibly
        // handling a subtree delete).  But we will need to check if there are
        // any subordinate backends that should stop us from attempting the
        // delete.
        Backend[] subBackends = backend.getSubordinateBackends();
        for (Backend b : subBackends)
        {
          DN[] baseDNs = b.getBaseDNs();
          for (DN dn : baseDNs)
          {
            if (dn.isDescendantOf(entryDN))
            {
              setResultCode(ResultCode.NOT_ALLOWED_ON_NONLEAF);
              appendErrorMessage(ERR_DELETE_HAS_SUB_BACKEND.get(
                                      String.valueOf(entryDN),
                                      String.valueOf(dn)));
              break deleteProcessing;
            }
          }
        }
        // Actually perform the delete.
        try
        {
          if (noOp)
          {
            setResultCode(ResultCode.NO_OPERATION);
            appendErrorMessage(INFO_DELETE_NOOP.get());
          }
          else
          {
            for (SynchronizationProvider provider :
                 DirectoryServer.getSynchronizationProviders())
            {
              try
              {
                SynchronizationProviderResult result =
                     provider.doPreOperation(this);
                if (! result.continueOperationProcessing())
                {
                  break deleteProcessing;
                }
              }
              catch (DirectoryException de)
              {
                if (debugEnabled())
                {
                  TRACER.debugCaught(DebugLogLevel.ERROR, de);
                }
                logError(ERR_DELETE_SYNCH_PREOP_FAILED.get(getConnectionID(),
                              getOperationID(), getExceptionMessage(de)));
                setResponseData(de);
                break deleteProcessing;
              }
            }
            backend.deleteEntry(entryDN, this);
          }
          processPreReadControl();
          if (! noOp)
          {
            setResultCode(ResultCode.SUCCESS);
          }
        }
        catch (DirectoryException de)
        {
          if (debugEnabled())
          {
            TRACER.debugCaught(DebugLogLevel.ERROR, de);
          }
          setResponseData(de);
          break deleteProcessing;
        }
        catch (CancelledOperationException coe)
        {
          if (debugEnabled())
          {
            TRACER.debugCaught(DebugLogLevel.ERROR, coe);
          }
          CancelResult cancelResult = coe.getCancelResult();
          setCancelResult(cancelResult);
          setResultCode(cancelResult.getResultCode());
          Message message = coe.getMessageObject();
          if ((message != null) && (message.length() > 0))
          {
            appendErrorMessage(message);
          }
          break deleteProcessing;
        }
      }
      finally
      {
        LockManager.unlock(entryDN, entryLock);
        for (SynchronizationProvider provider :
             DirectoryServer.getSynchronizationProviders())
        {
          try
          {
            provider.doPostOperation(this);
          }
          catch (DirectoryException de)
          {
            if (debugEnabled())
            {
              TRACER.debugCaught(DebugLogLevel.ERROR, de);
            }
            logError(ERR_DELETE_SYNCH_POSTOP_FAILED.get(getConnectionID(),
                          getOperationID(), getExceptionMessage(de)));
            setResponseData(de);
            break;
          }
        }
      }
    }
    // Indicate that it is now too late to attempt to cancel the operation.
    setCancelResult(CancelResult.TOO_LATE);
    // Invoke the post-operation or post-synchronization delete plugins.
    if (isSynchronizationOperation())
    {
      if (getResultCode() == ResultCode.SUCCESS)
      {
        pluginConfigManager.invokePostSynchronizationDeletePlugins(this);
      }
    }
    else if (! skipPostOperation)
    {
      PostOperationPluginResult postOperationResult =
           pluginConfigManager.invokePostOperationDeletePlugins(this);
      if (postOperationResult.connectionTerminated())
      {
        setResultCode(ResultCode.CANCELED);
        appendErrorMessage(ERR_CANCELED_BY_POSTOP_DISCONNECT.get());
        setProcessingStopTime();
        return;
      }
    }
    // Notify any change notification listeners that might be registered with
    // the server.
    if (getResultCode() == ResultCode.SUCCESS)
    {
      for (ChangeNotificationListener changeListener :
           DirectoryServer.getChangeNotificationListeners())
      {
        try
        {
          changeListener.handleDeleteOperation(this, entry);
        }
        catch (Exception e)
        {
          if (debugEnabled())
          {
            TRACER.debugCaught(DebugLogLevel.ERROR, e);
          }
          Message message = ERR_DELETE_ERROR_NOTIFYING_CHANGE_LISTENER.get(
              getExceptionMessage(e));
          logError(message);
        }
      }
    }
    // Stop the processing timer.
    setProcessingStopTime();
  }
  /**
   * Performs any request control processing needed for this operation.
   *
   * @throws  DirectoryException  If a problem occurs that should cause the
   *                              operation to fail.
   */
  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(entryDN, this, c))
        {
          skipPostOperation = true;
          throw new DirectoryException(ResultCode.INSUFFICIENT_ACCESS_RIGHTS,
                         ERR_CONTROL_INSUFFICIENT_ACCESS_RIGHTS.get(oid));
        }
        if (oid.equals(OID_LDAP_ASSERTION))
        {
          LDAPAssertionRequestControl assertControl;
          if (c instanceof LDAPAssertionRequestControl)
          {
            assertControl = (LDAPAssertionRequestControl) c;
          }
          else
          {
            try
            {
              assertControl = LDAPAssertionRequestControl.decodeControl(c);
              requestControls.set(i, assertControl);
            }
            catch (LDAPException le)
            {
              if (debugEnabled())
              {
                TRACER.debugCaught(DebugLogLevel.ERROR, le);
              }
              throw new DirectoryException(
                             ResultCode.valueOf(le.getResultCode()),
                             le.getMessageObject());
            }
          }
          try
          {
            // FIXME -- We need to determine whether the current user has
            //          permission to make this determination.
            SearchFilter filter = assertControl.getSearchFilter();
            if (! filter.matchesEntry(entry))
            {
              throw new DirectoryException(ResultCode.ASSERTION_FAILED,
                                           ERR_DELETE_ASSERTION_FAILED.get(
                                                String.valueOf(entryDN)));
            }
          }
          catch (DirectoryException de)
          {
            if (de.getResultCode() == ResultCode.ASSERTION_FAILED)
            {
              throw de;
            }
            if (debugEnabled())
            {
              TRACER.debugCaught(DebugLogLevel.ERROR, de);
            }
            throw new DirectoryException(ResultCode.PROTOCOL_ERROR,
                           ERR_DELETE_CANNOT_PROCESS_ASSERTION_FILTER.get(
                                String.valueOf(entryDN),
                                de.getMessageObject()));
          }
        }
        else if (oid.equals(OID_LDAP_NOOP_OPENLDAP_ASSIGNED))
        {
          noOp = true;
        }
        else if (oid.equals(OID_LDAP_READENTRY_PREREAD))
        {
          if (c instanceof LDAPPreReadRequestControl)
          {
            preReadRequest = (LDAPPreReadRequestControl) c;
          }
          else
          {
            try
            {
              preReadRequest = LDAPPreReadRequestControl.decodeControl(c);
              requestControls.set(i, preReadRequest);
            }
            catch (LDAPException le)
            {
              if (debugEnabled())
              {
                TRACER.debugCaught(DebugLogLevel.ERROR, le);
              }
              throw new DirectoryException(
                             ResultCode.valueOf(le.getResultCode()),
                             le.getMessageObject());
            }
          }
        }
        else if (oid.equals(OID_PROXIED_AUTH_V1))
        {
          // The requester must have the PROXIED_AUTH privilige in order to
          // be able to use this control.
          if (! clientConnection.hasPrivilege(Privilege.PROXIED_AUTH, this))
          {
            throw new DirectoryException(ResultCode.AUTHORIZATION_DENIED,
                           ERR_PROXYAUTH_INSUFFICIENT_PRIVILEGES.get());
          }
          ProxiedAuthV1Control proxyControl;
          if (c instanceof ProxiedAuthV1Control)
          {
            proxyControl = (ProxiedAuthV1Control) c;
          }
          else
          {
            try
            {
              proxyControl = ProxiedAuthV1Control.decodeControl(c);
            }
            catch (LDAPException le)
            {
              if (debugEnabled())
              {
                TRACER.debugCaught(DebugLogLevel.ERROR, le);
              }
              throw new DirectoryException(
                             ResultCode.valueOf(le.getResultCode()),
                             le.getMessageObject());
            }
          }
          Entry authorizationEntry = proxyControl.getAuthorizationEntry();
          setAuthorizationEntry(authorizationEntry);
          if (authorizationEntry == null)
          {
            setProxiedAuthorizationDN(DN.nullDN());
          }
          else
          {
            setProxiedAuthorizationDN(authorizationEntry.getDN());
          }
        }
        else if (oid.equals(OID_PROXIED_AUTH_V2))
        {
          // The requester must have the PROXIED_AUTH privilige in order to
          // be able to use this control.
          if (! clientConnection.hasPrivilege(Privilege.PROXIED_AUTH, this))
          {
            throw new DirectoryException(ResultCode.AUTHORIZATION_DENIED,
                           ERR_PROXYAUTH_INSUFFICIENT_PRIVILEGES.get());
          }
          ProxiedAuthV2Control proxyControl;
          if (c instanceof ProxiedAuthV2Control)
          {
            proxyControl = (ProxiedAuthV2Control) c;
          }
          else
          {
            try
            {
              proxyControl = ProxiedAuthV2Control.decodeControl(c);
            }
            catch (LDAPException le)
            {
              if (debugEnabled())
              {
                TRACER.debugCaught(DebugLogLevel.ERROR, le);
              }
              throw new DirectoryException(
                             ResultCode.valueOf(le.getResultCode()),
                             le.getMessageObject());
            }
          }
          Entry authorizationEntry = proxyControl.getAuthorizationEntry();
          setAuthorizationEntry(authorizationEntry);
          if (authorizationEntry == null)
          {
            setProxiedAuthorizationDN(DN.nullDN());
          }
          else
          {
            setProxiedAuthorizationDN(authorizationEntry.getDN());
          }
        }
        // NYI -- Add support for additional controls.
        else if (c.isCritical())
        {
          if ((backend == null) || (! backend.supportsControl(oid)))
          {
            throw new DirectoryException(
                           ResultCode.UNAVAILABLE_CRITICAL_EXTENSION,
                           ERR_DELETE_UNSUPPORTED_CRITICAL_CONTROL.get(
                                String.valueOf(entryDN), oid));
          }
        }
      }
    }
  }
  /**
   * Performs any processing needed for the LDAP pre-read control.
   */
  private void processPreReadControl()
  {
    if (preReadRequest != null)
    {
      Entry entryCopy = entry.duplicate(true);
      if (! preReadRequest.allowsAttribute(
                 DirectoryServer.getObjectClassAttributeType()))
      {
        entryCopy.removeAttribute(
             DirectoryServer.getObjectClassAttributeType());
      }
      if (! preReadRequest.returnAllUserAttributes())
      {
        Iterator<AttributeType> iterator =
             entryCopy.getUserAttributes().keySet().iterator();
        while (iterator.hasNext())
        {
          AttributeType attrType = iterator.next();
          if (! preReadRequest.allowsAttribute(attrType))
          {
            iterator.remove();
          }
        }
      }
      if (! preReadRequest.returnAllOperationalAttributes())
      {
        Iterator<AttributeType> iterator =
             entryCopy.getOperationalAttributes().keySet().iterator();
        while (iterator.hasNext())
        {
          AttributeType attrType = iterator.next();
          if (! preReadRequest.allowsAttribute(attrType))
          {
            iterator.remove();
          }
        }
      }
      // FIXME -- Check access controls on the entry to see if it should
      //          be returned or if any attributes need to be stripped
      //          out..
      SearchResultEntry searchEntry = new SearchResultEntry(entryCopy);
      LDAPPreReadResponseControl responseControl =
           new LDAPPreReadResponseControl(preReadRequest.getOID(),
                                          preReadRequest.isCritical(),
                                          searchEntry);
      addResponseControl(responseControl);
    }
  }
}
opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendModifyDNOperation.java
@@ -26,14 +26,71 @@
 */
package org.opends.server.workflowelement.localbackend;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.locks.Lock;
import org.opends.messages.Message;
import org.opends.messages.MessageBuilder;
import org.opends.server.api.Backend;
import org.opends.server.api.ChangeNotificationListener;
import org.opends.server.api.ClientConnection;
import org.opends.server.api.SynchronizationProvider;
import org.opends.server.api.plugin.PostOperationPluginResult;
import org.opends.server.api.plugin.PreOperationPluginResult;
import org.opends.server.controls.LDAPAssertionRequestControl;
import org.opends.server.controls.LDAPPostReadRequestControl;
import org.opends.server.controls.LDAPPostReadResponseControl;
import org.opends.server.controls.LDAPPreReadRequestControl;
import org.opends.server.controls.LDAPPreReadResponseControl;
import org.opends.server.controls.ProxiedAuthV1Control;
import org.opends.server.controls.ProxiedAuthV2Control;
import org.opends.server.core.AccessControlConfigManager;
import org.opends.server.core.DirectoryServer;
import org.opends.server.core.ModifyDNOperation;
import org.opends.server.core.ModifyDNOperationWrapper;
import org.opends.server.core.PluginConfigManager;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.protocols.asn1.ASN1OctetString;
import org.opends.server.types.Attribute;
import org.opends.server.types.AttributeType;
import org.opends.server.types.AttributeValue;
import org.opends.server.types.ByteString;
import org.opends.server.types.CancelledOperationException;
import org.opends.server.types.CancelResult;
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.LDAPException;
import org.opends.server.types.LockManager;
import org.opends.server.types.Modification;
import org.opends.server.types.ModificationType;
import org.opends.server.types.Privilege;
import org.opends.server.types.RDN;
import org.opends.server.types.ResultCode;
import org.opends.server.types.SearchFilter;
import org.opends.server.types.SearchResultEntry;
import org.opends.server.types.SynchronizationProviderResult;
import org.opends.server.types.operation.PostOperationModifyDNOperation;
import org.opends.server.types.operation.PostResponseModifyDNOperation;
import org.opends.server.types.operation.PreOperationModifyDNOperation;
import org.opends.server.types.operation.PostSynchronizationModifyDNOperation;
import static org.opends.messages.CoreMessages.*;
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 move an entry in a local backend
 * of the Directory Server.
@@ -45,12 +102,45 @@
             PostResponseModifyDNOperation,
             PostSynchronizationModifyDNOperation
{
  /**
   * The tracer object for the debug logger.
   */
  private static final DebugTracer TRACER = getTracer();
  // The backend in which the operation is to be processed.
  private Backend backend;
  // Indicates whether the no-op control was included in the request.
  private boolean noOp;
  // Indicates whether to skip post-operation plugin processing.
  private boolean skipPostOperation;
  // The client connection on which this operation was requested.
  private ClientConnection clientConnection;
  // The original DN of the entry.
  DN entryDN;
  // The current entry, before it is renamed.
  private Entry currentEntry;
  // The new entry, as it will appear after it has been renamed.
  private Entry newEntry;
  // The LDAP post-read request control, if present in the request.
  private LDAPPostReadRequestControl postReadRequest;
  // The LDAP pre-read request control, if present in the request.
  private LDAPPreReadRequestControl preReadRequest;
  // The new RDN for the entry.
  private RDN newRDN;
  /**
   * Creates a new operation that may be used to move an entry in a
   * local backend of the Directory Server.
@@ -63,6 +153,8 @@
    LocalBackendWorkflowElement.attachLocalOperation (operation, this);
  }
  /**
   * Retrieves the current entry, before it is renamed.  This will not be
   * available to pre-parse plugins or during the conflict resolution portion of
@@ -76,6 +168,8 @@
    return currentEntry;
  }
  /**
   * Retrieves the new entry, as it will appear after it is renamed.  This will
   * not be  available to pre-parse plugins or during the conflict resolution
@@ -89,31 +183,1246 @@
    return newEntry;
  }
  /**
   * Sets the current entry, before it is renamed.  This will not be
   * available to pre-parse plugins or during the conflict resolution portion of
   * the synchronization processing.
   *
   * @param entry  The current entry, or <CODE>null</CODE> if it is not yet
   *           available.
   */
  public final void setOriginalEntry(Entry entry)
  {
    this.currentEntry = entry;
  }
  /**
   * Sets the new entry, as it will appear after it is renamed.  This will
   * not be  available to pre-parse plugins or during the conflict resolution
   * portion of the synchronization processing.
   * Process this modify DN operation in a local backend.
   *
   * @param entry  The updated entry, or <CODE>null</CODE> if it is not yet
   *           available.
   * @param  backend  The backend in which the modify DN operation should be
   *                  processed.
   */
  public final void setUpdatedEntry(Entry entry)
  void processLocalModifyDN(Backend backend)
  {
    this.newEntry = entry;
    this.backend = backend;
    clientConnection = getClientConnection();
    // Get the plugin config manager that will be used for invoking plugins.
    PluginConfigManager pluginConfigManager =
         DirectoryServer.getPluginConfigManager();
    skipPostOperation = false;
    // Check for a request to cancel this operation.
    if (getCancelRequest() != null)
    {
      return;
    }
    // Create a labeled block of code that we can break out of if a problem is
    // detected.
modifyDNProcessing:
    {
      // Process the entry DN, newRDN, and newSuperior elements from their raw
      // forms as provided by the client to the forms required for the rest of
      // the modify DN processing.
      entryDN = getEntryDN();
      newRDN = getNewRDN();
      if (newRDN == null)
      {
        skipPostOperation = true;
        break modifyDNProcessing;
      }
      DN newSuperior = getNewSuperior();
      if ((newSuperior == null) &&
          (getRawNewSuperior() != null))
      {
        skipPostOperation = true;
        break modifyDNProcessing;
      }
      // Construct the new DN to use for the entry.
      DN parentDN;
      if (newSuperior == null)
      {
        parentDN = entryDN.getParentDNInSuffix();
      }
      else
      {
        parentDN = newSuperior;
      }
      if ((parentDN == null) || parentDN.isNullDN())
      {
        setResultCode(ResultCode.UNWILLING_TO_PERFORM);
        appendErrorMessage(ERR_MODDN_NO_PARENT.get(String.valueOf(entryDN)));
        break modifyDNProcessing;
      }
      DN newDN = parentDN.concat(newRDN);
      // Get the backend for the current entry, and the backend for the new
      // entry.  If either is null, or if they are different, then fail.
      Backend currentBackend = backend;
      if (currentBackend == null)
      {
        setResultCode(ResultCode.NO_SUCH_OBJECT);
        appendErrorMessage(ERR_MODDN_NO_BACKEND_FOR_CURRENT_ENTRY.get(
                                String.valueOf(entryDN)));
        break modifyDNProcessing;
      }
      Backend newBackend = DirectoryServer.getBackend(newDN);
      if (newBackend == null)
      {
        setResultCode(ResultCode.NO_SUCH_OBJECT);
        appendErrorMessage(ERR_MODDN_NO_BACKEND_FOR_NEW_ENTRY.get(
                                String.valueOf(entryDN),
                                String.valueOf(newDN)));
        break modifyDNProcessing;
      }
      else if (! currentBackend.equals(newBackend))
      {
        setResultCode(ResultCode.UNWILLING_TO_PERFORM);
        appendErrorMessage(ERR_MODDN_DIFFERENT_BACKENDS.get(
                                String.valueOf(entryDN),
                                String.valueOf(newDN)));
        break modifyDNProcessing;
  }
      // Check for a request to cancel this operation.
      if (getCancelRequest() != null)
      {
        return;
}
      // Acquire write locks for the current and new DN.
      Lock currentLock = null;
      for (int i=0; i < 3; i++)
      {
        currentLock = LockManager.lockWrite(entryDN);
        if (currentLock != null)
        {
          break;
        }
      }
      if (currentLock == null)
      {
        setResultCode(DirectoryServer.getServerErrorResultCode());
        appendErrorMessage(ERR_MODDN_CANNOT_LOCK_CURRENT_DN.get(
                                String.valueOf(entryDN)));
        skipPostOperation = true;
        break modifyDNProcessing;
      }
      Lock newLock = null;
      try
      {
        for (int i=0; i < 3; i++)
        {
          newLock = LockManager.lockWrite(newDN);
          if (newLock != null)
          {
            break;
          }
        }
      }
      catch (Exception e)
      {
        if (debugEnabled())
        {
          TRACER.debugCaught(DebugLogLevel.ERROR, e);
        }
        LockManager.unlock(entryDN, currentLock);
        if (newLock != null)
        {
          LockManager.unlock(newDN, newLock);
        }
        setResultCode(DirectoryServer.getServerErrorResultCode());
        appendErrorMessage(ERR_MODDN_EXCEPTION_LOCKING_NEW_DN.get(
                                String.valueOf(entryDN), String.valueOf(newDN),
                                getExceptionMessage(e)));
        skipPostOperation = true;
        break modifyDNProcessing;
      }
      if (newLock == null)
      {
        LockManager.unlock(entryDN, currentLock);
        setResultCode(DirectoryServer.getServerErrorResultCode());
        appendErrorMessage(ERR_MODDN_CANNOT_LOCK_NEW_DN.get(
                                String.valueOf(entryDN),
                                String.valueOf(newDN)));
        skipPostOperation = true;
        break modifyDNProcessing;
      }
      try
      {
        // Check for a request to cancel this operation.
        if (getCancelRequest() != null)
        {
          return;
        }
        // Get the current entry from the appropriate backend.  If it doesn't
        // exist, then fail.
        try
        {
          currentEntry = currentBackend.getEntry(entryDN);
        }
        catch (DirectoryException de)
        {
          if (debugEnabled())
          {
            TRACER.debugCaught(DebugLogLevel.ERROR, de);
          }
          setResponseData(de);
          break modifyDNProcessing;
        }
        if (getOriginalEntry() == null)
        {
          // See if one of the entry's ancestors exists.
          parentDN = entryDN.getParentDNInSuffix();
          while (parentDN != null)
          {
            try
            {
              if (DirectoryServer.entryExists(parentDN))
              {
                setMatchedDN(parentDN);
                break;
              }
            }
            catch (Exception e)
            {
              if (debugEnabled())
              {
                TRACER.debugCaught(DebugLogLevel.ERROR, e);
              }
              break;
            }
            parentDN = parentDN.getParentDNInSuffix();
          }
          setResultCode(ResultCode.NO_SUCH_OBJECT);
          appendErrorMessage(ERR_MODDN_NO_CURRENT_ENTRY.get(
                                  String.valueOf(entryDN)));
          break modifyDNProcessing;
        }
        // Invoke any conflict resolution processing that might be needed by the
        // synchronization provider.
        for (SynchronizationProvider provider :
             DirectoryServer.getSynchronizationProviders())
        {
          try
          {
            SynchronizationProviderResult result =
                 provider.handleConflictResolution(this);
            if (! result.continueOperationProcessing())
            {
              break modifyDNProcessing;
            }
          }
          catch (DirectoryException de)
          {
            if (debugEnabled())
            {
              TRACER.debugCaught(DebugLogLevel.ERROR, de);
            }
            logError(ERR_MODDN_SYNCH_CONFLICT_RESOLUTION_FAILED.get(
                          getConnectionID(), getOperationID(),
                          getExceptionMessage(de)));
            setResponseData(de);
            break modifyDNProcessing;
          }
        }
        // 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 modifyDNProcessing;
        }
        // Check to see if the client has permission to perform the
        // modify DN.
        // FIXME: for now assume that this will check all permission
        // pertinent to the operation. This includes proxy authorization
        // and any other controls specified.
        // FIXME: earlier checks to see if the entry or new superior
        // already exists may have already exposed sensitive information
        // to the client.
        if (! AccessControlConfigManager.getInstance().
                   getAccessControlHandler().isAllowed(this))
        {
          setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
          appendErrorMessage(ERR_MODDN_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS.get(
                                  String.valueOf(entryDN)));
          skipPostOperation = true;
          break modifyDNProcessing;
        }
        // Duplicate the entry and set its new DN.  Also, create an empty list
        // to hold the attribute-level modifications.
        newEntry = currentEntry.duplicate(false);
        newEntry.setDN(newDN);
        // init the modifications
        addModification(null);
        List<Modification> modifications = this.getModifications();
        // Apply any changes to the entry based on the change in its RDN.  Also,
        // perform schema checking on the updated entry.
        try
        {
          applyRDNChanges(modifications);
        }
        catch (DirectoryException de)
        {
          if (debugEnabled())
          {
            TRACER.debugCaught(DebugLogLevel.ERROR, de);
          }
          setResponseData(de);
          break modifyDNProcessing;
        }
        // Check for a request to cancel this operation.
        if (getCancelRequest() != null)
        {
          return;
        }
        // Get a count of the current number of modifications.  The
        // pre-operation plugins may alter this list, and we need to be able to
        // identify which changes were made after they're done.
        int modCount = modifications.size();
        // If the operation is not a synchronization operation,
        // Invoke the pre-operation modify DN plugins.
        if (! isSynchronizationOperation())
        {
          PreOperationPluginResult preOpResult =
            pluginConfigManager.invokePreOperationModifyDNPlugins(this);
          if (preOpResult.connectionTerminated())
          {
            // There's no point in continuing with anything.  Log the request
            // and result and return.
            setResultCode(ResultCode.CANCELED);
            appendErrorMessage(ERR_CANCELED_BY_PREOP_DISCONNECT.get());
            return;
          }
          else if (preOpResult.sendResponseImmediately())
          {
            skipPostOperation = true;
            break modifyDNProcessing;
          }
          else if (preOpResult.skipCoreProcessing())
          {
            skipPostOperation = false;
            break modifyDNProcessing;
          }
        }
        // Check to see if any of the pre-operation plugins made any changes to
        // the entry.  If so, then apply them.
        if (modifications.size() > modCount)
        {
          try
          {
            applyPreOpModifications(modifications, modCount);
          }
          catch (DirectoryException de)
          {
            if (debugEnabled())
            {
              TRACER.debugCaught(DebugLogLevel.ERROR, de);
            }
            setResponseData(de);
            break modifyDNProcessing;
          }
        }
        // Check for a request to cancel this operation.
        if (getCancelRequest() != null)
        {
          return;
        }
        // Actually perform the modify DN operation.
        // This should include taking
        // care of any synchronization that might be needed.
        try
        {
          // If it is not a private backend, then check to see if the server or
          // backend is operating in read-only mode.
          if (! currentBackend.isPrivateBackend())
          {
            switch (DirectoryServer.getWritabilityMode())
            {
              case DISABLED:
                setResultCode(ResultCode.UNWILLING_TO_PERFORM);
                appendErrorMessage(ERR_MODDN_SERVER_READONLY.get(
                                        String.valueOf(entryDN)));
                break modifyDNProcessing;
              case INTERNAL_ONLY:
                if (! (isInternalOperation() || isSynchronizationOperation()))
                {
                  setResultCode(ResultCode.UNWILLING_TO_PERFORM);
                  appendErrorMessage(ERR_MODDN_SERVER_READONLY.get(
                                          String.valueOf(entryDN)));
                  break modifyDNProcessing;
                }
            }
            switch (currentBackend.getWritabilityMode())
            {
              case DISABLED:
                setResultCode(ResultCode.UNWILLING_TO_PERFORM);
                appendErrorMessage(ERR_MODDN_BACKEND_READONLY.get(
                                        String.valueOf(entryDN)));
                break modifyDNProcessing;
              case INTERNAL_ONLY:
                if (! (isInternalOperation() || isSynchronizationOperation()))
                {
                  setResultCode(ResultCode.UNWILLING_TO_PERFORM);
                  appendErrorMessage(ERR_MODDN_BACKEND_READONLY.get(
                                          String.valueOf(entryDN)));
                  break modifyDNProcessing;
                }
            }
          }
          if (noOp)
          {
            appendErrorMessage(INFO_MODDN_NOOP.get());
            setResultCode(ResultCode.NO_OPERATION);
          }
          else
          {
            for (SynchronizationProvider provider :
                 DirectoryServer.getSynchronizationProviders())
            {
              try
              {
                SynchronizationProviderResult result =
                     provider.doPreOperation(this);
                if (! result.continueOperationProcessing())
                {
                  break modifyDNProcessing;
                }
              }
              catch (DirectoryException de)
              {
                if (debugEnabled())
                {
                  TRACER.debugCaught(DebugLogLevel.ERROR, de);
                }
                logError(ERR_MODDN_SYNCH_PREOP_FAILED.get(getConnectionID(),
                              getOperationID(), getExceptionMessage(de)));
                setResponseData(de);
                break modifyDNProcessing;
              }
            }
            currentBackend.renameEntry(entryDN, newEntry, this);
          }
          // Attach the pre-read and/or post-read controls to the response if
          // appropriate.
          processReadEntryControls();
          if (! noOp)
          {
            setResultCode(ResultCode.SUCCESS);
          }
        }
        catch (DirectoryException de)
        {
          if (debugEnabled())
          {
            TRACER.debugCaught(DebugLogLevel.ERROR, de);
          }
          setResponseData(de);
          break modifyDNProcessing;
        }
        catch (CancelledOperationException coe)
        {
          if (debugEnabled())
          {
            TRACER.debugCaught(DebugLogLevel.ERROR, coe);
          }
          CancelResult cancelResult = coe.getCancelResult();
          setCancelResult(cancelResult);
          setResultCode(cancelResult.getResultCode());
          Message message = coe.getMessageObject();
          if ((message != null) && (message.length() > 0))
          {
            appendErrorMessage(message);
          }
          break modifyDNProcessing;
        }
      }
      finally
      {
        LockManager.unlock(entryDN, currentLock);
        LockManager.unlock(newDN, newLock);
        for (SynchronizationProvider provider :
             DirectoryServer.getSynchronizationProviders())
        {
          try
          {
            provider.doPostOperation(this);
          }
          catch (DirectoryException de)
          {
            if (debugEnabled())
            {
              TRACER.debugCaught(DebugLogLevel.ERROR, de);
            }
            logError(ERR_MODDN_SYNCH_POSTOP_FAILED.get(getConnectionID(),
                          getOperationID(), getExceptionMessage(de)));
            setResponseData(de);
            break;
          }
        }
      }
    }
    // Indicate that it is now too late to attempt to cancel the operation.
    setCancelResult(CancelResult.TOO_LATE);
    // Invoke the post-operation or post-synchronization modify DN plugins.
    if (isSynchronizationOperation())
    {
      if (getResultCode() == ResultCode.SUCCESS)
      {
        pluginConfigManager.invokePostSynchronizationModifyDNPlugins(this);
      }
    }
    else if (! skipPostOperation)
    {
      PostOperationPluginResult postOperationResult =
           pluginConfigManager.invokePostOperationModifyDNPlugins(this);
      if (postOperationResult.connectionTerminated())
      {
        setResultCode(ResultCode.CANCELED);
        appendErrorMessage(ERR_CANCELED_BY_POSTOP_DISCONNECT.get());
        return;
      }
    }
    // Notify any change notification listeners that might be registered with
    // the server.
    if (getResultCode() == ResultCode.SUCCESS)
    {
      for (ChangeNotificationListener changeListener :
           DirectoryServer.getChangeNotificationListeners())
      {
        try
        {
          changeListener.handleModifyDNOperation(this, currentEntry, newEntry);
        }
        catch (Exception e)
        {
          if (debugEnabled())
          {
            TRACER.debugCaught(DebugLogLevel.ERROR, e);
          }
          Message message = ERR_MODDN_ERROR_NOTIFYING_CHANGE_LISTENER.get(
              getExceptionMessage(e));
          logError(message);
        }
      }
    }
  }
  /**
   * Processes the set of controls included in the request.
   *
   * @throws  DirectoryException  If a problem occurs that should cause the
   *                              modify DN operation to fail.
   */
  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(entryDN,  this, c))
        {
          skipPostOperation = true;
          throw new DirectoryException(ResultCode.INSUFFICIENT_ACCESS_RIGHTS,
                         ERR_CONTROL_INSUFFICIENT_ACCESS_RIGHTS.get(oid));
        }
        if (oid.equals(OID_LDAP_ASSERTION))
        {
          LDAPAssertionRequestControl assertControl;
          if (c instanceof LDAPAssertionRequestControl)
          {
            assertControl = (LDAPAssertionRequestControl) c;
          }
          else
          {
            try
            {
              assertControl = LDAPAssertionRequestControl.decodeControl(c);
              requestControls.set(i, assertControl);
            }
            catch (LDAPException le)
            {
              if (debugEnabled())
              {
                TRACER.debugCaught(DebugLogLevel.ERROR, le);
              }
              throw new DirectoryException(
                             ResultCode.valueOf(le.getResultCode()),
                             le.getMessageObject());
            }
          }
          try
          {
            // FIXME -- We need to determine whether the current user has
            //          permission to make this determination.
            SearchFilter filter = assertControl.getSearchFilter();
            if (! filter.matchesEntry(currentEntry))
            {
              throw new DirectoryException(ResultCode.ASSERTION_FAILED,
                                           ERR_MODDN_ASSERTION_FAILED.get(
                                                String.valueOf(entryDN)));
            }
          }
          catch (DirectoryException de)
          {
            if (de.getResultCode() == ResultCode.ASSERTION_FAILED)
            {
              throw de;
            }
            if (debugEnabled())
            {
              TRACER.debugCaught(DebugLogLevel.ERROR, de);
            }
            throw new DirectoryException(ResultCode.PROTOCOL_ERROR,
                           ERR_MODDN_CANNOT_PROCESS_ASSERTION_FILTER.get(
                                String.valueOf(entryDN),
                                de.getMessageObject()));
          }
        }
        else if (oid.equals(OID_LDAP_NOOP_OPENLDAP_ASSIGNED))
        {
          noOp = true;
        }
        else if (oid.equals(OID_LDAP_READENTRY_PREREAD))
        {
          if (c instanceof LDAPPreReadRequestControl)
          {
            preReadRequest = (LDAPPreReadRequestControl) c;
          }
          else
          {
            try
            {
              preReadRequest = LDAPPreReadRequestControl.decodeControl(c);
              requestControls.set(i, preReadRequest);
            }
            catch (LDAPException le)
            {
              if (debugEnabled())
              {
                TRACER.debugCaught(DebugLogLevel.ERROR, le);
              }
              throw new DirectoryException(
                             ResultCode.valueOf(le.getResultCode()),
                             le.getMessageObject());
            }
          }
        }
        else if (oid.equals(OID_LDAP_READENTRY_POSTREAD))
        {
          if (c instanceof LDAPPostReadRequestControl)
          {
            postReadRequest = (LDAPPostReadRequestControl) c;
          }
          else
          {
            try
            {
              postReadRequest = LDAPPostReadRequestControl.decodeControl(c);
              requestControls.set(i, postReadRequest);
            }
            catch (LDAPException le)
            {
              if (debugEnabled())
              {
                TRACER.debugCaught(DebugLogLevel.ERROR, le);
              }
              throw new DirectoryException(
                             ResultCode.valueOf(le.getResultCode()),
                             le.getMessageObject());
            }
          }
        }
        else if (oid.equals(OID_PROXIED_AUTH_V1))
        {
          // The requester must have the PROXIED_AUTH privilige in order to
          // be able to use this control.
          if (! clientConnection.hasPrivilege(Privilege.PROXIED_AUTH, this))
          {
            throw new DirectoryException(ResultCode.AUTHORIZATION_DENIED,
                           ERR_PROXYAUTH_INSUFFICIENT_PRIVILEGES.get());
          }
          ProxiedAuthV1Control proxyControl;
          if (c instanceof ProxiedAuthV1Control)
          {
            proxyControl = (ProxiedAuthV1Control) c;
          }
          else
          {
            try
            {
              proxyControl = ProxiedAuthV1Control.decodeControl(c);
            }
            catch (LDAPException le)
            {
              if (debugEnabled())
              {
                TRACER.debugCaught(DebugLogLevel.ERROR, le);
              }
              throw new DirectoryException(
                             ResultCode.valueOf(le.getResultCode()),
                             le.getMessageObject());
            }
          }
          Entry authorizationEntry = proxyControl.getAuthorizationEntry();
          setAuthorizationEntry(authorizationEntry);
          if (authorizationEntry == null)
          {
            setProxiedAuthorizationDN(DN.nullDN());
          }
          else
          {
            setProxiedAuthorizationDN(authorizationEntry.getDN());
          }
        }
        else if (oid.equals(OID_PROXIED_AUTH_V2))
        {
          // The requester must have the PROXIED_AUTH privilige in order to
          // be able to use this control.
          if (! clientConnection.hasPrivilege(Privilege.PROXIED_AUTH, this))
          {
            throw new DirectoryException(ResultCode.AUTHORIZATION_DENIED,
                           ERR_PROXYAUTH_INSUFFICIENT_PRIVILEGES.get());
          }
          ProxiedAuthV2Control proxyControl;
          if (c instanceof ProxiedAuthV2Control)
          {
            proxyControl = (ProxiedAuthV2Control) c;
          }
          else
          {
            try
            {
              proxyControl = ProxiedAuthV2Control.decodeControl(c);
            }
            catch (LDAPException le)
            {
              if (debugEnabled())
              {
                TRACER.debugCaught(DebugLogLevel.ERROR, le);
              }
              throw new DirectoryException(
                             ResultCode.valueOf(le.getResultCode()),
                             le.getMessageObject());
            }
          }
          Entry authorizationEntry = proxyControl.getAuthorizationEntry();
          setAuthorizationEntry(authorizationEntry);
          if (authorizationEntry == null)
          {
            setProxiedAuthorizationDN(DN.nullDN());
          }
          else
          {
            setProxiedAuthorizationDN(authorizationEntry.getDN());
          }
        }
        // NYI -- Add support for additional controls.
        else if (c.isCritical())
        {
          if ((backend == null) || (! backend.supportsControl(oid)))
          {
            throw new DirectoryException(
                           ResultCode.UNAVAILABLE_CRITICAL_EXTENSION,
                           ERR_MODDN_UNSUPPORTED_CRITICAL_CONTROL.get(
                                String.valueOf(entryDN), oid));
          }
        }
      }
    }
  }
  /**
   * Updates the entry so that its attributes are changed to reflect the changes
   * to the RDN.  This also performs schema checking on the updated entry.
   *
   * @param  modifications  A list to hold the modifications made to the entry.
   *
   * @throws  DirectoryException  If a problem occurs that should cause the
   *                              modify DN operation to fail.
   */
  private void applyRDNChanges(List<Modification> modifications)
          throws DirectoryException
  {
    // If we should delete the old RDN values from the entry, then do so.
    if (deleteOldRDN())
    {
      RDN currentRDN = entryDN.getRDN();
      int numValues  = currentRDN.getNumValues();
      for (int i=0; i < numValues; i++)
      {
        LinkedHashSet<AttributeValue> valueSet =
             new LinkedHashSet<AttributeValue>(1);
        valueSet.add(currentRDN.getAttributeValue(i));
        Attribute a = new Attribute(currentRDN.getAttributeType(i),
                                    currentRDN.getAttributeName(i), valueSet);
        // If the associated attribute type is marked NO-USER-MODIFICATION, then
        // refuse the update.
        if (a.getAttributeType().isNoUserModification())
        {
          if (! (isInternalOperation() || isSynchronizationOperation()))
          {
            throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
                           ERR_MODDN_OLD_RDN_ATTR_IS_NO_USER_MOD.get(
                                String.valueOf(entryDN), a.getName()));
          }
        }
        LinkedList<AttributeValue> missingValues =
             new LinkedList<AttributeValue>();
        newEntry.removeAttribute(a, missingValues);
        if (missingValues.isEmpty())
        {
          modifications.add(new Modification(ModificationType.DELETE, a));
        }
      }
    }
    // Add the new RDN values to the entry.
    int newRDNValues = newRDN.getNumValues();
    for (int i=0; i < newRDNValues; i++)
    {
      LinkedHashSet<AttributeValue> valueSet =
           new LinkedHashSet<AttributeValue>(1);
      valueSet.add(newRDN.getAttributeValue(i));
      Attribute a = new Attribute(newRDN.getAttributeType(i),
                                  newRDN.getAttributeName(i), valueSet);
      LinkedList<AttributeValue> duplicateValues =
           new LinkedList<AttributeValue>();
      newEntry.addAttribute(a, duplicateValues);
      if (duplicateValues.isEmpty())
      {
        // If the associated attribute type is marked NO-USER-MODIFICATION, then
        // refuse the update.
        if (a.getAttributeType().isNoUserModification())
        {
          if (! (isInternalOperation() || isSynchronizationOperation()))
          {
            throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
                           ERR_MODDN_NEW_RDN_ATTR_IS_NO_USER_MOD.get(
                                String.valueOf(entryDN), a.getName()));
          }
        }
        else
        {
          modifications.add(new Modification(ModificationType.ADD, a));
        }
      }
    }
    // If the server is configured to check the schema and the operation is not
    // a synchronization operation, make sure that the resulting entry is valid
    // as per the server schema.
    if ((DirectoryServer.checkSchema()) && (! isSynchronizationOperation()))
    {
      MessageBuilder invalidReason = new MessageBuilder();
      if (! newEntry.conformsToSchema(null, false, true, true,
                                      invalidReason))
      {
        throw new DirectoryException(ResultCode.OBJECTCLASS_VIOLATION,
                                     ERR_MODDN_VIOLATES_SCHEMA.get(
                                          String.valueOf(entryDN),
                                          String.valueOf(invalidReason)));
      }
      for (int i=0; i < newRDNValues; i++)
      {
        AttributeType at = newRDN.getAttributeType(i);
        if (at.isObsolete())
        {
          throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
                                       ERR_MODDN_NEWRDN_ATTR_IS_OBSOLETE.get(
                                            String.valueOf(entryDN),
                                            at.getNameOrOID()));
        }
      }
    }
  }
  /**
   * Applies any modifications performed during pre-operation plugin processing.
   * This also performs schema checking for the updated entry.
   *
   * @param  modifications  A list containing the modifications made to the
   *                        entry.
   * @param  startPos       The position in the list at which the pre-operation
   *                        modifications start.
   *
   * @throws  DirectoryException  If a problem occurs that should cause the
   *                              modify DN operation to fail.
   */
  private void applyPreOpModifications(List<Modification> modifications,
                                       int startPos)
          throws DirectoryException
  {
    for (int i=startPos; i < modifications.size(); i++)
    {
      Modification m = modifications.get(i);
      Attribute    a = m.getAttribute();
      switch (m.getModificationType())
      {
        case ADD:
          LinkedList<AttributeValue> duplicateValues =
               new LinkedList<AttributeValue>();
          newEntry.addAttribute(a, duplicateValues);
          break;
        case DELETE:
          LinkedList<AttributeValue> missingValues =
               new LinkedList<AttributeValue>();
          newEntry.removeAttribute(a, missingValues);
          break;
        case REPLACE:
          duplicateValues = new LinkedList<AttributeValue>();
          newEntry.removeAttribute(a.getAttributeType(), a.getOptions());
          newEntry.addAttribute(a, duplicateValues);
          break;
        case INCREMENT:
          List<Attribute> attrList =
               newEntry.getAttribute(a.getAttributeType(),
                                     a.getOptions());
          if ((attrList == null) || attrList.isEmpty())
          {
            throw new DirectoryException(ResultCode.NO_SUCH_ATTRIBUTE,
                                         ERR_MODDN_PREOP_INCREMENT_NO_ATTR.get(
                                              String.valueOf(entryDN),
                                              a.getName()));
          }
          else if (attrList.size() > 1)
          {
            throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
                           ERR_MODDN_PREOP_INCREMENT_MULTIPLE_VALUES.get(
                                String.valueOf(entryDN), a.getName()));
          }
          LinkedHashSet<AttributeValue> values =
               attrList.get(0).getValues();
          if ((values == null) || values.isEmpty())
          {
            throw new DirectoryException(ResultCode.NO_SUCH_ATTRIBUTE,
                                         ERR_MODDN_PREOP_INCREMENT_NO_ATTR.get(
                                              String.valueOf(entryDN),
                                              a.getName()));
          }
          else if (values.size() > 1)
          {
            throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
                           ERR_MODDN_PREOP_INCREMENT_MULTIPLE_VALUES.get(
                                String.valueOf(entryDN), a.getName()));
          }
          long currentLongValue;
          try
          {
            AttributeValue v = values.iterator().next();
            currentLongValue = Long.parseLong(v.getStringValue());
          }
          catch (Exception e)
          {
            if (debugEnabled())
            {
              TRACER.debugCaught(DebugLogLevel.ERROR, e);
            }
            throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
                           ERR_MODDN_PREOP_INCREMENT_VALUE_NOT_INTEGER.get(
                                String.valueOf(entryDN), a.getName()));
          }
          LinkedHashSet<AttributeValue> newValues = a.getValues();
          if ((newValues == null) || newValues.isEmpty())
          {
            throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
                           ERR_MODDN_PREOP_INCREMENT_NO_AMOUNT.get(
                                String.valueOf(entryDN), a.getName()));
          }
          else if (newValues.size() > 1)
          {
            throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
                           ERR_MODDN_PREOP_INCREMENT_MULTIPLE_AMOUNTS.get(
                                String.valueOf(entryDN), a.getName()));
          }
          long incrementAmount;
          try
          {
            AttributeValue v = values.iterator().next();
            incrementAmount = Long.parseLong(v.getStringValue());
          }
          catch (Exception e)
          {
            if (debugEnabled())
            {
              TRACER.debugCaught(DebugLogLevel.ERROR, e);
            }
            throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
                           ERR_MODDN_PREOP_INCREMENT_AMOUNT_NOT_INTEGER.get(
                                String.valueOf(entryDN), a.getName()));
          }
          long newLongValue = currentLongValue + incrementAmount;
          ByteString newValueOS =
               new ASN1OctetString(String.valueOf(newLongValue));
          newValues = new LinkedHashSet<AttributeValue>(1);
          newValues.add(new AttributeValue(a.getAttributeType(),
                                           newValueOS));
          List<Attribute> newAttrList = new ArrayList<Attribute>(1);
          newAttrList.add(new Attribute(a.getAttributeType(),
                                        a.getName(), newValues));
          newEntry.putAttribute(a.getAttributeType(), newAttrList);
          break;
      }
    }
    // Make sure that the updated entry still conforms to the server
    // schema.
    if (DirectoryServer.checkSchema())
    {
      MessageBuilder invalidReason = new MessageBuilder();
      if (! newEntry.conformsToSchema(null, false, true, true,
                                      invalidReason))
      {
        throw new DirectoryException(ResultCode.OBJECTCLASS_VIOLATION,
                                     ERR_MODDN_PREOP_VIOLATES_SCHEMA.get(
                                          String.valueOf(entryDN),
                                          String.valueOf(invalidReason)));
      }
    }
  }
  /**
   * Performs any necessary processing to create the pre-read and/or post-read
   * response controls and attach them to the response.
   */
  private void processReadEntryControls()
  {
    if (preReadRequest != null)
    {
      Entry entry = currentEntry.duplicate(true);
      if (! preReadRequest.allowsAttribute(
                 DirectoryServer.getObjectClassAttributeType()))
      {
        entry.removeAttribute(
             DirectoryServer.getObjectClassAttributeType());
      }
      if (! preReadRequest.returnAllUserAttributes())
      {
        Iterator<AttributeType> iterator =
             entry.getUserAttributes().keySet().iterator();
        while (iterator.hasNext())
        {
          AttributeType attrType = iterator.next();
          if (! preReadRequest.allowsAttribute(attrType))
          {
            iterator.remove();
          }
        }
      }
      if (! preReadRequest.returnAllOperationalAttributes())
      {
        Iterator<AttributeType> iterator =
             entry.getOperationalAttributes().keySet().iterator();
        while (iterator.hasNext())
        {
          AttributeType attrType = iterator.next();
          if (! preReadRequest.allowsAttribute(attrType))
          {
            iterator.remove();
          }
        }
      }
      // FIXME -- Check access controls on the entry to see if it should
      //          be returned or if any attributes need to be stripped
      //          out..
      SearchResultEntry searchEntry = new SearchResultEntry(entry);
      LDAPPreReadResponseControl responseControl =
           new LDAPPreReadResponseControl(preReadRequest.getOID(),
                                          preReadRequest.isCritical(),
                                          searchEntry);
      addResponseControl(responseControl);
    }
    if (postReadRequest != null)
    {
      Entry entry = newEntry.duplicate(true);
      if (! postReadRequest.allowsAttribute(
                 DirectoryServer.getObjectClassAttributeType()))
      {
        entry.removeAttribute(
             DirectoryServer.getObjectClassAttributeType());
      }
      if (! postReadRequest.returnAllUserAttributes())
      {
        Iterator<AttributeType> iterator =
             entry.getUserAttributes().keySet().iterator();
        while (iterator.hasNext())
        {
          AttributeType attrType = iterator.next();
          if (! postReadRequest.allowsAttribute(attrType))
          {
            iterator.remove();
          }
        }
      }
      if (! postReadRequest.returnAllOperationalAttributes())
      {
        Iterator<AttributeType> iterator =
             entry.getOperationalAttributes().keySet().iterator();
        while (iterator.hasNext())
        {
          AttributeType attrType = iterator.next();
          if (! postReadRequest.allowsAttribute(attrType))
          {
            iterator.remove();
          }
        }
      }
      // FIXME -- Check access controls on the entry to see if it should
      //          be returned or if any attributes need to be stripped
      //          out..
      SearchResultEntry searchEntry = new SearchResultEntry(entry);
      LDAPPostReadResponseControl responseControl =
           new LDAPPostReadResponseControl(postReadRequest.getOID(),
                                           postReadRequest.isCritical(),
                                           searchEntry);
      addResponseControl(responseControl);
    }
  }
}
opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendModifyOperation.java
@@ -26,41 +26,172 @@
 */
package org.opends.server.workflowelement.localbackend;
import java.util.List;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.locks.Lock;
import org.opends.messages.Message;
import org.opends.messages.MessageBuilder;
import org.opends.server.api.AttributeSyntax;
import org.opends.server.api.Backend;
import org.opends.server.api.ChangeNotificationListener;
import org.opends.server.api.ClientConnection;
import org.opends.server.api.PasswordStorageScheme;
import org.opends.server.api.SynchronizationProvider;
import org.opends.server.api.plugin.PostOperationPluginResult;
import org.opends.server.api.plugin.PreOperationPluginResult;
import org.opends.server.controls.LDAPAssertionRequestControl;
import org.opends.server.controls.LDAPPostReadRequestControl;
import org.opends.server.controls.LDAPPostReadResponseControl;
import org.opends.server.controls.LDAPPreReadRequestControl;
import org.opends.server.controls.LDAPPreReadResponseControl;
import org.opends.server.controls.PasswordPolicyErrorType;
import org.opends.server.controls.PasswordPolicyResponseControl;
import org.opends.server.controls.ProxiedAuthV1Control;
import org.opends.server.controls.ProxiedAuthV2Control;
import org.opends.server.core.AccessControlConfigManager;
import org.opends.server.core.DirectoryServer;
import org.opends.server.core.ModifyOperation;
import org.opends.server.core.ModifyOperationWrapper;
import org.opends.server.core.PasswordPolicyState;
import org.opends.server.core.PluginConfigManager;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.protocols.asn1.ASN1OctetString;
import org.opends.server.schema.AuthPasswordSyntax;
import org.opends.server.schema.BooleanSyntax;
import org.opends.server.schema.UserPasswordSyntax;
import org.opends.server.types.AccountStatusNotification;
import org.opends.server.types.AccountStatusNotificationType;
import org.opends.server.types.AcceptRejectWarn;
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.CancelledOperationException;
import org.opends.server.types.CancelResult;
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.LDAPException;
import org.opends.server.types.LockManager;
import org.opends.server.types.Modification;
import org.opends.server.types.ModificationType;
import org.opends.server.types.Privilege;
import org.opends.server.types.RDN;
import org.opends.server.types.ResultCode;
import org.opends.server.types.SearchFilter;
import org.opends.server.types.SearchResultEntry;
import org.opends.server.types.SynchronizationProviderResult;
import org.opends.server.types.operation.PostOperationModifyOperation;
import org.opends.server.types.operation.PostResponseModifyOperation;
import org.opends.server.types.operation.PreOperationModifyOperation;
import org.opends.server.types.operation.PostSynchronizationModifyOperation;
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 modify an entry in a local backend
 * of the Directory Server.
 */
public class LocalBackendModifyOperation extends ModifyOperationWrapper
  implements PreOperationModifyOperation,
             PostOperationModifyOperation,
public class LocalBackendModifyOperation
       extends ModifyOperationWrapper
       implements PreOperationModifyOperation, PostOperationModifyOperation,
             PostResponseModifyOperation,
             PostSynchronizationModifyOperation
{
  /**
   * The tracer object for the debug logger.
   */
  private static final DebugTracer TRACER = getTracer();
  // The backend in which the target entry exists.
  private Backend backend;
  // Indicates whether the request included the user's current password.
  private boolean currentPasswordProvided;
  // Indicates whether the user's account has been enabled or disabled by this
  // modify operation.
  private boolean enabledStateChanged;
  // Indicates whether the user's account is currently enabled.
  private boolean isEnabled;
  // Indicates whether the request included the LDAP no-op control.
  private boolean noOp;
  // Indicates whether this modify operation includees a password change.
  private boolean passwordChanged;
  // Indicates whether the request included the password policy request control.
  private boolean pwPolicyControlRequested;
  // Indicates whether the password change is a self-change.
  private boolean selfChange;
  // Indicates whether to skip post-operation plugin processing.
  private boolean skipPostOperation;
  // Indicates whether the user's account was locked before this change.
  private boolean wasLocked;
  // The client connection associated with this operation.
  private ClientConnection clientConnection;
  // The DN of the entry to modify.
  private DN entryDN;
  // The current entry, before any changes are applied.
  private Entry currentEntry = null;
  // The modified entry that will be stored in the backend.
  private Entry modifiedEntry = null;
  // The number of passwords contained in the modify operation.
  private int numPasswords;
  // The post-read request control, if present.
  private LDAPPostReadRequestControl postReadRequest;
  // The pre-read request control, if present.
  private LDAPPreReadRequestControl preReadRequest;
  // The set of clear-text current passwords (if any were provided).
  private List<AttributeValue> currentPasswords = null;
  // The set of clear-text new passwords (if any were provided).
  private List<AttributeValue> newPasswords = null;
  // The set of modifications contained in this request.
  private List<Modification> modifications;
  // The password policy error type for this operation.
  private PasswordPolicyErrorType pwpErrorType;
  // The password policy state for this modify operation.
  private PasswordPolicyState pwPolicyState;
  /**
   * Creates a new operation that may be used to modify an entry in a
   * local backend of the Directory Server.
@@ -73,6 +204,8 @@
    LocalBackendWorkflowElement.attachLocalOperation (modify, this);
  }
  /**
   * Retrieves the current entry before any modifications are applied.  This
   * will not be available to pre-parse plugins.
@@ -85,6 +218,8 @@
    return currentEntry;
  }
  /**
   * Retrieves the set of clear-text current passwords for the user, if
   * available.  This will only be available if the modify operation contains
@@ -101,6 +236,8 @@
    return currentPasswords;
  }
  /**
   * Retrieves the modified entry that is to be written to the backend.  This
   * will be available to pre-operation plugins, and if such a plugin does make
@@ -115,6 +252,8 @@
    return modifiedEntry;
  }
  /**
   * Retrieves the set of clear-text new passwords for the user, if available.
   * This will only be available if the modify operation contains one or more
@@ -130,57 +269,6 @@
    return newPasswords;
  }
  /**
   * Retrieves the current entry before any modifications are applied.  This
   * will not be available to pre-parse plugins.
   *
   * @param currentEntry The current entry.
   */
  public final void setCurrentEntry(Entry currentEntry)
  {
    this.currentEntry = currentEntry;
  }
  /**
   * Register the set of clear-text current passwords for the user, if
   * available.  This will only be available if the modify operation contains
   * one or more delete elements that target the password attribute and provide
   * the values to delete in the clear.
   *
   * @param currentPasswords The set of clear-text current password values as
   *                         provided in the modify request.
   */
  public final void setCurrentPasswords(List<AttributeValue> currentPasswords)
  {
    this.currentPasswords = currentPasswords;
  }
  /**
   * Register the modified entry that is to be written to the backend.
   *
   * @param modifiedEntry  The modified entry that is to be written to the
   *                       backend, or <CODE>null</CODE> if it is not yet
   *                       available.
   */
  public final void setModifiedEntry(Entry modifiedEntry)
  {
    this.modifiedEntry = modifiedEntry;
  }
  /**
   * Register the set of clear-text new passwords for the user, if available.
   * This will only be available if the modify operation contains one or more
   * add or replace elements that target the password attribute and provide the
   * values in the clear.
   *
   * @param newPasswords  The set of clear-text new passwords as provided in
   *                      the modify request, or <CODE>null</CODE> if there
   *                      were none or this information is not yet available.
   */
  public final void setNewPasswords(List<AttributeValue> newPasswords)
  {
    this.newPasswords = newPasswords;
  }
  /**
@@ -202,4 +290,2123 @@
    modifiedEntry.applyModification(modification);
    super.addModification(modification);
  }
  /**
   * Process this modify operation against a local backend.
   *
   * @param  backend  The backend in which the modify operation should be
   *                  performed.
   */
  void processLocalModify(Backend backend)
  {
    this.backend = backend;
    clientConnection = getClientConnection();
    // Get the plugin config manager that will be used for invoking plugins.
    PluginConfigManager pluginConfigManager =
      DirectoryServer.getPluginConfigManager();
    skipPostOperation = false;
    // Create a labeled block of code that we can break out of if a problem is
    // detected.
modifyProcessing:
    {
      entryDN = getEntryDN();
      if (entryDN == null){
        break modifyProcessing;
}
      // Process the modifications to convert them from their raw form to the
      // form required for the rest of the modify processing.
      modifications = getModifications();
      if (modifications == null)
      {
        break modifyProcessing;
      }
      if (modifications.isEmpty())
      {
        setResultCode(ResultCode.CONSTRAINT_VIOLATION);
        appendErrorMessage(ERR_MODIFY_NO_MODIFICATIONS.get(
                                String.valueOf(entryDN)));
        break modifyProcessing;
      }
      // If the user must change their password before doing anything else, and
      // if the target of the modify operation isn't the user's own entry, then
      // reject the request.
      if ((! isInternalOperation()) && clientConnection.mustChangePassword())
      {
        DN authzDN = getAuthorizationDN();
        if ((authzDN != null) && (! authzDN.equals(entryDN)))
        {
          // The user will not be allowed to do anything else before the
          // password gets changed.  Also note that we haven't yet checked the
          // request controls so we need to do that now to see if the password
          // policy request control was provided.
          for (Control c : getRequestControls())
          {
            if (c.getOID().equals(OID_PASSWORD_POLICY_CONTROL))
            {
              pwPolicyControlRequested = true;
              pwpErrorType = PasswordPolicyErrorType.CHANGE_AFTER_RESET;
              break;
            }
          }
          setResultCode(ResultCode.UNWILLING_TO_PERFORM);
          appendErrorMessage(ERR_MODIFY_MUST_CHANGE_PASSWORD.get());
          break modifyProcessing;
        }
      }
      // Check for a request to cancel this operation.
      if (getCancelRequest() != null)
      {
        return;
      }
      // Acquire a write lock on the target entry.
      Lock entryLock = null;
      for (int i=0; i < 3; i++)
      {
        entryLock = LockManager.lockWrite(entryDN);
        if (entryLock != null)
        {
          break;
        }
      }
      if (entryLock == null)
      {
        setResultCode(DirectoryServer.getServerErrorResultCode());
        appendErrorMessage(ERR_MODIFY_CANNOT_LOCK_ENTRY.get(
                                String.valueOf(entryDN)));
        skipPostOperation = true;
        break modifyProcessing;
      }
      try
      {
        // Check for a request to cancel this operation.
        if (getCancelRequest() != null)
        {
          return;
        }
        // Get the entry to modify.  If it does not exist, then fail.
        try
        {
          currentEntry = backend.getEntry(entryDN);
        }
        catch (DirectoryException de)
        {
          if (debugEnabled())
          {
            TRACER.debugCaught(DebugLogLevel.ERROR, de);
          }
          setResponseData(de);
          break modifyProcessing;
        }
        if (currentEntry == null)
        {
          setResultCode(ResultCode.NO_SUCH_OBJECT);
          appendErrorMessage(ERR_MODIFY_NO_SUCH_ENTRY.get(
                                  String.valueOf(entryDN)));
          // See if one of the entry's ancestors exists.
          DN parentDN = entryDN.getParentDNInSuffix();
          while (parentDN != null)
          {
            try
            {
              if (DirectoryServer.entryExists(parentDN))
              {
                setMatchedDN(parentDN);
                break;
              }
            }
            catch (Exception e)
            {
              if (debugEnabled())
              {
                TRACER.debugCaught(DebugLogLevel.ERROR, e);
              }
              break;
            }
            parentDN = parentDN.getParentDNInSuffix();
          }
          break modifyProcessing;
        }
        // Check to see if there are any controls in the request.  If so, then
        // see if there is any special processing required.
        try
        {
          processRequestControls();
        }
        catch (DirectoryException de)
        {
          if (debugEnabled())
          {
            TRACER.debugCaught(DebugLogLevel.ERROR, de);
          }
          setResponseData(de);
          break modifyProcessing;
        }
        // Get the password policy state object for the entry that can be used
        // to perform any appropriate password policy processing.  Also, see if
        // the entry is being updated by the end user or an administrator.
        selfChange = entryDN.equals(getAuthorizationDN());
        try
        {
          // FIXME -- Need a way to enable debug mode.
          pwPolicyState = new PasswordPolicyState(currentEntry, false, false);
        }
        catch (DirectoryException de)
        {
          if (debugEnabled())
          {
            TRACER.debugCaught(DebugLogLevel.ERROR, de);
          }
          setResultCode(de.getResultCode());
          appendErrorMessage(de.getMessageObject());
          break modifyProcessing;
        }
        // Create a duplicate of the entry and apply the changes to it.
        modifiedEntry = currentEntry.duplicate(false);
        if (! noOp)
        {
          // Invoke any conflict resolution processing that might be needed by
          // the synchronization provider.
          for (SynchronizationProvider provider :
            DirectoryServer.getSynchronizationProviders())
          {
            try
            {
              SynchronizationProviderResult result =
                provider.handleConflictResolution(this);
              if (! result.continueOperationProcessing())
              {
                break modifyProcessing;
              }
            }
            catch (DirectoryException de)
            {
              if (debugEnabled())
              {
                TRACER.debugCaught(DebugLogLevel.ERROR, de);
              }
              logError(ERR_MODIFY_SYNCH_CONFLICT_RESOLUTION_FAILED.get(
                            getConnectionID(), getOperationID(),
                            getExceptionMessage(de)));
              setResponseData(de);
              break modifyProcessing;
            }
          }
        }
        try
        {
          handleInitialPasswordPolicyAndSchemaProcessing();
        }
        catch (DirectoryException de)
        {
          if (debugEnabled())
          {
            TRACER.debugCaught(DebugLogLevel.ERROR, de);
          }
          setResponseData(de);
          break modifyProcessing;
        }
        // If there was a password change, then perform any additional checks
        // that may be necessary.
        wasLocked = false;
        if (passwordChanged)
        {
          try
          {
            performAdditionalPasswordChangedProcessing();
          }
          catch (DirectoryException de)
          {
            if (debugEnabled())
            {
              TRACER.debugCaught(DebugLogLevel.ERROR, de);
            }
            setResponseData(de);
            break modifyProcessing;
          }
        }
        // Check to see if the client has permission to perform the modify.
        // The access control check is not made any earlier because the handler
        // needs access to the modified entry.
        // FIXME: for now assume that this will check all permissions
        // pertinent to the operation. This includes proxy authorization
        // and any other controls specified.
        // FIXME: earlier checks to see if the entry already exists may have
        // already exposed sensitive information to the client.
        if (! AccessControlConfigManager.getInstance().
                   getAccessControlHandler().isAllowed(this))
        {
          setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
          appendErrorMessage(ERR_MODIFY_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS.get(
                                  String.valueOf(entryDN)));
          skipPostOperation = true;
          break modifyProcessing;
        }
        if ((! passwordChanged) && (! isInternalOperation()) &&
            pwPolicyState.mustChangePassword())
        {
          // The user will not be allowed to do anything else before the
          // password gets changed.
          pwpErrorType = PasswordPolicyErrorType.CHANGE_AFTER_RESET;
          setResultCode(ResultCode.UNWILLING_TO_PERFORM);
          appendErrorMessage(ERR_MODIFY_MUST_CHANGE_PASSWORD.get());
          break modifyProcessing;
        }
        // If the server is configured to check the schema and the
        // operation is not a sycnhronization operation,
        // make sure that the new entry is valid per the server schema.
        if ((DirectoryServer.checkSchema()) && (! isSynchronizationOperation()))
        {
          MessageBuilder invalidReason = new MessageBuilder();
          if (! modifiedEntry.conformsToSchema(null, false, false, false,
              invalidReason))
          {
            setResultCode(ResultCode.OBJECTCLASS_VIOLATION);
            appendErrorMessage(ERR_MODIFY_VIOLATES_SCHEMA.get(
                                    String.valueOf(entryDN), invalidReason));
            break modifyProcessing;
          }
        }
        // Check for a request to cancel this operation.
        if (getCancelRequest() != null)
        {
          return;
        }
        // If the operation is not a synchronization operation,
        // Invoke the pre-operation modify plugins.
        if (! isSynchronizationOperation())
        {
          PreOperationPluginResult preOpResult =
            pluginConfigManager.invokePreOperationModifyPlugins(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;
          }
          else if (preOpResult.sendResponseImmediately())
          {
            skipPostOperation = true;
            break modifyProcessing;
          }
          else if (preOpResult.skipCoreProcessing())
          {
            skipPostOperation = false;
            break modifyProcessing;
          }
        }
        // Check for a request to cancel this operation.
        if (getCancelRequest() != null)
        {
          return;
        }
        // Actually perform the modify operation.  This should also include
        // taking care of any synchronization that might be needed.
        if (backend == null)
        {
          setResultCode(ResultCode.NO_SUCH_OBJECT);
          appendErrorMessage(ERR_MODIFY_NO_BACKEND_FOR_ENTRY.get(
                                  String.valueOf(entryDN)));
          break modifyProcessing;
        }
        try
        {
          try
          {
            checkWritability();
          }
          catch (DirectoryException de)
          {
            if (debugEnabled())
            {
              TRACER.debugCaught(DebugLogLevel.ERROR, de);
            }
            setResponseData(de);
            break modifyProcessing;
          }
          if (noOp)
          {
            appendErrorMessage(INFO_MODIFY_NOOP.get());
            setResultCode(ResultCode.NO_OPERATION);
          }
          else
          {
            for (SynchronizationProvider provider :
              DirectoryServer.getSynchronizationProviders())
            {
              try
              {
                SynchronizationProviderResult result =
                     provider.doPreOperation(this);
                if (! result.continueOperationProcessing())
                {
                  break modifyProcessing;
                }
              }
              catch (DirectoryException de)
              {
                if (debugEnabled())
                {
                  TRACER.debugCaught(DebugLogLevel.ERROR, de);
                }
                logError(ERR_MODIFY_SYNCH_PREOP_FAILED.get(getConnectionID(),
                              getOperationID(), getExceptionMessage(de)));
                setResponseData(de);
                break modifyProcessing;
              }
            }
            backend.replaceEntry(modifiedEntry, this);
            // See if we need to generate any account status notifications as a
            // result of the changes.
            if (passwordChanged || enabledStateChanged || wasLocked)
            {
              handleAccountStatusNotifications();
            }
          }
          // Handle any processing that may be needed for the pre-read and/or
          // post-read controls.
          handleReadEntryProcessing();
          if (! noOp)
          {
            setResultCode(ResultCode.SUCCESS);
          }
        }
        catch (DirectoryException de)
        {
          if (debugEnabled())
          {
            TRACER.debugCaught(DebugLogLevel.ERROR, de);
          }
          setResponseData(de);
          break modifyProcessing;
        }
        catch (CancelledOperationException coe)
        {
          if (debugEnabled())
          {
            TRACER.debugCaught(DebugLogLevel.ERROR, coe);
          }
          CancelResult cancelResult = coe.getCancelResult();
          setCancelResult(cancelResult);
          setResultCode(cancelResult.getResultCode());
          Message message = coe.getMessageObject();
          if ((message != null) && (message.length() > 0))
          {
            appendErrorMessage(message);
          }
          break modifyProcessing;
        }
      }
      finally
      {
        LockManager.unlock(entryDN, entryLock);
        for (SynchronizationProvider provider :
          DirectoryServer.getSynchronizationProviders())
        {
          try
          {
            provider.doPostOperation(this);
          }
          catch (DirectoryException de)
          {
            if (debugEnabled())
            {
              TRACER.debugCaught(DebugLogLevel.ERROR, de);
            }
            logError(ERR_MODIFY_SYNCH_POSTOP_FAILED.get(getConnectionID(),
                          getOperationID(), getExceptionMessage(de)));
            setResponseData(de);
            break;
          }
        }
      }
    }
    // If the password policy request control was included, then make sure we
    // send the corresponding response control.
    if (pwPolicyControlRequested)
    {
      addResponseControl(new PasswordPolicyResponseControl(null, 0,
                                                           pwpErrorType));
    }
    // Indicate that it is now too late to attempt to cancel the operation.
    setCancelResult(CancelResult.TOO_LATE);
    // Invoke the post-operation or post-synchronization modify plugins.
    if (isSynchronizationOperation())
    {
      if (getResultCode() == ResultCode.SUCCESS)
      {
        pluginConfigManager.invokePostSynchronizationModifyPlugins(this);
      }
    }
    else if (! skipPostOperation)
    {
      // FIXME -- Should this also be done while holding the locks?
      PostOperationPluginResult postOpResult =
           pluginConfigManager.invokePostOperationModifyPlugins(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;
      }
    }
    // Notify any change notification listeners that might be registered with
    // the server.
    if (getResultCode() == ResultCode.SUCCESS)
    {
      for (ChangeNotificationListener changeListener :
           DirectoryServer.getChangeNotificationListeners())
      {
        try
        {
          changeListener.handleModifyOperation(this, currentEntry,
                                               modifiedEntry);
        }
        catch (Exception e)
        {
          if (debugEnabled())
          {
            TRACER.debugCaught(DebugLogLevel.ERROR, e);
          }
          Message message = ERR_MODIFY_ERROR_NOTIFYING_CHANGE_LISTENER.get(
              getExceptionMessage(e));
          logError(message);
        }
      }
    }
    // Stop the processing timer.
    setProcessingStopTime();
  }
  /**
   * Processes any controls contained in the modify request.
   *
   * @throws  DirectoryException  If a problem is encountered with any of the
   *                              controls.
   */
  private void processRequestControls()
          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(entryDN, this, c))
        {
          skipPostOperation = true;
          throw new DirectoryException(ResultCode.INSUFFICIENT_ACCESS_RIGHTS,
                         ERR_CONTROL_INSUFFICIENT_ACCESS_RIGHTS.get(oid));
        }
        if (oid.equals(OID_LDAP_ASSERTION))
        {
          LDAPAssertionRequestControl assertControl;
          if (c instanceof LDAPAssertionRequestControl)
          {
            assertControl = (LDAPAssertionRequestControl) c;
          }
          else
          {
            try
            {
              assertControl = LDAPAssertionRequestControl.decodeControl(c);
              requestControls.set(i, assertControl);
            }
            catch (LDAPException le)
            {
              if (debugEnabled())
              {
                TRACER.debugCaught(DebugLogLevel.ERROR, le);
              }
              throw new DirectoryException(
                             ResultCode.valueOf(le.getResultCode()),
                             le.getMessageObject());
            }
          }
          try
          {
            // FIXME -- We need to determine whether the current user has
            //          permission to make this determination.
            SearchFilter filter = assertControl.getSearchFilter();
            if (! filter.matchesEntry(currentEntry))
            {
              throw new DirectoryException(ResultCode.ASSERTION_FAILED,
                                           ERR_MODIFY_ASSERTION_FAILED.get(
                                                String.valueOf(entryDN)));
            }
          }
          catch (DirectoryException de)
          {
            if (de.getResultCode() == ResultCode.ASSERTION_FAILED)
            {
              throw de;
            }
            if (debugEnabled())
            {
              TRACER.debugCaught(DebugLogLevel.ERROR, de);
            }
            throw new DirectoryException(ResultCode.PROTOCOL_ERROR,
                           ERR_MODIFY_CANNOT_PROCESS_ASSERTION_FILTER.get(
                                String.valueOf(entryDN),
                                de.getMessageObject()));
          }
        }
        else if (oid.equals(OID_LDAP_NOOP_OPENLDAP_ASSIGNED))
        {
          noOp = true;
        }
        else if (oid.equals(OID_LDAP_READENTRY_PREREAD))
        {
          if (c instanceof LDAPPreReadRequestControl)
          {
            preReadRequest = (LDAPPreReadRequestControl) c;
          }
          else
          {
            try
            {
              preReadRequest = LDAPPreReadRequestControl.decodeControl(c);
              requestControls.set(i, preReadRequest);
            }
            catch (LDAPException le)
            {
              if (debugEnabled())
              {
                TRACER.debugCaught(DebugLogLevel.ERROR, le);
              }
              throw new DirectoryException(
                             ResultCode.valueOf(le.getResultCode()),
                             le.getMessageObject());
            }
          }
        }
        else if (oid.equals(OID_LDAP_READENTRY_POSTREAD))
        {
          if (c instanceof LDAPPostReadRequestControl)
          {
            postReadRequest = (LDAPPostReadRequestControl) c;
          }
          else
          {
            try
            {
              postReadRequest = LDAPPostReadRequestControl.decodeControl(c);
              requestControls.set(i, postReadRequest);
            }
            catch (LDAPException le)
            {
              if (debugEnabled())
              {
                TRACER.debugCaught(DebugLogLevel.ERROR, le);
              }
              throw new DirectoryException(
                             ResultCode.valueOf(le.getResultCode()),
                             le.getMessageObject());
            }
          }
        }
        else if (oid.equals(OID_PROXIED_AUTH_V1))
        {
          // The requester must have the PROXIED_AUTH privilige in order to
          // be able to use this control.
          if (! clientConnection.hasPrivilege(Privilege.PROXIED_AUTH, this))
          {
            throw new DirectoryException(ResultCode.AUTHORIZATION_DENIED,
                           ERR_PROXYAUTH_INSUFFICIENT_PRIVILEGES.get());
          }
          ProxiedAuthV1Control proxyControl;
          if (c instanceof ProxiedAuthV1Control)
          {
            proxyControl = (ProxiedAuthV1Control) c;
          }
          else
          {
            try
            {
              proxyControl = ProxiedAuthV1Control.decodeControl(c);
            }
            catch (LDAPException le)
            {
              if (debugEnabled())
              {
                TRACER.debugCaught(DebugLogLevel.ERROR, le);
              }
              throw new DirectoryException(
                             ResultCode.valueOf(le.getResultCode()),
                             le.getMessageObject());
            }
          }
          Entry authorizationEntry = proxyControl.getAuthorizationEntry();
          setAuthorizationEntry(authorizationEntry);
          if (authorizationEntry == null)
          {
            setProxiedAuthorizationDN(DN.nullDN());
          }
          else
          {
            setProxiedAuthorizationDN(authorizationEntry.getDN());
          }
        }
        else if (oid.equals(OID_PROXIED_AUTH_V2))
        {
          // The requester must have the PROXIED_AUTH privilige in order to
          // be able to use this control.
          if (! clientConnection.hasPrivilege(Privilege.PROXIED_AUTH, this))
          {
            throw new DirectoryException(ResultCode.AUTHORIZATION_DENIED,
                           ERR_PROXYAUTH_INSUFFICIENT_PRIVILEGES.get());
          }
          ProxiedAuthV2Control proxyControl;
          if (c instanceof ProxiedAuthV2Control)
          {
            proxyControl = (ProxiedAuthV2Control) c;
          }
          else
          {
            try
            {
              proxyControl = ProxiedAuthV2Control.decodeControl(c);
            }
            catch (LDAPException le)
            {
              if (debugEnabled())
              {
                TRACER.debugCaught(DebugLogLevel.ERROR, le);
              }
              throw new DirectoryException(
                             ResultCode.valueOf(le.getResultCode()),
                             le.getMessageObject());
            }
          }
          Entry authorizationEntry = proxyControl.getAuthorizationEntry();
          setAuthorizationEntry(authorizationEntry);
          if (authorizationEntry == null)
          {
            setProxiedAuthorizationDN(DN.nullDN());
          }
          else
          {
            setProxiedAuthorizationDN(authorizationEntry.getDN());
          }
        }
        else if (oid.equals(OID_PASSWORD_POLICY_CONTROL))
        {
          pwPolicyControlRequested = true;
        }
        // NYI -- Add support for additional controls.
        else if (c.isCritical())
        {
          if ((backend == null) || (! backend.supportsControl(oid)))
          {
            throw new DirectoryException(
                           ResultCode.UNAVAILABLE_CRITICAL_EXTENSION,
                           ERR_MODIFY_UNSUPPORTED_CRITICAL_CONTROL.get(
                                String.valueOf(entryDN), oid));
          }
        }
      }
    }
  }
  /**
   * Handles the initial set of password policy and schema processing for this
   * modify operation.
   *
   * @throws  DirectoryException  If a problem is encountered that should cause
   *                              the modify operation to fail.
   */
  private void handleInitialPasswordPolicyAndSchemaProcessing()
          throws DirectoryException
  {
    // Declare variables used for password policy state processing.
    currentPasswordProvided = false;
    isEnabled = true;
    enabledStateChanged = false;
    if (currentEntry.hasAttribute(
        pwPolicyState.getPolicy().getPasswordAttribute()))
    {
      // It may actually have more than one, but we can't tell the difference if
      // the values are encoded, and its enough for our purposes just to know
      // that there is at least one.
      numPasswords = 1;
    }
    else
    {
      numPasswords = 0;
    }
    // If it's not an internal or synchronization operation, then iterate
    // through the set of modifications to see if a password is included in the
    // changes.  If so, then add the appropriate state changes to the set of
    // modifications.
    if (! (isInternalOperation() || isSynchronizationOperation()))
    {
      for (Modification m : modifications)
      {
        if (m.getAttribute().getAttributeType().equals(
            pwPolicyState.getPolicy().getPasswordAttribute()))
        {
          passwordChanged = true;
          if (! selfChange)
          {
            if (! clientConnection.hasPrivilege(Privilege.PASSWORD_RESET, this))
            {
              pwpErrorType = PasswordPolicyErrorType.PASSWORD_MOD_NOT_ALLOWED;
              throw new DirectoryException(
                             ResultCode.INSUFFICIENT_ACCESS_RIGHTS,
                             ERR_MODIFY_PWRESET_INSUFFICIENT_PRIVILEGES.get());
            }
          }
          break;
        }
      }
    }
    for (Modification m : modifications)
    {
      Attribute     a = m.getAttribute();
      AttributeType t = a.getAttributeType();
      // If the attribute type is marked "NO-USER-MODIFICATION" then fail unless
      // this is an internal operation or is related to synchronization in some
      // way.
      if (t.isNoUserModification())
      {
        if (! (isInternalOperation() || isSynchronizationOperation() ||
                m.isInternal()))
        {
          throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
                         ERR_MODIFY_ATTR_IS_NO_USER_MOD.get(
                              String.valueOf(entryDN), a.getName()));
        }
      }
      // If the attribute type is marked "OBSOLETE" and the modification is
      // setting new values, then fail unless this is an internal operation or
      // is related to synchronization in some way.
      if (t.isObsolete())
      {
        if (a.hasValue() &&
            (m.getModificationType() != ModificationType.DELETE))
        {
          if (! (isInternalOperation() || isSynchronizationOperation() ||
                 m.isInternal()))
          {
            throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
                           ERR_MODIFY_ATTR_IS_OBSOLETE.get(
                                String.valueOf(entryDN), a.getName()));
          }
        }
      }
      // See if the attribute is one which controls the privileges available for
      // a user.  If it is, then the client must have the PRIVILEGE_CHANGE
      // privilege.
      if (t.hasName(OP_ATTR_PRIVILEGE_NAME))
      {
        if (! clientConnection.hasPrivilege(Privilege.PRIVILEGE_CHANGE, this))
        {
          throw new DirectoryException(ResultCode.INSUFFICIENT_ACCESS_RIGHTS,
               ERR_MODIFY_CHANGE_PRIVILEGE_INSUFFICIENT_PRIVILEGES.get());
        }
      }
      // If the modification is updating the password attribute, then perform
      // any necessary password policy processing.  This processing should be
      // skipped for synchronization operations.
      boolean isPassword =
           t.equals(pwPolicyState.getPolicy().getPasswordAttribute());
      if (isPassword && (!(isSynchronizationOperation())))
      {
        // If the attribute contains any options, then reject it.  Passwords
        // will not be allowed to have options. Skipped for internal operations.
        if(! isInternalOperation())
        {
          if (a.hasOptions())
          {
            throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
                           ERR_MODIFY_PASSWORDS_CANNOT_HAVE_OPTIONS.get());
          }
          // If it's a self change, then see if that's allowed.
          if (selfChange &&
              (! pwPolicyState.getPolicy().allowUserPasswordChanges()))
          {
            pwpErrorType = PasswordPolicyErrorType.PASSWORD_MOD_NOT_ALLOWED;
            throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
                                         ERR_MODIFY_NO_USER_PW_CHANGES.get());
          }
          // If we require secure password changes, then makes sure it's a
          // secure communication channel.
          if (pwPolicyState.getPolicy().requireSecurePasswordChanges() &&
              (! clientConnection.isSecure()))
          {
            pwpErrorType = PasswordPolicyErrorType.PASSWORD_MOD_NOT_ALLOWED;
            throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
                           ERR_MODIFY_REQUIRE_SECURE_CHANGES.get());
          }
          // If it's a self change and it's not been long enough since the
          // previous change, then reject it.
          if (selfChange && pwPolicyState.isWithinMinimumAge())
          {
            pwpErrorType = PasswordPolicyErrorType.PASSWORD_TOO_YOUNG;
            throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
                                         ERR_MODIFY_WITHIN_MINIMUM_AGE.get());
          }
        }
        // Check to see whether this will adding, deleting, or replacing
        // password values (increment doesn't make any sense for passwords).
        // Then perform the appropriate type of processing for that kind of
        // modification.
        boolean isAdd = (m.getModificationType() == ModificationType.ADD);
        LinkedHashSet<AttributeValue> pwValues = a.getValues();
        LinkedHashSet<AttributeValue> encodedValues =
          new LinkedHashSet<AttributeValue>();
        switch (m.getModificationType())
        {
          case ADD:
          case REPLACE:
            processInitialAddOrReplacePW(isAdd, pwValues, encodedValues, a);
            break;
          case DELETE:
            processInitialDeletePW(pwValues, encodedValues, a);
            break;
          default:
            throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
                           ERR_MODIFY_INVALID_MOD_TYPE_FOR_PASSWORD.get(
                                String.valueOf(m.getModificationType()),
                                a.getName()));
        }
      }
      else
      {
        // See if it's an attribute used to maintain the account
        // enabled/disabled state.
        AttributeType disabledAttr =
          DirectoryServer.getAttributeType(OP_ATTR_ACCOUNT_DISABLED, true);
        if (t.equals(disabledAttr))
        {
          enabledStateChanged = true;
          for (AttributeValue v : a.getValues())
          {
            try
            {
              isEnabled =
                   (! BooleanSyntax.decodeBooleanValue(v.getNormalizedValue()));
            }
            catch (DirectoryException de)
            {
              throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
                             ERR_MODIFY_INVALID_DISABLED_VALUE.get(
                                  OP_ATTR_ACCOUNT_DISABLED,
                                  String.valueOf(de.getMessageObject())), de);
            }
          }
        }
      }
      switch (m.getModificationType())
      {
        case ADD:
          processInitialAddSchema(a);
          break;
        case DELETE:
          processInitialDeleteSchema(a);
          break;
        case REPLACE:
          processInitialReplaceSchema(a);
          break;
        case INCREMENT:
          processInitialIncrementSchema(a);
          break;
      }
    }
  }
  /**
   * Performs the initial password policy add or replace processing.
   *
   * @param  isAdd          Indicates whether it is an add or replace update.
   * @param  pwValues       The set of password values as included in the
   *                        request.
   * @param  encodedValues  The set of encoded password values.
   * @param  pwAttr         The attribute involved in the password change.
   *
   * @throws  DirectoryException  If a problem occurs that should cause the
   *                              modify operation to fail.
   */
  private void processInitialAddOrReplacePW(boolean isAdd,
                    LinkedHashSet<AttributeValue> pwValues,
                    LinkedHashSet<AttributeValue> encodedValues,
                    Attribute pwAttr)
          throws DirectoryException
  {
    int passwordsToAdd = pwValues.size();
    if (isAdd)
    {
      numPasswords += passwordsToAdd;
    }
    else
    {
      numPasswords = passwordsToAdd;
    }
    // If there were multiple password values, then make sure that's OK.
    if ((! isInternalOperation()) &&
        (! pwPolicyState.getPolicy().allowMultiplePasswordValues()) &&
        (passwordsToAdd > 1))
    {
      pwpErrorType = PasswordPolicyErrorType.PASSWORD_MOD_NOT_ALLOWED;
      throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
                     ERR_MODIFY_MULTIPLE_VALUES_NOT_ALLOWED.get());
    }
    // Iterate through the password values and see if any of them are
    // pre-encoded.  If so, then check to see if we'll allow it.  Otherwise,
    // store the clear-text values for later validation and update the attribute
    // with the encoded values.
    for (AttributeValue v : pwValues)
    {
      if (pwPolicyState.passwordIsPreEncoded(v.getValue()))
      {
        if ((! isInternalOperation()) &&
            ! pwPolicyState.getPolicy().allowPreEncodedPasswords())
        {
          pwpErrorType = PasswordPolicyErrorType.INSUFFICIENT_PASSWORD_QUALITY;
          throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
                         ERR_MODIFY_NO_PREENCODED_PASSWORDS.get());
        }
        else
        {
          encodedValues.add(v);
        }
      }
      else
      {
        if (isAdd)
        {
          // Make sure that the password value doesn't already exist.
          if (pwPolicyState.passwordMatches(v.getValue()))
          {
            pwpErrorType = PasswordPolicyErrorType.PASSWORD_IN_HISTORY;
            throw new DirectoryException(ResultCode.ATTRIBUTE_OR_VALUE_EXISTS,
                                         ERR_MODIFY_PASSWORD_EXISTS.get());
          }
        }
        if (newPasswords == null)
        {
          newPasswords = new LinkedList<AttributeValue>();
        }
        newPasswords.add(v);
        for (ByteString s : pwPolicyState.encodePassword(v.getValue()))
        {
          encodedValues.add(new AttributeValue(pwAttr.getAttributeType(), s));
        }
      }
    }
    pwAttr.setValues(encodedValues);
  }
  /**
   * Performs the initial password policy delete processing.
   *
   * @param  pwValues       The set of password values as included in the
   *                        request.
   * @param  encodedValues  The set of encoded password values.
   * @param  pwAttr         The attribute involved in the password change.
   *
   * @throws  DirectoryException  If a problem occurs that should cause the
   *                              modify operation to fail.
   */
  private void processInitialDeletePW(LinkedHashSet<AttributeValue> pwValues,
                    LinkedHashSet<AttributeValue> encodedValues,
                    Attribute pwAttr)
          throws DirectoryException
  {
    // Iterate through the password values and see if any of them are
    // pre-encoded.  We will never allow pre-encoded passwords for user password
    // changes, but we will allow them for administrators.  For each clear-text
    // value, verify that at least one value in the entry matches and replace
    // the clear-text value with the appropriate encoded forms.
    for (AttributeValue v : pwValues)
    {
      if (pwPolicyState.passwordIsPreEncoded(v.getValue()))
      {
        if ((! isInternalOperation()) && selfChange)
        {
          pwpErrorType = PasswordPolicyErrorType.INSUFFICIENT_PASSWORD_QUALITY;
          throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
                         ERR_MODIFY_NO_PREENCODED_PASSWORDS.get());
        }
        else
        {
          encodedValues.add(v);
        }
      }
      else
      {
        List<Attribute> attrList =
             currentEntry.getAttribute(pwAttr.getAttributeType());
        if ((attrList == null) || (attrList.isEmpty()))
        {
          throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
                                       ERR_MODIFY_NO_EXISTING_VALUES.get());
        }
        boolean found = false;
        for (Attribute attr : attrList)
        {
          for (AttributeValue av : attr.getValues())
          {
            if (pwPolicyState.getPolicy().usesAuthPasswordSyntax())
            {
              if (AuthPasswordSyntax.isEncoded(av.getValue()))
              {
                StringBuilder[] compoenents =
                     AuthPasswordSyntax.decodeAuthPassword(av.getStringValue());
                PasswordStorageScheme scheme =
                     DirectoryServer.getAuthPasswordStorageScheme(
                          compoenents[0].toString());
                if (scheme != null)
                {
                  if (scheme.authPasswordMatches(v.getValue(),
                                                 compoenents[1].toString(),
                                                 compoenents[2].toString()))
                  {
                    encodedValues.add(av);
                    found = true;
                  }
                }
              }
              else
              {
                if (av.equals(v))
                {
                  encodedValues.add(v);
                  found = true;
                }
              }
            }
            else
            {
              if (UserPasswordSyntax.isEncoded(av.getValue()))
              {
                String[] compoenents = UserPasswordSyntax.decodeUserPassword(
                                            av.getStringValue());
                PasswordStorageScheme scheme =
                     DirectoryServer.getPasswordStorageScheme(
                          toLowerCase(compoenents[0]));
                if (scheme != null)
                {
                  if (scheme.passwordMatches(v.getValue(),
                                  new ASN1OctetString(compoenents[1])))
                  {
                    encodedValues.add(av);
                    found = true;
                  }
                }
              }
              else
              {
                if (av.equals(v))
                {
                  encodedValues.add(v);
                  found = true;
                }
              }
            }
          }
        }
        if (found)
        {
          if (currentPasswords == null)
          {
            currentPasswords = new LinkedList<AttributeValue>();
          }
          currentPasswords.add(v);
          numPasswords--;
        }
        else
        {
          throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
                                       ERR_MODIFY_INVALID_PASSWORD.get());
        }
        currentPasswordProvided = true;
      }
    }
    pwAttr.setValues(encodedValues);
  }
  /**
   * Performs the initial schema processing for an add modification.
   *
   * @param  attr  The attribute being added.
   *
   * @throws  DirectoryException  If a problem occurs that should cause the
   *                              modify operation to fail.
   */
  private void processInitialAddSchema(Attribute attr)
          throws DirectoryException
  {
    // Make sure that one or more values have been provided for the attribute.
    LinkedHashSet<AttributeValue> newValues = attr.getValues();
    if ((newValues == null) || newValues.isEmpty())
    {
      throw new DirectoryException(ResultCode.PROTOCOL_ERROR,
                     ERR_MODIFY_ADD_NO_VALUES.get(String.valueOf(entryDN),
                                                 attr.getName()));
    }
    // If the server is configured to check schema and the operation is not a
    // synchronization operation, make sure that all the new values are valid
    // according to the associated syntax.
    if ((DirectoryServer.checkSchema()) && (! isSynchronizationOperation()))
    {
      AcceptRejectWarn syntaxPolicy =
           DirectoryServer.getSyntaxEnforcementPolicy();
      AttributeSyntax syntax = attr.getAttributeType().getSyntax();
      if (syntaxPolicy == AcceptRejectWarn.REJECT)
      {
        MessageBuilder invalidReason = new MessageBuilder();
        for (AttributeValue v : newValues)
        {
          if (! syntax.valueIsAcceptable(v.getValue(), invalidReason))
          {
            throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
                           ERR_MODIFY_ADD_INVALID_SYNTAX.get(
                                String.valueOf(entryDN), attr.getName(),
                                v.getStringValue(), invalidReason));
          }
        }
      }
      else if (syntaxPolicy == AcceptRejectWarn.WARN)
      {
        MessageBuilder invalidReason = new MessageBuilder();
        for (AttributeValue v : newValues)
        {
          if (! syntax.valueIsAcceptable(v.getValue(), invalidReason))
          {
            setResultCode(ResultCode.INVALID_ATTRIBUTE_SYNTAX);
            logError(ERR_MODIFY_ADD_INVALID_SYNTAX.get(String.valueOf(entryDN),
                          attr.getName(), v.getStringValue(), invalidReason));
            invalidReason = new MessageBuilder();
          }
        }
      }
    }
    // Add the provided attribute or merge an existing attribute with
    // the values of the new attribute.  If there are any duplicates,
    // then fail.
    if (attr.getAttributeType().isObjectClassType())
    {
      modifiedEntry.addObjectClasses(newValues);
    }
    else
    {
      LinkedList<AttributeValue> duplicateValues =
           new LinkedList<AttributeValue>();
      modifiedEntry.addAttribute(attr, duplicateValues);
      if (! duplicateValues.isEmpty())
      {
        StringBuilder buffer = new StringBuilder();
        Iterator<AttributeValue> iterator = duplicateValues.iterator();
        buffer.append(iterator.next().getStringValue());
        while (iterator.hasNext())
        {
          buffer.append(", ");
          buffer.append(iterator.next().getStringValue());
        }
        throw new DirectoryException(ResultCode.ATTRIBUTE_OR_VALUE_EXISTS,
                       ERR_MODIFY_ADD_DUPLICATE_VALUE.get(
                            String.valueOf(entryDN), attr.getName(), buffer));
      }
    }
  }
  /**
   * Performs the initial schema processing for a delete modification.
   *
   * @param  attr  The attribute being deleted.
   *
   * @throws  DirectoryException  If a problem occurs that should cause the
   *                              modify operation to fail.
   */
  private void processInitialDeleteSchema(Attribute attr)
          throws DirectoryException
  {
    // Remove the specified attribute values or the entire attribute from the
    // value.  If there are any specified values that were not present, then
    // fail.  If the RDN attribute value would be removed, then fail.
    LinkedList<AttributeValue> missingValues = new LinkedList<AttributeValue>();
    boolean attrExists = modifiedEntry.removeAttribute(attr, missingValues);
    if (attrExists)
    {
      if (missingValues.isEmpty())
      {
        AttributeType t = attr.getAttributeType();
        RDN rdn = modifiedEntry.getDN().getRDN();
        if ((rdn !=  null) && rdn.hasAttributeType(t) &&
            (! modifiedEntry.hasValue(t, attr.getOptions(),
                                      rdn.getAttributeValue(t))))
        {
          throw new DirectoryException(ResultCode.NOT_ALLOWED_ON_RDN,
                                       ERR_MODIFY_DELETE_RDN_ATTR.get(
                                            String.valueOf(entryDN),
                                            attr.getName()));
        }
      }
      else
      {
        StringBuilder buffer = new StringBuilder();
        Iterator<AttributeValue> iterator = missingValues.iterator();
        buffer.append(iterator.next().getStringValue());
        while (iterator.hasNext())
        {
          buffer.append(", ");
          buffer.append(iterator.next().getStringValue());
        }
        throw new DirectoryException(ResultCode.NO_SUCH_ATTRIBUTE,
                       ERR_MODIFY_DELETE_MISSING_VALUES.get(
                            String.valueOf(entryDN), attr.getName(), buffer));
      }
    }
    else
    {
      throw new DirectoryException(ResultCode.NO_SUCH_ATTRIBUTE,
                     ERR_MODIFY_DELETE_NO_SUCH_ATTR.get(
                          String.valueOf(entryDN), attr.getName()));
    }
  }
  /**
   * Performs the initial schema processing for a replace modification.
   *
   * @param  attr  The attribute being replaced.
   *
   * @throws  DirectoryException  If a problem occurs that should cause the
   *                              modify operation to fail.
   */
  private void processInitialReplaceSchema(Attribute attr)
          throws DirectoryException
  {
    // If it is the objectclass attribute, then treat that separately.
    if (attr.getAttributeType().isObjectClassType())
    {
      modifiedEntry.setObjectClasses(attr.getValues());
      return;
    }
    // If the provided attribute does not have any values, then we will simply
    // remove the attribute from the entry (if it exists).
    AttributeType t = attr.getAttributeType();
    if (! attr.hasValue())
    {
      modifiedEntry.removeAttribute(t, attr.getOptions());
      RDN rdn = modifiedEntry.getDN().getRDN();
      if ((rdn !=  null) && rdn.hasAttributeType(t) &&
          (! modifiedEntry.hasValue(t, attr.getOptions(),
                                    rdn.getAttributeValue(t))))
      {
        throw new DirectoryException(ResultCode.NOT_ALLOWED_ON_RDN,
                       ERR_MODIFY_DELETE_RDN_ATTR.get(String.valueOf(entryDN),
                                                      attr.getName()));
      }
      return;
    }
    // If the server is configured to check schema and the operation is not a
    // synchronization operation, make sure that all the new values are valid
    // according to the associated syntax.
    LinkedHashSet<AttributeValue> newValues = attr.getValues();
    if ((DirectoryServer.checkSchema()) && (! isSynchronizationOperation()))
    {
      AcceptRejectWarn syntaxPolicy =
        DirectoryServer.getSyntaxEnforcementPolicy();
      AttributeSyntax syntax = t.getSyntax();
      if (syntaxPolicy == AcceptRejectWarn.REJECT)
      {
        MessageBuilder invalidReason = new MessageBuilder();
        for (AttributeValue v : newValues)
        {
          if (! syntax.valueIsAcceptable(v.getValue(), invalidReason))
          {
            throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
                           ERR_MODIFY_REPLACE_INVALID_SYNTAX.get(
                                String.valueOf(entryDN), attr.getName(),
                                v.getStringValue(), invalidReason));
          }
        }
      }
      else if (syntaxPolicy == AcceptRejectWarn.WARN)
      {
        MessageBuilder invalidReason = new MessageBuilder();
        for (AttributeValue v : newValues)
        {
          if (! syntax.valueIsAcceptable(v.getValue(), invalidReason))
          {
            setResultCode(ResultCode.INVALID_ATTRIBUTE_SYNTAX);
            logError(ERR_MODIFY_REPLACE_INVALID_SYNTAX.get(
                          String.valueOf(entryDN), attr.getName(),
                          v.getStringValue(), invalidReason));
            invalidReason = new MessageBuilder();
          }
        }
      }
    }
    // If the provided attribute does not have any options, then we will simply
    // use it in place of any existing attribute of the provided type (or add it
    // if it doesn't exist).
    if (! attr.hasOptions())
    {
      List<Attribute> attrList = new ArrayList<Attribute>(1);
      attrList.add(attr);
      modifiedEntry.putAttribute(t, attrList);
      RDN rdn = modifiedEntry.getDN().getRDN();
      if ((rdn !=  null) && rdn.hasAttributeType(t) &&
          (! modifiedEntry.hasValue(t, attr.getOptions(),
                                    rdn.getAttributeValue(t))))
      {
        throw new DirectoryException(ResultCode.NOT_ALLOWED_ON_RDN,
                                     ERR_MODIFY_DELETE_RDN_ATTR.get(
                                          String.valueOf(entryDN),
                                          attr.getName()));
      }
      return;
    }
    // See if there is an existing attribute of the provided type.  If not, then
    // we'll use the new one.
    List<Attribute> attrList = modifiedEntry.getAttribute(t);
    if ((attrList == null) || attrList.isEmpty())
    {
      attrList = new ArrayList<Attribute>(1);
      attrList.add(attr);
      modifiedEntry.putAttribute(t, attrList);
      RDN rdn = modifiedEntry.getDN().getRDN();
      if ((rdn !=  null) && rdn.hasAttributeType(t) &&
          (! modifiedEntry.hasValue(t, attr.getOptions(),
                                    rdn.getAttributeValue(t))))
      {
        throw new DirectoryException(ResultCode.NOT_ALLOWED_ON_RDN,
                                     ERR_MODIFY_DELETE_RDN_ATTR.get(
                                          String.valueOf(entryDN),
                                          attr.getName()));
      }
      return;
    }
    // There must be an existing occurrence of the provided attribute in the
    // entry.  If there is a version with exactly the set of options provided,
    // then replace it.  Otherwise, add a new one.
    boolean found = false;
    for (int i=0; i < attrList.size(); i++)
    {
      if (attrList.get(i).optionsEqual(attr.getOptions()))
      {
        attrList.set(i, attr);
        found = true;
        break;
      }
    }
    if (! found)
    {
      attrList.add(attr);
    }
    RDN rdn = modifiedEntry.getDN().getRDN();
    if ((rdn !=  null) && rdn.hasAttributeType(t) &&
        (! modifiedEntry.hasValue(t, attr.getOptions(),
                                  rdn.getAttributeValue(t))))
    {
      throw new DirectoryException(ResultCode.NOT_ALLOWED_ON_RDN,
                                   ERR_MODIFY_DELETE_RDN_ATTR.get(
                                        String.valueOf(entryDN),
                                        attr.getName()));
    }
  }
  /**
   * Performs the initial schema processing for an increment modification.
   *
   * @param  attr  The attribute being incremented.
   *
   * @throws  DirectoryException  If a problem occurs that should cause the
   *                              modify operation to fail.
   */
  private void processInitialIncrementSchema(Attribute attr)
          throws DirectoryException
  {
    // The specified attribute type must not be an RDN attribute.
    AttributeType t = attr.getAttributeType();
    RDN rdn = modifiedEntry.getDN().getRDN();
    if ((rdn !=  null) && rdn.hasAttributeType(t))
    {
      throw new DirectoryException(ResultCode.NOT_ALLOWED_ON_RDN,
                                   ERR_MODIFY_INCREMENT_RDN.get(
                                        String.valueOf(entryDN),
                                        attr.getName()));
    }
    // The provided attribute must have a single value, and it must be an
    // integer.
    LinkedHashSet<AttributeValue> values = attr.getValues();
    if ((values == null) || values.isEmpty())
    {
      throw new DirectoryException(ResultCode.PROTOCOL_ERROR,
                                   ERR_MODIFY_INCREMENT_REQUIRES_VALUE.get(
                                        String.valueOf(entryDN),
                                        attr.getName()));
    }
    else if (values.size() > 1)
    {
      throw new DirectoryException(ResultCode.PROTOCOL_ERROR,
                     ERR_MODIFY_INCREMENT_REQUIRES_SINGLE_VALUE.get(
                          String.valueOf(entryDN), attr.getName()));
    }
    AttributeValue v = values.iterator().next();
    long incrementValue;
    try
    {
      incrementValue = Long.parseLong(v.getNormalizedStringValue());
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
      throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
                     ERR_MODIFY_INCREMENT_PROVIDED_VALUE_NOT_INTEGER.get(
                          String.valueOf(entryDN), attr.getName(),
                          v.getStringValue()), e);
    }
    // Get the corresponding attribute from the entry and make sure that it has
    // a single integer value.
    List<Attribute> attrList = modifiedEntry.getAttribute(t, attr.getOptions());
    if ((attrList == null) || attrList.isEmpty())
    {
      throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
                     ERR_MODIFY_INCREMENT_REQUIRES_EXISTING_VALUE.get(
                          String.valueOf(entryDN), attr.getName()));
    }
    boolean updated = false;
    for (Attribute a : attrList)
    {
      LinkedHashSet<AttributeValue> valueList = a.getValues();
      if ((valueList == null) || valueList.isEmpty())
      {
        continue;
      }
      LinkedHashSet<AttributeValue> newValueList =
        new LinkedHashSet<AttributeValue>(valueList.size());
      for (AttributeValue existingValue : valueList)
      {
        long newIntValue;
        try
        {
          long existingIntValue =
                    Long.parseLong(existingValue.getStringValue());
          newIntValue = existingIntValue + incrementValue;
        }
        catch (Exception e)
        {
          if (debugEnabled())
          {
            TRACER.debugCaught(DebugLogLevel.ERROR, e);
          }
          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
                         ERR_MODIFY_INCREMENT_REQUIRES_INTEGER_VALUE.get(
                              String.valueOf(entryDN), a.getName(),
                              existingValue.getStringValue()), e);
        }
        ByteString newValue = new ASN1OctetString(String.valueOf(newIntValue));
        newValueList.add(new AttributeValue(t, newValue));
      }
      a.setValues(newValueList);
      updated = true;
    }
    if (! updated)
    {
      throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
                     ERR_MODIFY_INCREMENT_REQUIRES_EXISTING_VALUE.get(
                          String.valueOf(entryDN), attr.getName()));
    }
  }
  /**
   * Performs additional preliminary processing that is required for a password
   * change.
   *
   * @throws  DirectoryException  If a problem occurs that should cause the
   *                              modify operation to fail.
   */
  public void performAdditionalPasswordChangedProcessing()
         throws DirectoryException
  {
    // If it was a self change, then see if the current password was provided
    // and handle accordingly.
    if (selfChange &&
        pwPolicyState.getPolicy().requireCurrentPassword() &&
        (! currentPasswordProvided))
    {
      pwpErrorType = PasswordPolicyErrorType.MUST_SUPPLY_OLD_PASSWORD;
      throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
                     ERR_MODIFY_PW_CHANGE_REQUIRES_CURRENT_PW.get());
    }
    // If this change would result in multiple password values, then see if
    // that's OK.
    if ((numPasswords > 1) &&
        (! pwPolicyState.getPolicy().allowMultiplePasswordValues()))
    {
      pwpErrorType = PasswordPolicyErrorType.PASSWORD_MOD_NOT_ALLOWED;
      throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
                     ERR_MODIFY_MULTIPLE_PASSWORDS_NOT_ALLOWED.get());
    }
    // If any of the password values should be validated, then do so now.
    if (selfChange ||
        (! pwPolicyState.getPolicy().skipValidationForAdministrators()))
    {
      if (newPasswords != null)
      {
        HashSet<ByteString> clearPasswords = new HashSet<ByteString>();
        clearPasswords.addAll(pwPolicyState.getClearPasswords());
        if (currentPasswords != null)
        {
          if (clearPasswords.isEmpty())
          {
            for (AttributeValue v : currentPasswords)
            {
              clearPasswords.add(v.getValue());
            }
          }
          else
          {
            // NOTE:  We can't rely on the fact that Set doesn't allow
            // duplicates because technically it's possible that the values
            // aren't duplicates if they are ASN.1 elements with different types
            // (like 0x04 for a standard universal octet string type versus 0x80
            // for a simple password in a bind operation).  So we have to
            // manually check for duplicates.
            for (AttributeValue v : currentPasswords)
            {
              ByteString pw = v.getValue();
              boolean found = false;
              for (ByteString s : clearPasswords)
              {
                if (Arrays.equals(s.value(), pw.value()))
                {
                  found = true;
                  break;
                }
              }
              if (! found)
              {
                clearPasswords.add(pw);
              }
            }
          }
        }
        for (AttributeValue v : newPasswords)
        {
          MessageBuilder invalidReason = new MessageBuilder();
          if (! pwPolicyState.passwordIsAcceptable(this, modifiedEntry,
                                   v.getValue(), clearPasswords, invalidReason))
          {
            pwpErrorType =
                 PasswordPolicyErrorType.INSUFFICIENT_PASSWORD_QUALITY;
            throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
                                         ERR_MODIFY_PW_VALIDATION_FAILED.get(
                                              invalidReason));
          }
        }
      }
    }
    // If we should check the password history, then do so now.
    if (pwPolicyState.maintainHistory())
    {
      if (newPasswords != null)
      {
        for (AttributeValue v : newPasswords)
        {
          if (pwPolicyState.isPasswordInHistory(v.getValue()))
          {
            if (selfChange || (! pwPolicyState.getPolicy().
                                      skipValidationForAdministrators()))
            {
              pwpErrorType = PasswordPolicyErrorType.PASSWORD_IN_HISTORY;
              throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
                                           ERR_MODIFY_PW_IN_HISTORY.get());
            }
          }
        }
        pwPolicyState.updatePasswordHistory();
      }
    }
    // See if the account was locked for any reason.
    wasLocked = pwPolicyState.lockedDueToIdleInterval() ||
                pwPolicyState.lockedDueToMaximumResetAge() ||
                pwPolicyState.lockedDueToFailures();
    // Update the password policy state attributes in the user's entry.  If the
    // modification fails, then these changes won't be applied.
    pwPolicyState.setPasswordChangedTime();
    pwPolicyState.clearFailureLockout();
    pwPolicyState.clearGraceLoginTimes();
    pwPolicyState.clearWarnedTime();
    if (pwPolicyState.getPolicy().forceChangeOnAdd() ||
        pwPolicyState.getPolicy().forceChangeOnReset())
    {
      if (selfChange)
      {
        pwPolicyState.setMustChangePassword(false);
      }
      else
      {
        if ((pwpErrorType == null) &&
            pwPolicyState.getPolicy().forceChangeOnReset())
        {
          pwpErrorType = PasswordPolicyErrorType.CHANGE_AFTER_RESET;
        }
        pwPolicyState.setMustChangePassword(
             pwPolicyState.getPolicy().forceChangeOnReset());
      }
    }
    if (pwPolicyState.getPolicy().getRequireChangeByTime() > 0)
    {
      pwPolicyState.setRequiredChangeTime();
    }
    modifications.addAll(pwPolicyState.getModifications());
    modifiedEntry.applyModifications(pwPolicyState.getModifications());
  }
  /**
   * Checks to ensure that both the Directory Server and the backend are
   * writable.
   *
   * @throws  DirectoryException  If the modify operation should not be allowed
   *                              as a result of the writability check.
   */
  private void checkWritability()
          throws DirectoryException
  {
    // If it is not a private backend, then check to see if the server or
    // backend is operating in read-only mode.
    if (! backend.isPrivateBackend())
    {
      switch (DirectoryServer.getWritabilityMode())
      {
        case DISABLED:
          throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
                                       ERR_MODIFY_SERVER_READONLY.get(
                                            String.valueOf(entryDN)));
        case INTERNAL_ONLY:
          if (! (isInternalOperation() || isSynchronizationOperation()))
          {
            throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
                                         ERR_MODIFY_SERVER_READONLY.get(
                                              String.valueOf(entryDN)));
          }
      }
      switch (backend.getWritabilityMode())
      {
        case DISABLED:
          throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
                                       ERR_MODIFY_BACKEND_READONLY.get(
                                            String.valueOf(entryDN)));
        case INTERNAL_ONLY:
          if (! isInternalOperation() || isSynchronizationOperation())
          {
            throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
                                         ERR_MODIFY_BACKEND_READONLY.get(
                                              String.valueOf(entryDN)));
          }
      }
    }
  }
  /**
   * Handles any account status notifications that may be needed as a result of
   * modify processing.
   */
  private void handleAccountStatusNotifications()
  {
    if (passwordChanged)
    {
      if (selfChange)
      {
        AuthenticationInfo authInfo = clientConnection.getAuthenticationInfo();
        if (authInfo.getAuthenticationDN().equals(modifiedEntry.getDN()))
        {
          clientConnection.setMustChangePassword(false);
        }
        Message message = INFO_MODIFY_PASSWORD_CHANGED.get();
        pwPolicyState.generateAccountStatusNotification(
            AccountStatusNotificationType.PASSWORD_CHANGED,
            modifiedEntry, message,
            AccountStatusNotification.createProperties(pwPolicyState, false, -1,
                 currentPasswords, newPasswords));
      }
      else
      {
        Message message = INFO_MODIFY_PASSWORD_RESET.get();
        pwPolicyState.generateAccountStatusNotification(
            AccountStatusNotificationType.PASSWORD_RESET, modifiedEntry,
            message,
            AccountStatusNotification.createProperties(pwPolicyState, false, -1,
                 currentPasswords, newPasswords));
      }
    }
    if (enabledStateChanged)
    {
      if (isEnabled)
      {
        Message message = INFO_MODIFY_ACCOUNT_ENABLED.get();
        pwPolicyState.generateAccountStatusNotification(
            AccountStatusNotificationType.ACCOUNT_ENABLED,
            modifiedEntry, message,
            AccountStatusNotification.createProperties(pwPolicyState, false, -1,
                 null, null));
      }
      else
      {
        Message message = INFO_MODIFY_ACCOUNT_DISABLED.get();
        pwPolicyState.generateAccountStatusNotification(
            AccountStatusNotificationType.ACCOUNT_DISABLED,
            modifiedEntry, message,
            AccountStatusNotification.createProperties(pwPolicyState, false, -1,
                 null, null));
      }
    }
    if (wasLocked)
    {
      Message message = INFO_MODIFY_ACCOUNT_UNLOCKED.get();
      pwPolicyState.generateAccountStatusNotification(
          AccountStatusNotificationType.ACCOUNT_UNLOCKED, modifiedEntry,
          message,
          AccountStatusNotification.createProperties(pwPolicyState, false, -1,
               null, null));
    }
  }
  /**
   * Handles any processing that is required for the LDAP pre-read and/or
   * post-read controls.
   */
  private void handleReadEntryProcessing()
  {
    if (preReadRequest != null)
    {
      Entry entry = currentEntry.duplicate(true);
      if (! preReadRequest.allowsAttribute(
                 DirectoryServer.getObjectClassAttributeType()))
      {
        entry.removeAttribute(
                   DirectoryServer.getObjectClassAttributeType());
      }
      if (! preReadRequest.returnAllUserAttributes())
      {
        Iterator<AttributeType> iterator =
             entry.getUserAttributes().keySet().iterator();
        while (iterator.hasNext())
        {
          AttributeType attrType = iterator.next();
          if (! preReadRequest.allowsAttribute(attrType))
          {
            iterator.remove();
          }
        }
      }
      if (! preReadRequest.returnAllOperationalAttributes())
      {
        Iterator<AttributeType> iterator =
             entry.getOperationalAttributes().keySet().iterator();
        while (iterator.hasNext())
        {
          AttributeType attrType = iterator.next();
          if (! preReadRequest.allowsAttribute(attrType))
          {
            iterator.remove();
          }
        }
      }
      // FIXME -- Check access controls on the entry to see if it should be
      //          returned or if any attributes need to be stripped out..
      SearchResultEntry searchEntry = new SearchResultEntry(entry);
      LDAPPreReadResponseControl responseControl =
           new LDAPPreReadResponseControl(preReadRequest.getOID(),
                                          preReadRequest.isCritical(),
                                          searchEntry);
      getResponseControls().add(responseControl);
    }
    if (postReadRequest != null)
    {
      Entry entry = modifiedEntry.duplicate(true);
      if (! postReadRequest.allowsAttribute(
                 DirectoryServer.getObjectClassAttributeType()))
      {
        entry.removeAttribute(
                   DirectoryServer.getObjectClassAttributeType());
      }
      if (! postReadRequest.returnAllUserAttributes())
      {
        Iterator<AttributeType> iterator =
             entry.getUserAttributes().keySet().iterator();
        while (iterator.hasNext())
        {
          AttributeType attrType = iterator.next();
          if (! postReadRequest.allowsAttribute(attrType))
          {
            iterator.remove();
          }
        }
      }
      if (! postReadRequest.returnAllOperationalAttributes())
      {
        Iterator<AttributeType> iterator =
             entry.getOperationalAttributes().keySet().iterator();
        while (iterator.hasNext())
        {
          AttributeType attrType = iterator.next();
          if (! postReadRequest.allowsAttribute(attrType))
          {
            iterator.remove();
          }
        }
      }
      // FIXME -- Check access controls on the entry to see if it should be
      //          returned or if any attributes need to be stripped out..
      SearchResultEntry searchEntry = new SearchResultEntry(entry);
      LDAPPostReadResponseControl responseControl =
           new LDAPPostReadResponseControl(postReadRequest.getOID(),
                                           postReadRequest.isCritical(),
                                           searchEntry);
      getResponseControls().add(responseControl);
    }
  }
}
opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendSearchOperation.java
@@ -27,85 +27,621 @@
package org.opends.server.workflowelement.localbackend;
import java.util.List;
import org.opends.messages.Message;
import org.opends.server.api.Backend;
import org.opends.server.api.ClientConnection;
import org.opends.server.api.plugin.PostOperationPluginResult;
import org.opends.server.api.plugin.PreOperationPluginResult;
import org.opends.server.controls.LDAPAssertionRequestControl;
import org.opends.server.controls.MatchedValuesControl;
import org.opends.server.controls.PersistentSearchControl;
import org.opends.server.controls.ProxiedAuthV1Control;
import org.opends.server.controls.ProxiedAuthV2Control;
import org.opends.server.core.AccessControlConfigManager;
import org.opends.server.core.DirectoryServer;
import org.opends.server.core.PersistentSearch;
import org.opends.server.core.PluginConfigManager;
import org.opends.server.core.SearchOperationWrapper;
import org.opends.server.core.SearchOperation;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.types.CancelResult;
import org.opends.server.types.CancelledOperationException;
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.LDAPException;
import org.opends.server.types.Privilege;
import org.opends.server.types.ResultCode;
import org.opends.server.types.SearchFilter;
import org.opends.server.types.operation.PostOperationSearchOperation;
import org.opends.server.types.operation.PreOperationSearchOperation;
import org.opends.server.types.operation.SearchEntrySearchOperation;
import org.opends.server.types.operation.SearchReferenceSearchOperation;
import static org.opends.messages.CoreMessages.*;
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 search for entries in a local backend
 * of the Directory Server.
 */
public class LocalBackendSearchOperation extends SearchOperationWrapper
  implements PreOperationSearchOperation,
             PostOperationSearchOperation,
             SearchEntrySearchOperation,
             SearchReferenceSearchOperation
public class LocalBackendSearchOperation
       extends SearchOperationWrapper
       implements PreOperationSearchOperation, PostOperationSearchOperation,
                  SearchEntrySearchOperation, SearchReferenceSearchOperation
{
  /**
   * The tracer object for the debug logger.
   */
  private static final DebugTracer TRACER = getTracer();
  // The backend in which the search is to be performed.
  private Backend backend;
  // Indicates whether we should actually process the search.  This should
  // only be false if it's a persistent search with changesOnly=true.
  private boolean processSearch;
  // Indicates whether to skip post-operation plugin processing.
  private boolean skipPostOperation;
  // The client connection for the search operation.
  private ClientConnection clientConnection;
  // The base DN for the search.
  private DN baseDN;
  // The persistent search request, if applicable.
  private PersistentSearch persistentSearch;
  // The filter for the search.
  private SearchFilter filter;
  /**
   * Creates a new operation that may be used to search for entries in a
   * local backend of the Directory Server.
   * Creates a new operation that may be used to search for entries in a local
   * backend of the Directory Server.
   *
   * @param search The operation to enhance.
   * @param  search  The operation to process.
   */
  public LocalBackendSearchOperation(SearchOperation search){
  public LocalBackendSearchOperation(SearchOperation search)
  {
    super(search);
    LocalBackendWorkflowElement.attachLocalOperation(search, this);
  }
  /**
   * Processes the search in the provided backend and recursively through its
   * subordinate backends.
   * Process this search operation against a local backend.
   *
   * @param  backend  The backend in which to process the search.
   *
   * @throws  DirectoryException  If a problem occurs while processing the
   *                              search.
   *
   * @throws  CancelledOperationException  If the backend noticed and reacted
   *                                       to a request to cancel or abandon the
   *                                       search operation.
   * @param  backend  The backend in which the search operation should be
   *                  performed.
   */
  public final void searchBackend(Backend backend)
          throws DirectoryException, CancelledOperationException
  void processLocalSearch(Backend backend)
  {
    // Check for and handle a request to cancel this operation.
    this.backend = backend;
    clientConnection = getClientConnection();
    // Get the plugin config manager that will be used for invoking plugins.
    PluginConfigManager pluginConfigManager =
      DirectoryServer.getPluginConfigManager();
    skipPostOperation = false;
    processSearch = true;
    // Create a labeled block of code that we can break out of if a problem is
    // detected.
searchProcessing:
    {
      // Process the search base and filter to convert them from their raw forms
      // as provided by the client to the forms required for the rest of the
      // search processing.
      baseDN = getBaseDN();
      filter = getFilter();
      if ((baseDN == null) || (filter == null)){
        break searchProcessing;
      }
      // 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 searchProcessing;
      }
      // Check to see if the client has permission to perform the
      // search.
      // FIXME: for now assume that this will check all permission
      // pertinent to the operation. This includes proxy authorization
      // and any other controls specified.
      if (! AccessControlConfigManager.getInstance().getAccessControlHandler().
                 isAllowed(this))
      {
        setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
        appendErrorMessage(ERR_SEARCH_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS.get(
                                String.valueOf(baseDN)));
        skipPostOperation = true;
        break searchProcessing;
      }
      // Check for a request to cancel this operation.
    if (getCancelRequest() != null)
    {
      setCancelResult(CancelResult.CANCELED);
      setProcessingStopTime();
      return;
    }
    // Perform the search in the provided backend.
      // Invoke the pre-operation search plugins.
      PreOperationPluginResult preOpResult =
           pluginConfigManager.invokePreOperationSearchPlugins(this);
      if (preOpResult.connectionTerminated())
      {
        // There's no point in continuing with anything.  Log the request and
        // result and return.
        setResultCode(ResultCode.CANCELED);
        appendErrorMessage(ERR_CANCELED_BY_PREOP_DISCONNECT.get());
        setProcessingStopTime();
        return;
      }
      else if (preOpResult.sendResponseImmediately())
      {
        skipPostOperation = true;
        break searchProcessing;
      }
      else if (preOpResult.skipCoreProcessing())
      {
        skipPostOperation = false;
        break searchProcessing;
      }
      // Check for a request to cancel this operation.
      if (getCancelRequest() != null)
      {
        return;
      }
      // Get the backend that should hold the search base.  If there is none,
      // then fail.
      if (backend == null)
      {
        setResultCode(ResultCode.NO_SUCH_OBJECT);
        appendErrorMessage(ERR_SEARCH_BASE_DOESNT_EXIST.get(
                                String.valueOf(baseDN)));
        break searchProcessing;
      }
      // We'll set the result code to "success".  If a problem occurs, then it
      // will be overwritten.
      setResultCode(ResultCode.SUCCESS);
      // If there's a persistent search, then register it with the server.
      if (persistentSearch != null)
      {
        DirectoryServer.registerPersistentSearch(persistentSearch);
        setSendResponse(false);
      }
      // Process the search in the backend and all its subordinates.
      try
      {
        if (processSearch)
        {
    backend.search(this);
    // Search in the subordinate backends is now done by the workflows.
    // If there are any subordinate backends, then process the search there as
    // well.
    // FIXME jdemendi - From now on, do not search in the subordinate backends
    // because this is done by the workflow topology.
//    Backend[] subBackends = backend.getSubordinateBackends();
//    for (Backend b : subBackends)
//    {
//      DN[] baseDNs = b.getBaseDNs();
//      for (DN dn : baseDNs)
//      {
//        if (dn.isDescendantOf(getBaseDN()))
//        {
//          searchBackend(b);
//          break;
//        }
//      }
//    }
  }
}
      catch (DirectoryException de)
      {
        if (debugEnabled())
        {
          TRACER.debugCaught(DebugLogLevel.ERROR, de);
        }
        setResponseData(de);
        if (persistentSearch != null)
        {
          DirectoryServer.deregisterPersistentSearch(persistentSearch);
          setSendResponse(true);
        }
        break searchProcessing;
      }
      catch (CancelledOperationException coe)
      {
        if (debugEnabled())
        {
          TRACER.debugCaught(DebugLogLevel.ERROR, coe);
        }
        CancelResult cancelResult = coe.getCancelResult();
        setCancelResult(cancelResult);
        setResultCode(cancelResult.getResultCode());
        Message message = coe.getMessageObject();
        if ((message != null) && (message.length() > 0))
        {
          appendErrorMessage(message);
        }
        if (persistentSearch != null)
        {
          DirectoryServer.deregisterPersistentSearch(persistentSearch);
          setSendResponse(true);
        }
        skipPostOperation = true;
        break searchProcessing;
      }
      catch (Exception e)
      {
        if (debugEnabled())
        {
          TRACER.debugCaught(DebugLogLevel.ERROR, e);
        }
        setResultCode(DirectoryServer.getServerErrorResultCode());
        appendErrorMessage(ERR_SEARCH_BACKEND_EXCEPTION.get(
                                getExceptionMessage(e)));
        if (persistentSearch != null)
        {
          DirectoryServer.deregisterPersistentSearch(persistentSearch);
          setSendResponse(true);
        }
        skipPostOperation = true;
        break searchProcessing;
      }
    }
    // Check for a request to cancel this operation.
    if (getCancelRequest() != null)
    {
      return;
    }
    // Invoke the post-operation search plugins.
    if (! skipPostOperation)
    {
      PostOperationPluginResult postOperationResult =
           pluginConfigManager.invokePostOperationSearchPlugins(this);
      if (postOperationResult.connectionTerminated())
      {
        setResultCode(ResultCode.CANCELED);
        appendErrorMessage(ERR_CANCELED_BY_POSTOP_DISCONNECT.get());
        setProcessingStopTime();
        return;
      }
    }
  }
  /**
   * Handles any controls contained in the request.
   *
   * @throws  DirectoryException  If there is a problem with any of the request
   *                              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(baseDN, this, c))
        {
          skipPostOperation = true;
          throw new DirectoryException(ResultCode.INSUFFICIENT_ACCESS_RIGHTS,
                         ERR_CONTROL_INSUFFICIENT_ACCESS_RIGHTS.get(oid));
        }
        if (oid.equals(OID_LDAP_ASSERTION))
        {
          LDAPAssertionRequestControl assertControl;
          if (c instanceof LDAPAssertionRequestControl)
          {
            assertControl = (LDAPAssertionRequestControl) c;
          }
          else
          {
            try
            {
              assertControl = LDAPAssertionRequestControl.decodeControl(c);
              requestControls.set(i, assertControl);
            }
            catch (LDAPException le)
            {
              if (debugEnabled())
              {
                TRACER.debugCaught(DebugLogLevel.ERROR, le);
              }
              throw new DirectoryException(
                             ResultCode.valueOf(le.getResultCode()),
                             le.getMessageObject(), le);
            }
          }
          try
          {
            // FIXME -- We need to determine whether the current user has
            //          permission to make this determination.
            SearchFilter assertionFilter = assertControl.getSearchFilter();
            Entry entry;
            try
            {
              entry = DirectoryServer.getEntry(baseDN);
            }
            catch (DirectoryException de)
            {
              if (debugEnabled())
              {
                TRACER.debugCaught(DebugLogLevel.ERROR, de);
              }
              throw new DirectoryException(de.getResultCode(),
                             ERR_SEARCH_CANNOT_GET_ENTRY_FOR_ASSERTION.get(
                                  de.getMessageObject()));
            }
            if (entry == null)
            {
              throw new DirectoryException(ResultCode.NO_SUCH_OBJECT,
                             ERR_SEARCH_NO_SUCH_ENTRY_FOR_ASSERTION.get());
            }
            if (! assertionFilter.matchesEntry(entry))
            {
              throw new DirectoryException(ResultCode.ASSERTION_FAILED,
                                           ERR_SEARCH_ASSERTION_FAILED.get());
            }
          }
          catch (DirectoryException de)
          {
            if (de.getResultCode() == ResultCode.ASSERTION_FAILED)
            {
              throw de;
            }
            if (debugEnabled())
            {
              TRACER.debugCaught(DebugLogLevel.ERROR, de);
            }
            throw new DirectoryException(ResultCode.PROTOCOL_ERROR,
                           ERR_SEARCH_CANNOT_PROCESS_ASSERTION_FILTER.get(
                                de.getMessageObject()), de);
          }
        }
        else if (oid.equals(OID_PROXIED_AUTH_V1))
        {
          // The requester must have the PROXIED_AUTH privilige in order to be
          // able to use this control.
          if (! clientConnection.hasPrivilege(Privilege.PROXIED_AUTH, this))
          {
            throw new DirectoryException(ResultCode.AUTHORIZATION_DENIED,
                           ERR_PROXYAUTH_INSUFFICIENT_PRIVILEGES.get());
          }
          ProxiedAuthV1Control proxyControl;
          if (c instanceof ProxiedAuthV1Control)
          {
            proxyControl = (ProxiedAuthV1Control) c;
          }
          else
          {
            try
            {
              proxyControl = ProxiedAuthV1Control.decodeControl(c);
            }
            catch (LDAPException le)
            {
              if (debugEnabled())
              {
                TRACER.debugCaught(DebugLogLevel.ERROR, le);
              }
              throw new DirectoryException(
                             ResultCode.valueOf(le.getResultCode()),
                             le.getMessageObject(), le);
            }
          }
          Entry authorizationEntry = proxyControl.getAuthorizationEntry();
          setAuthorizationEntry(authorizationEntry);
          if (authorizationEntry == null)
          {
            setProxiedAuthorizationDN(DN.nullDN());
          }
          else
          {
            setProxiedAuthorizationDN(authorizationEntry.getDN());
          }
        }
        else if (oid.equals(OID_PROXIED_AUTH_V2))
        {
          // The requester must have the PROXIED_AUTH privilige in order to be
          // able to use this control.
          if (! clientConnection.hasPrivilege(Privilege.PROXIED_AUTH, this))
          {
            throw new DirectoryException(ResultCode.AUTHORIZATION_DENIED,
                           ERR_PROXYAUTH_INSUFFICIENT_PRIVILEGES.get());
          }
          ProxiedAuthV2Control proxyControl;
          if (c instanceof ProxiedAuthV2Control)
          {
            proxyControl = (ProxiedAuthV2Control) c;
          }
          else
          {
            try
            {
              proxyControl = ProxiedAuthV2Control.decodeControl(c);
            }
            catch (LDAPException le)
            {
              if (debugEnabled())
              {
                TRACER.debugCaught(DebugLogLevel.ERROR, le);
              }
              throw new DirectoryException(
                             ResultCode.valueOf(le.getResultCode()),
                             le.getMessageObject(), le);
            }
          }
          Entry authorizationEntry = proxyControl.getAuthorizationEntry();
          setAuthorizationEntry(authorizationEntry);
          if (authorizationEntry == null)
          {
            setProxiedAuthorizationDN(DN.nullDN());
          }
          else
          {
            setProxiedAuthorizationDN(authorizationEntry.getDN());
          }
        }
        else if (oid.equals(OID_PERSISTENT_SEARCH))
        {
          PersistentSearchControl psearchControl;
          if (c instanceof PersistentSearchControl)
          {
            psearchControl = (PersistentSearchControl) c;
          }
          else
          {
            try
            {
              psearchControl = PersistentSearchControl.decodeControl(c);
            }
            catch (LDAPException le)
            {
              if (debugEnabled())
              {
                TRACER.debugCaught(DebugLogLevel.ERROR, le);
              }
              throw new DirectoryException(
                             ResultCode.valueOf(le.getResultCode()),
                             le.getMessageObject(), le);
            }
          }
          persistentSearch = new PersistentSearch(this,
                                      psearchControl.getChangeTypes(),
                                      psearchControl.getReturnECs());
          setPersistentSearch(persistentSearch);
          // If we're only interested in changes, then we don't actually want
          // to process the search now.
          if (psearchControl.getChangesOnly())
          {
            processSearch = false;
          }
        }
        else if (oid.equals(OID_LDAP_SUBENTRIES))
        {
          setReturnLDAPSubentries(true);
        }
        else if (oid.equals(OID_MATCHED_VALUES))
        {
          if (c instanceof MatchedValuesControl)
          {
            setMatchedValuesControl((MatchedValuesControl) c);
          }
          else
          {
            try
            {
              MatchedValuesControl matchedValuesControl =
                MatchedValuesControl.decodeControl(c);
              setMatchedValuesControl(matchedValuesControl);
            }
            catch (LDAPException le)
            {
              if (debugEnabled())
              {
                TRACER.debugCaught(DebugLogLevel.ERROR, le);
              }
              throw new DirectoryException(
                             ResultCode.valueOf(le.getResultCode()),
                             le.getMessageObject(), le);
            }
          }
        }
        else if (oid.equals(OID_ACCOUNT_USABLE_CONTROL))
        {
          setIncludeUsableControl(true);
        }
        else if (oid.equals(OID_REAL_ATTRS_ONLY))
        {
          setRealAttributesOnly(true);
        }
        else if (oid.equals(OID_VIRTUAL_ATTRS_ONLY))
        {
          setVirtualAttributesOnly(true);
        }
        // NYI -- Add support for additional controls.
        else if (c.isCritical())
        {
          if ((backend == null) || (! backend.supportsControl(oid)))
          {
            throw new DirectoryException(
                           ResultCode.UNAVAILABLE_CRITICAL_EXTENSION,
                           ERR_SEARCH_UNSUPPORTED_CRITICAL_CONTROL.get(oid));
          }
        }
      }
    }
  }
}
opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendWorkflowElement.java
Diff too large