Exim 4, SpamAssassin and Dovecot (with LMTP and Sieve)

Date
Updated
Kernel
7.3.4-arch1-1 #1 SMP PREEMPT_DYNAMIC | x86_64 GNU/Linux
OS
Arch Linux

 

These are my installation notes for a full mail server. I don't explain a lot here but if it still helps you in some way then that's just fine.

SMTP submission authentication is handled by Dovecot/Exim and standard user accounts will work; just use the local part of a user account email address ie. 'foo' (minus the quotes) instead of 'foo@mydomain.tld' (minus the quotes) for mail client configuration.

# pacman -Syu exim dovecot pigeonhole spamassassin certbot grc

# mkdir -m 0750 /etc/mail/certs
# certbot -d mydomain.tld certonly <--- choose 'standalone web server' and temporarily open port 80 on the router/firewall
# cp /etc/letsencrypt/archive/mydomain.tld/fullchain1.pem /etc/mail/certs
# cp /etc/letsencrypt/archive/mydomain.tld/privkey1.pem /etc/mail/certs 
# chown -R root:exim /etc/mail/certs
# chmod 0640 /etc/mail/certs/privkey1.pem

# mkdir -m 0750 /etc/mail/keys
# cd /etc/mail/keys
# openssl genrsa -out dkim_private.pem 4096
# openssl rsa -in dkim_private.pem -pubout -out dkim_public.pem <--- add contents of dkim_public.pem to DNS
# chown -R root:exim /etc/mail/keys
# chmod 0640 root:exim /etc/mail/keys/dkim_private.pem

# mkdir -m 0750 /var/log/dovecot 
# touch /var/log/dovecot/dovecot.log 
# chown -R dovecot:root /var/log/dovecot 
# chmod 0640 /var/log/dovecot/dovecot.log

# mkdir -m 0750 /var/log/spamassassin 
# touch /var/log/spamassassin/spamd.log 
# chown -R spamd:root /var/log/spamassassin/ 
# chmod 0640 /var/log/spamassassin/spamd.log

 

Run sa-update. Create /etc/systemd/system/spamassassin.service.d/override.conf which contains the information below. Run systemctl daemon-reload and then systemctl restart spamassassin.

[Unit]
Description=SpamAssassin Daemon
After=network.target

[Service]
ExecStart=
ExecStart=/usr/bin/vendor_perl/spamd -x -u spamd --max-children 5 --helper-home-dir --syslog=/var/log/spamassassin/spamd.log
StandardOutput=null
StandardError=null
Restart=always

 

In /etc/mail/spamassassin/local.cf:

skip_rbl_checks 0

trusted_networks 192.168.0.0/24

#bayes_path /var/lib/spamassassin/.spamassassin
use_bayes 1
bayes_auto_learn 1
bayes_file_mode 0660

add_header spam Flag _YESNOCAPS_

dns_query_restriction deny multi.uribl.com

dns_query_restriction deny dnsblock_list.dnswl.org

dns_query_restriction deny bl.score.senderscore.com
dns_query_restriction deny sa-trusted.bondedsender.org
dns_query_restriction deny sa-accredit.habeas.com
dns_query_restriction deny list.dnswl.org

dns_query_restriction deny sorbs.net
dns_query_restriction deny URIBL
dns_query_restriction deny DNSWL
dns_query_restriction deny SURBL

clear_report_template
report "_TESTSSCORES(, )_"

required_score 4.5

whitelist_from contact@somedomain.tld
blacklist_from *@mentalmarketing.tld

 

In /etc/mail/exim.conf:

######################################################################
# EXIM 4 RUNTIME CONFIGURATION #
######################################################################

primary_hostname = mydomain.tld

smtp_banner = $smtp_active_hostname ESMTP $tod_full.

#log_selector = +all

disable_ipv6 = true

local_interfaces = 127.0.0.1 : 192.168.0.10

system_filter = /etc/mail/main_filter <--- File must begin with: # Exim filter

