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())

Reply via email to