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 2008-2010 Sun Microsystems, Inc. 025 * Portions Copyright 2013-2015 ForgeRock AS 026 */ 027package org.opends.server.authorization.dseecompat; 028 029import static org.opends.messages.AccessControlMessages.*; 030import static org.opends.server.authorization.dseecompat.Aci.*; 031 032import java.util.regex.Matcher; 033import java.util.regex.Pattern; 034 035import org.forgerock.i18n.LocalizableMessage; 036import org.opends.server.types.AttributeType; 037import org.opends.server.types.DN; 038import org.forgerock.opendj.ldap.SearchScope; 039 040/** 041 * This class represents target part of an ACI's syntax. This is the part 042 * of an ACI before the ACI body and specifies the entry, attributes, or set 043 * of entries and attributes which the ACI controls access. 044 * 045 * The supported ACI target keywords are: target, targetattr, 046 * targetscope, targetfilter, targattrfilters, targetcontrol and extop. 047 */ 048public class AciTargets { 049 050 /** 051 * ACI syntax has a target keyword. 052 */ 053 private Target target; 054 055 /** 056 * ACI syntax has a targetscope keyword. 057 */ 058 private SearchScope targetScope = SearchScope.WHOLE_SUBTREE; 059 060 /** 061 * ACI syntax has a targetattr keyword. 062 */ 063 private TargetAttr targetAttr; 064 065 /** 066 * ACI syntax has a targetfilter keyword. 067 */ 068 private TargetFilter targetFilter; 069 070 /** 071 * ACI syntax has a targattrtfilters keyword. 072 */ 073 private TargAttrFilters targAttrFilters; 074 075 /** 076 * The ACI syntax has a targetcontrol keyword. 077 */ 078 private TargetControl targetControl; 079 080 /** 081 * The ACI syntax has a extop keyword. 082 */ 083 private ExtOp extOp; 084 085 /** 086 * The number of regular expression group positions in a valid ACI target 087 * expression. 088 */ 089 private static final int targetElementCount = 3; 090 091 /** 092 * Regular expression group position of a target keyword. 093 */ 094 private static final int targetKeywordPos = 1; 095 096 /** 097 * Regular expression group position of a target operator enumeration. 098 */ 099 private static final int targetOperatorPos = 2; 100 101 /** 102 * Regular expression group position of a target expression statement. 103 */ 104 private static final int targetExpressionPos = 3; 105 106 /** 107 * Regular expression used to match a single target rule. 108 */ 109 private static final String targetRegex = 110 OPEN_PAREN + ZERO_OR_MORE_WHITESPACE + WORD_GROUP + 111 ZERO_OR_MORE_WHITESPACE + "(!?=)" + ZERO_OR_MORE_WHITESPACE + 112 "\"([^\"]+)\"" + ZERO_OR_MORE_WHITESPACE + CLOSED_PAREN + 113 ZERO_OR_MORE_WHITESPACE; 114 115 /** 116 * Regular expression used to match one or more target rules. The pattern is 117 * part of a general ACI verification. 118 */ 119 public static final String targetsRegex = "(" + targetRegex + ")*"; 120 121 /** 122 * Rights that are skipped for certain target evaluations. 123 * The test is use the skipRights array is: 124 * 125 * Either the ACI has a targetattr's rule and the current 126 * attribute type is null or the current attribute type has 127 * a type specified and the targetattr's rule is null. 128 * 129 * The actual check against the skipRights array is: 130 * 131 * 1. Is the ACI's rights in this array? For example, 132 * allow(all) or deny(add) 133 * 134 * AND 135 * 136 * 2. Is the rights from the LDAP operation in this array? For 137 * example, an LDAP add would have rights of add and all. 138 * 139 * If both are true, than the target match test returns true 140 * for this ACI. 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 LocalizableMessage 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 LocalizableMessage 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 LocalizableMessage 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 LocalizableMessage 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 LocalizableMessage 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 LocalizableMessage 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 LocalizableMessage 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 LocalizableMessage 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 LocalizableMessage 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 LocalizableMessage 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 LocalizableMessage 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 { 402 return SearchScope.BASE_OBJECT; 403 } 404 else if(expression.equalsIgnoreCase("onelevel")) 405 { 406 return SearchScope.SINGLE_LEVEL; 407 } 408 else if(expression.equalsIgnoreCase("subtree")) 409 { 410 return SearchScope.WHOLE_SUBTREE; 411 } 412 else if(expression.equalsIgnoreCase("subordinate")) 413 { 414 return SearchScope.SUBORDINATES; 415 } 416 else { 417 LocalizableMessage message = 418 WARN_ACI_SYNTAX_INVALID_TARGETSCOPE_EXPRESSION.get(expression); 419 throw new AciException(message); 420 } 421 } 422 423 /** 424 * Checks an ACI's targetfilter rule information against a target match 425 * context. 426 * @param aci The ACI to try an match the targetfilter of. 427 * @param matchCtx The target match context containing information needed 428 * to perform a target match. 429 * @return True if the targetfilter rule matched the target context. 430 */ 431 public static boolean isTargetFilterApplicable(Aci aci, 432 AciTargetMatchContext matchCtx) { 433 TargetFilter targetFilter=aci.getTargets().getTargetFilter(); 434 return targetFilter == null || targetFilter.isApplicable(matchCtx); 435 } 436 437 /** 438 * Check an ACI's targetcontrol rule against a target match context. 439 * 440 * @param aci The ACI to match the targetcontrol against. 441 * @param matchCtx The target match context containing the information 442 * needed to perform the target match. 443 * @return True if the targetcontrol rule matched the target context. 444 */ 445 public static boolean isTargetControlApplicable(Aci aci, 446 AciTargetMatchContext matchCtx) { 447 TargetControl targetControl=aci.getTargets().getTargetControl(); 448 return targetControl != null && targetControl.isApplicable(matchCtx); 449 } 450 451 /** 452 * Check an ACI's extop rule against a target match context. 453 * 454 * @param aci The ACI to match the extop rule against. 455 * @param matchCtx The target match context containing the information 456 * needed to perform the target match. 457 * @return True if the extop rule matched the target context. 458 */ 459 public static boolean isExtOpApplicable(Aci aci, 460 AciTargetMatchContext matchCtx) { 461 ExtOp extOp=aci.getTargets().getExtOp(); 462 return extOp != null && extOp.isApplicable(matchCtx); 463 } 464 465 466 /** 467 * Check an ACI's targattrfilters rule against a target match context. 468 * 469 * @param aci The ACI to match the targattrfilters against. 470 * @param matchCtx The target match context containing the information 471 * needed to perform the target match. 472 * @return True if the targattrfilters rule matched the target context. 473 */ 474 public static boolean isTargAttrFiltersApplicable(Aci aci, 475 AciTargetMatchContext matchCtx) { 476 boolean ret=true; 477 TargAttrFilters targAttrFilters=aci.getTargets().getTargAttrFilters(); 478 if(targAttrFilters != null) { 479 if((matchCtx.hasRights(ACI_ADD) && 480 targAttrFilters.hasMask(TARGATTRFILTERS_ADD)) || 481 (matchCtx.hasRights(ACI_DELETE) && 482 targAttrFilters.hasMask(TARGATTRFILTERS_DELETE))) 483 { 484 ret=targAttrFilters.isApplicableAddDel(matchCtx); 485 } 486 else if((matchCtx.hasRights(ACI_WRITE_ADD) && 487 targAttrFilters.hasMask(TARGATTRFILTERS_ADD)) || 488 (matchCtx.hasRights(ACI_WRITE_DELETE) && 489 targAttrFilters.hasMask(TARGATTRFILTERS_DELETE))) 490 { 491 ret=targAttrFilters.isApplicableMod(matchCtx, aci); 492 } 493 } 494 return ret; 495 } 496 497 /* 498 * TODO Evaluate making this method more efficient. 499 * The isTargetAttrApplicable method looks a lot less efficient than it 500 * could be with regard to the logic that it employs and the repeated use 501 * of method calls over local variables. 502 */ 503 /** 504 * Checks an provided ACI's targetattr rule against a target match 505 * context. 506 * 507 * @param aci The ACI to evaluate. 508 * @param targetMatchCtx The target match context to check the ACI against. 509 * @return True if the targetattr matched the target context. 510 */ 511 public static boolean isTargetAttrApplicable(Aci aci, 512 AciTargetMatchContext targetMatchCtx) { 513 boolean ret=true; 514 if(!targetMatchCtx.getTargAttrFiltersMatch()) { 515 TargetAttr targetAttr = aci.getTargets().getTargetAttr(); 516 AttributeType attrType = targetMatchCtx.getCurrentAttributeType(); 517 boolean isFirstAttr=targetMatchCtx.isFirstAttribute(); 518 519 if (attrType != null && targetAttr != null) { 520 ret=TargetAttr.isApplicable(attrType,targetAttr); 521 setEvalAttributes(targetMatchCtx,targetAttr,ret); 522 } else if (attrType != null || targetAttr != null) { 523 if (aci.hasRights(skipRights) 524 && skipRightsHasRights(targetMatchCtx.getRights())) { 525 ret = true; 526 } else { 527 ret = attrType == null 528 && targetAttr != null 529 && aci.hasRights(ACI_WRITE); 530 } 531 } 532 if (isFirstAttr && targetAttr == null 533 && aci.getTargets().getTargAttrFilters() == null) 534 { 535 targetMatchCtx.setEntryTestRule(true); 536 } 537 } 538 return ret; 539 } 540 541 /** 542 * Try and match a one or more of the specified rights in the skiprights 543 * mask. 544 * @param rights The rights to check for. 545 * @return True if the one or more of the specified rights are in the 546 * skiprights rights mask. 547 */ 548 public static boolean skipRightsHasRights(int rights) { 549 //geteffectiverights sets this flag, turn it off before evaluating. 550 int tmpRights=rights & ~ACI_SKIP_PROXY_CHECK; 551 return (skipRights & tmpRights) == tmpRights; 552 } 553 554 555 /** 556 * Wrapper class that passes an ACI, an ACI's targets and the specified 557 * target match context's resource entry DN to the main isTargetApplicable 558 * method. 559 * @param aci The ACI currently be matched. 560 * @param matchCtx The target match context to match against. 561 * @return True if the target matched the ACI. 562 */ 563 public static boolean isTargetApplicable(Aci aci, 564 AciTargetMatchContext matchCtx) { 565 return isTargetApplicable(aci, aci.getTargets(), 566 matchCtx.getResourceEntry().getName()); 567 } 568 569 /* 570 * TODO Investigate supporting alternative representations of the scope. 571 * 572 * Should we also consider supporting alternate representations of the 573 * scope values (in particular, allow "one" in addition to "onelevel" 574 * and "sub" in addition to "subtree") to match the very common 575 * abbreviations in widespread use for those terms? 576 */ 577 /** 578 * Main target isApplicable method. This method performs the target keyword 579 * match functionality, which allows for directory entry "targeting" using 580 * the specified ACI, ACI targets class and DN. 581 * 582 * @param aci The ACI to match the target against. 583 * @param targets The targets to use in this evaluation. 584 * @param entryDN The DN to use in this evaluation. 585 * @return True if the ACI matched the target and DN. 586 */ 587 public static boolean isTargetApplicable(Aci aci, 588 AciTargets targets, DN entryDN) { 589 DN targetDN=aci.getDN(); 590 /* 591 * Scoping of the ACI uses either the DN of the entry 592 * containing the ACI (aci.getDN above), or if the ACI item 593 * contains a simple target DN and a equality operator, that 594 * simple target DN is used as the target DN. 595 */ 596 if(targets.getTarget() != null && !targets.getTarget().isPattern()) { 597 EnumTargetOperator op=targets.getTarget().getOperator(); 598 if(op != EnumTargetOperator.NOT_EQUALITY) 599 { 600 targetDN=targets.getTarget().getDN(); 601 } 602 } 603 //Check if the scope is correct. 604 switch(targets.getTargetScope().asEnum()) { 605 case BASE_OBJECT: 606 if(!targetDN.equals(entryDN)) 607 { 608 return false; 609 } 610 break; 611 case SINGLE_LEVEL: 612 /* 613 * We use the standard definition of single level to mean the 614 * immediate children only -- not the target entry itself. 615 * Sun CR 6535035 has been raised on DSEE: 616 * Non-standard interpretation of onelevel in ACI targetScope. 617 */ 618 if(!targetDN.equals(entryDN.parent())) 619 { 620 return false; 621 } 622 break; 623 case WHOLE_SUBTREE: 624 if(!entryDN.isDescendantOf(targetDN)) 625 { 626 return false; 627 } 628 break; 629 case SUBORDINATES: 630 if (entryDN.size() <= targetDN.size() || 631 !entryDN.isDescendantOf(targetDN)) { 632 return false; 633 } 634 break; 635 default: 636 return false; 637 } 638 /* 639 * The entry is in scope. For inequality checks, scope was tested 640 * against the entry containing the ACI. If operator is inequality, 641 * check that it doesn't match the target DN. 642 */ 643 if(targets.getTarget() != null && 644 !targets.getTarget().isPattern()) { 645 EnumTargetOperator op=targets.getTarget().getOperator(); 646 if(op == EnumTargetOperator.NOT_EQUALITY) { 647 DN tmpDN=targets.getTarget().getDN(); 648 if(entryDN.isDescendantOf(tmpDN)) 649 { 650 return false; 651 } 652 } 653 } 654 /* 655 * There is a pattern, need to match the substring filter 656 * created when the ACI was decoded. If inequality flip the 657 * result. 658 */ 659 if(targets.getTarget() != null && 660 targets.getTarget().isPattern()) { 661 final boolean ret = targets.getTarget().matchesPattern(entryDN); 662 EnumTargetOperator op=targets.getTarget().getOperator(); 663 if(op == EnumTargetOperator.NOT_EQUALITY) 664 { 665 return !ret; 666 } 667 return ret; 668 } 669 return true; 670 } 671 672 673 /** 674 * The method is used to try and determine if a targetAttr expression that 675 * is applicable has a '*' (or '+' operational attributes) token or if it 676 * was applicable because of a specific attribute type declared in the 677 * targetattrs expression (i.e., targetattrs=cn). 678 * 679 * 680 * @param ctx The ctx to check against. 681 * @param targetAttr The targetattrs part of the ACI. 682 * @param ret The is true if the ACI has already been evaluated to be 683 * applicable. 684 */ 685 private static 686 void setEvalAttributes(AciTargetMatchContext ctx, TargetAttr targetAttr, 687 boolean ret) { 688 ctx.clearEvalAttributes(ACI_USER_ATTR_STAR_MATCHED); 689 ctx.clearEvalAttributes(ACI_OP_ATTR_PLUS_MATCHED); 690 /* 691 If an applicable targetattr's match rule has not 692 been seen (~ACI_FOUND_OP_ATTR_RULE or ~ACI_FOUND_USER_ATTR_RULE) and 693 the current attribute type is applicable because of a targetattr all 694 user (or operational) attributes rule match, 695 set a flag to indicate this situation (ACI_USER_ATTR_STAR_MATCHED or 696 ACI_OP_ATTR_PLUS_MATCHED). This check also catches the following case 697 where the match was by a specific attribute type (either user or 698 operational) and the other attribute type has an all attribute token. 699 For example, the expression is: (targetattrs="cn || +) and the current 700 attribute type is cn. 701 */ 702 if(ret && targetAttr.isAllUserAttributes() && 703 !ctx.hasEvalUserAttributes()) 704 { 705 ctx.setEvalUserAttributes(ACI_USER_ATTR_STAR_MATCHED); 706 } 707 else 708 { 709 ctx.setEvalUserAttributes(ACI_FOUND_USER_ATTR_RULE); 710 } 711 712 if(ret && targetAttr.isAllOpAttributes() && 713 !ctx.hasEvalOpAttributes()) 714 { 715 ctx.setEvalOpAttributes(ACI_OP_ATTR_PLUS_MATCHED); 716 } 717 else 718 { 719 ctx.setEvalOpAttributes(ACI_FOUND_OP_ATTR_RULE); 720 } 721 } 722}