From 4e806081638f22dade6802c2996295d263d3e377 Mon Sep 17 00:00:00 2001
From: neil_a_wilson <neil_a_wilson@localhost>
Date: Mon, 12 Feb 2007 16:39:30 +0000
Subject: [PATCH] Implement support for the proxied-auth privilege, which will be required in order to use the proxied authorization control.  This privilege is also used to determine whether a user can specify an alternate authorization identity for the SASL DIGEST-MD5 and PLAIN mechanisms.

---
 opends/src/server/org/opends/server/core/SearchOperation.java                                                     |   79 +
 opends/src/server/org/opends/server/extensions/DigestMD5SASLMechanismHandler.java                                 |  172 ++++
 opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/DigestMD5SASLMechanismHandlerTestCase.java |  178 ++++
 opends/src/server/org/opends/server/messages/CoreMessages.java                                                    |   13 
 opends/src/server/org/opends/server/messages/ExtensionsMessages.java                                              |  174 ++++
 opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/PlainSASLMechanismHandlerTestCase.java     |  125 +++
 opends/tests/unit-tests-testng/src/server/org/opends/server/types/PrivilegeTestCase.java                          | 1155 +++++++++++++++++++++++++++++
 opends/src/server/org/opends/server/core/CompareOperation.java                                                    |   26 
 opends/tests/unit-tests-testng/src/server/org/opends/server/core/TestModifyDNOperation.java                       |   73 
 opends/src/server/org/opends/server/core/AddOperation.java                                                        |   26 
 opends/src/server/org/opends/server/core/ModifyDNOperation.java                                                   |   27 
 opends/src/server/org/opends/server/core/ModifyOperation.java                                                     |   26 
 opends/src/server/org/opends/server/extensions/PlainSASLMechanismHandler.java                                     |  157 +++
 opends/src/server/org/opends/server/types/AuthenticationInfo.java                                                 |   15 
 opends/src/server/org/opends/server/core/DeleteOperation.java                                                     |   27 
 opends/tests/unit-tests-testng/src/server/org/opends/server/core/CompareOperationTestCase.java                    |   56 
 16 files changed, 2,202 insertions(+), 127 deletions(-)

diff --git a/opends/src/server/org/opends/server/core/AddOperation.java b/opends/src/server/org/opends/server/core/AddOperation.java
index 6d0bd25..6c79f9f 100644
--- a/opends/src/server/org/opends/server/core/AddOperation.java
+++ b/opends/src/server/org/opends/server/core/AddOperation.java
@@ -1775,6 +1775,17 @@
             }
             else if (oid.equals(OID_PROXIED_AUTH_V1))
             {
+              // The requester must have the PROXIED_AUTH privilige in order to
+              // be able to use this control.
+              if (! clientConnection.hasPrivilege(Privilege.PROXIED_AUTH, this))
+              {
+                int msgID = MSGID_PROXYAUTH_INSUFFICIENT_PRIVILEGES;
+                appendErrorMessage(getMessage(msgID));
+                setResultCode(ResultCode.AUTHORIZATION_DENIED);
+                break addProcessing;
+              }
+
+
               ProxiedAuthV1Control proxyControl;
               if (c instanceof ProxiedAuthV1Control)
               {
@@ -1814,12 +1825,21 @@
               }
 
 
-              // FIXME -- Should we specifically check permissions here, or let
-              //          the earlier access control checks handle it?
               setAuthorizationEntry(authorizationEntry);
             }
             else if (oid.equals(OID_PROXIED_AUTH_V2))
             {
+              // The requester must have the PROXIED_AUTH privilige in order to
+              // be able to use this control.
+              if (! clientConnection.hasPrivilege(Privilege.PROXIED_AUTH, this))
+              {
+                int msgID = MSGID_PROXYAUTH_INSUFFICIENT_PRIVILEGES;
+                appendErrorMessage(getMessage(msgID));
+                setResultCode(ResultCode.AUTHORIZATION_DENIED);
+                break addProcessing;
+              }
+
+
               ProxiedAuthV2Control proxyControl;
               if (c instanceof ProxiedAuthV2Control)
               {
@@ -1859,8 +1879,6 @@
               }
 
 
-              // FIXME -- Should we specifically check permissions here, or let
-              //          the earlier access control checks handle it?
               setAuthorizationEntry(authorizationEntry);
             }
 
diff --git a/opends/src/server/org/opends/server/core/CompareOperation.java b/opends/src/server/org/opends/server/core/CompareOperation.java
index 221cc91..abf3204 100644
--- a/opends/src/server/org/opends/server/core/CompareOperation.java
+++ b/opends/src/server/org/opends/server/core/CompareOperation.java
@@ -829,6 +829,17 @@
             }
             else if (oid.equals(OID_PROXIED_AUTH_V1))
             {
+              // The requester must have the PROXIED_AUTH privilige in order to
+              // be able to use this control.
+              if (! clientConnection.hasPrivilege(Privilege.PROXIED_AUTH, this))
+              {
+                int msgID = MSGID_PROXYAUTH_INSUFFICIENT_PRIVILEGES;
+                appendErrorMessage(getMessage(msgID));
+                setResultCode(ResultCode.AUTHORIZATION_DENIED);
+                break compareProcessing;
+              }
+
+
               ProxiedAuthV1Control proxyControl;
               if (c instanceof ProxiedAuthV1Control)
               {
@@ -868,12 +879,21 @@
               }
 
 
-              // FIXME -- Should we specifically check permissions here, or let
-              //          the earlier access control checks handle it?
               setAuthorizationEntry(authorizationEntry);
             }
             else if (oid.equals(OID_PROXIED_AUTH_V2))
             {
+              // The requester must have the PROXIED_AUTH privilige in order to
+              // be able to use this control.
+              if (! clientConnection.hasPrivilege(Privilege.PROXIED_AUTH, this))
+              {
+                int msgID = MSGID_PROXYAUTH_INSUFFICIENT_PRIVILEGES;
+                appendErrorMessage(getMessage(msgID));
+                setResultCode(ResultCode.AUTHORIZATION_DENIED);
+                break compareProcessing;
+              }
+
+
               ProxiedAuthV2Control proxyControl;
               if (c instanceof ProxiedAuthV2Control)
               {
@@ -913,8 +933,6 @@
               }
 
 
-              // FIXME -- Should we specifically check permissions here, or let
-              //          the earlier access control checks handle it?
               setAuthorizationEntry(authorizationEntry);
             }
 
diff --git a/opends/src/server/org/opends/server/core/DeleteOperation.java b/opends/src/server/org/opends/server/core/DeleteOperation.java
index 94748e9..5286f97 100644
--- a/opends/src/server/org/opends/server/core/DeleteOperation.java
+++ b/opends/src/server/org/opends/server/core/DeleteOperation.java
@@ -61,6 +61,7 @@
 import org.opends.server.types.ErrorLogSeverity;
 import org.opends.server.types.LockManager;
 import org.opends.server.types.OperationType;
+import org.opends.server.types.Privilege;
 import org.opends.server.types.ResultCode;
 import org.opends.server.types.SearchFilter;
 import org.opends.server.types.SearchResultEntry;
@@ -793,6 +794,17 @@
             }
             else if (oid.equals(OID_PROXIED_AUTH_V1))
             {
+              // The requester must have the PROXIED_AUTH privilige in order to
+              // be able to use this control.
+              if (! clientConnection.hasPrivilege(Privilege.PROXIED_AUTH, this))
+              {
+                int msgID = MSGID_PROXYAUTH_INSUFFICIENT_PRIVILEGES;
+                appendErrorMessage(getMessage(msgID));
+                setResultCode(ResultCode.AUTHORIZATION_DENIED);
+                break deleteProcessing;
+              }
+
+
               ProxiedAuthV1Control proxyControl;
               if (c instanceof ProxiedAuthV1Control)
               {
@@ -832,12 +844,21 @@
               }
 
 
-              // FIXME -- Should we specifically check permissions here, or let
-              //          the earlier access control checks handle it?
               setAuthorizationEntry(authorizationEntry);
             }
             else if (oid.equals(OID_PROXIED_AUTH_V2))
             {
+              // The requester must have the PROXIED_AUTH privilige in order to
+              // be able to use this control.
+              if (! clientConnection.hasPrivilege(Privilege.PROXIED_AUTH, this))
+              {
+                int msgID = MSGID_PROXYAUTH_INSUFFICIENT_PRIVILEGES;
+                appendErrorMessage(getMessage(msgID));
+                setResultCode(ResultCode.AUTHORIZATION_DENIED);
+                break deleteProcessing;
+              }
+
+
               ProxiedAuthV2Control proxyControl;
               if (c instanceof ProxiedAuthV2Control)
               {
@@ -877,8 +898,6 @@
               }
 
 
-              // FIXME -- Should we specifically check permissions here, or let
-              //          the earlier access control checks handle it?
               setAuthorizationEntry(authorizationEntry);
             }
 
diff --git a/opends/src/server/org/opends/server/core/ModifyDNOperation.java b/opends/src/server/org/opends/server/core/ModifyDNOperation.java
index 46a6402..e0a918d 100644
--- a/opends/src/server/org/opends/server/core/ModifyDNOperation.java
+++ b/opends/src/server/org/opends/server/core/ModifyDNOperation.java
@@ -69,6 +69,7 @@
 import org.opends.server.types.Modification;
 import org.opends.server.types.ModificationType;
 import org.opends.server.types.OperationType;
+import org.opends.server.types.Privilege;
 import org.opends.server.types.RDN;
 import org.opends.server.types.ResultCode;
 import org.opends.server.types.SearchFilter;
@@ -1284,6 +1285,17 @@
             }
             else if (oid.equals(OID_PROXIED_AUTH_V1))
             {
+              // The requester must have the PROXIED_AUTH privilige in order to
+              // be able to use this control.
+              if (! clientConnection.hasPrivilege(Privilege.PROXIED_AUTH, this))
+              {
+                int msgID = MSGID_PROXYAUTH_INSUFFICIENT_PRIVILEGES;
+                appendErrorMessage(getMessage(msgID));
+                setResultCode(ResultCode.AUTHORIZATION_DENIED);
+                break modifyDNProcessing;
+              }
+
+
               ProxiedAuthV1Control proxyControl;
               if (c instanceof ProxiedAuthV1Control)
               {
@@ -1323,12 +1335,21 @@
               }
 
 
-              // FIXME -- Should we specifically check permissions here, or let
-              //          the earlier access control checks handle it?
               setAuthorizationEntry(authorizationEntry);
             }
             else if (oid.equals(OID_PROXIED_AUTH_V2))
             {
+              // The requester must have the PROXIED_AUTH privilige in order to
+              // be able to use this control.
+              if (! clientConnection.hasPrivilege(Privilege.PROXIED_AUTH, this))
+              {
+                int msgID = MSGID_PROXYAUTH_INSUFFICIENT_PRIVILEGES;
+                appendErrorMessage(getMessage(msgID));
+                setResultCode(ResultCode.AUTHORIZATION_DENIED);
+                break modifyDNProcessing;
+              }
+
+
               ProxiedAuthV2Control proxyControl;
               if (c instanceof ProxiedAuthV2Control)
               {
@@ -1368,8 +1389,6 @@
               }
 
 
-              // FIXME -- Should we specifically check permissions here, or let
-              //          the earlier access control checks handle it?
               setAuthorizationEntry(authorizationEntry);
             }
 
diff --git a/opends/src/server/org/opends/server/core/ModifyOperation.java b/opends/src/server/org/opends/server/core/ModifyOperation.java
index be01a29..099f0d2 100644
--- a/opends/src/server/org/opends/server/core/ModifyOperation.java
+++ b/opends/src/server/org/opends/server/core/ModifyOperation.java
@@ -1087,6 +1087,17 @@
             }
             else if (oid.equals(OID_PROXIED_AUTH_V1))
             {
+              // The requester must have the PROXIED_AUTH privilige in order to
+              // be able to use this control.
+              if (! clientConnection.hasPrivilege(Privilege.PROXIED_AUTH, this))
+              {
+                int msgID = MSGID_PROXYAUTH_INSUFFICIENT_PRIVILEGES;
+                appendErrorMessage(getMessage(msgID));
+                setResultCode(ResultCode.AUTHORIZATION_DENIED);
+                break modifyProcessing;
+              }
+
+
               ProxiedAuthV1Control proxyControl;
               if (c instanceof ProxiedAuthV1Control)
               {
@@ -1126,12 +1137,21 @@
               }
 
 
-              // FIXME -- Should we specifically check permissions here, or let
-              //          the earlier access control checks handle it?
               setAuthorizationEntry(authorizationEntry);
             }
             else if (oid.equals(OID_PROXIED_AUTH_V2))
             {
+              // The requester must have the PROXIED_AUTH privilige in order to
+              // be able to use this control.
+              if (! clientConnection.hasPrivilege(Privilege.PROXIED_AUTH, this))
+              {
+                int msgID = MSGID_PROXYAUTH_INSUFFICIENT_PRIVILEGES;
+                appendErrorMessage(getMessage(msgID));
+                setResultCode(ResultCode.AUTHORIZATION_DENIED);
+                break modifyProcessing;
+              }
+
+
               ProxiedAuthV2Control proxyControl;
               if (c instanceof ProxiedAuthV2Control)
               {
@@ -1171,8 +1191,6 @@
               }
 
 
-              // FIXME -- Should we specifically check permissions here, or let
-              //          the earlier access control checks handle it?
               setAuthorizationEntry(authorizationEntry);
             }
 
