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