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