diff options
Diffstat (limited to 'srcs/phpmyadmin/libraries/classes/IpAllowDeny.php')
| -rw-r--r-- | srcs/phpmyadmin/libraries/classes/IpAllowDeny.php | 336 |
1 files changed, 336 insertions, 0 deletions
diff --git a/srcs/phpmyadmin/libraries/classes/IpAllowDeny.php b/srcs/phpmyadmin/libraries/classes/IpAllowDeny.php new file mode 100644 index 0000000..d1a7c79 --- /dev/null +++ b/srcs/phpmyadmin/libraries/classes/IpAllowDeny.php @@ -0,0 +1,336 @@ +<?php +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * This library is used with the server IP allow/deny host authentication + * feature + * + * @package PhpMyAdmin + */ +declare(strict_types=1); + +namespace PhpMyAdmin; + +use PhpMyAdmin\Core; + +/** + * PhpMyAdmin\IpAllowDeny class + * + * @package PhpMyAdmin + */ +class IpAllowDeny +{ + /** + * 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 + */ + public function ipMaskTest($testRange, $ipToTest) + { + if (mb_strpos($testRange, ':') > -1 + || mb_strpos($ipToTest, ':') > -1 + ) { + // assume IPv6 + $result = $this->ipv6MaskTest($testRange, $ipToTest); + } else { + $result = $this->ipv4MaskTest($testRange, $ipToTest); + } + + return $result; + } + + /** + * Based on IP Pattern Matcher + * Originally by J.Adams <jna@retina.net> + * Found on <https://www.php.net/manual/en/function.ip2long.php> + * Modified for phpMyAdmin + * + * Matches: + * xxx.xxx.xxx.xxx (exact) + * xxx.xxx.xxx.[yyy-zzz] (range) + * xxx.xxx.xxx.xxx/nn (CIDR) + * + * Does not match: + * xxx.xxx.xxx.xx[yyy-zzz] (range, partial octets not supported) + * + * @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 + */ + public function ipv4MaskTest($testRange, $ipToTest) + { + $result = true; + $match = preg_match( + '|([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)/([0-9]+)|', + $testRange, + $regs + ); + if ($match) { + // performs a mask match + $ipl = ip2long($ipToTest); + $rangel = ip2long( + $regs[1] . '.' . $regs[2] . '.' . $regs[3] . '.' . $regs[4] + ); + + $maskl = 0; + + for ($i = 0; $i < 31; $i++) { + if ($i < $regs[5] - 1) { + $maskl += pow(2, 30 - $i); + } // end if + } // end for + + return ($maskl & $rangel) == ($maskl & $ipl); + } + + // range based + $maskocts = explode('.', $testRange); + $ipocts = explode('.', $ipToTest); + + // perform a range match + for ($i = 0; $i < 4; $i++) { + if (preg_match('|\[([0-9]+)\-([0-9]+)\]|', $maskocts[$i], $regs)) { + if (($ipocts[$i] > $regs[2]) || ($ipocts[$i] < $regs[1])) { + $result = false; + } // end if + } else { + if ($maskocts[$i] <> $ipocts[$i]) { + $result = false; + } // end if + } // end if/else + } //end for + + return $result; + } + + /** + * IPv6 matcher + * CIDR section taken from https://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 + */ + public function ipv6MaskTest($test_range, $ip_to_test) + { + $result = true; + + // convert to lowercase for easier comparison + $test_range = mb_strtolower($test_range); + $ip_to_test = mb_strtolower($ip_to_test); + + $is_cidr = mb_strpos($test_range, '/') > -1; + $is_range = mb_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 = hash_equals($ip_hex, $range_hex); + return $result; + } + + if ($is_range) { + // what range do we operate on? + $range_match = []; + $match = preg_match( + '/\[([0-9a-f]+)\-([0-9a-f]+)\]/', + $test_range, + $range_match + ); + if ($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); + } + return $result; + } + + if ($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); + + $flexbits = 128 - (int) $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 = mb_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; + } + + // check if the IP to test is within the range + $result = ($ip_hex >= $first_hex && $ip_hex <= $last_hex); + } + + return $result; + } + + /** + * Runs through IP Allow rules the use of it below for more information + * + * @return bool Whether rule has matched + * + * @access public + * + * @see Core::getIp() + */ + public function allow() + { + return $this->allowDeny("allow"); + } + + /** + * Runs through IP Deny rules the use of it below for more information + * + * @return bool Whether rule has matched + * + * @access public + * + * @see Core::getIp() + */ + public function deny() + { + return $this->allowDeny("deny"); + } + + /** + * Runs through IP Allow/Deny rules the use of it below for more information + * + * @param string $type 'allow' | 'deny' type of rule to match + * + * @return bool Whether rule has matched + * + * @access public + * + * @see Core::getIp() + */ + private function allowDeny($type) + { + global $cfg; + + // Grabs true IP of the user and returns if it can't be found + $remote_ip = Core::getIp(); + if (empty($remote_ip)) { + return false; + } + + // copy username + $username = $cfg['Server']['user']; + + // copy rule database + if (isset($cfg['Server']['AllowDeny']['rules'])) { + $rules = $cfg['Server']['AllowDeny']['rules']; + if (! is_array($rules)) { + $rules = []; + } + } else { + $rules = []; + } + + // lookup table for some name shortcuts + $shortcuts = [ + 'all' => '0.0.0.0/0', + 'localhost' => '127.0.0.1/8', + ]; + + // Provide some useful shortcuts if server gives us address: + if (Core::getenv('SERVER_ADDR')) { + $shortcuts['localnetA'] = Core::getenv('SERVER_ADDR') . '/8'; + $shortcuts['localnetB'] = Core::getenv('SERVER_ADDR') . '/16'; + $shortcuts['localnetC'] = Core::getenv('SERVER_ADDR') . '/24'; + } + + foreach ($rules as $rule) { + // extract rule data + $rule_data = explode(' ', $rule); + + // check for rule type + if ($rule_data[0] != $type) { + continue; + } + + // check for username + if (($rule_data[1] != '%') //wildcarded first + && (! hash_equals($rule_data[1], $username)) + ) { + continue; + } + + // check if the config file has the full string with an extra + // 'from' in it and if it does, just discard it + if ($rule_data[2] == 'from') { + $rule_data[2] = $rule_data[3]; + } + + // Handle shortcuts with above array + if (isset($shortcuts[$rule_data[2]])) { + $rule_data[2] = $shortcuts[$rule_data[2]]; + } + + // Add code for host lookups here + // Excluded for the moment + + // Do the actual matching now + if ($this->ipMaskTest($rule_data[2], $remote_ip)) { + return true; + } + } // end while + + return false; + } +} |
