001/*
002 *  Licensed to the Apache Software Foundation (ASF) under one
003 *  or more contributor license agreements.  See the NOTICE file
004 *  distributed with this work for additional information
005 *  regarding copyright ownership.  The ASF licenses this file
006 *  to you under the Apache License, Version 2.0 (the
007 *  "License"); you may not use this file except in compliance
008 *  with the License.  You may obtain a copy of the License at
009 *
010 *    http://www.apache.org/licenses/LICENSE-2.0
011 *
012 *  Unless required by applicable law or agreed to in writing,
013 *  software distributed under the License is distributed on an
014 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 *  KIND, either express or implied.  See the License for the
016 *  specific language governing permissions and limitations
017 *  under the License.
018 *
019 */
020package org.apache.directory.server.ldap.handlers;
021
022
023import org.apache.commons.lang3.exception.ExceptionUtils;
024import org.apache.directory.api.ldap.codec.api.LdapApiService;
025import org.apache.directory.api.ldap.model.exception.LdapOperationException;
026import org.apache.directory.api.ldap.model.exception.LdapReferralException;
027import org.apache.directory.api.ldap.model.message.AbandonRequest;
028import org.apache.directory.api.ldap.model.message.BindRequest;
029import org.apache.directory.api.ldap.model.message.BindResponse;
030import org.apache.directory.api.ldap.model.message.BindResponseImpl;
031import org.apache.directory.api.ldap.model.message.ExtendedRequest;
032import org.apache.directory.api.ldap.model.message.LdapResult;
033import org.apache.directory.api.ldap.model.message.Referral;
034import org.apache.directory.api.ldap.model.message.ReferralImpl;
035import org.apache.directory.api.ldap.model.message.Request;
036import org.apache.directory.api.ldap.model.message.ResultCodeEnum;
037import org.apache.directory.api.ldap.model.message.ResultResponse;
038import org.apache.directory.api.ldap.model.message.ResultResponseRequest;
039import org.apache.directory.server.core.api.CoreSession;
040import org.apache.directory.server.core.shared.DefaultCoreSession;
041import org.apache.directory.server.i18n.I18n;
042import org.apache.directory.server.ldap.LdapServer;
043import org.apache.directory.server.ldap.LdapSession;
044import org.apache.directory.server.ldap.handlers.extended.StartTlsHandler;
045import org.apache.mina.core.filterchain.IoFilterChain;
046import org.apache.mina.core.session.IoSession;
047import org.apache.mina.handler.demux.MessageHandler;
048import org.slf4j.Logger;
049import org.slf4j.LoggerFactory;
050
051
052/**
053 * A base class for all LDAP request handlers.
054 *
055 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
056 */
057public abstract class LdapRequestHandler<T extends Request> implements MessageHandler<T>
058{
059    /** The logger for this class */
060    protected static final Logger LOG = LoggerFactory.getLogger( LdapRequestHandler.class );
061
062    /** The reference on the Ldap server instance */
063    protected LdapServer ldapServer;
064
065
066    /**
067     * @return The associated ldap server instance
068     */
069    public final LdapServer getLdapServer()
070    {
071        return ldapServer;
072    }
073
074
075    /**
076     * Associates a Ldap server instance to the message handler
077     * @param ldapServer the associated ldap server instance
078     */
079    public final void setLdapServer( LdapServer ldapServer )
080    {
081        this.ldapServer = ldapServer;
082    }
083
084
085    /**
086     * Checks to see if confidentiality requirements are met.  If the
087     * LdapServer requires confidentiality and the SSLFilter is engaged
088     * this will return true.  If confidentiality is not required this
089     * will return true.  If confidentially is required and the SSLFilter
090     * is not engaged in the IoFilterChain this will return false.
091     *
092     * This method is used by handlers to determine whether to send back
093     * {@link ResultCodeEnum#CONFIDENTIALITY_REQUIRED} error responses back
094     * to clients.
095     *
096     * @param session the MINA IoSession to check for TLS security
097     * @return true if confidentiality requirement is met, false otherwise
098     */
099    public final boolean isConfidentialityRequirementSatisfied( IoSession session )
100    {
101
102        if ( !ldapServer.isConfidentialityRequired() )
103        {
104            return true;
105        }
106
107        IoFilterChain chain = session.getFilterChain();
108        return chain.contains( "sslFilter" );
109    }
110
111
112    public void rejectWithoutConfidentiality( IoSession session, ResultResponse resp )
113    {
114        LdapResult result = resp.getLdapResult();
115        result.setResultCode( ResultCodeEnum.CONFIDENTIALITY_REQUIRED );
116        result.setDiagnosticMessage( "Confidentiality (TLS secured connection) is required." );
117        session.write( resp );
118    }
119
120
121    /**
122     *{@inheritDoc}
123     */
124    @Override
125    public final void handleMessage( IoSession session, T message ) throws Exception
126    {
127        LdapSession ldapSession = ldapServer.getLdapSessionManager().getLdapSession( session );
128
129        if ( ldapSession == null )
130        {
131            // in some cases the session is becoming null though the client is sending the UnbindRequest
132            // before closing
133            LOG.info( "ignoring the message {} received from null session", message );
134            return;
135        }
136
137        // First check that the client hasn't issued a previous BindRequest, unless it
138        // was a SASL BindRequest
139        if ( ldapSession.isAuthPending() )
140        {
141            // Only SASL BinRequest are allowed if we already are handling a
142            // SASL BindRequest
143            if ( !( message instanceof BindRequest ) || ( ( BindRequest ) message ).isSimple()
144                || ldapSession.isSimpleAuthPending() )
145            {
146                LOG.error( I18n.err( I18n.ERR_732 ) );
147                BindResponse bindResponse = new BindResponseImpl( message.getMessageId() );
148                LdapResult bindResult = bindResponse.getLdapResult();
149                bindResult.setResultCode( ResultCodeEnum.UNWILLING_TO_PERFORM );
150                bindResult.setDiagnosticMessage( I18n.err( I18n.ERR_732 ) );
151                ldapSession.getIoSession().write( bindResponse );
152                return;
153            }
154        }
155
156        // TODO - session you get from LdapServer should have the ldapServer
157        // member already set no?  Should remove these lines where ever they
158        // may be if that's the case.
159        ldapSession.setLdapServer( ldapServer );
160
161        // protect against insecure conns when confidentiality is required
162        if ( !isConfidentialityRequirementSatisfied( session ) )
163        {
164            if ( message instanceof ExtendedRequest )
165            {
166                // Reject all extended operations except StartTls
167                ExtendedRequest req = ( ExtendedRequest ) message;
168
169                if ( !req.getRequestName().equals( StartTlsHandler.EXTENSION_OID ) )
170                {
171                    rejectWithoutConfidentiality( session, req.getResultResponse() );
172                    return;
173                }
174
175                // Allow StartTls extended operations to go through
176            }
177            else if ( message instanceof ResultResponseRequest )
178            {
179                // Reject all other operations that have a result response
180                rejectWithoutConfidentiality( session, ( ( ResultResponseRequest ) message )
181                    .getResultResponse() );
182                return;
183            }
184            else
185            // Just return from unbind, and abandon immediately
186            {
187                return;
188            }
189        }
190
191        // We should check that the server allows anonymous requests
192        // only if it's not a BindRequest
193        if ( message instanceof BindRequest )
194        {
195            handle( ldapSession, message );
196        }
197        else
198        {
199            CoreSession coreSession = null;
200
201            /*
202             * All requests except bind automatically presume the authentication
203             * is anonymous if the session has not been authenticated.  Hence a
204             * default bind is presumed as the anonymous identity.
205             */
206            if ( ldapSession.isAuthenticated() )
207            {
208                coreSession = ldapSession.getCoreSession();
209                handle( ldapSession, message );
210                return;
211            }
212
213            coreSession = getLdapServer().getDirectoryService().getSession();
214            ldapSession.setCoreSession( coreSession );
215
216            // Store the IoSession in the coreSession
217            ( ( DefaultCoreSession ) coreSession ).setIoSession( ldapSession.getIoSession() );
218
219            if ( message instanceof AbandonRequest )
220            {
221                return;
222            }
223
224            handle( ldapSession, message );
225        }
226    }
227
228
229    /**
230     * Handle a Ldap message associated with a session
231     *
232     * @param session The associated session
233     * @param message The message we have to handle
234     * @throws Exception If there is an error during the processing of this message
235     */
236    public abstract void handle( LdapSession session, T message ) throws Exception;
237
238
239    /**
240     * Handles processing with referrals without ManageDsaIT decorator.
241     *
242     * @param session The associated session
243     * @param req The response
244     * @param e The associated exception
245     */
246    public void handleException( LdapSession session, ResultResponseRequest request, ResultResponse response, Exception e )
247    {
248        LdapResult result = request.getResultResponse().getLdapResult();
249
250        /*
251         * Set the result code or guess the best option.
252         */
253        ResultCodeEnum code;
254
255        if ( e instanceof LdapOperationException )
256        {
257            code = ( ( LdapOperationException ) e ).getResultCode();
258        }
259        else
260        {
261            code = ResultCodeEnum.getBestEstimate( e, request.getType() );
262        }
263
264        result.setResultCode( code );
265
266        /*
267         * Setup the error message to put into the request and put entire
268         * exception into the message if we are in debug mode.  Note we
269         * embed the result code name into the message.
270         */
271        String msg = code.toString() + ": failed for " + request + ": " + e.getLocalizedMessage();
272
273        LOG.debug( msg, e );
274
275        if ( LOG.isDebugEnabled() || ( code == ResultCodeEnum.OTHER ) )
276        {
277            msg += ":\n" + ExceptionUtils.getStackTrace( e );
278        }
279
280        result.setDiagnosticMessage( msg );
281
282        if ( e instanceof LdapOperationException )
283        {
284            LdapOperationException ne = ( LdapOperationException ) e;
285
286            // Add the matchedDN if necessary
287            boolean setMatchedDn = code == ResultCodeEnum.NO_SUCH_OBJECT || code == ResultCodeEnum.ALIAS_PROBLEM
288                || code == ResultCodeEnum.INVALID_DN_SYNTAX || code == ResultCodeEnum.ALIAS_DEREFERENCING_PROBLEM;
289
290            if ( ( ne.getResolvedDn() != null ) && setMatchedDn )
291            {
292                result.setMatchedDn( ne.getResolvedDn() );
293            }
294
295            // Add the referrals if necessary
296            if ( e instanceof LdapReferralException )
297            {
298                Referral referrals = new ReferralImpl();
299
300                do
301                {
302                    String ref = ( ( LdapReferralException ) e ).getReferralInfo();
303                    referrals.addLdapUrl( ref );
304                }
305                while ( ( ( LdapReferralException ) e ).skipReferral() );
306
307                result.setReferral( referrals );
308            }
309        }
310
311        session.getIoSession().write( response );
312    }
313
314
315    /**
316     * @return The LDAP API Codec service
317     */
318    protected LdapApiService getLdapApiService()
319    {
320        return ldapServer.getDirectoryService().getLdapCodecService();
321    }
322}