mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

dugan
21.31.2007 26c5a38c99c89eb3007853ab56a5762fde997179
Initial commit of the dseecompat ACI code
45 files added
9002 ■■■■■ changed files
opends/src/server/org/opends/server/authorization/dseecompat/Aci.java 207 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/authorization/dseecompat/AciBody.java 255 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/authorization/dseecompat/AciContainer.java 426 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/authorization/dseecompat/AciEvalContext.java 143 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/authorization/dseecompat/AciException.java 107 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/authorization/dseecompat/AciHandler.java 759 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/authorization/dseecompat/AciLDAPOperationContainer.java 120 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/authorization/dseecompat/AciList.java 251 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/authorization/dseecompat/AciListenerManager.java 210 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/authorization/dseecompat/AciMessages.java 827 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/authorization/dseecompat/AciProvider.java 85 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/authorization/dseecompat/AciTargetMatchContext.java 134 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/authorization/dseecompat/AciTargets.java 472 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/authorization/dseecompat/AuthMethod.java 112 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/authorization/dseecompat/BindRule.java 538 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/authorization/dseecompat/DNS.java 160 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/authorization/dseecompat/DayOfWeek.java 96 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/authorization/dseecompat/EnumAccessType.java 88 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/authorization/dseecompat/EnumAuthMethod.java 116 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/authorization/dseecompat/EnumBindRuleKeyword.java 118 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/authorization/dseecompat/EnumBindRuleType.java 104 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/authorization/dseecompat/EnumBooleanTypes.java 90 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/authorization/dseecompat/EnumDayOfWeek.java 154 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/authorization/dseecompat/EnumEvalResult.java 111 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/authorization/dseecompat/EnumRight.java 173 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/authorization/dseecompat/EnumTargetKeyword.java 108 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/authorization/dseecompat/EnumTargetOperator.java 81 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/authorization/dseecompat/EnumUserDNType.java 84 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/authorization/dseecompat/GroupDN.java 151 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/authorization/dseecompat/IpBitsNetworkCriteria.java 200 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/authorization/dseecompat/IpCriteria.java 291 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/authorization/dseecompat/IpMaskNetworkCriteria.java 140 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/authorization/dseecompat/KeywordBindRule.java 44 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/authorization/dseecompat/ParentInheritance.java 172 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/authorization/dseecompat/PermBindRulePair.java 93 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/authorization/dseecompat/Permission.java 117 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/authorization/dseecompat/RoleDN.java 157 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/authorization/dseecompat/TargAttrFilters.java 55 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/authorization/dseecompat/Target.java 198 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/authorization/dseecompat/TargetAttr.java 187 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/authorization/dseecompat/TargetFilter.java 103 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/authorization/dseecompat/TimeOfDay.java 121 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/authorization/dseecompat/UserAttr.java 388 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/authorization/dseecompat/UserDN.java 385 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/authorization/dseecompat/UserDNTypeURL.java 71 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/authorization/dseecompat/Aci.java
New file
@@ -0,0 +1,207 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying * information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.authorization.dseecompat;
import org.opends.server.types.ByteString;
import org.opends.server.types.DN;
import static org.opends.server.messages.MessageHandler.*;
import static org.opends.server.authorization.dseecompat.AciMessages.*;
import java.util.regex.Pattern;
/**
 * The Aci class represents ACI strings.
 */
public class Aci  {
    /*
     * The body of the ACI is the version, name and permission-bind rule
     * pairs.
     */
    private AciBody body;
    /*
     * The ACI targets.
     */
    private AciTargets targets=null;
    /**
     * The ACIs are on a linked list hashed by the ACI entry DN.
     * Next points to the next Aci object in the list.
     */
    /*
     * TODO Remove this linked list an replace with an array
     * of ACIs.
     */
    Aci next = null;
    /**
     * Version that we support.
     */
    public static final String supportedVersion="3.0";
    private String aciString;
    /*
     * The DN of the entry containing this ACI.
     */
    private DN dn;
    /*
     * This regular expression is used to do a quick syntax check
     * when an ACI is being decoded.
     */
    private static final String aciRegex =
            "^\\s*" + AciTargets.targetsRegex + "\\s*"+
             AciBody.bodyRegx + "\\s*$";
    /**
     * Construct a new Aci from the provided arguments.
     * @param input The string representation of the ACI.
     * @param dn The DN of entry containing the ACI.
     * @param body The body of the ACI.
     * @param targets The targets of the ACI.
     */
    private  Aci(String input, DN dn, AciBody body, AciTargets targets) {
        this.aciString  = input;
        this.dn=dn;
        this.body=body;
        this.targets=targets;
    }
    /**
     * Decode an ACI byte string.
     * @param byteString The ByteString containing the ACI string.
     * @param dn DN of the ACI entry.
     * @return  Returns a decoded ACI representing the string argument.
     * @throws AciException If the parsing of the ACI string fails.
     */
    //MPD remove ConfigException after fixing David's problem
    public static Aci decode (ByteString byteString, DN dn)
    throws AciException {
        String input=byteString.stringValue();
        //Perform an quick pattern check against the string to catch any
        //obvious syntax errors.
        if (!Pattern.matches(aciRegex, input)) {
            int msgID = MSGID_ACI_SYNTAX_GENERAL_PARSE_FAILED;
            String message = getMessage(msgID, input);
            throw new AciException(msgID, message);
        }
        //Decode the body first.
        AciBody body=AciBody.decode(input);
        //Create a substring from the start of the string to start of
        //the body. That should be the target.
        String targetStr = input.substring(0, body.getMatcherStartPos());
        //Decode that target string using the substring.
        AciTargets targets=AciTargets.decode(targetStr, dn);
        return new Aci(input, dn, body, targets);
    }
    /**
     * Return the string representation of the ACI. This was the string that
     * was used to create the Aci class.
     * @return A string representation of the ACI.
     */
    public String toString() {
        return aciString;
    }
    /**
     * Returns the targets of the ACI.
     * @return Any AciTargets of the ACI. There may be no targets
     * so this might be null.
     */
    public AciTargets getTargets() {
        return targets;
    }
    /**
     * Return the DN of the entry containing the ACI.
     * @return The DN of the entry containing the ACI.
     */
    public DN getDN() {
        return dn;
    }
    /**
     * Test if the given ACI is applicable using the target match information
     * provided. The ACI target can have four keywords at this time:
     *
     *       1. target - checked in isTargetApplicable.
     *       2. targetscope - checked in isTargetApplicable.
     *       3. targetfilter - checked in isTargetFilterApplicable.
     *       4. targetattr - checked in isTargetAttrApplicable.
     *
     * One and two are checked for match first. If they return true, then
     * three is checked. Lastly four is checked.
     *
     * @param aci The ACI to test.
     * @param matchCtx The target matching context containing all the info
     * needed to match ACI targets.
     * @return  True if this ACI targets are applicable or match.
     */
    public static boolean
    isApplicable(Aci aci, AciTargetMatchContext matchCtx) {
        return AciTargets.isTargetApplicable(aci, matchCtx) &&
                AciTargets.isTargetFilterApplicable(aci, matchCtx) &&
                AciTargets.isTargetAttrApplicable(aci, matchCtx);
    }
    /**
     * Check if the body of the ACI matches the rights specified.
     * @param rights Bit mask representing the rights to match.
     * @return True if the body's rights match one of the rights specified.
     */
    public boolean hasRights(int rights) {
        return body.hasRights(rights);
    }
    /**
     * Re-direct has access type to the body's hasAccessType method.
     * @param accessType The access type to match.
     * @return  True if the body's hasAccessType determines a permission
     * contains this access type (allow or deny are valid types).
     */
    public boolean hasAccessType(EnumAccessType accessType) {
        return body.hasAccessType(accessType);
    }
    /**
     * Evaluate this ACI using the evaluation context provided. Re-direct
     * that calls the body's evaluate method.
     * @param evalCtx The evaluation context to evaluate with.
     * @return EnumEvalResult that contains the evaluation result of this
     * aci evaluation.
     */
    private EnumEvalResult evaluate(AciEvalContext evalCtx) {
        return body.evaluate(evalCtx);
    }
    /**
     * Static class used to evaluate an ACI and evaluation context.
     * @param evalCtx  The context to evaluate with.
     * @param aci The ACI to evaluate.
     * @return EnumEvalResult that contains the evaluation result of the aci
     * evaluation.
     */
    public static EnumEvalResult evaluate(AciEvalContext evalCtx, Aci aci) {
        return aci.evaluate(evalCtx);
    }
}
opends/src/server/org/opends/server/authorization/dseecompat/AciBody.java
New file
@@ -0,0 +1,255 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying * information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.authorization.dseecompat;
import static org.opends.server.authorization.dseecompat.AciMessages.*;
import static org.opends.server.messages.MessageHandler.getMessage;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
 * This class represents the body of an ACI. The body of the ACI is the
 * version, name, and permission-bind rule pairs.
 */
public class AciBody {
    private static final int VERSION = 1;
    private static final int NAME = 2;
    private static final int PERM = 1;
    private static final int RIGHTS = 2;
    private static final int BINDRULE = 3;
    private int startPos=0;
    /*
     * The name of the ACI, currently not used but parsed.
     */
    private String name = null;
    /*
     * The version of the ACi, current not used but parsed and checked
     * for 3.0.
     */
    private String version = null;
    /*
     This structure represents a permission-bind rule pairs. There can be
     several of these.
    */
    private List<PermBindRulePair> permBindRulePairs;
    /*
     * TODO Define constants for these regular expressions to make them more
     * readable.
     * The regular expressions would probably be a lot easier
     * to understand if you defined a number of constants for the
     * individual components and then concatenated them.  For example,
     * "\\s*" could be defined in a constant named ZERO_OR_MORE_SPACES.
     * This would also help make it easier to understand which parentheses
     * were part of the regex and which were part of the ACI syntax.
     */
    private static final String permissionRegex = "(\\w+)\\s*\\(([^()]+)\\)";
    private static final String bindRuleRegex = "(.+?\"[)]*)\\s*;";
    private static final String actionRegex =
            "\\s*" + permissionRegex + "\\s*" + bindRuleRegex;
    private static final String versionRegex = "(\\d\\.\\d)";
    private static final String versionToken = "(?i)version";
    private static final String aclToken = "(?i)acl";
    /**
     * Regular expression used to parse the body of an ACI.
     */
    public static final String bodyRegx =
        "\\(\\s*" + versionToken + "\\s*"
        + versionRegex + "\\s*;\\s*" + aclToken + "\\s*\"(.*)\"\\s*;\\s*"
        + actionRegex + "\\s*\\)";
    /**
     * Regular expression used to parse the header of the ACI body. The
     * header is version and acl name.
     */
    public static final String header =
        "\\(\\s*" + versionToken + "\\s*"
        + versionRegex + "\\s*;\\s*" + aclToken + "\\s*\"(.*?)\"\\s*;";
    /**
     * Construct an ACI body from the specified version, name and
     * permission-bind rule pairs.
     *
     * @param verision The version of the ACI.
     * @param name The name of the ACI.
     * @param startPos The start position in the string of the ACI body.
     * @param permBindRulePairs The set of fully parsed permission-bind rule
     * pairs pertaining to this ACI.
     */
    private AciBody(String verision, String name, int startPos,
            List<PermBindRulePair> permBindRulePairs) {
        this.version=verision;
        this.name=name;
        this.startPos=startPos;
        this.permBindRulePairs=permBindRulePairs;
    }
    /**
     * Decode an ACI string representing the ACI body.
     *
     * @param input String representation of the ACI body.
     * @return An AciBody class representing the decoded ACI body string.
     * @throws AciException If the provided string contains errors.
     */
    public static AciBody decode(String input)
    throws AciException {
        String version=null, name=null;
        int startPos=0;
        List<PermBindRulePair> permBindRulePairs=
                new ArrayList<PermBindRulePair>();
        Pattern bodyPattern = Pattern.compile(header);
        Matcher bodyMatcher = bodyPattern.matcher(input);
        if(bodyMatcher.find()) {
            startPos=bodyMatcher.start();
            version  = bodyMatcher.group(VERSION);
            if (!version.equalsIgnoreCase(Aci.supportedVersion)) {
                int msgID = MSGID_ACI_SYNTAX_INVAILD_VERSION;
                String message = getMessage(msgID, version);
                throw new AciException(msgID, message);
            }
            name = bodyMatcher.group(NAME);
        }
        Pattern bodyPattern1 = Pattern.compile(actionRegex);
        Matcher bodyMatcher1 = bodyPattern1.matcher(input);
        /*
         * The may be many permission-bind rule pairs.
         */
        while(bodyMatcher1.find()) {
         String perm=bodyMatcher1.group(PERM);
         String rights=bodyMatcher1.group(RIGHTS);
         String bRule=bodyMatcher1.group(BINDRULE);
         PermBindRulePair pair = PermBindRulePair.decode(perm, rights, bRule);
         permBindRulePairs.add(pair);
        }
        return new AciBody(version, name, startPos, permBindRulePairs);
    }
    /**
     * Checks all of the permissions in this body for a specific access type.
     * Need to walk down each permission-bind rule pair and call it's
     * hasAccessType method.
     *
     * @param accessType The access type enumeration to search for.
     * @return True if the access type is found in a permission of
     * a permission bind rule pair.
     */
    public boolean hasAccessType(EnumAccessType accessType) {
        List<PermBindRulePair>pairs=getPermBindRulePairs();
         for(PermBindRulePair p : pairs) {
             if(p.hasAccessType(accessType))
                 return true;
         }
         return false;
    }
    /**
     * Search through each permission bind rule associated with this body and
     * try and match a single right of the specified rights.
     *
     * @param rights The rights that are used in the match.
     * @return True if a one or more right of the specified rights matches
     * a body's permission rights.
     */
    public boolean hasRights(int rights) {
        List<PermBindRulePair>pairs=getPermBindRulePairs();
        for(PermBindRulePair p : pairs) {
            if(p.hasRights(rights))
                return true;
        }
        return false;
    }
    /**
     * Retrieve the permission-bind rule pairs of this ACI body.
     *
     * @return The permission-bind rule pairs.
     */
    private List<PermBindRulePair> getPermBindRulePairs() {
        return permBindRulePairs;
    }
    /**
     * Get the start position in the ACI string of the ACI body.
     *
     * @return Index into the ACI string of the ACI body.
     */
    public int getMatcherStartPos() {
        return startPos;
    }
    //TODO Evaluate adding support for the "absolute" deny access
    //     type precedence operator.
    /**
     * Performs an evaluation of the permission-bind rule pairs
     * using the evaluation context. The method walks down
     * each PermBindRulePair object and:
     *
     *  1. Skips a pair if the evaluation context rights don't
     *     apply to that ACI. For example, an LDAP search would skip
     *     an ACI pair that allows writes.
     *
     *  2. The pair's bind rule is evaluated using the evaluation context.
     *  3. The result of the evaluation is itself evaluated. See comments
     *     below in the code.
     *
     * @param evalCtx The evaluation context to evaluate against.
     * @return An enumeration result of the evaluation.
     */
    public  EnumEvalResult evaluate(AciEvalContext evalCtx) {
        EnumEvalResult res=EnumEvalResult.FALSE;
        List<PermBindRulePair>pairs=getPermBindRulePairs();
        for(PermBindRulePair p : pairs) {
            if(!p.hasRights(evalCtx.getRights()))
                continue;
           res=p.getBindRule().evaluate(evalCtx);
           // The evaluation result could be FAIL. Stop processing and return
           //FAIL. Maybe an internal search failed.
           if((res != EnumEvalResult.TRUE) &&
              (res != EnumEvalResult.FALSE)) {
               res=EnumEvalResult.FAIL;
               break;
           //If the access type is DENY and the pair evaluated to TRUE,
           //then stop processing and return TRUE. A deny pair
           //succeeded.
           } else if((p.hasAccessType(EnumAccessType.DENY)) &&
                     (res == EnumEvalResult.TRUE)) {
               res=EnumEvalResult.TRUE;
               break;
           //An allow access type evaluated TRUE, stop processing
           //and return TRUE.
           } else if((p.hasAccessType(EnumAccessType.ALLOW) &&
                     (res == EnumEvalResult.TRUE))) {
               res=EnumEvalResult.TRUE;
               break;
           }
        }
        return res;
    }
}
opends/src/server/org/opends/server/authorization/dseecompat/AciContainer.java
New file
@@ -0,0 +1,426 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying * information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.authorization.dseecompat;
import org.opends.server.types.*;
import org.opends.server.api.ClientConnection;
import org.opends.server.api.Group;
import org.opends.server.core.AddOperation;
import org.opends.server.core.Operation;
import org.opends.server.extensions.TLSConnectionSecurityProvider;
import org.opends.server.util.ServerConstants;
import java.net.InetAddress;
import java.util.LinkedList;
/**
 *  The AciContainer class contains all of the needed information to perform
 *  both target match and evaluate an ACI. Target matching is the process
 *  of testing if an ACI is applicable to an operation, and evaluation is
 *  the actual access evaluation of the ACI.
 */
public abstract class AciContainer
implements AciTargetMatchContext, AciEvalContext {
    /*
     * The allow and deny lists.
     */
    private LinkedList<Aci> denyList, allowList;
    /*
     * The attribute type in the resource entry currently being evaluated.
     */
    private AttributeType attributeType;
    /*
     * The attribute type value in the resource entry currently being
     * evaluated.
     */
    private AttributeValue attributeValue;
    /*
     * True if this is the first attribute type in the resource entry being
     * evaluated.
     */
    private boolean isFirst = false;
    /*
     * True if an entry test rule was seen during target matching of an ACI
     * entry. A entry test rule is an ACI with targetattrs target keyword.
     */
    private boolean isEntryTestRule = false;
    /*
     * True if the evaluation of an ACI is from the deny list.
     */
    private boolean isDenyEval;
    /*
     * True if the evaluation is a result of an LDAP add operation.
     */
    private boolean isAddOp=false;
    /*
     * The rights to use in the evaluation of the LDAP operation.
     */
    private int rights;
    /*
     * The entry being evaluated (resource entry).
     */
    private Entry resourceEntry;
    /*
     * The client connection information.
     */
    private ClientConnection clientConnection;
    /*
     * The operation being evaluated.
     */
    private Operation operation;
    /**
     * This constructor is used by all currently supported LDAP operations.
     *
     * @param operation The Operation object being evaluated and target
     * matching.
     * @param rights The rights array to use in evaluation and target matching.
     * @param entry The current entry being evaluated and target matched.
     */
    protected AciContainer(Operation operation, int rights, Entry entry) {
      this.resourceEntry=entry;
      this.operation=operation;
      this.clientConnection=operation.getClientConnection();
      if(operation instanceof AddOperation)
          this.isAddOp=true;
      this.rights = rights;
    }
    /**
     * The list of deny ACIs. These are all of the applicable
     * ACIs that have a deny permission. Note that an ACI can
     * be on both allow and deny list if it has multiple
     * permission-bind rule pairs.
     *
     * @param denys The list of deny ACIs.
     */
    public void setDenyList(LinkedList<Aci> denys) {
        denyList=denys;
    }
    /**
     * The list of allow ACIs. These are all of the applicable
     * ACIs that have an allow permission.
     *
     * @param allows  The list of allow ACIs.
     */
    public void setAllowList(LinkedList<Aci> allows) {
        allowList=allows;
    }
    /**
     * Return the current attribute type being evaluated.
     * @return  Attribute type being evaluated.
     */
    public AttributeType getCurrentAttributeType() {
        return attributeType;
    }
    /**
     * Return the current attribute type value being evaluated.
     * @return Attribute type value being evaluated.
     */
    public AttributeValue getCurrentAttributeValue() {
        return attributeValue;
    }
    /**
     * Set the attribute type to be evaluated.
     * @param type The attribute type to evaluate.
     */
    public void setCurrentAttributeType(AttributeType type) {
        attributeType=type;
    }
    /**
     * Set the attribute type value to be evaluated.
     * @param value The attribute type value to evaluate.
     */
    public void setCurrentAttributeValue(AttributeValue value) {
        attributeValue=value;
    }
    /**
     * Check is this the first attribute being evaluated in an entry.
     * @return  True if it is the first attribute.
     */
    public boolean isFirstAttribute() {
        return isFirst;
    }
    /**
     * Set if this is the first attribute in the entry.
     * @param val True if this is the first attribute being evaluated in the
     * entry.
     */
    public void setIsFirstAttribute(boolean val) {
        isFirst=val;
    }
    /**
     * Check if an entry test rule was seen during target evaluation.
     * @return True if an entry test rule was seen.
     */
    public boolean hasEntryTestRule() {
        return isEntryTestRule;
    }
    /**
     * Used to set if an entry test rule was seen during target evaluation.
     * @param val Set to true if an entry test rule was seen.
     */
    public void setEntryTestRule(boolean val) {
        isEntryTestRule=val;
    }
    /**
     * Get the entry being evaluated (known as the resource entry).
     * @return  The entry being evaluated.
     */
    public Entry getResourceEntry() {
        return resourceEntry;
    }
    /**
     * Get the entry that corresponds to the client DN.
     * @return The client entry.
     */
    public Entry getClientEntry() {
       return clientConnection.getAuthenticationInfo().getAuthorizationEntry();
    }
    /**
     * Get the deny list of ACIs.
     * @return The deny ACI list.
     */
    public LinkedList<Aci> getDenyList() {
        return denyList;
     }
    /**
     * Get the allow list of ACIs.
     * @return The allow ACI list.
     */
    public LinkedList<Aci> getAllowList() {
       return allowList;
    }
    /**
     * Check is this is a deny ACI evaluation.
     * @return  True if the evaluation is using an ACI from
     * deny list.
     */
    public boolean isDenyEval() {
        return isDenyEval;
    }
    /**
     * Check is this operation bound anonymously.
     * @return  True if the authentication is anonymous.
     */
    public boolean isAnonymousUser() {
        return !clientConnection.getAuthenticationInfo().isAuthenticated();
    }
    /**
     * Set the deny evaluation flag.
     * @param val True if this evaluation is a deny ACI.
     */
    public void setDenyEval(boolean val) {
        isDenyEval = val;
    }
    /**
     * Returns the client authorization DN known as the client DN.
     * @return  The client's authorization DN.
     */
    public DN getClientDN() {
        return clientConnection.getAuthenticationInfo().getAuthorizationDN();
    }
    /**
     * Get the DN of the entry being evaluated.
     * @return The DN of the entry.
     */
    public DN getResourceDN() {
        return resourceEntry.getDN();
    }
    /**
     * Checks if the container's rights has the specified rights.
     * @param  rights The rights to check for.
     * @return True if the container's rights has the specified rights.
     */
    public boolean hasRights(int rights) {
       return (this.rights & rights) != 0;
    }
    /**
     * Return the rights set for this container's LDAP operation.
     * @return  The rights set for the container's LDAP operation.
     */
    public int getRights() {
        return this.rights;
    }
    /**
     * Sets the rights for this container to the specified rights.
     * @param rights The rights to set the container's rights to.
     */
    public void setRights(int rights) {
         this.rights=rights;
    }
    /**
     * Gets the hostname of the remote client.
     * @return  Cannonical hostname of remote client.
     */
    public String getHostName() {
        return clientConnection.getRemoteAddress().getCanonicalHostName();
    }
    /**
     * Gets the remote client's address information.
     * @return  Remote client's address.
     */
    public InetAddress getRemoteAddress() {
        return clientConnection.getRemoteAddress();
    }
    /**
     * Return true if this is an add operation.
     * @return True if this is an add operation.
     */
    public boolean isAddOperation() {
        return isAddOp;
    }
    /**
     * Tries to determine the authentication information from the connection
     * class. The checks are for simple and SASL, anything else is not a
     * match. If the bind rule needs the SSL client flag then that needs
     * to be set. This code is used by the authmethod bind rule keyword.
     * @param wantSSL True if the bind rule needs the ssl client auth check.
     * @return  Return an enumeration containing the authentication method
     * for this connection.
     */
    public EnumAuthMethod getAuthenticationMethod(boolean wantSSL) {
        EnumAuthMethod method=EnumAuthMethod.AUTHMETHOD_NOMATCH;
        AuthenticationInfo authInfo=clientConnection.getAuthenticationInfo();
        if(authInfo.isAuthenticated()) {
            if(authInfo.hasAuthenticationType(AuthenticationType.SIMPLE))
                method=EnumAuthMethod.AUTHMETHOD_SIMPLE;
            else if(authInfo.hasAuthenticationType(AuthenticationType.SASL))
                method=getSaslAuthenticationMethod(authInfo, wantSSL);
            else
                method=EnumAuthMethod.AUTHMETHOD_NOMATCH;
        }
        return method;
    }
    /*
     * TODO This method needs to be tested.
     * TODO Investigate multi-factor authentication.
     *   Second, OpenDS is devised so that it could be possible to use
     *   multi-factor or step-up authentication, in which the same client
     *   has provided multiple forms of credentials, but this method
     *   expects only a single authentication type.
     */
    /**
     * This method attempts to figure out what the SASL method was/is or
     * what the client auth is.
     * @param authInfo The authentication information to use.
     * @param wantSSL The bin drule wants the SSL client auth status.
     * @return An enumeration containing the SASL bind information.
     */
    private EnumAuthMethod
    getSaslAuthenticationMethod(AuthenticationInfo authInfo, boolean wantSSL) {
        EnumAuthMethod method=EnumAuthMethod.AUTHMETHOD_NOMATCH;
        if(authInfo.hasAuthenticationType(AuthenticationType.SASL)) {
            if(authInfo.hasSASLMechanism(ServerConstants.
                    SASL_MECHANISM_DIGEST_MD5))
                method=EnumAuthMethod.AUTHMETHOD_SASL_MD5;
            else if(authInfo.hasSASLMechanism(ServerConstants.
                    SASL_MECHANISM_GSSAPI))
                method=EnumAuthMethod.AUTHMETHOD_SASL_GSSAPI;
            else if(authInfo.hasSASLMechanism(ServerConstants.
                    SASL_MECHANISM_EXTERNAL)) {
                /*
                 * The bind rule wants ssl client auth information. Need the
                 * security provider to see if the clientAuthPolicy is
                 * required. If it is optional, we really can't determine if
                 * the client auth.
                */
                if(wantSSL) {
                    String mechName=
                        clientConnection.getConnectionSecurityProvider().
                                getSecurityMechanismName();
                    if(mechName.equalsIgnoreCase("TLS")) {
                        TLSConnectionSecurityProvider tlsProv=
                            (TLSConnectionSecurityProvider)clientConnection.
                                           getConnectionSecurityProvider();
                        SSLClientAuthPolicy clientAuthPolicy=
                            tlsProv.getSSLClientAuthPolicy();
                        if(clientAuthPolicy == SSLClientAuthPolicy.REQUIRED)
                            method=EnumAuthMethod.AUTHMETHOD_SSL;
                    } else
                       method=EnumAuthMethod.AUTHMETHOD_NOMATCH;
                } else {
                    method=EnumAuthMethod.AUTHMETHOD_SASL_EXTERNAL;
                }
            } else
                method=EnumAuthMethod.AUTHMETHOD_NOMATCH;
        }
        return method;
    }
    /**
     * Convienance method that checks if the the clientDN is a member of the
     * specified group.
     * @param group The group to check membership in.
     * @return True if the clientDN is a member of the specified group.
     */
    public boolean isMemberOf(Group group) {
        boolean ret;
        try {
           ret=clientConnection.isMemberOf(group, operation);
        } catch (DirectoryException ex) {
            ret=false;
        }
        return  ret;
    }
}
opends/src/server/org/opends/server/authorization/dseecompat/AciEvalContext.java
New file
@@ -0,0 +1,143 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying * information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.authorization.dseecompat;
import org.opends.server.types.DN;
import org.opends.server.types.Entry;
import org.opends.server.api.Group;
import java.net.InetAddress;
import java.util.LinkedList;
/**
 * Interface that provides a view of the AciContainer that is
 * used by the ACI evaluation code to evaluate an ACI.
 */
public interface AciEvalContext
{
    /**
     * Get client DN. The client DN is the authorization DN.
     * @return   The client DN.
     */
    public DN getClientDN();
    /**
     * Get the client entry. The client entry is the entry that corresponds
     * to the client DN.
     * @return The client entry corresponding to the client DN.
     */
    public Entry getClientEntry();
    /**
     * Get the resource DN. The resource DN is the DN of the entry being
     * evaluated.
     * @return   The resource DN.
     */
    public DN getResourceDN();
    /**
     * Get the list of deny ACIs.
     * @return The deny ACI list.
     */
    public LinkedList<Aci> getDenyList();
    /**
     * Get the list allow ACIs.
     * @return The allow ACI list.
     */
    public LinkedList<Aci> getAllowList();
    /**
     * Set when the deny list is being evaluated.
     * @param v True if deny's are being evaluated.
     */
    public void setDenyEval(boolean v);
    /**
     * Returns true if the deny list is being evaluated.
     * @return True if the deny list is being evaluated.
     */
    public boolean isDenyEval();
    /**
     * Check if the remote client is bound anonymously.
     * @return True if client is bound anonymously.
     */
    public boolean isAnonymousUser();
    /**
     * Return the rights set for this container's LDAP operation.
     * @return  The rights set for the container's LDAP operation.
     */
    public int getRights();
    /**
     * Return the entry being evaluated
     * .
     * @return The evaluation entry.
     */
    public Entry getResourceEntry();
    /**
     * Get the hostname of the bound connection.
     * @return The hostname of the connection.
     */
    public String getHostName();
    /**
     * Get the authentication method.
     * @param wantSSL The authmethod bind rule needs the SSL client auth
     * status.
     * @return An Enumeration of the auth method bound as.
     */
    public EnumAuthMethod getAuthenticationMethod(boolean wantSSL);
    /**
     * Get the  address of the bound connection.
     * @return The  address of the bound connection.
     */
    public InetAddress getRemoteAddress();
    /**
     * Return true if this is an add operation, needed by the userattr
     * USERDN parent inheritance level 0 processing.
     * @return True if this is an add operation.
     */
    public boolean isAddOperation();
    /**
     * Return true if the operation associated with this evaluation
     * context is a member of the specified group. Calls the
     * ClientConnection.isMemberOf() method, which checks authorization
     * DN membership in the specified group.
     * @param group The group to check membership in.
     * @return True if the authorization DN of the operation is a
     * member of the specified group.
     */
    public boolean isMemberOf(Group group);
}
opends/src/server/org/opends/server/authorization/dseecompat/AciException.java
New file
@@ -0,0 +1,107 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying * information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.authorization.dseecompat;
import static org.opends.server.loggers.Debug.debugConstructor;
import static org.opends.server.loggers.Debug.debugEnter;
/**
 * The AciException class defines an exception that may be thrown
 * either during ACI syntax verification of an "aci" attribute type value
 * or during evaluation of an LDAP operation using a set of applicable
 * ACIs.
 */
public class AciException extends Exception {
    /**
   * The fully-qualified name of this class for debugging purposes.
   */
  private static final String CLASS_NAME =
       "org.opends.server.authorization.dseecompat.AciException";
  /**
   * The serial version identifier required to satisfy the compiler because this
   * class extends <CODE>java.lang.Exception</CODE>, which implements the
   * <CODE>java.io.Serializable</CODE> interface.  This value was generated
   * using the <CODE>serialver</CODE> command-line utility included with the
   * Java SDK.
   */
  private static final long serialVersionUID = -2763328522960628853L;
    // The unique message ID for the associated message.
    private int messageID;
    /**
     * Constructs a new exception with <code>null</code> as its detail message.
     * The cause is not initialized. Used to break out of a recursive bind rule
     * decode and not print duplicate messages.
     */
    public AciException() {
      super();
    }
    /**
     * Creates a new ACI exception with the provided message.
     *
     * @param  messageID  The unique message ID for the provided message.
     * @param  message    The message to use for this ACI exception.
     */
    public AciException(int messageID, String message) {
      super(message);
      assert debugConstructor(CLASS_NAME, String.valueOf(messageID),
                              String.valueOf(message));
      this.messageID = messageID;
    }
    /**
     * Creates a new ACI exception with the provided message and root
     * cause.
     *
     * @param  messageID  The unique identifier for the associated message.
     * @param  message    The message that explains the problem that occurred.
     * @param  cause      The exception that was caught to trigger this
     *                    exception.
     */
    public AciException(int messageID, String message, Throwable cause) {
      super(message, cause);
      assert debugConstructor(CLASS_NAME, String.valueOf(message),
                              String.valueOf(cause));
      this.messageID = messageID;
    }
  /**
   * Retrieves the message ID for this exception.
   *
   * @return  The message ID for this exception.
   */
  public int getMessageID() {
    assert debugEnter(CLASS_NAME, "getMessageID");
    return messageID;
  }
}
opends/src/server/org/opends/server/authorization/dseecompat/AciHandler.java
New file
@@ -0,0 +1,759 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying * information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.authorization.dseecompat;
import org.opends.server.api.AccessControlHandler;
import static org.opends.server.authorization.dseecompat.AciMessages.*;
import org.opends.server.core.*;
import static org.opends.server.loggers.Debug.debugEnter;
import static org.opends.server.loggers.Error.logError;
import static org.opends.server.messages.MessageHandler.getMessage;
import org.opends.server.types.*;
import static org.opends.server.util.StaticUtils.toLowerCase;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/**
 * The AciHandler class performs the main processing for the
 * dseecompat package.
 */
