From fe4d6b1f8ee49c858ca2644851377ba2402d9509 Mon Sep 17 00:00:00 2001
From: Jean-Noel Rouvignac <jean-noel.rouvignac@forgerock.com>
Date: Thu, 25 Jul 2013 13:21:03 +0000
Subject: [PATCH] OPENDJ-948 (CR-1873) unauthorized disclosure of directory contents 

---
 opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendModifyOperation.java               |  222 +++++----
 opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendWorkflowElement.java               |  185 ++++++
 opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendModifyDNOperation.java             |  176 ++++---
 opends/src/server/org/opends/server/types/AbstractOperation.java                                                |   84 ++
 opends/src/server/org/opends/server/types/DirectoryException.java                                               |   85 +++
 opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendCompareOperation.java              |  119 ++--
 opends/src/server/org/opends/server/api/AccessControlHandler.java                                               |   37 +
 opends/resource/config/config.ldif                                                                              |    2 
 opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendAddOperation.java                  |  164 ++++--
 opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendSearchOperation.java               |   41 +
 opends/src/server/org/opends/server/loggers/TextAccessLogPublisher.java                                         |   63 +-
 opends/src/server/org/opends/server/core/OperationWrapper.java                                                  |   42 +
 opends/tests/unit-tests-testng/src/server/org/opends/server/authorization/dseecompat/TargetControlTestCase.java |    2 
 opends/src/server/org/opends/server/types/Operation.java                                                        |   46 +
 opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendDeleteOperation.java               |  133 +++--
 15 files changed, 951 insertions(+), 450 deletions(-)

diff --git a/opends/resource/config/config.ldif b/opends/resource/config/config.ldif
index a37d147..ec03cbd 100644
--- a/opends/resource/config/config.ldif
+++ b/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";)
diff --git a/opends/src/server/org/opends/server/api/AccessControlHandler.java b/opends/src/server/org/opends/server/api/AccessControlHandler.java
index b7e5d72..abd412d 100644
--- a/opends/src/server/org/opends/server/api/AccessControlHandler.java
+++ b/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
diff --git a/opends/src/server/org/opends/server/core/OperationWrapper.java b/opends/src/server/org/opends/server/core/OperationWrapper.java
index 0f29a37..421a00c 100644
--- a/opends/src/server/org/opends/server/core/OperationWrapper.java
+++ b/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}
    */
diff --git a/opends/src/server/org/opends/server/loggers/TextAccessLogPublisher.java b/opends/src/server/org/opends/server/loggers/TextAccessLogPublisher.java
index 2ae8ddd..2fc8ea6 100644
--- a/opends/src/server/org/opends/server/loggers/TextAccessLogPublisher.java
+++ b/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,
diff --git a/opends/src/server/org/opends/server/types/AbstractOperation.java b/opends/src/server/org/opends/server/types/AbstractOperation.java
index d58d4d1..390f082 100644
--- a/opends/src/server/org/opends/server/types/AbstractOperation.java
+++ b/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());
   }
 
 
diff --git a/opends/src/server/org/opends/server/types/DirectoryException.java b/opends/src/server/org/opends/server/types/DirectoryException.java
index 14b1730..673ca86 100644
--- a/opends/src/server/org/opends/server/types/DirectoryException.java
+++ b/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;
+  }
+}
diff --git a/opends/src/server/org/opends/server/types/Operation.java b/opends/src/server/org/opends/server/types/Operation.java
index 11772ca..12da6d8 100644
--- a/opends/src/server/org/opends/server/types/Operation.java
+++ b/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.
diff --git a/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendAddOperation.java b/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendAddOperation.java
index 4f09823..cacf2bf 100644
--- a/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendAddOperation.java
+++ b/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));
diff --git a/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendCompareOperation.java b/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendCompareOperation.java
index 54f46d3..3a30f8e 100644
--- a/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendCompareOperation.java
+++ b/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()));
diff --git a/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendDeleteOperation.java b/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendDeleteOperation.java
index d28ac4b..9f09437 100644
--- a/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendDeleteOperation.java
+++ b/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;
diff --git a/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendModifyDNOperation.java b/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendModifyDNOperation.java
index 28c9399..06114da 100644
--- a/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendModifyDNOperation.java
+++ b/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))
diff --git a/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendModifyOperation.java b/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendModifyOperation.java
index 0704a5c..910a7ed 100644
--- a/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendModifyOperation.java
+++ b/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;
diff --git a/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendSearchOperation.java b/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendSearchOperation.java
index b832090..19035f9 100644
--- a/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendSearchOperation.java
+++ b/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,
diff --git a/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendWorkflowElement.java b/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendWorkflowElement.java
index 095cf4a..d71b152 100644
--- a/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendWorkflowElement.java
+++ b/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.
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/authorization/dseecompat/TargetControlTestCase.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/authorization/dseecompat/TargetControlTestCase.java
index 5a51700..cc5a676 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/authorization/dseecompat/TargetControlTestCase.java
+++ b/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);

--
Gitblit v1.10.0