1
<?php
2
/**
3
 * This file is part of the Shieldon package.
4
 *
5
 * (c) Terry L. <contact@terryl.in>
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 <contact@terryl.in>
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 3
    public function __construct() 
61
    {
62 3
        parent::__construct();
63
    }
64

65
    /**
66
     * The IPv4 table.
67
     *
68
     * @return ResponseInterface
69
     */
70 3
    public function ip4(): ResponseInterface
71
    {
72 3
        return $this->iptables('IPv4');
73
    }
74

75
    /**
76
     * The IPv6 table.
77
     *
78
     * @return ResponseInterface
79
     */
80 3
    public function ip6(): ResponseInterface
81
    {
82 3
        return $this->iptables('IPv6');
83
    }
84

85
    /**
86
     * The IPv4 table.
87
     *
88
     * @return ResponseInterface
89
     */
90 3
    public function ip4status(): ResponseInterface
91
    {
92 3
        return $this->iptablesStatus('IPv4');
93
    }
94

95
    /**
96
     * The IPv6 table.
97
     *
98
     * @return ResponseInterface
99
     */
100 3
    public function ip6status(): ResponseInterface
101
    {
102 3
        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 3
    protected function iptables(string $type = 'IPv4'): ResponseInterface
113
    {
114 3
        $reflection = new ReflectionObject($this->kernel);
115 3
        $t = $reflection->getProperty('properties');
116 3
        $t->setAccessible(true);
117 3
        $properties = $t->getValue($this->kernel);
118

119 3
        $dir = $properties['iptables_watching_folder'];
120

121 3
        $commandLogFile = $dir . '/' . strtolower($type) . '_command.log';
122 3
        $iptablesQueueFile = $dir . '/iptables_queue.log';
123

124 3
        if ('POST' === get_request()->getMethod()) {
125 3
            $this->iptablesFormPost($type, $commandLogFile, $iptablesQueueFile);
126
        }
127

128 3
        $data = [];
129 3
        $ipCommand = '';
130

131 3
        if (file_exists($commandLogFile)) {
132 3
            $file = new SplFileObject($commandLogFile);
133

134 3
            $ipCommand = [];
135

136 3
            while (!$file->eof()) {
137 3
                $line = trim($file->fgets());
138 3
                $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 3
                if (!empty($ipInfo[6])) {
143 3
                    $ipCommand[] = $ipInfo;
144
                }
145
            }
146
        }
147

148 3
        $data['ipCommand'] = $ipCommand;
149 3
        $data['type'] = $type;
150

151 3
        $data['title'] = __('panel', 'title_iptables_manager', 'iptables Manager') . ' (' . $type . ')';
152

153 3
        return $this->renderPage('panel/iptables_manager', $data);
154
    }
155

156
    /**
157
     * System layer firwall - iptables Status
158
     * iptables -L
159
     * 
160
     * @param string $type The type of IP address.
161
     *
162
     * @return ResponseInterface
163
     */
164 3
    protected function iptablesStatus(string $type = 'IPv4'): ResponseInterface
165
    {
166 3
        $data = [];
167

168 3
        $reflection = new ReflectionObject($this->kernel);
169 3
        $t = $reflection->getProperty('properties');
170 3
        $t->setAccessible(true);
171 3
        $properties = $t->getValue($this->kernel);
172

173 3
        $dir = $properties['iptables_watching_folder'];
174

175
        // The iptables log files.
176 3
        $ipStatusFile = $dir . '/ipv4_status.log';
177

178 3
        if ('IPv6' === $type) {
179 3
            $ipStatusFile = $dir . '/ipv6_status.log';
180
        }
181
        
182 3
        $ipStatus = '';
183

184 3
        if (file_exists($ipStatusFile)) {
185 3
            $ipStatus = file_get_contents($ipStatusFile);
186
        }
187

188 3
        $data['ipStatus'] = $ipStatus;
189 3
        $data['type'] = $type;
190

191 3
        $data['title'] = __('panel', 'title_iptables_status', 'iptables Status') . ' (' . $type . ')';
192

193 3
        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 3
    private function iptablesFormPost(string $type, string $commandLogFile, string $iptablesQueueFile): void
207
    {
208 3
        $postParams = get_request()->getParsedBody();
209

210 3
        if (!is_array($postParams)) {
211
            // @codeCoverageIgnoreStart
212
            return;
213
            // @codeCoverageIgnoreEnd
214
        }
215

216 3
        if (!$this->iptablesFormPostVerification($postParams)) {
217 3
            return;
218
        }
219

220 3
        $ip       = $postParams['ip'];
221 3
        $port     = $postParams['port'];
222 3
        $subnet   = $postParams['subnet'];
223 3
        $protocol = $postParams['protocol'];
224 3
        $action   = $postParams['action'];
225 3
        $cPort    = $postParams['port_custom'] ?? 'all';
226

227 3
        $ipv = substr($type, -1);
228

229 3
        if ($port === 'custom') {
230 3
            $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 3
        if ($postParams['remove'] === 'yes') {
246

247 3
            $originCommandString = "add,$ipv,$ip,$subnet,$port,$protocol,$action";
248

249
            // Delete line from the log file.
250 3
            $fileArr = file($commandLogFile);
251
    
252 3
            if (is_array($fileArr)) {
253 3
                $keyFound = array_search(trim($originCommandString), $fileArr);
254

255
                // Remove the row from the file content.
256 3
                unset($fileArr[$keyFound]);
257
    
258 3
                $t = [];
259 3
                $i = 0;
260 3
                foreach ($fileArr as $f) {
261 3
                    $t[$i] = trim($f);
262 3
                    $i++;
263
                }
264

265
                // Save the filtered content.
266 3
                file_put_contents($commandLogFile, implode(PHP_EOL, $t));
267
            }
268
    
269
            // Pass the command to the iptables bridge file to remove the rule
270
            // which is in the Iptable rule list.
271 3
            $applyCommand = "delete,$ipv,$ip,$subnet,$port,$protocol,$action";
272 3
            file_put_contents($iptablesQueueFile, $applyCommand . "\n", FILE_APPEND | LOCK_EX);
273 3
            sleep(1);
274

275
            // Finish this action, return.
276 3
            return;
277
        }
278

279 3
        $applyCommand = "add,$ipv,$ip,$subnet,$port,$protocol,$action";
280 3
        file_put_contents($iptablesQueueFile, $applyCommand . "\n", FILE_APPEND | LOCK_EX);
281

282
        // Becase we need system cronjob done, and then the web page will show the actual results.
283 3
        sleep(10);
284
    }
285

286
    /**
287
     * Verify the form fields.
288
     *
289
     * @param array $postParams The PSR-7 variable of $_POST
290
     *
291
     * @return bool
292
     */
293 3
    private function iptablesFormPostVerification(array $postParams): bool
294
    {
295 3
        if (!$this->checkFieldIp($postParams)) {
296 3
            return false;
297
        }
298

299 3
        if (!$this->checkFieldPort($postParams)) {
300 3
            return false;
301
        }
302

303 3
        if (!$this->checkFieldSubnet($postParams)) {
304 3
            return false;
305
        }
306

307 3
        if (!$this->checkFieldProtocol($postParams)) {
308 3
            return false;
309
        }
310

311 3
        if (!$this->checkFieldAction($postParams)) {
312 3
            return false;
313
        }
314

315 3
        return true;
316
    }
317

318
    /**
319
     * Verify the form  field - Ip.
320
     *
321
     * @param array $postParams The PSR-7 variable of $_POST
322
     *
323
     * @return bool
324
     */
325 3
    private function checkFieldIp($postParams): bool
326
    {
327 3
        if (filter_var(explode('/', $postParams['ip'])[0], FILTER_VALIDATE_IP)) {
328 3
            return true;
329
        }
330 3
        return false;
331
    }
332

333
    /**
334
     * Verify the form field - Port.
335
     *
336
     * @param array $postParams The PSR-7 variable of $_POST
337
     *
338
     * @return bool
339
     */
340 3
    private function checkFieldPort($postParams): bool
341
    {
342
        if (
343 3
            is_numeric($postParams['port']) || 
344 3
            in_array($postParams['port'], ['all', 'custom'])
345
        ) {
346 3
            return true;
347
        }
348 3
        return false;
349
    }
350

351
    /**
352
     * Verify the form field - Subnet.
353
     *
354
     * @param array $postParams The PSR-7 variable of $_POST
355
     *
356
     * @return bool
357
     */
358 3
    private function checkFieldSubnet($postParams): bool
359
    {
360
        if (
361 3
            is_numeric($postParams['subnet']) || 
362 3
            $postParams['subnet'] === 'null'
363
        ) {
364 3
            return true;
365
        }
366 3
        return false;
367
    }
368

369
    /**
370
     * Verify the form field - Protocol.
371
     *
372
     * @param array $postParams The PSR-7 variable of $_POST
373
     *
374
     * @return bool
375
     */
376 3
    private function checkFieldProtocol($postParams): bool
377
    {
378 3
        if (in_array($postParams['protocol'], ['tcp', 'udp', 'all'])) {
379 3
            return true;
380
        }
381 3
        return false;
382
    }
383

384
    /**
385
     * Verify the form field - Action.
386
     *
387
     * @param array $postParams The PSR-7 variable of $_POST
388
     *
389
     * @return bool
390
     */
391 3
    private function checkFieldAction($postParams): bool
392
    {
393 3
        if (in_array($postParams['action'], ['allow', 'deny'])) {
394 3
            return true;
395
        }
396 3
        return false;
397
    }
398
}

Read our documentation on viewing source code .

Loading