001: /*
002: * $Id: AccessRuleList.java,v 1.51 2007/09/11 13:24:21 agoubard Exp $
003: *
004: * Copyright 2003-2007 Orange Nederland Breedband B.V.
005: * See the COPYRIGHT file for redistribution and use restrictions.
006: */
007: package org.xins.server;
008:
009: import java.util.StringTokenizer;
010:
011: import org.xins.common.MandatoryArgumentChecker;
012: import org.xins.common.Utils;
013: import org.xins.common.text.ParseException;
014:
015: /**
016: * Access rule list.
017: *
018: * <h3>Descriptor format</h3>
019: *
020: * <p>An access rule list <em>descriptor</em>, a character string, can be
021: * converted to produce an {@link AccessRuleList} object. A valid descriptor
022: * consists of a list of access rule descriptors (see class
023: * {@link AccessRule}) and/or access rule file descriptors (see class
024: * {@link AccessRuleFile}), separated by semi-colon characters (<code>';'</code>).
025: * Optionally, the rules can have any amount of whitespace (space-, tab-,
026: * newline- and carriage return-characters), before and after them. The last
027: * descriptor cannot end with a semi-colon.
028: *
029: * <h3>Descriptor examples</h3>
030: *
031: * <p>An example of an access rule list descriptor is:
032: *
033: * <blockquote><code>allow 194.134.168.213/32 *;
034: * <br>deny 194.134.168.213/24 _*;
035: * <br>allow 194.134.168.213/24 *;
036: * <br>file /var/conf/file1.acl;
037: * <br>deny 0.0.0.0/0 *</code></blockquote>
038: *
039: * <p>The above access control list grants the IP address 194.134.168.213
040: * access to all functions. Then in the second rule it denies
041: * access to all IP addresses in the range 194.134.168.0 to 194.134.168.255 to
042: * all functions that start with an underscore (<code>'_'</code>). Then it
043: * allows access for those IP addresses to all other functions, then it
044: * applies the rules in the <code>/var/conf/file1.acl</code> file and finally
045: * all other IP addresses are denied access to any of the functions.
046: *
047: * @version $Revision: 1.51 $ $Date: 2007/09/11 13:24:21 $
048: * @author <a href="mailto:ernst@ernstdehaan.com">Ernst de Haan</a>
049: *
050: * @since XINS 1.0.0
051: */
052: public final class AccessRuleList implements AccessRuleContainer {
053:
054: /**
055: * An empty access rule list. This field is never <code>null</code>.
056: */
057: static final AccessRuleList EMPTY = new AccessRuleList(
058: new AccessRuleContainer[0]);
059:
060: /**
061: * The list of rules. Cannot be <code>null</code>.
062: */
063: private AccessRuleContainer[] _rules;
064:
065: /**
066: * The string representation of this instance. Cannot be <code>null</code>.
067: */
068: private String _asString;
069:
070: /**
071: * Flag that indicates whether this object is disposed.
072: */
073: private boolean _disposed;
074:
075: /**
076: * Creates a new <code>AccessRuleList</code> object. The passed
077: * {@link AccessRuleContainer} array is assumed to be owned by the
078: * constructor.
079: *
080: * @param rules
081: * the list of rules, not <code>null</code> and should not contain any
082: * duplicate or <code>null</code> elements; if one of these latter 2
083: * constraints are violated, the behaviour is undefined.
084: *
085: * @throws NullPointerException
086: * if <code>rules == null</code>.
087: */
088: private AccessRuleList(AccessRuleContainer[] rules)
089: throws NullPointerException {
090:
091: // Count number of rules (may throw NPE)
092: int ruleCount = rules.length;
093:
094: // Build string representation and log
095: StringBuffer buffer = new StringBuffer(ruleCount * 40);
096: if (ruleCount > 0) {
097: String s = rules[0].toString();
098: buffer.append(s);
099: Log.log_3429(0, s);
100:
101: for (int i = 1; i < ruleCount; i++) {
102: s = rules[i].toString();
103:
104: buffer.append(';');
105: buffer.append(s);
106:
107: Log.log_3429(i, s);
108: }
109: }
110: _asString = buffer.toString();
111:
112: // Store the rules
113: _rules = rules;
114: }
115:
116: /**
117: * Parses the specified character string to construct a new
118: * <code>AccessRuleList</code> object, with the specified watch interval
119: * for referenced files.
120: *
121: * <p>If the specified interval is <code>0</code>, then no watching will be
122: * performed.
123: *
124: * @param descriptor
125: * the access rule list descriptor, the character string to parse,
126: * cannot be <code>null</code>.
127: *
128: * @param interval
129: * the interval used to check the ACL files for modification, in
130: * seconds, must be >= 0.
131: *
132: * @return
133: * an {@link AccessRuleList} instance, never <code>null</code>.
134: *
135: * @throws IllegalArgumentException
136: * if <code>descriptor == null || interval < 0</code>.
137: *
138: * @throws ParseException
139: * if there was a parsing error.
140: *
141: * @since XINS 1.1.0
142: */
143: public static final AccessRuleList parseAccessRuleList(
144: String descriptor, int interval)
145: throws IllegalArgumentException, ParseException {
146:
147: // Check preconditions
148: MandatoryArgumentChecker.check("descriptor", descriptor);
149: if (interval < 0) {
150: throw new IllegalArgumentException("interval (" + interval
151: + ") < 0");
152: }
153:
154: // First trim whitespace from the descriptor
155: descriptor = descriptor.trim();
156:
157: // Tokenize the descriptor, separator is semi-colon
158: StringTokenizer tokenizer = new StringTokenizer(descriptor, ";");
159: int ruleCount = tokenizer.countTokens();
160:
161: // Parse all tokens
162: AccessRuleContainer[] rules = new AccessRuleContainer[ruleCount];
163: for (int i = 0; i < ruleCount; i++) {
164:
165: // Remove leading and trailing whitespace from the next token
166: String token = tokenizer.nextToken().trim();
167:
168: // Parse and add the rule
169: if (token.startsWith("allow") || token.startsWith("deny")) {
170: rules[i] = AccessRule.parseAccessRule(token);
171: } else if (token.startsWith("file")) {
172: rules[i] = new AccessRuleFile(token, interval);
173: } else {
174: String detail = "Failed to parse access rule list. "
175: + "Expected token \"" + token
176: + "\" to start with "
177: + "\"allow\", \"deny\" or \"file\".";
178: throw new ParseException(detail);
179: }
180: }
181:
182: return new AccessRuleList(rules);
183: }
184:
185: /**
186: * Counts the number of rules in this list.
187: *
188: * @return
189: * the number of rules, always >= 0.
190: */
191: public int getRuleCount() {
192: return _rules.length;
193: }
194:
195: /**
196: * Determines if the specified IP address is allowed to access the
197: * specified function, returning a <code>Boolean</code> object or
198: * <code>null</code>.
199: *
200: * <p>This method finds the first matching rule and then returns the
201: * <em>allow</em> property of that rule (see
202: * {@link AccessRule#isAllowRule()}). If there is no matching rule, then
203: * <code>null</code> is returned.
204: *
205: * @param ip
206: * the IP address, cannot be <code>null</code>.
207: *
208: * @param functionName
209: * the name of the function, cannot be <code>null</code>.
210: *
211: * @param conventionName
212: * the name of the calling convention to match, can be <code>null</code>.
213: *
214: * @return
215: * {@link Boolean#TRUE} if the specified IP address is allowed to access
216: * the specified function, {@link Boolean#FALSE} if it is disallowed
217: * access or <code>null</code> if no match is found.
218: *
219: * @throws IllegalStateException
220: * if this object is disposed (<em>since XINS 1.3.0</em>).
221: *
222: * @throws IllegalArgumentException
223: * if <code>ip == null || functionName == null</code>.
224: *
225: * @throws ParseException
226: * if the specified IP address is malformed.
227: *
228: * @since XINS 2.1.
229: */
230: public Boolean isAllowed(String ip, String functionName,
231: String conventionName) throws IllegalStateException,
232: IllegalArgumentException, ParseException {
233:
234: // Check state
235: if (_disposed) {
236: String detail = "This AccessRuleList is disposed.";
237: Utils.logProgrammingError(detail);
238: throw new IllegalStateException(detail);
239: }
240:
241: // Check preconditions
242: MandatoryArgumentChecker.check("ip", ip, "functionName",
243: functionName);
244:
245: int ruleCount = _rules.length;
246: for (int i = 0; i < ruleCount; i++) {
247: AccessRuleContainer rule = _rules[i];
248:
249: String ruleString = rule.toString();
250:
251: Boolean allowed = rule.isAllowed(ip, functionName,
252: conventionName);
253: if (allowed != null) {
254:
255: // Choose between 'allow' and 'deny'
256: boolean allow = allowed.booleanValue();
257:
258: // Log this match
259: // XXX: Should this logging really be done in this class?
260: if (allow) {
261: Log.log_3550(ip, functionName, conventionName, i,
262: ruleString);
263: } else {
264: Log.log_3551(ip, functionName, conventionName, i,
265: ruleString);
266: }
267:
268: return allowed;
269: }
270: }
271: return null;
272: }
273:
274: /**
275: * Disposes this access rule. All claimed resources are freed as much as
276: * possible.
277: *
278: * <p>Once disposed, the {@link #isAllowed} method should no longer be
279: * called.
280: */
281: public void dispose() {
282:
283: // Check state
284: if (_disposed) {
285: String detail = "This AccessRule is already disposed.";
286: Utils.logProgrammingError(detail);
287: throw new IllegalStateException(detail);
288: }
289:
290: // Do not dispose the EMPTY list
291: if (this == EMPTY) {
292: return;
293: }
294:
295: // Mark this object as disposed
296: _disposed = true;
297:
298: // Dispose the current rules
299: int count = _rules == null ? 0 : _rules.length;
300: for (int i = 0; i < count; i++) {
301: AccessRuleContainer rule = _rules[i];
302: if (rule != null) {
303: try {
304: rule.dispose();
305: } catch (Throwable exception) {
306: Utils.logIgnoredException(exception);
307: }
308: }
309: }
310: _rules = null;
311: }
312:
313: /**
314: * Returns a character string representation of this object. The returned
315: * string is in the form:
316: *
317: * <blockquote><em>type a.b.c.d/m pattern;type a.b.c.d/m pattern</em></blockquote>
318: *
319: * where <em>type</em> is either <code>"allow"</code> or
320: * <code>"deny"</code>, <em>a.b.c.d</em> is the base IP address, <em>m</em>
321: * is the mask, and <em>pattern</em> is the function name simple pattern.
322: *
323: * @return
324: * a character string representation of this access rule, never
325: * <code>null</code>.
326: */
327: public String toString() {
328: return _asString;
329: }
330: }
|