001 /*
002 * CDDL HEADER START
003 *
004 * The contents of this file are subject to the terms of the
005 * Common Development and Distribution License, Version 1.0 only
006 * (the "License"). You may not use this file except in compliance
007 * with the License.
008 *
009 * You can obtain a copy of the license at
010 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
011 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
012 * See the License for the specific language governing permissions
013 * and limitations under the License.
014 *
015 * When distributing Covered Code, include this CDDL HEADER in each
016 * file and include the License file at
017 * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
018 * add the following below this CDDL HEADER, with the fields enclosed
019 * by brackets "[]" replaced with your own identifying information:
020 * Portions Copyright [yyyy] [name of copyright owner]
021 *
022 * CDDL HEADER END
023 *
024 *
025 * Copyright 2006-2008 Sun Microsystems, Inc.
026 */
027 package org.opends.server.types;
028 import org.opends.messages.Message;
029
030 import org.opends.server.config.ConfigException;
031 import static org.opends.messages.ProtocolMessages.*;
032 import java.util.BitSet;
033 import java.net.Inet6Address;
034 import java.net.InetAddress;
035 import java.net.UnknownHostException;
036
037 /**
038 * This class defines an address mask, which can be used to perform
039 * efficient comparisons against IP addresses to determine whether a
040 * particular IP address is in a given range.
041 */
042 @org.opends.server.types.PublicAPI(
043 stability=org.opends.server.types.StabilityLevel.VOLATILE,
044 mayInstantiate=true,
045 mayExtend=false,
046 mayInvoke=true)
047 public final class AddressMask
048 {
049 /**
050 * Types of rules we have.
051 *
052 * IPv4 - ipv4 rule
053 * IPv6 - ipv6 rule (begin with '[' or contains an ':').
054 * HOST - hostname match (foo.sun.com)
055 * HOSTPATTERN - host pattern match (begin with '.')
056 * ALLWILDCARD - *.*.*.* (first HOST is applied then ipv4)
057 *
058 */
059
060 enum RuleType
061 {
062 IPv4, IPv6, HOSTPATTERN, ALLWILDCARD, HOST
063 }
064
065 // Type of rule determined
066 private RuleType ruleType;
067
068 // IPv4 values for number of bytes and max CIDR prefix
069 /**
070 * IPv4 address size.
071 */
072 private static final int IN4ADDRSZ = 4;
073 private static final int IPV4MAXPREFIX = 32;
074
075 // IPv6 values for number of bytes and max CIDR prefix
076 private static final int IN6ADDRSZ = 16;
077 private static final int IPV6MAXPREFIX = 128;
078
079 //Holds binary representations of rule and mask respectively.
080 private byte[] ruleMask, prefixMask;
081
082 //Bit array that holds wildcard info for above binary arrays.
083 private final BitSet wildCard = new BitSet();
084
085 //Array that holds each component of a hostname.
086 private String[] hostName;
087
088 //Holds a hostname pattern (ie, rule that begins with '.');'
089 private String hostPattern;
090
091 //Holds string passed into the constructor.
092 private String ruleString;
093
094 /**
095 * Address mask constructor.
096 * @param rule The rule string to process.
097 * @throws ConfigException If the rule string is not valid.
098 */
099 private AddressMask( String rule)
100 throws ConfigException
101 {
102 determineRuleType(rule);
103 switch (ruleType)
104 {
105 case IPv6:
106 processIPv6(rule);
107 break;
108
109 case IPv4:
110 processIpv4(rule);
111 break;
112
113 case HOST:
114 processHost(rule);
115 break;
116
117 case HOSTPATTERN:
118 processHostPattern(rule);
119 break;
120
121 case ALLWILDCARD:
122 processAllWilds(rule);
123 }
124 ruleString=rule;
125 }
126
127 /**
128 * Try to determine what type of rule string this is. See
129 * RuleType above for valid types.
130 * @param ruleString The rule string to be examined.
131 * @throws ConfigException If the rule type cannot be
132 * determined from the rule string.
133 */
134 private void determineRuleType(String ruleString)
135 throws ConfigException
136 {
137
138 //Rule ending with '.' is invalid'
139 if(ruleString.endsWith("."))
140 {
141 Message message =
142 ERR_ADDRESSMASK_FORMAT_DECODE_ERROR.get();
143 throw new ConfigException(message);
144 }
145 else if(ruleString.startsWith("."))
146 {
147 ruleType=RuleType.HOSTPATTERN;
148 }
149 else if(ruleString.startsWith("[") ||
150 (ruleString.indexOf(':') != -1))
151 {
152 ruleType=RuleType.IPv6;
153 }
154 else
155 {
156 int wildCount=0;
157 String[] s = ruleString.split("\\.", -1);
158 //Try to figure out how many wildcards and if the rule is
159 // hostname (can't begin with digit) or ipv4 address.
160 //Default to IPv4 ruletype.
161 ruleType=RuleType.HOST;
162 for (String value : s) {
163 if (value.equals("*")) {
164 wildCount++;
165 continue;
166 }
167 //Looks like an ipv4 address
168 if (Character.isDigit(value.charAt(0))) {
169 ruleType = RuleType.IPv4;
170 break;
171 }
172 }
173 //All wildcards (*.*.*.*)
174 if(wildCount == s.length)
175 {
176 ruleType=RuleType.ALLWILDCARD;
177 }
178 }
179 }
180
181 /**
182 * The rule string is an IPv4 rule. Build both the prefix
183 * mask array and rule mask from the string.
184 * @param rule The rule string containing the IPv4 rule.
185 * @throws ConfigException If the rule string is not a valid
186 * IPv4 rule.
187 */
188 private void processIpv4(String rule)
189 throws ConfigException {
190 String[] s = rule.split("/", -1);
191 this.ruleMask=new byte[IN4ADDRSZ];
192 this.prefixMask=new byte[IN4ADDRSZ];
193 prefixMask(processPrefix(s,IPV4MAXPREFIX));
194 processIPv4Subnet((s.length == 0) ? rule : s[0]);
195 }
196
197 /**
198 * The rule string is all wildcards. Set both address wildcard
199 * bitmask and hostname wildcard array.
200 * @param rule The rule string containing all wildcards.
201 */
202 private void processAllWilds(String rule)
203 {
204 String s[]=rule.split("\\.", -1);
205 if(s.length == IN4ADDRSZ)
206 {
207 for(int i=0;i<IN4ADDRSZ;i++)
208 wildCard.set(i);
209 }
210 hostName=rule.split("\\.", -1);
211 }
212
213 /**
214 * Examine the rule string of a host pattern and set the
215 * host pattern from the rule.
216 * @param rule The rule string to examine.
217 * @throws ConfigException If the rule string is not a valid
218 * host pattern rule.
219 */
220 private void processHostPattern(String rule)
221 throws ConfigException
222 {
223 //quick check for invalid chars like " "
224 String s[]=rule.split("^[0-9a-zA-z-.]+");
225 if(s.length > 0)
226 {
227 Message message =
228 ERR_ADDRESSMASK_FORMAT_DECODE_ERROR.get();
229 throw new ConfigException(message);
230 }
231 hostPattern=rule;
232 }
233
234 /**
235 * Examine rule string and build a hostname string array
236 * of its parts.
237 * @param rule The rule string.
238 * @throws ConfigException If the rule string is not a valid
239 * host name.
240 */
241 private void processHost(String rule)
242 throws ConfigException
243 {
244 //Note that '*' is valid in host rule
245 String s[]=rule.split("^[0-9a-zA-z-.*]+");
246 if(s.length > 0)
247 {
248 Message message =
249 ERR_ADDRESSMASK_FORMAT_DECODE_ERROR.get();
250 throw new ConfigException(message);
251 }
252 hostName=rule.split("\\.", -1);
253 }
254
255 /**
256 * Build the prefix mask of prefix len bits set in the array.
257 * @param prefix The len of the prefix to use.
258 */
259 private void prefixMask(int prefix)
260 {
261 int i;
262 for( i=0;prefix > 8 ; i++)
263 {
264 this.prefixMask[i] = (byte) 0xff;
265 prefix -= 8;
266 }
267 this.prefixMask[i] = (byte) ((0xff) << (8 - prefix));
268 }
269
270 /**
271 * Examine the subnet part of a rule string and build a
272 * byte array representation of it.
273 * @param subnet The subnet string part of the rule.
274 * @throws ConfigException If the subnet string is not a valid
275 * IPv4 subnet string.
276 */
277 private void processIPv4Subnet(String subnet)
278 throws ConfigException {
279 String[] s = subnet.split("\\.", -1);
280 try {
281 //Make sure we have four parts
282 if(s.length != IN4ADDRSZ) {
283 Message message =
284 ERR_ADDRESSMASK_FORMAT_DECODE_ERROR.get();
285 throw new ConfigException(message);
286 }
287 for(int i=0; i < IN4ADDRSZ; i++)
288 {
289 String quad=s[i].trim();
290 if(quad.equals("*"))
291 wildCard.set(i) ; //see wildcard mark bitset
292 else
293 {
294 long val=Integer.parseInt(quad);
295 //must be between 0-255
296 if((val < 0) || (val > 0xff))
297 {
298 Message message =
299 ERR_ADDRESSMASK_FORMAT_DECODE_ERROR.get();
300 throw new ConfigException(message);
301 }
302 ruleMask[i] = (byte) (val & 0xff);
303 }
304 }
305 } catch (NumberFormatException nfex)
306 {
307 Message message =
308 ERR_ADDRESSMASK_FORMAT_DECODE_ERROR.get();
309 throw new ConfigException(message);
310 }
311 }
312
313 /**
314 * Examine rule string for correct prefix usage.
315 * @param s The string array with rule string add and prefix
316 * strings.
317 * @param maxPrefix The max value the prefix can be.
318 * @return The prefix integer value.
319 * @throws ConfigException If the string array and prefix
320 * are not valid.
321 */
322 private int processPrefix(String[] s, int maxPrefix)
323 throws ConfigException {
324 int prefix=maxPrefix;
325 try {
326 //can only have one prefix value and a subnet string
327 if((s.length < 1) || (s.length > 2) )
328 {
329 Message message =
330 ERR_ADDRESSMASK_FORMAT_DECODE_ERROR.get();
331 throw new ConfigException(message);
332 }
333 else if(s.length == 2)
334 {
335 //can't have wildcard with a prefix
336 if(s[0].indexOf('*') > -1)
337 {
338 Message message =
339 ERR_ADDRESSMASK_WILDCARD_DECODE_ERROR.get();
340 throw new ConfigException(message);
341 }
342 prefix = Integer.parseInt(s[1]);
343 }
344 //must be between 0-maxprefix
345 if((prefix < 0) || (prefix > maxPrefix))
346 {
347 Message message =
348 ERR_ADDRESSMASK_PREFIX_DECODE_ERROR.get();
349 throw new ConfigException(message);
350 }
351 }
352 catch(NumberFormatException nfex)
353 {
354 Message msg = ERR_ADDRESSMASK_FORMAT_DECODE_ERROR.get();
355 throw new ConfigException(msg);
356 }
357 return prefix;
358 }
359
360
361 /**
362 * Decodes the provided string as an address mask.
363 *
364 * @param maskString The string to decode as an address mask.
365 *
366 * @return AddressMask The address mask decoded from the
367 * provided string.
368 *
369 * @throws ConfigException If the provided string cannot be
370 * decoded as an address mask.
371 */
372
373
374 public static AddressMask decode(String maskString)
375 throws ConfigException {
376 return new AddressMask(maskString);
377 }
378
379 /**
380 * Indicates whether provided address or hostname matches one of
381 * the address masks in the provided array.
382 *
383 * @param remoteAddr The remote address byte array.
384 * @param remoteName The remote host name string.
385 * @param masks An array of address masks to check.
386 * @return <CODE>true</CODE> if the provided address or hostname
387 * does match one of the given address masks, or
388 * <CODE>false</CODE> if it does not.
389 */
390 public static boolean maskListContains(byte[] remoteAddr,
391 String remoteName,
392 AddressMask[] masks)
393 {
394 for (AddressMask mask : masks) {
395 if(mask.match(remoteAddr, remoteName))
396 return true;
397 }
398 return false;
399 }
400
401 /**
402 * Retrieves a string representation of this address mask.
403 *
404 * @return A string representation of this address mask.
405 */
406 public String toString()
407 {
408 return ruleString;
409 }
410
411 /**
412 * Main match function that determines which rule-type match
413 * function to use.
414 * @param remoteAddr The remote client address byte array.
415 * @param remoteName The remote client host name.
416 * @return <CODE>true</CODE>if one of the match functions found
417 * a match or <CODE>false</CODE>if not.
418 */
419 private boolean match(byte[] remoteAddr, String remoteName)
420 {
421 boolean ret=false;
422
423 switch(ruleType) {
424 case IPv6:
425 case IPv4:
426 //this Address mask is an IPv4 rule
427 ret=matchAddress(remoteAddr);
428 break;
429
430 case HOST:
431 // HOST rule use hostname
432 ret=matchHostName(remoteName);
433 break;
434
435 case HOSTPATTERN:
436 //HOSTPATTERN rule
437 ret=matchPattern(remoteName);
438 break;
439
440 case ALLWILDCARD:
441 //first try ipv4 addr match, then hostname
442 ret=matchAddress(remoteAddr);
443 if(!ret)
444 ret=matchHostName(remoteName);
445 break;
446 }
447 return ret;
448 }
449
450 /**
451 * Try to match remote host name string against the pattern rule.
452 * @param remoteHostName The remote client host name.
453 * @return <CODE>true</CODE>if the remote host name matches or
454 * <CODE>false</CODE>if not.
455 */
456 private boolean matchPattern(String remoteHostName) {
457 int len=remoteHostName.length() - hostPattern.length();
458 return len > 0 && remoteHostName.regionMatches(true, len,
459 hostPattern, 0, hostPattern.length());
460 }
461
462 /**
463 * Try to match remote client host name against rule host name.
464 * @param remoteHostName The remote host name string.
465 * @return <CODE>true</CODE>if the remote client host name matches
466 * <CODE>false</CODE> if it does not.
467 */
468 private boolean matchHostName(String remoteHostName) {
469 String[] s = remoteHostName.split("\\.", -1);
470 if(s.length != hostName.length)
471 return false;
472 if(ruleType == RuleType.ALLWILDCARD)
473 return true;
474 for(int i=0;i<s.length;i++)
475 {
476 if(!hostName[i].equals("*")) //skip if wildcard
477 {
478 if(!s[i].equalsIgnoreCase(hostName[i]))
479 return false;
480 }
481 }
482 return true;
483 }
484
485
486 /**
487 * Try to match remote client address using prefix mask and
488 * rule mask.
489 * @param remoteMask The byte array with remote client address.
490 * @return <CODE>true</CODE> if remote client address matches or
491 * <CODE>false</CODE>if not.
492 */
493 private boolean matchAddress(byte[] remoteMask)
494 {
495 if(prefixMask== null)
496 return false;
497 if(remoteMask.length != prefixMask.length)
498 return false;
499 if(ruleType == RuleType.ALLWILDCARD)
500 return true;
501 for(int i=0;i < prefixMask.length; i++)
502 {
503 if(!wildCard.get(i))
504 {
505 if((ruleMask[i] & prefixMask[i]) !=
506 (remoteMask[i] & prefixMask[i]))
507 return false;
508 }
509 }
510 return true;
511 }
512
513 /**
514 * The rule string is an IPv6 rule. Build both the prefix
515 * mask array and rule mask from the string.
516 *
517 * @param rule The rule string containing the IPv6 rule.
518 * @throws ConfigException If the rule string is not a valid
519 * IPv6 rule.
520 */
521 private void processIPv6(String rule) throws ConfigException {
522 String[] s = rule.split("/", -1);
523 InetAddress addr;
524 try {
525 addr = InetAddress.getByName(s[0]);
526 } catch (UnknownHostException ex) {
527 Message message =
528 ERR_ADDRESSMASK_FORMAT_DECODE_ERROR.get();
529 throw new ConfigException(message);
530 }
531 if(addr instanceof Inet6Address) {
532 this.ruleType=RuleType.IPv6;
533 Inet6Address addr6 = (Inet6Address) addr;
534 this.ruleMask=addr6.getAddress();
535 this.prefixMask=new byte[IN6ADDRSZ];
536 prefixMask(processPrefix(s,IPV6MAXPREFIX));
537 } else {
538 //The address might be an IPv4-compat address.
539 //Throw an error if the rule has a prefix.
540 if(s.length == 2) {
541 Message message =
542 ERR_ADDRESSMASK_FORMAT_DECODE_ERROR.get();
543 throw new ConfigException(message);
544 }
545 this.ruleMask=addr.getAddress();
546 this.ruleType=RuleType.IPv4;
547 this.prefixMask=new byte[IN4ADDRSZ];
548 prefixMask(processPrefix(s,IPV4MAXPREFIX));
549 }
550 }
551 }
552
553