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     */
020    package org.apache.directory.server.ldap.handlers;
021    
022    
023    import org.apache.directory.server.core.CoreSession;
024    import org.apache.directory.server.i18n.I18n;
025    import org.apache.directory.server.ldap.LdapServer;
026    import org.apache.directory.server.ldap.LdapSession;
027    import org.apache.directory.server.ldap.handlers.extended.StartTlsHandler;
028    import org.apache.directory.shared.ldap.exception.LdapOperationException;
029    import org.apache.directory.shared.ldap.exception.LdapReferralException;
030    import org.apache.directory.shared.ldap.message.BindRequestImpl;
031    import org.apache.directory.shared.ldap.message.BindResponseImpl;
032    import org.apache.directory.shared.ldap.message.ReferralImpl;
033    import org.apache.directory.shared.ldap.message.ResultCodeEnum;
034    import org.apache.directory.shared.ldap.message.internal.InternalAbandonRequest;
035    import org.apache.directory.shared.ldap.message.internal.InternalBindRequest;
036    import org.apache.directory.shared.ldap.message.internal.InternalBindResponse;
037    import org.apache.directory.shared.ldap.message.internal.InternalExtendedRequest;
038    import org.apache.directory.shared.ldap.message.internal.InternalLdapResult;
039    import org.apache.directory.shared.ldap.message.internal.InternalReferral;
040    import org.apache.directory.shared.ldap.message.internal.InternalRequest;
041    import org.apache.directory.shared.ldap.message.internal.InternalResultResponse;
042    import org.apache.directory.shared.ldap.message.internal.InternalResultResponseRequest;
043    import org.apache.directory.shared.ldap.name.DN;
044    import org.apache.directory.shared.ldap.util.ExceptionUtils;
045    import org.apache.mina.core.filterchain.IoFilterChain;
046    import org.apache.mina.core.session.IoSession;
047    import org.apache.mina.handler.demux.MessageHandler;
048    import org.slf4j.Logger;
049    import 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     * @version $Rev: 541827 $
057     */
058    public abstract class LdapRequestHandler<T extends InternalRequest> implements MessageHandler<T>
059    {
060        /** The logger for this class */
061        private static final Logger LOG = LoggerFactory.getLogger( LdapRequestHandler.class );
062    
063        /** The reference on the Ldap server instance */
064        protected LdapServer ldapServer;
065    
066    
067        /**
068         * @return The associated ldap server instance
069         */
070        public final LdapServer getLdapServer()
071        {
072            return ldapServer;
073        }
074    
075    
076        /**
077         * Associates a Ldap server instance to the message handler
078         * @param ldapServer the associated ldap server instance
079         */
080        public final void setLdapServer( LdapServer ldapServer )
081        {
082            this.ldapServer = ldapServer;
083        }
084        
085        
086        /**
087         * Checks to see if confidentiality requirements are met.  If the 
088         * LdapServer requires confidentiality and the SSLFilter is engaged
089         * this will return true.  If confidentiality is not required this 
090         * will return true.  If confidentially is required and the SSLFilter
091         * is not engaged in the IoFilterChain this will return false.
092         * 
093         * This method is used by handlers to determine whether to send back
094         * {@link ResultCodeEnum#CONFIDENTIALITY_REQUIRED} error responses back
095         * to clients.
096         * 
097         * @param session the MINA IoSession to check for TLS security
098         * @return true if confidentiality requirement is met, false otherwise
099         */
100        public final boolean isConfidentialityRequirementSatisfied( IoSession session )
101        {
102           
103           if ( ! ldapServer.isConfidentialityRequired() )
104           {
105               return true;
106           }
107           
108            IoFilterChain chain = session.getFilterChain();
109            return chain.contains( "sslFilter" );
110        }
111    
112        
113        public void rejectWithoutConfidentiality( IoSession session, InternalResultResponse resp ) 
114        {
115            InternalLdapResult result = resp.getLdapResult();
116            result.setResultCode( ResultCodeEnum.CONFIDENTIALITY_REQUIRED );
117            result.setErrorMessage( "Confidentiality (TLS secured connection) is required." );
118            session.write( resp );
119            return;
120        }
121    
122        /**
123         *{@inheritDoc} 
124         */
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 BindRequestImpl ) || 
144                     ((BindRequestImpl)message).isSimple() ||
145                     ldapSession.isSimpleAuthPending() )
146                {
147                    LOG.error( I18n.err( I18n.ERR_732 ) );
148                    InternalBindResponse bindResponse = new BindResponseImpl( message.getMessageId() );
149                    InternalLdapResult bindResult = bindResponse.getLdapResult();
150                    bindResult.setResultCode( ResultCodeEnum.UNWILLING_TO_PERFORM );
151                    bindResult.setErrorMessage( I18n.err( I18n.ERR_732 ) );
152                    ldapSession.getIoSession().write( bindResponse );
153                    return;
154                }
155            }
156            
157            // TODO - session you get from LdapServer should have the ldapServer 
158            // member already set no?  Should remove these lines where ever they
159            // may be if that's the case.
160            ldapSession.setLdapServer( ldapServer );
161            
162            // protect against insecure conns when confidentiality is required 
163            if ( ! isConfidentialityRequirementSatisfied( session ) )
164            {
165                if ( message instanceof InternalExtendedRequest )
166                {
167                    // Reject all extended operations except StartTls  
168                    InternalExtendedRequest req = ( InternalExtendedRequest ) message;
169                    
170                    if ( ! req.getID().equals( StartTlsHandler.EXTENSION_OID ) )
171                    {
172                        rejectWithoutConfidentiality( session, req.getResultResponse() );
173                        return;
174                    }
175                    
176                    // Allow StartTls extended operations to go through
177                }
178                else if ( message instanceof InternalResultResponseRequest )
179                {
180                    // Reject all other operations that have a result response  
181                    rejectWithoutConfidentiality( session, ( ( InternalResultResponseRequest ) message ).getResultResponse() );
182                    return;
183                }
184                else // Just return from unbind, and abandon immediately
185                {
186                    return;
187                }
188            }
189    
190            // We should check that the server allows anonymous requests
191            // only if it's not a BindRequest
192            if ( message instanceof InternalBindRequest )
193            {
194                handle( ldapSession, message );
195            }
196            else
197            {
198                CoreSession coreSession = null;
199                
200                /*
201                 * All requests except bind automatically presume the authentication 
202                 * is anonymous if the session has not been authenticated.  Hence a
203                 * default bind is presumed as the anonymous identity.
204                 */
205                if ( ldapSession.isAuthenticated() )
206                {
207                    coreSession = ldapSession.getCoreSession();
208                    handle( ldapSession, message );
209                    return;
210                }
211                
212                coreSession = getLdapServer().getDirectoryService().getSession();
213                ldapSession.setCoreSession( coreSession );
214    
215                if ( message instanceof InternalAbandonRequest )
216                {
217                    return;
218                }
219                
220                handle( ldapSession, message );
221                return;
222            }
223        }
224        
225        /**
226         * Handle a Ldap message associated with a session
227         * 
228         * @param session The associated session
229         * @param message The message we have to handle
230         * @throws Exception If there is an error during the processing of this message
231         */
232        public abstract void handle( LdapSession session, T message ) throws Exception;
233        
234        
235        /**
236         * Handles processing with referrals without ManageDsaIT control.
237         */
238        public void handleException( LdapSession session, InternalResultResponseRequest req, Exception e )
239        {
240            InternalLdapResult result = req.getResultResponse().getLdapResult();
241    
242            /*
243             * Set the result code or guess the best option.
244             */
245            ResultCodeEnum code;
246            if ( e instanceof LdapOperationException )
247            {
248                code = ( ( LdapOperationException ) e ).getResultCode();
249            }
250            else
251            {
252                code = ResultCodeEnum.getBestEstimate( e, req.getType() );
253            }
254            
255            result.setResultCode( code );
256    
257            /*
258             * Setup the error message to put into the request and put entire
259             * exception into the message if we are in debug mode.  Note we 
260             * embed the result code name into the message.
261             */
262            String msg = code.toString() + ": failed for " + req + ": " + e.getLocalizedMessage();
263    
264            if ( LOG.isDebugEnabled() )
265            {
266                LOG.debug( msg, e );
267            
268                msg += ":\n" + ExceptionUtils.getStackTrace( e );
269            }
270            
271            result.setErrorMessage( msg );
272    
273            if ( e instanceof LdapOperationException )
274            {
275                LdapOperationException ne = ( LdapOperationException ) e;
276    
277                // Add the matchedDN if necessary
278                boolean setMatchedDn = 
279                    code == ResultCodeEnum.NO_SUCH_OBJECT             || 
280                    code == ResultCodeEnum.ALIAS_PROBLEM              ||
281                    code == ResultCodeEnum.INVALID_DN_SYNTAX          || 
282                    code == ResultCodeEnum.ALIAS_DEREFERENCING_PROBLEM;
283                
284                if ( ( ne.getResolvedDn() != null ) && setMatchedDn )
285                {
286                    result.setMatchedDn( ( DN ) ne.getResolvedDn() );
287                }
288                
289                // Add the referrals if necessary
290                if ( e instanceof LdapReferralException )
291                {
292                    InternalReferral referrals = new ReferralImpl();
293                    
294                    do
295                    {
296                        String ref = ((LdapReferralException)e).getReferralInfo();
297                        referrals.addLdapUrl( ref );
298                    }
299                    while ( ((LdapReferralException)e).skipReferral() );
300                    
301                    result.setReferral( referrals );
302                }
303            }
304    
305            session.getIoSession().write( req.getResultResponse() );
306        }
307    }