On 9 Jan 2026, at 12:31, Aki Tuomi via dovecot <dovecot@dovecot.org> wrote:
On 09/01/2026 12:08 EET Lefteris Tsintjelis via dovecot <dovecot@dovecot.org> wrote:
Hi,
Is there a way to block with RBLs? I already have a really good and very trustworthy and accurate internal one that works extremely well and fast with my SMTP servers for years now. Is there a way to apply the same RBL to dovecot? Logs are really going crazy as they stopped with SMTP and started with IMAP for a while now since dovecot is wide open to these attacks. Anvil does not seem to do much here. I am looking for solutions other than fail2ban or anything similar to this.
Lefteris
You can use auth_policy_server settings to configure an external service for this, please see e.g. https://github.com/PowerDNS/weakforced/ as an example of such service.
Aki
I think the following is very light in resources by using POSIX and can be used with IPv4 and IPv6 and does exactly what I want to check my single trusted DNSBL.
This should be before any other passdb
passdb { args = /…path to.../local.rbl.sh driver = checkpassword result_success = continue result_failure = return-fail }
The following must be executable:
cat local.rbl.sh
#!/bin/sh
--- CONFIGURATION ---
DNSBL_ZONE=“mysingle.trusted.dnsbl" LOGGING=1
Log helper
log_msg() { if [ "$LOGGING" -eq 1 ]; then logger -p mail.notice -t dovecot-dnsbl "$1" fi }
1. CONSUME CREDENTIALS (Read FD 3 if available)
user="" if [ -e /dev/fd/3 ]; then data=$(dd if=/dev/fd/3 bs=4096 count=1 2>/dev/null) if [ -n "$data" ]; then user=$(echo "$data" | awk -F '\0' '{print $1}') fi fi
2. GET IP ADDRESS
REMOTE_IP="${TCPREMOTEIP:-$IP}"
If running manually for a test, allow passing IP as 2nd arg
usage: ./script.sh dummy_binary 1.2.3.4
if [ -z "$REMOTE_IP" ] && [ -n "$2" ]; then REMOTE_IP="$2" fi
if [ -z "$REMOTE_IP" ]; then # No IP found, allow access (e.g. internal socket or manual run without args) log_msg "Allowing (no IP provided)${user:+ for user $user}" if [ -x "$1" ]; then exec "$1" else printf '\0' exit 0 fi fi
3. DETECT IP VERSION AND PREPARE QUERY
if echo "$REMOTE_IP" | grep -q ":"; then # --- IPv6 LOGIC (AWK) --- REVERSED_IP=$(echo "$REMOTE_IP" | awk ' BEGIN { FS=":"; OFS="" } { if (index($0, "::") > 0) { match($0, /::/); head = substr($0, 1, RSTART-1); tail = substr($0, RSTART+2); n_head = (head == "") ? 0 : split(head, h, ":"); n_tail = (tail == "") ? 0 : split(tail, t, ":"); missing = 8 - n_head - n_tail; full = head; for(i=0; i<missing; i++) full = full ":0000"; if (tail != "") { if (full != "") full = full ":" tail; else full = tail; } } else { full = $0; } split(full, groups, ":"); res = ""; for (i=8; i>=1; i--) { val = groups[i]; while (length(val) < 4) val = "0" val; for (j=4; j>=1; j--) res = res "." substr(val, j, 1); } print substr(res, 2); }') else # --- IPv4 LOGIC --- REVERSED_IP=$(echo "$REMOTE_IP" | awk -F. '{print $4"."$3"."$2"."$1}') fi
4. PERFORM LOOKUP
if [ -z "$REVERSED_IP" ]; then # Invalid IP, allow log_msg "Allowing invalid IP $REMOTE_IP${user:+ for user $user}" if [ -x "$1" ]; then exec "$1" else printf '\0' exit 0 fi fi
QUERY="${REVERSED_IP}.${DNSBL_ZONE}"
if host -t A "$QUERY" >/dev/null 2>&1; then log_msg "REJECTED: $REMOTE_IP is listed in $DNSBL_ZONE${user:+ for user $user}" exit 1 fi
5. SUCCESS HAND-OFF
log_msg "Allowing $REMOTE_IP (Clean)${user:+ for user $user}" if [ -x "$1" ]; then exec "$1" else printf '\0' exit 0 fi
Lefteris
On 9 Jan 2026, at 12:31, Aki Tuomi via dovecot <dovecot@dovecot.org>
wrote:
On 09/01/2026 12:08 EET Lefteris Tsintjelis via dovecot
<dovecot@dovecot.org> wrote:
Hi,
Is there a way to block with RBLs? I already have a really good and
very trustworthy and accurate internal one that works extremely well
and fast with my SMTP servers for years now. Is there a way to apply
the same RBL to dovecot? Logs are really going crazy as they stopped
with SMTP and started with IMAP for a while now since dovecot is wide
open to these attacks. Anvil does not seem to do much here. I am
looking for solutions other than fail2ban or anything similar to this.
Lefteris
You can use auth_policy_server settings to configure an external service
for this, please see e.g. https://github.com/PowerDNS/weakforced/ as an
example of such service.
Aki
I think the following is very light in resources by using POSIX and can be used with IPv4 and IPv6 and does exactly what I want to check my single trusted DNSBL.
This should be before any other passdb
passdb {
args = /...path to.../local.rbl.sh
driver = checkpassword
result_success = continue
result_failure = return-fail
}
The following must be executable:
cat local.rbl.sh
#!/bin/sh
--- CONFIGURATION ---
DNSBL_ZONE="mysingle.trusted.dnsbl"
LOGGING=1
Log helper
log_msg() {
if [ "$LOGGING" -eq 1 ]; then
logger -p mail.notice -t dovecot-dnsbl "$1"
fi
}
1. CONSUME CREDENTIALS (Read FD 3 if available)
user=""
if [ -e /dev/fd/3 ]; then
data=$(dd if=/dev/fd/3 bs=4096 count=1 2>/dev/null)
if [ -n "$data" ]; then
user=$(echo "$data" | awk -F '\0' '{print $1}')
fi
fi
2. GET IP ADDRESS
REMOTE_IP="${TCPREMOTEIP:-$IP}"
If running manually for a test, allow passing IP as 2nd arg
usage: ./script.sh dummy_binary 1.2.3.4
if [ -z "$REMOTE_IP" ] && [ -n "$2" ]; then
REMOTE_IP="$2"
fi
if [ -z "$REMOTE_IP" ]; then
# No IP found, allow access (e.g. internal socket or manual run
without args)
log_msg "Allowing (no IP provided)${user:+ for user $user}"
if [ -x "$1" ]; then
exec "$1"
else
printf '\0'
exit 0
fi
fi
3. DETECT IP VERSION AND PREPARE QUERY
if echo "$REMOTE_IP" | grep -q ":"; then
# --- IPv6 LOGIC (AWK) ---
REVERSED_IP=$(echo "$REMOTE_IP" | awk '
BEGIN { FS=":"; OFS="" }
{
if (index($0, "::") > 0) {
match($0, /::/);
head = substr($0, 1, RSTART-1);
tail = substr($0, RSTART+2);
n_head = (head == "") ? 0 : split(head, h, ":");
n_tail = (tail == "") ? 0 : split(tail, t, ":");
missing = 8 - n_head - n_tail;
full = head;
for(i=0; i<missing; i++) full = full ":0000";
if (tail != "") { if (full != "") full = full ":" tail; else
full = tail; }
} else { full = $0; }
split(full, groups, ":");
res = "";
for (i=8; i>=1; i--) {
val = groups[i];
while (length(val) < 4) val = "0" val;
for (j=4; j>=1; j--) res = res "." substr(val, j, 1);
}
print substr(res, 2);
}')
else
# --- IPv4 LOGIC ---
REVERSED_IP=$(echo "$REMOTE_IP" | awk -F. '{print $4"."$3"."$2"."$1}')
fi
4. PERFORM LOOKUP
if [ -z "$REVERSED_IP" ]; then
# Invalid IP, allow
log_msg "Allowing invalid IP $REMOTE_IP${user:+ for user $user}"
if [ -x "$1" ]; then
exec "$1"
else
printf '\0'
exit 0
fi
fi
QUERY="${REVERSED_IP}.${DNSBL_ZONE}"
if host -t A "$QUERY" >/dev/null 2>&1; then
log_msg "REJECTED: $REMOTE_IP is listed in $DNSBL_ZONE${user:+ for
user $user}"
exit 1
fi
5. SUCCESS HAND-OFF
log_msg "Allowing $REMOTE_IP (Clean)${user:+ for user $user}"
if [ -x "$1" ]; then
exec "$1"
else
printf '\0'
exit 0
fi
Lefteris