system_filter_user = exim

keep_environment =

domainlist local_domains = @ : superhost.tld : anotherone.tld

domainlist relay_to_domains =

hostlist relay_from_hosts = 127.0.0.1

acl_smtp_connect = acl_check_conn

acl_smtp_rcpt = acl_check_rcpt

acl_smtp_data = acl_check_data

tls_advertise_hosts = *

auth_advertise_hosts = ${if eq {$tls_cipher}{}{}{*}}

tls_certificate = /etc/mail/certs/fullchain1.pem

tls_privatekey = /etc/mail/certs/privkey1.pem

daemon_smtp_ports = 25 : 587

spamd_address = 127.0.0.1 783

qualify_domain = mydomain.tld

exim_user = exim
exim_group = exim

never_users = root

host_lookup = *

rfc1413_query_timeout = 0s

ignore_bounce_errors_after = 5d

timeout_frozen_after = 7d

######################################################################
# ACL CONFIGURATION #
# Specifies access control lists for incoming SMTP mail #
######################################################################

begin acl

acl_check_conn:
drop hosts = /etc/mail/host_reject
message = Connection terminated.

deny condition = ${if eq{$received_port}{587}}
!hosts = localhost : 192.168.0.0/24
message = Submission port access denied for $sender_host_address.

accept

acl_check_rcpt:

accept hosts = :
control = dkim_disable_verify

deny message = Restricted characters in address
domains = +local_domains
local_parts = ^[.] : ^.*[@%!/|]

deny message = Restricted characters in address
domains = !+local_domains
local_parts = ^[./|] : ^.*[@%!] : ^.*/\\.\\./

accept local_parts = postmaster : abuse : root

domains = +local_domains

#require verify = sender

accept hosts = +relay_from_hosts
control = submission
control = dkim_disable_verify

accept authenticated = *
control = submission
control = dkim_disable_verify

deny
message = No rDNS found.
condition = ${if eq{$host_lookup_failed} {1} {1}{0}}

require message = relay not permitted
domains = +local_domains : +relay_to_domains

require verify = recipient

drop hosts = /etc/mail/host_reject
message = Connection refused.

deny
senders = /etc/mail/blocked_senders
message = Mail rejected.

deny
message = Access denied. $sender_host_address found on zen.spamhaus.org.
dnslists = zen.spamhaus.org

accept

acl_check_data:

# Do not scan messages larger than 500KB
accept
condition = ${if >{$message_size}{500000} {1}}

# # Add X-Spam headers and scan via spamd
warn
message = X-Spam-Score: $spam_score
spam = spamd:true

warn
message = X-Spam-Report: $spam_report
spam = spamd:true

deny
message = Spamtastic.
spam = spamd:true
condition = ${if >={$spam_score_int}{43}{1}{0}}

accept

######################################################################
# ROUTERS CONFIGURATION #
# Specifies how addresses are handled #
######################################################################
# THE ORDER IN WHICH THE ROUTERS ARE DEFINED IS IMPORTANT! #
# An address is passed to each router in turn until it is accepted. #
######################################################################

begin routers

dnslookup:
driver = dnslookup
domains = ! +local_domains
transport = remote_smtp
ignore_target_hosts = <; 0.0.0.0 ; 127.0.0.0/8 ; ::1
no_more

system_aliases:
driver = redirect
allow_fail
allow_defer
data = ${lookup{$local_part}lsearch{/etc/mail/aliases}}
file_transport = address_file
pipe_transport = address_pipe

userforward:
driver = redirect
check_local_user
file = $home/.forward
no_verify
no_expn
check_ancestor
file_transport = address_file
pipe_transport = address_pipe
reply_transport = address_reply

localuser:
debug_print = “R: local_user for $local_part@$domain”
driver = accept
domains = +local_domains
check_local_user
local_parts = ! root
transport = dovecot_lmtp
cannot_route_message = Unknown user

