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.DN; 031import org.forgerock.opendj.ldap.DereferenceAliasesPolicy; 032import org.forgerock.opendj.ldap.ResultCode; 033import org.forgerock.opendj.ldap.SearchScope; 034import org.forgerock.opendj.ldap.schema.AttributeType; 035import org.forgerock.opendj.ldap.schema.CoreSchema; 036import org.opends.server.api.AccessControlHandler; 037import org.opends.server.api.AuthenticationPolicyState; 038import org.opends.server.api.ClientConnection; 039import org.opends.server.api.plugin.PluginResult; 040import org.opends.server.controls.AccountUsableResponseControl; 041import org.opends.server.controls.MatchedValuesControl; 042import org.opends.server.protocols.ldap.LDAPFilter; 043import org.opends.server.types.AbstractOperation; 044import org.opends.server.types.Attribute; 045import org.opends.server.types.AttributeBuilder; 046import org.opends.server.types.CancelRequest; 047import org.opends.server.types.CancelResult; 048import org.opends.server.types.CanceledOperationException; 049import org.opends.server.types.Control; 050import org.opends.server.types.DirectoryException; 051import org.opends.server.types.Entry; 052import org.opends.server.types.OperationType; 053import org.opends.server.types.RawFilter; 054import org.opends.server.types.SearchFilter; 055import org.opends.server.types.SearchResultEntry; 056import org.opends.server.types.SearchResultReference; 057import org.opends.server.types.operation.PostResponseSearchOperation; 058import org.opends.server.types.operation.PreParseSearchOperation; 059import org.opends.server.types.operation.SearchEntrySearchOperation; 060import org.opends.server.types.operation.SearchReferenceSearchOperation; 061import org.opends.server.util.TimeThread; 062 063import static org.opends.messages.CoreMessages.*; 064import static org.opends.server.core.DirectoryServer.*; 065import static org.opends.server.loggers.AccessLogger.*; 066import static org.opends.server.util.ServerConstants.*; 067import static org.opends.server.util.StaticUtils.*; 068import static org.opends.server.workflowelement.localbackend.LocalBackendWorkflowElement.*; 069 070/** 071 * This class defines an operation that may be used to locate entries in the 072 * Directory Server based on a given set of criteria. 073 */ 074public class SearchOperationBasis 075 extends AbstractOperation 076 implements PreParseSearchOperation, 077 PostResponseSearchOperation, 078 SearchEntrySearchOperation, 079 SearchReferenceSearchOperation, 080 SearchOperation 081{ 082 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 083 084 /** 085 * Indicates whether a search result done response has been sent to the 086 * client. 087 */ 088 private final AtomicBoolean responseSent = new AtomicBoolean(false); 089 090 /** Indicates whether the client is able to handle referrals. */ 091 private boolean clientAcceptsReferrals = true; 092 093 /** 094 * Indicates whether to include the account usable control with search result 095 * entries. 096 */ 097 private boolean includeUsableControl; 098 099 /** Indicates whether to only real attributes should be returned. */ 100 private boolean realAttributesOnly; 101 102 /** Indicates whether only LDAP subentries should be returned. */ 103 private boolean returnSubentriesOnly; 104 105 /** 106 * Indicates whether the filter references subentry or ldapSubentry object 107 * class. 108 */ 109 private boolean filterIncludesSubentries; 110 private boolean filterNeedsCheckingForSubentries = true; 111 112 /** 113 * Indicates whether to include attribute types only or both types and values. 114 */ 115 private boolean typesOnly; 116 117 /** Indicates whether to only virtual attributes should be returned. */ 118 private boolean virtualAttributesOnly; 119 120 /** 121 * The raw, unprocessed base DN as included in the request from the client. 122 */ 123 private ByteString rawBaseDN; 124 125 /** The dereferencing policy for the search operation. */ 126 private DereferenceAliasesPolicy derefPolicy; 127 128 /** The base DN for the search operation. */ 129 private DN baseDN; 130 131 /** The proxied authorization target DN for this operation. */ 132 private DN proxiedAuthorizationDN; 133 134 /** The number of entries that have been sent to the client. */ 135 private int entriesSent; 136 137 /** 138 * The number of search result references that have been sent to the client. 139 */ 140 private int referencesSent; 141 142 /** The size limit for the search operation. */ 143 private int sizeLimit; 144 145 /** The time limit for the search operation. */ 146 private int timeLimit; 147 148 /** The raw, unprocessed filter as included in the request from the client. */ 149 private RawFilter rawFilter; 150 151 /** The set of attributes that should be returned in matching entries. */ 152 private Set<String> attributes; 153 154 /** The set of response controls for this search operation. */ 155 private final List<Control> responseControls = new ArrayList<>(); 156 157 /** The time that the search time limit has expired. */ 158 private long timeLimitExpiration; 159 160 /** The matched values control associated with this search operation. */ 161 private MatchedValuesControl matchedValuesControl; 162 163 /** The search filter for the search operation. */ 164 private SearchFilter filter; 165 166 /** The search scope for the search operation. */ 167 private SearchScope scope; 168 169 /** Indicates whether to send the search result done to the client or not. */ 170 private boolean sendResponse = true; 171 172 /** 173 * Creates a new search operation with the provided information. 174 * 175 * @param clientConnection The client connection with which this operation 176 * is associated. 177 * @param operationID The operation ID for this operation. 178 * @param messageID The message ID of the request with which this 179 * operation is associated. 180 * @param requestControls The set of controls included in the request. 181 * @param rawBaseDN The raw, unprocessed base DN as included in the 182 * request from the client. 183 * @param scope The scope for this search operation. 184 * @param derefPolicy The alias dereferencing policy for this search 185 * operation. 186 * @param sizeLimit The size limit for this search operation. 187 * @param timeLimit The time limit for this search operation. 188 * @param typesOnly The typesOnly flag for this search operation. 189 * @param rawFilter the raw, unprocessed filter as included in the 190 * request from the client. 191 * @param attributes The requested attributes for this search 192 * operation. 193 */ 194 public SearchOperationBasis(ClientConnection clientConnection, 195 long operationID, 196 int messageID, List<Control> requestControls, 197 ByteString rawBaseDN, SearchScope scope, 198 DereferenceAliasesPolicy derefPolicy, int sizeLimit, 199 int timeLimit, boolean typesOnly, RawFilter rawFilter, 200 Set<String> attributes) 201 { 202 super(clientConnection, operationID, messageID, requestControls); 203 204 this.rawBaseDN = rawBaseDN; 205 this.scope = scope; 206 this.derefPolicy = derefPolicy; 207 this.sizeLimit = sizeLimit; 208 this.timeLimit = timeLimit; 209 this.typesOnly = typesOnly; 210 this.rawFilter = rawFilter; 211 this.attributes = attributes != null ? attributes : new LinkedHashSet<String>(0); 212 213 this.sizeLimit = getSizeLimit(sizeLimit, clientConnection); 214 this.timeLimit = getTimeLimit(timeLimit, clientConnection); 215 } 216 217 /** 218 * Creates a new search operation with the provided information. 219 * 220 * @param clientConnection The client connection with which this operation 221 * is associated. 222 * @param operationID The operation ID for this operation. 223 * @param messageID The message ID of the request with which this 224 * operation is associated. 225 * @param requestControls The set of controls included in the request. 226 * @param baseDN The base DN for this search operation. 227 * @param scope The scope for this search operation. 228 * @param derefPolicy The alias dereferencing policy for this search 229 * operation. 230 * @param sizeLimit The size limit for this search operation. 231 * @param timeLimit The time limit for this search operation. 232 * @param typesOnly The typesOnly flag for this search operation. 233 * @param filter The filter for this search operation. 234 * @param attributes The attributes for this search operation. 235 */ 236 public SearchOperationBasis(ClientConnection clientConnection, 237 long operationID, 238 int messageID, List<Control> requestControls, 239 DN baseDN, SearchScope scope, 240 DereferenceAliasesPolicy derefPolicy, int sizeLimit, 241 int timeLimit, boolean typesOnly, SearchFilter filter, 242 Set<String> attributes) 243 { 244 super(clientConnection, operationID, messageID, requestControls); 245 246 this.baseDN = baseDN; 247 this.scope = scope; 248 this.derefPolicy = derefPolicy; 249 this.sizeLimit = sizeLimit; 250 this.timeLimit = timeLimit; 251 this.typesOnly = typesOnly; 252 this.filter = filter; 253 this.attributes = attributes != null ? attributes : new LinkedHashSet<String>(0); 254 255 rawBaseDN = ByteString.valueOfUtf8(baseDN.toString()); 256 rawFilter = new LDAPFilter(filter); 257 258 this.sizeLimit = getSizeLimit(sizeLimit, clientConnection); 259 this.timeLimit = getTimeLimit(timeLimit, clientConnection); 260 } 261 262 263 private int getSizeLimit(int sizeLimit, ClientConnection clientConnection) 264 { 265 if (clientConnection.getSizeLimit() <= 0) 266 { 267 return sizeLimit; 268 } 269 else if (sizeLimit <= 0) 270 { 271 return clientConnection.getSizeLimit(); 272 } 273 return Math.min(sizeLimit, clientConnection.getSizeLimit()); 274 } 275 276 private int getTimeLimit(int timeLimit, ClientConnection clientConnection) 277 { 278 if (clientConnection.getTimeLimit() <= 0) 279 { 280 return timeLimit; 281 } 282 else if (timeLimit <= 0) 283 { 284 return clientConnection.getTimeLimit(); 285 } 286 return Math.min(timeLimit, clientConnection.getTimeLimit()); 287 } 288 289 @Override 290 public final ByteString getRawBaseDN() 291 { 292 return rawBaseDN; 293 } 294 295 @Override 296 public final void setRawBaseDN(ByteString rawBaseDN) 297 { 298 this.rawBaseDN = rawBaseDN; 299 300 baseDN = null; 301 } 302 303 @Override 304 public final DN getBaseDN() 305 { 306 try 307 { 308 if (baseDN == null) 309 { 310 baseDN = DN.valueOf(rawBaseDN); 311 } 312 } 313 catch (LocalizedIllegalArgumentException e) 314 { 315 logger.traceException(e); 316 setResultCode(ResultCode.INVALID_DN_SYNTAX); 317 appendErrorMessage(e.getMessageObject()); 318 } 319 return baseDN; 320 } 321 322 @Override 323 public final void setBaseDN(DN baseDN) 324 { 325 this.baseDN = baseDN; 326 } 327 328 @Override 329 public final SearchScope getScope() 330 { 331 return scope; 332 } 333 334 @Override 335 public final void setScope(SearchScope scope) 336 { 337 this.scope = scope; 338 } 339 340 @Override 341 public final DereferenceAliasesPolicy getDerefPolicy() 342 { 343 return derefPolicy; 344 } 345 346 @Override 347 public final void setDerefPolicy(DereferenceAliasesPolicy derefPolicy) 348 { 349 this.derefPolicy = derefPolicy; 350 } 351 352 @Override 353 public final int getSizeLimit() 354 { 355 return sizeLimit; 356 } 357 358 @Override 359 public final void setSizeLimit(int sizeLimit) 360 { 361 this.sizeLimit = sizeLimit; 362 } 363 364 @Override 365 public final int getTimeLimit() 366 { 367 return timeLimit; 368 } 369 370 @Override 371 public final void setTimeLimit(int timeLimit) 372 { 373 this.timeLimit = timeLimit; 374 } 375 376 @Override 377 public final boolean getTypesOnly() 378 { 379 return typesOnly; 380 } 381 382 @Override 383 public final void setTypesOnly(boolean typesOnly) 384 { 385 this.typesOnly = typesOnly; 386 } 387 388 @Override 389 public final RawFilter getRawFilter() 390 { 391 return rawFilter; 392 } 393 394 @Override 395 public final void setRawFilter(RawFilter rawFilter) 396 { 397 this.rawFilter = rawFilter; 398 399 filter = null; 400 } 401 402 @Override 403 public final SearchFilter getFilter() 404 { 405 try 406 { 407 if (filter == null) 408 { 409 filter = rawFilter.toSearchFilter(); 410 } 411 } 412 catch (DirectoryException de) 413 { 414 logger.traceException(de); 415 setResponseData(de); 416 } 417 return filter; 418 } 419 420 @Override 421 public final Set<String> getAttributes() 422 { 423 return attributes; 424 } 425 426 @Override 427 public final void setAttributes(Set<String> attributes) 428 { 429 if (attributes == null) 430 { 431 this.attributes.clear(); 432 } 433 else 434 { 435 this.attributes = attributes; 436 } 437 } 438 439 @Override 440 public final int getEntriesSent() 441 { 442 return entriesSent; 443 } 444 445 @Override 446 public final int getReferencesSent() 447 { 448 return referencesSent; 449 } 450 451 @Override 452 public final boolean returnEntry(Entry entry, List<Control> controls) 453 { 454 return returnEntry(entry, controls, true); 455 } 456 457 @Override 458 public final boolean returnEntry(Entry entry, List<Control> controls, 459 boolean evaluateAci) 460 { 461 boolean typesOnly = getTypesOnly(); 462 463 // See if the size limit has been exceeded. If so, then don't send the 464 // entry and indicate that the search should end. 465 if (getSizeLimit() > 0 && getEntriesSent() >= getSizeLimit()) 466 { 467 setResultCode(ResultCode.SIZE_LIMIT_EXCEEDED); 468 appendErrorMessage(ERR_SEARCH_SIZE_LIMIT_EXCEEDED.get(getSizeLimit())); 469 return false; 470 } 471 472 // See if the time limit has expired. If so, then don't send the entry and 473 // indicate that the search should end. 474 if (getTimeLimit() > 0 475 && TimeThread.getTime() >= getTimeLimitExpiration()) 476 { 477 setResultCode(ResultCode.TIME_LIMIT_EXCEEDED); 478 appendErrorMessage(ERR_SEARCH_TIME_LIMIT_EXCEEDED.get(getTimeLimit())); 479 return false; 480 } 481 482 // Determine whether the provided entry is a subentry and if so whether it 483 // should be returned. 484 if (entry.isSubentry() || entry.isLDAPSubentry()) 485 { 486 if (filterNeedsCheckingForSubentries) 487 { 488 filterIncludesSubentries = checkFilterForLDAPSubEntry(filter, 0); 489 filterNeedsCheckingForSubentries = false; 490 } 491 492 if (getScope() != SearchScope.BASE_OBJECT 493 && !filterIncludesSubentries 494 && !isReturnSubentriesOnly()) 495 { 496 return true; 497 } 498 } 499 else if (isReturnSubentriesOnly()) 500 { 501 // Subentries are visible and normal entries are not. 502 return true; 503 } 504 505 // Determine whether to include the account usable control. If so, then 506 // create it now. 507 if (isIncludeUsableControl()) 508 { 509 if (controls == null) 510 { 511 controls = new ArrayList<>(1); 512 } 513 514 try 515 { 516 // FIXME -- Need a way to enable PWP debugging. 517 AuthenticationPolicyState state = AuthenticationPolicyState.forUser( 518 entry, false); 519 if (state.isPasswordPolicy()) 520 { 521 PasswordPolicyState pwpState = (PasswordPolicyState) state; 522 523 boolean isInactive = pwpState.isDisabled() 524 || pwpState.isAccountExpired(); 525 boolean isLocked = pwpState.isLocked(); 526 boolean isReset = pwpState.mustChangePassword(); 527 boolean isExpired = pwpState.isPasswordExpired(); 528 529 if (isInactive || isLocked || isReset || isExpired) 530 { 531 int secondsBeforeUnlock = pwpState.getSecondsUntilUnlock(); 532 int remainingGraceLogins = pwpState.getGraceLoginsRemaining(); 533 controls 534 .add(new AccountUsableResponseControl(isInactive, isReset, 535 isExpired, remainingGraceLogins, isLocked, 536 secondsBeforeUnlock)); 537 } 538 else 539 { 540 int secondsBeforeExpiration = pwpState.getSecondsUntilExpiration(); 541 controls.add(new AccountUsableResponseControl( 542 secondsBeforeExpiration)); 543 } 544 } 545 // Another type of authentication policy (e.g. PTA). 546 else if (state.isDisabled()) 547 { 548 controls.add(new AccountUsableResponseControl(false, false, false, 549 -1, true, -1)); 550 } 551 else 552 { 553 controls.add(new AccountUsableResponseControl(-1)); 554 } 555 } 556 catch (Exception e) 557 { 558 logger.traceException(e); 559 } 560 } 561 562 // Check to see if the entry can be read by the client. 563 SearchResultEntry unfilteredSearchEntry = new SearchResultEntry(entry, controls); 564 if (evaluateAci && !getACIHandler().maySend(this, unfilteredSearchEntry)) 565 { 566 return true; 567 } 568 569 // Make a copy of the entry and pare it down to only include the set 570 // of requested attributes. 571 572 // NOTE: that this copy will include the objectClass attribute. 573 Entry filteredEntry = 574 entry.filterEntry(getAttributes(), typesOnly, 575 isVirtualAttributesOnly(), isRealAttributesOnly()); 576 577 578 // If there is a matched values control, then further pare down the entry 579 // based on the filters that it contains. 580 MatchedValuesControl matchedValuesControl = getMatchedValuesControl(); 581 if (matchedValuesControl != null && !typesOnly) 582 { 583 // First, look at the set of objectclasses. 584 585 // NOTE: the objectClass attribute is also present and must be 586 // dealt with later. 587 AttributeType attrType = CoreSchema.getObjectClassAttributeType(); 588 Iterator<String> ocIterator = 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.getAttributeDescription()); 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.getAttributeDescription()); 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}