Compare commits

..

23 Commits

Author SHA1 Message Date
DerLinkman
a8bc4e3f37 Merge branch 'staging' 2023-07-28 10:35:17 +02:00
DerLinkman
815572f200 Merge branch 'feat/spamhaus-dqs-asn' into staging 2023-07-28 10:33:34 +02:00
Patrick Schult
23fc54f2cf Merge pull request #5332 from mailcow/staging
2023-07
2023-07-28 10:26:49 +02:00
FreddleSpl0it
11407973b1 [Web] change style of f2b active ban actions 2023-07-27 14:19:18 +02:00
FreddleSpl0it
b9867e3fe0 [Web] change style of f2b active ban actions 2023-07-27 14:16:11 +02:00
FreddleSpl0it
3814c3294f [Web] add edit/cors api endpoint to swagger 2023-07-27 13:45:57 +02:00
FreddleSpl0it
9c44b5e546 [Web] display is_catch_all and aliases_send_as_all if not empty #5320 2023-07-27 12:10:01 +02:00
FreddleSpl0it
cd635ec813 [Dockerapi] Update to 2.05 2023-07-27 11:30:47 +02:00
FreddleSpl0it
03831149f8 [Web] fix visual bug #5322 2023-07-27 11:28:49 +02:00
renovate[bot]
521120a448 Update dependency nextcloud/server to v27.0.1 (#5324)
Signed-off-by: milkmaker <milkmaker@mailcow.de>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-07-24 10:43:00 +02:00
DerLinkman
ec8d298c36 Update postfix.sh to include pbl for dqs 2023-07-13 16:42:59 +02:00
Patrick Schult
03580cbf39 Merge pull request #5315 from SnailShea/fix/twig-typos
Fixes several instances of missing </span>, extra role='tabpanel' and…
2023-07-12 08:55:28 +02:00
Niklas Meyer
2b009c71c1 Merge pull request #5316 from mailcow/feat/rspamd-securite-symbols
[Rspamd] Native mailcow Support for Securite ClamAV Signatures
2023-07-12 08:27:20 +02:00
SnailShea
b903cf3888 Fixes several instances of missing </span>, extra role='tabpanel' and misspelled 'collapse' 2023-07-11 19:00:05 -04:00
DerLinkman
6e9c024b3c Changed weight to score for CLAMD_SPAM 2023-06-27 10:28:52 +02:00
DerLinkman
8cd4ae1e34 Improved Scores 2023-06-23 16:19:37 +02:00
DerLinkman
689856b186 New Symbols defined for Security ClamAV DBs 2023-06-23 16:13:25 +02:00
DerLinkman
7b645303d6 Added Colorful Outputs for the Spamhaus info in PF 2023-06-23 15:54:49 +02:00
DerLinkman
408381bddb Update Postfix image to 1.69 + improvements 2023-06-23 15:48:13 +02:00
DerLinkman
380cdab6fc Removed dnsbl from main.cf 2023-06-23 14:26:17 +02:00
DerLinkman
03b7a8d639 Implemented Postfix Blocklist generation 2023-06-23 14:25:07 +02:00
DerLinkman
bf6a61fa2d Small corrections to update/generate.sh 2023-06-23 14:20:06 +02:00
DerLinkman
1de47072f8 Added DQS Values to update.sh/generate + check of variable 2023-06-23 12:26:57 +02:00
53 changed files with 334 additions and 260 deletions

1
.gitignore vendored
View File

@@ -36,6 +36,7 @@ data/conf/postfix/extra.cf
data/conf/postfix/sni.map data/conf/postfix/sni.map
data/conf/postfix/sni.map.db data/conf/postfix/sni.map.db
data/conf/postfix/sql data/conf/postfix/sql
data/conf/postfix/dns_blocklists.cf
data/conf/rspamd/custom/* data/conf/rspamd/custom/*
data/conf/rspamd/local.d/* data/conf/rspamd/local.d/*
data/conf/rspamd/override.d/* data/conf/rspamd/override.d/*

View File

@@ -16,7 +16,6 @@ import json
import iptc import iptc
import dns.resolver import dns.resolver
import dns.exception import dns.exception
import uuid
while True: while True:
try: try:
@@ -95,8 +94,6 @@ def verifyF2boptions(f2boptions):
verifyF2boption(f2boptions,'retry_window', 600) verifyF2boption(f2boptions,'retry_window', 600)
verifyF2boption(f2boptions,'netban_ipv4', 32) verifyF2boption(f2boptions,'netban_ipv4', 32)
verifyF2boption(f2boptions,'netban_ipv6', 128) verifyF2boption(f2boptions,'netban_ipv6', 128)
verifyF2boption(f2boptions,'banlist_id', str(uuid.uuid4()))
verifyF2boption(f2boptions,'manage_external', 0)
def verifyF2boption(f2boptions, f2boption, f2bdefault): def verifyF2boption(f2boptions, f2boption, f2bdefault):
f2boptions[f2boption] = f2boptions[f2boption] if f2boption in f2boptions and f2boptions[f2boption] is not None else f2bdefault f2boptions[f2boption] = f2boptions[f2boption] if f2boption in f2boptions and f2boptions[f2boption] is not None else f2bdefault
@@ -159,7 +156,6 @@ def mailcowChainOrder():
exit_code = 2 exit_code = 2
def ban(address): def ban(address):
global f2boptions
global lock global lock
refreshF2boptions() refreshF2boptions()
BAN_TIME = int(f2boptions['ban_time']) BAN_TIME = int(f2boptions['ban_time'])
@@ -201,7 +197,7 @@ def ban(address):
cur_time = int(round(time.time())) cur_time = int(round(time.time()))
NET_BAN_TIME = BAN_TIME if not BAN_TIME_INCREMENT else BAN_TIME * 2 ** bans[net]['ban_counter'] NET_BAN_TIME = BAN_TIME if not BAN_TIME_INCREMENT else BAN_TIME * 2 ** bans[net]['ban_counter']
logCrit('Banning %s for %d minutes' % (net, NET_BAN_TIME / 60 )) logCrit('Banning %s for %d minutes' % (net, NET_BAN_TIME / 60 ))
if type(ip) is ipaddress.IPv4Address and int(f2boptions['manage_external']) != 1: if type(ip) is ipaddress.IPv4Address:
with lock: with lock:
chain = iptc.Chain(iptc.Table(iptc.Table.FILTER), 'MAILCOW') chain = iptc.Chain(iptc.Table(iptc.Table.FILTER), 'MAILCOW')
rule = iptc.Rule() rule = iptc.Rule()
@@ -210,7 +206,7 @@ def ban(address):
rule.target = target rule.target = target
if rule not in chain.rules: if rule not in chain.rules:
chain.insert_rule(rule) chain.insert_rule(rule)
elif int(f2boptions['manage_external']) != 1: else:
with lock: with lock:
chain = iptc.Chain(iptc.Table6(iptc.Table6.FILTER), 'MAILCOW') chain = iptc.Chain(iptc.Table6(iptc.Table6.FILTER), 'MAILCOW')
rule = iptc.Rule6() rule = iptc.Rule6()
@@ -255,7 +251,6 @@ def unban(net):
bans[net]['ban_counter'] += 1 bans[net]['ban_counter'] += 1
def permBan(net, unban=False): def permBan(net, unban=False):
global f2boptions
global lock global lock
if type(ipaddress.ip_network(net, strict=False)) is ipaddress.IPv4Network: if type(ipaddress.ip_network(net, strict=False)) is ipaddress.IPv4Network:
with lock: with lock:
@@ -264,7 +259,7 @@ def permBan(net, unban=False):
rule.src = net rule.src = net
target = iptc.Target(rule, "REJECT") target = iptc.Target(rule, "REJECT")
rule.target = target rule.target = target
if rule not in chain.rules and not unban and int(f2boptions['manage_external']) != 1: if rule not in chain.rules and not unban:
logCrit('Add host/network %s to blacklist' % net) logCrit('Add host/network %s to blacklist' % net)
chain.insert_rule(rule) chain.insert_rule(rule)
r.hset('F2B_PERM_BANS', '%s' % net, int(round(time.time()))) r.hset('F2B_PERM_BANS', '%s' % net, int(round(time.time())))
@@ -279,7 +274,7 @@ def permBan(net, unban=False):
rule.src = net rule.src = net
target = iptc.Target(rule, "REJECT") target = iptc.Target(rule, "REJECT")
rule.target = target rule.target = target
if rule not in chain.rules and not unban and int(f2boptions['manage_external']) != 1: if rule not in chain.rules and not unban:
logCrit('Add host/network %s to blacklist' % net) logCrit('Add host/network %s to blacklist' % net)
chain.insert_rule(rule) chain.insert_rule(rule)
r.hset('F2B_PERM_BANS', '%s' % net, int(round(time.time()))) r.hset('F2B_PERM_BANS', '%s' % net, int(round(time.time())))
@@ -558,7 +553,7 @@ def initChain():
chain.insert_rule(rule) chain.insert_rule(rule)
if __name__ == '__main__': if __name__ == '__main__':
refreshF2boptions()
# In case a previous session was killed without cleanup # In case a previous session was killed without cleanup
clear() clear()
# Reinit MAILCOW chain # Reinit MAILCOW chain

View File

@@ -33,6 +33,7 @@ RUN groupadd -g 102 postfix \
syslog-ng-core \ syslog-ng-core \
syslog-ng-mod-redis \ syslog-ng-mod-redis \
tzdata \ tzdata \
whois \
&& rm -rf /var/lib/apt/lists/* \ && rm -rf /var/lib/apt/lists/* \
&& touch /etc/default/locale \ && touch /etc/default/locale \
&& printf '#!/bin/bash\n/usr/sbin/postconf -c /opt/postfix/conf "$@"' > /usr/local/sbin/postconf \ && printf '#!/bin/bash\n/usr/sbin/postconf -c /opt/postfix/conf "$@"' > /usr/local/sbin/postconf \

View File

@@ -393,6 +393,111 @@ query = SELECT goto FROM spamalias
AND validity >= UNIX_TIMESTAMP() AND validity >= UNIX_TIMESTAMP()
EOF EOF
if [ -n "$SPAMHAUS_DQS_KEY" ]; then
echo -e "\e[32mDetected SPAMHAUS_DQS_KEY variable from mailcow.conf...\e[0m"
echo -e "\e[33mUsing DQS Blocklists from Spamhaus!\e[0m"
cat <<EOF > /opt/postfix/conf/dns_blocklists.cf
# Autogenerated by mailcow
postscreen_dnsbl_sites = wl.mailspike.net=127.0.0.[18;19;20]*-2
hostkarma.junkemailfilter.com=127.0.0.1*-2
list.dnswl.org=127.0.[0..255].0*-2
list.dnswl.org=127.0.[0..255].1*-4
list.dnswl.org=127.0.[0..255].2*-6
list.dnswl.org=127.0.[0..255].3*-8
ix.dnsbl.manitu.net*2
bl.spamcop.net*2
bl.suomispam.net*2
hostkarma.junkemailfilter.com=127.0.0.2*3
hostkarma.junkemailfilter.com=127.0.0.4*2
hostkarma.junkemailfilter.com=127.0.1.2*1
backscatter.spameatingmonkey.net*2
bl.ipv6.spameatingmonkey.net*2
bl.spameatingmonkey.net*2
b.barracudacentral.org=127.0.0.2*7
bl.mailspike.net=127.0.0.2*5
bl.mailspike.net=127.0.0.[10;11;12]*4
dnsbl.sorbs.net=127.0.0.10*8
dnsbl.sorbs.net=127.0.0.5*6
dnsbl.sorbs.net=127.0.0.7*3
dnsbl.sorbs.net=127.0.0.8*2
dnsbl.sorbs.net=127.0.0.6*2
dnsbl.sorbs.net=127.0.0.9*2
${SPAMHAUS_DQS_KEY}.zen.dq.spamhaus.net=127.0.0.[4..7]*6
${SPAMHAUS_DQS_KEY}.zen.dq.spamhaus.org=127.0.0.[10;11]*8
${SPAMHAUS_DQS_KEY}.zen.dq.spamhaus.org=127.0.0.3*4
${SPAMHAUS_DQS_KEY}.zen.dq.spamhaus.org=127.0.0.2*3
${SPAMHAUS_DQS_KEY}.dbl.dq.spamhaus.net=127.0.0.3*4
${SPAMHAUS_DQS_KEY}.zrd.dq.spamhaus.net=127.0.0.2*3
EOF
else
if curl -s http://fuzzy.mailcow.email/asn_list.txt | grep $(whois -h whois.radb.net $(curl -s http://ipv4.mailcow.email) | grep -i origin | tr -s " " | cut -d " " -f2 | head -1) > /dev/null; then
echo -e "\e[31mThe AS of your IP is listed as a banned AS from Spamhaus!\e[0m"
echo -e "\e[33mNo SPAMHAUS_DQS_KEY found... Skipping Spamhaus blocklists entirely!\e[0m"
cat <<EOF > /opt/postfix/conf/dns_blocklists.cf
# Autogenerated by mailcow
postscreen_dnsbl_sites = wl.mailspike.net=127.0.0.[18;19;20]*-2
hostkarma.junkemailfilter.com=127.0.0.1*-2
list.dnswl.org=127.0.[0..255].0*-2
list.dnswl.org=127.0.[0..255].1*-4
list.dnswl.org=127.0.[0..255].2*-6
list.dnswl.org=127.0.[0..255].3*-8
ix.dnsbl.manitu.net*2
bl.spamcop.net*2
bl.suomispam.net*2
hostkarma.junkemailfilter.com=127.0.0.2*3
hostkarma.junkemailfilter.com=127.0.0.4*2
hostkarma.junkemailfilter.com=127.0.1.2*1
backscatter.spameatingmonkey.net*2
bl.ipv6.spameatingmonkey.net*2
bl.spameatingmonkey.net*2
b.barracudacentral.org=127.0.0.2*7
bl.mailspike.net=127.0.0.2*5
bl.mailspike.net=127.0.0.[10;11;12]*4
dnsbl.sorbs.net=127.0.0.10*8
dnsbl.sorbs.net=127.0.0.5*6
dnsbl.sorbs.net=127.0.0.7*3
dnsbl.sorbs.net=127.0.0.8*2
dnsbl.sorbs.net=127.0.0.6*2
dnsbl.sorbs.net=127.0.0.9*2
EOF
else
echo -e "\e[32mThe AS of your IP is NOT listed as a banned AS from Spamhaus!\e[0m"
echo -e "\e[33mUsing the open Spamhaus blocklists.\e[0m"
cat <<EOF > /opt/postfix/conf/dns_blocklists.cf
# Autogenerated by mailcow
postscreen_dnsbl_sites = wl.mailspike.net=127.0.0.[18;19;20]*-2
hostkarma.junkemailfilter.com=127.0.0.1*-2
list.dnswl.org=127.0.[0..255].0*-2
list.dnswl.org=127.0.[0..255].1*-4
list.dnswl.org=127.0.[0..255].2*-6
list.dnswl.org=127.0.[0..255].3*-8
ix.dnsbl.manitu.net*2
bl.spamcop.net*2
bl.suomispam.net*2
hostkarma.junkemailfilter.com=127.0.0.2*3
hostkarma.junkemailfilter.com=127.0.0.4*2
hostkarma.junkemailfilter.com=127.0.1.2*1
backscatter.spameatingmonkey.net*2
bl.ipv6.spameatingmonkey.net*2
bl.spameatingmonkey.net*2
b.barracudacentral.org=127.0.0.2*7
bl.mailspike.net=127.0.0.2*5
bl.mailspike.net=127.0.0.[10;11;12]*4
dnsbl.sorbs.net=127.0.0.10*8
dnsbl.sorbs.net=127.0.0.5*6
dnsbl.sorbs.net=127.0.0.7*3
dnsbl.sorbs.net=127.0.0.8*2
dnsbl.sorbs.net=127.0.0.6*2
dnsbl.sorbs.net=127.0.0.9*2
zen.spamhaus.org=127.0.0.[10;11]*8
zen.spamhaus.org=127.0.0.[4..7]*6
zen.spamhaus.org=127.0.0.3*4
zen.spamhaus.org=127.0.0.2*3
EOF
fi
fi
sed -i '/User overrides/q' /opt/postfix/conf/main.cf sed -i '/User overrides/q' /opt/postfix/conf/main.cf
echo >> /opt/postfix/conf/main.cf echo >> /opt/postfix/conf/main.cf
touch /opt/postfix/conf/extra.cf touch /opt/postfix/conf/extra.cf

View File

@@ -40,34 +40,6 @@ postscreen_blacklist_action = drop
postscreen_cache_cleanup_interval = 24h postscreen_cache_cleanup_interval = 24h
postscreen_cache_map = proxy:btree:$data_directory/postscreen_cache postscreen_cache_map = proxy:btree:$data_directory/postscreen_cache
postscreen_dnsbl_action = enforce postscreen_dnsbl_action = enforce
postscreen_dnsbl_sites = wl.mailspike.net=127.0.0.[18;19;20]*-2
hostkarma.junkemailfilter.com=127.0.0.1*-2
list.dnswl.org=127.0.[0..255].0*-2
list.dnswl.org=127.0.[0..255].1*-4
list.dnswl.org=127.0.[0..255].2*-6
list.dnswl.org=127.0.[0..255].3*-8
ix.dnsbl.manitu.net*2
bl.spamcop.net*2
bl.suomispam.net*2
hostkarma.junkemailfilter.com=127.0.0.2*3
hostkarma.junkemailfilter.com=127.0.0.4*2
hostkarma.junkemailfilter.com=127.0.1.2*1
backscatter.spameatingmonkey.net*2
bl.ipv6.spameatingmonkey.net*2
bl.spameatingmonkey.net*2
b.barracudacentral.org=127.0.0.2*7
bl.mailspike.net=127.0.0.2*5
bl.mailspike.net=127.0.0.[10;11;12]*4
dnsbl.sorbs.net=127.0.0.10*8
dnsbl.sorbs.net=127.0.0.5*6
dnsbl.sorbs.net=127.0.0.7*3
dnsbl.sorbs.net=127.0.0.8*2
dnsbl.sorbs.net=127.0.0.6*2
dnsbl.sorbs.net=127.0.0.9*2
zen.spamhaus.org=127.0.0.[10;11]*8
zen.spamhaus.org=127.0.0.[4..7]*6
zen.spamhaus.org=127.0.0.3*4
zen.spamhaus.org=127.0.0.2*3
postscreen_dnsbl_threshold = 6 postscreen_dnsbl_threshold = 6
postscreen_dnsbl_ttl = 5m postscreen_dnsbl_ttl = 5m
postscreen_greet_action = enforce postscreen_greet_action = enforce

View File

@@ -68,3 +68,39 @@ WL_FWD_HOST {
ENCRYPTED_CHAT { ENCRYPTED_CHAT {
expression = "CHAT_VERSION_HEADER & ENCRYPTED_PGP"; expression = "CHAT_VERSION_HEADER & ENCRYPTED_PGP";
} }
CLAMD_SPAM_FOUND {
expression = "CLAM_SECI_SPAM & !MAILCOW_WHITE";
description = "Probably Spam, Securite Spam Flag set through ClamAV";
score = 5;
}
CLAMD_BAD_PDF {
expression = "CLAM_SECI_PDF & !MAILCOW_WHITE";
description = "Bad PDF Found, Securite bad PDF Flag set through ClamAV";
score = 8;
}
CLAMD_BAD_JPG {
expression = "CLAM_SECI_JPG & !MAILCOW_WHITE";
description = "Bad JPG Found, Securite bad JPG Flag set through ClamAV";
score = 8;
}
CLAMD_ASCII_MALWARE {
expression = "CLAM_SECI_ASCII & !MAILCOW_WHITE";
description = "ASCII malware found, Securite ASCII malware Flag set through ClamAV";
score = 8;
}
CLAMD_HTML_MALWARE {
expression = "CLAM_SECI_HTML & !MAILCOW_WHITE";
description = "HTML malware found, Securite HTML malware Flag set through ClamAV";
score = 8;
}
CLAMD_JS_MALWARE {
expression = "CLAM_SECI_JS & !MAILCOW_WHITE";
description = "JS malware found, Securite JS malware Flag set through ClamAV";
score = 8;
}

View File

@@ -85,8 +85,6 @@ $cors_settings = cors('get');
$cors_settings['allowed_origins'] = str_replace(", ", "\n", $cors_settings['allowed_origins']); $cors_settings['allowed_origins'] = str_replace(", ", "\n", $cors_settings['allowed_origins']);
$cors_settings['allowed_methods'] = explode(", ", $cors_settings['allowed_methods']); $cors_settings['allowed_methods'] = explode(", ", $cors_settings['allowed_methods']);
$f2b_data = fail2ban('get');
$template = 'admin.twig'; $template = 'admin.twig';
$template_data = [ $template_data = [
'tfa_data' => $tfa_data, 'tfa_data' => $tfa_data,
@@ -103,8 +101,7 @@ $template_data = [
'domains' => $domains, 'domains' => $domains,
'all_domains' => $all_domains, 'all_domains' => $all_domains,
'mailboxes' => $mailboxes, 'mailboxes' => $mailboxes,
'f2b_data' => $f2b_data, 'f2b_data' => fail2ban('get'),
'f2b_banlist_url' => getBaseUrl() . "/api/v1/get/fail2ban/banlist/" . $f2b_data['banlist_id'],
'q_data' => quarantine('settings'), 'q_data' => quarantine('settings'),
'qn_data' => quota_notification('get'), 'qn_data' => quota_notification('get'),
'rsettings_map' => file_get_contents('http://nginx:8081/settings.php'), 'rsettings_map' => file_get_contents('http://nginx:8081/settings.php'),
@@ -115,7 +112,6 @@ $template_data = [
'password_complexity' => password_complexity('get'), 'password_complexity' => password_complexity('get'),
'show_rspamd_global_filters' => @$_SESSION['show_rspamd_global_filters'], 'show_rspamd_global_filters' => @$_SESSION['show_rspamd_global_filters'],
'cors_settings' => $cors_settings, 'cors_settings' => $cors_settings,
'is_https' => isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on',
'lang_admin' => json_encode($lang['admin']), 'lang_admin' => json_encode($lang['admin']),
'lang_datatables' => json_encode($lang['datatables']) 'lang_datatables' => json_encode($lang['datatables'])
]; ];

View File

@@ -5602,6 +5602,50 @@ paths:
description: You can list all mailboxes existing in system for a specific domain. description: You can list all mailboxes existing in system for a specific domain.
operationId: Get mailboxes of a domain operationId: Get mailboxes of a domain
summary: Get mailboxes of a domain summary: Get mailboxes of a domain
/api/v1/edit/cors:
post:
responses:
"401":
$ref: "#/components/responses/Unauthorized"
"200":
content:
application/json:
examples:
response:
value:
- type: "success"
log: ["cors", "edit", {"allowed_origins": ["*", "mail.mailcow.tld"], "allowed_methods": ["POST", "GET", "DELETE", "PUT"]}]
msg: "cors_headers_edited"
description: OK
headers: { }
tags:
- Cross-Origin Resource Sharing (CORS)
description: >-
This endpoint allows you to manage Cross-Origin Resource Sharing (CORS) settings for the API.
CORS is a security feature implemented by web browsers to prevent unauthorized cross-origin requests.
By editing the CORS settings, you can specify which domains and which methods are permitted to access the API resources from outside the mailcow domain.
operationId: Edit Cross-Origin Resource Sharing (CORS) settings
requestBody:
content:
application/json:
schema:
example:
attr:
allowed_origins: ["*", "mail.mailcow.tld"]
allowed_methods: ["POST", "GET", "DELETE", "PUT"]
properties:
attr:
type: object
properties:
allowed_origins:
type: array
items:
type: string
allowed_methods:
type: array
items:
type: string
summary: Edit Cross-Origin Resource Sharing (CORS) settings
tags: tags:
- name: Domains - name: Domains
@@ -5646,3 +5690,5 @@ tags:
description: Get the status of your cow description: Get the status of your cow
- name: Ratelimits - name: Ratelimits
description: Edit domain ratelimits description: Edit domain ratelimits
- name: Cross-Origin Resource Sharing (CORS)
description: Manage Cross-Origin Resource Sharing (CORS) settings

View File

@@ -1,5 +1,5 @@
<?php <?php
function fail2ban($_action, $_data = null, $_extra = null) { function fail2ban($_action, $_data = null) {
global $redis; global $redis;
$_data_log = $_data; $_data_log = $_data;
switch ($_action) { switch ($_action) {
@@ -247,7 +247,6 @@ function fail2ban($_action, $_data = null, $_extra = null) {
$netban_ipv6 = intval((isset($_data['netban_ipv6'])) ? $_data['netban_ipv6'] : $is_now['netban_ipv6']); $netban_ipv6 = intval((isset($_data['netban_ipv6'])) ? $_data['netban_ipv6'] : $is_now['netban_ipv6']);
$wl = (isset($_data['whitelist'])) ? $_data['whitelist'] : $is_now['whitelist']; $wl = (isset($_data['whitelist'])) ? $_data['whitelist'] : $is_now['whitelist'];
$bl = (isset($_data['blacklist'])) ? $_data['blacklist'] : $is_now['blacklist']; $bl = (isset($_data['blacklist'])) ? $_data['blacklist'] : $is_now['blacklist'];
$manage_external = (isset($_data['manage_external'])) ? intval($_data['manage_external']) : 0;
} }
else { else {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
@@ -267,8 +266,6 @@ function fail2ban($_action, $_data = null, $_extra = null) {
$f2b_options['netban_ipv6'] = ($netban_ipv6 > 128) ? 128 : $netban_ipv6; $f2b_options['netban_ipv6'] = ($netban_ipv6 > 128) ? 128 : $netban_ipv6;
$f2b_options['max_attempts'] = ($max_attempts < 1) ? 1 : $max_attempts; $f2b_options['max_attempts'] = ($max_attempts < 1) ? 1 : $max_attempts;
$f2b_options['retry_window'] = ($retry_window < 1) ? 1 : $retry_window; $f2b_options['retry_window'] = ($retry_window < 1) ? 1 : $retry_window;
$f2b_options['banlist_id'] = $is_now['banlist_id'];
$f2b_options['manage_external'] = ($manage_external > 0) ? 1 : 0;
try { try {
$redis->Set('F2B_OPTIONS', json_encode($f2b_options)); $redis->Set('F2B_OPTIONS', json_encode($f2b_options));
$redis->Del('F2B_WHITELIST'); $redis->Del('F2B_WHITELIST');
@@ -332,71 +329,5 @@ function fail2ban($_action, $_data = null, $_extra = null) {
'msg' => 'f2b_modified' 'msg' => 'f2b_modified'
); );
break; break;
case 'banlist':
try {
$f2b_options = json_decode($redis->Get('F2B_OPTIONS'), true);
}
catch (RedisException $e) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log, $_extra),
'msg' => array('redis_error', $e)
);
http_response_code(500);
return false;
}
if (is_array($_extra)) {
$_extra = $_extra[0];
}
if ($_extra != $f2b_options['banlist_id']){
http_response_code(404);
return false;
}
switch ($_data) {
case 'get':
try {
$bl = $redis->hKeys('F2B_BLACKLIST');
$active_bans = $redis->hKeys('F2B_ACTIVE_BANS');
}
catch (RedisException $e) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log, $_extra),
'msg' => array('redis_error', $e)
);
http_response_code(500);
return false;
}
$banlist = implode("\n", array_merge($bl, $active_bans));
return $banlist;
break;
case 'refresh':
if ($_SESSION['mailcow_cc_role'] != "admin") {
return false;
}
$f2b_options['banlist_id'] = uuid4();
try {
$redis->Set('F2B_OPTIONS', json_encode($f2b_options));
}
catch (RedisException $e) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log, $_extra),
'msg' => array('redis_error', $e)
);
return false;
}
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_data_log, $_extra),
'msg' => 'f2b_banlist_refreshed'
);
return true;
break;
}
break;
} }
} }

View File

@@ -2246,21 +2246,6 @@ function cors($action, $data = null) {
break; break;
} }
} }
function getBaseURL() {
$protocol = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? 'https' : 'http';
$host = $_SERVER['HTTP_HOST'];
$base_url = $protocol . '://' . $host;
return $base_url;
}
function uuid4() {
$data = openssl_random_pseudo_bytes(16);
$data[6] = chr(ord($data[6]) & 0x0f | 0x40);
$data[8] = chr(ord($data[8]) & 0x3f | 0x80);
return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
}
function get_logs($application, $lines = false) { function get_logs($application, $lines = false) {
if ($lines === false) { if ($lines === false) {

View File

@@ -70,8 +70,6 @@ try {
} }
} }
catch (Exception $e) { catch (Exception $e) {
// Stop when redis is not available
http_response_code(500);
?> ?>
<center style='font-family:sans-serif;'>Connection to Redis failed.<br /><br />The following error was reported:<br/><?=$e->getMessage();?></center> <center style='font-family:sans-serif;'>Connection to Redis failed.<br /><br />The following error was reported:<br/><?=$e->getMessage();?></center>
<?php <?php
@@ -100,7 +98,6 @@ try {
} }
catch (PDOException $e) { catch (PDOException $e) {
// Stop when SQL connection fails // Stop when SQL connection fails
http_response_code(500);
?> ?>
<center style='font-family:sans-serif;'>Connection to database failed.<br /><br />The following error was reported:<br/> <?=$e->getMessage();?></center> <center style='font-family:sans-serif;'>Connection to database failed.<br /><br />The following error was reported:<br/> <?=$e->getMessage();?></center>
<?php <?php
@@ -108,7 +105,6 @@ exit;
} }
// Stop when dockerapi is not available // Stop when dockerapi is not available
if (fsockopen("tcp://dockerapi", 443, $errno, $errstr) === false) { if (fsockopen("tcp://dockerapi", 443, $errno, $errstr) === false) {
http_response_code(500);
?> ?>
<center style='font-family:sans-serif;'>Connection to dockerapi container failed.<br /><br />The following error was reported:<br/><?=$errno;?> - <?=$errstr;?></center> <center style='font-family:sans-serif;'>Connection to dockerapi container failed.<br /><br />The following error was reported:<br/><?=$errno;?> - <?=$errstr;?></center>
<?php <?php

View File

@@ -371,11 +371,3 @@ function addTag(tagAddElem, tag = null){
$(tagValuesElem).val(JSON.stringify(value_tags)); $(tagValuesElem).val(JSON.stringify(value_tags));
$(tagInputElem).val(''); $(tagInputElem).val('');
} }
function copyToClipboard(id) {
var copyText = document.getElementById(id);
copyText.select();
copyText.setSelectionRange(0, 99999);
// only works with https connections
navigator.clipboard.writeText(copyText.value);
mailcow_alert_box(lang.copy_to_clipboard, "success");
}

View File

@@ -504,16 +504,6 @@ if (isset($_GET['query'])) {
$_SESSION['challenge'] = $WebAuthn->getChallenge(); $_SESSION['challenge'] = $WebAuthn->getChallenge();
return; return;
break; break;
case "fail2ban":
if (!isset($_SESSION['mailcow_cc_role'])){
switch ($object) {
case 'banlist':
header('Content-Type: text/plain');
echo fail2ban('banlist', 'get', $extra);
break;
}
}
break;
} }
if (isset($_SESSION['mailcow_cc_role'])) { if (isset($_SESSION['mailcow_cc_role'])) {
switch ($category) { switch ($category) {
@@ -1334,10 +1324,6 @@ if (isset($_GET['query'])) {
break; break;
case "fail2ban": case "fail2ban":
switch ($object) { switch ($object) {
case 'banlist':
header('Content-Type: text/plain');
echo fail2ban('banlist', 'get', $extra);
break;
default: default:
$data = fail2ban('get'); $data = fail2ban('get');
process_get_return($data); process_get_return($data);
@@ -1944,15 +1930,8 @@ if (isset($_GET['query'])) {
process_edit_return(fwdhost('edit', array_merge(array('fwdhost' => $items), $attr))); process_edit_return(fwdhost('edit', array_merge(array('fwdhost' => $items), $attr)));
break; break;
case "fail2ban": case "fail2ban":
switch ($object) {
case 'banlist':
process_edit_return(fail2ban('banlist', 'refresh', $items));
break;
default:
process_edit_return(fail2ban('edit', array_merge(array('network' => $items), $attr))); process_edit_return(fail2ban('edit', array_merge(array('network' => $items), $attr)));
break; break;
}
break;
case "ui_texts": case "ui_texts":
process_edit_return(customize('edit', 'ui_texts', $attr)); process_edit_return(customize('edit', 'ui_texts', $attr));
break; break;

View File

@@ -147,7 +147,6 @@
"change_logo": "Logo ändern", "change_logo": "Logo ändern",
"configuration": "Konfiguration", "configuration": "Konfiguration",
"convert_html_to_text": "Konvertiere HTML zu reinem Text", "convert_html_to_text": "Konvertiere HTML zu reinem Text",
"copy_to_clipboard": "Text wurde in die Zwischenablage kopiert!",
"cors_settings": "CORS Einstellungen", "cors_settings": "CORS Einstellungen",
"credentials_transport_warning": "<b>Warnung</b>: Das Hinzufügen einer neuen Regel bewirkt die Aktualisierung der Authentifizierungsdaten aller vorhandenen Einträge mit identischem Next Hop.", "credentials_transport_warning": "<b>Warnung</b>: Das Hinzufügen einer neuen Regel bewirkt die Aktualisierung der Authentifizierungsdaten aller vorhandenen Einträge mit identischem Next Hop.",
"customer_id": "Kunde", "customer_id": "Kunde",
@@ -181,8 +180,6 @@
"f2b_blacklist": "Blacklist für Netzwerke und Hosts", "f2b_blacklist": "Blacklist für Netzwerke und Hosts",
"f2b_filter": "Regex-Filter", "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 Blacklist wird immer eine Whitelist-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", "f2b_max_attempts": "Max. Versuche",
"f2b_max_ban_time": "Maximale Bannzeit in Sekunden", "f2b_max_ban_time": "Maximale Bannzeit in Sekunden",
"f2b_netban_ipv4": "Netzbereich für IPv4-Banns (8-32)", "f2b_netban_ipv4": "Netzbereich für IPv4-Banns (8-32)",
@@ -1022,7 +1019,6 @@
"domain_removed": "Domain %s wurde entfernt", "domain_removed": "Domain %s wurde entfernt",
"dovecot_restart_success": "Dovecot wurde erfolgreich neu gestartet", "dovecot_restart_success": "Dovecot wurde erfolgreich neu gestartet",
"eas_reset": "ActiveSync Gerät des Benutzers %s wurde zurückgesetzt", "eas_reset": "ActiveSync Gerät des Benutzers %s wurde zurückgesetzt",
"f2b_banlist_refreshed": "Banlist ID wurde erfolgreich erneuert.",
"f2b_modified": "Änderungen an Fail2ban-Parametern wurden gespeichert", "f2b_modified": "Änderungen an Fail2ban-Parametern wurden gespeichert",
"forwarding_host_added": "Weiterleitungs-Host %s wurde hinzugefügt", "forwarding_host_added": "Weiterleitungs-Host %s wurde hinzugefügt",
"forwarding_host_removed": "Weiterleitungs-Host %s wurde entfernt", "forwarding_host_removed": "Weiterleitungs-Host %s wurde entfernt",

View File

@@ -151,7 +151,6 @@
"change_logo": "Change logo", "change_logo": "Change logo",
"configuration": "Configuration", "configuration": "Configuration",
"convert_html_to_text": "Convert HTML to plain text", "convert_html_to_text": "Convert HTML to plain text",
"copy_to_clipboard": "Text copied to clipboard!",
"cors_settings": "CORS Settings", "cors_settings": "CORS Settings",
"credentials_transport_warning": "<b>Warning</b>: Adding a new transport map entry will update the credentials for all entries with a matching next hop column.", "credentials_transport_warning": "<b>Warning</b>: Adding a new transport map entry will update the credentials for all entries with a matching next hop column.",
"customer_id": "Customer ID", "customer_id": "Customer ID",
@@ -185,8 +184,6 @@
"f2b_blacklist": "Blacklisted networks/hosts", "f2b_blacklist": "Blacklisted networks/hosts",
"f2b_filter": "Regex filters", "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 blacklisted host or network will always outweigh a whitelist 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", "f2b_max_attempts": "Max. attempts",
"f2b_max_ban_time": "Max. ban time (s)", "f2b_max_ban_time": "Max. ban time (s)",
"f2b_netban_ipv4": "IPv4 subnet size to apply ban on (8-32)", "f2b_netban_ipv4": "IPv4 subnet size to apply ban on (8-32)",
@@ -1031,7 +1028,6 @@
"domain_removed": "Domain %s has been removed", "domain_removed": "Domain %s has been removed",
"dovecot_restart_success": "Dovecot was restarted successfully", "dovecot_restart_success": "Dovecot was restarted successfully",
"eas_reset": "ActiveSync devices for user %s were reset", "eas_reset": "ActiveSync devices for user %s were reset",
"f2b_banlist_refreshed": "Banlist ID has been successfully refreshed.",
"f2b_modified": "Changes to Fail2ban parameters have been saved", "f2b_modified": "Changes to Fail2ban parameters have been saved",
"forwarding_host_added": "Forwarding host %s has been added", "forwarding_host_added": "Forwarding host %s has been added",
"forwarding_host_removed": "Forwarding host %s has been removed", "forwarding_host_removed": "Forwarding host %s has been removed",

View File

@@ -1,4 +1,4 @@
<div role="tabpanel" class="tab-pane fade show active" id="tab-config-admins" role="tabpanel" aria-labelledby="tab-config-admins"> <div class="tab-pane fade show active" id="tab-config-admins" role="tabpanel" aria-labelledby="tab-config-admins">
<div class="card mb-4"> <div class="card mb-4">
<div class="card-header bg-danger text-white d-flex fs-5"> <div class="card-header bg-danger text-white d-flex fs-5">
<button class="btn d-md-none text-white flex-grow-1 text-start" data-bs-target="#collapse-tab-config-admins" data-bs-toggle="collapse" aria-controls="collapse-tab-config-admins"> <button class="btn d-md-none text-white flex-grow-1 text-start" data-bs-target="#collapse-tab-config-admins" data-bs-toggle="collapse" aria-controls="collapse-tab-config-admins">
@@ -227,7 +227,7 @@
<div class="card mb-4"> <div class="card mb-4">
<div class="card-header d-flex fs-5"> <div class="card-header d-flex fs-5">
<button class="btn d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-config-dadmins" data-bs-toggle="collapse" aria-controls="ollapse-tab-config-dadmins"> <button class="btn d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-config-dadmins" data-bs-toggle="collapse" aria-controls="collapse-tab-config-dadmins">
{{ lang.admin.domain_admins }} {{ lang.admin.domain_admins }}
</button> </button>
<span class="d-none d-md-block">{{ lang.admin.domain_admins }}</span> <span class="d-none d-md-block">{{ lang.admin.domain_admins }}</span>

View File

@@ -1,7 +1,7 @@
<div role="tabpanel" class="tab-pane fade" id="tab-config-customize" role="tabpanel" aria-labelledby="tab-config-customize"> <div class="tab-pane fade" id="tab-config-customize" role="tabpanel" aria-labelledby="tab-config-customize">
<div class="card mb-4"> <div class="card mb-4">
<div class="card-header d-flex fs-5"> <div class="card-header d-flex fs-5">
<button class="btn d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-config-customize" data-bs-toggle="collapse" aria-controls="ollapse-tab-config-customize"> <button class="btn d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-config-customize" data-bs-toggle="collapse" aria-controls="collapse-tab-config-customize">
{{ lang.admin.customize }} {{ lang.admin.customize }}
</button> </button>
<span class="d-none d-md-block">{{ lang.admin.customize }}</span> <span class="d-none d-md-block">{{ lang.admin.customize }}</span>

View File

@@ -1,7 +1,7 @@
<div role="tabpanel" class="tab-pane fade" id="tab-config-dkim" role="tabpanel" aria-labelledby="tab-config-dkim"> <div class="tab-pane fade" id="tab-config-dkim" role="tabpanel" aria-labelledby="tab-config-dkim">
<div class="card mb-4"> <div class="card mb-4">
<div class="card-header d-flex fs-5"> <div class="card-header d-flex fs-5">
<button class="btn d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-config-dkim" data-bs-toggle="collapse" aria-controls="ollapse-tab-config-dkim"> <button class="btn d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-config-dkim" data-bs-toggle="collapse" aria-controls="collapse-tab-config-dkim">
{{ lang.admin.dkim_keys }} {{ lang.admin.dkim_keys }}
</button> </button>
<span class="d-none d-md-block">{{ lang.admin.dkim_keys }}</span> <span class="d-none d-md-block">{{ lang.admin.dkim_keys }}</span>

View File

@@ -1,7 +1,7 @@
<div role="tabpanel" class="tab-pane fade" id="tab-config-f2b" role="tabpanel" aria-labelledby="tab-config-f2b"> <div class="tab-pane fade" id="tab-config-f2b" role="tabpanel" aria-labelledby="tab-config-f2b">
<div class="card mb-4"> <div class="card mb-4">
<div class="card-header d-flex fs-5"> <div class="card-header d-flex fs-5">
<button class="btn d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-config-f2b" data-bs-toggle="collapse" aria-controls="ollapse-tab-config-f2b"> <button class="btn d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-config-f2b" data-bs-toggle="collapse" aria-controls="collapse-tab-config-f2b">
{{ lang.admin.f2b_parameters }} {{ lang.admin.f2b_parameters }}
</button> </button>
<span class="d-none d-md-block">{{ lang.admin.f2b_parameters }}</span> <span class="d-none d-md-block">{{ lang.admin.f2b_parameters }}</span>
@@ -42,13 +42,6 @@
<input type="number" class="form-control" id="f2b_netban_ipv6" name="netban_ipv6" value="{{ f2b_data.netban_ipv6 }}" required> <input type="number" class="form-control" id="f2b_netban_ipv6" name="netban_ipv6" value="{{ f2b_data.netban_ipv6 }}" required>
</div> </div>
</div> </div>
<div class="mb-4">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" id="f2b_manage_external" value="1" name="manage_external" {% if f2b_data.manage_external == 1 %}checked{% endif %}>
<label class="form-check-label" for="f2b_manage_external">{{ lang.admin.f2b_manage_external }}</label>
</div>
<p class="text-muted">{{ lang.admin.f2b_manage_external_info }}</p>
</div>
<hr> <hr>
<p class="text-muted">{{ lang.admin.f2b_list_info|raw }}</p> <p class="text-muted">{{ lang.admin.f2b_list_info|raw }}</p>
<div class="mb-2"> <div class="mb-2">
@@ -97,27 +90,18 @@
{% if not f2b_data.active_bans and not f2b_data.perm_bans %} {% if not f2b_data.active_bans and not f2b_data.perm_bans %}
<i>{{ lang.admin.no_active_bans }}</i> <i>{{ lang.admin.no_active_bans }}</i>
{% endif %} {% endif %}
<form class="form-inline" data-id="f2b_banlist" role="form" method="post">
<div class="input-group mb-3">
<input type="text" class="form-control" aria-label="Banlist url" value="{{ f2b_banlist_url}}" id="banlist_url">
{% if is_https %}
<button class="btn btn-secondary" type="button" onclick="copyToClipboard('banlist_url')"><i class="bi bi-clipboard"></i></button>
{% endif %}
<button class="btn btn-secondary" type="button" data-action="edit_selected" data-item="{{ f2b_data.banlist_id }}" data-id="f2b_banlist" data-api-url='edit/fail2ban/banlist' data-api-attr='{}'><i class="bi bi-arrow-clockwise"></i></button>
</div>
</form>
{% for active_ban in f2b_data.active_bans %} {% for active_ban in f2b_data.active_bans %}
<p> <p>
<span class="badge fs-5 bg-info" style="padding:4px;font-size:85%;"> <span class="badge fs-5 bg-info py-0">
<i class="bi bi-funnel-fill"></i> <i class="bi bi-funnel-fill"></i>
<a href="https://bgp.he.net/ip/{{ active_ban.ip }}" target="_blank" style="color:white"> <a href="https://bgp.he.net/ip/{{ active_ban.ip }}" target="_blank" style="color:white">
{{ active_ban.network }} {{ active_ban.network }}
</a> </a>
({{ active_ban.banned_until }}) - ({{ active_ban.banned_until }}) -
{% if active_ban.queued_for_unban == 0 %} {% 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 class="btn btn-lg btn-link p-0 text-info" 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 class="btn btn-lg btn-link p-0 text-info" 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 class="btn btn-lg btn-link p-0 text-info" 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>
{% else %} {% else %}
<i>{{ lang.admin.unban_pending }}</i> <i>{{ lang.admin.unban_pending }}</i>
{% endif %} {% endif %}

View File

@@ -1,7 +1,7 @@
<div role="tabpanel" class="tab-pane fade" id="tab-config-fwdhosts" role="tabpanel" aria-labelledby="tab-config-fwdhosts"> <div class="tab-pane fade" id="tab-config-fwdhosts" role="tabpanel" aria-labelledby="tab-config-fwdhosts">
<div class="card mb-4"> <div class="card mb-4">
<div class="card-header d-flex fs-5"> <div class="card-header d-flex fs-5">
<button class="btn d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-config-fwdhosts" data-bs-toggle="collapse" aria-controls="ollapse-tab-config-fwdhosts"> <button class="btn d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-config-fwdhosts" data-bs-toggle="collapse" aria-controls="collapse-tab-config-fwdhosts">
{{ lang.admin.forwarding_hosts }} {{ lang.admin.forwarding_hosts }}
</button> </button>
<span class="d-none d-md-block">{{ lang.admin.forwarding_hosts }}</span> <span class="d-none d-md-block">{{ lang.admin.forwarding_hosts }}</span>

View File

@@ -1,7 +1,7 @@
<div role="tabpanel" class="tab-pane fade" id="tab-config-oauth2" role="tabpanel" aria-labelledby="tab-config-oauth2"> <div class="tab-pane fade" id="tab-config-oauth2" role="tabpanel" aria-labelledby="tab-config-oauth2">
<div class="card mb-4"> <div class="card mb-4">
<div class="card-header d-flex fs-5"> <div class="card-header d-flex fs-5">
<button class="btn d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-config-oauth2" data-bs-toggle="collapse" aria-controls="ollapse-tab-config-oauth2"> <button class="btn d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-config-oauth2" data-bs-toggle="collapse" aria-controls="collapse-tab-config-oauth2">
{{ lang.admin.oauth2_apps }} {{ lang.admin.oauth2_apps }}
</button> </button>
<span class="d-none d-md-block">{{ lang.admin.oauth2_apps }}</span> <span class="d-none d-md-block">{{ lang.admin.oauth2_apps }}</span>

View File

@@ -1,7 +1,7 @@
<div role="tabpanel" class="tab-pane fade" id="tab-config-password-policy" role="tabpanel" aria-labelledby="tab-config-password-policy"> <div class="tab-pane fade" id="tab-config-password-policy" role="tabpanel" aria-labelledby="tab-config-password-policy">
<div class="card mb-4"> <div class="card mb-4">
<div class="card-header d-flex fs-5"> <div class="card-header d-flex fs-5">
<button class="btn d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-config-password-policy" data-bs-toggle="collapse" aria-controls="ollapse-tab-config-password-policy"> <button class="btn d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-config-password-policy" data-bs-toggle="collapse" aria-controls="collapse-tab-config-password-policy">
{{ lang.admin.password_policy }} {{ lang.admin.password_policy }}
</button> </button>
<span class="d-none d-md-block">{{ lang.admin.password_policy }}</span> <span class="d-none d-md-block">{{ lang.admin.password_policy }}</span>

View File

@@ -1,7 +1,7 @@
<div role="tabpanel" class="tab-pane fade" id="tab-config-quarantine" role="tabpanel" aria-labelledby="tab-config-quarantine"> <div class="tab-pane fade" id="tab-config-quarantine" role="tabpanel" aria-labelledby="tab-config-quarantine">
<div class="card mb-4"> <div class="card mb-4">
<div class="card-header d-flex fs-5"> <div class="card-header d-flex fs-5">
<button class="btn d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-config-quarantine" data-bs-toggle="collapse" aria-controls="ollapse-tab-config-quarantine"> <button class="btn d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-config-quarantine" data-bs-toggle="collapse" aria-controls="collapse-tab-config-quarantine">
{{ lang.admin.quarantine }} {{ lang.admin.quarantine }}
</button> </button>
<span class="d-none d-md-block">{{ lang.admin.quarantine }}</span> <span class="d-none d-md-block">{{ lang.admin.quarantine }}</span>

View File

@@ -1,7 +1,7 @@
<div role="tabpanel" class="tab-pane fade" id="tab-config-quota" role="tabpanel" aria-labelledby="tab-config-quota"> <div class="tab-pane fade" id="tab-config-quota" role="tabpanel" aria-labelledby="tab-config-quota">
<div class="card mb-4"> <div class="card mb-4">
<div class="card-header d-flex fs-5"> <div class="card-header d-flex fs-5">
<button class="btn d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-config-quota" data-bs-toggle="collapse" aria-controls="ollapse-tab-config-quota"> <button class="btn d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-config-quota" data-bs-toggle="collapse" aria-controls="collapse-tab-config-quota">
{{ lang.admin.quota_notifications }} {{ lang.admin.quota_notifications }}
</button> </button>
<span class="d-none d-md-block">{{ lang.admin.quota_notifications }}</span> <span class="d-none d-md-block">{{ lang.admin.quota_notifications }}</span>

View File

@@ -1,7 +1,7 @@
<div role="tabpanel" class="tab-pane fade" id="tab-config-rsettings" role="tabpanel" aria-labelledby="tab-config-rsettings"> <div class="tab-pane fade" id="tab-config-rsettings" role="tabpanel" aria-labelledby="tab-config-rsettings">
<div class="card mb-4"> <div class="card mb-4">
<div class="card-header d-flex fs-5"> <div class="card-header d-flex fs-5">
<button class="btn d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-config-rsettings" data-bs-toggle="collapse" aria-controls="ollapse-tab-config-rsettings"> <button class="btn d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-config-rsettings" data-bs-toggle="collapse" aria-controls="collapse-tab-config-rsettings">
{{ lang.admin.rspamd_settings_map }} {{ lang.admin.rspamd_settings_map }}
</button> </button>
<span class="d-none d-md-block">{{ lang.admin.rspamd_settings_map }}</span> <span class="d-none d-md-block">{{ lang.admin.rspamd_settings_map }}</span>

View File

@@ -1,7 +1,7 @@
<div role="tabpanel" class="tab-pane fade" id="tab-config-rspamd" role="tabpanel" aria-labelledby="tab-config-rspamd"> <div class="tab-pane fade" id="tab-config-rspamd" role="tabpanel" aria-labelledby="tab-config-rspamd">
<div class="card mb-4"> <div class="card mb-4">
<div class="card-header d-flex fs-5"> <div class="card-header d-flex fs-5">
<button class="btn d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-config-rspamd" data-bs-toggle="collapse" aria-controls="ollapse-tab-config-rspamd"> <button class="btn d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-config-rspamd" data-bs-toggle="collapse" aria-controls="collapse-tab-config-rspamd">
Rspamd UI Rspamd UI
</button> </button>
<span class="d-none d-md-block">Rspamd UI</span> <span class="d-none d-md-block">Rspamd UI</span>

View File

@@ -1,7 +1,7 @@
<div role="tabpanel" class="tab-pane fade" id="tab-globalfilter-regex" role="tabpanel" aria-labelledby="tab-globalfilter-regex"> <div class="tab-pane fade" id="tab-globalfilter-regex" role="tabpanel" aria-labelledby="tab-globalfilter-regex">
<div class="card mb-4"> <div class="card mb-4">
<div class="card-header d-flex fs-5"> <div class="card-header d-flex fs-5">
<button class="btn d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-config-regex" data-bs-toggle="collapse" aria-controls="ollapse-tab-config-regex"> <button class="btn d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-config-regex" data-bs-toggle="collapse" aria-controls="collapse-tab-config-regex">
{{ lang.admin.rspamd_global_filters }} {{ lang.admin.rspamd_global_filters }}
</button> </button>
<span class="d-none d-md-block">{{ lang.admin.rspamd_global_filters }}</span> <span class="d-none d-md-block">{{ lang.admin.rspamd_global_filters }}</span>

View File

@@ -1,7 +1,7 @@
<div role="tabpanel" class="tab-pane fade" id="tab-config-ldap-admins" role="tabpanel" aria-labelledby="tab-config-ldap-admins"> <div class="tab-pane fade" id="tab-config-ldap-admins" role="tabpanel" aria-labelledby="tab-config-ldap-admins">
<div class="card mb-4"> <div class="card mb-4">
<div class="card-header d-flex fs-5"> <div class="card-header d-flex fs-5">
<button class="btn d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-config-ldap-admins" data-bs-toggle="collapse" aria-controls="ollapse-tab-config-ldap-admins"> <button class="btn d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-config-ldap-admins" data-bs-toggle="collapse" aria-controls="collapse-tab-config-ldap-admins">
{{ lang.admin.admins_ldap }} {{ lang.admin.admins_ldap }}
</button> </button>
<span class="d-none d-md-block">{{ lang.admin.admins_ldap }}</span> <span class="d-none d-md-block">{{ lang.admin.admins_ldap }}</span>

View File

@@ -1,7 +1,7 @@
<div role="tabpanel" class="tab-pane fade" id="tab-routing" role="tabpanel" aria-labelledby="tab-routing"> <div class="tab-pane fade" id="tab-routing" role="tabpanel" aria-labelledby="tab-routing">
<div class="card mb-4"> <div class="card mb-4">
<div class="card-header d-flex fs-5"> <div class="card-header d-flex fs-5">
<button class="btn d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-routing" data-bs-toggle="collapse" aria-controls="ollapse-tab-routing"> <button class="btn d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-routing" data-bs-toggle="collapse" aria-controls="collapse-tab-routing">
{{ lang.admin.relayhosts }} {{ lang.admin.relayhosts }}
</button> </button>
<span class="d-none d-md-block">{{ lang.admin.relayhosts }}</span> <span class="d-none d-md-block">{{ lang.admin.relayhosts }}</span>
@@ -47,7 +47,7 @@
<div class="card mb-4"> <div class="card mb-4">
<div class="card-header d-flex"> <div class="card-header d-flex">
<button class="btn d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-maps" data-bs-toggle="collapse" aria-controls="ollapse-tab-maps"> <button class="btn d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-maps" data-bs-toggle="collapse" aria-controls="collapse-tab-maps">
{{ lang.admin.transport_maps }} {{ lang.admin.transport_maps }}
</button> </button>
<span class="d-none d-md-block">{{ lang.admin.transport_maps }}</span> <span class="d-none d-md-block">{{ lang.admin.transport_maps }}</span>

View File

@@ -1,7 +1,7 @@
<div role="tabpanel" class="tab-pane fade" id="tab-sys-mails" role="tabpanel" aria-labelledby="tab-sys-mails"> <div class="tab-pane fade" id="tab-sys-mails" role="tabpanel" aria-labelledby="tab-sys-mails">
<div class="card mb-4"> <div class="card mb-4">
<div class="card-header d-flex fs-5"> <div class="card-header d-flex fs-5">
<button class="btn d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-sys-mails" data-bs-toggle="collapse" aria-controls="ollapse-tab-sys-mails"> <button class="btn d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-sys-mails" data-bs-toggle="collapse" aria-controls="collapse-tab-sys-mails">
{{ lang.admin.sys_mails }} {{ lang.admin.sys_mails }}
</button> </button>
<span class="d-none d-md-block">{{ lang.admin.sys_mails }}</span> <span class="d-none d-md-block">{{ lang.admin.sys_mails }}</span>

View File

@@ -1,4 +1,4 @@
<div role="tabpanel" class="tab-pane fade" id="tab-bcc" role="tabpanel" aria-labelledby="tab-bcc"> <div class="tab-pane fade" id="tab-bcc" role="tabpanel" aria-labelledby="tab-bcc">
<div class="card mb-4"> <div class="card mb-4">
<div class="card-header d-flex fs-5"> <div class="card-header d-flex fs-5">
<button class="btn d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-bcc" data-bs-toggle="collapse" aria-controls="collapse-tab-bcc"> <button class="btn d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-bcc" data-bs-toggle="collapse" aria-controls="collapse-tab-bcc">

View File

@@ -1,4 +1,4 @@
<div role="tabpanel" class="tab-pane fade" id="tab-domain-aliases" role="tabpanel" aria-labelledby="tab-domain-aliases"> <div class="tab-pane fade" id="tab-domain-aliases" role="tabpanel" aria-labelledby="tab-domain-aliases">
<div class="card mb-4"> <div class="card mb-4">
<div class="card-header d-flex fs-5"> <div class="card-header d-flex fs-5">
<button class="btn d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-domain-aliases" data-bs-toggle="collapse" aria-controls="collapse-tab-domain-aliases"> <button class="btn d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-domain-aliases" data-bs-toggle="collapse" aria-controls="collapse-tab-domain-aliases">

View File

@@ -1,4 +1,4 @@
<div role="tabpanel" class="tab-pane fade show active" id="tab-domains" role="tabpanel" aria-labelledby="tab-domains"> <div class="tab-pane fade show active" id="tab-domains" role="tabpanel" aria-labelledby="tab-domains">
<div class="card mb-4"> <div class="card mb-4">
<div class="card-header d-flex fs-5"> <div class="card-header d-flex fs-5">
<button class="btn d-sm-block d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-domains" data-bs-toggle="collapse" aria-controls="collapse-tab-domains"> <button class="btn d-sm-block d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-domains" data-bs-toggle="collapse" aria-controls="collapse-tab-domains">

View File

@@ -1,4 +1,4 @@
<div role="tabpanel" class="tab-pane fade" id="tab-filters" role="tabpanel" aria-labelledby="tab-filters"> <div class="tab-pane fade" id="tab-filters" role="tabpanel" aria-labelledby="tab-filters">
<div class="card mb-4"> <div class="card mb-4">
<div class="card-header d-flex fs-5"> <div class="card-header d-flex fs-5">
<button class="btn d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-filters" data-bs-toggle="collapse" aria-controls="collapse-tab-filters"> <button class="btn d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-filters" data-bs-toggle="collapse" aria-controls="collapse-tab-filters">

View File

@@ -1,4 +1,4 @@
<div role="tabpanel" class="tab-pane fade" id="tab-mailboxes" role="tabpanel" aria-labelledby="tab-mailboxes"> <div class="tab-pane fade" id="tab-mailboxes" role="tabpanel" aria-labelledby="tab-mailboxes">
<div class="card mb-4"> <div class="card mb-4">
<div class="card-header d-flex fs-5"> <div class="card-header d-flex fs-5">
<button class="btn d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-mailboxes" data-bs-toggle="collapse" aria-controls="collapse-tab-mailboxes"> <button class="btn d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-mailboxes" data-bs-toggle="collapse" aria-controls="collapse-tab-mailboxes">

View File

@@ -1,4 +1,4 @@
<div role="tabpanel" class="tab-pane fade" id="tab-mbox-aliases" role="tabpanel" aria-labelledby="tab-mbox-aliases"> <div class="tab-pane fade" id="tab-mbox-aliases" role="tabpanel" aria-labelledby="tab-mbox-aliases">
<div class="card mb-4"> <div class="card mb-4">
<div class="card-header d-flex fs-5"> <div class="card-header d-flex fs-5">
<button class="btn d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-mbox-aliases" data-bs-toggle="collapse" aria-controls="collapse-tab-mbox-aliases"> <button class="btn d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-mbox-aliases" data-bs-toggle="collapse" aria-controls="collapse-tab-mbox-aliases">

View File

@@ -1,4 +1,4 @@
<div role="tabpanel" class="tab-pane fade" id="tab-resources" role="tabpanel" aria-labelledby="tab-resources"> <div class="tab-pane fade" id="tab-resources" role="tabpanel" aria-labelledby="tab-resources">
<div class="card mb-4"> <div class="card mb-4">
<div class="card-header d-flex fs-5"> <div class="card-header d-flex fs-5">
<button class="btn d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-resources" data-bs-toggle="collapse" aria-controls="collapse-tab-resources"> <button class="btn d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-resources" data-bs-toggle="collapse" aria-controls="collapse-tab-resources">

View File

@@ -1,4 +1,4 @@
<div role="tabpanel" class="tab-pane fade" id="tab-syncjobs" role="tabpanel" aria-labelledby="tab-syncjobs"> <div class="tab-pane fade" id="tab-syncjobs" role="tabpanel" aria-labelledby="tab-syncjobs">
<div class="card mb-4"> <div class="card mb-4">
<div class="card-header d-flex fs-5"> <div class="card-header d-flex fs-5">
<button class="btn d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-syncjobs" data-bs-toggle="collapse" aria-controls="collapse-tab-syncjobs"> <button class="btn d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-syncjobs" data-bs-toggle="collapse" aria-controls="collapse-tab-syncjobs">

View File

@@ -1,4 +1,4 @@
<div role="tabpanel" class="tab-pane fade show" id="tab-templates-domains" role="tabpanel" aria-labelledby="tab-templates-domains"> <div class="tab-pane fade show" id="tab-templates-domains" role="tabpanel" aria-labelledby="tab-templates-domains">
<div class="card mb-4"> <div class="card mb-4">
<div class="card-header d-flex fs-5"> <div class="card-header d-flex fs-5">
<button class="btn d-sm-block d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-templates-domains" data-bs-toggle="collapse" aria-controls="collapse-tab-templates-domains"> <button class="btn d-sm-block d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-templates-domains" data-bs-toggle="collapse" aria-controls="collapse-tab-templates-domains">

View File

@@ -1,4 +1,4 @@
<div role="tabpanel" class="tab-pane fade show" id="tab-templates-mbox" role="tabpanel" aria-labelledby="tab-templates-mbox"> <div class="tab-pane fade show" id="tab-templates-mbox" role="tabpanel" aria-labelledby="tab-templates-mbox">
<div class="card mb-4"> <div class="card mb-4">
<div class="card-header d-flex fs-5"> <div class="card-header d-flex fs-5">
<button class="btn d-sm-block d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-templates-mbox" data-bs-toggle="collapse" aria-controls="collapse-tab-templates-mbox"> <button class="btn d-sm-block d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-templates-mbox" data-bs-toggle="collapse" aria-controls="collapse-tab-templates-mbox">

View File

@@ -1,4 +1,4 @@
<div role="tabpanel" class="tab-pane fade{% if mailcow_cc_role != 'admin' %} d-none{% endif %}" id="tab-tls-policy" role="tabpanel" aria-labelledby="tab-tls-policy"> <div class="tab-pane fade{% if mailcow_cc_role != 'admin' %} d-none{% endif %}" id="tab-tls-policy" role="tabpanel" aria-labelledby="tab-tls-policy">
<div class="card mb-4"> <div class="card mb-4">
<div class="card-header d-flex fs-5"> <div class="card-header d-flex fs-5">
<button class="btn d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-tls-policy" data-bs-toggle="collapse" aria-controls="collapse-tab-tls-policy"> <button class="btn d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-tls-policy" data-bs-toggle="collapse" aria-controls="collapse-tab-tls-policy">

View File

@@ -1,10 +1,10 @@
<div role="tabpanel" class="tab-pane fade" id="AppPasswds" role="tabpanel" aria-labelledby="AppPasswds"> <div class="tab-pane fade" id="AppPasswds" role="tabpanel" aria-labelledby="AppPasswds">
<div class="card mb-4"> <div class="card mb-4">
<div class="card-header d-flex fs-5"> <div class="card-header d-flex fs-5">
<button class="btn d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-AppPasswds" data-bs-toggle="collapse" aria-controls="collapse-tab-AppPasswds"> <button class="btn d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-AppPasswds" data-bs-toggle="collapse" aria-controls="collapse-tab-AppPasswds">
{{ lang.user.app_passwds }} {{ lang.user.app_passwds }}
</button> </button>
<span class="d-none d-md-block">{{ lang.user.app_passwds }} <span class="d-none d-md-block">{{ lang.user.app_passwds }}</span>
</div> </div>
<div id="collapse-tab-AppPasswds" class="card-body collapse" data-bs-parent="#user-content"> <div id="collapse-tab-AppPasswds" class="card-body collapse" data-bs-parent="#user-content">
<div class="mass-actions-user mb-4"> <div class="mass-actions-user mb-4">

View File

@@ -1,10 +1,10 @@
<div role="tabpanel" class="tab-pane fade" id="Pushover" role="tabpanel" aria-labelledby="Pushover"> <div class="tab-pane fade" id="Pushover" role="tabpanel" aria-labelledby="Pushover">
<div class="card mb-4"> <div class="card mb-4">
<div class="card-header d-flex fs-5"> <div class="card-header d-flex fs-5">
<button class="btn d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-Pushover" data-bs-toggle="collapse" aria-controls="collapse-tab-Pushover"> <button class="btn d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-Pushover" data-bs-toggle="collapse" aria-controls="collapse-tab-Pushover">
Pushover API Pushover API
</button> </button>
<span class="d-none d-md-block">Pushover API <span class="d-none d-md-block">Pushover API</span>
</div> </div>
<div id="collapse-tab-Pushover" class="card-body collapse" data-bs-parent="#user-content"> <div id="collapse-tab-Pushover" class="card-body collapse" data-bs-parent="#user-content">
<form data-id="pushover" class="form well" method="post"> <form data-id="pushover" class="form well" method="post">

View File

@@ -1,10 +1,10 @@
<div role="tabpanel" class="tab-pane fade" id="SpamAliases" role="tabpanel" aria-labelledby="SpamAliases"> <div class="tab-pane fade" id="SpamAliases" role="tabpanel" aria-labelledby="SpamAliases">
<div class="card mb-4"> <div class="card mb-4">
<div class="card-header d-flex fs-5"> <div class="card-header d-flex fs-5">
<button class="btn d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-SpamAliases" data-bs-toggle="collapse" aria-controls="collapse-tab-SpamAliases"> <button class="btn d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-SpamAliases" data-bs-toggle="collapse" aria-controls="collapse-tab-SpamAliases">
{{ lang.user.spam_aliases }} {{ lang.user.spam_aliases }}
</button> </button>
<span class="d-none d-md-block">{{ lang.user.spam_aliases }} <span class="d-none d-md-block">{{ lang.user.spam_aliases }}</span>
</div> </div>
<div id="collapse-tab-SpamAliases" class="card-body collapse" data-bs-parent="#user-content"> <div id="collapse-tab-SpamAliases" class="card-body collapse" data-bs-parent="#user-content">
<div class="row"> <div class="row">

View File

@@ -1,10 +1,10 @@
<div role="tabpanel" class="tab-pane fade" id="Spamfilter" role="tabpanel" aria-labelledby="Spamfilter"> <div class="tab-pane fade" id="Spamfilter" role="tabpanel" aria-labelledby="Spamfilter">
<div class="card mb-4"> <div class="card mb-4">
<div class="card-header d-flex fs-5"> <div class="card-header d-flex fs-5">
<button class="btn d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-Spamfilter" data-bs-toggle="collapse" aria-controls="collapse-tab-Spamfilter"> <button class="btn d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-Spamfilter" data-bs-toggle="collapse" aria-controls="collapse-tab-Spamfilter">
{{ lang.user.spamfilter }} {{ lang.user.spamfilter }}
</button> </button>
<span class="d-none d-md-block">{{ lang.user.spamfilter }} <span class="d-none d-md-block">{{ lang.user.spamfilter }}</span>
</div> </div>
<div id="collapse-tab-Spamfilter" class="card-body collapse" data-bs-parent="#user-content"> <div id="collapse-tab-Spamfilter" class="card-body collapse" data-bs-parent="#user-content">
<h4>{{ lang.user.spamfilter_behavior }}</h4> <h4>{{ lang.user.spamfilter_behavior }}</h4>

View File

@@ -1,10 +1,10 @@
<div role="tabpanel" class="tab-pane fade" id="Syncjobs" role="tabpanel" aria-labelledby="Syncjobs"> <div class="tab-pane fade" id="Syncjobs" role="tabpanel" aria-labelledby="Syncjobs">
<div class="card mb-4"> <div class="card mb-4">
<div class="card-header d-flex fs-5"> <div class="card-header d-flex fs-5">
<button class="btn d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-Syncjobs" data-bs-toggle="collapse" aria-controls="collapse-tab-Syncjobs"> <button class="btn d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-Syncjobs" data-bs-toggle="collapse" aria-controls="collapse-tab-Syncjobs">
{{ lang.user.sync_jobs }} {{ lang.user.sync_jobs }}
</button> </button>
<span class="d-none d-md-block">{{ lang.user.sync_jobs }} <span class="d-none d-md-block">{{ lang.user.sync_jobs }}</span>
</div> </div>
<div id="collapse-tab-Syncjobs" class="card-body collapse" data-bs-parent="#user-content"> <div id="collapse-tab-Syncjobs" class="card-body collapse" data-bs-parent="#user-content">
<div class="mass-actions-user mb-4"> <div class="mass-actions-user mb-4">

View File

@@ -1,10 +1,10 @@
<div role="tabpanel" class="tab-pane fade in active show" id="tab-user-auth" role="tabpanel" aria-labelledby="tab-user-auth"> <div class="tab-pane fade in active show" id="tab-user-auth" role="tabpanel" aria-labelledby="tab-user-auth">
<div class="card mb-4"> <div class="card mb-4">
<div class="card-header d-flex fs-5"> <div class="card-header d-flex fs-5">
<button class="btn d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-user-auth" data-bs-toggle="collapse" aria-controls="collapse-tab-user-auth"> <button class="btn d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-user-auth" data-bs-toggle="collapse" aria-controls="collapse-tab-user-auth">
{{ lang.user.mailbox_general }} {{ lang.user.mailbox_general }}
</button> </button>
<span class="d-none d-md-block">{{ lang.user.mailbox_general }} <span class="d-none d-md-block">{{ lang.user.mailbox_general }}</span>
</div> </div>
<div id="collapse-tab-user-auth" class="card-body collapse" data-bs-parent="#user-content"> <div id="collapse-tab-user-auth" class="card-body collapse" data-bs-parent="#user-content">
{% if mailboxdata.attributes.force_pw_update == '1' %} {% if mailboxdata.attributes.force_pw_update == '1' %}

View File

@@ -1,10 +1,10 @@
<div role="tabpanel" class="tab-pane fade" id="tab-user-details" role="tabpanel" aria-labelledby="tab-user-details"> <div class="tab-pane fade" id="tab-user-details" role="tabpanel" aria-labelledby="tab-user-details">
<div class="card mb-4"> <div class="card mb-4">
<div class="card-header d-flex fs-5"> <div class="card-header d-flex fs-5">
<button class="btn d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-user-details" data-bs-toggle="collapse" aria-controls="collapse-tab-user-details"> <button class="btn d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-user-details" data-bs-toggle="collapse" aria-controls="collapse-tab-user-details">
{{ lang.user.mailbox_details }} {{ lang.user.mailbox_details }}
</button> </button>
<span class="d-none d-md-block">{{ lang.user.mailbox_details }} <span class="d-none d-md-block">{{ lang.user.mailbox_details }}</span>
</div> </div>
<div id="collapse-tab-user-details" class="card-body collapse" data-bs-parent="#user-content"> <div id="collapse-tab-user-details" class="card-body collapse" data-bs-parent="#user-content">
<div class="row"> <div class="row">
@@ -46,7 +46,7 @@
<div class="col-sm-8 col-md-9 col-12"> <div class="col-sm-8 col-md-9 col-12">
<p> <p>
{% if user_get_alias_details.aliases_also_send_as == '*' %} {% if user_get_alias_details.aliases_also_send_as == '*' %}
{{ lang.user.sender_acl_disabled }} {{ lang.user.sender_acl_disabled | raw }}
{% elseif user_get_alias_details.aliases_also_send_as %} {% elseif user_get_alias_details.aliases_also_send_as %}
{{ user_get_alias_details.aliases_also_send_as }} {{ user_get_alias_details.aliases_also_send_as }}
{% else %} {% else %}
@@ -58,13 +58,13 @@
<div class="row"> <div class="row">
<div class="col-sm-4 col-md-3 col-12 text-sm-end text-start mb-4">{{ lang.user.aliases_send_as_all }}:</div> <div class="col-sm-4 col-md-3 col-12 text-sm-end text-start mb-4">{{ lang.user.aliases_send_as_all }}:</div>
<div class="col-sm-8 col-md-9 col-12"> <div class="col-sm-8 col-md-9 col-12">
<p>{% if not user_get_alias_details.aliases_send_as_all %}<i class="bi bi-x-lg"></i>{% endif %}</p> <p>{% if not user_get_alias_details.aliases_send_as_all %}<i class="bi bi-x-lg"></i>{% else %}{{ user_get_alias_details.aliases_send_as_all }}{% endif %}</p>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-sm-4 col-md-3 col-12 text-sm-end text-start mb-4">{{ lang.user.is_catch_all }}:</div> <div class="col-sm-4 col-md-3 col-12 text-sm-end text-start mb-4">{{ lang.user.is_catch_all }}:</div>
<div class="col-sm-8 col-md-9 col-12"> <div class="col-sm-8 col-md-9 col-12">
<p>{% if not user_get_alias_details.is_catch_all %}<i class="bi bi-x-lg"></i>{% endif %}</p> <p>{% if not user_get_alias_details.is_catch_all %}<i class="bi bi-x-lg"></i>{% else %}{{ user_get_alias_details.is_catch_all }}{% endif %}</p>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -1,10 +1,10 @@
<div role="tabpanel" class="tab-pane fade" id="tab-user-settings" role="tabpanel" aria-labelledby="tab-user-settings"> <div class="tab-pane fade" id="tab-user-settings" role="tabpanel" aria-labelledby="tab-user-settings">
<div class="card mb-4"> <div class="card mb-4">
<div class="card-header d-flex fs-5"> <div class="card-header d-flex fs-5">
<button class="btn d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-user-settings" data-bs-toggle="collapse" aria-controls="collapse-tab-user-settings"> <button class="btn d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-user-settings" data-bs-toggle="collapse" aria-controls="collapse-tab-user-settings">
{{ lang.user.mailbox_settings }} {{ lang.user.mailbox_settings }}
</button> </button>
<span class="d-none d-md-block">{{ lang.user.mailbox_settings }} <span class="d-none d-md-block">{{ lang.user.mailbox_settings }}</span>
</div> </div>
<div id="collapse-tab-user-settings" class="card-body collapse" data-bs-parent="#user-content"> <div id="collapse-tab-user-settings" class="card-body collapse" data-bs-parent="#user-content">
{# Show tagging options #} {# Show tagging options #}

View File

@@ -297,7 +297,7 @@ services:
- dovecot - dovecot
postfix-mailcow: postfix-mailcow:
image: mailcow/postfix:1.68 image: mailcow/postfix:1.69
depends_on: depends_on:
- mysql-mailcow - mysql-mailcow
volumes: volumes:
@@ -317,6 +317,7 @@ services:
- REDIS_SLAVEOF_IP=${REDIS_SLAVEOF_IP:-} - REDIS_SLAVEOF_IP=${REDIS_SLAVEOF_IP:-}
- REDIS_SLAVEOF_PORT=${REDIS_SLAVEOF_PORT:-} - REDIS_SLAVEOF_PORT=${REDIS_SLAVEOF_PORT:-}
- MAILCOW_HOSTNAME=${MAILCOW_HOSTNAME} - MAILCOW_HOSTNAME=${MAILCOW_HOSTNAME}
- SPAMHAUS_DQS_KEY=${SPAMHAUS_DQS_KEY:-}
cap_add: cap_add:
- NET_BIND_SERVICE - NET_BIND_SERVICE
ports: ports:
@@ -426,7 +427,7 @@ services:
- acme - acme
netfilter-mailcow: netfilter-mailcow:
image: mailcow/netfilter:1.53 image: mailcow/netfilter:1.52
stop_grace_period: 30s stop_grace_period: 30s
depends_on: depends_on:
- dovecot-mailcow - dovecot-mailcow
@@ -511,7 +512,7 @@ services:
- watchdog - watchdog
dockerapi-mailcow: dockerapi-mailcow:
image: mailcow/dockerapi:2.04 image: mailcow/dockerapi:2.05
security_opt: security_opt:
- label=disable - label=disable
restart: always restart: always

View File

@@ -21,7 +21,7 @@ if grep --help 2>&1 | head -n 1 | grep -q -i "busybox"; then echo "BusyBox grep
if cp --help 2>&1 | head -n 1 | grep -q -i "busybox"; then echo "BusyBox cp detected, please install coreutils, \"apk add --no-cache --upgrade coreutils\""; exit 1; fi if cp --help 2>&1 | head -n 1 | grep -q -i "busybox"; then echo "BusyBox cp detected, please install coreutils, \"apk add --no-cache --upgrade coreutils\""; exit 1; fi
if sed --help 2>&1 | head -n 1 | grep -q -i "busybox"; then echo "BusyBox sed detected, please install gnu sed, \"apk add --no-cache --upgrade sed\""; exit 1; fi if sed --help 2>&1 | head -n 1 | grep -q -i "busybox"; then echo "BusyBox sed detected, please install gnu sed, \"apk add --no-cache --upgrade sed\""; exit 1; fi
for bin in openssl curl docker git awk sha1sum; do for bin in openssl curl docker git awk sha1sum grep cut whois; do
if [[ -z $(which ${bin}) ]]; then echo "Cannot find ${bin}, exiting..."; exit 1; fi if [[ -z $(which ${bin}) ]]; then echo "Cannot find ${bin}, exiting..."; exit 1; fi
done done
@@ -58,6 +58,23 @@ else
exit 1 exit 1
fi fi
detect_bad_asn() {
if [[ curl -s http://fuzzy.mailcow.email/asn_list.txt | grep $(whois -h whois.radb.net $(curl -s http://ipv4.mailcow.email) | grep -i origin | tr -s " " | cut -d " " -f2 | head -1) ]]; then
if ! $SPAMHAUS_DQS_KEY; then
echo -e "\e[31mYour server's public IP uses an AS that is blocked by Spamhaus to use their DNS blocklists for Postfix."
echo -e "\e[31mmailcow did not detected a value for the variable SPAMHAUS_DQS_KEY inside mailcow.conf!"
echo ""
echo -e "\e[31mTo use the Spamhaus DNS Blocklists again, you will need to create a FREE account for their Data Query Service (DQS) at: https://www.spamhaus.com/free-trial/sign-up-for-a-free-data-query-service-account"
echo -e "\e[31mOnce done, enter your DQS API key in mailcow.conf and mailcow will do the rest for you!"
sleep 2
else
echo -e "\e[31mYour server's public IP uses an AS that is blocked by Spamhaus to use their DNS blocklists for Postfix."
echo -e "\e[33mmailcow detected a Value for the variable SPAMHAUS_DQS_KEY inside mailcow.conf. Postfix will use DQS with the given API key..."
fi
fi
}
### If generate_config.sh is started with --dev or -d it will not check out nightly or master branch and will keep on the current branch ### If generate_config.sh is started with --dev or -d it will not check out nightly or master branch and will keep on the current branch
if [[ ${1} == "--dev" || ${1} == "-d" ]]; then if [[ ${1} == "--dev" || ${1} == "-d" ]]; then
SKIP_BRANCH=y SKIP_BRANCH=y
@@ -431,6 +448,13 @@ ACME_CONTACT=
# root certificates can be placed for validation under mailcow-dockerized/data/web/inc/lib/WebAuthn/rootCertificates # root certificates can be placed for validation under mailcow-dockerized/data/web/inc/lib/WebAuthn/rootCertificates
WEBAUTHN_ONLY_TRUSTED_VENDORS=n WEBAUTHN_ONLY_TRUSTED_VENDORS=n
# Spamhaus Data Query Service Key
# Optional: Leave empty for none
# Enter your key here if you are using a blocked ASN (OVH, AWS, Cloudflare e.g) for the unregistered Spamhaus Blocklist.
# If empty, it will completely disable Spamhaus blocklists if it detects that you are running on a server using a blocked AS.
# Otherwise it will work normally.
SPAMHAUS_DQS_KEY=
EOF EOF
mkdir -p data/assets/ssl mkdir -p data/assets/ssl
@@ -503,3 +527,5 @@ else
echo '?>' >> data/web/inc/app_info.inc.php echo '?>' >> data/web/inc/app_info.inc.php
echo -e "\e[33mCannot determine current git repository version...\e[0m" echo -e "\e[33mCannot determine current git repository version...\e[0m"
fi fi
detect_bad_asn

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# renovate: datasource=github-releases depName=nextcloud/server versioning=semver extractVersion=^v(?<version>.*)$ # renovate: datasource=github-releases depName=nextcloud/server versioning=semver extractVersion=^v(?<version>.*)$
NEXTCLOUD_VERSION=27.0.0 NEXTCLOUD_VERSION=27.0.1
echo -ne "Checking prerequisites..." echo -ne "Checking prerequisites..."
sleep 1 sleep 1

View File

@@ -255,6 +255,25 @@ elif [ "${DOCKER_COMPOSE_VERSION}" == "standalone" ]; then
fi fi
} }
detect_bad_asn() {
if curl -s http://fuzzy.mailcow.email/asn_list.txt | grep $(whois -h whois.radb.net $(curl -s http://ipv4.mailcow.email) | grep -i origin | tr -s " " | cut -d " " -f2 | head -1) > /dev/null ; then
if [ -z "$SPAMHAUS_DQS_KEY" ]; then
echo -e "\e[33mYour server's public IP uses an AS that is blocked by Spamhaus to use their DNS public blocklists for Postfix.\e[0m"
echo -e "\e[33mmailcow did not detected a value for the variable SPAMHAUS_DQS_KEY inside mailcow.conf!\e[0m"
sleep 2
echo ""
echo -e "\e[33mTo use the Spamhaus DNS Blocklists again, you will need to create a FREE account for their Data Query Service (DQS) at: https://www.spamhaus.com/free-trial/sign-up-for-a-free-data-query-service-account\e[0m"
echo -e "\e[33mOnce done, enter your DQS API key in mailcow.conf and mailcow will do the rest for you!\e[0m"
echo ""
sleep 2
else
echo -e "\e[33mYour server's public IP uses an AS that is blocked by Spamhaus to use their DNS public blocklists for Postfix.\e[0m"
echo -e "\e[32mmailcow detected a Value for the variable SPAMHAUS_DQS_KEY inside mailcow.conf. Postfix will use DQS with the given API key...\e[0m"
fi
fi
}
############## End Function Section ############## ############## End Function Section ##############
# Check permissions # Check permissions
@@ -301,7 +320,7 @@ umask 0022
unset COMPOSE_COMMAND unset COMPOSE_COMMAND
unset DOCKER_COMPOSE_VERSION unset DOCKER_COMPOSE_VERSION
for bin in curl docker git awk sha1sum; do for bin in curl docker git awk sha1sum grep cut whois; do
if [[ -z $(command -v ${bin}) ]]; then if [[ -z $(command -v ${bin}) ]]; then
echo "Cannot find ${bin}, exiting..." echo "Cannot find ${bin}, exiting..."
exit 1; exit 1;
@@ -442,8 +461,11 @@ CONFIG_ARRAY=(
"ACME_CONTACT" "ACME_CONTACT"
"WATCHDOG_VERBOSE" "WATCHDOG_VERBOSE"
"WEBAUTHN_ONLY_TRUSTED_VENDORS" "WEBAUTHN_ONLY_TRUSTED_VENDORS"
"SPAMHAUS_DQS_KEY"
) )
detect_bad_asn
sed -i --follow-symlinks '$a\' mailcow.conf sed -i --follow-symlinks '$a\' mailcow.conf
for option in ${CONFIG_ARRAY[@]}; do for option in ${CONFIG_ARRAY[@]}; do
if [[ ${option} == "ADDITIONAL_SAN" ]]; then if [[ ${option} == "ADDITIONAL_SAN" ]]; then
@@ -642,6 +664,7 @@ for option in ${CONFIG_ARRAY[@]}; do
fi fi
elif [[ ${option} == "ADDITIONAL_SERVER_NAMES" ]]; then elif [[ ${option} == "ADDITIONAL_SERVER_NAMES" ]]; then
if ! grep -q ${option} mailcow.conf; then if ! grep -q ${option} mailcow.conf; then
echo "Adding new option \"${option}\" to mailcow.conf"
echo '# Additional server names for mailcow UI' >> mailcow.conf echo '# Additional server names for mailcow UI' >> mailcow.conf
echo '#' >> mailcow.conf echo '#' >> mailcow.conf
echo '# Specify alternative addresses for the mailcow UI to respond to' >> mailcow.conf echo '# Specify alternative addresses for the mailcow UI to respond to' >> mailcow.conf
@@ -653,6 +676,7 @@ for option in ${CONFIG_ARRAY[@]}; do
fi fi
elif [[ ${option} == "ACME_CONTACT" ]]; then elif [[ ${option} == "ACME_CONTACT" ]]; then
if ! grep -q ${option} mailcow.conf; then if ! grep -q ${option} mailcow.conf; then
echo "Adding new option \"${option}\" to mailcow.conf"
echo '# Lets Encrypt registration contact information' >> mailcow.conf echo '# Lets Encrypt registration contact information' >> mailcow.conf
echo '# Optional: Leave empty for none' >> mailcow.conf echo '# Optional: Leave empty for none' >> mailcow.conf
echo '# This value is only used on first order!' >> mailcow.conf echo '# This value is only used on first order!' >> mailcow.conf
@@ -662,13 +686,25 @@ for option in ${CONFIG_ARRAY[@]}; do
fi fi
elif [[ ${option} == "WEBAUTHN_ONLY_TRUSTED_VENDORS" ]]; then elif [[ ${option} == "WEBAUTHN_ONLY_TRUSTED_VENDORS" ]]; then
if ! grep -q ${option} mailcow.conf; then if ! grep -q ${option} mailcow.conf; then
echo "Adding new option \"${option}\" to mailcow.conf"
echo "# WebAuthn device manufacturer verification" >> mailcow.conf echo "# WebAuthn device manufacturer verification" >> mailcow.conf
echo '# After setting WEBAUTHN_ONLY_TRUSTED_VENDORS=y only devices from trusted manufacturers are allowed' >> mailcow.conf echo '# After setting WEBAUTHN_ONLY_TRUSTED_VENDORS=y only devices from trusted manufacturers are allowed' >> mailcow.conf
echo '# root certificates can be placed for validation under mailcow-dockerized/data/web/inc/lib/WebAuthn/rootCertificates' >> mailcow.conf echo '# root certificates can be placed for validation under mailcow-dockerized/data/web/inc/lib/WebAuthn/rootCertificates' >> mailcow.conf
echo 'WEBAUTHN_ONLY_TRUSTED_VENDORS=n' >> mailcow.conf echo 'WEBAUTHN_ONLY_TRUSTED_VENDORS=n' >> mailcow.conf
fi fi
elif [[ ${option} == "WATCHDOG_VERBOSE" ]]; then elif [[ ${option} == "SPAMHAUS_DQS_KEY" ]]; then
if ! grep -q ${option} mailcow.conf; then if ! grep -q ${option} mailcow.conf; then
echo "Adding new option \"${option}\" to mailcow.conf"
echo "# Spamhaus Data Query Service Key" >> mailcow.conf
echo '# Optional: Leave empty for none' >> mailcow.conf
echo '# Enter your key here if you are using a blocked ASN (OVH, AWS, Cloudflare e.g) for the unregistered Spamhaus Blocklist.' >> mailcow.conf
echo '# If empty, it will completely disable Spamhaus blocklists if it detects that you are running on a server using a blocked AS.' >> mailcow.conf
echo '# Otherwise it will work as usual.' >> mailcow.conf
echo 'SPAMHAUS_DQS_KEY=' >> mailcow.conf
fi
elif [[ ${option} == "WATCHDOG_VERBOSE" ]]; then
if ! grep -q ${option} mailcow.conf; then
echo "Adding new option \"${option}\" to mailcow.conf"
echo '# Enable watchdog verbose logging' >> mailcow.conf echo '# Enable watchdog verbose logging' >> mailcow.conf
echo 'WATCHDOG_VERBOSE=n' >> mailcow.conf echo 'WATCHDOG_VERBOSE=n' >> mailcow.conf
fi fi