// Get the user entry for the authentication ID. Allow for an
// authentication ID that is just a username (as per the CRAM-MD5 spec), but
// also allow a value in the authzid form specified in RFC 2829.
Entry userEntry = null;
String lowerUserName = toLowerCase(userName);
if (lowerUserName.startsWith("dn:"))
{
// Try to decode the user DN and retrieve the corresponding entry.
DN userDN;
try
{
userDN = DN.decode(userName.substring(3));
}
catch (DirectoryException de)
{
if (debugEnabled())
{
TRACER.debugCaught(DebugLogLevel.ERROR, de);
}
bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
Message message = ERR_SASLCRAMMD5_CANNOT_DECODE_USERNAME_AS_DN.get(
userName, de.getMessageObject());
bindOperation.setAuthFailureReason(message);
return;
}
if (userDN.isNullDN())
{
bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
Message message = ERR_SASLCRAMMD5_USERNAME_IS_NULL_DN.get();
bindOperation.setAuthFailureReason(message);
return;
}
DN rootDN = DirectoryServer.getActualRootBindDN(userDN);
if (rootDN != null)
{
userDN = rootDN;
}
// Acquire a read lock on the user entry. If this fails, then so will the
// authentication.
Lock readLock = null;
for (int i=0; i < 3; i++)
{
readLock = LockManager.lockRead(userDN);
if (readLock != null)
{
break;
}
}
if (readLock == null)
{
bindOperation.setResultCode(DirectoryServer.getServerErrorResultCode());
Message message = INFO_SASLCRAMMD5_CANNOT_LOCK_ENTRY.get(
String.valueOf(userDN));
bindOperation.setAuthFailureReason(message);
return;
}
try
{
userEntry = DirectoryServer.getEntry(userDN);
}
catch (DirectoryException de)
{
if (debugEnabled())
{
TRACER.debugCaught(DebugLogLevel.ERROR, de);
}
bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
Message message = ERR_SASLCRAMMD5_CANNOT_GET_ENTRY_BY_DN.get(
String.valueOf(userDN), de.getMessageObject());
bindOperation.setAuthFailureReason(message);
return;
}
finally
{
LockManager.unlock(userDN, readLock);
}
}
else
{
// Use the identity mapper to resolve the username to an entry.
if (lowerUserName.startsWith("u:"))
{
userName = userName.substring(2);
}
try
{
userEntry = identityMapper.getEntryForID(userName);
}
catch (DirectoryException de)
{
if (debugEnabled())
{
TRACER.debugCaught(DebugLogLevel.ERROR, de);
}
bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
Message message = ERR_SASLCRAMMD5_CANNOT_MAP_USERNAME.get(
String.valueOf(userName), de.getMessageObject());
bindOperation.setAuthFailureReason(message);
return;
}
}
// At this point, we should have a user entry. If we don't then fail.
if (userEntry == null)
{
bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
Message message = ERR_SASLCRAMMD5_NO_MATCHING_ENTRIES.get(userName);
bindOperation.setAuthFailureReason(message);
return;
}
else
{
bindOperation.setSASLAuthUserEntry(userEntry);
}
// Get the clear-text passwords from the user entry, if there are any.
List<ByteString> clearPasswords;
try
{
PasswordPolicyState pwPolicyState =
new PasswordPolicyState(userEntry, false);
clearPasswords = pwPolicyState.getClearPasswords();
if ((clearPasswords == null) || clearPasswords.isEmpty())
{
bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
Message message = ERR_SASLCRAMMD5_NO_REVERSIBLE_PASSWORDS.get(
String.valueOf(userEntry.getDN()));
bindOperation.setAuthFailureReason(message);
return;
}
}
catch (Exception e)
{
bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
Message message = ERR_SASLCRAMMD5_CANNOT_GET_REVERSIBLE_PASSWORDS.get(
String.valueOf(userEntry.getDN()),
String.valueOf(e));
bindOperation.setAuthFailureReason(message);
return;
}
// Iterate through the clear-text values and see if any of them can be used
// in conjunction with the challenge to construct the provided digest.
boolean matchFound = false;
for (ByteString clearPassword : clearPasswords)
{
byte[] generatedDigest = generateDigest(clearPassword, challenge);
if (Arrays.equals(digestBytes, generatedDigest))
{
matchFound = true;
break;
}
}
if (! matchFound)
{
bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
Message message = ERR_SASLCRAMMD5_INVALID_PASSWORD.get();
bindOperation.setAuthFailureReason(message);
return;
}
// If we've gotten here, then the authentication was successful.
bindOperation.setResultCode(ResultCode.SUCCESS);
AuthenticationInfo authInfo =
new AuthenticationInfo(userEntry, SASL_MECHANISM_CRAM_MD5,
clientCredentials,
DirectoryServer.isRootDN(userEntry.getDN()));
bindOperation.setAuthenticationInfo(authInfo);
return;
}