diff --git a/opends/src/server/org/opends/server/core/SearchOperation.java b/opends/src/server/org/opends/server/core/SearchOperation.java
index d8b6f44..15a319a 100644
--- a/opends/src/server/org/opends/server/core/SearchOperation.java
+++ b/opends/src/server/org/opends/server/core/SearchOperation.java
@@ -66,6 +66,7 @@
 import org.opends.server.types.Entry;
 import org.opends.server.types.FilterType;
 import org.opends.server.types.OperationType;
+import org.opends.server.types.Privilege;
 import org.opends.server.types.ResultCode;
 import org.opends.server.types.SearchFilter;
 import org.opends.server.types.SearchResultEntry;
@@ -1760,6 +1761,17 @@
           }
           else if (oid.equals(OID_PROXIED_AUTH_V1))
           {
+            // The requester must have the PROXIED_AUTH privilige in order to be
+            // able to use this control.
+            if (! clientConnection.hasPrivilege(Privilege.PROXIED_AUTH, this))
+            {
+              int msgID = MSGID_PROXYAUTH_INSUFFICIENT_PRIVILEGES;
+              appendErrorMessage(getMessage(msgID));
+              setResultCode(ResultCode.AUTHORIZATION_DENIED);
+              break searchProcessing;
+            }
+
+
             ProxiedAuthV1Control proxyControl;
             if (c instanceof ProxiedAuthV1Control)
             {
@@ -1783,28 +1795,37 @@
             }
 
 
-              Entry authorizationEntry;
-              try
-              {
-                authorizationEntry = proxyControl.getAuthorizationEntry();
-              }
-              catch (DirectoryException de)
-              {
-                assert debugException(CLASS_NAME, "run", de);
+            Entry authorizationEntry;
+            try
+            {
+              authorizationEntry = proxyControl.getAuthorizationEntry();
+            }
+            catch (DirectoryException de)
+            {
+              assert debugException(CLASS_NAME, "run", de);
 
-                setResultCode(de.getResultCode());
-                appendErrorMessage(de.getErrorMessage());
+              setResultCode(de.getResultCode());
+              appendErrorMessage(de.getErrorMessage());
 
-                break searchProcessing;
-              }
+              break searchProcessing;
+            }
 
 
-              // FIXME -- Should we specifically check permissions here, or let
-              //          the earlier access control checks handle it?
-              setAuthorizationEntry(authorizationEntry);
+            setAuthorizationEntry(authorizationEntry);
           }
           else if (oid.equals(OID_PROXIED_AUTH_V2))
           {
+            // The requester must have the PROXIED_AUTH privilige in order to be
+            // able to use this control.
+            if (! clientConnection.hasPrivilege(Privilege.PROXIED_AUTH, this))
+            {
+              int msgID = MSGID_PROXYAUTH_INSUFFICIENT_PRIVILEGES;
+              appendErrorMessage(getMessage(msgID));
+              setResultCode(ResultCode.AUTHORIZATION_DENIED);
+              break searchProcessing;
+            }
+
+
             ProxiedAuthV2Control proxyControl;
             if (c instanceof ProxiedAuthV2Control)
             {
@@ -1828,25 +1849,23 @@
             }
 
 
-              Entry authorizationEntry;
-              try
-              {
-                authorizationEntry = proxyControl.getAuthorizationEntry();
-              }
-              catch (DirectoryException de)
-              {
-                assert debugException(CLASS_NAME, "run", de);
+            Entry authorizationEntry;
+            try
+            {
+              authorizationEntry = proxyControl.getAuthorizationEntry();
+            }
+            catch (DirectoryException de)
+            {
+              assert debugException(CLASS_NAME, "run", de);
 
-                setResultCode(de.getResultCode());
-                appendErrorMessage(de.getErrorMessage());
+              setResultCode(de.getResultCode());
+              appendErrorMessage(de.getErrorMessage());
 
-                break searchProcessing;
-              }
+              break searchProcessing;
+            }
 
 
-              // FIXME -- Should we specifically check permissions here, or let
-              //          the earlier access control checks handle it?
-              setAuthorizationEntry(authorizationEntry);
+            setAuthorizationEntry(authorizationEntry);
           }
           else if (oid.equals(OID_PERSISTENT_SEARCH))
           {
diff --git a/opends/src/server/org/opends/server/extensions/DigestMD5SASLMechanismHandler.java b/opends/src/server/org/opends/server/extensions/DigestMD5SASLMechanismHandler.java
index 7036ebe..9a3303a 100644
--- a/opends/src/server/org/opends/server/extensions/DigestMD5SASLMechanismHandler.java
+++ b/opends/src/server/org/opends/server/extensions/DigestMD5SASLMechanismHandler.java
@@ -55,6 +55,7 @@
 import org.opends.server.core.DirectoryServer;
 import org.opends.server.core.PasswordPolicyState;
 import org.opends.server.protocols.asn1.ASN1OctetString;
+import org.opends.server.protocols.internal.InternalClientConnection;
 import org.opends.server.types.AuthenticationInfo;
 import org.opends.server.types.ByteString;
 import org.opends.server.types.ConfigChangeResult;
@@ -66,6 +67,7 @@
 import org.opends.server.types.ErrorLogSeverity;
 import org.opends.server.types.InitializationException;
 import org.opends.server.types.LockManager;
+import org.opends.server.types.Privilege;
 import org.opends.server.types.ResultCode;
 import org.opends.server.util.Base64;
 
@@ -893,6 +895,170 @@
     }
 
 
+    Entry authZEntry = userEntry;
+    if (responseAuthzID != null)
+    {
+      if (responseAuthzID.length() == 0)
+      {
+        // The authorization ID must not be an empty string.
+        bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
+
+        int    msgID   = MSGID_SASLDIGESTMD5_EMPTY_AUTHZID;
+        String message = getMessage(msgID);
+        bindOperation.setAuthFailureReason(msgID, message);
+        return;
+      }
+      else if (! responseAuthzID.equals(responseUserName))
+      {
+        String lowerAuthzID = toLowerCase(responseAuthzID);
+
+        if (lowerAuthzID.startsWith("dn:"))
+        {
+          DN authzDN;
+          try
+          {
+            authzDN = DN.decode(responseAuthzID.substring(3));
+          }
+          catch (DirectoryException de)
+          {
+            assert debugException(CLASS_NAME, "processSASLBind", de);
+
+            bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
+
+            int    msgID   = MSGID_SASLDIGESTMD5_AUTHZID_INVALID_DN;
+            String message = getMessage(msgID, responseAuthzID,
+                                        de.getErrorMessage());
+            bindOperation.setAuthFailureReason(msgID, message);
+            return;
+          }
+
+          DN actualAuthzDN = DirectoryServer.getActualRootBindDN(authzDN);
+          if (actualAuthzDN != null)
+          {
+            authzDN = actualAuthzDN;
+          }
+
+          if (! authzDN.equals(userEntry.getDN()))
+          {
+            AuthenticationInfo tempAuthInfo =
+              new AuthenticationInfo(userEntry,
+                       DirectoryServer.isRootDN(userEntry.getDN()));
+            InternalClientConnection tempConn =
+                 new InternalClientConnection(tempAuthInfo);
+            if (! tempConn.hasPrivilege(Privilege.PROXIED_AUTH, bindOperation))
+            {
+              bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
+
+              int msgID = MSGID_SASLDIGESTMD5_AUTHZID_INSUFFICIENT_PRIVILEGES;
+              String message = getMessage(msgID,
+                                          String.valueOf(userEntry.getDN()));
+              bindOperation.setAuthFailureReason(msgID, message);
+              return;
+            }
+
+            if (authzDN.isNullDN())
+            {
+              authZEntry = null;
+            }
+            else
+            {
+              try
+              {
+                authZEntry = DirectoryServer.getEntry(authzDN);
+                if (authZEntry == null)
+                {
+                  bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
+
+                  int msgID = MSGID_SASLDIGESTMD5_AUTHZID_NO_SUCH_ENTRY;
+                  String message = getMessage(msgID, String.valueOf(authzDN));
+                  bindOperation.setAuthFailureReason(msgID, message);
+                  return;
+                }
+              }
+              catch (DirectoryException de)
+              {
+                assert debugException(CLASS_NAME, "processSASLBind", de);
+
+                bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
+
+                int msgID = MSGID_SASLDIGESTMD5_AUTHZID_CANNOT_GET_ENTRY;
+                String message = getMessage(msgID, String.valueOf(authzDN),
+                                            de.getErrorMessage());
+                bindOperation.setAuthFailureReason(msgID, message);
+                return;
+              }
+            }
+          }
+        }
+        else
+        {
+          String idStr;
+          if (lowerAuthzID.startsWith("u:"))
+          {
+            idStr = responseAuthzID.substring(2);
+          }
+          else
+          {
+            idStr = responseAuthzID;
+          }
+
+          if (idStr.length() == 0)
+          {
+            authZEntry = null;
+          }
+          else
+          {
+            try
+            {
+              authZEntry = identityMapper.getEntryForID(idStr);
+              if (authZEntry == null)
+              {
+                bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
+
+                int    msgID   = MSGID_SASLDIGESTMD5_AUTHZID_NO_MAPPED_ENTRY;
+                String message = getMessage(msgID, responseAuthzID);
+                bindOperation.setAuthFailureReason(msgID, message);
+                return;
+              }
+            }
+            catch (DirectoryException de)
+            {
+              assert debugException(CLASS_NAME, "processSASLBind", de);
+
+              bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
+
+              int    msgID   = MSGID_SASLDIGESTMD5_CANNOT_MAP_AUTHZID;
+              String message = getMessage(msgID, responseAuthzID,
+                                          de.getErrorMessage());
+              bindOperation.setAuthFailureReason(msgID, message);
+              return;
+            }
+          }
+
+          if ((authZEntry == null) ||
+              (! authZEntry.getDN().equals(userEntry.getDN())))
+          {
+            AuthenticationInfo tempAuthInfo =
+              new AuthenticationInfo(userEntry,
+                       DirectoryServer.isRootDN(userEntry.getDN()));
+            InternalClientConnection tempConn =
+                 new InternalClientConnection(tempAuthInfo);
+            if (! tempConn.hasPrivilege(Privilege.PROXIED_AUTH, bindOperation))
+            {
+              bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
+
+              int msgID = MSGID_SASLDIGESTMD5_AUTHZID_INSUFFICIENT_PRIVILEGES;
+              String message = getMessage(msgID,
+                                          String.valueOf(userEntry.getDN()));
+              bindOperation.setAuthFailureReason(msgID, message);
+              return;
+            }
+          }
+        }
+      }
+    }
+
+
     // Get the clear-text passwords from the user entry, if there are any.
     List<ByteString> clearPasswords;
     try
@@ -968,9 +1134,6 @@
     }
 
 
-    // FIXME -- Need to do something with the authzid.
-
-
     // Generate the response auth element to include in the response to the
     // client.
     byte[] responseAuth;
@@ -1011,7 +1174,8 @@
 
 
     AuthenticationInfo authInfo =
-         new AuthenticationInfo(userEntry, SASL_MECHANISM_DIGEST_MD5,
+         new AuthenticationInfo(userEntry, authZEntry,
+                                SASL_MECHANISM_DIGEST_MD5,
                                 DirectoryServer.isRootDN(userEntry.getDN()));
     bindOperation.setAuthenticationInfo(authInfo);
     return;
diff --git a/opends/src/server/org/opends/server/extensions/PlainSASLMechanismHandler.java b/opends/src/server/org/opends/server/extensions/PlainSASLMechanismHandler.java
index 3adb6c0..7afe91d 100644
--- a/opends/src/server/org/opends/server/extensions/PlainSASLMechanismHandler.java
+++ b/opends/src/server/org/opends/server/extensions/PlainSASLMechanismHandler.java
@@ -44,6 +44,7 @@
 import org.opends.server.core.DirectoryServer;
 import org.opends.server.core.PasswordPolicyState;
 import org.opends.server.protocols.asn1.ASN1OctetString;
+import org.opends.server.protocols.internal.InternalClientConnection;
 import org.opends.server.types.AuthenticationInfo;
 import org.opends.server.types.ByteString;
 import org.opends.server.types.ConfigChangeResult;
@@ -52,6 +53,7 @@
 import org.opends.server.types.Entry;
 import org.opends.server.types.InitializationException;
 import org.opends.server.types.LockManager;
+import org.opends.server.types.Privilege;
 import org.opends.server.types.ResultCode;
 
 import static org.opends.server.config.ConfigConstants.*;
@@ -398,6 +400,156 @@
     }
 
 
