/****************************************************************
* Licensed to the Apache Software Foundation (ASF) under one *
* or more contributor license agreements. See the NOTICE file *
* distributed with this work for additional information *
* regarding copyright ownership. The ASF licenses this file *
* to you under the Apache License, Version 2.0 (the *
* "License"); you may not use this file except in compliance *
* with the License. You may obtain a copy of the License at *
* *
* http://www.apache.org/licenses/LICENSE-2.0 *
* *
* Unless required by applicable law or agreed to in writing, *
* software distributed under the License is distributed on an *
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
* KIND, either express or implied. See the License for the *
* specific language governing permissions and limitations *
* under the License. *
****************************************************************/
package org.apache.james.util.inetnetwork;
import java.net.UnknownHostException;
import org.apache.james.dnsservice.api.DNSService;
import org.apache.james.util.inetnetwork.model.Inet4Network;
import org.apache.james.util.inetnetwork.model.Inet6Network;
import org.apache.james.util.inetnetwork.model.InetNetwork;
/**
* Builds a InetNetwork (Inet4Network or Inet6Network) in function
* on the provided string pattern that represents a subnet.
*
* Inet4Network is constructed based on the IPv4
* subnet expressed in one of several formats:
* IPv4 Format Example
* Explicit address 127.0.0.1
* Address with a wildcard 127.0.0.*
* Domain name myHost.com
* Domain name + prefix-length myHost.com/24
* Domain name + mask myHost.com/255.255.255.0
* IP address + prefix-length 127.0.0.0/8
* IP + mask 127.0.0.0/255.0.0.0
* For more information on IP V4, see RFC 1518 and RFC 1519.
*
* Inet6Network is constructed based on the IPv4
* subnet expressed in one of several formats:
* IPv6 Format Example
* Explicit address 0000:0000:0000:0000:0000:0000:0000:0001
* IP address + subnet mask (/) 0000:0000:0000:0000:0000:0000:0000:0001/64
* IP address + subnet mask (%) 0000:0000:0000:0000:0000:0000:0000:0001%64
* The following V6 formats will be supported later:
* Domain name myHost.com
* Domain name + mask (/) myHost.com/48
* Domain name + mask (%) myHost.com%48
* Explicit shorted address ::1
* For more information on IP V6, see RFC 2460. (see also http://en.wikipedia.org/wiki/IPv6_address)
*/
public class InetNetworkBuilder {
/**
* The DNS Server used to create InetAddress for
* hostnames and IP adresses.
*/
private DNSService dnsService;
/**
* Constructs a InetNetwork.
*
* @param dnsServer the DNSService to use
*/
public InetNetworkBuilder(DNSService dnsServer) {
this.dnsService = dnsServer;
}
/**
* Creates a InetNetwork for the given String.
* Depending on the provided pattern and the platform configuration
* (IPv4 and/or IPv6), the returned type will be Inet4Network
* or Inet6Network.
*
* @param netspec the String which is will converted to InetNetwork
* @return network the InetNetwork
* @throws java.net.UnknownHostException
*/
public InetNetwork getFromString(String netspec) throws UnknownHostException {
return isV6(netspec) ? getV6FromString(netspec) : getV4FromString(netspec);
}
/**
* Returns true if the string parameters is a IPv6 pattern.
* Currently, only tests for presence of ':'.
* @param address
* @return boolean
*/
public static boolean isV6(String netspec) {
return netspec.contains(":");
}
/**
* Get a Inet4Network for the given String.
*
* @param netspec the String which is will converted to InetNetwork
* @return network the InetNetwork
* @throws java.net.UnknownHostException
*/
private InetNetwork getV4FromString(String netspec) throws UnknownHostException {
if (netspec.endsWith("*")) {
netspec = normalizeV4FromAsterisk(netspec);
}
else {
int iSlash = netspec.indexOf('/');
if (iSlash == -1) {
netspec += "/255.255.255.255";
}
else if (netspec.indexOf('.', iSlash) == -1) {
netspec = normalizeV4FromCIDR(netspec);
}
}
return new Inet4Network(
dnsService.getByName(netspec.substring(0, netspec.indexOf('/'))),
dnsService.getByName(netspec.substring(netspec.indexOf('/') + 1)));
}
/**
* Get a Inet6Network for the given String.
*
* @param netspec the String which is will converted to InetNetwork
* @return network the InetNetwork
* @throws java.net.UnknownHostException
*/
private InetNetwork getV6FromString(String netspec) throws UnknownHostException {
if (netspec.endsWith("*")) {
throw new UnsupportedOperationException("Wildcard for IPv6 not supported");
}
// Netmask can be separated with %
netspec.replaceAll("%", "/");
if (netspec.indexOf('/') == -1) {
netspec += "/32768";
}
return new Inet6Network(
dnsService.getByName(netspec.substring(0, netspec.indexOf('/'))),
new Integer(netspec.substring(netspec.indexOf('/') + 1)));
}
/**
* This converts from an uncommon "wildcard" CIDR format to "address + mask"
* format:
* * => 000.000.000.0/000.000.000.0
* xxx.* => xxx.000.000.0/255.000.000.0
* xxx.xxx.* => xxx.xxx.000.0/255.255.000.0
* xxx.xxx.xxx.* => xxx.xxx.xxx.0/255.255.255.0
*
* @param netspec
* @return addrMask the address/mask of the given argument
*/
private static String normalizeV4FromAsterisk(final String netspec) {
String[] masks = {
"0.0.0.0/0.0.0.0",
"0.0.0/255.0.0.0",
"0.0/255.255.0.0",
"0/255.255.255.0" };
char[] srcb = netspec.toCharArray();
int octets = 0;
for (int i = 1; i < netspec.length(); i++) {
if (srcb[i] == '.')
octets++;
}
return (octets == 0) ? masks[0] : netspec.substring(0, netspec.length() - 1).concat(masks[octets]);
}
/**
* RFC 1518, 1519 - Classless Inter-Domain Routing (CIDR) This converts from
* "prefix + prefix-length" format to "address + mask" format, e.g. from
* xxx.xxx.xxx.xxx/yy to xxx.xxx.xxx.xxx/yyy.yyy.yyy.yyy.
*
* @param netspec the xxx.xxx.xxx.xxx/yyy format
* @return addrMask the xxx.xxx.xxx.xxx/yyy.yyy.yyy.yyy format
*/
private static String normalizeV4FromCIDR(final String netspec) {
final int bits = 32 - Integer.parseInt(netspec.substring(netspec.indexOf('/') + 1));
final int mask = (bits == 32) ? 0 : 0xFFFFFFFF - ((1 << bits) - 1);
return netspec.substring(0, netspec.indexOf('/') + 1)
+ Integer.toString(mask >> 24 & 0xFF, 10) + "."
+ Integer.toString(mask >> 16 & 0xFF, 10) + "."
+ Integer.toString(mask >> 8 & 0xFF, 10) + "."
+ Integer.toString(mask >> 0 & 0xFF, 10);
}
}