[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 <git@evers.sh>
Co-authored-by: DerLinkman <niklas.meyer@servercow.de>
This commit is contained in:
Denis Evers 2025-07-16 16:08:55 +08:00 committed by GitHub
parent 1e5fcfe392
commit 95eb350f15
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 137 additions and 94 deletions

View File

@ -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
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 = 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)
logger = Logger()
logdebug("Sys.argv: %s" % sys.argv)
atexit.register(before_quit)
signal.signal(signal.SIGTERM, sigterm_quit)
# init Logger
logger = Logger()
# 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)

View File

@ -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',

View File

@ -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": "&#10005;",
"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",

View File

@ -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": "&#10005;",
"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",

View File

@ -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 %}

View File

@ -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