+    // If an authorization ID was provided, then make sure that it is
+    // acceptable.
+    Entry authZEntry = userEntry;
+    if (authzID != null)
+    {
+      String lowerAuthzID = toLowerCase(authzID);
+      if (lowerAuthzID.startsWith("dn:"))
+      {
+        DN authzDN;
+        try
+        {
+          authzDN = DN.decode(authzID.substring(3));
+        }
+        catch (DirectoryException de)
+        {
+          assert debugException(CLASS_NAME, "processSASLBind", de);
+
+          bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
+
+          int    msgID   = MSGID_SASLPLAIN_AUTHZID_INVALID_DN;
+          String message = getMessage(msgID, authzID, de.getErrorMessage());
+          bindOperation.setAuthFailureReason(msgID, message);
+          return;
+        }
+
+        DN actualAuthzDN = DirectoryServer.getActualRootBindDN(authzDN);
+        if (actualAuthzDN != null)
+        {
+          authzDN = actualAuthzDN;
+        }
+
+        if (! authzDN.equals(userEntry.getDN()))
+        {
+          AuthenticationInfo tempAuthInfo =
+            new AuthenticationInfo(userEntry,
+                     DirectoryServer.isRootDN(userEntry.getDN()));
+          InternalClientConnection tempConn =
+               new InternalClientConnection(tempAuthInfo);
+          if (! tempConn.hasPrivilege(Privilege.PROXIED_AUTH, bindOperation))
+          {
+            bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
+
+            int msgID = MSGID_SASLPLAIN_AUTHZID_INSUFFICIENT_PRIVILEGES;
+            String message = getMessage(msgID,
+                                        String.valueOf(userEntry.getDN()));
+            bindOperation.setAuthFailureReason(msgID, message);
+            return;
+          }
+
+          if (authzDN.isNullDN())
+          {
+            authZEntry = null;
+          }
+          else
+          {
+            try
+            {
+              authZEntry = DirectoryServer.getEntry(authzDN);
+              if (authZEntry == null)
+              {
+                bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
+
+                int msgID = MSGID_SASLPLAIN_AUTHZID_NO_SUCH_ENTRY;
+                String message = getMessage(msgID, String.valueOf(authzDN));
+                bindOperation.setAuthFailureReason(msgID, message);
+                return;
+              }
+            }
+            catch (DirectoryException de)
+            {
+              assert debugException(CLASS_NAME, "processSASLBind", de);
+
+              bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
+
+              int msgID = MSGID_SASLPLAIN_AUTHZID_CANNOT_GET_ENTRY;
+              String message = getMessage(msgID, String.valueOf(authzDN),
+                                          de.getErrorMessage());
+              bindOperation.setAuthFailureReason(msgID, message);
+              return;
+            }
+          }
+        }
+      }
+      else
+      {
+        String idStr;
+        if (lowerAuthzID.startsWith("u:"))
+        {
+          idStr = authzID.substring(2);
+        }
+        else
+        {
+          idStr = authzID;
+        }
+
+        if (idStr.length() == 0)
+        {
+          authZEntry = null;
+        }
+        else
+        {
+          try
+          {
+            authZEntry = identityMapper.getEntryForID(idStr);
+            if (authZEntry == null)
+            {
+              bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
+
+              int    msgID   = MSGID_SASLPLAIN_AUTHZID_NO_MAPPED_ENTRY;
+              String message = getMessage(msgID, authzID);
+              bindOperation.setAuthFailureReason(msgID, message);
+              return;
+            }
+          }
+          catch (DirectoryException de)
+          {
+            assert debugException(CLASS_NAME, "processSASLBind", de);
+
+            bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
+
+            int    msgID   = MSGID_SASLPLAIN_AUTHZID_CANNOT_MAP_AUTHZID;
+            String message = getMessage(msgID, authzID, de.getErrorMessage());
+            bindOperation.setAuthFailureReason(msgID, message);
+            return;
+          }
+        }
+
+        if ((authZEntry == null) ||
+            (! authZEntry.getDN().equals(userEntry.getDN())))
+        {
+          AuthenticationInfo tempAuthInfo =
+            new AuthenticationInfo(userEntry,
+                     DirectoryServer.isRootDN(userEntry.getDN()));
+          InternalClientConnection tempConn =
+               new InternalClientConnection(tempAuthInfo);
+          if (! tempConn.hasPrivilege(Privilege.PROXIED_AUTH, bindOperation))
+          {
+            bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
+
+            int msgID = MSGID_SASLPLAIN_AUTHZID_INSUFFICIENT_PRIVILEGES;
+            String message = getMessage(msgID,
+                                        String.valueOf(userEntry.getDN()));
+            bindOperation.setAuthFailureReason(msgID, message);
+            return;
+          }
+        }
+      }
+    }
+
+
     // Get the password policy for the user and use it to determine if the
     // provided password was correct.
     try
@@ -428,14 +580,11 @@
     }
 
 
-    // FIXME -- Figure out what to do with the authzID if one was provided.
-
-
     // If we've gotten here, then the authentication was successful.
     bindOperation.setResultCode(ResultCode.SUCCESS);
 
     AuthenticationInfo authInfo =
-         new AuthenticationInfo(userEntry, SASL_MECHANISM_PLAIN,
+         new AuthenticationInfo(userEntry, authZEntry, SASL_MECHANISM_PLAIN,
                                 DirectoryServer.isRootDN(userEntry.getDN()));
     bindOperation.setAuthenticationInfo(authInfo);
     return;
diff --git a/opends/src/server/org/opends/server/messages/CoreMessages.java b/opends/src/server/org/opends/server/messages/CoreMessages.java
index 4a6f374..160a34f 100644
--- a/opends/src/server/org/opends/server/messages/CoreMessages.java
+++ b/opends/src/server/org/opends/server/messages/CoreMessages.java
@@ -6229,6 +6229,16 @@
 
 
   /**
+   * The message ID for the message that will be used when a client attempts to
+   * use the proxied authorization control without sufficient privileges.  This
+   * does not take any arguments.
+   */
+  public static final int MSGID_PROXYAUTH_INSUFFICIENT_PRIVILEGES =
+       CATEGORY_MASK_CORE | SEVERITY_MASK_MILD_ERROR | 595;
+
+
+
+  /**
    * Associates a set of generic messages with the message IDs defined
    * in this class.
    */
@@ -8423,6 +8433,9 @@
     registerMessage(MSGID_CLIENTCONNECTION_AUDIT_HASPRIVILEGES,
                     "hasPrivilege determination for connID=%d opID=%d " +
                     "requesterDN=\"%s\" privilegeSet=\"%s\" result=%b");
+    registerMessage(MSGID_PROXYAUTH_INSUFFICIENT_PRIVILEGES,
+                    "You do not have sufficient privileges to use the " +
+                    "proxied authorization control.");
   }
 }
 
diff --git a/opends/src/server/org/opends/server/messages/ExtensionsMessages.java b/opends/src/server/org/opends/server/messages/ExtensionsMessages.java
index 66e04e5..56fba4d 100644
--- a/opends/src/server/org/opends/server/messages/ExtensionsMessages.java
+++ b/opends/src/server/org/opends/server/messages/ExtensionsMessages.java
@@ -4150,6 +4150,141 @@
 
 
   /**
+   * The message ID for the message that will be used if the DIGEST-MD5 authzid
+   * is the empty string.  This does not take any arguments.
+   */
+  public static final int MSGID_SASLDIGESTMD5_EMPTY_AUTHZID =
+       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_MILD_ERROR | 393;
+
+
+
+  /**
+   * The message ID for the message that will be used if the DIGEST-MD5 authzid
+   * contained an invalid DN.  This takes two arguments, which are the authzid
+   * and the reason that it was invalid.
+   */
+  public static final int MSGID_SASLDIGESTMD5_AUTHZID_INVALID_DN =
+       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_MILD_ERROR | 394;
+
+
+
+  /**
+   * The message ID for the message that will be used if the authenticating user
+   * does not have sufficient privilege to specify an authorization identity
+   * that is different from the authentication identity.  This takes a single
+   * argument, which is the DN of the authentication identity.
+   */
+  public static final int MSGID_SASLDIGESTMD5_AUTHZID_INSUFFICIENT_PRIVILEGES =
+       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_MILD_ERROR | 395;
+
+
+
+  /**
+   * The message ID for the message that will be used if the DIGEST-MD5 authzid
+   * references an entry that does not exist.  This takes a single argument,
+   * which is the DN of the target entry.
+   */
+  public static final int MSGID_SASLDIGESTMD5_AUTHZID_NO_SUCH_ENTRY =
+       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_MILD_ERROR | 396;
+
+
+
+  /**
+   * The message ID for the message that will be used if an error occurs while
+   * attempting to get the entry for the authorization identity.  This takes two
+   * arguments, which are the authorization DN and a message explaining the
+   * problem that occurred.
+   */
+  public static final int MSGID_SASLDIGESTMD5_AUTHZID_CANNOT_GET_ENTRY =
+       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_MILD_ERROR | 397;
+
+
+
+  /**
+   * The message ID for the message that will be used if the "u:"-form
+   * authorization ID cannot be mapped to a user entry.  This takes a single
+   * argument, which is the authzID string.
+   */
+  public static final int MSGID_SASLDIGESTMD5_AUTHZID_NO_MAPPED_ENTRY =
+       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_MILD_ERROR | 398;
+
+
+
+  /**
+   * The message ID for the message that will be used if an error occurs while
+   * attempting to map the "u:"-form authorization ID to a user entry.  This
+   * takes two arguments, which are the authzID string and a message explaining
+   * the problem that occurred.
+   */
+  public static final int MSGID_SASLDIGESTMD5_CANNOT_MAP_AUTHZID =
+       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_MILD_ERROR | 399;
+
+
+
+  /**
+   * The message ID for the message that will be used if the authorization ID is
+   * a malformed DN.  This takes two arguments, which are the authorization ID
+   * string and a message explaining the problem that occurred.
+   */
+  public static final int MSGID_SASLPLAIN_AUTHZID_INVALID_DN =
+       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_MILD_ERROR | 400;
+
+
+
+  /**
+   * The message ID for the message that will be used if the authenticating user
+   * attempts to provide an alternate authorization ID but does not have
+   * sufficient privileges to do so.  This takes a single argument, which is the
+   * DN of the authenticating user.
+   */
+  public static final int MSGID_SASLPLAIN_AUTHZID_INSUFFICIENT_PRIVILEGES =
+       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_MILD_ERROR | 401;
+
+
+
+  /**
+   * The message ID for the message that will be used if the authorization ID
+   * contains the DN of an entry that does not exist.  This takes a single
+   * argument, which is the authorization DN.
+   */
+  public static final int MSGID_SASLPLAIN_AUTHZID_NO_SUCH_ENTRY =
+       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_MILD_ERROR | 402;
+
+
+
+  /**
+   * The message ID for the message that will be used if a problem occurs while
+   * trying to get the entry for the authorization DN.  This takes two
+   * arguments, which are the authorization DN and a message explaining the
+   * problem that occurred.
+   */
+  public static final int MSGID_SASLPLAIN_AUTHZID_CANNOT_GET_ENTRY =
+       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_MILD_ERROR | 403;
+
+
+
+  /**
+   * The message ID for the message that will be used if the authorization ID
+   * specifies a username that does not map to an entry.  This takes a single
+   * argument, which is the authorization ID string.
+   */
+  public static final int MSGID_SASLPLAIN_AUTHZID_NO_MAPPED_ENTRY =
+       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_MILD_ERROR | 404;
+
+
+
+  /**
+   * The message ID for the message that will be used if an error occurs while
+   * trying to map the authorization ID username to an entry.  This takes two
+   * arguments, which are the authorization ID string and a message explaining
+   * the problem that occurred.
+   */
+  public static final int MSGID_SASLPLAIN_AUTHZID_CANNOT_MAP_AUTHZID =
+       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_MILD_ERROR | 405;
+
+
+
+  /**
    * Associates a set of generic messages with the message IDs defined in this
    * class.
    */
