Compare commits

...

10 Commits

Author SHA1 Message Date
milkmaker
3dcdf3f3ae update postscreen_access.cidr 2026-04-01 00:33:32 +00:00
FreddleSpl0it
ec24825280 Merge pull request #7173 from mailcow/fix/escaping
[Web][Dovecot] Improve input validation and escaping
2026-03-31 09:45:23 +02:00
FreddleSpl0it
5a00b5124b [Web][Dovecot] Add parameterized queries and input validation for quarantine_category 2026-03-29 12:08:45 +02:00
FreddleSpl0it
8c039f694f Improve template URI escaping and parameter handling 2026-03-19 12:48:43 +01:00
FreddleSpl0it
95bf46c1e4 escape HTML in autodiscover logs 2026-03-19 12:44:50 +01:00
FreddleSpl0it
edde35156d escape HTML in qitem details 2026-03-19 12:44:30 +01:00
FreddleSpl0it
84e3c32f13 escape HTML in last logins 2026-03-19 12:44:00 +01:00
FreddleSpl0it
ecb848493b add missing object-level access control 2026-03-19 12:42:45 +01:00
FreddleSpl0it
8a65b9d1c6 add missing access control 2026-03-19 12:41:47 +01:00
FreddleSpl0it
ed9264fd2a [Web] Allow force_tfa for LDAP and Keycloak users 2026-03-13 14:13:25 +01:00
13 changed files with 162 additions and 31 deletions

View File

@@ -47,7 +47,7 @@ try:
if max_score == "":
max_score = 9999.0
def query_mysql(query, headers = True, update = False):
def query_mysql(query, params = None, headers = True, update = False):
while True:
try:
cnx = MySQLdb.connect(user=os.environ.get('DBUSER'), password=os.environ.get('DBPASS'), database=os.environ.get('DBNAME'), charset="utf8mb4", collation="utf8mb4_general_ci")
@@ -57,7 +57,10 @@ try:
else:
break
cur = cnx.cursor()
cur.execute(query)
if params:
cur.execute(query, params)
else:
cur.execute(query)
if not update:
result = []
columns = tuple( [d[0] for d in cur.description] )
@@ -76,7 +79,7 @@ try:
def notify_rcpt(rcpt, msg_count, quarantine_acl, category):
if category == "add_header": category = "add header"
meta_query = query_mysql('SELECT `qhash`, id, subject, score, sender, created, action FROM quarantine WHERE notified = 0 AND rcpt = "%s" AND score < %f AND (action = "%s" OR "all" = "%s")' % (rcpt, max_score, category, category))
meta_query = query_mysql('SELECT `qhash`, id, subject, score, sender, created, action FROM quarantine WHERE notified = 0 AND rcpt = %s AND score < %s AND (action = %s OR "all" = %s)', (rcpt, max_score, category, category))
print("%s: %d of %d messages qualify for notification" % (rcpt, len(meta_query), msg_count))
if len(meta_query) == 0:
return
@@ -130,7 +133,7 @@ try:
server.sendmail(msg['From'], [str(redirect)] + [str(bcc)], text)
server.quit()
for res in meta_query:
query_mysql('UPDATE quarantine SET notified = 1 WHERE id = "%d"' % (res['id']), update = True)
query_mysql('UPDATE quarantine SET notified = 1 WHERE id = %s', (res['id'],), update = True)
r.hset('Q_LAST_NOTIFIED', record['rcpt'], time_now)
break
except Exception as ex:
@@ -138,7 +141,7 @@ try:
print('%s' % (ex))
time.sleep(3)
records = query_mysql('SELECT IFNULL(user_acl.quarantine, 0) AS quarantine_acl, count(id) AS counter, rcpt FROM quarantine LEFT OUTER JOIN user_acl ON user_acl.username = rcpt WHERE notified = 0 AND score < %f AND rcpt in (SELECT username FROM mailbox) GROUP BY rcpt' % (max_score))
records = query_mysql('SELECT IFNULL(user_acl.quarantine, 0) AS quarantine_acl, count(id) AS counter, rcpt FROM quarantine LEFT OUTER JOIN user_acl ON user_acl.username = rcpt WHERE notified = 0 AND score < %s AND rcpt in (SELECT username FROM mailbox) GROUP BY rcpt', (max_score,))
for record in records:
attrs = ''
@@ -156,7 +159,7 @@ try:
except Exception as ex:
print('Could not determine last notification for %s, assuming never' % (record['rcpt']))
last_notification = 0
attrs_json = query_mysql('SELECT attributes FROM mailbox WHERE username = "%s"' % (record['rcpt']))
attrs_json = query_mysql('SELECT attributes FROM mailbox WHERE username = %s', (record['rcpt'],))
attrs = attrs_json[0]['attributes']
if isinstance(attrs, str):
# if attr is str then just load it

