/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at
* trunk/opends/resource/legal-notices/OpenDS.LICENSE
* or https://OpenDS.dev.java.net/OpenDS.LICENSE.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at
* trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
* add the following below this CDDL HEADER, with the fields enclosed
* by brackets "[]" replaced with your own identifying * information:
* Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*
*
* Portions Copyright 2006 Sun Microsystems, Inc.
*/
package org.opends.server.extensions;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.locks.Lock;
import org.opends.server.api.ConfigurableComponent;
import org.opends.server.api.IdentityMapper;
import org.opends.server.api.PasswordStorageScheme;
import org.opends.server.api.SASLMechanismHandler;
import org.opends.server.config.ConfigAttribute;
import org.opends.server.config.ConfigEntry;
import org.opends.server.config.ConfigException;
import org.opends.server.config.DNConfigAttribute;
import org.opends.server.core.BindOperation;
import org.opends.server.core.DirectoryException;
import org.opends.server.core.DirectoryServer;
import org.opends.server.core.InitializationException;
import org.opends.server.core.LockManager;
import org.opends.server.protocols.asn1.ASN1OctetString;
import org.opends.server.types.Attribute;
import org.opends.server.types.AttributeType;
import org.opends.server.types.AttributeValue;
import org.opends.server.types.AuthenticationInfo;
import org.opends.server.types.ByteString;
import org.opends.server.types.ConfigChangeResult;
import org.opends.server.types.DN;
import org.opends.server.types.Entry;
import org.opends.server.types.ErrorLogCategory;
import org.opends.server.types.ErrorLogSeverity;
import org.opends.server.types.ResultCode;
import static org.opends.server.config.ConfigConstants.*;
import static org.opends.server.extensions.ExtensionsConstants.*;
import static org.opends.server.loggers.Debug.*;
import static org.opends.server.loggers.Error.*;
import static org.opends.server.messages.ExtensionsMessages.*;
import static org.opends.server.messages.MessageHandler.*;
import static org.opends.server.util.ServerConstants.*;
import static org.opends.server.util.StaticUtils.*;
/**
* This class provides an implementation of a SASL mechanism that uses
* plain-text authentication. It is based on the proposal defined in
* draft-ietf-sasl-plain-08 in which the SASL credentials are in the form:
*
*
[authzid] UTF8NULL authcid UTF8NULL passwd*
initializeSASLMechanismHandler method.
*/
public PlainSASLMechanismHandler()
{
super();
assert debugConstructor(CLASS_NAME);
}
/**
* Initializes this SASL mechanism handler based on the information in the
* provided configuration entry. It should also register itself with the
* Directory Server for the particular kinds of SASL mechanisms that it
* will process.
*
* @param configEntry The configuration entry that contains the information
* to use to initialize this SASL mechanism handler.
*
* @throws ConfigException If an unrecoverable problem arises in the
* process of performing the initialization.
*
* @throws InitializationException If a problem occurs during initialization
* that is not related to the server
* configuration.
*/
public void initializeSASLMechanismHandler(ConfigEntry configEntry)
throws ConfigException, InitializationException
{
assert debugEnter(CLASS_NAME, "initializeSASLMechanismHandler",
String.valueOf(configEntry));
this.configEntryDN = configEntry.getDN();
// Get the identity mapper that should be used to find users.
int msgID = MSGID_SASLPLAIN_DESCRIPTION_IDENTITY_MAPPER_DN;
DNConfigAttribute mapperStub =
new DNConfigAttribute(ATTR_IDMAPPER_DN, getMessage(msgID), true, false,
false);
try
{
DNConfigAttribute mapperAttr =
(DNConfigAttribute) configEntry.getConfigAttribute(mapperStub);
if (mapperAttr == null)
{
msgID = MSGID_SASLPLAIN_NO_IDENTITY_MAPPER_ATTR;
String message = getMessage(msgID, String.valueOf(configEntryDN));
throw new ConfigException(msgID, message);
}
else
{
identityMapperDN = mapperAttr.activeValue();
identityMapper = DirectoryServer.getIdentityMapper(identityMapperDN);
if (identityMapper == null)
{
msgID = MSGID_SASLPLAIN_NO_SUCH_IDENTITY_MAPPER;
String message = getMessage(msgID, String.valueOf(identityMapperDN),
String.valueOf(configEntryDN));
throw new ConfigException(msgID, message);
}
}
}
catch (ConfigException ce)
{
throw ce;
}
catch (Exception e)
{
assert debugException(CLASS_NAME, "initializeSASLMechanismHandler", e);
msgID = MSGID_SASLPLAIN_CANNOT_GET_IDENTITY_MAPPER;
String message = getMessage(msgID, String.valueOf(configEntryDN),
stackTraceToSingleLineString(e));
throw new InitializationException(msgID, message, e);
}
DirectoryServer.registerSASLMechanismHandler(SASL_MECHANISM_PLAIN, this);
DirectoryServer.registerConfigurableComponent(this);
}
/**
* Performs any finalization that may be necessary for this SASL mechanism
* handler.
*/
public void finalizeSASLMechanismHandler()
{
assert debugEnter(CLASS_NAME, "finalizeSASLMechanismHandler");
DirectoryServer.deregisterConfigurableComponent(this);
DirectoryServer.deregisterSASLMechanismHandler(SASL_MECHANISM_PLAIN);
}
/**
* Processes the provided SASL bind operation. Note that if the SASL
* processing gets far enough to be able to map the associated request to a
* user entry (regardless of whether the authentication is ultimately
* successful), then this method must call the
* BindOperation.setSASLAuthUserEntry to provide it with the
* entry for the user that attempted to authenticate.
*
* @param bindOperation The SASL bind operation to be processed.
*/
public void processSASLBind(BindOperation bindOperation)
{
assert debugEnter(CLASS_NAME, "processSASLBind",
String.valueOf(bindOperation));
// Get the SASL credentials provided by the user and decode them.
String authzID = null;
String authcID = null;
String password = null;
ByteString saslCredentials = bindOperation.getSASLCredentials();
if (saslCredentials == null)
{
bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
int msgID = MSGID_SASLPLAIN_NO_SASL_CREDENTIALS;
String message = getMessage(msgID);
bindOperation.setAuthFailureReason(msgID, message);
return;
}
String credString = saslCredentials.stringValue();
int length = credString.length();
int nullPos1 = credString.indexOf('\u0000');
if (nullPos1 < 0)
{
bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
int msgID = MSGID_SASLPLAIN_NO_NULLS_IN_CREDENTIALS;
String message = getMessage(msgID);
bindOperation.setAuthFailureReason(msgID, message);
return;
}
if (nullPos1 > 0)
{
authzID = credString.substring(0, nullPos1);
}
int nullPos2 = credString.indexOf('\u0000', nullPos1+1);
if (nullPos2 < 0)
{
bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
int msgID = MSGID_SASLPLAIN_NO_SECOND_NULL;
String message = getMessage(msgID);
bindOperation.setAuthFailureReason(msgID, message);
return;
}
if (nullPos2 == (nullPos1+1))
{
bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
int msgID = MSGID_SASLPLAIN_ZERO_LENGTH_AUTHCID;
String message = getMessage(msgID);
bindOperation.setAuthFailureReason(msgID, message);
return;
}
if (nullPos2 == (length-1))
{
bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
int msgID = MSGID_SASLPLAIN_ZERO_LENGTH_PASSWORD;
String message = getMessage(msgID);
bindOperation.setAuthFailureReason(msgID, message);
return;
}
authcID = credString.substring(nullPos1+1, nullPos2);
password = credString.substring(nullPos2+1);
// Get the user entry for the authentication ID. Allow for an
// authentication ID that is just a username (as per the SASL PLAIN spec),
// but also allow a value in the authzid form specified in RFC 2829.
Entry userEntry = null;
String lowerAuthcID = toLowerCase(authcID);
if (lowerAuthcID.startsWith("dn:"))
{
// Try to decode the user DN and retrieve the corresponding entry.
DN userDN;
try
{
userDN = DN.decode(authcID.substring(3));
}
catch (DirectoryException de)
{
assert debugException(CLASS_NAME, "processSASLBind", de);
bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
int msgID = MSGID_SASLPLAIN_CANNOT_DECODE_AUTHCID_AS_DN;
String message = getMessage(msgID, authcID, de.getErrorMessage());
bindOperation.setAuthFailureReason(msgID, message);
return;
}
if (userDN.isNullDN())
{
bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
int msgID = MSGID_SASLPLAIN_AUTHCID_IS_NULL_DN;
String message = getMessage(msgID);
bindOperation.setAuthFailureReason(msgID, 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());
int msgID = MSGID_SASLPLAIN_CANNOT_LOCK_ENTRY;
String message = getMessage(msgID, String.valueOf(userDN));
bindOperation.setAuthFailureReason(msgID, message);
return;
}
try
{
userEntry = DirectoryServer.getEntry(userDN);
}
catch (DirectoryException de)
{
assert debugException(CLASS_NAME, "processSASLBind", de);
bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
int msgID = MSGID_SASLPLAIN_CANNOT_GET_ENTRY_BY_DN;
String message = getMessage(msgID, String.valueOf(userDN),
de.getErrorMessage());
bindOperation.setAuthFailureReason(msgID, message);
return;
}
finally
{
LockManager.unlock(userDN, readLock);
}
}
else
{
// Use the identity mapper to resolve the username to an entry.
if (lowerAuthcID.startsWith("u:"))
{
authcID = authcID.substring(2);
}
try
{
userEntry = identityMapper.getEntryForID(authcID);
}
catch (DirectoryException de)
{
assert debugException(CLASS_NAME, "processSASLBind", de);
bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
int msgID = MSGID_SASLPLAIN_CANNOT_MAP_USERNAME;
String message = getMessage(msgID, String.valueOf(authcID),
de.getErrorMessage());
bindOperation.setAuthFailureReason(msgID, 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);
int msgID = MSGID_SASLPLAIN_NO_MATCHING_ENTRIES;
String message = getMessage(msgID, authcID);
bindOperation.setAuthFailureReason(msgID, message);
return;
}
else
{
bindOperation.setSASLAuthUserEntry(userEntry);
}
// Get the password attribute from the user entry and see if any of the
// values match the provided clear-text password.
// FIXME -- Determine the attribute based on the user's password policy.
AttributeType pwType = DirectoryServer.getAttributeType(ATTR_USER_PASSWORD);
if (pwType == null)
{
pwType = DirectoryServer.getDefaultAttributeType(ATTR_USER_PASSWORD);
}
Listtrue if the provided entry has an acceptable
* configuration for this component, or false if not.
*/
public boolean hasAcceptableConfiguration(ConfigEntry configEntry,
Listtrue if this SASL mechanism is password-based, or
* false if it uses some other form of credentials.
*/
public boolean isPasswordBased(String mechanism)
{
assert debugEnter(CLASS_NAME, "isPasswordBased", String.valueOf(mechanism));
// This is a password-based mechanism.
return true;
}
/**
* Indicates whether the specified SASL mechanism should be considered secure
* (i.e., it does not expose the authentication credentials in a manner that
* is useful to a third-party observer, and other aspects of the
* authentication are generally secure).
*
* @param mechanism The name of the mechanism for which to make the
* determination. This will only be invoked with names of
* mechanisms for which this handler has previously
* registered.
*
* @return true if this SASL mechanism should be considered
* secure, or false if not.
*/
public boolean isSecure(String mechanism)
{
assert debugEnter(CLASS_NAME, "isSecure", String.valueOf(mechanism));
// This is not a secure mechanism.
return false;
}
}