blob: 0c7b37d1dbd6e069ae17d6855679dcc9634d86da [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
package org.apache.directory.server.ldap.handlers;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.apache.directory.server.core.CoreSession;
import org.apache.directory.server.i18n.I18n;
import org.apache.directory.server.ldap.LdapServer;
import org.apache.directory.server.ldap.LdapSession;
import org.apache.directory.server.ldap.handlers.extended.StartTlsHandler;
import org.apache.directory.shared.ldap.exception.LdapOperationException;
import org.apache.directory.shared.ldap.exception.LdapReferralException;
import org.apache.directory.shared.ldap.message.AbandonRequest;
import org.apache.directory.shared.ldap.message.BindRequest;
import org.apache.directory.shared.ldap.codec.message.BindRequestImpl;
import org.apache.directory.shared.ldap.message.BindResponse;
import org.apache.directory.shared.ldap.message.ExtendedRequest;
import org.apache.directory.shared.ldap.message.LdapResult;
import org.apache.directory.shared.ldap.message.Referral;
import org.apache.directory.shared.ldap.codec.message.ReferralImpl;
import org.apache.directory.shared.ldap.message.Request;
import org.apache.directory.shared.ldap.message.ResultCodeEnum;
import org.apache.directory.shared.ldap.message.ResultResponse;
import org.apache.directory.shared.ldap.message.ResultResponseRequest;
import org.apache.directory.shared.ldap.name.DN;
import org.apache.mina.core.filterchain.IoFilterChain;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.handler.demux.MessageHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A base class for all LDAP request handlers.
*
* @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
*/
public abstract class LdapRequestHandler<T extends Request> implements MessageHandler<T>
{
/** The logger for this class */
private static final Logger LOG = LoggerFactory.getLogger( LdapRequestHandler.class );
/** The reference on the Ldap server instance */
protected LdapServer ldapServer;
/**
* @return The associated ldap server instance
*/
public final LdapServer getLdapServer()
{
return ldapServer;
}
/**
* Associates a Ldap server instance to the message handler
* @param ldapServer the associated ldap server instance
*/
public final void setLdapServer( LdapServer ldapServer )
{
this.ldapServer = ldapServer;
}
/**
* Checks to see if confidentiality requirements are met. If the
* LdapServer requires confidentiality and the SSLFilter is engaged
* this will return true. If confidentiality is not required this
* will return true. If confidentially is required and the SSLFilter
* is not engaged in the IoFilterChain this will return false.
*
* This method is used by handlers to determine whether to send back
* {@link ResultCodeEnum#CONFIDENTIALITY_REQUIRED} error responses back
* to clients.
*
* @param session the MINA IoSession to check for TLS security
* @return true if confidentiality requirement is met, false otherwise
*/
public final boolean isConfidentialityRequirementSatisfied( IoSession session )
{
if ( !ldapServer.isConfidentialityRequired() )
{
return true;
}
IoFilterChain chain = session.getFilterChain();
return chain.contains( "sslFilter" );
}
public void rejectWithoutConfidentiality( IoSession session, ResultResponse resp )
{
LdapResult result = resp.getLdapResult();
result.setResultCode( ResultCodeEnum.CONFIDENTIALITY_REQUIRED );
result.setErrorMessage( "Confidentiality (TLS secured connection) is required." );
session.write( resp );
}
/**
*{@inheritDoc}
*/
// False positive, we want to keep the comment
@SuppressWarnings("PMD.CollapsibleIfStatements")
public final void handleMessage( IoSession session, T message ) throws Exception
{
LdapSession ldapSession = ldapServer.getLdapSessionManager().getLdapSession( session );
if ( ldapSession == null )
{
// in some cases the session is becoming null though the client is sending the UnbindRequest
// before closing
LOG.info( "ignoring the message {} received from null session", message );
return;
}
// First check that the client hasn't issued a previous BindRequest, unless it
// was a SASL BindRequest
if ( ldapSession.isAuthPending() )
{
// Only SASL BinRequest are allowed if we already are handling a
// SASL BindRequest
if ( !( message instanceof BindRequestImpl ) || ( ( BindRequestImpl ) message ).isSimple()
|| ldapSession.isSimpleAuthPending() )
{
LOG.error( I18n.err( I18n.ERR_732 ) );
BindResponse bindResponse = new org.apache.directory.shared.ldap.codec.message.BindResponseImpl( message.getMessageId() );
LdapResult bindResult = bindResponse.getLdapResult();
bindResult.setResultCode( ResultCodeEnum.UNWILLING_TO_PERFORM );
bindResult.setErrorMessage( I18n.err( I18n.ERR_732 ) );
ldapSession.getIoSession().write( bindResponse );
return;
}
}
// TODO - session you get from LdapServer should have the ldapServer
// member already set no? Should remove these lines where ever they
// may be if that's the case.
ldapSession.setLdapServer( ldapServer );
// protect against insecure conns when confidentiality is required
if ( !isConfidentialityRequirementSatisfied( session ) )
{
if ( message instanceof ExtendedRequest )
{
// Reject all extended operations except StartTls
ExtendedRequest req = ( ExtendedRequest ) message;
if ( !req.getRequestName().equals( StartTlsHandler.EXTENSION_OID ) )
{
rejectWithoutConfidentiality( session, req.getResultResponse() );
return;
}
// Allow StartTls extended operations to go through
}
else if ( message instanceof ResultResponseRequest )
{
// Reject all other operations that have a result response
rejectWithoutConfidentiality( session, ( ( ResultResponseRequest ) message )
.getResultResponse() );
return;
}
else
// Just return from unbind, and abandon immediately
{
return;
}
}
// We should check that the server allows anonymous requests
// only if it's not a BindRequest
if ( message instanceof BindRequest )
{
handle( ldapSession, message );
}
else
{
CoreSession coreSession = null;
/*
* All requests except bind automatically presume the authentication
* is anonymous if the session has not been authenticated. Hence a
* default bind is presumed as the anonymous identity.
*/
if ( ldapSession.isAuthenticated() )
{
coreSession = ldapSession.getCoreSession();
handle( ldapSession, message );
return;
}
coreSession = getLdapServer().getDirectoryService().getSession();
ldapSession.setCoreSession( coreSession );
if ( message instanceof AbandonRequest )
{
return;
}
handle( ldapSession, message );
return;
}
}
/**
* Handle a Ldap message associated with a session
*
* @param session The associated session
* @param message The message we have to handle
* @throws Exception If there is an error during the processing of this message
*/
public abstract void handle( LdapSession session, T message ) throws Exception;
/**
* Handles processing with referrals without ManageDsaIT control.
*/
public void handleException( LdapSession session, ResultResponseRequest req, Exception e )
{
LdapResult result = req.getResultResponse().getLdapResult();
/*
* Set the result code or guess the best option.
*/
ResultCodeEnum code;
if ( e instanceof LdapOperationException )
{
code = ( ( LdapOperationException ) e ).getResultCode();
}
else
{
code = ResultCodeEnum.getBestEstimate( e, req.getType() );
}
result.setResultCode( code );
/*
* Setup the error message to put into the request and put entire
* exception into the message if we are in debug mode. Note we
* embed the result code name into the message.
*/
String msg = code.toString() + ": failed for " + req + ": " + e.getLocalizedMessage();
if ( LOG.isDebugEnabled() )
{
LOG.debug( msg, e );
msg += ":\n" + ExceptionUtils.getStackTrace( e );
}
result.setErrorMessage( msg );
if ( e instanceof LdapOperationException )
{
LdapOperationException ne = ( LdapOperationException ) e;
// Add the matchedDN if necessary
boolean setMatchedDn = code == ResultCodeEnum.NO_SUCH_OBJECT || code == ResultCodeEnum.ALIAS_PROBLEM
|| code == ResultCodeEnum.INVALID_DN_SYNTAX || code == ResultCodeEnum.ALIAS_DEREFERENCING_PROBLEM;
if ( ( ne.getResolvedDn() != null ) && setMatchedDn )
{
result.setMatchedDn( ( DN ) ne.getResolvedDn() );
}
// Add the referrals if necessary
if ( e instanceof LdapReferralException )
{
Referral referrals = new ReferralImpl();
do
{
String ref = ( ( LdapReferralException ) e ).getReferralInfo();
referrals.addLdapUrl( ref );
}
while ( ( ( LdapReferralException ) e ).skipReferral() );
result.setReferral( referrals );
}
}
session.getIoSession().write( req.getResultResponse() );
}
}