Projects/Mailserver with Debian, Exim, spamassassin, greylistd, DKIM, SRS, SPF, DMARC, forwarding, LDAP, dovecot, LMTP, disk crypto

From Hackerspace Amersfoort
Jump to navigation Jump to search


Project Mailserver with Debian, Exim, spamassassin, greylistd, DKIM, SRS, SPF, DMARC, forwarding, LDAP, dovecot, LMTP, disk crypto
Name Mailserver with Debian, Exim, spamassassin, greylistd, DKIM, SRS, SPF, DMARC, forwarding, LDAP, dovecot, LMTP, disk crypto
Start 2015/03/01
End
Contact WilcoBaanHofman
Website
Information This page describes how we set up our mail system and how other people can set up their own full blown modern mail servers.
Status Test"Test" is not in the list (Alpha, Beta, Production, Finished, Abandoned) of allowed values for the "Project status" property.


This page will talk about how to set up a mail server, and also how to comply with Google mail / gmail's bizarre, idiotic IPv6 spam policies.

You will need an SPF policy, DKIM and a valid forward/reverse DNS which matches the EHLO your mailserver sends. You will need spam filtering, virus filtering and a valid TLS connection.

TODO

  • SOGo
  • Running an external (partial) LDAP slave
  • Adjusting DNS records

Install the packages

Step 1: Install the required packages

~# apt-get install exim4-daemon-heavy spamassassin clamav-daemon greylistd spf-tools-perl sasl2-bin srs pyzor razor

If you want to run a mailing list server as well, also install mailman and apache2.

~# apt-get install mailman apache2

