Bug #32425 ยป IpAddressRange.php

Marco Huber, 2011-12-09 16:30

 
1
<?php
2
namespace TYPO3\FLOW3\Security\RequestPattern;
3

    
4
/*                                                                        *
5
 * This script belongs to the FLOW3 framework.                            *
6
 *                                                                        *
7
 * It is free software; you can redistribute it and/or modify it under    *
8
 * the terms of the GNU Lesser General Public License, either version 3   *
9
 * of the License, or (at your option) any later version.                 *
10
 *                                                                        *
11
 * The TYPO3 project - inspiring people to share!                         *
12
 *                                                                        */
13

    
14

    
15
/**
16
 * This class holds an ipAddressRange pattern an decides, if a \TYPO3\FLOW3\MVC\RequestInterface object matches against this pattern
17
 *
18
 */
19
class IpAddressRange implements \TYPO3\FLOW3\Security\RequestPatternInterface {
20

    
21
	/**
22
	 * @var string
23
	 */
24
	protected $ipAddressRange = '';
25

    
26
	/**
27
	 * @var \TYPO3\FLOW3\Utility\Environment
28
	 * @FLOW3\Inject
29
	 */
30
	protected $environment;
31

    
32
	/**
33
	 * Returns TRUE, if this pattern can match against the given request object.
34
	 *
35
	 * @param \TYPO3\FLOW3\MVC\RequestInterface $request The request that should be matched
36
	 * @return boolean TRUE if this pattern can match
37
	 */
38
	public function canMatch(\TYPO3\FLOW3\MVC\RequestInterface $request) {
39
		return TRUE;
40
	}
41

    
42
	/**
43
	 * Returns the set pattern
44
	 *
45
	 * @return string The set pattern
46
	 */
47
	public function getPattern() {
48
		return $this->ipAddressRange;
49
	}
50

    
51
	/**
52
	 * Sets an ip address range
53
	 *
54
	 * @param string $ipAddressRange The ip address range
55
	 * @return void
56
	 */
57
	public function setPattern($ipAddressRange) {
58
		$this->ipAddressRange = $ipAddressRange;
59
	}
60

    
61
	/**
62
	 * Matches a \TYPO3\FLOW3\MVC\RequestInterface against its set ip address range
63
	 *
64
	 * @param \TYPO3\FLOW3\MVC\RequestInterface $request The request that should be matched
65
	 * @return boolean TRUE if the pattern matched, FALSE otherwise
66
	 * @throws \TYPO3\FLOW3\Security\Exception\RequestTypeNotSupportedException
67
	 */
68
	public function matchRequest(\TYPO3\FLOW3\MVC\RequestInterface $request) {
69
		return self::cmpIP($this->environment->getRemoteAddress(), $this->ipAddressRange);
70
	}
71

    
72
	/**
73
	 * Match IP number with list of numbers with wildcard
74
	 * Dispatcher method for switching into specialised IPv4 and IPv6 methods.
75
	 * Usage: 10
76
	 *
77
	 * @param	string		$baseIP is the current remote IP address for instance, typ. REMOTE_ADDR
78
	 * @param	string		$list is a comma-list of IP-addresses to match with. *-wildcard allowed instead of number, plus leaving out parts in the IP number is accepted as wildcard (eg. 192.168.*.* equals 192.168). If list is "*" no check is done and the function returns TRUE immediately. An empty list always returns FALSE.
79
	 * @return	boolean		True if an IP-mask from $list matches $baseIP
80
	 */
81
	public static function cmpIP($baseIP, $list) {
82
		$list = trim($list);
83
		if ($list === '') {
84
			return FALSE;
85
		} elseif ($list === '*') {
86
			return TRUE;
87
		}
88
		if (strpos($baseIP, ':') !== FALSE && self::validIPv6($baseIP)) {
89
			return self::cmpIPv6($baseIP, $list);
90
		} else {
91
			return self::cmpIPv4($baseIP, $list);
92
		}
93
	}
94

    
95
	/**
96
	 * Match IPv4 number with list of numbers with wildcard
97
	 *
98
	 * @param	string		$baseIP is the current remote IP address for instance, typ. REMOTE_ADDR
99
	 * @param	string		$list is a comma-list of IP-addresses to match with. *-wildcard allowed instead of number, plus leaving out parts in the IP number is accepted as wildcard (eg. 192.168.*.* equals 192.168)
100
	 * @return	boolean		True if an IP-mask from $list matches $baseIP
101
	 */
102
	public static function cmpIPv4($baseIP, $list) {
103
		$IPpartsReq = explode('.', $baseIP);
104
		if (count($IPpartsReq) == 4) {
105
			$values = array_map('trim', explode(',', $list));
106

    
107
			foreach ($values as $test) {
108
				$mask = '';
109
				if (strpos($test, '/') > 0) {
110
					list($test, $mask) = explode('/', $test);
111
				}
112

    
113
				if (intval($mask)) {
114
					// "192.168.3.0/24"
115
					$lnet = ip2long($test);
116
					$lip = ip2long($baseIP);
117
					$binnet = str_pad(decbin($lnet), 32, '0', STR_PAD_LEFT);
118
					$firstpart = substr($binnet, 0, $mask);
119
					$binip = str_pad(decbin($lip), 32, '0', STR_PAD_LEFT);
120
					$firstip = substr($binip, 0, $mask);
121
					$yes = (strcmp($firstpart, $firstip) == 0);
122
				} else {
123
					// "192.168.*.*"
124
					$IPparts = explode('.', $test);
125
					$yes = 1;
126
					foreach ($IPparts as $index => $val) {
127
						$val = trim($val);
128
						if (strcmp($val, '*') && strcmp($IPpartsReq[$index], $val)) {
129
							$yes = 0;
130
						}
131
					}
132
				}
133
				if ($yes) {
134
					return TRUE;
135
				}
136
			}
137
		}
138
		return FALSE;
139
	}
140

    
141
	/**
142
	 * Match IPv6 address with a list of IPv6 prefixes
143
	 *
144
	 * @param	string		$baseIP is the current remote IP address for instance
145
	 * @param	string		$list is a comma-list of IPv6 prefixes, could also contain IPv4 addresses
146
	 * @return	boolean		True if an baseIP matches any prefix
147
	 */
148
	public static function cmpIPv6($baseIP, $list) {
149
		$success = FALSE; // Policy default: Deny connection
150
		$baseIP = self::normalizeIPv6($baseIP);
151

    
152
		$values = array_map('trim', explode(',', $list));
153
		foreach ($values as $test) {
154
			$testList = explode('/', $test);
155
			if (count($testList) == 2) {
156
				list($test, $mask) = $testList;
157
			} else {
158
				$mask = FALSE;
159
			}
160

    
161
			if (self::validIPv6($test)) {
162
				$test = self::normalizeIPv6($test);
163
				$maskInt = intval($mask) ? intval($mask) : 128;
164
				if ($mask === '0') { // special case; /0 is an allowed mask - equals a wildcard
165
					$success = TRUE;
166
				} elseif ($maskInt == 128) {
167
					$success = ($test === $baseIP);
168
				} else {
169
					$testBin = self::IPv6Hex2Bin($test);
170
					$baseIPBin = self::IPv6Hex2Bin($baseIP);
171
					$success = TRUE;
172

    
173
					// modulo is 0 if this is a 8-bit-boundary
174
					$maskIntModulo = $maskInt % 8;
175
					$numFullCharactersUntilBoundary = intval($maskInt / 8);
176

    
177
					if (substr($testBin, 0, $numFullCharactersUntilBoundary) !== substr($baseIPBin, 0, $numFullCharactersUntilBoundary)) {
178
						$success = FALSE;
179
					} elseif ($maskIntModulo > 0) {
180
						// if not an 8-bit-boundary, check bits of last character
181
						$testLastBits = str_pad(decbin(ord(substr($testBin, $numFullCharactersUntilBoundary, 1))), 8, '0', STR_PAD_LEFT);
182
						$baseIPLastBits = str_pad(decbin(ord(substr($baseIPBin, $numFullCharactersUntilBoundary, 1))), 8, '0', STR_PAD_LEFT);
183
						if (strncmp($testLastBits, $baseIPLastBits, $maskIntModulo) != 0) {
184
							$success = FALSE;
185
						}
186
					}
187
				}
188
			}
189
			if ($success) {
190
				return TRUE;
191
			}
192
		}
193
		return FALSE;
194
	}
195

    
196
	/**
197
	 * Transform a regular IPv6 address from hex-representation into binary
198
	 *
199
	 * @param string $hex IPv6 address in hex-presentation
200
	 * @return string Binary representation (16 characters, 128 characters)
201
	 * @see normalizeIPv6()
202
	 */
203
	public static function IPv6Hex2Bin($hex) {
204
		// normalized representation has 39 characters (0000:0000:0000:0000:0000:0000:0000:0000)
205
		if (strlen($hex) < 39) {
206
			$hex = self::normalizeIPv6($hex);
207
		}
208
		$hex = str_replace(':', '', $hex); // Replace colon to nothing
209
		$bin = pack("H*", $hex);
210
		return $bin;
211
	}
212

    
213
	/**
214
	 * Normalize an IPv6 address to full length
215
	 *
216
	 * @param	string		Given IPv6 address
217
	 * @return	string		Normalized address
218
	 */
219
	public static function normalizeIPv6($address) {
220
		$normalizedAddress = '';
221
		$stageOneAddress = '';
222

    
223
		$chunks = explode('::', $address); // Count 2 if if address has hidden zero blocks
224
		if (count($chunks) == 2) {
225
			$chunksLeft = explode(':', $chunks[0]);
226
			$chunksRight = explode(':', $chunks[1]);
227
			$left = count($chunksLeft);
228
			$right = count($chunksRight);
229

    
230
			// Special case: leading zero-only blocks count to 1, should be 0
231
			if ($left == 1 && strlen($chunksLeft[0]) == 0) {
232
				$left = 0;
233
			}
234

    
235
			$hiddenBlocks = 8 - ($left + $right);
236
			$hiddenPart = '';
237
			$h = 0;
238
			while ($h < $hiddenBlocks) {
239
				$hiddenPart .= '0000:';
240
				$h++;
241
			}
242

    
243
			if ($left == 0) {
244
				$stageOneAddress = $hiddenPart . $chunks[1];
245
			} else {
246
				$stageOneAddress = $chunks[0] . ':' . $hiddenPart . $chunks[1];
247
			}
248
		} else {
249
			$stageOneAddress = $address;
250
		}
251

    
252
		// normalize the blocks:
253
		$blocks = explode(':', $stageOneAddress);
254
		$divCounter = 0;
255
		foreach ($blocks as $block) {
256
			$tmpBlock = '';
257
			$i = 0;
258
			$hiddenZeros = 4 - strlen($block);
259
			while ($i < $hiddenZeros) {
260
				$tmpBlock .= '0';
261
				$i++;
262
			}
263
			$normalizedAddress .= $tmpBlock . $block;
264
			if ($divCounter < 7) {
265
				$normalizedAddress .= ':';
266
				$divCounter++;
267
			}
268
		}
269
		return $normalizedAddress;
270
	}
271

    
272
	/**
273
	 * Validate a given IP address.
274
	 * Possible format are IPv4 and IPv6.
275
	 *
276
	 * @param	string		IP address to be tested
277
	 * @return	boolean		True if $ip is either of IPv4 or IPv6 format.
278
	 */
279
	public static function validIP($ip) {
280
		return (filter_var($ip, FILTER_VALIDATE_IP) !== FALSE);
281
	}
282

    
283
	/**
284
	 * Validate a given IP address to the IPv4 address format.
285
	 * Example for possible format:  10.0.45.99
286
	 *
287
	 * @param	string		IP address to be tested
288
	 * @return	boolean		True if $ip is of IPv4 format.
289
	 */
290
	public static function validIPv4($ip) {
291
		return (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) !== FALSE);
292
	}
293

    
294
	/**
295
	 * Validate a given IP address to the IPv6 address format.
296
	 * Example for possible format:  43FB::BB3F:A0A0:0 | ::1
297
	 *
298
	 * @param	string		IP address to be tested
299
	 * @return	boolean		True if $ip is of IPv6 format.
300
	 */
301
	public static function validIPv6($ip) {
302
		return (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) !== FALSE);
303
	}
304
}
305

    
306
?>
    (1-1/1)