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 }