package org.infoglue.cms.util; import java.util.ArrayList; import java.util.List; import java.util.StringTokenizer; import java.util.regex.Pattern; /** * This class is used to match incoming IP-addresses against a list of allowed ones. * Handles wildcards for ipv4 but only exact matches for ipv6. */ public class IPMatcher { //Need to find a IP regexp that handles wildcards private static final Pattern IPV4_PATTERN = Pattern.compile("((?:\\d{1,3}\\.){3}\\d{1,3})(?:/(\\d{1,2}))?"); private static final Pattern IPV6_PATTERN = Pattern.compile("((?:::)|(?:(?:(?:[0-9a-fA-F]{1,4}:)|:){1,7})[0-9a-fA-F]{1,4})(?:/(\\d{1,3}))?"); /** * This method matches either of the two IP:s against a list of allowed IP-patterns. * * @param allowedIP The list of allowed IP:s. Can be: "localhost", or a literal IP address in the form 999.999.999.999, or a literal IP address in the form *.*.*.* where any of the octets can be the wildcard character (*), or an IP range in the form 999.999.999.999-999.999.999.999 * @param commonIP The primary user IP (IPv4 or IPv6-format) * @param alternateIP The primary user IP (IPv4 or IPv6-format) * @return true or false if the IP:s matches */ public static boolean isIpInList(List<String> allowedIP, String commonIP, String alternateIP) { boolean status = false; for (String ip : allowedIP) { ip = ip.trim(); if(IPV6_PATTERN.matcher(ip).matches()) { if (doesIPv6Match(ip, commonIP)||doesIPv6Match(ip, alternateIP)) { status = true; } } if(doesIPv4Match(ip, commonIP)||doesIPv4Match(ip, alternateIP)) { status = true; } } return status; } /** * Determines if 2 given IP addresses matches. The first, ip1, can include wildcards (*). </br> * ip1 can be: "localhost", or a literal IP address in the form 999.999.999.999, or a literal IP address in the form *.*.*.* where any of the octets can be the wildcard character (*), or an IP range in the form 999.999.999.999-999.999.999.999 * * @param ip1 the address to compare against * @param ip2 the address to compare * @return */ public static boolean doesIPv4Match(String ip1, String ip2) { // Check if ip2 == null, if so return false // Check if ip2 is a IPv4 number. if (ip2 == null || !IPV4_PATTERN.matcher(ip2).matches()) { return false; } // Scan the list of addresses configured and see if the ip2 // matches any. boolean ret = false; // Match test 1: localhost. if (ip1.equalsIgnoreCase("localhost")) { if (ip2.equalsIgnoreCase(ip1)) { ret = true; } } // Match test 2: exact address match. if (ip1.indexOf("*") == -1 & ip1.indexOf("-") == -1) { if (ip2.equalsIgnoreCase(ip1)) { ret = true; } } // Match test 3: A single address with wildcards. if (ip1.indexOf("*") != -1) { // Get all four octets from both the next address in the list and // the remote address so we can examine them individually. StringTokenizer nt = new StringTokenizer(ip1, "."); StringTokenizer rt = new StringTokenizer(ip2, "."); String ip1Octet1 = (String) nt.nextToken(); String ip1Octet2 = (String) nt.nextToken(); String ip1Octet3 = (String) nt.nextToken(); String ip1Octet4 = (String) nt.nextToken(); String ip2Octet1 = (String) rt.nextToken(); String ip2Octet2 = (String) rt.nextToken(); String ip2Octet3 = (String) rt.nextToken(); String ip2Octet4 = (String) rt.nextToken(); // Now, for each octet, see if we have either an exact match or a // wildcard match, and if so set the appropriate octet flag. boolean octet1Ok = false; boolean octet2Ok = false; boolean octet3Ok = false; boolean octet4Ok = false; if (ip2Octet1.equalsIgnoreCase(ip1Octet1) || ip1Octet1.equalsIgnoreCase("*")) { octet1Ok = true; } if (ip2Octet2.equalsIgnoreCase(ip1Octet2) || ip1Octet2.equalsIgnoreCase("*")) { octet2Ok = true; } if (ip2Octet3.equalsIgnoreCase(ip1Octet3) || ip1Octet3.equalsIgnoreCase("*")) { octet3Ok = true; } if (ip2Octet4.equalsIgnoreCase(ip1Octet4) || ip1Octet4.equalsIgnoreCase("*")) { octet4Ok = true; } // Finally, if all four flags are true, the address is OK. if (octet1Ok & octet2Ok & octet3Ok & octet4Ok) { ret = true; } } // Match test 4: IP range. if (ip1.indexOf("-") != -1) { StringTokenizer st = new StringTokenizer(ip1, "-"); String rangeStart = st.nextToken(); String rangeEnd = st.nextToken(); long rangeStartLong = ipToLong(rangeStart); long rangeEndLong = ipToLong(rangeEnd); long remoteAddrLong = ipToLong(ip2); if (remoteAddrLong >= rangeStartLong && remoteAddrLong <= rangeEndLong) { ret = true; } } return ret; } // End addressInList(). /** * Determines if the second IP number matches the first. * * @param ip1 the IP to match against * @param ip2 the IP to match * @return true if ip1 and ip2 matches */ public static boolean doesIPv6Match(String ip1, String ip2) { if(ip2==null || !IPV6_PATTERN.matcher(ip2).matches()) { return false; } if(ip2.equalsIgnoreCase(ip1)) { return true; } return false; } /** * Method that converts an IP address to a long. * * @param ip * The IP address to convert. * @return The IP address as a long. */ private static long ipToLong(String ip) { StringTokenizer st = new StringTokenizer(ip, "."); int o1 = Integer.parseInt((String) st.nextToken()); int o2 = Integer.parseInt((String) st.nextToken()); int o3 = Integer.parseInt((String) st.nextToken()); int o4 = Integer.parseInt((String) st.nextToken()); String o1S = Integer.toBinaryString(o1).trim(); String o2S = Integer.toBinaryString(o2).trim(); String o3S = Integer.toBinaryString(o3).trim(); String o4S = Integer.toBinaryString(o4).trim(); o1S = padBinByteStr(o1S); o2S = padBinByteStr(o2S); o3S = padBinByteStr(o3S); o4S = padBinByteStr(o4S); String bin = o1S + o2S + o3S + o4S; long res = 0; long j = 2147483648L; for (int i = 0; i < 32; i++) { char c = bin.charAt(i); if (c == '1') { res = res + j; } j = j / 2; } return res; } // End ipToLong(). /** * Method that pads (prefixes) a string representation of a byte with 0's. * * @param binByte String of the byte (maybe less than 8 bits) to pad. * @return String of the byte guaranteed to have 8 bits. */ private static String padBinByteStr(String binByte) { if (binByte.length() == 8) { return binByte; } StringBuffer sb = new StringBuffer(); for (int i = 0; i < (8 - binByte.length()); i++) { sb.append("0"); } sb.append(binByte); return sb.toString(); } // End padBinByteStr(). public static void main(String[] args) { List<String> ipList = new ArrayList<String>(); ipList.add("123.123.123.123"); ipList.add("124.124.124.*"); ipList.add("124.124.125.0-124.124.125.125"); ipList.add("124.124.124.*"); ipList.add("1080:0:0:0:8:800:200C:417A"); ipList.add("1080::8:800:200C:417B"); // Should be true boolean test1 = isIpInList(ipList, "123.123.123.123", null); System.out.println("Test 1 (true):" + test1); // Should be false boolean test2 = isIpInList(ipList, "123.123.123.124", null); System.out.println("Test 2 (false):" + test2); // Should be true boolean test3 = isIpInList(ipList, "123.123.123.124", "123.123.123.123"); System.out.println("Test 3 (true):" + test3); // Should be true boolean test4 = isIpInList(ipList, "124.124.124.123", null); System.out.println("Test 4 (true):" + test4); // Should be true boolean test5 = isIpInList(ipList, "124.124.125.123", null); System.out.println("Test 5 (true):" + test5); // Should be false boolean test6 = isIpInList(ipList, "124.124.125.126", null); System.out.println("Test 6 (false):" + test6); // Should be true boolean test7 = isIpInList(ipList, "1080:0:0:0:8:800:200C:417B", null); System.out.println("Test 7 (true):" + test7); // Should be true boolean test8 = isIpInList(ipList, "1080:0:0:0:8:800:200C:417A", null); System.out.println("Test 8 (true):" + test8); // Should be false boolean test9 = isIpInList(ipList, "1080:0:0:0:8:800:200C:417C", null); System.out.println("Test 9 (false):" + test9); System.out.println(IPV6_PATTERN.matcher("123.123.123.123").matches()); System.out.println(IPV6_PATTERN.matcher("1080:0:0:0:8:800:200C:417A").matches()); System.out.println(IPV6_PATTERN.matcher("fe80::89c4:9411:9a7f:c513").matches()); // System.out.println(IPV4_PATTERN_v2.matcher("123.123.123.*").matches()); } }