View File

@@ -1,6 +1,6 @@
# Whitelist generated by Postwhite v3.4 on Sun Mar 1 00:29:01 UTC 2026
# Whitelist generated by Postwhite v3.4 on Wed Apr 1 00:33:31 UTC 2026
# https://github.com/stevejenkins/postwhite/
# 2174 total rules
# 2246 total rules
2a00:1450:4000::/36 permit
2a01:111:f400::/48 permit
2a01:111:f403:2800::/53 permit
@@ -31,8 +31,10 @@
2a02:a60:0:5::/64 permit
2a0f:f640::/56 permit
2c0f:fb50:4000::/36 permit
2.207.151.32/27 permit
2.207.151.53 permit
2.207.217.30 permit
2.207.223.160/27 permit
3.64.237.68 permit
3.65.3.180 permit
3.70.123.177 permit
@@ -58,8 +60,8 @@
8.40.222.0/23 permit
8.40.222.250/31 permit
12.130.86.238 permit
13.107.213.51 permit
13.107.246.51 permit
13.107.213.38 permit
13.107.246.38 permit
13.108.16.0/20 permit
13.110.208.0/21 permit
13.110.209.0/24 permit
@@ -368,6 +370,7 @@
64.132.88.0/23 permit
64.132.92.0/24 permit
64.181.194.190 permit
64.181.213.254 permit
64.207.219.7 permit
64.207.219.8 permit
64.207.219.9 permit
@@ -453,7 +456,11 @@
66.218.75.252/31 permit
66.218.75.254 permit
66.220.144.128/25 permit
66.220.144.178 permit
66.220.144.179 permit
66.220.155.0/24 permit
66.220.155.178 permit
66.220.155.179 permit
66.220.157.0/25 permit
66.231.80.0/20 permit
66.240.227.0/24 permit
@@ -518,6 +525,8 @@
69.162.98.0/24 permit
69.169.224.0/20 permit
69.171.232.0/24 permit
69.171.232.180 permit
69.171.232.181 permit
69.171.244.0/23 permit
70.42.149.35 permit
72.3.185.0/24 permit
@@ -639,6 +648,7 @@
77.238.189.148/30 permit
79.135.106.0/24 permit
79.135.107.0/24 permit
80.225.160.128/25 permit
81.169.146.243 permit
81.169.146.245 permit
81.169.146.246 permit
@@ -657,6 +667,9 @@
82.165.159.45 permit
82.165.159.130 permit
82.165.159.131 permit
84.8.68.0/25 permit
84.8.192.128/25 permit
84.8.224.128/25 permit
85.9.206.169 permit
85.9.210.45 permit
85.158.136.0/21 permit
@@ -1408,6 +1421,20 @@
129.146.88.28 permit
129.146.147.105 permit
129.146.236.58 permit
129.148.135.0/25 permit
129.148.148.0/25 permit
129.148.164.0/25 permit
129.148.180.0/25 permit
129.148.215.0/25 permit
129.149.6.0/25 permit
129.149.22.0/25 permit
129.149.38.0/25 permit
129.149.52.0/25 permit
129.149.68.0/25 permit
129.149.84.0/25 permit
129.149.100.0/25 permit
129.149.118.0/25 permit
129.149.126.0/25 permit
129.151.67.221 permit
129.153.62.216 permit
129.153.104.71 permit
@@ -1420,16 +1447,19 @@
129.159.22.159 permit
129.159.87.137 permit
129.213.195.191 permit
130.35.116.0/25 permit
130.61.9.72 permit
130.162.39.83 permit
130.248.172.0/24 permit
130.248.173.0/24 permit
131.186.12.0/25 permit
131.253.30.0/24 permit
131.253.121.0/26 permit
132.145.13.209 permit
132.226.26.225 permit
132.226.49.32 permit
132.226.56.24 permit
134.98.248.128/25 permit
134.128.64.0/19 permit
134.128.96.0/19 permit
134.170.27.8 permit
@@ -1460,6 +1490,10 @@
136.147.182.0/24 permit
136.147.224.0/20 permit
136.179.50.206 permit
136.248.224.128/25 permit
136.248.232.128/25 permit
138.1.108.0/25 permit
138.1.170.0/24 permit
139.60.152.0/22 permit
139.138.35.44 permit
139.138.46.121 permit
@@ -1468,6 +1502,7 @@
139.138.57.55 permit
139.138.58.119 permit
139.167.79.86 permit
139.177.108.0/25 permit
139.180.17.0/24 permit
140.238.148.191 permit
141.148.55.217 permit
@@ -1506,6 +1541,9 @@
146.88.28.0/24 permit
146.148.116.76 permit
147.154.32.0/25 permit
147.154.63.0/24 permit
147.154.126.0/24 permit
147.154.191.0/24 permit
147.243.1.47 permit
147.243.1.48 permit
147.243.1.153 permit
@@ -1517,12 +1555,16 @@
149.72.234.184 permit
149.72.248.236 permit
149.97.173.180 permit
149.118.160.128/25 permit
150.136.21.199 permit
150.230.98.160 permit
151.145.38.14 permit
152.67.105.195 permit
152.69.200.236 permit
152.70.155.126 permit
155.248.135.128/25 permit
155.248.140.0/25 permit
155.248.148.0/25 permit
155.248.208.51 permit
155.248.220.138 permit
155.248.234.149 permit
@@ -1531,10 +1573,14 @@
157.58.30.128/25 permit
157.58.196.96/29 permit
157.58.249.3 permit
157.137.32.128/25 permit
157.137.96.128/25 permit
157.151.208.65 permit
157.255.1.64/29 permit
158.101.211.207 permit
158.247.16.0/20 permit
158.247.100.0/25 permit
159.13.4.0/25 permit
159.92.154.0/24 permit
159.92.155.0/24 permit
159.92.157.0/24 permit
@@ -1568,6 +1614,7 @@
161.71.64.0/20 permit
162.88.4.0/23 permit
162.88.8.0/24 permit
162.88.24.0/23 permit
162.88.24.0/24 permit
162.88.25.0/24 permit
162.88.36.0/24 permit
@@ -1585,10 +1632,12 @@
164.152.23.32 permit
164.152.25.241 permit
164.177.132.168/30 permit
165.1.100.0/25 permit
165.173.128.0/24 permit
165.173.180.1 permit
165.173.180.250/31 permit
165.173.182.250/31 permit
165.173.189.205 permit
166.78.68.0/22 permit
166.78.68.221 permit
166.78.69.169 permit
@@ -1607,9 +1656,12 @@
167.89.75.126 permit
167.89.75.136 permit
167.89.75.164 permit
167.89.101.2 permit
167.89.101.192/28 permit
167.220.67.232/29 permit
168.107.248.128/25 permit
168.110.160.128/25 permit
168.110.248.128/25 permit
168.129.184.128/25 permit
168.129.248.128/25 permit
168.138.5.36 permit
168.138.73.51 permit
168.138.77.31 permit
@@ -1745,8 +1797,24 @@
192.18.139.154 permit
192.18.145.36 permit
192.18.152.58 permit
192.22.32.128/25 permit
192.22.96.128/25 permit
192.22.160.128/25 permit
192.22.224.128/25 permit
192.28.128.0/18 permit
192.29.24.0/25 permit
192.29.44.0/25 permit
192.29.72.0/25 permit
192.29.88.0/25 permit
192.29.103.128/25 permit
192.29.134.0/25 permit
192.29.151.128/25 permit
192.29.172.0/25 permit
192.29.178.0/25 permit
192.29.200.0/25 permit
192.29.216.0/25 permit
192.29.232.0/25 permit
192.29.248.0/25 permit
192.30.252.0/22 permit
192.161.144.0/20 permit
192.162.87.0/24 permit
@@ -1754,11 +1822,6 @@
192.237.159.42 permit
192.237.159.43 permit
192.254.112.0/20 permit
192.254.112.60 permit
192.254.112.98/31 permit
192.254.113.10 permit
192.254.113.101 permit
192.254.114.176 permit
193.109.254.0/23 permit
193.122.128.100 permit
193.123.56.63 permit
@@ -1927,6 +1990,7 @@
207.211.30.128/25 permit
207.211.31.0/25 permit
207.211.41.113 permit
207.211.132.0/25 permit
207.218.90.0/24 permit
207.218.90.122 permit
207.250.68.0/24 permit
@@ -1934,6 +1998,8 @@
208.43.21.28/30 permit
208.43.21.64/29 permit
208.43.21.72/30 permit
208.56.9.224 permit
208.56.13.196 permit
208.64.132.0/22 permit
208.71.40.63 permit
208.71.40.64/31 permit
@@ -1960,6 +2026,7 @@
208.71.42.214 permit
208.72.249.240/29 permit
208.75.120.0/22 permit
208.76.62.0/23 permit
208.76.62.0/24 permit
208.76.63.0/24 permit
208.82.237.96/29 permit
@@ -2120,6 +2187,7 @@
216.136.168.80/28 permit
216.139.64.0/19 permit
216.145.221.0/24 permit
216.146.32.0/23 permit
216.146.32.0/24 permit
216.146.33.0/24 permit
216.198.0.0/18 permit
@@ -2140,6 +2208,7 @@
223.165.120.0/23 permit
2001:0868:0100:0600::/64 permit
2001:4860:4000::/36 permit
2001:4860:4864::/56 permit
2001:748:100:40::2:0/112 permit
2001:748:400:1300::3 permit
2001:748:400:1300::4 permit
@@ -2157,10 +2226,12 @@
2001:748:400:3301::3 permit
2001:748:400:3301::4 permit
2404:6800:4000::/36 permit
2404:6800:4864::/56 permit
2607:13c0:0001:0000:0000:0000:0000:7000/116 permit
2607:13c0:0002:0000:0000:0000:0000:1000/116 permit
2607:13c0:0004:0000:0000:0000:0000:0000/116 permit
2607:f8b0:4000::/36 permit
2607:f8b0:4864::/56 permit
2620:109:c003:104::/64 permit
2620:109:c003:104::215 permit
2620:109:c006:104::/64 permit
@@ -2172,8 +2243,9 @@
2620:10d:c09c:400::8:1 permit
2620:119:50c0:207::/64 permit
2620:119:50c0:207::215 permit
2620:1ec:46::51 permit
2620:1ec:bdf::51 permit
2620:1ec:46::38 permit
2620:1ec:bdf::38 permit
2800:3f0:4000::/36 permit
2800:3f0:4864::/56 permit
49.12.4.251 permit # checks.mailcow.email
2a01:4f8:c17:7906::10 permit # checks.mailcow.email

