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 }