If dovecot is going to run on this same host (you're not doing high-availability or full disk crypto mail store), also install dovecot-imapd, dovecot-ldap and dovecot-lmtpd. Do this on the host where you want to run your dovecot.

~# apt-get install dovecot-imapd dovecot-ldap dovecot-lmtpd dovecot-sieve dovecot-managesieved


Verify that no interfering packages are installed (like postfix, sendmail, amavis)

~$ dpkg -l |egrep '(postfix|sendmail|amavis)'

This command should give no output, if it does and starts with ii, remove that package.

Configuring exim4

~# dpkg-reconfigure exim4-config

Choose split files (unless you want to replace every exim4 file name here with exim4.conf.template in this manual).

Now try to see if simple address resolution works.

~$ telnet 2a02:2ca0:aaa::a843:657d 25
Trying 2a02:2ca0:aaa::a843:657d...
Connected to 2a02:2ca0:aaa::a843:657d.
Escape character is '^]'.
220 mail.bitlair.nl ESMTP Exim 4.80 Sun, 15 Mar 2015 12:37:27 -0400
EHLO mail.bitlair.nl
250-mail.bitlair.nl Hello mail.bitlair.nl [2a02:2ca0:aaa::a843:657d]
250-SIZE 52428800
250-8BITMIME
250-PIPELINING
250-STARTTLS
250 HELP
MAIL FROM: <wilco@bitlair.nl>
250 OK
RCPT TO: <wilco@bitlair.nl>
250 Accepted
RCPT TO: <aoeu@bitlair.nl>
550 Unrouteable address

TLS

Hint: StartCom signs certificates for free. Class 2 certification allows you do much more, though. In order to qualify for level 2 verification, do a personal validation once a year for $59 and you can issue an unlimited number of certificates for personal use. For business use, it requires an additional $59 for organisation verification on top of the personal validation. Your account will be converted to a business account, so no more personal certificates on that account.

To generate the key and certificate signing request:

~# openssl req -sha256 -days 3650 -nodes -new -newkey rsa:4096 -keyout /etc/ssl/private/mail.bitlair.nl-key.pem -out /etc/ssl/mail.bitlair.nl-csr.pem

Input your location and contact information. At the CN field, input your mail hostname (mail.bitlair.nl in my case). Do not enter a challenge password.

Copy the certificate signing request to a CA for signing. Allow the CA to sign it, then you'll receive a certificate from the CA. Place the received certificate in /etc/ssl/mail.your.domain-cert.pem

Put MAIN_TLS_ENABLE=yes near the top in /etc/exim4/conf.d/main/03_exim4-config_tlsoptions

Regenerate the configuration and restart exim:

~# update-exim4.conf
~# service exim4 restart

Testing STARTTLS

~$ telnet 2a02:2ca0:aaa::a843:657d 25 
Trying 2a02:2ca0:aaa::a843:657d...
Connected to 2a02:2ca0:aaa::a843:657d.
Escape character is '^]'.
220 mail.bitlair.nl ESMTP Exim 4.80 Sun, 15 Mar 2015 12:37:10 -0400
EHLO mail.bitlair.nl
250-mail.bitlair.nl Hello mail.bitlair.nl [2a02:2ca0:aaa::a843:657d]
250-SIZE 52428800
250-8BITMIME
250-PIPELINING
250-STARTTLS
250 HELP
STARTTLS
220 TLS go ahead

STARTTLS should be listed and give a 220 status code.

Testing TLS

Unfortunately, openssl s_client does not support IPv6, but this the tests the legacy IP listener as well as TLS:

~$ openssl s_client -connect mail.bitlair.nl:25 -starttls smtp -verify 5 -CAfile /etc/ssl/certs/ca-certificates.crt

New, TLSv1/SSLv3, Cipher is DHE-RSA-AES256-SHA256
Server public key is 1024 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
SSL-Session:
    Protocol  : TLSv1.2
    Cipher    : DHE-RSA-AES256-SHA256
    Session-ID: 82CBCEC7833853A674F6399694A03052566C494E1F6DDC8DE2CD4B3A9F8ED528
    Session-ID-ctx: 
    Master-Key: BAE67F5D50E5C1C95FBDF355C1BDE18C6251E13F5B8686977292A54657498EFECFF518290211F24F01C40E39929981C4
    Key-Arg   : None
    PSK identity: None
    PSK identity hint: None
    SRP username: None
    Start Time: 1426438799
    Timeout   : 300 (sec)
    Verify return code: 0 (ok)

SASL Authenticated relay

To enable relaying of messages after authentication, for SPF or just for laptops that are on different connections all the time, SASL authentication needs to be enabled.

Enabling other ports than 25

Port 25 is usually blocked by firewalls, for good reason. An alternative authenticated-only submission port exists on port 587 and a legacy TLS on connect port on 465 also exists.

To enable these, set the following in /etc/default/exim4:

SMTPLISTENEROPTIONS='-oX 25:465:587 -oP /var/run/exim4/exim.pid'

And in /etc/exim4/conf.d/main/03_exim4-config_tlsoptions add the following near the top:

tls_on_connect_ports=465

Regenerate the configuration and restart exim:

~# update-exim4.conf
~# service exim4 restart

SASL authentication

Set the following to /etc/default/saslauthd, this is the default these days

START=yes

In /etc/exim4/conf.d/auth/30_exim4-config_examples, uncomment the section:

plain_saslauthd_server:
  driver = plaintext
  public_name = PLAIN
  server_condition = ${if saslauthd{{$auth2}{$auth3}}{1}{0}}
  server_set_id = $auth2
  server_prompts = :
  .ifndef AUTH_SERVER_ALLOW_NOTLS_PASSWORDS
  server_advertise_condition = ${if eq{$tls_cipher}{}{}{*}}
  .endif

login_saslauthd_server:
  driver = plaintext
  public_name = LOGIN
  server_prompts = "Username:: : Password::"
  # don't send system passwords over unencrypted connections
  server_condition = ${if saslauthd{{$auth1}{$auth2}}{1}{0}}
  server_set_id = $auth1
  .ifndef AUTH_SERVER_ALLOW_NOTLS_PASSWORDS
  server_advertise_condition = ${if eq{$tls_cipher}{}{}{*}}
  .endif

Add the Debian-exim user to the sasl group.

~# usermod -a -G sasl Debian-exim

Regenerate the configuration and restart exim:

~# update-exim4.conf
~# service exim4 restart

Testing

~$ swaks -a -tls -q AUTH -s 2a02:2ca0:aaa::a843:657d -au username
Password: enter_your_password
=== Trying 2a02:2ca0:aaa::a843:657d:25...
=== Connected to 2a02:2ca0:aaa::a843:657d.
<-  220 mail.bitlair.nl ESMTP Exim 4.80 Sun, 15 Mar 2015 16:04:35 -0400
 -> EHLO mail.bitlair.nl
<-  250-mail.bitlair.nl Hello mail.bitlair.nl [2a02:2ca0:aaa::a843:657d]
<-  250-SIZE 52428800
<-  250-8BITMIME
<-  250-PIPELINING
<-  250-STARTTLS
<-  250 HELP
 -> STARTTLS
<-  220 TLS go ahead
=== TLS started w/ cipher DHE-RSA-AES256-SHA256
=== TLS peer subject DN="/C=NL/ST=Utrecht/L=Amersfoort/O=Stichting Bitlair/CN=mail.bitlair.nl"
 ~> EHLO mail.bitlair.nl
<~  250-mail.bitlair.nl Hello vps.bitlair.nl [2a02:2ca0:aaa::a843:657d]
<~  250-SIZE 52428800
<~  250-8BITMIME
<~  250-PIPELINING
<~  250-AUTH PLAIN LOGIN
<~  250 HELP
 ~> AUTH LOGIN
<~  334 VXNlcm5hbWU6
 ~> d2lsY28=
<~  334 UGFzc3dvcmQ6
 ~> YmxpZXA=
<~  235 Authentication succeeded
 ~> QUIT
<~  221 mail.bitlair.nl closing connection
Connection closed with remote host.

Authentication succeeded is what you want!

DNS block lists

Add CHECK_RCPT_IP_DNSBLS to the top of /etc/exim4/conf.d/acl/30_exim4-config_check_rcpt, like my list of DNSBLs:

CHECK_RCPT_IP_DNSBLS=cbl.abuseat.org sbl-xbl.spamhaus.org psbl.surriel.com b.barracudacentral.org dul.dnsbl.sorbs.net spamsources.fabel.dk

Regenerate the configuration and restart exim:

~# update-exim4.conf
~# service exim4 restart

More information exim4 configuration

Spamassassin

Spamassassin configuration

To enable spamd from spamassassin, set the following in /etc/default/spamassassin

ENABLED=1

And add "-u debian-spamd" to the options, you will get something like:

OPTIONS="--create-prefs --max-children 5 --helper-home-dir -u debian-spamd"

And also enable the cron rules updates by setting

CRON=1

You will probably want more terse spam reporting, because it will be in the email headers, to get this, add the following to /etc/spamassassin/local.cf:

clear_report_template
report _YESNO_, score=_SCORE_ required=_REQD_ autolearn=_AUTOLEARN_
report tests=_TESTSSCORES

While you're there, you may want to enable the bayesian classifier, and set up razor and pyzor, and fix RFC-ignorant rules.

#   Use Bayesian classifier (default: 1)
#
use_bayes 1


#   Bayesian classifier auto-learning (default: 1)
#
bayes_auto_learn 1

# Use razor and pyzor
use_razor2 1
use_pyzor 1

# Disable stupid RFC-incompatible spamassassin SPF_NEUTRAL check. Do not add points for SPF_NEUTRAL, as it should be treated the same as having no SPF record.
score SPF_NEUTRAL 0

Now, set up razor and pyzor:

~# razor-admin -home=/etc/mail/spamassassin/.razor -register
~# razor-admin -home=/etc/mail/spamassassin/.razor -create
~# razor-admin -home=/etc/mail/spamassassin/.razor -discover
~# pyzor --homedir /etc/mail/spamassassin discover

Start spamassassin.

~# service spamassassin start

Exim spamd integration

Uncomment the following line in /etc/exim4/conf.d/main/02_exim4-config_options

In /etc/exim4/conf.d/acl/40_exim4-config_check_data, uncomment the following section:
#   warn
#     spam = Debian-exim:true
#     add_header = X-Spam_score: $spam_score\n\
#               X-Spam_score_int: $spam_score_int\n\
#               X-Spam_bar: $spam_bar\n\
#               X-Spam_report: $spam_report

Also replace Debian-exim with debian-spamd, change _report to -Status and dash/upper case the rest. To bounce messages, also add this directly below:

  deny
    message = This message scored $spam_score spam points.
    spam = debian-spamd
    condition = ${if >{$spam_score_int}{120}{1}{0}}

You'll get something like this:

  warn
    spam = debian-spamd:true
    add_header = X-Spam-Score: $spam_score\n\
              X-Spam-Score-Int: $spam_score_int\n\
              X-Spam-Bar: $spam_bar\n\
              X-Spam-Status: $spam_report

  deny
    message = This message scored $spam_score spam points.
    spam = debian-spamd
    condition = ${if >{$spam_score_int}{120}{1}{0}}

Regenerate the configuration and restart exim:

~# update-exim4.conf
~# service exim4 restart

Testing spam blocking

$ telnet 127.0.0.1 25
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
220 mail.bitlair.nl ESMTP Exim 4.80 Sun, 15 Mar 2015 13:53:46 -0400
EHLO mail.bitlair.nl
250-mail.bitlair.nl Hello localhost.localdomain [127.0.0.1]
250-SIZE 52428800
250-8BITMIME
250-PIPELINING
250-STARTTLS
250 HELP
MAIL FROM: <wilco@bitlair.nl>
250 OK
RCPT TO: <wilco@bitlair.nl>
250 Accepted
DATA
354 Enter message, ending with "." on a line by itself
Subject: test            

XJS*C4JDBQADN1.NSBN3*2IDNEN*GTUBE-STANDARD-ANTI-UBE-TEST-EMAIL*C.34X

.
550 This message scored 1002.6 spam points.

You should get error 550 here.

More information on spamassassin

Antivirus

Setting up ClamAV

Update the antivirus database:

~# freshclam
ClamAV update process started at Sun Mar 15 13:57:01 2015
main.cvd is up to date (version: 55, sigs: 2424225, f-level: 60, builder: neo)
daily.cvd is up to date (version: 20194, sigs: 1348078, f-level: 63, builder: dgoddard)
bytecode.cvd is up to date (version: 247, sigs: 41, f-level: 63, builder: dgoddard)

You will get a message about outdated ClamAV if you do not have the latest version from stable-updates. Make sure you have it in your sources list and preferably in the unattended-upgrades list. Also see the troubleshooting section.

Add clamav to the Debian-exim group, so that clamav-daemon can read the message to scan.

~# usermod -a -G Debian-exim clamav

Restart clamav-daemon

~# service clamav-daemon restart

Exim clamd integration

In /etc/exim4/conf.d/main/02_exim4-config_options, uncomment the following line:

av_scanner = clamd:/var/run/clamav/clamd.ctl

In acl/40_exim4-config_check_data, uncomment the following section

  deny
    malware = *
    message = This message was detected as possible malware ($malware_name).

Regenerate the exim4 config and restart exim.

~# update-exim4.conf
~# service exim4 restart

Testing

~$ telnet 2a02:2ca0:aaa::a843:657d 25
Trying 2a02:2ca0:aaa::a843:657d...
Connected to 2a02:2ca0:aaa::a843:657d.
Escape character is '^]'.
220 mail.bitlair.nl ESMTP Exim 4.80 Sun, 15 Mar 2015 14:10:10 -0400
EHLO mail.bitlair.nl
250-mail.bitlair.nl Hello mail.bitlair.nl [2a02:2ca0:aaa::a843:657d]
250-SIZE 52428800
250-8BITMIME
250-PIPELINING
250-STARTTLS
250 HELP
MAIL FROM: <wilco@bitlair.nl>
250 OK
RCPT TO: <wilco@bitlair.nl>
250 Accepted
DATA
354 Enter message, ending with "." on a line by itself
Subject: test

X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*
.
550 This message was detected as possible malware (Eicar-Test-Signature).

You should get error 550 here. This is good.

More information on ClamAV

Setting up DKIM

Contrary to SPF and DMARC, DKIM is actually a good idea. This does not break the internet, I advise everyone to implement DKIM.

The first step to getting DKIM working, is generating an RSA key for DKIM. You will be tempted to use a large key here, but given that you have to add the public key to DNS, do not use more than 1024-bit keys. If you want to use stronger cryptography for this in the future, please consider contributing to the support of ECC crypto in DKIM.

~# mkdir -m 0750 /etc/exim4/dkim_keys
~# chown root:Debian-exim /etc/exim4/dkim_keys

To generate the RSA key pair (repeat per domain):

~# openssl genrsa -out /etc/exim4/dkim_keys/bitlair.nl.private.pem 1024
~# openssl rsa -in /etc/exim4/dkim_keys/bitlair.nl.private.pem -out /etc/exim4/dkim_keys/bitlair.nl.public.pem -pubout -outform PEM

Add the following to the top of /etc/exim4/conf.d/transport/30_exim4-config_remote_smtp:

DKIM_DOMAIN =  ${domain:$return_path}
DKIM_SELECTOR = exim
DKIM_FILE = /etc/exim4/dkim_keys/${lc:${domain:$return_path:}}.private.pem
DKIM_PRIVATE_KEY = ${if exists{DKIM_FILE}{DKIM_FILE}{0}}
DKIM_CANON = relaxed
DKIM_STRICT = true

Update your DNS zones to have the following records:

exim._domainkey IN    TXT   v=DKIM1; k=rsa; p=MIGfMA... <- your base64-encoded public key here
_domainkey         IN    TXT   o=~;

Regenerate the configuration and restart exim:

~# update-exim4.conf
~# service exim4 restart

Testing

Send an email to check-auth@verifier.port25.com from your MTA.

It should respond with something like:

DKIM check:         pass

More information on DKIM

SPF

Please note, before you add SPF: SPF is very broken. It assumes that people do not forward e-mail. It breaks .forward files, procmail forwards, etc. There is a remedy, called Sender Rewriting Scheme, but people are generally not aware of this. If you want to forward to gmail.com, which is known broken, because it mandates SPF records.

Adding SPF to your domain

You can set limits on who can send on behalf of your domain using DNS. However, extremely few mail forwards have implemented SRS. As stated on wikipedia: "Publishers of SPF FAIL policies must accept the risk that their legitimate emails are being rejected or bounced. They should test (e.g., with a SOFTFAIL policy) until they are satisfied with the results.".

Unfortunately, adding SPF to your domain adds to the legitimacy of your domain, and will decrease your chances of getting into a spam box. So it would be wise to set an SPF policy of allowing your mailservers and hosts under your domain, but leave the rest neutral with ?all. -all is guaranteed to break plain mail forwarding. ~all will make sure your mail reaches the spam box.

To have an SPF policy, I advise to not use any fails on your domain, but configure a record like:

@ IN TXT "v=spf1 a mx ?all"

This will ensure that hosts under your domain can pass SPF, as can your mailservers, but it is neutral for other, potentially forwarding hosts.

Adding Sending Rewriting Scheme (SRS)

Be careful with mail forwards, make sure your spam rules are set strictly enough, because your mail server reputation with gmail depends on forwarding mostly non-spam. If you want to do mail forwards to gmail, you need to implement SRS, because gmail on IPv6 is a known broken SPF mail system.

Installing srsd

Create /etc/init.d/srsd with content:

#! /bin/sh

### BEGIN INIT INFO
# Provides:          srsd
# Required-Start:    
# Required-Stop:     
# Should-Start:      
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: SRS daemon
# Description:       SRS daemon
### END INIT INFO

set -e

# /etc/init.d/srsd: start and stop the srsd daemon

DAEMON=/usr/bin/srsd
USER=Debian-exim
SECRETFILE=/etc/srsd.secret
PIDFILE=/var/run/srsd.pid
SOCKETFILE=/tmp/srsd
SRSD_OPTS="--secretfile ${SECRETFILE}"

test -x $DAEMON || exit 0

. /lib/lsb/init-functions

srsd_start() {
    if start-stop-daemon --start --quiet --background \
        --chuid $USER \
        --pidfile $PIDFILE --make-pidfile \
        --exec $DAEMON \
        -- $SRSD_OPTS
    then
        rc=0
        sleep 1
        if ! kill -0 $(cat $PIDFILE) >/dev/null 2>&1; then
            log_failure_msg "srsd daemon failed to start"
            rc=1
        fi
    else
        rc=1
    fi
    if [ $rc -eq 0 ]; then
        log_end_msg 0
    else
        log_end_msg 1
        rm -f $PIDFILE
    fi
} # srsd_start


case "$1" in
  start)
    log_daemon_msg "Starting srsd daemon" "srsd"
    if [ -s $PIDFILE ] && kill -0 $(cat $PIDFILE) >/dev/null 2>&1; then
        log_progress_msg "apparently already running"
        log_end_msg 0
        exit 0
    fi
        srsd_start
    ;;
  stop)
    log_daemon_msg "Stopping srsd daemon" "srsd"
    start-stop-daemon --stop --quiet --oknodo --pidfile $PIDFILE
    log_end_msg $?
    rm -f $PIDFILE
    rm -f $SOCKETFILE
    ;;

  restart)
    set +e
    log_daemon_msg "Restarting srsd daemon" "srsd"
    if [ -s $PIDFILE ] && kill -0 $(cat $PIDFILE) >/dev/null 2>&1; then
        start-stop-daemon --stop --quiet --oknodo --pidfile $PIDFILE || true
        sleep 1
    else
        log_warning_msg "srsd daemon not running, attempting to start."
            rm -f $PIDFILE
    fi
        srsd_start
    ;;

  status)
    status_of_proc -p $PIDFILE "$DAEMON" srsd
    exit $?    # notreached due to set -e
    ;;
  *)
    echo "Usage: /etc/init.d/srsd {start|stop|restart|status}"
    exit 1
