From 84f4f1a246cf107d74e7e62d25af6f884b712ba4 Mon Sep 17 00:00:00 2001 From: "J.M" Date: Thu, 27 Dec 2012 19:14:28 +0100 Subject: [PATCH] rfe #3542567 Accept IPv6 ranges and IPv6 CIDR notations in $cfg['Servers'][$i]['AllowDeny']['rules'] --- ChangeLog | 1 + doc/config.rst | 16 +++- doc/glossary.rst | 7 ++ libraries/ip_allow_deny.lib.php | 132 ++++++++++++++++++++++++++++++-- 4 files changed, 147 insertions(+), 9 deletions(-) diff --git a/ChangeLog b/ChangeLog index af5f9c87cd9b..70fbc634fe1b 100644 --- a/ChangeLog +++ b/ChangeLog @@ -79,6 +79,7 @@ VerboseMultiSubmit, ReplaceHelpImg + Patch #3597529 [status] Add raw value as title on server status page + Support MySQL 5.6 partitioning + Removed the AjaxEnable directive ++ rfe #3542567 Accept IPv6 ranges and IPv6 CIDR notations in $cfg['Servers'][$i]['AllowDeny']['rules'] 3.5.6.0 (not yet released) - bug #3593604 [status] Erroneous advisor rule diff --git a/doc/config.rst b/doc/config.rst index 6aa817e3e47e..6460dbf57d22 100644 --- a/doc/config.rst +++ b/doc/config.rst @@ -862,18 +862,26 @@ Server connection settings from all'`` if your rule order is set to ``'allow,deny'`` or ``'explicit'``. - For the :term:`IP` matching + For the :term:`IP address` matching system, the following work: - * ``xxx.xxx.xxx.xxx`` (an exact :term:`IP` address) - * ``xxx.xxx.xxx.[yyy-zzz]`` (an :term:`IP` address range) + * ``xxx.xxx.xxx.xxx`` (an exact :term:`IP address`) + * ``xxx.xxx.xxx.[yyy-zzz]`` (an :term:`IP address` range) * ``xxx.xxx.xxx.xxx/nn`` (CIDR, Classless Inter-Domain Routing type :term:`IP` addresses) But the following does not work: * ``xxx.xxx.xxx.xx[yyy-zzz]`` (partial :term:`IP` address range) - Also IPv6 addresses are not supported. + For :term:`IPv6` addresses, the following work: + + * ``xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx`` (an exact :term:`IPv6` address) + * ``xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:[yyyy-zzzz]`` (an :term:`IPv6` address range) + * ``xxxx:xxxx:xxxx:xxxx/nn`` (CIDR, Classless Inter-Domain Routing type :term:`IPv6` addresses) + + But the following does not work: + + * ``xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xx[yyy-zzz]`` (partial :term:`IPv6` address range) .. config:option:: $cfg['Servers'][$i]['DisableIS'] diff --git a/doc/glossary.rst b/doc/glossary.rst index da9a4c20dfad..d558b44059a2 100644 --- a/doc/glossary.rst +++ b/doc/glossary.rst @@ -161,6 +161,13 @@ From Wikipedia, the free encyclopedia .. seealso:: + IPv6 + IPv6 (Internet Protocol version 6) is the latest revision of the + Internet Protocol (:term:`IP`), designed to deal with the + long-anticipated problem of its precedessor IPv4 running out of addresses. + + .. seealso:: + ISAPI Internet Server Application Programming Interface is the API of Internet Information Services (IIS). diff --git a/libraries/ip_allow_deny.lib.php b/libraries/ip_allow_deny.lib.php index b62912c845e4..f253e37c151e 100644 --- a/libraries/ip_allow_deny.lib.php +++ b/libraries/ip_allow_deny.lib.php @@ -4,8 +4,6 @@ * This library is used with the server IP allow/deny host authentication * feature * - * @todo Broken for IPv6 - * * @package PhpMyAdmin */ if (! defined('PHPMYADMIN')) { @@ -51,6 +49,31 @@ function PMA_getIp() } // end of the 'PMA_getIp()' function +/** + * Matches for IPv4 or IPv6 addresses + * + * @param string $testRange string of IP range to match + * @param string $ipToTest string of IP to test against range + * + * @return boolean whether the IP mask matches + * + * @access public + */ +function PMA_ipMaskTest($testRange, $ipToTest) +{ + $result = true; + + if (strpos($testRange, ':') > -1 || strpos($ipToTest, ':') > -1) { + // assume IPv6 + $result = PMA_ipv6MaskTest($testRange, $ipToTest); + } else { + $result = PMA_ipv4MaskTest($testRange, $ipToTest); + } + + return $result; +} // end of the "PMA_ipMaskTest()" function + + /** * Based on IP Pattern Matcher * Originally by J.Adams @@ -68,11 +91,11 @@ function PMA_getIp() * @param string $testRange string of IP range to match * @param string $ipToTest string of IP to test against range * - * @return boolean always true + * @return boolean whether the IP mask matches * * @access public */ -function PMA_ipMaskTest($testRange, $ipToTest) +function PMA_ipv4MaskTest($testRange, $ipToTest) { $result = true; $match = preg_match( @@ -120,7 +143,106 @@ function PMA_ipMaskTest($testRange, $ipToTest) } //end if/else return $result; -} // end of the "PMA_IPMaskTest()" function +} // end of the "PMA_ipv4MaskTest()" function + + +/** + * IPv6 matcher + * CIDR section taken from http://stackoverflow.com/a/10086404 + * Modified for phpMyAdmin + * + * Matches: + * xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx (exact) + * xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:[yyyy-zzzz] (range, only at end of IP - no subnets) + * xxxx:xxxx:xxxx:xxxx/nn (CIDR) + * + * Does not match: + * xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xx[yyy-zzz] (range, partial octets not supported) + * + * @param string $test_range string of IP range to match + * @param string $ip_to_test string of IP to test against range + * + * @return boolean whether the IP mask matches + * + * @access public + */ +function PMA_ipv6MaskTest($test_range, $ip_to_test) +{ + $result = true; + + // convert to lowercase for easier comparison + $test_range = strtolower($test_range); + $ip_to_test = strtolower($ip_to_test); + + $is_cidr = strpos($test_range, '/') > -1; + $is_range = strpos($test_range, '[') > -1; + $is_single = ! $is_cidr && ! $is_range; + + $ip_hex = bin2hex(inet_pton($ip_to_test)); + + if ($is_single) { + $range_hex = bin2hex(inet_pton($test_range)); + $result = $ip_hex === $range_hex; + } elseif ($is_range) { + // what range do we operate on? + $range_match = array(); + if (preg_match('/\[([0-9a-f]+)\-([0-9a-f]+)\]/', $test_range, $range_match)) { + $range_start = $range_match[1]; + $range_end = $range_match[2]; + + // get the first and last allowed IPs + $first_ip = str_replace($range_match[0], $range_start, $test_range); + $first_hex = bin2hex(inet_pton($first_ip)); + $last_ip = str_replace($range_match[0], $range_end, $test_range); + $last_hex = bin2hex(inet_pton($last_ip)); + + // check if the IP to test is within the range + $result = ($ip_hex >= $first_hex && $ip_hex <= $last_hex); + } + } elseif ($is_cidr) { + // Split in address and prefix length + list($first_ip, $subnet) = explode('/', $test_range); + + // Parse the address into a binary string + $first_bin = inet_pton($first_ip); + $first_hex = bin2hex($first_bin); + + // Overwriting first address string to make sure notation is optimal + $first_ip = inet_ntop($first_bin); + + $flexbits = 128 - $subnet; + + // Build the hexadecimal string of the last address + $last_hex = $first_hex; + + $pos = 31; + while ($flexbits > 0) { + // Get the character at this position + $orig = substr($last_hex, $pos, 1); + + // Convert it to an integer + $origval = hexdec($orig); + + // OR it with (2^flexbits)-1, with flexbits limited to 4 at a time + $newval = $origval | (pow(2, min(4, $flexbits)) - 1); + + // Convert it back to a hexadecimal character + $new = dechex($newval); + + // And put that character back in the string + $last_hex = substr_replace($last_hex, $new, $pos, 1); + + // We processed one nibble, move to previous position + $flexbits -= 4; + $pos -= 1; + } + + // check if the IP to test is within the range + $result = ($ip_hex >= $first_hex && $ip_hex <= $last_hex); + } + + return $result; +} // end of the "PMA_ipv6MaskTest()" function /**