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