blob: c1417a711fd17df974457b1129e8b6ecd4886286 [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;
import org.apache.directory.shared.ldap.codec.api.BinaryAttributeDetector;
import org.apache.directory.shared.ldap.codec.api.LdapCodecServiceFactory;
import org.apache.directory.shared.ldap.codec.api.MessageDecorator;
import org.apache.directory.shared.ldap.codec.api.LdapMessageContainer;
import org.apache.directory.shared.ldap.model.message.extended.NoticeOfDisconnect;
import org.apache.directory.shared.ldap.model.exception.ResponseCarryingMessageException;
import org.apache.directory.shared.ldap.model.message.Control;
import org.apache.directory.shared.ldap.model.message.ExtendedRequest;
import org.apache.directory.shared.ldap.model.message.Message;
import org.apache.directory.shared.ldap.model.message.Request;
import org.apache.directory.shared.ldap.model.message.ResultCodeEnum;
import org.apache.directory.shared.ldap.model.message.ResultResponse;
import org.apache.directory.shared.ldap.model.message.ResultResponseRequest;
import org.apache.directory.shared.ldap.model.schema.MutableAttributeTypeImpl;
import org.apache.directory.shared.ldap.model.schema.SchemaManager;
import org.apache.directory.shared.util.Strings;
import org.apache.mina.core.service.IoHandler;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.ssl.SslFilter;
import org.apache.mina.handler.demux.DemuxingIoHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The MINA IoHandler implementation extending {@link DemuxingIoHandler} for
* the LDAP protocol. THe {@link LdapServer} creates this multiplexing
* {@link IoHandler} handler and populates it with subordinate handlers for
* the various kinds of LDAP {@link Request} messages. This is done in the
* setXxxHandler() methods of the LdapServer where Xxxx is Add, Modify, etc.
*
* @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
*/
class LdapProtocolHandler extends DemuxingIoHandler
{
/** The logger */
private static final Logger LOG = LoggerFactory.getLogger( LdapProtocolHandler.class );
/** the {@link LdapServer} this handler is associated with */
private final LdapServer ldapServer;
/**
* Creates a new instance of LdapProtocolHandler.
*
* @param ldapServer The LDAP server instance
*/
LdapProtocolHandler( LdapServer ldapServer )
{
this.ldapServer = ldapServer;
}
/**
* This method is called when a new session is created. We will store some
* informations that the session will need to process incoming requests.
*
* @param session the newly created session
*/
public void sessionCreated( IoSession session ) throws Exception
{
// First, create a new LdapSession and store it i the manager
LdapSession ldapSession = new LdapSession( session );
ldapServer.getLdapSessionManager().addLdapSession( ldapSession );
// Now, we have to store the DirectoryService instance into the session
session.setAttribute( "maxPDUSize", ldapServer.getDirectoryService().getMaxPDUSize() );
// Last, store the message container
LdapMessageContainer<? extends MessageDecorator<Message>> ldapMessageContainer =
new LdapMessageContainer<MessageDecorator<Message>>(
ldapServer.getDirectoryService().getLdapCodecService(),
new BinaryAttributeDetector()
{
public boolean isBinary( String id )
{
SchemaManager schemaManager = ldapServer.getDirectoryService().getSchemaManager();
try
{
MutableAttributeTypeImpl type = schemaManager.lookupAttributeTypeRegistry( id );
return !type.getSyntax().isHumanReadable();
}
catch ( Exception e )
{
return !Strings.isEmpty(id) && id.endsWith(";binary");
}
}
} );
session.setAttribute( "messageContainer", ldapMessageContainer );
}
/**
* This method is called when a session is closed. If we have some
* cleanup to do, it's done there.
*
* @param session the closing session
*/
public void sessionClosed( IoSession session )
{
// Get the associated LdapSession
LdapSession ldapSession = ldapServer.getLdapSessionManager().removeLdapSession( session );
// Clean it up !
cleanUpSession( ldapSession );
}
/**
* Explicitly handles {@link LdapSession} and {@link IoSession} cleanup tasks.
*
* @param ldapSession the LdapSession to cleanup after being removed from
* the {@link LdapSessionManager}
*/
private void cleanUpSession( LdapSession ldapSession )
{
if ( ldapSession == null )
{
LOG.warn( "Null LdapSession given to cleanUpSession." );
return;
}
LOG.debug( "Cleaning the {} session", ldapSession );
// Abandon all the requests
ldapSession.abandonAllOutstandingRequests();
if ( !ldapSession.getIoSession().isClosing() || ldapSession.getIoSession().isConnected() )
{
try
{
ldapSession.getIoSession().close( true );
}
catch ( Throwable t )
{
LOG.warn( "Failed to close IoSession for LdapSession." );
}
}
}
/**
* {@inheritDoc}
*/
public void messageSent( IoSession session, Object message ) throws Exception
{
// Do nothing : we have to ignore this message, otherwise we get an exception,
// thanks to the way MINA 2 works ...
}
/**
* {@inheritDoc}
*/
public void messageReceived( IoSession session, Object message ) throws Exception
{
// Translate SSLFilter messages into LDAP extended request
// defined in RFC #2830, 'Lightweight Directory Access Protocol (v3):
// Extension for Transport Layer Security'.
//
// The RFC specifies the payload should be empty, but we use
// it to notify the TLS state changes. This hack should be
// OK from the viewpointd of security because StartTLS
// handler should react to only SESSION_UNSECURED message
// and degrade authentication level to 'anonymous' as specified
// in the RFC, and this is no threat.
if ( message == SslFilter.SESSION_SECURED )
{
ExtendedRequest<?> req =
LdapCodecServiceFactory.getSingleton().newExtendedRequest( "1.3.6.1.4.1.1466.20037",
"SECURED".getBytes( "ISO-8859-1" ) );
message = req;
}
else if ( message == SslFilter.SESSION_UNSECURED )
{
ExtendedRequest<?> req =
LdapCodecServiceFactory.getSingleton().newExtendedRequest( "1.3.6.1.4.1.1466.20037",
"SECURED".getBytes( "ISO-8859-1" ) );
message = req;
}
if ( ( ( Request ) message ).getControls().size() > 0
&& message instanceof ResultResponseRequest )
{
ResultResponseRequest<?> req = ( ResultResponseRequest<?> ) message;
for ( Control control : req.getControls().values() )
{
if ( control.isCritical() && !ldapServer.getSupportedControls().contains( control.getOid() ) )
{
ResultResponse resp = req.getResultResponse();
resp.getLdapResult().setErrorMessage( "Unsupport critical control: " + control.getOid() );
resp.getLdapResult().setResultCode( ResultCodeEnum.UNAVAILABLE_CRITICAL_EXTENSION );
session.write( resp );
return;
}
}
}
super.messageReceived( session, message );
}
/**
* {@inheritDoc}
*/
public void exceptionCaught( IoSession session, Throwable cause )
{
if ( cause.getCause() instanceof ResponseCarryingMessageException)
{
ResponseCarryingMessageException rcme = ( ResponseCarryingMessageException ) cause.getCause();
if ( rcme.getResponse() != null )
{
session.write( rcme.getResponse() );
return;
}
}
LOG.warn( "Unexpected exception forcing session to close: sending disconnect notice to client.", cause );
session.write( NoticeOfDisconnect.PROTOCOLERROR );
LdapSession ldapSession = this.ldapServer.getLdapSessionManager().removeLdapSession( session );
cleanUpSession( ldapSession );
session.close( true );
}
}