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