Compare commits

..
Author SHA1 Message Date
FreddleSpl0it 96a70652c3 Merge pull request #7328 from mailcow/staging
Update 2026-07
2026-07-13 10:42:55 +02:00
FreddleSpl0it 2ac4b1deae Merge pull request #7260 from mailcow/staging
Update 2026-05c
2026-05-26 10:50:58 +02:00
FreddleSpl0it 1eb6d2e26c Merge pull request #7243 from mailcow/staging
Update 2025-05b
2026-05-21 08:33:36 +02:00
FreddleSpl0it 384e2f6ac1 Merge pull request #7227 from mailcow/staging
Update 2026-05a
2026-05-13 10:42:14 +02:00
FreddleSpl0it 32156a337a Merge pull request #7221 from mailcow/staging
Update 2026-05
2026-05-12 08:46:25 +02:00
FreddleSpl0it 281cf93db3 Merge pull request #7174 from mailcow/staging
Update 2026-03b
2026-03-31 09:57:16 +02:00
FreddleSpl0it f399c07c85 Merge pull request #7137 from mailcow/staging
Hotfix 2026-03a
2026-03-13 14:15:04 +01:00
FreddleSpl0it a693325fe6 Merge pull request #7135 from mailcow/staging
Update 2026-03a
2026-03-13 13:16:32 +01:00
FreddleSpl0it 9ad84eee92 Merge pull request #7113 from mailcow/staging
Hotfix 2026-03
2026-03-10 13:50:33 +01:00
FreddleSpl0it b59869b720 Merge pull request #7109 from mailcow/staging
Hotfix 2026-03
2026-03-10 10:39:43 +01:00
FreddleSpl0it 7515bef66c Merge pull request #7104 from mailcow/staging
Hotfix 2026-03
2026-03-10 10:05:39 +01:00
FreddleSpl0it b84ba8ded1 Merge pull request #7101 from mailcow/staging
Update 2026-03
2026-03-10 08:08:29 +01:00
FreddleSpl0it 4845928e7a Merge pull request #7027 from mailcow/staging
[Hotfix] Update 2026-01
2026-01-29 10:31:23 +01:00
FreddleSpl0it 4ccfedd6b3 Merge pull request #7024 from mailcow/staging
🐄🛡️ January 2026 Update | Limited EAS/DAV Access and Restricted Alias Sending
2026-01-29 07:25:09 +01:00
Ashitaka e8d9315d4a Merge pull request #6905 from Ashitaka57/6646-pbkdf2-sha512-verify-hash
Support for PBKDF2-SHA512 hash algorithm in verify_hash() (FreeIPA compatibility) (issue 6646)
2025-12-12 14:08:21 +01:00
DerLinkman d977ddb501 backup: add image prefetch function to verify latest image is used 2025-12-12 14:07:57 +01:00
DerLinkman e76f5237ed ofelia: revert fixed cron syntax for sa-rules download 2025-12-12 14:07:47 +01:00
c11ed5dd1e Prevent duplicate/plaintext login announcement rendering (#6963)
* Initial plan

* Fix duplicate login announcement display

Co-authored-by: DerLinkman <62480600+DerLinkman@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: DerLinkman <62480600+DerLinkman@users.noreply.github.com>
2025-12-12 14:07:36 +01:00
DerLinkman 4ef65fc382 Merge pull request #6948 from mailcow/staging
2025-12
2025-12-09 13:29:15 +01:00
43 changed files with 161 additions and 506 deletions
@@ -14,7 +14,7 @@ jobs:
pull-requests: write pull-requests: write
steps: steps:
- name: Mark/Close Stale Issues and Pull Requests 🗑️ - name: Mark/Close Stale Issues and Pull Requests 🗑️
uses: actions/stale@v11.0.0 uses: actions/stale@v10.4.0
with: with:
repo-token: ${{ secrets.STALE_ACTION_PAT }} repo-token: ${{ secrets.STALE_ACTION_PAT }}
days-before-stale: 60 days-before-stale: 60
-5
View File
@@ -23,15 +23,10 @@ A big thank you to everyone supporting us on GitHub Sponsors—your contribution
<a href="https://www.maehdros.com/" target=_blank><img <a href="https://www.maehdros.com/" target=_blank><img
src="https://avatars.githubusercontent.com/u/173894712" height="58" src="https://avatars.githubusercontent.com/u/173894712" height="58"
/></a> /></a>
<a href="https://garske-systems.de/" target=_blank><img
src="https://garske-systems.de/img/logo_min.png" height="58"
/></a>
### 50$/Month Sponsors ### 50$/Month Sponsors
<a href="https://github.com/vnukhr" target=_blank><img <a href="https://github.com/vnukhr" target=_blank><img
src="https://avatars.githubusercontent.com/u/7805987?s=52&v=4" height="58" src="https://avatars.githubusercontent.com/u/7805987?s=52&v=4" height="58"
/></a><a href="https://github.com/humiico" target=_blank><img
src="https://avatars.githubusercontent.com/u/110134705?s=52&v=4" height="58"
/></a> /></a>
## Info, documentation and support ## Info, documentation and support
-21
View File
@@ -249,33 +249,12 @@ while true; do
done <<< "${SQL_DOMAINS}" done <<< "${SQL_DOMAINS}"
if [[ ${ONLY_MAILCOW_HOSTNAME} != "y" ]]; then if [[ ${ONLY_MAILCOW_HOSTNAME} != "y" ]]; then
# Fetch all domains with an active MTA-STS policy once.
unset MTA_STS_ACTIVE_DOMAINS
declare -A MTA_STS_ACTIVE_DOMAINS
if [[ ${AUTODISCOVER_SAN} == "y" ]]; then
SQL_MTA_STS_DOMAINS=$(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT domain FROM mta_sts WHERE active = 1" -Bs)
if [[ $? -eq 0 ]]; then
while read mta_sts_domain; do
if [[ -z "${mta_sts_domain}" ]]; then
# ignore empty lines
continue
fi
MTA_STS_ACTIVE_DOMAINS["${mta_sts_domain}"]=1
done <<< "${SQL_MTA_STS_DOMAINS}"
fi
fi
for SQL_DOMAIN in "${SQL_DOMAIN_ARR[@]}"; do for SQL_DOMAIN in "${SQL_DOMAIN_ARR[@]}"; 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}" FULL_SUBDOMAIN="${SUBDOMAIN}.${SQL_DOMAIN}"
# Skip mta-sts subdomain unless MTA-STS is enabled (active) for this domain
if [[ "${SUBDOMAIN}" == "mta-sts" && -z "${MTA_STS_ACTIVE_DOMAINS[${SQL_DOMAIN}]}" ]]; then
log_f "MTA-STS is not enabled for ${SQL_DOMAIN} - skipping mta-sts subdomain certificate"
continue
fi
# Skip if subdomain matches MAILCOW_HOSTNAME # Skip if subdomain matches MAILCOW_HOSTNAME
if [[ "${FULL_SUBDOMAIN}" == "${MAILCOW_HOSTNAME}" ]]; then if [[ "${FULL_SUBDOMAIN}" == "${MAILCOW_HOSTNAME}" ]]; then
continue continue
+3 -3
View File
@@ -1,7 +1,7 @@
FROM alpine:3.24 AS builder FROM alpine:3.21 AS builder
WORKDIR /src WORKDIR /src
ENV CLAMD_VERSION=1.4.6 ENV CLAMD_VERSION=1.4.2
RUN apk upgrade --no-cache \ RUN apk upgrade --no-cache \
&& apk add --update --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 "/clamav/etc/clamav/clamav-milter.conf.sample" > "/clamav/etc/clamav/clamav-milter.conf" || exit 1
FROM alpine:3.24 FROM alpine:3.21
LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>" LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"
+1 -1
View File
@@ -1,4 +1,4 @@
FROM nginx:1.30.4-alpine FROM nginx:1.30.3-alpine
LABEL maintainer "The Infrastructure Company GmbH <info@servercow.de>" LABEL maintainer "The Infrastructure Company GmbH <info@servercow.de>"
ENV PIP_BREAK_SYSTEM_PACKAGES=1 ENV PIP_BREAK_SYSTEM_PACKAGES=1
+3 -3
View File
@@ -1,17 +1,17 @@
FROM golang:1.26-trixie AS builder FROM golang:1.25-bookworm AS builder
WORKDIR /src WORKDIR /src
ENV CGO_ENABLED=0 \ ENV CGO_ENABLED=0 \
GO111MODULE=on \ GO111MODULE=on \
NOOPT=1 \ NOOPT=1 \
VERSION=1.11.0 VERSION=1.8.22
RUN git clone --branch v${VERSION} https://github.com/Zuplu/postfix-tlspol && \ RUN git clone --branch v${VERSION} https://github.com/Zuplu/postfix-tlspol && \
cd /src/postfix-tlspol && \ cd /src/postfix-tlspol && \
scripts/build.sh build-only scripts/build.sh build-only
FROM debian:trixie-slim FROM debian:bookworm-slim
LABEL maintainer="The Infrastructure Company GmbH <info@servercow.de>" LABEL maintainer="The Infrastructure Company GmbH <info@servercow.de>"
ARG DEBIAN_FRONTEND=noninteractive ARG DEBIAN_FRONTEND=noninteractive
@@ -1,4 +1,4 @@
@version: 4.8 @version: 3.38
@include "scl.conf" @include "scl.conf"
options { options {
chain_hostnames(off); chain_hostnames(off);
@@ -7,7 +7,7 @@ options {
dns_cache(no); dns_cache(no);
use_fqdn(no); use_fqdn(no);
owner("root"); group("adm"); perm(0640); owner("root"); group("adm"); perm(0640);
stats(freq(0)); stats_freq(0);
bad_hostname("^gconfd$"); bad_hostname("^gconfd$");
}; };
source s_src { source s_src {
@@ -1,4 +1,4 @@
@version: 4.8 @version: 3.38
@include "scl.conf" @include "scl.conf"
options { options {
chain_hostnames(off); chain_hostnames(off);
@@ -7,7 +7,7 @@ options {
dns_cache(no); dns_cache(no);
use_fqdn(no); use_fqdn(no);
owner("root"); group("adm"); perm(0640); owner("root"); group("adm"); perm(0640);
stats(freq(0)); stats_freq(0);
bad_hostname("^gconfd$"); bad_hostname("^gconfd$");
}; };
source s_src { source s_src {
+1 -1
View File
@@ -2,7 +2,7 @@ FROM debian:trixie-slim
LABEL maintainer="The Infrastructure Company GmbH <info@servercow.de>" LABEL maintainer="The Infrastructure Company GmbH <info@servercow.de>"
ARG DEBIAN_FRONTEND=noninteractive ARG DEBIAN_FRONTEND=noninteractive
ARG RSPAMD_VER=rspamd_4.1.4-1~beb659b ARG RSPAMD_VER=rspamd_4.1.0-1~e2b0b18
ARG CODENAME=trixie ARG CODENAME=trixie
ENV LC_ALL=C ENV LC_ALL=C
+2 -2
View File
@@ -12,8 +12,8 @@ FROM debian:bookworm
LABEL maintainer="The Infrastructure Company GmbH <info@servercow.de>" LABEL maintainer="The Infrastructure Company GmbH <info@servercow.de>"
ARG DEBIAN_FRONTEND=noninteractive ARG DEBIAN_FRONTEND=noninteractive
ARG SOGO_VERSION=SOGo-5.12.10 ARG SOGO_VERSION=SOGo-5.12.9
ARG SOPE_VERSION=SOPE-5.12.10 ARG SOPE_VERSION=SOPE-5.12.9
# Security patches to apply (space-separated commit hashes) # Security patches to apply (space-separated commit hashes)
ARG SOGO_SECURITY_PATCHES="" ARG SOGO_SECURITY_PATCHES=""
# renovate: datasource=github-releases depName=tianon/gosu versioning=semver-coerced extractVersion=^(?<version>.*)$ # renovate: datasource=github-releases depName=tianon/gosu versioning=semver-coerced extractVersion=^(?<version>.*)$
+1 -10
View File
@@ -32,17 +32,8 @@ function auth_password_verify(request, password)
-- Returning PASSDB_RESULT_PASSWORD_MISMATCH will reset the user's auth cache entry. -- Returning PASSDB_RESULT_PASSWORD_MISMATCH will reset the user's auth cache entry.
-- Returning PASSDB_RESULT_INTERNAL_FAILURE keeps the existing 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. -- 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 if c ~= 200 and c ~= 401 then
dovecot.i_info("HTTP request failed with " .. tostring(c) .. " for user " .. request.user) dovecot.i_info("HTTP request failed with " .. c .. " for user " .. request.user)
return dovecot.auth.PASSDB_RESULT_PASSWORD_MISMATCH, "Upstream error" return dovecot.auth.PASSDB_RESULT_PASSWORD_MISMATCH, "Upstream error"
end end
+10
View File
@@ -1,3 +1,13 @@
# global_sieve_before script # global_sieve_before script
# global_sieve_before -> user sieve_before (mailcow UI) -> user sieve_after (mailcow UI) -> global_sieve_after # 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;
}
-7
View File
@@ -42,19 +42,12 @@ http {
https https; https https;
} }
map $server_port $sogo_auth_internal {
65510 "1";
default "";
}
{% if HTTP_REDIRECT %} {% if HTTP_REDIRECT %}
# HTTP to HTTPS redirect # HTTP to HTTPS redirect
server { server {
root /web; root /web;
listen {{ HTTP_PORT }} default_server; listen {{ HTTP_PORT }} default_server;
{% if ENABLE_IPV6 %}
listen [::]:{{ HTTP_PORT }} default_server; listen [::]:{{ HTTP_PORT }} default_server;
{%endif%}
server_name {{ MAILCOW_HOSTNAME }} autodiscover.* autoconfig.* mta-sts.* {{ ADDITIONAL_SERVER_NAMES | join(' ') }}; server_name {{ MAILCOW_HOSTNAME }} autodiscover.* autoconfig.* mta-sts.* {{ ADDITIONAL_SERVER_NAMES | join(' ') }};
@@ -116,9 +116,6 @@ location ~ \.php$ {
include /etc/nginx/fastcgi_params; include /etc/nginx/fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info; 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; fastcgi_read_timeout 3600;
fastcgi_send_timeout 3600; fastcgi_send_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_password_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_sasl_passwd_maps_sender_dependent.cf
smtp_sasl_security_options = smtp_sasl_security_options =
smtp_sasl_mechanism_filter = plain, login 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:QUERYwithTLSRPT smtp_tls_policy_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_tls_policy_override_maps.cf socketmap:inet:postfix-tlspol:8642:QUERY
smtp_header_checks = pcre:/opt/postfix/conf/anonymize_headers.pcre smtp_header_checks = pcre:/opt/postfix/conf/anonymize_headers.pcre
mail_name = Postcow mail_name = Postcow
# local_transport map catches local destinations and prevents routing local dests when the next map would route "*" # local_transport map catches local destinations and prevents routing local dests when the next map would route "*"
+35 -82
View File
@@ -1,6 +1,6 @@
# Whitelist generated by Postwhite v3.4 on Tue Sep 1 00:34:07 UTC 2026 # Whitelist generated by Postwhite v3.4 on Wed Jul 1 00:51:20 UTC 2026
# https://github.com/stevejenkins/postwhite/ # https://github.com/stevejenkins/postwhite/
# 2243 total rules # 2251 total rules
2a00:1450:4000::/36 permit 2a00:1450:4000::/36 permit
2a00:1450:4864::/56 permit 2a00:1450:4864::/56 permit
2a01:111:f400::/48 permit 2a01:111:f400::/48 permit
@@ -29,7 +29,6 @@
2a01:b747:3004:200::/56 permit 2a01:b747:3004:200::/56 permit
2a01:b747:3005:200::/56 permit 2a01:b747:3005:200::/56 permit
2a01:b747:3006:200::/56 permit 2a01:b747:3006:200::/56 permit
2a01:b747:3007::/56 permit
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
@@ -57,14 +56,7 @@
8.25.194.0/23 permit 8.25.194.0/23 permit
8.25.196.0/23 permit 8.25.196.0/23 permit
8.36.116.0/24 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.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 12.130.86.238 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
@@ -75,7 +67,6 @@
13.111.191.0/24 permit 13.111.191.0/24 permit
13.216.7.111 permit 13.216.7.111 permit
13.216.54.180 permit 13.216.54.180 permit
13.247.164.219 permit
15.200.21.50 permit 15.200.21.50 permit
15.200.44.248 permit 15.200.44.248 permit
15.200.201.185 permit 15.200.201.185 permit
@@ -125,6 +116,7 @@
23.249.208.0/20 permit 23.249.208.0/20 permit
23.251.224.0/19 permit 23.251.224.0/19 permit
23.253.141.0/24 permit 23.253.141.0/24 permit
23.253.182.0/23 permit
23.253.182.103 permit 23.253.182.103 permit
23.253.183.145 permit 23.253.183.145 permit
23.253.183.146 permit 23.253.183.146 permit
@@ -146,7 +138,24 @@
27.123.206.80/28 permit 27.123.206.80/28 permit
31.47.251.17 permit 31.47.251.17 permit
31.186.239.0/24 permit 31.186.239.0/24 permit
34.2.64.0/19 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.70.158.162 permit 34.70.158.162 permit
34.74.74.140 permit 34.74.74.140 permit
34.83.159.189 permit 34.83.159.189 permit
@@ -161,7 +170,6 @@
34.215.104.144 permit 34.215.104.144 permit
34.218.115.239 permit 34.218.115.239 permit
34.225.212.172 permit 34.225.212.172 permit
34.241.242.183 permit
35.83.148.184 permit 35.83.148.184 permit
35.155.198.111 permit 35.155.198.111 permit
35.158.23.94 permit 35.158.23.94 permit
@@ -173,7 +181,6 @@
35.205.92.9 permit 35.205.92.9 permit
35.228.216.85 permit 35.228.216.85 permit
35.242.169.159 permit 35.242.169.159 permit
37.187.220.204 permit
37.188.97.188 permit 37.188.97.188 permit
37.218.248.47 permit 37.218.248.47 permit
37.218.249.47 permit 37.218.249.47 permit
@@ -186,7 +193,6 @@
40.233.64.216 permit 40.233.64.216 permit
40.233.83.78 permit 40.233.83.78 permit
40.233.88.28 permit 40.233.88.28 permit
43.239.212.33 permit
44.206.138.57 permit 44.206.138.57 permit
44.210.169.44 permit 44.210.169.44 permit
44.217.45.156 permit 44.217.45.156 permit
@@ -247,8 +253,8 @@
46.228.39.112/31 permit 46.228.39.112/31 permit
46.243.88.174 permit 46.243.88.174 permit
46.243.88.175 permit 46.243.88.175 permit
46.243.88.176/30 permit 46.243.88.176 permit
46.243.95.96/27 permit 46.243.88.177 permit
46.243.95.179 permit 46.243.95.179 permit
46.243.95.180 permit 46.243.95.180 permit
50.16.246.183 permit 50.16.246.183 permit
@@ -266,11 +272,9 @@
50.56.130.221 permit 50.56.130.221 permit
50.56.130.222 permit 50.56.130.222 permit
50.112.246.219 permit 50.112.246.219 permit
50.152.146.25 permit
51.83.17.38 permit 51.83.17.38 permit
52.1.14.157 permit 52.1.14.157 permit
52.5.230.59 permit 52.5.230.59 permit
52.6.74.205 permit
52.12.53.23 permit 52.12.53.23 permit
52.13.214.179 permit 52.13.214.179 permit
52.26.1.71 permit 52.26.1.71 permit
@@ -327,7 +331,6 @@
54.244.54.130 permit 54.244.54.130 permit
54.244.242.0/24 permit 54.244.242.0/24 permit
54.255.61.23 permit 54.255.61.23 permit
56.124.6.228 permit
57.103.64.0/18 permit 57.103.64.0/18 permit
57.129.93.249 permit 57.129.93.249 permit
62.13.128.0/24 permit 62.13.128.0/24 permit
@@ -394,7 +397,6 @@
65.110.161.77 permit 65.110.161.77 permit
65.123.29.213 permit 65.123.29.213 permit
65.123.29.220 permit 65.123.29.220 permit
65.154.166.0/24 permit
65.212.180.36 permit 65.212.180.36 permit
66.102.0.0/20 permit 66.102.0.0/20 permit
66.119.150.192/26 permit 66.119.150.192/26 permit
@@ -638,7 +640,6 @@
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 80.225.160.128/25 permit
81.88.38.0/23 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
@@ -723,7 +724,6 @@
96.43.148.64/28 permit 96.43.148.64/28 permit
96.43.148.64/31 permit 96.43.148.64/31 permit
96.43.151.64/28 permit 96.43.151.64/28 permit
98.77.0.0/16 permit
98.97.248.0/21 permit 98.97.248.0/21 permit
98.136.44.181 permit 98.136.44.181 permit
98.136.44.182/31 permit 98.136.44.182/31 permit
@@ -1210,15 +1210,14 @@
99.78.197.208/28 permit 99.78.197.208/28 permit
103.9.96.0/22 permit 103.9.96.0/22 permit
103.28.42.0/24 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.151.192.0/23 permit
103.168.172.128/27 permit 103.168.172.128/27 permit
103.237.104.0/22 permit 103.237.104.0/22 permit
104.43.243.237 permit 104.43.243.237 permit
104.44.112.128/25 permit 104.44.112.128/25 permit
104.47.0.0/17 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.64/27 permit
106.10.144.100/31 permit 106.10.144.100/31 permit
106.10.144.103 permit 106.10.144.103 permit
@@ -1371,10 +1370,6 @@
117.120.16.0/21 permit 117.120.16.0/21 permit
119.42.242.52/31 permit 119.42.242.52/31 permit
119.42.242.156 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 123.126.78.64/29 permit
124.108.96.24/31 permit 124.108.96.24/31 permit
124.108.96.28/31 permit 124.108.96.28/31 permit
@@ -1440,7 +1435,6 @@
130.35.116.0/25 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.162.194.23 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.186.12.0/25 permit
@@ -1458,21 +1452,7 @@
134.170.141.64/26 permit 134.170.141.64/26 permit
134.170.143.0/24 permit 134.170.143.0/24 permit
134.170.174.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 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.146.128.0/20 permit
136.147.128.0/20 permit 136.147.128.0/20 permit
136.147.135.0/24 permit 136.147.135.0/24 permit
@@ -1492,7 +1472,6 @@
139.138.46.219 permit 139.138.46.219 permit
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.177.108.0/25 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
@@ -1524,6 +1503,9 @@
146.20.14.105 permit 146.20.14.105 permit
146.20.14.106 permit 146.20.14.106 permit
146.20.14.107 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.0/24 permit
146.20.215.182 permit 146.20.215.182 permit
146.88.28.0/24 permit 146.88.28.0/24 permit
@@ -1550,7 +1532,6 @@
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.165.251 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.135.128/25 permit
@@ -1572,7 +1553,6 @@
158.247.16.0/20 permit 158.247.16.0/20 permit
158.247.100.0/25 permit 158.247.100.0/25 permit
159.13.4.0/25 permit 159.13.4.0/25 permit
159.13.33.181 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
@@ -1600,8 +1580,6 @@
159.183.121.182 permit 159.183.121.182 permit
159.183.129.172 permit 159.183.129.172 permit
160.1.62.192 permit 160.1.62.192 permit
161.33.94.110 permit
161.33.228.244 permit
161.38.192.0/20 permit 161.38.192.0/20 permit
161.38.204.0/22 permit 161.38.204.0/22 permit
161.71.32.0/19 permit 161.71.32.0/19 permit
@@ -1633,6 +1611,7 @@
165.173.182.0/24 permit 165.173.182.0/24 permit
165.173.182.250/31 permit 165.173.182.250/31 permit
165.173.189.205 permit 165.173.189.205 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
166.78.69.170 permit 166.78.69.170 permit
@@ -1664,19 +1643,6 @@
168.245.12.252 permit 168.245.12.252 permit
168.245.46.9 permit 168.245.46.9 permit
168.245.127.231 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.9.232.254 permit
170.10.128.0/24 permit 170.10.128.0/24 permit
170.10.129.0/24 permit 170.10.129.0/24 permit
@@ -1787,9 +1753,9 @@
188.125.85.234/31 permit 188.125.85.234/31 permit
188.125.85.236/31 permit 188.125.85.236/31 permit
188.125.85.238 permit 188.125.85.238 permit
188.165.51.139 permit
188.172.128.0/20 permit 188.172.128.0/20 permit
192.0.64.0/18 permit 192.0.64.0/18 permit
192.9.166.184 permit
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
@@ -1814,6 +1780,7 @@
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
192.237.158.0/23 permit
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
@@ -1856,6 +1823,7 @@
198.21.0.0/21 permit 198.21.0.0/21 permit
198.37.144.0/20 permit 198.37.144.0/20 permit
198.37.152.186 permit 198.37.152.186 permit
198.61.254.0/23 permit
198.61.254.21 permit 198.61.254.21 permit
198.61.254.231 permit 198.61.254.231 permit
198.178.234.57 permit 198.178.234.57 permit
@@ -1881,16 +1849,7 @@
199.16.156.0/22 permit 199.16.156.0/22 permit
199.33.145.1 permit 199.33.145.1 permit
199.33.145.32 permit 199.33.145.32 permit
199.34.22.36 permit
199.59.148.0/22 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.161.130 permit
199.101.162.0/25 permit 199.101.162.0/25 permit
199.122.120.0/21 permit 199.122.120.0/21 permit
@@ -1942,17 +1901,12 @@
204.14.232.0/21 permit 204.14.232.0/21 permit
204.14.232.64/28 permit 204.14.232.64/28 permit
204.14.234.64/28 permit 204.14.234.64/28 permit
204.75.18.128/27 permit
204.75.142.0/24 permit 204.75.142.0/24 permit
204.92.114.187 permit 204.92.114.187 permit
204.92.114.203 permit 204.92.114.203 permit
204.92.114.204/31 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.216.164.202 permit
204.220.90.0/23 permit 204.220.160.0/21 permit
204.220.92.0/22 permit
204.220.160.0/20 permit
204.220.168.0/21 permit 204.220.168.0/21 permit
204.220.176.0/20 permit 204.220.176.0/20 permit
204.220.181.105 permit 204.220.181.105 permit
@@ -1988,8 +1942,6 @@
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.211.132.0/25 permit
207.211.147.87 permit
207.211.159.110 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
@@ -2039,6 +1991,7 @@
209.43.22.0/28 permit 209.43.22.0/28 permit
209.46.117.168 permit 209.46.117.168 permit
209.46.117.179 permit 209.46.117.179 permit
209.61.151.0/24 permit
209.61.151.236 permit 209.61.151.236 permit
209.61.151.249 permit 209.61.151.249 permit
209.61.151.251 permit 209.61.151.251 permit
@@ -2225,7 +2178,7 @@
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 2404:6800:4864::/56 permit
2603:1061:14:102::1 permit 2603:1061:14:1c2::1 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
+1 -1
View File
@@ -53,7 +53,7 @@ $raw_data = mb_convert_encoding($raw_data_content, 'HTML-ENTITIES', "UTF-8");
$raw_size = (int)$_FILES['message']['size']; $raw_size = (int)$_FILES['message']['size'];
$qid = $meta['qid'] ?? 'unknown'; $qid = $meta['qid'] ?? 'unknown';
$subject = $meta['subject'] ?? ''; $subject = iconv_mime_decode($meta['subject'] ?? '');
$score = $meta['score'] ?? 0; $score = $meta['score'] ?? 0;
$rcpts = $meta['rcpt'] ?? array(); $rcpts = $meta['rcpt'] ?? array();
$user = $meta['user'] ?? 'unknown'; $user = $meta['user'] ?? 'unknown';
+1 -1
View File
@@ -50,7 +50,7 @@ $qid = $meta['qid'] ?? 'unknown';
$rcpts = $meta['rcpt'] ?? array(); $rcpts = $meta['rcpt'] ?? array();
$sender = $meta['from'] ?? ''; $sender = $meta['from'] ?? '';
$ip = $meta['ip'] ?? 'unknown'; $ip = $meta['ip'] ?? 'unknown';
$subject = $meta['subject'] ?? ''; $subject = iconv_mime_decode($meta['subject'] ?? '');
$messageid= $meta['message_id'] ?? ''; $messageid= $meta['message_id'] ?? '';
$priority = 0; $priority = 0;
+2 -16
View File
@@ -185,9 +185,6 @@ paths:
example: example:
username: info@domain.tld username: info@domain.tld
domain: domain.tld domain: domain.tld
description: my time limited alias
validity: 8760
permanent: false
properties: properties:
username: username:
description: 'the mailbox an alias should be created for' description: 'the mailbox an alias should be created for'
@@ -195,15 +192,6 @@ paths:
domain: domain:
description: "the domain" description: "the domain"
type: string type: string
description:
description: "a description for the alias, defaults to an empty string"
type: string
validity:
description: "how many hours the alias stays valid, 1 to 87600, defaults to 8760 (one year)"
type: integer
permanent:
description: "keep the alias after it expired, defaults to false"
type: boolean
type: object type: object
summary: Create time limited alias summary: Create time limited alias
/api/v1/add/app-passwd: /api/v1/add/app-passwd:
@@ -4920,7 +4908,6 @@ paths:
quota: 3221225472 quota: 3221225472
quota_used: 0 quota_used: 0
rl: false rl: false
sender_acl: ["otherbox@doman3.tld", "@aliasdomain.tld"]
spam_aliases: 0 spam_aliases: 0
username: info@doman3.tld username: info@doman3.tld
tags: ["tag1", "tag2"] tags: ["tag1", "tag2"]
@@ -5847,7 +5834,6 @@ paths:
quota: 3221225472 quota: 3221225472
quota_used: 0 quota_used: 0
rl: false rl: false
sender_acl: ["otherbox@domain3.tld", "@aliasdomain.tld"]
spam_aliases: 0 spam_aliases: 0
username: info@domain3.tld username: info@domain3.tld
tags: ["tag1", "tag2"] tags: ["tag1", "tag2"]
@@ -5870,7 +5856,7 @@ paths:
response: response:
value: value:
- type: "success" - type: "success"
log: ["cors", "edit", {"allowed_origins": ["*", "https://mail.mailcow.tld"], "allowed_methods": ["POST", "GET", "DELETE", "PUT"]}] log: ["cors", "edit", {"allowed_origins": ["*", "mail.mailcow.tld"], "allowed_methods": ["POST", "GET", "DELETE", "PUT"]}]
msg: "cors_headers_edited" msg: "cors_headers_edited"
description: OK description: OK
headers: { } headers: { }
@@ -5887,7 +5873,7 @@ paths:
schema: schema:
example: example:
attr: attr:
allowed_origins: ["*", "https://mail.mailcow.tld"] allowed_origins: ["*", "mail.mailcow.tld"]
allowed_methods: ["POST", "GET", "DELETE", "PUT"] allowed_methods: ["POST", "GET", "DELETE", "PUT"]
properties: properties:
attr: attr:
+7 -27
View File
@@ -380,13 +380,13 @@ if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "adm
else { else {
$state = state_nomatch; $state = state_nomatch;
} }
$state .= '<br />' . htmlspecialchars($current[$data_field[$current['type']]]); $state .= '<br />' . $current[$data_field[$current['type']]];
} }
if ($current['type'] == 'TXT' && if ($current['type'] == 'TXT' &&
stripos($current['txt'], 'v=dmarc') === 0 && stripos($current['txt'], 'v=dmarc') === 0 &&
$record[2] == $dmarc_link) { $record[2] == $dmarc_link) {
$current['txt'] = str_replace(' ', '', $current['txt']); $current['txt'] = str_replace(' ', '', $current['txt']);
$state = htmlspecialchars($current[$data_field[$current['type']]]) . state_optional; $state = $current[$data_field[$current['type']]] . state_optional;
} }
elseif ($current['type'] == 'TXT' && elseif ($current['type'] == 'TXT' &&
stripos($current['txt'], 'v=spf') === 0 && 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)) { if (in_array($ip, $rslt) && in_array(expand_ipv6($ip6), $rslt)) {
$state = state_good; $state = state_good;
} }
$state .= '<br />' . htmlspecialchars($current[$data_field[$current['type']]]) . state_optional; $state .= '<br />' . $current[$data_field[$current['type']]] . state_optional;
} }
elseif ($current['type'] == 'TXT' && elseif ($current['type'] == 'TXT' &&
stripos($current['txt'], 'v=dkim') === 0 && 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) { if ($state == state_nomatch) {
$state = array(); $state = array();
foreach ($currents as $current) { foreach ($currents as $current) {
$state[] = htmlspecialchars($current[$data_field[$current['type']]]); $state[] = $current[$data_field[$current['type']]];
} }
$state = implode('<br />', $state); $state = implode('<br />', $state);
} }
@@ -436,22 +436,12 @@ if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "adm
<td>%s</td> <td>%s</td>
<td class="dns-found">%s</td> <td class="dns-found">%s</td>
<td class="dns-recommended">%s</td> <td class="dns-recommended">%s</td>
</tr>', htmlspecialchars($record[0]), htmlspecialchars($record[1]), $record[2], $state); </tr>', $record[0], $record[1], $record[2], $state);
$record[3] = explode('<br />', $state); $record[3] = explode('<br />', $state);
} }
unset($record); 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); $dns_data = sprintf("\$ORIGIN %s.\n", $domain);
foreach ($records as $record) { foreach ($records as $record) {
if ($domain == substr($record[0], -strlen($domain))) { if ($domain == substr($record[0], -strlen($domain))) {
@@ -472,32 +462,22 @@ if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "adm
$val = str_replace(state_optional, '', $val); $val = str_replace(state_optional, '', $val);
$val = str_replace(state_good, '', $val); $val = str_replace(state_good, '', $val);
if (strlen($val) > 0) { 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); $vals[] = sprintf("%s\tIN\t%s\t%s\n", $label, $record[1], $val);
} }
} }
} }
else { 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); $vals[] = sprintf("%s\tIN\t%s\t%s\n", $label, $record[1], $val);
} }
foreach ($vals as $val) { foreach ($vals as $val) {
$dns_data .= $val; $dns_data .= str_replace($domain, $domain . '.', $val);
} }
} }
} }
?> ?>
</table> </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='<?=htmlspecialchars($_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='<?=$_GET['domain'];?>.txt' type='text/csv'>Download</a>
<script> <script>
var zonefile_dl_link = document.getElementById('download-zonefile'); var zonefile_dl_link = document.getElementById('download-zonefile');
var zonefile = atob(zonefile_dl_link.getAttribute('data-zonefile')); var zonefile = atob(zonefile_dl_link.getAttribute('data-zonefile'));
-67
View File
@@ -1,67 +0,0 @@
<?php
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
header('Content-Type: application/json');
// Admins only
if (!isset($_SESSION['mailcow_cc_role']) || $_SESSION['mailcow_cc_role'] !== 'admin') {
http_response_code(403);
echo json_encode(array('status' => 'error', 'message' => 'access denied'));
exit;
}
$owner = $GLOBALS['MAILCOW_GIT_OWNER'];
$repo = $GLOBALS['MAILCOW_GIT_REPO'];
$current = $GLOBALS['MAILCOW_GIT_VERSION'];
// Cache key is bound to the running version, so an update invalidates it naturally
$cache_key = 'MAILCOW_UPDATE_CHECK/' . $current;
// Serve cached result if present (one GitHub call per TTL, not one per page load)
$cached = $redis->get($cache_key);
if ($cached !== false) {
echo $cached;
exit;
}
// GitHub requires a User-Agent and rate-limits unauthenticated requests to 60/h per IP
function github_get($url) {
$ch = curl_init($url);
curl_setopt_array($ch, array(
CURLOPT_RETURNTRANSFER => true,
CURLOPT_USERAGENT => 'mailcow-update-check',
CURLOPT_TIMEOUT => 10,
CURLOPT_HTTPHEADER => array('Accept: application/vnd.github+json'),
));
$body = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($body === false || $code !== 200) return false;
$json = json_decode($body, true);
return is_array($json) ? $json : false;
}
$latest = github_get("https://api.github.com/repos/{$owner}/{$repo}/releases/latest");
$release = github_get("https://api.github.com/repos/{$owner}/{$repo}/releases/tags/{$current}");
// Any failure (rate limit, offline, unexpected payload) -> honest error, cached briefly
if ($latest === false || $release === false ||
empty($latest['tag_name']) || empty($latest['created_at']) || empty($release['created_at'])) {
$result = json_encode(array('status' => 'error', 'message' => 'could not reach GitHub API'));
$redis->setex($cache_key, 300, $result);
echo $result;
exit;
}
$date_latest = strtotime($latest['created_at']);
$date_current = strtotime($release['created_at']);
if ($date_latest <= $date_current) {
$result = json_encode(array('status' => 'no_update'));
} else {
$result = json_encode(array('status' => 'update_available', 'latest_tag' => $latest['tag_name']));
}
// Cache the positive result for an hour
$redis->setex($cache_key, 3600, $result);
echo $result;
+3 -14
View File
@@ -429,25 +429,14 @@ function domain_admin_sso($_action, $_data) {
switch ($_action) { switch ($_action) {
case 'check': case 'check':
$token = preg_replace('/[^a-zA-Z0-9-]/', '', $_data); $token = $_data;
$stmt = $pdo->prepare("SELECT `t1`.`username` FROM `da_sso` AS `t1` JOIN `admin` AS `t2` ON `t1`.`username` = `t2`.`username` WHERE `t1`.`token` = :token AND `t1`.`created` > DATE_SUB(NOW(), INTERVAL '30' SECOND) AND `t2`.`active` = 1 AND `t2`.`superadmin` = 0;"); $stmt = $pdo->prepare("SELECT `t1`.`username` FROM `da_sso` AS `t1` JOIN `admin` AS `t2` ON `t1`.`username` = `t2`.`username` WHERE `t1`.`token` = :token AND `t1`.`created` > DATE_SUB(NOW(), INTERVAL '30' SECOND) AND `t2`.`active` = 1 AND `t2`.`superadmin` = 0;");
$stmt->execute(array( $stmt->execute(array(
':token' => $token ':token' => preg_replace('/[^a-zA-Z0-9-]/', '', $token)
)); ));
$return = $stmt->fetch(PDO::FETCH_ASSOC); $return = $stmt->fetch(PDO::FETCH_ASSOC);
if (empty($return['username'])) { return empty($return['username']) ? false : $return['username'];
return false;
}
// single-use: consume the token; if a concurrent request already used it, deny
$del = $pdo->prepare("DELETE FROM `da_sso` WHERE `token` = :token");
$del->execute(array(
':token' => $token
));
if ($del->rowCount() < 1) {
return false;
}
return $return['username'];
case 'issue': case 'issue':
if ($_SESSION['mailcow_cc_role'] != "admin") { if ($_SESSION['mailcow_cc_role'] != "admin") {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
+14 -83
View File
@@ -53,39 +53,6 @@ function valid_network($network) {
function valid_hostname($hostname) { function valid_hostname($hostname) {
return filter_var($hostname, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME); return filter_var($hostname, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME);
} }
// Validates a browser-style Origin: scheme://host[:port], no path/query/fragment/credentials
function valid_origin($origin) {
$parts = parse_url($origin);
if ($parts === false || !isset($parts['scheme']) || !isset($parts['host'])) {
return false;
}
if (!in_array(strtolower($parts['scheme']), array('http', 'https'), true)) {
return false;
}
if (isset($parts['user']) || isset($parts['pass']) || isset($parts['path']) || isset($parts['query']) || isset($parts['fragment'])) {
return false;
}
$host = $parts['host'];
$host_without_brackets = trim($host, '[]');
if (!valid_hostname($host_without_brackets) && !filter_var($host_without_brackets, FILTER_VALIDATE_IP)) {
return false;
}
// Reject anything that doesn't round-trip to the exact same origin (e.g. stray trailing slash)
$port = isset($parts['port']) ? ':' . $parts['port'] : '';
$rebuilt = strtolower($parts['scheme']) . '://' . strtolower($host) . $port;
return strtolower($origin) === $rebuilt;
}
// Bring an origin into the exact form a browser sends it (scheme://host[:port], lowercase).
function normalize_cors_origin($origin) {
$origin = trim($origin);
if ($origin === '*') {
return '*';
}
if ($origin !== '' && !preg_match('~^[a-z][a-z0-9+.-]*://~i', $origin)) {
$origin = 'https://' . $origin;
}
return valid_origin($origin) ? strtolower($origin) : false;
}
// Thanks to https://stackoverflow.com/a/49373789 // Thanks to https://stackoverflow.com/a/49373789
// Validates exact ip matches and ip-in-cidr, ipv4 and ipv6 // Validates exact ip matches and ip-in-cidr, ipv4 and ipv6
function ip_acl($ip, $networks) { function ip_acl($ip, $networks) {
@@ -591,7 +558,7 @@ function logger($_data = false) {
$type = $return['type']; $type = $return['type'];
$msg = null; $msg = null;
if (isset($return['msg'])) { if (isset($return['msg'])) {
$msg = json_encode($return['msg'], JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT); $msg = json_encode($return['msg'], JSON_UNESCAPED_UNICODE);
} }
$call = null; $call = null;
if (isset($return['log'])) { if (isset($return['log'])) {
@@ -633,18 +600,6 @@ function logger($_data = false) {
return true; 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) { function hasDomainAccess($username, $role, $domain) {
global $pdo; global $pdo;
if (empty($domain) || !is_valid_domain_name($domain)) { if (empty($domain) || !is_valid_domain_name($domain)) {
@@ -2328,13 +2283,10 @@ function cors($action, $data = null) {
return false; return false;
} }
$allowed_origins = isset($data['allowed_origins']) ? $data['allowed_origins'] : array(getBaseURL()); $allowed_origins = isset($data['allowed_origins']) ? $data['allowed_origins'] : array($_SERVER['SERVER_NAME']);
$allowed_origins = !is_array($allowed_origins) ? array_filter(array_map('trim', explode("\n", $allowed_origins))) : $allowed_origins; $allowed_origins = !is_array($allowed_origins) ? array_filter(array_map('trim', explode("\n", $allowed_origins))) : $allowed_origins;
foreach ($allowed_origins as &$origin) { foreach ($allowed_origins as $origin) {
if ($origin === '*') { if (!filter_var($origin, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME) && $origin != '*') {
continue;
}
if (!valid_origin($origin)) {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'danger', 'type' => 'danger',
'log' => array(__FUNCTION__, $action, $data), 'log' => array(__FUNCTION__, $action, $data),
@@ -2342,10 +2294,7 @@ function cors($action, $data = null) {
); );
return false; return false;
} }
// browsers always send a lowercase scheme/host in the Origin header, so normalize to match
$origin = strtolower($origin);
} }
unset($origin);
$allowed_methods = isset($data['allowed_methods']) ? $data['allowed_methods'] : array('GET', 'POST', 'PUT', 'DELETE'); $allowed_methods = isset($data['allowed_methods']) ? $data['allowed_methods'] : array('GET', 'POST', 'PUT', 'DELETE');
$allowed_methods = !is_array($allowed_methods) ? array_map('trim', preg_split( "/( |,|;|\n)/", $allowed_methods)) : $allowed_methods; $allowed_methods = !is_array($allowed_methods) ? array_map('trim', preg_split( "/( |,|;|\n)/", $allowed_methods)) : $allowed_methods;
@@ -2393,35 +2342,26 @@ function cors($action, $data = null) {
); );
} }
$cors_settings = !$cors_settings ? array('allowed_origins' => getBaseURL(), 'allowed_methods' => 'GET, POST, PUT, DELETE') : $cors_settings; $cors_settings = !$cors_settings ? array('allowed_origins' => $_SERVER['SERVER_NAME'], 'allowed_methods' => 'GET, POST, PUT, DELETE') : $cors_settings;
$cors_settings['allowed_origins'] = empty($cors_settings['allowed_origins']) ? getBaseURL() : $cors_settings['allowed_origins']; $cors_settings['allowed_origins'] = empty($cors_settings['allowed_origins']) ? $_SERVER['SERVER_NAME'] : $cors_settings['allowed_origins'];
$cors_settings['allowed_methods'] = empty($cors_settings['allowed_methods']) ? 'GET, POST, PUT, DELETE, OPTION' : $cors_settings['allowed_methods']; $cors_settings['allowed_methods'] = empty($cors_settings['allowed_methods']) ? 'GET, POST, PUT, DELETE, OPTION' : $cors_settings['allowed_methods'];
return $cors_settings; return $cors_settings;
break; break;
case "set_headers": case "set_headers":
$cors_settings = cors('get'); $cors_settings = cors('get');
// normalize the stored list; it may still hold bare hostnames written before origins were validated as origins
$allowed_origins = array_filter(array_map('normalize_cors_origin', explode(',', $cors_settings['allowed_origins'])));
$origin = isset($_SERVER['HTTP_ORIGIN']) ? normalize_cors_origin($_SERVER['HTTP_ORIGIN']) : false;
// check if requested origin is in allowed origins // check if requested origin is in allowed origins
$allow_origin = null; $allowed_origins = explode(', ', $cors_settings['allowed_origins']);
if (in_array('*', $allowed_origins, true)) { $cors_settings['allowed_origins'] = $allowed_origins[0];
$allow_origin = '*'; if (in_array('*', $allowed_origins)){
} else if ($origin !== false && in_array($origin, $allowed_origins, true)) { $cors_settings['allowed_origins'] = '*';
$allow_origin = $origin; } else if (array_key_exists('HTTP_ORIGIN', $_SERVER) && in_array($_SERVER['HTTP_ORIGIN'], $allowed_origins)) {
$cors_settings['allowed_origins'] = $_SERVER['HTTP_ORIGIN'];
} }
// always allow OPTIONS for preflight request // always allow OPTIONS for preflight request
$cors_settings["allowed_methods"] = empty($cors_settings["allowed_methods"]) ? 'OPTIONS' : $cors_settings["allowed_methods"] . ', ' . 'OPTIONS'; $cors_settings["allowed_methods"] = empty($cors_settings["allowed_methods"]) ? 'OPTIONS' : $cors_settings["allowed_methods"] . ', ' . 'OPTIONS';
// a disallowed origin gets no Access-Control-Allow-Origin at all; echoing a different origin than the requesting one tells a browser nothing header('Access-Control-Allow-Origin: ' . $cors_settings['allowed_origins']);
if ($allow_origin !== null) {
header('Access-Control-Allow-Origin: ' . $allow_origin);
}
if ($allow_origin !== '*') {
// the response depends on the request origin, keep caches from mixing them up
header('Vary: Origin', false);
}
header('Access-Control-Allow-Methods: '. $cors_settings['allowed_methods']); header('Access-Control-Allow-Methods: '. $cors_settings['allowed_methods']);
header('Access-Control-Allow-Headers: Accept, Content-Type, X-Api-Key, Origin'); header('Access-Control-Allow-Headers: Accept, Content-Type, X-Api-Key, Origin');
@@ -3563,16 +3503,7 @@ function protect_route($allowed_roles = ['admin', 'domainadmin', 'user'], $redir
if (isset($redirects['unauthenticated'])) { if (isset($redirects['unauthenticated'])) {
header('Location: ' . $redirects['unauthenticated']); header('Location: ' . $redirects['unauthenticated']);
} else { } else {
// Send a deep link to the login page for its area instead of the user login at /, header('Location: /');
// 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(); exit();
} }
+11 -56
View File
@@ -41,15 +41,13 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
else { else {
$username = $_SESSION['mailcow_cc_username']; $username = $_SESSION['mailcow_cc_username'];
} }
if (isset($_data["validity"])) { if (isset($_data["validity"]) && !filter_var($_data["validity"], FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 87600)))) {
if (!filter_var($_data["validity"], FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 87600)))) { $_SESSION['return'][] = array(
$_SESSION['return'][] = array( 'type' => 'danger',
'type' => 'danger', 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), 'msg' => 'validity_missing'
'msg' => 'validity_missing' );
); return false;
return false;
}
} }
else { else {
// Default to 1 yr // Default to 1 yr
@@ -62,8 +60,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$permanent = 0; $permanent = 0;
} }
$domain = $_data['domain']; $domain = $_data['domain'];
// spamalias.description is NOT NULL, an absent description must not become a null insert $description = $_data['description'];
$description = $_data['description'] ?? '';
$valid_domains[] = mailbox('get', 'mailbox_details', $username)['domain']; $valid_domains[] = mailbox('get', 'mailbox_details', $username)['domain'];
$valid_alias_domains = user_get_alias_details($username)['alias_domains']; $valid_alias_domains = user_get_alias_details($username)['alias_domains'];
if (!empty($valid_alias_domains)) { if (!empty($valid_alias_domains)) {
@@ -750,18 +747,6 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$goto_domain = idn_to_ascii(substr(strstr($goto, '@'), 1), 0, INTL_IDNA_VARIANT_UTS46); $goto_domain = idn_to_ascii(substr(strstr($goto, '@'), 1), 0, INTL_IDNA_VARIANT_UTS46);
$goto_local_part = strstr($goto, '@', true); $goto_local_part = strstr($goto, '@', true);
$goto = $goto_local_part.'@'.$goto_domain; $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` $stmt = $pdo->prepare("SELECT `username` FROM `mailbox`
WHERE `kind` REGEXP 'location|thing|group' WHERE `kind` REGEXP 'location|thing|group'
AND `username`= :goto"); AND `username`= :goto");
@@ -2727,19 +2712,6 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
unset($gotos[$i]); unset($gotos[$i]);
continue; 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) { if ($goto == $address) {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'danger', 'type' => 'danger',
@@ -5348,14 +5320,6 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$mailboxdata['rl_scope'] = 'domain'; $mailboxdata['rl_scope'] = 'domain';
} }
$mailboxdata['is_relayed'] = $row['backupmx']; $mailboxdata['is_relayed'] = $row['backupmx'];
// Internal send-as ACL for this mailbox, mirrors the sender_acl field accepted by edit/mailbox
$mailboxdata['sender_acl'] = array();
$stmt = $pdo->prepare("SELECT `send_as` FROM `sender_acl` WHERE `logged_in_as` = :username AND `external` = '0'");
$stmt->execute(array(':username' => $_data));
$sender_acl_rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($sender_acl_rows as $sender_acl_row) {
$mailboxdata['sender_acl'][] = $sender_acl_row['send_as'];
}
} }
$stmt = $pdo->prepare("SELECT `tag_name` $stmt = $pdo->prepare("SELECT `tag_name`
FROM `tags_mailbox` WHERE `username`= :username"); FROM `tags_mailbox` WHERE `username`= :username");
@@ -5683,11 +5647,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$stmt->execute(array( $stmt->execute(array(
':username' => $username ':username' => $username
)); ));
// bind LIKE pattern + escape LIKE wildcards so the value matches literally (no SQLi, no over-match) $stmt = $pdo->prepare("DELETE FROM `sogo_acl` WHERE `c_object` LIKE '%/" . $username . "/%' OR `c_uid` = :username");
$c_object_like = '%/' . addcslashes($username, '\\%_') . '/%';
$stmt = $pdo->prepare("DELETE FROM `sogo_acl` WHERE `c_object` LIKE :c_object_like OR `c_uid` = :username");
$stmt->execute(array( $stmt->execute(array(
':c_object_like' => $c_object_like,
':username' => $username ':username' => $username
)); ));
$stmt = $pdo->prepare("DELETE FROM `sogo_store` WHERE `c_folder_id` IN (SELECT `c_folder_id` FROM `sogo_folder_info` WHERE `c_path2` = :username)"); $stmt = $pdo->prepare("DELETE FROM `sogo_store` WHERE `c_folder_id` IN (SELECT `c_folder_id` FROM `sogo_folder_info` WHERE `c_path2` = :username)");
@@ -6083,11 +6044,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$stmt->execute(array( $stmt->execute(array(
':username' => $username ':username' => $username
)); ));
// bind LIKE pattern + escape LIKE wildcards so the value matches literally (no SQLi, no over-match) $stmt = $pdo->prepare("DELETE FROM `sogo_acl` WHERE `c_object` LIKE '%/" . str_replace('%', '\%', $username) . "/%' OR `c_uid` = :username");
$c_object_like = '%/' . addcslashes($username, '\\%_') . '/%';
$stmt = $pdo->prepare("DELETE FROM `sogo_acl` WHERE `c_object` LIKE :c_object_like OR `c_uid` = :username");
$stmt->execute(array( $stmt->execute(array(
':c_object_like' => $c_object_like,
':username' => $username ':username' => $username
)); ));
$stmt = $pdo->prepare("DELETE FROM `sogo_store` WHERE `c_folder_id` IN (SELECT `c_folder_id` FROM `sogo_folder_info` WHERE `c_path2` = :username)"); $stmt = $pdo->prepare("DELETE FROM `sogo_store` WHERE `c_folder_id` IN (SELECT `c_folder_id` FROM `sogo_folder_info` WHERE `c_path2` = :username)");
@@ -6243,11 +6201,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$stmt->execute(array( $stmt->execute(array(
':username' => $name ':username' => $name
)); ));
// bind LIKE pattern + escape LIKE wildcards so the value matches literally (no SQLi, no over-match) $stmt = $pdo->prepare("DELETE FROM `sogo_acl` WHERE `c_object` LIKE '%/" . $name . "/%' OR `c_uid` = :username");
$c_object_like = '%/' . addcslashes($name, '\\%_') . '/%';
$stmt = $pdo->prepare("DELETE FROM `sogo_acl` WHERE `c_object` LIKE :c_object_like OR `c_uid` = :username");
$stmt->execute(array( $stmt->execute(array(
':c_object_like' => $c_object_like,
':username' => $name ':username' => $name
)); ));
$stmt = $pdo->prepare("DELETE FROM `sogo_store` WHERE `c_folder_id` IN (SELECT `c_folder_id` FROM `sogo_folder_info` WHERE `c_path2` = :username)"); $stmt = $pdo->prepare("DELETE FROM `sogo_store` WHERE `c_folder_id` IN (SELECT `c_folder_id` FROM `sogo_folder_info` WHERE `c_path2` = :username)");
+1 -5
View File
@@ -170,8 +170,6 @@ function quarantine($_action, $_data = null) {
} }
elseif ($release_format == 'raw') { elseif ($release_format == 'raw') {
$detail_row['msg'] = preg_replace('/^X-Spam-Flag: (.*)/m', 'X-Pre-Release-Spam-Flag: $1', $detail_row['msg']); $detail_row['msg'] = preg_replace('/^X-Spam-Flag: (.*)/m', 'X-Pre-Release-Spam-Flag: $1', $detail_row['msg']);
// dot-stuffing per RFC5321 4.5.2: escape leading dots
$detail_row['msg'] = preg_replace('~^\.~m', '..', $detail_row['msg']);
$postfix_talk = array( $postfix_talk = array(
array('220', 'HELO quarantine' . chr(10)), array('220', 'HELO quarantine' . chr(10)),
array('250', 'MAIL FROM: ' . $sender . chr(10)), array('250', 'MAIL FROM: ' . $sender . chr(10)),
@@ -467,14 +465,12 @@ function quarantine($_action, $_data = null) {
} }
elseif ($release_format == 'raw') { elseif ($release_format == 'raw') {
$row['msg'] = preg_replace('/^X-Spam-Flag: (.*)/m', 'X-Pre-Release-Spam-Flag: $1', $row['msg']); $row['msg'] = preg_replace('/^X-Spam-Flag: (.*)/m', 'X-Pre-Release-Spam-Flag: $1', $row['msg']);
// dot-stuffing per RFC5321 4.5.2: escape leading dots
$row['msg'] = preg_replace('~^\.~m', '..', $row['msg']);
$postfix_talk = array( $postfix_talk = array(
array('220', 'HELO quarantine' . chr(10)), array('220', 'HELO quarantine' . chr(10)),
array('250', 'MAIL FROM: ' . $sender . chr(10)), array('250', 'MAIL FROM: ' . $sender . chr(10)),
array('250', 'RCPT TO: ' . $row['rcpt'] . chr(10)), array('250', 'RCPT TO: ' . $row['rcpt'] . chr(10)),
array('250', 'DATA' . chr(10)), array('250', 'DATA' . chr(10)),
array('354', $row['msg'] . chr(10) . '.' . chr(10)), array('354', str_replace("\n.", '', $row['msg']) . chr(10) . '.' . chr(10)),
array('250', 'QUIT' . chr(10)), array('250', 'QUIT' . chr(10)),
array('221', '') array('221', '')
); );
+2 -5
View File
@@ -4,7 +4,7 @@ function init_db_schema()
try { try {
global $pdo; global $pdo;
$db_version = "18082026_1200"; $db_version = "19022026_1220";
$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));
@@ -719,8 +719,7 @@ function init_db_schema()
"alias_domains" => "TINYINT(1) NOT NULL DEFAULT '0'", "alias_domains" => "TINYINT(1) NOT NULL DEFAULT '0'",
"mailbox_relayhost" => "TINYINT(1) NOT NULL DEFAULT '1'", "mailbox_relayhost" => "TINYINT(1) NOT NULL DEFAULT '1'",
"domain_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( "keys" => array(
"primary" => array( "primary" => array(
@@ -1465,8 +1464,6 @@ function init_db_schema()
"pop3_access" => intval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['pop3_access']), "pop3_access" => intval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['pop3_access']),
"smtp_access" => intval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['smtp_access']), "smtp_access" => intval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['smtp_access']),
"sieve_access" => intval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['sieve_access']), "sieve_access" => intval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['sieve_access']),
"eas_access" => intval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['eas_access']),
"dav_access" => intval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['dav_access']),
"acl_spam_alias" => 1, "acl_spam_alias" => 1,
"acl_tls_policy" => 1, "acl_tls_policy" => 1,
"acl_spam_score" => 1, "acl_spam_score" => 1,
-4
View File
@@ -246,10 +246,6 @@ $PW_RESET_TOKEN_LIMIT = 3;
// Maximum time in minutes a password reset token is valid // Maximum time in minutes a password reset token is valid
$PW_RESET_TOKEN_LIFETIME = 15; $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 // UV flag handling in FIDO2/WebAuthn - defaults to false to allow iOS logins
// true = required // true = required
// false = preferred // false = preferred
+33 -27
View File
@@ -30,7 +30,7 @@ $(document).ready(function() {
// create host cpu and mem charts // create host cpu and mem charts
createHostCpuAndMemChart(); createHostCpuAndMemChart();
// check for new version // check for new version
if (mailcow_info.branch === "master" && mailcow_cc_role === "admin"){ if (mailcow_info.branch === "master"){
check_update(mailcow_info.version_tag, mailcow_info.project_url); check_update(mailcow_info.version_tag, mailcow_info.project_url);
} }
$("#mailcow_version").click(function(){ $("#mailcow_version").click(function(){
@@ -1089,8 +1089,6 @@ jQuery(function($){
return str; return str;
}).join('<br>\n'); }).join('<br>\n');
item.subject = escapeHtml(item.subject); item.subject = escapeHtml(item.subject);
if (item.sender_mime != null) item.sender_mime = escapeHtml(item.sender_mime);
if (item['message-id'] != null) item['message-id'] = escapeHtml(item['message-id']);
var scan_time = item.time_real.toFixed(3); var scan_time = item.time_real.toFixed(3);
if (item.time_virtual) { if (item.time_virtual) {
scan_time += ' / ' + item.time_virtual.toFixed(3); scan_time += ' / ' + item.time_virtual.toFixed(3);
@@ -1169,7 +1167,6 @@ jQuery(function($){
if (item === null) { return true; } if (item === null) { return true; }
item.user = escapeHtml(item.user); item.user = escapeHtml(item.user);
item.call = escapeHtml(item.call); item.call = escapeHtml(item.call);
if (item.msg) item.msg = escapeHtml(item.msg);
item.task = '<code>' + item.task + '</code>'; item.task = '<code>' + item.task + '</code>';
item.type = '<span class="badge fs-6 bg-' + item.type + '">' + item.type + '</span>'; item.type = '<span class="badge fs-6 bg-' + item.type + '">' + item.type + '</span>';
}); });
@@ -1177,7 +1174,6 @@ jQuery(function($){
$.each(data, function (i, item) { $.each(data, function (i, item) {
if (item === null) { return true; } if (item === null) { return true; }
item.username = escapeHtml(item.username); 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>'; item.service = '<div class="badge fs-6 bg-secondary">' + item.service.toUpperCase() + '</div>';
}); });
} else if (table == 'general_syslog') { } else if (table == 'general_syslog') {
@@ -1216,11 +1212,6 @@ jQuery(function($){
}); });
} else if (table == 'rllog') { } else if (table == 'rllog') {
$.each(data, function (i, item) { $.each(data, function (i, item) {
if (item.header_from != null) item.header_from = escapeHtml(item.header_from);
if (item.header_subject != null) item.header_subject = escapeHtml(item.header_subject);
if (item.from != null) item.from = escapeHtml(item.from);
if (item.rcpt != null) item.rcpt = escapeHtml(item.rcpt);
if (item.message_id != null) item.message_id = escapeHtml(item.message_id);
if (item.user == null) { if (item.user == null) {
item.user = "none"; item.user = "none";
} }
@@ -1678,26 +1669,41 @@ function createHostCpuAndMemChart(){
function check_update(current_version, github_repo_url){ function check_update(current_version, github_repo_url){
if (!current_version || !github_repo_url) return false; if (!current_version || !github_repo_url) return false;
var github_account = github_repo_url.split("/")[3];
var github_repo_name = github_repo_url.split("/")[4];
window.fetch("/inc/ajax/update_check.php", {method:'GET',cache:'no-cache'}).then(function(response) { // get details about latest release
if (!response.ok) throw new Error("update check returned " + response.status); window.fetch("https://api.github.com/repos/"+github_account+"/"+github_repo_name+"/releases/latest", {method:'GET',cache:'no-cache'}).then(function(response) {
return response.json(); return response.json();
}).then(function(data) { }).then(function(latest_data) {
if (data.status === "no_update") { // get details about current release
$("#mailcow_update").removeClass("text-warning text-danger").addClass("text-success"); window.fetch("https://api.github.com/repos/"+github_account+"/"+github_repo_name+"/releases/tags/"+current_version, {method:'GET',cache:'no-cache'}).then(function(response) {
$("#mailcow_update").html("<b>" + lang_debug.no_update_available + "</b>"); return response.json();
} else if (data.status === "update_available") { }).then(function(current_data) {
$("#mailcow_update").removeClass("text-danger text-success").addClass("text-warning"); // compare releases
$("#mailcow_update").html(lang_debug.update_available + ` <a href="#" id="mailcow_update_changelog">`+data.latest_tag+`</a>`); var date_current = new Date(current_data.created_at);
$("#mailcow_update_changelog").click(function(){ var date_latest = new Date(latest_data.created_at);
if (mailcow_cc_role !== "admin" && mailcow_cc_role !== "domainadmin") if (date_latest.getTime() <= date_current.getTime()){
return; // no update available
$("#mailcow_update").removeClass("text-warning text-danger").addClass("text-success");
$("#mailcow_update").html("<b>" + lang_debug.no_update_available + "</b>");
} else {
// update available
$("#mailcow_update").removeClass("text-danger text-success").addClass("text-warning");
$("#mailcow_update").html(lang_debug.update_available + ` <a href="#" id="mailcow_update_changelog">`+latest_data.tag_name+`</a>`);
$("#mailcow_update_changelog").click(function(){
if (mailcow_cc_role !== "admin" && mailcow_cc_role !== "domainadmin")
return;
showVersionModal("New Release " + data.latest_tag, data.latest_tag); showVersionModal("New Release " + latest_data.tag_name, latest_data.tag_name);
}) })
} else { }
throw new Error(data.message || "update check failed"); }).catch(err => {
} // err
console.log(err);
$("#mailcow_update").removeClass("text-success text-warning").addClass("text-danger");
$("#mailcow_update").html("<b>"+ lang_debug.update_failed +"</b>");
});
}).catch(err => { }).catch(err => {
// err // err
console.log(err); console.log(err);
-1
View File
@@ -996,7 +996,6 @@ jQuery(function($){
'style="min-width:2em;width:' + item.percent_in_use + '%">' + item.percent_in_use + '%' + '</div></div>' 'style="min-width:2em;width:' + item.percent_in_use + '%">' + item.percent_in_use + '%' + '</div></div>'
}; };
item.username = escapeHtml(item.username); item.username = escapeHtml(item.username);
item.name = escapeHtml(item.name);
if (Array.isArray(item.tags)){ if (Array.isArray(item.tags)){
var tags = ''; var tags = '';
-3
View File
@@ -1,7 +1,6 @@
{ {
"acl": { "acl": {
"alias_domains": "Alias-Domains hinzufügen", "alias_domains": "Alias-Domains hinzufügen",
"alias_external_goto": "Aliase mit externen Ziel-Domains erlauben",
"app_passwds": "App-Passwörter verwalten", "app_passwds": "App-Passwörter verwalten",
"bcc_maps": "BCC-Maps", "bcc_maps": "BCC-Maps",
"delimiter_action": "Delimiter-Aktionen (tags)", "delimiter_action": "Delimiter-Aktionen (tags)",
@@ -444,7 +443,6 @@
"domain_not_found": "Domain %s nicht gefunden", "domain_not_found": "Domain %s nicht gefunden",
"domain_quota_m_in_use": "Domain-Speicherplatzlimit muss größer oder gleich %d MiB sein", "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", "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": "Externe Absenderadresse \"%s\" ist ungültig",
"extra_acl_invalid_domain": "Externe Absenderadresse \"%s\" verwendet eine ungültige Domain", "extra_acl_invalid_domain": "Externe Absenderadresse \"%s\" verwendet eine ungültige Domain",
"fido2_verification_failed": "FIDO2-Verifizierung fehlgeschlagen: %s", "fido2_verification_failed": "FIDO2-Verifizierung fehlgeschlagen: %s",
@@ -730,7 +728,6 @@
"mta_sts_mx": "MX-Server", "mta_sts_mx": "MX-Server",
"mta_sts_mx_info": "Erlaubt das Senden nur an explizit aufgeführte Mailserver-Hostnamen; der sendende MTA überprüft, ob der DNS-MX-Hostname mit der Richtlinienliste übereinstimmt, und erlaubt die Zustellung nur mit einem gültigen TLS-Zertifikat (schützt vor MITM).", "mta_sts_mx_info": "Erlaubt das Senden nur an explizit aufgeführte Mailserver-Hostnamen; der sendende MTA überprüft, ob der DNS-MX-Hostname mit der Richtlinienliste übereinstimmt, und erlaubt die Zustellung nur mit einem gültigen TLS-Zertifikat (schützt vor MITM).",
"mta_sts_mx_notice": "Es können mehrere MX-Server angegeben werden (durch Kommas getrennt).", "mta_sts_mx_notice": "Es können mehrere MX-Server angegeben werden (durch Kommas getrennt).",
"mta_sts_active_info": "Wenn nicht angehakt, wird die MTA-STS-Richtlinie nicht veröffentlicht und für die mta-sts-Subdomain kein Zertifikat über ACME angefordert.",
"multiple_bookings": "Mehrfaches Buchen", "multiple_bookings": "Mehrfaches Buchen",
"nexthop": "Next Hop", "nexthop": "Next Hop",
"none_inherit": "Keine Auswahl / Erben", "none_inherit": "Keine Auswahl / Erben",
-3
View File
@@ -1,7 +1,6 @@
{ {
"acl": { "acl": {
"alias_domains": "Add alias domains", "alias_domains": "Add alias domains",
"alias_external_goto": "Allow aliases with external goto domains",
"app_passwds": "Manage app passwords", "app_passwds": "Manage app passwords",
"bcc_maps": "BCC maps", "bcc_maps": "BCC maps",
"delimiter_action": "Delimiter action", "delimiter_action": "Delimiter action",
@@ -445,7 +444,6 @@
"domain_not_found": "Domain %s not found", "domain_not_found": "Domain %s not found",
"domain_quota_m_in_use": "Domain quota must be greater or equal to %s MiB", "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", "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": "External sender address \"%s\" is invalid",
"extra_acl_invalid_domain": "External sender \"%s\" uses an invalid domain", "extra_acl_invalid_domain": "External sender \"%s\" uses an invalid domain",
"fido2_verification_failed": "FIDO2 verification failed: %s", "fido2_verification_failed": "FIDO2 verification failed: %s",
@@ -730,7 +728,6 @@
"mta_sts_mx": "MX server", "mta_sts_mx": "MX server",
"mta_sts_mx_info": "Allows sending only to explicitly listed mail server hostnames; the sending MTA checks if the DNS MX hostname matches the policy list, and only allows delivery with a valid TLS certificate (guards against MITM).", "mta_sts_mx_info": "Allows sending only to explicitly listed mail server hostnames; the sending MTA checks if the DNS MX hostname matches the policy list, and only allows delivery with a valid TLS certificate (guards against MITM).",
"mta_sts_mx_notice": "Multiple MX servers can be specified (separated by commas).", "mta_sts_mx_notice": "Multiple MX servers can be specified (separated by commas).",
"mta_sts_active_info": "If unchecked, the MTA-STS policy will not be published and no certificate will be requested for the mta-sts subdomain via ACME.",
"multiple_bookings": "Multiple bookings", "multiple_bookings": "Multiple bookings",
"none_inherit": "None / Inherit", "none_inherit": "None / Inherit",
"nexthop": "Next hop", "nexthop": "Next hop",
+1 -1
View File
@@ -131,7 +131,7 @@
"additional_rows": " righe aggiuntive inserite", "additional_rows": " righe aggiuntive inserite",
"admin": "Amministratore", "admin": "Amministratore",
"admin_details": "Modifica impostazioni amministratore", "admin_details": "Modifica impostazioni amministratore",
"admin_domains": "Assegnazioni di dominio", "admin_domains": "Assengazioni di dominio",
"admins": "Amministratori", "admins": "Amministratori",
"admins_ldap": "Amministratori LDAP", "admins_ldap": "Amministratori LDAP",
"advanced_settings": "Impostazioni avanzate", "advanced_settings": "Impostazioni avanzate",
+3 -5
View File
@@ -786,8 +786,7 @@
"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": "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": { "fido2": {
"confirm": "Confirme", "confirm": "Confirme",
@@ -1035,8 +1034,7 @@
"weekly": "Semanalmente", "weekly": "Semanalmente",
"yes": "✓", "yes": "✓",
"iam": "Provedor de Identidade", "iam": "Provedor de Identidade",
"internal": "Interno", "internal": "Interno"
"force_tfa": "A2F"
}, },
"oauth2": { "oauth2": {
"access_denied": "Faça login como proprietário da mailbox para conceder acesso via OAuth2.", "access_denied": "Faça login como proprietário da mailbox para conceder acesso via OAuth2.",
@@ -1088,7 +1086,7 @@
"rspamd_result": "Resultado do Rspamd", "rspamd_result": "Resultado do Rspamd",
"sender": "Remetente (SMTP)", "sender": "Remetente (SMTP)",
"sender_header": "Remetente (cabeçalho “De”)", "sender_header": "Remetente (cabeçalho “De”)",
"settings_info": "Número máximo de elementos em quarentena (por caixa de correio): %s <br>Tamanho máximo do e-mail: %s MiB", "settings_info": "Quantidade máxima de elementos a serem colocados em quarentena: %s <br>Tamanho máximo do e-mail: %s MiB",
"show_item": "Mostrar item", "show_item": "Mostrar item",
"spam": "Spam", "spam": "Spam",
"spam_score": "Ponto", "spam_score": "Ponto",
+2 -3
View File
@@ -785,8 +785,7 @@
"internal": "Notranje", "internal": "Notranje",
"internal_info": "Notranji vzdevki so dostopni samo iz lastne domene ali vzdevkov domen.", "internal_info": "Notranji vzdevki so dostopni samo iz lastne domene ali vzdevkov domen.",
"sender_allowed": "Dovoli pošiljanje kot ta vzdevek", "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": { "footer": {
"restart_container_info": "<b>Pomembno:</b> Eleganten ponovni zagon lahko traja nekaj časa, zato počakajte, da se konča.", "restart_container_info": "<b>Pomembno:</b> Eleganten ponovni zagon lahko traja nekaj časa, zato počakajte, da se konča.",
@@ -1079,7 +1078,7 @@
"rspamd_result": "Rezultat Rspamd", "rspamd_result": "Rezultat Rspamd",
"sender": "Pošiljatelj (SMTP)", "sender": "Pošiljatelj (SMTP)",
"sender_header": "Pošiljatelj (glava »Od«)", "sender_header": "Pošiljatelj (glava »Od«)",
"settings_info": "Največje število elementov za karanteno (na poštni predal): %s<br>Največja velikost e-pošte: %s MiB", "settings_info": "Največje število elementov za karanteno: %s<br>Največja velikost e-pošte: %s MiB",
"show_item": "Prikaži element", "show_item": "Prikaži element",
"spam": "Neželena pošta", "spam": "Neželena pošta",
"subj": "Zadeva", "subj": "Zadeva",
-6
View File
@@ -4,7 +4,6 @@ require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
if (!isset($_SESSION['mailcow_cc_role'])) { if (!isset($_SESSION['mailcow_cc_role'])) {
$_SESSION['oauth2_request'] = $_SERVER['REQUEST_URI']; $_SESSION['oauth2_request'] = $_SERVER['REQUEST_URI'];
header('Location: /?oauth'); header('Location: /?oauth');
exit;
} }
$request = OAuth2\Request::createFromGlobals(); $request = OAuth2\Request::createFromGlobals();
@@ -25,11 +24,6 @@ if (!isset($_POST['authorized'])) {
exit; exit;
} }
if (empty($_SESSION['mailcow_cc_username'])) {
header('Location: /?oauth');
exit;
}
// print the authorization code if the user has authorized your client // print the authorization code if the user has authorized your client
$is_authorized = ($_POST['authorized'] == '1'); $is_authorized = ($_POST['authorized'] == '1');
$oauth2_server->handleAuthorizeRequest($request, $response, $is_authorized, $_SESSION['mailcow_cc_username']); $oauth2_server->handleAuthorizeRequest($request, $response, $is_authorized, $_SESSION['mailcow_cc_username']);
+2 -11
View File
@@ -8,12 +8,8 @@ $ALLOW_ADMIN_EMAIL_LOGIN = (preg_match(
$session_var_user_allowed = 'sogo-sso-user-allowed'; $session_var_user_allowed = 'sogo-sso-user-allowed';
$session_var_pass = 'sogo-sso-pass'; $session_var_pass = 'sogo-sso-pass';
// only the internal nginx auth_request loopback (127.0.0.1:65510) sets this;
// external clients can never supply it (bare fastcgi param, not an HTTP header)
$is_internal_auth = (($_SERVER['SOGO_AUTH_INTERNAL'] ?? '') === '1');
// validate credentials for basic auth requests // validate credentials for basic auth requests
if ($is_internal_auth && isset($_SERVER['PHP_AUTH_USER'])) { if (isset($_SERVER['PHP_AUTH_USER'])) {
// load prerequisites only when required // load prerequisites only when required
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
@@ -54,11 +50,6 @@ elseif (isset($_GET['login'])) {
(($_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) {
// enforce tenant boundary
if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $login)) {
header("Location: /");
exit;
}
// Block SOGo access if pending actions (2FA setup, password update) // Block SOGo access if pending actions (2FA setup, password update)
if (!empty($_SESSION['pending_tfa_setup']) || !empty($_SESSION['pending_pw_update'])) { if (!empty($_SESSION['pending_tfa_setup']) || !empty($_SESSION['pending_pw_update'])) {
header("Location: /"); header("Location: /");
@@ -89,7 +80,7 @@ elseif (isset($_GET['login'])) {
exit; exit;
} }
// only check for admin-login on sogo GUI requests // only check for admin-login on sogo GUI requests
elseif ($is_internal_auth && isset($_SERVER['HTTP_X_ORIGINAL_URI']) && strcasecmp(substr($_SERVER['HTTP_X_ORIGINAL_URI'], 0, 9), "/SOGo/so/") === 0) { elseif (isset($_SERVER['HTTP_X_ORIGINAL_URI']) && strcasecmp(substr($_SERVER['HTTP_X_ORIGINAL_URI'], 0, 9), "/SOGo/so/") === 0) {
// this is an nginx auth_request call, we check for existing sogo-sso session variables // this is an nginx auth_request call, we check for existing sogo-sso session variables
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/vars.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/vars.inc.php';
if (file_exists($_SERVER['DOCUMENT_ROOT'] . '/inc/vars.local.inc.php')) { if (file_exists($_SERVER['DOCUMENT_ROOT'] . '/inc/vars.local.inc.php')) {
-1
View File
@@ -49,7 +49,6 @@
<div class="input-group"> <div class="input-group">
<div class="input-group-text"><i class="bi bi-lock-fill"></i></div> <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"> <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> </div>
<div class="d-flex justify-content-between mt-4" style="position: relative"> <div class="d-flex justify-content-between mt-4" style="position: relative">
-4
View File
@@ -473,10 +473,6 @@ function recursiveBase64StrToArrayBuffer(obj) {
window.location.reload(); window.location.reload();
} else { } else {
var msg = (data && data[0] && data[0].msg) ? data[0].msg : 'Password change failed.'; 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); $('#changePWAlert').show().text(msg);
} }
}, },
@@ -49,7 +49,6 @@
<div class="input-group"> <div class="input-group">
<div class="input-group-text"><i class="bi bi-lock-fill"></i></div> <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"> <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> </div>
<div class="d-flex justify-content-between mt-4" style="position: relative"> <div class="d-flex justify-content-between mt-4" style="position: relative">
+1 -1
View File
@@ -340,7 +340,7 @@
<div class="row mb-4"> <div class="row mb-4">
<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><i style="font-size: 16px; cursor: pointer;" class="bi bi-patch-question-fill m-2 ms-0" data-bs-toggle="tooltip" data-bs-html="true" data-bs-placement="bottom" title="{{ lang.edit.mta_sts_active_info|raw }}"></i><input type="checkbox" class="form-check-input" value="1" name="active"{% if mta_sts.active == '1' %} checked{% endif %}> {{ lang.edit.active }}</label> <label><input type="checkbox" class="form-check-input" value="1" name="active"{% if mta_sts.active == '1' %} checked{% endif %}> {{ lang.edit.active }}</label>
</div> </div>
</div> </div>
</div> </div>
+1 -1
View File
@@ -267,7 +267,6 @@
<small class="text-muted">{{ lang.admin.password_reset_info }}</small> <small class="text-muted">{{ lang.admin.password_reset_info }}</small>
</div> </div>
</div> </div>
{% endif %}
<div data-acl="{{ acl.extend_sender_acl }}" class="row mb-4"> <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> <label class="control-label col-sm-2" for="extended_sender_acl">{{ lang.edit.extended_sender_acl }}</label>
<div class="col-sm-10"> <div class="col-sm-10">
@@ -280,6 +279,7 @@
{% endif %} {% endif %}
</div> </div>
</div> </div>
{% endif %}
<div class="row"> <div class="row">
<label class="control-label col-sm-2" for="protocol_access">{{ lang.edit.allowed_protocols }}</label> <label class="control-label col-sm-2" for="protocol_access">{{ lang.edit.allowed_protocols }}</label>
<div class="col-sm-10"> <div class="col-sm-10">
-1
View File
@@ -56,7 +56,6 @@
<div class="input-group"> <div class="input-group">
<div class="input-group-text"><i class="bi bi-lock-fill"></i></div> <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"> <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> </div>
<div class="mt-2 text-muted" style="font-size: 0.9rem;"> <div class="mt-2 text-muted" style="font-size: 0.9rem;">
+7 -7
View File
@@ -42,7 +42,7 @@ services:
- mysql - mysql
redis-mailcow: redis-mailcow:
image: redis:7.4.10-alpine image: redis:7.4.6-alpine
entrypoint: ["/bin/sh","/redis-conf.sh"] entrypoint: ["/bin/sh","/redis-conf.sh"]
volumes: volumes:
- redis-vol-1:/data/ - redis-vol-1:/data/
@@ -65,7 +65,7 @@ services:
- redis - redis
clamd-mailcow: clamd-mailcow:
image: ghcr.io/mailcow/clamd:1.4.6-1 image: ghcr.io/mailcow/clamd:1.71
restart: always restart: always
depends_on: depends_on:
unbound-mailcow: unbound-mailcow:
@@ -84,7 +84,7 @@ services:
- clamd - clamd
rspamd-mailcow: rspamd-mailcow:
image: ghcr.io/mailcow/rspamd:4.1.4-1 image: ghcr.io/mailcow/rspamd:4.1.0-1
stop_grace_period: 30s stop_grace_period: 30s
depends_on: depends_on:
- dovecot-mailcow - dovecot-mailcow
@@ -200,7 +200,7 @@ services:
- phpfpm - phpfpm
sogo-mailcow: sogo-mailcow:
image: ghcr.io/mailcow/sogo:5.12.10-1 image: ghcr.io/mailcow/sogo:5.12.9-1
environment: environment:
- DBNAME=${DBNAME} - DBNAME=${DBNAME}
- DBUSER=${DBUSER} - DBUSER=${DBUSER}
@@ -382,7 +382,7 @@ services:
- postfix - postfix
postfix-tlspol-mailcow: postfix-tlspol-mailcow:
image: ghcr.io/mailcow/postfix-tlspol:1.11.0 image: ghcr.io/mailcow/postfix-tlspol:1.8.23
depends_on: depends_on:
unbound-mailcow: unbound-mailcow:
condition: service_healthy condition: service_healthy
@@ -419,7 +419,7 @@ services:
- php-fpm-mailcow - php-fpm-mailcow
- sogo-mailcow - sogo-mailcow
- rspamd-mailcow - rspamd-mailcow
image: ghcr.io/mailcow/nginx:1.30.4-1 image: ghcr.io/mailcow/nginx:1.30.3-1
dns: dns:
- ${IPV4_NETWORK:-172.22.1}.254 - ${IPV4_NETWORK:-172.22.1}.254
environment: environment:
@@ -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.98 image: ghcr.io/mailcow/acme:1.97
dns: dns:
- ${IPV4_NETWORK:-172.22.1}.254 - ${IPV4_NETWORK:-172.22.1}.254
environment: environment: