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

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