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
|
?>
|