Commit 4ecc5461 authored by Alberto Bertogli's avatar Alberto Bertogli

Add driusan/dkim integration example and tests

This patch adds DKIM signing using https://github.com/driusan/dkim tools
to the example hook.

It also adds an optional integration test to exercise signing and
verification, and corresponding documentation.
parent ebad590c
# DKIM integration
[chasquid] supports generating [DKIM] signatures via the [hooks](hooks.md)
mechanism.
## Signing
The example hook in this repository contains an example of integration with
[driusan/dkim](https://github.com/driusan/dkim) tools, and assumes the
following:
- The [selector](https://tools.ietf.org/html/rfc6376#section-3.1) for a domain
can be found in the file `domains/$DOMAIN/dkim_selector`.
- The private key to use for signing can be found in the file
`certs/$DOMAIN/dkim_privkey.pem`.
Only authenticated email will be signed.
## Verification
Verifying signatures is technically supported as well, and can be done in the
same hook. However, it's not recommended for SMTP servers to reject mail on
verification failures
([source 1](https://tools.ietf.org/html/rfc6376#section-6.3),
[source 2](https://tools.ietf.org/html/rfc7601#section-2.7.1)), so it is not
included in the example.
[chasquid]: https://blitiri.com.ar/p/chasquid
[DKIM]: https://en.wikipedia.org/wiki/DomainKeys_Identified_Mail
......@@ -6,6 +6,7 @@
# - greylist (from greylistd) to do greylisting.
# - spamc (from Spamassassin) to filter spam.
# - clamdscan (from ClamAV) to filter virus.
# - dkimsign (from driusan/dkim) to do DKIM signing.
#
# If it exits with code 20, it will be considered a permanent error.
# Otherwise, temporary.
......@@ -53,3 +54,23 @@ if command -v clamdscan >/dev/null; then
echo "X-Virus-Scanned: pass"
fi
# DKIM sign with https://github.com/driusan/dkim.
#
# Do it only if all the following are true:
# - User has authenticated.
# - dkimsign binary exists.
# - domains/$DOMAIN/dkim_selector file exists.
# - certs/$DOMAIN/dkim_privkey.pem file exists.
#
# Note this has not been thoroughly tested, so might need further adjustments.
if [ "$AUTH_AS" != "" ] && command -v dkimsign; then
DOMAIN=$( echo "$MAIL_FROM" | cut -d '@' -f 2 )
if [ -f "domains/$DOMAIN/dkim_selector" ] \
&& [ -f "certs/$DOMAIN/dkim_privkey.pem" ]; then
dkimsign -n -hd \
-key "certs/$DOMAIN/dkim_privkey.pem" \
-s $(cat "domains/$DOMAIN/dkim_selector") \
-d "$DOMAIN" \
< "$TF"
fi
fi
......@@ -32,9 +32,15 @@ RUN cd test/t-02-exim && mkdir -p .exim4 && ln -s /usr/sbin/exim4 .exim4/
# Packages for the (optional) TLS tracking test.
RUN apt-get install -y -q dnsmasq
RUN go get -d ./...
RUN go install ./...
# Packages for the (optional) DKIM integration test.
RUN go get github.com/driusan/dkim/... \
&& go install github.com/driusan/dkim/cmd/dkimsign \
&& go install github.com/driusan/dkim/cmd/dkimverify \
&& go install github.com/driusan/dkim/cmd/dkimkeygen
# Install chasquid and its dependencies.
RUN go get -d -v ./...
RUN go install -v ./...
# Don't run the tests as root: it makes some integration tests more difficult,
# as for example Exim has hard-coded protections against running as root.
......
smtp_address: ":1025"
submission_address: ":1587"
monitoring_address: ":1099"
mail_delivery_agent_bin: "test-mda"
mail_delivery_agent_args: "%to%"
data_dir: "../.data"
mail_log_path: "../.logs/mail_log"
#!/bin/bash
# If authenticated, sign; otherwise, verify.
#
# It is not recommended that we fail delivery on dkim verification failures,
# but leave it to the MUA to handle verifications.
# https://tools.ietf.org/html/rfc6376#section-2.2
#
# We do a verification here so we have a stronger integration test (check
# encodings/dot-stuffing/etc. works ok), but it's not recommended for general
# purposes.
if [ "$AUTH_AS" != "" ]; then
DOMAIN=$( echo "$MAIL_FROM" | cut -d '@' -f 2 )
exec dkimsign -n -hd -key ../.dkimcerts/private.pem \
-s $(cat "domains/$DOMAIN/dkim_selector") -d "$DOMAIN"
fi
exec dkimverify -txt ../.dkimcerts/dns.txt
Subject: Prueba desde el test
To: someone@testserver
Crece desde el test el futuro
Crece desde el test
.
El punto de arriba testea el dot-stuffing, que es importante para DKIM.
testserver localhost
account default
host testserver
port 1587
tls on
tls_trust_file config/certs/testserver/fullchain.pem
from user@testserver
auth on
user user@testserver
password secretpassword
#!/bin/bash
#
# Test integration with driusan's DKIM tools.
# https://github.com/driusan/dkim
set -e
. $(dirname ${0})/../util/lib.sh
init
for binary in dkimsign dkimverify dkimkeygen; do
if ! which $binary > /dev/null; then
skip "$binary binary not found"
exit 0
fi
done
generate_certs_for testserver
( mkdir -p .dkimcerts; cd .dkimcerts; dkimkeygen )
add_user user@testserver secretpassword
add_user someone@testserver secretpassword
mkdir -p .logs
chasquid -v=2 --logfile=.logs/chasquid.log --config_dir=config &
wait_until_ready 1025
# Authenticated: user@testserver -> someone@testserver
# Should be signed.
run_msmtp someone@testserver < content
wait_for_file .mail/someone@testserver
mail_diff content .mail/someone@testserver
grep -q "DKIM-Signature:" .mail/someone@testserver
# Verify the signature manually, just in case.
dkimverify -txt .dkimcerts/dns.txt < .mail/someone@testserver
# Save the signed mail so we can verify it later.
# Drop the first line ("From blah") so it can be used as email contents.
tail -n +2 .mail/someone@testserver > .signed_content
# Not authenticated: someone@testserver -> someone@testserver
smtpc.py --server=localhost:1025 < .signed_content
# Check that the signature fails on modified content.
echo "Added content, invalid and not signed" >> .signed_content
if smtpc.py --server=localhost:1025 < .signed_content 2> /dev/null; then
fail "DKIM verification succeeded on modified content"
fi
success
......@@ -5,6 +5,7 @@
import argparse
import email.parser
import email.policy
import re
import smtplib
import sys
......@@ -16,15 +17,27 @@ args = ap.parse_args()
# Parse the email using the "default" policy, which is not really the default.
# If unspecified, compat32 is used, which does not support UTF8.
msg = email.parser.Parser(policy=email.policy.default).parse(sys.stdin)
rawmsg = sys.stdin.buffer.read()
msg = email.parser.Parser(policy=email.policy.default).parsestr(
rawmsg.decode('utf8'))
s = smtplib.SMTP(args.server)
s.starttls()
s.login(args.user, args.password)
if args.user:
s.login(args.user, args.password)
# Note this does NOT support non-ascii message payloads transparently (headers
# are ok).
s.send_message(msg)
# Send the raw message, not parsed, because the parser does not handle some
# corner cases that well (for example, DKIM-Signature headers get mime-encoded
# incorrectly).
# Replace \n with \r\n, which is normally done by the library, but will not do
# it in this case because we are giving it bytes and not a string (which we
# cannot do because it tries to incorrectly escape the headers).
crlfmsg = re.sub(br'(?:\r\n|\n|\r(?!\n))', b"\r\n", rawmsg)
s.sendmail(
from_addr=msg['from'], to_addrs=msg.get_all('to'),
msg=crlfmsg,
mail_options=['SMTPUTF8'])
s.quit()
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment