Explorar el Código

[Rspamd] use python bootstrapper to start RSPAMD container

FreddleSpl0it hace 3 meses
padre
commit
b8888521f1

+ 9 - 0
data/Dockerfiles/bootstrap/main.py

@@ -1,7 +1,14 @@
 import os
 import sys
+import signal
+
+def handle_sigterm(signum, frame):
+  print("Received SIGTERM, exiting gracefully...")
+  sys.exit(0)
 
 def main():
+  signal.signal(signal.SIGTERM, handle_sigterm)
+
   container_name = os.getenv("CONTAINER_NAME")
 
   if container_name == "sogo-mailcow":
@@ -12,6 +19,8 @@ def main():
     from modules.BootstrapPostfix import Bootstrap
   elif container_name == "dovecot-mailcow":
     from modules.BootstrapDovecot import Bootstrap
+  elif container_name == "rspamd-mailcow":
+    from modules.BootstrapRspamd import Bootstrap
   else:
     print(f"No bootstrap handler for container: {container_name}", file=sys.stderr)
     sys.exit(1)

+ 24 - 0
data/Dockerfiles/bootstrap/modules/BootstrapBase.py

@@ -13,6 +13,7 @@ import redis
 import hashlib
 import json
 from pathlib import Path
+import dns.resolver
 import mysql.connector
 from jinja2 import Environment, FileSystemLoader
 
@@ -395,6 +396,29 @@ class BootstrapBase:
       result = sock.connect_ex((host, port))
       return result == 0
 
