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

Jean-Noel Rouvignac
25.21.2013 fe4d6b1f8ee49c858ca2644851377ba2402d9509
OPENDJ-948 (CR-1873) unauthorized disclosure of directory contents 

This commit addresses information disclosure for:

- result code: change from info disclosing result codes to a default result code that hides the ACI-protected info to the current operation.
- error message (additional information): any message containing the entryDN will be filtered out if the entryDN is ACI-protected.
- matchedDN: check whether the matchedDN is ACI-protected
- debugsearchindex


Information disclosing result code and error message are now saved as masked result code and masked message and are logged as such when they are present.



config.ldif:
Added "debugsearchindex" to an ACI to prevent Anonymous Read Access. If this is not enough, then we would need to create a new ACI.


AccessControlHandler.java:
Added canDiscloseInformation().

Operation.java, AbstractOperation.java, OperationWrapper.java:
Added getMaskedResultCode(), setMaskedResultCode(), getMaskedErrorMessage(), setMaskedErrorMessage(), appendMaskedErrorMessage().
In setReponseData(), copied the masked result code and error message.

DirectoryException.java:
Added getMaskedResultCode(), setMaskedResultCode(), getMaskedErrorMessage(), setMaskedErrorMessage().

LocalBackendWorkflowElement.java:
Used javadocs.
Created method newDirectoryException(), setResultCodeAndMessageNoInfoDisclosure() and filterNonDisclosableMatchedDN().

LocalBackend*Operation.java:
Added setResultCodeAndMessageNoInfoDisclosure() and newDirectoryException() forwarding to the corresponding LocalBackendWorkflowElement methods + extensively made use of these methods.
In processLocal*() methods, added a try/finally to call LocalBackendWorkflowElement.filterNonDisclosableMatchedDN().
Moved some blocks of code under the protection of try/catch blocks to ensure proper error handling when calling the exception throwing setResultCodeAndMessageNoInfoDisclosure() + as a consequence, moved some null checks to the finally blocks.
Removed some try/catch duplicating outer try/catch blocks.

LocalBackendModifyOperation.java:
In processLocalModify(), removed try/catch around checkWritability() because catch is duplicated with outer try/catch.
Used StaticUtils.collectionToString().

TextAccessLogPublisher.java:
Renamed appendMessage() into appendResultCodeAndMessage() + integrated there logging of "result" + added logging of "maskedResult" and "maskedMessage".
15 files modified
1401 ■■■■■ changed files
opends/resource/config/config.ldif 2 ●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/api/AccessControlHandler.java 37 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/OperationWrapper.java 42 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/loggers/TextAccessLogPublisher.java 63 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/types/AbstractOperation.java 84 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/types/DirectoryException.java 85 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/types/Operation.java 46 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendAddOperation.java 164 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendCompareOperation.java 119 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendDeleteOperation.java 133 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendModifyDNOperation.java 176 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendModifyOperation.java 222 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendSearchOperation.java 41 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendWorkflowElement.java 185 ●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/authorization/dseecompat/TargetControlTestCase.java 2 ●●● patch | view | raw | blame | history
opends/resource/config/config.ldif
@@ -77,7 +77,7 @@
ds-cfg-global-aci: (extop="1.3.6.1.4.1.26027.1.6.1 || 1.3.6.1.4.1.26027.1.6.3 || 1.3.6.1.4.1.4203.1.11.1 || 1.3.6.1.4.1.1466.20037 || 1.3.6.1.4.1.4203.1.11.3") (version 3.0; acl "Anonymous extended operation access"; allow(read) userdn="ldap:///anyone";)
ds-cfg-global-aci: (targetcontrol="2.16.840.1.113730.3.4.2 || 2.16.840.1.113730.3.4.17 || 2.16.840.1.113730.3.4.19 || 1.3.6.1.4.1.4203.1.10.2 || 1.3.6.1.4.1.42.2.27.8.5.1 || 2.16.840.1.113730.3.4.16 || 1.2.840.113556.1.4.1413") (version 3.0; acl "Anonymous control access"; allow(read) userdn="ldap:///anyone";)
ds-cfg-global-aci: (targetcontrol="1.3.6.1.1.12 || 1.3.6.1.1.13.1 || 1.3.6.1.1.13.2 || 1.2.840.113556.1.4.319 || 1.2.826.0.1.3344810.2.3 || 2.16.840.1.113730.3.4.18 || 2.16.840.1.113730.3.4.9 || 1.2.840.113556.1.4.473 || 1.3.6.1.4.1.42.2.27.9.5.9") (version 3.0; acl "Authenticated users control access"; allow(read) userdn="ldap:///all";)
ds-cfg-global-aci: (targetattr!="userPassword||authPassword||changes||changeNumber||changeType||changeTime||targetDN||newRDN||newSuperior||deleteOldRDN||targetEntryUUID||changeInitiatorsName||changeLogCookie||includedAttributes")(version 3.0; acl "Anonymous read access"; allow (read,search,compare) userdn="ldap:///anyone";)
ds-cfg-global-aci: (targetattr!="userPassword||authPassword||changes||changeNumber||changeType||changeTime||debugsearchindex||targetDN||newRDN||newSuperior||deleteOldRDN||targetEntryUUID||changeInitiatorsName||changeLogCookie||includedAttributes")(version 3.0; acl "Anonymous read access"; allow (read,search,compare) userdn="ldap:///anyone";)
ds-cfg-global-aci: (targetattr="audio||authPassword||description||displayName||givenName||homePhone||homePostalAddress||initials||jpegPhoto||labeledURI||mobile||pager||postalAddress||postalCode||preferredLanguage||telephoneNumber||userPassword")(version 3.0; acl "Self entry modification"; allow (write) userdn="ldap:///self";)
ds-cfg-global-aci: (targetattr="userPassword||authPassword")(version 3.0; acl "Self entry read"; allow (read,search,compare) userdn="ldap:///self";)
ds-cfg-global-aci: (target="ldap:///cn=schema")(targetscope="base")(targetattr="objectClass||attributeTypes||dITContentRules||dITStructureRules||ldapSyntaxes||matchingRules||matchingRuleUse||nameForms||objectClasses")(version 3.0; acl "User-Visible Schema Operational Attributes"; allow (read,search,compare) userdn="ldap:///anyone";)
opends/src/server/org/opends/server/api/AccessControlHandler.java
@@ -23,7 +23,7 @@
 *
 *
 *      Copyright 2006-2009 Sun Microsystems, Inc.
 *      Portions Copyright 2011 ForgeRock AS
 *      Portions Copyright 2011-2013 ForgeRock AS
 */
package org.opends.server.api;
@@ -55,6 +55,7 @@
public abstract class AccessControlHandler
                      <T extends AccessControlHandlerCfg>
{
  /**
   * Initializes the access control handler implementation based on
   * the information in the provided configuration entry.
@@ -115,6 +116,40 @@
  public abstract void finalizeAccessControlHandler();
  /**
   * Checks whether the ACIs prevent sending information about the provided
   * entry, or entryDN if entry is null.
   *
   * @param entry
   *          the entry for which to check if ACIs prevent information
   *          disclosure, if null, then a fake entry will be created from the
   *          entryDN parameter
   * @param entryDN
   *          the entry dn for which to check if ACIs prevent information
   *          disclosure. Only used if entry is null.
   * @param operation
   *          the operation for which to check if ACIs prevent information
   *          disclosure
   * @return true if the information for this entry can be disclosed, false
   *         otherwise.
   * @throws DirectoryException
   *           If an error occurred while performing the access control check.
   */
  public boolean canDiscloseInformation(Entry entry, DN entryDN,
      Operation operation) throws DirectoryException
  {
    if (entry == null)
    {
      entry = DirectoryServer.getEntry(entryDN);
    }
    if (entry == null)
    {
      // no such entry exist, let's be safe and forbid any info disclosure.
      return false;
    }
    return maySend(operation, new SearchResultEntry(entry, operation
        .getResponseControls()));
  }
  /**
   * Indicates whether the provided add operation is allowed based on
opends/src/server/org/opends/server/core/OperationWrapper.java
@@ -86,6 +86,13 @@
    operation.appendErrorMessage(message);
  }
  /** {@inheritDoc} */
  @Override
  public void appendMaskedErrorMessage(Message maskedMessage)
  {
    operation.appendMaskedErrorMessage(maskedMessage);
  }
  /**
   * {@inheritDoc}
   */
@@ -208,6 +215,20 @@
    return operation.getErrorMessage();
  }
  /** {@inheritDoc} */
  @Override
  public MessageBuilder getMaskedErrorMessage()
  {
    return operation.getMaskedErrorMessage();
  }
  /** {@inheritDoc} */
  @Override
  public ResultCode getMaskedResultCode()
  {
    return operation.getMaskedResultCode();
  }
  /**
   * {@inheritDoc}
   */
@@ -462,6 +483,20 @@
    operation.setInternalOperation(isInternalOperation);
  }
  /** {@inheritDoc} */
  @Override
  public void setMaskedErrorMessage(MessageBuilder maskedErrorMessage)
  {
    operation.setMaskedErrorMessage(maskedErrorMessage);
  }
  /** {@inheritDoc} */
  @Override
  public void setMaskedResultCode(ResultCode maskedResultCode)
  {
    operation.setMaskedResultCode(maskedResultCode);
  }
  /**
   * {@inheritDoc}
   */
@@ -539,6 +574,13 @@
    return false;
  }
  /** {@inheritDoc} */
  @Override
  public String toString()
  {
    return "Wrapped " + operation.toString();
  }
  /**
   * {@inheritDoc}
   */
opends/src/server/org/opends/server/loggers/TextAccessLogPublisher.java
@@ -447,9 +447,7 @@
    {
      appendAbandonRequest(abandonOperation, buffer);
    }
    buffer.append(" result=");
    buffer.append(abandonOperation.getResultCode().getIntValue());
    appendMessage(buffer, abandonOperation);
    appendResultCodeAndMessage(buffer, abandonOperation);
    logAdditionalLogItems(abandonOperation, buffer);
@@ -506,10 +504,7 @@
    {
      appendAddRequest(addOperation, buffer);
    }
    buffer.append(" result=");
    buffer.append(addOperation.getResultCode().getIntValue());
    appendMessage(buffer, addOperation);
    appendResultCodeAndMessage(buffer, addOperation);
    logAdditionalLogItems(addOperation, buffer);
@@ -569,10 +564,7 @@
    {
      appendBindRequest(bindOperation, buffer);
    }
    buffer.append(" result=");
    buffer.append(bindOperation.getResultCode().getIntValue());
    appendMessage(buffer, bindOperation);
    appendResultCodeAndMessage(buffer, bindOperation);
    final Message failureMessage = bindOperation.getAuthFailureReason();
    if (failureMessage != null)
