mirror of
https://github.com/mailcow/mailcow-dockerized.git
synced 2026-03-31 02:42:44 +00:00
Compare commits
3 Commits
master
...
feat/valke
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c0f06bfc52 | ||
|
|
0698159f07 | ||
|
|
c27000215e |
@@ -14,7 +14,7 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- name: Mark/Close Stale Issues and Pull Requests 🗑️
|
- name: Mark/Close Stale Issues and Pull Requests 🗑️
|
||||||
uses: actions/stale@v10.2.0
|
uses: actions/stale@v10.1.0
|
||||||
with:
|
with:
|
||||||
repo-token: ${{ secrets.STALE_ACTION_PAT }}
|
repo-token: ${{ secrets.STALE_ACTION_PAT }}
|
||||||
days-before-stale: 60
|
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"
|
- "watchdog-mailcow"
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v5
|
||||||
- name: Setup Docker
|
- name: Setup Docker
|
||||||
run: |
|
run: |
|
||||||
curl -sSL https://get.docker.com/ | CHANNEL=stable sudo sh
|
curl -sSL https://get.docker.com/ | CHANNEL=stable sudo sh
|
||||||
|
|||||||
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
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Run the Action
|
- name: Run the Action
|
||||||
uses: devops-infra/action-pull-request@v1.0.2
|
uses: devops-infra/action-pull-request@v0.6.1
|
||||||
with:
|
with:
|
||||||
github_token: ${{ secrets.PRTONIGHTLY_ACTION_PAT }}
|
github_token: ${{ secrets.PRTONIGHTLY_ACTION_PAT }}
|
||||||
title: Automatic PR to nightly from ${{ github.event.repository.updated_at}}
|
title: Automatic PR to nightly from ${{ github.event.repository.updated_at}}
|
||||||
|
|||||||
10
.github/workflows/rebuild_backup_image.yml
vendored
10
.github/workflows/rebuild_backup_image.yml
vendored
@@ -13,24 +13,24 @@ jobs:
|
|||||||
packages: write
|
packages: write
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v4
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v4
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
- name: Login to GHCR
|
- name: Login to GHCR
|
||||||
if: github.event_name != 'pull_request'
|
if: github.event_name != 'pull_request'
|
||||||
uses: docker/login-action@v4
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
uses: docker/build-push-action@v7
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
|
|||||||
@@ -15,14 +15,14 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Generate postscreen_access.cidr
|
- name: Generate postscreen_access.cidr
|
||||||
run: |
|
run: |
|
||||||
bash helper-scripts/update_postscreen_whitelist.sh
|
bash helper-scripts/update_postscreen_whitelist.sh
|
||||||
|
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
uses: peter-evans/create-pull-request@v8
|
uses: peter-evans/create-pull-request@v7
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.mailcow_action_Update_postscreen_access_cidr_pat }}
|
token: ${{ secrets.mailcow_action_Update_postscreen_access_cidr_pat }}
|
||||||
commit-message: update postscreen_access.cidr
|
commit-message: update postscreen_access.cidr
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -51,7 +51,6 @@ data/conf/sogo/cron.creds
|
|||||||
data/conf/sogo/custom-fulllogo.svg
|
data/conf/sogo/custom-fulllogo.svg
|
||||||
data/conf/sogo/custom-shortlogo.svg
|
data/conf/sogo/custom-shortlogo.svg
|
||||||
data/conf/sogo/custom-fulllogo.png
|
data/conf/sogo/custom-fulllogo.png
|
||||||
data/conf/acme/dns-01.conf
|
|
||||||
data/gitea/
|
data/gitea/
|
||||||
data/gogs/
|
data/gogs/
|
||||||
data/hooks/dovecot/*
|
data/hooks/dovecot/*
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
# Contribution Guidelines
|
# Contribution Guidelines
|
||||||
**_Last modified on 12th November 2025_**
|
**_Last modified on 15th August 2024_**
|
||||||
|
|
||||||
First of all, thank you for wanting to provide a bugfix or a new feature for the mailcow community, it's because of your help that the project can continue to grow!
|
First of all, thank you for wanting to provide a bugfix or a new feature for the mailcow community, it's because of your help that the project can continue to grow!
|
||||||
|
|
||||||
As we want to keep mailcow's development structured we setup these Guidelines which helps you to create your issue/pull request accordingly.
|
As we want to keep mailcow's development structured we setup these Guidelines which helps you to create your issue/pull request accordingly.
|
||||||
|
|
||||||
**PLEASE NOTE, THAT WE WILL CLOSE ISSUES/PULL REQUESTS IF THEY DON'T FULFILL OUR WRITTEN GUIDELINES WRITTEN INSIDE THIS DOCUMENT**. So please check this guidelines before you propose a Issue/Pull Request.
|
**PLEASE NOTE, THAT WE MIGHT CLOSE ISSUES/PULL REQUESTS IF THEY DON'T FULLFIL OUR WRITTEN GUIDELINES WRITTEN INSIDE THIS DOCUMENT**. So please check this guidelines before you propose a Issue/Pull Request.
|
||||||
|
|
||||||
## Topics
|
## Topics
|
||||||
|
|
||||||
@@ -27,18 +27,14 @@ However, please note the following regarding pull requests:
|
|||||||
6. Please **ALWAYS** create the actual pull request against the staging branch and **NEVER** directly against the master branch. *If you forget to do this, our moobot will remind you to switch the branch to staging.*
|
6. Please **ALWAYS** create the actual pull request against the staging branch and **NEVER** directly against the master branch. *If you forget to do this, our moobot will remind you to switch the branch to staging.*
|
||||||
7. Wait for a merge commit: It may happen that we do not accept your pull request immediately or sometimes not at all for various reasons. Please do not be disappointed if this is the case. We always endeavor to incorporate any meaningful changes from the community into the mailcow project.
|
7. Wait for a merge commit: It may happen that we do not accept your pull request immediately or sometimes not at all for various reasons. Please do not be disappointed if this is the case. We always endeavor to incorporate any meaningful changes from the community into the mailcow project.
|
||||||
8. If you are planning larger and therefore more complex pull requests, it would be advisable to first announce this in a separate issue and then start implementing it after the idea has been accepted in order to avoid unnecessary frustration and effort!
|
8. If you are planning larger and therefore more complex pull requests, it would be advisable to first announce this in a separate issue and then start implementing it after the idea has been accepted in order to avoid unnecessary frustration and effort!
|
||||||
9. If your PR requires a Docker image rebuild (changes to Dockerfiles or files in data/Dockerfiles/), update the image tag in docker-compose.yml. Use the base-image versioning (e.g. ghcr.io/mailcow/sogo:5.12.4 → :5.12.5 for version bumps; append a letter for patch fixes, e.g. :5.12.4a). Follow this scheme.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Issue Reporting
|
## Issue Reporting
|
||||||
**_Last modified on 12th November 2025_**
|
**_Last modified on 15th August 2024_**
|
||||||
|
|
||||||
If you plan to report a issue within mailcow please read and understand the following rules:
|
If you plan to report a issue within mailcow please read and understand the following rules:
|
||||||
|
|
||||||
### Security disclosures / Security-related fixes
|
|
||||||
- Security vulnerabilities and security fixes must always be reported confidentially first to the contact address specified in SECURITY.md before they are integrated, published, or publicly disclosed in issues/PRs. Please wait for a response from the specified contact to ensure coordinated and responsible disclosure.
|
|
||||||
|
|
||||||
### Issue Reporting Guidelines
|
### Issue Reporting Guidelines
|
||||||
|
|
||||||
1. **ONLY** use the issue tracker for bug reports or improvement requests and NOT for support questions. For support questions you can either contact the [mailcow community on Telegram](https://docs.mailcow.email/#community-support-and-chat) or the mailcow team directly in exchange for a [support fee](https://docs.mailcow.email/#commercial-support).
|
1. **ONLY** use the issue tracker for bug reports or improvement requests and NOT for support questions. For support questions you can either contact the [mailcow community on Telegram](https://docs.mailcow.email/#community-support-and-chat) or the mailcow team directly in exchange for a [support fee](https://docs.mailcow.email/#commercial-support).
|
||||||
|
|||||||
@@ -38,45 +38,45 @@ get_docker_version(){
|
|||||||
}
|
}
|
||||||
|
|
||||||
get_compose_type(){
|
get_compose_type(){
|
||||||
if docker compose > /dev/null 2>&1; then
|
if docker compose > /dev/null 2>&1; then
|
||||||
if docker compose version --short | grep -e "^[2-9]\." -e "^v[2-9]\." -e "^[1-9][0-9]\." -e "^v[1-9][0-9]\." > /dev/null 2>&1; then
|
if docker compose version --short | grep -e "^2." -e "^v2." > /dev/null 2>&1; then
|
||||||
COMPOSE_VERSION=native
|
COMPOSE_VERSION=native
|
||||||
COMPOSE_COMMAND="docker compose"
|
COMPOSE_COMMAND="docker compose"
|
||||||
if [[ "$caller" == "update.sh" ]]; then
|
if [[ "$caller" == "update.sh" ]]; then
|
||||||
sed -i 's/^DOCKER_COMPOSE_VERSION=.*/DOCKER_COMPOSE_VERSION=native/' "$SCRIPT_DIR/mailcow.conf"
|
sed -i 's/^DOCKER_COMPOSE_VERSION=.*/DOCKER_COMPOSE_VERSION=native/' "$SCRIPT_DIR/mailcow.conf"
|
||||||
fi
|
fi
|
||||||
echo -e "\e[33mFound Docker Compose Plugin (native).\e[0m"
|
echo -e "\e[33mFound Docker Compose Plugin (native).\e[0m"
|
||||||
echo -e "\e[33mSetting the DOCKER_COMPOSE_VERSION Variable to native\e[0m"
|
echo -e "\e[33mSetting the DOCKER_COMPOSE_VERSION Variable to native\e[0m"
|
||||||
sleep 2
|
sleep 2
|
||||||
echo -e "\e[33mNotice: You'll have to update this Compose Version via your Package Manager manually!\e[0m"
|
echo -e "\e[33mNotice: You'll have to update this Compose Version via your Package Manager manually!\e[0m"
|
||||||
else
|
else
|
||||||
echo -e "\e[31mCannot find Docker Compose with a Version Higher than 2.X.X.\e[0m"
|
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"
|
echo -e "\e[31mPlease update/install it manually regarding to this doc site: https://docs.mailcow.email/install/\e[0m"
|
||||||
exit 1
|
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
|
fi
|
||||||
elif docker-compose > /dev/null 2>&1; then
|
|
||||||
if ! [[ $(alias docker-compose 2> /dev/null) ]] ; then
|
|
||||||
if docker-compose version --short | grep -e "^[2-9]\." -e "^[1-9][0-9]\." > /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
|
else
|
||||||
echo -e "\e[31mCannot find Docker Compose with a Version Higher than 2.X.X.\e[0m"
|
echo -e "\e[31mCannot find Docker Compose.\e[0m"
|
||||||
echo -e "\e[31mPlease update/install manually regarding to this doc site: https://docs.mailcow.email/install/\e[0m"
|
echo -e "\e[31mPlease install it regarding to this doc site: https://docs.mailcow.email/install/\e[0m"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
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() {
|
detect_bad_asn() {
|
||||||
|
|||||||
@@ -57,14 +57,11 @@ adapt_new_options() {
|
|||||||
"DISABLE_NETFILTER_ISOLATION_RULE"
|
"DISABLE_NETFILTER_ISOLATION_RULE"
|
||||||
"HTTP_REDIRECT"
|
"HTTP_REDIRECT"
|
||||||
"ENABLE_IPV6"
|
"ENABLE_IPV6"
|
||||||
"ACME_DNS_CHALLENGE"
|
|
||||||
"ACME_DNS_PROVIDER"
|
|
||||||
"ACME_ACCOUNT_EMAIL"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
sed -i --follow-symlinks '$a\' mailcow.conf
|
sed -i --follow-symlinks '$a\' mailcow.conf
|
||||||
for option in ${CONFIG_ARRAY[@]}; do
|
for option in ${CONFIG_ARRAY[@]}; do
|
||||||
if grep -q "^#\?${option}=" mailcow.conf; then
|
if grep -q "${option}" mailcow.conf; then
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -295,20 +292,6 @@ adapt_new_options() {
|
|||||||
echo '# This key is used to encrypt email addresses within SOGo URLs' >> 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 "SOGO_URL_ENCRYPTION_KEY=$(LC_ALL=C </dev/urandom tr -dc A-Za-z0-9 2>/dev/null | head -c 16)" >> mailcow.conf
|
||||||
;;
|
;;
|
||||||
ACME_DNS_CHALLENGE)
|
|
||||||
echo '# Enable DNS-01 challenge for ACME (acme-mailcow) - y/n' >> mailcow.conf
|
|
||||||
echo '# This requires you to set ACME_DNS_PROVIDER and ACME_ACCOUNT_EMAIL below' >> mailcow.conf
|
|
||||||
echo 'ACME_DNS_CHALLENGE=n' >> mailcow.conf
|
|
||||||
;;
|
|
||||||
ACME_DNS_PROVIDER)
|
|
||||||
echo '# DNS provider for DNS-01 challenge (e.g. dns_cf, dns_azure, dns_gd, etc.)' >> mailcow.conf
|
|
||||||
echo '# See the dns-01 provider documentation for more information.' >> mailcow.conf
|
|
||||||
echo 'ACME_DNS_PROVIDER=dns_xxx' >> mailcow.conf
|
|
||||||
;;
|
|
||||||
ACME_ACCOUNT_EMAIL)
|
|
||||||
echo '# Account email for ACME DNS-01 challenge registration' >> mailcow.conf
|
|
||||||
echo 'ACME_ACCOUNT_EMAIL=me@example.com' >> mailcow.conf
|
|
||||||
;;
|
|
||||||
*)
|
*)
|
||||||
echo "${option}=" >> mailcow.conf
|
echo "${option}=" >> mailcow.conf
|
||||||
;;
|
;;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM alpine:3.23
|
FROM alpine:3.21
|
||||||
|
|
||||||
LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"
|
LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"
|
||||||
|
|
||||||
@@ -14,22 +14,11 @@ RUN apk upgrade --no-cache \
|
|||||||
tini \
|
tini \
|
||||||
tzdata \
|
tzdata \
|
||||||
python3 \
|
python3 \
|
||||||
acme-tiny \
|
acme-tiny
|
||||||
git \
|
|
||||||
socat \
|
|
||||||
&& git clone --depth 1 https://github.com/acmesh-official/acme.sh.git /opt/acme.sh \
|
|
||||||
&& chmod +x /opt/acme.sh/acme.sh \
|
|
||||||
&& mkdir -p /var/lib/acme/acme-sh
|
|
||||||
|
|
||||||
ENV ACME_SH_BIN=/opt/acme.sh/acme.sh \
|
|
||||||
ACME_SH_HOME=/opt/acme.sh \
|
|
||||||
ACME_SH_CONFIG_HOME=/var/lib/acme/acme-sh
|
|
||||||
|
|
||||||
COPY acme.sh /srv/acme.sh
|
COPY acme.sh /srv/acme.sh
|
||||||
COPY functions.sh /srv/functions.sh
|
COPY functions.sh /srv/functions.sh
|
||||||
COPY obtain-certificate.sh /srv/obtain-certificate.sh
|
COPY obtain-certificate.sh /srv/obtain-certificate.sh
|
||||||
COPY obtain-certificate-dns.sh /srv/obtain-certificate-dns.sh
|
|
||||||
COPY load-dns-config.sh /srv/load-dns-config.sh
|
|
||||||
COPY reload-configurations.sh /srv/reload-configurations.sh
|
COPY reload-configurations.sh /srv/reload-configurations.sh
|
||||||
COPY expand6.sh /srv/expand6.sh
|
COPY expand6.sh /srv/expand6.sh
|
||||||
|
|
||||||
|
|||||||
@@ -3,28 +3,17 @@ set -o pipefail
|
|||||||
exec 5>&1
|
exec 5>&1
|
||||||
|
|
||||||
# Do not attempt to write to slave
|
# Do not attempt to write to slave
|
||||||
if [[ ! -z ${REDIS_SLAVEOF_IP} ]]; then
|
if [[ ! -z ${VALKEY_SLAVEOF_IP} ]]; then
|
||||||
export REDIS_CMDLINE="redis-cli -h ${REDIS_SLAVEOF_IP} -p ${REDIS_SLAVEOF_PORT} -a ${REDISPASS} --no-auth-warning"
|
export VALKEY_CMDLINE="redis-cli -h ${VALKEY_SLAVEOF_IP} -p ${VALKEY_SLAVEOF_PORT} -a ${VALKEYPASS} --no-auth-warning"
|
||||||
else
|
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
|
fi
|
||||||
|
|
||||||
until [[ $(${REDIS_CMDLINE} PING) == "PONG" ]]; do
|
until [[ $(${VALKEY_CMDLINE} PING) == "PONG" ]]; do
|
||||||
echo "Waiting for Redis..."
|
echo "Waiting for Valkey..."
|
||||||
sleep 2
|
sleep 2
|
||||||
done
|
done
|
||||||
|
|
||||||
# Create DNS-01 configuration template if it doesn't exist
|
|
||||||
if [[ ! -f /etc/acme/dns-01.conf ]]; then
|
|
||||||
mkdir -p /etc/acme
|
|
||||||
cat > /etc/acme/dns-01.conf <<'EOF'
|
|
||||||
# Add here your DNS-01 challenge configuration
|
|
||||||
# For more information, visit the acme.sh documentation:
|
|
||||||
# https://github.com/acmesh-official/acme.sh/wiki/dnsapi
|
|
||||||
EOF
|
|
||||||
echo "Created DNS-01 configuration template at /etc/acme/dns-01.conf"
|
|
||||||
fi
|
|
||||||
|
|
||||||
source /srv/functions.sh
|
source /srv/functions.sh
|
||||||
# Thanks to https://github.com/cvmiller -> https://github.com/cvmiller/expand6
|
# Thanks to https://github.com/cvmiller -> https://github.com/cvmiller/expand6
|
||||||
source /srv/expand6.sh
|
source /srv/expand6.sh
|
||||||
@@ -53,10 +42,6 @@ if [[ "${ENABLE_SSL_SNI}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
|
|||||||
ENABLE_SSL_SNI=y
|
ENABLE_SSL_SNI=y
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "${ACME_DNS_CHALLENGE}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
|
|
||||||
ACME_DNS_CHALLENGE=y
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "${SKIP_LETS_ENCRYPT}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
|
if [[ "${SKIP_LETS_ENCRYPT}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
|
||||||
log_f "SKIP_LETS_ENCRYPT=y, skipping Let's Encrypt..."
|
log_f "SKIP_LETS_ENCRYPT=y, skipping Let's Encrypt..."
|
||||||
sleep 365d
|
sleep 365d
|
||||||
@@ -253,46 +238,14 @@ while true; do
|
|||||||
unset VALIDATED_CONFIG_DOMAINS_SUBDOMAINS
|
unset VALIDATED_CONFIG_DOMAINS_SUBDOMAINS
|
||||||
declare -a VALIDATED_CONFIG_DOMAINS_SUBDOMAINS
|
declare -a VALIDATED_CONFIG_DOMAINS_SUBDOMAINS
|
||||||
for SUBDOMAIN in "${ADDITIONAL_WC_ARR[@]}"; do
|
for SUBDOMAIN in "${ADDITIONAL_WC_ARR[@]}"; do
|
||||||
FULL_SUBDOMAIN="${SUBDOMAIN}.${SQL_DOMAIN}"
|
if [[ "${SUBDOMAIN}.${SQL_DOMAIN}" != "${MAILCOW_HOSTNAME}" ]]; then
|
||||||
|
if check_domain "${SUBDOMAIN}.${SQL_DOMAIN}"; then
|
||||||
# Skip if subdomain matches MAILCOW_HOSTNAME
|
VALIDATED_CONFIG_DOMAINS_SUBDOMAINS+=("${SUBDOMAIN}.${SQL_DOMAIN}")
|
||||||
if [[ "${FULL_SUBDOMAIN}" == "${MAILCOW_HOSTNAME}" ]]; then
|
fi
|
||||||
continue
|
|
||||||
fi
|
|
||||||
# Skip if subdomain is covered by a wildcard in ADDITIONAL_SAN
|
|
||||||
if is_covered_by_wildcard "${FULL_SUBDOMAIN}"; then
|
|
||||||
log_f "Subdomain '${FULL_SUBDOMAIN}' is covered by wildcard - skipping explicit subdomain"
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
# Validate and add subdomain
|
|
||||||
if check_domain "${FULL_SUBDOMAIN}"; then
|
|
||||||
VALIDATED_CONFIG_DOMAINS_SUBDOMAINS+=("${FULL_SUBDOMAIN}")
|
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
VALIDATED_CONFIG_DOMAINS+=("${VALIDATED_CONFIG_DOMAINS_SUBDOMAINS[*]}")
|
VALIDATED_CONFIG_DOMAINS+=("${VALIDATED_CONFIG_DOMAINS_SUBDOMAINS[*]}")
|
||||||
done
|
done
|
||||||
|
|
||||||
# Fetch alias domains where target domain has MTA-STS enabled
|
|
||||||
if [[ ${AUTODISCOVER_SAN} == "y" ]]; then
|
|
||||||
SQL_ALIAS_DOMAINS=$(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT ad.alias_domain FROM alias_domain ad INNER JOIN mta_sts m ON ad.target_domain = m.domain WHERE ad.active = 1 AND m.active = 1" -Bs)
|
|
||||||
if [[ $? -eq 0 ]]; then
|
|
||||||
while read alias_domain; do
|
|
||||||
if [[ -z "${alias_domain}" ]]; then
|
|
||||||
# ignore empty lines
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
# Only add mta-sts subdomain for alias domains
|
|
||||||
if [[ "mta-sts.${alias_domain}" != "${MAILCOW_HOSTNAME}" ]]; then
|
|
||||||
# Skip if mta-sts subdomain is covered by a wildcard
|
|
||||||
if is_covered_by_wildcard "mta-sts.${alias_domain}"; then
|
|
||||||
log_f "Alias domain mta-sts subdomain 'mta-sts.${alias_domain}' is covered by wildcard - skipping"
|
|
||||||
elif check_domain "mta-sts.${alias_domain}"; then
|
|
||||||
VALIDATED_CONFIG_DOMAINS+=("mta-sts.${alias_domain}")
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
done <<< "${SQL_ALIAS_DOMAINS}"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if check_domain ${MAILCOW_HOSTNAME}; then
|
if check_domain ${MAILCOW_HOSTNAME}; then
|
||||||
@@ -321,31 +274,13 @@ while true; do
|
|||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check if MAILCOW_HOSTNAME is covered by a wildcard in ADDITIONAL_SAN
|
|
||||||
MAILCOW_HOSTNAME_COVERED=0
|
|
||||||
if [[ ! -z ${VALIDATED_MAILCOW_HOSTNAME} ]]; then
|
|
||||||
if is_covered_by_wildcard "${VALIDATED_MAILCOW_HOSTNAME}"; then
|
|
||||||
MAILCOW_PARENT_DOMAIN=$(echo ${VALIDATED_MAILCOW_HOSTNAME} | cut -d. -f2-)
|
|
||||||
log_f "MAILCOW_HOSTNAME '${VALIDATED_MAILCOW_HOSTNAME}' is covered by wildcard '*.${MAILCOW_PARENT_DOMAIN}' - skipping explicit hostname"
|
|
||||||
MAILCOW_HOSTNAME_COVERED=1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Unique domains for server certificate
|
# Unique domains for server certificate
|
||||||
if [[ ${ENABLE_SSL_SNI} == "y" ]]; then
|
if [[ ${ENABLE_SSL_SNI} == "y" ]]; then
|
||||||
# create certificate for server name and fqdn SANs only
|
# create certificate for server name and fqdn SANs only
|
||||||
if [[ ${MAILCOW_HOSTNAME_COVERED} == "1" ]]; then
|
SERVER_SAN_VALIDATED=(${VALIDATED_MAILCOW_HOSTNAME} $(echo ${ADDITIONAL_VALIDATED_SAN[*]} | xargs -n1 | sort -u | xargs))
|
||||||
SERVER_SAN_VALIDATED=($(echo ${ADDITIONAL_VALIDATED_SAN[*]} | xargs -n1 | sort -u | xargs))
|
|
||||||
else
|
|
||||||
SERVER_SAN_VALIDATED=(${VALIDATED_MAILCOW_HOSTNAME} $(echo ${ADDITIONAL_VALIDATED_SAN[*]} | xargs -n1 | sort -u | xargs))
|
|
||||||
fi
|
|
||||||
else
|
else
|
||||||
# create certificate for all domains, including all subdomains from other domains [*]
|
# create certificate for all domains, including all subdomains from other domains [*]
|
||||||
if [[ ${MAILCOW_HOSTNAME_COVERED} == "1" ]]; then
|
SERVER_SAN_VALIDATED=(${VALIDATED_MAILCOW_HOSTNAME} $(echo ${VALIDATED_CONFIG_DOMAINS[*]} ${ADDITIONAL_VALIDATED_SAN[*]} | xargs -n1 | sort -u | xargs))
|
||||||
SERVER_SAN_VALIDATED=($(echo ${VALIDATED_CONFIG_DOMAINS[*]} ${ADDITIONAL_VALIDATED_SAN[*]} | xargs -n1 | sort -u | xargs))
|
|
||||||
else
|
|
||||||
SERVER_SAN_VALIDATED=(${VALIDATED_MAILCOW_HOSTNAME} $(echo ${VALIDATED_CONFIG_DOMAINS[*]} ${ADDITIONAL_VALIDATED_SAN[*]} | xargs -n1 | sort -u | xargs))
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
if [[ ! -z ${SERVER_SAN_VALIDATED[*]} ]]; then
|
if [[ ! -z ${SERVER_SAN_VALIDATED[*]} ]]; then
|
||||||
CERT_NAME=${SERVER_SAN_VALIDATED[0]}
|
CERT_NAME=${SERVER_SAN_VALIDATED[0]}
|
||||||
@@ -413,7 +348,7 @@ while true; do
|
|||||||
if [[ -z ${VALIDATED_CERTIFICATES[*]} ]]; then
|
if [[ -z ${VALIDATED_CERTIFICATES[*]} ]]; then
|
||||||
log_f "Cannot validate any hostnames, skipping Let's Encrypt for 1 hour."
|
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."
|
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
|
sleep 1h
|
||||||
exec $(readlink -f "$0")
|
exec $(readlink -f "$0")
|
||||||
fi
|
fi
|
||||||
@@ -454,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)"
|
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
|
if [[ ${RELOAD_LOOP_C} -gt 3 ]]; then
|
||||||
log_f "Some services do return old end dates, something went wrong!"
|
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;
|
break;
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
@@ -475,7 +410,7 @@ while true; do
|
|||||||
;;
|
;;
|
||||||
*) # non-zero
|
*) # non-zero
|
||||||
log_f "Some errors occurred, retrying in 30 minutes..."
|
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
|
sleep 30m
|
||||||
exec $(readlink -f "$0")
|
exec $(readlink -f "$0")
|
||||||
;;
|
;;
|
||||||
|
|||||||
@@ -5,13 +5,13 @@ log_f() {
|
|||||||
echo -n "$(date) - ${1}"
|
echo -n "$(date) - ${1}"
|
||||||
elif [[ ${2} == "no_date" ]]; then
|
elif [[ ${2} == "no_date" ]]; then
|
||||||
echo "${1}"
|
echo "${1}"
|
||||||
elif [[ ${2} != "redis_only" ]]; then
|
elif [[ ${2} != "valkey_only" ]]; then
|
||||||
echo "$(date) - ${1}"
|
echo "$(date) - ${1}"
|
||||||
fi
|
fi
|
||||||
if [[ ${3} == "b64" ]]; then
|
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
|
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
|
tr '%&;$"[]{}-\r\n' ' ')\"}" > /dev/null
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
@@ -80,11 +80,6 @@ check_domain(){
|
|||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ ${ACME_DNS_CHALLENGE} == "y" ]]; then
|
|
||||||
log_f "ACME_DNS_CHALLENGE=y - skipping IP and HTTP validation for ${DOMAIN}"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
# Check if CNAME without v6 enabled target
|
# Check if CNAME without v6 enabled target
|
||||||
if [[ ! -z ${AAAA_DOMAIN} ]] && [[ -z $(echo ${AAAA_DOMAIN} | grep "^\([0-9a-fA-F]\{0,4\}:\)\{1,7\}[0-9a-fA-F]\{0,4\}$") ]]; then
|
if [[ ! -z ${AAAA_DOMAIN} ]] && [[ -z $(echo ${AAAA_DOMAIN} | grep "^\([0-9a-fA-F]\{0,4\}:\)\{1,7\}[0-9a-fA-F]\{0,4\}$") ]]; then
|
||||||
AAAA_DOMAIN=
|
AAAA_DOMAIN=
|
||||||
@@ -135,32 +130,3 @@ verify_challenge_path(){
|
|||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check if a domain is covered by a wildcard (*.example.com) in ADDITIONAL_SAN
|
|
||||||
# Usage: is_covered_by_wildcard "subdomain.example.com"
|
|
||||||
# Returns: 0 if covered, 1 if not covered
|
|
||||||
# Note: Only returns 0 (covered) when DNS-01 challenge is enabled,
|
|
||||||
# as wildcards cannot be validated with HTTP-01 challenge
|
|
||||||
is_covered_by_wildcard() {
|
|
||||||
local DOMAIN=$1
|
|
||||||
|
|
||||||
# Only skip if DNS challenge is enabled (wildcards require DNS-01)
|
|
||||||
if [[ ${ACME_DNS_CHALLENGE} != "y" ]]; then
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Return early if no ADDITIONAL_SAN is set
|
|
||||||
if [[ -z ${ADDITIONAL_SAN} ]]; then
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Extract parent domain (e.g., mail.example.com -> example.com)
|
|
||||||
local PARENT_DOMAIN=$(echo ${DOMAIN} | cut -d. -f2-)
|
|
||||||
|
|
||||||
# Check if ADDITIONAL_SAN contains a wildcard for this parent domain
|
|
||||||
if [[ "${ADDITIONAL_SAN}" == *"*.${PARENT_DOMAIN}"* ]]; then
|
|
||||||
return 0 # Covered by wildcard
|
|
||||||
fi
|
|
||||||
|
|
||||||
return 1 # Not covered
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,57 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
SCRIPT_SOURCE="${BASH_SOURCE[0]:-${0}}"
|
|
||||||
if [[ "${SCRIPT_SOURCE}" == "${0}" ]]; then
|
|
||||||
__dns_loader_standalone=1
|
|
||||||
else
|
|
||||||
__dns_loader_standalone=0
|
|
||||||
fi
|
|
||||||
|
|
||||||
CONFIG_PATH="${ACME_DNS_CONFIG_FILE:-/etc/acme/dns-01.conf}"
|
|
||||||
|
|
||||||
if [[ ! -f "${CONFIG_PATH}" ]]; then
|
|
||||||
if [[ $__dns_loader_standalone -eq 1 ]]; then
|
|
||||||
exit 0
|
|
||||||
else
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
source /srv/functions.sh
|
|
||||||
|
|
||||||
log_f "Loading DNS-01 configuration from ${CONFIG_PATH}"
|
|
||||||
|
|
||||||
LINE_NO=0
|
|
||||||
while IFS= read -r line || [[ -n "${line}" ]]; do
|
|
||||||
LINE_NO=$((LINE_NO+1))
|
|
||||||
line="${line%$'\r'}"
|
|
||||||
line_trimmed="$(printf '%s' "${line}" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
|
|
||||||
[[ -z "${line_trimmed}" ]] && continue
|
|
||||||
[[ "${line_trimmed:0:1}" == "#" ]] && continue
|
|
||||||
if [[ "${line_trimmed}" != *=* ]]; then
|
|
||||||
log_f "Skipping invalid DNS config line ${LINE_NO} (missing key=value)"
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
KEY="${line_trimmed%%=*}"
|
|
||||||
VALUE="${line_trimmed#*=}"
|
|
||||||
KEY="$(printf '%s' "${KEY}" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
|
|
||||||
VALUE="$(printf '%s' "${VALUE}" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
|
|
||||||
if [[ -z "${KEY}" ]]; then
|
|
||||||
log_f "Skipping invalid DNS config line ${LINE_NO} (empty key)"
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
if [[ "${VALUE}" =~ ^\".*\"$ ]]; then
|
|
||||||
VALUE="${VALUE:1:-1}"
|
|
||||||
elif [[ "${VALUE}" =~ ^\'.*\'$ ]]; then
|
|
||||||
VALUE="${VALUE:1:-1}"
|
|
||||||
fi
|
|
||||||
export "${KEY}"="${VALUE}"
|
|
||||||
log_f "Exported DNS config key ${KEY}"
|
|
||||||
|
|
||||||
done < "${CONFIG_PATH}"
|
|
||||||
|
|
||||||
if [[ $__dns_loader_standalone -eq 1 ]]; then
|
|
||||||
exit 0
|
|
||||||
else
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
@@ -1,177 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Return values / exit codes
|
|
||||||
# 0 = cert created successfully
|
|
||||||
# 1 = cert renewed successfully
|
|
||||||
# 2 = cert not due for renewal
|
|
||||||
# * = errors
|
|
||||||
|
|
||||||
source /srv/functions.sh
|
|
||||||
|
|
||||||
CERT_DOMAINS=(${DOMAINS[@]})
|
|
||||||
CERT_DOMAIN=${CERT_DOMAINS[0]}
|
|
||||||
ACME_BASE=/var/lib/acme
|
|
||||||
|
|
||||||
# Load optional DNS provider secrets from /etc/acme/dns-01.conf
|
|
||||||
if [[ -f /srv/load-dns-config.sh ]]; then
|
|
||||||
source /srv/load-dns-config.sh
|
|
||||||
if declare -F log_f >/dev/null; then
|
|
||||||
log_f "ACME_DNS_CHALLENGE is enabled, DNS provider secrets loaded"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
TYPE=${1}
|
|
||||||
PREFIX=""
|
|
||||||
# only support rsa certificates for now
|
|
||||||
if [[ "${TYPE}" != "rsa" ]]; then
|
|
||||||
log_f "Unknown certificate type '${TYPE}' requested"
|
|
||||||
exit 5
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -z "${ACME_DNS_PROVIDER}" ]]; then
|
|
||||||
log_f "ACME_DNS_PROVIDER is required when ACME_DNS_CHALLENGE is enabled"
|
|
||||||
exit 6
|
|
||||||
fi
|
|
||||||
|
|
||||||
DOMAINS_FILE=${ACME_BASE}/${CERT_DOMAIN}/domains
|
|
||||||
CERT=${ACME_BASE}/${CERT_DOMAIN}/${PREFIX}cert.pem
|
|
||||||
SHARED_KEY=${ACME_BASE}/acme/${PREFIX}key.pem # must already exist
|
|
||||||
KEY=${ACME_BASE}/${CERT_DOMAIN}/${PREFIX}key.pem
|
|
||||||
CSR=${ACME_BASE}/${CERT_DOMAIN}/${PREFIX}acme.csr
|
|
||||||
|
|
||||||
if [[ -z ${CERT_DOMAINS[*]} ]]; then
|
|
||||||
log_f "Missing CERT_DOMAINS to obtain a certificate"
|
|
||||||
exit 3
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "${LE_STAGING}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
|
|
||||||
if [[ ! -z "${DIRECTORY_URL}" ]]; then
|
|
||||||
log_f "Cannot use DIRECTORY_URL with LE_STAGING=y - ignoring DIRECTORY_URL"
|
|
||||||
fi
|
|
||||||
log_f "Using Let's Encrypt staging servers"
|
|
||||||
ACME_SH_SERVER_ARGS=("--staging")
|
|
||||||
elif [[ ! -z "${DIRECTORY_URL}" ]]; then
|
|
||||||
log_f "Using custom directory URL ${DIRECTORY_URL}"
|
|
||||||
ACME_SH_SERVER_ARGS=("--server" "${DIRECTORY_URL}")
|
|
||||||
else
|
|
||||||
log_f "Using Let's Encrypt production servers"
|
|
||||||
ACME_SH_SERVER_ARGS=("--server" "letsencrypt")
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -f ${DOMAINS_FILE} && "$(cat ${DOMAINS_FILE})" == "${CERT_DOMAINS[*]}" ]]; then
|
|
||||||
if [[ ! -f ${CERT} || ! -f "${KEY}" || -f "${ACME_BASE}/force_renew" ]]; then
|
|
||||||
log_f "Certificate ${CERT} doesn't exist yet or forced renewal - start obtaining"
|
|
||||||
elif ! openssl x509 -checkend 2592000 -noout -in ${CERT} > /dev/null; then
|
|
||||||
log_f "Certificate ${CERT} is due for renewal (< 30 days) - start renewing"
|
|
||||||
else
|
|
||||||
log_f "Certificate ${CERT} validation done, neither changed nor due for renewal."
|
|
||||||
exit 2
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
log_f "Certificate ${CERT} missing or changed domains '${CERT_DOMAINS[*]}' - start obtaining"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Make backup
|
|
||||||
if [[ -f ${CERT} ]]; then
|
|
||||||
DATE=$(date +%Y-%m-%d_%H_%M_%S)
|
|
||||||
BACKUP_DIR=${ACME_BASE}/backups/${CERT_DOMAIN}/${PREFIX}${DATE}
|
|
||||||
log_f "Creating backups in ${BACKUP_DIR} ..."
|
|
||||||
mkdir -p ${BACKUP_DIR}/
|
|
||||||
[[ -f ${DOMAINS_FILE} ]] && cp ${DOMAINS_FILE} ${BACKUP_DIR}/
|
|
||||||
[[ -f ${CERT} ]] && cp ${CERT} ${BACKUP_DIR}/
|
|
||||||
[[ -f ${KEY} ]] && cp ${KEY} ${BACKUP_DIR}/
|
|
||||||
[[ -f ${CSR} ]] && cp ${CSR} ${BACKUP_DIR}/
|
|
||||||
fi
|
|
||||||
|
|
||||||
mkdir -p ${ACME_BASE}/${CERT_DOMAIN}
|
|
||||||
if [[ ! -f ${KEY} ]]; then
|
|
||||||
log_f "Copying shared private key for this certificate..."
|
|
||||||
cp ${SHARED_KEY} ${KEY}
|
|
||||||
chmod 600 ${KEY}
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Generating CSR to keep layout parity with HTTP challenge flow
|
|
||||||
printf "[SAN]\nsubjectAltName=" > /tmp/_SAN
|
|
||||||
printf "DNS:%s," "${CERT_DOMAINS[@]}" >> /tmp/_SAN
|
|
||||||
sed -i '$s/,$//' /tmp/_SAN
|
|
||||||
openssl req -new -sha256 -key ${KEY} -subj "/" -reqexts SAN -config <(cat "$(openssl version -d | sed 's/.*\"\(.*\)\"/\1/g')/openssl.cnf" /tmp/_SAN) > ${CSR}
|
|
||||||
|
|
||||||
log_f "Checking resolver..."
|
|
||||||
until dig letsencrypt.org +time=3 +tries=1 @unbound > /dev/null; do
|
|
||||||
sleep 2
|
|
||||||
done
|
|
||||||
log_f "Resolver OK"
|
|
||||||
|
|
||||||
ACME_SH_BIN_PATH=${ACME_SH_BIN:-/opt/acme.sh/acme.sh}
|
|
||||||
ACME_SH_WORK_HOME=${ACME_SH_CONFIG_HOME:-/var/lib/acme/acme-sh}
|
|
||||||
mkdir -p ${ACME_SH_WORK_HOME}
|
|
||||||
|
|
||||||
if [[ ! -x ${ACME_SH_BIN_PATH} ]]; then
|
|
||||||
log_f "acme.sh binary not found at ${ACME_SH_BIN_PATH}"
|
|
||||||
exit 7
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ ! -f ${ACME_SH_WORK_HOME}/account.conf ]]; then
|
|
||||||
if [[ -z "${ACME_ACCOUNT_EMAIL}" ]]; then
|
|
||||||
log_f "ACME_ACCOUNT_EMAIL is required to register a new acme.sh account"
|
|
||||||
exit 8
|
|
||||||
fi
|
|
||||||
log_f "Registering acme.sh account for ${ACME_ACCOUNT_EMAIL}"
|
|
||||||
REGISTER_CMD=("${ACME_SH_BIN_PATH}" "--home" "${ACME_SH_WORK_HOME}" "--config-home" "${ACME_SH_WORK_HOME}" "--cert-home" "${ACME_SH_WORK_HOME}" "--register-account" "-m" "${ACME_ACCOUNT_EMAIL}")
|
|
||||||
REGISTER_CMD+=("${ACME_SH_SERVER_ARGS[@]}")
|
|
||||||
REGISTER_RESPONSE=$("${REGISTER_CMD[@]}" 2>&1)
|
|
||||||
if [[ $? -ne 0 ]]; then
|
|
||||||
log_f "Failed to register acme.sh account: ${REGISTER_RESPONSE}"
|
|
||||||
exit 9
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
TMP_CERT=$(mktemp /tmp/acme-cert.XXXXXX)
|
|
||||||
TMP_FULLCHAIN=$(mktemp /tmp/acme-fullchain.XXXXXX)
|
|
||||||
|
|
||||||
ACME_CMD=("${ACME_SH_BIN_PATH}" "--home" "${ACME_SH_WORK_HOME}" "--config-home" "${ACME_SH_WORK_HOME}" "--cert-home" "${ACME_SH_WORK_HOME}")
|
|
||||||
ACME_CMD+=("${ACME_SH_SERVER_ARGS[@]}")
|
|
||||||
ACME_CMD+=("--issue" "--dns" "${ACME_DNS_PROVIDER}" "--key-file" "${KEY}" "--cert-file" "${TMP_CERT}" "--fullchain-file" "${TMP_FULLCHAIN}" "--force")
|
|
||||||
for domain in "${CERT_DOMAINS[@]}"; do
|
|
||||||
ACME_CMD+=("-d" "${domain}")
|
|
||||||
done
|
|
||||||
|
|
||||||
log_f "Using command ${ACME_CMD[*]}"
|
|
||||||
if [[ -n "${ACME_DNS_PROVIDER}" ]]; then
|
|
||||||
log_f "DNS provider: ${ACME_DNS_PROVIDER}"
|
|
||||||
fi
|
|
||||||
if compgen -A variable | grep -Eq "^DNS_|^ACME_"; then
|
|
||||||
LOG_KEYS=$(env | grep -E "^(DNS_|ACME_)" | cut -d= -f1 | tr '\n' ' ')
|
|
||||||
log_f "Available DNS/ACME env keys: ${LOG_KEYS}" redis_only
|
|
||||||
fi
|
|
||||||
ACME_RESPONSE=$("${ACME_CMD[@]}" 2>&1 | 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
|
|
||||||
|
|
||||||
case "$SUCCESS" in
|
|
||||||
0)
|
|
||||||
log_f "Deploying certificate ${CERT}..."
|
|
||||||
if verify_hash_match ${TMP_FULLCHAIN} ${KEY}; then
|
|
||||||
RETURN=0
|
|
||||||
if [[ -f ${CERT} ]]; then
|
|
||||||
RETURN=1
|
|
||||||
fi
|
|
||||||
mv -f ${TMP_FULLCHAIN} ${CERT}
|
|
||||||
rm -f ${TMP_CERT}
|
|
||||||
echo -n ${CERT_DOMAINS[*]} > ${DOMAINS_FILE}
|
|
||||||
log_f "Certificate successfully obtained via DNS challenge"
|
|
||||||
exit ${RETURN}
|
|
||||||
else
|
|
||||||
log_f "Certificate was requested, but key and certificate hashes do not match"
|
|
||||||
rm -f ${TMP_CERT} ${TMP_FULLCHAIN}
|
|
||||||
exit 4
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
log_f "Failed to obtain certificate ${CERT} for domains '${CERT_DOMAINS[*]}' via DNS challenge"
|
|
||||||
redis-cli -h redis -a ${REDISPASS} --no-auth-warning SET ACME_FAIL_TIME "$(date +%s)"
|
|
||||||
rm -f ${TMP_CERT} ${TMP_FULLCHAIN}
|
|
||||||
exit 100${SUCCESS}
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
@@ -20,10 +20,6 @@ if [[ "${TYPE}" != "rsa" ]]; then
|
|||||||
log_f "Unknown certificate type '${TYPE}' requested"
|
log_f "Unknown certificate type '${TYPE}' requested"
|
||||||
exit 5
|
exit 5
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "${ACME_DNS_CHALLENGE}" == "y" ]]; then
|
|
||||||
exec /srv/obtain-certificate-dns.sh "$@"
|
|
||||||
fi
|
|
||||||
DOMAINS_FILE=${ACME_BASE}/${CERT_DOMAIN}/domains
|
DOMAINS_FILE=${ACME_BASE}/${CERT_DOMAIN}/domains
|
||||||
CERT=${ACME_BASE}/${CERT_DOMAIN}/${PREFIX}cert.pem
|
CERT=${ACME_BASE}/${CERT_DOMAIN}/${PREFIX}cert.pem
|
||||||
SHARED_KEY=${ACME_BASE}/acme/${PREFIX}key.pem # must already exist
|
SHARED_KEY=${ACME_BASE}/acme/${PREFIX}key.pem # must already exist
|
||||||
@@ -105,7 +101,7 @@ ACME_RESPONSE=$(acme-tiny ${DIRECTORY_URL} \
|
|||||||
--acme-dir /var/www/acme/ 2>&1 > /tmp/_cert.pem | tee /dev/fd/5; exit ${PIPESTATUS[0]})
|
--acme-dir /var/www/acme/ 2>&1 > /tmp/_cert.pem | tee /dev/fd/5; exit ${PIPESTATUS[0]})
|
||||||
SUCCESS="$?"
|
SUCCESS="$?"
|
||||||
ACME_RESPONSE_B64=$(echo "${ACME_RESPONSE}" | openssl enc -e -A -base64)
|
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
|
case "$SUCCESS" in
|
||||||
0) # cert requested
|
0) # cert requested
|
||||||
log_f "Deploying certificate ${CERT}..."
|
log_f "Deploying certificate ${CERT}..."
|
||||||
@@ -128,7 +124,7 @@ case "$SUCCESS" in
|
|||||||
;;
|
;;
|
||||||
*) # non-zero is non-fun
|
*) # non-zero is non-fun
|
||||||
log_f "Failed to obtain certificate ${CERT} for domains '${CERT_DOMAINS[*]}'"
|
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}
|
exit 100${SUCCESS}
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
FROM debian:trixie-slim
|
FROM debian:bookworm-slim
|
||||||
|
|
||||||
RUN apt update && apt install pigz zstd -y --no-install-recommends
|
RUN apt update && apt install pigz -y --no-install-recommends
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM alpine:3.23
|
FROM alpine:3.21
|
||||||
|
|
||||||
LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"
|
LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"
|
||||||
|
|
||||||
|
|||||||
@@ -32,21 +32,21 @@ async def lifespan(app: FastAPI):
|
|||||||
|
|
||||||
logger.info("Init APP")
|
logger.info("Init APP")
|
||||||
|
|
||||||
# Init redis client
|
# Init valkey client
|
||||||
if os.environ['REDIS_SLAVEOF_IP'] != "":
|
if os.environ['VALKEY_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'])
|
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:
|
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
|
# Init docker clients
|
||||||
sync_docker_client = docker.DockerClient(base_url='unix://var/run/docker.sock', version='auto')
|
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')
|
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")
|
logger.info("Subscribe to valkey channel")
|
||||||
# Subscribe to redis channel
|
# Subscribe to valkey channel
|
||||||
dockerapi.pubsub = redis.pubsub()
|
dockerapi.pubsub = valkey.pubsub()
|
||||||
await dockerapi.pubsub.subscribe("MC_CHANNEL")
|
await dockerapi.pubsub.subscribe("MC_CHANNEL")
|
||||||
asyncio.create_task(handle_pubsub_messages(dockerapi.pubsub))
|
asyncio.create_task(handle_pubsub_messages(dockerapi.pubsub))
|
||||||
|
|
||||||
@@ -57,9 +57,9 @@ async def lifespan(app: FastAPI):
|
|||||||
dockerapi.sync_docker_client.close()
|
dockerapi.sync_docker_client.close()
|
||||||
await dockerapi.async_docker_client.close()
|
await dockerapi.async_docker_client.close()
|
||||||
|
|
||||||
# Close redis
|
# Close valkey
|
||||||
await dockerapi.pubsub.unsubscribe("MC_CHANNEL")
|
await dockerapi.pubsub.unsubscribe("MC_CHANNEL")
|
||||||
await dockerapi.redis_client.close()
|
await dockerapi.valkey_client.close()
|
||||||
|
|
||||||
app = FastAPI(lifespan=lifespan)
|
app = FastAPI(lifespan=lifespan)
|
||||||
|
|
||||||
@@ -73,11 +73,11 @@ async def get_host_update_stats():
|
|||||||
dockerapi.host_stats_isUpdating = True
|
dockerapi.host_stats_isUpdating = True
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
if await dockerapi.redis_client.exists('host_stats'):
|
if await dockerapi.valkey_client.exists('host_stats'):
|
||||||
break
|
break
|
||||||
await asyncio.sleep(1.5)
|
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")
|
return Response(content=json.dumps(stats, indent=4), media_type="application/json")
|
||||||
|
|
||||||
@app.get("/containers/{container_id}/json")
|
@app.get("/containers/{container_id}/json")
|
||||||
@@ -110,12 +110,12 @@ async def get_container(container_id : str):
|
|||||||
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
||||||
|
|
||||||
@app.get("/containers/json")
|
@app.get("/containers/json")
|
||||||
async def get_containers(all: bool = False):
|
async def get_containers():
|
||||||
global dockerapi
|
global dockerapi
|
||||||
|
|
||||||
containers = {}
|
containers = {}
|
||||||
try:
|
try:
|
||||||
for container in (await dockerapi.async_docker_client.containers.list(all=all)):
|
for container in (await dockerapi.async_docker_client.containers.list()):
|
||||||
container_info = await container.show()
|
container_info = await container.show()
|
||||||
containers.update({container_info['Id']: container_info})
|
containers.update({container_info['Id']: container_info})
|
||||||
return Response(content=json.dumps(containers, indent=4), media_type="application/json")
|
return Response(content=json.dumps(containers, indent=4), media_type="application/json")
|
||||||
@@ -185,11 +185,11 @@ async def post_container_update_stats(container_id : str):
|
|||||||
dockerapi.containerIds_to_update.append(container_id)
|
dockerapi.containerIds_to_update.append(container_id)
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
if await dockerapi.redis_client.exists(container_id + '_stats'):
|
if await dockerapi.valkey_client.exists(container_id + '_stats'):
|
||||||
break
|
break
|
||||||
await asyncio.sleep(1.5)
|
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")
|
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
|
from fastapi import FastAPI, Response, Request
|
||||||
|
|
||||||
class DockerApi:
|
class DockerApi:
|
||||||
def __init__(self, redis_client, sync_docker_client, async_docker_client, logger):
|
def __init__(self, valkey_client, sync_docker_client, async_docker_client, logger):
|
||||||
self.redis_client = redis_client
|
self.valkey_client = valkey_client
|
||||||
self.sync_docker_client = sync_docker_client
|
self.sync_docker_client = sync_docker_client
|
||||||
self.async_docker_client = async_docker_client
|
self.async_docker_client = async_docker_client
|
||||||
self.logger = logger
|
self.logger = logger
|
||||||
@@ -533,7 +533,7 @@ class DockerApi:
|
|||||||
"architecture": platform.machine()
|
"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:
|
except Exception as e:
|
||||||
res = {
|
res = {
|
||||||
"type": "danger",
|
"type": "danger",
|
||||||
@@ -550,14 +550,14 @@ class DockerApi:
|
|||||||
if container._id == container_id:
|
if container._id == container_id:
|
||||||
res = await container.stats(stream=False)
|
res = await container.stats(stream=False)
|
||||||
|
|
||||||
if await self.redis_client.exists(container_id + '_stats'):
|
if await self.valkey_client.exists(container_id + '_stats'):
|
||||||
stats = json.loads(await self.redis_client.get(container_id + '_stats'))
|
stats = json.loads(await self.valkey_client.get(container_id + '_stats'))
|
||||||
else:
|
else:
|
||||||
stats = []
|
stats = []
|
||||||
stats.append(res[0])
|
stats.append(res[0])
|
||||||
if len(stats) > 3:
|
if len(stats) > 3:
|
||||||
del stats[0]
|
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:
|
except Exception as e:
|
||||||
res = {
|
res = {
|
||||||
"type": "danger",
|
"type": "danger",
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ FROM alpine:3.21
|
|||||||
LABEL maintainer="The Infrastructure Company GmbH <info@servercow.de>"
|
LABEL maintainer="The Infrastructure Company GmbH <info@servercow.de>"
|
||||||
|
|
||||||
# renovate: datasource=github-releases depName=tianon/gosu versioning=semver-coerced extractVersion=^(?<version>.*)$
|
# renovate: datasource=github-releases depName=tianon/gosu versioning=semver-coerced extractVersion=^(?<version>.*)$
|
||||||
ARG GOSU_VERSION=1.19
|
ARG GOSU_VERSION=1.17
|
||||||
|
|
||||||
ENV LANG=C.UTF-8
|
ENV LANG=C.UTF-8
|
||||||
ENV LC_ALL=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 trim_logs.sh /usr/local/bin/trim_logs.sh
|
||||||
COPY clean_q_aged.sh /usr/local/bin/clean_q_aged.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.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 /usr/local/bin/imapsync
|
||||||
COPY imapsync_runner.pl /usr/local/bin/imapsync_runner.pl
|
COPY imapsync_runner.pl /usr/local/bin/imapsync_runner.pl
|
||||||
COPY report-spam.sieve /usr/lib/dovecot/sieve/report-spam.sieve
|
COPY report-spam.sieve /usr/lib/dovecot/sieve/report-spam.sieve
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
source /source_env.sh
|
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
|
if [[ -z ${MAX_AGE} ]]; then
|
||||||
echo "Max age for quarantine items not defined"
|
echo "Max age for quarantine items not defined"
|
||||||
|
|||||||
@@ -13,18 +13,18 @@ until dig +short mailcow.email > /dev/null; do
|
|||||||
done
|
done
|
||||||
|
|
||||||
# Do not attempt to write to slave
|
# Do not attempt to write to slave
|
||||||
if [[ ! -z ${REDIS_SLAVEOF_IP} ]]; then
|
if [[ ! -z ${VALKEY_SLAVEOF_IP} ]]; then
|
||||||
REDIS_CMDLINE="redis-cli -h ${REDIS_SLAVEOF_IP} -p ${REDIS_SLAVEOF_PORT} -a ${REDISPASS} --no-auth-warning"
|
VALKEY_CMDLINE="redis-cli -h ${VALKEY_SLAVEOF_IP} -p ${VALKEY_SLAVEOF_PORT} -a ${VALKEYPASS} --no-auth-warning"
|
||||||
else
|
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
|
fi
|
||||||
|
|
||||||
until [[ $(${REDIS_CMDLINE} PING) == "PONG" ]]; do
|
until [[ $(${VALKEY_CMDLINE} PING) == "PONG" ]]; do
|
||||||
echo "Waiting for Redis..."
|
echo "Waiting for Valkey..."
|
||||||
sleep 2
|
sleep 2
|
||||||
done
|
done
|
||||||
|
|
||||||
${REDIS_CMDLINE} SET DOVECOT_REPL_HEALTH 1 > /dev/null
|
${VALKEY_CMDLINE} SET DOVECOT_REPL_HEALTH 1 > /dev/null
|
||||||
|
|
||||||
# Create missing directories
|
# Create missing directories
|
||||||
[[ ! -d /etc/dovecot/sql/ ]] && mkdir -p /etc/dovecot/sql/
|
[[ ! -d /etc/dovecot/sql/ ]] && mkdir -p /etc/dovecot/sql/
|
||||||
@@ -204,17 +204,16 @@ EOF
|
|||||||
# Create random master Password for SOGo SSO
|
# Create random master Password for SOGo SSO
|
||||||
RAND_PASS=$(cat /dev/urandom | tr -dc 'a-z0-9' | fold -w 32 | head -n 1)
|
RAND_PASS=$(cat /dev/urandom | tr -dc 'a-z0-9' | fold -w 32 | head -n 1)
|
||||||
echo -n ${RAND_PASS} > /etc/phpfpm/sogo-sso.pass
|
echo -n ${RAND_PASS} > /etc/phpfpm/sogo-sso.pass
|
||||||
|
# Creating additional creds file for SOGo notify crons (calendars, etc)
|
||||||
|
echo -n ${RAND_USER}@mailcow.local:${RAND_PASS} > /etc/sogo/cron.creds
|
||||||
cat <<EOF > /etc/dovecot/sogo-sso.conf
|
cat <<EOF > /etc/dovecot/sogo-sso.conf
|
||||||
# Autogenerated by mailcow
|
# Autogenerated by mailcow
|
||||||
passdb {
|
passdb {
|
||||||
driver = static
|
driver = static
|
||||||
args = allow_nets=${IPV4_NETWORK}.248/32 password={plain}${RAND_PASS}
|
args = allow_real_nets=${IPV4_NETWORK}.248/32 password={plain}${RAND_PASS}
|
||||||
}
|
}
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
# Creating additional creds file for SOGo notify crons (calendars, etc) (dummy user, sso password)
|
|
||||||
echo -n ${RAND_USER}@mailcow.local:${RAND_PASS} > /etc/sogo/cron.creds
|
|
||||||
|
|
||||||
if [[ "${MASTER}" =~ ^([nN][oO]|[nN])+$ ]]; then
|
if [[ "${MASTER}" =~ ^([nN][oO]|[nN])+$ ]]; then
|
||||||
# Toggling MASTER will result in a rebuild of containers, so the quota script will be recreated
|
# Toggling MASTER will result in a rebuild of containers, so the quota script will be recreated
|
||||||
cat <<'EOF' > /usr/local/bin/quota_notify.py
|
cat <<'EOF' > /usr/local/bin/quota_notify.py
|
||||||
@@ -342,8 +341,8 @@ done
|
|||||||
# May be related to something inside Docker, I seriously don't know
|
# May be related to something inside Docker, I seriously don't know
|
||||||
touch /etc/dovecot/auth/passwd-verify.lua
|
touch /etc/dovecot/auth/passwd-verify.lua
|
||||||
|
|
||||||
if [[ ! -z ${REDIS_SLAVEOF_IP} ]]; then
|
if [[ ! -z ${VALKEY_SLAVEOF_IP} ]]; then
|
||||||
cp /etc/syslog-ng/syslog-ng-redis_slave.conf /etc/syslog-ng/syslog-ng.conf
|
cp /etc/syslog-ng/syslog-ng-valkey_slave.conf /etc/syslog-ng/syslog-ng.conf
|
||||||
fi
|
fi
|
||||||
|
|
||||||
exec "$@"
|
exec "$@"
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ try:
|
|||||||
|
|
||||||
while True:
|
while True:
|
||||||
try:
|
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()
|
r.ping()
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
print('%s - trying again...' % (ex))
|
print('%s - trying again...' % (ex))
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ else:
|
|||||||
|
|
||||||
while True:
|
while True:
|
||||||
try:
|
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()
|
r.ping()
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
print('%s - trying again...' % (ex))
|
print('%s - trying again...' % (ex))
|
||||||
|
|||||||
@@ -3,16 +3,16 @@
|
|||||||
source /source_env.sh
|
source /source_env.sh
|
||||||
|
|
||||||
# Do not attempt to write to slave
|
# Do not attempt to write to slave
|
||||||
if [[ ! -z ${REDIS_SLAVEOF_IP} ]]; then
|
if [[ ! -z ${VALKEY_SLAVEOF_IP} ]]; then
|
||||||
REDIS_CMDLINE="redis-cli -h ${REDIS_SLAVEOF_IP} -p ${REDIS_SLAVEOF_PORT} -a ${REDISPASS} --no-auth-warning"
|
VALKEY_CMDLINE="redis-cli -h ${VALKEY_SLAVEOF_IP} -p ${VALKEY_SLAVEOF_PORT} -a ${VALKEYPASS} --no-auth-warning"
|
||||||
else
|
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
|
fi
|
||||||
|
|
||||||
# Is replication active?
|
# Is replication active?
|
||||||
# grep on file is less expensive than doveconf
|
# grep on file is less expensive than doveconf
|
||||||
if [ -n ${MAILCOW_REPLICA_IP} ]; then
|
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
|
exit
|
||||||
fi
|
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
|
# 1 failed job for mailcow.local is expected and healthy
|
||||||
if [[ "${FAILED_SYNCS}" != 0 ]] && [[ "${FAILED_SYNCS}" != 1 ]]; then
|
if [[ "${FAILED_SYNCS}" != 0 ]] && [[ "${FAILED_SYNCS}" != 1 ]]; then
|
||||||
printf "Dovecot replicator has %d failed jobs\n" "${FAILED_SYNCS}"
|
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
|
else
|
||||||
${REDIS_CMDLINE} SET DOVECOT_REPL_HEALTH 1 > /dev/null
|
${VALKEY_CMDLINE} SET DOVECOT_REPL_HEALTH 1 > /dev/null
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -15,21 +15,21 @@ source s_dgram {
|
|||||||
internal();
|
internal();
|
||||||
};
|
};
|
||||||
destination d_stdout { pipe("/dev/stdout"); };
|
destination d_stdout { pipe("/dev/stdout"); };
|
||||||
destination d_redis_ui_log {
|
destination d_valkey_ui_log {
|
||||||
redis(
|
redis(
|
||||||
host("`REDIS_SLAVEOF_IP`")
|
host("`VALKEY_SLAVEOF_IP`")
|
||||||
persist-name("redis1")
|
persist-name("valkey1")
|
||||||
port(`REDIS_SLAVEOF_PORT`)
|
port(`VALKEY_SLAVEOF_PORT`)
|
||||||
auth("`REDISPASS`")
|
auth("`VALKEYPASS`")
|
||||||
command("LPUSH" "DOVECOT_MAILLOG" "$(format-json time=\"$S_UNIXTIME\" priority=\"$PRIORITY\" program=\"$PROGRAM\" message=\"$MESSAGE\")\n")
|
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(
|
redis(
|
||||||
host("`REDIS_SLAVEOF_IP`")
|
host("`VALKEY_SLAVEOF_IP`")
|
||||||
persist-name("redis2")
|
persist-name("valkey2")
|
||||||
port(`REDIS_SLAVEOF_PORT`)
|
port(`VALKEY_SLAVEOF_PORT`)
|
||||||
auth("`REDISPASS`")
|
auth("`VALKEYPASS`")
|
||||||
command("PUBLISH" "F2B_CHANNEL" "$(sanitize $MESSAGE)")
|
command("PUBLISH" "F2B_CHANNEL" "$(sanitize $MESSAGE)")
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -48,6 +48,6 @@ log {
|
|||||||
filter(f_replica);
|
filter(f_replica);
|
||||||
destination(d_stdout);
|
destination(d_stdout);
|
||||||
filter(f_mail);
|
filter(f_mail);
|
||||||
destination(d_redis_ui_log);
|
destination(d_valkey_ui_log);
|
||||||
destination(d_redis_f2b_channel);
|
destination(d_valkey_f2b_channel);
|
||||||
};
|
};
|
||||||
@@ -15,21 +15,21 @@ source s_dgram {
|
|||||||
internal();
|
internal();
|
||||||
};
|
};
|
||||||
destination d_stdout { pipe("/dev/stdout"); };
|
destination d_stdout { pipe("/dev/stdout"); };
|
||||||
destination d_redis_ui_log {
|
destination d_valkey_ui_log {
|
||||||
redis(
|
redis(
|
||||||
host("redis-mailcow")
|
host("valkey-mailcow")
|
||||||
persist-name("redis1")
|
persist-name("valkey1")
|
||||||
port(6379)
|
port(6379)
|
||||||
auth("`REDISPASS`")
|
auth("`VALKEYPASS`")
|
||||||
command("LPUSH" "DOVECOT_MAILLOG" "$(format-json time=\"$S_UNIXTIME\" priority=\"$PRIORITY\" program=\"$PROGRAM\" message=\"$MESSAGE\")\n")
|
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(
|
redis(
|
||||||
host("redis-mailcow")
|
host("valkey-mailcow")
|
||||||
persist-name("redis2")
|
persist-name("valkey2")
|
||||||
port(6379)
|
port(6379)
|
||||||
auth("`REDISPASS`")
|
auth("`VALKEYPASS`")
|
||||||
command("PUBLISH" "F2B_CHANNEL" "$(sanitize $MESSAGE)")
|
command("PUBLISH" "F2B_CHANNEL" "$(sanitize $MESSAGE)")
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -48,6 +48,6 @@ log {
|
|||||||
filter(f_replica);
|
filter(f_replica);
|
||||||
destination(d_stdout);
|
destination(d_stdout);
|
||||||
filter(f_mail);
|
filter(f_mail);
|
||||||
destination(d_redis_ui_log);
|
destination(d_valkey_ui_log);
|
||||||
destination(d_redis_f2b_channel);
|
destination(d_valkey_f2b_channel);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,18 +9,17 @@ catch_non_zero() {
|
|||||||
}
|
}
|
||||||
source /source_env.sh
|
source /source_env.sh
|
||||||
# Do not attempt to write to slave
|
# Do not attempt to write to slave
|
||||||
if [[ ! -z ${REDIS_SLAVEOF_IP} ]]; then
|
if [[ ! -z ${VALKEY_SLAVEOF_IP} ]]; then
|
||||||
REDIS_CMDLINE="redis-cli -h ${REDIS_SLAVEOF_IP} -p ${REDIS_SLAVEOF_PORT} -a ${REDISPASS} --no-auth-warning"
|
VALKEY_CMDLINE="redis-cli -h ${VALKEY_SLAVEOF_IP} -p ${VALKEY_SLAVEOF_PORT} -a ${VALKEYPASS} --no-auth-warning"
|
||||||
else
|
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
|
fi
|
||||||
catch_non_zero "${REDIS_CMDLINE} LTRIM ACME_LOG 0 ${LOG_LINES}"
|
catch_non_zero "${VALKEY_CMDLINE} LTRIM ACME_LOG 0 ${LOG_LINES}"
|
||||||
catch_non_zero "${REDIS_CMDLINE} LTRIM POSTFIX_MAILLOG 0 ${LOG_LINES}"
|
catch_non_zero "${VALKEY_CMDLINE} LTRIM POSTFIX_MAILLOG 0 ${LOG_LINES}"
|
||||||
catch_non_zero "${REDIS_CMDLINE} LTRIM DOVECOT_MAILLOG 0 ${LOG_LINES}"
|
catch_non_zero "${VALKEY_CMDLINE} LTRIM DOVECOT_MAILLOG 0 ${LOG_LINES}"
|
||||||
catch_non_zero "${REDIS_CMDLINE} LTRIM SOGO_LOG 0 ${LOG_LINES}"
|
catch_non_zero "${VALKEY_CMDLINE} LTRIM SOGO_LOG 0 ${LOG_LINES}"
|
||||||
catch_non_zero "${REDIS_CMDLINE} LTRIM NETFILTER_LOG 0 ${LOG_LINES}"
|
catch_non_zero "${VALKEY_CMDLINE} LTRIM NETFILTER_LOG 0 ${LOG_LINES}"
|
||||||
catch_non_zero "${REDIS_CMDLINE} LTRIM AUTODISCOVER_LOG 0 ${LOG_LINES}"
|
catch_non_zero "${VALKEY_CMDLINE} LTRIM AUTODISCOVER_LOG 0 ${LOG_LINES}"
|
||||||
catch_non_zero "${REDIS_CMDLINE} LTRIM API_LOG 0 ${LOG_LINES}"
|
catch_non_zero "${VALKEY_CMDLINE} LTRIM API_LOG 0 ${LOG_LINES}"
|
||||||
catch_non_zero "${REDIS_CMDLINE} LTRIM RL_LOG 0 ${LOG_LINES}"
|
catch_non_zero "${VALKEY_CMDLINE} LTRIM RL_LOG 0 ${LOG_LINES}"
|
||||||
catch_non_zero "${REDIS_CMDLINE} LTRIM WATCHDOG_LOG 0 ${LOG_LINES}"
|
catch_non_zero "${VALKEY_CMDLINE} LTRIM WATCHDOG_LOG 0 ${LOG_LINES}"
|
||||||
catch_non_zero "${REDIS_CMDLINE} LTRIM CRON_LOG 0 ${LOG_LINES}"
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM alpine:3.23
|
FROM alpine:3.21
|
||||||
|
|
||||||
LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"
|
LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"
|
||||||
|
|
||||||
|
|||||||
@@ -44,25 +44,24 @@ def refreshF2boptions():
|
|||||||
global exit_code
|
global exit_code
|
||||||
f2boptions = {}
|
f2boptions = {}
|
||||||
|
|
||||||
if not r.get('F2B_OPTIONS'):
|
if not valkey.get('F2B_OPTIONS'):
|
||||||
f2boptions['ban_time'] = r.get('F2B_BAN_TIME')
|
f2boptions['ban_time'] = valkey.get('F2B_BAN_TIME')
|
||||||
f2boptions['max_ban_time'] = r.get('F2B_MAX_BAN_TIME')
|
f2boptions['max_ban_time'] = valkey.get('F2B_MAX_BAN_TIME')
|
||||||
f2boptions['ban_time_increment'] = r.get('F2B_BAN_TIME_INCREMENT')
|
f2boptions['ban_time_increment'] = valkey.get('F2B_BAN_TIME_INCREMENT')
|
||||||
f2boptions['max_attempts'] = r.get('F2B_MAX_ATTEMPTS')
|
f2boptions['max_attempts'] = valkey.get('F2B_MAX_ATTEMPTS')
|
||||||
f2boptions['retry_window'] = r.get('F2B_RETRY_WINDOW')
|
f2boptions['retry_window'] = valkey.get('F2B_RETRY_WINDOW')
|
||||||
f2boptions['netban_ipv4'] = r.get('F2B_NETBAN_IPV4')
|
f2boptions['netban_ipv4'] = valkey.get('F2B_NETBAN_IPV4')
|
||||||
f2boptions['netban_ipv6'] = r.get('F2B_NETBAN_IPV6')
|
f2boptions['netban_ipv6'] = valkey.get('F2B_NETBAN_IPV6')
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
f2boptions = json.loads(r.get('F2B_OPTIONS'))
|
f2boptions = json.loads(valkey.get('F2B_OPTIONS'))
|
||||||
except ValueError as e:
|
except ValueError:
|
||||||
logger.logCrit(
|
logger.logCrit('Error loading F2B options: F2B_OPTIONS is not json')
|
||||||
'Error loading F2B options: F2B_OPTIONS is not json. Exception: %s' % e)
|
|
||||||
quit_now = True
|
quit_now = True
|
||||||
exit_code = 2
|
exit_code = 2
|
||||||
|
|
||||||
verifyF2boptions(f2boptions)
|
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):
|
def verifyF2boptions(f2boptions):
|
||||||
verifyF2boption(f2boptions, 'ban_time', 1800)
|
verifyF2boption(f2boptions, 'ban_time', 1800)
|
||||||
@@ -82,7 +81,7 @@ def refreshF2bregex():
|
|||||||
global f2bregex
|
global f2bregex
|
||||||
global quit_now
|
global quit_now
|
||||||
global exit_code
|
global exit_code
|
||||||
if not r.get('F2B_REGEX'):
|
if not valkey.get('F2B_REGEX'):
|
||||||
f2bregex = {}
|
f2bregex = {}
|
||||||
f2bregex[1] = r'mailcow UI: Invalid password for .+ by ([0-9a-f\.:]+)'
|
f2bregex[1] = r'mailcow UI: Invalid password for .+ by ([0-9a-f\.:]+)'
|
||||||
f2bregex[2] = r'Rspamd UI: Invalid password by ([0-9a-f\.:]+)'
|
f2bregex[2] = r'Rspamd UI: Invalid password by ([0-9a-f\.:]+)'
|
||||||
@@ -93,11 +92,11 @@ def refreshF2bregex():
|
|||||||
f2bregex[7] = r'\w+\([^,]+,([0-9a-f\.:]+),<[^>]+>\): unknown user \(SHA1 of given password: [a-f0-9]+\)'
|
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[8] = r'SOGo.+ Login from \'([0-9a-f\.:]+)\' for user .+ might not have worked'
|
||||||
f2bregex[9] = r'([0-9a-f\.:]+) \"GET \/SOGo\/.* HTTP.+\" 403 .+'
|
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:
|
else:
|
||||||
try:
|
try:
|
||||||
f2bregex = {}
|
f2bregex = {}
|
||||||
f2bregex = json.loads(r.get('F2B_REGEX'))
|
f2bregex = json.loads(valkey.get('F2B_REGEX'))
|
||||||
except ValueError:
|
except ValueError:
|
||||||
logger.logCrit('Error loading F2B options: F2B_REGEX is not json')
|
logger.logCrit('Error loading F2B options: F2B_REGEX is not json')
|
||||||
quit_now = True
|
quit_now = True
|
||||||
@@ -176,7 +175,7 @@ def ban(address):
|
|||||||
|
|
||||||
logdebug("Updating F2B_ACTIVE_BANS[%s]=%d" %
|
logdebug("Updating F2B_ACTIVE_BANS[%s]=%d" %
|
||||||
(net, cur_time + NET_BAN_TIME))
|
(net, cur_time + NET_BAN_TIME))
|
||||||
r.hset('F2B_ACTIVE_BANS', '%s' % net, cur_time + NET_BAN_TIME)
|
valkey.hset('F2B_ACTIVE_BANS', '%s' % net, cur_time + NET_BAN_TIME)
|
||||||
else:
|
else:
|
||||||
logger.logWarn('%d more attempts in the next %d seconds until %s is banned' % (
|
logger.logWarn('%d more attempts in the next %d seconds until %s is banned' % (
|
||||||
MAX_ATTEMPTS - bans[net]['attempts'], RETRY_WINDOW, net))
|
MAX_ATTEMPTS - bans[net]['attempts'], RETRY_WINDOW, net))
|
||||||
@@ -187,7 +186,7 @@ def unban(net):
|
|||||||
if not net in bans:
|
if not net in bans:
|
||||||
logger.logInfo(
|
logger.logInfo(
|
||||||
'%s is not banned, skipping unban and deleting from queue (if any)' % net)
|
'%s is not banned, skipping unban and deleting from queue (if any)' % net)
|
||||||
r.hdel('F2B_QUEUE_UNBAN', '%s' % net)
|
valkey.hdel('F2B_QUEUE_UNBAN', '%s' % net)
|
||||||
return
|
return
|
||||||
logger.logInfo('Unbanning %s' % net)
|
logger.logInfo('Unbanning %s' % net)
|
||||||
if type(ipaddress.ip_network(net)) is ipaddress.IPv4Network:
|
if type(ipaddress.ip_network(net)) is ipaddress.IPv4Network:
|
||||||
@@ -198,8 +197,8 @@ def unban(net):
|
|||||||
with lock:
|
with lock:
|
||||||
logdebug("Calling tables.unbanIPv6(%s)" % net)
|
logdebug("Calling tables.unbanIPv6(%s)" % net)
|
||||||
tables.unbanIPv6(net)
|
tables.unbanIPv6(net)
|
||||||
r.hdel('F2B_ACTIVE_BANS', '%s' % net)
|
valkey.hdel('F2B_ACTIVE_BANS', '%s' % net)
|
||||||
r.hdel('F2B_QUEUE_UNBAN', '%s' % net)
|
valkey.hdel('F2B_QUEUE_UNBAN', '%s' % net)
|
||||||
if net in bans:
|
if net in bans:
|
||||||
logdebug("Unban for %s, setting attempts=0, ban_counter+=1" % net)
|
logdebug("Unban for %s, setting attempts=0, ban_counter+=1" % net)
|
||||||
bans[net]['attempts'] = 0
|
bans[net]['attempts'] = 0
|
||||||
@@ -226,10 +225,10 @@ def permBan(net, unban=False):
|
|||||||
|
|
||||||
|
|
||||||
if is_unbanned:
|
if is_unbanned:
|
||||||
r.hdel('F2B_PERM_BANS', '%s' % net)
|
valkey.hdel('F2B_PERM_BANS', '%s' % net)
|
||||||
logger.logCrit('Removed host/network %s from denylist' % net)
|
logger.logCrit('Removed host/network %s from denylist' % net)
|
||||||
elif is_banned:
|
elif is_banned:
|
||||||
r.hset('F2B_PERM_BANS', '%s' % net, int(round(time.time())))
|
valkey.hset('F2B_PERM_BANS', '%s' % net, int(round(time.time())))
|
||||||
logger.logCrit('Added host/network %s to denylist' % net)
|
logger.logCrit('Added host/network %s to denylist' % net)
|
||||||
|
|
||||||
def clear():
|
def clear():
|
||||||
@@ -244,17 +243,17 @@ def clear():
|
|||||||
tables.clearIPv6Table()
|
tables.clearIPv6Table()
|
||||||
try:
|
try:
|
||||||
if r is not None:
|
if r is not None:
|
||||||
r.delete('F2B_ACTIVE_BANS')
|
valkey.delete('F2B_ACTIVE_BANS')
|
||||||
r.delete('F2B_PERM_BANS')
|
valkey.delete('F2B_PERM_BANS')
|
||||||
except Exception as ex:
|
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():
|
def watch():
|
||||||
global pubsub
|
global pubsub
|
||||||
global quit_now
|
global quit_now
|
||||||
global exit_code
|
global exit_code
|
||||||
|
|
||||||
logger.logInfo('Watching Redis channel F2B_CHANNEL')
|
logger.logInfo('Watching Valkey channel F2B_CHANNEL')
|
||||||
pubsub.subscribe('F2B_CHANNEL')
|
pubsub.subscribe('F2B_CHANNEL')
|
||||||
|
|
||||||
while not quit_now:
|
while not quit_now:
|
||||||
@@ -306,7 +305,7 @@ def autopurge():
|
|||||||
time.sleep(10)
|
time.sleep(10)
|
||||||
refreshF2boptions()
|
refreshF2boptions()
|
||||||
MAX_ATTEMPTS = int(f2boptions['max_attempts'])
|
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)
|
logdebug("QUEUE_UNBAN: %s" % QUEUE_UNBAN)
|
||||||
if QUEUE_UNBAN:
|
if QUEUE_UNBAN:
|
||||||
for net in QUEUE_UNBAN:
|
for net in QUEUE_UNBAN:
|
||||||
@@ -391,7 +390,7 @@ def whitelistUpdate():
|
|||||||
global WHITELIST
|
global WHITELIST
|
||||||
while not quit_now:
|
while not quit_now:
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
list = r.hgetall('F2B_WHITELIST')
|
list = valkey.hgetall('F2B_WHITELIST')
|
||||||
new_whitelist = []
|
new_whitelist = []
|
||||||
if list:
|
if list:
|
||||||
new_whitelist = genNetworkList(list)
|
new_whitelist = genNetworkList(list)
|
||||||
@@ -406,7 +405,7 @@ def blacklistUpdate():
|
|||||||
global BLACKLIST
|
global BLACKLIST
|
||||||
while not quit_now:
|
while not quit_now:
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
list = r.hgetall('F2B_BLACKLIST')
|
list = valkey.hgetall('F2B_BLACKLIST')
|
||||||
new_blacklist = []
|
new_blacklist = []
|
||||||
if list:
|
if list:
|
||||||
new_blacklist = genNetworkList(list)
|
new_blacklist = genNetworkList(list)
|
||||||
@@ -467,35 +466,35 @@ if __name__ == '__main__':
|
|||||||
logger.logInfo(f"Setting {chain_name} isolation")
|
logger.logInfo(f"Setting {chain_name} isolation")
|
||||||
tables.create_mailcow_isolation_rule("br-mailcow", [3306, 6379, 8983, 12345], os.getenv("MAILCOW_REPLICA_IP"))
|
tables.create_mailcow_isolation_rule("br-mailcow", [3306, 6379, 8983, 12345], os.getenv("MAILCOW_REPLICA_IP"))
|
||||||
|
|
||||||
# connect to redis
|
# connect to valkey
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
redis_slaveof_ip = os.getenv('REDIS_SLAVEOF_IP', '')
|
valkey_slaveof_ip = os.getenv('VALKEY_SLAVEOF_IP', '')
|
||||||
redis_slaveof_port = os.getenv('REDIS_SLAVEOF_PORT', '')
|
valkey_slaveof_port = os.getenv('VALKEY_SLAVEOF_PORT', '')
|
||||||
logdebug(
|
logdebug(
|
||||||
"Connecting redis (SLAVEOF_IP:%s, PORT:%s)" % (redis_slaveof_ip, redis_slaveof_port))
|
"Connecting valkey (SLAVEOF_IP:%s, PORT:%s)" % (valkey_slaveof_ip, valkey_slaveof_port))
|
||||||
if "".__eq__(redis_slaveof_ip):
|
if "".__eq__(valkey_slaveof_ip):
|
||||||
r = redis.StrictRedis(
|
valkey = redis.StrictRedis(
|
||||||
host=os.getenv('IPV4_NETWORK', '172.22.1') + '.249', decode_responses=True, port=6379, db=0, password=os.environ['REDISPASS'])
|
host=os.getenv('IPV4_NETWORK', '172.22.1') + '.249', decode_responses=True, port=6379, db=0, password=os.environ['VALKEYPASS'])
|
||||||
else:
|
else:
|
||||||
r = redis.StrictRedis(
|
valkey = redis.StrictRedis(
|
||||||
host=redis_slaveof_ip, decode_responses=True, port=redis_slaveof_port, db=0, password=os.environ['REDISPASS'])
|
host=valkey_slaveof_ip, decode_responses=True, port=valkey_slaveof_port, db=0, password=os.environ['VALKEYPASS'])
|
||||||
r.ping()
|
valkey.ping()
|
||||||
pubsub = r.pubsub()
|
pubsub = valkey.pubsub()
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
logdebug(
|
logdebug(
|
||||||
'Redis connection failed: %s - trying again in 3 seconds' % (ex))
|
'Redis connection failed: %s - trying again in 3 seconds' % (ex))
|
||||||
time.sleep(3)
|
time.sleep(3)
|
||||||
else:
|
else:
|
||||||
break
|
break
|
||||||
logger.set_redis(r)
|
logger.set_valkey(valkey)
|
||||||
logdebug("Redis connection established, setting up F2B keys")
|
logdebug("Valkey connection established, setting up F2B keys")
|
||||||
|
|
||||||
if r.exists('F2B_LOG'):
|
if valkey.exists('F2B_LOG'):
|
||||||
logdebug("Renaming F2B_LOG to NETFILTER_LOG")
|
logdebug("Renaming F2B_LOG to NETFILTER_LOG")
|
||||||
r.rename('F2B_LOG', 'NETFILTER_LOG')
|
valkey.rename('F2B_LOG', 'NETFILTER_LOG')
|
||||||
r.delete('F2B_ACTIVE_BANS')
|
valkey.delete('F2B_ACTIVE_BANS')
|
||||||
r.delete('F2B_PERM_BANS')
|
valkey.delete('F2B_PERM_BANS')
|
||||||
|
|
||||||
refreshF2boptions()
|
refreshF2boptions()
|
||||||
|
|
||||||
|
|||||||
@@ -4,19 +4,19 @@ import datetime
|
|||||||
|
|
||||||
class Logger:
|
class Logger:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.r = None
|
self.valkey = None
|
||||||
|
|
||||||
def set_redis(self, redis):
|
def set_valkey(self, valkey):
|
||||||
self.r = redis
|
self.valkey = valkey
|
||||||
|
|
||||||
def _format_timestamp(self):
|
def _format_timestamp(self):
|
||||||
# Local time with milliseconds
|
# Local time with milliseconds
|
||||||
return datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
return datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||||
|
|
||||||
def log(self, priority, message):
|
def log(self, priority, message):
|
||||||
# build redis-friendly dict
|
# build valkey-friendly dict
|
||||||
tolog = {
|
tolog = {
|
||||||
'time': int(round(time.time())), # keep raw timestamp for Redis
|
'time': int(round(time.time())), # keep raw timestamp for Valkey
|
||||||
'priority': priority,
|
'priority': priority,
|
||||||
'message': message
|
'message': message
|
||||||
}
|
}
|
||||||
@@ -26,11 +26,11 @@ class Logger:
|
|||||||
print(f"{ts} {priority.upper()}: {message}", flush=True)
|
print(f"{ts} {priority.upper()}: {message}", flush=True)
|
||||||
|
|
||||||
# also push JSON to Redis if connected
|
# also push JSON to Redis if connected
|
||||||
if self.r is not None:
|
if self.valkey is not None:
|
||||||
try:
|
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:
|
except Exception as ex:
|
||||||
print(f'{ts} WARN: Failed logging to redis: {ex}', flush=True)
|
print(f'{ts} WARN: Failed logging to valkey: {ex}', flush=True)
|
||||||
|
|
||||||
def logWarn(self, message):
|
def logWarn(self, message):
|
||||||
self.log('warn', message)
|
self.log('warn', message)
|
||||||
|
|||||||
@@ -3,17 +3,17 @@ FROM php:8.2-fpm-alpine3.21
|
|||||||
LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"
|
LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"
|
||||||
|
|
||||||
# renovate: datasource=github-tags depName=krakjoe/apcu versioning=semver-coerced extractVersion=^v(?<version>.*)$
|
# renovate: datasource=github-tags depName=krakjoe/apcu versioning=semver-coerced extractVersion=^v(?<version>.*)$
|
||||||
ARG APCU_PECL_VERSION=5.1.28
|
ARG APCU_PECL_VERSION=5.1.26
|
||||||
# renovate: datasource=github-tags depName=Imagick/imagick versioning=semver-coerced extractVersion=(?<version>.*)$
|
# renovate: datasource=github-tags depName=Imagick/imagick versioning=semver-coerced extractVersion=(?<version>.*)$
|
||||||
ARG IMAGICK_PECL_VERSION=3.8.1
|
ARG IMAGICK_PECL_VERSION=3.8.0
|
||||||
# renovate: datasource=github-tags depName=php/pecl-mail-mailparse versioning=semver-coerced extractVersion=^v(?<version>.*)$
|
# renovate: datasource=github-tags depName=php/pecl-mail-mailparse versioning=semver-coerced extractVersion=^v(?<version>.*)$
|
||||||
ARG MAILPARSE_PECL_VERSION=3.1.9
|
ARG MAILPARSE_PECL_VERSION=3.1.8
|
||||||
# renovate: datasource=github-tags depName=php-memcached-dev/php-memcached versioning=semver-coerced extractVersion=^v(?<version>.*)$
|
# renovate: datasource=github-tags depName=php-memcached-dev/php-memcached versioning=semver-coerced extractVersion=^v(?<version>.*)$
|
||||||
ARG MEMCACHED_PECL_VERSION=3.4.0
|
ARG MEMCACHED_PECL_VERSION=3.3.0
|
||||||
# renovate: datasource=github-tags depName=phpredis/phpredis versioning=semver-coerced extractVersion=(?<version>.*)$
|
# renovate: datasource=github-tags depName=phpredis/phpredis versioning=semver-coerced extractVersion=(?<version>.*)$
|
||||||
ARG REDIS_PECL_VERSION=6.3.0
|
ARG REDIS_PECL_VERSION=6.2.0
|
||||||
# renovate: datasource=github-tags depName=composer/composer versioning=semver-coerced extractVersion=(?<version>.*)$
|
# renovate: datasource=github-tags depName=composer/composer versioning=semver-coerced extractVersion=(?<version>.*)$
|
||||||
ARG COMPOSER_VERSION=2.9.5
|
ARG COMPOSER_VERSION=2.8.6
|
||||||
|
|
||||||
RUN apk add -U --no-cache autoconf \
|
RUN apk add -U --no-cache autoconf \
|
||||||
aspell-dev \
|
aspell-dev \
|
||||||
|
|||||||
@@ -9,24 +9,24 @@ while ! mariadb-admin status --ssl=false --socket=/var/run/mysqld/mysqld.sock -u
|
|||||||
done
|
done
|
||||||
|
|
||||||
# Do not attempt to write to slave
|
# Do not attempt to write to slave
|
||||||
if [[ ! -z ${REDIS_SLAVEOF_IP} ]]; then
|
if [[ ! -z ${VALKEY_SLAVEOF_IP} ]]; then
|
||||||
REDIS_HOST=$REDIS_SLAVEOF_IP
|
VALKEY_HOST=$VALKEY_SLAVEOF_IP
|
||||||
REDIS_PORT=$REDIS_SLAVEOF_PORT
|
VALKEY_PORT=$VALKEY_SLAVEOF_PORT
|
||||||
else
|
else
|
||||||
REDIS_HOST="redis"
|
VALKEY_HOST="valkey-mailcow"
|
||||||
REDIS_PORT="6379"
|
VALKEY_PORT="6379"
|
||||||
fi
|
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
|
until [[ $(${VALKEY_CMDLINE} PING) == "PONG" ]]; do
|
||||||
echo "Waiting for Redis..."
|
echo "Waiting for Valkey..."
|
||||||
sleep 2
|
sleep 2
|
||||||
done
|
done
|
||||||
|
|
||||||
# Set redis session store
|
# Set valkey session store
|
||||||
echo -n '
|
echo -n '
|
||||||
session.save_handler = redis
|
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
|
' > /usr/local/etc/php/conf.d/session_store.ini
|
||||||
|
|
||||||
# Check mysql_upgrade (master and slave)
|
# Check mysql_upgrade (master and slave)
|
||||||
@@ -91,22 +91,22 @@ fi
|
|||||||
if [[ "${MASTER}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
|
if [[ "${MASTER}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
|
||||||
echo "We are master, preparing..."
|
echo "We are master, preparing..."
|
||||||
# Set a default release format
|
# Set a default release format
|
||||||
if [[ -z $(${REDIS_CMDLINE} --raw GET Q_RELEASE_FORMAT) ]]; then
|
if [[ -z $(${VALKEY_CMDLINE} --raw GET Q_RELEASE_FORMAT) ]]; then
|
||||||
${REDIS_CMDLINE} --raw SET Q_RELEASE_FORMAT raw
|
${VALKEY_CMDLINE} --raw SET Q_RELEASE_FORMAT raw
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Set max age of q items - if unset
|
# Set max age of q items - if unset
|
||||||
if [[ -z $(${REDIS_CMDLINE} --raw GET Q_MAX_AGE) ]]; then
|
if [[ -z $(${VALKEY_CMDLINE} --raw GET Q_MAX_AGE) ]]; then
|
||||||
${REDIS_CMDLINE} --raw SET Q_MAX_AGE 365
|
${VALKEY_CMDLINE} --raw SET Q_MAX_AGE 365
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Set default password policy - if unset
|
# Set default password policy - if unset
|
||||||
if [[ -z $(${REDIS_CMDLINE} --raw HGET PASSWD_POLICY length) ]]; then
|
if [[ -z $(${VALKEY_CMDLINE} --raw HGET PASSWD_POLICY length) ]]; then
|
||||||
${REDIS_CMDLINE} --raw HSET PASSWD_POLICY length 6
|
${VALKEY_CMDLINE} --raw HSET PASSWD_POLICY length 6
|
||||||
${REDIS_CMDLINE} --raw HSET PASSWD_POLICY chars 0
|
${VALKEY_CMDLINE} --raw HSET PASSWD_POLICY chars 0
|
||||||
${REDIS_CMDLINE} --raw HSET PASSWD_POLICY special_chars 0
|
${VALKEY_CMDLINE} --raw HSET PASSWD_POLICY special_chars 0
|
||||||
${REDIS_CMDLINE} --raw HSET PASSWD_POLICY lowerupper 0
|
${VALKEY_CMDLINE} --raw HSET PASSWD_POLICY lowerupper 0
|
||||||
${REDIS_CMDLINE} --raw HSET PASSWD_POLICY numbers 0
|
${VALKEY_CMDLINE} --raw HSET PASSWD_POLICY numbers 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Trigger db init
|
# 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
|
php -c /usr/local/etc/php -f /web/inc/init_db.inc.php
|
||||||
|
|
||||||
# Recreating domain map
|
# Recreating domain map
|
||||||
echo "Rebuilding domain map in Redis..."
|
echo "Rebuilding domain map in Valkey..."
|
||||||
declare -a DOMAIN_ARR
|
declare -a DOMAIN_ARR
|
||||||
${REDIS_CMDLINE} DEL DOMAIN_MAP > /dev/null
|
${VALKEY_CMDLINE} DEL DOMAIN_MAP > /dev/null
|
||||||
while read line
|
while read line
|
||||||
do
|
do
|
||||||
DOMAIN_ARR+=("$line")
|
DOMAIN_ARR+=("$line")
|
||||||
@@ -128,7 +128,7 @@ if [[ "${MASTER}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
|
|||||||
|
|
||||||
if [[ ! -z ${DOMAIN_ARR} ]]; then
|
if [[ ! -z ${DOMAIN_ARR} ]]; then
|
||||||
for domain in "${DOMAIN_ARR[@]}"; do
|
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
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -167,7 +167,7 @@ DELIMITER //
|
|||||||
CREATE EVENT clean_spamalias
|
CREATE EVENT clean_spamalias
|
||||||
ON SCHEDULE EVERY 1 DAY DO
|
ON SCHEDULE EVERY 1 DAY DO
|
||||||
BEGIN
|
BEGIN
|
||||||
DELETE FROM spamalias WHERE validity < UNIX_TIMESTAMP() AND permanent = 0;
|
DELETE FROM spamalias WHERE validity < UNIX_TIMESTAMP();
|
||||||
END;
|
END;
|
||||||
//
|
//
|
||||||
DELIMITER ;
|
DELIMITER ;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ WORKDIR /src
|
|||||||
ENV CGO_ENABLED=0 \
|
ENV CGO_ENABLED=0 \
|
||||||
GO111MODULE=on \
|
GO111MODULE=on \
|
||||||
NOOPT=1 \
|
NOOPT=1 \
|
||||||
VERSION=1.8.22
|
VERSION=1.8.14
|
||||||
|
|
||||||
RUN git clone --branch v${VERSION} https://github.com/Zuplu/postfix-tlspol && \
|
RUN git clone --branch v${VERSION} https://github.com/Zuplu/postfix-tlspol && \
|
||||||
cd /src/postfix-tlspol && \
|
cd /src/postfix-tlspol && \
|
||||||
@@ -34,7 +34,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
|||||||
|
|
||||||
COPY supervisord.conf /etc/supervisor/supervisord.conf
|
COPY supervisord.conf /etc/supervisor/supervisord.conf
|
||||||
COPY syslog-ng.conf /etc/syslog-ng/syslog-ng.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-tlspol.sh /opt/postfix-tlspol.sh
|
COPY postfix-tlspol.sh /opt/postfix-tlspol.sh
|
||||||
COPY stop-supervisor.sh /usr/local/sbin/stop-supervisor.sh
|
COPY stop-supervisor.sh /usr/local/sbin/stop-supervisor.sh
|
||||||
COPY docker-entrypoint.sh /docker-entrypoint.sh
|
COPY docker-entrypoint.sh /docker-entrypoint.sh
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
if [[ ! -z ${REDIS_SLAVEOF_IP} ]]; then
|
if [[ ! -z ${VALKEY_SLAVEOF_IP} ]]; then
|
||||||
cp /etc/syslog-ng/syslog-ng-redis_slave.conf /etc/syslog-ng/syslog-ng.conf
|
cp /etc/syslog-ng/syslog-ng-valkey_slave.conf /etc/syslog-ng/syslog-ng.conf
|
||||||
fi
|
fi
|
||||||
|
|
||||||
exec "$@"
|
exec "$@"
|
||||||
@@ -17,14 +17,14 @@ until dig +short mailcow.email > /dev/null; do
|
|||||||
done
|
done
|
||||||
|
|
||||||
# Do not attempt to write to slave
|
# Do not attempt to write to slave
|
||||||
if [[ ! -z ${REDIS_SLAVEOF_IP} ]]; then
|
if [[ ! -z ${VALKEY_SLAVEOF_IP} ]]; then
|
||||||
export REDIS_CMDLINE="redis-cli -h ${REDIS_SLAVEOF_IP} -p ${REDIS_SLAVEOF_PORT} -a ${REDISPASS} --no-auth-warning"
|
export VALKEY_CMDLINE="redis-cli -h ${VALKEY_SLAVEOF_IP} -p ${VALKEY_SLAVEOF_PORT} -a ${VALKEYPASS} --no-auth-warning"
|
||||||
else
|
else
|
||||||
export REDIS_CMDLINE="redis-cli -h redis -p 6379 -a ${REDISPASS} --no-auth-warning"
|
export VALKEY_CMDLINE="redis-cli -h valkey -p 6379 -a ${VALKEYPASS} --no-auth-warning"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
until [[ $(${REDIS_CMDLINE} PING) == "PONG" ]]; do
|
until [[ $(${VALKEY_CMDLINE} PING) == "PONG" ]]; do
|
||||||
echo "Waiting for Redis..."
|
echo "Waiting for Valkey..."
|
||||||
sleep 2
|
sleep 2
|
||||||
done
|
done
|
||||||
|
|
||||||
|
|||||||
@@ -15,12 +15,12 @@ source s_src {
|
|||||||
internal();
|
internal();
|
||||||
};
|
};
|
||||||
destination d_stdout { pipe("/dev/stdout"); };
|
destination d_stdout { pipe("/dev/stdout"); };
|
||||||
destination d_redis_ui_log {
|
destination d_valkey_ui_log {
|
||||||
redis(
|
redis(
|
||||||
host("`REDIS_SLAVEOF_IP`")
|
host("`VALKEY_SLAVEOF_IP`")
|
||||||
persist-name("redis1")
|
persist-name("valkey1")
|
||||||
port(`REDIS_SLAVEOF_PORT`)
|
port(`VALKEY_SLAVEOF_PORT`)
|
||||||
auth("`REDISPASS`")
|
auth("`VALKEYPASS`")
|
||||||
command("LPUSH" "POSTFIX_MAILLOG" "$(format-json time=\"$S_UNIXTIME\" priority=\"$PRIORITY\" program=\"$PROGRAM\" message=\"$MESSAGE\")\n")
|
command("LPUSH" "POSTFIX_MAILLOG" "$(format-json time=\"$S_UNIXTIME\" priority=\"$PRIORITY\" program=\"$PROGRAM\" message=\"$MESSAGE\")\n")
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -41,5 +41,5 @@ log {
|
|||||||
filter(f_ignore);
|
filter(f_ignore);
|
||||||
destination(d_stdout);
|
destination(d_stdout);
|
||||||
filter(f_mail);
|
filter(f_mail);
|
||||||
destination(d_redis_ui_log);
|
destination(d_valkey_ui_log);
|
||||||
};
|
};
|
||||||
@@ -15,12 +15,12 @@ source s_src {
|
|||||||
internal();
|
internal();
|
||||||
};
|
};
|
||||||
destination d_stdout { pipe("/dev/stdout"); };
|
destination d_stdout { pipe("/dev/stdout"); };
|
||||||
destination d_redis_ui_log {
|
destination d_valkey_ui_log {
|
||||||
redis(
|
redis(
|
||||||
host("redis-mailcow")
|
host("valkey-mailcow")
|
||||||
persist-name("redis1")
|
persist-name("valkey1")
|
||||||
port(6379)
|
port(6379)
|
||||||
auth("`REDISPASS`")
|
auth("`VALKEYPASS`")
|
||||||
command("LPUSH" "POSTFIX_MAILLOG" "$(format-json time=\"$S_UNIXTIME\" priority=\"$PRIORITY\" program=\"$PROGRAM\" message=\"$MESSAGE\")\n")
|
command("LPUSH" "POSTFIX_MAILLOG" "$(format-json time=\"$S_UNIXTIME\" priority=\"$PRIORITY\" program=\"$PROGRAM\" message=\"$MESSAGE\")\n")
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -41,5 +41,5 @@ log {
|
|||||||
filter(f_ignore);
|
filter(f_ignore);
|
||||||
destination(d_stdout);
|
destination(d_stdout);
|
||||||
filter(f_mail);
|
filter(f_mail);
|
||||||
destination(d_redis_ui_log);
|
destination(d_valkey_ui_log);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ RUN groupadd -g 102 postfix \
|
|||||||
|
|
||||||
COPY supervisord.conf /etc/supervisor/supervisord.conf
|
COPY supervisord.conf /etc/supervisor/supervisord.conf
|
||||||
COPY syslog-ng.conf /etc/syslog-ng/syslog-ng.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 postfix.sh /opt/postfix.sh
|
||||||
COPY rspamd-pipe-ham /usr/local/bin/rspamd-pipe-ham
|
COPY rspamd-pipe-ham /usr/local/bin/rspamd-pipe-ham
|
||||||
COPY rspamd-pipe-spam /usr/local/bin/rspamd-pipe-spam
|
COPY rspamd-pipe-spam /usr/local/bin/rspamd-pipe-spam
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ for file in /hooks/*; do
|
|||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
if [[ ! -z ${REDIS_SLAVEOF_IP} ]]; then
|
if [[ ! -z ${VALKEY_SLAVEOF_IP} ]]; then
|
||||||
cp /etc/syslog-ng/syslog-ng-redis_slave.conf /etc/syslog-ng/syslog-ng.conf
|
cp /etc/syslog-ng/syslog-ng-valkey_slave.conf /etc/syslog-ng/syslog-ng.conf
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Fix OpenSSL 3.X TLS1.0, 1.1 support (https://community.mailcow.email/d/4062-hi-all/20)
|
# Fix OpenSSL 3.X TLS1.0, 1.1 support (https://community.mailcow.email/d/4062-hi-all/20)
|
||||||
|
|||||||
@@ -329,17 +329,14 @@ query = SELECT goto FROM alias
|
|||||||
SELECT id FROM alias
|
SELECT id FROM alias
|
||||||
WHERE address='%s'
|
WHERE address='%s'
|
||||||
AND (active='1' OR active='2')
|
AND (active='1' OR active='2')
|
||||||
AND sender_allowed='1'
|
|
||||||
), (
|
), (
|
||||||
SELECT id FROM alias
|
SELECT id FROM alias
|
||||||
WHERE address='@%d'
|
WHERE address='@%d'
|
||||||
AND (active='1' OR active='2')
|
AND (active='1' OR active='2')
|
||||||
AND sender_allowed='1'
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
AND active='1'
|
AND active='1'
|
||||||
AND sender_allowed='1'
|
|
||||||
AND (domain IN
|
AND (domain IN
|
||||||
(SELECT domain FROM domain
|
(SELECT domain FROM domain
|
||||||
WHERE domain='%d'
|
WHERE domain='%d'
|
||||||
@@ -393,7 +390,7 @@ hosts = unix:/var/run/mysqld/mysqld.sock
|
|||||||
dbname = ${DBNAME}
|
dbname = ${DBNAME}
|
||||||
query = SELECT goto FROM spamalias
|
query = SELECT goto FROM spamalias
|
||||||
WHERE address='%s'
|
WHERE address='%s'
|
||||||
AND (validity >= UNIX_TIMESTAMP() OR permanent != 0)
|
AND validity >= UNIX_TIMESTAMP()
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
if [ ! -f /opt/postfix/conf/dns_blocklists.cf ]; then
|
if [ ! -f /opt/postfix/conf/dns_blocklists.cf ]; then
|
||||||
|
|||||||
@@ -15,21 +15,21 @@ source s_src {
|
|||||||
internal();
|
internal();
|
||||||
};
|
};
|
||||||
destination d_stdout { pipe("/dev/stdout"); };
|
destination d_stdout { pipe("/dev/stdout"); };
|
||||||
destination d_redis_ui_log {
|
destination d_valkey_ui_log {
|
||||||
redis(
|
redis(
|
||||||
host("`REDIS_SLAVEOF_IP`")
|
host("`VALKEY_SLAVEOF_IP`")
|
||||||
persist-name("redis1")
|
persist-name("valkey1")
|
||||||
port(`REDIS_SLAVEOF_PORT`)
|
port(`VALKEY_SLAVEOF_PORT`)
|
||||||
auth("`REDISPASS`")
|
auth("`VALKEYPASS`")
|
||||||
command("LPUSH" "POSTFIX_MAILLOG" "$(format-json time=\"$S_UNIXTIME\" priority=\"$PRIORITY\" program=\"$PROGRAM\" message=\"$MESSAGE\")\n")
|
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(
|
redis(
|
||||||
host("`REDIS_SLAVEOF_IP`")
|
host("`VALKEY_SLAVEOF_IP`")
|
||||||
persist-name("redis2")
|
persist-name("valkey2")
|
||||||
port(`REDIS_SLAVEOF_PORT`)
|
port(`VALKEY_SLAVEOF_PORT`)
|
||||||
auth("`REDISPASS`")
|
auth("`VALKEYPASS`")
|
||||||
command("PUBLISH" "F2B_CHANNEL" "$(sanitize $MESSAGE)")
|
command("PUBLISH" "F2B_CHANNEL" "$(sanitize $MESSAGE)")
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -50,6 +50,6 @@ log {
|
|||||||
filter(f_ignore);
|
filter(f_ignore);
|
||||||
destination(d_stdout);
|
destination(d_stdout);
|
||||||
filter(f_mail);
|
filter(f_mail);
|
||||||
destination(d_redis_ui_log);
|
destination(d_valkey_ui_log);
|
||||||
destination(d_redis_f2b_channel);
|
destination(d_valkey_f2b_channel);
|
||||||
};
|
};
|
||||||
@@ -15,21 +15,21 @@ source s_src {
|
|||||||
internal();
|
internal();
|
||||||
};
|
};
|
||||||
destination d_stdout { pipe("/dev/stdout"); };
|
destination d_stdout { pipe("/dev/stdout"); };
|
||||||
destination d_redis_ui_log {
|
destination d_valkey_ui_log {
|
||||||
redis(
|
redis(
|
||||||
host("redis-mailcow")
|
host("valkey-mailcow")
|
||||||
persist-name("redis1")
|
persist-name("valkey1")
|
||||||
port(6379)
|
port(6379)
|
||||||
auth("`REDISPASS`")
|
auth("`VALKEYPASS`")
|
||||||
command("LPUSH" "POSTFIX_MAILLOG" "$(format-json time=\"$S_UNIXTIME\" priority=\"$PRIORITY\" program=\"$PROGRAM\" message=\"$MESSAGE\")\n")
|
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(
|
redis(
|
||||||
host("redis-mailcow")
|
host("valkey-mailcow")
|
||||||
persist-name("redis2")
|
persist-name("valkey2")
|
||||||
port(6379)
|
port(6379)
|
||||||
auth("`REDISPASS`")
|
auth("`VALKEYPASS`")
|
||||||
command("PUBLISH" "F2B_CHANNEL" "$(sanitize $MESSAGE)")
|
command("PUBLISH" "F2B_CHANNEL" "$(sanitize $MESSAGE)")
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -50,6 +50,6 @@ log {
|
|||||||
filter(f_ignore);
|
filter(f_ignore);
|
||||||
destination(d_stdout);
|
destination(d_stdout);
|
||||||
filter(f_mail);
|
filter(f_mail);
|
||||||
destination(d_redis_ui_log);
|
destination(d_valkey_ui_log);
|
||||||
destination(d_redis_f2b_channel);
|
destination(d_valkey_f2b_channel);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
FROM debian:trixie-slim
|
FROM debian:bookworm-slim
|
||||||
LABEL maintainer="The Infrastructure Company GmbH <info@servercow.de>"
|
LABEL maintainer="The Infrastructure Company GmbH <info@servercow.de>"
|
||||||
|
|
||||||
ARG DEBIAN_FRONTEND=noninteractive
|
ARG DEBIAN_FRONTEND=noninteractive
|
||||||
ARG RSPAMD_VER=rspamd_3.14.3-1~236eb65
|
ARG RSPAMD_VER=rspamd_3.12.1-1~6dbfca2fa
|
||||||
ARG CODENAME=trixie
|
ARG CODENAME=bookworm
|
||||||
ENV LC_ALL=C
|
ENV LC_ALL=C
|
||||||
|
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
|||||||
@@ -52,33 +52,33 @@ if [[ ! -z ${RSPAMD_V6} ]]; then
|
|||||||
echo ${RSPAMD_V6}/128 >> /etc/rspamd/custom/rspamd_trusted.map
|
echo ${RSPAMD_V6}/128 >> /etc/rspamd/custom/rspamd_trusted.map
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ ! -z ${REDIS_SLAVEOF_IP} ]]; then
|
if [[ ! -z ${VALKEY_SLAVEOF_IP} ]]; then
|
||||||
cat <<EOF > /etc/rspamd/local.d/redis.conf
|
cat <<EOF > /etc/rspamd/local.d/redis.conf
|
||||||
read_servers = "redis:6379";
|
read_servers = "valkey-mailcow:6379";
|
||||||
write_servers = "${REDIS_SLAVEOF_IP}:${REDIS_SLAVEOF_PORT}";
|
write_servers = "${VALKEY_SLAVEOF_IP}:${VALKEY_SLAVEOF_PORT}";
|
||||||
password = "${REDISPASS}";
|
password = "${VALKEYPASS}";
|
||||||
timeout = 10;
|
timeout = 10;
|
||||||
EOF
|
EOF
|
||||||
until [[ $(redis-cli -h redis-mailcow -a ${REDISPASS} --no-auth-warning PING) == "PONG" ]]; do
|
until [[ $(redis-cli -h valkey-mailcow -a ${VALKEYPASS} --no-auth-warning PING) == "PONG" ]]; do
|
||||||
echo "Waiting for Redis @redis-mailcow..."
|
echo "Waiting for Valkey @valkey-mailcow..."
|
||||||
sleep 2
|
sleep 2
|
||||||
done
|
done
|
||||||
until [[ $(redis-cli -h ${REDIS_SLAVEOF_IP} -p ${REDIS_SLAVEOF_PORT} -a ${REDISPASS} --no-auth-warning PING) == "PONG" ]]; do
|
until [[ $(redis-cli -h ${VALKEY_SLAVEOF_IP} -p ${VALKEY_SLAVEOF_PORT} -a ${VALKEYPASS} --no-auth-warning PING) == "PONG" ]]; do
|
||||||
echo "Waiting for Redis @${REDIS_SLAVEOF_IP}..."
|
echo "Waiting for Valkey @${VALKEY_SLAVEOF_IP}..."
|
||||||
sleep 2
|
sleep 2
|
||||||
done
|
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
|
else
|
||||||
cat <<EOF > /etc/rspamd/local.d/redis.conf
|
cat <<EOF > /etc/rspamd/local.d/redis.conf
|
||||||
servers = "redis:6379";
|
servers = "valkey-mailcow:6379";
|
||||||
password = "${REDISPASS}";
|
password = "${VALKEYPASS}";
|
||||||
timeout = 10;
|
timeout = 10;
|
||||||
EOF
|
EOF
|
||||||
until [[ $(redis-cli -h redis-mailcow -a ${REDISPASS} --no-auth-warning PING) == "PONG" ]]; do
|
until [[ $(redis-cli -h valkey-mailcow -a ${VALKEYPASS} --no-auth-warning PING) == "PONG" ]]; do
|
||||||
echo "Waiting for Redis slave..."
|
echo "Waiting for Valkey slave..."
|
||||||
sleep 2
|
sleep 2
|
||||||
done
|
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
|
fi
|
||||||
|
|
||||||
if [[ "${SKIP_OLEFY}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
|
if [[ "${SKIP_OLEFY}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
|
||||||
|
|||||||
@@ -1,170 +1,50 @@
|
|||||||
# SOGo built from source to enable security patch application
|
FROM debian:bookworm-slim
|
||||||
# Repository: https://github.com/Alinto/sogo
|
|
||||||
# Version: SOGo-5.12.4
|
|
||||||
#
|
|
||||||
# Applied security patches:
|
|
||||||
# -
|
|
||||||
#
|
|
||||||
# To add new patches, modify SOGO_SECURITY_PATCHES ARG below with space-separated commit hashes
|
|
||||||
|
|
||||||
FROM debian:bookworm
|
|
||||||
|
|
||||||
LABEL maintainer="The Infrastructure Company GmbH <info@servercow.de>"
|
LABEL maintainer="The Infrastructure Company GmbH <info@servercow.de>"
|
||||||
|
|
||||||
ARG DEBIAN_FRONTEND=noninteractive
|
ARG DEBIAN_FRONTEND=noninteractive
|
||||||
ARG SOGO_VERSION=SOGo-5.12.5
|
ARG DEBIAN_VERSION=bookworm
|
||||||
ARG SOPE_VERSION=SOPE-5.12.5
|
ARG SOGO_DEBIAN_REPOSITORY=https://packagingv2.sogo.nu/sogo-nightly-debian/
|
||||||
# Security patches to apply (space-separated commit hashes)
|
|
||||||
ARG SOGO_SECURITY_PATCHES=""
|
|
||||||
# renovate: datasource=github-releases depName=tianon/gosu versioning=semver-coerced extractVersion=^(?<version>.*)$
|
# renovate: datasource=github-releases depName=tianon/gosu versioning=semver-coerced extractVersion=^(?<version>.*)$
|
||||||
ARG GOSU_VERSION=1.19
|
ARG GOSU_VERSION=1.17
|
||||||
ENV LC_ALL=C
|
ENV LC_ALL=C
|
||||||
|
|
||||||
# Install dependencies, build SOPE and SOGo, then clean up (all in one layer to minimize image size)
|
# Prerequisites
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
RUN echo "Building from repository $SOGO_DEBIAN_REPOSITORY" \
|
||||||
# Build dependencies
|
&& apt-get update && apt-get install -y --no-install-recommends \
|
||||||
git \
|
apt-transport-https \
|
||||||
build-essential \
|
ca-certificates \
|
||||||
gobjc \
|
gettext \
|
||||||
pkg-config \
|
gnupg \
|
||||||
gnustep-make \
|
mariadb-client \
|
||||||
gnustep-base-runtime \
|
rsync \
|
||||||
libgnustep-base-dev \
|
supervisor \
|
||||||
libxml2-dev \
|
syslog-ng \
|
||||||
libldap2-dev \
|
syslog-ng-core \
|
||||||
libssl-dev \
|
syslog-ng-mod-redis \
|
||||||
zlib1g-dev \
|
dirmngr \
|
||||||
libpq-dev \
|
netcat-traditional \
|
||||||
libmariadb-dev-compat \
|
psmisc \
|
||||||
libmemcached-dev \
|
wget \
|
||||||
libsodium-dev \
|
patch \
|
||||||
libcurl4-openssl-dev \
|
|
||||||
libzip-dev \
|
|
||||||
libytnef0-dev \
|
|
||||||
libwbxml2-dev \
|
|
||||||
curl \
|
|
||||||
ca-certificates \
|
|
||||||
# Runtime dependencies
|
|
||||||
apt-transport-https \
|
|
||||||
gettext \
|
|
||||||
gnupg \
|
|
||||||
mariadb-client \
|
|
||||||
rsync \
|
|
||||||
supervisor \
|
|
||||||
syslog-ng \
|
|
||||||
syslog-ng-core \
|
|
||||||
syslog-ng-mod-redis \
|
|
||||||
dirmngr \
|
|
||||||
netcat-traditional \
|
|
||||||
psmisc \
|
|
||||||
wget \
|
|
||||||
patch \
|
|
||||||
libobjc4 \
|
|
||||||
libxml2 \
|
|
||||||
libldap-2.5-0 \
|
|
||||||
libssl3 \
|
|
||||||
zlib1g \
|
|
||||||
libmariadb3 \
|
|
||||||
libmemcached11 \
|
|
||||||
libsodium23 \
|
|
||||||
libcurl4 \
|
|
||||||
libzip4 \
|
|
||||||
libytnef0 \
|
|
||||||
libwbxml2-1 \
|
|
||||||
# Download gosu
|
|
||||||
&& dpkgArch="$(dpkg --print-architecture | awk -F- '{ print $NF }')" \
|
&& dpkgArch="$(dpkg --print-architecture | awk -F- '{ print $NF }')" \
|
||||||
&& wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch" \
|
&& wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch" \
|
||||||
&& chmod +x /usr/local/bin/gosu \
|
&& chmod +x /usr/local/bin/gosu \
|
||||||
&& gosu nobody true \
|
&& gosu nobody true \
|
||||||
# Build SOPE
|
&& mkdir /usr/share/doc/sogo \
|
||||||
&& git clone --depth 1 --branch ${SOPE_VERSION} https://github.com/Alinto/sope.git /tmp/sope \
|
|
||||||
&& cd /tmp/sope \
|
|
||||||
&& rm -rf .git \
|
|
||||||
&& . /usr/share/GNUstep/Makefiles/GNUstep.sh \
|
|
||||||
&& ./configure --prefix=/usr --disable-debug --disable-strip \
|
|
||||||
&& make -j$(nproc) \
|
|
||||||
&& make install \
|
|
||||||
&& cd / \
|
|
||||||
&& rm -rf /tmp/sope \
|
|
||||||
# Build SOGo with security patches
|
|
||||||
&& git clone --depth 1 --branch ${SOGO_VERSION} https://github.com/Alinto/sogo.git /tmp/sogo \
|
|
||||||
&& cd /tmp/sogo \
|
|
||||||
&& git config user.email "builder@mailcow.local" \
|
|
||||||
&& git config user.name "SOGo Builder" \
|
|
||||||
&& for patch in ${SOGO_SECURITY_PATCHES}; do \
|
|
||||||
echo "Applying security patch: ${patch}"; \
|
|
||||||
git fetch origin ${patch} && git cherry-pick ${patch}; \
|
|
||||||
done \
|
|
||||||
&& rm -rf .git \
|
|
||||||
&& . /usr/share/GNUstep/Makefiles/GNUstep.sh \
|
|
||||||
&& ./configure --disable-debug --disable-strip \
|
|
||||||
&& make -j$(nproc) \
|
|
||||||
&& make install \
|
|
||||||
&& cd /tmp/sogo/ActiveSync \
|
|
||||||
&& . /usr/share/GNUstep/Makefiles/GNUstep.sh \
|
|
||||||
&& make -j$(nproc) install \
|
|
||||||
&& cd / \
|
|
||||||
&& rm -rf /tmp/sogo \
|
|
||||||
# Strip binaries
|
|
||||||
&& strip --strip-unneeded /usr/local/sbin/sogod 2>/dev/null || true \
|
|
||||||
&& strip --strip-unneeded /usr/local/sbin/sogo-tool 2>/dev/null || true \
|
|
||||||
&& strip --strip-unneeded /usr/local/sbin/sogo-ealarms-notify 2>/dev/null || true \
|
|
||||||
&& strip --strip-unneeded /usr/local/sbin/sogo-slapd-sockd 2>/dev/null || true \
|
|
||||||
# Remove build dependencies and clean up
|
|
||||||
&& apt-get purge -y --auto-remove \
|
|
||||||
git \
|
|
||||||
build-essential \
|
|
||||||
gobjc \
|
|
||||||
gnustep-make \
|
|
||||||
libgnustep-base-dev \
|
|
||||||
libxml2-dev \
|
|
||||||
libldap2-dev \
|
|
||||||
libssl-dev \
|
|
||||||
zlib1g-dev \
|
|
||||||
libpq-dev \
|
|
||||||
libmariadb-dev-compat \
|
|
||||||
libmemcached-dev \
|
|
||||||
libsodium-dev \
|
|
||||||
libcurl4-openssl-dev \
|
|
||||||
libzip-dev \
|
|
||||||
libytnef0-dev \
|
|
||||||
curl \
|
|
||||||
&& apt-get autoremove -y \
|
|
||||||
&& apt-get clean \
|
|
||||||
&& rm -rf /var/lib/apt/lists/* \
|
|
||||||
&& rm -rf /usr/share/doc/* \
|
|
||||||
&& rm -rf /usr/share/man/* \
|
|
||||||
&& rm -rf /var/cache/debconf/* \
|
|
||||||
&& rm -rf /tmp/* \
|
|
||||||
&& rm -rf /root/.cache \
|
|
||||||
&& find /usr/local/lib -name '*.a' -delete \
|
|
||||||
&& find /usr/lib -name '*.a' -delete \
|
|
||||||
&& mkdir -p /usr/share/doc/sogo \
|
|
||||||
&& touch /usr/share/doc/sogo/empty.sh \
|
&& touch /usr/share/doc/sogo/empty.sh \
|
||||||
|
&& wget -O- https://keys.openpgp.org/vks/v1/by-fingerprint/74FFC6D72B925A34B5D356BDF8A27B36A6E2EAE9 | gpg --dearmor | apt-key add - \
|
||||||
|
&& echo "deb [trusted=yes] ${SOGO_DEBIAN_REPOSITORY} ${DEBIAN_VERSION} main" > /etc/apt/sources.list.d/sogo.list \
|
||||||
|
&& apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
sogo \
|
||||||
|
sogo-activesync \
|
||||||
|
&& apt-get autoclean \
|
||||||
|
&& rm -rf /var/lib/apt/lists/* \
|
||||||
&& touch /etc/default/locale
|
&& touch /etc/default/locale
|
||||||
|
|
||||||
# Configure library paths
|
|
||||||
RUN echo "/usr/lib64" > /etc/ld.so.conf.d/sogo.conf \
|
|
||||||
&& echo "/usr/local/lib/sogo" >> /etc/ld.so.conf.d/sogo.conf \
|
|
||||||
&& echo "/usr/local/lib/GNUstep/Frameworks/SOGo.framework/Versions/5/sogo" >> /etc/ld.so.conf.d/sogo.conf \
|
|
||||||
&& ldconfig
|
|
||||||
|
|
||||||
# Create sogo user and group
|
|
||||||
RUN groupadd -r -g 999 sogo \
|
|
||||||
&& useradd -r -u 999 -g sogo -d /var/lib/sogo -s /bin/bash -c "SOGo Daemon" sogo \
|
|
||||||
&& mkdir -p /var/lib/sogo /var/run/sogo /var/log/sogo /var/spool/sogo \
|
|
||||||
&& chown -R sogo:sogo /var/lib/sogo /var/run/sogo /var/log/sogo /var/spool/sogo
|
|
||||||
|
|
||||||
# Create symlinks for SOGo binaries
|
|
||||||
RUN ln -s /usr/local/sbin/sogod /usr/sbin/sogod \
|
|
||||||
&& ln -s /usr/local/sbin/sogo-tool /usr/sbin/sogo-tool \
|
|
||||||
&& ln -s /usr/local/sbin/sogo-ealarms-notify /usr/sbin/sogo-ealarms-notify \
|
|
||||||
&& ln -s /usr/local/sbin/sogo-slapd-sockd /usr/sbin/sogo-slapd-sockd
|
|
||||||
|
|
||||||
# Copy configuration files and scripts
|
|
||||||
COPY ./bootstrap-sogo.sh /bootstrap-sogo.sh
|
COPY ./bootstrap-sogo.sh /bootstrap-sogo.sh
|
||||||
COPY syslog-ng.conf /etc/syslog-ng/syslog-ng.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 supervisord.conf /etc/supervisor/supervisord.conf
|
COPY supervisord.conf /etc/supervisor/supervisord.conf
|
||||||
COPY acl.diff /acl.diff
|
COPY acl.diff /acl.diff
|
||||||
COPY navMailcowBtns.diff /navMailcowBtns.diff
|
COPY navMailcowBtns.diff /navMailcowBtns.diff
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
--- /usr/local/lib/GNUstep/SOGo/Templates/UIxAclEditor.wox 2018-08-17 18:29:57.987504204 +0200
|
--- /usr/lib/GNUstep/SOGo/Templates/UIxAclEditor.wox 2018-08-17 18:29:57.987504204 +0200
|
||||||
+++ /usr/local/lib/GNUstep/SOGo/Templates/UIxAclEditor.wox 2018-08-17 18:29:35.918291298 +0200
|
+++ /usr/lib/GNUstep/SOGo/Templates/UIxAclEditor.wox 2018-08-17 18:29:35.918291298 +0200
|
||||||
@@ -46,7 +46,7 @@
|
@@ -46,7 +46,7 @@
|
||||||
</md-item-template>
|
</md-item-template>
|
||||||
</md-autocomplete>
|
</md-autocomplete>
|
||||||
|
|||||||
@@ -50,6 +50,10 @@ cat <<EOF > /var/lib/sogo/GNUstep/Defaults/sogod.plist
|
|||||||
<string>YES</string>
|
<string>YES</string>
|
||||||
<key>SOGoEncryptionKey</key>
|
<key>SOGoEncryptionKey</key>
|
||||||
<string>${RAND_PASS}</string>
|
<string>${RAND_PASS}</string>
|
||||||
|
<key>SOGoURLEncryptionEnabled</key>
|
||||||
|
<string>YES</string>
|
||||||
|
<key>SOGoURLEncryptionPassphrase</key>
|
||||||
|
<string>${SOGO_URL_ENCRYPTION_KEY}</string>
|
||||||
<key>OCSAdminURL</key>
|
<key>OCSAdminURL</key>
|
||||||
<string>mysql://${DBUSER}:${DBPASS}@%2Fvar%2Frun%2Fmysqld%2Fmysqld.sock/${DBNAME}/sogo_admin</string>
|
<string>mysql://${DBUSER}:${DBPASS}@%2Fvar%2Frun%2Fmysqld%2Fmysqld.sock/${DBNAME}/sogo_admin</string>
|
||||||
<key>OCSCacheFolderURL</key>
|
<key>OCSCacheFolderURL</key>
|
||||||
@@ -130,22 +134,18 @@ chmod 600 /var/lib/sogo/GNUstep/Defaults/sogod.plist
|
|||||||
# Patch ACLs
|
# Patch ACLs
|
||||||
#if [[ ${ACL_ANYONE} == 'allow' ]]; then
|
#if [[ ${ACL_ANYONE} == 'allow' ]]; then
|
||||||
# #enable any or authenticated targets for ACL
|
# #enable any or authenticated targets for ACL
|
||||||
# if patch -R -sfN --dry-run /usr/local/lib/GNUstep/SOGo/Templates/UIxAclEditor.wox < /acl.diff > /dev/null; then
|
# if patch -R -sfN --dry-run /usr/lib/GNUstep/SOGo/Templates/UIxAclEditor.wox < /acl.diff > /dev/null; then
|
||||||
# patch -R /usr/local/lib/GNUstep/SOGo/Templates/UIxAclEditor.wox < /acl.diff;
|
# patch -R /usr/lib/GNUstep/SOGo/Templates/UIxAclEditor.wox < /acl.diff;
|
||||||
# fi
|
# fi
|
||||||
#else
|
#else
|
||||||
# #disable any or authenticated targets for ACL
|
# #disable any or authenticated targets for ACL
|
||||||
# if patch -sfN --dry-run /usr/local/lib/GNUstep/SOGo/Templates/UIxAclEditor.wox < /acl.diff > /dev/null; then
|
# if patch -sfN --dry-run /usr/lib/GNUstep/SOGo/Templates/UIxAclEditor.wox < /acl.diff > /dev/null; then
|
||||||
# patch /usr/local/lib/GNUstep/SOGo/Templates/UIxAclEditor.wox < /acl.diff;
|
# patch /usr/lib/GNUstep/SOGo/Templates/UIxAclEditor.wox < /acl.diff;
|
||||||
# fi
|
# fi
|
||||||
#fi
|
#fi
|
||||||
|
|
||||||
# Apply custom UI patch (reverse patch to ADD buttons)
|
if patch -R -sfN --dry-run /usr/lib/GNUstep/SOGo/Templates/UIxTopnavToolbar.wox < /navMailcowBtns.diff > /dev/null; then
|
||||||
if patch -R -sfN --dry-run /usr/local/lib/GNUstep/SOGo/Templates/UIxTopnavToolbar.wox < /navMailcowBtns.diff > /dev/null; then
|
patch -R /usr/lib/GNUstep/SOGo/Templates/UIxTopnavToolbar.wox < /navMailcowBtns.diff;
|
||||||
echo "Applying navMailcowBtns patch (reverse to add buttons)..."
|
|
||||||
patch -R /usr/local/lib/GNUstep/SOGo/Templates/UIxTopnavToolbar.wox < /navMailcowBtns.diff;
|
|
||||||
else
|
|
||||||
echo "navMailcowBtns patch already applied or cannot be applied"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Rename custom logo, if any
|
# Rename custom logo, if any
|
||||||
@@ -153,7 +153,7 @@ fi
|
|||||||
|
|
||||||
# Rsync web content
|
# Rsync web content
|
||||||
echo "Syncing web content with named volume"
|
echo "Syncing web content with named volume"
|
||||||
rsync -a /usr/local/lib/GNUstep/SOGo/. /sogo_web/
|
rsync -a /usr/lib/GNUstep/SOGo/. /sogo_web/
|
||||||
|
|
||||||
# Chown backup path
|
# Chown backup path
|
||||||
chown -R sogo:sogo /sogo_backup
|
chown -R sogo:sogo /sogo_backup
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ if [[ "${SKIP_SOGO}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
|
|||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ ! -z ${REDIS_SLAVEOF_IP} ]]; then
|
if [[ ! -z ${VALKEY_SLAVEOF_IP} ]]; then
|
||||||
cp /etc/syslog-ng/syslog-ng-redis_slave.conf /etc/syslog-ng/syslog-ng.conf
|
cp /etc/syslog-ng/syslog-ng-valkey_slave.conf /etc/syslog-ng/syslog-ng.conf
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "$TZ" > /etc/timezone
|
echo "$TZ" > /etc/timezone
|
||||||
|
|||||||
@@ -17,28 +17,28 @@ source s_sogo {
|
|||||||
pipe("/dev/sogo_log" owner(sogo) group(sogo));
|
pipe("/dev/sogo_log" owner(sogo) group(sogo));
|
||||||
};
|
};
|
||||||
destination d_stdout { pipe("/dev/stdout"); };
|
destination d_stdout { pipe("/dev/stdout"); };
|
||||||
destination d_redis_ui_log {
|
destination d_valkey_ui_log {
|
||||||
redis(
|
redis(
|
||||||
host("`REDIS_SLAVEOF_IP`")
|
host("`VALKEY_SLAVEOF_IP`")
|
||||||
persist-name("redis1")
|
persist-name("valkey1")
|
||||||
port(`REDIS_SLAVEOF_PORT`)
|
port(`VALKEY_SLAVEOF_PORT`)
|
||||||
auth("`REDISPASS`")
|
auth("`VALKEYPASS`")
|
||||||
command("LPUSH" "SOGO_LOG" "$(format-json time=\"$S_UNIXTIME\" priority=\"$PRIORITY\" program=\"$PROGRAM\" message=\"$MESSAGE\")\n")
|
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(
|
redis(
|
||||||
host("`REDIS_SLAVEOF_IP`")
|
host("`VALKEY_SLAVEOF_IP`")
|
||||||
persist-name("redis2")
|
persist-name("valkey2")
|
||||||
port(`REDIS_SLAVEOF_PORT`)
|
port(`VALKEY_SLAVEOF_PORT`)
|
||||||
auth("`REDISPASS`")
|
auth("`VALKEYPASS`")
|
||||||
command("PUBLISH" "F2B_CHANNEL" "$(sanitize $MESSAGE)")
|
command("PUBLISH" "F2B_CHANNEL" "$(sanitize $MESSAGE)")
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
log {
|
log {
|
||||||
source(s_sogo);
|
source(s_sogo);
|
||||||
destination(d_redis_ui_log);
|
destination(d_valkey_ui_log);
|
||||||
destination(d_redis_f2b_channel);
|
destination(d_valkey_f2b_channel);
|
||||||
};
|
};
|
||||||
log {
|
log {
|
||||||
source(s_sogo);
|
source(s_sogo);
|
||||||
@@ -17,28 +17,28 @@ source s_sogo {
|
|||||||
pipe("/dev/sogo_log" owner(sogo) group(sogo));
|
pipe("/dev/sogo_log" owner(sogo) group(sogo));
|
||||||
};
|
};
|
||||||
destination d_stdout { pipe("/dev/stdout"); };
|
destination d_stdout { pipe("/dev/stdout"); };
|
||||||
destination d_redis_ui_log {
|
destination d_valkey_ui_log {
|
||||||
redis(
|
redis(
|
||||||
host("redis-mailcow")
|
host("valkey-mailcow")
|
||||||
persist-name("redis1")
|
persist-name("valkey1")
|
||||||
port(6379)
|
port(6379)
|
||||||
auth("`REDISPASS`")
|
auth("`VALKEYPASS`")
|
||||||
command("LPUSH" "SOGO_LOG" "$(format-json time=\"$S_UNIXTIME\" priority=\"$PRIORITY\" program=\"$PROGRAM\" message=\"$MESSAGE\")\n")
|
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(
|
redis(
|
||||||
host("redis-mailcow")
|
host("valkey-mailcow")
|
||||||
persist-name("redis2")
|
persist-name("valkey2")
|
||||||
port(6379)
|
port(6379)
|
||||||
auth("`REDISPASS`")
|
auth("`VALKEYPASS`")
|
||||||
command("PUBLISH" "F2B_CHANNEL" "$(sanitize $MESSAGE)")
|
command("PUBLISH" "F2B_CHANNEL" "$(sanitize $MESSAGE)")
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
log {
|
log {
|
||||||
source(s_sogo);
|
source(s_sogo);
|
||||||
destination(d_redis_ui_log);
|
destination(d_valkey_ui_log);
|
||||||
destination(d_redis_f2b_channel);
|
destination(d_valkey_f2b_channel);
|
||||||
};
|
};
|
||||||
log {
|
log {
|
||||||
source(s_sogo);
|
source(s_sogo);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM alpine:3.23
|
FROM alpine:3.21
|
||||||
|
|
||||||
LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"
|
LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"
|
||||||
|
|
||||||
|
|||||||
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
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM alpine:3.23
|
FROM alpine:3.21
|
||||||
|
|
||||||
LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"
|
LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"
|
||||||
|
|
||||||
@@ -37,6 +37,5 @@ RUN apk add --update \
|
|||||||
COPY watchdog.sh /watchdog.sh
|
COPY watchdog.sh /watchdog.sh
|
||||||
COPY check_mysql_slavestatus.sh /usr/lib/nagios/plugins/check_mysql_slavestatus.sh
|
COPY check_mysql_slavestatus.sh /usr/lib/nagios/plugins/check_mysql_slavestatus.sh
|
||||||
COPY check_dns.sh /usr/lib/mailcow/check_dns.sh
|
COPY check_dns.sh /usr/lib/mailcow/check_dns.sh
|
||||||
COPY client.cnf /etc/my.cnf.d/client.cnf
|
|
||||||
|
|
||||||
CMD ["/watchdog.sh"]
|
CMD ["/watchdog.sh"]
|
||||||
|
|||||||
@@ -19,19 +19,19 @@ if [ -z "$HOST" ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# run dig and measure the time it takes to run
|
# run dig and measure the time it takes to run
|
||||||
START_TIME=$(perl -MTime::HiRes -e 'print Time::HiRes::time')
|
START_TIME=$(date +%s%3N)
|
||||||
dig_output=$(dig +short +timeout=2 +tries=1 "$HOST" @"$SERVER" 2>/dev/null)
|
dig_output=$(dig +short +timeout=2 +tries=1 "$HOST" @"$SERVER" 2>/dev/null)
|
||||||
dig_rc=$?
|
dig_rc=$?
|
||||||
END_TIME=$(perl -MTime::HiRes -e 'print Time::HiRes::time')
|
|
||||||
dig_output_ips=$(echo "$dig_output" | grep -E '^[0-9.]+$' | sort | paste -sd ',' -)
|
dig_output_ips=$(echo "$dig_output" | grep -E '^[0-9.]+$' | sort | paste -sd ',' -)
|
||||||
ELAPSED_TIME=$(perl -e "printf('%.3f', $END_TIME - $START_TIME)")
|
END_TIME=$(date +%s%3N)
|
||||||
|
ELAPSED_TIME=$((END_TIME - START_TIME))
|
||||||
|
|
||||||
# validate and perform nagios like output and exit codes
|
# validate and perform nagios like output and exit codes
|
||||||
if [ $dig_rc -ne 0 ] || [ -z "$dig_output" ]; then
|
if [ $dig_rc -ne 0 ] || [ -z "$dig_output" ]; then
|
||||||
echo "Domain $HOST was not found by the server"
|
echo "Domain $HOST was not found by the server"
|
||||||
exit 2
|
exit 2
|
||||||
elif [ $dig_rc -eq 0 ]; then
|
elif [ $dig_rc -eq 0 ]; then
|
||||||
echo "DNS OK: $ELAPSED_TIME seconds response time. $HOST returns $dig_output_ips"
|
echo "DNS OK: $ELAPSED_TIME ms response time. $HOST returns $dig_output_ips"
|
||||||
exit 0
|
exit 0
|
||||||
else
|
else
|
||||||
echo "Unknown error"
|
echo "Unknown error"
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
[client]
|
|
||||||
ssl = false
|
|
||||||
ssl-verify-server-cert = false
|
|
||||||
@@ -38,24 +38,24 @@ if [[ ! -p /tmp/com_pipe ]]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Wait for containers
|
# Wait for containers
|
||||||
while ! mariadb-admin status --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u${DBUSER} -p${DBPASS} --silent; do
|
while ! mariadb-admin status --ssl=false --socket=/var/run/mysqld/mysqld.sock -u${DBUSER} -p${DBPASS} --silent; do
|
||||||
echo "Waiting for SQL..."
|
echo "Waiting for SQL..."
|
||||||
sleep 2
|
sleep 2
|
||||||
done
|
done
|
||||||
|
|
||||||
# Do not attempt to write to slave
|
# Do not attempt to write to slave
|
||||||
if [[ ! -z ${REDIS_SLAVEOF_IP} ]]; then
|
if [[ ! -z ${VALKEY_SLAVEOF_IP} ]]; then
|
||||||
REDIS_CMDLINE="redis-cli -h ${REDIS_SLAVEOF_IP} -p ${REDIS_SLAVEOF_PORT} -a ${REDISPASS} --no-auth-warning"
|
VALKEY_CMDLINE="redis-cli -h ${VALKEY_SLAVEOF_IP} -p ${VALKEY_SLAVEOF_PORT} -a ${VALKEYPASS} --no-auth-warning"
|
||||||
else
|
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
|
fi
|
||||||
|
|
||||||
until [[ $(${REDIS_CMDLINE} PING) == "PONG" ]]; do
|
until [[ $(${VALKEY_CMDLINE} PING) == "PONG" ]]; do
|
||||||
echo "Waiting for Redis..."
|
echo "Waiting for Valkey..."
|
||||||
sleep 2
|
sleep 2
|
||||||
done
|
done
|
||||||
|
|
||||||
${REDIS_CMDLINE} DEL F2B_RES > /dev/null
|
${VALKEY_CMDLINE} DEL F2B_RES > /dev/null
|
||||||
|
|
||||||
# Common functions
|
# Common functions
|
||||||
get_ipv6(){
|
get_ipv6(){
|
||||||
@@ -90,15 +90,15 @@ progress() {
|
|||||||
[[ ${CURRENT} -gt ${TOTAL} ]] && return
|
[[ ${CURRENT} -gt ${TOTAL} ]] && return
|
||||||
[[ ${CURRENT} -lt 0 ]] && CURRENT=0
|
[[ ${CURRENT} -lt 0 ]] && CURRENT=0
|
||||||
PERCENT=$(( 200 * ${CURRENT} / ${TOTAL} % 2 + 100 * ${CURRENT} / ${TOTAL} ))
|
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
|
${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_redis
|
log_msg "${SERVICE} health level: ${PERCENT}% (${CURRENT}/${TOTAL}), health trend: ${DIFF}" no_valkey
|
||||||
# Return 10 to indicate a dead service
|
# Return 10 to indicate a dead service
|
||||||
[ ${CURRENT} -le 0 ] && return 10
|
[ ${CURRENT} -le 0 ] && return 10
|
||||||
}
|
}
|
||||||
|
|
||||||
log_msg() {
|
log_msg() {
|
||||||
if [[ ${2} != "no_redis" ]]; then
|
if [[ ${2} != "no_valkey" ]]; then
|
||||||
${REDIS_CMDLINE} LPUSH WATCHDOG_LOG "{\"time\":\"$(date +%s)\",\"message\":\"$(printf '%s' "${1}" | \
|
${VALKEY_CMDLINE} LPUSH WATCHDOG_LOG "{\"time\":\"$(date +%s)\",\"message\":\"$(printf '%s' "${1}" | \
|
||||||
tr '\r\n%&;$"_[]{}-' ' ')\"}" > /dev/null
|
tr '\r\n%&;$"_[]{}-' ' ')\"}" > /dev/null
|
||||||
fi
|
fi
|
||||||
echo $(date) $(printf '%s\n' "${1}")
|
echo $(date) $(printf '%s\n' "${1}")
|
||||||
@@ -114,10 +114,10 @@ function notify_error() {
|
|||||||
# If exists, mail will be throttled by argument in seconds
|
# If exists, mail will be throttled by argument in seconds
|
||||||
[[ ! -z ${3} ]] && THROTTLE=${3}
|
[[ ! -z ${3} ]] && THROTTLE=${3}
|
||||||
if [[ ! -z ${THROTTLE} ]]; then
|
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
|
if [[ "${TTL_LEFT}" == "-2" ]]; then
|
||||||
# Delay key not found, setting a delay key now
|
# 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
|
else
|
||||||
log_msg "Not sending notification email now, blocked for ${TTL_LEFT} seconds..."
|
log_msg "Not sending notification email now, blocked for ${TTL_LEFT} seconds..."
|
||||||
return 1
|
return 1
|
||||||
@@ -324,21 +324,21 @@ unbound_checks() {
|
|||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
redis_checks() {
|
valkey_checks() {
|
||||||
# A check for the local redis container
|
# A check for the local valkey container
|
||||||
err_count=0
|
err_count=0
|
||||||
diff_c=0
|
diff_c=0
|
||||||
THRESHOLD=${REDIS_THRESHOLD}
|
THRESHOLD=${VALKEY_THRESHOLD}
|
||||||
# Reduce error count by 2 after restarting an unhealthy container
|
# Reduce error count by 2 after restarting an unhealthy container
|
||||||
trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1
|
trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1
|
||||||
while [ ${err_count} -lt ${THRESHOLD} ]; do
|
while [ ${err_count} -lt ${THRESHOLD} ]; do
|
||||||
touch /tmp/redis-mailcow; echo "$(tail -50 /tmp/redis-mailcow)" > /tmp/redis-mailcow
|
touch /tmp/valkey-mailcow; echo "$(tail -50 /tmp/valkey-mailcow)" > /tmp/valkey-mailcow
|
||||||
host_ip=$(get_container_ip redis-mailcow)
|
host_ip=$(get_container_ip valkey-mailcow)
|
||||||
err_c_cur=${err_count}
|
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} -eq ${err_count} ] && [ ! $((${err_count} - 1)) -lt 0 ] && err_count=$((${err_count} - 1)) diff_c=1
|
||||||
[ ${err_c_cur} -ne ${err_count} ] && diff_c=$(( ${err_c_cur} - ${err_count} ))
|
[ ${err_c_cur} -ne ${err_count} ] && diff_c=$(( ${err_c_cur} - ${err_count} ))
|
||||||
progress "Redis" ${THRESHOLD} $(( ${THRESHOLD} - ${err_count} )) ${diff_c}
|
progress "Valkey" ${THRESHOLD} $(( ${THRESHOLD} - ${err_count} )) ${diff_c}
|
||||||
if [[ $? == 10 ]]; then
|
if [[ $? == 10 ]]; then
|
||||||
diff_c=0
|
diff_c=0
|
||||||
sleep 1
|
sleep 1
|
||||||
@@ -359,8 +359,8 @@ mysql_checks() {
|
|||||||
while [ ${err_count} -lt ${THRESHOLD} ]; do
|
while [ ${err_count} -lt ${THRESHOLD} ]; do
|
||||||
touch /tmp/mysql-mailcow; echo "$(tail -50 /tmp/mysql-mailcow)" > /tmp/mysql-mailcow
|
touch /tmp/mysql-mailcow; echo "$(tail -50 /tmp/mysql-mailcow)" > /tmp/mysql-mailcow
|
||||||
err_c_cur=${err_count}
|
err_c_cur=${err_count}
|
||||||
/usr/lib/nagios/plugins/check_mysql -f /etc/my.cnf.d/client.cnf -s /var/run/mysqld/mysqld.sock -u ${DBUSER} -p ${DBPASS} -d ${DBNAME} 2>> /tmp/mysql-mailcow 1>&2; err_count=$(( ${err_count} + $? ))
|
/usr/lib/nagios/plugins/check_mysql -s /var/run/mysqld/mysqld.sock -u ${DBUSER} -p ${DBPASS} -d ${DBNAME} 2>> /tmp/mysql-mailcow 1>&2; err_count=$(( ${err_count} + $? ))
|
||||||
/usr/lib/nagios/plugins/check_mysql_query -f /etc/my.cnf.d/client.cnf -s /var/run/mysqld/mysqld.sock -u ${DBUSER} -p ${DBPASS} -d ${DBNAME} -q "SELECT COUNT(*) FROM information_schema.tables" 2>> /tmp/mysql-mailcow 1>&2; err_count=$(( ${err_count} + $? ))
|
/usr/lib/nagios/plugins/check_mysql_query -s /var/run/mysqld/mysqld.sock -u ${DBUSER} -p ${DBPASS} -d ${DBNAME} -q "SELECT COUNT(*) FROM information_schema.tables" 2>> /tmp/mysql-mailcow 1>&2; err_count=$(( ${err_count} + $? ))
|
||||||
[ ${err_c_cur} -eq ${err_count} ] && [ ! $((${err_count} - 1)) -lt 0 ] && err_count=$((${err_count} - 1)) diff_c=1
|
[ ${err_c_cur} -eq ${err_count} ] && [ ! $((${err_count} - 1)) -lt 0 ] && err_count=$((${err_count} - 1)) diff_c=1
|
||||||
[ ${err_c_cur} -ne ${err_count} ] && diff_c=$(( ${err_c_cur} - ${err_count} ))
|
[ ${err_c_cur} -ne ${err_count} ] && diff_c=$(( ${err_c_cur} - ${err_count} ))
|
||||||
progress "MySQL/MariaDB" ${THRESHOLD} $(( ${THRESHOLD} - ${err_count} )) ${diff_c}
|
progress "MySQL/MariaDB" ${THRESHOLD} $(( ${THRESHOLD} - ${err_count} )) ${diff_c}
|
||||||
@@ -384,7 +384,7 @@ mysql_repl_checks() {
|
|||||||
while [ ${err_count} -lt ${THRESHOLD} ]; do
|
while [ ${err_count} -lt ${THRESHOLD} ]; do
|
||||||
touch /tmp/mysql_repl_checks; echo "$(tail -50 /tmp/mysql_repl_checks)" > /tmp/mysql_repl_checks
|
touch /tmp/mysql_repl_checks; echo "$(tail -50 /tmp/mysql_repl_checks)" > /tmp/mysql_repl_checks
|
||||||
err_c_cur=${err_count}
|
err_c_cur=${err_count}
|
||||||
/usr/lib/nagios/plugins/check_mysql_slavestatus.sh -o /etc/my.cnf.d/client.cnf -S /var/run/mysqld/mysqld.sock -u root -p ${DBROOT} 2>> /tmp/mysql_repl_checks 1>&2; err_count=$(( ${err_count} + $? ))
|
/usr/lib/nagios/plugins/check_mysql_slavestatus.sh -S /var/run/mysqld/mysqld.sock -u root -p ${DBROOT} 2>> /tmp/mysql_repl_checks 1>&2; err_count=$(( ${err_count} + $? ))
|
||||||
[ ${err_c_cur} -eq ${err_count} ] && [ ! $((${err_count} - 1)) -lt 0 ] && err_count=$((${err_count} - 1)) diff_c=1
|
[ ${err_c_cur} -eq ${err_count} ] && [ ! $((${err_count} - 1)) -lt 0 ] && err_count=$((${err_count} - 1)) diff_c=1
|
||||||
[ ${err_c_cur} -ne ${err_count} ] && diff_c=$(( ${err_c_cur} - ${err_count} ))
|
[ ${err_c_cur} -ne ${err_count} ] && diff_c=$(( ${err_c_cur} - ${err_count} ))
|
||||||
progress "MySQL/MariaDB replication" ${THRESHOLD} $(( ${THRESHOLD} - ${err_count} )) ${diff_c}
|
progress "MySQL/MariaDB replication" ${THRESHOLD} $(( ${THRESHOLD} - ${err_count} )) ${diff_c}
|
||||||
@@ -533,12 +533,12 @@ dovecot_repl_checks() {
|
|||||||
err_count=0
|
err_count=0
|
||||||
diff_c=0
|
diff_c=0
|
||||||
THRESHOLD=${DOVECOT_REPL_THRESHOLD}
|
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
|
# Reduce error count by 2 after restarting an unhealthy container
|
||||||
trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1
|
trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1
|
||||||
while [ ${err_count} -lt ${THRESHOLD} ]; do
|
while [ ${err_count} -lt ${THRESHOLD} ]; do
|
||||||
err_c_cur=${err_count}
|
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
|
if [[ "${D_REPL_STATUS}" != "1" ]]; then
|
||||||
err_count=$(( ${err_count} + 1 ))
|
err_count=$(( ${err_count} + 1 ))
|
||||||
fi
|
fi
|
||||||
@@ -608,19 +608,19 @@ ratelimit_checks() {
|
|||||||
err_count=0
|
err_count=0
|
||||||
diff_c=0
|
diff_c=0
|
||||||
THRESHOLD=${RATELIMIT_THRESHOLD}
|
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
|
# Reduce error count by 2 after restarting an unhealthy container
|
||||||
trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1
|
trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1
|
||||||
while [ ${err_count} -lt ${THRESHOLD} ]; do
|
while [ ${err_count} -lt ${THRESHOLD} ]; do
|
||||||
err_c_cur=${err_count}
|
err_c_cur=${err_count}
|
||||||
RL_LOG_STATUS_PREV=${RL_LOG_STATUS}
|
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
|
if [[ ${RL_LOG_STATUS_PREV} != ${RL_LOG_STATUS} ]]; then
|
||||||
err_count=$(( ${err_count} + 1 ))
|
err_count=$(( ${err_count} + 1 ))
|
||||||
echo 'Last 10 applied ratelimits (may overlap with previous reports).' > /tmp/ratelimit
|
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 'Full ratelimit buckets can be emptied by deleting the ratelimit hash from within mailcow UI (see /debug -> Protocols -> Ratelimit):' >> /tmp/ratelimit
|
||||||
echo >> /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
|
fi
|
||||||
[ ${err_c_cur} -eq ${err_count} ] && [ ! $((${err_count} - 1)) -lt 0 ] && err_count=$((${err_count} - 1)) diff_c=1
|
[ ${err_c_cur} -eq ${err_count} ] && [ ! $((${err_count} - 1)) -lt 0 ] && err_count=$((${err_count} - 1)) diff_c=1
|
||||||
[ ${err_c_cur} -ne ${err_count} ] && diff_c=$(( ${err_c_cur} - ${err_count} ))
|
[ ${err_c_cur} -ne ${err_count} ] && diff_c=$(( ${err_c_cur} - ${err_count} ))
|
||||||
@@ -669,20 +669,20 @@ fail2ban_checks() {
|
|||||||
err_count=0
|
err_count=0
|
||||||
diff_c=0
|
diff_c=0
|
||||||
THRESHOLD=${FAIL2BAN_THRESHOLD}
|
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=
|
F2B_RES=
|
||||||
# Reduce error count by 2 after restarting an unhealthy container
|
# Reduce error count by 2 after restarting an unhealthy container
|
||||||
trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1
|
trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1
|
||||||
while [ ${err_count} -lt ${THRESHOLD} ]; do
|
while [ ${err_count} -lt ${THRESHOLD} ]; do
|
||||||
err_c_cur=${err_count}
|
err_c_cur=${err_count}
|
||||||
F2B_LOG_STATUS_PREV=(${F2B_LOG_STATUS[@]})
|
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
|
array_diff F2B_RES F2B_LOG_STATUS F2B_LOG_STATUS_PREV
|
||||||
if [[ ! -z "${F2B_RES}" ]]; then
|
if [[ ! -z "${F2B_RES}" ]]; then
|
||||||
err_count=$(( ${err_count} + 1 ))
|
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
|
if [ $? -ne 0 ]; then
|
||||||
${REDIS_CMDLINE} -x DEL F2B_RES
|
${VALKEY_CMDLINE} -x DEL F2B_RES
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
[ ${err_c_cur} -eq ${err_count} ] && [ ! $((${err_count} - 1)) -lt 0 ] && err_count=$((${err_count} - 1)) diff_c=1
|
[ ${err_c_cur} -eq ${err_count} ] && [ ! $((${err_count} - 1)) -lt 0 ] && err_count=$((${err_count} - 1)) diff_c=1
|
||||||
@@ -703,9 +703,9 @@ acme_checks() {
|
|||||||
err_count=0
|
err_count=0
|
||||||
diff_c=0
|
diff_c=0
|
||||||
THRESHOLD=${ACME_THRESHOLD}
|
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
|
if [[ -z "${ACME_LOG_STATUS}" ]]; then
|
||||||
${REDIS_CMDLINE} SET ACME_FAIL_TIME 0
|
${VALKEY_CMDLINE} SET ACME_FAIL_TIME 0
|
||||||
ACME_LOG_STATUS=0
|
ACME_LOG_STATUS=0
|
||||||
fi
|
fi
|
||||||
# Reduce error count by 2 after restarting an unhealthy container
|
# Reduce error count by 2 after restarting an unhealthy container
|
||||||
@@ -715,7 +715,7 @@ acme_checks() {
|
|||||||
ACME_LOG_STATUS_PREV=${ACME_LOG_STATUS}
|
ACME_LOG_STATUS_PREV=${ACME_LOG_STATUS}
|
||||||
ACME_LC=0
|
ACME_LC=0
|
||||||
until [[ ! -z ${ACME_LOG_STATUS} ]] || [ ${ACME_LC} -ge 3 ]; do
|
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
|
sleep 3
|
||||||
ACME_LC=$((ACME_LC+1))
|
ACME_LC=$((ACME_LC+1))
|
||||||
done
|
done
|
||||||
@@ -864,14 +864,14 @@ BACKGROUND_TASKS+=(${PID})
|
|||||||
|
|
||||||
(
|
(
|
||||||
while true; do
|
while true; do
|
||||||
if ! redis_checks; then
|
if ! valkey_checks; then
|
||||||
log_msg "Local Redis hit error limit"
|
log_msg "Local Valkey hit error limit"
|
||||||
echo redis-mailcow > /tmp/com_pipe
|
echo valkey-mailcow > /tmp/com_pipe
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
) &
|
) &
|
||||||
PID=$!
|
PID=$!
|
||||||
echo "Spawned redis_checks with PID ${PID}"
|
echo "Spawned valkey_checks with PID ${PID}"
|
||||||
BACKGROUND_TASKS+=(${PID})
|
BACKGROUND_TASKS+=(${PID})
|
||||||
|
|
||||||
(
|
(
|
||||||
@@ -1129,9 +1129,9 @@ while true; do
|
|||||||
# Define $2 to override message text, else print service was restarted at ...
|
# Define $2 to override message text, else print service was restarted at ...
|
||||||
notify_error "${com_pipe_answer}" "Please check acme-mailcow for further information."
|
notify_error "${com_pipe_answer}" "Please check acme-mailcow for further information."
|
||||||
elif [[ ${com_pipe_answer} == "fail2ban" ]]; then
|
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
|
if [[ ! -z "${F2B_RES}" ]]; then
|
||||||
${REDIS_CMDLINE} DEL F2B_RES > /dev/null
|
${VALKEY_CMDLINE} DEL F2B_RES > /dev/null
|
||||||
host=
|
host=
|
||||||
for host in "${F2B_RES[@]}"; do
|
for host in "${F2B_RES[@]}"; do
|
||||||
log_msg "Banned ${host}"
|
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';
|
require_once '../../../web/inc/lib/vendor/autoload.php';
|
||||||
|
|
||||||
|
|
||||||
// Init Redis
|
// Init Valkey
|
||||||
$redis = new Redis();
|
$valkey = new Redis();
|
||||||
try {
|
try {
|
||||||
if (!empty(getenv('REDIS_SLAVEOF_IP'))) {
|
if (!empty(getenv('VALKEY_SLAVEOF_IP'))) {
|
||||||
$redis->connect(getenv('REDIS_SLAVEOF_IP'), getenv('REDIS_SLAVEOF_PORT'));
|
$valkey->connect(getenv('VALKEY_SLAVEOF_IP'), getenv('VALKEY_SLAVEOF_PORT'));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$redis->connect('redis-mailcow', 6379);
|
$valkey->connect('valkey-mailcow', 6379);
|
||||||
}
|
}
|
||||||
$redis->auth(getenv("REDISPASS"));
|
$valkey->auth(getenv("VALKEYPASS"));
|
||||||
}
|
}
|
||||||
catch (Exception $e) {
|
catch (Exception $e) {
|
||||||
error_log("MAILCOWAUTH: " . $e . PHP_EOL);
|
error_log("MAILCOWAUTH: " . $e . PHP_EOL);
|
||||||
@@ -80,21 +80,14 @@ if ($isSOGoRequest) {
|
|||||||
}
|
}
|
||||||
if ($result === false){
|
if ($result === false){
|
||||||
// If it's a SOGo Request, don't check for protocol access
|
// If it's a SOGo Request, don't check for protocol access
|
||||||
if ($isSOGoRequest) {
|
$service = ($isSOGoRequest) ? false : array($post['service'] => true);
|
||||||
$service = 'SOGO';
|
$result = apppass_login($post['username'], $post['password'], $service, array(
|
||||||
$post['service'] = 'NONE';
|
|
||||||
} else {
|
|
||||||
$service = $post['service'];
|
|
||||||
}
|
|
||||||
|
|
||||||
$result = apppass_login($post['username'], $post['password'], array(
|
|
||||||
'service' => $post['service'],
|
|
||||||
'is_internal' => true,
|
'is_internal' => true,
|
||||||
'remote_addr' => $post['real_rip']
|
'remote_addr' => $post['real_rip']
|
||||||
));
|
));
|
||||||
if ($result) {
|
if ($result) {
|
||||||
error_log('MAILCOWAUTH: App auth for user ' . $post['username'] . " with service " . $service . " from IP " . $post['real_rip']);
|
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'], $service);
|
set_sasl_log($post['username'], $post['real_rip'], $post['service']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($result === false){
|
if ($result === false){
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ events {
|
|||||||
http {
|
http {
|
||||||
include /etc/nginx/mime.types;
|
include /etc/nginx/mime.types;
|
||||||
default_type application/octet-stream;
|
default_type application/octet-stream;
|
||||||
server_tokens off;
|
|
||||||
|
|
||||||
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
||||||
'$status $body_bytes_sent "$http_referer" '
|
'$status $body_bytes_sent "$http_referer" '
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ ssl_session_tickets off;
|
|||||||
|
|
||||||
add_header Strict-Transport-Security "max-age=15768000;";
|
add_header Strict-Transport-Security "max-age=15768000;";
|
||||||
add_header X-Content-Type-Options nosniff;
|
add_header X-Content-Type-Options nosniff;
|
||||||
|
add_header X-XSS-Protection "1; mode=block";
|
||||||
add_header X-Robots-Tag none;
|
add_header X-Robots-Tag none;
|
||||||
add_header X-Download-Options noopen;
|
add_header X-Download-Options noopen;
|
||||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||||
@@ -261,19 +262,19 @@ location ~* /sogo$ {
|
|||||||
}
|
}
|
||||||
|
|
||||||
location /SOGo.woa/WebServerResources/ {
|
location /SOGo.woa/WebServerResources/ {
|
||||||
alias /usr/local/lib/GNUstep/SOGo/WebServerResources/;
|
alias /usr/lib/GNUstep/SOGo/WebServerResources/;
|
||||||
}
|
}
|
||||||
|
|
||||||
location /.woa/WebServerResources/ {
|
location /.woa/WebServerResources/ {
|
||||||
alias /usr/local/lib/GNUstep/SOGo/WebServerResources/;
|
alias /usr/lib/GNUstep/SOGo/WebServerResources/;
|
||||||
}
|
}
|
||||||
|
|
||||||
location /SOGo/WebServerResources/ {
|
location /SOGo/WebServerResources/ {
|
||||||
alias /usr/local/lib/GNUstep/SOGo/WebServerResources/;
|
alias /usr/lib/GNUstep/SOGo/WebServerResources/;
|
||||||
}
|
}
|
||||||
|
|
||||||
location (^/SOGo/so/ControlPanel/Products/[^/]*UI/Resources/.*\.(jpg|png|gif|css|js)$) {
|
location (^/SOGo/so/ControlPanel/Products/[^/]*UI/Resources/.*\.(jpg|png|gif|css|js)$) {
|
||||||
alias /usr/local/lib/GNUstep/SOGo/$1.SOGo/Resources/$2;
|
alias /usr/lib/GNUstep/SOGo/$1.SOGo/Resources/$2;
|
||||||
}
|
}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
|||||||
@@ -23,16 +23,16 @@ catch (PDOException $e) {
|
|||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init Redis
|
// Init Valkey
|
||||||
$redis = new Redis();
|
$valkey = new Redis();
|
||||||
try {
|
try {
|
||||||
if (!empty(getenv('REDIS_SLAVEOF_IP'))) {
|
if (!empty(getenv('VALKEY_SLAVEOF_IP'))) {
|
||||||
$redis->connect(getenv('REDIS_SLAVEOF_IP'), getenv('REDIS_SLAVEOF_PORT'));
|
$valkey->connect(getenv('VALKEY_SLAVEOF_IP'), getenv('VALKEY_SLAVEOF_PORT'));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$redis->connect('redis-mailcow', 6379);
|
$valkey->connect('valkey-mailcow', 6379);
|
||||||
}
|
}
|
||||||
$redis->auth(getenv("REDISPASS"));
|
$valkey->auth(getenv("VALKEYPASS"));
|
||||||
}
|
}
|
||||||
catch (Exception $e) {
|
catch (Exception $e) {
|
||||||
echo "Exiting: " . $e->getMessage();
|
echo "Exiting: " . $e->getMessage();
|
||||||
@@ -41,7 +41,7 @@ catch (Exception $e) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function logMsg($priority, $message, $task = "Keycloak Sync") {
|
function logMsg($priority, $message, $task = "Keycloak Sync") {
|
||||||
global $redis;
|
global $valkey;
|
||||||
|
|
||||||
$finalMsg = array(
|
$finalMsg = array(
|
||||||
"time" => time(),
|
"time" => time(),
|
||||||
@@ -49,7 +49,7 @@ function logMsg($priority, $message, $task = "Keycloak Sync") {
|
|||||||
"task" => $task,
|
"task" => $task,
|
||||||
"message" => $message
|
"message" => $message
|
||||||
);
|
);
|
||||||
$redis->lPush('CRON_LOG', json_encode($finalMsg));
|
$valkey->lPush('CRON_LOG', json_encode($finalMsg));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load core functions first
|
// Load core functions first
|
||||||
|
|||||||
@@ -23,16 +23,16 @@ catch (PDOException $e) {
|
|||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init Redis
|
// Init Valkey
|
||||||
$redis = new Redis();
|
$valkey = new Redis();
|
||||||
try {
|
try {
|
||||||
if (!empty(getenv('REDIS_SLAVEOF_IP'))) {
|
if (!empty(getenv('VALKEY_SLAVEOF_IP'))) {
|
||||||
$redis->connect(getenv('REDIS_SLAVEOF_IP'), getenv('REDIS_SLAVEOF_PORT'));
|
$valkey->connect(getenv('VALKEY_SLAVEOF_IP'), getenv('VALKEY_SLAVEOF_PORT'));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$redis->connect('redis-mailcow', 6379);
|
$valkey->connect('valkey-mailcow', 6379);
|
||||||
}
|
}
|
||||||
$redis->auth(getenv("REDISPASS"));
|
$valkey->auth(getenv("VALKEYPASS"));
|
||||||
}
|
}
|
||||||
catch (Exception $e) {
|
catch (Exception $e) {
|
||||||
echo "Exiting: " . $e->getMessage();
|
echo "Exiting: " . $e->getMessage();
|
||||||
@@ -41,7 +41,7 @@ catch (Exception $e) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function logMsg($priority, $message, $task = "LDAP Sync") {
|
function logMsg($priority, $message, $task = "LDAP Sync") {
|
||||||
global $redis;
|
global $valkey;
|
||||||
|
|
||||||
$finalMsg = array(
|
$finalMsg = array(
|
||||||
"time" => time(),
|
"time" => time(),
|
||||||
@@ -49,7 +49,7 @@ function logMsg($priority, $message, $task = "LDAP Sync") {
|
|||||||
"task" => $task,
|
"task" => $task,
|
||||||
"message" => $message
|
"message" => $message
|
||||||
);
|
);
|
||||||
$redis->lPush('CRON_LOG', json_encode($finalMsg));
|
$valkey->lPush('CRON_LOG', json_encode($finalMsg));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load core functions first
|
// Load core functions first
|
||||||
|
|||||||
@@ -1,16 +1,7 @@
|
|||||||
; NOTE: Restart phpfpm on ANY manual changes to PHP files!
|
|
||||||
|
|
||||||
; opcache
|
|
||||||
opcache.enable=1
|
opcache.enable=1
|
||||||
opcache.enable_cli=1
|
opcache.enable_cli=1
|
||||||
opcache.interned_strings_buffer=16
|
opcache.interned_strings_buffer=16
|
||||||
opcache.max_accelerated_files=10000
|
opcache.max_accelerated_files=10000
|
||||||
opcache.memory_consumption=128
|
opcache.memory_consumption=128
|
||||||
opcache.save_comments=1
|
opcache.save_comments=1
|
||||||
opcache.validate_timestamps=0
|
opcache.revalidate_freq=1
|
||||||
|
|
||||||
; JIT
|
|
||||||
; Disabled for now due to some PHP segmentation faults observed
|
|
||||||
; in certain environments. Possibly some PHP or PHP extension bug.
|
|
||||||
opcache.jit=disable
|
|
||||||
opcache.jit_buffer_size=0
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Whitelist generated by Postwhite v3.4 on Sun Mar 1 00:29:01 UTC 2026
|
# Whitelist generated by Postwhite v3.4 on Wed Oct 1 00:21:33 UTC 2025
|
||||||
# https://github.com/stevejenkins/postwhite/
|
# https://github.com/stevejenkins/postwhite/
|
||||||
# 2174 total rules
|
# 2216 total rules
|
||||||
2a00:1450:4000::/36 permit
|
2a00:1450:4000::/36 permit
|
||||||
2a01:111:f400::/48 permit
|
2a01:111:f400::/48 permit
|
||||||
2a01:111:f403:2800::/53 permit
|
2a01:111:f403:2800::/53 permit
|
||||||
@@ -29,9 +29,7 @@
|
|||||||
2a01:b747:3005:200::/56 permit
|
2a01:b747:3005:200::/56 permit
|
||||||
2a01:b747:3006:200::/56 permit
|
2a01:b747:3006:200::/56 permit
|
||||||
2a02:a60:0:5::/64 permit
|
2a02:a60:0:5::/64 permit
|
||||||
2a0f:f640::/56 permit
|
|
||||||
2c0f:fb50:4000::/36 permit
|
2c0f:fb50:4000::/36 permit
|
||||||
2.207.151.53 permit
|
|
||||||
2.207.217.30 permit
|
2.207.217.30 permit
|
||||||
3.64.237.68 permit
|
3.64.237.68 permit
|
||||||
3.65.3.180 permit
|
3.65.3.180 permit
|
||||||
@@ -58,9 +56,8 @@
|
|||||||
8.40.222.0/23 permit
|
8.40.222.0/23 permit
|
||||||
8.40.222.250/31 permit
|
8.40.222.250/31 permit
|
||||||
12.130.86.238 permit
|
12.130.86.238 permit
|
||||||
13.107.213.51 permit
|
13.107.213.41 permit
|
||||||
13.107.246.51 permit
|
13.107.246.41 permit
|
||||||
13.108.16.0/20 permit
|
|
||||||
13.110.208.0/21 permit
|
13.110.208.0/21 permit
|
||||||
13.110.209.0/24 permit
|
13.110.209.0/24 permit
|
||||||
13.110.216.0/22 permit
|
13.110.216.0/22 permit
|
||||||
@@ -69,7 +66,6 @@
|
|||||||
13.111.191.0/24 permit
|
13.111.191.0/24 permit
|
||||||
13.216.7.111 permit
|
13.216.7.111 permit
|
||||||
13.216.54.180 permit
|
13.216.54.180 permit
|
||||||
13.247.164.219 permit
|
|
||||||
15.200.21.50 permit
|
15.200.21.50 permit
|
||||||
15.200.44.248 permit
|
15.200.44.248 permit
|
||||||
15.200.201.185 permit
|
15.200.201.185 permit
|
||||||
@@ -303,6 +299,15 @@
|
|||||||
52.94.124.0/28 permit
|
52.94.124.0/28 permit
|
||||||
52.95.48.152/29 permit
|
52.95.48.152/29 permit
|
||||||
52.95.49.88/29 permit
|
52.95.49.88/29 permit
|
||||||
|
52.96.91.34 permit
|
||||||
|
52.96.111.82 permit
|
||||||
|
52.96.172.98 permit
|
||||||
|
52.96.214.50 permit
|
||||||
|
52.96.222.194 permit
|
||||||
|
52.96.222.226 permit
|
||||||
|
52.96.223.2 permit
|
||||||
|
52.96.228.130 permit
|
||||||
|
52.96.229.242 permit
|
||||||
52.100.0.0/15 permit
|
52.100.0.0/15 permit
|
||||||
52.102.0.0/16 permit
|
52.102.0.0/16 permit
|
||||||
52.103.0.0/17 permit
|
52.103.0.0/17 permit
|
||||||
@@ -397,8 +402,27 @@
|
|||||||
64.207.219.143 permit
|
64.207.219.143 permit
|
||||||
64.233.160.0/19 permit
|
64.233.160.0/19 permit
|
||||||
65.52.80.137 permit
|
65.52.80.137 permit
|
||||||
|
65.54.51.64/26 permit
|
||||||
|
65.54.61.64/26 permit
|
||||||
|
65.54.121.120/29 permit
|
||||||
|
65.54.190.0/24 permit
|
||||||
|
65.54.241.0/24 permit
|
||||||
65.55.29.77 permit
|
65.55.29.77 permit
|
||||||
|
65.55.33.64/28 permit
|
||||||
|
65.55.34.0/24 permit
|
||||||
65.55.42.224/28 permit
|
65.55.42.224/28 permit
|
||||||
|
65.55.52.224/27 permit
|
||||||
|
65.55.78.128/25 permit
|
||||||
|
65.55.81.48/28 permit
|
||||||
|
65.55.90.0/24 permit
|
||||||
|
65.55.94.0/25 permit
|
||||||
|
65.55.111.0/24 permit
|
||||||
|
65.55.113.64/26 permit
|
||||||
|
65.55.116.0/25 permit
|
||||||
|
65.55.126.0/25 permit
|
||||||
|
65.55.174.0/25 permit
|
||||||
|
65.55.178.128/27 permit
|
||||||
|
65.55.234.192/26 permit
|
||||||
65.110.161.77 permit
|
65.110.161.77 permit
|
||||||
65.123.29.213 permit
|
65.123.29.213 permit
|
||||||
65.123.29.220 permit
|
65.123.29.220 permit
|
||||||
@@ -519,6 +543,7 @@
|
|||||||
69.169.224.0/20 permit
|
69.169.224.0/20 permit
|
||||||
69.171.232.0/24 permit
|
69.171.232.0/24 permit
|
||||||
69.171.244.0/23 permit
|
69.171.244.0/23 permit
|
||||||
|
70.37.151.128/25 permit
|
||||||
70.42.149.35 permit
|
70.42.149.35 permit
|
||||||
72.3.185.0/24 permit
|
72.3.185.0/24 permit
|
||||||
72.14.192.0/18 permit
|
72.14.192.0/18 permit
|
||||||
@@ -615,6 +640,7 @@
|
|||||||
74.208.4.220 permit
|
74.208.4.220 permit
|
||||||
74.208.4.221 permit
|
74.208.4.221 permit
|
||||||
74.209.250.0/24 permit
|
74.209.250.0/24 permit
|
||||||
|
75.2.70.75 permit
|
||||||
76.223.128.0/19 permit
|
76.223.128.0/19 permit
|
||||||
76.223.176.0/20 permit
|
76.223.176.0/20 permit
|
||||||
77.238.176.0/24 permit
|
77.238.176.0/24 permit
|
||||||
@@ -707,11 +733,11 @@
|
|||||||
87.248.117.205 permit
|
87.248.117.205 permit
|
||||||
87.253.232.0/21 permit
|
87.253.232.0/21 permit
|
||||||
89.22.108.0/24 permit
|
89.22.108.0/24 permit
|
||||||
91.198.2.177 permit
|
91.198.2.0/24 permit
|
||||||
91.198.2.217 permit
|
|
||||||
91.198.2.222 permit
|
|
||||||
91.211.240.0/22 permit
|
91.211.240.0/22 permit
|
||||||
94.236.119.0/26 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.131.104.0/21 permit
|
||||||
95.217.114.154 permit
|
95.217.114.154 permit
|
||||||
96.43.144.0/20 permit
|
96.43.144.0/20 permit
|
||||||
@@ -1204,9 +1230,9 @@
|
|||||||
98.139.245.208/30 permit
|
98.139.245.208/30 permit
|
||||||
98.139.245.212/31 permit
|
98.139.245.212/31 permit
|
||||||
99.78.197.208/28 permit
|
99.78.197.208/28 permit
|
||||||
|
99.83.190.102 permit
|
||||||
103.9.96.0/22 permit
|
103.9.96.0/22 permit
|
||||||
103.28.42.0/24 permit
|
103.28.42.0/24 permit
|
||||||
103.84.217.15 permit
|
|
||||||
103.84.217.238 permit
|
103.84.217.238 permit
|
||||||
103.89.75.238 permit
|
103.89.75.238 permit
|
||||||
103.151.192.0/23 permit
|
103.151.192.0/23 permit
|
||||||
@@ -1352,6 +1378,11 @@
|
|||||||
108.179.144.0/20 permit
|
108.179.144.0/20 permit
|
||||||
109.224.244.0/24 permit
|
109.224.244.0/24 permit
|
||||||
109.237.142.0/24 permit
|
109.237.142.0/24 permit
|
||||||
|
111.221.23.128/25 permit
|
||||||
|
111.221.26.0/27 permit
|
||||||
|
111.221.66.0/25 permit
|
||||||
|
111.221.69.128/25 permit
|
||||||
|
111.221.112.0/21 permit
|
||||||
112.19.199.64/29 permit
|
112.19.199.64/29 permit
|
||||||
112.19.242.64/29 permit
|
112.19.242.64/29 permit
|
||||||
116.214.12.47 permit
|
116.214.12.47 permit
|
||||||
@@ -1399,7 +1430,6 @@
|
|||||||
128.245.248.0/21 permit
|
128.245.248.0/21 permit
|
||||||
129.41.77.70 permit
|
129.41.77.70 permit
|
||||||
129.41.169.249 permit
|
129.41.169.249 permit
|
||||||
129.77.16.0/20 permit
|
|
||||||
129.80.5.164 permit
|
129.80.5.164 permit
|
||||||
129.80.64.36 permit
|
129.80.64.36 permit
|
||||||
129.80.67.121 permit
|
129.80.67.121 permit
|
||||||
@@ -1416,7 +1446,6 @@
|
|||||||
129.153.194.228 permit
|
129.153.194.228 permit
|
||||||
129.154.255.129 permit
|
129.154.255.129 permit
|
||||||
129.158.56.255 permit
|
129.158.56.255 permit
|
||||||
129.158.62.153 permit
|
|
||||||
129.159.22.159 permit
|
129.159.22.159 permit
|
||||||
129.159.87.137 permit
|
129.159.87.137 permit
|
||||||
129.213.195.191 permit
|
129.213.195.191 permit
|
||||||
@@ -1452,7 +1481,6 @@
|
|||||||
136.143.184.0/24 permit
|
136.143.184.0/24 permit
|
||||||
136.143.188.0/24 permit
|
136.143.188.0/24 permit
|
||||||
136.143.190.0/23 permit
|
136.143.190.0/23 permit
|
||||||
136.146.128.0/20 permit
|
|
||||||
136.147.128.0/20 permit
|
136.147.128.0/20 permit
|
||||||
136.147.135.0/24 permit
|
136.147.135.0/24 permit
|
||||||
136.147.176.0/20 permit
|
136.147.176.0/20 permit
|
||||||
@@ -1470,8 +1498,6 @@
|
|||||||
139.167.79.86 permit
|
139.167.79.86 permit
|
||||||
139.180.17.0/24 permit
|
139.180.17.0/24 permit
|
||||||
140.238.148.191 permit
|
140.238.148.191 permit
|
||||||
141.148.55.217 permit
|
|
||||||
141.148.91.244 permit
|
|
||||||
141.148.159.229 permit
|
141.148.159.229 permit
|
||||||
141.193.32.0/23 permit
|
141.193.32.0/23 permit
|
||||||
141.193.184.32/27 permit
|
141.193.184.32/27 permit
|
||||||
@@ -1517,7 +1543,6 @@
|
|||||||
149.72.234.184 permit
|
149.72.234.184 permit
|
||||||
149.72.248.236 permit
|
149.72.248.236 permit
|
||||||
149.97.173.180 permit
|
149.97.173.180 permit
|
||||||
150.136.21.199 permit
|
|
||||||
150.230.98.160 permit
|
150.230.98.160 permit
|
||||||
151.145.38.14 permit
|
151.145.38.14 permit
|
||||||
152.67.105.195 permit
|
152.67.105.195 permit
|
||||||
@@ -1527,7 +1552,20 @@
|
|||||||
155.248.220.138 permit
|
155.248.220.138 permit
|
||||||
155.248.234.149 permit
|
155.248.234.149 permit
|
||||||
155.248.237.141 permit
|
155.248.237.141 permit
|
||||||
|
157.55.0.192/26 permit
|
||||||
|
157.55.1.128/26 permit
|
||||||
|
157.55.2.0/25 permit
|
||||||
|
157.55.9.128/25 permit
|
||||||
|
157.55.11.0/25 permit
|
||||||
|
157.55.49.0/25 permit
|
||||||
|
157.55.61.0/24 permit
|
||||||
|
157.55.157.128/25 permit
|
||||||
|
157.55.225.0/25 permit
|
||||||
|
157.56.24.0/25 permit
|
||||||
157.56.120.128/26 permit
|
157.56.120.128/26 permit
|
||||||
|
157.56.232.0/21 permit
|
||||||
|
157.56.240.0/20 permit
|
||||||
|
157.56.248.0/21 permit
|
||||||
157.58.30.128/25 permit
|
157.58.30.128/25 permit
|
||||||
157.58.196.96/29 permit
|
157.58.196.96/29 permit
|
||||||
157.58.249.3 permit
|
157.58.249.3 permit
|
||||||
@@ -1556,10 +1594,8 @@
|
|||||||
159.135.224.0/20 permit
|
159.135.224.0/20 permit
|
||||||
159.135.228.10 permit
|
159.135.228.10 permit
|
||||||
159.183.0.0/16 permit
|
159.183.0.0/16 permit
|
||||||
159.183.14.233 permit
|
|
||||||
159.183.68.71 permit
|
159.183.68.71 permit
|
||||||
159.183.79.38 permit
|
159.183.79.38 permit
|
||||||
159.183.121.182 permit
|
|
||||||
159.183.129.172 permit
|
159.183.129.172 permit
|
||||||
160.1.62.192 permit
|
160.1.62.192 permit
|
||||||
161.38.192.0/20 permit
|
161.38.192.0/20 permit
|
||||||
@@ -1579,14 +1615,10 @@
|
|||||||
163.114.135.16 permit
|
163.114.135.16 permit
|
||||||
163.116.128.0/17 permit
|
163.116.128.0/17 permit
|
||||||
163.192.116.87 permit
|
163.192.116.87 permit
|
||||||
163.192.125.176 permit
|
|
||||||
163.192.196.146 permit
|
|
||||||
163.192.204.161 permit
|
|
||||||
164.152.23.32 permit
|
164.152.23.32 permit
|
||||||
164.152.25.241 permit
|
164.152.25.241 permit
|
||||||
164.177.132.168/30 permit
|
164.177.132.168/30 permit
|
||||||
165.173.128.0/24 permit
|
165.173.128.0/24 permit
|
||||||
165.173.180.1 permit
|
|
||||||
165.173.180.250/31 permit
|
165.173.180.250/31 permit
|
||||||
165.173.182.250/31 permit
|
165.173.182.250/31 permit
|
||||||
166.78.68.0/22 permit
|
166.78.68.0/22 permit
|
||||||
@@ -1627,15 +1659,16 @@
|
|||||||
169.148.144.10 permit
|
169.148.144.10 permit
|
||||||
169.148.146.0/23 permit
|
169.148.146.0/23 permit
|
||||||
169.148.175.3 permit
|
169.148.175.3 permit
|
||||||
169.148.179.3 permit
|
|
||||||
169.148.188.0/24 permit
|
169.148.188.0/24 permit
|
||||||
169.148.188.182 permit
|
169.148.188.182 permit
|
||||||
170.9.232.254 permit
|
|
||||||
170.10.128.0/24 permit
|
170.10.128.0/24 permit
|
||||||
170.10.129.0/24 permit
|
170.10.129.0/24 permit
|
||||||
170.10.132.56/29 permit
|
170.10.132.56/29 permit
|
||||||
170.10.132.64/29 permit
|
170.10.132.64/29 permit
|
||||||
170.10.133.0/24 permit
|
170.10.133.0/24 permit
|
||||||
|
172.217.32.0/20 permit
|
||||||
|
172.253.56.0/21 permit
|
||||||
|
172.253.112.0/20 permit
|
||||||
173.0.84.0/29 permit
|
173.0.84.0/29 permit
|
||||||
173.0.84.224/27 permit
|
173.0.84.224/27 permit
|
||||||
173.0.94.244/30 permit
|
173.0.94.244/30 permit
|
||||||
@@ -1663,7 +1696,8 @@
|
|||||||
182.50.78.64/28 permit
|
182.50.78.64/28 permit
|
||||||
183.240.219.64/29 permit
|
183.240.219.64/29 permit
|
||||||
185.4.120.0/22 permit
|
185.4.120.0/22 permit
|
||||||
185.11.255.144 permit
|
185.11.253.128/27 permit
|
||||||
|
185.11.255.0/24 permit
|
||||||
185.12.80.0/22 permit
|
185.12.80.0/22 permit
|
||||||
185.28.196.0/22 permit
|
185.28.196.0/22 permit
|
||||||
185.58.84.93 permit
|
185.58.84.93 permit
|
||||||
@@ -1677,16 +1711,8 @@
|
|||||||
185.138.56.128/25 permit
|
185.138.56.128/25 permit
|
||||||
185.189.236.0/22 permit
|
185.189.236.0/22 permit
|
||||||
185.211.120.0/22 permit
|
185.211.120.0/22 permit
|
||||||
185.233.188.68 permit
|
185.233.188.0/23 permit
|
||||||
185.233.188.75 permit
|
185.233.190.0/23 permit
|
||||||
185.233.188.84 permit
|
|
||||||
185.233.188.160 permit
|
|
||||||
185.233.188.176 permit
|
|
||||||
185.233.188.247 permit
|
|
||||||
185.233.189.44 permit
|
|
||||||
185.233.189.98 permit
|
|
||||||
185.233.189.122 permit
|
|
||||||
185.233.189.228 permit
|
|
||||||
185.250.236.0/22 permit
|
185.250.236.0/22 permit
|
||||||
185.250.239.148 permit
|
185.250.239.148 permit
|
||||||
185.250.239.168 permit
|
185.250.239.168 permit
|
||||||
@@ -1762,9 +1788,7 @@
|
|||||||
193.109.254.0/23 permit
|
193.109.254.0/23 permit
|
||||||
193.122.128.100 permit
|
193.122.128.100 permit
|
||||||
193.123.56.63 permit
|
193.123.56.63 permit
|
||||||
193.142.157.15 permit
|
193.142.157.0/24 permit
|
||||||
193.142.157.125 permit
|
|
||||||
193.142.157.158 permit
|
|
||||||
193.142.157.191 permit
|
193.142.157.191 permit
|
||||||
193.142.157.198 permit
|
193.142.157.198 permit
|
||||||
194.19.134.0/25 permit
|
194.19.134.0/25 permit
|
||||||
@@ -1788,7 +1812,6 @@
|
|||||||
194.97.212.12 permit
|
194.97.212.12 permit
|
||||||
194.106.220.0/23 permit
|
194.106.220.0/23 permit
|
||||||
194.113.24.0/22 permit
|
194.113.24.0/22 permit
|
||||||
194.113.42.0/26 permit
|
|
||||||
194.154.193.192/27 permit
|
194.154.193.192/27 permit
|
||||||
195.4.92.0/23 permit
|
195.4.92.0/23 permit
|
||||||
195.54.172.0/23 permit
|
195.54.172.0/23 permit
|
||||||
@@ -1802,7 +1825,6 @@
|
|||||||
198.61.254.21 permit
|
198.61.254.21 permit
|
||||||
198.61.254.231 permit
|
198.61.254.231 permit
|
||||||
198.178.234.57 permit
|
198.178.234.57 permit
|
||||||
198.202.211.1 permit
|
|
||||||
198.244.48.0/20 permit
|
198.244.48.0/20 permit
|
||||||
198.244.56.107 permit
|
198.244.56.107 permit
|
||||||
198.244.56.108 permit
|
198.244.56.108 permit
|
||||||
@@ -1886,6 +1908,7 @@
|
|||||||
204.14.232.64/28 permit
|
204.14.232.64/28 permit
|
||||||
204.14.234.64/28 permit
|
204.14.234.64/28 permit
|
||||||
204.75.142.0/24 permit
|
204.75.142.0/24 permit
|
||||||
|
204.79.197.212 permit
|
||||||
204.92.114.187 permit
|
204.92.114.187 permit
|
||||||
204.92.114.203 permit
|
204.92.114.203 permit
|
||||||
204.92.114.204/31 permit
|
204.92.114.204/31 permit
|
||||||
@@ -1913,13 +1936,24 @@
|
|||||||
206.165.246.80/29 permit
|
206.165.246.80/29 permit
|
||||||
206.191.224.0/19 permit
|
206.191.224.0/19 permit
|
||||||
206.246.157.1 permit
|
206.246.157.1 permit
|
||||||
|
207.46.4.128/25 permit
|
||||||
207.46.22.35 permit
|
207.46.22.35 permit
|
||||||
207.46.50.72 permit
|
207.46.50.72 permit
|
||||||
207.46.50.82 permit
|
207.46.50.82 permit
|
||||||
|
207.46.50.192/26 permit
|
||||||
|
207.46.50.224 permit
|
||||||
207.46.52.71 permit
|
207.46.52.71 permit
|
||||||
207.46.52.79 permit
|
207.46.52.79 permit
|
||||||
|
207.46.58.128/25 permit
|
||||||
|
207.46.116.128/29 permit
|
||||||
|
207.46.117.0/24 permit
|
||||||
|
207.46.132.128/27 permit
|
||||||
|
207.46.198.0/25 permit
|
||||||
|
207.46.200.0/27 permit
|
||||||
207.67.38.0/24 permit
|
207.67.38.0/24 permit
|
||||||
207.67.98.192/27 permit
|
207.67.98.192/27 permit
|
||||||
|
207.68.176.0/26 permit
|
||||||
|
207.68.176.96/27 permit
|
||||||
207.97.204.96/29 permit
|
207.97.204.96/29 permit
|
||||||
207.126.144.0/20 permit
|
207.126.144.0/20 permit
|
||||||
207.171.160.0/19 permit
|
207.171.160.0/19 permit
|
||||||
@@ -2068,10 +2102,14 @@
|
|||||||
212.227.126.225 permit
|
212.227.126.225 permit
|
||||||
212.227.126.226 permit
|
212.227.126.226 permit
|
||||||
212.227.126.227 permit
|
212.227.126.227 permit
|
||||||
|
213.95.19.64/27 permit
|
||||||
|
213.95.135.4 permit
|
||||||
213.199.128.139 permit
|
213.199.128.139 permit
|
||||||
213.199.128.145 permit
|
213.199.128.145 permit
|
||||||
213.199.138.181 permit
|
213.199.138.181 permit
|
||||||
213.199.138.191 permit
|
213.199.138.191 permit
|
||||||
|
213.199.161.128/27 permit
|
||||||
|
213.199.177.0/26 permit
|
||||||
216.17.150.242 permit
|
216.17.150.242 permit
|
||||||
216.17.150.251 permit
|
216.17.150.251 permit
|
||||||
216.24.224.0/20 permit
|
216.24.224.0/20 permit
|
||||||
@@ -2099,6 +2137,7 @@
|
|||||||
216.39.62.60/31 permit
|
216.39.62.60/31 permit
|
||||||
216.39.62.136/29 permit
|
216.39.62.136/29 permit
|
||||||
216.39.62.144/31 permit
|
216.39.62.144/31 permit
|
||||||
|
216.58.192.0/19 permit
|
||||||
216.66.217.240/29 permit
|
216.66.217.240/29 permit
|
||||||
216.71.138.33 permit
|
216.71.138.33 permit
|
||||||
216.71.152.207 permit
|
216.71.152.207 permit
|
||||||
@@ -2157,6 +2196,11 @@
|
|||||||
2001:748:400:3301::3 permit
|
2001:748:400:3301::3 permit
|
||||||
2001:748:400:3301::4 permit
|
2001:748:400:3301::4 permit
|
||||||
2404:6800:4000::/36 permit
|
2404:6800:4000::/36 permit
|
||||||
|
2603:1010:3:3::5b permit
|
||||||
|
2603:1020:201:10::10f permit
|
||||||
|
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:0001:0000:0000:0000:0000:7000/116 permit
|
||||||
2607:13c0:0002:0000:0000:0000:0000:1000/116 permit
|
2607:13c0:0002:0000:0000:0000:0000:1000/116 permit
|
||||||
2607:13c0:0004:0000:0000:0000:0000:0000/116 permit
|
2607:13c0:0004:0000:0000:0000:0000:0000/116 permit
|
||||||
@@ -2172,8 +2216,6 @@
|
|||||||
2620:10d:c09c:400::8:1 permit
|
2620:10d:c09c:400::8:1 permit
|
||||||
2620:119:50c0:207::/64 permit
|
2620:119:50c0:207::/64 permit
|
||||||
2620:119:50c0:207::215 permit
|
2620:119:50c0:207::215 permit
|
||||||
2620:1ec:46::51 permit
|
|
||||||
2620:1ec:bdf::51 permit
|
|
||||||
2800:3f0:4000::/36 permit
|
2800:3f0:4000::/36 permit
|
||||||
49.12.4.251 permit # checks.mailcow.email
|
49.12.4.251 permit # checks.mailcow.email
|
||||||
2a01:4f8:c17:7906::10 permit # checks.mailcow.email
|
2a01:4f8:c17:7906::10 permit # checks.mailcow.email
|
||||||
|
|||||||
@@ -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;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init Redis
|
// Init Valkey
|
||||||
$redis = new Redis();
|
$valkey = new Redis();
|
||||||
$redis->connect('redis-mailcow', 6379);
|
$valkey->connect('valkey-mailcow', 6379);
|
||||||
$redis->auth(getenv("REDISPASS"));
|
$valkey->auth(getenv("VALKEYPASS"));
|
||||||
|
|
||||||
function parse_email($email) {
|
function parse_email($email) {
|
||||||
if(!filter_var($email, FILTER_VALIDATE_EMAIL)) return false;
|
if(!filter_var($email, FILTER_VALIDATE_EMAIL)) return false;
|
||||||
@@ -60,7 +60,7 @@ $rcpt_final_mailboxes = array();
|
|||||||
|
|
||||||
// Skip if not a mailcow handled domain
|
// Skip if not a mailcow handled domain
|
||||||
try {
|
try {
|
||||||
if (!$redis->hGet('DOMAIN_MAP', $parsed_rcpt['domain'])) {
|
if (!$valkey->hGet('DOMAIN_MAP', $parsed_rcpt['domain'])) {
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -122,7 +122,7 @@ try {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$parsed_goto = parse_email($goto);
|
$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);
|
error_log("ALIAS EXPANDER:" . $goto . " is not a mailcow handled mailbox or alias address" . PHP_EOL);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
header('Content-Type: text/plain');
|
header('Content-Type: text/plain');
|
||||||
ini_set('error_reporting', 0);
|
ini_set('error_reporting', 0);
|
||||||
|
|
||||||
$redis = new Redis();
|
$valkey = new Redis();
|
||||||
$redis->connect('redis-mailcow', 6379);
|
$valkey->connect('valkey-mailcow', 6379);
|
||||||
$redis->auth(getenv("REDISPASS"));
|
$valkey->auth(getenv("VALKEYPASS"));
|
||||||
|
|
||||||
function in_net($addr, $net) {
|
function in_net($addr, $net) {
|
||||||
$net = explode('/', $net);
|
$net = explode('/', $net);
|
||||||
@@ -31,7 +31,7 @@ function in_net($addr, $net) {
|
|||||||
|
|
||||||
if (isset($_GET['host'])) {
|
if (isset($_GET['host'])) {
|
||||||
try {
|
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)) {
|
if (in_net($_GET['host'], $host)) {
|
||||||
echo '200 PERMIT';
|
echo '200 PERMIT';
|
||||||
exit;
|
exit;
|
||||||
@@ -46,7 +46,7 @@ if (isset($_GET['host'])) {
|
|||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
echo '240.240.240.240' . PHP_EOL;
|
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;
|
echo $host . PHP_EOL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -146,171 +146,8 @@ rspamd_config:register_symbol({
|
|||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Helper function to parse IPv6 into 8 segments
|
|
||||||
local function ipv6_to_segments(ip_str)
|
|
||||||
-- Remove zone identifier if present (e.g., %eth0)
|
|
||||||
ip_str = ip_str:gsub("%%.*$", "")
|
|
||||||
|
|
||||||
local segments = {}
|
|
||||||
|
|
||||||
-- Handle :: compression
|
|
||||||
if ip_str:find('::') then
|
|
||||||
local before, after = ip_str:match('^(.*)::(.*)$')
|
|
||||||
before = before or ''
|
|
||||||
after = after or ''
|
|
||||||
|
|
||||||
local before_parts = {}
|
|
||||||
local after_parts = {}
|
|
||||||
|
|
||||||
if before ~= '' then
|
|
||||||
for seg in before:gmatch('[^:]+') do
|
|
||||||
table.insert(before_parts, tonumber(seg, 16) or 0)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if after ~= '' then
|
|
||||||
for seg in after:gmatch('[^:]+') do
|
|
||||||
table.insert(after_parts, tonumber(seg, 16) or 0)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Add before segments
|
|
||||||
for _, seg in ipairs(before_parts) do
|
|
||||||
table.insert(segments, seg)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Add compressed zeros
|
|
||||||
local zeros_needed = 8 - #before_parts - #after_parts
|
|
||||||
for i = 1, zeros_needed do
|
|
||||||
table.insert(segments, 0)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Add after segments
|
|
||||||
for _, seg in ipairs(after_parts) do
|
|
||||||
table.insert(segments, seg)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
-- No compression
|
|
||||||
for seg in ip_str:gmatch('[^:]+') do
|
|
||||||
table.insert(segments, tonumber(seg, 16) or 0)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Ensure we have exactly 8 segments
|
|
||||||
while #segments < 8 do
|
|
||||||
table.insert(segments, 0)
|
|
||||||
end
|
|
||||||
|
|
||||||
return segments
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Generate all common IPv6 notations
|
|
||||||
local function get_ipv6_variants(ip_str)
|
|
||||||
local variants = {}
|
|
||||||
local seen = {}
|
|
||||||
|
|
||||||
local function add_variant(v)
|
|
||||||
if v and not seen[v] then
|
|
||||||
table.insert(variants, v)
|
|
||||||
seen[v] = true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- For IPv4, just return the original
|
|
||||||
if not ip_str:find(':') then
|
|
||||||
add_variant(ip_str)
|
|
||||||
return variants
|
|
||||||
end
|
|
||||||
|
|
||||||
local segments = ipv6_to_segments(ip_str)
|
|
||||||
|
|
||||||
-- 1. Fully expanded form (all zeros shown as 0000)
|
|
||||||
local expanded_parts = {}
|
|
||||||
for _, seg in ipairs(segments) do
|
|
||||||
table.insert(expanded_parts, string.format('%04x', seg))
|
|
||||||
end
|
|
||||||
add_variant(table.concat(expanded_parts, ':'))
|
|
||||||
|
|
||||||
-- 2. Standard form (no leading zeros, but all segments present)
|
|
||||||
local standard_parts = {}
|
|
||||||
for _, seg in ipairs(segments) do
|
|
||||||
table.insert(standard_parts, string.format('%x', seg))
|
|
||||||
end
|
|
||||||
add_variant(table.concat(standard_parts, ':'))
|
|
||||||
|
|
||||||
-- 3. Find all possible :: compressions
|
|
||||||
-- RFC 5952: compress the longest run of consecutive zeros
|
|
||||||
-- But we need to check all possibilities since Redis might have any form
|
|
||||||
|
|
||||||
-- Find all zero runs
|
|
||||||
local zero_runs = {}
|
|
||||||
local in_run = false
|
|
||||||
local run_start = 0
|
|
||||||
local run_length = 0
|
|
||||||
|
|
||||||
for i = 1, 8 do
|
|
||||||
if segments[i] == 0 then
|
|
||||||
if not in_run then
|
|
||||||
in_run = true
|
|
||||||
run_start = i
|
|
||||||
run_length = 1
|
|
||||||
else
|
|
||||||
run_length = run_length + 1
|
|
||||||
end
|
|
||||||
else
|
|
||||||
if in_run then
|
|
||||||
if run_length >= 1 then -- Allow single zero compression too
|
|
||||||
table.insert(zero_runs, {start = run_start, length = run_length})
|
|
||||||
end
|
|
||||||
in_run = false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Don't forget the last run
|
|
||||||
if in_run and run_length >= 1 then
|
|
||||||
table.insert(zero_runs, {start = run_start, length = run_length})
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Generate variant for each zero run compression
|
|
||||||
for _, run in ipairs(zero_runs) do
|
|
||||||
local parts = {}
|
|
||||||
|
|
||||||
-- Before compression
|
|
||||||
for i = 1, run.start - 1 do
|
|
||||||
table.insert(parts, string.format('%x', segments[i]))
|
|
||||||
end
|
|
||||||
|
|
||||||
-- The compression
|
|
||||||
if run.start == 1 then
|
|
||||||
table.insert(parts, '')
|
|
||||||
table.insert(parts, '')
|
|
||||||
elseif run.start + run.length - 1 == 8 then
|
|
||||||
table.insert(parts, '')
|
|
||||||
table.insert(parts, '')
|
|
||||||
else
|
|
||||||
table.insert(parts, '')
|
|
||||||
end
|
|
||||||
|
|
||||||
-- After compression
|
|
||||||
for i = run.start + run.length, 8 do
|
|
||||||
table.insert(parts, string.format('%x', segments[i]))
|
|
||||||
end
|
|
||||||
|
|
||||||
local compressed = table.concat(parts, ':'):gsub('::+', '::')
|
|
||||||
add_variant(compressed)
|
|
||||||
end
|
|
||||||
|
|
||||||
return variants
|
|
||||||
end
|
|
||||||
|
|
||||||
local from_ip_string = tostring(ip)
|
local from_ip_string = tostring(ip)
|
||||||
local ip_check_table = {}
|
ip_check_table = {from_ip_string}
|
||||||
|
|
||||||
-- Add all variants of the exact IP
|
|
||||||
for _, variant in ipairs(get_ipv6_variants(from_ip_string)) do
|
|
||||||
table.insert(ip_check_table, variant)
|
|
||||||
end
|
|
||||||
|
|
||||||
local maxbits = 128
|
local maxbits = 128
|
||||||
local minbits = 32
|
local minbits = 32
|
||||||
@@ -318,18 +155,10 @@ rspamd_config:register_symbol({
|
|||||||
maxbits = 32
|
maxbits = 32
|
||||||
minbits = 8
|
minbits = 8
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Add all CIDR notations with variants
|
|
||||||
for i=maxbits,minbits,-1 do
|
for i=maxbits,minbits,-1 do
|
||||||
local masked_ip = ip:apply_mask(i)
|
local nip = ip:apply_mask(i):to_string() .. "/" .. i
|
||||||
local cidr_base = masked_ip:to_string()
|
table.insert(ip_check_table, nip)
|
||||||
|
|
||||||
for _, variant in ipairs(get_ipv6_variants(cidr_base)) do
|
|
||||||
local cidr = variant .. "/" .. i
|
|
||||||
table.insert(ip_check_table, cidr)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local function keep_spam_cb(err, data)
|
local function keep_spam_cb(err, data)
|
||||||
if err then
|
if err then
|
||||||
rspamd_logger.infox(rspamd_config, "keep_spam query request for ip %s returned invalid or empty data (\"%s\") or error (\"%s\")", ip, data, err)
|
rspamd_logger.infox(rspamd_config, "keep_spam query request for ip %s returned invalid or empty data (\"%s\") or error (\"%s\")", ip, data, err)
|
||||||
@@ -337,15 +166,12 @@ rspamd_config:register_symbol({
|
|||||||
else
|
else
|
||||||
for k,v in pairs(data) do
|
for k,v in pairs(data) do
|
||||||
if (v and v ~= userdata and v == '1') then
|
if (v and v ~= userdata and v == '1') then
|
||||||
rspamd_logger.infox(rspamd_config, "found ip %s (checked as: %s) in keep_spam map, setting pre-result accept", from_ip_string, ip_check_table[k])
|
rspamd_logger.infox(rspamd_config, "found ip in keep_spam map, setting pre-result")
|
||||||
task:set_pre_result('accept', 'ip matched with forward hosts', 'keep_spam')
|
task:set_pre_result('accept', 'ip matched with forward hosts', 'keep_spam')
|
||||||
task:set_flag('no_stat')
|
|
||||||
return
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
table.insert(ip_check_table, 1, 'KEEP_SPAM')
|
table.insert(ip_check_table, 1, 'KEEP_SPAM')
|
||||||
local redis_ret_user = rspamd_redis_make_request(task,
|
local redis_ret_user = rspamd_redis_make_request(task,
|
||||||
redis_params, -- connect params
|
redis_params, -- connect params
|
||||||
@@ -384,7 +210,6 @@ rspamd_config:register_symbol({
|
|||||||
rspamd_config:register_symbol({
|
rspamd_config:register_symbol({
|
||||||
name = 'TAG_MOO',
|
name = 'TAG_MOO',
|
||||||
type = 'postfilter',
|
type = 'postfilter',
|
||||||
flags = 'ignore_passthrough',
|
|
||||||
callback = function(task)
|
callback = function(task)
|
||||||
local util = require("rspamd_util")
|
local util = require("rspamd_util")
|
||||||
local rspamd_logger = require "rspamd_logger"
|
local rspamd_logger = require "rspamd_logger"
|
||||||
@@ -392,7 +217,9 @@ rspamd_config:register_symbol({
|
|||||||
local rspamd_http = require "rspamd_http"
|
local rspamd_http = require "rspamd_http"
|
||||||
local rcpts = task:get_recipients('smtp')
|
local rcpts = task:get_recipients('smtp')
|
||||||
local lua_util = require "lua_util"
|
local lua_util = require "lua_util"
|
||||||
|
|
||||||
local tagged_rcpt = task:get_symbol("TAGGED_RCPT")
|
local tagged_rcpt = task:get_symbol("TAGGED_RCPT")
|
||||||
|
local mailcow_domain = task:get_symbol("RCPT_MAILCOW_DOMAIN")
|
||||||
|
|
||||||
local function remove_moo_tag()
|
local function remove_moo_tag()
|
||||||
local moo_tag_header = task:get_header('X-Moo-Tag', false)
|
local moo_tag_header = task:get_header('X-Moo-Tag', false)
|
||||||
@@ -404,147 +231,101 @@ rspamd_config:register_symbol({
|
|||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Check if we have exactly one recipient
|
if tagged_rcpt and tagged_rcpt[1].options and mailcow_domain then
|
||||||
if not (rcpts and #rcpts == 1) then
|
local tag = tagged_rcpt[1].options[1]
|
||||||
rspamd_logger.infox("TAG_MOO: not exactly one rcpt (%s), removing moo tag", rcpts and #rcpts or 0)
|
rspamd_logger.infox("found tag: %s", tag)
|
||||||
remove_moo_tag()
|
local action = task:get_metric_action('default')
|
||||||
return
|
rspamd_logger.infox("metric action now: %s", action)
|
||||||
end
|
|
||||||
|
|
||||||
local rcpt_addr = rcpts[1]['addr']
|
if action ~= 'no action' and action ~= 'greylist' then
|
||||||
local rcpt_user = rcpts[1]['user']
|
rspamd_logger.infox("skipping tag handler for action: %s", action)
|
||||||
local rcpt_domain = rcpts[1]['domain']
|
remove_moo_tag()
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
-- Check if recipient has a tag (contains '+')
|
local function http_callback(err_message, code, body, headers)
|
||||||
local tag = nil
|
if body ~= nil and body ~= "" then
|
||||||
if tagged_rcpt ~= nil then
|
rspamd_logger.infox(rspamd_config, "expanding rcpt to \"%s\"", body)
|
||||||
tag = tagged_rcpt
|
|
||||||
rspamd_logger.infox("TAG_MOO: found tag in recipient: %s (base: %s, tag: %s)", rcpt_addr, base_user, tag)
|
|
||||||
end
|
|
||||||
|
|
||||||
if not tag then
|
local function tag_callback_subject(err, data)
|
||||||
rspamd_logger.infox("TAG_MOO: no tag found in recipient %s, removing moo tag", rcpt_addr)
|
if err or type(data) ~= 'string' then
|
||||||
remove_moo_tag()
|
rspamd_logger.infox(rspamd_config, "subject tag handler rcpt %s returned invalid or empty data (\"%s\") or error (\"%s\") - trying subfolder tag handler...", body, data, err)
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Optional: Check if domain is a mailcow domain
|
local function tag_callback_subfolder(err, data)
|
||||||
-- When KEEP_SPAM is active, RCPT_MAILCOW_DOMAIN might not be set
|
if err or type(data) ~= 'string' then
|
||||||
-- If the mail is being delivered, we can assume it's valid
|
rspamd_logger.infox(rspamd_config, "subfolder tag handler for rcpt %s returned invalid or empty data (\"%s\") or error (\"%s\")", body, data, err)
|
||||||
local mailcow_domain = task:get_symbol("RCPT_MAILCOW_DOMAIN")
|
remove_moo_tag()
|
||||||
if not mailcow_domain then
|
else
|
||||||
rspamd_logger.infox("TAG_MOO: RCPT_MAILCOW_DOMAIN not set (possibly due to pre-result), proceeding anyway for domain %s", rcpt_domain)
|
rspamd_logger.infox("Add X-Moo-Tag header")
|
||||||
end
|
task:set_milter_reply({
|
||||||
|
add_headers = {['X-Moo-Tag'] = 'YES'}
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
local action = task:get_metric_action('default')
|
local redis_ret_subfolder = rspamd_redis_make_request(task,
|
||||||
rspamd_logger.infox("TAG_MOO: metric action: %s", action)
|
redis_params, -- connect params
|
||||||
|
body, -- hash key
|
||||||
|
false, -- is write
|
||||||
|
tag_callback_subfolder, --callback
|
||||||
|
'HGET', -- command
|
||||||
|
{'RCPT_WANTS_SUBFOLDER_TAG', body} -- arguments
|
||||||
|
)
|
||||||
|
if not redis_ret_subfolder then
|
||||||
|
rspamd_logger.infox(rspamd_config, "cannot make request to load tag handler for rcpt")
|
||||||
|
remove_moo_tag()
|
||||||
|
end
|
||||||
|
|
||||||
-- Check if we have a pre-result (e.g., from KEEP_SPAM or POSTMASTER_HANDLER)
|
else
|
||||||
local allow_processing = false
|
rspamd_logger.infox("user wants subject modified for tagged mail")
|
||||||
|
local sbj = task:get_header('Subject')
|
||||||
|
new_sbj = '=?UTF-8?B?' .. tostring(util.encode_base64('[' .. tag .. '] ' .. sbj)) .. '?='
|
||||||
|
task:set_milter_reply({
|
||||||
|
remove_headers = {
|
||||||
|
['Subject'] = 1,
|
||||||
|
['X-Moo-Tag'] = 0
|
||||||
|
},
|
||||||
|
add_headers = {['Subject'] = new_sbj}
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local redis_ret_subject = rspamd_redis_make_request(task,
|
||||||
|
redis_params, -- connect params
|
||||||
|
body, -- hash key
|
||||||
|
false, -- is write
|
||||||
|
tag_callback_subject, --callback
|
||||||
|
'HGET', -- command
|
||||||
|
{'RCPT_WANTS_SUBJECT_TAG', body} -- arguments
|
||||||
|
)
|
||||||
|
if not redis_ret_subject then
|
||||||
|
rspamd_logger.infox(rspamd_config, "cannot make request to load tag handler for rcpt")
|
||||||
|
remove_moo_tag()
|
||||||
|
end
|
||||||
|
|
||||||
if task.has_pre_result then
|
|
||||||
local has_pre, pre_action = task:has_pre_result()
|
|
||||||
if has_pre then
|
|
||||||
rspamd_logger.infox("TAG_MOO: pre-result detected: %s", tostring(pre_action))
|
|
||||||
if pre_action == 'accept' then
|
|
||||||
allow_processing = true
|
|
||||||
rspamd_logger.infox("TAG_MOO: pre-result is accept, will process")
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
-- Allow processing for mild actions or when we have pre-result accept
|
if rcpts and #rcpts == 1 then
|
||||||
if not allow_processing and action ~= 'no action' and action ~= 'greylist' then
|
for _,rcpt in ipairs(rcpts) do
|
||||||
rspamd_logger.infox("TAG_MOO: skipping tag handler for action: %s", action)
|
local rcpt_split = rspamd_str_split(rcpt['addr'], '@')
|
||||||
remove_moo_tag()
|
if #rcpt_split == 2 then
|
||||||
return true
|
if rcpt_split[1] == 'postmaster' then
|
||||||
end
|
rspamd_logger.infox(rspamd_config, "not expanding postmaster alias")
|
||||||
|
|
||||||
rspamd_logger.infox("TAG_MOO: processing allowed")
|
|
||||||
|
|
||||||
local function http_callback(err_message, code, body, headers)
|
|
||||||
if body ~= nil and body ~= "" then
|
|
||||||
rspamd_logger.infox(rspamd_config, "TAG_MOO: expanding rcpt to \"%s\"", body)
|
|
||||||
|
|
||||||
local function tag_callback_subject(err, data)
|
|
||||||
if err or type(data) ~= 'string' or data == '' then
|
|
||||||
rspamd_logger.infox(rspamd_config, "TAG_MOO: subject tag handler rcpt %s returned invalid or empty data (\"%s\") or error (\"%s\") - trying subfolder tag handler...", body, data, err)
|
|
||||||
|
|
||||||
local function tag_callback_subfolder(err, data)
|
|
||||||
if err or type(data) ~= 'string' or data == '' then
|
|
||||||
rspamd_logger.infox(rspamd_config, "TAG_MOO: subfolder tag handler for rcpt %s returned invalid or empty data (\"%s\") or error (\"%s\")", body, data, err)
|
|
||||||
remove_moo_tag()
|
|
||||||
else
|
|
||||||
rspamd_logger.infox("TAG_MOO: User wants subfolder tag, adding X-Moo-Tag header")
|
|
||||||
task:set_milter_reply({
|
|
||||||
add_headers = {['X-Moo-Tag'] = 'YES'}
|
|
||||||
})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local redis_ret_subfolder = rspamd_redis_make_request(task,
|
|
||||||
redis_params, -- connect params
|
|
||||||
body, -- hash key
|
|
||||||
false, -- is write
|
|
||||||
tag_callback_subfolder, --callback
|
|
||||||
'HGET', -- command
|
|
||||||
{'RCPT_WANTS_SUBFOLDER_TAG', body} -- arguments
|
|
||||||
)
|
|
||||||
if not redis_ret_subfolder then
|
|
||||||
rspamd_logger.infox(rspamd_config, "TAG_MOO: cannot make request to load tag handler for rcpt")
|
|
||||||
remove_moo_tag()
|
remove_moo_tag()
|
||||||
|
else
|
||||||
|
rspamd_http.request({
|
||||||
|
task=task,
|
||||||
|
url='http://nginx:8081/aliasexp.php',
|
||||||
|
body='',
|
||||||
|
callback=http_callback,
|
||||||
|
headers={Rcpt=rcpt['addr']},
|
||||||
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
else
|
|
||||||
rspamd_logger.infox("TAG_MOO: user wants subject modified for tagged mail")
|
|
||||||
local sbj = task:get_header('Subject') or ''
|
|
||||||
local tag_value = tag[1] and tag[1].options and tag[1].options[1] or ''
|
|
||||||
new_sbj = '=?UTF-8?B?' .. tostring(util.encode_base64('[' .. tag_value .. '] ' .. sbj)) .. '?='
|
|
||||||
task:set_milter_reply({
|
|
||||||
remove_headers = {
|
|
||||||
['Subject'] = 1,
|
|
||||||
['X-Moo-Tag'] = 0
|
|
||||||
},
|
|
||||||
add_headers = {['Subject'] = new_sbj}
|
|
||||||
})
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local redis_ret_subject = rspamd_redis_make_request(task,
|
|
||||||
redis_params, -- connect params
|
|
||||||
body, -- hash key
|
|
||||||
false, -- is write
|
|
||||||
tag_callback_subject, --callback
|
|
||||||
'HGET', -- command
|
|
||||||
{'RCPT_WANTS_SUBJECT_TAG', body} -- arguments
|
|
||||||
)
|
|
||||||
if not redis_ret_subject then
|
|
||||||
rspamd_logger.infox(rspamd_config, "TAG_MOO: cannot make request to load tag handler for rcpt")
|
|
||||||
remove_moo_tag()
|
|
||||||
end
|
|
||||||
else
|
|
||||||
rspamd_logger.infox("TAG_MOO: alias expansion returned empty body")
|
|
||||||
remove_moo_tag()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local rcpt_split = rspamd_str_split(rcpt_addr, '@')
|
|
||||||
if #rcpt_split == 2 then
|
|
||||||
if rcpt_split[1]:match('^postmaster') then
|
|
||||||
rspamd_logger.infox(rspamd_config, "TAG_MOO: not expanding postmaster alias")
|
|
||||||
remove_moo_tag()
|
|
||||||
else
|
|
||||||
rspamd_logger.infox("TAG_MOO: requesting alias expansion for %s", rcpt_addr)
|
|
||||||
rspamd_http.request({
|
|
||||||
task=task,
|
|
||||||
url='http://nginx:8081/aliasexp.php',
|
|
||||||
body='',
|
|
||||||
callback=http_callback,
|
|
||||||
headers={Rcpt=rcpt_addr},
|
|
||||||
})
|
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
rspamd_logger.infox("TAG_MOO: invalid rcpt format")
|
|
||||||
remove_moo_tag()
|
remove_moo_tag()
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
@@ -554,7 +335,6 @@ rspamd_config:register_symbol({
|
|||||||
rspamd_config:register_symbol({
|
rspamd_config:register_symbol({
|
||||||
name = 'BCC',
|
name = 'BCC',
|
||||||
type = 'postfilter',
|
type = 'postfilter',
|
||||||
flags = 'ignore_passthrough',
|
|
||||||
callback = function(task)
|
callback = function(task)
|
||||||
local util = require("rspamd_util")
|
local util = require("rspamd_util")
|
||||||
local rspamd_http = require "rspamd_http"
|
local rspamd_http = require "rspamd_http"
|
||||||
@@ -583,13 +363,11 @@ rspamd_config:register_symbol({
|
|||||||
local email_content = tostring(task:get_content())
|
local email_content = tostring(task:get_content())
|
||||||
email_content = string.gsub(email_content, "\r\n%.", "\r\n..")
|
email_content = string.gsub(email_content, "\r\n%.", "\r\n..")
|
||||||
-- send mail
|
-- send mail
|
||||||
local from_smtp = task:get_from('smtp')
|
|
||||||
local from_addr = (from_smtp and from_smtp[1] and from_smtp[1].addr) or 'mailer-daemon@localhost'
|
|
||||||
lua_smtp.sendmail({
|
lua_smtp.sendmail({
|
||||||
task = task,
|
task = task,
|
||||||
host = os.getenv("IPV4_NETWORK") .. '.253',
|
host = os.getenv("IPV4_NETWORK") .. '.253',
|
||||||
port = 591,
|
port = 591,
|
||||||
from = from_addr,
|
from = task:get_from(stp)[1].addr,
|
||||||
recipients = bcc_dest,
|
recipients = bcc_dest,
|
||||||
helo = 'bcc',
|
helo = 'bcc',
|
||||||
timeout = 20,
|
timeout = 20,
|
||||||
@@ -619,41 +397,27 @@ rspamd_config:register_symbol({
|
|||||||
end
|
end
|
||||||
|
|
||||||
local action = task:get_metric_action('default')
|
local action = task:get_metric_action('default')
|
||||||
rspamd_logger.infox("BCC: metric action: %s", action)
|
rspamd_logger.infox("metric action now: %s", action)
|
||||||
|
|
||||||
-- Check for pre-result accept (e.g., from KEEP_SPAM)
|
|
||||||
local allow_bcc = false
|
|
||||||
if task.has_pre_result then
|
|
||||||
local has_pre, pre_action = task:has_pre_result()
|
|
||||||
if has_pre and pre_action == 'accept' then
|
|
||||||
allow_bcc = true
|
|
||||||
rspamd_logger.infox("BCC: pre-result accept detected, will send BCC")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Allow BCC for mild actions or when we have pre-result accept
|
|
||||||
if not allow_bcc and action ~= 'no action' and action ~= 'add header' and action ~= 'rewrite subject' then
|
|
||||||
rspamd_logger.infox("BCC: skipping for action: %s", action)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
local function rcpt_callback(err_message, code, body, headers)
|
local function rcpt_callback(err_message, code, body, headers)
|
||||||
if err_message == nil and code == 201 and body ~= nil then
|
if err_message == nil and code == 201 and body ~= nil then
|
||||||
rspamd_logger.infox("BCC: sending BCC to %s for rcpt match", body)
|
if action == 'no action' or action == 'add header' or action == 'rewrite subject' then
|
||||||
send_mail(task, body)
|
send_mail(task, body)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function from_callback(err_message, code, body, headers)
|
local function from_callback(err_message, code, body, headers)
|
||||||
if err_message == nil and code == 201 and body ~= nil then
|
if err_message == nil and code == 201 and body ~= nil then
|
||||||
rspamd_logger.infox("BCC: sending BCC to %s for from match", body)
|
if action == 'no action' or action == 'add header' or action == 'rewrite subject' then
|
||||||
send_mail(task, body)
|
send_mail(task, body)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if rcpt_table then
|
if rcpt_table then
|
||||||
for _,e in ipairs(rcpt_table) do
|
for _,e in ipairs(rcpt_table) do
|
||||||
rspamd_logger.infox(rspamd_config, "BCC: checking bcc for rcpt address %s", e)
|
rspamd_logger.infox(rspamd_config, "checking bcc for rcpt address %s", e)
|
||||||
rspamd_http.request({
|
rspamd_http.request({
|
||||||
task=task,
|
task=task,
|
||||||
url='http://nginx:8081/bcc.php',
|
url='http://nginx:8081/bcc.php',
|
||||||
@@ -666,7 +430,7 @@ rspamd_config:register_symbol({
|
|||||||
|
|
||||||
if from_table then
|
if from_table then
|
||||||
for _,e in ipairs(from_table) do
|
for _,e in ipairs(from_table) do
|
||||||
rspamd_logger.infox(rspamd_config, "BCC: checking bcc for from address %s", e)
|
rspamd_logger.infox(rspamd_config, "checking bcc for from address %s", e)
|
||||||
rspamd_http.request({
|
rspamd_http.request({
|
||||||
task=task,
|
task=task,
|
||||||
url='http://nginx:8081/bcc.php',
|
url='http://nginx:8081/bcc.php',
|
||||||
@@ -677,7 +441,7 @@ rspamd_config:register_symbol({
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Don't return true to avoid symbol being logged
|
return true
|
||||||
end,
|
end,
|
||||||
priority = 20
|
priority = 20
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -21,10 +21,10 @@ catch (PDOException $e) {
|
|||||||
http_response_code(501);
|
http_response_code(501);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
// Init Redis
|
// Init Valkey
|
||||||
$redis = new Redis();
|
$valkey = new Redis();
|
||||||
$redis->connect('redis-mailcow', 6379);
|
$valkey->connect('valkey-mailcow', 6379);
|
||||||
$redis->auth(getenv("REDISPASS"));
|
$valkey->auth(getenv("VALKEYPASS"));
|
||||||
|
|
||||||
// Functions
|
// Functions
|
||||||
function parse_email($email) {
|
function parse_email($email) {
|
||||||
@@ -74,16 +74,16 @@ if ($fuzzy == 'unknown') {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$max_size = (int)$redis->Get('Q_MAX_SIZE');
|
$max_size = (int)$valkey->Get('Q_MAX_SIZE');
|
||||||
if (($max_size * 1048576) < $raw_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);
|
error_log(sprintf("QUARANTINE: Message too large: %d b exceeds %d b", $raw_size, ($max_size * 1048576)) . PHP_EOL);
|
||||||
http_response_code(505);
|
http_response_code(505);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
if ($exclude_domains = $redis->Get('Q_EXCLUDE_DOMAINS')) {
|
if ($exclude_domains = $valkey->Get('Q_EXCLUDE_DOMAINS')) {
|
||||||
$exclude_domains = json_decode($exclude_domains, true);
|
$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) {
|
catch (RedisException $e) {
|
||||||
error_log("QUARANTINE: " . $e . PHP_EOL);
|
error_log("QUARANTINE: " . $e . PHP_EOL);
|
||||||
@@ -103,7 +103,7 @@ foreach (json_decode($rcpts, true) as $rcpt) {
|
|||||||
|
|
||||||
// Skip if not a mailcow handled domain
|
// Skip if not a mailcow handled domain
|
||||||
try {
|
try {
|
||||||
if (!$redis->hGet('DOMAIN_MAP', $parsed_rcpt['domain'])) {
|
if (!$valkey->hGet('DOMAIN_MAP', $parsed_rcpt['domain'])) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -171,7 +171,7 @@ foreach (json_decode($rcpts, true) as $rcpt) {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$parsed_goto = parse_email($goto);
|
$parsed_goto = parse_email($goto);
|
||||||
if (!$redis->hGet('DOMAIN_MAP', $parsed_goto['domain'])) {
|
if (!$valkey->hGet('DOMAIN_MAP', $parsed_goto['domain'])) {
|
||||||
error_log("RCPT RESOVLER:" . $goto . " is not a mailcow handled mailbox or alias address" . PHP_EOL);
|
error_log("RCPT RESOVLER:" . $goto . " is not a mailcow handled mailbox or alias address" . PHP_EOL);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|||||||
@@ -5,16 +5,16 @@ header('Content-Type: text/plain');
|
|||||||
require_once "vars.inc.php";
|
require_once "vars.inc.php";
|
||||||
// Do not show errors, we log to using error_log
|
// Do not show errors, we log to using error_log
|
||||||
ini_set('error_reporting', 0);
|
ini_set('error_reporting', 0);
|
||||||
// Init Redis
|
// Init Valkey
|
||||||
$redis = new Redis();
|
$valkey = new Redis();
|
||||||
try {
|
try {
|
||||||
if (!empty(getenv('REDIS_SLAVEOF_IP'))) {
|
if (!empty(getenv('VALKEY_SLAVEOF_IP'))) {
|
||||||
$redis->connect(getenv('REDIS_SLAVEOF_IP'), getenv('REDIS_SLAVEOF_PORT'));
|
$valkey->connect(getenv('VALKEY_SLAVEOF_IP'), getenv('VALKEY_SLAVEOF_PORT'));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$redis->connect('redis-mailcow', 6379);
|
$valkey->connect('valkey-mailcow', 6379);
|
||||||
}
|
}
|
||||||
$redis->auth(getenv("REDISPASS"));
|
$valkey->auth(getenv("VALKEYPASS"));
|
||||||
}
|
}
|
||||||
catch (Exception $e) {
|
catch (Exception $e) {
|
||||||
exit;
|
exit;
|
||||||
@@ -44,6 +44,6 @@ $data['message_id'] = $raw_data_decoded['message_id'];
|
|||||||
$data['header_subject'] = implode(' ', $raw_data_decoded['header_subject']);
|
$data['header_subject'] = implode(' ', $raw_data_decoded['header_subject']);
|
||||||
$data['header_from'] = implode(', ', $raw_data_decoded['header_from']);
|
$data['header_from'] = implode(', ', $raw_data_decoded['header_from']);
|
||||||
|
|
||||||
$redis->lpush('RL_LOG', json_encode($data));
|
$valkey->lpush('RL_LOG', json_encode($data));
|
||||||
exit;
|
exit;
|
||||||
|
|
||||||
|
|||||||
@@ -21,10 +21,10 @@ catch (PDOException $e) {
|
|||||||
http_response_code(501);
|
http_response_code(501);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
// Init Redis
|
// Init Valkey
|
||||||
$redis = new Redis();
|
$valkey = new Redis();
|
||||||
$redis->connect('redis-mailcow', 6379);
|
$valkey->connect('valkey-mailcow', 6379);
|
||||||
$redis->auth(getenv("REDISPASS"));
|
$valkey->auth(getenv("VALKEYPASS"));
|
||||||
|
|
||||||
// Functions
|
// Functions
|
||||||
function parse_email($email) {
|
function parse_email($email) {
|
||||||
@@ -94,7 +94,7 @@ foreach (json_decode($rcpts, true) as $rcpt) {
|
|||||||
|
|
||||||
// Skip if not a mailcow handled domain
|
// Skip if not a mailcow handled domain
|
||||||
try {
|
try {
|
||||||
if (!$redis->hGet('DOMAIN_MAP', $parsed_rcpt['domain'])) {
|
if (!$valkey->hGet('DOMAIN_MAP', $parsed_rcpt['domain'])) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -156,7 +156,7 @@ foreach (json_decode($rcpts, true) as $rcpt) {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$parsed_goto = parse_email($goto);
|
$parsed_goto = parse_email($goto);
|
||||||
if (!$redis->hGet('DOMAIN_MAP', $parsed_goto['domain'])) {
|
if (!$valkey->hGet('DOMAIN_MAP', $parsed_goto['domain'])) {
|
||||||
error_log("RCPT RESOVLER:" . $goto . " is not a mailcow handled mailbox or alias address" . PHP_EOL);
|
error_log("RCPT RESOVLER:" . $goto . " is not a mailcow handled mailbox or alias address" . PHP_EOL);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|||||||
@@ -86,12 +86,6 @@
|
|||||||
SOGoMaximumFailedLoginInterval = 900;
|
SOGoMaximumFailedLoginInterval = 900;
|
||||||
SOGoFailedLoginBlockInterval = 900;
|
SOGoFailedLoginBlockInterval = 900;
|
||||||
|
|
||||||
// Enable SOGo URL Description for GDPR compliance, this may cause some issues with calendars and contacts. Also uncomment the encryption key below to use it.
|
|
||||||
//SOGoURLEncryptionEnabled = NO;
|
|
||||||
|
|
||||||
// Set a 16 character encryption key for SOGo URL Description, change this to your own value
|
|
||||||
//SOGoURLPathEncryptionKey = "SOGoSuperSecret0";
|
|
||||||
|
|
||||||
GCSChannelCollectionTimer = 60;
|
GCSChannelCollectionTimer = 60;
|
||||||
GCSChannelExpireAge = 60;
|
GCSChannelExpireAge = 60;
|
||||||
|
|
||||||
|
|||||||
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
|
<?php
|
||||||
$redis = new Redis();
|
$valkey = new Redis();
|
||||||
try {
|
try {
|
||||||
if (!empty(getenv('REDIS_SLAVEOF_IP'))) {
|
if (!empty(getenv('VALKEY_SLAVEOF_IP'))) {
|
||||||
$redis->connect(getenv('REDIS_SLAVEOF_IP'), getenv('REDIS_SLAVEOF_PORT'));
|
$valkey->connect(getenv('VALKEY_SLAVEOF_IP'), getenv('VALKEY_SLAVEOF_PORT'));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$redis->connect('redis-mailcow', 6379);
|
$valkey->connect('valkey-mailcow', 6379);
|
||||||
}
|
}
|
||||||
$redis->auth(getenv("REDISPASS"));
|
$valkey->auth(getenv("VALKEYPASS"));
|
||||||
}
|
}
|
||||||
catch (Exception $e) {
|
catch (Exception $e) {
|
||||||
exit;
|
exit;
|
||||||
@@ -15,4 +15,4 @@ catch (Exception $e) {
|
|||||||
header('Content-Type: application/json');
|
header('Content-Type: application/json');
|
||||||
echo '{"error":"Unauthorized"}';
|
echo '{"error":"Unauthorized"}';
|
||||||
error_log("Rspamd UI: Invalid password by " . $_SERVER['REMOTE_ADDR']);
|
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']);
|
||||||
|
|||||||
@@ -2,7 +2,18 @@
|
|||||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
|
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
|
||||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/triggers.admin.inc.php';
|
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/triggers.admin.inc.php';
|
||||||
|
|
||||||
protect_route(['admin']);
|
if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'domainadmin') {
|
||||||
|
header('Location: /domainadmin/mailbox');
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'user') {
|
||||||
|
header('Location: /user');
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
elseif (!isset($_SESSION['mailcow_cc_role']) || $_SESSION['mailcow_cc_role'] != "admin") {
|
||||||
|
header('Location: /admin');
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/header.inc.php';
|
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/header.inc.php';
|
||||||
$_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
|
$_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
|
||||||
@@ -10,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;
|
$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);
|
$_SESSION['gal'] = json_decode($license_cache, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,11 +3,8 @@ require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
|
|||||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/triggers.admin.inc.php';
|
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/triggers.admin.inc.php';
|
||||||
|
|
||||||
if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'admin') {
|
if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'admin') {
|
||||||
// Only redirect to dashboard if NO pending actions
|
header('Location: /admin/dashboard');
|
||||||
if (empty($_SESSION['pending_tfa_setup']) && empty($_SESSION['pending_pw_update'])) {
|
exit();
|
||||||
header('Location: /admin/dashboard');
|
|
||||||
exit();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'domainadmin') {
|
elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'domainadmin') {
|
||||||
header('Location: /domainadmin/mailbox');
|
header('Location: /domainadmin/mailbox');
|
||||||
|
|||||||
@@ -2,7 +2,18 @@
|
|||||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
|
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
|
||||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/triggers.admin.inc.php';
|
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/triggers.admin.inc.php';
|
||||||
|
|
||||||
protect_route(['admin']);
|
if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'domainadmin') {
|
||||||
|
header('Location: /domainadmin/mailbox');
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'user') {
|
||||||
|
header('Location: /user');
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
elseif (!isset($_SESSION['mailcow_cc_role']) || $_SESSION['mailcow_cc_role'] != "admin") {
|
||||||
|
header('Location: /admin');
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/header.inc.php';
|
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/header.inc.php';
|
||||||
$_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
|
$_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
|
||||||
|
|||||||
@@ -2,7 +2,19 @@
|
|||||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
|
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
|
||||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/triggers.admin.inc.php';
|
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/triggers.admin.inc.php';
|
||||||
|
|
||||||
protect_route(['admin']);
|
if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'domainadmin') {
|
||||||
|
header('Location: /domainadmin/mailbox');
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'user') {
|
||||||
|
header('Location: /user');
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
elseif (!isset($_SESSION['mailcow_cc_role']) || $_SESSION['mailcow_cc_role'] != "admin") {
|
||||||
|
header('Location: /admin');
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/header.inc.php';
|
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/header.inc.php';
|
||||||
$js_minifier->add('/web/js/site/queue.js');
|
$js_minifier->add('/web/js/site/queue.js');
|
||||||
|
|||||||
@@ -2,7 +2,18 @@
|
|||||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
|
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
|
||||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/triggers.admin.inc.php';
|
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/triggers.admin.inc.php';
|
||||||
|
|
||||||
protect_route(['admin']);
|
if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'domainadmin') {
|
||||||
|
header('Location: /domainadmin/mailbox');
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'user') {
|
||||||
|
header('Location: /user');
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
elseif (!isset($_SESSION['mailcow_cc_role']) || $_SESSION['mailcow_cc_role'] != "admin") {
|
||||||
|
header('Location: /admin');
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/header.inc.php';
|
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/header.inc.php';
|
||||||
$_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
|
$_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
|
||||||
|
|||||||
@@ -2454,90 +2454,6 @@ paths:
|
|||||||
type: object
|
type: object
|
||||||
type: object
|
type: object
|
||||||
summary: Delete mails in Quarantine
|
summary: Delete mails in Quarantine
|
||||||
/api/v1/edit/qitem:
|
|
||||||
post:
|
|
||||||
responses:
|
|
||||||
"401":
|
|
||||||
$ref: "#/components/responses/Unauthorized"
|
|
||||||
"200":
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
examples:
|
|
||||||
release:
|
|
||||||
value:
|
|
||||||
- log:
|
|
||||||
- quarantine
|
|
||||||
- edit
|
|
||||||
- id:
|
|
||||||
- "33"
|
|
||||||
action: release
|
|
||||||
msg:
|
|
||||||
- item_released
|
|
||||||
- "33"
|
|
||||||
type: success
|
|
||||||
learnham:
|
|
||||||
value:
|
|
||||||
- log:
|
|
||||||
- quarantine
|
|
||||||
- edit
|
|
||||||
- id:
|
|
||||||
- "34"
|
|
||||||
action: learnham
|
|
||||||
msg:
|
|
||||||
- item_learned
|
|
||||||
- "34"
|
|
||||||
type: success
|
|
||||||
schema:
|
|
||||||
properties:
|
|
||||||
log:
|
|
||||||
description: contains request object
|
|
||||||
items: {}
|
|
||||||
type: array
|
|
||||||
msg:
|
|
||||||
items: {}
|
|
||||||
type: array
|
|
||||||
type:
|
|
||||||
enum:
|
|
||||||
- success
|
|
||||||
- danger
|
|
||||||
- error
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
description: OK
|
|
||||||
headers: {}
|
|
||||||
tags:
|
|
||||||
- Quarantine
|
|
||||||
description: >-
|
|
||||||
Using this endpoint you can perform actions on quarantine items. It is possible to release
|
|
||||||
emails from quarantine into to the inbox, or learn them as ham to improve Rspamd filtering.
|
|
||||||
You must provide the quarantine item IDs. You can get the IDs using the GET method.
|
|
||||||
operationId: Edit mails in Quarantine
|
|
||||||
requestBody:
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
example:
|
|
||||||
items:
|
|
||||||
- "33"
|
|
||||||
- "34"
|
|
||||||
attr:
|
|
||||||
action: release
|
|
||||||
properties:
|
|
||||||
items:
|
|
||||||
description: contains list of quarantine item IDs to release or learn as ham
|
|
||||||
type: object
|
|
||||||
attr:
|
|
||||||
description: attributes for the action
|
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
action:
|
|
||||||
type: string
|
|
||||||
enum:
|
|
||||||
- release
|
|
||||||
- learnham
|
|
||||||
description: "release - return email to inbox; learnham - learn as ham to improve filtering"
|
|
||||||
type: object
|
|
||||||
summary: Edit mails in Quarantine
|
|
||||||
/api/v1/delete/recipient_map:
|
/api/v1/delete/recipient_map:
|
||||||
post:
|
post:
|
||||||
responses:
|
responses:
|
||||||
@@ -5496,9 +5412,9 @@ paths:
|
|||||||
started_at: "2019-12-22T21:00:07.186717617Z"
|
started_at: "2019-12-22T21:00:07.186717617Z"
|
||||||
state: running
|
state: running
|
||||||
type: info
|
type: info
|
||||||
redis-mailcow:
|
valkey-mailcow:
|
||||||
container: redis-mailcow
|
container: valkey-mailcow
|
||||||
image: "redis:5-alpine"
|
image: "valkey:7.2.8-alpine"
|
||||||
started_at: "2019-12-22T20:59:56.827166834Z"
|
started_at: "2019-12-22T20:59:56.827166834Z"
|
||||||
state: running
|
state: running
|
||||||
type: info
|
type: info
|
||||||
|
|||||||
@@ -29,8 +29,8 @@ header('Content-Type: application/xml');
|
|||||||
<clientConfig version="1.1">
|
<clientConfig version="1.1">
|
||||||
<emailProvider id="<?=$mailcow_hostname; ?>">
|
<emailProvider id="<?=$mailcow_hostname; ?>">
|
||||||
<domain>%EMAILDOMAIN%</domain>
|
<domain>%EMAILDOMAIN%</domain>
|
||||||
<displayName><?=$autodiscover_config['displayName']; ?></displayName>
|
<displayName>A mailcow mail server</displayName>
|
||||||
<displayShortName><?=$autodiscover_config['displayShortName']; ?></displayShortName>
|
<displayShortName>mail server</displayShortName>
|
||||||
|
|
||||||
<incomingServer type="imap">
|
<incomingServer type="imap">
|
||||||
<hostname><?=$autodiscover_config['imap']['server']; ?></hostname>
|
<hostname><?=$autodiscover_config['imap']['server']; ?></hostname>
|
||||||
|
|||||||
@@ -7,21 +7,19 @@ if(file_exists('inc/vars.local.inc.php')) {
|
|||||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.inc.php';
|
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.inc.php';
|
||||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.auth.inc.php';
|
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.auth.inc.php';
|
||||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/sessions.inc.php';
|
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/sessions.inc.php';
|
||||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.mailbox.inc.php';
|
|
||||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.ratelimit.inc.php';
|
|
||||||
$default_autodiscover_config = $autodiscover_config;
|
$default_autodiscover_config = $autodiscover_config;
|
||||||
$autodiscover_config = array_merge($default_autodiscover_config, $autodiscover_config);
|
$autodiscover_config = array_merge($default_autodiscover_config, $autodiscover_config);
|
||||||
|
|
||||||
// Redis
|
// Valkey
|
||||||
$redis = new Redis();
|
$valkey = new Redis();
|
||||||
try {
|
try {
|
||||||
if (!empty(getenv('REDIS_SLAVEOF_IP'))) {
|
if (!empty(getenv('VALKEY_SLAVEOF_IP'))) {
|
||||||
$redis->connect(getenv('REDIS_SLAVEOF_IP'), getenv('REDIS_SLAVEOF_PORT'));
|
$valkey->connect(getenv('VALKEY_SLAVEOF_IP'), getenv('VALKEY_SLAVEOF_PORT'));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$redis->connect('redis-mailcow', 6379);
|
$valkey->connect('valkey-mailcow', 6379);
|
||||||
}
|
}
|
||||||
$redis->auth(getenv("REDISPASS"));
|
$valkey->auth(getenv("VALKEYPASS"));
|
||||||
}
|
}
|
||||||
catch (Exception $e) {
|
catch (Exception $e) {
|
||||||
exit;
|
exit;
|
||||||
@@ -60,176 +58,110 @@ $pdo = new PDO($dsn, $database_user, $database_pass, $opt);
|
|||||||
$iam_provider = identity_provider('init');
|
$iam_provider = identity_provider('init');
|
||||||
$iam_settings = identity_provider('get');
|
$iam_settings = identity_provider('get');
|
||||||
|
|
||||||
// Passwordless autodiscover - no authentication required
|
$login_user = strtolower(trim($_SERVER['PHP_AUTH_USER']));
|
||||||
// Email will be extracted from the request body
|
$login_pass = trim(htmlspecialchars_decode($_SERVER['PHP_AUTH_PW']));
|
||||||
$login_user = null;
|
|
||||||
$login_role = null;
|
|
||||||
|
|
||||||
header("Content-Type: application/xml");
|
if (empty($_SERVER['PHP_AUTH_USER']) || empty($_SERVER['PHP_AUTH_PW'])) {
|
||||||
echo '<?xml version="1.0" encoding="utf-8" ?>' . PHP_EOL;
|
|
||||||
?>
|
|
||||||
<Autodiscover xmlns="http://schemas.microsoft.com/exchange/autodiscover/responseschema/2006">
|
|
||||||
<?php
|
|
||||||
if(!$data) {
|
|
||||||
try {
|
|
||||||
$json = json_encode(
|
|
||||||
array(
|
|
||||||
"time" => time(),
|
|
||||||
"ua" => $_SERVER['HTTP_USER_AGENT'],
|
|
||||||
"user" => "none",
|
|
||||||
"ip" => $_SERVER['REMOTE_ADDR'],
|
|
||||||
"service" => "Error: invalid or missing request data"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
$redis->lPush('AUTODISCOVER_LOG', $json);
|
|
||||||
$redis->lTrim('AUTODISCOVER_LOG', 0, 100);
|
|
||||||
$redis->publish("F2B_CHANNEL", "Autodiscover: Invalid request by " . $_SERVER['REMOTE_ADDR']);
|
|
||||||
error_log("Autodiscover: Invalid request by " . $_SERVER['REMOTE_ADDR']);
|
|
||||||
}
|
|
||||||
catch (RedisException $e) {
|
|
||||||
$_SESSION['return'][] = array(
|
|
||||||
'type' => 'danger',
|
|
||||||
'msg' => 'Redis: '.$e
|
|
||||||
);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
list($usec, $sec) = explode(' ', microtime());
|
|
||||||
?>
|
|
||||||
<Response>
|
|
||||||
<Error Time="<?=date('H:i:s', $sec) . substr($usec, 0, strlen($usec) - 2);?>" Id="<?=rand(1000000000, 9999999999);?>">
|
|
||||||
<ErrorCode>600</ErrorCode>
|
|
||||||
<Message>Invalid Request</Message>
|
|
||||||
<DebugData />
|
|
||||||
</Error>
|
|
||||||
</Response>
|
|
||||||
</Autodiscover>
|
|
||||||
<?php
|
|
||||||
exit(0);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
$discover = new SimpleXMLElement($data);
|
|
||||||
$email = $discover->Request->EMailAddress;
|
|
||||||
} catch (Exception $e) {
|
|
||||||
// If parsing fails, return error
|
|
||||||
try {
|
|
||||||
$json = json_encode(
|
|
||||||
array(
|
|
||||||
"time" => time(),
|
|
||||||
"ua" => $_SERVER['HTTP_USER_AGENT'],
|
|
||||||
"user" => "none",
|
|
||||||
"ip" => $_SERVER['REMOTE_ADDR'],
|
|
||||||
"service" => "Error: could not parse email from request"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
$redis->lPush('AUTODISCOVER_LOG', $json);
|
|
||||||
$redis->lTrim('AUTODISCOVER_LOG', 0, 100);
|
|
||||||
$redis->publish("F2B_CHANNEL", "Autodiscover: Malformed XML by " . $_SERVER['REMOTE_ADDR']);
|
|
||||||
error_log("Autodiscover: Malformed XML by " . $_SERVER['REMOTE_ADDR']);
|
|
||||||
}
|
|
||||||
catch (RedisException $e) {
|
|
||||||
// Silently fail
|
|
||||||
}
|
|
||||||
list($usec, $sec) = explode(' ', microtime());
|
|
||||||
?>
|
|
||||||
<Response>
|
|
||||||
<Error Time="<?=date('H:i:s', $sec) . substr($usec, 0, strlen($usec) - 2);?>" Id="<?=rand(1000000000, 9999999999);?>">
|
|
||||||
<ErrorCode>600</ErrorCode>
|
|
||||||
<Message>Invalid Request</Message>
|
|
||||||
<DebugData />
|
|
||||||
</Error>
|
|
||||||
</Response>
|
|
||||||
</Autodiscover>
|
|
||||||
<?php
|
|
||||||
exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
$username = trim((string)$email);
|
|
||||||
try {
|
|
||||||
$stmt = $pdo->prepare("SELECT `mailbox`.`name`, `mailbox`.`active` FROM `mailbox`
|
|
||||||
INNER JOIN `domain` ON `mailbox`.`domain` = `domain`.`domain`
|
|
||||||
WHERE `mailbox`.`username` = :username
|
|
||||||
AND `mailbox`.`active` = '1'
|
|
||||||
AND `domain`.`active` = '1'");
|
|
||||||
$stmt->execute(array(':username' => $username));
|
|
||||||
$MailboxData = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
||||||
}
|
|
||||||
catch(PDOException $e) {
|
|
||||||
// Database error - return error response with complete XML
|
|
||||||
list($usec, $sec) = explode(' ', microtime());
|
|
||||||
?>
|
|
||||||
<Response>
|
|
||||||
<Error Time="<?=date('H:i:s', $sec) . substr($usec, 0, strlen($usec) - 2);?>" Id="<?=rand(1000000000, 9999999999);?>">
|
|
||||||
<ErrorCode>500</ErrorCode>
|
|
||||||
<Message>Database Error</Message>
|
|
||||||
<DebugData />
|
|
||||||
</Error>
|
|
||||||
</Response>
|
|
||||||
</Autodiscover>
|
|
||||||
<?php
|
|
||||||
exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mailbox not found or not active - return generic error to prevent user enumeration
|
|
||||||
if (empty($MailboxData)) {
|
|
||||||
try {
|
|
||||||
$json = json_encode(
|
|
||||||
array(
|
|
||||||
"time" => time(),
|
|
||||||
"ua" => $_SERVER['HTTP_USER_AGENT'],
|
|
||||||
"user" => $email,
|
|
||||||
"ip" => $_SERVER['REMOTE_ADDR'],
|
|
||||||
"service" => "Error: mailbox not found or inactive"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
$redis->lPush('AUTODISCOVER_LOG', $json);
|
|
||||||
$redis->lTrim('AUTODISCOVER_LOG', 0, 100);
|
|
||||||
$redis->publish("F2B_CHANNEL", "Autodiscover: Invalid mailbox attempt by " . $_SERVER['REMOTE_ADDR']);
|
|
||||||
error_log("Autodiscover: Invalid mailbox attempt by " . $_SERVER['REMOTE_ADDR']);
|
|
||||||
}
|
|
||||||
catch (RedisException $e) {
|
|
||||||
// Silently fail
|
|
||||||
}
|
|
||||||
list($usec, $sec) = explode(' ', microtime());
|
|
||||||
?>
|
|
||||||
<Response>
|
|
||||||
<Error Time="<?=date('H:i:s', $sec) . substr($usec, 0, strlen($usec) - 2);?>" Id="<?=rand(1000000000, 9999999999);?>">
|
|
||||||
<ErrorCode>600</ErrorCode>
|
|
||||||
<Message>Invalid Request</Message>
|
|
||||||
<DebugData />
|
|
||||||
</Error>
|
|
||||||
</Response>
|
|
||||||
</Autodiscover>
|
|
||||||
<?php
|
|
||||||
exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!empty($MailboxData['name'])) {
|
|
||||||
$displayname = $MailboxData['name'];
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$displayname = $email;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
$json = json_encode(
|
$json = json_encode(
|
||||||
array(
|
array(
|
||||||
"time" => time(),
|
"time" => time(),
|
||||||
"ua" => $_SERVER['HTTP_USER_AGENT'],
|
"ua" => $_SERVER['HTTP_USER_AGENT'],
|
||||||
"user" => $email,
|
"user" => "none",
|
||||||
"ip" => $_SERVER['REMOTE_ADDR'],
|
"ip" => $_SERVER['REMOTE_ADDR'],
|
||||||
"service" => $autodiscover_config['autodiscoverType']
|
"service" => "Error: must be authenticated"
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
$redis->lPush('AUTODISCOVER_LOG', $json);
|
$valkey->lPush('AUTODISCOVER_LOG', $json);
|
||||||
$redis->lTrim('AUTODISCOVER_LOG', 0, 100);
|
header('WWW-Authenticate: Basic realm="' . $_SERVER['HTTP_HOST'] . '"');
|
||||||
|
header('HTTP/1.0 401 Unauthorized');
|
||||||
|
exit(0);
|
||||||
}
|
}
|
||||||
catch (RedisException $e) {
|
|
||||||
$_SESSION['return'][] = array(
|
$login_role = check_login($login_user, $login_pass, array('eas' => TRUE));
|
||||||
'type' => 'danger',
|
|
||||||
'msg' => 'Redis: '.$e
|
if ($login_role === "user") {
|
||||||
);
|
header("Content-Type: application/xml");
|
||||||
return false;
|
echo '<?xml version="1.0" encoding="utf-8" ?>' . PHP_EOL;
|
||||||
}
|
?>
|
||||||
if ($autodiscover_config['autodiscoverType'] == 'imap') {
|
<Autodiscover xmlns="http://schemas.microsoft.com/exchange/autodiscover/responseschema/2006">
|
||||||
|
<?php
|
||||||
|
if(!$data) {
|
||||||
|
try {
|
||||||
|
$json = json_encode(
|
||||||
|
array(
|
||||||
|
"time" => time(),
|
||||||
|
"ua" => $_SERVER['HTTP_USER_AGENT'],
|
||||||
|
"user" => $_SERVER['PHP_AUTH_USER'],
|
||||||
|
"ip" => $_SERVER['REMOTE_ADDR'],
|
||||||
|
"service" => "Error: invalid or missing request data"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
$valkey->lPush('AUTODISCOVER_LOG', $json);
|
||||||
|
$valkey->lTrim('AUTODISCOVER_LOG', 0, 100);
|
||||||
|
}
|
||||||
|
catch (RedisException $e) {
|
||||||
|
$_SESSION['return'][] = array(
|
||||||
|
'type' => 'danger',
|
||||||
|
'msg' => 'Valkey: '.$e
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
list($usec, $sec) = explode(' ', microtime());
|
||||||
|
?>
|
||||||
|
<Response>
|
||||||
|
<Error Time="<?=date('H:i:s', $sec) . substr($usec, 0, strlen($usec) - 2);?>" Id="2477272013">
|
||||||
|
<ErrorCode>600</ErrorCode>
|
||||||
|
<Message>Invalid Request</Message>
|
||||||
|
<DebugData />
|
||||||
|
</Error>
|
||||||
|
</Response>
|
||||||
|
</Autodiscover>
|
||||||
|
<?php
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$discover = new SimpleXMLElement($data);
|
||||||
|
$email = $discover->Request->EMailAddress;
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$email = $_SERVER['PHP_AUTH_USER'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$username = trim($email);
|
||||||
|
try {
|
||||||
|
$stmt = $pdo->prepare("SELECT `name` FROM `mailbox` WHERE `username`= :username");
|
||||||
|
$stmt->execute(array(':username' => $username));
|
||||||
|
$MailboxData = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
}
|
||||||
|
catch(PDOException $e) {
|
||||||
|
die("Failed to determine name from SQL");
|
||||||
|
}
|
||||||
|
if (!empty($MailboxData['name'])) {
|
||||||
|
$displayname = $MailboxData['name'];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$displayname = $email;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$json = json_encode(
|
||||||
|
array(
|
||||||
|
"time" => time(),
|
||||||
|
"ua" => $_SERVER['HTTP_USER_AGENT'],
|
||||||
|
"user" => $_SERVER['PHP_AUTH_USER'],
|
||||||
|
"ip" => $_SERVER['REMOTE_ADDR'],
|
||||||
|
"service" => $autodiscover_config['autodiscoverType']
|
||||||
|
)
|
||||||
|
);
|
||||||
|
$valkey->lPush('AUTODISCOVER_LOG', $json);
|
||||||
|
$valkey->lTrim('AUTODISCOVER_LOG', 0, 100);
|
||||||
|
}
|
||||||
|
catch (RedisException $e) {
|
||||||
|
$_SESSION['return'][] = array(
|
||||||
|
'type' => 'danger',
|
||||||
|
'msg' => 'Valkey: '.$e
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if ($autodiscover_config['autodiscoverType'] == 'imap') {
|
||||||
?>
|
?>
|
||||||
<Response xmlns="http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a">
|
<Response xmlns="http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a">
|
||||||
<User>
|
<User>
|
||||||
@@ -304,3 +236,6 @@ if ($autodiscover_config['autodiscoverType'] == 'imap') {
|
|||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
</Autodiscover>
|
</Autodiscover>
|
||||||
|
<?php
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|||||||
@@ -3,11 +3,8 @@ require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
|
|||||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/triggers.domainadmin.inc.php';
|
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/triggers.domainadmin.inc.php';
|
||||||
|
|
||||||
if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'domainadmin') {
|
if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'domainadmin') {
|
||||||
// Only redirect to mailbox if NO pending actions
|
header('Location: /domainadmin/mailbox');
|
||||||
if (empty($_SESSION['pending_tfa_setup']) && empty($_SESSION['pending_pw_update'])) {
|
exit();
|
||||||
header('Location: /domainadmin/mailbox');
|
|
||||||
exit();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'admin') {
|
elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'admin') {
|
||||||
header('Location: /admin/dashboard');
|
header('Location: /admin/dashboard');
|
||||||
|
|||||||
@@ -2,7 +2,18 @@
|
|||||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
|
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
|
||||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/triggers.domainadmin.inc.php';
|
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/triggers.domainadmin.inc.php';
|
||||||
|
|
||||||
protect_route(['domainadmin']);
|
if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'admin') {
|
||||||
|
header('Location: /admin/dashboard');
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'user') {
|
||||||
|
header('Location: /user');
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
elseif (!isset($_SESSION['mailcow_cc_role']) || $_SESSION['mailcow_cc_role'] != "domainadmin") {
|
||||||
|
header('Location: /domainadmin');
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/header.inc.php';
|
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/header.inc.php';
|
||||||
$_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
|
$_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
|
||||||
|
|||||||
@@ -2,28 +2,41 @@
|
|||||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
|
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
|
||||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/triggers.domainadmin.inc.php';
|
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/triggers.domainadmin.inc.php';
|
||||||
|
|
||||||
/*
|
if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'domainadmin') {
|
||||||
/ DOMAIN ADMIN
|
|
||||||
*/
|
|
||||||
|
|
||||||
protect_route(['domainadmin']);
|
/*
|
||||||
|
/ DOMAIN ADMIN
|
||||||
|
*/
|
||||||
|
|
||||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/header.inc.php';
|
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/header.inc.php';
|
||||||
$_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
|
$_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
|
||||||
$tfa_data = get_tfa();
|
$tfa_data = get_tfa();
|
||||||
$fido2_data = fido2(array("action" => "get_friendly_names"));
|
$fido2_data = fido2(array("action" => "get_friendly_names"));
|
||||||
$username = $_SESSION['mailcow_cc_username'];
|
$username = $_SESSION['mailcow_cc_username'];
|
||||||
|
|
||||||
$template = 'domainadmin.twig';
|
$template = 'domainadmin.twig';
|
||||||
$template_data = [
|
$template_data = [
|
||||||
'acl' => $_SESSION['acl'],
|
'acl' => $_SESSION['acl'],
|
||||||
'acl_json' => json_encode($_SESSION['acl']),
|
'acl_json' => json_encode($_SESSION['acl']),
|
||||||
'user_spam_score' => mailbox('get', 'spam_score', $username),
|
'user_spam_score' => mailbox('get', 'spam_score', $username),
|
||||||
'tfa_data' => $tfa_data,
|
'tfa_data' => $tfa_data,
|
||||||
'fido2_data' => $fido2_data,
|
'fido2_data' => $fido2_data,
|
||||||
'lang_user' => json_encode($lang['user']),
|
'lang_user' => json_encode($lang['user']),
|
||||||
'lang_datatables' => json_encode($lang['datatables']),
|
'lang_datatables' => json_encode($lang['datatables']),
|
||||||
];
|
];
|
||||||
|
}
|
||||||
|
elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'admin') {
|
||||||
|
header('Location: /admin/dashboard');
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'user') {
|
||||||
|
header('Location: /user');
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
header('Location: /domainadmin');
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
$js_minifier->add('/web/js/site/user.js');
|
$js_minifier->add('/web/js/site/user.js');
|
||||||
$js_minifier->add('/web/js/site/pwgen.js');
|
$js_minifier->add('/web/js/site/pwgen.js');
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
<?php
|
<?php
|
||||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
|
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
|
||||||
|
$AuthUsers = array("admin", "domainadmin", "user");
|
||||||
protect_route();
|
if (!isset($_SESSION['mailcow_cc_role']) OR !in_array($_SESSION['mailcow_cc_role'], $AuthUsers)) {
|
||||||
|
header('Location: /');
|
||||||
|
exit();
|
||||||
|
}
|
||||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/header.inc.php';
|
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/header.inc.php';
|
||||||
|
|
||||||
$template = 'edit.twig';
|
$template = 'edit.twig';
|
||||||
|
|||||||
@@ -129,16 +129,7 @@ if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "adm
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if domain is an alias domain and get target domain's MTA-STS
|
$mta_sts = mailbox('get', 'mta_sts', $domain);
|
||||||
$alias_domain_details = mailbox('get', 'alias_domain_details', $domain);
|
|
||||||
$mta_sts_domain = $domain;
|
|
||||||
|
|
||||||
if ($alias_domain_details !== false && !empty($alias_domain_details['target_domain'])) {
|
|
||||||
// This is an alias domain, check target domain for MTA-STS
|
|
||||||
$mta_sts_domain = $alias_domain_details['target_domain'];
|
|
||||||
}
|
|
||||||
|
|
||||||
$mta_sts = mailbox('get', 'mta_sts', $mta_sts_domain);
|
|
||||||
if (count($mta_sts) > 0 && $mta_sts['active'] == 1) {
|
if (count($mta_sts) > 0 && $mta_sts['active'] == 1) {
|
||||||
if (!in_array($domain, $alias_domains)) {
|
if (!in_array($domain, $alias_domains)) {
|
||||||
$records[] = array(
|
$records[] = array(
|
||||||
|
|||||||
@@ -64,8 +64,6 @@ $globalVariables = [
|
|||||||
'pending_tfa_methods' => @$_SESSION['pending_tfa_methods'],
|
'pending_tfa_methods' => @$_SESSION['pending_tfa_methods'],
|
||||||
'pending_tfa_authmechs' => $pending_tfa_authmechs,
|
'pending_tfa_authmechs' => $pending_tfa_authmechs,
|
||||||
'pending_mailcow_cc_username' => @$_SESSION['pending_mailcow_cc_username'],
|
'pending_mailcow_cc_username' => @$_SESSION['pending_mailcow_cc_username'],
|
||||||
'pending_tfa_setup' => !empty($_SESSION['pending_tfa_setup']),
|
|
||||||
'pending_pw_update_modal' => !empty($_SESSION['pending_pw_update']),
|
|
||||||
'lang_footer' => json_encode($lang['footer']),
|
'lang_footer' => json_encode($lang['footer']),
|
||||||
'lang_acl' => json_encode($lang['acl']),
|
'lang_acl' => json_encode($lang['acl']),
|
||||||
'lang_tfa' => json_encode($lang['tfa']),
|
'lang_tfa' => json_encode($lang['tfa']),
|
||||||
|
|||||||
@@ -121,56 +121,34 @@ function admin($_action, $_data = null) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Check if this is a self password change via forced update
|
if (!empty($password)) {
|
||||||
if ($username == $_SESSION['mailcow_cc_username'] && !empty($_SESSION['pending_pw_update'])) {
|
if (password_check($password, $password2) !== true) {
|
||||||
// Forced password update: only change password and clear force_pw_update flag
|
return false;
|
||||||
if (!empty($password)) {
|
|
||||||
if (password_check($password, $_data['password2']) !== true) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
$password_hashed = hash_password($password);
|
|
||||||
$stmt = $pdo->prepare("UPDATE `admin` SET `password` = :password_hashed,
|
|
||||||
`attributes` = JSON_SET(COALESCE(`attributes`, '{}'), '$.force_pw_update', '0')
|
|
||||||
WHERE `username` = :username");
|
|
||||||
$stmt->execute(array(
|
|
||||||
':password_hashed' => $password_hashed,
|
|
||||||
':username' => $username
|
|
||||||
));
|
|
||||||
unset($_SESSION['pending_pw_update']);
|
|
||||||
}
|
}
|
||||||
} else {
|
$password_hashed = hash_password($password);
|
||||||
// Normal admin edit: update all attributes
|
$stmt = $pdo->prepare("UPDATE `admin` SET `username` = :username_new, `active` = :active, `password` = :password_hashed WHERE `username` = :username");
|
||||||
$force_tfa = intval($_data['force_tfa'] ?? 0) ? 1 : 0;
|
$stmt->execute(array(
|
||||||
$force_pw_update = intval($_data['force_pw_update'] ?? 0) ? 1 : 0;
|
':password_hashed' => $password_hashed,
|
||||||
if (!empty($password)) {
|
':username_new' => $username_new,
|
||||||
if (password_check($password, $password2) !== true) {
|
':username' => $username,
|
||||||
return false;
|
':active' => $active
|
||||||
}
|
));
|
||||||
$password_hashed = hash_password($password);
|
if (isset($_data['disable_tfa'])) {
|
||||||
$stmt = $pdo->prepare("UPDATE `admin` SET `username` = :username_new, `active` = :active, `password` = :password_hashed,
|
$stmt = $pdo->prepare("UPDATE `tfa` SET `active` = '0' WHERE `username` = :username");
|
||||||
`attributes` = JSON_SET(COALESCE(`attributes`, '{}'), '$.force_tfa', :force_tfa, '$.force_pw_update', :force_pw_update)
|
$stmt->execute(array(':username' => $username));
|
||||||
WHERE `username` = :username");
|
|
||||||
$stmt->execute(array(
|
|
||||||
':password_hashed' => $password_hashed,
|
|
||||||
':username_new' => $username_new,
|
|
||||||
':username' => $username,
|
|
||||||
':active' => $active,
|
|
||||||
':force_tfa' => strval($force_tfa),
|
|
||||||
':force_pw_update' => strval($force_pw_update)
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$stmt = $pdo->prepare("UPDATE `admin` SET `username` = :username_new, `active` = :active,
|
$stmt = $pdo->prepare("UPDATE `tfa` SET `username` = :username_new WHERE `username` = :username");
|
||||||
`attributes` = JSON_SET(COALESCE(`attributes`, '{}'), '$.force_tfa', :force_tfa, '$.force_pw_update', :force_pw_update)
|
$stmt->execute(array(':username_new' => $username_new, ':username' => $username));
|
||||||
WHERE `username` = :username");
|
|
||||||
$stmt->execute(array(
|
|
||||||
':username_new' => $username_new,
|
|
||||||
':username' => $username,
|
|
||||||
':active' => $active,
|
|
||||||
':force_tfa' => strval($force_tfa),
|
|
||||||
':force_pw_update' => strval($force_pw_update)
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$stmt = $pdo->prepare("UPDATE `admin` SET `username` = :username_new, `active` = :active WHERE `username` = :username");
|
||||||
|
$stmt->execute(array(
|
||||||
|
':username_new' => $username_new,
|
||||||
|
':username' => $username,
|
||||||
|
':active' => $active
|
||||||
|
));
|
||||||
if (isset($_data['disable_tfa'])) {
|
if (isset($_data['disable_tfa'])) {
|
||||||
$stmt = $pdo->prepare("UPDATE `tfa` SET `active` = '0' WHERE `username` = :username");
|
$stmt = $pdo->prepare("UPDATE `tfa` SET `active` = '0' WHERE `username` = :username");
|
||||||
$stmt->execute(array(':username' => $username));
|
$stmt->execute(array(':username' => $username));
|
||||||
@@ -245,8 +223,7 @@ function admin($_action, $_data = null) {
|
|||||||
`tfa`.`active` AS `tfa_active`,
|
`tfa`.`active` AS `tfa_active`,
|
||||||
`admin`.`username`,
|
`admin`.`username`,
|
||||||
`admin`.`created`,
|
`admin`.`created`,
|
||||||
`admin`.`active` AS `active`,
|
`admin`.`active` AS `active`
|
||||||
`admin`.`attributes` AS `attributes`
|
|
||||||
FROM `admin`
|
FROM `admin`
|
||||||
LEFT OUTER JOIN `tfa` ON `tfa`.`username`=`admin`.`username`
|
LEFT OUTER JOIN `tfa` ON `tfa`.`username`=`admin`.`username`
|
||||||
WHERE `admin`.`username`= :admin AND `superadmin` = '1'");
|
WHERE `admin`.`username`= :admin AND `superadmin` = '1'");
|
||||||
@@ -263,7 +240,6 @@ function admin($_action, $_data = null) {
|
|||||||
$admindata['active'] = $row['active'];
|
$admindata['active'] = $row['active'];
|
||||||
$admindata['active_int'] = $row['active'];
|
$admindata['active_int'] = $row['active'];
|
||||||
$admindata['created'] = $row['created'];
|
$admindata['created'] = $row['created'];
|
||||||
$admindata['attributes'] = json_decode($row['attributes'], true) ?? array('force_tfa' => '0', 'force_pw_update' => '0');
|
|
||||||
return $admindata;
|
return $admindata;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
<?php
|
<?php
|
||||||
function check_login($user, $pass, $extra = null) {
|
function check_login($user, $pass, $app_passwd_data = false, $extra = null) {
|
||||||
global $pdo;
|
global $pdo;
|
||||||
global $redis;
|
global $valkey;
|
||||||
|
|
||||||
$is_internal = $extra['is_internal'];
|
$is_internal = $extra['is_internal'];
|
||||||
$role = $extra['role'];
|
$role = $extra['role'];
|
||||||
$extra['service'] = !isset($extra['service']) ? 'NONE' : $extra['service'];
|
|
||||||
|
|
||||||
// Try validate admin
|
// Try validate admin
|
||||||
if (!isset($role) || $role == "admin") {
|
if (!isset($role) || $role == "admin") {
|
||||||
@@ -26,20 +25,34 @@ function check_login($user, $pass, $extra = null) {
|
|||||||
|
|
||||||
// Try validate app password
|
// Try validate app password
|
||||||
if (!isset($role) || $role == "app") {
|
if (!isset($role) || $role == "app") {
|
||||||
$result = apppass_login($user, $pass, $extra);
|
$result = apppass_login($user, $pass, $app_passwd_data);
|
||||||
if ($result !== false) {
|
if ($result !== false) {
|
||||||
|
if ($app_passwd_data['eas'] === true) {
|
||||||
|
$service = 'EAS';
|
||||||
|
} elseif ($app_passwd_data['dav'] === true) {
|
||||||
|
$service = 'DAV';
|
||||||
|
} else {
|
||||||
|
$service = 'NONE';
|
||||||
|
}
|
||||||
$real_rip = ($_SERVER['HTTP_X_REAL_IP'] ?? $_SERVER['REMOTE_ADDR']);
|
$real_rip = ($_SERVER['HTTP_X_REAL_IP'] ?? $_SERVER['REMOTE_ADDR']);
|
||||||
set_sasl_log($user, $real_rip, $extra['service'], $pass);
|
set_sasl_log($user, $real_rip, $service, $pass);
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try validate user
|
// Try validate user
|
||||||
if (!isset($role) || $role == "user") {
|
if (!isset($role) || $role == "user") {
|
||||||
$result = user_login($user, $pass, $extra);
|
$result = user_login($user, $pass);
|
||||||
if ($result !== false) {
|
if ($result !== false) {
|
||||||
|
if ($app_passwd_data['eas'] === true) {
|
||||||
|
$service = 'EAS';
|
||||||
|
} elseif ($app_passwd_data['dav'] === true) {
|
||||||
|
$service = 'DAV';
|
||||||
|
} else {
|
||||||
|
$service = 'MAILCOWUI';
|
||||||
|
}
|
||||||
$real_rip = ($_SERVER['HTTP_X_REAL_IP'] ?? $_SERVER['REMOTE_ADDR']);
|
$real_rip = ($_SERVER['HTTP_X_REAL_IP'] ?? $_SERVER['REMOTE_ADDR']);
|
||||||
set_sasl_log($user, $real_rip, $extra['service']);
|
set_sasl_log($user, $real_rip, $service);
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -49,12 +62,12 @@ function check_login($user, $pass, $extra = null) {
|
|||||||
|
|
||||||
if (!isset($_SESSION['ldelay'])) {
|
if (!isset($_SESSION['ldelay'])) {
|
||||||
$_SESSION['ldelay'] = "0";
|
$_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']);
|
error_log("mailcow UI: Invalid password for " . $user . " by " . $_SERVER['REMOTE_ADDR']);
|
||||||
}
|
}
|
||||||
elseif (!isset($_SESSION['mailcow_cc_username'])) {
|
elseif (!isset($_SESSION['mailcow_cc_username'])) {
|
||||||
$_SESSION['ldelay'] = $_SESSION['ldelay']+0.5;
|
$_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']);
|
error_log("mailcow UI: Invalid password for " . $user . " by " . $_SERVER['REMOTE_ADDR']);
|
||||||
}
|
}
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
@@ -82,7 +95,7 @@ function admin_login($user, $pass){
|
|||||||
}
|
}
|
||||||
|
|
||||||
$user = strtolower(trim($user));
|
$user = strtolower(trim($user));
|
||||||
$stmt = $pdo->prepare("SELECT `password`, `attributes` FROM `admin`
|
$stmt = $pdo->prepare("SELECT `password` FROM `admin`
|
||||||
WHERE `superadmin` = '1'
|
WHERE `superadmin` = '1'
|
||||||
AND `active` = '1'
|
AND `active` = '1'
|
||||||
AND `username` = :user");
|
AND `username` = :user");
|
||||||
@@ -91,13 +104,6 @@ function admin_login($user, $pass){
|
|||||||
|
|
||||||
// verify password
|
// verify password
|
||||||
if (verify_hash($row['password'], $pass)) {
|
if (verify_hash($row['password'], $pass)) {
|
||||||
$admin_attrs = json_decode($row['attributes'], true) ?? [];
|
|
||||||
|
|
||||||
// Check force_pw_update
|
|
||||||
if (intval($admin_attrs['force_pw_update'] ?? 0) == 1) {
|
|
||||||
$_SESSION['pending_pw_update'] = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// check for tfa authenticators
|
// check for tfa authenticators
|
||||||
$authenticators = get_tfa($user);
|
$authenticators = get_tfa($user);
|
||||||
if (isset($authenticators['additional']) && is_array($authenticators['additional']) && count($authenticators['additional']) > 0) {
|
if (isset($authenticators['additional']) && is_array($authenticators['additional']) && count($authenticators['additional']) > 0) {
|
||||||
@@ -117,10 +123,6 @@ function admin_login($user, $pass){
|
|||||||
// Reactivate TFA if it was set to "deactivate TFA for next login"
|
// Reactivate TFA if it was set to "deactivate TFA for next login"
|
||||||
$stmt = $pdo->prepare("UPDATE `tfa` SET `active`='1' WHERE `username` = :user");
|
$stmt = $pdo->prepare("UPDATE `tfa` SET `active`='1' WHERE `username` = :user");
|
||||||
$stmt->execute(array(':user' => $user));
|
$stmt->execute(array(':user' => $user));
|
||||||
// Check force_tfa: only force setup if NO TFA exists at all
|
|
||||||
if (intval($admin_attrs['force_tfa'] ?? 0) == 1 && !tfa_exists($user)) {
|
|
||||||
$_SESSION['pending_tfa_setup'] = true;
|
|
||||||
}
|
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'success',
|
'type' => 'success',
|
||||||
'log' => array(__FUNCTION__, $user, '*'),
|
'log' => array(__FUNCTION__, $user, '*'),
|
||||||
@@ -146,7 +148,7 @@ function domainadmin_login($user, $pass){
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$stmt = $pdo->prepare("SELECT `password`, `attributes` FROM `admin`
|
$stmt = $pdo->prepare("SELECT `password` FROM `admin`
|
||||||
WHERE `superadmin` = '0'
|
WHERE `superadmin` = '0'
|
||||||
AND `active`='1'
|
AND `active`='1'
|
||||||
AND `username` = :user");
|
AND `username` = :user");
|
||||||
@@ -155,13 +157,6 @@ function domainadmin_login($user, $pass){
|
|||||||
|
|
||||||
// verify password
|
// verify password
|
||||||
if (verify_hash($row['password'], $pass) !== false) {
|
if (verify_hash($row['password'], $pass) !== false) {
|
||||||
$admin_attrs = json_decode($row['attributes'], true) ?? [];
|
|
||||||
|
|
||||||
// Check force_pw_update
|
|
||||||
if (intval($admin_attrs['force_pw_update'] ?? 0) == 1) {
|
|
||||||
$_SESSION['pending_pw_update'] = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// check for tfa authenticators
|
// check for tfa authenticators
|
||||||
$authenticators = get_tfa($user);
|
$authenticators = get_tfa($user);
|
||||||
if (isset($authenticators['additional']) && is_array($authenticators['additional']) && count($authenticators['additional']) > 0) {
|
if (isset($authenticators['additional']) && is_array($authenticators['additional']) && count($authenticators['additional']) > 0) {
|
||||||
@@ -181,10 +176,6 @@ function domainadmin_login($user, $pass){
|
|||||||
// Reactivate TFA if it was set to "deactivate TFA for next login"
|
// Reactivate TFA if it was set to "deactivate TFA for next login"
|
||||||
$stmt = $pdo->prepare("UPDATE `tfa` SET `active`='1' WHERE `username` = :user");
|
$stmt = $pdo->prepare("UPDATE `tfa` SET `active`='1' WHERE `username` = :user");
|
||||||
$stmt->execute(array(':user' => $user));
|
$stmt->execute(array(':user' => $user));
|
||||||
// Check force_tfa: only force setup if NO TFA exists at all
|
|
||||||
if (intval($admin_attrs['force_tfa'] ?? 0) == 1 && !tfa_exists($user)) {
|
|
||||||
$_SESSION['pending_tfa_setup'] = true;
|
|
||||||
}
|
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'success',
|
'type' => 'success',
|
||||||
'log' => array(__FUNCTION__, $user, '*'),
|
'log' => array(__FUNCTION__, $user, '*'),
|
||||||
@@ -202,7 +193,7 @@ function user_login($user, $pass, $extra = null){
|
|||||||
global $iam_settings;
|
global $iam_settings;
|
||||||
|
|
||||||
$is_internal = $extra['is_internal'];
|
$is_internal = $extra['is_internal'];
|
||||||
$extra['service'] = !isset($extra['service']) ? 'NONE' : $extra['service'];
|
$service = $extra['service'];
|
||||||
|
|
||||||
if (!filter_var($user, FILTER_VALIDATE_EMAIL) && !ctype_alnum(str_replace(array('_', '.', '-'), '', $user))) {
|
if (!filter_var($user, FILTER_VALIDATE_EMAIL) && !ctype_alnum(str_replace(array('_', '.', '-'), '', $user))) {
|
||||||
if (!$is_internal){
|
if (!$is_internal){
|
||||||
@@ -245,10 +236,10 @@ function user_login($user, $pass, $extra = null){
|
|||||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
if (!empty($row)) {
|
if (!empty($row)) {
|
||||||
// check if user has access to service (imap, smtp, pop3, sieve, dav, eas) if service is set
|
// check if user has access to service (imap, smtp, pop3, sieve) if service is set
|
||||||
$row['attributes'] = json_decode($row['attributes'], true);
|
$row['attributes'] = json_decode($row['attributes'], true);
|
||||||
if ($extra['service'] != 'NONE') {
|
if (isset($service)) {
|
||||||
$key = strtolower($extra['service']) . "_access";
|
$key = strtolower($service) . "_access";
|
||||||
if (isset($row['attributes'][$key]) && $row['attributes'][$key] != '1') {
|
if (isset($row['attributes'][$key]) && $row['attributes'][$key] != '1') {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -262,8 +253,8 @@ function user_login($user, $pass, $extra = null){
|
|||||||
|
|
||||||
// check if user has access to service (imap, smtp, pop3, sieve) if service is set
|
// check if user has access to service (imap, smtp, pop3, sieve) if service is set
|
||||||
$row['attributes'] = json_decode($row['attributes'], true);
|
$row['attributes'] = json_decode($row['attributes'], true);
|
||||||
if ($extra['service'] != 'NONE') {
|
if (isset($service)) {
|
||||||
$key = strtolower($extra['service']) . "_access";
|
$key = strtolower($service) . "_access";
|
||||||
if (isset($row['attributes'][$key]) && $row['attributes'][$key] != '1') {
|
if (isset($row['attributes'][$key]) && $row['attributes'][$key] != '1') {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -287,8 +278,6 @@ function user_login($user, $pass, $extra = null){
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$row['attributes'] = json_decode($row['attributes'], true);
|
|
||||||
|
|
||||||
// check for tfa authenticators
|
// check for tfa authenticators
|
||||||
$authenticators = get_tfa($user);
|
$authenticators = get_tfa($user);
|
||||||
if (isset($authenticators['additional']) && is_array($authenticators['additional']) && count($authenticators['additional']) > 0 && !$is_internal) {
|
if (isset($authenticators['additional']) && is_array($authenticators['additional']) && count($authenticators['additional']) > 0 && !$is_internal) {
|
||||||
@@ -310,10 +299,6 @@ function user_login($user, $pass, $extra = null){
|
|||||||
// Reactivate TFA if it was set to "deactivate TFA for next login"
|
// Reactivate TFA if it was set to "deactivate TFA for next login"
|
||||||
$stmt = $pdo->prepare("UPDATE `tfa` SET `active`='1' WHERE `username` = :user");
|
$stmt = $pdo->prepare("UPDATE `tfa` SET `active`='1' WHERE `username` = :user");
|
||||||
$stmt->execute(array(':user' => $user));
|
$stmt->execute(array(':user' => $user));
|
||||||
// Check force_tfa: only force setup if NO TFA exists at all
|
|
||||||
if (intval($row['attributes']['force_tfa']) == 1 && !tfa_exists($user)) {
|
|
||||||
$_SESSION['pending_tfa_setup'] = true;
|
|
||||||
}
|
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'success',
|
'type' => 'success',
|
||||||
'log' => array(__FUNCTION__, $user, '*', 'Provider: Keycloak'),
|
'log' => array(__FUNCTION__, $user, '*', 'Provider: Keycloak'),
|
||||||
@@ -345,8 +330,6 @@ function user_login($user, $pass, $extra = null){
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$row['attributes'] = json_decode($row['attributes'], true);
|
|
||||||
|
|
||||||
// check for tfa authenticators
|
// check for tfa authenticators
|
||||||
$authenticators = get_tfa($user);
|
$authenticators = get_tfa($user);
|
||||||
if (isset($authenticators['additional']) && is_array($authenticators['additional']) && count($authenticators['additional']) > 0 && !$is_internal) {
|
if (isset($authenticators['additional']) && is_array($authenticators['additional']) && count($authenticators['additional']) > 0 && !$is_internal) {
|
||||||
@@ -368,10 +351,6 @@ function user_login($user, $pass, $extra = null){
|
|||||||
// Reactivate TFA if it was set to "deactivate TFA for next login"
|
// Reactivate TFA if it was set to "deactivate TFA for next login"
|
||||||
$stmt = $pdo->prepare("UPDATE `tfa` SET `active`='1' WHERE `username` = :user");
|
$stmt = $pdo->prepare("UPDATE `tfa` SET `active`='1' WHERE `username` = :user");
|
||||||
$stmt->execute(array(':user' => $user));
|
$stmt->execute(array(':user' => $user));
|
||||||
// Check force_tfa: only force setup if NO TFA exists at all
|
|
||||||
if (intval($row['attributes']['force_tfa']) == 1 && !tfa_exists($user)) {
|
|
||||||
$_SESSION['pending_tfa_setup'] = true;
|
|
||||||
}
|
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'success',
|
'type' => 'success',
|
||||||
'log' => array(__FUNCTION__, $user, '*', 'Provider: LDAP'),
|
'log' => array(__FUNCTION__, $user, '*', 'Provider: LDAP'),
|
||||||
@@ -415,10 +394,6 @@ function user_login($user, $pass, $extra = null){
|
|||||||
// Reactivate TFA if it was set to "deactivate TFA for next login"
|
// Reactivate TFA if it was set to "deactivate TFA for next login"
|
||||||
$stmt = $pdo->prepare("UPDATE `tfa` SET `active`='1' WHERE `username` = :user");
|
$stmt = $pdo->prepare("UPDATE `tfa` SET `active`='1' WHERE `username` = :user");
|
||||||
$stmt->execute(array(':user' => $user));
|
$stmt->execute(array(':user' => $user));
|
||||||
// Check force_tfa: only force setup if NO TFA exists at all
|
|
||||||
if (intval($row['attributes']['force_tfa']) == 1 && !tfa_exists($user)) {
|
|
||||||
$_SESSION['pending_tfa_setup'] = true;
|
|
||||||
}
|
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'success',
|
'type' => 'success',
|
||||||
'log' => array(__FUNCTION__, $user, '*', 'Provider: mailcow'),
|
'log' => array(__FUNCTION__, $user, '*', 'Provider: mailcow'),
|
||||||
@@ -433,7 +408,7 @@ function user_login($user, $pass, $extra = null){
|
|||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
function apppass_login($user, $pass, $extra = null){
|
function apppass_login($user, $pass, $app_passwd_data, $extra = null){
|
||||||
global $pdo;
|
global $pdo;
|
||||||
|
|
||||||
$is_internal = $extra['is_internal'];
|
$is_internal = $extra['is_internal'];
|
||||||
@@ -449,8 +424,20 @@ function apppass_login($user, $pass, $extra = null){
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$extra['service'] = !isset($extra['service']) ? 'NONE' : $extra['service'];
|
$protocol = false;
|
||||||
if (!$is_internal && $extra['service'] == 'NONE') {
|
if ($app_passwd_data['eas']){
|
||||||
|
$protocol = 'eas';
|
||||||
|
} else if ($app_passwd_data['dav']){
|
||||||
|
$protocol = 'dav';
|
||||||
|
} else if ($app_passwd_data['smtp']){
|
||||||
|
$protocol = 'smtp';
|
||||||
|
} else if ($app_passwd_data['imap']){
|
||||||
|
$protocol = 'imap';
|
||||||
|
} else if ($app_passwd_data['sieve']){
|
||||||
|
$protocol = 'sieve';
|
||||||
|
} else if ($app_passwd_data['pop3']){
|
||||||
|
$protocol = 'pop3';
|
||||||
|
} else if (!$is_internal) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -471,7 +458,7 @@ function apppass_login($user, $pass, $extra = null){
|
|||||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
foreach ($rows as $row) {
|
foreach ($rows as $row) {
|
||||||
if ($extra['service'] != 'NONE' && $row[strtolower($extra['service']) . '_access'] != '1'){
|
if ($protocol && $row[$protocol . '_access'] != '1'){
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
function customize($_action, $_item, $_data = null) {
|
function customize($_action, $_item, $_data = null) {
|
||||||
global $redis;
|
global $valkey;
|
||||||
global $lang;
|
global $lang;
|
||||||
global $LOGO_LIMITS;
|
global $LOGO_LIMITS;
|
||||||
|
|
||||||
@@ -82,13 +82,13 @@ function customize($_action, $_item, $_data = null) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
try {
|
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) {
|
catch (RedisException $e) {
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'danger',
|
'type' => 'danger',
|
||||||
'log' => array(__FUNCTION__, $_action, $_item, $_data),
|
'log' => array(__FUNCTION__, $_action, $_item, $_data),
|
||||||
'msg' => array('redis_error', $e)
|
'msg' => array('valkey_error', $e)
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -134,13 +134,13 @@ function customize($_action, $_item, $_data = null) {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
$redis->set('APP_LINKS', json_encode($out));
|
$valkey->set('APP_LINKS', json_encode($out));
|
||||||
}
|
}
|
||||||
catch (RedisException $e) {
|
catch (RedisException $e) {
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'danger',
|
'type' => 'danger',
|
||||||
'log' => array(__FUNCTION__, $_action, $_item, $_data),
|
'log' => array(__FUNCTION__, $_action, $_item, $_data),
|
||||||
'msg' => array('redis_error', $e)
|
'msg' => array('valkey_error', $e)
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -162,20 +162,20 @@ function customize($_action, $_item, $_data = null) {
|
|||||||
$ui_announcement_active = (!empty($_data['ui_announcement_active']) ? 1 : 0);
|
$ui_announcement_active = (!empty($_data['ui_announcement_active']) ? 1 : 0);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$redis->set('TITLE_NAME', htmlspecialchars($title_name));
|
$valkey->set('TITLE_NAME', htmlspecialchars($title_name));
|
||||||
$redis->set('MAIN_NAME', htmlspecialchars($main_name));
|
$valkey->set('MAIN_NAME', htmlspecialchars($main_name));
|
||||||
$redis->set('APPS_NAME', htmlspecialchars($apps_name));
|
$valkey->set('APPS_NAME', htmlspecialchars($apps_name));
|
||||||
$redis->set('HELP_TEXT', $help_text);
|
$valkey->set('HELP_TEXT', $help_text);
|
||||||
$redis->set('UI_FOOTER', $ui_footer);
|
$valkey->set('UI_FOOTER', $ui_footer);
|
||||||
$redis->set('UI_ANNOUNCEMENT_TEXT', $ui_announcement_text);
|
$valkey->set('UI_ANNOUNCEMENT_TEXT', $ui_announcement_text);
|
||||||
$redis->set('UI_ANNOUNCEMENT_TYPE', $ui_announcement_type);
|
$valkey->set('UI_ANNOUNCEMENT_TYPE', $ui_announcement_type);
|
||||||
$redis->set('UI_ANNOUNCEMENT_ACTIVE', $ui_announcement_active);
|
$valkey->set('UI_ANNOUNCEMENT_ACTIVE', $ui_announcement_active);
|
||||||
}
|
}
|
||||||
catch (RedisException $e) {
|
catch (RedisException $e) {
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'danger',
|
'type' => 'danger',
|
||||||
'log' => array(__FUNCTION__, $_action, $_item, $_data),
|
'log' => array(__FUNCTION__, $_action, $_item, $_data),
|
||||||
'msg' => array('redis_error', $e)
|
'msg' => array('valkey_error', $e)
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -188,13 +188,13 @@ function customize($_action, $_item, $_data = null) {
|
|||||||
case 'ip_check':
|
case 'ip_check':
|
||||||
$ip_check = ($_data['ip_check_opt_in'] == "1") ? 1 : 0;
|
$ip_check = ($_data['ip_check_opt_in'] == "1") ? 1 : 0;
|
||||||
try {
|
try {
|
||||||
$redis->set('IP_CHECK', $ip_check);
|
$valkey->set('IP_CHECK', $ip_check);
|
||||||
}
|
}
|
||||||
catch (RedisException $e) {
|
catch (RedisException $e) {
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'danger',
|
'type' => 'danger',
|
||||||
'log' => array(__FUNCTION__, $_action, $_item, $_data),
|
'log' => array(__FUNCTION__, $_action, $_item, $_data),
|
||||||
'msg' => array('redis_error', $e)
|
'msg' => array('valkey_error', $e)
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -217,7 +217,7 @@ function customize($_action, $_item, $_data = null) {
|
|||||||
"force_sso" => $force_sso,
|
"force_sso" => $force_sso,
|
||||||
);
|
);
|
||||||
try {
|
try {
|
||||||
$redis->set('CUSTOM_LOGIN', json_encode($custom_login));
|
$valkey->set('CUSTOM_LOGIN', json_encode($custom_login));
|
||||||
}
|
}
|
||||||
catch (RedisException $e) {
|
catch (RedisException $e) {
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
@@ -257,7 +257,7 @@ function customize($_action, $_item, $_data = null) {
|
|||||||
case 'main_logo':
|
case 'main_logo':
|
||||||
case 'main_logo_dark':
|
case 'main_logo_dark':
|
||||||
try {
|
try {
|
||||||
if ($redis->del(strtoupper($_item))) {
|
if ($valkey->del(strtoupper($_item))) {
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'success',
|
'type' => 'success',
|
||||||
'log' => array(__FUNCTION__, $_action, $_item, $_data),
|
'log' => array(__FUNCTION__, $_action, $_item, $_data),
|
||||||
@@ -270,7 +270,7 @@ function customize($_action, $_item, $_data = null) {
|
|||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'danger',
|
'type' => 'danger',
|
||||||
'log' => array(__FUNCTION__, $_action, $_item, $_data),
|
'log' => array(__FUNCTION__, $_action, $_item, $_data),
|
||||||
'msg' => array('redis_error', $e)
|
'msg' => array('valkey_error', $e)
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -281,13 +281,13 @@ function customize($_action, $_item, $_data = null) {
|
|||||||
switch ($_item) {
|
switch ($_item) {
|
||||||
case 'app_links':
|
case 'app_links':
|
||||||
try {
|
try {
|
||||||
$app_links = json_decode($redis->get('APP_LINKS'), true);
|
$app_links = json_decode($valkey->get('APP_LINKS'), true);
|
||||||
}
|
}
|
||||||
catch (RedisException $e) {
|
catch (RedisException $e) {
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'danger',
|
'type' => 'danger',
|
||||||
'log' => array(__FUNCTION__, $_action, $_item, $_data),
|
'log' => array(__FUNCTION__, $_action, $_item, $_data),
|
||||||
'msg' => array('redis_error', $e)
|
'msg' => array('valkey_error', $e)
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -312,13 +312,13 @@ function customize($_action, $_item, $_data = null) {
|
|||||||
case 'main_logo':
|
case 'main_logo':
|
||||||
case 'main_logo_dark':
|
case 'main_logo_dark':
|
||||||
try {
|
try {
|
||||||
return $redis->get(strtoupper($_item));
|
return $valkey->get(strtoupper($_item));
|
||||||
}
|
}
|
||||||
catch (RedisException $e) {
|
catch (RedisException $e) {
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'danger',
|
'type' => 'danger',
|
||||||
'log' => array(__FUNCTION__, $_action, $_item, $_data),
|
'log' => array(__FUNCTION__, $_action, $_item, $_data),
|
||||||
'msg' => array('redis_error', $e)
|
'msg' => array('valkey_error', $e)
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -327,25 +327,25 @@ function customize($_action, $_item, $_data = null) {
|
|||||||
try {
|
try {
|
||||||
$mailcow_hostname = strtolower(getenv("MAILCOW_HOSTNAME"));
|
$mailcow_hostname = strtolower(getenv("MAILCOW_HOSTNAME"));
|
||||||
|
|
||||||
$data['title_name'] = ($title_name = $redis->get('TITLE_NAME')) ? $title_name : "$mailcow_hostname - mail UI";
|
$data['title_name'] = ($title_name = $valkey->get('TITLE_NAME')) ? $title_name : "$mailcow_hostname - mail UI";
|
||||||
$data['main_name'] = ($main_name = $redis->get('MAIN_NAME')) ? $main_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 = $redis->get('APPS_NAME')) ? $apps_name : $lang['header']['apps'];
|
$data['apps_name'] = ($apps_name = $valkey->get('APPS_NAME')) ? $apps_name : $lang['header']['apps'];
|
||||||
$data['help_text'] = ($help_text = $redis->get('HELP_TEXT')) ? $help_text : false;
|
$data['help_text'] = ($help_text = $valkey->get('HELP_TEXT')) ? $help_text : false;
|
||||||
if (!empty($redis->get('UI_IMPRESS'))) {
|
if (!empty($valkey->get('UI_IMPRESS'))) {
|
||||||
$redis->set('UI_FOOTER', $redis->get('UI_IMPRESS'));
|
$valkey->set('UI_FOOTER', $valkey->get('UI_IMPRESS'));
|
||||||
$redis->del('UI_IMPRESS');
|
$valkey->del('UI_IMPRESS');
|
||||||
}
|
}
|
||||||
$data['ui_footer'] = ($ui_footer = $redis->get('UI_FOOTER')) ? $ui_footer : false;
|
$data['ui_footer'] = ($ui_footer = $valkey->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_text'] = ($ui_announcement_text = $valkey->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_type'] = ($ui_announcement_type = $valkey->get('UI_ANNOUNCEMENT_TYPE')) ? $ui_announcement_type : false;
|
||||||
$data['ui_announcement_active'] = ($redis->get('UI_ANNOUNCEMENT_ACTIVE') == 1) ? 1 : 0;
|
$data['ui_announcement_active'] = ($valkey->get('UI_ANNOUNCEMENT_ACTIVE') == 1) ? 1 : 0;
|
||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
catch (RedisException $e) {
|
catch (RedisException $e) {
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'danger',
|
'type' => 'danger',
|
||||||
'log' => array(__FUNCTION__, $_action, $_item, $_data),
|
'log' => array(__FUNCTION__, $_action, $_item, $_data),
|
||||||
'msg' => array('redis_error', $e)
|
'msg' => array('valkey_error', $e)
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -376,21 +376,21 @@ function customize($_action, $_item, $_data = null) {
|
|||||||
break;
|
break;
|
||||||
case 'ip_check':
|
case 'ip_check':
|
||||||
try {
|
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;
|
return $ip_check;
|
||||||
}
|
}
|
||||||
catch (RedisException $e) {
|
catch (RedisException $e) {
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'danger',
|
'type' => 'danger',
|
||||||
'log' => array(__FUNCTION__, $_action, $_item, $_data),
|
'log' => array(__FUNCTION__, $_action, $_item, $_data),
|
||||||
'msg' => array('redis_error', $e)
|
'msg' => array('valkey_error', $e)
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'custom_login':
|
case 'custom_login':
|
||||||
try {
|
try {
|
||||||
$custom_login = $redis->get('CUSTOM_LOGIN');
|
$custom_login = $valkey->get('CUSTOM_LOGIN');
|
||||||
return $custom_login ? json_decode($custom_login, true) : array();
|
return $custom_login ? json_decode($custom_login, true) : array();
|
||||||
}
|
}
|
||||||
catch (RedisException $e) {
|
catch (RedisException $e) {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
function dkim($_action, $_data = null, $privkey = false) {
|
function dkim($_action, $_data = null, $privkey = false) {
|
||||||
global $redis;
|
global $valkey;
|
||||||
global $lang;
|
global $lang;
|
||||||
switch ($_action) {
|
switch ($_action) {
|
||||||
case 'add':
|
case 'add':
|
||||||
@@ -18,7 +18,7 @@ function dkim($_action, $_data = null, $privkey = false) {
|
|||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if ($redis->hGet('DKIM_PUB_KEYS', $domain)) {
|
if ($valkey->hGet('DKIM_PUB_KEYS', $domain)) {
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'danger',
|
'type' => 'danger',
|
||||||
'log' => array(__FUNCTION__, $_action, $_data),
|
'log' => array(__FUNCTION__, $_action, $_data),
|
||||||
@@ -54,30 +54,30 @@ function dkim($_action, $_data = null, $privkey = false) {
|
|||||||
explode(PHP_EOL, $key_details['key'])
|
explode(PHP_EOL, $key_details['key'])
|
||||||
), 1, -1)
|
), 1, -1)
|
||||||
);
|
);
|
||||||
// Save public key and selector to redis
|
// Save public key and selector to valkey
|
||||||
try {
|
try {
|
||||||
$redis->hSet('DKIM_PUB_KEYS', $domain, $pubKey);
|
$valkey->hSet('DKIM_PUB_KEYS', $domain, $pubKey);
|
||||||
$redis->hSet('DKIM_SELECTORS', $domain, $dkim_selector);
|
$valkey->hSet('DKIM_SELECTORS', $domain, $dkim_selector);
|
||||||
}
|
}
|
||||||
catch (RedisException $e) {
|
catch (RedisException $e) {
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'danger',
|
'type' => 'danger',
|
||||||
'log' => array(__FUNCTION__, $_action, $_data),
|
'log' => array(__FUNCTION__, $_action, $_data),
|
||||||
'msg' => array('redis_error', $e)
|
'msg' => array('valkey_error', $e)
|
||||||
);
|
);
|
||||||
continue;
|
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);
|
openssl_pkey_export($keypair_ressource, $privKey);
|
||||||
if (isset($privKey) && !empty($privKey)) {
|
if (isset($privKey) && !empty($privKey)) {
|
||||||
try {
|
try {
|
||||||
$redis->hSet('DKIM_PRIV_KEYS', $dkim_selector . '.' . $domain, trim($privKey));
|
$valkey->hSet('DKIM_PRIV_KEYS', $dkim_selector . '.' . $domain, trim($privKey));
|
||||||
}
|
}
|
||||||
catch (RedisException $e) {
|
catch (RedisException $e) {
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'danger',
|
'type' => 'danger',
|
||||||
'log' => array(__FUNCTION__, $_action, $_data),
|
'log' => array(__FUNCTION__, $_action, $_data),
|
||||||
'msg' => array('redis_error', $e)
|
'msg' => array('valkey_error', $e)
|
||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -121,15 +121,15 @@ function dkim($_action, $_data = null, $privkey = false) {
|
|||||||
$to_domains = array_filter($to_domains);
|
$to_domains = array_filter($to_domains);
|
||||||
foreach ($to_domains as $to_domain) {
|
foreach ($to_domains as $to_domain) {
|
||||||
try {
|
try {
|
||||||
$redis->hSet('DKIM_PUB_KEYS', $to_domain, $from_domain_dkim['pubkey']);
|
$valkey->hSet('DKIM_PUB_KEYS', $to_domain, $from_domain_dkim['pubkey']);
|
||||||
$redis->hSet('DKIM_SELECTORS', $to_domain, $from_domain_dkim['dkim_selector']);
|
$valkey->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_PRIV_KEYS', $from_domain_dkim['dkim_selector'] . '.' . $to_domain, base64_decode(trim($from_domain_dkim['privkey'])));
|
||||||
}
|
}
|
||||||
catch (RedisException $e) {
|
catch (RedisException $e) {
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'danger',
|
'type' => 'danger',
|
||||||
'log' => array(__FUNCTION__, $_action, $_data),
|
'log' => array(__FUNCTION__, $_action, $_data),
|
||||||
'msg' => array('redis_error', $e)
|
'msg' => array('valkey_error', $e)
|
||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -178,7 +178,7 @@ function dkim($_action, $_data = null, $privkey = false) {
|
|||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if ($redis->hGet('DKIM_PUB_KEYS', $domain)) {
|
if ($valkey->hGet('DKIM_PUB_KEYS', $domain)) {
|
||||||
if ($overwrite_existing == 0) {
|
if ($overwrite_existing == 0) {
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'danger',
|
'type' => 'danger',
|
||||||
@@ -198,15 +198,15 @@ function dkim($_action, $_data = null, $privkey = false) {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
dkim('delete', array('domains' => $domain));
|
dkim('delete', array('domains' => $domain));
|
||||||
$redis->hSet('DKIM_PUB_KEYS', $domain, $pem_public_key);
|
$valkey->hSet('DKIM_PUB_KEYS', $domain, $pem_public_key);
|
||||||
$redis->hSet('DKIM_SELECTORS', $domain, $dkim_selector);
|
$valkey->hSet('DKIM_SELECTORS', $domain, $dkim_selector);
|
||||||
$redis->hSet('DKIM_PRIV_KEYS', $dkim_selector . '.' . $domain, $private_key_normalized);
|
$valkey->hSet('DKIM_PRIV_KEYS', $dkim_selector . '.' . $domain, $private_key_normalized);
|
||||||
}
|
}
|
||||||
catch (RedisException $e) {
|
catch (RedisException $e) {
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'danger',
|
'type' => 'danger',
|
||||||
'log' => array(__FUNCTION__, $_action, $_data),
|
'log' => array(__FUNCTION__, $_action, $_data),
|
||||||
'msg' => array('redis_error', $e)
|
'msg' => array('valkey_error', $e)
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -219,7 +219,7 @@ function dkim($_action, $_data = null, $privkey = false) {
|
|||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'danger',
|
'type' => 'danger',
|
||||||
'log' => array(__FUNCTION__, $_action, $_data),
|
'log' => array(__FUNCTION__, $_action, $_data),
|
||||||
'msg' => array('redis_error', $e)
|
'msg' => array('valkey_error', $e)
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -235,8 +235,8 @@ function dkim($_action, $_data = null, $privkey = false) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
$dkimdata = array();
|
$dkimdata = array();
|
||||||
if ($redis_dkim_key_data = $redis->hGet('DKIM_PUB_KEYS', $_data)) {
|
if ($valkey_dkim_key_data = $valkey->hGet('DKIM_PUB_KEYS', $_data)) {
|
||||||
$dkimdata['pubkey'] = $redis_dkim_key_data;
|
$dkimdata['pubkey'] = $valkey_dkim_key_data;
|
||||||
if (strlen($dkimdata['pubkey']) < 391) {
|
if (strlen($dkimdata['pubkey']) < 391) {
|
||||||
$dkimdata['length'] = "1024";
|
$dkimdata['length'] = "1024";
|
||||||
}
|
}
|
||||||
@@ -253,15 +253,15 @@ function dkim($_action, $_data = null, $privkey = false) {
|
|||||||
$dkimdata['length'] = ">= 8192";
|
$dkimdata['length'] = ">= 8192";
|
||||||
}
|
}
|
||||||
if ($GLOBALS['SPLIT_DKIM_255'] === true) {
|
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 ) );
|
$dkimdata['dkim_txt'] = sprintf('"%s"', implode('" "', (array)$dkim_txt_tmp ) );
|
||||||
}
|
}
|
||||||
else {
|
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) {
|
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 {
|
else {
|
||||||
$dkimdata['privkey'] = '';
|
$dkimdata['privkey'] = '';
|
||||||
@@ -279,8 +279,8 @@ function dkim($_action, $_data = null, $privkey = false) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
$blinddkim = array();
|
$blinddkim = array();
|
||||||
foreach ($redis->hKeys('DKIM_PUB_KEYS') as $redis_dkim_domain) {
|
foreach ($valkey->hKeys('DKIM_PUB_KEYS') as $valkey_dkim_domain) {
|
||||||
$blinddkim[] = $redis_dkim_domain;
|
$blinddkim[] = $valkey_dkim_domain;
|
||||||
}
|
}
|
||||||
return array_diff($blinddkim, array_merge(mailbox('get', 'domains'), mailbox('get', 'alias_domains')));
|
return array_diff($blinddkim, array_merge(mailbox('get', 'domains'), mailbox('get', 'alias_domains')));
|
||||||
break;
|
break;
|
||||||
@@ -304,16 +304,16 @@ function dkim($_action, $_data = null, $privkey = false) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
$selector = $redis->hGet('DKIM_SELECTORS', $domain);
|
$selector = $valkey->hGet('DKIM_SELECTORS', $domain);
|
||||||
$redis->hDel('DKIM_PUB_KEYS', $domain);
|
$valkey->hDel('DKIM_PUB_KEYS', $domain);
|
||||||
$redis->hDel('DKIM_PRIV_KEYS', $selector . '.' . $domain);
|
$valkey->hDel('DKIM_PRIV_KEYS', $selector . '.' . $domain);
|
||||||
$redis->hDel('DKIM_SELECTORS', $domain);
|
$valkey->hDel('DKIM_SELECTORS', $domain);
|
||||||
}
|
}
|
||||||
catch (RedisException $e) {
|
catch (RedisException $e) {
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'danger',
|
'type' => 'danger',
|
||||||
'log' => array(__FUNCTION__, $_action, $_data),
|
'log' => array(__FUNCTION__, $_action, $_data),
|
||||||
'msg' => array('redis_error', $e)
|
'msg' => array('valkey_error', $e)
|
||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
function docker($action, $service_name = null, $attr1 = null, $attr2 = null, $extra_headers = null) {
|
function docker($action, $service_name = null, $attr1 = null, $attr2 = null, $extra_headers = null) {
|
||||||
global $DOCKER_TIMEOUT;
|
global $DOCKER_TIMEOUT;
|
||||||
global $redis;
|
global $valkey;
|
||||||
$curl = curl_init();
|
$curl = curl_init();
|
||||||
curl_setopt($curl, CURLOPT_HTTPHEADER,array('Content-Type: application/json' ));
|
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
|
// We are using our mail certificates for dockerapi, the names will not match, the certs are trusted anyway
|
||||||
@@ -63,7 +63,7 @@ function docker($action, $service_name = null, $attr1 = null, $attr2 = null, $ex
|
|||||||
break;
|
break;
|
||||||
case 'info':
|
case 'info':
|
||||||
if (empty($service_name)) {
|
if (empty($service_name)) {
|
||||||
curl_setopt($curl, CURLOPT_URL, 'https://dockerapi:443/containers/json?all=true');
|
curl_setopt($curl, CURLOPT_URL, 'https://dockerapi:443/containers/json');
|
||||||
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
|
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
|
||||||
curl_setopt($curl, CURLOPT_POST, 0);
|
curl_setopt($curl, CURLOPT_POST, 0);
|
||||||
curl_setopt($curl, CURLOPT_TIMEOUT, $DOCKER_TIMEOUT);
|
curl_setopt($curl, CURLOPT_TIMEOUT, $DOCKER_TIMEOUT);
|
||||||
@@ -200,7 +200,7 @@ function docker($action, $service_name = null, $attr1 = null, $attr2 = null, $ex
|
|||||||
"request" => $attr2
|
"request" => $attr2
|
||||||
);
|
);
|
||||||
|
|
||||||
$redis->publish("MC_CHANNEL", json_encode($request));
|
$valkey->publish("MC_CHANNEL", json_encode($request));
|
||||||
return true;
|
return true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -195,23 +195,17 @@ function domain_admin($_action, $_data = null) {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$force_tfa = intval($_data['force_tfa'] ?? 0) ? 1 : 0;
|
|
||||||
$force_pw_update = intval($_data['force_pw_update'] ?? 0) ? 1 : 0;
|
|
||||||
if (!empty($password)) {
|
if (!empty($password)) {
|
||||||
if (password_check($password, $password2) !== true) {
|
if (password_check($password, $password2) !== true) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
$password_hashed = hash_password($password);
|
$password_hashed = hash_password($password);
|
||||||
$stmt = $pdo->prepare("UPDATE `admin` SET `username` = :username_new, `active` = :active, `password` = :password_hashed,
|
$stmt = $pdo->prepare("UPDATE `admin` SET `username` = :username_new, `active` = :active, `password` = :password_hashed WHERE `username` = :username");
|
||||||
`attributes` = JSON_SET(COALESCE(`attributes`, '{}'), '$.force_tfa', :force_tfa, '$.force_pw_update', :force_pw_update)
|
|
||||||
WHERE `username` = :username");
|
|
||||||
$stmt->execute(array(
|
$stmt->execute(array(
|
||||||
':password_hashed' => $password_hashed,
|
':password_hashed' => $password_hashed,
|
||||||
':username_new' => $username_new,
|
':username_new' => $username_new,
|
||||||
':username' => $username,
|
':username' => $username,
|
||||||
':active' => $active,
|
':active' => $active
|
||||||
':force_tfa' => strval($force_tfa),
|
|
||||||
':force_pw_update' => strval($force_pw_update)
|
|
||||||
));
|
));
|
||||||
if (isset($_data['disable_tfa'])) {
|
if (isset($_data['disable_tfa'])) {
|
||||||
$stmt = $pdo->prepare("UPDATE `tfa` SET `active` = '0' WHERE `username` = :username");
|
$stmt = $pdo->prepare("UPDATE `tfa` SET `active` = '0' WHERE `username` = :username");
|
||||||
@@ -223,15 +217,11 @@ function domain_admin($_action, $_data = null) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$stmt = $pdo->prepare("UPDATE `admin` SET `username` = :username_new, `active` = :active,
|
$stmt = $pdo->prepare("UPDATE `admin` SET `username` = :username_new, `active` = :active WHERE `username` = :username");
|
||||||
`attributes` = JSON_SET(COALESCE(`attributes`, '{}'), '$.force_tfa', :force_tfa, '$.force_pw_update', :force_pw_update)
|
|
||||||
WHERE `username` = :username");
|
|
||||||
$stmt->execute(array(
|
$stmt->execute(array(
|
||||||
':username_new' => $username_new,
|
':username_new' => $username_new,
|
||||||
':username' => $username,
|
':username' => $username,
|
||||||
':active' => $active,
|
':active' => $active
|
||||||
':force_tfa' => strval($force_tfa),
|
|
||||||
':force_pw_update' => strval($force_pw_update)
|
|
||||||
));
|
));
|
||||||
if (isset($_data['disable_tfa'])) {
|
if (isset($_data['disable_tfa'])) {
|
||||||
$stmt = $pdo->prepare("UPDATE `tfa` SET `active` = '0' WHERE `username` = :username");
|
$stmt = $pdo->prepare("UPDATE `tfa` SET `active` = '0' WHERE `username` = :username");
|
||||||
@@ -254,37 +244,31 @@ function domain_admin($_action, $_data = null) {
|
|||||||
// Can only edit itself
|
// Can only edit itself
|
||||||
elseif ($_SESSION['mailcow_cc_role'] == "domainadmin") {
|
elseif ($_SESSION['mailcow_cc_role'] == "domainadmin") {
|
||||||
$username = $_SESSION['mailcow_cc_username'];
|
$username = $_SESSION['mailcow_cc_username'];
|
||||||
$password_old = $_data['user_old_pass'] ?? '';
|
$password_old = $_data['user_old_pass'];
|
||||||
$password_new = $_data['user_new_pass'];
|
$password_new = $_data['user_new_pass'];
|
||||||
$password_new2 = $_data['user_new_pass2'];
|
$password_new2 = $_data['user_new_pass2'];
|
||||||
|
|
||||||
// Only verify old password if this is NOT a forced password update
|
$stmt = $pdo->prepare("SELECT `password` FROM `admin`
|
||||||
if (empty($_SESSION['pending_pw_update'])) {
|
WHERE `username` = :user");
|
||||||
$stmt = $pdo->prepare("SELECT `password` FROM `admin`
|
$stmt->execute(array(':user' => $username));
|
||||||
WHERE `username` = :user");
|
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
$stmt->execute(array(':user' => $username));
|
if (!verify_hash($row['password'], $password_old)) {
|
||||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
$_SESSION['return'][] = array(
|
||||||
if (!verify_hash($row['password'], $password_old)) {
|
'type' => 'danger',
|
||||||
$_SESSION['return'][] = array(
|
'log' => array(__FUNCTION__, $_action, $_data_log),
|
||||||
'type' => 'danger',
|
'msg' => 'access_denied'
|
||||||
'log' => array(__FUNCTION__, $_action, $_data_log),
|
);
|
||||||
'msg' => 'access_denied'
|
return false;
|
||||||
);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (password_check($password_new, $password_new2) !== true) {
|
if (password_check($password_new, $password_new2) !== true) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
$password_hashed = hash_password($password_new);
|
$password_hashed = hash_password($password_new);
|
||||||
$stmt = $pdo->prepare("UPDATE `admin` SET `password` = :password_hashed,
|
$stmt = $pdo->prepare("UPDATE `admin` SET `password` = :password_hashed WHERE `username` = :username");
|
||||||
`attributes` = JSON_SET(COALESCE(`attributes`, '{}'), '$.force_pw_update', '0')
|
|
||||||
WHERE `username` = :username");
|
|
||||||
$stmt->execute(array(
|
$stmt->execute(array(
|
||||||
':password_hashed' => $password_hashed,
|
':password_hashed' => $password_hashed,
|
||||||
':username' => $username
|
':username' => $username
|
||||||
));
|
));
|
||||||
unset($_SESSION['pending_pw_update']);
|
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'success',
|
'type' => 'success',
|
||||||
'log' => array(__FUNCTION__, $_action, $_data_log),
|
'log' => array(__FUNCTION__, $_action, $_data_log),
|
||||||
@@ -376,11 +360,9 @@ function domain_admin($_action, $_data = null) {
|
|||||||
`tfa`.`active` AS `tfa_active`,
|
`tfa`.`active` AS `tfa_active`,
|
||||||
`domain_admins`.`username`,
|
`domain_admins`.`username`,
|
||||||
`domain_admins`.`created`,
|
`domain_admins`.`created`,
|
||||||
`domain_admins`.`active` AS `active`,
|
`domain_admins`.`active` AS `active`
|
||||||
`admin`.`attributes` AS `attributes`
|
|
||||||
FROM `domain_admins`
|
FROM `domain_admins`
|
||||||
LEFT OUTER JOIN `tfa` ON `tfa`.`username`=`domain_admins`.`username`
|
LEFT OUTER JOIN `tfa` ON `tfa`.`username`=`domain_admins`.`username`
|
||||||
LEFT OUTER JOIN `admin` ON `admin`.`username`=`domain_admins`.`username`
|
|
||||||
WHERE `domain_admins`.`username`= :domain_admin");
|
WHERE `domain_admins`.`username`= :domain_admin");
|
||||||
$stmt->execute(array(
|
$stmt->execute(array(
|
||||||
':domain_admin' => $_data
|
':domain_admin' => $_data
|
||||||
@@ -395,7 +377,6 @@ function domain_admin($_action, $_data = null) {
|
|||||||
$domainadmindata['active'] = $row['active'];
|
$domainadmindata['active'] = $row['active'];
|
||||||
$domainadmindata['active_int'] = $row['active'];
|
$domainadmindata['active_int'] = $row['active'];
|
||||||
$domainadmindata['created'] = $row['created'];
|
$domainadmindata['created'] = $row['created'];
|
||||||
$domainadmindata['attributes'] = json_decode($row['attributes'], true) ?? array('force_tfa' => '0', 'force_pw_update' => '0');
|
|
||||||
// GET SELECTED
|
// GET SELECTED
|
||||||
$stmt = $pdo->prepare("SELECT `domain` FROM `domain`
|
$stmt = $pdo->prepare("SELECT `domain` FROM `domain`
|
||||||
WHERE `domain` IN (
|
WHERE `domain` IN (
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
function fail2ban($_action, $_data = null, $_extra = null) {
|
function fail2ban($_action, $_data = null, $_extra = null) {
|
||||||
global $redis;
|
global $valkey;
|
||||||
$_data_log = $_data;
|
$_data_log = $_data;
|
||||||
switch ($_action) {
|
switch ($_action) {
|
||||||
case 'get':
|
case 'get':
|
||||||
@@ -9,9 +9,9 @@ function fail2ban($_action, $_data = null, $_extra = null) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
$f2b_options = json_decode($redis->Get('F2B_OPTIONS'), true);
|
$f2b_options = json_decode($valkey->Get('F2B_OPTIONS'), true);
|
||||||
$f2b_options['regex'] = json_decode($redis->Get('F2B_REGEX'), true);
|
$f2b_options['regex'] = json_decode($valkey->Get('F2B_REGEX'), true);
|
||||||
$wl = $redis->hGetAll('F2B_WHITELIST');
|
$wl = $valkey->hGetAll('F2B_WHITELIST');
|
||||||
if (is_array($wl)) {
|
if (is_array($wl)) {
|
||||||
foreach ($wl as $key => $value) {
|
foreach ($wl as $key => $value) {
|
||||||
$tmp_wl_data[] = $key;
|
$tmp_wl_data[] = $key;
|
||||||
@@ -27,7 +27,7 @@ function fail2ban($_action, $_data = null, $_extra = null) {
|
|||||||
else {
|
else {
|
||||||
$f2b_options['whitelist'] = "";
|
$f2b_options['whitelist'] = "";
|
||||||
}
|
}
|
||||||
$bl = $redis->hGetAll('F2B_BLACKLIST');
|
$bl = $valkey->hGetAll('F2B_BLACKLIST');
|
||||||
if (is_array($bl)) {
|
if (is_array($bl)) {
|
||||||
foreach ($bl as $key => $value) {
|
foreach ($bl as $key => $value) {
|
||||||
$tmp_bl_data[] = $key;
|
$tmp_bl_data[] = $key;
|
||||||
@@ -43,7 +43,7 @@ function fail2ban($_action, $_data = null, $_extra = null) {
|
|||||||
else {
|
else {
|
||||||
$f2b_options['blacklist'] = "";
|
$f2b_options['blacklist'] = "";
|
||||||
}
|
}
|
||||||
$pb = $redis->hGetAll('F2B_PERM_BANS');
|
$pb = $valkey->hGetAll('F2B_PERM_BANS');
|
||||||
if (is_array($pb)) {
|
if (is_array($pb)) {
|
||||||
foreach ($pb as $key => $value) {
|
foreach ($pb as $key => $value) {
|
||||||
$f2b_options['perm_bans'][] = array(
|
$f2b_options['perm_bans'][] = array(
|
||||||
@@ -56,8 +56,8 @@ function fail2ban($_action, $_data = null, $_extra = null) {
|
|||||||
else {
|
else {
|
||||||
$f2b_options['perm_bans'] = "";
|
$f2b_options['perm_bans'] = "";
|
||||||
}
|
}
|
||||||
$active_bans = $redis->hGetAll('F2B_ACTIVE_BANS');
|
$active_bans = $valkey->hGetAll('F2B_ACTIVE_BANS');
|
||||||
$queue_unban = $redis->hGetAll('F2B_QUEUE_UNBAN');
|
$queue_unban = $valkey->hGetAll('F2B_QUEUE_UNBAN');
|
||||||
if (is_array($active_bans)) {
|
if (is_array($active_bans)) {
|
||||||
foreach ($active_bans as $network => $banned_until) {
|
foreach ($active_bans as $network => $banned_until) {
|
||||||
$queued_for_unban = (isset($queue_unban[$network]) && $queue_unban[$network] == 1) ? 1 : 0;
|
$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(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'danger',
|
'type' => 'danger',
|
||||||
'log' => array(__FUNCTION__, $_action, $_data_log),
|
'log' => array(__FUNCTION__, $_action, $_data_log),
|
||||||
'msg' => array('redis_error', $e)
|
'msg' => array('valkey_error', $e)
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -98,22 +98,22 @@ function fail2ban($_action, $_data = null, $_extra = null) {
|
|||||||
// Reset regex filters
|
// Reset regex filters
|
||||||
if ($_data['action'] == "reset-regex") {
|
if ($_data['action'] == "reset-regex") {
|
||||||
try {
|
try {
|
||||||
$redis->Del('F2B_REGEX');
|
$valkey->Del('F2B_REGEX');
|
||||||
}
|
}
|
||||||
catch (RedisException $e) {
|
catch (RedisException $e) {
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'danger',
|
'type' => 'danger',
|
||||||
'log' => array(__FUNCTION__, $_action, $_data_log),
|
'log' => array(__FUNCTION__, $_action, $_data_log),
|
||||||
'msg' => array('redis_error', $e)
|
'msg' => array('valkey_error', $e)
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// Rules will also be recreated on log events, but rules may seem empty for a second in the UI
|
// Rules will also be recreated on log events, but rules may seem empty for a second in the UI
|
||||||
docker('post', 'netfilter-mailcow', 'restart');
|
docker('post', 'netfilter-mailcow', 'restart');
|
||||||
$fail_count = 0;
|
$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) {
|
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++;
|
$fail_count++;
|
||||||
sleep(1);
|
sleep(1);
|
||||||
}
|
}
|
||||||
@@ -135,7 +135,7 @@ function fail2ban($_action, $_data = null, $_extra = null) {
|
|||||||
$rule_id++;
|
$rule_id++;
|
||||||
}
|
}
|
||||||
if (!empty($regex_array)) {
|
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(
|
$_SESSION['return'][] = array(
|
||||||
@@ -154,13 +154,13 @@ function fail2ban($_action, $_data = null, $_extra = null) {
|
|||||||
if ($_data['action'] == "unban") {
|
if ($_data['action'] == "unban") {
|
||||||
if (valid_network($network)) {
|
if (valid_network($network)) {
|
||||||
try {
|
try {
|
||||||
$redis->hSet('F2B_QUEUE_UNBAN', $network, 1);
|
$valkey->hSet('F2B_QUEUE_UNBAN', $network, 1);
|
||||||
}
|
}
|
||||||
catch (RedisException $e) {
|
catch (RedisException $e) {
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'danger',
|
'type' => 'danger',
|
||||||
'log' => array(__FUNCTION__, $_action, $_data_log),
|
'log' => array(__FUNCTION__, $_action, $_data_log),
|
||||||
'msg' => array('redis_error', $e)
|
'msg' => array('valkey_error', $e)
|
||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -171,15 +171,15 @@ function fail2ban($_action, $_data = null, $_extra = null) {
|
|||||||
if (empty($network)) { continue; }
|
if (empty($network)) { continue; }
|
||||||
if (valid_network($network)) {
|
if (valid_network($network)) {
|
||||||
try {
|
try {
|
||||||
$redis->hSet('F2B_WHITELIST', $network, 1);
|
$valkey->hSet('F2B_WHITELIST', $network, 1);
|
||||||
$redis->hDel('F2B_BLACKLIST', $network, 1);
|
$valkey->hDel('F2B_BLACKLIST', $network, 1);
|
||||||
$redis->hSet('F2B_QUEUE_UNBAN', $network, 1);
|
$valkey->hSet('F2B_QUEUE_UNBAN', $network, 1);
|
||||||
}
|
}
|
||||||
catch (RedisException $e) {
|
catch (RedisException $e) {
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'danger',
|
'type' => 'danger',
|
||||||
'log' => array(__FUNCTION__, $_action, $_data_log),
|
'log' => array(__FUNCTION__, $_action, $_data_log),
|
||||||
'msg' => array('redis_error', $e)
|
'msg' => array('valkey_error', $e)
|
||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -204,15 +204,15 @@ function fail2ban($_action, $_data = null, $_extra = null) {
|
|||||||
getenv('IPV6_NETWORK')
|
getenv('IPV6_NETWORK')
|
||||||
))) {
|
))) {
|
||||||
try {
|
try {
|
||||||
$redis->hSet('F2B_BLACKLIST', $network, 1);
|
$valkey->hSet('F2B_BLACKLIST', $network, 1);
|
||||||
$redis->hDel('F2B_WHITELIST', $network, 1);
|
$valkey->hDel('F2B_WHITELIST', $network, 1);
|
||||||
//$response = docker('post', 'netfilter-mailcow', 'restart');
|
//$response = docker('post', 'netfilter-mailcow', 'restart');
|
||||||
}
|
}
|
||||||
catch (RedisException $e) {
|
catch (RedisException $e) {
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'danger',
|
'type' => 'danger',
|
||||||
'log' => array(__FUNCTION__, $_action, $_data_log),
|
'log' => array(__FUNCTION__, $_action, $_data_log),
|
||||||
'msg' => array('redis_error', $e)
|
'msg' => array('valkey_error', $e)
|
||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -270,16 +270,16 @@ function fail2ban($_action, $_data = null, $_extra = null) {
|
|||||||
$f2b_options['banlist_id'] = $is_now['banlist_id'];
|
$f2b_options['banlist_id'] = $is_now['banlist_id'];
|
||||||
$f2b_options['manage_external'] = ($manage_external > 0) ? 1 : 0;
|
$f2b_options['manage_external'] = ($manage_external > 0) ? 1 : 0;
|
||||||
try {
|
try {
|
||||||
$redis->Set('F2B_OPTIONS', json_encode($f2b_options));
|
$valkey->Set('F2B_OPTIONS', json_encode($f2b_options));
|
||||||
$redis->Del('F2B_WHITELIST');
|
$valkey->Del('F2B_WHITELIST');
|
||||||
$redis->Del('F2B_BLACKLIST');
|
$valkey->Del('F2B_BLACKLIST');
|
||||||
if(!empty($wl)) {
|
if(!empty($wl)) {
|
||||||
$wl_array = array_map('trim', preg_split( "/( |,|;|\n)/", $wl));
|
$wl_array = array_map('trim', preg_split( "/( |,|;|\n)/", $wl));
|
||||||
$wl_array = array_filter($wl_array);
|
$wl_array = array_filter($wl_array);
|
||||||
if (is_array($wl_array)) {
|
if (is_array($wl_array)) {
|
||||||
foreach ($wl_array as $wl_item) {
|
foreach ($wl_array as $wl_item) {
|
||||||
if (valid_network($wl_item) || valid_hostname($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 {
|
else {
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
@@ -304,7 +304,7 @@ function fail2ban($_action, $_data = null, $_extra = null) {
|
|||||||
getenv('IPV4_NETWORK') . '0',
|
getenv('IPV4_NETWORK') . '0',
|
||||||
getenv('IPV6_NETWORK')
|
getenv('IPV6_NETWORK')
|
||||||
))) {
|
))) {
|
||||||
$redis->hSet('F2B_BLACKLIST', $bl_item, 1);
|
$valkey->hSet('F2B_BLACKLIST', $bl_item, 1);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
@@ -322,7 +322,7 @@ function fail2ban($_action, $_data = null, $_extra = null) {
|
|||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'danger',
|
'type' => 'danger',
|
||||||
'log' => array(__FUNCTION__, $_action, $_data_log),
|
'log' => array(__FUNCTION__, $_action, $_data_log),
|
||||||
'msg' => array('redis_error', $e)
|
'msg' => array('valkey_error', $e)
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -334,13 +334,13 @@ function fail2ban($_action, $_data = null, $_extra = null) {
|
|||||||
break;
|
break;
|
||||||
case 'banlist':
|
case 'banlist':
|
||||||
try {
|
try {
|
||||||
$f2b_options = json_decode($redis->Get('F2B_OPTIONS'), true);
|
$f2b_options = json_decode($valkey->Get('F2B_OPTIONS'), true);
|
||||||
}
|
}
|
||||||
catch (RedisException $e) {
|
catch (RedisException $e) {
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'danger',
|
'type' => 'danger',
|
||||||
'log' => array(__FUNCTION__, $_action, $_data_log, $_extra),
|
'log' => array(__FUNCTION__, $_action, $_data_log, $_extra),
|
||||||
'msg' => array('redis_error', $e)
|
'msg' => array('valkey_error', $e)
|
||||||
);
|
);
|
||||||
http_response_code(500);
|
http_response_code(500);
|
||||||
return false;
|
return false;
|
||||||
@@ -356,14 +356,14 @@ function fail2ban($_action, $_data = null, $_extra = null) {
|
|||||||
switch ($_data) {
|
switch ($_data) {
|
||||||
case 'get':
|
case 'get':
|
||||||
try {
|
try {
|
||||||
$bl = $redis->hKeys('F2B_BLACKLIST');
|
$bl = $valkey->hKeys('F2B_BLACKLIST');
|
||||||
$active_bans = $redis->hKeys('F2B_ACTIVE_BANS');
|
$active_bans = $valkey->hKeys('F2B_ACTIVE_BANS');
|
||||||
}
|
}
|
||||||
catch (RedisException $e) {
|
catch (RedisException $e) {
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'danger',
|
'type' => 'danger',
|
||||||
'log' => array(__FUNCTION__, $_action, $_data_log, $_extra),
|
'log' => array(__FUNCTION__, $_action, $_data_log, $_extra),
|
||||||
'msg' => array('redis_error', $e)
|
'msg' => array('valkey_error', $e)
|
||||||
);
|
);
|
||||||
http_response_code(500);
|
http_response_code(500);
|
||||||
return false;
|
return false;
|
||||||
@@ -378,13 +378,13 @@ function fail2ban($_action, $_data = null, $_extra = null) {
|
|||||||
|
|
||||||
$f2b_options['banlist_id'] = uuid4();
|
$f2b_options['banlist_id'] = uuid4();
|
||||||
try {
|
try {
|
||||||
$redis->Set('F2B_OPTIONS', json_encode($f2b_options));
|
$valkey->Set('F2B_OPTIONS', json_encode($f2b_options));
|
||||||
}
|
}
|
||||||
catch (RedisException $e) {
|
catch (RedisException $e) {
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'danger',
|
'type' => 'danger',
|
||||||
'log' => array(__FUNCTION__, $_action, $_data_log, $_extra),
|
'log' => array(__FUNCTION__, $_action, $_data_log, $_extra),
|
||||||
'msg' => array('redis_error', $e)
|
'msg' => array('valkey_error', $e)
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
function fwdhost($_action, $_data = null) {
|
function fwdhost($_action, $_data = null) {
|
||||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/spf.inc.php';
|
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/spf.inc.php';
|
||||||
global $redis;
|
global $valkey;
|
||||||
global $lang;
|
global $lang;
|
||||||
$_data_log = $_data;
|
$_data_log = $_data;
|
||||||
switch ($_action) {
|
switch ($_action) {
|
||||||
@@ -37,19 +37,19 @@ function fwdhost($_action, $_data = null) {
|
|||||||
}
|
}
|
||||||
foreach ($hosts as $host) {
|
foreach ($hosts as $host) {
|
||||||
try {
|
try {
|
||||||
$redis->hSet('WHITELISTED_FWD_HOST', $host, $source);
|
$valkey->hSet('WHITELISTED_FWD_HOST', $host, $source);
|
||||||
if ($filter_spam == 0) {
|
if ($filter_spam == 0) {
|
||||||
$redis->hSet('KEEP_SPAM', $host, 1);
|
$valkey->hSet('KEEP_SPAM', $host, 1);
|
||||||
}
|
}
|
||||||
elseif ($redis->hGet('KEEP_SPAM', $host)) {
|
elseif ($valkey->hGet('KEEP_SPAM', $host)) {
|
||||||
$redis->hDel('KEEP_SPAM', $host);
|
$valkey->hDel('KEEP_SPAM', $host);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (RedisException $e) {
|
catch (RedisException $e) {
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'danger',
|
'type' => 'danger',
|
||||||
'log' => array(__FUNCTION__, $_action, $_data_log),
|
'log' => array(__FUNCTION__, $_action, $_data_log),
|
||||||
'msg' => array('redis_error', $e)
|
'msg' => array('valkey_error', $e)
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -86,17 +86,17 @@ function fwdhost($_action, $_data = null) {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
if ($keep_spam == 1) {
|
if ($keep_spam == 1) {
|
||||||
$redis->hSet('KEEP_SPAM', $fwdhost, 1);
|
$valkey->hSet('KEEP_SPAM', $fwdhost, 1);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$redis->hDel('KEEP_SPAM', $fwdhost);
|
$valkey->hDel('KEEP_SPAM', $fwdhost);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (RedisException $e) {
|
catch (RedisException $e) {
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'danger',
|
'type' => 'danger',
|
||||||
'log' => array(__FUNCTION__, $_action, $_data_log),
|
'log' => array(__FUNCTION__, $_action, $_data_log),
|
||||||
'msg' => array('redis_error', $e)
|
'msg' => array('valkey_error', $e)
|
||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -111,14 +111,14 @@ function fwdhost($_action, $_data = null) {
|
|||||||
$hosts = (array)$_data['forwardinghost'];
|
$hosts = (array)$_data['forwardinghost'];
|
||||||
foreach ($hosts as $host) {
|
foreach ($hosts as $host) {
|
||||||
try {
|
try {
|
||||||
$redis->hDel('WHITELISTED_FWD_HOST', $host);
|
$valkey->hDel('WHITELISTED_FWD_HOST', $host);
|
||||||
$redis->hDel('KEEP_SPAM', $host);
|
$valkey->hDel('KEEP_SPAM', $host);
|
||||||
}
|
}
|
||||||
catch (RedisException $e) {
|
catch (RedisException $e) {
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'danger',
|
'type' => 'danger',
|
||||||
'log' => array(__FUNCTION__, $_action, $_data_log),
|
'log' => array(__FUNCTION__, $_action, $_data_log),
|
||||||
'msg' => array('redis_error', $e)
|
'msg' => array('valkey_error', $e)
|
||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -135,10 +135,10 @@ function fwdhost($_action, $_data = null) {
|
|||||||
}
|
}
|
||||||
$fwdhostsdata = array();
|
$fwdhostsdata = array();
|
||||||
try {
|
try {
|
||||||
$fwd_hosts = $redis->hGetAll('WHITELISTED_FWD_HOST');
|
$fwd_hosts = $valkey->hGetAll('WHITELISTED_FWD_HOST');
|
||||||
if (!empty($fwd_hosts)) {
|
if (!empty($fwd_hosts)) {
|
||||||
foreach ($fwd_hosts as $fwd_host => $source) {
|
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(
|
$fwdhostsdata[] = array(
|
||||||
'host' => $fwd_host,
|
'host' => $fwd_host,
|
||||||
'source' => $source,
|
'source' => $source,
|
||||||
@@ -151,7 +151,7 @@ function fwdhost($_action, $_data = null) {
|
|||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'danger',
|
'type' => 'danger',
|
||||||
'log' => array(__FUNCTION__, $_action, $_data_log),
|
'log' => array(__FUNCTION__, $_action, $_data_log),
|
||||||
'msg' => array('redis_error', $e)
|
'msg' => array('valkey_error', $e)
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -163,17 +163,17 @@ function fwdhost($_action, $_data = null) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
if ($source = $redis->hGet('WHITELISTED_FWD_HOST', $_data)) {
|
if ($source = $valkey->hGet('WHITELISTED_FWD_HOST', $_data)) {
|
||||||
$fwdhostdetails['host'] = $_data;
|
$fwdhostdetails['host'] = $_data;
|
||||||
$fwdhostdetails['source'] = $source;
|
$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) {
|
catch (RedisException $e) {
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'danger',
|
'type' => 'danger',
|
||||||
'log' => array(__FUNCTION__, $_action, $_data_log),
|
'log' => array(__FUNCTION__, $_action, $_data_log),
|
||||||
'msg' => array('redis_error', $e)
|
'msg' => array('valkey_error', $e)
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ function hash_password($password) {
|
|||||||
return $pw_hash;
|
return $pw_hash;
|
||||||
}
|
}
|
||||||
function password_complexity($_action, $_data = null) {
|
function password_complexity($_action, $_data = null) {
|
||||||
global $redis;
|
global $valkey;
|
||||||
global $lang;
|
global $lang;
|
||||||
switch ($_action) {
|
switch ($_action) {
|
||||||
case 'edit':
|
case 'edit':
|
||||||
@@ -147,7 +147,7 @@ function password_complexity($_action, $_data = null) {
|
|||||||
$numbers = (isset($_data['numbers'])) ? intval($_data['numbers']) : $is_now['numbers'];
|
$numbers = (isset($_data['numbers'])) ? intval($_data['numbers']) : $is_now['numbers'];
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
$redis->hMSet('PASSWD_POLICY', [
|
$valkey->hMSet('PASSWD_POLICY', [
|
||||||
'length' => $length,
|
'length' => $length,
|
||||||
'chars' => $chars,
|
'chars' => $chars,
|
||||||
'special_chars' => $special_chars,
|
'special_chars' => $special_chars,
|
||||||
@@ -159,7 +159,7 @@ function password_complexity($_action, $_data = null) {
|
|||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'danger',
|
'type' => 'danger',
|
||||||
'log' => array(__FUNCTION__, $_action, $_data),
|
'log' => array(__FUNCTION__, $_action, $_data),
|
||||||
'msg' => array('redis_error', $e)
|
'msg' => array('valkey_error', $e)
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -171,11 +171,11 @@ function password_complexity($_action, $_data = null) {
|
|||||||
break;
|
break;
|
||||||
case 'get':
|
case 'get':
|
||||||
try {
|
try {
|
||||||
$length = $redis->hGet('PASSWD_POLICY', 'length');
|
$length = $valkey->hGet('PASSWD_POLICY', 'length');
|
||||||
$chars = $redis->hGet('PASSWD_POLICY', 'chars');
|
$chars = $valkey->hGet('PASSWD_POLICY', 'chars');
|
||||||
$special_chars = $redis->hGet('PASSWD_POLICY', 'special_chars');
|
$special_chars = $valkey->hGet('PASSWD_POLICY', 'special_chars');
|
||||||
$lowerupper = $redis->hGet('PASSWD_POLICY', 'lowerupper');
|
$lowerupper = $valkey->hGet('PASSWD_POLICY', 'lowerupper');
|
||||||
$numbers = $redis->hGet('PASSWD_POLICY', 'numbers');
|
$numbers = $valkey->hGet('PASSWD_POLICY', 'numbers');
|
||||||
return array(
|
return array(
|
||||||
'length' => $length,
|
'length' => $length,
|
||||||
'chars' => $chars,
|
'chars' => $chars,
|
||||||
@@ -188,7 +188,7 @@ function password_complexity($_action, $_data = null) {
|
|||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'danger',
|
'type' => 'danger',
|
||||||
'log' => array(__FUNCTION__, $_action, $_data),
|
'log' => array(__FUNCTION__, $_action, $_data),
|
||||||
'msg' => array('redis_error', $e)
|
'msg' => array('valkey_error', $e)
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -205,42 +205,6 @@ function password_complexity($_action, $_data = null) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function password_generate(){
|
|
||||||
$password_complexity = password_complexity('get');
|
|
||||||
$min_length = max(16, intval($password_complexity['length']));
|
|
||||||
|
|
||||||
$lowercase = range('a', 'z');
|
|
||||||
$uppercase = range('A', 'Z');
|
|
||||||
$digits = range(0, 9);
|
|
||||||
$special_chars = str_split('!@#$%^&*()?=');
|
|
||||||
|
|
||||||
$password = [
|
|
||||||
$lowercase[random_int(0, count($lowercase) - 1)],
|
|
||||||
$uppercase[random_int(0, count($uppercase) - 1)],
|
|
||||||
$digits[random_int(0, count($digits) - 1)],
|
|
||||||
$special_chars[random_int(0, count($special_chars) - 1)],
|
|
||||||
];
|
|
||||||
|
|
||||||
$all = array_merge($lowercase, $uppercase, $digits, $special_chars);
|
|
||||||
|
|
||||||
while (count($password) < $min_length) {
|
|
||||||
$password[] = $all[random_int(0, count($all) - 1)];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cryptographically secure shuffle using Fisher-Yates algorithm
|
|
||||||
$count = count($password);
|
|
||||||
for ($i = $count - 1; $i > 0; $i--) {
|
|
||||||
$j = random_int(0, $i);
|
|
||||||
$temp = $password[$i];
|
|
||||||
$password[$i] = $password[$j];
|
|
||||||
$password[$j] = $temp;
|
|
||||||
}
|
|
||||||
|
|
||||||
return implode('', $password);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function password_check($password1, $password2) {
|
function password_check($password1, $password2) {
|
||||||
$password_complexity = password_complexity('get');
|
$password_complexity = password_complexity('get');
|
||||||
|
|
||||||
@@ -289,7 +253,7 @@ function password_check($password1, $password2) {
|
|||||||
}
|
}
|
||||||
function last_login($action, $username, $sasl_limit_days = 7, $ui_offset = 1) {
|
function last_login($action, $username, $sasl_limit_days = 7, $ui_offset = 1) {
|
||||||
global $pdo;
|
global $pdo;
|
||||||
global $redis;
|
global $valkey;
|
||||||
$sasl_limit_days = intval($sasl_limit_days);
|
$sasl_limit_days = intval($sasl_limit_days);
|
||||||
switch ($action) {
|
switch ($action) {
|
||||||
case 'get':
|
case 'get':
|
||||||
@@ -308,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)) {
|
elseif (filter_var($sasl[$k]['real_rip'], FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
|
||||||
try {
|
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) {
|
catch (RedisException $e) {
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'danger',
|
'type' => 'danger',
|
||||||
'log' => array(__FUNCTION__, $_action, $_data_log),
|
'log' => array(__FUNCTION__, $_action, $_data_log),
|
||||||
'msg' => array('redis_error', $e)
|
'msg' => array('valkey_error', $e)
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -330,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'])) {
|
if ($ip_data_array !== false and !empty($ip_data_array['shortcountry'])) {
|
||||||
$sasl[$k]['location'] = $ip_data_array['shortcountry'];
|
$sasl[$k]['location'] = $ip_data_array['shortcountry'];
|
||||||
try {
|
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) {
|
catch (RedisException $e) {
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'danger',
|
'type' => 'danger',
|
||||||
'log' => array(__FUNCTION__, $_action, $_data_log),
|
'log' => array(__FUNCTION__, $_action, $_data_log),
|
||||||
'msg' => array('redis_error', $e)
|
'msg' => array('valkey_error', $e)
|
||||||
);
|
);
|
||||||
curl_close($curl);
|
curl_close($curl);
|
||||||
return false;
|
return false;
|
||||||
@@ -850,32 +814,6 @@ function verify_hash($hash, $password) {
|
|||||||
$hash = $components[4];
|
$hash = $components[4];
|
||||||
return hash_equals(hash_pbkdf2('sha1', $password, $salt, $rounds), $hash);
|
return hash_equals(hash_pbkdf2('sha1', $password, $salt, $rounds), $hash);
|
||||||
|
|
||||||
case "PBKDF2-SHA512":
|
|
||||||
// Handle FreeIPA-style hash: {PBKDF2-SHA512}10000$<base64_salt>$<base64_hash>
|
|
||||||
$components = explode('$', $hash);
|
|
||||||
if (count($components) !== 3) return false;
|
|
||||||
|
|
||||||
// 1st part: iteration count (integer)
|
|
||||||
$iterations = intval($components[0]);
|
|
||||||
if ($iterations <= 0) return false;
|
|
||||||
|
|
||||||
// 2nd part: salt (base64-encoded)
|
|
||||||
$salt = $components[1];
|
|
||||||
// 3rd part: hash (base64-encoded)
|
|
||||||
$stored_hash_b64 = $components[2];
|
|
||||||
|
|
||||||
// Decode salt and hash from base64
|
|
||||||
$salt_bin = base64_decode($salt, true);
|
|
||||||
$hash_bin = base64_decode($stored_hash_b64, true);
|
|
||||||
if ($salt_bin === false || $hash_bin === false) return false;
|
|
||||||
// Get length of hash in bytes
|
|
||||||
$hash_len = strlen($hash_bin);
|
|
||||||
if ($hash_len === 0) return false;
|
|
||||||
|
|
||||||
// Calculate PBKDF2-SHA512 hash for provided password
|
|
||||||
$test_hash = hash_pbkdf2('sha512', $password, $salt_bin, $iterations, $hash_len, true);
|
|
||||||
return hash_equals($hash_bin, $test_hash);
|
|
||||||
|
|
||||||
case "PLAIN-MD4":
|
case "PLAIN-MD4":
|
||||||
return hash_equals(hash('md4', $password), $hash);
|
return hash_equals(hash('md4', $password), $hash);
|
||||||
|
|
||||||
@@ -1033,24 +971,20 @@ function edit_user_account($_data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// edit password
|
// edit password
|
||||||
$is_forced_pw_update = !empty($_SESSION['pending_pw_update']);
|
if (!empty($password_old) && !empty($_data['user_new_pass']) && !empty($_data['user_new_pass2'])) {
|
||||||
if (((!empty($password_old) || $is_forced_pw_update) && !empty($_data['user_new_pass']) && !empty($_data['user_new_pass2']))) {
|
$stmt = $pdo->prepare("SELECT `password` FROM `mailbox`
|
||||||
// Only verify old password if this is NOT a forced password update
|
WHERE `kind` NOT REGEXP 'location|thing|group'
|
||||||
if (!$is_forced_pw_update) {
|
AND `username` = :user AND authsource = 'mailcow'");
|
||||||
$stmt = $pdo->prepare("SELECT `password` FROM `mailbox`
|
$stmt->execute(array(':user' => $username));
|
||||||
WHERE `kind` NOT REGEXP 'location|thing|group'
|
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
AND `username` = :user AND authsource = 'mailcow'");
|
|
||||||
$stmt->execute(array(':user' => $username));
|
|
||||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
||||||
|
|
||||||
if (!verify_hash($row['password'], $password_old)) {
|
if (!verify_hash($row['password'], $password_old)) {
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'danger',
|
'type' => 'danger',
|
||||||
'log' => array(__FUNCTION__, $_data_log),
|
'log' => array(__FUNCTION__, $_data_log),
|
||||||
'msg' => 'access_denied'
|
'msg' => 'access_denied'
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$password_new = $_data['user_new_pass'];
|
$password_new = $_data['user_new_pass'];
|
||||||
@@ -1072,7 +1006,7 @@ function edit_user_account($_data) {
|
|||||||
update_sogo_static_view();
|
update_sogo_static_view();
|
||||||
}
|
}
|
||||||
// edit password recovery email
|
// edit password recovery email
|
||||||
elseif (!empty($password_old) && isset($pw_recovery_email)) {
|
elseif (isset($pw_recovery_email)) {
|
||||||
if (!isset($_SESSION['acl']['pw_reset']) || $_SESSION['acl']['pw_reset'] != "1" ) {
|
if (!isset($_SESSION['acl']['pw_reset']) || $_SESSION['acl']['pw_reset'] != "1" ) {
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'danger',
|
'type' => 'danger',
|
||||||
@@ -1082,21 +1016,6 @@ function edit_user_account($_data) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$stmt = $pdo->prepare("SELECT `password` FROM `mailbox`
|
|
||||||
WHERE `kind` NOT REGEXP 'location|thing|group'
|
|
||||||
AND `username` = :user AND authsource = 'mailcow'");
|
|
||||||
$stmt->execute(array(':user' => $username));
|
|
||||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
||||||
|
|
||||||
if (!verify_hash($row['password'], $password_old)) {
|
|
||||||
$_SESSION['return'][] = array(
|
|
||||||
'type' => 'danger',
|
|
||||||
'log' => array(__FUNCTION__, $_data_log),
|
|
||||||
'msg' => 'access_denied'
|
|
||||||
);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
$pw_recovery_email = (!filter_var($pw_recovery_email, FILTER_VALIDATE_EMAIL)) ? '' : $pw_recovery_email;
|
$pw_recovery_email = (!filter_var($pw_recovery_email, FILTER_VALIDATE_EMAIL)) ? '' : $pw_recovery_email;
|
||||||
$stmt = $pdo->prepare("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.recovery_email', :recovery_email)
|
$stmt = $pdo->prepare("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.recovery_email', :recovery_email)
|
||||||
WHERE `username` = :username AND authsource = 'mailcow'");
|
WHERE `username` = :username AND authsource = 'mailcow'");
|
||||||
@@ -1214,52 +1133,50 @@ function set_tfa($_data) {
|
|||||||
global $iam_settings;
|
global $iam_settings;
|
||||||
|
|
||||||
$_data_log = $_data;
|
$_data_log = $_data;
|
||||||
|
$access_denied = null;
|
||||||
!isset($_data_log['confirm_password']) ?: $_data_log['confirm_password'] = '*';
|
!isset($_data_log['confirm_password']) ?: $_data_log['confirm_password'] = '*';
|
||||||
|
$username = $_SESSION['mailcow_cc_username'];
|
||||||
|
|
||||||
// skip password check if this is a forced TFA enrollment after login
|
// check for empty user and role
|
||||||
if (!empty($_SESSION['pending_tfa_setup'])) {
|
if (!isset($_SESSION['mailcow_cc_role']) || empty($username)) $access_denied = true;
|
||||||
$username = $_SESSION['mailcow_cc_username'];
|
|
||||||
if (empty($username) || !isset($_SESSION['mailcow_cc_role'])) {
|
// check admin confirm password
|
||||||
$_SESSION['return'][] = array('type' => 'danger', 'log' => array(__FUNCTION__, $_data_log), 'msg' => 'access_denied');
|
if ($access_denied === null) {
|
||||||
return false;
|
$stmt = $pdo->prepare("SELECT `password` FROM `admin`
|
||||||
|
WHERE `username` = :username");
|
||||||
|
$stmt->execute(array(':username' => $username));
|
||||||
|
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
if ($row) {
|
||||||
|
if (!verify_hash($row['password'], $_data["confirm_password"])) $access_denied = true;
|
||||||
|
else $access_denied = false;
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
$username = $_SESSION['mailcow_cc_username'];
|
|
||||||
$access_denied = null;
|
|
||||||
|
|
||||||
if (!isset($_SESSION['mailcow_cc_role']) || empty($username)) $access_denied = true;
|
// check mailbox confirm password
|
||||||
|
if ($access_denied === null) {
|
||||||
// check admin password
|
$stmt = $pdo->prepare("SELECT `password`, `authsource` FROM `mailbox`
|
||||||
if ($access_denied === null) {
|
WHERE `username` = :username");
|
||||||
$stmt = $pdo->prepare("SELECT `password` FROM `admin` WHERE `username` = :username");
|
$stmt->execute(array(':username' => $username));
|
||||||
$stmt->execute(array(':username' => $username));
|
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
if ($row) {
|
||||||
if ($row) {
|
if ($row['authsource'] == 'ldap'){
|
||||||
|
if (!ldap_mbox_login($username, $_data["confirm_password"], $iam_settings)) $access_denied = true;
|
||||||
|
else $access_denied = false;
|
||||||
|
} else {
|
||||||
if (!verify_hash($row['password'], $_data["confirm_password"])) $access_denied = true;
|
if (!verify_hash($row['password'], $_data["confirm_password"])) $access_denied = true;
|
||||||
else $access_denied = false;
|
else $access_denied = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// check mailbox password
|
// set access_denied error
|
||||||
if ($access_denied === null) {
|
if ($access_denied){
|
||||||
$stmt = $pdo->prepare("SELECT `password`, `authsource` FROM `mailbox` WHERE `username` = :username");
|
$_SESSION['return'][] = array(
|
||||||
$stmt->execute(array(':username' => $username));
|
'type' => 'danger',
|
||||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
'log' => array(__FUNCTION__, $_data_log),
|
||||||
if ($row) {
|
'msg' => 'access_denied'
|
||||||
if ($row['authsource'] == 'ldap'){
|
);
|
||||||
if (!ldap_mbox_login($username, $_data["confirm_password"], $iam_settings)) $access_denied = true;
|
return false;
|
||||||
else $access_denied = false;
|
|
||||||
} else {
|
|
||||||
if (!verify_hash($row['password'], $_data["confirm_password"])) $access_denied = true;
|
|
||||||
else $access_denied = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($access_denied) {
|
|
||||||
$_SESSION['return'][] = array('type' => 'danger', 'log' => array(__FUNCTION__, $_data_log), 'msg' => 'access_denied');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switch ($_data["tfa_method"]) {
|
switch ($_data["tfa_method"]) {
|
||||||
@@ -1312,7 +1229,6 @@ function set_tfa($_data) {
|
|||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
unset($_SESSION['pending_tfa_setup']);
|
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'success',
|
'type' => 'success',
|
||||||
'log' => array(__FUNCTION__, $_data_log),
|
'log' => array(__FUNCTION__, $_data_log),
|
||||||
@@ -1326,7 +1242,6 @@ function set_tfa($_data) {
|
|||||||
//$stmt->execute(array(':username' => $username));
|
//$stmt->execute(array(':username' => $username));
|
||||||
$stmt = $pdo->prepare("INSERT INTO `tfa` (`username`, `key_id`, `authmech`, `secret`, `active`) VALUES (?, ?, 'totp', ?, '1')");
|
$stmt = $pdo->prepare("INSERT INTO `tfa` (`username`, `key_id`, `authmech`, `secret`, `active`) VALUES (?, ?, 'totp', ?, '1')");
|
||||||
$stmt->execute(array($username, $key_id, $_POST['totp_secret']));
|
$stmt->execute(array($username, $key_id, $_POST['totp_secret']));
|
||||||
unset($_SESSION['pending_tfa_setup']);
|
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'success',
|
'type' => 'success',
|
||||||
'log' => array(__FUNCTION__, $_data_log),
|
'log' => array(__FUNCTION__, $_data_log),
|
||||||
@@ -1355,7 +1270,6 @@ function set_tfa($_data) {
|
|||||||
0
|
0
|
||||||
));
|
));
|
||||||
|
|
||||||
unset($_SESSION['pending_tfa_setup']);
|
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'success',
|
'type' => 'success',
|
||||||
'log' => array(__FUNCTION__, $_data_log),
|
'log' => array(__FUNCTION__, $_data_log),
|
||||||
@@ -1363,25 +1277,6 @@ function set_tfa($_data) {
|
|||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case "none":
|
case "none":
|
||||||
// Block TFA removal if force_tfa policy is active
|
|
||||||
$is_forced_tfa = false;
|
|
||||||
if ($_SESSION['mailcow_cc_role'] === 'user') {
|
|
||||||
$stmt_check = $pdo->prepare("SELECT JSON_EXTRACT(`attributes`, '$.force_tfa') FROM `mailbox` WHERE `username` = ?");
|
|
||||||
$stmt_check->execute(array($username));
|
|
||||||
$is_forced_tfa = ($stmt_check->fetchColumn() == '1');
|
|
||||||
} else {
|
|
||||||
$stmt_check = $pdo->prepare("SELECT JSON_EXTRACT(`attributes`, '$.force_tfa') FROM `admin` WHERE `username` = ?");
|
|
||||||
$stmt_check->execute(array($username));
|
|
||||||
$is_forced_tfa = ($stmt_check->fetchColumn() == '1');
|
|
||||||
}
|
|
||||||
if ($is_forced_tfa) {
|
|
||||||
$_SESSION['return'][] = array(
|
|
||||||
'type' => 'danger',
|
|
||||||
'log' => array(__FUNCTION__, $_data_log),
|
|
||||||
'msg' => 'tfa_removal_blocked'
|
|
||||||
);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
$stmt = $pdo->prepare("DELETE FROM `tfa` WHERE `username` = :username");
|
$stmt = $pdo->prepare("DELETE FROM `tfa` WHERE `username` = :username");
|
||||||
$stmt->execute(array(':username' => $username));
|
$stmt->execute(array(':username' => $username));
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
@@ -1634,26 +1529,6 @@ function unset_tfa_key($_data) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Block key removal if force_tfa policy is active
|
|
||||||
$is_forced_tfa = false;
|
|
||||||
if ($_SESSION['mailcow_cc_role'] === 'user') {
|
|
||||||
$stmt_check = $pdo->prepare("SELECT JSON_EXTRACT(`attributes`, '$.force_tfa') FROM `mailbox` WHERE `username` = ?");
|
|
||||||
$stmt_check->execute(array($username));
|
|
||||||
$is_forced_tfa = ($stmt_check->fetchColumn() == '1');
|
|
||||||
} else {
|
|
||||||
$stmt_check = $pdo->prepare("SELECT JSON_EXTRACT(`attributes`, '$.force_tfa') FROM `admin` WHERE `username` = ?");
|
|
||||||
$stmt_check->execute(array($username));
|
|
||||||
$is_forced_tfa = ($stmt_check->fetchColumn() == '1');
|
|
||||||
}
|
|
||||||
if ($is_forced_tfa) {
|
|
||||||
$_SESSION['return'][] = array(
|
|
||||||
'type' => 'danger',
|
|
||||||
'log' => array(__FUNCTION__, $_data_log),
|
|
||||||
'msg' => 'tfa_removal_blocked'
|
|
||||||
);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if it's last key
|
// check if it's last key
|
||||||
$stmt = $pdo->prepare("SELECT COUNT(*) AS `keys` FROM `tfa`
|
$stmt = $pdo->prepare("SELECT COUNT(*) AS `keys` FROM `tfa`
|
||||||
WHERE `username` = :username AND `active` = '1'");
|
WHERE `username` = :username AND `active` = '1'");
|
||||||
@@ -1686,15 +1561,6 @@ function unset_tfa_key($_data) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function tfa_exists($username) {
|
|
||||||
global $pdo;
|
|
||||||
if (empty($username)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
$stmt = $pdo->prepare("SELECT COUNT(*) as count FROM `tfa` WHERE `username` = :username");
|
|
||||||
$stmt->execute(array(':username' => $username));
|
|
||||||
return $stmt->fetch(PDO::FETCH_ASSOC)['count'] > 0;
|
|
||||||
}
|
|
||||||
function get_tfa($username = null, $id = null) {
|
function get_tfa($username = null, $id = null) {
|
||||||
global $pdo;
|
global $pdo;
|
||||||
if (empty($username) && isset($_SESSION['mailcow_cc_username'])) {
|
if (empty($username) && isset($_SESSION['mailcow_cc_username'])) {
|
||||||
@@ -2133,7 +1999,7 @@ function admin_api($access, $action, $data = null) {
|
|||||||
}
|
}
|
||||||
function license($action, $data = null) {
|
function license($action, $data = null) {
|
||||||
global $pdo;
|
global $pdo;
|
||||||
global $redis;
|
global $valkey;
|
||||||
global $lang;
|
global $lang;
|
||||||
if ($_SESSION['mailcow_cc_role'] != "admin") {
|
if ($_SESSION['mailcow_cc_role'] != "admin") {
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
@@ -2183,13 +2049,13 @@ function license($action, $data = null) {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
// json_encode needs "true"/"false" instead of true/false, to not encode it to 0 or 1
|
// 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) {
|
catch (RedisException $e) {
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'danger',
|
'type' => 'danger',
|
||||||
'log' => array(__FUNCTION__, $_action, $_data_log),
|
'log' => array(__FUNCTION__, $_action, $_data_log),
|
||||||
'msg' => array('redis_error', $e)
|
'msg' => array('valkey_error', $e)
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -2270,7 +2136,7 @@ function rspamd_ui($action, $data = null) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
function cors($action, $data = null) {
|
function cors($action, $data = null) {
|
||||||
global $redis;
|
global $valkey;
|
||||||
|
|
||||||
switch ($action) {
|
switch ($action) {
|
||||||
case "edit":
|
case "edit":
|
||||||
@@ -2311,7 +2177,7 @@ function cors($action, $data = null) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$redis->hMSet('CORS_SETTINGS', array(
|
$valkey->hMSet('CORS_SETTINGS', array(
|
||||||
'allowed_origins' => implode(', ', $allowed_origins),
|
'allowed_origins' => implode(', ', $allowed_origins),
|
||||||
'allowed_methods' => implode(', ', $allowed_methods)
|
'allowed_methods' => implode(', ', $allowed_methods)
|
||||||
));
|
));
|
||||||
@@ -2319,7 +2185,7 @@ function cors($action, $data = null) {
|
|||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'danger',
|
'type' => 'danger',
|
||||||
'log' => array(__FUNCTION__, $action, $data),
|
'log' => array(__FUNCTION__, $action, $data),
|
||||||
'msg' => array('redis_error', $e)
|
'msg' => array('valkey_error', $e)
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -2333,12 +2199,12 @@ function cors($action, $data = null) {
|
|||||||
break;
|
break;
|
||||||
case "get":
|
case "get":
|
||||||
try {
|
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) {
|
} catch (RedisException $e) {
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'danger',
|
'type' => 'danger',
|
||||||
'log' => array(__FUNCTION__, $action, $data),
|
'log' => array(__FUNCTION__, $action, $data),
|
||||||
'msg' => array('redis_error', $e)
|
'msg' => array('valkey_error', $e)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3101,7 +2967,7 @@ function identity_provider($_action = null, $_data = null, $_extra = null) {
|
|||||||
}
|
}
|
||||||
function reset_password($action, $data = null) {
|
function reset_password($action, $data = null) {
|
||||||
global $pdo;
|
global $pdo;
|
||||||
global $redis;
|
global $valkey;
|
||||||
global $mailcow_hostname;
|
global $mailcow_hostname;
|
||||||
global $PW_RESET_TOKEN_LIMIT;
|
global $PW_RESET_TOKEN_LIMIT;
|
||||||
global $PW_RESET_TOKEN_LIFETIME;
|
global $PW_RESET_TOKEN_LIFETIME;
|
||||||
@@ -3337,10 +3203,10 @@ function reset_password($action, $data = null) {
|
|||||||
$type = $data;
|
$type = $data;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$settings['from'] = $redis->Get('PW_RESET_FROM');
|
$settings['from'] = $valkey->Get('PW_RESET_FROM');
|
||||||
$settings['subject'] = $redis->Get('PW_RESET_SUBJ');
|
$settings['subject'] = $valkey->Get('PW_RESET_SUBJ');
|
||||||
$settings['html_tmpl'] = $redis->Get('PW_RESET_HTML');
|
$settings['html_tmpl'] = $valkey->Get('PW_RESET_HTML');
|
||||||
$settings['text_tmpl'] = $redis->Get('PW_RESET_TEXT');
|
$settings['text_tmpl'] = $valkey->Get('PW_RESET_TEXT');
|
||||||
if (empty($settings['html_tmpl']) && empty($settings['text_tmpl'])) {
|
if (empty($settings['html_tmpl']) && empty($settings['text_tmpl'])) {
|
||||||
$settings['html_tmpl'] = file_get_contents("/tpls/pw_reset_html.tpl");
|
$settings['html_tmpl'] = file_get_contents("/tpls/pw_reset_html.tpl");
|
||||||
$settings['text_tmpl'] = file_get_contents("/tpls/pw_reset_text.tpl");
|
$settings['text_tmpl'] = file_get_contents("/tpls/pw_reset_text.tpl");
|
||||||
@@ -3355,7 +3221,7 @@ function reset_password($action, $data = null) {
|
|||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'danger',
|
'type' => 'danger',
|
||||||
'log' => array(__FUNCTION__, $action, $_data_log),
|
'log' => array(__FUNCTION__, $action, $_data_log),
|
||||||
'msg' => array('redis_error', $e)
|
'msg' => array('valkey_error', $e)
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -3458,16 +3324,16 @@ function reset_password($action, $data = null) {
|
|||||||
$html = (empty($data['html_tmpl'])) ? "" : $data['html_tmpl'];
|
$html = (empty($data['html_tmpl'])) ? "" : $data['html_tmpl'];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$redis->Set('PW_RESET_FROM', $from);
|
$valkey->Set('PW_RESET_FROM', $from);
|
||||||
$redis->Set('PW_RESET_SUBJ', $subject);
|
$valkey->Set('PW_RESET_SUBJ', $subject);
|
||||||
$redis->Set('PW_RESET_HTML', $html);
|
$valkey->Set('PW_RESET_HTML', $html);
|
||||||
$redis->Set('PW_RESET_TEXT', $text);
|
$valkey->Set('PW_RESET_TEXT', $text);
|
||||||
}
|
}
|
||||||
catch (RedisException $e) {
|
catch (RedisException $e) {
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'danger',
|
'type' => 'danger',
|
||||||
'log' => array(__FUNCTION__, $action, $_data_log),
|
'log' => array(__FUNCTION__, $action, $_data_log),
|
||||||
'msg' => array('redis_error', $e)
|
'msg' => array('valkey_error', $e)
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -3497,49 +3363,6 @@ function set_user_loggedin_session($user) {
|
|||||||
unset($_SESSION['pending_mailcow_cc_role']);
|
unset($_SESSION['pending_mailcow_cc_role']);
|
||||||
unset($_SESSION['pending_tfa_methods']);
|
unset($_SESSION['pending_tfa_methods']);
|
||||||
}
|
}
|
||||||
function protect_route($allowed_roles = ['admin', 'domainadmin', 'user'], $redirects = []) {
|
|
||||||
// Check if user is authenticated
|
|
||||||
if (!isset($_SESSION['mailcow_cc_role'])) {
|
|
||||||
if (isset($redirects['unauthenticated'])) {
|
|
||||||
header('Location: ' . $redirects['unauthenticated']);
|
|
||||||
} else {
|
|
||||||
header('Location: /');
|
|
||||||
}
|
|
||||||
exit();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for pending actions (2FA setup, password update)
|
|
||||||
if (!empty($_SESSION['pending_tfa_setup']) || !empty($_SESSION['pending_pw_update'])) {
|
|
||||||
$pending_redirect = '/';
|
|
||||||
if ($_SESSION['mailcow_cc_role'] === 'admin') {
|
|
||||||
$pending_redirect = '/admin';
|
|
||||||
} elseif ($_SESSION['mailcow_cc_role'] === 'domainadmin') {
|
|
||||||
$pending_redirect = '/domainadmin';
|
|
||||||
}
|
|
||||||
header('Location: ' . $pending_redirect);
|
|
||||||
exit();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if user's role is in the allowed roles for the route
|
|
||||||
if (!in_array($_SESSION['mailcow_cc_role'], $allowed_roles)) {
|
|
||||||
if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'admin') {
|
|
||||||
header('Location: /admin/dashboard');
|
|
||||||
exit();
|
|
||||||
}
|
|
||||||
elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'domainadmin') {
|
|
||||||
header('Location: /domainadmin/mailbox');
|
|
||||||
exit();
|
|
||||||
}
|
|
||||||
elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'user') {
|
|
||||||
header('Location: /user');
|
|
||||||
exit();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
header('Location: /');
|
|
||||||
exit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function get_logs($application, $lines = false) {
|
function get_logs($application, $lines = false) {
|
||||||
if ($lines === false) {
|
if ($lines === false) {
|
||||||
$lines = $GLOBALS['LOG_LINES'] - 1;
|
$lines = $GLOBALS['LOG_LINES'] - 1;
|
||||||
@@ -3553,7 +3376,7 @@ function get_logs($application, $lines = false) {
|
|||||||
$to = intval($to);
|
$to = intval($to);
|
||||||
if ($from < 1 || $to < $from) { return false; }
|
if ($from < 1 || $to < $from) { return false; }
|
||||||
}
|
}
|
||||||
global $redis;
|
global $valkey;
|
||||||
global $pdo;
|
global $pdo;
|
||||||
if ($_SESSION['mailcow_cc_role'] != "admin") {
|
if ($_SESSION['mailcow_cc_role'] != "admin") {
|
||||||
return false;
|
return false;
|
||||||
@@ -3602,10 +3425,10 @@ function get_logs($application, $lines = false) {
|
|||||||
// Redis
|
// Redis
|
||||||
if ($application == "dovecot-mailcow") {
|
if ($application == "dovecot-mailcow") {
|
||||||
if (isset($from) && isset($to)) {
|
if (isset($from) && isset($to)) {
|
||||||
$data = $redis->lRange('DOVECOT_MAILLOG', $from - 1, $to - 1);
|
$data = $valkey->lRange('DOVECOT_MAILLOG', $from - 1, $to - 1);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$data = $redis->lRange('DOVECOT_MAILLOG', 0, $lines);
|
$data = $valkey->lRange('DOVECOT_MAILLOG', 0, $lines);
|
||||||
}
|
}
|
||||||
if ($data) {
|
if ($data) {
|
||||||
foreach ($data as $json_line) {
|
foreach ($data as $json_line) {
|
||||||
@@ -3616,10 +3439,10 @@ function get_logs($application, $lines = false) {
|
|||||||
}
|
}
|
||||||
if ($application == "cron-mailcow") {
|
if ($application == "cron-mailcow") {
|
||||||
if (isset($from) && isset($to)) {
|
if (isset($from) && isset($to)) {
|
||||||
$data = $redis->lRange('CRON_LOG', $from - 1, $to - 1);
|
$data = $valkey->lRange('CRON_LOG', $from - 1, $to - 1);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$data = $redis->lRange('CRON_LOG', 0, $lines);
|
$data = $valkey->lRange('CRON_LOG', 0, $lines);
|
||||||
}
|
}
|
||||||
if ($data) {
|
if ($data) {
|
||||||
foreach ($data as $json_line) {
|
foreach ($data as $json_line) {
|
||||||
@@ -3630,10 +3453,10 @@ function get_logs($application, $lines = false) {
|
|||||||
}
|
}
|
||||||
if ($application == "postfix-mailcow") {
|
if ($application == "postfix-mailcow") {
|
||||||
if (isset($from) && isset($to)) {
|
if (isset($from) && isset($to)) {
|
||||||
$data = $redis->lRange('POSTFIX_MAILLOG', $from - 1, $to - 1);
|
$data = $valkey->lRange('POSTFIX_MAILLOG', $from - 1, $to - 1);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$data = $redis->lRange('POSTFIX_MAILLOG', 0, $lines);
|
$data = $valkey->lRange('POSTFIX_MAILLOG', 0, $lines);
|
||||||
}
|
}
|
||||||
if ($data) {
|
if ($data) {
|
||||||
foreach ($data as $json_line) {
|
foreach ($data as $json_line) {
|
||||||
@@ -3644,10 +3467,10 @@ function get_logs($application, $lines = false) {
|
|||||||
}
|
}
|
||||||
if ($application == "sogo-mailcow") {
|
if ($application == "sogo-mailcow") {
|
||||||
if (isset($from) && isset($to)) {
|
if (isset($from) && isset($to)) {
|
||||||
$data = $redis->lRange('SOGO_LOG', $from - 1, $to - 1);
|
$data = $valkey->lRange('SOGO_LOG', $from - 1, $to - 1);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$data = $redis->lRange('SOGO_LOG', 0, $lines);
|
$data = $valkey->lRange('SOGO_LOG', 0, $lines);
|
||||||
}
|
}
|
||||||
if ($data) {
|
if ($data) {
|
||||||
foreach ($data as $json_line) {
|
foreach ($data as $json_line) {
|
||||||
@@ -3658,10 +3481,10 @@ function get_logs($application, $lines = false) {
|
|||||||
}
|
}
|
||||||
if ($application == "watchdog-mailcow") {
|
if ($application == "watchdog-mailcow") {
|
||||||
if (isset($from) && isset($to)) {
|
if (isset($from) && isset($to)) {
|
||||||
$data = $redis->lRange('WATCHDOG_LOG', $from - 1, $to - 1);
|
$data = $valkey->lRange('WATCHDOG_LOG', $from - 1, $to - 1);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$data = $redis->lRange('WATCHDOG_LOG', 0, $lines);
|
$data = $valkey->lRange('WATCHDOG_LOG', 0, $lines);
|
||||||
}
|
}
|
||||||
if ($data) {
|
if ($data) {
|
||||||
foreach ($data as $json_line) {
|
foreach ($data as $json_line) {
|
||||||
@@ -3672,10 +3495,10 @@ function get_logs($application, $lines = false) {
|
|||||||
}
|
}
|
||||||
if ($application == "acme-mailcow") {
|
if ($application == "acme-mailcow") {
|
||||||
if (isset($from) && isset($to)) {
|
if (isset($from) && isset($to)) {
|
||||||
$data = $redis->lRange('ACME_LOG', $from - 1, $to - 1);
|
$data = $valkey->lRange('ACME_LOG', $from - 1, $to - 1);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$data = $redis->lRange('ACME_LOG', 0, $lines);
|
$data = $valkey->lRange('ACME_LOG', 0, $lines);
|
||||||
}
|
}
|
||||||
if ($data) {
|
if ($data) {
|
||||||
foreach ($data as $json_line) {
|
foreach ($data as $json_line) {
|
||||||
@@ -3686,10 +3509,10 @@ function get_logs($application, $lines = false) {
|
|||||||
}
|
}
|
||||||
if ($application == "ratelimited") {
|
if ($application == "ratelimited") {
|
||||||
if (isset($from) && isset($to)) {
|
if (isset($from) && isset($to)) {
|
||||||
$data = $redis->lRange('RL_LOG', $from - 1, $to - 1);
|
$data = $valkey->lRange('RL_LOG', $from - 1, $to - 1);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$data = $redis->lRange('RL_LOG', 0, $lines);
|
$data = $valkey->lRange('RL_LOG', 0, $lines);
|
||||||
}
|
}
|
||||||
if ($data) {
|
if ($data) {
|
||||||
foreach ($data as $json_line) {
|
foreach ($data as $json_line) {
|
||||||
@@ -3700,10 +3523,10 @@ function get_logs($application, $lines = false) {
|
|||||||
}
|
}
|
||||||
if ($application == "api-mailcow") {
|
if ($application == "api-mailcow") {
|
||||||
if (isset($from) && isset($to)) {
|
if (isset($from) && isset($to)) {
|
||||||
$data = $redis->lRange('API_LOG', $from - 1, $to - 1);
|
$data = $valkey->lRange('API_LOG', $from - 1, $to - 1);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$data = $redis->lRange('API_LOG', 0, $lines);
|
$data = $valkey->lRange('API_LOG', 0, $lines);
|
||||||
}
|
}
|
||||||
if ($data) {
|
if ($data) {
|
||||||
foreach ($data as $json_line) {
|
foreach ($data as $json_line) {
|
||||||
@@ -3714,10 +3537,10 @@ function get_logs($application, $lines = false) {
|
|||||||
}
|
}
|
||||||
if ($application == "netfilter-mailcow") {
|
if ($application == "netfilter-mailcow") {
|
||||||
if (isset($from) && isset($to)) {
|
if (isset($from) && isset($to)) {
|
||||||
$data = $redis->lRange('NETFILTER_LOG', $from - 1, $to - 1);
|
$data = $valkey->lRange('NETFILTER_LOG', $from - 1, $to - 1);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$data = $redis->lRange('NETFILTER_LOG', 0, $lines);
|
$data = $valkey->lRange('NETFILTER_LOG', 0, $lines);
|
||||||
}
|
}
|
||||||
if ($data) {
|
if ($data) {
|
||||||
foreach ($data as $json_line) {
|
foreach ($data as $json_line) {
|
||||||
@@ -3728,10 +3551,10 @@ function get_logs($application, $lines = false) {
|
|||||||
}
|
}
|
||||||
if ($application == "autodiscover-mailcow") {
|
if ($application == "autodiscover-mailcow") {
|
||||||
if (isset($from) && isset($to)) {
|
if (isset($from) && isset($to)) {
|
||||||
$data = $redis->lRange('AUTODISCOVER_LOG', $from - 1, $to - 1);
|
$data = $valkey->lRange('AUTODISCOVER_LOG', $from - 1, $to - 1);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$data = $redis->lRange('AUTODISCOVER_LOG', 0, $lines);
|
$data = $valkey->lRange('AUTODISCOVER_LOG', 0, $lines);
|
||||||
}
|
}
|
||||||
if ($data) {
|
if ($data) {
|
||||||
foreach ($data as $json_line) {
|
foreach ($data as $json_line) {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||||
global $pdo;
|
global $pdo;
|
||||||
global $redis;
|
global $valkey;
|
||||||
global $lang;
|
global $lang;
|
||||||
global $MAILBOX_DEFAULT_ATTRIBUTES;
|
global $MAILBOX_DEFAULT_ATTRIBUTES;
|
||||||
global $iam_settings;
|
global $iam_settings;
|
||||||
@@ -9,10 +9,6 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
$_data_log = $_data;
|
$_data_log = $_data;
|
||||||
!isset($_data_log['password']) ?: $_data_log['password'] = '*';
|
!isset($_data_log['password']) ?: $_data_log['password'] = '*';
|
||||||
!isset($_data_log['password2']) ?: $_data_log['password2'] = '*';
|
!isset($_data_log['password2']) ?: $_data_log['password2'] = '*';
|
||||||
|
|
||||||
// Track mailboxes affected by alias operations for incremental SOGo updates
|
|
||||||
$update_sogo_mailboxes = array();
|
|
||||||
|
|
||||||
switch ($_action) {
|
switch ($_action) {
|
||||||
case 'add':
|
case 'add':
|
||||||
switch ($_type) {
|
switch ($_type) {
|
||||||
@@ -53,12 +49,6 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
// Default to 1 yr
|
// Default to 1 yr
|
||||||
$_data["validity"] = 8760;
|
$_data["validity"] = 8760;
|
||||||
}
|
}
|
||||||
if (isset($_data["permanent"]) && filter_var($_data["permanent"], FILTER_VALIDATE_BOOL)) {
|
|
||||||
$permanent = 1;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$permanent = 0;
|
|
||||||
}
|
|
||||||
$domain = $_data['domain'];
|
$domain = $_data['domain'];
|
||||||
$description = $_data['description'];
|
$description = $_data['description'];
|
||||||
$valid_domains[] = mailbox('get', 'mailbox_details', $username)['domain'];
|
$valid_domains[] = mailbox('get', 'mailbox_details', $username)['domain'];
|
||||||
@@ -75,14 +65,13 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
$validity = strtotime("+" . $_data["validity"] . " hour");
|
$validity = strtotime("+" . $_data["validity"] . " hour");
|
||||||
$stmt = $pdo->prepare("INSERT INTO `spamalias` (`address`, `description`, `goto`, `validity`, `permanent`) VALUES
|
$stmt = $pdo->prepare("INSERT INTO `spamalias` (`address`, `description`, `goto`, `validity`) VALUES
|
||||||
(:address, :description, :goto, :validity, :permanent)");
|
(:address, :description, :goto, :validity)");
|
||||||
$stmt->execute(array(
|
$stmt->execute(array(
|
||||||
':address' => readable_random_string(rand(rand(3, 9), rand(3, 9))) . '.' . readable_random_string(rand(rand(3, 9), rand(3, 9))) . '@' . $domain,
|
':address' => readable_random_string(rand(rand(3, 9), rand(3, 9))) . '.' . readable_random_string(rand(rand(3, 9), rand(3, 9))) . '@' . $domain,
|
||||||
':description' => $description,
|
':description' => $description,
|
||||||
':goto' => $username,
|
':goto' => $username,
|
||||||
':validity' => $validity,
|
':validity' => $validity
|
||||||
':permanent' => $permanent
|
|
||||||
));
|
));
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'success',
|
'type' => 'success',
|
||||||
@@ -639,13 +628,13 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$redis->hSet('DOMAIN_MAP', $domain, 1);
|
$valkey->hSet('DOMAIN_MAP', $domain, 1);
|
||||||
}
|
}
|
||||||
catch (RedisException $e) {
|
catch (RedisException $e) {
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'danger',
|
'type' => 'danger',
|
||||||
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||||
'msg' => array('redis_error', $e)
|
'msg' => array('valkey_error', $e)
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -657,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['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'];
|
$_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($_data['key_size']) && !empty($_data['dkim_selector'])) {
|
||||||
if (!empty($redis->hGet('DKIM_SELECTORS', $domain))) {
|
if (!empty($valkey->hGet('DKIM_SELECTORS', $domain))) {
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'success',
|
'type' => 'success',
|
||||||
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||||
@@ -699,7 +688,6 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
$gotos = array_map('trim', preg_split( "/( |,|;|\n)/", $_data['goto']));
|
$gotos = array_map('trim', preg_split( "/( |,|;|\n)/", $_data['goto']));
|
||||||
$internal = intval($_data['internal']);
|
$internal = intval($_data['internal']);
|
||||||
$active = intval($_data['active']);
|
$active = intval($_data['active']);
|
||||||
$sender_allowed = intval($_data['sender_allowed']);
|
|
||||||
$sogo_visible = intval($_data['sogo_visible']);
|
$sogo_visible = intval($_data['sogo_visible']);
|
||||||
$goto_null = intval($_data['goto_null']);
|
$goto_null = intval($_data['goto_null']);
|
||||||
$goto_spam = intval($_data['goto_spam']);
|
$goto_spam = intval($_data['goto_spam']);
|
||||||
@@ -855,8 +843,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
$stmt = $pdo->prepare("INSERT INTO `alias` (`address`, `public_comment`, `private_comment`, `goto`, `domain`, `sogo_visible`, `internal`, `sender_allowed`, `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, :sender_allowed, :active)");
|
VALUES (:address, :public_comment, :private_comment, :goto, :domain, :sogo_visible, :internal, :active)");
|
||||||
if (!filter_var($address, FILTER_VALIDATE_EMAIL) === true) {
|
if (!filter_var($address, FILTER_VALIDATE_EMAIL) === true) {
|
||||||
$stmt->execute(array(
|
$stmt->execute(array(
|
||||||
':address' => '@'.$domain,
|
':address' => '@'.$domain,
|
||||||
@@ -867,7 +855,6 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
':domain' => $domain,
|
':domain' => $domain,
|
||||||
':sogo_visible' => $sogo_visible,
|
':sogo_visible' => $sogo_visible,
|
||||||
':internal' => $internal,
|
':internal' => $internal,
|
||||||
':sender_allowed' => $sender_allowed,
|
|
||||||
':active' => $active
|
':active' => $active
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@@ -880,7 +867,6 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
':domain' => $domain,
|
':domain' => $domain,
|
||||||
':sogo_visible' => $sogo_visible,
|
':sogo_visible' => $sogo_visible,
|
||||||
':internal' => $internal,
|
':internal' => $internal,
|
||||||
':sender_allowed' => $sender_allowed,
|
|
||||||
':active' => $active
|
':active' => $active
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@@ -890,17 +876,6 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||||
'msg' => array('alias_added', $address, $id)
|
'msg' => array('alias_added', $address, $id)
|
||||||
);
|
);
|
||||||
|
|
||||||
// Track affected mailboxes for SOGo update
|
|
||||||
if (!empty($goto)) {
|
|
||||||
$gotos = array_map('trim', explode(',', $goto));
|
|
||||||
foreach ($gotos as $g) {
|
|
||||||
if (filter_var($g, FILTER_VALIDATE_EMAIL) &&
|
|
||||||
!in_array($g, array('null@localhost', 'spam@localhost', 'ham@localhost'))) {
|
|
||||||
$update_sogo_mailboxes[] = $g;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'alias_domain':
|
case 'alias_domain':
|
||||||
@@ -999,13 +974,13 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
':active' => $active
|
':active' => $active
|
||||||
));
|
));
|
||||||
try {
|
try {
|
||||||
$redis->hSet('DOMAIN_MAP', $alias_domain, 1);
|
$valkey->hSet('DOMAIN_MAP', $alias_domain, 1);
|
||||||
}
|
}
|
||||||
catch (RedisException $e) {
|
catch (RedisException $e) {
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'danger',
|
'type' => 'danger',
|
||||||
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||||
'msg' => array('redis_error', $e)
|
'msg' => array('valkey_error', $e)
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -1013,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));
|
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($_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(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'success',
|
'type' => 'success',
|
||||||
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||||
@@ -1093,12 +1068,9 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
$_data['pop3_access'] = (in_array('pop3', $_data['protocol_access'])) ? 1 : 0;
|
$_data['pop3_access'] = (in_array('pop3', $_data['protocol_access'])) ? 1 : 0;
|
||||||
$_data['smtp_access'] = (in_array('smtp', $_data['protocol_access'])) ? 1 : 0;
|
$_data['smtp_access'] = (in_array('smtp', $_data['protocol_access'])) ? 1 : 0;
|
||||||
$_data['sieve_access'] = (in_array('sieve', $_data['protocol_access'])) ? 1 : 0;
|
$_data['sieve_access'] = (in_array('sieve', $_data['protocol_access'])) ? 1 : 0;
|
||||||
$_data['eas_access'] = (in_array('eas', $_data['protocol_access'])) ? 1 : 0;
|
|
||||||
$_data['dav_access'] = (in_array('dav', $_data['protocol_access'])) ? 1 : 0;
|
|
||||||
}
|
}
|
||||||
$active = (isset($_data['active'])) ? intval($_data['active']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['active']);
|
$active = (isset($_data['active'])) ? intval($_data['active']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['active']);
|
||||||
$force_pw_update = (isset($_data['force_pw_update'])) ? intval($_data['force_pw_update']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['force_pw_update']);
|
$force_pw_update = (isset($_data['force_pw_update'])) ? intval($_data['force_pw_update']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['force_pw_update']);
|
||||||
$force_tfa = (isset($_data['force_tfa'])) ? intval($_data['force_tfa']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['force_tfa']);
|
|
||||||
$tls_enforce_in = (isset($_data['tls_enforce_in'])) ? intval($_data['tls_enforce_in']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['tls_enforce_in']);
|
$tls_enforce_in = (isset($_data['tls_enforce_in'])) ? intval($_data['tls_enforce_in']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['tls_enforce_in']);
|
||||||
$tls_enforce_out = (isset($_data['tls_enforce_out'])) ? intval($_data['tls_enforce_out']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['tls_enforce_out']);
|
$tls_enforce_out = (isset($_data['tls_enforce_out'])) ? intval($_data['tls_enforce_out']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['tls_enforce_out']);
|
||||||
$sogo_access = (isset($_data['sogo_access'])) ? intval($_data['sogo_access']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['sogo_access']);
|
$sogo_access = (isset($_data['sogo_access'])) ? intval($_data['sogo_access']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['sogo_access']);
|
||||||
@@ -1106,8 +1078,6 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
$pop3_access = (isset($_data['pop3_access'])) ? intval($_data['pop3_access']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['pop3_access']);
|
$pop3_access = (isset($_data['pop3_access'])) ? intval($_data['pop3_access']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['pop3_access']);
|
||||||
$smtp_access = (isset($_data['smtp_access'])) ? intval($_data['smtp_access']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['smtp_access']);
|
$smtp_access = (isset($_data['smtp_access'])) ? intval($_data['smtp_access']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['smtp_access']);
|
||||||
$sieve_access = (isset($_data['sieve_access'])) ? intval($_data['sieve_access']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['sieve_access']);
|
$sieve_access = (isset($_data['sieve_access'])) ? intval($_data['sieve_access']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['sieve_access']);
|
||||||
$eas_access = (isset($_data['eas_access'])) ? intval($_data['eas_access']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['eas_access']);
|
|
||||||
$dav_access = (isset($_data['dav_access'])) ? intval($_data['dav_access']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['dav_access']);
|
|
||||||
$relayhost = (isset($_data['relayhost'])) ? intval($_data['relayhost']) : 0;
|
$relayhost = (isset($_data['relayhost'])) ? intval($_data['relayhost']) : 0;
|
||||||
$quarantine_notification = (isset($_data['quarantine_notification'])) ? strval($_data['quarantine_notification']) : strval($MAILBOX_DEFAULT_ATTRIBUTES['quarantine_notification']);
|
$quarantine_notification = (isset($_data['quarantine_notification'])) ? strval($_data['quarantine_notification']) : strval($MAILBOX_DEFAULT_ATTRIBUTES['quarantine_notification']);
|
||||||
$quarantine_category = (isset($_data['quarantine_category'])) ? strval($_data['quarantine_category']) : strval($MAILBOX_DEFAULT_ATTRIBUTES['quarantine_category']);
|
$quarantine_category = (isset($_data['quarantine_category'])) ? strval($_data['quarantine_category']) : strval($MAILBOX_DEFAULT_ATTRIBUTES['quarantine_category']);
|
||||||
@@ -1116,13 +1086,9 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
if (in_array($authsource, array('keycloak', 'generic-oidc', 'ldap'))){
|
if (in_array($authsource, array('keycloak', 'generic-oidc', 'ldap'))){
|
||||||
$force_pw_update = 0;
|
$force_pw_update = 0;
|
||||||
}
|
}
|
||||||
if ($authsource == 'generic-oidc'){
|
|
||||||
$force_tfa = 0;
|
|
||||||
}
|
|
||||||
$mailbox_attrs = json_encode(
|
$mailbox_attrs = json_encode(
|
||||||
array(
|
array(
|
||||||
'force_pw_update' => strval($force_pw_update),
|
'force_pw_update' => strval($force_pw_update),
|
||||||
'force_tfa' => strval($force_tfa),
|
|
||||||
'tls_enforce_in' => strval($tls_enforce_in),
|
'tls_enforce_in' => strval($tls_enforce_in),
|
||||||
'tls_enforce_out' => strval($tls_enforce_out),
|
'tls_enforce_out' => strval($tls_enforce_out),
|
||||||
'sogo_access' => strval($sogo_access),
|
'sogo_access' => strval($sogo_access),
|
||||||
@@ -1130,8 +1096,6 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
'pop3_access' => strval($pop3_access),
|
'pop3_access' => strval($pop3_access),
|
||||||
'smtp_access' => strval($smtp_access),
|
'smtp_access' => strval($smtp_access),
|
||||||
'sieve_access' => strval($sieve_access),
|
'sieve_access' => strval($sieve_access),
|
||||||
'eas_access' => strval($eas_access),
|
|
||||||
'dav_access' => strval($dav_access),
|
|
||||||
'relayhost' => strval($relayhost),
|
'relayhost' => strval($relayhost),
|
||||||
'passwd_update' => time(),
|
'passwd_update' => time(),
|
||||||
'mailbox_format' => strval($MAILBOX_DEFAULT_ATTRIBUTES['mailbox_format']),
|
'mailbox_format' => strval($MAILBOX_DEFAULT_ATTRIBUTES['mailbox_format']),
|
||||||
@@ -1385,8 +1349,15 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
), $_extra);
|
), $_extra);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Track affected mailboxes for SOGo update
|
try {
|
||||||
$update_sogo_mailboxes[] = $username;
|
update_sogo_static_view($username);
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
$_SESSION['return'][] = array(
|
||||||
|
'type' => 'danger',
|
||||||
|
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||||
|
'msg' => $e->getMessage()
|
||||||
|
);
|
||||||
|
}
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'success',
|
'type' => 'success',
|
||||||
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||||
@@ -1617,9 +1588,6 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||||
'msg' => array('resource_added', htmlspecialchars($name))
|
'msg' => array('resource_added', htmlspecialchars($name))
|
||||||
);
|
);
|
||||||
|
|
||||||
// Track affected mailboxes for SOGo update
|
|
||||||
$update_sogo_mailboxes[] = $name;
|
|
||||||
break;
|
break;
|
||||||
case 'domain_templates':
|
case 'domain_templates':
|
||||||
if ($_SESSION['mailcow_cc_role'] != "admin") {
|
if ($_SESSION['mailcow_cc_role'] != "admin") {
|
||||||
@@ -1736,7 +1704,6 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
$attr["rl_frame"] = (!empty($_data['rl_frame'])) ? $_data['rl_frame'] : "s";
|
$attr["rl_frame"] = (!empty($_data['rl_frame'])) ? $_data['rl_frame'] : "s";
|
||||||
$attr["rl_value"] = (!empty($_data['rl_value'])) ? $_data['rl_value'] : "";
|
$attr["rl_value"] = (!empty($_data['rl_value'])) ? $_data['rl_value'] : "";
|
||||||
$attr["force_pw_update"] = isset($_data['force_pw_update']) ? intval($_data['force_pw_update']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['force_pw_update']);
|
$attr["force_pw_update"] = isset($_data['force_pw_update']) ? intval($_data['force_pw_update']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['force_pw_update']);
|
||||||
$attr["force_tfa"] = isset($_data['force_tfa']) ? intval($_data['force_tfa']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['force_tfa']);
|
|
||||||
$attr["sogo_access"] = isset($_data['sogo_access']) ? intval($_data['sogo_access']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['sogo_access']);
|
$attr["sogo_access"] = isset($_data['sogo_access']) ? intval($_data['sogo_access']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['sogo_access']);
|
||||||
$attr["active"] = isset($_data['active']) ? intval($_data['active']) : 1;
|
$attr["active"] = isset($_data['active']) ? intval($_data['active']) : 1;
|
||||||
$attr["tls_enforce_in"] = isset($_data['tls_enforce_in']) ? intval($_data['tls_enforce_in']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['tls_enforce_in']);
|
$attr["tls_enforce_in"] = isset($_data['tls_enforce_in']) ? intval($_data['tls_enforce_in']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['tls_enforce_in']);
|
||||||
@@ -1747,16 +1714,12 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
$attr['pop3_access'] = (in_array('pop3', $_data['protocol_access'])) ? 1 : 0;
|
$attr['pop3_access'] = (in_array('pop3', $_data['protocol_access'])) ? 1 : 0;
|
||||||
$attr['smtp_access'] = (in_array('smtp', $_data['protocol_access'])) ? 1 : 0;
|
$attr['smtp_access'] = (in_array('smtp', $_data['protocol_access'])) ? 1 : 0;
|
||||||
$attr['sieve_access'] = (in_array('sieve', $_data['protocol_access'])) ? 1 : 0;
|
$attr['sieve_access'] = (in_array('sieve', $_data['protocol_access'])) ? 1 : 0;
|
||||||
$attr['eas_access'] = (in_array('eas', $_data['protocol_access'])) ? 1 : 0;
|
|
||||||
$attr['dav_access'] = (in_array('dav', $_data['protocol_access'])) ? 1 : 0;
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$attr['imap_access'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['imap_access']);
|
$attr['imap_access'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['imap_access']);
|
||||||
$attr['pop3_access'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['pop3_access']);
|
$attr['pop3_access'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['pop3_access']);
|
||||||
$attr['smtp_access'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['smtp_access']);
|
$attr['smtp_access'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['smtp_access']);
|
||||||
$attr['sieve_access'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['sieve_access']);
|
$attr['sieve_access'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['sieve_access']);
|
||||||
$attr['eas_access'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['eas_access']);
|
|
||||||
$attr['dav_access'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['dav_access']);
|
|
||||||
}
|
}
|
||||||
if (isset($_data['acl'])) {
|
if (isset($_data['acl'])) {
|
||||||
$_data['acl'] = (array)$_data['acl'];
|
$_data['acl'] = (array)$_data['acl'];
|
||||||
@@ -2140,23 +2103,15 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (empty($_data['validity']) && empty($_data['permanent'])) {
|
if (empty($_data['validity'])) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (isset($_data['permanent']) && filter_var($_data['permanent'], FILTER_VALIDATE_BOOL)) {
|
$validity = round((int)time() + ($_data['validity'] * 3600));
|
||||||
$permanent = 1;
|
$stmt = $pdo->prepare("UPDATE `spamalias` SET `validity` = :validity WHERE
|
||||||
$validity = 0;
|
|
||||||
}
|
|
||||||
else if (isset($_data['validity'])) {
|
|
||||||
$permanent = 0;
|
|
||||||
$validity = round((int)time() + ($_data['validity'] * 3600));
|
|
||||||
}
|
|
||||||
$stmt = $pdo->prepare("UPDATE `spamalias` SET `validity` = :validity, `permanent` = :permanent WHERE
|
|
||||||
`address` = :address");
|
`address` = :address");
|
||||||
$stmt->execute(array(
|
$stmt->execute(array(
|
||||||
':address' => $address,
|
':address' => $address,
|
||||||
':validity' => $validity,
|
':validity' => $validity
|
||||||
':permanent' => $permanent
|
|
||||||
));
|
));
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'success',
|
'type' => 'success',
|
||||||
@@ -2192,42 +2147,42 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
}
|
}
|
||||||
if (isset($_data['tagged_mail_handler']) && $_data['tagged_mail_handler'] == "subject") {
|
if (isset($_data['tagged_mail_handler']) && $_data['tagged_mail_handler'] == "subject") {
|
||||||
try {
|
try {
|
||||||
$redis->hSet('RCPT_WANTS_SUBJECT_TAG', $username, 1);
|
$valkey->hSet('RCPT_WANTS_SUBJECT_TAG', $username, 1);
|
||||||
$redis->hDel('RCPT_WANTS_SUBFOLDER_TAG', $username);
|
$valkey->hDel('RCPT_WANTS_SUBFOLDER_TAG', $username);
|
||||||
}
|
}
|
||||||
catch (RedisException $e) {
|
catch (RedisException $e) {
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'danger',
|
'type' => 'danger',
|
||||||
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||||
'msg' => array('redis_error', $e)
|
'msg' => array('valkey_error', $e)
|
||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (isset($_data['tagged_mail_handler']) && $_data['tagged_mail_handler'] == "subfolder") {
|
else if (isset($_data['tagged_mail_handler']) && $_data['tagged_mail_handler'] == "subfolder") {
|
||||||
try {
|
try {
|
||||||
$redis->hSet('RCPT_WANTS_SUBFOLDER_TAG', $username, 1);
|
$valkey->hSet('RCPT_WANTS_SUBFOLDER_TAG', $username, 1);
|
||||||
$redis->hDel('RCPT_WANTS_SUBJECT_TAG', $username);
|
$valkey->hDel('RCPT_WANTS_SUBJECT_TAG', $username);
|
||||||
}
|
}
|
||||||
catch (RedisException $e) {
|
catch (RedisException $e) {
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'danger',
|
'type' => 'danger',
|
||||||
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||||
'msg' => array('redis_error', $e)
|
'msg' => array('valkey_error', $e)
|
||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
try {
|
try {
|
||||||
$redis->hDel('RCPT_WANTS_SUBJECT_TAG', $username);
|
$valkey->hDel('RCPT_WANTS_SUBJECT_TAG', $username);
|
||||||
$redis->hDel('RCPT_WANTS_SUBFOLDER_TAG', $username);
|
$valkey->hDel('RCPT_WANTS_SUBFOLDER_TAG', $username);
|
||||||
}
|
}
|
||||||
catch (RedisException $e) {
|
catch (RedisException $e) {
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'danger',
|
'type' => 'danger',
|
||||||
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||||
'msg' => array('redis_error', $e)
|
'msg' => array('valkey_error', $e)
|
||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -2531,7 +2486,6 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
if (!empty($is_now)) {
|
if (!empty($is_now)) {
|
||||||
$internal = (isset($_data['internal'])) ? intval($_data['internal']) : $is_now['internal'];
|
$internal = (isset($_data['internal'])) ? intval($_data['internal']) : $is_now['internal'];
|
||||||
$active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active'];
|
$active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active'];
|
||||||
$sender_allowed = (isset($_data['sender_allowed'])) ? intval($_data['sender_allowed']) : $is_now['sender_allowed'];
|
|
||||||
$sogo_visible = (isset($_data['sogo_visible'])) ? intval($_data['sogo_visible']) : $is_now['sogo_visible'];
|
$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;
|
$goto_null = (isset($_data['goto_null'])) ? intval($_data['goto_null']) : 0;
|
||||||
$goto_spam = (isset($_data['goto_spam'])) ? intval($_data['goto_spam']) : 0;
|
$goto_spam = (isset($_data['goto_spam'])) ? intval($_data['goto_spam']) : 0;
|
||||||
@@ -2717,7 +2671,6 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
`goto` = :goto,
|
`goto` = :goto,
|
||||||
`sogo_visible`= :sogo_visible,
|
`sogo_visible`= :sogo_visible,
|
||||||
`internal`= :internal,
|
`internal`= :internal,
|
||||||
`sender_allowed`= :sender_allowed,
|
|
||||||
`active`= :active
|
`active`= :active
|
||||||
WHERE `id` = :id");
|
WHERE `id` = :id");
|
||||||
$stmt->execute(array(
|
$stmt->execute(array(
|
||||||
@@ -2728,7 +2681,6 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
':goto' => $goto,
|
':goto' => $goto,
|
||||||
':sogo_visible' => $sogo_visible,
|
':sogo_visible' => $sogo_visible,
|
||||||
':internal' => $internal,
|
':internal' => $internal,
|
||||||
':sender_allowed' => $sender_allowed,
|
|
||||||
':active' => $active,
|
':active' => $active,
|
||||||
':id' => $is_now['id']
|
':id' => $is_now['id']
|
||||||
));
|
));
|
||||||
@@ -2738,28 +2690,6 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||||
'msg' => array('alias_modified', htmlspecialchars($address))
|
'msg' => array('alias_modified', htmlspecialchars($address))
|
||||||
);
|
);
|
||||||
|
|
||||||
// Track affected mailboxes for SOGo update (both old and new goto addresses)
|
|
||||||
// Old goto: to remove alias from their view
|
|
||||||
if (!empty($is_now['goto'])) {
|
|
||||||
$old_gotos = array_map('trim', explode(',', $is_now['goto']));
|
|
||||||
foreach ($old_gotos as $g) {
|
|
||||||
if (filter_var($g, FILTER_VALIDATE_EMAIL) &&
|
|
||||||
!in_array($g, array('null@localhost', 'spam@localhost', 'ham@localhost'))) {
|
|
||||||
$update_sogo_mailboxes[] = $g;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// New goto: to add alias to their view
|
|
||||||
if (!empty($goto)) {
|
|
||||||
$new_gotos = array_map('trim', explode(',', $goto));
|
|
||||||
foreach ($new_gotos as $g) {
|
|
||||||
if (filter_var($g, FILTER_VALIDATE_EMAIL) &&
|
|
||||||
!in_array($g, array('null@localhost', 'spam@localhost', 'ham@localhost'))) {
|
|
||||||
$update_sogo_mailboxes[] = $g;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'domain':
|
case 'domain':
|
||||||
@@ -3098,20 +3028,15 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
$_data['pop3_access'] = (in_array('pop3', $_data['protocol_access'])) ? 1 : 0;
|
$_data['pop3_access'] = (in_array('pop3', $_data['protocol_access'])) ? 1 : 0;
|
||||||
$_data['smtp_access'] = (in_array('smtp', $_data['protocol_access'])) ? 1 : 0;
|
$_data['smtp_access'] = (in_array('smtp', $_data['protocol_access'])) ? 1 : 0;
|
||||||
$_data['sieve_access'] = (in_array('sieve', $_data['protocol_access'])) ? 1 : 0;
|
$_data['sieve_access'] = (in_array('sieve', $_data['protocol_access'])) ? 1 : 0;
|
||||||
$_data['eas_access'] = (in_array('eas', $_data['protocol_access'])) ? 1 : 0;
|
|
||||||
$_data['dav_access'] = (in_array('dav', $_data['protocol_access'])) ? 1 : 0;
|
|
||||||
}
|
}
|
||||||
if (!empty($is_now)) {
|
if (!empty($is_now)) {
|
||||||
$active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active'];
|
$active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active'];
|
||||||
(int)$force_pw_update = (isset($_data['force_pw_update'])) ? intval($_data['force_pw_update']) : intval($is_now['attributes']['force_pw_update']);
|
(int)$force_pw_update = (isset($_data['force_pw_update'])) ? intval($_data['force_pw_update']) : intval($is_now['attributes']['force_pw_update']);
|
||||||
(int)$force_tfa = (isset($_data['force_tfa'])) ? intval($_data['force_tfa']) : intval($is_now['attributes']['force_tfa']);
|
|
||||||
(int)$sogo_access = (isset($_data['sogo_access']) && hasACLAccess("sogo_access")) ? intval($_data['sogo_access']) : intval($is_now['attributes']['sogo_access']);
|
(int)$sogo_access = (isset($_data['sogo_access']) && hasACLAccess("sogo_access")) ? intval($_data['sogo_access']) : intval($is_now['attributes']['sogo_access']);
|
||||||
(int)$imap_access = (isset($_data['imap_access']) && hasACLAccess("protocol_access")) ? intval($_data['imap_access']) : intval($is_now['attributes']['imap_access']);
|
(int)$imap_access = (isset($_data['imap_access']) && hasACLAccess("protocol_access")) ? intval($_data['imap_access']) : intval($is_now['attributes']['imap_access']);
|
||||||
(int)$pop3_access = (isset($_data['pop3_access']) && hasACLAccess("protocol_access")) ? intval($_data['pop3_access']) : intval($is_now['attributes']['pop3_access']);
|
(int)$pop3_access = (isset($_data['pop3_access']) && hasACLAccess("protocol_access")) ? intval($_data['pop3_access']) : intval($is_now['attributes']['pop3_access']);
|
||||||
(int)$smtp_access = (isset($_data['smtp_access']) && hasACLAccess("protocol_access")) ? intval($_data['smtp_access']) : intval($is_now['attributes']['smtp_access']);
|
(int)$smtp_access = (isset($_data['smtp_access']) && hasACLAccess("protocol_access")) ? intval($_data['smtp_access']) : intval($is_now['attributes']['smtp_access']);
|
||||||
(int)$sieve_access = (isset($_data['sieve_access']) && hasACLAccess("protocol_access")) ? intval($_data['sieve_access']) : intval($is_now['attributes']['sieve_access']);
|
(int)$sieve_access = (isset($_data['sieve_access']) && hasACLAccess("protocol_access")) ? intval($_data['sieve_access']) : intval($is_now['attributes']['sieve_access']);
|
||||||
(int)$eas_access = (isset($_data['eas_access']) && hasACLAccess("protocol_access")) ? intval($_data['eas_access']) : intval($is_now['attributes']['eas_access']);
|
|
||||||
(int)$dav_access = (isset($_data['dav_access']) && hasACLAccess("protocol_access")) ? intval($_data['dav_access']) : intval($is_now['attributes']['dav_access']);
|
|
||||||
(int)$relayhost = (isset($_data['relayhost']) && hasACLAccess("mailbox_relayhost")) ? intval($_data['relayhost']) : intval($is_now['attributes']['relayhost']);
|
(int)$relayhost = (isset($_data['relayhost']) && hasACLAccess("mailbox_relayhost")) ? intval($_data['relayhost']) : intval($is_now['attributes']['relayhost']);
|
||||||
(int)$quota_m = (isset_has_content($_data['quota'])) ? intval($_data['quota']) : ($is_now['quota'] / 1048576);
|
(int)$quota_m = (isset_has_content($_data['quota'])) ? intval($_data['quota']) : ($is_now['quota'] / 1048576);
|
||||||
$name = (!empty($_data['name'])) ? ltrim(rtrim($_data['name'], '>'), '<') : $is_now['name'];
|
$name = (!empty($_data['name'])) ? ltrim(rtrim($_data['name'], '>'), '<') : $is_now['name'];
|
||||||
@@ -3129,9 +3054,6 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
if (in_array($authsource, array('keycloak', 'generic-oidc', 'ldap'))){
|
if (in_array($authsource, array('keycloak', 'generic-oidc', 'ldap'))){
|
||||||
$force_pw_update = 0;
|
$force_pw_update = 0;
|
||||||
}
|
}
|
||||||
if ($authsource == 'generic-oidc'){
|
|
||||||
$force_tfa = 0;
|
|
||||||
}
|
|
||||||
$pw_recovery_email = (isset($_data['pw_recovery_email']) && $authsource == 'mailcow') ? $_data['pw_recovery_email'] : $is_now['attributes']['recovery_email'];
|
$pw_recovery_email = (isset($_data['pw_recovery_email']) && $authsource == 'mailcow') ? $_data['pw_recovery_email'] : $is_now['attributes']['recovery_email'];
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -3248,10 +3170,9 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
}
|
}
|
||||||
if (isset($_data['sender_acl'])) {
|
if (isset($_data['sender_acl'])) {
|
||||||
// Get sender_acl items set by admin
|
// Get sender_acl items set by admin
|
||||||
$current_sender_acls = mailbox('get', 'sender_acl_handles', $username);
|
|
||||||
$sender_acl_admin = array_merge(
|
$sender_acl_admin = array_merge(
|
||||||
$current_sender_acls['sender_acl_domains']['ro'],
|
mailbox('get', 'sender_acl_handles', $username)['sender_acl_domains']['ro'],
|
||||||
$current_sender_acls['sender_acl_addresses']['ro']
|
mailbox('get', 'sender_acl_handles', $username)['sender_acl_addresses']['ro']
|
||||||
);
|
);
|
||||||
// Get sender_acl items from POST array
|
// Get sender_acl items from POST array
|
||||||
// Set sender_acl_domain_admin to empty array if sender_acl contains "default" to trigger a reset
|
// Set sender_acl_domain_admin to empty array if sender_acl contains "default" to trigger a reset
|
||||||
@@ -3339,25 +3260,16 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
$stmt->execute(array(
|
$stmt->execute(array(
|
||||||
':username' => $username
|
':username' => $username
|
||||||
));
|
));
|
||||||
$sender_acl_handles = mailbox('get', 'sender_acl_handles', $username);
|
$fixed_sender_aliases = mailbox('get', 'sender_acl_handles', $username)['fixed_sender_aliases'];
|
||||||
$fixed_sender_aliases_allowed = $sender_acl_handles['fixed_sender_aliases_allowed'];
|
|
||||||
$fixed_sender_aliases_blocked = $sender_acl_handles['fixed_sender_aliases_blocked'];
|
|
||||||
|
|
||||||
foreach ($sender_acl_merged as $sender_acl) {
|
foreach ($sender_acl_merged as $sender_acl) {
|
||||||
$domain = ltrim($sender_acl, '@');
|
$domain = ltrim($sender_acl, '@');
|
||||||
if (is_valid_domain_name($domain)) {
|
if (is_valid_domain_name($domain)) {
|
||||||
$sender_acl = '@' . $domain;
|
$sender_acl = '@' . $domain;
|
||||||
}
|
}
|
||||||
|
// Don't add if allowed by alias
|
||||||
// Always add to sender_acl table to create explicit permission
|
if (in_array($sender_acl, $fixed_sender_aliases)) {
|
||||||
// Skip only if it's in allowed list (would be redundant)
|
|
||||||
// But DO add if it's in blocked list (creates override)
|
|
||||||
if (in_array($sender_acl, $fixed_sender_aliases_allowed)) {
|
|
||||||
// Skip: already allowed by sender_allowed=1, no need for sender_acl entry
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add to sender_acl (either override for blocked aliases, or grant for selectable ones)
|
|
||||||
$stmt = $pdo->prepare("INSERT INTO `sender_acl` (`send_as`, `logged_in_as`)
|
$stmt = $pdo->prepare("INSERT INTO `sender_acl` (`send_as`, `logged_in_as`)
|
||||||
VALUES (:sender_acl, :username)");
|
VALUES (:sender_acl, :username)");
|
||||||
$stmt->execute(array(
|
$stmt->execute(array(
|
||||||
@@ -3402,15 +3314,12 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
`quota` = :quota_b,
|
`quota` = :quota_b,
|
||||||
`authsource` = :authsource,
|
`authsource` = :authsource,
|
||||||
`attributes` = JSON_SET(`attributes`, '$.force_pw_update', :force_pw_update),
|
`attributes` = JSON_SET(`attributes`, '$.force_pw_update', :force_pw_update),
|
||||||
`attributes` = JSON_SET(`attributes`, '$.force_tfa', :force_tfa),
|
|
||||||
`attributes` = JSON_SET(`attributes`, '$.sogo_access', :sogo_access),
|
`attributes` = JSON_SET(`attributes`, '$.sogo_access', :sogo_access),
|
||||||
`attributes` = JSON_SET(`attributes`, '$.imap_access', :imap_access),
|
`attributes` = JSON_SET(`attributes`, '$.imap_access', :imap_access),
|
||||||
`attributes` = JSON_SET(`attributes`, '$.sieve_access', :sieve_access),
|
`attributes` = JSON_SET(`attributes`, '$.sieve_access', :sieve_access),
|
||||||
`attributes` = JSON_SET(`attributes`, '$.pop3_access', :pop3_access),
|
`attributes` = JSON_SET(`attributes`, '$.pop3_access', :pop3_access),
|
||||||
`attributes` = JSON_SET(`attributes`, '$.relayhost', :relayhost),
|
`attributes` = JSON_SET(`attributes`, '$.relayhost', :relayhost),
|
||||||
`attributes` = JSON_SET(`attributes`, '$.smtp_access', :smtp_access),
|
`attributes` = JSON_SET(`attributes`, '$.smtp_access', :smtp_access),
|
||||||
`attributes` = JSON_SET(`attributes`, '$.eas_access', :eas_access),
|
|
||||||
`attributes` = JSON_SET(`attributes`, '$.dav_access', :dav_access),
|
|
||||||
`attributes` = JSON_SET(`attributes`, '$.recovery_email', :recovery_email),
|
`attributes` = JSON_SET(`attributes`, '$.recovery_email', :recovery_email),
|
||||||
`attributes` = JSON_SET(`attributes`, '$.attribute_hash', :attribute_hash)
|
`attributes` = JSON_SET(`attributes`, '$.attribute_hash', :attribute_hash)
|
||||||
WHERE `username` = :username");
|
WHERE `username` = :username");
|
||||||
@@ -3420,14 +3329,11 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
':quota_b' => $quota_b,
|
':quota_b' => $quota_b,
|
||||||
':attribute_hash' => $attribute_hash,
|
':attribute_hash' => $attribute_hash,
|
||||||
':force_pw_update' => $force_pw_update,
|
':force_pw_update' => $force_pw_update,
|
||||||
':force_tfa' => $force_tfa,
|
|
||||||
':sogo_access' => $sogo_access,
|
':sogo_access' => $sogo_access,
|
||||||
':imap_access' => $imap_access,
|
':imap_access' => $imap_access,
|
||||||
':pop3_access' => $pop3_access,
|
':pop3_access' => $pop3_access,
|
||||||
':sieve_access' => $sieve_access,
|
':sieve_access' => $sieve_access,
|
||||||
':smtp_access' => $smtp_access,
|
':smtp_access' => $smtp_access,
|
||||||
':eas_access' => $eas_access,
|
|
||||||
':dav_access' => $dav_access,
|
|
||||||
':recovery_email' => $pw_recovery_email,
|
':recovery_email' => $pw_recovery_email,
|
||||||
':relayhost' => $relayhost,
|
':relayhost' => $relayhost,
|
||||||
':username' => $username,
|
':username' => $username,
|
||||||
@@ -3476,8 +3382,15 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
'msg' => array('mailbox_modified', $username)
|
'msg' => array('mailbox_modified', $username)
|
||||||
);
|
);
|
||||||
|
|
||||||
// Track affected mailboxes for SOGo update
|
try {
|
||||||
$update_sogo_mailboxes[] = $username;
|
update_sogo_static_view($username);
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
$_SESSION['return'][] = array(
|
||||||
|
'type' => 'danger',
|
||||||
|
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||||
|
'msg' => $e->getMessage()
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
break;
|
break;
|
||||||
@@ -3803,8 +3716,6 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
$attr['pop3_access'] = (in_array('pop3', $_data['protocol_access'])) ? 1 : 0;
|
$attr['pop3_access'] = (in_array('pop3', $_data['protocol_access'])) ? 1 : 0;
|
||||||
$attr['smtp_access'] = (in_array('smtp', $_data['protocol_access'])) ? 1 : 0;
|
$attr['smtp_access'] = (in_array('smtp', $_data['protocol_access'])) ? 1 : 0;
|
||||||
$attr['sieve_access'] = (in_array('sieve', $_data['protocol_access'])) ? 1 : 0;
|
$attr['sieve_access'] = (in_array('sieve', $_data['protocol_access'])) ? 1 : 0;
|
||||||
$attr['eas_access'] = (in_array('eas', $_data['protocol_access'])) ? 1 : 0;
|
|
||||||
$attr['dav_access'] = (in_array('dav', $_data['protocol_access'])) ? 1 : 0;
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
foreach ($is_now as $key => $value){
|
foreach ($is_now as $key => $value){
|
||||||
@@ -4106,9 +4017,6 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||||
'msg' => array('resource_modified', htmlspecialchars($name))
|
'msg' => array('resource_modified', htmlspecialchars($name))
|
||||||
);
|
);
|
||||||
|
|
||||||
// Track affected mailboxes for SOGo update
|
|
||||||
$update_sogo_mailboxes[] = $name;
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'domain_wide_footer':
|
case 'domain_wide_footer':
|
||||||
@@ -4237,22 +4145,13 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
$data['sender_acl_addresses']['rw'] = array();
|
$data['sender_acl_addresses']['rw'] = array();
|
||||||
$data['sender_acl_addresses']['selectable'] = array();
|
$data['sender_acl_addresses']['selectable'] = array();
|
||||||
$data['fixed_sender_aliases'] = array();
|
$data['fixed_sender_aliases'] = array();
|
||||||
$data['fixed_sender_aliases_allowed'] = array();
|
|
||||||
$data['fixed_sender_aliases_blocked'] = array();
|
|
||||||
$data['external_sender_aliases'] = array();
|
$data['external_sender_aliases'] = array();
|
||||||
// Fixed addresses - split by sender_allowed status
|
// Fixed addresses
|
||||||
$stmt = $pdo->prepare("SELECT `address`, `sender_allowed` FROM `alias` WHERE `goto` REGEXP :goto AND `address` NOT LIKE '@%'");
|
$stmt = $pdo->prepare("SELECT `address` FROM `alias` WHERE `goto` REGEXP :goto AND `address` NOT LIKE '@%'");
|
||||||
$stmt->execute(array(':goto' => '(^|,)'.preg_quote($_data, '/').'($|,)'));
|
$stmt->execute(array(':goto' => '(^|,)'.preg_quote($_data, '/').'($|,)'));
|
||||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
while ($row = array_shift($rows)) {
|
while ($row = array_shift($rows)) {
|
||||||
// Keep old array for backward compatibility
|
|
||||||
$data['fixed_sender_aliases'][] = $row['address'];
|
$data['fixed_sender_aliases'][] = $row['address'];
|
||||||
// Split into allowed/blocked for proper display
|
|
||||||
if ($row['sender_allowed'] == '1') {
|
|
||||||
$data['fixed_sender_aliases_allowed'][] = $row['address'];
|
|
||||||
} else {
|
|
||||||
$data['fixed_sender_aliases_blocked'][] = $row['address'];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
$stmt = $pdo->prepare("SELECT CONCAT(`local_part`, '@', `alias_domain`.`alias_domain`) AS `alias_domain_alias` FROM `mailbox`, `alias_domain`
|
$stmt = $pdo->prepare("SELECT CONCAT(`local_part`, '@', `alias_domain`.`alias_domain`) AS `alias_domain_alias` FROM `mailbox`, `alias_domain`
|
||||||
WHERE `alias_domain`.`target_domain` = `mailbox`.`domain`
|
WHERE `alias_domain`.`target_domain` = `mailbox`.`domain`
|
||||||
@@ -4685,12 +4584,10 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
`description`,
|
`description`,
|
||||||
`validity`,
|
`validity`,
|
||||||
`created`,
|
`created`,
|
||||||
`modified`,
|
`modified`
|
||||||
`permanent`
|
|
||||||
FROM `spamalias`
|
FROM `spamalias`
|
||||||
WHERE `goto` = :username
|
WHERE `goto` = :username
|
||||||
AND (`validity` >= :unixnow
|
AND `validity` >= :unixnow");
|
||||||
OR `permanent` != 0)");
|
|
||||||
$stmt->execute(array(':username' => $_data, ':unixnow' => time()));
|
$stmt->execute(array(':username' => $_data, ':unixnow' => time()));
|
||||||
$tladata = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
$tladata = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
return $tladata;
|
return $tladata;
|
||||||
@@ -4706,10 +4603,10 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
$_data = $_SESSION['mailcow_cc_username'];
|
$_data = $_SESSION['mailcow_cc_username'];
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
if ($redis->hGet('RCPT_WANTS_SUBJECT_TAG', $_data)) {
|
if ($valkey->hGet('RCPT_WANTS_SUBJECT_TAG', $_data)) {
|
||||||
return "subject";
|
return "subject";
|
||||||
}
|
}
|
||||||
elseif ($redis->hGet('RCPT_WANTS_SUBFOLDER_TAG', $_data)) {
|
elseif ($valkey->hGet('RCPT_WANTS_SUBFOLDER_TAG', $_data)) {
|
||||||
return "subfolder";
|
return "subfolder";
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -4720,7 +4617,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'danger',
|
'type' => 'danger',
|
||||||
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||||
'msg' => array('redis_error', $e)
|
'msg' => array('valkey_error', $e)
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -4812,7 +4709,6 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
`internal`,
|
`internal`,
|
||||||
`active`,
|
`active`,
|
||||||
`sogo_visible`,
|
`sogo_visible`,
|
||||||
`sender_allowed`,
|
|
||||||
`created`,
|
`created`,
|
||||||
`modified`
|
`modified`
|
||||||
FROM `alias`
|
FROM `alias`
|
||||||
@@ -4846,7 +4742,6 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
$aliasdata['active_int'] = $row['active'];
|
$aliasdata['active_int'] = $row['active'];
|
||||||
$aliasdata['sogo_visible'] = $row['sogo_visible'];
|
$aliasdata['sogo_visible'] = $row['sogo_visible'];
|
||||||
$aliasdata['sogo_visible_int'] = $row['sogo_visible'];
|
$aliasdata['sogo_visible_int'] = $row['sogo_visible'];
|
||||||
$aliasdata['sender_allowed'] = $row['sender_allowed'];
|
|
||||||
$aliasdata['created'] = $row['created'];
|
$aliasdata['created'] = $row['created'];
|
||||||
$aliasdata['modified'] = $row['modified'];
|
$aliasdata['modified'] = $row['modified'];
|
||||||
if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $aliasdata['domain'])) {
|
if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $aliasdata['domain'])) {
|
||||||
@@ -5267,7 +5162,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
$stmt = $pdo->prepare("SELECT COALESCE(SUM(`quota`), 0) as `in_use` FROM `mailbox` WHERE (`kind` = '' OR `kind` = NULL) AND `domain` = :domain AND `username` != :username");
|
$stmt = $pdo->prepare("SELECT COALESCE(SUM(`quota`), 0) as `in_use` FROM `mailbox` WHERE (`kind` = '' OR `kind` = NULL) AND `domain` = :domain AND `username` != :username");
|
||||||
$stmt->execute(array(':domain' => $row['domain'], ':username' => $_data));
|
$stmt->execute(array(':domain' => $row['domain'], ':username' => $_data));
|
||||||
$MailboxUsage = $stmt->fetch(PDO::FETCH_ASSOC);
|
$MailboxUsage = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
$stmt = $pdo->prepare("SELECT IFNULL(COUNT(`address`), 0) AS `sa_count` FROM `spamalias` WHERE `goto` = :address AND (`validity` >= :unixnow OR `permanent` != 0)");
|
$stmt = $pdo->prepare("SELECT IFNULL(COUNT(`address`), 0) AS `sa_count` FROM `spamalias` WHERE `goto` = :address AND `validity` >= :unixnow");
|
||||||
$stmt->execute(array(':address' => $_data, ':unixnow' => time()));
|
$stmt->execute(array(':address' => $_data, ':unixnow' => time()));
|
||||||
$SpamaliasUsage = $stmt->fetch(PDO::FETCH_ASSOC);
|
$SpamaliasUsage = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
$mailboxdata['max_new_quota'] = ($DomainQuota['quota'] * 1048576) - $MailboxUsage['in_use'];
|
$mailboxdata['max_new_quota'] = ($DomainQuota['quota'] * 1048576) - $MailboxUsage['in_use'];
|
||||||
@@ -5741,14 +5636,14 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
$stmt = $pdo->query("DELETE FROM `admin` WHERE `superadmin` = 0 AND `username` NOT IN (SELECT `username`FROM `domain_admins`);");
|
$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`);");
|
$stmt = $pdo->query("DELETE FROM `da_acl` WHERE `username` NOT IN (SELECT `username`FROM `domain_admins`);");
|
||||||
try {
|
try {
|
||||||
$redis->hDel('DOMAIN_MAP', $domain);
|
$valkey->hDel('DOMAIN_MAP', $domain);
|
||||||
$redis->hDel('RL_VALUE', $domain);
|
$valkey->hDel('RL_VALUE', $domain);
|
||||||
}
|
}
|
||||||
catch (RedisException $e) {
|
catch (RedisException $e) {
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'danger',
|
'type' => 'danger',
|
||||||
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||||
'msg' => array('redis_error', $e)
|
'msg' => array('valkey_error', $e)
|
||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -5813,18 +5708,6 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Track affected mailboxes for SOGo update (capture before deletion)
|
|
||||||
if (!empty($alias_data['goto'])) {
|
|
||||||
$gotos = array_map('trim', explode(',', $alias_data['goto']));
|
|
||||||
foreach ($gotos as $g) {
|
|
||||||
if (filter_var($g, FILTER_VALIDATE_EMAIL) &&
|
|
||||||
!in_array($g, array('null@localhost', 'spam@localhost', 'ham@localhost'))) {
|
|
||||||
$update_sogo_mailboxes[] = $g;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$stmt = $pdo->prepare("DELETE FROM `alias` WHERE `id` = :id");
|
$stmt = $pdo->prepare("DELETE FROM `alias` WHERE `id` = :id");
|
||||||
$stmt->execute(array(
|
$stmt->execute(array(
|
||||||
':id' => $alias_data['id']
|
':id' => $alias_data['id']
|
||||||
@@ -5886,14 +5769,14 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
':alias_domain' => $alias_domain,
|
':alias_domain' => $alias_domain,
|
||||||
));
|
));
|
||||||
try {
|
try {
|
||||||
$redis->hDel('DOMAIN_MAP', $alias_domain);
|
$valkey->hDel('DOMAIN_MAP', $alias_domain);
|
||||||
$redis->hDel('RL_VALUE', $domain);
|
$valkey->hDel('RL_VALUE', $domain);
|
||||||
}
|
}
|
||||||
catch (RedisException $e) {
|
catch (RedisException $e) {
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'danger',
|
'type' => 'danger',
|
||||||
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||||
'msg' => array('redis_error', $e)
|
'msg' => array('valkey_error', $e)
|
||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -6072,25 +5955,31 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
$redis->hDel('RL_VALUE', $username);
|
$valkey->hDel('RL_VALUE', $username);
|
||||||
}
|
}
|
||||||
catch (RedisException $e) {
|
catch (RedisException $e) {
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'danger',
|
'type' => 'danger',
|
||||||
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||||
'msg' => array('redis_error', $e)
|
'msg' => array('valkey_error', $e)
|
||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
update_sogo_static_view($username);
|
||||||
|
}catch (PDOException $e) {
|
||||||
|
$_SESSION['return'][] = array(
|
||||||
|
'type' => 'success',
|
||||||
|
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||||
|
'msg' => $e->getMessage()
|
||||||
|
);
|
||||||
|
}
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'success',
|
'type' => 'success',
|
||||||
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||||
'msg' => array('mailbox_removed', htmlspecialchars($username))
|
'msg' => array('mailbox_removed', htmlspecialchars($username))
|
||||||
);
|
);
|
||||||
|
|
||||||
// Track affected mailboxes for SOGo update
|
|
||||||
$update_sogo_mailboxes[] = $username;
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
break;
|
break;
|
||||||
@@ -6192,9 +6081,6 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||||
'msg' => array('resource_removed', htmlspecialchars($name))
|
'msg' => array('resource_removed', htmlspecialchars($name))
|
||||||
);
|
);
|
||||||
|
|
||||||
// Track affected mailboxes for SOGo update
|
|
||||||
$update_sogo_mailboxes[] = $name;
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'tags_domain':
|
case 'tags_domain':
|
||||||
@@ -6301,21 +6187,9 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if ($_action != 'get' && in_array($_type, array('domain', 'alias', 'alias_domain', 'resource', 'mailbox')) && getenv('SKIP_SOGO') != "y") {
|
if ($_action != 'get' && in_array($_type, array('domain', 'alias', 'alias_domain', 'resource')) && getenv('SKIP_SOGO') != "y") {
|
||||||
try {
|
try {
|
||||||
if (($_type == 'alias' || $_type == 'resource' || $_type == 'mailbox') && !empty($update_sogo_mailboxes)) {
|
update_sogo_static_view();
|
||||||
// INCREMENTAL UPDATE: Update only affected mailboxes/resources
|
|
||||||
$update_sogo_mailboxes = array_unique($update_sogo_mailboxes);
|
|
||||||
foreach ($update_sogo_mailboxes as $mailbox) {
|
|
||||||
update_sogo_static_view($mailbox);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// FULL REBUILD: For domain and alias_domain operations or if no tracked mailboxes
|
|
||||||
// Domain operations affect all mailboxes
|
|
||||||
// Alias_domain operations affect entire target domain
|
|
||||||
update_sogo_static_view();
|
|
||||||
}
|
|
||||||
}catch (PDOException $e) {
|
}catch (PDOException $e) {
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'success',
|
'type' => 'success',
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
function oauth2($_action, $_type, $_data = null) {
|
function oauth2($_action, $_type, $_data = null) {
|
||||||
global $pdo;
|
global $pdo;
|
||||||
global $redis;
|
global $valkey;
|
||||||
global $lang;
|
global $lang;
|
||||||
if ($_SESSION['mailcow_cc_role'] != "admin") {
|
if ($_SESSION['mailcow_cc_role'] != "admin") {
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user