Monday, February 9, 2015

OpenBSD Mail Server - Part 4, SpamAssassin and SpamPD

1.  Install p5-Mail-SpamAssassin and spampd from packages.

2.  Edit /etc/mail/spamassassin/ and uncomment the "rewrite_header" line.

3.  Spampd will be used as a proxy like clamsmtp.  For purposes of this guide, only incoming mail will be scanned.  Spampd by default runs on port 10025 but that port is already being used for clamsmtp.  So, add the following to /etc/rc.conf.local:

spampd_flags="--port=10035 --relayhost= --tagall -aw"

With these flags, spampd will listen on port 10035 and after processing the mail through SpamAssassin, spampd will relay the mail back to port 10036, where OpenSMTPD will be listening.

UPDATE: spampd seems to have trouble binding to the right port (10035 in this case) upon a reboot even with those spampd_flags set in /etc/rc.conf.local. It tries to bind to 10025 which, as noted previously, is being used by clamsmtp, and therefore spampd fails to work and incoming mail has no place to go when opensmtpd tries to relay it to spampd. I have to manually log in and kick spampd to get it to bind to 10035. Still investigating a solution other than changing all the ports around ...

Add "spamassassin" and "spampd" to pkg_scripts in /etc/rc.conf.local and then start both spamassassin and spampd.  A "netstat -na -f inet" should show spampd listening on port 10035.

4.  Once spampd was processing mail, there were errors in /var/log/maillog along the lines of: “spampd Insecure dependency -T switch at” and it wasn't working.  Turns out spampd needs patching for newer Perl.  See this:  Here is a patch to /usr/local/sbin/spampd (also found here):

--- spampd.orig Thu Jan 29 23:19:45 2015
+++ spampd Thu Jan 29 23:21:31 2015
@@ -824,6 +824,22 @@ if ( $logsock !~ /^(unix|inet)$/ ) {
+# Untaint some options provided by admin command line.
+$pidfile =~ /^(.*)$/;
+$pidfile = $1;
+$relayhost =~ /^(.*)$/;
+$relayhost = $1;
+$relayport =~ /^(.*)$/;
+$relayport = $1;
+$host =~ /^(.*)$/;
+$host = $1;
+$port =~ /^(.*)$/;
+$port = $1;
 if ( $options{tagall} ) { $tagall = 1; }
 if ( $options{'log-rules-hit'} ) { $rh = 1; }
 if ( $options{debug} ) { $debug = 1; $nsloglevel = 4; }

5.  Restart spampd after applying that patch.

6.  Now, modify /etc/mail/smtpd.conf similar to what was done for clamsmtp:

# cat /etc/mail/smtpd.conf
pki certificate "/etc/ssl/"
pki key "/etc/ssl/private/"

listen on lo0
listen on lo0 port 10026 tag CLAM_IN # incoming mail
listen on lo0 port 10028 tag CLAM_OUT # outgoing mail
listen on lo0 port 10036 tag SPAM_IN # incoming mail
listen on egress tls pki auth-optional
listen on egress port submission tls-require pki auth

table aliases db:/etc/mail/aliases.db
table vusers file:/etc/mail/vusers
table vdomains file:/etc/mail/vdomains

accept for local alias <aliases> deliver to maildir

# tagged mail returned from spampd deliver to maildir
accept tagged SPAM_IN for domain <vdomains> virtual <vusers> deliver to maildir

# tagged mail returned from clamsmtpd either send to spampd or relay
accept tagged CLAM_IN for any relay via smtp:// # send to spampd
accept tagged CLAM_OUT for any relay

# start here - untagged mail is sent to clamsmtpd
accept from any for domain <vdomains> relay via smtp:// # incoming mail
accept from local for any relay via smtp:// # outgoing mail

7.  There were still some errors in /var/log/maillog.  First, there was something like this:

Feb 03 16:48:44 server spampd[22524]: spf: lookup failed: available_nameservers: No DNS servers available!
Feb 03 16:48:44 server spampd[22524]: rules: failed to run USER_IN_DEF_DKIM_WL test, skipping:  (available_nameservers: No DNS servers available! )

Turns out, SpamAssassin had broken DNS lookups.  Here is the patch to /usr/local/libdata/perl5/site_perl/Mail/SpamAssassin/ (also found here):

--- Fri Feb  7 03:36:28 2014
+++      Thu Nov 13 16:04:01 2014
@@ -204,8 +204,10 @@
     @ns_addr_port = @{$self->{conf}->{dns_servers}};
     dbg("dns: servers set by config to: %s", join(', ',@ns_addr_port));
   } elsif ($res) {  # default as provided by Net::DNS, e.g. /etc/resolv.conf
-    @ns_addr_port = map(untaint_var("[$_]:" . $res->{port}),
-                        @{$res->{nameservers}});
+    my @ns = $res->UNIVERSAL::can('nameservers') ? $res->nameservers
+                                                 : @{$res->{nameservers}};
+    my $port = $res->UNIVERSAL::can('port') ? $res->port : $res->{port};
+    @ns_addr_port = map(untaint_var("[$_]:" . $port), @ns);
     dbg("dns: servers obtained from Net::DNS : %s", join(', ',@ns_addr_port));
   return @ns_addr_port;

8.  Then, there was this:

Feb 03 16:48:44 server spampd[22524]: plugin: eval failed: bayes: (in learn) locker: safe_lock: cannot create tmp lockfile /var/spampd/.spamassassin/ for /var/spampd/.spamassassin/bayes.lock: Permission denied

It appeared that although /var/spampd was set to _spampd:_spampd, the /var/spampd/.spamassassin was set to root:_spampd and the permissions were 700 (IIRC).  Anyway, chown that directory to also be _spampd:_spampd and then it appears to work fine.

9.  So now here is what's happening:

Incoming mail:

pf -> relay to spamd -> send to opensmtpd on lo0 -> relay untagged mail to clamsmtpd on port 10025 -> relay to clamd on port 3310 -> return to clamsmtpd -> return to opensmtpd on lo0 port 10026 and tag it CLAM_IN -> relay tagged CLAM_IN mail to spampd on port 10035 -> run it through SpamAssassin -> return to spampd -> return to opensmtpd on lo0 port 10036 and tag it SPAM_IN -> deliver to maildir

Outoing mail (unchanged from before since outgoing mail is not sent to spampd):

opensmtpd on lo0 -> relay untagged mail to clamsmtpd on port 10027 -> relay to clamd on port 3310 -> return to clamsmtpd -> return to opensmtpd on lo0 port 10028 and tag it CLAM_OUT -> relay out

10.  Test again, both ways.  Use the GTUBE test to see if it’s flagged as spam.  There should be SpamAssassin headers in the incoming email.  SpamAssassin can be further set up for Bayesian training and cron entries for running sa-learn on designated directories.


  1. Thanks for the fine guide, I've setup a pretty complete test server using this. Just need to fixup the ldap backend.

    Regarding the spampd startup issue, I first tried rc.local without succes, but then I simply added this to crontab for root:

    @reboot /etc/rc.d/spampd start