@@ -674,10 +666,7 @@
    {
      appendCompareRequest(compareOperation, buffer);
    }
    buffer.append(" result=");
    buffer.append(compareOperation.getResultCode().getIntValue());
    appendMessage(buffer, compareOperation);
    appendResultCodeAndMessage(buffer, compareOperation);
    logAdditionalLogItems(compareOperation, buffer);
@@ -773,10 +762,7 @@
    {
      appendDeleteRequest(deleteOperation, buffer);
    }
    buffer.append(" result=");
    buffer.append(deleteOperation.getResultCode().getIntValue());
    appendMessage(buffer, deleteOperation);
    appendResultCodeAndMessage(buffer, deleteOperation);
    logAdditionalLogItems(deleteOperation, buffer);
@@ -885,11 +871,7 @@
      }
      appendLabel(buffer, "oid", oid);
    }
    buffer.append(" result=");
    buffer.append(extendedOperation.getResultCode().getIntValue());
    appendMessage(buffer, extendedOperation);
    appendResultCodeAndMessage(buffer, extendedOperation);
    logAdditionalLogItems(extendedOperation, buffer);
@@ -946,10 +928,7 @@
    {
      appendModifyDNRequest(modifyDNOperation, buffer);
    }
    buffer.append(" result=");
    buffer.append(modifyDNOperation.getResultCode().getIntValue());
    appendMessage(buffer, modifyDNOperation);
    appendResultCodeAndMessage(buffer, modifyDNOperation);
    logAdditionalLogItems(modifyDNOperation, buffer);
@@ -1009,10 +988,7 @@
    {
      appendModifyRequest(modifyOperation, buffer);
    }
    buffer.append(" result=");
    buffer.append(modifyOperation.getResultCode().getIntValue());
    appendMessage(buffer, modifyOperation);
    appendResultCodeAndMessage(buffer, modifyOperation);
    logAdditionalLogItems(modifyOperation, buffer);
@@ -1072,10 +1048,7 @@
    {
      appendSearchRequest(searchOperation, buffer);
    }
    buffer.append(" result=");
    buffer.append(searchOperation.getResultCode().getIntValue());
    appendMessage(buffer, searchOperation);
    appendResultCodeAndMessage(buffer, searchOperation);
    buffer.append(" nentries=");
    buffer.append(searchOperation.getEntriesSent());
