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