From 877b9b70549bc9f7424e8b3ddca8d70277be89d8 Mon Sep 17 00:00:00 2001 From: andryyy Date: Thu, 27 Aug 2020 20:43:33 +0200 Subject: [PATCH] [Web] Sync jobs: Use STARTTLS instead of TLS; Feature: Allow to edit fail2ban-like regex filters in UI --- data/web/admin.php | 35 ++++++- data/web/css/site/admin.css | 4 + data/web/edit.php | 6 +- data/web/inc/functions.fail2ban.inc.php | 128 ++++++++++++++++++++---- data/web/js/site/admin.js | 19 +++- data/web/lang/lang.de.json | 5 +- data/web/lang/lang.en.json | 3 + data/web/modals/mailbox.php | 6 +- data/web/modals/user.php | 6 +- 9 files changed, 176 insertions(+), 36 deletions(-) diff --git a/data/web/admin.php b/data/web/admin.php index fbf09035..bf482155 100644 --- a/data/web/admin.php +++ b/data/web/admin.php @@ -698,6 +698,7 @@ if (!isset($_SESSION['gal']) && $license_cache = $redis->Get('LICENSE_STATUS_CAC +

@@ -708,11 +709,41 @@ if (!isset($_SESSION['gal']) && $license_cache = $redis->Get('LICENSE_STATUS_CAC
- - + +

+

+

+
+ + + + + + + $regex_val) { + ?> + + + + + + +
IDRegExp 
+

+ + + +

+
+

diff --git a/data/web/inc/functions.fail2ban.inc.php b/data/web/inc/functions.fail2ban.inc.php index 066c8d59..72d60062 100644 --- a/data/web/inc/functions.fail2ban.inc.php +++ b/data/web/inc/functions.fail2ban.inc.php @@ -10,6 +10,7 @@ function fail2ban($_action, $_data = null) { } try { $f2b_options = json_decode($redis->Get('F2B_OPTIONS'), true); + $f2b_options['regex'] = json_decode($redis->Get('F2B_REGEX'), true); $wl = $redis->hGetAll('F2B_WHITELIST'); if (is_array($wl)) { foreach ($wl as $key => $value) { @@ -87,20 +88,101 @@ function fail2ban($_action, $_data = null) { ); return false; } - if (isset($_data['action']) && !empty($_data['network'])) { - $networks = (array) $_data['network']; - foreach ($networks as $network) { + // Start to read actions, if any + if (isset($_data['action'])) { + // Reset regex filters + if ($_data['action'] == "reset-regex") { try { + $redis->Del('F2B_REGEX'); + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('redis_error', $e) + ); + return false; + } + // Rules will also be recreated on log events, but rules may seem empty for a second in the UI + docker('post', 'netfilter-mailcow', 'restart'); + $fail_count = 0; + $regex_result = json_decode($redis->Get('F2B_REGEX'), true); + while (empty($regex_result) && $fail_count < 10) { + $regex_result = json_decode($redis->Get('F2B_REGEX'), true); + $fail_count++; + sleep(1); + } + if ($fail_count >= 10) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('reset_f2b_regex') + ); + return false; + } + } + elseif ($_data['action'] == "edit-regex") { + if (!empty($_data['regex'])) { + $rule_id = 1; + $regex_array = array(); + foreach($_data['regex'] as $regex) { + $regex_array[$rule_id] = $regex; + $rule_id++; + } + $redis->Set('F2B_REGEX', json_encode($regex_array, JSON_UNESCAPED_SLASHES)); + } + else { + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => print_r($_data, true) + ); + return false; + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('object_modified', htmlspecialchars($network)) + ); + return true; + } + + // Start actions in dependency of network + if (!empty($_data['network'])) { + $networks = (array)$_data['network']; + foreach ($networks as $network) { + // Unban network if ($_data['action'] == "unban") { if (valid_network($network)) { - $redis->hSet('F2B_QUEUE_UNBAN', $network, 1); + try { + $redis->hSet('F2B_QUEUE_UNBAN', $network, 1); + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('redis_error', $e) + ); + continue; + } } } + // Whitelist network elseif ($_data['action'] == "whitelist") { if (valid_network($network)) { - $redis->hSet('F2B_WHITELIST', $network, 1); - $redis->hDel('F2B_BLACKLIST', $network, 1); - $redis->hSet('F2B_QUEUE_UNBAN', $network, 1); + try { + $redis->hSet('F2B_WHITELIST', $network, 1); + $redis->hDel('F2B_BLACKLIST', $network, 1); + $redis->hSet('F2B_QUEUE_UNBAN', $network, 1); + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('redis_error', $e) + ); + continue; + } } else { $_SESSION['return'][] = array( @@ -111,6 +193,7 @@ function fail2ban($_action, $_data = null) { continue; } } + // Blacklist network elseif ($_data['action'] == "blacklist") { if (valid_network($network) && !in_array($network, array( '0.0.0.0', @@ -119,9 +202,19 @@ function fail2ban($_action, $_data = null) { getenv('IPV4_NETWORK') . '0', getenv('IPV6_NETWORK') ))) { - $redis->hSet('F2B_BLACKLIST', $network, 1); - $redis->hDel('F2B_WHITELIST', $network, 1); - //$response = docker('post', 'netfilter-mailcow', 'restart'); + try { + $redis->hSet('F2B_BLACKLIST', $network, 1); + $redis->hDel('F2B_WHITELIST', $network, 1); + //$response = docker('post', 'netfilter-mailcow', 'restart'); + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('redis_error', $e) + ); + continue; + } } else { $_SESSION['return'][] = array( @@ -132,23 +225,16 @@ function fail2ban($_action, $_data = null) { continue; } } - } - catch (RedisException $e) { $_SESSION['return'][] = array( - 'type' => 'danger', + 'type' => 'success', 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('redis_error', $e) + 'msg' => array('object_modified', htmlspecialchars($network)) ); - continue; } - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('object_modified', htmlspecialchars($network)) - ); + return true; } - return true; } + // Start default edit without specific action $is_now = fail2ban('get'); if (!empty($is_now)) { $ban_time = intval((isset($_data['ban_time'])) ? $_data['ban_time'] : $is_now['ban_time']); diff --git a/data/web/js/site/admin.js b/data/web/js/site/admin.js index 027ff96f..cb2c91ea 100644 --- a/data/web/js/site/admin.js +++ b/data/web/js/site/admin.js @@ -460,11 +460,17 @@ jQuery(function($){ } }) // App links - function add_table_row(table_id) { + function add_table_row(table_id, type) { var row = $(''); + if (type == "app_link") { cols = ''; cols += ''; - cols += 'Remove row'; + cols += '">' + lang.remove_row + ''; + } else if (type == "f2b_regex") { + cols = ''; + cols += ''; + cols += '' + lang.remove_row + ''; + } row.append(cols); table_id.append(row); } @@ -472,8 +478,15 @@ jQuery(function($){ e.preventDefault(); $(this).parents('tr').remove(); }); + $('#f2b_regex_table').on('click', 'tr a', function (e) { + e.preventDefault(); + $(this).parents('tr').remove(); + }); $('#add_app_link_row').click(function() { - add_table_row($('#app_link_table')); + add_table_row($('#app_link_table'), "app_link"); + }); + $('#add_f2b_regex_row').click(function() { + add_table_row($('#f2b_regex_table'), "f2b_regex"); }); }); diff --git a/data/web/lang/lang.de.json b/data/web/lang/lang.de.json index a63b3350..876dfc82 100644 --- a/data/web/lang/lang.de.json +++ b/data/web/lang/lang.de.json @@ -74,7 +74,7 @@ "password": "Passwort", "password_repeat": "Passwort wiederholen", "port": "Port", - "post_domain_add": "Der SOGo-Container, \"sogo-mailcow\" muss nach dem Hinzufügen einer Domain neugestartet werden!

Im Anschluss sollte die DNS-Konfiguration der Domain überprüft- und \"acme-mailcow\" gegebenenfalls neugestartet werden, um Änderungen am Zertifikat zu übernehmen (autoconfig.<domain>, autodiscover.<domain>).
Dieser Schritt ist optional und wird alle 24 Stunden automatisch ausgeführt.", + "post_domain_add": "Der SOGo-Container - \"sogo-mailcow\" - muss nach dem Hinzufügen einer Domain neugestartet werden!

Im Anschluss sollte die DNS-Konfiguration der Domain überprüft- und \"acme-mailcow\" gegebenenfalls neugestartet werden, um Änderungen am Zertifikat zu übernehmen (autoconfig.<domain>, autodiscover.<domain>).
Dieser Schritt ist optional und wird alle 24 Stunden automatisch ausgeführt.", "private_comment": "Privater Kommentar", "public_comment": "Öffentlicher Kommentar", "quota_mb": "Speicherplatz (MiB)", @@ -165,11 +165,13 @@ "excludes": "Diese Empfänger ausschließen", "f2b_ban_time": "Bannzeit in Sekunden", "f2b_blacklist": "Blacklist für Netzwerke und Hosts", + "f2b_filter": "Regex Filter", "f2b_list_info": "Ein Host oder Netzwerk auf der Blacklist wird immer eine Whitelist-Einheit überwiegen. Die Aktualisierung der Liste dauert einige Sekunden.", "f2b_max_attempts": "Max. Versuche", "f2b_netban_ipv4": "Netzbereich für IPv4-Bans (8-32)", "f2b_netban_ipv6": "Netzbereich für IPv6-Bans (8-128)", "f2b_parameters": "Fail2ban-Parameter", + "f2b_regex_info": "Berücksichtigte Logs: SOGo, Postfix, Dovecot, PHP-FPM.", "f2b_retry_window": "Wiederholungen im Zeitraum von (s)", "f2b_whitelist": "Whitelist für Netzwerke und Hosts", "filter_table": "Tabelle filtern", @@ -396,6 +398,7 @@ "redis_error": "Redis Fehler: %s", "relayhost_invalid": "Mapeintrag %s ist ungültig", "release_send_failed": "Die Nachricht konnte nicht versendet werden: %s", + "reset_f2b_regex": "Regex Filter konnten nicht in vorgegebener Zeit zurückgesetzt werden, bitte erneut versuchen oder die Webseite neu laden.", "resource_invalid": "Ressourcenname %s ist ungültig", "rl_timeframe": "Ratelimit-Zeitraum ist inkorrekt", "rspamd_ui_pw_length": "Rspamd UI-Passwort muss mindestens 6 Zeichen lang sein", diff --git a/data/web/lang/lang.en.json b/data/web/lang/lang.en.json index f416d025..be87b83b 100644 --- a/data/web/lang/lang.en.json +++ b/data/web/lang/lang.en.json @@ -164,11 +164,13 @@ "excludes": "Excludes these recipients", "f2b_ban_time": "Ban time (s)", "f2b_blacklist": "Blacklisted networks/hosts", + "f2b_filter": "Regex filters", "f2b_list_info": "A blacklisted host or network will always outweigh a whitelist entity. List updates will take a few seconds to be applied.", "f2b_max_attempts": "Max. attempts", "f2b_netban_ipv4": "IPv4 subnet size to apply ban on (8-32)", "f2b_netban_ipv6": "IPv6 subnet size to apply ban on (8-128)", "f2b_parameters": "Fail2ban parameters", + "f2b_regex_info": "Logs taken into consideration: SOGo, Postfix, Dovecot, PHP-FPM.", "f2b_retry_window": "Retry window (s) for max. attempts", "f2b_whitelist": "Whitelisted networks/hosts", "filter_table": "Filter table", @@ -395,6 +397,7 @@ "redis_error": "Redis error: %s", "relayhost_invalid": "Map entry %s is invalid", "release_send_failed": "Message could not be released: %s", + "reset_f2b_regex": "Regex filter could not be reset in time, please try again or wait a few more seconds and reload the website.", "resource_invalid": "Resource name %s is invalid", "rl_timeframe": "Rate limit time frame is incorrect", "rspamd_ui_pw_length": "Rspamd UI password should be at least 6 chars long", diff --git a/data/web/modals/mailbox.php b/data/web/modals/mailbox.php index 063a1066..d0a82f47 100644 --- a/data/web/modals/mailbox.php +++ b/data/web/modals/mailbox.php @@ -448,9 +448,9 @@ if (!isset($_SESSION['mailcow_cc_role'])) {
diff --git a/data/web/modals/user.php b/data/web/modals/user.php index e4c18611..216717e2 100644 --- a/data/web/modals/user.php +++ b/data/web/modals/user.php @@ -44,9 +44,9 @@ if (!isset($_SESSION['mailcow_cc_role'])) {