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    }