--- experimental/server/ACL/ACL.java 2001/12/19 23:43:27 1.1 +++ experimental/server/ACL/ACL.java 2002/01/06 23:32:27 1.11 @@ -3,109 +3,271 @@ //---IMPORTS--- import uk.org.iscream.cms.server.util.*; -import java.util.LinkedList; -import java.util.Iterator; +import java.util.ArrayList; +import java.util.StringTokenizer; import java.net.InetAddress; +import java.io.Serializable; /** - * Access Control List + * Access Control List for use primarily + * with the ACLServerSocket. It could, however + * have other uses as it has a fairly generic + * behaviour. Rules are added using the add + * method, and then checks can be made using + * the relevant check method. * * @author $Author: tdb $ - * @version $Id: ACL.java,v 1.1 2001/12/19 23:43:27 tdb Exp $ + * @version $Id: ACL.java,v 1.11 2002/01/06 23:32:27 tdb Exp $ */ -public class ACL { +public class ACL implements Serializable { //---FINAL ATTRIBUTES--- /** * The current CVS revision of this class */ - public static final String REVISION = "$Revision: 1.1 $"; - + public static final String REVISION = "$Revision: 1.11 $"; + + /** + * static to be used when adding an ALLOW rule to the ACL. + */ public static final boolean ALLOW = true; + + /** + * static to be used when adding a DENY rule to the ACL. + */ public static final boolean DENY = false; //---STATIC METHODS--- //---CONSTRUCTORS--- + /** + * Construct a new Access Control List. The default + * mode is to ALLOW anything that isn't explicitly + * blocked by a rule. + */ public ACL() { // default to ACL.ALLOW this(ACL.ALLOW); } + /** + * Construct a new Access Control List with a given + * default mode. This mode specifies what should + * happen if a check does not match any rules. + * + * @param defaultMode the default mode for non-matched checks + */ public ACL(boolean defaultMode) { _defaultMode = defaultMode; } //---PUBLIC METHODS--- + /** + * Add a new rule to the ACL immediately after the + * previous rule. The rule can either be an ACL.ALLOW + * rule, or an ACL.DENY rule. The expression can + * contain a wildcard (a * only). Rules can only be + * added to the end of the list. + * + * param allow whether this is an ALLOW or DENY rule + * param expression what this rule matches using wildcards + */ public void add(boolean allow, String expression) { - _acl.add(new ACLItem(allow, expression)); + // default to expecting it to be an IP + // we will try to disprove this :) + boolean ip = true; + short[] ipaddr = {-1, -1, -1, -1}; + int i = 0; + String s = ""; + // tokenize the expression on fullstops, so we can break + // up the quads of an IP (if it's an IP!) + StringTokenizer st = new StringTokenizer(expression, "."); + while(st.hasMoreTokens() && i++ < 4) { + s = st.nextToken(); + // if it's a wildcard, we'll skip to the next one + // as no more checks are required + if(s.equals("*")) { + continue; + } + // attempt to parse it into a short + try { + short n = Short.parseShort(s); + // if it's an int but outside of the + // valid range, it can't be an IP + if(n < 0 || n > 255) { + ip = false; + // give up checking further + break; + } + ipaddr[i-1] = n; + } + // if it didn't parse as an int it can't be an IP + catch (NumberFormatException e) { + ip = false; + // give up checking further + break; + } + } + // we've done 4 parts, so if there's any + // more this can't be an IP + if(st.hasMoreTokens()) { + ip = false; + } + // if we've done less than 4, see if the last one + // was a wildcard - if it isn't then it's not an IP + // -- this allows 129.12.* + if(i < 4 && !s.equals("*")) { + ip = false; + } + // if we had one or less entries it can't be an IP + // -- this disallows * matching as an IP due + // to the rule above + if(i <= 1) { + ip = false; + } + // add the rule to our array + _acl.add(new ACLRule(allow, expression, ipaddr, ip)); } + /** + * Check to see if a string is permitted by the + * ACL. Useful for testing, and non-Socket uses + * of this class. + * + * @param address the string to check + * @return whether the address was permitted by the ACL + */ public boolean check(String address) { - Iterator i = _acl.iterator(); - while(i.hasNext()) { - ACLItem item = (ACLItem) i.next(); - if(StringUtils.wildcardCheck(address, item._expression)) { - return item._allow; + for(int i=0; i < _acl.size(); i++) { + ACLRule rule = (ACLRule) _acl.get(i); + if(StringUtils.wildcardCheck(address, rule._expression)) { + return rule._allow; } } - // what to do here? - // -- basically a default of deny/allow is needed return _defaultMode; } + /** + * Check to see if an InetAddress is permitted + * by the ACL. Perfect for Socket uses of this + * class. A rule will either be for a name, or + * an IP address (this is determined in the add + * method), and the appropriate comparison will + * be performed. + * + * @param address the InetAddress to check + * @return whether the InetAddress was permitted by the ACL + */ public boolean check(InetAddress address) { - Iterator i = _acl.iterator(); - while(i.hasNext()) { - ACLItem item = (ACLItem) i.next(); - if(StringUtils.wildcardCheck(address.getHostName(), item._expression)) { - return item._allow; + // gather the details first + String hostname = address.getHostName(); + String ip = address.getHostAddress(); + short[] ipaddr = ipStringToShort(ip); + // check each rule against this InetAddress + for(int i=0; i < _acl.size(); i++) { + ACLRule rule = (ACLRule) _acl.get(i); + if(rule._iprule) { + // if this is an IP rule do a short comparison + // must specify the wildcarded rule first + if(compareShorts(rule._ipaddr, ipaddr)) { + return rule._allow; + } } - if(StringUtils.wildcardCheck(address.getHostAddress(), item._expression)) { - return item._allow; + else { + // if not do a full blown String comparsion + if(StringUtils.wildcardCheck(hostname, rule._expression)) { + return rule._allow; + } } + } - // what to do here? - // -- basically a default of deny/allow is needed + // if we haven't matched a rule, return the default return _defaultMode; } - public String getACL() { - String acl = ""; - Iterator i = _acl.iterator(); - while(i.hasNext()) { - ACLItem item = (ACLItem) i.next(); - if(item._allow) { - acl += "ALLOW:" + item._expression + " "; + /** + * Gives a String representation of this ACL. + * + * @return A String representation of this ACL. + */ + public String toString() { + StringBuffer acl = new StringBuffer(); + // put in the i-scream toString code + acl.append(FormatName.getName(_name, getClass().getName(), REVISION)); + acl.append("{"); + // put the value of each Rule in the result + for(int i=0; i < _acl.size(); i++) { + acl.append((ACLRule) _acl.get(i)); + acl.append(","); + } + // put the default mode in the result + if(_defaultMode) { + acl.append("DEFAULT=ALLOW"); + } + else { + acl.append("DEFAULT=DENY"); + } + acl.append("}"); + return acl.toString(); + } + +//---PRIVATE METHODS--- + + /** + * Converts an IP address in String format into + * a short array of length 4. Any wildcards, *, + * found in the IP address are represented by + * a -1. + * + * @param ip The IP address in String format + * @return The IP address in a short[] + */ + private short[] ipStringToShort(String ip) { + short[] ipaddr = {-1, -1, -1, -1}; + StringTokenizer st = new StringTokenizer(ip, "."); + for(int i=0; i < 4 && st.hasMoreTokens(); i++) { + try { + ipaddr[i] = Short.parseShort(st.nextToken()); } - else { - acl += "DENY:" + item._expression + " "; + catch(NumberFormatException e) { + // do nothing... + // we just want to leave it as -1 + // -- actually, maybe we want to do more checks in here? + // although in this code context it'll probably be ok, + // it might be worth verifying wildcards and that the + // number is in range... } } - return acl.substring(0, acl.length()-1); + return ipaddr; } /** - * Overrides the {@link java.lang.Object#toString() Object.toString()} - * method to provide clean logging (every class should have this). + * Compares two short arrays. The first array can contain a -1, + * which will always match any value -- it's a wildcard. + * They must be the same length to match. * - * This uses the uk.org.iscream.cms.server.util.FormatName class - * to format the toString() - * - * @return the name of this class and its CVS revision + * @param first The first array to compare (with -1 wildcard if required) + * @param second The second array to compare + * @result the result of the comparison */ - public String toString() { - return FormatName.getName( - _name, - getClass().getName(), - REVISION); + private boolean compareShorts(short[] first, short[] second) { + if(first.length != second.length) { + return false; + } + for(int i=0; i < first.length; i++) { + if(first[i] == -1) { + continue; + } + if(first[i] != second[i]) { + return false; + } + } + return true; } -//---PRIVATE METHODS--- - //---ACCESSOR/MUTATOR METHODS--- //---ATTRIBUTES--- @@ -120,23 +282,75 @@ public class ACL { * be changed to null for utility classes. */ private String _name = null; - - private LinkedList _acl = new LinkedList(); + + /** + * The ACL is stored in this ArrayList. + */ + private ArrayList _acl = new ArrayList(); + + /** + * The default mode of this ACL. + */ private boolean _defaultMode; //---STATIC ATTRIBUTES--- //---INNER CLASSES--- - private class ACLItem { + /** + * Wrapper class for an ACL rule. + */ + private class ACLRule implements Serializable { - private ACLItem(boolean allow, String expression) { + /** + * Construct an ACL rule. + * + * @param allow whether this is an ALLOW or DENY rule + * @param expression what this rule matches + * @param ipaddr the IP address wildcard this rule matches if it's an IP rule + * @param iprule whether this is an IP rule + */ + private ACLRule(boolean allow, String expression, short[] ipaddr, boolean iprule) { _allow = allow; _expression = expression; + _ipaddr = ipaddr; + _iprule = iprule; } + /** + * Returns a String representation of this rule. + * + * @return A String representation of this rule. + */ + public String toString() { + if(_allow) { + return _expression + "=ALLOW"; + } + else { + return _expression + "=DENY"; + } + } + + /** + * Whether this is an ALLOW or DENY rule. + */ private boolean _allow; + + /** + * What this rule matches. + */ private String _expression; + + /** + * The IP wildcard, only valid if this + * is an IP rule. + */ + private short[] _ipaddr; + + /** + * Whether this is an IP rule. + */ + private boolean _iprule; }