public class AciHandler extends AccessControlHandler
{
    private static final String CLASS_NAME =
        "org.opends.server.authorization.dseecompat.AciHandler";
    /**
     * ACI_ADD is used to set the container rights for a LDAP add operation.
     */
    public static final int ACI_ADD = 0x0001;
    /**
     * ACI_DELETE is used to set the container rights for a LDAP
     * delete operation.
     */
    public static final int ACI_DELETE = 0x0002;
    /**
     * ACI_READ is used to set the container rights for a LDAP
     * search operation.
     */
    public static final int ACI_READ = 0x0004;
    /**
     * ACI_WRITE is used to set the container rights for a LDAP
     * modify operation.
     */
    public static final int ACI_WRITE = 0x0008;
    /**
     * ACI_COMPARE is used to set the container rights for a LDAP
     * compare operation.
     */
    public static final int ACI_COMPARE = 0x0010;
    /**
     * ACI_SEARCH is used to set the container rights a LDAP search operation.
     */
    public static final int ACI_SEARCH = 0x0020;
    /**
     * ACI_SELF is used for the SELFWRITE right. Currently not implemented.
     */
    public static final int ACI_SELF = 0x0040;
    /**
     * ACI_ALL is used to as a mask for all of the above. These
     * six below are not masked by the ACI_ALL.
     */
    public static final int ACI_ALL = 0x007F;
    /**
     * ACI_PROXY is used for the PROXY right. Currently not implemented.
     */
    public static final int ACI_PROXY = 0x0080;
    /**
     * ACI_IMPORT is used to set the container rights for a LDAP
     * modify dn operation. Currently not implemented.
     */
    public static final int ACI_IMPORT = 0x0100;
    /**
     * ACI_EXPORT is used to set the container rights for a LDAP
     * modify dn operation. Currently not implemented.
     */
    public static final int ACI_EXPORT = 0x0200;
    /**
     * ACI_WRITE_ADD and ACI_WRITE_DELETE are used by the LDAP modify
     * operation. They currently don't have much value; but will be needed
     * once the targetattrfilters target and modify dn are implemented.
     */
    public static final int ACI_WRITE_ADD = 0x800;
    /**
     * See above.
     */
    public static final int ACI_WRITE_DELETE = 0x400;
    /**
     * ACI_NULL is used to set the container rights to all zeros. Used
     * by LDAP modify.
     */
    public static final int ACI_NULL = 0x0000;
    /*
     * The list that holds that ACIs keyed by the DN of the entry
      * holding the ACI.
     */
    private AciList aciList;
    /**
     * Attribute type corresponding to "aci" attribute.
     */
    public static AttributeType aciType;
    /**
     * Constructor that registers the message catalog, creates the ACI list
     * class that manages the ACI list. Instantiates and registers the change
     * notification listener that is used to manage the ACI list on
     * modifications and the backend initialization listener that is used to
     * register/de-register aci attribute types in backends when backends
     * are initialized/finalized.
    */
    public AciHandler() {
        AciMessages.registerMessages();
        aciList = new AciList();
        AciListenerManager aciListenerMgr =
            new AciListenerManager(aciList);
        DirectoryServer.registerChangeNotificationListener(aciListenerMgr);
        DirectoryServer.registerBackendInitializationListener(aciListenerMgr);
        if((aciType = DirectoryServer.getAttributeType("aci")) == null)
            aciType = DirectoryServer.getDefaultAttributeType("aci");
    }
    /*
     * TODO
     * The internal search performed by the searchAcis method will require
     * a presence index on the aci attribute for any database of any significant
     * size.  We should probably consider making this index present by default,
     * because if they aren't using the DSEE-compatible implementation then
     * they probably won't have any instances of the aci attribute.
     */
    /**
     * Checks to see if a LDAP modification is allowed access.
     *
     * @param container  The structure containing the LDAP modifications
     * @param operation The operation to check modify privileges on.
     * operation to check and the evaluation context to apply the check against.
     * @param skipAccessCheck True if access checking should be skipped.
     * @return  True if access is allowed.
     */
    private boolean aciCheckMods(AciLDAPOperationContainer container,
                                 Operation operation,
                                 boolean skipAccessCheck) {
        Entry resourceEntry=container.getResourceEntry();
        DN dn=resourceEntry.getDN();
        List<Modification> modifications=container.getModifications();
        for(Modification m : modifications) {
            Attribute modAttr=m.getAttribute();
            AttributeType modType=modAttr.getAttributeType();
            switch(m.getModificationType()) {
            /*
             * TODO Increment modification type needs to be handled.
             */
                case DELETE:
                case REPLACE:
                {
                    /*
                        Check if we have rights to delete all values of
                        an attribute type in the resource entry.
                    */
                    if(resourceEntry.hasAttribute(modType)) {
                        container.setCurrentAttributeType(modType);
                        List<Attribute> attrList =
                            resourceEntry.getAttribute(modType,null);
                        for (Attribute a : attrList) {
                            for (AttributeValue v : a.getValues()) {
                                container.setCurrentAttributeValue(v);
                                container.setRights(ACI_WRITE_DELETE);
                                if(!skipAccessCheck &&
                                   !accessAllowed(container))
                                    return false;
                            }
                        }
                    }
                }
            }
            if(modAttr.hasValue()) {
               boolean checkPrivileges=true;
               for(AttributeValue v : modAttr.getValues()) {
                   container.setCurrentAttributeType(modType);
                   container.setCurrentAttributeValue(v);
                   if((m.getModificationType() == ModificationType.ADD) ||
                      (m.getModificationType() == ModificationType.REPLACE)) {
                       container.setRights(ACI_WRITE_ADD);
                       if(!skipAccessCheck && !accessAllowed(container))
                           return false;
                   } else if(m.getModificationType()
                           == ModificationType.DELETE) {
                       container.setRights(ACI_WRITE_DELETE);
                       if(!skipAccessCheck && !accessAllowed(container))
                           return false;
                   } else
                       return false;
                   /*
                    Check if the modification type has an "aci" attribute type.
                    If so, check the syntax of that attribute value. Fail the
                    the operation if the syntax check fails.
                    */
                   if(modType.equals(aciType)) {
                       try {
                           /*
                            * Check that the operation has modify privileges if
                            * it contains an "aci" attribute type. Flip the
                            * boolean to false so this check isn't made again
                            * if there are several ACI values being added.
                            */
                           if(checkPrivileges) {
                            if (!operation.getClientConnection().
                               hasPrivilege(Privilege.MODIFY_ACL, operation)) {
                              int  msgID  =
                                    MSGID_ACI_MODIFY_FAILED_PRIVILEGE;
                              String message = getMessage(msgID,
                                      String.valueOf(container.getResourceDN()),
                                    String.valueOf(container.getClientDN()));
                              logError(ErrorLogCategory.ACCESS_CONTROL,
                                         ErrorLogSeverity.SEVERE_WARNING,
                                         message, msgID);
                              return false;
                            }
                            checkPrivileges=false;
                           }
                           Aci.decode(v.getValue(),dn);
                       } catch (AciException ex) {
                           int    msgID  = MSGID_ACI_MODIFY_FAILED_DECODE;
                           String message = getMessage(msgID,
                                   String.valueOf(dn),
                                   ex.getMessage());
                           logError(ErrorLogCategory.ACCESS_CONTROL,
                                    ErrorLogSeverity.SEVERE_WARNING,
                                    message, msgID);
                           return false;
                       }
                   }
               }
            }
        }
        return true;
    }
    /**
     * Performs the test of the deny and allow access lists using the
     * provided evaluation context. The deny list is checked first.
     *
     * @param evalCtx  The evaluation context to use.
     * @return  True if access is allowed.
     */
    private boolean testApplicableLists(AciEvalContext evalCtx) {
        EnumEvalResult res=EnumEvalResult.FALSE;
        //First check deny lists
        LinkedList<Aci>denys=evalCtx.getDenyList();
        evalCtx.setDenyEval(true);
        for(Aci denyAci : denys) {
           res=Aci.evaluate(evalCtx, denyAci);
            //Failure could be returned if a system limit is hit or
            //search fails
           if((res.equals(EnumEvalResult.FAIL) ||
              (res.equals(EnumEvalResult.TRUE)))) {
               return false;
           }
        }
        //Now check the allows -- flip the deny flag to false first.
        evalCtx.setDenyEval(false);
        LinkedList<Aci>allows=evalCtx.getAllowList();
        for(Aci allowAci : allows) {
           res=Aci.evaluate(evalCtx, allowAci);
           if(res.equals(EnumEvalResult.TRUE)) {
               break;
           }
        }
        return res.getBoolVal();
    }
    /**
     * Creates the allow and deny ACI lists based on the provided target
     * match context. These lists are stored in the evaluation context.
     * @param candidates  List of all possible ACI candidates.
     * @param targetMatchCtx Target matching context to use for testing each
     * ACI.
     */
    private void createApplicableList(LinkedList<Aci> candidates,
                                      AciTargetMatchContext targetMatchCtx)
    {
        LinkedList<Aci>denys=new LinkedList<Aci>();
        LinkedList<Aci>allows=new LinkedList<Aci>();
        for(Aci aci : candidates) {
            if(Aci.isApplicable(aci, targetMatchCtx)) {
                if (aci.hasAccessType(EnumAccessType.DENY)) {
                    denys.add(aci);
                }
                if(aci.hasAccessType(EnumAccessType.ALLOW)) {
                   allows.add(aci);
                }
            }
        }
        targetMatchCtx.setAllowList(allows);
        targetMatchCtx.setDenyList(denys);
    }
    /**
     * Check to see if the client entry has BYPASS_ACL privileges
     * for this operation.
     * @param operation The operation to check privileges on.
     * @return True if access checking can be skipped because
     * the operation client connection has BYPASS_ACL privileges.
     */
    boolean skipAccessCheck(Operation operation) {
        return operation.getClientConnection().
                hasPrivilege(Privilege.BYPASS_ACL, operation);
    }
    /**
     * Check access using the specified container. This container will have all
     * of the information to gather applicable ACIs and perform evaluation on
     * them.
     *
     * @param container An ACI operation container which has all of the
     * information needed to check access.
     *
     * @return True if access is allowed.
     */
    private boolean accessAllowed(AciContainer container)
    {
        DN dn = container.getResourceEntry().getDN();
        //For ACI_WRITE_ADD and ACI_WRITE_DELETE set the ACI_WRITE
        //right.
        if(container.hasRights(ACI_WRITE_ADD) ||
           container.hasRights(ACI_WRITE_DELETE))
                container.setRights(container.getRights() | ACI_WRITE);
        /*
         * First get all allowed candidate ACIs.
         */
        LinkedList<Aci>candidates = aciList.getCandidateAcis(dn);
        /*
         * Create an applicable list of ACIs by target matching each
         * candidate ACI against the container's target match view.
         */
        createApplicableList(candidates,container);
        /*
         * Lastly, evaluate the applicable list.
         */
        return(testApplicableLists(container));
    }
    /**
     * Performs an access check against all of the attributes of an entry.
     * The attributes that fail access are removed from the entry. This method
     * performs the processing needed for the filterEntry method processing.
     *
     * @param container The search or compare container which has all of the
     * information needed to filter the attributes for this entry.
     * @return The  entry to send back to the client, minus any attribute
     * types that failed access check.
     */
    private SearchResultEntry
    accessAllowedAttrs(AciLDAPOperationContainer container) {
        Entry e=container.getResourceEntry();
        List<AttributeType> typeList=getAllAttrs(e);
        for(AttributeType attrType : typeList) {
            container.setCurrentAttributeType(attrType);
            if(!accessAllowed(container)) {
                e.removeAttribute(attrType);
            }
        }
        return container.getSearchResultEntry();
    }
    /**
     * Gathers all of the attribute types in an entry along with the
     * "objectclass" attribute type in a List. The "objectclass" attribute is
     * added to the list first so it is evaluated first.
     *
     * @param e Entry to gather the attributes for.
     * @return List containing the attribute types.
     */
    private List<AttributeType> getAllAttrs(Entry e) {
        Map<AttributeType,List<Attribute>> attrMap = e.getUserAttributes();
        List<AttributeType> typeList=new LinkedList<AttributeType>();
        Attribute attr=e.getObjectClassAttribute();
        /*
         * When a search is not all attributes returned, the "objectclass"
         * attribute type is missing from the entry.
         */
        if(attr != null) {
           AttributeType ocType=attr.getAttributeType();
           typeList.add(ocType);
        }
        typeList.addAll(attrMap.keySet());
        return typeList;
    }
    /*
     * TODO Evaluate performance of this method.
     * TODO Evaluate security concerns of this method. Logic from this method
     * taken almost directly from DS6 implementation.
     *
     *  I find the work done in the accessAllowedEntry method, particularly
     *  with regard to the entry test evaluation, to be very confusing and
     *  potentially pretty inefficient.  I'm also concerned that the "return
     *  "true" inside the for loop could potentially allow access when it
     *  should be denied.
     */
    /**
     * Check if access is allowed on an entry. Access is checked by iterating
     * through each attribute of an entry, starting with the "objectclass"
     * attribute type.
     *
     * If access is allowed on the entry based on one of it's attribute types,
     * then a possible second access check is performed. This second check is
     * only performed if an entry test ACI was found during the earlier
     * successful access check. An entry test ACI has no "targetattrs" keyword,
     * so allowing access based on an attribute type only would be incorrect.
     *
     * @param container ACI search container containing all of the information
     * needed to check access.
     *
     * @return True if access is allowed.
     */
    private boolean accessAllowedEntry(AciLDAPOperationContainer container) {
        boolean ret=false;
        //set flag that specifies this is the first attribute evaluated
        //in the entry
        container.setIsFirstAttribute(true);
        List<AttributeType> typeList=getAllAttrs(container.getResourceEntry());
        for(AttributeType attrType : typeList) {
            container.setCurrentAttributeType(attrType);
            /*
             * Check if access is allowed. If true, then check to see if an
             * entry test rule was found (no targetattrs) during target match
             * evaluation. If such a rule was found, set the current attribute
             * type to "null" and check access again so that rule is applied.
             */
            if(accessAllowed(container)) {
                if(container.hasEntryTestRule()) {
                    container.setCurrentAttributeType(null);
                    if(!accessAllowed(container)) {
                        /*
                         * If we failed because of a deny permission-bind rule,
                         * we need to stop and return false.
                         */
                        if(container.isDenyEval()) {
                            return false;
                        }
                        /*
                         * If we failed because there was no explicit
                         * allow rule, then we grant implicit access to the
                         * entry.
                         */
                    }
                }
                return true;
            }
        }
        return ret;
    }
    /**
     * Evaluate an entry to be added to see if it has any "aci"
     * attribute type. If it does, examines each "aci" attribute type
     * value for syntax errors. All of the "aci" attribute type values
     * must pass syntax check for the add operation to proceed. Any
     * entry with an "aci" attribute type must have "modify-acl"
     * privileges.
     *
     * @param entry  The entry to be examined.
     * @param operation The operation to to check privileges on.
     * @param clientDN The authorization DN.
     * @return True if the entry has no ACI attributes or if all of the "aci"
     * attributes values pass ACI syntax checking.
     */
    private boolean
    verifySyntax(Entry entry, Operation operation, DN clientDN) {
        if(entry.hasOperationalAttribute(aciType)) {
            /*
             * Check that the operation has "modify-acl" privileges since the
             * entry to be added has an "aci" attribute type.
             */
            if (!operation.getClientConnection().
                    hasPrivilege(Privilege.MODIFY_ACL, operation))  {
                int    msgID  = MSGID_ACI_ADD_FAILED_PRIVILEGE;
                String message = getMessage(msgID,
                        String.valueOf(entry.getDN()),
                        String.valueOf(clientDN));
                logError(ErrorLogCategory.ACCESS_CONTROL,
                         ErrorLogSeverity.SEVERE_WARNING,
                         message, msgID);
                return false;
            }
            List<Attribute> attributeList =
                entry.getOperationalAttribute(aciType, null);
            for (Attribute attribute : attributeList)
            {
                for (AttributeValue value : attribute.getValues())
                {
                    try {
                       DN dn=entry.getDN();
                       Aci.decode(value.getValue(),dn);
                    } catch (AciException ex) {
                        int    msgID  = MSGID_ACI_ADD_FAILED_DECODE;
                        String message = getMessage(msgID,
                                String.valueOf(entry.getDN()),
                                ex.getMessage());
                        logError(ErrorLogCategory.ACCESS_CONTROL,
                                 ErrorLogSeverity.SEVERE_WARNING,
                                 message, msgID);
                        return false;
                    }
                }
            }
        }
        return true;
    }
    /**
     * Check access using the accessAllowed method. The
     * LDAP add, compare, modify and delete operations use this function.
     * The other supported LDAP operations have more specialized checks.
     * @param operationContainer  The container containing the information
     * needed to evaluate this operation.
     * @param operation The operation being evaluated.
     * @return True if this operation is allowed access.
     */
    private boolean isAllowed(AciLDAPOperationContainer operationContainer,
                              Operation operation) {
        return skipAccessCheck(operation) || accessAllowed(operationContainer);
    }
    /**
     * Check access on add operations.
     *
     * @param operation The add operation to check access on.
     * @return  True if access is allowed.
     */
    public boolean isAllowed(AddOperation operation) {
        assert debugEnter(CLASS_NAME, "isAllowed");
        AciLDAPOperationContainer operationContainer =
                new AciLDAPOperationContainer(operation, ACI_ADD);
        boolean ret=isAllowed(operationContainer,operation);
        //LDAP add needs a verify ACI syntax step in case any
        //"aci" attribute types are being added.
        if(ret)
            ret=verifySyntax(operation.getEntryToAdd(), operation,
                             operationContainer.getClientDN());
        return ret;
    }
   /**
     * Check access on compare operations. Note that the attribute
     * type is unavailable at this time, so this method partially
     * parses the raw attribute string to get the base attribute
     * type. Options are ignored.
     *
     * @param operation The compare operation to check access on.
     * @return  True if access is allowed.
     */
   public boolean isAllowed(CompareOperation operation) {
       assert debugEnter(CLASS_NAME, "isAllowed");
       AciLDAPOperationContainer operationContainer =
               new AciLDAPOperationContainer(operation, ACI_COMPARE);
       String baseName;
       String rawAttributeType=operation.getRawAttributeType();
       int  semicolonPosition=rawAttributeType.indexOf(';');
       if (semicolonPosition > 0)
         baseName =
             toLowerCase(rawAttributeType.substring(0, semicolonPosition));
       else
         baseName = toLowerCase(rawAttributeType);
       AttributeType attributeType;
       if((attributeType =
           DirectoryServer.getAttributeType(baseName)) == null)
           attributeType = DirectoryServer.getDefaultAttributeType(baseName);
       AttributeValue attributeValue =
           new AttributeValue(attributeType, operation.getAssertionValue());
       operationContainer.setCurrentAttributeType(attributeType);
       operationContainer.setCurrentAttributeValue(attributeValue);
       return isAllowed(operationContainer, operation);
   }
   /**
     * Check access on delete operations.
     *
     * @param operation The delete operation to check access on.
     * @return  True if access is allowed.
     */
   public boolean isAllowed(DeleteOperation operation) {
       assert debugEnter(CLASS_NAME, "isAllowed");
       AciLDAPOperationContainer operationContainer=
               new AciLDAPOperationContainer(operation, ACI_DELETE);
       return isAllowed(operationContainer, operation);
   }
   /**
    * Check access on modify operations.
    *
    * @param operation The modify operation to check access on.
    * @return  True if access is allowed.
    */
  public boolean isAllowed(ModifyOperation operation) {
      assert debugEnter(CLASS_NAME, "isAllowed");
      AciLDAPOperationContainer operationContainer=
              new AciLDAPOperationContainer(operation, ACI_NULL);
      return aciCheckMods(operationContainer, operation,
                          skipAccessCheck(operation));
  }
  /*
   * TODO Add access testing of the filter against the entry. This was
   * brought up in the first code review.
   *
   *  The static block that creates the arrays of EnumRight objects needs to
   *  be documented to explain what they are.  Also, I still disagree with
   *  the  interpretation that the READ right is all that is necessary to
   *  perform either search or compare operations.  That definitely goes
   *  against the documentation, which states that READ applies only to
   *  the search operation, and that users must have both SEARCH and READ
   *  in order to access the results.
   */
  /**
   * Checks access on a search operation.
   * @param operation The search operation class containing information to
   * check the access on.
   * @param entry  The entry to evaluate access.
   * @return   True if access is allowed.
   */
  public boolean
  maySend(SearchOperation operation, SearchResultEntry entry) {
      assert debugEnter(CLASS_NAME, "maySend");
      AciLDAPOperationContainer operationContainer =
              new AciLDAPOperationContainer(operation,
                                           (ACI_READ | ACI_SEARCH), entry);
      return skipAccessCheck(operation) ||
              accessAllowedEntry(operationContainer);
  }
  /*
   * TODO Rename this method. Needs to be changed in SearchOperation.
   *
   * I find the name of the filterEntry method to be misleading because
   * it works on a search operation but has nothing to do with the search
   * filter.  Something like "removeDisallowedAttributes" would be clearer.
   */
  /**
   * Checks access on each attribute in an entry. It removes those attributes
   * that fail access check.
   *
   * @param operation The search operation class containing information to
   * check access on.
   * @param entry   The entry containing the attributes.
   * @return    The entry to return minus filtered attributes.
   */
  public SearchResultEntry filterEntry(SearchOperation operation,
                                       SearchResultEntry entry) {
      assert debugEnter(CLASS_NAME, "filterEntry");
      AciLDAPOperationContainer operationContainer =
              new AciLDAPOperationContainer(operation,
                                            (ACI_READ | ACI_SEARCH), entry);
      SearchResultEntry returnEntry;
      if(!skipAccessCheck(operation)) {
          returnEntry=accessAllowedAttrs(operationContainer);
      } else
          returnEntry=entry;
      return returnEntry;
  }
  //Planned to be implemented methods
  /**
   * {@inheritDoc}
   */
  @Override
  public boolean maySend(SearchOperation operation,
      SearchResultReference reference) {
    assert debugEnter(CLASS_NAME, "maySend");
    //TODO: Planned to be implemented.
    return true;
  }
  /**
   * {@inheritDoc}
   */
  @Override
  public boolean isAllowed(ModifyDNOperation modifyDNOperation) {
      assert debugEnter(CLASS_NAME, "isAllowed");
      // TODO: Planned to be implemented.
      return true;
  }
  //Not planned to be implemented methods.
  /**
   * {@inheritDoc}
   */
  @Override
  public boolean isAllowed(BindOperation bindOperation) {
      assert debugEnter(CLASS_NAME, "isAllowed");
      //Not planned to be implemented.
      return true;
  }
  /**
   * {@inheritDoc}
   */
  @Override
  public boolean isAllowed(ExtendedOperation extendedOperation) {
      assert debugEnter(CLASS_NAME, "isAllowed");
      //Not planned to be implemented.
      return true;
  }
  /**
   * {@inheritDoc}
   */
  @Override
  public boolean isAllowed(SearchOperation searchOperation) {
      assert debugEnter(CLASS_NAME, "isAllowed");
      //Not planned to be implemented.
      return true;
  }
}
opends/src/server/org/opends/server/authorization/dseecompat/AciLDAPOperationContainer.java
New file
@@ -0,0 +1,120 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying * information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.authorization.dseecompat;
import java.util.List;
import org.opends.server.core.AddOperation;
import org.opends.server.core.CompareOperation;
import org.opends.server.core.DeleteOperation;
import org.opends.server.core.ModifyOperation;
import org.opends.server.core.SearchOperation;
import org.opends.server.types.Modification;
import org.opends.server.types.SearchResultEntry;
/**
 * The AciLDAPOperationContainer is an AciContainer
 * extended class that wraps each LDAP operation being
 * evaluated or tested for target matched of an ACI.
 */
public class AciLDAPOperationContainer extends AciContainer  {
    /**
     * The entry to be returned if this is a LDAP search.
     */
    private SearchResultEntry searchEntry;
    /**
     * The list of modifications if this operation is a LDAP
     * modify.
     */
    private List<Modification>  modifications;
    /**
     * Constructor interface for the compare operation.
     * @param operation The compare operation to evaluate.
     * @param rights  The rights of a compare operation.
     */
    public AciLDAPOperationContainer(CompareOperation operation, int rights) {
        super(operation, rights, operation.getEntryToCompare());
    }
    /**
     * Constructor interface for the add operation.
     * @param operation The add operation to evaluate.
     * @param rights  The rights of an add operation.
     */
    public AciLDAPOperationContainer(AddOperation operation, int rights) {
        super(operation, rights, operation.getEntryToAdd());
    }
    /**
     * Constructor interface for the delete operation.
     * @param operation The add operation to evaluate.
     * @param rights  The rights of a delete operation.
     */
    public AciLDAPOperationContainer(DeleteOperation operation,  int rights) {
        super(operation, rights, operation.getEntryToDelete());
    }
    /**
     * Constructor interface for the modify operation.
     * @param rights The rights of modify operation.
     * @param operation The add operation to evaluate.
     */
    public AciLDAPOperationContainer(ModifyOperation operation, int rights) {
        super(operation, rights, operation.getCurrentEntry());
        this.modifications=operation.getModifications();
    }
    /**
     * Constructor interface for the LDAP search operation.
     * @param operation The search operation.
     * @param rights The rights of a search operation.
     * @param entry The entry to be evaluated for this search.
     */
    public AciLDAPOperationContainer(SearchOperation operation,  int rights,
                                     SearchResultEntry entry) {
        super(operation, rights,  entry);
        this.searchEntry = entry;
    }
    /**
     * Retrieve the search result entry of the search operation.
     * @return The search result entry.
     */
    public SearchResultEntry getSearchResultEntry() {
        return this.searchEntry;
    }
    /** Retrieve the list of modifications if this is a LDAP modify.
     * @return The list of LDAP modifications to made on the resource entry.
     */
    public  List<Modification>  getModifications() {
        return modifications;
    }
}
opends/src/server/org/opends/server/authorization/dseecompat/AciList.java
New file
@@ -0,0 +1,251 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying * information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.authorization.dseecompat;
import static org.opends.server.authorization.dseecompat.AciMessages.*;
import static org.opends.server.loggers.Error.logError;
import static org.opends.server.messages.MessageHandler.getMessage;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.opends.server.types.Attribute;
import org.opends.server.types.AttributeValue;
import org.opends.server.types.DN;
import org.opends.server.types.Entry;
import org.opends.server.types.ErrorLogCategory;
import org.opends.server.types.ErrorLogSeverity;
import org.opends.server.api.Backend;
/**
 * The AciList class performs caching of the ACI attribute values
 * using the entry DN as the key.
 */
public class AciList {
    /*
     * TODO Change linked list implementation as suggested below.
     *  I would strongly recommend that you change aciList to be
     *  LinkedHashMap<DN,List<Aci>> or LinkedHashMap<DN,Aci[]> rather than
     *  LinkedHashMap<String,Aci>.  It looks like there are some costly
     *  string->DN and even string->DN->string conversions.  Further, the very
     *  hackish way that the linked-list is currently maintained is very
     *  ugly and potentially error-prone.
     */
    private LinkedHashMap<DN, Aci> aciList =
            new LinkedHashMap<DN, Aci>();
    /*
     * TODO Evaluate making this class lock-free.
     *  I would definitely try to make this a lock-free class if at all
     *  possible. Read locks aren't free to acquire, since they still require
     *  an exclusive lock at some point.  If possible, you should use a
     *  copy-on-write structure so that you only incur penalties for changing
     *  the ACI list (which should be a rare event) and there is no need for
     *  any kind of locking at all for read operations.
     */
    private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    private final Lock aciReadLock  = rwl.readLock();
    private final Lock aciWriteLock = rwl.writeLock();
    /*
     * TODO Add support global ACIs in config.ldif.
     *
     */
    /**
     * Using the base DN, return a list of ACIs that are candidates for
     * evaluation by walking up from the base DN towards the root of the
     * DIT gathering ACIs on parents.
     *
     * @param baseDN  The DN to check.
     * @return A list of candidate ACIs that might be applicable.
     */
    public LinkedList<Aci> getCandidateAcis(DN baseDN) {
        LinkedList<Aci> candidates = new LinkedList<Aci>();
        if(baseDN == null)
            return candidates;
        try {
            aciReadLock.lock();
            while(baseDN != null) {
                Aci aci = aciList.get(baseDN);
                if (aci != null)
                {
                    while (aci != null)
                    {
                        candidates.add(aci);
                        aci = aci.next;
                    }
                }
                if(baseDN.isNullDN())
                    break;
               DN parentDN=baseDN.getParent();
               if(parentDN == null)
                    baseDN=DN.nullDN();
                else
                    baseDN=parentDN;
            }
        } finally {
            aciReadLock.unlock();
        }
        return candidates;
    }
    /**
     * Add all of an entries ACI attribute values to the ACI list. This
     * method locks/unlocks the list.
     * @param entry The entry containing the "aci" attribute values.\
     * @return The number of valid ACI attribute values added to the ACI list.
     */
    public int addAci(Entry entry) {
        int validAcis=0;
        DN dn=entry.getDN();
        List<Attribute> attributeList =
                entry.getOperationalAttribute(AciHandler.aciType);
        try {
            aciWriteLock.lock();
            validAcis=addAciAttributeListNoLock(dn, attributeList);
        } finally {
            aciWriteLock.unlock();
        }
        return validAcis;
    }
    /**
     * Add "aci" attribute type values to the ACI list. There is a chance
     * that an ACI will throw an exception if it has an invalid syntax.
     * If that happens a message will be logged and the ACI skipped.
     * @param dn The DN to use a the key in the ACI list.
     * @param attributeList List of attributes contain the "aci" attribute
     * values.
     * @return The number of valid "aci" attribute types added to the ACI list.
     */
    private int addAciAttributeListNoLock(DN dn,
                                    List<Attribute> attributeList) {
        int validAcis=0;
        for (Attribute attribute : attributeList) {
            for (AttributeValue value : attribute.getValues()) {
                try {
                    Aci aci= Aci.decode(value.getValue(),dn);
                    addAci(dn, aci);
                    validAcis++;
                } catch (AciException ex) {
                    /* An illegal ACI might have been loaded
                     * during import and is failing at ACI handler
                     * initialization time. Log a message and continue
                     * processing. ACIs added via LDAP add have their
                     * syntax checked before adding and should never
                     * hit this code.
                     */
                    int    msgID  = MSGID_ACI_ADD_LIST_FAILED_DECODE;
                    String message = getMessage(msgID,
                            ex.getMessage());
                    logError(ErrorLogCategory.ACCESS_CONTROL,
                             ErrorLogSeverity.SEVERE_WARNING,
                             message, msgID);
                }
            }
        }
        return validAcis;
    }
    /**
     * Remove all of the ACIs related to the old entry and then add all of the
     * ACIs related to the new entry. This method locks/unlocks the list.
     * @param oldEntry The old entry maybe containing old "aci" attribute
     * values.
     * @param newEntry The new entry maybe containing new "aci" attribute
     * values.
     */
    public void modAciOldNewEntry(Entry oldEntry, Entry newEntry) {
         if((oldEntry.hasOperationalAttribute(AciHandler.aciType)) ||
                 (newEntry.hasOperationalAttribute(AciHandler.aciType))) {
             try {
                 aciWriteLock.lock();
                 aciList.remove(oldEntry.getDN());
                 List<Attribute> attributeList =
                     newEntry.getOperationalAttribute(AciHandler.aciType, null);
                 addAciAttributeListNoLock(newEntry.getDN(),attributeList);
             } finally {
                 aciWriteLock.unlock();
             }
         }
     }
    /**
     * Add an ACI using the DN as a key. If the DN already
     * has ACI(s) on the list, then the new ACI is added to the
     * end of the linked list.
     * @param dn The DN to use as the key.
     * @param aci  The ACI to add to the list.
     */
    public void addAci(DN dn, Aci aci)  {
        if(aciList.containsKey(dn)) {
            Aci tmpAci = aciList.get(dn);
            while(tmpAci.next != null)
                tmpAci=tmpAci.next;
            tmpAci.next=aci;
        } else
            aciList.put(dn, aci);
    }
    /**
     * Remove ACIs related to an entry.
     * @param entry The entry to be removed.
     * @return True if the ACI set was deleted.
     */
    public boolean removeAci(Entry entry) {
        boolean deleted = false;
        try {
            aciWriteLock.lock();
            if (aciList.remove(entry.getDN()) != null)
                deleted = true;
        } finally {
            aciWriteLock.unlock();
        }
        return deleted;
    }
    /**
     * Remove all ACIs related to a backend.
     * @param backend  The backend to check if each DN is handled by that
     * backend.
     */
    public void removeAci (Backend backend) {
        try {
            aciWriteLock.lock();
            Set<DN> keys=aciList.keySet();
            for(DN dn : keys) {
                if (backend.handlesEntry(dn))
                    aciList.remove(dn);
            }
        } finally {
            aciWriteLock.unlock();
        }
    }
}
opends/src/server/org/opends/server/authorization/dseecompat/AciListenerManager.java
New file
@@ -0,0 +1,210 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying * information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.authorization.dseecompat;
import org.opends.server.api.ChangeNotificationListener;
import org.opends.server.api.BackendInitializationListener;
import org.opends.server.api.Backend;
import org.opends.server.types.*;
import org.opends.server.types.operation.PostResponseAddOperation;
import org.opends.server.types.operation.PostResponseDeleteOperation;
import org.opends.server.types.operation.PostResponseModifyOperation;
import org.opends.server.types.operation.PostResponseModifyDNOperation;
import org.opends.server.protocols.internal.InternalClientConnection;
import org.opends.server.protocols.internal.InternalSearchOperation;
import static org.opends.server.loggers.Debug.debugException;
import static org.opends.server.loggers.Error.logError;
import static org.opends.server.authorization.dseecompat.AciMessages.*;
import static org.opends.server.messages.MessageHandler.getMessage;
import java.util.LinkedHashSet;
/**
 * The AciListenerManager updates an ACI list after each
 * modification operation. Also, updates ACI list when backends are initialized
 * and finalized.
 */
