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