Hi list,
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 !
Andi..
- 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)
while True:
yield await reader.readline()
async def filter():
loop = asyncio.get_running_loop()
async for line in input(loop):
line = line.strip().decode()
emit(line, sys.stderr)
args = line.split('|')
if args[0] == 'filter':
dnsbl = args[8].split(':')[0].split('.')
dnsbl.reverse()
dnsbl.append('zen.spamhaus.org')
response = None
try:
await loop.getaddrinfo('.'.join(dnsbl), None)
except socket.gaierror as e:
if e.args[0] == socket.EAI_NODATA:
response = '|'.join(['filter-result', args[6], args[5], 'proceed'])
else:
emit(str(e), std.stderr)
except Exception as e:
emit(str(e), std.stderr)
pass
if response is None:
response = '|'.join(['filter-result', args[6], args[5], 'disconnect',
'421 Temporary failure'])
emit(response, sys.stderr)
emit(response)
for line in sys.stdin:
line = line.strip()
emit(line, sys.stderr)
if line == 'config|ready':
break
emit('register|filter|smtp-in|connect')
emit('register|ready')
asyncio.run(filter())