public class AciListenerManager
        implements ChangeNotificationListener, BackendInitializationListener {
    /**
   * The fully-qualified name of this class for debugging purposes.
   */
  private static final String CLASS_NAME =
       "org.opends.server.core.AciListenerManager";
    private AciList aciList;
       /*
     * Search filter used in context search for "aci" attribute types.
     */
    private static SearchFilter aciFilter;
    /**
     * The aci attribute type is operational so we need to specify it to be
     * returned.
     */
    private static LinkedHashSet<String> attrs = new LinkedHashSet<String>();
    static {
        /*
         * Set up the filter used to search private and public contexts.
         */
        try {
            aciFilter=SearchFilter.createFilterFromString("(aci=*)");
        } catch (DirectoryException ex) {
            //TODO should never happen, error message?
        }
        attrs.add("aci");
    }
    /**
     * Save the list created by the AciHandler routine.
     * @param aciList The list object created and loaded by the handler.
     */
    public AciListenerManager(AciList aciList) {
        this.aciList=aciList;
    }
    /**
     * A delete operation succeeded. Remove any ACIs associated with the
     * entry deleted.
     * @param deleteOperation The delete operation.
     * @param entry The entry being deleted.
     */
    public void handleDeleteOperation(PostResponseDeleteOperation
                                      deleteOperation, Entry entry) {
       if(entry.hasOperationalAttribute(AciHandler.aciType)) {
            aciList.removeAci(entry);
       }
    }
    /**
     * An Add operation succeeded. Add any ACIs associated with the
     * entry being added.
     * @param addOperation  The add operation.
     * @param entry   The entry being added.
     */
    public void handleAddOperation(PostResponseAddOperation addOperation,
            Entry entry) {
        if(entry.hasOperationalAttribute(AciHandler.aciType))
        {
            aciList.addAci(entry);
        }
    }
    /**
     * A modify operation succeeded. Adjust the ACIs by removing
     * ACIs based on the oldEntry and then adding ACIs based on the new
     * entry.
     * @param modOperation  the modify operation.
     * @param oldEntry The old entry to examine.
     * @param newEntry  The new entry to examine.
     */
    public void handleModifyOperation(PostResponseModifyOperation modOperation,
            Entry oldEntry, Entry newEntry)
    {
        aciList.modAciOldNewEntry(oldEntry, newEntry);
    }
    /**
     * Not implemented.
     * @param modifyDNOperation  The LDAP modify DN operation.
     * @param oldEntry  The old entry.
     * @param newEntry The new entry.
     */
    public void handleModifyDNOperation(
            PostResponseModifyDNOperation modifyDNOperation,
            Entry oldEntry, Entry newEntry)
    {
        /*
         * TODO Not yet implemented.
         */
    }
    /**
     * {@inheritDoc}  In this case, the server will search the backend to find
     * all aci attribute type values that it may contain and add them to the
     * ACI list.
     */
    public void performBackendInitializationProcessing(Backend backend) {
        InternalClientConnection conn =
                InternalClientConnection.getRootConnection();
        for (DN baseDN : backend.getBaseDNs()) {
            try {
                if (! backend.entryExists(baseDN))  {
                    continue;
                }
            } catch (Exception e) {
                assert debugException(CLASS_NAME,
                        "performBackendInitializationProcessing", e);
                //TODO log message
                continue;
            }
            InternalSearchOperation internalSearch =
                    new InternalSearchOperation(conn,
                            InternalClientConnection.nextOperationID(),
                            InternalClientConnection.nextMessageID(),
                            null, baseDN, SearchScope.WHOLE_SUBTREE,
                            DereferencePolicy.NEVER_DEREF_ALIASES,
                            0, 0, false, aciFilter, attrs, null);
            try
            {
                backend.search(internalSearch);
            } catch (Exception e) {
                assert debugException(CLASS_NAME,
                        "performBackendInitializationProcessing", e);
                //TODO log message
                continue;
            }
            if(internalSearch.getSearchEntries().isEmpty()) {
                int    msgID  = MSGID_ACI_ADD_LIST_NO_ACIS;
                String message = getMessage(msgID, String.valueOf(baseDN));
                logError(ErrorLogCategory.ACCESS_CONTROL,
                        ErrorLogSeverity.NOTICE, message, msgID);
            } else {
                int validAcis=0;
                for (SearchResultEntry entry :
                        internalSearch.getSearchEntries()) {
                    validAcis += aciList.addAci(entry);
                }
                int    msgID  = MSGID_ACI_ADD_LIST_ACIS;
                String message = getMessage(msgID, Integer.toString(validAcis),
                        String.valueOf(baseDN));
                logError(ErrorLogCategory.ACCESS_CONTROL,
                        ErrorLogSeverity.NOTICE,
                        message, msgID);
            }
        }
    }
    /**
     * {@inheritDoc}  In this case, the server will remove all aci attribute
     * type values associated with entries in the provided backend.
     */
    public void performBackendFinalizationProcessing(Backend backend) {
        aciList.removeAci(backend);
    }
}
opends/src/server/org/opends/server/authorization/dseecompat/AciMessages.java
New file
@@ -0,0 +1,827 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying * information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.authorization.dseecompat;
import static org.opends.server.messages.MessageHandler.*;
/**
 * The AciMessages class defines the set of message IDs and default format
 * strings for messages associated with the dseecompat access control
 * implementation.
 */
public class AciMessages {
    /**
     * The message ID for the message that will be used if an "aci" attribute
     * type value cannot be parsed because it failed the non-specific regular
     * expression check during the initial ACI decode process. This takes one
     * argument, which is the string representation of the "aci" attribute
     * type value.
     */
    public static final int MSGID_ACI_SYNTAX_GENERAL_PARSE_FAILED =
        CATEGORY_MASK_ACCESS_CONTROL | SEVERITY_MASK_SEVERE_WARNING | 1;
    /**
     * The message ID for the message that will be used if an "aci" attribute
     * type value failed parsing because of an invalid version string. This
     * takes one argument, which is the version string parsed from the
     * "aci" attribute type value.
     */
    public static final int MSGID_ACI_SYNTAX_INVAILD_VERSION =
        CATEGORY_MASK_ACCESS_CONTROL | SEVERITY_MASK_SEVERE_WARNING | 2;
    /**
     * The message ID for the message that will be used if an "aci" attribute
     * type value failed parsing because of an invalid access type string. This
     * takes one argument, which is the access type string parsed from the
     * "aci" attribute type value.
     */
    public static final int MSGID_ACI_SYNTAX_INVALID_ACCESS_TYPE_VERSION =
        CATEGORY_MASK_ACCESS_CONTROL | SEVERITY_MASK_SEVERE_WARNING | 3;
    /**
     * The message ID for the message that will be used if an "aci" attribute
     * type value failed parsing because of an invalid rights string. This
     * takes one argument, which is the rights string parsed from the
     * "aci" attribute type value.
     */
    public static final int MSGID_ACI_SYNTAX_INVALID_RIGHTS_SYNTAX =
        CATEGORY_MASK_ACCESS_CONTROL | SEVERITY_MASK_SEVERE_WARNING | 4;
    /**
     * The message ID for the message that will be used if an "aci" attribute
     * type value failed parsing because of an invalid rights keyword. This
     * takes one argument, which is the rights keyword string parsed from the
     * rights string.
     */
    public static final int MSGID_ACI_SYNTAX_INVALID_RIGHTS_KEYWORD =
        CATEGORY_MASK_ACCESS_CONTROL | SEVERITY_MASK_SEVERE_WARNING | 5;
    /**
     * The message ID for the message that will be used if an ACI bind rule
     * value failed parsing because it starts with an open parenthesis,
     * but does not contain a matching close parenthesis.  This takes one
     * argument, which is the bind rule string.
     */
    public static final int MSGID_ACI_SYNTAX_BIND_RULE_MISSING_CLOSE_PAREN =
        CATEGORY_MASK_ACCESS_CONTROL | SEVERITY_MASK_MILD_ERROR | 6;
    /**
     * The message ID for the message that will be used if an ACI bind rule
     * value failed parsing because it is an invalid bind rule syntax. This
     * takes one argument, which is the bind rule string.
     */
    public static final int MSGID_ACI_SYNTAX_INVALID_BIND_RULE_SYNTAX =
        CATEGORY_MASK_ACCESS_CONTROL | SEVERITY_MASK_MILD_ERROR | 7;
    /**
     * The message ID for the message that will be used if an "aci" attribute
     * type value failed parsing because of an invalid bind rule keyword. This
     * takes one argument, which is the bind rule keyword string parsed from
     * the bind rule string.
     */
    public static final int MSGID_ACI_SYNTAX_INVALID_BIND_RULE_KEYWORD =
        CATEGORY_MASK_ACCESS_CONTROL | SEVERITY_MASK_SEVERE_WARNING | 8;
    /**
     * The message ID for the message that will be used if an "aci" attribute
     * type value failed parsing because of an invalid bind rule operator. This
     * takes one argument, which is the bind rule operator string parsed
     * from the bind rule string.
     */
    public static final int MSGID_ACI_SYNTAX_INVALID_BIND_RULE_OPERATOR =
        CATEGORY_MASK_ACCESS_CONTROL | SEVERITY_MASK_SEVERE_WARNING | 9;
    /**
     * The message ID for the message that will be used if an "aci" attribute
     * type value failed parsing because of a missing bind rule expression
     * string. This takes one argument, which is the bind rule string.
     */
    public static final int MSGID_ACI_SYNTAX_MISSING_BIND_RULE_EXPRESSION =
        CATEGORY_MASK_ACCESS_CONTROL | SEVERITY_MASK_SEVERE_WARNING | 10;
    /**
     * The message ID for the message that will be used if an "aci" attribute
     * type value failed parsing because of an invalid bind rule boolean
     * operator. This takes one argument, which is the bind rule boolean
     * operator string parsed from the bind rule string.
     */
    public static
    final int MSGID_ACI_SYNTAX_INVALID_BIND_RULE_BOOLEAN_OPERATOR =
        CATEGORY_MASK_ACCESS_CONTROL | SEVERITY_MASK_SEVERE_WARNING | 11;
    /**
     * The message ID for the message that will be used if an "aci" attribute
     * type value failed parsing because of an invalid bind rule keyword,
     * keyword operation combination. This takes two arguments, which are the
     * bind rule keyword string and the bind rule keyword operator parsed from
     * the bind rule string.
     */
    public static
    final int MSGID_ACI_SYNTAX_INVALID_BIND_RULE_KEYWORD_OPERATOR_COMBO =
        CATEGORY_MASK_ACCESS_CONTROL | SEVERITY_MASK_SEVERE_WARNING | 12;
    /**
     * The message ID for the message that will be used if an "aci" attribute
     * type value parse failed because a bind rule userdn LDAP URL failed
     * to decode.  This takes one argument the message from the LDAP
     * URL decode DirectoryException.
     */
    public static final int MSGID_ACI_SYNTAX_INVALID_USERDN_URL =
        CATEGORY_MASK_ACCESS_CONTROL | SEVERITY_MASK_SEVERE_WARNING | 13;
    /**
     * The message ID for the message that will be used if an "aci" attribute
     * type value parse failed because a bind rule roledn expression failed
     * to parse.  This takes one argument, which is the roledn expression
     * string parsed from the bind rule string.
     */
    public static final int MSGID_ACI_SYNTAX_INVALID_ROLEDN_EXPRESSION =
        CATEGORY_MASK_ACCESS_CONTROL | SEVERITY_MASK_SEVERE_WARNING | 14;
    /**
     * The message ID for the message that will be used if an "aci" attribute
     * type value parse failed because a bind rule roledn LDAP URL failed
     * to decode.  This takes one argument the message from the LDAP
     * URL decode DirectoryException.
     */
    public static final int MSGID_ACI_SYNTAX_INVALID_ROLEDN_URL =
        CATEGORY_MASK_ACCESS_CONTROL | SEVERITY_MASK_SEVERE_WARNING | 15;
    /**
     * The message ID for the message that will be used if an "aci" attribute
     * type value parse failed because a bind rule groupdn expression failed
     * to parse.  This takes one argument, which is the groupdn expression
     * string parsed from the bind rule string.
     */
    public static final int MSGID_ACI_SYNTAX_INVALID_GROUPDN_EXPRESSION =
        CATEGORY_MASK_ACCESS_CONTROL | SEVERITY_MASK_SEVERE_WARNING | 16;
    /**
     * The message ID for the message that will be used if an "aci" attribute
     * type value parse failed because a bind rule groupdn LDAP URL failed
     * to decode.  This takes one argument the message from the LDAP
     * URL decode DirectoryException.
     */
    public static final int MSGID_ACI_SYNTAX_INVALID_GROUPDN_URL =
        CATEGORY_MASK_ACCESS_CONTROL | SEVERITY_MASK_SEVERE_WARNING | 17;
    /**
     * The message ID for the message that will be used if an "aci" attribute
     * type value parse failed because a bind rule ip keyword expression
     * network mask value did not match the expression network address value.
     * For example, the ACI has a IPV6 network mask; but the internet
     * address part is IPV4. This takes two arguments, which are the
     * bind rule ip netmask string and the bind rule ip inet address
     * parsed from the bind rule string.
     */
    public static final int MSGID_ACI_SYNTAX_ADDRESS_FAMILY_MISMATCH =
        CATEGORY_MASK_ACCESS_CONTROL | SEVERITY_MASK_SEVERE_WARNING | 18;
    /**
     * The message ID for the message that will be used if an "aci" attribute
     * type value parse failed because a bind rule ip keyword expression
     * failed to parse because the number of bits specified to match the
     * network was not valid for the inet address specified. This takes
     * two arguments, which an string specifying the address type
     * (inet6address, inet4address) and an error message.
     */
    public static final int MSGID_ACI_SYNTAX_INVALID_NETWORK_BIT_MATCH =
        CATEGORY_MASK_ACCESS_CONTROL | SEVERITY_MASK_SEVERE_WARNING | 19;
    /**
     * The message ID for the message that will be used if an "aci" attribute
     * type value parse failed because a bind rule ip expression failed
     * to decode.  This takes one argument, the message from the
     * thrown exception.
     */
    public static final int MSGID_ACI_SYNTAX_INVALID_IP_CRITERIA_DECODE =
        CATEGORY_MASK_ACCESS_CONTROL | SEVERITY_MASK_SEVERE_WARNING | 20;
    /**
     * The message ID for the message that will be used if an "aci" attribute
     * type value parse failed because a bind rule ip expression failed
     * to parse.  This takes one argument, which is the ip expression
     * string parsed from the bind rule string.
     */
    public static final int MSGID_ACI_SYNTAX_INVALID_IP_EXPRESSION =
        CATEGORY_MASK_ACCESS_CONTROL | SEVERITY_MASK_SEVERE_WARNING | 21;
    /**
     * The message ID for the message that will be used if an "aci" attribute
     * type value parse failed because a bind rule dns expression failed
     * to parse.  This takes one argument, which is the dns expression
     * string parsed from the bind rule string.
     */
    public static final int MSGID_ACI_SYNTAX_INVALID_DNS_EXPRESSION =
        CATEGORY_MASK_ACCESS_CONTROL | SEVERITY_MASK_SEVERE_WARNING | 22;
    /**
     * The message ID for the message that will be used if an "aci" attribute
     * type value parse failed because a bind rule dns expression failed
     * to parse because a wild-card was not in the leftmost position.
     * This takes one argument, which is the dns expression string parsed
     * from the bind rule string.
     */
    public static final int MSGID_ACI_SYNTAX_INVALID_DNS_WILDCARD =
        CATEGORY_MASK_ACCESS_CONTROL | SEVERITY_MASK_SEVERE_WARNING | 23;
    /**
     * The message ID for the message that will be used if an "aci" attribute
     * type value parse failed because a bind rule dayofweek expression failed
     * to parse.  This takes one argument, which is the dayofweek expression
     * string parsed from the bind rule string.
     */
    public static final int MSGID_ACI_SYNTAX_INVALID_DAYOFWEEK =
        CATEGORY_MASK_ACCESS_CONTROL | SEVERITY_MASK_SEVERE_WARNING |24;
    /**
     * The message ID for the message that will be used if an "aci" attribute
     * type value parse failed because a bind rule timeofday expression failed
     * to parse.  This takes one argument, which is the timeofday expression
     * string parsed from the bind rule string.
     */
    public static final int MSGID_ACI_SYNTAX_INVALID_TIMEOFDAY =
        CATEGORY_MASK_ACCESS_CONTROL | SEVERITY_MASK_SEVERE_WARNING |25;
    /**
     * The message ID for the message that will be used if an "aci" attribute
     * type value parse failed because a bind rule timeofday expression failed
     * to parse because the timeofday was not in a valid range.  This takes one
     * argument, which is the timeofday expression string parsed from the
     * bind rule string.
     */
    public static final int MSGID_ACI_SYNTAX_INVALID_TIMEOFDAY_RANGE =
        CATEGORY_MASK_ACCESS_CONTROL | SEVERITY_MASK_SEVERE_WARNING |26;
    /**
     * The message ID for the message that will be used if an "aci" attribute
     * type value parse failed because a bind rule authmethod expression failed
     * to parse.  This takes one argument, which is the authmethod expression
     * string parsed from the bind rule string.
     */
    public static final int MSGID_ACI_SYNTAX_INVALID_AUTHMETHOD_EXPRESSION =
        CATEGORY_MASK_ACCESS_CONTROL | SEVERITY_MASK_SEVERE_WARNING |27;
    /**
     * The message ID for the message that will be used if an "aci" attribute
     * type value parse failed because a bind rule userattr expression failed
     * to decode.  This takes one argument, the message from the
     * thrown exception.
     */
    public static final int MSGID_ACI_SYNTAX_INVALID_USERATTR_EXPRESSION =
        CATEGORY_MASK_ACCESS_CONTROL | SEVERITY_MASK_SEVERE_WARNING | 28;
    /**
     * The message ID for the message that will be used if an "aci" attribute
     * type value parse failed because a bind rule userattr expression value
     * is not supported.  This takes one argument, which is the userattr
     * expression string parsed from the bind rule string.
     */
    public static final int MSGID_ACI_SYNTAX_INVALID_USERATTR_KEYWORD =
        CATEGORY_MASK_ACCESS_CONTROL | SEVERITY_MASK_SEVERE_WARNING | 29;
    /**
     * The message ID for the message that will be used if an "aci" attribute
     * type value parse failed because a bind rule userattr expression
     * inheritance pattern did not parse.  This takes one argument, which
     * is the userattr expression inheritance pattern string parsed
     * from the bind rule string.
     */
    public static
    final int MSGID_ACI_SYNTAX_INVALID_USERATTR_INHERITANCE_PATTERN =
        CATEGORY_MASK_ACCESS_CONTROL | SEVERITY_MASK_SEVERE_WARNING | 30;
    /**
     * The message ID for the message that will be used if an "aci" attribute
     * type value parse failed because a bind rule userattr expression
     * inheritance level exceeded the max value.  This takes two arguments,
     * which are the userattr expression inheritance pattern string parsed
     * from the bind rule string and the max leval value.
     */
    public static
    final int MSGID_ACI_SYNTAX_MAX_USERATTR_INHERITANCE_LEVEL_EXCEEDED =
        CATEGORY_MASK_ACCESS_CONTROL | SEVERITY_MASK_SEVERE_WARNING | 31;
    /**
     * The message ID for the message that will be used if an "aci" attribute
     * type value parse failed because a bind rule userattr expression
     * inheritance level was non-numeric.  This takes one argument,
     * which is the userattr expression inheritance level pattern string
     * parsed from the bind rule string.
     */
    public static
    final int MSGID_ACI_SYNTAX_INVALID_INHERITANCE_VALUE =
        CATEGORY_MASK_ACCESS_CONTROL | SEVERITY_MASK_SEVERE_WARNING | 32;
    /**
     * The message ID for the message that will be used if an "aci" attribute
     * type value parse failed because a target rule had an invalid syntax.
     * This takes one argument, which is the target rule string
     * parsed from the "aci" attribute type value string.
     */
    public static final int MSGID_ACI_SYNTAX_INVALID_TARGET_SYNTAX =
        CATEGORY_MASK_ACCESS_CONTROL | SEVERITY_MASK_SEVERE_WARNING | 33;
    /**
     * The message ID for the message that will be used if an "aci" attribute
     * type value failed parsing because of an invalid target keyword. This
     * takes one argument, which is the target keyword string parsed
     * from the "aci" attribute type value string.
     */
    public static final int MSGID_ACI_SYNTAX_INVALID_TARGET_KEYWORD =
        CATEGORY_MASK_ACCESS_CONTROL | SEVERITY_MASK_SEVERE_WARNING | 34;
    /**
     * The message ID for the message that will be used if an "aci" attribute
     * type value failed parsing because of an invalid target keyword operator.
     * This takes one argument, which is the target keyword operator string
     * parsed from the "aci" attribute type value string.
     */
    public static final int MSGID_ACI_SYNTAX_INVALID_TARGET_OPERATOR =
        CATEGORY_MASK_ACCESS_CONTROL | SEVERITY_MASK_SEVERE_WARNING | 35;
    /**
     * The message ID for the message that will be used if an "aci" attribute
     * type value failed parsing because of a target keyword is not supported
     * at this time. This takes one argument, which is the unsupported target
     * keyword string parsed from the "aci" attribute type value string.
     */
    public static final int MSGID_ACI_SYNTAX_TARGET_KEYWORD_NOT_SUPPORTED =
        CATEGORY_MASK_ACCESS_CONTROL | SEVERITY_MASK_SEVERE_WARNING | 36;
    /**
     * The message ID for the message that will be used if an "aci" attribute
     * type value failed parsing because of a target keyword was seen multiple
     * times in the value. This takes two arguments, which are the target
     * keyword string parsed from the "aci" attribute type value string and
     * the "aci" attribute type value string.
     */
    public static
    final int MSGID_ACI_SYNTAX_INVALID_TARGET_DUPLICATE_KEYWORDS =
        CATEGORY_MASK_ACCESS_CONTROL | SEVERITY_MASK_SEVERE_WARNING | 37;
    /**
     * The message ID for the message that will be used if an "aci" attribute
     * type value failed parsing because of an invalid targetscope keyword
     * operator. This takes one argument, which is the targetscope keyword
     * operator string parsed from the "aci" attribute type value string.
     */
    public static final int MSGID_ACI_SYNTAX_INVALID_TARGETSCOPE_OPERATOR =
        CATEGORY_MASK_ACCESS_CONTROL | SEVERITY_MASK_SEVERE_WARNING | 38;
    /**
     * The message ID for the message that will be used if an "aci" attribute
     * type value failed parsing because of an invalid targetscope expression.
     * This takes one argument, which is the targetscope expression
     * string parsed from the "aci" attribute type value string.
     */
    public static final int MSGID_ACI_SYNTAX_INVALID_TARGETSCOPE_EXPRESSION =
        CATEGORY_MASK_ACCESS_CONTROL | SEVERITY_MASK_SEVERE_WARNING | 39;
    /**
     * The message ID for the message that will be used if an "aci" attribute
     * type value failed parsing because of an invalid target keyword
     * expression.  This takes one argument, which is the target keyword
     * expression string parsed from the "aci" attribute type value string.
     */
    public static final int MSGID_ACI_SYNTAX_INVALID_TARGETKEYWORD_EXPRESSION =
        CATEGORY_MASK_ACCESS_CONTROL | SEVERITY_MASK_SEVERE_WARNING | 40;
    /**
     * The message ID for the message that will be used if an "aci" attribute
     * type value failed parsing because of a target keyword DN is not a
     * descendant of the ACI entry DN. This takes two arguments, which are
     * the target keyword DN string parsed from the "aci" attribute type value
     * string and the DN of the "aci" attribute type entry.
     */
    public static
    final int MSGID_ACI_SYNTAX_TARGET_DN_NOT_DESCENDENTOF =
        CATEGORY_MASK_ACCESS_CONTROL | SEVERITY_MASK_SEVERE_WARNING | 41;
    /**
     * The message ID for the message that will be used if an "aci" attribute
     * type value failed parsing because of a targetattr keyword expression
     * is invalid. This takes one argument, which is the targetattr
     * keyword expression string parsed from the "aci" attribute type value
     * string.
     */
    public static
    final int MSGID_ACI_SYNTAX_INVALID_TARGETATTRKEYWORD_EXPRESSION =
        CATEGORY_MASK_ACCESS_CONTROL | SEVERITY_MASK_SEVERE_WARNING | 42;
    /**
     * The message ID for the message that will be used if an "aci" attribute
     * type value failed parsing because of a targetfilter keyword expression
     * string is invalid. This takes one argument, which is the targetfilter
     * keyword expression string parsed from the "aci" attribute type value
     * string.
     */
    public static
    final int MSGID_ACI_SYNTAX_INVALID_TARGETFILTERKEYWORD_EXPRESSION =
        CATEGORY_MASK_ACCESS_CONTROL | SEVERITY_MASK_SEVERE_WARNING | 43;
    /**
     * The message ID for the ACI message that will be generated when a client
     * attempts to add an entry with the "aci" attribute type
     * and they do not have the required "modify-acl"privilege.  This takes two
     * arguments, which are the string representation of the entry DN of the
     * entry being added, and the string representation of the
     * authorization DN.
     */
    public static final int MSGID_ACI_ADD_FAILED_PRIVILEGE =
        CATEGORY_MASK_ACCESS_CONTROL  | 44;
    /**
     * The message ID for the ACI message that will be generated when a client
     * attempts to perform a modification on an "aci" attribute type
     * and they do not have the required "modify-acl"privilege.  This takes two
     * arguments, which are the string representation of the entry DN of the
     * entry being modified, and the string representation of the
     * authorization DN.
     */
    public static final int MSGID_ACI_MODIFY_FAILED_PRIVILEGE =
        CATEGORY_MASK_ACCESS_CONTROL  | 45;
    /**
     * The message ID for the ACI message that will be generated when a client
     * attempts to add an entry with the "aci" attribute type
     * and the ACI decode failed because of an syntax error.  This takes one
     * argument, which is the message string thrown by the AciException.
     */
    public static final int MSGID_ACI_ADD_FAILED_DECODE =
        CATEGORY_MASK_ACCESS_CONTROL | SEVERITY_MASK_SEVERE_WARNING | 46;
    /**
     * The message ID for the ACI message that will be generated when a client
     * attempts to perform a modification on an "aci" attribute type
     * and the ACI decode failed because of a syntax error.  This takes one
     * argument, which is the message string thrown by the AciException.
     */
    public static final int MSGID_ACI_MODIFY_FAILED_DECODE =
        CATEGORY_MASK_ACCESS_CONTROL | SEVERITY_MASK_SEVERE_WARNING | 47;
    /**
     * The message ID for the ACI message that will be generated when
     * an ACI decode failed because of an syntax error. This message is usually
     * generated by an invalid ACI that was added during import which
     * fails the decode at server startup. This takes one
     * argument, which is the message string thrown by the AciException.
     */
    public static final int MSGID_ACI_ADD_LIST_FAILED_DECODE =
        CATEGORY_MASK_ACCESS_CONTROL | SEVERITY_MASK_SEVERE_WARNING | 48;
    /**
     * The message ID for the ACI message that will be generated the server
     * searches an directory context for "aci" attribute types and finds none.
     * This takes one argument, which is the DN of the directory context.
     */
    public static final int MSGID_ACI_ADD_LIST_NO_ACIS =
        CATEGORY_MASK_ACCESS_CONTROL | 49;
    /**
     * The message ID for the ACI message that will be generated the server
     * searches an directory context for "aci" attribute types and finds some.
     * This takes two arguments, which are the DN of the directory context,
     * the number of valid ACIs decoded.
     */
    public static final int MSGID_ACI_ADD_LIST_ACIS =
        CATEGORY_MASK_ACCESS_CONTROL | 50;
    /**
     * The message ID for the message that will be used if an "aci" attribute
     * type value parse failed because a bind rule userattr roledn expression
     * inheritance pattern did not parse.  This takes one argument, which
     * is the userattr expression inheritance pattern string parsed
     * from the bind rule string.
     */
    public static
    final int MSGID_ACI_SYNTAX_INVALID_USERATTR_ROLEDN_INHERITANCE_PATTERN =
        CATEGORY_MASK_ACCESS_CONTROL | SEVERITY_MASK_SEVERE_WARNING | 51;
    /**
     * Associates a set of generic messages with the message IDs defined in
     * this class.
     */
    public static void registerMessages() {
        registerMessage(MSGID_ACI_SYNTAX_GENERAL_PARSE_FAILED,
                "The provided string  \"%s\" could not be parsed as a valid " +
                "Access Control Instruction (ACI) because it failed "+
                "general ACI syntax evaluation.");
        registerMessage(MSGID_ACI_SYNTAX_INVAILD_VERSION,
                "The provided Access Control Instruction (ACI) version " +
                "value  \"%s\" is invalid, only the version 3.0 is " +
                "supported.");
        registerMessage(MSGID_ACI_SYNTAX_INVALID_ACCESS_TYPE_VERSION,
                "The provided Access Control Instruction access " +
                "type value  \"%s\" is invalid. A valid access type " +
                "value is either allow or deny.");
        registerMessage(MSGID_ACI_SYNTAX_INVALID_RIGHTS_SYNTAX,
                "The provided Access Control Instruction (ACI) rights " +
                "values \"%s\" are invalid. The rights must be a " +
                "list of 1 to 6 comma-separated keywords enclosed in " +
                "parentheses.");
        registerMessage(MSGID_ACI_SYNTAX_INVALID_RIGHTS_KEYWORD,
                "The provided Access Control Instruction (ACI) rights " +
                "keyword values \"%s\" are invalid. The valid rights " +
                "keyword values are one or more of the following: read, " +
                "write, add, delete, search, compare or the single value" +
                "all.");
        registerMessage(MSGID_ACI_SYNTAX_BIND_RULE_MISSING_CLOSE_PAREN,
                "The provided Access Control Instruction (ACI) bind " +
                "rule value \"%s\" is invalid because it is missing a " +
                "close parenthesis that corresponded to the initial open " +
                "parenthesis.");
        registerMessage(MSGID_ACI_SYNTAX_INVALID_BIND_RULE_SYNTAX,
                "The provided Access Control Instruction (ACI) bind rule " +
                "value \"%s\" is invalid. A valid bind rule value must " +
                "be in the following form: " +
                "keyword operator \"expression\".");
        registerMessage(MSGID_ACI_SYNTAX_INVALID_BIND_RULE_KEYWORD,
                "The provided Access Control Instruction (ACI) bind rule " +
                "keyword value \"%s\" is invalid. A valid keyword value is" +
                " one of the following: userdn, groupdn, roledn, userattr," +
                "ip, dns, dayofweek, timeofday or authmethod.");
        registerMessage(MSGID_ACI_SYNTAX_INVALID_BIND_RULE_OPERATOR ,
                "The provided Access Control Instruction (ACI) bind rule " +
                "operator value  \"%s\" is invalid. A valid bind rule " +
                "operator value is either '=' or \"!=\".");
        registerMessage(MSGID_ACI_SYNTAX_MISSING_BIND_RULE_EXPRESSION ,
                "The provided Access Control Instruction (ACI) bind rule " +
                "expression value corresponding to the keyword value " +
                "\"%s\" is missing an expression. A valid bind rule value " +
                "must be in the following form:" +
                " keyword operator \"expression\".");
        registerMessage(MSGID_ACI_SYNTAX_INVALID_BIND_RULE_BOOLEAN_OPERATOR ,
                "The provided Access Control Instruction (ACI) bind rule " +
                "boolean operator value \"%s\" is invalid. A valid bind" +
                "rule boolean operator value is either \"OR\" or \"AND\".");
        registerMessage(
                MSGID_ACI_SYNTAX_INVALID_BIND_RULE_KEYWORD_OPERATOR_COMBO,
                "The provided Access Control Instruction (ACI) bind rule " +
                "keyword string  \"%s\" is invalid for the bind rule " +
                "operator string \"%s\".");
        registerMessage(MSGID_ACI_SYNTAX_INVALID_USERDN_URL,
                "The provided Access Control Instruction (ACI) bind rule " +
                "userdn expression failed to URL decode for " +
                "the following reason: %s");
        registerMessage(MSGID_ACI_SYNTAX_INVALID_ROLEDN_EXPRESSION,
                "The provided Access Control Instruction (ACI) bind rule " +
                "roledn expression value \"%s\" is invalid. A valid roledn " +
                "keyword expression value requires one or more LDAP URLs " +
                "in the following format: " +
                "ldap:///dn [|| ldap:///dn] ... [|| ldap:///dn].");
        registerMessage(MSGID_ACI_SYNTAX_INVALID_ROLEDN_URL,
                "The provided Access Control Instruction (ACI) bind rule " +
                "roledn expression failed to URL decode for " +
                "the following reason: %s");
        registerMessage(MSGID_ACI_SYNTAX_INVALID_GROUPDN_EXPRESSION,
         "The provided Access Control Instruction (ACI) bind rule " +
          "groupdn expression value \"%s\" is invalid. A valid groupdn " +
         "keyword expression  value requires one or more LDAP URLs in the" +
         " following format: " +
         "ldap:///groupdn [|| ldap:///groupdn] ... [|| ldap:///groupdn].");
        registerMessage(MSGID_ACI_SYNTAX_INVALID_GROUPDN_URL,
                "The provided Access Control Instruction (ACI) bind rule " +
                "groupdn expression value failed to URL decode for " +
                "the following reason: %s");
        registerMessage(MSGID_ACI_SYNTAX_ADDRESS_FAMILY_MISMATCH,
                "The network mask value \"%s\" is not valid for " +
                "the ip expression network address \"%s\".");
        registerMessage(MSGID_ACI_SYNTAX_INVALID_NETWORK_BIT_MATCH,
                "The bit mask for address type value \"%s\" is not valid." +
                "%s.");
        registerMessage(MSGID_ACI_SYNTAX_INVALID_IP_CRITERIA_DECODE,
                "The provided Access Control Instruction (ACI) bind rule " +
                "ip expression value failed to decode for " +
                "the following reason: %s");
        registerMessage(MSGID_ACI_SYNTAX_INVALID_IP_EXPRESSION,
                "The provided Access Control Instruction (ACI) bind rule " +
                "ip expression value \"%s\" is invalid. A valid ip " +
                "keyword expression value requires one or more" +
                "comma-separated elements of an IP address list expression.");
        registerMessage(MSGID_ACI_SYNTAX_INVALID_DNS_EXPRESSION,
                "The provided Access Control Instruction (ACI) bind rule " +
                "dns expression value \"%s\" is invalid. A valid dns " +
                "keyword expression value requires a valid fully qualified"+
                " DNS domain name.");
        registerMessage(MSGID_ACI_SYNTAX_INVALID_DNS_WILDCARD,
                "The provided Access Control Instruction (ACI) bind rule " +
                "dns expression value \"%s\" is invalid, because a wild-card" +
                " pattern was found in the wrong position. A valid dns " +
                "keyword wild-card expression value requires the '*' " +
                "character only be in the leftmost position of the " +
                "domain name.");
        registerMessage(MSGID_ACI_SYNTAX_INVALID_DAYOFWEEK,
                "The provided Access Control Instruction (ACI) bind rule " +
                "dayofweek expression value \"%s\" is invalid, because of " +
                "an invalid day of week value. A valid dayofweek value " +
                "is one of the following English three-letter abbreviations" +
                "for the days of the week: sun, mon, tue, wed, thu, " +
                "fri, or sat.");
        registerMessage(MSGID_ACI_SYNTAX_INVALID_TIMEOFDAY,
                "The provided Access Control Instruction (ACI) bind rule " +
                "timeofday expression value \"%s\" is invalid. A valid " +
                "timeofday value is expressed as four digits representing " +
                "hours and minutes in the 24-hour clock (0 to 2359).");
        registerMessage(MSGID_ACI_SYNTAX_INVALID_TIMEOFDAY_RANGE,
                "The provided Access Control Instruction (ACI) bind rule " +
                "timeofday expression value \"%s\" is not in the valid" +
                 " range. A valid timeofday value is expressed as four" +
                 " digits representing hours and minutes in the 24-hour" +
                 " clock (0 to 2359).");
        registerMessage(MSGID_ACI_SYNTAX_INVALID_AUTHMETHOD_EXPRESSION,
                "The provided Access Control Instruction (ACI) bind rule " +
                "authmethod expression value \"%s\" is invalid. A valid " +
                "authmethod value is one of the following: none, simple," +
                "SSL, sasl EXTERNAL, sasl DIGEST-MD5, or sasl GSSAPI.");
        registerMessage(MSGID_ACI_SYNTAX_INVALID_USERATTR_EXPRESSION,
                "The provided Access Control Instruction (ACI) bind rule " +
                "userattr expression value \"%s\" is invalid.");
        registerMessage(MSGID_ACI_SYNTAX_INVALID_USERATTR_KEYWORD,
                "The provided Access Control Instruction (ACI) bind rule " +
                "userattr expression value \"%s\" is not supported.");
        registerMessage(MSGID_ACI_SYNTAX_INVALID_USERATTR_INHERITANCE_PATTERN,
                "The provided Access Control Instruction (ACI) bind rule " +
                "userattr expression inheritance pattern value \"%s\" is " +
                "invalid. A valid inheritance pattern value must have" +
                "the following format:" +
                " parent[inheritance_level].attribute#bindType.");
        registerMessage(
                MSGID_ACI_SYNTAX_MAX_USERATTR_INHERITANCE_LEVEL_EXCEEDED,
                "The provided Access Control Instruction (ACI) bind rule " +
                "userattr expression inheritance pattern value \"%s\" is " +
                "invalid. The inheritance level value cannot exceed the" +
                "max level limit of %s.");
        registerMessage(
                MSGID_ACI_SYNTAX_INVALID_INHERITANCE_VALUE,
                "The provided Access Control Instruction (ACI) bind rule " +
                "userattr expression inheritance pattern value \"%s\" is" +
                " invalid because it is non-numeric.");
        registerMessage(MSGID_ACI_SYNTAX_INVALID_TARGET_SYNTAX,
                "The provided Access Control Instruction (ACI) target rule" +
                "value \"%s\" is invalid. A valid target rule value must" +
                "be in the following form: " +
                "keyword operator \"expression\".");
        registerMessage(MSGID_ACI_SYNTAX_INVALID_TARGET_KEYWORD,
                "The provided Access Control Instruction (ACI) target " +
                "keyword value \"%s\" is invalid. A valid target keyword" +
                " value is one of the following: target, targetscope, " +
                "targetfilter, targetattr or targetattrfilters.");
        registerMessage(MSGID_ACI_SYNTAX_INVALID_TARGET_OPERATOR,
                "The provided Access Control Instruction (ACI) target " +
                "keyword operator value  \"%s\" is invalid. A valid target" +
                "keyword operator value is either '=' or \"!=\".");
        registerMessage(MSGID_ACI_SYNTAX_TARGET_KEYWORD_NOT_SUPPORTED,
                "The provided Access Control Instruction (ACI) " +
                "target keyword value \"%s\" is not supported at this time.");
        registerMessage(MSGID_ACI_SYNTAX_INVALID_TARGET_DUPLICATE_KEYWORDS,
                "The provided Access Control Instruction (ACI) " +
                "target keyword value \"%s\" was seen multiple times in" +
                " the ACI \"%s\".");
        registerMessage(MSGID_ACI_SYNTAX_INVALID_TARGETSCOPE_OPERATOR,
                "The provided Access Control Instruction (ACI) targetscope" +
                " keyword operator value \"%s\" is invalid. The only valid" +
                "targetscope operator value is '='.");
        registerMessage(MSGID_ACI_SYNTAX_INVALID_TARGETSCOPE_EXPRESSION,
                "The provided Access Control Instruction (ACI) targetscope" +
                " expression operator value  \"%s\" is invalid. A valid" +
                " targetscope expression value is one of the following: one," +
                " onelevel or subtree.");
        registerMessage(MSGID_ACI_SYNTAX_INVALID_TARGETKEYWORD_EXPRESSION,
                "The provided Access Control Instruction (ACI)" +
                " target expression value \"%s\" is invalid. A valid target" +
                " keyword expression  value requires a LDAP URL in the" +
                " following format: ldap:///distinguished_name.");
        registerMessage(MSGID_ACI_SYNTAX_TARGET_DN_NOT_DESCENDENTOF,
                "The provided Access Control Instruction (ACI) " +
                "target expression DN value \"%s\" is invalid. The target " +
                "expression DN value must be a descendant of the ACI entry" +
                " DN \"%s\", if no wild-card is specified in the target" +
                "expression DN.");
        registerMessage(MSGID_ACI_SYNTAX_INVALID_TARGETATTRKEYWORD_EXPRESSION,
                "The provided Access Control Instruction (ACI) " +
                "targetattr expression value \"%s\" is invalid. A valid " +
                "targetattr keyword expression value requires one or more " +
                "attribute type values in the following format: " +
                "attribute1 [|| attribute1] ... [|| attributen].");
        registerMessage(
                MSGID_ACI_SYNTAX_INVALID_TARGETFILTERKEYWORD_EXPRESSION,
                "The provided Access Control Instruction (ACI)" +
                " targetfilter expression value \"%s\" is invalid because it" +
                " is not a valid LDAP filter.");
        registerMessage(MSGID_ACI_ADD_FAILED_PRIVILEGE,
                "An attempt to add the entry \"%s\" containing" +
                " an aci attribute type failed, because the authorization DN" +
                " \"%s\" lacked modify-acl privileges.");
        registerMessage(MSGID_ACI_MODIFY_FAILED_PRIVILEGE,
                "An attempt to modify an aci "+
                "attribute type in the entry \"%s\" failed, because the" +
                "authorization DN \"%s\" lacked modify-acl privileges.");
        registerMessage(MSGID_ACI_ADD_FAILED_DECODE,
                "An attempt to add the entry \"%s\" containing" +
                " an aci attribute type failed because of the following" +
                " reason: %s");
        registerMessage(MSGID_ACI_MODIFY_FAILED_DECODE,
                "An attempt to modify an aci "+
                "attribute type in the entry \"%s\" failed "+
                "because of the following reason: %s");
        registerMessage(MSGID_ACI_ADD_LIST_FAILED_DECODE,
                "An attempt to decode an Access Control Instruction (ACI)" +
                " failed because of the following reason: %s");
        registerMessage(MSGID_ACI_ADD_LIST_NO_ACIS,
                "No Access Control Instruction (ACI) attribute types were" +
                " found in context \"%s\".");
        registerMessage(MSGID_ACI_ADD_LIST_ACIS,
                "Added %s Access Control Instruction (ACI) attribute types" +
                " found in context \"%s\" to the access" +
                "control evaluation engine.");
        registerMessage(
                MSGID_ACI_SYNTAX_INVALID_USERATTR_ROLEDN_INHERITANCE_PATTERN,
                "The provided Access Control Instruction (ACI) bind rule " +
                "userattr expression inheritance pattern value " +
                "\"%s\" is invalid for the roledn keyword because it starts " +
                        "with the string \"parent[\".");
    }
}
opends/src/server/org/opends/server/authorization/dseecompat/AciProvider.java
New file
@@ -0,0 +1,85 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying * information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.authorization.dseecompat;
import org.opends.server.api.AccessControlHandler;
import org.opends.server.api.AccessControlProvider;
import org.opends.server.config.ConfigEntry;
import org.opends.server.config.ConfigException;
import org.opends.server.types.InitializationException;
import static org.opends.server.loggers.Debug.debugConstructor;
/**
 * This class is the provider class for the dseecompt ACI.
 */
