Compare commits

..
Author SHA1 Message Date
milkmakerandStefano fe4e675754 [Web] Updated lang.it-it.json (#7460)
Co-authored-by: Stefano <stefano.vassena@gmail.com>
2026-09-09 22:28:28 +02:00
b05f59b705 Translations update from Weblate (#7457)
* [Web] Updated lang.si-si.json

Co-authored-by: Matjaž Tekavec <matjaz@moj-svet.si>
Co-authored-by: milkmaker <milkmaker@mailcow.de>

* [Web] Updated lang.zh-cn.json

Co-authored-by: 雨 <luotianyi@luotianyi.me>

---------

Co-authored-by: Matjaž Tekavec <matjaz@moj-svet.si>
Co-authored-by: 雨 <luotianyi@luotianyi.me>
2026-09-07 21:38:12 +02:00
milkmaker c4d0ca56d2 update postscreen_access.cidr (#7450) 2026-09-01 13:07:54 +02:00
7ec61fde8a Translations update from Weblate (#7431)
* [Web] Updated lang.it-it.json

Co-authored-by: Claudio <cb@bastard.it>

* [Web] Updated lang.si-si.json

Co-authored-by: Matjaž Tekavec <matjaz@moj-svet.si>

---------

Co-authored-by: Claudio <cb@bastard.it>
Co-authored-by: Matjaž Tekavec <matjaz@moj-svet.si>
2026-08-19 20:23:04 +02:00
FreddleSpl0it d80b15b06c Merge pull request #7376 from smpaz7467/fix/extended-sender-acl-authsource
[Web] show external sender addresses regardless of authsource
2026-08-18 14:41:20 +02:00
FreddleSpl0it ed4fe41d7a Merge pull request #7349 from smpaz7467/fix/login-redirect-deeplink
[Web] redirect deep links to the matching login page
2026-08-18 14:37:32 +02:00
FreddleSpl0it 6b9d875773 Merge pull request #7347 from smpaz7467/fix/dns-zonefile-absolute-rhs
[Web] use absolute RHS names in generated DNS zonefile
2026-08-18 14:30:50 +02:00
FreddleSpl0it 4d5b9d1c80 Merge pull request #7342 from FrauJulian/feat/show-password
[Web] Add show/hide password toggle on login pages
2026-08-18 14:25:22 +02:00
FreddleSpl0it 8abf453e1c Merge pull request #7428 from mailcow/feat/forbid-external-alias
[Web] Add ACL + global switch to disable external alias goto
2026-08-18 14:13:39 +02:00
FreddleSpl0it 8bd9e12a73 [Web] Add ACL + global switch to disable external alias goto 2026-08-18 14:07:37 +02:00
FreddleSpl0it c665d430de Merge pull request #7344 from smpaz7467/fix/forced-pw-change-raw-lang-key
[Web] translate password errors in the forced password change modal
2026-08-18 13:31:35 +02:00
FreddleSpl0it 2195a674ff Merge pull request #7290 from wryfi/7150-fix-passwd-verify
Fix #7150: passwd-verify.lua wipes auth cache during any nginx outage
2026-08-18 13:27:01 +02:00
FreddleSpl0it 089c44aee1 Merge pull request #7334 from DerLinkman/feat/postfix-tlspol-update
postfix-tlspol: upgrade to trixie + 1.11.0 update
2026-08-18 13:20:16 +02:00
FreddleSpl0it 6644699e65 Merge pull request #7426 from mailcow/fix/web-hardening
[Web] Minor hardening across web UI and nginx
2026-08-18 09:10:09 +02:00
FreddleSpl0it 91d3b7246a [Web] Minor hardening across web UI and nginx 2026-08-18 09:08:17 +02:00
FreddleSpl0it b362c7105d Merge pull request #7425 from mailcow/feat/redis-7.4.10
[Redis] Update to 7.4.10
2026-08-18 08:14:21 +02:00
FreddleSpl0it 555e7ed706 [Redis] Update to 7.4.10 2026-08-18 08:12:32 +02:00
FreddleSpl0it 59e2a9ac77 Merge pull request #7423 from mailcow/fix/7418
[Dovecot] Remove legacy DeltaChat auto-filing sieve rule
2026-08-17 08:41:18 +02:00
FreddleSpl0it feed5ad183 [Dovecot] Remove legacy DeltaChat auto-filing sieve rule 2026-08-17 08:35:53 +02:00
FreddleSpl0it f1f7a9800b Merge pull request #7422 from mailcow/feat/sogo-5.12.10
[SOGo] Update to 5.12.10
2026-08-17 08:24:10 +02:00
FreddleSpl0it 8c85044781 [SOGo] Update to 5.12.10 2026-08-17 08:20:45 +02:00
FreddleSpl0it 8fb32e4b59 Merge pull request #7415 from mailcow/feat/clamd-1.4.6
[Clamd] Update to 1.4.6
2026-08-13 13:59:13 +02:00
FreddleSpl0it b5fa926bc0 [Clamd] Update to 1.4.6 2026-08-13 13:56:49 +02:00
d1a2f4e168 Translations update from Weblate (#7400)
* [Web] Updated lang.si-si.json

Co-authored-by: Matjaž Tekavec <matjaz@moj-svet.si>
Co-authored-by: milkmaker <milkmaker@mailcow.de>

* [Web] Updated lang.pt-br.json

Co-authored-by: André Glazastov <andre@glazastov.com>

---------

Co-authored-by: Matjaž Tekavec <matjaz@moj-svet.si>
Co-authored-by: André Glazastov <andre@glazastov.com>
2026-08-04 18:37:38 +02:00
milkmaker f7a536d634 update postscreen_access.cidr (#7394) 2026-08-04 18:35:48 +02:00
Maximal Benedikt 8b456a33e7 Merge pull request #7392 from mailcow/update-sponsoring
update README.md sponsors
2026-07-30 14:21:39 +02:00
Stephen Ritz 9cd16f0001 [Web] show external sender addresses regardless of authsource
The extended_sender_acl field sat inside the
`{% if not result.authsource or result.authsource == 'mailcow' %}` block
that hides the local password fields for mailboxes authenticating against
an external identity provider. As a result the "External sender addresses"
input was not rendered at all for keycloak / generic-oidc / ldap mailboxes,
even for a full admin, so those addresses could neither be reviewed nor
edited in the UI while the underlying sender_acl rows stayed active.

Extended sender ACLs are unrelated to local password management. Move the
field out of that block; it stays gated by acl.extend_sender_acl as intended.

Fixes #7365
2026-07-28 07:12:17 -07:00
Stephen RitzandClaude Opus 4.8 90ca1bf25a [Web] redirect deep links to the matching login page
An unauthenticated request to a deep link such as /admin/dashboard was
redirected to /, the user login, instead of the admin login. protect_route
always sent unauthenticated visitors to /.

Pick the login page from the request path: /admin/* redirects to /admin,
/domainadmin/* to /domainadmin, everything else to / as before.

Fixes #7284

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 16:39:19 -07:00
Stephen RitzandClaude Opus 4.8 e5d0ed8c4c [Web] use absolute RHS names in generated DNS zonefile
The DNS overview "Download" produces a $ORIGIN zonefile, but the
right-hand side of MX, CNAME and SRV records was emitted as a relative
name. A target such as mail.example.net is then read relative to the
origin and expands to mail.example.net.example.org., which is wrong.

The only prior attempt at making names absolute was
str_replace($domain, $domain . '.', ...), which appended a dot only to
targets that happened to contain the origin domain, so cross-domain
targets stayed relative. That same replace also corrupted any TXT value
containing the origin (e.g. a DMARC rua=mailto:x@example.org became
...@example.org.).

Absolutize the RHS per record type at export time only: MX and CNAME
targets, and the SRV target token, get a trailing dot; ports, the SRV
root target ".", IP addresses and TXT character strings are left as is.
The records used for the on-page DNS validation are untouched, so
matching against dns_get_record() output still works.

Fixes #6984

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 16:33:46 -07:00
Stephen RitzandClaude Opus 4.8 4cba448671 [Web] translate password errors in the forced password change modal
The forced password change modal posts to the JSON API and renders
data[0].msg directly. The API returns raw language keys rather than
translated strings, so a user who fails the complexity policy is shown
the literal text "password_complexity" instead of a message.

Every other password form renders errors through alertbox_log_parser(),
which resolves the key against $lang. The modal is the only one that
talks to the API directly, and it never resolved the key.

Resolve it against lang_danger, which base.twig already exposes for this
purpose. This also covers password_mismatch, password_empty and
access_denied, and applies to the admin path as well. Unknown keys still
fall through unchanged.

The lookup is guarded with hasOwnProperty because msg is attacker-
independent but dynamic: a bare lang_danger[msg] would resolve inherited
Object.prototype members such as "constructor" to a function, which
jQuery's .text() would then invoke as a callback.

Language files are untouched: prerequisites.inc.php loads lang.en-gb.json
as the base and merges the active locale over it, so locales that lack the
key inherit the English string.

Fixes #7301

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 15:11:17 -07:00
Paul Seebach 5408bc809d [Web] Use input-group-text for password toggle button
Replaces btn-outline-secondary with input-group-text so the toggle
button border matches the surrounding input group consistently.
2026-07-14 21:20:58 +02:00
Paul Seebach bd5c18b145 [Web] Add show/hide password toggle on login pages
Adds an eye icon button to the password field on the user, admin, and
domain admin login pages. Clicking it toggles between hidden and visible
password text, using Bootstrap Icons (bi-eye / bi-eye-slash).

Closes #6893
2026-07-14 21:20:58 +02:00
DerLinkman 0f06c8e563 postfix: prepare TLSRPT compatibility for tlspol 2026-07-13 19:34:58 +02:00
DerLinkman fffe1aec3d postfix-tlspol: upgrade to trixie + 1.11.0 update 2026-07-13 19:16:10 +02:00
Chris Haumesser ddcce811c1 Fixes #7150 (Dovecot passwd-verify.lua wipes auth cache during any nginx restart or unavailability)
- distinguishes between text and numeric responses from nginx
  - prevents auth cache from being invalidated due to nginx unavailability
2026-06-12 16:32:22 -06:00
29 changed files with 251 additions and 91 deletions
+3 -3
View File
@@ -1,7 +1,7 @@
FROM alpine:3.21 AS builder
FROM alpine:3.24 AS builder
WORKDIR /src
ENV CLAMD_VERSION=1.4.2
ENV CLAMD_VERSION=1.4.6
RUN apk upgrade --no-cache \
&& apk add --update --no-cache \
@@ -68,7 +68,7 @@ RUN wget -P /src https://www.clamav.net/downloads/production/clamav-${CLAMD_VERS
"/clamav/etc/clamav/clamav-milter.conf.sample" > "/clamav/etc/clamav/clamav-milter.conf" || exit 1
FROM alpine:3.21
FROM alpine:3.24
LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"
+3 -3
View File
@@ -1,17 +1,17 @@
FROM golang:1.25-bookworm AS builder
FROM golang:1.26-trixie AS builder
WORKDIR /src
ENV CGO_ENABLED=0 \
GO111MODULE=on \
NOOPT=1 \
VERSION=1.8.22
VERSION=1.11.0
RUN git clone --branch v${VERSION} https://github.com/Zuplu/postfix-tlspol && \
cd /src/postfix-tlspol && \
scripts/build.sh build-only
FROM debian:bookworm-slim
FROM debian:trixie-slim
LABEL maintainer="The Infrastructure Company GmbH <info@servercow.de>"
ARG DEBIAN_FRONTEND=noninteractive
@@ -1,4 +1,4 @@
@version: 3.38
@version: 4.8
@include "scl.conf"
options {
chain_hostnames(off);
@@ -7,7 +7,7 @@ options {
dns_cache(no);
use_fqdn(no);
owner("root"); group("adm"); perm(0640);
stats_freq(0);
stats(freq(0));
bad_hostname("^gconfd$");
};
source s_src {
@@ -1,4 +1,4 @@
@version: 3.38
@version: 4.8
@include "scl.conf"
options {
chain_hostnames(off);
@@ -7,7 +7,7 @@ options {
dns_cache(no);
use_fqdn(no);
owner("root"); group("adm"); perm(0640);
stats_freq(0);
stats(freq(0));
bad_hostname("^gconfd$");
};
source s_src {
+2 -2
View File
@@ -12,8 +12,8 @@ FROM debian:bookworm
LABEL maintainer="The Infrastructure Company GmbH <info@servercow.de>"
ARG DEBIAN_FRONTEND=noninteractive
ARG SOGO_VERSION=SOGo-5.12.9
ARG SOPE_VERSION=SOPE-5.12.9
ARG SOGO_VERSION=SOGo-5.12.10
ARG SOPE_VERSION=SOPE-5.12.10
# Security patches to apply (space-separated commit hashes)
ARG SOGO_SECURITY_PATCHES=""
# renovate: datasource=github-releases depName=tianon/gosu versioning=semver-coerced extractVersion=^(?<version>.*)$
+10 -1
View File
@@ -32,8 +32,17 @@ function auth_password_verify(request, password)
-- Returning PASSDB_RESULT_PASSWORD_MISMATCH will reset the user's auth cache entry.
-- Returning PASSDB_RESULT_INTERNAL_FAILURE keeps the existing cache entry,
-- even if the TTL has expired. Useful to avoid cache eviction during backend issues.
-- On a network-level failure (nginx unreachable, DNS failure, timeout) https.request
-- returns nil plus an error string, so c is not a numeric HTTP status code. Treat this
-- as a backend outage and keep the cache entry, rather than wiping it as a mismatch.
if type(c) ~= "number" then
dovecot.i_info("HTTP request to auth backend failed with " .. tostring(c) .. " for user " .. request.user)
return dovecot.auth.PASSDB_RESULT_INTERNAL_FAILURE, "Upstream unreachable"
end
if c ~= 200 and c ~= 401 then
dovecot.i_info("HTTP request failed with " .. c .. " for user " .. request.user)
dovecot.i_info("HTTP request failed with " .. tostring(c) .. " for user " .. request.user)
return dovecot.auth.PASSDB_RESULT_PASSWORD_MISMATCH, "Upstream error"
end
-10
View File
@@ -1,13 +1,3 @@
# global_sieve_before script
# global_sieve_before -> user sieve_before (mailcow UI) -> user sieve_after (mailcow UI) -> global_sieve_after
require ["mailbox", "fileinto"];
if header :contains ["Chat-Version"] [""] {
if mailboxexists "DeltaChat" {
fileinto "DeltaChat";
} else {
fileinto :create "DeltaChat";
}
stop;
}
@@ -116,6 +116,7 @@ location ~ \.php$ {
include /etc/nginx/fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_param HTTP_X_REAL_IP $remote_addr;
# trusted internal-auth marker; empty for external clients (see nginx.conf map)
fastcgi_param SOGO_AUTH_INTERNAL $sogo_auth_internal;
fastcgi_read_timeout 3600;
+1 -1
View File
@@ -150,7 +150,7 @@ smtp_sasl_auth_enable = yes
smtp_sasl_password_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_sasl_passwd_maps_sender_dependent.cf
smtp_sasl_security_options =
smtp_sasl_mechanism_filter = plain, login
smtp_tls_policy_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_tls_policy_override_maps.cf socketmap:inet:postfix-tlspol:8642:QUERY
smtp_tls_policy_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_tls_policy_override_maps.cf socketmap:inet:postfix-tlspol:8642:QUERYwithTLSRPT
smtp_header_checks = pcre:/opt/postfix/conf/anonymize_headers.pcre
mail_name = Postcow
# local_transport map catches local destinations and prevents routing local dests when the next map would route "*"
+82 -35
View File
@@ -1,6 +1,6 @@
# Whitelist generated by Postwhite v3.4 on Wed Jul 1 00:51:20 UTC 2026
# Whitelist generated by Postwhite v3.4 on Tue Sep 1 00:34:07 UTC 2026
# https://github.com/stevejenkins/postwhite/
# 2251 total rules
# 2243 total rules
2a00:1450:4000::/36 permit
2a00:1450:4864::/56 permit
2a01:111:f400::/48 permit
@@ -29,6 +29,7 @@
2a01:b747:3004:200::/56 permit
2a01:b747:3005:200::/56 permit
2a01:b747:3006:200::/56 permit
2a01:b747:3007::/56 permit
2a02:a60:0:5::/64 permit
2a0f:f640::/56 permit
2c0f:fb50:4000::/36 permit
@@ -56,7 +57,14 @@
8.25.194.0/23 permit
8.25.196.0/23 permit
8.36.116.0/24 permit
8.39.54.0/23 permit
8.39.54.240/29 permit
8.39.54.250/31 permit
8.39.144.0/24 permit
8.40.222.0/23 permit
8.40.222.240/29 permit
8.40.222.250/31 permit
8.47.10.9 permit
12.130.86.238 permit
13.108.16.0/20 permit
13.110.208.0/21 permit
@@ -67,6 +75,7 @@
13.111.191.0/24 permit
13.216.7.111 permit
13.216.54.180 permit
13.247.164.219 permit
15.200.21.50 permit
15.200.44.248 permit
15.200.201.185 permit
@@ -116,7 +125,6 @@
23.249.208.0/20 permit
23.251.224.0/19 permit
23.253.141.0/24 permit
23.253.182.0/23 permit
23.253.182.103 permit
23.253.183.145 permit
23.253.183.146 permit
@@ -138,24 +146,7 @@
27.123.206.80/28 permit
31.47.251.17 permit
31.186.239.0/24 permit
34.2.64.0/22 permit
34.2.68.0/23 permit
34.2.70.0/23 permit
34.2.71.64/26 permit
34.2.72.0/22 permit
34.2.75.0/26 permit
34.2.78.0/23 permit
34.2.80.0/23 permit
34.2.82.0/23 permit
34.2.84.0/24 permit
34.2.84.64/26 permit
34.2.85.0/24 permit
34.2.85.64/26 permit
34.2.86.0/23 permit
34.2.88.0/23 permit
34.2.90.0/23 permit
34.2.92.0/23 permit
34.2.94.0/23 permit
34.2.64.0/19 permit
34.70.158.162 permit
34.74.74.140 permit
34.83.159.189 permit
@@ -170,6 +161,7 @@
34.215.104.144 permit
34.218.115.239 permit
34.225.212.172 permit
34.241.242.183 permit
35.83.148.184 permit
35.155.198.111 permit
35.158.23.94 permit
@@ -181,6 +173,7 @@
35.205.92.9 permit
35.228.216.85 permit
35.242.169.159 permit
37.187.220.204 permit
37.188.97.188 permit
37.218.248.47 permit
37.218.249.47 permit
@@ -193,6 +186,7 @@
40.233.64.216 permit
40.233.83.78 permit
40.233.88.28 permit
43.239.212.33 permit
44.206.138.57 permit
44.210.169.44 permit
44.217.45.156 permit
@@ -253,8 +247,8 @@
46.228.39.112/31 permit
46.243.88.174 permit
46.243.88.175 permit
46.243.88.176 permit
46.243.88.177 permit
46.243.88.176/30 permit
46.243.95.96/27 permit
46.243.95.179 permit
46.243.95.180 permit
50.16.246.183 permit
@@ -272,9 +266,11 @@
50.56.130.221 permit
50.56.130.222 permit
50.112.246.219 permit
50.152.146.25 permit
51.83.17.38 permit
52.1.14.157 permit
52.5.230.59 permit
52.6.74.205 permit
52.12.53.23 permit
52.13.214.179 permit
52.26.1.71 permit
@@ -331,6 +327,7 @@
54.244.54.130 permit
54.244.242.0/24 permit
54.255.61.23 permit
56.124.6.228 permit
57.103.64.0/18 permit
57.129.93.249 permit
62.13.128.0/24 permit
@@ -397,6 +394,7 @@
65.110.161.77 permit
65.123.29.213 permit
65.123.29.220 permit
65.154.166.0/24 permit
65.212.180.36 permit
66.102.0.0/20 permit
66.119.150.192/26 permit
@@ -640,6 +638,7 @@
79.135.106.0/24 permit
79.135.107.0/24 permit
80.225.160.128/25 permit
81.88.38.0/23 permit
81.169.146.243 permit
81.169.146.245 permit
81.169.146.246 permit
@@ -724,6 +723,7 @@
96.43.148.64/28 permit
96.43.148.64/31 permit
96.43.151.64/28 permit
98.77.0.0/16 permit
98.97.248.0/21 permit
98.136.44.181 permit
98.136.44.182/31 permit
@@ -1210,14 +1210,15 @@
99.78.197.208/28 permit
103.9.96.0/22 permit
103.28.42.0/24 permit
103.84.217.15 permit
103.84.217.238 permit
103.89.75.238 permit
103.151.192.0/23 permit
103.168.172.128/27 permit
103.237.104.0/22 permit
104.43.243.237 permit
104.44.112.128/25 permit
104.47.0.0/17 permit
104.130.96.0/28 permit
104.130.122.0/23 permit
106.10.144.64/27 permit
106.10.144.100/31 permit
106.10.144.103 permit
@@ -1370,6 +1371,10 @@
117.120.16.0/21 permit
119.42.242.52/31 permit
119.42.242.156 permit
121.244.91.31 permit
121.244.91.48 permit
121.244.91.52 permit
122.15.156.182 permit
123.126.78.64/29 permit
124.108.96.24/31 permit
124.108.96.28/31 permit
@@ -1435,6 +1440,7 @@
130.35.116.0/25 permit
130.61.9.72 permit
130.162.39.83 permit
130.162.194.23 permit
130.248.172.0/24 permit
130.248.173.0/24 permit
131.186.12.0/25 permit
@@ -1452,7 +1458,21 @@
134.170.141.64/26 permit
134.170.143.0/24 permit
134.170.174.0/24 permit
135.84.80.0/24 permit
135.84.81.0/24 permit
135.84.82.0/24 permit
135.84.83.0/24 permit
135.84.216.0/22 permit
136.143.160.0/24 permit
136.143.161.0/24 permit
136.143.162.0/24 permit
136.143.176.0/24 permit
136.143.177.0/24 permit
136.143.178.49 permit
136.143.182.0/23 permit
136.143.184.0/24 permit
136.143.188.0/24 permit
136.143.190.0/23 permit
136.146.128.0/20 permit
136.147.128.0/20 permit
136.147.135.0/24 permit
@@ -1472,6 +1492,7 @@
139.138.46.219 permit
139.138.57.55 permit
139.138.58.119 permit
139.167.79.86 permit
139.177.108.0/25 permit
139.180.17.0/24 permit
140.238.148.191 permit
@@ -1503,9 +1524,6 @@
146.20.14.105 permit
146.20.14.106 permit
146.20.14.107 permit
146.20.112.0/26 permit
146.20.113.0/24 permit
146.20.191.0/24 permit
146.20.215.0/24 permit
146.20.215.182 permit
146.88.28.0/24 permit
@@ -1532,6 +1550,7 @@
150.230.98.160 permit
151.145.38.14 permit
152.67.105.195 permit
152.69.165.251 permit
152.69.200.236 permit
152.70.155.126 permit
155.248.135.128/25 permit
@@ -1553,6 +1572,7 @@
158.247.16.0/20 permit
158.247.100.0/25 permit
159.13.4.0/25 permit
159.13.33.181 permit
159.92.154.0/24 permit
159.92.155.0/24 permit
159.92.157.0/24 permit
@@ -1580,6 +1600,8 @@
159.183.121.182 permit
159.183.129.172 permit
160.1.62.192 permit
161.33.94.110 permit
161.33.228.244 permit
161.38.192.0/20 permit
161.38.204.0/22 permit
161.71.32.0/19 permit
@@ -1611,7 +1633,6 @@
165.173.182.0/24 permit
165.173.182.250/31 permit
165.173.189.205 permit
166.78.68.0/22 permit
166.78.68.221 permit
166.78.69.169 permit
166.78.69.170 permit
@@ -1643,6 +1664,19 @@
168.245.12.252 permit
168.245.46.9 permit
168.245.127.231 permit
169.148.129.0/24 permit
169.148.131.0/24 permit
169.148.138.0/24 permit
169.148.142.10 permit
169.148.142.33 permit
169.148.144.0/25 permit
169.148.144.10 permit
169.148.146.0/23 permit
169.148.174.10 permit
169.148.175.3 permit
169.148.179.3 permit
169.148.188.0/24 permit
169.148.188.182 permit
170.9.232.254 permit
170.10.128.0/24 permit
170.10.129.0/24 permit
@@ -1753,9 +1787,9 @@
188.125.85.234/31 permit
188.125.85.236/31 permit
188.125.85.238 permit
188.165.51.139 permit
188.172.128.0/20 permit
192.0.64.0/18 permit
192.9.166.184 permit
192.18.139.154 permit
192.18.145.36 permit
192.18.152.58 permit
@@ -1780,7 +1814,6 @@
192.30.252.0/22 permit
192.161.144.0/20 permit
192.162.87.0/24 permit
192.237.158.0/23 permit
192.237.159.42 permit
192.237.159.43 permit
192.254.112.0/20 permit
@@ -1823,7 +1856,6 @@
198.21.0.0/21 permit
198.37.144.0/20 permit
198.37.152.186 permit
198.61.254.0/23 permit
198.61.254.21 permit
198.61.254.231 permit
198.178.234.57 permit
@@ -1849,7 +1881,16 @@
199.16.156.0/22 permit
199.33.145.1 permit
199.33.145.32 permit
199.34.22.36 permit
199.59.148.0/22 permit
199.67.80.2 permit
199.67.80.20 permit
199.67.82.2 permit
199.67.82.20 permit
199.67.84.0/24 permit
199.67.86.0/24 permit
199.67.88.0/24 permit
199.67.90.0/24 permit
199.101.161.130 permit
199.101.162.0/25 permit
199.122.120.0/21 permit
@@ -1901,12 +1942,17 @@
204.14.232.0/21 permit
204.14.232.64/28 permit
204.14.234.64/28 permit
204.75.18.128/27 permit
204.75.142.0/24 permit
204.92.114.187 permit
204.92.114.203 permit
204.92.114.204/31 permit
204.141.32.0/23 permit
204.141.42.0/23 permit
204.216.164.202 permit
204.220.160.0/21 permit
204.220.90.0/23 permit
204.220.92.0/22 permit
204.220.160.0/20 permit
204.220.168.0/21 permit
204.220.176.0/20 permit
204.220.181.105 permit
@@ -1942,6 +1988,8 @@
207.211.31.0/25 permit
207.211.41.113 permit
207.211.132.0/25 permit
207.211.147.87 permit
207.211.159.110 permit
207.218.90.0/24 permit
207.218.90.122 permit
207.250.68.0/24 permit
@@ -1991,7 +2039,6 @@
209.43.22.0/28 permit
209.46.117.168 permit
209.46.117.179 permit
209.61.151.0/24 permit
209.61.151.236 permit
209.61.151.249 permit
209.61.151.251 permit
@@ -2178,7 +2225,7 @@
2001:748:400:3301::4 permit
2404:6800:4000::/36 permit
2404:6800:4864::/56 permit
2603:1061:14:1c2::1 permit
2603:1061:14:102::1 permit
2607:13c0:0001:0000:0000:0000:0000:7000/116 permit
2607:13c0:0002:0000:0000:0000:0000:1000/116 permit
2607:13c0:0004:0000:0000:0000:0000:0000/116 permit
+27 -7
View File
@@ -380,13 +380,13 @@ if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "adm
else {
$state = state_nomatch;
}
$state .= '<br />' . $current[$data_field[$current['type']]];
$state .= '<br />' . htmlspecialchars($current[$data_field[$current['type']]]);
}
if ($current['type'] == 'TXT' &&
stripos($current['txt'], 'v=dmarc') === 0 &&
$record[2] == $dmarc_link) {
$current['txt'] = str_replace(' ', '', $current['txt']);
$state = $current[$data_field[$current['type']]] . state_optional;
$state = htmlspecialchars($current[$data_field[$current['type']]]) . state_optional;
}
elseif ($current['type'] == 'TXT' &&
stripos($current['txt'], 'v=spf') === 0 &&
@@ -396,7 +396,7 @@ if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "adm
if (in_array($ip, $rslt) && in_array(expand_ipv6($ip6), $rslt)) {
$state = state_good;
}
$state .= '<br />' . $current[$data_field[$current['type']]] . state_optional;
$state .= '<br />' . htmlspecialchars($current[$data_field[$current['type']]]) . state_optional;
}
elseif ($current['type'] == 'TXT' &&
stripos($current['txt'], 'v=dkim') === 0 &&
@@ -426,7 +426,7 @@ if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "adm
if ($state == state_nomatch) {
$state = array();
foreach ($currents as $current) {
$state[] = $current[$data_field[$current['type']]];
$state[] = htmlspecialchars($current[$data_field[$current['type']]]);
}
$state = implode('<br />', $state);
}
@@ -436,12 +436,22 @@ if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "adm
<td>%s</td>
<td class="dns-found">%s</td>
<td class="dns-recommended">%s</td>
</tr>', $record[0], $record[1], $record[2], $state);
</tr>', htmlspecialchars($record[0]), htmlspecialchars($record[1]), $record[2], $state);
$record[3] = explode('<br />', $state);
}
unset($record);
// Make a hostname RHS absolute so it is not read relative to $ORIGIN.
// Already-absolute names, the SRV root target "." and IP addresses are left alone.
$absolutize = function($host) {
$host = trim($host);
if ($host === '' || $host === '.' || substr($host, -1) === '.' || filter_var($host, FILTER_VALIDATE_IP)) {
return $host;
}
return $host . '.';
};
$dns_data = sprintf("\$ORIGIN %s.\n", $domain);
foreach ($records as $record) {
if ($domain == substr($record[0], -strlen($domain))) {
@@ -462,22 +472,32 @@ if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "adm
$val = str_replace(state_optional, '', $val);
$val = str_replace(state_good, '', $val);
if (strlen($val) > 0) {
// these are all TXT values, their RHS is a character string, not a name
$vals[] = sprintf("%s\tIN\t%s\t%s\n", $label, $record[1], $val);
}
}
}
else {
if ($record[1] == 'MX' || $record[1] == 'CNAME') {
$val = $absolutize($val);
}
elseif ($record[1] == 'SRV') {
// format here is "target port"; only the target is a name
$parts = explode(' ', $val, 2);
$parts[0] = $absolutize($parts[0]);
$val = implode(' ', $parts);
}
$vals[] = sprintf("%s\tIN\t%s\t%s\n", $label, $record[1], $val);
}
foreach ($vals as $val) {
$dns_data .= str_replace($domain, $domain . '.', $val);
$dns_data .= $val;
}
}
}
?>
</table>
<a id='download-zonefile' class="btn btn-sm btn-secondary visible-xs-block visible-sm-inline visible-md-inline visible-lg-inline mb-4" style="margin-top:10px" data-zonefile="<?=base64_encode($dns_data);?>" download='<?=$_GET['domain'];?>.txt' type='text/csv'>Download</a>
<a id='download-zonefile' class="btn btn-sm btn-secondary visible-xs-block visible-sm-inline visible-md-inline visible-lg-inline mb-4" style="margin-top:10px" data-zonefile="<?=base64_encode($dns_data);?>" download='<?=htmlspecialchars($_GET['domain']);?>.txt' type='text/csv'>Download</a>
<script>
var zonefile_dl_link = document.getElementById('download-zonefile');
var zonefile = atob(zonefile_dl_link.getAttribute('data-zonefile'));
+23 -2
View File
@@ -591,7 +591,7 @@ function logger($_data = false) {
$type = $return['type'];
$msg = null;
if (isset($return['msg'])) {
$msg = json_encode($return['msg'], JSON_UNESCAPED_UNICODE);
$msg = json_encode($return['msg'], JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT);
}
$call = null;
if (isset($return['log'])) {
@@ -633,6 +633,18 @@ function logger($_data = false) {
return true;
}
}
function is_local_mailcow_domain($domain) {
// True if domain is a locally managed, active primary or alias domain
global $pdo;
$domain = idn_to_ascii($domain, 0, INTL_IDNA_VARIANT_UTS46);
if (empty($domain)) {
return false;
}
$stmt = $pdo->prepare("SELECT 1 FROM `domain` WHERE `domain` = :d AND `active` = 1
UNION SELECT 1 FROM `alias_domain` WHERE `alias_domain` = :d2 AND `active` = 1 LIMIT 1");
$stmt->execute(array(':d' => $domain, ':d2' => $domain));
return (bool)$stmt->fetchColumn();
}
function hasDomainAccess($username, $role, $domain) {
global $pdo;
if (empty($domain) || !is_valid_domain_name($domain)) {
@@ -3551,7 +3563,16 @@ function protect_route($allowed_roles = ['admin', 'domainadmin', 'user'], $redir
if (isset($redirects['unauthenticated'])) {
header('Location: ' . $redirects['unauthenticated']);
} else {
header('Location: /');
// Send a deep link to the login page for its area instead of the user login at /,
// e.g. /admin/dashboard -> /admin rather than /
$request_uri = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '/';
if (strpos($request_uri, '/admin/') === 0) {
header('Location: /admin');
} elseif (strpos($request_uri, '/domainadmin/') === 0) {
header('Location: /domainadmin');
} else {
header('Location: /');
}
}
exit();
}
+25
View File
@@ -750,6 +750,18 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$goto_domain = idn_to_ascii(substr(strstr($goto, '@'), 1), 0, INTL_IDNA_VARIANT_UTS46);
$goto_local_part = strstr($goto, '@', true);
$goto = $goto_local_part.'@'.$goto_domain;
// Deny external goto domains: global switch (all roles) overrides the per-DA ACL
if (($GLOBALS['ALIAS_DISABLE_EXTERNAL_DOMAINS'] === true ||
(isset($_SESSION['acl']['alias_external_goto']) && $_SESSION['acl']['alias_external_goto'] != "1")) &&
!is_local_mailcow_domain($goto_domain)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('external_goto_denied', htmlspecialchars($goto))
);
unset($gotos[$i]);
continue;
}
$stmt = $pdo->prepare("SELECT `username` FROM `mailbox`
WHERE `kind` REGEXP 'location|thing|group'
AND `username`= :goto");
@@ -2715,6 +2727,19 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
unset($gotos[$i]);
continue;
}
// Deny external goto domains: global switch (all roles) overrides the per-DA ACL
$goto_domain = idn_to_ascii(substr(strstr($goto, '@'), 1), 0, INTL_IDNA_VARIANT_UTS46);
if (($GLOBALS['ALIAS_DISABLE_EXTERNAL_DOMAINS'] === true ||
(isset($_SESSION['acl']['alias_external_goto']) && $_SESSION['acl']['alias_external_goto'] != "1")) &&
!is_local_mailcow_domain($goto_domain)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('external_goto_denied', htmlspecialchars($goto))
);
unset($gotos[$i]);
continue;
}
if ($goto == $address) {
$_SESSION['return'][] = array(
'type' => 'danger',
+3 -2
View File
@@ -4,7 +4,7 @@ function init_db_schema()
try {
global $pdo;
$db_version = "19022026_1220";
$db_version = "18082026_1200";
$stmt = $pdo->query("SHOW TABLES LIKE 'versions'");
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
@@ -719,7 +719,8 @@ function init_db_schema()
"alias_domains" => "TINYINT(1) NOT NULL DEFAULT '0'",
"mailbox_relayhost" => "TINYINT(1) NOT NULL DEFAULT '1'",
"domain_relayhost" => "TINYINT(1) NOT NULL DEFAULT '1'",
"domain_desc" => "TINYINT(1) NOT NULL DEFAULT '0'"
"domain_desc" => "TINYINT(1) NOT NULL DEFAULT '0'",
"alias_external_goto" => "TINYINT(1) NOT NULL DEFAULT '1'"
),
"keys" => array(
"primary" => array(
+4
View File
@@ -246,6 +246,10 @@ $PW_RESET_TOKEN_LIMIT = 3;
// Maximum time in minutes a password reset token is valid
$PW_RESET_TOKEN_LIFETIME = 15;
// Globally forbid aliases with external (non-local) goto domains for ALL roles (incl. admins),
// overriding the per-domain-admin da_acl. false = defer to da_acl.
$ALIAS_DISABLE_EXTERNAL_DOMAINS = false;
// UV flag handling in FIDO2/WebAuthn - defaults to false to allow iOS logins
// true = required
// false = preferred
+2
View File
@@ -1169,6 +1169,7 @@ jQuery(function($){
if (item === null) { return true; }
item.user = escapeHtml(item.user);
item.call = escapeHtml(item.call);
if (item.msg) item.msg = escapeHtml(item.msg);
item.task = '<code>' + item.task + '</code>';
item.type = '<span class="badge fs-6 bg-' + item.type + '">' + item.type + '</span>';
});
@@ -1176,6 +1177,7 @@ jQuery(function($){
$.each(data, function (i, item) {
if (item === null) { return true; }
item.username = escapeHtml(item.username);
item.real_rip = escapeHtml(item.real_rip);
item.service = '<div class="badge fs-6 bg-secondary">' + item.service.toUpperCase() + '</div>';
});
} else if (table == 'general_syslog') {
+2
View File
@@ -1,6 +1,7 @@
{
"acl": {
"alias_domains": "Alias-Domains hinzufügen",
"alias_external_goto": "Aliase mit externen Ziel-Domains erlauben",
"app_passwds": "App-Passwörter verwalten",
"bcc_maps": "BCC-Maps",
"delimiter_action": "Delimiter-Aktionen (tags)",
@@ -443,6 +444,7 @@
"domain_not_found": "Domain %s nicht gefunden",
"domain_quota_m_in_use": "Domain-Speicherplatzlimit muss größer oder gleich %d MiB sein",
"extended_sender_acl_denied": "Keine Rechte zum Setzen von externen Absenderadressen",
"external_goto_denied": "Externe Ziel-Adresse %s ist nicht erlaubt",
"extra_acl_invalid": "Externe Absenderadresse \"%s\" ist ungültig",
"extra_acl_invalid_domain": "Externe Absenderadresse \"%s\" verwendet eine ungültige Domain",
"fido2_verification_failed": "FIDO2-Verifizierung fehlgeschlagen: %s",
+2
View File
@@ -1,6 +1,7 @@
{
"acl": {
"alias_domains": "Add alias domains",
"alias_external_goto": "Allow aliases with external goto domains",
"app_passwds": "Manage app passwords",
"bcc_maps": "BCC maps",
"delimiter_action": "Delimiter action",
@@ -444,6 +445,7 @@
"domain_not_found": "Domain %s not found",
"domain_quota_m_in_use": "Domain quota must be greater or equal to %s MiB",
"extended_sender_acl_denied": "missing ACL to set external sender addresses",
"external_goto_denied": "External goto address %s is not allowed",
"extra_acl_invalid": "External sender address \"%s\" is invalid",
"extra_acl_invalid_domain": "External sender \"%s\" uses an invalid domain",
"fido2_verification_failed": "FIDO2 verification failed: %s",
+5 -2
View File
@@ -109,7 +109,10 @@
"bcc_dest_format": "Il destinatario in copia nascosta deve essere un singolo indirizzo email.<br>Se si vuole spedire una copia del messaggio a più destinatari, bisogna creare un alias ed utilizzarlo per questa opzione.",
"app_passwd_protocols": "Protocolli consentiti per la password dell'app",
"tags": "Tag",
"dry": "Simula sincronizzazione"
"dry": "Simula sincronizzazione",
"internal": "Interno",
"internal_info": "Gli alias interni sono accessibili solo dal proprio dominio o dai domini alias.",
"sender_allowed": "Consenti l'invio con questo alias"
},
"admin": {
"access": "Accedi",
@@ -131,7 +134,7 @@
"additional_rows": " righe aggiuntive inserite",
"admin": "Amministratore",
"admin_details": "Modifica impostazioni amministratore",
"admin_domains": "Assengazioni di dominio",
"admin_domains": "Assegnazioni di dominio",
"admins": "Amministratori",
"admins_ldap": "Amministratori LDAP",
"advanced_settings": "Impostazioni avanzate",
+5 -3
View File
@@ -786,7 +786,8 @@
"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).",
"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."
"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.",
"mta_sts_active_info": "Se esta opção estiver desmarcada, a política MTA-STS não será publicada e nenhum certificado será solicitado via ACME para o subdomínio mta-sts."
},
"fido2": {
"confirm": "Confirme",
@@ -1034,7 +1035,8 @@
"weekly": "Semanalmente",
"yes": "✓",
"iam": "Provedor de Identidade",
"internal": "Interno"
"internal": "Interno",
"force_tfa": "A2F"
},
"oauth2": {
"access_denied": "Faça login como proprietário da mailbox para conceder acesso via OAuth2.",
@@ -1086,7 +1088,7 @@
"rspamd_result": "Resultado do Rspamd",
"sender": "Remetente (SMTP)",
"sender_header": "Remetente (cabeçalho “De”)",
"settings_info": "Quantidade máxima de elementos a serem colocados em quarentena: %s <br>Tamanho máximo do e-mail: %s MiB",
"settings_info": "Número máximo de elementos em quarentena (por caixa de correio): %s <br>Tamanho máximo do e-mail: %s MiB",
"show_item": "Mostrar item",
"spam": "Spam",
"spam_score": "Ponto",
+7 -4
View File
@@ -29,7 +29,8 @@
"extend_sender_acl": "Dovoli razširitev pošiljateljevega ACL z zunanjimi e-poštnimi naslovi",
"quarantine_category": "Spremeni kategorijo obvestil o karanteni",
"syncjobs": "Sinhronizacijska opravila",
"pw_reset": "Dovoli ponastavitev uporabniškega gesla mailcow"
"pw_reset": "Dovoli ponastavitev uporabniškega gesla mailcow",
"alias_external_goto": "Dovoli vzdevke z zunanjimi domenami goto"
},
"add": {
"active": "Aktivno",
@@ -559,7 +560,8 @@
"mx_invalid": "Zapis MX %s je neveljaven",
"version_invalid": "Različica %s je neveljavna",
"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"
"quarantine_category_invalid": "Kategorija karantene mora biti ena od: add_header, reject, all",
"external_goto_denied": "Zunanji naslov goto %s ni dovoljen"
},
"debug": {
"containers_info": "Informacije o zabojniku",
@@ -785,7 +787,8 @@
"internal": "Notranje",
"internal_info": "Notranji vzdevki so dostopni samo iz lastne domene ali vzdevkov domen.",
"sender_allowed": "Dovoli pošiljanje kot ta vzdevek",
"sender_allowed_info": "Če je onemogočeno, lahko ta vzdevek samo prejema pošto. Za preglasitev in dodelitev dovoljenja za pošiljanje določenim poštnim predalom uporabite seznam za nadzor dostopa pošiljatelja."
"sender_allowed_info": "Če je onemogočeno, lahko ta vzdevek samo prejema pošto. Za preglasitev in dodelitev dovoljenja za pošiljanje določenim poštnim predalom uporabite seznam za nadzor dostopa pošiljatelja.",
"mta_sts_active_info": "Če ni označeno, pravilnik MTA-STS ne bo objavljen in za poddomeno mta-sts ne bo zahtevano nobeno potrdilo prek ACME."
},
"footer": {
"restart_container_info": "<b>Pomembno:</b> Eleganten ponovni zagon lahko traja nekaj časa, zato počakajte, da se konča.",
@@ -1078,7 +1081,7 @@
"rspamd_result": "Rezultat Rspamd",
"sender": "Pošiljatelj (SMTP)",
"sender_header": "Pošiljatelj (glava »Od«)",
"settings_info": "Največje število elementov za karanteno: %s<br>Največja velikost e-pošte: %s MiB",
"settings_info": "Največje število elementov za karanteno (na poštni predal): %s<br>Največja velikost e-pošte: %s MiB",
"show_item": "Prikaži element",
"spam": "Neželena pošta",
"subj": "Zadeva",
+22 -7
View File
@@ -29,7 +29,8 @@
"syncjobs": "同步任务",
"tls_policy": "TLS 策略",
"unlimited_quota": "无限邮箱容量配额",
"pw_reset": "允许重置mailcow使用者密码"
"pw_reset": "允许重置mailcow使用者密码",
"alias_external_goto": "允许带有外部目标地址的别名"
},
"add": {
"activate_filter_warn": "当“启用”选项被勾选后,其它所有的过滤器都会被禁用。",
@@ -111,7 +112,8 @@
"validation_success": "验证成功",
"dry": "模拟同步(Dry run",
"internal_info": "内部的别名只能在域内部或者别名域内部访问。",
"internal": "内部的"
"internal": "内部的",
"sender_allowed": "允许以该别名发送"
},
"admin": {
"access": "权限管理",
@@ -555,7 +557,10 @@
"max_age_invalid": "最大有效时间 %s 无效",
"mode_invalid": "模式 %s 无效",
"mx_invalid": "MX 记录 %s 无效",
"version_invalid": "版本 %s 无效"
"version_invalid": "版本 %s 无效",
"tfa_removal_blocked": "无法移除两步验证,你的账户必须使用两步验证。",
"external_goto_denied": "不允许使用外部目标地址 %s",
"quarantine_category_invalid": "隔离分类必须是 add_header, reject, all 的其中一个"
},
"debug": {
"chart_this_server": "图表 (此服务器)",
@@ -754,7 +759,10 @@
"mta_sts_max_age_info": "接收方邮件服务器可缓存该策略的时长(秒),超出后需重新获取策略。",
"mta_sts_mx": "MX 服务器",
"mta_sts_mx_info": "仅允许向明确列出的邮件服务器发送邮件;发送方 MTA 会验证 DNS MX 记录的主机名是否与策略列表匹配,并仅允许携带有效 TLS 证书的投递(可防范中间人攻击)。",
"mta_sts_mx_notice": "可配置多个 MX 服务器(以逗号分隔)。"
"mta_sts_mx_notice": "可配置多个 MX 服务器(以逗号分隔)。",
"sender_allowed": "允许以此别名发送",
"sender_allowed_info": "如果禁用了,此别名只能接收邮件。使用发件者 ACL 来覆盖此选项并赋予指定的邮箱发送权限。",
"mta_sts_active_info": "如果未选中,MTA-STS 策略不会被公布并且不会通过 ACME 为 MTA-STS 子域名请求证书。"
},
"fido2": {
"confirm": "确认",
@@ -1002,7 +1010,8 @@
"templates": "模板",
"template": "模板",
"iam": "身份提供者(IDP",
"internal": "内部的"
"internal": "内部的",
"force_tfa": "两步验证"
},
"oauth2": {
"access_denied": "请作为邮箱所有者登录以使用 OAuth2 授权。",
@@ -1210,7 +1219,12 @@
"waiting_usb_auth": "<i>等待 USB 设备中...</i><br><br>现在请触碰你的 WebAuthn USB 设备上的按钮。",
"waiting_usb_register": "<i>等待 USB 设备中...</i><br><br>请在上方输入你的密码并请触碰你的 WebAuthn USB 设备上的按钮以确认注册该 WebAuthn 设备。",
"yubi_otp": "Yubico OTP 认证",
"authenticators": "验证器(Authenticators"
"authenticators": "验证器(Authenticators",
"force_tfa": "在登录时强制启用 2FA",
"force_tfa_info": "在访问面板前用户会被要求设置两步验证。",
"setup_title": "需要设置两步验证",
"setup_required": "你的账户需要设置两步验证。请添加一个两步验证方法以继续。",
"cancel_setup": "取消并注销"
},
"user": {
"action": "操作",
@@ -1384,7 +1398,8 @@
"overview": "概览",
"expire_never": "永不过期",
"forever": "永久",
"spam_aliases_info": "垃圾邮件别名是一种临时电子邮件地址,可用于保护真实电子邮件地址。<br>还可以选择设置过期时间,以便在设定的时间后自动停用别名,从而有效地销毁被滥用或泄露的地址。"
"spam_aliases_info": "垃圾邮件别名是一种临时电子邮件地址,可用于保护真实电子邮件地址。<br>还可以选择设置过期时间,以便在设定的时间后自动停用别名,从而有效地销毁被滥用或泄露的地址。",
"pw_update_required": "你的账户需要修改一次密码。请设置一个新密码以继续。"
},
"warning": {
"cannot_delete_self": "不能删除已登录的用户",
+6
View File
@@ -4,6 +4,7 @@ require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
if (!isset($_SESSION['mailcow_cc_role'])) {
$_SESSION['oauth2_request'] = $_SERVER['REQUEST_URI'];
header('Location: /?oauth');
exit;
}
$request = OAuth2\Request::createFromGlobals();
@@ -24,6 +25,11 @@ if (!isset($_POST['authorized'])) {
exit;
}
if (empty($_SESSION['mailcow_cc_username'])) {
header('Location: /?oauth');
exit;
}
// print the authorization code if the user has authorized your client
$is_authorized = ($_POST['authorized'] == '1');
$oauth2_server->handleAuthorizeRequest($request, $response, $is_authorized, $_SESSION['mailcow_cc_username']);
+1
View File
@@ -49,6 +49,7 @@
<div class="input-group">
<div class="input-group-text"><i class="bi bi-lock-fill"></i></div>
<input name="pass_user" type="password" id="pass_user" class="form-control" placeholder="{{ lang.login.password }}" required="" autocomplete="current-password">
<button type="button" class="input-group-text" style="cursor:pointer;" onclick="var i=document.getElementById('pass_user'),ic=this.querySelector('i');if(i.type==='password'){i.type='text';ic.className='bi bi-eye-slash';}else{i.type='password';ic.className='bi bi-eye';}" tabindex="-1" aria-label="Toggle password visibility"><i class="bi bi-eye"></i></button>
</div>
</div>
<div class="d-flex justify-content-between mt-4" style="position: relative">
+4
View File
@@ -473,6 +473,10 @@ function recursiveBase64StrToArrayBuffer(obj) {
window.location.reload();
} else {
var msg = (data && data[0] && data[0].msg) ? data[0].msg : 'Password change failed.';
// the API returns raw language keys, resolve them like the alert box does
if (Object.prototype.hasOwnProperty.call(lang_danger, msg)) {
msg = lang_danger[msg];
}
$('#changePWAlert').show().text(msg);
}
},
@@ -49,6 +49,7 @@
<div class="input-group">
<div class="input-group-text"><i class="bi bi-lock-fill"></i></div>
<input name="pass_user" type="password" id="pass_user" class="form-control" placeholder="{{ lang.login.password }}" required="" autocomplete="current-password">
<button type="button" class="input-group-text" style="cursor:pointer;" onclick="var i=document.getElementById('pass_user'),ic=this.querySelector('i');if(i.type==='password'){i.type='text';ic.className='bi bi-eye-slash';}else{i.type='password';ic.className='bi bi-eye';}" tabindex="-1" aria-label="Toggle password visibility"><i class="bi bi-eye"></i></button>
</div>
</div>
<div class="d-flex justify-content-between mt-4" style="position: relative">
+1 -1
View File
@@ -267,6 +267,7 @@
<small class="text-muted">{{ lang.admin.password_reset_info }}</small>
</div>
</div>
{% endif %}
<div data-acl="{{ acl.extend_sender_acl }}" class="row mb-4">
<label class="control-label col-sm-2" for="extended_sender_acl">{{ lang.edit.extended_sender_acl }}</label>
<div class="col-sm-10">
@@ -279,7 +280,6 @@
{% endif %}
</div>
</div>
{% endif %}
<div class="row">
<label class="control-label col-sm-2" for="protocol_access">{{ lang.edit.allowed_protocols }}</label>
<div class="col-sm-10">
+1
View File
@@ -56,6 +56,7 @@
<div class="input-group">
<div class="input-group-text"><i class="bi bi-lock-fill"></i></div>
<input name="pass_user" type="password" id="pass_user" class="form-control" placeholder="{{ lang.login.password }}" required="" autocomplete="current-password">
<button type="button" class="input-group-text" style="cursor:pointer;" onclick="var i=document.getElementById('pass_user'),ic=this.querySelector('i');if(i.type==='password'){i.type='text';ic.className='bi bi-eye-slash';}else{i.type='password';ic.className='bi bi-eye';}" tabindex="-1" aria-label="Toggle password visibility"><i class="bi bi-eye"></i></button>
</div>
</div>
<div class="mt-2 text-muted" style="font-size: 0.9rem;">
+4 -4
View File
@@ -42,7 +42,7 @@ services:
- mysql
redis-mailcow:
image: redis:7.4.6-alpine
image: redis:7.4.10-alpine
entrypoint: ["/bin/sh","/redis-conf.sh"]
volumes:
- redis-vol-1:/data/
@@ -65,7 +65,7 @@ services:
- redis
clamd-mailcow:
image: ghcr.io/mailcow/clamd:1.71
image: ghcr.io/mailcow/clamd:1.4.6-1
restart: always
depends_on:
unbound-mailcow:
@@ -200,7 +200,7 @@ services:
- phpfpm
sogo-mailcow:
image: ghcr.io/mailcow/sogo:5.12.9-1
image: ghcr.io/mailcow/sogo:5.12.10-1
environment:
- DBNAME=${DBNAME}
- DBUSER=${DBUSER}
@@ -382,7 +382,7 @@ services:
- postfix
postfix-tlspol-mailcow:
image: ghcr.io/mailcow/postfix-tlspol:1.8.23
image: ghcr.io/mailcow/postfix-tlspol:1.11.0
depends_on:
unbound-mailcow:
condition: service_healthy