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.
@jarmo broken means, that DKIM verifiers like rspamd mark the mail as R_DKIM_REJECT. After some debugging at rspamd i found mismatching hash values inside the signature.
2023-09-14 13:29:34 #28619(rspamd_proxy) <63f5b4>; dkim; rspamd_dkim_check: bh value mismatch: 73794e7dc4b38643febef62cb793c44a6810b94713d6aee82dfef9b59de04a35 versus fd67b50a88067747f497666b704a925fdc84a4b1869e755dc6d5957cc8526742, try add LF; try adding CRLF 2023-09-14 13:29:34 #28619(rspamd_proxy) <63f5b4>; dkim; rspamd_dkim_check: bh value mismatch after added CRLF: 73794e7dc4b38643febef62cb793c44a6810b94713d6aee82dfef9b59de04a35 versus 97a5fd6a2b11f37512a8e2deda4add1f950bd7959cef37bee35ce 39e15f06787, try add LF 2023-09-14 13:29:34 #28619(rspamd_proxy) <63f5b4>; dkim; rspamd_dkim_check: bh value mismatch after added LF: 73794e7dc4b38643febef62cb793c44a6810b94713d6aee82dfef9b59de04a35 versus fc7051de34e1074b3bad12ed2e64cfd8c4f904b1e4eb45bae4c0085 8c81a6546