public class AciProvider  implements AccessControlProvider  {
    private static final String CLASS_NAME =
      "org.opends.server.authorization.dseecompat.AciProvider";
    private static AciHandler instance = null;
    /**
     * Create an aci provider. This doesn't do much.
     */
    public AciProvider() {
        super();
        assert debugConstructor(CLASS_NAME);
    }
    /**
     * Creates the AciHandler class and calls its initialization method.
     * @param configEntry The entry containing the configuration Access Control
     * entry.
     * @throws ConfigException If the initialization fails.
     * @throws InitializationException If the initialization fails.
     */
    public void initializeAccessControlHandler(ConfigEntry configEntry)
    throws ConfigException, InitializationException {
        getInstance();
    }
    /**
     * Returns a new AciHandler instance. There can be only one active.
     * @return  A new AciHandler instance.
     */
    public  AccessControlHandler getInstance() {
        if (instance == null) {
            instance = new AciHandler();
        }
        return instance;
    }
    /**
     * Not used at this time.
     */
    public void finalizeAccessControlHandler() {
    }
}
opends/src/server/org/opends/server/authorization/dseecompat/AciTargetMatchContext.java
New file
@@ -0,0 +1,134 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying * information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.authorization.dseecompat;
import org.opends.server.types.AttributeType;
import org.opends.server.types.AttributeValue;
import org.opends.server.types.Entry;
import java.util.LinkedList;
/**
 * The AciTargetMatchContext interface provides a
 * view of an AciContainer that exposes information to be
 * used by the Aci.isApplicable() method to determine if
 * an ACI is applicable (targets matched) to the LDAP operation,
 * operation rights and entry and attributes having access
 * checked on.
 */
public interface AciTargetMatchContext {
    /**
     * Set the deny ACI list.
     * @param denyList The deny ACI list.
     */
    public void setDenyList(LinkedList<Aci> denyList);
    /**
     * Set the allow ACI list.
     * @param allowList The list of allow ACIs.
     */
    public void setAllowList(LinkedList<Aci> allowList);
    /**
     * Get the entry being evaluated. This is known as the
     * resource entry.
     * @return The entry being evaluated.
     */
    public Entry getResourceEntry();
    /**
     * Get the current attribute type being evaluated.
     * @return  The attribute type being evaluated.
     */
    public AttributeType getCurrentAttributeType();
    /**
     * The current attribute type value being evaluated.
     * @return The current attribute type value being evaluated.
     */
    public AttributeValue getCurrentAttributeValue();
    /**
     * True if the first attribute of the resource entry is being evaluated.
     * @return True if this is the first attribute.
     */
    public boolean isFirstAttribute();
    /**
     * Set to true if the first attribute of the resource entry is
     * being evaluated.
     * @param isFirst  True if this is the first attribute of the
     * resource entry being evaluated.
     */
    public void setIsFirstAttribute(boolean isFirst);
    /**
     * Set the attribute type to be evaluated.
     * @param type  The attribute type to set to.
     */
    public void setCurrentAttributeType(AttributeType type);
    /**
     * Set the attribute value to be evaluated.
     * @param v The current attribute value to set to.
     */
    public void setCurrentAttributeValue(AttributeValue v);
    /**
     * True if the target matching code found an entry test rule. An
     * entry test rule is an ACI without a targetattr target rule.
     * @param val True if an entry test rule was found.
     */
    public void setEntryTestRule(boolean val);
    /**
     * True if an entry test rule was found.
     * @return True if an entry test rule was found.
     */
    public boolean hasEntryTestRule();
    /**
     * Return the rights for this container's LDAP operation.
     * @return  The rights for the container's LDAP operation.
     */
    public int getRights();
    /**
     * Checks if the container's rights has the specified rights.
     * @param  rights The rights to check for.
     * @return True if the container's rights has the specified rights.
     */
    public boolean hasRights(int rights);
    /**
     * Set the rights of the container to the specified rights.
     * @param rights The rights to set the container's rights to.
     */
    public void setRights(int rights);
}
opends/src/server/org/opends/server/authorization/dseecompat/AciTargets.java
New file
@@ -0,0 +1,472 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying * information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.authorization.dseecompat;
import static org.opends.server.authorization.dseecompat.AciMessages.*;
import static org.opends.server.messages.MessageHandler.getMessage;
import org.opends.server.types.AttributeType;
import org.opends.server.types.DN;
import org.opends.server.types.SearchScope;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
 * This class represents target part of an ACI's syntax. This is the part
 * of an ACI before the ACI body and specifies the entry, attributes, or set
 * of entries and attributes which the ACI controls access.
 *
 * The four supported  ACI target keywords currently
 * supported are: target, targetattr, targetscope and targetfilter.
 * Missing is support for targetattrfilters.
 */
public class AciTargets {
    /*
     * ACI syntax has a target keyword.
     */
    private Target target = null ;
    /*
     * ACI syntax has a targetscope keyword.
     */
    private SearchScope targetScope = SearchScope.WHOLE_SUBTREE;
    /*
     * ACI syntax has a targetattr keyword.
     */
    private TargetAttr targetAttr = null ;
    /*
     * ACI syntax has a targetfilter keyword.
     */
    private TargetFilter targetFilter=null;
    private TargAttrFilters targAttrFilters=null;
    /*
     * These are used in the regular expression parsing.
     */
    private static final int targetElementCount = 3;
    private static final int targetKeywordPos       = 1;
    private static final int targetOperatorPos      = 2;
    private static final int targetExpressionPos    = 3;
    /*
     * TODO Make the regular expression strings below easier to
     * understand.
     *
     * The same note earlier about making regex values easier to
     * understand applies to this class as well.
     */
    private static final String targetRegex =
            "\\(\\s*(\\w+)\\s*(!?=)\\s*\"([^\"]+)\"\\s*\\)\\s*";
    /**
    * Regular expression used in target matching.
    */
    public static final String targetsRegex = "(" + targetRegex + ")*";
    /*
     * Rights that are skipped for certain target evaluations.
     * The test is use the skipRights array is:
     *
     * Either the ACI has a targetattr's rule and the current
     * attribute type is null or the current attribute type has
     * a type specified and the targetattr's rule is null.
     *
     * The actual check against the skipRights array is:
     *
     *  1. Is the ACI's rights in this array? For example,
     *     allow(all) or deny(add)
     *
     *  AND
     *
     *  2. Is the rights from the LDAP operation in this array? For
     *      example, an LDAP add would have rights of add and all.
     *
     *  If both are true, than the target match test returns true
     *  for this ACI.
     */
    private static final int skipRights =
            (AciHandler.ACI_ADD | AciHandler.ACI_DELETE | AciHandler.ACI_PROXY);
    /**
     * Creates an ACI target from the specified arguments. All of these
     * may be null -- the ACI has no targets an will use defaults.
     * @param targetEntry The ACI target keyword if any.
     * @param targetAttr The ACI targetattr keyword if any.
     * @param targetFilter The ACI targetfilter keyword if any.
     * @param targetScope The ACI targetscope keyword if any.
     */
    private AciTargets(Target targetEntry, TargetAttr targetAttr,
                       TargetFilter targetFilter,
                       SearchScope targetScope) {
       this.target=targetEntry;
       this.targetAttr=targetAttr;
       this.targetScope=targetScope;
       this.targetFilter=targetFilter;
    }
    /**
     * Return class representing the ACI target keyword. May be
     * null. The default is the use the DN of the entry containing
     * the ACI and check if the resource entry is a descendant of that.
     * @return The ACI target class.
     */
    public Target getTarget() {
        return target;
    }
    /**
     * Return class representing the ACI targetattr keyword. May be null.
     * The default is to not match any attribute types in an entry.
     * @return The ACI targetattr class.
     */
    public TargetAttr getTargetAttr() {
        return targetAttr;
    }
    /**
     * Return the ACI targetscope keyword. Default is WHOLE_SUBTREE.
     * @return The ACI targetscope information.
     */
    public SearchScope getTargetScope() {
        return targetScope;
    }
    /**
     * Return class representing the  ACI targetfilter keyword. May be null.
     * @return The targetscope information.
     */
    public TargetFilter getTargetFilter() {
        return targetFilter;
    }
    /**
     * Decode an ACI's target part of the syntax from the string provided.
     * @param input String representing an ACI target part of syntax.
     * @param dn The DN of the entry containing the ACI.
     * @return An AciTargets class representing the decoded ACI target string.
     * @throws AciException If the provided string contains errors.
     */
    public static AciTargets decode(String input, DN dn)
    throws AciException {
        Target target=null;
        TargetAttr targetAttr=null;
        TargetFilter targetFilter=null;
        TargAttrFilters targAttrFilters=null;
        SearchScope targetScope=SearchScope.WHOLE_SUBTREE;
        Pattern targetPattern = Pattern.compile(targetRegex);
        Matcher targetMatcher = targetPattern.matcher(input);
        while (targetMatcher.find())
        {
            if (targetMatcher.groupCount() != targetElementCount) {
                int msgID = MSGID_ACI_SYNTAX_INVALID_TARGET_SYNTAX;
                String message = getMessage(msgID, input);
                throw new AciException(msgID, message);
            }
            String keyword = targetMatcher.group(targetKeywordPos);
            EnumTargetKeyword targetKeyword  =
                EnumTargetKeyword.createKeyword(keyword);
            if (targetKeyword == null) {
                int msgID = MSGID_ACI_SYNTAX_INVALID_TARGET_KEYWORD;
                String message = getMessage(msgID, keyword  );
                throw new AciException(msgID, message);
            }
            String operator =
                targetMatcher.group(targetOperatorPos);
            EnumTargetOperator targetOperator =
                EnumTargetOperator.createOperator(operator);
            if (targetOperator == null) {
                int msgID = MSGID_ACI_SYNTAX_INVALID_TARGET_OPERATOR;
                String message = getMessage(msgID, operator);
                throw new AciException(msgID, message);
            }
            String expression = targetMatcher.group(targetExpressionPos);
            switch(targetKeyword)
            {
            case KEYWORD_TARGET:
            {
                if (target == null){
                    target =  Target.decode(targetOperator, expression, dn);
                }
                else
                {
                    int msgID =
                        MSGID_ACI_SYNTAX_INVALID_TARGET_DUPLICATE_KEYWORDS;
                    String message =
                        getMessage(msgID, "target", input);
                    throw new AciException(msgID, message);
                }
                break;
            }
            case KEYWORD_TARGETATTR:
            {
                if (targetAttr == null){
                    targetAttr = TargetAttr.decode(targetOperator,
                            expression);
                }
                else {
                    int msgID =
                        MSGID_ACI_SYNTAX_INVALID_TARGET_DUPLICATE_KEYWORDS;
                    String message =
                        getMessage(msgID, "targetattr", input);
                    throw new AciException(msgID, message);
                }
                break;
            }
            case KEYWORD_TARGETSCOPE:
            {
                // Check the operator for the targetscope is EQUALITY
                if (targetOperator == EnumTargetOperator.NOT_EQUALITY) {
                    int msgID = MSGID_ACI_SYNTAX_INVALID_TARGETSCOPE_OPERATOR;
                    String message = getMessage(msgID, operator);
                    throw new AciException(msgID, message);
                }
                targetScope=createScope(expression);
                break;
            }
            case KEYWORD_TARGETFILTER:
            {
                if (targetFilter == null){
                    targetFilter = TargetFilter.decode(targetOperator,
                            expression);
                }
                else {
                    int msgID =
                        MSGID_ACI_SYNTAX_INVALID_TARGET_DUPLICATE_KEYWORDS;
                    String message =
                        getMessage(msgID, "targetfilter", input);
                    throw new AciException(msgID, message);
                }
                break;
            }
                case KEYWORD_TARGATTRFILTERS:
                {
                    if (targAttrFilters == null){
                        targAttrFilters = TargAttrFilters.decode(targetOperator,
                                expression);
                    }
                    else {
                        int msgID =
                             MSGID_ACI_SYNTAX_INVALID_TARGET_DUPLICATE_KEYWORDS;
                        String message =
                                getMessage(msgID, "targattrfilters", input);
                        throw new AciException(msgID, message);
                    }
                    break;
                }
            }
        }
        return new AciTargets(target, targetAttr, targetFilter, targetScope);
    }
    /*
     * TODO Add support for the SearchScope.SUBORDINATE_SUBTREE scope.
     */
    /**
     * Evaluates a provided scope string and returns an appropriate
     * SearchScope enumeration.
     * @param expression The expression string.
     * @return An search scope enumeration matching the string.
     * @throws AciException If the expression is an invalid targetscope
     * string.
     */
    private static SearchScope createScope(String expression)
    throws AciException {
        if(expression.equalsIgnoreCase("base"))
                return SearchScope.BASE_OBJECT;
        else if(expression.equalsIgnoreCase("onelevel"))
            return SearchScope.SINGLE_LEVEL;
        else if(expression.equalsIgnoreCase("subtree"))
            return SearchScope.WHOLE_SUBTREE;
        else {
            int msgID =
                MSGID_ACI_SYNTAX_INVALID_TARGETSCOPE_EXPRESSION;
            String message = getMessage(msgID, expression);
            throw new AciException(msgID, message);
        }
    }
    /**
     * Checks an ACI's targetfilter information against an target match
     * context.
     * @param aci The ACI to try an match the targetfilter of.
     * @param matchCtx The target match context containing information needed
     * to perform a target match.
     * @return True if the targetfilter matched the target context.
     */
    public static boolean isTargetFilterApplicable(Aci aci,
                                              AciTargetMatchContext matchCtx) {
        boolean ret=true;
        TargetFilter targetFilter=aci.getTargets().getTargetFilter();
        if(targetFilter != null)
             ret=targetFilter.isApplicable(matchCtx);
        return ret;
    }
    /*
     * TODO Evaluate making this method more efficient.
     * The isTargetAttrApplicable method looks a lot less efficient than it
     * could be with regard to the logic that it employs and the repeated use
     * of method calls over local variables.
     */
    /**
     * Checks an provided ACI's targetattr information against a target match
     * context.
     * @param aci The ACI to evaluate.
     * @param targetMatchCtx The target match context to check the ACI against.
     * @return True if the targetattr matched the target context.
     */
    public static boolean isTargetAttrApplicable(Aci aci,
                                AciTargetMatchContext targetMatchCtx) {
        boolean ret=true;
        AciTargets targets=aci.getTargets();
        AttributeType a=targetMatchCtx.getCurrentAttributeType();
        int rights=targetMatchCtx.getRights();
        boolean isFirstAttr=targetMatchCtx.isFirstAttribute();
        if((a != null) && (targets.getTargetAttr() != null)) {
            ret=TargetAttr.isApplicable(a, targets.getTargetAttr());
        } else if((a != null) || (targets.getTargetAttr() != null)) {
            if((aci.hasRights(skipRights)) && (skipRightsHasRights(rights))) {
                ret=true;
            } else if ((targets.getTargetAttr() != null) &&
                    (a == null) && (aci.hasRights(AciHandler.ACI_WRITE))) {
                ret = true;
            } else {
                ret = false;
            }
        }
        if((isFirstAttr) && (aci.getTargets().getTargetAttr() == null))
            targetMatchCtx.setEntryTestRule(true);
        return ret;
    }
    /**
     * Try and match a one or more of the specified rights in the skiprights
     * mask.
     * @param rights The rights to check for.
     * @return  True if the one or more of the specified rights are in the
     * skiprights rights mask.
     */
    public static boolean skipRightsHasRights(int rights) {
         return  ((skipRights & rights) == rights);
    }
    /*
     * TODO Track DS 6.1 changes to ONELEVEL scope.
     *
     * The isTargetApplicable method appears to handle the ONELEVEL scope
     * incorrectly.  The standard definition of onelevel only includes
     * the immediate children of a given entry -- it does not include that
     * entry itself.  It is a bug for the server to behave in any other way.
     * Unfortunately, it does appear that the implementation you currently
     * have matches the implementation in DS6. Nevertheless, I don't think
     * that it is acceptable use this standard term in a nonstandard way and
     * therefore we must change it to the standards-compliant interpretation
     *  which does not include the parent.
     *
     * TODO Investigate supporting alternative representations of the scope.
     *
     * Should we also consider supporting alternate representations of the
     * scope values (in particular, allow "one" in addition to "onelevel"
     * and "sub" in addition to "subtree") to match the very common
     * abbreviations in widespread use for those terms?
     */
    /**
     * Checks an provided ACI's target information against an target match
     * context.
     * @param aci The ACI to match the target against.
     * @param matchCtx The target match context to check the ACI against.
     * @return True if the target matched the context.
     */
    public static boolean isTargetApplicable(Aci aci,
            AciTargetMatchContext matchCtx) {
        boolean ret=true;
        DN entryDN=matchCtx.getResourceEntry().getDN();
        DN targetDN=aci.getDN();
        AciTargets targets=aci.getTargets();
        /*
         * Scoping of the ACI uses either the DN of the entry
         * containing the ACI (aci.getDN above), or if the ACI item
         * contains a simple target DN and a equality operator that
         * target DN is used.
         */
        if((targets.getTarget() != null) &&
                (!targets.getTarget().isPattern())) {
            EnumTargetOperator op=targets.getTarget().getOperator();
            if(op != EnumTargetOperator.NOT_EQUALITY)
                targetDN=targets.getTarget().getDN();
        }
        switch(targets.getTargetScope()) {
        case BASE_OBJECT:
            if(!targetDN.equals(entryDN))
                return false;
            break;
        case SINGLE_LEVEL:
            if((!targetDN.equals(entryDN)) &&
                    (!entryDN.getParent().equals(targetDN)))
                return false;
            break;
        case WHOLE_SUBTREE:
            if(!entryDN.isDescendantOf(targetDN))
                return false;
            break;
        /*
         * TODO Add support for the SearchScope.SUBORDINATE_SUBTREE scope.
         *
         * The isTargetApplicable method doesn't account for the subordinate
         * subtree search scope.
        */
        default:
            return false;
        }
        /*
         * The entry is in scope. For inequality checks, scope was tested
         * against the entry containing the ACI. If operator is inequality,
         * check that it doesn't match the target DN.
         */
        if((targets.getTarget() != null) &&
                (!targets.getTarget().isPattern())) {
            EnumTargetOperator op=targets.getTarget().getOperator();
            if(op == EnumTargetOperator.NOT_EQUALITY) {
                DN tmpDN=targets.getTarget().getDN();
                if(entryDN.isDescendantOf(tmpDN))
                    return false;
            }
        }
        /*
         * There is a pattern, need to match the substring filter
         * created when the ACI was decoded. If inequality flip the
         * result.
         */
        if((targets.getTarget() != null) &&
                (targets.getTarget().isPattern()))  {
            ret=targets.getTarget().matchesPattern(entryDN);
            EnumTargetOperator op=targets.getTarget().getOperator();
            if(ret && op == EnumTargetOperator.NOT_EQUALITY)
                ret=!ret;
        }
        return ret;
    }
}
opends/src/server/org/opends/server/authorization/dseecompat/AuthMethod.java
New file
@@ -0,0 +1,112 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying * information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.authorization.dseecompat;
import static org.opends.server.authorization.dseecompat.AciMessages.*;
import static org.opends.server.messages.MessageHandler.getMessage;
/**
 * The AuthMethod class represents an authmethod bind rule keyword expression.
 */
public class AuthMethod implements KeywordBindRule {
    private EnumAuthMethod authMethod=null;
    private EnumBindRuleType type=null;
    /**
     * Create a class representing an authmethod bind rule keyword from the
     * provided method and bind rule type.
     * @param method An enumeration representing the method of the expression.
     * @param type An enumeration representing the type of the expression.
     */
    private AuthMethod(EnumAuthMethod method, EnumBindRuleType type) {
        this.authMethod=method;
        this.type=type;
    }
    /**
     * Decode a string representing a authmethod bind rule.
     * @param expr  The string representing the bind rule.
     * @param type An enumeration representing the bind rule type.
     * @return  An keyword bind rule class that can be used to evaluate the
     * bind rule.
     * @throws AciException If the expression string is invalid.
     */
    public static KeywordBindRule decode(String expr, EnumBindRuleType type)
    throws AciException  {
        EnumAuthMethod method=EnumAuthMethod.createAuthmethod(expr);
        if (method == null)
        {
            int msgID = MSGID_ACI_SYNTAX_INVALID_AUTHMETHOD_EXPRESSION;
            String message = getMessage(msgID, expr);
            throw new AciException(msgID, message);
        }
        return new AuthMethod(method, type);
    }
    /*
     * TODO Evaluate if AUTHMETHOD_NONE processing is correct. This was fixed
     * prior to Neil's review. Verify in a unit test.
     *
     * I'm not sure that the evaluate() method handles AUTHMETHOD_NONE
     * correctly. My understanding is that it should only match in cases
     * in which no authentication has been performed, but you have it
     * always matching.
     */
    /**
     * Evaluate authmethod bind rule using the provided evaluation context.
     * @param evalCtx  An evaluation context to use.
     * @return  An enumeration evaluation result.
     */
    public EnumEvalResult evaluate(AciEvalContext evalCtx) {
        EnumEvalResult matched=EnumEvalResult.FALSE;
        if(authMethod==EnumAuthMethod.AUTHMETHOD_NONE) {
            matched=EnumEvalResult.TRUE;
        } else if(authMethod==EnumAuthMethod.AUTHMETHOD_SIMPLE) {
            if(evalCtx.getAuthenticationMethod(false)
                    == EnumAuthMethod.AUTHMETHOD_SIMPLE){
                matched=EnumEvalResult.TRUE;
            }
        } else if(authMethod == EnumAuthMethod.AUTHMETHOD_SSL) {
            /*
             * TODO Verfiy that SSL authemethod is correctly handled in a
             * unit test.
             * I'm not sure that the evaluate() method correctly handles
             * SASL EXTERNAL in all cases.  My understanding is that in
             * DS 5/6, an authmethod of SSL is the same as an authmethod of
             * SASL EXTERNAL.  If that's true, then you don't properly handle
             * that condition.
             */
            if(authMethod == evalCtx.getAuthenticationMethod(true))
                    matched=EnumEvalResult.TRUE;
        } else {
            if(authMethod ==evalCtx.getAuthenticationMethod(false))
                matched=EnumEvalResult.TRUE;
        }
        return matched.getRet(type, false);
    }
}
opends/src/server/org/opends/server/authorization/dseecompat/BindRule.java
New file
@@ -0,0 +1,538 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying * information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.authorization.dseecompat;
import static org.opends.server.authorization.dseecompat.AciMessages.*;
import static org.opends.server.messages.MessageHandler.getMessage;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
import java.util.HashMap;
/**
 * This class represents a single bind rule of an ACI permission-bind rule
 * pair.
 */