esac

exit 0

Now make it executable and run at boot time.

~# chmod 755 /etc/init.d/srsd
~# update-rc.d srsd defaults

Generate a new random secret:

~# touch /etc/srsd.secret
~# chown Debian-exim /etc/srsd.secret
~# chmod 600 /etc/srsd.secret
~# openssl rand -base64 12 > /etc/srsd.secret

Start srsd:

~# service srsd start

Implement srsd integration into exim4

Create a file in /etc/exim4/conf.d/router/175_exim4-config_srs with the following content, replace spf-must-die.org to your own SRS rewriting domain:

srs_bounce:
  debug_print = "R: srs_bounce for $local_part@$domain"
  driver = redirect
  allow_fail
  allow_defer
  domains = spf-must-die.org
  local_part_prefix = srs0+ : srs0- : srs0= : srs1+ : srs1- : srs1=
  caseful_local_part
  address_data = ${readsocket{/tmp/srsd}{REVERSE $local_part_prefix$local_part@$domain}{5s}{\n}{:defer: SRS daemon failure}}
  data = ${if match{$address_data}{^ERROR}{:fail: Invalid SRS address}{$address_data}}


srs_forward:
  debug_print = "R: srs_forward for $local_part@$domain"
  no_verify
  senders = ! : ! *@+local_domains
  address_data = ${readsocket{/tmp/srsd}\
                {FORWARD $sender_address_local_part@$sender_address_domain spf-must-die.org\n}\
                                        {5s}{\n}{:defer: SRS daemon failure}}
  errors_to = ${quote_local_part:${local_part:$address_data}}@${domain:$address_data}
  headers_add = "X-SRS: Sender address rewritten from <$sender_address> to <${quote_local_part:${local_part:$address_data}}@${domain:$address_data}> by $primary_hostname."
  driver = redirect
  repeat_use = false
  allow_defer
  data = ${quote_local_part:$local_part}@$domain

Regenerate the configuration and restart exim:

~# update-exim4.conf
~# service exim4 restart

Testing the SRS forwarding

Create an alias in /etc/aliases to your own mailbox, like this:

user: your@email.address

run newaliases to regenerate the hash table

~# newaliases

Now try to send email to user@your-mail-server.

You should get it in your mailbox.. look at the message source, you will see the following headers if DKIM and SRS are working (note the d=rewriting domain):

