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 }