001 /**************************************************************** 002 * Licensed to the Apache Software Foundation (ASF) under one * 003 * or more contributor license agreements. See the NOTICE file * 004 * distributed with this work for additional information * 005 * regarding copyright ownership. The ASF licenses this file * 006 * to you under the Apache License, Version 2.0 (the * 007 * "License"); you may not use this file except in compliance * 008 * with the License. You may obtain a copy of the License at * 009 * * 010 * http://www.apache.org/licenses/LICENSE-2.0 * 011 * * 012 * Unless required by applicable law or agreed to in writing, * 013 * software distributed under the License is distributed on an * 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * 015 * KIND, either express or implied. See the License for the * 016 * specific language governing permissions and limitations * 017 * under the License. * 018 ****************************************************************/ 019 020 package org.apache.james.jspf.core; 021 022 import java.net.UnknownHostException; 023 024 import org.apache.james.jspf.core.exceptions.PermErrorException; 025 import org.xbill.DNS.Address; 026 027 public class IPAddr { 028 029 // Default IP4 030 031 private static final int MASK8 = 255; 032 033 private static final int MASK16 = 65535; 034 035 private int[] address = new int[4]; 036 037 private int[] mask = new int[4]; 038 039 private int maskLength = 32; 040 041 private int ipLength = 4; 042 043 private int ipRun = 4; 044 045 private String ipJoiner = "."; 046 047 private static String ipv4MappedRegex = "::FFFF:[1-9][0-9]{0,2}\\.[1-9][0-9]{0,2}\\.[1-9][0-9]{0,2}\\.[1-9][0-9]{0,2}"; 048 049 // Allow factory creates only 050 private IPAddr() { 051 052 } 053 054 /** 055 * Get ipAddress for the given String and netmask 056 * 057 * @param netAddress 058 * The ipAddress given as String 059 * @param maskLength 060 * The netmask 061 * @return IpAddress AAn Arraylist which contains all ipAddresses 062 * @throws PermErrorException 063 * on error 064 */ 065 public static IPAddr getAddress(String netAddress, int maskLength) 066 throws PermErrorException { 067 IPAddr returnAddress = new IPAddr(); 068 returnAddress.stringToInternal(netAddress); 069 returnAddress.setMask(maskLength); 070 return returnAddress; 071 } 072 073 /** 074 * 075 * @see #getAddress(String, int) 076 */ 077 public static IPAddr getAddress(String netAddress) 078 throws PermErrorException { 079 IPAddr returnAddress = new IPAddr(); 080 returnAddress.stringToInternal(netAddress); 081 returnAddress.setMask(returnAddress.maskLength); 082 return returnAddress; 083 } 084 085 /** 086 * Check if a the Object is instance of this class 087 * 088 * @param data 089 * The object to check 090 * @return true or false 091 */ 092 public static boolean isIPAddr(String data) { 093 try { 094 getAddress(data); 095 return true; 096 } catch (PermErrorException e) { 097 return false; 098 } 099 } 100 101 /** 102 * Set default values for ipv6 103 * 104 */ 105 private void setIP6Defaults() { 106 ipLength = 16; 107 ipJoiner = ":"; 108 address = new int[8]; 109 mask = new int[8]; 110 ipRun = 8; 111 } 112 113 /** 114 * create series of 16 bit masks for each ip block 115 * 116 * @param maskLength 117 * The netmask 118 */ 119 public void setMask(int maskLength) { 120 int startMask; 121 int shift; 122 int maskSize; 123 124 this.maskLength = maskLength; 125 if (ipLength == 4) { 126 if (!((maskLength > -1) && (maskLength < 33))) { 127 maskLength = 32; 128 } 129 maskSize = 8; 130 startMask = (maskLength - 1) / maskSize; 131 } else { 132 if (!((maskLength > -1) && (maskLength < 129))) { 133 maskLength = 128; 134 } 135 maskSize = 16; 136 startMask = (maskLength - 1) / maskSize; 137 } 138 139 for (int i = 0; i < ipRun; i++) { 140 // full mask 141 if (i < startMask) { 142 mask[i] = MASK16; 143 // variable mask 144 } else if (i == startMask) { 145 shift = ((i + 1) * maskSize) - maskLength; 146 mask[i] = (MASK16 << shift) & MASK16; 147 // no mask 148 } else { 149 mask[i] = 0; 150 } 151 } 152 } 153 154 /** 155 * Strip the last char of a string when it ends with a dot 156 * 157 * @param data 158 * The String where the dot should removed 159 * @return modified The Given String with last char stripped 160 */ 161 public static String stripDot(String data) { 162 163 data = data.trim(); 164 165 if (data.endsWith(".")) { 166 return data.substring(0, data.length() - 1); 167 } else { 168 return data; 169 } 170 171 } 172 173 /** 174 * Convert ipAddress to a byte Array which represent the ipAddress 175 * 176 * @param netAddress 177 * The ipAddress we should convert 178 * @throws PermErrorException 179 * on error 180 */ 181 private void stringToInternal(String netAddress) throws PermErrorException { 182 netAddress = stripDot(netAddress); 183 184 try { 185 byte[] bytes = Inet6Util.createByteArrayFromIPAddressString(netAddress); 186 187 if (bytes.length == 4) { 188 for (int i = 0; i < bytes.length; i++) { 189 address[i] = bytes[i]; 190 } 191 } else if (bytes.length == 16) { 192 setIP6Defaults(); 193 for (int i = 0; i < bytes.length / 2; i++) { 194 address[i] = unsigned(bytes[i * 2]) * 256 195 + unsigned(bytes[i * 2 + 1]); 196 } 197 } else { 198 throw new PermErrorException("Not a valid address: " + netAddress); 199 } 200 } catch (NumberFormatException e) { 201 throw new PermErrorException("Not a valid address: " + netAddress); 202 } 203 } 204 205 /** 206 * Return the Hexdecimal representation of the given long value 207 * 208 * @param data The value to retrieve the Hexdecimal for 209 * @return The Hexdecimal representation of the given value 210 */ 211 private String getHex(long data) { 212 StringBuffer fullHex = new StringBuffer(); 213 fullHex.append("0000" + Long.toHexString(data).toUpperCase()); 214 fullHex = fullHex.delete(0, fullHex.length() - 4); 215 return fullHex.toString(); 216 } 217 218 /** 219 * @see #getInAddress(String) 220 */ 221 public String getIPAddress() { 222 return getIPAddress(address); 223 } 224 225 /** 226 * Get ip Address from given int Array 227 * 228 * @param addressData 229 * The int Array 230 * @return ipAddress The ipAddress 231 */ 232 private String getIPAddress(int[] addressData) { 233 StringBuffer createAddress = new StringBuffer(); 234 int[] workingAddress; 235 236 // convert internal address to 8 bit 237 if (ipLength == 4) { 238 workingAddress = get8BitAddress(addressData); 239 // create IP string 240 createAddress.append(workingAddress[0]); 241 for (int i = 1; i < ipRun; i++) { 242 createAddress.append(ipJoiner + workingAddress[i]); 243 } 244 // leave internal address as 16 bit 245 } else { 246 workingAddress = addressData; 247 // create IP string 248 createAddress.append(getHex(workingAddress[0])); 249 for (int i = 1; i < ipRun; i++) { 250 createAddress.append(ipJoiner + getHex(workingAddress[i])); 251 } 252 } 253 254 return createAddress.toString(); 255 } 256 257 /** 258 * 259 * @see #getIPAddress(int[]) 260 */ 261 public String getMaskedIPAddress() { 262 return getIPAddress(maskedAddress(address, mask)); 263 } 264 265 /** 266 * Return the NibbleFormat of the IPAddr 267 * 268 * @return ipAddress The ipAddress in nibbleFormat 269 */ 270 public String getNibbleFormat() { 271 return getNibbleFormat(address); 272 } 273 274 private String getNibbleFormat(int[] address) { 275 StringBuffer sb = new StringBuffer(); 276 int[] ip = address; 277 for (int i = 0; i < ip.length; i++) { 278 String hex = getHex(ip[i]); 279 for (int j = 0; j < hex.length(); j++) { 280 sb.append(hex.charAt(j)); 281 if (i != ip.length -1 || j != hex.length() -1) { 282 sb.append("."); 283 } 284 } 285 } 286 return sb.toString(); 287 } 288 289 /** 290 * Get reverse ipAddress 291 * 292 * @return reverse ipAddress 293 */ 294 public String getReverseIP() { 295 if(isIPV6(getIPAddress())) { 296 StringBuffer ip6 = new StringBuffer(getNibbleFormat()); 297 return ip6.reverse().append(".ip6.arpa").toString(); 298 } 299 return (getIPAddress(reverseIP(address)) + ".in-addr.arpa"); 300 } 301 302 /** 303 * Converts 16 bit representation to 8 bit for IP4 304 * 305 * @param addressData 306 * The given int Array 307 * @return converted String 308 */ 309 private int[] get8BitAddress(int[] addressData) { 310 int[] convertAddress = new int[4]; 311 for (int i = 0; i < ipRun; i++) { 312 convertAddress[i] = addressData[i] & MASK8; 313 } 314 return convertAddress; 315 } 316 317 /** 318 * Create a masked address given an address and mask 319 * 320 * @param addressData 321 * The int Array represent the ipAddress 322 * @param maskData 323 * The int array represent the mask 324 * @return maskedAddress 325 */ 326 private int[] maskedAddress(int[] addressData, int[] maskData) { 327 int[] maskedAddress = new int[ipLength]; 328 329 for (int i = 0; i < ipRun; i++) { 330 maskedAddress[i] = addressData[i] & maskData[i]; 331 } 332 return maskedAddress; 333 } 334 335 /** 336 * Reverses internal address 337 * 338 * @param addressData 339 * The int array represent the ipAddress 340 * @return reverseIP 341 */ 342 private int[] reverseIP(int[] addressData) { 343 int[] reverseIP = new int[ipLength]; 344 int temp; 345 for (int i = 0; i < ipRun; i++) { 346 temp = addressData[i]; 347 reverseIP[i] = addressData[(ipRun - 1) - i]; 348 reverseIP[(ipRun - 1) - i] = temp; 349 } 350 return reverseIP; 351 } 352 353 /** 354 * Get mask length 355 * 356 * @return maskLength 357 */ 358 public int getMaskLength() { 359 return maskLength; 360 } 361 362 363 public String toString() { 364 return getIPAddress(); 365 } 366 367 private int unsigned(byte data) { 368 return data >= 0 ? data : 256 + data; 369 } 370 371 /** 372 * This method return the InAddress for the given ip. 373 * 374 * @param ipAddress - 375 * ipAddress that should be processed 376 * @return the inAddress (in-addr or ip6) 377 * @throws PermErrorException 378 * if the ipAddress is not valid (rfc conform) 379 */ 380 public static String getInAddress(String ipAddress) 381 throws PermErrorException { 382 if (ipAddress == null) { 383 throw new PermErrorException( 384 "IP is not a valid ipv4 or ipv6 address"); 385 } else if (Inet6Util.isValidIPV4Address(ipAddress)) { 386 return "in-addr"; 387 } else if (Inet6Util.isValidIP6Address(ipAddress)) { 388 return "ip6"; 389 } else { 390 throw new PermErrorException( 391 "IP is not a valid ipv4 or ipv6 address"); 392 } 393 } 394 395 /** 396 * Check if the given IP is valid. Works with ipv4 and ip6 397 * 398 * @param ip 399 * The ipaddress to check 400 * @return true or false 401 */ 402 public static boolean isValidIP(String ip) { 403 return ip != null 404 && (Inet6Util.isValidIPV4Address(ip) || Inet6Util 405 .isValidIP6Address(ip)); 406 } 407 408 /** 409 * Return if the given ipAddress is ipv6 410 * 411 * @param ip The ipAddress 412 * @return true or false 413 */ 414 public static boolean isIPV6(String ip) { 415 return Inet6Util.isValidIP6Address(ip); 416 } 417 418 /** 419 * This method try to covnert an ip address to an easy readable ip. See 420 * http://java.sun.com/j2se/1.4.2/docs/api/java/net/Inet6Address.html for 421 * the format it returns. For ipv4 it make no convertion 422 * 423 * @param ip 424 * The ip which should be tried to convert 425 * @return ip The converted ip 426 */ 427 public static String getReadableIP(String ip) { 428 429 // Convert the ip if its an ipv6 ip. For ipv4 no conversion is needed 430 if (Inet6Util.isValidIP6Address(ip)) { 431 try { 432 return getConvertedIP(ip); 433 } catch (UnknownHostException e) { 434 // ignore this 435 } 436 } 437 return ip; 438 } 439 440 private static String getConvertedIP(String ip) throws UnknownHostException { 441 // Convert the ip if its an ipv6 ip. For ipv4 no conversion is needed 442 return Address.getByName(ip).getHostAddress(); 443 } 444 445 /** 446 * This method convert the given ip to the proper format. Convertion will only done if the given ipAddress is ipv6 and ipv4-mapped 447 * 448 * This must be done to correct handle IPv4-mapped-addresses. 449 * See: http://java.sun.com/j2se/1.4.2/docs/api/java/net/Inet6Address.html 450 * 451 * Special IPv6 address: 452 * IPv4-mapped address: 453 * Of the form::ffff:w.x.y.z, this IPv6 address is used to represent an IPv4 address. It allows 454 * the native program to use the same address data structure and also the same socket when 455 * communicating with both IPv4 and IPv6 nodes. In InetAddress and Inet6Address, it is used 456 * for internal representation; it has no functional role. Java will never return an IPv4-mapped address. 457 * These classes can take an IPv4-mapped address as input, both in byte array and text representation. 458 * However, it will be converted into an IPv4 address. 459 * @param ip the ipAddress to convert 460 * @return return converted ip 461 * @throws PermErrorException if the given ipAddress is invalid 462 */ 463 public static String getProperIpAddress(String ip) throws PermErrorException { 464 if (isIPV6(ip) && isIPV4MappedIP(ip)) { 465 try { 466 return getConvertedIP(ip); 467 } catch (UnknownHostException e) { 468 throw new PermErrorException("Invalid ipAddress: " + ip); 469 } 470 } 471 return ip; 472 473 } 474 475 /** 476 * Return true if the given ipAddress is a ipv4-mapped-address 477 * @param ip 478 * @return 479 */ 480 private static boolean isIPV4MappedIP(String ip) { 481 return ip.toUpperCase().matches(ipv4MappedRegex); 482 } 483 484 }