1 |
//---PACKAGE DECLARATION--- |
2 |
package uk.org.iscream.cms.server.util; |
3 |
|
4 |
//---IMPORTS--- |
5 |
import java.util.ArrayList; |
6 |
import java.util.StringTokenizer; |
7 |
import java.net.InetAddress; |
8 |
import java.io.Serializable; |
9 |
|
10 |
/** |
11 |
* Access Control List for use primarily |
12 |
* with the ACLServerSocket. It could, however |
13 |
* have other uses as it has a fairly generic |
14 |
* behaviour. Rules are added using the add |
15 |
* method, and then checks can be made using |
16 |
* the relevant check method. |
17 |
* |
18 |
* @author $Author: tdb $ |
19 |
* @version $Id: ACL.java,v 1.3 2002/03/19 12:18:22 tdb Exp $ |
20 |
*/ |
21 |
public class ACL implements Serializable { |
22 |
|
23 |
//---FINAL ATTRIBUTES--- |
24 |
|
25 |
/** |
26 |
* The current CVS revision of this class |
27 |
*/ |
28 |
public static final String REVISION = "$Revision: 1.3 $"; |
29 |
|
30 |
/** |
31 |
* static to be used when adding an ALLOW rule to the ACL. |
32 |
*/ |
33 |
public static final boolean ALLOW = true; |
34 |
|
35 |
/** |
36 |
* static to be used when adding a DENY rule to the ACL. |
37 |
*/ |
38 |
public static final boolean DENY = false; |
39 |
|
40 |
/** |
41 |
* default setting for the default mode for a new ACL. |
42 |
*/ |
43 |
public static final boolean DEFMODE = ACL.ALLOW; |
44 |
|
45 |
//---STATIC METHODS--- |
46 |
|
47 |
//---CONSTRUCTORS--- |
48 |
|
49 |
/** |
50 |
* Construct a new Access Control List. The default |
51 |
* mode is to ALLOW anything that isn't explicitly |
52 |
* blocked by a rule. |
53 |
*/ |
54 |
public ACL() { |
55 |
// default to DEFMODE |
56 |
this(DEFMODE); |
57 |
} |
58 |
|
59 |
/** |
60 |
* Construct a new Access Control List with a given |
61 |
* default mode. This mode specifies what should |
62 |
* happen if a check does not match any rules. |
63 |
* |
64 |
* @param defaultMode the default mode for non-matched checks |
65 |
*/ |
66 |
public ACL(boolean defaultMode) { |
67 |
setDefaultMode(defaultMode); |
68 |
} |
69 |
|
70 |
/** |
71 |
* Construct a new Access Control List with a given |
72 |
* String representation of the ACL rules. The String |
73 |
* should be of the format: |
74 |
* expression:rule;expression:rule;expression:rule... |
75 |
* Where expression is a wildcard to match against, and |
76 |
* rule is either 'ALLOW' or 'DENY'. There is a special |
77 |
* expression of 'DEFAULT' which represents the default |
78 |
* rule (what should happen if no expression is matched |
79 |
* when performing a check). |
80 |
* The default mode is set to ALLOW if one is not |
81 |
* specified in the String. |
82 |
* |
83 |
* @param acl a String representation of the ACL. |
84 |
*/ |
85 |
public ACL(String acl) { |
86 |
setDefaultMode(DEFMODE); |
87 |
add(acl); |
88 |
} |
89 |
|
90 |
//---PUBLIC METHODS--- |
91 |
|
92 |
/** |
93 |
* Add a new rule to the ACL immediately after the |
94 |
* previous rule. The rule can either be an ACL.ALLOW |
95 |
* rule, or an ACL.DENY rule. The expression can |
96 |
* contain a wildcard (a * only). Rules can only be |
97 |
* added to the end of the list. |
98 |
* |
99 |
* param allow whether this is an ALLOW or DENY rule |
100 |
* param expression what this rule matches using wildcards |
101 |
*/ |
102 |
public void add(boolean allow, String expression) { |
103 |
// try and convert the expression into an IP address |
104 |
short[] ipaddr = ipStringToShort(expression); |
105 |
// a result of null means it's not an IP address |
106 |
// add either a name rule or an IP rule |
107 |
if(ipaddr != null) { |
108 |
_acl.add(new ACLRule(allow, expression, ipaddr, true)); |
109 |
} |
110 |
else { |
111 |
_acl.add(new ACLRule(allow, expression, ipaddr, false)); |
112 |
} |
113 |
} |
114 |
|
115 |
/** |
116 |
* Add some new rules to the Access Control List in |
117 |
* the form of a String. The String should be of the |
118 |
* following format: |
119 |
* expression:rule;expression:rule;expression:rule... |
120 |
* Where expression is a wildcard to match against, and |
121 |
* rule is either 'ALLOW' or 'DENY'. There is a special |
122 |
* expression of 'DEFAULT' which represents the default |
123 |
* rule (what should happen if no expression is matched |
124 |
* when performing a check). |
125 |
* |
126 |
* @param acl a String representation of the ACL. |
127 |
*/ |
128 |
public void add(String acl) { |
129 |
if(acl != null) { |
130 |
// split the String into expression:rule parts |
131 |
StringTokenizer st1 = new StringTokenizer(acl, ";"); |
132 |
while(st1.hasMoreTokens()) { |
133 |
String token1 = st1.nextToken(); |
134 |
// if it doesn't have a :, it's not the correct format |
135 |
if(token1.indexOf(":") != -1) { |
136 |
// split into expression and rule part |
137 |
StringTokenizer st2 = new StringTokenizer(token1, ":"); |
138 |
String expression = ""; |
139 |
String rule = ""; |
140 |
if(st2.hasMoreTokens()) { |
141 |
expression = st2.nextToken(); |
142 |
} |
143 |
else { |
144 |
// mall-formed? |
145 |
continue; |
146 |
} |
147 |
if(st2.hasMoreTokens()) { |
148 |
rule = st2.nextToken(); |
149 |
} |
150 |
else { |
151 |
// mall-formed? |
152 |
continue; |
153 |
} |
154 |
// check to see what sort of rule |
155 |
if(rule.equals("ALLOW")) { |
156 |
// case for special 'DEFAULT' expression |
157 |
if(expression.equals("DEFAULT")) { |
158 |
setDefaultMode(ACL.ALLOW); |
159 |
} |
160 |
else { |
161 |
add(ACL.ALLOW, expression); |
162 |
} |
163 |
} |
164 |
else if(rule.equals("DENY")) { |
165 |
// case for special 'DEFAULT' expression |
166 |
if(expression.equals("DEFAULT")) { |
167 |
setDefaultMode(ACL.DENY); |
168 |
} |
169 |
else { |
170 |
add(ACL.DENY, expression); |
171 |
} |
172 |
} |
173 |
// if it's not ALLOW or DENY, it's not a |
174 |
// proper rule, so we'll ignore it |
175 |
} |
176 |
} |
177 |
} |
178 |
} |
179 |
|
180 |
/** |
181 |
* Check to see if a string is permitted by the |
182 |
* ACL. Useful for testing, and non-Socket uses |
183 |
* of this class. |
184 |
* |
185 |
* @param address the string to check |
186 |
* @return whether the address was permitted by the ACL |
187 |
*/ |
188 |
public boolean check(String address) { |
189 |
for(int i=0; i < _acl.size(); i++) { |
190 |
ACLRule rule = (ACLRule) _acl.get(i); |
191 |
if(StringUtils.wildcardMatch(address, rule._expression)) { |
192 |
return rule._allow; |
193 |
} |
194 |
} |
195 |
return _defaultMode; |
196 |
} |
197 |
|
198 |
/** |
199 |
* Check to see if an InetAddress is permitted |
200 |
* by the ACL. Perfect for Socket uses of this |
201 |
* class. A rule will either be for a name, or |
202 |
* an IP address (this is determined in the add |
203 |
* method), and the appropriate comparison will |
204 |
* be performed. |
205 |
* |
206 |
* @param address the InetAddress to check |
207 |
* @return whether the InetAddress was permitted by the ACL |
208 |
*/ |
209 |
public boolean check(InetAddress address) { |
210 |
// gather the details first |
211 |
String hostname = address.getHostName(); |
212 |
String ip = address.getHostAddress(); |
213 |
short[] ipaddr = ipStringToShort(ip); |
214 |
// check each rule against this InetAddress |
215 |
for(int i=0; i < _acl.size(); i++) { |
216 |
ACLRule rule = (ACLRule) _acl.get(i); |
217 |
if(rule._iprule) { |
218 |
// if this is an IP rule do a short comparison |
219 |
// must specify the wildcarded rule first |
220 |
if(compareShorts(rule._ipaddr, ipaddr)) { |
221 |
return rule._allow; |
222 |
} |
223 |
} |
224 |
else { |
225 |
// if not do a full blown String comparsion |
226 |
if(StringUtils.wildcardMatch(hostname, rule._expression)) { |
227 |
return rule._allow; |
228 |
} |
229 |
} |
230 |
|
231 |
} |
232 |
// if we haven't matched a rule, return the default |
233 |
return _defaultMode; |
234 |
} |
235 |
|
236 |
/** |
237 |
* Clears the ACL and resets the default mode. |
238 |
*/ |
239 |
public void clear() { |
240 |
// just clear out our underlying ArrayList |
241 |
// containing our ACL objects |
242 |
_acl.clear(); |
243 |
// and reset the default mode to the default |
244 |
setDefaultMode(DEFMODE); |
245 |
} |
246 |
|
247 |
/** |
248 |
* Changes the default mode of the ACL. This is what |
249 |
* the check will return if it does not find an explict |
250 |
* rule to match against. |
251 |
* |
252 |
* @param defaultMode the new default mode |
253 |
*/ |
254 |
public void setDefaultMode(boolean defaultMode) { |
255 |
_defaultMode = defaultMode; |
256 |
} |
257 |
|
258 |
/** |
259 |
* Gives a String representation of this ACL. |
260 |
* |
261 |
* @return A String representation of this ACL. |
262 |
*/ |
263 |
public String toString() { |
264 |
StringBuffer acl = new StringBuffer(); |
265 |
// put in the i-scream toString code |
266 |
acl.append(FormatName.getName(_name, getClass().getName(), REVISION)); |
267 |
acl.append("{"); |
268 |
// put the value of each Rule in the result |
269 |
for(int i=0; i < _acl.size(); i++) { |
270 |
acl.append((ACLRule) _acl.get(i)); |
271 |
acl.append(","); |
272 |
} |
273 |
// put the default mode in the result |
274 |
if(_defaultMode) { |
275 |
acl.append("DEFAULT=ALLOW"); |
276 |
} |
277 |
else { |
278 |
acl.append("DEFAULT=DENY"); |
279 |
} |
280 |
acl.append("}"); |
281 |
return acl.toString(); |
282 |
} |
283 |
|
284 |
//---PRIVATE METHODS--- |
285 |
|
286 |
/** |
287 |
* Converts an IP address in String format into |
288 |
* a short array of length 4. Any wildcards, *, |
289 |
* found in the IP address are represented by |
290 |
* a -1. If the given String is not an IP address |
291 |
* null is returned instead. |
292 |
* |
293 |
* @param ip The IP address in String format |
294 |
* @return The IP address in a short[] |
295 |
*/ |
296 |
private short[] ipStringToShort(String ip) { |
297 |
// default to expecting it to be an IP |
298 |
// we will try to disprove this :) |
299 |
short[] ipaddr = {-1, -1, -1, -1}; |
300 |
int i = 0; |
301 |
String s = ""; |
302 |
// tokenize the String on fullstops, so we can break |
303 |
// up the quads of an IP (if it's an IP!) |
304 |
StringTokenizer st = new StringTokenizer(ip, "."); |
305 |
while(st.hasMoreTokens() && i++ < 4) { |
306 |
s = st.nextToken(); |
307 |
// if it's a wildcard, we'll skip to the next one |
308 |
// as no more checks are required |
309 |
if(s.equals("*")) { |
310 |
continue; |
311 |
} |
312 |
// attempt to parse it into a short |
313 |
try { |
314 |
short n = Short.parseShort(s); |
315 |
// if it's an int but outside of the |
316 |
// valid range, it can't be an IP |
317 |
if(n < 0 || n > 255) { |
318 |
// give up checking further |
319 |
return null; |
320 |
} |
321 |
ipaddr[i-1] = n; |
322 |
} |
323 |
// if it didn't parse as a short it can't be an IP |
324 |
catch (NumberFormatException e) { |
325 |
// give up checking further |
326 |
return null; |
327 |
} |
328 |
} |
329 |
// we've done 4 parts, so if there's any |
330 |
// more this can't be an IP |
331 |
if(st.hasMoreTokens()) { |
332 |
return null; |
333 |
} |
334 |
// if we've done less than 4, see if the last one |
335 |
// was a wildcard - if it isn't then it's not an IP |
336 |
// -- this allows 129.12.* |
337 |
if(i < 4 && !s.equals("*")) { |
338 |
return null; |
339 |
} |
340 |
// if we had one or less entries it can't be an IP |
341 |
// -- this disallows * matching as an IP due |
342 |
// to the rule above |
343 |
if(i <= 1) { |
344 |
return null; |
345 |
} |
346 |
return ipaddr; |
347 |
} |
348 |
|
349 |
/** |
350 |
* Compares two short arrays. The first array can contain a -1, |
351 |
* which will always match any value -- it's a wildcard. |
352 |
* They must be the same length to match. |
353 |
* |
354 |
* @param first The first array to compare (with -1 wildcard if required) |
355 |
* @param second The second array to compare |
356 |
* @return the result of the comparison |
357 |
*/ |
358 |
private boolean compareShorts(short[] first, short[] second) { |
359 |
if(first.length != second.length) { |
360 |
return false; |
361 |
} |
362 |
for(int i=0; i < first.length; i++) { |
363 |
if(first[i] == -1) { |
364 |
continue; |
365 |
} |
366 |
if(first[i] != second[i]) { |
367 |
return false; |
368 |
} |
369 |
} |
370 |
return true; |
371 |
} |
372 |
|
373 |
//---ACCESSOR/MUTATOR METHODS--- |
374 |
|
375 |
//---ATTRIBUTES--- |
376 |
|
377 |
/** |
378 |
* This is the friendly identifier of the |
379 |
* component this class is running in. |
380 |
* eg, a Filter may be called "filter1", |
381 |
* If this class does not have an owning |
382 |
* component, a name from the configuration |
383 |
* can be placed here. This name could also |
384 |
* be changed to null for utility classes. |
385 |
*/ |
386 |
private String _name = null; |
387 |
|
388 |
/** |
389 |
* The ACL is stored in this ArrayList. |
390 |
*/ |
391 |
private ArrayList _acl = new ArrayList(); |
392 |
|
393 |
/** |
394 |
* The default mode of this ACL. |
395 |
*/ |
396 |
private boolean _defaultMode = DEFMODE; |
397 |
|
398 |
//---STATIC ATTRIBUTES--- |
399 |
|
400 |
//---INNER CLASSES--- |
401 |
|
402 |
/** |
403 |
* Wrapper class for an ACL rule. |
404 |
*/ |
405 |
private class ACLRule implements Serializable { |
406 |
|
407 |
/** |
408 |
* Construct an ACL rule. |
409 |
* |
410 |
* @param allow whether this is an ALLOW or DENY rule |
411 |
* @param expression what this rule matches |
412 |
* @param ipaddr the IP address wildcard this rule matches if it's an IP rule |
413 |
* @param iprule whether this is an IP rule |
414 |
*/ |
415 |
private ACLRule(boolean allow, String expression, short[] ipaddr, boolean iprule) { |
416 |
_allow = allow; |
417 |
_expression = expression; |
418 |
_ipaddr = ipaddr; |
419 |
_iprule = iprule; |
420 |
} |
421 |
|
422 |
/** |
423 |
* Returns a String representation of this rule. |
424 |
* |
425 |
* @return A String representation of this rule. |
426 |
*/ |
427 |
public String toString() { |
428 |
if(_allow) { |
429 |
return _expression + "=ALLOW"; |
430 |
} |
431 |
else { |
432 |
return _expression + "=DENY"; |
433 |
} |
434 |
} |
435 |
|
436 |
/** |
437 |
* Whether this is an ALLOW or DENY rule. |
438 |
*/ |
439 |
private boolean _allow; |
440 |
|
441 |
/** |
442 |
* What this rule matches. |
443 |
*/ |
444 |
private String _expression; |
445 |
|
446 |
/** |
447 |
* The IP wildcard, only valid if this |
448 |
* is an IP rule. |
449 |
*/ |
450 |
private short[] _ipaddr; |
451 |
|
452 |
/** |
453 |
* Whether this is an IP rule. |
454 |
*/ |
455 |
private boolean _iprule; |
456 |
|
457 |
} |
458 |
|
459 |
} |