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
010     * trunk/opends/resource/legal-notices/OpenDS.LICENSE
011     * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
012     * See the License for the specific language governing permissions
013     * and limitations under the License.
014     *
015     * When distributing Covered Code, include this CDDL HEADER in each
016     * file and include the License file at
017     * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
018     * add the following below this CDDL HEADER, with the fields enclosed
019     * by brackets "[]" replaced with your own identifying information:
020     *      Portions Copyright [yyyy] [name of copyright owner]
021     *
022     * CDDL HEADER END
023     *
024     *
025     *      Copyright 2008 Sun Microsystems, Inc.
026     */
027    
028    package org.opends.server.authorization.dseecompat;
029    import org.opends.messages.Message;
030    
031    import static org.opends.messages.AccessControlMessages.*;
032    import static org.opends.server.authorization.dseecompat.Aci.*;
033    import org.opends.server.types.AttributeType;
034    import org.opends.server.types.DN;
035    import org.opends.server.types.SearchScope;
036    import java.util.regex.Matcher;
037    import java.util.regex.Pattern;
038    
039    /**
040     * This class represents target part of an ACI's syntax. This is the part
041     * of an ACI before the ACI body and specifies the entry, attributes, or set
042     * of entries and attributes which the ACI controls access.
043     *
044     * The supported  ACI target keywords are: target, targetattr,
045     * targetscope, targetfilter, targattrfilters, targetcontrol and extop.
046     */
047    public class AciTargets {
048    
049        /*
050         * ACI syntax has a target keyword.
051         */
052        private Target target = null ;
053    
054        /*
055         * ACI syntax has a targetscope keyword.
056         */
057        private SearchScope targetScope = SearchScope.WHOLE_SUBTREE;
058    
059        /*
060         * ACI syntax has a targetattr keyword.
061         */
062        private TargetAttr targetAttr = null ;
063    
064        /*
065         * ACI syntax has a targetfilter keyword.
066         */
067        private TargetFilter targetFilter=null;
068    
069        /*
070         * ACI syntax has a targattrtfilters keyword.
071         */
072        private TargAttrFilters targAttrFilters=null;
073    
074       /**
075        * The ACI syntax has a targetcontrol keyword.
076        */
077        private TargetControl targetControl=null;
078    
079       /**
080        * The ACI syntax has a extop keyword.
081        */
082        private ExtOp extOp=null;
083    
084        /*
085         * The number of regular expression group positions in a valid ACI target
086         * expression.
087         */
088        private static final int targetElementCount = 3;
089    
090        /*
091         *  Regular expression group position of a target keyword.
092         */
093        private static final int targetKeywordPos       = 1;
094    
095        /*
096         *  Regular expression group position of a target operator enumeration.
097         */
098        private static final int targetOperatorPos      = 2;
099    
100        /*
101         *  Regular expression group position of a target expression statement.
102         */
103        private static final int targetExpressionPos    = 3;
104    
105        /*
106         * Regular expression used to match a single target rule.
107         */
108        private static final String targetRegex =
109               OPEN_PAREN +  ZERO_OR_MORE_WHITESPACE  +  WORD_GROUP +
110               ZERO_OR_MORE_WHITESPACE + "(!?=)" + ZERO_OR_MORE_WHITESPACE +
111               "\"([^\"]+)\"" + ZERO_OR_MORE_WHITESPACE + CLOSED_PAREN +
112               ZERO_OR_MORE_WHITESPACE;
113    
114        /**
115        * Regular expression used to match one or more target rules. The patern is
116        * part of a general ACI verification.
117        */
118        public static final String targetsRegex = "(" + targetRegex + ")*";
119    
120        /*
121         * Rights that are skipped for certain target evaluations.
122         * The test is use the skipRights array is:
123         *
124         * Either the ACI has a targetattr's rule and the current
125         * attribute type is null or the current attribute type has
126         * a type specified and the targetattr's rule is null.
127         *
128         * The actual check against the skipRights array is:
129         *
130         *  1. Is the ACI's rights in this array? For example,
131         *     allow(all) or deny(add)
132         *
133         *  AND
134         *
135         *  2. Is the rights from the LDAP operation in this array? For
136         *      example, an LDAP add would have rights of add and all.
137         *
138         *  If both are true, than the target match test returns true
139         *  for this ACI.
140         */
141    
142        private static final int skipRights = (ACI_ADD | ACI_DELETE | ACI_PROXY);
143    
144        /**
145         * Creates an ACI target from the specified arguments. All of these
146         * may be null. If the ACI has no targets defaults will be used.
147         *
148         * @param targetEntry The ACI target keyword class.
149         * @param targetAttr The ACI targetattr keyword class.
150         * @param targetFilter The ACI targetfilter keyword class.
151         * @param targetScope The ACI targetscope keyword class.
152         * @param targAttrFilters The ACI targAttrFilters keyword class.
153         * @param targetControl The ACI targetControl keyword class.
154         * @param extOp The ACI extop keyword class.
155         */
156        private AciTargets(Target targetEntry, TargetAttr targetAttr,
157                           TargetFilter targetFilter,
158                           SearchScope targetScope,
159                           TargAttrFilters targAttrFilters,
160                           TargetControl targetControl,
161                           ExtOp extOp) {
162           this.target=targetEntry;
163           this.targetAttr=targetAttr;
164           this.targetScope=targetScope;
165           this.targetFilter=targetFilter;
166           this.targAttrFilters=targAttrFilters;
167           this.targetControl=targetControl;
168           this.extOp=extOp;
169        }
170    
171        /**
172         * Return class representing the ACI target keyword. May be
173         * null. The default is the use the DN of the entry containing
174         * the ACI and check if the resource entry is a descendant of that.
175         * @return The ACI target class.
176         */
177        private Target getTarget() {
178            return target;
179        }
180    
181        /**
182         * Return class representing the ACI targetattr keyword. May be null.
183         * The default is to not match any attribute types in an entry.
184         * @return The ACI targetattr class.
185         */
186        public TargetAttr getTargetAttr() {
187            return targetAttr;
188        }
189    
190        /**
191         * Return the ACI targetscope keyword. Default is WHOLE_SUBTREE.
192         * @return The ACI targetscope information.
193         */
194        public SearchScope getTargetScope() {
195            return targetScope;
196        }
197    
198        /**
199         * Return class representing the  ACI targetfilter keyword. May be null.
200         * @return The targetscope information.
201         */
202        public TargetFilter getTargetFilter() {
203            return targetFilter;
204        }
205    
206        /**
207         * Return the class representing the ACI targattrfilters keyword. May be
208         * null.
209         * @return The targattrfilters information.
210         */
211        public TargAttrFilters getTargAttrFilters() {
212            return targAttrFilters;
213        }
214    
215       /**
216        * Return the class representing the ACI targetcontrol keyword. May be
217        * null.
218        * @return The targetcontrol information.
219       */
220        public TargetControl getTargetControl() {
221          return targetControl;
222        }
223    
224    
225       /**
226        * Return the class representing the ACI extop keyword. May be
227        * null.
228        * @return The extop information.
229       */
230        public ExtOp getExtOp() {
231          return extOp;
232        }
233    
234        /**
235         * Decode an ACI's target part of the syntax from the string provided.
236         * @param input String representing an ACI target part of syntax.
237         * @param dn The DN of the entry containing the ACI.
238         * @return An AciTargets class representing the decoded ACI target string.
239         * @throws AciException If the provided string contains errors.
240         */
241        public static AciTargets decode(String input, DN dn)
242        throws AciException {
243            Target target=null;
244            TargetAttr targetAttr=null;
245            TargetFilter targetFilter=null;
246            TargAttrFilters targAttrFilters=null;
247            TargetControl targetControl=null;
248            ExtOp extOp=null;
249            SearchScope targetScope=SearchScope.WHOLE_SUBTREE;
250            Pattern targetPattern = Pattern.compile(targetRegex);
251            Matcher targetMatcher = targetPattern.matcher(input);
252            while (targetMatcher.find())
253            {
254                if (targetMatcher.groupCount() != targetElementCount) {
255                    Message message =
256                        WARN_ACI_SYNTAX_INVALID_TARGET_SYNTAX.get(input);
257                    throw new AciException(message);
258                }
259                String keyword = targetMatcher.group(targetKeywordPos);
260                EnumTargetKeyword targetKeyword  =
261                    EnumTargetKeyword.createKeyword(keyword);
262                if (targetKeyword == null) {
263                    Message message =
264                        WARN_ACI_SYNTAX_INVALID_TARGET_KEYWORD.get(keyword);
265                    throw new AciException(message);
266                }
267                String operator =
268                    targetMatcher.group(targetOperatorPos);
269                EnumTargetOperator targetOperator =
270                    EnumTargetOperator.createOperator(operator);
271                if (targetOperator == null) {
272                    Message message =
273                        WARN_ACI_SYNTAX_INVALID_TARGETS_OPERATOR.get(operator);
274                    throw new AciException(message);
275                }
276                String expression = targetMatcher.group(targetExpressionPos);
277                switch(targetKeyword)
278                {
279                case KEYWORD_TARGET:
280                {
281                    if (target == null){
282                        target =  Target.decode(targetOperator, expression, dn);
283                    }
284                    else
285                    {
286                      Message message =
287                              WARN_ACI_SYNTAX_INVALID_TARGET_DUPLICATE_KEYWORDS.
288                                      get("target", input);
289                      throw new AciException(message);
290                    }
291                    break;
292                }
293                case KEYWORD_TARGETCONTROL:
294                {
295                  if (targetControl == null){
296                    targetControl =
297                            TargetControl.decode(targetOperator, expression);
298                  }
299                  else
300                  {
301                    Message message =
302                            WARN_ACI_SYNTAX_INVALID_TARGET_DUPLICATE_KEYWORDS.
303                                    get("targetcontrol", input);
304                    throw new AciException(message);
305                  }
306                  break;
307                }
308                case KEYWORD_EXTOP:
309                {
310                  if (extOp == null){
311                    extOp =  ExtOp.decode(targetOperator, expression);
312                  }
313                  else
314                  {
315                    Message message =
316                            WARN_ACI_SYNTAX_INVALID_TARGET_DUPLICATE_KEYWORDS.
317                                    get("extop", input);
318                    throw new AciException(message);
319                  }
320                  break;
321                }
322                case KEYWORD_TARGETATTR:
323                {
324                    if (targetAttr == null){
325                        targetAttr = TargetAttr.decode(targetOperator,
326                                expression);
327                    }
328                    else {
329                      Message message =
330                              WARN_ACI_SYNTAX_INVALID_TARGET_DUPLICATE_KEYWORDS.
331                                      get("targetattr", input);
332                      throw new AciException(message);
333                    }
334                    break;
335                }
336                case KEYWORD_TARGETSCOPE:
337                {
338                    // Check the operator for the targetscope is EQUALITY
339                    if (targetOperator == EnumTargetOperator.NOT_EQUALITY) {
340                        Message message =
341                                WARN_ACI_SYNTAX_INVALID_TARGET_NOT_OPERATOR.
342                                  get(operator, targetKeyword.name());
343                        throw new AciException(message);
344                    }
345                    targetScope=createScope(expression);
346                    break;
347                }
348                case KEYWORD_TARGETFILTER:
349                {
350                    if (targetFilter == null){
351                        targetFilter = TargetFilter.decode(targetOperator,
352                                expression);
353                    }
354                    else {
355                      Message message =
356                              WARN_ACI_SYNTAX_INVALID_TARGET_DUPLICATE_KEYWORDS.
357                                      get("targetfilter", input);
358                      throw new AciException(message);
359                    }
360                    break;
361                }
362                case KEYWORD_TARGATTRFILTERS:
363                {
364                    if (targAttrFilters == null){
365                        // Check the operator for the targattrfilters is EQUALITY
366                        if (targetOperator == EnumTargetOperator.NOT_EQUALITY) {
367                          Message message =
368                                  WARN_ACI_SYNTAX_INVALID_TARGET_NOT_OPERATOR.
369                                          get(operator, targetKeyword.name());
370                          throw new AciException(message);
371                        }
372                        targAttrFilters = TargAttrFilters.decode(targetOperator,
373                                expression);
374                    }
375                    else {
376                      Message message =
377                              WARN_ACI_SYNTAX_INVALID_TARGET_DUPLICATE_KEYWORDS.
378                                      get("targattrfilters", input);
379                      throw new AciException(message);
380                    }
381                    break;
382                }
383                }
384            }
385            return new AciTargets(target, targetAttr, targetFilter,
386                                  targetScope, targAttrFilters, targetControl,
387                                  extOp);
388        }
389    
390        /**
391         * Evaluates a provided scope string and returns an appropriate
392         * SearchScope enumeration.
393         * @param expression The expression string.
394         * @return An search scope enumeration matching the string.
395         * @throws AciException If the expression is an invalid targetscope
396         * string.
397         */
398        private static SearchScope createScope(String expression)
399        throws AciException {
400            if(expression.equalsIgnoreCase("base"))
401                    return SearchScope.BASE_OBJECT;
402            else if(expression.equalsIgnoreCase("onelevel"))
403                return SearchScope.SINGLE_LEVEL;
404            else if(expression.equalsIgnoreCase("subtree"))
405                return SearchScope.WHOLE_SUBTREE;
406            else if(expression.equalsIgnoreCase("subordinate"))
407                return SearchScope.SUBORDINATE_SUBTREE;
408            else {
409                Message message =
410                    WARN_ACI_SYNTAX_INVALID_TARGETSCOPE_EXPRESSION.get(expression);
411                throw new AciException(message);
412            }
413        }
414    
415        /**
416         * Checks an ACI's targetfilter rule information against a target match
417         * context.
418         * @param aci The ACI to try an match the targetfilter of.
419         * @param matchCtx The target match context containing information needed
420         * to perform a target match.
421         * @return True if the targetfilter rule matched the target context.
422         */
423        public static boolean isTargetFilterApplicable(Aci aci,
424                                                  AciTargetMatchContext matchCtx) {
425            boolean ret=true;
426            TargetFilter targetFilter=aci.getTargets().getTargetFilter();
427            if(targetFilter != null)
428                 ret=targetFilter.isApplicable(matchCtx);
429            return ret;
430        }
431    
432        /**
433         * Check an ACI's targetcontrol rule against a target match context.
434         *
435         * @param aci The ACI to match the targetcontrol against.
436         * @param matchCtx The target match context containing the information
437         *                 needed to perform the target match.
438         * @return  True if the targetcontrol rule matched the target context.
439         */
440        public static boolean isTargetControlApplicable(Aci aci,
441                                                AciTargetMatchContext matchCtx) {
442          boolean ret=false;
443          TargetControl targetControl=aci.getTargets().getTargetControl();
444          if(targetControl != null)
445            ret=targetControl.isApplicable(matchCtx);
446          return ret;
447        }
448    
449        /**
450         * Check an ACI's extop rule against a target match context.
451         *
452         * @param aci The ACI to match the extop rule against.
453         * @param matchCtx The target match context containing the information
454         *                 needed to perform the target match.
455         * @return  True if the extop rule matched the target context.
456         */
457        public static boolean isExtOpApplicable(Aci aci,
458                                                  AciTargetMatchContext matchCtx) {
459          boolean ret=false;
460          ExtOp extOp=aci.getTargets().getExtOp();
461          if(extOp != null)
462            ret=extOp.isApplicable(matchCtx);
463          return ret;
464        }
465    
466    
467        /**
468         * Check an ACI's targattrfilters rule against a target match context.
469         *
470         * @param aci The ACI to match the targattrfilters against.
471         * @param matchCtx  The target match context containing the information
472         * needed to perform the target match.
473         * @return True if the targattrfilters rule matched the target context.
474         */
475        public static boolean isTargAttrFiltersApplicable(Aci aci,
476                                                   AciTargetMatchContext matchCtx) {
477            boolean ret=true;
478            TargAttrFilters targAttrFilters=aci.getTargets().getTargAttrFilters();
479            if(targAttrFilters != null) {
480                if((matchCtx.hasRights(ACI_ADD) &&
481                    targAttrFilters.hasMask(TARGATTRFILTERS_ADD)) ||
482                  (matchCtx.hasRights(ACI_DELETE) &&
483                   targAttrFilters.hasMask(TARGATTRFILTERS_DELETE)))
484                    ret=targAttrFilters.isApplicableAddDel(matchCtx);
485                else if((matchCtx.hasRights(ACI_WRITE_ADD) &&
486                         targAttrFilters.hasMask(TARGATTRFILTERS_ADD)) ||
487                        (matchCtx.hasRights(ACI_WRITE_DELETE) &&
488                        targAttrFilters.hasMask(TARGATTRFILTERS_DELETE)))
489                    ret=targAttrFilters.isApplicableMod(matchCtx, aci);
490            }
491            return ret;
492        }
493    
494        /*
495         * TODO Evaluate making this method more efficient.
496         * The isTargetAttrApplicable method looks a lot less efficient than it
497         * could be with regard to the logic that it employs and the repeated use
498         * of method calls over local variables.
499         */
500        /**
501         * Checks an provided ACI's targetattr rule against a target match
502         * context.
503         *
504         * @param aci The ACI to evaluate.
505         * @param targetMatchCtx The target match context to check the ACI against.
506         * @return True if the targetattr matched the target context.
507         */
508        public static boolean isTargetAttrApplicable(Aci aci,
509                                             AciTargetMatchContext targetMatchCtx) {
510            boolean ret=true;
511            if(!targetMatchCtx.getTargAttrFiltersMatch()) {
512                AciTargets targets=aci.getTargets();
513                AttributeType a=targetMatchCtx.getCurrentAttributeType();
514                int rights=targetMatchCtx.getRights();
515                boolean isFirstAttr=targetMatchCtx.isFirstAttribute();
516                if((a != null) && (targets.getTargetAttr() != null))  {
517                  ret=TargetAttr.isApplicable(a,targets.getTargetAttr());
518                  setEvalAttributes(targetMatchCtx,targets,ret);
519                } else if((a != null) || (targets.getTargetAttr() != null)) {
520                    if((aci.hasRights(skipRights)) &&
521                                                    (skipRightsHasRights(rights)))
522                        ret=true;
523                    else if ((targets.getTargetAttr() != null) &&
524                            (a == null) && (aci.hasRights(ACI_WRITE)))
525                        ret = true;
526                    else
527                        ret = false;
528                }
529                if((isFirstAttr) && (aci.getTargets().getTargetAttr() == null)
530                    && aci.getTargets().getTargAttrFilters() == null)
531                    targetMatchCtx.setEntryTestRule(true);
532            }
533            return ret;
534        }
535    
536        /**
537         * Try and match a one or more of the specified rights in the skiprights
538         * mask.
539         * @param rights The rights to check for.
540         * @return  True if the one or more of the specified rights are in the
541         * skiprights rights mask.
542         */
543        public static boolean skipRightsHasRights(int rights) {
544          //geteffectiverights sets this flag, turn it off before evaluating.
545          int tmpRights=rights & ~ACI_SKIP_PROXY_CHECK;
546          return  ((skipRights & tmpRights) == tmpRights);
547        }
548    
549    
550        /**
551         * Wrapper class that passes an ACI, an ACI's targets and the specified
552         * target match context's resource entry DN to the main isTargetApplicable
553         * method.
554         * @param aci The ACI currently be matched.
555         * @param matchCtx The target match context to match against.
556         * @return True if the target matched the ACI.
557         */
558        public static boolean isTargetApplicable(Aci aci,
559                                                 AciTargetMatchContext matchCtx) {
560            return isTargetApplicable(aci, aci.getTargets(),
561                                            matchCtx.getResourceEntry().getDN());
562        }
563    
564        /*
565         * TODO Investigate supporting alternative representations of the scope.
566         *
567         * Should we also consider supporting alternate representations of the
568         * scope values (in particular, allow "one" in addition to "onelevel"
569         * and "sub" in addition to "subtree") to match the very common
570         * abbreviations in widespread use for those terms?
571         */
572        /**
573         * Main target isApplicable method. This method performs the target keyword
574         * match functionality, which allows for directory entry "targeting" using
575         * the specifed ACI, ACI targets class and DN.
576         * @param aci The ACI to match the target against.
577         * @param targets The targets to use in this evaluation.
578         * @param entryDN The DN to use in this evaluation.
579         * @return True if the ACI matched the target and DN.
580         */
581    
582        public static boolean isTargetApplicable(Aci aci,
583                AciTargets targets, DN entryDN) {
584            boolean ret=true;
585            DN targetDN=aci.getDN();
586            /*
587             * Scoping of the ACI uses either the DN of the entry
588             * containing the ACI (aci.getDN above), or if the ACI item
589             * contains a simple target DN and a equality operator, that
590             * simple target DN is used as the target DN.
591             */
592            if((targets.getTarget() != null) &&
593                    (!targets.getTarget().isPattern())) {
594                EnumTargetOperator op=targets.getTarget().getOperator();
595                if(op != EnumTargetOperator.NOT_EQUALITY)
596                    targetDN=targets.getTarget().getDN();
597            }
598            //Check if the scope is correct.
599            switch(targets.getTargetScope()) {
600            case BASE_OBJECT:
601                if(!targetDN.equals(entryDN))
602                    return false;
603                break;
604            case SINGLE_LEVEL:
605                /**
606                 * We use the standard definition of single level to mean the
607                 * immediate children only -- not the target entry itself.
608                 * Sun CR 6535035 has been raised on DSEE:
609                 * Non-standard interpretation of onelevel in ACI targetScope.
610                 */
611                if(!entryDN.getParent().equals(targetDN))
612                    return false;
613                break;
614            case WHOLE_SUBTREE:
615                if(!entryDN.isDescendantOf(targetDN))
616                    return false;
617                break;
618            case SUBORDINATE_SUBTREE:
619                if ((entryDN.getNumComponents() <= targetDN.getNumComponents()) ||
620                     !entryDN.isDescendantOf(targetDN)) {
621                  return false;
622                }
623                break;
624            default:
625                return false;
626            }
627            /*
628             * The entry is in scope. For inequality checks, scope was tested
629             * against the entry containing the ACI. If operator is inequality,
630             * check that it doesn't match the target DN.
631             */
632            if((targets.getTarget() != null) &&
633                    (!targets.getTarget().isPattern())) {
634                EnumTargetOperator op=targets.getTarget().getOperator();
635                if(op == EnumTargetOperator.NOT_EQUALITY) {
636                    DN tmpDN=targets.getTarget().getDN();
637                    if(entryDN.isDescendantOf(tmpDN))
638                        return false;
639                }
640            }
641            /*
642             * There is a pattern, need to match the substring filter
643             * created when the ACI was decoded. If inequality flip the
644             * result.
645             */
646            if((targets.getTarget() != null) &&
647                    (targets.getTarget().isPattern()))  {
648                ret=targets.getTarget().matchesPattern(entryDN);
649                EnumTargetOperator op=targets.getTarget().getOperator();
650                if(op == EnumTargetOperator.NOT_EQUALITY)
651                    ret=!ret;
652            }
653            return ret;
654        }
655    
656    
657        /**
658         * The method is used to try and determine if a targetAttr expression that
659         * is applicable has a '*' (or '+' operational attributes) token or if it
660         * was applicable because of a specific attribute type declared in the
661         * targetattrs expression (i.e., targetattrs=cn).
662         *
663         *
664         * @param ctx  The ctx to check against.
665         * @param targets The targets part of the ACI.
666         * @param ret  The is true if the ACI has already been evaluated to be
667         *             applicable.
668         */
669        private static
670        void setEvalAttributes(AciTargetMatchContext ctx, AciTargets targets,
671                               boolean ret) {
672            ctx.clearEvalAttributes(ACI_USER_ATTR_STAR_MATCHED);
673            ctx.clearEvalAttributes(ACI_OP_ATTR_PLUS_MATCHED);
674            /*
675             If an applicable targetattr's match rule has not
676             been seen (~ACI_FOUND_OP_ATTR_RULE or ~ACI_FOUND_USER_ATTR_RULE) and
677             the current attribute type is applicable because of a targetattr all
678             user (or operational) attributes rule match,
679             set a flag to indicate this situation (ACI_USER_ATTR_STAR_MATCHED or
680             ACI_OP_ATTR_PLUS_MATCHED). This check also catches the following case
681             where the match was by a specific attribute type (either user or
682             operational) and the other attribute type has an all attribute token.
683             For example, the expression is: (targetattrs="cn || +) and the current
684             attribute type is cn.
685            */
686            if(ret && targets.getTargetAttr().isAllUserAttributes() &&
687                    !ctx.hasEvalUserAttributes())
688              ctx.setEvalUserAttributes(ACI_USER_ATTR_STAR_MATCHED);
689            else
690              ctx.setEvalUserAttributes(ACI_FOUND_USER_ATTR_RULE);
691            if(ret && targets.getTargetAttr().isAllOpAttributes() &&
692                    !ctx.hasEvalOpAttributes())
693              ctx.setEvalOpAttributes(ACI_OP_ATTR_PLUS_MATCHED);
694            else
695              ctx.setEvalOpAttributes(ACI_FOUND_OP_ATTR_RULE);
696        }
697    }