View File

@@ -108,6 +108,14 @@ function fwdhost($_action, $_data = null) {
}
break;
case 'delete':
if ($_SESSION['mailcow_cc_role'] != "admin") {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => 'access_denied'
);
return false;
}
$hosts = (array)$_data['forwardinghost'];
foreach ($hosts as $host) {
try {

View File

@@ -1111,10 +1111,21 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$relayhost = (isset($_data['relayhost'])) ? intval($_data['relayhost']) : 0;
$quarantine_notification = (isset($_data['quarantine_notification'])) ? strval($_data['quarantine_notification']) : strval($MAILBOX_DEFAULT_ATTRIBUTES['quarantine_notification']);
$quarantine_category = (isset($_data['quarantine_category'])) ? strval($_data['quarantine_category']) : strval($MAILBOX_DEFAULT_ATTRIBUTES['quarantine_category']);
// Validate quarantine_category
if (!in_array($quarantine_category, array('add_header', 'reject', 'all'))) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'quarantine_category_invalid'
);
return false;
}
$quota_b = ($quota_m * 1048576);
$attribute_hash = (!empty($_data['attribute_hash'])) ? $_data['attribute_hash'] : '';
if (in_array($authsource, array('keycloak', 'generic-oidc', 'ldap'))){
$force_pw_update = 0;
}
if ($authsource == 'generic-oidc'){
$force_tfa = 0;
}
$mailbox_attrs = json_encode(
@@ -1731,6 +1742,15 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$attr["tagged_mail_handler"] = (!empty($_data['tagged_mail_handler'])) ? $_data['tagged_mail_handler'] : strval($MAILBOX_DEFAULT_ATTRIBUTES['tagged_mail_handler']);
$attr["quarantine_notification"] = (!empty($_data['quarantine_notification'])) ? $_data['quarantine_notification'] : strval($MAILBOX_DEFAULT_ATTRIBUTES['quarantine_notification']);
$attr["quarantine_category"] = (!empty($_data['quarantine_category'])) ? $_data['quarantine_category'] : strval($MAILBOX_DEFAULT_ATTRIBUTES['quarantine_category']);
// Validate quarantine_category
if (!in_array($attr["quarantine_category"], array('add_header', 'reject', 'all'))) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_extra),
'msg' => 'quarantine_category_invalid'
);
return false;
}
$attr["rl_frame"] = (!empty($_data['rl_frame'])) ? $_data['rl_frame'] : "s";
$attr["rl_value"] = (!empty($_data['rl_value'])) ? $_data['rl_value'] : "";
$attr["force_pw_update"] = isset($_data['force_pw_update']) ? intval($_data['force_pw_update']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['force_pw_update']);
@@ -2060,6 +2080,14 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
return false;
}
foreach ($usernames as $username) {
if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
continue;
}
if ($_data['spam_score'] == "default") {
$stmt = $pdo->prepare("DELETE FROM `filterconf` WHERE `object` = :username
AND (`option` = 'lowspamlevel' OR `option` = 'highspamlevel')");
@@ -3126,6 +3154,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
}
if (in_array($authsource, array('keycloak', 'generic-oidc', 'ldap'))){
$force_pw_update = 0;
}
if ($authsource == 'generic-oidc'){
$force_tfa = 0;
}
$pw_recovery_email = (isset($_data['pw_recovery_email']) && $authsource == 'mailcow') ? $_data['pw_recovery_email'] : $is_now['attributes']['recovery_email'];
@@ -3786,6 +3816,15 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$attr["tagged_mail_handler"] = (!empty($_data['tagged_mail_handler'])) ? $_data['tagged_mail_handler'] : $is_now['tagged_mail_handler'];
$attr["quarantine_notification"] = (!empty($_data['quarantine_notification'])) ? $_data['quarantine_notification'] : $is_now['quarantine_notification'];
$attr["quarantine_category"] = (!empty($_data['quarantine_category'])) ? $_data['quarantine_category'] : $is_now['quarantine_category'];
// Validate quarantine_category
if (!in_array($attr["quarantine_category"], array('add_header', 'reject', 'all'))) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_extra),
'msg' => 'quarantine_category_invalid'
);
continue;
}
$attr["rl_frame"] = (!empty($_data['rl_frame'])) ? $_data['rl_frame'] : $is_now['rl_frame'];
$attr["rl_value"] = (!empty($_data['rl_value'])) ? $_data['rl_value'] : $is_now['rl_value'];
$attr["force_pw_update"] = isset($_data['force_pw_update']) ? intval($_data['force_pw_update']) : $is_now['force_pw_update'];

