1
0
mirror of https://github.com/mailcow/mailcow-dockerized.git synced 2026-06-14 02:20:28 +00:00

Compare commits

...

72 Commits

Author SHA1 Message Date
FreddleSpl0it c0f06bfc52 Merge branch 'staging' into feat/valkey 2025-10-10 12:40:41 +02:00
milkmaker 79cf0abc6e [Web] Updated lang.zh-cn.json (#6826)
Co-authored-by: Easton Man <me@eastonman.com>
2025-10-09 19:54:12 +02:00
Olavo Rocha Neto 7de70322d6 Update pt-br lang (#6803)
* [Web] Updated lang.si-si.json

Co-authored-by: Matjaž Tekavec <matjaz@moj-svet.si>

* Update pt-br lang

* Complimentary adjustments

* Revert "[Web] Updated lang.si-si.json"

This reverts commit b23848e0f2.

---------

Co-authored-by: milkmaker <milkmaker@mailcow.de>
Co-authored-by: Matjaž Tekavec <matjaz@moj-svet.si>
2025-10-09 19:36:36 +02:00
DerLinkman 417835dea8 netfilter: improve logging and mark iptables-legacy as deprecated 2025-10-09 16:37:05 +02:00
FreddleSpl0it df4d3bb6e0 [Web] Fix dashboard host stats 2025-10-07 11:41:57 +02:00
FreddleSpl0it 455ef084b4 [Web] clear old app_passwd log entries 2025-10-07 10:37:44 +02:00
FreddleSpl0it c2948735f2 [Web] clear old app_passwd log entries 2025-10-07 10:18:07 +02:00
FreddleSpl0it 1ef0149076 [Web] make SameSite policy and cookie name configurable via vars.local.inc 2025-10-06 11:00:03 +02:00
FreddleSpl0it 922d173540 [Web] include hostname in default website title 2025-10-06 10:58:35 +02:00
renovate[bot] fd088cb504 chore(deps): update actions/stale action to v10.1.0 (#6806) 2025-10-04 14:13:48 +02:00
Valentin Brandl 721ee2394e Update variable name for prometheus-exporter security token (#6776)
* update variable name for prometheus-exporter security token

* update `MAILCOW_EXPORTER_TOKEN_DISABLE` variable name
2025-10-03 18:03:03 +01:00
Colin Kubon c217be06c6 scripts: make sure /etc/docker exists (#6791) 2025-10-02 09:24:06 +02:00
Jonas 871c422ec1 Fix typos in config (#6792)
Co-authored-by: DerLinkman <niklas.meyer@servercow.de>
2025-10-02 09:22:35 +02:00
sdsys-ch 3cc28af607 [Helper] Fix cold-standby script to support digits and override files (#6800)
This commit fixes two bugs in the cold-standby script:

1. Support digits in COMPOSE_PROJECT_NAME
   The script was stripping digits from COMPOSE_PROJECT_NAME, while
   backup_and_restore.sh (fixed in a71d991c) correctly supports them.
   Added '0-9' to the tr character set to align behavior.

2. Support docker-compose.override.yml on remote
   Lines 172 and 287 explicitly used '-f docker-compose.yml' which
   causes Docker Compose to ignore docker-compose.override.yml even
   when present. Changed to 'cd && compose' pattern (matching line 296)
   to auto-discover override files.

   Impact: Users with custom volumes/services in override file would
   experience silent failures - volumes not created, images not pulled,
   data syncing to wrong locations.

Both fixes ensure cold-standby works correctly with standard Docker
Compose conventions and user customizations.

Co-authored-by: Christophe Neuerburg <c.neuerburg@sdsys.ch>
2025-10-02 09:21:26 +02:00
milkmaker 796e131c3a update postscreen_access.cidr (#6801) 2025-10-01 11:14:57 +02:00
milkmaker c51a769aec [Web] Updated lang.si-si.json (#6794)
Co-authored-by: Matjaž Tekavec <matjaz@moj-svet.si>
2025-09-29 18:10:39 +02:00
FreddleSpl0it 45a61755a5 Merge pull request #6777 from patschi/enable-https-redirect-default
Enable HTTPS redirect by default on new setups
2025-09-29 11:56:46 +02:00
FreddleSpl0it 769c57c355 Merge pull request #6779 from patschi/remove-debug-consolelog
Remove debug console.log calls
2025-09-29 11:54:23 +02:00
FreddleSpl0it 2e7eb7c0fd Merge pull request #6780 from patschi/fix-pwcomplexity-apppasswds
Fixed password complexity check for AppPasswords creation/edit
2025-09-29 11:53:26 +02:00
FreddleSpl0it 4c83147d01 Merge pull request #6781 from patschi/pw-field-name-consistency
Rename password fields for AppPasswords same way for consistency
2025-09-29 11:52:08 +02:00
FreddleSpl0it ca0bec4fc2 Merge pull request #6782 from patschi/fix-footer-escape
Fixed wrong footer escaping for certain characters
2025-09-29 11:45:42 +02:00
FreddleSpl0it 6f50dd17da Merge pull request #6786 from patschi/fix-sql-typo
Fix several SQL statements
2025-09-29 11:39:30 +02:00
FreddleSpl0it 4a331929d0 Merge pull request #6787 from patschi/hide-relayhosts-if-no-acl
Hide relayhosts when ACL does not allow
2025-09-29 11:38:52 +02:00
FreddleSpl0it 748bc893b6 Merge pull request #6788 from patschi/lastmodified-default-value
Show "Never" by default if no last-modified date saved
2025-09-29 11:37:52 +02:00
FreddleSpl0it e462602ddc Merge pull request #6789 from patschi/domain-descr-readonly-when-no-acl
Make domain description field readonly when no ACL
2025-09-29 11:36:42 +02:00
milkmaker 4e0f435d12 [Web] Updated lang.si-si.json (#6793) 2025-09-28 15:12:14 +02:00
milkmaker 46f0581936 [Web] Updated lang.si-si.json (#6790)
Co-authored-by: Matjaž Tekavec <matjaz@moj-svet.si>
2025-09-26 19:14:07 +02:00
Patrik Kernstock 20f04ecf6b Make domain description field readonly when no ACL 2025-09-26 17:13:24 +02:00
Patrik Kernstock ff43799763 Show "Never" by default if no last-modified date 2025-09-26 17:02:22 +02:00
Patrik Kernstock 85ca197615 Hide relayhosts when ACL does not allow 2025-09-26 16:50:58 +02:00
Patrik Kernstock d06d23bbaf Fix several SQL statements 2025-09-26 14:58:04 +02:00
Patrik Kernstock 702ed85dfd Fixed footer escaping 2025-09-26 14:41:19 +02:00
milkmaker 8abe74a562 [Web] Updated lang.si-si.json (#6785)
Co-authored-by: Matjaž Tekavec <matjaz@moj-svet.si>
2025-09-26 10:57:32 +02:00
Patrik Kernstock 5c5287ca21 Fixed wrong footer escaping 2025-09-26 04:04:45 +02:00
Patrik Kernstock ce219668cf Rename AppPasswds fields uniquely like 'add' 2025-09-26 03:37:49 +02:00
Patrik Kernstock 5b1b49a418 Fixed password complexity check for AppPasswords 2025-09-26 02:37:02 +02:00
Patrik Kernstock 8978a9ad79 Remove debug console.log() lines 2025-09-26 02:13:22 +02:00
Patrik Kernstock 5f4a4fd759 Removed new lines for consistency 2025-09-26 01:14:33 +02:00
Patrik Kernstock 171c591da4 Enable REDIRECT_HTTP=y by default 2025-09-26 01:14:23 +02:00
FreddleSpl0it 9133b9899c Merge pull request #6764 from patschi/tools-install-clear-msg
Clearer message to install required tool, e.g. jq
2025-09-25 09:00:41 +02:00
FreddleSpl0it 701c9fb1b4 Merge pull request #6772 from patschi/update-issue-template
Update GitHub's issue template
2025-09-25 08:53:18 +02:00
Patrik Kernstock eabd22188b Re-intend checkboxes 2025-09-24 21:20:48 +02:00
Patrik Kernstock 7028619742 Update GitHub's issue template 2025-09-24 21:17:29 +02:00
Patrik Kernstock c915bf2ee2 Add docs link to get_installed_tools() message 2025-09-24 19:06:47 +02:00
milkmaker 011edd5ac9 [Web] Updated lang.si-si.json (#6771)
Co-authored-by: Matjaž Tekavec <matjaz@moj-svet.si>
2025-09-24 17:36:09 +02:00
FreddleSpl0it 7ba3de4ced Merge pull request #6767 from mailcow/fix/rename-phpsessid
[Web] Rename PHP Cookie to MCSESSID
2025-09-23 12:41:01 +02:00
FreddleSpl0it 8ead77083f [Web] Rename PHP Cookie to MCSESSID 2025-09-23 12:39:48 +02:00
FreddleSpl0it b2774fb50b Merge pull request #6766 from mailcow/fix/samesite-cookie
[Web] set cookie SameSite attribute to Lax
2025-09-23 12:36:11 +02:00
FreddleSpl0it 4440bd46ad [Web] set cookie SameSite attribute to Lax 2025-09-23 12:24:25 +02:00
FreddleSpl0it 28985973eb [Web] Revert - allow "*" as wildcard domain 2025-09-23 10:07:33 +02:00
Christian 🦄 f2c4697ca3 Fixed typo in lang de-de (#6765) 2025-09-22 22:45:54 +01:00
Patrik Kernstock 383b5affb5 More clearer message to install required tool 2025-09-22 19:49:31 +02:00
FreddleSpl0it ed4dcff63b [Web] allow "*" as wildcard domain 2025-09-22 14:42:14 +02:00
FreddleSpl0it caca32bbba Merge pull request #6759 from mailcow/fix/6720
[Web] Allow wildcard subdomains for MTA-STS
2025-09-22 14:20:36 +02:00
FreddleSpl0it d31e74c778 Merge pull request #6760 from mailcow/fix/6739
[Web] Remove Port from HTTP_HOST
2025-09-22 14:20:15 +02:00
FreddleSpl0it 6c00e29276 Merge pull request #6762 from mailcow/fix/6740
[Nginx] do not invert ENABLE_IPV6
2025-09-22 14:19:57 +02:00
FreddleSpl0it 9940c503a2 [Nginx] do not invert ENABLE_IPV6 2025-09-22 14:16:42 +02:00
FreddleSpl0it 4b2862cb3c [Web] Remove Port from HTTP_HOST 2025-09-22 14:07:17 +02:00
FreddleSpl0it a36485f0f1 [Web] Allow wildcard subdomains for MTA-STS 2025-09-22 13:55:18 +02:00
FreddleSpl0it 78168ee80a Merge pull request #6758 from mailcow/feat/sogo-url-encryption
[SOGo][Web] SOGo URL Encryption support
2025-09-22 13:32:58 +02:00
FreddleSpl0it 610609378f [SOGo][Web] Set URL encryption key in mailcow.conf 2025-09-22 12:58:05 +02:00
FreddleSpl0it 260906e350 [SOGo][Web] Enable SOGo URL Encryption 2025-09-22 12:28:09 +02:00
milkmaker 2891bbf82a Translations update from Weblate (#6749)
* [Web] Updated lang.cs-cz.json

Co-authored-by: Filip Hajny <filip@hajny.net>

* [Web] Updated lang.lv-lv.json

Co-authored-by: Edgars Andersons <Edgars+Mailcow+Weblate@gaitenis.id.lv>

---------

Co-authored-by: Filip Hajny <filip@hajny.net>
Co-authored-by: Edgars Andersons <Edgars+Mailcow+Weblate@gaitenis.id.lv>
2025-09-16 18:24:12 +02:00
milkmaker eb26bcbc94 Translations update from Weblate (#6743)
* [Web] Updated lang.zh-cn.json

Co-authored-by: Easton Man <me@eastonman.com>

* [Web] Updated lang.si-si.json

[Web] Updated lang.si-si.json

Co-authored-by: Matjaž Tekavec <matjaz@moj-svet.si>
Co-authored-by: milkmaker <milkmaker@mailcow.de>

---------

Co-authored-by: Easton Man <me@eastonman.com>
Co-authored-by: Matjaž Tekavec <matjaz@moj-svet.si>
2025-09-13 21:41:59 +02:00
patr_ 84e230de8f [Nginx] fix: Disable IPv6 support in Nginx configuration (#6736)
Co-authored-by: patr_ <patbernh@gmail.com>
2025-09-12 11:17:18 +02:00
FreddleSpl0it f67a12d157 Merge pull request #6726 from mailcow/fix/6135
[Web] remove unused bcc dest column from alias table
2025-09-11 13:50:25 +02:00
FreddleSpl0it 34b48eedfc Merge pull request #6727 from mailcow/fix/6423
[SOGo] Drop deprecated `sogo_update_password` sql trigger if it still exists
2025-09-11 13:50:05 +02:00
FreddleSpl0it 0d900d4fc8 [SOGo] Drop deprecated sogo_update_password sql trigger if it still exists 2025-09-11 11:01:50 +02:00
FreddleSpl0it 642ac6d02c [Web] remove unused bcc dest column from alias table 2025-09-11 10:34:35 +02:00
DerLinkman 94c1a6c4e1 scripts: ipv6_controller improvement + fix modules handling (#6722)
* Fix subscript handling for modules

* ipv6: detect case when link local is present

* v6-controller: removed fixed-cidr for docker 28+
2025-09-10 16:20:58 +02:00
FreddleSpl0it 0698159f07 Add redis-to-valkey migratior 2025-02-28 15:49:28 +01:00
FreddleSpl0it c27000215e migrate from redis to valkey 2025-02-28 15:36:19 +01:00
120 changed files with 1686 additions and 1272 deletions
+37 -32
View File
@@ -11,22 +11,35 @@ body:
required: true required: true
- type: checkboxes - type: checkboxes
attributes: attributes:
label: I've found a bug and checked that ... label: Checklist prior issue creation
description: Prior to placing the issue, please check following:** *(fill out each checkbox with an `X` once done)* description: Prior to creating the issue...
options: options:
- label: ... I understand that not following the below instructions will result in immediate closure and/or deletion of my issue. - label: I understand that failure to follow below instructions may cause this issue to be closed.
required: true required: true
- label: ... I have understood that this bug report is dedicated for bugs, and not for support-related inquiries. - label: I understand that vague, incomplete or inaccurate information may cause this issue to be closed.
required: true required: true
- label: ... I have understood that answers are voluntary and community-driven, and not commercial support. - label: I understand that this form is intended solely for reporting software bugs and not for support-related inquiries.
required: true required: true
- label: ... I have verified that my issue has not been already answered in the past. I also checked previous [issues](https://github.com/mailcow/mailcow-dockerized/issues). - label: I understand that all responses are voluntary and community-driven, and do not constitute commercial support.
required: true
- label: I confirm that I have reviewed previous [issues](https://github.com/mailcow/mailcow-dockerized/issues) to ensure this matter has not already been addressed.
required: true
- label: I confirm that my environment meets all [prerequisite requirements](https://docs.mailcow.email/getstarted/prerequisite-system/) as specified in the official documentation.
required: true required: true
- type: textarea - type: textarea
attributes: attributes:
label: Description label: Description
description: Please provide a brief description of the bug in 1-2 sentences. If applicable, add screenshots to help explain your problem. Very useful for bugs in mailcow UI. description: Please provide a brief description of the bug. If applicable, add screenshots to help explain your problem. (Very useful for bugs in mailcow UI.)
render: plain text validations:
required: true
- type: textarea
attributes:
label: "Steps to reproduce:"
description: "Please describe the steps to reproduce the bug. Screenshots can be added, if helpful."
placeholder: |-
1. ...
2. ...
3. ...
validations: validations:
required: true required: true
- type: textarea - type: textarea
@@ -36,45 +49,36 @@ body:
render: plain text render: plain text
validations: validations:
required: true required: true
- type: textarea
attributes:
label: "Steps to reproduce:"
description: "Please describe the steps to reproduce the bug. Screenshots can be added, if helpful."
render: plain text
placeholder: |-
1. ...
2. ...
3. ...
validations:
required: true
- type: markdown - type: markdown
attributes: attributes:
value: | value: |
## System information ## System information
### In this stage we would kindly ask you to attach general system information about your setup. In this stage we would kindly ask you to attach general system information about your setup.
- type: dropdown - type: dropdown
attributes: attributes:
label: "Which branch are you using?" label: "Which branch are you using?"
description: "#### `git rev-parse --abbrev-ref HEAD`" description: "#### Run: `git rev-parse --abbrev-ref HEAD`"
multiple: false multiple: false
options: options:
- master - master (stable)
- staging
- nightly - nightly
validations: validations:
required: true required: true
- type: dropdown - type: dropdown
attributes: attributes:
label: "Which architecture are you using?" label: "Which architecture are you using?"
description: "#### `uname -m`" description: "#### Run: `uname -m`"
multiple: false multiple: false
options: options:
- x86 - x86_64
- ARM64 (aarch64) - ARM64 (aarch64)
validations: validations:
required: true required: true
- type: input - type: input
attributes: attributes:
label: "Operating System:" label: "Operating System:"
description: "#### Run: `lsb_release -ds`"
placeholder: "e.g. Ubuntu 22.04 LTS" placeholder: "e.g. Ubuntu 22.04 LTS"
validations: validations:
required: true required: true
@@ -93,43 +97,44 @@ body:
- type: input - type: input
attributes: attributes:
label: "Virtualization technology:" label: "Virtualization technology:"
placeholder: "KVM, VMware, Xen, etc - **LXC and OpenVZ are not supported**" description: "LXC and OpenVZ are not supported!"
placeholder: "KVM, VMware ESXi, Xen, etc"
validations: validations:
required: true required: true
- type: input - type: input
attributes: attributes:
label: "Docker version:" label: "Docker version:"
description: "#### `docker version`" description: "#### Run: `docker version`"
placeholder: "20.10.21" placeholder: "20.10.21"
validations: validations:
required: true required: true
- type: input - type: input
attributes: attributes:
label: "docker-compose version or docker compose version:" label: "docker-compose version or docker compose version:"
description: "#### `docker-compose version` or `docker compose version`" description: "#### Run: `docker-compose version` or `docker compose version`"
placeholder: "v2.12.2" placeholder: "v2.12.2"
validations: validations:
required: true required: true
- type: input - type: input
attributes: attributes:
label: "mailcow version:" label: "mailcow version:"
description: "#### ```git describe --tags `git rev-list --tags --max-count=1` ```" description: "#### Run: ```git describe --tags `git rev-list --tags --max-count=1` ```"
placeholder: "2022-08" placeholder: "2022-08x"
validations: validations:
required: true required: true
- type: input - type: input
attributes: attributes:
label: "Reverse proxy:" label: "Reverse proxy:"
placeholder: "e.g. Nginx/Traefik" placeholder: "e.g. nginx/Traefik, or none"
validations: validations:
required: true required: true
- type: textarea - type: textarea
attributes: attributes:
label: "Logs of git diff:" label: "Logs of git diff:"
description: "#### Output of `git diff origin/master`, any other changes to the code? If so, **please post them**:" description: "#### Output of `git diff origin/master`, any other changes to the code? Sanitize if needed. If so, **please post them**:"
render: plain text render: plain text
validations: validations:
required: true required: false
- type: textarea - type: textarea
attributes: attributes:
label: "Logs of iptables -L -vn:" label: "Logs of iptables -L -vn:"
@@ -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.0.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
+7 -1
View File
@@ -17,7 +17,13 @@ caller="${BASH_SOURCE[1]##*/}"
get_installed_tools(){ get_installed_tools(){
for bin in openssl curl docker git awk sha1sum grep cut jq; do for bin in openssl curl docker git awk sha1sum grep cut jq; do
if [[ -z $(command -v ${bin}) ]]; then echo "Cannot find ${bin}, exiting..."; exit 1; fi if [[ -z $(command -v ${bin}) ]]; then
echo "Error: Cannot find command '${bin}'. Cannot proceed."
echo "Solution: Please review system requirements and install requirements. Then, re-run the script."
echo "See System Requirements: https://docs.mailcow.email/getstarted/install/"
echo "Exiting..."
exit 1
fi
done done
if grep --help 2>&1 | head -n 1 | grep -q -i "busybox"; then echo -e "${LIGHT_RED}BusyBox grep detected, please install gnu grep, \"apk add --no-cache --upgrade grep\"${NC}"; exit 1; fi if grep --help 2>&1 | head -n 1 | grep -q -i "busybox"; then echo -e "${LIGHT_RED}BusyBox grep detected, please install gnu grep, \"apk add --no-cache --upgrade grep\"${NC}"; exit 1; fi
+110 -39
View File
@@ -5,14 +5,65 @@
# 1) Check if the host supports IPv6 # 1) Check if the host supports IPv6
get_ipv6_support() { get_ipv6_support() {
if grep -qs '^1' /proc/sys/net/ipv6/conf/all/disable_ipv6 2>/dev/null \ # ---- helper: probe external IPv6 connectivity without DNS ----
|| ! ip -6 route show default &>/dev/null; then _probe_ipv6_connectivity() {
# Use literal, always-on IPv6 echo responders (no DNS required)
local PROBE_IPS=("2001:4860:4860::8888" "2606:4700:4700::1111")
local ip rc=1
for ip in "${PROBE_IPS[@]}"; do
if command -v ping6 &>/dev/null; then
ping6 -c1 -W2 "$ip" &>/dev/null || ping6 -c1 -w2 "$ip" &>/dev/null
rc=$?
elif command -v ping &>/dev/null; then
ping -6 -c1 -W2 "$ip" &>/dev/null || ping -6 -c1 -w2 "$ip" &>/dev/null
rc=$?
else
rc=1
fi
[[ $rc -eq 0 ]] && return 0
done
return 1
}
if [[ ! -f /proc/net/if_inet6 ]] || grep -qs '^1' /proc/sys/net/ipv6/conf/all/disable_ipv6 2>/dev/null; then
DETECTED_IPV6=false DETECTED_IPV6=false
echo -e "${YELLOW}IPv6 not detected on host ${LIGHT_RED}disabling IPv6 support${YELLOW}.${NC}" echo -e "${YELLOW}IPv6 not detected on host ${LIGHT_RED}IPv6 is administratively disabled${YELLOW}.${NC}"
else return
DETECTED_IPV6=true
echo -e "IPv6 detected on host ${LIGHT_GREEN}leaving IPv6 support enabled${YELLOW}.${NC}"
fi fi
if ip -6 route show default 2>/dev/null | grep -qE '^default'; then
echo -e "${YELLOW}Default IPv6 route found testing external IPv6 connectivity...${NC}"
if _probe_ipv6_connectivity; then
DETECTED_IPV6=true
echo -e "IPv6 detected on host ${LIGHT_GREEN}leaving IPv6 support enabled${YELLOW}.${NC}"
else
DETECTED_IPV6=false
echo -e "${YELLOW}Default IPv6 route present but external IPv6 connectivity failed ${LIGHT_RED}disabling IPv6 support${YELLOW}.${NC}"
fi
return
fi
if ip -6 addr show scope global 2>/dev/null | grep -q 'inet6'; then
DETECTED_IPV6=false
echo -e "${YELLOW}Global IPv6 address present but no default route ${LIGHT_RED}disabling IPv6 support${YELLOW}.${NC}"
return
fi
if ip -6 addr show scope link 2>/dev/null | grep -q 'inet6'; then
echo -e "${YELLOW}Only link-local IPv6 addresses found testing external IPv6 connectivity...${NC}"
if _probe_ipv6_connectivity; then
DETECTED_IPV6=true
echo -e "External IPv6 connectivity available ${LIGHT_GREEN}leaving IPv6 support enabled${YELLOW}.${NC}"
else
DETECTED_IPV6=false
echo -e "${YELLOW}Only link-local IPv6 present and no external connectivity ${LIGHT_RED}disabling IPv6 support${YELLOW}.${NC}"
fi
return
fi
DETECTED_IPV6=false
echo -e "${YELLOW}IPv6 not detected on host ${LIGHT_RED}disabling IPv6 support${YELLOW}.${NC}"
} }
# 2) Ensure Docker daemon.json has (or create) the required IPv6 settings # 2) Ensure Docker daemon.json has (or create) the required IPv6 settings
@@ -21,7 +72,7 @@ docker_daemon_edit(){
DOCKER_MAJOR=$(docker version --format '{{.Server.Version}}' 2>/dev/null | cut -d. -f1) DOCKER_MAJOR=$(docker version --format '{{.Server.Version}}' 2>/dev/null | cut -d. -f1)
MISSING=() MISSING=()
_has_kv() { grep -Eq "\"$1\"\s*:\s*$2" "$DOCKER_DAEMON_CONFIG" 2>/dev/null; } _has_kv() { grep -Eq "\"$1\"[[:space:]]*:[[:space:]]*$2" "$DOCKER_DAEMON_CONFIG" 2>/dev/null; }
if [[ -f "$DOCKER_DAEMON_CONFIG" ]]; then if [[ -f "$DOCKER_DAEMON_CONFIG" ]]; then
@@ -38,12 +89,18 @@ docker_daemon_edit(){
fi fi
# Gather missing keys # Gather missing keys
! _has_kv ipv6 true && MISSING+=("ipv6: true") ! _has_kv ipv6 true && MISSING+=("ipv6: true")
! grep -Eq '"fixed-cidr-v6"\s*:\s*".+"' "$DOCKER_DAEMON_CONFIG" \
&& MISSING+=('fixed-cidr-v6: "fd00:dead:beef:c0::/80"') # For Docker < 28, keep requiring fixed-cidr-v6 (default bridge needs it on old engines)
if [[ -n "$DOCKER_MAJOR" && "$DOCKER_MAJOR" -le 27 ]]; then if [[ -n "$DOCKER_MAJOR" && "$DOCKER_MAJOR" -lt 28 ]]; then
! grep -Eq '"fixed-cidr-v6"[[:space:]]*:[[:space:]]*".+"' "$DOCKER_DAEMON_CONFIG" \
&& MISSING+=('fixed-cidr-v6: "fd00:dead:beef:c0::/80"')
fi
# For Docker < 27, ip6tables needed and was tied to experimental in older releases
if [[ -n "$DOCKER_MAJOR" && "$DOCKER_MAJOR" -lt 27 ]]; then
_has_kv ipv6 true && ! _has_kv ip6tables true && MISSING+=("ip6tables: true") _has_kv ipv6 true && ! _has_kv ip6tables true && MISSING+=("ip6tables: true")
! _has_kv experimental true && MISSING+=("experimental: true") ! _has_kv experimental true && MISSING+=("experimental: true")
fi fi
# Fix if needed # Fix if needed
@@ -60,9 +117,19 @@ docker_daemon_edit(){
cp "$DOCKER_DAEMON_CONFIG" "${DOCKER_DAEMON_CONFIG}.bak" cp "$DOCKER_DAEMON_CONFIG" "${DOCKER_DAEMON_CONFIG}.bak"
if command -v jq &>/dev/null; then if command -v jq &>/dev/null; then
TMP=$(mktemp) TMP=$(mktemp)
JQ_FILTER='.ipv6 = true | .["fixed-cidr-v6"] = "fd00:dead:beef:c0::/80"' # Base filter: ensure ipv6 = true
[[ "$DOCKER_MAJOR" && "$DOCKER_MAJOR" -lt 27 ]] \ JQ_FILTER='.ipv6 = true'
&& JQ_FILTER+=' | .ip6tables = true | .experimental = true'
# Add fixed-cidr-v6 only for Docker < 28
if [[ -n "$DOCKER_MAJOR" && "$DOCKER_MAJOR" -lt 28 ]]; then
JQ_FILTER+=' | .["fixed-cidr-v6"] = (.["fixed-cidr-v6"] // "fd00:dead:beef:c0::/80")'
fi
# Add ip6tables/experimental only for Docker < 27
if [[ -n "$DOCKER_MAJOR" && "$DOCKER_MAJOR" -lt 27 ]]; then
JQ_FILTER+=' | .ip6tables = true | .experimental = true'
fi
jq "$JQ_FILTER" "$DOCKER_DAEMON_CONFIG" >"$TMP" && mv "$TMP" "$DOCKER_DAEMON_CONFIG" jq "$JQ_FILTER" "$DOCKER_DAEMON_CONFIG" >"$TMP" && mv "$TMP" "$DOCKER_DAEMON_CONFIG"
echo -e "${LIGHT_GREEN}daemon.json updated. Restarting Docker...${NC}" echo -e "${LIGHT_GREEN}daemon.json updated. Restarting Docker...${NC}"
(command -v systemctl &>/dev/null && systemctl restart docker) || service docker restart (command -v systemctl &>/dev/null && systemctl restart docker) || service docker restart
@@ -88,6 +155,7 @@ docker_daemon_edit(){
fi fi
if [[ $ans =~ ^[Yy]$ ]]; then if [[ $ans =~ ^[Yy]$ ]]; then
mkdir -p "$(dirname "$DOCKER_DAEMON_CONFIG")"
if [[ -n "$DOCKER_MAJOR" && "$DOCKER_MAJOR" -lt 27 ]]; then if [[ -n "$DOCKER_MAJOR" && "$DOCKER_MAJOR" -lt 27 ]]; then
cat > "$DOCKER_DAEMON_CONFIG" <<EOF cat > "$DOCKER_DAEMON_CONFIG" <<EOF
{ {
@@ -97,12 +165,19 @@ docker_daemon_edit(){
"experimental": true "experimental": true
} }
EOF EOF
else elif [[ -n "$DOCKER_MAJOR" && "$DOCKER_MAJOR" -lt 28 ]]; then
cat > "$DOCKER_DAEMON_CONFIG" <<EOF cat > "$DOCKER_DAEMON_CONFIG" <<EOF
{ {
"ipv6": true, "ipv6": true,
"fixed-cidr-v6": "fd00:dead:beef:c0::/80" "fixed-cidr-v6": "fd00:dead:beef:c0::/80"
} }
EOF
else
# Docker 28+: ipv6 works without fixed-cidr-v6
cat > "$DOCKER_DAEMON_CONFIG" <<EOF
{
"ipv6": true
}
EOF EOF
fi fi
echo -e "${GREEN}Created $DOCKER_DAEMON_CONFIG with IPv6 settings.${NC}" echo -e "${GREEN}Created $DOCKER_DAEMON_CONFIG with IPv6 settings.${NC}"
@@ -122,7 +197,7 @@ configure_ipv6() {
# detect manual override if mailcow.conf is present # detect manual override if mailcow.conf is present
if [[ -n "$MAILCOW_CONF" && -f "$MAILCOW_CONF" ]] && grep -q '^ENABLE_IPV6=' "$MAILCOW_CONF"; then if [[ -n "$MAILCOW_CONF" && -f "$MAILCOW_CONF" ]] && grep -q '^ENABLE_IPV6=' "$MAILCOW_CONF"; then
MANUAL_SETTING=$(grep '^ENABLE_IPV6=' "$MAILCOW_CONF" | cut -d= -f2) MANUAL_SETTING=$(grep '^ENABLE_IPV6=' "$MAILCOW_CONF" | cut -d= -f2)
elif [[ -z "$MAILCOW_CONF" ]] && [[ ! -z "${ENABLE_IPV6:-}" ]]; then elif [[ -z "$MAILCOW_CONF" ]] && [[ -n "${ENABLE_IPV6:-}" ]]; then
MANUAL_SETTING="$ENABLE_IPV6" MANUAL_SETTING="$ENABLE_IPV6"
else else
MANUAL_SETTING="" MANUAL_SETTING=""
@@ -131,38 +206,34 @@ configure_ipv6() {
get_ipv6_support get_ipv6_support
# if user manually set it, check for mismatch # if user manually set it, check for mismatch
if [[ -n "$MANUAL_SETTING" ]]; then if [[ "$DETECTED_IPV6" != "true" ]]; then
if [[ "$MANUAL_SETTING" == "false" && "$DETECTED_IPV6" == "true" ]]; then if [[ -n "$MAILCOW_CONF" && -f "$MAILCOW_CONF" ]]; then
echo -e "${RED}ERROR: You have ENABLE_IPV6=false but your host and Docker support IPv6.${NC}" if grep -q '^ENABLE_IPV6=' "$MAILCOW_CONF"; then
echo -e "${RED}This can create an open relay. Please set ENABLE_IPV6=true in your mailcow.conf and re-run.${NC}" sed -i 's/^ENABLE_IPV6=.*/ENABLE_IPV6=false/' "$MAILCOW_CONF"
exit 1 else
elif [[ "$MANUAL_SETTING" == "true" && "$DETECTED_IPV6" == "false" ]]; then echo "ENABLE_IPV6=false" >> "$MAILCOW_CONF"
echo -e "${RED}ERROR: You have ENABLE_IPV6=true but your host does not support IPv6.${NC}" fi
echo -e "${RED}Please disable or fix your host/Docker IPv6 support, or set ENABLE_IPV6=false.${NC}"
exit 1
else else
return export IPV6_BOOL=false
fi fi
fi
# no manual override: proceed to set or export
if [[ "$DETECTED_IPV6" == "true" ]]; then
docker_daemon_edit
else
echo "Skipping Docker IPv6 configuration because host does not support IPv6." echo "Skipping Docker IPv6 configuration because host does not support IPv6."
echo "Make sure to check if your docker daemon.json does not include \"enable_ipv6\": true if you do not want IPv6."
echo "IPv6 configuration complete: ENABLE_IPV6=false"
sleep 2
return
fi fi
# now write into mailcow.conf or export docker_daemon_edit
if [[ -n "$MAILCOW_CONF" && -f "$MAILCOW_CONF" ]]; then if [[ -n "$MAILCOW_CONF" && -f "$MAILCOW_CONF" ]]; then
LINE="ENABLE_IPV6=$DETECTED_IPV6"
if grep -q '^ENABLE_IPV6=' "$MAILCOW_CONF"; then if grep -q '^ENABLE_IPV6=' "$MAILCOW_CONF"; then
sed -i "s/^ENABLE_IPV6=.*/$LINE/" "$MAILCOW_CONF" sed -i 's/^ENABLE_IPV6=.*/ENABLE_IPV6=true/' "$MAILCOW_CONF"
else else
echo "$LINE" >> "$MAILCOW_CONF" echo "ENABLE_IPV6=true" >> "$MAILCOW_CONF"
fi fi
else else
export IPV6_BOOL="$DETECTED_IPV6" export IPV6_BOOL=true
fi fi
echo "IPv6 configuration complete: ENABLE_IPV6=$DETECTED_IPV6" echo "IPv6 configuration complete: ENABLE_IPV6=true"
} }
+6 -5
View File
@@ -43,6 +43,7 @@ adapt_new_options() {
"ALLOW_ADMIN_EMAIL_LOGIN" "ALLOW_ADMIN_EMAIL_LOGIN"
"SKIP_HTTP_VERIFICATION" "SKIP_HTTP_VERIFICATION"
"SOGO_EXPIRE_SESSION" "SOGO_EXPIRE_SESSION"
"SOGO_URL_ENCRYPTION_KEY"
"REDIS_PORT" "REDIS_PORT"
"REDISPASS" "REDISPASS"
"DOVECOT_MASTER_USER" "DOVECOT_MASTER_USER"
@@ -94,7 +95,6 @@ adapt_new_options() {
echo '# Max log lines per service to keep in Redis logs' >> mailcow.conf echo '# Max log lines per service to keep in Redis logs' >> mailcow.conf
echo "LOG_LINES=9999" >> mailcow.conf echo "LOG_LINES=9999" >> mailcow.conf
;; ;;
IPV4_NETWORK) IPV4_NETWORK)
echo '# Internal IPv4 /24 subnet, format n.n.n. (expands to n.n.n.0/24)' >> mailcow.conf echo '# Internal IPv4 /24 subnet, format n.n.n. (expands to n.n.n.0/24)' >> mailcow.conf
echo "IPV4_NETWORK=172.22.1" >> mailcow.conf echo "IPV4_NETWORK=172.22.1" >> mailcow.conf
@@ -276,21 +276,22 @@ adapt_new_options() {
echo '# A COMPLETE DOCKER STACK REBUILD (compose down && compose up -d) IS NEEDED TO APPLY THIS.' >> mailcow.conf echo '# A COMPLETE DOCKER STACK REBUILD (compose down && compose up -d) IS NEEDED TO APPLY THIS.' >> mailcow.conf
echo ENABLE_IPV6=${IPV6_BOOL} >> mailcow.conf echo ENABLE_IPV6=${IPV6_BOOL} >> mailcow.conf
;; ;;
SKIP_CLAMD) SKIP_CLAMD)
echo '# Skip ClamAV (clamd-mailcow) anti-virus (Rspamd will auto-detect a missing ClamAV container) - y/n' >> mailcow.conf echo '# Skip ClamAV (clamd-mailcow) anti-virus (Rspamd will auto-detect a missing ClamAV container) - y/n' >> mailcow.conf
echo 'SKIP_CLAMD=n' >> mailcow.conf echo 'SKIP_CLAMD=n' >> mailcow.conf
;; ;;
SKIP_OLEFY) SKIP_OLEFY)
echo '# Skip Olefy (olefy-mailcow) anti-virus for Office documents (Rspamd will auto-detect a missing Olefy container) - y/n' >> mailcow.conf echo '# Skip Olefy (olefy-mailcow) anti-virus for Office documents (Rspamd will auto-detect a missing Olefy container) - y/n' >> mailcow.conf
echo 'SKIP_OLEFY=n' >> mailcow.conf echo 'SKIP_OLEFY=n' >> mailcow.conf
;; ;;
REDISPASS) REDISPASS)
echo "REDISPASS=$(LC_ALL=C </dev/urandom tr -dc A-Za-z0-9 2>/dev/null | head -c 28)" >> mailcow.conf echo "REDISPASS=$(LC_ALL=C </dev/urandom tr -dc A-Za-z0-9 2>/dev/null | head -c 28)" >> mailcow.conf
;; ;;
SOGO_URL_ENCRYPTION_KEY)
echo '# SOGo URL encryption key (exactly 16 characters, limited to AZ, az, 09)' >> mailcow.conf
echo '# This key is used to encrypt email addresses within SOGo URLs' >> mailcow.conf
echo "SOGO_URL_ENCRYPTION_KEY=$(LC_ALL=C </dev/urandom tr -dc A-Za-z0-9 2>/dev/null | head -c 16)" >> mailcow.conf
;;
*) *)
echo "${option}=" >> mailcow.conf echo "${option}=" >> mailcow.conf
;; ;;
+8 -8
View File
@@ -3,14 +3,14 @@ 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
@@ -348,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
@@ -389,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
@@ -410,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")
;; ;;
+3 -3
View File
@@ -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
} }
+2 -2
View File
@@ -101,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}..."
@@ -124,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
+14 -14
View File
@@ -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")
@@ -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",
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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/
@@ -341,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))
+1 -1
View File
@@ -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))
+6 -6
View File
@@ -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);
}; };
+10 -10
View File
@@ -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);
}; };
+12 -13
View File
@@ -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,6 +1,6 @@
#!/bin/sh #!/bin/sh
backend=iptables backend=nftables
nft list table ip filter &>/dev/null nft list table ip filter &>/dev/null
nftables_found=$? nftables_found=$?
+50 -46
View File
@@ -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)
@@ -449,6 +448,11 @@ if __name__ == '__main__':
tables = NFTables(chain_name, logger) tables = NFTables(chain_name, logger)
else: else:
logger.logInfo('Using IPTables backend') logger.logInfo('Using IPTables backend')
logger.logWarn(
"DEPRECATION: iptables-legacy is deprecated and will be removed in future releases. "
"Please switch to nftables on your host to ensure complete compatibility."
)
time.sleep(5)
tables = IPTables(chain_name, logger) tables = IPTables(chain_name, logger)
clear() clear()
@@ -462,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()
+23 -11
View File
@@ -1,24 +1,36 @@
import time import time
import json import json
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):
# Local time with milliseconds
return datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
def log(self, priority, message): def log(self, priority, message):
tolog = {} # build valkey-friendly dict
tolog['time'] = int(round(time.time())) tolog = {
tolog['priority'] = priority 'time': int(round(time.time())), # keep raw timestamp for Valkey
tolog['message'] = message 'priority': priority,
print(message) 'message': message
if self.r is not None: }
# print human-readable message with timestamp
ts = self._format_timestamp()
print(f"{ts} {priority.upper()}: {message}", flush=True)
# also push JSON to Redis if connected
if self.valkey is not None:
try: 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('Failed logging to redis: %s' % (ex)) 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)
+1 -1
View File
@@ -10,7 +10,7 @@ def includes_conf(env, template_vars):
server_name_config = f"server_name {template_vars['MAILCOW_HOSTNAME']} autodiscover.* autoconfig.* {' '.join(template_vars['ADDITIONAL_SERVER_NAMES'])};" server_name_config = f"server_name {template_vars['MAILCOW_HOSTNAME']} autodiscover.* autoconfig.* {' '.join(template_vars['ADDITIONAL_SERVER_NAMES'])};"
listen_plain_config = f"listen {template_vars['HTTP_PORT']};" listen_plain_config = f"listen {template_vars['HTTP_PORT']};"
listen_ssl_config = f"listen {template_vars['HTTPS_PORT']};" listen_ssl_config = f"listen {template_vars['HTTPS_PORT']};"
if not template_vars['ENABLE_IPV6']: if template_vars['ENABLE_IPV6']:
listen_plain_config += f"\nlisten [::]:{template_vars['HTTP_PORT']};" listen_plain_config += f"\nlisten [::]:{template_vars['HTTP_PORT']};"
listen_ssl_config += f"\nlisten [::]:{template_vars['HTTPS_PORT']} ssl;" listen_ssl_config += f"\nlisten [::]:{template_vars['HTTPS_PORT']} ssl;"
listen_ssl_config += "\nhttp2 on;" listen_ssl_config += "\nhttp2 on;"
+23 -23
View File
@@ -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
+1 -1
View File
@@ -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);
}; };
+1 -1
View File
@@ -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)
@@ -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);
}; };
+10 -10
View File
@@ -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);
}; };
+14 -14
View File
@@ -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 -1
View File
@@ -44,7 +44,7 @@ RUN echo "Building from repository $SOGO_DEBIAN_REPOSITORY" \
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
+8
View File
@@ -24,6 +24,10 @@ while [[ "${DBV_NOW}" != "${DBV_NEW}" ]]; do
done done
echo "DB schema is ${DBV_NOW}" echo "DB schema is ${DBV_NOW}"
if [[ "${MASTER}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "DROP TRIGGER IF EXISTS sogo_update_password"
fi
# cat /dev/urandom seems to hang here occasionally and is not recommended anyway, better use openssl # cat /dev/urandom seems to hang here occasionally and is not recommended anyway, better use openssl
RAND_PASS=$(openssl rand -base64 16 | tr -dc _A-Z-a-z-0-9) RAND_PASS=$(openssl rand -base64 16 | tr -dc _A-Z-a-z-0-9)
@@ -46,6 +50,10 @@ cat <<EOF > /var/lib/sogo/GNUstep/Defaults/sogod.plist
<string>YES</string> <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>
+2 -2
View File
@@ -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);
+10 -10
View File
@@ -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);
@@ -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"]
@@ -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
+37 -37
View File
@@ -44,18 +44,18 @@ 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_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
@@ -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}"
+6 -6
View File
@@ -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);
+4 -4
View File
@@ -78,7 +78,7 @@ http {
{%endif%} {%endif%}
listen {{ HTTPS_PORT }}{% if NGINX_USE_PROXY_PROTOCOL %} proxy_protocol{%endif%} ssl; listen {{ HTTPS_PORT }}{% if NGINX_USE_PROXY_PROTOCOL %} proxy_protocol{%endif%} ssl;
{% if not DISABLE_IPv6 %} {% if ENABLE_IPV6 %}
{% if not HTTP_REDIRECT %} {% if not HTTP_REDIRECT %}
listen [::]:{{ HTTP_PORT }}{% if NGINX_USE_PROXY_PROTOCOL %} proxy_protocol{%endif%}; listen [::]:{{ HTTP_PORT }}{% if NGINX_USE_PROXY_PROTOCOL %} proxy_protocol{%endif%};
{%endif%} {%endif%}
@@ -105,7 +105,7 @@ http {
{%endif%} {%endif%}
listen {{ HTTPS_PORT }}{% if NGINX_USE_PROXY_PROTOCOL %} proxy_protocol{%endif%} ssl; listen {{ HTTPS_PORT }}{% if NGINX_USE_PROXY_PROTOCOL %} proxy_protocol{%endif%} ssl;
{% if not DISABLE_IPv6 %} {% if ENABLE_IPV6 %}
{% if not HTTP_REDIRECT %} {% if not HTTP_REDIRECT %}
listen [::]:{{ HTTP_PORT }}{% if NGINX_USE_PROXY_PROTOCOL %} proxy_protocol{%endif%}; listen [::]:{{ HTTP_PORT }}{% if NGINX_USE_PROXY_PROTOCOL %} proxy_protocol{%endif%};
{%endif%} {%endif%}
@@ -126,7 +126,7 @@ http {
# rspamd dynmaps: # rspamd dynmaps:
server { server {
listen 8081; listen 8081;
{% if not DISABLE_IPv6 %} {% if ENABLE_IPV6 %}
listen [::]:8081; listen [::]:8081;
{%endif%} {%endif%}
index index.php index.html; index index.php index.html;
@@ -199,7 +199,7 @@ http {
{%endif%} {%endif%}
listen {{ HTTPS_PORT }}{% if NGINX_USE_PROXY_PROTOCOL %} proxy_protocol{%endif%} ssl; listen {{ HTTPS_PORT }}{% if NGINX_USE_PROXY_PROTOCOL %} proxy_protocol{%endif%} ssl;
{% if not DISABLE_IPv6 %} {% if ENABLE_IPV6 %}
{% if not HTTP_REDIRECT %} {% if not HTTP_REDIRECT %}
listen [::]:{{ HTTP_PORT }}{% if NGINX_USE_PROXY_PROTOCOL %} proxy_protocol{%endif%}; listen [::]:{{ HTTP_PORT }}{% if NGINX_USE_PROXY_PROTOCOL %} proxy_protocol{%endif%};
{%endif%} {%endif%}
+8 -8
View File
@@ -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
+8 -8
View File
@@ -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
+19 -23
View File
@@ -1,12 +1,13 @@
# Whitelist generated by Postwhite v3.4 on Mon Sep 1 00:23:07 UTC 2025 # 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/
# 2165 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::/49 permit 2a01:111:f403:2800::/53 permit
2a01:111:f403:8000::/50 permit
2a01:111:f403:8000::/51 permit 2a01:111:f403:8000::/51 permit
2a01:111:f403::/49 permit
2a01:111:f403:c000::/51 permit 2a01:111:f403:c000::/51 permit
2a01:111:f403:d000::/53 permit
2a01:111:f403:f000::/52 permit 2a01:111:f403:f000::/52 permit
2a01:238:20a:202:5370::1 permit 2a01:238:20a:202:5370::1 permit
2a01:238:20a:202:5372::1 permit 2a01:238:20a:202:5372::1 permit
@@ -55,7 +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.246.40 permit 13.107.213.41 permit
13.107.246.41 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
@@ -174,6 +176,7 @@
35.161.32.253 permit 35.161.32.253 permit
35.162.73.231 permit 35.162.73.231 permit
35.167.93.243 permit 35.167.93.243 permit
35.174.145.124 permit
35.176.132.251 permit 35.176.132.251 permit
35.205.92.9 permit 35.205.92.9 permit
35.228.216.85 permit 35.228.216.85 permit
@@ -183,7 +186,6 @@
37.218.249.47 permit 37.218.249.47 permit
37.218.251.62 permit 37.218.251.62 permit
39.156.163.64/29 permit 39.156.163.64/29 permit
40.90.65.81 permit
40.92.0.0/15 permit 40.92.0.0/15 permit
40.92.0.0/16 permit 40.92.0.0/16 permit
40.107.0.0/16 permit 40.107.0.0/16 permit
@@ -271,9 +273,6 @@
50.56.130.221 permit 50.56.130.221 permit
50.56.130.222 permit 50.56.130.222 permit
50.112.246.219 permit 50.112.246.219 permit
51.77.79.158 permit
51.83.17.38 permit
51.89.119.103 permit
52.1.14.157 permit 52.1.14.157 permit
52.5.230.59 permit 52.5.230.59 permit
52.6.74.205 permit 52.6.74.205 permit
@@ -324,8 +323,6 @@
52.234.172.96/28 permit 52.234.172.96/28 permit
52.235.253.128 permit 52.235.253.128 permit
52.236.28.240/28 permit 52.236.28.240/28 permit
54.36.149.183 permit
54.38.221.122 permit
54.90.148.255 permit 54.90.148.255 permit
54.165.19.38 permit 54.165.19.38 permit
54.174.52.0/24 permit 54.174.52.0/24 permit
@@ -686,6 +683,8 @@
82.165.159.45 permit 82.165.159.45 permit
82.165.159.130 permit 82.165.159.130 permit
82.165.159.131 permit 82.165.159.131 permit
85.9.206.169 permit
85.9.210.45 permit
85.158.136.0/21 permit 85.158.136.0/21 permit
85.215.255.39 permit 85.215.255.39 permit
85.215.255.40 permit 85.215.255.40 permit
@@ -1234,16 +1233,14 @@
99.83.190.102 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.122.78.238 permit 103.84.217.238 permit
103.89.75.238 permit
103.151.192.0/23 permit 103.151.192.0/23 permit
103.168.172.128/27 permit 103.168.172.128/27 permit
103.237.104.0/22 permit 103.237.104.0/22 permit
104.43.243.237 permit 104.43.243.237 permit
104.44.112.128/25 permit 104.44.112.128/25 permit
104.47.0.0/17 permit 104.47.0.0/17 permit
104.47.20.0/23 permit
104.47.75.0/24 permit
104.47.108.0/23 permit
104.130.96.0/28 permit 104.130.96.0/28 permit
104.130.122.0/23 permit 104.130.122.0/23 permit
106.10.144.64/27 permit 106.10.144.64/27 permit
@@ -1378,7 +1375,6 @@
108.174.6.215 permit 108.174.6.215 permit
108.175.18.45 permit 108.175.18.45 permit
108.175.30.45 permit 108.175.30.45 permit
108.177.96.0/20 permit
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
@@ -1544,6 +1540,7 @@
148.105.0.0/16 permit 148.105.0.0/16 permit
148.105.8.0/21 permit 148.105.8.0/21 permit
149.72.0.0/16 permit 149.72.0.0/16 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.230.98.160 permit 150.230.98.160 permit
@@ -1599,6 +1596,7 @@
159.183.0.0/16 permit 159.183.0.0/16 permit
159.183.68.71 permit 159.183.68.71 permit
159.183.79.38 permit 159.183.79.38 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
161.38.204.0/22 permit 161.38.204.0/22 permit
@@ -1616,6 +1614,7 @@
163.114.134.16 permit 163.114.134.16 permit
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
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
@@ -1655,6 +1654,7 @@
169.148.131.0/24 permit 169.148.131.0/24 permit
169.148.138.0/24 permit 169.148.138.0/24 permit
169.148.142.10 permit 169.148.142.10 permit
169.148.142.33 permit
169.148.144.0/25 permit 169.148.144.0/25 permit
169.148.144.10 permit 169.148.144.10 permit
169.148.146.0/23 permit 169.148.146.0/23 permit
@@ -1666,11 +1666,7 @@
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.0.0/20 permit
172.217.32.0/20 permit 172.217.32.0/20 permit
172.217.128.0/19 permit
172.217.160.0/20 permit
172.217.192.0/19 permit
172.253.56.0/21 permit 172.253.56.0/21 permit
172.253.112.0/20 permit 172.253.112.0/20 permit
173.0.84.0/29 permit 173.0.84.0/29 permit
@@ -2209,17 +2205,17 @@
2607:13c0:0002:0000:0000:0000:0000:1000/116 permit 2607:13c0:0002:0000:0000:0000:0000:1000/116 permit
2607:13c0:0004:0000:0000:0000:0000:0000/116 permit 2607:13c0:0004:0000:0000:0000:0000:0000/116 permit
2607:f8b0:4000::/36 permit 2607:f8b0:4000::/36 permit
2620:109:c003:104::215 permit
2620:109:c003:104::/64 permit 2620:109:c003:104::/64 permit
2620:109:c006:104::215 permit 2620:109:c003:104::215 permit
2620:109:c006:104::/64 permit 2620:109:c006:104::/64 permit
2620:109:c006:104::215 permit
2620:109:c00d:104::/64 permit 2620:109:c00d:104::/64 permit
2620:10d:c090:400::8:1 permit 2620:10d:c090:400::8:1 permit
2620:10d:c091:400::8:1 permit 2620:10d:c091:400::8:1 permit
2620:10d:c09b:400::8:1 permit 2620:10d:c09b:400::8:1 permit
2620:10d:c09c:400::8:1 permit 2620:10d:c09c:400::8:1 permit
2620:119:50c0:207::215 permit
2620:119:50c0:207::/64 permit 2620:119:50c0:207::/64 permit
2620:119:50c0:207::215 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
-12
View File
@@ -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
+7 -7
View File
@@ -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 {
@@ -133,7 +133,7 @@ try {
error_log("ALIAS EXPANDER: http pipe: goto address " . $goto . " is an alias branch for " . $goto_branch . PHP_EOL); error_log("ALIAS EXPANDER: http pipe: goto address " . $goto . " is an alias branch for " . $goto_branch . PHP_EOL);
$goto_branch_array = explode(',', $goto_branch); $goto_branch_array = explode(',', $goto_branch);
} else { } else {
$stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain AND `active` AND '1'"); $stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain AND `active` = '1'");
$stmt->execute(array(':domain' => $parsed_goto['domain'])); $stmt->execute(array(':domain' => $parsed_goto['domain']));
$goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['target_domain']; $goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['target_domain'];
if ($goto_branch) { if ($goto_branch) {
+5 -5
View File
@@ -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;
} }
} }
+10 -10
View File
@@ -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 {
@@ -182,7 +182,7 @@ foreach (json_decode($rcpts, true) as $rcpt) {
error_log("RCPT RESOVLER: http pipe: goto address " . $goto . " is an alias branch for " . $goto_branch . PHP_EOL); error_log("RCPT RESOVLER: http pipe: goto address " . $goto . " is an alias branch for " . $goto_branch . PHP_EOL);
$goto_branch_array = explode(',', $goto_branch); $goto_branch_array = explode(',', $goto_branch);
} else { } else {
$stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain AND `active` AND '1'"); $stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain AND `active` = '1'");
$stmt->execute(array(':domain' => $parsed_goto['domain'])); $stmt->execute(array(':domain' => $parsed_goto['domain']));
$goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['target_domain']; $goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['target_domain'];
if ($goto_branch) { if ($goto_branch) {
+7 -7
View File
@@ -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;
+7 -7
View File
@@ -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 {
@@ -167,7 +167,7 @@ foreach (json_decode($rcpts, true) as $rcpt) {
error_log("RCPT RESOVLER: http pipe: goto address " . $goto . " is an alias branch for " . $goto_branch . PHP_EOL); error_log("RCPT RESOVLER: http pipe: goto address " . $goto . " is an alias branch for " . $goto_branch . PHP_EOL);
$goto_branch_array = explode(',', $goto_branch); $goto_branch_array = explode(',', $goto_branch);
} else { } else {
$stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain AND `active` AND '1'"); $stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain AND `active` = '1'");
$stmt->execute(array(':domain' => $parsed_goto['domain'])); $stmt->execute(array(':domain' => $parsed_goto['domain']));
$goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['target_domain']; $goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['target_domain'];
if ($goto_branch) { if ($goto_branch) {
+12
View 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 "$@"
+6 -6
View File
@@ -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']);
+1 -1
View File
@@ -21,7 +21,7 @@ $clamd_status = (preg_match("/^([yY][eE][sS]|[yY])+$/", $_ENV["SKIP_CLAMD"])) ?
$olefy_status = (preg_match("/^([yY][eE][sS]|[yY])+$/", $_ENV["SKIP_OLEFY"])) ? false : true; $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 -3
View File
@@ -5412,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
+13 -13
View File
@@ -10,16 +10,16 @@ require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/sessions.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;
@@ -71,7 +71,7 @@ if (empty($_SERVER['PHP_AUTH_USER']) || empty($_SERVER['PHP_AUTH_PW'])) {
"service" => "Error: must be authenticated" "service" => "Error: must be authenticated"
) )
); );
$redis->lPush('AUTODISCOVER_LOG', $json); $valkey->lPush('AUTODISCOVER_LOG', $json);
header('WWW-Authenticate: Basic realm="' . $_SERVER['HTTP_HOST'] . '"'); header('WWW-Authenticate: Basic realm="' . $_SERVER['HTTP_HOST'] . '"');
header('HTTP/1.0 401 Unauthorized'); header('HTTP/1.0 401 Unauthorized');
exit(0); exit(0);
@@ -96,13 +96,13 @@ if ($login_role === "user") {
"service" => "Error: invalid or missing request data" "service" => "Error: invalid or missing request data"
) )
); );
$redis->lPush('AUTODISCOVER_LOG', $json); $valkey->lPush('AUTODISCOVER_LOG', $json);
$redis->lTrim('AUTODISCOVER_LOG', 0, 100); $valkey->lTrim('AUTODISCOVER_LOG', 0, 100);
} }
catch (RedisException $e) { catch (RedisException $e) {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'danger', 'type' => 'danger',
'msg' => 'Redis: '.$e 'msg' => 'Valkey: '.$e
); );
return false; return false;
} }
@@ -151,13 +151,13 @@ if ($login_role === "user") {
"service" => $autodiscover_config['autodiscoverType'] "service" => $autodiscover_config['autodiscoverType']
) )
); );
$redis->lPush('AUTODISCOVER_LOG', $json); $valkey->lPush('AUTODISCOVER_LOG', $json);
$redis->lTrim('AUTODISCOVER_LOG', 0, 100); $valkey->lTrim('AUTODISCOVER_LOG', 0, 100);
} }
catch (RedisException $e) { catch (RedisException $e) {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'danger', 'type' => 'danger',
'msg' => 'Redis: '.$e 'msg' => 'Valkey: '.$e
); );
return false; return false;
} }
+12 -40
View File
@@ -1,7 +1,7 @@
<?php <?php
function app_passwd($_action, $_data = null) { function app_passwd($_action, $_data = null) {
global $pdo; global $pdo;
global $lang; global $lang;
$_data_log = $_data; $_data_log = $_data;
!isset($_data_log['app_passwd']) ?: $_data_log['app_passwd'] = '*'; !isset($_data_log['app_passwd']) ?: $_data_log['app_passwd'] = '*';
!isset($_data_log['app_passwd2']) ?: $_data_log['app_passwd2'] = '*'; !isset($_data_log['app_passwd2']) ?: $_data_log['app_passwd2'] = '*';
@@ -43,20 +43,7 @@ function app_passwd($_action, $_data = null) {
); );
return false; return false;
} }
if (!preg_match('/' . $GLOBALS['PASSWD_REGEP'] . '/', $password)) { if (password_check($password, $password2) !== true) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => 'password_complexity'
);
return false;
}
if ($password != $password2) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => 'password_mismatch'
);
return false; return false;
} }
$password_hashed = hash_password($password); $password_hashed = hash_password($password);
@@ -88,15 +75,15 @@ function app_passwd($_action, $_data = null) {
'log' => array(__FUNCTION__, $_action, $_data_log), 'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => 'app_passwd_added' 'msg' => 'app_passwd_added'
); );
break; break;
case 'edit': case 'edit':
$ids = (array)$_data['id']; $ids = (array)$_data['id'];
foreach ($ids as $id) { foreach ($ids as $id) {
$is_now = app_passwd('details', $id); $is_now = app_passwd('details', $id);
if (!empty($is_now)) { if (!empty($is_now)) {
$app_name = (!empty($_data['app_name'])) ? $_data['app_name'] : $is_now['name']; $app_name = (!empty($_data['app_name'])) ? $_data['app_name'] : $is_now['name'];
$password = (!empty($_data['password'])) ? $_data['password'] : null; $password = (!empty($_data['app_passwd'])) ? $_data['app_passwd'] : null;
$password2 = (!empty($_data['password2'])) ? $_data['password2'] : null; $password2 = (!empty($_data['app_passwd2'])) ? $_data['app_passwd2'] : null;
if (isset($_data['protocols'])) { if (isset($_data['protocols'])) {
$protocols = (array)$_data['protocols']; $protocols = (array)$_data['protocols'];
$imap_access = (in_array('imap_access', $protocols)) ? 1 : 0; $imap_access = (in_array('imap_access', $protocols)) ? 1 : 0;
@@ -126,20 +113,7 @@ function app_passwd($_action, $_data = null) {
} }
$app_name = htmlspecialchars(trim($app_name)); $app_name = htmlspecialchars(trim($app_name));
if (!empty($password) && !empty($password2)) { if (!empty($password) && !empty($password2)) {
if (!preg_match('/' . $GLOBALS['PASSWD_REGEP'] . '/', $password)) { if (password_check($password, $password2) !== true) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'password_complexity'
);
continue;
}
if ($password != $password2) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'password_mismatch'
);
continue; continue;
} }
$password_hashed = hash_password($password); $password_hashed = hash_password($password);
@@ -182,7 +156,7 @@ function app_passwd($_action, $_data = null) {
'msg' => array('object_modified', htmlspecialchars(implode(', ', $ids))) 'msg' => array('object_modified', htmlspecialchars(implode(', ', $ids)))
); );
} }
break; break;
case 'delete': case 'delete':
$ids = (array)$_data['id']; $ids = (array)$_data['id'];
foreach ($ids as $id) { foreach ($ids as $id) {
@@ -213,19 +187,17 @@ function app_passwd($_action, $_data = null) {
'msg' => array('app_passwd_removed', htmlspecialchars($id)) 'msg' => array('app_passwd_removed', htmlspecialchars($id))
); );
} }
break; break;
case 'get': case 'get':
$app_passwds = array(); $app_passwds = array();
$stmt = $pdo->prepare("SELECT `id`, `name` FROM `app_passwd` WHERE `mailbox` = :username"); $stmt = $pdo->prepare("SELECT `id`, `name` FROM `app_passwd` WHERE `mailbox` = :username");
$stmt->execute(array(':username' => $username)); $stmt->execute(array(':username' => $username));
$app_passwds = $stmt->fetchAll(PDO::FETCH_ASSOC); $app_passwds = $stmt->fetchAll(PDO::FETCH_ASSOC);
return $app_passwds; return $app_passwds;
break; break;
case 'details': case 'details':
$app_passwd_data = array(); $app_passwd_data = array();
$stmt = $pdo->prepare("SELECT * $stmt = $pdo->prepare("SELECT * FROM `app_passwd` WHERE `id` = :id");
FROM `app_passwd`
WHERE `id` = :id");
$stmt->execute(array(':id' => $_data)); $stmt->execute(array(':id' => $_data));
$app_passwd_data = $stmt->fetch(PDO::FETCH_ASSOC); $app_passwd_data = $stmt->fetch(PDO::FETCH_ASSOC);
if (empty($app_passwd_data)) { if (empty($app_passwd_data)) {
@@ -237,6 +209,6 @@ function app_passwd($_action, $_data = null) {
} }
$app_passwd_data['name'] = htmlspecialchars(trim($app_passwd_data['name'])); $app_passwd_data['name'] = htmlspecialchars(trim($app_passwd_data['name']));
return $app_passwd_data; return $app_passwd_data;
break; break;
} }
} }
+3 -3
View File
@@ -1,7 +1,7 @@
<?php <?php
function check_login($user, $pass, $app_passwd_data = false, $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'];
@@ -62,12 +62,12 @@ function check_login($user, $pass, $app_passwd_data = false, $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(
+40 -38
View File
@@ -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,38 +312,40 @@ 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;
} }
break; break;
case 'ui_texts': case 'ui_texts':
try { try {
$data['title_name'] = ($title_name = $redis->get('TITLE_NAME')) ? $title_name : 'mailcow UI'; $mailcow_hostname = strtolower(getenv("MAILCOW_HOSTNAME"));
$data['main_name'] = ($main_name = $redis->get('MAIN_NAME')) ? $main_name : 'mailcow UI';
$data['apps_name'] = ($apps_name = $redis->get('APPS_NAME')) ? $apps_name : $lang['header']['apps']; $data['title_name'] = ($title_name = $valkey->get('TITLE_NAME')) ? $title_name : "$mailcow_hostname - mail UI";
$data['help_text'] = ($help_text = $redis->get('HELP_TEXT')) ? $help_text : false; $data['main_name'] = ($main_name = $valkey->get('MAIN_NAME')) ? $main_name : "$mailcow_hostname - mail UI";
if (!empty($redis->get('UI_IMPRESS'))) { $data['apps_name'] = ($apps_name = $valkey->get('APPS_NAME')) ? $apps_name : $lang['header']['apps'];
$redis->set('UI_FOOTER', $redis->get('UI_IMPRESS')); $data['help_text'] = ($help_text = $valkey->get('HELP_TEXT')) ? $help_text : false;
$redis->del('UI_IMPRESS'); if (!empty($valkey->get('UI_IMPRESS'))) {
$valkey->set('UI_FOOTER', $valkey->get('UI_IMPRESS'));
$valkey->del('UI_IMPRESS');
} }
$data['ui_footer'] = ($ui_footer = $redis->get('UI_FOOTER')) ? $ui_footer : false; $data['ui_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;
} }
@@ -374,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) {
+32 -32
View File
@@ -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;
} }
+2 -2
View File
@@ -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
@@ -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;
} }
+36 -36
View File
@@ -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;
} }
+18 -18
View File
@@ -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;
} }
+65 -55
View File
@@ -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;
} }
@@ -253,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':
@@ -272,13 +272,13 @@ function last_login($action, $username, $sasl_limit_days = 7, $ui_offset = 1) {
} }
elseif (filter_var($sasl[$k]['real_rip'], FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) { 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;
} }
@@ -294,13 +294,13 @@ function last_login($action, $username, $sasl_limit_days = 7, $ui_offset = 1) {
if ($ip_data_array !== false and !empty($ip_data_array['shortcountry'])) { 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;
@@ -1107,11 +1107,21 @@ function user_get_alias_details($username) {
} }
return $data; return $data;
} }
function is_valid_domain_name($domain_name) { function is_valid_domain_name($domain_name, $options = array()) {
if (empty($domain_name)) { if (empty($domain_name)) {
return false; return false;
} }
// Convert domain name to ASCII for validation
$domain_name = idn_to_ascii($domain_name, 0, INTL_IDNA_VARIANT_UTS46); $domain_name = idn_to_ascii($domain_name, 0, INTL_IDNA_VARIANT_UTS46);
if (isset($options['allow_wildcard']) && $options['allow_wildcard'] == true) {
// Remove '*.' if wildcard subdomains are allowed
if (strpos($domain_name, '*.') === 0) {
$domain_name = substr($domain_name, 2);
}
}
return (preg_match("/^([a-z\d](-*[a-z\d])*)(\.([a-z\d](-*[a-z\d])*))*$/i", $domain_name) return (preg_match("/^([a-z\d](-*[a-z\d])*)(\.([a-z\d](-*[a-z\d])*))*$/i", $domain_name)
&& preg_match("/^.{1,253}$/", $domain_name) && preg_match("/^.{1,253}$/", $domain_name)
&& preg_match("/^[^\.]{1,63}(\.[^\.]{1,63})*$/", $domain_name)); && preg_match("/^[^\.]{1,63}(\.[^\.]{1,63})*$/", $domain_name));
@@ -1989,7 +1999,7 @@ function admin_api($access, $action, $data = null) {
} }
function license($action, $data = null) { 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(
@@ -2039,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;
} }
@@ -2126,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":
@@ -2167,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)
)); ));
@@ -2175,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;
} }
@@ -2189,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)
); );
} }
@@ -2957,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;
@@ -3193,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");
@@ -3211,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;
} }
@@ -3314,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;
} }
@@ -3366,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;
@@ -3415,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) {
@@ -3429,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) {
@@ -3443,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) {
@@ -3457,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) {
@@ -3471,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) {
@@ -3485,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) {
@@ -3499,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) {
@@ -3513,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) {
@@ -3527,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) {
@@ -3541,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) {
+29 -29
View File
@@ -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;
@@ -628,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;
} }
@@ -646,7 +646,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$_data['key_size'] = (isset($_data['key_size'])) ? intval($_data['key_size']) : $DOMAIN_DEFAULT_ATTRIBUTES['key_size']; $_data['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),
@@ -974,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;
} }
@@ -988,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),
@@ -1446,7 +1446,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
} }
foreach ($mx as $index => $mx_domain) { foreach ($mx as $index => $mx_domain) {
$mx_domain = idn_to_ascii(strtolower(trim($mx_domain)), 0, INTL_IDNA_VARIANT_UTS46); $mx_domain = idn_to_ascii(strtolower(trim($mx_domain)), 0, INTL_IDNA_VARIANT_UTS46);
if (!is_valid_domain_name($mx_domain)) { if (!is_valid_domain_name($mx_domain, array('allow_wildcard' => true))) {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'danger', 'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data, $_attr), 'log' => array(__FUNCTION__, $_action, $_type, $_data, $_attr),
@@ -2147,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;
} }
@@ -3897,7 +3897,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
foreach ($mx as $index => $mx_domain) { foreach ($mx as $index => $mx_domain) {
$mx_domain = idn_to_ascii(strtolower(trim($mx_domain)), 0, INTL_IDNA_VARIANT_UTS46); $mx_domain = idn_to_ascii(strtolower(trim($mx_domain)), 0, INTL_IDNA_VARIANT_UTS46);
$invalid_mx = false; $invalid_mx = false;
if (!is_valid_domain_name($mx_domain)) { if (!is_valid_domain_name($mx_domain, array('allow_wildcard' => true))) {
$invalid_mx = $mx_domain; $invalid_mx = $mx_domain;
break; break;
} }
@@ -4603,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 {
@@ -4617,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;
} }
@@ -5636,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;
} }
@@ -5769,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;
} }
@@ -5955,13 +5955,13 @@ 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;
} }
+1 -1
View File
@@ -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(
+1 -1
View File
@@ -1,7 +1,7 @@
<?php <?php
function policy($_action, $_scope, $_data = null) { function policy($_action, $_scope, $_data = null) {
global $pdo; global $pdo;
global $redis; global $valkey;
global $lang; global $lang;
$_data_log = $_data; $_data_log = $_data;
switch ($_action) { switch ($_action) {
+29 -29
View File
@@ -4,7 +4,7 @@ use PHPMailer\PHPMailer\SMTP;
use PHPMailer\PHPMailer\Exception; use PHPMailer\PHPMailer\Exception;
function quarantine($_action, $_data = null) { function quarantine($_action, $_data = null) {
global $pdo; global $pdo;
global $redis; global $valkey;
global $lang; global $lang;
$_data_log = $_data; $_data_log = $_data;
switch ($_action) { switch ($_action) {
@@ -102,14 +102,14 @@ function quarantine($_action, $_data = null) {
return false; return false;
} }
try { try {
$release_format = $redis->Get('Q_RELEASE_FORMAT'); $release_format = $valkey->Get('Q_RELEASE_FORMAT');
} }
catch (RedisException $e) { catch (RedisException $e) {
logger(array('return' => array( logger(array('return' => array(
array( 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;
@@ -332,23 +332,23 @@ function quarantine($_action, $_data = null) {
} }
$exclude_domains = (array)$_data['exclude_domains']; $exclude_domains = (array)$_data['exclude_domains'];
try { try {
$redis->Set('Q_RETENTION_SIZE', intval($retention_size)); $valkey->Set('Q_RETENTION_SIZE', intval($retention_size));
$redis->Set('Q_MAX_SIZE', intval($max_size)); $valkey->Set('Q_MAX_SIZE', intval($max_size));
$redis->Set('Q_MAX_SCORE', $max_score); $valkey->Set('Q_MAX_SCORE', $max_score);
$redis->Set('Q_MAX_AGE', $max_age); $valkey->Set('Q_MAX_AGE', $max_age);
$redis->Set('Q_EXCLUDE_DOMAINS', json_encode($exclude_domains)); $valkey->Set('Q_EXCLUDE_DOMAINS', json_encode($exclude_domains));
$redis->Set('Q_RELEASE_FORMAT', $release_format); $valkey->Set('Q_RELEASE_FORMAT', $release_format);
$redis->Set('Q_SENDER', $sender); $valkey->Set('Q_SENDER', $sender);
$redis->Set('Q_BCC', $bcc); $valkey->Set('Q_BCC', $bcc);
$redis->Set('Q_REDIRECT', $redirect); $valkey->Set('Q_REDIRECT', $redirect);
$redis->Set('Q_SUBJ', $subject); $valkey->Set('Q_SUBJ', $subject);
$redis->Set('Q_HTML', $html); $valkey->Set('Q_HTML', $html);
} }
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;
} }
@@ -403,13 +403,13 @@ function quarantine($_action, $_data = null) {
continue; continue;
} }
try { try {
$release_format = $redis->Get('Q_RELEASE_FORMAT'); $release_format = $valkey->Get('Q_RELEASE_FORMAT');
} }
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;
} }
@@ -776,18 +776,18 @@ function quarantine($_action, $_data = null) {
case 'settings': case 'settings':
try { try {
if ($_SESSION['mailcow_cc_role'] == "admin") { if ($_SESSION['mailcow_cc_role'] == "admin") {
$settings['exclude_domains'] = json_decode($redis->Get('Q_EXCLUDE_DOMAINS'), true); $settings['exclude_domains'] = json_decode($valkey->Get('Q_EXCLUDE_DOMAINS'), true);
} }
$settings['max_size'] = $redis->Get('Q_MAX_SIZE'); $settings['max_size'] = $valkey->Get('Q_MAX_SIZE');
$settings['max_score'] = $redis->Get('Q_MAX_SCORE'); $settings['max_score'] = $valkey->Get('Q_MAX_SCORE');
$settings['max_age'] = $redis->Get('Q_MAX_AGE'); $settings['max_age'] = $valkey->Get('Q_MAX_AGE');
$settings['retention_size'] = $redis->Get('Q_RETENTION_SIZE'); $settings['retention_size'] = $valkey->Get('Q_RETENTION_SIZE');
$settings['release_format'] = $redis->Get('Q_RELEASE_FORMAT'); $settings['release_format'] = $valkey->Get('Q_RELEASE_FORMAT');
$settings['subject'] = $redis->Get('Q_SUBJ'); $settings['subject'] = $valkey->Get('Q_SUBJ');
$settings['sender'] = $redis->Get('Q_SENDER'); $settings['sender'] = $valkey->Get('Q_SENDER');
$settings['bcc'] = $redis->Get('Q_BCC'); $settings['bcc'] = $valkey->Get('Q_BCC');
$settings['redirect'] = $redis->Get('Q_REDIRECT'); $settings['redirect'] = $valkey->Get('Q_REDIRECT');
$settings['html_tmpl'] = htmlspecialchars($redis->Get('Q_HTML')); $settings['html_tmpl'] = htmlspecialchars($valkey->Get('Q_HTML'));
if (empty($settings['html_tmpl'])) { if (empty($settings['html_tmpl'])) {
$settings['html_tmpl'] = htmlspecialchars(file_get_contents("/tpls/quarantine.tpl")); $settings['html_tmpl'] = htmlspecialchars(file_get_contents("/tpls/quarantine.tpl"));
} }
@@ -796,7 +796,7 @@ function quarantine($_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;
} }
@@ -1,6 +1,6 @@
<?php <?php
function quota_notification($_action, $_data = null) { function quota_notification($_action, $_data = null) {
global $redis; global $valkey;
$_data_log = $_data; $_data_log = $_data;
if ($_SESSION['mailcow_cc_role'] != "admin") { if ($_SESSION['mailcow_cc_role'] != "admin") {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
@@ -26,15 +26,15 @@ function quota_notification($_action, $_data = null) {
} }
$html = $_data['html_tmpl']; $html = $_data['html_tmpl'];
try { try {
$redis->Set('QW_SENDER', $sender); $valkey->Set('QW_SENDER', $sender);
$redis->Set('QW_SUBJ', $subject); $valkey->Set('QW_SUBJ', $subject);
$redis->Set('QW_HTML', $html); $valkey->Set('QW_HTML', $html);
} }
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;
} }
@@ -46,9 +46,9 @@ function quota_notification($_action, $_data = null) {
break; break;
case 'get': case 'get':
try { try {
$settings['subject'] = $redis->Get('QW_SUBJ'); $settings['subject'] = $valkey->Get('QW_SUBJ');
$settings['sender'] = $redis->Get('QW_SENDER'); $settings['sender'] = $valkey->Get('QW_SENDER');
$settings['html_tmpl'] = htmlspecialchars($redis->Get('QW_HTML')); $settings['html_tmpl'] = htmlspecialchars($valkey->Get('QW_HTML'));
if (empty($settings['html_tmpl'])) { if (empty($settings['html_tmpl'])) {
$settings['html_tmpl'] = htmlspecialchars(file_get_contents("/tpls/quota.tpl")); $settings['html_tmpl'] = htmlspecialchars(file_get_contents("/tpls/quota.tpl"));
} }
@@ -57,7 +57,7 @@ function quota_notification($_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;
} }
@@ -66,7 +66,7 @@ function quota_notification($_action, $_data = null) {
} }
} }
function quota_notification_bcc($_action, $_data = null) { function quota_notification_bcc($_action, $_data = null) {
global $redis; global $valkey;
$_data_log = $_data; $_data_log = $_data;
if ($_SESSION['mailcow_cc_role'] != "admin" && $_SESSION['mailcow_cc_role'] != "domainadmin") { if ($_SESSION['mailcow_cc_role'] != "admin" && $_SESSION['mailcow_cc_role'] != "domainadmin") {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
@@ -108,13 +108,13 @@ function quota_notification_bcc($_action, $_data = null) {
} }
try { try {
$redis->hSet('QW_BCC', $domain, json_encode(array('bcc_rcpts' => $bcc_rcpts, 'active' => $active))); $valkey->hSet('QW_BCC', $domain, json_encode(array('bcc_rcpts' => $bcc_rcpts, 'active' => $active)));
} }
catch (RedisException $e) { 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;
} }
@@ -135,13 +135,13 @@ function quota_notification_bcc($_action, $_data = null) {
return false; return false;
} }
try { try {
return json_decode($redis->hGet('QW_BCC', $domain), true); return json_decode($valkey->hGet('QW_BCC', $domain), true);
} }
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;
} }
+18 -18
View File
@@ -1,6 +1,6 @@
<?php <?php
function ratelimit($_action, $_scope, $_data = null, $_extra = null) { function ratelimit($_action, $_scope, $_data = null, $_extra = null) {
global $redis; global $valkey;
$_data_log = $_data; $_data_log = $_data;
switch ($_action) { switch ($_action) {
case 'edit': case 'edit':
@@ -42,26 +42,26 @@ function ratelimit($_action, $_scope, $_data = null, $_extra = null) {
} }
if (empty($rl_value)) { if (empty($rl_value)) {
try { try {
$redis->hDel('RL_VALUE', $object); $valkey->hDel('RL_VALUE', $object);
} }
catch (RedisException $e) { catch (RedisException $e) {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'danger', 'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log),
'msg' => array('redis_error', $e) 'msg' => array('valkey_error', $e)
); );
continue; continue;
} }
} }
else { else {
try { try {
$redis->hSet('RL_VALUE', $object, $rl_value . ' / 1' . $rl_frame); $valkey->hSet('RL_VALUE', $object, $rl_value . ' / 1' . $rl_frame);
} }
catch (RedisException $e) { catch (RedisException $e) {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'danger', 'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log),
'msg' => array('redis_error', $e) 'msg' => array('valkey_error', $e)
); );
continue; continue;
} }
@@ -103,26 +103,26 @@ function ratelimit($_action, $_scope, $_data = null, $_extra = null) {
} }
if (empty($rl_value)) { if (empty($rl_value)) {
try { try {
$redis->hDel('RL_VALUE', $object); $valkey->hDel('RL_VALUE', $object);
} }
catch (RedisException $e) { catch (RedisException $e) {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'danger', 'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log),
'msg' => array('redis_error', $e) 'msg' => array('valkey_error', $e)
); );
continue; continue;
} }
} }
else { else {
try { try {
$redis->hSet('RL_VALUE', $object, $rl_value . ' / 1' . $rl_frame); $valkey->hSet('RL_VALUE', $object, $rl_value . ' / 1' . $rl_frame);
} }
catch (RedisException $e) { catch (RedisException $e) {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'danger', 'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log),
'msg' => array('redis_error', $e) 'msg' => array('valkey_error', $e)
); );
continue; continue;
} }
@@ -143,7 +143,7 @@ function ratelimit($_action, $_scope, $_data = null, $_extra = null) {
return false; return false;
} }
try { try {
if ($rl_value = $redis->hGet('RL_VALUE', $_data)) { if ($rl_value = $valkey->hGet('RL_VALUE', $_data)) {
$rl = explode(' / 1', $rl_value); $rl = explode(' / 1', $rl_value);
$data['value'] = $rl[0]; $data['value'] = $rl[0];
$data['frame'] = $rl[1]; $data['frame'] = $rl[1];
@@ -157,7 +157,7 @@ function ratelimit($_action, $_scope, $_data = null, $_extra = null) {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'danger', 'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log),
'msg' => array('redis_error', $e) 'msg' => array('valkey_error', $e)
); );
return false; return false;
} }
@@ -169,7 +169,7 @@ function ratelimit($_action, $_scope, $_data = null, $_extra = null) {
return false; return false;
} }
try { try {
if ($rl_value = $redis->hGet('RL_VALUE', $_data)) { if ($rl_value = $valkey->hGet('RL_VALUE', $_data)) {
$rl = explode(' / 1', $rl_value); $rl = explode(' / 1', $rl_value);
$data['value'] = $rl[0]; $data['value'] = $rl[0];
$data['frame'] = $rl[1]; $data['frame'] = $rl[1];
@@ -183,7 +183,7 @@ function ratelimit($_action, $_scope, $_data = null, $_extra = null) {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'danger', 'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log),
'msg' => array('redis_error', $e) 'msg' => array('valkey_error', $e)
); );
return false; return false;
} }
@@ -202,16 +202,16 @@ function ratelimit($_action, $_scope, $_data = null, $_extra = null) {
return false; return false;
} }
try { try {
$data_rllog = $redis->lRange('RL_LOG', 0, -1); $data_rllog = $valkey->lRange('RL_LOG', 0, -1);
if ($data_rllog) { if ($data_rllog) {
foreach ($data_rllog as $json_line) { foreach ($data_rllog as $json_line) {
if (preg_match('/' . $data['hash'] . '/i', $json_line)) { if (preg_match('/' . $data['hash'] . '/i', $json_line)) {
$redis->lRem('RL_LOG', $json_line, 0); $valkey->lRem('RL_LOG', $json_line, 0);
} }
} }
} }
if ($redis->type($data['hash']) == Redis::REDIS_HASH) { if ($valkey->type($data['hash']) == Redis::REDIS_HASH) {
$redis->delete($data['hash']); $valkey->delete($data['hash']);
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'success', 'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log),
@@ -232,7 +232,7 @@ function ratelimit($_action, $_scope, $_data = null, $_extra = null) {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'danger', 'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log),
'msg' => array('redis_error', $e) 'msg' => array('valkey_error', $e)
); );
return false; return false;
} }
+5 -1
View File
@@ -62,7 +62,11 @@ if ($app_links_processed){
} }
} }
// Workaround to get text with <br> straight to twig.
// Using "nl2br" doesn't work with Twig as it would escape everything by default.
if (isset($UI_TEXTS["ui_footer"])) {
$UI_TEXTS["ui_footer"] = nl2br($UI_TEXTS["ui_footer"]);
}
$globalVariables = [ $globalVariables = [
'mailcow_hostname' => getenv('MAILCOW_HOSTNAME'), 'mailcow_hostname' => getenv('MAILCOW_HOSTNAME'),
+9 -1
View File
@@ -4,7 +4,7 @@ function init_db_schema()
try { try {
global $pdo; global $pdo;
$db_version = "19082025_1436"; $db_version = "07102025_1015";
$stmt = $pdo->query("SHOW TABLES LIKE 'versions'"); $stmt = $pdo->query("SHOW TABLES LIKE 'versions'");
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
@@ -1337,6 +1337,14 @@ function init_db_schema()
$pdo->query($create); $pdo->query($create);
} }
// Clear old app_passwd log entries
$pdo->exec("DELETE FROM logs
WHERE role != 'unauthenticated'
AND JSON_EXTRACT(`call`, '$[0]') = 'app_passwd'
AND JSON_EXTRACT(`call`, '$[1]') = 'edit'
AND (JSON_CONTAINS_PATH(`call`, 'one', '$[2].password')
OR JSON_CONTAINS_PATH(`call`, 'one', '$[2].password2'));");
// Mitigate imapsync argument injection issue // Mitigate imapsync argument injection issue
$pdo->query("UPDATE `imapsync` SET `custom_params` = '' $pdo->query("UPDATE `imapsync` SET `custom_params` = ''
WHERE `custom_params` LIKE '%pipemess%' WHERE `custom_params` LIKE '%pipemess%'
+8 -8
View File
@@ -57,22 +57,22 @@ $WebAuthn = new lbuchs\WebAuthn\WebAuthn('WebAuthn Library', $server_name, $form
// only include root ca's when needed // only include root ca's when needed
if (getenv('WEBAUTHN_ONLY_TRUSTED_VENDORS') == 'y') $WebAuthn->addRootCertificates($_SERVER['DOCUMENT_ROOT'] . '/inc/lib/WebAuthn/rootCertificates'); if (getenv('WEBAUTHN_ONLY_TRUSTED_VENDORS') == 'y') $WebAuthn->addRootCertificates($_SERVER['DOCUMENT_ROOT'] . '/inc/lib/WebAuthn/rootCertificates');
// 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) {
// Stop when redis is not available // Stop when valkey is not available
http_response_code(500); http_response_code(500);
?> ?>
<center style='font-family:sans-serif;'>Connection to Redis failed.<br /><br />The following error was reported:<br/><?=$e->getMessage();?></center> <center style='font-family:sans-serif;'>Connection to Valkey failed.<br /><br />The following error was reported:<br/><?=$e->getMessage();?></center>
<?php <?php
exit; exit;
} }
+4 -2
View File
@@ -1,7 +1,9 @@
<?php <?php
// Start session // Start session
if (session_status() !== PHP_SESSION_ACTIVE) { if (session_status() !== PHP_SESSION_ACTIVE) {
session_name($SESSION_NAME);
ini_set("session.cookie_httponly", 1); ini_set("session.cookie_httponly", 1);
ini_set("session.cookie_samesite", $SESSION_SAMESITE_POLICY);
ini_set('session.gc_maxlifetime', $SESSION_LIFETIME); ini_set('session.gc_maxlifetime', $SESSION_LIFETIME);
} }
@@ -67,7 +69,7 @@ if (!empty($_SERVER['HTTP_X_API_KEY'])) {
} }
} }
else { else {
$redis->publish("F2B_CHANNEL", "mailcow UI: Invalid password for API_USER by " . $_SERVER['REMOTE_ADDR']); $valkey->publish("F2B_CHANNEL", "mailcow UI: Invalid password for API_USER by " . $_SERVER['REMOTE_ADDR']);
error_log("mailcow UI: Invalid password for " . $user . " by " . $_SERVER['REMOTE_ADDR']); error_log("mailcow UI: Invalid password for " . $user . " by " . $_SERVER['REMOTE_ADDR']);
http_response_code(401); http_response_code(401);
echo json_encode(array( echo json_encode(array(
@@ -79,7 +81,7 @@ if (!empty($_SERVER['HTTP_X_API_KEY'])) {
} }
} }
else { else {
$redis->publish("F2B_CHANNEL", "mailcow UI: Invalid password for API_USER by " . $_SERVER['REMOTE_ADDR']); $valkey->publish("F2B_CHANNEL", "mailcow UI: Invalid password for API_USER by " . $_SERVER['REMOTE_ADDR']);
error_log("mailcow UI: Invalid password for " . $user . " by " . $_SERVER['REMOTE_ADDR']); error_log("mailcow UI: Invalid password for " . $user . " by " . $_SERVER['REMOTE_ADDR']);
http_response_code(401); http_response_code(401);
echo json_encode(array( echo json_encode(array(
+2 -2
View File
@@ -80,7 +80,7 @@ if (isset($_POST["verify_tfa_login"])) {
intval($user_details['attributes']['force_pw_update']) != 1 && intval($user_details['attributes']['force_pw_update']) != 1 &&
getenv('SKIP_SOGO') != "y" && getenv('SKIP_SOGO') != "y" &&
!$is_dual) { !$is_dual) {
header("Location: /SOGo/so/{$_SESSION['mailcow_cc_username']}"); header("Location: /SOGo/so/");
die(); die();
} else { } else {
header("Location: /user"); header("Location: /user");
@@ -146,7 +146,7 @@ if (isset($_POST["login_user"]) && isset($_POST["pass_user"])) {
intval($user_details['attributes']['force_pw_update']) != 1 && intval($user_details['attributes']['force_pw_update']) != 1 &&
getenv('SKIP_SOGO') != "y" && getenv('SKIP_SOGO') != "y" &&
!$is_dual) { !$is_dual) {
header("Location: /SOGo/so/{$login_user}"); header("Location: /SOGo/so/");
die(); die();
} else { } else {
header("Location: /user"); header("Location: /user");
+7
View File
@@ -153,6 +153,13 @@ $LOG_PAGINATION_SIZE = 50;
// Session lifetime in seconds // Session lifetime in seconds
$SESSION_LIFETIME = 10800; $SESSION_LIFETIME = 10800;
// Session SameSite Policy
// Use "None", "Lax" or "Strict"
$SESSION_SAMESITE_POLICY = "Lax";
// Name of the session cookie
$SESSION_NAME = "MCSESSID";
// Label for OTP devices // Label for OTP devices
$OTP_LABEL = "mailcow UI"; $OTP_LABEL = "mailcow UI";
+5 -1
View File
@@ -48,7 +48,11 @@ $(document).ready(function() {
}) })
} }
$(".rot-enc").html(function(){ $(".rot-enc").html(function(){
return str_rot13($(this).html()) footer_html = $(this).html();
footer_html = footer_html.replace(/&lt;/g, '<').replace(/&gt;/g, '>')
.replace(/&amp;/g, '&').replace(/&nzc;/g, '&')
.replace(/&quot;/g, '"').replace(/&#x27;/g, "'");
return str_rot13(footer_html)
}); });
// https://stackoverflow.com/questions/4399005/implementing-jquerys-shake-effect-with-animate // https://stackoverflow.com/questions/4399005/implementing-jquerys-shake-effect-with-animate
function shake(div,interval,distance,times) { function shake(div,interval,distance,times) {
-1
View File
@@ -715,7 +715,6 @@ jQuery(function($){
$('.app_hide').off('change'); $('.app_hide').off('change');
$('.app_hide').on('change', function (e) { $('.app_hide').on('change', function (e) {
var value = $(this).is(':checked') ? '1' : '0'; var value = $(this).is(':checked') ? '1' : '0';
console.log(value)
$(this).parent().children(':first-child').val(value); $(this).parent().children(':first-child').val(value);
}) })
} }
-6
View File
@@ -47,8 +47,6 @@ $(document).ready(function() {
window.fetch("/api/v1/get/status/host/ip", { method:'GET', cache:'no-cache' }).then(function(response) { window.fetch("/api/v1/get/status/host/ip", { method:'GET', cache:'no-cache' }).then(function(response) {
return response.json(); return response.json();
}).then(function(data) { }).then(function(data) {
console.log(data);
// display host ips // display host ips
if (data.ipv4) if (data.ipv4)
$("#host_ipv4").text(data.ipv4); $("#host_ipv4").text(data.ipv4);
@@ -1348,8 +1346,6 @@ function update_stats(timeout=5){
window.fetch("/api/v1/get/status/host", {method:'GET',cache:'no-cache'}).then(function(response) { window.fetch("/api/v1/get/status/host", {method:'GET',cache:'no-cache'}).then(function(response) {
return response.json(); return response.json();
}).then(function(data) { }).then(function(data) {
console.log(data);
if (data){ if (data){
// display table data // display table data
$("#host_date").text(data.system_time); $("#host_date").text(data.system_time);
@@ -1399,8 +1395,6 @@ function update_container_stats(timeout=5){
var diskIOCtx = Chart.getChart(container + "_DiskIOChart"); var diskIOCtx = Chart.getChart(container + "_DiskIOChart");
var netIOCtx = Chart.getChart(container + "_NetIOChart"); var netIOCtx = Chart.getChart(container + "_NetIOChart");
console.log(container);
console.log(data);
prev_stats = null; prev_stats = null;
if (data.length >= 2){ if (data.length >= 2){
prev_stats = data[data.length -2]; prev_stats = data[data.length -2];
-1
View File
@@ -66,7 +66,6 @@ $(document).ready(function() {
// load tags // load tags
if ($('#tags').length){ if ($('#tags').length){
var tagsEl = $('#tags').parent().find('.tag-values')[0]; var tagsEl = $('#tags').parent().find('.tag-values')[0];
console.log($(tagsEl).val())
var tags = JSON.parse($(tagsEl).val()); var tags = JSON.parse($(tagsEl).val());
$(tagsEl).val(""); $(tagsEl).val("");
-5
View File
@@ -1949,11 +1949,6 @@ jQuery(function($){
defaultContent: '', defaultContent: '',
responsivePriority: 5, responsivePriority: 5,
}, },
{
title: lang.bcc_destinations,
data: 'bcc_dest',
defaultContent: ''
},
{ {
title: lang.sogo_visible, title: lang.sogo_visible,
data: 'sogo_visible', data: 'sogo_visible',
-5
View File
@@ -169,7 +169,6 @@ jQuery(function($){
type: "GET", type: "GET",
url: "/api/v1/get/time_limited_aliases", url: "/api/v1/get/time_limited_aliases",
dataSrc: function(data){ dataSrc: function(data){
console.log(data);
$.each(data, function (i, item) { $.each(data, function (i, item) {
if (acl_data.spam_alias === 1) { if (acl_data.spam_alias === 1) {
item.action = '<div class="btn-group">' + item.action = '<div class="btn-group">' +
@@ -262,7 +261,6 @@ jQuery(function($){
type: "GET", type: "GET",
url: '/api/v1/get/syncjobs/' + encodeURIComponent(mailcow_cc_username) + '/no_log', url: '/api/v1/get/syncjobs/' + encodeURIComponent(mailcow_cc_username) + '/no_log',
dataSrc: function(data){ dataSrc: function(data){
console.log(data);
$.each(data, function (i, item) { $.each(data, function (i, item) {
item.user1 = escapeHtml(item.user1); item.user1 = escapeHtml(item.user1);
item.log = '<a href="#syncjobLogModal" data-bs-toggle="modal" data-syncjob-id="' + item.id + '">' + lang.open_logs + '</a>' item.log = '<a href="#syncjobLogModal" data-bs-toggle="modal" data-syncjob-id="' + item.id + '">' + lang.open_logs + '</a>'
@@ -418,7 +416,6 @@ jQuery(function($){
type: "GET", type: "GET",
url: '/api/v1/get/app-passwd/all', url: '/api/v1/get/app-passwd/all',
dataSrc: function(data){ dataSrc: function(data){
console.log(data);
$.each(data, function (i, item) { $.each(data, function (i, item) {
item.name = escapeHtml(item.name) item.name = escapeHtml(item.name)
item.protocols = [] item.protocols = []
@@ -514,7 +511,6 @@ jQuery(function($){
type: "GET", type: "GET",
url: '/api/v1/get/policy_wl_mailbox', url: '/api/v1/get/policy_wl_mailbox',
dataSrc: function(data){ dataSrc: function(data){
console.log(data);
$.each(data, function (i, item) { $.each(data, function (i, item) {
if (validateEmail(item.object)) { if (validateEmail(item.object)) {
item.chkbox = '<input type="checkbox" class="form-check-input" data-id="policy_wl_mailbox" name="multi_select" value="' + item.prefid + '" />'; item.chkbox = '<input type="checkbox" class="form-check-input" data-id="policy_wl_mailbox" name="multi_select" value="' + item.prefid + '" />';
@@ -585,7 +581,6 @@ jQuery(function($){
type: "GET", type: "GET",
url: '/api/v1/get/policy_bl_mailbox', url: '/api/v1/get/policy_bl_mailbox',
dataSrc: function(data){ dataSrc: function(data){
console.log(data);
$.each(data, function (i, item) { $.each(data, function (i, item) {
if (validateEmail(item.object)) { if (validateEmail(item.object)) {
item.chkbox = '<input type="checkbox" class="form-check-input" data-id="policy_bl_mailbox" name="multi_select" value="' + item.prefid + '" />'; item.chkbox = '<input type="checkbox" class="form-check-input" data-id="policy_bl_mailbox" name="multi_select" value="' + item.prefid + '" />';
+3 -3
View File
@@ -8,7 +8,7 @@ header('Content-Type: application/json');
error_reporting(0); error_reporting(0);
function api_log($_data) { function api_log($_data) {
global $redis; global $valkey;
$data_var = array(); $data_var = array();
foreach ($_data as $data => &$value) { foreach ($_data as $data => &$value) {
if ($data == 'csrf_token') { if ($data == 'csrf_token') {
@@ -36,12 +36,12 @@ function api_log($_data) {
'remote' => get_remote_ip(), 'remote' => get_remote_ip(),
'data' => implode(', ', $data_var) 'data' => implode(', ', $data_var)
); );
$redis->lPush('API_LOG', json_encode($log_line)); $valkey->lPush('API_LOG', json_encode($log_line));
} }
catch (RedisException $e) { catch (RedisException $e) {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'danger', 'type' => 'danger',
'msg' => 'Redis: '.$e 'msg' => 'Valkey: '.$e
); );
return false; return false;
} }
+36 -12
View File
@@ -24,7 +24,7 @@
"sogo_access": "Správa přístupu do SOGo", "sogo_access": "Správa přístupu do SOGo",
"sogo_profile_reset": "Resetování profilu SOGo", "sogo_profile_reset": "Resetování profilu SOGo",
"spam_alias": "Dočasné aliasy", "spam_alias": "Dočasné aliasy",
"spam_policy": "Blacklist/Whitelist", "spam_policy": "Denylist/Allowlist",
"spam_score": "Skóre spamu", "spam_score": "Skóre spamu",
"syncjobs": "Synchronizační úlohy", "syncjobs": "Synchronizační úlohy",
"tls_policy": "Pravidla TLS", "tls_policy": "Pravidla TLS",
@@ -109,7 +109,9 @@
"validate": "Ověřit", "validate": "Ověřit",
"validation_success": "Úspěšně ověřeno", "validation_success": "Úspěšně ověřeno",
"tags": "Štítky", "tags": "Štítky",
"dry": "Simulovat synchronizaci" "dry": "Simulovat synchronizaci",
"internal": "Interní",
"internal_info": "Interní aliasy jsou přístupné jen z vlastních domén nebo jejich aliasů."
}, },
"admin": { "admin": {
"access": "Přístupy", "access": "Přístupy",
@@ -303,7 +305,7 @@
"rspamd_global_filters": "Mapa globálních filtrů", "rspamd_global_filters": "Mapa globálních filtrů",
"rspamd_global_filters_agree": "Budu opatrný!", "rspamd_global_filters_agree": "Budu opatrný!",
"rspamd_global_filters_info": "Mapa globálních filtrů obsahuje jiné globální black- a whitelisty.", "rspamd_global_filters_info": "Mapa globálních filtrů obsahuje jiné globální black- a whitelisty.",
"rspamd_global_filters_regex": "Názvy jsou dostatečným vysvětlením. Musí obsahovat jen platné regulární výrazy ve formátu \"/vyraz/parametry\" (e.g. <code>/.+@domena\\.tld/i</code>).<br>\r\n Každý výraz bude podroben základní kontrole, přesto je možné Rspamd 'rozbít', nebude-li syntax zcela korektní.<br>\r\n Rspamd se pokusí načíst mapu po každé změně. V případě potíží, <a href=\"\" data-toggle=\"modal\" data-container=\"rspamd-mailcow\" data-target=\"#RestartContainer\">restartujte Rspamd</a>, aby se konfigurace načetla explicitně.", "rspamd_global_filters_regex": "Názvy stačí k vysvětlení. Položky musejí obsahovat jen platné regulární výrazy ve tvaru \"/vyraz/parametry\" (e.g. <code>/.+@domena\\.tld/i</code>).<br>\n Každý výraz bude podroben základní kontrole, přesto je možné Rspamd 'rozbít', nebude-li syntax zcela korektní.<br>\n Rspamd se pokusí po každé změně načíst mapu znovu. V případě potíží <a href=\"\" data-toggle=\"modal\" data-container=\"rspamd-mailcow\" data-target=\"#RestartContainer\">restartujte Rspamd</a>, aby se konfigurace načetla explicitně.",
"rspamd_settings_map": "Nastavení Rspamd", "rspamd_settings_map": "Nastavení Rspamd",
"sal_level": "Úroveň 'Moo'", "sal_level": "Úroveň 'Moo'",
"save": "Uložit změny", "save": "Uložit změny",
@@ -407,7 +409,9 @@
"iam_extra_permission": "Aby vše fungovalo, musí mít mailcow klient v Keycloaku nastavený <code>servisní účet</code> a povolení <code>view-users</code>.", "iam_extra_permission": "Aby vše fungovalo, musí mít mailcow klient v Keycloaku nastavený <code>servisní účet</code> a povolení <code>view-users</code>.",
"iam_host": "Hostitel", "iam_host": "Hostitel",
"iam_host_info": "Zadejte jeden či více hostitelů, oddělte čárkou.", "iam_host_info": "Zadejte jeden či více hostitelů, oddělte čárkou.",
"iam_import_users": "Importovat uživatele" "iam_import_users": "Importovat uživatele",
"iam_auth_flow": "Proces autentizace",
"needs_restart": "potřebuje restart"
}, },
"danger": { "danger": {
"access_denied": "Přístup odepřen nebo jsou neplatná data ve formuláři", "access_denied": "Přístup odepřen nebo jsou neplatná data ve formuláři",
@@ -494,7 +498,7 @@
"pushover_token": "Token služby Pushover nemá správný formát", "pushover_token": "Token služby Pushover nemá správný formát",
"quota_not_0_not_numeric": "Kvóta musí být číslo >= 0", "quota_not_0_not_numeric": "Kvóta musí být číslo >= 0",
"recipient_map_entry_exists": "Položka mapy příjemců \"%s\" již existuje", "recipient_map_entry_exists": "Položka mapy příjemců \"%s\" již existuje",
"redis_error": "Chyba Redis: %s", "valkey_error": "Chyba Valkey: %s",
"relayhost_invalid": "Položky %s je neplatná", "relayhost_invalid": "Položky %s je neplatná",
"release_send_failed": "Zprávu nelze propustit: %s", "release_send_failed": "Zprávu nelze propustit: %s",
"reset_f2b_regex": "Regex filtr se nepodařilo resetovat, zkuste to znovu nebo počkejte pár sekund a obnovte stránku.", "reset_f2b_regex": "Regex filtr se nepodařilo resetovat, zkuste to znovu nebo počkejte pár sekund a obnovte stránku.",
@@ -548,7 +552,11 @@
"img_size_exceeded": "Obrázek má větší než povolenou velikost souboru", "img_size_exceeded": "Obrázek má větší než povolenou velikost souboru",
"invalid_reset_token": "Neplatný resetovací token", "invalid_reset_token": "Neplatný resetovací token",
"required_data_missing": "Chybí potřebný údaj %s", "required_data_missing": "Chybí potřebný údaj %s",
"reset_token_limit_exceeded": "Byl překročen limit na reset tokeny. Zkuste to později." "reset_token_limit_exceeded": "Byl překročen limit na reset tokeny. Zkuste to později.",
"max_age_invalid": "Maximální životnost %s není platná",
"mode_invalid": "Mód %s není platný",
"mx_invalid": "Záznam MX %s není platný",
"version_invalid": "Verze %s není platná"
}, },
"datatables": { "datatables": {
"emptyTable": "Tabulka neobsahuje žádná data", "emptyTable": "Tabulka neobsahuje žádná data",
@@ -584,7 +592,7 @@
"history_all_servers": "Záznam (všechny servery)", "history_all_servers": "Záznam (všechny servery)",
"in_memory_logs": "Logy v paměti", "in_memory_logs": "Logy v paměti",
"last_modified": "Naposledy změněn", "last_modified": "Naposledy změněn",
"log_info": "<p><b>Logy v paměti</b> jsou shromažďovány v Redis seznamech a jsou oříznuty na LOG_LINES (%d) každou minutu, aby se nepřetěžoval server.\r\n <br>Logy v paměti nemají být trvalé. Všechny aplikace, které logují do paměti, zároveň logují i do Docker služby podle nastavení logging driveru.\r\n <br>Logy v paměti lze použít pro ladění drobných problémů s kontejnery.</p>\r\n <p><b>Externí logy</b> jsou shromažďovány pomocí API dané aplikace.</p>\r\n <p><b>Statické logy</b> jsou většinou logy činností, které nejsou zaznamenávány do Docker služby, ale přesto je dobré je schraňovat (výjimkou jsou logy API).</p>", "log_info": "<p><b>Logy v paměti</b> jsou shromažďovány v Valkey seznamech a jsou oříznuty na LOG_LINES (%d) každou minutu, aby se nepřetěžoval server.\r\n <br>Logy v paměti nemají být trvalé. Všechny aplikace, které logují do paměti, zároveň logují i do Docker služby podle nastavení logging driveru.\r\n <br>Logy v paměti lze použít pro ladění drobných problémů s kontejnery.</p>\r\n <p><b>Externí logy</b> jsou shromažďovány pomocí API dané aplikace.</p>\r\n <p><b>Statické logy</b> jsou většinou logy činností, které nejsou zaznamenávány do Docker služby, ale přesto je dobré je schraňovat (výjimkou jsou logy API).</p>",
"login_time": "Čas", "login_time": "Čas",
"logs": "Logy", "logs": "Logy",
"online_users": "Uživatelů online", "online_users": "Uživatelů online",
@@ -759,7 +767,20 @@
"mailbox_rename_warning": "DŮLEŽITÉ! Vytvořte si zálohu schránky, než ji přejmenujete.", "mailbox_rename_warning": "DŮLEŽITÉ! Vytvořte si zálohu schránky, než ji přejmenujete.",
"mailbox_rename_alias": "Automaticky vytvořit alias", "mailbox_rename_alias": "Automaticky vytvořit alias",
"mailbox_rename_title": "Nový název zdejší schránky", "mailbox_rename_title": "Nový název zdejší schránky",
"pushover": "Pushover" "pushover": "Pushover",
"internal": "Interní",
"internal_info": "Interní aliasy jsou přístupné jen z vlastních domén nebo jejich aliasů.",
"mta_sts": "MTA-STS",
"mta_sts_info": "<a href='https://en.wikipedia.org/wiki/Simple_Mail_Transfer_Protocol#SMTP_MTA_Strict_Transport_Security' target='_blank'>MTA-STS</a> je standard, jenž říká poštovním serverům, aby komunikovaly pomocí TLS s platnými certifikáty. <br>Používá se, pokud není k dispozici <a target='_blank' href='https://en.wikipedia.org/wiki/DNS-based_Authentication_of_Named_Entities'>DANE</a>, např. chybí-li či není podporováno DNSSEC.<br><b>Pozn.</b>: Podporuje-li přijímající doména DANE a DNSSEC, bude <b>vždy</b> použito DANE; MTA-STS zůstane jako plán B.",
"mta_sts_version": "Verze",
"mta_sts_version_info": "Určuje verzi standardu MTA-STS zatím je podporována jen <code>STSv1</code>.",
"mta_sts_mode": "Mód",
"mta_sts_mode_info": "K dispozici jsou tři módy:<ul><li><em>testing</em> pravidlo se jen sleduje, porušení je bez následků.</li><li><em>enforce</em> pravidlo je důsledně dodržováno, spojení bez platného TLS jsou odmítána.</li><li><em>none</em> pravidlo je zveřejněno, ale neuplatňuje se.</li></ul>",
"mta_sts_max_age": "Maximální životnost",
"mta_sts_max_age_info": "Doba v sekundách, po niž poštovní servery mohou toho pravidlo držet v mezipaměti bez nutnosti obnovení.",
"mta_sts_mx": "Server MX",
"mta_sts_mx_info": "Dovoluje odesílání jen výslovně vypsaným poštovním serverům; odesílající server kontroluje, že server MX určený v DNS odpovídá pravidlu, a povolí doručení jen s platným certifikátem TLS (chrání přes útokem typu MITM).",
"mta_sts_mx_notice": "Lze zadat více serverů MX (oddělte čárkou)."
}, },
"fido2": { "fido2": {
"confirm": "Potvrdit", "confirm": "Potvrdit",
@@ -829,7 +850,8 @@
"login_admintext": "Přihlášení správce", "login_admintext": "Přihlášení správce",
"login_user": "Přihlášení uživatele", "login_user": "Přihlášení uživatele",
"login_dadmin": "Přihlášení správce domény", "login_dadmin": "Přihlášení správce domény",
"login_admin": "Přihlášení správce" "login_admin": "Přihlášení správce",
"email": "Mailová adresa"
}, },
"mailbox": { "mailbox": {
"action": "Akce", "action": "Akce",
@@ -861,7 +883,7 @@
"bcc": "BCC", "bcc": "BCC",
"bcc_destination": "Cíl kopie", "bcc_destination": "Cíl kopie",
"bcc_destinations": "Cíl kopií", "bcc_destinations": "Cíl kopií",
"bcc_info": "Skrytá kopie (mapa BCC) se používá pro tiché předávání kopií všech zpráv na jinou adresu. Mapa příjemců se použije, funguje-li je místní cíl jako adresát zprávy. Totéž platí pro mapy odesílatelů.\nMístní cíl se nedozví, selže-li doručení na cíl BCC.", "bcc_info": "<br/>Skrytá kopie (mapa BCC) se používá pro tiché předávání kopií všech zpráv na jinou adresu. Mapa příjemců se použije, funguje-li je místní cíl jako adresát zprávy. Totéž platí pro mapy odesílatelů.<br/>\n Místní cíl se nedozví, selže-li doručení na cíl BCC.",
"bcc_local_dest": "Týká se", "bcc_local_dest": "Týká se",
"bcc_map": "Skrytá kopie", "bcc_map": "Skrytá kopie",
"bcc_map_type": "Typ skryté kopie", "bcc_map_type": "Typ skryté kopie",
@@ -1005,7 +1027,8 @@
"weekly": "Každý týden", "weekly": "Každý týden",
"yes": "&#10003;", "yes": "&#10003;",
"relay_unknown": "Předávání neexistujících schránek", "relay_unknown": "Předávání neexistujících schránek",
"iam": "Poskytovatel identity" "iam": "Poskytovatel identity",
"internal": "Interní"
}, },
"oauth2": { "oauth2": {
"access_denied": "K udělení přístupu se přihlašte jako vlastník mailové schránky.", "access_denied": "K udělení přístupu se přihlašte jako vlastník mailové schránky.",
@@ -1082,7 +1105,8 @@
"hold_mail_legend": "Podrží vybrané e-maily. (Zabrání dalším pokusům o doručení)", "hold_mail_legend": "Podrží vybrané e-maily. (Zabrání dalším pokusům o doručení)",
"show_message": "Zobrazit zprávu", "show_message": "Zobrazit zprávu",
"unhold_mail": "Uvolnit", "unhold_mail": "Uvolnit",
"unhold_mail_legend": "Uvolnit vybrané e-maily k doručení. (Pouze v případě předchozího podržení)" "unhold_mail_legend": "Uvolnit vybrané e-maily k doručení. (Pouze v případě předchozího podržení)",
"unban": "odblokovat"
}, },
"ratelimit": { "ratelimit": {
"disabled": "Vypnuto", "disabled": "Vypnuto",
+2 -2
View File
@@ -401,7 +401,7 @@
"pushover_token": "Pushover-token har et forkert format", "pushover_token": "Pushover-token har et forkert format",
"quota_not_0_not_numeric": "Kvoten skal være numerisk og >= 0", "quota_not_0_not_numeric": "Kvoten skal være numerisk og >= 0",
"recipient_map_entry_exists": "En modtagerkortpost \"%s\" eksisterer", "recipient_map_entry_exists": "En modtagerkortpost \"%s\" eksisterer",
"redis_error": "Redis fejl: %s", "valkey_error": "Valkey fejl: %s",
"relayhost_invalid": "Kortindtastning %s er ugyldig", "relayhost_invalid": "Kortindtastning %s er ugyldig",
"release_send_failed": "Beskeden kunne ikke frigives: %s", "release_send_failed": "Beskeden kunne ikke frigives: %s",
"reset_f2b_regex": "Regex filter kunne ikke nulstilles i tide, prøv igen eller vent et par sekunder mere, og genindlæs webstedet.", "reset_f2b_regex": "Regex filter kunne ikke nulstilles i tide, prøv igen eller vent et par sekunder mere, og genindlæs webstedet.",
@@ -444,7 +444,7 @@
"external_logs": "Eksterne logfiler", "external_logs": "Eksterne logfiler",
"history_all_servers": "Historie (alle servere)", "history_all_servers": "Historie (alle servere)",
"in_memory_logs": "In-memory logs", "in_memory_logs": "In-memory logs",
"log_info": "<p>mailcow <b>in-memory logs</b> er samlet i Redis-lister og trimmet til LOG_LINES (%d) hvert minut for at reducere hamring.\r\n <br>Logbøger i hukommelsen er ikke beregnet til at være vedholdende. Alle applikationer, der logger ind i hukommelsen, logger også på Docker-dæmonen og derfor til standardlogdriveren.\r\n <br>Logtypen i hukommelsen skal bruges til fejlfinding af mindre problemer med containere.</p>\r\n <p><b>Eksterne logfiler</b> indsamles via API for den givne applikation.</p>\r\n <p><b>Statiske logfiler</b> er for det meste aktivitetslogfiler, der ikke er logget på Dockerd, men stadig skal være vedholdende (undtagen API-logfiler).</p>", "log_info": "<p>mailcow <b>in-memory logs</b> er samlet i Valkey-lister og trimmet til LOG_LINES (%d) hvert minut for at reducere hamring.\r\n <br>Logbøger i hukommelsen er ikke beregnet til at være vedholdende. Alle applikationer, der logger ind i hukommelsen, logger også på Docker-dæmonen og derfor til standardlogdriveren.\r\n <br>Logtypen i hukommelsen skal bruges til fejlfinding af mindre problemer med containere.</p>\r\n <p><b>Eksterne logfiler</b> indsamles via API for den givne applikation.</p>\r\n <p><b>Statiske logfiler</b> er for det meste aktivitetslogfiler, der ikke er logget på Dockerd, men stadig skal være vedholdende (undtagen API-logfiler).</p>",
"logs": "Logs", "logs": "Logs",
"restart_container": "Genstart", "restart_container": "Genstart",
"started_on": "Startede den", "started_on": "Startede den",
+3 -3
View File
@@ -514,7 +514,7 @@
"quota_not_0_not_numeric": "Speicherplatz muss numerisch und >= 0 sein", "quota_not_0_not_numeric": "Speicherplatz muss numerisch und >= 0 sein",
"recipient_map_entry_exists": "Eine Empfängerumschreibung für Objekt \"%s\" existiert bereits", "recipient_map_entry_exists": "Eine Empfängerumschreibung für Objekt \"%s\" existiert bereits",
"recovery_email_failed": "E-Mail zur Wiederherstellung konnte nicht gesendet werden. Bitte wenden Sie sich an Ihren Administrator.", "recovery_email_failed": "E-Mail zur Wiederherstellung konnte nicht gesendet werden. Bitte wenden Sie sich an Ihren Administrator.",
"redis_error": "Redis Fehler: %s", "valkey_error": "Valkey Fehler: %s",
"relayhost_invalid": "Map-Eintrag %s ist ungültig", "relayhost_invalid": "Map-Eintrag %s ist ungültig",
"release_send_failed": "Die Nachricht konnte nicht versendet werden: %s", "release_send_failed": "Die Nachricht konnte nicht versendet werden: %s",
"reset_f2b_regex": "Regex-Filter konnten nicht in vorgegebener Zeit zurückgesetzt werden, bitte erneut versuchen oder die Webseite neu laden.", "reset_f2b_regex": "Regex-Filter konnten nicht in vorgegebener Zeit zurückgesetzt werden, bitte erneut versuchen oder die Webseite neu laden.",
@@ -600,7 +600,7 @@
"history_all_servers": "History (alle Server)", "history_all_servers": "History (alle Server)",
"in_memory_logs": "In-memory Logs", "in_memory_logs": "In-memory Logs",
"last_modified": "Zuletzt geändert", "last_modified": "Zuletzt geändert",
"log_info": "<p>mailcow <b>in-memory Logs</b> werden in Redis Listen gespeichert, die maximale Anzahl der Einträge pro Anwendung richtet sich nach LOG_LINES (%d).\r\n <br>In-memory Logs sind vergänglich und nicht zur ständigen Aufbewahrung bestimmt. Alle Anwendungen, die in-memory protokollieren, schreiben ebenso in den Docker Daemon.\r\n <br>Das in-memory Protokoll versteht sich als schnelle Übersicht zum Debugging eines Containers, für komplexere Protokolle sollte der Docker Daemon konsultiert werden.</p>\r\n <p><b>Externe Logs</b> werden via API externer Applikationen bezogen.</p>\r\n <p><b>Statische Logs</b> sind weitestgehend Aktivitätsprotokolle, die nicht in den Docker Daemon geschrieben werden, jedoch permanent verfügbar sein müssen (ausgeschlossen API Logs).</p>", "log_info": "<p>mailcow <b>in-memory Logs</b> werden in Valkey Listen gespeichert, die maximale Anzahl der Einträge pro Anwendung richtet sich nach LOG_LINES (%d).\r\n <br>In-memory Logs sind vergänglich und nicht zur ständigen Aufbewahrung bestimmt. Alle Anwendungen, die in-memory protokollieren, schreiben ebenso in den Docker Daemon.\r\n <br>Das in-memory Protokoll versteht sich als schnelle Übersicht zum Debugging eines Containers, für komplexere Protokolle sollte der Docker Daemon konsultiert werden.</p>\r\n <p><b>Externe Logs</b> werden via API externer Applikationen bezogen.</p>\r\n <p><b>Statische Logs</b> sind weitestgehend Aktivitätsprotokolle, die nicht in den Docker Daemon geschrieben werden, jedoch permanent verfügbar sein müssen (ausgeschlossen API Logs).</p>",
"login_time": "Zeit", "login_time": "Zeit",
"logs": "Protokolle", "logs": "Protokolle",
"memory": "Arbeitsspeicher", "memory": "Arbeitsspeicher",
@@ -1100,7 +1100,7 @@
"legend": "Funktionen der Mailqueue Aktionen:", "legend": "Funktionen der Mailqueue Aktionen:",
"ays": "Soll die derzeitige Queue wirklich komplett bereinigt werden?", "ays": "Soll die derzeitige Queue wirklich komplett bereinigt werden?",
"deliver_mail": "Ausliefern", "deliver_mail": "Ausliefern",
"deliver_mail_legend": "Versucht eine erneute Zustellung der ausgwählten Mails.", "deliver_mail_legend": "Versucht eine erneute Zustellung der ausgewählten Mails.",
"hold_mail": "Zurückhalten", "hold_mail": "Zurückhalten",
"hold_mail_legend": "Hält die ausgewählten Mails zurück. (Verhindert weitere Zustellversuche)", "hold_mail_legend": "Hält die ausgewählten Mails zurück. (Verhindert weitere Zustellversuche)",
"queue_manager": "Queue Manager", "queue_manager": "Queue Manager",
+2 -2
View File
@@ -514,7 +514,7 @@
"quota_not_0_not_numeric": "Quota must be numeric and >= 0", "quota_not_0_not_numeric": "Quota must be numeric and >= 0",
"recipient_map_entry_exists": "A Recipient map entry \"%s\" exists", "recipient_map_entry_exists": "A Recipient map entry \"%s\" exists",
"recovery_email_failed": "Could not send a recovery email. Please contact your administrator.", "recovery_email_failed": "Could not send a recovery email. Please contact your administrator.",
"redis_error": "Redis error: %s", "valkey_error": "Valkey error: %s",
"relayhost_invalid": "Map entry %s is invalid", "relayhost_invalid": "Map entry %s is invalid",
"release_send_failed": "Message could not be released: %s", "release_send_failed": "Message could not be released: %s",
"required_data_missing": "Required data %s is missing", "required_data_missing": "Required data %s is missing",
@@ -600,7 +600,7 @@
"history_all_servers": "History (all servers)", "history_all_servers": "History (all servers)",
"in_memory_logs": "In-memory logs", "in_memory_logs": "In-memory logs",
"last_modified": "Last modified", "last_modified": "Last modified",
"log_info": "<p>mailcow <b>in-memory logs</b> are collected in Redis lists and trimmed to LOG_LINES (%d) every minute to reduce hammering.\r\n <br>In-memory logs are not meant to be persistent. All applications that log in-memory, also log to the Docker daemon and therefore to the default logging driver.\r\n <br>The in-memory log type should be used for debugging minor issues with containers.</p>\r\n <p><b>External logs</b> are collected via API of the given application.</p>\r\n <p><b>Static logs</b> are mostly activity logs, that are not logged to the Dockerd but still need to be persistent (except for API logs).</p>", "log_info": "<p>mailcow <b>in-memory logs</b> are collected in Valkey lists and trimmed to LOG_LINES (%d) every minute to reduce hammering.\r\n <br>In-memory logs are not meant to be persistent. All applications that log in-memory, also log to the Docker daemon and therefore to the default logging driver.\r\n <br>The in-memory log type should be used for debugging minor issues with containers.</p>\r\n <p><b>External logs</b> are collected via API of the given application.</p>\r\n <p><b>Static logs</b> are mostly activity logs, that are not logged to the Dockerd but still need to be persistent (except for API logs).</p>",
"login_time": "Time", "login_time": "Time",
"logs": "Logs", "logs": "Logs",
"memory": "Memory", "memory": "Memory",
+2 -2
View File
@@ -462,7 +462,7 @@
"private_key_error": "Error en la llave privada: %s", "private_key_error": "Error en la llave privada: %s",
"quota_not_0_not_numeric": "Cuota debe ser numérica y >= 0", "quota_not_0_not_numeric": "Cuota debe ser numérica y >= 0",
"recipient_map_entry_exists": "Una regla de destinatario \"%s\" existe", "recipient_map_entry_exists": "Una regla de destinatario \"%s\" existe",
"redis_error": "Redis error: %s", "valkey_error": "Valkey error: %s",
"relayhost_invalid": "relayhost %s es invalido", "relayhost_invalid": "relayhost %s es invalido",
"release_send_failed": "El mensaje no pudo ser liberado: %s", "release_send_failed": "El mensaje no pudo ser liberado: %s",
"rl_timeframe": "Marco de tiempo del límite de velocidad esta incorrecto", "rl_timeframe": "Marco de tiempo del límite de velocidad esta incorrecto",
@@ -548,7 +548,7 @@
"disk_usage": "Utilización de disco", "disk_usage": "Utilización de disco",
"external_logs": "Logs externos", "external_logs": "Logs externos",
"in_memory_logs": "Logs en memoria", "in_memory_logs": "Logs en memoria",
"log_info": "<p>Los <b>logs en memoria</b> son recopilados en listas de Redis y recortados a LOG_LINES (%d) cada minuto para prevenir sobrecarga en el sistema.\r\n <br>Los logs en memoria no están destinados a ser persistentes. Todas las aplicaciones que logean a la memoria, también logean en el daemon de Docker y, por lo tanto, en el controlador de registro predeterminado.\r\n El log en memoria se debe utilizar para analizar problemas menores con los contenedores.</p>\r\n <p>Los <b>logs externos</b> se recopilan a través de la API de la aplicación dada.</p>\r\n <p>Los <b>logs estáticos</b> son principalmente registros de actividad, que no están registrados en Dockerd pero que aún deben ser persistentes (excepto los registros de API).</p>", "log_info": "<p>Los <b>logs en memoria</b> son recopilados en listas de Valkey y recortados a LOG_LINES (%d) cada minuto para prevenir sobrecarga en el sistema.\r\n <br>Los logs en memoria no están destinados a ser persistentes. Todas las aplicaciones que logean a la memoria, también logean en el daemon de Docker y, por lo tanto, en el controlador de registro predeterminado.\r\n El log en memoria se debe utilizar para analizar problemas menores con los contenedores.</p>\r\n <p>Los <b>logs externos</b> se recopilan a través de la API de la aplicación dada.</p>\r\n <p>Los <b>logs estáticos</b> son principalmente registros de actividad, que no están registrados en Dockerd pero que aún deben ser persistentes (excepto los registros de API).</p>",
"logs": "Logs", "logs": "Logs",
"restart_container": "Reiniciar", "restart_container": "Reiniciar",
"docs": "Docs", "docs": "Docs",
+2 -2
View File
@@ -344,7 +344,7 @@
"private_key_error": "Yksityisen avaimen virhe: %s", "private_key_error": "Yksityisen avaimen virhe: %s",
"quota_not_0_not_numeric": "Kiintiön on oltava numeerinen ja >= 0", "quota_not_0_not_numeric": "Kiintiön on oltava numeerinen ja >= 0",
"recipient_map_entry_exists": "Vastaanottajan kartta merkintä \"%s\" on olemassa", "recipient_map_entry_exists": "Vastaanottajan kartta merkintä \"%s\" on olemassa",
"redis_error": "Redis virhe: %s", "valkey_error": "Valkey virhe: %s",
"relayhost_invalid": "Osoite %s on väärin", "relayhost_invalid": "Osoite %s on väärin",
"release_send_failed": "Viestiä ei voitu vapauttaa: %s", "release_send_failed": "Viestiä ei voitu vapauttaa: %s",
"resource_invalid": "Resurssin nimi %s on virheellinen", "resource_invalid": "Resurssin nimi %s on virheellinen",
@@ -380,7 +380,7 @@
"disk_usage": "Levyn käyttö", "disk_usage": "Levyn käyttö",
"external_logs": "Ulkoiset loki", "external_logs": "Ulkoiset loki",
"in_memory_logs": "Muistissa olevat lokit", "in_memory_logs": "Muistissa olevat lokit",
"log_info": "<p>mailcow <b>muistissa olevat lokit</b> kerätään Redis-luetteloihin ja leikataan LOG_LINES (%d) joka minuutti lyömisen vähentämiseksi.\r\n <br>Muistissa olevien lokien ei ole tarkoitus olla pysyviä. Kaikki sovellukset, jotka kirjautuvat muistiin, kirjautuvat myös Docker-daemoniin ja siten oletusarvoiseen lokitiedostoon.\r\n <br>Muistin lokityyppiä olisi käytettävä virheiden virheenkorjaukseen säilöissä.</p>\r\n <p><b>Ulkoiset lokit</b> kerätään annetun sovelluksen API: n kautta.</p>\r\n <p><b>Staattiset lokit</b> ovat useimmiten toimintalokkeja, joita ei kirjata Dockerdiin, mutta joiden on silti oltava pysyviä (paitsi API-lokit).</p>", "log_info": "<p>mailcow <b>muistissa olevat lokit</b> kerätään Valkey-luetteloihin ja leikataan LOG_LINES (%d) joka minuutti lyömisen vähentämiseksi.\r\n <br>Muistissa olevien lokien ei ole tarkoitus olla pysyviä. Kaikki sovellukset, jotka kirjautuvat muistiin, kirjautuvat myös Docker-daemoniin ja siten oletusarvoiseen lokitiedostoon.\r\n <br>Muistin lokityyppiä olisi käytettävä virheiden virheenkorjaukseen säilöissä.</p>\r\n <p><b>Ulkoiset lokit</b> kerätään annetun sovelluksen API: n kautta.</p>\r\n <p><b>Staattiset lokit</b> ovat useimmiten toimintalokkeja, joita ei kirjata Dockerdiin, mutta joiden on silti oltava pysyviä (paitsi API-lokit).</p>",
"logs": "Logit tausta palveluista", "logs": "Logit tausta palveluista",
"restart_container": "Uudelleen käynnistä", "restart_container": "Uudelleen käynnistä",
"docs": "Docs", "docs": "Docs",
+2 -2
View File
@@ -493,7 +493,7 @@
"pushover_token": "Le jeton Pushover a un mauvais format", "pushover_token": "Le jeton Pushover a un mauvais format",
"quota_not_0_not_numeric": "Le quota doit être numerique et >= 0", "quota_not_0_not_numeric": "Le quota doit être numerique et >= 0",
"recipient_map_entry_exists": "Une entrée dans la carte du destinataire « %s » existe", "recipient_map_entry_exists": "Une entrée dans la carte du destinataire « %s » existe",
"redis_error": "Erreur Redis : %s", "valkey_error": "Erreur Valkey : %s",
"relayhost_invalid": "La saisie de la carte %s est invalide", "relayhost_invalid": "La saisie de la carte %s est invalide",
"release_send_failed": "Le message na pas pu être diffusé : %s", "release_send_failed": "Le message na pas pu être diffusé : %s",
"reset_f2b_regex": "Le filtre regex n'a pas pu être réinitialisé à temps, veuillez réessayer ou attendre quelques secondes de plus et recharger le site web.", "reset_f2b_regex": "Le filtre regex n'a pas pu être réinitialisé à temps, veuillez réessayer ou attendre quelques secondes de plus et recharger le site web.",
@@ -557,7 +557,7 @@
"external_logs": "Logs externes", "external_logs": "Logs externes",
"history_all_servers": "Historique (tous les serveurs)", "history_all_servers": "Historique (tous les serveurs)",
"in_memory_logs": "Logs En-mémoire", "in_memory_logs": "Logs En-mémoire",
"log_info": "<p>Les logs <b>En-mémoire</b> Mailcow sont collectés dans des listes Redis et découpées en LOG_LINES (%d) chaque minute pour réduire la charge.\n <br>Les logs En-mémoire ne sont pas destinés à être persistants. Toutes les applications qui se connectent en mémoire, se connectent également au démon Docker, et donc au pilote de journalisation par défaut.\n <br>Le type de journal en mémoire doit être utilisé pour déboguer les problèmes mineurs avec les conteneurs.</p>\n <p><b>Les logs externes</b> sont collectés via l'API de l'application concernée.</p>\n <p>Les journaux <b>statiques</b> sont principalement des journaux dactivité, qui ne sont pas enregistrés dans Dockerd, mais qui doivent toujours être persistants (sauf pour les logs API).</p>", "log_info": "<p>Les logs <b>En-mémoire</b> Mailcow sont collectés dans des listes Valkey et découpées en LOG_LINES (%d) chaque minute pour réduire la charge.\n <br>Les logs En-mémoire ne sont pas destinés à être persistants. Toutes les applications qui se connectent en mémoire, se connectent également au démon Docker, et donc au pilote de journalisation par défaut.\n <br>Le type de journal en mémoire doit être utilisé pour déboguer les problèmes mineurs avec les conteneurs.</p>\n <p><b>Les logs externes</b> sont collectés via l'API de l'application concernée.</p>\n <p>Les journaux <b>statiques</b> sont principalement des journaux dactivité, qui ne sont pas enregistrés dans Dockerd, mais qui doivent toujours être persistants (sauf pour les logs API).</p>",
"logs": "Logs", "logs": "Logs",
"restart_container": "Redémarrer", "restart_container": "Redémarrer",
"docs": "Docs", "docs": "Docs",
+2 -2
View File
@@ -463,7 +463,7 @@
"pushover_token": "Pushover token has a wrong format", "pushover_token": "Pushover token has a wrong format",
"quota_not_0_not_numeric": "Lo spazio deve essere numerico e >= 0", "quota_not_0_not_numeric": "Lo spazio deve essere numerico e >= 0",
"recipient_map_entry_exists": "A Recipient map entry \"%s\" exists", "recipient_map_entry_exists": "A Recipient map entry \"%s\" exists",
"redis_error": "Redis error: %s", "valkey_error": "Valkey error: %s",
"relayhost_invalid": "Map entry %s is invalid", "relayhost_invalid": "Map entry %s is invalid",
"release_send_failed": "Message could not be released: %s", "release_send_failed": "Message could not be released: %s",
"reset_f2b_regex": "Regex filter could not be reset in time, please try again or wait a few more seconds and reload the website.", "reset_f2b_regex": "Regex filter could not be reset in time, please try again or wait a few more seconds and reload the website.",
@@ -522,7 +522,7 @@
"history_all_servers": "Cronologia (tutti i server)", "history_all_servers": "Cronologia (tutti i server)",
"in_memory_logs": "In-memory logs", "in_memory_logs": "In-memory logs",
"last_modified": "Ultima modifica", "last_modified": "Ultima modifica",
"log_info": "<p>mailcow <b>in-memory logs</b> are collected in Redis lists and trimmed to LOG_LINES (%d) every minute to reduce hammering.\r\n <br>In-memory logs are not meant to be persistent. All applications that log in-memory, also log to the Docker daemon and therefore to the default logging driver.\r\n <br>The in-memory log type should be used for debugging minor issues with containers.</p>\r\n <p><b>External logs</b> are collected via API of the given application.</p>\r\n <p><b>Static logs</b> are mostly activity logs, that are not logged to the Dockerd but still need to be persistent (except for API logs).</p>", "log_info": "<p>mailcow <b>in-memory logs</b> are collected in Valkey lists and trimmed to LOG_LINES (%d) every minute to reduce hammering.\r\n <br>In-memory logs are not meant to be persistent. All applications that log in-memory, also log to the Docker daemon and therefore to the default logging driver.\r\n <br>The in-memory log type should be used for debugging minor issues with containers.</p>\r\n <p><b>External logs</b> are collected via API of the given application.</p>\r\n <p><b>Static logs</b> are mostly activity logs, that are not logged to the Dockerd but still need to be persistent (except for API logs).</p>",
"login_time": "Orario", "login_time": "Orario",
"logs": "Logs", "logs": "Logs",
"online_users": "Utenti online", "online_users": "Utenti online",
+2 -2
View File
@@ -456,7 +456,7 @@
"quota_not_0_not_numeric": "クォータは数値で >= 0 である必要があります", "quota_not_0_not_numeric": "クォータは数値で >= 0 である必要があります",
"recipient_map_entry_exists": "受信者マップエントリ \"%s\" が存在します", "recipient_map_entry_exists": "受信者マップエントリ \"%s\" が存在します",
"recovery_email_failed": "リカバリーメールを送信できませんでした。管理者にお問い合わせください。", "recovery_email_failed": "リカバリーメールを送信できませんでした。管理者にお問い合わせください。",
"redis_error": "Redisエラー: %s", "valkey_error": "Valkeyエラー: %s",
"relayhost_invalid": "マップエントリ %s は無効です", "relayhost_invalid": "マップエントリ %s は無効です",
"release_send_failed": "メッセージをリリースできませんでした: %s", "release_send_failed": "メッセージをリリースできませんでした: %s",
"reset_f2b_regex": "正規表現フィルターをタイムリーにリセットできませんでした。再試行するか、数秒待ってからウェブサイトをリロードしてください。", "reset_f2b_regex": "正規表現フィルターをタイムリーにリセットできませんでした。再試行するか、数秒待ってからウェブサイトをリロードしてください。",
@@ -540,7 +540,7 @@
"history_all_servers": "履歴(すべてのサーバー)", "history_all_servers": "履歴(すべてのサーバー)",
"in_memory_logs": "インメモリーログ", "in_memory_logs": "インメモリーログ",
"last_modified": "最終更新日時", "last_modified": "最終更新日時",
"log_info": "<p>mailcowの<b>インメモリーログ</b>はRedisリストに収集され、ハンマリングを軽減するために1分ごとにLOG_LINES (%d)にトリムされます。\r\n <br>インメモリーログは永続化を目的としたものではありません。インメモリーログを記録するすべてのアプリケーションは、Dockerデーモンとデフォルトのログドライバーにもログを記録します。\r\n <br>インメモリーログタイプは、コンテナの軽微な問題をデバッグするために使用してください。</p>\r\n <p><b>外部ログ</b>は指定されたアプリケーションのAPIを介して収集されます。</p>\r\n <p><b>静的ログ</b>は主にアクティビティログであり、Dockerdには記録されませんが(APIログを除く)、永続化が必要です。</p>", "log_info": "<p>mailcowの<b>インメモリーログ</b>はValkeyリストに収集され、ハンマリングを軽減するために1分ごとにLOG_LINES (%d)にトリムされます。\r\n <br>インメモリーログは永続化を目的としたものではありません。インメモリーログを記録するすべてのアプリケーションは、Dockerデーモンとデフォルトのログドライバーにもログを記録します。\r\n <br>インメモリーログタイプは、コンテナの軽微な問題をデバッグするために使用してください。</p>\r\n <p><b>外部ログ</b>は指定されたアプリケーションのAPIを介して収集されます。</p>\r\n <p><b>静的ログ</b>は主にアクティビティログであり、Dockerdには記録されませんが(APIログを除く)、永続化が必要です。</p>",
"login_time": "ログイン時間", "login_time": "ログイン時間",
"logs": "ログ", "logs": "ログ",
"memory": "メモリ", "memory": "メモリ",
+2 -2
View File
@@ -437,7 +437,7 @@
"pushover_token": "Pushover 토큰 포맷이 잘못되었습니다.", "pushover_token": "Pushover 토큰 포맷이 잘못되었습니다.",
"quota_not_0_not_numeric": "할당량은 숫자이여야 하고 0보다 크거나 같아야 합니다.", "quota_not_0_not_numeric": "할당량은 숫자이여야 하고 0보다 크거나 같아야 합니다.",
"recipient_map_entry_exists": "수신자 맵 항목 \"%s\"이 존재합니다", "recipient_map_entry_exists": "수신자 맵 항목 \"%s\"이 존재합니다",
"redis_error": "Redis 에러: %s", "valkey_error": "Valkey 에러: %s",
"relayhost_invalid": "유효하지 않은 맵 기록 %s", "relayhost_invalid": "유효하지 않은 맵 기록 %s",
"release_send_failed": "메시지를 릴리즈할 수 없습니다.: %s", "release_send_failed": "메시지를 릴리즈할 수 없습니다.: %s",
"resource_invalid": "리소스 이름 %s이 유효하지 않습니다.", "resource_invalid": "리소스 이름 %s이 유효하지 않습니다.",
@@ -499,7 +499,7 @@
"external_logs": "External logs", "external_logs": "External logs",
"history_all_servers": "History (all servers)", "history_all_servers": "History (all servers)",
"in_memory_logs": "In-memory logs", "in_memory_logs": "In-memory logs",
"log_info": "<p>mailcow <b>in-memory logs</b> are collected in Redis lists and trimmed to LOG_LINES (%d) every minute to reduce hammering.\r\n <br>In-memory logs are not meant to be persistent. All applications that log in-memory, also log to the Docker daemon and therefore to the default logging driver.\r\n <br>The in-memory log type should be used for debugging minor issues with containers.</p>\r\n <p><b>External logs</b> are collected via API of the given application.</p>\r\n <p><b>Static logs</b> are mostly activity logs, that are not logged to the Dockerd but still need to be persistent (except for API logs).</p>", "log_info": "<p>mailcow <b>in-memory logs</b> are collected in Valkey lists and trimmed to LOG_LINES (%d) every minute to reduce hammering.\r\n <br>In-memory logs are not meant to be persistent. All applications that log in-memory, also log to the Docker daemon and therefore to the default logging driver.\r\n <br>The in-memory log type should be used for debugging minor issues with containers.</p>\r\n <p><b>External logs</b> are collected via API of the given application.</p>\r\n <p><b>Static logs</b> are mostly activity logs, that are not logged to the Dockerd but still need to be persistent (except for API logs).</p>",
"logs": "Logs", "logs": "Logs",
"restart_container": "Restart", "restart_container": "Restart",
"docs": "Docs", "docs": "Docs",
+69 -43
View File
@@ -39,16 +39,16 @@
"alias_domain_info": "<small>Tikai derīgi domēna vārdi (komatu atdalīti).</small>", "alias_domain_info": "<small>Tikai derīgi domēna vārdi (komatu atdalīti).</small>",
"automap": "Mēģiniet automatizēt mapes (\"Nosūtītie vienumi\", \"Nosūtītie\" => \"Nosūtītie\" etc.)", "automap": "Mēģiniet automatizēt mapes (\"Nosūtītie vienumi\", \"Nosūtītie\" => \"Nosūtītie\" etc.)",
"backup_mx_options": "Dublējuma MX iespējas", "backup_mx_options": "Dublējuma MX iespējas",
"delete1": "Dzēst no avota, kad tas ir pabeigts", "delete1": "Izdzēst no avota pēc pabeigšanas",
"delete2": "Dzēsiet ziņojumus galamērķī, kas nav avotā", "delete2": "Dzēsiet ziņojumus galamērķī, kas nav avotā",
"delete2duplicates": "Dzēst dublikātus galamērķī", "delete2duplicates": "Izdzēst atkārtojošos vienumus galamērķī",
"description": "Apraksts", "description": "Apraksts",
"domain": "Domēns", "domain": "Domēns",
"domain_quota_m": "Kopējā domēna kvota (MiB)", "domain_quota_m": "Kopējā domēna kvota (MiB)",
"enc_method": "Šifrēšanas metode", "enc_method": "Šifrēšanas metode",
"exclude": "Izslēgt objektus (regex)", "exclude": "Izslēgt objektus (regex)",
"full_name": "Pilns vārds", "full_name": "Pilns vārds",
"goto_null": "Klusām dzēst pastu", "goto_null": "Klusām atmest pastu",
"hostname": "Saimniekdators", "hostname": "Saimniekdators",
"kind": "Veids", "kind": "Veids",
"mailbox_quota_m": "Maks. kvota pastkastei (MiB)", "mailbox_quota_m": "Maks. kvota pastkastei (MiB)",
@@ -77,12 +77,13 @@
"target_domain": "Mērķa domēns", "target_domain": "Mērķa domēns",
"username": "Lietotājvārds", "username": "Lietotājvārds",
"validate": "Apstiprināt", "validate": "Apstiprināt",
"validation_success": "Apstiprināts veiksmīgi", "validation_success": "Sekmīgi apstiprināts",
"bcc_dest_format": "BCC galamērķim ir jābūt vienai derīgai e-pasta adresei.<br>Ja ir nepieciešams nosūtīt kopiju vairākām adresēm, jāizveido aizstājvārds un jāizmanto tas šeit.", "bcc_dest_format": "BCC galamērķim ir jābūt vienai derīgai e-pasta adresei.<br>Ja ir nepieciešams nosūtīt kopiju vairākām adresēm, jāizveido aizstājvārds un jāizmanto tas šeit.",
"domain_matches_hostname": "Domēns %s atbilst saimniekdatora nosaukumam", "domain_matches_hostname": "Domēns %s atbilst saimniekdatora nosaukumam",
"disable_login": "Neļaut pieteikšanos (ienākošais pasts joprojām tiks pieņemts)", "disable_login": "Neļaut pieteikšanos (ienākošais pasts joprojām tiks pieņemts)",
"app_password": "Pievienot lietotnes paroli", "app_password": "Pievienot lietotnes paroli",
"app_passwd_protocols": "Atļautie lietotnes paroles protokoli" "app_passwd_protocols": "Atļautie lietotnes paroles protokoli",
"goto_spam": "Apgūt kā <span class=\"text-danger\"><b>mēstuli</b></span>"
}, },
"admin": { "admin": {
"access": "Pieeja", "access": "Pieeja",
@@ -115,14 +116,14 @@
"domain": "Domēns", "domain": "Domēns",
"domain_admins": "Domēna administratori", "domain_admins": "Domēna administratori",
"edit": "Labot", "edit": "Labot",
"empty": "Nav rezultātu", "empty": "Nav iznākuma",
"f2b_ban_time": "Aizlieguma laiks (s)", "f2b_ban_time": "Aizlieguma laiks (s)",
"f2b_max_attempts": "Maks. piegājieni", "f2b_max_attempts": "Maks. piegājieni",
"f2b_netban_ipv4": "IPv4 apakštīkla izmērs, lai piemērotu aizliegumu uz (8-32)", "f2b_netban_ipv4": "IPv4 apakštīkla izmērs, lai piemērotu aizliegumu uz (8-32)",
"f2b_netban_ipv6": "IPv6 apakštīkla izmērs, lai piemērotu aizliegumu uz (8-128)", "f2b_netban_ipv6": "IPv6 apakštīkla izmērs, lai piemērotu aizliegumu uz (8-128)",
"f2b_parameters": "Fail2ban parametri", "f2b_parameters": "Fail2ban parametri",
"f2b_retry_window": "Atkārtošanas logs (s) priekš maks. piegājiena", "f2b_retry_window": "Atkārtošanas logs (s) priekš maks. piegājiena",
"f2b_whitelist": "Baltā saraksta tīkls/hosts", "f2b_whitelist": "Atļautie tīkli/resursdatori",
"filter_table": "Filtru tabula", "filter_table": "Filtru tabula",
"forwarding_hosts": "Hostu pārsūtīšana", "forwarding_hosts": "Hostu pārsūtīšana",
"forwarding_hosts_add_hint": "Var norādīt vai nu IPv4/IPv6 adreses, tīklu ar CIDR apzīmējumu, saimniekdatoru nosaukumus (kas tiks atrisināti IP adresēs) vai arī domēna vārdus (kas tiks atrisināti IP adresēs, vaicājot SPF ierakstus, vai, ja tādu nav, MX ierakstus).", "forwarding_hosts_add_hint": "Var norādīt vai nu IPv4/IPv6 adreses, tīklu ar CIDR apzīmējumu, saimniekdatoru nosaukumus (kas tiks atrisināti IP adresēs) vai arī domēna vārdus (kas tiks atrisināti IP adresēs, vaicājot SPF ierakstus, vai, ja tādu nav, MX ierakstus).",
@@ -181,7 +182,10 @@
"rspamd_com_settings": "Iestatījuma nosaukums tiks izveidots automātiski. Lūgums zemāk skatīt priekšiestatījumu piemērus. Vairāk informācijas ir <a href=\"https://rspamd.com/doc/configuration/settings.html#settings-structure\" target=\"_blank\">Rspamd dokumentācijā</a>", "rspamd_com_settings": "Iestatījuma nosaukums tiks izveidots automātiski. Lūgums zemāk skatīt priekšiestatījumu piemērus. Vairāk informācijas ir <a href=\"https://rspamd.com/doc/configuration/settings.html#settings-structure\" target=\"_blank\">Rspamd dokumentācijā</a>",
"reset_password_vars": "<code>{{link}}</code> Izveidotā paroles atiestatīšanas saite<br><code>{{username}}</code> Lietotāja, kurš pieprasīja paroles atiestatīšanu, pastkastes nosaukums<br><code>{{username2}}</code> Atkopšanas pastkastes nosaukums<br><code>{{date}}</code> Paroles atiestatīšanas pieprasījuma veikšanas datums<br><code>{{token_lifetime}}</code> Pilnvaras derīgums minūtēs<br><code>{{hostname}}</code> mailcow saimniekdatora nosaukums", "reset_password_vars": "<code>{{link}}</code> Izveidotā paroles atiestatīšanas saite<br><code>{{username}}</code> Lietotāja, kurš pieprasīja paroles atiestatīšanu, pastkastes nosaukums<br><code>{{username2}}</code> Atkopšanas pastkastes nosaukums<br><code>{{date}}</code> Paroles atiestatīšanas pieprasījuma veikšanas datums<br><code>{{token_lifetime}}</code> Pilnvaras derīgums minūtēs<br><code>{{hostname}}</code> mailcow saimniekdatora nosaukums",
"ui_header_announcement_help": "Paziņojums ir redzams visiem lietotājiem, kuri ir pieteikušies, un pieteikšanās ekrānā saskarnē.", "ui_header_announcement_help": "Paziņojums ir redzams visiem lietotājiem, kuri ir pieteikušies, un pieteikšanās ekrānā saskarnē.",
"login_time": "Pieteikšanās laiks" "login_time": "Pieteikšanās laiks",
"iam_version": "Versija",
"quarantine_max_age": "Lielākais pieļaujamais vecums dienās<br><small>Vērtībai jābūt vienādai ar vai lielākai par 1 dienu.</small>",
"quarantine_max_score": "Atmest paziņojumu, ja e-pasta ziņojuma mēstuļu novērtējums ir augstāks par šo vērtību:<br><small>Noklusējums ir 9999.0</small>"
}, },
"danger": { "danger": {
"access_denied": "Piekļuve liegta, vai nepareizi dati", "access_denied": "Piekļuve liegta, vai nepareizi dati",
@@ -201,8 +205,8 @@
"goto_empty": "Aizstājādresei jāsatur vismaz viena derīga mērķa adrese", "goto_empty": "Aizstājādresei jāsatur vismaz viena derīga mērķa adrese",
"goto_invalid": "Goto adrese nepareiza", "goto_invalid": "Goto adrese nepareiza",
"imagick_exception": "Kļūda: Imagick izņēmums, lasot attēlu", "imagick_exception": "Kļūda: Imagick izņēmums, lasot attēlu",
"img_invalid": "Nevar apstiprināt attēla failu", "img_invalid": "Nevar apstiprināt attēla datni",
"img_tmp_missing": "Nevar apstiprināt attēla failu: pagaidu failu nav atrasts", "img_tmp_missing": "Nevar apstiprināt attēla datni: pagaidu datne nav atrasta",
"invalid_mime_type": "Nederīgs mime tips", "invalid_mime_type": "Nederīgs mime tips",
"is_alias": "%s jau ir zināma kā aizstājadrese", "is_alias": "%s jau ir zināma kā aizstājadrese",
"is_alias_or_mailbox": "%s jau ir zināms kā aizstājvārds, pastkaste vai aizstājadrese, kas ir izvērsta no aizstājdomēna.", "is_alias_or_mailbox": "%s jau ir zināms kā aizstājvārds, pastkaste vai aizstājadrese, kas ir izvērsta no aizstājdomēna.",
@@ -234,7 +238,10 @@
"username_invalid": "Lietotājvārds nevar tikt izmantots", "username_invalid": "Lietotājvārds nevar tikt izmantots",
"validity_missing": "Lūdzu piešķiriet derīguma termiņu", "validity_missing": "Lūdzu piešķiriet derīguma termiņu",
"domain_cannot_match_hostname": "Domēns nevar atbilst saimniekdatora nosaukumam", "domain_cannot_match_hostname": "Domēns nevar atbilst saimniekdatora nosaukumam",
"app_passwd_id_invalid": "Lietotnes paroles Id %s ir nederīgs" "app_passwd_id_invalid": "Lietotnes paroles Id %s ir nederīgs",
"img_dimensions_exceeded": "Attēls pārsniedz lielāko pieļaujamo attēla lielumu",
"img_size_exceeded": "Attēls pārsniedz lielāko pieļaujamo datnes lielumu",
"version_invalid": "Versija %s ir nederīga"
}, },
"diagnostics": { "diagnostics": {
"cname_from_a": "Vērtība, kas iegūta no A/AAAA ieraksta. Tas tiek atbalstīts tik ilgi, kamēr ieraksts norāda uz pareizo resursu.", "cname_from_a": "Vērtība, kas iegūta no A/AAAA ieraksta. Tas tiek atbalstīts tik ilgi, kamēr ieraksts norāda uz pareizo resursu.",
@@ -251,9 +258,9 @@
"alias": "Labot aizstājvārdu", "alias": "Labot aizstājvārdu",
"automap": "Mēģiniet automatizēt mapes (\"Nosūtītie vienumi\", \"Nosūtītie\" => \"Nosūtītie\" utt.)", "automap": "Mēģiniet automatizēt mapes (\"Nosūtītie vienumi\", \"Nosūtītie\" => \"Nosūtītie\" utt.)",
"backup_mx_options": "Dublēt MX iespējas", "backup_mx_options": "Dublēt MX iespējas",
"delete1": "Dzēst no avota, kad pabeigts", "delete1": "Izdzēst no avota pēc pabeigšanas",
"delete2": "Dzēsiet ziņojumus galamērķī, kas nav avotā", "delete2": "Dzēsiet ziņojumus galamērķī, kas nav avotā",
"delete2duplicates": "Dzēst dublikātus galamērķī", "delete2duplicates": "Izdzēst atkārtojošos vienumus galamērķī",
"description": "Apraksts", "description": "Apraksts",
"domain": "Labot domēnu", "domain": "Labot domēnu",
"domain_admin": "Labot domēna administratoru", "domain_admin": "Labot domēna administratoru",
@@ -273,7 +280,7 @@
"max_aliases": "Lielākais aizstājvārdu skaits", "max_aliases": "Lielākais aizstājvārdu skaits",
"max_mailboxes": "Maks. iespējamās pastkastes", "max_mailboxes": "Maks. iespējamās pastkastes",
"max_quota": "Maks. kvota uz pastkasti (MiB)", "max_quota": "Maks. kvota uz pastkasti (MiB)",
"maxage": "Lielākais ziņojumu, kuri tiks vaicāti attālajā serverī, vecums dienās<br><small>(0 = neņemt vērā vecumu)</small>", "maxage": "Lielākais pieļaujamais ziņojumu, kuri tiks vaicāti attālajā serverī, vecums dienās<br><small>(0 = neņemt vērā vecumu)</small>",
"maxbytespersecond": "Maks. baiti sekundē (0 ir vienāds ar neierobežotu skaitu)", "maxbytespersecond": "Maks. baiti sekundē (0 ir vienāds ar neierobežotu skaitu)",
"mins_interval": "Intervāls (min)", "mins_interval": "Intervāls (min)",
"multiple_bookings": "Vairāki rezervējumi", "multiple_bookings": "Vairāki rezervējumi",
@@ -292,8 +299,8 @@
"sieve_type": "Filtra tips", "sieve_type": "Filtra tips",
"skipcrossduplicates": "Izlaist dublētus ziņojumus pa mapēm (pirmais nāk, pirmais kalpo)", "skipcrossduplicates": "Izlaist dublētus ziņojumus pa mapēm (pirmais nāk, pirmais kalpo)",
"spam_alias": "Izveidot vai mainīt laika ierobežotas aizstājadreses", "spam_alias": "Izveidot vai mainīt laika ierobežotas aizstājadreses",
"spam_policy": "Pievienot vai noņemt vienumus baltajā-/melnajā sarakstā", "spam_policy": "Pievienot vai noņemt vienumus atļautajā/liegumu sarakstā",
"spam_score": "Iestatīt pielāgotu surogātpasta vērtējumu", "spam_score": "Iestatīt pielāgotu mēstules vērtējumu",
"subfolder2": "Sinhronizēt galamērķa apakšmapē<br><small>(tukšs = neizmantot apakšmapi)</small>", "subfolder2": "Sinhronizēt galamērķa apakšmapē<br><small>(tukšs = neizmantot apakšmapi)</small>",
"syncjob": "Labot sinhronizācijas darbu", "syncjob": "Labot sinhronizācijas darbu",
"target_address": "Mērķa adrese/s <small>(atdalītas ar komatu)</small>", "target_address": "Mērķa adrese/s <small>(atdalītas ar komatu)</small>",
@@ -316,17 +323,21 @@
"disable_login": "Neļaut pieteikšanos (ienākošais pasts joprojām tiks pieņemts)", "disable_login": "Neļaut pieteikšanos (ienākošais pasts joprojām tiks pieņemts)",
"app_passwd_protocols": "Atļautie lietotnes paroles protokoli", "app_passwd_protocols": "Atļautie lietotnes paroles protokoli",
"allowed_protocols": "Atļautie protokoli tiešai lietotāja piekļuvei (neietekmē lietotnes paroles protokolus)", "allowed_protocols": "Atļautie protokoli tiešai lietotāja piekļuvei (neietekmē lietotnes paroles protokolus)",
"app_passwd": "Lietotnes parole" "app_passwd": "Lietotnes parole",
"mta_sts_version": "Versija",
"mta_sts_version_info": "Norāda MTA-STS standarta versiju pašreiz ir derīga tikai <code>STSv1</code>.",
"sender_acl_disabled": "<span class=\"badge fs-6 bg-danger\">Sūtītāja pārbaude ir atspējota</span>"
}, },
"footer": { "footer": {
"cancel": "Atcelt", "cancel": "Atcelt",
"confirm_delete": "Apstiprināt dzēšanu", "confirm_delete": "Apstiprināt izdzēšanu",
"delete_now": "Dzēst tagad", "delete_now": "Izdzēst tagad",
"delete_these_items": "Lūgums apstiprināt izmaiņas šim objekta Id", "delete_these_items": "Lūgums apstiprināt izmaiņas šim objekta Id",
"loading": "Lūgums uzgaidīt...", "loading": "Lūgums uzgaidīt...",
"restart_container": "Restartēt konteineri", "restart_container": "Restartēt konteineri",
"restart_container_info": "<b>Svarīgi:</b> nesteidzīga pārsāknēšana var aizņemt ilgāku laiku. Lūgums uzgaidīt, līdz tā tiek pabeigta.", "restart_container_info": "<b>Svarīgi:</b> nesteidzīga pārsāknēšana var aizņemt ilgāku laiku. Lūgums uzgaidīt, līdz tā tiek pabeigta.",
"restart_now": "Pārsāknēt tagad" "restart_now": "Pārsāknēt tagad",
"hibp_nok": "Sakrīt. Šī, iespējams, ir bīstama parole."
}, },
"header": { "header": {
"administration": "Konfigurācija un informācija", "administration": "Konfigurācija un informācija",
@@ -389,7 +400,7 @@
"domain_quota_total": "Kopējais domēna ierobežojums", "domain_quota_total": "Kopējais domēna ierobežojums",
"domains": "Domēns", "domains": "Domēns",
"edit": "Labot", "edit": "Labot",
"empty": "Nav rezultātu", "empty": "Nav iznākuma",
"excludes": "Izslēdzot", "excludes": "Izslēdzot",
"filter_table": "Filtra tabula", "filter_table": "Filtra tabula",
"filters": "Filtri", "filters": "Filtri",
@@ -448,13 +459,15 @@
"add_alias_expand": "Izvērst aizstājvārdu pār aizstājdomēniem", "add_alias_expand": "Izvērst aizstājvārdu pār aizstājdomēniem",
"alias_domain_alias_hint": "Aizstājvārdi <b>netiek</b> automātiski piemēroti domēnu aizstājvārdiem. Aizstājadrese <code>my-alias@domain</code> <b>nenosedz</b> adresi <code>my-alias@alias-domain</code> (kur \"alias-domain\" ir iedomāts \"domain\" aizstājdomēns).<br>Lūgums izmantot sieta atlasi, lai pārvirzītu pastu uz ārēju pastkasti (skatīt cilti \"Atlasīšana\" vai izmantot SOGo -> Pārsūtītājs). \"Izvērst aizstājvārdu pār aizstājdomēniem\" ir izmantojams, lai automātiski pievienotu trūkstošos aiztājvārdus.", "alias_domain_alias_hint": "Aizstājvārdi <b>netiek</b> automātiski piemēroti domēnu aizstājvārdiem. Aizstājadrese <code>my-alias@domain</code> <b>nenosedz</b> adresi <code>my-alias@alias-domain</code> (kur \"alias-domain\" ir iedomāts \"domain\" aizstājdomēns).<br>Lūgums izmantot sieta atlasi, lai pārvirzītu pastu uz ārēju pastkasti (skatīt cilti \"Atlasīšana\" vai izmantot SOGo -> Pārsūtītājs). \"Izvērst aizstājvārdu pār aizstājdomēniem\" ir izmantojams, lai automātiski pievienotu trūkstošos aiztājvārdus.",
"alias_domain_backupmx": "Aizstājdomēns ir neaktīvs retranslācijas domēnam", "alias_domain_backupmx": "Aizstājdomēns ir neaktīvs retranslācijas domēnam",
"disable_login": "Neļaut pieteikšanos (ienākošais pasts joprojām tiks pieņemts)" "disable_login": "Neļaut pieteikšanos (ienākošais pasts joprojām tiks pieņemts)",
"sieve_preset_1": "Atmest e-pasta vēstules ar iespējami bīstamiem datņu veidiem",
"syncjob_last_run_result": "Pēdējās izpildes iznākums"
}, },
"quarantine": { "quarantine": {
"action": "Darbības", "action": "Darbības",
"atts": "Pielikumi", "atts": "Pielikumi",
"check_hash": "Meklēt faila hašu @ VT", "check_hash": "Meklēt datnes jaucējvērtību @ VT",
"empty": "Nav rezultātu", "empty": "Nav iznākuma",
"qid": "Rspamd QID", "qid": "Rspamd QID",
"qitem": "Karantīnas vienumi", "qitem": "Karantīnas vienumi",
"quarantine": "Karantīna", "quarantine": "Karantīna",
@@ -463,7 +476,7 @@
"received": "Saņemtie", "received": "Saņemtie",
"recipients": "Adresāts", "recipients": "Adresāts",
"release": "Atbrīvot", "release": "Atbrīvot",
"release_body": "Šim ziņojumam mēs esam pievienojuši jūsu ziņojumu kā eml failu.", "release_body": "Mēs pievienojām Tavu ziņojumu kā .eml datni šim ziņojumam.",
"release_subject": "Potenciāli kaitīgs karantīnas vienums %s", "release_subject": "Potenciāli kaitīgs karantīnas vienums %s",
"remove": "Noņemt", "remove": "Noņemt",
"sender": "Sūtītājs (SMTP)", "sender": "Sūtītājs (SMTP)",
@@ -473,8 +486,14 @@
"text_plain_content": "Saturs (teksts/vienkāršs)", "text_plain_content": "Saturs (teksts/vienkāršs)",
"toggle_all": "Pārslēgt visu", "toggle_all": "Pārslēgt visu",
"disabled_by_config": "Pašreizējā sistēmas konfigurācija atspējo karantīnu. Lūgums iestatīt \"saglabāšanu katrai pastkastītei\" un \"lielākais pieļaujamais lielums\" karantīnas vienumiem.", "disabled_by_config": "Pašreizējā sistēmas konfigurācija atspējo karantīnu. Lūgums iestatīt \"saglabāšanu katrai pastkastītei\" un \"lielākais pieļaujamais lielums\" karantīnas vienumiem.",
"qhandler_success": "Pieprasījums veiksmīgi nosūtīts sistēmai. Tagad var aizvērt logu.", "qhandler_success": "Pieprasījums sekmīgi nosūtīts sistēmai. Logu tagad var aizvērt.",
"qinfo": "Karantīnas sistēma datubāzē saglabās noraidīto pastu (sūtītājam <em>netiks</em> radīts iespaids par piegādātu pastu), kā arī pastu, kas tiek piegādāts kā kopija pastkastes mēstuļu mapē.\n <br>\"Apgūt kā surogātpastu un izdzēst\" apgūs ziņojumu kā surogātpastu ar Bajesa teorēmu un aprēķinās arī nestriktas jaucējvērtības, lai nākotnē noraidītu līdzīgus ziņojumus.\n <br>Lūgums apzināties, ka vairāku ziņojumu apgūšana var būt laikietilpīga atkarībā no sistēmas.<br>Melnā saraksta vienumi karantīnā netiek iekļauti." "qinfo": "Karantīnas sistēma datubāzē saglabās noraidīto pastu (sūtītājam <em>netiks</em> radīts iespaids par piegādātu pastu), kā arī pastu, kas tiek piegādāts kā kopija pastkastes mēstuļu mapē.\n <br>\"Apgūt kā surogātpastu un izdzēst\" apgūs ziņojumu kā surogātpastu ar Bajesa teorēmu un aprēķinās arī nestriktas jaucējvērtības, lai nākotnē noraidītu līdzīgus ziņojumus.\n <br>Lūgums apzināties, ka vairāku ziņojumu apgūšana var būt laikietilpīga atkarībā no sistēmas.<br>Lieguma saraksta vienumi karantīnā netiek iekļauti.",
"danger": "Bīstamība",
"notified": "Paziņots",
"refresh": "Atsvaidzināt",
"rspamd_result": "Rspamd iznākums",
"settings_info": "Lielākais pieļaujamais karantējamo vienumu daudzums: %s<br>Lielākais pieļaujamais e-pasta lielums: %s MiB",
"spam_score": "Novērtējums"
}, },
"queue": { "queue": {
"queue_manager": "Rindas pārvaldnieks", "queue_manager": "Rindas pārvaldnieks",
@@ -505,8 +524,8 @@
"f2b_modified": "Fail2ban parametru izmaiņas tika saglabātas", "f2b_modified": "Fail2ban parametru izmaiņas tika saglabātas",
"forwarding_host_added": "Pāradresācijas hosts %s pievienotsd", "forwarding_host_added": "Pāradresācijas hosts %s pievienotsd",
"forwarding_host_removed": "Pāradresācijas hosts %s noņemts", "forwarding_host_removed": "Pāradresācijas hosts %s noņemts",
"item_deleted": "Vērtība %s veiksmīgi dzēsta", "item_deleted": "Vienums %s izdzēsts sekmīgi",
"items_deleted": "Vērtība %s veiksmīgi dzēsta", "items_deleted": "Vienums %s izdzēsts sekmīgi",
"items_released": "Atlasītie vienumi tika izlaisti", "items_released": "Atlasītie vienumi tika izlaisti",
"mailbox_added": "Pastkaste %s ir pievienota", "mailbox_added": "Pastkaste %s ir pievienota",
"mailbox_modified": "Izmaiņas pastkastei %s ir saglabātas", "mailbox_modified": "Izmaiņas pastkastei %s ir saglabātas",
@@ -519,20 +538,21 @@
"resource_modified": "Izmaiņas %s ir saglabātas", "resource_modified": "Izmaiņas %s ir saglabātas",
"resource_removed": "Resurs %s tika noņemts", "resource_removed": "Resurs %s tika noņemts",
"ui_texts": "Saglabāt UI izmaiņas tekstiem", "ui_texts": "Saglabāt UI izmaiņas tekstiem",
"upload_success": "Faila augšupielāde veiksmīga", "upload_success": "Datne sekmīgi augšupielādēta",
"verified_fido2_login": "Apliecināta FIDO2 pieteikšanās", "verified_fido2_login": "Apliecināta FIDO2 pieteikšanās",
"verified_webauthn_login": "Apliecināta WebAuthn pieteikšanās", "verified_webauthn_login": "Apliecināta WebAuthn pieteikšanās",
"verified_totp_login": "Apliecināta TOTP pieteikšanās", "verified_totp_login": "Apliecināta TOTP pieteikšanās",
"verified_yotp_login": "Apliecināta Yubico OTP pieteikšanās", "verified_yotp_login": "Apliecināta Yubico OTP pieteikšanās",
"app_passwd_removed": "Noņemta lietotnes parole ar Id %s", "app_passwd_removed": "Noņemta lietotnes parole ar Id %s",
"app_passwd_added": "Pievienota jauna lietotnes parole" "app_passwd_added": "Pievienota jauna lietotnes parole",
"f2b_banlist_refreshed": "Liegumu saraksta Id tika sekmīgi atsvaidzināts."
}, },
"tfa": { "tfa": {
"api_register": "%s izmanto Yubico Cloud API. Lūdzu iegūstiet API atslēgu priekš Jūsu atslēgas<a href=\"https://upgrade.yubico.com/getapikey/\" target=\"_blank\">here</a>", "api_register": "%s izmanto Yubico Cloud API. Lūdzu iegūstiet API atslēgu priekš Jūsu atslēgas<a href=\"https://upgrade.yubico.com/getapikey/\" target=\"_blank\">here</a>",
"confirm": "Apstiprināt", "confirm": "Apstiprināt",
"confirm_totp_token": "Lūdzu apstipriniet Jūsu izmaiņas ievadot uzģenerēto tekstu", "confirm_totp_token": "Lūgums apstiprināt savas izmaiņas ar izveidotās tekstvienības ievadīšanu",
"delete_tfa": "Atspējot TFA", "delete_tfa": "Atspējot TFA",
"disable_tfa": "Atspējot TFA līdz nākamajai veiksmīgajai pieteikšanās reizei", "disable_tfa": "Atspējot TFA līdz nākamajai sekmīgajai pieteikšanās reizei",
"enter_qr_code": "TOTP kods, ja Tava ierīce nevar nolasīt kvadrātkodus", "enter_qr_code": "TOTP kods, ja Tava ierīce nevar nolasīt kvadrātkodus",
"key_id": "Jūsu YubiKey identifikators", "key_id": "Jūsu YubiKey identifikators",
"key_id_totp": "Identifikators Jūsu atslēgai", "key_id_totp": "Identifikators Jūsu atslēgai",
@@ -544,7 +564,7 @@
"totp": "Uz laiku bāzēta vienreizēja parole (Google Autentifikātors utt.)", "totp": "Uz laiku bāzēta vienreizēja parole (Google Autentifikātors utt.)",
"webauthn": "WebAuthn autentifikācija", "webauthn": "WebAuthn autentifikācija",
"waiting_usb_auth": "<i>Gaida USB ierīci...</i><br><br>Lūdzu, tagad nospiežiet pogu uz Jūsu WebAuthn USB ierīces.", "waiting_usb_auth": "<i>Gaida USB ierīci...</i><br><br>Lūdzu, tagad nospiežiet pogu uz Jūsu WebAuthn USB ierīces.",
"waiting_usb_register": "<i>Gaida USB ierīci...</i><br><br>Lūdzu augšā ievadiet Jūsu paroli un apstipriniet WebAuthn reģistrāciju nospiežot pogu uz Jūsu WebAuthn USB ierīces.", "waiting_usb_register": "<i>Gaida USB ierīci...</i><br><br>Lūgums augstāk ievadīt savu paroli un apstiprināt reģistrēšanos ar USB ierīces pogas nospiešanu.",
"yubi_otp": "Yubico OTP autentifikators", "yubi_otp": "Yubico OTP autentifikators",
"authenticators": "Autentificētāji" "authenticators": "Autentificētāji"
}, },
@@ -589,7 +609,7 @@
"new_password_repeat": "Paroles apstiprinājums (atkārtoti)", "new_password_repeat": "Paroles apstiprinājums (atkārtoti)",
"no_active_filter": "Nav pieejami aktīvi filtri", "no_active_filter": "Nav pieejami aktīvi filtri",
"no_record": "Nav ieraksta", "no_record": "Nav ieraksta",
"password_now": "Pašreizējā parole (Apstiprināt izmaiņas)", "password_now": "Pašreizējā parole (apstiprināt izmaiņas)",
"remove": "Noņemt", "remove": "Noņemt",
"running": "Darbojas", "running": "Darbojas",
"save_changes": "Saglabāt izmaiņas", "save_changes": "Saglabāt izmaiņas",
@@ -599,11 +619,11 @@
"spam_aliases": "Pagaidu e-pasta aizstājvārdi", "spam_aliases": "Pagaidu e-pasta aizstājvārdi",
"spamfilter": "Mēstuļu filtrs", "spamfilter": "Mēstuļu filtrs",
"spamfilter_behavior": "Reitings", "spamfilter_behavior": "Reitings",
"spamfilter_bl": "Melnais saraksts", "spamfilter_bl": "Liegumu saraksts",
"spamfilter_bl_desc": "No melnajā sarakstā iekļautajām e-pasta adresēm saņemtās vēstules <b>vienmēr</b> tiks atzīmētas kā mēstules un noraidītas. Noraidītais pasts <b>netiks</b> ievietots karantīnā. Var izmantot aizstājzīmes. Atlasīšana tiek pielietota tikai tiešiem aizstājvārdiem (aizstājvārdiem ar vienu mērķa pastkasti), izņemot visu tverošos aizstājvārdus un pašu pastkasti.", "spamfilter_bl_desc": "No lieguma sarakstā iekļautajām e-pasta adresēm saņemtās vēstules <b>vienmēr</b> tiks atzīmētas kā mēstules un noraidītas. Noraidītais pasts <b>netiks</b> ievietots karantīnā. Var izmantot aizstājzīmes. Atlasīšana tiek pielietota tikai tiešiem aizstājvārdiem (aizstājvārdiem ar vienu mērķa pastkasti), izņemot visu tverošos aizstājvārdus un pašu pastkasti.",
"spamfilter_default_score": "Noklusējuma vērtības", "spamfilter_default_score": "Noklusējuma vērtības",
"spamfilter_green": "Zaļš: šī nav mēstule", "spamfilter_green": "Zaļš: šī nav mēstule",
"spamfilter_hint": "Pirmā vērtība norāda uz zemu \"Spam vērtējumu\" vērtējumu, otra vērtība par \"Augstu spam vērtējumu\".", "spamfilter_hint": "Pirmā vērtība norāda uz zemu \"mēstules novērtējumu\", otrā atspoguļo \"augstu mēstules novērtējumu\".",
"spamfilter_red": "Sarkans: Šī vēstule noteikti ir spams un tiek nekavējoties noraidīta", "spamfilter_red": "Sarkans: Šī vēstule noteikti ir spams un tiek nekavējoties noraidīta",
"spamfilter_table_action": "Darbība", "spamfilter_table_action": "Darbība",
"spamfilter_table_add": "Pievienot vienību", "spamfilter_table_add": "Pievienot vienību",
@@ -611,8 +631,8 @@
"spamfilter_table_empty": "Nav datu ko parādīt", "spamfilter_table_empty": "Nav datu ko parādīt",
"spamfilter_table_remove": "noņemt", "spamfilter_table_remove": "noņemt",
"spamfilter_table_rule": "Noteikums", "spamfilter_table_rule": "Noteikums",
"spamfilter_wl": "Baltais saraksts", "spamfilter_wl": "Atļautais saraksts",
"spamfilter_wl_desc": "No baltā saraksta e-pasta adresēm saņemtās vēstules <b>nekad</b> netiks atzīmētas kā mēstules. Var tikt izmantotas aizstājzīmes. Atlase tiek piemērota tikai tiešiem aizstājvārdiem (aizstājvārdiem ar vienu mērķa pastkasti), izņemot visu tverošos aizstājvārdus un pašu pastkasti.", "spamfilter_wl_desc": "No atļautā saraksta e-pasta adresēm saņemtās vēstules <b>nekad</b> netiks atzīmētas kā mēstules. Var tikt izmantotas aizstājzīmes. Atlase tiek piemērota tikai tiešiem aizstājvārdiem (aizstājvārdiem ar vienu mērķa pastkasti), izņemot visu tverošos aizstājvārdus un pašu pastkasti.",
"spamfilter_yellow": "Dzeltens: šī vēstule visticamāk ir spams un tiks pārvietota uz Junk mapi", "spamfilter_yellow": "Dzeltens: šī vēstule visticamāk ir spams un tiks pārvietota uz Junk mapi",
"status": "Status", "status": "Status",
"sync_jobs": "Sinhronizācijas uzdevumi", "sync_jobs": "Sinhronizācijas uzdevumi",
@@ -644,15 +664,21 @@
"change_password_hint_app_passwords": "Kontā ir %d lietotņu paroles, kas netiks mainītas. Lai pārvaldītu tās, jādodas uz cilni \"Lietotņu paroles\".", "change_password_hint_app_passwords": "Kontā ir %d lietotņu paroles, kas netiks mainītas. Lai pārvaldītu tās, jādodas uz cilni \"Lietotņu paroles\".",
"with_app_password": "ar lietotnes paroli", "with_app_password": "ar lietotnes paroli",
"apple_connection_profile_with_app_password": "Jauna lietotnes parole ir izveidota un pievienota profilam, lai ierīces iestatīšanas laikā nebūtu nepieciešams ievadīt paroli. Lūgums nekopīgot datni, jo tā nodrošina pilnu piekļuvi pastkastei.", "apple_connection_profile_with_app_password": "Jauna lietotnes parole ir izveidota un pievienota profilam, lai ierīces iestatīšanas laikā nebūtu nepieciešams ievadīt paroli. Lūgums nekopīgot datni, jo tā nodrošina pilnu piekļuvi pastkastei.",
"tfa_info": "Divpakāpju autentificēšanās palīdz aizsargāt kontu.Ja tā ir iespējota, var būt nepieciešamas lietotņu paroles, lai pieteiktos lietotnēs vai pakalpojumos, kas nenodrošina divpakāpju autentificēšanos (piem., e-pasta klienti).", "tfa_info": "Divpakāpju autentificēšanās palīdz aizsargāt kontu.Ja tā ir iespējota, ir nepieciešamas lietotņu paroles, lai pieteiktos lietotnēs vai pakalpojumos, kas nenodrošina divpakāpju autentificēšanos (piem., e-pasta klienti).",
"app_passwds": "Lietotņu paroles", "app_passwds": "Lietotņu paroles",
"create_app_passwd": "Izveidot lietotnes paroli" "create_app_passwd": "Izveidot lietotnes paroli",
"empty": "Nav iznākuma",
"quarantine_notification_info": "Tiklīdz paziņojums ir nosūtīts, vienumi tiks atzīmēti kā \"paziņoti\", un par šo vienumu vairs netiks sūtīti paziņojumi.",
"sender_acl_disabled": "<span class=\"badge fs-6 bg-danger\">Sūtītāja pārbaude ir atspējota</span>",
"syncjob_last_run_result": "Pēdējās izpildes iznākums"
}, },
"datatables": { "datatables": {
"paginate": { "paginate": {
"first": "Pirmā", "first": "Pirmā",
"last": "Pēdējā" "last": "Pēdējā"
} },
"emptyTable": "Tabulā nav datu",
"search": "Meklēt:"
}, },
"debug": { "debug": {
"last_modified": "Pēdējoreiz mainīts", "last_modified": "Pēdējoreiz mainīts",
+1 -1
View File
@@ -423,7 +423,7 @@
"pushover_token": "Formaat van Pushover-token is ongeldig", "pushover_token": "Formaat van Pushover-token is ongeldig",
"quota_not_0_not_numeric": "Quota dient numeriek en groter dan 0 te zijn", "quota_not_0_not_numeric": "Quota dient numeriek en groter dan 0 te zijn",
"recipient_map_entry_exists": "Ontvanger-map met \"%s\" bestaat reeds", "recipient_map_entry_exists": "Ontvanger-map met \"%s\" bestaat reeds",
"redis_error": "Redis-error: %s", "valkey_error": "Valkey-error: %s",
"relayhost_invalid": "Invoer %s is ongeldig", "relayhost_invalid": "Invoer %s is ongeldig",
"release_send_failed": "Het volgende bericht kon niet worden vrijgegeven: %s", "release_send_failed": "Het volgende bericht kon niet worden vrijgegeven: %s",
"reset_f2b_regex": "Regex-filters konden niet worden hersteld, probeer het opnieuw of herlaad de pagina over enkele seconden.", "reset_f2b_regex": "Regex-filters konden niet worden hersteld, probeer het opnieuw of herlaad de pagina over enkele seconden.",
+107 -17
View File
@@ -1,7 +1,7 @@
{ {
"acl": { "acl": {
"alias_domains": "Adicionar domínios alias", "alias_domains": "Adicionar alias de domínios",
"app_passwds": "Gerenciar senhas de aplicativos", "app_passwds": "Gerenciar senhas de app",
"bcc_maps": "Mapas BCC", "bcc_maps": "Mapas BCC",
"delimiter_action": "Ação delimitadora", "delimiter_action": "Ação delimitadora",
"domain_desc": "Alterar descrição do domínio", "domain_desc": "Alterar descrição do domínio",
@@ -9,7 +9,7 @@
"eas_reset": "Redefinir dispositivos EAS", "eas_reset": "Redefinir dispositivos EAS",
"extend_sender_acl": "Permitir estender a ACL do remetente por endereços externos", "extend_sender_acl": "Permitir estender a ACL do remetente por endereços externos",
"filters": "Filtros", "filters": "Filtros",
"login_as": "Faça login como usuário da mailbox", "login_as": "Fazer login como usuário da mailbox",
"mailbox_relayhost": "Alterar relayhost para uma mailbox", "mailbox_relayhost": "Alterar relayhost para uma mailbox",
"prohibited": "Proibido pela ACL", "prohibited": "Proibido pela ACL",
"protocol_access": "Alterar o acesso ao protocolo", "protocol_access": "Alterar o acesso ao protocolo",
@@ -109,7 +109,9 @@
"username": "Nome de usuário", "username": "Nome de usuário",
"validate": "Validar", "validate": "Validar",
"validation_success": "Validado com sucesso", "validation_success": "Validado com sucesso",
"dry": "Simular sincronização" "dry": "Simular sincronização",
"internal": "Interno",
"internal_info": "Aliases internos são acessíveis apenas a partir do próprio domínio ou alias de domínio."
}, },
"admin": { "admin": {
"access": "Acesso", "access": "Acesso",
@@ -364,7 +366,52 @@
"iam_client_secret": "Senha de cliente", "iam_client_secret": "Senha de cliente",
"iam_auth_flow": "Fluxo de autenticação", "iam_auth_flow": "Fluxo de autenticação",
"iam_client_scopes": "Escopo do cliente", "iam_client_scopes": "Escopo do cliente",
"iam_default_template": "Template Padrão" "iam_default_template": "Template Padrão",
"admin_quicklink": "Ocultar link rápido para página de login do administrador",
"app_hide": "Ocultar para login",
"login_page": "Página de login",
"domainadmin_quicklink": "Ocultar link rápido para página de login do administrador de domínio",
"filter": "Filtro",
"force_sso_text": "Se um provedor OIDC externo for configurado, esta opção oculta os formulários de login padrão do mailcow e mostra apenas o botão de single sign-on",
"force_sso": "Desabilitar login do mailcow e mostrar apenas single sign-on",
"iam": "Provedor de identidade",
"iam_attribute_field": "Campo de atributo",
"iam_authorize_url": "Endpoint de autorização",
"iam_auth_flow_info": "Além do fluxo de código de autorização (fluxo padrão no Keycloak), que é usado para login de single sign-on, o mailcow também suporta fluxo de autenticação com credenciais diretas. O fluxo Mailpassword tenta validar as credenciais do usuário usando a API REST do administrador do Keycloak. O mailcow recupera a senha hash do atributo <code>mailcow_password</code>, que é mapeado no Keycloak.",
"iam_basedn": "DN base",
"iam_default_template_description": "Se nenhum template for atribuído a um usuário, o template padrão será usado para criar a caixa de correio, mas não para atualizar a caixa de correio.",
"iam_description": "Configure um provedor externo para autenticação<br>As caixas de correio dos usuários serão criadas automaticamente no primeiro login, desde que um mapeamento de atributos tenha sido definido.",
"iam_extra_permission": "Para que as configurações a seguir funcionem, o cliente mailcow no Keycloak precisa de uma <code>conta de serviço</code> e a permissão para <code>visualizar usuários</code>.",
"iam_host": "Host",
"iam_host_info": "Digite um ou mais hosts LDAP, separados por vírgulas.",
"iam_import_users": "Importar usuários",
"iam_login_provisioning": "Criar usuários automaticamente no login",
"iam_mapping": "Mapeamento de atributos",
"iam_bindpass": "Senha de vinculação",
"iam_periodic_full_sync": "Sincronização completa periódica",
"iam_port": "Porta",
"iam_realm": "Realm",
"iam_redirect_url": "URL de redirecionamento",
"iam_rest_flow": "Fluxo Mailpassword",
"iam_server_url": "URL do servidor",
"iam_sso": "Single sign-on",
"iam_sync_interval": "Intervalo de sincronização/importação (min)",
"iam_test_connection": "Testar conexão",
"iam_token_url": "Endpoint de token",
"iam_userinfo_url": "Endpoint de informações do usuário",
"iam_username_field": "Campo de nome de usuário",
"iam_binddn": "DN de vinculação",
"iam_use_ssl": "Usar SSL",
"iam_use_ssl_info": "Se habilitar SSL e a porta estiver definida como 389, ela será automaticamente substituída para usar 636.",
"iam_use_tls": "Usar StartTLS",
"iam_use_tls_info": "Se habilitar TLS, você deve usar a porta padrão para seu servidor LDAP (389). Portas SSL não podem ser usadas.",
"iam_version": "Versão",
"ignore_ssl_error": "Ignorar erros SSL",
"needs_restart": "precisa reiniciar",
"quicklink_text": "Mostrar ou ocultar links rápidos para outras páginas de login abaixo do formulário de login",
"task": "Tarefa",
"user_link": "Link do usuário",
"user_quicklink": "Ocultar link rápido para página de login do usuário"
}, },
"danger": { "danger": {
"access_denied": "Acesso negado ou dados de formulário inválidos", "access_denied": "Acesso negado ou dados de formulário inválidos",
@@ -461,7 +508,7 @@
"quota_not_0_not_numeric": "A cota deve ser numérica e >= 0", "quota_not_0_not_numeric": "A cota deve ser numérica e >= 0",
"recipient_map_entry_exists": "Existe uma entrada de mapa de destinatários “%s”", "recipient_map_entry_exists": "Existe uma entrada de mapa de destinatários “%s”",
"recovery_email_failed": "Não foi possível enviar um email de recuperação. Por favor, contacte seu administrador.", "recovery_email_failed": "Não foi possível enviar um email de recuperação. Por favor, contacte seu administrador.",
"redis_error": "Erro do Redis: %s", "valkey_error": "Erro do Valkey: %s",
"relayhost_invalid": "A entrada de mapa %s é inválida", "relayhost_invalid": "A entrada de mapa %s é inválida",
"release_send_failed": "A mensagem não pôde ser liberada: %s", "release_send_failed": "A mensagem não pôde ser liberada: %s",
"reset_f2b_regex": "O filtro Regex não pôde ser redefinido a tempo. Tente novamente ou aguarde mais alguns segundos e recarregue o site.", "reset_f2b_regex": "O filtro Regex não pôde ser redefinido a tempo. Tente novamente ou aguarde mais alguns segundos e recarregue o site.",
@@ -501,7 +548,15 @@
"username_invalid": "O nome de usuário %s não pode ser usado", "username_invalid": "O nome de usuário %s não pode ser usado",
"validity_missing": "Por favor, atribua um período de validade", "validity_missing": "Por favor, atribua um período de validade",
"value_missing": "Forneça todos os valores", "value_missing": "Forneça todos os valores",
"yotp_verification_failed": "Falha na verificação do Yubico OTP: %s" "yotp_verification_failed": "Falha na verificação do Yubico OTP: %s",
"authsource_in_use": "O provedor de identidade não pode ser alterado ou excluído pois está sendo usado por um ou mais usuários.",
"generic_server_error": "Ocorreu um erro inesperado no servidor. Entre em contato com seu administrador.",
"iam_test_connection": "Falha na conexão",
"max_age_invalid": "Idade máxima %s é inválida",
"mode_invalid": "Modo %s é inválido",
"mx_invalid": "Registro MX %s é inválido",
"required_data_missing": "Dados obrigatórios %s estão ausentes",
"version_invalid": "Versão %s é inválida"
}, },
"datatables": { "datatables": {
"collapse_all": "Recolher tudo", "collapse_all": "Recolher tudo",
@@ -545,7 +600,7 @@
"history_all_servers": "Histórico (todos os servidores)", "history_all_servers": "Histórico (todos os servidores)",
"in_memory_logs": "Registros na memória", "in_memory_logs": "Registros na memória",
"last_modified": "Última modificação", "last_modified": "Última modificação",
"log_info": "<p>Os <b>registros na memória do</b> mailcow são coletados em listas do Redis e reduzidos para LOG_LINES (%d) a cada minuto para reduzir o martelamento.\r\n Os <br>registros na memória não devem ser persistentes. Todos os aplicativos que fazem login na memória também fazem login no daemon do Docker e, portanto, no driver de registro padrão.\r\n </p><br>O tipo de registro na memória deve ser usado para depurar pequenos problemas com contêineres.\r\n <p>Os <b>registros externos</b> são coletados por meio da API do aplicativo em questão.</p>\r\n <p>Os <b>registros estáticos</b> são principalmente registros de atividades, que não são registrados no Dockerd, mas ainda precisam ser persistentes (exceto os registros da API).</p>", "log_info": "<p>Os <b>registros na memória do</b> mailcow são coletados em listas do Valkey e reduzidos para LOG_LINES (%d) a cada minuto para reduzir o martelamento.\r\n Os <br>registros na memória não devem ser persistentes. Todos os aplicativos que fazem login na memória também fazem login no daemon do Docker e, portanto, no driver de registro padrão.\r\n </p><br>O tipo de registro na memória deve ser usado para depurar pequenos problemas com contêineres.\r\n <p>Os <b>registros externos</b> são coletados por meio da API do aplicativo em questão.</p>\r\n <p>Os <b>registros estáticos</b> são principalmente registros de atividades, que não são registrados no Dockerd, mas ainda precisam ser persistentes (exceto os registros da API).</p>",
"login_time": "Hora", "login_time": "Hora",
"logs": "Registros", "logs": "Registros",
"memory": "Memória", "memory": "Memória",
@@ -708,7 +763,25 @@
"title": "Editar objeto", "title": "Editar objeto",
"unchanged_if_empty": "Se inalterado, deixe em branco", "unchanged_if_empty": "Se inalterado, deixe em branco",
"username": "Nome de usuário", "username": "Nome de usuário",
"validate_save": "Valide e salve" "validate_save": "Validar e salvar",
"internal": "Interno",
"internal_info": "Aliases internos são acessíveis apenas a partir do próprio domínio ou domínios alias.",
"mailbox_rename": "Renomear caixa de correio",
"mailbox_rename_agree": "Eu criei um backup.",
"mailbox_rename_warning": "IMPORTANTE! Crie um backup antes de renomear a caixa de correio.",
"mailbox_rename_alias": "Criar alias automaticamente",
"mailbox_rename_title": "Novo nome da caixa de correio local",
"mta_sts": "MTA-STS",
"mta_sts_info": "<a href='https://en.wikipedia.org/wiki/Simple_Mail_Transfer_Protocol#SMTP_MTA_Strict_Transport_Security' target='_blank'>MTA-STS</a> é um padrão que força a entrega de email entre servidores de email para usar TLS com certificados válidos. <br>É usado quando <a target='_blank' href='https://en.wikipedia.org/wiki/DNS-based_Authentication_of_Named_Entities'>DANE</a> não é possível devido ao DNSSEC ausente ou não suportado.<br><b>Nota</b>: Se o domínio de recepção suporta DANE com DNSSEC, DANE é <b>sempre</b> preferido MTA-STS atua apenas como fallback.",
"mta_sts_version": "Versão",
"mta_sts_version_info": "Define a versão do padrão MTA-STS atualmente apenas <code>STSv1</code> é válido.",
"mta_sts_mode": "Modo",
"mta_sts_mode_info": "Há três modos para escolher:<ul><li><em>testing</em> política é apenas monitorada, violações não têm impacto.</li><li><em>enforce</em> política é rigorosamente aplicada, conexões sem TLS válido são rejeitadas.</li><li><em>none</em> política é publicada mas não aplicada.</li></ul>",
"mta_sts_max_age": "Idade máxima",
"mta_sts_max_age_info": "Tempo em segundos que servidores de email de recepção podem armazenar esta política em cache até buscar novamente.",
"mta_sts_mx": "Servidor MX",
"mta_sts_mx_info": "Permite envio apenas para nomes de host de servidor de email explicitamente listados; o MTA de envio verifica se o nome do host DNS MX corresponde à lista de políticas e permite entrega apenas com certificado TLS válido (protege contra MITM).",
"mta_sts_mx_notice": "Múltiplos servidores MX podem ser especificados (separados por vírgulas)."
}, },
"fido2": { "fido2": {
"confirm": "Confirme", "confirm": "Confirme",
@@ -771,7 +844,15 @@
"password": "Senha", "password": "Senha",
"reset_password": "Recuperar a senha", "reset_password": "Recuperar a senha",
"request_reset_password": "Solicitar troca de senha", "request_reset_password": "Solicitar troca de senha",
"username": "Nome de usuário" "username": "Nome de usuário",
"login_linkstext": "Login incorreto?",
"login_usertext": "Entrar como usuário",
"login_domainadmintext": "Entrar como administrador de domínio",
"login_admintext": "Entrar como administrador",
"login_user": "Login de usuário",
"login_dadmin": "Login como administrador de domínio",
"login_admin": "Login como administrador",
"email": "Endereço de email"
}, },
"mailbox": { "mailbox": {
"action": "Ação", "action": "Ação",
@@ -946,7 +1027,9 @@
"username": "Nome de usuário", "username": "Nome de usuário",
"waiting": "Esperando", "waiting": "Esperando",
"weekly": "Semanalmente", "weekly": "Semanalmente",
"yes": "✓" "yes": "✓",
"iam": "Provedor de Identidade",
"internal": "Interno"
}, },
"oauth2": { "oauth2": {
"access_denied": "Faça login como proprietário da mailbox para conceder acesso via OAuth2.", "access_denied": "Faça login como proprietário da mailbox para conceder acesso via OAuth2.",
@@ -961,8 +1044,8 @@
"action": "Ação", "action": "Ação",
"atts": "Anexos", "atts": "Anexos",
"check_hash": "Arquivo de pesquisa hash @ VT", "check_hash": "Arquivo de pesquisa hash @ VT",
"confirm": "Confirme", "confirm": "Confirmar",
"confirm_delete": "Confirme a exclusão desse elemento.", "confirm_delete": "Confirmar exclusão desse elemento.",
"danger": "Perigo", "danger": "Perigo",
"deliver_inbox": "Entregar na caixa de entrada", "deliver_inbox": "Entregar na caixa de entrada",
"disabled_by_config": "A configuração atual do sistema desativa a funcionalidade de quarentena. Defina “retenções por mailbox” e um “tamanho máximo” para os elementos de quarentena.", "disabled_by_config": "A configuração atual do sistema desativa a funcionalidade de quarentena. Defina “retenções por mailbox” e um “tamanho máximo” para os elementos de quarentena.",
@@ -1123,12 +1206,15 @@
"verified_fido2_login": "Login FIDO2 verificado", "verified_fido2_login": "Login FIDO2 verificado",
"verified_totp_login": "Login TOTP verificado", "verified_totp_login": "Login TOTP verificado",
"verified_webauthn_login": "Login verificado do WebAuthn", "verified_webauthn_login": "Login verificado do WebAuthn",
"verified_yotp_login": "Login OTP verificado do Yubico" "verified_yotp_login": "Login OTP verificado do Yubico",
"custom_login_modified": "Personalização de login foi salva com sucesso",
"iam_test_connection": "Conexão bem-sucedida",
"mailbox_renamed": "Caixa de correio foi renomeada de %s para %s"
}, },
"tfa": { "tfa": {
"authenticators": "Autenticadores", "authenticators": "Autenticadores",
"api_register": "%s usa a API Yubico Cloud. Obtenha uma chave de API para sua chave <a href=\"https://upgrade.yubico.com/getapikey/\" target=\"_blank\">aqui</a>", "api_register": "%s usa a API Yubico Cloud. Obtenha uma chave de API para sua chave <a href=\"https://upgrade.yubico.com/getapikey/\" target=\"_blank\">aqui</a>",
"confirm": "Confirme", "confirm": "Confirmar",
"confirm_totp_token": "Confirme suas alterações inserindo o token gerado", "confirm_totp_token": "Confirme suas alterações inserindo o token gerado",
"delete_tfa": "Desativar o TFA", "delete_tfa": "Desativar o TFA",
"disable_tfa": "Desative o TFA até o próximo login bem-sucedido", "disable_tfa": "Desative o TFA até o próximo login bem-sucedido",
@@ -1141,7 +1227,7 @@
"reload_retry": "- (recarregue o navegador se o erro persistir)", "reload_retry": "- (recarregue o navegador se o erro persistir)",
"scan_qr_code": "Escaneie o código a seguir com seu aplicativo autenticador ou insira o código manualmente.", "scan_qr_code": "Escaneie o código a seguir com seu aplicativo autenticador ou insira o código manualmente.",
"select": "Por favor, selecione", "select": "Por favor, selecione",
"set_tfa": "Defina o método de autenticação de dois fatores", "set_tfa": "Método de autenticação de dois fatores",
"start_webauthn_validation": "Iniciar validação", "start_webauthn_validation": "Iniciar validação",
"tfa": "Autenticação de dois fatores", "tfa": "Autenticação de dois fatores",
"tfa_token_invalid": "Token TFA inválido", "tfa_token_invalid": "Token TFA inválido",
@@ -1318,7 +1404,11 @@
"weeks": "semanas", "weeks": "semanas",
"with_app_password": "com senha do aplicativo", "with_app_password": "com senha do aplicativo",
"year": "ano", "year": "ano",
"years": "anos" "years": "anos",
"authentication": "Autenticação",
"overview": "Visão geral",
"protocols": "Protocolos",
"tfa_info": "A autenticação de dois fatores ajuda a proteger sua conta. Se você habilitá-la, precisará de senhas de aplicativo para fazer login em aplicativos ou serviços que não suportam autenticação de dois fatores (por exemplo, clientes de email)."
}, },
"warning": { "warning": {
"cannot_delete_self": "Não é possível excluir o usuário conectado", "cannot_delete_self": "Não é possível excluir o usuário conectado",
+2 -2
View File
@@ -429,7 +429,7 @@
"pushover_token": "Jetonul pushover are formatul greșit", "pushover_token": "Jetonul pushover are formatul greșit",
"quota_not_0_not_numeric": "Cota trebuie să fie numerică și >= 0", "quota_not_0_not_numeric": "Cota trebuie să fie numerică și >= 0",
"recipient_map_entry_exists": "O intrare a hărții destinatarului \"%s\" există", "recipient_map_entry_exists": "O intrare a hărții destinatarului \"%s\" există",
"redis_error": "Eroare Redis: %s", "valkey_error": "Eroare Valkey: %s",
"relayhost_invalid": "Intrarea hărții %s este invalidă", "relayhost_invalid": "Intrarea hărții %s este invalidă",
"release_send_failed": "Mesajul nu a putut fi eliberat: %s", "release_send_failed": "Mesajul nu a putut fi eliberat: %s",
"reset_f2b_regex": "Filtrul regex nu a putut fi resetat la timp, încercați din nou sau așteptați câteva secunde și reîncărcați pagina.", "reset_f2b_regex": "Filtrul regex nu a putut fi resetat la timp, încercați din nou sau așteptați câteva secunde și reîncărcați pagina.",
@@ -482,7 +482,7 @@
"history_all_servers": "Istoric (toate serverele)", "history_all_servers": "Istoric (toate serverele)",
"in_memory_logs": "Jurnale din memorie", "in_memory_logs": "Jurnale din memorie",
"last_modified": "Ultima modificare", "last_modified": "Ultima modificare",
"log_info": "<p><b>jurnalele din memorie</b> pentru mailcow sunt colectate în listele Redis și trimise la LOG_LINES (%d) în fiecare minut pentru a reduce ciocnirea.\n <br>Jurnalele din memorie nu sunt menite a fi persistente. Toate aplicațiile care înregistrează jurnale în memorie, înregistrează de asemenea jurnale în daemonul Docker și, prin urmare, în driverul de jurnale implicit.\n <br>Tipul de jurnal din memorie trebuie utilizat pentru depanarea problemelor minore cu containerele.</p>\n <p><b>Jurnalele externe</b> sunt colectate prin API-ul aplicației respective.</p>\n <p><b>Jurnalele statice</b> sunt, în majoritate, jurnale de activitate care nu sunt înregistrate în Docker, dar trebuie să fie persistente (cu excepția jurnalelor API).</p>", "log_info": "<p><b>jurnalele din memorie</b> pentru mailcow sunt colectate în listele Valkey și trimise la LOG_LINES (%d) în fiecare minut pentru a reduce ciocnirea.\n <br>Jurnalele din memorie nu sunt menite a fi persistente. Toate aplicațiile care înregistrează jurnale în memorie, înregistrează de asemenea jurnale în daemonul Docker și, prin urmare, în driverul de jurnale implicit.\n <br>Tipul de jurnal din memorie trebuie utilizat pentru depanarea problemelor minore cu containerele.</p>\n <p><b>Jurnalele externe</b> sunt colectate prin API-ul aplicației respective.</p>\n <p><b>Jurnalele statice</b> sunt, în majoritate, jurnale de activitate care nu sunt înregistrate în Docker, dar trebuie să fie persistente (cu excepția jurnalelor API).</p>",
"login_time": "Moment", "login_time": "Moment",
"logs": "Jurnale", "logs": "Jurnale",
"online_users": "Utilizatori online", "online_users": "Utilizatori online",
+2 -2
View File
@@ -506,7 +506,7 @@
"quota_not_0_not_numeric": "Размер квоты должен быть больше или равен нулю", "quota_not_0_not_numeric": "Размер квоты должен быть больше или равен нулю",
"recipient_map_entry_exists": "Правило перезаписи \"%s\" уже существует", "recipient_map_entry_exists": "Правило перезаписи \"%s\" уже существует",
"recovery_email_failed": "Не удалось отправить письмо для восстановления. Пожалуйста, свяжитесь с вашим администратором.", "recovery_email_failed": "Не удалось отправить письмо для восстановления. Пожалуйста, свяжитесь с вашим администратором.",
"redis_error": "Ошибка в Redis: %s", "valkey_error": "Ошибка в Valkey: %s",
"relayhost_invalid": "Недопустимое правило %s", "relayhost_invalid": "Недопустимое правило %s",
"release_send_failed": "Сообщение не может быть восстановлено: %s", "release_send_failed": "Сообщение не может быть восстановлено: %s",
"reset_f2b_regex": "Сброс фильтров не был выполнен за отведённый промежуток времени, пожалуйста, повторите попытку или подождите еще несколько секунд и перезагрузите веб страницу.", "reset_f2b_regex": "Сброс фильтров не был выполнен за отведённый промежуток времени, пожалуйста, повторите попытку или подождите еще несколько секунд и перезагрузите веб страницу.",
@@ -594,7 +594,7 @@
"history_all_servers": "История (все серверы)", "history_all_servers": "История (все серверы)",
"in_memory_logs": "Журналы контейнеров", "in_memory_logs": "Журналы контейнеров",
"last_modified": "Последние изменения", "last_modified": "Последние изменения",
"log_info": "<p><b>Журналы контейнеров</b> mailcow сохраняются в Redis, и раз в минуту строки журнала за пределами <code>LOG_LINES (%d)</code> удаляются, чтобы уменьшить нагрузку на сервер.\r\n <br>Сами журналы контейнеров не сохраняются после перезагрузки контейнера. Все контейнеры дополнительно пишут логи в службу Docker, и, следовательно, используют драйвер логирования по умолчанию. Журналы контейнеров предусмотрены только для отладки мелких проблем. Для других задач, пожалуйста, настройте драйвер логирования Docker самостоятельно.</p>\r\n <p><b>Внешние журналы</b> собираются через API приложений.</p>\r\n <p><b>Статические журналы</b> &ndash; это, в основном, журналы активности, которые не записываются в Dockerd, но все равно должны быть постоянными (за исключением журналов API).</p>", "log_info": "<p><b>Журналы контейнеров</b> mailcow сохраняются в Valkey, и раз в минуту строки журнала за пределами <code>LOG_LINES (%d)</code> удаляются, чтобы уменьшить нагрузку на сервер.\r\n <br>Сами журналы контейнеров не сохраняются после перезагрузки контейнера. Все контейнеры дополнительно пишут логи в службу Docker, и, следовательно, используют драйвер логирования по умолчанию. Журналы контейнеров предусмотрены только для отладки мелких проблем. Для других задач, пожалуйста, настройте драйвер логирования Docker самостоятельно.</p>\r\n <p><b>Внешние журналы</b> собираются через API приложений.</p>\r\n <p><b>Статические журналы</b> &ndash; это, в основном, журналы активности, которые не записываются в Dockerd, но все равно должны быть постоянными (за исключением журналов API).</p>",
"login_time": "Время входа", "login_time": "Время входа",
"logs": "Журналы", "logs": "Журналы",
"memory": "Память", "memory": "Память",

Some files were not shown because too many files have changed in this diff Show More