I've been a happy opensmtpd user on netbsd for many years.
I've now upgraded to version 6.6.1p1 and wrote a dnsbl filter in python3
using asyncio that talks to zen.spamhaus.org. I've included the source
code at the bottom of this message.
This is my first time using asyncio.
I know that the filter feature of 6.6.1 is not (completely) documented and
is bound to change so I used the github smtpd-filters.7 man page for
reference and little bit of debugging to figure it out.
I implemented this as a 'register|filter|smtp-in|connect' filter.
The strangest thing was that I had to swap parts 5 and 6 of a filter line
(the unique session identifier and the opaque token) when returning them
with a filter-result line. Other than that, this new filter interface is
super nice and readable and a pleasure to use.
Thank you for making it happen !
----- filter.py -----
import asyncio, socket, sys
def emit(line, out=sys.stdout):
print(line, file=out, flush=True)
async def input(loop):
reader = asyncio.StreamReader()
protocol = asyncio.StreamReaderProtocol(reader)
await loop.connect_read_pipe(lambda: protocol, sys.stdin)
yield await reader.readline()
async def filter():
loop = asyncio.get_running_loop()
async for line in input(loop):
line = line.strip().decode()
args = line.split('|')
if args == 'filter':
dnsbl = args.split(':').split('.')
response = None
await loop.getaddrinfo('.'.join(dnsbl), None)
except socket.gaierror as e:
if e.args == socket.EAI_NODATA:
response = '|'.join(['filter-result', args, args, 'proceed'])
except Exception as e:
if response is None:
response = '|'.join(['filter-result', args, args, 'disconnect',
'421 Temporary failure'])
for line in sys.stdin:
line = line.strip()
if line == 'config|ready':