Hi Martin,

On Tue, 21 Jan 2020, Martijn van Duren wrote:

I've already written filter-dnsbl, which is in OpenBSD-ports.
Source can be found here[0][1]. I also made filter-dkimsign[2].

Your filters look great and would great be material for inclusion in OS package distributions. My filter is just an example about one can go about writing a simple filter in python3, using asyncio. One of the advantages of the new filter API is that it makes such filters, written in any language, much easier to write.

I made libopensmtpd compile on Ubuntu, so I don't expect much problems
with NetBSD. The filters themselves haven't been made portable yet.
Could you help me set these filters up for NetBSD?

Please, contact the maintainer of the opensmtpd package for netbsd. The opensmtpd package version there is still 5.7.3p2.

Andi..


martijn@

[0] http://imperialat.at/dev/libopensmtpd/
[1] http://imperialat.at/dev/filter-dnsbl/
[2] http://imperialat.at/dev/filter-dkimsign/

On 1/21/20 1:27 AM, Andi Vajda wrote:

 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