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