@@ -1267,14 +1240,28 @@
    }
  }
  private void appendMessage(final StringBuilder buffer,
      final Operation operation)
  private void appendResultCodeAndMessage(StringBuilder buffer,
      Operation operation)
  {
    buffer.append(" result=");
    buffer.append(operation.getResultCode().getIntValue());
    final MessageBuilder msg = operation.getErrorMessage();
    if ((msg != null) && (msg.length() > 0))
    {
      appendLabel(buffer, "message", msg);
    }
    if (operation.getMaskedResultCode() != null)
    {
      buffer.append(" maskedResult=");
      buffer.append(operation.getMaskedResultCode().getIntValue());
    }
    final MessageBuilder maskedMsg = operation.getMaskedErrorMessage();
    if (maskedMsg != null && maskedMsg.length() > 0)
    {
      appendLabel(buffer, "maskedMessage", maskedMsg);
    }
  }
  private void appendEtime(final StringBuilder buffer,
opends/src/server/org/opends/server/types/AbstractOperation.java
@@ -42,7 +42,6 @@
import org.opends.server.types.operation.PreParseOperation;
import org.opends.server.util.Validator;
/**
 * This class defines a generic operation that may be processed by the
 * Directory Server.  Specific subclasses should implement specific
@@ -139,6 +138,12 @@
  private ResultCode resultCode;
  /**
   * The real, masked result code  for this operation that will not be included
   * in the response to the client, but will be logged.
   */
  private ResultCode maskedResultCode;
  /**
   * Additional information that should be included in the log but not sent to
   * the client.
   */
@@ -151,6 +156,12 @@
  private MessageBuilder errorMessage;
  /**
   * The real, masked error message for this operation that will not be included
   * in the response to the client, but will be logged.
   */
  private MessageBuilder maskedErrorMessage;
  /**
   * Indicates whether this operation needs to be synchronized to other copies
   * of the data.
   */
@@ -355,6 +366,20 @@
  /** {@inheritDoc} */
  @Override
  public final ResultCode getMaskedResultCode()
  {
    return maskedResultCode;
  }
  /** {@inheritDoc} */
  @Override
  public final void setMaskedResultCode(ResultCode maskedResultCode)
  {
    this.maskedResultCode = maskedResultCode;
  }
  /** {@inheritDoc} */
  @Override
  public final MessageBuilder getErrorMessage()
  {
    return errorMessage;
@@ -364,14 +389,7 @@
  @Override
  public final void setErrorMessage(MessageBuilder errorMessage)
  {
    if (errorMessage == null)
    {
      this.errorMessage = new MessageBuilder();
    }
    else
    {
      this.errorMessage = errorMessage;
    }
    this.errorMessage = errorMessage;
  }
  /** {@inheritDoc} */
@@ -380,20 +398,44 @@
  {
    if (errorMessage == null)
    {
      errorMessage = new MessageBuilder(message);
      errorMessage = new MessageBuilder();
    }
    else
    else if (errorMessage.length() > 0)
    {
      if (errorMessage.length() > 0)
      {
        errorMessage.append("  ");
      }
      errorMessage.append(message);
      errorMessage.append("  ");
    }
    errorMessage.append(message);
  }
  /** {@inheritDoc} */
  @Override
  public final MessageBuilder getMaskedErrorMessage()
  {
    return maskedErrorMessage;
  }
  /** {@inheritDoc} */
  @Override
  public final void setMaskedErrorMessage(MessageBuilder maskedErrorMessage)
  {
    this.maskedErrorMessage = maskedErrorMessage;
  }
  /** {@inheritDoc} */
  @Override
  public final void appendMaskedErrorMessage(Message maskedMessage)
  {
    if (maskedErrorMessage == null)
    {
      maskedErrorMessage = new MessageBuilder();
    }
    else if (maskedErrorMessage.length() > 0)
    {
      maskedErrorMessage.append("  ");
    }
    maskedErrorMessage.append(maskedMessage);
  }
  /**
   * {@inheritDoc}
@@ -460,11 +502,13 @@
  public final void setResponseData(
                         DirectoryException directoryException)
  {
    this.resultCode   = directoryException.getResultCode();
    this.matchedDN    = directoryException.getMatchedDN();
    this.referralURLs = directoryException.getReferralURLs();
    this.resultCode       = directoryException.getResultCode();
    this.maskedResultCode = directoryException.getMaskedResultCode();
    this.matchedDN        = directoryException.getMatchedDN();
    this.referralURLs     = directoryException.getReferralURLs();
    appendErrorMessage(directoryException.getMessageObject());
    appendMaskedErrorMessage(directoryException.getMaskedMessage());
  }
opends/src/server/org/opends/server/types/DirectoryException.java
@@ -23,17 +23,13 @@
 *
 *
 *      Copyright 2006-2008 Sun Microsystems, Inc.
 *      Portions Copyright 2013 ForgeRock AS
 */
package org.opends.server.types;
import org.opends.messages.Message;
import java.util.List;
import org.opends.messages.Message;
/**
 * This class defines an exception that may be thrown if a problem
@@ -58,16 +54,38 @@
  // The matched DN for this directory exception.
  /** The matched DN returned to the client for this directory exception. */
  private final DN matchedDN;
  // The set of referral URLs for this directory exception.
  /** The set of referral URLs for this directory exception. */
  private final List<String> referralURLs;
  // The result code for this directory exception.
  /**
   * The result code returned to the client for this directory exception. Note:
   * for security considerations (information leak) this result code might not
   * be the underlying reason why the directory server refused to execute the
   * operation.
   *
   * @see #maskedResultCode for the underlying reason why the directory server
   *      refused to execute the operation
   */
  private final ResultCode resultCode;
  /**
   * If set, this is the real message for this directory exception that cannot
   * be returned to the client, but will be logged.
   *
   * @see #getMessage() for the message returned to the client
   */
  private Message maskedMessage;
  /**
   * If set, this is the real result code for this directory exception that
   * cannot be returned to the client, but will be logged.
   *
   * @see #resultCode for the reason code returned to the client
   */
  private ResultCode maskedResultCode;
  /**
   * Creates a new directory exception with the provided information.
@@ -224,5 +242,52 @@
  {
    return referralURLs;
  }
}
  /**
   * Returns the real, masked message for this directory exception that cannot
   * be returned to the client, but will be logged.
   *
   * @return the real, masked message
   * @see #getMessage() for the message returned to the client
   */
  public Message getMaskedMessage()
  {
    return maskedMessage;
  }
  /**
   * Returns the real result code for this directory exception that cannot be
   * returned to the client, but will be logged.
   *
   * @return the real, masked result code
   * @see #getResultCode() for the result code returned to the client
   */
  public ResultCode getMaskedResultCode()
  {
    return maskedResultCode;
  }
  /**
   * Sets the real message for this directory exception that cannot be returned
   * to the client, but will be logged.
   *
   * @param maskedMessage
   *          the real, masked message to set
   */
  public void setMaskedMessage(Message maskedMessage)
  {
    this.maskedMessage = maskedMessage;
  }
  /**
   * Sets the real result code for this directory exception that cannot be
   * returned to the client, but will be logged.
   *
   * @param maskedResultCode
   *          the real, masked result code to set
   */
  public void setMaskedResultCode(ResultCode maskedResultCode)
  {
    this.maskedResultCode = maskedResultCode;
  }
}
opends/src/server/org/opends/server/types/Operation.java
@@ -211,6 +211,23 @@
  public abstract void setResultCode(ResultCode resultCode);
  /**
   * Retrieves the real, masked result code for this operation.
   *
   * @return The real, masked result code associated for this operation, or
   *         {@code UNDEFINED} if the operation has not yet completed.
   */
  ResultCode getMaskedResultCode();
  /**
   * Specifies the real, masked result code for this operation. This method may
   * not be called by post-response plugins.
   *
   * @param maskedResultCode
   *          The real, masked result code for this operation.
   */
  void setMaskedResultCode(ResultCode maskedResultCode);
  /**
   * Retrieves the error message for this operation.  Its contents may
   * be altered by pre-parse, pre-operation, and post-operation
   * plugins, but not by post-response plugins.
@@ -238,6 +255,35 @@
  public abstract void appendErrorMessage(Message message);
  /**
   * Retrieves the real, masked error message for this operation. Its contents
   * may be altered by pre-parse, pre-operation, and post-operation plugins, but
   * not by post-response plugins.
   *
   * @return The real, masked error message for this operation.
   */
  MessageBuilder getMaskedErrorMessage();
  /**
   * Specifies the real, masked error message for this operation. This method
   * may not be called by post-response plugins.
   *
   * @param maskedErrorMessage
   *          The real, masked error message for this operation.
   */
  void setMaskedErrorMessage(MessageBuilder maskedErrorMessage);
  /**
   * Appends the provided message to the real, masked error message buffer. If
   * the buffer has not yet been created, then this will create it first and
   * then add the provided message. This method may not be called by
   * post-response plugins.
   *
   * @param maskedMessage
   *          The message to append to the real, masked error message
   */
  void appendMaskedErrorMessage(Message maskedMessage);
  /**
   * Returns an unmodifiable list containing the additional log items for this
   * operation, which should be written to the log but not included in the
   * response to the client.
opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendAddOperation.java
@@ -160,34 +160,40 @@
    // Check for a request to cancel this operation.
    checkIfCanceled(false);
    BooleanHolder executePostOpPlugins = new BooleanHolder(false);
    processAdd(clientConnection, executePostOpPlugins);
    PluginConfigManager pluginConfigManager =
        DirectoryServer.getPluginConfigManager();
    // Invoke the post-operation or post-synchronization add plugins.
    if (isSynchronizationOperation())
    try
    {
      if (getResultCode() == ResultCode.SUCCESS)
      BooleanHolder executePostOpPlugins = new BooleanHolder(false);
      processAdd(clientConnection, executePostOpPlugins);
      PluginConfigManager pluginConfigManager =
          DirectoryServer.getPluginConfigManager();
      // Invoke the post-operation or post-synchronization add plugins.
      if (isSynchronizationOperation())
      {
        pluginConfigManager.invokePostSynchronizationAddPlugins(this);
        if (getResultCode() == ResultCode.SUCCESS)
        {
          pluginConfigManager.invokePostSynchronizationAddPlugins(this);
        }
      }
      else if (executePostOpPlugins.value)
      {
        // FIXME -- Should this also be done while holding the locks?
        PluginResult.PostOperation postOpResult =
            pluginConfigManager.invokePostOperationAddPlugins(this);
        if (!postOpResult.continueProcessing())
        {
          setResultCode(postOpResult.getResultCode());
          appendErrorMessage(postOpResult.getErrorMessage());
          setMatchedDN(postOpResult.getMatchedDN());
          setReferralURLs(postOpResult.getReferralURLs());
          return;
        }
      }
    }
    else if (executePostOpPlugins.value)
    finally
    {
      // FIXME -- Should this also be done while holding the locks?
      PluginResult.PostOperation postOpResult =
          pluginConfigManager.invokePostOperationAddPlugins(this);
      if (!postOpResult.continueProcessing())
      {
        setResultCode(postOpResult.getResultCode());
        appendErrorMessage(postOpResult.getErrorMessage());
        setMatchedDN(postOpResult.getMatchedDN());
        setReferralURLs(postOpResult.getReferralURLs());
        return;
      }
      LocalBackendWorkflowElement.filterNonDisclosableMatchedDN(this);
    }
    // Register a post-response call-back which will notify persistent
@@ -269,9 +275,8 @@
      entryLock = LockManager.lockWrite(entryDN);
      if (entryLock == null)
      {
        setResultCode(ResultCode.BUSY);
        appendErrorMessage(ERR_ADD_CANNOT_LOCK_ENTRY.get(String
            .valueOf(entryDN)));
        setResultCodeAndMessageNoInfoDisclosure(entryDN, ResultCode.BUSY,
            ERR_ADD_CANNOT_LOCK_ENTRY.get(String.valueOf(entryDN)));
        return;
      }
@@ -286,8 +291,8 @@
              provider.handleConflictResolution(this);
          if (!result.continueProcessing())
          {
            setResultCode(result.getResultCode());
            appendErrorMessage(result.getErrorMessage());
            setResultCodeAndMessageNoInfoDisclosure(entryDN,
                result.getResultCode(), result.getErrorMessage());
            setMatchedDN(result.getMatchedDN());
            setReferralURLs(result.getReferralURLs());
            return;
@@ -333,9 +338,9 @@
      // above the parent results in a correct referral.
      if (DirectoryServer.entryExists(entryDN))
      {
        setResultCode(ResultCode.ENTRY_ALREADY_EXISTS);
        appendErrorMessage(ERR_ADD_ENTRY_ALREADY_EXISTS.get(String
            .valueOf(entryDN)));
        setResultCodeAndMessageNoInfoDisclosure(entryDN,
            ResultCode.ENTRY_ALREADY_EXISTS,
            ERR_ADD_ENTRY_ALREADY_EXISTS.get(String.valueOf(entryDN)));
        return;
      }
@@ -347,12 +352,24 @@
        if (parentEntry == null)
        {
          setMatchedDN(findMatchedDN(parentDN));
          final DN matchedDN = findMatchedDN(parentDN);
          setMatchedDN(matchedDN);
          // 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)));
          if (matchedDN != null)
          {
            // check whether matchedDN allows to disclose info
            setResultCodeAndMessageNoInfoDisclosure(matchedDN,
                ResultCode.NO_SUCH_OBJECT, ERR_ADD_NO_PARENT.get(String
                    .valueOf(entryDN), String.valueOf(parentDN)));
          }
          else
          {
            // no matched DN either, so let's return normal error code
            setResultCode(ResultCode.NO_SUCH_OBJECT);
            appendErrorMessage(ERR_ADD_NO_PARENT.get(String.valueOf(entryDN),
                String.valueOf(parentDN)));
          }
          return;
        }
      }
@@ -429,9 +446,10 @@
        if (!AccessControlConfigManager.getInstance().getAccessControlHandler()
            .isAllowed(this))
        {
          setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
          appendErrorMessage(ERR_ADD_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS
              .get(String.valueOf(entryDN)));
          setResultCodeAndMessageNoInfoDisclosure(entryDN,
              ResultCode.INSUFFICIENT_ACCESS_RIGHTS,
              ERR_ADD_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS.get(
                  String.valueOf(entryDN)));
          return;
        }
      }
@@ -470,17 +488,17 @@
        switch (DirectoryServer.getWritabilityMode())
        {
        case DISABLED:
          setResultCode(ResultCode.UNWILLING_TO_PERFORM);
          appendErrorMessage(ERR_ADD_SERVER_READONLY.get(String
              .valueOf(entryDN)));
          setResultCodeAndMessageNoInfoDisclosure(entryDN,
              ResultCode.UNWILLING_TO_PERFORM,
              ERR_ADD_SERVER_READONLY.get(String.valueOf(entryDN)));
          return;
        case INTERNAL_ONLY:
          if (!(isInternalOperation() || isSynchronizationOperation()))
          {
            setResultCode(ResultCode.UNWILLING_TO_PERFORM);
            appendErrorMessage(ERR_ADD_SERVER_READONLY.get(String
                .valueOf(entryDN)));
            setResultCodeAndMessageNoInfoDisclosure(entryDN,
                ResultCode.UNWILLING_TO_PERFORM,
                ERR_ADD_SERVER_READONLY.get(String.valueOf(entryDN)));
            return;
          }
          break;
@@ -489,17 +507,17 @@
        switch (backend.getWritabilityMode())
        {
        case DISABLED:
          setResultCode(ResultCode.UNWILLING_TO_PERFORM);
          appendErrorMessage(ERR_ADD_BACKEND_READONLY.get(String
              .valueOf(entryDN)));
          setResultCodeAndMessageNoInfoDisclosure(entryDN,
              ResultCode.UNWILLING_TO_PERFORM,
              ERR_ADD_BACKEND_READONLY.get(String.valueOf(entryDN)));
          return;
        case INTERNAL_ONLY:
          if (!(isInternalOperation() || isSynchronizationOperation()))
          {
            setResultCode(ResultCode.UNWILLING_TO_PERFORM);
            appendErrorMessage(ERR_ADD_BACKEND_READONLY.get(String
                .valueOf(entryDN)));
            setResultCodeAndMessageNoInfoDisclosure(entryDN,
                ResultCode.UNWILLING_TO_PERFORM,
                ERR_ADD_BACKEND_READONLY.get(String.valueOf(entryDN)));
            return;
          }
          break;
@@ -556,7 +574,6 @@
      }
      setResponseData(de);
      return;
    }
    finally
    {
@@ -619,7 +636,7 @@
  }
  private boolean checkHasReadOnlyAttributes(
      Map<AttributeType, List<Attribute>> attributes)
      Map<AttributeType, List<Attribute>> attributes) throws DirectoryException
  {
    for (AttributeType at : attributes.keySet())
    {
@@ -627,10 +644,10 @@
      {
        if (!(isInternalOperation() || isSynchronizationOperation()))
        {
          setResultCode(ResultCode.CONSTRAINT_VIOLATION);
          appendErrorMessage(ERR_ADD_ATTR_IS_NO_USER_MOD.get(String
              .valueOf(entryDN), at.getNameOrOID()));
          setResultCodeAndMessageNoInfoDisclosure(entryDN,
              ResultCode.CONSTRAINT_VIOLATION,
              ERR_ADD_ATTR_IS_NO_USER_MOD.get(
                  String.valueOf(entryDN), at.getNameOrOID()));
          return true;
        }
      }
@@ -638,6 +655,23 @@
    return false;
  }
  private DirectoryException newDirectoryException(DN entryDN,
      ResultCode resultCode, Message message) throws DirectoryException
  {
    return LocalBackendWorkflowElement.newDirectoryException(this, null,
        entryDN, resultCode, message, ResultCode.INSUFFICIENT_ACCESS_RIGHTS,
        ERR_ADD_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS.get(String.valueOf(entryDN)));
  }
  private void setResultCodeAndMessageNoInfoDisclosure(DN entryDN,
      ResultCode resultCode, Message message) throws DirectoryException
  {
    LocalBackendWorkflowElement.setResultCodeAndMessageNoInfoDisclosure(this,
        null, entryDN, resultCode, message,
        ResultCode.INSUFFICIENT_ACCESS_RIGHTS,
        ERR_ADD_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS.get(String.valueOf(entryDN)));
  }
  /**
   * Acquire a read lock on the parent of the entry to add.
   *
@@ -679,7 +713,7 @@
      parentLock = LockManager.lockRead(parentDN);
      if (parentLock == null)
      {
        throw new DirectoryException(ResultCode.BUSY,
        throw newDirectoryException(entryDN, ResultCode.BUSY,
                                     ERR_ADD_CANNOT_LOCK_PARENT.get(
                                          String.valueOf(entryDN),
                                          String.valueOf(parentDN)));
@@ -736,7 +770,7 @@
      }
      else
      {
        throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
        throw newDirectoryException(entryDN, ResultCode.CONSTRAINT_VIOLATION,
                                     ERR_ADD_MISSING_RDN_ATTRIBUTE.get(
                                          String.valueOf(entryDN), n));
      }
@@ -1056,7 +1090,7 @@
    {
      if (at.isObsolete())
      {
        throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
        throw newDirectoryException(entryDN, ResultCode.CONSTRAINT_VIOLATION,
                                     WARN_ADD_ATTR_IS_OBSOLETE.get(
                                          String.valueOf(entryDN),
                                          at.getNameOrOID()));
@@ -1067,7 +1101,7 @@
    {
      if (at.isObsolete())
      {
        throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
        throw newDirectoryException(entryDN, ResultCode.CONSTRAINT_VIOLATION,
                                     WARN_ADD_ATTR_IS_OBSOLETE.get(
                                          String.valueOf(entryDN),
                                          at.getNameOrOID()));
@@ -1078,7 +1112,7 @@
    {
      if (oc.isObsolete())
      {
        throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
        throw newDirectoryException(entryDN, ResultCode.CONSTRAINT_VIOLATION,
                                     WARN_ADD_OC_IS_OBSOLETE.get(
                                          String.valueOf(entryDN),
                                          oc.getNameOrOID()));
@@ -1179,7 +1213,7 @@
              TRACER.debugCaught(DebugLogLevel.ERROR, de);
            }
            throw new DirectoryException(de.getResultCode(),
            throw newDirectoryException(entryDN, de.getResultCode(),
                ERR_ADD_CANNOT_PROCESS_ASSERTION_FILTER.get(
                    String.valueOf(entryDN),
                    de.getMessageObject()));
@@ -1199,7 +1233,7 @@
          {
            if (!filter.matchesEntry(entry))
            {
              throw new DirectoryException(ResultCode.ASSERTION_FAILED,
              throw newDirectoryException(entryDN, ResultCode.ASSERTION_FAILED,
                  ERR_ADD_ASSERTION_FAILED.get(String
                      .valueOf(entryDN)));
            }
@@ -1216,7 +1250,7 @@
              TRACER.debugCaught(DebugLogLevel.ERROR, de);
            }
            throw new DirectoryException(de.getResultCode(),
            throw newDirectoryException(entryDN, de.getResultCode(),
                ERR_ADD_CANNOT_PROCESS_ASSERTION_FILTER.get(
                    String.valueOf(entryDN),
                    de.getMessageObject()));
@@ -1296,7 +1330,7 @@
        {
          if ((backend == null) || (! backend.supportsControl(oid)))
          {
            throw new DirectoryException(
            throw newDirectoryException(entryDN,
                           ResultCode.UNAVAILABLE_CRITICAL_EXTENSION,
                           ERR_ADD_UNSUPPORTED_CRITICAL_CONTROL.get(
                                String.valueOf(entryDN), oid));
opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendCompareOperation.java
@@ -31,6 +31,7 @@
import java.util.Set;
import java.util.concurrent.locks.Lock;
import org.opends.messages.Message;
import org.opends.server.api.Backend;
import org.opends.server.api.ClientConnection;
import org.opends.server.api.plugin.PluginResult;
@@ -132,27 +133,33 @@
    // Check for a request to cancel this operation.
    checkIfCanceled(false);
    BooleanHolder executePostOpPlugins = new BooleanHolder(false);
    processCompare(executePostOpPlugins);
    // Check for a request to cancel this operation.
    checkIfCanceled(false);
    // Invoke the post-operation compare plugins.
    if (executePostOpPlugins.value)
    try
    {
      PluginResult.PostOperation postOpResult =
          DirectoryServer.getPluginConfigManager()
              .invokePostOperationComparePlugins(this);
      if (!postOpResult.continueProcessing())
      BooleanHolder executePostOpPlugins = new BooleanHolder(false);
      processCompare(executePostOpPlugins);
      // Check for a request to cancel this operation.
      checkIfCanceled(false);
      // Invoke the post-operation compare plugins.
      if (executePostOpPlugins.value)
      {
        setResultCode(postOpResult.getResultCode());
        appendErrorMessage(postOpResult.getErrorMessage());
        setMatchedDN(postOpResult.getMatchedDN());
        setReferralURLs(postOpResult.getReferralURLs());
        PluginResult.PostOperation postOpResult =
            DirectoryServer.getPluginConfigManager()
                .invokePostOperationComparePlugins(this);
        if (!postOpResult.continueProcessing())
        {
          setResultCode(postOpResult.getResultCode());
          appendErrorMessage(postOpResult.getErrorMessage());
          setMatchedDN(postOpResult.getMatchedDN());
          setReferralURLs(postOpResult.getReferralURLs());
        }
      }
    }
    finally
    {
      LocalBackendWorkflowElement.filterNonDisclosableMatchedDN(this);
    }
  }
  private void processCompare(BooleanHolder executePostOpPlugins)
@@ -170,7 +177,7 @@
    // 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)))
        && !clientConnection.hasPrivilege(Privilege.CONFIG_READ, this))
    {
      appendErrorMessage(ERR_COMPARE_CONFIG_INSUFFICIENT_PRIVILEGES.get());
      setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
@@ -183,16 +190,16 @@
    // Grab a read lock on the entry.
    final Lock readLock = LockManager.lockRead(entryDN);
    if (readLock == null)
    {
      setResultCode(ResultCode.BUSY);
      appendErrorMessage(ERR_COMPARE_CANNOT_LOCK_ENTRY.get(String
          .valueOf(entryDN)));
      return;
    }
    try
    {
      if (readLock == null)
      {
        setResultCodeAndMessageNoInfoDisclosure(null, entryDN, ResultCode.BUSY,
            ERR_COMPARE_CANNOT_LOCK_ENTRY.get(String.valueOf(entryDN)));
        return;
      }
      // Get the entry. If it does not exist, then fail.
      try
      {
@@ -216,27 +223,14 @@
          TRACER.debugCaught(DebugLogLevel.ERROR, de);
        }
        setResultCode(de.getResultCode());
        appendErrorMessage(de.getMessageObject());
        setResultCodeAndMessageNoInfoDisclosure(entry, entryDN,
            de.getResultCode(), de.getMessageObject());
        return;
      }
      // 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);
        return;
      }
      handleRequestControls();
      // Check to see if the client has permission to perform the
@@ -253,9 +247,10 @@
        if (!AccessControlConfigManager.getInstance().getAccessControlHandler()
            .isAllowed(this))
        {
          setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
          appendErrorMessage(ERR_COMPARE_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS
              .get(String.valueOf(entryDN)));
          setResultCodeAndMessageNoInfoDisclosure(entry, entryDN,
              ResultCode.INSUFFICIENT_ACCESS_RIGHTS,
              ERR_COMPARE_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS.get(String
                  .valueOf(entryDN)));
          return;
        }
      }
@@ -331,12 +326,40 @@
        }
      }
    }
    catch (DirectoryException de)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, de);
      }
      setResponseData(de);
    }
    finally
    {
      LockManager.unlock(entryDN, readLock);
      if (readLock != null)
      {
        LockManager.unlock(entryDN, readLock);
      }
    }
  }
  private DirectoryException newDirectoryException(Entry entry,
      ResultCode resultCode, Message message) throws DirectoryException
  {
    return LocalBackendWorkflowElement.newDirectoryException(this, entry, null,
        resultCode, message, ResultCode.NO_SUCH_OBJECT,
        ERR_COMPARE_NO_SUCH_ENTRY.get(String.valueOf(entryDN)));
  }
  private void setResultCodeAndMessageNoInfoDisclosure(Entry entry, DN entryDN,
      ResultCode realResultCode, Message realMessage) throws DirectoryException
  {
    LocalBackendWorkflowElement.setResultCodeAndMessageNoInfoDisclosure(this,
        entry, entryDN, realResultCode, realMessage, ResultCode.NO_SUCH_OBJECT,
        ERR_COMPARE_NO_SUCH_ENTRY.get(String.valueOf(entryDN)));
  }
  private DN findMatchedDN(DN entryDN)
  {
    try
@@ -400,7 +423,7 @@
              TRACER.debugCaught(DebugLogLevel.ERROR, de);
            }
            throw new DirectoryException(de.getResultCode(),
            throw newDirectoryException(entry, de.getResultCode(),
                           ERR_COMPARE_CANNOT_PROCESS_ASSERTION_FILTER.get(
                                String.valueOf(entryDN),
                                de.getMessageObject()));
@@ -420,7 +443,7 @@
          {
            if (!filter.matchesEntry(entry))
            {
              throw new DirectoryException(ResultCode.ASSERTION_FAILED,
              throw newDirectoryException(entry, ResultCode.ASSERTION_FAILED,
                  ERR_COMPARE_ASSERTION_FAILED.get(String
                      .valueOf(entryDN)));
            }
@@ -437,7 +460,7 @@
              TRACER.debugCaught(DebugLogLevel.ERROR, de);
            }
            throw new DirectoryException(de.getResultCode(),
            throw newDirectoryException(entry, de.getResultCode(),
                           ERR_COMPARE_CANNOT_PROCESS_ASSERTION_FILTER.get(
                                String.valueOf(entryDN),
                                de.getMessageObject()));
opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendDeleteOperation.java
@@ -144,31 +144,38 @@
    // Check for a request to cancel this operation.
    checkIfCanceled(false);
    BooleanHolder executePostOpPlugins = new BooleanHolder(false);
    processDelete(executePostOpPlugins);
    // Invoke the post-operation or post-synchronization delete plugins.
    PluginConfigManager pluginConfigManager =
        DirectoryServer.getPluginConfigManager();
    if (isSynchronizationOperation())
    try
    {
      if (getResultCode() == ResultCode.SUCCESS)
      BooleanHolder executePostOpPlugins = new BooleanHolder(false);
      processDelete(executePostOpPlugins);
      // Invoke the post-operation or post-synchronization delete plugins.
      PluginConfigManager pluginConfigManager =
          DirectoryServer.getPluginConfigManager();
      if (isSynchronizationOperation())
      {
        pluginConfigManager.invokePostSynchronizationDeletePlugins(this);
        if (getResultCode() == ResultCode.SUCCESS)
        {
          pluginConfigManager.invokePostSynchronizationDeletePlugins(this);
        }
      }
      else if (executePostOpPlugins.value)
      {
        PluginResult.PostOperation postOpResult =
            pluginConfigManager.invokePostOperationDeletePlugins(this);
        if (!postOpResult.continueProcessing())
        {
          setResultCode(postOpResult.getResultCode());
          appendErrorMessage(postOpResult.getErrorMessage());
          setMatchedDN(postOpResult.getMatchedDN());
          setReferralURLs(postOpResult.getReferralURLs());
          return;
        }
      }
    }
    else if (executePostOpPlugins.value)
    finally
    {
      PluginResult.PostOperation postOpResult =
          pluginConfigManager.invokePostOperationDeletePlugins(this);
      if (!postOpResult.continueProcessing())
      {
        setResultCode(postOpResult.getResultCode());
        appendErrorMessage(postOpResult.getErrorMessage());
        setMatchedDN(postOpResult.getMatchedDN());
        setReferralURLs(postOpResult.getReferralURLs());
        return;
      }
      LocalBackendWorkflowElement.filterNonDisclosableMatchedDN(this);
    }
    // Register a post-response call-back which will notify persistent
@@ -227,16 +234,16 @@
    // Grab a write lock on the entry.
    final Lock entryLock = LockManager.lockWrite(entryDN);
    if (entryLock == null)
    {
      setResultCode(ResultCode.BUSY);
      appendErrorMessage(ERR_DELETE_CANNOT_LOCK_ENTRY.get(String
          .valueOf(entryDN)));
      return;
    }
    try
    {
      if (entryLock == null)
      {
        setResultCodeAndMessageNoInfoDisclosure(entry, ResultCode.BUSY,
            ERR_DELETE_CANNOT_LOCK_ENTRY.get(String.valueOf(entryDN)));
        return;
      }
      // Get the entry to delete. If it doesn't exist, then fail.
      entry = backend.getEntry(entryDN);
      if (entry == null)
@@ -272,9 +279,10 @@
        if (!AccessControlConfigManager.getInstance().getAccessControlHandler()
            .isAllowed(this))
        {
          setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
          appendErrorMessage(ERR_DELETE_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS
              .get(String.valueOf(entryDN)));
          setResultCodeAndMessageNoInfoDisclosure(entry,
              ResultCode.INSUFFICIENT_ACCESS_RIGHTS,
              ERR_DELETE_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS.get(String
                  .valueOf(entryDN)));
          return;
        }
      }
@@ -322,17 +330,17 @@
        switch (DirectoryServer.getWritabilityMode())
        {
        case DISABLED:
          setResultCode(ResultCode.UNWILLING_TO_PERFORM);
          appendErrorMessage(ERR_DELETE_SERVER_READONLY.get(String
              .valueOf(entryDN)));
          setResultCodeAndMessageNoInfoDisclosure(entry,
              ResultCode.UNWILLING_TO_PERFORM,
              ERR_DELETE_SERVER_READONLY.get(String.valueOf(entryDN)));
          return;
        case INTERNAL_ONLY:
          if (!(isInternalOperation() || isSynchronizationOperation()))
          {
            setResultCode(ResultCode.UNWILLING_TO_PERFORM);
            appendErrorMessage(ERR_DELETE_SERVER_READONLY.get(String
                .valueOf(entryDN)));
            setResultCodeAndMessageNoInfoDisclosure(entry,
                ResultCode.UNWILLING_TO_PERFORM,
                ERR_DELETE_SERVER_READONLY.get(String.valueOf(entryDN)));
            return;
          }
        }
@@ -340,17 +348,17 @@
        switch (backend.getWritabilityMode())
        {
        case DISABLED:
          setResultCode(ResultCode.UNWILLING_TO_PERFORM);
          appendErrorMessage(ERR_DELETE_BACKEND_READONLY.get(String
              .valueOf(entryDN)));
          setResultCodeAndMessageNoInfoDisclosure(entry,
              ResultCode.UNWILLING_TO_PERFORM,
              ERR_DELETE_BACKEND_READONLY.get(String.valueOf(entryDN)));
          return;
        case INTERNAL_ONLY:
          if (!(isInternalOperation() || isSynchronizationOperation()))
          {
            setResultCode(ResultCode.UNWILLING_TO_PERFORM);
            appendErrorMessage(ERR_DELETE_BACKEND_READONLY.get(String
                .valueOf(entryDN)));
            setResultCodeAndMessageNoInfoDisclosure(entry,
                ResultCode.UNWILLING_TO_PERFORM,
                ERR_DELETE_BACKEND_READONLY.get(String.valueOf(entryDN)));
            return;
          }
        }
@@ -368,9 +376,10 @@
        {
          if (dn.isDescendantOf(entryDN))
          {
            setResultCode(ResultCode.NOT_ALLOWED_ON_NONLEAF);
            appendErrorMessage(ERR_DELETE_HAS_SUB_BACKEND.get(String
                .valueOf(entryDN), String.valueOf(dn)));
            setResultCodeAndMessageNoInfoDisclosure(entry,
                ResultCode.NOT_ALLOWED_ON_NONLEAF,
                ERR_DELETE_HAS_SUB_BACKEND.get(String.valueOf(entryDN),
                    String.valueOf(dn)));
            return;
          }
        }
@@ -407,15 +416,33 @@
      }
      setResponseData(de);
      return;
    }
    finally
    {
      LockManager.unlock(entryDN, entryLock);
      if (entryLock != null)
      {
        LockManager.unlock(entryDN, entryLock);
      }
      processSynchPostOperationPlugins();
    }
  }
  private DirectoryException newDirectoryException(Entry entry,
      ResultCode resultCode, Message message) throws DirectoryException
  {
    return LocalBackendWorkflowElement.newDirectoryException(this, entry, null,
        resultCode, message, ResultCode.NO_SUCH_OBJECT,
        ERR_DELETE_NO_SUCH_ENTRY.get(String.valueOf(entryDN)));
  }
  private void setResultCodeAndMessageNoInfoDisclosure(Entry entry,
      ResultCode resultCode, Message message) throws DirectoryException
  {
    LocalBackendWorkflowElement.setResultCodeAndMessageNoInfoDisclosure(this,
        entry, null, resultCode, message, ResultCode.NO_SUCH_OBJECT,
        ERR_DELETE_NO_SUCH_ENTRY.get(String.valueOf(entryDN)));
  }
  private DN findMatchedDN(DN entryDN)
  {
    try
@@ -480,7 +507,7 @@
              TRACER.debugCaught(DebugLogLevel.ERROR, de);
            }
            throw new DirectoryException(de.getResultCode(),
            throw newDirectoryException(entry, de.getResultCode(),
                           ERR_DELETE_CANNOT_PROCESS_ASSERTION_FILTER.get(
                                String.valueOf(entryDN),
                                de.getMessageObject()));
@@ -500,7 +527,7 @@
          {
            if (!filter.matchesEntry(entry))
            {
              throw new DirectoryException(ResultCode.ASSERTION_FAILED,
              throw newDirectoryException(entry, ResultCode.ASSERTION_FAILED,
                  ERR_DELETE_ASSERTION_FAILED.get(String
                      .valueOf(entryDN)));
            }
@@ -517,7 +544,7 @@
              TRACER.debugCaught(DebugLogLevel.ERROR, de);
            }
            throw new DirectoryException(de.getResultCode(),
            throw newDirectoryException(entry, de.getResultCode(),
                           ERR_DELETE_CANNOT_PROCESS_ASSERTION_FILTER.get(
                                String.valueOf(entryDN),
                                de.getMessageObject()));
@@ -591,7 +618,7 @@
        {
          if ((backend == null) || (! backend.supportsControl(oid)))
          {
            throw new DirectoryException(
            throw newDirectoryException(entry,
                           ResultCode.UNAVAILABLE_CRITICAL_EXTENSION,
                           ERR_DELETE_UNSUPPORTED_CRITICAL_CONTROL.get(
                                String.valueOf(entryDN), oid));
@@ -617,8 +644,8 @@
              SynchronizationProviderResult result =
                  provider.handleConflictResolution(this);
              if (! result.continueProcessing()) {
                  setResultCode(result.getResultCode());
                  appendErrorMessage(result.getErrorMessage());
                  setResultCodeAndMessageNoInfoDisclosure(entry,
                      result.getResultCode(), result.getErrorMessage());
                  setMatchedDN(result.getMatchedDN());
                  setReferralURLs(result.getReferralURLs());
                  returnVal = false;
opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendModifyDNOperation.java
@@ -178,31 +178,38 @@
    // Check for a request to cancel this operation.
    checkIfCanceled(false);
    BooleanHolder executePostOpPlugins = new BooleanHolder(false);
    processModifyDN(executePostOpPlugins);
    // Invoke the post-operation or post-synchronization modify DN plugins.
    PluginConfigManager pluginConfigManager =
        DirectoryServer.getPluginConfigManager();
    if (isSynchronizationOperation())
    try
    {
      if (getResultCode() == ResultCode.SUCCESS)
      BooleanHolder executePostOpPlugins = new BooleanHolder(false);
      processModifyDN(executePostOpPlugins);
      // Invoke the post-operation or post-synchronization modify DN plugins.
      PluginConfigManager pluginConfigManager =
          DirectoryServer.getPluginConfigManager();
      if (isSynchronizationOperation())
      {
        pluginConfigManager.invokePostSynchronizationModifyDNPlugins(this);
        if (getResultCode() == ResultCode.SUCCESS)
        {
          pluginConfigManager.invokePostSynchronizationModifyDNPlugins(this);
        }
      }
      else if (executePostOpPlugins.value)
      {
        PluginResult.PostOperation postOpResult =
            pluginConfigManager.invokePostOperationModifyDNPlugins(this);
        if (!postOpResult.continueProcessing())
        {
          setResultCode(postOpResult.getResultCode());
          appendErrorMessage(postOpResult.getErrorMessage());
          setMatchedDN(postOpResult.getMatchedDN());
          setReferralURLs(postOpResult.getReferralURLs());
          return;
        }
      }
    }
    else if (executePostOpPlugins.value)
    finally
    {
      PluginResult.PostOperation postOpResult =
          pluginConfigManager.invokePostOperationModifyDNPlugins(this);
      if (!postOpResult.continueProcessing())
      {
        setResultCode(postOpResult.getResultCode());
        appendErrorMessage(postOpResult.getErrorMessage());
        setMatchedDN(postOpResult.getMatchedDN());
        setReferralURLs(postOpResult.getReferralURLs());
        return;
      }
      LocalBackendWorkflowElement.filterNonDisclosableMatchedDN(this);
    }
    // Register a post-response call-back which will notify persistent
@@ -328,51 +335,42 @@
    // Acquire write locks for the current and new DN.
    final Lock currentLock = LockManager.lockWrite(entryDN);
    if (currentLock == null)
    {
      setResultCode(ResultCode.BUSY);
      appendErrorMessage(ERR_MODDN_CANNOT_LOCK_CURRENT_DN.get(String
          .valueOf(entryDN)));
      return;
    }
    Lock newLock = null;
    try
    {
      newLock = LockManager.lockWrite(newDN);
    }
    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)));
      return;
    }
    if (newLock == null)
    {
      LockManager.unlock(entryDN, currentLock);
      setResultCode(ResultCode.BUSY);
      appendErrorMessage(ERR_MODDN_CANNOT_LOCK_NEW_DN.get(String
          .valueOf(entryDN), String.valueOf(newDN)));
      return;
    }
    try
    {
      if (currentLock == null)
      {
        setResultCodeAndMessageNoInfoDisclosure(null, entryDN, ResultCode.BUSY,
            ERR_MODDN_CANNOT_LOCK_CURRENT_DN.get(String.valueOf(entryDN)));
        return;
      }
      try
      {
        newLock = LockManager.lockWrite(newDN);
        if (newLock == null)
        {
          setResultCodeAndMessageNoInfoDisclosure(null, newDN, ResultCode.BUSY,
              ERR_MODDN_CANNOT_LOCK_NEW_DN.get(String.valueOf(entryDN), String
                  .valueOf(newDN)));
          return;
        }
      }
      catch (Exception e)
      {
        if (debugEnabled())
        {
          TRACER.debugCaught(DebugLogLevel.ERROR, e);
        }
        setResultCodeAndMessageNoInfoDisclosure(null, newDN,
            DirectoryServer.getServerErrorResultCode(),
            ERR_MODDN_EXCEPTION_LOCKING_NEW_DN.get(String.valueOf(entryDN),
                String.valueOf(newDN), getExceptionMessage(e)));
        return;
      }
      // Check for a request to cancel this operation.
      checkIfCanceled(false);
@@ -410,9 +408,10 @@
        if (!AccessControlConfigManager.getInstance().getAccessControlHandler()
            .isAllowed(this))
        {
          setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
          appendErrorMessage(ERR_MODDN_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS
              .get(String.valueOf(entryDN)));
          setResultCodeAndMessageNoInfoDisclosure(currentEntry, entryDN,
              ResultCode.INSUFFICIENT_ACCESS_RIGHTS,
              ERR_MODDN_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS.get(String
                  .valueOf(entryDN)));
          return;
        }
      }
@@ -430,7 +429,7 @@
      // init the modifications
      addModification(null);
      List<Modification> modifications = this.getModifications();
      List<Modification> modifications = getModifications();
      if (!handleConflictResolution())
      {
@@ -567,12 +566,34 @@
    }
    finally
    {
      LockManager.unlock(entryDN, currentLock);
      LockManager.unlock(newDN, newLock);
      if (currentLock != null)
      {
        LockManager.unlock(entryDN, currentLock);
      }
      if (newLock != null)
      {
        LockManager.unlock(newDN, newLock);
      }
      processSynchPostOperationPlugins();
    }
  }
  private DirectoryException newDirectoryException(Entry entry, DN entryDN,
      ResultCode resultCode, Message message) throws DirectoryException
  {
    return LocalBackendWorkflowElement.newDirectoryException(this, entry,
        entryDN, resultCode, message, ResultCode.NO_SUCH_OBJECT,
        ERR_MODDN_NO_CURRENT_ENTRY.get(String.valueOf(entryDN)));
  }
  private void setResultCodeAndMessageNoInfoDisclosure(Entry entry, DN entryDN,
      ResultCode realResultCode, Message realMessage) throws DirectoryException
  {
    LocalBackendWorkflowElement.setResultCodeAndMessageNoInfoDisclosure(this,
        entry, entryDN, realResultCode, realMessage, ResultCode.NO_SUCH_OBJECT,
        ERR_MODDN_NO_CURRENT_ENTRY.get(String.valueOf(entryDN)));
  }
  private DN findMatchedDN(DN entryDN)
  {
    try
@@ -637,10 +658,11 @@
              TRACER.debugCaught(DebugLogLevel.ERROR, de);
            }
            throw new DirectoryException(de.getResultCode(),
                           ERR_MODDN_CANNOT_PROCESS_ASSERTION_FILTER.get(
                                String.valueOf(entryDN),
                                de.getMessageObject()));
            throw newDirectoryException(currentEntry, entryDN,
                de.getResultCode(),
                ERR_MODDN_CANNOT_PROCESS_ASSERTION_FILTER.get(
                    String.valueOf(entryDN),
                    de.getMessageObject()));
          }
          // Check if the current user has permission to make
@@ -657,7 +679,8 @@
          {
            if (!filter.matchesEntry(currentEntry))
            {
              throw new DirectoryException(ResultCode.ASSERTION_FAILED,
              throw newDirectoryException(currentEntry, entryDN,
                  ResultCode.ASSERTION_FAILED,
                  ERR_MODDN_ASSERTION_FAILED.get(String
                      .valueOf(entryDN)));
            }
@@ -674,10 +697,11 @@
              TRACER.debugCaught(DebugLogLevel.ERROR, de);
            }
            throw new DirectoryException(de.getResultCode(),
                           ERR_MODDN_CANNOT_PROCESS_ASSERTION_FILTER.get(
                                String.valueOf(entryDN),
                                de.getMessageObject()));
            throw newDirectoryException(currentEntry, entryDN,
                de.getResultCode(),
                ERR_MODDN_CANNOT_PROCESS_ASSERTION_FILTER.get(
                    String.valueOf(entryDN),
                    de.getMessageObject()));
          }
        }
        else if (oid.equals(OID_LDAP_NOOP_OPENLDAP_ASSIGNED))
opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendModifyOperation.java
@@ -70,8 +70,6 @@
   */
  private static final DebugTracer TRACER = getTracer();
  /**
   * The backend in which the target entry exists.
   */
@@ -295,40 +293,47 @@
    // Check for a request to cancel this operation.
    checkIfCanceled(false);
    BooleanHolder executePostOpPlugins = new BooleanHolder(false);
    processModify(executePostOpPlugins);
    // If the password policy request control was included, then make sure we
    // send the corresponding response control.
    if (pwPolicyControlRequested)
    try
    {
      addResponseControl(new PasswordPolicyResponseControl(null, 0,
                                                           pwpErrorType));
    }
      BooleanHolder executePostOpPlugins = new BooleanHolder(false);
      processModify(executePostOpPlugins);
    // Invoke the post-operation or post-synchronization modify plugins.
    PluginConfigManager pluginConfigManager =
        DirectoryServer.getPluginConfigManager();
    if (isSynchronizationOperation())
    {
      if (getResultCode() == ResultCode.SUCCESS)
      // If the password policy request control was included, then make sure we
      // send the corresponding response control.
      if (pwPolicyControlRequested)
      {
        pluginConfigManager.invokePostSynchronizationModifyPlugins(this);
        addResponseControl(new PasswordPolicyResponseControl(null, 0,
            pwpErrorType));
      }
      // Invoke the post-operation or post-synchronization modify plugins.
      PluginConfigManager pluginConfigManager =
          DirectoryServer.getPluginConfigManager();
      if (isSynchronizationOperation())
      {
        if (getResultCode() == ResultCode.SUCCESS)
        {
          pluginConfigManager.invokePostSynchronizationModifyPlugins(this);
        }
      }
      else if (executePostOpPlugins.value)
      {
        // FIXME -- Should this also be done while holding the locks?
        PluginResult.PostOperation postOpResult =
            pluginConfigManager.invokePostOperationModifyPlugins(this);
        if (!postOpResult.continueProcessing())
        {
          setResultCode(postOpResult.getResultCode());
          appendErrorMessage(postOpResult.getErrorMessage());
          setMatchedDN(postOpResult.getMatchedDN());
          setReferralURLs(postOpResult.getReferralURLs());
          return;
        }
      }
    }
    else if (executePostOpPlugins.value)
    finally
    {
      // FIXME -- Should this also be done while holding the locks?
      PluginResult.PostOperation postOpResult =
           pluginConfigManager.invokePostOperationModifyPlugins(this);
      if (!postOpResult.continueProcessing())
      {
        setResultCode(postOpResult.getResultCode());
        appendErrorMessage(postOpResult.getErrorMessage());
        setMatchedDN(postOpResult.getMatchedDN());
        setReferralURLs(postOpResult.getReferralURLs());
        return;
      }
      LocalBackendWorkflowElement.filterNonDisclosableMatchedDN(this);
    }
@@ -407,16 +412,16 @@
    // Acquire a write lock on the target entry.
    final Lock entryLock = LockManager.lockWrite(entryDN);
    if (entryLock == null)
    {
      setResultCode(ResultCode.BUSY);
      appendErrorMessage(ERR_MODIFY_CANNOT_LOCK_ENTRY.get(String
          .valueOf(entryDN)));
      return;
    }
    try
    {
      if (entryLock == null)
      {
        setResultCodeAndMessageNoInfoDisclosure(currentEntry, ResultCode.BUSY,
            ERR_MODIFY_CANNOT_LOCK_ENTRY.get(String.valueOf(entryDN)));
        return;
      }
      // Check for a request to cancel this operation.
      checkIfCanceled(false);
@@ -480,12 +485,9 @@
      // Create a duplicate of the entry and apply the changes to it.
      modifiedEntry = currentEntry.duplicate(false);
      if (!noOp)
      if (!noOp && !handleConflictResolution())
      {
        if (!handleConflictResolution())
        {
          return;
        }
        return;
      }
      handleSchemaProcessing();
@@ -505,9 +507,10 @@
        if (!AccessControlConfigManager.getInstance().getAccessControlHandler()
            .isAllowed(this))
        {
          setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
          appendErrorMessage(ERR_MODIFY_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS
              .get(String.valueOf(entryDN)));
          setResultCodeAndMessageNoInfoDisclosure(modifiedEntry,
              ResultCode.INSUFFICIENT_ACCESS_RIGHTS,
              ERR_MODIFY_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS.get(String
                  .valueOf(entryDN)));
          return;
        }
      }
@@ -620,15 +623,33 @@
      }
      setResponseData(de);
      return;
    }
    finally
    {
      LockManager.unlock(entryDN, entryLock);
      if (entryLock != null)
      {
        LockManager.unlock(entryDN, entryLock);
      }
      processSynchPostOperationPlugins();
    }
  }
  private DirectoryException newDirectoryException(Entry entry,
      ResultCode resultCode, Message message) throws DirectoryException
  {
    return LocalBackendWorkflowElement.newDirectoryException(this, entry, null,
        resultCode, message, ResultCode.NO_SUCH_OBJECT,
        ERR_MODIFY_NO_SUCH_ENTRY.get(String.valueOf(entryDN)));
  }
  private void setResultCodeAndMessageNoInfoDisclosure(Entry entry,
      ResultCode realResultCode, Message realMessage) throws DirectoryException
  {
    LocalBackendWorkflowElement.setResultCodeAndMessageNoInfoDisclosure(this,
        entry, null, realResultCode, realMessage, ResultCode.NO_SUCH_OBJECT,
        ERR_MODIFY_NO_SUCH_ENTRY.get(String.valueOf(entryDN)));
  }
  private DN findMatchedDN(DN entryDN)
  {
    try
@@ -694,7 +715,7 @@
              TRACER.debugCaught(DebugLogLevel.ERROR, de);
            }
            throw new DirectoryException(de.getResultCode(),
            throw newDirectoryException(currentEntry, de.getResultCode(),
                           ERR_MODIFY_CANNOT_PROCESS_ASSERTION_FILTER.get(
                                String.valueOf(entryDN),
                                de.getMessageObject()));
@@ -714,9 +735,9 @@
          {
            if (!filter.matchesEntry(currentEntry))
            {
              throw new DirectoryException(ResultCode.ASSERTION_FAILED,
                  ERR_MODIFY_ASSERTION_FAILED.get(String
                      .valueOf(entryDN)));
              throw newDirectoryException(currentEntry,
                  ResultCode.ASSERTION_FAILED,
                  ERR_MODIFY_ASSERTION_FAILED.get(String.valueOf(entryDN)));
            }
          }
          catch (DirectoryException de)
@@ -731,7 +752,7 @@
              TRACER.debugCaught(DebugLogLevel.ERROR, de);
            }
            throw new DirectoryException(de.getResultCode(),
            throw newDirectoryException(currentEntry, de.getResultCode(),
                           ERR_MODIFY_CANNOT_PROCESS_ASSERTION_FILTER.get(
                                String.valueOf(entryDN),
                                de.getMessageObject()));
@@ -825,7 +846,7 @@
        {
          if ((backend == null) || (! backend.supportsControl(oid)))
          {
            throw new DirectoryException(
            throw newDirectoryException(currentEntry,
                           ResultCode.UNAVAILABLE_CRITICAL_EXTENSION,
                           ERR_MODIFY_UNSUPPORTED_CRITICAL_CONTROL.get(
                                String.valueOf(entryDN), oid));
@@ -858,8 +879,9 @@
        if (! (isInternalOperation() || isSynchronizationOperation() ||
                m.isInternal()))
        {
          throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
                  ERR_MODIFY_ATTR_IS_NO_USER_MOD.get(
          throw newDirectoryException(currentEntry,
              ResultCode.CONSTRAINT_VIOLATION,
              ERR_MODIFY_ATTR_IS_NO_USER_MOD.get(
                          String.valueOf(entryDN), a.getName()));
        }
      }
@@ -875,8 +897,9 @@
          if (! (isInternalOperation() || isSynchronizationOperation() ||
                  m.isInternal()))
          {
            throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
                    ERR_MODIFY_ATTR_IS_OBSOLETE.get(
            throw newDirectoryException(currentEntry,
                ResultCode.CONSTRAINT_VIOLATION,
                ERR_MODIFY_ATTR_IS_OBSOLETE.get(
                            String.valueOf(entryDN), a.getName()));
          }
        }
@@ -1379,7 +1402,7 @@
    // attribute.
    if (attr.isEmpty())
    {
      throw new DirectoryException(ResultCode.PROTOCOL_ERROR,
      throw newDirectoryException(currentEntry, ResultCode.PROTOCOL_ERROR,
          ERR_MODIFY_ADD_NO_VALUES.get(String.valueOf(entryDN),
              attr.getName()));
    }
@@ -1403,15 +1426,17 @@
            if (!syntax.isHumanReadable() || syntax.isBinary())
            {
              // Value is not human-readable
              throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
                ERR_MODIFY_ADD_INVALID_SYNTAX_NO_VALUE.get(
                    String.valueOf(entryDN), attr.getName(), invalidReason));
              throw newDirectoryException(currentEntry,
                  ResultCode.INVALID_ATTRIBUTE_SYNTAX,
                  ERR_MODIFY_ADD_INVALID_SYNTAX_NO_VALUE.get(
                      String.valueOf(entryDN), attr.getName(), invalidReason));
            }
            else
            {
              throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
                ERR_MODIFY_ADD_INVALID_SYNTAX.get(String.valueOf(entryDN), attr
                    .getName(), v.getValue().toString(), invalidReason));
              throw newDirectoryException(currentEntry,
                  ResultCode.INVALID_ATTRIBUTE_SYNTAX,
                  ERR_MODIFY_ADD_INVALID_SYNTAX.get(String.valueOf(entryDN),
                      attr.getName(), v.getValue().toString(), invalidReason));
            }
          }
        }
@@ -1455,16 +1480,16 @@
    // Add the provided attribute or merge an existing attribute with
    // the values of the new attribute. If there are any duplicates,
    // then fail.
    List<AttributeValue> duplicateValues =
      new LinkedList<AttributeValue>();
    List<AttributeValue> duplicateValues = new LinkedList<AttributeValue>();
    modifiedEntry.addAttribute(attr, duplicateValues);
    if (!duplicateValues.isEmpty() && !permissiveModify)
    {
      String duplicateValuesStr = collectionToString(duplicateValues, ", ");
      throw new DirectoryException(ResultCode.ATTRIBUTE_OR_VALUE_EXISTS,
          ERR_MODIFY_ADD_DUPLICATE_VALUE.get(String.valueOf(entryDN), attr
              .getName(), duplicateValuesStr));
      throw newDirectoryException(currentEntry,
          ResultCode.ATTRIBUTE_OR_VALUE_EXISTS,
          ERR_MODIFY_ADD_DUPLICATE_VALUE.get(
              String.valueOf(entryDN), attr.getName(), duplicateValuesStr));
    }
  }