@@ -5084,6 +5219,24 @@
     registerMessage(MSGID_SASLPLAIN_NO_MATCHING_ENTRIES,
                     "The server was not able to find any user entries for " +
                     "the provided authentication ID of %s.");
+    registerMessage(MSGID_SASLPLAIN_AUTHZID_INVALID_DN,
+                    "The provided authorization ID %s contained an invalid " +
+                    "DN:  %s.");
+    registerMessage(MSGID_SASLPLAIN_AUTHZID_INSUFFICIENT_PRIVILEGES,
+                    "The authenticating user %s does not have sufficient " +
+                    "privileges to specify an alternate authorization ID.");
+    registerMessage(MSGID_SASLPLAIN_AUTHZID_NO_SUCH_ENTRY,
+                    "The entry corresponding to authorization DN %s does not " +
+                    "exist in the Directory Server.");
+    registerMessage(MSGID_SASLPLAIN_AUTHZID_CANNOT_GET_ENTRY,
+                    "An error occurred while attempting to retrieve entry %s " +
+                    "specified as the authorization ID:  %s.");
+    registerMessage(MSGID_SASLPLAIN_AUTHZID_NO_MAPPED_ENTRY,
+                    "No entry corresponding to authorization ID %s was found " +
+                    "in the server.");
+    registerMessage(MSGID_SASLPLAIN_AUTHZID_CANNOT_MAP_AUTHZID,
+                    "An error occurred while attempting to map authorization " +
+                    "ID %s to a user entry:  %s.");
     registerMessage(MSGID_SASLPLAIN_NO_PW_ATTR,
                     "The SASL PLAIN authentication failed because the mapped " +
                     "user entry did not contain any values for the %s " +
@@ -5469,6 +5622,27 @@
     registerMessage(MSGID_SASLDIGESTMD5_NO_MATCHING_ENTRIES,
                     "The server was not able to find any user entries for " +
                     "the provided username of %s.");
+    registerMessage(MSGID_SASLDIGESTMD5_EMPTY_AUTHZID,
+                    "The provided authorization ID was empty, which is not " +
+                    "allowed for DIGEST-MD5 authentication.");
+    registerMessage(MSGID_SASLDIGESTMD5_AUTHZID_INVALID_DN,
+                    "The provided authorization ID %s contained an invalid " +
+                    "DN:  %s.");
+    registerMessage(MSGID_SASLDIGESTMD5_AUTHZID_INSUFFICIENT_PRIVILEGES,
+                    "The authenticating user %s does not have sufficient " +
+                    "privileges to assume a different authorization identity.");
+    registerMessage(MSGID_SASLDIGESTMD5_AUTHZID_NO_SUCH_ENTRY,
+                    "The entry %s specified as the authorization identity " +
+                    "does not exist.");
+    registerMessage(MSGID_SASLDIGESTMD5_AUTHZID_CANNOT_GET_ENTRY,
+                    "The entry %s specified as the authorization identity " +
+                    "could not be retrieved:  %s.");
+    registerMessage(MSGID_SASLDIGESTMD5_AUTHZID_NO_MAPPED_ENTRY,
+                    "The server was unable to find any entry corresponding " +
+                    "to authorization ID %s.");
+    registerMessage(MSGID_SASLDIGESTMD5_CANNOT_MAP_AUTHZID,
+                    "An error occurred while attempting to map authorization " +
+                    "ID %s to a user entry:  %s.");
     registerMessage(MSGID_SASLDIGESTMD5_NO_PW_ATTR,
                     "The SASL DIGEST-MD5 authentication failed because the " +
                     "mapped user entry did not contain any values for the %s " +
diff --git a/opends/src/server/org/opends/server/types/AuthenticationInfo.java b/opends/src/server/org/opends/server/types/AuthenticationInfo.java
index a570693..bde9e7d 100644
--- a/opends/src/server/org/opends/server/types/AuthenticationInfo.java
+++ b/opends/src/server/org/opends/server/types/AuthenticationInfo.java
@@ -224,8 +224,9 @@
    * @param  authorizationEntry   The entry of the user that will be
    *                              used as the default authorization
    *                              identity, or {@code null} to
-   *                              indicate that it should be the same
-   *                              as the authentication entry.
+   *                              indicate that the authorization
+   *                              identity should be the
+   *                              unauthenticated user.
    * @param  saslMechanism        The SASL mechanism used to
    *                              authenticate.  This must be provided
    *                              in all-uppercase characters and must
@@ -246,17 +247,9 @@
     ensureNotNull(authenticationEntry, saslMechanism);
 
     this.authenticationEntry = authenticationEntry;
+    this.authorizationEntry  = authorizationEntry;
     this.isRoot              = isRoot;
 
-    if (authorizationEntry == null)
-    {
-      this.authorizationEntry = authenticationEntry;
-    }
-    else
-    {
-      this.authorizationEntry = authorizationEntry;
-    }
-
     isAuthenticated    = true;
     mustChangePassword = false;
     simplePassword     = null;
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/core/CompareOperationTestCase.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/core/CompareOperationTestCase.java
index 347acd8..396fea5 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/core/CompareOperationTestCase.java
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/core/CompareOperationTestCase.java
@@ -22,7 +22,7 @@
  * CDDL HEADER END
  *
  *
- *      Portions Copyright 2006 Sun Microsystems, Inc.
+ *      Portions Copyright 2006-2007 Sun Microsystems, Inc.
  */
 
 package org.opends.server.core;
@@ -32,8 +32,10 @@
 import org.opends.server.protocols.asn1.ASN1Reader;
 import org.opends.server.protocols.asn1.ASN1Writer;
 import org.opends.server.protocols.ldap.*;
+import org.opends.server.types.AuthenticationInfo;
 import org.opends.server.types.Control;
 import org.opends.server.types.ResultCode;
+import org.opends.server.types.DN;
 import org.opends.server.types.Entry;
 import org.opends.server.types.LockManager;
 import org.opends.server.TestCaseUtils;
@@ -54,6 +56,7 @@
 public class CompareOperationTestCase extends OperationTestCase
 {
   private Entry entry;
+  private InternalClientConnection proxyUserConn;
 
 
   @BeforeClass
@@ -101,6 +104,25 @@
                                entry.getOperationalAttributes());
     assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS);
     assertNotNull(DirectoryServer.getEntry(entry.getDN()));
+
+    // Add a user capable of using the proxied authorization control.
+    TestCaseUtils.addEntry(
+         "dn: uid=proxy.user,o=test",
+         "objectClass: top",
+         "objectClass: person",
+         "objectClass: organizationalPerson",
+         "objectClass: inetOrgPerson",
+         "uid: proxy.user",
+         "givenName: Proxy",
+         "sn: User",
+         "cn: Proxy User",
+         "userPassword: password",
+         "ds-privilege-name: proxied-auth");
+
+    Entry proxyUserEntry =
+               DirectoryServer.getEntry(DN.decode("uid=proxy.user,o=test"));
+    AuthenticationInfo authInfo = new AuthenticationInfo(proxyUserEntry, false);
+    proxyUserConn = new InternalClientConnection(authInfo);
   }
 
 
