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\Kernel;
24

25
use Shieldon\Firewall\Kernel;
26
use function Shieldon\Firewall\__;
27
use function Shieldon\Firewall\get_cpu_usage;
28
use function Shieldon\Firewall\get_memory_usage;
29
use function file_exists;
30
use function file_put_contents;
31
use function filter_var;
32
use function is_writable;
33
use function time;
34

35
/*
36
 * @since 1.0.0
37
 */
38
trait RuleTrait
39
{
40
    /**
41
     * The events.
42
     *
43
     * @var array
44
     */
45
    protected $event = [
46

47
        // Update rule table when this value true.
48
        'update_rule_table' => false,
49

50
        // Send notifications when this value true.
51
        'trigger_messengers' => false,
52
    ];
53

54
    /**
55
     * Set the message body.
56
     *
57
     * @param string $message The message text.
58
     *
59
     * @return void
60
     */
61
    abstract protected function setMessageBody(string $message = ''): void;
62

63
    /**
64
     * Save and return the result identifier.
65
     * This method is for passing value from traits.
66
     *
67
     * @param int $resultCode The result identifier.
68
     *
69
     * @return int
70
     */
71
    abstract protected function setResultCode(int $resultCode): int;
72

73
    /**
74
     * Look up the rule table.
75
     *
76
     * If a specific IP address doesn't exist, return false. 
77
     * Otherwise, return true.
78
     *
79
     * @return bool
80
     */
81 3
    protected function isRuleExist()
82
    {
83 3
        $ipRule = $this->driver->get($this->ip, 'rule');
84

85 3
        if (empty($ipRule)) {
86 3
            return false;
87
        }
88

89 3
        $this->reason = $ipRule['reason'];
90

91 3
        $ruleType = (int) $ipRule['type'];
92

93
        // Apply the status code.
94 3
        $this->setResultCode($ruleType);
95

96 3
        if ($ruleType === kernel::ACTION_ALLOW) {
97 3
            return true;
98
        }
99

100
        // Current visitor has been blocked. If he still attempts accessing the site, 
101
        // then we can drop him into the permanent block list.
102 3
        $attempts = $ipRule['attempts'] ?? 0;
103 3
        $attempts = (int) $attempts;
104 3
        $now = time();
105 3
        $logData = [];
106 3
        $handleType = 0;
107

108 3
        $logData['log_ip']     = $ipRule['log_ip'];
109 3
        $logData['ip_resolve'] = $ipRule['ip_resolve'];
110 3
        $logData['time']       = $now;
111 3
        $logData['type']       = $ipRule['type'];
112 3
        $logData['reason']     = $ipRule['reason'];
113 3
        $logData['attempts']   = $attempts;
114

115
        // @since 0.2.0
116 3
        $attemptPeriod = $this->properties['record_attempt_detection_period'];
117 3
        $attemptReset  = $this->properties['reset_attempt_counter'];
118

119 3
        $lastTimeDiff = $now - $ipRule['time'];
120

121 3
        if ($lastTimeDiff <= $attemptPeriod) {
122 3
            $logData['attempts'] = ++$attempts;
123
        }
124

125 3
        if ($lastTimeDiff > $attemptReset) {
126 3
            $logData['attempts'] = 0;
127
        }
128

129 3
        if ($ruleType === kernel::ACTION_TEMPORARILY_DENY) {
130 3
            $ratd = $this->determineAttemptsTemporaryDeny($logData, $handleType, $attempts);
131 3
            $logData = $ratd['log_data'];
132 3
            $handleType = $ratd['handle_type'];
133
        }
134

135 3
        if ($ruleType === kernel::ACTION_DENY) {
136 3
            $rapd = $this->determineAttemptsPermanentDeny($logData, $handleType, $attempts);
137 3
            $logData = $rapd['log_data'];
138 3
            $handleType = $rapd['handle_type'];
139
        }
140

141
        // We only update data when `deny_attempt_enable` is enable.
142
        // Because we want to get the last visited time and attempt counter.
143
        // Otherwise, we don't update it everytime to avoid wasting CPU resource.
144 3
        if ($this->event['update_rule_table']) {
145 3
            $this->driver->save($this->ip, $logData, 'rule');
146
        }
147

148
        // Notify this event to messenger.
149 3
        if ($this->event['trigger_messengers']) {
150 3
            $message = $this->prepareMessengerBody($logData, $handleType);
151

152
            // Method from MessageTrait.
153 3
            $this->setMessageBody($message);
154
        }
155

156 3
        return true;
157
    }
158

159
    /**
160
     * Record the attempts when the user is temporarily denied by rule table.
161
     *
162
     * @param array $logData    The log data.
163
     * @param int   $handleType The type for i18n string of the message.
164
     * @param int   $attempts   The attempt times.
165
     * 
166
     * @return array
167
     */
168 3
    protected function determineAttemptsTemporaryDeny(array $logData, int $handleType, int $attempts): array
169
    {
170 3
        if ($this->properties['deny_attempt_enable']['data_circle']) {
171 3
            $this->event['update_rule_table'] = true;
172

173 3
            $buffer = $this->properties['deny_attempt_buffer']['data_circle'];
174

175 3
            if ($attempts >= $buffer) {
176

177 3
                if ($this->properties['deny_attempt_notify']['data_circle']) {
178 3
                    $this->event['trigger_messengers'] = true;
179
                }
180

181 3
                $logData['type'] = kernel::ACTION_DENY;
182

183
                // Reset this value for next checking process - iptables.
184 3
                $logData['attempts'] = 0;
185 3
                $handleType = 1;
186
            }
187
        }
188

189
        return [
190 3
            'log_data' => $logData,
191 3
            'handle_type' => $handleType,
192
        ];
193
    }
194

195
    /**
196
     * Record the attempts when the user is permanently denied by rule table.
197
     *
198
     * @param array $logData    The log data.
199
     * @param int   $handleType The type for i18n string of the message.
200
     * @param int   $attempts   The attempt times.
201
     * 
202
     * @return array
203
     */
204 3
    protected function determineAttemptsPermanentDeny(array $logData, int $handleType, int $attempts): array
205
    {
206 3
        if ($this->properties['deny_attempt_enable']['system_firewall']) {
207 3
            $this->event['update_rule_table'] = true;
208

209
            // For the requests that are already banned, but they are still attempting access, that means 
210
            // that they are programmably accessing your website. Consider put them in the system-layer fireall
211
            // such as IPTABLE.
212 3
            $bufferIptable = $this->properties['deny_attempt_buffer']['system_firewall'];
213

214 3
            if ($attempts >= $bufferIptable) {
215

216 3
                if ($this->properties['deny_attempt_notify']['system_firewall']) {
217 3
                    $this->event['trigger_messengers'] = true;
218
                }
219

220 3
                $folder = rtrim($this->properties['iptables_watching_folder'], '/');
221

222 3
                if (file_exists($folder) && is_writable($folder)) {
223 3
                    $filePath = $folder . '/iptables_queue.log';
224

225
                    // command, ipv4/6, ip, subnet, port, protocol, action
226
                    // add,4,127.0.0.1,null,all,all,drop  (example)
227
                    // add,4,127.0.0.1,null,80,tcp,drop   (example)
228 3
                    $command = 'add,4,' . $this->ip . ',null,all,all,deny';
229

230 3
                    if (filter_var($this->ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
231 3
                        $command = 'add,6,' . $this->ip . ',null,all,allow';
232
                    }
233

234
                    // Add this IP address to itables_queue.log
235
                    // Use `bin/iptables.sh` for adding it into IPTABLES. See document for more information. 
236 3
                    file_put_contents($filePath, $command . "\n", FILE_APPEND | LOCK_EX);
237

238 3
                    $logData['attempts'] = 0;
239 3
                    $handleType = 2;
240
                }
241
            }
242
        }
243

244
        return [
245 3
            'log_data' => $logData,
246 3
            'handle_type' => $handleType,
247
        ];
248
    }
249

250
    /**
251
     * Prepare the message body for messenger modules to sent.
252
     *
253
     * @param array $logData    The log data.
254
     * @param int   $handleType The type for i18n string of the message.
255
     * 
256
     * @return string
257
     */
258 3
    protected function prepareMessengerBody(array $logData, int $handleType): string
259
    {
260
        // The data strings that will be appended to message body.
261
        $prepareMessageData = [
262 3
            __('core', 'messenger_text_ip')       => $logData['log_ip'],
263 3
            __('core', 'messenger_text_rdns')     => $logData['ip_resolve'],
264 3
            __('core', 'messenger_text_reason')   => __('core', 'messenger_text_reason_code_' . $logData['reason']),
265 3
            __('core', 'messenger_text_handle')   => __('core', 'messenger_text_handle_type_' . $handleType),
266 3
            __('core', 'messenger_text_system')   => '',
267 3
            __('core', 'messenger_text_cpu')      => get_cpu_usage(),
268 3
            __('core', 'messenger_text_memory')   => get_memory_usage(),
269 3
            __('core', 'messenger_text_time')     => date('Y-m-d H:i:s', $logData['time']),
270 3
            __('core', 'messenger_text_timezone') => date_default_timezone_get(),
271
        ];
272

273 3
        $message = __('core', 'messenger_notification_subject', 'Notification for {0}', [$this->ip]) . "\n\n";
274

275 3
        foreach ($prepareMessageData as $key => $value) {
276 3
            $message .= $key . ': ' . $value . "\n";
277
        }
278

279 3
        return $message;
280
    }
281
}

Read our documentation on viewing source code .

Loading