@@ -1505,16 +1530,16 @@
      ObjectClass oc = DirectoryServer.getObjectClass(lowerName);
      if (oc == null)
      {
        Message message = ERR_ENTRY_ADD_UNKNOWN_OC.get(name, String
            .valueOf(entryDN));
        throw new DirectoryException(ResultCode.OBJECTCLASS_VIOLATION, message);
        throw newDirectoryException(currentEntry,
            ResultCode.OBJECTCLASS_VIOLATION,
            ERR_ENTRY_ADD_UNKNOWN_OC.get(name, String.valueOf(entryDN)));
      }
      if (oc.isObsolete())
      {
        Message message = ERR_ENTRY_ADD_OBSOLETE_OC.get(name, String
            .valueOf(entryDN));
        throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
        throw newDirectoryException(currentEntry,
            ResultCode.CONSTRAINT_VIOLATION,
            ERR_ENTRY_ADD_OBSOLETE_OC.get(name, String.valueOf(entryDN)));
      }
    }
  }
@@ -1551,10 +1576,10 @@
            (! 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()));
          throw newDirectoryException(currentEntry,
              ResultCode.NOT_ALLOWED_ON_RDN,
              ERR_MODIFY_DELETE_RDN_ATTR.get(
                  String.valueOf(entryDN), attr.getName()));
        }
      }
      else
