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}