Remove broken link
[phpmyadmin.git] / libraries / ip_allow_deny.lib.php
blob85fb82cf26cf83a5058b39a209809afc36208db8
1 <?php
2 /* vim: set expandtab sw=4 ts=4 sts=4: */
3 /**
4 * This library is used with the server IP allow/deny host authentication
5 * feature
7 * @package PhpMyAdmin
8 */
10 /**
11 * Matches for IPv4 or IPv6 addresses
13 * @param string $testRange string of IP range to match
14 * @param string $ipToTest string of IP to test against range
16 * @return boolean whether the IP mask matches
18 * @access public
20 function PMA_ipMaskTest($testRange, $ipToTest)
22 if (mb_strpos($testRange, ':') > -1
23 || mb_strpos($ipToTest, ':') > -1
24 ) {
25 // assume IPv6
26 $result = PMA_ipv6MaskTest($testRange, $ipToTest);
27 } else {
28 $result = PMA_ipv4MaskTest($testRange, $ipToTest);
31 return $result;
32 } // end of the "PMA_ipMaskTest()" function
35 /**
36 * Based on IP Pattern Matcher
37 * Originally by J.Adams <jna@retina.net>
38 * Found on <https://secure.php.net/manual/en/function.ip2long.php>
39 * Modified for phpMyAdmin
41 * Matches:
42 * xxx.xxx.xxx.xxx (exact)
43 * xxx.xxx.xxx.[yyy-zzz] (range)
44 * xxx.xxx.xxx.xxx/nn (CIDR)
46 * Does not match:
47 * xxx.xxx.xxx.xx[yyy-zzz] (range, partial octets not supported)
49 * @param string $testRange string of IP range to match
50 * @param string $ipToTest string of IP to test against range
52 * @return boolean whether the IP mask matches
54 * @access public
56 function PMA_ipv4MaskTest($testRange, $ipToTest)
58 $result = true;
59 $match = preg_match(
60 '|([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)/([0-9]+)|',
61 $testRange,
62 $regs
64 if ($match) {
65 // performs a mask match
66 $ipl = ip2long($ipToTest);
67 $rangel = ip2long(
68 $regs[1] . '.' . $regs[2] . '.' . $regs[3] . '.' . $regs[4]
71 $maskl = 0;
73 for ($i = 0; $i < 31; $i++) {
74 if ($i < $regs[5] - 1) {
75 $maskl = $maskl + pow(2, (30 - $i));
76 } // end if
77 } // end for
79 if (($maskl & $rangel) == ($maskl & $ipl)) {
80 return true;
83 return false;
86 // range based
87 $maskocts = explode('.', $testRange);
88 $ipocts = explode('.', $ipToTest);
90 // perform a range match
91 for ($i = 0; $i < 4; $i++) {
92 if (preg_match('|\[([0-9]+)\-([0-9]+)\]|', $maskocts[$i], $regs)) {
93 if (($ipocts[$i] > $regs[2]) || ($ipocts[$i] < $regs[1])) {
94 $result = false;
95 } // end if
96 } else {
97 if ($maskocts[$i] <> $ipocts[$i]) {
98 $result = false;
99 } // end if
100 } // end if/else
101 } //end for
103 return $result;
104 } // end of the "PMA_ipv4MaskTest()" function
108 * IPv6 matcher
109 * CIDR section taken from https://stackoverflow.com/a/10086404
110 * Modified for phpMyAdmin
112 * Matches:
113 * xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx
114 * (exact)
115 * xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:[yyyy-zzzz]
116 * (range, only at end of IP - no subnets)
117 * xxxx:xxxx:xxxx:xxxx/nn
118 * (CIDR)
120 * Does not match:
121 * xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xx[yyy-zzz]
122 * (range, partial octets not supported)
124 * @param string $test_range string of IP range to match
125 * @param string $ip_to_test string of IP to test against range
127 * @return boolean whether the IP mask matches
129 * @access public
131 function PMA_ipv6MaskTest($test_range, $ip_to_test)
133 $result = true;
135 // convert to lowercase for easier comparison
136 $test_range = mb_strtolower($test_range);
137 $ip_to_test = mb_strtolower($ip_to_test);
139 $is_cidr = mb_strpos($test_range, '/') > -1;
140 $is_range = mb_strpos($test_range, '[') > -1;
141 $is_single = ! $is_cidr && ! $is_range;
143 $ip_hex = bin2hex(inet_pton($ip_to_test));
145 if ($is_single) {
146 $range_hex = bin2hex(inet_pton($test_range));
147 $result = hash_equals($ip_hex, $range_hex);
148 return $result;
151 if ($is_range) {
152 // what range do we operate on?
153 $range_match = array();
154 $match = preg_match(
155 '/\[([0-9a-f]+)\-([0-9a-f]+)\]/', $test_range, $range_match
157 if ($match) {
158 $range_start = $range_match[1];
159 $range_end = $range_match[2];
161 // get the first and last allowed IPs
162 $first_ip = str_replace($range_match[0], $range_start, $test_range);
163 $first_hex = bin2hex(inet_pton($first_ip));
164 $last_ip = str_replace($range_match[0], $range_end, $test_range);
165 $last_hex = bin2hex(inet_pton($last_ip));
167 // check if the IP to test is within the range
168 $result = ($ip_hex >= $first_hex && $ip_hex <= $last_hex);
170 return $result;
173 if ($is_cidr) {
174 // Split in address and prefix length
175 list($first_ip, $subnet) = explode('/', $test_range);
177 // Parse the address into a binary string
178 $first_bin = inet_pton($first_ip);
179 $first_hex = bin2hex($first_bin);
181 $flexbits = 128 - $subnet;
183 // Build the hexadecimal string of the last address
184 $last_hex = $first_hex;
186 $pos = 31;
187 while ($flexbits > 0) {
188 // Get the character at this position
189 $orig = mb_substr($last_hex, $pos, 1);
191 // Convert it to an integer
192 $origval = hexdec($orig);
194 // OR it with (2^flexbits)-1, with flexbits limited to 4 at a time
195 $newval = $origval | (pow(2, min(4, $flexbits)) - 1);
197 // Convert it back to a hexadecimal character
198 $new = dechex($newval);
200 // And put that character back in the string
201 $last_hex = substr_replace($last_hex, $new, $pos, 1);
203 // We processed one nibble, move to previous position
204 $flexbits -= 4;
205 --$pos;
208 // check if the IP to test is within the range
209 $result = ($ip_hex >= $first_hex && $ip_hex <= $last_hex);
212 return $result;
213 } // end of the "PMA_ipv6MaskTest()" function
217 * Runs through IP Allow/Deny rules the use of it below for more information
219 * @param string $type 'allow' | 'deny' type of rule to match
221 * @return bool Whether rule has matched
223 * @access public
225 * @see PMA_getIp()
227 function PMA_allowDeny($type)
229 global $cfg;
231 // Grabs true IP of the user and returns if it can't be found
232 $remote_ip = PMA_getIp();
233 if (empty($remote_ip)) {
234 return false;
237 // copy username
238 $username = $cfg['Server']['user'];
240 // copy rule database
241 if (isset($cfg['Server']['AllowDeny']['rules'])) {
242 $rules = $cfg['Server']['AllowDeny']['rules'];
243 if (! is_array($rules)) {
244 $rules = array();
246 } else {
247 $rules = array();
250 // lookup table for some name shortcuts
251 $shortcuts = array(
252 'all' => '0.0.0.0/0',
253 'localhost' => '127.0.0.1/8'
256 // Provide some useful shortcuts if server gives us address:
257 if (PMA_getenv('SERVER_ADDR')) {
258 $shortcuts['localnetA'] = PMA_getenv('SERVER_ADDR') . '/8';
259 $shortcuts['localnetB'] = PMA_getenv('SERVER_ADDR') . '/16';
260 $shortcuts['localnetC'] = PMA_getenv('SERVER_ADDR') . '/24';
263 foreach ($rules as $rule) {
264 // extract rule data
265 $rule_data = explode(' ', $rule);
267 // check for rule type
268 if ($rule_data[0] != $type) {
269 continue;
272 // check for username
273 if (($rule_data[1] != '%') //wildcarded first
274 && (! hash_equals($rule_data[1], $username))
276 continue;
279 // check if the config file has the full string with an extra
280 // 'from' in it and if it does, just discard it
281 if ($rule_data[2] == 'from') {
282 $rule_data[2] = $rule_data[3];
285 // Handle shortcuts with above array
286 if (isset($shortcuts[$rule_data[2]])) {
287 $rule_data[2] = $shortcuts[$rule_data[2]];
290 // Add code for host lookups here
291 // Excluded for the moment
293 // Do the actual matching now
294 if (PMA_ipMaskTest($rule_data[2], $remote_ip)) {
295 return true;
297 } // end while
299 return false;
300 } // end of the "PMA_AllowDeny()" function