public class BindRule {
    /*
     * This hash table holds the keyword bind rule mapping.
     */
    private HashMap<String, KeywordBindRule> keywordRuleMap =
                                    new HashMap<String, KeywordBindRule>();
    //True is a boolean "not" was seen.
    private boolean negate=false;
    //Complex bind rules have left and right values.
    private BindRule left = null;
    private BindRule right = null;
    //Enumeration of the boolean type of the complex bind rule ("and" or "or").
    private EnumBooleanTypes booleanType = null;
    //The keyword of a simple bind rule.
    private EnumBindRuleKeyword keyword = null;
    //Regular expression stuff that needs to be made clearer.
    private static final int keywordPos = 1;
    private static final int opPos = 2;
    private static final int expressionPos = 3;
    private static final String keywordRegex = "^(\\w+)";
    private static final String opRegex = "([!=<>]+)";
    private static final String expressionRegex = "\"([^\"]+)\"\\s*";
    private static final String bindruleRegex =
        keywordRegex + "\\s*" + opRegex + "\\s*" + expressionRegex;
    private static final int remainingOperandPos = 1;
    private static final int remainingBindrulePos = 2;
    private static final String remainingBindruleRegex =
        "^\\s*(\\w+)\\s*(.*)$";
    /**
     * Constructor that takes an keyword enumeration and corresponding
     * simple bind rule. The keyword string is the key for the keyword rule in
     * the keywordRuleMap. This is a simple bind rule representation:
     * keyword  op  rule
     *
     * An example of a simple bind rule is:
     *
     *  userdn = "ldap:///anyone"
     *
     * @param keyword The keyword enumeration.
     * @param rule The rule corresponding to this keyword.
     */
    private BindRule(EnumBindRuleKeyword keyword, KeywordBindRule rule) {
        this.keyword=keyword;
        this.keywordRuleMap.put(keyword.toString(), rule);
    }
    /*
     * TODO Verify that this handles the NOT boolean properly by
     * creating a unit test.
     *
     * I'm a bit confused by the constructor which takes left and right
     * arguments. Is it always supposed to have exactly two elements?
     * Is it supposed to keep nesting bind rules in a chain until all of
     * them have been processed?  The documentation for this method needs
     * to be a lot clearer.  Also, it doesn't look like it handles the NOT
     * type properly.
     */
    /**
     * Constructor that represents a complex bind rule. The left and right
     * bind rules are saved along with the boolean type operator. A complex
     * bind rule looks like:
     *
     *  bindrule   booleantype   bindrule
     *
     * Each side of the complex bind rule can be complex bind rule(s)
     * itself. An example of a complex bind rule would be:
     *
     * (dns="*.example.com" and (userdn="ldap:///anyone" or
     * (userdn="ldap:///cn=foo,dc=example,dc=com and ip=129.34.56.66)))
     *
     * This constructor should always have two elements. The processing
     * of a complex bind rule is dependent on the boolean operator type.
     * See the evalComplex method for more information.
     *
     *
     * @param left The bind rule left of the boolean.
     * @param right The right bind rule.
     * @param booleanType The boolean type enumeration ("and" or "or").
     */
    private BindRule(BindRule left, BindRule right,
            EnumBooleanTypes booleanType) {
        this.booleanType = booleanType;
        this.left = left;
        this.right = right;
    }
    /*
     * TODO Verify this method handles escaped parentheses by writing
     * a unit test.
     *
     * It doesn't look like the decode() method handles the possibility of
     * escaped parentheses in a bind rule.
     */
    /**
     * Decode an ACI bind rule string representation.
     * @param input The string representation of the bind rule.
     * @return A BindRule class representing the bind rule.
     * @throws AciException If the string is an invalid bind rule.
     */
    public static BindRule decode (String input)
    throws AciException {
        if ((input == null) || (input.length() == 0))
        {
          return null;
        }
        String bindruleStr = input.trim();
        char firstChar = bindruleStr.charAt(0);
        char[] bindruleArray = bindruleStr.toCharArray();
        if (firstChar == '(')
        {
          BindRule bindrule_1 = null;
          int currentPos;
          int numOpen = 0;
          int numClose = 0;
          // Find the associated closed parenthesis
          for (currentPos = 0; currentPos < bindruleArray.length; currentPos++)
          {
            if (bindruleArray[currentPos] == '(')
            {
              numOpen++;
            }
            else if (bindruleArray[currentPos] == ')')
            {
              numClose++;
            }
            if (numClose == numOpen)
            {
              //We found the associated closed parenthesis
              //the parenthesis are removed
              String bindruleStr1 = bindruleStr.substring(1, currentPos);
              bindrule_1 = BindRule.decode(bindruleStr1);
              break;
            }
          }
          /*
           * Check that the number of open parenthesis is the same as
           * the number of closed parenthesis.
           * Raise an exception otherwise.
           */
          if (numOpen > numClose) {
              int msgID = MSGID_ACI_SYNTAX_BIND_RULE_MISSING_CLOSE_PAREN;
              String message = getMessage(msgID, input);
              throw new AciException(msgID, message);
          }
          /*
           * If there are remaining chars => there MUST be an
           * operand (AND / OR)
           * otherwise there is a syntax error
           */
          if (currentPos < (bindruleArray.length - 1))
          {
            String remainingBindruleStr =
                bindruleStr.substring(currentPos + 1);
            return createBindRule(bindrule_1, remainingBindruleStr);
          }
          else
          {
            return bindrule_1;
          }
        }
        else
        {
          StringBuilder b=new StringBuilder(bindruleStr);
          /*
           * TODO Verify by unit test that this negation
           * is correct. This code handles a simple bind rule negation such
           * as:
           *
           *  not userdn="ldap:///anyone"
           */
          boolean negate=determineNegation(b);
          bindruleStr=b.toString();
          Pattern bindrulePattern = Pattern.compile(bindruleRegex);
          Matcher bindruleMatcher = bindrulePattern.matcher(bindruleStr);
          int bindruleEndIndex;
          if (bindruleMatcher.find())
          {
            bindruleEndIndex = bindruleMatcher.end();
            BindRule bindrule_1 = parseAndCreateBindrule(bindruleMatcher);
            bindrule_1.setNegate(negate);
            if (bindruleEndIndex < bindruleStr.length())
            {
              String remainingBindruleStr =
                  bindruleStr.substring(bindruleEndIndex);
              return createBindRule(bindrule_1, remainingBindruleStr);
            }
            else {
              return bindrule_1;
            }
          }
          else {
              int msgID = MSGID_ACI_SYNTAX_INVALID_BIND_RULE_SYNTAX;
              String message = getMessage(msgID, input);
              throw new AciException(msgID, message);
          }
        }
    }
    /**
     * Parses a simple bind rule using the regular expression matcher.
     * @param bindruleMatcher A regular expression matcher holding
     * the engine to use in the creation of a simple bind rule.
     * @return A BindRule determined by the matcher.
     * @throws AciException If the bind rule matcher found errors.
     */
    private static BindRule parseAndCreateBindrule(Matcher bindruleMatcher)
    throws AciException {
        String keywordStr = bindruleMatcher.group(keywordPos);
        String operatorStr = bindruleMatcher.group(opPos);
        String expression = bindruleMatcher.group(expressionPos);
        EnumBindRuleKeyword keyword;
        EnumBindRuleType operator;
        // Get the Keyword
        keyword = EnumBindRuleKeyword.createBindRuleKeyword(keywordStr);
        if (keyword == null)
        {
            int msgID = MSGID_ACI_SYNTAX_INVALID_BIND_RULE_KEYWORD;
            String message = getMessage(msgID, keywordStr);
            throw new AciException(msgID, message);
        }
        // Get the operator
        operator = EnumBindRuleType.createBindruleOperand(operatorStr);
        if (operator == null) {
            int msgID = MSGID_ACI_SYNTAX_INVALID_BIND_RULE_OPERATOR;
            String message = getMessage(msgID, operatorStr);
            throw new AciException(msgID, message);
        }
        //expression can't be null
        if (expression == null) {
            int msgID = MSGID_ACI_SYNTAX_MISSING_BIND_RULE_EXPRESSION;
            String message = getMessage(msgID, operatorStr);
            throw new AciException(msgID, message);
        }
        validateOperation(keyword, operator);
        KeywordBindRule rule = decode(expression, keyword, operator);
        return new BindRule(keyword, rule);
    }
    /**
     * Create a complex bind rule from a substring
     * parsed from the ACI string.
     * @param bindrule The left hand part of a complex bind rule
     * parsed previously.
     * @param remainingBindruleStr The string used to determine the right
     * hand part.
     * @return A BindRule representing a complex bind rule.
     * @throws AciException If the string contains an invalid
     * right hand bind rule string.
     */
    private static BindRule createBindRule(BindRule bindrule,
            String remainingBindruleStr) throws AciException {
        Pattern remainingBindrulePattern =
            Pattern.compile(remainingBindruleRegex);
        Matcher remainingBindruleMatcher =
            remainingBindrulePattern.matcher(remainingBindruleStr);
        if (remainingBindruleMatcher.find()) {
            String remainingOperand =
                remainingBindruleMatcher.group(remainingOperandPos);
            String remainingBindrule =
                remainingBindruleMatcher.group(remainingBindrulePos);
            EnumBooleanTypes operand =
                EnumBooleanTypes.createBindruleOperand(remainingOperand);
            if ((operand == null)
                    || ((operand != EnumBooleanTypes.AND_BOOLEAN_TYPE) &&
                            (operand != EnumBooleanTypes.OR_BOOLEAN_TYPE))) {
                int msgID =
                    MSGID_ACI_SYNTAX_INVALID_BIND_RULE_BOOLEAN_OPERATOR;
                String message = getMessage(msgID, remainingOperand);
                throw new AciException(msgID, message);
            }
            StringBuilder ruleExpr=new StringBuilder(remainingBindrule);
            /* TODO write a unit test to verify.
             * This is a check for something like:
             * bindrule and not (bindrule)
             * or something ill-advised like:
             * and not not not (bindrule).
             */
            boolean negate=determineNegation(ruleExpr);
            remainingBindrule=ruleExpr.toString();
            BindRule bindrule_2 =
                BindRule.decode(remainingBindrule);
            bindrule_2.setNegate(negate);
            return new BindRule(bindrule, bindrule_2, operand);
        } else {
            int msgID = MSGID_ACI_SYNTAX_INVALID_BIND_RULE_SYNTAX;
            String message = getMessage(msgID, remainingBindruleStr);
            throw new AciException(msgID, message);
        }
    }
    /**
     * Tries to strip an "not" boolean modifier from the string and
     * determine at the same time if the value should be flipped.
     * For example:
     *
     * not not not bindrule
     *
     * is true.
     *
     * @param ruleExpr The bindrule expression to evaluate. This
     * string will be changed if needed.
     * @return True if the boolean needs to be negated.
     */
    private static boolean determineNegation(StringBuilder ruleExpr)  {
        boolean negate=false;
        String ruleStr=ruleExpr.toString();
        while(ruleStr.regionMatches(true, 0, "not ", 0, 4)) {
            negate = !negate;
            ruleStr = ruleStr.substring(4);
        }
        ruleExpr.replace(0, ruleExpr.length(), ruleStr);
        return negate;
    }
    /**
     * Set the negation parameter as determined by the function above.
     * @param v The value to assign negate to.
     */
    private void setNegate(boolean v) {
        negate=v;
    }
    /*
     * TODO This method needs to handle the userattr keyword. Also verify
     * that the rest of the keywords are handled correctly.
     * TODO Investigate moving this method into EnumBindRuleKeyword class.
     *
     * Does validateOperation need a default case?  Why is USERATTR not in this
     * list? Why is TIMEOFDAY not in this list when DAYOFWEEK is in the list?
     * Would it be more appropriate to put this logic in the
     * EnumBindRuleKeyword class so we can be sure it's always handled properly
     *  for all keywords?
     */
    /**
     * Checks the keyword operator enumeration to make sure it is valid.
     * This method doesn't handle all cases.
     * @param keyword The keyword enumeration to evaluate.
     * @param op The operation enumeration to evaluate.
     * @throws AciException If the operation is not valid for the keyword.
     */
    private static void validateOperation(EnumBindRuleKeyword keyword,
                                        EnumBindRuleType op)
    throws AciException {
        switch (keyword) {
        case USERDN:
        case ROLEDN:
        case GROUPDN:
        case IP:
        case DNS:
        case AUTHMETHOD:
        case DAYOFWEEK:
            if ((op != EnumBindRuleType.EQUAL_BINDRULE_TYPE)
                    && (op != EnumBindRuleType.NOT_EQUAL_BINDRULE_TYPE)) {
                int msgID =
                    MSGID_ACI_SYNTAX_INVALID_BIND_RULE_KEYWORD_OPERATOR_COMBO;
                String message = getMessage(msgID,
                                            keyword.toString(),
                                            op.toString());
                throw new AciException(msgID, message);
            }
        }
    }
    /*
     * TODO Investigate moving into the EnumBindRuleKeyword class.
     *
     * Should we move the logic in the
     * decode(String,EnumBindRuleKeyword,EnumBindRuleType) method into the
     * EnumBindRuleKeyword class so we can be sure that it's always
     * handled properly for all keywords?
     */
    /**
     * Creates a keyword bind rule suitable for saving in the keyword
     * rule map table. Each individual keyword class will do further
     * parsing and validation of the expression string.  This processing
     * is part of the simple bind rule creation.
     * @param expr The expression string to further parse.
     * @param keyword The keyword to create.
     * @param op The operation part of the bind rule.
     * @return A keyword bind rule class that can be stored in the
     * map table.
     * @throws AciException If the expr string contains a invalid
     * bind rule.
     */
    private static KeywordBindRule decode(String expr,
                                          EnumBindRuleKeyword keyword,
                                          EnumBindRuleType op)
            throws AciException  {
        KeywordBindRule rule;
        switch (keyword) {
            case USERDN:
            {
                rule = UserDN.decode(expr, op);
                break;
            }
            case ROLEDN:
            {
                rule = RoleDN.decode(expr, op);
                break;
            }
            case GROUPDN:
            {
                rule = GroupDN.decode(expr, op);
                break;
            }
            case IP:
            {
                rule = IpCriteria.decode(expr, op);
                break;
            }
            case DNS:
            {
                rule = DNS.decode(expr, op);
                break;
            }
            case DAYOFWEEK:
            {
                rule = DayOfWeek.decode(expr, op);
                break;
            }
            case TIMEOFDAY:
            {
                rule=TimeOfDay.decode(expr, op);
                break;
            }
            case AUTHMETHOD:
            {
                rule = AuthMethod.decode(expr, op);
                break;
            }
            case USERATTR:
            {
                rule = UserAttr.decode(expr, op);
                break;
            }
            default:  {
                int msgID = MSGID_ACI_SYNTAX_INVALID_BIND_RULE_KEYWORD;
                String message = getMessage(msgID, keyword.toString());
                throw new AciException(msgID, message);
            }
        }
        return rule;
    }
    /**
     * Evaluate the results of a complex bind rule. If the boolean
     * is an AND type then left and right must be TRUE, else
     * it must be an OR result and one of the bind rules must be
     * TRUE.
     * @param left The left bind rule result to evaluate.
     * @param right The right bind result to evaluate.
     * @return The result of the complex evaluation.
     */
    private EnumEvalResult evalComplex(EnumEvalResult left,
                                       EnumEvalResult right) {
        EnumEvalResult ret=EnumEvalResult.FALSE;
        if(booleanType == EnumBooleanTypes.AND_BOOLEAN_TYPE) {
           if((left == EnumEvalResult.TRUE) && (right == EnumEvalResult.TRUE))
                ret=EnumEvalResult.TRUE;
        } else if((left == EnumEvalResult.TRUE) ||
                  (right == EnumEvalResult.TRUE))
            ret=EnumEvalResult.TRUE;
       return ret;
    }
    /**
     * Evaluate an bind rule against an evaluation context. If it is a simple
     * bind rule (no boolean type) then grab the keyword rule from the map
     * table and call the corresponding evaluate function. If it is a
     * complex rule call the routine above "evalComplex()".
     * @param evalCtx The evaluation context to pass to the keyword
     * evaluation function.
     * @return An result enumeration containing the result of the evaluation.
     */
    public EnumEvalResult evaluate(AciEvalContext evalCtx) {
        EnumEvalResult ret;
        //Simple bind rules have a null booleanType enumeration.
        if(this.booleanType == null) {
            KeywordBindRule rule=keywordRuleMap.get(keyword.toString());
            ret = rule.evaluate(evalCtx);
        }  else
            ret=evalComplex(left.evaluate(evalCtx),right.evaluate(evalCtx));
        return EnumEvalResult.negateIfNeeded(ret, negate);
    }
}
opends/src/server/org/opends/server/authorization/dseecompat/DNS.java
New file
@@ -0,0 +1,160 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying * information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.authorization.dseecompat;
import static org.opends.server.authorization.dseecompat.AciMessages.*;
import static org.opends.server.messages.MessageHandler.getMessage;
import java.util.LinkedList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
 * This class implements the dns bind rule keyword.
 */
public class DNS implements KeywordBindRule {
    LinkedList<String> patterns=null;
    private EnumBindRuleType type=null;
    /**
     * Create a class representing a dns bind rule keyword.
     * @param patterns List of dns patterns to match against.
     * @param type An enumeration representing the bind rule type.
     */
    private DNS(LinkedList<String> patterns, EnumBindRuleType type) {
        this.patterns=patterns;
        this.type=type;
    }
    /**
     * Decode an string representing a dns bind rule.
     * @param expr A string representation of the bind rule.
     * @param type  An enumeration representing the bind rule type.
     * @return  A keyword bind rule class that can be used to evaluate
     * this bind rule.
     * @throws AciException  If the expression string is invalid.
     */
    public static DNS decode(String expr,  EnumBindRuleType type)
    throws AciException
    {
        String valueRegex = "([a-zA-Z0-9\\.\\-\\*]+)";
        String valuesRegex = valueRegex + "\\s*(,\\s*" + valueRegex + ")*";
        if (!Pattern.matches(valuesRegex, expr)) {
            int msgID = MSGID_ACI_SYNTAX_INVALID_DNS_EXPRESSION;
            String message = getMessage(msgID, expr);
            throw new AciException(msgID, message);
        }
        LinkedList<String>dns=new LinkedList<String>();
        int valuePos = 1;
        Pattern valuePattern = Pattern.compile(valueRegex);
        Matcher valueMatcher = valuePattern.matcher(expr);
        while (valueMatcher.find()) {
            String hn=valueMatcher.group(valuePos);
            String[] hnArray=hn.split("\\.", -1);
            for(int i=1, n=hnArray.length; i < n; i++) {
                if(hnArray[i].equals("*")) {
                    int msgID = MSGID_ACI_SYNTAX_INVALID_DNS_WILDCARD;
                    String message = getMessage(msgID, expr);
                    throw new AciException(msgID, message);
                }
            }
            dns.add(hn);
        }
        return new DNS(dns, type);
    }
    /**
     * Performs evaluation of dns keyword bind rule using the provided
     * evaluation context.
     * @param evalCtx  An evaluation context to use in the evaluation.
     * @return An enumeration evaluation result.
     */
    public EnumEvalResult evaluate(AciEvalContext evalCtx) {
        EnumEvalResult matched=EnumEvalResult.FALSE;
        String[] remoteHost = evalCtx.getHostName().split("\\.", -1);
        for(String p : patterns) {
          String[] pat = p.split("\\.", -1);
          if(evalHostName(remoteHost, pat)) {
              matched=EnumEvalResult.TRUE;
              break;
          }
        }
        return matched.getRet(type, false);
    }
    /*
     * TODO Verify that a DNS pattern of "*" is valid by writing a unit
     * test. Probably isn't.
     *
     * TODO Evaluate if extending the wild-card matching to multiple name
     * components should be supported. Currently wild-cards are only permitted
     * in the leftmost field and the rest of the domain name components must
     * match.
     *
     * TODO Evaluate extending wild-card matching to non-complete name matching.
     *
     * Is it acceptable to have a DNS address of just "*"
     * (which presumably will match any system)?
     *
     * Is it acceptable for a wildcard to match multiple name components?  For
     * example, is "*.example.com" supposed to be considered a match for
     * "host.east.example.com"?  Similarly, would a pattern like
     * "www.*.example.com" match "www.newyork.east.example.com"?  It doesn't
     * appear that the current implementation matches either of them.
     *
     * Is it acceptable for a wildcard to appear as anything other than a
     * complete name component?  For example, if I have three web servers
     * "www1.example.com","www2.example.com", and "www3.example.com", then
     * can I use "www*.example.com"? It doesn't appear that the current
     * implementation allows that.  Further, would "www*.example.com" match
     * cases like "www.example.com" or "www1.east.example.com"?
     */
    /**
     * Checks an array containing the remote client's hostname against
     * patterns specified in the bind rule expression. Wild-cards are
     * only permitted in the leftmost field and the rest of the domain
     * name array components must match.
     * @param remoteHostName  Array containing components of the remote clients
     * hostname (split on ".").
     * @param pat  An array containing the pattern specified in
     * the bind rule expression. The first array slot may be a wild-card "*".
     * @return  True if the remote hostname matches the pattern.
     */
    private boolean evalHostName(String[] remoteHostName, String[] pat) {
        if(remoteHostName.length != pat.length)
            return false;
        for(int i=0;i<remoteHostName.length;i++)
        {
            if(!pat[i].equals("*")) {
                if(!pat[i].equalsIgnoreCase(remoteHostName[i]))
                    return false;
            }
        }
        return true;
    }
}
opends/src/server/org/opends/server/authorization/dseecompat/DayOfWeek.java
New file
@@ -0,0 +1,96 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying * information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.authorization.dseecompat;
import static org.opends.server.authorization.dseecompat.AciMessages.*;
import static org.opends.server.messages.MessageHandler.getMessage;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.LinkedList;
/**
 * This class implements the dayofweek bind rule keyword.
 */
public class DayOfWeek  implements KeywordBindRule {
    LinkedList<EnumDayOfWeek> days=null;
    private EnumBindRuleType type=null;
    /**
     * Create a class representing a dayofweek bind rule keyword.
     * @param days  A list of day of the week enumerations.
     * @param type An enumeration representing the bind rule type.
     */
    private DayOfWeek(LinkedList<EnumDayOfWeek> days, EnumBindRuleType type) {
        this.days=days;
        this.type=type;
    }
    /**
     * Decode an string representing a dayofweek bind rule.
     * @param expr A string representation of the bind rule.
     * @param type  An enumeration representing the bind rule type.
     * @return  A keyword bind rule class that can be used to evaluate
     * this bind rule.
     * @throws AciException  If the expression string is invalid.
     */
    public static KeywordBindRule decode(String expr, EnumBindRuleType type)
    throws AciException
    {
        LinkedList<EnumDayOfWeek>days=new LinkedList<EnumDayOfWeek>();
        String[] dayArray=expr.split(",", -1);
        for(int i=0, m=dayArray.length; i < m; i++)
        {
          EnumDayOfWeek day=EnumDayOfWeek.createDayOfWeek(dayArray[i]);
          if (day == null)
          {
              int msgID = MSGID_ACI_SYNTAX_INVALID_DAYOFWEEK;
              String message = getMessage(msgID, expr);
              throw new AciException(msgID, message);
          }
          days.add(day);
        }
        return new DayOfWeek(days, type);
    }
    /**
     * Performs evaluation of a dayofweek bind rule using the provided
     * evaluation context.
     * @param evalCtx  An evaluation context to use in the evaluation.
     * @return An enumeration evaluation result.
     */
    public EnumEvalResult evaluate(AciEvalContext evalCtx) {
        EnumEvalResult matched=EnumEvalResult.FALSE;
        GregorianCalendar calendar = new GregorianCalendar();
        EnumDayOfWeek dayofweek
            = EnumDayOfWeek.getDayOfWeek(calendar.get(Calendar.DAY_OF_WEEK));
        if(days.contains(dayofweek))
            matched=EnumEvalResult.TRUE;
        return matched.getRet(type, false);
    }
}
opends/src/server/org/opends/server/authorization/dseecompat/EnumAccessType.java
New file
@@ -0,0 +1,88 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying * information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.authorization.dseecompat;
/**
 * This class provides an enumeration of the two access
 * types (allow, deny).
 */
public enum EnumAccessType {
    /**
     * Allow access type.
     */
    ALLOW   ("allow"),
    /**
     *  Deny access type.
     */
    DENY    ("deny");
    private final String accessType;
    /**
     * Constructor that sets the accessType string.
     * @param accessType The access type string to set.
     */
    EnumAccessType (String accessType){
        this.accessType = accessType ;
    }
    /**
     * Checks if the access type is equal to the string
     * representation passed in.
     * @param type The string representation of the access type.
     * @return True if the access types are equal.
     */
    public boolean isAccessType(String type){
        return type.equalsIgnoreCase(accessType);
    }
    /*
     * TODO Make this method and all other Enum decode methods more efficient.
     *
     * Using the Enum.values() method is documented to be potentially slow.
     * If we ever expect to use the decode() method in a performance-critical
     * manner, then we should make it more efficient.  The same thing applies
     * to all of the other enumeration types defined in the package.
     */
    /**
     * Decodes an access type enumeration from a string passed into the method.
     * @param type The string representation of the access type.
     * @return   Return an EnumAccessType matching the string representation,
     * or null if the string is not valid.
     */
    public static EnumAccessType decode(String type){
        if (type != null){
            for (EnumAccessType t : EnumAccessType.values()) {
                if (t.isAccessType(type)){
                    return t;
                }
            }
        }
        return null;
    }
}
opends/src/server/org/opends/server/authorization/dseecompat/EnumAuthMethod.java
New file
@@ -0,0 +1,116 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying * information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.authorization.dseecompat;
/*
 * TODO Evaluate moving this to a non-enumeration class that can add
 * SASL mechanisms dynamically.
 *
 *  Given our previous discussion about needing to support any kind of SASL
 *  mechanism that may be registered with the server, perhaps an enum isn't
 *  the right way to handle this because we don't know ahead of time what
 *  auth methods might be available (certainly not at compile time, but
 *  potentially not even at runtime since I can add support for a new SASL
 *  mechanism on the fly without restarting the server).
 */
/**
 * This class provides an enumeration of the allowed authmethod types.
 */
public enum EnumAuthMethod {
    /**
     * The enumeration type when the bind rule has specified authentication of
     * none.
     */
    AUTHMETHOD_NONE          ("none"),
    /**
      * The enumeration type when the bind rule has specified authentication of
     *  simple.
     */
    AUTHMETHOD_SIMPLE        ("simple"),
    /**
      * The enumeration type when the bind rule has specified authentication of
     *  ssl client auth.
     */
    AUTHMETHOD_SSL           ("ssl"),
    /**
     * The enumeration type when the bind rule has specified authentication of
     * sasl DIGEST-MD5.
     */
    AUTHMETHOD_SASL_MD5      ("sasl DIGEST-MD5"),
    /**
     * The enumeration type when the bind rule has specified authentication of
     * sasl EXTERNAL.
     */
    AUTHMETHOD_SASL_EXTERNAL ("sasl EXTERNAL"),
    /**
     * The enumeration type when the bind rule has specified authentication of
     * sasl GSSAPI.
     */
    AUTHMETHOD_SASL_GSSAPI   ("sasl GSSAPI"),
    /**
     * Special internal enumeration for when there is no match.
     */
    AUTHMETHOD_NOMATCH       ("nomatch");
    /**
     * The name of the authmethod.
     */
    public String authmethod = null;
    /**
     * Creates a new enumeration type for this authmethod.
     * @param authmethod The authemethod name.
     */
    EnumAuthMethod (String authmethod){
        this.authmethod = authmethod;
    }
    /**
     * Checks if a authmethod name is equal to this enumeration.
     * @param myauthmethod  The name to test for.
     * @return  True if the names match.
     */
    public boolean isAuthMethod(String myauthmethod){
        return myauthmethod.equalsIgnoreCase(this.authmethod);
    }
    /**
     * Creates an authmethod enumeration from the name passed in.
     * @param myauthmethod The name to create.
     * @return An authmethod enumeration if the name was found or null if not.
     */
    public static EnumAuthMethod createAuthmethod(String myauthmethod){
        if (myauthmethod != null){
            for (EnumAuthMethod t : EnumAuthMethod.values()){
                if (t.isAuthMethod(myauthmethod)){
                    return t;
                }
            }
        }
        return null;
    }
}
opends/src/server/org/opends/server/authorization/dseecompat/EnumBindRuleKeyword.java
New file
@@ -0,0 +1,118 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying * information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.authorization.dseecompat;
/**
 * This class provides an enumeration of the allowed bind rule
 * keyword types.
 */
public enum EnumBindRuleKeyword {
    /**
     * The enumeration type when the bind rule has specified keyword of
     * userdn.
     */
    USERDN     ("userdn"),
    /**
     * The enumeration type when the bind rule has specified keyword of
     * groupdn.
     */
    GROUPDN    ("groupdn"),
    /**
     * The enumeration type when the bind rule has specified keyword of
     * roledn.
     */
    ROLEDN     ("roledn"),
    /**
     * The enumeration type when the bind rule has specified keyword of
     * ip.
     */
    IP         ("ip"),
    /**
     * The enumeration type when the bind rule has specified keyword of
     * dns.
     */
    DNS        ("dns"),
    /**
     * The enumeration type when the bind rule has specified keyword of
     * dayofweek.
     */
    DAYOFWEEK  ("dayofweek"),
    /**
     * The enumeration type when the bind rule has specified keyword of
     * timeofday.
     */
    TIMEOFDAY  ("timeofday"),
    /**
     * The enumeration type when the bind rule has specified keyword of
     * userattr.
     */
    USERATTR ("userattr"),
    /**
     * The enumeration type when the bind rule has specified keyword of
     * authmethod.
     */
    AUTHMETHOD ("authmethod");
    /**
     * The keyword name.
     */
    public final String keyword;
    /**
     * Creates a new enumeration type for the specified keyword.
     * @param keyword The keyword name.
     */
    EnumBindRuleKeyword(String keyword){
        this.keyword = keyword;
    }
    /**
     * Checks to see if the keyword string is equal to the enumeration.
     * @param keywordStr   The keyword name to check equality for.
     * @return  True if the keyword is equal to the specified name.
     */
    public boolean isBindRuleKeyword(String keywordStr){
        return keywordStr.equalsIgnoreCase(this.keyword);
    }
    /**
     * Create a new enumeration type for the specified keyword name.
     * @param keywordStr The name of the enumeration to create.
     * @return A new enumeration type for the name or null if the name is
     * not valid.
     */
    public static EnumBindRuleKeyword createBindRuleKeyword(String keywordStr){
        if (keywordStr != null){
            for (EnumBindRuleKeyword t : EnumBindRuleKeyword.values()){
                if (t.isBindRuleKeyword(keywordStr)){
                    return t;
                }
            }
        }
        return null;
    }
}
opends/src/server/org/opends/server/authorization/dseecompat/EnumBindRuleType.java
New file
@@ -0,0 +1,104 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying * information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.authorization.dseecompat;
/**
 * This class provides an enumeration of the allowed bind rule types.
 */
public enum EnumBindRuleType {
    /**
     * The enumeration type when the bind rule has specified type of
     * "=".
     */
    EQUAL_BINDRULE_TYPE             ("="),
    /**
     * The enumeration type when the bind rule has specified type of
     * "!=".
     */
    NOT_EQUAL_BINDRULE_TYPE         ("!="),
    /**
     * The enumeration type when the bind rule has specified type of
     * "<".
     */
    LESS_BINDRULE_TYPE              ("<"),
    /**
     * The enumeration type when the bind rule has specified type of
     * "<=".
     */
    LESS_OR_EQUAL_BINDRULE_TYPE     ("<="),
    /**
     * The enumeration type when the bind rule has specified type of
     * >".
     */
    GREATER_BINDRULE_TYPE           (">"),
    /**
     * The enumeration type when the bind rule has specified type of
     * ">=".
     */
    GREATER_OR_EQUAL_BINDRULE_TYPE  (">=");
    /**
     * The bind rule type name.
     */
    private final String type;
    /**
     * Creates a new enumeration type for the specified bind rule type.
     * @param type The bind rule type name.
     */
    EnumBindRuleType(String type){
        this.type = type;
    }
    /**
     * Checks to see if the type string is equal to the enumeration type
     * name.
     * @param type  The type name to check equality for.
     * @return  True if the keyword is equal to the specified name.
     */
    public boolean isBindRuleType(String type){
        return type.equals(this.type);
    }
    /**
     * Create a new enumeration type for the specified type name.
     * @param type  The name of the enumeration to create.
     * @return A new enumeration type for the name or null if the name is
     * not valid.
     */
    public static EnumBindRuleType createBindruleOperand(String type){
        if (type != null){
            for (EnumBindRuleType t : EnumBindRuleType.values()){
                if (t.isBindRuleType(type)){
                    return t;
                }
            }
        }
        return null;
    }
}
opends/src/server/org/opends/server/authorization/dseecompat/EnumBooleanTypes.java
New file
@@ -0,0 +1,90 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying * information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.authorization.dseecompat;
/**
 * This class provides an enumeration of the allowed bind rule booelan types.
 */
