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}