mirror of
https://blitiri.com.ar/repos/chasquid
synced 2025-12-17 14:37:02 +00:00
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.
This commit is contained in:
33
docs/dkim.md
Normal file
33
docs/dkim.md
Normal file
@@ -0,0 +1,33 @@
|
||||
|
||||
# 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
|
||||
|
||||
# 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
|
||||
|
||||
RUN go get -d ./...
|
||||
RUN go install ./...
|
||||
# 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.
|
||||
|
||||
9
test/t-15-driusan_dkim/config/chasquid.conf
Normal file
9
test/t-15-driusan_dkim/config/chasquid.conf
Normal file
@@ -0,0 +1,9 @@
|
||||
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"
|
||||
@@ -0,0 +1 @@
|
||||
testselector1
|
||||
19
test/t-15-driusan_dkim/config/hooks/post-data
Executable file
19
test/t-15-driusan_dkim/config/hooks/post-data
Executable file
@@ -0,0 +1,19 @@
|
||||
#!/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
|
||||
9
test/t-15-driusan_dkim/content
Normal file
9
test/t-15-driusan_dkim/content
Normal file
@@ -0,0 +1,9 @@
|
||||
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.
|
||||
1
test/t-15-driusan_dkim/hosts
Normal file
1
test/t-15-driusan_dkim/hosts
Normal file
@@ -0,0 +1 @@
|
||||
testserver localhost
|
||||
14
test/t-15-driusan_dkim/msmtprc
Normal file
14
test/t-15-driusan_dkim/msmtprc
Normal file
@@ -0,0 +1,14 @@
|
||||
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
|
||||
|
||||
51
test/t-15-driusan_dkim/run.sh
Executable file
51
test/t-15-driusan_dkim/run.sh
Executable file
@@ -0,0 +1,51 @@
|
||||
#!/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()
|
||||
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()
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user