001/* 002 * CDDL HEADER START 003 * 004 * The contents of this file are subject to the terms of the 005 * Common Development and Distribution License, Version 1.0 only 006 * (the "License"). You may not use this file except in compliance 007 * with the License. 008 * 009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt 010 * or http://forgerock.org/license/CDDLv1.0.html. 011 * See the License for the specific language governing permissions 012 * and limitations under the License. 013 * 014 * When distributing Covered Code, include this CDDL HEADER in each 015 * file and include the License file at legal-notices/CDDLv1_0.txt. 016 * If applicable, add the following below this CDDL HEADER, with the 017 * fields enclosed by brackets "[]" replaced with your own identifying 018 * information: 019 * Portions Copyright [yyyy] [name of copyright owner] 020 * 021 * CDDL HEADER END 022 * 023 * 024 * Copyright 2006-2010 Sun Microsystems, Inc. 025 * Portions Copyright 2011-2015 ForgeRock AS 026 */ 027package org.opends.server.core; 028 029import java.util.ArrayList; 030import java.util.Iterator; 031import java.util.LinkedHashSet; 032import java.util.List; 033import java.util.Map; 034import java.util.Set; 035import java.util.concurrent.atomic.AtomicBoolean; 036 037import org.forgerock.i18n.slf4j.LocalizedLogger; 038import org.forgerock.opendj.ldap.ByteString; 039import org.forgerock.opendj.ldap.DereferenceAliasesPolicy; 040import org.forgerock.opendj.ldap.ResultCode; 041import org.forgerock.opendj.ldap.SearchScope; 042import org.opends.server.api.AccessControlHandler; 043import org.opends.server.api.AuthenticationPolicyState; 044import org.opends.server.api.ClientConnection; 045import org.opends.server.api.plugin.PluginResult; 046import org.opends.server.controls.AccountUsableResponseControl; 047import org.opends.server.controls.MatchedValuesControl; 048import org.opends.server.protocols.ldap.LDAPFilter; 049import org.opends.server.types.AbstractOperation; 050import org.opends.server.types.Attribute; 051import org.opends.server.types.AttributeBuilder; 052import org.opends.server.types.AttributeType; 053import org.opends.server.types.CancelRequest; 054import org.opends.server.types.CancelResult; 055import org.opends.server.types.CanceledOperationException; 056import org.opends.server.types.Control; 057import org.opends.server.types.DN; 058import org.opends.server.types.DirectoryException; 059import org.opends.server.types.Entry; 060import org.opends.server.types.OperationType; 061import org.opends.server.types.RawFilter; 062import org.opends.server.types.SearchFilter; 063import org.opends.server.types.SearchResultEntry; 064import org.opends.server.types.SearchResultReference; 065import org.opends.server.types.operation.PostResponseSearchOperation; 066import org.opends.server.types.operation.PreParseSearchOperation; 067import org.opends.server.types.operation.SearchEntrySearchOperation; 068import org.opends.server.types.operation.SearchReferenceSearchOperation; 069import org.opends.server.util.TimeThread; 070 071import static org.opends.messages.CoreMessages.*; 072import static org.opends.server.core.DirectoryServer.*; 073import static org.opends.server.loggers.AccessLogger.*; 074import static org.opends.server.util.ServerConstants.*; 075import static org.opends.server.util.StaticUtils.*; 076import static org.opends.server.workflowelement.localbackend.LocalBackendWorkflowElement.*; 077 078/** 079 * This class defines an operation that may be used to locate entries in the 080 * Directory Server based on a given set of criteria. 081 */ 082public class SearchOperationBasis 083 extends AbstractOperation 084 implements PreParseSearchOperation, 085 PostResponseSearchOperation, 086 SearchEntrySearchOperation, 087 SearchReferenceSearchOperation, 088 SearchOperation 089{ 090 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 091 092 /** 093 * Indicates whether a search result done response has been sent to the 094 * client. 095 */ 096 private final AtomicBoolean responseSent = new AtomicBoolean(false); 097 098 /** Indicates whether the client is able to handle referrals. */ 099 private boolean clientAcceptsReferrals = true; 100 101 /** 102 * Indicates whether to include the account usable control with search result 103 * entries. 104 */ 105 private boolean includeUsableControl; 106 107 /** Indicates whether to only real attributes should be returned. */ 108 private boolean realAttributesOnly; 109 110 /** Indicates whether only LDAP subentries should be returned. */ 111 private boolean returnSubentriesOnly; 112 113 /** 114 * Indicates whether the filter references subentry or ldapSubentry object 115 * class. 116 */ 117 private boolean filterIncludesSubentries; 118 private boolean filterNeedsCheckingForSubentries = true; 119 120 /** 121 * Indicates whether to include attribute types only or both types and values. 122 */ 123 private boolean typesOnly; 124 125 /** Indicates whether to only virtual attributes should be returned. */ 126 private boolean virtualAttributesOnly; 127 128 /** 129 * The raw, unprocessed base DN as included in the request from the client. 130 */ 131 private ByteString rawBaseDN; 132 133 /** The dereferencing policy for the search operation. */ 134 private DereferenceAliasesPolicy derefPolicy; 135 136 /** The base DN for the search operation. */ 137 private DN baseDN; 138 139 /** The proxied authorization target DN for this operation. */ 140 private DN proxiedAuthorizationDN; 141 142 /** The number of entries that have been sent to the client. */ 143 private int entriesSent; 144 145 /** 146 * The number of search result references that have been sent to the client. 147 */ 148 private int referencesSent; 149 150 /** The size limit for the search operation. */ 151 private int sizeLimit; 152 153 /** The time limit for the search operation. */ 154 private int timeLimit; 155 156 /** The raw, unprocessed filter as included in the request from the client. */ 157 private RawFilter rawFilter; 158 159 /** The set of attributes that should be returned in matching entries. */ 160 private Set<String> attributes; 161 162 /** The set of response controls for this search operation. */ 163 private final List<Control> responseControls = new ArrayList<>(); 164 165 /** The time that the search time limit has expired. */ 166 private long timeLimitExpiration; 167 168 /** The matched values control associated with this search operation. */ 169 private MatchedValuesControl matchedValuesControl; 170 171 /** The search filter for the search operation. */ 172 private SearchFilter filter; 173 174 /** The search scope for the search operation. */ 175 private SearchScope scope; 176 177 /** Indicates whether to send the search result done to the client or not. */ 178 private boolean sendResponse = true; 179 180 /** 181 * Creates a new search operation with the provided information. 182 * 183 * @param clientConnection The client connection with which this operation 184 * is associated. 185 * @param operationID The operation ID for this operation. 186 * @param messageID The message ID of the request with which this 187 * operation is associated. 188 * @param requestControls The set of controls included in the request. 189 * @param rawBaseDN The raw, unprocessed base DN as included in the 190 * request from the client. 191 * @param scope The scope for this search operation. 192 * @param derefPolicy The alias dereferencing policy for this search 193 * operation. 194 * @param sizeLimit The size limit for this search operation. 195 * @param timeLimit The time limit for this search operation. 196 * @param typesOnly The typesOnly flag for this search operation. 197 * @param rawFilter the raw, unprocessed filter as included in the 198 * request from the client. 199 * @param attributes The requested attributes for this search 200 * operation. 201 */ 202 public SearchOperationBasis(ClientConnection clientConnection, 203 long operationID, 204 int messageID, List<Control> requestControls, 205 ByteString rawBaseDN, SearchScope scope, 206 DereferenceAliasesPolicy derefPolicy, int sizeLimit, 207 int timeLimit, boolean typesOnly, RawFilter rawFilter, 208 Set<String> attributes) 209 { 210 super(clientConnection, operationID, messageID, requestControls); 211 212 this.rawBaseDN = rawBaseDN; 213 this.scope = scope; 214 this.derefPolicy = derefPolicy; 215 this.sizeLimit = sizeLimit; 216 this.timeLimit = timeLimit; 217 this.typesOnly = typesOnly; 218 this.rawFilter = rawFilter; 219 this.attributes = attributes != null ? attributes : new LinkedHashSet<String>(0); 220 221 this.sizeLimit = getSizeLimit(sizeLimit, clientConnection); 222 this.timeLimit = getTimeLimit(timeLimit, clientConnection); 223 } 224 225 /** 226 * Creates a new search operation with the provided information. 227 * 228 * @param clientConnection The client connection with which this operation 229 * is associated. 230 * @param operationID The operation ID for this operation. 231 * @param messageID The message ID of the request with which this 232 * operation is associated. 233 * @param requestControls The set of controls included in the request. 234 * @param baseDN The base DN for this search operation. 235 * @param scope The scope for this search operation. 236 * @param derefPolicy The alias dereferencing policy for this search 237 * operation. 238 * @param sizeLimit The size limit for this search operation. 239 * @param timeLimit The time limit for this search operation. 240 * @param typesOnly The typesOnly flag for this search operation. 241 * @param filter The filter for this search operation. 242 * @param attributes The attributes for this search operation. 243 */ 244 public SearchOperationBasis(ClientConnection clientConnection, 245 long operationID, 246 int messageID, List<Control> requestControls, 247 DN baseDN, SearchScope scope, 248 DereferenceAliasesPolicy derefPolicy, int sizeLimit, 249 int timeLimit, boolean typesOnly, SearchFilter filter, 250 Set<String> attributes) 251 { 252 super(clientConnection, operationID, messageID, requestControls); 253 254 this.baseDN = baseDN; 255 this.scope = scope; 256 this.derefPolicy = derefPolicy; 257 this.sizeLimit = sizeLimit; 258 this.timeLimit = timeLimit; 259 this.typesOnly = typesOnly; 260 this.filter = filter; 261 this.attributes = attributes != null ? attributes : new LinkedHashSet<String>(0); 262 263 rawBaseDN = ByteString.valueOfUtf8(baseDN.toString()); 264 rawFilter = new LDAPFilter(filter); 265 266 this.sizeLimit = getSizeLimit(sizeLimit, clientConnection); 267 this.timeLimit = getTimeLimit(timeLimit, clientConnection); 268 } 269 270 271 private int getSizeLimit(int sizeLimit, ClientConnection clientConnection) 272 { 273 if (clientConnection.getSizeLimit() <= 0) 274 { 275 return sizeLimit; 276 } 277 else if (sizeLimit <= 0) 278 { 279 return clientConnection.getSizeLimit(); 280 } 281 return Math.min(sizeLimit, clientConnection.getSizeLimit()); 282 } 283 284 private int getTimeLimit(int timeLimit, ClientConnection clientConnection) 285 { 286 if (clientConnection.getTimeLimit() <= 0) 287 { 288 return timeLimit; 289 } 290 else if (timeLimit <= 0) 291 { 292 return clientConnection.getTimeLimit(); 293 } 294 return Math.min(timeLimit, clientConnection.getTimeLimit()); 295 } 296 297 @Override 298 public final ByteString getRawBaseDN() 299 { 300 return rawBaseDN; 301 } 302 303 @Override 304 public final void setRawBaseDN(ByteString rawBaseDN) 305 { 306 this.rawBaseDN = rawBaseDN; 307 308 baseDN = null; 309 } 310 311 @Override 312 public final DN getBaseDN() 313 { 314 try 315 { 316 if (baseDN == null) 317 { 318 baseDN = DN.decode(rawBaseDN); 319 } 320 } 321 catch (DirectoryException de) 322 { 323 logger.traceException(de); 324 setResponseData(de); 325 } 326 return baseDN; 327 } 328 329 @Override 330 public final void setBaseDN(DN baseDN) 331 { 332 this.baseDN = baseDN; 333 } 334 335 @Override 336 public final SearchScope getScope() 337 { 338 return scope; 339 } 340 341 @Override 342 public final void setScope(SearchScope scope) 343 { 344 this.scope = scope; 345 } 346 347 @Override 348 public final DereferenceAliasesPolicy getDerefPolicy() 349 { 350 return derefPolicy; 351 } 352 353 @Override 354 public final void setDerefPolicy(DereferenceAliasesPolicy derefPolicy) 355 { 356 this.derefPolicy = derefPolicy; 357 } 358 359 @Override 360 public final int getSizeLimit() 361 { 362 return sizeLimit; 363 } 364 365 @Override 366 public final void setSizeLimit(int sizeLimit) 367 { 368 this.sizeLimit = sizeLimit; 369 } 370 371 @Override 372 public final int getTimeLimit() 373 { 374 return timeLimit; 375 } 376 377 @Override 378 public final void setTimeLimit(int timeLimit) 379 { 380 this.timeLimit = timeLimit; 381 } 382 383 @Override 384 public final boolean getTypesOnly() 385 { 386 return typesOnly; 387 } 388 389 @Override 390 public final void setTypesOnly(boolean typesOnly) 391 { 392 this.typesOnly = typesOnly; 393 } 394 395 @Override 396 public final RawFilter getRawFilter() 397 { 398 return rawFilter; 399 } 400 401 @Override 402 public final void setRawFilter(RawFilter rawFilter) 403 { 404 this.rawFilter = rawFilter; 405 406 filter = null; 407 } 408 409 @Override 410 public final SearchFilter getFilter() 411 { 412 try 413 { 414 if (filter == null) 415 { 416 filter = rawFilter.toSearchFilter(); 417 } 418 } 419 catch (DirectoryException de) 420 { 421 logger.traceException(de); 422 setResponseData(de); 423 } 424 return filter; 425 } 426 427 @Override 428 public final Set<String> getAttributes() 429 { 430 return attributes; 431 } 432 433 @Override 434 public final void setAttributes(Set<String> attributes) 435 { 436 if (attributes == null) 437 { 438 this.attributes.clear(); 439 } 440 else 441 { 442 this.attributes = attributes; 443 } 444 } 445 446 @Override 447 public final int getEntriesSent() 448 { 449 return entriesSent; 450 } 451 452 @Override 453 public final int getReferencesSent() 454 { 455 return referencesSent; 456 } 457 458 @Override 459 public final boolean returnEntry(Entry entry, List<Control> controls) 460 { 461 return returnEntry(entry, controls, true); 462 } 463 464 @Override 465 public final boolean returnEntry(Entry entry, List<Control> controls, 466 boolean evaluateAci) 467 { 468 boolean typesOnly = getTypesOnly(); 469 470 // See if the size limit has been exceeded. If so, then don't send the 471 // entry and indicate that the search should end. 472 if (getSizeLimit() > 0 && getEntriesSent() >= getSizeLimit()) 473 { 474 setResultCode(ResultCode.SIZE_LIMIT_EXCEEDED); 475 appendErrorMessage(ERR_SEARCH_SIZE_LIMIT_EXCEEDED.get(getSizeLimit())); 476 return false; 477 } 478 479 // See if the time limit has expired. If so, then don't send the entry and 480 // indicate that the search should end. 481 if (getTimeLimit() > 0 482 && TimeThread.getTime() >= getTimeLimitExpiration()) 483 { 484 setResultCode(ResultCode.TIME_LIMIT_EXCEEDED); 485 appendErrorMessage(ERR_SEARCH_TIME_LIMIT_EXCEEDED.get(getTimeLimit())); 486 return false; 487 } 488 489 // Determine whether the provided entry is a subentry and if so whether it 490 // should be returned. 491 if (entry.isSubentry() || entry.isLDAPSubentry()) 492 { 493 if (filterNeedsCheckingForSubentries) 494 { 495 filterIncludesSubentries = checkFilterForLDAPSubEntry(filter, 0); 496 filterNeedsCheckingForSubentries = false; 497 } 498 499 if (getScope() != SearchScope.BASE_OBJECT 500 && !filterIncludesSubentries 501 && !isReturnSubentriesOnly()) 502 { 503 return true; 504 } 505 } 506 else if (isReturnSubentriesOnly()) 507 { 508 // Subentries are visible and normal entries are not. 509 return true; 510 } 511 512 // Determine whether to include the account usable control. If so, then 513 // create it now. 514 if (isIncludeUsableControl()) 515 { 516 if (controls == null) 517 { 518 controls = new ArrayList<>(1); 519 } 520 521 try 522 { 523 // FIXME -- Need a way to enable PWP debugging. 524 AuthenticationPolicyState state = AuthenticationPolicyState.forUser( 525 entry, false); 526 if (state.isPasswordPolicy()) 527 { 528 PasswordPolicyState pwpState = (PasswordPolicyState) state; 529 530 boolean isInactive = pwpState.isDisabled() 531 || pwpState.isAccountExpired(); 532 boolean isLocked = pwpState.isLocked(); 533 boolean isReset = pwpState.mustChangePassword(); 534 boolean isExpired = pwpState.isPasswordExpired(); 535 536 if (isInactive || isLocked || isReset || isExpired) 537 { 538 int secondsBeforeUnlock = pwpState.getSecondsUntilUnlock(); 539 int remainingGraceLogins = pwpState.getGraceLoginsRemaining(); 540 controls 541 .add(new AccountUsableResponseControl(isInactive, isReset, 542 isExpired, remainingGraceLogins, isLocked, 543 secondsBeforeUnlock)); 544 } 545 else 546 { 547 int secondsBeforeExpiration = pwpState.getSecondsUntilExpiration(); 548 controls.add(new AccountUsableResponseControl( 549 secondsBeforeExpiration)); 550 } 551 } 552 // Another type of authentication policy (e.g. PTA). 553 else if (state.isDisabled()) 554 { 555 controls.add(new AccountUsableResponseControl(false, false, false, 556 -1, true, -1)); 557 } 558 else 559 { 560 controls.add(new AccountUsableResponseControl(-1)); 561 } 562 } 563 catch (Exception e) 564 { 565 logger.traceException(e); 566 } 567 } 568 569 // Check to see if the entry can be read by the client. 570 SearchResultEntry unfilteredSearchEntry = new SearchResultEntry(entry, controls); 571 if (evaluateAci && !getACIHandler().maySend(this, unfilteredSearchEntry)) 572 { 573 return true; 574 } 575 576 // Make a copy of the entry and pare it down to only include the set 577 // of requested attributes. 578 579 // NOTE: that this copy will include the objectClass attribute. 580 Entry filteredEntry = 581 entry.filterEntry(getAttributes(), typesOnly, 582 isVirtualAttributesOnly(), isRealAttributesOnly()); 583 584 585 // If there is a matched values control, then further pare down the entry 586 // based on the filters that it contains. 587 MatchedValuesControl matchedValuesControl = getMatchedValuesControl(); 588 if (matchedValuesControl != null && !typesOnly) 589 { 590 // First, look at the set of objectclasses. 591 592 // NOTE: the objectClass attribute is also present and must be 593 // dealt with later. 594 AttributeType attrType = DirectoryServer.getObjectClassAttributeType(); 595 Iterator<String> ocIterator = 596 filteredEntry.getObjectClasses().values().iterator(); 597 while (ocIterator.hasNext()) 598 { 599 ByteString ocName = ByteString.valueOfUtf8(ocIterator.next()); 600 if (! matchedValuesControl.valueMatches(attrType, ocName)) 601 { 602 ocIterator.remove(); 603 } 604 } 605 606 607 // Next, the set of user attributes (incl. objectClass attribute). 608 for (Map.Entry<AttributeType, List<Attribute>> e : filteredEntry 609 .getUserAttributes().entrySet()) 610 { 611 AttributeType t = e.getKey(); 612 List<Attribute> oldAttributes = e.getValue(); 613 List<Attribute> newAttributes = new ArrayList<>(oldAttributes.size()); 614 615 for (Attribute a : oldAttributes) 616 { 617 // Assume that the attribute will be either empty or contain 618 // very few values. 619 AttributeBuilder builder = new AttributeBuilder(a, true); 620 for (ByteString v : a) 621 { 622 if (matchedValuesControl.valueMatches(t, v)) 623 { 624 builder.add(v); 625 } 626 } 627 newAttributes.add(builder.toAttribute()); 628 } 629 e.setValue(newAttributes); 630 } 631 632 633 // Then the set of operational attributes. 634 for (Map.Entry<AttributeType, List<Attribute>> e : filteredEntry 635 .getOperationalAttributes().entrySet()) 636 { 637 AttributeType t = e.getKey(); 638 List<Attribute> oldAttributes = e.getValue(); 639 List<Attribute> newAttributes = new ArrayList<>(oldAttributes.size()); 640 641 for (Attribute a : oldAttributes) 642 { 643 // Assume that the attribute will be either empty or contain 644 // very few values. 645 AttributeBuilder builder = new AttributeBuilder(a, true); 646 for (ByteString v : a) 647 { 648 if (matchedValuesControl.valueMatches(t, v)) 649 { 650 builder.add(v); 651 } 652 } 653 newAttributes.add(builder.toAttribute()); 654 } 655 e.setValue(newAttributes); 656 } 657 } 658 659 660 // Convert the provided entry to a search result entry. 661 SearchResultEntry filteredSearchEntry = new SearchResultEntry( 662 filteredEntry, controls); 663 664 // Strip out any attributes that the client does not have access to. 665 666 // FIXME: need some way to prevent plugins from adding attributes or 667 // values that the client is not permitted to see. 668 if (evaluateAci) 669 { 670 getACIHandler().filterEntry(this, unfilteredSearchEntry, filteredSearchEntry); 671 } 672 673 // Invoke any search entry plugins that may be registered with the server. 674 PluginResult.IntermediateResponse pluginResult = 675 DirectoryServer.getPluginConfigManager(). 676 invokeSearchResultEntryPlugins(this, filteredSearchEntry); 677 678 // Send the entry to the client. 679 if (pluginResult.sendResponse()) 680 { 681 // Log the entry sent to the client. 682 logSearchResultEntry(this, filteredSearchEntry); 683 684 try 685 { 686 sendSearchEntry(filteredSearchEntry); 687 688 entriesSent++; 689 } 690 catch (DirectoryException de) 691 { 692 logger.traceException(de); 693 694 setResponseData(de); 695 return false; 696 } 697 } 698 699 return pluginResult.continueProcessing(); 700 } 701 702 private AccessControlHandler<?> getACIHandler() 703 { 704 return AccessControlConfigManager.getInstance().getAccessControlHandler(); 705 } 706 707 @Override 708 public final boolean returnReference(DN dn, SearchResultReference reference) 709 { 710 return returnReference(dn, reference, true); 711 } 712 713 @Override 714 public final boolean returnReference(DN dn, SearchResultReference reference, 715 boolean evaluateAci) 716 { 717 // See if the time limit has expired. If so, then don't send the entry and 718 // indicate that the search should end. 719 if (getTimeLimit() > 0 720 && TimeThread.getTime() >= getTimeLimitExpiration()) 721 { 722 setResultCode(ResultCode.TIME_LIMIT_EXCEEDED); 723 appendErrorMessage(ERR_SEARCH_TIME_LIMIT_EXCEEDED.get(getTimeLimit())); 724 return false; 725 } 726 727 728 // See if we know that this client can't handle referrals. If so, then 729 // don't even try to send it. 730 if (!isClientAcceptsReferrals() 731 // See if the client has permission to read this reference. 732 || (evaluateAci && !getACIHandler().maySend(dn, this, reference))) 733 { 734 return true; 735 } 736 737 738 // Invoke any search reference plugins that may be registered with the 739 // server. 740 PluginResult.IntermediateResponse pluginResult = 741 DirectoryServer.getPluginConfigManager(). 742 invokeSearchResultReferencePlugins(this, reference); 743 744 // Send the reference to the client. Note that this could throw an 745 // exception, which would indicate that the associated client can't handle 746 // referrals. If that't the case, then set a flag so we'll know not to try 747 // to send any more. 748 if (pluginResult.sendResponse()) 749 { 750 // Log the entry sent to the client. 751 logSearchResultReference(this, reference); 752 753 try 754 { 755 if (sendSearchReference(reference)) 756 { 757 referencesSent++; 758 759 // FIXME -- Should the size limit apply here? 760 } 761 else 762 { 763 // We know that the client can't handle referrals, so we won't try to 764 // send it any more. 765 setClientAcceptsReferrals(false); 766 } 767 } 768 catch (DirectoryException de) 769 { 770 logger.traceException(de); 771 772 setResponseData(de); 773 return false; 774 } 775 } 776 777 return pluginResult.continueProcessing(); 778 } 779 780 @Override 781 public final void sendSearchResultDone() 782 { 783 // Send the search result done message to the client. We want to make sure 784 // that this only gets sent once, and it's possible that this could be 785 // multithreaded in the event of a persistent search, so do it safely. 786 if (responseSent.compareAndSet(false, true)) 787 { 788 logSearchResultDone(this); 789 790 clientConnection.sendResponse(this); 791 792 invokePostResponsePlugins(); 793 } 794 } 795 796 @Override 797 public final OperationType getOperationType() 798 { 799 // Note that no debugging will be done in this method because it is a likely 800 // candidate for being called by the logging subsystem. 801 return OperationType.SEARCH; 802 } 803 804 @Override 805 public DN getProxiedAuthorizationDN() 806 { 807 return proxiedAuthorizationDN; 808 } 809 810 @Override 811 public final List<Control> getResponseControls() 812 { 813 return responseControls; 814 } 815 816 @Override 817 public final void addResponseControl(Control control) 818 { 819 responseControls.add(control); 820 } 821 822 @Override 823 public final void removeResponseControl(Control control) 824 { 825 responseControls.remove(control); 826 } 827 828 @Override 829 public void abort(CancelRequest cancelRequest) 830 { 831 if(cancelResult == null && this.cancelRequest == null) 832 { 833 this.cancelRequest = cancelRequest; 834 } 835 } 836 837 @Override 838 public final void toString(StringBuilder buffer) 839 { 840 buffer.append("SearchOperation(connID="); 841 buffer.append(clientConnection.getConnectionID()); 842 buffer.append(", opID="); 843 buffer.append(operationID); 844 buffer.append(", baseDN="); 845 buffer.append(rawBaseDN); 846 buffer.append(", scope="); 847 buffer.append(scope); 848 buffer.append(", filter="); 849 buffer.append(rawFilter); 850 buffer.append(")"); 851 } 852 853 @Override 854 public void setTimeLimitExpiration(long timeLimitExpiration) 855 { 856 this.timeLimitExpiration = timeLimitExpiration; 857 } 858 859 @Override 860 public boolean isReturnSubentriesOnly() 861 { 862 return returnSubentriesOnly; 863 } 864 865 @Override 866 public void setReturnSubentriesOnly(boolean returnLDAPSubentries) 867 { 868 this.returnSubentriesOnly = returnLDAPSubentries; 869 } 870 871 @Override 872 public MatchedValuesControl getMatchedValuesControl() 873 { 874 return matchedValuesControl; 875 } 876 877 @Override 878 public void setMatchedValuesControl(MatchedValuesControl controls) 879 { 880 this.matchedValuesControl = controls; 881 } 882 883 @Override 884 public boolean isIncludeUsableControl() 885 { 886 return includeUsableControl; 887 } 888 889 @Override 890 public void setIncludeUsableControl(boolean includeUsableControl) 891 { 892 this.includeUsableControl = includeUsableControl; 893 } 894 895 @Override 896 public long getTimeLimitExpiration() 897 { 898 return timeLimitExpiration; 899 } 900 901 @Override 902 public boolean isClientAcceptsReferrals() 903 { 904 return clientAcceptsReferrals; 905 } 906 907 @Override 908 public void setClientAcceptsReferrals(boolean clientAcceptReferrals) 909 { 910 this.clientAcceptsReferrals = clientAcceptReferrals; 911 } 912 913 @Override 914 public boolean isSendResponse() 915 { 916 return sendResponse; 917 } 918 919 @Override 920 public void setSendResponse(boolean sendResponse) 921 { 922 this.sendResponse = sendResponse; 923 } 924 925 @Override 926 public boolean isRealAttributesOnly() 927 { 928 return this.realAttributesOnly; 929 } 930 931 @Override 932 public boolean isVirtualAttributesOnly() 933 { 934 return this.virtualAttributesOnly; 935 } 936 937 @Override 938 public void setRealAttributesOnly(boolean realAttributesOnly) 939 { 940 this.realAttributesOnly = realAttributesOnly; 941 } 942 943 @Override 944 public void setVirtualAttributesOnly(boolean virtualAttributesOnly) 945 { 946 this.virtualAttributesOnly = virtualAttributesOnly; 947 } 948 949 @Override 950 public void sendSearchEntry(SearchResultEntry searchEntry) 951 throws DirectoryException 952 { 953 getClientConnection().sendSearchEntry(this, searchEntry); 954 } 955 956 @Override 957 public boolean sendSearchReference(SearchResultReference searchReference) 958 throws DirectoryException 959 { 960 return getClientConnection().sendSearchReference(this, searchReference); 961 } 962 963 @Override 964 public void setProxiedAuthorizationDN(DN proxiedAuthorizationDN) 965 { 966 this.proxiedAuthorizationDN = proxiedAuthorizationDN; 967 } 968 969 @Override 970 public final void run() 971 { 972 setResultCode(ResultCode.UNDEFINED); 973 974 // Start the processing timer. 975 setProcessingStartTime(); 976 977 logSearchRequest(this); 978 979 setSendResponse(true); 980 981 int timeLimit = getTimeLimit(); 982 long timeLimitExpiration; 983 if (timeLimit <= 0) 984 { 985 timeLimitExpiration = Long.MAX_VALUE; 986 } 987 else 988 { 989 // FIXME -- Factor in the user's effective time limit. 990 timeLimitExpiration = getProcessingStartTime() + (1000L * timeLimit); 991 } 992 setTimeLimitExpiration(timeLimitExpiration); 993 994 try 995 { 996 // Check for and handle a request to cancel this operation. 997 checkIfCanceled(false); 998 999 if (!processOperationResult(getPluginConfigManager().invokePreParseSearchPlugins(this))) 1000 { 1001 return; 1002 } 1003 1004 // Check for and handle a request to cancel this operation. 1005 checkIfCanceled(false); 1006 1007 // Process the search base and filter to convert them from their raw forms 1008 // as provided by the client to the forms required for the rest of the 1009 // search processing. 1010 DN baseDN = getBaseDN(); 1011 if (baseDN == null){ 1012 return; 1013 } 1014 1015 execute(this, baseDN); 1016 } 1017 catch(CanceledOperationException coe) 1018 { 1019 logger.traceException(coe); 1020 1021 setResultCode(ResultCode.CANCELLED); 1022 cancelResult = new CancelResult(ResultCode.CANCELLED, null); 1023 1024 appendErrorMessage(coe.getCancelRequest().getCancelReason()); 1025 } 1026 finally 1027 { 1028 // Stop the processing timer. 1029 setProcessingStopTime(); 1030 1031 if(cancelRequest == null || cancelResult == null || 1032 cancelResult.getResultCode() != ResultCode.CANCELLED) 1033 { 1034 // If everything is successful to this point and it is not a persistent 1035 // search, then send the search result done message to the client. 1036 // Otherwise, we'll want to make the size and time limit values 1037 // unlimited to ensure that the remainder of the persistent search 1038 // isn't subject to those restrictions. 1039 if (isSendResponse()) 1040 { 1041 sendSearchResultDone(); 1042 } 1043 else 1044 { 1045 setSizeLimit(0); 1046 setTimeLimit(0); 1047 } 1048 } 1049 else if(cancelRequest.notifyOriginalRequestor() || 1050 DirectoryServer.notifyAbandonedOperations()) 1051 { 1052 sendSearchResultDone(); 1053 } 1054 1055 // If no cancel result, set it 1056 if(cancelResult == null) 1057 { 1058 cancelResult = new CancelResult(ResultCode.TOO_LATE, null); 1059 } 1060 } 1061 } 1062 1063 1064 /** Invokes the post response plugins. */ 1065 private void invokePostResponsePlugins() 1066 { 1067 // Invoke the post response plugins that have been registered with 1068 // the current operation 1069 getPluginConfigManager().invokePostResponseSearchPlugins(this); 1070 } 1071 1072 @Override 1073 public void updateOperationErrMsgAndResCode() 1074 { 1075 setResultCode(ResultCode.NO_SUCH_OBJECT); 1076 appendErrorMessage(ERR_SEARCH_BASE_DOESNT_EXIST.get(getBaseDN())); 1077 } 1078 1079 /** 1080 * Checks if the filter contains an equality element with the objectclass 1081 * attribute type and a value of "ldapSubentry" and if so sets 1082 * returnSubentriesOnly to <code>true</code>. 1083 * 1084 * @param filter 1085 * The complete filter being checked, of which this filter may be a 1086 * subset. 1087 * @param depth 1088 * The current depth of the evaluation, which is used to prevent 1089 * infinite recursion due to highly nested filters and eventually 1090 * running out of stack space. 1091 * @return {@code true} if the filter references the sub-entry object class. 1092 */ 1093 private boolean checkFilterForLDAPSubEntry(SearchFilter filter, int depth) 1094 { 1095 // Paranoid check to avoid recursion deep enough to provoke 1096 // the stack overflow. This should never happen because if 1097 // a given filter is too nested SearchFilter exception gets 1098 // raised long before this method is invoked. 1099 if (depth >= MAX_NESTED_FILTER_DEPTH) 1100 { 1101 if (logger.isTraceEnabled()) 1102 { 1103 logger.trace("Exceeded maximum filter depth"); 1104 } 1105 return false; 1106 } 1107 1108 switch (filter.getFilterType()) 1109 { 1110 case EQUALITY: 1111 if (filter.getAttributeType().isObjectClass()) 1112 { 1113 ByteString v = filter.getAssertionValue(); 1114 // FIXME : technically this is not correct since the presence 1115 // of draft oc would trigger rfc oc visibility and visa versa. 1116 String stringValueLC = toLowerCase(v.toString()); 1117 if (OC_LDAP_SUBENTRY_LC.equals(stringValueLC) || 1118 OC_SUBENTRY.equals(stringValueLC)) 1119 { 1120 return true; 1121 } 1122 } 1123 break; 1124 case AND: 1125 case OR: 1126 for (SearchFilter f : filter.getFilterComponents()) 1127 { 1128 if (checkFilterForLDAPSubEntry(f, depth + 1)) 1129 { 1130 return true; 1131 } 1132 } 1133 break; 1134 } 1135 1136 return false; 1137 } 1138}