From Segfault
Jump to navigation Jump to search


We want to enable ClamAV and Spamassassin scanning, but without AMaViS inbetween:

 # apt-get install postfix spamassassin spamc clamav-daemon clamsmtp

ClamSMTP is needed as a proxy between the MTA and clamd. Although clamd can listen on a network socket, postfix cannot talk directly to it.


This is basically explained here, here's the short version again, with snippets from the configuration files:

 # ClamSMTPd
 smtp      inet  n       -       -       -       -       smtpd
       -o content_filter=scan:
 # Maildrop
 maildrop  unix  -       n       n       -       -       pipe
 flags=DRhu argv=/usr/bin/maildrop -d ${recipient}
 # SpamAssassin
 spamassassin unix -     n       n       -       -       pipe
       user=mail argv=/usr/bin/spamc -u ${user} -e /usr/sbin/sendmail -oi -f ${sender} ${recipient}
 # ClamAV
 scan    unix    -       -       n       -       16      smtp
       -o smtp_send_xforward_command=yes inet n  -       n       -       16      smtpd
       -o content_filter=spamassassin
       -o receive_override_options=no_unknown_recipient_checks,no_header_body_checks
       -o smtpd_helo_restrictions=
       -o smtpd_client_restrictions=
       -o smtpd_sender_restrictions=
       -o smtpd_recipient_restrictions=permit_mynetworks,reject
       -o mynetworks_style=host
       -o smtpd_authorized_xforward_hosts=

