Important
Current version of this code has moved into a proper GitHub repository: https://github.com/palant/opensmtpd-filters
The OpenSMTPD documentation currently suggests using either opensmtpd-filter-dkimsign
or opensmtpd-filter-rspamd
for DKIM support. The former lacks functionality and requires you to compile code from some Austrian web server yourself. The latter is overdimensioned for my needs. So I’ve written my own fairly simple filters in Python.
These filters require Python 3 with dkimpy module installed. You can optionally install pyspf module as well, if you want dkimverify.py
to perform SPF verification as well.
Your smtpd.conf
file should contain directives like the following:
filter dkimverify proc-exec "/usr/local/bin/dkimverify.py example.com"
filter dkimsign proc-exec "/usr/local/bin/dkimsign.py example.com:mydkim:/etc/mail/dkim/mydkim.key"
listen on eth0 tls filter dkimverify
listen on eth0 port 587 tls-require auth filter dkimsign
This sets up dkimverify
filter for port 25 (incoming mail) and dkimsign
filter for port 587 (outgoing mail).
dkimverify.py
takes a single command line parameter: the host name to appear in the Authentication-Results
email header. It will add a header like Authentication-Results: example.com; dkim=pass; spf=fail (sender is example.com/1.2.3.4) [email protected]
to emails, this header can then be considered in further processing.
dkimsign.py
takes one or multiple parameters of the form domain:selector:keyfile
on the command line. Instead of configuring all domains on the command line, you can also pass this script -c /etc/mail/dkim/dkim.conf
parameter, with the file /etc/mail/dkim/dkim.conf
containing domain configurations in the same format, one per line.
The opensmtpd.py
module here allows implementing OpenSMTPD filters easily. It is used like following:
from opensmtpd import FilterServer
server = FilterServer()
server.register_handler('report', 'link-auth', handle_auth)
server.register_handler('filter', 'connect', handle_connect)
server.serve_forever()
def handle_auth(session, username, result):
if result == 'pass':
print('Session {} authenticated'.format(session), file=sys.stderr)
def handle_connect(session, rdns, fcrdns, src, dest):
if fcrdns == 'pass':
return 'proceed'
else:
return 'junk'
See smtpd-filters man page for the description of the existing report events and filter requests and their parameters. The FilterServer
class also exposes a convenience method register_message_filter()
that allows filtering complete email messages:
server.register_message_filter(handle_message)
def handle_message(context, lines):
return map(lambda line: line.replace('xyz', 'abc'), lines)
There is also method track_context()
. If called during registration phase, the server will create a context object for each session and pass it to the handlers instead of the session ID.
@skrause87 but what if there are dots part of the message? For example, what if body is something like this:
It's pretty common (https://editorsmanual.com/articles/ellipsis/) and I think (have not double checked) it's allowed by RFC too.
Maybe should check that there's only two dots on the entire line and not just starting with two dots?