Return-Path: <SRS0=eCT+=EL=baanhofman.nl=wilco@spf-must-die.org>
DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=spf-must-die.org; s=exim;
        h=Content-Type:Subject:To:MIME-Version:From:Date:Message-ID; bh=Nh/X++v9YcgLCxTBH1lYXZg22kWTVrM8UJ+92lkoOFQ=;
        b=eC+zN8okGJYoNUgllB9TUb9XDmHVIWYwFiIC+m2gcji5zlM+LIDYEz0Z6tAdZt1vUhugefB7DCLos6aiKwP/jShm2Cn4XTY4U+i0WG1wxf3L9wp8bs1bfo1oJLaI8iyNuSGnUDbQspWwJj1toXp2J1nGkV2MeagggaGr7+GLXro=;
X-SRS: Sender address rewritten from <wilco@baanhofman.nl> to <SRS0=eCT+=EL=baanhofman.nl=wilco@spf-must-die.org> by vps.bitlair.nl.

Enable SPF checking

Enabling SPF checking will break mail forwarding to your domain without SRS on domains that have a fail configured. This will likely block much legitimate e-mail, but if you want to forward to the big players, it's nearly mandatory to do..

To enable inbound SPF checking, add the following to the top of /etc/exim4/conf.d/acl/30_exim4-config_check_rcpt:

CHECK_RCPT_SPF=yes

Testing SPF checking

~$ telnet 2a02:2ca0:aaa::a843:657d 25
Trying 2a02:2ca0:aaa::a843:657d...
Connected to 2a02:2ca0:aaa::a843:657d.
Escape character is '^]'.
220 mail.bitlair.nl ESMTP Exim 4.80 Sun, 29 Mar 2015 14:37:47 +0200
EHLO mail.bitlair.nl
250-mail.bitlair.nl Hello mail.bitlair.nl [2a02:2ca0:aaa::a843:657d]
250-SIZE 52428800
250-8BITMIME
250-PIPELINING
250-STARTTLS
250 HELP
MAIL FROM: <spf-test@openspf.net>
250 OK
RCPT TO: <wilco@bitlair.nl>
550-[SPF] 2001:41d0:52:300::107c is not allowed to send mail from openspf.net. 
550 Please see http://www.openspf.org/Why?scope=mfrom;identity=spf-test@openspf.net;ip=2001:41d0:52:300::107c

You should see a 550 reject here when there is an SPF FAIL.

More information on SPF and SRS

Greylisting

Greylisting is bouncing all email from new senders with a temporary failure code. Implementing this helps a good deal against spammers, at the cost of having to wait for an email from a new sender.

Set up exim

To set up configuration, simply do

~# greylistd-setup-exim4 add
~# service greylist restart

And regenerate the configuration and restart exim4

~# update-exim4.conf
~# service exim4 restart

Testing

~$ telnet 2a02:2ca0:aaa::a843:657d 25
Trying 2a02:2ca0:aaa::a843:657d...
Connected to 2a02:2ca0:aaa::a843:657d.
Escape character is '^]'.
220 mail.bitlair.nl ESMTP Exim 4.80 Sun, 15 Mar 2015 15:27:34 -0400
EHLO mail.bitlair.nl
250-mail.bitlair.nl Hello mail.bitlair.nl [2a02:2ca0:aaa::a843:657d]
250-SIZE 52428800
250-8BITMIME
250-PIPELINING
250-STARTTLS
250 HELP
MAIL FROM: <wilco@bitlair.nl>
250 OK
RCPT TO: <wilco@bitlair.nl>
451-2a02:2ca0:aaa::a843:657d is not yet authorized to deliver mail from
451 <wilco@bitlair.nl> to <wilco@bitlair.nl>. Please try later.

451 is the temporary failure error, this is the desired behaviour.

More information on Greylisting

DMARC

Be careful with DMARC, as DMARC with p=reject breaks your outbound e-mail even further than SPF. With p=reject, you will no longer be able to use mailing lists or mail forwarders, even those with SRS enabled. DMARC checks the 'From:' header, instead of the envelope sender of the mail, so setting DMARC policies will break all of your forwarding, and will make sure that your mail does not reach its destination. I also recommend that you reject all mail with a DMARC policy set on your mailing lists, because the bounces will cause your members to bounce off the mailing list.

Enable this if the risk of impersonation/identity fraud is greater than the risk of your outgoing emails not arriving at their destination. So, if you are paypal, a bank, linkedin, twitter or facebook.. or using this for internal mail only or to signal that nobody should be sending mail on behalf of a domain. If you do not have full control over your users

It does have useful features, like reporting, so setting a p=none can add at least some value to your mail setup.

Set up a mailbox

Create a user in whatever system you're using. If you're not using LDAP or MySQL or something, this should do:

~# useradd dmarc
~# passwd dmarc
Enter new UNIX password: 
Retype new UNIX password: 
passwd: password updated successfully

Publish a DMARC policy

You can add the following DMARC policy to your domain. p=none is important!

_dmarc IN TXT v=DMARC;p=none;rua=dmarc@bitlair.nl;ruf=dmarc@bitlair.nl

Doing something useful with the reports

This section is TODO. I haven't found a good tool yet.

More information on DMARC

LDAP integration

This assumes you already have an LDAP running. To set one up, please see the spacefed guide.

Schema

Load the qmail LDAP schema:

~# ldapadd -H ldapi:/// -Y EXTERNAL << EOF
dn: cn=qmail,cn=schema,cn=config
objectClass: olcSchemaConfig
cn: {4}qmail
olcAttributeTypes: {0}( 1.3.6.1.4.1.7914.1.2.1.1 NAME 'qmailUID' DESC 'UID of 
 the user on the mailsystem' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115
 .121.1.27 SINGLE-VALUE )
olcAttributeTypes: {1}( 1.3.6.1.4.1.7914.1.2.1.2 NAME 'qmailGID' DESC 'GID of 
 the user on the mailsystem' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115
 .121.1.27 SINGLE-VALUE )
olcAttributeTypes: {2}( 1.3.6.1.4.1.7914.1.2.1.3 NAME 'mailMessageStore' DESC 
 'Path to the maildir/mbox on the mail system' EQUALITY caseExactIA5Match SUBS
 TR caseIgnoreIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} SIN
 GLE-VALUE )
olcAttributeTypes: {3}( 1.3.6.1.4.1.7914.1.2.1.4 NAME 'mailAlternateAddress' D
 ESC 'Secondary (alias) mailaddresses for the same user' EQUALITY caseIgnoreIA
 5Match SUBSTR caseIgnoreIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.
 26{256} )
olcAttributeTypes: {4}( 1.3.6.1.4.1.7914.1.2.1.5 NAME 'mailQuota' DESC 'The am
 ount of space the user can use until all further messages get bounced.' SYNTA
 X 1.3.6.1.4.1.1466.115.121.1.44 SINGLE-VALUE )
olcAttributeTypes: {5}( 1.3.6.1.4.1.7914.1.2.1.6 NAME 'mailHost' DESC 'On whic
 h qmail server the messagestore of this user is located.' EQUALITY caseIgnore
 IA5Match SUBSTR caseIgnoreIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.
 1.26{256} SINGLE-VALUE )
olcAttributeTypes: {6}( 1.3.6.1.4.1.7914.1.2.1.7 NAME 'mailForwardingAddress' 
 DESC 'Address(es) to forward all incoming messages to.' EQUALITY caseIgnoreIA
 5Match SUBSTR caseIgnoreIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.
 26{256} )
olcAttributeTypes: {7}( 1.3.6.1.4.1.7914.1.2.1.8 NAME 'deliveryProgramPath' DE
 SC 'Program to execute for all incoming mails.' EQUALITY caseExactIA5Match SU
 BSTR caseIgnoreIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} )
olcAttributeTypes: {8}( 1.3.6.1.4.1.7914.1.2.1.9 NAME 'qmailDotMode' DESC 'Int
 erpretation of .qmail files: both, dotonly, ldaponly, ldapwithprog' EQUALITY 
 caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{32} SINGLE-VALUE )