######################################################################
# TRANSPORTS CONFIGURATION #
######################################################################
# ORDER DOES NOT MATTER #
# Only one appropriate transport is called for each delivery. #
######################################################################
# A transport is used only when referenced from a router that successfully
# handles an address.

begin transports

remote_smtp:
driver = smtp
port = 25
headers_remove = received : X-Spam-Score : X-Spam-Report
dkim_domain = mydomain.tld
dkim_selector = default
dkim_private_key = /etc/mail/keys/dkim_private.pem
dkim_hash = sha256
dkim_canon = relaxed
dkim_strict = 1
dkim_timestamps = 864000

internal_smtp:
driver = smtp

dovecot_lmtp:
driver = lmtp
socket = /var/run/dovecot/lmtp
batch_max = 200
rcpt_include_affixes

address_pipe:
driver = pipe
return_output

address_file:
driver = appendfile
delivery_date_add
envelope_to_add
return_path_add

address_reply:
driver = autoreply

######################################################################
# RETRY CONFIGURATION #
######################################################################

begin retry

# Address or Domain Error Retries
# ----------------- ----- -------

* * F,2h,15m; G,16h,1h,1.5; F,4d,6h

######################################################################
# REWRITE CONFIGURATION #
######################################################################
# There are no rewriting specifications in this default configuration file.

begin rewrite

######################################################################
# AUTHENTICATION CONFIGURATION #
######################################################################

begin authenticators

dovecot_plain:
driver = dovecot
public_name = LOGIN
server_socket = /var/run/dovecot/exim-auth
server_set_id = $auth1

fixed_plain:
driver = plaintext
public_name = PLAIN
client_send = ^myusername^mypassword


# End of Exim configuration file

 

In /etc/dovecot/dovecot.conf:

 

## Dovecot configuration file

# Dovecot configuration version. This must be the first setting in the
# configuration file. It specifies the configuration syntax, the used setting
# names and the expected default values.

dovecot_config_version = 2.4.5

# Dovecot storage file format version. It specifies the oldest Dovecot version
# that must be able to read files written by this Dovecot instance. The
# intention is that when upgrading Dovecot cluster, this setting is first kept
# as the old Dovecot version. Once the cluster is fully upgraded to a new
# version and there is no intention to rollback to the old version anymore,
# this version number can be increased.

dovecot_storage_version = 2.4.5

# The configuration below is a minimal configuration file using system user authentication.
# See https://doc.dovecot.org/latest/core/config/quick.html

!include_try conf.d/*.conf

# Enable wanted protocols:

protocols {
imap = yes
lmtp = yes
}

mail_driver = maildir
mail_path = ~/Maildir

service imap-login {
inet_listener imap {
port = 0
}

inet_listener imaps {
port = 993
ssl = yes
}
}

auth_failure_delay = 12 secs

auth_mechanisms = plain login

protocol lmtp {

mail_plugins = sieve

auth_username_format = %{user | username}

}

recipient_delimiter = + -

log_path = /var/log/dovecot/dovecot.log

service auth {
unix_listener exim-auth {
mode = 0660
user = exim
group = exim
}
}

service lmtp {
unix_listener /var/run/dovecot/exim-lmtp {
mode = 0600
user = exim
group = exim
}
}

namespace inbox {
inbox = yes
separator = /
}

# Authenticate as system users:
passdb pam {
}

userdb passwd {
}

ssl_server {
cert_file = /etc/letsencrypt/live/mydomain.tld/fullchain.pem
key_file = /etc/letsencrypt/live/mydomain.tld/privkey.pem
}

 

In /home/foo/.dovecot.sieve:

require ["fileinto", "envelope", "regex", "duplicate"];


## spam filing block

if allof ( not header :matches "X-Spam-Score" "-*",
header :regex "X-Spam-Score" "^4\.4$" ) {
fileinto "Spam";
stop;
}

# Continue as/if necessary

if address :is "from" "logs@mydomain.tld" {
fileinto "Logs";
stop;
}

# All other mail should end up in the INBOX