public enum EnumBooleanTypes {
    /**
     * The enumeration type when the bind rule has specified boolean type of
     * "AND".
     */
    AND_BOOLEAN_TYPE               ("and"),
    /**
     * The enumeration type when the bind rule has specified boolean type of
     * "OR".
     */
    OR_BOOLEAN_TYPE                ("or"),
    /**
     * The enumeration type when the bind rule has specified boolean type of
     * "NOT".
     */
    NOT_BOOLEAN_TYPE                ("not");
    /**
    * The bind rule boolean type name.
     */
    private final String booleanType;
    /**
     * Creates a new enumeration type for the specified bind rule boolean type.
     * @param booleanType  The boolean type name.
     */
    EnumBooleanTypes(String booleanType){
        this.booleanType = booleanType;
    }
    /**
     * Checks to see if the boolean type string is equal to the enumeration type
     * name.
     * @param booleanType  The type name to check equality for.
     * @return  True if the keyword is equal to the specified name.
     */
    public boolean isBindRuleBooleanOperand(String booleanType){
        return booleanType.equalsIgnoreCase(this.booleanType);
    }
    /**
     * Create a new enumeration type for the specified boolean type name.
     * @param booleanType  The name of the enumeration to create.
     * @return A new enumeration type for the name or null if the name is
     * not valid.
     */
    public static
    EnumBooleanTypes createBindruleOperand(String booleanType) {
        if (booleanType != null){
          for (EnumBooleanTypes t : EnumBooleanTypes.values()) {
                if (t.isBindRuleBooleanOperand(booleanType)) {
                    return t;
                }
            }
        }
        return null;
    }
}
opends/src/server/org/opends/server/authorization/dseecompat/EnumDayOfWeek.java
New file
@@ -0,0 +1,154 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying * information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.authorization.dseecompat;
import java.util.Calendar;
/**
 * This class provides an enumeration of the allowed dayofweek types.
 */
public enum EnumDayOfWeek {
    /**
     * The enumeration type when the bind rule has specified dayofweek type of
     * "mon".
     */
    DAY_MONDAY      ("mon"),
    /**
     * The enumeration type when the bind rule has specified dayofweek type of
     * "tue" .
     */
    DAY_TUESDAY     ("tue"),
    /**
     * The enumeration type when the bind rule has specified dayofweek type of
     * "wed".
     */
    DAY_WEDNESDAY   ("wed"),
    /**
     * The enumeration type when the bind rule has specified dayofweek type of
     * "thu".
     */
    DAY_THURSDAY    ("thu"),
    /**
     * The enumeration type when the bind rule has specified dayofweek type of
     * "fri".
     */
    DAY_FRIDAY      ("fri"),
    /**
     * The enumeration type when the bind rule has specified dayofweek type of
     * "sat".
     */
    DAY_SATURDAY    ("sat"),
    /**
     * The enumeration type when the bind rule has specified dayofweek type of
     * "sun".
     */
    DAY_SUNDAY      ("sun");
    /**
    * The bind rule dayofweek type name.
     */
    private String day = null;
    /**
     * Creates a new enumeration type for the specified bind rule dayofweek
     * type.
     * @param day  The day name.
     */
    EnumDayOfWeek (String day){
        this.day = day;
    }
    /**
     * Creates a new enumeration type for the specified bind rule dayofweek
     * type.
     * @param day  The boolean type name.
     * @return  True if the keyword is equal to the specified name.
     */
    public boolean isDayOfWeek(String day){
        return day.equalsIgnoreCase(this.day);
    }
    /**
     * Create a new enumeration type for the specified dayofweek type name.
     * @param day  The name of the enumeration to create.
     * @return A new enumeration type for the name or null if the name is
     * not valid.
     */
    public static EnumDayOfWeek createDayOfWeek(String day)
    {
        if (day != null){
            for (EnumDayOfWeek t : EnumDayOfWeek.values()){
                if (t.isDayOfWeek(day)){
                    return t;
                }
            }
        }
        return null;
    }
    /*
     * TODO Evaluate supporting alternative forms for days of the week.
     *
     *  Should we support alternate forms for the names of the days of the
     *  week in the isDayOfWeek() or createdayOfWeek() method?  In particular,
     *  should we handle the case in which the user provided the full name
     *  (e.g., "monday" instead of "mon")?
     */
    /**
     *  Return a enumeration relating to a Calendar day of week field.
     * @param day The day of week index to get.
     * @return  An enumeration corresponding to the wanted day of the week or
     * null if the day index is invalid.
     */
    public static EnumDayOfWeek getDayOfWeek(int day)
    {
        switch(day){
        case Calendar.SUNDAY:
            return DAY_SUNDAY;
        case Calendar.MONDAY:
            return DAY_MONDAY;
        case Calendar.TUESDAY:
            return DAY_TUESDAY;
        case Calendar.WEDNESDAY:
            return DAY_WEDNESDAY;
        case Calendar.THURSDAY:
            return DAY_THURSDAY;
        case Calendar.FRIDAY:
            return DAY_FRIDAY;
        case Calendar.SATURDAY:
            return DAY_SATURDAY;
        }
        return null;
    }
}
opends/src/server/org/opends/server/authorization/dseecompat/EnumEvalResult.java
New file
@@ -0,0 +1,111 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying * information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.authorization.dseecompat;
/**
 * This class provides an enumeration of evaluation results returned by
 * the bind rule evaluation methods.
 */
public enum EnumEvalResult {
    /**
     * This enumeration is returned when the result of the evaluation is TRUE.
     */
    TRUE(0),
    /**
     * This enumeration is returned when the result of the evaluation is FALSE.
     */
    FALSE(1),
    /**
     * This enumeration is returned when the result of the evaluation is FAIL.
     * This should only be returned when a system failure occurred.
     */
    FAIL(2),
    /**
     * This is an internal enumeration used during evaluation of bind rule when
     * internal processing of the evaluation is undefined. It is never returned
     * back as a result of the evaluation.
     */
    ERR(3);
    /**
     * Create a new enumeration type for the specified result value.
     * @param v The value of the result.
     */
    EnumEvalResult(int v) {
    }
    /**
     * The method tries to determine if the result was undefined, and if so
     * it returns an FAIL enumeration. If the result was not undefined (the
     * common case for all of the bind rule evaluations), then the bind rule
     * type is examined to see if the result needs to be flipped (type equals
     * NOT_EQUAL_BINDRULE_TYPE).
     * @param type The bind rule type enumeration of the bind rule.
     * @param undefined  A flag that signals the the result was undefined.
     * @return An enumeration containing the correct result after processing
     * the undefined field and the bind rule type enumeration.
     */
    public EnumEvalResult getRet(EnumBindRuleType type, boolean undefined) {
        EnumEvalResult ret=this;
        if(this.equals(EnumEvalResult.TRUE) || !undefined) {
            if(type.equals(EnumBindRuleType.NOT_EQUAL_BINDRULE_TYPE))
                if(this.equals(EnumEvalResult.TRUE))
                    ret=EnumEvalResult.FALSE;
                else
                    ret=EnumEvalResult.TRUE;
        } else
            ret=EnumEvalResult.FAIL;
        return ret;
    }
    /**
     * This method is used to possibly negate the result of a simple bind rule
     * evaluation. If the boolean is true than the result is negated.
     * @param v The enumeration result of the simple bind rule evaluation.
     * @param n If true the result should be negated (TRUE->FALSE, FALSE->TRUE).
     * @return  A possibly negated enumeration result.
     */
    public  static EnumEvalResult negateIfNeeded(EnumEvalResult v, boolean n) {
        if(n) {
            if(v.equals(EnumEvalResult.TRUE))
                v=EnumEvalResult.FALSE;
            else
                v=EnumEvalResult.TRUE;
        }
        return v;
    }
    /**
     * Helper method that converts this enumeration to a boolean. Usually the
     * FAIL enumeration has been handled before this is called.
     * @return True if the enumeration is TRUE, else false.
     */
    public boolean getBoolVal() {
        return this == EnumEvalResult.TRUE;
    }
}
opends/src/server/org/opends/server/authorization/dseecompat/EnumRight.java
New file
@@ -0,0 +1,173 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying * information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.authorization.dseecompat;
/**
 * This class provides an enumeration of the allowed rights.
 */
public enum EnumRight {
    /**
     * This enumeration is returned when the result of the right is "read".
     */
    READ        ("read"),
    /**
     * This enumeration is returned when the result of the right is "write".
     */
    WRITE       ("write"),
    /**
     * This enumeration is returned when the result of the right is "add".
     */
    ADD         ("add"),
    /**
     * This enumeration is returned when the result of the right is "delete".
     */
    DELETE      ("delete"),
    /**
     * This enumeration is returned when the result of the right is "search".
     */
    SEARCH      ("search"),
    /**
     * This enumeration is returned when the result of the right is "compare".
     */
    COMPARE     ("compare"),
    /**
     * This enumeration is returned when the result of the right is
     * "selfwrite".
     */
    SELFWRITE   ("selfwrite"),
    /**
     * This enumeration is returned when the result of the right is "proxy".
     */
    PROXY       ("proxy"),
    /**
     * This enumeration is returned when the result of the right is "import".
     */
    IMPORT      ("import"),
    /**
     * This enumeration is returned when the result of the right is "export".
     */
    EXPORT      ("export"),
    /**
     * This enumeration is returned when the result of the right is "all".
     */
    ALL         ("all"),
    /**
     * This enumeration is used internally by the modify operation
     * processing and is not part of the ACI syntax.
     */
    DELWRITE    ("delwrite"),
    /**
     * This enumerations is used internally by the modify operation
     * processing and is not part of the ACI syntax.
     */
    ADDWRITE    ("addwrite");
    /**
     * The name of the right.
     */
    private final String right;
    /**
     * Creates an enumeration of the right name.
     * @param right The name of the right.
     */
    EnumRight (String right) {
        this.right = right ;
    }
    /**
     * Checks if the enumeration is equal to the right name.
     * @param right The name of the right to check.
     * @return  True if the right is equal to the enumeration's.
     */
    public boolean isRight(String right){
        return right.equalsIgnoreCase(this.right);
    }
    /**
     * Creates an enumeration of the right name.
     * @param right The name of the right.
     * @return An enumeration of the right or null if the name is invalid.
     */
    public static EnumRight decode(String right){
        if (right != null){
            for (EnumRight t : EnumRight.values()){
                if (t.isRight(right)){
                    return t;
                }
            }
        }
        return null;
    }
    /**
     * Returns bit mask associated with the specified right.
     * @param right The right enumeration to return the mask for.
     * @return The bit mask associated with the right.
     */
    public static int getMask(EnumRight right) {
        int mask=AciHandler.ACI_NULL;
        switch(right) {
            case READ:
                mask=AciHandler.ACI_READ;
                break;
            case WRITE:
                mask=AciHandler.ACI_WRITE;
                break;
            case ADD:
                mask=AciHandler.ACI_ADD;
                break;
            case DELETE:
                mask=AciHandler.ACI_DELETE;
                break;
            case SEARCH:
                mask=AciHandler.ACI_SEARCH;
                break;
            case COMPARE:
                mask=AciHandler.ACI_COMPARE;
                break;
            case ALL:
                mask=AciHandler.ACI_ALL;
                break;
            case  EXPORT:
                mask=AciHandler.ACI_EXPORT;
                break;
            case IMPORT:
                mask=AciHandler.ACI_IMPORT;
                break;
            case PROXY:
                mask=AciHandler.ACI_PROXY;
                break;
            case SELFWRITE:
                mask=AciHandler.ACI_SELF;
                break;
        }
        return mask;
    }
}
opends/src/server/org/opends/server/authorization/dseecompat/EnumTargetKeyword.java
New file
@@ -0,0 +1,108 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying * information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.authorization.dseecompat;
/**
 *  This class provides an enumeration of the valid ACI target keywords.
 */
public enum EnumTargetKeyword {
    /**
     * This enumeration is returned when the target keyword is
     * "target".
     */
    KEYWORD_TARGET      ("target"),
    /**
     * This enumeration is returned when the target keyword is
     * "targetattr".
     */
    KEYWORD_TARGETATTR  ("targetattr"),
    /**
     * This enumeration is returned when the target keyword  is
     * "targetscope".
     */
    KEYWORD_TARGETSCOPE ("targetscope"),
    /**
     * This enumeration is returned when the target keyword is
     * "targetfilter".
     */
    KEYWORD_TARGETFILTER ("targetfilter"),
    /**
     * This enumeration is returned when the target keyword is
     * "targattrfilters".
     */
    KEYWORD_TARGATTRFILTERS ("targattrfilters");
    /*
     * TODO Add support for the targattrfilters keyword.
     */
    /**
     * The target keyword name.
     */
    private final String keyword;
    /**
     * Create a target keyword enumeration of the specified name.
     * @param keyword    The keyword name.
     */
    EnumTargetKeyword(String keyword){
        this.keyword = keyword;
    }
    /**
     * Checks if the keyword name is equal to the enumeration name.
     * @param keyword The keyword name to check.
     * @return  True if the keyword name is equal to the enumeration.
     */
    public boolean isKeyword(String keyword){
        return keyword.equalsIgnoreCase(this.keyword);
    }
    /**
     * Create an enumeration of the provided keyword name.
     * @param keyword The keyword name to create.
     * @return  An enumeration of the specified keyword name or null
     * if the keyword name is invalid.
     */
    public static EnumTargetKeyword createKeyword(String keyword){
        if (keyword != null){
            for (EnumTargetKeyword t : EnumTargetKeyword.values()){
                if (t.isKeyword(keyword)){
                    return t;
                }
            }
        }
        return null;
    }
    /**
     * Return the enumeration keyword name.
     * @return The keyword name.
     */
    public String getKeyword() {
      return keyword;
    }
}
opends/src/server/org/opends/server/authorization/dseecompat/EnumTargetOperator.java
New file
@@ -0,0 +1,81 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying * information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.authorization.dseecompat;
/**
 *  This class provides an enumeration of the valid ACI target operators.
 */
public enum EnumTargetOperator {
    /**
    * This enumeration is returned when the target operator is  "=".
     */
    EQUALITY        ("="),
    /**
    * This enumeration is returned when the target operator is  "!=".
     */
    NOT_EQUALITY    ("!=");
    /**
     * The target operator name.
     */
    private final String operator;
    /**
     * Create an enumeration of the provided operator name.
     * @param operator The operator name to create.
     */
    EnumTargetOperator(String operator){
        this.operator = operator;
    }
    /**
     * Checks if the provided operator name is equal to the enumeration.
     * @param op The operator name to check for.
     * @return  True if the operator name is equal to the enumeration.
     */
    public boolean isOperator(String op){
        return op.equalsIgnoreCase(operator);
    }
    /**
     * Creates an enumeration of the specified operator type name.
     * @param op The operator type name to create.
     * @return  Return an enumeration of the operator type name or null if the
     * name is invalid.
     */
    public static EnumTargetOperator createOperator(String op){
        if (op != null){
            for (EnumTargetOperator t : EnumTargetOperator.values()){
                if (t.isOperator(op)){
                    return t;
                }
            }
        }
        return null;
    }
}
opends/src/server/org/opends/server/authorization/dseecompat/EnumUserDNType.java
New file
@@ -0,0 +1,84 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying * information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.authorization.dseecompat;
/**
 * Enumeration that represents the type an "userdn" keyword DN can have.
 * The issues is the syntax allows invalid URLs such as "ldap:///anyone"
 * and "ldap:///self".  The strategy is to use this class to hold
 * the type and another class UserDNTypeURL to hold both this type and URL.
 *
 * If the URL is an invalid URL, then a dummy URL is saved.
 * For types such as URL, DN and DNPATTERN, the actual URL is saved and can
 * be retrieved by the UserDN.evaluate() method when needed. The dummy URL is
 * ignored in the UserDN.evaluate() method for types such as: ALL, PARENT,
 * SELF and ANYONE.
 */
public enum EnumUserDNType {
        /**
         * The enumeration type when the "userdn" URL contains only a DN (no
         * filter or scope) and that DN has no pattern.
         */
        DN(0),
        /**
         * The enumeration type when the "userdn" URL contains only a DN (no
         * filter or scope) and that DN has a substring pattern.
         */
        DNPATTERN(1),
        /**
         * The enumeration type when the "userdn" URL has the value of:
         *  "ldap:///all".
         */
        ALL(2),
        /**
         * The enumeration type when the "userdn" URL has the value of:
         *  "ldap:///parent".
         */
        PARENT(3),
        /**
         * The enumeration type when the "userdn" URL has the value of:
         *  "ldap:///self".
         */
        SELF(4),
        /**
         * The enumeration type when the "userdn" URL has the value of:
         *  "ldap:///anyone".
         */
        ANYONE(5),
        /**
         * The enumeration type when the "userdn" URL is contains a DN (suffix),
         * a scope and a filter.
         */
        URL(6);
        /**
         * Constructor taking an integer value.
         * @param v Integer value.
         */
        EnumUserDNType(int v) {}
}
opends/src/server/org/opends/server/authorization/dseecompat/GroupDN.java
New file
@@ -0,0 +1,151 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying * information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.authorization.dseecompat;
import static org.opends.server.authorization.dseecompat.AciMessages.*;
import static org.opends.server.messages.MessageHandler.getMessage;
import org.opends.server.types.*;
import org.opends.server.api.Group;
import org.opends.server.core.GroupManager;
import org.opends.server.core.DirectoryServer;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.LinkedHashSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
 * This class implements the groupdn bind rule keyword.
 */
public class GroupDN implements KeywordBindRule {
    LinkedList<DN> groupDNs=null;
    private EnumBindRuleType type=null;
    private static GroupManager groupManager =
            DirectoryServer.getGroupManager();
    /**
     * Create a class representing a groupdn bind rule keyword.
     * @param type An enumeration representing the bind rule type.
     * @param groupDNs A list of the dns representing groups.
     */
    private GroupDN(EnumBindRuleType type, LinkedList<DN> groupDNs ) {
        this.groupDNs=groupDNs;
        this.type=type;
    }
    /**
     * Decode an string expression representing a groupdn bind rule.
     * @param expr  A string representation of the bind rule.
     * @param type An enumeration of the type of the bind rule.
     * @return  A keyword bind rule class that can be used to evaluate
     * this bind rule.
     * @throws AciException   If the expression string is invalid.
     */
    public static KeywordBindRule decode(String expr, EnumBindRuleType type)
    throws AciException  {
        String ldapURLRegex = "\\s*(ldap:///[^\\|]+)";
        String ldapURLSRegex =
            ldapURLRegex + "\\s*(\\|\\|\\s*" + ldapURLRegex + ")*";
        if (!Pattern.matches(ldapURLSRegex, expr)) {
            int msgID = MSGID_ACI_SYNTAX_INVALID_GROUPDN_EXPRESSION;
            String message = getMessage(msgID, expr);
            throw new AciException(msgID, message);
        }
        LinkedList<DN>groupDNs=new LinkedList<DN>();
        int ldapURLPos = 1;
        Pattern ldapURLPattern = Pattern.compile(ldapURLRegex);
        Matcher ldapURLMatcher = ldapURLPattern.matcher(expr);
        while (ldapURLMatcher.find()) {
            try {
               String value = ldapURLMatcher.group(ldapURLPos).trim();
               DN dn=LDAPURL.decode(value, true).getBaseDN();
               groupDNs.add(dn);
            } catch (DirectoryException ex) {
                int msgID = MSGID_ACI_SYNTAX_INVALID_GROUPDN_URL;
                String message = getMessage(msgID, ex.getErrorMessage());
                throw new AciException(msgID, message);
            }
        }
        return new GroupDN(type, groupDNs);
    }
    /**
     * Performs the evaluation of a groupdn bind rule based on the
     * evaluation context passed to it. The evaluation stops when there
     * are no more group DNs to evaluate, or if a group DN evaluates to true
     * if it contains the client DN.
     * @param evalCtx  An evaluation context to use  in the evaluation.
     * @return  Enumeration evaluation result.
     */
    public EnumEvalResult evaluate(AciEvalContext evalCtx) {
        EnumEvalResult matched = EnumEvalResult.FALSE;
       Iterator<DN> it=groupDNs.iterator();
        for(; it.hasNext() && matched != EnumEvalResult.TRUE;) {
            DN  groupDN=it.next();
            Group group = groupManager.getGroupInstance(groupDN);
            if((group != null) && (evalCtx.isMemberOf(group)))
               matched = EnumEvalResult.TRUE;
        }
        return matched.getRet(type, false);
    }
    /**
     * Performs an evaluation of a group that was specified in an attribute
     * type value of the specified entry and attribute type. Each
     * value of the attribute type is assumed to be a group DN and evaluation
     * stops when there are no more values or if the group DN evaluates to
     * true if it contains the client DN.
     * @param e The entry to use in the evaluation.
     * @param evalCtx  The evaluation context to use in the evaluation.
     * @param attributeType The attribute type of the entry to use to get the
     * values for the groupd DNs.
     * @return Enumeration evaluation result.
     */
    public static EnumEvalResult evaluate (Entry e, AciEvalContext evalCtx,
                                           AttributeType attributeType) {
        EnumEvalResult matched= EnumEvalResult.FALSE;
        List<Attribute> attrs = e.getAttribute(attributeType);
        LinkedHashSet<AttributeValue> vals = attrs.get(0).getValues();
        for(AttributeValue v : vals) {
            try {
                DN groupDN=DN.decode(v.getStringValue());
                Group group = groupManager.getGroupInstance(groupDN);
                if((group != null) && (evalCtx.isMemberOf(group))) {
                    matched=EnumEvalResult.TRUE;
                    break;
                }
            } catch (DirectoryException ex) {
                break;
            }
        }
        return matched;
    }
}
opends/src/server/org/opends/server/authorization/dseecompat/IpBitsNetworkCriteria.java
New file
@@ -0,0 +1,200 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying * information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.authorization.dseecompat;
import static org.opends.server.authorization.dseecompat.AciMessages.*;
import static org.opends.server.messages.MessageHandler.getMessage;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
/**
 * This class builds a network string to an internal representation.
 */
public class IpBitsNetworkCriteria {
  byte[] _address;    // address in byte format
  int _bits;      // number of bits for matching
  byte[] _bitsArray;    // bits in network order
  InetAddress _inetAddress;
  /**
   * Creates a new IpBitsNeworkCriteria instance.
   *
   * @param  theInputAddress     IP address associated the rule. For IPV4
   *                             addresses, the following
   *                             textual formats are supported
   *                             a.b.c.d
   *                             a.b.c
   *                             a.b
   *                             a
   *                             For IPv6 addresses, the following textual
   *                             format are supported:
   *                             x:x:x:x:x:x:x:x, where x are the hexadecimal
   *                             values of the 8
   *                             16-bits pieces of the address
   *                             Use of :: to compress the leading
   *                             and/or trailing zeros e.g.
   *                             x::x:x:x:x:x:x
   *
   * @param  theBits             Number of bits of the network address
   *                             necessary for matching.
   *                             Max is 32 for IPv4 addresses and 128
   *                              for IPv6 addresses \
   *
   * @throws UnknownHostException Thrown if the inetaddress cannot be gotten
   * from the input address string.
   * @throws AciException Thrown if the bit count is not in the correct
   * ranges.
   */
  public IpBitsNetworkCriteria(String theInputAddress, int theBits)
  throws UnknownHostException, AciException
  {
    boolean ipv4 = true;
    _inetAddress = InetAddress.getByName(theInputAddress);
    if (_inetAddress instanceof Inet6Address)
    {
      if (theBits < 0 || theBits > 128) {
          int msgID = MSGID_ACI_SYNTAX_INVALID_NETWORK_BIT_MATCH;
          String message = getMessage(msgID, "IPV6",
                  "Bits must be in [0..128] range.");
          throw new AciException(msgID, message);
      }
      ipv4=false;
    }
    else
    {
      // Assume IPv4
      if (theBits < 0 || theBits > 32) {
          int msgID = MSGID_ACI_SYNTAX_INVALID_NETWORK_BIT_MATCH;
          String message = getMessage(msgID, "IPV4",
                  "Bits must be in [0..32] range.");
          throw new AciException(msgID, message);
      }
    }
    _bits = theBits;
    // Convert the bits into a mask in network byte order
    if (ipv4)
    {
      _bitsArray = new byte[4];
      // in java int is exactly 4 bytes
      int rawBits;
      if (theBits==0)
        rawBits=0;
      else
        rawBits=~0;
      rawBits = rawBits << (32 - theBits);
      // Use network order for the comparison
      _bitsArray[0] = (byte) ((rawBits >> 24) & 0xFF );
      _bitsArray[1] = (byte) ((rawBits >> 16) & 0xFF );
      _bitsArray[2] = (byte) ((rawBits >> 8) & 0xFF );
      _bitsArray[3] = (byte) ((rawBits) & 0xFF );
    }
    else
    {
      _bitsArray = new byte[16];
      int index=0;
      if (theBits > 64)
      {
        _bitsArray[0] = (byte) 0xFF;
        _bitsArray[1] = (byte) 0xFF;
        _bitsArray[2] = (byte) 0xFF;
        _bitsArray[3] = (byte) 0xFF;
        _bitsArray[4] = (byte) 0xFF;
        _bitsArray[5] = (byte) 0xFF;
        _bitsArray[6] = (byte) 0xFF;
        _bitsArray[7] = (byte) 0xFF;
        theBits-=64;
        index=8;
      }
      long rawBits = ~0;
      rawBits = rawBits << (64 - theBits);
      if (_bits !=0)
      {
        _bitsArray[index++] = (byte) ((rawBits >> 56) & 0xFF );
        _bitsArray[index++] = (byte) ((rawBits >> 48) & 0xFF );
        _bitsArray[index++] = (byte) ((rawBits >> 40) & 0xFF );
        _bitsArray[index++] = (byte) ((rawBits >> 32) & 0xFF );
        _bitsArray[index++] = (byte) ((rawBits >> 24) & 0xFF );
        _bitsArray[index++] = (byte) ((rawBits >> 16) & 0xFF );
        _bitsArray[index++] = (byte) ((rawBits >> 8) & 0xFF );
        _bitsArray[index] = (byte) ((rawBits ) & 0xFF );
      }
    }
    _address = _inetAddress.getAddress();
  }
  /**
   * Compare an IP address with the network rule.
   *
   * @param  theSourceAddress   IP source address of the client contacting
   *                            the proxy server.
   * @return  <CODE>true</CODE> if client matches the network rule or
   *          <CODE>false</CODE> if they may not.
   */
  public boolean match (InetAddress theSourceAddress)
  {
    byte[] addr = theSourceAddress.getAddress();
    if ((addr.length * 8) < _bits) {
      // Client IP  too small. Won't match.
      return false;
    }
    for (int i=0; i<addr.length; i++)
    {
      if ((addr[i] & _bitsArray[i]) != (_address[i] & _bitsArray[i])) {
        return false;
      }
    }
    return true;
  }
  /**
   * String representation of this criteria.
   *
   * @return  a String representation of the IpMaskNetworkCriteria
   */
  public String toString()
  {
    return "Address:" + _inetAddress.getHostAddress() +
        "/" + Integer.toString(_bits);
  }
}
opends/src/server/org/opends/server/authorization/dseecompat/IpCriteria.java
New file
@@ -0,0 +1,291 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying * information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.authorization.dseecompat;
import static org.opends.server.authorization.dseecompat.AciMessages.*;
import static org.opends.server.messages.MessageHandler.getMessage;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.HashSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
 * This class represents a ip bind rule keyword.
 */
public class IpCriteria implements KeywordBindRule
{
    private EnumBindRuleType type=null;
    // private token to express that any address is accepted
    private final static String ANY_ADDRESSES = "ALL";
    private boolean matchAnyAddress = false;
    private IpBitsNetworkCriteria[] ipBitsCriteria = null;
    private IpMaskNetworkCriteria[] ipMaskCriteria = null;
    /*
     * TODO Verifiy IpCriteria constructor adheres to DS 5.2 ip keyword
     * syntax.
     *
     * Based on the contents of the constructor, it doesn't appear that the
     * IpCriteria class uses the same set of allowed values as DS 5.2 does
     * with the "ip" keyword (as documented at
     * http://docs.sun.com/source/817-7613/aci.html#wp20242).  Was that the
     * intention?  It looks like it doesn't allow asterisks as wild-ccards or
     * plus signs to specify netmasks (although it appears that it expects a
     * slash to be  used if you want a netmask).  While I don't mind allowing
     * alternate formats (e.g., CIDR-style addresses), we can't drop support
     * for the existing ones if we're trying to maintain compatibility.
     */
    /**
     * Constructor that creates an IpCriteria from an array of values and
     * an enumeration bind rule type.
     * @param values An array of address values.
     * @param type An enumeration of the bind rule type.
     * @throws UnknownHostException If the host address cannot be resolved to
     * a hostname.
     * @throws AciException  If a part of the address is invalid.
     * @throws IndexOutOfBoundsException  If an index is incremented past an
     * array bounds when copying or evaluating an address.
     */
    public IpCriteria(String[] values, EnumBindRuleType type)
    throws UnknownHostException, IndexOutOfBoundsException, AciException {
        IpBitsNetworkCriteria[] ipBitsCriteria_2 = null;
        IpMaskNetworkCriteria[] ipMaskCriteria_2 = null;
        try
        {
            for (String value : values)
            {
                if (value.equalsIgnoreCase(ANY_ADDRESSES))
                {
                    matchAnyAddress = true;
                    continue;
                }
                // determine what format it is to instantiate
                // the right criteria object
                int slash = value.indexOf("/");
                if (slash == -1)
                {
                    // simple raw IP address
                    IpBitsNetworkCriteria newInstance;
                    if (InetAddress.getByName(value)
                            instanceof Inet6Address)
                    {
                        newInstance = new IpBitsNetworkCriteria(value, 128);
                    } else
                    {
                        newInstance = new IpBitsNetworkCriteria(value, 32);
                    }
                    if (ipBitsCriteria_2 == null)
                    {
                        ipBitsCriteria_2 = new IpBitsNetworkCriteria[1];
                    } else
                    {
                        IpBitsNetworkCriteria[] newIpBitsCriteria =
                         new IpBitsNetworkCriteria[ipBitsCriteria_2.length + 1];
                        System.arraycopy(ipBitsCriteria_2, 0,
                                newIpBitsCriteria, 0, ipBitsCriteria_2.length);
                        ipBitsCriteria_2 = newIpBitsCriteria;
                    }
                    ipBitsCriteria_2[ipBitsCriteria_2.length - 1] = newInstance;
                } else
                {
                    // Extract data following the / and figure out whether it
                    // is a bit number or a mask
                    try
                    {
                        int bits =
                                Integer.parseInt(value.substring(slash + 1));
                        // Well, no exception, so this is a bit
                        // Let's instantiate the corresponding criterion
                        if (ipBitsCriteria_2 == null)
                        {
                            ipBitsCriteria_2 = new IpBitsNetworkCriteria[1];
                        } else
                        {
                         IpBitsNetworkCriteria[] newIpBitsCriteria =
                         new IpBitsNetworkCriteria[ipBitsCriteria_2.length + 1];
                            System.arraycopy(ipBitsCriteria_2, 0,
                                 newIpBitsCriteria, 0, ipBitsCriteria_2.length);
                            ipBitsCriteria_2 = newIpBitsCriteria;
                        }
                        ipBitsCriteria_2[ipBitsCriteria_2.length - 1] =
                                new IpBitsNetworkCriteria(value.
                                        substring(0, slash), bits);
                    }
                    catch (IndexOutOfBoundsException e1)
                    {
                        throw e1;
                    }
                    catch (Exception e2)
                    {
                        // Looks like this is a network mask.
                        if (ipMaskCriteria_2 == null)
                        {
                            ipMaskCriteria_2 = new IpMaskNetworkCriteria[1];
                        } else
                        {
                        IpMaskNetworkCriteria[] newIpMaskCriteria =
                         new IpMaskNetworkCriteria[ipMaskCriteria_2.length + 1];
                            System.arraycopy(ipMaskCriteria_2, 0,
                                 newIpMaskCriteria, 0, ipMaskCriteria_2.length);
                            ipMaskCriteria_2 = newIpMaskCriteria;
                        }
                        try
                        {
                            ipMaskCriteria_2[ipMaskCriteria_2.length - 1] =
                              new IpMaskNetworkCriteria(value.
                              substring(0, slash), value.substring(slash + 1));
                        }
                        catch (IndexOutOfBoundsException e3)
                        {
                            throw e3;
                        }
                    }
                }
            }
        }
        catch (UnknownHostException ue)
        {
            throw ue;
        }
        ipBitsCriteria = ipBitsCriteria_2;
        ipMaskCriteria = ipMaskCriteria_2;
        this.type=type;
    }
    /**
     * Return the ipBitsNetworkCriteria of this  IpCriteria.
     * @return Returns the ipBitsNetworkCriteria.
     */
    public IpBitsNetworkCriteria[] getIpBitsNetworkCriteria() {
        return ipBitsCriteria;
    }
    /**
     * Return the ipMaskNetworkCriteria of this IpCriteria.
     * @return Returns the ipMaskNetworkCriteria.
     */
    public IpMaskNetworkCriteria[] getIpMaskNetworkCriteria() {
        return ipMaskCriteria;
    }
    /**
     * Compare an IP address with the network rule.
     *
     * @param  theSourceAddress   IP source address of the client.
     * @return  <CODE>true</CODE> if client matches the network rule or
     *          <CODE>false</CODE> if they may not.
     */
    public boolean match (InetAddress theSourceAddress)
    {
        if (matchAnyAddress){
            return true;
        }
        if (ipMaskCriteria != null)
        {
            for (IpMaskNetworkCriteria anIpMaskCriteria : ipMaskCriteria)
            {
                if (anIpMaskCriteria.match(theSourceAddress))
                {
                    return true;
                }
            }
        }
        if (ipBitsCriteria != null)
        {
            for (IpBitsNetworkCriteria anIpBitsCriteria : ipBitsCriteria)
            {
                if (anIpBitsCriteria.match(theSourceAddress))
                {
                    return true;
                }
            }
        }
        return (ipBitsCriteria == null) && (ipMaskCriteria == null);
    }
    /**
     * Decode an expression string representing a ip keyword bind rule
     * expression.
     * @param expr A string representing the expression.
     * @param type An enumeration representing the bind rule type.
     * @return  An keyword bind rule that can be used to evaluate the
     * expression.
     * @throws AciException  If the expression string is invalid.
     */
    public static KeywordBindRule decode(String expr, EnumBindRuleType type)
    throws AciException  {
        String valueRegex = "([^,\\s]+)";
        String valuesRegex = valueRegex + "\\s*(,\\s*" + valueRegex + ")*";
        if (!Pattern.matches(valuesRegex, expr)) {
            int msgID = MSGID_ACI_SYNTAX_INVALID_IP_EXPRESSION;
            String message = getMessage(msgID, expr);
            throw new AciException(msgID, message);
        }
        int valuePos = 1;
        Pattern valuePattern = Pattern.compile(valueRegex);
        Matcher valueMatcher = valuePattern.matcher(expr);
        HashSet<String> values = new HashSet<String>();
        while (valueMatcher.find()) {
            String value = valueMatcher.group(valuePos);
            values.add(value);
        }
        IpCriteria ipCriteria;
        String[] strValues = null;
        if (!values.isEmpty()) {
            strValues = values.toArray(new String[values.size()]);
        }
        try {
            ipCriteria = new IpCriteria(strValues, type);
        } catch (Exception e) {
            int msgID = MSGID_ACI_SYNTAX_INVALID_IP_CRITERIA_DECODE;
            String message = getMessage(msgID, e.getMessage());
            throw new AciException(msgID, message);
        }
        return ipCriteria;
    }
    /**
     * Evaluate the evaluation context against this ip criteria.
     * @param evalCtx An evaluation context to use.
     * @return An enumeration evaluation result.
     */
    public EnumEvalResult evaluate(AciEvalContext evalCtx) {
        EnumEvalResult matched=EnumEvalResult.FALSE;
        if(match(evalCtx.getRemoteAddress()))
              matched=EnumEvalResult.TRUE;
        return matched.getRet(type, false);
    }
}
opends/src/server/org/opends/server/authorization/dseecompat/IpMaskNetworkCriteria.java
New file
@@ -0,0 +1,140 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying * information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.authorization.dseecompat;
import static org.opends.server.authorization.dseecompat.AciMessages.*;
import static org.opends.server.messages.MessageHandler.getMessage;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
/**
 *   This class creates a network mask criteria from the address and mask
 *   string passed to it.
 */