@@ -1563,10 +1588,10 @@
        {
          String missingValuesStr = collectionToString(missingValues, ", ");
          throw new DirectoryException(ResultCode.NO_SUCH_ATTRIBUTE,
                       ERR_MODIFY_DELETE_MISSING_VALUES.get(
                            String.valueOf(entryDN), attr.getName(),
                            missingValuesStr));
          throw newDirectoryException(currentEntry,
              ResultCode.NO_SUCH_ATTRIBUTE,
              ERR_MODIFY_DELETE_MISSING_VALUES.get(
                  String.valueOf(entryDN), attr.getName(), missingValuesStr));
        }
      }
    }
@@ -1574,7 +1599,7 @@
    {
      if (! permissiveModify)
      {
        throw new DirectoryException(ResultCode.NO_SUCH_ATTRIBUTE,
        throw newDirectoryException(currentEntry, ResultCode.NO_SUCH_ATTRIBUTE,
                     ERR_MODIFY_DELETE_NO_SUCH_ATTR.get(
                          String.valueOf(entryDN), attr.getName()));
      }
@@ -1615,15 +1640,17 @@
            if (!syntax.isHumanReadable() || syntax.isBinary())
            {
              // Value is not human-readable
              throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
                ERR_MODIFY_REPLACE_INVALID_SYNTAX_NO_VALUE.get(
                    String.valueOf(entryDN), attr.getName(), invalidReason));
              throw newDirectoryException(currentEntry,
                  ResultCode.INVALID_ATTRIBUTE_SYNTAX,
                  ERR_MODIFY_REPLACE_INVALID_SYNTAX_NO_VALUE.get(
                      String.valueOf(entryDN), attr.getName(), invalidReason));
            }
            else
            {
              throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
                ERR_MODIFY_REPLACE_INVALID_SYNTAX.get(String.valueOf(entryDN),
                    attr.getName(), v.getValue().toString(), invalidReason));
              throw newDirectoryException(currentEntry,
                  ResultCode.INVALID_ATTRIBUTE_SYNTAX,
                  ERR_MODIFY_REPLACE_INVALID_SYNTAX.get(String.valueOf(entryDN),
                      attr.getName(), v.getValue().toString(), invalidReason));
            }
          }
        }