View File

@@ -89,7 +89,7 @@ $globalVariables = [
'app_links' => $app_links,
'app_links_processed' => $app_links_processed,
'is_root_uri' => (parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) == '/'),
'uri' => $_SERVER['REQUEST_URI'],
'uri' => parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) ?: '/',
];
foreach ($globalVariables as $globalVariableName => $globalVariableValue) {

View File

@@ -13,7 +13,9 @@ $twig = new Environment($loader, [
// functions
$twig->addFunction(new TwigFunction('query_string', function (array $params = []) {
return http_build_query(array_merge($_GET, $params));
$allowed = ['lang', 'mobileconfig'];
$filtered = array_intersect_key($_GET, array_flip($allowed));
return http_build_query(array_merge($filtered, $params));
}));
$twig->addFunction(new TwigFunction('is_uri', function (string $uri, string $where = null) {

View File

@@ -1128,6 +1128,11 @@ jQuery(function($){
item.ua = escapeHtml(item.ua);
}
item.ua = '<span style="font-size:small">' + item.ua + '</span>';
if (item.user == null) {
item.user = 'unknown';
} else {
item.user = escapeHtml(item.user);
}
if (item.service == "activesync") {
item.service = '<span class="badge fs-6 bg-info">ActiveSync</span>';
}

View File

@@ -226,18 +226,18 @@ jQuery(function($){
}
if (typeof data.fuzzy_hashes === 'object' && data.fuzzy_hashes !== null && data.fuzzy_hashes.length !== 0) {
$.each(data.fuzzy_hashes, function (index, value) {
$('#qid_detail_fuzzy').append('<p style="font-family:monospace">' + value + '</p>');
$('#qid_detail_fuzzy').append('<p style="font-family:monospace">' + escapeHtml(value) + '</p>');
});
} else {
$('#qid_detail_fuzzy').append('-');
}
if (typeof data.score !== 'undefined' && typeof data.action !== 'undefined') {
if (data.action == "add header") {
$('#qid_detail_score').append('<span class="label-rspamd-action badge fs-6 bg-warning"><b>' + data.score + '</b> - ' + lang.junk_folder + '</span>');
$('#qid_detail_score').append('<span class="label-rspamd-action badge fs-6 bg-warning"><b>' + escapeHtml(data.score) + '</b> - ' + lang.junk_folder + '</span>');
} else if (data.action == "reject") {
$('#qid_detail_score').append('<span class="label-rspamd-action badge fs-6 bg-danger"><b>' + data.score + '</b> - ' + lang.rejected + '</span>');
$('#qid_detail_score').append('<span class="label-rspamd-action badge fs-6 bg-danger"><b>' + escapeHtml(data.score) + '</b> - ' + lang.rejected + '</span>');
} else if (data.action == "rewrite subject") {
$('#qid_detail_score').append('<span class="label-rspamd-action badge fs-6 bg-warning"><b>' + data.score + '</b> - ' + lang.rewrite_subject + '</span>');
$('#qid_detail_score').append('<span class="label-rspamd-action badge fs-6 bg-warning"><b>' + escapeHtml(data.score) + '</b> - ' + lang.rewrite_subject + '</span>');
}
}
if (typeof data.recipients !== 'undefined') {
@@ -254,8 +254,8 @@ jQuery(function($){
qAtts.text('');
$.each(data.attachments, function(index, value) {
qAtts.append(
'<p><a href="/inc/ajax/qitem_details.php?id=' + qitem + '&att=' + index + '" target="_blank">' + value[0] + '</a> (' + value[1] + ')' +
' - <small><a href="' + value[3] + '" target="_blank">' + lang.check_hash + '</a></small></p>'
'<p><a href="/inc/ajax/qitem_details.php?id=' + escapeHtml(qitem) + '&amp;att=' + index + '" target="_blank">' + escapeHtml(value[0]) + '</a> (' + escapeHtml(value[1]) + ')' +
' - <small><a href="' + escapeHtml(value[3]) + '" target="_blank">' + lang.check_hash + '</a></small></p>'
);
});
}

View File

@@ -98,8 +98,8 @@ jQuery(function($){
var local_datetime = datetime.toLocaleDateString(undefined, {year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit"});
var service = '<div class="badge bg-secondary">' + item.service.toUpperCase() + '</div>';
var app_password = item.app_password ? ' <a href="/edit/app-passwd/' + item.app_password + '"><i class="bi bi-key-fill"></i><span class="ms-1">' + escapeHtml(item.app_password_name || "App") + '</span></a>' : '';
var real_rip = item.real_rip.startsWith("Web") ? item.real_rip : '<a href="https://bgp.tools/prefix/' + item.real_rip + '" target="_blank">' + item.real_rip + "</a>";
var ip_location = item.location ? ' <span class="flag-icon flag-icon-' + item.location.toLowerCase() + '"></span>' : '';
var real_rip = item.real_rip.startsWith("Web") ? escapeHtml(item.real_rip) : '<a href="https://bgp.tools/prefix/' + escapeHtml(item.real_rip) + '" target="_blank">' + escapeHtml(item.real_rip) + "</a>";
var ip_location = item.location ? ' <span class="flag-icon flag-icon-' + escapeHtml(item.location.toLowerCase()) + '"></span>' : '';
var ip_data = real_rip + ip_location + app_password;
$(".last-sasl-login").append(`

View File

@@ -512,6 +512,7 @@
"pushover_credentials_missing": "Pushover Token und/oder Key fehlen",
"pushover_key": "Pushover Key hat das falsche Format",
"pushover_token": "Pushover Token hat das falsche Format",
"quarantine_category_invalid": "Quarantäne-Kategorie muss eine der folgenden sein: add_header, reject, all",
"quota_not_0_not_numeric": "Speicherplatz muss numerisch und >= 0 sein",
"recipient_map_entry_exists": "Eine Empfängerumschreibung für Objekt \"%s\" existiert bereits",
"recovery_email_failed": "E-Mail zur Wiederherstellung konnte nicht gesendet werden. Bitte wenden Sie sich an Ihren Administrator.",

View File

@@ -513,6 +513,7 @@
"pushover_credentials_missing": "Pushover token and or key missing",
"pushover_key": "Pushover key has a wrong format",
"pushover_token": "Pushover token has a wrong format",
"quarantine_category_invalid": "Quarantine category must be one of: add_header, reject, all",
"quota_not_0_not_numeric": "Quota must be numeric and >= 0",
"recipient_map_entry_exists": "A Recipient map entry \"%s\" exists",
"recovery_email_failed": "Could not send a recovery email. Please contact your administrator.",

View File

@@ -193,7 +193,7 @@ $(window).scroll(function() {
});
// Select language and reopen active URL without POST
function setLang(sel) {
$.post( '{{ uri }}', {lang: sel} );
$.post( '{{ uri|escape("js") }}', {lang: sel} );
window.location.href = window.location.pathname + window.location.search;
}
// FIDO2 functions

View File

@@ -252,7 +252,7 @@ services:
- sogo
dovecot-mailcow:
image: ghcr.io/mailcow/dovecot:2.3.21.1-1
image: ghcr.io/mailcow/dovecot:2.3.21.1-2
depends_on:
- mysql-mailcow
- netfilter-mailcow