Replacing Exim with Postfix

Fast guide to replacing exim4 with postfix on debian 8. Searched and used a lot of resources around the web to get this configuration setup and running. The reason for replacing has to do with the amount of custom maintenance and spam fighting I had to do. Postfix does have a bunch of more simple guides and implementations (like amavis) to fight spam.

step 1

Replace exim and install all deps needed.
This will remove exim, install postfix and a bunch of unpacking programs to support amavis spam filter / virus scanner.

1
2
apt-get remove exim4 exim4-base exim4-config exim4-daemon-light
apt-get install amavisd-new spamassassin clamav clamav-daemon zoo unzip bzip2 libnet-ph-perl libnet-snpp-perl libnet-telnet-perl nomarch lzop arj bzip2 cabextract cpio file gzip lhasa pax rar unrar unzip zip dovecot-sieve postfix

Now reconfigure postfix and answer all questions.

1
sudo dpkg-reconfigure postfix

ie. choose: Internet Site, set your domain name, set your other domain names if they are available.

step 2

Configure /etc/postfix/main.cf to use SASL (ssl) and Dovecot, this is the config I came up with (compatible with the previous installed dovecot configuration found in my Exim setup):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# Dovecot
home_mailbox = Maildir/
smtpd_sasl_auth_enable = yes
smtpd_sasl_security_options = noanonymous
smtpd_sasl_local_domain = $myhostname
broken_sasl_auth_clients = yes
smtpd_sender_restrictions = permit_sasl_authenticated, permit_mynetworks
smtpd_recipient_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination, reject_unknown_sender_domain

# Debian specific: Specifying a file name will cause the first
# line of that file to be used as the name. The Debian default
# is /etc/mailname.
#myorigin = /etc/mailname

smtpd_banner = $myhostname ESMTP $mail_name (Debian/GNU)
biff = no

# appending .domain is the MUA's job.
append_dot_mydomain = no

# Uncomment the next line to generate "delayed mail" warnings
#delay_warning_time = 4h

readme_directory = no

smtpd_tls_security_level = may
smtp_tls_security_level = may
smtp_tls_note_starttls_offer = yes

# TLS parameters - use your own cert files
smtpd_tls_cert_file = /etc/ssl/cybertim.chained.crt
smtpd_tls_key_file = /etc/ssl/cybertim.key
#smtpd_use_tls = yes
#smtpd_tls_auth_only = yes
smtpd_tls_loglevel = 1
tls_random_source = dev:/dev/urandom
smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache
smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache

# See /usr/share/doc/postfix/TLS_README.gz in the postfix-doc package for
# information on enabling SSL in the smtp client.

smtpd_relay_restrictions = permit_mynetworks permit_sasl_authenticated defer_unauth_destination
myhostname = cybertim.net
alias_maps = hash:/etc/aliases
alias_database = hash:/etc/aliases
myorigin = /etc/mailname
mydestination = cybertim.net, localhost
relayhost =
mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128

#mailbox_command = procmail -a "$EXTENSION"
mailbox_command = /usr/lib/dovecot/deliver
mailbox_size_limit = 0
recipient_delimiter = +
inet_interfaces = all

content_filter = amavis:[127.0.0.1]:10024
receive_override_options = no_address_mappings

/etc/postfix/sasl/smtpd.conf should contain:

1
2
pwcheck_method: saslauthd
mech_list: plain login

Start sasld by default and configure it for postfix on debian, set /etc/default/saslauthd

1
2
3
4
5
6
7
START=yes
DESC="SASL Authentication Daemon"
NAME="saslauthd"
MECHANISMS="pam"
MECH_OPTIONS=""
THREADS=2
OPTIONS="-c -m /var/spool/postfix/var/run/saslauthd"

make sure postfix has access to the sasl group:

1
adduser postfix sasl

step 3

Configure all new virus/spam services.
uncomment everything in /etc/amavis/conf.d/15-content_filter_mode so it looks like:

1
2
3
4
5
6
7
8
9
use strict;

@bypass_virus_checks_maps = (
\%bypass_virus_checks, \@bypass_virus_checks_acl, \$bypass_virus_checks_re);

@bypass_spam_checks_maps = (
\%bypass_spam_checks, \@bypass_spam_checks_acl, \$bypass_spam_checks_re);

1;

