mirror of
https://github.com/mailcow/mailcow-dockerized.git
synced 2026-02-13 21:00:03 +00:00
Compare commits
153 Commits
feat/pytho
...
feat/valke
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c0f06bfc52 | ||
|
|
79cf0abc6e | ||
|
|
7de70322d6 | ||
|
|
417835dea8 | ||
|
|
df4d3bb6e0 | ||
|
|
455ef084b4 | ||
|
|
c2948735f2 | ||
|
|
1ef0149076 | ||
|
|
922d173540 | ||
|
|
fd088cb504 | ||
|
|
721ee2394e | ||
|
|
c217be06c6 | ||
|
|
871c422ec1 | ||
|
|
3cc28af607 | ||
|
|
796e131c3a | ||
|
|
c51a769aec | ||
|
|
45a61755a5 | ||
|
|
769c57c355 | ||
|
|
2e7eb7c0fd | ||
|
|
4c83147d01 | ||
|
|
ca0bec4fc2 | ||
|
|
6f50dd17da | ||
|
|
4a331929d0 | ||
|
|
748bc893b6 | ||
|
|
e462602ddc | ||
|
|
4e0f435d12 | ||
|
|
46f0581936 | ||
|
|
20f04ecf6b | ||
|
|
ff43799763 | ||
|
|
85ca197615 | ||
|
|
d06d23bbaf | ||
|
|
702ed85dfd | ||
|
|
8abe74a562 | ||
|
|
5c5287ca21 | ||
|
|
ce219668cf | ||
|
|
5b1b49a418 | ||
|
|
8978a9ad79 | ||
|
|
5f4a4fd759 | ||
|
|
171c591da4 | ||
|
|
9133b9899c | ||
|
|
701c9fb1b4 | ||
|
|
eabd22188b | ||
|
|
7028619742 | ||
|
|
c915bf2ee2 | ||
|
|
011edd5ac9 | ||
|
|
7ba3de4ced | ||
|
|
8ead77083f | ||
|
|
b2774fb50b | ||
|
|
4440bd46ad | ||
|
|
28985973eb | ||
|
|
f2c4697ca3 | ||
|
|
383b5affb5 | ||
|
|
ed4dcff63b | ||
|
|
caca32bbba | ||
|
|
d31e74c778 | ||
|
|
6c00e29276 | ||
|
|
9940c503a2 | ||
|
|
4b2862cb3c | ||
|
|
a36485f0f1 | ||
|
|
78168ee80a | ||
|
|
610609378f | ||
|
|
260906e350 | ||
|
|
2891bbf82a | ||
|
|
eb26bcbc94 | ||
|
|
84e230de8f | ||
|
|
f67a12d157 | ||
|
|
34b48eedfc | ||
|
|
0d900d4fc8 | ||
|
|
642ac6d02c | ||
|
|
94c1a6c4e1 | ||
|
|
262fe04286 | ||
|
|
1c438330c6 | ||
|
|
8cb25709ae | ||
|
|
221f2989b0 | ||
|
|
3d05207bc7 | ||
|
|
8c8497d885 | ||
|
|
56d083ced4 | ||
|
|
a90b3544a7 | ||
|
|
08aea7fb26 | ||
|
|
13f7f9830b | ||
|
|
2f75039194 | ||
|
|
1e192e14f4 | ||
|
|
9cd1f931fc | ||
|
|
8d7235b535 | ||
|
|
8446abd484 | ||
|
|
f67c0530f5 | ||
|
|
06db1d6a72 | ||
|
|
81775ab4d5 | ||
|
|
34877ecf9c | ||
|
|
dbde144014 | ||
|
|
5361a4a4ee | ||
|
|
0997548d7f | ||
|
|
921de02a2b | ||
|
|
48e90a72dc | ||
|
|
c0b7a98e6c | ||
|
|
0b0a65a3f3 | ||
|
|
6c5d82c4df | ||
|
|
5e66ffa366 | ||
|
|
4d88e19106 | ||
|
|
29e28b47ed | ||
|
|
1cb38bacdb | ||
|
|
169aafec50 | ||
|
|
3826c4b5be | ||
|
|
e1410baaeb | ||
|
|
c39712af67 | ||
|
|
53c35493a5 | ||
|
|
af871fdacb | ||
|
|
2b93b59cdd | ||
|
|
2b2da1679e | ||
|
|
8cdb0b869e | ||
|
|
1e42b8dd21 | ||
|
|
842cb235b6 | ||
|
|
e91d678bd1 | ||
|
|
ef5739c32f | ||
|
|
88bf9b02e1 | ||
|
|
3803b5d351 | ||
|
|
14d58c8163 | ||
|
|
728fcdb375 | ||
|
|
1fc36263dc | ||
|
|
69420113f7 | ||
|
|
360fe03497 | ||
|
|
7557802933 | ||
|
|
2e9ba1e9b3 | ||
|
|
795bcdc5d2 | ||
|
|
ad9b328ed5 | ||
|
|
3d5b57889a | ||
|
|
6b8e981bdc | ||
|
|
2f1eb4b004 | ||
|
|
3ee3d7d969 | ||
|
|
95eb350f15 | ||
|
|
1e5fcfe392 | ||
|
|
02557b2098 | ||
|
|
4c7a9ed195 | ||
|
|
d5b30a7a08 | ||
|
|
b7acef4d9d | ||
|
|
fc43c26c48 | ||
|
|
b12ce1eacd | ||
|
|
ec6dbb099a | ||
|
|
2fbbbbe9a9 | ||
|
|
1e4f3c55d8 | ||
|
|
a0f5454c2a | ||
|
|
4e7adacda9 | ||
|
|
4c64cf18a6 | ||
|
|
8a89f5c685 | ||
|
|
cc0e4fee9d | ||
|
|
5861c9af29 | ||
|
|
dd475c0ab3 | ||
|
|
407e9d3584 | ||
|
|
d4f899b091 | ||
|
|
372923ae2f | ||
|
|
3bd01190bf | ||
|
|
0698159f07 | ||
|
|
c27000215e |
69
.github/ISSUE_TEMPLATE/Bug_report.yml
vendored
69
.github/ISSUE_TEMPLATE/Bug_report.yml
vendored
@@ -11,22 +11,35 @@ body:
|
||||
required: true
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: I've found a bug and checked that ...
|
||||
description: Prior to placing the issue, please check following:** *(fill out each checkbox with an `X` once done)*
|
||||
label: Checklist prior issue creation
|
||||
description: Prior to creating the issue...
|
||||
options:
|
||||
- label: ... I understand that not following the below instructions will result in immediate closure and/or deletion of my issue.
|
||||
- label: I understand that failure to follow below instructions may cause this issue to be closed.
|
||||
required: true
|
||||
- label: ... I have understood that this bug report is dedicated for bugs, and not for support-related inquiries.
|
||||
- label: I understand that vague, incomplete or inaccurate information may cause this issue to be closed.
|
||||
required: true
|
||||
- label: ... I have understood that answers are voluntary and community-driven, and not commercial support.
|
||||
- label: I understand that this form is intended solely for reporting software bugs and not for support-related inquiries.
|
||||
required: true
|
||||
- label: ... I have verified that my issue has not been already answered in the past. I also checked previous [issues](https://github.com/mailcow/mailcow-dockerized/issues).
|
||||
- label: I understand that all responses are voluntary and community-driven, and do not constitute commercial support.
|
||||
required: true
|
||||
- label: I confirm that I have reviewed previous [issues](https://github.com/mailcow/mailcow-dockerized/issues) to ensure this matter has not already been addressed.
|
||||
required: true
|
||||
- label: I confirm that my environment meets all [prerequisite requirements](https://docs.mailcow.email/getstarted/prerequisite-system/) as specified in the official documentation.
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Description
|
||||
description: Please provide a brief description of the bug in 1-2 sentences. If applicable, add screenshots to help explain your problem. Very useful for bugs in mailcow UI.
|
||||
render: plain text
|
||||
description: Please provide a brief description of the bug. If applicable, add screenshots to help explain your problem. (Very useful for bugs in mailcow UI.)
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: "Steps to reproduce:"
|
||||
description: "Please describe the steps to reproduce the bug. Screenshots can be added, if helpful."
|
||||
placeholder: |-
|
||||
1. ...
|
||||
2. ...
|
||||
3. ...
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
@@ -36,45 +49,36 @@ body:
|
||||
render: plain text
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: "Steps to reproduce:"
|
||||
description: "Please describe the steps to reproduce the bug. Screenshots can be added, if helpful."
|
||||
render: plain text
|
||||
placeholder: |-
|
||||
1. ...
|
||||
2. ...
|
||||
3. ...
|
||||
validations:
|
||||
required: true
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
## System information
|
||||
### In this stage we would kindly ask you to attach general system information about your setup.
|
||||
In this stage we would kindly ask you to attach general system information about your setup.
|
||||
- type: dropdown
|
||||
attributes:
|
||||
label: "Which branch are you using?"
|
||||
description: "#### `git rev-parse --abbrev-ref HEAD`"
|
||||
description: "#### Run: `git rev-parse --abbrev-ref HEAD`"
|
||||
multiple: false
|
||||
options:
|
||||
- master
|
||||
- master (stable)
|
||||
- staging
|
||||
- nightly
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
attributes:
|
||||
label: "Which architecture are you using?"
|
||||
description: "#### `uname -m`"
|
||||
description: "#### Run: `uname -m`"
|
||||
multiple: false
|
||||
options:
|
||||
- x86
|
||||
- x86_64
|
||||
- ARM64 (aarch64)
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
attributes:
|
||||
label: "Operating System:"
|
||||
description: "#### Run: `lsb_release -ds`"
|
||||
placeholder: "e.g. Ubuntu 22.04 LTS"
|
||||
validations:
|
||||
required: true
|
||||
@@ -93,43 +97,44 @@ body:
|
||||
- type: input
|
||||
attributes:
|
||||
label: "Virtualization technology:"
|
||||
placeholder: "KVM, VMware, Xen, etc - **LXC and OpenVZ are not supported**"
|
||||
description: "LXC and OpenVZ are not supported!"
|
||||
placeholder: "KVM, VMware ESXi, Xen, etc"
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
attributes:
|
||||
label: "Docker version:"
|
||||
description: "#### `docker version`"
|
||||
description: "#### Run: `docker version`"
|
||||
placeholder: "20.10.21"
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
attributes:
|
||||
label: "docker-compose version or docker compose version:"
|
||||
description: "#### `docker-compose version` or `docker compose version`"
|
||||
description: "#### Run: `docker-compose version` or `docker compose version`"
|
||||
placeholder: "v2.12.2"
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
attributes:
|
||||
label: "mailcow version:"
|
||||
description: "#### ```git describe --tags `git rev-list --tags --max-count=1` ```"
|
||||
placeholder: "2022-08"
|
||||
description: "#### Run: ```git describe --tags `git rev-list --tags --max-count=1` ```"
|
||||
placeholder: "2022-08x"
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
attributes:
|
||||
label: "Reverse proxy:"
|
||||
placeholder: "e.g. Nginx/Traefik"
|
||||
placeholder: "e.g. nginx/Traefik, or none"
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: "Logs of git diff:"
|
||||
description: "#### Output of `git diff origin/master`, any other changes to the code? If so, **please post them**:"
|
||||
description: "#### Output of `git diff origin/master`, any other changes to the code? Sanitize if needed. If so, **please post them**:"
|
||||
render: plain text
|
||||
validations:
|
||||
required: true
|
||||
required: false
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: "Logs of iptables -L -vn:"
|
||||
|
||||
@@ -14,7 +14,7 @@ jobs:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Mark/Close Stale Issues and Pull Requests 🗑️
|
||||
uses: actions/stale@v9.1.0
|
||||
uses: actions/stale@v10.1.0
|
||||
with:
|
||||
repo-token: ${{ secrets.STALE_ACTION_PAT }}
|
||||
days-before-stale: 60
|
||||
|
||||
2
.github/workflows/image_builds.yml
vendored
2
.github/workflows/image_builds.yml
vendored
@@ -27,7 +27,7 @@ jobs:
|
||||
- "watchdog-mailcow"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- name: Setup Docker
|
||||
run: |
|
||||
curl -sSL https://get.docker.com/ | CHANNEL=stable sudo sh
|
||||
|
||||
4
.github/workflows/pr_to_nightly.yml
vendored
4
.github/workflows/pr_to_nightly.yml
vendored
@@ -8,11 +8,11 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Run the Action
|
||||
uses: devops-infra/action-pull-request@v0.6.0
|
||||
uses: devops-infra/action-pull-request@v0.6.1
|
||||
with:
|
||||
github_token: ${{ secrets.PRTONIGHTLY_ACTION_PAT }}
|
||||
title: Automatic PR to nightly from ${{ github.event.repository.updated_at}}
|
||||
|
||||
2
.github/workflows/rebuild_backup_image.yml
vendored
2
.github/workflows/rebuild_backup_image.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
||||
packages: write
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
@@ -15,7 +15,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Generate postscreen_access.cidr
|
||||
run: |
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -75,3 +75,4 @@ refresh_images.sh
|
||||
update_diffs/
|
||||
create_cold_standby.sh
|
||||
!data/conf/nginx/mailcow_auth.conf
|
||||
data/conf/postfix/postfix-tlspol
|
||||
230
_modules/scripts/core.sh
Normal file
230
_modules/scripts/core.sh
Normal file
@@ -0,0 +1,230 @@
|
||||
#!/usr/bin/env bash
|
||||
# _modules/scripts/core.sh
|
||||
# THIS SCRIPT IS DESIGNED TO BE RUNNING BY MAILCOW SCRIPTS ONLY!
|
||||
# DO NOT, AGAIN, NOT TRY TO RUN THIS SCRIPT STANDALONE!!!!!!
|
||||
|
||||
# ANSI color for red errors
|
||||
RED='\e[31m'
|
||||
GREEN='\e[32m'
|
||||
YELLOW='\e[33m'
|
||||
BLUE='\e[34m'
|
||||
MAGENTA='\e[35m'
|
||||
LIGHT_RED='\e[91m'
|
||||
LIGHT_GREEN='\e[92m'
|
||||
NC='\e[0m'
|
||||
|
||||
caller="${BASH_SOURCE[1]##*/}"
|
||||
|
||||
get_installed_tools(){
|
||||
for bin in openssl curl docker git awk sha1sum grep cut jq; do
|
||||
if [[ -z $(command -v ${bin}) ]]; then
|
||||
echo "Error: Cannot find command '${bin}'. Cannot proceed."
|
||||
echo "Solution: Please review system requirements and install requirements. Then, re-run the script."
|
||||
echo "See System Requirements: https://docs.mailcow.email/getstarted/install/"
|
||||
echo "Exiting..."
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
if grep --help 2>&1 | head -n 1 | grep -q -i "busybox"; then echo -e "${LIGHT_RED}BusyBox grep detected, please install gnu grep, \"apk add --no-cache --upgrade grep\"${NC}"; exit 1; fi
|
||||
# This will also cover sort
|
||||
if cp --help 2>&1 | head -n 1 | grep -q -i "busybox"; then echo -e "${LIGHT_RED}BusyBox cp detected, please install coreutils, \"apk add --no-cache --upgrade coreutils\"${NC}"; exit 1; fi
|
||||
if sed --help 2>&1 | head -n 1 | grep -q -i "busybox"; then echo -e "${LIGHT_RED}BusyBox sed detected, please install gnu sed, \"apk add --no-cache --upgrade sed\"${NC}"; exit 1; fi
|
||||
}
|
||||
|
||||
get_docker_version(){
|
||||
# Check Docker Version (need at least 24.X)
|
||||
docker_version=$(docker version --format '{{.Server.Version}}' | cut -d '.' -f 1)
|
||||
}
|
||||
|
||||
get_compose_type(){
|
||||
if docker compose > /dev/null 2>&1; then
|
||||
if docker compose version --short | grep -e "^2." -e "^v2." > /dev/null 2>&1; then
|
||||
COMPOSE_VERSION=native
|
||||
COMPOSE_COMMAND="docker compose"
|
||||
if [[ "$caller" == "update.sh" ]]; then
|
||||
sed -i 's/^DOCKER_COMPOSE_VERSION=.*/DOCKER_COMPOSE_VERSION=native/' "$SCRIPT_DIR/mailcow.conf"
|
||||
fi
|
||||
echo -e "\e[33mFound Docker Compose Plugin (native).\e[0m"
|
||||
echo -e "\e[33mSetting the DOCKER_COMPOSE_VERSION Variable to native\e[0m"
|
||||
sleep 2
|
||||
echo -e "\e[33mNotice: You'll have to update this Compose Version via your Package Manager manually!\e[0m"
|
||||
else
|
||||
echo -e "\e[31mCannot find Docker Compose with a Version Higher than 2.X.X.\e[0m"
|
||||
echo -e "\e[31mPlease update/install it manually regarding to this doc site: https://docs.mailcow.email/install/\e[0m"
|
||||
exit 1
|
||||
fi
|
||||
elif docker-compose > /dev/null 2>&1; then
|
||||
if ! [[ $(alias docker-compose 2> /dev/null) ]] ; then
|
||||
if docker-compose version --short | grep "^2." > /dev/null 2>&1; then
|
||||
COMPOSE_VERSION=standalone
|
||||
COMPOSE_COMMAND="docker-compose"
|
||||
if [[ "$caller" == "update.sh" ]]; then
|
||||
sed -i 's/^DOCKER_COMPOSE_VERSION=.*/DOCKER_COMPOSE_VERSION=standalone/' "$SCRIPT_DIR/mailcow.conf"
|
||||
fi
|
||||
echo -e "\e[33mFound Docker Compose Standalone.\e[0m"
|
||||
echo -e "\e[33mSetting the DOCKER_COMPOSE_VERSION Variable to standalone\e[0m"
|
||||
sleep 2
|
||||
echo -e "\e[33mNotice: For an automatic update of docker-compose please use the update_compose.sh scripts located at the helper-scripts folder.\e[0m"
|
||||
else
|
||||
echo -e "\e[31mCannot find Docker Compose with a Version Higher than 2.X.X.\e[0m"
|
||||
echo -e "\e[31mPlease update/install manually regarding to this doc site: https://docs.mailcow.email/install/\e[0m"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
else
|
||||
echo -e "\e[31mCannot find Docker Compose.\e[0m"
|
||||
echo -e "\e[31mPlease install it regarding to this doc site: https://docs.mailcow.email/install/\e[0m"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
detect_bad_asn() {
|
||||
echo -e "\e[33mDetecting if your IP is listed on Spamhaus Bad ASN List...\e[0m"
|
||||
response=$(curl --connect-timeout 15 --max-time 30 -s -o /dev/null -w "%{http_code}" "https://asn-check.mailcow.email")
|
||||
if [ "$response" -eq 503 ]; then
|
||||
if [ -z "$SPAMHAUS_DQS_KEY" ]; then
|
||||
echo -e "\e[33mYour server's public IP uses an AS that is blocked by Spamhaus to use their DNS public blocklists for Postfix.\e[0m"
|
||||
echo -e "\e[33mmailcow did not detected a value for the variable SPAMHAUS_DQS_KEY inside mailcow.conf!\e[0m"
|
||||
sleep 2
|
||||
echo ""
|
||||
echo -e "\e[33mTo use the Spamhaus DNS Blocklists again, you will need to create a FREE account for their Data Query Service (DQS) at: https://www.spamhaus.com/free-trial/sign-up-for-a-free-data-query-service-account\e[0m"
|
||||
echo -e "\e[33mOnce done, enter your DQS API key in mailcow.conf and mailcow will do the rest for you!\e[0m"
|
||||
echo ""
|
||||
sleep 2
|
||||
else
|
||||
echo -e "\e[33mYour server's public IP uses an AS that is blocked by Spamhaus to use their DNS public blocklists for Postfix.\e[0m"
|
||||
echo -e "\e[32mmailcow detected a Value for the variable SPAMHAUS_DQS_KEY inside mailcow.conf. Postfix will use DQS with the given API key...\e[0m"
|
||||
fi
|
||||
elif [ "$response" -eq 200 ]; then
|
||||
echo -e "\e[33mCheck completed! Your IP is \e[32mclean\e[0m"
|
||||
elif [ "$response" -eq 429 ]; then
|
||||
echo -e "\e[33mCheck completed! \e[31mYour IP seems to be rate limited on the ASN Check service... please try again later!\e[0m"
|
||||
else
|
||||
echo -e "\e[31mCheck failed! \e[0mMaybe a DNS or Network problem?\e[0m"
|
||||
fi
|
||||
}
|
||||
|
||||
check_online_status() {
|
||||
CHECK_ONLINE_DOMAINS=('https://github.com' 'https://hub.docker.com')
|
||||
for domain in "${CHECK_ONLINE_DOMAINS[@]}"; do
|
||||
if timeout 6 curl --head --silent --output /dev/null ${domain}; then
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
prefetch_images() {
|
||||
[[ -z ${BRANCH} ]] && { echo -e "\e[33m\nUnknown branch...\e[0m"; exit 1; }
|
||||
git fetch origin #${BRANCH}
|
||||
while read image; do
|
||||
RET_C=0
|
||||
until docker pull "${image}"; do
|
||||
RET_C=$((RET_C + 1))
|
||||
echo -e "\e[33m\nError pulling $image, retrying...\e[0m"
|
||||
[ ${RET_C} -gt 3 ] && { echo -e "\e[31m\nToo many failed retries, exiting\e[0m"; exit 1; }
|
||||
sleep 1
|
||||
done
|
||||
done < <(git show "origin/${BRANCH}:docker-compose.yml" | grep "image:" | awk '{ gsub("image:","", $3); print $2 }')
|
||||
}
|
||||
|
||||
docker_garbage() {
|
||||
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )/../.." && pwd )"
|
||||
IMGS_TO_DELETE=()
|
||||
|
||||
declare -A IMAGES_INFO
|
||||
COMPOSE_IMAGES=($(grep -oP "image: \K(ghcr\.io/)?mailcow.+" "${SCRIPT_DIR}/docker-compose.yml"))
|
||||
|
||||
for existing_image in $(docker images --format "{{.ID}}:{{.Repository}}:{{.Tag}}" | grep -E '(mailcow/|ghcr\.io/mailcow/)'); do
|
||||
ID=$(echo "$existing_image" | cut -d ':' -f 1)
|
||||
REPOSITORY=$(echo "$existing_image" | cut -d ':' -f 2)
|
||||
TAG=$(echo "$existing_image" | cut -d ':' -f 3)
|
||||
|
||||
if [[ "$REPOSITORY" == "mailcow/backup" || "$REPOSITORY" == "ghcr.io/mailcow/backup" ]]; then
|
||||
if [[ "$TAG" != "<none>" ]]; then
|
||||
continue
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ " ${COMPOSE_IMAGES[@]} " =~ " ${REPOSITORY}:${TAG} " ]]; then
|
||||
continue
|
||||
else
|
||||
IMGS_TO_DELETE+=("$ID")
|
||||
IMAGES_INFO["$ID"]="$REPOSITORY:$TAG"
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ ! -z ${IMGS_TO_DELETE[*]} ]]; then
|
||||
echo "The following unused mailcow images were found:"
|
||||
for id in "${IMGS_TO_DELETE[@]}"; do
|
||||
echo " ${IMAGES_INFO[$id]} ($id)"
|
||||
done
|
||||
|
||||
if [ -z "$FORCE" ]; then
|
||||
read -r -p "Do you want to delete them to free up some space? [y/N] " response
|
||||
if [[ "$response" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
|
||||
docker rmi ${IMGS_TO_DELETE[*]}
|
||||
else
|
||||
echo "OK, skipped."
|
||||
fi
|
||||
else
|
||||
echo "Running in forced mode! Force removing old mailcow images..."
|
||||
docker rmi ${IMGS_TO_DELETE[*]}
|
||||
fi
|
||||
echo -e "\e[32mFurther cleanup...\e[0m"
|
||||
echo "If you want to cleanup further garbage collected by Docker, please make sure all containers are up and running before cleaning your system by executing \"docker system prune\""
|
||||
fi
|
||||
}
|
||||
|
||||
in_array() {
|
||||
local e match="$1"
|
||||
shift
|
||||
for e; do [[ "$e" == "$match" ]] && return 0; done
|
||||
return 1
|
||||
}
|
||||
|
||||
detect_major_update() {
|
||||
if [ ${BRANCH} == "master" ]; then
|
||||
# Array with major versions
|
||||
# Add major versions here
|
||||
MAJOR_VERSIONS=(
|
||||
"2025-02"
|
||||
"2025-03"
|
||||
"2025-09"
|
||||
)
|
||||
|
||||
current_version=""
|
||||
if [[ -f "${SCRIPT_DIR}/data/web/inc/app_info.inc.php" ]]; then
|
||||
current_version=$(grep 'MAILCOW_GIT_VERSION' ${SCRIPT_DIR}/data/web/inc/app_info.inc.php | sed -E 's/.*MAILCOW_GIT_VERSION="([^"]+)".*/\1/')
|
||||
fi
|
||||
if [[ -z "$current_version" ]]; then
|
||||
return 1
|
||||
fi
|
||||
release_url="https://github.com/mailcow/mailcow-dockerized/releases/tag"
|
||||
|
||||
updates_to_apply=()
|
||||
|
||||
for version in "${MAJOR_VERSIONS[@]}"; do
|
||||
if [[ "$current_version" < "$version" ]]; then
|
||||
updates_to_apply+=("$version")
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ ${#updates_to_apply[@]} -gt 0 ]]; then
|
||||
echo -e "\e[33m\nMAJOR UPDATES to be applied:\e[0m"
|
||||
for update in "${updates_to_apply[@]}"; do
|
||||
echo "$update - $release_url/$update"
|
||||
done
|
||||
|
||||
echo -e "\nPlease read the release notes before proceeding."
|
||||
read -p "Do you want to proceed with the update? [y/n] " response
|
||||
if [[ "${response}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
|
||||
echo "Proceeding with the update..."
|
||||
else
|
||||
echo "Update canceled. Exiting."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
}
|
||||
239
_modules/scripts/ipv6_controller.sh
Normal file
239
_modules/scripts/ipv6_controller.sh
Normal file
@@ -0,0 +1,239 @@
|
||||
#!/usr/bin/env bash
|
||||
# _modules/scripts/ipv6_controller.sh
|
||||
# THIS SCRIPT IS DESIGNED TO BE RUNNING BY MAILCOW SCRIPTS ONLY!
|
||||
# DO NOT, AGAIN, NOT TRY TO RUN THIS SCRIPT STANDALONE!!!!!!
|
||||
|
||||
# 1) Check if the host supports IPv6
|
||||
get_ipv6_support() {
|
||||
# ---- helper: probe external IPv6 connectivity without DNS ----
|
||||
_probe_ipv6_connectivity() {
|
||||
# Use literal, always-on IPv6 echo responders (no DNS required)
|
||||
local PROBE_IPS=("2001:4860:4860::8888" "2606:4700:4700::1111")
|
||||
local ip rc=1
|
||||
|
||||
for ip in "${PROBE_IPS[@]}"; do
|
||||
if command -v ping6 &>/dev/null; then
|
||||
ping6 -c1 -W2 "$ip" &>/dev/null || ping6 -c1 -w2 "$ip" &>/dev/null
|
||||
rc=$?
|
||||
elif command -v ping &>/dev/null; then
|
||||
ping -6 -c1 -W2 "$ip" &>/dev/null || ping -6 -c1 -w2 "$ip" &>/dev/null
|
||||
rc=$?
|
||||
else
|
||||
rc=1
|
||||
fi
|
||||
[[ $rc -eq 0 ]] && return 0
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
if [[ ! -f /proc/net/if_inet6 ]] || grep -qs '^1' /proc/sys/net/ipv6/conf/all/disable_ipv6 2>/dev/null; then
|
||||
DETECTED_IPV6=false
|
||||
echo -e "${YELLOW}IPv6 not detected on host – ${LIGHT_RED}IPv6 is administratively disabled${YELLOW}.${NC}"
|
||||
return
|
||||
fi
|
||||
|
||||
if ip -6 route show default 2>/dev/null | grep -qE '^default'; then
|
||||
echo -e "${YELLOW}Default IPv6 route found – testing external IPv6 connectivity...${NC}"
|
||||
if _probe_ipv6_connectivity; then
|
||||
DETECTED_IPV6=true
|
||||
echo -e "IPv6 detected on host – ${LIGHT_GREEN}leaving IPv6 support enabled${YELLOW}.${NC}"
|
||||
else
|
||||
DETECTED_IPV6=false
|
||||
echo -e "${YELLOW}Default IPv6 route present but external IPv6 connectivity failed – ${LIGHT_RED}disabling IPv6 support${YELLOW}.${NC}"
|
||||
fi
|
||||
return
|
||||
fi
|
||||
|
||||
if ip -6 addr show scope global 2>/dev/null | grep -q 'inet6'; then
|
||||
DETECTED_IPV6=false
|
||||
echo -e "${YELLOW}Global IPv6 address present but no default route – ${LIGHT_RED}disabling IPv6 support${YELLOW}.${NC}"
|
||||
return
|
||||
fi
|
||||
|
||||
if ip -6 addr show scope link 2>/dev/null | grep -q 'inet6'; then
|
||||
echo -e "${YELLOW}Only link-local IPv6 addresses found – testing external IPv6 connectivity...${NC}"
|
||||
if _probe_ipv6_connectivity; then
|
||||
DETECTED_IPV6=true
|
||||
echo -e "External IPv6 connectivity available – ${LIGHT_GREEN}leaving IPv6 support enabled${YELLOW}.${NC}"
|
||||
else
|
||||
DETECTED_IPV6=false
|
||||
echo -e "${YELLOW}Only link-local IPv6 present and no external connectivity – ${LIGHT_RED}disabling IPv6 support${YELLOW}.${NC}"
|
||||
fi
|
||||
return
|
||||
fi
|
||||
|
||||
DETECTED_IPV6=false
|
||||
echo -e "${YELLOW}IPv6 not detected on host – ${LIGHT_RED}disabling IPv6 support${YELLOW}.${NC}"
|
||||
}
|
||||
|
||||
# 2) Ensure Docker daemon.json has (or create) the required IPv6 settings
|
||||
docker_daemon_edit(){
|
||||
DOCKER_DAEMON_CONFIG="/etc/docker/daemon.json"
|
||||
DOCKER_MAJOR=$(docker version --format '{{.Server.Version}}' 2>/dev/null | cut -d. -f1)
|
||||
MISSING=()
|
||||
|
||||
_has_kv() { grep -Eq "\"$1\"[[:space:]]*:[[:space:]]*$2" "$DOCKER_DAEMON_CONFIG" 2>/dev/null; }
|
||||
|
||||
if [[ -f "$DOCKER_DAEMON_CONFIG" ]]; then
|
||||
|
||||
# reject empty or whitespace-only file immediately
|
||||
if [[ ! -s "$DOCKER_DAEMON_CONFIG" ]] || ! grep -Eq '[{}]' "$DOCKER_DAEMON_CONFIG"; then
|
||||
echo -e "${RED}ERROR: $DOCKER_DAEMON_CONFIG exists but is empty or contains no JSON braces – please initialize it with valid JSON (e.g. {}).${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Validate JSON if jq is present
|
||||
if command -v jq &>/dev/null && ! jq empty "$DOCKER_DAEMON_CONFIG" &>/dev/null; then
|
||||
echo -e "${RED}ERROR: Invalid JSON in $DOCKER_DAEMON_CONFIG – please correct manually.${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Gather missing keys
|
||||
! _has_kv ipv6 true && MISSING+=("ipv6: true")
|
||||
|
||||
# For Docker < 28, keep requiring fixed-cidr-v6 (default bridge needs it on old engines)
|
||||
if [[ -n "$DOCKER_MAJOR" && "$DOCKER_MAJOR" -lt 28 ]]; then
|
||||
! grep -Eq '"fixed-cidr-v6"[[:space:]]*:[[:space:]]*".+"' "$DOCKER_DAEMON_CONFIG" \
|
||||
&& MISSING+=('fixed-cidr-v6: "fd00:dead:beef:c0::/80"')
|
||||
fi
|
||||
|
||||
# For Docker < 27, ip6tables needed and was tied to experimental in older releases
|
||||
if [[ -n "$DOCKER_MAJOR" && "$DOCKER_MAJOR" -lt 27 ]]; then
|
||||
_has_kv ipv6 true && ! _has_kv ip6tables true && MISSING+=("ip6tables: true")
|
||||
! _has_kv experimental true && MISSING+=("experimental: true")
|
||||
fi
|
||||
|
||||
# Fix if needed
|
||||
if ((${#MISSING[@]}>0)); then
|
||||
echo -e "${MAGENTA}Your daemon.json is missing: ${YELLOW}${MISSING[*]}${NC}"
|
||||
if [[ -n "$FORCE" ]]; then
|
||||
ans=Y
|
||||
else
|
||||
read -p "Would you like to update $DOCKER_DAEMON_CONFIG now? [Y/n] " ans
|
||||
ans=${ans:-Y}
|
||||
fi
|
||||
|
||||
if [[ $ans =~ ^[Yy]$ ]]; then
|
||||
cp "$DOCKER_DAEMON_CONFIG" "${DOCKER_DAEMON_CONFIG}.bak"
|
||||
if command -v jq &>/dev/null; then
|
||||
TMP=$(mktemp)
|
||||
# Base filter: ensure ipv6 = true
|
||||
JQ_FILTER='.ipv6 = true'
|
||||
|
||||
# Add fixed-cidr-v6 only for Docker < 28
|
||||
if [[ -n "$DOCKER_MAJOR" && "$DOCKER_MAJOR" -lt 28 ]]; then
|
||||
JQ_FILTER+=' | .["fixed-cidr-v6"] = (.["fixed-cidr-v6"] // "fd00:dead:beef:c0::/80")'
|
||||
fi
|
||||
|
||||
# Add ip6tables/experimental only for Docker < 27
|
||||
if [[ -n "$DOCKER_MAJOR" && "$DOCKER_MAJOR" -lt 27 ]]; then
|
||||
JQ_FILTER+=' | .ip6tables = true | .experimental = true'
|
||||
fi
|
||||
|
||||
jq "$JQ_FILTER" "$DOCKER_DAEMON_CONFIG" >"$TMP" && mv "$TMP" "$DOCKER_DAEMON_CONFIG"
|
||||
echo -e "${LIGHT_GREEN}daemon.json updated. Restarting Docker...${NC}"
|
||||
(command -v systemctl &>/dev/null && systemctl restart docker) || service docker restart
|
||||
echo -e "${YELLOW}Docker restarted.${NC}"
|
||||
else
|
||||
echo -e "${RED}Please install jq or manually update daemon.json and restart Docker.${NC}"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo -e "${YELLOW}User declined Docker update – please insert these changes manually:${NC}"
|
||||
echo "${MISSING[*]}"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
else
|
||||
# Create new daemon.json if missing
|
||||
if [[ -n "$FORCE" ]]; then
|
||||
ans=Y
|
||||
else
|
||||
read -p "$DOCKER_DAEMON_CONFIG not found. Create it with IPv6 settings? [Y/n] " ans
|
||||
ans=${ans:-Y}
|
||||
fi
|
||||
|
||||
if [[ $ans =~ ^[Yy]$ ]]; then
|
||||
mkdir -p "$(dirname "$DOCKER_DAEMON_CONFIG")"
|
||||
if [[ -n "$DOCKER_MAJOR" && "$DOCKER_MAJOR" -lt 27 ]]; then
|
||||
cat > "$DOCKER_DAEMON_CONFIG" <<EOF
|
||||
{
|
||||
"ipv6": true,
|
||||
"fixed-cidr-v6": "fd00:dead:beef:c0::/80",
|
||||
"ip6tables": true,
|
||||
"experimental": true
|
||||
}
|
||||
EOF
|
||||
elif [[ -n "$DOCKER_MAJOR" && "$DOCKER_MAJOR" -lt 28 ]]; then
|
||||
cat > "$DOCKER_DAEMON_CONFIG" <<EOF
|
||||
{
|
||||
"ipv6": true,
|
||||
"fixed-cidr-v6": "fd00:dead:beef:c0::/80"
|
||||
}
|
||||
EOF
|
||||
else
|
||||
# Docker 28+: ipv6 works without fixed-cidr-v6
|
||||
cat > "$DOCKER_DAEMON_CONFIG" <<EOF
|
||||
{
|
||||
"ipv6": true
|
||||
}
|
||||
EOF
|
||||
fi
|
||||
echo -e "${GREEN}Created $DOCKER_DAEMON_CONFIG with IPv6 settings.${NC}"
|
||||
echo "Restarting Docker..."
|
||||
(command -v systemctl &>/dev/null && systemctl restart docker) || service docker restart
|
||||
echo "Docker restarted."
|
||||
else
|
||||
echo "User declined to create daemon.json – please manually merge the docker daemon with these configs:"
|
||||
echo "${MISSING[*]}"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# 3) Main wrapper for generate_config.sh and update.sh
|
||||
configure_ipv6() {
|
||||
# detect manual override if mailcow.conf is present
|
||||
if [[ -n "$MAILCOW_CONF" && -f "$MAILCOW_CONF" ]] && grep -q '^ENABLE_IPV6=' "$MAILCOW_CONF"; then
|
||||
MANUAL_SETTING=$(grep '^ENABLE_IPV6=' "$MAILCOW_CONF" | cut -d= -f2)
|
||||
elif [[ -z "$MAILCOW_CONF" ]] && [[ -n "${ENABLE_IPV6:-}" ]]; then
|
||||
MANUAL_SETTING="$ENABLE_IPV6"
|
||||
else
|
||||
MANUAL_SETTING=""
|
||||
fi
|
||||
|
||||
get_ipv6_support
|
||||
|
||||
# if user manually set it, check for mismatch
|
||||
if [[ "$DETECTED_IPV6" != "true" ]]; then
|
||||
if [[ -n "$MAILCOW_CONF" && -f "$MAILCOW_CONF" ]]; then
|
||||
if grep -q '^ENABLE_IPV6=' "$MAILCOW_CONF"; then
|
||||
sed -i 's/^ENABLE_IPV6=.*/ENABLE_IPV6=false/' "$MAILCOW_CONF"
|
||||
else
|
||||
echo "ENABLE_IPV6=false" >> "$MAILCOW_CONF"
|
||||
fi
|
||||
else
|
||||
export IPV6_BOOL=false
|
||||
fi
|
||||
echo "Skipping Docker IPv6 configuration because host does not support IPv6."
|
||||
echo "Make sure to check if your docker daemon.json does not include \"enable_ipv6\": true if you do not want IPv6."
|
||||
echo "IPv6 configuration complete: ENABLE_IPV6=false"
|
||||
sleep 2
|
||||
return
|
||||
fi
|
||||
|
||||
docker_daemon_edit
|
||||
|
||||
if [[ -n "$MAILCOW_CONF" && -f "$MAILCOW_CONF" ]]; then
|
||||
if grep -q '^ENABLE_IPV6=' "$MAILCOW_CONF"; then
|
||||
sed -i 's/^ENABLE_IPV6=.*/ENABLE_IPV6=true/' "$MAILCOW_CONF"
|
||||
else
|
||||
echo "ENABLE_IPV6=true" >> "$MAILCOW_CONF"
|
||||
fi
|
||||
else
|
||||
export IPV6_BOOL=true
|
||||
fi
|
||||
|
||||
echo "IPv6 configuration complete: ENABLE_IPV6=true"
|
||||
}
|
||||
96
_modules/scripts/migrate_options.sh
Normal file
96
_modules/scripts/migrate_options.sh
Normal file
@@ -0,0 +1,96 @@
|
||||
#!/usr/bin/env bash
|
||||
# _modules/scripts/migrate_options.sh
|
||||
# THIS SCRIPT IS DESIGNED TO BE RUNNING BY MAILCOW SCRIPTS ONLY!
|
||||
# DO NOT, AGAIN, NOT TRY TO RUN THIS SCRIPT STANDALONE!!!!!!
|
||||
|
||||
migrate_config_options() {
|
||||
|
||||
sed -i --follow-symlinks '$a\' mailcow.conf
|
||||
|
||||
KEYS=(
|
||||
SOLR_HEAP
|
||||
SKIP_SOLR
|
||||
SOLR_PORT
|
||||
FLATCURVE_EXPERIMENTAL
|
||||
DISABLE_IPv6
|
||||
ACME_CONTACT
|
||||
)
|
||||
|
||||
for key in "${KEYS[@]}"; do
|
||||
if grep -q "${key}" mailcow.conf; then
|
||||
case "${key}" in
|
||||
SOLR_HEAP)
|
||||
echo "Removing ${key} in mailcow.conf"
|
||||
sed -i '/# Solr heap size in MB\b/d' mailcow.conf
|
||||
sed -i '/# Solr is a prone to run\b/d' mailcow.conf
|
||||
sed -i '/SOLR_HEAP\b/d' mailcow.conf
|
||||
;;
|
||||
SKIP_SOLR)
|
||||
echo "Removing ${key} in mailcow.conf"
|
||||
sed -i '/\bSkip Solr on low-memory\b/d' mailcow.conf
|
||||
sed -i '/\bSolr is disabled by default\b/d' mailcow.conf
|
||||
sed -i '/\bDisable Solr or\b/d' mailcow.conf
|
||||
sed -i '/\bSKIP_SOLR\b/d' mailcow.conf
|
||||
;;
|
||||
SOLR_PORT)
|
||||
echo "Removing ${key} in mailcow.conf"
|
||||
sed -i '/\bSOLR_PORT\b/d' mailcow.conf
|
||||
;;
|
||||
FLATCURVE_EXPERIMENTAL)
|
||||
echo "Removing ${key} in mailcow.conf"
|
||||
sed -i '/\bFLATCURVE_EXPERIMENTAL\b/d' mailcow.conf
|
||||
;;
|
||||
DISABLE_IPv6)
|
||||
echo "Migrating ${key} to ENABLE_IPv6 in mailcow.conf"
|
||||
local old=$(grep '^DISABLE_IPv6=' "mailcow.conf" | cut -d'=' -f2)
|
||||
local new
|
||||
if [[ "$old" == "y" ]]; then
|
||||
new="false"
|
||||
else
|
||||
new="true"
|
||||
fi
|
||||
sed -i '/^DISABLE_IPv6=/d' "mailcow.conf"
|
||||
echo "ENABLE_IPV6=$new" >> "mailcow.conf"
|
||||
;;
|
||||
ACME_CONTACT)
|
||||
echo "Deleting obsoleted ${key} in mailcow.conf"
|
||||
sed -i '/^# Lets Encrypt registration contact information/d' mailcow.conf
|
||||
sed -i '/^# Optional: Leave empty for none/d' mailcow.conf
|
||||
sed -i '/^# This value is only used on first order!/d' mailcow.conf
|
||||
sed -i '/^# Setting it at a later point will require the following steps:/d' mailcow.conf
|
||||
sed -i '/^# https:\/\/docs.mailcow.email\/troubleshooting\/debug-reset_tls\//d' mailcow.conf
|
||||
sed -i '/^ACME_CONTACT=.*/d' mailcow.conf
|
||||
sed -i '/^#ACME_CONTACT=.*/d' mailcow.conf
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
done
|
||||
|
||||
solr_volume=$(docker volume ls -qf name=^${COMPOSE_PROJECT_NAME}_solr-vol-1)
|
||||
if [[ -n $solr_volume ]]; then
|
||||
echo -e "\e[34mSolr has been replaced within mailcow since 2025-01.\nThe volume $solr_volume is unused.\e[0m"
|
||||
sleep 1
|
||||
if [ ! "$FORCE" ]; then
|
||||
read -r -p "Remove $solr_volume? [y/N] " response
|
||||
if [[ "$response" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
|
||||
echo -e "\e[33mRemoving $solr_volume...\e[0m"
|
||||
docker volume rm $solr_volume || echo -e "\e[31mFailed to remove. Remove it manually!\e[0m"
|
||||
echo -e "\e[32mSuccessfully removed $solr_volume!\e[0m"
|
||||
else
|
||||
echo -e "Not removing $solr_volume. Run \`docker volume rm $solr_volume\` manually if needed."
|
||||
fi
|
||||
else
|
||||
echo -e "\e[33mForce removing $solr_volume...\e[0m"
|
||||
docker volume rm $solr_volume || echo -e "\e[31mFailed to remove. Remove it manually!\e[0m"
|
||||
echo -e "\e[32mSuccessfully removed $solr_volume!\e[0m"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Delete old fts.conf before forced switch to flatcurve to ensure update is working properly
|
||||
FTS_CONF_PATH="${SCRIPT_DIR}/data/conf/dovecot/conf.d/fts.conf"
|
||||
if [[ -f "$FTS_CONF_PATH" ]]; then
|
||||
if grep -q "Autogenerated by mailcow" "$FTS_CONF_PATH"; then
|
||||
rm -rf $FTS_CONF_PATH
|
||||
fi
|
||||
fi
|
||||
}
|
||||
300
_modules/scripts/new_options.sh
Normal file
300
_modules/scripts/new_options.sh
Normal file
@@ -0,0 +1,300 @@
|
||||
#!/usr/bin/env bash
|
||||
# _modules/scripts/new_options.sh
|
||||
# THIS SCRIPT IS DESIGNED TO BE RUNNING BY MAILCOW SCRIPTS ONLY!
|
||||
# DO NOT, AGAIN, NOT TRY TO RUN THIS SCRIPT STANDALONE!!!!!!
|
||||
|
||||
adapt_new_options() {
|
||||
|
||||
CONFIG_ARRAY=(
|
||||
"AUTODISCOVER_SAN"
|
||||
"SKIP_LETS_ENCRYPT"
|
||||
"SKIP_SOGO"
|
||||
"USE_WATCHDOG"
|
||||
"WATCHDOG_NOTIFY_EMAIL"
|
||||
"WATCHDOG_NOTIFY_WEBHOOK"
|
||||
"WATCHDOG_NOTIFY_WEBHOOK_BODY"
|
||||
"WATCHDOG_NOTIFY_BAN"
|
||||
"WATCHDOG_NOTIFY_START"
|
||||
"WATCHDOG_EXTERNAL_CHECKS"
|
||||
"WATCHDOG_SUBJECT"
|
||||
"SKIP_CLAMD"
|
||||
"SKIP_OLEFY"
|
||||
"SKIP_IP_CHECK"
|
||||
"ADDITIONAL_SAN"
|
||||
"DOVEADM_PORT"
|
||||
"IPV4_NETWORK"
|
||||
"IPV6_NETWORK"
|
||||
"LOG_LINES"
|
||||
"SNAT_TO_SOURCE"
|
||||
"SNAT6_TO_SOURCE"
|
||||
"COMPOSE_PROJECT_NAME"
|
||||
"DOCKER_COMPOSE_VERSION"
|
||||
"SQL_PORT"
|
||||
"API_KEY"
|
||||
"API_KEY_READ_ONLY"
|
||||
"API_ALLOW_FROM"
|
||||
"MAILDIR_GC_TIME"
|
||||
"MAILDIR_SUB"
|
||||
"ACL_ANYONE"
|
||||
"FTS_HEAP"
|
||||
"FTS_PROCS"
|
||||
"SKIP_FTS"
|
||||
"ENABLE_SSL_SNI"
|
||||
"ALLOW_ADMIN_EMAIL_LOGIN"
|
||||
"SKIP_HTTP_VERIFICATION"
|
||||
"SOGO_EXPIRE_SESSION"
|
||||
"SOGO_URL_ENCRYPTION_KEY"
|
||||
"REDIS_PORT"
|
||||
"REDISPASS"
|
||||
"DOVECOT_MASTER_USER"
|
||||
"DOVECOT_MASTER_PASS"
|
||||
"MAILCOW_PASS_SCHEME"
|
||||
"ADDITIONAL_SERVER_NAMES"
|
||||
"WATCHDOG_VERBOSE"
|
||||
"WEBAUTHN_ONLY_TRUSTED_VENDORS"
|
||||
"SPAMHAUS_DQS_KEY"
|
||||
"SKIP_UNBOUND_HEALTHCHECK"
|
||||
"DISABLE_NETFILTER_ISOLATION_RULE"
|
||||
"HTTP_REDIRECT"
|
||||
"ENABLE_IPV6"
|
||||
)
|
||||
|
||||
sed -i --follow-symlinks '$a\' mailcow.conf
|
||||
for option in ${CONFIG_ARRAY[@]}; do
|
||||
if grep -q "${option}" mailcow.conf; then
|
||||
continue
|
||||
fi
|
||||
|
||||
echo "Adding new option \"${option}\" to mailcow.conf"
|
||||
|
||||
case "${option}" in
|
||||
AUTODISCOVER_SAN)
|
||||
echo '# Obtain certificates for autodiscover.* and autoconfig.* domains.' >> mailcow.conf
|
||||
echo '# This can be useful to switch off in case you are in a scenario where a reverse proxy already handles those.' >> mailcow.conf
|
||||
echo '# There are mixed scenarios where ports 80,443 are occupied and you do not want to share certs' >> mailcow.conf
|
||||
echo '# between services. So acme-mailcow obtains for maildomains and all web-things get handled' >> mailcow.conf
|
||||
echo '# in the reverse proxy.' >> mailcow.conf
|
||||
echo 'AUTODISCOVER_SAN=y' >> mailcow.conf
|
||||
;;
|
||||
|
||||
DOCKER_COMPOSE_VERSION)
|
||||
echo "# Used Docker Compose version" >> mailcow.conf
|
||||
echo "# Switch here between native (compose plugin) and standalone" >> mailcow.conf
|
||||
echo "# For more informations take a look at the mailcow docs regarding the configuration options." >> mailcow.conf
|
||||
echo "# Normally this should be untouched but if you decided to use either of those you can switch it manually here." >> mailcow.conf
|
||||
echo "# Please be aware that at least one of those variants should be installed on your machine or mailcow will fail." >> mailcow.conf
|
||||
echo "" >> mailcow.conf
|
||||
echo "DOCKER_COMPOSE_VERSION=${DOCKER_COMPOSE_VERSION}" >> mailcow.conf
|
||||
;;
|
||||
|
||||
DOVEADM_PORT)
|
||||
echo "DOVEADM_PORT=127.0.0.1:19991" >> mailcow.conf
|
||||
;;
|
||||
|
||||
LOG_LINES)
|
||||
echo '# Max log lines per service to keep in Redis logs' >> mailcow.conf
|
||||
echo "LOG_LINES=9999" >> mailcow.conf
|
||||
;;
|
||||
IPV4_NETWORK)
|
||||
echo '# Internal IPv4 /24 subnet, format n.n.n. (expands to n.n.n.0/24)' >> mailcow.conf
|
||||
echo "IPV4_NETWORK=172.22.1" >> mailcow.conf
|
||||
;;
|
||||
IPV6_NETWORK)
|
||||
echo '# Internal IPv6 subnet in fc00::/7' >> mailcow.conf
|
||||
echo "IPV6_NETWORK=fd4d:6169:6c63:6f77::/64" >> mailcow.conf
|
||||
;;
|
||||
SQL_PORT)
|
||||
echo '# Bind SQL to 127.0.0.1 on port 13306' >> mailcow.conf
|
||||
echo "SQL_PORT=127.0.0.1:13306" >> mailcow.conf
|
||||
;;
|
||||
API_KEY)
|
||||
echo '# Create or override API key for web UI' >> mailcow.conf
|
||||
echo "#API_KEY=" >> mailcow.conf
|
||||
;;
|
||||
API_KEY_READ_ONLY)
|
||||
echo '# Create or override read-only API key for web UI' >> mailcow.conf
|
||||
echo "#API_KEY_READ_ONLY=" >> mailcow.conf
|
||||
;;
|
||||
API_ALLOW_FROM)
|
||||
echo '# Must be set for API_KEY to be active' >> mailcow.conf
|
||||
echo '# IPs only, no networks (networks can be set via UI)' >> mailcow.conf
|
||||
echo "#API_ALLOW_FROM=" >> mailcow.conf
|
||||
;;
|
||||
SNAT_TO_SOURCE)
|
||||
echo '# Use this IPv4 for outgoing connections (SNAT)' >> mailcow.conf
|
||||
echo "#SNAT_TO_SOURCE=" >> mailcow.conf
|
||||
;;
|
||||
SNAT6_TO_SOURCE)
|
||||
echo '# Use this IPv6 for outgoing connections (SNAT)' >> mailcow.conf
|
||||
echo "#SNAT6_TO_SOURCE=" >> mailcow.conf
|
||||
;;
|
||||
MAILDIR_GC_TIME)
|
||||
echo '# Garbage collector cleanup' >> mailcow.conf
|
||||
echo '# Deleted domains and mailboxes are moved to /var/vmail/_garbage/timestamp_sanitizedstring' >> mailcow.conf
|
||||
echo '# How long should objects remain in the garbage until they are being deleted? (value in minutes)' >> mailcow.conf
|
||||
echo '# Check interval is hourly' >> mailcow.conf
|
||||
echo 'MAILDIR_GC_TIME=1440' >> mailcow.conf
|
||||
;;
|
||||
ACL_ANYONE)
|
||||
echo '# Set this to "allow" to enable the anyone pseudo user. Disabled by default.' >> mailcow.conf
|
||||
echo '# When enabled, ACL can be created, that apply to "All authenticated users"' >> mailcow.conf
|
||||
echo '# This should probably only be activated on mail hosts, that are used exclusively by one organisation.' >> mailcow.conf
|
||||
echo '# Otherwise a user might share data with too many other users.' >> mailcow.conf
|
||||
echo 'ACL_ANYONE=disallow' >> mailcow.conf
|
||||
;;
|
||||
FTS_HEAP)
|
||||
echo '# Dovecot Indexing (FTS) Process maximum heap size in MB, there is no recommendation, please see Dovecot docs.' >> mailcow.conf
|
||||
echo '# Flatcurve is used as FTS Engine. It is supposed to be pretty efficient in CPU and RAM consumption.' >> mailcow.conf
|
||||
echo '# Please always monitor your Resource consumption!' >> mailcow.conf
|
||||
echo "FTS_HEAP=128" >> mailcow.conf
|
||||
;;
|
||||
SKIP_FTS)
|
||||
echo '# Skip FTS (Fulltext Search) for Dovecot on low-memory, low-threaded systems or if you simply want to disable it.' >> mailcow.conf
|
||||
echo "# Dovecot inside mailcow use Flatcurve as FTS Backend." >> mailcow.conf
|
||||
echo "SKIP_FTS=y" >> mailcow.conf
|
||||
;;
|
||||
FTS_PROCS)
|
||||
echo '# Controls how many processes the Dovecot indexing process can spawn at max.' >> mailcow.conf
|
||||
echo '# Too many indexing processes can use a lot of CPU and Disk I/O' >> mailcow.conf
|
||||
echo '# Please visit: https://doc.dovecot.org/configuration_manual/service_configuration/#indexer-worker for more informations' >> mailcow.conf
|
||||
echo "FTS_PROCS=1" >> mailcow.conf
|
||||
;;
|
||||
ENABLE_SSL_SNI)
|
||||
echo '# Create seperate certificates for all domains - y/n' >> mailcow.conf
|
||||
echo '# this will allow adding more than 100 domains, but some email clients will not be able to connect with alternative hostnames' >> mailcow.conf
|
||||
echo '# see https://wiki.dovecot.org/SSL/SNIClientSupport' >> mailcow.conf
|
||||
echo "ENABLE_SSL_SNI=n" >> mailcow.conf
|
||||
;;
|
||||
SKIP_SOGO)
|
||||
echo '# Skip SOGo: Will disable SOGo integration and therefore webmail, DAV protocols and ActiveSync support (experimental, unsupported, not fully implemented) - y/n' >> mailcow.conf
|
||||
echo "SKIP_SOGO=n" >> mailcow.conf
|
||||
;;
|
||||
MAILDIR_SUB)
|
||||
echo '# MAILDIR_SUB defines a path in a users virtual home to keep the maildir in. Leave empty for updated setups.' >> mailcow.conf
|
||||
echo "#MAILDIR_SUB=Maildir" >> mailcow.conf
|
||||
echo "MAILDIR_SUB=" >> mailcow.conf
|
||||
;;
|
||||
WATCHDOG_NOTIFY_WEBHOOK)
|
||||
echo '# Send notifications to a webhook URL that receives a POST request with the content type "application/json".' >> mailcow.conf
|
||||
echo '# You can use this to send notifications to services like Discord, Slack and others.' >> mailcow.conf
|
||||
echo '#WATCHDOG_NOTIFY_WEBHOOK=https://discord.com/api/webhooks/XXXXXXXXXXXXXXXXXXX/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' >> mailcow.conf
|
||||
;;
|
||||
WATCHDOG_NOTIFY_WEBHOOK_BODY)
|
||||
echo '# JSON body included in the webhook POST request. Needs to be in single quotes.' >> mailcow.conf
|
||||
echo '# Following variables are available: SUBJECT, BODY' >> mailcow.conf
|
||||
WEBHOOK_BODY='{"username": "mailcow Watchdog", "content": "**${SUBJECT}**\n${BODY}"}'
|
||||
echo "#WATCHDOG_NOTIFY_WEBHOOK_BODY='${WEBHOOK_BODY}'" >> mailcow.conf
|
||||
;;
|
||||
WATCHDOG_NOTIFY_BAN)
|
||||
echo '# Notify about banned IP. Includes whois lookup.' >> mailcow.conf
|
||||
echo "WATCHDOG_NOTIFY_BAN=y" >> mailcow.conf
|
||||
;;
|
||||
WATCHDOG_NOTIFY_START)
|
||||
echo '# Send a notification when the watchdog is started.' >> mailcow.conf
|
||||
echo "WATCHDOG_NOTIFY_START=y" >> mailcow.conf
|
||||
;;
|
||||
WATCHDOG_SUBJECT)
|
||||
echo '# Subject for watchdog mails. Defaults to "Watchdog ALERT" followed by the error message.' >> mailcow.conf
|
||||
echo "#WATCHDOG_SUBJECT=" >> mailcow.conf
|
||||
;;
|
||||
WATCHDOG_EXTERNAL_CHECKS)
|
||||
echo '# Checks if mailcow is an open relay. Requires a SAL. More checks will follow.' >> mailcow.conf
|
||||
echo '# No data is collected. Opt-in and anonymous.' >> mailcow.conf
|
||||
echo '# Will only work with unmodified mailcow setups.' >> mailcow.conf
|
||||
echo "WATCHDOG_EXTERNAL_CHECKS=n" >> mailcow.conf
|
||||
;;
|
||||
SOGO_EXPIRE_SESSION)
|
||||
echo '# SOGo session timeout in minutes' >> mailcow.conf
|
||||
echo "SOGO_EXPIRE_SESSION=480" >> mailcow.conf
|
||||
;;
|
||||
REDIS_PORT)
|
||||
echo "REDIS_PORT=127.0.0.1:7654" >> mailcow.conf
|
||||
;;
|
||||
DOVECOT_MASTER_USER)
|
||||
echo '# DOVECOT_MASTER_USER and _PASS must _both_ be provided. No special chars.' >> mailcow.conf
|
||||
echo '# Empty by default to auto-generate master user and password on start.' >> mailcow.conf
|
||||
echo '# User expands to DOVECOT_MASTER_USER@mailcow.local' >> mailcow.conf
|
||||
echo '# LEAVE EMPTY IF UNSURE' >> mailcow.conf
|
||||
echo "DOVECOT_MASTER_USER=" >> mailcow.conf
|
||||
;;
|
||||
DOVECOT_MASTER_PASS)
|
||||
echo '# LEAVE EMPTY IF UNSURE' >> mailcow.conf
|
||||
echo "DOVECOT_MASTER_PASS=" >> mailcow.conf
|
||||
;;
|
||||
MAILCOW_PASS_SCHEME)
|
||||
echo '# Password hash algorithm' >> mailcow.conf
|
||||
echo '# Only certain password hash algorithm are supported. For a fully list of supported schemes,' >> mailcow.conf
|
||||
echo '# see https://docs.mailcow.email/models/model-passwd/' >> mailcow.conf
|
||||
echo "MAILCOW_PASS_SCHEME=BLF-CRYPT" >> mailcow.conf
|
||||
;;
|
||||
ADDITIONAL_SERVER_NAMES)
|
||||
echo '# Additional server names for mailcow UI' >> mailcow.conf
|
||||
echo '#' >> mailcow.conf
|
||||
echo '# Specify alternative addresses for the mailcow UI to respond to' >> mailcow.conf
|
||||
echo '# This is useful when you set mail.* as ADDITIONAL_SAN and want to make sure mail.maildomain.com will always point to the mailcow UI.' >> mailcow.conf
|
||||
echo '# If the server name does not match a known site, Nginx decides by best-guess and may redirect users to the wrong web root.' >> mailcow.conf
|
||||
echo '# You can understand this as server_name directive in Nginx.' >> mailcow.conf
|
||||
echo '# Comma separated list without spaces! Example: ADDITIONAL_SERVER_NAMES=a.b.c,d.e.f' >> mailcow.conf
|
||||
echo 'ADDITIONAL_SERVER_NAMES=' >> mailcow.conf
|
||||
;;
|
||||
WEBAUTHN_ONLY_TRUSTED_VENDORS)
|
||||
echo "# WebAuthn device manufacturer verification" >> mailcow.conf
|
||||
echo '# After setting WEBAUTHN_ONLY_TRUSTED_VENDORS=y only devices from trusted manufacturers are allowed' >> mailcow.conf
|
||||
echo '# root certificates can be placed for validation under mailcow-dockerized/data/web/inc/lib/WebAuthn/rootCertificates' >> mailcow.conf
|
||||
echo 'WEBAUTHN_ONLY_TRUSTED_VENDORS=n' >> mailcow.conf
|
||||
;;
|
||||
SPAMHAUS_DQS_KEY)
|
||||
echo "# Spamhaus Data Query Service Key" >> mailcow.conf
|
||||
echo '# Optional: Leave empty for none' >> mailcow.conf
|
||||
echo '# Enter your key here if you are using a blocked ASN (OVH, AWS, Cloudflare e.g) for the unregistered Spamhaus Blocklist.' >> mailcow.conf
|
||||
echo '# If empty, it will completely disable Spamhaus blocklists if it detects that you are running on a server using a blocked AS.' >> mailcow.conf
|
||||
echo '# Otherwise it will work as usual.' >> mailcow.conf
|
||||
echo 'SPAMHAUS_DQS_KEY=' >> mailcow.conf
|
||||
;;
|
||||
WATCHDOG_VERBOSE)
|
||||
echo '# Enable watchdog verbose logging' >> mailcow.conf
|
||||
echo 'WATCHDOG_VERBOSE=n' >> mailcow.conf
|
||||
;;
|
||||
SKIP_UNBOUND_HEALTHCHECK)
|
||||
echo '# Skip Unbound (DNS Resolver) Healthchecks (NOT Recommended!) - y/n' >> mailcow.conf
|
||||
echo 'SKIP_UNBOUND_HEALTHCHECK=n' >> mailcow.conf
|
||||
;;
|
||||
DISABLE_NETFILTER_ISOLATION_RULE)
|
||||
echo '# Prevent netfilter from setting an iptables/nftables rule to isolate the mailcow docker network - y/n' >> mailcow.conf
|
||||
echo '# CAUTION: Disabling this may expose container ports to other neighbors on the same subnet, even if the ports are bound to localhost' >> mailcow.conf
|
||||
echo 'DISABLE_NETFILTER_ISOLATION_RULE=n' >> mailcow.conf
|
||||
;;
|
||||
HTTP_REDIRECT)
|
||||
echo '# Redirect HTTP connections to HTTPS - y/n' >> mailcow.conf
|
||||
echo 'HTTP_REDIRECT=n' >> mailcow.conf
|
||||
;;
|
||||
ENABLE_IPV6)
|
||||
echo '# IPv6 Controller Section' >> mailcow.conf
|
||||
echo '# This variable controls the usage of IPv6 within mailcow.' >> mailcow.conf
|
||||
echo '# Can either be true or false | Defaults to true' >> mailcow.conf
|
||||
echo '# WARNING: MAKE SURE TO PROPERLY CONFIGURE IPv6 ON YOUR HOST FIRST BEFORE ENABLING THIS AS FAULTY CONFIGURATIONS CAN LEAD TO OPEN RELAYS!' >> mailcow.conf
|
||||
echo '# A COMPLETE DOCKER STACK REBUILD (compose down && compose up -d) IS NEEDED TO APPLY THIS.' >> mailcow.conf
|
||||
echo ENABLE_IPV6=${IPV6_BOOL} >> mailcow.conf
|
||||
;;
|
||||
SKIP_CLAMD)
|
||||
echo '# Skip ClamAV (clamd-mailcow) anti-virus (Rspamd will auto-detect a missing ClamAV container) - y/n' >> mailcow.conf
|
||||
echo 'SKIP_CLAMD=n' >> mailcow.conf
|
||||
;;
|
||||
SKIP_OLEFY)
|
||||
echo '# Skip Olefy (olefy-mailcow) anti-virus for Office documents (Rspamd will auto-detect a missing Olefy container) - y/n' >> mailcow.conf
|
||||
echo 'SKIP_OLEFY=n' >> mailcow.conf
|
||||
;;
|
||||
REDISPASS)
|
||||
echo "REDISPASS=$(LC_ALL=C </dev/urandom tr -dc A-Za-z0-9 2>/dev/null | head -c 28)" >> mailcow.conf
|
||||
;;
|
||||
SOGO_URL_ENCRYPTION_KEY)
|
||||
echo '# SOGo URL encryption key (exactly 16 characters, limited to A–Z, a–z, 0–9)' >> mailcow.conf
|
||||
echo '# This key is used to encrypt email addresses within SOGo URLs' >> mailcow.conf
|
||||
echo "SOGO_URL_ENCRYPTION_KEY=$(LC_ALL=C </dev/urandom tr -dc A-Za-z0-9 2>/dev/null | head -c 16)" >> mailcow.conf
|
||||
;;
|
||||
*)
|
||||
echo "${option}=" >> mailcow.conf
|
||||
;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
@@ -3,14 +3,14 @@ set -o pipefail
|
||||
exec 5>&1
|
||||
|
||||
# Do not attempt to write to slave
|
||||
if [[ ! -z ${REDIS_SLAVEOF_IP} ]]; then
|
||||
export REDIS_CMDLINE="redis-cli -h ${REDIS_SLAVEOF_IP} -p ${REDIS_SLAVEOF_PORT} -a ${REDISPASS} --no-auth-warning"
|
||||
if [[ ! -z ${VALKEY_SLAVEOF_IP} ]]; then
|
||||
export VALKEY_CMDLINE="redis-cli -h ${VALKEY_SLAVEOF_IP} -p ${VALKEY_SLAVEOF_PORT} -a ${VALKEYPASS} --no-auth-warning"
|
||||
else
|
||||
export REDIS_CMDLINE="redis-cli -h redis -p 6379 -a ${REDISPASS} --no-auth-warning"
|
||||
export VALKEY_CMDLINE="redis-cli -h valkey-mailcow -p 6379 -a ${VALKEYPASS} --no-auth-warning"
|
||||
fi
|
||||
|
||||
until [[ $(${REDIS_CMDLINE} PING) == "PONG" ]]; do
|
||||
echo "Waiting for Redis..."
|
||||
until [[ $(${VALKEY_CMDLINE} PING) == "PONG" ]]; do
|
||||
echo "Waiting for Valkey..."
|
||||
sleep 2
|
||||
done
|
||||
|
||||
@@ -159,18 +159,6 @@ while true; do
|
||||
fi
|
||||
if [[ ! -f ${ACME_BASE}/acme/account.pem ]]; then
|
||||
log_f "Generating missing Lets Encrypt account key..."
|
||||
if [[ ! -z ${ACME_CONTACT} ]]; then
|
||||
if ! verify_email "${ACME_CONTACT}"; then
|
||||
log_f "Invalid email address, will not start registration!"
|
||||
sleep 365d
|
||||
exec $(readlink -f "$0")
|
||||
else
|
||||
ACME_CONTACT_PARAMETER="--contact mailto:${ACME_CONTACT}"
|
||||
log_f "Valid email address, using ${ACME_CONTACT} for registration"
|
||||
fi
|
||||
else
|
||||
ACME_CONTACT_PARAMETER=""
|
||||
fi
|
||||
openssl genrsa 4096 > ${ACME_BASE}/acme/account.pem
|
||||
else
|
||||
log_f "Using existing Lets Encrypt account key ${ACME_BASE}/acme/account.pem"
|
||||
@@ -218,7 +206,7 @@ while true; do
|
||||
|
||||
if [[ ${AUTODISCOVER_SAN} == "y" ]]; then
|
||||
# Fetch certs for autoconfig and autodiscover subdomains
|
||||
ADDITIONAL_WC_ARR+=('autodiscover' 'autoconfig')
|
||||
ADDITIONAL_WC_ARR+=('autodiscover' 'autoconfig' 'mta-sts')
|
||||
fi
|
||||
|
||||
if [[ ${SKIP_IP_CHECK} != "y" ]]; then
|
||||
@@ -299,7 +287,7 @@ while true; do
|
||||
VALIDATED_CERTIFICATES+=("${CERT_NAME}")
|
||||
|
||||
# obtain server certificate if required
|
||||
ACME_CONTACT_PARAMETER=${ACME_CONTACT_PARAMETER} DOMAINS=${SERVER_SAN_VALIDATED[@]} /srv/obtain-certificate.sh rsa
|
||||
DOMAINS=${SERVER_SAN_VALIDATED[@]} /srv/obtain-certificate.sh rsa
|
||||
RETURN="$?"
|
||||
if [[ "$RETURN" == "0" ]]; then # 0 = cert created successfully
|
||||
CERT_AMOUNT_CHANGED=1
|
||||
@@ -360,7 +348,7 @@ while true; do
|
||||
if [[ -z ${VALIDATED_CERTIFICATES[*]} ]]; then
|
||||
log_f "Cannot validate any hostnames, skipping Let's Encrypt for 1 hour."
|
||||
log_f "Use SKIP_LETS_ENCRYPT=y in mailcow.conf to skip it permanently."
|
||||
${REDIS_CMDLINE} SET ACME_FAIL_TIME "$(date +%s)"
|
||||
${VALKEY_CMDLINE} SET ACME_FAIL_TIME "$(date +%s)"
|
||||
sleep 1h
|
||||
exec $(readlink -f "$0")
|
||||
fi
|
||||
@@ -401,7 +389,7 @@ while true; do
|
||||
DOVECOT_CERT_SERIAL_NEW="$(echo | openssl s_client -connect dovecot:143 -starttls imap 2>/dev/null | openssl x509 -inform pem -noout -serial | cut -d "=" -f 2)"
|
||||
if [[ ${RELOAD_LOOP_C} -gt 3 ]]; then
|
||||
log_f "Some services do return old end dates, something went wrong!"
|
||||
${REDIS_CMDLINE} SET ACME_FAIL_TIME "$(date +%s)"
|
||||
${VALKEY_CMDLINE} SET ACME_FAIL_TIME "$(date +%s)"
|
||||
break;
|
||||
fi
|
||||
done
|
||||
@@ -422,7 +410,7 @@ while true; do
|
||||
;;
|
||||
*) # non-zero
|
||||
log_f "Some errors occurred, retrying in 30 minutes..."
|
||||
${REDIS_CMDLINE} SET ACME_FAIL_TIME "$(date +%s)"
|
||||
${VALKEY_CMDLINE} SET ACME_FAIL_TIME "$(date +%s)"
|
||||
sleep 30m
|
||||
exec $(readlink -f "$0")
|
||||
;;
|
||||
|
||||
@@ -5,13 +5,13 @@ log_f() {
|
||||
echo -n "$(date) - ${1}"
|
||||
elif [[ ${2} == "no_date" ]]; then
|
||||
echo "${1}"
|
||||
elif [[ ${2} != "redis_only" ]]; then
|
||||
elif [[ ${2} != "valkey_only" ]]; then
|
||||
echo "$(date) - ${1}"
|
||||
fi
|
||||
if [[ ${3} == "b64" ]]; then
|
||||
${REDIS_CMDLINE} LPUSH ACME_LOG "{\"time\":\"$(date +%s)\",\"message\":\"base64,$(printf '%s' "${MAILCOW_HOSTNAME} - ${1}")\"}" > /dev/null
|
||||
${VALKEY_CMDLINE} LPUSH ACME_LOG "{\"time\":\"$(date +%s)\",\"message\":\"base64,$(printf '%s' "${MAILCOW_HOSTNAME} - ${1}")\"}" > /dev/null
|
||||
else
|
||||
${REDIS_CMDLINE} LPUSH ACME_LOG "{\"time\":\"$(date +%s)\",\"message\":\"$(printf '%s' "${MAILCOW_HOSTNAME} - ${1}" | \
|
||||
${VALKEY_CMDLINE} LPUSH ACME_LOG "{\"time\":\"$(date +%s)\",\"message\":\"$(printf '%s' "${MAILCOW_HOSTNAME} - ${1}" | \
|
||||
tr '%&;$"[]{}-\r\n' ' ')\"}" > /dev/null
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -93,15 +93,15 @@ until dig letsencrypt.org +time=3 +tries=1 @unbound > /dev/null; do
|
||||
sleep 2
|
||||
done
|
||||
log_f "Resolver OK"
|
||||
log_f "Using command acme-tiny ${DIRECTORY_URL} ${ACME_CONTACT_PARAMETER} --account-key ${ACME_BASE}/acme/account.pem --disable-check --csr ${CSR} --acme-dir /var/www/acme/"
|
||||
ACME_RESPONSE=$(acme-tiny ${DIRECTORY_URL} ${ACME_CONTACT_PARAMETER} \
|
||||
log_f "Using command acme-tiny ${DIRECTORY_URL} --account-key ${ACME_BASE}/acme/account.pem --disable-check --csr ${CSR} --acme-dir /var/www/acme/"
|
||||
ACME_RESPONSE=$(acme-tiny ${DIRECTORY_URL} \
|
||||
--account-key ${ACME_BASE}/acme/account.pem \
|
||||
--disable-check \
|
||||
--csr ${CSR} \
|
||||
--acme-dir /var/www/acme/ 2>&1 > /tmp/_cert.pem | tee /dev/fd/5; exit ${PIPESTATUS[0]})
|
||||
SUCCESS="$?"
|
||||
ACME_RESPONSE_B64=$(echo "${ACME_RESPONSE}" | openssl enc -e -A -base64)
|
||||
log_f "${ACME_RESPONSE_B64}" redis_only b64
|
||||
log_f "${ACME_RESPONSE_B64}" valkey_only b64
|
||||
case "$SUCCESS" in
|
||||
0) # cert requested
|
||||
log_f "Deploying certificate ${CERT}..."
|
||||
@@ -124,7 +124,7 @@ case "$SUCCESS" in
|
||||
;;
|
||||
*) # non-zero is non-fun
|
||||
log_f "Failed to obtain certificate ${CERT} for domains '${CERT_DOMAINS[*]}'"
|
||||
redis-cli -h redis -a ${REDISPASS} --no-auth-warning SET ACME_FAIL_TIME "$(date +%s)"
|
||||
redis-cli -h valkey-mailcow -a ${VALKEYPASS} --no-auth-warning SET ACME_FAIL_TIME "$(date +%s)"
|
||||
exit 100${SUCCESS}
|
||||
;;
|
||||
esac
|
||||
|
||||
@@ -8,7 +8,7 @@ fi
|
||||
|
||||
# Cleaning up garbage
|
||||
echo "Cleaning up tmp files..."
|
||||
rm -rf /var/lib/clamav/clamav-*.tmp
|
||||
rm -rf /var/lib/clamav/tmp.*
|
||||
|
||||
# Prepare whitelist
|
||||
|
||||
|
||||
@@ -32,21 +32,21 @@ async def lifespan(app: FastAPI):
|
||||
|
||||
logger.info("Init APP")
|
||||
|
||||
# Init redis client
|
||||
if os.environ['REDIS_SLAVEOF_IP'] != "":
|
||||
redis_client = redis = await aioredis.from_url(f"redis://{os.environ['REDIS_SLAVEOF_IP']}:{os.environ['REDIS_SLAVEOF_PORT']}/0", password=os.environ['REDISPASS'])
|
||||
# Init valkey client
|
||||
if os.environ['VALKEY_SLAVEOF_IP'] != "":
|
||||
valkey_client = valkey = await aioredis.from_url(f"redis://{os.environ['VALKEY_SLAVEOF_IP']}:{os.environ['VALKEY_SLAVEOF_PORT']}/0", password=os.environ['VALKEYPASS'])
|
||||
else:
|
||||
redis_client = redis = await aioredis.from_url("redis://redis-mailcow:6379/0", password=os.environ['REDISPASS'])
|
||||
valkey_client = valkey = await aioredis.from_url("redis://valkey-mailcow:6379/0", password=os.environ['VALKEYPASS'])
|
||||
|
||||
# Init docker clients
|
||||
sync_docker_client = docker.DockerClient(base_url='unix://var/run/docker.sock', version='auto')
|
||||
async_docker_client = aiodocker.Docker(url='unix:///var/run/docker.sock')
|
||||
|
||||
dockerapi = DockerApi(redis_client, sync_docker_client, async_docker_client, logger)
|
||||
dockerapi = DockerApi(valkey_client, sync_docker_client, async_docker_client, logger)
|
||||
|
||||
logger.info("Subscribe to redis channel")
|
||||
# Subscribe to redis channel
|
||||
dockerapi.pubsub = redis.pubsub()
|
||||
logger.info("Subscribe to valkey channel")
|
||||
# Subscribe to valkey channel
|
||||
dockerapi.pubsub = valkey.pubsub()
|
||||
await dockerapi.pubsub.subscribe("MC_CHANNEL")
|
||||
asyncio.create_task(handle_pubsub_messages(dockerapi.pubsub))
|
||||
|
||||
@@ -57,9 +57,9 @@ async def lifespan(app: FastAPI):
|
||||
dockerapi.sync_docker_client.close()
|
||||
await dockerapi.async_docker_client.close()
|
||||
|
||||
# Close redis
|
||||
# Close valkey
|
||||
await dockerapi.pubsub.unsubscribe("MC_CHANNEL")
|
||||
await dockerapi.redis_client.close()
|
||||
await dockerapi.valkey_client.close()
|
||||
|
||||
app = FastAPI(lifespan=lifespan)
|
||||
|
||||
@@ -73,11 +73,11 @@ async def get_host_update_stats():
|
||||
dockerapi.host_stats_isUpdating = True
|
||||
|
||||
while True:
|
||||
if await dockerapi.redis_client.exists('host_stats'):
|
||||
if await dockerapi.valkey_client.exists('host_stats'):
|
||||
break
|
||||
await asyncio.sleep(1.5)
|
||||
|
||||
stats = json.loads(await dockerapi.redis_client.get('host_stats'))
|
||||
stats = json.loads(await dockerapi.valkey_client.get('host_stats'))
|
||||
return Response(content=json.dumps(stats, indent=4), media_type="application/json")
|
||||
|
||||
@app.get("/containers/{container_id}/json")
|
||||
@@ -185,11 +185,11 @@ async def post_container_update_stats(container_id : str):
|
||||
dockerapi.containerIds_to_update.append(container_id)
|
||||
|
||||
while True:
|
||||
if await dockerapi.redis_client.exists(container_id + '_stats'):
|
||||
if await dockerapi.valkey_client.exists(container_id + '_stats'):
|
||||
break
|
||||
await asyncio.sleep(1.5)
|
||||
|
||||
stats = json.loads(await dockerapi.redis_client.get(container_id + '_stats'))
|
||||
stats = json.loads(await dockerapi.valkey_client.get(container_id + '_stats'))
|
||||
return Response(content=json.dumps(stats, indent=4), media_type="application/json")
|
||||
|
||||
|
||||
|
||||
@@ -10,8 +10,8 @@ from datetime import datetime
|
||||
from fastapi import FastAPI, Response, Request
|
||||
|
||||
class DockerApi:
|
||||
def __init__(self, redis_client, sync_docker_client, async_docker_client, logger):
|
||||
self.redis_client = redis_client
|
||||
def __init__(self, valkey_client, sync_docker_client, async_docker_client, logger):
|
||||
self.valkey_client = valkey_client
|
||||
self.sync_docker_client = sync_docker_client
|
||||
self.async_docker_client = async_docker_client
|
||||
self.logger = logger
|
||||
@@ -533,7 +533,7 @@ class DockerApi:
|
||||
"architecture": platform.machine()
|
||||
}
|
||||
|
||||
await self.redis_client.set('host_stats', json.dumps(host_stats), ex=10)
|
||||
await self.valkey_client.set('host_stats', json.dumps(host_stats), ex=10)
|
||||
except Exception as e:
|
||||
res = {
|
||||
"type": "danger",
|
||||
@@ -550,14 +550,14 @@ class DockerApi:
|
||||
if container._id == container_id:
|
||||
res = await container.stats(stream=False)
|
||||
|
||||
if await self.redis_client.exists(container_id + '_stats'):
|
||||
stats = json.loads(await self.redis_client.get(container_id + '_stats'))
|
||||
if await self.valkey_client.exists(container_id + '_stats'):
|
||||
stats = json.loads(await self.valkey_client.get(container_id + '_stats'))
|
||||
else:
|
||||
stats = []
|
||||
stats.append(res[0])
|
||||
if len(stats) > 3:
|
||||
del stats[0]
|
||||
await self.redis_client.set(container_id + '_stats', json.dumps(stats), ex=60)
|
||||
await self.valkey_client.set(container_id + '_stats', json.dumps(stats), ex=60)
|
||||
except Exception as e:
|
||||
res = {
|
||||
"type": "danger",
|
||||
|
||||
@@ -3,7 +3,7 @@ FROM alpine:3.21
|
||||
LABEL maintainer="The Infrastructure Company GmbH <info@servercow.de>"
|
||||
|
||||
# renovate: datasource=github-releases depName=tianon/gosu versioning=semver-coerced extractVersion=^(?<version>.*)$
|
||||
ARG GOSU_VERSION=1.16
|
||||
ARG GOSU_VERSION=1.17
|
||||
|
||||
ENV LANG=C.UTF-8
|
||||
ENV LC_ALL=C.UTF-8
|
||||
@@ -118,7 +118,7 @@ RUN addgroup -g 5000 vmail \
|
||||
COPY trim_logs.sh /usr/local/bin/trim_logs.sh
|
||||
COPY clean_q_aged.sh /usr/local/bin/clean_q_aged.sh
|
||||
COPY syslog-ng.conf /etc/syslog-ng/syslog-ng.conf
|
||||
COPY syslog-ng-redis_slave.conf /etc/syslog-ng/syslog-ng-redis_slave.conf
|
||||
COPY syslog-ng-valkey_slave.conf /etc/syslog-ng/syslog-ng-valkey_slave.conf
|
||||
COPY imapsync /usr/local/bin/imapsync
|
||||
COPY imapsync_runner.pl /usr/local/bin/imapsync_runner.pl
|
||||
COPY report-spam.sieve /usr/lib/dovecot/sieve/report-spam.sieve
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
source /source_env.sh
|
||||
|
||||
MAX_AGE=$(redis-cli --raw -h redis-mailcow -a ${REDISPASS} --no-auth-warning GET Q_MAX_AGE)
|
||||
MAX_AGE=$(redis-cli --raw -h valkey-mailcow -a ${VALKEYPASS} --no-auth-warning GET Q_MAX_AGE)
|
||||
|
||||
if [[ -z ${MAX_AGE} ]]; then
|
||||
echo "Max age for quarantine items not defined"
|
||||
|
||||
@@ -13,18 +13,18 @@ until dig +short mailcow.email > /dev/null; do
|
||||
done
|
||||
|
||||
# Do not attempt to write to slave
|
||||
if [[ ! -z ${REDIS_SLAVEOF_IP} ]]; then
|
||||
REDIS_CMDLINE="redis-cli -h ${REDIS_SLAVEOF_IP} -p ${REDIS_SLAVEOF_PORT} -a ${REDISPASS} --no-auth-warning"
|
||||
if [[ ! -z ${VALKEY_SLAVEOF_IP} ]]; then
|
||||
VALKEY_CMDLINE="redis-cli -h ${VALKEY_SLAVEOF_IP} -p ${VALKEY_SLAVEOF_PORT} -a ${VALKEYPASS} --no-auth-warning"
|
||||
else
|
||||
REDIS_CMDLINE="redis-cli -h redis -p 6379 -a ${REDISPASS} --no-auth-warning"
|
||||
VALKEY_CMDLINE="redis-cli -h valkey-mailcow -p 6379 -a ${VALKEYPASS} --no-auth-warning"
|
||||
fi
|
||||
|
||||
until [[ $(${REDIS_CMDLINE} PING) == "PONG" ]]; do
|
||||
echo "Waiting for Redis..."
|
||||
until [[ $(${VALKEY_CMDLINE} PING) == "PONG" ]]; do
|
||||
echo "Waiting for Valkey..."
|
||||
sleep 2
|
||||
done
|
||||
|
||||
${REDIS_CMDLINE} SET DOVECOT_REPL_HEALTH 1 > /dev/null
|
||||
${VALKEY_CMDLINE} SET DOVECOT_REPL_HEALTH 1 > /dev/null
|
||||
|
||||
# Create missing directories
|
||||
[[ ! -d /etc/dovecot/sql/ ]] && mkdir -p /etc/dovecot/sql/
|
||||
@@ -341,8 +341,8 @@ done
|
||||
# May be related to something inside Docker, I seriously don't know
|
||||
touch /etc/dovecot/auth/passwd-verify.lua
|
||||
|
||||
if [[ ! -z ${REDIS_SLAVEOF_IP} ]]; then
|
||||
cp /etc/syslog-ng/syslog-ng-redis_slave.conf /etc/syslog-ng/syslog-ng.conf
|
||||
if [[ ! -z ${VALKEY_SLAVEOF_IP} ]]; then
|
||||
cp /etc/syslog-ng/syslog-ng-valkey_slave.conf /etc/syslog-ng/syslog-ng.conf
|
||||
fi
|
||||
|
||||
exec "$@"
|
||||
|
||||
@@ -132,8 +132,8 @@ while ($row = $sth->fetchrow_arrayref()) {
|
||||
"--tmpdir", "/tmp",
|
||||
"--nofoldersizes",
|
||||
"--addheader",
|
||||
($timeout1 gt "0" ? () : ('--timeout1', $timeout1)),
|
||||
($timeout2 gt "0" ? () : ('--timeout2', $timeout2)),
|
||||
($timeout1 le "0" ? () : ('--timeout1', $timeout1)),
|
||||
($timeout2 le "0" ? () : ('--timeout2', $timeout2)),
|
||||
($exclude eq "" ? () : ("--exclude", $exclude)),
|
||||
($subfolder2 eq "" ? () : ('--subfolder2', $subfolder2)),
|
||||
($maxage eq "0" ? () : ('--maxage', $maxage)),
|
||||
|
||||
@@ -8,7 +8,8 @@ from email.mime.multipart import MIMEMultipart
|
||||
from email.mime.text import MIMEText
|
||||
from email.utils import COMMASPACE, formatdate
|
||||
import jinja2
|
||||
from jinja2 import Template
|
||||
from jinja2 import TemplateError
|
||||
from jinja2.sandbox import SandboxedEnvironment
|
||||
import json
|
||||
import redis
|
||||
import time
|
||||
@@ -31,7 +32,7 @@ try:
|
||||
|
||||
while True:
|
||||
try:
|
||||
r = redis.StrictRedis(host='redis', decode_responses=True, port=6379, db=0, password=os.environ['REDISPASS'])
|
||||
r = redis.StrictRedis(host='valkey-mailcow', decode_responses=True, port=6379, db=0, password=os.environ['VALKEYPASS'])
|
||||
r.ping()
|
||||
except Exception as ex:
|
||||
print('%s - trying again...' % (ex))
|
||||
@@ -75,22 +76,27 @@ try:
|
||||
|
||||
def notify_rcpt(rcpt, msg_count, quarantine_acl, category):
|
||||
if category == "add_header": category = "add header"
|
||||
meta_query = query_mysql('SELECT SHA2(CONCAT(id, qid), 256) AS 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 < %f AND (action = "%s" OR "all" = "%s")' % (rcpt, max_score, category, category))
|
||||
print("%s: %d of %d messages qualify for notification" % (rcpt, len(meta_query), msg_count))
|
||||
if len(meta_query) == 0:
|
||||
return
|
||||
msg_count = len(meta_query)
|
||||
env = SandboxedEnvironment()
|
||||
if r.get('Q_HTML'):
|
||||
try:
|
||||
template = Template(r.get('Q_HTML'))
|
||||
except:
|
||||
print("Error: Cannot parse quarantine template, falling back to default template.")
|
||||
with open('/templates/quarantine.tpl') as file_:
|
||||
template = Template(file_.read())
|
||||
try:
|
||||
template = env.from_string(r.get('Q_HTML'))
|
||||
except Exception:
|
||||
print("Error: Cannot parse quarantine template, falling back to default template.")
|
||||
with open('/templates/quarantine.tpl') as file_:
|
||||
template = env.from_string(file_.read())
|
||||
else:
|
||||
with open('/templates/quarantine.tpl') as file_:
|
||||
template = Template(file_.read())
|
||||
html = template.render(meta=meta_query, username=rcpt, counter=msg_count, hostname=mailcow_hostname, quarantine_acl=quarantine_acl)
|
||||
with open('/templates/quarantine.tpl') as file_:
|
||||
template = env.from_string(file_.read())
|
||||
try:
|
||||
html = template.render(meta=meta_query, username=rcpt, counter=msg_count, hostname=mailcow_hostname, quarantine_acl=quarantine_acl)
|
||||
except (jinja2.exceptions.SecurityError, TemplateError) as ex:
|
||||
print(f"SecurityError or TemplateError in template rendering: {ex}")
|
||||
return
|
||||
text = html2text.html2text(html)
|
||||
count = 0
|
||||
while count < 15:
|
||||
|
||||
@@ -6,7 +6,7 @@ from email.mime.multipart import MIMEMultipart
|
||||
from email.mime.text import MIMEText
|
||||
from email.utils import COMMASPACE, formatdate
|
||||
import jinja2
|
||||
from jinja2 import Template
|
||||
from jinja2.sandbox import SandboxedEnvironment
|
||||
import redis
|
||||
import time
|
||||
import json
|
||||
@@ -23,7 +23,7 @@ else:
|
||||
|
||||
while True:
|
||||
try:
|
||||
r = redis.StrictRedis(host='redis', decode_responses=True, port=6379, db=0, username='quota_notify', password='')
|
||||
r = redis.StrictRedis(host='valkey-mailcow', decode_responses=True, port=6379, db=0, username='quota_notify', password='')
|
||||
r.ping()
|
||||
except Exception as ex:
|
||||
print('%s - trying again...' % (ex))
|
||||
@@ -33,16 +33,24 @@ while True:
|
||||
|
||||
if r.get('QW_HTML'):
|
||||
try:
|
||||
template = Template(r.get('QW_HTML'))
|
||||
except:
|
||||
print("Error: Cannot parse quarantine template, falling back to default template.")
|
||||
env = SandboxedEnvironment()
|
||||
template = env.from_string(r.get('QW_HTML'))
|
||||
except Exception:
|
||||
print("Error: Cannot parse quota template, falling back to default template.")
|
||||
with open('/templates/quota.tpl') as file_:
|
||||
template = Template(file_.read())
|
||||
env = SandboxedEnvironment()
|
||||
template = env.from_string(file_.read())
|
||||
else:
|
||||
with open('/templates/quota.tpl') as file_:
|
||||
template = Template(file_.read())
|
||||
env = SandboxedEnvironment()
|
||||
template = env.from_string(file_.read())
|
||||
|
||||
try:
|
||||
html = template.render(username=username, percent=percent)
|
||||
except (jinja2.exceptions.SecurityError, jinja2.TemplateError) as ex:
|
||||
print(f"SecurityError or TemplateError in template rendering: {ex}")
|
||||
sys.exit(1)
|
||||
|
||||
html = template.render(username=username, percent=percent)
|
||||
text = html2text.html2text(html)
|
||||
|
||||
try:
|
||||
|
||||
@@ -3,16 +3,16 @@
|
||||
source /source_env.sh
|
||||
|
||||
# Do not attempt to write to slave
|
||||
if [[ ! -z ${REDIS_SLAVEOF_IP} ]]; then
|
||||
REDIS_CMDLINE="redis-cli -h ${REDIS_SLAVEOF_IP} -p ${REDIS_SLAVEOF_PORT} -a ${REDISPASS} --no-auth-warning"
|
||||
if [[ ! -z ${VALKEY_SLAVEOF_IP} ]]; then
|
||||
VALKEY_CMDLINE="redis-cli -h ${VALKEY_SLAVEOF_IP} -p ${VALKEY_SLAVEOF_PORT} -a ${VALKEYPASS} --no-auth-warning"
|
||||
else
|
||||
REDIS_CMDLINE="redis-cli -h redis -p 6379 -a ${REDISPASS} --no-auth-warning"
|
||||
VALKEY_CMDLINE="redis-cli -h valkey-mailcow -p 6379 -a ${VALKEYPASS} --no-auth-warning"
|
||||
fi
|
||||
|
||||
# Is replication active?
|
||||
# grep on file is less expensive than doveconf
|
||||
if [ -n ${MAILCOW_REPLICA_IP} ]; then
|
||||
${REDIS_CMDLINE} SET DOVECOT_REPL_HEALTH 1 > /dev/null
|
||||
${VALKEY_CMDLINE} SET DOVECOT_REPL_HEALTH 1 > /dev/null
|
||||
exit
|
||||
fi
|
||||
|
||||
@@ -22,7 +22,7 @@ FAILED_SYNCS=$(doveadm replicator status | grep "Waiting 'failed' requests" | gr
|
||||
# 1 failed job for mailcow.local is expected and healthy
|
||||
if [[ "${FAILED_SYNCS}" != 0 ]] && [[ "${FAILED_SYNCS}" != 1 ]]; then
|
||||
printf "Dovecot replicator has %d failed jobs\n" "${FAILED_SYNCS}"
|
||||
${REDIS_CMDLINE} SET DOVECOT_REPL_HEALTH "${FAILED_SYNCS}" > /dev/null
|
||||
${VALKEY_CMDLINE} SET DOVECOT_REPL_HEALTH "${FAILED_SYNCS}" > /dev/null
|
||||
else
|
||||
${REDIS_CMDLINE} SET DOVECOT_REPL_HEALTH 1 > /dev/null
|
||||
${VALKEY_CMDLINE} SET DOVECOT_REPL_HEALTH 1 > /dev/null
|
||||
fi
|
||||
|
||||
@@ -15,21 +15,21 @@ source s_dgram {
|
||||
internal();
|
||||
};
|
||||
destination d_stdout { pipe("/dev/stdout"); };
|
||||
destination d_redis_ui_log {
|
||||
destination d_valkey_ui_log {
|
||||
redis(
|
||||
host("`REDIS_SLAVEOF_IP`")
|
||||
persist-name("redis1")
|
||||
port(`REDIS_SLAVEOF_PORT`)
|
||||
auth("`REDISPASS`")
|
||||
host("`VALKEY_SLAVEOF_IP`")
|
||||
persist-name("valkey1")
|
||||
port(`VALKEY_SLAVEOF_PORT`)
|
||||
auth("`VALKEYPASS`")
|
||||
command("LPUSH" "DOVECOT_MAILLOG" "$(format-json time=\"$S_UNIXTIME\" priority=\"$PRIORITY\" program=\"$PROGRAM\" message=\"$MESSAGE\")\n")
|
||||
);
|
||||
};
|
||||
destination d_redis_f2b_channel {
|
||||
destination d_valkey_f2b_channel {
|
||||
redis(
|
||||
host("`REDIS_SLAVEOF_IP`")
|
||||
persist-name("redis2")
|
||||
port(`REDIS_SLAVEOF_PORT`)
|
||||
auth("`REDISPASS`")
|
||||
host("`VALKEY_SLAVEOF_IP`")
|
||||
persist-name("valkey2")
|
||||
port(`VALKEY_SLAVEOF_PORT`)
|
||||
auth("`VALKEYPASS`")
|
||||
command("PUBLISH" "F2B_CHANNEL" "$(sanitize $MESSAGE)")
|
||||
);
|
||||
};
|
||||
@@ -48,6 +48,6 @@ log {
|
||||
filter(f_replica);
|
||||
destination(d_stdout);
|
||||
filter(f_mail);
|
||||
destination(d_redis_ui_log);
|
||||
destination(d_redis_f2b_channel);
|
||||
destination(d_valkey_ui_log);
|
||||
destination(d_valkey_f2b_channel);
|
||||
};
|
||||
@@ -15,21 +15,21 @@ source s_dgram {
|
||||
internal();
|
||||
};
|
||||
destination d_stdout { pipe("/dev/stdout"); };
|
||||
destination d_redis_ui_log {
|
||||
destination d_valkey_ui_log {
|
||||
redis(
|
||||
host("redis-mailcow")
|
||||
persist-name("redis1")
|
||||
host("valkey-mailcow")
|
||||
persist-name("valkey1")
|
||||
port(6379)
|
||||
auth("`REDISPASS`")
|
||||
auth("`VALKEYPASS`")
|
||||
command("LPUSH" "DOVECOT_MAILLOG" "$(format-json time=\"$S_UNIXTIME\" priority=\"$PRIORITY\" program=\"$PROGRAM\" message=\"$MESSAGE\")\n")
|
||||
);
|
||||
};
|
||||
destination d_redis_f2b_channel {
|
||||
destination d_valkey_f2b_channel {
|
||||
redis(
|
||||
host("redis-mailcow")
|
||||
persist-name("redis2")
|
||||
host("valkey-mailcow")
|
||||
persist-name("valkey2")
|
||||
port(6379)
|
||||
auth("`REDISPASS`")
|
||||
auth("`VALKEYPASS`")
|
||||
command("PUBLISH" "F2B_CHANNEL" "$(sanitize $MESSAGE)")
|
||||
);
|
||||
};
|
||||
@@ -48,6 +48,6 @@ log {
|
||||
filter(f_replica);
|
||||
destination(d_stdout);
|
||||
filter(f_mail);
|
||||
destination(d_redis_ui_log);
|
||||
destination(d_redis_f2b_channel);
|
||||
destination(d_valkey_ui_log);
|
||||
destination(d_valkey_f2b_channel);
|
||||
};
|
||||
|
||||
@@ -9,18 +9,17 @@ catch_non_zero() {
|
||||
}
|
||||
source /source_env.sh
|
||||
# Do not attempt to write to slave
|
||||
if [[ ! -z ${REDIS_SLAVEOF_IP} ]]; then
|
||||
REDIS_CMDLINE="redis-cli -h ${REDIS_SLAVEOF_IP} -p ${REDIS_SLAVEOF_PORT} -a ${REDISPASS} --no-auth-warning"
|
||||
if [[ ! -z ${VALKEY_SLAVEOF_IP} ]]; then
|
||||
VALKEY_CMDLINE="redis-cli -h ${VALKEY_SLAVEOF_IP} -p ${VALKEY_SLAVEOF_PORT} -a ${VALKEYPASS} --no-auth-warning"
|
||||
else
|
||||
REDIS_CMDLINE="redis-cli -h redis -p 6379 -a ${REDISPASS} --no-auth-warning"
|
||||
VALKEY_CMDLINE="redis-cli -h valkey-mailcow -p 6379 -a ${VALKEYPASS} --no-auth-warning"
|
||||
fi
|
||||
catch_non_zero "${REDIS_CMDLINE} LTRIM ACME_LOG 0 ${LOG_LINES}"
|
||||
catch_non_zero "${REDIS_CMDLINE} LTRIM POSTFIX_MAILLOG 0 ${LOG_LINES}"
|
||||
catch_non_zero "${REDIS_CMDLINE} LTRIM DOVECOT_MAILLOG 0 ${LOG_LINES}"
|
||||
catch_non_zero "${REDIS_CMDLINE} LTRIM SOGO_LOG 0 ${LOG_LINES}"
|
||||
catch_non_zero "${REDIS_CMDLINE} LTRIM NETFILTER_LOG 0 ${LOG_LINES}"
|
||||
catch_non_zero "${REDIS_CMDLINE} LTRIM AUTODISCOVER_LOG 0 ${LOG_LINES}"
|
||||
catch_non_zero "${REDIS_CMDLINE} LTRIM API_LOG 0 ${LOG_LINES}"
|
||||
catch_non_zero "${REDIS_CMDLINE} LTRIM RL_LOG 0 ${LOG_LINES}"
|
||||
catch_non_zero "${REDIS_CMDLINE} LTRIM WATCHDOG_LOG 0 ${LOG_LINES}"
|
||||
catch_non_zero "${REDIS_CMDLINE} LTRIM CRON_LOG 0 ${LOG_LINES}"
|
||||
catch_non_zero "${VALKEY_CMDLINE} LTRIM ACME_LOG 0 ${LOG_LINES}"
|
||||
catch_non_zero "${VALKEY_CMDLINE} LTRIM POSTFIX_MAILLOG 0 ${LOG_LINES}"
|
||||
catch_non_zero "${VALKEY_CMDLINE} LTRIM DOVECOT_MAILLOG 0 ${LOG_LINES}"
|
||||
catch_non_zero "${VALKEY_CMDLINE} LTRIM SOGO_LOG 0 ${LOG_LINES}"
|
||||
catch_non_zero "${VALKEY_CMDLINE} LTRIM NETFILTER_LOG 0 ${LOG_LINES}"
|
||||
catch_non_zero "${VALKEY_CMDLINE} LTRIM AUTODISCOVER_LOG 0 ${LOG_LINES}"
|
||||
catch_non_zero "${VALKEY_CMDLINE} LTRIM API_LOG 0 ${LOG_LINES}"
|
||||
catch_non_zero "${VALKEY_CMDLINE} LTRIM RL_LOG 0 ${LOG_LINES}"
|
||||
catch_non_zero "${VALKEY_CMDLINE} LTRIM WATCHDOG_LOG 0 ${LOG_LINES}"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/bin/sh
|
||||
|
||||
backend=iptables
|
||||
backend=nftables
|
||||
|
||||
nft list table ip filter &>/dev/null
|
||||
nftables_found=$?
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
DEBUG = False
|
||||
|
||||
import re
|
||||
import os
|
||||
import sys
|
||||
@@ -20,10 +22,13 @@ from modules.Logger import Logger
|
||||
from modules.IPTables import IPTables
|
||||
from modules.NFTables import NFTables
|
||||
|
||||
def logdebug(msg):
|
||||
if DEBUG:
|
||||
logger.logInfo("DEBUG: %s" % msg)
|
||||
|
||||
# globals
|
||||
# Globals
|
||||
WHITELIST = []
|
||||
BLACKLIST= []
|
||||
BLACKLIST = []
|
||||
bans = {}
|
||||
quit_now = False
|
||||
exit_code = 0
|
||||
@@ -33,43 +38,41 @@ r = None
|
||||
pubsub = None
|
||||
clear_before_quit = False
|
||||
|
||||
|
||||
def refreshF2boptions():
|
||||
global f2boptions
|
||||
global quit_now
|
||||
global exit_code
|
||||
|
||||
f2boptions = {}
|
||||
|
||||
if not r.get('F2B_OPTIONS'):
|
||||
f2boptions['ban_time'] = r.get('F2B_BAN_TIME')
|
||||
f2boptions['max_ban_time'] = r.get('F2B_MAX_BAN_TIME')
|
||||
f2boptions['ban_time_increment'] = r.get('F2B_BAN_TIME_INCREMENT')
|
||||
f2boptions['max_attempts'] = r.get('F2B_MAX_ATTEMPTS')
|
||||
f2boptions['retry_window'] = r.get('F2B_RETRY_WINDOW')
|
||||
f2boptions['netban_ipv4'] = r.get('F2B_NETBAN_IPV4')
|
||||
f2boptions['netban_ipv6'] = r.get('F2B_NETBAN_IPV6')
|
||||
if not valkey.get('F2B_OPTIONS'):
|
||||
f2boptions['ban_time'] = valkey.get('F2B_BAN_TIME')
|
||||
f2boptions['max_ban_time'] = valkey.get('F2B_MAX_BAN_TIME')
|
||||
f2boptions['ban_time_increment'] = valkey.get('F2B_BAN_TIME_INCREMENT')
|
||||
f2boptions['max_attempts'] = valkey.get('F2B_MAX_ATTEMPTS')
|
||||
f2boptions['retry_window'] = valkey.get('F2B_RETRY_WINDOW')
|
||||
f2boptions['netban_ipv4'] = valkey.get('F2B_NETBAN_IPV4')
|
||||
f2boptions['netban_ipv6'] = valkey.get('F2B_NETBAN_IPV6')
|
||||
else:
|
||||
try:
|
||||
f2boptions = json.loads(r.get('F2B_OPTIONS'))
|
||||
f2boptions = json.loads(valkey.get('F2B_OPTIONS'))
|
||||
except ValueError:
|
||||
logger.logCrit('Error loading F2B options: F2B_OPTIONS is not json')
|
||||
quit_now = True
|
||||
exit_code = 2
|
||||
|
||||
verifyF2boptions(f2boptions)
|
||||
r.set('F2B_OPTIONS', json.dumps(f2boptions, ensure_ascii=False))
|
||||
valkey.set('F2B_OPTIONS', json.dumps(f2boptions, ensure_ascii=False))
|
||||
|
||||
def verifyF2boptions(f2boptions):
|
||||
verifyF2boption(f2boptions,'ban_time', 1800)
|
||||
verifyF2boption(f2boptions,'max_ban_time', 10000)
|
||||
verifyF2boption(f2boptions,'ban_time_increment', True)
|
||||
verifyF2boption(f2boptions,'max_attempts', 10)
|
||||
verifyF2boption(f2boptions,'retry_window', 600)
|
||||
verifyF2boption(f2boptions,'netban_ipv4', 32)
|
||||
verifyF2boption(f2boptions,'netban_ipv6', 128)
|
||||
verifyF2boption(f2boptions,'banlist_id', str(uuid.uuid4()))
|
||||
verifyF2boption(f2boptions,'manage_external', 0)
|
||||
verifyF2boption(f2boptions, 'ban_time', 1800)
|
||||
verifyF2boption(f2boptions, 'max_ban_time', 10000)
|
||||
verifyF2boption(f2boptions, 'ban_time_increment', True)
|
||||
verifyF2boption(f2boptions, 'max_attempts', 10)
|
||||
verifyF2boption(f2boptions, 'retry_window', 600)
|
||||
verifyF2boption(f2boptions, 'netban_ipv4', 32)
|
||||
verifyF2boption(f2boptions, 'netban_ipv6', 128)
|
||||
verifyF2boption(f2boptions, 'banlist_id', str(uuid.uuid4()))
|
||||
verifyF2boption(f2boptions, 'manage_external', 0)
|
||||
|
||||
def verifyF2boption(f2boptions, f2boption, f2bdefault):
|
||||
f2boptions[f2boption] = f2boptions[f2boption] if f2boption in f2boptions and f2boptions[f2boption] is not None else f2bdefault
|
||||
@@ -78,7 +81,7 @@ def refreshF2bregex():
|
||||
global f2bregex
|
||||
global quit_now
|
||||
global exit_code
|
||||
if not r.get('F2B_REGEX'):
|
||||
if not valkey.get('F2B_REGEX'):
|
||||
f2bregex = {}
|
||||
f2bregex[1] = r'mailcow UI: Invalid password for .+ by ([0-9a-f\.:]+)'
|
||||
f2bregex[2] = r'Rspamd UI: Invalid password by ([0-9a-f\.:]+)'
|
||||
@@ -89,11 +92,11 @@ def refreshF2bregex():
|
||||
f2bregex[7] = r'\w+\([^,]+,([0-9a-f\.:]+),<[^>]+>\): unknown user \(SHA1 of given password: [a-f0-9]+\)'
|
||||
f2bregex[8] = r'SOGo.+ Login from \'([0-9a-f\.:]+)\' for user .+ might not have worked'
|
||||
f2bregex[9] = r'([0-9a-f\.:]+) \"GET \/SOGo\/.* HTTP.+\" 403 .+'
|
||||
r.set('F2B_REGEX', json.dumps(f2bregex, ensure_ascii=False))
|
||||
valkey.set('F2B_REGEX', json.dumps(f2bregex, ensure_ascii=False))
|
||||
else:
|
||||
try:
|
||||
f2bregex = {}
|
||||
f2bregex = json.loads(r.get('F2B_REGEX'))
|
||||
f2bregex = json.loads(valkey.get('F2B_REGEX'))
|
||||
except ValueError:
|
||||
logger.logCrit('Error loading F2B options: F2B_REGEX is not json')
|
||||
quit_now = True
|
||||
@@ -111,7 +114,7 @@ def get_ip(address):
|
||||
def ban(address):
|
||||
global f2boptions
|
||||
global lock
|
||||
|
||||
logdebug("ban() called with address=%s" % address)
|
||||
refreshF2boptions()
|
||||
MAX_ATTEMPTS = int(f2boptions['max_attempts'])
|
||||
RETRY_WINDOW = int(f2boptions['retry_window'])
|
||||
@@ -119,31 +122,43 @@ def ban(address):
|
||||
NETBAN_IPV6 = '/' + str(f2boptions['netban_ipv6'])
|
||||
|
||||
ip = get_ip(address)
|
||||
if not ip: return
|
||||
if not ip:
|
||||
logdebug("No valid IP -- skipping ban()")
|
||||
return
|
||||
address = str(ip)
|
||||
self_network = ipaddress.ip_network(address)
|
||||
|
||||
with lock:
|
||||
temp_whitelist = set(WHITELIST)
|
||||
if temp_whitelist:
|
||||
for wl_key in temp_whitelist:
|
||||
wl_net = ipaddress.ip_network(wl_key, False)
|
||||
if wl_net.overlaps(self_network):
|
||||
logger.logInfo('Address %s is whitelisted by rule %s' % (self_network, wl_net))
|
||||
return
|
||||
logdebug("Checking if %s overlaps with any WHITELIST entries" % self_network)
|
||||
if temp_whitelist:
|
||||
for wl_key in temp_whitelist:
|
||||
wl_net = ipaddress.ip_network(wl_key, False)
|
||||
logdebug("Checking overlap between %s and %s" % (self_network, wl_net))
|
||||
if wl_net.overlaps(self_network):
|
||||
logger.logInfo(
|
||||
'Address %s is allowlisted by rule %s' % (self_network, wl_net))
|
||||
return
|
||||
|
||||
net = ipaddress.ip_network((address + (NETBAN_IPV4 if type(ip) is ipaddress.IPv4Address else NETBAN_IPV6)), strict=False)
|
||||
net = ipaddress.ip_network(
|
||||
(address + (NETBAN_IPV4 if type(ip) is ipaddress.IPv4Address else NETBAN_IPV6)), strict=False)
|
||||
net = str(net)
|
||||
logdebug("Ban net: %s" % net)
|
||||
|
||||
if not net in bans:
|
||||
bans[net] = {'attempts': 0, 'last_attempt': 0, 'ban_counter': 0}
|
||||
logdebug("Initing new ban counter for %s" % net)
|
||||
|
||||
current_attempt = time.time()
|
||||
logdebug("Current attempt ts=%s, previous: %s, retry_window: %s" %
|
||||
(current_attempt, bans[net]['last_attempt'], RETRY_WINDOW))
|
||||
if current_attempt - bans[net]['last_attempt'] > RETRY_WINDOW:
|
||||
bans[net]['attempts'] = 0
|
||||
logdebug("Ban counter for %s reset as window expired" % net)
|
||||
|
||||
bans[net]['attempts'] += 1
|
||||
bans[net]['last_attempt'] = current_attempt
|
||||
logdebug("%s attempts now %d" % (net, bans[net]['attempts']))
|
||||
|
||||
if bans[net]['attempts'] >= MAX_ATTEMPTS:
|
||||
cur_time = int(round(time.time()))
|
||||
@@ -151,34 +166,41 @@ def ban(address):
|
||||
logger.logCrit('Banning %s for %d minutes' % (net, NET_BAN_TIME / 60 ))
|
||||
if type(ip) is ipaddress.IPv4Address and int(f2boptions['manage_external']) != 1:
|
||||
with lock:
|
||||
logdebug("Calling tables.banIPv4(%s)" % net)
|
||||
tables.banIPv4(net)
|
||||
elif int(f2boptions['manage_external']) != 1:
|
||||
with lock:
|
||||
logdebug("Calling tables.banIPv6(%s)" % net)
|
||||
tables.banIPv6(net)
|
||||
|
||||
r.hset('F2B_ACTIVE_BANS', '%s' % net, cur_time + NET_BAN_TIME)
|
||||
logdebug("Updating F2B_ACTIVE_BANS[%s]=%d" %
|
||||
(net, cur_time + NET_BAN_TIME))
|
||||
valkey.hset('F2B_ACTIVE_BANS', '%s' % net, cur_time + NET_BAN_TIME)
|
||||
else:
|
||||
logger.logWarn('%d more attempts in the next %d seconds until %s is banned' % (MAX_ATTEMPTS - bans[net]['attempts'], RETRY_WINDOW, net))
|
||||
logger.logWarn('%d more attempts in the next %d seconds until %s is banned' % (
|
||||
MAX_ATTEMPTS - bans[net]['attempts'], RETRY_WINDOW, net))
|
||||
|
||||
def unban(net):
|
||||
global lock
|
||||
|
||||
logdebug("Calling unban() with net=%s" % net)
|
||||
if not net in bans:
|
||||
logger.logInfo('%s is not banned, skipping unban and deleting from queue (if any)' % net)
|
||||
r.hdel('F2B_QUEUE_UNBAN', '%s' % net)
|
||||
return
|
||||
|
||||
logger.logInfo(
|
||||
'%s is not banned, skipping unban and deleting from queue (if any)' % net)
|
||||
valkey.hdel('F2B_QUEUE_UNBAN', '%s' % net)
|
||||
return
|
||||
logger.logInfo('Unbanning %s' % net)
|
||||
if type(ipaddress.ip_network(net)) is ipaddress.IPv4Network:
|
||||
with lock:
|
||||
logdebug("Calling tables.unbanIPv4(%s)" % net)
|
||||
tables.unbanIPv4(net)
|
||||
else:
|
||||
with lock:
|
||||
logdebug("Calling tables.unbanIPv6(%s)" % net)
|
||||
tables.unbanIPv6(net)
|
||||
|
||||
r.hdel('F2B_ACTIVE_BANS', '%s' % net)
|
||||
r.hdel('F2B_QUEUE_UNBAN', '%s' % net)
|
||||
valkey.hdel('F2B_ACTIVE_BANS', '%s' % net)
|
||||
valkey.hdel('F2B_QUEUE_UNBAN', '%s' % net)
|
||||
if net in bans:
|
||||
logdebug("Unban for %s, setting attempts=0, ban_counter+=1" % net)
|
||||
bans[net]['attempts'] = 0
|
||||
bans[net]['ban_counter'] += 1
|
||||
|
||||
@@ -203,33 +225,35 @@ def permBan(net, unban=False):
|
||||
|
||||
|
||||
if is_unbanned:
|
||||
r.hdel('F2B_PERM_BANS', '%s' % net)
|
||||
logger.logCrit('Removed host/network %s from blacklist' % net)
|
||||
valkey.hdel('F2B_PERM_BANS', '%s' % net)
|
||||
logger.logCrit('Removed host/network %s from denylist' % net)
|
||||
elif is_banned:
|
||||
r.hset('F2B_PERM_BANS', '%s' % net, int(round(time.time())))
|
||||
logger.logCrit('Added host/network %s to blacklist' % net)
|
||||
valkey.hset('F2B_PERM_BANS', '%s' % net, int(round(time.time())))
|
||||
logger.logCrit('Added host/network %s to denylist' % net)
|
||||
|
||||
def clear():
|
||||
global lock
|
||||
logger.logInfo('Clearing all bans')
|
||||
for net in bans.copy():
|
||||
logdebug("Unbanning net: %s" % net)
|
||||
unban(net)
|
||||
with lock:
|
||||
logdebug("Clearing IPv4/IPv6 table")
|
||||
tables.clearIPv4Table()
|
||||
tables.clearIPv6Table()
|
||||
try:
|
||||
if r is not None:
|
||||
r.delete('F2B_ACTIVE_BANS')
|
||||
r.delete('F2B_PERM_BANS')
|
||||
valkey.delete('F2B_ACTIVE_BANS')
|
||||
valkey.delete('F2B_PERM_BANS')
|
||||
except Exception as ex:
|
||||
logger.logWarn('Error clearing redis keys F2B_ACTIVE_BANS and F2B_PERM_BANS: %s' % ex)
|
||||
logger.logWarn('Error clearing valkey keys F2B_ACTIVE_BANS and F2B_PERM_BANS: %s' % ex)
|
||||
|
||||
def watch():
|
||||
global pubsub
|
||||
global quit_now
|
||||
global exit_code
|
||||
|
||||
logger.logInfo('Watching Redis channel F2B_CHANNEL')
|
||||
logger.logInfo('Watching Valkey channel F2B_CHANNEL')
|
||||
pubsub.subscribe('F2B_CHANNEL')
|
||||
|
||||
while not quit_now:
|
||||
@@ -275,21 +299,35 @@ def snat6(snat_target):
|
||||
|
||||
def autopurge():
|
||||
global f2boptions
|
||||
|
||||
logdebug("autopurge thread started")
|
||||
while not quit_now:
|
||||
logdebug("autopurge tick")
|
||||
time.sleep(10)
|
||||
refreshF2boptions()
|
||||
MAX_ATTEMPTS = int(f2boptions['max_attempts'])
|
||||
QUEUE_UNBAN = r.hgetall('F2B_QUEUE_UNBAN')
|
||||
QUEUE_UNBAN = valkey.hgetall('F2B_QUEUE_UNBAN')
|
||||
logdebug("QUEUE_UNBAN: %s" % QUEUE_UNBAN)
|
||||
if QUEUE_UNBAN:
|
||||
for net in QUEUE_UNBAN:
|
||||
logdebug("Autopurge: unbanning queued net: %s" % net)
|
||||
unban(str(net))
|
||||
for net in bans.copy():
|
||||
if bans[net]['attempts'] >= MAX_ATTEMPTS:
|
||||
NET_BAN_TIME = calcNetBanTime(bans[net]['ban_counter'])
|
||||
TIME_SINCE_LAST_ATTEMPT = time.time() - bans[net]['last_attempt']
|
||||
if TIME_SINCE_LAST_ATTEMPT > NET_BAN_TIME:
|
||||
unban(net)
|
||||
# Only check expiry for actively banned IPs:
|
||||
active_bans = r.hgetall('F2B_ACTIVE_BANS')
|
||||
now = time.time()
|
||||
for net_str, expire_str in active_bans.items():
|
||||
logdebug("Checking ban expiry for (actively banned): %s" % net_str)
|
||||
# Defensive: always process if timer missing or expired
|
||||
try:
|
||||
expire = float(expire_str)
|
||||
except Exception:
|
||||
logdebug("Invalid expire time for %s; unbanning" % net_str)
|
||||
unban(net_str)
|
||||
continue
|
||||
time_left = expire - now
|
||||
logdebug("Time left for %s: %.1f seconds" % (net_str, time_left))
|
||||
if time_left <= 0:
|
||||
logdebug("Ban expired for %s" % net_str)
|
||||
unban(net_str)
|
||||
|
||||
def mailcowChainOrder():
|
||||
global lock
|
||||
@@ -352,14 +390,14 @@ def whitelistUpdate():
|
||||
global WHITELIST
|
||||
while not quit_now:
|
||||
start_time = time.time()
|
||||
list = r.hgetall('F2B_WHITELIST')
|
||||
list = valkey.hgetall('F2B_WHITELIST')
|
||||
new_whitelist = []
|
||||
if list:
|
||||
new_whitelist = genNetworkList(list)
|
||||
with lock:
|
||||
if Counter(new_whitelist) != Counter(WHITELIST):
|
||||
WHITELIST = new_whitelist
|
||||
logger.logInfo('Whitelist was changed, it has %s entries' % len(WHITELIST))
|
||||
logger.logInfo('Allowlist was changed, it has %s entries' % len(WHITELIST))
|
||||
time.sleep(60.0 - ((time.time() - start_time) % 60.0))
|
||||
|
||||
def blacklistUpdate():
|
||||
@@ -367,7 +405,7 @@ def blacklistUpdate():
|
||||
global BLACKLIST
|
||||
while not quit_now:
|
||||
start_time = time.time()
|
||||
list = r.hgetall('F2B_BLACKLIST')
|
||||
list = valkey.hgetall('F2B_BLACKLIST')
|
||||
new_blacklist = []
|
||||
if list:
|
||||
new_blacklist = genNetworkList(list)
|
||||
@@ -375,7 +413,7 @@ def blacklistUpdate():
|
||||
addban = set(new_blacklist).difference(BLACKLIST)
|
||||
delban = set(BLACKLIST).difference(new_blacklist)
|
||||
BLACKLIST = new_blacklist
|
||||
logger.logInfo('Blacklist was changed, it has %s entries' % len(BLACKLIST))
|
||||
logger.logInfo('Denylist was changed, it has %s entries' % len(BLACKLIST))
|
||||
if addban:
|
||||
for net in addban:
|
||||
permBan(net=net)
|
||||
@@ -386,71 +424,77 @@ def blacklistUpdate():
|
||||
|
||||
def sigterm_quit(signum, frame):
|
||||
global clear_before_quit
|
||||
logdebug("SIGTERM received, setting clear_before_quit to True and exiting")
|
||||
clear_before_quit = True
|
||||
sys.exit(exit_code)
|
||||
|
||||
def berfore_quit():
|
||||
def before_quit():
|
||||
logdebug("before_quit called, clear_before_quit=%s" % clear_before_quit)
|
||||
if clear_before_quit:
|
||||
clear()
|
||||
if pubsub is not None:
|
||||
pubsub.unsubscribe()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
atexit.register(berfore_quit)
|
||||
logger = Logger()
|
||||
logdebug("Sys.argv: %s" % sys.argv)
|
||||
atexit.register(before_quit)
|
||||
signal.signal(signal.SIGTERM, sigterm_quit)
|
||||
|
||||
# init Logger
|
||||
logger = Logger()
|
||||
|
||||
# init backend
|
||||
backend = sys.argv[1]
|
||||
logdebug("Backend: %s" % backend)
|
||||
if backend == "nftables":
|
||||
logger.logInfo('Using NFTables backend')
|
||||
tables = NFTables(chain_name, logger)
|
||||
else:
|
||||
logger.logInfo('Using IPTables backend')
|
||||
logger.logWarn(
|
||||
"DEPRECATION: iptables-legacy is deprecated and will be removed in future releases. "
|
||||
"Please switch to nftables on your host to ensure complete compatibility."
|
||||
)
|
||||
time.sleep(5)
|
||||
tables = IPTables(chain_name, logger)
|
||||
|
||||
# In case a previous session was killed without cleanup
|
||||
clear()
|
||||
|
||||
# Reinit MAILCOW chain
|
||||
# Is called before threads start, no locking
|
||||
logger.logInfo("Initializing mailcow netfilter chain")
|
||||
tables.initChainIPv4()
|
||||
tables.initChainIPv6()
|
||||
|
||||
if os.getenv("DISABLE_NETFILTER_ISOLATION_RULE").lower() in ("y", "yes"):
|
||||
if os.getenv("DISABLE_NETFILTER_ISOLATION_RULE", "").lower() in ("y", "yes"):
|
||||
logger.logInfo(f"Skipping {chain_name} isolation")
|
||||
else:
|
||||
logger.logInfo(f"Setting {chain_name} isolation")
|
||||
tables.create_mailcow_isolation_rule("br-mailcow", [3306, 6379, 8983, 12345], os.getenv("MAILCOW_REPLICA_IP"))
|
||||
|
||||
# connect to redis
|
||||
# connect to valkey
|
||||
while True:
|
||||
try:
|
||||
redis_slaveof_ip = os.getenv('REDIS_SLAVEOF_IP', '')
|
||||
redis_slaveof_port = os.getenv('REDIS_SLAVEOF_PORT', '')
|
||||
if "".__eq__(redis_slaveof_ip):
|
||||
r = redis.StrictRedis(host=os.getenv('IPV4_NETWORK', '172.22.1') + '.249', decode_responses=True, port=6379, db=0, password=os.environ['REDISPASS'])
|
||||
valkey_slaveof_ip = os.getenv('VALKEY_SLAVEOF_IP', '')
|
||||
valkey_slaveof_port = os.getenv('VALKEY_SLAVEOF_PORT', '')
|
||||
logdebug(
|
||||
"Connecting valkey (SLAVEOF_IP:%s, PORT:%s)" % (valkey_slaveof_ip, valkey_slaveof_port))
|
||||
if "".__eq__(valkey_slaveof_ip):
|
||||
valkey = redis.StrictRedis(
|
||||
host=os.getenv('IPV4_NETWORK', '172.22.1') + '.249', decode_responses=True, port=6379, db=0, password=os.environ['VALKEYPASS'])
|
||||
else:
|
||||
r = redis.StrictRedis(host=redis_slaveof_ip, decode_responses=True, port=redis_slaveof_port, db=0, password=os.environ['REDISPASS'])
|
||||
r.ping()
|
||||
pubsub = r.pubsub()
|
||||
valkey = redis.StrictRedis(
|
||||
host=valkey_slaveof_ip, decode_responses=True, port=valkey_slaveof_port, db=0, password=os.environ['VALKEYPASS'])
|
||||
valkey.ping()
|
||||
pubsub = valkey.pubsub()
|
||||
except Exception as ex:
|
||||
print('%s - trying again in 3 seconds' % (ex))
|
||||
logdebug(
|
||||
'Redis connection failed: %s - trying again in 3 seconds' % (ex))
|
||||
time.sleep(3)
|
||||
else:
|
||||
break
|
||||
logger.set_redis(r)
|
||||
logger.set_valkey(valkey)
|
||||
logdebug("Valkey connection established, setting up F2B keys")
|
||||
|
||||
# rename fail2ban to netfilter
|
||||
if r.exists('F2B_LOG'):
|
||||
r.rename('F2B_LOG', 'NETFILTER_LOG')
|
||||
# clear bans in redis
|
||||
r.delete('F2B_ACTIVE_BANS')
|
||||
r.delete('F2B_PERM_BANS')
|
||||
if valkey.exists('F2B_LOG'):
|
||||
logdebug("Renaming F2B_LOG to NETFILTER_LOG")
|
||||
valkey.rename('F2B_LOG', 'NETFILTER_LOG')
|
||||
valkey.delete('F2B_ACTIVE_BANS')
|
||||
valkey.delete('F2B_PERM_BANS')
|
||||
|
||||
refreshF2boptions()
|
||||
|
||||
@@ -463,7 +507,7 @@ if __name__ == '__main__':
|
||||
snat_ip = os.getenv('SNAT_TO_SOURCE')
|
||||
snat_ipo = ipaddress.ip_address(snat_ip)
|
||||
if type(snat_ipo) is ipaddress.IPv4Address:
|
||||
snat4_thread = Thread(target=snat4,args=(snat_ip,))
|
||||
snat4_thread = Thread(target=snat4, args=(snat_ip,))
|
||||
snat4_thread.daemon = True
|
||||
snat4_thread.start()
|
||||
except ValueError:
|
||||
@@ -499,4 +543,5 @@ if __name__ == '__main__':
|
||||
while not quit_now:
|
||||
time.sleep(0.5)
|
||||
|
||||
sys.exit(exit_code)
|
||||
logdebug("Exiting with code %s" % exit_code)
|
||||
sys.exit(exit_code)
|
||||
@@ -1,24 +1,36 @@
|
||||
import time
|
||||
import json
|
||||
import datetime
|
||||
|
||||
class Logger:
|
||||
def __init__(self):
|
||||
self.r = None
|
||||
self.valkey = None
|
||||
|
||||
def set_redis(self, redis):
|
||||
self.r = redis
|
||||
def set_valkey(self, valkey):
|
||||
self.valkey = valkey
|
||||
|
||||
def _format_timestamp(self):
|
||||
# Local time with milliseconds
|
||||
return datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
def log(self, priority, message):
|
||||
tolog = {}
|
||||
tolog['time'] = int(round(time.time()))
|
||||
tolog['priority'] = priority
|
||||
tolog['message'] = message
|
||||
print(message)
|
||||
if self.r is not None:
|
||||
# build valkey-friendly dict
|
||||
tolog = {
|
||||
'time': int(round(time.time())), # keep raw timestamp for Valkey
|
||||
'priority': priority,
|
||||
'message': message
|
||||
}
|
||||
|
||||
# print human-readable message with timestamp
|
||||
ts = self._format_timestamp()
|
||||
print(f"{ts} {priority.upper()}: {message}", flush=True)
|
||||
|
||||
# also push JSON to Redis if connected
|
||||
if self.valkey is not None:
|
||||
try:
|
||||
self.r.lpush('NETFILTER_LOG', json.dumps(tolog, ensure_ascii=False))
|
||||
self.valkey.lpush('NETFILTER_LOG', json.dumps(tolog, ensure_ascii=False))
|
||||
except Exception as ex:
|
||||
print('Failed logging to redis: %s' % (ex))
|
||||
print(f'{ts} WARN: Failed logging to valkey: {ex}', flush=True)
|
||||
|
||||
def logWarn(self, message):
|
||||
self.log('warn', message)
|
||||
@@ -27,4 +39,4 @@ class Logger:
|
||||
self.log('crit', message)
|
||||
|
||||
def logInfo(self, message):
|
||||
self.log('info', message)
|
||||
self.log('info', message)
|
||||
@@ -10,7 +10,7 @@ def includes_conf(env, template_vars):
|
||||
server_name_config = f"server_name {template_vars['MAILCOW_HOSTNAME']} autodiscover.* autoconfig.* {' '.join(template_vars['ADDITIONAL_SERVER_NAMES'])};"
|
||||
listen_plain_config = f"listen {template_vars['HTTP_PORT']};"
|
||||
listen_ssl_config = f"listen {template_vars['HTTPS_PORT']};"
|
||||
if not template_vars['DISABLE_IPv6']:
|
||||
if template_vars['ENABLE_IPV6']:
|
||||
listen_plain_config += f"\nlisten [::]:{template_vars['HTTP_PORT']};"
|
||||
listen_ssl_config += f"\nlisten [::]:{template_vars['HTTPS_PORT']} ssl;"
|
||||
listen_ssl_config += "\nhttp2 on;"
|
||||
@@ -58,7 +58,7 @@ def prepare_template_vars():
|
||||
'SOGOHOST': os.getenv("SOGOHOST", ipv4_network + ".248"),
|
||||
'RSPAMDHOST': os.getenv("RSPAMDHOST", "rspamd-mailcow"),
|
||||
'PHPFPMHOST': os.getenv("PHPFPMHOST", "php-fpm-mailcow"),
|
||||
'DISABLE_IPv6': os.getenv("DISABLE_IPv6", "n").lower() in ("y", "yes"),
|
||||
'ENABLE_IPV6': os.getenv("ENABLE_IPV6", "true").lower() != "false",
|
||||
'HTTP_REDIRECT': os.getenv("HTTP_REDIRECT", "n").lower() in ("y", "yes"),
|
||||
}
|
||||
|
||||
|
||||
@@ -3,15 +3,15 @@ FROM php:8.2-fpm-alpine3.21
|
||||
LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"
|
||||
|
||||
# renovate: datasource=github-tags depName=krakjoe/apcu versioning=semver-coerced extractVersion=^v(?<version>.*)$
|
||||
ARG APCU_PECL_VERSION=5.1.24
|
||||
ARG APCU_PECL_VERSION=5.1.26
|
||||
# renovate: datasource=github-tags depName=Imagick/imagick versioning=semver-coerced extractVersion=(?<version>.*)$
|
||||
ARG IMAGICK_PECL_VERSION=3.8.0
|
||||
# renovate: datasource=github-tags depName=php/pecl-mail-mailparse versioning=semver-coerced extractVersion=^v(?<version>.*)$
|
||||
ARG MAILPARSE_PECL_VERSION=3.1.8
|
||||
# renovate: datasource=github-tags depName=php-memcached-dev/php-memcached versioning=semver-coerced extractVersion=^v(?<version>.*)$
|
||||
ARG MEMCACHED_PECL_VERSION=3.2.0
|
||||
ARG MEMCACHED_PECL_VERSION=3.3.0
|
||||
# renovate: datasource=github-tags depName=phpredis/phpredis versioning=semver-coerced extractVersion=(?<version>.*)$
|
||||
ARG REDIS_PECL_VERSION=6.1.0
|
||||
ARG REDIS_PECL_VERSION=6.2.0
|
||||
# renovate: datasource=github-tags depName=composer/composer versioning=semver-coerced extractVersion=(?<version>.*)$
|
||||
ARG COMPOSER_VERSION=2.8.6
|
||||
|
||||
|
||||
@@ -9,24 +9,24 @@ while ! mariadb-admin status --ssl=false --socket=/var/run/mysqld/mysqld.sock -u
|
||||
done
|
||||
|
||||
# Do not attempt to write to slave
|
||||
if [[ ! -z ${REDIS_SLAVEOF_IP} ]]; then
|
||||
REDIS_HOST=$REDIS_SLAVEOF_IP
|
||||
REDIS_PORT=$REDIS_SLAVEOF_PORT
|
||||
if [[ ! -z ${VALKEY_SLAVEOF_IP} ]]; then
|
||||
VALKEY_HOST=$VALKEY_SLAVEOF_IP
|
||||
VALKEY_PORT=$VALKEY_SLAVEOF_PORT
|
||||
else
|
||||
REDIS_HOST="redis"
|
||||
REDIS_PORT="6379"
|
||||
VALKEY_HOST="valkey-mailcow"
|
||||
VALKEY_PORT="6379"
|
||||
fi
|
||||
REDIS_CMDLINE="redis-cli -h ${REDIS_HOST} -p ${REDIS_PORT} -a ${REDISPASS} --no-auth-warning"
|
||||
VALKEY_CMDLINE="redis-cli -h ${VALKEY_HOST} -p ${VALKEY_PORT} -a ${VALKEYPASS} --no-auth-warning"
|
||||
|
||||
until [[ $(${REDIS_CMDLINE} PING) == "PONG" ]]; do
|
||||
echo "Waiting for Redis..."
|
||||
until [[ $(${VALKEY_CMDLINE} PING) == "PONG" ]]; do
|
||||
echo "Waiting for Valkey..."
|
||||
sleep 2
|
||||
done
|
||||
|
||||
# Set redis session store
|
||||
# Set valkey session store
|
||||
echo -n '
|
||||
session.save_handler = redis
|
||||
session.save_path = "tcp://'${REDIS_HOST}':'${REDIS_PORT}'?auth='${REDISPASS}'"
|
||||
session.save_path = "tcp://'${VALKEY_HOST}':'${VALKEY_PORT}'?auth='${VALKEYPASS}'"
|
||||
' > /usr/local/etc/php/conf.d/session_store.ini
|
||||
|
||||
# Check mysql_upgrade (master and slave)
|
||||
@@ -91,22 +91,22 @@ fi
|
||||
if [[ "${MASTER}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
|
||||
echo "We are master, preparing..."
|
||||
# Set a default release format
|
||||
if [[ -z $(${REDIS_CMDLINE} --raw GET Q_RELEASE_FORMAT) ]]; then
|
||||
${REDIS_CMDLINE} --raw SET Q_RELEASE_FORMAT raw
|
||||
if [[ -z $(${VALKEY_CMDLINE} --raw GET Q_RELEASE_FORMAT) ]]; then
|
||||
${VALKEY_CMDLINE} --raw SET Q_RELEASE_FORMAT raw
|
||||
fi
|
||||
|
||||
# Set max age of q items - if unset
|
||||
if [[ -z $(${REDIS_CMDLINE} --raw GET Q_MAX_AGE) ]]; then
|
||||
${REDIS_CMDLINE} --raw SET Q_MAX_AGE 365
|
||||
if [[ -z $(${VALKEY_CMDLINE} --raw GET Q_MAX_AGE) ]]; then
|
||||
${VALKEY_CMDLINE} --raw SET Q_MAX_AGE 365
|
||||
fi
|
||||
|
||||
# Set default password policy - if unset
|
||||
if [[ -z $(${REDIS_CMDLINE} --raw HGET PASSWD_POLICY length) ]]; then
|
||||
${REDIS_CMDLINE} --raw HSET PASSWD_POLICY length 6
|
||||
${REDIS_CMDLINE} --raw HSET PASSWD_POLICY chars 0
|
||||
${REDIS_CMDLINE} --raw HSET PASSWD_POLICY special_chars 0
|
||||
${REDIS_CMDLINE} --raw HSET PASSWD_POLICY lowerupper 0
|
||||
${REDIS_CMDLINE} --raw HSET PASSWD_POLICY numbers 0
|
||||
if [[ -z $(${VALKEY_CMDLINE} --raw HGET PASSWD_POLICY length) ]]; then
|
||||
${VALKEY_CMDLINE} --raw HSET PASSWD_POLICY length 6
|
||||
${VALKEY_CMDLINE} --raw HSET PASSWD_POLICY chars 0
|
||||
${VALKEY_CMDLINE} --raw HSET PASSWD_POLICY special_chars 0
|
||||
${VALKEY_CMDLINE} --raw HSET PASSWD_POLICY lowerupper 0
|
||||
${VALKEY_CMDLINE} --raw HSET PASSWD_POLICY numbers 0
|
||||
fi
|
||||
|
||||
# Trigger db init
|
||||
@@ -114,9 +114,9 @@ if [[ "${MASTER}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
|
||||
php -c /usr/local/etc/php -f /web/inc/init_db.inc.php
|
||||
|
||||
# Recreating domain map
|
||||
echo "Rebuilding domain map in Redis..."
|
||||
echo "Rebuilding domain map in Valkey..."
|
||||
declare -a DOMAIN_ARR
|
||||
${REDIS_CMDLINE} DEL DOMAIN_MAP > /dev/null
|
||||
${VALKEY_CMDLINE} DEL DOMAIN_MAP > /dev/null
|
||||
while read line
|
||||
do
|
||||
DOMAIN_ARR+=("$line")
|
||||
@@ -128,7 +128,7 @@ if [[ "${MASTER}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
|
||||
|
||||
if [[ ! -z ${DOMAIN_ARR} ]]; then
|
||||
for domain in "${DOMAIN_ARR[@]}"; do
|
||||
${REDIS_CMDLINE} HSET DOMAIN_MAP ${domain} 1 > /dev/null
|
||||
${VALKEY_CMDLINE} HSET DOMAIN_MAP ${domain} 1 > /dev/null
|
||||
done
|
||||
fi
|
||||
|
||||
|
||||
50
data/Dockerfiles/postfix-tlspol/Dockerfile
Normal file
50
data/Dockerfiles/postfix-tlspol/Dockerfile
Normal file
@@ -0,0 +1,50 @@
|
||||
FROM golang:1.25-bookworm AS builder
|
||||
WORKDIR /src
|
||||
|
||||
ENV CGO_ENABLED=0 \
|
||||
GO111MODULE=on \
|
||||
NOOPT=1 \
|
||||
VERSION=1.8.14
|
||||
|
||||
RUN git clone --branch v${VERSION} https://github.com/Zuplu/postfix-tlspol && \
|
||||
cd /src/postfix-tlspol && \
|
||||
scripts/build.sh build-only
|
||||
|
||||
|
||||
FROM debian:bookworm-slim
|
||||
LABEL maintainer="The Infrastructure Company GmbH <info@servercow.de>"
|
||||
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
ENV LC_ALL=C
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
ca-certificates \
|
||||
dirmngr \
|
||||
dnsutils \
|
||||
iputils-ping \
|
||||
sudo \
|
||||
supervisor \
|
||||
redis-tools \
|
||||
syslog-ng \
|
||||
syslog-ng-core \
|
||||
syslog-ng-mod-redis \
|
||||
tzdata \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& touch /etc/default/locale
|
||||
|
||||
COPY supervisord.conf /etc/supervisor/supervisord.conf
|
||||
COPY syslog-ng.conf /etc/syslog-ng/syslog-ng.conf
|
||||
COPY syslog-ng-valkey_slave.conf /etc/syslog-ng/syslog-ng-valkey_slave.conf
|
||||
COPY postfix-tlspol.sh /opt/postfix-tlspol.sh
|
||||
COPY stop-supervisor.sh /usr/local/sbin/stop-supervisor.sh
|
||||
COPY docker-entrypoint.sh /docker-entrypoint.sh
|
||||
COPY --from=builder /src/postfix-tlspol/build/postfix-tlspol /usr/local/bin/postfix-tlspol
|
||||
|
||||
RUN chmod +x /opt/postfix-tlspol.sh \
|
||||
/usr/local/sbin/stop-supervisor.sh \
|
||||
/docker-entrypoint.sh
|
||||
RUN rm -rf /tmp/* /var/tmp/*
|
||||
|
||||
ENTRYPOINT ["/docker-entrypoint.sh"]
|
||||
|
||||
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/supervisord.conf"]
|
||||
7
data/Dockerfiles/postfix-tlspol/docker-entrypoint.sh
Executable file
7
data/Dockerfiles/postfix-tlspol/docker-entrypoint.sh
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [[ ! -z ${VALKEY_SLAVEOF_IP} ]]; then
|
||||
cp /etc/syslog-ng/syslog-ng-valkey_slave.conf /etc/syslog-ng/syslog-ng.conf
|
||||
fi
|
||||
|
||||
exec "$@"
|
||||
52
data/Dockerfiles/postfix-tlspol/postfix-tlspol.sh
Executable file
52
data/Dockerfiles/postfix-tlspol/postfix-tlspol.sh
Executable file
@@ -0,0 +1,52 @@
|
||||
#!/bin/bash
|
||||
|
||||
LOGLVL=info
|
||||
|
||||
if [ ${DEV_MODE} != "n" ]; then
|
||||
echo -e "\e[31mEnabling debug mode\e[0m"
|
||||
set -x
|
||||
LOGLVL=debug
|
||||
fi
|
||||
|
||||
[[ ! -d /etc/postfix-tlspol ]] && mkdir -p /etc/postfix-tlspol
|
||||
[[ ! -d /var/lib/postfix-tlspol ]] && mkdir -p /var/lib/postfix-tlspol
|
||||
|
||||
until dig +short mailcow.email > /dev/null; do
|
||||
echo "Waiting for DNS..."
|
||||
sleep 1
|
||||
done
|
||||
|
||||
# Do not attempt to write to slave
|
||||
if [[ ! -z ${VALKEY_SLAVEOF_IP} ]]; then
|
||||
export VALKEY_CMDLINE="redis-cli -h ${VALKEY_SLAVEOF_IP} -p ${VALKEY_SLAVEOF_PORT} -a ${VALKEYPASS} --no-auth-warning"
|
||||
else
|
||||
export VALKEY_CMDLINE="redis-cli -h valkey -p 6379 -a ${VALKEYPASS} --no-auth-warning"
|
||||
fi
|
||||
|
||||
until [[ $(${VALKEY_CMDLINE} PING) == "PONG" ]]; do
|
||||
echo "Waiting for Valkey..."
|
||||
sleep 2
|
||||
done
|
||||
|
||||
echo "Waiting for Postfix..."
|
||||
until ping postfix -c1 > /dev/null; do
|
||||
sleep 1
|
||||
done
|
||||
echo "Postfix OK"
|
||||
|
||||
cat <<EOF > /etc/postfix-tlspol/config.yaml
|
||||
server:
|
||||
address: 0.0.0.0:8642
|
||||
|
||||
log-level: ${LOGLVL}
|
||||
|
||||
prefetch: true
|
||||
|
||||
cache-file: /var/lib/postfix-tlspol/cache.db
|
||||
|
||||
dns:
|
||||
# must support DNSSEC
|
||||
address: 127.0.0.11:53
|
||||
EOF
|
||||
|
||||
/usr/local/bin/postfix-tlspol -config /etc/postfix-tlspol/config.yaml
|
||||
8
data/Dockerfiles/postfix-tlspol/stop-supervisor.sh
Executable file
8
data/Dockerfiles/postfix-tlspol/stop-supervisor.sh
Executable file
@@ -0,0 +1,8 @@
|
||||
#!/bin/bash
|
||||
|
||||
printf "READY\n";
|
||||
|
||||
while read line; do
|
||||
echo "Processing Event: $line" >&2;
|
||||
kill -3 $(cat "/var/run/supervisord.pid")
|
||||
done < /dev/stdin
|
||||
25
data/Dockerfiles/postfix-tlspol/supervisord.conf
Normal file
25
data/Dockerfiles/postfix-tlspol/supervisord.conf
Normal file
@@ -0,0 +1,25 @@
|
||||
[supervisord]
|
||||
pidfile=/var/run/supervisord.pid
|
||||
nodaemon=true
|
||||
user=root
|
||||
|
||||
[program:syslog-ng]
|
||||
command=/usr/sbin/syslog-ng --foreground --no-caps
|
||||
stdout_logfile=/dev/stdout
|
||||
stdout_logfile_maxbytes=0
|
||||
stderr_logfile=/dev/stderr
|
||||
stderr_logfile_maxbytes=0
|
||||
autostart=true
|
||||
|
||||
[program:postfix-tlspol]
|
||||
startsecs=10
|
||||
autorestart=true
|
||||
command=/opt/postfix-tlspol.sh
|
||||
stdout_logfile=/dev/stdout
|
||||
stdout_logfile_maxbytes=0
|
||||
stderr_logfile=/dev/stderr
|
||||
stderr_logfile_maxbytes=0
|
||||
|
||||
[eventlistener:processes]
|
||||
command=/usr/local/sbin/stop-supervisor.sh
|
||||
events=PROCESS_STATE_STOPPED, PROCESS_STATE_EXITED, PROCESS_STATE_FATAL
|
||||
45
data/Dockerfiles/postfix-tlspol/syslog-ng-valkey_slave.conf
Normal file
45
data/Dockerfiles/postfix-tlspol/syslog-ng-valkey_slave.conf
Normal file
@@ -0,0 +1,45 @@
|
||||
@version: 3.38
|
||||
@include "scl.conf"
|
||||
options {
|
||||
chain_hostnames(off);
|
||||
flush_lines(0);
|
||||
use_dns(no);
|
||||
dns_cache(no);
|
||||
use_fqdn(no);
|
||||
owner("root"); group("adm"); perm(0640);
|
||||
stats_freq(0);
|
||||
bad_hostname("^gconfd$");
|
||||
};
|
||||
source s_src {
|
||||
unix-stream("/dev/log");
|
||||
internal();
|
||||
};
|
||||
destination d_stdout { pipe("/dev/stdout"); };
|
||||
destination d_valkey_ui_log {
|
||||
redis(
|
||||
host("`VALKEY_SLAVEOF_IP`")
|
||||
persist-name("valkey1")
|
||||
port(`VALKEY_SLAVEOF_PORT`)
|
||||
auth("`VALKEYPASS`")
|
||||
command("LPUSH" "POSTFIX_MAILLOG" "$(format-json time=\"$S_UNIXTIME\" priority=\"$PRIORITY\" program=\"$PROGRAM\" message=\"$MESSAGE\")\n")
|
||||
);
|
||||
};
|
||||
filter f_mail { facility(mail); };
|
||||
# start
|
||||
# overriding warnings are still displayed when the entrypoint runs its initial check
|
||||
# warnings logged by postfix-mailcow to syslog are hidden to reduce repeating msgs
|
||||
# Some other warnings are ignored
|
||||
filter f_ignore {
|
||||
not match("overriding earlier entry" value("MESSAGE"));
|
||||
not match("TLS SNI from checks.mailcow.email" value("MESSAGE"));
|
||||
not match("no SASL support" value("MESSAGE"));
|
||||
not facility (local0, local1, local2, local3, local4, local5, local6, local7);
|
||||
};
|
||||
# end
|
||||
log {
|
||||
source(s_src);
|
||||
filter(f_ignore);
|
||||
destination(d_stdout);
|
||||
filter(f_mail);
|
||||
destination(d_valkey_ui_log);
|
||||
};
|
||||
45
data/Dockerfiles/postfix-tlspol/syslog-ng.conf
Normal file
45
data/Dockerfiles/postfix-tlspol/syslog-ng.conf
Normal file
@@ -0,0 +1,45 @@
|
||||
@version: 3.38
|
||||
@include "scl.conf"
|
||||
options {
|
||||
chain_hostnames(off);
|
||||
flush_lines(0);
|
||||
use_dns(no);
|
||||
dns_cache(no);
|
||||
use_fqdn(no);
|
||||
owner("root"); group("adm"); perm(0640);
|
||||
stats_freq(0);
|
||||
bad_hostname("^gconfd$");
|
||||
};
|
||||
source s_src {
|
||||
unix-stream("/dev/log");
|
||||
internal();
|
||||
};
|
||||
destination d_stdout { pipe("/dev/stdout"); };
|
||||
destination d_valkey_ui_log {
|
||||
redis(
|
||||
host("valkey-mailcow")
|
||||
persist-name("valkey1")
|
||||
port(6379)
|
||||
auth("`VALKEYPASS`")
|
||||
command("LPUSH" "POSTFIX_MAILLOG" "$(format-json time=\"$S_UNIXTIME\" priority=\"$PRIORITY\" program=\"$PROGRAM\" message=\"$MESSAGE\")\n")
|
||||
);
|
||||
};
|
||||
filter f_mail { facility(mail); };
|
||||
# start
|
||||
# overriding warnings are still displayed when the entrypoint runs its initial check
|
||||
# warnings logged by postfix-mailcow to syslog are hidden to reduce repeating msgs
|
||||
# Some other warnings are ignored
|
||||
filter f_ignore {
|
||||
not match("overriding earlier entry" value("MESSAGE"));
|
||||
not match("TLS SNI from checks.mailcow.email" value("MESSAGE"));
|
||||
not match("no SASL support" value("MESSAGE"));
|
||||
not facility (local0, local1, local2, local3, local4, local5, local6, local7);
|
||||
};
|
||||
# end
|
||||
log {
|
||||
source(s_src);
|
||||
filter(f_ignore);
|
||||
destination(d_stdout);
|
||||
filter(f_mail);
|
||||
destination(d_valkey_ui_log);
|
||||
};
|
||||
@@ -1,9 +1,9 @@
|
||||
FROM debian:bookworm-slim
|
||||
|
||||
LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"
|
||||
LABEL maintainer="The Infrastructure Company GmbH <info@servercow.de>"
|
||||
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
ENV LC_ALL C
|
||||
ENV LC_ALL=C
|
||||
|
||||
RUN dpkg-divert --local --rename --add /sbin/initctl \
|
||||
&& ln -sf /bin/true /sbin/initctl \
|
||||
@@ -41,7 +41,7 @@ RUN groupadd -g 102 postfix \
|
||||
|
||||
COPY supervisord.conf /etc/supervisor/supervisord.conf
|
||||
COPY syslog-ng.conf /etc/syslog-ng/syslog-ng.conf
|
||||
COPY syslog-ng-redis_slave.conf /etc/syslog-ng/syslog-ng-redis_slave.conf
|
||||
COPY syslog-ng-valkey_slave.conf /etc/syslog-ng/syslog-ng-valkey_slave.conf
|
||||
COPY postfix.sh /opt/postfix.sh
|
||||
COPY rspamd-pipe-ham /usr/local/bin/rspamd-pipe-ham
|
||||
COPY rspamd-pipe-spam /usr/local/bin/rspamd-pipe-spam
|
||||
|
||||
@@ -8,8 +8,8 @@ for file in /hooks/*; do
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ ! -z ${REDIS_SLAVEOF_IP} ]]; then
|
||||
cp /etc/syslog-ng/syslog-ng-redis_slave.conf /etc/syslog-ng/syslog-ng.conf
|
||||
if [[ ! -z ${VALKEY_SLAVEOF_IP} ]]; then
|
||||
cp /etc/syslog-ng/syslog-ng-valkey_slave.conf /etc/syslog-ng/syslog-ng.conf
|
||||
fi
|
||||
|
||||
# Fix OpenSSL 3.X TLS1.0, 1.1 support (https://community.mailcow.email/d/4062-hi-all/20)
|
||||
@@ -21,6 +21,6 @@ if grep -qE '\!SSLv2|\!SSLv3|>=TLSv1(\.[0-1])?$' /opt/postfix/conf/main.cf /opt/
|
||||
echo "[tls_system_default]" >> /etc/ssl/openssl.cnf
|
||||
echo "MinProtocol = TLSv1" >> /etc/ssl/openssl.cnf
|
||||
echo "CipherString = DEFAULT@SECLEVEL=0" >> /etc/ssl/openssl.cnf
|
||||
fi
|
||||
fi
|
||||
|
||||
exec "$@"
|
||||
|
||||
@@ -524,4 +524,4 @@ if [[ $? != 0 ]]; then
|
||||
else
|
||||
postfix -c /opt/postfix/conf start
|
||||
sleep 126144000
|
||||
fi
|
||||
fi
|
||||
@@ -15,21 +15,21 @@ source s_src {
|
||||
internal();
|
||||
};
|
||||
destination d_stdout { pipe("/dev/stdout"); };
|
||||
destination d_redis_ui_log {
|
||||
destination d_valkey_ui_log {
|
||||
redis(
|
||||
host("`REDIS_SLAVEOF_IP`")
|
||||
persist-name("redis1")
|
||||
port(`REDIS_SLAVEOF_PORT`)
|
||||
auth("`REDISPASS`")
|
||||
host("`VALKEY_SLAVEOF_IP`")
|
||||
persist-name("valkey1")
|
||||
port(`VALKEY_SLAVEOF_PORT`)
|
||||
auth("`VALKEYPASS`")
|
||||
command("LPUSH" "POSTFIX_MAILLOG" "$(format-json time=\"$S_UNIXTIME\" priority=\"$PRIORITY\" program=\"$PROGRAM\" message=\"$MESSAGE\")\n")
|
||||
);
|
||||
};
|
||||
destination d_redis_f2b_channel {
|
||||
destination d_valkey_f2b_channel {
|
||||
redis(
|
||||
host("`REDIS_SLAVEOF_IP`")
|
||||
persist-name("redis2")
|
||||
port(`REDIS_SLAVEOF_PORT`)
|
||||
auth("`REDISPASS`")
|
||||
host("`VALKEY_SLAVEOF_IP`")
|
||||
persist-name("valkey2")
|
||||
port(`VALKEY_SLAVEOF_PORT`)
|
||||
auth("`VALKEYPASS`")
|
||||
command("PUBLISH" "F2B_CHANNEL" "$(sanitize $MESSAGE)")
|
||||
);
|
||||
};
|
||||
@@ -50,6 +50,6 @@ log {
|
||||
filter(f_ignore);
|
||||
destination(d_stdout);
|
||||
filter(f_mail);
|
||||
destination(d_redis_ui_log);
|
||||
destination(d_redis_f2b_channel);
|
||||
destination(d_valkey_ui_log);
|
||||
destination(d_valkey_f2b_channel);
|
||||
};
|
||||
@@ -15,21 +15,21 @@ source s_src {
|
||||
internal();
|
||||
};
|
||||
destination d_stdout { pipe("/dev/stdout"); };
|
||||
destination d_redis_ui_log {
|
||||
destination d_valkey_ui_log {
|
||||
redis(
|
||||
host("redis-mailcow")
|
||||
persist-name("redis1")
|
||||
host("valkey-mailcow")
|
||||
persist-name("valkey1")
|
||||
port(6379)
|
||||
auth("`REDISPASS`")
|
||||
auth("`VALKEYPASS`")
|
||||
command("LPUSH" "POSTFIX_MAILLOG" "$(format-json time=\"$S_UNIXTIME\" priority=\"$PRIORITY\" program=\"$PROGRAM\" message=\"$MESSAGE\")\n")
|
||||
);
|
||||
};
|
||||
destination d_redis_f2b_channel {
|
||||
destination d_valkey_f2b_channel {
|
||||
redis(
|
||||
host("redis-mailcow")
|
||||
persist-name("redis2")
|
||||
host("valkey-mailcow")
|
||||
persist-name("valkey2")
|
||||
port(6379)
|
||||
auth("`REDISPASS`")
|
||||
auth("`VALKEYPASS`")
|
||||
command("PUBLISH" "F2B_CHANNEL" "$(sanitize $MESSAGE)")
|
||||
);
|
||||
};
|
||||
@@ -50,6 +50,6 @@ log {
|
||||
filter(f_ignore);
|
||||
destination(d_stdout);
|
||||
filter(f_mail);
|
||||
destination(d_redis_ui_log);
|
||||
destination(d_redis_f2b_channel);
|
||||
destination(d_valkey_ui_log);
|
||||
destination(d_valkey_f2b_channel);
|
||||
};
|
||||
|
||||
@@ -2,7 +2,7 @@ FROM debian:bookworm-slim
|
||||
LABEL maintainer="The Infrastructure Company GmbH <info@servercow.de>"
|
||||
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
ARG RSPAMD_VER=rspamd_3.11.1-1~ab0b44951
|
||||
ARG RSPAMD_VER=rspamd_3.12.1-1~6dbfca2fa
|
||||
ARG CODENAME=bookworm
|
||||
ENV LC_ALL=C
|
||||
|
||||
|
||||
@@ -52,33 +52,33 @@ if [[ ! -z ${RSPAMD_V6} ]]; then
|
||||
echo ${RSPAMD_V6}/128 >> /etc/rspamd/custom/rspamd_trusted.map
|
||||
fi
|
||||
|
||||
if [[ ! -z ${REDIS_SLAVEOF_IP} ]]; then
|
||||
if [[ ! -z ${VALKEY_SLAVEOF_IP} ]]; then
|
||||
cat <<EOF > /etc/rspamd/local.d/redis.conf
|
||||
read_servers = "redis:6379";
|
||||
write_servers = "${REDIS_SLAVEOF_IP}:${REDIS_SLAVEOF_PORT}";
|
||||
password = "${REDISPASS}";
|
||||
read_servers = "valkey-mailcow:6379";
|
||||
write_servers = "${VALKEY_SLAVEOF_IP}:${VALKEY_SLAVEOF_PORT}";
|
||||
password = "${VALKEYPASS}";
|
||||
timeout = 10;
|
||||
EOF
|
||||
until [[ $(redis-cli -h redis-mailcow -a ${REDISPASS} --no-auth-warning PING) == "PONG" ]]; do
|
||||
echo "Waiting for Redis @redis-mailcow..."
|
||||
until [[ $(redis-cli -h valkey-mailcow -a ${VALKEYPASS} --no-auth-warning PING) == "PONG" ]]; do
|
||||
echo "Waiting for Valkey @valkey-mailcow..."
|
||||
sleep 2
|
||||
done
|
||||
until [[ $(redis-cli -h ${REDIS_SLAVEOF_IP} -p ${REDIS_SLAVEOF_PORT} -a ${REDISPASS} --no-auth-warning PING) == "PONG" ]]; do
|
||||
echo "Waiting for Redis @${REDIS_SLAVEOF_IP}..."
|
||||
until [[ $(redis-cli -h ${VALKEY_SLAVEOF_IP} -p ${VALKEY_SLAVEOF_PORT} -a ${VALKEYPASS} --no-auth-warning PING) == "PONG" ]]; do
|
||||
echo "Waiting for Valkey @${VALKEY_SLAVEOF_IP}..."
|
||||
sleep 2
|
||||
done
|
||||
redis-cli -h redis-mailcow -a ${REDISPASS} --no-auth-warning SLAVEOF ${REDIS_SLAVEOF_IP} ${REDIS_SLAVEOF_PORT}
|
||||
redis-cli -h valkey-mailcow -a ${VALKEYPASS} --no-auth-warning SLAVEOF ${VALKEY_SLAVEOF_IP} ${VALKEY_SLAVEOF_PORT}
|
||||
else
|
||||
cat <<EOF > /etc/rspamd/local.d/redis.conf
|
||||
servers = "redis:6379";
|
||||
password = "${REDISPASS}";
|
||||
servers = "valkey-mailcow:6379";
|
||||
password = "${VALKEYPASS}";
|
||||
timeout = 10;
|
||||
EOF
|
||||
until [[ $(redis-cli -h redis-mailcow -a ${REDISPASS} --no-auth-warning PING) == "PONG" ]]; do
|
||||
echo "Waiting for Redis slave..."
|
||||
until [[ $(redis-cli -h valkey-mailcow -a ${VALKEYPASS} --no-auth-warning PING) == "PONG" ]]; do
|
||||
echo "Waiting for Valkey slave..."
|
||||
sleep 2
|
||||
done
|
||||
redis-cli -h redis-mailcow -a ${REDISPASS} --no-auth-warning SLAVEOF NO ONE
|
||||
redis-cli -h valkey-mailcow -a ${VALKEYPASS} --no-auth-warning SLAVEOF NO ONE
|
||||
fi
|
||||
|
||||
if [[ "${SKIP_OLEFY}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
|
||||
@@ -86,7 +86,8 @@ if [[ "${SKIP_OLEFY}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
|
||||
rm /etc/rspamd/local.d/external_services.conf
|
||||
fi
|
||||
else
|
||||
cat <<EOF > /etc/rspamd/local.d/external_services.conf
|
||||
if [[ ! -f /etc/rspamd/local.d/external_services.conf ]]; then
|
||||
cat <<EOF > /etc/rspamd/local.d/external_services.conf
|
||||
oletools {
|
||||
# default olefy settings
|
||||
servers = "olefy:10055";
|
||||
@@ -100,6 +101,7 @@ oletools {
|
||||
retransmits = 1;
|
||||
}
|
||||
EOF
|
||||
fi
|
||||
fi
|
||||
|
||||
# Provide additional lua modules
|
||||
|
||||
@@ -44,7 +44,7 @@ RUN echo "Building from repository $SOGO_DEBIAN_REPOSITORY" \
|
||||
|
||||
COPY ./bootstrap-sogo.sh /bootstrap-sogo.sh
|
||||
COPY syslog-ng.conf /etc/syslog-ng/syslog-ng.conf
|
||||
COPY syslog-ng-redis_slave.conf /etc/syslog-ng/syslog-ng-redis_slave.conf
|
||||
COPY syslog-ng-valkey_slave.conf /etc/syslog-ng/syslog-ng-valkey_slave.conf
|
||||
COPY supervisord.conf /etc/supervisor/supervisord.conf
|
||||
COPY acl.diff /acl.diff
|
||||
COPY navMailcowBtns.diff /navMailcowBtns.diff
|
||||
|
||||
@@ -24,6 +24,10 @@ while [[ "${DBV_NOW}" != "${DBV_NEW}" ]]; do
|
||||
done
|
||||
echo "DB schema is ${DBV_NOW}"
|
||||
|
||||
if [[ "${MASTER}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
|
||||
mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "DROP TRIGGER IF EXISTS sogo_update_password"
|
||||
fi
|
||||
|
||||
# cat /dev/urandom seems to hang here occasionally and is not recommended anyway, better use openssl
|
||||
RAND_PASS=$(openssl rand -base64 16 | tr -dc _A-Z-a-z-0-9)
|
||||
|
||||
@@ -46,6 +50,10 @@ cat <<EOF > /var/lib/sogo/GNUstep/Defaults/sogod.plist
|
||||
<string>YES</string>
|
||||
<key>SOGoEncryptionKey</key>
|
||||
<string>${RAND_PASS}</string>
|
||||
<key>SOGoURLEncryptionEnabled</key>
|
||||
<string>YES</string>
|
||||
<key>SOGoURLEncryptionPassphrase</key>
|
||||
<string>${SOGO_URL_ENCRYPTION_KEY}</string>
|
||||
<key>OCSAdminURL</key>
|
||||
<string>mysql://${DBUSER}:${DBPASS}@%2Fvar%2Frun%2Fmysqld%2Fmysqld.sock/${DBNAME}/sogo_admin</string>
|
||||
<key>OCSCacheFolderURL</key>
|
||||
|
||||
@@ -6,8 +6,8 @@ if [[ "${SKIP_SOGO}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ ! -z ${REDIS_SLAVEOF_IP} ]]; then
|
||||
cp /etc/syslog-ng/syslog-ng-redis_slave.conf /etc/syslog-ng/syslog-ng.conf
|
||||
if [[ ! -z ${VALKEY_SLAVEOF_IP} ]]; then
|
||||
cp /etc/syslog-ng/syslog-ng-valkey_slave.conf /etc/syslog-ng/syslog-ng.conf
|
||||
fi
|
||||
|
||||
echo "$TZ" > /etc/timezone
|
||||
|
||||
@@ -17,28 +17,28 @@ source s_sogo {
|
||||
pipe("/dev/sogo_log" owner(sogo) group(sogo));
|
||||
};
|
||||
destination d_stdout { pipe("/dev/stdout"); };
|
||||
destination d_redis_ui_log {
|
||||
destination d_valkey_ui_log {
|
||||
redis(
|
||||
host("`REDIS_SLAVEOF_IP`")
|
||||
persist-name("redis1")
|
||||
port(`REDIS_SLAVEOF_PORT`)
|
||||
auth("`REDISPASS`")
|
||||
host("`VALKEY_SLAVEOF_IP`")
|
||||
persist-name("valkey1")
|
||||
port(`VALKEY_SLAVEOF_PORT`)
|
||||
auth("`VALKEYPASS`")
|
||||
command("LPUSH" "SOGO_LOG" "$(format-json time=\"$S_UNIXTIME\" priority=\"$PRIORITY\" program=\"$PROGRAM\" message=\"$MESSAGE\")\n")
|
||||
);
|
||||
};
|
||||
destination d_redis_f2b_channel {
|
||||
destination d_valkey_f2b_channel {
|
||||
redis(
|
||||
host("`REDIS_SLAVEOF_IP`")
|
||||
persist-name("redis2")
|
||||
port(`REDIS_SLAVEOF_PORT`)
|
||||
auth("`REDISPASS`")
|
||||
host("`VALKEY_SLAVEOF_IP`")
|
||||
persist-name("valkey2")
|
||||
port(`VALKEY_SLAVEOF_PORT`)
|
||||
auth("`VALKEYPASS`")
|
||||
command("PUBLISH" "F2B_CHANNEL" "$(sanitize $MESSAGE)")
|
||||
);
|
||||
};
|
||||
log {
|
||||
source(s_sogo);
|
||||
destination(d_redis_ui_log);
|
||||
destination(d_redis_f2b_channel);
|
||||
destination(d_valkey_ui_log);
|
||||
destination(d_valkey_f2b_channel);
|
||||
};
|
||||
log {
|
||||
source(s_sogo);
|
||||
@@ -17,28 +17,28 @@ source s_sogo {
|
||||
pipe("/dev/sogo_log" owner(sogo) group(sogo));
|
||||
};
|
||||
destination d_stdout { pipe("/dev/stdout"); };
|
||||
destination d_redis_ui_log {
|
||||
destination d_valkey_ui_log {
|
||||
redis(
|
||||
host("redis-mailcow")
|
||||
persist-name("redis1")
|
||||
host("valkey-mailcow")
|
||||
persist-name("valkey1")
|
||||
port(6379)
|
||||
auth("`REDISPASS`")
|
||||
auth("`VALKEYPASS`")
|
||||
command("LPUSH" "SOGO_LOG" "$(format-json time=\"$S_UNIXTIME\" priority=\"$PRIORITY\" program=\"$PROGRAM\" message=\"$MESSAGE\")\n")
|
||||
);
|
||||
};
|
||||
destination d_redis_f2b_channel {
|
||||
destination d_valkey_f2b_channel {
|
||||
redis(
|
||||
host("redis-mailcow")
|
||||
persist-name("redis2")
|
||||
host("valkey-mailcow")
|
||||
persist-name("valkey2")
|
||||
port(6379)
|
||||
auth("`REDISPASS`")
|
||||
auth("`VALKEYPASS`")
|
||||
command("PUBLISH" "F2B_CHANNEL" "$(sanitize $MESSAGE)")
|
||||
);
|
||||
};
|
||||
log {
|
||||
source(s_sogo);
|
||||
destination(d_redis_ui_log);
|
||||
destination(d_redis_f2b_channel);
|
||||
destination(d_valkey_ui_log);
|
||||
destination(d_valkey_f2b_channel);
|
||||
};
|
||||
log {
|
||||
source(s_sogo);
|
||||
|
||||
8
data/Dockerfiles/valkeymigrator/Dockerfile
Normal file
8
data/Dockerfiles/valkeymigrator/Dockerfile
Normal file
@@ -0,0 +1,8 @@
|
||||
FROM python:3.13.2-alpine3.21
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY migrate.py /app/migrate.py
|
||||
RUN pip install --no-cache-dir redis
|
||||
|
||||
CMD ["python", "/app/migrate.py"]
|
||||
78
data/Dockerfiles/valkeymigrator/migrate.py
Normal file
78
data/Dockerfiles/valkeymigrator/migrate.py
Normal file
@@ -0,0 +1,78 @@
|
||||
import subprocess
|
||||
import redis
|
||||
import time
|
||||
import os
|
||||
|
||||
# Container names
|
||||
SOURCE_CONTAINER = "redis-old-mailcow"
|
||||
DEST_CONTAINER = "valkey-mailcow"
|
||||
VALKEYPASS = os.getenv("VALKEYPASS")
|
||||
|
||||
|
||||
def migrate_redis():
|
||||
src_redis = redis.StrictRedis(host=SOURCE_CONTAINER, port=6379, db=0, password=VALKEYPASS, decode_responses=False)
|
||||
dest_redis = redis.StrictRedis(host=DEST_CONTAINER, port=6379, db=0, password=VALKEYPASS, decode_responses=False)
|
||||
|
||||
cursor = 0
|
||||
batch_size = 100
|
||||
migrated_count = 0
|
||||
|
||||
print("Starting migration...")
|
||||
|
||||
while True:
|
||||
cursor, keys = src_redis.scan(cursor=cursor, match="*", count=batch_size)
|
||||
keys_to_migrate = [key for key in keys if not key.startswith(b"PHPREDIS_SESSION:")]
|
||||
|
||||
for key in keys_to_migrate:
|
||||
key_type = src_redis.type(key)
|
||||
print(f"Import {key} of type {key_type}")
|
||||
|
||||
if key_type == b"string":
|
||||
value = src_redis.get(key)
|
||||
dest_redis.set(key, value)
|
||||
|
||||
elif key_type == b"hash":
|
||||
value = src_redis.hgetall(key)
|
||||
dest_redis.hset(key, mapping=value)
|
||||
|
||||
elif key_type == b"list":
|
||||
value = src_redis.lrange(key, 0, -1)
|
||||
for v in value:
|
||||
dest_redis.rpush(key, v)
|
||||
|
||||
elif key_type == b"set":
|
||||
value = src_redis.smembers(key)
|
||||
for v in value:
|
||||
dest_redis.sadd(key, v)
|
||||
|
||||
elif key_type == b"zset":
|
||||
value = src_redis.zrange(key, 0, -1, withscores=True)
|
||||
for v, score in value:
|
||||
dest_redis.zadd(key, {v: score})
|
||||
|
||||
# Preserve TTL if exists
|
||||
ttl = src_redis.ttl(key)
|
||||
if ttl > 0:
|
||||
dest_redis.expire(key, ttl)
|
||||
|
||||
migrated_count += 1
|
||||
|
||||
if cursor == 0:
|
||||
break # No more keys to scan
|
||||
|
||||
print(f"Migration completed! {migrated_count} keys migrated.")
|
||||
|
||||
print("Forcing Valkey to save data...")
|
||||
try:
|
||||
dest_redis.save() # Immediate RDB save (blocking)
|
||||
dest_redis.bgrewriteaof() # Rewrites the AOF file in the background
|
||||
print("Data successfully saved to disk.")
|
||||
except Exception as e:
|
||||
print(f"Failed to save data: {e}")
|
||||
|
||||
# Main script execution
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
migrate_redis()
|
||||
finally:
|
||||
pass
|
||||
@@ -16,7 +16,6 @@ RUN apk add --update \
|
||||
fcgi \
|
||||
openssl \
|
||||
nagios-plugins-mysql \
|
||||
nagios-plugins-dns \
|
||||
nagios-plugins-disk \
|
||||
bind-tools \
|
||||
redis \
|
||||
@@ -32,9 +31,11 @@ RUN apk add --update \
|
||||
tzdata \
|
||||
whois \
|
||||
&& curl https://raw.githubusercontent.com/mludvig/smtp-cli/v3.10/smtp-cli -o /smtp-cli \
|
||||
&& chmod +x smtp-cli
|
||||
&& chmod +x smtp-cli \
|
||||
&& mkdir /usr/lib/mailcow
|
||||
|
||||
COPY watchdog.sh /watchdog.sh
|
||||
COPY check_mysql_slavestatus.sh /usr/lib/nagios/plugins/check_mysql_slavestatus.sh
|
||||
COPY check_dns.sh /usr/lib/mailcow/check_dns.sh
|
||||
|
||||
CMD ["/watchdog.sh"]
|
||||
|
||||
39
data/Dockerfiles/watchdog/check_dns.sh
Executable file
39
data/Dockerfiles/watchdog/check_dns.sh
Executable file
@@ -0,0 +1,39 @@
|
||||
#!/bin/sh
|
||||
|
||||
while getopts "H:s:" opt; do
|
||||
case "$opt" in
|
||||
H) HOST="$OPTARG" ;;
|
||||
s) SERVER="$OPTARG" ;;
|
||||
*) echo "Usage: $0 -H host -s server"; exit 3 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ -z "$SERVER" ]; then
|
||||
echo "No DNS Server provided"
|
||||
exit 3
|
||||
fi
|
||||
|
||||
if [ -z "$HOST" ]; then
|
||||
echo "No host to test provided"
|
||||
exit 3
|
||||
fi
|
||||
|
||||
# run dig and measure the time it takes to run
|
||||
START_TIME=$(date +%s%3N)
|
||||
dig_output=$(dig +short +timeout=2 +tries=1 "$HOST" @"$SERVER" 2>/dev/null)
|
||||
dig_rc=$?
|
||||
dig_output_ips=$(echo "$dig_output" | grep -E '^[0-9.]+$' | sort | paste -sd ',' -)
|
||||
END_TIME=$(date +%s%3N)
|
||||
ELAPSED_TIME=$((END_TIME - START_TIME))
|
||||
|
||||
# validate and perform nagios like output and exit codes
|
||||
if [ $dig_rc -ne 0 ] || [ -z "$dig_output" ]; then
|
||||
echo "Domain $HOST was not found by the server"
|
||||
exit 2
|
||||
elif [ $dig_rc -eq 0 ]; then
|
||||
echo "DNS OK: $ELAPSED_TIME ms response time. $HOST returns $dig_output_ips"
|
||||
exit 0
|
||||
else
|
||||
echo "Unknown error"
|
||||
exit 3
|
||||
fi
|
||||
@@ -1,5 +1,10 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [ "${DEV_MODE}" != "n" ]; then
|
||||
echo -e "\e[31mEnabled Debug Mode\e[0m"
|
||||
set -x
|
||||
fi
|
||||
|
||||
trap "exit" INT TERM
|
||||
trap "kill 0" EXIT
|
||||
|
||||
@@ -39,18 +44,18 @@ while ! mariadb-admin status --ssl=false --socket=/var/run/mysqld/mysqld.sock -u
|
||||
done
|
||||
|
||||
# Do not attempt to write to slave
|
||||
if [[ ! -z ${REDIS_SLAVEOF_IP} ]]; then
|
||||
REDIS_CMDLINE="redis-cli -h ${REDIS_SLAVEOF_IP} -p ${REDIS_SLAVEOF_PORT} -a ${REDISPASS} --no-auth-warning"
|
||||
if [[ ! -z ${VALKEY_SLAVEOF_IP} ]]; then
|
||||
VALKEY_CMDLINE="redis-cli -h ${VALKEY_SLAVEOF_IP} -p ${VALKEY_SLAVEOF_PORT} -a ${VALKEYPASS} --no-auth-warning"
|
||||
else
|
||||
REDIS_CMDLINE="redis-cli -h redis -p 6379 -a ${REDISPASS} --no-auth-warning"
|
||||
VALKEY_CMDLINE="redis-cli -h valkey-mailcow -p 6379 -a ${VALKEYPASS} --no-auth-warning"
|
||||
fi
|
||||
|
||||
until [[ $(${REDIS_CMDLINE} PING) == "PONG" ]]; do
|
||||
echo "Waiting for Redis..."
|
||||
until [[ $(${VALKEY_CMDLINE} PING) == "PONG" ]]; do
|
||||
echo "Waiting for Valkey..."
|
||||
sleep 2
|
||||
done
|
||||
|
||||
${REDIS_CMDLINE} DEL F2B_RES > /dev/null
|
||||
${VALKEY_CMDLINE} DEL F2B_RES > /dev/null
|
||||
|
||||
# Common functions
|
||||
get_ipv6(){
|
||||
@@ -85,15 +90,15 @@ progress() {
|
||||
[[ ${CURRENT} -gt ${TOTAL} ]] && return
|
||||
[[ ${CURRENT} -lt 0 ]] && CURRENT=0
|
||||
PERCENT=$(( 200 * ${CURRENT} / ${TOTAL} % 2 + 100 * ${CURRENT} / ${TOTAL} ))
|
||||
${REDIS_CMDLINE} LPUSH WATCHDOG_LOG "{\"time\":\"$(date +%s)\",\"service\":\"${SERVICE}\",\"lvl\":\"${PERCENT}\",\"hpnow\":\"${CURRENT}\",\"hptotal\":\"${TOTAL}\",\"hpdiff\":\"${DIFF}\"}" > /dev/null
|
||||
log_msg "${SERVICE} health level: ${PERCENT}% (${CURRENT}/${TOTAL}), health trend: ${DIFF}" no_redis
|
||||
${VALKEY_CMDLINE} LPUSH WATCHDOG_LOG "{\"time\":\"$(date +%s)\",\"service\":\"${SERVICE}\",\"lvl\":\"${PERCENT}\",\"hpnow\":\"${CURRENT}\",\"hptotal\":\"${TOTAL}\",\"hpdiff\":\"${DIFF}\"}" > /dev/null
|
||||
log_msg "${SERVICE} health level: ${PERCENT}% (${CURRENT}/${TOTAL}), health trend: ${DIFF}" no_valkey
|
||||
# Return 10 to indicate a dead service
|
||||
[ ${CURRENT} -le 0 ] && return 10
|
||||
}
|
||||
|
||||
log_msg() {
|
||||
if [[ ${2} != "no_redis" ]]; then
|
||||
${REDIS_CMDLINE} LPUSH WATCHDOG_LOG "{\"time\":\"$(date +%s)\",\"message\":\"$(printf '%s' "${1}" | \
|
||||
if [[ ${2} != "no_valkey" ]]; then
|
||||
${VALKEY_CMDLINE} LPUSH WATCHDOG_LOG "{\"time\":\"$(date +%s)\",\"message\":\"$(printf '%s' "${1}" | \
|
||||
tr '\r\n%&;$"_[]{}-' ' ')\"}" > /dev/null
|
||||
fi
|
||||
echo $(date) $(printf '%s\n' "${1}")
|
||||
@@ -109,10 +114,10 @@ function notify_error() {
|
||||
# If exists, mail will be throttled by argument in seconds
|
||||
[[ ! -z ${3} ]] && THROTTLE=${3}
|
||||
if [[ ! -z ${THROTTLE} ]]; then
|
||||
TTL_LEFT="$(${REDIS_CMDLINE} TTL THROTTLE_${1} 2> /dev/null)"
|
||||
TTL_LEFT="$(${VALKEY_CMDLINE} TTL THROTTLE_${1} 2> /dev/null)"
|
||||
if [[ "${TTL_LEFT}" == "-2" ]]; then
|
||||
# Delay key not found, setting a delay key now
|
||||
${REDIS_CMDLINE} SET THROTTLE_${1} 1 EX ${THROTTLE}
|
||||
${VALKEY_CMDLINE} SET THROTTLE_${1} 1 EX ${THROTTLE}
|
||||
else
|
||||
log_msg "Not sending notification email now, blocked for ${TTL_LEFT} seconds..."
|
||||
return 1
|
||||
@@ -297,7 +302,7 @@ unbound_checks() {
|
||||
touch /tmp/unbound-mailcow; echo "$(tail -50 /tmp/unbound-mailcow)" > /tmp/unbound-mailcow
|
||||
host_ip=$(get_container_ip unbound-mailcow)
|
||||
err_c_cur=${err_count}
|
||||
/usr/lib/nagios/plugins/check_dns -s ${host_ip} -H stackoverflow.com 2>> /tmp/unbound-mailcow 1>&2; err_count=$(( ${err_count} + $? ))
|
||||
/usr/lib/mailcow/check_dns.sh -s ${host_ip} -H stackoverflow.com 2>> /tmp/unbound-mailcow 1>&2; err_count=$(( ${err_count} + $? ))
|
||||
DNSSEC=$(dig com +dnssec | egrep 'flags:.+ad')
|
||||
if [[ -z ${DNSSEC} ]]; then
|
||||
echo "DNSSEC failure" 2>> /tmp/unbound-mailcow 1>&2
|
||||
@@ -319,21 +324,21 @@ unbound_checks() {
|
||||
return 1
|
||||
}
|
||||
|
||||
redis_checks() {
|
||||
# A check for the local redis container
|
||||
valkey_checks() {
|
||||
# A check for the local valkey container
|
||||
err_count=0
|
||||
diff_c=0
|
||||
THRESHOLD=${REDIS_THRESHOLD}
|
||||
THRESHOLD=${VALKEY_THRESHOLD}
|
||||
# Reduce error count by 2 after restarting an unhealthy container
|
||||
trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1
|
||||
while [ ${err_count} -lt ${THRESHOLD} ]; do
|
||||
touch /tmp/redis-mailcow; echo "$(tail -50 /tmp/redis-mailcow)" > /tmp/redis-mailcow
|
||||
host_ip=$(get_container_ip redis-mailcow)
|
||||
touch /tmp/valkey-mailcow; echo "$(tail -50 /tmp/valkey-mailcow)" > /tmp/valkey-mailcow
|
||||
host_ip=$(get_container_ip valkey-mailcow)
|
||||
err_c_cur=${err_count}
|
||||
/usr/lib/nagios/plugins/check_tcp -4 -H redis-mailcow -p 6379 -E -s "AUTH ${REDISPASS}\nPING\n" -q "QUIT" -e "PONG" 2>> /tmp/redis-mailcow 1>&2; err_count=$(( ${err_count} + $? ))
|
||||
/usr/lib/nagios/plugins/check_tcp -4 -H valkey-mailcow -p 6379 -E -s "AUTH ${VALKEYPASS}\nPING\n" -q "QUIT" -e "PONG" 2>> /tmp/valkey-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} -ne ${err_count} ] && diff_c=$(( ${err_c_cur} - ${err_count} ))
|
||||
progress "Redis" ${THRESHOLD} $(( ${THRESHOLD} - ${err_count} )) ${diff_c}
|
||||
progress "Valkey" ${THRESHOLD} $(( ${THRESHOLD} - ${err_count} )) ${diff_c}
|
||||
if [[ $? == 10 ]]; then
|
||||
diff_c=0
|
||||
sleep 1
|
||||
@@ -445,6 +450,31 @@ postfix_checks() {
|
||||
return 1
|
||||
}
|
||||
|
||||
postfix-tlspol_checks() {
|
||||
err_count=0
|
||||
diff_c=0
|
||||
THRESHOLD=${POSTFIX_TLSPOL_THRESHOLD}
|
||||
# Reduce error count by 2 after restarting an unhealthy container
|
||||
trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1
|
||||
while [ ${err_count} -lt ${THRESHOLD} ]; do
|
||||
touch /tmp/postfix-tlspol-mailcow; echo "$(tail -50 /tmp/postfix-tlspol-mailcow)" > /tmp/postfix-tlspol-mailcow
|
||||
host_ip=$(get_container_ip postfix-tlspol-mailcow)
|
||||
err_c_cur=${err_count}
|
||||
/usr/lib/nagios/plugins/check_tcp -4 -H ${host_ip} -p 8642 2>> /tmp/postfix-tlspol-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} -ne ${err_count} ] && diff_c=$(( ${err_c_cur} - ${err_count} ))
|
||||
progress "Postfix TLS Policy companion" ${THRESHOLD} $(( ${THRESHOLD} - ${err_count} )) ${diff_c}
|
||||
if [[ $? == 10 ]]; then
|
||||
diff_c=0
|
||||
sleep 1
|
||||
else
|
||||
diff_c=0
|
||||
sleep $(( ( RANDOM % 60 ) + 20 ))
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
clamd_checks() {
|
||||
err_count=0
|
||||
diff_c=0
|
||||
@@ -503,12 +533,12 @@ dovecot_repl_checks() {
|
||||
err_count=0
|
||||
diff_c=0
|
||||
THRESHOLD=${DOVECOT_REPL_THRESHOLD}
|
||||
D_REPL_STATUS=$(redis-cli -h redis -a ${REDISPASS} --no-auth-warning -r GET DOVECOT_REPL_HEALTH)
|
||||
D_REPL_STATUS=$(redis-cli -h valkey-mailcow -a ${VALKEYPASS} --no-auth-warning -r GET DOVECOT_REPL_HEALTH)
|
||||
# Reduce error count by 2 after restarting an unhealthy container
|
||||
trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1
|
||||
while [ ${err_count} -lt ${THRESHOLD} ]; do
|
||||
err_c_cur=${err_count}
|
||||
D_REPL_STATUS=$(redis-cli --raw -h redis -a ${REDISPASS} --no-auth-warning GET DOVECOT_REPL_HEALTH)
|
||||
D_REPL_STATUS=$(redis-cli --raw -h valkey-mailcow -a ${VALKEYPASS} --no-auth-warning GET DOVECOT_REPL_HEALTH)
|
||||
if [[ "${D_REPL_STATUS}" != "1" ]]; then
|
||||
err_count=$(( ${err_count} + 1 ))
|
||||
fi
|
||||
@@ -578,19 +608,19 @@ ratelimit_checks() {
|
||||
err_count=0
|
||||
diff_c=0
|
||||
THRESHOLD=${RATELIMIT_THRESHOLD}
|
||||
RL_LOG_STATUS=$(redis-cli -h redis -a ${REDISPASS} --no-auth-warning LRANGE RL_LOG 0 0 | jq .qid)
|
||||
RL_LOG_STATUS=$(redis-cli -h valkey-mailcow -a ${VALKEYPASS} --no-auth-warning LRANGE RL_LOG 0 0 | jq .qid)
|
||||
# Reduce error count by 2 after restarting an unhealthy container
|
||||
trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1
|
||||
while [ ${err_count} -lt ${THRESHOLD} ]; do
|
||||
err_c_cur=${err_count}
|
||||
RL_LOG_STATUS_PREV=${RL_LOG_STATUS}
|
||||
RL_LOG_STATUS=$(redis-cli -h redis -a ${REDISPASS} --no-auth-warning LRANGE RL_LOG 0 0 | jq .qid)
|
||||
RL_LOG_STATUS=$(redis-cli -h valkey-mailcow -a ${VALKEYPASS} --no-auth-warning LRANGE RL_LOG 0 0 | jq .qid)
|
||||
if [[ ${RL_LOG_STATUS_PREV} != ${RL_LOG_STATUS} ]]; then
|
||||
err_count=$(( ${err_count} + 1 ))
|
||||
echo 'Last 10 applied ratelimits (may overlap with previous reports).' > /tmp/ratelimit
|
||||
echo 'Full ratelimit buckets can be emptied by deleting the ratelimit hash from within mailcow UI (see /debug -> Protocols -> Ratelimit):' >> /tmp/ratelimit
|
||||
echo >> /tmp/ratelimit
|
||||
redis-cli --raw -h redis -a ${REDISPASS} --no-auth-warning LRANGE RL_LOG 0 10 | jq . >> /tmp/ratelimit
|
||||
redis-cli --raw -h valkey-mailcow -a ${VALKEYPASS} --no-auth-warning LRANGE RL_LOG 0 10 | jq . >> /tmp/ratelimit
|
||||
fi
|
||||
[ ${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} ))
|
||||
@@ -639,20 +669,20 @@ fail2ban_checks() {
|
||||
err_count=0
|
||||
diff_c=0
|
||||
THRESHOLD=${FAIL2BAN_THRESHOLD}
|
||||
F2B_LOG_STATUS=($(${REDIS_CMDLINE} --raw HKEYS F2B_ACTIVE_BANS))
|
||||
F2B_LOG_STATUS=($(${VALKEY_CMDLINE} --raw HKEYS F2B_ACTIVE_BANS))
|
||||
F2B_RES=
|
||||
# Reduce error count by 2 after restarting an unhealthy container
|
||||
trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1
|
||||
while [ ${err_count} -lt ${THRESHOLD} ]; do
|
||||
err_c_cur=${err_count}
|
||||
F2B_LOG_STATUS_PREV=(${F2B_LOG_STATUS[@]})
|
||||
F2B_LOG_STATUS=($(${REDIS_CMDLINE} --raw HKEYS F2B_ACTIVE_BANS))
|
||||
F2B_LOG_STATUS=($(${VALKEY_CMDLINE} --raw HKEYS F2B_ACTIVE_BANS))
|
||||
array_diff F2B_RES F2B_LOG_STATUS F2B_LOG_STATUS_PREV
|
||||
if [[ ! -z "${F2B_RES}" ]]; then
|
||||
err_count=$(( ${err_count} + 1 ))
|
||||
echo -n "${F2B_RES[@]}" | tr -cd "[a-fA-F0-9.:/] " | timeout 3s ${REDIS_CMDLINE} -x SET F2B_RES > /dev/null
|
||||
echo -n "${F2B_RES[@]}" | tr -cd "[a-fA-F0-9.:/] " | timeout 3s ${VALKEY_CMDLINE} -x SET F2B_RES > /dev/null
|
||||
if [ $? -ne 0 ]; then
|
||||
${REDIS_CMDLINE} -x DEL F2B_RES
|
||||
${VALKEY_CMDLINE} -x DEL F2B_RES
|
||||
fi
|
||||
fi
|
||||
[ ${err_c_cur} -eq ${err_count} ] && [ ! $((${err_count} - 1)) -lt 0 ] && err_count=$((${err_count} - 1)) diff_c=1
|
||||
@@ -673,9 +703,9 @@ acme_checks() {
|
||||
err_count=0
|
||||
diff_c=0
|
||||
THRESHOLD=${ACME_THRESHOLD}
|
||||
ACME_LOG_STATUS=$(redis-cli -h redis -a ${REDISPASS} --no-auth-warning GET ACME_FAIL_TIME)
|
||||
ACME_LOG_STATUS=$(redis-cli -h valkey-mailcow -a ${VALKEYPASS} --no-auth-warning GET ACME_FAIL_TIME)
|
||||
if [[ -z "${ACME_LOG_STATUS}" ]]; then
|
||||
${REDIS_CMDLINE} SET ACME_FAIL_TIME 0
|
||||
${VALKEY_CMDLINE} SET ACME_FAIL_TIME 0
|
||||
ACME_LOG_STATUS=0
|
||||
fi
|
||||
# Reduce error count by 2 after restarting an unhealthy container
|
||||
@@ -685,7 +715,7 @@ acme_checks() {
|
||||
ACME_LOG_STATUS_PREV=${ACME_LOG_STATUS}
|
||||
ACME_LC=0
|
||||
until [[ ! -z ${ACME_LOG_STATUS} ]] || [ ${ACME_LC} -ge 3 ]; do
|
||||
ACME_LOG_STATUS=$(redis-cli -h redis -a ${REDISPASS} --no-auth-warning GET ACME_FAIL_TIME 2> /dev/null)
|
||||
ACME_LOG_STATUS=$(redis-cli -h valkey-mailcow -a ${VALKEYPASS} --no-auth-warning GET ACME_FAIL_TIME 2> /dev/null)
|
||||
sleep 3
|
||||
ACME_LC=$((ACME_LC+1))
|
||||
done
|
||||
@@ -834,14 +864,14 @@ BACKGROUND_TASKS+=(${PID})
|
||||
|
||||
(
|
||||
while true; do
|
||||
if ! redis_checks; then
|
||||
log_msg "Local Redis hit error limit"
|
||||
echo redis-mailcow > /tmp/com_pipe
|
||||
if ! valkey_checks; then
|
||||
log_msg "Local Valkey hit error limit"
|
||||
echo valkey-mailcow > /tmp/com_pipe
|
||||
fi
|
||||
done
|
||||
) &
|
||||
PID=$!
|
||||
echo "Spawned redis_checks with PID ${PID}"
|
||||
echo "Spawned valkey_checks with PID ${PID}"
|
||||
BACKGROUND_TASKS+=(${PID})
|
||||
|
||||
(
|
||||
@@ -922,6 +952,18 @@ PID=$!
|
||||
echo "Spawned mailq_checks with PID ${PID}"
|
||||
BACKGROUND_TASKS+=(${PID})
|
||||
|
||||
(
|
||||
while true; do
|
||||
if ! postfix-tlspol_checks; then
|
||||
log_msg "Postfix TLS Policy hit error limit"
|
||||
echo postfix-tlspol-mailcow > /tmp/com_pipe
|
||||
fi
|
||||
done
|
||||
) &
|
||||
PID=$!
|
||||
echo "Spawned postfix-tlspol_checks with PID ${PID}"
|
||||
BACKGROUND_TASKS+=(${PID})
|
||||
|
||||
(
|
||||
while true; do
|
||||
if ! dovecot_checks; then
|
||||
@@ -1087,9 +1129,9 @@ while true; do
|
||||
# Define $2 to override message text, else print service was restarted at ...
|
||||
notify_error "${com_pipe_answer}" "Please check acme-mailcow for further information."
|
||||
elif [[ ${com_pipe_answer} == "fail2ban" ]]; then
|
||||
F2B_RES=($(timeout 4s ${REDIS_CMDLINE} --raw GET F2B_RES 2> /dev/null))
|
||||
F2B_RES=($(timeout 4s ${VALKEY_CMDLINE} --raw GET F2B_RES 2> /dev/null))
|
||||
if [[ ! -z "${F2B_RES}" ]]; then
|
||||
${REDIS_CMDLINE} DEL F2B_RES > /dev/null
|
||||
${VALKEY_CMDLINE} DEL F2B_RES > /dev/null
|
||||
host=
|
||||
for host in "${F2B_RES[@]}"; do
|
||||
log_msg "Banned ${host}"
|
||||
|
||||
@@ -23,16 +23,16 @@ if (file_exists('../../../web/inc/vars.local.inc.php')) {
|
||||
require_once '../../../web/inc/lib/vendor/autoload.php';
|
||||
|
||||
|
||||
// Init Redis
|
||||
$redis = new Redis();
|
||||
// Init Valkey
|
||||
$valkey = new Redis();
|
||||
try {
|
||||
if (!empty(getenv('REDIS_SLAVEOF_IP'))) {
|
||||
$redis->connect(getenv('REDIS_SLAVEOF_IP'), getenv('REDIS_SLAVEOF_PORT'));
|
||||
if (!empty(getenv('VALKEY_SLAVEOF_IP'))) {
|
||||
$valkey->connect(getenv('VALKEY_SLAVEOF_IP'), getenv('VALKEY_SLAVEOF_PORT'));
|
||||
}
|
||||
else {
|
||||
$redis->connect('redis-mailcow', 6379);
|
||||
$valkey->connect('valkey-mailcow', 6379);
|
||||
}
|
||||
$redis->auth(getenv("REDISPASS"));
|
||||
$valkey->auth(getenv("VALKEYPASS"));
|
||||
}
|
||||
catch (Exception $e) {
|
||||
error_log("MAILCOWAUTH: " . $e . PHP_EOL);
|
||||
@@ -86,7 +86,7 @@ if ($result === false){
|
||||
'remote_addr' => $post['real_rip']
|
||||
));
|
||||
if ($result) {
|
||||
error_log('MAILCOWAUTH: App auth for user ' . $post['username']);
|
||||
error_log('MAILCOWAUTH: App auth for user ' . $post['username'] . " with service " . $post['service'] . " from IP " . $post['real_rip']);
|
||||
set_sasl_log($post['username'], $post['real_rip'], $post['service']);
|
||||
}
|
||||
}
|
||||
@@ -94,9 +94,9 @@ if ($result === false){
|
||||
// Init Identity Provider
|
||||
$iam_provider = identity_provider('init');
|
||||
$iam_settings = identity_provider('get');
|
||||
$result = user_login($post['username'], $post['password'], array('is_internal' => true));
|
||||
$result = user_login($post['username'], $post['password'], array('is_internal' => true, 'service' => $post['service']));
|
||||
if ($result) {
|
||||
error_log('MAILCOWAUTH: User auth for user ' . $post['username']);
|
||||
error_log('MAILCOWAUTH: User auth for user ' . $post['username'] . " with service " . $post['service'] . " from IP " . $post['real_rip']);
|
||||
set_sasl_log($post['username'], $post['real_rip'], $post['service']);
|
||||
}
|
||||
}
|
||||
@@ -105,7 +105,7 @@ if ($result) {
|
||||
http_response_code(200); // OK
|
||||
$return['success'] = true;
|
||||
} else {
|
||||
error_log("MAILCOWAUTH: Login failed for user " . $post['username']);
|
||||
error_log("MAILCOWAUTH: Login failed for user " . $post['username'] . " with service " . $post['service'] . " from IP " . $post['real_rip']);
|
||||
http_response_code(401); // Unauthorized
|
||||
}
|
||||
|
||||
|
||||
@@ -48,13 +48,21 @@ http {
|
||||
listen {{ HTTP_PORT }} default_server;
|
||||
listen [::]:{{ HTTP_PORT }} default_server;
|
||||
|
||||
server_name {{ MAILCOW_HOSTNAME }} autodiscover.* autoconfig.* {{ ADDITIONAL_SERVER_NAMES | join(' ') }};
|
||||
server_name {{ MAILCOW_HOSTNAME }} autodiscover.* autoconfig.* mta-sts.* {{ ADDITIONAL_SERVER_NAMES | join(' ') }};
|
||||
|
||||
if ( $request_uri ~* "%0A|%0D" ) { return 403; }
|
||||
location ^~ /.well-known/acme-challenge/ {
|
||||
allow all;
|
||||
default_type "text/plain";
|
||||
}
|
||||
location ^~ /.well-known/mta-sts.txt {
|
||||
allow all;
|
||||
fastcgi_split_path_info ^(.+\.php)(/.+)$;
|
||||
fastcgi_pass {{ PHPFPMHOST }}:9002;
|
||||
include /etc/nginx/fastcgi_params;
|
||||
fastcgi_param SCRIPT_FILENAME $document_root/mta-sts.php;
|
||||
fastcgi_param PATH_INFO $fastcgi_path_info;
|
||||
}
|
||||
location / {
|
||||
return 301 https://$host$uri$is_args$args;
|
||||
}
|
||||
@@ -70,7 +78,7 @@ http {
|
||||
{%endif%}
|
||||
listen {{ HTTPS_PORT }}{% if NGINX_USE_PROXY_PROTOCOL %} proxy_protocol{%endif%} ssl;
|
||||
|
||||
{% if not DISABLE_IPv6 %}
|
||||
{% if ENABLE_IPV6 %}
|
||||
{% if not HTTP_REDIRECT %}
|
||||
listen [::]:{{ HTTP_PORT }}{% if NGINX_USE_PROXY_PROTOCOL %} proxy_protocol{%endif%};
|
||||
{%endif%}
|
||||
@@ -82,7 +90,7 @@ http {
|
||||
ssl_certificate /etc/ssl/mail/cert.pem;
|
||||
ssl_certificate_key /etc/ssl/mail/key.pem;
|
||||
|
||||
server_name {{ MAILCOW_HOSTNAME }} autodiscover.* autoconfig.*;
|
||||
server_name {{ MAILCOW_HOSTNAME }} autodiscover.* autoconfig.* mta-sts.*;
|
||||
|
||||
include /etc/nginx/includes/sites-default.conf;
|
||||
}
|
||||
@@ -97,7 +105,7 @@ http {
|
||||
{%endif%}
|
||||
listen {{ HTTPS_PORT }}{% if NGINX_USE_PROXY_PROTOCOL %} proxy_protocol{%endif%} ssl;
|
||||
|
||||
{% if not DISABLE_IPv6 %}
|
||||
{% if ENABLE_IPV6 %}
|
||||
{% if not HTTP_REDIRECT %}
|
||||
listen [::]:{{ HTTP_PORT }}{% if NGINX_USE_PROXY_PROTOCOL %} proxy_protocol{%endif%};
|
||||
{%endif%}
|
||||
@@ -118,7 +126,7 @@ http {
|
||||
# rspamd dynmaps:
|
||||
server {
|
||||
listen 8081;
|
||||
{% if not DISABLE_IPv6 %}
|
||||
{% if ENABLE_IPV6 %}
|
||||
listen [::]:8081;
|
||||
{%endif%}
|
||||
index index.php index.html;
|
||||
@@ -191,7 +199,7 @@ http {
|
||||
{%endif%}
|
||||
listen {{ HTTPS_PORT }}{% if NGINX_USE_PROXY_PROTOCOL %} proxy_protocol{%endif%} ssl;
|
||||
|
||||
{% if not DISABLE_IPv6 %}
|
||||
{% if ENABLE_IPV6 %}
|
||||
{% if not HTTP_REDIRECT %}
|
||||
listen [::]:{{ HTTP_PORT }}{% if NGINX_USE_PROXY_PROTOCOL %} proxy_protocol{%endif%};
|
||||
{%endif%}
|
||||
|
||||
@@ -76,6 +76,14 @@ location ^~ /.well-known/acme-challenge/ {
|
||||
allow all;
|
||||
default_type "text/plain";
|
||||
}
|
||||
location ^~ /.well-known/mta-sts.txt {
|
||||
allow all;
|
||||
fastcgi_split_path_info ^(.+\.php)(/.+)$;
|
||||
fastcgi_pass {{ PHPFPMHOST }}:9002;
|
||||
include /etc/nginx/fastcgi_params;
|
||||
fastcgi_param SCRIPT_FILENAME $document_root/mta-sts.php;
|
||||
fastcgi_param PATH_INFO $fastcgi_path_info;
|
||||
}
|
||||
|
||||
rewrite ^/.well-known/caldav$ /SOGo/dav/ permanent;
|
||||
rewrite ^/.well-known/carddav$ /SOGo/dav/ permanent;
|
||||
|
||||
@@ -23,16 +23,16 @@ catch (PDOException $e) {
|
||||
exit;
|
||||
}
|
||||
|
||||
// Init Redis
|
||||
$redis = new Redis();
|
||||
// Init Valkey
|
||||
$valkey = new Redis();
|
||||
try {
|
||||
if (!empty(getenv('REDIS_SLAVEOF_IP'))) {
|
||||
$redis->connect(getenv('REDIS_SLAVEOF_IP'), getenv('REDIS_SLAVEOF_PORT'));
|
||||
if (!empty(getenv('VALKEY_SLAVEOF_IP'))) {
|
||||
$valkey->connect(getenv('VALKEY_SLAVEOF_IP'), getenv('VALKEY_SLAVEOF_PORT'));
|
||||
}
|
||||
else {
|
||||
$redis->connect('redis-mailcow', 6379);
|
||||
$valkey->connect('valkey-mailcow', 6379);
|
||||
}
|
||||
$redis->auth(getenv("REDISPASS"));
|
||||
$valkey->auth(getenv("VALKEYPASS"));
|
||||
}
|
||||
catch (Exception $e) {
|
||||
echo "Exiting: " . $e->getMessage();
|
||||
@@ -41,7 +41,7 @@ catch (Exception $e) {
|
||||
}
|
||||
|
||||
function logMsg($priority, $message, $task = "Keycloak Sync") {
|
||||
global $redis;
|
||||
global $valkey;
|
||||
|
||||
$finalMsg = array(
|
||||
"time" => time(),
|
||||
@@ -49,7 +49,7 @@ function logMsg($priority, $message, $task = "Keycloak Sync") {
|
||||
"task" => $task,
|
||||
"message" => $message
|
||||
);
|
||||
$redis->lPush('CRON_LOG', json_encode($finalMsg));
|
||||
$valkey->lPush('CRON_LOG', json_encode($finalMsg));
|
||||
}
|
||||
|
||||
// Load core functions first
|
||||
|
||||
@@ -23,16 +23,16 @@ catch (PDOException $e) {
|
||||
exit;
|
||||
}
|
||||
|
||||
// Init Redis
|
||||
$redis = new Redis();
|
||||
// Init Valkey
|
||||
$valkey = new Redis();
|
||||
try {
|
||||
if (!empty(getenv('REDIS_SLAVEOF_IP'))) {
|
||||
$redis->connect(getenv('REDIS_SLAVEOF_IP'), getenv('REDIS_SLAVEOF_PORT'));
|
||||
if (!empty(getenv('VALKEY_SLAVEOF_IP'))) {
|
||||
$valkey->connect(getenv('VALKEY_SLAVEOF_IP'), getenv('VALKEY_SLAVEOF_PORT'));
|
||||
}
|
||||
else {
|
||||
$redis->connect('redis-mailcow', 6379);
|
||||
$valkey->connect('valkey-mailcow', 6379);
|
||||
}
|
||||
$redis->auth(getenv("REDISPASS"));
|
||||
$valkey->auth(getenv("VALKEYPASS"));
|
||||
}
|
||||
catch (Exception $e) {
|
||||
echo "Exiting: " . $e->getMessage();
|
||||
@@ -41,7 +41,7 @@ catch (Exception $e) {
|
||||
}
|
||||
|
||||
function logMsg($priority, $message, $task = "LDAP Sync") {
|
||||
global $redis;
|
||||
global $valkey;
|
||||
|
||||
$finalMsg = array(
|
||||
"time" => time(),
|
||||
@@ -49,7 +49,7 @@ function logMsg($priority, $message, $task = "LDAP Sync") {
|
||||
"task" => $task,
|
||||
"message" => $message
|
||||
);
|
||||
$redis->lPush('CRON_LOG', json_encode($finalMsg));
|
||||
$valkey->lPush('CRON_LOG', json_encode($finalMsg));
|
||||
}
|
||||
|
||||
// Load core functions first
|
||||
|
||||
@@ -152,7 +152,7 @@ smtp_sasl_auth_enable = yes
|
||||
smtp_sasl_password_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_sasl_passwd_maps_sender_dependent.cf
|
||||
smtp_sasl_security_options =
|
||||
smtp_sasl_mechanism_filter = plain, login
|
||||
smtp_tls_policy_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_tls_policy_override_maps.cf
|
||||
smtp_tls_policy_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_tls_policy_override_maps.cf socketmap:inet:postfix-tlspol:8642:QUERY
|
||||
smtp_header_checks = pcre:/opt/postfix/conf/anonymize_headers.pcre
|
||||
mail_name = Postcow
|
||||
# local_transport map catches local destinations and prevents routing local dests when the next map would route "*"
|
||||
|
||||
@@ -1,13 +1,26 @@
|
||||
# Whitelist generated by Postwhite v3.4 on Thu May 1 00:21:10 UTC 2025
|
||||
# Whitelist generated by Postwhite v3.4 on Wed Oct 1 00:21:33 UTC 2025
|
||||
# https://github.com/stevejenkins/postwhite/
|
||||
# 2058 total rules
|
||||
# 2216 total rules
|
||||
2a00:1450:4000::/36 permit
|
||||
2a01:111:f400::/48 permit
|
||||
2a01:111:f403:8000::/50 permit
|
||||
2a01:111:f403:2800::/53 permit
|
||||
2a01:111:f403:8000::/51 permit
|
||||
2a01:111:f403::/49 permit
|
||||
2a01:111:f403:c000::/51 permit
|
||||
2a01:111:f403:d000::/53 permit
|
||||
2a01:111:f403:f000::/52 permit
|
||||
2a01:238:20a:202:5370::1 permit
|
||||
2a01:238:20a:202:5372::1 permit
|
||||
2a01:238:20a:202:5373::1 permit
|
||||
2a01:238:400:101:53::1 permit
|
||||
2a01:238:400:102:53::1 permit
|
||||
2a01:238:400:103:53::1 permit
|
||||
2a01:238:400:301:53::1 permit
|
||||
2a01:238:400:302:53::1 permit
|
||||
2a01:238:400:303:53::1 permit
|
||||
2a01:238:400:470:53::1 permit
|
||||
2a01:238:400:471:53::1 permit
|
||||
2a01:238:400:472:53::1 permit
|
||||
2a01:b747:3000:200::/56 permit
|
||||
2a01:b747:3001:200::/56 permit
|
||||
2a01:b747:3002:200::/56 permit
|
||||
@@ -17,26 +30,42 @@
|
||||
2a01:b747:3006:200::/56 permit
|
||||
2a02:a60:0:5::/64 permit
|
||||
2c0f:fb50:4000::/36 permit
|
||||
2.207.151.53 permit
|
||||
2.207.217.30 permit
|
||||
3.64.237.68 permit
|
||||
3.65.3.180 permit
|
||||
3.70.123.177 permit
|
||||
3.72.182.33 permit
|
||||
3.74.81.189 permit
|
||||
3.74.125.228 permit
|
||||
3.75.33.185 permit
|
||||
3.93.157.0/24 permit
|
||||
3.94.40.108 permit
|
||||
3.121.107.214 permit
|
||||
3.129.120.190 permit
|
||||
3.210.190.0/24 permit
|
||||
3.211.80.218 permit
|
||||
3.216.221.67 permit
|
||||
3.221.209.22 permit
|
||||
8.20.114.31 permit
|
||||
8.25.194.0/23 permit
|
||||
8.25.196.0/23 permit
|
||||
8.36.116.0/24 permit
|
||||
8.39.54.0/23 permit
|
||||
8.39.54.250/31 permit
|
||||
8.39.144.0/24 permit
|
||||
8.40.222.0/23 permit
|
||||
8.40.222.250/31 permit
|
||||
12.130.86.238 permit
|
||||
13.107.246.59 permit
|
||||
13.107.213.41 permit
|
||||
13.107.246.41 permit
|
||||
13.110.208.0/21 permit
|
||||
13.110.209.0/24 permit
|
||||
13.110.216.0/22 permit
|
||||
13.110.224.0/20 permit
|
||||
13.111.0.0/16 permit
|
||||
13.111.191.0/24 permit
|
||||
13.216.7.111 permit
|
||||
13.216.54.180 permit
|
||||
15.200.21.50 permit
|
||||
15.200.44.248 permit
|
||||
15.200.201.185 permit
|
||||
@@ -49,16 +78,21 @@
|
||||
18.97.1.184/29 permit
|
||||
18.97.2.64/26 permit
|
||||
18.156.89.250 permit
|
||||
18.156.205.64 permit
|
||||
18.157.70.148 permit
|
||||
18.157.114.255 permit
|
||||
18.157.243.190 permit
|
||||
18.158.153.154 permit
|
||||
18.194.95.56 permit
|
||||
18.197.217.180 permit
|
||||
18.198.96.88 permit
|
||||
18.199.210.3 permit
|
||||
18.207.52.234 permit
|
||||
18.208.124.128/25 permit
|
||||
18.216.232.154 permit
|
||||
18.235.27.253 permit
|
||||
18.236.40.242 permit
|
||||
18.236.56.161 permit
|
||||
20.51.6.32/30 permit
|
||||
20.51.98.61 permit
|
||||
20.52.52.2 permit
|
||||
20.52.128.133 permit
|
||||
20.59.80.4/30 permit
|
||||
@@ -88,6 +122,7 @@
|
||||
23.253.183.147 permit
|
||||
23.253.183.148 permit
|
||||
23.253.183.150 permit
|
||||
24.110.64.0/18 permit
|
||||
27.123.204.128/30 permit
|
||||
27.123.204.132/31 permit
|
||||
27.123.204.148/30 permit
|
||||
@@ -100,7 +135,6 @@
|
||||
27.123.206.56/29 permit
|
||||
27.123.206.76/30 permit
|
||||
27.123.206.80/28 permit
|
||||
31.25.48.222 permit
|
||||
31.47.251.17 permit
|
||||
31.186.239.0/24 permit
|
||||
34.2.64.0/22 permit
|
||||
@@ -125,16 +159,25 @@
|
||||
34.74.74.140 permit
|
||||
34.83.159.189 permit
|
||||
34.141.160.224 permit
|
||||
34.193.58.168 permit
|
||||
34.195.217.107 permit
|
||||
34.197.10.50 permit
|
||||
34.197.254.9 permit
|
||||
34.198.94.229 permit
|
||||
34.198.218.121 permit
|
||||
34.212.163.75 permit
|
||||
34.215.104.144 permit
|
||||
34.218.116.3 permit
|
||||
34.218.115.239 permit
|
||||
34.225.212.172 permit
|
||||
34.241.242.183 permit
|
||||
35.83.148.184 permit
|
||||
35.155.198.111 permit
|
||||
35.158.23.94 permit
|
||||
35.161.32.253 permit
|
||||
35.162.73.231 permit
|
||||
35.167.93.243 permit
|
||||
35.174.145.124 permit
|
||||
35.176.132.251 permit
|
||||
35.190.247.0/24 permit
|
||||
35.191.0.0/16 permit
|
||||
35.205.92.9 permit
|
||||
35.228.216.85 permit
|
||||
35.242.169.159 permit
|
||||
@@ -150,12 +193,21 @@
|
||||
40.233.64.216 permit
|
||||
40.233.83.78 permit
|
||||
40.233.88.28 permit
|
||||
43.239.212.33 permit
|
||||
44.206.138.57 permit
|
||||
44.210.169.44 permit
|
||||
44.217.45.156 permit
|
||||
44.236.56.93 permit
|
||||
44.238.220.251 permit
|
||||
44.245.243.92 permit
|
||||
44.246.1.125 permit
|
||||
44.246.68.102 permit
|
||||
44.246.77.92 permit
|
||||
45.14.148.0/22 permit
|
||||
46.19.170.16 permit
|
||||
45.143.132.0/24 permit
|
||||
45.143.133.0/24 permit
|
||||
45.143.134.0/24 permit
|
||||
45.143.135.0/24 permit
|
||||
46.226.48.0/21 permit
|
||||
46.228.36.37 permit
|
||||
46.228.36.38/31 permit
|
||||
@@ -206,6 +258,7 @@
|
||||
46.243.88.177 permit
|
||||
46.243.95.179 permit
|
||||
46.243.95.180 permit
|
||||
50.16.246.183 permit
|
||||
50.18.45.249 permit
|
||||
50.18.121.236 permit
|
||||
50.18.121.248 permit
|
||||
@@ -219,14 +272,24 @@
|
||||
50.56.130.220 permit
|
||||
50.56.130.221 permit
|
||||
50.56.130.222 permit
|
||||
50.112.246.219 permit
|
||||
52.1.14.157 permit
|
||||
52.5.230.59 permit
|
||||
52.6.74.205 permit
|
||||
52.12.53.23 permit
|
||||
52.13.214.179 permit
|
||||
52.26.1.71 permit
|
||||
52.27.5.72 permit
|
||||
52.27.28.47 permit
|
||||
52.28.63.81 permit
|
||||
52.28.197.132 permit
|
||||
52.34.181.151 permit
|
||||
52.35.192.45 permit
|
||||
52.36.138.31 permit
|
||||
52.37.142.146 permit
|
||||
52.42.203.116 permit
|
||||
52.50.24.208 permit
|
||||
52.57.120.243 permit
|
||||
52.58.216.183 permit
|
||||
52.59.143.3 permit
|
||||
52.60.41.5 permit
|
||||
@@ -269,23 +332,24 @@
|
||||
54.174.63.0/24 permit
|
||||
54.186.193.102 permit
|
||||
54.191.223.56 permit
|
||||
54.211.126.101 permit
|
||||
54.213.20.246 permit
|
||||
54.214.39.184 permit
|
||||
54.240.0.0/18 permit
|
||||
54.240.64.0/19 permit
|
||||
54.240.96.0/19 permit
|
||||
54.240.64.0/18 permit
|
||||
54.241.16.209 permit
|
||||
54.244.54.130 permit
|
||||
54.244.242.0/24 permit
|
||||
54.255.61.23 permit
|
||||
56.124.6.228 permit
|
||||
57.103.64.0/18 permit
|
||||
57.129.93.249 permit
|
||||
62.13.128.0/24 permit
|
||||
62.13.129.128/25 permit
|
||||
62.13.136.0/21 permit
|
||||
62.13.144.0/21 permit
|
||||
62.13.152.0/21 permit
|
||||
62.17.146.128/26 permit
|
||||
62.179.121.0/24 permit
|
||||
62.201.172.0/27 permit
|
||||
62.201.172.32/27 permit
|
||||
62.253.227.114 permit
|
||||
@@ -293,6 +357,9 @@
|
||||
63.128.21.0/24 permit
|
||||
63.143.57.128/25 permit
|
||||
63.143.59.128/25 permit
|
||||
63.176.194.123 permit
|
||||
63.178.132.221 permit
|
||||
63.178.143.178 permit
|
||||
64.18.0.0/20 permit
|
||||
64.20.241.45 permit
|
||||
64.69.212.0/24 permit
|
||||
@@ -305,6 +372,7 @@
|
||||
64.127.115.252 permit
|
||||
64.132.88.0/23 permit
|
||||
64.132.92.0/24 permit
|
||||
64.181.194.190 permit
|
||||
64.207.219.7 permit
|
||||
64.207.219.8 permit
|
||||
64.207.219.9 permit
|
||||
@@ -358,10 +426,10 @@
|
||||
65.110.161.77 permit
|
||||
65.123.29.213 permit
|
||||
65.123.29.220 permit
|
||||
65.154.166.0/24 permit
|
||||
65.212.180.36 permit
|
||||
66.102.0.0/20 permit
|
||||
66.119.150.192/26 permit
|
||||
66.162.193.226/31 permit
|
||||
66.163.184.0/24 permit
|
||||
66.163.185.0/24 permit
|
||||
66.163.186.0/24 permit
|
||||
@@ -567,7 +635,6 @@
|
||||
74.86.241.250/31 permit
|
||||
74.112.67.243 permit
|
||||
74.125.0.0/16 permit
|
||||
74.202.227.40 permit
|
||||
74.208.4.200 permit
|
||||
74.208.4.201 permit
|
||||
74.208.4.220 permit
|
||||
@@ -596,6 +663,11 @@
|
||||
77.238.189.142 permit
|
||||
77.238.189.146/31 permit
|
||||
77.238.189.148/30 permit
|
||||
79.135.106.0/24 permit
|
||||
79.135.107.0/24 permit
|
||||
81.169.146.243 permit
|
||||
81.169.146.245 permit
|
||||
81.169.146.246 permit
|
||||
81.223.46.0/27 permit
|
||||
82.165.159.2 permit
|
||||
82.165.159.3 permit
|
||||
@@ -611,10 +683,17 @@
|
||||
82.165.159.45 permit
|
||||
82.165.159.130 permit
|
||||
82.165.159.131 permit
|
||||
84.116.6.0/23 permit
|
||||
84.116.36.0/24 permit
|
||||
84.116.50.0/23 permit
|
||||
85.9.206.169 permit
|
||||
85.9.210.45 permit
|
||||
85.158.136.0/21 permit
|
||||
85.215.255.39 permit
|
||||
85.215.255.40 permit
|
||||
85.215.255.41 permit
|
||||
85.215.255.45 permit
|
||||
85.215.255.46 permit
|
||||
85.215.255.47 permit
|
||||
85.215.255.48 permit
|
||||
85.215.255.49 permit
|
||||
86.61.88.25 permit
|
||||
87.238.80.0/21 permit
|
||||
87.248.103.12 permit
|
||||
@@ -654,12 +733,13 @@
|
||||
87.248.117.205 permit
|
||||
87.253.232.0/21 permit
|
||||
89.22.108.0/24 permit
|
||||
91.198.2.0/24 permit
|
||||
91.211.240.0/22 permit
|
||||
94.169.2.0/23 permit
|
||||
94.236.119.0/26 permit
|
||||
94.245.112.0/27 permit
|
||||
94.245.112.10/31 permit
|
||||
95.131.104.0/21 permit
|
||||
95.217.114.154 permit
|
||||
96.43.144.0/20 permit
|
||||
96.43.144.64/28 permit
|
||||
96.43.144.64/31 permit
|
||||
@@ -1153,15 +1233,14 @@
|
||||
99.83.190.102 permit
|
||||
103.9.96.0/22 permit
|
||||
103.28.42.0/24 permit
|
||||
103.84.217.238 permit
|
||||
103.89.75.238 permit
|
||||
103.151.192.0/23 permit
|
||||
103.168.172.128/27 permit
|
||||
103.237.104.0/22 permit
|
||||
104.43.243.237 permit
|
||||
104.44.112.128/25 permit
|
||||
104.47.0.0/17 permit
|
||||
104.47.20.0/23 permit
|
||||
104.47.75.0/24 permit
|
||||
104.47.108.0/23 permit
|
||||
104.130.96.0/28 permit
|
||||
104.130.122.0/23 permit
|
||||
106.10.144.64/27 permit
|
||||
@@ -1287,6 +1366,7 @@
|
||||
106.50.16.0/28 permit
|
||||
107.20.18.111 permit
|
||||
107.20.210.250 permit
|
||||
107.22.191.150 permit
|
||||
108.174.0.0/24 permit
|
||||
108.174.0.215 permit
|
||||
108.174.3.0/24 permit
|
||||
@@ -1295,9 +1375,8 @@
|
||||
108.174.6.215 permit
|
||||
108.175.18.45 permit
|
||||
108.175.30.45 permit
|
||||
108.177.8.0/21 permit
|
||||
108.177.96.0/19 permit
|
||||
108.179.144.0/20 permit
|
||||
109.224.244.0/24 permit
|
||||
109.237.142.0/24 permit
|
||||
111.221.23.128/25 permit
|
||||
111.221.26.0/27 permit
|
||||
@@ -1321,6 +1400,9 @@
|
||||
117.120.16.0/21 permit
|
||||
119.42.242.52/31 permit
|
||||
119.42.242.156 permit
|
||||
121.244.91.48 permit
|
||||
121.244.91.52 permit
|
||||
122.15.156.182 permit
|
||||
123.126.78.64/29 permit
|
||||
124.108.96.24/31 permit
|
||||
124.108.96.28/31 permit
|
||||
@@ -1369,7 +1451,6 @@
|
||||
129.213.195.191 permit
|
||||
130.61.9.72 permit
|
||||
130.162.39.83 permit
|
||||
130.211.0.0/22 permit
|
||||
130.248.172.0/24 permit
|
||||
130.248.173.0/24 permit
|
||||
131.253.30.0/24 permit
|
||||
@@ -1378,12 +1459,28 @@
|
||||
132.226.26.225 permit
|
||||
132.226.49.32 permit
|
||||
132.226.56.24 permit
|
||||
134.128.64.0/19 permit
|
||||
134.128.96.0/19 permit
|
||||
134.170.27.8 permit
|
||||
134.170.113.0/26 permit
|
||||
134.170.141.64/26 permit
|
||||
134.170.143.0/24 permit
|
||||
134.170.174.0/24 permit
|
||||
135.84.80.0/24 permit
|
||||
135.84.81.0/24 permit
|
||||
135.84.82.0/24 permit
|
||||
135.84.83.0/24 permit
|
||||
135.84.216.0/22 permit
|
||||
136.143.160.0/24 permit
|
||||
136.143.161.0/24 permit
|
||||
136.143.162.0/24 permit
|
||||
136.143.176.0/24 permit
|
||||
136.143.177.0/24 permit
|
||||
136.143.178.49 permit
|
||||
136.143.182.0/23 permit
|
||||
136.143.184.0/24 permit
|
||||
136.143.188.0/24 permit
|
||||
136.143.190.0/23 permit
|
||||
136.147.128.0/20 permit
|
||||
136.147.135.0/24 permit
|
||||
136.147.176.0/20 permit
|
||||
@@ -1398,6 +1495,7 @@
|
||||
139.138.46.219 permit
|
||||
139.138.57.55 permit
|
||||
139.138.58.119 permit
|
||||
139.167.79.86 permit
|
||||
139.180.17.0/24 permit
|
||||
140.238.148.191 permit
|
||||
141.148.159.229 permit
|
||||
@@ -1442,7 +1540,7 @@
|
||||
148.105.0.0/16 permit
|
||||
148.105.8.0/21 permit
|
||||
149.72.0.0/16 permit
|
||||
149.72.223.204 permit
|
||||
149.72.234.184 permit
|
||||
149.72.248.236 permit
|
||||
149.97.173.180 permit
|
||||
150.230.98.160 permit
|
||||
@@ -1498,6 +1596,7 @@
|
||||
159.183.0.0/16 permit
|
||||
159.183.68.71 permit
|
||||
159.183.79.38 permit
|
||||
159.183.129.172 permit
|
||||
160.1.62.192 permit
|
||||
161.38.192.0/20 permit
|
||||
161.38.204.0/22 permit
|
||||
@@ -1515,9 +1614,13 @@
|
||||
163.114.134.16 permit
|
||||
163.114.135.16 permit
|
||||
163.116.128.0/17 permit
|
||||
163.192.116.87 permit
|
||||
164.152.23.32 permit
|
||||
164.152.25.241 permit
|
||||
164.177.132.168/30 permit
|
||||
165.173.128.0/24 permit
|
||||
165.173.180.250/31 permit
|
||||
165.173.182.250/31 permit
|
||||
166.78.68.0/22 permit
|
||||
166.78.68.221 permit
|
||||
166.78.69.169 permit
|
||||
@@ -1542,20 +1645,28 @@
|
||||
168.138.5.36 permit
|
||||
168.138.73.51 permit
|
||||
168.138.77.31 permit
|
||||
168.138.237.153 permit
|
||||
168.245.0.0/17 permit
|
||||
168.245.12.252 permit
|
||||
168.245.46.9 permit
|
||||
168.245.127.231 permit
|
||||
169.148.129.0/24 permit
|
||||
169.148.131.0/24 permit
|
||||
169.148.138.0/24 permit
|
||||
169.148.142.10 permit
|
||||
169.148.142.33 permit
|
||||
169.148.144.0/25 permit
|
||||
169.148.144.10 permit
|
||||
169.148.146.0/23 permit
|
||||
169.148.175.3 permit
|
||||
169.148.188.0/24 permit
|
||||
169.148.188.182 permit
|
||||
170.10.128.0/24 permit
|
||||
170.10.129.0/24 permit
|
||||
170.10.132.56/29 permit
|
||||
170.10.132.64/29 permit
|
||||
170.10.133.0/24 permit
|
||||
172.217.0.0/19 permit
|
||||
172.217.32.0/20 permit
|
||||
172.217.128.0/19 permit
|
||||
172.217.160.0/20 permit
|
||||
172.217.192.0/19 permit
|
||||
172.253.56.0/21 permit
|
||||
172.253.112.0/20 permit
|
||||
173.0.84.0/29 permit
|
||||
@@ -1585,9 +1696,14 @@
|
||||
182.50.78.64/28 permit
|
||||
183.240.219.64/29 permit
|
||||
185.4.120.0/22 permit
|
||||
185.11.253.128/27 permit
|
||||
185.11.255.0/24 permit
|
||||
185.12.80.0/22 permit
|
||||
185.28.196.0/22 permit
|
||||
185.58.84.93 permit
|
||||
185.70.40.0/24 permit
|
||||
185.70.41.0/24 permit
|
||||
185.70.43.0/24 permit
|
||||
185.80.93.204 permit
|
||||
185.80.93.227 permit
|
||||
185.80.95.31 permit
|
||||
@@ -1595,6 +1711,8 @@
|
||||
185.138.56.128/25 permit
|
||||
185.189.236.0/22 permit
|
||||
185.211.120.0/22 permit
|
||||
185.233.188.0/23 permit
|
||||
185.233.190.0/23 permit
|
||||
185.250.236.0/22 permit
|
||||
185.250.239.148 permit
|
||||
185.250.239.168 permit
|
||||
@@ -1647,6 +1765,7 @@
|
||||
188.125.85.234/31 permit
|
||||
188.125.85.236/31 permit
|
||||
188.125.85.238 permit
|
||||
188.165.51.139 permit
|
||||
188.172.128.0/20 permit
|
||||
192.0.64.0/18 permit
|
||||
192.18.139.154 permit
|
||||
@@ -1669,7 +1788,12 @@
|
||||
193.109.254.0/23 permit
|
||||
193.122.128.100 permit
|
||||
193.123.56.63 permit
|
||||
193.142.157.0/24 permit
|
||||
193.142.157.191 permit
|
||||
193.142.157.198 permit
|
||||
194.19.134.0/25 permit
|
||||
194.25.134.16/28 permit
|
||||
194.25.134.80/28 permit
|
||||
194.64.234.129 permit
|
||||
194.97.196.0/24 permit
|
||||
194.97.196.3 permit
|
||||
@@ -1722,7 +1846,16 @@
|
||||
199.16.156.0/22 permit
|
||||
199.33.145.1 permit
|
||||
199.33.145.32 permit
|
||||
199.34.22.36 permit
|
||||
199.59.148.0/22 permit
|
||||
199.67.80.2 permit
|
||||
199.67.80.20 permit
|
||||
199.67.82.2 permit
|
||||
199.67.82.20 permit
|
||||
199.67.84.0/24 permit
|
||||
199.67.86.0/24 permit
|
||||
199.67.88.0/24 permit
|
||||
199.67.90.0/24 permit
|
||||
199.101.161.130 permit
|
||||
199.101.162.0/25 permit
|
||||
199.122.120.0/21 permit
|
||||
@@ -1779,9 +1912,13 @@
|
||||
204.92.114.187 permit
|
||||
204.92.114.203 permit
|
||||
204.92.114.204/31 permit
|
||||
204.141.32.0/23 permit
|
||||
204.141.42.0/23 permit
|
||||
204.216.164.202 permit
|
||||
204.220.160.0/21 permit
|
||||
204.220.168.0/21 permit
|
||||
204.220.176.0/20 permit
|
||||
204.220.181.105 permit
|
||||
204.232.168.0/24 permit
|
||||
205.139.110.0/24 permit
|
||||
205.201.128.0/20 permit
|
||||
@@ -1856,8 +1993,6 @@
|
||||
208.71.42.212/31 permit
|
||||
208.71.42.214 permit
|
||||
208.72.249.240/29 permit
|
||||
208.74.204.5 permit
|
||||
208.74.204.9 permit
|
||||
208.75.120.0/22 permit
|
||||
208.76.62.0/24 permit
|
||||
208.76.63.0/24 permit
|
||||
@@ -1921,6 +2056,8 @@
|
||||
212.227.15.4 permit
|
||||
212.227.15.5 permit
|
||||
212.227.15.6 permit
|
||||
212.227.15.7 permit
|
||||
212.227.15.8 permit
|
||||
212.227.15.14 permit
|
||||
212.227.15.15 permit
|
||||
212.227.15.18 permit
|
||||
@@ -1937,21 +2074,36 @@
|
||||
212.227.15.53 permit
|
||||
212.227.15.54 permit
|
||||
212.227.15.55 permit
|
||||
212.227.17.1 permit
|
||||
212.227.17.2 permit
|
||||
212.227.17.7 permit
|
||||
212.227.17.11 permit
|
||||
212.227.17.12 permit
|
||||
212.227.17.16 permit
|
||||
212.227.17.17 permit
|
||||
212.227.17.18 permit
|
||||
212.227.17.19 permit
|
||||
212.227.17.20 permit
|
||||
212.227.17.21 permit
|
||||
212.227.17.22 permit
|
||||
212.227.17.26 permit
|
||||
212.227.17.27 permit
|
||||
212.227.17.28 permit
|
||||
212.227.17.29 permit
|
||||
212.227.126.206 permit
|
||||
212.227.126.207 permit
|
||||
212.227.126.208 permit
|
||||
212.227.126.209 permit
|
||||
212.227.126.220 permit
|
||||
212.227.126.221 permit
|
||||
212.227.126.222 permit
|
||||
212.227.126.223 permit
|
||||
212.227.126.224 permit
|
||||
212.227.126.225 permit
|
||||
212.227.126.226 permit
|
||||
212.227.126.227 permit
|
||||
213.46.255.0/24 permit
|
||||
213.95.19.64/27 permit
|
||||
213.95.135.4 permit
|
||||
213.199.128.139 permit
|
||||
213.199.128.145 permit
|
||||
213.199.138.181 permit
|
||||
@@ -1961,6 +2113,7 @@
|
||||
216.17.150.242 permit
|
||||
216.17.150.251 permit
|
||||
216.24.224.0/20 permit
|
||||
216.27.86.152/31 permit
|
||||
216.39.60.154/31 permit
|
||||
216.39.60.156/30 permit
|
||||
216.39.60.160/30 permit
|
||||
@@ -1998,6 +2151,8 @@
|
||||
216.99.5.68 permit
|
||||
216.109.114.32/27 permit
|
||||
216.109.114.64/29 permit
|
||||
216.113.162.65 permit
|
||||
216.113.163.65 permit
|
||||
216.128.126.97 permit
|
||||
216.136.162.65 permit
|
||||
216.136.162.120/29 permit
|
||||
@@ -2046,6 +2201,9 @@
|
||||
2603:1030:20e:3::23c permit
|
||||
2603:1030:b:3::152 permit
|
||||
2603:1030:c02:8::14 permit
|
||||
2607:13c0:0001:0000:0000:0000:0000:7000/116 permit
|
||||
2607:13c0:0002:0000:0000:0000:0000:1000/116 permit
|
||||
2607:13c0:0004:0000:0000:0000:0000:0000/116 permit
|
||||
2607:f8b0:4000::/36 permit
|
||||
2620:109:c003:104::/64 permit
|
||||
2620:109:c003:104::215 permit
|
||||
@@ -2059,4 +2217,5 @@
|
||||
2620:119:50c0:207::/64 permit
|
||||
2620:119:50c0:207::215 permit
|
||||
2800:3f0:4000::/36 permit
|
||||
194.25.134.0/24 permit # t-online.de
|
||||
49.12.4.251 permit # checks.mailcow.email
|
||||
2a01:4f8:c17:7906::10 permit # checks.mailcow.email
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
cat <<EOF > /redis.conf
|
||||
requirepass $REDISPASS
|
||||
user quota_notify on nopass ~QW_* -@all +get +hget +ping
|
||||
EOF
|
||||
|
||||
if [ -n "$REDISMASTERPASS" ]; then
|
||||
echo "masterauth $REDISMASTERPASS" >> /redis.conf
|
||||
fi
|
||||
|
||||
exec redis-server /redis.conf
|
||||
@@ -22,10 +22,10 @@ catch (PDOException $e) {
|
||||
exit;
|
||||
}
|
||||
|
||||
// Init Redis
|
||||
$redis = new Redis();
|
||||
$redis->connect('redis-mailcow', 6379);
|
||||
$redis->auth(getenv("REDISPASS"));
|
||||
// Init Valkey
|
||||
$valkey = new Redis();
|
||||
$valkey->connect('valkey-mailcow', 6379);
|
||||
$valkey->auth(getenv("VALKEYPASS"));
|
||||
|
||||
function parse_email($email) {
|
||||
if(!filter_var($email, FILTER_VALIDATE_EMAIL)) return false;
|
||||
@@ -60,7 +60,7 @@ $rcpt_final_mailboxes = array();
|
||||
|
||||
// Skip if not a mailcow handled domain
|
||||
try {
|
||||
if (!$redis->hGet('DOMAIN_MAP', $parsed_rcpt['domain'])) {
|
||||
if (!$valkey->hGet('DOMAIN_MAP', $parsed_rcpt['domain'])) {
|
||||
exit;
|
||||
}
|
||||
}
|
||||
@@ -122,7 +122,7 @@ try {
|
||||
}
|
||||
else {
|
||||
$parsed_goto = parse_email($goto);
|
||||
if (!$redis->hGet('DOMAIN_MAP', $parsed_goto['domain'])) {
|
||||
if (!$valkey->hGet('DOMAIN_MAP', $parsed_goto['domain'])) {
|
||||
error_log("ALIAS EXPANDER:" . $goto . " is not a mailcow handled mailbox or alias address" . PHP_EOL);
|
||||
}
|
||||
else {
|
||||
@@ -133,7 +133,7 @@ try {
|
||||
error_log("ALIAS EXPANDER: http pipe: goto address " . $goto . " is an alias branch for " . $goto_branch . PHP_EOL);
|
||||
$goto_branch_array = explode(',', $goto_branch);
|
||||
} else {
|
||||
$stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain AND `active` AND '1'");
|
||||
$stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain AND `active` = '1'");
|
||||
$stmt->execute(array(':domain' => $parsed_goto['domain']));
|
||||
$goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['target_domain'];
|
||||
if ($goto_branch) {
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
header('Content-Type: text/plain');
|
||||
ini_set('error_reporting', 0);
|
||||
|
||||
$redis = new Redis();
|
||||
$redis->connect('redis-mailcow', 6379);
|
||||
$redis->auth(getenv("REDISPASS"));
|
||||
$valkey = new Redis();
|
||||
$valkey->connect('valkey-mailcow', 6379);
|
||||
$valkey->auth(getenv("VALKEYPASS"));
|
||||
|
||||
function in_net($addr, $net) {
|
||||
$net = explode('/', $net);
|
||||
@@ -31,7 +31,7 @@ function in_net($addr, $net) {
|
||||
|
||||
if (isset($_GET['host'])) {
|
||||
try {
|
||||
foreach ($redis->hGetAll('WHITELISTED_FWD_HOST') as $host => $source) {
|
||||
foreach ($valkey->hGetAll('WHITELISTED_FWD_HOST') as $host => $source) {
|
||||
if (in_net($_GET['host'], $host)) {
|
||||
echo '200 PERMIT';
|
||||
exit;
|
||||
@@ -46,7 +46,7 @@ if (isset($_GET['host'])) {
|
||||
} else {
|
||||
try {
|
||||
echo '240.240.240.240' . PHP_EOL;
|
||||
foreach ($redis->hGetAll('WHITELISTED_FWD_HOST') as $host => $source) {
|
||||
foreach ($valkey->hGetAll('WHITELISTED_FWD_HOST') as $host => $source) {
|
||||
echo $host . PHP_EOL;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ function normalize_email($email) {
|
||||
$email = explode('@', $email);
|
||||
$email[0] = str_replace('.', '', $email[0]);
|
||||
$email = implode('@', $email);
|
||||
}
|
||||
}
|
||||
$gm_alt = "@googlemail.com";
|
||||
if (substr_compare($email, $gm_alt, -strlen($gm_alt)) == 0) {
|
||||
$email = explode('@', $email);
|
||||
@@ -114,7 +114,7 @@ function ucl_rcpts($object, $type) {
|
||||
$rcpt[] = str_replace('/', '\/', $row['address']);
|
||||
}
|
||||
// Aliases by alias domains
|
||||
$stmt = $pdo->prepare("SELECT CONCAT(`local_part`, '@', `alias_domain`.`alias_domain`) AS `alias` FROM `mailbox`
|
||||
$stmt = $pdo->prepare("SELECT CONCAT(`local_part`, '@', `alias_domain`.`alias_domain`) AS `alias` FROM `mailbox`
|
||||
LEFT OUTER JOIN `alias_domain` ON `mailbox`.`domain` = `alias_domain`.`target_domain`
|
||||
WHERE `mailbox`.`username` = :object");
|
||||
$stmt->execute(array(
|
||||
@@ -184,7 +184,7 @@ while ($row = array_shift($rows)) {
|
||||
rcpt = <?=json_encode($rcpt, JSON_UNESCAPED_SLASHES);?>;
|
||||
<?php
|
||||
}
|
||||
$stmt = $pdo->prepare("SELECT `option`, `value` FROM `filterconf`
|
||||
$stmt = $pdo->prepare("SELECT `option`, `value` FROM `filterconf`
|
||||
WHERE (`option` = 'highspamlevel' OR `option` = 'lowspamlevel')
|
||||
AND `object`= :object");
|
||||
$stmt->execute(array(':object' => $row['object']));
|
||||
@@ -468,4 +468,36 @@ while ($row = array_shift($rows)) {
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
|
||||
<?php
|
||||
// Start internal aliases
|
||||
|
||||
$stmt = $pdo->query("SELECT `id`, `address`, `domain` FROM `alias` WHERE `active` = '1' AND `internal` = '1'");
|
||||
$aliases = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
while ($alias = array_shift($aliases)) {
|
||||
// build allowed_domains regex and add target domain and alias domains
|
||||
$stmt = $pdo->prepare("SELECT `alias_domain` FROM `alias_domain` WHERE `active` = '1' AND `target_domain` = :target_domain");
|
||||
$stmt->execute(array(':target_domain' => $alias['domain']));
|
||||
$allowed_domains = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
$allowed_domains = array_map(function($item) {
|
||||
return str_replace('.', '\.', $item['alias_domain']);
|
||||
}, $allowed_domains);
|
||||
$allowed_domains[] = str_replace('.', '\.', $alias['domain']);
|
||||
$allowed_domains = implode('|', $allowed_domains);
|
||||
?>
|
||||
internal_alias_<?=$alias['id'];?> {
|
||||
priority = 10;
|
||||
rcpt = "<?=$alias['address'];?>";
|
||||
from = "/^((?!.*@(<?=$allowed_domains;?>)).)*$/";
|
||||
apply "default" {
|
||||
MAILCOW_INTERNAL_ALIAS = 9999.0;
|
||||
}
|
||||
symbols [
|
||||
"MAILCOW_INTERNAL_ALIAS"
|
||||
]
|
||||
}
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ rspamd_config:register_symbol({
|
||||
local rcpt_split = rspamd_str_split(rcpt['addr'], '@')
|
||||
if #rcpt_split == 2 then
|
||||
if rcpt_split[1] == 'postmaster' then
|
||||
task:set_pre_result('accept', 'whitelisting postmaster smtp rcpt')
|
||||
task:set_pre_result('accept', 'whitelisting postmaster smtp rcpt', 'postmaster')
|
||||
return
|
||||
end
|
||||
end
|
||||
@@ -167,7 +167,7 @@ rspamd_config:register_symbol({
|
||||
for k,v in pairs(data) do
|
||||
if (v and v ~= userdata and v == '1') then
|
||||
rspamd_logger.infox(rspamd_config, "found ip in keep_spam map, setting pre-result")
|
||||
task:set_pre_result('accept', 'ip matched with forward hosts')
|
||||
task:set_pre_result('accept', 'ip matched with forward hosts', 'keep_spam')
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -454,12 +454,18 @@ rspamd_config:register_symbol({
|
||||
local redis_params = rspamd_parse_redis_server('dyn_rl')
|
||||
local rspamd_logger = require "rspamd_logger"
|
||||
local envfrom = task:get_from(1)
|
||||
local envrcpt = task:get_recipients(1) or {}
|
||||
local uname = task:get_user()
|
||||
if not envfrom or not uname then
|
||||
return false
|
||||
end
|
||||
|
||||
local uname = uname:lower()
|
||||
|
||||
if #envrcpt == 1 and envrcpt[1].addr:lower() == uname then
|
||||
return false
|
||||
end
|
||||
|
||||
local env_from_domain = envfrom[1].domain:lower() -- get smtp from domain in lower case
|
||||
|
||||
local function redis_cb_user(err, data)
|
||||
@@ -544,13 +550,13 @@ rspamd_config:register_symbol({
|
||||
-- determine newline type
|
||||
local function newline(task)
|
||||
local t = task:get_newlines_type()
|
||||
|
||||
|
||||
if t == 'cr' then
|
||||
return '\r'
|
||||
elseif t == 'lf' then
|
||||
return '\n'
|
||||
end
|
||||
|
||||
|
||||
return '\r\n'
|
||||
end
|
||||
-- retrieve footer
|
||||
@@ -558,7 +564,7 @@ rspamd_config:register_symbol({
|
||||
if err or type(data) ~= 'string' then
|
||||
rspamd_logger.infox(rspamd_config, "domain wide footer request for user %s returned invalid or empty data (\"%s\") or error (\"%s\")", uname, data, err)
|
||||
else
|
||||
|
||||
|
||||
-- parse json string
|
||||
local footer = cjson.decode(data)
|
||||
if not footer then
|
||||
@@ -607,26 +613,30 @@ rspamd_config:register_symbol({
|
||||
if footer.plain and footer.plain ~= "" then
|
||||
footer.plain = lua_util.jinja_template(footer.plain, replacements, true)
|
||||
end
|
||||
|
||||
|
||||
-- add footer
|
||||
local out = {}
|
||||
local rewrite = lua_mime.add_text_footer(task, footer.html, footer.plain) or {}
|
||||
|
||||
|
||||
local seen_cte
|
||||
local newline_s = newline(task)
|
||||
|
||||
|
||||
local function rewrite_ct_cb(name, hdr)
|
||||
if rewrite.need_rewrite_ct then
|
||||
if name:lower() == 'content-type' then
|
||||
local nct = string.format('%s: %s/%s; charset=utf-8',
|
||||
'Content-Type', rewrite.new_ct.type, rewrite.new_ct.subtype)
|
||||
-- include boundary if present
|
||||
local boundary_part = rewrite.new_ct.boundary and
|
||||
string.format('; boundary="%s"', rewrite.new_ct.boundary) or ''
|
||||
local nct = string.format('%s: %s/%s; charset=utf-8%s',
|
||||
'Content-Type', rewrite.new_ct.type, rewrite.new_ct.subtype, boundary_part)
|
||||
out[#out + 1] = nct
|
||||
-- update Content-Type header
|
||||
-- update Content-Type header (include boundary if present)
|
||||
task:set_milter_reply({
|
||||
remove_headers = {['Content-Type'] = 0},
|
||||
})
|
||||
task:set_milter_reply({
|
||||
add_headers = {['Content-Type'] = string.format('%s/%s; charset=utf-8', rewrite.new_ct.type, rewrite.new_ct.subtype)}
|
||||
add_headers = {['Content-Type'] = string.format('%s/%s; charset=utf-8%s',
|
||||
rewrite.new_ct.type, rewrite.new_ct.subtype, boundary_part)}
|
||||
})
|
||||
return
|
||||
elseif name:lower() == 'content-transfer-encoding' then
|
||||
@@ -645,16 +655,16 @@ rspamd_config:register_symbol({
|
||||
end
|
||||
out[#out + 1] = hdr.raw:gsub('\r?\n?$', '')
|
||||
end
|
||||
|
||||
|
||||
task:headers_foreach(rewrite_ct_cb, {full = true})
|
||||
|
||||
|
||||
if not seen_cte and rewrite.need_rewrite_ct then
|
||||
out[#out + 1] = string.format('%s: %s', 'Content-Transfer-Encoding', 'quoted-printable')
|
||||
end
|
||||
|
||||
|
||||
-- End of headers
|
||||
out[#out + 1] = newline_s
|
||||
|
||||
|
||||
if rewrite.out then
|
||||
for _,o in ipairs(rewrite.out) do
|
||||
out[#out + 1] = o
|
||||
|
||||
@@ -21,10 +21,10 @@ catch (PDOException $e) {
|
||||
http_response_code(501);
|
||||
exit;
|
||||
}
|
||||
// Init Redis
|
||||
$redis = new Redis();
|
||||
$redis->connect('redis-mailcow', 6379);
|
||||
$redis->auth(getenv("REDISPASS"));
|
||||
// Init Valkey
|
||||
$valkey = new Redis();
|
||||
$valkey->connect('valkey-mailcow', 6379);
|
||||
$valkey->auth(getenv("VALKEYPASS"));
|
||||
|
||||
// Functions
|
||||
function parse_email($email) {
|
||||
@@ -74,16 +74,16 @@ if ($fuzzy == 'unknown') {
|
||||
}
|
||||
|
||||
try {
|
||||
$max_size = (int)$redis->Get('Q_MAX_SIZE');
|
||||
$max_size = (int)$valkey->Get('Q_MAX_SIZE');
|
||||
if (($max_size * 1048576) < $raw_size) {
|
||||
error_log(sprintf("QUARANTINE: Message too large: %d b exceeds %d b", $raw_size, ($max_size * 1048576)) . PHP_EOL);
|
||||
http_response_code(505);
|
||||
exit;
|
||||
}
|
||||
if ($exclude_domains = $redis->Get('Q_EXCLUDE_DOMAINS')) {
|
||||
if ($exclude_domains = $valkey->Get('Q_EXCLUDE_DOMAINS')) {
|
||||
$exclude_domains = json_decode($exclude_domains, true);
|
||||
}
|
||||
$retention_size = (int)$redis->Get('Q_RETENTION_SIZE');
|
||||
$retention_size = (int)$valkey->Get('Q_RETENTION_SIZE');
|
||||
}
|
||||
catch (RedisException $e) {
|
||||
error_log("QUARANTINE: " . $e . PHP_EOL);
|
||||
@@ -103,7 +103,7 @@ foreach (json_decode($rcpts, true) as $rcpt) {
|
||||
|
||||
// Skip if not a mailcow handled domain
|
||||
try {
|
||||
if (!$redis->hGet('DOMAIN_MAP', $parsed_rcpt['domain'])) {
|
||||
if (!$valkey->hGet('DOMAIN_MAP', $parsed_rcpt['domain'])) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -171,7 +171,7 @@ foreach (json_decode($rcpts, true) as $rcpt) {
|
||||
}
|
||||
else {
|
||||
$parsed_goto = parse_email($goto);
|
||||
if (!$redis->hGet('DOMAIN_MAP', $parsed_goto['domain'])) {
|
||||
if (!$valkey->hGet('DOMAIN_MAP', $parsed_goto['domain'])) {
|
||||
error_log("RCPT RESOVLER:" . $goto . " is not a mailcow handled mailbox or alias address" . PHP_EOL);
|
||||
}
|
||||
else {
|
||||
@@ -182,7 +182,7 @@ foreach (json_decode($rcpts, true) as $rcpt) {
|
||||
error_log("RCPT RESOVLER: http pipe: goto address " . $goto . " is an alias branch for " . $goto_branch . PHP_EOL);
|
||||
$goto_branch_array = explode(',', $goto_branch);
|
||||
} else {
|
||||
$stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain AND `active` AND '1'");
|
||||
$stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain AND `active` = '1'");
|
||||
$stmt->execute(array(':domain' => $parsed_goto['domain']));
|
||||
$goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['target_domain'];
|
||||
if ($goto_branch) {
|
||||
@@ -236,6 +236,9 @@ foreach ($rcpt_final_mailboxes as $rcpt_final) {
|
||||
':action' => $action,
|
||||
':fuzzy_hashes' => $fuzzy
|
||||
));
|
||||
$lastId = $pdo->lastInsertId();
|
||||
$stmt_update = $pdo->prepare("UPDATE `quarantine` SET `qhash` = SHA2(CONCAT(`id`, `qid`), 256) WHERE `id` = :id");
|
||||
$stmt_update->execute(array(':id' => $lastId));
|
||||
$stmt = $pdo->prepare('DELETE FROM `quarantine` WHERE `rcpt` = :rcpt AND `id` NOT IN (
|
||||
SELECT `id`
|
||||
FROM (
|
||||
|
||||
@@ -5,16 +5,16 @@ header('Content-Type: text/plain');
|
||||
require_once "vars.inc.php";
|
||||
// Do not show errors, we log to using error_log
|
||||
ini_set('error_reporting', 0);
|
||||
// Init Redis
|
||||
$redis = new Redis();
|
||||
// Init Valkey
|
||||
$valkey = new Redis();
|
||||
try {
|
||||
if (!empty(getenv('REDIS_SLAVEOF_IP'))) {
|
||||
$redis->connect(getenv('REDIS_SLAVEOF_IP'), getenv('REDIS_SLAVEOF_PORT'));
|
||||
if (!empty(getenv('VALKEY_SLAVEOF_IP'))) {
|
||||
$valkey->connect(getenv('VALKEY_SLAVEOF_IP'), getenv('VALKEY_SLAVEOF_PORT'));
|
||||
}
|
||||
else {
|
||||
$redis->connect('redis-mailcow', 6379);
|
||||
$valkey->connect('valkey-mailcow', 6379);
|
||||
}
|
||||
$redis->auth(getenv("REDISPASS"));
|
||||
$valkey->auth(getenv("VALKEYPASS"));
|
||||
}
|
||||
catch (Exception $e) {
|
||||
exit;
|
||||
@@ -44,6 +44,6 @@ $data['message_id'] = $raw_data_decoded['message_id'];
|
||||
$data['header_subject'] = implode(' ', $raw_data_decoded['header_subject']);
|
||||
$data['header_from'] = implode(', ', $raw_data_decoded['header_from']);
|
||||
|
||||
$redis->lpush('RL_LOG', json_encode($data));
|
||||
$valkey->lpush('RL_LOG', json_encode($data));
|
||||
exit;
|
||||
|
||||
|
||||
@@ -21,10 +21,10 @@ catch (PDOException $e) {
|
||||
http_response_code(501);
|
||||
exit;
|
||||
}
|
||||
// Init Redis
|
||||
$redis = new Redis();
|
||||
$redis->connect('redis-mailcow', 6379);
|
||||
$redis->auth(getenv("REDISPASS"));
|
||||
// Init Valkey
|
||||
$valkey = new Redis();
|
||||
$valkey->connect('valkey-mailcow', 6379);
|
||||
$valkey->auth(getenv("VALKEYPASS"));
|
||||
|
||||
// Functions
|
||||
function parse_email($email) {
|
||||
@@ -94,7 +94,7 @@ foreach (json_decode($rcpts, true) as $rcpt) {
|
||||
|
||||
// Skip if not a mailcow handled domain
|
||||
try {
|
||||
if (!$redis->hGet('DOMAIN_MAP', $parsed_rcpt['domain'])) {
|
||||
if (!$valkey->hGet('DOMAIN_MAP', $parsed_rcpt['domain'])) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -156,7 +156,7 @@ foreach (json_decode($rcpts, true) as $rcpt) {
|
||||
}
|
||||
else {
|
||||
$parsed_goto = parse_email($goto);
|
||||
if (!$redis->hGet('DOMAIN_MAP', $parsed_goto['domain'])) {
|
||||
if (!$valkey->hGet('DOMAIN_MAP', $parsed_goto['domain'])) {
|
||||
error_log("RCPT RESOVLER:" . $goto . " is not a mailcow handled mailbox or alias address" . PHP_EOL);
|
||||
}
|
||||
else {
|
||||
@@ -167,7 +167,7 @@ foreach (json_decode($rcpts, true) as $rcpt) {
|
||||
error_log("RCPT RESOVLER: http pipe: goto address " . $goto . " is an alias branch for " . $goto_branch . PHP_EOL);
|
||||
$goto_branch_array = explode(',', $goto_branch);
|
||||
} else {
|
||||
$stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain AND `active` AND '1'");
|
||||
$stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain AND `active` = '1'");
|
||||
$stmt->execute(array(':domain' => $parsed_goto['domain']));
|
||||
$goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['target_domain'];
|
||||
if ($goto_branch) {
|
||||
|
||||
12
data/conf/valkey/valkey-conf.sh
Executable file
12
data/conf/valkey/valkey-conf.sh
Executable file
@@ -0,0 +1,12 @@
|
||||
#!/bin/sh
|
||||
|
||||
cat <<EOF > /valkey.conf
|
||||
requirepass $VALKEYPASS
|
||||
user quota_notify on nopass ~QW_* -@all +get +hget +ping
|
||||
EOF
|
||||
|
||||
if [ -n "$VALKEYMASTERPASS" ]; then
|
||||
echo "masterauth $VALKEYMASTERPASS" >> /valkey.conf
|
||||
fi
|
||||
|
||||
exec "$@"
|
||||
@@ -1,13 +1,13 @@
|
||||
<?php
|
||||
$redis = new Redis();
|
||||
$valkey = new Redis();
|
||||
try {
|
||||
if (!empty(getenv('REDIS_SLAVEOF_IP'))) {
|
||||
$redis->connect(getenv('REDIS_SLAVEOF_IP'), getenv('REDIS_SLAVEOF_PORT'));
|
||||
if (!empty(getenv('VALKEY_SLAVEOF_IP'))) {
|
||||
$valkey->connect(getenv('VALKEY_SLAVEOF_IP'), getenv('VALKEY_SLAVEOF_PORT'));
|
||||
}
|
||||
else {
|
||||
$redis->connect('redis-mailcow', 6379);
|
||||
$valkey->connect('valkey-mailcow', 6379);
|
||||
}
|
||||
$redis->auth(getenv("REDISPASS"));
|
||||
$valkey->auth(getenv("VALKEYPASS"));
|
||||
}
|
||||
catch (Exception $e) {
|
||||
exit;
|
||||
@@ -15,4 +15,4 @@ catch (Exception $e) {
|
||||
header('Content-Type: application/json');
|
||||
echo '{"error":"Unauthorized"}';
|
||||
error_log("Rspamd UI: Invalid password by " . $_SERVER['REMOTE_ADDR']);
|
||||
$redis->publish("F2B_CHANNEL", "Rspamd UI: Invalid password by " . $_SERVER['REMOTE_ADDR']);
|
||||
$valkey->publish("F2B_CHANNEL", "Rspamd UI: Invalid password by " . $_SERVER['REMOTE_ADDR']);
|
||||
|
||||
@@ -21,7 +21,7 @@ $clamd_status = (preg_match("/^([yY][eE][sS]|[yY])+$/", $_ENV["SKIP_CLAMD"])) ?
|
||||
$olefy_status = (preg_match("/^([yY][eE][sS]|[yY])+$/", $_ENV["SKIP_OLEFY"])) ? false : true;
|
||||
|
||||
|
||||
if (!isset($_SESSION['gal']) && $license_cache = $redis->Get('LICENSE_STATUS_CACHE')) {
|
||||
if (!isset($_SESSION['gal']) && $license_cache = $valkey->Get('LICENSE_STATUS_CACHE')) {
|
||||
$_SESSION['gal'] = json_decode($license_cache, true);
|
||||
}
|
||||
|
||||
|
||||
@@ -5412,9 +5412,9 @@ paths:
|
||||
started_at: "2019-12-22T21:00:07.186717617Z"
|
||||
state: running
|
||||
type: info
|
||||
redis-mailcow:
|
||||
container: redis-mailcow
|
||||
image: "redis:5-alpine"
|
||||
valkey-mailcow:
|
||||
container: valkey-mailcow
|
||||
image: "valkey:7.2.8-alpine"
|
||||
started_at: "2019-12-22T20:59:56.827166834Z"
|
||||
state: running
|
||||
type: info
|
||||
|
||||
@@ -10,16 +10,16 @@ require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/sessions.inc.php';
|
||||
$default_autodiscover_config = $autodiscover_config;
|
||||
$autodiscover_config = array_merge($default_autodiscover_config, $autodiscover_config);
|
||||
|
||||
// Redis
|
||||
$redis = new Redis();
|
||||
// Valkey
|
||||
$valkey = new Redis();
|
||||
try {
|
||||
if (!empty(getenv('REDIS_SLAVEOF_IP'))) {
|
||||
$redis->connect(getenv('REDIS_SLAVEOF_IP'), getenv('REDIS_SLAVEOF_PORT'));
|
||||
if (!empty(getenv('VALKEY_SLAVEOF_IP'))) {
|
||||
$valkey->connect(getenv('VALKEY_SLAVEOF_IP'), getenv('VALKEY_SLAVEOF_PORT'));
|
||||
}
|
||||
else {
|
||||
$redis->connect('redis-mailcow', 6379);
|
||||
$valkey->connect('valkey-mailcow', 6379);
|
||||
}
|
||||
$redis->auth(getenv("REDISPASS"));
|
||||
$valkey->auth(getenv("VALKEYPASS"));
|
||||
}
|
||||
catch (Exception $e) {
|
||||
exit;
|
||||
@@ -71,7 +71,7 @@ if (empty($_SERVER['PHP_AUTH_USER']) || empty($_SERVER['PHP_AUTH_PW'])) {
|
||||
"service" => "Error: must be authenticated"
|
||||
)
|
||||
);
|
||||
$redis->lPush('AUTODISCOVER_LOG', $json);
|
||||
$valkey->lPush('AUTODISCOVER_LOG', $json);
|
||||
header('WWW-Authenticate: Basic realm="' . $_SERVER['HTTP_HOST'] . '"');
|
||||
header('HTTP/1.0 401 Unauthorized');
|
||||
exit(0);
|
||||
@@ -96,13 +96,13 @@ if ($login_role === "user") {
|
||||
"service" => "Error: invalid or missing request data"
|
||||
)
|
||||
);
|
||||
$redis->lPush('AUTODISCOVER_LOG', $json);
|
||||
$redis->lTrim('AUTODISCOVER_LOG', 0, 100);
|
||||
$valkey->lPush('AUTODISCOVER_LOG', $json);
|
||||
$valkey->lTrim('AUTODISCOVER_LOG', 0, 100);
|
||||
}
|
||||
catch (RedisException $e) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'msg' => 'Redis: '.$e
|
||||
'msg' => 'Valkey: '.$e
|
||||
);
|
||||
return false;
|
||||
}
|
||||
@@ -151,13 +151,13 @@ if ($login_role === "user") {
|
||||
"service" => $autodiscover_config['autodiscoverType']
|
||||
)
|
||||
);
|
||||
$redis->lPush('AUTODISCOVER_LOG', $json);
|
||||
$redis->lTrim('AUTODISCOVER_LOG', 0, 100);
|
||||
$valkey->lPush('AUTODISCOVER_LOG', $json);
|
||||
$valkey->lTrim('AUTODISCOVER_LOG', 0, 100);
|
||||
}
|
||||
catch (RedisException $e) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'msg' => 'Redis: '.$e
|
||||
'msg' => 'Valkey: '.$e
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -48,6 +48,12 @@ if (isset($_SESSION['mailcow_cc_role'])) {
|
||||
$rl = ratelimit('get', 'domain', $domain);
|
||||
$rlyhosts = relayhost('get');
|
||||
$domain_footer = mailbox('get', 'domain_wide_footer', $domain);
|
||||
$mta_sts = mailbox('get', 'mta_sts', $domain);
|
||||
if (count($mta_sts) == 0) {
|
||||
$mta_sts = false;
|
||||
} elseif (isset($mta_sts['mx'])) {
|
||||
$mta_sts['mx'] = implode(',', $mta_sts['mx']);
|
||||
}
|
||||
$template = 'edit/domain.twig';
|
||||
$template_data = [
|
||||
'acl' => $_SESSION['acl'],
|
||||
@@ -58,6 +64,7 @@ if (isset($_SESSION['mailcow_cc_role'])) {
|
||||
'dkim' => dkim('details', $domain),
|
||||
'domain_details' => $result,
|
||||
'domain_footer' => $domain_footer,
|
||||
'mta_sts' => $mta_sts,
|
||||
'mailboxes' => mailbox('get', 'mailboxes', $_GET["domain"]),
|
||||
'aliases' => mailbox('get', 'aliases', $_GET["domain"], 'address'),
|
||||
'alias_domains' => mailbox('get', 'alias_domains', $_GET["domain"])
|
||||
@@ -125,6 +132,7 @@ if (isset($_SESSION['mailcow_cc_role'])) {
|
||||
'mailbox' => $mailbox,
|
||||
'rl' => $rl,
|
||||
'pushover_data' => $pushover_data,
|
||||
'get_tagging_options' => mailbox('get', 'delimiter_action', $mailbox),
|
||||
'quarantine_notification' => $quarantine_notification,
|
||||
'quarantine_category' => $quarantine_category,
|
||||
'get_tls_policy' => $get_tls_policy,
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 17 KiB |
@@ -71,6 +71,7 @@ if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "adm
|
||||
// Init records array
|
||||
$spf_link = '<a href="http://www.open-spf.org/SPF_Record_Syntax/" target="_blank">SPF Record Syntax</a><br />';
|
||||
$dmarc_link = '<a href="https://www.kitterman.com/dmarc/assistant.html" target="_blank">DMARC Assistant</a>';
|
||||
$mtasts_report_link = '<a href="https://mxtoolbox.com/dmarc/smtp-tls/how-to-setup-smtp-tls-reports" target="_blank">TLS Report Record Syntax</a>';
|
||||
|
||||
$records = array();
|
||||
|
||||
@@ -128,6 +129,27 @@ if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "adm
|
||||
);
|
||||
}
|
||||
|
||||
$mta_sts = mailbox('get', 'mta_sts', $domain);
|
||||
if (count($mta_sts) > 0 && $mta_sts['active'] == 1) {
|
||||
if (!in_array($domain, $alias_domains)) {
|
||||
$records[] = array(
|
||||
'mta-sts.' . $domain,
|
||||
'CNAME',
|
||||
$mailcow_hostname
|
||||
);
|
||||
}
|
||||
$records[] = array(
|
||||
'_mta-sts.' . $domain,
|
||||
'TXT',
|
||||
"v={$mta_sts['version']};id={$mta_sts['id']};",
|
||||
);
|
||||
$records[] = array(
|
||||
'_smtp._tls.' . $domain,
|
||||
'TXT',
|
||||
$mtasts_report_link,
|
||||
);
|
||||
}
|
||||
|
||||
$records[] = array(
|
||||
$domain,
|
||||
'TXT',
|
||||
@@ -341,15 +363,25 @@ if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "adm
|
||||
}
|
||||
|
||||
foreach ($currents as &$current) {
|
||||
if ($current['type'] == "TXT" &&
|
||||
stripos(strtolower($current['txt']), 'v=sts') === 0) {
|
||||
if (strtolower($current[$data_field[$current['type']]]) == strtolower($record[2])) {
|
||||
$state = state_good;
|
||||
}
|
||||
else {
|
||||
$state = state_nomatch;
|
||||
}
|
||||
$state .= '<br />' . $current[$data_field[$current['type']]];
|
||||
}
|
||||
if ($current['type'] == 'TXT' &&
|
||||
stripos($current['txt'], 'v=dmarc') === 0 &&
|
||||
$record[2] == $dmarc_link) {
|
||||
stripos($current['txt'], 'v=dmarc') === 0 &&
|
||||
$record[2] == $dmarc_link) {
|
||||
$current['txt'] = str_replace(' ', '', $current['txt']);
|
||||
$state = $current[$data_field[$current['type']]] . state_optional;
|
||||
}
|
||||
elseif ($current['type'] == 'TXT' &&
|
||||
stripos($current['txt'], 'v=spf') === 0 &&
|
||||
$record[2] == $spf_link) {
|
||||
stripos($current['txt'], 'v=spf') === 0 &&
|
||||
$record[2] == $spf_link) {
|
||||
$state = state_nomatch;
|
||||
$rslt = get_spf_allowed_hosts($record[0], true);
|
||||
if (in_array($ip, $rslt) && in_array(expand_ipv6($ip6), $rslt)) {
|
||||
@@ -358,8 +390,8 @@ if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "adm
|
||||
$state .= '<br />' . $current[$data_field[$current['type']]] . state_optional;
|
||||
}
|
||||
elseif ($current['type'] == 'TXT' &&
|
||||
stripos($current['txt'], 'v=dkim') === 0 &&
|
||||
stripos($record[2], 'v=dkim') === 0) {
|
||||
stripos($current['txt'], 'v=dkim') === 0 &&
|
||||
stripos($record[2], 'v=dkim') === 0) {
|
||||
preg_match('/v=DKIM1;.*k=rsa;.*p=([^;]*).*/i', $current[$data_field[$current['type']]], $dkim_matches_current);
|
||||
preg_match('/v=DKIM1;.*k=rsa;.*p=([^;]*).*/i', $record[2], $dkim_matches_good);
|
||||
if ($dkim_matches_current[1] == $dkim_matches_good[1]) {
|
||||
@@ -367,7 +399,7 @@ if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "adm
|
||||
}
|
||||
}
|
||||
elseif ($current['type'] != 'TXT' &&
|
||||
isset($data_field[$current['type']]) && $state != state_good) {
|
||||
isset($data_field[$current['type']]) && $state != state_good) {
|
||||
$state = state_nomatch;
|
||||
if ($current[$data_field[$current['type']]] == $record[2]) {
|
||||
$state = state_good;
|
||||
|
||||
@@ -26,23 +26,25 @@ if (is_array($alertbox_log_parser)) {
|
||||
|
||||
// map tfa details for twig
|
||||
$pending_tfa_authmechs = [];
|
||||
foreach($_SESSION['pending_tfa_methods'] as $authdata){
|
||||
$pending_tfa_authmechs[$authdata['authmech']] = false;
|
||||
}
|
||||
if (isset($pending_tfa_authmechs['webauthn'])) {
|
||||
$pending_tfa_authmechs['webauthn'] = true;
|
||||
}
|
||||
if (!isset($pending_tfa_authmechs['webauthn'])
|
||||
&& isset($pending_tfa_authmechs['yubi_otp'])) {
|
||||
$pending_tfa_authmechs['yubi_otp'] = true;
|
||||
}
|
||||
if (!isset($pending_tfa_authmechs['webauthn'])
|
||||
&& !isset($pending_tfa_authmechs['yubi_otp'])
|
||||
&& isset($pending_tfa_authmechs['totp'])) {
|
||||
$pending_tfa_authmechs['totp'] = true;
|
||||
}
|
||||
if (isset($pending_tfa_authmechs['u2f'])) {
|
||||
$pending_tfa_authmechs['u2f'] = true;
|
||||
if (array_key_exists('pending_tfa_methods', $_SESSION)) {
|
||||
foreach($_SESSION['pending_tfa_methods'] as $authdata){
|
||||
$pending_tfa_authmechs[$authdata['authmech']] = false;
|
||||
}
|
||||
if (isset($pending_tfa_authmechs['webauthn'])) {
|
||||
$pending_tfa_authmechs['webauthn'] = true;
|
||||
}
|
||||
if (!isset($pending_tfa_authmechs['webauthn'])
|
||||
&& isset($pending_tfa_authmechs['yubi_otp'])) {
|
||||
$pending_tfa_authmechs['yubi_otp'] = true;
|
||||
}
|
||||
if (!isset($pending_tfa_authmechs['webauthn'])
|
||||
&& !isset($pending_tfa_authmechs['yubi_otp'])
|
||||
&& isset($pending_tfa_authmechs['totp'])) {
|
||||
$pending_tfa_authmechs['totp'] = true;
|
||||
}
|
||||
if (isset($pending_tfa_authmechs['u2f'])) {
|
||||
$pending_tfa_authmechs['u2f'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
// globals
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
function app_passwd($_action, $_data = null) {
|
||||
global $pdo;
|
||||
global $lang;
|
||||
global $pdo;
|
||||
global $lang;
|
||||
$_data_log = $_data;
|
||||
!isset($_data_log['app_passwd']) ?: $_data_log['app_passwd'] = '*';
|
||||
!isset($_data_log['app_passwd2']) ?: $_data_log['app_passwd2'] = '*';
|
||||
@@ -43,20 +43,7 @@ function app_passwd($_action, $_data = null) {
|
||||
);
|
||||
return false;
|
||||
}
|
||||
if (!preg_match('/' . $GLOBALS['PASSWD_REGEP'] . '/', $password)) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_data_log),
|
||||
'msg' => 'password_complexity'
|
||||
);
|
||||
return false;
|
||||
}
|
||||
if ($password != $password2) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_data_log),
|
||||
'msg' => 'password_mismatch'
|
||||
);
|
||||
if (password_check($password, $password2) !== true) {
|
||||
return false;
|
||||
}
|
||||
$password_hashed = hash_password($password);
|
||||
@@ -88,15 +75,15 @@ function app_passwd($_action, $_data = null) {
|
||||
'log' => array(__FUNCTION__, $_action, $_data_log),
|
||||
'msg' => 'app_passwd_added'
|
||||
);
|
||||
break;
|
||||
break;
|
||||
case 'edit':
|
||||
$ids = (array)$_data['id'];
|
||||
foreach ($ids as $id) {
|
||||
$is_now = app_passwd('details', $id);
|
||||
if (!empty($is_now)) {
|
||||
$app_name = (!empty($_data['app_name'])) ? $_data['app_name'] : $is_now['name'];
|
||||
$password = (!empty($_data['password'])) ? $_data['password'] : null;
|
||||
$password2 = (!empty($_data['password2'])) ? $_data['password2'] : null;
|
||||
$password = (!empty($_data['app_passwd'])) ? $_data['app_passwd'] : null;
|
||||
$password2 = (!empty($_data['app_passwd2'])) ? $_data['app_passwd2'] : null;
|
||||
if (isset($_data['protocols'])) {
|
||||
$protocols = (array)$_data['protocols'];
|
||||
$imap_access = (in_array('imap_access', $protocols)) ? 1 : 0;
|
||||
@@ -126,20 +113,7 @@ function app_passwd($_action, $_data = null) {
|
||||
}
|
||||
$app_name = htmlspecialchars(trim($app_name));
|
||||
if (!empty($password) && !empty($password2)) {
|
||||
if (!preg_match('/' . $GLOBALS['PASSWD_REGEP'] . '/', $password)) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||
'msg' => 'password_complexity'
|
||||
);
|
||||
continue;
|
||||
}
|
||||
if ($password != $password2) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||
'msg' => 'password_mismatch'
|
||||
);
|
||||
if (password_check($password, $password2) !== true) {
|
||||
continue;
|
||||
}
|
||||
$password_hashed = hash_password($password);
|
||||
@@ -182,7 +156,7 @@ function app_passwd($_action, $_data = null) {
|
||||
'msg' => array('object_modified', htmlspecialchars(implode(', ', $ids)))
|
||||
);
|
||||
}
|
||||
break;
|
||||
break;
|
||||
case 'delete':
|
||||
$ids = (array)$_data['id'];
|
||||
foreach ($ids as $id) {
|
||||
@@ -213,19 +187,17 @@ function app_passwd($_action, $_data = null) {
|
||||
'msg' => array('app_passwd_removed', htmlspecialchars($id))
|
||||
);
|
||||
}
|
||||
break;
|
||||
break;
|
||||
case 'get':
|
||||
$app_passwds = array();
|
||||
$stmt = $pdo->prepare("SELECT `id`, `name` FROM `app_passwd` WHERE `mailbox` = :username");
|
||||
$stmt->execute(array(':username' => $username));
|
||||
$app_passwds = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
return $app_passwds;
|
||||
break;
|
||||
break;
|
||||
case 'details':
|
||||
$app_passwd_data = array();
|
||||
$stmt = $pdo->prepare("SELECT *
|
||||
FROM `app_passwd`
|
||||
WHERE `id` = :id");
|
||||
$stmt = $pdo->prepare("SELECT * FROM `app_passwd` WHERE `id` = :id");
|
||||
$stmt->execute(array(':id' => $_data));
|
||||
$app_passwd_data = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
if (empty($app_passwd_data)) {
|
||||
@@ -237,6 +209,6 @@ function app_passwd($_action, $_data = null) {
|
||||
}
|
||||
$app_passwd_data['name'] = htmlspecialchars(trim($app_passwd_data['name']));
|
||||
return $app_passwd_data;
|
||||
break;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
function check_login($user, $pass, $app_passwd_data = false, $extra = null) {
|
||||
global $pdo;
|
||||
global $redis;
|
||||
global $valkey;
|
||||
|
||||
$is_internal = $extra['is_internal'];
|
||||
$role = $extra['role'];
|
||||
@@ -62,12 +62,12 @@ function check_login($user, $pass, $app_passwd_data = false, $extra = null) {
|
||||
|
||||
if (!isset($_SESSION['ldelay'])) {
|
||||
$_SESSION['ldelay'] = "0";
|
||||
$redis->publish("F2B_CHANNEL", "mailcow UI: Invalid password for " . $user . " by " . $_SERVER['REMOTE_ADDR']);
|
||||
$valkey->publish("F2B_CHANNEL", "mailcow UI: Invalid password for " . $user . " by " . $_SERVER['REMOTE_ADDR']);
|
||||
error_log("mailcow UI: Invalid password for " . $user . " by " . $_SERVER['REMOTE_ADDR']);
|
||||
}
|
||||
elseif (!isset($_SESSION['mailcow_cc_username'])) {
|
||||
$_SESSION['ldelay'] = $_SESSION['ldelay']+0.5;
|
||||
$redis->publish("F2B_CHANNEL", "mailcow UI: Invalid password for " . $user . " by " . $_SERVER['REMOTE_ADDR']);
|
||||
$valkey->publish("F2B_CHANNEL", "mailcow UI: Invalid password for " . $user . " by " . $_SERVER['REMOTE_ADDR']);
|
||||
error_log("mailcow UI: Invalid password for " . $user . " by " . $_SERVER['REMOTE_ADDR']);
|
||||
}
|
||||
$_SESSION['return'][] = array(
|
||||
@@ -193,6 +193,7 @@ function user_login($user, $pass, $extra = null){
|
||||
global $iam_settings;
|
||||
|
||||
$is_internal = $extra['is_internal'];
|
||||
$service = $extra['service'];
|
||||
|
||||
if (!filter_var($user, FILTER_VALIDATE_EMAIL) && !ctype_alnum(str_replace(array('_', '.', '-'), '', $user))) {
|
||||
if (!$is_internal){
|
||||
@@ -235,6 +236,14 @@ function user_login($user, $pass, $extra = null){
|
||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!empty($row)) {
|
||||
// check if user has access to service (imap, smtp, pop3, sieve) if service is set
|
||||
$row['attributes'] = json_decode($row['attributes'], true);
|
||||
if (isset($service)) {
|
||||
$key = strtolower($service) . "_access";
|
||||
if (isset($row['attributes'][$key]) && $row['attributes'][$key] != '1') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -242,7 +251,14 @@ function user_login($user, $pass, $extra = null){
|
||||
return false;
|
||||
}
|
||||
|
||||
// check if user has access to service (imap, smtp, pop3, sieve) if service is set
|
||||
$row['attributes'] = json_decode($row['attributes'], true);
|
||||
if (isset($service)) {
|
||||
$key = strtolower($service) . "_access";
|
||||
if (isset($row['attributes'][$key]) && $row['attributes'][$key] != '1') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
switch ($row['authsource']) {
|
||||
case 'keycloak':
|
||||
// user authsource is keycloak, try using via rest flow
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
function customize($_action, $_item, $_data = null) {
|
||||
global $redis;
|
||||
global $valkey;
|
||||
global $lang;
|
||||
global $LOGO_LIMITS;
|
||||
|
||||
@@ -82,13 +82,13 @@ function customize($_action, $_item, $_data = null) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
$redis->Set(strtoupper($_item), 'data:' . $_data[$_item]['type'] . ';base64,' . base64_encode(file_get_contents($_data[$_item]['tmp_name'])));
|
||||
$valkey->Set(strtoupper($_item), 'data:' . $_data[$_item]['type'] . ';base64,' . base64_encode(file_get_contents($_data[$_item]['tmp_name'])));
|
||||
}
|
||||
catch (RedisException $e) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_item, $_data),
|
||||
'msg' => array('redis_error', $e)
|
||||
'msg' => array('valkey_error', $e)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
@@ -134,13 +134,13 @@ function customize($_action, $_item, $_data = null) {
|
||||
));
|
||||
}
|
||||
try {
|
||||
$redis->set('APP_LINKS', json_encode($out));
|
||||
$valkey->set('APP_LINKS', json_encode($out));
|
||||
}
|
||||
catch (RedisException $e) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_item, $_data),
|
||||
'msg' => array('redis_error', $e)
|
||||
'msg' => array('valkey_error', $e)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
@@ -162,20 +162,20 @@ function customize($_action, $_item, $_data = null) {
|
||||
$ui_announcement_active = (!empty($_data['ui_announcement_active']) ? 1 : 0);
|
||||
|
||||
try {
|
||||
$redis->set('TITLE_NAME', htmlspecialchars($title_name));
|
||||
$redis->set('MAIN_NAME', htmlspecialchars($main_name));
|
||||
$redis->set('APPS_NAME', htmlspecialchars($apps_name));
|
||||
$redis->set('HELP_TEXT', $help_text);
|
||||
$redis->set('UI_FOOTER', $ui_footer);
|
||||
$redis->set('UI_ANNOUNCEMENT_TEXT', $ui_announcement_text);
|
||||
$redis->set('UI_ANNOUNCEMENT_TYPE', $ui_announcement_type);
|
||||
$redis->set('UI_ANNOUNCEMENT_ACTIVE', $ui_announcement_active);
|
||||
$valkey->set('TITLE_NAME', htmlspecialchars($title_name));
|
||||
$valkey->set('MAIN_NAME', htmlspecialchars($main_name));
|
||||
$valkey->set('APPS_NAME', htmlspecialchars($apps_name));
|
||||
$valkey->set('HELP_TEXT', $help_text);
|
||||
$valkey->set('UI_FOOTER', $ui_footer);
|
||||
$valkey->set('UI_ANNOUNCEMENT_TEXT', $ui_announcement_text);
|
||||
$valkey->set('UI_ANNOUNCEMENT_TYPE', $ui_announcement_type);
|
||||
$valkey->set('UI_ANNOUNCEMENT_ACTIVE', $ui_announcement_active);
|
||||
}
|
||||
catch (RedisException $e) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_item, $_data),
|
||||
'msg' => array('redis_error', $e)
|
||||
'msg' => array('valkey_error', $e)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
@@ -188,13 +188,13 @@ function customize($_action, $_item, $_data = null) {
|
||||
case 'ip_check':
|
||||
$ip_check = ($_data['ip_check_opt_in'] == "1") ? 1 : 0;
|
||||
try {
|
||||
$redis->set('IP_CHECK', $ip_check);
|
||||
$valkey->set('IP_CHECK', $ip_check);
|
||||
}
|
||||
catch (RedisException $e) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_item, $_data),
|
||||
'msg' => array('redis_error', $e)
|
||||
'msg' => array('valkey_error', $e)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
@@ -217,7 +217,7 @@ function customize($_action, $_item, $_data = null) {
|
||||
"force_sso" => $force_sso,
|
||||
);
|
||||
try {
|
||||
$redis->set('CUSTOM_LOGIN', json_encode($custom_login));
|
||||
$valkey->set('CUSTOM_LOGIN', json_encode($custom_login));
|
||||
}
|
||||
catch (RedisException $e) {
|
||||
$_SESSION['return'][] = array(
|
||||
@@ -257,7 +257,7 @@ function customize($_action, $_item, $_data = null) {
|
||||
case 'main_logo':
|
||||
case 'main_logo_dark':
|
||||
try {
|
||||
if ($redis->del(strtoupper($_item))) {
|
||||
if ($valkey->del(strtoupper($_item))) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'success',
|
||||
'log' => array(__FUNCTION__, $_action, $_item, $_data),
|
||||
@@ -270,7 +270,7 @@ function customize($_action, $_item, $_data = null) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_item, $_data),
|
||||
'msg' => array('redis_error', $e)
|
||||
'msg' => array('valkey_error', $e)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
@@ -281,19 +281,19 @@ function customize($_action, $_item, $_data = null) {
|
||||
switch ($_item) {
|
||||
case 'app_links':
|
||||
try {
|
||||
$app_links = json_decode($redis->get('APP_LINKS'), true);
|
||||
$app_links = json_decode($valkey->get('APP_LINKS'), true);
|
||||
}
|
||||
catch (RedisException $e) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_item, $_data),
|
||||
'msg' => array('redis_error', $e)
|
||||
'msg' => array('valkey_error', $e)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (empty($app_links)){
|
||||
return false;
|
||||
return [];
|
||||
}
|
||||
|
||||
// convert from old style
|
||||
@@ -312,38 +312,40 @@ function customize($_action, $_item, $_data = null) {
|
||||
case 'main_logo':
|
||||
case 'main_logo_dark':
|
||||
try {
|
||||
return $redis->get(strtoupper($_item));
|
||||
return $valkey->get(strtoupper($_item));
|
||||
}
|
||||
catch (RedisException $e) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_item, $_data),
|
||||
'msg' => array('redis_error', $e)
|
||||
'msg' => array('valkey_error', $e)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case 'ui_texts':
|
||||
try {
|
||||
$data['title_name'] = ($title_name = $redis->get('TITLE_NAME')) ? $title_name : 'mailcow UI';
|
||||
$data['main_name'] = ($main_name = $redis->get('MAIN_NAME')) ? $main_name : 'mailcow UI';
|
||||
$data['apps_name'] = ($apps_name = $redis->get('APPS_NAME')) ? $apps_name : $lang['header']['apps'];
|
||||
$data['help_text'] = ($help_text = $redis->get('HELP_TEXT')) ? $help_text : false;
|
||||
if (!empty($redis->get('UI_IMPRESS'))) {
|
||||
$redis->set('UI_FOOTER', $redis->get('UI_IMPRESS'));
|
||||
$redis->del('UI_IMPRESS');
|
||||
$mailcow_hostname = strtolower(getenv("MAILCOW_HOSTNAME"));
|
||||
|
||||
$data['title_name'] = ($title_name = $valkey->get('TITLE_NAME')) ? $title_name : "$mailcow_hostname - mail UI";
|
||||
$data['main_name'] = ($main_name = $valkey->get('MAIN_NAME')) ? $main_name : "$mailcow_hostname - mail UI";
|
||||
$data['apps_name'] = ($apps_name = $valkey->get('APPS_NAME')) ? $apps_name : $lang['header']['apps'];
|
||||
$data['help_text'] = ($help_text = $valkey->get('HELP_TEXT')) ? $help_text : false;
|
||||
if (!empty($valkey->get('UI_IMPRESS'))) {
|
||||
$valkey->set('UI_FOOTER', $valkey->get('UI_IMPRESS'));
|
||||
$valkey->del('UI_IMPRESS');
|
||||
}
|
||||
$data['ui_footer'] = ($ui_footer = $redis->get('UI_FOOTER')) ? $ui_footer : false;
|
||||
$data['ui_announcement_text'] = ($ui_announcement_text = $redis->get('UI_ANNOUNCEMENT_TEXT')) ? $ui_announcement_text : false;
|
||||
$data['ui_announcement_type'] = ($ui_announcement_type = $redis->get('UI_ANNOUNCEMENT_TYPE')) ? $ui_announcement_type : false;
|
||||
$data['ui_announcement_active'] = ($redis->get('UI_ANNOUNCEMENT_ACTIVE') == 1) ? 1 : 0;
|
||||
$data['ui_footer'] = ($ui_footer = $valkey->get('UI_FOOTER')) ? $ui_footer : false;
|
||||
$data['ui_announcement_text'] = ($ui_announcement_text = $valkey->get('UI_ANNOUNCEMENT_TEXT')) ? $ui_announcement_text : false;
|
||||
$data['ui_announcement_type'] = ($ui_announcement_type = $valkey->get('UI_ANNOUNCEMENT_TYPE')) ? $ui_announcement_type : false;
|
||||
$data['ui_announcement_active'] = ($valkey->get('UI_ANNOUNCEMENT_ACTIVE') == 1) ? 1 : 0;
|
||||
return $data;
|
||||
}
|
||||
catch (RedisException $e) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_item, $_data),
|
||||
'msg' => array('redis_error', $e)
|
||||
'msg' => array('valkey_error', $e)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
@@ -374,21 +376,21 @@ function customize($_action, $_item, $_data = null) {
|
||||
break;
|
||||
case 'ip_check':
|
||||
try {
|
||||
$ip_check = ($ip_check = $redis->get('IP_CHECK')) ? $ip_check : 0;
|
||||
$ip_check = ($ip_check = $valkey->get('IP_CHECK')) ? $ip_check : 0;
|
||||
return $ip_check;
|
||||
}
|
||||
catch (RedisException $e) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_item, $_data),
|
||||
'msg' => array('redis_error', $e)
|
||||
'msg' => array('valkey_error', $e)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case 'custom_login':
|
||||
try {
|
||||
$custom_login = $redis->get('CUSTOM_LOGIN');
|
||||
$custom_login = $valkey->get('CUSTOM_LOGIN');
|
||||
return $custom_login ? json_decode($custom_login, true) : array();
|
||||
}
|
||||
catch (RedisException $e) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
|
||||
function dkim($_action, $_data = null, $privkey = false) {
|
||||
global $redis;
|
||||
global $valkey;
|
||||
global $lang;
|
||||
switch ($_action) {
|
||||
case 'add':
|
||||
@@ -18,7 +18,7 @@ function dkim($_action, $_data = null, $privkey = false) {
|
||||
);
|
||||
continue;
|
||||
}
|
||||
if ($redis->hGet('DKIM_PUB_KEYS', $domain)) {
|
||||
if ($valkey->hGet('DKIM_PUB_KEYS', $domain)) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_data),
|
||||
@@ -54,30 +54,30 @@ function dkim($_action, $_data = null, $privkey = false) {
|
||||
explode(PHP_EOL, $key_details['key'])
|
||||
), 1, -1)
|
||||
);
|
||||
// Save public key and selector to redis
|
||||
// Save public key and selector to valkey
|
||||
try {
|
||||
$redis->hSet('DKIM_PUB_KEYS', $domain, $pubKey);
|
||||
$redis->hSet('DKIM_SELECTORS', $domain, $dkim_selector);
|
||||
$valkey->hSet('DKIM_PUB_KEYS', $domain, $pubKey);
|
||||
$valkey->hSet('DKIM_SELECTORS', $domain, $dkim_selector);
|
||||
}
|
||||
catch (RedisException $e) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_data),
|
||||
'msg' => array('redis_error', $e)
|
||||
'msg' => array('valkey_error', $e)
|
||||
);
|
||||
continue;
|
||||
}
|
||||
// Export private key and save private key to redis
|
||||
// Export private key and save private key to valkey
|
||||
openssl_pkey_export($keypair_ressource, $privKey);
|
||||
if (isset($privKey) && !empty($privKey)) {
|
||||
try {
|
||||
$redis->hSet('DKIM_PRIV_KEYS', $dkim_selector . '.' . $domain, trim($privKey));
|
||||
$valkey->hSet('DKIM_PRIV_KEYS', $dkim_selector . '.' . $domain, trim($privKey));
|
||||
}
|
||||
catch (RedisException $e) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_data),
|
||||
'msg' => array('redis_error', $e)
|
||||
'msg' => array('valkey_error', $e)
|
||||
);
|
||||
continue;
|
||||
}
|
||||
@@ -121,15 +121,15 @@ function dkim($_action, $_data = null, $privkey = false) {
|
||||
$to_domains = array_filter($to_domains);
|
||||
foreach ($to_domains as $to_domain) {
|
||||
try {
|
||||
$redis->hSet('DKIM_PUB_KEYS', $to_domain, $from_domain_dkim['pubkey']);
|
||||
$redis->hSet('DKIM_SELECTORS', $to_domain, $from_domain_dkim['dkim_selector']);
|
||||
$redis->hSet('DKIM_PRIV_KEYS', $from_domain_dkim['dkim_selector'] . '.' . $to_domain, base64_decode(trim($from_domain_dkim['privkey'])));
|
||||
$valkey->hSet('DKIM_PUB_KEYS', $to_domain, $from_domain_dkim['pubkey']);
|
||||
$valkey->hSet('DKIM_SELECTORS', $to_domain, $from_domain_dkim['dkim_selector']);
|
||||
$valkey->hSet('DKIM_PRIV_KEYS', $from_domain_dkim['dkim_selector'] . '.' . $to_domain, base64_decode(trim($from_domain_dkim['privkey'])));
|
||||
}
|
||||
catch (RedisException $e) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_data),
|
||||
'msg' => array('redis_error', $e)
|
||||
'msg' => array('valkey_error', $e)
|
||||
);
|
||||
continue;
|
||||
}
|
||||
@@ -178,7 +178,7 @@ function dkim($_action, $_data = null, $privkey = false) {
|
||||
);
|
||||
return false;
|
||||
}
|
||||
if ($redis->hGet('DKIM_PUB_KEYS', $domain)) {
|
||||
if ($valkey->hGet('DKIM_PUB_KEYS', $domain)) {
|
||||
if ($overwrite_existing == 0) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
@@ -198,15 +198,15 @@ function dkim($_action, $_data = null, $privkey = false) {
|
||||
}
|
||||
try {
|
||||
dkim('delete', array('domains' => $domain));
|
||||
$redis->hSet('DKIM_PUB_KEYS', $domain, $pem_public_key);
|
||||
$redis->hSet('DKIM_SELECTORS', $domain, $dkim_selector);
|
||||
$redis->hSet('DKIM_PRIV_KEYS', $dkim_selector . '.' . $domain, $private_key_normalized);
|
||||
$valkey->hSet('DKIM_PUB_KEYS', $domain, $pem_public_key);
|
||||
$valkey->hSet('DKIM_SELECTORS', $domain, $dkim_selector);
|
||||
$valkey->hSet('DKIM_PRIV_KEYS', $dkim_selector . '.' . $domain, $private_key_normalized);
|
||||
}
|
||||
catch (RedisException $e) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_data),
|
||||
'msg' => array('redis_error', $e)
|
||||
'msg' => array('valkey_error', $e)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
@@ -219,7 +219,7 @@ function dkim($_action, $_data = null, $privkey = false) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_data),
|
||||
'msg' => array('redis_error', $e)
|
||||
'msg' => array('valkey_error', $e)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
@@ -235,8 +235,8 @@ function dkim($_action, $_data = null, $privkey = false) {
|
||||
return false;
|
||||
}
|
||||
$dkimdata = array();
|
||||
if ($redis_dkim_key_data = $redis->hGet('DKIM_PUB_KEYS', $_data)) {
|
||||
$dkimdata['pubkey'] = $redis_dkim_key_data;
|
||||
if ($valkey_dkim_key_data = $valkey->hGet('DKIM_PUB_KEYS', $_data)) {
|
||||
$dkimdata['pubkey'] = $valkey_dkim_key_data;
|
||||
if (strlen($dkimdata['pubkey']) < 391) {
|
||||
$dkimdata['length'] = "1024";
|
||||
}
|
||||
@@ -253,15 +253,15 @@ function dkim($_action, $_data = null, $privkey = false) {
|
||||
$dkimdata['length'] = ">= 8192";
|
||||
}
|
||||
if ($GLOBALS['SPLIT_DKIM_255'] === true) {
|
||||
$dkim_txt_tmp = str_split('v=DKIM1;k=rsa;t=s;s=email;p=' . $redis_dkim_key_data, 255);
|
||||
$dkim_txt_tmp = str_split('v=DKIM1;k=rsa;t=s;s=email;p=' . $valkey_dkim_key_data, 255);
|
||||
$dkimdata['dkim_txt'] = sprintf('"%s"', implode('" "', (array)$dkim_txt_tmp ) );
|
||||
}
|
||||
else {
|
||||
$dkimdata['dkim_txt'] = 'v=DKIM1;k=rsa;t=s;s=email;p=' . $redis_dkim_key_data;
|
||||
$dkimdata['dkim_txt'] = 'v=DKIM1;k=rsa;t=s;s=email;p=' . $valkey_dkim_key_data;
|
||||
}
|
||||
$dkimdata['dkim_selector'] = $redis->hGet('DKIM_SELECTORS', $_data);
|
||||
$dkimdata['dkim_selector'] = $valkey->hGet('DKIM_SELECTORS', $_data);
|
||||
if ($GLOBALS['SHOW_DKIM_PRIV_KEYS'] || $privkey == true) {
|
||||
$dkimdata['privkey'] = base64_encode($redis->hGet('DKIM_PRIV_KEYS', $dkimdata['dkim_selector'] . '.' . $_data));
|
||||
$dkimdata['privkey'] = base64_encode($valkey->hGet('DKIM_PRIV_KEYS', $dkimdata['dkim_selector'] . '.' . $_data));
|
||||
}
|
||||
else {
|
||||
$dkimdata['privkey'] = '';
|
||||
@@ -279,8 +279,8 @@ function dkim($_action, $_data = null, $privkey = false) {
|
||||
return false;
|
||||
}
|
||||
$blinddkim = array();
|
||||
foreach ($redis->hKeys('DKIM_PUB_KEYS') as $redis_dkim_domain) {
|
||||
$blinddkim[] = $redis_dkim_domain;
|
||||
foreach ($valkey->hKeys('DKIM_PUB_KEYS') as $valkey_dkim_domain) {
|
||||
$blinddkim[] = $valkey_dkim_domain;
|
||||
}
|
||||
return array_diff($blinddkim, array_merge(mailbox('get', 'domains'), mailbox('get', 'alias_domains')));
|
||||
break;
|
||||
@@ -304,16 +304,16 @@ function dkim($_action, $_data = null, $privkey = false) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
$selector = $redis->hGet('DKIM_SELECTORS', $domain);
|
||||
$redis->hDel('DKIM_PUB_KEYS', $domain);
|
||||
$redis->hDel('DKIM_PRIV_KEYS', $selector . '.' . $domain);
|
||||
$redis->hDel('DKIM_SELECTORS', $domain);
|
||||
$selector = $valkey->hGet('DKIM_SELECTORS', $domain);
|
||||
$valkey->hDel('DKIM_PUB_KEYS', $domain);
|
||||
$valkey->hDel('DKIM_PRIV_KEYS', $selector . '.' . $domain);
|
||||
$valkey->hDel('DKIM_SELECTORS', $domain);
|
||||
}
|
||||
catch (RedisException $e) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_data),
|
||||
'msg' => array('redis_error', $e)
|
||||
'msg' => array('valkey_error', $e)
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
function docker($action, $service_name = null, $attr1 = null, $attr2 = null, $extra_headers = null) {
|
||||
global $DOCKER_TIMEOUT;
|
||||
global $redis;
|
||||
global $valkey;
|
||||
$curl = curl_init();
|
||||
curl_setopt($curl, CURLOPT_HTTPHEADER,array('Content-Type: application/json' ));
|
||||
// We are using our mail certificates for dockerapi, the names will not match, the certs are trusted anyway
|
||||
@@ -102,7 +102,7 @@ function docker($action, $service_name = null, $attr1 = null, $attr2 = null, $ex
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (isset($decoded_response['Config']['Labels']['com.docker.compose.project'])
|
||||
if (isset($decoded_response['Config']['Labels']['com.docker.compose.project'])
|
||||
&& strtolower($decoded_response['Config']['Labels']['com.docker.compose.project']) == strtolower(getenv('COMPOSE_PROJECT_NAME'))) {
|
||||
unset($container['Config']['Env']);
|
||||
$out[$decoded_response['Config']['Labels']['com.docker.compose.service']]['State'] = $decoded_response['State'];
|
||||
@@ -200,7 +200,7 @@ function docker($action, $service_name = null, $attr1 = null, $attr2 = null, $ex
|
||||
"request" => $attr2
|
||||
);
|
||||
|
||||
$redis->publish("MC_CHANNEL", json_encode($request));
|
||||
$valkey->publish("MC_CHANNEL", json_encode($request));
|
||||
return true;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
function fail2ban($_action, $_data = null, $_extra = null) {
|
||||
global $redis;
|
||||
global $valkey;
|
||||
$_data_log = $_data;
|
||||
switch ($_action) {
|
||||
case 'get':
|
||||
@@ -9,9 +9,9 @@ function fail2ban($_action, $_data = null, $_extra = null) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
$f2b_options = json_decode($redis->Get('F2B_OPTIONS'), true);
|
||||
$f2b_options['regex'] = json_decode($redis->Get('F2B_REGEX'), true);
|
||||
$wl = $redis->hGetAll('F2B_WHITELIST');
|
||||
$f2b_options = json_decode($valkey->Get('F2B_OPTIONS'), true);
|
||||
$f2b_options['regex'] = json_decode($valkey->Get('F2B_REGEX'), true);
|
||||
$wl = $valkey->hGetAll('F2B_WHITELIST');
|
||||
if (is_array($wl)) {
|
||||
foreach ($wl as $key => $value) {
|
||||
$tmp_wl_data[] = $key;
|
||||
@@ -27,7 +27,7 @@ function fail2ban($_action, $_data = null, $_extra = null) {
|
||||
else {
|
||||
$f2b_options['whitelist'] = "";
|
||||
}
|
||||
$bl = $redis->hGetAll('F2B_BLACKLIST');
|
||||
$bl = $valkey->hGetAll('F2B_BLACKLIST');
|
||||
if (is_array($bl)) {
|
||||
foreach ($bl as $key => $value) {
|
||||
$tmp_bl_data[] = $key;
|
||||
@@ -43,7 +43,7 @@ function fail2ban($_action, $_data = null, $_extra = null) {
|
||||
else {
|
||||
$f2b_options['blacklist'] = "";
|
||||
}
|
||||
$pb = $redis->hGetAll('F2B_PERM_BANS');
|
||||
$pb = $valkey->hGetAll('F2B_PERM_BANS');
|
||||
if (is_array($pb)) {
|
||||
foreach ($pb as $key => $value) {
|
||||
$f2b_options['perm_bans'][] = array(
|
||||
@@ -56,8 +56,8 @@ function fail2ban($_action, $_data = null, $_extra = null) {
|
||||
else {
|
||||
$f2b_options['perm_bans'] = "";
|
||||
}
|
||||
$active_bans = $redis->hGetAll('F2B_ACTIVE_BANS');
|
||||
$queue_unban = $redis->hGetAll('F2B_QUEUE_UNBAN');
|
||||
$active_bans = $valkey->hGetAll('F2B_ACTIVE_BANS');
|
||||
$queue_unban = $valkey->hGetAll('F2B_QUEUE_UNBAN');
|
||||
if (is_array($active_bans)) {
|
||||
foreach ($active_bans as $network => $banned_until) {
|
||||
$queued_for_unban = (isset($queue_unban[$network]) && $queue_unban[$network] == 1) ? 1 : 0;
|
||||
@@ -78,7 +78,7 @@ function fail2ban($_action, $_data = null, $_extra = null) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_data_log),
|
||||
'msg' => array('redis_error', $e)
|
||||
'msg' => array('valkey_error', $e)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
@@ -98,22 +98,22 @@ function fail2ban($_action, $_data = null, $_extra = null) {
|
||||
// Reset regex filters
|
||||
if ($_data['action'] == "reset-regex") {
|
||||
try {
|
||||
$redis->Del('F2B_REGEX');
|
||||
$valkey->Del('F2B_REGEX');
|
||||
}
|
||||
catch (RedisException $e) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_data_log),
|
||||
'msg' => array('redis_error', $e)
|
||||
'msg' => array('valkey_error', $e)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
// Rules will also be recreated on log events, but rules may seem empty for a second in the UI
|
||||
docker('post', 'netfilter-mailcow', 'restart');
|
||||
$fail_count = 0;
|
||||
$regex_result = json_decode($redis->Get('F2B_REGEX'), true);
|
||||
$regex_result = json_decode($valkey->Get('F2B_REGEX'), true);
|
||||
while (empty($regex_result) && $fail_count < 10) {
|
||||
$regex_result = json_decode($redis->Get('F2B_REGEX'), true);
|
||||
$regex_result = json_decode($valkey->Get('F2B_REGEX'), true);
|
||||
$fail_count++;
|
||||
sleep(1);
|
||||
}
|
||||
@@ -135,7 +135,7 @@ function fail2ban($_action, $_data = null, $_extra = null) {
|
||||
$rule_id++;
|
||||
}
|
||||
if (!empty($regex_array)) {
|
||||
$redis->Set('F2B_REGEX', json_encode($regex_array, JSON_UNESCAPED_SLASHES));
|
||||
$valkey->Set('F2B_REGEX', json_encode($regex_array, JSON_UNESCAPED_SLASHES));
|
||||
}
|
||||
}
|
||||
$_SESSION['return'][] = array(
|
||||
@@ -154,13 +154,13 @@ function fail2ban($_action, $_data = null, $_extra = null) {
|
||||
if ($_data['action'] == "unban") {
|
||||
if (valid_network($network)) {
|
||||
try {
|
||||
$redis->hSet('F2B_QUEUE_UNBAN', $network, 1);
|
||||
$valkey->hSet('F2B_QUEUE_UNBAN', $network, 1);
|
||||
}
|
||||
catch (RedisException $e) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_data_log),
|
||||
'msg' => array('redis_error', $e)
|
||||
'msg' => array('valkey_error', $e)
|
||||
);
|
||||
continue;
|
||||
}
|
||||
@@ -171,15 +171,15 @@ function fail2ban($_action, $_data = null, $_extra = null) {
|
||||
if (empty($network)) { continue; }
|
||||
if (valid_network($network)) {
|
||||
try {
|
||||
$redis->hSet('F2B_WHITELIST', $network, 1);
|
||||
$redis->hDel('F2B_BLACKLIST', $network, 1);
|
||||
$redis->hSet('F2B_QUEUE_UNBAN', $network, 1);
|
||||
$valkey->hSet('F2B_WHITELIST', $network, 1);
|
||||
$valkey->hDel('F2B_BLACKLIST', $network, 1);
|
||||
$valkey->hSet('F2B_QUEUE_UNBAN', $network, 1);
|
||||
}
|
||||
catch (RedisException $e) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_data_log),
|
||||
'msg' => array('redis_error', $e)
|
||||
'msg' => array('valkey_error', $e)
|
||||
);
|
||||
continue;
|
||||
}
|
||||
@@ -204,15 +204,15 @@ function fail2ban($_action, $_data = null, $_extra = null) {
|
||||
getenv('IPV6_NETWORK')
|
||||
))) {
|
||||
try {
|
||||
$redis->hSet('F2B_BLACKLIST', $network, 1);
|
||||
$redis->hDel('F2B_WHITELIST', $network, 1);
|
||||
$valkey->hSet('F2B_BLACKLIST', $network, 1);
|
||||
$valkey->hDel('F2B_WHITELIST', $network, 1);
|
||||
//$response = docker('post', 'netfilter-mailcow', 'restart');
|
||||
}
|
||||
catch (RedisException $e) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_data_log),
|
||||
'msg' => array('redis_error', $e)
|
||||
'msg' => array('valkey_error', $e)
|
||||
);
|
||||
continue;
|
||||
}
|
||||
@@ -270,16 +270,16 @@ function fail2ban($_action, $_data = null, $_extra = null) {
|
||||
$f2b_options['banlist_id'] = $is_now['banlist_id'];
|
||||
$f2b_options['manage_external'] = ($manage_external > 0) ? 1 : 0;
|
||||
try {
|
||||
$redis->Set('F2B_OPTIONS', json_encode($f2b_options));
|
||||
$redis->Del('F2B_WHITELIST');
|
||||
$redis->Del('F2B_BLACKLIST');
|
||||
$valkey->Set('F2B_OPTIONS', json_encode($f2b_options));
|
||||
$valkey->Del('F2B_WHITELIST');
|
||||
$valkey->Del('F2B_BLACKLIST');
|
||||
if(!empty($wl)) {
|
||||
$wl_array = array_map('trim', preg_split( "/( |,|;|\n)/", $wl));
|
||||
$wl_array = array_filter($wl_array);
|
||||
if (is_array($wl_array)) {
|
||||
foreach ($wl_array as $wl_item) {
|
||||
if (valid_network($wl_item) || valid_hostname($wl_item)) {
|
||||
$redis->hSet('F2B_WHITELIST', $wl_item, 1);
|
||||
$valkey->hSet('F2B_WHITELIST', $wl_item, 1);
|
||||
}
|
||||
else {
|
||||
$_SESSION['return'][] = array(
|
||||
@@ -304,7 +304,7 @@ function fail2ban($_action, $_data = null, $_extra = null) {
|
||||
getenv('IPV4_NETWORK') . '0',
|
||||
getenv('IPV6_NETWORK')
|
||||
))) {
|
||||
$redis->hSet('F2B_BLACKLIST', $bl_item, 1);
|
||||
$valkey->hSet('F2B_BLACKLIST', $bl_item, 1);
|
||||
}
|
||||
else {
|
||||
$_SESSION['return'][] = array(
|
||||
@@ -322,7 +322,7 @@ function fail2ban($_action, $_data = null, $_extra = null) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_data_log),
|
||||
'msg' => array('redis_error', $e)
|
||||
'msg' => array('valkey_error', $e)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
@@ -334,13 +334,13 @@ function fail2ban($_action, $_data = null, $_extra = null) {
|
||||
break;
|
||||
case 'banlist':
|
||||
try {
|
||||
$f2b_options = json_decode($redis->Get('F2B_OPTIONS'), true);
|
||||
}
|
||||
$f2b_options = json_decode($valkey->Get('F2B_OPTIONS'), true);
|
||||
}
|
||||
catch (RedisException $e) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_data_log, $_extra),
|
||||
'msg' => array('redis_error', $e)
|
||||
'msg' => array('valkey_error', $e)
|
||||
);
|
||||
http_response_code(500);
|
||||
return false;
|
||||
@@ -356,14 +356,14 @@ function fail2ban($_action, $_data = null, $_extra = null) {
|
||||
switch ($_data) {
|
||||
case 'get':
|
||||
try {
|
||||
$bl = $redis->hKeys('F2B_BLACKLIST');
|
||||
$active_bans = $redis->hKeys('F2B_ACTIVE_BANS');
|
||||
}
|
||||
$bl = $valkey->hKeys('F2B_BLACKLIST');
|
||||
$active_bans = $valkey->hKeys('F2B_ACTIVE_BANS');
|
||||
}
|
||||
catch (RedisException $e) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_data_log, $_extra),
|
||||
'msg' => array('redis_error', $e)
|
||||
'msg' => array('valkey_error', $e)
|
||||
);
|
||||
http_response_code(500);
|
||||
return false;
|
||||
@@ -378,13 +378,13 @@ function fail2ban($_action, $_data = null, $_extra = null) {
|
||||
|
||||
$f2b_options['banlist_id'] = uuid4();
|
||||
try {
|
||||
$redis->Set('F2B_OPTIONS', json_encode($f2b_options));
|
||||
}
|
||||
$valkey->Set('F2B_OPTIONS', json_encode($f2b_options));
|
||||
}
|
||||
catch (RedisException $e) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_data_log, $_extra),
|
||||
'msg' => array('redis_error', $e)
|
||||
'msg' => array('valkey_error', $e)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
function fwdhost($_action, $_data = null) {
|
||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/spf.inc.php';
|
||||
global $redis;
|
||||
global $valkey;
|
||||
global $lang;
|
||||
$_data_log = $_data;
|
||||
switch ($_action) {
|
||||
@@ -37,19 +37,19 @@ function fwdhost($_action, $_data = null) {
|
||||
}
|
||||
foreach ($hosts as $host) {
|
||||
try {
|
||||
$redis->hSet('WHITELISTED_FWD_HOST', $host, $source);
|
||||
$valkey->hSet('WHITELISTED_FWD_HOST', $host, $source);
|
||||
if ($filter_spam == 0) {
|
||||
$redis->hSet('KEEP_SPAM', $host, 1);
|
||||
$valkey->hSet('KEEP_SPAM', $host, 1);
|
||||
}
|
||||
elseif ($redis->hGet('KEEP_SPAM', $host)) {
|
||||
$redis->hDel('KEEP_SPAM', $host);
|
||||
elseif ($valkey->hGet('KEEP_SPAM', $host)) {
|
||||
$valkey->hDel('KEEP_SPAM', $host);
|
||||
}
|
||||
}
|
||||
catch (RedisException $e) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_data_log),
|
||||
'msg' => array('redis_error', $e)
|
||||
'msg' => array('valkey_error', $e)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
@@ -86,17 +86,17 @@ function fwdhost($_action, $_data = null) {
|
||||
}
|
||||
try {
|
||||
if ($keep_spam == 1) {
|
||||
$redis->hSet('KEEP_SPAM', $fwdhost, 1);
|
||||
$valkey->hSet('KEEP_SPAM', $fwdhost, 1);
|
||||
}
|
||||
else {
|
||||
$redis->hDel('KEEP_SPAM', $fwdhost);
|
||||
$valkey->hDel('KEEP_SPAM', $fwdhost);
|
||||
}
|
||||
}
|
||||
catch (RedisException $e) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_data_log),
|
||||
'msg' => array('redis_error', $e)
|
||||
'msg' => array('valkey_error', $e)
|
||||
);
|
||||
continue;
|
||||
}
|
||||
@@ -111,14 +111,14 @@ function fwdhost($_action, $_data = null) {
|
||||
$hosts = (array)$_data['forwardinghost'];
|
||||
foreach ($hosts as $host) {
|
||||
try {
|
||||
$redis->hDel('WHITELISTED_FWD_HOST', $host);
|
||||
$redis->hDel('KEEP_SPAM', $host);
|
||||
$valkey->hDel('WHITELISTED_FWD_HOST', $host);
|
||||
$valkey->hDel('KEEP_SPAM', $host);
|
||||
}
|
||||
catch (RedisException $e) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_data_log),
|
||||
'msg' => array('redis_error', $e)
|
||||
'msg' => array('valkey_error', $e)
|
||||
);
|
||||
continue;
|
||||
}
|
||||
@@ -135,10 +135,10 @@ function fwdhost($_action, $_data = null) {
|
||||
}
|
||||
$fwdhostsdata = array();
|
||||
try {
|
||||
$fwd_hosts = $redis->hGetAll('WHITELISTED_FWD_HOST');
|
||||
$fwd_hosts = $valkey->hGetAll('WHITELISTED_FWD_HOST');
|
||||
if (!empty($fwd_hosts)) {
|
||||
foreach ($fwd_hosts as $fwd_host => $source) {
|
||||
$keep_spam = ($redis->hGet('KEEP_SPAM', $fwd_host)) ? "yes" : "no";
|
||||
$keep_spam = ($valkey->hGet('KEEP_SPAM', $fwd_host)) ? "yes" : "no";
|
||||
$fwdhostsdata[] = array(
|
||||
'host' => $fwd_host,
|
||||
'source' => $source,
|
||||
@@ -151,7 +151,7 @@ function fwdhost($_action, $_data = null) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_data_log),
|
||||
'msg' => array('redis_error', $e)
|
||||
'msg' => array('valkey_error', $e)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
@@ -163,17 +163,17 @@ function fwdhost($_action, $_data = null) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
if ($source = $redis->hGet('WHITELISTED_FWD_HOST', $_data)) {
|
||||
if ($source = $valkey->hGet('WHITELISTED_FWD_HOST', $_data)) {
|
||||
$fwdhostdetails['host'] = $_data;
|
||||
$fwdhostdetails['source'] = $source;
|
||||
$fwdhostdetails['keep_spam'] = ($redis->hGet('KEEP_SPAM', $_data)) ? "yes" : "no";
|
||||
$fwdhostdetails['keep_spam'] = ($valkey->hGet('KEEP_SPAM', $_data)) ? "yes" : "no";
|
||||
}
|
||||
}
|
||||
catch (RedisException $e) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_data_log),
|
||||
'msg' => array('redis_error', $e)
|
||||
'msg' => array('valkey_error', $e)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -126,7 +126,7 @@ function hash_password($password) {
|
||||
return $pw_hash;
|
||||
}
|
||||
function password_complexity($_action, $_data = null) {
|
||||
global $redis;
|
||||
global $valkey;
|
||||
global $lang;
|
||||
switch ($_action) {
|
||||
case 'edit':
|
||||
@@ -147,7 +147,7 @@ function password_complexity($_action, $_data = null) {
|
||||
$numbers = (isset($_data['numbers'])) ? intval($_data['numbers']) : $is_now['numbers'];
|
||||
}
|
||||
try {
|
||||
$redis->hMSet('PASSWD_POLICY', [
|
||||
$valkey->hMSet('PASSWD_POLICY', [
|
||||
'length' => $length,
|
||||
'chars' => $chars,
|
||||
'special_chars' => $special_chars,
|
||||
@@ -159,7 +159,7 @@ function password_complexity($_action, $_data = null) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_data),
|
||||
'msg' => array('redis_error', $e)
|
||||
'msg' => array('valkey_error', $e)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
@@ -171,11 +171,11 @@ function password_complexity($_action, $_data = null) {
|
||||
break;
|
||||
case 'get':
|
||||
try {
|
||||
$length = $redis->hGet('PASSWD_POLICY', 'length');
|
||||
$chars = $redis->hGet('PASSWD_POLICY', 'chars');
|
||||
$special_chars = $redis->hGet('PASSWD_POLICY', 'special_chars');
|
||||
$lowerupper = $redis->hGet('PASSWD_POLICY', 'lowerupper');
|
||||
$numbers = $redis->hGet('PASSWD_POLICY', 'numbers');
|
||||
$length = $valkey->hGet('PASSWD_POLICY', 'length');
|
||||
$chars = $valkey->hGet('PASSWD_POLICY', 'chars');
|
||||
$special_chars = $valkey->hGet('PASSWD_POLICY', 'special_chars');
|
||||
$lowerupper = $valkey->hGet('PASSWD_POLICY', 'lowerupper');
|
||||
$numbers = $valkey->hGet('PASSWD_POLICY', 'numbers');
|
||||
return array(
|
||||
'length' => $length,
|
||||
'chars' => $chars,
|
||||
@@ -188,7 +188,7 @@ function password_complexity($_action, $_data = null) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_data),
|
||||
'msg' => array('redis_error', $e)
|
||||
'msg' => array('valkey_error', $e)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
@@ -253,7 +253,7 @@ function password_check($password1, $password2) {
|
||||
}
|
||||
function last_login($action, $username, $sasl_limit_days = 7, $ui_offset = 1) {
|
||||
global $pdo;
|
||||
global $redis;
|
||||
global $valkey;
|
||||
$sasl_limit_days = intval($sasl_limit_days);
|
||||
switch ($action) {
|
||||
case 'get':
|
||||
@@ -272,13 +272,13 @@ function last_login($action, $username, $sasl_limit_days = 7, $ui_offset = 1) {
|
||||
}
|
||||
elseif (filter_var($sasl[$k]['real_rip'], FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
|
||||
try {
|
||||
$sasl[$k]['location'] = $redis->hGet('IP_SHORTCOUNTRY', $sasl[$k]['real_rip']);
|
||||
$sasl[$k]['location'] = $valkey->hGet('IP_SHORTCOUNTRY', $sasl[$k]['real_rip']);
|
||||
}
|
||||
catch (RedisException $e) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_data_log),
|
||||
'msg' => array('redis_error', $e)
|
||||
'msg' => array('valkey_error', $e)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
@@ -294,13 +294,13 @@ function last_login($action, $username, $sasl_limit_days = 7, $ui_offset = 1) {
|
||||
if ($ip_data_array !== false and !empty($ip_data_array['shortcountry'])) {
|
||||
$sasl[$k]['location'] = $ip_data_array['shortcountry'];
|
||||
try {
|
||||
$redis->hSet('IP_SHORTCOUNTRY', $sasl[$k]['real_rip'], $ip_data_array['shortcountry']);
|
||||
$valkey->hSet('IP_SHORTCOUNTRY', $sasl[$k]['real_rip'], $ip_data_array['shortcountry']);
|
||||
}
|
||||
catch (RedisException $e) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_data_log),
|
||||
'msg' => array('redis_error', $e)
|
||||
'msg' => array('valkey_error', $e)
|
||||
);
|
||||
curl_close($curl);
|
||||
return false;
|
||||
@@ -1107,11 +1107,21 @@ function user_get_alias_details($username) {
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
function is_valid_domain_name($domain_name) {
|
||||
function is_valid_domain_name($domain_name, $options = array()) {
|
||||
if (empty($domain_name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Convert domain name to ASCII for validation
|
||||
$domain_name = idn_to_ascii($domain_name, 0, INTL_IDNA_VARIANT_UTS46);
|
||||
|
||||
if (isset($options['allow_wildcard']) && $options['allow_wildcard'] == true) {
|
||||
// Remove '*.' if wildcard subdomains are allowed
|
||||
if (strpos($domain_name, '*.') === 0) {
|
||||
$domain_name = substr($domain_name, 2);
|
||||
}
|
||||
}
|
||||
|
||||
return (preg_match("/^([a-z\d](-*[a-z\d])*)(\.([a-z\d](-*[a-z\d])*))*$/i", $domain_name)
|
||||
&& preg_match("/^.{1,253}$/", $domain_name)
|
||||
&& preg_match("/^[^\.]{1,63}(\.[^\.]{1,63})*$/", $domain_name));
|
||||
@@ -1989,7 +1999,7 @@ function admin_api($access, $action, $data = null) {
|
||||
}
|
||||
function license($action, $data = null) {
|
||||
global $pdo;
|
||||
global $redis;
|
||||
global $valkey;
|
||||
global $lang;
|
||||
if ($_SESSION['mailcow_cc_role'] != "admin") {
|
||||
$_SESSION['return'][] = array(
|
||||
@@ -2039,13 +2049,13 @@ function license($action, $data = null) {
|
||||
}
|
||||
try {
|
||||
// json_encode needs "true"/"false" instead of true/false, to not encode it to 0 or 1
|
||||
$redis->Set('LICENSE_STATUS_CACHE', json_encode($_SESSION['gal']));
|
||||
$valkey->Set('LICENSE_STATUS_CACHE', json_encode($_SESSION['gal']));
|
||||
}
|
||||
catch (RedisException $e) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_data_log),
|
||||
'msg' => array('redis_error', $e)
|
||||
'msg' => array('valkey_error', $e)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
@@ -2126,7 +2136,7 @@ function rspamd_ui($action, $data = null) {
|
||||
}
|
||||
}
|
||||
function cors($action, $data = null) {
|
||||
global $redis;
|
||||
global $valkey;
|
||||
|
||||
switch ($action) {
|
||||
case "edit":
|
||||
@@ -2167,7 +2177,7 @@ function cors($action, $data = null) {
|
||||
}
|
||||
|
||||
try {
|
||||
$redis->hMSet('CORS_SETTINGS', array(
|
||||
$valkey->hMSet('CORS_SETTINGS', array(
|
||||
'allowed_origins' => implode(', ', $allowed_origins),
|
||||
'allowed_methods' => implode(', ', $allowed_methods)
|
||||
));
|
||||
@@ -2175,7 +2185,7 @@ function cors($action, $data = null) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $action, $data),
|
||||
'msg' => array('redis_error', $e)
|
||||
'msg' => array('valkey_error', $e)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
@@ -2189,12 +2199,12 @@ function cors($action, $data = null) {
|
||||
break;
|
||||
case "get":
|
||||
try {
|
||||
$cors_settings = $redis->hMGet('CORS_SETTINGS', array('allowed_origins', 'allowed_methods'));
|
||||
$cors_settings = $valkey->hMGet('CORS_SETTINGS', array('allowed_origins', 'allowed_methods'));
|
||||
} catch (RedisException $e) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $action, $data),
|
||||
'msg' => array('redis_error', $e)
|
||||
'msg' => array('valkey_error', $e)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2211,7 +2221,7 @@ function cors($action, $data = null) {
|
||||
$cors_settings['allowed_origins'] = $allowed_origins[0];
|
||||
if (in_array('*', $allowed_origins)){
|
||||
$cors_settings['allowed_origins'] = '*';
|
||||
} else if (in_array($_SERVER['HTTP_ORIGIN'], $allowed_origins)) {
|
||||
} else if (array_key_exists('HTTP_ORIGIN', $_SERVER) && in_array($_SERVER['HTTP_ORIGIN'], $allowed_origins)) {
|
||||
$cors_settings['allowed_origins'] = $_SERVER['HTTP_ORIGIN'];
|
||||
}
|
||||
// always allow OPTIONS for preflight request
|
||||
@@ -2957,7 +2967,7 @@ function identity_provider($_action = null, $_data = null, $_extra = null) {
|
||||
}
|
||||
function reset_password($action, $data = null) {
|
||||
global $pdo;
|
||||
global $redis;
|
||||
global $valkey;
|
||||
global $mailcow_hostname;
|
||||
global $PW_RESET_TOKEN_LIMIT;
|
||||
global $PW_RESET_TOKEN_LIFETIME;
|
||||
@@ -3193,10 +3203,10 @@ function reset_password($action, $data = null) {
|
||||
$type = $data;
|
||||
|
||||
try {
|
||||
$settings['from'] = $redis->Get('PW_RESET_FROM');
|
||||
$settings['subject'] = $redis->Get('PW_RESET_SUBJ');
|
||||
$settings['html_tmpl'] = $redis->Get('PW_RESET_HTML');
|
||||
$settings['text_tmpl'] = $redis->Get('PW_RESET_TEXT');
|
||||
$settings['from'] = $valkey->Get('PW_RESET_FROM');
|
||||
$settings['subject'] = $valkey->Get('PW_RESET_SUBJ');
|
||||
$settings['html_tmpl'] = $valkey->Get('PW_RESET_HTML');
|
||||
$settings['text_tmpl'] = $valkey->Get('PW_RESET_TEXT');
|
||||
if (empty($settings['html_tmpl']) && empty($settings['text_tmpl'])) {
|
||||
$settings['html_tmpl'] = file_get_contents("/tpls/pw_reset_html.tpl");
|
||||
$settings['text_tmpl'] = file_get_contents("/tpls/pw_reset_text.tpl");
|
||||
@@ -3211,7 +3221,7 @@ function reset_password($action, $data = null) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $action, $_data_log),
|
||||
'msg' => array('redis_error', $e)
|
||||
'msg' => array('valkey_error', $e)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
@@ -3314,16 +3324,16 @@ function reset_password($action, $data = null) {
|
||||
$html = (empty($data['html_tmpl'])) ? "" : $data['html_tmpl'];
|
||||
|
||||
try {
|
||||
$redis->Set('PW_RESET_FROM', $from);
|
||||
$redis->Set('PW_RESET_SUBJ', $subject);
|
||||
$redis->Set('PW_RESET_HTML', $html);
|
||||
$redis->Set('PW_RESET_TEXT', $text);
|
||||
$valkey->Set('PW_RESET_FROM', $from);
|
||||
$valkey->Set('PW_RESET_SUBJ', $subject);
|
||||
$valkey->Set('PW_RESET_HTML', $html);
|
||||
$valkey->Set('PW_RESET_TEXT', $text);
|
||||
}
|
||||
catch (RedisException $e) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $action, $_data_log),
|
||||
'msg' => array('redis_error', $e)
|
||||
'msg' => array('valkey_error', $e)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
@@ -3366,7 +3376,7 @@ function get_logs($application, $lines = false) {
|
||||
$to = intval($to);
|
||||
if ($from < 1 || $to < $from) { return false; }
|
||||
}
|
||||
global $redis;
|
||||
global $valkey;
|
||||
global $pdo;
|
||||
if ($_SESSION['mailcow_cc_role'] != "admin") {
|
||||
return false;
|
||||
@@ -3415,10 +3425,10 @@ function get_logs($application, $lines = false) {
|
||||
// Redis
|
||||
if ($application == "dovecot-mailcow") {
|
||||
if (isset($from) && isset($to)) {
|
||||
$data = $redis->lRange('DOVECOT_MAILLOG', $from - 1, $to - 1);
|
||||
$data = $valkey->lRange('DOVECOT_MAILLOG', $from - 1, $to - 1);
|
||||
}
|
||||
else {
|
||||
$data = $redis->lRange('DOVECOT_MAILLOG', 0, $lines);
|
||||
$data = $valkey->lRange('DOVECOT_MAILLOG', 0, $lines);
|
||||
}
|
||||
if ($data) {
|
||||
foreach ($data as $json_line) {
|
||||
@@ -3429,10 +3439,10 @@ function get_logs($application, $lines = false) {
|
||||
}
|
||||
if ($application == "cron-mailcow") {
|
||||
if (isset($from) && isset($to)) {
|
||||
$data = $redis->lRange('CRON_LOG', $from - 1, $to - 1);
|
||||
$data = $valkey->lRange('CRON_LOG', $from - 1, $to - 1);
|
||||
}
|
||||
else {
|
||||
$data = $redis->lRange('CRON_LOG', 0, $lines);
|
||||
$data = $valkey->lRange('CRON_LOG', 0, $lines);
|
||||
}
|
||||
if ($data) {
|
||||
foreach ($data as $json_line) {
|
||||
@@ -3443,10 +3453,10 @@ function get_logs($application, $lines = false) {
|
||||
}
|
||||
if ($application == "postfix-mailcow") {
|
||||
if (isset($from) && isset($to)) {
|
||||
$data = $redis->lRange('POSTFIX_MAILLOG', $from - 1, $to - 1);
|
||||
$data = $valkey->lRange('POSTFIX_MAILLOG', $from - 1, $to - 1);
|
||||
}
|
||||
else {
|
||||
$data = $redis->lRange('POSTFIX_MAILLOG', 0, $lines);
|
||||
$data = $valkey->lRange('POSTFIX_MAILLOG', 0, $lines);
|
||||
}
|
||||
if ($data) {
|
||||
foreach ($data as $json_line) {
|
||||
@@ -3457,10 +3467,10 @@ function get_logs($application, $lines = false) {
|
||||
}
|
||||
if ($application == "sogo-mailcow") {
|
||||
if (isset($from) && isset($to)) {
|
||||
$data = $redis->lRange('SOGO_LOG', $from - 1, $to - 1);
|
||||
$data = $valkey->lRange('SOGO_LOG', $from - 1, $to - 1);
|
||||
}
|
||||
else {
|
||||
$data = $redis->lRange('SOGO_LOG', 0, $lines);
|
||||
$data = $valkey->lRange('SOGO_LOG', 0, $lines);
|
||||
}
|
||||
if ($data) {
|
||||
foreach ($data as $json_line) {
|
||||
@@ -3471,10 +3481,10 @@ function get_logs($application, $lines = false) {
|
||||
}
|
||||
if ($application == "watchdog-mailcow") {
|
||||
if (isset($from) && isset($to)) {
|
||||
$data = $redis->lRange('WATCHDOG_LOG', $from - 1, $to - 1);
|
||||
$data = $valkey->lRange('WATCHDOG_LOG', $from - 1, $to - 1);
|
||||
}
|
||||
else {
|
||||
$data = $redis->lRange('WATCHDOG_LOG', 0, $lines);
|
||||
$data = $valkey->lRange('WATCHDOG_LOG', 0, $lines);
|
||||
}
|
||||
if ($data) {
|
||||
foreach ($data as $json_line) {
|
||||
@@ -3485,10 +3495,10 @@ function get_logs($application, $lines = false) {
|
||||
}
|
||||
if ($application == "acme-mailcow") {
|
||||
if (isset($from) && isset($to)) {
|
||||
$data = $redis->lRange('ACME_LOG', $from - 1, $to - 1);
|
||||
$data = $valkey->lRange('ACME_LOG', $from - 1, $to - 1);
|
||||
}
|
||||
else {
|
||||
$data = $redis->lRange('ACME_LOG', 0, $lines);
|
||||
$data = $valkey->lRange('ACME_LOG', 0, $lines);
|
||||
}
|
||||
if ($data) {
|
||||
foreach ($data as $json_line) {
|
||||
@@ -3499,10 +3509,10 @@ function get_logs($application, $lines = false) {
|
||||
}
|
||||
if ($application == "ratelimited") {
|
||||
if (isset($from) && isset($to)) {
|
||||
$data = $redis->lRange('RL_LOG', $from - 1, $to - 1);
|
||||
$data = $valkey->lRange('RL_LOG', $from - 1, $to - 1);
|
||||
}
|
||||
else {
|
||||
$data = $redis->lRange('RL_LOG', 0, $lines);
|
||||
$data = $valkey->lRange('RL_LOG', 0, $lines);
|
||||
}
|
||||
if ($data) {
|
||||
foreach ($data as $json_line) {
|
||||
@@ -3513,10 +3523,10 @@ function get_logs($application, $lines = false) {
|
||||
}
|
||||
if ($application == "api-mailcow") {
|
||||
if (isset($from) && isset($to)) {
|
||||
$data = $redis->lRange('API_LOG', $from - 1, $to - 1);
|
||||
$data = $valkey->lRange('API_LOG', $from - 1, $to - 1);
|
||||
}
|
||||
else {
|
||||
$data = $redis->lRange('API_LOG', 0, $lines);
|
||||
$data = $valkey->lRange('API_LOG', 0, $lines);
|
||||
}
|
||||
if ($data) {
|
||||
foreach ($data as $json_line) {
|
||||
@@ -3527,10 +3537,10 @@ function get_logs($application, $lines = false) {
|
||||
}
|
||||
if ($application == "netfilter-mailcow") {
|
||||
if (isset($from) && isset($to)) {
|
||||
$data = $redis->lRange('NETFILTER_LOG', $from - 1, $to - 1);
|
||||
$data = $valkey->lRange('NETFILTER_LOG', $from - 1, $to - 1);
|
||||
}
|
||||
else {
|
||||
$data = $redis->lRange('NETFILTER_LOG', 0, $lines);
|
||||
$data = $valkey->lRange('NETFILTER_LOG', 0, $lines);
|
||||
}
|
||||
if ($data) {
|
||||
foreach ($data as $json_line) {
|
||||
@@ -3541,10 +3551,10 @@ function get_logs($application, $lines = false) {
|
||||
}
|
||||
if ($application == "autodiscover-mailcow") {
|
||||
if (isset($from) && isset($to)) {
|
||||
$data = $redis->lRange('AUTODISCOVER_LOG', $from - 1, $to - 1);
|
||||
$data = $valkey->lRange('AUTODISCOVER_LOG', $from - 1, $to - 1);
|
||||
}
|
||||
else {
|
||||
$data = $redis->lRange('AUTODISCOVER_LOG', 0, $lines);
|
||||
$data = $valkey->lRange('AUTODISCOVER_LOG', 0, $lines);
|
||||
}
|
||||
if ($data) {
|
||||
foreach ($data as $json_line) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
global $pdo;
|
||||
global $redis;
|
||||
global $valkey;
|
||||
global $lang;
|
||||
global $MAILBOX_DEFAULT_ATTRIBUTES;
|
||||
global $iam_settings;
|
||||
@@ -628,13 +628,13 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
}
|
||||
|
||||
try {
|
||||
$redis->hSet('DOMAIN_MAP', $domain, 1);
|
||||
$valkey->hSet('DOMAIN_MAP', $domain, 1);
|
||||
}
|
||||
catch (RedisException $e) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||
'msg' => array('redis_error', $e)
|
||||
'msg' => array('valkey_error', $e)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
@@ -646,7 +646,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
$_data['key_size'] = (isset($_data['key_size'])) ? intval($_data['key_size']) : $DOMAIN_DEFAULT_ATTRIBUTES['key_size'];
|
||||
$_data['dkim_selector'] = (isset($_data['dkim_selector'])) ? $_data['dkim_selector'] : $DOMAIN_DEFAULT_ATTRIBUTES['dkim_selector'];
|
||||
if (!empty($_data['key_size']) && !empty($_data['dkim_selector'])) {
|
||||
if (!empty($redis->hGet('DKIM_SELECTORS', $domain))) {
|
||||
if (!empty($valkey->hGet('DKIM_SELECTORS', $domain))) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'success',
|
||||
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||
@@ -684,15 +684,16 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
return true;
|
||||
break;
|
||||
case 'alias':
|
||||
$addresses = array_map('trim', preg_split( "/( |,|;|\n)/", $_data['address']));
|
||||
$gotos = array_map('trim', preg_split( "/( |,|;|\n)/", $_data['goto']));
|
||||
$active = intval($_data['active']);
|
||||
$sogo_visible = intval($_data['sogo_visible']);
|
||||
$goto_null = intval($_data['goto_null']);
|
||||
$goto_spam = intval($_data['goto_spam']);
|
||||
$goto_ham = intval($_data['goto_ham']);
|
||||
$addresses = array_map('trim', preg_split( "/( |,|;|\n)/", $_data['address']));
|
||||
$gotos = array_map('trim', preg_split( "/( |,|;|\n)/", $_data['goto']));
|
||||
$internal = intval($_data['internal']);
|
||||
$active = intval($_data['active']);
|
||||
$sogo_visible = intval($_data['sogo_visible']);
|
||||
$goto_null = intval($_data['goto_null']);
|
||||
$goto_spam = intval($_data['goto_spam']);
|
||||
$goto_ham = intval($_data['goto_ham']);
|
||||
$private_comment = $_data['private_comment'];
|
||||
$public_comment = $_data['public_comment'];
|
||||
$public_comment = $_data['public_comment'];
|
||||
if (strlen($private_comment) > 160 | strlen($public_comment) > 160){
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
@@ -842,8 +843,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
);
|
||||
continue;
|
||||
}
|
||||
$stmt = $pdo->prepare("INSERT INTO `alias` (`address`, `public_comment`, `private_comment`, `goto`, `domain`, `sogo_visible`, `active`)
|
||||
VALUES (:address, :public_comment, :private_comment, :goto, :domain, :sogo_visible, :active)");
|
||||
$stmt = $pdo->prepare("INSERT INTO `alias` (`address`, `public_comment`, `private_comment`, `goto`, `domain`, `sogo_visible`, `internal`, `active`)
|
||||
VALUES (:address, :public_comment, :private_comment, :goto, :domain, :sogo_visible, :internal, :active)");
|
||||
if (!filter_var($address, FILTER_VALIDATE_EMAIL) === true) {
|
||||
$stmt->execute(array(
|
||||
':address' => '@'.$domain,
|
||||
@@ -853,6 +854,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
':goto' => $goto,
|
||||
':domain' => $domain,
|
||||
':sogo_visible' => $sogo_visible,
|
||||
':internal' => $internal,
|
||||
':active' => $active
|
||||
));
|
||||
}
|
||||
@@ -864,6 +866,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
':goto' => $goto,
|
||||
':domain' => $domain,
|
||||
':sogo_visible' => $sogo_visible,
|
||||
':internal' => $internal,
|
||||
':active' => $active
|
||||
));
|
||||
}
|
||||
@@ -971,13 +974,13 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
':active' => $active
|
||||
));
|
||||
try {
|
||||
$redis->hSet('DOMAIN_MAP', $alias_domain, 1);
|
||||
$valkey->hSet('DOMAIN_MAP', $alias_domain, 1);
|
||||
}
|
||||
catch (RedisException $e) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||
'msg' => array('redis_error', $e)
|
||||
'msg' => array('valkey_error', $e)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
@@ -985,7 +988,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
ratelimit('edit', 'domain', array('rl_value' => $_data['rl_value'], 'rl_frame' => $_data['rl_frame'], 'object' => $alias_domain));
|
||||
}
|
||||
if (!empty($_data['key_size']) && !empty($_data['dkim_selector'])) {
|
||||
if (!empty($redis->hGet('DKIM_SELECTORS', $alias_domain))) {
|
||||
if (!empty($valkey->hGet('DKIM_SELECTORS', $alias_domain))) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'success',
|
||||
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||
@@ -1223,6 +1226,14 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
$stmt->execute(array(
|
||||
':username' => $username
|
||||
));
|
||||
// save delimiter_action
|
||||
if (isset($_data['tagged_mail_handler'])) {
|
||||
mailbox('edit', 'delimiter_action', array(
|
||||
'username' => $username,
|
||||
'tagged_mail_handler' => $_data['tagged_mail_handler']
|
||||
));
|
||||
}
|
||||
|
||||
// save tags
|
||||
foreach($tags as $index => $tag){
|
||||
if (empty($tag)) continue;
|
||||
@@ -1392,6 +1403,80 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
|
||||
return mailbox('add', 'mailbox', $mailbox_attributes);
|
||||
break;
|
||||
case 'mta_sts':
|
||||
$domain = idn_to_ascii(strtolower(trim($_data['domain'])), 0, INTL_IDNA_VARIANT_UTS46);
|
||||
$version = strtolower($_data['version']);
|
||||
$mode = strtolower($_data['mode']);
|
||||
$mx = explode(",", preg_replace('/\s+/', '', $_data['mx']));
|
||||
$max_age = intval($_data['max_age']);
|
||||
$active = (intval($_data['active']) == 1) ? 1 : 0;
|
||||
$id = date('YmdHis');
|
||||
|
||||
if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_type, $_data, $_attr),
|
||||
'msg' => 'access_denied'
|
||||
);
|
||||
return false;
|
||||
}
|
||||
if (empty($version) || !in_array($version, array('stsv1'))) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_type, $_data, $_attr),
|
||||
'msg' => array('version_invalid', htmlspecialchars($domain))
|
||||
);
|
||||
return false;
|
||||
}
|
||||
if (empty($mode) || !in_array($mode, array('enforce', 'testing', 'none'))) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_type, $_data, $_attr),
|
||||
'msg' => array('mode_invalid', htmlspecialchars($domain))
|
||||
);
|
||||
return false;
|
||||
}
|
||||
if (empty($max_age) || $max_age < 0 || $max_age > 31536000) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_type, $_data, $_attr),
|
||||
'msg' => array('max_age_invalid', htmlspecialchars($domain))
|
||||
);
|
||||
return false;
|
||||
}
|
||||
foreach ($mx as $index => $mx_domain) {
|
||||
$mx_domain = idn_to_ascii(strtolower(trim($mx_domain)), 0, INTL_IDNA_VARIANT_UTS46);
|
||||
if (!is_valid_domain_name($mx_domain, array('allow_wildcard' => true))) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_type, $_data, $_attr),
|
||||
'msg' => array('mx_invalid', htmlspecialchars($mx_domain))
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$stmt = $pdo->prepare("INSERT INTO `mta_sts` (`id`, `domain`, `version`, `mode`, `mx`, `max_age`, `active`)
|
||||
VALUES (:id, :domain, :version, :mode, :mx, :max_age, :active)");
|
||||
$stmt->execute(array(
|
||||
':id' => $id,
|
||||
':domain' => $domain,
|
||||
':version' => $version,
|
||||
':mode' => $mode,
|
||||
':mx' => implode(",", $mx),
|
||||
':max_age' => $max_age,
|
||||
':active' => $active
|
||||
));
|
||||
} catch (PDOException $e) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_type, $_data),
|
||||
'msg' => $e->getMessage()
|
||||
);
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case 'resource':
|
||||
$domain = idn_to_ascii(strtolower(trim($_data['domain'])), 0, INTL_IDNA_VARIANT_UTS46);
|
||||
$description = $_data['description'];
|
||||
@@ -1613,6 +1698,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
$attr = array();
|
||||
$attr["quota"] = isset($_data['quota']) ? intval($_data['quota']) * 1048576 : 0;
|
||||
$attr['tags'] = (isset($_data['tags'])) ? $_data['tags'] : array();
|
||||
$attr["tagged_mail_handler"] = (!empty($_data['tagged_mail_handler'])) ? $_data['tagged_mail_handler'] : strval($MAILBOX_DEFAULT_ATTRIBUTES['tagged_mail_handler']);
|
||||
$attr["quarantine_notification"] = (!empty($_data['quarantine_notification'])) ? $_data['quarantine_notification'] : strval($MAILBOX_DEFAULT_ATTRIBUTES['quarantine_notification']);
|
||||
$attr["quarantine_category"] = (!empty($_data['quarantine_category'])) ? $_data['quarantine_category'] : strval($MAILBOX_DEFAULT_ATTRIBUTES['quarantine_category']);
|
||||
$attr["rl_frame"] = (!empty($_data['rl_frame'])) ? $_data['rl_frame'] : "s";
|
||||
@@ -2061,42 +2147,42 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
}
|
||||
if (isset($_data['tagged_mail_handler']) && $_data['tagged_mail_handler'] == "subject") {
|
||||
try {
|
||||
$redis->hSet('RCPT_WANTS_SUBJECT_TAG', $username, 1);
|
||||
$redis->hDel('RCPT_WANTS_SUBFOLDER_TAG', $username);
|
||||
$valkey->hSet('RCPT_WANTS_SUBJECT_TAG', $username, 1);
|
||||
$valkey->hDel('RCPT_WANTS_SUBFOLDER_TAG', $username);
|
||||
}
|
||||
catch (RedisException $e) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||
'msg' => array('redis_error', $e)
|
||||
'msg' => array('valkey_error', $e)
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (isset($_data['tagged_mail_handler']) && $_data['tagged_mail_handler'] == "subfolder") {
|
||||
try {
|
||||
$redis->hSet('RCPT_WANTS_SUBFOLDER_TAG', $username, 1);
|
||||
$redis->hDel('RCPT_WANTS_SUBJECT_TAG', $username);
|
||||
$valkey->hSet('RCPT_WANTS_SUBFOLDER_TAG', $username, 1);
|
||||
$valkey->hDel('RCPT_WANTS_SUBJECT_TAG', $username);
|
||||
}
|
||||
catch (RedisException $e) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||
'msg' => array('redis_error', $e)
|
||||
'msg' => array('valkey_error', $e)
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else {
|
||||
try {
|
||||
$redis->hDel('RCPT_WANTS_SUBJECT_TAG', $username);
|
||||
$redis->hDel('RCPT_WANTS_SUBFOLDER_TAG', $username);
|
||||
$valkey->hDel('RCPT_WANTS_SUBJECT_TAG', $username);
|
||||
$valkey->hDel('RCPT_WANTS_SUBFOLDER_TAG', $username);
|
||||
}
|
||||
catch (RedisException $e) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||
'msg' => array('redis_error', $e)
|
||||
'msg' => array('valkey_error', $e)
|
||||
);
|
||||
continue;
|
||||
}
|
||||
@@ -2398,6 +2484,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
foreach ($ids as $id) {
|
||||
$is_now = mailbox('get', 'alias_details', $id);
|
||||
if (!empty($is_now)) {
|
||||
$internal = (isset($_data['internal'])) ? intval($_data['internal']) : $is_now['internal'];
|
||||
$active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active'];
|
||||
$sogo_visible = (isset($_data['sogo_visible'])) ? intval($_data['sogo_visible']) : $is_now['sogo_visible'];
|
||||
$goto_null = (isset($_data['goto_null'])) ? intval($_data['goto_null']) : 0;
|
||||
@@ -2583,6 +2670,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
`domain` = :domain,
|
||||
`goto` = :goto,
|
||||
`sogo_visible`= :sogo_visible,
|
||||
`internal`= :internal,
|
||||
`active`= :active
|
||||
WHERE `id` = :id");
|
||||
$stmt->execute(array(
|
||||
@@ -2592,6 +2680,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
':domain' => $domain,
|
||||
':goto' => $goto,
|
||||
':sogo_visible' => $sogo_visible,
|
||||
':internal' => $internal,
|
||||
':active' => $active,
|
||||
':id' => $is_now['id']
|
||||
));
|
||||
@@ -3259,6 +3348,13 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
);
|
||||
return false;
|
||||
}
|
||||
// save delimiter_action
|
||||
if (isset($_data['tagged_mail_handler'])) {
|
||||
mailbox('edit', 'delimiter_action', array(
|
||||
'username' => $username,
|
||||
'tagged_mail_handler' => $_data['tagged_mail_handler']
|
||||
));
|
||||
}
|
||||
// save tags
|
||||
foreach($tags as $index => $tag){
|
||||
if (empty($tag)) continue;
|
||||
@@ -3604,6 +3700,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
$attr = array();
|
||||
$attr["quota"] = isset($_data['quota']) ? intval($_data['quota']) * 1048576 : 0;
|
||||
$attr['tags'] = (isset($_data['tags'])) ? $_data['tags'] : $is_now['tags'];
|
||||
$attr["tagged_mail_handler"] = (!empty($_data['tagged_mail_handler'])) ? $_data['tagged_mail_handler'] : $is_now['tagged_mail_handler'];
|
||||
$attr["quarantine_notification"] = (!empty($_data['quarantine_notification'])) ? $_data['quarantine_notification'] : $is_now['quarantine_notification'];
|
||||
$attr["quarantine_category"] = (!empty($_data['quarantine_category'])) ? $_data['quarantine_category'] : $is_now['quarantine_category'];
|
||||
$attr["rl_frame"] = (!empty($_data['rl_frame'])) ? $_data['rl_frame'] : $is_now['rl_frame'];
|
||||
@@ -3724,6 +3821,125 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
|
||||
return true;
|
||||
break;
|
||||
case 'mta_sts':
|
||||
if (!is_array($_data['domains'])) {
|
||||
$domains = array();
|
||||
$domains[] = $_data['domains'];
|
||||
}
|
||||
else {
|
||||
$domains = $_data['domains'];
|
||||
}
|
||||
|
||||
foreach ($domains as $domain) {
|
||||
$domain = idn_to_ascii(strtolower(trim($domain)), 0, INTL_IDNA_VARIANT_UTS46);
|
||||
|
||||
if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_type, $_data, $_attr),
|
||||
'msg' => 'access_denied'
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
$is_now = mailbox('get', 'mta_sts', $domain);
|
||||
if (!empty($is_now)) {
|
||||
$version = (isset($_data['version'])) ? strtolower($_data['version']) : $is_now['version'];
|
||||
$active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active'];
|
||||
$active = ($active == 1) ? 1 : 0;
|
||||
$mode = (isset($_data['mode'])) ? strtolower($_data['mode']) : $is_now['mode'];
|
||||
$mx = (isset($_data['mx'])) ? explode(",", preg_replace('/\s+/', '', $_data['mx'])) : $is_now['mx'];
|
||||
$max_age = (isset($_data['max_age'])) ? intval($_data['max_age']) : $is_now['max_age'];
|
||||
|
||||
// Update ID if neccesary
|
||||
if ($version != strtolower($is_now['version']) ||
|
||||
$mode != strtolower($is_now['mode']) ||
|
||||
$mx != $is_now['mx'] ||
|
||||
$max_age != $is_now['max_age']) {
|
||||
$id = date('YmdHis');
|
||||
} else {
|
||||
$id = $is_now['id'];
|
||||
}
|
||||
|
||||
} else {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||
'msg' => 'access_denied'
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (empty($version) || !in_array($version, array('stsv1'))) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_type, $_data, $_attr),
|
||||
'msg' => array('version_invalid', htmlspecialchars($version))
|
||||
);
|
||||
continue;
|
||||
}
|
||||
if (empty($mode) || !in_array($mode, array('enforce', 'testing', 'none'))) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_type, $_data, $_attr),
|
||||
'msg' => array('mode_invalid', htmlspecialchars($domain))
|
||||
);
|
||||
continue;
|
||||
}
|
||||
if (empty($max_age) || $max_age < 0 || $max_age > 31557600) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_type, $_data, $_attr),
|
||||
'msg' => array('max_age_invalid', htmlspecialchars($domain))
|
||||
);
|
||||
continue;
|
||||
}
|
||||
foreach ($mx as $index => $mx_domain) {
|
||||
$mx_domain = idn_to_ascii(strtolower(trim($mx_domain)), 0, INTL_IDNA_VARIANT_UTS46);
|
||||
$invalid_mx = false;
|
||||
if (!is_valid_domain_name($mx_domain, array('allow_wildcard' => true))) {
|
||||
$invalid_mx = $mx_domain;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($invalid_mx) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_type, $_data, $_attr),
|
||||
'msg' => array('mx_invalid', htmlspecialchars($invalid_mx))
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
$stmt = $pdo->prepare("UPDATE `mta_sts` SET `id` = :id, `version` = :version, `mode` = :mode, `mx` = :mx, `max_age` = :max_age, `active` = :active WHERE `domain` = :domain");
|
||||
$stmt->execute(array(
|
||||
':id' => $id,
|
||||
':domain' => $domain,
|
||||
':version' => $version,
|
||||
':mode' => $mode,
|
||||
':mx' => implode(",", $mx),
|
||||
':max_age' => $max_age,
|
||||
':active' => $active
|
||||
));
|
||||
} catch (PDOException $e) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_type, $_data),
|
||||
'msg' => $e->getMessage()
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'success',
|
||||
'log' => array(__FUNCTION__, $_action, $_type, $_data, $_attr),
|
||||
'msg' => array('object_modified', $domain)
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
break;
|
||||
case 'resource':
|
||||
if (!is_array($_data['name'])) {
|
||||
$names = array();
|
||||
@@ -4387,10 +4603,10 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
$_data = $_SESSION['mailcow_cc_username'];
|
||||
}
|
||||
try {
|
||||
if ($redis->hGet('RCPT_WANTS_SUBJECT_TAG', $_data)) {
|
||||
if ($valkey->hGet('RCPT_WANTS_SUBJECT_TAG', $_data)) {
|
||||
return "subject";
|
||||
}
|
||||
elseif ($redis->hGet('RCPT_WANTS_SUBFOLDER_TAG', $_data)) {
|
||||
elseif ($valkey->hGet('RCPT_WANTS_SUBFOLDER_TAG', $_data)) {
|
||||
return "subfolder";
|
||||
}
|
||||
else {
|
||||
@@ -4401,7 +4617,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||
'msg' => array('redis_error', $e)
|
||||
'msg' => array('valkey_error', $e)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
@@ -4490,6 +4706,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
`address`,
|
||||
`public_comment`,
|
||||
`private_comment`,
|
||||
`internal`,
|
||||
`active`,
|
||||
`sogo_visible`,
|
||||
`created`,
|
||||
@@ -4520,6 +4737,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
$aliasdata['goto'] = $row['goto'];
|
||||
$aliasdata['address'] = $row['address'];
|
||||
(!filter_var($aliasdata['address'], FILTER_VALIDATE_EMAIL)) ? $aliasdata['is_catch_all'] = 1 : $aliasdata['is_catch_all'] = 0;
|
||||
$aliasdata['internal'] = $row['internal'];
|
||||
$aliasdata['active'] = $row['active'];
|
||||
$aliasdata['active_int'] = $row['active'];
|
||||
$aliasdata['sogo_visible'] = $row['sogo_visible'];
|
||||
@@ -5012,6 +5230,20 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
return $rows;
|
||||
}
|
||||
break;
|
||||
case 'mta_sts':
|
||||
$stmt = $pdo->prepare("SELECT * FROM `mta_sts` WHERE `domain` = :domain");
|
||||
$stmt->execute(array(
|
||||
':domain' => $_data,
|
||||
));
|
||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
if (empty($row)){
|
||||
return [];
|
||||
}
|
||||
$row['mx'] = explode(',', $row['mx']);
|
||||
$row['version'] = strtoupper(substr($row['version'], 0, 3)) . substr($row['version'], 3);
|
||||
|
||||
return $row;
|
||||
break;
|
||||
case 'resource_details':
|
||||
$resourcedata = array();
|
||||
if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) {
|
||||
@@ -5397,17 +5629,21 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
$stmt->execute(array(
|
||||
':domain' => $domain,
|
||||
));
|
||||
$stmt = $pdo->prepare("DELETE FROM `mta_sts` WHERE `domain` = :domain");
|
||||
$stmt->execute(array(
|
||||
':domain' => $domain,
|
||||
));
|
||||
$stmt = $pdo->query("DELETE FROM `admin` WHERE `superadmin` = 0 AND `username` NOT IN (SELECT `username`FROM `domain_admins`);");
|
||||
$stmt = $pdo->query("DELETE FROM `da_acl` WHERE `username` NOT IN (SELECT `username`FROM `domain_admins`);");
|
||||
try {
|
||||
$redis->hDel('DOMAIN_MAP', $domain);
|
||||
$redis->hDel('RL_VALUE', $domain);
|
||||
$valkey->hDel('DOMAIN_MAP', $domain);
|
||||
$valkey->hDel('RL_VALUE', $domain);
|
||||
}
|
||||
catch (RedisException $e) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||
'msg' => array('redis_error', $e)
|
||||
'msg' => array('valkey_error', $e)
|
||||
);
|
||||
continue;
|
||||
}
|
||||
@@ -5533,14 +5769,14 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
':alias_domain' => $alias_domain,
|
||||
));
|
||||
try {
|
||||
$redis->hDel('DOMAIN_MAP', $alias_domain);
|
||||
$redis->hDel('RL_VALUE', $domain);
|
||||
$valkey->hDel('DOMAIN_MAP', $alias_domain);
|
||||
$valkey->hDel('RL_VALUE', $domain);
|
||||
}
|
||||
catch (RedisException $e) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||
'msg' => array('redis_error', $e)
|
||||
'msg' => array('valkey_error', $e)
|
||||
);
|
||||
continue;
|
||||
}
|
||||
@@ -5719,13 +5955,13 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
));
|
||||
}
|
||||
try {
|
||||
$redis->hDel('RL_VALUE', $username);
|
||||
$valkey->hDel('RL_VALUE', $username);
|
||||
}
|
||||
catch (RedisException $e) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||
'msg' => array('redis_error', $e)
|
||||
'msg' => array('valkey_error', $e)
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
function oauth2($_action, $_type, $_data = null) {
|
||||
global $pdo;
|
||||
global $redis;
|
||||
global $valkey;
|
||||
global $lang;
|
||||
if ($_SESSION['mailcow_cc_role'] != "admin") {
|
||||
$_SESSION['return'][] = array(
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
function policy($_action, $_scope, $_data = null) {
|
||||
global $pdo;
|
||||
global $redis;
|
||||
global $valkey;
|
||||
global $lang;
|
||||
$_data_log = $_data;
|
||||
switch ($_action) {
|
||||
|
||||
@@ -4,7 +4,7 @@ use PHPMailer\PHPMailer\SMTP;
|
||||
use PHPMailer\PHPMailer\Exception;
|
||||
function quarantine($_action, $_data = null) {
|
||||
global $pdo;
|
||||
global $redis;
|
||||
global $valkey;
|
||||
global $lang;
|
||||
$_data_log = $_data;
|
||||
switch ($_action) {
|
||||
@@ -22,7 +22,7 @@ function quarantine($_action, $_data = null) {
|
||||
return false;
|
||||
}
|
||||
$stmt = $pdo->prepare('SELECT `id` FROM `quarantine` LEFT OUTER JOIN `user_acl` ON `user_acl`.`username` = `rcpt`
|
||||
WHERE SHA2(CONCAT(`id`, `qid`), 256) = :hash
|
||||
WHERE `qhash` = :hash
|
||||
AND user_acl.quarantine = 1
|
||||
AND rcpt IN (SELECT username FROM mailbox)');
|
||||
$stmt->execute(array(':hash' => $hash));
|
||||
@@ -65,7 +65,7 @@ function quarantine($_action, $_data = null) {
|
||||
return false;
|
||||
}
|
||||
$stmt = $pdo->prepare('SELECT `id` FROM `quarantine` LEFT OUTER JOIN `user_acl` ON `user_acl`.`username` = `rcpt`
|
||||
WHERE SHA2(CONCAT(`id`, `qid`), 256) = :hash
|
||||
WHERE `qhash` = :hash
|
||||
AND `user_acl`.`quarantine` = 1
|
||||
AND `username` IN (SELECT `username` FROM `mailbox`)');
|
||||
$stmt->execute(array(':hash' => $hash));
|
||||
@@ -102,14 +102,14 @@ function quarantine($_action, $_data = null) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
$release_format = $redis->Get('Q_RELEASE_FORMAT');
|
||||
$release_format = $valkey->Get('Q_RELEASE_FORMAT');
|
||||
}
|
||||
catch (RedisException $e) {
|
||||
logger(array('return' => array(
|
||||
array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_data_log),
|
||||
'msg' => array('redis_error', $e)
|
||||
'msg' => array('valkey_error', $e)
|
||||
)
|
||||
)));
|
||||
return false;
|
||||
@@ -180,7 +180,7 @@ function quarantine($_action, $_data = null) {
|
||||
array('221', '')
|
||||
);
|
||||
// Thanks to https://stackoverflow.com/questions/6632399/given-an-email-as-raw-text-how-can-i-send-it-using-php
|
||||
$smtp_connection = fsockopen($postfix, 590, $errno, $errstr, 1);
|
||||
$smtp_connection = fsockopen($postfix, 590, $errno, $errstr, 1);
|
||||
if (!$smtp_connection) {
|
||||
logger(array('return' => array(
|
||||
array(
|
||||
@@ -192,7 +192,7 @@ function quarantine($_action, $_data = null) {
|
||||
return false;
|
||||
}
|
||||
for ($i=0; $i < count($postfix_talk); $i++) {
|
||||
$smtp_resource = fgets($smtp_connection, 256);
|
||||
$smtp_resource = fgets($smtp_connection, 256);
|
||||
if (substr($smtp_resource, 0, 3) !== $postfix_talk[$i][0]) {
|
||||
$ret = substr($smtp_resource, 0, 3);
|
||||
$ret = (empty($ret)) ? '-' : $ret;
|
||||
@@ -332,23 +332,23 @@ function quarantine($_action, $_data = null) {
|
||||
}
|
||||
$exclude_domains = (array)$_data['exclude_domains'];
|
||||
try {
|
||||
$redis->Set('Q_RETENTION_SIZE', intval($retention_size));
|
||||
$redis->Set('Q_MAX_SIZE', intval($max_size));
|
||||
$redis->Set('Q_MAX_SCORE', $max_score);
|
||||
$redis->Set('Q_MAX_AGE', $max_age);
|
||||
$redis->Set('Q_EXCLUDE_DOMAINS', json_encode($exclude_domains));
|
||||
$redis->Set('Q_RELEASE_FORMAT', $release_format);
|
||||
$redis->Set('Q_SENDER', $sender);
|
||||
$redis->Set('Q_BCC', $bcc);
|
||||
$redis->Set('Q_REDIRECT', $redirect);
|
||||
$redis->Set('Q_SUBJ', $subject);
|
||||
$redis->Set('Q_HTML', $html);
|
||||
$valkey->Set('Q_RETENTION_SIZE', intval($retention_size));
|
||||
$valkey->Set('Q_MAX_SIZE', intval($max_size));
|
||||
$valkey->Set('Q_MAX_SCORE', $max_score);
|
||||
$valkey->Set('Q_MAX_AGE', $max_age);
|
||||
$valkey->Set('Q_EXCLUDE_DOMAINS', json_encode($exclude_domains));
|
||||
$valkey->Set('Q_RELEASE_FORMAT', $release_format);
|
||||
$valkey->Set('Q_SENDER', $sender);
|
||||
$valkey->Set('Q_BCC', $bcc);
|
||||
$valkey->Set('Q_REDIRECT', $redirect);
|
||||
$valkey->Set('Q_SUBJ', $subject);
|
||||
$valkey->Set('Q_HTML', $html);
|
||||
}
|
||||
catch (RedisException $e) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_data_log),
|
||||
'msg' => array('redis_error', $e)
|
||||
'msg' => array('valkey_error', $e)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
@@ -403,13 +403,13 @@ function quarantine($_action, $_data = null) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
$release_format = $redis->Get('Q_RELEASE_FORMAT');
|
||||
$release_format = $valkey->Get('Q_RELEASE_FORMAT');
|
||||
}
|
||||
catch (RedisException $e) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_data_log),
|
||||
'msg' => array('redis_error', $e)
|
||||
'msg' => array('valkey_error', $e)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
@@ -475,7 +475,7 @@ function quarantine($_action, $_data = null) {
|
||||
array('221', '')
|
||||
);
|
||||
// Thanks to https://stackoverflow.com/questions/6632399/given-an-email-as-raw-text-how-can-i-send-it-using-php
|
||||
$smtp_connection = fsockopen($postfix, 590, $errno, $errstr, 1);
|
||||
$smtp_connection = fsockopen($postfix, 590, $errno, $errstr, 1);
|
||||
if (!$smtp_connection) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'warning',
|
||||
@@ -485,7 +485,7 @@ function quarantine($_action, $_data = null) {
|
||||
return false;
|
||||
}
|
||||
for ($i=0; $i < count($postfix_talk); $i++) {
|
||||
$smtp_resource = fgets($smtp_connection, 256);
|
||||
$smtp_resource = fgets($smtp_connection, 256);
|
||||
if (substr($smtp_resource, 0, 3) !== $postfix_talk[$i][0]) {
|
||||
$ret = substr($smtp_resource, 0, 3);
|
||||
$ret = (empty($ret)) ? '-' : $ret;
|
||||
@@ -776,18 +776,18 @@ function quarantine($_action, $_data = null) {
|
||||
case 'settings':
|
||||
try {
|
||||
if ($_SESSION['mailcow_cc_role'] == "admin") {
|
||||
$settings['exclude_domains'] = json_decode($redis->Get('Q_EXCLUDE_DOMAINS'), true);
|
||||
$settings['exclude_domains'] = json_decode($valkey->Get('Q_EXCLUDE_DOMAINS'), true);
|
||||
}
|
||||
$settings['max_size'] = $redis->Get('Q_MAX_SIZE');
|
||||
$settings['max_score'] = $redis->Get('Q_MAX_SCORE');
|
||||
$settings['max_age'] = $redis->Get('Q_MAX_AGE');
|
||||
$settings['retention_size'] = $redis->Get('Q_RETENTION_SIZE');
|
||||
$settings['release_format'] = $redis->Get('Q_RELEASE_FORMAT');
|
||||
$settings['subject'] = $redis->Get('Q_SUBJ');
|
||||
$settings['sender'] = $redis->Get('Q_SENDER');
|
||||
$settings['bcc'] = $redis->Get('Q_BCC');
|
||||
$settings['redirect'] = $redis->Get('Q_REDIRECT');
|
||||
$settings['html_tmpl'] = htmlspecialchars($redis->Get('Q_HTML'));
|
||||
$settings['max_size'] = $valkey->Get('Q_MAX_SIZE');
|
||||
$settings['max_score'] = $valkey->Get('Q_MAX_SCORE');
|
||||
$settings['max_age'] = $valkey->Get('Q_MAX_AGE');
|
||||
$settings['retention_size'] = $valkey->Get('Q_RETENTION_SIZE');
|
||||
$settings['release_format'] = $valkey->Get('Q_RELEASE_FORMAT');
|
||||
$settings['subject'] = $valkey->Get('Q_SUBJ');
|
||||
$settings['sender'] = $valkey->Get('Q_SENDER');
|
||||
$settings['bcc'] = $valkey->Get('Q_BCC');
|
||||
$settings['redirect'] = $valkey->Get('Q_REDIRECT');
|
||||
$settings['html_tmpl'] = htmlspecialchars($valkey->Get('Q_HTML'));
|
||||
if (empty($settings['html_tmpl'])) {
|
||||
$settings['html_tmpl'] = htmlspecialchars(file_get_contents("/tpls/quarantine.tpl"));
|
||||
}
|
||||
@@ -796,7 +796,7 @@ function quarantine($_action, $_data = null) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_data_log),
|
||||
'msg' => array('redis_error', $e)
|
||||
'msg' => array('valkey_error', $e)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
@@ -833,7 +833,7 @@ function quarantine($_action, $_data = null) {
|
||||
)));
|
||||
return false;
|
||||
}
|
||||
$stmt = $pdo->prepare('SELECT * FROM `quarantine` WHERE SHA2(CONCAT(`id`, `qid`), 256) = :hash');
|
||||
$stmt = $pdo->prepare('SELECT * FROM `quarantine` WHERE `qhash` = :hash');
|
||||
$stmt->execute(array(':hash' => $hash));
|
||||
return $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
break;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
function quota_notification($_action, $_data = null) {
|
||||
global $redis;
|
||||
global $valkey;
|
||||
$_data_log = $_data;
|
||||
if ($_SESSION['mailcow_cc_role'] != "admin") {
|
||||
$_SESSION['return'][] = array(
|
||||
@@ -26,15 +26,15 @@ function quota_notification($_action, $_data = null) {
|
||||
}
|
||||
$html = $_data['html_tmpl'];
|
||||
try {
|
||||
$redis->Set('QW_SENDER', $sender);
|
||||
$redis->Set('QW_SUBJ', $subject);
|
||||
$redis->Set('QW_HTML', $html);
|
||||
$valkey->Set('QW_SENDER', $sender);
|
||||
$valkey->Set('QW_SUBJ', $subject);
|
||||
$valkey->Set('QW_HTML', $html);
|
||||
}
|
||||
catch (RedisException $e) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_data_log),
|
||||
'msg' => array('redis_error', $e)
|
||||
'msg' => array('valkey_error', $e)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
@@ -46,9 +46,9 @@ function quota_notification($_action, $_data = null) {
|
||||
break;
|
||||
case 'get':
|
||||
try {
|
||||
$settings['subject'] = $redis->Get('QW_SUBJ');
|
||||
$settings['sender'] = $redis->Get('QW_SENDER');
|
||||
$settings['html_tmpl'] = htmlspecialchars($redis->Get('QW_HTML'));
|
||||
$settings['subject'] = $valkey->Get('QW_SUBJ');
|
||||
$settings['sender'] = $valkey->Get('QW_SENDER');
|
||||
$settings['html_tmpl'] = htmlspecialchars($valkey->Get('QW_HTML'));
|
||||
if (empty($settings['html_tmpl'])) {
|
||||
$settings['html_tmpl'] = htmlspecialchars(file_get_contents("/tpls/quota.tpl"));
|
||||
}
|
||||
@@ -57,7 +57,7 @@ function quota_notification($_action, $_data = null) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_data_log),
|
||||
'msg' => array('redis_error', $e)
|
||||
'msg' => array('valkey_error', $e)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
@@ -66,7 +66,7 @@ function quota_notification($_action, $_data = null) {
|
||||
}
|
||||
}
|
||||
function quota_notification_bcc($_action, $_data = null) {
|
||||
global $redis;
|
||||
global $valkey;
|
||||
$_data_log = $_data;
|
||||
if ($_SESSION['mailcow_cc_role'] != "admin" && $_SESSION['mailcow_cc_role'] != "domainadmin") {
|
||||
$_SESSION['return'][] = array(
|
||||
@@ -105,16 +105,16 @@ function quota_notification_bcc($_action, $_data = null) {
|
||||
$bcc_rcpts = array_filter($bcc_rcpts);
|
||||
if (empty($bcc_rcpts)) {
|
||||
$active = 0;
|
||||
|
||||
|
||||
}
|
||||
try {
|
||||
$redis->hSet('QW_BCC', $domain, json_encode(array('bcc_rcpts' => $bcc_rcpts, 'active' => $active)));
|
||||
$valkey->hSet('QW_BCC', $domain, json_encode(array('bcc_rcpts' => $bcc_rcpts, 'active' => $active)));
|
||||
}
|
||||
catch (RedisException $e) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_data_log),
|
||||
'msg' => array('redis_error', $e)
|
||||
'msg' => array('valkey_error', $e)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
@@ -135,13 +135,13 @@ function quota_notification_bcc($_action, $_data = null) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
return json_decode($redis->hGet('QW_BCC', $domain), true);
|
||||
return json_decode($valkey->hGet('QW_BCC', $domain), true);
|
||||
}
|
||||
catch (RedisException $e) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_data_log),
|
||||
'msg' => array('redis_error', $e)
|
||||
'msg' => array('valkey_error', $e)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
function ratelimit($_action, $_scope, $_data = null, $_extra = null) {
|
||||
global $redis;
|
||||
global $valkey;
|
||||
$_data_log = $_data;
|
||||
switch ($_action) {
|
||||
case 'edit':
|
||||
@@ -42,26 +42,26 @@ function ratelimit($_action, $_scope, $_data = null, $_extra = null) {
|
||||
}
|
||||
if (empty($rl_value)) {
|
||||
try {
|
||||
$redis->hDel('RL_VALUE', $object);
|
||||
$valkey->hDel('RL_VALUE', $object);
|
||||
}
|
||||
catch (RedisException $e) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_scope, $_data_log),
|
||||
'msg' => array('redis_error', $e)
|
||||
'msg' => array('valkey_error', $e)
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else {
|
||||
try {
|
||||
$redis->hSet('RL_VALUE', $object, $rl_value . ' / 1' . $rl_frame);
|
||||
$valkey->hSet('RL_VALUE', $object, $rl_value . ' / 1' . $rl_frame);
|
||||
}
|
||||
catch (RedisException $e) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_scope, $_data_log),
|
||||
'msg' => array('redis_error', $e)
|
||||
'msg' => array('valkey_error', $e)
|
||||
);
|
||||
continue;
|
||||
}
|
||||
@@ -103,26 +103,26 @@ function ratelimit($_action, $_scope, $_data = null, $_extra = null) {
|
||||
}
|
||||
if (empty($rl_value)) {
|
||||
try {
|
||||
$redis->hDel('RL_VALUE', $object);
|
||||
$valkey->hDel('RL_VALUE', $object);
|
||||
}
|
||||
catch (RedisException $e) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_scope, $_data_log),
|
||||
'msg' => array('redis_error', $e)
|
||||
'msg' => array('valkey_error', $e)
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else {
|
||||
try {
|
||||
$redis->hSet('RL_VALUE', $object, $rl_value . ' / 1' . $rl_frame);
|
||||
$valkey->hSet('RL_VALUE', $object, $rl_value . ' / 1' . $rl_frame);
|
||||
}
|
||||
catch (RedisException $e) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_scope, $_data_log),
|
||||
'msg' => array('redis_error', $e)
|
||||
'msg' => array('valkey_error', $e)
|
||||
);
|
||||
continue;
|
||||
}
|
||||
@@ -143,7 +143,7 @@ function ratelimit($_action, $_scope, $_data = null, $_extra = null) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
if ($rl_value = $redis->hGet('RL_VALUE', $_data)) {
|
||||
if ($rl_value = $valkey->hGet('RL_VALUE', $_data)) {
|
||||
$rl = explode(' / 1', $rl_value);
|
||||
$data['value'] = $rl[0];
|
||||
$data['frame'] = $rl[1];
|
||||
@@ -157,7 +157,7 @@ function ratelimit($_action, $_scope, $_data = null, $_extra = null) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_scope, $_data_log),
|
||||
'msg' => array('redis_error', $e)
|
||||
'msg' => array('valkey_error', $e)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
@@ -169,7 +169,7 @@ function ratelimit($_action, $_scope, $_data = null, $_extra = null) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
if ($rl_value = $redis->hGet('RL_VALUE', $_data)) {
|
||||
if ($rl_value = $valkey->hGet('RL_VALUE', $_data)) {
|
||||
$rl = explode(' / 1', $rl_value);
|
||||
$data['value'] = $rl[0];
|
||||
$data['frame'] = $rl[1];
|
||||
@@ -183,7 +183,7 @@ function ratelimit($_action, $_scope, $_data = null, $_extra = null) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_scope, $_data_log),
|
||||
'msg' => array('redis_error', $e)
|
||||
'msg' => array('valkey_error', $e)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
@@ -202,16 +202,16 @@ function ratelimit($_action, $_scope, $_data = null, $_extra = null) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
$data_rllog = $redis->lRange('RL_LOG', 0, -1);
|
||||
$data_rllog = $valkey->lRange('RL_LOG', 0, -1);
|
||||
if ($data_rllog) {
|
||||
foreach ($data_rllog as $json_line) {
|
||||
if (preg_match('/' . $data['hash'] . '/i', $json_line)) {
|
||||
$redis->lRem('RL_LOG', $json_line, 0);
|
||||
$valkey->lRem('RL_LOG', $json_line, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($redis->type($data['hash']) == Redis::REDIS_HASH) {
|
||||
$redis->delete($data['hash']);
|
||||
if ($valkey->type($data['hash']) == Redis::REDIS_HASH) {
|
||||
$valkey->delete($data['hash']);
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'success',
|
||||
'log' => array(__FUNCTION__, $_action, $_scope, $_data_log),
|
||||
@@ -232,7 +232,7 @@ function ratelimit($_action, $_scope, $_data = null, $_extra = null) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_scope, $_data_log),
|
||||
'msg' => array('redis_error', $e)
|
||||
'msg' => array('valkey_error', $e)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -62,7 +62,11 @@ if ($app_links_processed){
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Workaround to get text with <br> straight to twig.
|
||||
// Using "nl2br" doesn't work with Twig as it would escape everything by default.
|
||||
if (isset($UI_TEXTS["ui_footer"])) {
|
||||
$UI_TEXTS["ui_footer"] = nl2br($UI_TEXTS["ui_footer"]);
|
||||
}
|
||||
|
||||
$globalVariables = [
|
||||
'mailcow_hostname' => getenv('MAILCOW_HOSTNAME'),
|
||||
|
||||
@@ -4,7 +4,7 @@ function init_db_schema()
|
||||
try {
|
||||
global $pdo;
|
||||
|
||||
$db_version = "27012025_1555";
|
||||
$db_version = "07102025_1015";
|
||||
|
||||
$stmt = $pdo->query("SHOW TABLES LIKE 'versions'");
|
||||
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
|
||||
@@ -184,6 +184,7 @@ function init_db_schema()
|
||||
"private_comment" => "TEXT",
|
||||
"public_comment" => "TEXT",
|
||||
"sogo_visible" => "TINYINT(1) NOT NULL DEFAULT '1'",
|
||||
"internal" => "TINYINT(1) NOT NULL DEFAULT '0'",
|
||||
"active" => "TINYINT(1) NOT NULL DEFAULT '1'"
|
||||
),
|
||||
"keys" => array(
|
||||
@@ -345,10 +346,14 @@ function init_db_schema()
|
||||
"notified" => "TINYINT(1) NOT NULL DEFAULT '0'",
|
||||
"created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)",
|
||||
"user" => "VARCHAR(255) NOT NULL DEFAULT 'unknown'",
|
||||
"qhash" => "VARCHAR(64)",
|
||||
),
|
||||
"keys" => array(
|
||||
"primary" => array(
|
||||
"" => array("id")
|
||||
),
|
||||
"key" => array(
|
||||
"qhash" => array("qhash")
|
||||
)
|
||||
),
|
||||
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
|
||||
@@ -471,6 +476,23 @@ function init_db_schema()
|
||||
),
|
||||
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
|
||||
),
|
||||
"mta_sts" => array(
|
||||
"cols" => array(
|
||||
"id" => "BIGINT NOT NULL",
|
||||
"domain" => "VARCHAR(255) NOT NULL",
|
||||
"version" => "VARCHAR(255) NOT NULL",
|
||||
"mode" => "VARCHAR(255) NOT NULL",
|
||||
"mx" => "VARCHAR(255) NOT NULL",
|
||||
"max_age" => "VARCHAR(255) NOT NULL",
|
||||
"active" => "TINYINT(1) NOT NULL DEFAULT '1'"
|
||||
),
|
||||
"keys" => array(
|
||||
"primary" => array(
|
||||
"" => array("domain")
|
||||
)
|
||||
),
|
||||
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
|
||||
),
|
||||
"user_acl" => array(
|
||||
"cols" => array(
|
||||
"username" => "VARCHAR(255) NOT NULL",
|
||||
@@ -1315,6 +1337,14 @@ function init_db_schema()
|
||||
$pdo->query($create);
|
||||
}
|
||||
|
||||
// Clear old app_passwd log entries
|
||||
$pdo->exec("DELETE FROM logs
|
||||
WHERE role != 'unauthenticated'
|
||||
AND JSON_EXTRACT(`call`, '$[0]') = 'app_passwd'
|
||||
AND JSON_EXTRACT(`call`, '$[1]') = 'edit'
|
||||
AND (JSON_CONTAINS_PATH(`call`, 'one', '$[2].password')
|
||||
OR JSON_CONTAINS_PATH(`call`, 'one', '$[2].password2'));");
|
||||
|
||||
// Mitigate imapsync argument injection issue
|
||||
$pdo->query("UPDATE `imapsync` SET `custom_params` = ''
|
||||
WHERE `custom_params` LIKE '%pipemess%'
|
||||
@@ -1482,6 +1512,10 @@ function init_db_schema()
|
||||
'msg' => 'db_init_complete'
|
||||
);
|
||||
}
|
||||
|
||||
// fill quarantine.qhash
|
||||
$pdo->query("UPDATE `quarantine` SET `qhash` = SHA2(CONCAT(`id`, `qid`), 256) WHERE ISNULL(`qhash`)");
|
||||
|
||||
} catch (PDOException $e) {
|
||||
if (php_sapi_name() == "cli") {
|
||||
echo "DB initialization failed: " . print_r($e, true) . PHP_EOL;
|
||||
|
||||
@@ -57,22 +57,22 @@ $WebAuthn = new lbuchs\WebAuthn\WebAuthn('WebAuthn Library', $server_name, $form
|
||||
// only include root ca's when needed
|
||||
if (getenv('WEBAUTHN_ONLY_TRUSTED_VENDORS') == 'y') $WebAuthn->addRootCertificates($_SERVER['DOCUMENT_ROOT'] . '/inc/lib/WebAuthn/rootCertificates');
|
||||
|
||||
// Redis
|
||||
$redis = new Redis();
|
||||
// Valkey
|
||||
$valkey = new Redis();
|
||||
try {
|
||||
if (!empty(getenv('REDIS_SLAVEOF_IP'))) {
|
||||
$redis->connect(getenv('REDIS_SLAVEOF_IP'), getenv('REDIS_SLAVEOF_PORT'));
|
||||
if (!empty(getenv('VALKEY_SLAVEOF_IP'))) {
|
||||
$valkey->connect(getenv('VALKEY_SLAVEOF_IP'), getenv('VALKEY_SLAVEOF_PORT'));
|
||||
}
|
||||
else {
|
||||
$redis->connect('redis-mailcow', 6379);
|
||||
$valkey->connect('valkey-mailcow', 6379);
|
||||
}
|
||||
$redis->auth(getenv("REDISPASS"));
|
||||
$valkey->auth(getenv("VALKEYPASS"));
|
||||
}
|
||||
catch (Exception $e) {
|
||||
// Stop when redis is not available
|
||||
// Stop when valkey is not available
|
||||
http_response_code(500);
|
||||
?>
|
||||
<center style='font-family:sans-serif;'>Connection to Redis failed.<br /><br />The following error was reported:<br/><?=$e->getMessage();?></center>
|
||||
<center style='font-family:sans-serif;'>Connection to Valkey failed.<br /><br />The following error was reported:<br/><?=$e->getMessage();?></center>
|
||||
<?php
|
||||
exit;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
<?php
|
||||
// Start session
|
||||
if (session_status() !== PHP_SESSION_ACTIVE) {
|
||||
session_name($SESSION_NAME);
|
||||
ini_set("session.cookie_httponly", 1);
|
||||
ini_set("session.cookie_samesite", $SESSION_SAMESITE_POLICY);
|
||||
ini_set('session.gc_maxlifetime', $SESSION_LIFETIME);
|
||||
}
|
||||
|
||||
@@ -67,7 +69,7 @@ if (!empty($_SERVER['HTTP_X_API_KEY'])) {
|
||||
}
|
||||
}
|
||||
else {
|
||||
$redis->publish("F2B_CHANNEL", "mailcow UI: Invalid password for API_USER by " . $_SERVER['REMOTE_ADDR']);
|
||||
$valkey->publish("F2B_CHANNEL", "mailcow UI: Invalid password for API_USER by " . $_SERVER['REMOTE_ADDR']);
|
||||
error_log("mailcow UI: Invalid password for " . $user . " by " . $_SERVER['REMOTE_ADDR']);
|
||||
http_response_code(401);
|
||||
echo json_encode(array(
|
||||
@@ -79,7 +81,7 @@ if (!empty($_SERVER['HTTP_X_API_KEY'])) {
|
||||
}
|
||||
}
|
||||
else {
|
||||
$redis->publish("F2B_CHANNEL", "mailcow UI: Invalid password for API_USER by " . $_SERVER['REMOTE_ADDR']);
|
||||
$valkey->publish("F2B_CHANNEL", "mailcow UI: Invalid password for API_USER by " . $_SERVER['REMOTE_ADDR']);
|
||||
error_log("mailcow UI: Invalid password for " . $user . " by " . $_SERVER['REMOTE_ADDR']);
|
||||
http_response_code(401);
|
||||
echo json_encode(array(
|
||||
|
||||
@@ -80,7 +80,7 @@ if (isset($_POST["verify_tfa_login"])) {
|
||||
intval($user_details['attributes']['force_pw_update']) != 1 &&
|
||||
getenv('SKIP_SOGO') != "y" &&
|
||||
!$is_dual) {
|
||||
header("Location: /SOGo/so/{$_SESSION['mailcow_cc_username']}");
|
||||
header("Location: /SOGo/so/");
|
||||
die();
|
||||
} else {
|
||||
header("Location: /user");
|
||||
@@ -146,7 +146,7 @@ if (isset($_POST["login_user"]) && isset($_POST["pass_user"])) {
|
||||
intval($user_details['attributes']['force_pw_update']) != 1 &&
|
||||
getenv('SKIP_SOGO') != "y" &&
|
||||
!$is_dual) {
|
||||
header("Location: /SOGo/so/{$login_user}");
|
||||
header("Location: /SOGo/so/");
|
||||
die();
|
||||
} else {
|
||||
header("Location: /user");
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user