@@ -1673,7 +1700,7 @@
        && (!modifiedEntry.hasValue(t, attr.getOptions(), rdn
            .getAttributeValue(t))))
    {
      throw new DirectoryException(ResultCode.NOT_ALLOWED_ON_RDN,
      throw newDirectoryException(modifiedEntry, ResultCode.NOT_ALLOWED_ON_RDN,
          ERR_MODIFY_DELETE_RDN_ATTR.get(String.valueOf(entryDN), attr
              .getName()));
    }
@@ -1699,7 +1726,7 @@
    RDN rdn = modifiedEntry.getDN().getRDN();
    if ((rdn != null) && rdn.hasAttributeType(t))
    {
      throw new DirectoryException(ResultCode.NOT_ALLOWED_ON_RDN,
      throw newDirectoryException(modifiedEntry, ResultCode.NOT_ALLOWED_ON_RDN,
          ERR_MODIFY_INCREMENT_RDN.get(String.valueOf(entryDN),
              attr.getName()));
    }
@@ -1708,14 +1735,14 @@
    // an integer.
    if (attr.isEmpty())
    {
      throw new DirectoryException(ResultCode.PROTOCOL_ERROR,
      throw newDirectoryException(modifiedEntry, ResultCode.PROTOCOL_ERROR,
          ERR_MODIFY_INCREMENT_REQUIRES_VALUE.get(String.valueOf(entryDN), attr
              .getName()));
    }
    if (attr.size() > 1)
    {
      throw new DirectoryException(ResultCode.PROTOCOL_ERROR,
      throw newDirectoryException(modifiedEntry, ResultCode.PROTOCOL_ERROR,
          ERR_MODIFY_INCREMENT_REQUIRES_SINGLE_VALUE.get(String
              .valueOf(entryDN), attr.getName()));
    }
@@ -1743,7 +1770,8 @@
    Attribute a = modifiedEntry.getExactAttribute(t, attr.getOptions());
    if (a == null)
    {
      throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
      throw newDirectoryException(modifiedEntry,
          ResultCode.CONSTRAINT_VIOLATION,
          ERR_MODIFY_INCREMENT_REQUIRES_EXISTING_VALUE.get(String
              .valueOf(entryDN), attr.getName()));
    }
@@ -2110,8 +2138,8 @@
              SynchronizationProviderResult result =
                  provider.handleConflictResolution(this);
              if (! result.continueProcessing()) {
                  setResultCode(result.getResultCode());
                  appendErrorMessage(result.getErrorMessage());
                  setResultCodeAndMessageNoInfoDisclosure(modifiedEntry,
                      result.getResultCode(), result.getErrorMessage());
                  setMatchedDN(result.getMatchedDN());
                  setReferralURLs(result.getReferralURLs());
                  returnVal = false;
opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendSearchOperation.java
@@ -23,7 +23,7 @@
 *
 *
 *      Copyright 2008-2010 Sun Microsystems, Inc.
 *      Portions copyright 2011-2012 ForgeRock AS
 *      Portions copyright 2011-2013 ForgeRock AS
 */
package org.opends.server.workflowelement.localbackend;
@@ -138,26 +138,33 @@
    // Check for a request to cancel this operation.
    checkIfCanceled(false);
    BooleanHolder executePostOpPlugins = new BooleanHolder(false);
    processSearch(wfe, executePostOpPlugins);
    // Check for a request to cancel this operation.
    checkIfCanceled(false);
    // Invoke the post-operation search plugins.
    if (executePostOpPlugins.value)
    try
    {
      PluginResult.PostOperation postOpResult =
          DirectoryServer.getPluginConfigManager()
              .invokePostOperationSearchPlugins(this);
      if (!postOpResult.continueProcessing())
      BooleanHolder executePostOpPlugins = new BooleanHolder(false);
      processSearch(wfe, executePostOpPlugins);
      // Check for a request to cancel this operation.
      checkIfCanceled(false);
      // Invoke the post-operation search plugins.
      if (executePostOpPlugins.value)
      {
        setResultCode(postOpResult.getResultCode());
        appendErrorMessage(postOpResult.getErrorMessage());
        setMatchedDN(postOpResult.getMatchedDN());
        setReferralURLs(postOpResult.getReferralURLs());
        PluginResult.PostOperation postOpResult =
            DirectoryServer.getPluginConfigManager()
                .invokePostOperationSearchPlugins(this);
        if (!postOpResult.continueProcessing())
        {
          setResultCode(postOpResult.getResultCode());
          appendErrorMessage(postOpResult.getErrorMessage());
          setMatchedDN(postOpResult.getMatchedDN());
          setReferralURLs(postOpResult.getReferralURLs());
        }
      }
    }
    finally
    {
      LocalBackendWorkflowElement.filterNonDisclosableMatchedDN(this);
    }
  }
  private void processSearch(LocalBackendWorkflowElement wfe,
opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendWorkflowElement.java
@@ -27,8 +27,6 @@
 */
package org.opends.server.workflowelement.localbackend;
import java.util.ArrayList;
import java.util.List;
import java.util.TreeMap;
@@ -47,14 +45,13 @@
import org.opends.server.controls.LDAPPreReadRequestControl;
import org.opends.server.controls.LDAPPreReadResponseControl;
import org.opends.server.core.*;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.types.*;
import org.opends.server.workflowelement.LeafWorkflowElement;
import static org.opends.messages.CoreMessages.*;
import static org.opends.server.config.ConfigConstants.*;
import static org.opends.server.loggers.debug.DebugLogger.*;
/**
 * This class defines a local backend workflow element; e-g an entity that
@@ -64,26 +61,34 @@
    LeafWorkflowElement<LocalBackendWorkflowElementCfg>
    implements ConfigurationChangeListener<LocalBackendWorkflowElementCfg>
{
  // the backend associated with the local workflow element
  /**
   * The tracer object for the debug logger.
   */
  private static final DebugTracer TRACER = getTracer();
  /** the backend associated with the local workflow element. */
  private Backend backend;
  // the set of local backend workflow elements registered with the server
  /** the set of local backend workflow elements registered with the server. */
  private static TreeMap<String, LocalBackendWorkflowElement>
       registeredLocalBackends =
            new TreeMap<String, LocalBackendWorkflowElement>();
  // The set of persistent searches registered with this work flow
  // element.
  /**
   * The set of persistent searches registered with this work flow element.
   */
  private final List<PersistentSearch> persistentSearches =
    new CopyOnWriteArrayList<PersistentSearch>();
  // a lock to guarantee safe concurrent access to the registeredLocalBackends
  // variable
  /**
   * a lock to guarantee safe concurrent access to the registeredLocalBackends
   * variable.
   */
  private static final Object registeredLocalBackendsLock = new Object();
  // A string indicating the type of the workflow element.
  /** A string indicating the type of the workflow element. */
  private static final String BACKEND_WORKFLOW_ELEMENT = "Backend";
@@ -177,10 +182,7 @@
      List<Message>                  unacceptableReasons
      )
  {
    boolean isAcceptable =
      processWorkflowElementConfig(configuration, false);
    return isAcceptable;
    return processWorkflowElementConfig(configuration, false);
  }
@@ -192,14 +194,10 @@
      LocalBackendWorkflowElementCfg configuration
      )
  {
    // Returned result.
    ConfigChangeResult changeResult = new ConfigChangeResult(
        ResultCode.SUCCESS, false, new ArrayList<Message>()
        );
    processWorkflowElementConfig(configuration, true);
    return changeResult;
    return new ConfigChangeResult(ResultCode.SUCCESS, false,
        new ArrayList<Message>());
  }
@@ -354,7 +352,7 @@
      }
      else
      {
        // We don't want the backend to process this non-critical control, so
        // We do not want the backend to process this non-critical control, so
        // remove it.
        op.removeRequestControl(control);
        return false;
@@ -363,7 +361,148 @@
    return true;
  }
  /**
   * Returns a new {@link DirectoryException} built from the provided
   * resultCodes and messages. Depending on whether ACIs prevent information
   * disclosure, the provided resultCode and message will be masked and
   * altResultCode and altMessage will be used instead.
   *
   * @param operation
   *          the operation for which to check if ACIs prevent information
   *          disclosure
   * @param entry
   *          the entry for which to check if ACIs prevent information
   *          disclosure, if null, then a fake entry will be created from the
   *          entryDN parameter
   * @param entryDN
   *          the entry dn for which to check if ACIs prevent information
   *          disclosure. Only used if entry is null.
   * @param resultCode
   *          the result code to put on the DirectoryException if ACIs allow
   *          disclosure. Otherwise it will be put on the DirectoryException as
   *          a masked result code.
   * @param message
   *          the message to put on the DirectoryException if ACIs allow
   *          disclosure. Otherwise it will be put on the DirectoryException as
   *          a masked message.
   * @param altResultCode
   *          the result code to put on the DirectoryException if ACIs do not
   *          allow disclosing the resultCode.
   * @param altMessage
   *          the result code to put on the DirectoryException if ACIs do not
   *          allow disclosing the message.
   * @return a new DirectoryException containing the provided resultCodes and
   *         messages depending on ACI allowing disclosure or not
   * @throws DirectoryException
   *           If an error occurred while performing the access control check.
   */
  static DirectoryException newDirectoryException(Operation operation,
      Entry entry, DN entryDN, ResultCode resultCode, Message message,
      ResultCode altResultCode, Message altMessage) throws DirectoryException
  {
    if (AccessControlConfigManager.getInstance().getAccessControlHandler()
        .canDiscloseInformation(entry, entryDN, operation))
    {
      return new DirectoryException(resultCode, message);
    }
    // replacement reason returned to the user
    final DirectoryException ex =
        new DirectoryException(altResultCode, altMessage);
    // real underlying reason
    ex.setMaskedResultCode(resultCode);
    ex.setMaskedMessage(message);
    return ex;
  }
  /**
   * Sets the provided resultCodes and messages on the provided operation.
   * Depending on whether ACIs prevent information disclosure, the provided
   * resultCode and message will be masked and altResultCode and altMessage will
   * be used instead.
   *
   * @param operation
   *          the operation for which to check if ACIs prevent information
   *          disclosure
   * @param entry
   *          the entry for which to check if ACIs prevent information
   *          disclosure, if null, then a fake entry will be created from the
   *          entryDN parameter
   * @param entryDN
   *          the entry dn for which to check if ACIs prevent information
   *          disclosure. Only used if entry is null.
   * @param resultCode
   *          the result code to put on the DirectoryException if ACIs allow
   *          disclosure. Otherwise it will be put on the DirectoryException as
   *          a masked result code.
   * @param message
   *          the message to put on the DirectoryException if ACIs allow
   *          disclosure. Otherwise it will be put on the DirectoryException as
   *          a masked message.
   * @param altResultCode
   *          the result code to put on the DirectoryException if ACIs do not
   *          allow disclosing the resultCode.
   * @param altMessage
   *          the result code to put on the DirectoryException if ACIs do not
   *          allow disclosing the message.
   * @throws DirectoryException
   *           If an error occurred while performing the access control check.
   */
  static void setResultCodeAndMessageNoInfoDisclosure(Operation operation,
      Entry entry, DN entryDN, ResultCode resultCode, Message message,
      ResultCode altResultCode, Message altMessage) throws DirectoryException
  {
    if (AccessControlConfigManager.getInstance().getAccessControlHandler()
        .canDiscloseInformation(entry, entryDN, operation))
    {
      operation.setResultCode(resultCode);
      operation.appendErrorMessage(message);
    }
    else
    {
      // replacement reason returned to the user
      operation.setResultCode(altResultCode);
      operation.appendMaskedErrorMessage(altMessage);
      // real underlying reason
      operation.setMaskedResultCode(resultCode);
      operation.appendMaskedErrorMessage(message);
    }
  }
  /**
   * Removes the matchedDN from the supplied operation if ACIs prevent its
   * disclosure.
   *
   * @param operation
   *          where to filter the matchedDN from
   */
  static void filterNonDisclosableMatchedDN(Operation operation)
  {
    if (operation.getMatchedDN() == null)
    {
      return;
    }
    try
    {
      if (!AccessControlConfigManager.getInstance().getAccessControlHandler()
          .canDiscloseInformation(null, operation.getMatchedDN(), operation))
      {
        operation.setMatchedDN(null);
      }
    }
    catch (DirectoryException de)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, de);
      }
      operation.setResponseData(de);
      // At this point it is impossible to tell whether the matchedDN can be
      // disclosed. It is probably safer to hide it by default.
      operation.setMatchedDN(null);
    }
  }
  /**
   * Adds the post-read response control to the response if requested.
opends/tests/unit-tests-testng/src/server/org/opends/server/authorization/dseecompat/TargetControlTestCase.java
@@ -353,7 +353,7 @@
            base, filter, "aclRights mail description", false, false,
            0 /* disallowed but non-critical */);
    LDIFModify(aciRight, superUser, PWD, OID_LDAP_READENTRY_PREREAD,
            LDAPResultCode.INSUFFICIENT_ACCESS_RIGHTS);
            LDAPResultCode.NO_SUCH_OBJECT);
    deleteAttrFromEntry (base, "aci");
    String aciAllow=makeAddLDIF("aci", base, controlWC, ALLOW_ALL);
    LDIFModify(aciAllow, DIR_MGR_DN, PWD);