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 legal-notices/CDDLv1_0.txt 010 * or http://forgerock.org/license/CDDLv1.0.html. 011 * See the License for the specific language governing permissions 012 * and limitations under the License. 013 * 014 * When distributing Covered Code, include this CDDL HEADER in each 015 * file and include the License file at legal-notices/CDDLv1_0.txt. 016 * If applicable, add the following below this CDDL HEADER, with the 017 * fields enclosed by brackets "[]" replaced with your own identifying 018 * information: 019 * Portions Copyright [yyyy] [name of copyright owner] 020 * 021 * CDDL HEADER END 022 * 023 * 024 * Copyright 2008 Sun Microsystems, Inc. 025 * Portions Copyright 2014-2015 ForgeRock AS 026 */ 027 028 029package org.opends.server.authorization.dseecompat; 030import org.forgerock.i18n.LocalizableMessage; 031 032import static org.opends.messages.AccessControlMessages.*; 033import java.util.BitSet; 034import java.util.HashMap; 035import java.net.InetAddress; 036import java.net.UnknownHostException; 037import java.net.Inet6Address; 038 039/** 040 * A class representing a single IP address parsed from a IP bind rule 041 * expression. The class can be used to evaluate a remote clients IP address 042 * using the information parsed from the IP bind rule expression. 043 */ 044public class PatternIP { 045 046 /** 047 * Enumeration that represents if the pattern is IPv5 or 048 * IPv4. 049 */ 050 enum IPType { 051 IPv4, IPv6 052 } 053 054 /** The IP address type (v6 or v4). */ 055 private IPType ipType; 056 057 /** IPv4 sizes of addresses and prefixes. */ 058 private static int IN4ADDRSZ = 4; 059 private static int IPV4MAXPREFIX = 32; 060 061 /** IPv6 sizes of addresses and prefixes. */ 062 private static int IN6ADDRSZ = 16; 063 private static int IPV6MAXPREFIX = 128; 064 065 /** 066 Byte arrays used to match the remote IP address. The ruleAddrByte array 067 contains the bytes of the address from the ACI IP bind rule. The 068 rulePrefixBytes array contains the bytes of the cidr prefix or netmask 069 representation. 070 */ 071 private byte[] ruleAddrBytes, rulePrefixBytes; 072 073 /** 074 Bit set that holds the wild-card information of processed IPv4 addresses. 075 */ 076 private BitSet wildCardBitSet; 077 078 /** Hash map of valid netmask strings. Used in parsing netmask values. */ 079 private static HashMap<String,String> validNetMasks = new HashMap<>(); 080 081 /** Initialize valid netmask hash map. */ 082 static { 083 initNetMask( 084 "255.255.255.255", 085 "255.255.255.254", 086 "255.255.255.252", 087 "255.255.255.248", 088 "255.255.255.240", 089 "255.255.255.224", 090 "255.255.255.192", 091 "255.255.255.128", 092 "255.255.255.0", 093 "255.255.254.0", 094 "255.255.252.0", 095 "255.255.248.0", 096 "255.255.240.0", 097 "255.255.224.0", 098 "255.255.192.0", 099 "255.255.128.0", 100 "255.255.0.0", 101 "255.254.0.0", 102 "255.252.0.0", 103 "255.248.0.0", 104 "255.240.0.0", 105 "255.224.0.0", 106 "255.192.0.0", 107 "255.128.0.0", 108 "255.0.0.0", 109 "254.0.0.0", 110 "252.0.0.0", 111 "248.0.0.0", 112 "240.0.0.0", 113 "224.0.0.0", 114 "192.0.0.0", 115 "128.0.0.0", 116 "0.0.0.0" 117 ); 118 } 119 120 /** 121 * Load the valid netmask hash map with the 33 possible valid netmask 122 * strings. 123 * 124 * @param lines The strings representing the valid netmasks. 125 */ 126 private static void initNetMask(String... lines) { 127 for(String line : lines) { 128 validNetMasks.put(line, line); 129 } 130 } 131 132 /** 133 * Create a class that can be used to evaluate an IP address using the 134 * information decoded from the ACI IP bind rule expression. 135 * 136 * @param ipType The type of the ACI IP address (IPv4 or 6). 137 * @param ruleAddrBytes Byte array representing the ACI IP address. 138 * @param rulePrefixBytes Prefix byte array corresponding to the bits set 139 * by the cidr prefix or netmask. 140 * @param wildCardBitSet Bit set holding IPv4 wild-card information. 141 */ 142 private PatternIP(IPType ipType, byte[] ruleAddrBytes, 143 byte[] rulePrefixBytes, BitSet wildCardBitSet) { 144 this.ipType=ipType; 145 this.ruleAddrBytes=ruleAddrBytes; 146 this.rulePrefixBytes=rulePrefixBytes; 147 this.wildCardBitSet=wildCardBitSet; 148 } 149 150 /** 151 * Decode the provided address expression string and create a class that 152 * can be used to perform an evaluation of an IP address based on the 153 * decoded expression string information. 154 * 155 * @param expr The address expression string from the ACI IP bind rule. 156 * @return A class that can evaluate a remote clients IP address using the 157 * expression's information. 158 * @throws AciException If the address expression is invalid. 159 */ 160 public static 161 PatternIP decode(String expr) throws AciException { 162 IPType ipType=IPType.IPv4; 163 byte[] prefixBytes; 164 String addrStr; 165 if(expr.indexOf(':') != -1) { 166 ipType = IPType.IPv6; 167 } 168 if(expr.indexOf('/') != -1) { 169 String prefixStr=null; 170 String[] s = expr.split("[/]", -1); 171 if(s.length == 2) { 172 prefixStr=s[1]; 173 } 174 int prefix = getPrefixValue(ipType, s.length, expr, prefixStr); 175 prefixBytes=getPrefixBytes(prefix, ipType); 176 addrStr=s[0]; 177 } else if(expr.indexOf('+') != -1) { 178 String netMaskStr=null; 179 String[] s = expr.split("[+]", -1); 180 if(s.length == 2) { 181 netMaskStr=s[1]; 182 } 183 prefixBytes=getNetmaskBytes(netMaskStr, s.length, expr); 184 addrStr=s[0]; 185 } else { 186 int prefix = getPrefixValue(ipType, 1, expr, null); 187 prefixBytes=getPrefixBytes(prefix, ipType); 188 addrStr=expr; 189 } 190 // Set the bit set size fo IN6ADDRSZ even though only 4 positions are used. 191 BitSet wildCardBitSet = new BitSet(IN6ADDRSZ); 192 byte[] addrBytes; 193 if(ipType == IPType.IPv4) { 194 addrBytes = procIPv4Addr(addrStr, wildCardBitSet, expr); 195 } else { 196 addrBytes=procIPv6Addr(addrStr, expr); 197 //The IPv6 address processed above might be a IPv4-compatible 198 //address, in which case only 4 bytes will be returned in the 199 //address byte array. Ignore any IPv6 prefix. 200 if(addrBytes.length == IN4ADDRSZ) { 201 ipType=IPType.IPv4; 202 prefixBytes=getPrefixBytes(IPV4MAXPREFIX, ipType); 203 } 204 } 205 return new PatternIP(ipType, addrBytes, prefixBytes, wildCardBitSet); 206 } 207 208 /** 209 * Process the IP address prefix part of the expression. Handles if there is 210 * no prefix in the expression. 211 * 212 * @param ipType The type of the expression, either IPv6 or IPv4. 213 * @param numParts The number of parts in the IP address expression. 214 * 1 if there isn't a prefix, and 2 if there is. Anything 215 * else is an error (i.e., 254.244.123.234/7/6). 216 * @param expr The original expression from the bind rule. 217 * @param prefixStr The string representation of the prefix part of the 218 * IP address. 219 * @return An integer value determined from the prefix string. 220 * @throws AciException If the prefix string is invalid. 221 */ 222 private static int 223 getPrefixValue(IPType ipType, int numParts, String expr, String prefixStr) 224 throws AciException { 225 226 int prefix = IPV4MAXPREFIX; 227 int maxPrefix= IPV4MAXPREFIX; 228 if(ipType == IPType.IPv6) { 229 prefix= IPV6MAXPREFIX; 230 maxPrefix=IPV6MAXPREFIX; 231 } 232 try { 233 //Can only have one prefix value and one address string. 234 if(numParts < 1 || numParts > 2 ) { 235 LocalizableMessage message = 236 WARN_ACI_SYNTAX_INVALID_PREFIX_FORMAT.get(expr); 237 throw new AciException(message); 238 } 239 if(prefixStr != null) { 240 prefix = Integer.parseInt(prefixStr); 241 } 242 //Must be between 0 to maxprefix. 243 if(prefix < 0 || prefix > maxPrefix) { 244 LocalizableMessage message = 245 WARN_ACI_SYNTAX_INVALID_PREFIX_VALUE.get(expr); 246 throw new AciException(message); 247 } 248 } catch(NumberFormatException nfex) { 249 LocalizableMessage msg = WARN_ACI_SYNTAX_PREFIX_NOT_NUMERIC.get(expr); 250 throw new AciException(msg); 251 } 252 return prefix; 253 } 254 255 /** 256 * Determine the prefix bit mask based on the provided prefix value. Handles 257 * both IPv4 and IPv6 prefix values. 258 * 259 * @param prefix The value of the prefix parsed from the address 260 * expression. 261 * @param ipType The type of the prefix, either IPv6 or IPv4. 262 * @return A byte array representing the prefix bit mask used to match 263 * IP addresses. 264 */ 265 private static byte[] getPrefixBytes(int prefix, IPType ipType) { 266 int i; 267 int maxSize=IN4ADDRSZ; 268 if(ipType==IPType.IPv6) { 269 maxSize= IN6ADDRSZ; 270 } 271 byte[] prefixBytes=new byte[maxSize]; 272 for(i=0;prefix > 8 ; i++) { 273 prefixBytes[i] = (byte) 0xff; 274 prefix -= 8; 275 } 276 prefixBytes[i] = (byte) (0xff << 8 - prefix); 277 return prefixBytes; 278 } 279 280 /** 281 * Process the specified netmask string. Only pertains to IPv4 address 282 * expressions. 283 * 284 * @param netmaskStr String representation of the netmask parsed from the 285 * address expression. 286 * @param numParts The number of parts in the IP address expression. 287 * 1 if there isn't a netmask, and 2 if there is. Anything 288 * else is an error (i.e., 254.244.123.234++255.255.255.0). 289 * @param expr The original expression from the bind rule. 290 * @return A byte array representing the netmask bit mask used to match 291 * IP addresses. 292 * @throws AciException If the netmask string is invalid. 293 */ 294 private static 295 byte[] getNetmaskBytes(String netmaskStr, int numParts, String expr) 296 throws AciException { 297 byte[] netmaskBytes=new byte[IN4ADDRSZ]; 298 //Look up the string in the valid netmask hash table. If it isn't 299 //there it is an error. 300 if(!validNetMasks.containsKey(netmaskStr)) { 301 LocalizableMessage message = WARN_ACI_SYNTAX_INVALID_NETMASK.get(expr); 302 throw new AciException(message); 303 } 304 //Can only have one netmask value and one address string. 305 if(numParts < 1 || numParts > 2 ) { 306 LocalizableMessage message = WARN_ACI_SYNTAX_INVALID_NETMASK_FORMAT.get(expr); 307 throw new AciException(message); 308 } 309 String[] s = netmaskStr.split("\\.", -1); 310 try { 311 for(int i=0; i < IN4ADDRSZ; i++) { 312 String quad=s[i].trim(); 313 long val=Integer.parseInt(quad); 314 netmaskBytes[i] = (byte) (val & 0xff); 315 } 316 } catch (NumberFormatException nfex) { 317 LocalizableMessage message = WARN_ACI_SYNTAX_IPV4_NOT_NUMERIC.get(expr); 318 throw new AciException(message); 319 } 320 return netmaskBytes; 321 } 322 323 /** 324 * Process the provided IPv4 address string parsed from the IP bind rule 325 * address expression. It returns a byte array corresponding to the 326 * address string. The specified bit set represents wild-card characters 327 * '*' found in the string. 328 * 329 * @param addrStr A string representing an IPv4 address. 330 * @param wildCardBitSet A bit set used to save wild-card information. 331 * @param expr The original expression from the IP bind rule. 332 * @return A address byte array that can be used along with the prefix bit 333 * mask to evaluate an IPv4 address. 334 * 335 * @throws AciException If the address string is not a valid IPv4 address 336 * string. 337 */ 338 private static byte[] 339 procIPv4Addr(String addrStr, BitSet wildCardBitSet, String expr) 340 throws AciException { 341 byte[] addrBytes=new byte[IN4ADDRSZ]; 342 String[] s = addrStr.split("\\.", -1); 343 try { 344 if(s.length != IN4ADDRSZ) { 345 LocalizableMessage message = WARN_ACI_SYNTAX_INVALID_IPV4_FORMAT.get(expr); 346 throw new AciException(message); 347 } 348 for(int i=0; i < IN4ADDRSZ; i++) { 349 String quad=s[i].trim(); 350 if(quad.equals("*")) { 351 wildCardBitSet.set(i) ; 352 } 353 else { 354 long val=Integer.parseInt(quad); 355 //must be between 0-255 356 if(val < 0 || val > 0xff) { 357 LocalizableMessage message = 358 WARN_ACI_SYNTAX_INVALID_IPV4_VALUE.get(expr); 359 throw new AciException(message); 360 } 361 addrBytes[i] = (byte) (val & 0xff); 362 } 363 } 364 } catch (NumberFormatException nfex) { 365 LocalizableMessage message = WARN_ACI_SYNTAX_IPV4_NOT_NUMERIC.get(expr); 366 throw new AciException(message); 367 } 368 return addrBytes; 369 } 370 371 /** 372 * Process the provided IPv6 address string parsed from the IP bind rule 373 * IP expression. It returns a byte array corresponding to the 374 * address string. Wild-cards are not allowed in IPv6 addresses. 375 * 376 * @param addrStr A string representing an IPv6 address. 377 * @param expr The original expression from the IP bind rule. 378 * @return A address byte array that can be used along with the prefix bit 379 * mask to evaluate an IPv6 address. 380 * @throws AciException If the address string is not a valid IPv6 address 381 * string. 382 */ 383 private static byte[] 384 procIPv6Addr(String addrStr, String expr) throws AciException { 385 if(addrStr.indexOf('*') > -1) { 386 LocalizableMessage message = WARN_ACI_SYNTAX_IPV6_WILDCARD_INVALID.get(expr); 387 throw new AciException(message); 388 } 389 byte[] addrBytes; 390 try { 391 addrBytes=InetAddress.getByName(addrStr).getAddress(); 392 } catch (UnknownHostException ex) { 393 LocalizableMessage message = 394 WARN_ACI_SYNTAX_INVALID_IPV6_FORMAT.get(expr, ex.getMessage()); 395 throw new AciException(message); 396 } 397 return addrBytes; 398 } 399 400 /** 401 * Evaluate the provided IP address against the information processed during 402 * the IP bind rule expression decode. 403 * 404 * @param remoteAddr A IP address to evaluate. 405 * @return An enumeration representing the result of the evaluation. 406 */ 407 public EnumEvalResult evaluate(InetAddress remoteAddr) { 408 EnumEvalResult matched=EnumEvalResult.FALSE; 409 IPType ipType=IPType.IPv4; 410 byte[] addressBytes=remoteAddr.getAddress(); 411 if(remoteAddr instanceof Inet6Address) { 412 ipType=IPType.IPv6; 413 Inet6Address addr6 = (Inet6Address) remoteAddr; 414 addressBytes= addr6.getAddress(); 415 if(addr6.isIPv4CompatibleAddress()) { 416 ipType=IPType.IPv4; 417 } 418 } 419 if(ipType != this.ipType) { 420 return EnumEvalResult.FALSE; 421 } 422 if(matchAddress(addressBytes)) { 423 matched=EnumEvalResult.TRUE; 424 } 425 return matched; 426 } 427 428 /** 429 * Attempt to match the address byte array using the prefix bit mask array 430 * and the address byte array processed in the decode. Wild-cards take 431 * priority over the mask. 432 * 433 * @param addrBytes IP address byte array. 434 * @return True if the remote address matches based on the information 435 * parsed from the IP bind rule expression. 436 */ 437 private boolean matchAddress(byte[] addrBytes) { 438 if(wildCardBitSet.cardinality() == IN4ADDRSZ) { 439 return true; 440 } 441 for(int i=0;i <rulePrefixBytes.length; i++) { 442 if (!wildCardBitSet.get(i) 443 && (ruleAddrBytes[i] & rulePrefixBytes[i]) != 444 (addrBytes[i] & rulePrefixBytes[i])) 445 { 446 return false; 447 } 448 } 449 return true; 450 } 451}