Simplified Sendmail Config

reading time ( words)

This is a quick post documenting the most recent Sendmail setup that I’m testing for a project. It differs slightly from my typical setup on Debian systems, so I’m documenting it here mostly for the sake of keeping notes.

The Basics

For this setup, I need a simple MTA and Submission server. On the MTA side, I would be using the Spamhaus Zen blocklist right after my custom anti-spam rulesets, then SpamAssassin and my favorite AV scanner ClamAV to catch the ocassional virus and a fair share of 419s.

pexels.com

On the Submission side, I’ll be using SASL to provide Sendmail with a mechanism to check credentials.

I’ll be using Let’s Encrypt certificates for my setup.

Setting up SSL with Let’s Encrypt and Sendmail

Sendmail as packaged by Debian places all TLS-related configuration parts under /etc/mail/tls. The sendmailconfig command usually takes care of making certificates and the file /etc/mail/tls/starttls.m4 contains the parameters for setting up your configuration.

Since I want to use the same certificates for all my applications, I adjusted the symlinks from /etc/mail/tls so that they point to the right certificates and keys that I use for other applications, maintained with Let’s Encrypt.

# ls -l /etc/mail/tls
total 20
lrwxrwxrwx 1 root  root    42 Nov 16  2016 sendmail-client.crt -> /etc/letsencrypt/live/lem.click/cert.pem
lrwxrwxrwx 1 root  root    45 Nov 16  2016 sendmail-common.key -> /etc/letsencrypt/live/lem.click/privkey.pem
lrwxrwxrwx 1 root  root    42 Nov 16  2016 sendmail-server.crt -> /etc/letsencrypt/live/lem.click/cert.pem
-rwxr--r-- 1 root  root  3273 Aug 20 08:47 starttls.m4

Let’s Encrypt uses symlinks to keep proper references to the actual certificates / keys. My setup places the actual key material under /etc/letsencrypt/archive/lem.click/. Notice the permissions and group assignments I use:

# ls -l /etc/letsencrypt/archive/athena.pics/
total 176
-rw-r----- 1 smmta certs 1915 Nov 15  2016 cert1.pem
-rw-r----- 1 smmta certs 1915 Jan 15  2017 cert2.pem
-rw-r----- 1 smmta certs 1915 Mar 16 00:00 cert3.pem
⋮
-rw-r----- 1 smmta certs 1647 Nov 15  2016 chain1.pem
-rw-r----- 1 smmta certs 1647 Jan 15  2017 chain2.pem
-rw-r----- 1 smmta certs 1647 Mar 16 00:00 chain3.pem
⋮
-rw-r----- 1 smmta certs 3562 Nov 15  2016 fullchain1.pem
-rw-r----- 1 smmta certs 3562 Jan 15  2017 fullchain2.pem
-rw-r----- 1 smmta certs 3562 Mar 16 00:00 fullchain3.pem
⋮
-rw-r----- 1 smmta certs 1704 Nov 15  2016 privkey1.pem
-rw-r----- 1 smmta certs 1708 Jan 15  2017 privkey2.pem
-rw-r----- 1 smmta certs 1704 Mar 16 00:00 privkey3.pem
⋮
# getent group certs
certs:x:1022:smmta,smmsp,www-data

This allows Sendmail and nginx to access the key material without issue. In order to keep the permissions and ownership like this, I use a simple certbot renew hook in /etc/certbot-renew-hook:

#!/bin/sh

# Ensure that all certificates and related files managed by certbot have proper
# permissions and group ownership

set -e

LEROOT=/etc/letsencrypt/archive

for domain in $RENEWED_DOMAINS; do
  [ -d ${LEROOT}/${domain} ] && ( find ${LEROOT}/${domain} -type f \
    | xargs --no-run-if-empty chown smmta:certs )
  [ -d ${LEROOT}/${domain} ] && ( find ${LEROOT}/${domain} -type f \
    | xargs --no-run-if-empty chmod o-rwx )
