1
0
mirror of https://github.com/mailcow/mailcow-dockerized.git synced 2026-06-17 20:10:32 +00:00

Compare commits

..

1 Commits

Author SHA1 Message Date
FreddleSpl0it cb70023241 [Web] Add User ACL to manage SOGo access 2026-03-12 13:06:16 +01:00
38 changed files with 206 additions and 368 deletions
+7 -38
View File
@@ -253,20 +253,10 @@ while true; do
unset VALIDATED_CONFIG_DOMAINS_SUBDOMAINS unset VALIDATED_CONFIG_DOMAINS_SUBDOMAINS
declare -a VALIDATED_CONFIG_DOMAINS_SUBDOMAINS declare -a VALIDATED_CONFIG_DOMAINS_SUBDOMAINS
for SUBDOMAIN in "${ADDITIONAL_WC_ARR[@]}"; do for SUBDOMAIN in "${ADDITIONAL_WC_ARR[@]}"; do
FULL_SUBDOMAIN="${SUBDOMAIN}.${SQL_DOMAIN}" if [[ "${SUBDOMAIN}.${SQL_DOMAIN}" != "${MAILCOW_HOSTNAME}" ]]; then
if check_domain "${SUBDOMAIN}.${SQL_DOMAIN}"; then
# Skip if subdomain matches MAILCOW_HOSTNAME VALIDATED_CONFIG_DOMAINS_SUBDOMAINS+=("${SUBDOMAIN}.${SQL_DOMAIN}")
if [[ "${FULL_SUBDOMAIN}" == "${MAILCOW_HOSTNAME}" ]]; then fi
continue
fi
# Skip if subdomain is covered by a wildcard in ADDITIONAL_SAN
if is_covered_by_wildcard "${FULL_SUBDOMAIN}"; then
log_f "Subdomain '${FULL_SUBDOMAIN}' is covered by wildcard - skipping explicit subdomain"
continue
fi
# Validate and add subdomain
if check_domain "${FULL_SUBDOMAIN}"; then
VALIDATED_CONFIG_DOMAINS_SUBDOMAINS+=("${FULL_SUBDOMAIN}")
fi fi
done done
VALIDATED_CONFIG_DOMAINS+=("${VALIDATED_CONFIG_DOMAINS_SUBDOMAINS[*]}") VALIDATED_CONFIG_DOMAINS+=("${VALIDATED_CONFIG_DOMAINS_SUBDOMAINS[*]}")
@@ -283,10 +273,7 @@ while true; do
fi fi
# Only add mta-sts subdomain for alias domains # Only add mta-sts subdomain for alias domains
if [[ "mta-sts.${alias_domain}" != "${MAILCOW_HOSTNAME}" ]]; then if [[ "mta-sts.${alias_domain}" != "${MAILCOW_HOSTNAME}" ]]; then
# Skip if mta-sts subdomain is covered by a wildcard if check_domain "mta-sts.${alias_domain}"; then
if is_covered_by_wildcard "mta-sts.${alias_domain}"; then
log_f "Alias domain mta-sts subdomain 'mta-sts.${alias_domain}' is covered by wildcard - skipping"
elif check_domain "mta-sts.${alias_domain}"; then
VALIDATED_CONFIG_DOMAINS+=("mta-sts.${alias_domain}") VALIDATED_CONFIG_DOMAINS+=("mta-sts.${alias_domain}")
fi fi
fi fi
@@ -321,31 +308,13 @@ while true; do
done done
fi fi
# Check if MAILCOW_HOSTNAME is covered by a wildcard in ADDITIONAL_SAN
MAILCOW_HOSTNAME_COVERED=0
if [[ ! -z ${VALIDATED_MAILCOW_HOSTNAME} ]]; then
if is_covered_by_wildcard "${VALIDATED_MAILCOW_HOSTNAME}"; then
MAILCOW_PARENT_DOMAIN=$(echo ${VALIDATED_MAILCOW_HOSTNAME} | cut -d. -f2-)
log_f "MAILCOW_HOSTNAME '${VALIDATED_MAILCOW_HOSTNAME}' is covered by wildcard '*.${MAILCOW_PARENT_DOMAIN}' - skipping explicit hostname"
MAILCOW_HOSTNAME_COVERED=1
fi
fi
# Unique domains for server certificate # Unique domains for server certificate
if [[ ${ENABLE_SSL_SNI} == "y" ]]; then if [[ ${ENABLE_SSL_SNI} == "y" ]]; then
# create certificate for server name and fqdn SANs only # create certificate for server name and fqdn SANs only
if [[ ${MAILCOW_HOSTNAME_COVERED} == "1" ]]; then SERVER_SAN_VALIDATED=(${VALIDATED_MAILCOW_HOSTNAME} $(echo ${ADDITIONAL_VALIDATED_SAN[*]} | xargs -n1 | sort -u | xargs))
SERVER_SAN_VALIDATED=($(echo ${ADDITIONAL_VALIDATED_SAN[*]} | xargs -n1 | sort -u | xargs))
else
SERVER_SAN_VALIDATED=(${VALIDATED_MAILCOW_HOSTNAME} $(echo ${ADDITIONAL_VALIDATED_SAN[*]} | xargs -n1 | sort -u | xargs))
fi
else else
# create certificate for all domains, including all subdomains from other domains [*] # create certificate for all domains, including all subdomains from other domains [*]
if [[ ${MAILCOW_HOSTNAME_COVERED} == "1" ]]; then SERVER_SAN_VALIDATED=(${VALIDATED_MAILCOW_HOSTNAME} $(echo ${VALIDATED_CONFIG_DOMAINS[*]} ${ADDITIONAL_VALIDATED_SAN[*]} | xargs -n1 | sort -u | xargs))
SERVER_SAN_VALIDATED=($(echo ${VALIDATED_CONFIG_DOMAINS[*]} ${ADDITIONAL_VALIDATED_SAN[*]} | xargs -n1 | sort -u | xargs))
else
SERVER_SAN_VALIDATED=(${VALIDATED_MAILCOW_HOSTNAME} $(echo ${VALIDATED_CONFIG_DOMAINS[*]} ${ADDITIONAL_VALIDATED_SAN[*]} | xargs -n1 | sort -u | xargs))
fi
fi fi
if [[ ! -z ${SERVER_SAN_VALIDATED[*]} ]]; then if [[ ! -z ${SERVER_SAN_VALIDATED[*]} ]]; then
CERT_NAME=${SERVER_SAN_VALIDATED[0]} CERT_NAME=${SERVER_SAN_VALIDATED[0]}
-29
View File
@@ -135,32 +135,3 @@ verify_challenge_path(){
return 1 return 1
fi fi
} }
# Check if a domain is covered by a wildcard (*.example.com) in ADDITIONAL_SAN
# Usage: is_covered_by_wildcard "subdomain.example.com"
# Returns: 0 if covered, 1 if not covered
# Note: Only returns 0 (covered) when DNS-01 challenge is enabled,
# as wildcards cannot be validated with HTTP-01 challenge
is_covered_by_wildcard() {
local DOMAIN=$1
# Only skip if DNS challenge is enabled (wildcards require DNS-01)
if [[ ${ACME_DNS_CHALLENGE} != "y" ]]; then
return 1
fi
# Return early if no ADDITIONAL_SAN is set
if [[ -z ${ADDITIONAL_SAN} ]]; then
return 1
fi
# Extract parent domain (e.g., mail.example.com -> example.com)
local PARENT_DOMAIN=$(echo ${DOMAIN} | cut -d. -f2-)
# Check if ADDITIONAL_SAN contains a wildcard for this parent domain
if [[ "${ADDITIONAL_SAN}" == *"*.${PARENT_DOMAIN}"* ]]; then
return 0 # Covered by wildcard
fi
return 1 # Not covered
}
@@ -47,7 +47,7 @@ try:
if max_score == "": if max_score == "":
max_score = 9999.0 max_score = 9999.0
def query_mysql(query, params = None, headers = True, update = False): def query_mysql(query, headers = True, update = False):
while True: while True:
try: 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") 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,10 +57,7 @@ try:
else: else:
break break
cur = cnx.cursor() cur = cnx.cursor()
if params: cur.execute(query)
cur.execute(query, params)
else:
cur.execute(query)
if not update: if not update:
result = [] result = []
columns = tuple( [d[0] for d in cur.description] ) columns = tuple( [d[0] for d in cur.description] )
@@ -79,7 +76,7 @@ try:
def notify_rcpt(rcpt, msg_count, quarantine_acl, category): def notify_rcpt(rcpt, msg_count, quarantine_acl, category):
if category == "add_header": category = "add header" 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 < %s 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 < %f 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)) print("%s: %d of %d messages qualify for notification" % (rcpt, len(meta_query), msg_count))
if len(meta_query) == 0: if len(meta_query) == 0:
return return
@@ -133,7 +130,7 @@ try:
server.sendmail(msg['From'], [str(redirect)] + [str(bcc)], text) server.sendmail(msg['From'], [str(redirect)] + [str(bcc)], text)
server.quit() server.quit()
for res in meta_query: for res in meta_query:
query_mysql('UPDATE quarantine SET notified = 1 WHERE id = %s', (res['id'],), update = True) query_mysql('UPDATE quarantine SET notified = 1 WHERE id = "%d"' % (res['id']), update = True)
r.hset('Q_LAST_NOTIFIED', record['rcpt'], time_now) r.hset('Q_LAST_NOTIFIED', record['rcpt'], time_now)
break break
except Exception as ex: except Exception as ex:
@@ -141,7 +138,7 @@ try:
print('%s' % (ex)) print('%s' % (ex))
time.sleep(3) 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 < %s 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 < %f AND rcpt in (SELECT username FROM mailbox) GROUP BY rcpt' % (max_score))
for record in records: for record in records:
attrs = '' attrs = ''
@@ -159,7 +156,7 @@ try:
except Exception as ex: except Exception as ex:
print('Could not determine last notification for %s, assuming never' % (record['rcpt'])) print('Could not determine last notification for %s, assuming never' % (record['rcpt']))
last_notification = 0 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'] attrs = attrs_json[0]['attributes']
if isinstance(attrs, str): if isinstance(attrs, str):
# if attr is str then just load it # if attr is str then just load it
+1 -1
View File
@@ -66,7 +66,7 @@ $_SESSION['acl']['tls_policy'] = "1";
$_SESSION['acl']['quarantine_notification'] = "1"; $_SESSION['acl']['quarantine_notification'] = "1";
$_SESSION['acl']['quarantine_category'] = "1"; $_SESSION['acl']['quarantine_category'] = "1";
$_SESSION['acl']['ratelimit'] = "1"; $_SESSION['acl']['ratelimit'] = "1";
$_SESSION['acl']['sogo_access'] = "1"; $_SESSION['acl']['sogo_redirection'] = "1";
$_SESSION['acl']['protocol_access'] = "1"; $_SESSION['acl']['protocol_access'] = "1";
$_SESSION['acl']['mailbox_relayhost'] = "1"; $_SESSION['acl']['mailbox_relayhost'] = "1";
$_SESSION['acl']['unlimited_quota'] = "1"; $_SESSION['acl']['unlimited_quota'] = "1";
+1 -1
View File
@@ -66,7 +66,7 @@ $_SESSION['acl']['tls_policy'] = "1";
$_SESSION['acl']['quarantine_notification'] = "1"; $_SESSION['acl']['quarantine_notification'] = "1";
$_SESSION['acl']['quarantine_category'] = "1"; $_SESSION['acl']['quarantine_category'] = "1";
$_SESSION['acl']['ratelimit'] = "1"; $_SESSION['acl']['ratelimit'] = "1";
$_SESSION['acl']['sogo_access'] = "1"; $_SESSION['acl']['sogo_redirection'] = "1";
$_SESSION['acl']['protocol_access'] = "1"; $_SESSION['acl']['protocol_access'] = "1";
$_SESSION['acl']['mailbox_relayhost'] = "1"; $_SESSION['acl']['mailbox_relayhost'] = "1";
$_SESSION['acl']['unlimited_quota'] = "1"; $_SESSION['acl']['unlimited_quota'] = "1";
+13 -85
View File
@@ -1,6 +1,6 @@
# Whitelist generated by Postwhite v3.4 on Wed Apr 1 00:33:31 UTC 2026 # Whitelist generated by Postwhite v3.4 on Sun Mar 1 00:29:01 UTC 2026
# https://github.com/stevejenkins/postwhite/ # https://github.com/stevejenkins/postwhite/
# 2246 total rules # 2174 total rules
2a00:1450:4000::/36 permit 2a00:1450:4000::/36 permit
2a01:111:f400::/48 permit 2a01:111:f400::/48 permit
2a01:111:f403:2800::/53 permit 2a01:111:f403:2800::/53 permit
@@ -31,10 +31,8 @@
2a02:a60:0:5::/64 permit 2a02:a60:0:5::/64 permit
2a0f:f640::/56 permit 2a0f:f640::/56 permit
2c0f:fb50:4000::/36 permit 2c0f:fb50:4000::/36 permit
2.207.151.32/27 permit
2.207.151.53 permit 2.207.151.53 permit
2.207.217.30 permit 2.207.217.30 permit
2.207.223.160/27 permit
3.64.237.68 permit 3.64.237.68 permit
3.65.3.180 permit 3.65.3.180 permit
3.70.123.177 permit 3.70.123.177 permit
@@ -60,8 +58,8 @@
8.40.222.0/23 permit 8.40.222.0/23 permit
8.40.222.250/31 permit 8.40.222.250/31 permit
12.130.86.238 permit 12.130.86.238 permit
13.107.213.38 permit 13.107.213.51 permit
13.107.246.38 permit 13.107.246.51 permit
13.108.16.0/20 permit 13.108.16.0/20 permit
13.110.208.0/21 permit 13.110.208.0/21 permit
13.110.209.0/24 permit 13.110.209.0/24 permit
@@ -370,7 +368,6 @@
64.132.88.0/23 permit 64.132.88.0/23 permit
64.132.92.0/24 permit 64.132.92.0/24 permit
64.181.194.190 permit 64.181.194.190 permit
64.181.213.254 permit
64.207.219.7 permit 64.207.219.7 permit
64.207.219.8 permit 64.207.219.8 permit
64.207.219.9 permit 64.207.219.9 permit
@@ -456,11 +453,7 @@
66.218.75.252/31 permit 66.218.75.252/31 permit
66.218.75.254 permit 66.218.75.254 permit
66.220.144.128/25 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.0/24 permit
66.220.155.178 permit
66.220.155.179 permit
66.220.157.0/25 permit 66.220.157.0/25 permit
66.231.80.0/20 permit 66.231.80.0/20 permit
66.240.227.0/24 permit 66.240.227.0/24 permit
@@ -525,8 +518,6 @@
69.162.98.0/24 permit 69.162.98.0/24 permit
69.169.224.0/20 permit 69.169.224.0/20 permit
69.171.232.0/24 permit 69.171.232.0/24 permit
69.171.232.180 permit
69.171.232.181 permit
69.171.244.0/23 permit 69.171.244.0/23 permit
70.42.149.35 permit 70.42.149.35 permit
72.3.185.0/24 permit 72.3.185.0/24 permit
@@ -648,7 +639,6 @@
77.238.189.148/30 permit 77.238.189.148/30 permit
79.135.106.0/24 permit 79.135.106.0/24 permit
79.135.107.0/24 permit 79.135.107.0/24 permit
80.225.160.128/25 permit
81.169.146.243 permit 81.169.146.243 permit
81.169.146.245 permit 81.169.146.245 permit
81.169.146.246 permit 81.169.146.246 permit
@@ -667,9 +657,6 @@
82.165.159.45 permit 82.165.159.45 permit
82.165.159.130 permit 82.165.159.130 permit
82.165.159.131 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.206.169 permit
85.9.210.45 permit 85.9.210.45 permit
85.158.136.0/21 permit 85.158.136.0/21 permit
@@ -1421,20 +1408,6 @@
129.146.88.28 permit 129.146.88.28 permit
129.146.147.105 permit 129.146.147.105 permit
129.146.236.58 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.151.67.221 permit
129.153.62.216 permit 129.153.62.216 permit
129.153.104.71 permit 129.153.104.71 permit
@@ -1447,19 +1420,16 @@
129.159.22.159 permit 129.159.22.159 permit
129.159.87.137 permit 129.159.87.137 permit
129.213.195.191 permit 129.213.195.191 permit
130.35.116.0/25 permit
130.61.9.72 permit 130.61.9.72 permit
130.162.39.83 permit 130.162.39.83 permit
130.248.172.0/24 permit 130.248.172.0/24 permit
130.248.173.0/24 permit 130.248.173.0/24 permit
131.186.12.0/25 permit
131.253.30.0/24 permit 131.253.30.0/24 permit
131.253.121.0/26 permit 131.253.121.0/26 permit
132.145.13.209 permit 132.145.13.209 permit
132.226.26.225 permit 132.226.26.225 permit
132.226.49.32 permit 132.226.49.32 permit
132.226.56.24 permit 132.226.56.24 permit
134.98.248.128/25 permit
134.128.64.0/19 permit 134.128.64.0/19 permit
134.128.96.0/19 permit 134.128.96.0/19 permit
134.170.27.8 permit 134.170.27.8 permit
@@ -1490,10 +1460,6 @@
136.147.182.0/24 permit 136.147.182.0/24 permit
136.147.224.0/20 permit 136.147.224.0/20 permit
136.179.50.206 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.60.152.0/22 permit
139.138.35.44 permit 139.138.35.44 permit
139.138.46.121 permit 139.138.46.121 permit
@@ -1502,7 +1468,6 @@
139.138.57.55 permit 139.138.57.55 permit
139.138.58.119 permit 139.138.58.119 permit
139.167.79.86 permit 139.167.79.86 permit
139.177.108.0/25 permit
139.180.17.0/24 permit 139.180.17.0/24 permit
140.238.148.191 permit 140.238.148.191 permit
141.148.55.217 permit 141.148.55.217 permit
@@ -1541,9 +1506,6 @@
146.88.28.0/24 permit 146.88.28.0/24 permit
146.148.116.76 permit 146.148.116.76 permit
147.154.32.0/25 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.47 permit
147.243.1.48 permit 147.243.1.48 permit
147.243.1.153 permit 147.243.1.153 permit
@@ -1555,16 +1517,12 @@
149.72.234.184 permit 149.72.234.184 permit
149.72.248.236 permit 149.72.248.236 permit
149.97.173.180 permit 149.97.173.180 permit
149.118.160.128/25 permit
150.136.21.199 permit 150.136.21.199 permit
150.230.98.160 permit 150.230.98.160 permit
151.145.38.14 permit 151.145.38.14 permit
152.67.105.195 permit 152.67.105.195 permit
152.69.200.236 permit 152.69.200.236 permit
152.70.155.126 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.208.51 permit
155.248.220.138 permit 155.248.220.138 permit
155.248.234.149 permit 155.248.234.149 permit
@@ -1573,14 +1531,10 @@
157.58.30.128/25 permit 157.58.30.128/25 permit
157.58.196.96/29 permit 157.58.196.96/29 permit
157.58.249.3 permit 157.58.249.3 permit
157.137.32.128/25 permit
157.137.96.128/25 permit
157.151.208.65 permit 157.151.208.65 permit
157.255.1.64/29 permit 157.255.1.64/29 permit
158.101.211.207 permit 158.101.211.207 permit
158.247.16.0/20 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.154.0/24 permit
159.92.155.0/24 permit 159.92.155.0/24 permit
159.92.157.0/24 permit 159.92.157.0/24 permit
@@ -1614,7 +1568,6 @@
161.71.64.0/20 permit 161.71.64.0/20 permit
162.88.4.0/23 permit 162.88.4.0/23 permit
162.88.8.0/24 permit 162.88.8.0/24 permit
162.88.24.0/23 permit
162.88.24.0/24 permit 162.88.24.0/24 permit
162.88.25.0/24 permit 162.88.25.0/24 permit
162.88.36.0/24 permit 162.88.36.0/24 permit
@@ -1632,12 +1585,10 @@
164.152.23.32 permit 164.152.23.32 permit
164.152.25.241 permit 164.152.25.241 permit
164.177.132.168/30 permit 164.177.132.168/30 permit
165.1.100.0/25 permit
165.173.128.0/24 permit 165.173.128.0/24 permit
165.173.180.1 permit 165.173.180.1 permit
165.173.180.250/31 permit 165.173.180.250/31 permit
165.173.182.250/31 permit 165.173.182.250/31 permit
165.173.189.205 permit
166.78.68.0/22 permit 166.78.68.0/22 permit
166.78.68.221 permit 166.78.68.221 permit
166.78.69.169 permit 166.78.69.169 permit
@@ -1656,12 +1607,9 @@
167.89.75.126 permit 167.89.75.126 permit
167.89.75.136 permit 167.89.75.136 permit
167.89.75.164 permit 167.89.75.164 permit
167.89.101.2 permit
167.89.101.192/28 permit
167.220.67.232/29 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.5.36 permit
168.138.73.51 permit 168.138.73.51 permit
168.138.77.31 permit 168.138.77.31 permit
@@ -1797,24 +1745,8 @@
192.18.139.154 permit 192.18.139.154 permit
192.18.145.36 permit 192.18.145.36 permit
192.18.152.58 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.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.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.30.252.0/22 permit
192.161.144.0/20 permit 192.161.144.0/20 permit
192.162.87.0/24 permit 192.162.87.0/24 permit
@@ -1822,6 +1754,11 @@
192.237.159.42 permit 192.237.159.42 permit
192.237.159.43 permit 192.237.159.43 permit
192.254.112.0/20 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.109.254.0/23 permit
193.122.128.100 permit 193.122.128.100 permit
193.123.56.63 permit 193.123.56.63 permit
@@ -1990,7 +1927,6 @@
207.211.30.128/25 permit 207.211.30.128/25 permit
207.211.31.0/25 permit 207.211.31.0/25 permit
207.211.41.113 permit 207.211.41.113 permit
207.211.132.0/25 permit
207.218.90.0/24 permit 207.218.90.0/24 permit
207.218.90.122 permit 207.218.90.122 permit
207.250.68.0/24 permit 207.250.68.0/24 permit
@@ -1998,8 +1934,6 @@
208.43.21.28/30 permit 208.43.21.28/30 permit
208.43.21.64/29 permit 208.43.21.64/29 permit
208.43.21.72/30 permit 208.43.21.72/30 permit
208.56.9.224 permit
208.56.13.196 permit
208.64.132.0/22 permit 208.64.132.0/22 permit
208.71.40.63 permit 208.71.40.63 permit
208.71.40.64/31 permit 208.71.40.64/31 permit
@@ -2026,7 +1960,6 @@
208.71.42.214 permit 208.71.42.214 permit
208.72.249.240/29 permit 208.72.249.240/29 permit
208.75.120.0/22 permit 208.75.120.0/22 permit
208.76.62.0/23 permit
208.76.62.0/24 permit 208.76.62.0/24 permit
208.76.63.0/24 permit 208.76.63.0/24 permit
208.82.237.96/29 permit 208.82.237.96/29 permit
@@ -2187,7 +2120,6 @@
216.136.168.80/28 permit 216.136.168.80/28 permit
216.139.64.0/19 permit 216.139.64.0/19 permit
216.145.221.0/24 permit 216.145.221.0/24 permit
216.146.32.0/23 permit
216.146.32.0/24 permit 216.146.32.0/24 permit
216.146.33.0/24 permit 216.146.33.0/24 permit
216.198.0.0/18 permit 216.198.0.0/18 permit
@@ -2208,7 +2140,6 @@
223.165.120.0/23 permit 223.165.120.0/23 permit
2001:0868:0100:0600::/64 permit 2001:0868:0100:0600::/64 permit
2001:4860:4000::/36 permit 2001:4860:4000::/36 permit
2001:4860:4864::/56 permit
2001:748:100:40::2:0/112 permit 2001:748:100:40::2:0/112 permit
2001:748:400:1300::3 permit 2001:748:400:1300::3 permit
2001:748:400:1300::4 permit 2001:748:400:1300::4 permit
@@ -2226,12 +2157,10 @@
2001:748:400:3301::3 permit 2001:748:400:3301::3 permit
2001:748:400:3301::4 permit 2001:748:400:3301::4 permit
2404:6800:4000::/36 permit 2404:6800:4000::/36 permit
2404:6800:4864::/56 permit
2607:13c0:0001:0000:0000:0000:0000:7000/116 permit 2607:13c0:0001:0000:0000:0000:0000:7000/116 permit
2607:13c0:0002:0000:0000:0000:0000:1000/116 permit 2607:13c0:0002:0000:0000:0000:0000:1000/116 permit
2607:13c0:0004:0000:0000:0000:0000:0000/116 permit 2607:13c0:0004:0000:0000:0000:0000:0000/116 permit
2607:f8b0:4000::/36 permit 2607:f8b0:4000::/36 permit
2607:f8b0:4864::/56 permit
2620:109:c003:104::/64 permit 2620:109:c003:104::/64 permit
2620:109:c003:104::215 permit 2620:109:c003:104::215 permit
2620:109:c006:104::/64 permit 2620:109:c006:104::/64 permit
@@ -2243,9 +2172,8 @@
2620:10d:c09c:400::8:1 permit 2620:10d:c09c:400::8:1 permit
2620:119:50c0:207::/64 permit 2620:119:50c0:207::/64 permit
2620:119:50c0:207::215 permit 2620:119:50c0:207::215 permit
2620:1ec:46::38 permit 2620:1ec:46::51 permit
2620:1ec:bdf::38 permit 2620:1ec:bdf::51 permit
2800:3f0:4000::/36 permit 2800:3f0:4000::/36 permit
2800:3f0:4864::/56 permit
49.12.4.251 permit # checks.mailcow.email 49.12.4.251 permit # checks.mailcow.email
2a01:4f8:c17:7906::10 permit # checks.mailcow.email 2a01:4f8:c17:7906::10 permit # checks.mailcow.email
+7 -7
View File
@@ -754,7 +754,7 @@ paths:
- syncjobs - syncjobs
- quarantine - quarantine
- login_as - login_as
- sogo_access - sogo_redirection
- app_passwds - app_passwds
- bcc_maps - bcc_maps
- pushover - pushover
@@ -807,7 +807,7 @@ paths:
- syncjobs - syncjobs
- quarantine - quarantine
- login_as - login_as
- sogo_access - sogo_redirection
- app_passwds - app_passwds
- bcc_maps - bcc_maps
- pushover - pushover
@@ -3423,7 +3423,7 @@ paths:
- info@domain2.tld - info@domain2.tld
- domain3.tld - domain3.tld
- "*" - "*"
sogo_access: "1" sogo_redirection: "1"
username: username:
- info@domain.tld - info@domain.tld
tags: ["tag3", "tag4"] tags: ["tag3", "tag4"]
@@ -3474,7 +3474,7 @@ paths:
- info@domain2.tld - info@domain2.tld
- domain3.tld - domain3.tld
- "*" - "*"
sogo_access: "1" sogo_redirection: "1"
tags: ["tag3", "tag4"] tags: ["tag3", "tag4"]
items: items:
- info@domain.tld - info@domain.tld
@@ -3506,7 +3506,7 @@ paths:
sender_acl: sender_acl:
description: list of allowed send from addresses description: list of allowed send from addresses
type: object type: object
sogo_access: sogo_redirection:
description: is access to SOGo webmail active or not description: is access to SOGo webmail active or not
type: boolean type: boolean
type: object type: object
@@ -4883,7 +4883,7 @@ paths:
force_pw_update: "0" force_pw_update: "0"
mailbox_format: "maildir:" mailbox_format: "maildir:"
quarantine_notification: never quarantine_notification: never
sogo_access: "1" sogo_redirection: "1"
tls_enforce_in: "0" tls_enforce_in: "0"
tls_enforce_out: "0" tls_enforce_out: "0"
domain: doman3.tld domain: doman3.tld
@@ -5807,7 +5807,7 @@ paths:
force_pw_update: "0" force_pw_update: "0"
mailbox_format: "maildir:" mailbox_format: "maildir:"
quarantine_notification: never quarantine_notification: never
sogo_access: "1" sogo_redirection: "1"
tls_enforce_in: "0" tls_enforce_in: "0"
tls_enforce_out: "0" tls_enforce_out: "0"
custom_attributes: {} custom_attributes: {}
+9
View File
@@ -48,7 +48,12 @@ function acl($_action, $_scope = null, $_data = null, $_extra = null) {
); );
continue; continue;
} }
$sogo_acl_changed = false;
foreach ($set_acls as $set_acl_key => $set_acl_val) { foreach ($set_acls as $set_acl_key => $set_acl_val) {
// Track if sogo_access ACL changed
if ($set_acl_key == 'sogo_access' && $is_now[$set_acl_key] != $set_acl_val) {
$sogo_acl_changed = true;
}
$stmt = $pdo->prepare("UPDATE `user_acl` SET " . $set_acl_key . " = " . intval($set_acl_val) . " $stmt = $pdo->prepare("UPDATE `user_acl` SET " . $set_acl_key . " = " . intval($set_acl_val) . "
WHERE `username` = :username"); WHERE `username` = :username");
$stmt->execute(array( $stmt->execute(array(
@@ -60,6 +65,10 @@ function acl($_action, $_scope = null, $_data = null, $_extra = null) {
'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log),
'msg' => array('acl_saved', $username) 'msg' => array('acl_saved', $username)
); );
// Update SOGo static view if sogo_access ACL changed
if ($sogo_acl_changed) {
update_sogo_static_view($username);
}
} }
break; break;
case 'domainadmin': case 'domainadmin':
-4
View File
@@ -287,8 +287,6 @@ function user_login($user, $pass, $extra = null){
return false; return false;
} }
$row['attributes'] = json_decode($row['attributes'], true);
// check for tfa authenticators // check for tfa authenticators
$authenticators = get_tfa($user); $authenticators = get_tfa($user);
if (isset($authenticators['additional']) && is_array($authenticators['additional']) && count($authenticators['additional']) > 0 && !$is_internal) { if (isset($authenticators['additional']) && is_array($authenticators['additional']) && count($authenticators['additional']) > 0 && !$is_internal) {
@@ -345,8 +343,6 @@ function user_login($user, $pass, $extra = null){
return false; return false;
} }
$row['attributes'] = json_decode($row['attributes'], true);
// check for tfa authenticators // check for tfa authenticators
$authenticators = get_tfa($user); $authenticators = get_tfa($user);
if (isset($authenticators['additional']) && is_array($authenticators['additional']) && count($authenticators['additional']) > 0 && !$is_internal) { if (isset($authenticators['additional']) && is_array($authenticators['additional']) && count($authenticators['additional']) > 0 && !$is_internal) {
-8
View File
@@ -108,14 +108,6 @@ function fwdhost($_action, $_data = null) {
} }
break; break;
case 'delete': 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']; $hosts = (array)$_data['forwardinghost'];
foreach ($hosts as $host) { foreach ($hosts as $host) {
try { try {
+37 -6
View File
@@ -940,8 +940,12 @@ function update_sogo_static_view($mailbox = null) {
$mailbox_exists = false; $mailbox_exists = false;
if ($mailbox !== null) { if ($mailbox !== null) {
// Check if the mailbox exists // Check if the mailbox exists and should have SOGo access
$stmt = $pdo->prepare("SELECT username FROM mailbox WHERE username = :mailbox AND active = '1'"); $stmt = $pdo->prepare("SELECT m.username FROM mailbox m
LEFT JOIN user_acl u ON m.username = u.username
WHERE m.username = :mailbox
AND m.active = '1'
AND (u.sogo_access IS NULL OR u.sogo_access = 1)");
$stmt->execute(array(':mailbox' => $mailbox)); $stmt->execute(array(':mailbox' => $mailbox));
$row = $stmt->fetch(PDO::FETCH_ASSOC); $row = $stmt->fetch(PDO::FETCH_ASSOC);
if ($row){ if ($row){
@@ -976,8 +980,10 @@ function update_sogo_static_view($mailbox = null) {
LEFT OUTER JOIN grouped_mail_aliases ga ON ga.username REGEXP CONCAT('(^|,)', mailbox.username, '($|,)') LEFT OUTER JOIN grouped_mail_aliases ga ON ga.username REGEXP CONCAT('(^|,)', mailbox.username, '($|,)')
LEFT OUTER JOIN grouped_domain_alias_address gda ON gda.username = mailbox.username LEFT OUTER JOIN grouped_domain_alias_address gda ON gda.username = mailbox.username
LEFT OUTER JOIN grouped_sender_acl_external external_acl ON external_acl.username = mailbox.username LEFT OUTER JOIN grouped_sender_acl_external external_acl ON external_acl.username = mailbox.username
LEFT OUTER JOIN user_acl ON user_acl.username = mailbox.username
WHERE WHERE
mailbox.active = '1' mailbox.active = '1'
AND (user_acl.sogo_access IS NULL OR user_acl.sogo_access = 1)
$subquery $subquery
ON DUPLICATE KEY UPDATE ON DUPLICATE KEY UPDATE
`domain` = VALUES(`domain`), `domain` = VALUES(`domain`),
@@ -1005,7 +1011,27 @@ function update_sogo_static_view($mailbox = null) {
)); ));
} }
$stmt = $pdo->query("DELETE FROM _sogo_static_view WHERE `c_uid` NOT IN (SELECT `username` FROM `mailbox` WHERE `active` = '1');"); if ($mailbox_exists) {
// For single mailbox update, only delete this specific user
$stmt = $pdo->prepare("DELETE FROM _sogo_static_view
WHERE `c_uid` = :mailbox
AND `c_uid` NOT IN (
SELECT m.`username` FROM `mailbox` m
LEFT JOIN `user_acl` u ON m.`username` = u.`username`
WHERE m.`active` = '1'
AND m.`username` = :mailbox2
AND (u.`sogo_access` IS NULL OR u.`sogo_access` = 1)
)");
$stmt->execute(array(':mailbox' => $mailbox, ':mailbox2' => $mailbox));
} else {
// Full cleanup for all users
$stmt = $pdo->query("DELETE FROM _sogo_static_view WHERE `c_uid` NOT IN (
SELECT m.`username` FROM `mailbox` m
LEFT JOIN `user_acl` u ON m.`username` = u.`username`
WHERE m.`active` = '1'
AND (u.`sogo_access` IS NULL OR u.`sogo_access` = 1)
);");
}
flush_memcached(); flush_memcached();
} }
@@ -3490,9 +3516,14 @@ function set_user_loggedin_session($user) {
session_regenerate_id(true); session_regenerate_id(true);
$_SESSION['mailcow_cc_username'] = $user; $_SESSION['mailcow_cc_username'] = $user;
$_SESSION['mailcow_cc_role'] = 'user'; $_SESSION['mailcow_cc_role'] = 'user';
$sogo_sso_pass = file_get_contents("/etc/sogo-sso/sogo-sso.pass");
$_SESSION['sogo-sso-user-allowed'][] = $user; acl('to_session');
$_SESSION['sogo-sso-pass'] = $sogo_sso_pass; if (hasACLAccess("sogo_access")) {
$sogo_sso_pass = file_get_contents("/etc/sogo-sso/sogo-sso.pass");
$_SESSION['sogo-sso-user-allowed'][] = $user;
$_SESSION['sogo-sso-pass'] = $sogo_sso_pass;
}
unset($_SESSION['pending_mailcow_cc_username']); unset($_SESSION['pending_mailcow_cc_username']);
unset($_SESSION['pending_mailcow_cc_role']); unset($_SESSION['pending_mailcow_cc_role']);
unset($_SESSION['pending_tfa_methods']); unset($_SESSION['pending_tfa_methods']);
+15 -48
View File
@@ -1101,7 +1101,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$force_tfa = (isset($_data['force_tfa'])) ? intval($_data['force_tfa']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['force_tfa']); $force_tfa = (isset($_data['force_tfa'])) ? intval($_data['force_tfa']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['force_tfa']);
$tls_enforce_in = (isset($_data['tls_enforce_in'])) ? intval($_data['tls_enforce_in']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['tls_enforce_in']); $tls_enforce_in = (isset($_data['tls_enforce_in'])) ? intval($_data['tls_enforce_in']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['tls_enforce_in']);
$tls_enforce_out = (isset($_data['tls_enforce_out'])) ? intval($_data['tls_enforce_out']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['tls_enforce_out']); $tls_enforce_out = (isset($_data['tls_enforce_out'])) ? intval($_data['tls_enforce_out']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['tls_enforce_out']);
$sogo_access = (isset($_data['sogo_access'])) ? intval($_data['sogo_access']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['sogo_access']); $sogo_redirection = (isset($_data['sogo_redirection'])) ? intval($_data['sogo_redirection']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['sogo_redirection']);
$imap_access = (isset($_data['imap_access'])) ? intval($_data['imap_access']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['imap_access']); $imap_access = (isset($_data['imap_access'])) ? intval($_data['imap_access']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['imap_access']);
$pop3_access = (isset($_data['pop3_access'])) ? intval($_data['pop3_access']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['pop3_access']); $pop3_access = (isset($_data['pop3_access'])) ? intval($_data['pop3_access']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['pop3_access']);
$smtp_access = (isset($_data['smtp_access'])) ? intval($_data['smtp_access']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['smtp_access']); $smtp_access = (isset($_data['smtp_access'])) ? intval($_data['smtp_access']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['smtp_access']);
@@ -1111,21 +1111,10 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$relayhost = (isset($_data['relayhost'])) ? intval($_data['relayhost']) : 0; $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_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']); $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); $quota_b = ($quota_m * 1048576);
$attribute_hash = (!empty($_data['attribute_hash'])) ? $_data['attribute_hash'] : ''; $attribute_hash = (!empty($_data['attribute_hash'])) ? $_data['attribute_hash'] : '';
if (in_array($authsource, array('keycloak', 'generic-oidc', 'ldap'))){ if (in_array($authsource, array('keycloak', 'generic-oidc', 'ldap'))){
$force_pw_update = 0; $force_pw_update = 0;
}
if ($authsource == 'generic-oidc'){
$force_tfa = 0; $force_tfa = 0;
} }
$mailbox_attrs = json_encode( $mailbox_attrs = json_encode(
@@ -1134,7 +1123,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
'force_tfa' => strval($force_tfa), 'force_tfa' => strval($force_tfa),
'tls_enforce_in' => strval($tls_enforce_in), 'tls_enforce_in' => strval($tls_enforce_in),
'tls_enforce_out' => strval($tls_enforce_out), 'tls_enforce_out' => strval($tls_enforce_out),
'sogo_access' => strval($sogo_access), 'sogo_redirection' => strval($sogo_redirection),
'imap_access' => strval($imap_access), 'imap_access' => strval($imap_access),
'pop3_access' => strval($pop3_access), 'pop3_access' => strval($pop3_access),
'smtp_access' => strval($smtp_access), 'smtp_access' => strval($smtp_access),
@@ -1325,6 +1314,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$_data['syncjobs'] = (in_array('syncjobs', $_data['acl'])) ? 1 : 0; $_data['syncjobs'] = (in_array('syncjobs', $_data['acl'])) ? 1 : 0;
$_data['eas_reset'] = (in_array('eas_reset', $_data['acl'])) ? 1 : 0; $_data['eas_reset'] = (in_array('eas_reset', $_data['acl'])) ? 1 : 0;
$_data['sogo_profile_reset'] = (in_array('sogo_profile_reset', $_data['acl'])) ? 1 : 0; $_data['sogo_profile_reset'] = (in_array('sogo_profile_reset', $_data['acl'])) ? 1 : 0;
$_data['sogo_access'] = (in_array('sogo_access', $_data['acl'])) ? 1 : 0;
$_data['pushover'] = (in_array('pushover', $_data['acl'])) ? 1 : 0; $_data['pushover'] = (in_array('pushover', $_data['acl'])) ? 1 : 0;
$_data['quarantine'] = (in_array('quarantine', $_data['acl'])) ? 1 : 0; $_data['quarantine'] = (in_array('quarantine', $_data['acl'])) ? 1 : 0;
$_data['quarantine_attachments'] = (in_array('quarantine_attachments', $_data['acl'])) ? 1 : 0; $_data['quarantine_attachments'] = (in_array('quarantine_attachments', $_data['acl'])) ? 1 : 0;
@@ -1341,6 +1331,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$_data['syncjobs'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_syncjobs']); $_data['syncjobs'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_syncjobs']);
$_data['eas_reset'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_eas_reset']); $_data['eas_reset'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_eas_reset']);
$_data['sogo_profile_reset'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_sogo_profile_reset']); $_data['sogo_profile_reset'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_sogo_profile_reset']);
$_data['sogo_access'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_sogo_access']);
$_data['pushover'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_pushover']); $_data['pushover'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_pushover']);
$_data['quarantine'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_quarantine']); $_data['quarantine'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_quarantine']);
$_data['quarantine_attachments'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_quarantine_attachments']); $_data['quarantine_attachments'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_quarantine_attachments']);
@@ -1352,9 +1343,9 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
try { try {
$stmt = $pdo->prepare("INSERT INTO `user_acl` $stmt = $pdo->prepare("INSERT INTO `user_acl`
(`username`, `spam_alias`, `tls_policy`, `spam_score`, `spam_policy`, `delimiter_action`, `syncjobs`, `eas_reset`, `sogo_profile_reset`, (`username`, `spam_alias`, `tls_policy`, `spam_score`, `spam_policy`, `delimiter_action`, `syncjobs`, `eas_reset`, `sogo_profile_reset`, `sogo_access`,
`pushover`, `quarantine`, `quarantine_attachments`, `quarantine_notification`, `quarantine_category`, `app_passwds`, `pw_reset`) `pushover`, `quarantine`, `quarantine_attachments`, `quarantine_notification`, `quarantine_category`, `app_passwds`, `pw_reset`)
VALUES (:username, :spam_alias, :tls_policy, :spam_score, :spam_policy, :delimiter_action, :syncjobs, :eas_reset, :sogo_profile_reset, VALUES (:username, :spam_alias, :tls_policy, :spam_score, :spam_policy, :delimiter_action, :syncjobs, :eas_reset, :sogo_profile_reset, :sogo_access,
:pushover, :quarantine, :quarantine_attachments, :quarantine_notification, :quarantine_category, :app_passwds, :pw_reset) "); :pushover, :quarantine, :quarantine_attachments, :quarantine_notification, :quarantine_category, :app_passwds, :pw_reset) ");
$stmt->execute(array( $stmt->execute(array(
':username' => $username, ':username' => $username,
@@ -1366,6 +1357,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
':syncjobs' => $_data['syncjobs'], ':syncjobs' => $_data['syncjobs'],
':eas_reset' => $_data['eas_reset'], ':eas_reset' => $_data['eas_reset'],
':sogo_profile_reset' => $_data['sogo_profile_reset'], ':sogo_profile_reset' => $_data['sogo_profile_reset'],
':sogo_access' => $_data['sogo_access'],
':pushover' => $_data['pushover'], ':pushover' => $_data['pushover'],
':quarantine' => $_data['quarantine'], ':quarantine' => $_data['quarantine'],
':quarantine_attachments' => $_data['quarantine_attachments'], ':quarantine_attachments' => $_data['quarantine_attachments'],
@@ -1742,20 +1734,11 @@ 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["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_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']); $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_frame"] = (!empty($_data['rl_frame'])) ? $_data['rl_frame'] : "s";
$attr["rl_value"] = (!empty($_data['rl_value'])) ? $_data['rl_value'] : ""; $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']); $attr["force_pw_update"] = isset($_data['force_pw_update']) ? intval($_data['force_pw_update']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['force_pw_update']);
$attr["force_tfa"] = isset($_data['force_tfa']) ? intval($_data['force_tfa']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['force_tfa']); $attr["force_tfa"] = isset($_data['force_tfa']) ? intval($_data['force_tfa']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['force_tfa']);
$attr["sogo_access"] = isset($_data['sogo_access']) ? intval($_data['sogo_access']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['sogo_access']); $attr["sogo_redirection"] = isset($_data['sogo_redirection']) ? intval($_data['sogo_redirection']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['sogo_redirection']);
$attr["active"] = isset($_data['active']) ? intval($_data['active']) : 1; $attr["active"] = isset($_data['active']) ? intval($_data['active']) : 1;
$attr["tls_enforce_in"] = isset($_data['tls_enforce_in']) ? intval($_data['tls_enforce_in']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['tls_enforce_in']); $attr["tls_enforce_in"] = isset($_data['tls_enforce_in']) ? intval($_data['tls_enforce_in']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['tls_enforce_in']);
$attr["tls_enforce_out"] = isset($_data['tls_enforce_out']) ? intval($_data['tls_enforce_out']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['tls_enforce_out']); $attr["tls_enforce_out"] = isset($_data['tls_enforce_out']) ? intval($_data['tls_enforce_out']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['tls_enforce_out']);
@@ -1786,6 +1769,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$attr['acl_syncjobs'] = (in_array('syncjobs', $_data['acl'])) ? 1 : 0; $attr['acl_syncjobs'] = (in_array('syncjobs', $_data['acl'])) ? 1 : 0;
$attr['acl_eas_reset'] = (in_array('eas_reset', $_data['acl'])) ? 1 : 0; $attr['acl_eas_reset'] = (in_array('eas_reset', $_data['acl'])) ? 1 : 0;
$attr['acl_sogo_profile_reset'] = (in_array('sogo_profile_reset', $_data['acl'])) ? 1 : 0; $attr['acl_sogo_profile_reset'] = (in_array('sogo_profile_reset', $_data['acl'])) ? 1 : 0;
$attr['acl_sogo_access'] = (in_array('sogo_access', $_data['acl'])) ? 1 : 0;
$attr['acl_pushover'] = (in_array('pushover', $_data['acl'])) ? 1 : 0; $attr['acl_pushover'] = (in_array('pushover', $_data['acl'])) ? 1 : 0;
$attr['acl_quarantine'] = (in_array('quarantine', $_data['acl'])) ? 1 : 0; $attr['acl_quarantine'] = (in_array('quarantine', $_data['acl'])) ? 1 : 0;
$attr['acl_quarantine_attachments'] = (in_array('quarantine_attachments', $_data['acl'])) ? 1 : 0; $attr['acl_quarantine_attachments'] = (in_array('quarantine_attachments', $_data['acl'])) ? 1 : 0;
@@ -1803,6 +1787,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$attr['acl_syncjobs'] = 0; $attr['acl_syncjobs'] = 0;
$attr['acl_eas_reset'] = 0; $attr['acl_eas_reset'] = 0;
$attr['acl_sogo_profile_reset'] = 0; $attr['acl_sogo_profile_reset'] = 0;
$attr['acl_sogo_access'] = 0;
$attr['acl_pushover'] = 0; $attr['acl_pushover'] = 0;
$attr['acl_quarantine'] = 0; $attr['acl_quarantine'] = 0;
$attr['acl_quarantine_attachments'] = 0; $attr['acl_quarantine_attachments'] = 0;
@@ -2080,14 +2065,6 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
return false; return false;
} }
foreach ($usernames as $username) { 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") { if ($_data['spam_score'] == "default") {
$stmt = $pdo->prepare("DELETE FROM `filterconf` WHERE `object` = :username $stmt = $pdo->prepare("DELETE FROM `filterconf` WHERE `object` = :username
AND (`option` = 'lowspamlevel' OR `option` = 'highspamlevel')"); AND (`option` = 'lowspamlevel' OR `option` = 'highspamlevel')");
@@ -3131,7 +3108,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active']; $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active'];
(int)$force_pw_update = (isset($_data['force_pw_update'])) ? intval($_data['force_pw_update']) : intval($is_now['attributes']['force_pw_update']); (int)$force_pw_update = (isset($_data['force_pw_update'])) ? intval($_data['force_pw_update']) : intval($is_now['attributes']['force_pw_update']);
(int)$force_tfa = (isset($_data['force_tfa'])) ? intval($_data['force_tfa']) : intval($is_now['attributes']['force_tfa']); (int)$force_tfa = (isset($_data['force_tfa'])) ? intval($_data['force_tfa']) : intval($is_now['attributes']['force_tfa']);
(int)$sogo_access = (isset($_data['sogo_access']) && hasACLAccess("sogo_access")) ? intval($_data['sogo_access']) : intval($is_now['attributes']['sogo_access']); (int)$sogo_redirection = (isset($_data['sogo_redirection'])) ? intval($_data['sogo_redirection']) : intval($is_now['attributes']['sogo_redirection']);
(int)$imap_access = (isset($_data['imap_access']) && hasACLAccess("protocol_access")) ? intval($_data['imap_access']) : intval($is_now['attributes']['imap_access']); (int)$imap_access = (isset($_data['imap_access']) && hasACLAccess("protocol_access")) ? intval($_data['imap_access']) : intval($is_now['attributes']['imap_access']);
(int)$pop3_access = (isset($_data['pop3_access']) && hasACLAccess("protocol_access")) ? intval($_data['pop3_access']) : intval($is_now['attributes']['pop3_access']); (int)$pop3_access = (isset($_data['pop3_access']) && hasACLAccess("protocol_access")) ? intval($_data['pop3_access']) : intval($is_now['attributes']['pop3_access']);
(int)$smtp_access = (isset($_data['smtp_access']) && hasACLAccess("protocol_access")) ? intval($_data['smtp_access']) : intval($is_now['attributes']['smtp_access']); (int)$smtp_access = (isset($_data['smtp_access']) && hasACLAccess("protocol_access")) ? intval($_data['smtp_access']) : intval($is_now['attributes']['smtp_access']);
@@ -3154,8 +3131,6 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
} }
if (in_array($authsource, array('keycloak', 'generic-oidc', 'ldap'))){ if (in_array($authsource, array('keycloak', 'generic-oidc', 'ldap'))){
$force_pw_update = 0; $force_pw_update = 0;
}
if ($authsource == 'generic-oidc'){
$force_tfa = 0; $force_tfa = 0;
} }
$pw_recovery_email = (isset($_data['pw_recovery_email']) && $authsource == 'mailcow') ? $_data['pw_recovery_email'] : $is_now['attributes']['recovery_email']; $pw_recovery_email = (isset($_data['pw_recovery_email']) && $authsource == 'mailcow') ? $_data['pw_recovery_email'] : $is_now['attributes']['recovery_email'];
@@ -3429,7 +3404,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
`authsource` = :authsource, `authsource` = :authsource,
`attributes` = JSON_SET(`attributes`, '$.force_pw_update', :force_pw_update), `attributes` = JSON_SET(`attributes`, '$.force_pw_update', :force_pw_update),
`attributes` = JSON_SET(`attributes`, '$.force_tfa', :force_tfa), `attributes` = JSON_SET(`attributes`, '$.force_tfa', :force_tfa),
`attributes` = JSON_SET(`attributes`, '$.sogo_access', :sogo_access), `attributes` = JSON_SET(`attributes`, '$.sogo_redirection', :sogo_redirection),
`attributes` = JSON_SET(`attributes`, '$.imap_access', :imap_access), `attributes` = JSON_SET(`attributes`, '$.imap_access', :imap_access),
`attributes` = JSON_SET(`attributes`, '$.sieve_access', :sieve_access), `attributes` = JSON_SET(`attributes`, '$.sieve_access', :sieve_access),
`attributes` = JSON_SET(`attributes`, '$.pop3_access', :pop3_access), `attributes` = JSON_SET(`attributes`, '$.pop3_access', :pop3_access),
@@ -3447,7 +3422,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
':attribute_hash' => $attribute_hash, ':attribute_hash' => $attribute_hash,
':force_pw_update' => $force_pw_update, ':force_pw_update' => $force_pw_update,
':force_tfa' => $force_tfa, ':force_tfa' => $force_tfa,
':sogo_access' => $sogo_access, ':sogo_redirection' => $sogo_redirection,
':imap_access' => $imap_access, ':imap_access' => $imap_access,
':pop3_access' => $pop3_access, ':pop3_access' => $pop3_access,
':sieve_access' => $sieve_access, ':sieve_access' => $sieve_access,
@@ -3816,19 +3791,10 @@ 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["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_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']; $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_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["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']; $attr["force_pw_update"] = isset($_data['force_pw_update']) ? intval($_data['force_pw_update']) : $is_now['force_pw_update'];
$attr["sogo_access"] = isset($_data['sogo_access']) ? intval($_data['sogo_access']) : $is_now['sogo_access']; $attr["sogo_redirection"] = isset($_data['sogo_redirection']) ? intval($_data['sogo_redirection']) : $is_now['sogo_redirection'];
$attr["active"] = isset($_data['active']) ? intval($_data['active']) : $is_now['active']; $attr["active"] = isset($_data['active']) ? intval($_data['active']) : $is_now['active'];
$attr["tls_enforce_in"] = isset($_data['tls_enforce_in']) ? intval($_data['tls_enforce_in']) : $is_now['tls_enforce_in']; $attr["tls_enforce_in"] = isset($_data['tls_enforce_in']) ? intval($_data['tls_enforce_in']) : $is_now['tls_enforce_in'];
$attr["tls_enforce_out"] = isset($_data['tls_enforce_out']) ? intval($_data['tls_enforce_out']) : $is_now['tls_enforce_out']; $attr["tls_enforce_out"] = isset($_data['tls_enforce_out']) ? intval($_data['tls_enforce_out']) : $is_now['tls_enforce_out'];
@@ -3856,6 +3822,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$attr['acl_syncjobs'] = (in_array('syncjobs', $_data['acl'])) ? 1 : 0; $attr['acl_syncjobs'] = (in_array('syncjobs', $_data['acl'])) ? 1 : 0;
$attr['acl_eas_reset'] = (in_array('eas_reset', $_data['acl'])) ? 1 : 0; $attr['acl_eas_reset'] = (in_array('eas_reset', $_data['acl'])) ? 1 : 0;
$attr['acl_sogo_profile_reset'] = (in_array('sogo_profile_reset', $_data['acl'])) ? 1 : 0; $attr['acl_sogo_profile_reset'] = (in_array('sogo_profile_reset', $_data['acl'])) ? 1 : 0;
$attr['acl_sogo_access'] = (in_array('sogo_access', $_data['acl'])) ? 1 : 0;
$attr['acl_pushover'] = (in_array('pushover', $_data['acl'])) ? 1 : 0; $attr['acl_pushover'] = (in_array('pushover', $_data['acl'])) ? 1 : 0;
$attr['acl_quarantine'] = (in_array('quarantine', $_data['acl'])) ? 1 : 0; $attr['acl_quarantine'] = (in_array('quarantine', $_data['acl'])) ? 1 : 0;
$attr['acl_quarantine_attachments'] = (in_array('quarantine_attachments', $_data['acl'])) ? 1 : 0; $attr['acl_quarantine_attachments'] = (in_array('quarantine_attachments', $_data['acl'])) ? 1 : 0;
+1 -1
View File
@@ -89,7 +89,7 @@ $globalVariables = [
'app_links' => $app_links, 'app_links' => $app_links,
'app_links_processed' => $app_links_processed, 'app_links_processed' => $app_links_processed,
'is_root_uri' => (parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) == '/'), 'is_root_uri' => (parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) == '/'),
'uri' => parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) ?: '/', 'uri' => $_SERVER['REQUEST_URI'],
]; ];
foreach ($globalVariables as $globalVariableName => $globalVariableValue) { foreach ($globalVariables as $globalVariableName => $globalVariableValue) {
+13 -4
View File
@@ -4,7 +4,7 @@ function init_db_schema()
try { try {
global $pdo; global $pdo;
$db_version = "19022026_1220"; $db_version = "12032026_1300";
$stmt = $pdo->query("SHOW TABLES LIKE 'versions'"); $stmt = $pdo->query("SHOW TABLES LIKE 'versions'");
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
@@ -506,6 +506,7 @@ function init_db_schema()
"syncjobs" => "TINYINT(1) NOT NULL DEFAULT '0'", "syncjobs" => "TINYINT(1) NOT NULL DEFAULT '0'",
"eas_reset" => "TINYINT(1) NOT NULL DEFAULT '1'", "eas_reset" => "TINYINT(1) NOT NULL DEFAULT '1'",
"sogo_profile_reset" => "TINYINT(1) NOT NULL DEFAULT '0'", "sogo_profile_reset" => "TINYINT(1) NOT NULL DEFAULT '0'",
"sogo_access" => "TINYINT(1) NOT NULL DEFAULT '1'",
"pushover" => "TINYINT(1) NOT NULL DEFAULT '1'", "pushover" => "TINYINT(1) NOT NULL DEFAULT '1'",
// quarantine is for quarantine actions, todo: rename // quarantine is for quarantine actions, todo: rename
"quarantine" => "TINYINT(1) NOT NULL DEFAULT '1'", "quarantine" => "TINYINT(1) NOT NULL DEFAULT '1'",
@@ -705,7 +706,7 @@ function init_db_schema()
"syncjobs" => "TINYINT(1) NOT NULL DEFAULT '1'", "syncjobs" => "TINYINT(1) NOT NULL DEFAULT '1'",
"quarantine" => "TINYINT(1) NOT NULL DEFAULT '1'", "quarantine" => "TINYINT(1) NOT NULL DEFAULT '1'",
"login_as" => "TINYINT(1) NOT NULL DEFAULT '1'", "login_as" => "TINYINT(1) NOT NULL DEFAULT '1'",
"sogo_access" => "TINYINT(1) NOT NULL DEFAULT '1'", "sogo_redirection" => "TINYINT(1) NOT NULL DEFAULT '1'",
"app_passwds" => "TINYINT(1) NOT NULL DEFAULT '1'", "app_passwds" => "TINYINT(1) NOT NULL DEFAULT '1'",
"bcc_maps" => "TINYINT(1) NOT NULL DEFAULT '1'", "bcc_maps" => "TINYINT(1) NOT NULL DEFAULT '1'",
"pushover" => "TINYINT(1) NOT NULL DEFAULT '0'", "pushover" => "TINYINT(1) NOT NULL DEFAULT '0'",
@@ -1397,7 +1398,10 @@ function init_db_schema()
$pdo->query("UPDATE `admin` SET `attributes` = JSON_SET(`attributes`, '$.force_tfa', \"0\") WHERE JSON_VALUE(`attributes`, '$.force_tfa') IS NULL;"); $pdo->query("UPDATE `admin` SET `attributes` = JSON_SET(`attributes`, '$.force_tfa', \"0\") WHERE JSON_VALUE(`attributes`, '$.force_tfa') IS NULL;");
$pdo->query("UPDATE `admin` SET `attributes` = JSON_SET(`attributes`, '$.force_pw_update', \"0\") WHERE JSON_VALUE(`attributes`, '$.force_pw_update') IS NULL;"); $pdo->query("UPDATE `admin` SET `attributes` = JSON_SET(`attributes`, '$.force_pw_update', \"0\") WHERE JSON_VALUE(`attributes`, '$.force_pw_update') IS NULL;");
$pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.sieve_access', \"1\") WHERE JSON_VALUE(`attributes`, '$.sieve_access') IS NULL;"); $pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.sieve_access', \"1\") WHERE JSON_VALUE(`attributes`, '$.sieve_access') IS NULL;");
$pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.sogo_access', \"1\") WHERE JSON_VALUE(`attributes`, '$.sogo_access') IS NULL;"); // Migrate sogo_access attribute to sogo_redirection
$pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.sogo_redirection', JSON_VALUE(`attributes`, '$.sogo_access')) WHERE JSON_VALUE(`attributes`, '$.sogo_access') IS NOT NULL;");
$pdo->query("UPDATE `mailbox` SET `attributes` = JSON_REMOVE(`attributes`, '$.sogo_access') WHERE JSON_VALUE(`attributes`, '$.sogo_access') IS NOT NULL;");
$pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.sogo_redirection', \"1\") WHERE JSON_VALUE(`attributes`, '$.sogo_redirection') IS NULL;");
$pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.imap_access', \"1\") WHERE JSON_VALUE(`attributes`, '$.imap_access') IS NULL;"); $pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.imap_access', \"1\") WHERE JSON_VALUE(`attributes`, '$.imap_access') IS NULL;");
$pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.pop3_access', \"1\") WHERE JSON_VALUE(`attributes`, '$.pop3_access') IS NULL;"); $pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.pop3_access', \"1\") WHERE JSON_VALUE(`attributes`, '$.pop3_access') IS NULL;");
$pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.smtp_access', \"1\") WHERE JSON_VALUE(`attributes`, '$.smtp_access') IS NULL;"); $pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.smtp_access', \"1\") WHERE JSON_VALUE(`attributes`, '$.smtp_access') IS NULL;");
@@ -1421,6 +1425,10 @@ function init_db_schema()
// Fix domain_admins // Fix domain_admins
$pdo->query("DELETE FROM `domain_admins` WHERE `domain` = 'ALL';"); $pdo->query("DELETE FROM `domain_admins` WHERE `domain` = 'ALL';");
// Migrate template sogo_access to sogo_redirection
$pdo->query("UPDATE `templates` SET `attributes` = JSON_SET(`attributes`, '$.sogo_redirection', JSON_VALUE(`attributes`, '$.sogo_access')) WHERE `type` = 'mailbox' AND JSON_VALUE(`attributes`, '$.sogo_access') IS NOT NULL;");
$pdo->query("UPDATE `templates` SET `attributes` = JSON_REMOVE(`attributes`, '$.sogo_access') WHERE `type` = 'mailbox' AND JSON_VALUE(`attributes`, '$.sogo_access') IS NOT NULL;");
// add default templates // add default templates
$default_domain_template = array( $default_domain_template = array(
"template" => "Default", "template" => "Default",
@@ -1456,7 +1464,7 @@ function init_db_schema()
"rl_value" => "", "rl_value" => "",
"force_pw_update" => intval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['force_pw_update']), "force_pw_update" => intval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['force_pw_update']),
"force_tfa" => intval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['force_tfa']), "force_tfa" => intval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['force_tfa']),
"sogo_access" => intval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['sogo_access']), "sogo_redirection" => intval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['sogo_redirection']),
"active" => 1, "active" => 1,
"tls_enforce_in" => intval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['tls_enforce_in']), "tls_enforce_in" => intval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['tls_enforce_in']),
"tls_enforce_out" => intval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['tls_enforce_out']), "tls_enforce_out" => intval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['tls_enforce_out']),
@@ -1472,6 +1480,7 @@ function init_db_schema()
"acl_syncjobs" => 0, "acl_syncjobs" => 0,
"acl_eas_reset" => 1, "acl_eas_reset" => 1,
"acl_sogo_profile_reset" => 0, "acl_sogo_profile_reset" => 0,
"acl_sogo_access" => 1,
"acl_pushover" => 1, "acl_pushover" => 1,
"acl_quarantine" => 1, "acl_quarantine" => 1,
"acl_quarantine_attachments" => 1, "acl_quarantine_attachments" => 1,
+4 -2
View File
@@ -81,8 +81,9 @@ if (isset($_POST["verify_tfa_login"])) {
header("Location: /"); header("Location: /");
die(); die();
} }
if (intval($user_details['attributes']['sogo_access']) == 1 && if (intval($user_details['attributes']['sogo_redirection']) == 1 &&
intval($user_details['attributes']['force_pw_update']) != 1 && intval($user_details['attributes']['force_pw_update']) != 1 &&
hasACLAccess('sogo_access') &&
getenv('SKIP_SOGO') != "y" && getenv('SKIP_SOGO') != "y" &&
!$is_dual) { !$is_dual) {
header("Location: /SOGo/so/"); header("Location: /SOGo/so/");
@@ -161,8 +162,9 @@ if (isset($_POST["login_user"]) && isset($_POST["pass_user"])) {
header("Location: /"); header("Location: /");
die(); die();
} }
if (intval($user_details['attributes']['sogo_access']) == 1 && if (intval($user_details['attributes']['sogo_redirection']) == 1 &&
intval($user_details['attributes']['force_pw_update']) != 1 && intval($user_details['attributes']['force_pw_update']) != 1 &&
hasACLAccess('sogo_access') &&
getenv('SKIP_SOGO') != "y" && getenv('SKIP_SOGO') != "y" &&
!$is_dual) { !$is_dual) {
header("Location: /SOGo/so/"); header("Location: /SOGo/so/");
+1 -3
View File
@@ -13,9 +13,7 @@ $twig = new Environment($loader, [
// functions // functions
$twig->addFunction(new TwigFunction('query_string', function (array $params = []) { $twig->addFunction(new TwigFunction('query_string', function (array $params = []) {
$allowed = ['lang', 'mobileconfig']; return http_build_query(array_merge($_GET, $params));
$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) { $twig->addFunction(new TwigFunction('is_uri', function (string $uri, string $where = null) {
+2 -2
View File
@@ -196,8 +196,8 @@ $MAILBOX_DEFAULT_ATTRIBUTES['force_pw_update'] = false;
// Force 2FA enrollment at next login // Force 2FA enrollment at next login
$MAILBOX_DEFAULT_ATTRIBUTES['force_tfa'] = false; $MAILBOX_DEFAULT_ATTRIBUTES['force_tfa'] = false;
// Enable SOGo access - Users will be redirected to SOGo after login (set to false to disable redirect by default) // Enable SOGo redirection - Users will be redirected to SOGo after login (set to false to disable redirect by default)
$MAILBOX_DEFAULT_ATTRIBUTES['sogo_access'] = true; $MAILBOX_DEFAULT_ATTRIBUTES['sogo_redirection'] = true;
// How to handle tagged emails // How to handle tagged emails
// none - No special handling // none - No special handling
+3 -1
View File
@@ -12,7 +12,9 @@ elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == '
if (empty($_SESSION['pending_tfa_setup']) && empty($_SESSION['pending_pw_update'])) { if (empty($_SESSION['pending_tfa_setup']) && empty($_SESSION['pending_pw_update'])) {
$user_details = mailbox("get", "mailbox_details", $_SESSION['mailcow_cc_username']); $user_details = mailbox("get", "mailbox_details", $_SESSION['mailcow_cc_username']);
$is_dual = (!empty($_SESSION["dual-login"]["username"])) ? true : false; $is_dual = (!empty($_SESSION["dual-login"]["username"])) ? true : false;
if (intval($user_details['attributes']['sogo_access']) == 1 && !$is_dual && getenv('SKIP_SOGO') != "y") { if (intval($user_details['attributes']['sogo_redirection']) == 1 &&
hasACLAccess('sogo_access') &&
!$is_dual && getenv('SKIP_SOGO') != "y") {
header("Location: /SOGo/so/"); header("Location: /SOGo/so/");
} else { } else {
header("Location: /user"); header("Location: /user");
+2 -2
View File
@@ -345,7 +345,7 @@ $(document).ready(function() {
$('.main-logo-dark').addClass('d-none'); $('.main-logo-dark').addClass('d-none');
if ($('#rspamd_logo').length) $('#rspamd_logo').attr('src', '/img/rspamd_logo_dark.png'); if ($('#rspamd_logo').length) $('#rspamd_logo').attr('src', '/img/rspamd_logo_dark.png');
if ($('#rspamd_logo_sm').length) $('#rspamd_logo_sm').attr('src', '/img/rspamd_logo_dark.png'); if ($('#rspamd_logo_sm').length) $('#rspamd_logo_sm').attr('src', '/img/rspamd_logo_dark.png');
localStorage.setItem('mailcow_theme', 'light'); localStorage.setItem('theme', 'light');
}else{ }else{
$('head').append('<link id="dark-mode-theme" rel="stylesheet" type="text/css" href="/css/themes/mailcow-darkmode.css">'); $('head').append('<link id="dark-mode-theme" rel="stylesheet" type="text/css" href="/css/themes/mailcow-darkmode.css">');
$('#dark-mode-toggle').prop('checked', true); $('#dark-mode-toggle').prop('checked', true);
@@ -353,7 +353,7 @@ $(document).ready(function() {
$('.main-logo-dark').removeClass('d-none'); $('.main-logo-dark').removeClass('d-none');
if ($('#rspamd_logo').length) $('#rspamd_logo').attr('src', '/img/rspamd_logo_light.png'); if ($('#rspamd_logo').length) $('#rspamd_logo').attr('src', '/img/rspamd_logo_light.png');
if ($('#rspamd_logo_sm').length) $('#rspamd_logo_sm').attr('src', '/img/rspamd_logo_light.png'); if ($('#rspamd_logo_sm').length) $('#rspamd_logo_sm').attr('src', '/img/rspamd_logo_light.png');
localStorage.setItem('mailcow_theme', 'dark'); localStorage.setItem('theme', 'dark');
} }
} }
-5
View File
@@ -1128,11 +1128,6 @@ jQuery(function($){
item.ua = escapeHtml(item.ua); item.ua = escapeHtml(item.ua);
} }
item.ua = '<span style="font-size:small">' + item.ua + '</span>'; 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") { if (item.service == "activesync") {
item.service = '<span class="badge fs-6 bg-info">ActiveSync</span>'; item.service = '<span class="badge fs-6 bg-info">ActiveSync</span>';
} }
+3 -4
View File
@@ -1,6 +1,5 @@
$(document).ready(function() { $(document).ready(function() {
var theme = localStorage.getItem("mailcow_theme"); var theme = localStorage.getItem("theme");
if (theme !== null) { localStorage.clear();
localStorage.setItem("mailcow_theme", theme); localStorage.setItem("theme", theme);
}
}); });
+9 -6
View File
@@ -385,6 +385,9 @@ $(document).ready(function() {
if (template.acl_sogo_profile_reset == 1){ if (template.acl_sogo_profile_reset == 1){
acl.push("sogo_profile_reset"); acl.push("sogo_profile_reset");
} }
if (template.acl_sogo_access == 1){
acl.push("sogo_access");
}
if (template.acl_pushover == 1){ if (template.acl_pushover == 1){
acl.push("pushover"); acl.push("pushover");
} }
@@ -424,10 +427,10 @@ $(document).ready(function() {
} else { } else {
$('#force_pw_update').prop('checked', false); $('#force_pw_update').prop('checked', false);
} }
if (template.sogo_access == 1){ if (template.sogo_redirection == 1){
$('#sogo_access').prop('checked', true); $('#sogo_redirection').prop('checked', true);
} else { } else {
$('#sogo_access').prop('checked', false); $('#sogo_redirection').prop('checked', false);
} }
// load tags // load tags
@@ -1241,7 +1244,7 @@ jQuery(function($){
item.attributes.sieve_access = '<i class="text-' + (item.attributes.sieve_access == 1 ? 'success' : 'danger') + ' bi bi-' + (item.attributes.sieve_access == 1 ? 'check-lg' : 'x-lg') + '"><span class="sorting-value">' + (item.attributes.sieve_access == 1 ? '1' : '0') + '</span></i>'; item.attributes.sieve_access = '<i class="text-' + (item.attributes.sieve_access == 1 ? 'success' : 'danger') + ' bi bi-' + (item.attributes.sieve_access == 1 ? 'check-lg' : 'x-lg') + '"><span class="sorting-value">' + (item.attributes.sieve_access == 1 ? '1' : '0') + '</span></i>';
item.attributes.eas_access = '<i class="text-' + (item.attributes.eas_access == 1 ? 'success' : 'danger') + ' bi bi-' + (item.attributes.eas_access == 1 ? 'check-lg' : 'x-lg') + '"><span class="sorting-value">' + (item.attributes.eas_access == 1 ? '1' : '0') + '</span></i>'; item.attributes.eas_access = '<i class="text-' + (item.attributes.eas_access == 1 ? 'success' : 'danger') + ' bi bi-' + (item.attributes.eas_access == 1 ? 'check-lg' : 'x-lg') + '"><span class="sorting-value">' + (item.attributes.eas_access == 1 ? '1' : '0') + '</span></i>';
item.attributes.dav_access = '<i class="text-' + (item.attributes.dav_access == 1 ? 'success' : 'danger') + ' bi bi-' + (item.attributes.dav_access == 1 ? 'check-lg' : 'x-lg') + '"><span class="sorting-value">' + (item.attributes.dav_access == 1 ? '1' : '0') + '</span></i>'; item.attributes.dav_access = '<i class="text-' + (item.attributes.dav_access == 1 ? 'success' : 'danger') + ' bi bi-' + (item.attributes.dav_access == 1 ? 'check-lg' : 'x-lg') + '"><span class="sorting-value">' + (item.attributes.dav_access == 1 ? '1' : '0') + '</span></i>';
item.attributes.sogo_access = '<i class="text-' + (item.attributes.sogo_access == 1 ? 'success' : 'danger') + ' bi bi-' + (item.attributes.sogo_access == 1 ? 'check-lg' : 'x-lg') + '"><span class="sorting-value">' + (item.attributes.sogo_access == 1 ? '1' : '0') + '</span></i>'; item.attributes.sogo_redirection = '<i class="text-' + (item.attributes.sogo_redirection == 1 ? 'success' : 'danger') + ' bi bi-' + (item.attributes.sogo_redirection == 1 ? 'check-lg' : 'x-lg') + '"><span class="sorting-value">' + (item.attributes.sogo_redirection == 1 ? '1' : '0') + '</span></i>';
if (item.attributes.quarantine_notification === 'never') { if (item.attributes.quarantine_notification === 'never') {
item.attributes.quarantine_notification = lang.never; item.attributes.quarantine_notification = lang.never;
} else if (item.attributes.quarantine_notification === 'hourly') { } else if (item.attributes.quarantine_notification === 'hourly') {
@@ -1360,8 +1363,8 @@ jQuery(function($){
defaultContent: '', defaultContent: '',
}, },
{ {
title: 'SOGO', title: 'SOGO redirection',
data: 'attributes.sogo_access', data: 'attributes.sogo_redirection',
defaultContent: '', defaultContent: '',
}, },
{ {
+6 -6
View File
@@ -226,18 +226,18 @@ jQuery(function($){
} }
if (typeof data.fuzzy_hashes === 'object' && data.fuzzy_hashes !== null && data.fuzzy_hashes.length !== 0) { if (typeof data.fuzzy_hashes === 'object' && data.fuzzy_hashes !== null && data.fuzzy_hashes.length !== 0) {
$.each(data.fuzzy_hashes, function (index, value) { $.each(data.fuzzy_hashes, function (index, value) {
$('#qid_detail_fuzzy').append('<p style="font-family:monospace">' + escapeHtml(value) + '</p>'); $('#qid_detail_fuzzy').append('<p style="font-family:monospace">' + value + '</p>');
}); });
} else { } else {
$('#qid_detail_fuzzy').append('-'); $('#qid_detail_fuzzy').append('-');
} }
if (typeof data.score !== 'undefined' && typeof data.action !== 'undefined') { if (typeof data.score !== 'undefined' && typeof data.action !== 'undefined') {
if (data.action == "add header") { if (data.action == "add header") {
$('#qid_detail_score').append('<span class="label-rspamd-action badge fs-6 bg-warning"><b>' + escapeHtml(data.score) + '</b> - ' + lang.junk_folder + '</span>'); $('#qid_detail_score').append('<span class="label-rspamd-action badge fs-6 bg-warning"><b>' + data.score + '</b> - ' + lang.junk_folder + '</span>');
} else if (data.action == "reject") { } else if (data.action == "reject") {
$('#qid_detail_score').append('<span class="label-rspamd-action badge fs-6 bg-danger"><b>' + escapeHtml(data.score) + '</b> - ' + lang.rejected + '</span>'); $('#qid_detail_score').append('<span class="label-rspamd-action badge fs-6 bg-danger"><b>' + data.score + '</b> - ' + lang.rejected + '</span>');
} else if (data.action == "rewrite subject") { } else if (data.action == "rewrite subject") {
$('#qid_detail_score').append('<span class="label-rspamd-action badge fs-6 bg-warning"><b>' + escapeHtml(data.score) + '</b> - ' + lang.rewrite_subject + '</span>'); $('#qid_detail_score').append('<span class="label-rspamd-action badge fs-6 bg-warning"><b>' + data.score + '</b> - ' + lang.rewrite_subject + '</span>');
} }
} }
if (typeof data.recipients !== 'undefined') { if (typeof data.recipients !== 'undefined') {
@@ -254,8 +254,8 @@ jQuery(function($){
qAtts.text(''); qAtts.text('');
$.each(data.attachments, function(index, value) { $.each(data.attachments, function(index, value) {
qAtts.append( qAtts.append(
'<p><a href="/inc/ajax/qitem_details.php?id=' + escapeHtml(qitem) + '&amp;att=' + index + '" target="_blank">' + escapeHtml(value[0]) + '</a> (' + escapeHtml(value[1]) + ')' + '<p><a href="/inc/ajax/qitem_details.php?id=' + qitem + '&att=' + index + '" target="_blank">' + value[0] + '</a> (' + value[1] + ')' +
' - <small><a href="' + escapeHtml(value[3]) + '" target="_blank">' + lang.check_hash + '</a></small></p>' ' - <small><a href="' + value[3] + '" target="_blank">' + lang.check_hash + '</a></small></p>'
); );
}); });
} }
+2 -2
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 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 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 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") ? escapeHtml(item.real_rip) : '<a href="https://bgp.tools/prefix/' + escapeHtml(item.real_rip) + '" target="_blank">' + escapeHtml(item.real_rip) + "</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-' + escapeHtml(item.location.toLowerCase()) + '"></span>' : ''; var ip_location = item.location ? ' <span class="flag-icon flag-icon-' + item.location.toLowerCase() + '"></span>' : '';
var ip_data = real_rip + ip_location + app_password; var ip_data = real_rip + ip_location + app_password;
$(".last-sasl-login").append(` $(".last-sasl-login").append(`
-5
View File
@@ -1,5 +0,0 @@
{
"acl": {
"login_as": "E-poçt qutusu istifadəçisi olaraq daxil ol"
}
}
+6 -6
View File
@@ -22,14 +22,15 @@
"ratelimit": "Rate limit", "ratelimit": "Rate limit",
"recipient_maps": "Empfängerumschreibungen", "recipient_maps": "Empfängerumschreibungen",
"smtp_ip_access": "Verwalten der erlaubten Hosts für SMTP", "smtp_ip_access": "Verwalten der erlaubten Hosts für SMTP",
"sogo_access": "Verwalten des SOGo-Zugriffsrechts erlauben", "sogo_access": "SOGo-Nutzung erlauben",
"sogo_profile_reset": "SOGo-Profil zurücksetzen", "sogo_profile_reset": "SOGo-Profil zurücksetzen",
"spam_alias": "Temporäre E-Mail-Aliasse", "spam_alias": "Temporäre E-Mail-Aliasse",
"spam_policy": "Deny/Allowlist", "spam_policy": "Deny/Allowlist",
"spam_score": "Spam-Bewertung", "spam_score": "Spam-Bewertung",
"syncjobs": "Sync Jobs", "syncjobs": "Sync Jobs",
"tls_policy": "Verschlüsselungsrichtlinie", "tls_policy": "Verschlüsselungsrichtlinie",
"unlimited_quota": "Unendliche Quota für Mailboxen" "unlimited_quota": "Unendliche Quota für Mailboxen",
"sogo_redirection": "Verwalten der SOGo-Weiterleitung erlauben"
}, },
"add": { "add": {
"activate_filter_warn": "Alle anderen Filter dieses Typs werden deaktiviert, falls dieses Script aktiviert wird.", "activate_filter_warn": "Alle anderen Filter dieses Typs werden deaktiviert, falls dieses Script aktiviert wird.",
@@ -512,7 +513,6 @@
"pushover_credentials_missing": "Pushover Token und/oder Key fehlen", "pushover_credentials_missing": "Pushover Token und/oder Key fehlen",
"pushover_key": "Pushover Key hat das falsche Format", "pushover_key": "Pushover Key hat das falsche Format",
"pushover_token": "Pushover Token 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", "quota_not_0_not_numeric": "Speicherplatz muss numerisch und >= 0 sein",
"recipient_map_entry_exists": "Eine Empfängerumschreibung für Objekt \"%s\" existiert bereits", "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.", "recovery_email_failed": "E-Mail zur Wiederherstellung konnte nicht gesendet werden. Bitte wenden Sie sich an Ihren Administrator.",
@@ -768,8 +768,6 @@
"sieve_desc": "Kurze Beschreibung", "sieve_desc": "Kurze Beschreibung",
"sieve_type": "Filtertyp", "sieve_type": "Filtertyp",
"skipcrossduplicates": "Duplikate auch über Ordner hinweg überspringen (\"first come, first serve\")", "skipcrossduplicates": "Duplikate auch über Ordner hinweg überspringen (\"first come, first serve\")",
"sogo_access": "Direktes weiterleiten an SOGo",
"sogo_access_info": "Nach dem Einloggen wird der Benutzer automatisch an SOGo weitergeleitet.",
"sogo_visible": "Alias in SOGo sichtbar", "sogo_visible": "Alias in SOGo sichtbar",
"sogo_visible_info": "Diese Option hat lediglich Einfluss auf Objekte, die in SOGo darstellbar sind (geteilte oder nicht-geteilte Alias-Adressen mit dem Ziel mindestens einer lokalen Mailbox).", "sogo_visible_info": "Diese Option hat lediglich Einfluss auf Objekte, die in SOGo darstellbar sind (geteilte oder nicht-geteilte Alias-Adressen mit dem Ziel mindestens einer lokalen Mailbox).",
"spam_alias": "Anpassen temporärer Alias-Adressen", "spam_alias": "Anpassen temporärer Alias-Adressen",
@@ -786,7 +784,9 @@
"unchanged_if_empty": "Unverändert, wenn leer", "unchanged_if_empty": "Unverändert, wenn leer",
"username": "Benutzername", "username": "Benutzername",
"validate_save": "Validieren und speichern", "validate_save": "Validieren und speichern",
"pushover_sound": "Ton" "pushover_sound": "Ton",
"sogo_redirection": "Direktes weiterleiten an SOGo",
"sogo_redirection_info": "Nach dem Einloggen wird der Benutzer automatisch an SOGo weitergeleitet."
}, },
"fido2": { "fido2": {
"confirm": "Bestätigen", "confirm": "Bestätigen",
+6 -6
View File
@@ -22,14 +22,15 @@
"ratelimit": "Rate limit", "ratelimit": "Rate limit",
"recipient_maps": "Recipient maps", "recipient_maps": "Recipient maps",
"smtp_ip_access": "Change allowed hosts for SMTP", "smtp_ip_access": "Change allowed hosts for SMTP",
"sogo_access": "Allow management of SOGo access", "sogo_access": "Allow SOGo usage",
"sogo_profile_reset": "Reset SOGo profile", "sogo_profile_reset": "Reset SOGo profile",
"spam_alias": "Temporary aliases", "spam_alias": "Temporary aliases",
"spam_policy": "Denylist/Allowlist", "spam_policy": "Denylist/Allowlist",
"spam_score": "Spam score", "spam_score": "Spam score",
"syncjobs": "Sync jobs", "syncjobs": "Sync jobs",
"tls_policy": "TLS policy", "tls_policy": "TLS policy",
"unlimited_quota": "Unlimited quota for mailboxes" "unlimited_quota": "Unlimited quota for mailboxes",
"sogo_redirection": "Allow management of SOGo forwarding"
}, },
"add": { "add": {
"activate_filter_warn": "All other filters will be deactivated, when active is checked.", "activate_filter_warn": "All other filters will be deactivated, when active is checked.",
@@ -513,7 +514,6 @@
"pushover_credentials_missing": "Pushover token and or key missing", "pushover_credentials_missing": "Pushover token and or key missing",
"pushover_key": "Pushover key has a wrong format", "pushover_key": "Pushover key has a wrong format",
"pushover_token": "Pushover token 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", "quota_not_0_not_numeric": "Quota must be numeric and >= 0",
"recipient_map_entry_exists": "A Recipient map entry \"%s\" exists", "recipient_map_entry_exists": "A Recipient map entry \"%s\" exists",
"recovery_email_failed": "Could not send a recovery email. Please contact your administrator.", "recovery_email_failed": "Could not send a recovery email. Please contact your administrator.",
@@ -769,8 +769,6 @@
"sieve_desc": "Short description", "sieve_desc": "Short description",
"sieve_type": "Filter type", "sieve_type": "Filter type",
"skipcrossduplicates": "Skip duplicate messages across folders (first come, first serve)", "skipcrossduplicates": "Skip duplicate messages across folders (first come, first serve)",
"sogo_access": "Direct forwarding to SOGo",
"sogo_access_info": "After logging in, the user is automatically redirected to SOGo.",
"sogo_visible": "Alias is visible in SOGo", "sogo_visible": "Alias is visible in SOGo",
"sogo_visible_info": "This option only affects objects, that can be displayed in SOGo (shared or non-shared alias addresses pointing to at least one local mailbox). If hidden, an alias will not appear as selectable sender in SOGo.", "sogo_visible_info": "This option only affects objects, that can be displayed in SOGo (shared or non-shared alias addresses pointing to at least one local mailbox). If hidden, an alias will not appear as selectable sender in SOGo.",
"spam_alias": "Create or change time limited alias addresses", "spam_alias": "Create or change time limited alias addresses",
@@ -786,7 +784,9 @@
"title": "Edit object", "title": "Edit object",
"unchanged_if_empty": "If unchanged leave blank", "unchanged_if_empty": "If unchanged leave blank",
"username": "Username", "username": "Username",
"validate_save": "Validate and save" "validate_save": "Validate and save",
"sogo_redirection": "Direct forwarding to SOGo",
"sogo_redirection_info": "After logging in, the user is automatically redirected to SOGo."
}, },
"fido2": { "fido2": {
"confirm": "Confirm", "confirm": "Confirm",
+2 -18
View File
@@ -1144,8 +1144,7 @@
"subscribeall": "Feliratkozás minden mappára", "subscribeall": "Feliratkozás minden mappára",
"syncjob": "Szinkronizálási feladat hozzáadása", "syncjob": "Szinkronizálási feladat hozzáadása",
"internal": "Belső", "internal": "Belső",
"internal_info": "Belső álnevek csak a saját domain vagy domain álnév számára elérhető.", "internal_info": "Belső álnevek csak a saját domain vagy domain álnév számára elérhető."
"sender_allowed": "Küldés engedélyezése ezzel az aliasszal"
}, },
"danger": { "danger": {
"access_denied": "Hozzáférés megtagatva vagy nem megfelelő űrlap adat", "access_denied": "Hozzáférés megtagatva vagy nem megfelelő űrlap adat",
@@ -1246,21 +1245,6 @@
"pushover_key": "A pushover kulcs rossz formátumú", "pushover_key": "A pushover kulcs rossz formátumú",
"pushover_token": "A Pushover token rossz formátumú", "pushover_token": "A Pushover token rossz formátumú",
"quota_not_0_not_numeric": "A kvótának numerikusnak és >= 0-nak kell lennie.", "quota_not_0_not_numeric": "A kvótának numerikusnak és >= 0-nak kell lennie.",
"recipient_map_entry_exists": "Létezik egy \"%s\" címzett-térkép bejegyzés", "recipient_map_entry_exists": "Létezik egy \"%s\" címzett-térkép bejegyzés"
"redis_error": "Redis hiba lépett fel: %s",
"relayhost_invalid": "A(z) %s elem érvénytelen a leképezésben.",
"release_send_failed": "Az üzenet felszabadítása sikertelen: %s",
"reset_f2b_regex": "A regex-szűrő időtúllépés miatt nem állt le. Próbálja újra, vagy várjon egy kicsit, és töltse újra az oldalt.",
"resource_invalid": "A(z) %s erőforrásnév érvénytelen",
"rl_timeframe": "Érvénytelen időkeret a lekérdezési korláthoz",
"rspamd_ui_pw_length": "A Rspamd UI jelszónak legalább 6 karakter hosszúnak kell lennie.",
"script_empty": "A szkript nem lehet üres",
"sender_acl_invalid": "A küldőhöz tartozó ACL-érték (%s) érvénytelen",
"set_acl_failed": "Az ACL beállítása meghiúsult",
"settings_map_invalid": "Érvénytelen beállítás-leképezési azonosító: %s",
"recovery_email_failed": "A helyreállítási email kiküldése sikertelen. Kérlek, lépj kapcsolatba az adminisztrátorral!",
"reset_token_limit_exceeded": "Túl sok visszaállítási kísérlet. Kérjük, várjon, mielőtt újra próbálkozna.",
"required_data_missing": "Hiányzik a(z) szükséges %s adat",
"tfa_removal_blocked": "A kétfaktoros hitelesítés nem távolítható el, mert elengedhetetlen a fiókod használatához."
} }
} }
+22 -36
View File
@@ -111,8 +111,7 @@
"validation_success": "Validado com sucesso", "validation_success": "Validado com sucesso",
"dry": "Simular sincronização", "dry": "Simular sincronização",
"internal": "Interno", "internal": "Interno",
"internal_info": "Aliases internos são acessíveis apenas a partir do próprio domínio ou alias de domínio.", "internal_info": "Aliases internos são acessíveis apenas a partir do próprio domínio ou alias de domínio."
"sender_allowed": "Permitir enviar como este alias"
}, },
"admin": { "admin": {
"access": "Acesso", "access": "Acesso",
@@ -152,7 +151,7 @@
"arrival_time": "Hora de chegada (hora do servidor)", "arrival_time": "Hora de chegada (hora do servidor)",
"authed_user": "Usuário autoritário", "authed_user": "Usuário autoritário",
"ays": "Tem certeza de que deseja continuar?", "ays": "Tem certeza de que deseja continuar?",
"ban_list_info": "Veja abaixo a lista de IPs banidos: <b>rede (tempo restante do banimento) - [ações]</b>.<br />IPs na fila para serem desbanidos serão removidos da lista de banimentos ativos em alguns segundos.<br />Rótulos vermelhos indicam banimentos permanentes ativos na lista de bloqueio.", "ban_list_info": "Veja uma lista de IPs banidos abaixo: <b>rede (tempo restante de banimento) - [ações]</b>. <br />Os IPs na fila para serem desbanidos serão removidos da lista de banimentos ativos em alguns segundos. <br />Rótulos vermelhos indicam proibições permanentes ativas na lista negra.",
"change_logo": "Alterar logotipo", "change_logo": "Alterar logotipo",
"logo_normal_label": "Normal", "logo_normal_label": "Normal",
"logo_dark_label": "Invertido para o modo escuro", "logo_dark_label": "Invertido para o modo escuro",
@@ -189,9 +188,9 @@
"excludes": "Exclui esses destinatários", "excludes": "Exclui esses destinatários",
"f2b_ban_time": "Tempo (s) de proibição", "f2b_ban_time": "Tempo (s) de proibição",
"f2b_ban_time_increment": "O tempo de banimento é incrementado com cada banimento", "f2b_ban_time_increment": "O tempo de banimento é incrementado com cada banimento",
"f2b_blacklist": "Redes/hosts na lista de bloqueio", "f2b_blacklist": "Redes/hosts na lista negra",
"f2b_filter": "Filtros Regex", "f2b_filter": "Filtros Regex",
"f2b_list_info": "Um host ou rede na lista de bloqueio sempre terá prioridade sobre uma entidade na lista de permissões. <b>As atualizações das listas levarão alguns segundos para serem aplicadas.</b>", "f2b_list_info": "Um host ou rede na lista negra sempre superará uma entidade na lista branca. <b>As atualizações da lista levarão alguns segundos para serem aplicadas.</b>",
"f2b_manage_external": "Gerenciar Fail2Ban externamente", "f2b_manage_external": "Gerenciar Fail2Ban externamente",
"f2b_manage_external_info": "O Fail2ban ainda manterá a lista de banimentos, mas não definirá ativamente regras para bloquear o tráfego. Use a lista de banimento gerada abaixo para bloquear externamente o tráfego.", "f2b_manage_external_info": "O Fail2ban ainda manterá a lista de banimentos, mas não definirá ativamente regras para bloquear o tráfego. Use a lista de banimento gerada abaixo para bloquear externamente o tráfego.",
"f2b_max_attempts": "Máximo de tentativas", "f2b_max_attempts": "Máximo de tentativas",
@@ -201,7 +200,7 @@
"f2b_parameters": "Parâmetros do Fail2ban", "f2b_parameters": "Parâmetros do Fail2ban",
"f2b_regex_info": "Registros considerados: SoGo, Postfix, Dovecot, PHP-FPM.", "f2b_regex_info": "Registros considerados: SoGo, Postfix, Dovecot, PHP-FPM.",
"f2b_retry_window": "Repita a (s) janela (s) para o máximo de tentativas", "f2b_retry_window": "Repita a (s) janela (s) para o máximo de tentativas",
"f2b_whitelist": "Redes/hosts na lista de permissões", "f2b_whitelist": "Redes/hosts incluídos na lista branca",
"filter_table": "Tabela de filtros", "filter_table": "Tabela de filtros",
"forwarding_hosts": "Anfitriões de encaminhamento", "forwarding_hosts": "Anfitriões de encaminhamento",
"forwarding_hosts_add_hint": "Você pode especificar endereços IPv4/IPv6, redes em notação CIDR, nomes de host (que serão resolvidos para endereços IP) ou nomes de domínio (que serão resolvidos para endereços IP consultando registros SPF ou, na ausência deles, registros MX).", "forwarding_hosts_add_hint": "Você pode especificar endereços IPv4/IPv6, redes em notação CIDR, nomes de host (que serão resolvidos para endereços IP) ou nomes de domínio (que serão resolvidos para endereços IP consultando registros SPF ou, na ausência deles, registros MX).",
@@ -321,8 +320,8 @@
"rspamd_com_settings": "Um nome de configuração será gerado automaticamente, veja os exemplos de predefinições abaixo. Para obter mais detalhes, consulte a documentação <a href=\"https://rspamd.com/doc/configuration/settings.html#settings-structure\" target=\"_blank\">do Rspamd</a>", "rspamd_com_settings": "Um nome de configuração será gerado automaticamente, veja os exemplos de predefinições abaixo. Para obter mais detalhes, consulte a documentação <a href=\"https://rspamd.com/doc/configuration/settings.html#settings-structure\" target=\"_blank\">do Rspamd</a>",
"rspamd_global_filters": "Mapas de filtro globais", "rspamd_global_filters": "Mapas de filtro globais",
"rspamd_global_filters_agree": "Eu vou ter cuidado!", "rspamd_global_filters_agree": "Eu vou ter cuidado!",
"rspamd_global_filters_info": "Os mapas de filtros globais contêm diferentes tipos de listas globais de bloqueio e de permissões.", "rspamd_global_filters_info": "Os mapas de filtros globais contêm diferentes tipos de listas negras e brancas globais.",
"rspamd_global_filters_regex": "Os nomes explicam sua finalidade. Todo o conteúdo deve conter uma expressão regular válida no formato \"/padrão/opções\" (por exemplo, <code>/.[+@domain.tld](mailto:+@domain.tld)/i</code>).<br>\nEmbora verificações básicas sejam executadas em cada linha de regex, a funcionalidade do Rspamd pode ser comprometida se ele não conseguir ler a sintaxe corretamente.<br>\nO Rspamd tentará ler o conteúdo do mapa quando ele for alterado. Se você tiver problemas, <a href=\"\" data-toggle=\"modal\" data-container=\"rspamd-mailcow\" data-target=\"#RestartContainer\">reinicie o Rspamd</a> para forçar o recarregamento do mapa.<br>Elementos na lista de bloqueio são excluídos da quarentena.", "rspamd_global_filters_regex": "Seus nomes explicam seu propósito. <code>Todo o conteúdo deve conter uma expressão regular válida no formato /padrão/opções (por exemplo, /. + @domain\\ .tld/i</code>). <br>\r\n Embora verificações rudimentares estejam sendo executadas em cada linha de regex, a funcionalidade do Rspamd pode ser interrompida se não conseguir ler a sintaxe corretamente. <br>\r\n O Rspamd tentará ler o conteúdo do mapa quando alterado. Se você tiver problemas, <a href=\"\" data-toggle=\"modal\" data-container=\"rspamd-mailcow\" data-target=\"#RestartContainer\">reinicie o Rspamd</a> para forçar o recarregamento do mapa. <br>Os elementos da lista negra são excluídos da quarentena.",
"rspamd_settings_map": "Mapa de configurações do Rspamd", "rspamd_settings_map": "Mapa de configurações do Rspamd",
"sal_level": "Nível de humor", "sal_level": "Nível de humor",
"save": "Salvar alterações", "save": "Salvar alterações",
@@ -557,9 +556,7 @@
"mode_invalid": "Modo %s é inválido", "mode_invalid": "Modo %s é inválido",
"mx_invalid": "Registro MX %s é inválido", "mx_invalid": "Registro MX %s é inválido",
"required_data_missing": "Dados obrigatórios %s estão ausentes", "required_data_missing": "Dados obrigatórios %s estão ausentes",
"version_invalid": "Versão %s é inválida", "version_invalid": "Versão %s é inválida"
"tfa_removal_blocked": "A autenticação de dois fatores não pode ser removida, pois é obrigatória para a sua conta.",
"quarantine_category_invalid": "A categoria da quarentena deve ser uma das seguintes: add_header, reject, all"
}, },
"datatables": { "datatables": {
"collapse_all": "Recolher tudo", "collapse_all": "Recolher tudo",
@@ -749,13 +746,13 @@
"sieve_desc": "Breve descrição", "sieve_desc": "Breve descrição",
"sieve_type": "Tipo de filtro", "sieve_type": "Tipo de filtro",
"skipcrossduplicates": "Ignore mensagens duplicadas entre pastas (primeiro a chegar, primeiro a ser servido)", "skipcrossduplicates": "Ignore mensagens duplicadas entre pastas (primeiro a chegar, primeiro a ser servido)",
"sogo_access": "Encaminhamento direto para o SOGo", "sogo_access": "Encaminhamento direto para o SOGoo",
"sogo_access_info": "Após o login, o usuário é automaticamente redirecionado para o SOGo.", "sogo_access_info": "Depois de fazer login, o usuário é automaticamente redirecionado para o SOGo.",
"sogo_visible": "O alias é visível no SoGo", "sogo_visible": "O alias é visível no SoGo",
"sogo_visible_info": "Essa opção afeta somente objetos, que podem ser exibidos no SoGo (endereços de alias compartilhados ou não compartilhados apontando para pelo menos uma mailbox local). Se estiver oculto, um alias não aparecerá como remetente selecionável no SoGo.", "sogo_visible_info": "Essa opção afeta somente objetos, que podem ser exibidos no SoGo (endereços de alias compartilhados ou não compartilhados apontando para pelo menos uma mailbox local). Se estiver oculto, um alias não aparecerá como remetente selecionável no SoGo.",
"spam_alias": "Crie ou altere endereços de alias com limite de tempo", "spam_alias": "Crie ou altere endereços de alias com limite de tempo",
"spam_filter": "Filtro de spam", "spam_filter": "Filtro de spam",
"spam_policy": "Adicionar ou remover itens da lista de permissões/bloqueio", "spam_policy": "Adicionar ou remover itens da lista branca/negra",
"spam_score": "Defina uma pontuação de spam personalizada", "spam_score": "Defina uma pontuação de spam personalizada",
"subfolder2": "Sincronizar na subpasta no destino <br><small>(vazio = não usar subpasta</small>)", "subfolder2": "Sincronizar na subpasta no destino <br><small>(vazio = não usar subpasta</small>)",
"syncjob": "Editar tarefa de sincronização", "syncjob": "Editar tarefa de sincronização",
@@ -784,9 +781,7 @@
"mta_sts_max_age_info": "Tempo em segundos que servidores de email de recepção podem armazenar esta política em cache até buscar novamente.", "mta_sts_max_age_info": "Tempo em segundos que servidores de email de recepção podem armazenar esta política em cache até buscar novamente.",
"mta_sts_mx": "Servidor MX", "mta_sts_mx": "Servidor MX",
"mta_sts_mx_info": "Permite envio apenas para nomes de host de servidor de email explicitamente listados; o MTA de envio verifica se o nome do host DNS MX corresponde à lista de políticas e permite entrega apenas com certificado TLS válido (protege contra MITM).", "mta_sts_mx_info": "Permite envio apenas para nomes de host de servidor de email explicitamente listados; o MTA de envio verifica se o nome do host DNS MX corresponde à lista de políticas e permite entrega apenas com certificado TLS válido (protege contra MITM).",
"mta_sts_mx_notice": "Múltiplos servidores MX podem ser especificados (separados por vírgulas).", "mta_sts_mx_notice": "Múltiplos servidores MX podem ser especificados (separados por vírgulas)."
"sender_allowed": "Permitir enviar como este alias",
"sender_allowed_info": "Se desativado, este alias poderá apenas receber e-mails. Use a ACL de remetente para substituir essa configuração e conceder a caixas de correio específicas permissão para enviar."
}, },
"fido2": { "fido2": {
"confirm": "Confirme", "confirm": "Confirme",
@@ -975,7 +970,7 @@
"recipient_map_new": "Novo destinatário", "recipient_map_new": "Novo destinatário",
"recipient_map_new_info": "O destino do mapa do destinatário deve ser um endereço de e-mail válido ou um nome de domínio.", "recipient_map_new_info": "O destino do mapa do destinatário deve ser um endereço de e-mail válido ou um nome de domínio.",
"recipient_map_old": "Destinatário original", "recipient_map_old": "Destinatário original",
"recipient_map_old_info": "O destino original do mapa de destinatário deve ser um endereço de e-mail válido ou um nome de domínio.", "recipient_map_old_info": "O destino original do mapa de um destinatário deve ser um endereço de e-mail válido ou um nome de domínio.",
"recipient_maps": "Mapas de destinatários", "recipient_maps": "Mapas de destinatários",
"relay_all": "Retransmita todos os destinatários", "relay_all": "Retransmita todos os destinatários",
"relay_unknown": "Retransmitir mailboxes desconhecidas", "relay_unknown": "Retransmitir mailboxes desconhecidas",
@@ -1066,7 +1061,7 @@
"notified": "Notificado", "notified": "Notificado",
"qhandler_success": "Solicitação enviada com sucesso para o sistema. Agora você pode fechar a janela.", "qhandler_success": "Solicitação enviada com sucesso para o sistema. Agora você pode fechar a janela.",
"qid": "Respand AID", "qid": "Respand AID",
"qinfo": "O sistema de quarentena irá salvar no banco de dados os e-mails rejeitados (o remetente <em>não</em> terá a impressão de que o e-mail foi entregue), bem como os e-mails que são entregues como cópia na pasta de spam de uma mailbox.\n<br>“Aprender como spam e excluir” irá classificar a mensagem como spam por meio do Teorema de Bayes e também calcular hashes fuzzy para bloquear mensagens semelhantes no futuro.\n<br>Esteja ciente de que o aprendizado de múltiplas mensagens pode, dependendo do seu sistema, levar tempo. <br>Elementos na lista de bloqueio são excluídos da quarentena.", "qinfo": "O sistema de quarentena salvará as mensagens rejeitadas no banco de dados (o remetente <em>não</em> terá a impressão de uma mensagem entregue), bem como as mensagens, que são entregues como cópia na pasta Lixo eletrônico de uma mailbox.\n <br>“Aprenda como spam e exclua” aprenderá uma mensagem como spam por meio do Teorema de Bayes e também calculará hashes difusos para negar mensagens semelhantes no futuro.\n <br>Esteja ciente de que aprender várias mensagens pode ser demorado, dependendo do seu sistema. <br>Os elementos da lista negra são excluídos da quarentena.",
"qitem": "Item de quarentena", "qitem": "Item de quarentena",
"quarantine": "Quarentena", "quarantine": "Quarentena",
"quick_actions": "Ações", "quick_actions": "Ações",
@@ -1242,12 +1237,7 @@
"webauthn": "Autenticação WebAuthn", "webauthn": "Autenticação WebAuthn",
"waiting_usb_auth": "<i>Aguardando o dispositivo USB...</i> <br><br>Toque no botão no seu dispositivo USB agora.", "waiting_usb_auth": "<i>Aguardando o dispositivo USB...</i> <br><br>Toque no botão no seu dispositivo USB agora.",
"waiting_usb_register": "<i>Aguardando o dispositivo USB...</i> <br><br>Digite sua senha acima e confirme seu registro tocando no botão no seu dispositivo USB.", "waiting_usb_register": "<i>Aguardando o dispositivo USB...</i> <br><br>Digite sua senha acima e confirme seu registro tocando no botão no seu dispositivo USB.",
"yubi_otp": "Autenticação Yubico OTP", "yubi_otp": "Autenticação Yubico OTP"
"force_tfa": "Forçar o cadastro de 2FA no login",
"force_tfa_info": "O usuário será obrigado a configurar a autenticação de dois fatores antes de acessar o painel.",
"setup_title": "Autenticação de Dois Fatores Obrigatória",
"setup_required": "Sua conta exige autenticação de dois fatores. Configure um método de 2FA para continuar.",
"cancel_setup": "Cancelar e sair"
}, },
"user": { "user": {
"action": "Ação", "action": "Ação",
@@ -1286,7 +1276,7 @@
"delete_ays": "Confirme o processo de exclusão.", "delete_ays": "Confirme o processo de exclusão.",
"direct_aliases": "Endereços de alias diretos", "direct_aliases": "Endereços de alias diretos",
"direct_aliases_desc": "Os endereços de alias diretos são afetados pelo filtro de spam e pelas configurações da política TLS.", "direct_aliases_desc": "Os endereços de alias diretos são afetados pelo filtro de spam e pelas configurações da política TLS.",
"direct_protocol_access": "Este usuário da mailbox possui <b>acesso direto e externo</b> aos seguintes protocolos e aplicações. Esta configuração é controlada pelo seu administrador. Senhas de aplicativo podem ser criadas para conceder acesso a protocolos e aplicações específicos.<br>O botão “Webmail” fornece login único (SSO) para o SOGo e está sempre disponível.", "direct_protocol_access": "Esse usuário da mailbox tem <b>acesso externo direto</b> aos seguintes protocolos e aplicativos. Essa configuração é controlada pelo administrador. As senhas de aplicativos podem ser criadas para conceder acesso a protocolos e aplicativos individuais. <br>O botão “Login no webmail” fornece login único no SoGo e está sempre disponível.",
"eas_reset": "Redefinir o cache do dispositivo ActiveSync", "eas_reset": "Redefinir o cache do dispositivo ActiveSync",
"eas_reset_help": "Em muitos casos, uma redefinição do cache do dispositivo ajudará a recuperar um perfil quebrado do ActiveSync. <br><b>Atenção:</b> Todos os elementos serão baixados novamente!", "eas_reset_help": "Em muitos casos, uma redefinição do cache do dispositivo ajudará a recuperar um perfil quebrado do ActiveSync. <br><b>Atenção:</b> Todos os elementos serão baixados novamente!",
"eas_reset_now": "Reinicie agora", "eas_reset_now": "Reinicie agora",
@@ -1362,12 +1352,12 @@
"sogo_profile_reset": "Redefinir perfil SoGo", "sogo_profile_reset": "Redefinir perfil SoGo",
"sogo_profile_reset_help": "Isso destruirá o perfil SoGo de um usuário e <b>excluirá todos os dados de contato e calendário irrecuperáveis</b>.", "sogo_profile_reset_help": "Isso destruirá o perfil SoGo de um usuário e <b>excluirá todos os dados de contato e calendário irrecuperáveis</b>.",
"sogo_profile_reset_now": "Redefina o perfil agora", "sogo_profile_reset_now": "Redefina o perfil agora",
"spam_aliases": "Aliases de e-mail de spam", "spam_aliases": "Aliases de e-mail temporários",
"spam_score_reset": "Redefinir para o padrão do servidor", "spam_score_reset": "Redefinir para o padrão do servidor",
"spamfilter": "Filtro de spam", "spamfilter": "Filtro de spam",
"spamfilter_behavior": "Avaliação", "spamfilter_behavior": "Avaliação",
"spamfilter_bl": "Lista de bloqueio", "spamfilter_bl": "Lista negra",
"spamfilter_bl_desc": "Endereços de e-mail na lista de bloqueio para <b>sempre</b> classificar como spam e rejeitados. E-mails rejeitados <b>não</b> serão copiados para a quarentena. Coringas podem ser utilizados. O filtro é aplicado apenas a aliases diretos (aliases com uma única mailbox de destino), excluindo aliases catch-all e a própria mailbox.", "spamfilter_bl_desc": "Endereços de e-mail na lista negra para <b>sempre</b> serem classificados como spam e rejeitados. E-mails rejeitados <b>não</b> serão copiados para a quarentena. Podem ser usados curingas. Um filtro é aplicado a aliases diretos (aliases com uma única caixa de correio de destino), excluindo aliases abrangentes e a própria mailbox.",
"spamfilter_default_score": "Valores padrão", "spamfilter_default_score": "Valores padrão",
"spamfilter_green": "Verde: esta mensagem não é spam", "spamfilter_green": "Verde: esta mensagem não é spam",
"spamfilter_hint": "O primeiro valor descreve a “pontuação baixa de spam”, o segundo representa a “alta pontuação de spam”.", "spamfilter_hint": "O primeiro valor descreve a “pontuação baixa de spam”, o segundo representa a “alta pontuação de spam”.",
@@ -1378,8 +1368,8 @@
"spamfilter_table_empty": "Não há dados para exibir", "spamfilter_table_empty": "Não há dados para exibir",
"spamfilter_table_remove": "remover", "spamfilter_table_remove": "remover",
"spamfilter_table_rule": "Regra", "spamfilter_table_rule": "Regra",
"spamfilter_wl": "Lista de permissões", "spamfilter_wl": "Lista branca",
"spamfilter_wl_desc": "Endereços de e-mail na lista de permissões são configurados para <b>nunca</b> serem classificados como spam. Coringas podem ser utilizados. O filtro é aplicado apenas a aliases diretos (aliases com uma única mailbox de destino), excluindo aliases catch-all e a própria mailbox.", "spamfilter_wl_desc": "Os endereços de e-mail incluídos na lista branca são programados para <b>nunca</b> serem classificados como spam. Podem ser usados curingas. Um filtro é aplicado a aliases diretos (aliases com uma única mailbox de destino), excluindo aliases abrangentes e a própria mailbox.",
"spamfilter_yellow": "Amarelo: esta mensagem pode ser spam, será marcada como spam e movida para sua pasta de lixo eletrônico", "spamfilter_yellow": "Amarelo: esta mensagem pode ser spam, será marcada como spam e movida para sua pasta de lixo eletrônico",
"status": "Status", "status": "Status",
"sync_jobs": "Trabalhos de sincronização", "sync_jobs": "Trabalhos de sincronização",
@@ -1418,11 +1408,7 @@
"authentication": "Autenticação", "authentication": "Autenticação",
"overview": "Visão geral", "overview": "Visão geral",
"protocols": "Protocolos", "protocols": "Protocolos",
"tfa_info": "A autenticação de dois fatores ajuda a proteger sua conta. Se você habilitá-la, precisará de senhas de aplicativo para fazer login em aplicativos ou serviços que não suportam autenticação de dois fatores (por exemplo, clientes de email).", "tfa_info": "A autenticação de dois fatores ajuda a proteger sua conta. Se você habilitá-la, precisará de senhas de aplicativo para fazer login em aplicativos ou serviços que não suportam autenticação de dois fatores (por exemplo, clientes de email)."
"expire_never": "Nunca expirar",
"forever": "Para sempre",
"pw_update_required": "Sua conta exige a alteração de senha. Defina uma nova senha para continuar.",
"spam_aliases_info": "Um alias de spam é um endereço de e-mail temporário que pode ser usado para proteger endereços de e-mail reais. <br>Opcionalmente, pode-se definir um tempo de expiração para que o alias seja automaticamente desativado após o período definido, descartando efetivamente endereços que tenham sido abusados ou vazados."
}, },
"warning": { "warning": {
"cannot_delete_self": "Não é possível excluir o usuário conectado", "cannot_delete_self": "Não é possível excluir o usuário conectado",
+1 -2
View File
@@ -558,8 +558,7 @@
"mode_invalid": "Način %s ni veljaven", "mode_invalid": "Način %s ni veljaven",
"mx_invalid": "Zapis MX %s je neveljaven", "mx_invalid": "Zapis MX %s je neveljaven",
"version_invalid": "Različica %s je neveljavna", "version_invalid": "Različica %s je neveljavna",
"tfa_removal_blocked": "Dvofaktorske avtentikacije ni mogoče odstraniti, ker je obvezna za vaš račun.", "tfa_removal_blocked": "Dvofaktorske avtentikacije ni mogoče odstraniti, ker je obvezna za vaš račun."
"quarantine_category_invalid": "Kategorija karantene mora biti ena od: add_header, reject, all"
}, },
"debug": { "debug": {
"containers_info": "Informacije o zabojniku", "containers_info": "Informacije o zabojniku",
-1
View File
@@ -65,7 +65,6 @@ if (isset($_GET['app_password'])) {
$attr['protocols'][] = 'dav_access'; $attr['protocols'][] = 'dav_access';
} }
app_passwd("add", $attr); app_passwd("add", $attr);
$password = htmlspecialchars($password, ENT_NOQUOTES);
} else { } else {
$app_password = false; $app_password = false;
} }
+2 -1
View File
@@ -27,7 +27,7 @@ if (isset($_SERVER['PHP_AUTH_USER'])) {
} }
$login_check = check_login($username, $password, array('service' => $service)); $login_check = check_login($username, $password, array('service' => $service));
if ($login_check === 'user') { if ($login_check === 'user' && hasACLAccess('sogo_access')) {
header("X-User: $username"); header("X-User: $username");
header("X-Auth: Basic ".base64_encode("$username:$password")); header("X-Auth: Basic ".base64_encode("$username:$password"));
header("X-Auth-Type: Basic"); header("X-Auth-Type: Basic");
@@ -47,6 +47,7 @@ elseif (isset($_GET['login'])) {
// check permissions (if dual_login is active, deny sso when acl is not given) // check permissions (if dual_login is active, deny sso when acl is not given)
$login = html_entity_decode(rawurldecode($_GET["login"])); $login = html_entity_decode(rawurldecode($_GET["login"]));
if (isset($_SESSION['mailcow_cc_role']) && if (isset($_SESSION['mailcow_cc_role']) &&
hasACLAccess('sogo_access') &&
(($_SESSION['acl']['login_as'] == "1" && $ALLOW_ADMIN_EMAIL_LOGIN !== 0) || ($is_dual === false && $login == $_SESSION['mailcow_cc_username']))) { (($_SESSION['acl']['login_as'] == "1" && $ALLOW_ADMIN_EMAIL_LOGIN !== 0) || ($is_dual === false && $login == $_SESSION['mailcow_cc_username']))) {
if (filter_var($login, FILTER_VALIDATE_EMAIL)) { if (filter_var($login, FILTER_VALIDATE_EMAIL)) {
if (user_get_alias_details($login) !== false) { if (user_get_alias_details($login) !== false) {
+3 -3
View File
@@ -11,8 +11,8 @@
<link rel="stylesheet" href="{{ css_path }}"> <link rel="stylesheet" href="{{ css_path }}">
<script> <script>
// check if darkmode is preferred by OS or set by localStorage // check if darkmode is preferred by OS or set by localStorage
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches && localStorage.getItem("mailcow_theme") !== "light" || if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches && localStorage.getItem("theme") !== "light" ||
localStorage.getItem("mailcow_theme") === "dark") { localStorage.getItem("theme") === "dark") {
var head = document.getElementsByTagName('head')[0]; var head = document.getElementsByTagName('head')[0];
var link = document.createElement('link'); var link = document.createElement('link');
link.id = 'dark-mode-theme'; link.id = 'dark-mode-theme';
@@ -193,7 +193,7 @@ $(window).scroll(function() {
}); });
// Select language and reopen active URL without POST // Select language and reopen active URL without POST
function setLang(sel) { function setLang(sel) {
$.post( '{{ uri|escape("js") }}', {lang: sel} ); $.post( '{{ uri }}', {lang: sel} );
window.location.href = window.location.pathname + window.location.search; window.location.href = window.location.pathname + window.location.search;
} }
// FIDO2 functions // FIDO2 functions
@@ -8,7 +8,7 @@
<input type="hidden" value="default" name="sender_acl"> <input type="hidden" value="default" name="sender_acl">
<input type="hidden" value="0" name="force_pw_update"> <input type="hidden" value="0" name="force_pw_update">
<input type="hidden" value="0" name="sogo_access"> <input type="hidden" value="0" name="sogo_redirection">
<input type="hidden" value="0" name="protocol_access"> <input type="hidden" value="0" name="protocol_access">
<div class="row mb-4"> <div class="row mb-4">
@@ -125,6 +125,7 @@
<option value="syncjobs" {% if template.attributes.acl_syncjobs == '1' %} selected{% endif %}>{{ lang.acl["syncjobs"] }}</option> <option value="syncjobs" {% if template.attributes.acl_syncjobs == '1' %} selected{% endif %}>{{ lang.acl["syncjobs"] }}</option>
<option value="eas_reset" {% if template.attributes.acl_eas_reset == '1' %} selected{% endif %}>{{ lang.acl["eas_reset"] }}</option> <option value="eas_reset" {% if template.attributes.acl_eas_reset == '1' %} selected{% endif %}>{{ lang.acl["eas_reset"] }}</option>
<option value="sogo_profile_reset" {% if template.attributes.acl_sogo_profile_reset == '1' %} selected{% endif %}>{{ lang.acl["sogo_profile_reset"] }}</option> <option value="sogo_profile_reset" {% if template.attributes.acl_sogo_profile_reset == '1' %} selected{% endif %}>{{ lang.acl["sogo_profile_reset"] }}</option>
<option value="sogo_access" {% if template.attributes.acl_sogo_access == '1' %} selected{% endif %}>{{ lang.acl["sogo_access"] }}</option>
<option value="pushover" {% if template.attributes.acl_pushover == '1' %} selected{% endif %}>{{ lang.acl["pushover"] }}</option> <option value="pushover" {% if template.attributes.acl_pushover == '1' %} selected{% endif %}>{{ lang.acl["pushover"] }}</option>
<option value="quarantine" {% if template.attributes.acl_quarantine == '1' %} selected{% endif %}>{{ lang.acl["quarantine"] }}</option> <option value="quarantine" {% if template.attributes.acl_quarantine == '1' %} selected{% endif %}>{{ lang.acl["quarantine"] }}</option>
<option value="quarantine_attachments" {% if template.attributes.acl_quarantine_attachments == '1' %} selected{% endif %}>{{ lang.acl["quarantine_attachments"] }}</option> <option value="quarantine_attachments" {% if template.attributes.acl_quarantine_attachments == '1' %} selected{% endif %}>{{ lang.acl["quarantine_attachments"] }}</option>
@@ -169,8 +170,8 @@
<div class="row"> <div class="row">
<div class="offset-sm-2 col-sm-10"> <div class="offset-sm-2 col-sm-10">
<div class="form-check"> <div class="form-check">
<label><input type="checkbox" class="form-check-input" value="1" name="sogo_access"{% if template.attributes.sogo_access == '1' %} checked{% endif %}> {{ lang.edit.sogo_access }}</label> <label><input type="checkbox" class="form-check-input" value="1" name="sogo_redirection"{% if template.attributes.sogo_redirection == '1' %} checked{% endif %}> {{ lang.edit.sogo_redirection }}</label>
<small class="text-muted">{{ lang.edit.sogo_access_info }}</small> <small class="text-muted">{{ lang.edit.sogo_redirection_info }}</small>
</div> </div>
</div> </div>
</div> </div>
+4 -4
View File
@@ -25,7 +25,7 @@
<input type="hidden" value="default" name="sender_acl"> <input type="hidden" value="default" name="sender_acl">
<input type="hidden" value="0" name="force_pw_update"> <input type="hidden" value="0" name="force_pw_update">
<input type="hidden" value="0" name="force_tfa"> <input type="hidden" value="0" name="force_tfa">
<input type="hidden" value="0" name="sogo_access"> <input type="hidden" value="0" name="sogo_redirection">
<input type="hidden" value="0" name="protocol_access"> <input type="hidden" value="0" name="protocol_access">
<div class="row mb-2"> <div class="row mb-2">
<label class="control-label col-sm-2">{{ lang.admin.iam }}</label> <label class="control-label col-sm-2">{{ lang.admin.iam }}</label>
@@ -327,11 +327,11 @@
</div> </div>
</div> </div>
{% if not skip_sogo %} {% if not skip_sogo %}
<div data-acl="{{ acl.sogo_access }}" class="row"> <div data-acl="{{ acl.sogo_redirection }}" class="row">
<div class="offset-sm-2 col-sm-10"> <div class="offset-sm-2 col-sm-10">
<div class="form-check"> <div class="form-check">
<label><input type="checkbox" class="form-check-input" value="1" name="sogo_access"{% if result.attributes.sogo_access == '1' %} checked{% endif %}> {{ lang.edit.sogo_access }}</label> <label><input type="checkbox" class="form-check-input" value="1" name="sogo_redirection"{% if result.attributes.sogo_redirection == '1' %} checked{% endif %}> {{ lang.edit.sogo_redirection }}</label>
<small class="text-muted">{{ lang.edit.sogo_access_info }}</small> <small class="text-muted">{{ lang.edit.sogo_redirection_info }}</small>
</div> </div>
</div> </div>
</div> </div>
+7 -6
View File
@@ -10,7 +10,7 @@
<form class="form-horizontal" data-cached-form="true" data-id="add_mailbox" role="form" autocomplete="off"> <form class="form-horizontal" data-cached-form="true" data-id="add_mailbox" role="form" autocomplete="off">
<input type="hidden" value="0" name="force_pw_update"> <input type="hidden" value="0" name="force_pw_update">
<input type="hidden" value="0" name="force_tfa"> <input type="hidden" value="0" name="force_tfa">
<input type="hidden" value="0" name="sogo_access"> <input type="hidden" value="0" name="sogo_redirection">
<input type="hidden" value="0" name="protocol_access"> <input type="hidden" value="0" name="protocol_access">
<input type="hidden" value="mailcow" name="authsource"> <input type="hidden" value="mailcow" name="authsource">
@@ -166,6 +166,7 @@
<option value="syncjobs">{{ lang.acl["syncjobs"] }}</option> <option value="syncjobs">{{ lang.acl["syncjobs"] }}</option>
<option value="eas_reset" selected>{{ lang.acl["eas_reset"] }}</option> <option value="eas_reset" selected>{{ lang.acl["eas_reset"] }}</option>
<option value="sogo_profile_reset">{{ lang.acl["sogo_profile_reset"] }}</option> <option value="sogo_profile_reset">{{ lang.acl["sogo_profile_reset"] }}</option>
<option value="sogo_access" selected>{{ lang.acl["sogo_access"] }}</option>
<option value="pushover" selected>{{ lang.acl["pushover"] }}</option> <option value="pushover" selected>{{ lang.acl["pushover"] }}</option>
<option value="quarantine" selected>{{ lang.acl["quarantine"] }}</option> <option value="quarantine" selected>{{ lang.acl["quarantine"] }}</option>
<option value="quarantine_attachments" selected>{{ lang.acl["quarantine_attachments"] }}</option> <option value="quarantine_attachments" selected>{{ lang.acl["quarantine_attachments"] }}</option>
@@ -217,8 +218,8 @@
<div class="row"> <div class="row">
<div class="offset-sm-2 col-sm-10"> <div class="offset-sm-2 col-sm-10">
<div class="form-check"> <div class="form-check">
<label><input type="checkbox" class="form-check-input" value="1" name="sogo_access" id="sogo_access"> {{ lang.edit.sogo_access }}</label> <label><input type="checkbox" class="form-check-input" value="1" name="sogo_redirection" id="sogo_access"> {{ lang.edit.sogo_redirection }}</label>
<small class="text-muted">{{ lang.edit.sogo_access_info }}</small> <small class="text-muted">{{ lang.edit.sogo_redirection_info }}</small>
</div> </div>
</div> </div>
</div> </div>
@@ -247,7 +248,7 @@
<input type="hidden" value="default" name="sender_acl"> <input type="hidden" value="default" name="sender_acl">
<input type="hidden" value="0" name="force_pw_update"> <input type="hidden" value="0" name="force_pw_update">
<input type="hidden" value="0" name="force_tfa"> <input type="hidden" value="0" name="force_tfa">
<input type="hidden" value="0" name="sogo_access"> <input type="hidden" value="0" name="sogo_redirection">
<input type="hidden" value="0" name="protocol_access"> <input type="hidden" value="0" name="protocol_access">
<div class="row mb-4"> <div class="row mb-4">
@@ -416,8 +417,8 @@
<div class="row"> <div class="row">
<div class="offset-sm-2 col-sm-10"> <div class="offset-sm-2 col-sm-10">
<div class="form-check"> <div class="form-check">
<label><input type="checkbox" class="form-check-input" value="1" name="sogo_access"> {{ lang.edit.sogo_access }}</label> <label><input type="checkbox" class="form-check-input" value="1" name="sogo_redirection"> {{ lang.edit.sogo_redirection }}</label>
<small class="text-muted">{{ lang.edit.sogo_access_info }}</small> <small class="text-muted">{{ lang.edit.sogo_redirection_info }}</small>
</div> </div>
</div> </div>
</div> </div>
+5 -1
View File
@@ -23,10 +23,14 @@
<a href="/sogo-auth.php?login={{ mailcow_cc_username }}" role="button" class="btn btn-primary btn-lg btn-block btn-xs-lg w-100"> <a href="/sogo-auth.php?login={{ mailcow_cc_username }}" role="button" class="btn btn-primary btn-lg btn-block btn-xs-lg w-100">
{{ lang.user.open_webmail_sso }} <i class="bi bi-arrow-right"></i> {{ lang.user.open_webmail_sso }} <i class="bi bi-arrow-right"></i>
</a> </a>
{% else %} {% elseif acl.sogo_access == 1 %}
<a href="/SOGo/so" role="button" class="btn btn-primary btn-lg btn-block btn-xs-lg w-100"> <a href="/SOGo/so" role="button" class="btn btn-primary btn-lg btn-block btn-xs-lg w-100">
{{ lang.user.open_webmail_sso }} <i class="bi bi-arrow-right"></i> {{ lang.user.open_webmail_sso }} <i class="bi bi-arrow-right"></i>
</a> </a>
{% else %}
<button disabled class="btn btn-secondary btn-block btn-xs-lg w-100">
{{ lang.user.open_webmail_sso }} <i class="bi bi-arrow-right"></i>
</button>
{% endif %} {% endif %}
</div> </div>
</div> </div>
+2 -2
View File
@@ -252,7 +252,7 @@ services:
- sogo - sogo
dovecot-mailcow: dovecot-mailcow:
image: ghcr.io/mailcow/dovecot:2.3.21.1-2 image: ghcr.io/mailcow/dovecot:2.3.21.1-1
depends_on: depends_on:
- mysql-mailcow - mysql-mailcow
- netfilter-mailcow - netfilter-mailcow
@@ -465,7 +465,7 @@ services:
condition: service_started condition: service_started
unbound-mailcow: unbound-mailcow:
condition: service_healthy condition: service_healthy
image: ghcr.io/mailcow/acme:1.97 image: ghcr.io/mailcow/acme:1.96
dns: dns:
- ${IPV4_NETWORK:-172.22.1}.254 - ${IPV4_NETWORK:-172.22.1}.254
environment: environment: