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