public
nobgit
read
NobMail
Based on mailcow: dockerized
Languages
Repository composition by tracked source files.
PHP
49%
JavaScript
35%
HTML
9%
CSS
4%
Shell
2%
Python
1%
Lua
0%
Perl
0%
Ruby
0%
SCSS
0%
Create file
Wiki Documentation
Clone
https://nobgit.com/orgs/nobgit/nobmail.git
ssh://[email protected]:2222/orgs/nobgit/nobmail.git
Commit
[netfilter] fix negative timer, no unbanning of IPs (#6575)
* [netfilter] added debug logs and updated autopurge * updated "Allow/Blacklist" terms * netfilter: bumped compose version * netfilter: changed black/whitelist terms in code --------- Co-authored-by: Denis Evers <[email protected]> Co-authored-by: DerLinkman <[email protected]>
95eb350f
data/Dockerfiles/netfilter/main.py | 165 +++++++++++++++++----------
data/web/inc/vars.inc.php | 12 +-
data/web/lang/lang.de-de.json | 23 ++--
data/web/lang/lang.en-gb.json | 27 ++---
data/web/templates/admin/tab-config-f2b.twig | 4 +-
docker-compose.yml | 2 +-
6 files changed, 138 insertions(+), 95 deletions(-)
Diff
diff --git a/data/Dockerfiles/netfilter/main.py b/data/Dockerfiles/netfilter/main.py
index 2b332d20..2232d0d1 100644
--- a/data/Dockerfiles/netfilter/main.py
+++ b/data/Dockerfiles/netfilter/main.py
@@ -1,5 +1,7 @@
#!/usr/bin/env python3
+DEBUG = False
+
import re
import os
import sys
@@ -20,10 +22,13 @@ from modules.Logger import Logger
from modules.IPTables import IPTables
from modules.NFTables import NFTables
+def logdebug(msg):
+ if DEBUG:
+ logger.logInfo("DEBUG: %s" % msg)
-# globals
+# Globals
WHITELIST = []
-BLACKLIST= []
+BLACKLIST = []
bans = {}
quit_now = False
exit_code = 0
@@ -33,12 +38,10 @@ r = None
pubsub = None
clear_before_quit = False
-
def refreshF2boptions():
global f2boptions
global quit_now
global exit_code
-
f2boptions = {}
if not r.get('F2B_OPTIONS'):
@@ -52,8 +55,9 @@ def refreshF2boptions():
else:
try:
f2boptions = json.loads(r.get('F2B_OPTIONS'))
- except ValueError:
- logger.logCrit('Error loading F2B options: F2B_OPTIONS is not json')
+ except ValueError as e:
+ logger.logCrit(
+ 'Error loading F2B options: F2B_OPTIONS is not json. Exception: %s' % e)
quit_now = True
exit_code = 2
@@ -61,15 +65,15 @@ def refreshF2boptions():
r.set('F2B_OPTIONS', json.dumps(f2boptions, ensure_ascii=False))
def verifyF2boptions(f2boptions):
- verifyF2boption(f2boptions,'ban_time', 1800)
- verifyF2boption(f2boptions,'max_ban_time', 10000)
- verifyF2boption(f2boptions,'ban_time_increment', True)
- verifyF2boption(f2boptions,'max_attempts', 10)
- verifyF2boption(f2boptions,'retry_window', 600)
- verifyF2boption(f2boptions,'netban_ipv4', 32)
- verifyF2boption(f2boptions,'netban_ipv6', 128)
- verifyF2boption(f2boptions,'banlist_id', str(uuid.uuid4()))
- verifyF2boption(f2boptions,'manage_external', 0)
+ verifyF2boption(f2boptions, 'ban_time', 1800)
+ verifyF2boption(f2boptions, 'max_ban_time', 10000)
+ verifyF2boption(f2boptions, 'ban_time_increment', True)
+ verifyF2boption(f2boptions, 'max_attempts', 10)
+ verifyF2boption(f2boptions, 'retry_window', 600)
+ verifyF2boption(f2boptions, 'netban_ipv4', 32)
+ verifyF2boption(f2boptions, 'netban_ipv6', 128)
+ verifyF2boption(f2boptions, 'banlist_id', str(uuid.uuid4()))
+ verifyF2boption(f2boptions, 'manage_external', 0)
def verifyF2boption(f2boptions, f2boption, f2bdefault):
f2boptions[f2boption] = f2boptions[f2boption] if f2boption in f2boptions and f2boptions[f2boption] is not None else f2bdefault
@@ -111,7 +115,7 @@ def get_ip(address):
def ban(address):
global f2boptions
global lock
-
+ logdebug("ban() called with address=%s" % address)
refreshF2boptions()
MAX_ATTEMPTS = int(f2boptions['max_attempts'])
RETRY_WINDOW = int(f2boptions['retry_window'])
@@ -119,31 +123,43 @@ def ban(address):
NETBAN_IPV6 = '/' + str(f2boptions['netban_ipv6'])
ip = get_ip(address)
- if not ip: return
+ if not ip:
+ logdebug("No valid IP -- skipping ban()")
+ return
address = str(ip)
self_network = ipaddress.ip_network(address)
with lock:
temp_whitelist = set(WHITELIST)
- if temp_whitelist:
- for wl_key in temp_whitelist:
- wl_net = ipaddress.ip_network(wl_key, False)
- if wl_net.overlaps(self_network):
- logger.logInfo('Address %s is whitelisted by rule %s' % (self_network, wl_net))
- return
-
- net = ipaddress.ip_network((address + (NETBAN_IPV4 if type(ip) is ipaddress.IPv4Address else NETBAN_IPV6)), strict=False)
+ logdebug("Checking if %s overlaps with any WHITELIST entries" % self_network)
+ if temp_whitelist:
+ for wl_key in temp_whitelist:
+ wl_net = ipaddress.ip_network(wl_key, False)
+ logdebug("Checking overlap between %s and %s" % (self_network, wl_net))
+ if wl_net.overlaps(self_network):
+ logger.logInfo(
+ 'Address %s is allowlisted by rule %s' % (self_network, wl_net))
+ return
+
+ net = ipaddress.ip_network(
+ (address + (NETBAN_IPV4 if type(ip) is ipaddress.IPv4Address else NETBAN_IPV6)), strict=False)
net = str(net)
+ logdebug("Ban net: %s" % net)
if not net in bans:
bans[net] = {'attempts': 0, 'last_attempt': 0, 'ban_counter': 0}
+ logdebug("Initing new ban counter for %s" % net)
current_attempt = time.time()
+ logdebug("Current attempt ts=%s, previous: %s, retry_window: %s" %
+ (current_attempt, bans[net]['last_attempt'], RETRY_WINDOW))
if current_attempt - bans[net]['last_attempt'] > RETRY_WINDOW:
bans[net]['attempts'] = 0
+ logdebug("Ban counter for %s reset as window expired" % net)
bans[net]['attempts'] += 1
bans[net]['last_attempt'] = current_attempt
+ logdebug("%s attempts now %d" % (net, bans[net]['attempts']))
if bans[net]['attempts'] >= MAX_ATTEMPTS:
cur_time = int(round(time.time()))
@@ -151,34 +167,41 @@ def ban(address):
logger.logCrit('Banning %s for %d minutes' % (net, NET_BAN_TIME / 60 ))
if type(ip) is ipaddress.IPv4Address and int(f2boptions['manage_external']) != 1:
with lock:
+ logdebug("Calling tables.banIPv4(%s)" % net)
tables.banIPv4(net)
elif int(f2boptions['manage_external']) != 1:
with lock:
+ logdebug("Calling tables.banIPv6(%s)" % net)
tables.banIPv6(net)
+ logdebug("Updating F2B_ACTIVE_BANS[%s]=%d" %
+ (net, cur_time + NET_BAN_TIME))
r.hset('F2B_ACTIVE_BANS', '%s' % net, cur_time + NET_BAN_TIME)
else:
- logger.logWarn('%d more attempts in the next %d seconds until %s is banned' % (MAX_ATTEMPTS - bans[net]['attempts'], RETRY_WINDOW, net))
+ logger.logWarn('%d more attempts in the next %d seconds until %s is banned' % (
+ MAX_ATTEMPTS - bans[net]['attempts'], RETRY_WINDOW, net))
def unban(net):
global lock
-
+ logdebug("Calling unban() with net=%s" % net)
if not net in bans:
- logger.logInfo('%s is not banned, skipping unban and deleting from queue (if any)' % net)
- r.hdel('F2B_QUEUE_UNBAN', '%s' % net)
- return
-
+ logger.logInfo(
+ '%s is not banned, skipping unban and deleting from queue (if any)' % net)
+ r.hdel('F2B_QUEUE_UNBAN', '%s' % net)
+ return
logger.logInfo('Unbanning %s' % net)
if type(ipaddress.ip_network(net)) is ipaddress.IPv4Network:
with lock:
+ logdebug("Calling tables.unbanIPv4(%s)" % net)
tables.unbanIPv4(net)
else:
with lock:
+ logdebug("Calling tables.unbanIPv6(%s)" % net)
tables.unbanIPv6(net)
-
r.hdel('F2B_ACTIVE_BANS', '%s' % net)
r.hdel('F2B_QUEUE_UNBAN', '%s' % net)
if net in bans:
+ logdebug("Unban for %s, setting attempts=0, ban_counter+=1" % net)
bans[net]['attempts'] = 0
bans[net]['ban_counter'] += 1
@@ -204,17 +227,19 @@ def permBan(net, unban=False):
if is_unbanned:
r.hdel('F2B_PERM_BANS', '%s' % net)
- logger.logCrit('Removed host/network %s from blacklist' % net)
+ logger.logCrit('Removed host/network %s from denylist' % net)
elif is_banned:
r.hset('F2B_PERM_BANS', '%s' % net, int(round(time.time())))
- logger.logCrit('Added host/network %s to blacklist' % net)
+ logger.logCrit('Added host/network %s to denylist' % net)
def clear():
global lock
logger.logInfo('Clearing all bans')
for net in bans.copy():
+ logdebug("Unbanning net: %s" % net)
unban(net)
with lock:
+ logdebug("Clearing IPv4/IPv6 table")
tables.clearIPv4Table()
tables.clearIPv6Table()
try:
@@ -275,21 +300,35 @@ def snat6(snat_target):
def autopurge():
global f2boptions
-
+ logdebug("autopurge thread started")
while not quit_now:
+ logdebug("autopurge tick")
time.sleep(10)
refreshF2boptions()
MAX_ATTEMPTS = int(f2boptions['max_attempts'])
QUEUE_UNBAN = r.hgetall('F2B_QUEUE_UNBAN')
+ logdebug("QUEUE_UNBAN: %s" % QUEUE_UNBAN)
if QUEUE_UNBAN:
for net in QUEUE_UNBAN:
+ logdebug("Autopurge: unbanning queued net: %s" % net)
unban(str(net))
- for net in bans.copy():
- if bans[net]['attempts'] >= MAX_ATTEMPTS:
- NET_BAN_TIME = calcNetBanTime(bans[net]['ban_counter'])
- TIME_SINCE_LAST_ATTEMPT = time.time() - bans[net]['last_attempt']
- if TIME_SINCE_LAST_ATTEMPT > NET_BAN_TIME:
- unban(net)
+ # Only check expiry for actively banned IPs:
+ active_bans = r.hgetall('F2B_ACTIVE_BANS')
+ now = time.time()
+ for net_str, expire_str in active_bans.items():
+ logdebug("Checking ban expiry for (actively banned): %s" % net_str)
+ # Defensive: always process if timer missing or expired
+ try:
+ expire = float(expire_str)
+ except Exception:
+ logdebug("Invalid expire time for %s; unbanning" % net_str)
+ unban(net_str)
+ continue
+ time_left = expire - now
+ logdebug("Time left for %s: %.1f seconds" % (net_str, time_left))
+ if time_left <= 0:
+ logdebug("Ban expired for %s" % net_str)
+ unban(net_str)
def mailcowChainOrder():
global lock
@@ -359,7 +398,7 @@ def whitelistUpdate():
with lock:
if Counter(new_whitelist) != Counter(WHITELIST):
WHITELIST = new_whitelist
- logger.logInfo('Whitelist was changed, it has %s entries' % len(WHITELIST))
+ logger.logInfo('Allowlist was changed, it has %s entries' % len(WHITELIST))
time.sleep(60.0 - ((time.time() - start_time) % 60.0))
def blacklistUpdate():
@@ -375,7 +414,7 @@ def blacklistUpdate():
addban = set(new_blacklist).difference(BLACKLIST)
delban = set(BLACKLIST).difference(new_blacklist)
BLACKLIST = new_blacklist
- logger.logInfo('Blacklist was changed, it has %s entries' % len(BLACKLIST))
+ logger.logInfo('Denylist was changed, it has %s entries' % len(BLACKLIST))
if addban:
for net in addban:
permBan(net=net)
@@ -386,25 +425,25 @@ def blacklistUpdate():
def sigterm_quit(signum, frame):
global clear_before_quit
+ logdebug("SIGTERM received, setting clear_before_quit to True and exiting")
clear_before_quit = True
sys.exit(exit_code)
-def berfore_quit():
+def before_quit():
+ logdebug("before_quit called, clear_before_quit=%s" % clear_before_quit)
if clear_before_quit:
clear()
if pubsub is not None:
pubsub.unsubscribe()
-
if __name__ == '__main__':
- atexit.register(berfore_quit)
- signal.signal(signal.SIGTERM, sigterm_quit)
-
- # init Logger
logger = Logger()
+ logdebug("Sys.argv: %s" % sys.argv)
+ atexit.register(before_quit)
+ signal.signal(signal.SIGTERM, sigterm_quit)
- # init backend
backend = sys.argv[1]
+ logdebug("Backend: %s" % backend)
if backend == "nftables":
logger.logInfo('Using NFTables backend')
tables = NFTables(chain_name, logger)
@@ -412,16 +451,12 @@ if __name__ == '__main__':
logger.logInfo('Using IPTables backend')
tables = IPTables(chain_name, logger)
- # In case a previous session was killed without cleanup
clear()
-
- # Reinit MAILCOW chain
- # Is called before threads start, no locking
logger.logInfo("Initializing mailcow netfilter chain")
tables.initChainIPv4()
tables.initChainIPv6()
- if os.getenv("DISABLE_NETFILTER_ISOLATION_RULE").lower() in ("y", "yes"):
+ if os.getenv("DISABLE_NETFILTER_ISOLATION_RULE", "").lower() in ("y", "yes"):
logger.logInfo(f"Skipping {chain_name} isolation")
else:
logger.logInfo(f"Setting {chain_name} isolation")
@@ -432,23 +467,28 @@ if __name__ == '__main__':
try:
redis_slaveof_ip = os.getenv('REDIS_SLAVEOF_IP', '')
redis_slaveof_port = os.getenv('REDIS_SLAVEOF_PORT', '')
+ logdebug(
+ "Connecting redis (SLAVEOF_IP:%s, PORT:%s)" % (redis_slaveof_ip, redis_slaveof_port))
if "".__eq__(redis_slaveof_ip):
- r = redis.StrictRedis(host=os.getenv('IPV4_NETWORK', '172.22.1') + '.249', decode_responses=True, port=6379, db=0, password=os.environ['REDISPASS'])
+ r = redis.StrictRedis(
+ host=os.getenv('IPV4_NETWORK', '172.22.1') + '.249', decode_responses=True, port=6379, db=0, password=os.environ['REDISPASS'])
else:
- r = redis.StrictRedis(host=redis_slaveof_ip, decode_responses=True, port=redis_slaveof_port, db=0, password=os.environ['REDISPASS'])
+ r = redis.StrictRedis(
+ host=redis_slaveof_ip, decode_responses=True, port=redis_slaveof_port, db=0, password=os.environ['REDISPASS'])
r.ping()
pubsub = r.pubsub()
except Exception as ex:
- print('%s - trying again in 3 seconds' % (ex))
+ logdebug(
+ 'Redis connection failed: %s - trying again in 3 seconds' % (ex))
time.sleep(3)
else:
break
logger.set_redis(r)
+ logdebug("Redis connection established, setting up F2B keys")
- # rename fail2ban to netfilter
if r.exists('F2B_LOG'):
+ logdebug("Renaming F2B_LOG to NETFILTER_LOG")
r.rename('F2B_LOG', 'NETFILTER_LOG')
- # clear bans in redis
r.delete('F2B_ACTIVE_BANS')
r.delete('F2B_PERM_BANS')
@@ -463,7 +503,7 @@ if __name__ == '__main__':
snat_ip = os.getenv('SNAT_TO_SOURCE')
snat_ipo = ipaddress.ip_address(snat_ip)
if type(snat_ipo) is ipaddress.IPv4Address:
- snat4_thread = Thread(target=snat4,args=(snat_ip,))
+ snat4_thread = Thread(target=snat4, args=(snat_ip,))
snat4_thread.daemon = True
snat4_thread.start()
except ValueError:
@@ -499,4 +539,5 @@ if __name__ == '__main__':
while not quit_now:
time.sleep(0.5)
- sys.exit(exit_code)
+ logdebug("Exiting with code %s" % exit_code)
+ sys.exit(exit_code)
\ No newline at end of file
diff --git a/data/web/inc/vars.inc.php b/data/web/inc/vars.inc.php
index 802a41f9..671e2033 100644
--- a/data/web/inc/vars.inc.php
+++ b/data/web/inc/vars.inc.php
@@ -238,12 +238,12 @@ $FIDO2_FORMATS = array('apple', 'android-key', 'android-safetynet', 'fido-u2f',
// Set visible Rspamd maps in mailcow UI, do not change unless you know what you are doing
$RSPAMD_MAPS = array(
'regex' => array(
- 'Header-From: Blacklist' => 'global_mime_from_blacklist.map',
- 'Header-From: Whitelist' => 'global_mime_from_whitelist.map',
- 'Envelope Sender Blacklist' => 'global_smtp_from_blacklist.map',
- 'Envelope Sender Whitelist' => 'global_smtp_from_whitelist.map',
- 'Recipient Blacklist' => 'global_rcpt_blacklist.map',
- 'Recipient Whitelist' => 'global_rcpt_whitelist.map',
+ 'Header-From: Denylist' => 'global_mime_from_blacklist.map',
+ 'Header-From: Allowlist' => 'global_mime_from_whitelist.map',
+ 'Envelope Sender Denylist' => 'global_smtp_from_blacklist.map',
+ 'Envelope Sender Allowlist' => 'global_smtp_from_whitelist.map',
+ 'Recipient Denylist' => 'global_rcpt_blacklist.map',
+ 'Recipient Allowlist' => 'global_rcpt_whitelist.map',
'Fishy TLDS (only fired in combination with bad words)' => 'fishy_tlds.map',
'Bad Words (only fired in combination with fishy TLDs)' => 'bad_words.map',
'Bad Words DE (only fired in combination with fishy TLDs)' => 'bad_words_de.map',
diff --git a/data/web/lang/lang.de-de.json b/data/web/lang/lang.de-de.json
index 6e1b4d4c..df41b894 100644
--- a/data/web/lang/lang.de-de.json
+++ b/data/web/lang/lang.de-de.json
@@ -25,7 +25,7 @@
"sogo_access": "Verwalten des SOGo-Zugriffsrechts erlauben",
"sogo_profile_reset": "SOGo-Profil zurücksetzen",
"spam_alias": "Temporäre E-Mail-Aliasse",
- "spam_policy": "Blacklist/Whitelist",
+ "spam_policy": "Deny/Allowlist",
"spam_score": "Spam-Bewertung",
"syncjobs": "Sync Jobs",
"tls_policy": "Verschlüsselungsrichtlinie",
@@ -147,7 +147,7 @@
"arrival_time": "Ankunftszeit (Serverzeit)",
"authed_user": "Auth. Benutzer",
"ays": "Soll der Vorgang wirklich ausgeführt werden?",
- "ban_list_info": "Übersicht ausgesperrter Netzwerke: <b>Netzwerk (verbleibende Bannzeit) - [Aktionen]</b>.<br />IPs, die zum Entsperren eingereiht werden, verlassen die Liste aktiver Banns nach wenigen Sekunden.<br />Rote Labels sind Indikatoren für aktive Blacklist-Einträge.",
+ "ban_list_info": "Übersicht ausgesperrter Netzwerke: <b>Netzwerk (verbleibende Bannzeit) - [Aktionen]</b>.<br />IPs, die zum Entsperren eingereiht werden, verlassen die Liste aktiver Banns nach wenigen Sekunden.<br />Rote Labels sind Indikatoren für aktive Allowlist-Einträge.",
"change_logo": "Logo ändern",
"configuration": "Konfiguration",
"convert_html_to_text": "Konvertiere HTML zu reinem Text",
@@ -184,9 +184,9 @@
"excludes": "Diese Empfänger ausschließen",
"f2b_ban_time": "Bannzeit in Sekunden",
"f2b_ban_time_increment": "Bannzeit erhöht sich mit jedem Bann",
- "f2b_blacklist": "Blacklist für Netzwerke und Hosts",
+ "f2b_blacklist": "Denyliste 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. <b>Die Aktualisierung der Liste dauert einige Sekunden.</b>",
+ "f2b_list_info": "Ein Host oder Netzwerk auf der Denyliste wird immer eine Allowlist-Einheit überwiegen. <b>Die Aktualisierung der Liste dauert einige Sekunden.</b>",
"f2b_manage_external": "Fail2Ban extern verwalten",
"f2b_manage_external_info": "Fail2ban wird die Banlist weiterhin pflegen, jedoch werden keine aktiven Regeln zum blockieren gesetzt. Die unten generierte Banlist, kann verwendet werden, um den Datenverkehr extern zu blockieren.",
"f2b_max_attempts": "Max. Versuche",
@@ -196,7 +196,7 @@
"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",
+ "f2b_whitelist": "Allowliste für Netzwerke und Hosts",
"filter_table": "Tabelle filtern",
"force_sso_text": "Wenn ein externer OIDC-Provider konfiguriert ist, blendet diese Option die mailcow Loginform aus und zeigt nur den Single Sign-On-Button an.",
"force_sso": "mailcow Login deaktivieren und nur Single Sign-On anzeigen",
@@ -272,6 +272,7 @@
"message": "Nachricht",
"message_size": "Nachrichtengröße",
"nexthop": "Next Hop",
+ "needs_restart": "benötigt Neustart",
"no": "✕",
"no_active_bans": "Keine aktiven Banns",
"no_new_rows": "Keine weiteren Zeilen vorhanden",
@@ -354,8 +355,8 @@
"rspamd_com_settings": "Ein Name wird automatisch generiert. Beispielinhalte zur Einsicht stehen nachstehend bereit. Siehe auch <a href=\"https://rspamd.com/doc/configuration/settings.html#settings-structure\" target=\"_blank\">Rspamd docs</a>",
"rspamd_global_filters": "Globale Filter-Maps",
"rspamd_global_filters_agree": "Ich werde vorsichtig sein!",
- "rspamd_global_filters_info": "Globale Filter-Maps steuern globales White- und Blacklisting dieses Servers.",
- "rspamd_global_filters_regex": "Die akzeptierte Form für Einträge sind <b>ausschließlich</b> Regular Expressions.\r\n Trotz rudimentärer Überprüfung der Map, kann es zu fehlerhaften Einträgen kommen, die Rspamd im schlechtesten Fall mit unvorhersehbarer Funktionalität bestraft.<br>\r\n Das korrekte Format lautet \"/pattern/options\" (Beispiel: <code>/.+@domain\\.tld/i</code>).<br>\r\n Der Name der Map beschreibt die jeweilige Funktion.<br>\r\n Rspamd versucht die Maps umgehend aufzulösen. Bei Problemen sollte <a href=\"\" data-toggle=\"modal\" data-container=\"rspamd-mailcow\" data-target=\"#RestartContainer\">Rspamd manuell neugestartet werden</a>.<br>Elemente auf Blacklists sind von der Quarantäne ausgeschlossen.",
+ "rspamd_global_filters_info": "Globale Filter-Maps steuern globales Allow- und Denylisting dieses Servers.",
+ "rspamd_global_filters_regex": "Die akzeptierte Form für Einträge sind <b>ausschließlich</b> Regular Expressions.\r\n Trotz rudimentärer Überprüfung der Map, kann es zu fehlerhaften Einträgen kommen, die Rspamd im schlechtesten Fall mit unvorhersehbarer Funktionalität bestraft.<br>\r\n Das korrekte Format lautet \"/pattern/options\" (Beispiel: <code>/.+@domain\\.tld/i</code>).<br>\r\n Der Name der Map beschreibt die jeweilige Funktion.<br>\r\n Rspamd versucht die Maps umgehend aufzulösen. Bei Problemen sollte <a href=\"\" data-toggle=\"modal\" data-container=\"rspamd-mailcow\" data-target=\"#RestartContainer\">Rspamd manuell neugestartet werden</a>.<br>Elemente auf Denylisten sind von der Quarantäne ausgeschlossen.",
"rspamd_settings_map": "Rspamd-Settings-Map",
"sal_level": "Moo-Level",
"save": "Änderungen speichern",
@@ -747,7 +748,7 @@
"sogo_visible_info": "Diese Option hat lediglich Einfluss auf Objekte, die in SOGo darstellbar sind (geteilte oder nicht-geteilte Alias-Adressen mit dem Ziel mindestens einer lokalen Mailbox).",
"spam_alias": "Anpassen temporärer Alias-Adressen",
"spam_filter": "Spamfilter",
- "spam_policy": "Hinzufügen und Entfernen von Einträgen in White- und Blacklists",
+ "spam_policy": "Hinzufügen und Entfernen von Einträgen in Allow- und Denylisten",
"spam_score": "Einen benutzerdefiniterten Spam-Score festlegen",
"subfolder2": "Ziel-Ordner<br><small>(leer = kein Unterordner)</small>",
"syncjob": "Sync-Job bearbeiten",
@@ -1037,7 +1038,7 @@
"notified": "Benachrichtigt",
"qhandler_success": "Aktion wurde an das System übergeben. Sie dürfen dieses Fenster nun schließen.",
"qid": "Rspamd QID",
- "qinfo": "Das Quarantänesystem speichert abgelehnte Nachrichten in der Datenbank (dem Sender wird <em>nicht</em> signalisiert, dass seine E-Mail zugestellt wurde) als auch diese, die als Kopie in den Junk-Ordner der jeweiligen Mailbox zugestellt wurden.\r\n <br>\"Als Spam lernen und löschen\" lernt Nachrichten nach bayesscher Statistik als Spam und erstellt Fuzzy Hashes ausgehend von der jeweiligen Nachricht, um ähnliche Inhalte zukünftig zu unterbinden.\r\n <br>Der Prozess des Lernens kann abhängig vom System zeitintensiv sein.<br>Auf Blacklists vorkommende Elemente sind von der Quarantäne ausgeschlossen.",
+ "qinfo": "Das Quarantänesystem speichert abgelehnte Nachrichten in der Datenbank (dem Sender wird <em>nicht</em> signalisiert, dass seine E-Mail zugestellt wurde) als auch diese, die als Kopie in den Junk-Ordner der jeweiligen Mailbox zugestellt wurden.\r\n <br>\"Als Spam lernen und löschen\" lernt Nachrichten nach bayesscher Statistik als Spam und erstellt Fuzzy Hashes ausgehend von der jeweiligen Nachricht, um ähnliche Inhalte zukünftig zu unterbinden.\r\n <br>Der Prozess des Lernens kann abhängig vom System zeitintensiv sein.<br>Auf Denylisten vorkommende Elemente sind von der Quarantäne ausgeschlossen.",
"qitem": "Quarantäneeintrag",
"quarantine": "Quarantäne",
"quick_actions": "Aktionen",
@@ -1326,7 +1327,7 @@
"spam_score_reset": "Auf Server-Standard zurücksetzen",
"spamfilter": "Spamfilter",
"spamfilter_behavior": "Bewertung",
- "spamfilter_bl": "Blacklist",
+ "spamfilter_bl": "Denyliste",
"spamfilter_bl_desc": "Für E-Mail-Adressen, die vom Spamfilter <b>immer</b> als Spam erfasst und abgelehnt werden. Die Quarantäne-Funktion ist für diese Nachrichten deaktiviert. Die Verwendung von Wildcards ist gestattet. Ein Filter funktioniert lediglich für direkte nicht-\"Catch All\" Alias-Adressen (Alias-Adressen mit lediglich einer Mailbox als Ziel-Adresse) sowie die Mailbox-Adresse selbst.",
"spamfilter_default_score": "Standardwert",
"spamfilter_green": "Grün: Die Nachricht ist kein Spam",
@@ -1338,7 +1339,7 @@
"spamfilter_table_empty": "Keine Einträge vorhanden",
"spamfilter_table_remove": "Entfernen",
"spamfilter_table_rule": "Regel",
- "spamfilter_wl": "Whitelist",
+ "spamfilter_wl": "Allowliste",
"spamfilter_wl_desc": "Für E-Mail-Adressen, die vom Spamfilter <b>nicht</b> erfasst werden sollen. Die Verwendung von Wildcards ist gestattet. Ein Filter funktioniert lediglich für direkte nicht-\"Catch All\" Alias-Adressen (Alias-Adressen mit lediglich einer Mailbox als Ziel-Adresse) sowie die Mailbox-Adresse selbst.",
"spamfilter_yellow": "Gelb: Die Nachricht ist vielleicht Spam, wird als Spam markiert und in den Junk-Ordner verschoben",
"status": "Status",
diff --git a/data/web/lang/lang.en-gb.json b/data/web/lang/lang.en-gb.json
index 21d26d8c..727fd687 100644
--- a/data/web/lang/lang.en-gb.json
+++ b/data/web/lang/lang.en-gb.json
@@ -25,7 +25,7 @@
"sogo_access": "Allow management of SOGo access",
"sogo_profile_reset": "Reset SOGo profile",
"spam_alias": "Temporary aliases",
- "spam_policy": "Blacklist/Whitelist",
+ "spam_policy": "Denylist/Allowlist",
"spam_score": "Spam score",
"syncjobs": "Sync jobs",
"tls_policy": "TLS policy",
@@ -151,7 +151,7 @@
"arrival_time": "Arrival time (server time)",
"authed_user": "Auth. user",
"ays": "Are you sure you want to proceed?",
- "ban_list_info": "See a list of banned IPs below: <b>network (remaining ban time) - [actions]</b>.<br />IPs queued to be unbanned will be removed from the active ban list within a few seconds.<br />Red labels indicate active permanent bans by blacklisting.",
+ "ban_list_info": "See a list of banned IPs below: <b>network (remaining ban time) - [actions]</b>.<br />IPs queued to be unbanned will be removed from the active ban list within a few seconds.<br />Red labels indicate active permanent bans by denylisting.",
"change_logo": "Change logo",
"logo_normal_label": "Normal",
"logo_dark_label": "Inverted for dark mode",
@@ -190,9 +190,9 @@
"excludes": "Excludes these recipients",
"f2b_ban_time": "Ban time (s)",
"f2b_ban_time_increment": "Ban time is incremented with each ban",
- "f2b_blacklist": "Blacklisted networks/hosts",
+ "f2b_blacklist": "Denylisted networks/hosts",
"f2b_filter": "Regex filters",
- "f2b_list_info": "A blacklisted host or network will always outweigh a whitelist entity. <b>List updates will take a few seconds to be applied.</b>",
+ "f2b_list_info": "A denylisted host or network will always outweigh a allowlist entity. <b>List updates will take a few seconds to be applied.</b>",
"f2b_manage_external": "Manage Fail2Ban externally",
"f2b_manage_external_info": "Fail2ban will still maintain the banlist, but it will not actively set rules to block traffic. Use the generated banlist below to externally block the traffic.",
"f2b_max_attempts": "Max. attempts",
@@ -202,7 +202,7 @@
"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",
+ "f2b_whitelist": "Allowlisted networks/hosts",
"filter": "Filter",
"filter_table": "Filter table",
"force_sso_text": "If an external OIDC provider is configured, this option hides the default mailcow login forms and only shows the Single Sign-On button",
@@ -279,6 +279,7 @@
"message": "Message",
"message_size": "Message size",
"nexthop": "Next hop",
+ "needs_restart": "needs restart",
"no": "✕",
"no_active_bans": "No active bans",
"no_new_rows": "No further rows available",
@@ -364,8 +365,8 @@
"rspamd_com_settings": "A setting name will be auto-generated, please see the example presets below. For more details see <a href=\"https://rspamd.com/doc/configuration/settings.html#settings-structure\" target=\"_blank\">Rspamd docs</a>",
"rspamd_global_filters": "Global filter maps",
"rspamd_global_filters_agree": "I will be careful!",
- "rspamd_global_filters_info": "Global filter maps contain different kind of global black and whitelists.",
- "rspamd_global_filters_regex": "Their names explain their purpose. All content must contain valid regular expression in the format of \"/pattern/options\" (e.g. <code>/.+@domain\\.tld/i</code>).<br>\r\n Although rudimentary checks are being executed on each line of regex, Rspamds functionality can be broken, if it fails to read the syntax correctly.<br>\r\n Rspamd will try to read the map content when changed. If you experience problems, <a href=\"\" data-toggle=\"modal\" data-container=\"rspamd-mailcow\" data-target=\"#RestartContainer\">restart Rspamd</a> to enforce a map reload.<br>Blacklisted elements are excluded from quarantine.",
+ "rspamd_global_filters_info": "Global filter maps contain different kind of global deny and allowlists.",
+ "rspamd_global_filters_regex": "Their names explain their purpose. All content must contain valid regular expression in the format of \"/pattern/options\" (e.g. <code>/.+@domain\\.tld/i</code>).<br>\r\n Although rudimentary checks are being executed on each line of regex, Rspamds functionality can be broken, if it fails to read the syntax correctly.<br>\r\n Rspamd will try to read the map content when changed. If you experience problems, <a href=\"\" data-toggle=\"modal\" data-container=\"rspamd-mailcow\" data-target=\"#RestartContainer\">restart Rspamd</a> to enforce a map reload.<br>Denylisted elements are excluded from quarantine.",
"rspamd_settings_map": "Rspamd settings map",
"sal_level": "Moo level",
"save": "Save changes",
@@ -750,7 +751,7 @@
"sogo_visible_info": "This option only affects objects, that can be displayed in SOGo (shared or non-shared alias addresses pointing to at least one local mailbox). If hidden, an alias will not appear as selectable sender in SOGo.",
"spam_alias": "Create or change time limited alias addresses",
"spam_filter": "Spam filter",
- "spam_policy": "Add or remove items to white-/blacklist",
+ "spam_policy": "Add or remove items to allow-/denylist",
"spam_score": "Set a custom spam score",
"subfolder2": "Sync into subfolder on destination<br><small>(empty = do not use subfolder)</small>",
"syncjob": "Edit sync job",
@@ -1039,7 +1040,7 @@
"notified": "Notified",
"qhandler_success": "Request successfully sent to the system. You can now close the window.",
"qid": "Rspamd QID",
- "qinfo": "The quarantine system will save rejected mail to the database (the sender will <em>not</em> be given the impression of a delivered mail) as well as mail, that is delivered as copy into the Junk folder of a mailbox.\r\n <br>\"Learn as spam and delete\" will learn a message as spam via Bayesian theorem and also calculate fuzzy hashes to deny similar messages in the future.\r\n <br>Please be aware that learning multiple messages can be - depending on your system - time consuming.<br>Blacklisted elements are excluded from the quarantine.",
+ "qinfo": "The quarantine system will save rejected mail to the database (the sender will <em>not</em> be given the impression of a delivered mail) as well as mail, that is delivered as copy into the Junk folder of a mailbox.\r\n <br>\"Learn as spam and delete\" will learn a message as spam via Bayesian theorem and also calculate fuzzy hashes to deny similar messages in the future.\r\n <br>Please be aware that learning multiple messages can be - depending on your system - time consuming.<br>Denylisted elements are excluded from the quarantine.",
"qitem": "Quarantine item",
"quarantine": "Quarantine",
"quick_actions": "Actions",
@@ -1337,8 +1338,8 @@
"spam_score_reset": "Reset to server default",
"spamfilter": "Spam filter",
"spamfilter_behavior": "Rating",
- "spamfilter_bl": "Blacklist",
- "spamfilter_bl_desc": "Blacklisted email addresses to <b>always</b> classify as spam and reject. Rejected mail will <b>not</b> be copied to quarantine. Wildcards may be used. A filter is only applied to direct aliases (aliases with a single target mailbox) excluding catch-all aliases and a mailbox itself.",
+ "spamfilter_bl": "Denylist",
+ "spamfilter_bl_desc": "Denylisted email addresses to <b>always</b> classify as spam and reject. Rejected mail will <b>not</b> be copied to quarantine. Wildcards may be used. A filter is only applied to direct aliases (aliases with a single target mailbox) excluding catch-all aliases and a mailbox itself.",
"spamfilter_default_score": "Default values",
"spamfilter_green": "Green: this message is not spam",
"spamfilter_hint": "The first value describes the \"low spam score\", the second represents the \"high spam score\".",
@@ -1349,8 +1350,8 @@
"spamfilter_table_empty": "No data to display",
"spamfilter_table_remove": "remove",
"spamfilter_table_rule": "Rule",
- "spamfilter_wl": "Whitelist",
- "spamfilter_wl_desc": "Whitelisted email addresses are programmed to <b>never</b> classify as spam. Wildcards may be used. A filter is only applied to direct aliases (aliases with a single target mailbox) excluding catch-all aliases and a mailbox itself.",
+ "spamfilter_wl": "Allowlist",
+ "spamfilter_wl_desc": "Allowlisted email addresses are programmed to <b>never</b> classify as spam. Wildcards may be used. A filter is only applied to direct aliases (aliases with a single target mailbox) excluding catch-all aliases and a mailbox itself.",
"spamfilter_yellow": "Yellow: this message may be spam, will be tagged as spam and moved to your junk folder",
"status": "Status",
"sync_jobs": "Sync jobs",
diff --git a/data/web/templates/admin/tab-config-f2b.twig b/data/web/templates/admin/tab-config-f2b.twig
index d33c242a..29535b72 100644
--- a/data/web/templates/admin/tab-config-f2b.twig
+++ b/data/web/templates/admin/tab-config-f2b.twig
@@ -118,8 +118,8 @@
<span class="d-none d-sm-inline"> - </span>
{% if active_ban.queued_for_unban == 0 %}
<a data-action="edit_selected" data-item="{{ active_ban.network }}" data-id="f2b-quick" data-api-url='edit/fail2ban' data-api-attr='{"action":"unban"}' href="#">[{{ lang.admin.queue_unban }}]</a>
- <a data-action="edit_selected" data-item="{{ active_ban.network }}" data-id="f2b-quick" data-api-url='edit/fail2ban' data-api-attr='{"action":"whitelist"}' href="#">[whitelist]</a>
- <a data-action="edit_selected" data-item="{{ active_ban.network }}" data-id="f2b-quick" data-api-url='edit/fail2ban' data-api-attr='{"action":"blacklist"}' href="#">[blacklist (<b>needs restart</b>)]</a>
+ <a data-action="edit_selected" data-item="{{ active_ban.network }}" data-id="f2b-quick" data-api-url='edit/fail2ban' data-api-attr='{"action":"whitelist"}' href="#">[allowlist]</a>
+ <a data-action="edit_selected" data-item="{{ active_ban.network }}" data-id="f2b-quick" data-api-url='edit/fail2ban' data-api-attr='{"action":"blacklist"}' href="#">[denylist (<b>{{ lang.admin.needs_restart }}</b>)]</a>
{% else %}
<i>{{ lang.admin.unban_pending }}</i>
{% endif %}
diff --git a/docker-compose.yml b/docker-compose.yml
index d9524146..f447dea4 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -477,7 +477,7 @@ services:
- acme
netfilter-mailcow:
- image: ghcr.io/mailcow/netfilter:1.61
+ image: ghcr.io/mailcow/netfilter:1.62
stop_grace_period: 30s
restart: always
privileged: true