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.InvalidNameException;
024 import javax.naming.NameNotFoundException;
025 import javax.naming.NamingException;
026
027 import org.apache.directory.server.core.entry.ClonedServerEntry;
028 import org.apache.directory.server.core.entry.ServerAttribute;
029 import org.apache.directory.server.ldap.LdapSession;
030 import org.apache.directory.shared.ldap.codec.util.LdapURLEncodingException;
031 import org.apache.directory.shared.ldap.constants.SchemaConstants;
032 import org.apache.directory.shared.ldap.entry.Value;
033 import org.apache.directory.shared.ldap.exception.LdapException;
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.ResultCodeEnum;
038 import org.apache.directory.shared.ldap.message.InternalResultResponseRequest;
039 import org.apache.directory.shared.ldap.message.InternalSearchRequest;
040 import org.apache.directory.shared.ldap.message.control.ManageDsaITControl;
041 import org.apache.directory.shared.ldap.name.LdapDN;
042 import org.apache.directory.shared.ldap.util.ExceptionUtils;
043 import org.apache.directory.shared.ldap.util.LdapURL;
044
045 import org.slf4j.Logger;
046 import org.slf4j.LoggerFactory;
047
048
049 /**
050 * A based class for handlers which deal with SingleReplyRequests. This class
051 * provides various capabilities out of the box for these kinds of requests so
052 * common handling code is not duplicated. Namely, exception handling and
053 * referral handling code common to most SingleReplyRequests (minus
054 * ExtendedRequests) are handled thanks to this class.
055 *
056 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
057 * @version $Rev$, $Date$
058 */
059 public abstract class ReferralAwareRequestHandler<T extends InternalResultResponseRequest> extends LdapRequestHandler<T>
060 {
061 private static final Logger LOG = LoggerFactory.getLogger( ReferralAwareRequestHandler.class );
062
063 /** Speedup for logs */
064 private static final boolean IS_DEBUG = LOG.isDebugEnabled();
065
066
067 /* (non-Javadoc)
068 * @see org.apache.directory.server.ldap.handlers.LdapRequestHandler#handle(org.apache.directory.server.ldap.LdapSession, org.apache.directory.shared.ldap.message.Request)
069 */
070 @Override
071 public final void handle( LdapSession session, T req ) throws Exception
072 {
073 LOG.debug( "Handling single reply request: {}", req );
074
075 // First, if we have the ManageDSAIt control, go directly
076 // to the handling without pre-processing the request
077 if ( req.getControls().containsKey( ManageDsaITControl.CONTROL_OID ) )
078 {
079 // If the ManageDsaIT control is present, we will
080 // consider that the user wants to get entry which
081 // are referrals as plain entry. We have to return
082 // SearchResponseEntry elements instead of
083 // SearchResponseReference elements.
084 LOG.debug( "ManageDsaITControl detected." );
085 handleIgnoringReferrals( session, req );
086 }
087 else
088 {
089 // No ManageDsaIT control. If the found entries is a referral,
090 // we will return SearchResponseReference elements.
091 LOG.debug( "ManageDsaITControl NOT detected." );
092
093 switch ( req.getType() )
094 {
095 case SEARCH_REQUEST:
096 handleWithReferrals( session, ( ( InternalSearchRequest ) req ).getBase(), req );
097 break;
098
099 case EXTENDED_REQ:
100 throw new IllegalStateException(
101 "Although ExtendedRequests are SingleReplyRequests they're not handled" +
102 " using this base class. They have no target entry unlike the rest of" +
103 " the SingleReplyRequests" );
104
105 default:
106 throw new IllegalStateException(
107 "Unidentified single reply request/response type: " + req );
108 }
109
110 }
111
112 }
113
114
115 public static final boolean isEntryReferral( ClonedServerEntry entry ) throws Exception
116 {
117 return entry.getOriginalEntry().contains( SchemaConstants.OBJECT_CLASS_AT, SchemaConstants.REFERRAL_OC );
118 }
119
120
121 /**
122 * Searches up the ancestry of a DN searching for the farthest referral
123 * ancestor. This is required to properly handle referrals. Note that
124 * this function is quite costly since it attempts to lookup all the
125 * ancestors up the hierarchy just to see if they represent referrals.
126 * Techniques can be employed later to improve this performance hit by
127 * having an intelligent referral cache.
128 *
129 * @return the farthest referral ancestor or null
130 * @throws Exception if there are problems during this search
131 */
132 public static final ClonedServerEntry getFarthestReferralAncestor( LdapSession session, LdapDN target )
133 throws Exception
134 {
135 ClonedServerEntry entry;
136 ClonedServerEntry farthestReferralAncestor = null;
137 LdapDN dn = ( LdapDN ) target.clone();
138
139 try
140 {
141 dn.remove( dn.size() - 1 );
142 }
143 catch ( InvalidNameException e2 )
144 {
145 // never thrown
146 }
147
148 while ( ! dn.isEmpty() )
149 {
150 LOG.debug( "Walking ancestors of {} to find referrals.", dn );
151
152 try
153 {
154 entry = session.getCoreSession().lookup( dn );
155
156 if ( isEntryReferral( entry ) )
157 {
158 farthestReferralAncestor = entry;
159 }
160
161 dn.remove( dn.size() - 1 );
162 }
163 catch ( NameNotFoundException e )
164 {
165 LOG.debug( "Entry for {} not found.", dn );
166
167 // update the DN as we strip last component
168 try
169 {
170 dn.remove( dn.size() - 1 );
171 }
172 catch ( InvalidNameException e1 )
173 {
174 // never happens
175 }
176 }
177 }
178
179 return farthestReferralAncestor;
180 }
181
182
183 /**
184 * Handles processing with referrals without ManageDsaIT control and with
185 * an ancestor that is a referral. The original entry was not found and
186 * the walk of the ancestry returned a referral.
187 *
188 * @param referralAncestor the farthest referral ancestor of the missing
189 * entry
190 */
191 public InternalReferral getReferralOnAncestor( LdapSession session, LdapDN reqTargetDn, T req,
192 ClonedServerEntry referralAncestor ) throws Exception
193 {
194 LOG.debug( "Inside getReferralOnAncestor()" );
195
196 ServerAttribute refAttr = ( ServerAttribute ) referralAncestor.getOriginalEntry()
197 .get( SchemaConstants.REF_AT );
198 InternalReferral referral = new ReferralImpl();
199
200 for ( Value<?> value : refAttr )
201 {
202 String ref = value.getString();
203
204 LOG.debug( "Calculating LdapURL for referrence value {}", ref );
205
206 // need to add non-ldap URLs as-is
207 if ( ! ref.startsWith( "ldap" ) )
208 {
209 referral.addLdapUrl( ref );
210 continue;
211 }
212
213 // parse the ref value and normalize the DN
214 LdapURL ldapUrl = new LdapURL();
215 try
216 {
217 ldapUrl.parse( ref.toCharArray() );
218 }
219 catch ( LdapURLEncodingException e )
220 {
221 LOG.error( "Bad URL ({}) for ref in {}. Reference will be ignored.", ref, referralAncestor );
222 }
223
224 LdapDN urlDn = new LdapDN( ldapUrl.getDn().getUpName() );
225 urlDn.normalize( session.getCoreSession().getDirectoryService().getRegistries()
226 .getAttributeTypeRegistry().getNormalizerMapping() );
227
228 if ( urlDn.getNormName().equals( referralAncestor.getDn().getNormName() ) )
229 {
230 // according to the protocol there is no need for the dn since it is the same as this request
231 StringBuilder buf = new StringBuilder();
232 buf.append( ldapUrl.getScheme() );
233 buf.append( ldapUrl.getHost() );
234
235 if ( ldapUrl.getPort() > 0 )
236 {
237 buf.append( ":" );
238 buf.append( ldapUrl.getPort() );
239 }
240
241 referral.addLdapUrl( buf.toString() );
242 continue;
243 }
244
245 /*
246 * If we get here then the DN of the referral was not the same as the
247 * DN of the ref LDAP URL. We must calculate the remaining (difference)
248 * name past the farthest referral DN which the target name extends.
249 */
250 int diff = reqTargetDn.size() - referralAncestor.getDn().size();
251 LdapDN extra = new LdapDN();
252
253 // TODO - fix this by access unormalized RDN values
254 // seems we have to do this because get returns normalized rdns
255 LdapDN reqUnnormalizedDn = new LdapDN( reqTargetDn.getUpName() );
256 for ( int jj = 0; jj < diff; jj++ )
257 {
258 extra.add( reqUnnormalizedDn.get( referralAncestor.getDn().size() + jj ) );
259 }
260
261 urlDn.addAll( extra );
262
263 StringBuilder buf = new StringBuilder();
264 buf.append( ldapUrl.getScheme() );
265 buf.append( ldapUrl.getHost() );
266
267 if ( ldapUrl.getPort() > 0 )
268 {
269 buf.append( ":" );
270 buf.append( ldapUrl.getPort() );
271 }
272
273 buf.append( "/" );
274 buf.append( LdapURL.urlEncode( urlDn.getUpName(), false ) );
275 referral.addLdapUrl( buf.toString() );
276 }
277
278 return referral;
279 }
280
281
282 /**
283 * Handles processing with referrals without ManageDsaIT control and with
284 * an ancestor that is a referral. The original entry was not found and
285 * the walk of the ancestry returned a referral.
286 *
287 * @param referralAncestor the farthest referral ancestor of the missing
288 * entry
289 */
290 public InternalReferral getReferralOnAncestorForSearch( LdapSession session, InternalSearchRequest req,
291 ClonedServerEntry referralAncestor ) throws Exception
292 {
293 LOG.debug( "Inside getReferralOnAncestor()" );
294
295 ServerAttribute refAttr = ( ServerAttribute ) referralAncestor.getOriginalEntry()
296 .get( SchemaConstants.REF_AT );
297 InternalReferral referral = new ReferralImpl();
298
299 for ( Value<?> value : refAttr )
300 {
301 String ref = value.getString();
302
303 LOG.debug( "Calculating LdapURL for referrence value {}", ref );
304
305 // need to add non-ldap URLs as-is
306 if ( ! ref.startsWith( "ldap" ) )
307 {
308 referral.addLdapUrl( ref );
309 continue;
310 }
311
312 // Parse the ref value
313 LdapURL ldapUrl = new LdapURL();
314 try
315 {
316 ldapUrl.parse( ref.toCharArray() );
317 }
318 catch ( LdapURLEncodingException e )
319 {
320 LOG.error( "Bad URL ({}) for ref in {}. Reference will be ignored.", ref, referralAncestor );
321 }
322
323 // Normalize the DN to check for same dn
324 LdapDN urlDn = new LdapDN( ldapUrl.getDn().getUpName() );
325 urlDn.normalize( session.getCoreSession().getDirectoryService().getRegistries()
326 .getAttributeTypeRegistry().getNormalizerMapping() );
327
328 if ( urlDn.getNormName().equals( req.getBase().getNormName() ) )
329 {
330 ldapUrl.setForceScopeRendering( true );
331 ldapUrl.setAttributes( req.getAttributes() );
332 ldapUrl.setScope( req.getScope().getJndiScope() );
333 referral.addLdapUrl( ldapUrl.toString() );
334 continue;
335 }
336
337 /*
338 * If we get here then the DN of the referral was not the same as the
339 * DN of the ref LDAP URL. We must calculate the remaining (difference)
340 * name past the farthest referral DN which the target name extends.
341 */
342 int diff = req.getBase().size() - referralAncestor.getDn().size();
343 LdapDN extra = new LdapDN();
344
345 // TODO - fix this by access unormalized RDN values
346 // seems we have to do this because get returns normalized rdns
347 LdapDN reqUnnormalizedDn = new LdapDN( req.getBase().getUpName() );
348 for ( int jj = 0; jj < diff; jj++ )
349 {
350 extra.add( reqUnnormalizedDn.get( referralAncestor.getDn().size() + jj ) );
351 }
352
353 ldapUrl.getDn().addAll( extra );
354 ldapUrl.setForceScopeRendering( true );
355 ldapUrl.setAttributes( req.getAttributes() );
356 ldapUrl.setScope( req.getScope().getJndiScope() );
357 referral.addLdapUrl( ldapUrl.toString() );
358 }
359
360 return referral;
361 }
362
363
364 /**
365 * Handles processing with referrals without ManageDsaIT control.
366 */
367 public void handleException( LdapSession session, InternalResultResponseRequest req, Exception e )
368 {
369 InternalLdapResult result = req.getResultResponse().getLdapResult();
370
371 /*
372 * Set the result code or guess the best option.
373 */
374 ResultCodeEnum code;
375
376 if ( e instanceof LdapException )
377 {
378 code = ( ( LdapException ) e ).getResultCode();
379 }
380 else
381 {
382 code = ResultCodeEnum.getBestEstimate( e, req.getType() );
383 }
384
385 result.setResultCode( code );
386
387 /*
388 * Setup the error message to put into the request and put entire
389 * exception into the message if we are in debug mode. Note we
390 * embed the result code name into the message.
391 */
392 String msg = code.toString() + ": failed for " + req + ": " + e.getMessage();
393 LOG.debug( msg, e );
394
395 if ( IS_DEBUG )
396 {
397 msg += ":\n" + ExceptionUtils.getStackTrace( e );
398 }
399
400 result.setErrorMessage( msg );
401
402 if ( e instanceof NamingException )
403 {
404 NamingException ne = ( NamingException ) e;
405
406 // Add the matchedDN if necessary
407 boolean setMatchedDn =
408 code == ResultCodeEnum.NO_SUCH_OBJECT ||
409 code == ResultCodeEnum.ALIAS_PROBLEM ||
410 code == ResultCodeEnum.INVALID_DN_SYNTAX ||
411 code == ResultCodeEnum.ALIAS_DEREFERENCING_PROBLEM;
412
413 if ( ( ne.getResolvedName() != null ) && setMatchedDn )
414 {
415 result.setMatchedDn( ( LdapDN ) ne.getResolvedName() );
416 }
417 }
418
419 session.getIoSession().write( req.getResultResponse() );
420 }
421
422
423 /**
424 * Handles processing without referral handling in effect: either with the
425 * ManageDsaIT control or when the entry or all of it's ancestors are non-
426 * referral entries.
427 *
428 * Implementors
429 *
430 * @param session the LDAP session under which processing occurs
431 * @param reqTargetDn the target entry DN associated with the request
432 * @param entry the target entry if it exists and has been looked up, may
433 * be null even if the entry exists, offered in case the entry is looked
434 * up to avoid repeat lookups. Implementations should check if the entry
435 * is null and attempt a lookup instead of presuming the entry does not
436 * exist.
437 * @param req the request to be handled
438 */
439 public abstract void handleIgnoringReferrals( LdapSession session, T req );
440
441
442 /**
443 * Handles processing with referrals without ManageDsaIT control.
444 */
445 public abstract void handleWithReferrals( LdapSession session, LdapDN reqTargetDn, T req ) throws NamingException;
446 }