1 | <?php |
||
2 | /** |
||
3 | * This file is part of the Shieldon package. |
||
4 | * |
||
5 | * (c) Terry L. <[email protected]> |
||
6 | * |
||
7 | * For the full copyright and license information, please view the LICENSE |
||
8 | * file that was distributed with this source code. |
||
9 | * |
||
10 | * php version 7.1.0 |
||
11 | * |
||
12 | * @category Web-security |
||
13 | * @package Shieldon |
||
14 | * @author Terry Lin <[email protected]> |
||
15 | * @copyright 2019 terrylinooo |
||
16 | * @license https://github.com/terrylinooo/shieldon/blob/2.x/LICENSE MIT |
||
17 | * @link https://github.com/terrylinooo/shieldon |
||
18 | * @see https://shieldon.io |
||
19 | */ |
||
20 | |||
21 | declare(strict_types=1); |
||
22 | |||
23 | namespace Shieldon\Firewall\Panel; |
||
24 | |||
25 | use Psr\Http\Message\ResponseInterface; |
||
26 | use Shieldon\Firewall\Panel\BaseController; |
||
27 | use SplFileObject; |
||
28 | use ReflectionObject; |
||
29 | use function Shieldon\Firewall\__; |
||
30 | use function Shieldon\Firewall\get_request; |
||
31 | use function explode; |
||
32 | use function file; |
||
33 | use function file_exists; |
||
34 | use function file_put_contents; |
||
35 | use function filter_var; |
||
36 | use function in_array; |
||
37 | use function is_array; |
||
38 | use function is_numeric; |
||
39 | use function sleep; |
||
40 | use function trim; |
||
41 | |||
42 | /** |
||
43 | * The bridge between the Shieldon firewall and the iptables firewall. |
||
44 | */ |
||
45 | class Iptables extends BaseController |
||
46 | { |
||
47 | /** |
||
48 | * Public methods | Desctiotion |
||
49 | * ----------------------|--------------------------------------------- |
||
50 | * ip4 | The page for iptables (IPv4) management. |
||
51 | * ip6 | The page for iptables (IPv6) management. |
||
52 | * ip4status | The page for dispalying iptables (IPv4) status. |
||
53 | * ip6status | The page for dispalying iptables (IPv6) status. |
||
54 | * ----------------------|--------------------------------------------- |
||
55 | */ |
||
56 | |||
57 | /** |
||
58 | * Constructor |
||
59 | */ |
||
60 | 8 | public function __construct() |
|
61 | { |
||
62 | 8 | parent::__construct(); |
|
63 | } |
||
64 | |||
65 | /** |
||
66 | * The IPv4 table. |
||
67 | * |
||
68 | * @return ResponseInterface |
||
69 | */ |
||
70 | 7 | public function ip4(): ResponseInterface |
|
71 | { |
||
72 | 7 | return $this->iptables('IPv4'); |
|
73 | } |
||
74 | |||
75 | /** |
||
76 | * The IPv6 table. |
||
77 | * |
||
78 | * @return ResponseInterface |
||
79 | */ |
||
80 | 1 | public function ip6(): ResponseInterface |
|
81 | { |
||
82 | 1 | return $this->iptables('IPv6'); |
|
83 | } |
||
84 | |||
85 | /** |
||
86 | * The IPv4 table. |
||
87 | * |
||
88 | * @return ResponseInterface |
||
89 | */ |
||
90 | 1 | public function ip4status(): ResponseInterface |
|
91 | { |
||
92 | 1 | return $this->iptablesStatus('IPv4'); |
|
93 | } |
||
94 | |||
95 | /** |
||
96 | * The IPv6 table. |
||
97 | * |
||
98 | * @return ResponseInterface |
||
99 | */ |
||
100 | 1 | public function ip6status(): ResponseInterface |
|
101 | { |
||
102 | 1 | return $this->iptablesStatus('IPv6'); |
|
103 | } |
||
104 | |||
105 | /** |
||
106 | * System layer firwall - iptables |
||
107 | * |
||
108 | * @param string $type The type of IP address. |
||
109 | * |
||
110 | * @return ResponseInterface |
||
111 | */ |
||
112 | 7 | protected function iptables(string $type = 'IPv4'): ResponseInterface |
|
113 | { |
||
114 | 7 | $reflection = new ReflectionObject($this->kernel); |
|
115 | 7 | $t = $reflection->getProperty('properties'); |
|
116 | 7 | $t->setAccessible(true); |
|
117 | 7 | $properties = $t->getValue($this->kernel); |
|
118 | |||
119 | 7 | $dir = $properties['iptables_watching_folder']; |
|
120 | |||
121 | 7 | $commandLogFile = $dir . '/' . strtolower($type) . '_command.log'; |
|
122 | 7 | $iptablesQueueFile = $dir . '/iptables_queue.log'; |
|
123 | |||
124 | 7 | if ('POST' === get_request()->getMethod()) { |
|
125 | 6 | $this->iptablesFormPost($type, $commandLogFile, $iptablesQueueFile); |
|
126 | } |
||
127 | |||
128 | 7 | $data = []; |
|
129 | 7 | $ipCommand = ''; |
|
130 | |||
131 | 7 | if (file_exists($commandLogFile)) { |
|
132 | 7 | $file = new SplFileObject($commandLogFile); |
|
133 | |||
134 | 7 | $ipCommand = []; |
|
135 | |||
136 | 7 | while (!$file->eof()) { |
|
137 | 7 | $line = trim($file->fgets()); |
|
138 | 7 | $ipInfo = explode(',', $line); |
|
139 | |||
140 | // If the column amount is at least 6 maning that the data is |
||
141 | // existed so that we can use it. |
||
142 | 7 | if (!empty($ipInfo[6])) { |
|
143 | 6 | $ipCommand[] = $ipInfo; |
|
144 | } |
||
145 | } |
||
146 | } |
||
147 | |||
148 | 7 | $data['ipCommand'] = $ipCommand; |
|
149 | 7 | $data['type'] = $type; |
|
150 | |||
151 | 7 | $data['title'] = __('panel', 'title_iptables_manager', 'IpTables Manager') . ' (' . $type . ')'; |
|
152 | |||
153 | 7 | return $this->renderPage('panel/iptables_manager', $data); |
|
154 | } |
||
155 | |||
156 | /** |
||
157 | * System layer firewall - iptables Status |
||
158 | * iptables -L |
||
159 | * |
||
160 | * @param string $type The type of IP address. |
||
161 | * |
||
162 | * @return ResponseInterface |
||
163 | */ |
||
164 | 1 | protected function iptablesStatus(string $type = 'IPv4'): ResponseInterface |
|
165 | { |
||
166 | 1 | $data = []; |
|
167 | |||
168 | 1 | $reflection = new ReflectionObject($this->kernel); |
|
169 | 1 | $t = $reflection->getProperty('properties'); |
|
170 | 1 | $t->setAccessible(true); |
|
171 | 1 | $properties = $t->getValue($this->kernel); |
|
172 | |||
173 | 1 | $dir = $properties['iptables_watching_folder']; |
|
174 | |||
175 | // The iptables log files. |
||
176 | 1 | $ipStatusFile = $dir . '/ipv4_status.log'; |
|
177 | |||
178 | 1 | if ('IPv6' === $type) { |
|
179 | 1 | $ipStatusFile = $dir . '/ipv6_status.log'; |
|
180 | } |
||
181 | |||
182 | 1 | $ipStatus = ''; |
|
183 | |||
184 | 1 | if (file_exists($ipStatusFile)) { |
|
185 | 1 | $ipStatus = file_get_contents($ipStatusFile); |
|
186 | } |
||
187 | |||
188 | 1 | $data['ipStatus'] = $ipStatus; |
|
189 | 1 | $data['type'] = $type; |
|
190 | |||
191 | 1 | $data['title'] = __('panel', 'title_iptables_status', 'IpTables Status') . ' (' . $type . ')'; |
|
192 | |||
193 | 1 | return $this->renderPage('panel/iptables_status', $data); |
|
194 | } |
||
195 | |||
196 | /** |
||
197 | * Detect and handle form post action. |
||
198 | * |
||
199 | * @param string $type IPv4 or IPv6 |
||
200 | * @param string $commandLogFile The log file contains executed commands. |
||
201 | * @param string $iptablesQueueFile The file contains the commands that wil |
||
202 | * be executed by iptables |
||
203 | * |
||
204 | * @return void |
||
205 | */ |
||
206 | 6 | private function iptablesFormPost(string $type, string $commandLogFile, string $iptablesQueueFile): void |
|
207 | { |
||
208 | 6 | $postParams = get_request()->getParsedBody(); |
|
209 | |||
210 | 6 | if (!is_array($postParams)) { |
|
211 | // @codeCoverageIgnoreStart |
||
212 | return; |
||
213 | // @codeCoverageIgnoreEnd |
||
214 | } |
||
215 | |||
216 | 6 | if (!$this->iptablesFormPostVerification($postParams)) { |
|
217 | 5 | return; |
|
218 | } |
||
219 | |||
220 | 1 | $ip = $postParams['ip']; |
|
221 | 1 | $port = $postParams['port']; |
|
222 | 1 | $subnet = $postParams['subnet']; |
|
223 | 1 | $protocol = $postParams['protocol']; |
|
224 | 1 | $action = $postParams['action']; |
|
225 | 1 | $cPort = $postParams['port_custom'] ?? 'all'; |
|
226 | |||
227 | 1 | $ipv = substr($type, -1); |
|
228 | |||
229 | 1 | if ($port === 'custom') { |
|
230 | 1 | $port = $cPort; |
|
231 | } |
||
232 | |||
233 | /** |
||
234 | * The process of add or remove command string from two files: |
||
235 | * |
||
236 | * (1) The command file - |
||
237 | * This file is used on display the commands on the page |
||
238 | * iptables Manager. |
||
239 | * (2) The queue file - |
||
240 | * This file is a bridge between Shieldon Firewall and Iptalbes. |
||
241 | * ipbales_bridge.sh will monitor this file, once commands come, |
||
242 | * transforming the commands into iptables syntax commands and |
||
243 | * then execute the iptables commands. |
||
244 | */ |
||
245 | 1 | if ($postParams['remove'] === 'yes') { |
|
246 | $originCommandString = "add,$ipv,$ip,$subnet,$port,$protocol,$action"; |
||
247 | 1 | ||
248 | // Delete line from the log file. |
||
249 | $fileArr = file($commandLogFile); |
||
250 | 1 | ||
251 | if (is_array($fileArr)) { |
||
0 ignored issues
–
show
introduced
by
![]() |
|||
252 | 1 | $keyFound = array_search(trim($originCommandString), $fileArr); |
|
253 | 1 | ||
254 | // Remove the row from the file content. |
||
255 | unset($fileArr[$keyFound]); |
||
256 | 1 | ||
257 | $t = []; |
||
258 | 1 | $i = 0; |
|
259 | 1 | foreach ($fileArr as $f) { |
|
260 | 1 | $t[$i] = trim($f); |
|
261 | 1 | $i++; |
|
262 | 1 | } |
|
263 | |||
264 | // Save the filtered content. |
||
265 | file_put_contents($commandLogFile, implode(PHP_EOL, $t)); |
||
266 | 1 | } |
|
267 | |||
268 | // Pass the command to the iptables bridge file to remove the rule |
||
269 | // which is in the Iptable rule list. |
||
270 | $applyCommand = "delete,$ipv,$ip,$subnet,$port,$protocol,$action"; |
||
271 | 1 | file_put_contents($iptablesQueueFile, $applyCommand . "\n", FILE_APPEND | LOCK_EX); |
|
272 | 1 | sleep(1); |
|
273 | 1 | ||
274 | // Finish this action, return. |
||
275 | return; |
||
276 | 1 | } |
|
277 | |||
278 | $applyCommand = "add,$ipv,$ip,$subnet,$port,$protocol,$action"; |
||
279 | 1 | file_put_contents($iptablesQueueFile, $applyCommand . "\n", FILE_APPEND | LOCK_EX); |
|
280 | 1 | ||
281 | // Becase we need system cronjob done, and then the web page will show the actual results. |
||
282 | sleep(10); |
||
283 | 1 | } |
|
284 | |||
285 | /** |
||
286 | * Verify the form fields. |
||
287 | * |
||
288 | * @param array $postParams The PSR-7 variable of $_POST |
||
289 | * |
||
290 | * @return bool |
||
291 | */ |
||
292 | private function iptablesFormPostVerification(array $postParams): bool |
||
293 | 6 | { |
|
294 | if (!$this->checkFieldIp($postParams)) { |
||
295 | 6 | return false; |
|
296 | 1 | } |
|
297 | |||
298 | if (!$this->checkFieldPort($postParams)) { |
||
299 | 5 | return false; |
|
300 | 1 | } |
|
301 | |||
302 | if (!$this->checkFieldSubnet($postParams)) { |
||
303 | 4 | return false; |
|
304 | 1 | } |
|
305 | |||
306 | if (!$this->checkFieldProtocol($postParams)) { |
||
307 | 3 | return false; |
|
308 | 1 | } |
|
309 | |||
310 | if (!$this->checkFieldAction($postParams)) { |
||
311 | 2 | return false; |
|
312 | 1 | } |
|
313 | |||
314 | return true; |
||
315 | 1 | } |
|
316 | |||
317 | /** |
||
318 | * Verify the form field - Ip. |
||
319 | * |
||
320 | * @param array $postParams The PSR-7 variable of $_POST |
||
321 | * |
||
322 | * @return bool |
||
323 | */ |
||
324 | private function checkFieldIp($postParams): bool |
||
325 | 6 | { |
|
326 | if (filter_var(explode('/', $postParams['ip'])[0], FILTER_VALIDATE_IP)) { |
||
327 | 6 | return true; |
|
328 | 5 | } |
|
329 | return false; |
||
330 | 1 | } |
|
331 | |||
332 | /** |
||
333 | * Verify the form field - Port. |
||
334 | * |
||
335 | * @param array $postParams The PSR-7 variable of $_POST |
||
336 | * |
||
337 | * @return bool |
||
338 | */ |
||
339 | private function checkFieldPort($postParams): bool |
||
340 | 5 | { |
|
341 | if (is_numeric($postParams['port']) || |
||
342 | in_array($postParams['port'], ['all', 'custom']) |
||
343 | 5 | ) { |
|
344 | 5 | return true; |
|
345 | } |
||
346 | 4 | return false; |
|
347 | } |
||
348 | 1 | ||
349 | /** |
||
350 | * Verify the form field - Subnet. |
||
351 | * |
||
352 | * @param array $postParams The PSR-7 variable of $_POST |
||
353 | * |
||
354 | * @return bool |
||
355 | */ |
||
356 | private function checkFieldSubnet($postParams): bool |
||
357 | { |
||
358 | 4 | if (is_numeric($postParams['subnet']) || |
|
359 | $postParams['subnet'] === 'null' |
||
360 | ) { |
||
361 | 4 | return true; |
|
362 | 4 | } |
|
363 | return false; |
||
364 | 3 | } |
|
365 | |||
366 | 1 | /** |
|
367 | * Verify the form field - Protocol. |
||
368 | * |
||
369 | * @param array $postParams The PSR-7 variable of $_POST |
||
370 | * |
||
371 | * @return bool |
||
372 | */ |
||
373 | private function checkFieldProtocol($postParams): bool |
||
374 | { |
||
375 | if (in_array($postParams['protocol'], ['tcp', 'udp', 'all'])) { |
||
376 | 3 | return true; |
|
377 | } |
||
378 | 3 | return false; |
|
379 | 2 | } |
|
380 | |||
381 | 1 | /** |
|
382 | * Verify the form field - Action. |
||
383 | * |
||
384 | * @param array $postParams The PSR-7 variable of $_POST |
||
385 | * |
||
386 | * @return bool |
||
387 | */ |
||
388 | private function checkFieldAction($postParams): bool |
||
389 | { |
||
390 | if (in_array($postParams['action'], ['allow', 'deny'])) { |
||
391 | 2 | return true; |
|
392 | } |
||
393 | 2 | return false; |
|
394 | 1 | } |
|
395 | } |
||
396 |