@@ -435,16 +457,14 @@
   {
     InvocationCounterPlugin.resetAllCounters();
 
-    InternalClientConnection conn =
-         InternalClientConnection.getRootConnection();
-
     ProxiedAuthV1Control authV1Control =
          new ProxiedAuthV1Control(new ASN1OctetString());
     List<Control> controls = new ArrayList<Control>();
     controls.add(authV1Control);
 
     CompareOperation compareOperation =
-         new CompareOperation(conn, InternalClientConnection.nextOperationID(),
+         new CompareOperation(proxyUserConn,
+                              InternalClientConnection.nextOperationID(),
                               InternalClientConnection.nextMessageID(),
                               controls,
                               new ASN1OctetString(entry.getDN().toString()),
@@ -461,10 +481,9 @@
   @Test
   public void testCompareProxiedAuthV1Denied() throws Exception
   {
-    InvocationCounterPlugin.resetAllCounters();
 
-    InternalClientConnection conn =
-         InternalClientConnection.getRootConnection();
+
+    InvocationCounterPlugin.resetAllCounters();
 
     ProxiedAuthV1Control authV1Control =
          new ProxiedAuthV1Control(new ASN1OctetString("cn=nonexistent,o=test"));
@@ -472,7 +491,8 @@
     controls.add(authV1Control);
 
     CompareOperation compareOperation =
-         new CompareOperation(conn, InternalClientConnection.nextOperationID(),
+         new CompareOperation(proxyUserConn,
+                              InternalClientConnection.nextOperationID(),
                               InternalClientConnection.nextMessageID(),
                               controls,
                               new ASN1OctetString(entry.getDN().toString()),
@@ -490,16 +510,14 @@
   {
     InvocationCounterPlugin.resetAllCounters();
 
-    InternalClientConnection conn =
-         InternalClientConnection.getRootConnection();
-
     ProxiedAuthV2Control authV2Control =
          new ProxiedAuthV2Control(new ASN1OctetString("dn:"));
     List<Control> controls = new ArrayList<Control>();
     controls.add(authV2Control);
 
     CompareOperation compareOperation =
-         new CompareOperation(conn, InternalClientConnection.nextOperationID(),
+         new CompareOperation(proxyUserConn,
+                              InternalClientConnection.nextOperationID(),
                               InternalClientConnection.nextMessageID(),
                               controls,
                               new ASN1OctetString(entry.getDN().toString()),
@@ -518,16 +536,14 @@
   {
     InvocationCounterPlugin.resetAllCounters();
 
-    InternalClientConnection conn =
-         InternalClientConnection.getRootConnection();
-
     ProxiedAuthV2Control authV2Control = new ProxiedAuthV2Control(
          new ASN1OctetString("dn:cn=nonexistent,o=test"));
     List<Control> controls = new ArrayList<Control>();
     controls.add(authV2Control);
 
     CompareOperation compareOperation =
-         new CompareOperation(conn, InternalClientConnection.nextOperationID(),
+         new CompareOperation(proxyUserConn,
+                              InternalClientConnection.nextOperationID(),
                               InternalClientConnection.nextMessageID(),
                               controls,
                               new ASN1OctetString(entry.getDN().toString()),
@@ -545,9 +561,6 @@
   {
     InvocationCounterPlugin.resetAllCounters();
 
-    InternalClientConnection conn =
-         InternalClientConnection.getRootConnection();
-
     ProxiedAuthV2Control authV2Control =
          new ProxiedAuthV2Control(new ASN1OctetString());
     authV2Control.setCritical(false);
@@ -555,7 +568,8 @@
     controls.add(authV2Control);
 
     CompareOperation compareOperation =
-         new CompareOperation(conn, InternalClientConnection.nextOperationID(),
+         new CompareOperation(proxyUserConn,
+                              InternalClientConnection.nextOperationID(),
                               InternalClientConnection.nextMessageID(),
                               controls,
                               new ASN1OctetString(entry.getDN().toString()),
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/core/TestModifyDNOperation.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/core/TestModifyDNOperation.java
index 3e53387..75ae817 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/core/TestModifyDNOperation.java
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/core/TestModifyDNOperation.java
@@ -56,6 +56,7 @@
 {
 
   private Entry entry;
+  private InternalClientConnection proxyUserConn;
 
   @BeforeClass
   public void setUp() throws Exception
@@ -132,6 +133,25 @@
                                entry.getOperationalAttributes());
     assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS);
     assertNotNull(DirectoryServer.getEntry(entry.getDN()));
+
+    // Add a user capable of using the proxied authorization control.
+    TestCaseUtils.addEntry(
+         "dn: uid=proxy.user,o=test",
+         "objectClass: top",
+         "objectClass: person",
+         "objectClass: organizationalPerson",
+         "objectClass: inetOrgPerson",
+         "uid: proxy.user",
+         "givenName: Proxy",
+         "sn: User",
+         "cn: Proxy User",
+         "userPassword: password",
+         "ds-privilege-name: proxied-auth");
+
+    Entry proxyUserEntry =
+               DirectoryServer.getEntry(DN.decode("uid=proxy.user,o=test"));
+    AuthenticationInfo authInfo = new AuthenticationInfo(proxyUserEntry, false);
+    proxyUserConn = new InternalClientConnection(authInfo);
   }
 
   /**
@@ -806,12 +826,9 @@
     controls.add(authV1Control);
     InvocationCounterPlugin.resetAllCounters();
 
-    InternalClientConnection conn =
-         InternalClientConnection.getRootConnection();
-
     ModifyDNOperation modifyDNOperation =
-         new ModifyDNOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
-                               controls,
+         new ModifyDNOperation(proxyUserConn, proxyUserConn.nextOperationID(),
+                               proxyUserConn.nextMessageID(), controls,
                                new ASN1OctetString("uid=user.0,ou=People,dc=example,dc=com"),
                                new ASN1OctetString("uid=user.test0"), false,
                                null);
@@ -835,8 +852,8 @@
     InvocationCounterPlugin.resetAllCounters();
 
     modifyDNOperation =
-         new ModifyDNOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
-                               controls,
+         new ModifyDNOperation(proxyUserConn, proxyUserConn.nextOperationID(),
+                               proxyUserConn.nextMessageID(), controls,
                                new ASN1OctetString("uid=user.test0,ou=People,dc=example,dc=com"),
                                new ASN1OctetString("uid=user.0"), true,
                                null);
@@ -868,12 +885,9 @@
     controls.add(authV1Control);
     InvocationCounterPlugin.resetAllCounters();
 
-    InternalClientConnection conn =
-         InternalClientConnection.getRootConnection();
-
     ModifyDNOperation modifyDNOperation =
-         new ModifyDNOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
-                               controls,
+         new ModifyDNOperation(proxyUserConn, proxyUserConn.nextOperationID(),
+                               proxyUserConn.nextMessageID(), controls,
                                DN.decode("uid=user.0,ou=People,dc=example,dc=com"),
                                RDN.decode("uid=user.test0"), false,
                                null);
@@ -897,7 +911,8 @@
     InvocationCounterPlugin.resetAllCounters();
 
     modifyDNOperation =
-         new ModifyDNOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+         new ModifyDNOperation(proxyUserConn, proxyUserConn.nextOperationID(),
+                               proxyUserConn.nextMessageID(),
                                controls,
                                DN.decode("uid=user.test0,ou=People,dc=example,dc=com"),
                                RDN.decode("uid=user.0"), true,
@@ -930,12 +945,9 @@
     controls.add(authV1Control);
     InvocationCounterPlugin.resetAllCounters();
 
-    InternalClientConnection conn =
-         InternalClientConnection.getRootConnection();
-
     ModifyDNOperation modifyDNOperation =
-         new ModifyDNOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
-                               controls,
+         new ModifyDNOperation(proxyUserConn, proxyUserConn.nextOperationID(),
+                               proxyUserConn.nextMessageID(), controls,
                                DN.decode("uid=user.0,ou=People,dc=example,dc=com"),
                                RDN.decode("uid=user.test0"), false,
                                null);
@@ -957,12 +969,9 @@
     controls.add(authV2Control);
     InvocationCounterPlugin.resetAllCounters();
 
-    InternalClientConnection conn =
-         InternalClientConnection.getRootConnection();
-
     ModifyDNOperation modifyDNOperation =
-         new ModifyDNOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
-                               controls,
+         new ModifyDNOperation(proxyUserConn, proxyUserConn.nextOperationID(),
+                               proxyUserConn.nextMessageID(), controls,
                                DN.decode("uid=user.0,ou=People,dc=example,dc=com"),
                                RDN.decode("uid=user.test0"), false,
                                null);
@@ -986,8 +995,8 @@
     InvocationCounterPlugin.resetAllCounters();
 
     modifyDNOperation =
-         new ModifyDNOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
-                               controls,
+         new ModifyDNOperation(proxyUserConn, proxyUserConn.nextOperationID(),
+                               proxyUserConn.nextMessageID(), controls,
                                DN.decode("uid=user.test0,ou=People,dc=example,dc=com"),
                                RDN.decode("uid=user.0"), true,
                                null);
@@ -1019,12 +1028,9 @@
     controls.add(authV2Control);
     InvocationCounterPlugin.resetAllCounters();
 
-    InternalClientConnection conn =
-         InternalClientConnection.getRootConnection();
-
     ModifyDNOperation modifyDNOperation =
-         new ModifyDNOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
-                               controls,
+         new ModifyDNOperation(proxyUserConn, proxyUserConn.nextOperationID(),
+                               proxyUserConn.nextMessageID(), controls,
                                DN.decode("uid=user.0,ou=People,dc=example,dc=com"),
                                RDN.decode("uid=user.test0"), false,
                                null);
@@ -1047,12 +1053,9 @@
     controls.add(authV2Control);
     InvocationCounterPlugin.resetAllCounters();
 
-    InternalClientConnection conn =
-         InternalClientConnection.getRootConnection();
-
     ModifyDNOperation modifyDNOperation =
-         new ModifyDNOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
-                               controls,
+         new ModifyDNOperation(proxyUserConn, proxyUserConn.nextOperationID(),
+                               proxyUserConn.nextMessageID(), controls,
                                DN.decode("uid=user.0,ou=People,dc=example,dc=com"),
                                RDN.decode("uid=user.test0"), false,
                                null);
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/DigestMD5SASLMechanismHandlerTestCase.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/DigestMD5SASLMechanismHandlerTestCase.java
index 238624a..2693a70 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/DigestMD5SASLMechanismHandlerTestCase.java
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/DigestMD5SASLMechanismHandlerTestCase.java
@@ -22,7 +22,7 @@
  * CDDL HEADER END
  *
  *
- *      Portions Copyright 2006 Sun Microsystems, Inc.
+ *      Portions Copyright 2006-2007 Sun Microsystems, Inc.
  */
 package org.opends.server.extensions;
 
@@ -727,6 +727,50 @@
 
 
   /**
+   * Performs a failed LDAP bind using DIGEST-MD5 using an empty authorization
+   * ID.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testLDAPBindFailEmptyAuthzID()
+         throws Exception
+  {
+    TestCaseUtils.initializeTestBackend(true);
+    TestCaseUtils.addEntry(
+      "dn: uid=test.user,o=test",
+      "objectClass: top",
+      "objectClass: person",
+      "objectClass: organizationalPerson",
+      "objectClass: inetOrgPerson",
+      "uid: test.user",
+      "givenName: Test",
+      "sn: User",
+      "cn: Test User",
+      "userPassword: password",
+      "ds-privilege-name: proxied-auth",
+      "ds-pwp-password-policy-dn: cn=Clear UserPassword Policy," +
+           "cn=Password Policies,cn=config");
+
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-o", "mech=DIGEST-MD5",
+      "-o", "authid=dn:uid=test.user,o=test",
+      "-o", "authzid=",
+      "-o", "realm=o=test",
+      "-w", "password",
+      "-b", "",
+      "-s", "base",
+      "(objectClass=*)"
+    };
+    assertFalse(LDAPSearch.mainSearch(args, false, null, null) == 0);
+  }
+
+
+
+  /**
    * Performs a failed LDAP bind using DIGEST-MD5 using the dn: form of the
    * authentication ID with the root DN (which has a stored password that's not
    * reversible).
@@ -811,6 +855,138 @@
 
 
   /**
+   * Performs a failed LDAP bind using DIGEST-MD5 using an authorization ID that
+   * contains the DN of an entry that doesn't exist.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testLDAPBindFailNonexistentAuthzDN()
+         throws Exception
+  {
+    TestCaseUtils.initializeTestBackend(true);
+    TestCaseUtils.addEntry(
+      "dn: uid=test.user,o=test",
+      "objectClass: top",
+      "objectClass: person",
+      "objectClass: organizationalPerson",
+      "objectClass: inetOrgPerson",
+      "uid: test.user",
+      "givenName: Test",
+      "sn: User",
+      "cn: Test User",
+      "userPassword: password",
+      "ds-privilege-name: proxied-auth",
+      "ds-pwp-password-policy-dn: cn=Clear UserPassword Policy," +
+           "cn=Password Policies,cn=config");
+
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-o", "mech=DIGEST-MD5",
+      "-o", "authid=dn:uid=test.user,o=test",
+      "-o", "authzid=dn:uid=nonexistent,o=test",
+      "-o", "realm=o=test",
+      "-w", "password",
+      "-b", "",
+      "-s", "base",
+      "(objectClass=*)"
+    };
+    assertFalse(LDAPSearch.mainSearch(args, false, null, null) == 0);
+  }
+
+
+
+  /**
+   * Performs a failed LDAP bind using DIGEST-MD5 using an authorization ID that
+   * contains a username for an entry that doesn't exist.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testLDAPBindFailNonexistentAuthzUsername()
+         throws Exception
+  {
+    TestCaseUtils.initializeTestBackend(true);
+    TestCaseUtils.addEntry(
+      "dn: uid=test.user,o=test",
+      "objectClass: top",
+      "objectClass: person",
+      "objectClass: organizationalPerson",
+      "objectClass: inetOrgPerson",
+      "uid: test.user",
+      "givenName: Test",
+      "sn: User",
+      "cn: Test User",
+      "userPassword: password",
+      "ds-privilege-name: proxied-auth",
+      "ds-pwp-password-policy-dn: cn=Clear UserPassword Policy," +
+           "cn=Password Policies,cn=config");
+
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-o", "mech=DIGEST-MD5",
+      "-o", "authid=dn:uid=test.user,o=test",
+      "-o", "authzid=u:nonexistent",
+      "-o", "realm=o=test",
+      "-w", "password",
+      "-b", "",
+      "-s", "base",
+      "(objectClass=*)"
+    };
+    assertFalse(LDAPSearch.mainSearch(args, false, null, null) == 0);
+  }
+
+
+
+  /**
+   * Performs a failed LDAP bind using DIGEST-MD5 using an authorization ID that
+   * contains a malformed DN.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testLDAPBindFailMalformedAuthzDN()
+         throws Exception
+  {
+    TestCaseUtils.initializeTestBackend(true);
+    TestCaseUtils.addEntry(
+      "dn: uid=test.user,o=test",
+      "objectClass: top",
+      "objectClass: person",
+      "objectClass: organizationalPerson",
+      "objectClass: inetOrgPerson",
+      "uid: test.user",
+      "givenName: Test",
+      "sn: User",
+      "cn: Test User",
+      "userPassword: password",
+      "ds-privilege-name: proxied-auth",
+      "ds-pwp-password-policy-dn: cn=Clear UserPassword Policy," +
+           "cn=Password Policies,cn=config");
+
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-o", "mech=DIGEST-MD5",
+      "-o", "authid=dn:uid=test.user,o=test",
+      "-o", "authzid=dn:malformed",
+      "-o", "realm=o=test",
+      "-w", "password",
+      "-b", "",
+      "-s", "base",
+      "(objectClass=*)"
+    };
+    assertFalse(LDAPSearch.mainSearch(args, false, null, null) == 0);
+  }
+
+
+
+  /**
    * Verifies that the server will reject a DIGEST-MD5 bind in which the first
    * message contains SASL credentials (which isn't allowed).
    *
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/PlainSASLMechanismHandlerTestCase.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/PlainSASLMechanismHandlerTestCase.java
index 0b47b1f..eedbede 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/PlainSASLMechanismHandlerTestCase.java
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/PlainSASLMechanismHandlerTestCase.java
@@ -22,7 +22,7 @@
  * CDDL HEADER END
  *
  *
- *      Portions Copyright 2006 Sun Microsystems, Inc.
+ *      Portions Copyright 2006-2007 Sun Microsystems, Inc.
  */
 package org.opends.server.extensions;
 
@@ -516,5 +516,128 @@
                                        saslCredentials);
     assertEquals(bindOperation.getResultCode(), ResultCode.INVALID_CREDENTIALS);
   }
+
+
+
+  /**
+   * Performs a failed LDAP bind using PLAIN with an authorization ID that
+   * contains the DN of an entry that doesn't exist.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testLDAPBindFailNonexistentAuthzDN()
+         throws Exception
+  {
+    TestCaseUtils.initializeTestBackend(true);
+    TestCaseUtils.addEntry(
+      "dn: uid=test.user,o=test",
+      "objectClass: top",
+      "objectClass: person",
+      "objectClass: organizationalPerson",
+      "objectClass: inetOrgPerson",
+      "uid: test.user",
+      "givenName: Test",
+      "sn: User",
+      "cn: Test User",
+      "userPassword: password",
+      "ds-privilege-name: proxied-auth");
+
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-o", "mech=PLAIN",
+      "-o", "authid=dn:uid=test.user,o=test",
+      "-o", "authzid=dn:uid=nonexistent,o=test",
+      "-w", "password",
+      "-b", "",
+      "-s", "base",
+      "(objectClass=*)"
+    };
+    assertFalse(LDAPSearch.mainSearch(args, false, null, null) == 0);
+  }
+
+
+
+  /**
+   * Performs a failed LDAP bind using PLAIN with an authorization ID that
+   * contains a username for an entry that doesn't exist.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testLDAPBindFailNonexistentAuthzUsername()
+         throws Exception
+  {
+    TestCaseUtils.initializeTestBackend(true);
+    TestCaseUtils.addEntry(
+      "dn: uid=test.user,o=test",
+      "objectClass: top",
+      "objectClass: person",
+      "objectClass: organizationalPerson",
+      "objectClass: inetOrgPerson",
+      "uid: test.user",
+      "givenName: Test",
+      "sn: User",
+      "cn: Test User",
+      "userPassword: password",
+      "ds-privilege-name: proxied-auth");
+
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-o", "mech=PLAIN",
+      "-o", "authid=dn:uid=test.user,o=test",
+      "-o", "authzid=u:nonexistent",
+      "-w", "password",
+      "-b", "",
+      "-s", "base",
+      "(objectClass=*)"
+    };
+    assertFalse(LDAPSearch.mainSearch(args, false, null, null) == 0);
+  }
+
+
+
+  /**
+   * Performs a failed LDAP bind using PLAIN with an authorization ID that
+   * contains a malformed DN.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testLDAPBindFailMalformedAuthzDN()
+         throws Exception
+  {
+    TestCaseUtils.initializeTestBackend(true);
+    TestCaseUtils.addEntry(
+      "dn: uid=test.user,o=test",
+      "objectClass: top",
+      "objectClass: person",
+      "objectClass: organizationalPerson",
+      "objectClass: inetOrgPerson",
+      "uid: test.user",
+      "givenName: Test",
+      "sn: User",
+      "cn: Test User",
+      "userPassword: password",
+      "ds-privilege-name: proxied-auth");
+
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-o", "mech=PLAIN",
+      "-o", "authid=dn:uid=test.user,o=test",
+      "-o", "authzid=dn:malformed",
+      "-w", "password",
+      "-b", "",
+      "-s", "base",
+      "(objectClass=*)"
+    };
+    assertFalse(LDAPSearch.mainSearch(args, false, null, null) == 0);
+  }
 }
 
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/types/PrivilegeTestCase.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/types/PrivilegeTestCase.java
index 2fbf660..6f89632 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/types/PrivilegeTestCase.java
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/types/PrivilegeTestCase.java
@@ -43,6 +43,8 @@
 import org.opends.server.backends.task.Task;
 import org.opends.server.backends.task.TaskBackend;
 import org.opends.server.backends.task.TaskState;
+import org.opends.server.controls.ProxiedAuthV1Control;
+import org.opends.server.controls.ProxiedAuthV2Control;
 import org.opends.server.core.AddOperation;
 import org.opends.server.core.CompareOperation;
 import org.opends.server.core.DeleteOperation;
@@ -50,10 +52,12 @@
 import org.opends.server.core.ModifyOperation;
 import org.opends.server.core.ModifyDNOperation;
 import org.opends.server.core.SchemaConfigManager;
+import org.opends.server.protocols.asn1.ASN1OctetString;
 import org.opends.server.protocols.internal.InternalClientConnection;
 import org.opends.server.protocols.internal.InternalSearchOperation;
 import org.opends.server.tools.LDAPModify;
 import org.opends.server.tools.LDAPPasswordModify;
+import org.opends.server.tools.LDAPSearch;
 import org.opends.server.types.Attribute;
 import org.opends.server.types.AuthenticationInfo;
 import org.opends.server.types.DN;
@@ -120,6 +124,7 @@
       "cn: Unprivileged Root",
       "givenName: Unprivileged",
       "sn: Root",
+      "uid: unprivileged.root",
       "userPassword: password",
       "ds-privilege-name: -config-read",
       "ds-privilege-name: -config-write",
@@ -130,6 +135,19 @@
       "ds-privilege-name: -backend-backup",
       "ds-privilege-name: -backend-restore",
       "",
+      "dn: cn=Proxy Root,cn=Root DNs,cn=config",
+      "objectClass: top",
+      "objectClass: person",
+      "objectClass: organizationalPerson",
+      "objectClass: inetOrgPerson",
+      "objectClass: ds-cfg-root-dn",
+      "cn: Proxy Root",
+      "givenName: Proxy",
+      "sn: Root",
+      "uid: proxy.root",
+      "userPassword: password",
+      "ds-privilege-name: proxied-auth",
+      "",
       "dn: cn=Privileged User,o=test",
       "objectClass: top",
       "objectClass: person",
@@ -138,6 +156,7 @@
       "cn: Privileged User",
       "givenName: Privileged",
       "sn: User",
+      "uid: privileged.user",
       "userPassword: password",
       "ds-privilege-name: config-read",
       "ds-privilege-name: config-write",
@@ -147,6 +166,9 @@
       "ds-privilege-name: ldif-export",
       "ds-privilege-name: backend-backup",
       "ds-privilege-name: backend-restore",
+      "ds-privilege-name: proxied-auth",
+      "ds-pwp-password-policy-dn: cn=Clear UserPassword Policy," +
+           "cn=Password Policies,cn=config",
       "",
       "dn: cn=Unprivileged User,o=test",
       "objectClass: top",
@@ -156,7 +178,10 @@
       "cn: Unprivileged User",
       "givenName: Unprivileged",
       "sn: User",
+      "uid: unprivileged.user",
       "userPassword: password",
+      "ds-pwp-password-policy-dn: cn=Clear UserPassword Policy," +
+           "cn=Password Policies,cn=config",
       "",
       "dn: cn=PWReset Target,o=test",
       "objectClass: top",
@@ -166,6 +191,7 @@
       "cn: PWReset Target",
       "givenName: PWReset",
       "sn: Target",
+      "uid: pwreset.target",
       "userPassword: password");
 
 // FIXME -- It will likely be necessary to also have access control rules in
@@ -196,6 +222,12 @@
     connList.add(new InternalClientConnection(authInfo));
     successList.add(false);
 
+    userDN    = "cn=Proxy Root,cn=Root DNs,cn=config";
+    userEntry = DirectoryServer.getEntry(DN.decode(userDN));
+    authInfo  = new AuthenticationInfo(userEntry, true);
+    connList.add(new InternalClientConnection(authInfo));
+    successList.add(true);
+
     userDN    = "cn=Unprivileged User,o=test";
     userEntry = DirectoryServer.getEntry(DN.decode(userDN));
     authInfo  = new AuthenticationInfo(userEntry, false);
@@ -985,6 +1017,1129 @@
 
 
   /**
+   * Tests to ensure that the use of the Directory Server will properly respect
+   * the PROXIED_AUTH privilege for add, delete, modify and modify DN requests
+   * that contain the proxied auth v1 control.
+   *
+   * @param  conn          The client connection to use to perform the
+   *                       operation.
+   * @param  hasPrivilege  Indicates whether the authenticated user is expected
+   *                       to have the PROXIED_AUTH privilege and therefore
+   *                       the operation should succeed.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test(dataProvider = "testdata")
+  public void testProxyAuthV1Write(InternalClientConnection conn,
+                                   boolean hasPrivilege)
+         throws Exception
+  {
+    // We can't trust the value of hasPrivilege because root users don't get
+    // proxy privileges by default.  So make the determination based on the
+    // privileges the user actually has.
+    boolean hasProxyPrivilege = conn.hasPrivilege(Privilege.PROXIED_AUTH, null);
+
+    Entry e = TestCaseUtils.makeEntry(
+      "dn: cn=ProxyV1 Test,o=test",
+      "objectClass: top",
+      "objectClass: person",
+      "objectClass: organizationalPerson",
+      "objectClass: inetOrgPerson",
+      "cn: ProxyV1 Test",
+      "givenName: ProxyV1",
+      "sn: Test");
+
+    ArrayList<Control> controls = new ArrayList<Control>(1);
+    controls.add(new ProxiedAuthV1Control(
+                          DN.decode("cn=PWReset Target,o=test")));
+
+
+    // Try to add the entry.  If this fails with the proxy control, then add it
+    // with a root connection so we can do other things with it.
+    AddOperation addOperation =
+         new AddOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+                          controls, e.getDN(), e.getObjectClasses(),
+                          e.getUserAttributes(), e.getOperationalAttributes());
+    addOperation.run();
+
+    if (hasProxyPrivilege)
+    {
+      assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS);
+    }
+    else
+    {
+      assertEquals(addOperation.getResultCode(),
+                   ResultCode.AUTHORIZATION_DENIED);
+      TestCaseUtils.addEntry(e);
+    }
+
+
+    // Try to modify the entry to add a description.
+    ArrayList<Modification> mods = new ArrayList<Modification>(1);
+    mods.add(new Modification(ModificationType.REPLACE,
+                              new Attribute("description", "foo")));
+
+    ModifyOperation modifyOperation =
+         new ModifyOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+                             controls, e.getDN(), mods);
+    modifyOperation.run();
+
+    if (hasProxyPrivilege)
+    {
+      assertEquals(modifyOperation.getResultCode(), ResultCode.SUCCESS);
+    }
+    else
+    {
+      assertEquals(modifyOperation.getResultCode(),
+                   ResultCode.AUTHORIZATION_DENIED);
+    }
+
+
+    // Try to rename the entry.
+    ModifyDNOperation modifyDNOperation =
+         new ModifyDNOperation(conn, conn.nextOperationID(),
+                               conn.nextMessageID(), controls, e.getDN(),
+                               RDN.decode("cn=Proxy V1 Test"), true, null);
+    modifyDNOperation.run();
+
+    DN newEntryDN;
+    if (hasProxyPrivilege)
+    {
+      assertEquals(modifyDNOperation.getResultCode(), ResultCode.SUCCESS);
+      newEntryDN = modifyDNOperation.getUpdatedEntry().getDN();
+    }
+    else
+    {
+      assertEquals(modifyDNOperation.getResultCode(),
+                   ResultCode.AUTHORIZATION_DENIED);
+      newEntryDN = e.getDN();
+    }
+
+
+    // Try to delete the operation.  If this fails, then delete it with a root
+    // connection so it gets cleaned up.
+    DeleteOperation deleteOperation =
+         new DeleteOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+                             controls, newEntryDN);
+    deleteOperation.run();
+
+    if (hasProxyPrivilege)
+    {
+      assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS);
+    }
+    else
+    {
+      assertEquals(deleteOperation.getResultCode(),
+                   ResultCode.AUTHORIZATION_DENIED);
+
+      InternalClientConnection rootConnection =
+           InternalClientConnection.getRootConnection();
+      deleteOperation = rootConnection.processDelete(newEntryDN);
+      assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS);
+    }
+  }
+
+
+
+  /**
+   * Tests to ensure that the use of the Directory Server will properly respect
+   * the PROXIED_AUTH privilege for search and compare requests that contain the
+   * proxied auth v1 control.
+   *
+   * @param  conn          The client connection to use to perform the
+   *                       operation.
+   * @param  hasPrivilege  Indicates whether the authenticated user is expected
+   *                       to have the PROXIED_AUTH privilege and therefore
+   *                       the operation should succeed.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test(dataProvider = "testdata")
+  public void testProxyAuthV1Read(InternalClientConnection conn,
+                                  boolean hasPrivilege)
+         throws Exception
+  {
+    // We can't trust the value of hasPrivilege because root users don't get
+    // proxy privileges by default.  So make the determination based on the
+    // privileges the user actually has.
+    boolean hasProxyPrivilege = conn.hasPrivilege(Privilege.PROXIED_AUTH, null);
+
+    DN targetDN = DN.decode("cn=PWReset Target,o=test");
+    ArrayList<Control> controls = new ArrayList<Control>(1);
+    controls.add(new ProxiedAuthV1Control(targetDN));
+
+
+    // Test a compare operation against the PWReset Target user.
+    CompareOperation compareOperation =
+         new CompareOperation(conn, conn.nextOperationID(),
+                              conn.nextMessageID(), controls, targetDN,
+                              DirectoryServer.getAttributeType("cn", true),
+                              ByteStringFactory.create("PWReset Target"));
+    compareOperation.run();
+
+    if (hasProxyPrivilege)
+    {
+      assertEquals(compareOperation.getResultCode(), ResultCode.COMPARE_TRUE);
+    }
+    else
+    {
+      assertEquals(compareOperation.getResultCode(),
+                   ResultCode.AUTHORIZATION_DENIED);
+    }
+
+
+    // Test a search operation against the PWReset Target user.
+    InternalSearchOperation searchOperation =
+         new InternalSearchOperation(conn, conn.nextOperationID(),
+                  conn.nextMessageID(), controls, targetDN,
+                  SearchScope.BASE_OBJECT,
+                  DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
+                  SearchFilter.createFilterFromString("(objectClass=*)"), null,
+                  null);
+    searchOperation.run();
+
+    if (hasProxyPrivilege)
+    {
+      assertEquals(searchOperation.getResultCode(), ResultCode.SUCCESS);
+    }
+    else
+    {
+      assertEquals(searchOperation.getResultCode(),
+                   ResultCode.AUTHORIZATION_DENIED);
+    }
+  }
+
+
+
+  /**
+   * Tests to ensure that the use of the Directory Server will properly respect
+   * the PROXIED_AUTH privilege for add, delete, modify and modify DN requests
+   * that contain the proxied auth v2 control.
+   *
+   * @param  conn          The client connection to use to perform the
+   *                       operation.
+   * @param  hasPrivilege  Indicates whether the authenticated user is expected
+   *                       to have the PROXIED_AUTH privilege and therefore
+   *                       the operation should succeed.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test(dataProvider = "testdata")
+  public void testProxyAuthV2Write(InternalClientConnection conn,
+                                   boolean hasPrivilege)
+         throws Exception
+  {
+    // We can't trust the value of hasPrivilege because root users don't get
+    // proxy privileges by default.  So make the determination based on the
+    // privileges the user actually has.
+    boolean hasProxyPrivilege = conn.hasPrivilege(Privilege.PROXIED_AUTH, null);
+
+    Entry e = TestCaseUtils.makeEntry(
+      "dn: cn=ProxyV2 Test,o=test",
+      "objectClass: top",
+      "objectClass: person",
+      "objectClass: organizationalPerson",
+      "objectClass: inetOrgPerson",
+      "cn: ProxyV2 Test",
+      "givenName: ProxyV2",
+      "sn: Test");
+
+    ArrayList<Control> controls = new ArrayList<Control>(1);
+    controls.add(new ProxiedAuthV2Control(
+                          new ASN1OctetString("dn:cn=PWReset Target,o=test")));
+
+
+    // Try to add the entry.  If this fails with the proxy control, then add it
+    // with a root connection so we can do other things with it.
+    AddOperation addOperation =
+         new AddOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+                          controls, e.getDN(), e.getObjectClasses(),
+                          e.getUserAttributes(), e.getOperationalAttributes());
+    addOperation.run();
+
+    if (hasProxyPrivilege)
+    {
+      assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS);
+    }
+    else
+    {
+      assertEquals(addOperation.getResultCode(),
+                   ResultCode.AUTHORIZATION_DENIED);
+      TestCaseUtils.addEntry(e);
+    }
+
+
+    // Try to modify the entry to add a description.
+    ArrayList<Modification> mods = new ArrayList<Modification>(1);
+    mods.add(new Modification(ModificationType.REPLACE,
+                              new Attribute("description", "foo")));
+
+    ModifyOperation modifyOperation =
+         new ModifyOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+                             controls, e.getDN(), mods);
+    modifyOperation.run();
+
+    if (hasProxyPrivilege)
+    {
+      assertEquals(modifyOperation.getResultCode(), ResultCode.SUCCESS);
+    }
+    else
+    {
+      assertEquals(modifyOperation.getResultCode(),
+                   ResultCode.AUTHORIZATION_DENIED);
+    }
+
+
+    // Try to rename the entry.
+    ModifyDNOperation modifyDNOperation =
+         new ModifyDNOperation(conn, conn.nextOperationID(),
+                               conn.nextMessageID(), controls, e.getDN(),
+                               RDN.decode("cn=Proxy V2 Test"), true, null);
+    modifyDNOperation.run();
+
+    DN newEntryDN;
+    if (hasProxyPrivilege)
+    {
+      assertEquals(modifyDNOperation.getResultCode(), ResultCode.SUCCESS);
+      newEntryDN = modifyDNOperation.getUpdatedEntry().getDN();
+    }
+    else
+    {
+      assertEquals(modifyDNOperation.getResultCode(),
+                   ResultCode.AUTHORIZATION_DENIED);
+      newEntryDN = e.getDN();
+    }
+
+
+    // Try to delete the operation.  If this fails, then delete it with a root
+    // connection so it gets cleaned up.
+    DeleteOperation deleteOperation =
+         new DeleteOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+                             controls, newEntryDN);
+    deleteOperation.run();
+
+    if (hasProxyPrivilege)
+    {
+      assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS);
+    }
+    else
+    {
+      assertEquals(deleteOperation.getResultCode(),
+                   ResultCode.AUTHORIZATION_DENIED);
+
+      InternalClientConnection rootConnection =
+           InternalClientConnection.getRootConnection();
+      deleteOperation = rootConnection.processDelete(newEntryDN);
+      assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS);
+    }
+  }
+
+
+
+  /**
+   * Tests to ensure that the use of the Directory Server will properly respect
+   * the PROXIED_AUTH privilege for search and compare requests that contain the
+   * proxied auth v2 control.
+   *
+   * @param  conn          The client connection to use to perform the
+   *                       operation.
+   * @param  hasPrivilege  Indicates whether the authenticated user is expected
+   *                       to have the PROXIED_AUTH privilege and therefore
+   *                       the operation should succeed.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test(dataProvider = "testdata")
+  public void testProxyAuthV2Read(InternalClientConnection conn,
+                                  boolean hasPrivilege)
+         throws Exception
+  {
+    // We can't trust the value of hasPrivilege because root users don't get
+    // proxy privileges by default.  So make the determination based on the
+    // privileges the user actually has.
+    boolean hasProxyPrivilege = conn.hasPrivilege(Privilege.PROXIED_AUTH, null);
+
+    DN targetDN = DN.decode("cn=PWReset Target,o=test");
+    ArrayList<Control> controls = new ArrayList<Control>(1);
+    controls.add(new ProxiedAuthV2Control(
+                          new ASN1OctetString("dn:" + targetDN.toString())));
+
+
+    // Test a compare operation against the PWReset Target user.
+    CompareOperation compareOperation =
+         new CompareOperation(conn, conn.nextOperationID(),
+                              conn.nextMessageID(), controls, targetDN,
+                              DirectoryServer.getAttributeType("cn", true),
+                              ByteStringFactory.create("PWReset Target"));
+    compareOperation.run();
+
+    if (hasProxyPrivilege)
+    {
+      assertEquals(compareOperation.getResultCode(), ResultCode.COMPARE_TRUE);
+    }
+    else
+    {
+      assertEquals(compareOperation.getResultCode(),
+                   ResultCode.AUTHORIZATION_DENIED);
+    }
+
+
+    // Test a search operation against the PWReset Target user.
+    InternalSearchOperation searchOperation =
+         new InternalSearchOperation(conn, conn.nextOperationID(),
+                  conn.nextMessageID(), controls, targetDN,
+                  SearchScope.BASE_OBJECT,
+                  DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
+                  SearchFilter.createFilterFromString("(objectClass=*)"), null,
+                  null);
+    searchOperation.run();
+
+    if (hasProxyPrivilege)
+    {
+      assertEquals(searchOperation.getResultCode(), ResultCode.SUCCESS);
+    }
+    else
+    {
+      assertEquals(searchOperation.getResultCode(),
+                   ResultCode.AUTHORIZATION_DENIED);
+    }
+  }
+
+
+
+  /**
+   * Tests to ensure that the server will properly respect the PROXIED_AUTH
+   * privilege when attempting to perform DIGEST-MD5 authentication when an
+   * anonymous authorization ID is specified with an authentication identity
+   * that has sufficient privileges and using the "dn:" syntax.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testDIGESTMD5AnonymousAuthzIDSuccessfulDNColon()
+         throws Exception
+  {
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-o", "mech=DIGEST-MD5",
+      "-o", "authid=dn:cn=Privileged User,o=test",
+      "-o", "authzid=dn:",
+      "-o", "realm=o=test",
+      "-w", "password",
+      "-b", "o=test",
+      "-s", "base",
+      "(objectClass=*)"
+    };
+
+    assertEquals(LDAPSearch.mainSearch(args, false, null, System.err), 0);
+  }
+
+
+
+  /**
+   * Tests to ensure that the server will behave properly when attempting to
+   * perform DIGEST-MD5 authentication when an authorization ID equaling the
+   * authentication ID is specified with an authentication identity that has
+   * sufficient privileges and using the "dn:" syntax.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testDIGESTMD5SameAuthzIDSuccessfulDNColon()
+         throws Exception
+  {
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-o", "mech=DIGEST-MD5",
+      "-o", "authid=dn:cn=Privileged User,o=test",
+      "-o", "authzid=dn:cn=Privileged User,o=test",
+      "-o", "realm=o=test",
+      "-w", "password",
+      "-b", "o=test",
+      "-s", "base",
+      "(objectClass=*)"
+    };
+
+    assertEquals(LDAPSearch.mainSearch(args, false, null, System.err), 0);
+  }
+
+
+
+  /**
+   * Tests to ensure that the server will properly respect the PROXIED_AUTH
+   * privilege when attempting to perform DIGEST-MD5 authentication when an
+   * alternate authorization ID is specified with an authentication identity
+   * that has sufficient privileges and using the "dn:" syntax.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testDIGESTMD5DifferentAuthzIDSuccessfulDNColon()
+         throws Exception
+  {
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-o", "mech=DIGEST-MD5",
+      "-o", "authid=dn:cn=Privileged User,o=test",
+      "-o", "authzid=dn:cn=Unprivileged User,o=test",
+      "-o", "realm=o=test",
+      "-w", "password",
+      "-b", "o=test",
+      "-s", "base",
+      "(objectClass=*)"
+    };
+
+    assertEquals(LDAPSearch.mainSearch(args, false, null, System.err), 0);
+  }
+
+
+
+  /**
+   * Tests to ensure that the server will properly respect the PROXIED_AUTH
+   * privilege when attempting to perform DIGEST-MD5 authentication when an
+   * anonymous authorization ID is specified with an authentication identity
+   * that has sufficient privileges and using the "u:" syntax.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testDIGESTMD5AnonymousAuthzIDSuccessfulUColon()
+         throws Exception
+  {
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-o", "mech=DIGEST-MD5",
+      "-o", "authid=u:privileged.user",
+      "-o", "authzid=u:",
+      "-o", "realm=o=test",
+      "-w", "password",
+      "-b", "o=test",
+      "-s", "base",
+      "(objectClass=*)"
+    };
+
+    assertEquals(LDAPSearch.mainSearch(args, false, null, System.err), 0);
+  }
+
+
+
+  /**
+   * Tests to ensure that the server will behave properly when attempting to
+   * perform DIGEST-MD5 authentication when an authorization ID equaling the
+   * authentication ID is specified with an authentication identity that has
+   * sufficient privileges and using the "u:" syntax.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testDIGESTMD5SameAuthzIDSuccessfulUColon()
+         throws Exception
+  {
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-o", "mech=DIGEST-MD5",
+      "-o", "authid=u:privileged.user",
+      "-o", "authzid=u:privileged.user",
+      "-o", "realm=o=test",
+      "-w", "password",
+      "-b", "o=test",
+      "-s", "base",
+      "(objectClass=*)"
+    };
+
+    assertEquals(LDAPSearch.mainSearch(args, false, null, System.err), 0);
+  }
+
+
+
+  /**
+   * Tests to ensure that the server will properly respect the PROXIED_AUTH
+   * privilege when attempting to perform DIGEST-MD5 authentication when an
+   * alternate authorization ID is specified with an authentication identity
+   * that has sufficient privileges and using the "u:" syntax.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testDIGESTMD5DifferentAuthzIDSuccessfulUColon()
+         throws Exception
+  {
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-o", "mech=DIGEST-MD5",
+      "-o", "authid=u:privileged.user",
+      "-o", "authzid=u:unprivileged.user",
+      "-o", "realm=o=test",
+      "-w", "password",
+      "-b", "o=test",
+      "-s", "base",
+      "(objectClass=*)"
+    };
+
+    assertEquals(LDAPSearch.mainSearch(args, false, null, System.err), 0);
+  }
+
+
+
+  /**
+   * Tests to ensure that the server will properly respect the PROXIED_AUTH
+   * privilege when attempting to perform DIGEST-MD5 authentication when an
+   * anonymous authorization ID is specified with an authentication identity
+   * that does not have sufficient privileges and using the "dn:" syntax.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testDIGESTMD5AnonymousAuthzIDFailedDNColon()
+         throws Exception
+  {
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-o", "mech=DIGEST-MD5",
+      "-o", "authid=dn:cn=Unprivileged User,o=test",
+      "-o", "authzid=dn:",
+      "-o", "realm=o=test",
+      "-w", "password",
+      "-b", "o=test",
+      "-s", "base",
+      "(objectClass=*)"
+    };
+
+    assertFalse(LDAPSearch.mainSearch(args, false, null, null) == 0);
+  }
+
+
+
+  /**
+   * Tests to ensure that the server will behave properly when attempting to
+   * perform DIGEST-MD5 authentication when an authorization ID equaling the
+   * authentication ID is specified with an authentication identity that does
+   * not have sufficient privileges and using the "dn:" syntax.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testDIGESTMD5SameUnprivAuthzIDSuccessfulDNColon()
+         throws Exception
+  {
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-o", "mech=DIGEST-MD5",
+      "-o", "authid=dn:cn=Unprivileged User,o=test",
+      "-o", "authzid=dn:cn=Unprivileged User,o=test",
+      "-o", "realm=o=test",
+      "-w", "password",
+      "-b", "o=test",
+      "-s", "base",
+      "(objectClass=*)"
+    };
+
+    assertEquals(LDAPSearch.mainSearch(args, false, null, System.err), 0);
+  }
+
+
+
+  /**
+   * Tests to ensure that the server will properly respect the PROXIED_AUTH
+   * privilege when attempting to perform DIGEST-MD5 authentication when an
+   * alternate authorization ID is specified with an authentication identity
+   * that does not have sufficient privileges and using the "dn:" syntax.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testDIGESTMD5DifferentAuthzIDFailedDNColon()
+         throws Exception
+  {
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-o", "mech=DIGEST-MD5",
+      "-o", "authid=dn:cn=Unprivileged User,o=test",
+      "-o", "authzid=dn:cn=Privileged User,o=test",
+      "-o", "realm=o=test",
+      "-w", "password",
+      "-b", "o=test",
+      "-s", "base",
+      "(objectClass=*)"
+    };
+
+    assertFalse(LDAPSearch.mainSearch(args, false, null, null) == 0);
+  }
+
+
+
+  /**
+   * Tests to ensure that the server will properly respect the PROXIED_AUTH
+   * privilege when attempting to perform DIGEST-MD5 authentication when an
+   * anonymous authorization ID is specified with an authentication identity
+   * that does not have sufficient privileges and using the "u:" syntax.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testDIGESTMD5AnonymousAuthzIDFailedUColon()
+         throws Exception
+  {
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-o", "mech=DIGEST-MD5",
+      "-o", "authid=u:unprivileged.user",
+      "-o", "authzid=u:",
+      "-o", "realm=o=test",
+      "-w", "password",
+      "-b", "o=test",
+      "-s", "base",
+      "(objectClass=*)"
+    };
+
+    assertFalse(LDAPSearch.mainSearch(args, false, null, null) == 0);
+  }
+
+
+
+  /**
+   * Tests to ensure that the server will behave properly when attempting to
+   * perform DIGEST-MD5 authentication when an authorization ID equaling the
+   * authentication ID is specified with an authentication identity that does
+   * not have sufficient privileges and using the "u:" syntax.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testDIGESTMD5SameUnprivAuthzIDSuccessfulUColon()
+         throws Exception
+  {
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-o", "mech=DIGEST-MD5",
+      "-o", "authid=u:unprivileged.user",
+      "-o", "authzid=u:unprivileged.user",
+      "-o", "realm=o=test",
+      "-w", "password",
+      "-b", "o=test",
+      "-s", "base",
+      "(objectClass=*)"
+    };
+
+    assertEquals(LDAPSearch.mainSearch(args, false, null, System.err), 0);
+  }
+
+
+
+  /**
+   * Tests to ensure that the server will properly respect the PROXIED_AUTH
+   * privilege when attempting to perform DIGEST-MD5 authentication when an
+   * alternate authorization ID is specified with an authentication identity
+   * that does not have sufficient privileges and using the "u:" syntax.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testDIGESTMD5DifferentAuthzIDFailedUColon()
+         throws Exception
+  {
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-o", "mech=DIGEST-MD5",
+      "-o", "authid=u:unprivileged.user",
+      "-o", "authzid=u:privileged.user",
+      "-o", "realm=o=test",
+      "-w", "password",
+      "-b", "o=test",
+      "-s", "base",
+      "(objectClass=*)"
+    };
+
+    assertFalse(LDAPSearch.mainSearch(args, false, null, null) == 0);
+  }
+
+
+
+  /**
+   * Tests to ensure that the server will properly respect the PROXIED_AUTH
+   * privilege when attempting to perform PLAIN authentication when an
+   * anonymous authorization ID is specified with an authentication identity
+   * that has sufficient privileges and using the "dn:" syntax.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testPLAINAnonymousAuthzIDSuccessfulDNColon()
+         throws Exception
+  {
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-o", "mech=PLAIN",
+      "-o", "authid=dn:cn=Privileged User,o=test",
+      "-o", "authzid=dn:",
+      "-w", "password",
+      "-b", "o=test",
+      "-s", "base",
+      "(objectClass=*)"
+    };
+
+    assertEquals(LDAPSearch.mainSearch(args, false, null, System.err), 0);
+  }
+
+
+
+  /**
+   * Tests to ensure that the server will behave properly when attempting to
+   * perform PLAIN authentication when an authorization ID equaling the
+   * authentication ID is specified with an authentication identity that has
+   * sufficient privileges and using the "dn:" syntax.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testPLAINSameAuthzIDSuccessfulDNColon()
+         throws Exception
+  {
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-o", "mech=PLAIN",
+      "-o", "authid=dn:cn=Privileged User,o=test",
+      "-o", "authzid=dn:cn=Privileged User,o=test",
+      "-w", "password",
+      "-b", "o=test",
+      "-s", "base",
+      "(objectClass=*)"
+    };
+
+    assertEquals(LDAPSearch.mainSearch(args, false, null, System.err), 0);
+  }
+
+
+
+  /**
+   * Tests to ensure that the server will properly respect the PROXIED_AUTH
+   * privilege when attempting to perform PLAIN authentication when an
+   * alternate authorization ID is specified with an authentication identity
+   * that has sufficient privileges and using the "dn:" syntax.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testPLAINDifferentAuthzIDSuccessfulDNColon()
+         throws Exception
+  {
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-o", "mech=PLAIN",
+      "-o", "authid=dn:cn=Privileged User,o=test",
+      "-o", "authzid=dn:cn=Unprivileged User,o=test",
+      "-w", "password",
+      "-b", "o=test",
+      "-s", "base",
+      "(objectClass=*)"
+    };
+
+    assertEquals(LDAPSearch.mainSearch(args, false, null, System.err), 0);
+  }
+
+
+
+  /**
+   * Tests to ensure that the server will properly respect the PROXIED_AUTH
+   * privilege when attempting to perform PLAIN authentication when an
+   * anonymous authorization ID is specified with an authentication identity
+   * that has sufficient privileges and using the "u:" syntax.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testPLAINAnonymousAuthzIDSuccessfulUColon()
+         throws Exception
+  {
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-o", "mech=PLAIN",
+      "-o", "authid=u:privileged.user",
+      "-o", "authzid=u:",
+      "-w", "password",
+      "-b", "o=test",
+      "-s", "base",
+      "(objectClass=*)"
+    };
+
+    assertEquals(LDAPSearch.mainSearch(args, false, null, System.err), 0);
+  }
+
+
+
+  /**
+   * Tests to ensure that the server will behave properly when attempting to
+   * perform PLAIN authentication when an authorization ID equaling the
+   * authentication ID is specified with an authentication identity that has
+   * sufficient privileges and using the "u:" syntax.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testPLAINSameAuthzIDSuccessfulUColon()
+         throws Exception
+  {
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-o", "mech=PLAIN",
+      "-o", "authid=u:privileged.user",
+      "-o", "authzid=u:privileged.user",
+      "-w", "password",
+      "-b", "o=test",
+      "-s", "base",
+      "(objectClass=*)"
+    };
+
+    assertEquals(LDAPSearch.mainSearch(args, false, null, System.err), 0);
+  }
+
+
+
+  /**
+   * Tests to ensure that the server will properly respect the PROXIED_AUTH
+   * privilege when attempting to perform PLAIN authentication when an
+   * alternate authorization ID is specified with an authentication identity
+   * that has sufficient privileges and using the "u:" syntax.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testPLAINDifferentAuthzIDSuccessfulUColon()
+         throws Exception
+  {
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-o", "mech=PLAIN",
+      "-o", "authid=u:privileged.user",
+      "-o", "authzid=u:unprivileged.user",
+      "-w", "password",
+      "-b", "o=test",
+      "-s", "base",
+      "(objectClass=*)"
+    };
+
+    assertEquals(LDAPSearch.mainSearch(args, false, null, System.err), 0);
+  }
+
+
+
+  /**
+   * Tests to ensure that the server will properly respect the PROXIED_AUTH
+   * privilege when attempting to perform PLAIN authentication when an
+   * anonymous authorization ID is specified with an authentication identity
+   * that does not have sufficient privileges and using the "dn:" syntax.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testPLAINAnonymousAuthzIDFailedDNColon()
+         throws Exception
+  {
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-o", "mech=PLAIN",
+      "-o", "authid=dn:cn=Unprivileged User,o=test",
+      "-o", "authzid=dn:",
+      "-w", "password",
+      "-b", "o=test",
+      "-s", "base",
+      "(objectClass=*)"
+    };
+
+    assertFalse(LDAPSearch.mainSearch(args, false, null, null) == 0);
+  }
+
+
+
+  /**
+   * Tests to ensure that the server will behave properly when attempting to
+   * perform PLAIN authentication when an authorization ID equaling the
+   * authentication ID is specified with an authentication identity that does
+   * not have sufficient privileges and using the "dn:" syntax.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testPLAINSameUnprivAuthzIDSuccessfulDNColon()
+         throws Exception
+  {
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-o", "mech=PLAIN",
+      "-o", "authid=dn:cn=Unprivileged User,o=test",
+      "-o", "authzid=dn:cn=Unprivileged User,o=test",
+      "-w", "password",
+      "-b", "o=test",
+      "-s", "base",
+      "(objectClass=*)"
+    };
+
+    assertEquals(LDAPSearch.mainSearch(args, false, null, System.err), 0);
+  }
+
+
+
+  /**
+   * Tests to ensure that the server will properly respect the PROXIED_AUTH
+   * privilege when attempting to perform PLAIN authentication when an
+   * alternate authorization ID is specified with an authentication identity
+   * that does not have sufficient privileges and using the "dn:" syntax.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testPLAINDifferentAuthzIDFailedDNColon()
+         throws Exception
+  {
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-o", "mech=PLAIN",
+      "-o", "authid=dn:cn=Unprivileged User,o=test",
+      "-o", "authzid=dn:cn=Privileged User,o=test",
+      "-w", "password",
+      "-b", "o=test",
+      "-s", "base",
+      "(objectClass=*)"
+    };
+
+    assertFalse(LDAPSearch.mainSearch(args, false, null, null) == 0);
+  }
+
+
+
+  /**
+   * Tests to ensure that the server will properly respect the PROXIED_AUTH
+   * privilege when attempting to perform PLAIN authentication when an
+   * anonymous authorization ID is specified with an authentication identity
+   * that does not have sufficient privileges and using the "u:" syntax.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testPLAINAnonymousAuthzIDFailedUColon()
+         throws Exception
+  {
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-o", "mech=PLAIN",
+      "-o", "authid=u:unprivileged.user",
+      "-o", "authzid=u:",
+      "-w", "password",
+      "-b", "o=test",
+      "-s", "base",
+      "(objectClass=*)"
+    };
+
+    assertFalse(LDAPSearch.mainSearch(args, false, null, null) == 0);
+  }
+
+
+
+  /**
+   * Tests to ensure that the server will behave properly when attempting to
+   * perform PLAIN authentication when an authorization ID equaling the
+   * authentication ID is specified with an authentication identity that does
+   * not have sufficient privileges and using the "u:" syntax.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testPLAINSameUnprivAuthzIDSuccessfulUColon()
+         throws Exception
+  {
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-o", "mech=PLAIN",
+      "-o", "authid=u:unprivileged.user",
+      "-o", "authzid=u:unprivileged.user",
+      "-w", "password",
+      "-b", "o=test",
+      "-s", "base",
+      "(objectClass=*)"
+    };
+
+    assertEquals(LDAPSearch.mainSearch(args, false, null, System.err), 0);
+  }
+
+
+
+  /**
+   * Tests to ensure that the server will properly respect the PROXIED_AUTH
+   * privilege when attempting to perform PLAIN authentication when an
+   * alternate authorization ID is specified with an authentication identity
+   * that does not have sufficient privileges and using the "u:" syntax.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testPLAINDifferentAuthzIDFailedUColon()
+         throws Exception
+  {
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-o", "mech=PLAIN",
+      "-o", "authid=u:unprivileged.user",
+      "-o", "authzid=u:privileged.user",
+      "-w", "password",
+      "-b", "o=test",
+      "-s", "base",
+      "(objectClass=*)"
+    };
+
+    assertFalse(LDAPSearch.mainSearch(args, false, null, null) == 0);
+  }
+
+
+
+  /**
    * Tests the ability to update the set of privileges for a user on the fly
    * and have them take effect immediately.
    *

--
Gitblit v1.10.0