Change some amavis defaults (personal preference) /etc/amavis/conf.d/20-debian_defaults but this is what I use:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$QUARANTINEDIR = "$MYHOME/virusmails";
$quarantine_subdir_levels = 1; # enable quarantine dir hashing

$log_recip_templ = undef; # disable by-recipient level-0 log entries
$DO_SYSLOG = 1; # log via syslogd (preferred)
$syslog_ident = 'amavis'; # syslog ident tag, prepended to all messages
$syslog_facility = 'mail';
$syslog_priority = 'debug'; # switch to info to drop debug output, etc

$enable_db = 1; # enable use of BerkeleyDB/libdb (SNMP and nanny)
$enable_global_cache = 1; # enable use of libdb-based cache if $enable_db=1

$inet_socket_port = 10024; # default listening socket

$sa_spam_subject_tag = '***SPAM*** ';
$sa_tag_level_deflt = -999; # add spam info headers if at, or above that level
$sa_tag2_level_deflt = 5.5; # add 'spam detected' headers at that level
$sa_kill_level_deflt = 21; # triggers spam evasive actions
$sa_dsn_cutoff_level = 4; # spam level beyond which a DSN is not sent

$sa_mail_body_size_limit = 200*1024; # don't waste time on SA if mail is larger
$sa_local_tests_only = 0; # only tests which do not require internet access?

[...]

add the following to the end of /etc/postfix/master.cf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
amavis unix - - - - 2 smtp
-o smtp_data_done_timeout=1200
-o smtp_send_xforward_command=yes
127.0.0.1:10025 inet n - - - - smtpd

-o content_filter=
-o local_recipient_maps=
-o relay_recipient_maps=
-o smtpd_restriction_classes=
-o smtpd_client_restrictions=
-o smtpd_helo_restrictions=
-o smtpd_sender_restrictions=
-o smtpd_recipient_restrictions=permit_mynetworks,reject
-o mynetworks=127.0.0.0/8
-o strict_rfc821_envelopes=yes
-o receive_override_options=no_unknown_recipient_checks,no_header_body_checks
-o smtpd_bind_address=127.0.0.1

make sure /etc/spamassassin/local.cf has pyzor enabled:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#pyzor
use_pyzor 1
pyzor_path /usr/bin/pyzor
#
##razor
use_razor2 1
razor_config /etc/razor/razor-agent.conf
#
##bayes
use_bayes 1
use_bayes_rules 1
bayes_auto_learn 1

[...]

clamav should be able to access amavis and the other way around:

1
2
adduser clamav amavis
adduser amavis clamav

my freshclam config looked like this in the end /etc/clamav/freshclam.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
DatabaseOwner clamav
UpdateLogFile /var/log/clamav/freshclam.log
LogVerbose false
LogSyslog false
LogFacility LOG_LOCAL6
LogFileMaxSize 0
LogRotate true
LogTime true
Foreground false
Debug false
MaxAttempts 5
DatabaseDirectory /var/lib/clamav
DNSDatabaseInfo current.cvd.clamav.net
ConnectTimeout 30
ReceiveTimeout 30
TestDatabases yes
ScriptedUpdates yes
CompressLocalDatabase no
SafeBrowsing false
Bytecode true
NotifyClamd /etc/clamav/clamd.conf
# Check for new database 24 times a day
Checks 24
DatabaseMirror db.local.clamav.net
DatabaseMirror database.clamav.net

step 4

Restart all services to use their new configs:

1
2
3
4
service clamav-freshclam restart
service amavis restart
service saslauthd restart
service postfix restart

You can check different services and log files to see if everything is working:

1
2
3
sudo service postfix status
sudo service amavis status
tail -f /var/log/mail.log

step 5 - bonus

If you used ~/.forward as a (spam) filter on exim, you will notice some issues.
For postfix you will need to use dovecot-sieve (already installed if you used the apt-get at the top.)

edit /etc/dovecot/conf.d/90-sieve.conf and uncomment:

1
2
3
4
5
plugin {
# The path to the user's main active script. If ManageSieve is used, this the
# location of the symbolic link controlled by ManageSieve.
sieve = ~/.dovecot.sieve
[...]

now remove your ~/.forward file and create ~/.dovecot.sieve
This is my ‘Junk’ filter rules in the sieve file:

1
2
3
4
5
6
7
8
9
require "fileinto";

if header :contains "X-Spam-Flag" "YES" {
fileinto "Junk"
;

}

if header :contains "subject" "Content-filter at" {
fileinto "Junk"
;

}

we’re really done now :-)