done

exit 0

You might want to point the symlinks from /etc/mail/tls to the fullchain.pem files instead, so that Sendmail provides the certificate chain when talking to other MTAs. This improves the chances that the remote MTA will validate the certificate.

After running sendmailconfig with correct settings, you should be able to observe STARTTLS being offered by Sendmail in the SMTP dialog:

$ swaks -f foo -t bar -s smtp.lem.click --port 587 -tls
=== Trying smtp.lem.click:587...
=== Connected to smtp.lem.click.
<-  220 libertad.link ESMTP Sendmail ᠁
 -> EHLO ws.lem.click
<-  250-libertad.link Hello ᠁, pleased to meet you
⋮
<-  250-STARTTLS
⋮
 -> STARTTLS
<-  220 2.0.0 Ready to start TLS
=== TLS started with cipher TLSv1:DHE-RSA-AES256-SHA:256
=== TLS no local certificate set
=== TLS peer DN="/CN=smtp.lem.click"

As you can see in the swaks output above, Sendmail needs to offer STARTTLS in the response to the EHLO ESMTP command. I added the following tweaks to the default TLS configuration, so as to not bother with client certificates:

dnl #
dnl #---------------------------------------------------------------------
dnl # Don't ask for client certs
dnl #---------------------------------------------------------------------
define(`confTLS_SRV_OPTIONS', `V')

Setting up the Submission side

The Submission service requires the ability to authenticate the client. Since I already had this configured, I simply reused my SASL setup. However setting this up from scratch requires installing a few packages and re-running sendmailconfig, paying attention to the instructions provided by the script.

aptitude install libsasl2-modules libsasl2-2 sasl2-bin
⋮
sendmailconfig
⋮

You’ll find instructions under /usr/share/doc/sasl2-bin/README.Debian.gz and /usr/share/doc/libsasl2-2/README.Debian. Make sure you can test saslauthd before moving forward, as nothing will work otherwise.

In my case, I made the following tweaks in the sendmail.mc file to ensure that credentials won’t be exposed by sending them without crypto.

dnl #---------------------------------------------------------------------
dnl # Require credentials to be encrypted, dnon't ask for client certs
dnl #---------------------------------------------------------------------
define(`confAUTH_OPTIONS',`A,p,y')dnl

With this setting in place and SASL properly configured, notice how Sendmail offers different AUTH methods depending on the session status:

$ swaks -f foo -t bar -s smtp.lem.click --port 587 -tls
=== Trying smtp.lem.click:587...
=== Connected to smtp.lem.click.
<-  220 libertad.link ESMTP Sendmail ᠁
 -> EHLO ws.lem.click
<-  250-libertad.link Hello ᠁, pleased to meet you
⋮
<-  250-AUTH DIGEST-MD5 CRAM-MD5
<-  250-STARTTLS
⋮
 -> STARTTLS
<-  220 2.0.0 Ready to start TLS
=== TLS started with cipher TLSv1:DHE-RSA-AES256-SHA:256
=== TLS no local certificate set
=== TLS peer DN="/CN=smtp.lem.click"
 ~> EHLO ws.lem.click
<~  250-libertad.link Hello ᠁, pleased to meet you
⋮
<~  250-AUTH DIGEST-MD5 CRAM-MD5 LOGIN PLAIN

At the beginning of the session there’s no crypto. Sendmail offers STARTTLS and AUTH methods that do not expose the credentials in the clear. After the client issues the STARTTLS command and crypto is established, Sendmail offers AUTH PLAIN and AUTH LOGIN.

With this setup in place, an SMTP client should not send credentials in the clear and even if it did, they would not work so this should trigger an error to the user.

SpamAssassin and ClamAV via Milter

I opted to plug SpamAssassin and ClamAV directly to Sendmail using the Milter interface rather than via Amavisd. So far this has resulted in a reduction in the memory footprint of the system without any noticeable drawbacks.

This is a matter of installing and configuring the required packages:

aptitude install clamav-milter clamd clamav-daemon spamass-milter spamassassin

For my current use case I stuck to Unix sockets, as I can affort to run all the pieces in the same box. For larger deployments you may want to consider separating your AV and Spam filter to different machines and using TCP sockets to plug the milters.

On my sendmail.mc I added the following commands to complete the connection:

INPUT_MAIL_FILTER(`clamav', `S=local:/var/run/clamav/clamav-milter.ctl, F=, T=S:4m;R:4m')dnl
INPUT_MAIL_FILTER(`spamassassin', `S=local:/var/run/spamass/spamass.sock, F=, T=C:15m;S:4m;R:4m;E:10m')
define(`confINPUT_MAIL_FILTERS', `spamassassin,clamav')dnl

I’m placing the anti-virus after SpamAssassin because on this day and age, email viruses are way less frequent than spam, so it makes sense to remove them early without wasting effort in analyzing it.

One sendmailconfig later, I can proceed to test sending the EICAR anti-malware test file payload.

# swaks -t me@lem.click -f me@lem.click --attach - --server smtp.lem.click --suppress-data <~/eicar.com
=== Trying smtp.lem.click:587...
=== Connected to smtp.lem.click.
<-  220 libertad.link ESMTP Sendmail ᠁
 -> EHLO ws.lem.click
<-  250-libertad.link Hello ᠁, pleased to meet you
⋮
<-  250 2.0.0 v7K9je55029741 Message accepted for delivery
 -> QUIT
<-  221 2.0.0 libertad.link closing connection
=== Connection closed with remote host.

Now look in your logs to see how ClamAV quarantined the file:

Aug 20 09:45:42 ns1 sm-mta[29741]: v7K9je55029741: Milter add: header: X-Virus-Scanned: clamav-milter 0.99.2 at ns1.libertad.link
Aug 20 09:45:42 ns1 sm-mta[29741]: v7K9je55029741: Milter add: header: X-Virus-Status: Infected (Eicar-Test-Signature)
Aug 20 09:45:42 ns1 sm-mta[29741]: v7K9je55029741: milter=clamav, quarantine=quarantined by clamav-milter

In my case, I would rather not accept the message. Truth is I won’t see it, and the sender has no idea that the message was quarantined on my end. Therefore, I prefer the message to be rejected during the SMTP transction so that there’s no expectation that I’ll see the message. To do this, make these edits to your /etc/clamav/clamav-milter.conf.

OnInfected Reject
RejectMsg Rejected because %v was found in your message

Then restart the milter.

systemctl restart clamav-milter.service

After restarting, notice the different behavior:

# swaks -t me@lem.click -f me@lem.click --attach - --server smtp.lem.click --suppress-data <~/eicar.com
=== Trying smtp.lem.click:587...
=== Connected to smtp.lem.click.
<-  220 libertad.link ESMTP Sendmail ᠁
 -> EHLO ws.lem.click
<-  250-libertad.link Hello ᠁, pleased to meet you
⋮
<** 550 5.7.1 Rejected because Eicar-Test-Signature was found in your message
 -> QUIT
<-  221 2.0.0 libertad.link closing connection
=== Connection closed with remote host.

Place Zen in the front line

Knowing that upwards of 90% of the SMTP server I get is spam, I’ve settled in the use of DNS black lists as my first line of defense. A DNS lookup is considerably cheaper than all the work required for content filtering.

Nowadays adding a DNS black list lookup is very simple. Just add this to your sendmail.mc file, run sendmailconfig and you’re done.

dnl # Protect ourselves with Zen
FEATURE(`dnsbl',`zen.spamhaus.org')dnl

Any host listed in the Spamhaus’ Zen list will be automatically rejected during the SMTP transaction, sparing the CPU and bandwith required for content analysis.