class IpMaskNetworkCriteria
{
  byte[] _address;    // address in byte format
  byte[] _mask;     // mask in byte format
  InetAddress _inetAddress;
  InetAddress _inetMask;
  boolean _ipv4;      // true if ipv4 address
  /**
   * Creates a new IpMaskNeworkCriteria instance.
   *
   * @param  theInputAddress     IP address associated the rule. For IPV4
   *                             addresses, the following
   *                             textual formats are supported
   *                             a.b.c.d
   *                             a.b.c
   *                             a.b
   *                             a
   *                             For IPv6 addresses, the following textual
   *                             format are supported:
   *                             x:x:x:x:x:x:x:x, where x are the hexadecimal
   *                             values of the 8 16-bits pieces of the address
   *                             Use of :: to compress the leading  and/or
   *                             trailing zeros e.g.x::x:x:x:x:x:x
   *
   * @param  theInputMask        Bits of the network address necessary
   *                             for matching.
   *                             Same format as the IP address above.
   *
   * @throws UnknownHostException Thrown if the hostname of the input address
   * cannot be resolved.
   * @throws AciException If the address family has a mismatch.
   */
  public IpMaskNetworkCriteria(String theInputAddress, String theInputMask)
  throws UnknownHostException, AciException {
    _inetAddress = InetAddress.getByName(theInputAddress);
    _inetMask = InetAddress.getByName(theInputMask);
    _address = _inetAddress.getAddress();
    _mask = _inetMask.getAddress();
    if (_inetAddress instanceof Inet4Address)
      _ipv4=true;
    if (_ipv4 && !(_inetMask instanceof Inet4Address) ||
       (!_ipv4 && !(_inetMask instanceof Inet6Address))) {
        int msgID = MSGID_ACI_SYNTAX_ADDRESS_FAMILY_MISMATCH;
        String message = getMessage(msgID, theInputMask, theInputAddress);
        throw new AciException(msgID, message);
    }
  }
  /**
   * Compare an IP address with the network criteria.
   *
   * @param  theSourceAddress   IP source address of the client.
   * @return  <CODE>true</CODE> if client matches the network rule or
   *          <CODE>false</CODE> if they may not.
   */
  public boolean match (InetAddress theSourceAddress)
  {
    // First address family must match
    if (_ipv4)
    {
      if (!(theSourceAddress instanceof Inet4Address))
        return false;
    }
    else
    {
      if (!(theSourceAddress instanceof Inet6Address))
        return false;
    }
    byte[] addr = theSourceAddress.getAddress();
    for (int i=0; i<addr.length; i++) {
      if ((addr[i] & _mask[i]) != (_address[i] & _mask[i])) {
        return false;
      }
    }
    return true;
  }
  /**
   * String representation of this rule.
   *
   * @return  a String representation of the IpMaskNetworkRule.
   */
  public String toString()
  {
    return "Address:" + _inetAddress.getHostAddress() +
        " Mask:" + _inetMask.getHostAddress();
  }
}
opends/src/server/org/opends/server/authorization/dseecompat/KeywordBindRule.java
New file
@@ -0,0 +1,44 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying * information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.authorization.dseecompat;
/**
 * This interface represents a keyword  bind rule class
 * that can evaluate an evaluation context. It defines a single
 * function that each of the keyword functions implement (ip, dns,
 * roledn, groupdn, ...)
 */
public interface KeywordBindRule
{
    /**
     * Evaluate a bind rule using the passed in context.
     * @param evalCtx An evaluation context to use in the evaluation.
     * @return An enumeration evaluation result.
     */
    public EnumEvalResult evaluate(AciEvalContext evalCtx);
}
opends/src/server/org/opends/server/authorization/dseecompat/ParentInheritance.java
New file
@@ -0,0 +1,172 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying * information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.authorization.dseecompat;
import static org.opends.server.authorization.dseecompat.AciMessages.*;
import static org.opends.server.messages.MessageHandler.getMessage;
import java.util.StringTokenizer;
import org.opends.server.core.DirectoryServer;
import org.opends.server.types.AttributeType;
/**
 * This class is used by USERDN and GROUPDN userattr types
 * to determine what parent inheritance checks to make.
 */
public class ParentInheritance {
    /*
     * The maximum number of parent inheritance levels supported.
     *
     */
    private static final int MAX_LEVELS=10;
    private String parentPat="parent[";
    private int[] levels=new int[MAX_LEVELS];
    private int numLevels;
    private AttributeType attributeType;
    /**
     * Construct a class from the inheritance pattern. The skipParsing boolean
     * specifies that parent parsing should be skipped and sets up the class:
     * with numLevels=1, level[0]=0 and an attribute type from the
     * specified pattern.
     *
     * @param pattern The string pattern containing the inheritance
     * information.
     * @param skipParse Specify if the parent inheritance parsing should be
     * skipped or not.
     * @throws AciException  If the pattern is invalid.
     */
    ParentInheritance (String pattern, boolean skipParse)  throws AciException {
        if (skipParse) {
            //The "parent[" pattern is invalid for ROLEDN user attr keyword.
            if(pattern.startsWith(parentPat)) {
                int msgID =
                  MSGID_ACI_SYNTAX_INVALID_USERATTR_ROLEDN_INHERITANCE_PATTERN;
                String message = getMessage(msgID, pattern);
                throw new AciException(msgID, message);
            }  else {
                pattern=pattern.trim();
                if((this.attributeType =
                        DirectoryServer.getAttributeType(pattern)) == null)
                    this.attributeType =
                            DirectoryServer.getDefaultAttributeType(pattern);
                numLevels=1;
                levels[0]=0;
            }
        } else
            parse(pattern);
    }
    /**
     * Performs all parsing of the specified pattern string.
     * @param pattern The string pattern containing the inheritance
     * information.
     * @throws AciException  If the pattern is invalid.
     */
    private void parse (String pattern) throws AciException {
        pattern=pattern.trim();
        /**
         * Check if we have a "parent[" string.
         */
        if(pattern.startsWith(parentPat)) {
            numLevels=0;
            levels[0]=0;
            String p=pattern.substring(parentPat.length());
            /**
             * Format needs to be parent[XX].attribute -- everything after the
             * '.' is the attribute type.
             */
            String[] toks=p.split("\\.");
            if(toks.length != 2) {
                int msgID =
                    MSGID_ACI_SYNTAX_INVALID_USERATTR_INHERITANCE_PATTERN;
                String message = getMessage(msgID, pattern);
                throw new AciException(msgID, message);
            }
            if((this.attributeType =
                DirectoryServer.getAttributeType(toks[1])) == null)
                this.attributeType =
                    DirectoryServer.getDefaultAttributeType(toks[1]);
            StringTokenizer tok=new StringTokenizer(toks[0],"],",false);
            while(tok.hasMoreTokens()) {
                String v=tok.nextToken();
                /**
                 * Everything between the brackets must be an integer or it's
                 * an error.
                 */
                try {
                    if(numLevels < MAX_LEVELS) {
                        levels[numLevels++]=Integer.decode(v);
                    } else {
                        int msgID =
                      MSGID_ACI_SYNTAX_MAX_USERATTR_INHERITANCE_LEVEL_EXCEEDED;
                        String message = getMessage(msgID, pattern,
                                               Integer.toString(MAX_LEVELS));
                        throw new AciException(msgID, message);
                    }
                } catch (NumberFormatException ex) {
                    int msgID = MSGID_ACI_SYNTAX_INVALID_INHERITANCE_VALUE;
                    String message = getMessage(msgID, pattern);
                    throw new AciException(msgID, message);
                }
            }
        } else {
            if((this.attributeType =
                DirectoryServer.getAttributeType(pattern)) == null)
                this.attributeType =
                    DirectoryServer.getDefaultAttributeType(pattern);
            numLevels=1;
            levels[0]=0;
        }
    }
    /**
     * Returns the number of levels counted.
     * @return The number of levels.
     */
    public int getNumLevels() {
        return numLevels;
    }
    /**
     * Returns an array of levels, where levels are integers.
     * @return Return an array of levels.
     */
    public int[] getLevels() {
        return levels;
    }
    /**
     * Return the attribute type.
     * @return The attribute type.
     */
    public AttributeType getAttributeType() {
        return attributeType;
    }
}
opends/src/server/org/opends/server/authorization/dseecompat/PermBindRulePair.java
New file
@@ -0,0 +1,93 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying * information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.authorization.dseecompat;
/**
 * A class representing a permission-bind rule pair. There can be multiple
 * of these in an ACI.
 */
public class PermBindRulePair {
    private BindRule bindRule;
    private Permission perm=null;
    /**
     * This constructor calls the permission and bind rule decodes
     * with the appropriate strings.
     * @param perm  A string representing the permissions.
     * @param rights  A string representing the rights.
     * @param bindRule A string representing the bind rule.
     * @throws AciException  If any of the strings fail to decode.
     */
    private  PermBindRulePair(String perm, String rights, String bindRule)
    throws AciException {
     this.perm=Permission.decode(perm, rights);
     this.bindRule=BindRule.decode(bindRule);
    }
    /**
     * Decodes a permission bind rule pair.
     * @param perm  A string representing the permissions.
     * @param rights  A string representing the rights.
     * @param bRule A string representing the bind rule.
     * @return An permission bind rule pair class representing this pair.
     * @throws AciException  If any of the strings fail to decode.
     */
    public static PermBindRulePair decode(String perm, String rights,
                                          String bRule) throws AciException {
       return new PermBindRulePair(perm, rights, bRule);
    }
    /**
     * Gets the bind rule part of this pair.
     * @return  The bind rule part of this pair.
     */
    public BindRule getBindRule () {
        return bindRule;
    }
    /**
     * Checks the permission to see if it has this access type.
     * @param accessType An enumeration of the desired access type.
     * @return True if the access type equals the permission access type.
     */
    public boolean hasAccessType(EnumAccessType accessType) {
        return perm.hasAccessType(accessType);
    }
    /**
     * Try and match one or more of the specified rights against a rights set
     * of the permission class.
     * @param right  The rights to match.
     * @return True if one or more of the specified rights match a right in
     * the rights set of the permission class.
     */
    public boolean hasRights(int right) {
        return perm.hasRights(right);
    }
}
opends/src/server/org/opends/server/authorization/dseecompat/Permission.java
New file
@@ -0,0 +1,117 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying * information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.authorization.dseecompat;
import static org.opends.server.messages.MessageHandler.getMessage;
import static org.opends.server.authorization.dseecompat.AciMessages.*;
import java.util.regex.Pattern;
/**
 * A class representing the permissions of an bind rule. The permissions
 * of an ACI look like deny(search, write).
 */
public class Permission {
    //the access type (allow,deny)
    private EnumAccessType accessType = null;
    private int rights;
    private static final String separatorToken = ",";
    private static final String rightsRegex =
        "\\s*(\\w+)\\s*(,\\s*(\\w+)\\s*)*";
    /**
     * Constructor creating a class representing a permission part of an bind
     * rule.
     * @param accessType A string representing access type.
     * @param rights  A string representing the rights.
     * @throws AciException If the access type string or rights string
     * is invalid.
     */
    private Permission(String accessType, String rights)
    throws AciException {
        if ((this.accessType =
            EnumAccessType.decode(accessType)) == null){
            int msgID = MSGID_ACI_SYNTAX_INVALID_ACCESS_TYPE_VERSION;
            String message = getMessage(msgID, accessType);
            throw new AciException(msgID, message);
        }
        if (!Pattern.matches(rightsRegex, rights)){
            int msgID = MSGID_ACI_SYNTAX_INVALID_RIGHTS_SYNTAX;
            String message = getMessage(msgID, rights);
            throw new AciException(msgID, message);
        }
        else {
            Pattern separatorPattern = Pattern.compile(separatorToken);
            String[] rightsStr =
                separatorPattern.split(rights.replaceAll("\\s", ""));
            for (String r : rightsStr) {
                EnumRight right = EnumRight.decode(r);
                if (right != null)
                    this.rights|= EnumRight.getMask(right);
                else {
                    int msgID = MSGID_ACI_SYNTAX_INVALID_RIGHTS_KEYWORD;
                    String message = getMessage(msgID, rights);
                    throw new AciException(msgID, message);
                }
            }
        }
    }
    /**
     * Decode an string representation of bind rule permission into a Permission
     * class.
     * @param accessType  A string representing the access type.
     * @param rights   A string representing the rights.
     * @return  A Permission class representing the permissions of the bind
     * rule.
     * @throws AciException  If the accesstype or rights strings are invalid.
     */
    public static
    Permission decode (String accessType, String rights)
    throws AciException {
        return new Permission(accessType, rights);
    }
    /**
     * Checks if a given access type enumeration is equal to this classes
     * access type.
     * @param accessType An enumeration representing an access type.
     * @return True if the access types are equal.
     */
    public boolean hasAccessType(EnumAccessType accessType) {
        return this.accessType == accessType;
    }
    /**
     * Checks if the permission's rights has the specified rights.
     * @param  rights The rights to check for.
     * @return True if the permission's rights has the specified rights.
     */
    public boolean hasRights(int rights) {
        return (this.rights & rights) != 0;
    }
}
opends/src/server/org/opends/server/authorization/dseecompat/RoleDN.java
New file
@@ -0,0 +1,157 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying * information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.authorization.dseecompat;
import static org.opends.server.authorization.dseecompat.AciMessages.*;
import static org.opends.server.messages.MessageHandler.getMessage;
import org.opends.server.types.*;
import org.opends.server.api.Group;
import org.opends.server.core.GroupManager;
import org.opends.server.core.DirectoryServer;
import java.util.LinkedList;
import java.util.Iterator;
import java.util.List;
import java.util.LinkedHashSet;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
/**
 * A class representing a roledn bind rule keyword. This class is almost
 * an exact copy of groupDN, except for variable names and error messages.
 */
public class RoleDN  implements KeywordBindRule {
    LinkedList<DN> roleDNs=null;
    private EnumBindRuleType type=null;
    private static GroupManager groupManager =
            DirectoryServer.getGroupManager();
    /**
     * Constructor creating a class representing a roledn keyword of a bind
     * rule.
     * @param type An enumeration of the type of the bind rule.
     * @param roleDNs A list of the role dns parsed from the expression string.
     */
    private RoleDN(EnumBindRuleType type, LinkedList<DN> roleDNs ) {
        this.roleDNs=roleDNs;
        this.type=type;
    }
    /**
     * Decodes an expression string representing an roledn bind rule.
     * @param expr A string representation of the bind rule.
     * @param type An enumeration of the type of the bind rule.
     * @return A keyword bind rule class that can be used to evaluate
     * this bind rule.
     * @throws AciException If the expression is invalid.
     */
    public static KeywordBindRule decode(String expr, EnumBindRuleType type)
    throws AciException {
        String ldapURLRegex = "\\s*(ldap:///[^\\|]+)";
        String ldapURLSRegex =
            ldapURLRegex + "\\s*(\\|\\|\\s*" + ldapURLRegex + ")*";
        if (!Pattern.matches(ldapURLSRegex, expr)) {
            int msgID = MSGID_ACI_SYNTAX_INVALID_ROLEDN_EXPRESSION;
            String message = getMessage(msgID, expr);
            throw new AciException(msgID, message);
        }
        LinkedList<DN>roleDNs=new LinkedList<DN>();
        int ldapURLPos = 1;
        Pattern ldapURLPattern = Pattern.compile(ldapURLRegex);
        Matcher ldapURLMatcher = ldapURLPattern.matcher(expr);
        while (ldapURLMatcher.find()) {
            String val = ldapURLMatcher.group(ldapURLPos);
            val = val.trim();
            DN dn;
            try {
                dn=DN.decode(val);
            } catch (DirectoryException ex) {
                int msgID = MSGID_ACI_SYNTAX_INVALID_ROLEDN_URL;
                String message = getMessage(msgID, ex.getErrorMessage());
                throw new AciException(msgID, message);
            }
            roleDNs.add(dn);
        }
        return new RoleDN(type, roleDNs);
    }
    /**
     * Performs the evaluation of a roledn bind rule based on the
     * evaluation context passed to it. The method uses an exact copy
     * evaluation method as the groupDN.evaluate().  The evaluation stops when
     * there are no more group DNs to evaluate, or if a group DN evaluates to
     * true if it contains the authorization DN.
     * @param evalCtx  An evaluation context to use  in the evaluation.
     * @return  Enumeration evaluation result.
     */
    public EnumEvalResult evaluate(AciEvalContext evalCtx) {
        EnumEvalResult matched = EnumEvalResult.FALSE;
       Iterator<DN> it=roleDNs.iterator();
        for(; it.hasNext() && matched != EnumEvalResult.TRUE;) {
            DN groupDN=it.next();
            Group group = groupManager.getGroupInstance(groupDN);
            if(evalCtx.isMemberOf(group))
               matched = EnumEvalResult.TRUE;
        }
        return matched.getRet(type, false);
    }
       /**
     * Performs an evaluation of a group that was specified in an attribute
     * type value of the specified entry and attribute type. Each
     * value of the attribute type is assumed to be a group DN and evaluation
     * stops when there are no more values or if the group DN evaluates to
     * true if it contains the client DN.
     * @param e The entry to use in the evaluation.
     * @param evalCtx  The evaluation context to use in the evaluation.
     * @param attributeType The attribute type of the entry to use to get the
     * values for the groupd DNs.
     * @return Enumeration evaluation result.
     */
    public static EnumEvalResult evaluate (Entry e, AciEvalContext evalCtx,
                                           AttributeType attributeType) {
        EnumEvalResult matched= EnumEvalResult.FALSE;
        List<Attribute> attrs = e.getAttribute(attributeType);
        LinkedHashSet<AttributeValue> vals = attrs.get(0).getValues();
        for(AttributeValue v : vals) {
            try {
                DN groupDN=DN.decode(v.getStringValue());
                Group group = groupManager.getGroupInstance(groupDN);
                if((group != null) && (evalCtx.isMemberOf(group))) {
                    matched=EnumEvalResult.TRUE;
                    break;
                }
            } catch (DirectoryException ex) {
                break;
            }
        }
        return matched;
    }
}
opends/src/server/org/opends/server/authorization/dseecompat/TargAttrFilters.java
New file
@@ -0,0 +1,55 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying * information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.authorization.dseecompat;
/**
 * Placeholder. This class is partially complete. The TargAttrFilters class
 * will represent an targAttrFitlers rule.
 */
public class TargAttrFilters {
    /**
     * Represents an targAttrFilters rule.
     */
    public TargAttrFilters() {
    }
    /**
     * Decode an string representing a targattrfilters keyword.
     * @param operator The operator of the rule.
     * @param expression The string parsed from the ACI representing the
     * targattrfilters rule.
     * @return  An object representing an targattrfilters rule.
     * @throws  AciException if the expression string cannot be parsed.
     */
    public static TargAttrFilters decode(EnumTargetOperator operator,
                                  String expression) throws AciException {
        return new TargAttrFilters();
    }
}
opends/src/server/org/opends/server/authorization/dseecompat/Target.java
New file
@@ -0,0 +1,198 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying * information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.authorization.dseecompat;
import static org.opends.server.authorization.dseecompat.AciMessages.*;
import static org.opends.server.messages.MessageHandler.getMessage;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.regex.Pattern;
import org.opends.server.core.DirectoryServer;
import org.opends.server.types.Attribute;
import org.opends.server.types.AttributeType;
import org.opends.server.types.AttributeValue;
import org.opends.server.types.DN;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.Entry;
import org.opends.server.types.LDAPURL;
import org.opends.server.types.SearchFilter;
/**
 * A class representing an ACI target keyword.
 */
public class Target
{
    private EnumTargetOperator operator = EnumTargetOperator.EQUALITY;
    private LDAPURL targetURL = null;
    private DN urlDN=null;
    private boolean isPattern=false;
    private SearchFilter filter=null;
    private AttributeType targetType;
      /*
     * TODO Save aciDN parameter and use it in matchesPattern re-write.
     *
     * Should the aciDN argument provided to the constructor be stored so that
     * it can be used in the matchesPattern() method?  The DN should only be
     * considered a potential match if it is at or below the entry containing
     * the ACI.
     *
     * TODO Evaluate re-writing pattern (substring) determination code. The
     * current code is similar to current DS6 implementation.
     *
     * I'm confused by the part of the constructor that generates a search
     * filter. First, there is no substring matching rule defined for the
     * DN syntax in the official standard, so technically trying to perform
     * substring matching against DNs is illegal.  Although we do try to use
     * the caseIgnoreSubstringsMatch rule, it is extremely unreliable for DNs
     * because it's just not possible to do substring matching correctly in all
     * cases for them.  Also, the logic in place there will only generate a
     * filter if the DN contains a wildcard, and if it starts with a wildcard
     * (which is handled by the targetDN.startsWith("*") clause), then you'll
     * end up with something like "(target=**dc=example,dc=com)", which isn't
     *  legal.
     */
    /**
     * This constructor parses the target string.
     * @param operator  An enumeration of the operation of this target.
     * @param target A string representation of the target.
     * @param aciDN The dn of the ACI entry used for a descendant check.
     * @throws AciException If the target string is invalid.
     */
    private Target(EnumTargetOperator operator, String target, DN aciDN)
            throws AciException {
        this.operator = operator;
        try {
          String ldapURLRegex = "\\s*(ldap:///[^\\|]+)";
          if (!Pattern.matches(ldapURLRegex, target)) {
              int msgID = MSGID_ACI_SYNTAX_INVALID_TARGETKEYWORD_EXPRESSION;
              String message = getMessage(msgID, target);
              throw new AciException(msgID, message);
          }
          targetURL =  LDAPURL.decode(target, false);
          urlDN=targetURL.getBaseDN();
          String targetDN=urlDN.toNormalizedString();
          if((targetDN.startsWith("*")) ||
             (targetDN.indexOf("*") != -1)) {
              this.isPattern=true;
              String pattern="target=*"+targetDN;
              filter=SearchFilter.createFilterFromString(pattern);
              targetType = DirectoryServer.getAttributeType("target");
              if (targetType == null)
                  targetType =
                          DirectoryServer.getDefaultAttributeType("target");
          } else {
              if(!urlDN.isDescendantOf(aciDN)) {
                  int msgID = MSGID_ACI_SYNTAX_TARGET_DN_NOT_DESCENDENTOF;
                  String message = getMessage(msgID,
                                              urlDN.toNormalizedString(),
                                              aciDN.toNormalizedString());
                  throw new AciException(msgID, message);
              }
          }
        }
        catch (DirectoryException e){
            int msgID = MSGID_ACI_SYNTAX_INVALID_TARGETKEYWORD_EXPRESSION;
            String message = getMessage(msgID, target);
            throw new AciException(msgID, message);
        }
    }
    /**
     *  Decode an expression string representing a target keyword expression.
     * @param operator  An enumeration of the operation of this target.
     * @param expr A string representation of the target.
     * @param aciDN  The DN of the ACI entry used for a descendant check.
     * @return  A Target class representing this target.
     * @throws AciException  If the expression string is invalid.
     */
    public static Target decode(EnumTargetOperator operator,
                                String expr, DN aciDN)
            throws AciException {
        return new Target(operator, expr, aciDN);
    }
    /**
     * Returns the operator of this expression.
     * @return An enumeration of the operation value.
     */
    public EnumTargetOperator getOperator() {
        return operator;
    }
    /**
     * Returns the URL DN of the expression.
     * @return A DN of the URL.
     */
    public DN getDN() {
        return urlDN;
    }
    /**
     * Returns boolean if a pattern was seen during parsing.
     * @return  True if the DN is a wild-card.
     */
    public boolean isPattern() {
        return isPattern;
    }
    /*
     * TODO Evaluate re-writing this method.
     *
     * The matchesPattern() method really needs to be rewritten.  It's using a
     * very inefficient and very error-prone method to make the determination.
     * If you're really going to attempt pattern matching on a DN, then I'd
     * suggest trying a regular expression against the normalized DN rather
     * than a filter.
     */
    /**
     * This method tries to match a pattern against a DN. It builds an entry
     * with a target attribute containing the pattern and then matches against
     * it.
     * @param dn  The DN to try an match.
     * @return True if the pattern matches.
     */
    public boolean matchesPattern(DN dn) {
        boolean ret;
        String targetDN=dn.toNormalizedString();
        LinkedHashSet<AttributeValue> values =
            new LinkedHashSet<AttributeValue>();
        values.add(new AttributeValue(targetType, targetDN));
        Attribute attr = new Attribute(targetType, "target", values);
        Entry e = new Entry(DN.nullDN(), null, null, null);
        e.addAttribute(attr,new ArrayList<AttributeValue>());
        try {
            ret=filter.matchesEntry(e);
        } catch (DirectoryException ex) {
            //TODO information message?
            return false;
        }
        return  ret;
    }
}
opends/src/server/org/opends/server/authorization/dseecompat/TargetAttr.java
New file
@@ -0,0 +1,187 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying * information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.authorization.dseecompat;
import static org.opends.server.authorization.dseecompat.AciMessages.*;
import static org.opends.server.messages.MessageHandler.getMessage;
import java.util.HashSet;
import java.util.regex.Pattern;
import org.opends.server.core.DirectoryServer;
import org.opends.server.types.AttributeType;
/**
 * A class representing an ACI's targetattr keyword.
 */
public class TargetAttr {
    private EnumTargetOperator operator = EnumTargetOperator.EQUALITY;
    private boolean allAttributes = false ;
    /*
     * HashSet of the attribute types parsed by the constructor.
     */
    private HashSet<AttributeType> attributes = new HashSet<AttributeType>();
    //private String[] attributes = new String[0];
    private static final String allAttrsRegex  = "\\s*\\*\\s*";
    private static final String noAttrsRegex   = "\\s*";
    private static final String separatorToken = "\\|\\|";
    private static final String attrListRegex  =
        "\\s*(\\w+)\\s*(" + separatorToken + "\\s*(\\w+)\\s*)*";
    /**
     * Constructor creating a class representing a targetattr keyword of an ACI.
     * @param operator The operation enumeration of the targetattr
     * expression (=, !=).
     * @param attrString A string representing the attributes specified in
     * the targetattr expression (ie, dn || cn).
     * @throws AciException If the attrs string is invalid.
     */
    private TargetAttr(EnumTargetOperator operator, String attrString)
    throws AciException {
        this.operator = operator;
        if (attrString != null) {
            if (Pattern.matches(allAttrsRegex, attrString) ){
                allAttributes = true ;
            } else {
                if (Pattern.matches(noAttrsRegex, attrString)){
                    allAttributes = false;
                } else {
                    if (Pattern.matches(attrListRegex, attrString)) {
                        // Remove the spaces in the attr string and
                        // split the list.
                        Pattern separatorPattern =
                            Pattern.compile(separatorToken);
                        attrString=attrString.replaceAll("\\s", "");
                        String[] attributeArray=
                             separatorPattern.split(attrString);
                        //Add each element of array to attributes HashSet
                        //after converting it to AttributeType.
                        arrayToAttributeTypes(attributeArray);
                    } else {
                      int msgID =
                         MSGID_ACI_SYNTAX_INVALID_TARGETATTRKEYWORD_EXPRESSION;
                      String message = getMessage(msgID, operator);
                      throw new AciException(msgID, message);
                    }
                }
            }
        }
    }
    /**
     * Converts each element of an array of attribute type strings
     * to attribute types and adds them to the attributes HashSet.
     * @param attributeArray The array of attribute type strings.
     */
    private void arrayToAttributeTypes(String[] attributeArray) {
        for (int i=0, n=attributeArray.length; i < n; i++) {
            String attribute=attributeArray[i].toLowerCase();
            AttributeType attributeType;
            if((attributeType =
                DirectoryServer.getAttributeType(attribute)) == null)
                attributeType =
                    DirectoryServer.getDefaultAttributeType(attribute);
            attributes.add(attributeType);
        }
    }
    /**
     * Returns the operator enumeration of the targetattr expression.
     * @return The operator enumeration.
     */
    public EnumTargetOperator getOperator() {
        return operator;
    }
    /**
     * This flag is set if the parsing code saw:
     * targetattr="*" or targetattr != "*".
     * @return True if all attributes was seen.
     */
    public boolean isAllAttributes() {
        return allAttributes;
    }
    /**
     * Return array holding each attribute type to be evaluated
     * in the expression.
     * @return Array holding each attribute types.
     */
    public HashSet<AttributeType> getAttributes() {
        return attributes;
    }
    /**
     * Decodes an targetattr expression string into a targetattr class suitable
     * for evaluation.
     * @param operator The operator enumeration of the expression.
     * @param expr The expression string to be decoded.
     * @return A TargetAttr suitable to evaluate this ACI's targetattrs.
     * @throws AciException If the expression string is invalid.
     */
    public static TargetAttr decode(EnumTargetOperator operator, String expr)
            throws AciException  {
        return new TargetAttr(operator, expr);
    }
    /**
     * Perform two checks to see if a specified attribute type is applicable.
     * First, check the targetAttr's isAllAttributes() boolean. The
     * isAllAttributes boolean is set true when the string:
     *
     *       targetattrs="*"
     *
     * is  seen when an ACI is parsed. If the isAllAttributes boolean is
     * true, the second check is skipped and the TargetAttr's operator is
     * checked to see if the method should return false (NOT_EQUALITY)
     * instead of true.
     *
     * If the isAllAttributes boolean is false, then the TargeAttr's
     * attribute type HashSet is searched to see if it contains the
     * specified attribute type. That result could be negated depending
     * on if the TargetAttr's operator is NOT_EQUALITY.
     *
     * @param a The attribute type to evaluate.
     * @param targetAttr The ACI's TargetAttr class to evaluate against.
     * @return The boolean result of the above tests and application
     * TargetAttr's operator value applied to the test result.
     */
    public static boolean isApplicable(AttributeType a,
                          TargetAttr targetAttr) {
      boolean ret;
      if(targetAttr.isAllAttributes()) {
          ret =
             !targetAttr.getOperator().equals(EnumTargetOperator.NOT_EQUALITY);
      }  else {
          ret=false;
          HashSet<AttributeType> attributes=targetAttr.getAttributes();
          if(attributes.contains(a))
              ret=true;
          if(targetAttr.getOperator().equals(EnumTargetOperator.NOT_EQUALITY))
              ret = !ret;
      }
      return ret;
    }
}
opends/src/server/org/opends/server/authorization/dseecompat/TargetFilter.java
New file
@@ -0,0 +1,103 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying * information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.authorization.dseecompat;
import static org.opends.server.authorization.dseecompat.AciMessages.*;
import static org.opends.server.messages.MessageHandler.getMessage;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.Entry;
import org.opends.server.types.SearchFilter;
/**
 * This class represents a targetfilter keyword of an aci.
 *
 */
