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 */ 020package org.apache.directory.server.ldap.handlers.request; 021 022 023import static java.lang.Math.min; 024import static org.apache.directory.server.ldap.LdapServer.NO_SIZE_LIMIT; 025import static org.apache.directory.server.ldap.LdapServer.NO_TIME_LIMIT; 026 027import java.util.Collection; 028import java.util.Map; 029import java.util.concurrent.TimeUnit; 030 031import org.apache.commons.lang3.exception.ExceptionUtils; 032import org.apache.directory.api.ldap.extras.controls.syncrepl.syncRequest.SyncRequestValue; 033import org.apache.directory.api.ldap.model.constants.SchemaConstants; 034import org.apache.directory.api.ldap.model.cursor.Cursor; 035import org.apache.directory.api.ldap.model.cursor.CursorClosedException; 036import org.apache.directory.api.ldap.model.entry.Attribute; 037import org.apache.directory.api.ldap.model.entry.Entry; 038import org.apache.directory.api.ldap.model.entry.Value; 039import org.apache.directory.api.ldap.model.exception.LdapException; 040import org.apache.directory.api.ldap.model.exception.LdapOperationException; 041import org.apache.directory.api.ldap.model.exception.LdapURLEncodingException; 042import org.apache.directory.api.ldap.model.exception.OperationAbandonedException; 043import org.apache.directory.api.ldap.model.filter.EqualityNode; 044import org.apache.directory.api.ldap.model.filter.ExprNode; 045import org.apache.directory.api.ldap.model.filter.OrNode; 046import org.apache.directory.api.ldap.model.filter.PresenceNode; 047import org.apache.directory.api.ldap.model.message.Control; 048import org.apache.directory.api.ldap.model.message.LdapResult; 049import org.apache.directory.api.ldap.model.message.MessageTypeEnum; 050import org.apache.directory.api.ldap.model.message.Referral; 051import org.apache.directory.api.ldap.model.message.ReferralImpl; 052import org.apache.directory.api.ldap.model.message.Response; 053import org.apache.directory.api.ldap.model.message.ResultCodeEnum; 054import org.apache.directory.api.ldap.model.message.ResultResponseRequest; 055import org.apache.directory.api.ldap.model.message.SearchRequest; 056import org.apache.directory.api.ldap.model.message.SearchResultDone; 057import org.apache.directory.api.ldap.model.message.SearchResultEntry; 058import org.apache.directory.api.ldap.model.message.SearchResultEntryImpl; 059import org.apache.directory.api.ldap.model.message.SearchResultReference; 060import org.apache.directory.api.ldap.model.message.SearchResultReferenceImpl; 061import org.apache.directory.api.ldap.model.message.SearchScope; 062import org.apache.directory.api.ldap.model.message.controls.ManageDsaIT; 063import org.apache.directory.api.ldap.model.message.controls.PagedResults; 064import org.apache.directory.api.ldap.model.message.controls.PagedResultsImpl; 065import org.apache.directory.api.ldap.model.message.controls.PersistentSearch; 066import org.apache.directory.api.ldap.model.name.Dn; 067import org.apache.directory.api.ldap.model.schema.AttributeType; 068import org.apache.directory.api.ldap.model.url.LdapUrl; 069import org.apache.directory.api.util.Strings; 070import org.apache.directory.server.core.api.DirectoryService; 071import org.apache.directory.server.core.api.ReferralManager; 072import org.apache.directory.server.core.api.entry.ClonedServerEntry; 073import org.apache.directory.server.core.api.event.EventType; 074import org.apache.directory.server.core.api.event.NotificationCriteria; 075import org.apache.directory.server.core.api.filtering.EntryFilteringCursor; 076import org.apache.directory.server.core.api.partition.PartitionNexus; 077import org.apache.directory.server.i18n.I18n; 078import org.apache.directory.server.ldap.LdapSession; 079import org.apache.directory.server.ldap.handlers.LdapRequestHandler; 080import org.apache.directory.server.ldap.handlers.PersistentSearchListener; 081import org.apache.directory.server.ldap.handlers.SearchAbandonListener; 082import org.apache.directory.server.ldap.handlers.SearchTimeLimitingMonitor; 083import org.apache.directory.server.ldap.handlers.controls.PagedSearchContext; 084import org.apache.directory.server.ldap.replication.provider.ReplicationRequestHandler; 085import org.slf4j.Logger; 086import org.slf4j.LoggerFactory; 087 088 089/** 090 * A MessageReceived handler for processing search requests. 091 * 092 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 093 */ 094public class SearchRequestHandler extends LdapRequestHandler<SearchRequest> 095{ 096 /** The logger */ 097 private static final Logger LOG = LoggerFactory.getLogger( SearchRequestHandler.class ); 098 099 private static final Logger SEARCH_TIME_LOG = LoggerFactory.getLogger( "org.apache.directory.server.ldap.handlers.request.SEARCH_TIME_LOG" ); 100 101 /** Speedup for logs */ 102 private static final boolean IS_DEBUG = LOG.isDebugEnabled(); 103 104 /** The replication handler */ 105 protected ReplicationRequestHandler replicationReqHandler; 106 107 108 /** 109 * Constructs a new filter EqualityNode asserting that a candidate 110 * objectClass is a referral. 111 * 112 * @param session the {@link LdapSession} to construct the node for 113 * @return the {@link org.apache.directory.api.ldap.model.filter.EqualityNode} (objectClass=referral) non-normalized 114 * @throws Exception in the highly unlikely event of schema related failures 115 */ 116 private EqualityNode<String> newIsReferralEqualityNode( LdapSession session ) throws Exception 117 { 118 AttributeType objectClassAT = session.getCoreSession().getDirectoryService().getAtProvider().getObjectClass(); 119 120 return new EqualityNode<>( objectClassAT, new Value( objectClassAT, SchemaConstants.REFERRAL_OC ) ); 121 } 122 123 124 /** 125 * Handles search requests containing the persistent search decorator but 126 * delegates to doSimpleSearch() if the changesOnly parameter of the 127 * decorator is set to false. 128 * 129 * @param session the LdapSession for which this search is conducted 130 * @param req the search request containing the persistent search decorator 131 * @param psearchDecorator the persistent search decorator extracted 132 * @throws Exception if failures are encountered while searching 133 */ 134 private void handlePersistentSearch( LdapSession session, SearchRequest req, PersistentSearch psearch ) throws Exception 135 { 136 /* 137 * We want the search to complete first before we start listening to 138 * events when the decorator does NOT specify changes ONLY mode. 139 */ 140 if ( !psearch.isChangesOnly() ) 141 { 142 SearchResultDone done = doSimpleSearch( session, req ); 143 144 // ok if normal search beforehand failed somehow quickly abandon psearch 145 if ( done.getLdapResult().getResultCode() != ResultCodeEnum.SUCCESS ) 146 { 147 session.getIoSession().write( done ); 148 149 return; 150 } 151 } 152 153 if ( req.isAbandoned() ) 154 { 155 return; 156 } 157 158 // now we process entries forever as they change 159 PersistentSearchListener persistentSearchListener = new PersistentSearchListener( session, req ); 160 161 // compose notification criteria and add the listener to the event 162 // service using that notification criteria to determine which events 163 // are to be delivered to the persistent search issuing client 164 NotificationCriteria criteria = new NotificationCriteria( session.getCoreSession().getDirectoryService().getSchemaManager() ); 165 criteria.setAliasDerefMode( req.getDerefAliases() ); 166 criteria.setBase( req.getBase() ); 167 criteria.setFilter( req.getFilter() ); 168 criteria.setScope( req.getScope() ); 169 criteria.setEventMask( EventType.getEventTypes( psearch.getChangeTypes() ) ); 170 getLdapServer().getDirectoryService().getEventService().addListener( persistentSearchListener, criteria ); 171 req.addAbandonListener( new SearchAbandonListener( ldapServer, persistentSearchListener ) ); 172 } 173 174 175 /** 176 * {@inheritDoc} 177 */ 178 @Override 179 public final void handle( LdapSession session, SearchRequest req ) throws Exception 180 { 181 if ( IS_DEBUG ) 182 { 183 LOG.debug( "Handling single reply request: {}", req ); 184 } 185 186 // check first for the syncrepl search request decorator 187 if ( req.getControls().containsKey( SyncRequestValue.OID ) ) 188 { 189 handleReplication( session, req ); 190 } 191 // if we have the ManageDSAIt decorator, go directly 192 // to the handling without pre-processing the request 193 else if ( req.getControls().containsKey( ManageDsaIT.OID ) ) 194 { 195 // If the ManageDsaIT decorator is present, we will 196 // consider that the user wants to get entry which 197 // are referrals as plain entry. We have to return 198 // SearchResponseEntry elements instead of 199 // SearchResponseReference elements. 200 LOG.debug( "ManageDsaITControl detected." ); 201 handleIgnoringReferrals( session, req ); 202 } 203 else 204 { 205 // No ManageDsaIT decorator. If the found entries is a referral, 206 // we will return SearchResponseReference elements. 207 LOG.debug( "ManageDsaITControl NOT detected." ); 208 209 if ( req.getType() == MessageTypeEnum.SEARCH_REQUEST ) 210 { 211 handleWithReferrals( session, req ); 212 } 213 else 214 { 215 throw new IllegalStateException( I18n.err( I18n.ERR_685, req ) ); 216 } 217 } 218 } 219 220 221 /** 222 * Handle the replication request. 223 */ 224 private void handleReplication( LdapSession session, SearchRequest searchRequest ) throws LdapException 225 { 226 SearchResultDone done = ( SearchResultDone ) searchRequest.getResultResponse(); 227 228 if ( replicationReqHandler != null ) 229 { 230 replicationReqHandler.handleSyncRequest( session, searchRequest ); 231 } 232 else 233 { 234 // Replication is not allowed on this server. generate a error message 235 LOG.warn( "This server does not allow replication" ); 236 LdapResult result = done.getLdapResult(); 237 238 result.setDiagnosticMessage( "Replication is not allowed on this server" ); 239 result.setResultCode( ResultCodeEnum.OTHER ); 240 session.getIoSession().write( done ); 241 } 242 } 243 244 245 /** 246 * Handles a simple lookup, or a RootDSE lookup. 247 * 248 * @param session the LdapSession for which this search is conducted 249 * @param req the search request on the RootDSE 250 * @throws Exception if failures are encountered while searching 251 */ 252 private void handleLookup( LdapSession session, SearchRequest req ) throws Exception 253 { 254 Map<String, Control> controlMap = req.getControls(); 255 Control[] controls = null; 256 257 if ( controlMap != null ) 258 { 259 Collection<Control> controlValues = controlMap.values(); 260 261 controls = new Control[controlValues.size()]; 262 int pos = 0; 263 264 for ( Control control : controlMap.values() ) 265 { 266 controls[pos++] = control; 267 } 268 } 269 270 Entry entry = session.getCoreSession().lookup( 271 req.getBase(), 272 controls, 273 req.getAttributes().toArray( new String[] 274 {} ) ); 275 276 session.getIoSession().write( generateResponse( session, req, entry ) ); 277 278 // write the SearchResultDone message 279 session.getIoSession().write( req.getResultResponse() ); 280 } 281 282 283 /** 284 * Based on the server maximum time limits configured for search and the 285 * requested time limits this method determines if at all to replace the 286 * default ClosureMonitor of the result set Cursor with one that closes 287 * the Cursor when either server mandated or request mandated time limits 288 * are reached. 289 * 290 * @param req the {@link SearchRequest} issued 291 * @param session the {@link LdapSession} on which search was requested 292 * @param cursor the {@link EntryFilteringCursor} over the search results 293 */ 294 private void setTimeLimitsOnCursor( SearchRequest req, LdapSession session, 295 final Cursor<Entry> cursor ) 296 { 297 // Don't bother setting time limits for administrators 298 if ( session.getCoreSession().isAnAdministrator() && req.getTimeLimit() == NO_TIME_LIMIT ) 299 { 300 return; 301 } 302 303 /* 304 * Non administrator based searches are limited by time if the server 305 * has been configured with unlimited time and the request specifies 306 * unlimited search time 307 */ 308 if ( ldapServer.getMaxTimeLimit() == NO_TIME_LIMIT && req.getTimeLimit() == NO_TIME_LIMIT ) 309 { 310 return; 311 } 312 313 /* 314 * If the non-administrator user specifies unlimited time but the server 315 * is configured to limit the search time then we limit by the max time 316 * allowed by the configuration 317 */ 318 if ( req.getTimeLimit() == 0 ) 319 { 320 cursor.setClosureMonitor( new SearchTimeLimitingMonitor( ldapServer.getMaxTimeLimit(), TimeUnit.SECONDS ) ); 321 return; 322 } 323 324 /* 325 * If the non-administrative user specifies a time limit equal to or 326 * less than the maximum limit configured in the server then we 327 * constrain search by the amount specified in the request 328 */ 329 if ( ldapServer.getMaxTimeLimit() >= req.getTimeLimit() ) 330 { 331 cursor.setClosureMonitor( new SearchTimeLimitingMonitor( req.getTimeLimit(), TimeUnit.SECONDS ) ); 332 return; 333 } 334 335 /* 336 * Here the non-administrative user's requested time limit is greater 337 * than what the server's configured maximum limit allows so we limit 338 * the search to the configured limit 339 */ 340 cursor.setClosureMonitor( new SearchTimeLimitingMonitor( ldapServer.getMaxTimeLimit(), TimeUnit.SECONDS ) ); 341 } 342 343 344 /** 345 * Return the server size limit 346 */ 347 private long getServerSizeLimit( LdapSession session, SearchRequest request ) 348 { 349 if ( session.getCoreSession().isAnAdministrator() ) 350 { 351 if ( request.getSizeLimit() == NO_SIZE_LIMIT ) 352 { 353 return Long.MAX_VALUE; 354 } 355 else 356 { 357 return request.getSizeLimit(); 358 } 359 } 360 else 361 { 362 if ( ldapServer.getMaxSizeLimit() == NO_SIZE_LIMIT ) 363 { 364 return Long.MAX_VALUE; 365 } 366 else 367 { 368 return ldapServer.getMaxSizeLimit(); 369 } 370 } 371 } 372 373 374 private void writeResults( LdapSession session, SearchRequest req, LdapResult ldapResult, 375 Cursor<Entry> cursor, long sizeLimit ) throws Exception 376 { 377 long count = 0; 378 379 while ( ( count < sizeLimit ) && cursor.next() ) 380 { 381 // Handle closed session 382 if ( session.getIoSession().isClosing() ) 383 { 384 // The client has closed the connection 385 if ( IS_DEBUG ) 386 { 387 LOG.debug( "Request terminated for message {}, the client has closed the session", 388 req.getMessageId() ); 389 } 390 391 break; 392 } 393 394 if ( req.isAbandoned() ) 395 { 396 cursor.close( new OperationAbandonedException() ); 397 398 // The cursor has been closed by an abandon request. 399 if ( IS_DEBUG ) 400 { 401 LOG.debug( "Request terminated by an AbandonRequest for message {}", req.getMessageId() ); 402 } 403 404 break; 405 } 406 407 Entry entry = cursor.get(); 408 session.getIoSession().write( generateResponse( session, req, entry ) ); 409 410 if ( IS_DEBUG ) 411 { 412 LOG.debug( "Sending {}", entry.getDn() ); 413 } 414 415 count++; 416 } 417 418 // check if the result code is not already set 419 // the result code might be set when sort control is present 420 if ( ldapResult.getResultCode() == null ) 421 { 422 // DO NOT WRITE THE RESPONSE - JUST RETURN IT 423 ldapResult.setResultCode( ResultCodeEnum.SUCCESS ); 424 } 425 426 if ( ( count >= sizeLimit ) && ( cursor.next() ) ) 427 { 428 // We have reached the limit 429 // Move backward on the cursor to restore the previous position, as we moved forward 430 // to check if there is one more entry available 431 cursor.previous(); 432 // Special case if the user has requested more elements than the request size limit 433 ldapResult.setResultCode( ResultCodeEnum.SIZE_LIMIT_EXCEEDED ); 434 } 435 } 436 437 438 private void readPagedResults( LdapSession session, SearchRequest req, LdapResult ldapResult, 439 Cursor<Entry> cursor, long sizeLimit, int pagedLimit, PagedSearchContext pagedContext, 440 PagedResults pagedResultsControl ) throws Exception 441 { 442 req.addAbandonListener( new SearchAbandonListener( ldapServer, cursor ) ); 443 setTimeLimitsOnCursor( req, session, cursor ); 444 445 if ( IS_DEBUG ) 446 { 447 LOG.debug( "using <{},{}> for size limit", sizeLimit, pagedLimit ); 448 } 449 450 int cookieValue = 0; 451 452 int count = pagedContext.getCurrentPosition(); 453 int pageCount = 0; 454 455 while ( ( count < sizeLimit ) && ( pageCount < pagedLimit ) && cursor.next() ) 456 { 457 if ( session.getIoSession().isClosing() ) 458 { 459 break; 460 } 461 462 Entry entry = cursor.get(); 463 session.getIoSession().write( generateResponse( session, req, entry ) ); 464 count++; 465 pageCount++; 466 } 467 468 // DO NOT WRITE THE RESPONSE - JUST RETURN IT 469 ldapResult.setResultCode( ResultCodeEnum.SUCCESS ); 470 471 boolean hasMoreEntry = cursor.next(); 472 473 // We have some entry, move back to the first one, as we just moved forward 474 // to get the first entry 475 if ( hasMoreEntry ) 476 { 477 cursor.previous(); 478 } 479 480 if ( !hasMoreEntry ) 481 { 482 // That means we don't have anymore entry 483 // If we are here, it means we have returned all the entries 484 // We have to remove the cookie from the session 485 cookieValue = pagedContext.getCookieValue(); 486 PagedSearchContext psCookie = session.removePagedSearchContext( cookieValue ); 487 488 // Close the cursor if there is one 489 if ( psCookie != null ) 490 { 491 cursor = psCookie.getCursor(); 492 493 if ( cursor != null ) 494 { 495 cursor.close(); 496 } 497 } 498 499 pagedResultsControl = new PagedResultsImpl(); 500 pagedResultsControl.setCritical( true ); 501 pagedResultsControl.setSize( 0 ); 502 req.getResultResponse().addControl( pagedResultsControl ); 503 } 504 else 505 { 506 // We have reached one limit 507 508 if ( count < sizeLimit ) 509 { 510 // We stop here. We have to add a ResponseControl 511 // DO NOT WRITE THE RESPONSE - JUST RETURN IT 512 ldapResult.setResultCode( ResultCodeEnum.SUCCESS ); 513 req.getResultResponse().addControl( pagedResultsControl ); 514 515 // Stores the cursor current position 516 pagedContext.incrementCurrentPosition( pageCount ); 517 } 518 else 519 { 520 // Return an exception, close the cursor, and clean the session 521 ldapResult.setResultCode( ResultCodeEnum.SIZE_LIMIT_EXCEEDED ); 522 523 cursor.close(); 524 525 session.removePagedSearchContext( cookieValue ); 526 } 527 } 528 } 529 530 531 /** 532 * Manage the abandoned Paged Search (when paged size = 0). We have to 533 * remove the cookie and its associated cursor from the session. 534 */ 535 private SearchResultDone abandonPagedSearch( LdapSession session, SearchRequest req ) throws Exception 536 { 537 PagedResults pagedSearchControl = ( PagedResults ) req.getControls().get( PagedResults.OID ); 538 byte[] cookie = pagedSearchControl.getCookie(); 539 540 if ( !Strings.isEmpty( cookie ) ) 541 { 542 // If the cookie is not null, we have to destroy the associated 543 // cursor stored into the session (if any) 544 int cookieValue = pagedSearchControl.getCookieValue(); 545 PagedSearchContext psCookie = session.removePagedSearchContext( cookieValue ); 546 pagedSearchControl.setCookie( psCookie.getCookie() ); 547 pagedSearchControl.setSize( 0 ); 548 pagedSearchControl.setCritical( true ); 549 550 // Close the cursor 551 Cursor<Entry> cursor = psCookie.getCursor(); 552 553 if ( cursor != null ) 554 { 555 cursor.close(); 556 } 557 } 558 else 559 { 560 pagedSearchControl.setSize( 0 ); 561 pagedSearchControl.setCritical( true ); 562 } 563 564 // and return 565 // DO NOT WRITE THE RESPONSE - JUST RETURN IT 566 LdapResult ldapResult = req.getResultResponse().getLdapResult(); 567 ldapResult.setResultCode( ResultCodeEnum.SUCCESS ); 568 req.getResultResponse().addControl( pagedSearchControl ); 569 570 return ( SearchResultDone ) req.getResultResponse(); 571 } 572 573 574 /** 575 * Remove a cookie instance from the session, if it exists. 576 */ 577 private PagedSearchContext removeContext( LdapSession session, PagedSearchContext cookieInstance ) 578 { 579 if ( cookieInstance == null ) 580 { 581 return null; 582 } 583 584 int cookieValue = cookieInstance.getCookieValue(); 585 586 return session.removePagedSearchContext( cookieValue ); 587 } 588 589 590 /** 591 * Handle a Paged Search request. 592 */ 593 private SearchResultDone doPagedSearch( LdapSession session, SearchRequest req, PagedResults control ) 594 throws Exception 595 { 596 PagedResults pagedSearchControl = control; 597 PagedResults pagedResultsControl = null; 598 599 // Get the size limits 600 // Don't bother setting size limits for administrators that don't ask for it 601 long serverLimit = getServerSizeLimit( session, req ); 602 603 long requestLimit = req.getSizeLimit() == 0L ? Long.MAX_VALUE : req.getSizeLimit(); 604 long sizeLimit = min( serverLimit, requestLimit ); 605 606 int pagedLimit = pagedSearchControl.getSize(); 607 Cursor<Entry> cursor = null; 608 PagedSearchContext pagedContext = null; 609 610 // We have the following cases : 611 // 1) The SIZE is 0 and the cookie is the same than the previous one : this 612 // is a abandon request for this paged search. 613 // 2) The cookie is empty : this is a new request. If the requested 614 // size is above the serverLimit and the request limit, this is a normal 615 // search 616 // 3) The cookie is not empty and the request is the same, we return 617 // the next SIZE elements 618 // 4) The cookie is not empty, but the request is not the same : this is 619 // a new request (we have to discard the cookie and do a new search from 620 // the beginning) 621 // 5) The SIZE is above the size-limit : the request is treated as if it 622 // was a simple search 623 624 // Case 1 625 if ( pagedLimit == 0L ) 626 { 627 // An abandoned paged search 628 return abandonPagedSearch( session, req ); 629 } 630 631 // Now, depending on the cookie, we will deal with case 2, 3, 4 and 5 632 byte[] cookie = pagedSearchControl.getCookie(); 633 LdapResult ldapResult = req.getResultResponse().getLdapResult(); 634 635 if ( Strings.isEmpty( cookie ) ) 636 { 637 // No cursor : do a search. 638 cursor = session.getCoreSession().search( req ); 639 640 // Position the cursor at the beginning 641 cursor.beforeFirst(); 642 643 // This is a new search. We have a special case when the paged size 644 // is above the server size limit : in this case, we default to a 645 // standard search 646 if ( pagedLimit > sizeLimit ) 647 { 648 // Normal search : create the cursor, and set pagedControl to false 649 try 650 { 651 // And write the entries 652 writeResults( session, req, ldapResult, cursor, sizeLimit ); 653 } 654 finally 655 { 656 try 657 { 658 cursor.close(); 659 } 660 catch ( Exception e ) 661 { 662 LOG.error( I18n.err( I18n.ERR_168 ), e ); 663 } 664 } 665 666 // If we had a cookie in the session, remove it 667 removeContext( session, pagedContext ); 668 669 return ( SearchResultDone ) req.getResultResponse(); 670 } 671 else 672 { 673 // Case 2 : create the context 674 pagedContext = new PagedSearchContext( req ); 675 676 session.addPagedSearchContext( pagedContext ); 677 cookie = pagedContext.getCookie(); 678 pagedResultsControl = new PagedResultsImpl(); 679 pagedResultsControl.setCookie( cookie ); 680 pagedResultsControl.setSize( 0 ); 681 pagedResultsControl.setCritical( true ); 682 683 // And stores the cursor into the session 684 pagedContext.setCursor( cursor ); 685 } 686 } 687 else 688 { 689 // We have a cookie 690 // Either case 3, 4 or 5 691 int cookieValue = pagedSearchControl.getCookieValue(); 692 pagedContext = session.getPagedSearchContext( cookieValue ); 693 694 if ( pagedContext == null ) 695 { 696 // We didn't found the cookie into the session : it must be invalid 697 // send an error. 698 ldapResult.setDiagnosticMessage( "Invalid cookie for this PagedSearch request." ); 699 ldapResult.setResultCode( ResultCodeEnum.UNWILLING_TO_PERFORM ); 700 701 return ( SearchResultDone ) req.getResultResponse(); 702 } 703 704 if ( pagedContext.hasSameRequest( req, session ) ) 705 { 706 // Case 3 : continue the search 707 cursor = pagedContext.getCursor(); 708 709 // get the cookie 710 cookie = pagedContext.getCookie(); 711 pagedResultsControl = new PagedResultsImpl(); 712 pagedResultsControl.setCookie( cookie ); 713 pagedResultsControl.setSize( 0 ); 714 pagedResultsControl.setCritical( true ); 715 716 } 717 else 718 { 719 // case 2 : create a new cursor 720 // We have to close the cursor 721 cursor = pagedContext.getCursor(); 722 723 if ( cursor != null ) 724 { 725 cursor.close(); 726 } 727 728 // Now create a new context and stores it into the session 729 pagedContext = new PagedSearchContext( req ); 730 731 session.addPagedSearchContext( pagedContext ); 732 733 cookie = pagedContext.getCookie(); 734 pagedResultsControl = new PagedResultsImpl(); 735 pagedResultsControl.setCookie( cookie ); 736 pagedResultsControl.setSize( 0 ); 737 pagedResultsControl.setCritical( true ); 738 } 739 } 740 741 // Now, do the real search 742 /* 743 * Iterate through all search results building and sending back responses 744 * for each search result returned. 745 */ 746 try 747 { 748 readPagedResults( session, req, ldapResult, cursor, sizeLimit, pagedLimit, pagedContext, 749 pagedResultsControl ); 750 } 751 catch ( Exception e ) 752 { 753 if ( cursor != null ) 754 { 755 try 756 { 757 cursor.close(); 758 } 759 catch ( Exception ne ) 760 { 761 LOG.error( I18n.err( I18n.ERR_168 ), ne ); 762 } 763 } 764 } 765 766 return ( SearchResultDone ) req.getResultResponse(); 767 } 768 769 770 /** 771 * Conducts a simple search across the result set returning each entry 772 * back except for the search response done. This is calculated but not 773 * returned so the persistent search mechanism can leverage this method 774 * along with standard search.<br> 775 * <br> 776 * @param session the LDAP session object for this request 777 * @param req the search request 778 * @return the result done 779 * @throws Exception if there are failures while processing the request 780 */ 781 private SearchResultDone doSimpleSearch( LdapSession session, SearchRequest req ) throws Exception 782 { 783 LdapResult ldapResult = req.getResultResponse().getLdapResult(); 784 785 // Check if we are using the Paged Search Control 786 Object control = req.getControls().get( PagedResults.OID ); 787 788 if ( control != null ) 789 { 790 // Let's deal with the pagedControl 791 return doPagedSearch( session, req, ( PagedResults ) control ); 792 } 793 794 // A normal search 795 // Check that we have a cursor or not. 796 // No cursor : do a search. 797 Cursor<Entry> cursor = session.getCoreSession().search( req ); 798 799 // register the request in the session 800 session.registerSearchRequest( req, cursor ); 801 802 // Position the cursor at the beginning 803 cursor.beforeFirst(); 804 805 /* 806 * Iterate through all search results building and sending back responses 807 * for each search result returned. 808 */ 809 try 810 { 811 // Get the size limits 812 // Don't bother setting size limits for administrators that don't ask for it 813 long serverLimit = getServerSizeLimit( session, req ); 814 815 long requestLimit = req.getSizeLimit() == 0L ? Long.MAX_VALUE : req.getSizeLimit(); 816 817 req.addAbandonListener( new SearchAbandonListener( ldapServer, cursor ) ); 818 setTimeLimitsOnCursor( req, session, cursor ); 819 820 if ( IS_DEBUG ) 821 { 822 LOG.debug( "using <{},{}> for size limit", requestLimit, serverLimit ); 823 } 824 825 long sizeLimit = min( requestLimit, serverLimit ); 826 827 writeResults( session, req, ldapResult, cursor, sizeLimit ); 828 } 829 finally 830 { 831 if ( !cursor.isClosed() ) 832 { 833 try 834 { 835 cursor.close(); 836 } 837 catch ( Exception e ) 838 { 839 LOG.error( I18n.err( I18n.ERR_168 ), e ); 840 } 841 } 842 } 843 844 return ( SearchResultDone ) req.getResultResponse(); 845 } 846 847 848 /** 849 * Generates a response for an entry retrieved from the server core based 850 * on the nature of the request with respect to referral handling. This 851 * method will either generate a SearchResponseEntry or a 852 * SearchResponseReference depending on if the entry is a referral or if 853 * the ManageDSAITControl has been enabled. 854 * 855 * @param req the search request 856 * @param entry the entry to be handled 857 * @return the response for the entry 858 * @throws Exception if there are problems in generating the response 859 */ 860 private Response generateResponse( LdapSession session, SearchRequest req, Entry entry ) throws Exception 861 { 862 Attribute ref = entry.get( SchemaConstants.REF_AT ); 863 boolean hasManageDsaItControl = req.getControls().containsKey( ManageDsaIT.OID ); 864 865 if ( ( ref != null ) && !hasManageDsaItControl ) 866 { 867 // The entry is a referral. 868 SearchResultReference respRef; 869 respRef = new SearchResultReferenceImpl( req.getMessageId() ); 870 respRef.setReferral( new ReferralImpl() ); 871 872 for ( Value val : ref ) 873 { 874 String url = val.getString(); 875 876 if ( !url.startsWith( "ldap" ) ) 877 { 878 respRef.getReferral().addLdapUrl( url ); 879 } 880 881 LdapUrl ldapUrl = null; 882 883 try 884 { 885 ldapUrl = new LdapUrl( url ); 886 ldapUrl.setForceScopeRendering( true ); 887 888 switch ( req.getScope() ) 889 { 890 case SUBTREE: 891 ldapUrl.setScope( SearchScope.SUBTREE.getScope() ); 892 break; 893 894 case ONELEVEL: // one level here is object level on remote server 895 ldapUrl.setScope( SearchScope.OBJECT.getScope() ); 896 break; 897 898 default: 899 ldapUrl.setScope( SearchScope.OBJECT.getScope() ); 900 } 901 } 902 catch ( LdapURLEncodingException e ) 903 { 904 LOG.error( I18n.err( I18n.ERR_165, url, entry ) ); 905 ldapUrl = new LdapUrl(); 906 } 907 908 respRef.getReferral().addLdapUrl( ldapUrl.toString() ); 909 } 910 911 return respRef; 912 } 913 else 914 { 915 // The entry is not a referral, or the ManageDsaIt decorator is set 916 SearchResultEntry respEntry; 917 respEntry = new SearchResultEntryImpl( req.getMessageId() ); 918 respEntry.setEntry( entry ); 919 respEntry.setObjectName( entry.getDn() ); 920 921 // Filter the userPassword if the server mandate to do so 922 if ( session.getCoreSession().getDirectoryService().isPasswordHidden() ) 923 { 924 // Remove the userPassord attribute from the entry. 925 respEntry.getEntry().removeAttributes( SchemaConstants.USER_PASSWORD_AT ); 926 } 927 928 return respEntry; 929 } 930 } 931 932 933 /** 934 * Alters the filter expression based on the presence of the 935 * ManageDsaIT decorator. If the decorator is not present, the search 936 * filter will be altered to become a disjunction with two terms. 937 * The first term is the original filter. The second term is a 938 * (objectClass=referral) assertion. When OR'd together these will 939 * make sure we get all referrals so we can process continuations 940 * properly without having the filter remove them from the result 941 * set. 942 * 943 * NOTE: original filter is first since most entries are not referrals 944 * so it has a higher probability on average of accepting and shorting 945 * evaluation before having to waste cycles trying to evaluate if the 946 * entry is a referral. 947 * 948 * @param session the session to use to construct the filter (schema access) 949 * @param req the request to get the original filter from 950 * @throws Exception if there are schema access problems 951 */ 952 private void modifyFilter( LdapSession session, SearchRequest req ) throws Exception 953 { 954 if ( req.hasControl( ManageDsaIT.OID ) ) 955 { 956 return; 957 } 958 959 /* 960 * Most of the time the search filter is just (objectClass=*) and if 961 * this is the case then there's no reason at all to OR this with an 962 * (objectClass=referral). If we detect this case then we leave it 963 * as is to represent the OR condition: 964 * 965 * (| (objectClass=referral)(objectClass=*)) == (objectClass=*) 966 */ 967 if ( req.getFilter() instanceof PresenceNode ) 968 { 969 PresenceNode presenceNode = ( PresenceNode ) req.getFilter(); 970 971 if ( presenceNode.isSchemaAware() ) 972 { 973 AttributeType attributeType = presenceNode.getAttributeType(); 974 975 AttributeType objectClassAT = session.getCoreSession().getDirectoryService().getAtProvider().getObjectClass(); 976 if ( attributeType.equals( objectClassAT ) ) 977 { 978 return; 979 } 980 } 981 else 982 { 983 String attribute = presenceNode.getAttribute(); 984 985 if ( attribute.equalsIgnoreCase( SchemaConstants.OBJECT_CLASS_AT ) 986 || attribute.equalsIgnoreCase( SchemaConstants.OBJECT_CLASS_AT_OID ) ) 987 { 988 return; 989 } 990 } 991 } 992 993 /* 994 * Do not add the OR'd (objectClass=referral) expression if the user 995 * searches for the subSchemaSubEntry as the SchemaIntercepter can't 996 * handle an OR'd filter. 997 */ 998 if ( isSubSchemaSubEntrySearch( session, req ) ) 999 { 1000 return; 1001 } 1002 1003 // using varags to add two expressions to an OR node 1004 req.setFilter( new OrNode( req.getFilter(), newIsReferralEqualityNode( session ) ) ); 1005 } 1006 1007 1008 /** 1009 * Handles the RootDSE and lookups searches 1010 */ 1011 private boolean handleLookupAndRootDse( LdapSession session, SearchRequest req ) throws Exception 1012 { 1013 boolean isBaseScope = req.getScope() == SearchScope.OBJECT; 1014 boolean isObjectClassFilter = false; 1015 1016 if ( req.getFilter() instanceof PresenceNode ) 1017 { 1018 ExprNode filter = req.getFilter(); 1019 1020 if ( filter.isSchemaAware() ) 1021 { 1022 AttributeType attributeType = ( ( PresenceNode ) req.getFilter() ).getAttributeType(); 1023 isObjectClassFilter = attributeType.equals( session.getCoreSession().getDirectoryService() 1024 .getAtProvider().getObjectClass() ); 1025 } 1026 else 1027 { 1028 String attribute = ( ( PresenceNode ) req.getFilter() ).getAttribute(); 1029 isObjectClassFilter = attribute.equalsIgnoreCase( SchemaConstants.OBJECT_CLASS_AT ) 1030 || attribute.equals( SchemaConstants.OBJECT_CLASS_AT_OID ); 1031 } 1032 } 1033 1034 /* 1035 if ( isBaseScope && isObjectClassFilter ) 1036 { 1037 // This is a lookup 1038 handleLookup( session, req ); 1039 1040 return true; 1041 } 1042 else 1043 { 1044 // a standard search 1045 return false; 1046 } 1047 */ 1048 boolean isBaseIsRoot = req.getBase().isEmpty(); 1049 1050 if ( isBaseScope && isObjectClassFilter ) 1051 { 1052 if ( isBaseIsRoot ) 1053 { 1054 // This is a rootDse lookup 1055 handleLookup( session, req ); 1056 1057 return true; 1058 } 1059 else 1060 { 1061 // This is a lookup 1062 //handleLookup( session, req ); 1063 1064 return false; 1065 } 1066 } 1067 else 1068 { 1069 // a standard search 1070 return false; 1071 } 1072 } 1073 1074 1075 /** 1076 * Main message handing method for search requests. This will be called 1077 * even if the ManageDsaIT decorator is present because the super class does 1078 * not know that the search operation has more to do after finding the 1079 * base. The call to this means that finding the base can ignore 1080 * referrals. 1081 * 1082 * @param session the associated session 1083 * @param req the received SearchRequest 1084 */ 1085 private void handleIgnoringReferrals( LdapSession session, SearchRequest req ) 1086 { 1087 if ( IS_DEBUG ) 1088 { 1089 LOG.debug( "Message received: {}", req ); 1090 } 1091 1092 // A flag set if we have a persistent search 1093 boolean isPersistentSearch = false; 1094 1095 // A flag set when we've got an exception while processing a 1096 // persistent search 1097 boolean persistentSearchException = false; 1098 1099 // add the search request to the registry of outstanding requests for this session 1100 session.registerOutstandingRequest( req ); 1101 1102 try 1103 { 1104 // =============================================================== 1105 // Handle search in rootDSE and simple lookups differently. 1106 // =============================================================== 1107 if ( handleLookupAndRootDse( session, req ) ) 1108 { 1109 return; 1110 } 1111 1112 // modify the filter to affect continuation support 1113 modifyFilter( session, req ); 1114 1115 // =============================================================== 1116 // Handle psearch differently 1117 // =============================================================== 1118 1119 PersistentSearch psearch = ( PersistentSearch ) req.getControls().get( PersistentSearch.OID ); 1120 1121 if ( psearch != null ) 1122 { 1123 // Set the flag to avoid the request being removed 1124 // from the session 1125 isPersistentSearch = true; 1126 1127 handlePersistentSearch( session, req, psearch ); 1128 1129 return; 1130 } 1131 1132 // =============================================================== 1133 // Handle regular search requests from here down 1134 // =============================================================== 1135 1136 boolean isLogSearchTime = SEARCH_TIME_LOG.isDebugEnabled(); 1137 1138 long t0 = 0; 1139 String filter = null; 1140 1141 if ( isLogSearchTime ) 1142 { 1143 t0 = System.nanoTime(); 1144 filter = req.getFilter().toString(); 1145 } 1146 1147 SearchResultDone done = doSimpleSearch( session, req ); 1148 session.getIoSession().write( done ); 1149 1150 if ( isLogSearchTime ) 1151 { 1152 long t1 = System.nanoTime(); 1153 SEARCH_TIME_LOG.debug( "Search with filter {} took {}ms. Filter with assigned counts is {}", filter, 1154 ( ( t1 - t0 ) / 1000000 ), req.getFilter() ); 1155 } 1156 } 1157 catch ( Exception e ) 1158 { 1159 /* 1160 * From RFC 2251 Section 4.11: 1161 * 1162 * In the event that a server receives an Abandon Request on a Search 1163 * operation in the midst of transmitting responses to the Search, that 1164 * server MUST cease transmitting entry responses to the abandoned 1165 * request immediately, and MUST NOT send the SearchResultDone. Of 1166 * course, the server MUST ensure that only properly encoded LDAPMessage 1167 * PDUs are transmitted. 1168 * 1169 * SO DON'T SEND BACK ANYTHING!!!!! 1170 */ 1171 if ( e instanceof OperationAbandonedException ) 1172 { 1173 return; 1174 } 1175 1176 // If it was a persistent search and if we had an exception, 1177 // we set the flag to remove the request from the session 1178 if ( isPersistentSearch ) 1179 { 1180 persistentSearchException = true; 1181 } 1182 1183 handleException( session, req, e ); 1184 } 1185 finally 1186 { 1187 1188 // remove the request from the session, except if 1189 // we didn't got an exception for a Persistent search 1190 if ( !isPersistentSearch || persistentSearchException ) 1191 { 1192 session.unregisterOutstandingRequest( req ); 1193 } 1194 } 1195 } 1196 1197 1198 /** 1199 * Handles processing with referrals without ManageDsaIT decorator. 1200 */ 1201 private void handleWithReferrals( LdapSession session, SearchRequest req ) throws LdapException 1202 { 1203 LdapResult result = req.getResultResponse().getLdapResult(); 1204 Entry entry = null; 1205 boolean isReferral = false; 1206 boolean isparentReferral = false; 1207 DirectoryService directoryService = session.getCoreSession().getDirectoryService(); 1208 ReferralManager referralManager = directoryService.getReferralManager(); 1209 Dn reqTargetDn = req.getBase(); 1210 1211 if ( !reqTargetDn.isSchemaAware() ) 1212 { 1213 reqTargetDn = new Dn( directoryService.getSchemaManager(), reqTargetDn ); 1214 req.setBase( reqTargetDn ); 1215 } 1216 1217 // Check if the entry itself is a referral 1218 referralManager.lockRead(); 1219 1220 try 1221 { 1222 isReferral = referralManager.isReferral( reqTargetDn ); 1223 1224 if ( !isReferral ) 1225 { 1226 // Check if the entry has a parent which is a referral 1227 isparentReferral = referralManager.hasParentReferral( reqTargetDn ); 1228 } 1229 } 1230 finally 1231 { 1232 // Unlock the ReferralManager 1233 referralManager.unlock(); 1234 } 1235 1236 if ( !isReferral && !isparentReferral ) 1237 { 1238 // This is not a referral and it does not have a parent which 1239 // is a referral : standard case, just deal with the request 1240 if ( IS_DEBUG ) 1241 { 1242 LOG.debug( "Entry {} is NOT a referral.", reqTargetDn ); 1243 } 1244 1245 handleIgnoringReferrals( session, req ); 1246 } 1247 else 1248 { 1249 // ------------------------------------------------------------------- 1250 // Lookup Entry 1251 // ------------------------------------------------------------------- 1252 1253 // try to lookup the entry but ignore exceptions when it does not 1254 // exist since entry may not exist but may have an ancestor that is a 1255 // referral - would rather attempt a lookup that fails then do check 1256 // for existence than have to do another lookup to get entry info 1257 try 1258 { 1259 entry = session.getCoreSession().lookup( reqTargetDn ); 1260 1261 if ( IS_DEBUG ) 1262 { 1263 LOG.debug( "Entry for {} was found: ", reqTargetDn, entry ); 1264 } 1265 } 1266 catch ( LdapException e ) 1267 { 1268 /* ignore */ 1269 LOG.debug( "Entry for {} not found.", reqTargetDn ); 1270 } 1271 catch ( Exception e ) 1272 { 1273 /* serious and needs handling */ 1274 handleException( session, req, e ); 1275 1276 return; 1277 } 1278 1279 // ------------------------------------------------------------------- 1280 // Handle Existing Entry 1281 // ------------------------------------------------------------------- 1282 1283 if ( entry != null ) 1284 { 1285 try 1286 { 1287 if ( IS_DEBUG ) 1288 { 1289 LOG.debug( "Entry is a referral: {}", entry ); 1290 } 1291 1292 handleReferralEntryForSearch( session, req, entry ); 1293 } 1294 catch ( Exception e ) 1295 { 1296 handleException( session, req, e ); 1297 } 1298 } 1299 1300 // ------------------------------------------------------------------- 1301 // Handle Non-existing Entry 1302 // ------------------------------------------------------------------- 1303 1304 // if the entry is null we still have to check for a referral ancestor 1305 // also the referrals need to be adjusted based on the ancestor's ref 1306 // values to yield the correct path to the entry in the target DSAs 1307 1308 else 1309 { 1310 // The entry is null : it has a parent referral. 1311 Entry referralAncestor = null; 1312 1313 try 1314 { 1315 referralAncestor = getFarthestReferralAncestor( session, reqTargetDn ); 1316 } 1317 catch ( Exception e ) 1318 { 1319 handleException( session, req, e ); 1320 1321 return; 1322 } 1323 1324 if ( referralAncestor == null ) 1325 { 1326 result.setDiagnosticMessage( "Entry not found." ); 1327 result.setResultCode( ResultCodeEnum.NO_SUCH_OBJECT ); 1328 session.getIoSession().write( req.getResultResponse() ); 1329 1330 return; 1331 } 1332 1333 // if we get here then we have a valid referral ancestor 1334 try 1335 { 1336 Referral referral = getReferralOnAncestorForSearch( session, req, referralAncestor ); 1337 1338 result.setResultCode( ResultCodeEnum.REFERRAL ); 1339 result.setReferral( referral ); 1340 session.getIoSession().write( req.getResultResponse() ); 1341 } 1342 catch ( Exception e ) 1343 { 1344 handleException( session, req, e ); 1345 } 1346 } 1347 } 1348 } 1349 1350 1351 /** 1352 * Handles processing a referral response on a target entry which is a 1353 * referral. It will for any request that returns an LdapResult in it's 1354 * response. 1355 * 1356 * @param session the session to use for processing 1357 * @param req the request 1358 * @param entry the entry associated with the request 1359 */ 1360 private void handleReferralEntryForSearch( LdapSession session, SearchRequest req, Entry entry ) 1361 throws Exception 1362 { 1363 LdapResult result = req.getResultResponse().getLdapResult(); 1364 ReferralImpl referral = new ReferralImpl(); 1365 result.setReferral( referral ); 1366 result.setResultCode( ResultCodeEnum.REFERRAL ); 1367 result.setDiagnosticMessage( "Encountered referral attempting to handle request." ); 1368 result.setMatchedDn( req.getBase() ); 1369 1370 Attribute refAttr = ( ( ClonedServerEntry ) entry ).getOriginalEntry().get( SchemaConstants.REF_AT ); 1371 1372 for ( Value refval : refAttr ) 1373 { 1374 String refstr = refval.getString(); 1375 1376 // need to add non-ldap URLs as-is 1377 if ( !refstr.startsWith( "ldap" ) ) 1378 { 1379 referral.addLdapUrl( refstr ); 1380 continue; 1381 } 1382 1383 // parse the ref value and normalize the Dn 1384 LdapUrl ldapUrl = null; 1385 1386 try 1387 { 1388 ldapUrl = new LdapUrl( refstr ); 1389 } 1390 catch ( LdapURLEncodingException e ) 1391 { 1392 LOG.error( I18n.err( I18n.ERR_165, refstr, entry ) ); 1393 continue; 1394 } 1395 1396 ldapUrl.setForceScopeRendering( true ); 1397 ldapUrl.setAttributes( req.getAttributes() ); 1398 ldapUrl.setScope( req.getScope().getScope() ); 1399 referral.addLdapUrl( ldapUrl.toString() ); 1400 } 1401 1402 session.getIoSession().write( req.getResultResponse() ); 1403 } 1404 1405 1406 /** 1407 * <p> 1408 * Determines if a search request is a subSchemaSubEntry search. 1409 * </p> 1410 * <p> 1411 * It is a schema search if: 1412 * - the base Dn is the Dn of the subSchemaSubEntry of the root DSE 1413 * - and the scope is BASE OBJECT 1414 * - and the filter is (objectClass=subschema) 1415 * (RFC 4512, 4.4,) 1416 * </p> 1417 * <p> 1418 * However in this method we only check the first condition to avoid 1419 * performance issues. 1420 * </p> 1421 * 1422 * @param session the LDAP session 1423 * @param req the request issued 1424 * 1425 * @return true if the search is on the subSchemaSubEntry, false otherwise 1426 * 1427 * @throws Exception the exception 1428 */ 1429 private boolean isSubSchemaSubEntrySearch( LdapSession session, SearchRequest req ) throws Exception 1430 { 1431 Dn base = req.getBase(); 1432 1433 DirectoryService ds = session.getCoreSession().getDirectoryService(); 1434 PartitionNexus nexus = ds.getPartitionNexus(); 1435 1436 Value subschemaSubentry = nexus.getRootDseValue( ds.getAtProvider().getSubschemaSubentry() ); 1437 Dn subschemaSubentryDn = ds.getDnFactory().create( subschemaSubentry.getString() ); 1438 1439 return subschemaSubentryDn.equals( base ); 1440 } 1441 1442 1443 /** 1444 * Handles processing with referrals without ManageDsaIT decorator and with 1445 * an ancestor that is a referral. The original entry was not found and 1446 * the walk of the ancestry returned a referral. 1447 * 1448 * @param session The LdapSession in use 1449 * @param req The SearchRequest 1450 * @param referralAncestor the farthest referral ancestor of the missing 1451 * entry 1452 * @return The found referral 1453 * @throws LdapException If we weren't able to retrieve the referral 1454 */ 1455 public Referral getReferralOnAncestorForSearch( LdapSession session, SearchRequest req, 1456 Entry referralAncestor ) throws LdapException 1457 { 1458 if ( IS_DEBUG ) 1459 { 1460 LOG.debug( "Inside getReferralOnAncestor()" ); 1461 } 1462 1463 Attribute refAttr = ( ( ClonedServerEntry ) referralAncestor ).getOriginalEntry().get( SchemaConstants.REF_AT ); 1464 Referral referral = new ReferralImpl(); 1465 1466 for ( Value value : refAttr ) 1467 { 1468 String ref = value.getString(); 1469 1470 if ( IS_DEBUG ) 1471 { 1472 LOG.debug( "Calculating LdapURL for referrence value {}", ref ); 1473 } 1474 1475 // need to add non-ldap URLs as-is 1476 if ( !ref.startsWith( "ldap" ) ) 1477 { 1478 referral.addLdapUrl( ref ); 1479 continue; 1480 } 1481 1482 // Parse the ref value 1483 LdapUrl ldapUrl = null; 1484 1485 try 1486 { 1487 ldapUrl = new LdapUrl( ref ); 1488 } 1489 catch ( LdapURLEncodingException e ) 1490 { 1491 LOG.error( I18n.err( I18n.ERR_165, ref, referralAncestor ) ); 1492 ldapUrl = new LdapUrl(); 1493 } 1494 1495 // Normalize the Dn to check for same dn 1496 Dn urlDn = new Dn( session.getCoreSession().getDirectoryService() 1497 .getSchemaManager(), ldapUrl.getDn().getName() ); 1498 1499 if ( urlDn.equals( req.getBase() ) ) 1500 { 1501 ldapUrl.setForceScopeRendering( true ); 1502 ldapUrl.setAttributes( req.getAttributes() ); 1503 ldapUrl.setScope( req.getScope().getScope() ); 1504 referral.addLdapUrl( ldapUrl.toString() ); 1505 continue; 1506 } 1507 1508 /* 1509 * If we get here then the Dn of the referral was not the same as the 1510 * Dn of the ref LDAP URL. We must calculate the remaining (difference) 1511 * name past the farthest referral Dn which the target name extends. 1512 */ 1513 Dn suffix = req.getBase().getDescendantOf( referralAncestor.getDn() ); 1514 Dn refDn = urlDn.add( suffix ); 1515 1516 ldapUrl.setDn( refDn ); 1517 ldapUrl.setForceScopeRendering( true ); 1518 ldapUrl.setAttributes( req.getAttributes() ); 1519 ldapUrl.setScope( req.getScope().getScope() ); 1520 referral.addLdapUrl( ldapUrl.toString() ); 1521 } 1522 1523 return referral; 1524 } 1525 1526 1527 /** 1528 * Handles processing with referrals without ManageDsaIT decorator and with 1529 * an ancestor that is a referral. The original entry was not found and 1530 * the walk of the ancestry returned a referral. 1531 * 1532 * @param session The LdapSession in use 1533 * @param reqTargetDn the request target Dn 1534 * @param req The SearchRequest 1535 * @param referralAncestor the farthest referral ancestor of the missing 1536 * entry 1537 * @return The found referral 1538 * @throws LdapException If we weren't able to retrieve the ancestor 1539 */ 1540 public Referral getReferralOnAncestor( LdapSession session, Dn reqTargetDn, SearchRequest req, 1541 Entry referralAncestor ) throws LdapException 1542 { 1543 if ( IS_DEBUG ) 1544 { 1545 LOG.debug( "Inside getReferralOnAncestor()" ); 1546 } 1547 1548 Attribute refAttr = ( ( ClonedServerEntry ) referralAncestor ).getOriginalEntry().get( SchemaConstants.REF_AT ); 1549 Referral referral = new ReferralImpl(); 1550 1551 for ( Value value : refAttr ) 1552 { 1553 String ref = value.getString(); 1554 1555 if ( IS_DEBUG ) 1556 { 1557 LOG.debug( "Calculating LdapURL for referrence value {}", ref ); 1558 } 1559 1560 // need to add non-ldap URLs as-is 1561 if ( !ref.startsWith( "ldap" ) ) 1562 { 1563 referral.addLdapUrl( ref ); 1564 continue; 1565 } 1566 1567 // parse the ref value and normalize the Dn 1568 LdapUrl ldapUrl = null; 1569 1570 try 1571 { 1572 ldapUrl = new LdapUrl( ref ); 1573 } 1574 catch ( LdapURLEncodingException e ) 1575 { 1576 LOG.error( I18n.err( I18n.ERR_165, ref, referralAncestor ) ); 1577 ldapUrl = new LdapUrl(); 1578 } 1579 1580 Dn urlDn = new Dn( session.getCoreSession().getDirectoryService() 1581 .getSchemaManager(), ldapUrl.getDn().getName() ); 1582 1583 if ( urlDn.equals( referralAncestor.getDn() ) ) 1584 { 1585 // according to the protocol there is no need for the dn since it is the same as this request 1586 StringBuilder buf = new StringBuilder(); 1587 buf.append( ldapUrl.getScheme() ); 1588 buf.append( ldapUrl.getHost() ); 1589 1590 if ( ldapUrl.getPort() > 0 ) 1591 { 1592 buf.append( ":" ); 1593 buf.append( ldapUrl.getPort() ); 1594 } 1595 1596 referral.addLdapUrl( buf.toString() ); 1597 continue; 1598 } 1599 1600 /* 1601 * If we get here then the Dn of the referral was not the same as the 1602 * Dn of the ref LDAP URL. We must calculate the remaining (difference) 1603 * name past the farthest referral Dn which the target name extends. 1604 */ 1605 Dn suffix = req.getBase().getDescendantOf( referralAncestor.getDn() ); 1606 urlDn = urlDn.add( suffix ); 1607 1608 StringBuilder buf = new StringBuilder(); 1609 buf.append( ldapUrl.getScheme() ); 1610 buf.append( ldapUrl.getHost() ); 1611 1612 if ( ldapUrl.getPort() > 0 ) 1613 { 1614 buf.append( ":" ); 1615 buf.append( ldapUrl.getPort() ); 1616 } 1617 1618 buf.append( "/" ); 1619 buf.append( LdapUrl.urlEncode( urlDn.getName(), false ) ); 1620 referral.addLdapUrl( buf.toString() ); 1621 } 1622 1623 return referral; 1624 } 1625 1626 1627 /** 1628 * Handles processing with referrals without ManageDsaIT decorator. 1629 */ 1630 public void handleException( LdapSession session, ResultResponseRequest req, Exception e ) 1631 { 1632 SearchResultDone done = ( SearchResultDone ) req.getResultResponse(); 1633 LdapResult result = done.getLdapResult(); 1634 Exception cause = null; 1635 1636 /* 1637 * Set the result code or guess the best option. 1638 */ 1639 ResultCodeEnum code; 1640 1641 if ( e instanceof CursorClosedException ) 1642 { 1643 cause = ( Exception ) ( ( CursorClosedException ) e ).getCause(); 1644 1645 if ( cause == null ) 1646 { 1647 cause = e; 1648 } 1649 } 1650 else 1651 { 1652 cause = e; 1653 } 1654 1655 if ( cause instanceof LdapOperationException ) 1656 { 1657 code = ( ( LdapOperationException ) cause ).getResultCode(); 1658 } 1659 else 1660 { 1661 code = ResultCodeEnum.getBestEstimate( cause, req.getType() ); 1662 } 1663 1664 result.setResultCode( code ); 1665 1666 /* 1667 * Setup the error message to put into the request and put entire 1668 * exception into the message if we are in debug mode. Note we 1669 * embed the result code name into the message. 1670 */ 1671 String msg = code.toString() + ": failed for " + req + ": " + cause.getLocalizedMessage(); 1672 1673 if ( IS_DEBUG ) 1674 { 1675 LOG.debug( msg, cause ); 1676 msg += ":\n" + ExceptionUtils.getStackTrace( cause ); 1677 } 1678 1679 result.setDiagnosticMessage( msg ); 1680 1681 if ( cause instanceof LdapOperationException ) 1682 { 1683 LdapOperationException ne = ( LdapOperationException ) cause; 1684 1685 // Add the matchedDN if necessary 1686 boolean setMatchedDn = code == ResultCodeEnum.NO_SUCH_OBJECT || code == ResultCodeEnum.ALIAS_PROBLEM 1687 || code == ResultCodeEnum.INVALID_DN_SYNTAX || code == ResultCodeEnum.ALIAS_DEREFERENCING_PROBLEM; 1688 1689 if ( ( ne.getResolvedDn() != null ) && setMatchedDn ) 1690 { 1691 result.setMatchedDn( ne.getResolvedDn() ); 1692 } 1693 } 1694 1695 session.getIoSession().write( done ); 1696 } 1697 1698 1699 /** 1700 * Searches up the ancestry of a Dn searching for the farthest referral 1701 * ancestor. This is required to properly handle referrals. Note that 1702 * this function is quite costly since it attempts to lookup all the 1703 * ancestors up the hierarchy just to see if they represent referrals. 1704 * Techniques can be employed later to improve this performance hit by 1705 * having an intelligent referral cache. 1706 * 1707 * @param session The LdapSession in use 1708 * @param target the base Dn 1709 * @return the farthest referral ancestor or null 1710 */ 1711 // This will suppress PMD.EmptyCatchBlock warnings in this method 1712 public static final Entry getFarthestReferralAncestor( LdapSession session, Dn target ) 1713 { 1714 Entry entry; 1715 Entry farthestReferralAncestor = null; 1716 Dn dn = target; 1717 1718 dn = dn.getParent(); 1719 1720 while ( !dn.isEmpty() ) 1721 { 1722 if ( IS_DEBUG ) 1723 { 1724 LOG.debug( "Walking ancestors of {} to find referrals.", dn ); 1725 } 1726 1727 try 1728 { 1729 entry = session.getCoreSession().lookup( dn ); 1730 1731 boolean isReferral = ( ( ClonedServerEntry ) entry ).getOriginalEntry().contains( 1732 SchemaConstants.OBJECT_CLASS_AT, SchemaConstants.REFERRAL_OC ); 1733 1734 if ( isReferral ) 1735 { 1736 farthestReferralAncestor = entry; 1737 } 1738 1739 dn = dn.getParent(); 1740 } 1741 catch ( LdapException e ) 1742 { 1743 if ( IS_DEBUG ) 1744 { 1745 LOG.debug( "Entry for {} not found.", dn ); 1746 } 1747 1748 // update the Dn as we strip last component 1749 dn = dn.getParent(); 1750 } 1751 } 1752 1753 return farthestReferralAncestor; 1754 } 1755 1756 1757 /** 1758 * Install the replication handler when it's allowed by this server 1759 * @param replicationReqHandler The replication handler provider 1760 */ 1761 public void setReplicationReqHandler( ReplicationRequestHandler replicationReqHandler ) 1762 { 1763 this.replicationReqHandler = replicationReqHandler; 1764 } 1765}