Note: somehow it's important that the spamassassin filter has a user= option, otherwise we might get errors like:

 postfix/pipe[9911]: fatal: missing user= command-line attribute
 postfix/qmgr[9876]: warning: private/spamassassin socket: malformed response
 postfix/qmgr[9876]: warning: transport spamassassin failure -- see a previous warning/fatal/panic
                     logfile record for the problem description
 postfix/master[9874]: warning: process /usr/lib/postfix/pipe pid 9911 exit status 1
 postfix/master[9874]: warning: /usr/lib/postfix/pipe: bad command startup -- throttling
 postfix/error[9922]: DD05E3DD5C: to=<bob@localhost>, relay=none, delay=1.1, delays=0.07/1/0/0.05,
                      dsn=4.3.0, status=deferred (unknown mail transport error

A few basic configuration parameters[1]:

home_mailbox = Maildir/
mailbox_command = /usr/bin/maildrop -d ${USER}
maildrop_destination_recipient_limit = 1
mailbox_size_limit = 0
message_size_limit[2] = 0

smtp_tls_security_level[3] = may
mydestination =, mail, localhost,
mynetworks = [::ffff:]/104 [::1]/128

Postfix settings can be queried (and modified) via postconf:

$ postconf inet_protocols
inet_protocols = ipv4

$ postconf inet_protocols="ipv4 ipv6"
$ service postfix restart


 OutAddress: 10026
 ClamAddress: /var/run/clamav/clamd.ctl
 TempDirectory: /var/spool/clamsmtp
 Action: pass

Note: setting TempDirectory is important too, otherwise clamsmtpd may not be able to access TMPDIR, which may be set in root's environment and will produce errors like:

 clamsmtpd: 100058: clamav error: /var/spool/clamsmtp/clamsmtpd.1Lp6Vz: Can't create temporary directory ERROR
 clamsmtpd: 100058:, 
                    to=bob@localhost, status=CLAMAV-ERROR
 postfix/smtp[16155]: E563C11E89: to=<bob@localhost>, relay=[]:10025, delay=0.08,
                      delays=0.03/0/0.04/0, dsn=4.0.0, status=deferred (host[]
                      said: 451 Local Error (in reply to end of DATA command))

While we're at it, we might want to set "LocalSocketMode 0660" in /etc/clamav/clamd.conf to restrict permissions to the control socket. Be sure to make clamav (i.e. the group "clamd" is running under) the primary group of clamsmtp:

$ usermod -g clamav -G clamsmtp clamsmtp
$ service clamsmtp restart

$ ps -e -o pid,user,group,euser,egroup,comm | grep cla[m]
10103 clamav   clamav   clamav   clamav   clamd
10396 clamsmtp clamav   clamsmtp clamav   clamsmtpd

For added security, disable the login shell for the system users:

for i in postfix clamav clamsmtp mail; do echo $i; chsh -s /bin/false $i; done

TODO: When clamsmtp is scanning, it sets the following variables, which can be used by VirusAction afterwards:


I'm still looking for a way to pass these variables to maildrop...


As OpenDKIM development seems to have stalled, we'll use dkimpy-milter now.

We need to generate some keys:

dknewkey --ktype rsa     key0
dknewkey --ktype ed25519 key1

This should have produced:

$ ls -go key*
-rw------- 1  420 Jul 12 22:47 key0.dns
-rw------- 1 1675 Jul 12 22:47 key0.key
-rw------- 1   66 Jul 12 22:47 key1.dns
-rw------- 1   44 Jul 12 22:47 key1.key

Note: if opendkim were installed, RSA key generation would look like this:

$ opendkim-genkey --bits=2048 --hash-algorithms=sha256 --selector=key0

But opendkim-genkey is not able to generate Ed25519, so this could be done manually:[4]

$ openssl genpkey -algorithm ed25519 -out key1.private
$ openssl pkey -in key1.private -pubout -out key1.txt

$ ls -go key*
-rw------- 1 1679 Jul 12 22:36 key0.private
-rw------- 1  501 Jul 12 22:36 key0.txt
-rw------- 1  119 Jul 12 22:35 key1.private
-rw------- 1  113 Jul 12 22:35 key1.txt

While key0.txt already contains the DNS record needed, we'll need to manually construct the same for the Ed25519 key:

$ openssl asn1parse -in key1.txt -offset 12 -noout -out /dev/stdout | openssl base64 | sed 's/^/key1._domainkey IN TXT ( "v=DKIM1; k=ed25519; p=/;s/$/")/'
key0._domainkey IN TXT ( "v=DKIM1; k=ed25519; p=U12Vr3xzvXBjzNErIk5CRQxS3NniRxCWocrjnmZWNCs=")

Once the TXT records have been published in DNS, dkimpy-milter itself can be configured:

$ grep ^[A-Z] /etc/dkimpy-milter/dkimpy-milter.conf 
Syslog                  yes
UMask                   007


KeyFile                 /etc/dkimkeys/key0.private
Selector                key0

KeyFileEd25519          /etc/dkimkeys/key1.private
SelectorEd25519         key1

Canonicalization        relaxed/relaxed
Mode                    sv
Socket                  inet:8892@localhost
PidFile                 /run/dkimpy-milter/
UserID                  dkimpy-milter
SyslogSuccess           yes

With dkimpy-milter running on localhost:8892 now, we can configure Postfix to use it:

smtpd_milters                   = inet:localhost:8892
non_smtpd_milters               = inet:localhost:8892
# smtpd_milters                 = unix:private/opendkim
# non_smtpd_milters             = unix:private/opendkim
milter_default_action           = tempfail

We'll use milter_default_action with:

tempfail - Reject all further commands in this session with a temporary status code.





Sometimes postfix is running, but its transports (spam engines, virus scanners) or its destinations (/home not mounted to deliver mails) are still missing. Nasty error messages are generated:

 postfix/master[21760]: daemon started -- version 2.5.5, configuration /etc/postfix
 postfix/qmgr[21763]: 4B63511EA5: from=<>, size=7763, nrcpt=1 (queue active)
 postfix/qmgr[21763]: warning: connect to transport spamassassin: Connection refused
 postfix/error[21765]: 4B63511EA5: to=<bob@localhost>, relay=none, status=deferred (mail transport unavailable)

Often it's just a matter of requeueing the messages:

 postqueue -p                         # print mail queue (mailq)
 postsuper -r ALL                     # requeue all messages
 postqueue -f  or  postfix flush      # flush mail queue (exim -qff), should happen automatically[5]

Open Relay Tests

Once our MTA is configured, we should make sure that it's not an open relay. While this could be accomplished by telnet or netcat, we can use more modern tools as well,[6] like swaks:

$ swaks --to --from root@localhost --server
=== Trying
=== Connected to
<-  220 ESMTP Postfix
 -> EHLO localhost
<-  250-SIZE 10240000
<-  250-VRFY
<-  250-ETRN
<-  250-STARTTLS
<-  250-8BITMIME
<-  250-DSN
<-  250-SMTPUTF8
<-  250 CHUNKING
 -> MAIL FROM:<root@localhost>
<-  250 2.1.0 Ok
 -> RCPT TO:<>
<** 454 4.7.1 <>: Relay access denied
 -> QUIT
<-  221 2.0.0 Bye
=== Connection closed with remote host.