blob: 6f6b024f9db21c5ae19d6424a9cbd52af8f87dcb [file] [log] [blame]
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001<?php
2function fail2ban($_action, $_data = null) {
3 global $redis;
4 $_data_log = $_data;
5 switch ($_action) {
6 case 'get':
7 $f2b_options = array();
8 if ($_SESSION['mailcow_cc_role'] != "admin") {
9 return false;
10 }
11 try {
12 $f2b_options = json_decode($redis->Get('F2B_OPTIONS'), true);
13 $f2b_options['regex'] = json_decode($redis->Get('F2B_REGEX'), true);
14 $wl = $redis->hGetAll('F2B_WHITELIST');
15 if (is_array($wl)) {
16 foreach ($wl as $key => $value) {
17 $tmp_wl_data[] = $key;
18 }
19 if (isset($tmp_wl_data)) {
20 natsort($tmp_wl_data);
21 $f2b_options['whitelist'] = implode(PHP_EOL, $tmp_wl_data);
22 }
23 else {
24 $f2b_options['whitelist'] = "";
25 }
26 }
27 else {
28 $f2b_options['whitelist'] = "";
29 }
30 $bl = $redis->hGetAll('F2B_BLACKLIST');
31 if (is_array($bl)) {
32 foreach ($bl as $key => $value) {
33 $tmp_bl_data[] = $key;
34 }
35 if (isset($tmp_bl_data)) {
36 natsort($tmp_bl_data);
37 $f2b_options['blacklist'] = implode(PHP_EOL, $tmp_bl_data);
38 }
39 else {
40 $f2b_options['blacklist'] = "";
41 }
42 }
43 else {
44 $f2b_options['blacklist'] = "";
45 }
46 $pb = $redis->hGetAll('F2B_PERM_BANS');
47 if (is_array($pb)) {
48 foreach ($pb as $key => $value) {
49 $f2b_options['perm_bans'][] = $key;
50 }
51 }
52 else {
53 $f2b_options['perm_bans'] = "";
54 }
55 $active_bans = $redis->hGetAll('F2B_ACTIVE_BANS');
56 $queue_unban = $redis->hGetAll('F2B_QUEUE_UNBAN');
57 if (is_array($active_bans)) {
58 foreach ($active_bans as $network => $banned_until) {
59 $queued_for_unban = (isset($queue_unban[$network]) && $queue_unban[$network] == 1) ? 1 : 0;
60 $difference = $banned_until - time();
61 $f2b_options['active_bans'][] = array(
62 'queued_for_unban' => $queued_for_unban,
63 'network' => $network,
64 'banned_until' => sprintf('%02dh %02dm %02ds', ($difference/3600), ($difference/60%60), $difference%60)
65 );
66 }
67 }
68 else {
69 $f2b_options['active_bans'] = "";
70 }
71 }
72 catch (RedisException $e) {
73 $_SESSION['return'][] = array(
74 'type' => 'danger',
75 'log' => array(__FUNCTION__, $_action, $_data_log),
76 'msg' => array('redis_error', $e)
77 );
78 return false;
79 }
80 return $f2b_options;
81 break;
82 case 'edit':
83 if ($_SESSION['mailcow_cc_role'] != "admin") {
84 $_SESSION['return'][] = array(
85 'type' => 'danger',
86 'log' => array(__FUNCTION__, $_action, $_data_log),
87 'msg' => 'access_denied'
88 );
89 return false;
90 }
91 // Start to read actions, if any
92 if (isset($_data['action'])) {
93 // Reset regex filters
94 if ($_data['action'] == "reset-regex") {
95 try {
96 $redis->Del('F2B_REGEX');
97 }
98 catch (RedisException $e) {
99 $_SESSION['return'][] = array(
100 'type' => 'danger',
101 'log' => array(__FUNCTION__, $_action, $_data_log),
102 'msg' => array('redis_error', $e)
103 );
104 return false;
105 }
106 // Rules will also be recreated on log events, but rules may seem empty for a second in the UI
107 docker('post', 'netfilter-mailcow', 'restart');
108 $fail_count = 0;
109 $regex_result = json_decode($redis->Get('F2B_REGEX'), true);
110 while (empty($regex_result) && $fail_count < 10) {
111 $regex_result = json_decode($redis->Get('F2B_REGEX'), true);
112 $fail_count++;
113 sleep(1);
114 }
115 if ($fail_count >= 10) {
116 $_SESSION['return'][] = array(
117 'type' => 'danger',
118 'log' => array(__FUNCTION__, $_action, $_data_log),
119 'msg' => array('reset_f2b_regex')
120 );
121 return false;
122 }
123 }
124 elseif ($_data['action'] == "edit-regex") {
125 if (!empty($_data['regex'])) {
126 $rule_id = 1;
127 $regex_array = array();
128 foreach($_data['regex'] as $regex) {
129 $regex_array[$rule_id] = $regex;
130 $rule_id++;
131 }
132 if (!empty($regex_array)) {
133 $redis->Set('F2B_REGEX', json_encode($regex_array, JSON_UNESCAPED_SLASHES));
134 }
135 }
136 else {
137 $_SESSION['return'][] = array(
138 'type' => 'success',
139 'log' => array(__FUNCTION__, $_action, $_data_log),
140 'msg' => print_r($_data, true)
141 );
142 return false;
143 }
144 $_SESSION['return'][] = array(
145 'type' => 'success',
146 'log' => array(__FUNCTION__, $_action, $_data_log),
147 'msg' => array('object_modified', htmlspecialchars($network))
148 );
149 return true;
150 }
151
152 // Start actions in dependency of network
153 if (!empty($_data['network'])) {
154 $networks = (array)$_data['network'];
155 foreach ($networks as $network) {
156 // Unban network
157 if ($_data['action'] == "unban") {
158 if (valid_network($network)) {
159 try {
160 $redis->hSet('F2B_QUEUE_UNBAN', $network, 1);
161 }
162 catch (RedisException $e) {
163 $_SESSION['return'][] = array(
164 'type' => 'danger',
165 'log' => array(__FUNCTION__, $_action, $_data_log),
166 'msg' => array('redis_error', $e)
167 );
168 continue;
169 }
170 }
171 }
172 // Whitelist network
173 elseif ($_data['action'] == "whitelist") {
174 if (empty($network)) { continue; }
175 if (valid_network($network)) {
176 try {
177 $redis->hSet('F2B_WHITELIST', $network, 1);
178 $redis->hDel('F2B_BLACKLIST', $network, 1);
179 $redis->hSet('F2B_QUEUE_UNBAN', $network, 1);
180 }
181 catch (RedisException $e) {
182 $_SESSION['return'][] = array(
183 'type' => 'danger',
184 'log' => array(__FUNCTION__, $_action, $_data_log),
185 'msg' => array('redis_error', $e)
186 );
187 continue;
188 }
189 }
190 else {
191 $_SESSION['return'][] = array(
192 'type' => 'danger',
193 'log' => array(__FUNCTION__, $_action, $_data_log),
194 'msg' => array('network_host_invalid', $network)
195 );
196 continue;
197 }
198 }
199 // Blacklist network
200 elseif ($_data['action'] == "blacklist") {
201 if (empty($network)) { continue; }
202 if (valid_network($network) && !in_array($network, array(
203 '0.0.0.0',
204 '0.0.0.0/0',
205 getenv('IPV4_NETWORK') . '0/24',
206 getenv('IPV4_NETWORK') . '0',
207 getenv('IPV6_NETWORK')
208 ))) {
209 try {
210 $redis->hSet('F2B_BLACKLIST', $network, 1);
211 $redis->hDel('F2B_WHITELIST', $network, 1);
212 //$response = docker('post', 'netfilter-mailcow', 'restart');
213 }
214 catch (RedisException $e) {
215 $_SESSION['return'][] = array(
216 'type' => 'danger',
217 'log' => array(__FUNCTION__, $_action, $_data_log),
218 'msg' => array('redis_error', $e)
219 );
220 continue;
221 }
222 }
223 else {
224 $_SESSION['return'][] = array(
225 'type' => 'danger',
226 'log' => array(__FUNCTION__, $_action, $_data_log),
227 'msg' => array('network_host_invalid', $network)
228 );
229 continue;
230 }
231 }
232 $_SESSION['return'][] = array(
233 'type' => 'success',
234 'log' => array(__FUNCTION__, $_action, $_data_log),
235 'msg' => array('object_modified', htmlspecialchars($network))
236 );
237 }
238 return true;
239 }
240 }
241 // Start default edit without specific action
242 $is_now = fail2ban('get');
243 if (!empty($is_now)) {
244 $ban_time = intval((isset($_data['ban_time'])) ? $_data['ban_time'] : $is_now['ban_time']);
245 $max_attempts = intval((isset($_data['max_attempts'])) ? $_data['max_attempts'] : $is_now['max_attempts']);
246 $retry_window = intval((isset($_data['retry_window'])) ? $_data['retry_window'] : $is_now['retry_window']);
247 $netban_ipv4 = intval((isset($_data['netban_ipv4'])) ? $_data['netban_ipv4'] : $is_now['netban_ipv4']);
248 $netban_ipv6 = intval((isset($_data['netban_ipv6'])) ? $_data['netban_ipv6'] : $is_now['netban_ipv6']);
249 $wl = (isset($_data['whitelist'])) ? $_data['whitelist'] : $is_now['whitelist'];
250 $bl = (isset($_data['blacklist'])) ? $_data['blacklist'] : $is_now['blacklist'];
251 }
252 else {
253 $_SESSION['return'][] = array(
254 'type' => 'danger',
255 'log' => array(__FUNCTION__, $_action, $_data_log),
256 'msg' => 'access_denied'
257 );
258 return false;
259 }
260 $f2b_options = array();
261 $f2b_options['ban_time'] = ($ban_time < 60) ? 60 : $ban_time;
262 $f2b_options['netban_ipv4'] = ($netban_ipv4 < 8) ? 8 : $netban_ipv4;
263 $f2b_options['netban_ipv6'] = ($netban_ipv6 < 8) ? 8 : $netban_ipv6;
264 $f2b_options['netban_ipv4'] = ($netban_ipv4 > 32) ? 32 : $netban_ipv4;
265 $f2b_options['netban_ipv6'] = ($netban_ipv6 > 128) ? 128 : $netban_ipv6;
266 $f2b_options['max_attempts'] = ($max_attempts < 1) ? 1 : $max_attempts;
267 $f2b_options['retry_window'] = ($retry_window < 1) ? 1 : $retry_window;
268 try {
269 $redis->Set('F2B_OPTIONS', json_encode($f2b_options));
270 $redis->Del('F2B_WHITELIST');
271 $redis->Del('F2B_BLACKLIST');
272 if(!empty($wl)) {
273 $wl_array = array_map('trim', preg_split( "/( |,|;|\n)/", $wl));
274 $wl_array = array_filter($wl_array);
275 if (is_array($wl_array)) {
276 foreach ($wl_array as $wl_item) {
277 if (valid_network($wl_item) || valid_hostname($wl_item)) {
278 $redis->hSet('F2B_WHITELIST', $wl_item, 1);
279 }
280 else {
281 $_SESSION['return'][] = array(
282 'type' => 'danger',
283 'log' => array(__FUNCTION__, $_action, $_data_log),
284 'msg' => array('network_host_invalid', $wl_item)
285 );
286 continue;
287 }
288 }
289 }
290 }
291 if(!empty($bl)) {
292 $bl_array = array_map('trim', preg_split( "/( |,|;|\n)/", $bl));
293 $bl_array = array_filter($bl_array);
294 if (is_array($bl_array)) {
295 foreach ($bl_array as $bl_item) {
296 if (valid_network($bl_item) && !in_array($bl_item, array(
297 '0.0.0.0',
298 '0.0.0.0/0',
299 getenv('IPV4_NETWORK') . '0/24',
300 getenv('IPV4_NETWORK') . '0',
301 getenv('IPV6_NETWORK')
302 ))) {
303 $redis->hSet('F2B_BLACKLIST', $bl_item, 1);
304 }
305 else {
306 $_SESSION['return'][] = array(
307 'type' => 'danger',
308 'log' => array(__FUNCTION__, $_action, $_data_log),
309 'msg' => array('network_host_invalid', $bl_item)
310 );
311 continue;
312 }
313 }
314 }
315 }
316 }
317 catch (RedisException $e) {
318 $_SESSION['return'][] = array(
319 'type' => 'danger',
320 'log' => array(__FUNCTION__, $_action, $_data_log),
321 'msg' => array('redis_error', $e)
322 );
323 return false;
324 }
325 $_SESSION['return'][] = array(
326 'type' => 'success',
327 'log' => array(__FUNCTION__, $_action, $_data_log),
328 'msg' => 'f2b_modified'
329 );
330 break;
331 }
332}