olcAttributeTypes: {9}( 1.3.6.1.4.1.7914.1.2.1.10 NAME 'deliveryMode' DESC 'mu
 lti field entries of: nolocal, noforward, noprogram, reply' EQUALITY caseIgno
 reIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{32} )
olcAttributeTypes: {10}( 1.3.6.1.4.1.7914.1.2.1.11 NAME 'mailReplyText' DESC '
 A reply text for every incoming message' EQUALITY caseIgnoreMatch SUBSTR case
 IgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{4096} SINGLE-VALUE
  )
olcAttributeTypes: {11}( 1.3.6.1.4.1.7914.1.2.1.12 NAME 'accountStatus' DESC '
 The status of a user account: active, noaccess, disabled, deleted' EQUALITY c
 aseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )
olcAttributeTypes: {12}( 1.3.6.1.4.1.7914.1.2.1.14 NAME 'qmailAccountPurge' DE
 SC 'The earliest date when a mailMessageStore will be purged' EQUALITY numeri
 cStringMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.36 SINGLE-VALUE )
olcAttributeTypes: {13}( 1.3.6.1.4.1.7914.1.2.1.15 NAME 'mailQuotaSize' DESC '
 The size of space the user can have until further messages get bounced.' EQUA
 LITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {14}( 1.3.6.1.4.1.7914.1.2.1.16 NAME 'mailQuotaCount' DESC 
 'The number of messages the user can have until further messages get bounced.
 ' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {15}( 1.3.6.1.4.1.7914.1.2.1.17 NAME 'mailSizeMax' DESC 'Th
 e maximum size of a single messages the user accepts.' EQUALITY integerMatch 
 SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {16}( 1.3.6.1.4.1.7914.1.3.1.1 NAME 'dnmember' DESC 'Group 
 member specified as distinguished name.' EQUALITY distinguishedNameMatch SYNT
 AX 1.3.6.1.4.1.1466.115.121.1.12 )
olcAttributeTypes: {17}( 1.3.6.1.4.1.7914.1.3.1.2 NAME 'rfc822member' DESC 'Gr
 oup member specified as normal rf822 email address.' EQUALITY caseIgnoreIA5Ma
 tch SUBSTR caseIgnoreIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{
 256} )
olcAttributeTypes: {18}( 1.3.6.1.4.1.7914.1.3.1.3 NAME 'filtermember' DESC 'Gr
 oup member specified as ldap search filter.' EQUALITY caseIgnoreIA5Match SUBS
 TR caseIgnoreIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{512} )
olcAttributeTypes: {19}( 1.3.6.1.4.1.7914.1.3.1.4 NAME 'senderconfirm' DESC 'S
 ender to Group has to answer confirmation email.' EQUALITY booleanMatch SYNTA
 X 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE )
olcAttributeTypes: {20}( 1.3.6.1.4.1.7914.1.3.1.5 NAME 'membersonly' DESC 'Sen
 der to Group must be group member itself.' EQUALITY booleanMatch SYNTAX 1.3.6
 .1.4.1.1466.115.121.1.7 SINGLE-VALUE )
olcAttributeTypes: {21}( 1.3.6.1.4.1.7914.1.3.1.6 NAME 'confirmtext' DESC 'Tex
 t that will be sent with sender confirmation email.' EQUALITY caseIgnoreMatch
  SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{4096} 
 SINGLE-VALUE )
olcAttributeTypes: {22}( 1.3.6.1.4.1.7914.1.3.1.7 NAME 'dnmoderator' DESC 'Gro
 up moderator specified as Distinguished name.' EQUALITY distinguishedNameMatc
 h SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 )
olcAttributeTypes: {23}( 1.3.6.1.4.1.7914.1.3.1.8 NAME 'rfc822moderator' DESC 
 'Group moderator specified as normal rfc822 email address.' EQUALITY caseIgno
 reIA5Match SUBSTR caseIgnoreIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.12
 1.1.26{256} )
olcAttributeTypes: {24}( 1.3.6.1.4.1.7914.1.3.1.9 NAME 'moderatortext' DESC 'T
 ext that will be sent with request for moderation email.' EQUALITY caseIgnore
 Match SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{4
 096} SINGLE-VALUE )
olcAttributeTypes: {25}( 1.3.6.1.4.1.7914.1.3.1.10 NAME 'dnsender' DESC 'Allow
 ed sender specified as distinguished name.' EQUALITY distinguishedNameMatch S
 YNTAX 1.3.6.1.4.1.1466.115.121.1.12 )
olcAttributeTypes: {26}( 1.3.6.1.4.1.7914.1.3.1.11 NAME 'rfc822sender' DESC 'A
 llowed sender specified as normal rf822 email address.' EQUALITY caseIgnoreIA
 5Match SUBSTR caseIgnoreIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.
 26{256} )
olcAttributeTypes: {27}( 1.3.6.1.4.1.7914.1.3.1.12 NAME 'filtersender' DESC 'A
 llowed sender specified as ldap search filter.' EQUALITY caseIgnoreIA5Match S
 UBSTR caseIgnoreIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{512} 
 )
olcAttributeTypes: {28}( 1.3.6.1.4.1.7914.1.4.1.1 NAME 'qladnmanager' DESC '' 
 EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 )
olcAttributeTypes: {29}( 1.3.6.1.4.1.7914.1.4.1.2 NAME 'qlaDomainList' DESC ''
  EQUALITY caseIgnoreIA5Match SUBSTR caseIgnoreIA5SubstringsMatch SYNTAX 1.3.6
 .1.4.1.1466.115.121.1.26{256} )
olcAttributeTypes: {30}( 1.3.6.1.4.1.7914.1.4.1.3 NAME 'qlaUidPrefix' DESC '' 
 EQUALITY caseIgnoreIA5Match SUBSTR caseIgnoreIA5SubstringsMatch SYNTAX 1.3.6.
 1.4.1.1466.115.121.1.26{256} SINGLE-VALUE )
olcAttributeTypes: {31}( 1.3.6.1.4.1.7914.1.4.1.4 NAME 'qlaQmailUid' DESC '' E
 QUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {32}( 1.3.6.1.4.1.7914.1.4.1.5 NAME 'qlaQmailGid' DESC '' E
 QUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {33}( 1.3.6.1.4.1.7914.1.4.1.6 NAME 'qlaMailMStorePrefix' D
 ESC '' EQUALITY caseIgnoreIA5Match SUBSTR caseIgnoreIA5SubstringsMatch SYNTAX
  1.3.6.1.4.1.1466.115.121.1.26{256} SINGLE-VALUE )
olcAttributeTypes: {34}( 1.3.6.1.4.1.7914.1.4.1.7 NAME 'qlaMailQuotaSize' DESC
  '' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {35}( 1.3.6.1.4.1.7914.1.4.1.8 NAME 'qlaMailQuotaCount' DES
 C '' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE 
 )
olcAttributeTypes: {36}( 1.3.6.1.4.1.7914.1.4.1.9 NAME 'qlaMailSizeMax' DESC '
 ' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {37}( 1.3.6.1.4.1.7914.1.4.1.10 NAME 'qlaMailHostList' DESC
  '' EQUALITY caseIgnoreIA5Match SUBSTR caseIgnoreIA5SubstringsMatch SYNTAX 1.
 3.6.1.4.1.1466.115.121.1.26{256} )
olcObjectClasses: {0}( 1.3.6.1.4.1.7914.1.2.2.1 NAME 'qmailUser' DESC 'QMail-L
 DAP User' SUP top AUXILIARY MUST mail MAY ( uid $ mailMessageStore $ homeDire
 ctory $ userPassword $ mailAlternateAddress $ qmailUID $ qmailGID $ mailHost 
 $ mailForwardingAddress $ deliveryProgramPath $ qmailDotMode $ deliveryMode $
  mailReplyText $ accountStatus $ qmailAccountPurge $ mailQuotaSize $ mailQuot
 aCount $ mailSizeMax ) )
olcObjectClasses: {1}( 1.3.6.1.4.1.7914.1.3.2.1 NAME 'qmailGroup' DESC 'QMail-
 LDAP Group' SUP top AUXILIARY MUST ( mail $ mailAlternateAddress $ mailMessag
 eStore ) MAY ( dnmember $ rfc822member $ filtermember $ senderconfirm $ membe
 rsonly $ confirmtext $ dnmoderator $ rfc822moderator $ moderatortext $ dnsend
 er $ rfc822sender $ filtersender ) )
olcObjectClasses: {2}( 1.3.6.1.4.1.7914.1.4.2.1 NAME 'qldapAdmin' DESC 'QMail-
 LDAP Subtree Admin' SUP top AUXILIARY MUST ( qlaDnManager $ qlaDomainList $ q
 laMailMStorePrefix $ qlaMailHostList ) MAY ( qlaUidPrefix $ qlaQmailUid $ qla
 QmailGid $ qlaMailQuotaSize $ qlaMailQuotaCount $ qlaMailSizeMax ) )
EOF

Exim LDAP integration

Add the following two routers:

/etc/exim4/conf.d/router/450_exim4-config_ldap_aliases:

ldap_aliases:
  driver = redirect
  allow_fail
  allow_defer
  data = ${lookup ldapm{ \
                  user=cn=exim,ou=System,dc=bitlair,dc=nl \
                  pass=That would be dumb \
                  ldaps://ldap.bitlair.nl/dc=bitlair,dc=nl?mailForwardingAddress?sub?\
                  (&\
                      (objectclass=qmailUser)\
                      (|\
                          (mail=${quote_ldap:$local_part}@${quote_ldap:$domain})\
                          (mailAlternateAddress=${quote_ldap:$local_part}@${quote_ldap:$domain})\
                      )\
                  )} {$value} fail }
  file_transport = address_file
  pipe_transport = address_pipe

/etc/exim4/conf.d/router/451_exim4-config_ldap_users:

ldap_users:
  driver = accept
  condition = ${lookup ldapm{ \
                  user=cn=exim,ou=System,dc=bitlair,dc=nl \
                  pass=Still not that dumb \
                  ldaps://ldap.bitlair.nl/dc=bitlair,dc=nl?uid?sub?\
                  (&\
                       (objectclass=posixAccount)\
                       (|\
                           (mail=${quote_ldap:$local_part}@${quote_ldap:$domain})\
                           (mailAlternateAddress=${quote_ldap:$local_part}@${quote_ldap:$domain})\
                       )\
                  )} {$value} fail }
  transport = lmtp

Testing Exim LDAP integration

To run a lookup:

~# exim -bt -d+lookup wilco@bitlair.nl

To assemble to proper query:

~# exim -be
> ${lookup ldapm{ user=bla pass=bla ldaps://your.ldap.server/dc=your,dc=basedn?mail?sub?mail=wilco@bitlair.nl}}
wilco@bitlair.nl

Should give you back your email address.

Saslauthd LDAP integration

Change the mechanisms to ldap in /etc/default/saslauthd

MECHANISMS="ldap"

Write up a configuration file like this in /etc/saslauthd.conf (use your own LDAP settings):

ldap_bind_dn: cn=exim,ou=System,dc=bitlair,dc=nl
ldap_bind_pw: Your password
ldap_servers: ldaps://ldap.bitlair.nl/
ldap_search_base: dc=bitlair,dc=nl
ldap_filter: (&(objectclass=posixAccount)(uid=%u))

Restart saslauthd.

~# service saslauthd restart

Testing saslauthd LDAP integration

~# testsaslauthd -u johndoe -p secret

Should give back something like:

0: OK "Success."

Next up is testing SASL exim, see the SASL integration section for exim, above here.

More information on LDAP integration

LMTP delivery

If you want to deliver your e-mail via LMTP, just set dc_localdelivery='lmtp' in update-exim4.conf.conf. Then follow either the local or remote delivery section of this guide. Using the remote delivery section has the advantage of allowing full disk crypto on the mail store. You cannot do this on the MTA, because it needs to power on automatically to accept new emails instantly.

Local delivery

For local delivery to dovecot, add this section in /etc/exim4/conf.d/transport/40-exim4_config-lmtp

In transport/40-exim4-config-lmtp, modify the lmtp section to match this:

lmtp:
        driver = lmtp
        socket = /var/run/dovecot/lmtp
        #maximum number of deliveries per batch, default 1
        batch_max = 200

For remote delivery with 3 week hold function via LMTP

Setting up remote delivery via LMTP makes sense, because you can have your mail store encrypted and still receive new emails, because the MTA will keep accepting mails. To set this up, update exim's retry policy for ::1, so that the MTA will hold on to temporarily failed e-mail deliveries for 3 weeks and will retry every 5 minutes.

Add the following in /etc/exim4/conf.d/retry/30_exim4-config:

::::1  *           F,3w,5m

In transport/40-exim4-config-lmtp, modify the lmtp section to match this:

lmtp:
  driver = smtp
  protocol = lmtp
  port = 24
  hosts = ::::1
  allow_localhost
  return_path_add

This will deliver to localhost on port 24. If you want to use a mail store with full disk crypto seperate from your Mail Transfer Agent, configure a secure tunnel from local port 24 to the LDA. That's because LMTP with crypto is not supported properly by dovecot.. and exim4 does not support delivery to TLS-on-connect secure tunnels.. so.. we will configure a pair of stunnels later.


Regenerate the configuration and restart exim:

~# update-exim4.conf
~# service exim4 restart

Dovecot doesn't support TLS for LMTP, so install stunnel4 using apt-get install stunnel4 on both the mailstore and the MTA machine.

On the MTA, create a /etc/stunnel/lmtp.conf, with the following contents:

setuid = stunnel4
setgid = stunnel4
pid = /var/run/stunnel4/lmtp.pid
debug = 7
output = /var/log/stunnel4/stunnel.log
verify = 2
CAfile = /etc/ssl/certs/ca-certificates.crt

options = SINGLE_ECDH_USE
options = SINGLE_DH_USE

[lmtp]
client = yes
accept = ::1:24
connect = mailstore.bitlair.nl:24

; vim:ft=dosini

The other end (in this case dovecot), should have a similar stunnel4 setup.

Create a /etc/stunnel/lmtp.conf, with the following contents:

setuid = stunnel4
setgid = stunnel4
pid = /var/run/stunnel4/lmtp.pid
debug = 7
output = /var/log/stunnel4/stunnel.log

cert = /etc/ssl/mailstore.bitlair.nl-cert.pem
key = /etc/ssl/private/mailstore.bitlair.nl-key.pem

options = SINGLE_ECDH_USE
options = SINGLE_DH_USE

[lmtp]
accept  = :::24
connect = /var/run/dovecot/lmtp

; vim:ft=dosini

Now on both the MTA and the mail store, start the stunnel services.

~# service stunnel4 start

Testing LMTP delivery

To test if LMTP works, you need to have dovecot set up already. Send yourself an e-mail, see if it arrives.

More information on LMTP

Dovecot

Getting a certificate

Generate a certificate for imap.your.domain and get it signed at a CA.

~# openssl req -sha256 -days 3650 -nodes -new -newkey rsa:4096 -keyout /etc/ssl/private/imap.bitlair.nl-key.pem -out /etc/ssl/imap.bitlair.nl-csr.pem

Input your location and contact information. At the CN field, input your mail hostname (imap.bitlair.nl in my case). Do not enter a challenge password.

Submit the -csr.pem file to a CA, store the certificate you get signed from the CA in /etc/ssl/imap.your.domain-cert.pem.

Setting up dovecot

Configuring dovecot should be pretty straightforward. The first step is to disable plain IMAP.

In /etc/dovecot/conf.d/10-master.cf set port = 0 for both imap and pop3 and enable imaps. Only use the TLS-protected ports.

  inet_listener imap {
          port = 0
  }
  inet_listener imaps {
    port = 993
    ssl = yes
  }

In /etc/dovecot/conf.d/10-ssl.conf, disable SSLv3, mandate TLS and configure your certificates:

ssl = required
ssl_cert = </etc/ssl/mailstore.bitlair.nl-cert.pem
ssl_key = </etc/dovecot/private/mailstore.bitlair.nl-key.pem
ssl_protocols = !SSLv2 !SSLv3

Set the mail location where you want your mail delivered in /etc/dovecot/conf.d/10-mail.conf

mail_location = mdbox:/srv/mail/%n

If you want mailbox sharing between users, dovecot needs to setuid all mailboxes with the same user, so set this in /etc/dovecot/conf.d/10-mail.conf

mail_uid = vmail
mail_gid = vmail

Enable the lmtp service in /etc/dovecot/conf.d/10-master.conf and override the user, so LMTP doesn't run as root.

service lmtp {
  user = vmail
  unix_listener lmtp {
  }
}

And also, if you're not going to use LDAP, set the following overrides in /etc/dovecot/conf.d/auth-system.conf.ext

userdb {
  # <doc/wiki/AuthDatabase.Passwd.txt>
  driver = passwd
  # [blocking=no]
  #args =

  # Override fields from passwd
  override_fields = uid=vmail gid=vmail
}

Create the vmail user and create the mail store

~# useradd --system vmail
~# mkdir -m 0700 /srv/mail
~# chown vmail: /srv/mail

To set up sieve filters, uncomment the listen section in /etc/dovecot/conf.d/20-managesieve.conf:

service managesieve-login {
  inet_listener sieve {
    port = 4190
  }
}

Now you're basically done. If you want LDAP integration, continue with the next section.

~# service dovecot restart

Integrating LDAP into dovecot

Make sure dovecot-ldap is installed. Change /etc/dovecot/conf.d/10-auth.conf to include auth-ldap.conf.ext and not include auth-system.conf.ext

Open /etc/dovecot/conf.d/10-auth.conf and change your overrides in the userdb section:

userdb {
  driver = ldap
  args = /etc/dovecot/dovecot-ldap.conf.ext

  # Default fields can be used to specify defaults that LDAP may override
  #default_fields = home=/home/virtual/%u

  # Override fields that are always set to this value, regardless of what LDAP returns
  override_fields = uid=vmail gid=vmail
}

Now open /etc/dovecot/dovecot-ldap.conf.ext, configure your exim search user, base dn and search filter.

You should be done now.

~# service dovecot restart

Testing

Testing logins:

~$ openssl s_client -connect localhost:993
* OK [CAPABILITY IMAP4rev1 LITERAL+ SASL-IR LOGIN-REFERRALS ID ENABLE IDLE AUTH=PLAIN] Dovecot ready.
1 LOGIN username password
1 OK [CAPABILITY IMAP4rev1 LITERAL+ SASL-IR LOGIN-REFERRALS ID ENABLE IDLE SORT SORT=DISPLAY THREAD=REFERENCES THREAD=REFS MULTIAPPEND UNSELECT CHILDREN NAMESPACE UIDPLUS LIST-EXTENDED I18NLEVEL=1 CONDSTORE QRESYNC ESEARCH ESORT SEARCHRES WITHIN CONTEXT=SEARCH LIST-STATUS SPECIAL-USE] Logged in
2 LOGOUT
* BYE Logging out
2 OK Logout completed.

Testing your TLS configuration:

~$ openssl s_client -connect localhost:993 -CAfile /etc/ssl/certs/ca-certificates.crt -verify 5

... ->snip certificate stuff<- ...
    Verify return code: 0 (ok)
---
* OK [CAPABILITY IMAP4rev1 LITERAL+ SASL-IR LOGIN-REFERRALS ID ENABLE IDLE AUTH=PLAIN] Dovecot ready.

You should see return code: 0 (ok), any other return code is bad.

Mailman

Mailman is a mailing list server. Setting this up will enable you to run both public and private, archived mailing lists, compatible with DKIM and SPF. Note that DMARC policies interfere with mailing lists, so either reject mail from DMARC p=reject domains or Munge the from address in the mails..

You will need a mailing list subdomain, for example list.bitlair.nl. This way, Exim will route messages through a pipe if it's destined for a mailing list.

Getting a certificate

Generate a certificate for list.your.domain and get it signed at a CA.

~# openssl req -sha256 -days 3650 -nodes -new -newkey rsa:4096 -keyout /etc/ssl/private/list.bitlair.nl-key.pem -out /etc/ssl/list.bitlair.nl-csr.pem

Input your location and contact information. At the CN field, input your mail hostname (list.bitlair.nl in my case). Do not enter a challenge password.

Submit the -csr.pem file to a CA, store the certificate you get signed from the CA in /etc/ssl/list.your.domain-cert.pem.

Setting up Mailman

First thing is to create a mailman mailing list.

~# newlist mailman
Enter the email of the person running the list: your@email.address
Initial mailman password: 

Set the URL and mailing list domain in /etc/mailman/mm_cfg.py:

#-------------------------------------------------------------
# Default domain for email addresses of newly created MLs
DEFAULT_EMAIL_HOST = 'list.bitlair.nl'
#-------------------------------------------------------------
# Default host for web interface of newly created MLs
DEFAULT_URL_HOST   = 'list.bitlair.nl'

Add the following lines near the end in /etc/mailman/mm_cfg.py to streamline message sending.

# Max recipients for each message
SMTP_MAX_RCPTS = 1000
# Max messages sent in each SMTP connection
SMTP_MAX_SESSIONS_PER_CONNECTION = 1

Configuring Exim for mailman

Unfortunately, mailman 2.1 does not support LMTP yet. It requires a bit more configuration to get the mailman transport going. Mailman 2.2 (development branch now deprecated in favour of 3.0) does have it, so once mailman 3 arrives, upgrade to a more simple configuration set-up than this.

Configure the mailman exim settings in /etc/exim4/conf.d/main/05_exim4-config_mailman_options:

# Mailman macro definitions

# Home dir for the Mailman installation
MM_HOME=/var/lib/mailman

# User and group for Mailman
MM_UID=list
MM_GID=list

#
# Domains that your lists are in - colon separated list
# you may wish to add these into local_domains as well
domainlist mm_domains=list.bitlair.nl

# The path of the Mailman mail wrapper script
MM_WRAP=MM_HOME/mail/mailman
#
# The path of the list config file (used as a required file when
# verifying list addresses)
MM_LISTCHK=MM_HOME/lists/${lc::$local_part}/config.pck

Configure the mailman router in /etc/exim4/conf.d/router/452_exim4-config_mailman_router:

mailman_router:
  driver = accept
  domains = +mm_domains
  require_files = MM_LISTCHK
  local_part_suffix_optional
  local_part_suffix = -admin : \
    -bounces   : -bounces+*  : \
    -confirm   : -confirm+*  : \
    -join      : -leave      : \
    -owner     : -request    : \
    -subscribe : -unsubscribe
  transport = mailman_transport

Configure the mailman transport in /etc/exim4/conf.d/transport/40_exim4-config_mailman_pipe:

mailman_transport:
  driver  = pipe
  command = MM_WRAP \
          '${if def:local_part_suffix \
                {${sg{$local_part_suffix}{-(\\w+)(\\+.*)?}{\$1}}} \
                {post}}' \
          $local_part
  current_directory = MM_HOME
  home_directory    = MM_HOME
  user              = MM_UID
  group             = MM_GID

Configuring apache for mailman

Create a site configuration in /etc/apache2/sites-available/list.bitlair.nl:

<VirtualHost *:443>
        ServerName list.bitlair.nl
        ServerAdmin your@email.address
        DocumentRoot /var/www/
	SSLEngine on
	SSLCertificateFile    /etc/ssl/list.bitlair.nl-cert.pem
	SSLCertificateKeyFile /etc/ssl/private/list.bitlair.nl-key.pem
        SSLCertificateChainFile /etc/ssl/sub.class2.server.sha2.ca.pem
        SSLProtocol all -SSLv2 -SSLv3
        SSLHonorCipherOrder on
        SSLCipherSuite "EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA384 \
              EECDH+ECDSA+SHA256 EECDH+aRSA+SHA384 EECDH+aRSA+SHA256 \
              EECDH !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS"
        
        <Directory /var/www/>
                Options Indexes FollowSymLinks MultiViews
                AllowOverride None
                Order allow,deny
                allow from all
                # This directive allows us to have apache2's default start page
                # in /apache2-default/, but still have / go to the right place
                RedirectMatch ^/$ /cgi-bin/mailman/listinfo
        </Directory>

        ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/
        <Directory "/usr/lib/cgi-bin">
                AllowOverride None
                Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch
                Order allow,deny
                Allow from all
        </Directory>
	Alias /pipermail /var/lib/mailman/archives/public
	Alias /images/mailman /usr/share/images/mailman
	<Directory /var/lib/mailman/archives/public>
	    DirectoryIndex index.html
	</Directory>
</VirtualHost>

Of course, replace list.bitlair.nl with your own list subdomain.

Enable the site and ssl module

~# a2enmod ssl
~# a2ensite list.bitlair.nl

Reload apache's configuration gracefully

~# apache2ctl graceful

Setting up mailing lists

TODO, guided tour:

  • Set DMARC-policies!

Testing Mailman

Browse to your list url, e.g. https://list.bitlair.nl. Create a mailing list and subscribe yourself and a test mail address. Play around with the settings until you're comfortable with it.

To verify your SSL-setup on your URL:

~# openssl s_client -connect list.bitlair.nl:443 -verify 5 -CAfile /etc/ssl/certs/ca-certificates.crt

Should give you the Verification status 0 (ok) again.

More information about mailman

Hardening

Exim4 rate limiting

First, we need to make sure only 1 message is sent per connection, we also want to limit the number of simultaneous connections. For most small mail providers, allowing 20 simulataneous connections should be plenty.

Create a file in /etc/exim4/conf.d/main/04_ratelimiting with contents:

smtp_accept_max_per_connection = 1
smtp_accept_max_per_host = 1
smtp_accept_max = 20
acl_smtp_connect = acl_check_connect

And create the ACL file /etc/exim4/conf.d/acl/50_exim4-connectdelay with contents:

acl_check_connect:
  accept
    delay = 3s

And regenerate the configuration and restart exim4

~# update-exim4.conf
~# service exim4 restart

Testing the banner delay

~$ telnet ::1 25
Trying ::1...
Connected to ::1.
Escape character is '^]'.
EHLO boe
554 SMTP synchronization error
Connection closed by foreign host.

The SMTP banner should only appear after 3 seconds of not giving any inputs.


Rate limiting hosts with iptables

Make sure you input something like this into your firewall. The mask here implies throttling new connections for entire /64s:

ip6tables -A INPUT -p tcp --dport 25 -m state --state NEW -m recent --set --name smtpthrottle --mask ffff:ffff:ffff:ffff::
ip6tables -A INPUT -p tcp --dport 25 -m state --state NEW -m recent --update --name smtpthrottle --seconds 60 --hitcount 6 --mask ffff:ffff:ffff:ffff:: -j DROP

For legacy IP:

iptables -A INPUT -p tcp --dport 25 -m state --state NEW -m recent --set --name smtpthrottle
iptables -A INPUT -p tcp --dport 25 -m state --state NEW -m recent --update --name smtpthrottle --seconds 60 --hitcount 6 -j DROP

This makes sure that nobody can connect more than 6 times per minute to your SMTP server. I also have a port scan wrapper which auto-blacklists bad people, so my firewall looks like this in my executable /etc/network/if-pre-up.d/ip6tables:

#!/bin/bash
(ip6tables -F
ip6tables -X
ipset flush local6
ipset destroy local6
)&>/dev/null


ipset create local6 hash:net hashsize 1024 family inet6
ipset add local6 2001:470:7b66::/48
ipset add local6 fe80::/16
ipset add local6 ::1/128

ip6tables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
ip6tables -A INPUT -m set --match-set local6 src -j ACCEPT
ip6tables -A INPUT -p tcp -m state --state NEW -m recent --rcheck --name badpeople --seconds 3600 --hitcount 5 --mask ffff:ffff:ffff:ffff:: -j DROP
ip6tables -A INPUT -p tcp --dport 25 -m state --state NEW -m recent --set --name smtpthrottle --mask ffff:ffff:ffff:ffff::
ip6tables -A INPUT -p tcp --dport 25 -m state --state NEW -m recent --update --name smtpthrottle --seconds 60 --hitcount 6 --mask ffff:ffff:ffff:ffff:: -j DROP
ip6tables -A INPUT -p tcp --dport 22 -m state --state NEW -m recent --set --name sshthrottle --mask ffff:ffff:ffff:ffff::
ip6tables -A INPUT -p tcp --dport 22 -m state --state NEW -m recent --update --name sshthrottle --seconds 3600 --hitcount 5 --mask ffff:ffff:ffff:ffff:: -j DROP
ip6tables -A INPUT -p tcp --dport 22 -j ACCEPT
ip6tables -A INPUT -p tcp --dport 25 -j ACCEPT
ip6tables -A INPUT -p icmpv6 \! --icmpv6-type redirect -j ACCEPT
ip6tables -A INPUT -p tcp --dport 1:21 -m state --state NEW -m recent --set --name badpeople --mask ffff:ffff:ffff:ffff::
ip6tables -A INPUT -p tcp --dport 1:21 -m state --state NEW -m recent --update --name badpeople --seconds 3600 --hitcount 5 --mask ffff:ffff:ffff:ffff:: -j DROP
ip6tables -A INPUT -p tcp --dport 26:1023 -m state --state NEW -m recent --set --name badpeople --mask ffff:ffff:ffff:ffff::
ip6tables -A INPUT -p tcp --dport 26:1023 -m state --state NEW -m recent --update --name badpeople --seconds 3600 --hitcount 5 --mask ffff:ffff:ffff:ffff:: -j DROP
ip6tables -A INPUT -j DROP

More information on hardening

Troubleshooting

In general, look at the log files /var/log/exim4/mainlog and /var/log/exim4/paniclog. For Clamav, look at /var/log/clamav/clamav.log. For spamd, check out the main syslog in /var/log/syslog.

Most issues are caused by forgetting to regenerate the configuration, try regenerating and restarting exim4 to see if your problem persists.

~# update-exim4.conf
~# service exim4 restart

Forgetting to add the clamav user to the exim group

2015-03-15 14:13:46 1YXD2f-0000D2-BL malware acl condition: clamd: ClamAV 
   returned: /var/spool/exim4/scan/1YXD2f-0000D2-BL/1YXD2f-0000D2-BL.eml: lstat() failed: Permission denied. ERROR

Can be fixed with:

# usermod -a -G Debian-exim clamav

Not using wheezy-updates or jessie-updates repository

WARNING: Your ClamAV installation is OUTDATED!
WARNING: Local version: 0.98.5 Recommended version: 0.98.6
DON'T PANIC! Read http://www.clamav.net/support/faq

Can be fixed by adding the -updates repository to /etc/apt/sources.list, for wheezy:

deb http://ftp.debian.org/debian wheezy-updates main

For Debian jessie:

deb http://ftp.debian.org/debian jessie-updates main

Forgetting to add the exim to sasl group

You'll see something like this in the mainlog:

2015-03-15 16:01:54 plain_saslauthd_server authenticator failed for (mail.bitlair.nl) [2a02:2ca0:aaa::a843:657d]: 
    435 Unable to authenticate at present (set_id=wilco): cannot connect to saslauthd daemon at /var/run/saslauthd/mux: Permission denied

And swaks will tell you:

<~* 435 Unable to authenticate at present

Running the following will fix it:

~# usermod -a -G sasl Debian-exim
~# service exim4 restart

Forgetting to install dovecot-ldap

You cannot find the auth-ldap files in the /etc/dovecot/conf.d or the dovecot-ldap file in /etc/dovecot.

Can be fixed with:

~# apt-get install dovecot-ldap

Forgetting to install dovecot-lmtpd

You've enabled the lmtp service, but when starting dovecot, the UNIX socket /var/run/dovecot/lmtp is not created.

Exim will give messages like:

2015-03-31 09:43:57 1Ycqq4-0004Q8-NP == wilco@bitlair.nl <root@bitlair.nl> R=ldap_users T=lmtp defer (-1): 
    Failed to connect to socket /var/run/dovecot/lmtp for lmtp transport: No such file or directory

Can be fixed with:

~# apt-get install dovecot-lmtpd