mirror of
https://github.com/mailcow/mailcow-dockerized.git
synced 2026-07-31 20:32:16 +00:00
Compare commits
95
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
489db90512 | ||
|
|
50be483c39 | ||
|
|
d64c923aca | ||
|
|
2d5f166ba8 | ||
|
|
94b5a623ab | ||
|
|
e406ecd461 | ||
|
|
68a95c3af3 | ||
|
|
bd037d5644 | ||
|
|
7036dbf4f8 | ||
|
|
2f10a4633f | ||
|
|
36c70db86c | ||
|
|
f4961c4023 | ||
|
|
38de21592c | ||
|
|
f44bd2f36a | ||
|
|
95a77f2dcb | ||
|
|
e856510fb2 | ||
|
|
d51d06d716 | ||
|
|
54170d075a | ||
|
|
b4bb1a625b | ||
|
|
2e5a29bf69 | ||
|
|
e245ac04d9 | ||
|
|
c877fdf0a5 | ||
|
|
fea38c8e1b | ||
|
|
cc9af65852 | ||
|
|
92cc8bec90 | ||
|
|
145745329e | ||
|
|
8e72d22c56 | ||
|
|
ddd76d99cd | ||
|
|
8c9524a2fe | ||
|
|
2ebd32d2ee | ||
|
|
641ed63782 | ||
|
|
4b62af9d02 | ||
|
|
14772c3a20 | ||
|
|
c17cc8a792 | ||
|
|
7dab6c63d1 | ||
|
|
6f7fee49cd | ||
|
|
96a70652c3 | ||
|
|
c1d75cf808 | ||
|
|
473fe2885d | ||
|
|
31e1550668 | ||
|
|
315f55bc65 | ||
|
|
e696ee2f6c | ||
|
|
d65aa11870 | ||
|
|
f6630e5b43 | ||
|
|
98e26c5603 | ||
|
|
3098caebbc | ||
|
|
94e23a3cd3 | ||
|
|
1840074991 | ||
|
|
99768e16f5 | ||
|
|
1f8b4679cc | ||
|
|
9e723e942d | ||
|
|
47e4552b44 | ||
|
|
7e6c36dce1 | ||
|
|
fa154c71f3 | ||
|
|
83a045ed3e | ||
|
|
064817ac70 | ||
|
|
2bd7a24b8c | ||
|
|
ecc2462e4c | ||
|
|
2d8db72d46 | ||
|
|
277a307fb9 | ||
|
|
d455555621 | ||
|
|
9ea2ff1dff | ||
|
|
24cc369d84 | ||
|
|
5f5367f2f9 | ||
|
|
03c31f825a | ||
|
|
c0050e8836 | ||
|
|
15891960f7 | ||
|
|
cce02e2b15 | ||
|
|
0fafda696b | ||
|
|
843db5854c | ||
|
|
23e4e4f373 | ||
|
|
2ac4b1deae | ||
|
|
ffbc37a00c | ||
|
|
1eb6d2e26c | ||
|
|
104af58628 | ||
|
|
384e2f6ac1 | ||
|
|
32156a337a | ||
|
|
8ead057ce9 | ||
|
|
b0873edb6a | ||
|
|
694d751915 | ||
|
|
281cf93db3 | ||
|
|
08278a8be9 | ||
|
|
f399c07c85 | ||
|
|
a693325fe6 | ||
|
|
9ad84eee92 | ||
|
|
b59869b720 | ||
|
|
7515bef66c | ||
|
|
b84ba8ded1 | ||
|
|
4845928e7a | ||
|
|
4ccfedd6b3 | ||
|
|
e8d9315d4a | ||
|
|
d977ddb501 | ||
|
|
e76f5237ed | ||
|
|
c11ed5dd1e | ||
|
|
4ef65fc382 |
@@ -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@v10.3.0
|
uses: actions/stale@v11.0.0
|
||||||
with:
|
with:
|
||||||
repo-token: ${{ secrets.STALE_ACTION_PAT }}
|
repo-token: ${{ secrets.STALE_ACTION_PAT }}
|
||||||
days-before-stale: 60
|
days-before-stale: 60
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ jobs:
|
|||||||
- "watchdog-mailcow"
|
- "watchdog-mailcow"
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v7
|
||||||
- name: Setup Docker
|
- name: Setup Docker
|
||||||
run: |
|
run: |
|
||||||
curl -sSL https://get.docker.com/ | CHANNEL=stable sudo sh
|
curl -sSL https://get.docker.com/ | CHANNEL=stable sudo sh
|
||||||
|
|||||||
@@ -8,11 +8,11 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Run the Action
|
- name: Run the Action
|
||||||
uses: devops-infra/action-pull-request@v1.2.1
|
uses: devops-infra/action-pull-request@v1.4.0
|
||||||
with:
|
with:
|
||||||
github_token: ${{ secrets.PRTONIGHTLY_ACTION_PAT }}
|
github_token: ${{ secrets.PRTONIGHTLY_ACTION_PAT }}
|
||||||
title: Automatic PR to nightly from ${{ github.event.repository.updated_at}}
|
title: Automatic PR to nightly from ${{ github.event.repository.updated_at}}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ jobs:
|
|||||||
packages: write
|
packages: write
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v4
|
uses: docker/setup-qemu-action@v4
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
|
|
||||||
- name: Generate postscreen_access.cidr
|
- name: Generate postscreen_access.cidr
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -23,6 +23,9 @@ 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
|
||||||
|
|||||||
@@ -249,12 +249,33 @@ 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
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM nginx:1.30.2-alpine
|
FROM nginx:1.30.4-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
|
||||||
|
|||||||
@@ -7,13 +7,13 @@ ARG APCU_PECL_VERSION=5.1.28
|
|||||||
# renovate: datasource=github-tags depName=Imagick/imagick versioning=semver-coerced extractVersion=(?<version>.*)$
|
# renovate: datasource=github-tags depName=Imagick/imagick versioning=semver-coerced extractVersion=(?<version>.*)$
|
||||||
ARG IMAGICK_PECL_VERSION=3.8.1
|
ARG IMAGICK_PECL_VERSION=3.8.1
|
||||||
# renovate: datasource=github-tags depName=php/pecl-mail-mailparse versioning=semver-coerced extractVersion=^v(?<version>.*)$
|
# renovate: datasource=github-tags depName=php/pecl-mail-mailparse versioning=semver-coerced extractVersion=^v(?<version>.*)$
|
||||||
ARG MAILPARSE_PECL_VERSION=3.1.9
|
ARG MAILPARSE_PECL_VERSION=3.2.0
|
||||||
# renovate: datasource=github-tags depName=php-memcached-dev/php-memcached versioning=semver-coerced extractVersion=^v(?<version>.*)$
|
# renovate: datasource=github-tags depName=php-memcached-dev/php-memcached versioning=semver-coerced extractVersion=^v(?<version>.*)$
|
||||||
ARG MEMCACHED_PECL_VERSION=3.4.0
|
ARG MEMCACHED_PECL_VERSION=3.4.0
|
||||||
# renovate: datasource=github-tags depName=phpredis/phpredis versioning=semver-coerced extractVersion=(?<version>.*)$
|
# renovate: datasource=github-tags depName=phpredis/phpredis versioning=semver-coerced extractVersion=(?<version>.*)$
|
||||||
ARG REDIS_PECL_VERSION=6.3.0
|
ARG REDIS_PECL_VERSION=6.3.0
|
||||||
# renovate: datasource=github-tags depName=composer/composer versioning=semver-coerced extractVersion=(?<version>.*)$
|
# renovate: datasource=github-tags depName=composer/composer versioning=semver-coerced extractVersion=(?<version>.*)$
|
||||||
ARG COMPOSER_VERSION=2.9.5
|
ARG COMPOSER_VERSION=2.10.2
|
||||||
|
|
||||||
RUN apk add -U --no-cache autoconf \
|
RUN apk add -U --no-cache autoconf \
|
||||||
aspell-dev \
|
aspell-dev \
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM debian:bookworm-slim
|
FROM debian:trixie-slim
|
||||||
|
|
||||||
LABEL maintainer="The Infrastructure Company GmbH <info@servercow.de>"
|
LABEL maintainer="The Infrastructure Company GmbH <info@servercow.de>"
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
@version: 3.38
|
@version: 4.8
|
||||||
@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: 3.38
|
@version: 4.8
|
||||||
@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 {
|
||||||
|
|||||||
@@ -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_3.14.3-1~236eb65
|
ARG RSPAMD_VER=rspamd_4.1.4-1~beb659b
|
||||||
ARG CODENAME=trixie
|
ARG CODENAME=trixie
|
||||||
ENV LC_ALL=C
|
ENV LC_ALL=C
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# SOGo built from source to enable security patch application
|
# SOGo built from source to enable security patch application
|
||||||
# Repository: https://github.com/Alinto/sogo
|
# Repository: https://github.com/Alinto/sogo
|
||||||
# Version: SOGo-5.12.8
|
# Version: SOGo-5.12.9
|
||||||
#
|
#
|
||||||
# Applied security patches:
|
# Applied security patches:
|
||||||
# -
|
# -
|
||||||
@@ -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.8
|
ARG SOGO_VERSION=SOGo-5.12.9
|
||||||
ARG SOPE_VERSION=SOPE-5.12.8
|
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>.*)$
|
||||||
|
|||||||
@@ -42,12 +42,19 @@ 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,6 +116,8 @@ 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;
|
||||||
|
# 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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ postscreen_access_list = permit_mynetworks,
|
|||||||
cidr:/opt/postfix/conf/postscreen_access.cidr,
|
cidr:/opt/postfix/conf/postscreen_access.cidr,
|
||||||
tcp:127.0.0.1:10027
|
tcp:127.0.0.1:10027
|
||||||
postscreen_bare_newline_enable = no
|
postscreen_bare_newline_enable = no
|
||||||
postscreen_blacklist_action = drop
|
postscreen_denylist_action = drop
|
||||||
postscreen_cache_cleanup_interval = 24h
|
postscreen_cache_cleanup_interval = 24h
|
||||||
postscreen_cache_map = proxy:btree:$data_directory/postscreen_cache
|
postscreen_cache_map = proxy:btree:$data_directory/postscreen_cache
|
||||||
postscreen_dnsbl_action = enforce
|
postscreen_dnsbl_action = enforce
|
||||||
@@ -107,8 +107,6 @@ smtpd_sender_restrictions = reject_authenticated_sender_login_mismatch,
|
|||||||
reject_unknown_sender_domain
|
reject_unknown_sender_domain
|
||||||
smtpd_soft_error_limit = 3
|
smtpd_soft_error_limit = 3
|
||||||
smtpd_tls_auth_only = yes
|
smtpd_tls_auth_only = yes
|
||||||
smtpd_tls_dh1024_param_file = /etc/ssl/mail/dhparams.pem
|
|
||||||
smtpd_tls_eecdh_grade = auto
|
|
||||||
smtpd_tls_exclude_ciphers = ECDHE-RSA-RC4-SHA, RC4, aNULL, DES-CBC3-SHA, ECDHE-RSA-DES-CBC3-SHA, EDH-RSA-DES-CBC3-SHA
|
smtpd_tls_exclude_ciphers = ECDHE-RSA-RC4-SHA, RC4, aNULL, DES-CBC3-SHA, ECDHE-RSA-DES-CBC3-SHA, EDH-RSA-DES-CBC3-SHA
|
||||||
smtpd_tls_loglevel = 1
|
smtpd_tls_loglevel = 1
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ smtps inet n - n - - smtpd
|
|||||||
# TLS protocol can be modified by setting submission_smtpd_tls_mandatory_protocols in extra.cf
|
# TLS protocol can be modified by setting submission_smtpd_tls_mandatory_protocols in extra.cf
|
||||||
submission inet n - n - - smtpd
|
submission inet n - n - - smtpd
|
||||||
-o smtpd_client_restrictions=permit_mynetworks,permit_sasl_authenticated,reject
|
-o smtpd_client_restrictions=permit_mynetworks,permit_sasl_authenticated,reject
|
||||||
-o smtpd_enforce_tls=yes
|
|
||||||
-o smtpd_tls_security_level=encrypt
|
-o smtpd_tls_security_level=encrypt
|
||||||
-o smtpd_tls_mandatory_protocols=$submission_smtpd_tls_mandatory_protocols
|
-o smtpd_tls_mandatory_protocols=$submission_smtpd_tls_mandatory_protocols
|
||||||
-o tls_preempt_cipherlist=yes
|
-o tls_preempt_cipherlist=yes
|
||||||
@@ -38,7 +37,6 @@ submission inet n - n - - smtpd
|
|||||||
10587 inet n - n - - smtpd
|
10587 inet n - n - - smtpd
|
||||||
-o smtpd_upstream_proxy_protocol=haproxy
|
-o smtpd_upstream_proxy_protocol=haproxy
|
||||||
-o smtpd_client_restrictions=permit_mynetworks,permit_sasl_authenticated,reject
|
-o smtpd_client_restrictions=permit_mynetworks,permit_sasl_authenticated,reject
|
||||||
-o smtpd_enforce_tls=yes
|
|
||||||
-o smtpd_tls_security_level=encrypt
|
-o smtpd_tls_security_level=encrypt
|
||||||
-o smtpd_tls_mandatory_protocols=$submission_smtpd_tls_mandatory_protocols
|
-o smtpd_tls_mandatory_protocols=$submission_smtpd_tls_mandatory_protocols
|
||||||
-o tls_preempt_cipherlist=yes
|
-o tls_preempt_cipherlist=yes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Whitelist generated by Postwhite v3.4 on Fri May 1 00:43:37 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/
|
||||||
# 2249 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
|
||||||
@@ -56,14 +56,8 @@
|
|||||||
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.250/31 permit
|
|
||||||
8.39.144.0/24 permit
|
8.39.144.0/24 permit
|
||||||
8.40.222.0/23 permit
|
|
||||||
8.40.222.250/31 permit
|
|
||||||
12.130.86.238 permit
|
12.130.86.238 permit
|
||||||
13.107.213.40 permit
|
|
||||||
13.107.246.40 permit
|
|
||||||
13.108.16.0/20 permit
|
13.108.16.0/20 permit
|
||||||
13.110.208.0/21 permit
|
13.110.208.0/21 permit
|
||||||
13.110.209.0/24 permit
|
13.110.209.0/24 permit
|
||||||
@@ -73,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
|
||||||
@@ -177,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
|
||||||
@@ -201,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
|
||||||
@@ -284,7 +275,6 @@
|
|||||||
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
|
||||||
@@ -341,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
|
||||||
@@ -408,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
|
||||||
@@ -1222,9 +1210,6 @@
|
|||||||
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
|
||||||
@@ -1385,9 +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.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
|
||||||
@@ -1470,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
|
||||||
@@ -1504,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
|
||||||
@@ -1554,12 +1521,14 @@
|
|||||||
147.243.128.26 permit
|
147.243.128.26 permit
|
||||||
148.105.0.0/16 permit
|
148.105.0.0/16 permit
|
||||||
148.105.8.0/21 permit
|
148.105.8.0/21 permit
|
||||||
|
148.116.32.128/25 permit
|
||||||
149.72.0.0/16 permit
|
149.72.0.0/16 permit
|
||||||
149.72.234.184 permit
|
149.72.234.184 permit
|
||||||
149.72.248.236 permit
|
149.72.248.236 permit
|
||||||
149.97.173.180 permit
|
149.97.173.180 permit
|
||||||
149.118.160.128/25 permit
|
149.118.160.128/25 permit
|
||||||
150.136.21.199 permit
|
150.136.21.199 permit
|
||||||
|
150.171.109.183 permit
|
||||||
150.230.98.160 permit
|
150.230.98.160 permit
|
||||||
151.145.38.14 permit
|
151.145.38.14 permit
|
||||||
152.67.105.195 permit
|
152.67.105.195 permit
|
||||||
@@ -1637,8 +1606,9 @@
|
|||||||
164.177.132.168/30 permit
|
164.177.132.168/30 permit
|
||||||
165.1.100.0/25 permit
|
165.1.100.0/25 permit
|
||||||
165.173.128.0/24 permit
|
165.173.128.0/24 permit
|
||||||
165.173.180.1 permit
|
165.173.180.0/24 permit
|
||||||
165.173.180.250/31 permit
|
165.173.180.250/31 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.0/22 permit
|
||||||
@@ -1673,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
|
||||||
@@ -1700,6 +1657,7 @@
|
|||||||
173.203.81.39 permit
|
173.203.81.39 permit
|
||||||
173.224.161.128/25 permit
|
173.224.161.128/25 permit
|
||||||
173.224.165.0/26 permit
|
173.224.165.0/26 permit
|
||||||
|
173.224.166.48/28 permit
|
||||||
174.36.84.8/29 permit
|
174.36.84.8/29 permit
|
||||||
174.36.84.16/29 permit
|
174.36.84.16/29 permit
|
||||||
174.36.84.32/29 permit
|
174.36.84.32/29 permit
|
||||||
@@ -1891,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
|
||||||
@@ -1956,8 +1905,6 @@
|
|||||||
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.160.0/21 permit
|
204.220.160.0/21 permit
|
||||||
204.220.168.0/21 permit
|
204.220.168.0/21 permit
|
||||||
@@ -2231,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:72::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
|
||||||
|
|||||||
@@ -3,8 +3,7 @@ rules {
|
|||||||
backend = "http";
|
backend = "http";
|
||||||
url = "http://nginx:9081/pipe.php";
|
url = "http://nginx:9081/pipe.php";
|
||||||
selector = "reject_no_global_bl";
|
selector = "reject_no_global_bl";
|
||||||
formatter = "default";
|
formatter = "multipart";
|
||||||
meta_headers = true;
|
|
||||||
}
|
}
|
||||||
RLINFO {
|
RLINFO {
|
||||||
backend = "http";
|
backend = "http";
|
||||||
@@ -16,8 +15,7 @@ rules {
|
|||||||
backend = "http";
|
backend = "http";
|
||||||
url = "http://nginx:9081/pushover.php";
|
url = "http://nginx:9081/pushover.php";
|
||||||
selector = "mailcow_rcpt";
|
selector = "mailcow_rcpt";
|
||||||
formatter = "json";
|
formatter = "multipart";
|
||||||
meta_headers = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,47 +32,42 @@ function parse_email($email) {
|
|||||||
$a = strrpos($email, '@');
|
$a = strrpos($email, '@');
|
||||||
return array('local' => substr($email, 0, $a), 'domain' => substr(substr($email, $a), 1));
|
return array('local' => substr($email, 0, $a), 'domain' => substr(substr($email, $a), 1));
|
||||||
}
|
}
|
||||||
if (!function_exists('getallheaders')) {
|
// rspamd metadata_exporter (multipart formatter):
|
||||||
function getallheaders() {
|
// - $_POST['metadata'] JSON with the rspamd metadata
|
||||||
if (!is_array($_SERVER)) {
|
// - $_FILES['message'] raw RFC822 message
|
||||||
return array();
|
if (empty($_POST['metadata']) || !isset($_FILES['message']) || $_FILES['message']['error'] !== UPLOAD_ERR_OK) {
|
||||||
}
|
error_log("QUARANTINE: missing multipart parts from rspamd" . PHP_EOL);
|
||||||
$headers = array();
|
http_response_code(400);
|
||||||
foreach ($_SERVER as $name => $value) {
|
exit;
|
||||||
if (substr($name, 0, 5) == 'HTTP_') {
|
|
||||||
$headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $headers;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$raw_data_content = file_get_contents('php://input');
|
$meta = json_decode($_POST['metadata'], true);
|
||||||
|
if (!is_array($meta)) {
|
||||||
|
error_log("QUARANTINE: cannot decode metadata JSON" . PHP_EOL);
|
||||||
|
http_response_code(400);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$raw_data_content = file_get_contents($_FILES['message']['tmp_name']);
|
||||||
$raw_data = mb_convert_encoding($raw_data_content, 'HTML-ENTITIES', "UTF-8");
|
$raw_data = mb_convert_encoding($raw_data_content, 'HTML-ENTITIES', "UTF-8");
|
||||||
$headers = getallheaders();
|
$raw_size = (int)$_FILES['message']['size'];
|
||||||
|
|
||||||
$qid = $headers['X-Rspamd-Qid'];
|
$qid = $meta['qid'] ?? 'unknown';
|
||||||
$fuzzy = $headers['X-Rspamd-Fuzzy'];
|
$subject = $meta['subject'] ?? '';
|
||||||
$subject = iconv_mime_decode($headers['X-Rspamd-Subject']);
|
$score = $meta['score'] ?? 0;
|
||||||
$score = $headers['X-Rspamd-Score'];
|
$rcpts = $meta['rcpt'] ?? array();
|
||||||
$rcpts = $headers['X-Rspamd-Rcpt'];
|
$user = $meta['user'] ?? 'unknown';
|
||||||
$user = $headers['X-Rspamd-User'];
|
$ip = $meta['ip'] ?? 'unknown';
|
||||||
$ip = $headers['X-Rspamd-Ip'];
|
$action = $meta['action'] ?? 'no action';
|
||||||
$action = $headers['X-Rspamd-Action'];
|
$sender = $meta['from'] ?? '';
|
||||||
$sender = $headers['X-Rspamd-From'];
|
$symbols = json_encode($meta['symbols'] ?? array());
|
||||||
$symbols = $headers['X-Rspamd-Symbols'];
|
$fuzzy = json_encode(is_array($meta['fuzzy'] ?? null) ? $meta['fuzzy'] : array());
|
||||||
|
|
||||||
$raw_size = (int)$_SERVER['CONTENT_LENGTH'];
|
|
||||||
|
|
||||||
if (empty($sender)) {
|
if (empty($sender)) {
|
||||||
error_log("QUARANTINE: Unknown sender, assuming empty-env-from@localhost" . PHP_EOL);
|
error_log("QUARANTINE: Unknown sender, assuming empty-env-from@localhost" . PHP_EOL);
|
||||||
$sender = 'empty-env-from@localhost';
|
$sender = 'empty-env-from@localhost';
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($fuzzy == 'unknown') {
|
|
||||||
$fuzzy = '[]';
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$max_size = (int)$redis->Get('Q_MAX_SIZE');
|
$max_size = (int)$redis->Get('Q_MAX_SIZE');
|
||||||
if (($max_size * 1048576) < $raw_size) {
|
if (($max_size * 1048576) < $raw_size) {
|
||||||
@@ -94,7 +89,7 @@ catch (RedisException $e) {
|
|||||||
$rcpt_final_mailboxes = array();
|
$rcpt_final_mailboxes = array();
|
||||||
|
|
||||||
// Loop through all rcpts
|
// Loop through all rcpts
|
||||||
foreach (json_decode($rcpts, true) as $rcpt) {
|
foreach ($rcpts as $rcpt) {
|
||||||
// Remove tag
|
// Remove tag
|
||||||
$rcpt = preg_replace('/^(.*?)\+.*(@.*)$/', '$1$2', $rcpt);
|
$rcpt = preg_replace('/^(.*?)\+.*(@.*)$/', '$1$2', $rcpt);
|
||||||
|
|
||||||
|
|||||||
@@ -32,50 +32,46 @@ function parse_email($email) {
|
|||||||
$a = strrpos($email, '@');
|
$a = strrpos($email, '@');
|
||||||
return array('local' => substr($email, 0, $a), 'domain' => substr(substr($email, $a), 1));
|
return array('local' => substr($email, 0, $a), 'domain' => substr(substr($email, $a), 1));
|
||||||
}
|
}
|
||||||
if (!function_exists('getallheaders')) {
|
// rspamd metadata_exporter (multipart formatter): metadata JSON arrives as $_POST['metadata'].
|
||||||
function getallheaders() {
|
if (empty($_POST['metadata'])) {
|
||||||
if (!is_array($_SERVER)) {
|
error_log("NOTIFY: missing metadata part from rspamd" . PHP_EOL);
|
||||||
return array();
|
http_response_code(400);
|
||||||
}
|
exit;
|
||||||
$headers = array();
|
|
||||||
foreach ($_SERVER as $name => $value) {
|
|
||||||
if (substr($name, 0, 5) == 'HTTP_') {
|
|
||||||
$headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $headers;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$headers = getallheaders();
|
$meta = json_decode($_POST['metadata'], true);
|
||||||
$json_body = json_decode(file_get_contents('php://input'));
|
if (!is_array($meta)) {
|
||||||
|
error_log("NOTIFY: cannot decode metadata JSON" . PHP_EOL);
|
||||||
|
http_response_code(400);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
$qid = $headers['X-Rspamd-Qid'];
|
$qid = $meta['qid'] ?? 'unknown';
|
||||||
$rcpts = $headers['X-Rspamd-Rcpt'];
|
$rcpts = $meta['rcpt'] ?? array();
|
||||||
$sender = $headers['X-Rspamd-From'];
|
$sender = $meta['from'] ?? '';
|
||||||
$ip = $headers['X-Rspamd-Ip'];
|
$ip = $meta['ip'] ?? 'unknown';
|
||||||
$subject = iconv_mime_decode($headers['X-Rspamd-Subject']);
|
$subject = $meta['subject'] ?? '';
|
||||||
$messageid= $json_body->message_id;
|
$messageid= $meta['message_id'] ?? '';
|
||||||
$priority = 0;
|
$priority = 0;
|
||||||
|
|
||||||
$symbols_array = json_decode($headers['X-Rspamd-Symbols'], true);
|
$symbols_array = $meta['symbols'] ?? array();
|
||||||
if (is_array($symbols_array)) {
|
if (is_array($symbols_array)) {
|
||||||
foreach ($symbols_array as $symbol) {
|
foreach ($symbols_array as $symbol) {
|
||||||
if ($symbol['name'] == 'HAS_X_PRIO_ONE') {
|
if (($symbol['name'] ?? null) == 'HAS_X_PRIO_ONE') {
|
||||||
$priority = 1;
|
$priority = 1;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$sender_address = $json_body->header_from[0];
|
$sender_address = $meta['header_from'][0] ?? '';
|
||||||
$sender_name = '-';
|
$sender_name = '-';
|
||||||
if (preg_match('/(?<name>.*?)<(?<address>.*?)>/i', $sender_address, $matches)) {
|
if (preg_match('/(?<name>.*?)<(?<address>.*?)>/i', $sender_address, $matches)) {
|
||||||
$sender_address = $matches['address'];
|
$sender_address = $matches['address'];
|
||||||
$sender_name = trim($matches['name'], '"\' ');
|
$sender_name = trim($matches['name'], '"\' ');
|
||||||
}
|
}
|
||||||
|
|
||||||
$to_address = $json_body->header_to[0];
|
$to_address = $meta['header_to'][0] ?? '';
|
||||||
$to_name = '-';
|
$to_name = '-';
|
||||||
if (preg_match('/(?<name>.*?)<(?<address>.*?)>/i', $to_address, $matches)) {
|
if (preg_match('/(?<name>.*?)<(?<address>.*?)>/i', $to_address, $matches)) {
|
||||||
$to_address = $matches['address'];
|
$to_address = $matches['address'];
|
||||||
@@ -85,7 +81,7 @@ if (preg_match('/(?<name>.*?)<(?<address>.*?)>/i', $to_address, $matches)) {
|
|||||||
$rcpt_final_mailboxes = array();
|
$rcpt_final_mailboxes = array();
|
||||||
|
|
||||||
// Loop through all rcpts
|
// Loop through all rcpts
|
||||||
foreach (json_decode($rcpts, true) as $rcpt) {
|
foreach ($rcpts as $rcpt) {
|
||||||
// Remove tag
|
// Remove tag
|
||||||
$rcpt = preg_replace('/^(.*?)\+.*(@.*)$/', '$1$2', $rcpt);
|
$rcpt = preg_replace('/^(.*?)\+.*(@.*)$/', '$1$2', $rcpt);
|
||||||
|
|
||||||
|
|||||||
@@ -185,6 +185,9 @@ 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'
|
||||||
@@ -192,6 +195,15 @@ 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:
|
||||||
@@ -1072,6 +1084,7 @@ paths:
|
|||||||
password2: "*"
|
password2: "*"
|
||||||
quota: "3072"
|
quota: "3072"
|
||||||
force_pw_update: "1"
|
force_pw_update: "1"
|
||||||
|
force_tfa: "1"
|
||||||
tls_enforce_in: "1"
|
tls_enforce_in: "1"
|
||||||
tls_enforce_out: "1"
|
tls_enforce_out: "1"
|
||||||
tags: ["tag1", "tag2"]
|
tags: ["tag1", "tag2"]
|
||||||
@@ -1118,6 +1131,7 @@ paths:
|
|||||||
password2: atedismonsin
|
password2: atedismonsin
|
||||||
quota: "3072"
|
quota: "3072"
|
||||||
force_pw_update: "1"
|
force_pw_update: "1"
|
||||||
|
force_tfa: "1"
|
||||||
tls_enforce_in: "1"
|
tls_enforce_in: "1"
|
||||||
tls_enforce_out: "1"
|
tls_enforce_out: "1"
|
||||||
tags: ["tag1", "tag2"]
|
tags: ["tag1", "tag2"]
|
||||||
@@ -1151,6 +1165,9 @@ paths:
|
|||||||
force_pw_update:
|
force_pw_update:
|
||||||
description: forces the user to update its password on first login
|
description: forces the user to update its password on first login
|
||||||
type: boolean
|
type: boolean
|
||||||
|
force_tfa:
|
||||||
|
description: force 2FA enrollment at login
|
||||||
|
type: boolean
|
||||||
tls_enforce_in:
|
tls_enforce_in:
|
||||||
description: force inbound email tls encryption
|
description: force inbound email tls encryption
|
||||||
type: boolean
|
type: boolean
|
||||||
@@ -3414,6 +3431,7 @@ paths:
|
|||||||
- mailbox
|
- mailbox
|
||||||
- active: "1"
|
- active: "1"
|
||||||
force_pw_update: "0"
|
force_pw_update: "0"
|
||||||
|
force_tfa: "0"
|
||||||
name: Full name
|
name: Full name
|
||||||
password: "*"
|
password: "*"
|
||||||
password2: "*"
|
password2: "*"
|
||||||
@@ -3464,6 +3482,7 @@ paths:
|
|||||||
attr:
|
attr:
|
||||||
active: "1"
|
active: "1"
|
||||||
force_pw_update: "0"
|
force_pw_update: "0"
|
||||||
|
force_tfa: "0"
|
||||||
name: Full name
|
name: Full name
|
||||||
authsource: mailcow
|
authsource: mailcow
|
||||||
password: ""
|
password: ""
|
||||||
@@ -3487,6 +3506,9 @@ paths:
|
|||||||
force_pw_update:
|
force_pw_update:
|
||||||
description: force user to change password on next login
|
description: force user to change password on next login
|
||||||
type: boolean
|
type: boolean
|
||||||
|
force_tfa:
|
||||||
|
description: force 2FA enrollment at login
|
||||||
|
type: boolean
|
||||||
name:
|
name:
|
||||||
description: Full name of the mailbox user
|
description: Full name of the mailbox user
|
||||||
type: string
|
type: string
|
||||||
@@ -4881,6 +4903,7 @@ paths:
|
|||||||
- active: "1"
|
- active: "1"
|
||||||
attributes:
|
attributes:
|
||||||
force_pw_update: "0"
|
force_pw_update: "0"
|
||||||
|
force_tfa: "0"
|
||||||
mailbox_format: "maildir:"
|
mailbox_format: "maildir:"
|
||||||
quarantine_notification: never
|
quarantine_notification: never
|
||||||
sogo_access: "1"
|
sogo_access: "1"
|
||||||
@@ -4897,6 +4920,7 @@ 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"]
|
||||||
@@ -5805,6 +5829,7 @@ paths:
|
|||||||
- active: "1"
|
- active: "1"
|
||||||
attributes:
|
attributes:
|
||||||
force_pw_update: "0"
|
force_pw_update: "0"
|
||||||
|
force_tfa: "0"
|
||||||
mailbox_format: "maildir:"
|
mailbox_format: "maildir:"
|
||||||
quarantine_notification: never
|
quarantine_notification: never
|
||||||
sogo_access: "1"
|
sogo_access: "1"
|
||||||
@@ -5822,6 +5847,7 @@ 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"]
|
||||||
@@ -5844,7 +5870,7 @@ paths:
|
|||||||
response:
|
response:
|
||||||
value:
|
value:
|
||||||
- type: "success"
|
- type: "success"
|
||||||
log: ["cors", "edit", {"allowed_origins": ["*", "mail.mailcow.tld"], "allowed_methods": ["POST", "GET", "DELETE", "PUT"]}]
|
log: ["cors", "edit", {"allowed_origins": ["*", "https://mail.mailcow.tld"], "allowed_methods": ["POST", "GET", "DELETE", "PUT"]}]
|
||||||
msg: "cors_headers_edited"
|
msg: "cors_headers_edited"
|
||||||
description: OK
|
description: OK
|
||||||
headers: { }
|
headers: { }
|
||||||
@@ -5861,7 +5887,7 @@ paths:
|
|||||||
schema:
|
schema:
|
||||||
example:
|
example:
|
||||||
attr:
|
attr:
|
||||||
allowed_origins: ["*", "mail.mailcow.tld"]
|
allowed_origins: ["*", "https://mail.mailcow.tld"]
|
||||||
allowed_methods: ["POST", "GET", "DELETE", "PUT"]
|
allowed_methods: ["POST", "GET", "DELETE", "PUT"]
|
||||||
properties:
|
properties:
|
||||||
attr:
|
attr:
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
<?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;
|
||||||
@@ -429,14 +429,25 @@ function domain_admin_sso($_action, $_data) {
|
|||||||
|
|
||||||
switch ($_action) {
|
switch ($_action) {
|
||||||
case 'check':
|
case 'check':
|
||||||
$token = $_data;
|
$token = preg_replace('/[^a-zA-Z0-9-]/', '', $_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' => preg_replace('/[^a-zA-Z0-9-]/', '', $token)
|
':token' => $token
|
||||||
));
|
));
|
||||||
$return = $stmt->fetch(PDO::FETCH_ASSOC);
|
$return = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
return empty($return['username']) ? false : $return['username'];
|
if (empty($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(
|
||||||
|
|||||||
@@ -53,6 +53,39 @@ 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) {
|
||||||
@@ -2283,10 +2316,13 @@ function cors($action, $data = null) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$allowed_origins = isset($data['allowed_origins']) ? $data['allowed_origins'] : array($_SERVER['SERVER_NAME']);
|
$allowed_origins = isset($data['allowed_origins']) ? $data['allowed_origins'] : array(getBaseURL());
|
||||||
$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 (!filter_var($origin, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME) && $origin != '*') {
|
if ($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),
|
||||||
@@ -2294,7 +2330,10 @@ 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;
|
||||||
@@ -2342,26 +2381,35 @@ function cors($action, $data = null) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
$cors_settings = !$cors_settings ? array('allowed_origins' => $_SERVER['SERVER_NAME'], 'allowed_methods' => 'GET, POST, PUT, DELETE') : $cors_settings;
|
$cors_settings = !$cors_settings ? array('allowed_origins' => getBaseURL(), 'allowed_methods' => 'GET, POST, PUT, DELETE') : $cors_settings;
|
||||||
$cors_settings['allowed_origins'] = empty($cors_settings['allowed_origins']) ? $_SERVER['SERVER_NAME'] : $cors_settings['allowed_origins'];
|
$cors_settings['allowed_origins'] = empty($cors_settings['allowed_origins']) ? getBaseURL() : $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
|
||||||
$allowed_origins = explode(', ', $cors_settings['allowed_origins']);
|
$allow_origin = null;
|
||||||
$cors_settings['allowed_origins'] = $allowed_origins[0];
|
if (in_array('*', $allowed_origins, true)) {
|
||||||
if (in_array('*', $allowed_origins)){
|
$allow_origin = '*';
|
||||||
$cors_settings['allowed_origins'] = '*';
|
} else if ($origin !== false && in_array($origin, $allowed_origins, true)) {
|
||||||
} else if (array_key_exists('HTTP_ORIGIN', $_SERVER) && in_array($_SERVER['HTTP_ORIGIN'], $allowed_origins)) {
|
$allow_origin = $origin;
|
||||||
$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';
|
||||||
|
|
||||||
header('Access-Control-Allow-Origin: ' . $cors_settings['allowed_origins']);
|
// a disallowed origin gets no Access-Control-Allow-Origin at all; echoing a different origin than the requesting one tells a browser nothing
|
||||||
|
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');
|
||||||
|
|
||||||
|
|||||||
@@ -41,7 +41,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
else {
|
else {
|
||||||
$username = $_SESSION['mailcow_cc_username'];
|
$username = $_SESSION['mailcow_cc_username'];
|
||||||
}
|
}
|
||||||
if (isset($_data["validity"]) && !filter_var($_data["validity"], FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 87600)))) {
|
if (isset($_data["validity"])) {
|
||||||
|
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),
|
||||||
@@ -49,6 +50,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
// Default to 1 yr
|
// Default to 1 yr
|
||||||
$_data["validity"] = 8760;
|
$_data["validity"] = 8760;
|
||||||
@@ -60,7 +62,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
$permanent = 0;
|
$permanent = 0;
|
||||||
}
|
}
|
||||||
$domain = $_data['domain'];
|
$domain = $_data['domain'];
|
||||||
$description = $_data['description'];
|
// spamalias.description is NOT NULL, an absent description must not become a null insert
|
||||||
|
$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)) {
|
||||||
@@ -3505,7 +3508,6 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
// Track affected mailboxes for SOGo update
|
// Track affected mailboxes for SOGo update
|
||||||
$update_sogo_mailboxes[] = $username;
|
$update_sogo_mailboxes[] = $username;
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
break;
|
break;
|
||||||
case 'mailbox_rename':
|
case 'mailbox_rename':
|
||||||
$domain = $_data['domain'];
|
$domain = $_data['domain'];
|
||||||
@@ -3828,6 +3830,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
$attr["rl_frame"] = (!empty($_data['rl_frame'])) ? $_data['rl_frame'] : $is_now['rl_frame'];
|
$attr["rl_frame"] = (!empty($_data['rl_frame'])) ? $_data['rl_frame'] : $is_now['rl_frame'];
|
||||||
$attr["rl_value"] = (!empty($_data['rl_value'])) ? $_data['rl_value'] : $is_now['rl_value'];
|
$attr["rl_value"] = (!empty($_data['rl_value'])) ? $_data['rl_value'] : $is_now['rl_value'];
|
||||||
$attr["force_pw_update"] = isset($_data['force_pw_update']) ? intval($_data['force_pw_update']) : $is_now['force_pw_update'];
|
$attr["force_pw_update"] = isset($_data['force_pw_update']) ? intval($_data['force_pw_update']) : $is_now['force_pw_update'];
|
||||||
|
$attr["force_tfa"] = isset($_data['force_tfa']) ? intval($_data['force_tfa']) : $is_now['force_tfa'];
|
||||||
$attr["sogo_access"] = isset($_data['sogo_access']) ? intval($_data['sogo_access']) : $is_now['sogo_access'];
|
$attr["sogo_access"] = isset($_data['sogo_access']) ? intval($_data['sogo_access']) : $is_now['sogo_access'];
|
||||||
$attr["active"] = isset($_data['active']) ? intval($_data['active']) : $is_now['active'];
|
$attr["active"] = isset($_data['active']) ? intval($_data['active']) : $is_now['active'];
|
||||||
$attr["tls_enforce_in"] = isset($_data['tls_enforce_in']) ? intval($_data['tls_enforce_in']) : $is_now['tls_enforce_in'];
|
$attr["tls_enforce_in"] = isset($_data['tls_enforce_in']) ? intval($_data['tls_enforce_in']) : $is_now['tls_enforce_in'];
|
||||||
@@ -5320,6 +5323,14 @@ 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");
|
||||||
@@ -5647,8 +5658,11 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
$stmt->execute(array(
|
$stmt->execute(array(
|
||||||
':username' => $username
|
':username' => $username
|
||||||
));
|
));
|
||||||
$stmt = $pdo->prepare("DELETE FROM `sogo_acl` WHERE `c_object` LIKE '%/" . $username . "/%' OR `c_uid` = :username");
|
// bind LIKE pattern + escape LIKE wildcards so the value matches literally (no SQLi, no over-match)
|
||||||
|
$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)");
|
||||||
@@ -6044,8 +6058,11 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
$stmt->execute(array(
|
$stmt->execute(array(
|
||||||
':username' => $username
|
':username' => $username
|
||||||
));
|
));
|
||||||
$stmt = $pdo->prepare("DELETE FROM `sogo_acl` WHERE `c_object` LIKE '%/" . str_replace('%', '\%', $username) . "/%' OR `c_uid` = :username");
|
// bind LIKE pattern + escape LIKE wildcards so the value matches literally (no SQLi, no over-match)
|
||||||
|
$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)");
|
||||||
@@ -6127,7 +6144,6 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
// Track affected mailboxes for SOGo update
|
// Track affected mailboxes for SOGo update
|
||||||
$update_sogo_mailboxes[] = $username;
|
$update_sogo_mailboxes[] = $username;
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
break;
|
break;
|
||||||
case 'mailbox_templates':
|
case 'mailbox_templates':
|
||||||
if ($_SESSION['mailcow_cc_role'] != "admin") {
|
if ($_SESSION['mailcow_cc_role'] != "admin") {
|
||||||
@@ -6202,8 +6218,11 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
$stmt->execute(array(
|
$stmt->execute(array(
|
||||||
':username' => $name
|
':username' => $name
|
||||||
));
|
));
|
||||||
$stmt = $pdo->prepare("DELETE FROM `sogo_acl` WHERE `c_object` LIKE '%/" . $name . "/%' OR `c_uid` = :username");
|
// bind LIKE pattern + escape LIKE wildcards so the value matches literally (no SQLi, no over-match)
|
||||||
|
$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)");
|
||||||
|
|||||||
@@ -170,6 +170,8 @@ 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)),
|
||||||
@@ -465,12 +467,14 @@ 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', str_replace("\n.", '', $row['msg']) . chr(10) . '.' . chr(10)),
|
array('354', $row['msg'] . chr(10) . '.' . chr(10)),
|
||||||
array('250', 'QUIT' . chr(10)),
|
array('250', 'QUIT' . chr(10)),
|
||||||
array('221', '')
|
array('221', '')
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1464,6 +1464,8 @@ 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,
|
||||||
|
|||||||
@@ -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"){
|
if (mailcow_info.branch === "master" && mailcow_cc_role === "admin"){
|
||||||
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,6 +1089,8 @@ 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);
|
||||||
@@ -1212,6 +1214,11 @@ 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";
|
||||||
}
|
}
|
||||||
@@ -1669,34 +1676,25 @@ 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];
|
|
||||||
|
|
||||||
// get details about latest release
|
window.fetch("/inc/ajax/update_check.php", {method:'GET',cache:'no-cache'}).then(function(response) {
|
||||||
window.fetch("https://api.github.com/repos/"+github_account+"/"+github_repo_name+"/releases/latest", {method:'GET',cache:'no-cache'}).then(function(response) {
|
if (!response.ok) throw new Error("update check returned " + response.status);
|
||||||
return response.json();
|
return response.json();
|
||||||
}).then(function(latest_data) {
|
}).then(function(data) {
|
||||||
// get details about current release
|
if (data.status === "no_update") {
|
||||||
window.fetch("https://api.github.com/repos/"+github_account+"/"+github_repo_name+"/releases/tags/"+current_version, {method:'GET',cache:'no-cache'}).then(function(response) {
|
|
||||||
return response.json();
|
|
||||||
}).then(function(current_data) {
|
|
||||||
// compare releases
|
|
||||||
var date_current = new Date(current_data.created_at);
|
|
||||||
var date_latest = new Date(latest_data.created_at);
|
|
||||||
if (date_latest.getTime() <= date_current.getTime()){
|
|
||||||
// no update available
|
|
||||||
$("#mailcow_update").removeClass("text-warning text-danger").addClass("text-success");
|
$("#mailcow_update").removeClass("text-warning text-danger").addClass("text-success");
|
||||||
$("#mailcow_update").html("<b>" + lang_debug.no_update_available + "</b>");
|
$("#mailcow_update").html("<b>" + lang_debug.no_update_available + "</b>");
|
||||||
} else {
|
} else if (data.status === "update_available") {
|
||||||
// update available
|
|
||||||
$("#mailcow_update").removeClass("text-danger text-success").addClass("text-warning");
|
$("#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").html(lang_debug.update_available + ` <a href="#" id="mailcow_update_changelog">`+data.latest_tag+`</a>`);
|
||||||
$("#mailcow_update_changelog").click(function(){
|
$("#mailcow_update_changelog").click(function(){
|
||||||
if (mailcow_cc_role !== "admin" && mailcow_cc_role !== "domainadmin")
|
if (mailcow_cc_role !== "admin" && mailcow_cc_role !== "domainadmin")
|
||||||
return;
|
return;
|
||||||
|
|
||||||
showVersionModal("New Release " + latest_data.tag_name, latest_data.tag_name);
|
showVersionModal("New Release " + data.latest_tag, data.latest_tag);
|
||||||
})
|
})
|
||||||
|
} else {
|
||||||
|
throw new Error(data.message || "update check failed");
|
||||||
}
|
}
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
// err
|
// err
|
||||||
@@ -1704,12 +1702,6 @@ function check_update(current_version, github_repo_url){
|
|||||||
$("#mailcow_update").removeClass("text-success text-warning").addClass("text-danger");
|
$("#mailcow_update").removeClass("text-success text-warning").addClass("text-danger");
|
||||||
$("#mailcow_update").html("<b>"+ lang_debug.update_failed +"</b>");
|
$("#mailcow_update").html("<b>"+ lang_debug.update_failed +"</b>");
|
||||||
});
|
});
|
||||||
}).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>");
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
// show version changelog modal
|
// show version changelog modal
|
||||||
function showVersionModal(title, version){
|
function showVersionModal(title, version){
|
||||||
|
|||||||
@@ -424,6 +424,11 @@ $(document).ready(function() {
|
|||||||
} else {
|
} else {
|
||||||
$('#force_pw_update').prop('checked', false);
|
$('#force_pw_update').prop('checked', false);
|
||||||
}
|
}
|
||||||
|
if (template.force_tfa == 1){
|
||||||
|
$('#force_tfa').prop('checked', true);
|
||||||
|
} else {
|
||||||
|
$('#force_tfa').prop('checked', false);
|
||||||
|
}
|
||||||
if (template.sogo_access == 1){
|
if (template.sogo_access == 1){
|
||||||
$('#sogo_access').prop('checked', true);
|
$('#sogo_access').prop('checked', true);
|
||||||
} else {
|
} else {
|
||||||
@@ -991,6 +996,7 @@ 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 = '';
|
||||||
@@ -1242,6 +1248,7 @@ jQuery(function($){
|
|||||||
item.attributes.eas_access = '<i class="text-' + (item.attributes.eas_access == 1 ? 'success' : 'danger') + ' bi bi-' + (item.attributes.eas_access == 1 ? 'check-lg' : 'x-lg') + '"><span class="sorting-value">' + (item.attributes.eas_access == 1 ? '1' : '0') + '</span></i>';
|
item.attributes.eas_access = '<i class="text-' + (item.attributes.eas_access == 1 ? 'success' : 'danger') + ' bi bi-' + (item.attributes.eas_access == 1 ? 'check-lg' : 'x-lg') + '"><span class="sorting-value">' + (item.attributes.eas_access == 1 ? '1' : '0') + '</span></i>';
|
||||||
item.attributes.dav_access = '<i class="text-' + (item.attributes.dav_access == 1 ? 'success' : 'danger') + ' bi bi-' + (item.attributes.dav_access == 1 ? 'check-lg' : 'x-lg') + '"><span class="sorting-value">' + (item.attributes.dav_access == 1 ? '1' : '0') + '</span></i>';
|
item.attributes.dav_access = '<i class="text-' + (item.attributes.dav_access == 1 ? 'success' : 'danger') + ' bi bi-' + (item.attributes.dav_access == 1 ? 'check-lg' : 'x-lg') + '"><span class="sorting-value">' + (item.attributes.dav_access == 1 ? '1' : '0') + '</span></i>';
|
||||||
item.attributes.sogo_access = '<i class="text-' + (item.attributes.sogo_access == 1 ? 'success' : 'danger') + ' bi bi-' + (item.attributes.sogo_access == 1 ? 'check-lg' : 'x-lg') + '"><span class="sorting-value">' + (item.attributes.sogo_access == 1 ? '1' : '0') + '</span></i>';
|
item.attributes.sogo_access = '<i class="text-' + (item.attributes.sogo_access == 1 ? 'success' : 'danger') + ' bi bi-' + (item.attributes.sogo_access == 1 ? 'check-lg' : 'x-lg') + '"><span class="sorting-value">' + (item.attributes.sogo_access == 1 ? '1' : '0') + '</span></i>';
|
||||||
|
item.attributes.force_tfa = '<i class="text-' + (item.attributes.force_tfa == 1 ? 'success' : 'danger') + ' bi bi-' + (item.attributes.force_tfa == 1 ? 'check-lg' : 'x-lg') + '"><span class="sorting-value">' + (item.attributes.force_tfa == 1 ? '1' : '0') + '</span></i>';
|
||||||
if (item.attributes.quarantine_notification === 'never') {
|
if (item.attributes.quarantine_notification === 'never') {
|
||||||
item.attributes.quarantine_notification = lang.never;
|
item.attributes.quarantine_notification = lang.never;
|
||||||
} else if (item.attributes.quarantine_notification === 'hourly') {
|
} else if (item.attributes.quarantine_notification === 'hourly') {
|
||||||
@@ -1385,6 +1392,11 @@ jQuery(function($){
|
|||||||
return 1==data?'<i class="bi bi-check-lg"></i>':'<i class="bi bi-x-lg"></i>';
|
return 1==data?'<i class="bi bi-check-lg"></i>':'<i class="bi bi-x-lg"></i>';
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: lang.force_tfa,
|
||||||
|
data: 'attributes.force_tfa',
|
||||||
|
defaultContent: ''
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: lang_edit.ratelimit,
|
title: lang_edit.ratelimit,
|
||||||
data: 'attributes.ratelimit',
|
data: 'attributes.ratelimit',
|
||||||
|
|||||||
@@ -728,6 +728,7 @@
|
|||||||
"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",
|
||||||
@@ -1086,7 +1087,7 @@
|
|||||||
"rspamd_result": "Rspamd-Ergebnis",
|
"rspamd_result": "Rspamd-Ergebnis",
|
||||||
"sender": "Sender (SMTP)",
|
"sender": "Sender (SMTP)",
|
||||||
"sender_header": "Sender (\"From\"-Header)",
|
"sender_header": "Sender (\"From\"-Header)",
|
||||||
"settings_info": "Maximale Anzahl der zurückgehaltenen E-Mails: %s<br>Maximale Größe einer zu speichernden E-Mail: %s MiB",
|
"settings_info": "Maximale Anzahl der zurückgehaltenen E-Mails (pro Mailbox): %s<br>Maximale Größe einer zu speichernden E-Mail: %s MiB",
|
||||||
"show_item": "Details",
|
"show_item": "Details",
|
||||||
"spam": "Spam",
|
"spam": "Spam",
|
||||||
"spam_score": "Bewertung",
|
"spam_score": "Bewertung",
|
||||||
|
|||||||
@@ -728,6 +728,7 @@
|
|||||||
"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",
|
||||||
@@ -929,6 +930,7 @@
|
|||||||
"filters": "Filters",
|
"filters": "Filters",
|
||||||
"fname": "Full name",
|
"fname": "Full name",
|
||||||
"force_pw_update": "Force password update at next login",
|
"force_pw_update": "Force password update at next login",
|
||||||
|
"force_tfa": "TFA",
|
||||||
"gal": "Global Address List",
|
"gal": "Global Address List",
|
||||||
"goto_ham": "Learn as <b>ham</b>",
|
"goto_ham": "Learn as <b>ham</b>",
|
||||||
"goto_spam": "Learn as <b>spam</b>",
|
"goto_spam": "Learn as <b>spam</b>",
|
||||||
@@ -1086,7 +1088,7 @@
|
|||||||
"rspamd_result": "Rspamd result",
|
"rspamd_result": "Rspamd result",
|
||||||
"sender": "Sender (SMTP)",
|
"sender": "Sender (SMTP)",
|
||||||
"sender_header": "Sender (\"From\" header)",
|
"sender_header": "Sender (\"From\" header)",
|
||||||
"settings_info": "Maximum amount of elements to be quarantined: %s<br>Maximum email size: %s MiB",
|
"settings_info": "Maximum amount of elements to be quarantined (per mailbox): %s<br>Maximum email size: %s MiB",
|
||||||
"show_item": "Show item",
|
"show_item": "Show item",
|
||||||
"spam": "Spam",
|
"spam": "Spam",
|
||||||
"spam_score": "Score",
|
"spam_score": "Score",
|
||||||
|
|||||||
@@ -111,7 +111,8 @@
|
|||||||
"app_passwd_protocols": "Protocoles autorisés pour le mot de passe de l'application",
|
"app_passwd_protocols": "Protocoles autorisés pour le mot de passe de l'application",
|
||||||
"dry": "Simuler la synchronisation",
|
"dry": "Simuler la synchronisation",
|
||||||
"internal": "Interne",
|
"internal": "Interne",
|
||||||
"internal_info": "Les alias internes sont accessibles uniquement depuis le domaine ou les alias du domaine."
|
"internal_info": "Les alias internes sont accessibles uniquement depuis le domaine ou les alias du domaine.",
|
||||||
|
"sender_allowed": "Autoriser l'envoi sous cet alias"
|
||||||
},
|
},
|
||||||
"admin": {
|
"admin": {
|
||||||
"access": "Accès",
|
"access": "Accès",
|
||||||
@@ -556,7 +557,9 @@
|
|||||||
"max_age_invalid": "L'âge maximum %s est invalide",
|
"max_age_invalid": "L'âge maximum %s est invalide",
|
||||||
"mode_invalid": "Le mode %s est invalide",
|
"mode_invalid": "Le mode %s est invalide",
|
||||||
"mx_invalid": "L'enregistrement MX %s est invalide",
|
"mx_invalid": "L'enregistrement MX %s est invalide",
|
||||||
"version_invalid": "La version %s est invalide"
|
"version_invalid": "La version %s est invalide",
|
||||||
|
"tfa_removal_blocked": "L’authentification à deux facteurs ne peut pas être supprimée, car elle est requise pour votre compte.",
|
||||||
|
"quarantine_category_invalid": "La catégorie de quarantaine doit être l’une des suivantes : add_header, reject, all\""
|
||||||
},
|
},
|
||||||
"debug": {
|
"debug": {
|
||||||
"chart_this_server": "Graphique (ce serveur)",
|
"chart_this_server": "Graphique (ce serveur)",
|
||||||
@@ -755,7 +758,9 @@
|
|||||||
"mta_sts_info": "<a href='https://en.wikipedia.org/wiki/Simple_Mail_Transfer_Protocol#SMTP_MTA_Strict_Transport_Security' target='_blank'>MTA-STS</a> est un standard qui oblige la délivrance des courriels entre les serveurs de courriels à utiliser TLS avec des certificats valides. <br>Il est utilisé quand <a target='_blank' href='https://en.wikipedia.org/wiki/DNS-based_Authentication_of_Named_Entities'>DANE</a> n'est pas possible à cause d'un manque ou d'un non support de DNSSEC.<br><b>Note</b> : Si le domaine du destinataire supporte DANE avec DNSSEC, DANE est <b>toujours</b> préféré – MTA-STS sert seulement en secours.",
|
"mta_sts_info": "<a href='https://en.wikipedia.org/wiki/Simple_Mail_Transfer_Protocol#SMTP_MTA_Strict_Transport_Security' target='_blank'>MTA-STS</a> est un standard qui oblige la délivrance des courriels entre les serveurs de courriels à utiliser TLS avec des certificats valides. <br>Il est utilisé quand <a target='_blank' href='https://en.wikipedia.org/wiki/DNS-based_Authentication_of_Named_Entities'>DANE</a> n'est pas possible à cause d'un manque ou d'un non support de DNSSEC.<br><b>Note</b> : Si le domaine du destinataire supporte DANE avec DNSSEC, DANE est <b>toujours</b> préféré – MTA-STS sert seulement en secours.",
|
||||||
"mta_sts_mode_info": "Il y a trois modes parmi lesquels choisir :<ul><li><em>testing</em> – la politique est seulement surveillée, les violations n'ont pas d'impact.</li><li><em>enforce</em> – la politique est appliquée strictement, les connexions sans TLS valide sont rejetées.</li><li><em>none</em> – la politique est publiée mais non appliquée.</li></ul>",
|
"mta_sts_mode_info": "Il y a trois modes parmi lesquels choisir :<ul><li><em>testing</em> – la politique est seulement surveillée, les violations n'ont pas d'impact.</li><li><em>enforce</em> – la politique est appliquée strictement, les connexions sans TLS valide sont rejetées.</li><li><em>none</em> – la politique est publiée mais non appliquée.</li></ul>",
|
||||||
"mta_sts_max_age_info": "Durée en secondes pendant laquelle les serveurs de courriel peuvent mettre en cache cette politique avant de revérifier.",
|
"mta_sts_max_age_info": "Durée en secondes pendant laquelle les serveurs de courriel peuvent mettre en cache cette politique avant de revérifier.",
|
||||||
"mta_sts_mx_info": "Autoriser l'envoi uniquement aux noms d'hôtes des serveurs de courriels indiqués explicitement ; le MTA émetteur vérifie si le nom d'hôte DNS du MX correspond à la liste de la politique, et autorise la délivrance seulement avec un certificat TLS valide (protège contre le MITM)."
|
"mta_sts_mx_info": "Autoriser l'envoi uniquement aux noms d'hôtes des serveurs de courriels indiqués explicitement ; le MTA émetteur vérifie si le nom d'hôte DNS du MX correspond à la liste de la politique, et autorise la délivrance seulement avec un certificat TLS valide (protège contre le MITM).",
|
||||||
|
"sender_allowed": "Autoriser l'envoi sous cet alias",
|
||||||
|
"sender_allowed_info": "Si cette option est désactivée, cet alias peut uniquement recevoir des courriels. Utilisez les ACL d’expéditeur pour autoriser certaines boîtes aux lettres à envoyer des messages via cet alias."
|
||||||
},
|
},
|
||||||
"footer": {
|
"footer": {
|
||||||
"cancel": "Annuler",
|
"cancel": "Annuler",
|
||||||
@@ -988,7 +993,8 @@
|
|||||||
"recipient": "Destinataire",
|
"recipient": "Destinataire",
|
||||||
"open_logs": "Afficher les journaux",
|
"open_logs": "Afficher les journaux",
|
||||||
"iam": "Fournisseur d'identité",
|
"iam": "Fournisseur d'identité",
|
||||||
"internal": "Interne"
|
"internal": "Interne",
|
||||||
|
"force_tfa": "TFA"
|
||||||
},
|
},
|
||||||
"oauth2": {
|
"oauth2": {
|
||||||
"access_denied": "Veuillez vous connecter en tant que propriétaire de la boîte de réception pour accorder l’accès via Oauth2.",
|
"access_denied": "Veuillez vous connecter en tant que propriétaire de la boîte de réception pour accorder l’accès via Oauth2.",
|
||||||
@@ -1189,7 +1195,11 @@
|
|||||||
"yubi_otp": "Authentification OTP Yubico",
|
"yubi_otp": "Authentification OTP Yubico",
|
||||||
"authenticators": "Authentificateurs",
|
"authenticators": "Authentificateurs",
|
||||||
"u2f_deprecated_important": "Veuillez enregistrer votre clé dans le panneau d'administration avec la nouvelle méthode WebAuthn.",
|
"u2f_deprecated_important": "Veuillez enregistrer votre clé dans le panneau d'administration avec la nouvelle méthode WebAuthn.",
|
||||||
"u2f_deprecated": "Il semble que votre clé ait été enregistrée à l'aide de la méthode U2F obsolète. Nous allons désactiver l'authentification à deux facteurs pour vous et supprimer votre clé."
|
"u2f_deprecated": "Il semble que votre clé ait été enregistrée à l'aide de la méthode U2F obsolète. Nous allons désactiver l'authentification à deux facteurs pour vous et supprimer votre clé.",
|
||||||
|
"force_tfa": "Imposer l'authentification à deux facteurs lors de la connexion",
|
||||||
|
"force_tfa_info": "L’utilisateur doit configurer l’authentification à deux facteurs avant de pouvoir accéder à l’interface.",
|
||||||
|
"setup_title": "Authentification à deux facteurs requise",
|
||||||
|
"setup_required": "Votre compte nécessite l’authentification à deux facteurs. Veuillez configurer une méthode 2FA pour continuer."
|
||||||
},
|
},
|
||||||
"fido2": {
|
"fido2": {
|
||||||
"set_fn": "Définir un nom",
|
"set_fn": "Définir un nom",
|
||||||
@@ -1378,7 +1388,8 @@
|
|||||||
"forever": "Pour toujours",
|
"forever": "Pour toujours",
|
||||||
"spam_aliases_info": "Un alias de spam est une adresse de courriel temporaire qui peut être utilisée pour protéger les véritables adresses de courriel. <br> De manière optionnelle, une durée d'expiration peut être définie afin que l'alias soit automatiquement désactivé après la période définie, éliminant ainsi les adresses étant abusées ou ayant fuité.",
|
"spam_aliases_info": "Un alias de spam est une adresse de courriel temporaire qui peut être utilisée pour protéger les véritables adresses de courriel. <br> De manière optionnelle, une durée d'expiration peut être définie afin que l'alias soit automatiquement désactivé après la période définie, éliminant ainsi les adresses étant abusées ou ayant fuité.",
|
||||||
"authentication": "Authentification",
|
"authentication": "Authentification",
|
||||||
"protocols": "Protocoles"
|
"protocols": "Protocoles",
|
||||||
|
"pw_update_required": "Votre compte nécessite un changement de mot de passe. Veuillez définir un nouveau mot de passe pour continuer."
|
||||||
},
|
},
|
||||||
"warning": {
|
"warning": {
|
||||||
"cannot_delete_self": "Impossible de supprimer l’utilisateur connecté",
|
"cannot_delete_self": "Impossible de supprimer l’utilisateur connecté",
|
||||||
|
|||||||
@@ -1001,7 +1001,8 @@
|
|||||||
"weekly": "Tedensko",
|
"weekly": "Tedensko",
|
||||||
"sieve_info": "Na uporabnika lahko shranite več filtrov, vendar je lahko hkrati aktiven le en predfilter in en postfilter.<br>\nVsak filter bo obdelan v opisanem vrstnem redu. Niti neuspešen skript niti izdan ukaz »keep;« ne bosta ustavila obdelave nadaljnjih skript. Spremembe globalnih skriptov sita bodo sprožile ponovni zagon Dovecota.<br><br>Globalni predfilter sita • Predfilter • Uporabniški skripti • Postfilter • Globalni postfilter sita",
|
"sieve_info": "Na uporabnika lahko shranite več filtrov, vendar je lahko hkrati aktiven le en predfilter in en postfilter.<br>\nVsak filter bo obdelan v opisanem vrstnem redu. Niti neuspešen skript niti izdan ukaz »keep;« ne bosta ustavila obdelave nadaljnjih skript. Spremembe globalnih skriptov sita bodo sprožile ponovni zagon Dovecota.<br><br>Globalni predfilter sita • Predfilter • Uporabniški skripti • Postfilter • Globalni postfilter sita",
|
||||||
"tls_policy_maps_info": "Ta preslikava pravilnikov preglasi pravila odhodnega prenosa TLS neodvisno od uporabnikovih nastavitev pravilnikov TLS.<br>\n Za več informacij preverite <a href=\"http://www.postfix.org/postconf.5.html#smtp_tls_policy_maps\" target=\"_blank\">dokumentacijo »smtp_tls_policy_maps«</a>.",
|
"tls_policy_maps_info": "Ta preslikava pravilnikov preglasi pravila odhodnega prenosa TLS neodvisno od uporabnikovih nastavitev pravilnikov TLS.<br>\n Za več informacij preverite <a href=\"http://www.postfix.org/postconf.5.html#smtp_tls_policy_maps\" target=\"_blank\">dokumentacijo »smtp_tls_policy_maps«</a>.",
|
||||||
"internal": "Notranje"
|
"internal": "Notranje",
|
||||||
|
"force_tfa": "TFA"
|
||||||
},
|
},
|
||||||
"fido2": {
|
"fido2": {
|
||||||
"known_ids": "Znani ID-ji",
|
"known_ids": "Znani ID-ji",
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ if (isset($_GET['app_password'])) {
|
|||||||
$attr['protocols'][] = 'dav_access';
|
$attr['protocols'][] = 'dav_access';
|
||||||
}
|
}
|
||||||
app_passwd("add", $attr);
|
app_passwd("add", $attr);
|
||||||
|
$password = htmlspecialchars($password, ENT_NOQUOTES);
|
||||||
} else {
|
} else {
|
||||||
$app_password = false;
|
$app_password = false;
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-2
@@ -8,8 +8,12 @@ $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 (isset($_SERVER['PHP_AUTH_USER'])) {
|
if ($is_internal_auth && 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';
|
||||||
|
|
||||||
@@ -50,6 +54,11 @@ 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: /");
|
||||||
@@ -80,7 +89,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 (isset($_SERVER['HTTP_X_ORIGINAL_URI']) && strcasecmp(substr($_SERVER['HTTP_X_ORIGINAL_URI'], 0, 9), "/SOGo/so/") === 0) {
|
elseif ($is_internal_auth && 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')) {
|
||||||
|
|||||||
@@ -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><input type="checkbox" class="form-check-input" value="1" name="active"{% if mta_sts.active == '1' %} checked{% endif %}> {{ lang.edit.active }}</label>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
<input type="hidden" value="default" name="sender_acl">
|
<input type="hidden" value="default" name="sender_acl">
|
||||||
<input type="hidden" value="0" name="force_pw_update">
|
<input type="hidden" value="0" name="force_pw_update">
|
||||||
|
<input type="hidden" value="0" name="force_tfa">
|
||||||
<input type="hidden" value="0" name="sogo_access">
|
<input type="hidden" value="0" name="sogo_access">
|
||||||
<input type="hidden" value="0" name="protocol_access">
|
<input type="hidden" value="0" name="protocol_access">
|
||||||
|
|
||||||
@@ -165,6 +166,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="offset-sm-2 col-sm-10">
|
||||||
|
<div class="form-check">
|
||||||
|
<label><input type="checkbox" class="form-check-input" value="1" name="force_tfa" id="force_tfa"{% if template.attributes.force_tfa == '1' %} checked{% endif %}> {{ lang.tfa.force_tfa }}</label>
|
||||||
|
<small class="text-muted">{{ lang.tfa.force_tfa_info }}</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{% if not skip_sogo %}
|
{% if not skip_sogo %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="offset-sm-2 col-sm-10">
|
<div class="offset-sm-2 col-sm-10">
|
||||||
|
|||||||
+6
-6
@@ -84,7 +84,7 @@ services:
|
|||||||
- clamd
|
- clamd
|
||||||
|
|
||||||
rspamd-mailcow:
|
rspamd-mailcow:
|
||||||
image: ghcr.io/mailcow/rspamd:3.14.3-1
|
image: ghcr.io/mailcow/rspamd:4.1.4-1
|
||||||
stop_grace_period: 30s
|
stop_grace_period: 30s
|
||||||
depends_on:
|
depends_on:
|
||||||
- dovecot-mailcow
|
- dovecot-mailcow
|
||||||
@@ -117,7 +117,7 @@ services:
|
|||||||
- rspamd
|
- rspamd
|
||||||
|
|
||||||
php-fpm-mailcow:
|
php-fpm-mailcow:
|
||||||
image: ghcr.io/mailcow/phpfpm:8.2.29-2
|
image: ghcr.io/mailcow/phpfpm:8.2.29-3
|
||||||
command: "php-fpm -d date.timezone=${TZ} -d expose_php=0"
|
command: "php-fpm -d date.timezone=${TZ} -d expose_php=0"
|
||||||
depends_on:
|
depends_on:
|
||||||
- redis-mailcow
|
- redis-mailcow
|
||||||
@@ -200,7 +200,7 @@ services:
|
|||||||
- phpfpm
|
- phpfpm
|
||||||
|
|
||||||
sogo-mailcow:
|
sogo-mailcow:
|
||||||
image: ghcr.io/mailcow/sogo:5.12.8-1
|
image: ghcr.io/mailcow/sogo:5.12.9-1
|
||||||
environment:
|
environment:
|
||||||
- DBNAME=${DBNAME}
|
- DBNAME=${DBNAME}
|
||||||
- DBUSER=${DBUSER}
|
- DBUSER=${DBUSER}
|
||||||
@@ -339,7 +339,7 @@ services:
|
|||||||
- dovecot
|
- dovecot
|
||||||
|
|
||||||
postfix-mailcow:
|
postfix-mailcow:
|
||||||
image: ghcr.io/mailcow/postfix:3.7.11-2
|
image: ghcr.io/mailcow/postfix:3.10.12-1
|
||||||
depends_on:
|
depends_on:
|
||||||
mysql-mailcow:
|
mysql-mailcow:
|
||||||
condition: service_started
|
condition: service_started
|
||||||
@@ -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.2-1
|
image: ghcr.io/mailcow/nginx:1.30.4-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.97
|
image: ghcr.io/mailcow/acme:1.98
|
||||||
dns:
|
dns:
|
||||||
- ${IPV4_NETWORK:-172.22.1}.254
|
- ${IPV4_NETWORK:-172.22.1}.254
|
||||||
environment:
|
environment:
|
||||||
|
|||||||
+1
-1
@@ -25,6 +25,6 @@ services:
|
|||||||
- /var/run/mysqld/mysqld.sock:/var/run/mysqld/mysqld.sock
|
- /var/run/mysqld/mysqld.sock:/var/run/mysqld/mysqld.sock
|
||||||
|
|
||||||
mysql-mailcow:
|
mysql-mailcow:
|
||||||
image: alpine:3.23
|
image: alpine:3.24
|
||||||
command: /bin/true
|
command: /bin/true
|
||||||
restart: "no"
|
restart: "no"
|
||||||
|
|||||||
Reference in New Issue
Block a user