From c11ed5dd1e899173500161ebf52e572de42917ca Mon Sep 17 00:00:00 2001
From: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Fri, 12 Dec 2025 12:34:20 +0100
Subject: [PATCH 1/4] Prevent duplicate/plaintext login announcement rendering
(#6963)
* Initial plan
* Fix duplicate login announcement display
Co-authored-by: DerLinkman <62480600+DerLinkman@users.noreply.github.com>
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: DerLinkman <62480600+DerLinkman@users.noreply.github.com>
---
data/web/templates/base.twig | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/data/web/templates/base.twig b/data/web/templates/base.twig
index a8d0f6f39..98fdd86e4 100644
--- a/data/web/templates/base.twig
+++ b/data/web/templates/base.twig
@@ -144,7 +144,7 @@
-{% if ui_texts.ui_announcement_text and ui_texts.ui_announcement_active and not is_root_uri %}
+{% if ui_texts.ui_announcement_text and ui_texts.ui_announcement_active and not is_root_uri and mailcow_cc_username %}
{{ ui_texts.ui_announcement_text }}
From e76f5237edcccd8fdcc613e22f9d9b84265459db Mon Sep 17 00:00:00 2001
From: DerLinkman
Date: Thu, 11 Dec 2025 13:29:11 +0100
Subject: [PATCH 2/4] ofelia: revert fixed cron syntax for sa-rules download
---
docker-compose.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docker-compose.yml b/docker-compose.yml
index 75d00af34..ae9a18d98 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -321,7 +321,7 @@ services:
ofelia.job-exec.dovecot_clean_q_aged.command: "/bin/bash -c \"[[ $${MASTER} == y ]] && /usr/local/bin/gosu vmail /usr/local/bin/clean_q_aged.sh || exit 0\""
ofelia.job-exec.dovecot_maildir_gc.schedule: "0 */30 * * * *"
ofelia.job-exec.dovecot_maildir_gc.command: "/bin/bash -c \"source /source_env.sh ; /usr/local/bin/gosu vmail /usr/local/bin/maildir_gc.sh\""
- ofelia.job-exec.dovecot_sarules.schedule: "0 0 0 * * *"
+ ofelia.job-exec.dovecot_sarules.schedule: "@every 24h"
ofelia.job-exec.dovecot_sarules.command: "/bin/bash -c \"/usr/local/bin/sa-rules.sh\""
ofelia.job-exec.dovecot_fts.schedule: "0 0 0 * * *"
ofelia.job-exec.dovecot_fts.command: "/bin/bash -c \"/usr/local/bin/gosu vmail /usr/local/bin/optimize-fts.sh\""
From d977ddb5017e27f817f09310dcb7056449cb88fd Mon Sep 17 00:00:00 2001
From: DerLinkman
Date: Wed, 10 Dec 2025 08:43:04 +0100
Subject: [PATCH 3/4] backup: add image prefetch function to verify latest
image is used
---
helper-scripts/backup_and_restore.sh | 38 ++++++++++++++++++++++++++++
1 file changed, 38 insertions(+)
diff --git a/helper-scripts/backup_and_restore.sh b/helper-scripts/backup_and_restore.sh
index 7b75272cf..2e65f1343 100755
--- a/helper-scripts/backup_and_restore.sh
+++ b/helper-scripts/backup_and_restore.sh
@@ -91,6 +91,44 @@ if grep --help 2>&1 | head -n 1 | grep -q -i "busybox"; then
exit 1
fi
+# Add image prefetch function
+function prefetch_image() {
+ echo "Checking Docker image: ${DEBIAN_DOCKER_IMAGE}"
+
+ # Get local image digest if it exists
+ local local_digest=$(docker image inspect ${DEBIAN_DOCKER_IMAGE} --format='{{index .RepoDigests 0}}' 2>/dev/null | cut -d'@' -f2)
+
+ # Get remote image digest without pulling
+ local remote_digest=$(docker manifest inspect ${DEBIAN_DOCKER_IMAGE} 2>/dev/null | grep -oP '"digest":\s*"\K[^"]+' | head -1)
+
+ if [[ -z "${remote_digest}" ]]; then
+ echo "Warning: Unable to check remote image"
+ if [[ -n "${local_digest}" ]]; then
+ echo "Using cached version"
+ echo
+ return 0
+ else
+ echo "Error: Image ${DEBIAN_DOCKER_IMAGE} not found locally or remotely"
+ exit 1
+ fi
+ fi
+
+ if [[ "${local_digest}" != "${remote_digest}" ]]; then
+ echo "Image update available, pulling ${DEBIAN_DOCKER_IMAGE}"
+ if docker pull ${DEBIAN_DOCKER_IMAGE} 2>/dev/null; then
+ echo "Successfully pulled ${DEBIAN_DOCKER_IMAGE}"
+ else
+ echo "Error: Failed to pull ${DEBIAN_DOCKER_IMAGE}"
+ exit 1
+ fi
+ else
+ echo "Image is up to date (${remote_digest:0:12}...)"
+ fi
+ echo
+}
+
+# Prefetch the image early in the script
+prefetch_image
function backup() {
DATE=$(date +"%Y-%m-%d-%H-%M-%S")
From e8d9315d4ad6ae886b8146f618df1813db546254 Mon Sep 17 00:00:00 2001
From: Ashitaka <65665184+Ashitaka57@users.noreply.github.com>
Date: Wed, 10 Dec 2025 11:41:06 +0100
Subject: [PATCH 4/4] Merge pull request #6905 from
Ashitaka57/6646-pbkdf2-sha512-verify-hash
Support for PBKDF2-SHA512 hash algorithm in verify_hash() (FreeIPA compatibility) (issue 6646)
---
data/web/inc/functions.inc.php | 26 ++++++++++++++++++++++++++
1 file changed, 26 insertions(+)
diff --git a/data/web/inc/functions.inc.php b/data/web/inc/functions.inc.php
index 81b3f7e08..1947ec465 100644
--- a/data/web/inc/functions.inc.php
+++ b/data/web/inc/functions.inc.php
@@ -814,6 +814,32 @@ function verify_hash($hash, $password) {
$hash = $components[4];
return hash_equals(hash_pbkdf2('sha1', $password, $salt, $rounds), $hash);
+ case "PBKDF2-SHA512":
+ // Handle FreeIPA-style hash: {PBKDF2-SHA512}10000$$
+ $components = explode('$', $hash);
+ if (count($components) !== 3) return false;
+
+ // 1st part: iteration count (integer)
+ $iterations = intval($components[0]);
+ if ($iterations <= 0) return false;
+
+ // 2nd part: salt (base64-encoded)
+ $salt = $components[1];
+ // 3rd part: hash (base64-encoded)
+ $stored_hash_b64 = $components[2];
+
+ // Decode salt and hash from base64
+ $salt_bin = base64_decode($salt, true);
+ $hash_bin = base64_decode($stored_hash_b64, true);
+ if ($salt_bin === false || $hash_bin === false) return false;
+ // Get length of hash in bytes
+ $hash_len = strlen($hash_bin);
+ if ($hash_len === 0) return false;
+
+ // Calculate PBKDF2-SHA512 hash for provided password
+ $test_hash = hash_pbkdf2('sha512', $password, $salt_bin, $iterations, $hash_len, true);
+ return hash_equals($hash_bin, $test_hash);
+
case "PLAIN-MD4":
return hash_equals(hash('md4', $password), $hash);