Turns out that awk lends itself very nicely to writing OpenSMTPD filters with its
line-based filter protocol. Below is a simple example of how to implement a simple DNSBL check.
Note, the snippet below is an example, only, and has some shortcomings for simplicity:
- only handles IPv4 addresses
- does not do any version detection of the filter protocol, and doesn't work on filter protocol < 0.6 (which was used in OpenSMTPD < 6.7; see comments in the script for details)
- SMTP error returned on blacklisting is hardcoded, but should be configurable
- has hardcoded logging of every filter action, people might want to silence it
BEGIN {
if (ARGC != 4) {
printf("Error, 4 args expected, got %d\n", ARGC) > "/dev/stderr"
exit 1
}
RESOLVE_CMD = ARGV[1]
DNSBL = ARGV[2]
IP_BL = ARGV[3]
ARGC = 0
FS = "|"
}
"config|ready" == $0 {
print("register|filter|smtp-in|connect") > "/dev/stdin"
print("register|ready") > "/dev/stdin"
next
}
"filter" == $1 {
if (NF < 9) {
printf("Error, filter line not having enough fields, 9+ expected, got %d\n", NF) > "/dev/stderr"
next
}
sess_id = $6
resp_token = $7
split($9, x, "[.:]")
req = x[4]"."x[3]"."x[2]"."x[1]"."DNSBL
ret = "proceed"
cmd = sprintf(RESOLVE_CMD, req)
while((cmd | getline r) > 0) {
if(r ~ IP_BL) {
ret = "reject|550 connecting server is blacklisted"
break
}
}
close(cmd)
print(sess_id" DNSBL check: "$8" ["$9"]: "cmd" => "ret) > "/dev/stderr"
print("filter-result|"sess_id"|"resp_token"|"ret) > "/dev/stdin"
}