mirror of
https://github.com/mailcow/mailcow-dockerized.git
synced 2026-10-22 10:48:13 +00:00
Compare commits
206
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2bad774bcc | ||
|
|
015fd04a37 | ||
|
|
ca8546fa4a | ||
|
|
6f6edfb85e | ||
|
|
6c877fc0ac | ||
|
|
4a29767a31 | ||
|
|
cd29426933 | ||
|
|
37be306f1c | ||
|
|
a351017455 | ||
|
|
887b8a3594 | ||
|
|
571c1b5b29 | ||
|
|
8cd231c9b9 | ||
|
|
74facc4b5b | ||
|
|
ff6329d01b | ||
|
|
e8209d6504 | ||
|
|
626d006a3d | ||
|
|
e88c734d0a | ||
|
|
86bba266f6 | ||
|
|
e3ed8d2fc2 | ||
|
|
88d92261e4 | ||
|
|
017d0c7ff0 | ||
|
|
a28fa19044 | ||
|
|
fa2ad14fc4 | ||
|
|
a480a96e10 | ||
|
|
42d387ca42 | ||
|
|
fe4e675754 | ||
|
|
b05f59b705 | ||
|
|
c4d0ca56d2 | ||
|
|
7ec61fde8a | ||
|
|
d80b15b06c | ||
|
|
ed4fe41d7a | ||
|
|
6b9d875773 | ||
|
|
4d5b9d1c80 | ||
|
|
8abf453e1c | ||
|
|
8bd9e12a73 | ||
|
|
c665d430de | ||
|
|
2195a674ff | ||
|
|
089c44aee1 | ||
|
|
6644699e65 | ||
|
|
91d3b7246a | ||
|
|
b362c7105d | ||
|
|
555e7ed706 | ||
|
|
59e2a9ac77 | ||
|
|
feed5ad183 | ||
|
|
f1f7a9800b | ||
|
|
8c85044781 | ||
|
|
8fb32e4b59 | ||
|
|
b5fa926bc0 | ||
|
|
d1a2f4e168 | ||
|
|
f7a536d634 | ||
|
|
8b456a33e7 | ||
|
|
54da526d41 | ||
|
|
50be483c39 | ||
|
|
d64c923aca | ||
|
|
2d5f166ba8 | ||
|
|
94b5a623ab | ||
|
|
e406ecd461 | ||
|
|
68a95c3af3 | ||
|
|
bd037d5644 | ||
|
|
7036dbf4f8 | ||
|
|
2f10a4633f | ||
|
|
36c70db86c | ||
|
|
9cd16f0001 | ||
|
|
f4961c4023 | ||
|
|
38de21592c | ||
|
|
f44bd2f36a | ||
|
|
95a77f2dcb | ||
|
|
e856510fb2 | ||
|
|
d51d06d716 | ||
|
|
54170d075a | ||
|
|
b4bb1a625b | ||
|
|
2e5a29bf69 | ||
|
|
e245ac04d9 | ||
|
|
c877fdf0a5 | ||
|
|
fea38c8e1b | ||
|
|
cc9af65852 | ||
|
|
92cc8bec90 | ||
|
|
145745329e | ||
|
|
8e72d22c56 | ||
|
|
ddd76d99cd | ||
|
|
8c9524a2fe | ||
|
|
2ebd32d2ee | ||
|
|
4b62af9d02 | ||
|
|
90ca1bf25a | ||
|
|
14772c3a20 | ||
|
|
e5d0ed8c4c | ||
|
|
c17cc8a792 | ||
|
|
4cba448671 | ||
|
|
7dab6c63d1 | ||
|
|
5408bc809d | ||
|
|
bd5c18b145 | ||
|
|
0f06c8e563 | ||
|
|
fffe1aec3d | ||
|
|
6f7fee49cd | ||
|
|
c1d75cf808 | ||
|
|
473fe2885d | ||
|
|
31e1550668 | ||
|
|
315f55bc65 | ||
|
|
e696ee2f6c | ||
|
|
d65aa11870 | ||
|
|
f6630e5b43 | ||
|
|
98e26c5603 | ||
|
|
3098caebbc | ||
|
|
94e23a3cd3 | ||
|
|
1840074991 | ||
|
|
99768e16f5 | ||
|
|
1f8b4679cc | ||
|
|
9e723e942d | ||
|
|
47e4552b44 | ||
|
|
ddcce811c1 | ||
|
|
7e6c36dce1 | ||
|
|
fa154c71f3 | ||
|
|
83a045ed3e | ||
|
|
064817ac70 | ||
|
|
2bd7a24b8c | ||
|
|
ecc2462e4c | ||
|
|
2d8db72d46 | ||
|
|
277a307fb9 | ||
|
|
d455555621 | ||
|
|
9ea2ff1dff | ||
|
|
24cc369d84 | ||
|
|
5f5367f2f9 | ||
|
|
03c31f825a | ||
|
|
c0050e8836 | ||
|
|
15891960f7 | ||
|
|
cce02e2b15 | ||
|
|
0fafda696b | ||
|
|
843db5854c | ||
|
|
23e4e4f373 | ||
|
|
175878f8f1 | ||
|
|
3fcda21c4e | ||
|
|
eac1bf02fc | ||
|
|
a7f2b2145f | ||
|
|
faac56a611 | ||
|
|
be37bc5a68 | ||
|
|
b227c76156 | ||
|
|
db4e3b4d54 | ||
|
|
ffbc37a00c | ||
|
|
e6fa0c5099 | ||
|
|
ce3ceaf5b8 | ||
|
|
efc2e76159 | ||
|
|
7d17715e2e | ||
|
|
aed440bcb3 | ||
|
|
4f2348631a | ||
|
|
da586682f6 | ||
|
|
22ae1f5363 | ||
|
|
c9cf8d7f38 | ||
|
|
cbd3d8b9bc | ||
|
|
f553f38635 | ||
|
|
07373907e3 | ||
|
|
104af58628 | ||
|
|
4ddcee28e4 | ||
|
|
b0d16bbcee | ||
|
|
9175e5f086 | ||
|
|
ff3d571054 | ||
|
|
a8e945f3da | ||
|
|
33547d1d73 | ||
|
|
539c32d99c | ||
|
|
d42c3823c4 | ||
|
|
8ead057ce9 | ||
|
|
886dbcc419 | ||
|
|
b0873edb6a | ||
|
|
694d751915 | ||
|
|
dc15994d40 | ||
|
|
ec24825280 | ||
|
|
08278a8be9 | ||
|
|
5a00b5124b | ||
|
|
8c039f694f | ||
|
|
95bf46c1e4 | ||
|
|
edde35156d | ||
|
|
84e3c32f13 | ||
|
|
ecb848493b | ||
|
|
8a65b9d1c6 | ||
|
|
ed9264fd2a | ||
|
|
7817dda43f | ||
|
|
018e292854 | ||
|
|
127fb1e8f5 | ||
|
|
09f09cb850 | ||
|
|
d4bf377a96 | ||
|
|
abd6fe8c79 | ||
|
|
5f8382ef44 | ||
|
|
03eccd4e42 | ||
|
|
1da8d1c894 | ||
|
|
d1feebf164 | ||
|
|
293b885a85 | ||
|
|
1e0850193a | ||
|
|
33acf56526 | ||
|
|
bea9ad7e8f | ||
|
|
e7ea3aa608 | ||
|
|
5888e248c3 | ||
|
|
2e176339ba | ||
|
|
8f883f3d37 | ||
|
|
709117fe19 | ||
|
|
82ea418423 | ||
|
|
fd24163c6e | ||
|
|
3caef45a12 | ||
|
|
8760e7e5db | ||
|
|
e848226062 | ||
|
|
efaeb77e13 | ||
|
|
569b4cf985 | ||
|
|
6c857243ec | ||
|
|
905e93627c | ||
|
|
598ea21827 | ||
|
|
6012cf4486 | ||
|
|
fe71f84c82 | ||
|
|
4e33c7143f |
No files matched your search
@@ -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.2.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.0.2
|
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,13 +13,13 @@ 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
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v4
|
||||||
|
|
||||||
- name: Login to GHCR
|
- name: Login to GHCR
|
||||||
if: github.event_name != 'pull_request'
|
if: github.event_name != 'pull_request'
|
||||||
@@ -30,7 +30,7 @@ jobs:
|
|||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v7
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
|
|||||||
@@ -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: |
|
||||||
|
|||||||
@@ -64,6 +64,8 @@ data/web/templates/cache/*
|
|||||||
!data/web/templates/cache/.gitkeep
|
!data/web/templates/cache/.gitkeep
|
||||||
data/web/.well-known/acme-challenge
|
data/web/.well-known/acme-challenge
|
||||||
data/web/css/build/0081-custom-mailcow.css
|
data/web/css/build/0081-custom-mailcow.css
|
||||||
|
data/web/css/themes/*/
|
||||||
|
!data/web/css/themes/servercow/
|
||||||
data/web/inc/vars.local.inc.php
|
data/web/inc/vars.local.inc.php
|
||||||
data/web/inc/app_info.inc.php
|
data/web/inc/app_info.inc.php
|
||||||
data/web/nextcloud*/
|
data/web/nextcloud*/
|
||||||
|
|||||||
@@ -23,10 +23,15 @@ A big thank you to everyone supporting us on GitHub Sponsors—your contribution
|
|||||||
<a href="https://www.maehdros.com/" target=_blank><img
|
<a href="https://www.maehdros.com/" target=_blank><img
|
||||||
src="https://avatars.githubusercontent.com/u/173894712" height="58"
|
src="https://avatars.githubusercontent.com/u/173894712" height="58"
|
||||||
/></a>
|
/></a>
|
||||||
|
<a href="https://garske-systems.de/" target=_blank><img
|
||||||
|
src="https://garske-systems.de/img/logo_min.png" height="58"
|
||||||
|
/></a>
|
||||||
|
|
||||||
### 50$/Month Sponsors
|
### 50$/Month Sponsors
|
||||||
<a href="https://github.com/vnukhr" target=_blank><img
|
<a href="https://github.com/vnukhr" target=_blank><img
|
||||||
src="https://avatars.githubusercontent.com/u/7805987?s=52&v=4" height="58"
|
src="https://avatars.githubusercontent.com/u/7805987?s=52&v=4" height="58"
|
||||||
|
/></a><a href="https://github.com/humiico" target=_blank><img
|
||||||
|
src="https://avatars.githubusercontent.com/u/110134705?s=52&v=4" height="58"
|
||||||
/></a>
|
/></a>
|
||||||
|
|
||||||
## Info, documentation and support
|
## Info, documentation and support
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ adapt_new_options() {
|
|||||||
|
|
||||||
sed -i --follow-symlinks '$a\' mailcow.conf
|
sed -i --follow-symlinks '$a\' mailcow.conf
|
||||||
for option in ${CONFIG_ARRAY[@]}; do
|
for option in ${CONFIG_ARRAY[@]}; do
|
||||||
if grep -q "${option}" mailcow.conf; then
|
if grep -q "^#\?${option}=" mailcow.conf; then
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -302,7 +302,7 @@ adapt_new_options() {
|
|||||||
;;
|
;;
|
||||||
ACME_DNS_PROVIDER)
|
ACME_DNS_PROVIDER)
|
||||||
echo '# DNS provider for DNS-01 challenge (e.g. dns_cf, dns_azure, dns_gd, etc.)' >> mailcow.conf
|
echo '# DNS provider for DNS-01 challenge (e.g. dns_cf, dns_azure, dns_gd, etc.)' >> mailcow.conf
|
||||||
echo '# See the dns-101 provider documentation for more information.' >> mailcow.conf
|
echo '# See the dns-01 provider documentation for more information.' >> mailcow.conf
|
||||||
echo 'ACME_DNS_PROVIDER=dns_xxx' >> mailcow.conf
|
echo 'ACME_DNS_PROVIDER=dns_xxx' >> mailcow.conf
|
||||||
;;
|
;;
|
||||||
ACME_ACCOUNT_EMAIL)
|
ACME_ACCOUNT_EMAIL)
|
||||||
|
|||||||
@@ -249,14 +249,45 @@ 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
|
||||||
if [[ "${SUBDOMAIN}.${SQL_DOMAIN}" != "${MAILCOW_HOSTNAME}" ]]; then
|
FULL_SUBDOMAIN="${SUBDOMAIN}.${SQL_DOMAIN}"
|
||||||
if check_domain "${SUBDOMAIN}.${SQL_DOMAIN}"; then
|
|
||||||
VALIDATED_CONFIG_DOMAINS_SUBDOMAINS+=("${SUBDOMAIN}.${SQL_DOMAIN}")
|
# Skip mta-sts subdomain unless MTA-STS is enabled (active) for this domain
|
||||||
fi
|
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
|
||||||
|
if [[ "${FULL_SUBDOMAIN}" == "${MAILCOW_HOSTNAME}" ]]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
# Skip if subdomain is covered by a wildcard in ADDITIONAL_SAN
|
||||||
|
if is_covered_by_wildcard "${FULL_SUBDOMAIN}"; then
|
||||||
|
log_f "Subdomain '${FULL_SUBDOMAIN}' is covered by wildcard - skipping explicit subdomain"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
# Validate and add subdomain
|
||||||
|
if check_domain "${FULL_SUBDOMAIN}"; then
|
||||||
|
VALIDATED_CONFIG_DOMAINS_SUBDOMAINS+=("${FULL_SUBDOMAIN}")
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
VALIDATED_CONFIG_DOMAINS+=("${VALIDATED_CONFIG_DOMAINS_SUBDOMAINS[*]}")
|
VALIDATED_CONFIG_DOMAINS+=("${VALIDATED_CONFIG_DOMAINS_SUBDOMAINS[*]}")
|
||||||
@@ -273,7 +304,10 @@ while true; do
|
|||||||
fi
|
fi
|
||||||
# Only add mta-sts subdomain for alias domains
|
# Only add mta-sts subdomain for alias domains
|
||||||
if [[ "mta-sts.${alias_domain}" != "${MAILCOW_HOSTNAME}" ]]; then
|
if [[ "mta-sts.${alias_domain}" != "${MAILCOW_HOSTNAME}" ]]; then
|
||||||
if check_domain "mta-sts.${alias_domain}"; then
|
# Skip if mta-sts subdomain is covered by a wildcard
|
||||||
|
if is_covered_by_wildcard "mta-sts.${alias_domain}"; then
|
||||||
|
log_f "Alias domain mta-sts subdomain 'mta-sts.${alias_domain}' is covered by wildcard - skipping"
|
||||||
|
elif check_domain "mta-sts.${alias_domain}"; then
|
||||||
VALIDATED_CONFIG_DOMAINS+=("mta-sts.${alias_domain}")
|
VALIDATED_CONFIG_DOMAINS+=("mta-sts.${alias_domain}")
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
@@ -308,13 +342,31 @@ while true; do
|
|||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Check if MAILCOW_HOSTNAME is covered by a wildcard in ADDITIONAL_SAN
|
||||||
|
MAILCOW_HOSTNAME_COVERED=0
|
||||||
|
if [[ ! -z ${VALIDATED_MAILCOW_HOSTNAME} ]]; then
|
||||||
|
if is_covered_by_wildcard "${VALIDATED_MAILCOW_HOSTNAME}"; then
|
||||||
|
MAILCOW_PARENT_DOMAIN=$(echo ${VALIDATED_MAILCOW_HOSTNAME} | cut -d. -f2-)
|
||||||
|
log_f "MAILCOW_HOSTNAME '${VALIDATED_MAILCOW_HOSTNAME}' is covered by wildcard '*.${MAILCOW_PARENT_DOMAIN}' - skipping explicit hostname"
|
||||||
|
MAILCOW_HOSTNAME_COVERED=1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
# Unique domains for server certificate
|
# Unique domains for server certificate
|
||||||
if [[ ${ENABLE_SSL_SNI} == "y" ]]; then
|
if [[ ${ENABLE_SSL_SNI} == "y" ]]; then
|
||||||
# create certificate for server name and fqdn SANs only
|
# create certificate for server name and fqdn SANs only
|
||||||
SERVER_SAN_VALIDATED=(${VALIDATED_MAILCOW_HOSTNAME} $(echo ${ADDITIONAL_VALIDATED_SAN[*]} | xargs -n1 | sort -u | xargs))
|
if [[ ${MAILCOW_HOSTNAME_COVERED} == "1" ]]; then
|
||||||
|
SERVER_SAN_VALIDATED=($(echo ${ADDITIONAL_VALIDATED_SAN[*]} | xargs -n1 | sort -u | xargs))
|
||||||
|
else
|
||||||
|
SERVER_SAN_VALIDATED=(${VALIDATED_MAILCOW_HOSTNAME} $(echo ${ADDITIONAL_VALIDATED_SAN[*]} | xargs -n1 | sort -u | xargs))
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
# create certificate for all domains, including all subdomains from other domains [*]
|
# create certificate for all domains, including all subdomains from other domains [*]
|
||||||
SERVER_SAN_VALIDATED=(${VALIDATED_MAILCOW_HOSTNAME} $(echo ${VALIDATED_CONFIG_DOMAINS[*]} ${ADDITIONAL_VALIDATED_SAN[*]} | xargs -n1 | sort -u | xargs))
|
if [[ ${MAILCOW_HOSTNAME_COVERED} == "1" ]]; then
|
||||||
|
SERVER_SAN_VALIDATED=($(echo ${VALIDATED_CONFIG_DOMAINS[*]} ${ADDITIONAL_VALIDATED_SAN[*]} | xargs -n1 | sort -u | xargs))
|
||||||
|
else
|
||||||
|
SERVER_SAN_VALIDATED=(${VALIDATED_MAILCOW_HOSTNAME} $(echo ${VALIDATED_CONFIG_DOMAINS[*]} ${ADDITIONAL_VALIDATED_SAN[*]} | xargs -n1 | sort -u | xargs))
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
if [[ ! -z ${SERVER_SAN_VALIDATED[*]} ]]; then
|
if [[ ! -z ${SERVER_SAN_VALIDATED[*]} ]]; then
|
||||||
CERT_NAME=${SERVER_SAN_VALIDATED[0]}
|
CERT_NAME=${SERVER_SAN_VALIDATED[0]}
|
||||||
|
|||||||
@@ -135,3 +135,32 @@ verify_challenge_path(){
|
|||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Check if a domain is covered by a wildcard (*.example.com) in ADDITIONAL_SAN
|
||||||
|
# Usage: is_covered_by_wildcard "subdomain.example.com"
|
||||||
|
# Returns: 0 if covered, 1 if not covered
|
||||||
|
# Note: Only returns 0 (covered) when DNS-01 challenge is enabled,
|
||||||
|
# as wildcards cannot be validated with HTTP-01 challenge
|
||||||
|
is_covered_by_wildcard() {
|
||||||
|
local DOMAIN=$1
|
||||||
|
|
||||||
|
# Only skip if DNS challenge is enabled (wildcards require DNS-01)
|
||||||
|
if [[ ${ACME_DNS_CHALLENGE} != "y" ]]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Return early if no ADDITIONAL_SAN is set
|
||||||
|
if [[ -z ${ADDITIONAL_SAN} ]]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Extract parent domain (e.g., mail.example.com -> example.com)
|
||||||
|
local PARENT_DOMAIN=$(echo ${DOMAIN} | cut -d. -f2-)
|
||||||
|
|
||||||
|
# Check if ADDITIONAL_SAN contains a wildcard for this parent domain
|
||||||
|
if [[ "${ADDITIONAL_SAN}" == *"*.${PARENT_DOMAIN}"* ]]; then
|
||||||
|
return 0 # Covered by wildcard
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 1 # Not covered
|
||||||
|
}
|
||||||
@@ -7,7 +7,7 @@ else
|
|||||||
__dns_loader_standalone=0
|
__dns_loader_standalone=0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
CONFIG_PATH="${ACME_DNS_CONFIG_FILE:-/etc/acme/dns-101.conf}"
|
CONFIG_PATH="${ACME_DNS_CONFIG_FILE:-/etc/acme/dns-01.conf}"
|
||||||
|
|
||||||
if [[ ! -f "${CONFIG_PATH}" ]]; then
|
if [[ ! -f "${CONFIG_PATH}" ]]; then
|
||||||
if [[ $__dns_loader_standalone -eq 1 ]]; then
|
if [[ $__dns_loader_standalone -eq 1 ]]; then
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ CERT_DOMAINS=(${DOMAINS[@]})
|
|||||||
CERT_DOMAIN=${CERT_DOMAINS[0]}
|
CERT_DOMAIN=${CERT_DOMAINS[0]}
|
||||||
ACME_BASE=/var/lib/acme
|
ACME_BASE=/var/lib/acme
|
||||||
|
|
||||||
# Load optional DNS provider secrets from /etc/acme/dns-101.conf
|
# Load optional DNS provider secrets from /etc/acme/dns-01.conf
|
||||||
if [[ -f /srv/load-dns-config.sh ]]; then
|
if [[ -f /srv/load-dns-config.sh ]]; then
|
||||||
source /srv/load-dns-config.sh
|
source /srv/load-dns-config.sh
|
||||||
if declare -F log_f >/dev/null; then
|
if declare -F log_f >/dev/null; then
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
FROM alpine:3.21 AS builder
|
FROM alpine:3.24 AS builder
|
||||||
|
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
ENV CLAMD_VERSION=1.4.2
|
ENV CLAMD_VERSION=1.4.6
|
||||||
|
|
||||||
RUN apk upgrade --no-cache \
|
RUN apk upgrade --no-cache \
|
||||||
&& apk add --update --no-cache \
|
&& apk add --update --no-cache \
|
||||||
@@ -68,7 +68,7 @@ RUN wget -P /src https://www.clamav.net/downloads/production/clamav-${CLAMD_VERS
|
|||||||
"/clamav/etc/clamav/clamav-milter.conf.sample" > "/clamav/etc/clamav/clamav-milter.conf" || exit 1
|
"/clamav/etc/clamav/clamav-milter.conf.sample" > "/clamav/etc/clamav/clamav-milter.conf" || exit 1
|
||||||
|
|
||||||
|
|
||||||
FROM alpine:3.21
|
FROM alpine:3.24
|
||||||
|
|
||||||
LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"
|
LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM alpine:3.22
|
FROM alpine:3.21
|
||||||
|
|
||||||
LABEL maintainer="The Infrastructure Company GmbH <info@servercow.de>"
|
LABEL maintainer="The Infrastructure Company GmbH <info@servercow.de>"
|
||||||
|
|
||||||
|
|||||||
@@ -44,109 +44,90 @@ if [[ "${MASTER}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
|
|||||||
else
|
else
|
||||||
QUOTA_TABLE=quota2replica
|
QUOTA_TABLE=quota2replica
|
||||||
fi
|
fi
|
||||||
|
|
||||||
cat <<EOF > /etc/dovecot/conf.d/12-mysql.conf
|
|
||||||
# Autogenerated by mailcow - DO NOT TOUCH!
|
|
||||||
mysql /var/run/mysqld/mysqld.sock {
|
|
||||||
dbname=${DBNAME}
|
|
||||||
user=${DBUSER}
|
|
||||||
password=${DBPASS}
|
|
||||||
|
|
||||||
ssl = no
|
|
||||||
}
|
|
||||||
EOF
|
|
||||||
|
|
||||||
|
|
||||||
cat <<EOF > /etc/dovecot/sql/dovecot-dict-sql-quota.conf
|
cat <<EOF > /etc/dovecot/sql/dovecot-dict-sql-quota.conf
|
||||||
# Autogenerated by mailcow
|
# Autogenerated by mailcow
|
||||||
dict_map priv/quota/storage {
|
connect = "host=/var/run/mysqld/mysqld.sock dbname=${DBNAME} user=${DBUSER} password=${DBPASS}"
|
||||||
sql_table = ${QUOTA_TABLE}
|
map {
|
||||||
|
pattern = priv/quota/storage
|
||||||
|
table = ${QUOTA_TABLE}
|
||||||
username_field = username
|
username_field = username
|
||||||
value_field bytes {
|
value_field = bytes
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
map {
|
||||||
dict_map priv/quota/messages {
|
pattern = priv/quota/messages
|
||||||
sql_table = ${QUOTA_TABLE}
|
table = ${QUOTA_TABLE}
|
||||||
username_field = username
|
username_field = username
|
||||||
value_field messages {
|
value_field = messages
|
||||||
}
|
|
||||||
}
|
}
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
# Create dict used for sieve pre and postfilters
|
# Create dict used for sieve pre and postfilters
|
||||||
cat <<EOF > /etc/dovecot/sql/dovecot-dict-sql-sieve_before.conf
|
cat <<EOF > /etc/dovecot/sql/dovecot-dict-sql-sieve_before.conf
|
||||||
# Autogenerated by mailcow
|
# Autogenerated by mailcow
|
||||||
|
connect = "host=/var/run/mysqld/mysqld.sock dbname=${DBNAME} user=${DBUSER} password=${DBPASS}"
|
||||||
dict_map priv/sieve/name/\$script_name {
|
map {
|
||||||
sql_table = sieve_before
|
pattern = priv/sieve/name/\$script_name
|
||||||
|
table = sieve_before
|
||||||
username_field = username
|
username_field = username
|
||||||
value_field id {
|
value_field = id
|
||||||
}
|
fields {
|
||||||
|
script_name = \$script_name
|
||||||
# The script name field in the table to query
|
|
||||||
key_field script_name {
|
|
||||||
value = \$script_name
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
map {
|
||||||
dict_map priv/sieve/data/\$id {
|
pattern = priv/sieve/data/\$id
|
||||||
sql_table = sieve_before
|
table = sieve_before
|
||||||
username_field = username
|
username_field = username
|
||||||
value_field script_data {
|
value_field = script_data
|
||||||
}
|
fields {
|
||||||
key_field id {
|
id = \$id
|
||||||
value = \$id
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
cat <<EOF > /etc/dovecot/sql/dovecot-dict-sql-sieve_after.conf
|
cat <<EOF > /etc/dovecot/sql/dovecot-dict-sql-sieve_after.conf
|
||||||
# Autogenerated by mailcow
|
# Autogenerated by mailcow
|
||||||
|
connect = "host=/var/run/mysqld/mysqld.sock dbname=${DBNAME} user=${DBUSER} password=${DBPASS}"
|
||||||
dict_map priv/sieve/name/\$script_name {
|
map {
|
||||||
sql_table = sieve_after
|
pattern = priv/sieve/name/\$script_name
|
||||||
|
table = sieve_after
|
||||||
username_field = username
|
username_field = username
|
||||||
value_field id {
|
value_field = id
|
||||||
}
|
fields {
|
||||||
key_field script_name {
|
script_name = \$script_name
|
||||||
value = \$script_name
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
map {
|
||||||
dict_map priv/sieve/data/\$id {
|
pattern = priv/sieve/data/\$id
|
||||||
sql_table = sieve_after
|
table = sieve_after
|
||||||
username_field = username
|
username_field = username
|
||||||
value_field script_data {
|
value_field = script_data
|
||||||
}
|
fields {
|
||||||
key_field id {
|
id = \$id
|
||||||
value = \$id
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
if [[ "${ACL_ANYONE}" == "allow" ]]; then
|
echo -n ${ACL_ANYONE} > /etc/dovecot/acl_anyone
|
||||||
echo -n "yes" > /etc/dovecot/acl_anyone
|
|
||||||
else
|
|
||||||
echo -n "no" > /etc/dovecot/acl_anyone
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "${SKIP_FTS}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
|
if [[ "${SKIP_FTS}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
|
||||||
echo -e "\e[33mDetecting SKIP_FTS=y... not enabling Flatcurve (FTS) then...\e[0m"
|
echo -e "\e[33mDetecting SKIP_FTS=y... not enabling Flatcurve (FTS) then...\e[0m"
|
||||||
echo -n 'quota quota_clone acl mail_crypt mail_crypt_acl mail_log mail_compress notify lazy_expunge' > /etc/dovecot/mail_plugins
|
echo -n 'quota acl zlib mail_crypt mail_crypt_acl mail_log notify listescape replication lazy_expunge' > /etc/dovecot/mail_plugins
|
||||||
echo -n 'quota quota_clone imap_quota imap_acl acl imap_sieve mail_crypt mail_crypt_acl mail_compress notify mail_log' > /etc/dovecot/mail_plugins_imap
|
echo -n 'quota imap_quota imap_acl acl zlib imap_zlib imap_sieve mail_crypt mail_crypt_acl notify listescape replication mail_log' > /etc/dovecot/mail_plugins_imap
|
||||||
echo -n 'quota quota_clone sieve acl mail_crypt mail_crypt_acl mail_compress notify' > /etc/dovecot/mail_plugins_lmtp
|
echo -n 'quota sieve acl zlib mail_crypt mail_crypt_acl notify listescape replication' > /etc/dovecot/mail_plugins_lmtp
|
||||||
else
|
else
|
||||||
echo -e "\e[32mDetecting SKIP_FTS=n... enabling Flatcurve (FTS)\e[0m"
|
echo -e "\e[32mDetecting SKIP_FTS=n... enabling Flatcurve (FTS)\e[0m"
|
||||||
echo -n 'quota quota_clone acl mail_crypt mail_crypt_acl mail_log mail_compress notify fts fts_flatcurve lazy_expunge' > /etc/dovecot/mail_plugins
|
echo -n 'quota acl zlib mail_crypt mail_crypt_acl mail_log notify fts fts_flatcurve listescape replication lazy_expunge' > /etc/dovecot/mail_plugins
|
||||||
echo -n 'quota quota_clone imap_quota imap_acl acl imap_sieve mail_crypt mail_crypt_acl mail_compress notify mail_log fts fts_flatcurve' > /etc/dovecot/mail_plugins_imap
|
echo -n 'quota imap_quota imap_acl acl zlib imap_zlib imap_sieve mail_crypt mail_crypt_acl notify mail_log fts fts_flatcurve listescape replication' > /etc/dovecot/mail_plugins_imap
|
||||||
echo -n 'quota quota_clone sieve acl mail_crypt mail_crypt_acl mail_compress fts fts_flatcurve notify' > /etc/dovecot/mail_plugins_lmtp
|
echo -n 'quota sieve acl zlib mail_crypt mail_crypt_acl fts fts_flatcurve notify listescape replication' > /etc/dovecot/mail_plugins_lmtp
|
||||||
fi
|
fi
|
||||||
chmod 644 /etc/dovecot/mail_plugins /etc/dovecot/mail_plugins_imap /etc/dovecot/mail_plugins_lmtp /templates/quarantine.tpl
|
chmod 644 /etc/dovecot/mail_plugins /etc/dovecot/mail_plugins_imap /etc/dovecot/mail_plugins_lmtp /templates/quarantine.tpl
|
||||||
|
|
||||||
cat <<EOF > /etc/dovecot/sql/dovecot-dict-sql-userdb.conf
|
cat <<EOF > /etc/dovecot/sql/dovecot-dict-sql-userdb.conf
|
||||||
# Autogenerated by mailcow
|
# Autogenerated by mailcow
|
||||||
query = SELECT CONCAT(JSON_UNQUOTE(JSON_VALUE(attributes, '$.mailbox_format')), mailbox_path_prefix, '%{user | domain }}/%{user | username }/Maildir:VOLATILEDIR=/var/volatile/%{user}:INDEX=/var/vmail_index/%{user}') AS mail, '%{protocol}' AS protocol, 5000 AS uid, 5000 AS gid, concat('*:bytes=', quota) AS quota_rule FROM mailbox WHERE username = '%{user}' AND (active = '1' OR active = '2')
|
driver = mysql
|
||||||
|
connect = "host=/var/run/mysqld/mysqld.sock dbname=${DBNAME} user=${DBUSER} password=${DBPASS}"
|
||||||
|
user_query = SELECT CONCAT(JSON_UNQUOTE(JSON_VALUE(attributes, '$.mailbox_format')), mailbox_path_prefix, '%d/%n/${MAILDIR_SUB}:VOLATILEDIR=/var/volatile/%u:INDEX=/var/vmail_index/%u') AS mail, '%s' AS protocol, 5000 AS uid, 5000 AS gid, concat('*:bytes=', quota) AS quota_rule FROM mailbox WHERE username = '%u' AND (active = '1' OR active = '2')
|
||||||
iterate_query = SELECT username FROM mailbox WHERE active = '1' OR active = '2';
|
iterate_query = SELECT username FROM mailbox WHERE active = '1' OR active = '2';
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
@@ -177,8 +158,8 @@ for cert_dir in /etc/ssl/mail/*/ ; do
|
|||||||
domains=($(cat ${cert_dir}domains))
|
domains=($(cat ${cert_dir}domains))
|
||||||
for domain in ${domains[@]}; do
|
for domain in ${domains[@]}; do
|
||||||
echo 'local_name '${domain}' {' >> /etc/dovecot/sni.conf;
|
echo 'local_name '${domain}' {' >> /etc/dovecot/sni.conf;
|
||||||
echo ' ssl_server_cert_file = '${cert_dir}'cert.pem' >> /etc/dovecot/sni.conf;
|
echo ' ssl_cert = <'${cert_dir}'cert.pem' >> /etc/dovecot/sni.conf;
|
||||||
echo ' ssl_server_key_file = '${cert_dir}'key.pem' >> /etc/dovecot/sni.conf;
|
echo ' ssl_key = <'${cert_dir}'key.pem' >> /etc/dovecot/sni.conf;
|
||||||
echo '}' >> /etc/dovecot/sni.conf;
|
echo '}' >> /etc/dovecot/sni.conf;
|
||||||
done
|
done
|
||||||
done
|
done
|
||||||
@@ -202,13 +183,11 @@ else
|
|||||||
fi
|
fi
|
||||||
cat <<EOF > /etc/dovecot/shared_namespace.conf
|
cat <<EOF > /etc/dovecot/shared_namespace.conf
|
||||||
# Autogenerated by mailcow
|
# Autogenerated by mailcow
|
||||||
namespace shared {
|
namespace {
|
||||||
type = shared
|
type = shared
|
||||||
separator = /
|
separator = /
|
||||||
prefix = Shared/\$user/
|
prefix = Shared/%%u/
|
||||||
mail_driver = maildir
|
location = maildir:%%h${MAILDIR_SUB_SHARED}:INDEX=~${MAILDIR_SUB_SHARED}/Shared/%%u
|
||||||
mail_path = %{owner_home}${MAILDIR_SUB_SHARED}
|
|
||||||
mail_index_private_path = ~${MAILDIR_SUB_SHARED}/Shared/%{owner_user}
|
|
||||||
subscriptions = no
|
subscriptions = no
|
||||||
list = children
|
list = children
|
||||||
}
|
}
|
||||||
@@ -218,7 +197,7 @@ EOF
|
|||||||
cat <<EOF > /etc/dovecot/sogo_trusted_ip.conf
|
cat <<EOF > /etc/dovecot/sogo_trusted_ip.conf
|
||||||
# Autogenerated by mailcow
|
# Autogenerated by mailcow
|
||||||
remote ${IPV4_NETWORK}.248 {
|
remote ${IPV4_NETWORK}.248 {
|
||||||
auth_allow_cleartext = yes
|
disable_plaintext_auth = no
|
||||||
}
|
}
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
@@ -227,13 +206,9 @@ RAND_PASS=$(cat /dev/urandom | tr -dc 'a-z0-9' | fold -w 32 | head -n 1)
|
|||||||
echo -n ${RAND_PASS} > /etc/phpfpm/sogo-sso.pass
|
echo -n ${RAND_PASS} > /etc/phpfpm/sogo-sso.pass
|
||||||
cat <<EOF > /etc/dovecot/sogo-sso.conf
|
cat <<EOF > /etc/dovecot/sogo-sso.conf
|
||||||
# Autogenerated by mailcow
|
# Autogenerated by mailcow
|
||||||
passdb static {
|
passdb {
|
||||||
fields {
|
driver = static
|
||||||
allow_real_nets=${IPV4_NETWORK}.248/32
|
args = allow_nets=${IPV4_NETWORK}.248/32 password={plain}${RAND_PASS}
|
||||||
}
|
|
||||||
|
|
||||||
password={plain}${RAND_PASS}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
@@ -261,9 +236,9 @@ fi
|
|||||||
if [[ "${SKIP_FTS}" =~ ^([nN][oO]|[nN])+$ ]]; then
|
if [[ "${SKIP_FTS}" =~ ^([nN][oO]|[nN])+$ ]]; then
|
||||||
echo -e "\e[94mConfiguring FTS Settings...\e[0m"
|
echo -e "\e[94mConfiguring FTS Settings...\e[0m"
|
||||||
echo -e "\e[94mSetting FTS Memory Limit (per process) to ${FTS_HEAP} MB\e[0m"
|
echo -e "\e[94mSetting FTS Memory Limit (per process) to ${FTS_HEAP} MB\e[0m"
|
||||||
sed -i "s/vsz_limit\s*=\s*[0-9]*\s*MB*/vsz_limit=${FTS_HEAP} MB/" /etc/dovecot/conf.d/35-fts.conf
|
sed -i "s/vsz_limit\s*=\s*[0-9]*\s*MB*/vsz_limit=${FTS_HEAP} MB/" /etc/dovecot/conf.d/fts.conf
|
||||||
echo -e "\e[94mSetting FTS Process Limit to ${FTS_PROCS}\e[0m"
|
echo -e "\e[94mSetting FTS Process Limit to ${FTS_PROCS}\e[0m"
|
||||||
sed -i "s/process_limit\s*=\s*[0-9]*/process_limit=${FTS_PROCS}/" /etc/dovecot/conf.d/35-fts.conf
|
sed -i "s/process_limit\s*=\s*[0-9]*/process_limit=${FTS_PROCS}/" /etc/dovecot/conf.d/fts.conf
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 401 is user dovecot
|
# 401 is user dovecot
|
||||||
@@ -275,16 +250,16 @@ else
|
|||||||
chown 401 /mail_crypt/ecprivkey.pem /mail_crypt/ecpubkey.pem
|
chown 401 /mail_crypt/ecprivkey.pem /mail_crypt/ecpubkey.pem
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# # Fix OpenSSL 3.X TLS1.0, 1.1 support (https://community.mailcow.email/d/4062-hi-all/20)
|
# Fix OpenSSL 3.X TLS1.0, 1.1 support (https://community.mailcow.email/d/4062-hi-all/20)
|
||||||
# if grep -qE 'ssl_min_protocol\s*=\s*(TLSv1|TLSv1\.1)\s*$' /etc/dovecot/dovecot.conf /etc/dovecot/extra.conf; then
|
if grep -qE 'ssl_min_protocol\s*=\s*(TLSv1|TLSv1\.1)\s*$' /etc/dovecot/dovecot.conf /etc/dovecot/extra.conf; then
|
||||||
# sed -i '/\[openssl_init\]/a ssl_conf = ssl_configuration' /etc/ssl/openssl.cnf
|
sed -i '/\[openssl_init\]/a ssl_conf = ssl_configuration' /etc/ssl/openssl.cnf
|
||||||
|
|
||||||
# echo "[ssl_configuration]" >> /etc/ssl/openssl.cnf
|
echo "[ssl_configuration]" >> /etc/ssl/openssl.cnf
|
||||||
# echo "system_default = tls_system_default" >> /etc/ssl/openssl.cnf
|
echo "system_default = tls_system_default" >> /etc/ssl/openssl.cnf
|
||||||
# echo "[tls_system_default]" >> /etc/ssl/openssl.cnf
|
echo "[tls_system_default]" >> /etc/ssl/openssl.cnf
|
||||||
# echo "MinProtocol = TLSv1" >> /etc/ssl/openssl.cnf
|
echo "MinProtocol = TLSv1" >> /etc/ssl/openssl.cnf
|
||||||
# echo "CipherString = DEFAULT@SECLEVEL=0" >> /etc/ssl/openssl.cnf
|
echo "CipherString = DEFAULT@SECLEVEL=0" >> /etc/ssl/openssl.cnf
|
||||||
# fi
|
fi
|
||||||
|
|
||||||
# Compile sieve scripts
|
# Compile sieve scripts
|
||||||
sievec /var/vmail/sieve/global_sieve_before.sieve
|
sievec /var/vmail/sieve/global_sieve_before.sieve
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ try:
|
|||||||
if max_score == "":
|
if max_score == "":
|
||||||
max_score = 9999.0
|
max_score = 9999.0
|
||||||
|
|
||||||
def query_mysql(query, headers = True, update = False):
|
def query_mysql(query, params = None, headers = True, update = False):
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
cnx = MySQLdb.connect(user=os.environ.get('DBUSER'), password=os.environ.get('DBPASS'), database=os.environ.get('DBNAME'), charset="utf8mb4", collation="utf8mb4_general_ci")
|
cnx = MySQLdb.connect(user=os.environ.get('DBUSER'), password=os.environ.get('DBPASS'), database=os.environ.get('DBNAME'), charset="utf8mb4", collation="utf8mb4_general_ci")
|
||||||
@@ -57,7 +57,10 @@ try:
|
|||||||
else:
|
else:
|
||||||
break
|
break
|
||||||
cur = cnx.cursor()
|
cur = cnx.cursor()
|
||||||
cur.execute(query)
|
if params:
|
||||||
|
cur.execute(query, params)
|
||||||
|
else:
|
||||||
|
cur.execute(query)
|
||||||
if not update:
|
if not update:
|
||||||
result = []
|
result = []
|
||||||
columns = tuple( [d[0] for d in cur.description] )
|
columns = tuple( [d[0] for d in cur.description] )
|
||||||
@@ -76,7 +79,7 @@ try:
|
|||||||
|
|
||||||
def notify_rcpt(rcpt, msg_count, quarantine_acl, category):
|
def notify_rcpt(rcpt, msg_count, quarantine_acl, category):
|
||||||
if category == "add_header": category = "add header"
|
if category == "add_header": category = "add header"
|
||||||
meta_query = query_mysql('SELECT `qhash`, id, subject, score, sender, created, action FROM quarantine WHERE notified = 0 AND rcpt = "%s" AND score < %f AND (action = "%s" OR "all" = "%s")' % (rcpt, max_score, category, category))
|
meta_query = query_mysql('SELECT `qhash`, id, subject, score, sender, created, action FROM quarantine WHERE notified = 0 AND rcpt = %s AND score < %s AND (action = %s OR "all" = %s)', (rcpt, max_score, category, category))
|
||||||
print("%s: %d of %d messages qualify for notification" % (rcpt, len(meta_query), msg_count))
|
print("%s: %d of %d messages qualify for notification" % (rcpt, len(meta_query), msg_count))
|
||||||
if len(meta_query) == 0:
|
if len(meta_query) == 0:
|
||||||
return
|
return
|
||||||
@@ -130,7 +133,7 @@ try:
|
|||||||
server.sendmail(msg['From'], [str(redirect)] + [str(bcc)], text)
|
server.sendmail(msg['From'], [str(redirect)] + [str(bcc)], text)
|
||||||
server.quit()
|
server.quit()
|
||||||
for res in meta_query:
|
for res in meta_query:
|
||||||
query_mysql('UPDATE quarantine SET notified = 1 WHERE id = "%d"' % (res['id']), update = True)
|
query_mysql('UPDATE quarantine SET notified = 1 WHERE id = %s', (res['id'],), update = True)
|
||||||
r.hset('Q_LAST_NOTIFIED', record['rcpt'], time_now)
|
r.hset('Q_LAST_NOTIFIED', record['rcpt'], time_now)
|
||||||
break
|
break
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
@@ -138,7 +141,7 @@ try:
|
|||||||
print('%s' % (ex))
|
print('%s' % (ex))
|
||||||
time.sleep(3)
|
time.sleep(3)
|
||||||
|
|
||||||
records = query_mysql('SELECT IFNULL(user_acl.quarantine, 0) AS quarantine_acl, count(id) AS counter, rcpt FROM quarantine LEFT OUTER JOIN user_acl ON user_acl.username = rcpt WHERE notified = 0 AND score < %f AND rcpt in (SELECT username FROM mailbox) GROUP BY rcpt' % (max_score))
|
records = query_mysql('SELECT IFNULL(user_acl.quarantine, 0) AS quarantine_acl, count(id) AS counter, rcpt FROM quarantine LEFT OUTER JOIN user_acl ON user_acl.username = rcpt WHERE notified = 0 AND score < %s AND rcpt in (SELECT username FROM mailbox) GROUP BY rcpt', (max_score,))
|
||||||
|
|
||||||
for record in records:
|
for record in records:
|
||||||
attrs = ''
|
attrs = ''
|
||||||
@@ -156,7 +159,7 @@ try:
|
|||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
print('Could not determine last notification for %s, assuming never' % (record['rcpt']))
|
print('Could not determine last notification for %s, assuming never' % (record['rcpt']))
|
||||||
last_notification = 0
|
last_notification = 0
|
||||||
attrs_json = query_mysql('SELECT attributes FROM mailbox WHERE username = "%s"' % (record['rcpt']))
|
attrs_json = query_mysql('SELECT attributes FROM mailbox WHERE username = %s', (record['rcpt'],))
|
||||||
attrs = attrs_json[0]['attributes']
|
attrs = attrs_json[0]['attributes']
|
||||||
if isinstance(attrs, str):
|
if isinstance(attrs, str):
|
||||||
# if attr is str then just load it
|
# if attr is str then just load it
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM nginx: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
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM php:8.2-fpm-alpine3.21
|
FROM php:8.2-fpm-alpine3.24
|
||||||
|
|
||||||
LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"
|
LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"
|
||||||
|
|
||||||
@@ -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 \
|
||||||
@@ -47,6 +47,7 @@ RUN apk add -U --no-cache autoconf \
|
|||||||
libressl-dev \
|
libressl-dev \
|
||||||
librsvg \
|
librsvg \
|
||||||
libtool \
|
libtool \
|
||||||
|
libwebp \
|
||||||
libwebp-dev \
|
libwebp-dev \
|
||||||
libxml2-dev \
|
libxml2-dev \
|
||||||
libxpm \
|
libxpm \
|
||||||
@@ -63,13 +64,16 @@ RUN apk add -U --no-cache autoconf \
|
|||||||
samba-client \
|
samba-client \
|
||||||
zlib-dev \
|
zlib-dev \
|
||||||
tzdata \
|
tzdata \
|
||||||
&& pecl install APCu-${APCU_PECL_VERSION} \
|
&& docker-php-source extract \
|
||||||
&& pecl install imagick-${IMAGICK_PECL_VERSION} \
|
# PECL extensions from their GitHub tags (pecl.php.net is not reliable enough for builds)
|
||||||
&& pecl install mailparse-${MAILPARSE_PECL_VERSION} \
|
&& mkdir -p /usr/src/php/ext/apcu /usr/src/php/ext/imagick /usr/src/php/ext/mailparse /usr/src/php/ext/memcached /usr/src/php/ext/redis \
|
||||||
&& pecl install memcached-${MEMCACHED_PECL_VERSION} \
|
&& curl -fsSL https://github.com/krakjoe/apcu/archive/refs/tags/v${APCU_PECL_VERSION}.tar.gz | tar -xz --strip-components=1 -C /usr/src/php/ext/apcu \
|
||||||
&& pecl install redis-${REDIS_PECL_VERSION} \
|
&& curl -fsSL https://github.com/Imagick/imagick/archive/refs/tags/${IMAGICK_PECL_VERSION}.tar.gz | tar -xz --strip-components=1 -C /usr/src/php/ext/imagick \
|
||||||
&& docker-php-ext-enable apcu imagick memcached mailparse redis \
|
&& sed -i "s/@PACKAGE_VERSION@/${IMAGICK_PECL_VERSION}/" /usr/src/php/ext/imagick/php_imagick.h \
|
||||||
&& pecl clear-cache \
|
&& curl -fsSL https://github.com/php/pecl-mail-mailparse/archive/refs/tags/v${MAILPARSE_PECL_VERSION}.tar.gz | tar -xz --strip-components=1 -C /usr/src/php/ext/mailparse \
|
||||||
|
&& curl -fsSL https://github.com/php-memcached-dev/php-memcached/archive/refs/tags/v${MEMCACHED_PECL_VERSION}.tar.gz | tar -xz --strip-components=1 -C /usr/src/php/ext/memcached \
|
||||||
|
&& curl -fsSL https://github.com/phpredis/phpredis/archive/refs/tags/${REDIS_PECL_VERSION}.tar.gz | tar -xz --strip-components=1 -C /usr/src/php/ext/redis \
|
||||||
|
&& docker-php-ext-install -j 4 apcu imagick mailparse memcached redis \
|
||||||
&& docker-php-ext-configure intl \
|
&& docker-php-ext-configure intl \
|
||||||
&& docker-php-ext-configure exif \
|
&& docker-php-ext-configure exif \
|
||||||
&& docker-php-ext-configure gd --with-freetype=/usr/include/ \
|
&& docker-php-ext-configure gd --with-freetype=/usr/include/ \
|
||||||
@@ -83,6 +87,7 @@ RUN apk add -U --no-cache autoconf \
|
|||||||
&& curl --silent --show-error https://getcomposer.org/installer | php -- --version=${COMPOSER_VERSION} \
|
&& curl --silent --show-error https://getcomposer.org/installer | php -- --version=${COMPOSER_VERSION} \
|
||||||
&& mv composer.phar /usr/local/bin/composer \
|
&& mv composer.phar /usr/local/bin/composer \
|
||||||
&& chmod +x /usr/local/bin/composer \
|
&& chmod +x /usr/local/bin/composer \
|
||||||
|
&& docker-php-source delete \
|
||||||
&& apk del --purge autoconf \
|
&& apk del --purge autoconf \
|
||||||
aspell-dev \
|
aspell-dev \
|
||||||
cyrus-sasl-dev \
|
cyrus-sasl-dev \
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
FROM golang:1.25-bookworm AS builder
|
FROM golang:1.26-trixie AS builder
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
|
|
||||||
ENV CGO_ENABLED=0 \
|
ENV CGO_ENABLED=0 \
|
||||||
GO111MODULE=on \
|
GO111MODULE=on \
|
||||||
NOOPT=1 \
|
NOOPT=1 \
|
||||||
VERSION=1.8.22
|
VERSION=1.14.0
|
||||||
|
|
||||||
RUN git clone --branch v${VERSION} https://github.com/Zuplu/postfix-tlspol && \
|
RUN git clone --branch v${VERSION} https://github.com/Zuplu/postfix-tlspol && \
|
||||||
cd /src/postfix-tlspol && \
|
cd /src/postfix-tlspol && \
|
||||||
scripts/build.sh build-only
|
scripts/build.sh build-only
|
||||||
|
|
||||||
|
|
||||||
FROM debian:bookworm-slim
|
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
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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.2-82~90302bc
|
ARG RSPAMD_VER=rspamd_4.2.1-1~b7a16ae
|
||||||
ARG CODENAME=trixie
|
ARG CODENAME=trixie
|
||||||
ENV LC_ALL=C
|
ENV LC_ALL=C
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
# 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.4
|
# Version: SOGo-5.12.11
|
||||||
#
|
#
|
||||||
# Applied security patches:
|
# Applied security patches:
|
||||||
# - 16ab99e7cf8db2c30b211f0d5e338d7f9e3a9efb: XSS vulnerability in theme parameter
|
# -
|
||||||
#
|
#
|
||||||
# To add new patches, modify SOGO_SECURITY_PATCHES ARG below with space-separated commit hashes
|
# To add new patches, modify SOGO_SECURITY_PATCHES ARG below with space-separated commit hashes
|
||||||
|
|
||||||
@@ -12,10 +12,10 @@ 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.4
|
ARG SOGO_VERSION=SOGo-5.12.11
|
||||||
ARG SOPE_VERSION=SOPE-5.12.4
|
ARG SOPE_VERSION=SOPE-5.12.11
|
||||||
# Security patches to apply (space-separated commit hashes)
|
# Security patches to apply (space-separated commit hashes)
|
||||||
ARG SOGO_SECURITY_PATCHES="16ab99e7cf8db2c30b211f0d5e338d7f9e3a9efb"
|
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>.*)$
|
||||||
ARG GOSU_VERSION=1.19
|
ARG GOSU_VERSION=1.19
|
||||||
ENV LC_ALL=C
|
ENV LC_ALL=C
|
||||||
@@ -26,6 +26,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
|||||||
git \
|
git \
|
||||||
build-essential \
|
build-essential \
|
||||||
gobjc \
|
gobjc \
|
||||||
|
pkg-config \
|
||||||
gnustep-make \
|
gnustep-make \
|
||||||
gnustep-base-runtime \
|
gnustep-base-runtime \
|
||||||
libgnustep-base-dev \
|
libgnustep-base-dev \
|
||||||
@@ -40,6 +41,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
|||||||
libcurl4-openssl-dev \
|
libcurl4-openssl-dev \
|
||||||
libzip-dev \
|
libzip-dev \
|
||||||
libytnef0-dev \
|
libytnef0-dev \
|
||||||
|
libwbxml2-dev \
|
||||||
curl \
|
curl \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
# Runtime dependencies
|
# Runtime dependencies
|
||||||
@@ -68,6 +70,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
|||||||
libcurl4 \
|
libcurl4 \
|
||||||
libzip4 \
|
libzip4 \
|
||||||
libytnef0 \
|
libytnef0 \
|
||||||
|
libwbxml2-1 \
|
||||||
# Download gosu
|
# Download gosu
|
||||||
&& dpkgArch="$(dpkg --print-architecture | awk -F- '{ print $NF }')" \
|
&& dpkgArch="$(dpkg --print-architecture | awk -F- '{ print $NF }')" \
|
||||||
&& wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch" \
|
&& wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch" \
|
||||||
@@ -97,6 +100,9 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
|||||||
&& ./configure --disable-debug --disable-strip \
|
&& ./configure --disable-debug --disable-strip \
|
||||||
&& make -j$(nproc) \
|
&& make -j$(nproc) \
|
||||||
&& make install \
|
&& make install \
|
||||||
|
&& cd /tmp/sogo/ActiveSync \
|
||||||
|
&& . /usr/share/GNUstep/Makefiles/GNUstep.sh \
|
||||||
|
&& make -j$(nproc) install \
|
||||||
&& cd / \
|
&& cd / \
|
||||||
&& rm -rf /tmp/sogo \
|
&& rm -rf /tmp/sogo \
|
||||||
# Strip binaries
|
# Strip binaries
|
||||||
@@ -146,8 +152,8 @@ RUN echo "/usr/lib64" > /etc/ld.so.conf.d/sogo.conf \
|
|||||||
# Create sogo user and group
|
# Create sogo user and group
|
||||||
RUN groupadd -r -g 999 sogo \
|
RUN groupadd -r -g 999 sogo \
|
||||||
&& useradd -r -u 999 -g sogo -d /var/lib/sogo -s /bin/bash -c "SOGo Daemon" sogo \
|
&& useradd -r -u 999 -g sogo -d /var/lib/sogo -s /bin/bash -c "SOGo Daemon" sogo \
|
||||||
&& mkdir -p /var/lib/sogo /var/run/sogo /var/log/sogo \
|
&& mkdir -p /var/lib/sogo /var/run/sogo /var/log/sogo /var/spool/sogo \
|
||||||
&& chown -R sogo:sogo /var/lib/sogo /var/run/sogo /var/log/sogo
|
&& chown -R sogo:sogo /var/lib/sogo /var/run/sogo /var/log/sogo /var/spool/sogo
|
||||||
|
|
||||||
# Create symlinks for SOGo binaries
|
# Create symlinks for SOGo binaries
|
||||||
RUN ln -s /usr/local/sbin/sogod /usr/sbin/sogod \
|
RUN ln -s /usr/local/sbin/sogod /usr/sbin/sogod \
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
--- /usr/lib/GNUstep/SOGo/Templates/UIxAclEditor.wox 2018-08-17 18:29:57.987504204 +0200
|
--- /usr/local/lib/GNUstep/SOGo/Templates/UIxAclEditor.wox 2018-08-17 18:29:57.987504204 +0200
|
||||||
+++ /usr/lib/GNUstep/SOGo/Templates/UIxAclEditor.wox 2018-08-17 18:29:35.918291298 +0200
|
+++ /usr/local/lib/GNUstep/SOGo/Templates/UIxAclEditor.wox 2018-08-17 18:29:35.918291298 +0200
|
||||||
@@ -46,7 +46,7 @@
|
@@ -46,7 +46,7 @@
|
||||||
</md-item-template>
|
</md-item-template>
|
||||||
</md-autocomplete>
|
</md-autocomplete>
|
||||||
|
|||||||
@@ -130,18 +130,22 @@ chmod 600 /var/lib/sogo/GNUstep/Defaults/sogod.plist
|
|||||||
# Patch ACLs
|
# Patch ACLs
|
||||||
#if [[ ${ACL_ANYONE} == 'allow' ]]; then
|
#if [[ ${ACL_ANYONE} == 'allow' ]]; then
|
||||||
# #enable any or authenticated targets for ACL
|
# #enable any or authenticated targets for ACL
|
||||||
# if patch -R -sfN --dry-run /usr/lib/GNUstep/SOGo/Templates/UIxAclEditor.wox < /acl.diff > /dev/null; then
|
# if patch -R -sfN --dry-run /usr/local/lib/GNUstep/SOGo/Templates/UIxAclEditor.wox < /acl.diff > /dev/null; then
|
||||||
# patch -R /usr/lib/GNUstep/SOGo/Templates/UIxAclEditor.wox < /acl.diff;
|
# patch -R /usr/local/lib/GNUstep/SOGo/Templates/UIxAclEditor.wox < /acl.diff;
|
||||||
# fi
|
# fi
|
||||||
#else
|
#else
|
||||||
# #disable any or authenticated targets for ACL
|
# #disable any or authenticated targets for ACL
|
||||||
# if patch -sfN --dry-run /usr/lib/GNUstep/SOGo/Templates/UIxAclEditor.wox < /acl.diff > /dev/null; then
|
# if patch -sfN --dry-run /usr/local/lib/GNUstep/SOGo/Templates/UIxAclEditor.wox < /acl.diff > /dev/null; then
|
||||||
# patch /usr/lib/GNUstep/SOGo/Templates/UIxAclEditor.wox < /acl.diff;
|
# patch /usr/local/lib/GNUstep/SOGo/Templates/UIxAclEditor.wox < /acl.diff;
|
||||||
# fi
|
# fi
|
||||||
#fi
|
#fi
|
||||||
|
|
||||||
if patch -R -sfN --dry-run /usr/lib/GNUstep/SOGo/Templates/UIxTopnavToolbar.wox < /navMailcowBtns.diff > /dev/null; then
|
# Apply custom UI patch (reverse patch to ADD buttons)
|
||||||
patch -R /usr/lib/GNUstep/SOGo/Templates/UIxTopnavToolbar.wox < /navMailcowBtns.diff;
|
if patch -R -sfN --dry-run /usr/local/lib/GNUstep/SOGo/Templates/UIxTopnavToolbar.wox < /navMailcowBtns.diff > /dev/null; then
|
||||||
|
echo "Applying navMailcowBtns patch (reverse to add buttons)..."
|
||||||
|
patch -R /usr/local/lib/GNUstep/SOGo/Templates/UIxTopnavToolbar.wox < /navMailcowBtns.diff;
|
||||||
|
else
|
||||||
|
echo "navMailcowBtns patch already applied or cannot be applied"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Rename custom logo, if any
|
# Rename custom logo, if any
|
||||||
@@ -149,7 +153,7 @@ fi
|
|||||||
|
|
||||||
# Rsync web content
|
# Rsync web content
|
||||||
echo "Syncing web content with named volume"
|
echo "Syncing web content with named volume"
|
||||||
rsync -a /usr/lib/GNUstep/SOGo/. /sogo_web/
|
rsync -a /usr/local/lib/GNUstep/SOGo/. /sogo_web/
|
||||||
|
|
||||||
# Chown backup path
|
# Chown backup path
|
||||||
chown -R sogo:sogo /sogo_backup
|
chown -R sogo:sogo /sogo_backup
|
||||||
|
|||||||
@@ -2,11 +2,15 @@ FROM alpine:3.23
|
|||||||
|
|
||||||
LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"
|
LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"
|
||||||
|
|
||||||
|
# install unbound from alpine:edge to get security patches
|
||||||
|
# minimum version is pinned
|
||||||
|
RUN apk add --no-cache --repository=https://dl-cdn.alpinelinux.org/alpine/edge/main "unbound>=1.26.1-r0"
|
||||||
|
|
||||||
|
# install other packages from regular alpine stable repo
|
||||||
RUN apk add --update --no-cache \
|
RUN apk add --update --no-cache \
|
||||||
curl \
|
curl \
|
||||||
bind-tools \
|
bind-tools \
|
||||||
coreutils \
|
coreutils \
|
||||||
unbound \
|
|
||||||
bash \
|
bash \
|
||||||
openssl \
|
openssl \
|
||||||
drill \
|
drill \
|
||||||
|
|||||||
@@ -37,5 +37,6 @@ RUN apk add --update \
|
|||||||
COPY watchdog.sh /watchdog.sh
|
COPY watchdog.sh /watchdog.sh
|
||||||
COPY check_mysql_slavestatus.sh /usr/lib/nagios/plugins/check_mysql_slavestatus.sh
|
COPY check_mysql_slavestatus.sh /usr/lib/nagios/plugins/check_mysql_slavestatus.sh
|
||||||
COPY check_dns.sh /usr/lib/mailcow/check_dns.sh
|
COPY check_dns.sh /usr/lib/mailcow/check_dns.sh
|
||||||
|
COPY client.cnf /etc/my.cnf.d/client.cnf
|
||||||
|
|
||||||
CMD ["/watchdog.sh"]
|
CMD ["/watchdog.sh"]
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
[client]
|
||||||
|
ssl = false
|
||||||
|
ssl-verify-server-cert = false
|
||||||
@@ -38,7 +38,7 @@ if [[ ! -p /tmp/com_pipe ]]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Wait for containers
|
# Wait for containers
|
||||||
while ! mariadb-admin status --ssl=false --socket=/var/run/mysqld/mysqld.sock -u${DBUSER} -p${DBPASS} --silent; do
|
while ! mariadb-admin status --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u${DBUSER} -p${DBPASS} --silent; do
|
||||||
echo "Waiting for SQL..."
|
echo "Waiting for SQL..."
|
||||||
sleep 2
|
sleep 2
|
||||||
done
|
done
|
||||||
@@ -359,8 +359,8 @@ mysql_checks() {
|
|||||||
while [ ${err_count} -lt ${THRESHOLD} ]; do
|
while [ ${err_count} -lt ${THRESHOLD} ]; do
|
||||||
touch /tmp/mysql-mailcow; echo "$(tail -50 /tmp/mysql-mailcow)" > /tmp/mysql-mailcow
|
touch /tmp/mysql-mailcow; echo "$(tail -50 /tmp/mysql-mailcow)" > /tmp/mysql-mailcow
|
||||||
err_c_cur=${err_count}
|
err_c_cur=${err_count}
|
||||||
/usr/lib/nagios/plugins/check_mysql -s /var/run/mysqld/mysqld.sock -u ${DBUSER} -p ${DBPASS} -d ${DBNAME} 2>> /tmp/mysql-mailcow 1>&2; err_count=$(( ${err_count} + $? ))
|
/usr/lib/nagios/plugins/check_mysql -f /etc/my.cnf.d/client.cnf -s /var/run/mysqld/mysqld.sock -u ${DBUSER} -p ${DBPASS} -d ${DBNAME} 2>> /tmp/mysql-mailcow 1>&2; err_count=$(( ${err_count} + $? ))
|
||||||
/usr/lib/nagios/plugins/check_mysql_query -s /var/run/mysqld/mysqld.sock -u ${DBUSER} -p ${DBPASS} -d ${DBNAME} -q "SELECT COUNT(*) FROM information_schema.tables" 2>> /tmp/mysql-mailcow 1>&2; err_count=$(( ${err_count} + $? ))
|
/usr/lib/nagios/plugins/check_mysql_query -f /etc/my.cnf.d/client.cnf -s /var/run/mysqld/mysqld.sock -u ${DBUSER} -p ${DBPASS} -d ${DBNAME} -q "SELECT COUNT(*) FROM information_schema.tables" 2>> /tmp/mysql-mailcow 1>&2; err_count=$(( ${err_count} + $? ))
|
||||||
[ ${err_c_cur} -eq ${err_count} ] && [ ! $((${err_count} - 1)) -lt 0 ] && err_count=$((${err_count} - 1)) diff_c=1
|
[ ${err_c_cur} -eq ${err_count} ] && [ ! $((${err_count} - 1)) -lt 0 ] && err_count=$((${err_count} - 1)) diff_c=1
|
||||||
[ ${err_c_cur} -ne ${err_count} ] && diff_c=$(( ${err_c_cur} - ${err_count} ))
|
[ ${err_c_cur} -ne ${err_count} ] && diff_c=$(( ${err_c_cur} - ${err_count} ))
|
||||||
progress "MySQL/MariaDB" ${THRESHOLD} $(( ${THRESHOLD} - ${err_count} )) ${diff_c}
|
progress "MySQL/MariaDB" ${THRESHOLD} $(( ${THRESHOLD} - ${err_count} )) ${diff_c}
|
||||||
@@ -384,7 +384,7 @@ mysql_repl_checks() {
|
|||||||
while [ ${err_count} -lt ${THRESHOLD} ]; do
|
while [ ${err_count} -lt ${THRESHOLD} ]; do
|
||||||
touch /tmp/mysql_repl_checks; echo "$(tail -50 /tmp/mysql_repl_checks)" > /tmp/mysql_repl_checks
|
touch /tmp/mysql_repl_checks; echo "$(tail -50 /tmp/mysql_repl_checks)" > /tmp/mysql_repl_checks
|
||||||
err_c_cur=${err_count}
|
err_c_cur=${err_count}
|
||||||
/usr/lib/nagios/plugins/check_mysql_slavestatus.sh -S /var/run/mysqld/mysqld.sock -u root -p ${DBROOT} 2>> /tmp/mysql_repl_checks 1>&2; err_count=$(( ${err_count} + $? ))
|
/usr/lib/nagios/plugins/check_mysql_slavestatus.sh -o /etc/my.cnf.d/client.cnf -S /var/run/mysqld/mysqld.sock -u root -p ${DBROOT} 2>> /tmp/mysql_repl_checks 1>&2; err_count=$(( ${err_count} + $? ))
|
||||||
[ ${err_c_cur} -eq ${err_count} ] && [ ! $((${err_count} - 1)) -lt 0 ] && err_count=$((${err_count} - 1)) diff_c=1
|
[ ${err_c_cur} -eq ${err_count} ] && [ ! $((${err_count} - 1)) -lt 0 ] && err_count=$((${err_count} - 1)) diff_c=1
|
||||||
[ ${err_c_cur} -ne ${err_count} ] && diff_c=$(( ${err_c_cur} - ${err_count} ))
|
[ ${err_c_cur} -ne ${err_count} ] && diff_c=$(( ${err_c_cur} - ${err_count} ))
|
||||||
progress "MySQL/MariaDB replication" ${THRESHOLD} $(( ${THRESHOLD} - ${err_count} )) ${diff_c}
|
progress "MySQL/MariaDB replication" ${THRESHOLD} $(( ${THRESHOLD} - ${err_count} )) ${diff_c}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
function auth_password_verify(request, password)
|
function auth_password_verify(request, password)
|
||||||
request.domain = request.auth_user:match("@(.+)") or nil
|
|
||||||
if request.domain == nil then
|
if request.domain == nil then
|
||||||
return dovecot.auth.PASSDB_RESULT_USER_UNKNOWN, "No such user"
|
return dovecot.auth.PASSDB_RESULT_USER_UNKNOWN, "No such user"
|
||||||
end
|
end
|
||||||
@@ -10,10 +9,10 @@ function auth_password_verify(request, password)
|
|||||||
https.TIMEOUT = 30
|
https.TIMEOUT = 30
|
||||||
|
|
||||||
local req = {
|
local req = {
|
||||||
username = request.auth_user,
|
username = request.user,
|
||||||
password = password,
|
password = password,
|
||||||
real_rip = request.remote_ip,
|
real_rip = request.real_rip,
|
||||||
service = request.protocol
|
service = request.service
|
||||||
}
|
}
|
||||||
local req_json = json.encode(req)
|
local req_json = json.encode(req)
|
||||||
local res = {}
|
local res = {}
|
||||||
@@ -33,7 +32,17 @@ function auth_password_verify(request, password)
|
|||||||
-- Returning PASSDB_RESULT_PASSWORD_MISMATCH will reset the user's auth cache entry.
|
-- Returning PASSDB_RESULT_PASSWORD_MISMATCH will reset the user's auth cache entry.
|
||||||
-- Returning PASSDB_RESULT_INTERNAL_FAILURE keeps the existing cache entry,
|
-- Returning PASSDB_RESULT_INTERNAL_FAILURE keeps the existing cache entry,
|
||||||
-- even if the TTL has expired. Useful to avoid cache eviction during backend issues.
|
-- even if the TTL has expired. Useful to avoid cache eviction during backend issues.
|
||||||
|
|
||||||
|
-- On a network-level failure (nginx unreachable, DNS failure, timeout) https.request
|
||||||
|
-- returns nil plus an error string, so c is not a numeric HTTP status code. Treat this
|
||||||
|
-- as a backend outage and keep the cache entry, rather than wiping it as a mismatch.
|
||||||
|
if type(c) ~= "number" then
|
||||||
|
dovecot.i_info("HTTP request to auth backend failed with " .. tostring(c) .. " for user " .. request.user)
|
||||||
|
return dovecot.auth.PASSDB_RESULT_INTERNAL_FAILURE, "Upstream unreachable"
|
||||||
|
end
|
||||||
|
|
||||||
if c ~= 200 and c ~= 401 then
|
if c ~= 200 and c ~= 401 then
|
||||||
|
dovecot.i_info("HTTP request failed with " .. tostring(c) .. " for user " .. request.user)
|
||||||
return dovecot.auth.PASSDB_RESULT_PASSWORD_MISMATCH, "Upstream error"
|
return dovecot.auth.PASSDB_RESULT_PASSWORD_MISMATCH, "Upstream error"
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -46,7 +55,7 @@ function auth_password_verify(request, password)
|
|||||||
end
|
end
|
||||||
|
|
||||||
if response_json.success == true then
|
if response_json.success == true then
|
||||||
return dovecot.auth.PASSDB_RESULT_OK, { msg = "" }
|
return dovecot.auth.PASSDB_RESULT_OK, ""
|
||||||
end
|
end
|
||||||
|
|
||||||
return dovecot.auth.PASSDB_RESULT_PASSWORD_MISMATCH, "Failed to authenticate"
|
return dovecot.auth.PASSDB_RESULT_PASSWORD_MISMATCH, "Failed to authenticate"
|
||||||
@@ -55,7 +64,3 @@ end
|
|||||||
function auth_passdb_lookup(req)
|
function auth_passdb_lookup(req)
|
||||||
return dovecot.auth.PASSDB_RESULT_USER_UNKNOWN, ""
|
return dovecot.auth.PASSDB_RESULT_USER_UNKNOWN, ""
|
||||||
end
|
end
|
||||||
|
|
||||||
function auth_passdb_get_cache_key()
|
|
||||||
return "%{protocol}:%{user | username}\t:%{password}"
|
|
||||||
end
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
# /etc/dovecot/conf.d/05-core.conf
|
|
||||||
# Core, single-line settings that don't fit elsewhere.
|
|
||||||
recipient_delimiter = +
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
# /etc/dovecot/conf.d/10-logging.conf
|
|
||||||
# Logging and debug.
|
|
||||||
#mail_debug = yes
|
|
||||||
#auth_debug = yes
|
|
||||||
#log_debug = category=fts-flatcurve
|
|
||||||
log_path = syslog
|
|
||||||
log_timestamp = "%Y-%m-%d %H:%M:%S "
|
|
||||||
login_log_format_elements = "user=<%{user}> method=%{mechanism} rip=%{remote_ip} lip=%{local_ip} mpid=%{mail_pid} %{secured} session=<%{session}>"
|
|
||||||
|
|
||||||
# Mail event logging.
|
|
||||||
mail_log_events = delete undelete expunge copy mailbox_delete mailbox_rename
|
|
||||||
mail_log_fields = uid box msgid size
|
|
||||||
mail_log_cached_only = yes
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
# /etc/dovecot/conf.d/10-mail.conf
|
|
||||||
# Mail storage paths and core mail settings.
|
|
||||||
mail_home = /var/vmail/%{user | domain }/%{user | username }
|
|
||||||
mail_driver = maildir
|
|
||||||
mail_path = ~/Maildir
|
|
||||||
mail_index_path = /var/vmail_index/%{user}
|
|
||||||
mail_plugins = </etc/dovecot/mail_plugins
|
|
||||||
mail_shared_explicit_inbox = yes
|
|
||||||
mailbox_list_storage_escape_char = "\\"
|
|
||||||
mail_prefetch_count = 30
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
# /etc/dovecot/conf.d/10-ssl.conf
|
|
||||||
# TLS/SSL settings.
|
|
||||||
ssl_min_protocol = TLSv1.2
|
|
||||||
ssl_cipher_list = ALL:!ADH:!LOW:!SSLv2:!SSLv3:!EXP:!aNULL:!eNULL:!3DES:!MD5:!PSK:!DSS:!RC4:!SEED:!IDEA:+HIGH:+MEDIUM
|
|
||||||
ssl_options = no_ticket
|
|
||||||
#ssl_dh_parameters_length = 2048
|
|
||||||
|
|
||||||
ssl_server {
|
|
||||||
prefer_ciphers = server
|
|
||||||
dh_file = /etc/ssl/mail/dhparams.pem
|
|
||||||
cert_file = /etc/ssl/mail/cert.pem
|
|
||||||
key_file = /etc/ssl/mail/key.pem
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
# /etc/dovecot/conf.d/11-sql.conf
|
|
||||||
# Default SQL driver used by SQL-based dicts/userdb.
|
|
||||||
sql_driver = mysql
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
# Autogenerated by mailcow - DO NOT TOUCH!
|
|
||||||
mysql /var/run/mysqld/mysqld.sock {
|
|
||||||
dbname=mailcow
|
|
||||||
user=mailcow
|
|
||||||
password=D8O9BIivJc7Pb2VCfpAeLbAzUOZ0
|
|
||||||
|
|
||||||
ssl = no
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
# /etc/dovecot/conf.d/12-storage-attachments.conf
|
|
||||||
# External attachment storage.
|
|
||||||
fs mail_ext_attachment {
|
|
||||||
fs_driver = posix
|
|
||||||
mail_ext_attachment_path = /var/attachments
|
|
||||||
mail_ext_attachment_min_size = 128k
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
# /etc/dovecot/conf.d/15-performance.conf
|
|
||||||
# Performance and mailbox tuning.
|
|
||||||
# Enable only when you do not manually touch cur/.
|
|
||||||
maildir_very_dirty_syncs = yes
|
|
||||||
|
|
||||||
# NFS examples | Only modify if using NFS!:
|
|
||||||
#mm ap_disable = yes
|
|
||||||
#mail_fsync = always
|
|
||||||
#mail_nfs_index = yes
|
|
||||||
#mail_nfs_storage = yes
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
# /etc/dovecot/conf.d/20-auth.conf
|
|
||||||
# Authentication mechanisms, master/user separation, passdb chain, auth cache.
|
|
||||||
auth_mechanisms = plain login
|
|
||||||
auth_allow_cleartext = yes
|
|
||||||
auth_master_user_separator = *
|
|
||||||
|
|
||||||
auth_cache_verify_password_with_worker = yes
|
|
||||||
auth_cache_negative_ttl = 60s
|
|
||||||
auth_cache_ttl = 300s
|
|
||||||
auth_cache_size = 10M
|
|
||||||
auth_verbose_passwords = sha1:6
|
|
||||||
|
|
||||||
# 1) Lua password verification (blocking, return mapping).
|
|
||||||
passdb lua {
|
|
||||||
driver = lua
|
|
||||||
lua_file = /etc/dovecot/auth/passwd-verify.lua
|
|
||||||
lua_settings {
|
|
||||||
blocking=yes
|
|
||||||
result_success = return-ok
|
|
||||||
result_failure = continue
|
|
||||||
result_internalfail = continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# 2) Master password for master user logins.
|
|
||||||
passdb master {
|
|
||||||
driver = passwd-file
|
|
||||||
passwd_file_path = /etc/dovecot/dovecot-master.passwd
|
|
||||||
master = yes
|
|
||||||
skip = authenticated
|
|
||||||
}
|
|
||||||
|
|
||||||
# 3) Mandatory return layer: empty Lua (e.g. for forced reset).
|
|
||||||
passdb empty-lua {
|
|
||||||
driver = lua
|
|
||||||
lua_file = /etc/dovecot/auth/passwd-verify.lua
|
|
||||||
lua_settings {
|
|
||||||
blocking = yes
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
# /etc/dovecot/conf.d/20-userdb.conf
|
|
||||||
# User database chain.
|
|
||||||
userdb passwd {
|
|
||||||
driver = passwd-file
|
|
||||||
passwd_file_path = /etc/dovecot/dovecot-master.userdb
|
|
||||||
}
|
|
||||||
|
|
||||||
userdb sql {
|
|
||||||
!include /etc/dovecot/sql/dovecot-dict-sql-userdb.conf
|
|
||||||
skip = found
|
|
||||||
}
|
|
||||||
@@ -1,144 +0,0 @@
|
|||||||
# /etc/dovecot/conf.d/25-services.conf
|
|
||||||
# All service listeners and workers.
|
|
||||||
|
|
||||||
# doveadm remote admin
|
|
||||||
# Set doveadm_password in extra.conf.
|
|
||||||
service doveadm {
|
|
||||||
inet_listener doveadm {
|
|
||||||
port = 12345
|
|
||||||
}
|
|
||||||
vsz_limit = 2048 MB
|
|
||||||
}
|
|
||||||
|
|
||||||
# dict
|
|
||||||
service dict {
|
|
||||||
unix_listener dict {
|
|
||||||
mode = 0660
|
|
||||||
user = vmail
|
|
||||||
group = vmail
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# log
|
|
||||||
service log {
|
|
||||||
user = dovenull
|
|
||||||
}
|
|
||||||
|
|
||||||
# config socket
|
|
||||||
service config {
|
|
||||||
unix_listener config {
|
|
||||||
user = root
|
|
||||||
group = vmail
|
|
||||||
mode = 0660
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# anvil socket
|
|
||||||
service anvil {
|
|
||||||
unix_listener anvil {
|
|
||||||
user = vmail
|
|
||||||
group = vmail
|
|
||||||
mode = 0660
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# auth sockets and inet
|
|
||||||
service auth {
|
|
||||||
inet_listener auth-inet {
|
|
||||||
port = 10001
|
|
||||||
}
|
|
||||||
unix_listener auth-master {
|
|
||||||
mode = 0600
|
|
||||||
user = vmail
|
|
||||||
}
|
|
||||||
unix_listener auth-userdb {
|
|
||||||
mode = 0600
|
|
||||||
user = vmail
|
|
||||||
}
|
|
||||||
vsz_limit = 2G
|
|
||||||
}
|
|
||||||
|
|
||||||
# managesieve login
|
|
||||||
service managesieve-login {
|
|
||||||
inet_listener sieve {
|
|
||||||
port = 4190
|
|
||||||
}
|
|
||||||
inet_listener sieve_haproxy {
|
|
||||||
port = 14190
|
|
||||||
haproxy = yes
|
|
||||||
}
|
|
||||||
service_restart_request_count = 1
|
|
||||||
process_min_avail = 2
|
|
||||||
vsz_limit = 1G
|
|
||||||
}
|
|
||||||
|
|
||||||
# imap login
|
|
||||||
service imap-login {
|
|
||||||
service_restart_request_count = 1
|
|
||||||
process_min_avail = 2
|
|
||||||
process_limit = 10000
|
|
||||||
vsz_limit = 1G
|
|
||||||
user = dovenull
|
|
||||||
inet_listener imap_haproxy {
|
|
||||||
port = 10143
|
|
||||||
haproxy = yes
|
|
||||||
}
|
|
||||||
inet_listener imaps_haproxy {
|
|
||||||
port = 10993
|
|
||||||
ssl = yes
|
|
||||||
haproxy = yes
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# pop3 login
|
|
||||||
service pop3-login {
|
|
||||||
service_restart_request_count = 1
|
|
||||||
process_min_avail = 1
|
|
||||||
vsz_limit = 1G
|
|
||||||
inet_listener pop3_haproxy {
|
|
||||||
port = 10110
|
|
||||||
haproxy = yes
|
|
||||||
}
|
|
||||||
inet_listener pop3s_haproxy {
|
|
||||||
port = 10995
|
|
||||||
ssl = yes
|
|
||||||
haproxy = yes
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# imap worker
|
|
||||||
service imap {
|
|
||||||
executable = imap
|
|
||||||
user = vmail
|
|
||||||
vsz_limit = 1G
|
|
||||||
}
|
|
||||||
|
|
||||||
# managesieve worker
|
|
||||||
service managesieve {
|
|
||||||
process_limit = 256
|
|
||||||
}
|
|
||||||
|
|
||||||
# lmtp
|
|
||||||
service lmtp {
|
|
||||||
inet_listener lmtp-inet {
|
|
||||||
port = 24
|
|
||||||
}
|
|
||||||
user = vmail
|
|
||||||
}
|
|
||||||
|
|
||||||
# quota warning hook
|
|
||||||
service quota-warning {
|
|
||||||
executable = script /usr/local/bin/quota_notify.py
|
|
||||||
user = vmail
|
|
||||||
unix_listener quota-warning {
|
|
||||||
user = vmail
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# stats
|
|
||||||
service stats {
|
|
||||||
unix_listener stats-writer {
|
|
||||||
mode = 0660
|
|
||||||
user = vmail
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
# /etc/dovecot/conf.d/30-protocols.conf
|
|
||||||
# IMAP protocol specifics.
|
|
||||||
protocol imap {
|
|
||||||
mail_plugins = </etc/dovecot/mail_plugins_imap
|
|
||||||
imap_metadata = yes
|
|
||||||
}
|
|
||||||
|
|
||||||
# LMTP protocol specifics.
|
|
||||||
protocol lmtp {
|
|
||||||
mail_plugins = </etc/dovecot/mail_plugins_lmtp
|
|
||||||
auth_socket_path = /var/run/dovecot/auth-master
|
|
||||||
}
|
|
||||||
|
|
||||||
# ManageSieve protocol specifics.
|
|
||||||
protocol sieve {
|
|
||||||
managesieve_logout_format = bytes=%i/%o
|
|
||||||
}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
# mailcow FTS Flatcurve Settings, change them as you like.
|
|
||||||
|
|
||||||
# Maximum term length can be set via the 'maxlen' argument (maxlen is
|
|
||||||
# specified in bytes, not number of UTF-8 characters)
|
|
||||||
language_tokenizer_address_token_maxlen = 100
|
|
||||||
language_tokenizer_generic_algorithm = simple
|
|
||||||
language_tokenizer_generic_token_maxlen = 30
|
|
||||||
|
|
||||||
# These are not flatcurve settings, but required for Dovecot FTS. See
|
|
||||||
# Dovecot FTS Configuration link above for further information.
|
|
||||||
language en {
|
|
||||||
default = yes
|
|
||||||
language_filters = lowercase snowball english-possessive stopwords
|
|
||||||
}
|
|
||||||
|
|
||||||
language de {
|
|
||||||
language_filters = lowercase snowball stopwords
|
|
||||||
}
|
|
||||||
|
|
||||||
language es {
|
|
||||||
language_filters = lowercase snowball stopwords
|
|
||||||
}
|
|
||||||
|
|
||||||
language_tokenizers = generic email-address
|
|
||||||
|
|
||||||
fts_search_timeout = 300s
|
|
||||||
|
|
||||||
fts_autoindex = yes
|
|
||||||
# Tweak this setting if you only want to ensure big and frequent folders are indexed, not all.
|
|
||||||
fts_autoindex_max_recent_msgs = 20
|
|
||||||
fts flatcurve {
|
|
||||||
substring_search = no
|
|
||||||
}
|
|
||||||
|
|
||||||
### THIS PART WILL BE CHANGED BY MODIFYING mailcow.conf AUTOMATICALLY DURING RUNTIME! ###
|
|
||||||
|
|
||||||
service indexer-worker {
|
|
||||||
# Max amount of simultaniously running indexer jobs.
|
|
||||||
process_limit=1
|
|
||||||
|
|
||||||
# Max amount of RAM used by EACH indexer process.
|
|
||||||
vsz_limit=128 MB
|
|
||||||
}
|
|
||||||
|
|
||||||
### THIS PART WILL BE CHANGED BY MODIFYING mailcow.conf AUTOMATICALLY DURING RUNTIME! ###
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
# /etc/dovecot/conf.d/40-acl.conf
|
|
||||||
# ACL and shared mailboxes.
|
|
||||||
imap_acl_allow_anyone = </etc/dovecot/acl_anyone
|
|
||||||
|
|
||||||
acl_sharing_map {
|
|
||||||
dict file {
|
|
||||||
path = /var/vmail/shared-mailboxes.db
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
acl_driver = vfile
|
|
||||||
acl_user = %{user}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
# /etc/dovecot/conf.d/40-attributes.conf
|
|
||||||
# User/mail attributes.
|
|
||||||
mail_attribute {
|
|
||||||
dict file {
|
|
||||||
path = /etc/dovecot/dovecot-attributes
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
# /etc/dovecot/conf.d/50-quota.conf
|
|
||||||
# Quota configuration and notifications.
|
|
||||||
quota "User quota" {
|
|
||||||
driver = count
|
|
||||||
|
|
||||||
warning warn-95 {
|
|
||||||
quota_storage_percentage = 95
|
|
||||||
execute quota-warning {
|
|
||||||
args = 95 %{user}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
warning warn-80 {
|
|
||||||
quota_storage_percentage = 80
|
|
||||||
execute quota-warning {
|
|
||||||
args = 80 %{user}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
quota_clone {
|
|
||||||
dict proxy {
|
|
||||||
name = mysql_quota
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,97 +0,0 @@
|
|||||||
# /etc/dovecot/conf.d/60-sieve-pipeline.conf
|
|
||||||
# Complete Sieve pipeline: personal/global scripts, plugins, limits, training.
|
|
||||||
|
|
||||||
# Global before/after (file and dict)
|
|
||||||
sieve_script before {
|
|
||||||
type = before
|
|
||||||
driver = file
|
|
||||||
path = /var/vmail/sieve/global_sieve_before.sieve
|
|
||||||
}
|
|
||||||
|
|
||||||
sieve_script before2 {
|
|
||||||
type = before
|
|
||||||
driver = dict
|
|
||||||
name = active
|
|
||||||
dict proxy {
|
|
||||||
name = sieve_before
|
|
||||||
}
|
|
||||||
bin_path = /var/vmail/sieve_before_bindir/%{user}
|
|
||||||
}
|
|
||||||
|
|
||||||
sieve_script after {
|
|
||||||
type = after
|
|
||||||
driver = file
|
|
||||||
path = /var/vmail/sieve/global_sieve_after.sieve
|
|
||||||
}
|
|
||||||
|
|
||||||
sieve_script after2 {
|
|
||||||
type = after
|
|
||||||
driver = dict
|
|
||||||
name = active
|
|
||||||
dict proxy {
|
|
||||||
name = sieve_after
|
|
||||||
}
|
|
||||||
bin_path = /var/vmail/sieve_after_bindir/%{user}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Personal scripts
|
|
||||||
sieve_script personal {
|
|
||||||
type = personal
|
|
||||||
driver = file
|
|
||||||
path = ~/sieve
|
|
||||||
active_path = ~/.dovecot.sieve
|
|
||||||
}
|
|
||||||
|
|
||||||
# Plugins and behavior
|
|
||||||
sieve_plugins = sieve_imapsieve sieve_extprograms
|
|
||||||
sieve_vacation_send_from_recipient = yes
|
|
||||||
sieve_redirect_envelope_from = recipient
|
|
||||||
|
|
||||||
# IMAPSieve training
|
|
||||||
imapsieve_from Junk {
|
|
||||||
sieve_script ham {
|
|
||||||
type = before
|
|
||||||
cause = copy
|
|
||||||
path = /usr/lib/dovecot/sieve/report-ham.sieve
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mailbox Junk {
|
|
||||||
sieve_script spam {
|
|
||||||
type = before
|
|
||||||
cause = copy
|
|
||||||
path = /usr/lib/dovecot/sieve/report-spam.sieve
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Extprograms and extensions
|
|
||||||
sieve_pipe_bin_dir = /usr/lib/dovecot/sieve
|
|
||||||
sieve_plugins {
|
|
||||||
sieve_extprograms = yes
|
|
||||||
}
|
|
||||||
sieve_global_extensions {
|
|
||||||
vnd.dovecot.pipe = yes
|
|
||||||
vnd.dovecot.execute = yes
|
|
||||||
}
|
|
||||||
|
|
||||||
# Limits and duplicate handling
|
|
||||||
sieve_max_script_size = 1M
|
|
||||||
sieve_max_redirects = 100
|
|
||||||
sieve_max_actions = 101
|
|
||||||
sieve_quota_script_count = 0
|
|
||||||
sieve_quota_storage_size = 0
|
|
||||||
sieve_vacation_min_period = 5s
|
|
||||||
sieve_vacation_max_period = 365d
|
|
||||||
sieve_vacation_default_period = 60s
|
|
||||||
sieve_duplicate_default_period = 1m
|
|
||||||
sieve_duplicate_max_period = 7d
|
|
||||||
|
|
||||||
sieve_extensions {
|
|
||||||
vacation-seconds = yes
|
|
||||||
editheader = yes
|
|
||||||
}
|
|
||||||
|
|
||||||
# pipe sockets in /var/run/dovecot/sieve-pipe
|
|
||||||
sieve_pipe_socket_dir = sieve-pipe
|
|
||||||
|
|
||||||
# execute sockets in /var/run/dovecot/sieve-execute
|
|
||||||
sieve_execute_socket_dir = sieve-execute
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
# /etc/dovecot/conf.d/70-crypto.conf
|
|
||||||
# Global mail-crypt keys.
|
|
||||||
crypt_global_private_key global {
|
|
||||||
crypt_private_key_file = /mail_crypt/ecprivkey.pem
|
|
||||||
}
|
|
||||||
crypt_global_public_key_file = /mail_crypt/ecpubkey.pem
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
# /etc/dovecot/conf.d/80-compress.conf
|
|
||||||
# Compression settings.
|
|
||||||
mail_compress_write_method = lz4
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
# /etc/dovecot/conf.d/90-dict.conf
|
|
||||||
# Dict declarations and SQL bindings.
|
|
||||||
dict_server {
|
|
||||||
dict sieve_after {
|
|
||||||
driver = sql
|
|
||||||
!include /etc/dovecot/sql/dovecot-dict-sql-sieve_after.conf
|
|
||||||
}
|
|
||||||
|
|
||||||
dict sieve_before {
|
|
||||||
driver = sql
|
|
||||||
!include /etc/dovecot/sql/dovecot-dict-sql-sieve_before.conf
|
|
||||||
}
|
|
||||||
|
|
||||||
dict mysql_quota {
|
|
||||||
driver = sql
|
|
||||||
!include /etc/dovecot/sql/dovecot-dict-sql-quota.conf
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
# /etc/dovecot/conf.d/90-limits.conf
|
|
||||||
# Connection and memory limits; doveadm port.
|
|
||||||
mail_max_userip_connections = 500
|
|
||||||
imap_max_line_length = 2 M
|
|
||||||
default_client_limit = 10400
|
|
||||||
default_vsz_limit = 1024 M
|
|
||||||
doveadm_port = 12345
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
# /etc/dovecot/conf.d/99-includes.conf
|
|
||||||
# Late includes and site-specific bits.
|
|
||||||
|
|
||||||
# Mailbox layout includes (if used)
|
|
||||||
!include /etc/dovecot/dovecot.folders.conf
|
|
||||||
|
|
||||||
# Optional replication
|
|
||||||
!include_try /etc/dovecot/mail_replica.conf
|
|
||||||
|
|
||||||
# Existing includes you already had
|
|
||||||
!include_try /etc/dovecot/sni.conf
|
|
||||||
!include_try /etc/dovecot/sogo_trusted_ip.conf
|
|
||||||
!include_try /etc/dovecot/shared_namespace.conf
|
|
||||||
!include_try /etc/dovecot/conf.d/fts.conf
|
|
||||||
|
|
||||||
# Remote auth override
|
|
||||||
remote 127.0.0.1 {
|
|
||||||
auth_allow_cleartext = yes
|
|
||||||
}
|
|
||||||
|
|
||||||
# Outbound submission target
|
|
||||||
submission_host = postfix:588
|
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
# mailcow FTS Flatcurve Settings, change them as you like.
|
||||||
|
plugin {
|
||||||
|
fts_autoindex = yes
|
||||||
|
fts_autoindex_exclude = \Junk
|
||||||
|
fts_autoindex_exclude2 = \Trash
|
||||||
|
# Tweak this setting if you only want to ensure big and frequent folders are indexed, not all.
|
||||||
|
fts_autoindex_max_recent_msgs = 20
|
||||||
|
fts = flatcurve
|
||||||
|
|
||||||
|
# Maximum term length can be set via the 'maxlen' argument (maxlen is
|
||||||
|
# specified in bytes, not number of UTF-8 characters)
|
||||||
|
fts_tokenizer_email_address = maxlen=100
|
||||||
|
fts_tokenizer_generic = algorithm=simple maxlen=30
|
||||||
|
|
||||||
|
# These are not flatcurve settings, but required for Dovecot FTS. See
|
||||||
|
# Dovecot FTS Configuration link above for further information.
|
||||||
|
fts_languages = en es de
|
||||||
|
fts_tokenizers = generic email-address
|
||||||
|
|
||||||
|
# OPTIONAL: Recommended default FTS core configuration
|
||||||
|
fts_filters = normalizer-icu snowball stopwords
|
||||||
|
fts_filters_en = lowercase snowball english-possessive stopwords
|
||||||
|
|
||||||
|
fts_index_timeout = 300s
|
||||||
|
}
|
||||||
|
|
||||||
|
### THIS PART WILL BE CHANGED BY MODIFYING mailcow.conf AUTOMATICALLY DURING RUNTIME! ###
|
||||||
|
|
||||||
|
service indexer-worker {
|
||||||
|
# Max amount of simultaniously running indexer jobs.
|
||||||
|
process_limit=1
|
||||||
|
|
||||||
|
# Max amount of RAM used by EACH indexer process.
|
||||||
|
vsz_limit=128 MB
|
||||||
|
}
|
||||||
|
|
||||||
|
### THIS PART WILL BE CHANGED BY MODIFYING mailcow.conf AUTOMATICALLY DURING RUNTIME! ###
|
||||||
+307
-30
@@ -1,34 +1,311 @@
|
|||||||
# /etc/dovecot/dovecot.conf
|
# --------------------------------------------------------------------------
|
||||||
# Base file kept minimal. All real config lives under conf.d/.
|
# Please create a file "extra.conf" for persistent overrides to dovecot.conf
|
||||||
dovecot_config_version = 2.4.0
|
# --------------------------------------------------------------------------
|
||||||
dovecot_storage_version = 2.4.0
|
# LDAP example:
|
||||||
|
#passdb {
|
||||||
|
# args = /etc/dovecot/ldap/passdb.conf
|
||||||
|
# driver = ldap
|
||||||
|
#}
|
||||||
|
|
||||||
listen = *,[::]
|
auth_mechanisms = plain login
|
||||||
|
#mail_debug = yes
|
||||||
|
#auth_debug = yes
|
||||||
|
#log_debug = category=fts-flatcurve # Activate Logging for Flatcurve FTS Searchings
|
||||||
|
log_path = syslog
|
||||||
|
disable_plaintext_auth = yes
|
||||||
|
# Uncomment on NFS share
|
||||||
|
#mmap_disable = yes
|
||||||
|
#mail_fsync = always
|
||||||
|
#mail_nfs_index = yes
|
||||||
|
#mail_nfs_storage = yes
|
||||||
|
login_log_format_elements = "user=<%u> method=%m rip=%r lip=%l mpid=%e %c %k"
|
||||||
|
mail_home = /var/vmail/%d/%n
|
||||||
|
mail_location = maildir:~/
|
||||||
|
mail_plugins = </etc/dovecot/mail_plugins
|
||||||
|
mail_attachment_fs = crypt:set_prefix=mail_crypt_global:posix:
|
||||||
|
mail_attachment_dir = /var/attachments
|
||||||
|
mail_attachment_min_size = 128k
|
||||||
|
# Significantly speeds up very large mailboxes, but is only safe to enable if
|
||||||
|
# you do not manually modify the files in the `cur` directories in
|
||||||
|
# mailcowdockerized_vmail-vol-1.
|
||||||
|
# https://docs.mailcow.email/manual-guides/Dovecot/u_e-dovecot-performance/
|
||||||
|
maildir_very_dirty_syncs = yes
|
||||||
|
|
||||||
|
# Dovecot 2.2
|
||||||
|
#ssl_protocols = !SSLv3
|
||||||
|
# Dovecot 2.3
|
||||||
|
ssl_min_protocol = TLSv1.2
|
||||||
|
|
||||||
|
ssl_prefer_server_ciphers = yes
|
||||||
|
ssl_cipher_list = ALL:!ADH:!LOW:!SSLv2:!SSLv3:!EXP:!aNULL:!eNULL:!3DES:!MD5:!PSK:!DSS:!RC4:!SEED:!IDEA:+HIGH:+MEDIUM
|
||||||
|
|
||||||
|
# Default in Dovecot 2.3
|
||||||
|
ssl_options = no_compression no_ticket
|
||||||
|
|
||||||
|
# New in Dovecot 2.3
|
||||||
|
ssl_dh = </etc/ssl/mail/dhparams.pem
|
||||||
|
# Dovecot 2.2
|
||||||
|
#ssl_dh_parameters_length = 2048
|
||||||
|
log_timestamp = "%Y-%m-%d %H:%M:%S "
|
||||||
|
recipient_delimiter = +
|
||||||
|
auth_master_user_separator = *
|
||||||
|
mail_shared_explicit_inbox = yes
|
||||||
|
mail_prefetch_count = 30
|
||||||
|
passdb {
|
||||||
|
driver = lua
|
||||||
|
args = file=/etc/dovecot/auth/passwd-verify.lua blocking=yes cache_key=%s:%u:%w
|
||||||
|
result_success = return-ok
|
||||||
|
result_failure = continue
|
||||||
|
result_internalfail = continue
|
||||||
|
}
|
||||||
|
# try a master passwd
|
||||||
|
passdb {
|
||||||
|
driver = passwd-file
|
||||||
|
args = /etc/dovecot/dovecot-master.passwd
|
||||||
|
master = yes
|
||||||
|
skip = authenticated
|
||||||
|
}
|
||||||
|
# check for regular password - if empty (e.g. force-passwd-reset), previous pass=yes passdbs also fail
|
||||||
|
# a return of the following passdb is mandatory
|
||||||
|
passdb {
|
||||||
|
driver = lua
|
||||||
|
args = file=/etc/dovecot/auth/passwd-verify.lua blocking=yes
|
||||||
|
}
|
||||||
|
# Set doveadm_password=your-secret-password in data/conf/dovecot/extra.conf (create if missing)
|
||||||
|
service doveadm {
|
||||||
|
inet_listener {
|
||||||
|
port = 12345
|
||||||
|
}
|
||||||
|
vsz_limit=2048 MB
|
||||||
|
}
|
||||||
|
!include /etc/dovecot/dovecot.folders.conf
|
||||||
protocols = imap sieve lmtp pop3
|
protocols = imap sieve lmtp pop3
|
||||||
|
service dict {
|
||||||
|
unix_listener dict {
|
||||||
|
mode = 0660
|
||||||
|
user = vmail
|
||||||
|
group = vmail
|
||||||
|
}
|
||||||
|
}
|
||||||
|
service log {
|
||||||
|
user = dovenull
|
||||||
|
}
|
||||||
|
service config {
|
||||||
|
unix_listener config {
|
||||||
|
user = root
|
||||||
|
group = vmail
|
||||||
|
mode = 0660
|
||||||
|
}
|
||||||
|
}
|
||||||
|
service auth {
|
||||||
|
inet_listener auth-inet {
|
||||||
|
port = 10001
|
||||||
|
}
|
||||||
|
unix_listener auth-master {
|
||||||
|
mode = 0600
|
||||||
|
user = vmail
|
||||||
|
}
|
||||||
|
unix_listener auth-userdb {
|
||||||
|
mode = 0600
|
||||||
|
user = vmail
|
||||||
|
}
|
||||||
|
vsz_limit = 2G
|
||||||
|
}
|
||||||
|
service managesieve-login {
|
||||||
|
inet_listener sieve {
|
||||||
|
port = 4190
|
||||||
|
}
|
||||||
|
inet_listener sieve_haproxy {
|
||||||
|
port = 14190
|
||||||
|
haproxy = yes
|
||||||
|
}
|
||||||
|
service_count = 1
|
||||||
|
process_min_avail = 2
|
||||||
|
vsz_limit = 1G
|
||||||
|
}
|
||||||
|
service imap-login {
|
||||||
|
service_count = 1
|
||||||
|
process_min_avail = 2
|
||||||
|
process_limit = 10000
|
||||||
|
vsz_limit = 1G
|
||||||
|
user = dovenull
|
||||||
|
inet_listener imap_haproxy {
|
||||||
|
port = 10143
|
||||||
|
haproxy = yes
|
||||||
|
}
|
||||||
|
inet_listener imaps_haproxy {
|
||||||
|
port = 10993
|
||||||
|
ssl = yes
|
||||||
|
haproxy = yes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
service pop3-login {
|
||||||
|
service_count = 1
|
||||||
|
process_min_avail = 1
|
||||||
|
vsz_limit = 1G
|
||||||
|
inet_listener pop3_haproxy {
|
||||||
|
port = 10110
|
||||||
|
haproxy = yes
|
||||||
|
}
|
||||||
|
inet_listener pop3s_haproxy {
|
||||||
|
port = 10995
|
||||||
|
ssl = yes
|
||||||
|
haproxy = yes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
service imap {
|
||||||
|
executable = imap
|
||||||
|
user = vmail
|
||||||
|
vsz_limit = 1G
|
||||||
|
}
|
||||||
|
service managesieve {
|
||||||
|
process_limit = 256
|
||||||
|
}
|
||||||
|
service lmtp {
|
||||||
|
inet_listener lmtp-inet {
|
||||||
|
port = 24
|
||||||
|
}
|
||||||
|
user = vmail
|
||||||
|
}
|
||||||
|
listen = *,[::]
|
||||||
|
ssl_cert = </etc/ssl/mail/cert.pem
|
||||||
|
ssl_key = </etc/ssl/mail/key.pem
|
||||||
|
userdb {
|
||||||
|
driver = passwd-file
|
||||||
|
args = /etc/dovecot/dovecot-master.userdb
|
||||||
|
}
|
||||||
|
userdb {
|
||||||
|
args = /etc/dovecot/sql/dovecot-dict-sql-userdb.conf
|
||||||
|
driver = sql
|
||||||
|
skip = found
|
||||||
|
}
|
||||||
|
protocol imap {
|
||||||
|
mail_plugins = </etc/dovecot/mail_plugins_imap
|
||||||
|
imap_metadata = yes
|
||||||
|
}
|
||||||
|
mail_attribute_dict = file:%h/dovecot-attributes
|
||||||
|
protocol lmtp {
|
||||||
|
mail_plugins = </etc/dovecot/mail_plugins_lmtp
|
||||||
|
auth_socket_path = /var/run/dovecot/auth-master
|
||||||
|
}
|
||||||
|
protocol sieve {
|
||||||
|
managesieve_logout_format = bytes=%i/%o
|
||||||
|
}
|
||||||
|
plugin {
|
||||||
|
# Allow "any" or "authenticated" to be used in ACLs
|
||||||
|
acl_anyone = </etc/dovecot/acl_anyone
|
||||||
|
acl_shared_dict = file:/var/vmail/shared-mailboxes.db
|
||||||
|
acl = vfile
|
||||||
|
acl_user = %u
|
||||||
|
quota = dict:Userquota::proxy::sqlquota
|
||||||
|
quota_rule2 = Trash:storage=+100%%
|
||||||
|
sieve = /var/vmail/sieve/%u.sieve
|
||||||
|
sieve_plugins = sieve_imapsieve sieve_extprograms
|
||||||
|
sieve_vacation_send_from_recipient = yes
|
||||||
|
sieve_redirect_envelope_from = recipient
|
||||||
|
# From elsewhere to Spam folder
|
||||||
|
imapsieve_mailbox1_name = Junk
|
||||||
|
imapsieve_mailbox1_causes = COPY
|
||||||
|
imapsieve_mailbox1_before = file:/usr/lib/dovecot/sieve/report-spam.sieve
|
||||||
|
# END
|
||||||
|
# From Spam folder to elsewhere
|
||||||
|
imapsieve_mailbox2_name = *
|
||||||
|
imapsieve_mailbox2_from = Junk
|
||||||
|
imapsieve_mailbox2_causes = COPY
|
||||||
|
imapsieve_mailbox2_before = file:/usr/lib/dovecot/sieve/report-ham.sieve
|
||||||
|
# END
|
||||||
|
master_user = %u
|
||||||
|
quota_warning = storage=95%% quota-warning 95 %u
|
||||||
|
quota_warning2 = storage=80%% quota-warning 80 %u
|
||||||
|
sieve_pipe_bin_dir = /usr/lib/dovecot/sieve
|
||||||
|
sieve_global_extensions = +vnd.dovecot.pipe +vnd.dovecot.execute
|
||||||
|
sieve_extensions = +notify +imapflags +vacation-seconds +editheader
|
||||||
|
sieve_max_script_size = 1M
|
||||||
|
sieve_max_redirects = 100
|
||||||
|
sieve_max_actions = 101
|
||||||
|
sieve_quota_max_scripts = 0
|
||||||
|
sieve_quota_max_storage = 0
|
||||||
|
listescape_char = "\\"
|
||||||
|
sieve_vacation_min_period = 5s
|
||||||
|
sieve_vacation_max_period = 0
|
||||||
|
sieve_vacation_default_period = 60s
|
||||||
|
sieve_before = /var/vmail/sieve/global_sieve_before.sieve
|
||||||
|
sieve_before2 = dict:proxy::sieve_before;name=active;bindir=/var/vmail/sieve_before_bindir
|
||||||
|
sieve_after = dict:proxy::sieve_after;name=active;bindir=/var/vmail/sieve_after_bindir
|
||||||
|
sieve_after2 = /var/vmail/sieve/global_sieve_after.sieve
|
||||||
|
sieve_duplicate_default_period = 1m
|
||||||
|
sieve_duplicate_max_period = 7d
|
||||||
|
|
||||||
!include_try /etc/dovecot/conf.d/05-core.conf
|
# -- Global keys
|
||||||
!include_try /etc/dovecot/conf.d/10-logging.conf
|
mail_crypt_global_private_key = </mail_crypt/ecprivkey.pem
|
||||||
!include_try /etc/dovecot/conf.d/10-mail.conf
|
mail_crypt_global_public_key = </mail_crypt/ecpubkey.pem
|
||||||
!include_try /etc/dovecot/conf.d/10-ssl.conf
|
mail_crypt_save_version = 2
|
||||||
!include_try /etc/dovecot/conf.d/11-sql.conf
|
|
||||||
!include_try /etc/dovecot/conf.d/12-mysql.conf
|
|
||||||
!include_try /etc/dovecot/conf.d/12-storage-attachments.conf
|
|
||||||
!include_try /etc/dovecot/conf.d/15-performance.conf
|
|
||||||
!include_try /etc/dovecot/conf.d/20-auth.conf
|
|
||||||
!include_try /etc/dovecot/conf.d/20-userdb.conf
|
|
||||||
!include_try /etc/dovecot/conf.d/25-services.conf
|
|
||||||
!include_try /etc/dovecot/conf.d/30-protocols.conf
|
|
||||||
!include_try /etc/dovecot/conf.d/35-fts.conf
|
|
||||||
!include_try /etc/dovecot/conf.d/40-acl.conf
|
|
||||||
!include_try /etc/dovecot/conf.d/40-attributes.conf
|
|
||||||
!include_try /etc/dovecot/conf.d/50-quota.conf
|
|
||||||
!include_try /etc/dovecot/conf.d/60-sieve-pipeline.conf
|
|
||||||
!include_try /etc/dovecot/conf.d/70-crypto.conf
|
|
||||||
!include_try /etc/dovecot/conf.d/80-compress.conf
|
|
||||||
!include_try /etc/dovecot/conf.d/80-mail-logging.conf
|
|
||||||
!include_try /etc/dovecot/conf.d/90-limits.conf
|
|
||||||
!include_try /etc/dovecot/conf.d/90-dict.conf
|
|
||||||
!include_try /etc/dovecot/conf.d/99-includes.conf
|
|
||||||
|
|
||||||
# Last: local overrides
|
# Enable compression while saving, lz4 Dovecot v2.3.17+
|
||||||
!include_try /etc/dovecot/extra.conf
|
zlib_save = lz4
|
||||||
|
|
||||||
|
mail_log_events = delete undelete expunge copy mailbox_delete mailbox_rename
|
||||||
|
mail_log_fields = uid box msgid size
|
||||||
|
mail_log_cached_only = yes
|
||||||
|
|
||||||
|
# Try set mail_replica
|
||||||
|
!include_try /etc/dovecot/mail_replica.conf
|
||||||
|
}
|
||||||
|
service quota-warning {
|
||||||
|
executable = script /usr/local/bin/quota_notify.py
|
||||||
|
# use some unprivileged user for executing the quota warnings
|
||||||
|
user = vmail
|
||||||
|
unix_listener quota-warning {
|
||||||
|
user = vmail
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dict {
|
||||||
|
sqlquota = mysql:/etc/dovecot/sql/dovecot-dict-sql-quota.conf
|
||||||
|
sieve_after = mysql:/etc/dovecot/sql/dovecot-dict-sql-sieve_after.conf
|
||||||
|
sieve_before = mysql:/etc/dovecot/sql/dovecot-dict-sql-sieve_before.conf
|
||||||
|
}
|
||||||
|
remote 127.0.0.1 {
|
||||||
|
disable_plaintext_auth = no
|
||||||
|
}
|
||||||
|
submission_host = postfix:588
|
||||||
|
mail_max_userip_connections = 500
|
||||||
|
service stats {
|
||||||
|
unix_listener stats-writer {
|
||||||
|
mode = 0660
|
||||||
|
user = vmail
|
||||||
|
}
|
||||||
|
}
|
||||||
|
imap_max_line_length = 2 M
|
||||||
|
auth_cache_verify_password_with_worker = yes
|
||||||
|
auth_cache_negative_ttl = 60s
|
||||||
|
auth_cache_ttl = 300s
|
||||||
|
auth_cache_size = 10M
|
||||||
|
auth_verbose_passwords = sha1:6
|
||||||
|
service replicator {
|
||||||
|
process_min_avail = 1
|
||||||
|
}
|
||||||
|
service aggregator {
|
||||||
|
fifo_listener replication-notify-fifo {
|
||||||
|
user = vmail
|
||||||
|
}
|
||||||
|
unix_listener replication-notify {
|
||||||
|
user = vmail
|
||||||
|
}
|
||||||
|
}
|
||||||
|
service replicator {
|
||||||
|
unix_listener replicator-doveadm {
|
||||||
|
mode = 0666
|
||||||
|
}
|
||||||
|
}
|
||||||
|
replication_max_conns = 10
|
||||||
|
doveadm_port = 12345
|
||||||
|
replication_dsync_parameters = -d -l 30 -U -n INBOX
|
||||||
|
# <Includes>
|
||||||
|
!include_try /etc/dovecot/sni.conf
|
||||||
|
!include_try /etc/dovecot/sogo_trusted_ip.conf
|
||||||
|
!include_try /etc/dovecot/extra.conf
|
||||||
|
!include_try /etc/dovecot/shared_namespace.conf
|
||||||
|
!include_try /etc/dovecot/conf.d/fts.conf
|
||||||
|
# </Includes>
|
||||||
|
default_client_limit = 10400
|
||||||
|
default_vsz_limit = 1024 M
|
||||||
@@ -1,14 +1,10 @@
|
|||||||
namespace inbox {
|
namespace inbox {
|
||||||
inbox = yes
|
inbox = yes
|
||||||
|
location =
|
||||||
separator = /
|
separator = /
|
||||||
mailbox storage/* {
|
|
||||||
quota_storage_extra = 100M
|
|
||||||
}
|
|
||||||
mailbox "Trash" {
|
mailbox "Trash" {
|
||||||
auto = subscribe
|
auto = subscribe
|
||||||
special_use = \Trash
|
special_use = \Trash
|
||||||
quota_storage_percentage = 100
|
|
||||||
fts_autoindex = no
|
|
||||||
}
|
}
|
||||||
mailbox "Deleted Messages" {
|
mailbox "Deleted Messages" {
|
||||||
special_use = \Trash
|
special_use = \Trash
|
||||||
@@ -199,7 +195,6 @@ namespace inbox {
|
|||||||
mailbox "Junk" {
|
mailbox "Junk" {
|
||||||
auto = subscribe
|
auto = subscribe
|
||||||
special_use = \Junk
|
special_use = \Junk
|
||||||
fts_autoindex = no
|
|
||||||
}
|
}
|
||||||
mailbox "Junk-E-Mail" {
|
mailbox "Junk-E-Mail" {
|
||||||
special_use = \Junk
|
special_use = \Junk
|
||||||
|
|||||||
@@ -1,13 +1,3 @@
|
|||||||
# global_sieve_before script
|
# global_sieve_before script
|
||||||
# global_sieve_before -> user sieve_before (mailcow UI) -> user sieve_after (mailcow UI) -> global_sieve_after
|
# global_sieve_before -> user sieve_before (mailcow UI) -> user sieve_after (mailcow UI) -> global_sieve_after
|
||||||
|
|
||||||
require ["mailbox", "fileinto"];
|
|
||||||
|
|
||||||
if header :contains ["Chat-Version"] [""] {
|
|
||||||
if mailboxexists "DeltaChat" {
|
|
||||||
fileinto "DeltaChat";
|
|
||||||
} else {
|
|
||||||
fileinto :create "DeltaChat";
|
|
||||||
}
|
|
||||||
stop;
|
|
||||||
}
|
|
||||||
@@ -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,9 @@ location ~ \.php$ {
|
|||||||
include /etc/nginx/fastcgi_params;
|
include /etc/nginx/fastcgi_params;
|
||||||
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
||||||
fastcgi_param PATH_INFO $fastcgi_path_info;
|
fastcgi_param PATH_INFO $fastcgi_path_info;
|
||||||
|
fastcgi_param HTTP_X_REAL_IP $remote_addr;
|
||||||
|
# trusted internal-auth marker; empty for external clients (see nginx.conf map)
|
||||||
|
fastcgi_param SOGO_AUTH_INTERNAL $sogo_auth_internal;
|
||||||
fastcgi_read_timeout 3600;
|
fastcgi_read_timeout 3600;
|
||||||
fastcgi_send_timeout 3600;
|
fastcgi_send_timeout 3600;
|
||||||
}
|
}
|
||||||
@@ -261,19 +264,19 @@ location ~* /sogo$ {
|
|||||||
}
|
}
|
||||||
|
|
||||||
location /SOGo.woa/WebServerResources/ {
|
location /SOGo.woa/WebServerResources/ {
|
||||||
alias /usr/lib/GNUstep/SOGo/WebServerResources/;
|
alias /usr/local/lib/GNUstep/SOGo/WebServerResources/;
|
||||||
}
|
}
|
||||||
|
|
||||||
location /.woa/WebServerResources/ {
|
location /.woa/WebServerResources/ {
|
||||||
alias /usr/lib/GNUstep/SOGo/WebServerResources/;
|
alias /usr/local/lib/GNUstep/SOGo/WebServerResources/;
|
||||||
}
|
}
|
||||||
|
|
||||||
location /SOGo/WebServerResources/ {
|
location /SOGo/WebServerResources/ {
|
||||||
alias /usr/lib/GNUstep/SOGo/WebServerResources/;
|
alias /usr/local/lib/GNUstep/SOGo/WebServerResources/;
|
||||||
}
|
}
|
||||||
|
|
||||||
location (^/SOGo/so/ControlPanel/Products/[^/]*UI/Resources/.*\.(jpg|png|gif|css|js)$) {
|
location (^/SOGo/so/ControlPanel/Products/[^/]*UI/Resources/.*\.(jpg|png|gif|css|js)$) {
|
||||||
alias /usr/lib/GNUstep/SOGo/$1.SOGo/Resources/$2;
|
alias /usr/local/lib/GNUstep/SOGo/$1.SOGo/Resources/$2;
|
||||||
}
|
}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
max_execution_time = 3600
|
max_execution_time = 3600
|
||||||
max_input_time = 3600
|
max_input_time = 3600
|
||||||
memory_limit = 512M
|
memory_limit = 512M
|
||||||
|
; not used by mailcow, disabled as hardening (PHP security release 2026-10-21)
|
||||||
|
session.upload_progress.enabled = Off
|
||||||
@@ -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
|
||||||
|
|
||||||
@@ -152,7 +150,7 @@ smtp_sasl_auth_enable = yes
|
|||||||
smtp_sasl_password_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_sasl_passwd_maps_sender_dependent.cf
|
smtp_sasl_password_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_sasl_passwd_maps_sender_dependent.cf
|
||||||
smtp_sasl_security_options =
|
smtp_sasl_security_options =
|
||||||
smtp_sasl_mechanism_filter = plain, login
|
smtp_sasl_mechanism_filter = plain, login
|
||||||
smtp_tls_policy_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_tls_policy_override_maps.cf socketmap:inet:postfix-tlspol:8642:QUERY
|
smtp_tls_policy_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_tls_policy_override_maps.cf socketmap:inet:postfix-tlspol:8642:QUERYwithTLSRPT
|
||||||
smtp_header_checks = pcre:/opt/postfix/conf/anonymize_headers.pcre
|
smtp_header_checks = pcre:/opt/postfix/conf/anonymize_headers.pcre
|
||||||
mail_name = Postcow
|
mail_name = Postcow
|
||||||
# local_transport map catches local destinations and prevents routing local dests when the next map would route "*"
|
# local_transport map catches local destinations and prevents routing local dests when the next map would route "*"
|
||||||
|
|||||||
@@ -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,7 +1,8 @@
|
|||||||
# Whitelist generated by Postwhite v3.4 on Sun Mar 1 00:29:01 UTC 2026
|
# Whitelist generated by Postwhite v3.4 on Thu Oct 1 00:35:20 UTC 2026
|
||||||
# https://github.com/stevejenkins/postwhite/
|
# https://github.com/stevejenkins/postwhite/
|
||||||
# 2174 total rules
|
# 2246 total rules
|
||||||
2a00:1450:4000::/36 permit
|
2a00:1450:4000::/36 permit
|
||||||
|
2a00:1450:4864::/56 permit
|
||||||
2a01:111:f400::/48 permit
|
2a01:111:f400::/48 permit
|
||||||
2a01:111:f403:2800::/53 permit
|
2a01:111:f403:2800::/53 permit
|
||||||
2a01:111:f403:8000::/51 permit
|
2a01:111:f403:8000::/51 permit
|
||||||
@@ -28,11 +29,15 @@
|
|||||||
2a01:b747:3004:200::/56 permit
|
2a01:b747:3004:200::/56 permit
|
||||||
2a01:b747:3005:200::/56 permit
|
2a01:b747:3005:200::/56 permit
|
||||||
2a01:b747:3006:200::/56 permit
|
2a01:b747:3006:200::/56 permit
|
||||||
|
2a01:b747:3007::/56 permit
|
||||||
2a02:a60:0:5::/64 permit
|
2a02:a60:0:5::/64 permit
|
||||||
2a0f:f640::/56 permit
|
2a0f:f640::/56 permit
|
||||||
2c0f:fb50:4000::/36 permit
|
2c0f:fb50:4000::/36 permit
|
||||||
|
2c0f:fb50:4864::/56 permit
|
||||||
|
2.207.151.32/27 permit
|
||||||
2.207.151.53 permit
|
2.207.151.53 permit
|
||||||
2.207.217.30 permit
|
2.207.217.30 permit
|
||||||
|
2.207.223.160/27 permit
|
||||||
3.64.237.68 permit
|
3.64.237.68 permit
|
||||||
3.65.3.180 permit
|
3.65.3.180 permit
|
||||||
3.70.123.177 permit
|
3.70.123.177 permit
|
||||||
@@ -53,13 +58,14 @@
|
|||||||
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.0/23 permit
|
||||||
|
8.39.54.240/29 permit
|
||||||
8.39.54.250/31 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.0/23 permit
|
||||||
|
8.40.222.240/29 permit
|
||||||
8.40.222.250/31 permit
|
8.40.222.250/31 permit
|
||||||
|
8.47.10.9 permit
|
||||||
12.130.86.238 permit
|
12.130.86.238 permit
|
||||||
13.107.213.51 permit
|
|
||||||
13.107.246.51 permit
|
|
||||||
13.108.16.0/20 permit
|
13.108.16.0/20 permit
|
||||||
13.110.208.0/21 permit
|
13.110.208.0/21 permit
|
||||||
13.110.209.0/24 permit
|
13.110.209.0/24 permit
|
||||||
@@ -119,7 +125,6 @@
|
|||||||
23.249.208.0/20 permit
|
23.249.208.0/20 permit
|
||||||
23.251.224.0/19 permit
|
23.251.224.0/19 permit
|
||||||
23.253.141.0/24 permit
|
23.253.141.0/24 permit
|
||||||
23.253.182.0/23 permit
|
|
||||||
23.253.182.103 permit
|
23.253.182.103 permit
|
||||||
23.253.183.145 permit
|
23.253.183.145 permit
|
||||||
23.253.183.146 permit
|
23.253.183.146 permit
|
||||||
@@ -141,24 +146,7 @@
|
|||||||
27.123.206.80/28 permit
|
27.123.206.80/28 permit
|
||||||
31.47.251.17 permit
|
31.47.251.17 permit
|
||||||
31.186.239.0/24 permit
|
31.186.239.0/24 permit
|
||||||
34.2.64.0/22 permit
|
34.2.64.0/19 permit
|
||||||
34.2.68.0/23 permit
|
|
||||||
34.2.70.0/23 permit
|
|
||||||
34.2.71.64/26 permit
|
|
||||||
34.2.72.0/22 permit
|
|
||||||
34.2.75.0/26 permit
|
|
||||||
34.2.78.0/23 permit
|
|
||||||
34.2.80.0/23 permit
|
|
||||||
34.2.82.0/23 permit
|
|
||||||
34.2.84.0/24 permit
|
|
||||||
34.2.84.64/26 permit
|
|
||||||
34.2.85.0/24 permit
|
|
||||||
34.2.85.64/26 permit
|
|
||||||
34.2.86.0/23 permit
|
|
||||||
34.2.88.0/23 permit
|
|
||||||
34.2.90.0/23 permit
|
|
||||||
34.2.92.0/23 permit
|
|
||||||
34.2.94.0/23 permit
|
|
||||||
34.70.158.162 permit
|
34.70.158.162 permit
|
||||||
34.74.74.140 permit
|
34.74.74.140 permit
|
||||||
34.83.159.189 permit
|
34.83.159.189 permit
|
||||||
@@ -185,6 +173,7 @@
|
|||||||
35.205.92.9 permit
|
35.205.92.9 permit
|
||||||
35.228.216.85 permit
|
35.228.216.85 permit
|
||||||
35.242.169.159 permit
|
35.242.169.159 permit
|
||||||
|
37.187.220.204 permit
|
||||||
37.188.97.188 permit
|
37.188.97.188 permit
|
||||||
37.218.248.47 permit
|
37.218.248.47 permit
|
||||||
37.218.249.47 permit
|
37.218.249.47 permit
|
||||||
@@ -258,8 +247,8 @@
|
|||||||
46.228.39.112/31 permit
|
46.228.39.112/31 permit
|
||||||
46.243.88.174 permit
|
46.243.88.174 permit
|
||||||
46.243.88.175 permit
|
46.243.88.175 permit
|
||||||
46.243.88.176 permit
|
46.243.88.176/30 permit
|
||||||
46.243.88.177 permit
|
46.243.95.96/27 permit
|
||||||
46.243.95.179 permit
|
46.243.95.179 permit
|
||||||
46.243.95.180 permit
|
46.243.95.180 permit
|
||||||
50.16.246.183 permit
|
50.16.246.183 permit
|
||||||
@@ -277,6 +266,8 @@
|
|||||||
50.56.130.221 permit
|
50.56.130.221 permit
|
||||||
50.56.130.222 permit
|
50.56.130.222 permit
|
||||||
50.112.246.219 permit
|
50.112.246.219 permit
|
||||||
|
50.152.146.25 permit
|
||||||
|
51.83.17.38 permit
|
||||||
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.6.74.205 permit
|
||||||
@@ -367,7 +358,9 @@
|
|||||||
64.127.115.252 permit
|
64.127.115.252 permit
|
||||||
64.132.88.0/23 permit
|
64.132.88.0/23 permit
|
||||||
64.132.92.0/24 permit
|
64.132.92.0/24 permit
|
||||||
|
64.181.136.241 permit
|
||||||
64.181.194.190 permit
|
64.181.194.190 permit
|
||||||
|
64.181.213.254 permit
|
||||||
64.207.219.7 permit
|
64.207.219.7 permit
|
||||||
64.207.219.8 permit
|
64.207.219.8 permit
|
||||||
64.207.219.9 permit
|
64.207.219.9 permit
|
||||||
@@ -453,7 +446,11 @@
|
|||||||
66.218.75.252/31 permit
|
66.218.75.252/31 permit
|
||||||
66.218.75.254 permit
|
66.218.75.254 permit
|
||||||
66.220.144.128/25 permit
|
66.220.144.128/25 permit
|
||||||
|
66.220.144.178 permit
|
||||||
|
66.220.144.179 permit
|
||||||
66.220.155.0/24 permit
|
66.220.155.0/24 permit
|
||||||
|
66.220.155.178 permit
|
||||||
|
66.220.155.179 permit
|
||||||
66.220.157.0/25 permit
|
66.220.157.0/25 permit
|
||||||
66.231.80.0/20 permit
|
66.231.80.0/20 permit
|
||||||
66.240.227.0/24 permit
|
66.240.227.0/24 permit
|
||||||
@@ -518,6 +515,8 @@
|
|||||||
69.162.98.0/24 permit
|
69.162.98.0/24 permit
|
||||||
69.169.224.0/20 permit
|
69.169.224.0/20 permit
|
||||||
69.171.232.0/24 permit
|
69.171.232.0/24 permit
|
||||||
|
69.171.232.180 permit
|
||||||
|
69.171.232.181 permit
|
||||||
69.171.244.0/23 permit
|
69.171.244.0/23 permit
|
||||||
70.42.149.35 permit
|
70.42.149.35 permit
|
||||||
72.3.185.0/24 permit
|
72.3.185.0/24 permit
|
||||||
@@ -639,6 +638,8 @@
|
|||||||
77.238.189.148/30 permit
|
77.238.189.148/30 permit
|
||||||
79.135.106.0/24 permit
|
79.135.106.0/24 permit
|
||||||
79.135.107.0/24 permit
|
79.135.107.0/24 permit
|
||||||
|
80.225.160.128/25 permit
|
||||||
|
81.88.38.0/23 permit
|
||||||
81.169.146.243 permit
|
81.169.146.243 permit
|
||||||
81.169.146.245 permit
|
81.169.146.245 permit
|
||||||
81.169.146.246 permit
|
81.169.146.246 permit
|
||||||
@@ -657,6 +658,9 @@
|
|||||||
82.165.159.45 permit
|
82.165.159.45 permit
|
||||||
82.165.159.130 permit
|
82.165.159.130 permit
|
||||||
82.165.159.131 permit
|
82.165.159.131 permit
|
||||||
|
84.8.68.0/25 permit
|
||||||
|
84.8.192.128/25 permit
|
||||||
|
84.8.224.128/25 permit
|
||||||
85.9.206.169 permit
|
85.9.206.169 permit
|
||||||
85.9.210.45 permit
|
85.9.210.45 permit
|
||||||
85.158.136.0/21 permit
|
85.158.136.0/21 permit
|
||||||
@@ -720,6 +724,7 @@
|
|||||||
96.43.148.64/28 permit
|
96.43.148.64/28 permit
|
||||||
96.43.148.64/31 permit
|
96.43.148.64/31 permit
|
||||||
96.43.151.64/28 permit
|
96.43.151.64/28 permit
|
||||||
|
98.77.0.0/16 permit
|
||||||
98.97.248.0/21 permit
|
98.97.248.0/21 permit
|
||||||
98.136.44.181 permit
|
98.136.44.181 permit
|
||||||
98.136.44.182/31 permit
|
98.136.44.182/31 permit
|
||||||
@@ -1215,8 +1220,6 @@
|
|||||||
104.43.243.237 permit
|
104.43.243.237 permit
|
||||||
104.44.112.128/25 permit
|
104.44.112.128/25 permit
|
||||||
104.47.0.0/17 permit
|
104.47.0.0/17 permit
|
||||||
104.130.96.0/28 permit
|
|
||||||
104.130.122.0/23 permit
|
|
||||||
106.10.144.64/27 permit
|
106.10.144.64/27 permit
|
||||||
106.10.144.100/31 permit
|
106.10.144.100/31 permit
|
||||||
106.10.144.103 permit
|
106.10.144.103 permit
|
||||||
@@ -1369,6 +1372,7 @@
|
|||||||
117.120.16.0/21 permit
|
117.120.16.0/21 permit
|
||||||
119.42.242.52/31 permit
|
119.42.242.52/31 permit
|
||||||
119.42.242.156 permit
|
119.42.242.156 permit
|
||||||
|
121.244.91.31 permit
|
||||||
121.244.91.48 permit
|
121.244.91.48 permit
|
||||||
121.244.91.52 permit
|
121.244.91.52 permit
|
||||||
122.15.156.182 permit
|
122.15.156.182 permit
|
||||||
@@ -1408,6 +1412,20 @@
|
|||||||
129.146.88.28 permit
|
129.146.88.28 permit
|
||||||
129.146.147.105 permit
|
129.146.147.105 permit
|
||||||
129.146.236.58 permit
|
129.146.236.58 permit
|
||||||
|
129.148.135.0/25 permit
|
||||||
|
129.148.148.0/25 permit
|
||||||
|
129.148.164.0/25 permit
|
||||||
|
129.148.180.0/25 permit
|
||||||
|
129.148.215.0/25 permit
|
||||||
|
129.149.6.0/25 permit
|
||||||
|
129.149.22.0/25 permit
|
||||||
|
129.149.38.0/25 permit
|
||||||
|
129.149.52.0/25 permit
|
||||||
|
129.149.68.0/25 permit
|
||||||
|
129.149.84.0/25 permit
|
||||||
|
129.149.100.0/25 permit
|
||||||
|
129.149.118.0/25 permit
|
||||||
|
129.149.126.0/25 permit
|
||||||
129.151.67.221 permit
|
129.151.67.221 permit
|
||||||
129.153.62.216 permit
|
129.153.62.216 permit
|
||||||
129.153.104.71 permit
|
129.153.104.71 permit
|
||||||
@@ -1420,18 +1438,21 @@
|
|||||||
129.159.22.159 permit
|
129.159.22.159 permit
|
||||||
129.159.87.137 permit
|
129.159.87.137 permit
|
||||||
129.213.195.191 permit
|
129.213.195.191 permit
|
||||||
|
130.35.116.0/25 permit
|
||||||
130.61.9.72 permit
|
130.61.9.72 permit
|
||||||
130.162.39.83 permit
|
130.162.39.83 permit
|
||||||
|
130.162.194.23 permit
|
||||||
130.248.172.0/24 permit
|
130.248.172.0/24 permit
|
||||||
130.248.173.0/24 permit
|
130.248.173.0/24 permit
|
||||||
|
131.186.12.0/25 permit
|
||||||
131.253.30.0/24 permit
|
131.253.30.0/24 permit
|
||||||
131.253.121.0/26 permit
|
131.253.121.0/26 permit
|
||||||
132.145.13.209 permit
|
132.145.13.209 permit
|
||||||
132.226.26.225 permit
|
132.226.26.225 permit
|
||||||
132.226.49.32 permit
|
132.226.49.32 permit
|
||||||
132.226.56.24 permit
|
132.226.56.24 permit
|
||||||
134.128.64.0/19 permit
|
134.98.248.128/25 permit
|
||||||
134.128.96.0/19 permit
|
134.128.64.0/18 permit
|
||||||
134.170.27.8 permit
|
134.170.27.8 permit
|
||||||
134.170.113.0/26 permit
|
134.170.113.0/26 permit
|
||||||
134.170.141.64/26 permit
|
134.170.141.64/26 permit
|
||||||
@@ -1460,6 +1481,10 @@
|
|||||||
136.147.182.0/24 permit
|
136.147.182.0/24 permit
|
||||||
136.147.224.0/20 permit
|
136.147.224.0/20 permit
|
||||||
136.179.50.206 permit
|
136.179.50.206 permit
|
||||||
|
136.248.224.128/25 permit
|
||||||
|
136.248.232.128/25 permit
|
||||||
|
138.1.108.0/25 permit
|
||||||
|
138.1.170.0/24 permit
|
||||||
139.60.152.0/22 permit
|
139.60.152.0/22 permit
|
||||||
139.138.35.44 permit
|
139.138.35.44 permit
|
||||||
139.138.46.121 permit
|
139.138.46.121 permit
|
||||||
@@ -1468,6 +1493,7 @@
|
|||||||
139.138.57.55 permit
|
139.138.57.55 permit
|
||||||
139.138.58.119 permit
|
139.138.58.119 permit
|
||||||
139.167.79.86 permit
|
139.167.79.86 permit
|
||||||
|
139.177.108.0/25 permit
|
||||||
139.180.17.0/24 permit
|
139.180.17.0/24 permit
|
||||||
140.238.148.191 permit
|
140.238.148.191 permit
|
||||||
141.148.55.217 permit
|
141.148.55.217 permit
|
||||||
@@ -1498,14 +1524,14 @@
|
|||||||
146.20.14.105 permit
|
146.20.14.105 permit
|
||||||
146.20.14.106 permit
|
146.20.14.106 permit
|
||||||
146.20.14.107 permit
|
146.20.14.107 permit
|
||||||
146.20.112.0/26 permit
|
|
||||||
146.20.113.0/24 permit
|
|
||||||
146.20.191.0/24 permit
|
|
||||||
146.20.215.0/24 permit
|
146.20.215.0/24 permit
|
||||||
146.20.215.182 permit
|
146.20.215.182 permit
|
||||||
146.88.28.0/24 permit
|
146.88.28.0/24 permit
|
||||||
146.148.116.76 permit
|
146.148.116.76 permit
|
||||||
147.154.32.0/25 permit
|
147.154.32.0/25 permit
|
||||||
|
147.154.63.0/24 permit
|
||||||
|
147.154.126.0/24 permit
|
||||||
|
147.154.191.0/24 permit
|
||||||
147.243.1.47 permit
|
147.243.1.47 permit
|
||||||
147.243.1.48 permit
|
147.243.1.48 permit
|
||||||
147.243.1.153 permit
|
147.243.1.153 permit
|
||||||
@@ -1513,16 +1539,23 @@
|
|||||||
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
|
||||||
150.136.21.199 permit
|
150.136.21.199 permit
|
||||||
|
150.171.109.178 permit
|
||||||
150.230.98.160 permit
|
150.230.98.160 permit
|
||||||
151.145.38.14 permit
|
151.145.38.14 permit
|
||||||
152.67.105.195 permit
|
152.67.105.195 permit
|
||||||
|
152.69.165.251 permit
|
||||||
152.69.200.236 permit
|
152.69.200.236 permit
|
||||||
152.70.155.126 permit
|
152.70.155.126 permit
|
||||||
|
155.248.135.128/25 permit
|
||||||
|
155.248.140.0/25 permit
|
||||||
|
155.248.148.0/25 permit
|
||||||
155.248.208.51 permit
|
155.248.208.51 permit
|
||||||
155.248.220.138 permit
|
155.248.220.138 permit
|
||||||
155.248.234.149 permit
|
155.248.234.149 permit
|
||||||
@@ -1531,10 +1564,16 @@
|
|||||||
157.58.30.128/25 permit
|
157.58.30.128/25 permit
|
||||||
157.58.196.96/29 permit
|
157.58.196.96/29 permit
|
||||||
157.58.249.3 permit
|
157.58.249.3 permit
|
||||||
|
157.137.32.128/25 permit
|
||||||
|
157.137.96.128/25 permit
|
||||||
157.151.208.65 permit
|
157.151.208.65 permit
|
||||||
157.255.1.64/29 permit
|
157.255.1.64/29 permit
|
||||||
158.101.211.207 permit
|
158.101.211.207 permit
|
||||||
158.247.16.0/20 permit
|
158.247.16.0/20 permit
|
||||||
|
158.247.100.0/25 permit
|
||||||
|
159.13.4.0/25 permit
|
||||||
|
159.13.33.181 permit
|
||||||
|
159.26.176.0/20 permit
|
||||||
159.92.154.0/24 permit
|
159.92.154.0/24 permit
|
||||||
159.92.155.0/24 permit
|
159.92.155.0/24 permit
|
||||||
159.92.157.0/24 permit
|
159.92.157.0/24 permit
|
||||||
@@ -1562,12 +1601,15 @@
|
|||||||
159.183.121.182 permit
|
159.183.121.182 permit
|
||||||
159.183.129.172 permit
|
159.183.129.172 permit
|
||||||
160.1.62.192 permit
|
160.1.62.192 permit
|
||||||
|
161.33.94.110 permit
|
||||||
|
161.33.228.244 permit
|
||||||
161.38.192.0/20 permit
|
161.38.192.0/20 permit
|
||||||
161.38.204.0/22 permit
|
161.38.204.0/22 permit
|
||||||
161.71.32.0/19 permit
|
161.71.32.0/19 permit
|
||||||
161.71.64.0/20 permit
|
161.71.64.0/20 permit
|
||||||
162.88.4.0/23 permit
|
162.88.4.0/23 permit
|
||||||
162.88.8.0/24 permit
|
162.88.8.0/24 permit
|
||||||
|
162.88.24.0/23 permit
|
||||||
162.88.24.0/24 permit
|
162.88.24.0/24 permit
|
||||||
162.88.25.0/24 permit
|
162.88.25.0/24 permit
|
||||||
162.88.36.0/24 permit
|
162.88.36.0/24 permit
|
||||||
@@ -1585,11 +1627,13 @@
|
|||||||
164.152.23.32 permit
|
164.152.23.32 permit
|
||||||
164.152.25.241 permit
|
164.152.25.241 permit
|
||||||
164.177.132.168/30 permit
|
164.177.132.168/30 permit
|
||||||
|
165.1.100.0/25 permit
|
||||||
165.173.128.0/24 permit
|
165.173.128.0/24 permit
|
||||||
165.173.180.1 permit
|
165.173.180.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
|
||||||
166.78.68.0/22 permit
|
165.173.189.205 permit
|
||||||
166.78.68.221 permit
|
166.78.68.221 permit
|
||||||
166.78.69.169 permit
|
166.78.69.169 permit
|
||||||
166.78.69.170 permit
|
166.78.69.170 permit
|
||||||
@@ -1607,9 +1651,12 @@
|
|||||||
167.89.75.126 permit
|
167.89.75.126 permit
|
||||||
167.89.75.136 permit
|
167.89.75.136 permit
|
||||||
167.89.75.164 permit
|
167.89.75.164 permit
|
||||||
167.89.101.2 permit
|
|
||||||
167.89.101.192/28 permit
|
|
||||||
167.220.67.232/29 permit
|
167.220.67.232/29 permit
|
||||||
|
168.107.248.128/25 permit
|
||||||
|
168.110.160.128/25 permit
|
||||||
|
168.110.248.128/25 permit
|
||||||
|
168.129.184.128/25 permit
|
||||||
|
168.129.248.128/25 permit
|
||||||
168.138.5.36 permit
|
168.138.5.36 permit
|
||||||
168.138.73.51 permit
|
168.138.73.51 permit
|
||||||
168.138.77.31 permit
|
168.138.77.31 permit
|
||||||
@@ -1626,6 +1673,7 @@
|
|||||||
169.148.144.0/25 permit
|
169.148.144.0/25 permit
|
||||||
169.148.144.10 permit
|
169.148.144.10 permit
|
||||||
169.148.146.0/23 permit
|
169.148.146.0/23 permit
|
||||||
|
169.148.174.10 permit
|
||||||
169.148.175.3 permit
|
169.148.175.3 permit
|
||||||
169.148.179.3 permit
|
169.148.179.3 permit
|
||||||
169.148.188.0/24 permit
|
169.148.188.0/24 permit
|
||||||
@@ -1644,6 +1692,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
|
||||||
@@ -1658,6 +1707,8 @@
|
|||||||
175.41.215.51 permit
|
175.41.215.51 permit
|
||||||
176.32.105.0/24 permit
|
176.32.105.0/24 permit
|
||||||
176.32.127.0/24 permit
|
176.32.127.0/24 permit
|
||||||
|
176.94.34.32/27 permit
|
||||||
|
176.94.34.64/27 permit
|
||||||
178.236.10.128/26 permit
|
178.236.10.128/26 permit
|
||||||
182.50.76.0/22 permit
|
182.50.76.0/22 permit
|
||||||
182.50.78.64/28 permit
|
182.50.78.64/28 permit
|
||||||
@@ -1739,26 +1790,36 @@
|
|||||||
188.125.85.234/31 permit
|
188.125.85.234/31 permit
|
||||||
188.125.85.236/31 permit
|
188.125.85.236/31 permit
|
||||||
188.125.85.238 permit
|
188.125.85.238 permit
|
||||||
188.165.51.139 permit
|
|
||||||
188.172.128.0/20 permit
|
188.172.128.0/20 permit
|
||||||
192.0.64.0/18 permit
|
192.0.64.0/18 permit
|
||||||
|
192.9.166.184 permit
|
||||||
192.18.139.154 permit
|
192.18.139.154 permit
|
||||||
192.18.145.36 permit
|
192.18.145.36 permit
|
||||||
192.18.152.58 permit
|
192.18.152.58 permit
|
||||||
|
192.22.32.128/25 permit
|
||||||
|
192.22.96.128/25 permit
|
||||||
|
192.22.160.128/25 permit
|
||||||
|
192.22.224.128/25 permit
|
||||||
192.28.128.0/18 permit
|
192.28.128.0/18 permit
|
||||||
|
192.29.24.0/25 permit
|
||||||
|
192.29.44.0/25 permit
|
||||||
|
192.29.72.0/25 permit
|
||||||
|
192.29.88.0/25 permit
|
||||||
192.29.103.128/25 permit
|
192.29.103.128/25 permit
|
||||||
|
192.29.134.0/25 permit
|
||||||
|
192.29.151.128/25 permit
|
||||||
|
192.29.172.0/25 permit
|
||||||
|
192.29.178.0/25 permit
|
||||||
|
192.29.200.0/25 permit
|
||||||
|
192.29.216.0/25 permit
|
||||||
|
192.29.232.0/25 permit
|
||||||
|
192.29.248.0/25 permit
|
||||||
192.30.252.0/22 permit
|
192.30.252.0/22 permit
|
||||||
192.161.144.0/20 permit
|
192.161.144.0/20 permit
|
||||||
192.162.87.0/24 permit
|
192.162.87.0/24 permit
|
||||||
192.237.158.0/23 permit
|
|
||||||
192.237.159.42 permit
|
192.237.159.42 permit
|
||||||
192.237.159.43 permit
|
192.237.159.43 permit
|
||||||
192.254.112.0/20 permit
|
192.254.112.0/20 permit
|
||||||
192.254.112.60 permit
|
|
||||||
192.254.112.98/31 permit
|
|
||||||
192.254.113.10 permit
|
|
||||||
192.254.113.101 permit
|
|
||||||
192.254.114.176 permit
|
|
||||||
193.109.254.0/23 permit
|
193.109.254.0/23 permit
|
||||||
193.122.128.100 permit
|
193.122.128.100 permit
|
||||||
193.123.56.63 permit
|
193.123.56.63 permit
|
||||||
@@ -1798,7 +1859,6 @@
|
|||||||
198.21.0.0/21 permit
|
198.21.0.0/21 permit
|
||||||
198.37.144.0/20 permit
|
198.37.144.0/20 permit
|
||||||
198.37.152.186 permit
|
198.37.152.186 permit
|
||||||
198.61.254.0/23 permit
|
|
||||||
198.61.254.21 permit
|
198.61.254.21 permit
|
||||||
198.61.254.231 permit
|
198.61.254.231 permit
|
||||||
198.178.234.57 permit
|
198.178.234.57 permit
|
||||||
@@ -1885,6 +1945,7 @@
|
|||||||
204.14.232.0/21 permit
|
204.14.232.0/21 permit
|
||||||
204.14.232.64/28 permit
|
204.14.232.64/28 permit
|
||||||
204.14.234.64/28 permit
|
204.14.234.64/28 permit
|
||||||
|
204.75.18.128/27 permit
|
||||||
204.75.142.0/24 permit
|
204.75.142.0/24 permit
|
||||||
204.92.114.187 permit
|
204.92.114.187 permit
|
||||||
204.92.114.203 permit
|
204.92.114.203 permit
|
||||||
@@ -1892,7 +1953,9 @@
|
|||||||
204.141.32.0/23 permit
|
204.141.32.0/23 permit
|
||||||
204.141.42.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.90.0/23 permit
|
||||||
|
204.220.92.0/22 permit
|
||||||
|
204.220.160.0/20 permit
|
||||||
204.220.168.0/21 permit
|
204.220.168.0/21 permit
|
||||||
204.220.176.0/20 permit
|
204.220.176.0/20 permit
|
||||||
204.220.181.105 permit
|
204.220.181.105 permit
|
||||||
@@ -1927,6 +1990,9 @@
|
|||||||
207.211.30.128/25 permit
|
207.211.30.128/25 permit
|
||||||
207.211.31.0/25 permit
|
207.211.31.0/25 permit
|
||||||
207.211.41.113 permit
|
207.211.41.113 permit
|
||||||
|
207.211.132.0/25 permit
|
||||||
|
207.211.147.87 permit
|
||||||
|
207.211.159.110 permit
|
||||||
207.218.90.0/24 permit
|
207.218.90.0/24 permit
|
||||||
207.218.90.122 permit
|
207.218.90.122 permit
|
||||||
207.250.68.0/24 permit
|
207.250.68.0/24 permit
|
||||||
@@ -1934,6 +2000,8 @@
|
|||||||
208.43.21.28/30 permit
|
208.43.21.28/30 permit
|
||||||
208.43.21.64/29 permit
|
208.43.21.64/29 permit
|
||||||
208.43.21.72/30 permit
|
208.43.21.72/30 permit
|
||||||
|
208.56.9.224 permit
|
||||||
|
208.56.13.196 permit
|
||||||
208.64.132.0/22 permit
|
208.64.132.0/22 permit
|
||||||
208.71.40.63 permit
|
208.71.40.63 permit
|
||||||
208.71.40.64/31 permit
|
208.71.40.64/31 permit
|
||||||
@@ -1960,6 +2028,7 @@
|
|||||||
208.71.42.214 permit
|
208.71.42.214 permit
|
||||||
208.72.249.240/29 permit
|
208.72.249.240/29 permit
|
||||||
208.75.120.0/22 permit
|
208.75.120.0/22 permit
|
||||||
|
208.76.62.0/23 permit
|
||||||
208.76.62.0/24 permit
|
208.76.62.0/24 permit
|
||||||
208.76.63.0/24 permit
|
208.76.63.0/24 permit
|
||||||
208.82.237.96/29 permit
|
208.82.237.96/29 permit
|
||||||
@@ -1973,7 +2042,6 @@
|
|||||||
209.43.22.0/28 permit
|
209.43.22.0/28 permit
|
||||||
209.46.117.168 permit
|
209.46.117.168 permit
|
||||||
209.46.117.179 permit
|
209.46.117.179 permit
|
||||||
209.61.151.0/24 permit
|
|
||||||
209.61.151.236 permit
|
209.61.151.236 permit
|
||||||
209.61.151.249 permit
|
209.61.151.249 permit
|
||||||
209.61.151.251 permit
|
209.61.151.251 permit
|
||||||
@@ -2120,6 +2188,7 @@
|
|||||||
216.136.168.80/28 permit
|
216.136.168.80/28 permit
|
||||||
216.139.64.0/19 permit
|
216.139.64.0/19 permit
|
||||||
216.145.221.0/24 permit
|
216.145.221.0/24 permit
|
||||||
|
216.146.32.0/23 permit
|
||||||
216.146.32.0/24 permit
|
216.146.32.0/24 permit
|
||||||
216.146.33.0/24 permit
|
216.146.33.0/24 permit
|
||||||
216.198.0.0/18 permit
|
216.198.0.0/18 permit
|
||||||
@@ -2140,6 +2209,7 @@
|
|||||||
223.165.120.0/23 permit
|
223.165.120.0/23 permit
|
||||||
2001:0868:0100:0600::/64 permit
|
2001:0868:0100:0600::/64 permit
|
||||||
2001:4860:4000::/36 permit
|
2001:4860:4000::/36 permit
|
||||||
|
2001:4860:4864::/56 permit
|
||||||
2001:748:100:40::2:0/112 permit
|
2001:748:100:40::2:0/112 permit
|
||||||
2001:748:400:1300::3 permit
|
2001:748:400:1300::3 permit
|
||||||
2001:748:400:1300::4 permit
|
2001:748:400:1300::4 permit
|
||||||
@@ -2157,10 +2227,13 @@
|
|||||||
2001:748:400:3301::3 permit
|
2001:748:400:3301::3 permit
|
||||||
2001:748:400:3301::4 permit
|
2001:748:400:3301::4 permit
|
||||||
2404:6800:4000::/36 permit
|
2404:6800:4000::/36 permit
|
||||||
|
2404:6800:4864::/56 permit
|
||||||
|
2603:1061:14:1d0::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
|
||||||
2607:f8b0:4000::/36 permit
|
2607:f8b0:4000::/36 permit
|
||||||
|
2607:f8b0:4864::/56 permit
|
||||||
2620:109:c003:104::/64 permit
|
2620:109:c003:104::/64 permit
|
||||||
2620:109:c003:104::215 permit
|
2620:109:c003:104::215 permit
|
||||||
2620:109:c006:104::/64 permit
|
2620:109:c006:104::/64 permit
|
||||||
@@ -2172,8 +2245,7 @@
|
|||||||
2620:10d:c09c:400::8:1 permit
|
2620:10d:c09c:400::8:1 permit
|
||||||
2620:119:50c0:207::/64 permit
|
2620:119:50c0:207::/64 permit
|
||||||
2620:119:50c0:207::215 permit
|
2620:119:50c0:207::215 permit
|
||||||
2620:1ec:46::51 permit
|
|
||||||
2620:1ec:bdf::51 permit
|
|
||||||
2800:3f0:4000::/36 permit
|
2800:3f0:4000::/36 permit
|
||||||
|
2800:3f0:4864::/56 permit
|
||||||
49.12.4.251 permit # checks.mailcow.email
|
49.12.4.251 permit # checks.mailcow.email
|
||||||
2a01:4f8:c17:7906::10 permit # checks.mailcow.email
|
2a01:4f8:c17:7906::10 permit # checks.mailcow.email
|
||||||
@@ -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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -856,6 +856,8 @@ rspamd_config:register_symbol({
|
|||||||
|
|
||||||
local seen_cte
|
local seen_cte
|
||||||
local newline_s = newline(task)
|
local newline_s = newline(task)
|
||||||
|
-- use the CTE add_text_footer actually applied
|
||||||
|
local new_cte = rewrite.new_cte or 'quoted-printable'
|
||||||
|
|
||||||
local function rewrite_ct_cb(name, hdr)
|
local function rewrite_ct_cb(name, hdr)
|
||||||
if rewrite.need_rewrite_ct then
|
if rewrite.need_rewrite_ct then
|
||||||
@@ -877,13 +879,13 @@ rspamd_config:register_symbol({
|
|||||||
return
|
return
|
||||||
elseif name:lower() == 'content-transfer-encoding' then
|
elseif name:lower() == 'content-transfer-encoding' then
|
||||||
out[#out + 1] = string.format('%s: %s',
|
out[#out + 1] = string.format('%s: %s',
|
||||||
'Content-Transfer-Encoding', 'quoted-printable')
|
'Content-Transfer-Encoding', new_cte)
|
||||||
-- update Content-Transfer-Encoding header
|
-- update Content-Transfer-Encoding header
|
||||||
task:set_milter_reply({
|
task:set_milter_reply({
|
||||||
remove_headers = {['Content-Transfer-Encoding'] = 0},
|
remove_headers = {['Content-Transfer-Encoding'] = 0},
|
||||||
})
|
})
|
||||||
task:set_milter_reply({
|
task:set_milter_reply({
|
||||||
add_headers = {['Content-Transfer-Encoding'] = 'quoted-printable'}
|
add_headers = {['Content-Transfer-Encoding'] = new_cte}
|
||||||
})
|
})
|
||||||
seen_cte = true
|
seen_cte = true
|
||||||
return
|
return
|
||||||
@@ -895,7 +897,7 @@ rspamd_config:register_symbol({
|
|||||||
task:headers_foreach(rewrite_ct_cb, {full = true})
|
task:headers_foreach(rewrite_ct_cb, {full = true})
|
||||||
|
|
||||||
if not seen_cte and rewrite.need_rewrite_ct then
|
if not seen_cte and rewrite.need_rewrite_ct then
|
||||||
out[#out + 1] = string.format('%s: %s', 'Content-Transfer-Encoding', 'quoted-printable')
|
out[#out + 1] = string.format('%s: %s', 'Content-Transfer-Encoding', new_cte)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- End of headers
|
-- End of headers
|
||||||
@@ -914,7 +916,7 @@ rspamd_config:register_symbol({
|
|||||||
out_parts[#out_parts + 1] = o
|
out_parts[#out_parts + 1] = o
|
||||||
out_parts[#out_parts + 1] = newline_s
|
out_parts[#out_parts + 1] = newline_s
|
||||||
else
|
else
|
||||||
local removePrefix = "--\x0D\x0AContent-Type"
|
local removePrefix = "--\x0D\x0AContent-"
|
||||||
if string.lower(string.sub(tostring(o[1]), 1, string.len(removePrefix))) == string.lower(removePrefix) then
|
if string.lower(string.sub(tostring(o[1]), 1, string.len(removePrefix))) == string.lower(removePrefix) then
|
||||||
o[1] = string.sub(tostring(o[1]), string.len("--\x0D\x0A") + 1)
|
o[1] = string.sub(tostring(o[1]), string.len("--\x0D\x0A") + 1)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
@@ -158,12 +153,12 @@ foreach (json_decode($rcpts, true) as $rcpt) {
|
|||||||
|
|
||||||
// Loop through all found gotos
|
// Loop through all found gotos
|
||||||
foreach ($gotos_array as $index => &$goto) {
|
foreach ($gotos_array as $index => &$goto) {
|
||||||
error_log("RCPT RESOVLER: http pipe: query " . $goto . " as username from mailbox" . PHP_EOL);
|
error_log("RCPT RESOLVER: http pipe: query " . $goto . " as username from mailbox" . PHP_EOL);
|
||||||
$stmt = $pdo->prepare("SELECT `username` FROM `mailbox` WHERE `username` = :goto AND (`active`= '1' OR `active`= '2');");
|
$stmt = $pdo->prepare("SELECT `username` FROM `mailbox` WHERE `username` = :goto AND (`active`= '1' OR `active`= '2');");
|
||||||
$stmt->execute(array(':goto' => $goto));
|
$stmt->execute(array(':goto' => $goto));
|
||||||
$username = $stmt->fetch(PDO::FETCH_ASSOC)['username'];
|
$username = $stmt->fetch(PDO::FETCH_ASSOC)['username'];
|
||||||
if (!empty($username)) {
|
if (!empty($username)) {
|
||||||
error_log("RCPT RESOVLER: http pipe: mailbox found: " . $username . PHP_EOL);
|
error_log("RCPT RESOLVER: http pipe: mailbox found: " . $username . PHP_EOL);
|
||||||
// Current goto is a mailbox, save to rcpt_final_mailboxes if not a duplicate
|
// Current goto is a mailbox, save to rcpt_final_mailboxes if not a duplicate
|
||||||
if (!in_array($username, $rcpt_final_mailboxes)) {
|
if (!in_array($username, $rcpt_final_mailboxes)) {
|
||||||
$rcpt_final_mailboxes[] = $username;
|
$rcpt_final_mailboxes[] = $username;
|
||||||
@@ -172,21 +167,21 @@ foreach (json_decode($rcpts, true) as $rcpt) {
|
|||||||
else {
|
else {
|
||||||
$parsed_goto = parse_email($goto);
|
$parsed_goto = parse_email($goto);
|
||||||
if (!$redis->hGet('DOMAIN_MAP', $parsed_goto['domain'])) {
|
if (!$redis->hGet('DOMAIN_MAP', $parsed_goto['domain'])) {
|
||||||
error_log("RCPT RESOVLER:" . $goto . " is not a mailcow handled mailbox or alias address" . PHP_EOL);
|
error_log("RCPT RESOLVER:" . $goto . " is not a mailcow handled mailbox or alias address" . PHP_EOL);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$stmt = $pdo->prepare("SELECT `goto` FROM `alias` WHERE `address` = :goto AND `active` = '1'");
|
$stmt = $pdo->prepare("SELECT `goto` FROM `alias` WHERE `address` = :goto AND `active` = '1'");
|
||||||
$stmt->execute(array(':goto' => $goto));
|
$stmt->execute(array(':goto' => $goto));
|
||||||
$goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['goto'];
|
$goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['goto'];
|
||||||
if ($goto_branch) {
|
if ($goto_branch) {
|
||||||
error_log("RCPT RESOVLER: http pipe: goto address " . $goto . " is an alias branch for " . $goto_branch . PHP_EOL);
|
error_log("RCPT RESOLVER: http pipe: goto address " . $goto . " is an alias branch for " . $goto_branch . PHP_EOL);
|
||||||
$goto_branch_array = explode(',', $goto_branch);
|
$goto_branch_array = explode(',', $goto_branch);
|
||||||
} else {
|
} else {
|
||||||
$stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain AND `active` = '1'");
|
$stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain AND `active` = '1'");
|
||||||
$stmt->execute(array(':domain' => $parsed_goto['domain']));
|
$stmt->execute(array(':domain' => $parsed_goto['domain']));
|
||||||
$goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['target_domain'];
|
$goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['target_domain'];
|
||||||
if ($goto_branch) {
|
if ($goto_branch) {
|
||||||
error_log("RCPT RESOVLER: http pipe: goto domain " . $parsed_goto['domain'] . " is a domain alias branch for " . $goto_branch . PHP_EOL);
|
error_log("RCPT RESOLVER: http pipe: goto domain " . $parsed_goto['domain'] . " is a domain alias branch for " . $goto_branch . PHP_EOL);
|
||||||
$goto_branch_array = array($parsed_goto['local'] . '@' . $goto_branch);
|
$goto_branch_array = array($parsed_goto['local'] . '@' . $goto_branch);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -208,11 +203,11 @@ foreach (json_decode($rcpts, true) as $rcpt) {
|
|||||||
// Force exit if loop cannot be solved
|
// Force exit if loop cannot be solved
|
||||||
// Postfix does not allow for alias loops, so this should never happen.
|
// Postfix does not allow for alias loops, so this should never happen.
|
||||||
$loop_c++;
|
$loop_c++;
|
||||||
error_log("RCPT RESOVLER: http pipe: goto array count on loop #". $loop_c . " is " . count($gotos_array) . PHP_EOL);
|
error_log("RCPT RESOLVER: http pipe: goto array count on loop #". $loop_c . " is " . count($gotos_array) . PHP_EOL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (PDOException $e) {
|
catch (PDOException $e) {
|
||||||
error_log("RCPT RESOVLER: " . $e->getMessage() . PHP_EOL);
|
error_log("RCPT RESOLVER: " . $e->getMessage() . PHP_EOL);
|
||||||
http_response_code(502);
|
http_response_code(502);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
@@ -143,12 +139,12 @@ foreach (json_decode($rcpts, true) as $rcpt) {
|
|||||||
|
|
||||||
// Loop through all found gotos
|
// Loop through all found gotos
|
||||||
foreach ($gotos_array as $index => &$goto) {
|
foreach ($gotos_array as $index => &$goto) {
|
||||||
error_log("RCPT RESOVLER: http pipe: query " . $goto . " as username from mailbox" . PHP_EOL);
|
error_log("RCPT RESOLVER: http pipe: query " . $goto . " as username from mailbox" . PHP_EOL);
|
||||||
$stmt = $pdo->prepare("SELECT `username` FROM `mailbox` WHERE `username` = :goto AND (`active`= '1' OR `active`= '2');");
|
$stmt = $pdo->prepare("SELECT `username` FROM `mailbox` WHERE `username` = :goto AND (`active`= '1' OR `active`= '2');");
|
||||||
$stmt->execute(array(':goto' => $goto));
|
$stmt->execute(array(':goto' => $goto));
|
||||||
$username = $stmt->fetch(PDO::FETCH_ASSOC)['username'];
|
$username = $stmt->fetch(PDO::FETCH_ASSOC)['username'];
|
||||||
if (!empty($username)) {
|
if (!empty($username)) {
|
||||||
error_log("RCPT RESOVLER: http pipe: mailbox found: " . $username . PHP_EOL);
|
error_log("RCPT RESOLVER: http pipe: mailbox found: " . $username . PHP_EOL);
|
||||||
// Current goto is a mailbox, save to rcpt_final_mailboxes if not a duplicate
|
// Current goto is a mailbox, save to rcpt_final_mailboxes if not a duplicate
|
||||||
if (!in_array($username, $rcpt_final_mailboxes)) {
|
if (!in_array($username, $rcpt_final_mailboxes)) {
|
||||||
$rcpt_final_mailboxes[] = $username;
|
$rcpt_final_mailboxes[] = $username;
|
||||||
@@ -157,21 +153,21 @@ foreach (json_decode($rcpts, true) as $rcpt) {
|
|||||||
else {
|
else {
|
||||||
$parsed_goto = parse_email($goto);
|
$parsed_goto = parse_email($goto);
|
||||||
if (!$redis->hGet('DOMAIN_MAP', $parsed_goto['domain'])) {
|
if (!$redis->hGet('DOMAIN_MAP', $parsed_goto['domain'])) {
|
||||||
error_log("RCPT RESOVLER:" . $goto . " is not a mailcow handled mailbox or alias address" . PHP_EOL);
|
error_log("RCPT RESOLVER:" . $goto . " is not a mailcow handled mailbox or alias address" . PHP_EOL);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$stmt = $pdo->prepare("SELECT `goto` FROM `alias` WHERE `address` = :goto AND `active` = '1'");
|
$stmt = $pdo->prepare("SELECT `goto` FROM `alias` WHERE `address` = :goto AND `active` = '1'");
|
||||||
$stmt->execute(array(':goto' => $goto));
|
$stmt->execute(array(':goto' => $goto));
|
||||||
$goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['goto'];
|
$goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['goto'];
|
||||||
if ($goto_branch) {
|
if ($goto_branch) {
|
||||||
error_log("RCPT RESOVLER: http pipe: goto address " . $goto . " is an alias branch for " . $goto_branch . PHP_EOL);
|
error_log("RCPT RESOLVER: http pipe: goto address " . $goto . " is an alias branch for " . $goto_branch . PHP_EOL);
|
||||||
$goto_branch_array = explode(',', $goto_branch);
|
$goto_branch_array = explode(',', $goto_branch);
|
||||||
} else {
|
} else {
|
||||||
$stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain AND `active` = '1'");
|
$stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain AND `active` = '1'");
|
||||||
$stmt->execute(array(':domain' => $parsed_goto['domain']));
|
$stmt->execute(array(':domain' => $parsed_goto['domain']));
|
||||||
$goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['target_domain'];
|
$goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['target_domain'];
|
||||||
if ($goto_branch) {
|
if ($goto_branch) {
|
||||||
error_log("RCPT RESOVLER: http pipe: goto domain " . $parsed_goto['domain'] . " is a domain alias branch for " . $goto_branch . PHP_EOL);
|
error_log("RCPT RESOLVER: http pipe: goto domain " . $parsed_goto['domain'] . " is a domain alias branch for " . $goto_branch . PHP_EOL);
|
||||||
$goto_branch_array = array($parsed_goto['local'] . '@' . $goto_branch);
|
$goto_branch_array = array($parsed_goto['local'] . '@' . $goto_branch);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -193,11 +189,11 @@ foreach (json_decode($rcpts, true) as $rcpt) {
|
|||||||
// Force exit if loop cannot be solved
|
// Force exit if loop cannot be solved
|
||||||
// Postfix does not allow for alias loops, so this should never happen.
|
// Postfix does not allow for alias loops, so this should never happen.
|
||||||
$loop_c++;
|
$loop_c++;
|
||||||
error_log("RCPT RESOVLER: http pipe: goto array count on loop #". $loop_c . " is " . count($gotos_array) . PHP_EOL);
|
error_log("RCPT RESOLVER: http pipe: goto array count on loop #". $loop_c . " is " . count($gotos_array) . PHP_EOL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (PDOException $e) {
|
catch (PDOException $e) {
|
||||||
error_log("RCPT RESOVLER: " . $e->getMessage() . PHP_EOL);
|
error_log("RCPT RESOLVER: " . $e->getMessage() . PHP_EOL);
|
||||||
http_response_code(502);
|
http_response_code(502);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -115,6 +115,8 @@ $template_data = [
|
|||||||
'logo_dark_specs' => customize('get', 'main_logo_dark_specs'),
|
'logo_dark_specs' => customize('get', 'main_logo_dark_specs'),
|
||||||
'ip_check' => customize('get', 'ip_check'),
|
'ip_check' => customize('get', 'ip_check'),
|
||||||
'custom_login' => customize('get', 'custom_login'),
|
'custom_login' => customize('get', 'custom_login'),
|
||||||
|
'ui_themes' => customize('get', 'ui_themes'),
|
||||||
|
'ui_theme' => customize('get', 'ui_theme'),
|
||||||
'password_complexity' => password_complexity('get'),
|
'password_complexity' => password_complexity('get'),
|
||||||
'show_rspamd_global_filters' => @$_SESSION['show_rspamd_global_filters'],
|
'show_rspamd_global_filters' => @$_SESSION['show_rspamd_global_filters'],
|
||||||
'cors_settings' => $cors_settings,
|
'cors_settings' => $cors_settings,
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -2510,7 +2527,7 @@ paths:
|
|||||||
description: >-
|
description: >-
|
||||||
Using this endpoint you can perform actions on quarantine items. It is possible to release
|
Using this endpoint you can perform actions on quarantine items. It is possible to release
|
||||||
emails from quarantine into to the inbox, or learn them as ham to improve Rspamd filtering.
|
emails from quarantine into to the inbox, or learn them as ham to improve Rspamd filtering.
|
||||||
You must provide the quarantine item IDs. You can get the IDs using the GET method.
|
You must provide the quarantine item IDs. You can get the IDs using the GET method.
|
||||||
operationId: Edit mails in Quarantine
|
operationId: Edit mails in Quarantine
|
||||||
requestBody:
|
requestBody:
|
||||||
content:
|
content:
|
||||||
@@ -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:
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
/*!
|
/*!
|
||||||
* Bootstrap-select v1.14.0-beta2 (https://developer.snapappointments.com/bootstrap-select)
|
* Bootstrap-select v1.14.0-beta3 (https://developer.snapappointments.com/bootstrap-select)
|
||||||
*
|
*
|
||||||
* Copyright 2012-2021 SnapAppointments, LLC
|
* Copyright 2012-2022 SnapAppointments, LLC
|
||||||
* Licensed under MIT (https://github.com/snapappointments/bootstrap-select/blob/master/LICENSE)
|
* Licensed under MIT (https://github.com/snapappointments/bootstrap-select/blob/master/LICENSE)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@-webkit-keyframes bs-notify-fadeOut {
|
@-webkit-keyframes bs-notify-fadeOut {
|
||||||
0% {
|
0% {
|
||||||
opacity: 0.9;
|
opacity: 0.9;
|
||||||
@@ -35,8 +35,7 @@ select.selectpicker {
|
|||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
.bootstrap-select {
|
.bootstrap-select {
|
||||||
width: 220px \0;
|
width: 220px;
|
||||||
/*IE9 and below*/
|
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
.bootstrap-select > .dropdown-toggle {
|
.bootstrap-select > .dropdown-toggle {
|
||||||
|
|||||||
@@ -17,15 +17,15 @@ table.dataTable>tbody>tr.child ul.dtr-details>li {
|
|||||||
}
|
}
|
||||||
table.dataTable.dtr-inline.collapsed>tbody>tr>td.dtr-control:before:hover,
|
table.dataTable.dtr-inline.collapsed>tbody>tr>td.dtr-control:before:hover,
|
||||||
table.dataTable.dtr-inline.collapsed>tbody>tr>th.dtr-control:before:hover {
|
table.dataTable.dtr-inline.collapsed>tbody>tr>th.dtr-control:before:hover {
|
||||||
background-color: #5e5e5e;
|
background-color: var(--mc-dtr-hover-bg);
|
||||||
}
|
}
|
||||||
table.dataTable.dtr-inline.collapsed>tbody>tr>td.dtr-control:before,
|
table.dataTable.dtr-inline.collapsed>tbody>tr>td.dtr-control:before,
|
||||||
table.dataTable.dtr-inline.collapsed>tbody>tr>th.dtr-control:before,
|
table.dataTable.dtr-inline.collapsed>tbody>tr>th.dtr-control:before,
|
||||||
table.dataTable td.dt-control:before {
|
table.dataTable td.dt-control:before {
|
||||||
background-color: #979797 !important;
|
background-color: var(--mc-dtr-bg) !important;
|
||||||
border: 1.5px solid #616161 !important;
|
border: 1.5px solid var(--mc-dtr-border) !important;
|
||||||
border-radius: 2px !important;
|
border-radius: 2px !important;
|
||||||
color: #fff;
|
color: var(--mc-dtr-color);
|
||||||
height: 1em;
|
height: 1em;
|
||||||
width: 1em;
|
width: 1em;
|
||||||
line-height: 1.25em;
|
line-height: 1.25em;
|
||||||
@@ -37,7 +37,7 @@ table.dataTable td.dt-control:before {
|
|||||||
table.dataTable.dtr-inline.collapsed>tbody>tr.parent>td.dtr-control:before,
|
table.dataTable.dtr-inline.collapsed>tbody>tr.parent>td.dtr-control:before,
|
||||||
table.dataTable.dtr-inline.collapsed>tbody>tr.parent>th.dtr-control:before,
|
table.dataTable.dtr-inline.collapsed>tbody>tr.parent>th.dtr-control:before,
|
||||||
table.dataTable td.dt-control:before {
|
table.dataTable td.dt-control:before {
|
||||||
background-color: #979797 !important;
|
background-color: var(--mc-dtr-active-bg) !important;
|
||||||
}
|
}
|
||||||
table.dataTable.table-striped>tbody>tr>td {
|
table.dataTable.table-striped>tbody>tr>td {
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
|
|||||||
@@ -34,11 +34,44 @@
|
|||||||
url('/fonts/noto-sans-v12-latin_greek_cyrillic-700italic.woff') format('woff');
|
url('/fonts/noto-sans-v12-latin_greek_cyrillic-700italic.woff') format('woff');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* theme tokens, override in css/themes/<name>/theme.css */
|
||||||
|
:root {
|
||||||
|
--mc-font-family: "Noto Sans","Helvetica Neue",Helvetica,Arial,sans-serif;
|
||||||
|
--mc-font-size: 10.5pt;
|
||||||
|
--mc-body-bg: #fbfbfb;
|
||||||
|
--mc-primary: #0F688D;
|
||||||
|
--mc-primary-border: #0d526d;
|
||||||
|
--mc-info: #148DBC;
|
||||||
|
--mc-info-border: #127ea8;
|
||||||
|
--mc-border: #dfdfdf;
|
||||||
|
--mc-surface: #fff;
|
||||||
|
--mc-surface-alt: #f7f7f7;
|
||||||
|
--mc-muted: #959595;
|
||||||
|
--mc-hr: #ccc;
|
||||||
|
--mc-progress-bg: #d5d5d5;
|
||||||
|
--mc-placeholder: rgb(137 137 137);
|
||||||
|
--mc-btn-outline-color: #000000;
|
||||||
|
--mc-btn-outline-border: #cfcfcf;
|
||||||
|
--mc-btn-outline-hover-bg: #f0f0f0;
|
||||||
|
--mc-btn-outline-active-bg: #f0f0f0;
|
||||||
|
--mc-btn-light-active-color: #fff;
|
||||||
|
--mc-btn-light-active-bg: #555;
|
||||||
|
--mc-btn-light-active-border: #4d4d4d;
|
||||||
|
--mc-dtr-bg: #979797;
|
||||||
|
--mc-dtr-border: #616161;
|
||||||
|
--mc-dtr-color: #fff;
|
||||||
|
--mc-dtr-hover-bg: #5e5e5e;
|
||||||
|
--mc-dtr-active-bg: #979797;
|
||||||
|
--mc-login-user: #28b62c;
|
||||||
|
--mc-login-domainadmin: #ffc107;
|
||||||
|
--mc-login-admin: #ff4136;
|
||||||
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
background-color: #fbfbfb;
|
background-color: var(--mc-body-bg);
|
||||||
}
|
}
|
||||||
#maxmsgsize { min-width: 80px; }
|
#maxmsgsize { min-width: 80px; }
|
||||||
#slider1 .slider-selection {
|
#slider1 .slider-selection {
|
||||||
@@ -86,17 +119,17 @@ body {
|
|||||||
max-height: 1000px
|
max-height: 1000px
|
||||||
}
|
}
|
||||||
.nav-tabs .nav-link, .nav-tabs .nav-link.disabled, .nav-tabs .nav-link.disabled:hover, .nav-tabs .nav-link.disabled:focus {
|
.nav-tabs .nav-link, .nav-tabs .nav-link.disabled, .nav-tabs .nav-link.disabled:hover, .nav-tabs .nav-link.disabled:focus {
|
||||||
border-color: #dfdfdf;
|
border-color: var(--mc-border);
|
||||||
}
|
}
|
||||||
.nav-tabs .nav-link.active, .nav-tabs .nav-item.show .nav-link {
|
.nav-tabs .nav-link.active, .nav-tabs .nav-item.show .nav-link {
|
||||||
border-color: #dfdfdf;
|
border-color: var(--mc-border);
|
||||||
border-bottom: 1px solid #ffffff;
|
border-bottom: 1px solid var(--mc-surface);
|
||||||
}
|
}
|
||||||
.nav-tabs .nav-link:hover, .nav-tabs .nav-link:focus {
|
.nav-tabs .nav-link:hover, .nav-tabs .nav-link:focus {
|
||||||
border-color: #dfdfdf;
|
border-color: var(--mc-border);
|
||||||
}
|
}
|
||||||
.nav-tabs {
|
.nav-tabs {
|
||||||
border-bottom: 1px solid #dfdfdf;
|
border-bottom: 1px solid var(--mc-border);
|
||||||
}
|
}
|
||||||
.bi {
|
.bi {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
@@ -164,13 +197,13 @@ body.modal-open {
|
|||||||
padding-right: inherit !important;
|
padding-right: inherit !important;
|
||||||
}
|
}
|
||||||
body {
|
body {
|
||||||
font-family: "Noto Sans","Helvetica Neue",Helvetica,Arial,sans-serif;
|
font-family: var(--mc-font-family);
|
||||||
font-size: 10.5pt;
|
font-size: var(--mc-font-size);
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
html {
|
html {
|
||||||
font-family: "Noto Sans","Helvetica Neue",Helvetica,Arial,sans-serif;
|
font-family: var(--mc-font-family);
|
||||||
font-size: 10.5pt;
|
font-size: var(--mc-font-size);
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
#mailcow-alert {
|
#mailcow-alert {
|
||||||
@@ -206,7 +239,7 @@ legend {
|
|||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
.overlay {
|
.overlay {
|
||||||
background: #fff;
|
background: var(--mc-surface);
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 10000;
|
z-index: 10000;
|
||||||
top: 0; right: 0; bottom: 0; left: 0;
|
top: 0; right: 0; bottom: 0; left: 0;
|
||||||
@@ -215,9 +248,6 @@ legend {
|
|||||||
.bootstrap-select.btn-group .no-results {
|
.bootstrap-select.btn-group .no-results {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-secondary {
|
|
||||||
color: rgb(197, 197, 197) !important;
|
|
||||||
}
|
|
||||||
.haveibeenpwned {
|
.haveibeenpwned {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
-webkit-user-select: none;
|
-webkit-user-select: none;
|
||||||
@@ -239,7 +269,7 @@ legend {
|
|||||||
.footer {
|
.footer {
|
||||||
margin-top: 27px;
|
margin-top: 27px;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
color: #959595;
|
color: var(--mc-muted);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
@@ -337,7 +367,7 @@ code {
|
|||||||
overflow-x: auto!important;
|
overflow-x: auto!important;
|
||||||
}
|
}
|
||||||
.well {
|
.well {
|
||||||
border: 1px solid #dfdfdf;
|
border: 1px solid var(--mc-border);
|
||||||
background-color: #f9f9f9;
|
background-color: #f9f9f9;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
@@ -356,47 +386,47 @@ button[aria-expanded='true'] > .caret {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.list-group-details {
|
.list-group-details {
|
||||||
background: #fff;
|
background: var(--mc-surface);
|
||||||
}
|
}
|
||||||
.list-group-header {
|
.list-group-header {
|
||||||
background: #f7f7f7;
|
background: var(--mc-surface-alt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.bg-primary, .alert-primary, .btn-primary {
|
.bg-primary, .alert-primary, .btn-primary {
|
||||||
background-color: #0F688D !important;
|
background-color: var(--mc-primary) !important;
|
||||||
border-color: #0d526d !important;
|
border-color: var(--mc-primary-border) !important;
|
||||||
}
|
}
|
||||||
.bg-info, .alert-info, .btn-info {
|
.bg-info, .alert-info, .btn-info {
|
||||||
background-color: #148DBC !important;
|
background-color: var(--mc-info) !important;
|
||||||
border-color: #127ea8 !important;
|
border-color: var(--mc-info-border) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-secondary {
|
.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-secondary {
|
||||||
color: rgb(137 137 137)!important;
|
color: var(--mc-placeholder) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.progress {
|
.progress {
|
||||||
height: 16px;
|
height: 16px;
|
||||||
background-color: #d5d5d5;
|
background-color: var(--mc-progress-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.btn-outline-secondary:hover {
|
.btn-outline-secondary:hover {
|
||||||
background-color: #f0f0f0;
|
background-color: var(--mc-btn-outline-hover-bg);
|
||||||
}
|
}
|
||||||
.btn.btn-outline-secondary {
|
.btn.btn-outline-secondary {
|
||||||
color: #000000 !important;
|
color: var(--mc-btn-outline-color) !important;
|
||||||
border-color: #cfcfcf !important;
|
border-color: var(--mc-btn-outline-border) !important;
|
||||||
}
|
}
|
||||||
.btn-check:checked+.btn-outline-secondary, .btn-check:active+.btn-outline-secondary, .btn-outline-secondary:active, .btn-outline-secondary.active, .btn-outline-secondary.dropdown-toggle.show {
|
.btn-check:checked+.btn-outline-secondary, .btn-check:active+.btn-outline-secondary, .btn-outline-secondary:active, .btn-outline-secondary.active, .btn-outline-secondary.dropdown-toggle.show {
|
||||||
background-color: #f0f0f0 !important;
|
background-color: var(--mc-btn-outline-active-bg) !important;
|
||||||
}
|
}
|
||||||
.btn-check:checked+.btn-light, .btn-check:active+.btn-light, .btn-light:active, .btn-light.active, .show>.btn-light.dropdown-toggle {
|
.btn-check:checked+.btn-light, .btn-check:active+.btn-light, .btn-light:active, .btn-light.active, .show>.btn-light.dropdown-toggle {
|
||||||
color: #fff;
|
color: var(--mc-btn-light-active-color);
|
||||||
background-color: #555;
|
background-color: var(--mc-btn-light-active-bg);
|
||||||
background-image: none;
|
background-image: none;
|
||||||
border-color: #4d4d4d;
|
border-color: var(--mc-btn-light-active-border);
|
||||||
}
|
}
|
||||||
.btn-check:checked+.btn-light:focus, .btn-check:active+.btn-light:focus, .btn-light:active:focus, .btn-light.active:focus, .show>.btn-light.dropdown-toggle:focus,
|
.btn-check:checked+.btn-light:focus, .btn-check:active+.btn-light:focus, .btn-light:active:focus, .btn-light.active:focus, .show>.btn-light.dropdown-toggle:focus,
|
||||||
.btn-check:focus+.btn-light, .btn-light:focus {
|
.btn-check:focus+.btn-light, .btn-light:focus {
|
||||||
@@ -412,6 +442,26 @@ button[aria-expanded='true'] > .caret {
|
|||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* role marker on login pages, header icons differ too (color blindness) */
|
||||||
|
.login-card::before {
|
||||||
|
content: "";
|
||||||
|
display: block;
|
||||||
|
height: 5px;
|
||||||
|
background: var(--mc-login-color);
|
||||||
|
border-radius: inherit;
|
||||||
|
border-bottom-left-radius: 0;
|
||||||
|
border-bottom-right-radius: 0;
|
||||||
|
}
|
||||||
|
.login-user {
|
||||||
|
--mc-login-color: var(--mc-login-user);
|
||||||
|
}
|
||||||
|
.login-domainadmin {
|
||||||
|
--mc-login-color: var(--mc-login-domainadmin);
|
||||||
|
}
|
||||||
|
.login-admin {
|
||||||
|
--mc-login-color: var(--mc-login-admin);
|
||||||
|
}
|
||||||
|
|
||||||
.hr-title {
|
.hr-title {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -423,7 +473,7 @@ button[aria-expanded='true'] > .caret {
|
|||||||
.hr-title::after {
|
.hr-title::after {
|
||||||
content: "";
|
content: "";
|
||||||
flex: 1;
|
flex: 1;
|
||||||
border-bottom: 1px solid #ccc;
|
border-bottom: 1px solid var(--mc-hr);
|
||||||
}
|
}
|
||||||
|
|
||||||
.hr-title:not(:empty)::before {
|
.hr-title:not(:empty)::before {
|
||||||
|
|||||||
+3750
-2935
File diff suppressed because it is too large.
Load diff
@@ -1,62 +1,103 @@
|
|||||||
|
/* theme tokens, override in css/themes/<name>/dark.css */
|
||||||
|
:root {
|
||||||
|
--mc-body-bg: #1c1c1e;
|
||||||
|
--mc-body-color: #f2f2f7;
|
||||||
|
--mc-surface: #2c2c2e;
|
||||||
|
--mc-surface-alt: #282828;
|
||||||
|
--mc-text: #ccc;
|
||||||
|
--mc-text-soft: #bbb;
|
||||||
|
--mc-muted: #8e8e93;
|
||||||
|
--mc-border: #555;
|
||||||
|
--mc-tab-border: #444444;
|
||||||
|
--mc-link: #6fc7e9;
|
||||||
|
--mc-link-hover: #3daedb;
|
||||||
|
--mc-btn-bg: #5e5e5e;
|
||||||
|
--mc-btn-border: #4c4c4e;
|
||||||
|
--mc-input-bg: #424242;
|
||||||
|
--mc-input-color: #e2e2e2;
|
||||||
|
--mc-input-border: #999;
|
||||||
|
--mc-dropdown-bg: #424242;
|
||||||
|
--mc-table-bg: #3a3a3a;
|
||||||
|
--mc-table-stripe: #282828;
|
||||||
|
--mc-table-stripe-row: #424242;
|
||||||
|
--mc-table-hover: #343434;
|
||||||
|
--mc-table-border: #5c5c5c;
|
||||||
|
--mc-progress-bg: #555;
|
||||||
|
--mc-placeholder: #d4d4d4;
|
||||||
|
--mc-btn-outline-color: #e0e0e0;
|
||||||
|
--mc-btn-outline-border: #7a7a7a;
|
||||||
|
--mc-btn-outline-hover-bg: #5c5c5c;
|
||||||
|
--mc-btn-outline-active-bg: #7a7a7a;
|
||||||
|
--mc-btn-light-active-color: #f2f2f7;
|
||||||
|
--mc-btn-light-active-bg: #242424;
|
||||||
|
--mc-btn-light-active-border: #1c1c1e;
|
||||||
|
--mc-btn-dark-bg: #242424;
|
||||||
|
--mc-input-disabled-bg: #6e6e6e;
|
||||||
|
--mc-input-disabled-color: #a8a8a8;
|
||||||
|
--mc-list-header-bg: #444;
|
||||||
|
--mc-list-details-bg: #555;
|
||||||
|
--mc-chip-bg: #444;
|
||||||
|
--mc-table-child-bg: #414141;
|
||||||
|
--mc-dtr-bg: #7a7a7a;
|
||||||
|
--mc-dtr-border: #5c5c5c;
|
||||||
|
--mc-dtr-color: #e0e0e0;
|
||||||
|
--mc-dtr-hover-bg: #7a7a7a;
|
||||||
|
--mc-dtr-active-bg: #949494;
|
||||||
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
background-color: #1c1c1e;
|
background-color: var(--mc-body-bg);
|
||||||
color: #f2f2f7;
|
color: var(--mc-body-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.card {
|
.card {
|
||||||
border: 1px solid #2c2c2e;
|
border: 1px solid var(--mc-surface);
|
||||||
background-color: #2c2c2e;
|
background-color: var(--mc-surface);
|
||||||
}
|
}
|
||||||
|
|
||||||
legend {
|
legend {
|
||||||
color: #f2f2f7;
|
color: var(--mc-body-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-header {
|
.card-header {
|
||||||
color: #8e8e93;
|
color: var(--mc-muted);
|
||||||
background-color: #1c1c1e;
|
background-color: var(--mc-body-bg);
|
||||||
border-color: transparent;
|
border-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-body {
|
.card-body {
|
||||||
--bs-card-color: #bbb;
|
--bs-card-color: var(--mc-text-soft);
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-secondary, .paginate_button, .page-link, .btn-light {
|
.btn-secondary, .paginate_button, .page-link, .btn-light {
|
||||||
color: #f2f2f7 !important;
|
color: var(--mc-body-color) !important;
|
||||||
background-color: #5e5e5e !important;
|
background-color: var(--mc-btn-bg) !important;
|
||||||
border-color: #4c4c4e !important;
|
border-color: var(--mc-btn-border) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-dark {
|
.btn-dark {
|
||||||
color: #f2f2f7 !important;
|
color: var(--mc-body-color) !important;
|
||||||
background-color: #242424 !important;
|
background-color: var(--mc-btn-dark-bg) !important;
|
||||||
border-color: #1c1c1e !important;
|
border-color: var(--mc-body-bg) !important;
|
||||||
}
|
|
||||||
|
|
||||||
.btn-secondary:focus, .btn-secondary:hover, .btn-group.open .dropdown-toggle.btn-secondary {
|
|
||||||
background-color: #444444;
|
|
||||||
border-color: #4c4c4e !important;
|
|
||||||
color: #f2f2f7;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-check:checked+.btn-secondary, .btn-check:active+.btn-secondary, .btn-secondary:active, .btn-secondary.active, .show>.btn-secondary.dropdown-toggle {
|
.btn-check:checked+.btn-secondary, .btn-check:active+.btn-secondary, .btn-secondary:active, .btn-secondary.active, .show>.btn-secondary.dropdown-toggle {
|
||||||
border-color: #5e5e5e !important;
|
border-color: var(--mc-btn-bg) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.alert-secondary {
|
.alert-secondary {
|
||||||
color: #f2f2f7 !important;
|
color: var(--mc-body-color) !important;
|
||||||
background-color: #5e5e5e !important;
|
background-color: var(--mc-btn-bg) !important;
|
||||||
border-color: #4c4c4e !important;
|
border-color: var(--mc-btn-border) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-secondary {
|
.bg-secondary {
|
||||||
color: #f2f2f7 !important;
|
color: var(--mc-body-color) !important;
|
||||||
background-color: #5e5e5e !important;
|
background-color: var(--mc-btn-bg) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.alert-secondary, .alert-secondary a, .alert-secondary .alert-link {
|
.alert-secondary, .alert-secondary a, .alert-secondary .alert-link {
|
||||||
color: #f2f2f7;
|
color: var(--mc-body-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-item.active .page-link {
|
.page-item.active .page-link {
|
||||||
@@ -65,18 +106,18 @@ legend {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.btn-secondary:focus, .btn-secondary:hover, .btn-group.open .dropdown-toggle.btn-secondary {
|
.btn-secondary:focus, .btn-secondary:hover, .btn-group.open .dropdown-toggle.btn-secondary {
|
||||||
background-color: #5e5e5e;
|
background-color: var(--mc-btn-bg);
|
||||||
border-color: #4c4c4e !important;
|
border-color: var(--mc-btn-border) !important;
|
||||||
color: #f2f2f7;
|
color: var(--mc-body-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-secondary:disabled, .btn-secondary.disabled {
|
.btn-secondary:disabled, .btn-secondary.disabled {
|
||||||
border-color: #5e5e5e !important;
|
border-color: var(--mc-btn-bg) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-content {
|
.modal-content {
|
||||||
--bs-modal-color: #bbb;
|
--bs-modal-color: var(--mc-text-soft);
|
||||||
background-color: #2c2c2e;
|
background-color: var(--mc-surface);
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-header {
|
.modal-header {
|
||||||
@@ -84,7 +125,7 @@ legend {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.modal-title {
|
.modal-title {
|
||||||
color: #bbb;
|
color: var(--mc-text-soft);
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal .btn-close {
|
.modal .btn-close {
|
||||||
@@ -92,12 +133,12 @@ legend {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.navbar.bg-light {
|
.navbar.bg-light {
|
||||||
background-color: #1c1c1e !important;
|
background-color: var(--mc-body-bg) !important;
|
||||||
border-color: #2c2c2e;
|
border-color: var(--mc-surface);
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-link {
|
.nav-link {
|
||||||
color: #8e8e93 !important;
|
color: var(--mc-muted) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-tabs .nav-link.active, .nav-tabs .nav-item.show .nav-link {
|
.nav-tabs .nav-link.active, .nav-tabs .nav-item.show .nav-link {
|
||||||
@@ -105,23 +146,23 @@ legend {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.nav-tabs, .nav-tabs .nav-link {
|
.nav-tabs, .nav-tabs .nav-link {
|
||||||
border-color: #444444 !important;
|
border-color: var(--mc-tab-border) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-tabs .nav-link:not(.disabled):hover, .nav-tabs .nav-link:not(.disabled):focus, .nav-tabs .nav-link.active {
|
.nav-tabs .nav-link:not(.disabled):hover, .nav-tabs .nav-link:not(.disabled):focus, .nav-tabs .nav-link.active {
|
||||||
border-bottom-color: #1c1c1e !important;
|
border-bottom-color: var(--mc-body-bg) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card .nav-tabs .nav-link:not(.disabled):hover, .card .nav-tabs .nav-link:not(.disabled):focus, .card .nav-tabs .nav-link.active {
|
.card .nav-tabs .nav-link:not(.disabled):hover, .card .nav-tabs .nav-link:not(.disabled):focus, .card .nav-tabs .nav-link.active {
|
||||||
border-bottom-color: #2c2c2e !important;
|
border-bottom-color: var(--mc-surface) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.table, .table-striped>tbody>tr:nth-of-type(odd)>*, tbody tr {
|
.table, .table-striped>tbody>tr:nth-of-type(odd)>*, tbody tr {
|
||||||
color: #f2f2f7 !important;
|
color: var(--mc-body-color) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropdown-menu {
|
.dropdown-menu {
|
||||||
background-color: #424242;
|
background-color: var(--mc-dropdown-bg);
|
||||||
border: 1px solid #282828;
|
border: 1px solid #282828;
|
||||||
}
|
}
|
||||||
.dropdown-menu>li>a:focus, .dropdown-menu>li>a:hover {
|
.dropdown-menu>li>a:focus, .dropdown-menu>li>a:hover {
|
||||||
@@ -132,79 +173,79 @@ legend {
|
|||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
.bootstrap-select>.dropdown-toggle.bs-placeholder, .bootstrap-select>.dropdown-toggle.bs-placeholder.btn-secondary {
|
.bootstrap-select>.dropdown-toggle.bs-placeholder, .bootstrap-select>.dropdown-toggle.bs-placeholder.btn-secondary {
|
||||||
color: #d4d4d4 !important;
|
color: var(--mc-placeholder) !important;
|
||||||
}
|
}
|
||||||
tbody tr {
|
tbody tr {
|
||||||
color: #ccc;
|
color: var(--mc-text);
|
||||||
}
|
}
|
||||||
.navbar-default .navbar-nav>.open>a, .navbar-default .navbar-nav>.open>a:focus, .navbar-default .navbar-nav>.open>a:hover {
|
.navbar-default .navbar-nav>.open>a, .navbar-default .navbar-nav>.open>a:focus, .navbar-default .navbar-nav>.open>a:hover {
|
||||||
color: #ccc;
|
color: var(--mc-text);
|
||||||
}
|
}
|
||||||
.navbar-default .navbar-nav>.active>a, .navbar-default .navbar-nav>.active>a:focus, .navbar-default .navbar-nav>.active>a:hover {
|
.navbar-default .navbar-nav>.active>a, .navbar-default .navbar-nav>.active>a:focus, .navbar-default .navbar-nav>.active>a:hover {
|
||||||
color: #ccc;
|
color: var(--mc-text);
|
||||||
}
|
}
|
||||||
.list-group-item {
|
.list-group-item {
|
||||||
background-color: #282828;
|
background-color: var(--mc-surface-alt);
|
||||||
border: 1px solid #555;
|
border: 1px solid var(--mc-border);
|
||||||
}
|
}
|
||||||
.table-striped>tbody>tr:nth-of-type(odd) {
|
.table-striped>tbody>tr:nth-of-type(odd) {
|
||||||
background-color: #424242;
|
background-color: var(--mc-table-stripe-row);
|
||||||
}
|
}
|
||||||
table.dataTable>tbody>tr.child ul.dtr-details>li {
|
table.dataTable>tbody>tr.child ul.dtr-details>li {
|
||||||
border-bottom: 1px solid rgba(255, 255, 255, 0.13);
|
border-bottom: 1px solid rgba(255, 255, 255, 0.13);
|
||||||
}
|
}
|
||||||
.label.label-last-login {
|
.label.label-last-login {
|
||||||
color: #ccc !important;
|
color: var(--mc-text) !important;
|
||||||
background-color: #555 !important;
|
background-color: #555 !important;
|
||||||
}
|
}
|
||||||
.progress {
|
.progress {
|
||||||
background-color: #555;
|
background-color: var(--mc-progress-bg);
|
||||||
}
|
}
|
||||||
.h1, .h2, .h3, .h4, .h5, .h6, h1, h2, h3, h4, h5, h6 {
|
.h1, .h2, .h3, .h4, .h5, .h6, h1, h2, h3, h4, h5, h6 {
|
||||||
color: #ccc;
|
color: var(--mc-text);
|
||||||
}
|
}
|
||||||
div.numberedtextarea-number {
|
div.numberedtextarea-number {
|
||||||
color: #999;
|
color: #999;
|
||||||
}
|
}
|
||||||
.well {
|
.well {
|
||||||
border: 1px solid #555;
|
border: 1px solid var(--mc-border);
|
||||||
background-color: #282828;
|
background-color: var(--mc-surface-alt);
|
||||||
}
|
}
|
||||||
pre {
|
pre {
|
||||||
color: #ccc;
|
color: var(--mc-text);
|
||||||
background-color: #282828;
|
background-color: var(--mc-surface-alt);
|
||||||
border: 1px solid #555;
|
border: 1px solid var(--mc-border);
|
||||||
}
|
}
|
||||||
.form-control {
|
.form-control {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
input.form-control, textarea.form-control {
|
input.form-control, textarea.form-control {
|
||||||
color: #e2e2e2 !important;
|
color: var(--mc-input-color) !important;
|
||||||
background-color: #424242 !important;
|
background-color: var(--mc-input-bg) !important;
|
||||||
border: 1px solid #999;
|
border: 1px solid var(--mc-input-border);
|
||||||
}
|
}
|
||||||
input.form-control:focus, textarea.form-control {
|
input.form-control:focus, textarea.form-control {
|
||||||
background-color: #424242 !important;
|
background-color: var(--mc-input-bg) !important;
|
||||||
}
|
}
|
||||||
input.form-control:disabled, textarea.form-disabled {
|
input.form-control:disabled, textarea.form-disabled {
|
||||||
color: #a8a8a8 !important;
|
color: var(--mc-input-disabled-color) !important;
|
||||||
background-color: #6e6e6e !important;
|
background-color: var(--mc-input-disabled-bg) !important;
|
||||||
}
|
}
|
||||||
.input-group-addon {
|
.input-group-addon {
|
||||||
color: #ccc;
|
color: var(--mc-text);
|
||||||
background-color: #424242 !important;
|
background-color: var(--mc-input-bg) !important;
|
||||||
border: 1px solid #999;
|
border: 1px solid var(--mc-input-border);
|
||||||
}
|
}
|
||||||
.input-group-text {
|
.input-group-text {
|
||||||
color: #ccc;
|
color: var(--mc-text);
|
||||||
background-color: #1c1c1c;
|
background-color: #1c1c1c;
|
||||||
}
|
}
|
||||||
|
|
||||||
.list-group-item {
|
.list-group-item {
|
||||||
color: #ccc;
|
color: var(--mc-text);
|
||||||
}
|
}
|
||||||
.dropdown-item {
|
.dropdown-item {
|
||||||
color: #ccc;
|
color: var(--mc-text);
|
||||||
}
|
}
|
||||||
.dropdown-item:hover {
|
.dropdown-item:hover {
|
||||||
color: #616161 !important;
|
color: #616161 !important;
|
||||||
@@ -214,9 +255,9 @@ input.form-control:disabled, textarea.form-disabled {
|
|||||||
background-color: #007aff;
|
background-color: #007aff;
|
||||||
}
|
}
|
||||||
.form-select {
|
.form-select {
|
||||||
color: #e2e2e2!important;
|
color: var(--mc-input-color)!important;
|
||||||
background-color: #424242!important;
|
background-color: var(--mc-input-bg)!important;
|
||||||
border: 1px solid #999;
|
border: 1px solid var(--mc-input-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
.responsive-tabs .card-header button[data-bs-toggle="collapse"] {
|
.responsive-tabs .card-header button[data-bs-toggle="collapse"] {
|
||||||
@@ -240,15 +281,15 @@ input.form-control:disabled, textarea.form-disabled {
|
|||||||
|
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: #6fc7e9;
|
color: var(--mc-link);
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
a:hover {
|
a:hover {
|
||||||
color: #3daedb;
|
color: var(--mc-link-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
.breadcrumb-item.active {
|
.breadcrumb-item.active {
|
||||||
color: #ccc;
|
color: var(--mc-text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -273,11 +314,11 @@ a:hover {
|
|||||||
background-color: #3a3a3a;
|
background-color: #3a3a3a;
|
||||||
}
|
}
|
||||||
.accordion-button {
|
.accordion-button {
|
||||||
color: #bbb;
|
color: var(--mc-text-soft);
|
||||||
background-color: #2c2c2c;
|
background-color: #2c2c2c;
|
||||||
}
|
}
|
||||||
.accordion-button:not(.collapsed) {
|
.accordion-button:not(.collapsed) {
|
||||||
color: #6fc7e9;
|
color: var(--mc-link);
|
||||||
background-color: #2c2c2c;
|
background-color: #2c2c2c;
|
||||||
}
|
}
|
||||||
.accordion-button::after {
|
.accordion-button::after {
|
||||||
@@ -300,15 +341,15 @@ a:hover {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.tag-box {
|
.tag-box {
|
||||||
background-color: #282828;
|
background-color: var(--mc-surface-alt);
|
||||||
border: 1px solid #555;
|
border: 1px solid var(--mc-border);
|
||||||
}
|
}
|
||||||
.tag-input {
|
.tag-input {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
background-color: #282828;
|
background-color: var(--mc-surface-alt);
|
||||||
}
|
}
|
||||||
.tag-add {
|
.tag-add {
|
||||||
color: #ccc;
|
color: var(--mc-text);
|
||||||
}
|
}
|
||||||
.tag-add:hover {
|
.tag-add:hover {
|
||||||
color: #d1d1d1;
|
color: #d1d1d1;
|
||||||
@@ -318,19 +359,19 @@ a:hover {
|
|||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
.btn-outline-secondary:hover {
|
.btn-outline-secondary:hover {
|
||||||
background-color: #5c5c5c;
|
background-color: var(--mc-btn-outline-hover-bg);
|
||||||
}
|
}
|
||||||
.btn.btn-outline-secondary {
|
.btn.btn-outline-secondary {
|
||||||
color: #e0e0e0 !important;
|
color: var(--mc-btn-outline-color) !important;
|
||||||
border-color: #7a7a7a !important;
|
border-color: var(--mc-btn-outline-border) !important;
|
||||||
}
|
}
|
||||||
.btn-check:checked+.btn-outline-secondary, .btn-check:active+.btn-outline-secondary, .btn-outline-secondary:active, .btn-outline-secondary.active, .btn-outline-secondary.dropdown-toggle.show {
|
.btn-check:checked+.btn-outline-secondary, .btn-check:active+.btn-outline-secondary, .btn-outline-secondary:active, .btn-outline-secondary.active, .btn-outline-secondary.dropdown-toggle.show {
|
||||||
background-color: #7a7a7a !important;
|
background-color: var(--mc-btn-outline-active-bg) !important;
|
||||||
}
|
}
|
||||||
.btn-check:checked+.btn-light, .btn-check:active+.btn-light, .btn-light:active, .btn-light.active, .show>.btn-light.dropdown-toggle {
|
.btn-check:checked+.btn-light, .btn-check:active+.btn-light, .btn-light:active, .btn-light.active, .show>.btn-light.dropdown-toggle {
|
||||||
color: #f2f2f7 !important;
|
color: var(--mc-btn-light-active-color) !important;
|
||||||
background-color: #242424 !important;
|
background-color: var(--mc-btn-light-active-bg) !important;
|
||||||
border-color: #1c1c1e !important;
|
border-color: var(--mc-btn-light-active-border) !important;
|
||||||
}
|
}
|
||||||
.btn-input-missing,
|
.btn-input-missing,
|
||||||
.btn-input-missing:hover,
|
.btn-input-missing:hover,
|
||||||
@@ -348,16 +389,16 @@ a:hover {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.list-group-details {
|
.list-group-details {
|
||||||
background: #555;
|
background: var(--mc-list-details-bg);
|
||||||
}
|
}
|
||||||
.list-group-header {
|
.list-group-header {
|
||||||
background: #444;
|
background: var(--mc-list-header-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
span.mail-address-item {
|
span.mail-address-item {
|
||||||
background-color: #444;
|
background-color: var(--mc-chip-bg);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
border: 1px solid #555;
|
border: 1px solid var(--mc-border);
|
||||||
padding: 2px 7px;
|
padding: 2px 7px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin: 2px 6px 2px 0;
|
margin: 2px 6px 2px 0;
|
||||||
@@ -365,52 +406,52 @@ span.mail-address-item {
|
|||||||
|
|
||||||
table.dataTable.dtr-inline.collapsed>tbody>tr>td.dtr-control:before:hover,
|
table.dataTable.dtr-inline.collapsed>tbody>tr>td.dtr-control:before:hover,
|
||||||
table.dataTable.dtr-inline.collapsed>tbody>tr>th.dtr-control:before:hover {
|
table.dataTable.dtr-inline.collapsed>tbody>tr>th.dtr-control:before:hover {
|
||||||
background-color: #7a7a7a !important;
|
background-color: var(--mc-dtr-hover-bg) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
table.dataTable.dtr-inline.collapsed>tbody>tr>td.dtr-control:before,
|
table.dataTable.dtr-inline.collapsed>tbody>tr>td.dtr-control:before,
|
||||||
table.dataTable.dtr-inline.collapsed>tbody>tr>th.dtr-control:before {
|
table.dataTable.dtr-inline.collapsed>tbody>tr>th.dtr-control:before {
|
||||||
background-color: #7a7a7a !important;
|
background-color: var(--mc-dtr-bg) !important;
|
||||||
border: 1.5px solid #5c5c5c !important;
|
border: 1.5px solid var(--mc-dtr-border) !important;
|
||||||
color: #e0e0e0 !important;
|
color: var(--mc-dtr-color) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
table.dataTable.dtr-inline.collapsed>tbody>tr.parent>td.dtr-control:before,
|
table.dataTable.dtr-inline.collapsed>tbody>tr.parent>td.dtr-control:before,
|
||||||
table.dataTable.dtr-inline.collapsed>tbody>tr.parent>th.dtr-control:before {
|
table.dataTable.dtr-inline.collapsed>tbody>tr.parent>th.dtr-control:before {
|
||||||
background-color: #949494;
|
background-color: var(--mc-dtr-active-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
table.dataTable.dtr-inline.collapsed>tbody>tr>td.child,
|
table.dataTable.dtr-inline.collapsed>tbody>tr>td.child,
|
||||||
table.dataTable.dtr-inline.collapsed>tbody>tr>th.child,
|
table.dataTable.dtr-inline.collapsed>tbody>tr>th.child,
|
||||||
table.dataTable.dtr-inline.collapsed>tbody>tr>td.dataTables_empty {
|
table.dataTable.dtr-inline.collapsed>tbody>tr>td.dataTables_empty {
|
||||||
background-color: #414141;
|
background-color: var(--mc-table-child-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
table.table, .table-striped>tbody>tr:nth-of-type(odd)>*, tbody tr {
|
table.table, .table-striped>tbody>tr:nth-of-type(odd)>*, tbody tr {
|
||||||
color: #ccc !important;
|
color: var(--mc-text) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-secondary {
|
.table-secondary {
|
||||||
--bs-table-bg: #282828;
|
--bs-table-bg: #282828;
|
||||||
--bs-table-striped-bg: #343434;
|
--bs-table-striped-bg: #343434;
|
||||||
--bs-table-striped-color: #f2f2f7;
|
--bs-table-striped-color: var(--mc-body-color);
|
||||||
--bs-table-active-bg: #4c4c4c;
|
--bs-table-active-bg: #4c4c4c;
|
||||||
--bs-table-active-color: #f2f2f7;
|
--bs-table-active-color: var(--mc-body-color);
|
||||||
--bs-table-hover-bg: #3a3a3a;
|
--bs-table-hover-bg: #3a3a3a;
|
||||||
--bs-table-hover-color: #f2f2f7;
|
--bs-table-hover-color: var(--mc-body-color);
|
||||||
color: #ccc;
|
color: var(--mc-text);
|
||||||
border-color: #3a3a3a;
|
border-color: #3a3a3a;
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-light {
|
.table-light {
|
||||||
--bs-table-bg: #3a3a3a;
|
--bs-table-bg: #3a3a3a;
|
||||||
--bs-table-striped-bg: #444444;
|
--bs-table-striped-bg: #444444;
|
||||||
--bs-table-striped-color: #f2f2f7;
|
--bs-table-striped-color: var(--mc-body-color);
|
||||||
--bs-table-active-bg: #5c5c5c;
|
--bs-table-active-bg: #5c5c5c;
|
||||||
--bs-table-active-color: #f2f2f7;
|
--bs-table-active-color: var(--mc-body-color);
|
||||||
--bs-table-hover-bg: #4c4c4c;
|
--bs-table-hover-bg: #4c4c4c;
|
||||||
--bs-table-hover-color: #f2f2f7;
|
--bs-table-hover-color: var(--mc-body-color);
|
||||||
color: #ccc;
|
color: var(--mc-text);
|
||||||
border-color: #4c4c4c;
|
border-color: #4c4c4c;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -430,23 +471,23 @@ table.table, .table-striped>tbody>tr:nth-of-type(odd)>*, tbody tr {
|
|||||||
|
|
||||||
.table-striped>tbody>tr:nth-of-type(odd)>td,
|
.table-striped>tbody>tr:nth-of-type(odd)>td,
|
||||||
.table-striped>tbody>tr:nth-of-type(odd)>th {
|
.table-striped>tbody>tr:nth-of-type(odd)>th {
|
||||||
background-color: #282828;
|
background-color: var(--mc-table-stripe);
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-hover>tbody>tr:hover {
|
.table-hover>tbody>tr:hover {
|
||||||
background-color: #343434;
|
background-color: var(--mc-table-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
.table>:not(caption)>*>* {
|
.table>:not(caption)>*>* {
|
||||||
border-color: #5c5c5c;
|
border-color: var(--mc-table-border);
|
||||||
--bs-table-color-state:#bbb;
|
--bs-table-color-state:var(--mc-text-soft);
|
||||||
--bs-table-bg: #3a3a3a;
|
--bs-table-bg: var(--mc-table-bg);
|
||||||
}
|
}
|
||||||
.text-muted {
|
.text-muted {
|
||||||
--bs-secondary-color: #8e8e93;
|
--bs-secondary-color: var(--mc-muted);
|
||||||
}
|
}
|
||||||
input::placeholder {
|
input::placeholder {
|
||||||
color: #8e8e93 !important;
|
color: var(--mc-muted) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-select {
|
.form-select {
|
||||||
|
|||||||
@@ -0,0 +1,212 @@
|
|||||||
|
/* servercow theme, dark mode, bundled after mailcow core dark mode */
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--sc-brown-hover: #806b55;
|
||||||
|
--sc-brown-active: #8a745c;
|
||||||
|
--sc-brown-soft: rgba(201, 180, 154, .18);
|
||||||
|
--sc-brown-tint: rgba(201, 180, 154, .08);
|
||||||
|
--sc-link: #c9b49a;
|
||||||
|
--sc-danger-text: #ff8a8a;
|
||||||
|
--sc-ink: #ece7e1;
|
||||||
|
--sc-muted: #a89f94;
|
||||||
|
--sc-placeholder: #8a8177;
|
||||||
|
--sc-bg: #171513;
|
||||||
|
--sc-surface: #211e1b;
|
||||||
|
--sc-cap: #2a2622;
|
||||||
|
--sc-addon: #2a2622;
|
||||||
|
--sc-line: #463f38;
|
||||||
|
--sc-divider: #34302b;
|
||||||
|
--sc-cap-line: #3d3732;
|
||||||
|
--sc-stripe: rgba(255, 255, 255, .025);
|
||||||
|
--sc-hover: rgba(201, 180, 154, .08);
|
||||||
|
--sc-focus-border: #8a7864;
|
||||||
|
--sc-focus: rgba(201, 180, 154, .25);
|
||||||
|
--sc-shadow: 0 0 10px 0 rgba(0, 0, 0, .5);
|
||||||
|
--bs-body-color: #ece7e1;
|
||||||
|
--bs-body-bg: #171513;
|
||||||
|
/* core dark tokens, see css/themes/mailcow-darkmode.css */
|
||||||
|
--mc-body-bg: var(--sc-bg);
|
||||||
|
--mc-body-color: var(--sc-ink);
|
||||||
|
--mc-surface: var(--sc-surface);
|
||||||
|
--mc-surface-alt: var(--sc-cap);
|
||||||
|
--mc-text: var(--sc-ink);
|
||||||
|
--mc-text-soft: var(--sc-ink);
|
||||||
|
--mc-muted: var(--sc-muted);
|
||||||
|
--mc-border: var(--sc-divider);
|
||||||
|
--mc-tab-border: var(--sc-divider);
|
||||||
|
--mc-link: var(--sc-link);
|
||||||
|
--mc-link-hover: #e0cfba;
|
||||||
|
--mc-btn-bg: var(--sc-cap);
|
||||||
|
--mc-btn-border: var(--sc-line);
|
||||||
|
--mc-input-bg: var(--sc-bg);
|
||||||
|
--mc-input-color: var(--sc-ink);
|
||||||
|
--mc-input-border: var(--sc-line);
|
||||||
|
--mc-dropdown-bg: var(--sc-surface);
|
||||||
|
--mc-table-bg: transparent;
|
||||||
|
--mc-table-stripe: var(--sc-stripe);
|
||||||
|
--mc-table-stripe-row: transparent;
|
||||||
|
--mc-table-hover: var(--sc-hover);
|
||||||
|
--mc-table-border: var(--sc-divider);
|
||||||
|
--mc-progress-bg: var(--sc-cap);
|
||||||
|
--mc-placeholder: var(--sc-placeholder);
|
||||||
|
--mc-hr: var(--sc-divider);
|
||||||
|
--mc-btn-outline-color: var(--sc-ink);
|
||||||
|
--mc-btn-outline-border: var(--sc-line);
|
||||||
|
--mc-btn-outline-hover-bg: var(--sc-brown-tint);
|
||||||
|
--mc-btn-outline-active-bg: var(--sc-brown);
|
||||||
|
--mc-btn-light-active-color: #fff;
|
||||||
|
--mc-btn-light-active-bg: var(--sc-brown);
|
||||||
|
--mc-btn-light-active-border: var(--sc-brown);
|
||||||
|
--mc-btn-dark-bg: var(--sc-brown);
|
||||||
|
--mc-input-disabled-bg: var(--sc-cap);
|
||||||
|
--mc-input-disabled-color: var(--sc-muted);
|
||||||
|
--mc-list-header-bg: var(--sc-cap);
|
||||||
|
--mc-list-details-bg: var(--sc-surface);
|
||||||
|
--mc-chip-bg: var(--sc-cap);
|
||||||
|
--mc-table-child-bg: var(--sc-surface);
|
||||||
|
--mc-dtr-bg: var(--sc-cap);
|
||||||
|
--mc-dtr-border: var(--sc-line);
|
||||||
|
--mc-dtr-color: var(--sc-link);
|
||||||
|
--mc-dtr-hover-bg: var(--sc-hover);
|
||||||
|
--mc-dtr-active-bg: var(--sc-brown-soft);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* navbar */
|
||||||
|
.navbar.bg-light {
|
||||||
|
background-color: var(--sc-surface) !important;
|
||||||
|
border-bottom-color: var(--sc-green);
|
||||||
|
}
|
||||||
|
.navbar-light .navbar-nav .nav-link {
|
||||||
|
color: var(--sc-ink) !important;
|
||||||
|
}
|
||||||
|
.navbar-light .navbar-nav .nav-link:hover,
|
||||||
|
.navbar-light .navbar-nav .nav-link:focus,
|
||||||
|
.navbar-light .navbar-nav .nav-link.active,
|
||||||
|
.navbar-light .navbar-nav .show > .nav-link {
|
||||||
|
color: var(--sc-link) !important;
|
||||||
|
}
|
||||||
|
.dropdown-menu {
|
||||||
|
border-color: var(--sc-cap-line);
|
||||||
|
}
|
||||||
|
.dropdown-item:hover, .dropdown-item:focus {
|
||||||
|
color: var(--sc-link) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* cards and modals */
|
||||||
|
.card-header {
|
||||||
|
color: var(--sc-ink);
|
||||||
|
background-color: var(--sc-cap);
|
||||||
|
border-color: var(--sc-cap-line);
|
||||||
|
}
|
||||||
|
.modal-content {
|
||||||
|
background-color: var(--sc-surface);
|
||||||
|
}
|
||||||
|
.modal-header {
|
||||||
|
border-color: var(--sc-cap-line);
|
||||||
|
}
|
||||||
|
.input-group-text {
|
||||||
|
background-color: var(--sc-addon);
|
||||||
|
}
|
||||||
|
.form-control::file-selector-button {
|
||||||
|
color: var(--sc-ink);
|
||||||
|
background-color: var(--sc-cap);
|
||||||
|
border-color: var(--sc-line);
|
||||||
|
}
|
||||||
|
.form-control:hover:not(:disabled):not([readonly])::file-selector-button {
|
||||||
|
color: var(--sc-link);
|
||||||
|
background-color: var(--sc-line);
|
||||||
|
}
|
||||||
|
.form-check-input:not(:checked) {
|
||||||
|
background-color: var(--sc-bg);
|
||||||
|
border-color: var(--sc-line);
|
||||||
|
}
|
||||||
|
kbd {
|
||||||
|
color: var(--sc-ink);
|
||||||
|
background-color: var(--sc-cap);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* buttons */
|
||||||
|
.btn-secondary:hover, .btn-secondary:focus, .btn-light:hover, .btn-light:focus, .page-link:hover {
|
||||||
|
color: var(--sc-link) !important;
|
||||||
|
background-color: var(--sc-line) !important;
|
||||||
|
}
|
||||||
|
.btn-check:checked + .btn-secondary, .btn-secondary.active, .show > .btn-secondary.dropdown-toggle,
|
||||||
|
.page-item.active .page-link {
|
||||||
|
color: #fff !important;
|
||||||
|
background-color: var(--sc-brown) !important;
|
||||||
|
border-color: var(--sc-brown) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* tabs */
|
||||||
|
.nav-tabs .nav-link {
|
||||||
|
color: var(--sc-ink) !important;
|
||||||
|
border-bottom-color: transparent !important;
|
||||||
|
}
|
||||||
|
.nav-tabs .nav-link:not(.disabled):hover, .nav-tabs .nav-link:not(.disabled):focus,
|
||||||
|
.card .nav-tabs .nav-link:not(.disabled):hover, .card .nav-tabs .nav-link:not(.disabled):focus {
|
||||||
|
color: var(--sc-link) !important;
|
||||||
|
border-bottom-color: transparent !important;
|
||||||
|
}
|
||||||
|
.nav-tabs .nav-link.active, .card .nav-tabs .nav-link.active {
|
||||||
|
color: var(--sc-link) !important;
|
||||||
|
border-bottom-color: var(--sc-link) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* semantic text */
|
||||||
|
.text-success {
|
||||||
|
color: var(--sc-green) !important;
|
||||||
|
}
|
||||||
|
.text-warning {
|
||||||
|
color: var(--sc-warning) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* alerts, opaque dark tints (also used for floating notifications) */
|
||||||
|
.alert-primary {
|
||||||
|
color: #e6d9c9 !important;
|
||||||
|
background-color: #3c342b !important;
|
||||||
|
border-color: #5e4f3e !important;
|
||||||
|
}
|
||||||
|
.alert-info {
|
||||||
|
color: #cfe9f6 !important;
|
||||||
|
background-color: #2f3334 !important;
|
||||||
|
border-color: #44535a !important;
|
||||||
|
}
|
||||||
|
.alert-success {
|
||||||
|
color: #cdeea0;
|
||||||
|
background-color: #2e3419;
|
||||||
|
border-color: #405515;
|
||||||
|
}
|
||||||
|
.alert-danger {
|
||||||
|
color: #f9c4c4;
|
||||||
|
background-color: #402a28;
|
||||||
|
border-color: #693a38;
|
||||||
|
}
|
||||||
|
.alert-warning {
|
||||||
|
color: #f5d496;
|
||||||
|
background-color: #3c2f19;
|
||||||
|
border-color: #654815;
|
||||||
|
}
|
||||||
|
.alert-secondary, .alert-light {
|
||||||
|
color: var(--sc-ink) !important;
|
||||||
|
background-color: var(--sc-cap) !important;
|
||||||
|
border-color: var(--sc-divider) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert .btn-close {
|
||||||
|
filter: invert(1) grayscale(100%) brightness(200%);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* slider */
|
||||||
|
.noUi-target {
|
||||||
|
background: var(--sc-bg);
|
||||||
|
border-color: var(--sc-line);
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
.noUi-handle {
|
||||||
|
background: var(--sc-cap);
|
||||||
|
border-color: var(--sc-line);
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
.noUi-handle::before, .noUi-handle::after {
|
||||||
|
background: var(--sc-line);
|
||||||
|
}
|
||||||
@@ -0,0 +1,202 @@
|
|||||||
|
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
Copyright 2020 The Poppins Project Authors (https://github.com/itfoundry/Poppins)
|
||||||
|
|
||||||
|
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||||
|
This license is copied below, and is also available with a FAQ at:
|
||||||
|
http://scripts.sil.org/OFL
|
||||||
|
|
||||||
|
|
||||||
|
-----------------------------------------------------------
|
||||||
|
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||||
|
-----------------------------------------------------------
|
||||||
|
|
||||||
|
PREAMBLE
|
||||||
|
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||||
|
development of collaborative font projects, to support the font creation
|
||||||
|
efforts of academic and linguistic communities, and to provide a free and
|
||||||
|
open framework in which fonts may be shared and improved in partnership
|
||||||
|
with others.
|
||||||
|
|
||||||
|
The OFL allows the licensed fonts to be used, studied, modified and
|
||||||
|
redistributed freely as long as they are not sold by themselves. The
|
||||||
|
fonts, including any derivative works, can be bundled, embedded,
|
||||||
|
redistributed and/or sold with any software provided that any reserved
|
||||||
|
names are not used by derivative works. The fonts and derivatives,
|
||||||
|
however, cannot be released under any other type of license. The
|
||||||
|
requirement for fonts to remain under this license does not apply
|
||||||
|
to any document created using the fonts or their derivatives.
|
||||||
|
|
||||||
|
DEFINITIONS
|
||||||
|
"Font Software" refers to the set of files released by the Copyright
|
||||||
|
Holder(s) under this license and clearly marked as such. This may
|
||||||
|
include source files, build scripts and documentation.
|
||||||
|
|
||||||
|
"Reserved Font Name" refers to any names specified as such after the
|
||||||
|
copyright statement(s).
|
||||||
|
|
||||||
|
"Original Version" refers to the collection of Font Software components as
|
||||||
|
distributed by the Copyright Holder(s).
|
||||||
|
|
||||||
|
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||||
|
or substituting -- in part or in whole -- any of the components of the
|
||||||
|
Original Version, by changing formats or by porting the Font Software to a
|
||||||
|
new environment.
|
||||||
|
|
||||||
|
"Author" refers to any designer, engineer, programmer, technical
|
||||||
|
writer or other person who contributed to the Font Software.
|
||||||
|
|
||||||
|
PERMISSION & CONDITIONS
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||||
|
redistribute, and sell modified and unmodified copies of the Font
|
||||||
|
Software, subject to the following conditions:
|
||||||
|
|
||||||
|
1) Neither the Font Software nor any of its individual components,
|
||||||
|
in Original or Modified Versions, may be sold by itself.
|
||||||
|
|
||||||
|
2) Original or Modified Versions of the Font Software may be bundled,
|
||||||
|
redistributed and/or sold with any software, provided that each copy
|
||||||
|
contains the above copyright notice and this license. These can be
|
||||||
|
included either as stand-alone text files, human-readable headers or
|
||||||
|
in the appropriate machine-readable metadata fields within text or
|
||||||
|
binary files as long as those fields can be easily viewed by the user.
|
||||||
|
|
||||||
|
3) No Modified Version of the Font Software may use the Reserved Font
|
||||||
|
Name(s) unless explicit written permission is granted by the corresponding
|
||||||
|
Copyright Holder. This restriction only applies to the primary font name as
|
||||||
|
presented to the users.
|
||||||
|
|
||||||
|
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||||
|
Software shall not be used to promote, endorse or advertise any
|
||||||
|
Modified Version, except to acknowledge the contribution(s) of the
|
||||||
|
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||||
|
permission.
|
||||||
|
|
||||||
|
5) The Font Software, modified or unmodified, in part or in whole,
|
||||||
|
must be distributed entirely under this license, and must not be
|
||||||
|
distributed under any other license. The requirement for fonts to
|
||||||
|
remain under this license does not apply to any document created
|
||||||
|
using the Font Software.
|
||||||
|
|
||||||
|
TERMINATION
|
||||||
|
This license becomes null and void if any of the above conditions are
|
||||||
|
not met.
|
||||||
|
|
||||||
|
DISCLAIMER
|
||||||
|
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||||
|
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||||
|
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||||
|
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||||
|
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
@@ -0,0 +1,584 @@
|
|||||||
|
/* servercow theme for mailcow, based on the servercow customer portal */
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Poppins';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
font-display: swap;
|
||||||
|
src: local(''),
|
||||||
|
url('/css/themes/servercow/fonts/poppins-v24-latin_latin-ext-regular.woff2') format('woff2'),
|
||||||
|
url('/css/themes/servercow/fonts/poppins-v24-latin_latin-ext-regular.woff') format('woff');
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Poppins';
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 400;
|
||||||
|
font-display: swap;
|
||||||
|
src: local(''),
|
||||||
|
url('/css/themes/servercow/fonts/poppins-v24-latin_latin-ext-italic.woff2') format('woff2'),
|
||||||
|
url('/css/themes/servercow/fonts/poppins-v24-latin_latin-ext-italic.woff') format('woff');
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Poppins';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 500;
|
||||||
|
font-display: swap;
|
||||||
|
src: local(''),
|
||||||
|
url('/css/themes/servercow/fonts/poppins-v24-latin_latin-ext-500.woff2') format('woff2'),
|
||||||
|
url('/css/themes/servercow/fonts/poppins-v24-latin_latin-ext-500.woff') format('woff');
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Poppins';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 600;
|
||||||
|
font-display: swap;
|
||||||
|
src: local(''),
|
||||||
|
url('/css/themes/servercow/fonts/poppins-v24-latin_latin-ext-600.woff2') format('woff2'),
|
||||||
|
url('/css/themes/servercow/fonts/poppins-v24-latin_latin-ext-600.woff') format('woff');
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Roboto Slab';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
font-display: swap;
|
||||||
|
src: local(''),
|
||||||
|
url('/css/themes/servercow/fonts/roboto-slab-v36-cyrillic_greek_latin-regular.woff2') format('woff2'),
|
||||||
|
url('/css/themes/servercow/fonts/roboto-slab-v36-cyrillic_greek_latin-regular.woff') format('woff');
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Roboto Slab';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 500;
|
||||||
|
font-display: swap;
|
||||||
|
src: local(''),
|
||||||
|
url('/css/themes/servercow/fonts/roboto-slab-v36-cyrillic_greek_latin-500.woff2') format('woff2'),
|
||||||
|
url('/css/themes/servercow/fonts/roboto-slab-v36-cyrillic_greek_latin-500.woff') format('woff');
|
||||||
|
}
|
||||||
|
|
||||||
|
/* tokens */
|
||||||
|
:root {
|
||||||
|
--sc-brown: #6F5D49;
|
||||||
|
--sc-brown-hover: #5e4f3e;
|
||||||
|
--sc-brown-active: #594a3a;
|
||||||
|
--sc-brown-soft: rgba(111, 93, 73, .2);
|
||||||
|
--sc-brown-tint: rgba(111, 93, 73, .08);
|
||||||
|
--sc-link: #6F5D49;
|
||||||
|
--sc-green: #8AD608;
|
||||||
|
--sc-blue: #96D0EC;
|
||||||
|
--sc-warning: #e3950b;
|
||||||
|
--sc-danger: #ef6f6f;
|
||||||
|
--sc-danger-hover: #e85e5e;
|
||||||
|
--sc-danger-text: #c43c3c;
|
||||||
|
--sc-ink: #212529;
|
||||||
|
--sc-muted: #6c757d;
|
||||||
|
--sc-placeholder: #6c757d;
|
||||||
|
--sc-bg: #F6F8F8;
|
||||||
|
--sc-surface: #ffffff;
|
||||||
|
--sc-cap: #f7f7f7;
|
||||||
|
--sc-addon: #e9ecef;
|
||||||
|
--sc-line: #ced4da;
|
||||||
|
--sc-divider: #dee2e6;
|
||||||
|
--sc-cap-line: rgba(0, 0, 0, .175);
|
||||||
|
--sc-stripe: rgba(111, 93, 73, .04);
|
||||||
|
--sc-hover: rgba(111, 93, 73, .08);
|
||||||
|
--sc-focus-border: #b7aea4;
|
||||||
|
--sc-focus: rgba(111, 93, 73, .25);
|
||||||
|
--sc-shadow: 0 0 10px 0 rgba(134, 134, 134, .3);
|
||||||
|
--sc-font: 'Poppins', system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||||
|
--sc-font-head: 'Roboto Slab', Georgia, serif;
|
||||||
|
--bs-body-color: #212529;
|
||||||
|
--bs-body-bg: #F6F8F8;
|
||||||
|
--bs-body-font-family: var(--sc-font);
|
||||||
|
/* core tokens, see css/build/014-mailcow.css */
|
||||||
|
--mc-font-family: var(--sc-font);
|
||||||
|
--mc-font-size: 10pt;
|
||||||
|
--mc-body-bg: var(--sc-bg);
|
||||||
|
--mc-primary: var(--sc-brown);
|
||||||
|
--mc-primary-border: var(--sc-brown);
|
||||||
|
--mc-info: var(--sc-blue);
|
||||||
|
--mc-info-border: var(--sc-blue);
|
||||||
|
--mc-border: var(--sc-divider);
|
||||||
|
--mc-surface: var(--sc-surface);
|
||||||
|
--mc-surface-alt: var(--sc-cap);
|
||||||
|
--mc-muted: var(--sc-muted);
|
||||||
|
--mc-hr: var(--sc-divider);
|
||||||
|
--mc-progress-bg: var(--sc-addon);
|
||||||
|
--mc-placeholder: var(--sc-placeholder);
|
||||||
|
--mc-btn-outline-color: var(--sc-ink);
|
||||||
|
--mc-btn-outline-border: var(--sc-line);
|
||||||
|
--mc-btn-outline-hover-bg: var(--sc-brown-tint);
|
||||||
|
--mc-btn-outline-active-bg: var(--sc-brown);
|
||||||
|
--mc-btn-light-active-color: #fff;
|
||||||
|
--mc-btn-light-active-bg: var(--sc-brown);
|
||||||
|
--mc-btn-light-active-border: var(--sc-brown);
|
||||||
|
--mc-dtr-bg: var(--sc-surface);
|
||||||
|
--mc-dtr-border: var(--sc-line);
|
||||||
|
--mc-dtr-color: var(--sc-brown);
|
||||||
|
--mc-dtr-hover-bg: var(--sc-brown-tint);
|
||||||
|
--mc-dtr-active-bg: var(--sc-brown-soft);
|
||||||
|
--mc-login-user: var(--sc-green);
|
||||||
|
--mc-login-domainadmin: #f5c400;
|
||||||
|
--mc-login-admin: var(--sc-danger);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* base */
|
||||||
|
body {
|
||||||
|
color: var(--sc-ink);
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
}
|
||||||
|
h1, h2, h3, h4, h5, h6, .h1, .h2, .h3, .h4, .h5, .h6, legend, .modal-title {
|
||||||
|
font-family: var(--sc-font-head);
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--sc-ink);
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
color: var(--sc-link);
|
||||||
|
}
|
||||||
|
a:hover {
|
||||||
|
color: var(--sc-brown-hover);
|
||||||
|
}
|
||||||
|
.text-muted, .text-secondary {
|
||||||
|
color: var(--sc-muted) !important;
|
||||||
|
}
|
||||||
|
::selection {
|
||||||
|
background: var(--sc-brown-soft);
|
||||||
|
}
|
||||||
|
hr {
|
||||||
|
border-color: var(--sc-divider);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
::placeholder {
|
||||||
|
color: var(--sc-placeholder) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* navbar, white with green line */
|
||||||
|
.navbar.bg-light {
|
||||||
|
background-color: var(--sc-surface) !important;
|
||||||
|
border: 0;
|
||||||
|
border-bottom: 5px solid var(--sc-green);
|
||||||
|
}
|
||||||
|
.navbar-light .navbar-nav .nav-link {
|
||||||
|
color: var(--sc-ink);
|
||||||
|
}
|
||||||
|
.navbar-light .navbar-nav .nav-link:hover,
|
||||||
|
.navbar-light .navbar-nav .nav-link:focus,
|
||||||
|
.navbar-light .navbar-nav .nav-link.active,
|
||||||
|
.navbar-light .navbar-nav .show > .nav-link {
|
||||||
|
color: var(--sc-brown);
|
||||||
|
}
|
||||||
|
@media (min-width: 992px) {
|
||||||
|
.navbar-nav .nav-link {
|
||||||
|
height: 60px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.dropdown-menu {
|
||||||
|
border: 1px solid var(--sc-cap-line);
|
||||||
|
border-radius: 0;
|
||||||
|
box-shadow: var(--sc-shadow);
|
||||||
|
}
|
||||||
|
.dropdown-item {
|
||||||
|
color: var(--sc-ink);
|
||||||
|
}
|
||||||
|
.dropdown-item:hover, .dropdown-item:focus {
|
||||||
|
color: var(--sc-brown) !important;
|
||||||
|
background-color: var(--sc-brown-soft);
|
||||||
|
}
|
||||||
|
.dropdown-item.active, .dropdown-item:active {
|
||||||
|
color: #fff;
|
||||||
|
background-color: var(--sc-brown);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* cards, white with soft shadow */
|
||||||
|
.card {
|
||||||
|
border: 0 !important;
|
||||||
|
border-radius: 0;
|
||||||
|
background-color: var(--sc-surface);
|
||||||
|
box-shadow: var(--sc-shadow);
|
||||||
|
}
|
||||||
|
.card-header, .card-header:first-child {
|
||||||
|
font-family: var(--sc-font-head);
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
color: var(--sc-ink);
|
||||||
|
background-color: var(--sc-cap);
|
||||||
|
border-bottom: 1px solid var(--sc-cap-line);
|
||||||
|
border-radius: 0;
|
||||||
|
padding: .5rem 1rem;
|
||||||
|
}
|
||||||
|
.card-header .btn, .card-header .form-control, .card-header .form-select {
|
||||||
|
font-family: var(--sc-font);
|
||||||
|
font-size: .875rem;
|
||||||
|
}
|
||||||
|
.card-footer {
|
||||||
|
background-color: var(--sc-cap);
|
||||||
|
border-top: 1px solid var(--sc-cap-line);
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
.card .card {
|
||||||
|
box-shadow: none;
|
||||||
|
border: 1px solid var(--sc-divider) !important;
|
||||||
|
}
|
||||||
|
/* colored headers become a top line */
|
||||||
|
.card:has(> .card-header.bg-danger) {
|
||||||
|
border-top: 4px solid var(--sc-danger) !important;
|
||||||
|
}
|
||||||
|
.card-header.bg-danger, .card-header.bg-success, .card-header.bg-primary, .card-header.bg-info {
|
||||||
|
color: var(--sc-ink) !important;
|
||||||
|
background-color: var(--sc-cap) !important;
|
||||||
|
}
|
||||||
|
.list-group-item {
|
||||||
|
border-color: var(--sc-divider);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* modals */
|
||||||
|
.modal-content {
|
||||||
|
border: 0;
|
||||||
|
border-radius: 0;
|
||||||
|
box-shadow: 0 8px 40px rgba(0, 0, 0, .2);
|
||||||
|
}
|
||||||
|
.modal-header {
|
||||||
|
background-color: var(--sc-cap);
|
||||||
|
border-bottom: 1px solid var(--sc-cap-line);
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
.modal-footer {
|
||||||
|
border-top: 1px solid var(--sc-divider);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* buttons, flat and square */
|
||||||
|
.btn {
|
||||||
|
border-width: 1px;
|
||||||
|
border-radius: 0;
|
||||||
|
font-weight: 400;
|
||||||
|
transition: none;
|
||||||
|
}
|
||||||
|
.btn:not(.disabled):hover,
|
||||||
|
.btn:not(.disabled):active {
|
||||||
|
margin-top: 0;
|
||||||
|
border-bottom-width: 1px;
|
||||||
|
}
|
||||||
|
.btn:focus, .btn-check:focus + .btn {
|
||||||
|
box-shadow: 0 0 0 .2rem var(--sc-focus);
|
||||||
|
}
|
||||||
|
.btn-primary, .btn-primary:hover, .btn-primary:focus, .btn-primary:active {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.btn-primary:hover, .btn-primary:focus, .btn-primary:active {
|
||||||
|
background-color: var(--sc-brown-hover) !important;
|
||||||
|
border-color: var(--sc-brown-active) !important;
|
||||||
|
}
|
||||||
|
/* main call to action, portal primary */
|
||||||
|
.btn-success, .btn-success:not(.disabled):hover, .btn-success:not(.disabled):active, .btn-success:focus,
|
||||||
|
.btn-success:disabled, .btn-success.disabled {
|
||||||
|
color: #fff;
|
||||||
|
background-color: var(--sc-brown);
|
||||||
|
border-color: var(--sc-brown);
|
||||||
|
}
|
||||||
|
.btn-success:hover, .btn-success:active, .btn-success:focus {
|
||||||
|
background-color: var(--sc-brown-hover);
|
||||||
|
border-color: var(--sc-brown-active);
|
||||||
|
}
|
||||||
|
.btn-danger, .btn-danger:not(.disabled):hover, .btn-danger:not(.disabled):active, .btn-danger:focus,
|
||||||
|
.btn-danger:disabled, .btn-danger.disabled {
|
||||||
|
color: #000;
|
||||||
|
background-color: var(--sc-danger);
|
||||||
|
border-color: var(--sc-danger);
|
||||||
|
}
|
||||||
|
.btn-danger:hover, .btn-danger:active, .btn-danger:focus {
|
||||||
|
background-color: var(--sc-danger-hover);
|
||||||
|
border-color: var(--sc-danger-hover);
|
||||||
|
}
|
||||||
|
.btn-warning, .btn-warning:not(.disabled):hover, .btn-warning:not(.disabled):active, .btn-warning:focus {
|
||||||
|
color: #000;
|
||||||
|
background-color: var(--sc-warning);
|
||||||
|
border-color: var(--sc-warning);
|
||||||
|
}
|
||||||
|
.bg-info, .alert-info, .btn-info, .badge.bg-info > a {
|
||||||
|
color: #000 !important;
|
||||||
|
}
|
||||||
|
.btn-secondary, .btn-secondary:not(.disabled):hover, .btn-secondary:not(.disabled):active, .btn-secondary:focus,
|
||||||
|
.btn-light, .btn-light:not(.disabled):hover, .btn-light:not(.disabled):active, .btn-light:focus {
|
||||||
|
color: var(--sc-ink);
|
||||||
|
background-color: var(--sc-surface);
|
||||||
|
border-color: var(--sc-line);
|
||||||
|
}
|
||||||
|
.btn-secondary:hover, .btn-secondary:active, .btn-secondary:focus,
|
||||||
|
.btn-light:hover, .btn-light:focus {
|
||||||
|
color: var(--sc-brown);
|
||||||
|
background-color: var(--sc-brown-tint);
|
||||||
|
border-color: var(--sc-line);
|
||||||
|
}
|
||||||
|
.btn-check:checked + .btn-secondary, .btn-secondary.active, .show > .btn-secondary.dropdown-toggle {
|
||||||
|
color: #fff;
|
||||||
|
background-color: var(--sc-brown);
|
||||||
|
border-color: var(--sc-brown);
|
||||||
|
}
|
||||||
|
.btn-check:checked + .btn-outline-secondary, .btn-outline-secondary.active {
|
||||||
|
color: #fff !important;
|
||||||
|
border-color: var(--sc-brown) !important;
|
||||||
|
}
|
||||||
|
.btn-outline-primary {
|
||||||
|
color: var(--sc-brown);
|
||||||
|
border-color: var(--sc-brown);
|
||||||
|
}
|
||||||
|
.btn-outline-primary:hover {
|
||||||
|
color: #fff;
|
||||||
|
background-color: var(--sc-brown);
|
||||||
|
border-color: var(--sc-brown);
|
||||||
|
}
|
||||||
|
/* active toggle, e.g. tagging options */
|
||||||
|
.btn-dark, .btn-dark:hover, .btn-dark:focus, .btn-dark:active {
|
||||||
|
color: #fff;
|
||||||
|
background-color: var(--sc-brown);
|
||||||
|
border-color: var(--sc-brown);
|
||||||
|
}
|
||||||
|
.btn-xs, .btn-group-xs > .btn {
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* forms */
|
||||||
|
.form-control, .form-select {
|
||||||
|
border-color: var(--sc-line);
|
||||||
|
border-radius: 0;
|
||||||
|
box-shadow: none;
|
||||||
|
color: var(--sc-ink);
|
||||||
|
background-color: var(--sc-surface);
|
||||||
|
}
|
||||||
|
.form-control:focus, .form-select:focus {
|
||||||
|
border-color: var(--sc-focus-border);
|
||||||
|
box-shadow: 0 0 0 .25rem var(--sc-focus);
|
||||||
|
}
|
||||||
|
.form-control::file-selector-button {
|
||||||
|
color: var(--sc-ink);
|
||||||
|
background-color: var(--sc-addon);
|
||||||
|
border-color: var(--sc-line);
|
||||||
|
}
|
||||||
|
.form-control:hover:not(:disabled):not([readonly])::file-selector-button {
|
||||||
|
color: var(--sc-brown);
|
||||||
|
background-color: var(--sc-brown-soft);
|
||||||
|
}
|
||||||
|
.input-group-text {
|
||||||
|
color: var(--sc-ink);
|
||||||
|
background-color: var(--sc-addon);
|
||||||
|
border-color: var(--sc-line);
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
.input-group > :not(:first-child), .input-group > :not(:last-child) {
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
.form-check-input {
|
||||||
|
border-color: var(--sc-line);
|
||||||
|
}
|
||||||
|
.form-check-input:checked {
|
||||||
|
background-color: var(--sc-brown);
|
||||||
|
border-color: var(--sc-brown);
|
||||||
|
}
|
||||||
|
.form-check-input:focus {
|
||||||
|
border-color: var(--sc-focus-border);
|
||||||
|
box-shadow: 0 0 0 .25rem var(--sc-focus);
|
||||||
|
}
|
||||||
|
.bootstrap-select > .dropdown-toggle {
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
.bootstrap-select > .dropdown-toggle.bs-placeholder,
|
||||||
|
.bootstrap-select > .dropdown-toggle.bs-placeholder:hover,
|
||||||
|
.bootstrap-select > .dropdown-toggle.bs-placeholder:focus,
|
||||||
|
.bootstrap-select > .dropdown-toggle.bs-placeholder:active {
|
||||||
|
color: var(--mc-placeholder);
|
||||||
|
}
|
||||||
|
.bootstrap-select .dropdown-menu li a.active, .bootstrap-select .dropdown-menu li a:active {
|
||||||
|
color: #fff;
|
||||||
|
background-color: var(--sc-brown);
|
||||||
|
}
|
||||||
|
div.numberedtextarea-number {
|
||||||
|
color: var(--sc-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* nav tabs */
|
||||||
|
.nav-tabs {
|
||||||
|
border-bottom: 1px solid var(--sc-divider);
|
||||||
|
}
|
||||||
|
.nav-tabs .nav-link,
|
||||||
|
.nav-tabs .nav-link.disabled, .nav-tabs .nav-link.disabled:hover, .nav-tabs .nav-link.disabled:focus {
|
||||||
|
margin-top: 0;
|
||||||
|
color: var(--sc-ink);
|
||||||
|
border: 0;
|
||||||
|
border-bottom: 3px solid transparent;
|
||||||
|
border-radius: 0;
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
|
.nav-tabs .nav-link:not(.disabled):hover, .nav-tabs .nav-link:not(.disabled):focus {
|
||||||
|
padding-bottom: .5rem;
|
||||||
|
color: var(--sc-brown);
|
||||||
|
}
|
||||||
|
.nav-tabs .nav-link.active, .nav-tabs .nav-item.show .nav-link,
|
||||||
|
.nav-tabs .nav-link:not(.disabled).active {
|
||||||
|
padding-bottom: .5rem;
|
||||||
|
color: var(--sc-brown);
|
||||||
|
background: none;
|
||||||
|
border-bottom: 3px solid var(--sc-brown);
|
||||||
|
}
|
||||||
|
.nav-pills .nav-link {
|
||||||
|
border-radius: 0;
|
||||||
|
color: var(--sc-ink);
|
||||||
|
}
|
||||||
|
.nav-pills .nav-link.active, .nav-pills .show > .nav-link {
|
||||||
|
background-color: var(--sc-brown);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* tables */
|
||||||
|
.table {
|
||||||
|
--bs-table-bg: transparent;
|
||||||
|
--bs-table-striped-bg: var(--sc-stripe);
|
||||||
|
--bs-table-striped-color: var(--sc-ink);
|
||||||
|
--bs-table-hover-color: var(--sc-ink);
|
||||||
|
--bs-table-hover-bg: var(--sc-hover);
|
||||||
|
color: var(--sc-ink);
|
||||||
|
border-color: var(--sc-divider);
|
||||||
|
}
|
||||||
|
.table > thead > tr > th, table.dataTable thead th {
|
||||||
|
color: var(--sc-ink);
|
||||||
|
font-weight: 600;
|
||||||
|
border-bottom: 1px solid var(--sc-line);
|
||||||
|
}
|
||||||
|
table.dataTable.table-striped > tbody > tr.odd > * {
|
||||||
|
box-shadow: inset 0 0 0 9999px var(--sc-stripe);
|
||||||
|
}
|
||||||
|
table.dataTable.table-hover > tbody > tr:hover > * {
|
||||||
|
box-shadow: inset 0 0 0 9999px var(--sc-hover);
|
||||||
|
}
|
||||||
|
.table > :not(:first-child) {
|
||||||
|
border-top: 0;
|
||||||
|
}
|
||||||
|
.dataTables_wrapper .dataTables_filter input,
|
||||||
|
.dataTables_wrapper .dataTables_length select {
|
||||||
|
border: 1px solid var(--sc-line);
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
.pagination > li > a, .pagination > li > span, .pagination .page-link,
|
||||||
|
.pagination > li > a:hover, .pagination > li > a:focus, .pagination > li > a:active {
|
||||||
|
top: 0;
|
||||||
|
color: var(--sc-brown);
|
||||||
|
font-weight: 400;
|
||||||
|
text-transform: none;
|
||||||
|
background-color: var(--sc-surface);
|
||||||
|
border: 1px solid var(--sc-divider);
|
||||||
|
border-radius: 0 !important;
|
||||||
|
}
|
||||||
|
.pagination > li > a:hover, .pagination > li > a:focus {
|
||||||
|
background-color: var(--sc-brown-tint);
|
||||||
|
}
|
||||||
|
.pagination > .disabled > a, .pagination > .disabled > a:hover, .pagination > .disabled > a:active,
|
||||||
|
.page-item.disabled .page-link {
|
||||||
|
color: var(--sc-muted);
|
||||||
|
background-color: var(--sc-surface);
|
||||||
|
border-width: 1px;
|
||||||
|
border-color: var(--sc-divider);
|
||||||
|
}
|
||||||
|
.page-item.active .page-link {
|
||||||
|
color: #fff;
|
||||||
|
background-color: var(--sc-brown);
|
||||||
|
border-color: var(--sc-brown);
|
||||||
|
}
|
||||||
|
.breadcrumb {
|
||||||
|
border: 1px solid var(--sc-divider);
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* alerts like the portal */
|
||||||
|
.alert {
|
||||||
|
border: 1px solid;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
.alert-primary {
|
||||||
|
color: #43382c !important;
|
||||||
|
background-color: #e2dfdb !important;
|
||||||
|
border-color: #d4cec8 !important;
|
||||||
|
}
|
||||||
|
.alert-info {
|
||||||
|
color: #3c535e !important;
|
||||||
|
background-color: #eaf6fb !important;
|
||||||
|
border-color: #e0f1f9 !important;
|
||||||
|
}
|
||||||
|
.alert-success {
|
||||||
|
color: #375603;
|
||||||
|
background-color: #e8f7ce;
|
||||||
|
border-color: #dcf3b5;
|
||||||
|
}
|
||||||
|
.alert-danger {
|
||||||
|
color: #8f4343;
|
||||||
|
background-color: #fce2e2;
|
||||||
|
border-color: #fad4d4;
|
||||||
|
}
|
||||||
|
.alert-warning {
|
||||||
|
color: #885907;
|
||||||
|
background-color: #f9eace;
|
||||||
|
border-color: #f7dfb6;
|
||||||
|
}
|
||||||
|
.alert-secondary, .alert-light {
|
||||||
|
color: var(--sc-ink);
|
||||||
|
background-color: var(--sc-cap);
|
||||||
|
border-color: var(--sc-divider);
|
||||||
|
}
|
||||||
|
.alert .alert-link {
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
.alert .btn-close {
|
||||||
|
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000'%3e%3cpath d='M.293.293a1 1 0 0 1 1.414 0L8 6.586 14.293.293a1 1 0 1 1 1.414 1.414L9.414 8l6.293 6.293a1 1 0 0 1-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 0 1-1.414-1.414L6.586 8 .293 1.707a1 1 0 0 1 0-1.414z'/%3e%3c/svg%3e");
|
||||||
|
opacity: .5;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* badges and semantic colors, readable text on top */
|
||||||
|
.badge {
|
||||||
|
font-weight: 500;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
.bg-success {
|
||||||
|
color: #000 !important;
|
||||||
|
background-color: var(--sc-green) !important;
|
||||||
|
}
|
||||||
|
.bg-warning {
|
||||||
|
color: #000 !important;
|
||||||
|
background-color: var(--sc-warning) !important;
|
||||||
|
}
|
||||||
|
.bg-danger {
|
||||||
|
color: #000 !important;
|
||||||
|
background-color: var(--sc-danger) !important;
|
||||||
|
}
|
||||||
|
.badge.bg-secondary, .badge.bg-light {
|
||||||
|
color: var(--sc-ink);
|
||||||
|
background-color: var(--sc-addon) !important;
|
||||||
|
}
|
||||||
|
.text-success {
|
||||||
|
color: #4f8a0b !important;
|
||||||
|
}
|
||||||
|
.text-danger {
|
||||||
|
color: var(--sc-danger-text) !important;
|
||||||
|
}
|
||||||
|
.text-warning {
|
||||||
|
color: #9a6100 !important;
|
||||||
|
}
|
||||||
|
.progress {
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
.progress-bar {
|
||||||
|
color: #000;
|
||||||
|
background-color: var(--sc-green);
|
||||||
|
}
|
||||||
|
.progress-bar.bg-danger, .progress-bar-danger {
|
||||||
|
color: #000;
|
||||||
|
background-color: var(--sc-danger) !important;
|
||||||
|
}
|
||||||
|
.progress-bar-warning {
|
||||||
|
color: #000;
|
||||||
|
background-color: var(--sc-warning) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* footer */
|
||||||
|
.footer, .footer a {
|
||||||
|
color: var(--sc-muted);
|
||||||
|
}
|
||||||
|
.footer hr {
|
||||||
|
border-color: var(--sc-divider);
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"name": "servercow",
|
||||||
|
"description": "Based on the servercow theme"
|
||||||
|
}
|
||||||
@@ -380,13 +380,13 @@ if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "adm
|
|||||||
else {
|
else {
|
||||||
$state = state_nomatch;
|
$state = state_nomatch;
|
||||||
}
|
}
|
||||||
$state .= '<br />' . $current[$data_field[$current['type']]];
|
$state .= '<br />' . htmlspecialchars($current[$data_field[$current['type']]]);
|
||||||
}
|
}
|
||||||
if ($current['type'] == 'TXT' &&
|
if ($current['type'] == 'TXT' &&
|
||||||
stripos($current['txt'], 'v=dmarc') === 0 &&
|
stripos($current['txt'], 'v=dmarc') === 0 &&
|
||||||
$record[2] == $dmarc_link) {
|
$record[2] == $dmarc_link) {
|
||||||
$current['txt'] = str_replace(' ', '', $current['txt']);
|
$current['txt'] = str_replace(' ', '', $current['txt']);
|
||||||
$state = $current[$data_field[$current['type']]] . state_optional;
|
$state = htmlspecialchars($current[$data_field[$current['type']]]) . state_optional;
|
||||||
}
|
}
|
||||||
elseif ($current['type'] == 'TXT' &&
|
elseif ($current['type'] == 'TXT' &&
|
||||||
stripos($current['txt'], 'v=spf') === 0 &&
|
stripos($current['txt'], 'v=spf') === 0 &&
|
||||||
@@ -396,7 +396,7 @@ if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "adm
|
|||||||
if (in_array($ip, $rslt) && in_array(expand_ipv6($ip6), $rslt)) {
|
if (in_array($ip, $rslt) && in_array(expand_ipv6($ip6), $rslt)) {
|
||||||
$state = state_good;
|
$state = state_good;
|
||||||
}
|
}
|
||||||
$state .= '<br />' . $current[$data_field[$current['type']]] . state_optional;
|
$state .= '<br />' . htmlspecialchars($current[$data_field[$current['type']]]) . state_optional;
|
||||||
}
|
}
|
||||||
elseif ($current['type'] == 'TXT' &&
|
elseif ($current['type'] == 'TXT' &&
|
||||||
stripos($current['txt'], 'v=dkim') === 0 &&
|
stripos($current['txt'], 'v=dkim') === 0 &&
|
||||||
@@ -426,7 +426,7 @@ if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "adm
|
|||||||
if ($state == state_nomatch) {
|
if ($state == state_nomatch) {
|
||||||
$state = array();
|
$state = array();
|
||||||
foreach ($currents as $current) {
|
foreach ($currents as $current) {
|
||||||
$state[] = $current[$data_field[$current['type']]];
|
$state[] = htmlspecialchars($current[$data_field[$current['type']]]);
|
||||||
}
|
}
|
||||||
$state = implode('<br />', $state);
|
$state = implode('<br />', $state);
|
||||||
}
|
}
|
||||||
@@ -436,12 +436,22 @@ if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "adm
|
|||||||
<td>%s</td>
|
<td>%s</td>
|
||||||
<td class="dns-found">%s</td>
|
<td class="dns-found">%s</td>
|
||||||
<td class="dns-recommended">%s</td>
|
<td class="dns-recommended">%s</td>
|
||||||
</tr>', $record[0], $record[1], $record[2], $state);
|
</tr>', htmlspecialchars($record[0]), htmlspecialchars($record[1]), $record[2], $state);
|
||||||
$record[3] = explode('<br />', $state);
|
$record[3] = explode('<br />', $state);
|
||||||
}
|
}
|
||||||
|
|
||||||
unset($record);
|
unset($record);
|
||||||
|
|
||||||
|
// Make a hostname RHS absolute so it is not read relative to $ORIGIN.
|
||||||
|
// Already-absolute names, the SRV root target "." and IP addresses are left alone.
|
||||||
|
$absolutize = function($host) {
|
||||||
|
$host = trim($host);
|
||||||
|
if ($host === '' || $host === '.' || substr($host, -1) === '.' || filter_var($host, FILTER_VALIDATE_IP)) {
|
||||||
|
return $host;
|
||||||
|
}
|
||||||
|
return $host . '.';
|
||||||
|
};
|
||||||
|
|
||||||
$dns_data = sprintf("\$ORIGIN %s.\n", $domain);
|
$dns_data = sprintf("\$ORIGIN %s.\n", $domain);
|
||||||
foreach ($records as $record) {
|
foreach ($records as $record) {
|
||||||
if ($domain == substr($record[0], -strlen($domain))) {
|
if ($domain == substr($record[0], -strlen($domain))) {
|
||||||
@@ -462,22 +472,32 @@ if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "adm
|
|||||||
$val = str_replace(state_optional, '', $val);
|
$val = str_replace(state_optional, '', $val);
|
||||||
$val = str_replace(state_good, '', $val);
|
$val = str_replace(state_good, '', $val);
|
||||||
if (strlen($val) > 0) {
|
if (strlen($val) > 0) {
|
||||||
|
// these are all TXT values, their RHS is a character string, not a name
|
||||||
$vals[] = sprintf("%s\tIN\t%s\t%s\n", $label, $record[1], $val);
|
$vals[] = sprintf("%s\tIN\t%s\t%s\n", $label, $record[1], $val);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
if ($record[1] == 'MX' || $record[1] == 'CNAME') {
|
||||||
|
$val = $absolutize($val);
|
||||||
|
}
|
||||||
|
elseif ($record[1] == 'SRV') {
|
||||||
|
// format here is "target port"; only the target is a name
|
||||||
|
$parts = explode(' ', $val, 2);
|
||||||
|
$parts[0] = $absolutize($parts[0]);
|
||||||
|
$val = implode(' ', $parts);
|
||||||
|
}
|
||||||
$vals[] = sprintf("%s\tIN\t%s\t%s\n", $label, $record[1], $val);
|
$vals[] = sprintf("%s\tIN\t%s\t%s\n", $label, $record[1], $val);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($vals as $val) {
|
foreach ($vals as $val) {
|
||||||
$dns_data .= str_replace($domain, $domain . '.', $val);
|
$dns_data .= $val;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
</table>
|
</table>
|
||||||
<a id='download-zonefile' class="btn btn-sm btn-secondary visible-xs-block visible-sm-inline visible-md-inline visible-lg-inline mb-4" style="margin-top:10px" data-zonefile="<?=base64_encode($dns_data);?>" download='<?=$_GET['domain'];?>.txt' type='text/csv'>Download</a>
|
<a id='download-zonefile' class="btn btn-sm btn-secondary visible-xs-block visible-sm-inline visible-md-inline visible-lg-inline mb-4" style="margin-top:10px" data-zonefile="<?=base64_encode($dns_data);?>" download='<?=htmlspecialchars($_GET['domain']);?>.txt' type='text/csv'>Download</a>
|
||||||
<script>
|
<script>
|
||||||
var zonefile_dl_link = document.getElementById('download-zonefile');
|
var zonefile_dl_link = document.getElementById('download-zonefile');
|
||||||
var zonefile = atob(zonefile_dl_link.getAttribute('data-zonefile'));
|
var zonefile = atob(zonefile_dl_link.getAttribute('data-zonefile'));
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -287,6 +287,8 @@ function user_login($user, $pass, $extra = null){
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$row['attributes'] = json_decode($row['attributes'], true);
|
||||||
|
|
||||||
// check for tfa authenticators
|
// check for tfa authenticators
|
||||||
$authenticators = get_tfa($user);
|
$authenticators = get_tfa($user);
|
||||||
if (isset($authenticators['additional']) && is_array($authenticators['additional']) && count($authenticators['additional']) > 0 && !$is_internal) {
|
if (isset($authenticators['additional']) && is_array($authenticators['additional']) && count($authenticators['additional']) > 0 && !$is_internal) {
|
||||||
@@ -343,6 +345,8 @@ function user_login($user, $pass, $extra = null){
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$row['attributes'] = json_decode($row['attributes'], true);
|
||||||
|
|
||||||
// check for tfa authenticators
|
// check for tfa authenticators
|
||||||
$authenticators = get_tfa($user);
|
$authenticators = get_tfa($user);
|
||||||
if (isset($authenticators['additional']) && is_array($authenticators['additional']) && count($authenticators['additional']) > 0 && !$is_internal) {
|
if (isset($authenticators['additional']) && is_array($authenticators['additional']) && count($authenticators['additional']) > 0 && !$is_internal) {
|
||||||
|
|||||||
@@ -233,6 +233,38 @@ function customize($_action, $_item, $_data = null) {
|
|||||||
'msg' => 'custom_login_modified'
|
'msg' => 'custom_login_modified'
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
case 'ui_theme':
|
||||||
|
$ui_theme = is_array($_data['ui_theme']) ? (string)reset($_data['ui_theme']) : (string)$_data['ui_theme'];
|
||||||
|
if ($ui_theme !== '' && !array_key_exists($ui_theme, customize('get', 'ui_themes'))) {
|
||||||
|
$_SESSION['return'][] = array(
|
||||||
|
'type' => 'danger',
|
||||||
|
'log' => array(__FUNCTION__, $_action, $_item, $_data),
|
||||||
|
'msg' => 'ui_theme_invalid'
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if ($ui_theme === '') {
|
||||||
|
$redis->del('UI_THEME');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$redis->set('UI_THEME', $ui_theme);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (RedisException $e) {
|
||||||
|
$_SESSION['return'][] = array(
|
||||||
|
'type' => 'danger',
|
||||||
|
'log' => array(__FUNCTION__, $_action, $_item, $_data),
|
||||||
|
'msg' => array('redis_error', $e)
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$_SESSION['return'][] = array(
|
||||||
|
'type' => 'success',
|
||||||
|
'log' => array(__FUNCTION__, $_action, $_item, $_data),
|
||||||
|
'msg' => 'ui_theme_modified'
|
||||||
|
);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'delete':
|
case 'delete':
|
||||||
@@ -402,6 +434,42 @@ function customize($_action, $_item, $_data = null) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case 'ui_themes':
|
||||||
|
// theme folders in css/themes/<name>/ with a theme.css
|
||||||
|
$themes = array();
|
||||||
|
foreach (glob('/web/css/themes/*/theme.css') as $theme_css) {
|
||||||
|
$theme_dir = dirname($theme_css);
|
||||||
|
$theme = basename($theme_dir);
|
||||||
|
if (!preg_match('/^[a-zA-Z0-9_-]+$/', $theme)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$meta = json_decode(@file_get_contents($theme_dir . '/theme.json'), true);
|
||||||
|
$themes[$theme] = array(
|
||||||
|
'name' => (!empty($meta['name'])) ? $meta['name'] : $theme,
|
||||||
|
'description' => (!empty($meta['description'])) ? $meta['description'] : '',
|
||||||
|
'dark' => file_exists($theme_dir . '/dark.css')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
ksort($themes);
|
||||||
|
return $themes;
|
||||||
|
break;
|
||||||
|
case 'ui_theme':
|
||||||
|
try {
|
||||||
|
$ui_theme = $redis->get('UI_THEME');
|
||||||
|
}
|
||||||
|
catch (RedisException $e) {
|
||||||
|
$_SESSION['return'][] = array(
|
||||||
|
'type' => 'danger',
|
||||||
|
'log' => array(__FUNCTION__, $_action, $_item, $_data),
|
||||||
|
'msg' => array('redis_error', $e)
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (empty($ui_theme) || !preg_match('/^[a-zA-Z0-9_-]+$/', $ui_theme) || !file_exists('/web/css/themes/' . $ui_theme . '/theme.css')) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return $ui_theme;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -108,6 +108,14 @@ function fwdhost($_action, $_data = null) {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'delete':
|
case 'delete':
|
||||||
|
if ($_SESSION['mailcow_cc_role'] != "admin") {
|
||||||
|
$_SESSION['return'][] = array(
|
||||||
|
'type' => 'danger',
|
||||||
|
'log' => array(__FUNCTION__, $_action, $_data_log),
|
||||||
|
'msg' => 'access_denied'
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
$hosts = (array)$_data['forwardinghost'];
|
$hosts = (array)$_data['forwardinghost'];
|
||||||
foreach ($hosts as $host) {
|
foreach ($hosts as $host) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -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) {
|
||||||
@@ -558,7 +591,7 @@ function logger($_data = false) {
|
|||||||
$type = $return['type'];
|
$type = $return['type'];
|
||||||
$msg = null;
|
$msg = null;
|
||||||
if (isset($return['msg'])) {
|
if (isset($return['msg'])) {
|
||||||
$msg = json_encode($return['msg'], JSON_UNESCAPED_UNICODE);
|
$msg = json_encode($return['msg'], JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT);
|
||||||
}
|
}
|
||||||
$call = null;
|
$call = null;
|
||||||
if (isset($return['log'])) {
|
if (isset($return['log'])) {
|
||||||
@@ -600,6 +633,18 @@ function logger($_data = false) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
function is_local_mailcow_domain($domain) {
|
||||||
|
// True if domain is a locally managed, active primary or alias domain
|
||||||
|
global $pdo;
|
||||||
|
$domain = idn_to_ascii($domain, 0, INTL_IDNA_VARIANT_UTS46);
|
||||||
|
if (empty($domain)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$stmt = $pdo->prepare("SELECT 1 FROM `domain` WHERE `domain` = :d AND `active` = 1
|
||||||
|
UNION SELECT 1 FROM `alias_domain` WHERE `alias_domain` = :d2 AND `active` = 1 LIMIT 1");
|
||||||
|
$stmt->execute(array(':d' => $domain, ':d2' => $domain));
|
||||||
|
return (bool)$stmt->fetchColumn();
|
||||||
|
}
|
||||||
function hasDomainAccess($username, $role, $domain) {
|
function hasDomainAccess($username, $role, $domain) {
|
||||||
global $pdo;
|
global $pdo;
|
||||||
if (empty($domain) || !is_valid_domain_name($domain)) {
|
if (empty($domain) || !is_valid_domain_name($domain)) {
|
||||||
@@ -2283,10 +2328,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 +2342,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 +2393,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');
|
||||||
|
|
||||||
@@ -3503,7 +3563,16 @@ function protect_route($allowed_roles = ['admin', 'domainadmin', 'user'], $redir
|
|||||||
if (isset($redirects['unauthenticated'])) {
|
if (isset($redirects['unauthenticated'])) {
|
||||||
header('Location: ' . $redirects['unauthenticated']);
|
header('Location: ' . $redirects['unauthenticated']);
|
||||||
} else {
|
} else {
|
||||||
header('Location: /');
|
// Send a deep link to the login page for its area instead of the user login at /,
|
||||||
|
// e.g. /admin/dashboard -> /admin rather than /
|
||||||
|
$request_uri = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '/';
|
||||||
|
if (strpos($request_uri, '/admin/') === 0) {
|
||||||
|
header('Location: /admin');
|
||||||
|
} elseif (strpos($request_uri, '/domainadmin/') === 0) {
|
||||||
|
header('Location: /domainadmin');
|
||||||
|
} else {
|
||||||
|
header('Location: /');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
exit();
|
exit();
|
||||||
}
|
}
|
||||||
|
|||||||
Loaded 100 of 147 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user