public class TargetFilter {
    private EnumTargetOperator op = EnumTargetOperator.EQUALITY;
    private SearchFilter filter;
    /**
     * Class representing a targetfilter keyword.
     * @param op The operation of the targetfilter expression (=, !=)
     * @param filter The filter itself.
     */
    private TargetFilter(EnumTargetOperator op, SearchFilter filter) {
        this.op=op;
        this.filter=filter;
    }
    /**
     * Decode a aci's targetfilter string.
     * @param op The operation enumeration of the expression.
     * @param expr A string representing the target filter.
     * @return A TargetFilter class suitable for using in a match.
     * @throws AciException If the expression string is invalid.
     */
    public static TargetFilter decode(EnumTargetOperator op, String expr)
    throws AciException {
        SearchFilter filter;
        try {
            filter = SearchFilter.createFilterFromString(expr);
        } catch (DirectoryException ex) {
            int msgID =
                MSGID_ACI_SYNTAX_INVALID_TARGETFILTERKEYWORD_EXPRESSION;
            String message = getMessage(msgID, expr);
            throw new AciException(msgID, message);
        }
        return new TargetFilter(op, filter);
    }
    /**
     * Checks if a targetfilter matches an evaluation context.
     * @param matchCtx The evaluation context to use in the matching.
     * @return True if the target filter matched the context.
     */
    public boolean isApplicable(AciTargetMatchContext matchCtx) {
        boolean ret;
        ret=matchesFilter(matchCtx.getResourceEntry());
        if(op.equals(EnumTargetOperator.NOT_EQUALITY))
            ret = !ret;
        return ret;
    }
    /**
     * Checks the filter against an entry taken from the match context.
     * @param e The entry from the evaluation context above.
     * @return True if the filter matches the entry.
     */
    private boolean matchesFilter(Entry e) {
        boolean ret;
        try {
            ret=filter.matchesEntry(e);
        } catch (DirectoryException ex) {
            //TODO information message?
            return false;
        }
        return ret;
    }
}
opends/src/server/org/opends/server/authorization/dseecompat/TimeOfDay.java
New file
@@ -0,0 +1,121 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying * information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.authorization.dseecompat;
import static org.opends.server.authorization.dseecompat.AciMessages.*;
import static org.opends.server.messages.MessageHandler.getMessage;
import org.opends.server.util.TimeThread;
import java.util.regex.Pattern;
/**
 * This class represents the timeofday keyword in a bind rule.
 */
public class TimeOfDay implements KeywordBindRule {
    private static final String timeofdayRegex = "[0-2]\\d[0-5]\\d";
    private EnumBindRuleType type=null;
    private int timeRef;
    /**
     * Constructor to create a timeofday keyword class.
     * @param timeVal The time value to check for (0-2359).
     * @param type An enumeration of the type of the expression.
     */
    private TimeOfDay(int timeVal, EnumBindRuleType type) {
        this.timeRef=timeVal;
        this.type=type;
    }
    /**
     * Decodes a string representation of a timeofday bind rule expression.
     * @param expr A string representation of the expression.
     * @param type An enumeration of the type of the expression.
     * @return  A TimeOfDay class representing the expression.
     * @throws AciException If the expression is invalid.
     */
    public static TimeOfDay decode(String expr,  EnumBindRuleType type)
    throws AciException  {
        if (!Pattern.matches(timeofdayRegex, expr))
        {
            int msgID = MSGID_ACI_SYNTAX_INVALID_TIMEOFDAY;
            String message = getMessage(msgID,expr);
            throw new AciException(msgID, message);
         }
        int valueAsInt = Integer.parseInt(expr);
        if ((valueAsInt < 0) || (valueAsInt > 2359))
        {
            int msgID = MSGID_ACI_SYNTAX_INVALID_TIMEOFDAY_RANGE;
            String message = getMessage(msgID,expr);
            throw new AciException(msgID, message);
        }
        return new TimeOfDay(valueAsInt, type);
    }
    /**
     * Evaluates the timeofday bind rule using the evaluation context
     * passed into the method.
     * @param evalCtx  The evaluation context to use for the evaluation.
     * @return  An enumeration result representing the result of the
     * evaluation.
     */
    public EnumEvalResult evaluate(AciEvalContext evalCtx) {
        EnumEvalResult matched=EnumEvalResult.FALSE;
        int currentTime=TimeThread.getHourAndMinute();
        //check the type
        switch (type) {
        case LESS_OR_EQUAL_BINDRULE_TYPE:
            if (currentTime <= timeRef)
            {
                matched=EnumEvalResult.TRUE;
            }
            break;
        case LESS_BINDRULE_TYPE:
            if (currentTime < timeRef)
            {
                matched=EnumEvalResult.TRUE;
            }
            break;
        case GREATER_OR_EQUAL_BINDRULE_TYPE:
            if (currentTime >= timeRef)
            {
                matched=EnumEvalResult.TRUE;
            }
            break;
        case GREATER_BINDRULE_TYPE:
            if (currentTime > timeRef)
            {
                matched=EnumEvalResult.TRUE;
            }
        }
        return matched.getRet(type, false);
    }
}
opends/src/server/org/opends/server/authorization/dseecompat/UserAttr.java
New file
@@ -0,0 +1,388 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying * information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.authorization.dseecompat;
import static org.opends.server.authorization.dseecompat.AciMessages.*;
import static org.opends.server.messages.MessageHandler.getMessage;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import org.opends.server.core.DirectoryServer;
import org.opends.server.protocols.internal.InternalClientConnection;
import org.opends.server.protocols.internal.InternalSearchOperation;
import org.opends.server.types.*;
/*
 * TODO Evaluate making this class more efficient.
 *
 * This class isn't as efficient as it could be.  For example, the evalVAL()
 * method should be able to use cached versions of the attribute type and
 * filter. The evalURL() and evalDN() methods should also be able to use a
 * cached version of the attribute type.
 */
/**
 * This class implements the  userattr bind rule keyword.
 */
public class UserAttr implements KeywordBindRule {
    /**
     * This enumeration is the various types the userattr can have after
     * the "#" token.
     */
    enum UserAttrType {
        USERDN, GROUPDN, ROLEDN, URL, VALUE
    }
    private  static String f="objectclass=*";
    private  String attrStr=null;
    private  String attrVal=null;
    private UserAttrType userAttrType=null;
    private EnumBindRuleType type=null;
    private ParentInheritance parentInheritance=null;
    /**
     * Create an non-USERDN/GROUPDN instance of the userattr keyword class.
     * @param attrStr The attribute name in string form. Kept in string form
     * until processing.
     * @param attrVal The attribute value in string form -- used in the USERDN
     * evaluation for the parent hierarchy expression.
     * @param userAttrType The userattr type of the rule
     * "USERDN, GROUPDN, ...".
     * @param type The bind rule type "=, !=".
     */
    private UserAttr(String attrStr, String attrVal, UserAttrType userAttrType,
            EnumBindRuleType type) {
        this.attrStr=attrStr;
        this.attrVal=attrVal;
        this.userAttrType=userAttrType;
        this.type=type;
    }
    /**
     * Create an USERDN or GROUPDN  instance of the userattr keyword class.
     * @param userAttrType The userattr type of the rule (USERDN or GROUPDN)
     * only.
     * @param type The bind rule type "=, !=".
     * @param parentInheritance The parent inheritance class to use for parent
     * inheritance checks if any.
     */
    private UserAttr(UserAttrType userAttrType, EnumBindRuleType type,
                     ParentInheritance parentInheritance) {
        this.userAttrType=userAttrType;
        this.type=type;
        this.parentInheritance=parentInheritance;
    }
    /**
     * Decode an string containing the userattr bind rule expression.
     * @param expression The expression string.
     * @param type The bind rule type.
     * @return A class suitable for evaluating a userattr bind rule.
     * @throws AciException If the string contains an invalid expression.
     */
    public static KeywordBindRule decode(String expression,
                                         EnumBindRuleType type)
    throws AciException {
        String[] vals=expression.split("#");
        if(vals.length != 2) {
            int msgID = MSGID_ACI_SYNTAX_INVALID_USERATTR_EXPRESSION;
            String message = getMessage(msgID, expression);
            throw new AciException(msgID, message);
        }
        UserAttrType userAttrType=getType(vals[1]);
        switch (userAttrType) {
                case GROUPDN:
                case USERDN: {
                    ParentInheritance parentInheritance =
                            new ParentInheritance(vals[0], false);
                    return new UserAttr (userAttrType, type, parentInheritance);
                }
                case ROLEDN: {
                    //Even though parent inheritance is invalid for the ROLEDN
                    //keyword, we are going to up a simple parent inheritance
                    //class so that most of the evaluate methods in this class
                    //can be re-used. The true boolean means to skip parsing,
                    //except for a quick validation parse.
                    ParentInheritance parentInheritance =
                            new ParentInheritance(vals[0], true);
                     return new UserAttr(userAttrType, type, parentInheritance);
                }
         }
         return new UserAttr(vals[0], vals[1], userAttrType, type);
    }
    /**
     * Evaluate the expression using an evaluation context.
     * @param evalCtx   The evaluation context to use in the evaluation of the
     * userattr expression.
     * @return  An enumeration containing the result of the evaluation.
     */
    public EnumEvalResult evaluate(AciEvalContext evalCtx) {
        EnumEvalResult matched;
        boolean undefined=false;
        switch(userAttrType) {
        case ROLEDN:
        case GROUPDN:
        case USERDN: {
            matched=evalDNKeywords(evalCtx);
            break;
        }
        case URL: {
            matched=evalURL(evalCtx);
            break;
        }
        default:
            matched=evalVAL(evalCtx);
        }
        if(matched == EnumEvalResult.ERR)
            undefined=true;
        return matched.getRet(type, undefined);
    }
    /** Evaluate a VALUE userattr type. Look in client entry for an
     *  attribute value and in the resource entry for the same
     *  value. If both entries have the same value than return true.
     * @param evalCtx The evaluation context to use.
     * @return An enumeration containing the result of the
     * evaluation.
     */
    private EnumEvalResult evalVAL(AciEvalContext evalCtx) {
        EnumEvalResult matched= EnumEvalResult.FALSE;
        boolean undefined=false;
        AttributeType attrType;
        if((attrType = DirectoryServer.getAttributeType(attrStr)) == null)
            attrType = DirectoryServer.getDefaultAttributeType(attrStr);
        try {
            InternalClientConnection conn =
                InternalClientConnection.getRootConnection();
            InternalSearchOperation op =
                    conn.processSearch(evalCtx.getClientDN(),
                    SearchScope.BASE_OBJECT,
                    DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
                    SearchFilter.createFilterFromString(f), null);
            LinkedList<SearchResultEntry> result = op.getSearchEntries();
            if (!result.isEmpty()) {
                AttributeValue val=new AttributeValue(attrType, attrVal);
                SearchResultEntry resultEntry = result.getFirst();
                if(resultEntry.hasValue(attrType, null, val)) {
                    Entry e=evalCtx.getResourceEntry();
                    if(e.hasValue(attrType, null, val))
                        matched=EnumEvalResult.TRUE;
                }
            }
        } catch (DirectoryException ex) {
            undefined = true;
            matched = EnumEvalResult.ERR;
        }
        return matched.getRet(type, undefined);
    }
    /**
     * Parses the substring after the '#' character to determine the userattr
     * type.
     * @param expr The string with the substring.
     * @return An enumeration containing the type.
     * @throws AciException If the substring contains an invalid type (roledn
     * or groupdn).
     */
    private static UserAttrType getType(String expr) throws AciException {
        UserAttrType userAttrType;
        if(expr.equalsIgnoreCase("userdn"))
            userAttrType=UserAttrType.USERDN;
        else if(expr.equalsIgnoreCase("groupdn")) {
             userAttrType=UserAttrType.GROUPDN;
      /*
            int msgID = MSGID_ACI_SYNTAX_INVALID_USERATTR_KEYWORD;
            String message = getMessage(msgID, "The groupdn userattr" +
                    "keyword is not supported.");
            throw new AciException(msgID, message);
        */
        } else if(expr.equalsIgnoreCase("roledn")) {
            userAttrType=UserAttrType.ROLEDN;
            /*
            int msgID = MSGID_ACI_SYNTAX_INVALID_USERATTR_KEYWORD;
            String message = getMessage(msgID, "The roledn userattr" +
                    "keyword is not supported.");
            throw new AciException(msgID, message);
            */
        } else if(expr.equalsIgnoreCase("ldapurl"))
            userAttrType=UserAttrType.URL;
        else
            userAttrType=UserAttrType.VALUE;
        return userAttrType;
    }
    /**
     * Evaluate an URL userattr type. Look into the resource entry for the
     * specified attribute and values. Assume it is an URL. Decode it an try
     * and match it against the client entry attribute.
     * @param evalCtx  The evaluation context to evaluate with.
     * @return An enumeration containing a result of the URL evaluation.
     */
    private EnumEvalResult evalURL(AciEvalContext evalCtx) {
        EnumEvalResult matched= EnumEvalResult.FALSE;
        boolean undefined=false;
        AttributeType attrType;
        if((attrType = DirectoryServer.getAttributeType(attrStr)) == null)
            attrType = DirectoryServer.getDefaultAttributeType(attrStr);
        List<Attribute> attrs=evalCtx.getResourceEntry().getAttribute(attrType);
        if(!attrs.isEmpty()) {
            for(Attribute a : attrs) {
                LinkedHashSet<AttributeValue> vals=a.getValues();
                for(AttributeValue v : vals) {
                    String urlStr=v.getStringValue();
                    LDAPURL url;
                    try {
                       url=LDAPURL.decode(urlStr, true);
                    } catch (DirectoryException e) {
                        break;
                    }
                    matched=UserDN.evalURL(evalCtx, url);
                    if(matched != EnumEvalResult.FALSE)
                        break;
                }
                if(matched == EnumEvalResult.TRUE)
                    break;
                if(matched == EnumEvalResult.ERR) {
                    undefined=true;
                    break;
                }
            }
        }
        return matched.getRet(type, undefined);
    }
    /**
     * Evaluate the DN type userattr keywords. These are roledn, userdn and
     * groupdn. The processing is the same for all three, although roledn is
     * a slightly different. For the roledn userattr keyword, a very simple
     * parent inheritance class was created. The rest of the processing is the
     * same for all three keywords.
     *
     * @param evalCtx The evaluation context to evaluate with.
     * @return An enumeration containing a result of the USERDN evaluation.
     */
    private EnumEvalResult evalDNKeywords(AciEvalContext evalCtx) {
        EnumEvalResult matched= EnumEvalResult.FALSE;
        boolean undefined=false, stop=false;
        int numLevels=parentInheritance.getNumLevels();
        int[] levels=parentInheritance.getLevels();
        AttributeType attrType=parentInheritance.getAttributeType();
        for(int i=0;((i < numLevels) && !stop); i++ ) {
            //The ROLEDN keyword will always enter this statement. The others
            //might. For the add operation, the resource itself (level 0)
            //must never be allowed to give access.
            if(levels[i] == 0) {
                if(evalCtx.isAddOperation()) {
                    undefined=true;
                } else if (evalCtx.getResourceEntry().hasAttribute(attrType)) {
                    matched =
                        evalEntryAttr(evalCtx.getResourceEntry(),
                                evalCtx,attrType);
                   if(matched.equals(EnumEvalResult.TRUE))
                        stop=true;
                }
            } else {
                try {
                    DN pDN=
                        getDNParentLevel(levels[i], evalCtx.getResourceDN());
                    if(pDN == null)
                        continue;
                    InternalClientConnection conn =
                        InternalClientConnection.getRootConnection();
                    InternalSearchOperation op = conn.processSearch(pDN,
                            SearchScope.BASE_OBJECT,
                            DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
                            SearchFilter.createFilterFromString(f), null);
                    LinkedList<SearchResultEntry> result =
                        op.getSearchEntries();
                    if (!result.isEmpty()) {
                        Entry e = result.getFirst();
                        if (e.hasAttribute(attrType)) {
                            matched = evalEntryAttr(e, evalCtx, attrType);
                           if(matched.equals(EnumEvalResult.TRUE))
                                stop=true;
                        }
                    }
                } catch (DirectoryException ex) {
                    undefined=true;
                    stop=true;
                    matched=EnumEvalResult.ERR;
                }
            }
        }
        return matched.getRet(type, undefined);
    }
    /**
     * This method returns a parent DN based on the level. Not very
     * sophisticated but it works.
     * @param l The level.
     * @param dn The DN to get the parent of.
     * @return Parent DN based on the level or null if the level is greater
     * than the  rdn count.
     */
    private DN getDNParentLevel(int l, DN dn) {
        int rdns=dn.getNumComponents();
        if(l > rdns)
            return null;
        DN theDN=dn;
        for(int i=0; i < l;i++) {
            theDN=theDN.getParent();
        }
        return theDN;
    }
    /**
     * This method evaluates the user attribute type and calls the correct
     * evalaution method. The three user attribute types that can be selected
     * are ROLEDN, USERDN or GROUPDN.
     * @param e The entry to use in the evaluation.
     * @param evalCtx The evaluation context to use in the evaluation.
     * @param attributeType The attribute type to use in the evaluation.
     * @return The result of the evaluation routine.
     */
    private EnumEvalResult evalEntryAttr(Entry e, AciEvalContext evalCtx,
                                         AttributeType attributeType) {
        EnumEvalResult result=EnumEvalResult.FALSE;
        switch (userAttrType) {
            case USERDN: {
                result=UserDN.evaluate(e, evalCtx.getClientDN(),
                                       attributeType);
                break;
            }
            case ROLEDN:
                result=RoleDN.evaluate(e, evalCtx, attributeType);
                break;
            case GROUPDN: {
                result=GroupDN.evaluate(e, evalCtx, attributeType);
                break;
            }
        }
        return result;
    }
}
opends/src/server/org/opends/server/authorization/dseecompat/UserDN.java
New file
@@ -0,0 +1,385 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying * information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.authorization.dseecompat;
import static org.opends.server.authorization.dseecompat.AciMessages.*;
import static org.opends.server.messages.MessageHandler.getMessage;
import java.util.*;
import org.opends.server.types.*;
import org.opends.server.core.DirectoryServer;
/**
 * This class represents the userdn keyword in a bind rule.
 */
public class UserDN implements KeywordBindRule {
    /*
     * A dummy URL for invalid URLs such as: all, parent, anyone, self.
     */
    private static String urlStr="ldap:///";
    /**
     * This list holds a list of objects representing a EnumUserDNType
     * URL mapping.
     */
    List<UserDNTypeURL> urlList=null;
    private EnumBindRuleType type=null;
    private AttributeType userDNAttrType;
    /**
     * Constructor that creates the userdn class. It also sets up an attribute
     * type ("userdn") needed  for wild-card matching.
     * @param type The type of  operation.
     * @param urlList  A list of enumerations containing the URL type and URL
     * object that can be retrieved at evaluation time.
     */
    private UserDN(EnumBindRuleType type, List<UserDNTypeURL> urlList) {
       this.type=type;
       this.urlList=urlList;
       userDNAttrType = DirectoryServer.getAttributeType("userdn");
       if (userDNAttrType == null)
          userDNAttrType = DirectoryServer.getDefaultAttributeType("userdn");
    }
    /**
     * Decodes an expression string representing a userdn bind rule.
     * @param expression The string representation of the userdn bind rule
     * expression.
     * @param type An enumeration of the type of the bind rule.
     * @return A KeywordBindRule class that represents the bind rule.
     * @throws AciException If the expression failed to LDAP URL decode.
     */
    public static KeywordBindRule decode(String expression,
            EnumBindRuleType type) throws AciException {
        String[] vals=expression.split("[|][|]");
        List<UserDNTypeURL> urlList = new LinkedList<UserDNTypeURL>();
         for(int i=0, m=vals.length; i < m; i++)
        {
            StringBuilder value = new StringBuilder(vals[i].trim());
           /*
            * TODO Evaluate using a wild-card in the dn portion of LDAP url.
            * The current implementation (DS6) does not treat a "*"
            * as a wild-card.
            *
            * Is it allowed to have a full LDAP URL (i.e., including a base,
            * scope, and filter) in which the base DN contains asterisks to
            * make it a wildcard?  If so, then I don't think that the current
            * implementation handles that correctly.  It will probably fail
            * when attempting to create the LDAP URL because the base DN isn't a
            * valid DN.
            */
            EnumUserDNType userDNType = UserDN.getType(value);
            LDAPURL url;
            try {
               url=LDAPURL.decode(value.toString(), true);
            } catch (DirectoryException de) {
                int msgID = MSGID_ACI_SYNTAX_INVALID_USERDN_URL;
                String message = getMessage(msgID, de.getErrorMessage());
                throw new AciException(msgID, message);
            }
            UserDNTypeURL dnTypeURL=new UserDNTypeURL(userDNType, url);
            urlList.add(dnTypeURL);
        }
        return new UserDN(type, urlList);
      }
    /**
     * This method determines the type of the DN (suffix in URL terms)
     * part of a URL, by examining the full URL itself for known strings
     * such as (corresponding type shown in parenthesis)
     *
     *      "ldap:///anyone"    (EnumUserDNType.ANYONE)
     *      "ldap:///parent"    (EnumUserDNType.PARENT)
     *      "ldap:///all"       (EnumUserDNType.ALL)
     *      "ldap:///self"      (EnumUserDNType.SELF)
     *
     * If one of the four above are found, the URL is replaced with a dummy
     * pattern "ldap:///". This is done because the above four are invalid
     * URLs; but the syntax is valid for an userdn keyword expression. The
     * dummy URLs are never used.
     *
     * If none of the above are found, it determine if the URL DN is a
     * substring pattern, such as:
     *
     *      "ldap:///uid=*, dc=example, dc=com" (EnumUserDNType.PATTERN)
     *
     * If none of the above are determined, it checks if the URL
     * is a complete URL with scope and filter defined:
     *
     *  "ldap:///uid=test,dc=example,dc=com??sub?(cn=j*)"  (EnumUserDNType.URL)
     *
     * If none of these those types can be identified, it defaults to
     * EnumUserDNType.DN.
     *
     * @param bldr A string representation of the URL that can be modified.
     * @return  The user DN type of the URL.
     */
    private static EnumUserDNType getType(StringBuilder bldr) {
        EnumUserDNType type;
        String str=bldr.toString();
        if(str.indexOf("?") != -1) {
            type = EnumUserDNType.URL;
        } else  if(str.equalsIgnoreCase("ldap:///self")) {
            type = EnumUserDNType.SELF;
            bldr.replace(0, bldr.length(), urlStr);
        } else if(str.equalsIgnoreCase("ldap:///anyone")) {
            type = EnumUserDNType.ANYONE;
            bldr.replace(0, bldr.length(), urlStr);
        } else if(str.equalsIgnoreCase("ldap:///parent")) {
            type = EnumUserDNType.PARENT;
            bldr.replace(0, bldr.length(), urlStr);
        } else if(str.equalsIgnoreCase("ldap:///all")) {
            type = EnumUserDNType.ALL;
            bldr.replace(0, bldr.length(), urlStr);
        } else if(str.indexOf("*") != -1) {
            type = EnumUserDNType.DNPATTERN;
        } else {
            type = EnumUserDNType.DN;
        }
        return type;
    }
    /**
     * Performs the evaluation of a userdn bind rule based on the
     * evaluation context passed to it. The evaluation stops when there
     * are no more UserDNTypeURLs to evaluate or if an UserDNTypeURL
     * evaluates to true.
     * @param evalCtx The evaluation context to evaluate with.
     * @return  An evaluation result enumeration containing the result
     * of the evaluation.
     */
    public EnumEvalResult evaluate(AciEvalContext evalCtx) {
        EnumEvalResult matched = EnumEvalResult.FALSE;
        boolean undefined=false;
        boolean isAnonUser=evalCtx.isAnonymousUser();
        Iterator<UserDNTypeURL> it=urlList.iterator();
        for(; it.hasNext() && matched != EnumEvalResult.TRUE &&
                matched != EnumEvalResult.ERR;) {
            UserDNTypeURL dnTypeURL=it.next();
            //Handle anonymous checks here
            if(isAnonUser) {
                if(dnTypeURL.getUserDNType() == EnumUserDNType.ANYONE)
                    matched = EnumEvalResult.TRUE;
            }  else
                matched=evalNonAnonymous(evalCtx, dnTypeURL);
        }
        return matched.getRet(type, undefined);
    }
    /**
     * Performs an evaluation of a single UserDNTypeURL of a userdn bind
     * rule using the evaluation context provided. This method is called
     * for the non-anonymous user case.
     * @param evalCtx  The evaluation context to evaluate with.
     * @param dnTypeURL The URL dn type mapping to evaluate.
     * @return An evaluation result enumeration containing the result
     * of the evaluation.
     */
    private EnumEvalResult evalNonAnonymous(AciEvalContext evalCtx,
                                            UserDNTypeURL dnTypeURL) {
        DN clientDN=evalCtx.getClientDN();
        DN resDN=evalCtx.getResourceDN();
        EnumEvalResult matched = EnumEvalResult.FALSE;
        EnumUserDNType type=dnTypeURL.getUserDNType();
        LDAPURL url=dnTypeURL.getURL();
        switch (type) {
            case URL:
            {
                matched = evalURL(evalCtx, url);
                break;
            }
            case ANYONE:
            {
                matched = EnumEvalResult.TRUE;
                break;
            }
            case SELF:
            {
                if (clientDN.equals(resDN)) matched = EnumEvalResult.TRUE;
                break;
            }
            case PARENT:
            {
                DN parentDN = resDN.getParent();
                if ((parentDN != null) &&
                        (parentDN.equals(clientDN)))
                    matched = EnumEvalResult.TRUE;
                break;
            }
            case ALL:
            {
                matched = EnumEvalResult.TRUE;
                break;
            }
            case DNPATTERN:
            {
                matched = evalDNPattern(evalCtx, url);
                break;
            }
            case DN:
            {
                try
                {
                    DN dn = url.getBaseDN();
                    if (clientDN.equals(dn))
                        matched = EnumEvalResult.TRUE;
                } catch (DirectoryException ex) {
                    //TODO add message
                }
            }
        }
        return matched;
    }
    /*
     * TODO Evaluate making this more efficient.
     *
     * The evalDNPattern() method looks like it suffers from the
     * same problem as the matchesPattern() method in the Target
     * class.  Creating a dummy entry and attempting to do substring
     * matching on a DN is a pretty expensive and error-prone approach.
     * Using a regular expression would likely be much more efficient and
     *  should be simpler.
     */
    /**
     * This method evaluates a DN pattern userdn expression. It creates a
     * dummy entry and a substring filter and applies the filter to the
     * entry.
     * @param evalCtx  The evaluation context to use.
     * @param url The LDAP URL containing the pattern.
     * @return An enumeration evaluation result.
     */
    private EnumEvalResult evalDNPattern(AciEvalContext evalCtx, LDAPURL url) {
        boolean rc;
        EnumEvalResult ret=EnumEvalResult.TRUE;
        String urlDN;
        SearchFilter filter;
        try {
            urlDN=url.getBaseDN().toNormalizedString();
            String pattern="userdn="+urlDN;
            filter=SearchFilter.createFilterFromString(pattern);
        } catch (DirectoryException ex) {
            return EnumEvalResult.FALSE;
        }
        LinkedHashSet<AttributeValue> vals =
                new LinkedHashSet<AttributeValue>();
        String userDNStr=evalCtx.getClientDN().toNormalizedString();
        vals.add(new AttributeValue(userDNAttrType, userDNStr));
        Attribute attr = new Attribute(userDNAttrType, "userdn", vals);
        Entry e = new Entry(DN.nullDN(), null, null, null);
        e.addAttribute(attr,new ArrayList<AttributeValue>());
        try {
            rc=filter.matchesEntry(e);
        } catch (DirectoryException ex) {
            return EnumEvalResult.FALSE;
        }
        if(!rc)
            ret=EnumEvalResult.FALSE;
        return ret;
    }
    /**
     * This method evaluates an URL userdn expression. Something like:
     * ldap:///suffix??sub?(filter). It also searches for the client DN
     * entry and saves it in the evaluation context for repeat evaluations
     * that might come later in processing.
     *
     * @param evalCtx  The evaluation context to use.
     * @param url URL containing the URL to use in the evaluation.
     * @return An enumeration of the evaluation result.
     */
    public static EnumEvalResult evalURL(AciEvalContext evalCtx, LDAPURL url) {
        EnumEvalResult ret=EnumEvalResult.FALSE;
        DN urlDN;
        SearchFilter filter;
        try {
            urlDN=url.getBaseDN();
            filter=url.getFilter();
        } catch (DirectoryException ex) {
            return EnumEvalResult.FALSE;
        }
        SearchScope scope=url.getScope();
        if(scope == SearchScope.WHOLE_SUBTREE) {
            if(!evalCtx.getClientDN().isDescendantOf(urlDN))
                return EnumEvalResult.FALSE;
        } else if(scope == SearchScope.SINGLE_LEVEL) {
            DN parent=evalCtx.getClientDN().getParent();
            if((parent != null) && !parent.equals(urlDN))
                return EnumEvalResult.FALSE;
        } else {
            if(!evalCtx.getClientDN().equals(urlDN))
                return EnumEvalResult.FALSE;
        }
        try {
            if(filter.matchesEntry(evalCtx.getClientEntry()))
                ret=EnumEvalResult.TRUE;
        } catch (DirectoryException ex) {
            return EnumEvalResult.FALSE;
        }
        return ret;
    }
    /*
     * TODO Evaluate making this method more efficient.
     *
     * The evalDNEntryAttr method isn't as efficient as it could be.  It would
     * probably be faster to to convert the clientDN to an AttributeValue and
     * see if the entry has that value than to decode each value as a DN and
     * see if it matches the clientDN.
     */
    /**
     * This method searches an entry for an attribute value that is
     * treated as a DN. That DN is then compared against the client
     * DN.
     * @param e The entry to get the attribute type from.
     * @param clientDN The client authorization DN to check for.
     * @param attrType The attribute type from the bind rule.
     * @return An enumeration with the result.
     */
    public static EnumEvalResult evaluate(Entry e, DN clientDN,
                                           AttributeType attrType) {
        EnumEvalResult matched= EnumEvalResult.FALSE;
        List<Attribute> attrs =  e.getAttribute(attrType);
        LinkedHashSet<AttributeValue> vals = attrs.get(0).getValues();
        for(AttributeValue v : vals) {
            try {
                DN dn=DN.decode(v.getStringValue());
                if(dn.equals(clientDN)) {
                    matched=EnumEvalResult.TRUE;
                    break;
                }
            } catch (DirectoryException ex) {
                break;
            }
        }
        return matched;
    }
}
opends/src/server/org/opends/server/authorization/dseecompat/UserDNTypeURL.java
New file
@@ -0,0 +1,71 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying * information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.authorization.dseecompat;
import org.opends.server.types.LDAPURL;
/**
 * The UserDNTypeURL class contains the EnumUserDNType and the URL value,
 * of a "userdn" URL decoded by the UserDN.decode() method.
 */
public class UserDNTypeURL {
    /**
     * The DN type of the URL.
     */
    private EnumUserDNType dnType;
    /*
     * The URL value. Maybe a dummy value for types such as ANYONE or SELF.
     */
    private LDAPURL url;
    /**
     * Create a class representing the "userdn" URL decoded by the
     * UserDN.decode() method.
     * @param dnType The type of the URL determined by examining the DN
     * or suffix.
     * @param url The URL itself from the ACI "userdn" string expression.
     */
    UserDNTypeURL(EnumUserDNType dnType, LDAPURL url) {
        this.url=url;
        this.dnType=dnType;
    }
    /**
     * Returns the DN type.
     * @return The DN type of the URL.
     */
    public EnumUserDNType getUserDNType() {
        return this.dnType;
    }
    /** Returns the URL.
     * @return The URL decoded by the UserDN.decode() method.
     */
    public LDAPURL getURL() {
        return this.url;
    }
}