Eric Radman : a Journal

OpenSMTPD Mail Filtering

Running an email server that deals competently with spam has always been a difficult, low-reward task. This tutorial will use packages:


spamassassin is the classification engine, spampd connects to the spamassassin daemon and forwards the mail to an MTA, and procmail writes the messages in the mbox format.

SpamAssassin & SpamPD

Filtering mail with OpenSMTPd is most easily accomplished by running a SpamAssassin proxy that we can forward mail to. The SpamPD relay will add X-Spam- headers to each message and forwarded them back to localhost on the port that we specify

# rc.conf.local
pkg_scripts=dkimproxy_out spampd
spampd_flags="--port=10025 --relayhost= --tagall --log-rules-hit --maxsize=2048"

These SpamPD options only need to match the MTA configuration. Setting --maxsize allows messages with largish attachments to be processed. --tagall and --log-rules-hit are not required, but useful for diagnostics.

After routing through spampd each message will contain headers with the evaluation. This is spam that did not reach the configured threshold

X-Spam-Level: ***
X-Spam-Status: No, score=3.0 required=4.0 tests=BAD_CREDIT,DKIM_SIGNED,

And this is a message that was not spam

X-Spam-Status: No, score=-2.2 required=4.0 tests=BAYES_00,DKIM_SIGNED,
        RCVD_IN_MSPIKE_H2,URIBL_BLOCKED autolearn=ham autolearn_force=no version=3.4.1


Now that each message is annotated with a score we can optionally route it using Procmail, which can be configured per user with $HOME/.procmailrc or per site with /etc/procmailrc. The following rule will cause suspect mail to be delivered to an mbox named "spam"

* ^X-Spam-Flag: YES

If the path to the mailbox does not exist procmail will not create the directory, it will ignore the rule.

OpenSMTPD Routing

table aliases file:/etc/mail/aliases

listen on localhost port 25
listen on egress inet4 port 25
listen on localhost port 10028 tag DKIM
listen on localhost port 10026 tag SCORED

action "mbox" mbox alias <aliases>
action "relay" relay
action relay_dkim relay host smtp://
action spampd relay host smtp://
action procmail mda "/usr/local/bin/procmail -f -"

match for local action "mbox"
match auth from any for domain action "mbox"

match tag SCORED from any for domain action "procmail"

match tag DKIM for any action "relay"
match for any action "relay_dkim"
match auth from any for any action "relay"

match from any for domain action "spampd"

Incoming mail on port 25 is not tagged, which matches the rule relay via smtp:// After SpamAssassin grades the mail, SpamPD relays it back to OpenSMTPD on port 10026 where it matches the tag SCORED and is handed over to procmail for delivery.

This configuration assumes that spam and DKIM signing daemons are up rc.local

pkg_scripts=dkimproxy_out spamassassin spampd
spampd_flags="--port=10025 --relayhost= --tagall --log-rules-hit --maxsize=2048"

DKIM Signing

dkimproxy_out.conf specifies the ports to listen and relay on, along with the domains to sign

# specify what address/port DKIMproxy to listen on

# specify what address/port DKIMproxy forwards mail to

# specify what domains DKIMproxy can sign for (comma-separated, no spaces)

# specify what signatures to add
signature dkim(c=relaxed)
signature domainkeys(c=nofws)

# specify location of the private key
keyfile   /etc/mail/dkim-private.key

# specify the selector (i.e. the name of the key record put in DNS)
selector  selector1

Failure Conditions for SpamPD

1. If spampd corrupts it's own bayes database, stop the daemon, and remove everything under /var/spampd/.spamassassin/

2. If spampd encounters trouble creating lockfiles, raise open-files-cur in /etc/login.conf

3. SpamPD can use a significant amount of RAM, provision 500MB for this task

$ ps -U _spampd -o rss | awk '{sum += $1} END {print "RSS for _spamd", sum/1024 "MB"}'
RSS for _spamd 598.551MB

4. If spamassassin is not yet running spamd may panic and begin pegging the server CPU. Start spamd late in the boot process