+  def resolve_docker_dns_record(self, hostname, record_type="A"):
+    """
+    Resolves DNS A or AAAA records for a given hostname.
+
+    Args:
+        hostname (str): The domain to query.
+        record_type (str): "A" for IPv4, "AAAA" for IPv6. Default is "A".
+
+    Returns:
+        list[str]: A list of resolved IP addresses.
+
+    Raises:
+        Exception: If resolution fails or no results are found.
+    """
+
+    try:
+      resolver = dns.resolver.Resolver()
+      resolver.nameservers = ["127.0.0.11"]
+      answers = resolver.resolve(hostname, record_type)
+      return [answer.to_text() for answer in answers]
+    except Exception as e:
+      raise Exception(f"Failed to resolve {record_type} record for {hostname}: {e}")
+
   def kill_proc(self, process):
     """
     Sends a SIGTERM signal to all processes matching the given name using `killall`.

+ 137 - 0
data/Dockerfiles/bootstrap/modules/BootstrapRspamd.py

@@ -0,0 +1,137 @@
+from jinja2 import Environment, FileSystemLoader
+from modules.BootstrapBase import BootstrapBase
+from pathlib import Path
+import os
+import sys
+import time
+import platform
+
+class Bootstrap(BootstrapBase):
+  def bootstrap(self):
+    # Connect to MySQL
+    self.connect_mysql()
+
+    # Connect to MySQL
+    self.connect_redis()
+
+    # get dovecot ips
+    dovecot_v4 = []
+    dovecot_v6 = []
+    while not dovecot_v4 and not dovecot_v6:
+      try:
+        dovecot_v4 = self.resolve_docker_dns_record("dovecot-mailcow", "A")
+        dovecot_v6 = self.resolve_docker_dns_record("dovecot-mailcow", "AAAA")
+      except Exception as e:
+        print(e)
+      if not dovecot_v4 and not dovecot_v6:
+        print("Waiting for Dovecot IPs...")
+        time.sleep(3)
+
+    # get rspamd ips
+    rspamd_v4 = []
+    rspamd_v6 = []
+    while not rspamd_v4 and not rspamd_v6:
+      try:
+        rspamd_v4 = self.resolve_docker_dns_record("rspamd-mailcow", "A")
+        rspamd_v6 = self.resolve_docker_dns_record("rspamd-mailcow", "AAAA")
+      except Exception:
+        print(e)
+      if not rspamd_v4 and not rspamd_v6:
+        print("Waiting for Rspamd IPs...")
+        time.sleep(3)
+
+    # wait for Services
+    services = [
+      ["php-fpm-mailcow", 9001],
+      ["php-fpm-mailcow", 9002]
+    ]
+    for service in services:
+      while not self.is_port_open(service[0], service[1]):
+        print(f"Waiting for {service[0]} on port {service[1]}...")
+        time.sleep(1)
+      print(f"Service {service[0]} on port {service[1]} is ready!")
+
+    for dir_path in ["/etc/rspamd/plugins.d", "/etc/rspamd/custom"]:
+      Path(dir_path).mkdir(parents=True, exist_ok=True)
+    for file_path in ["/etc/rspamd/rspamd.conf.local", "/etc/rspamd/rspamd.conf.override"]:
+      Path(file_path).touch(exist_ok=True)
+    self.set_permissions("/var/lib/rspamd", 0o755)
+
+
+    # Setup Jinja2 Environment and load vars
+    self.env = Environment(
+      loader=FileSystemLoader('./etc/rspamd/config_templates'),
+      keep_trailing_newline=True,
+      lstrip_blocks=True,
+      trim_blocks=True
+    )
+    extra_vars = {
+      "DOVECOT_V4": dovecot_v4[0],
+      "DOVECOT_V6": dovecot_v6[0],
+      "RSPAMD_V4": rspamd_v4[0],
+      "RSPAMD_V6": rspamd_v6[0],
+    }
+    self.env_vars = self.prepare_template_vars('/overwrites.json', extra_vars)
+
+    print("Set Timezone")
+    self.set_timezone()
+
+    print("Render config")
+    self.render_config("mailcow_networks.map.j2", "/etc/rspamd/custom/mailcow_networks.map")
+    self.render_config("dovecot_trusted.map.j2", "/etc/rspamd/custom/dovecot_trusted.map")
+    self.render_config("rspamd_trusted.map.j2", "/etc/rspamd/custom/rspamd_trusted.map")
+    self.render_config("external_services.conf.j2", "/etc/rspamd/local.d/external_services.conf")
+    self.render_config("redis.conf.j2", "/etc/rspamd/local.d/redis.conf")
+    self.render_config("dqs-rbl.conf.j2", "/etc/rspamd/custom/dqs-rbl.conf")
+    self.render_config("worker-controller-password.inc.j2", "/etc/rspamd/override.d/worker-controller-password.inc")
+
+    # Fix missing default global maps, if any
+    # These exists in mailcow UI and should not be removed
+    files = [
+      "/etc/rspamd/custom/global_mime_from_blacklist.map",
+      "/etc/rspamd/custom/global_rcpt_blacklist.map",
+      "/etc/rspamd/custom/global_smtp_from_blacklist.map",
+      "/etc/rspamd/custom/global_mime_from_whitelist.map",
+      "/etc/rspamd/custom/global_rcpt_whitelist.map",
+      "/etc/rspamd/custom/global_smtp_from_whitelist.map",
+      "/etc/rspamd/custom/bad_languages.map",
+      "/etc/rspamd/custom/sa-rules",
+      "/etc/rspamd/custom/dovecot_trusted.map",
+      "/etc/rspamd/custom/rspamd_trusted.map",
+      "/etc/rspamd/custom/mailcow_networks.map",
+      "/etc/rspamd/custom/ip_wl.map",
+      "/etc/rspamd/custom/fishy_tlds.map",
+      "/etc/rspamd/custom/bad_words.map",
+      "/etc/rspamd/custom/bad_asn.map",
+      "/etc/rspamd/custom/bad_words_de.map",
+      "/etc/rspamd/custom/bulk_header.map",
+      "/etc/rspamd/custom/bad_header.map"
+    ]
+    for file in files:
+      path = Path(file)
+      path.parent.mkdir(parents=True, exist_ok=True)
+      path.touch(exist_ok=True)
+
+    # Fix permissions
+    paths_rspamd = [
+      "/var/lib/rspamd",
+      "/etc/rspamd/local.d",
+      "/etc/rspamd/override.d",
+      "/etc/rspamd/rspamd.conf.local",
+      "/etc/rspamd/rspamd.conf.override",
+      "/etc/rspamd/plugins.d"
+    ]
+    for path in paths_rspamd:
+      self.set_owner(path, "_rspamd", "_rspamd", recursive=True)
+    self.set_owner("/etc/rspamd/custom", "_rspamd", "_rspamd")
+    self.set_permissions("/etc/rspamd/custom", 0o755)
+
+    custom_path = Path("/etc/rspamd/custom")
+    for child in custom_path.iterdir():
+      if child.is_file():
+        self.set_owner(child, 82, 82)
+        self.set_permissions(child, 0o644)
+
+    # Provide additional lua modules
+    arch = platform.machine()
+    self.run_command(["ln", "-s", f"/usr/lib/{arch}-linux-gnu/liblua5.1-cjson.so.0.0.0", "/usr/lib/rspamd/cjson.so"], check=False)

+ 2 - 1
data/Dockerfiles/dovecot/Dockerfile

@@ -118,7 +118,8 @@ RUN addgroup -g 5000 vmail \
 RUN pip install  --break-system-packages \
   mysql-connector-python \
   jinja2 \
-  redis
+  redis \
+  dnspython
 
 
 COPY data/Dockerfiles/bootstrap /bootstrap

+ 2 - 1
data/Dockerfiles/postfix/Dockerfile

@@ -43,7 +43,8 @@ RUN groupadd -g 102 postfix \
 RUN pip install  --break-system-packages \
   mysql-connector-python \
   jinja2 \
-	redis
+	redis \
+	dnspython
 
 COPY data/Dockerfiles/bootstrap /bootstrap
 COPY data/Dockerfiles/postfix/supervisord.conf /etc/supervisor/supervisord.conf

+ 15 - 7
data/Dockerfiles/rspamd/Dockerfile

@@ -14,10 +14,11 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
   dnsutils \
   netcat-traditional \
   wget \
-  redis-tools \ 
-  procps \ 
+  redis-tools \
+  procps \
   nano \
   lua-cjson \
+  python3 python3-pip \
   && arch=$(arch | sed s/aarch64/arm64/ | sed s/x86_64/amd64/) \
   && wget -P /tmp https://rspamd.com/apt-stable/pool/main/r/rspamd/${RSPAMD_VER}~${CODENAME}_${arch}.deb\
   && apt install -y /tmp/${RSPAMD_VER}~${CODENAME}_${arch}.deb \
@@ -29,12 +30,19 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
   && echo 'alias ll="ls -la --color"' >> ~/.bashrc \
   && sed -i 's/#analysis_keyword_table > 0/analysis_cat_table.macro_exist == "M"/g' /usr/share/rspamd/lualib/lua_scanners/oletools.lua
 
-COPY settings.conf /etc/rspamd/settings.conf
-COPY set_worker_password.sh /set_worker_password.sh
-COPY docker-entrypoint.sh /docker-entrypoint.sh
+RUN pip install  --break-system-packages \
+  mysql-connector-python \
+  jinja2 \
+  redis \
+  dnspython
 
-ENTRYPOINT ["/docker-entrypoint.sh"]
 
-STOPSIGNAL SIGTERM
+COPY data/Dockerfiles/bootstrap /bootstrap
+COPY data/Dockerfiles/rspamd/settings.conf /etc/rspamd/settings.conf
+COPY data/Dockerfiles/rspamd/set_worker_password.sh /set_worker_password.sh
+COPY data/Dockerfiles/rspamd/docker-entrypoint.sh /docker-entrypoint.sh
+
 
+STOPSIGNAL SIGTERM
+ENTRYPOINT ["/docker-entrypoint.sh"]
 CMD ["/usr/bin/rspamd", "-f", "-u", "_rspamd", "-g", "_rspamd"]

+ 6 - 322
data/Dockerfiles/rspamd/docker-entrypoint.sh

@@ -1,144 +1,5 @@
 #!/bin/bash
 
-until nc phpfpm 9001 -z; do
-  echo "Waiting for PHP on port 9001..."
-  sleep 3
-done
-
-until nc phpfpm 9002 -z; do
-  echo "Waiting for PHP on port 9002..."
-  sleep 3
-done
-
-mkdir -p /etc/rspamd/plugins.d \
-  /etc/rspamd/custom
-
-touch /etc/rspamd/rspamd.conf.local \
-  /etc/rspamd/rspamd.conf.override
-
-chmod 755 /var/lib/rspamd
-
-
-[[ ! -f /etc/rspamd/override.d/worker-controller-password.inc ]] && echo '# Autogenerated by mailcow' > /etc/rspamd/override.d/worker-controller-password.inc
-
-echo ${IPV4_NETWORK}.0/24 > /etc/rspamd/custom/mailcow_networks.map
-echo ${IPV6_NETWORK} >> /etc/rspamd/custom/mailcow_networks.map
-
-DOVECOT_V4=
-DOVECOT_V6=
-until [[ ! -z ${DOVECOT_V4} ]]; do
-  DOVECOT_V4=$(dig a dovecot +short)
-  DOVECOT_V6=$(dig aaaa dovecot +short)
-  [[ ! -z ${DOVECOT_V4} ]] && break;
-  echo "Waiting for Dovecot..."
-  sleep 3
-done
-echo ${DOVECOT_V4}/32 > /etc/rspamd/custom/dovecot_trusted.map
-if [[ ! -z ${DOVECOT_V6} ]]; then
-  echo ${DOVECOT_V6}/128 >> /etc/rspamd/custom/dovecot_trusted.map
-fi
-
-RSPAMD_V4=
-RSPAMD_V6=
-until [[ ! -z ${RSPAMD_V4} ]]; do
-  RSPAMD_V4=$(dig a rspamd +short)
-  RSPAMD_V6=$(dig aaaa rspamd +short)
-  [[ ! -z ${RSPAMD_V4} ]] && break;
-  echo "Waiting for Rspamd..."
-  sleep 3
-done
-echo ${RSPAMD_V4}/32 > /etc/rspamd/custom/rspamd_trusted.map
-if [[ ! -z ${RSPAMD_V6} ]]; then
-  echo ${RSPAMD_V6}/128 >> /etc/rspamd/custom/rspamd_trusted.map
-fi
-
-if [[ ! -z ${REDIS_SLAVEOF_IP} ]]; then
-  cat <<EOF > /etc/rspamd/local.d/redis.conf
-read_servers = "redis:6379";
-write_servers = "${REDIS_SLAVEOF_IP}:${REDIS_SLAVEOF_PORT}";
-password = "${REDISPASS}";
-timeout = 10;
-EOF
-  until [[ $(redis-cli -h redis-mailcow -a ${REDISPASS} --no-auth-warning PING) == "PONG" ]]; do
-    echo "Waiting for Redis @redis-mailcow..."
-    sleep 2
-  done
-  until [[ $(redis-cli -h ${REDIS_SLAVEOF_IP} -p ${REDIS_SLAVEOF_PORT} -a ${REDISPASS} --no-auth-warning PING) == "PONG" ]]; do
-    echo "Waiting for Redis @${REDIS_SLAVEOF_IP}..."
-    sleep 2
-  done
-  redis-cli -h redis-mailcow -a ${REDISPASS} --no-auth-warning SLAVEOF ${REDIS_SLAVEOF_IP} ${REDIS_SLAVEOF_PORT}
-else
-  cat <<EOF > /etc/rspamd/local.d/redis.conf
-servers = "redis:6379";
-password = "${REDISPASS}";
-timeout = 10;
-EOF
-  until [[ $(redis-cli -h redis-mailcow -a ${REDISPASS} --no-auth-warning PING) == "PONG" ]]; do
-    echo "Waiting for Redis slave..."
-    sleep 2
-  done
-  redis-cli -h redis-mailcow -a ${REDISPASS} --no-auth-warning SLAVEOF NO ONE
-fi
-
-if [[ "${SKIP_OLEFY}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
-  if [[ -f /etc/rspamd/local.d/external_services.conf ]]; then
-    rm /etc/rspamd/local.d/external_services.conf
-  fi
-else
-  cat <<EOF > /etc/rspamd/local.d/external_services.conf
-oletools {
-  # default olefy settings
-  servers = "olefy:10055";
-  # needs to be set explicitly for Rspamd < 1.9.5
-  scan_mime_parts = true;
-  # mime-part regex matching in content-type or filename
-  # block all macros
-  extended = true;
-  max_size = 3145728;
-  timeout = 20.0;
-  retransmits = 1;
-}
-EOF
-fi
-
-# Provide additional lua modules
-ln -s /usr/lib/$(uname -m)-linux-gnu/liblua5.1-cjson.so.0.0.0 /usr/lib/rspamd/cjson.so
-
-chown -R _rspamd:_rspamd /var/lib/rspamd \
-  /etc/rspamd/local.d \
-  /etc/rspamd/override.d \
-  /etc/rspamd/rspamd.conf.local \
-  /etc/rspamd/rspamd.conf.override \
-  /etc/rspamd/plugins.d
-
-# Fix missing default global maps, if any
-# These exists in mailcow UI and should not be removed
-touch /etc/rspamd/custom/global_mime_from_blacklist.map \
-  /etc/rspamd/custom/global_rcpt_blacklist.map \
-  /etc/rspamd/custom/global_smtp_from_blacklist.map \
-  /etc/rspamd/custom/global_mime_from_whitelist.map \
-  /etc/rspamd/custom/global_rcpt_whitelist.map \
-  /etc/rspamd/custom/global_smtp_from_whitelist.map \
-  /etc/rspamd/custom/bad_languages.map \
-  /etc/rspamd/custom/sa-rules \
-  /etc/rspamd/custom/dovecot_trusted.map \
-  /etc/rspamd/custom/rspamd_trusted.map \
-  /etc/rspamd/custom/mailcow_networks.map \
-  /etc/rspamd/custom/ip_wl.map \
-  /etc/rspamd/custom/fishy_tlds.map \
-  /etc/rspamd/custom/bad_words.map \
-  /etc/rspamd/custom/bad_asn.map \
-  /etc/rspamd/custom/bad_words_de.map \
-  /etc/rspamd/custom/bulk_header.map \
-  /etc/rspamd/custom/bad_header.map
-
-# www-data (82) group needs to write to these files
-chown _rspamd:_rspamd /etc/rspamd/custom/
-chmod 0755 /etc/rspamd/custom/.
-chown -R 82:82 /etc/rspamd/custom/*
-chmod 644 -R /etc/rspamd/custom/*
-
 # Run hooks
 for file in /hooks/*; do
   if [ -x "${file}" ]; then
@@ -147,190 +8,13 @@ for file in /hooks/*; do
   fi
 done
 
-# If DQS KEY is set in mailcow.conf add Spamhaus DQS RBLs
-if [[ ! -z ${SPAMHAUS_DQS_KEY} ]]; then
-    cat <<EOF > /etc/rspamd/custom/dqs-rbl.conf
-  # Autogenerated by mailcow. DO NOT TOUCH!
-    spamhaus {
-        rbl = "${SPAMHAUS_DQS_KEY}.zen.dq.spamhaus.net";
-        from = false;
-    }
-    spamhaus_from {
-        from = true;
-        received = false;
-        rbl = "${SPAMHAUS_DQS_KEY}.zen.dq.spamhaus.net";
-        returncodes {
-          SPAMHAUS_ZEN = [ "127.0.0.2", "127.0.0.3", "127.0.0.4", "127.0.0.5", "127.0.0.6", "127.0.0.7", "127.0.0.9", "127.0.0.10", "127.0.0.11" ];
-        }
-    }
-    spamhaus_authbl_received {
-        # Check if the sender client is listed in AuthBL (AuthBL is *not* part of ZEN)
-        rbl = "${SPAMHAUS_DQS_KEY}.authbl.dq.spamhaus.net";
-        from = false;
-        received = true;
-        ipv6 = true;
-        returncodes {
-          SH_AUTHBL_RECEIVED = "127.0.0.20"
-        }
-    }
-    spamhaus_dbl {
-        # Add checks on the HELO string
-        rbl = "${SPAMHAUS_DQS_KEY}.dbl.dq.spamhaus.net";
-        helo = true;
-        rdns = true;
-        dkim = true;
-        disable_monitoring = true;
-        returncodes {
-            RBL_DBL_SPAM = "127.0.1.2";
-            RBL_DBL_PHISH = "127.0.1.4";
-            RBL_DBL_MALWARE = "127.0.1.5";
-            RBL_DBL_BOTNET = "127.0.1.6";
-            RBL_DBL_ABUSED_SPAM = "127.0.1.102";
-            RBL_DBL_ABUSED_PHISH = "127.0.1.104";
-            RBL_DBL_ABUSED_MALWARE = "127.0.1.105";
-            RBL_DBL_ABUSED_BOTNET = "127.0.1.106";
-            RBL_DBL_DONT_QUERY_IPS = "127.0.1.255";
-        }
-    }
-    spamhaus_dbl_fullurls {
-        ignore_defaults = true;
-        no_ip = true;
-        rbl = "${SPAMHAUS_DQS_KEY}.dbl.dq.spamhaus.net";
-        selector = 'urls:get_host'
-        disable_monitoring = true;
-        returncodes {
-            DBLABUSED_SPAM_FULLURLS = "127.0.1.102";
-            DBLABUSED_PHISH_FULLURLS = "127.0.1.104";
-            DBLABUSED_MALWARE_FULLURLS = "127.0.1.105";
-            DBLABUSED_BOTNET_FULLURLS = "127.0.1.106";
-        }
-    }
-    spamhaus_zrd {
-        # Add checks on the HELO string also for DQS
-        rbl = "${SPAMHAUS_DQS_KEY}.zrd.dq.spamhaus.net";
-        helo = true;
-        rdns = true;
-        dkim = true;
-        disable_monitoring = true;
-        returncodes {
-            RBL_ZRD_VERY_FRESH_DOMAIN = ["127.0.2.2", "127.0.2.3", "127.0.2.4"];
-            RBL_ZRD_FRESH_DOMAIN = [
-              "127.0.2.5", "127.0.2.6", "127.0.2.7", "127.0.2.8", "127.0.2.9", "127.0.2.10", "127.0.2.11", "127.0.2.12", "127.0.2.13", "127.0.2.14", "127.0.2.15", "127.0.2.16", "127.0.2.17", "127.0.2.18", "127.0.2.19", "127.0.2.20", "127.0.2.21", "127.0.2.22", "127.0.2.23", "127.0.2.24"
-            ];
-            RBL_ZRD_DONT_QUERY_IPS = "127.0.2.255";
-        }
-    }
-    "SPAMHAUS_ZEN_URIBL" {
-      enabled = true;
-      rbl = "${SPAMHAUS_DQS_KEY}.zen.dq.spamhaus.net";
-      resolve_ip = true;
-      checks = ['urls'];
-      replyto = true;
-      emails = true;
-      ipv4 = true;
-      ipv6 = true;
-      emails_domainonly = true;
-      returncodes {
-        URIBL_SBL = "127.0.0.2";
-        URIBL_SBL_CSS = "127.0.0.3";
-        URIBL_XBL = ["127.0.0.4", "127.0.0.5", "127.0.0.6", "127.0.0.7"];
-        URIBL_PBL = ["127.0.0.10", "127.0.0.11"];
-        URIBL_DROP = "127.0.0.9";
-      }
-    }
-    SH_EMAIL_DBL {
-      ignore_defaults = true;
-      replyto = true;
-      emails_domainonly = true;
-      disable_monitoring = true;
-      rbl = "${SPAMHAUS_DQS_KEY}.dbl.dq.spamhaus.net";
-      returncodes = {
-        SH_EMAIL_DBL = [
-          "127.0.1.2",
-          "127.0.1.4",
-          "127.0.1.5",
-          "127.0.1.6"
-        ];
-        SH_EMAIL_DBL_ABUSED = [
-          "127.0.1.102",
-          "127.0.1.104",
-          "127.0.1.105",
-          "127.0.1.106"
-        ];
-        SH_EMAIL_DBL_DONT_QUERY_IPS = [ "127.0.1.255" ];
-      }
-    }
-    SH_EMAIL_ZRD {
-      ignore_defaults = true;
-      replyto = true;
-      emails_domainonly = true;
-      disable_monitoring = true;
-      rbl = "${SPAMHAUS_DQS_KEY}.zrd.dq.spamhaus.net";
-      returncodes = {
-        SH_EMAIL_ZRD_VERY_FRESH_DOMAIN = ["127.0.2.2", "127.0.2.3", "127.0.2.4"];
-        SH_EMAIL_ZRD_FRESH_DOMAIN = [
-          "127.0.2.5", "127.0.2.6", "127.0.2.7", "127.0.2.8", "127.0.2.9", "127.0.2.10", "127.0.2.11", "127.0.2.12", "127.0.2.13", "127.0.2.14", "127.0.2.15", "127.0.2.16", "127.0.2.17", "127.0.2.18", "127.0.2.19", "127.0.2.20", "127.0.2.21", "127.0.2.22", "127.0.2.23", "127.0.2.24"
-        ];
-        SH_EMAIL_ZRD_DONT_QUERY_IPS = [ "127.0.2.255" ];
-      }
-    }
-    "DBL" {
-        # override the defaults for DBL defined in modules.d/rbl.conf
-        rbl = "${SPAMHAUS_DQS_KEY}.dbl.dq.spamhaus.net";
-        disable_monitoring = true;
-    }
-    "ZRD" {
-        ignore_defaults = true;
-        rbl = "${SPAMHAUS_DQS_KEY}.zrd.dq.spamhaus.net";
-        no_ip = true;
-        dkim = true;
-        emails = true;
-        emails_domainonly = true;
-        urls = true;
-        returncodes = {
-            ZRD_VERY_FRESH_DOMAIN = ["127.0.2.2", "127.0.2.3", "127.0.2.4"];
-            ZRD_FRESH_DOMAIN = ["127.0.2.5", "127.0.2.6", "127.0.2.7", "127.0.2.8", "127.0.2.9", "127.0.2.10", "127.0.2.11", "127.0.2.12", "127.0.2.13", "127.0.2.14", "127.0.2.15", "127.0.2.16", "127.0.2.17", "127.0.2.18", "127.0.2.19", "127.0.2.20", "127.0.2.21", "127.0.2.22", "127.0.2.23", "127.0.2.24"];
-        }
-    }
-    spamhaus_sbl_url {
-        ignore_defaults = true
-        rbl = "${SPAMHAUS_DQS_KEY}.sbl.dq.spamhaus.net";
-        checks = ['urls'];
-        disable_monitoring = true;
-        returncodes {
-            SPAMHAUS_SBL_URL = "127.0.0.2";
-        }
-    }
-
-    SH_HBL_EMAIL {
-      ignore_defaults = true;
-      rbl = "_email.${SPAMHAUS_DQS_KEY}.hbl.dq.spamhaus.net";
-      emails_domainonly = false;
-      selector = "from('smtp').lower;from('mime').lower";
-      ignore_whitelist = true;
-      checks = ['emails', 'replyto'];
-      hash = "sha1";
-      returncodes = {
-        SH_HBL_EMAIL = [
-          "127.0.3.2"
-        ];
-      }
-    }
+python3 -u /bootstrap/main.py
+BOOTSTRAP_EXIT_CODE=$?
 
-    spamhaus_dqs_hbl {
-      symbol = "HBL_FILE_UNKNOWN";
-      rbl = "_file.${SPAMHAUS_DQS_KEY}.hbl.dq.spamhaus.net.";
-      selector = "attachments('rbase32', 'sha256')";
-      ignore_whitelist = true;
-      ignore_defaults = true;
-      returncodes {
-        SH_HBL_FILE_MALICIOUS = "127.0.3.10";
-        SH_HBL_FILE_SUSPICIOUS = "127.0.3.15";
-      }
-    }
-EOF
-else
-  rm -rf /etc/rspamd/custom/dqs-rbl.conf
+if [ $BOOTSTRAP_EXIT_CODE -ne 0 ]; then
+  echo "Bootstrap failed with exit code $BOOTSTRAP_EXIT_CODE. Not starting Rspamd."
+  exit $BOOTSTRAP_EXIT_CODE
 fi
 
+echo "Bootstrap succeeded. Starting Rspamd..."
 exec "$@"

+ 2 - 1
data/Dockerfiles/sogo/Dockerfile

@@ -46,7 +46,8 @@ RUN echo "Building from repository $SOGO_DEBIAN_REPOSITORY" \
 RUN pip install  --break-system-packages \
   mysql-connector-python \
   jinja2 \
-  redis
+  redis \
+  dnspython
 
 
 COPY data/Dockerfiles/bootstrap /bootstrap

+ 2 - 0
data/conf/rspamd/config_templates/dovecot_trusted.map.j2

@@ -0,0 +1,2 @@
+{{ DOVECOT_V4 }}/32
+{{ DOVECOT_V6 }}/128

+ 179 - 0
data/conf/rspamd/config_templates/dqs-rbl.conf.j2

@@ -0,0 +1,179 @@
+{% if SPAMHAUS_DQS_KEY %}
+spamhaus {
+    rbl = "{{ SPAMHAUS_DQS_KEY }}.zen.dq.spamhaus.net";
+    from = false;
+}
+spamhaus_from {
+    from = true;
+    received = false;
+    rbl = "{{ SPAMHAUS_DQS_KEY }}.zen.dq.spamhaus.net";
+    returncodes {
+      SPAMHAUS_ZEN = [ "127.0.0.2", "127.0.0.3", "127.0.0.4", "127.0.0.5", "127.0.0.6", "127.0.0.7", "127.0.0.9", "127.0.0.10", "127.0.0.11" ];
+    }
+}
+spamhaus_authbl_received {
+    # Check if the sender client is listed in AuthBL (AuthBL is *not* part of ZEN)
+    rbl = "{{ SPAMHAUS_DQS_KEY }}.authbl.dq.spamhaus.net";
+    from = false;
+    received = true;
+    ipv6 = true;
+    returncodes {
+      SH_AUTHBL_RECEIVED = "127.0.0.20"
+    }
+}
+spamhaus_dbl {
+    # Add checks on the HELO string
+    rbl = "{{ SPAMHAUS_DQS_KEY }}.dbl.dq.spamhaus.net";
+    helo = true;
+    rdns = true;
+    dkim = true;
+    disable_monitoring = true;
+    returncodes {
+        RBL_DBL_SPAM = "127.0.1.2";
+        RBL_DBL_PHISH = "127.0.1.4";
+        RBL_DBL_MALWARE = "127.0.1.5";
+        RBL_DBL_BOTNET = "127.0.1.6";
+        RBL_DBL_ABUSED_SPAM = "127.0.1.102";
+        RBL_DBL_ABUSED_PHISH = "127.0.1.104";
+        RBL_DBL_ABUSED_MALWARE = "127.0.1.105";
+        RBL_DBL_ABUSED_BOTNET = "127.0.1.106";
+        RBL_DBL_DONT_QUERY_IPS = "127.0.1.255";
+    }
+}
+spamhaus_dbl_fullurls {
+    ignore_defaults = true;
+    no_ip = true;
+    rbl = "{{ SPAMHAUS_DQS_KEY }}.dbl.dq.spamhaus.net";
+    selector = 'urls:get_host'
+    disable_monitoring = true;
+    returncodes {
+        DBLABUSED_SPAM_FULLURLS = "127.0.1.102";
+        DBLABUSED_PHISH_FULLURLS = "127.0.1.104";
+        DBLABUSED_MALWARE_FULLURLS = "127.0.1.105";
+        DBLABUSED_BOTNET_FULLURLS = "127.0.1.106";
+    }
+}
+spamhaus_zrd {
+    # Add checks on the HELO string also for DQS
+    rbl = "{{ SPAMHAUS_DQS_KEY }}.zrd.dq.spamhaus.net";
+    helo = true;
+    rdns = true;
+    dkim = true;
+    disable_monitoring = true;
+    returncodes {
+        RBL_ZRD_VERY_FRESH_DOMAIN = ["127.0.2.2", "127.0.2.3", "127.0.2.4"];
+        RBL_ZRD_FRESH_DOMAIN = [
+          "127.0.2.5", "127.0.2.6", "127.0.2.7", "127.0.2.8", "127.0.2.9", "127.0.2.10", "127.0.2.11", "127.0.2.12", "127.0.2.13", "127.0.2.14", "127.0.2.15", "127.0.2.16", "127.0.2.17", "127.0.2.18", "127.0.2.19", "127.0.2.20", "127.0.2.21", "127.0.2.22", "127.0.2.23", "127.0.2.24"
+        ];
+        RBL_ZRD_DONT_QUERY_IPS = "127.0.2.255";
+    }
+}
+"SPAMHAUS_ZEN_URIBL" {
+  enabled = true;
+  rbl = "{{ SPAMHAUS_DQS_KEY }}.zen.dq.spamhaus.net";
+  resolve_ip = true;
+  checks = ['urls'];
+  replyto = true;
+  emails = true;
+  ipv4 = true;
+  ipv6 = true;
+  emails_domainonly = true;
+  returncodes {
+    URIBL_SBL = "127.0.0.2";
+    URIBL_SBL_CSS = "127.0.0.3";
+    URIBL_XBL = ["127.0.0.4", "127.0.0.5", "127.0.0.6", "127.0.0.7"];
+    URIBL_PBL = ["127.0.0.10", "127.0.0.11"];
+    URIBL_DROP = "127.0.0.9";
+  }
+}
+SH_EMAIL_DBL {
+  ignore_defaults = true;
+  replyto = true;
+  emails_domainonly = true;
+  disable_monitoring = true;
+  rbl = "{{ SPAMHAUS_DQS_KEY }}.dbl.dq.spamhaus.net";
+  returncodes = {
+    SH_EMAIL_DBL = [
+      "127.0.1.2",
+      "127.0.1.4",
+      "127.0.1.5",
+      "127.0.1.6"
+    ];
+    SH_EMAIL_DBL_ABUSED = [
+      "127.0.1.102",
+      "127.0.1.104",
+      "127.0.1.105",
+      "127.0.1.106"
+    ];
+    SH_EMAIL_DBL_DONT_QUERY_IPS = [ "127.0.1.255" ];
+  }
+}
+SH_EMAIL_ZRD {
+  ignore_defaults = true;
+  replyto = true;
+  emails_domainonly = true;
+  disable_monitoring = true;
+  rbl = "{{ SPAMHAUS_DQS_KEY }}.zrd.dq.spamhaus.net";
+  returncodes = {
+    SH_EMAIL_ZRD_VERY_FRESH_DOMAIN = ["127.0.2.2", "127.0.2.3", "127.0.2.4"];
+    SH_EMAIL_ZRD_FRESH_DOMAIN = [
+      "127.0.2.5", "127.0.2.6", "127.0.2.7", "127.0.2.8", "127.0.2.9", "127.0.2.10", "127.0.2.11", "127.0.2.12", "127.0.2.13", "127.0.2.14", "127.0.2.15", "127.0.2.16", "127.0.2.17", "127.0.2.18", "127.0.2.19", "127.0.2.20", "127.0.2.21", "127.0.2.22", "127.0.2.23", "127.0.2.24"
+    ];
+    SH_EMAIL_ZRD_DONT_QUERY_IPS = [ "127.0.2.255" ];
+  }
+}
+"DBL" {
+    # override the defaults for DBL defined in modules.d/rbl.conf
+    rbl = "{{ SPAMHAUS_DQS_KEY }}.dbl.dq.spamhaus.net";
+    disable_monitoring = true;
+}
+"ZRD" {
+    ignore_defaults = true;
+    rbl = "{{ SPAMHAUS_DQS_KEY }}.zrd.dq.spamhaus.net";
+    no_ip = true;
+    dkim = true;
+    emails = true;
+    emails_domainonly = true;
+    urls = true;
+    returncodes = {
+        ZRD_VERY_FRESH_DOMAIN = ["127.0.2.2", "127.0.2.3", "127.0.2.4"];
+        ZRD_FRESH_DOMAIN = ["127.0.2.5", "127.0.2.6", "127.0.2.7", "127.0.2.8", "127.0.2.9", "127.0.2.10", "127.0.2.11", "127.0.2.12", "127.0.2.13", "127.0.2.14", "127.0.2.15", "127.0.2.16", "127.0.2.17", "127.0.2.18", "127.0.2.19", "127.0.2.20", "127.0.2.21", "127.0.2.22", "127.0.2.23", "127.0.2.24"];
+    }
+}
+spamhaus_sbl_url {
+    ignore_defaults = true
+    rbl = "{{ SPAMHAUS_DQS_KEY }}.sbl.dq.spamhaus.net";
+    checks = ['urls'];
+    disable_monitoring = true;
+    returncodes {
+        SPAMHAUS_SBL_URL = "127.0.0.2";
+    }
+}
+
+SH_HBL_EMAIL {
+  ignore_defaults = true;
+  rbl = "_email.{{ SPAMHAUS_DQS_KEY }}.hbl.dq.spamhaus.net";
+  emails_domainonly = false;
+  selector = "from('smtp').lower;from('mime').lower";
+  ignore_whitelist = true;
+  checks = ['emails', 'replyto'];
+  hash = "sha1";
+  returncodes = {
+    SH_HBL_EMAIL = [
+      "127.0.3.2"
+    ];
+  }
+}
+
+spamhaus_dqs_hbl {
+  symbol = "HBL_FILE_UNKNOWN";
+  rbl = "_file.{{ SPAMHAUS_DQS_KEY }}.hbl.dq.spamhaus.net.";
+  selector = "attachments('rbase32', 'sha256')";
+  ignore_whitelist = true;
+  ignore_defaults = true;
+  returncodes {
+    SH_HBL_FILE_MALICIOUS = "127.0.3.10";
+    SH_HBL_FILE_SUSPICIOUS = "127.0.3.15";
+  }
+}
+{% endif %}

+ 16 - 0
data/conf/rspamd/config_templates/external_services.conf.j2

@@ -0,0 +1,16 @@
+{% if SKIP_OLEFY|lower in ['y', 'yes'] %}
+# OLEFY deactivated
+{% else %}
+oletools {
+  # default olefy settings
+  servers = "olefy-mailcow:10055";
+  # needs to be set explicitly for Rspamd < 1.9.5
+  scan_mime_parts = true;
+  # mime-part regex matching in content-type or filename
+  # block all macros
+  extended = true;
+  max_size = 3145728;
+  timeout = 20.0;
+  retransmits = 1;
+}
+{% endif %}

+ 2 - 0
data/conf/rspamd/config_templates/mailcow_networks.map.j2

@@ -0,0 +1,2 @@
+{{ IPV4_NETWORK }}.0/24
+{{ IPV6_NETWORK }}

+ 10 - 0
data/conf/rspamd/config_templates/redis.conf.j2

@@ -0,0 +1,10 @@
+{% if REDIS_SLAVEOF_IP and REDIS_SLAVEOF_PORT %}
+read_servers = "redis-mailcow:6379";
+write_servers = "{{ REDIS_SLAVEOF_IP }}:{{ REDIS_SLAVEOF_PORT }}";
+password = "{{ REDISPASS }}";
+timeout = 10;
+{% else %}
+servers = "redis-mailcow:6379";
+password = "{{ REDISPASS }}";
+timeout = 10;
+{% endif %}

+ 2 - 0
data/conf/rspamd/config_templates/rspamd_trusted.map.j2

@@ -0,0 +1,2 @@
+{{ RSPAMD_V4 }}/32
+{{ RSPAMD_V6 }}/128

+ 1 - 0
data/conf/rspamd/config_templates/worker-controller-password.inc.j2

@@ -0,0 +1 @@
+# Autogenerated by mailcow

+ 7 - 1
docker-compose.yml

@@ -84,12 +84,16 @@ services:
             - clamd
 
     rspamd-mailcow:
-      image: ghcr.io/mailcow/rspamd:2.2
+      image: ghcr.io/mailcow/rspamd:nightly-19052025
       stop_grace_period: 30s
       depends_on:
         - dovecot-mailcow
         - clamd-mailcow
       environment:
+        - CONTAINER_NAME=rspamd-mailcow
+        - DBNAME=${DBNAME}
+        - DBUSER=${DBUSER}
+        - DBPASS=${DBPASS}
         - TZ=${TZ}
         - IPV4_NETWORK=${IPV4_NETWORK:-172.22.1}
         - IPV6_NETWORK=${IPV6_NETWORK:-fd4d:6169:6c63:6f77::/64}
@@ -99,6 +103,7 @@ services:
         - SPAMHAUS_DQS_KEY=${SPAMHAUS_DQS_KEY:-}
       volumes:
         - ./data/hooks/rspamd:/hooks:Z
+        - ./data/conf/rspamd/config_templates/:/etc/rspamd/config_templates:z
         - ./data/conf/rspamd/custom/:/etc/rspamd/custom:z
         - ./data/conf/rspamd/override.d/:/etc/rspamd/override.d:Z
         - ./data/conf/rspamd/local.d/:/etc/rspamd/local.d:Z
@@ -106,6 +111,7 @@ services:
         - ./data/conf/rspamd/lua/:/etc/rspamd/lua/:ro,Z
         - ./data/conf/rspamd/rspamd.conf.local:/etc/rspamd/rspamd.conf.local:Z
         - ./data/conf/rspamd/rspamd.conf.override:/etc/rspamd/rspamd.conf.override:Z
+        - mysql-socket-vol-1:/var/run/mysqld/
         - rspamd-vol-1:/var/lib/rspamd
       restart: always
       hostname: rspamd