This document no longer describes a live configuration; I'll hopefully write up my current Postfix setup at some point.
Assumptions
This is a slightly anonymized version of pseudorandom.co.uk's mail configuration, in which I'll pretend I'm configuring a host whose real name is colo.example.com, which hosts domains example.com, example.org and subdomain.example.com. You can find out what those names are pseudonyms for if you spend 5 minutes doing DNS lookups ;-)
In my configuration, only real system users in /etc/passwd receive mail;
for each user, e.g. "fred", there is a "real" address fred@colo.example.com
.
There are also hosted virtual domains like example.com, example.org and
subdomain.example.com - each address at one of these domains either gets
delivered to one or more system users, or rejected as unknown. You can do
catch-all delivery too, but I don't, for spam reasons.
I'm not using databases for configuration, for simplicity - I don't host many domains.
Simple Exim 4 virtual hosting
apt-get install exim4-daemon-heavy exim4
I used the -heavy
rather than -light
variant so I could have
content scanning using clamav
, which is configured later.
When exim4-config
prompts you, select:
- Internet site
- Use split configuration
- Local hostnames are e.g.
colo.example.com
,colo
andspam.example.com
(there's no need to include your virtual hosts, we'll set that up in a moment).spam.example.com
is for dspam support, which I'll configure later on. Depending which version of Debian you're using, you may need to separate the hostnames with colons:
or with semicolons;
- pay attention to the debconf prompt.
Create an empty directory /etc/exim4/virtual
.
If you want users' mail delivered into a Maildir (for use with Courier or Dovecot IMAP) by default, edit /etc/exim4/update-exim4.conf.conf to include
dc_localdelivery='maildir_home'
To add all your virtual hosts to the list of local hostnames, edit
/etc/exim4/conf.d/main/01_exim4-config_listmacrosdefs
and add
dsearch;/etc/exim4/virtual
to the line that defines local_domains,
so it looks something like::
local_domains = MAIN_LOCAL_DOMAINS : dsearch;/etc/exim4/virtual
(For Debian etch you can create a file
/etc/exim4/conf.d/main/005_local_hostnames
containing
MAIN_LOCAL_DOMAINS = DEBCONFlocal_domainsDEBCONF : dsearch;/etc/exim4/virtual
but it seems that in lenny/sid the configuration scheme has had incompatible
changes again.)
(If you're not using the split configuration like I recommended, you can put this in /etc/exim4/exim4.conf.localmacros instead.)
In /etc/exim4/conf.d/router, add a file 250_local_vdom_aliases::
vdom_aliases:
debug_print = "R: vdom_aliases for $local_part@$domain"
driver = redirect
allow_defer
allow_fail
domains = dsearch;/etc/exim4/virtual
data = ${expand:${lookup{$local_part}lsearch*@{/etc/exim4/virtual/$domain}}}
retry_use_local_part
pipe_transport = address_pipe
file_transport = address_file
# not no_more - we try again without the suffix
vdom_aliases_suffix:
debug_print = "R: vdom_aliases_suffix for $local_part@$domain"
driver = redirect
local_part_suffix = -*
local_part_suffix_optional
allow_defer
allow_fail
domains = dsearch;/etc/exim4/virtual
data = ${expand:${lookup{$local_part}lsearch*@{/etc/exim4/virtual/$domain}}}
retry_use_local_part
pipe_transport = address_pipe
file_transport = address_file
no_more
(If you're not using the "split files" configuration style, add this to
/etc/exim4/exim4.conf.template
just before the real_local
router
instead.)
Now you can just drop a file in /etc/exim4/virtual named after each virtual domain, e.g. /etc/exim4/virtual/example.com should look something like this::
abuse : fred@localhost
postmaster : fred@localhost
fred : fred@localhost
fred.smith : fred@localhost
joe : joe@localhost
joseph.bloggs : joe@localhost
...
where fred
and joe
are local system users. Now mail to
joseph.bloggs@example.com
goes to joe
, and so on.
Also, suffixes are set up, so mail to joe-anything@example.com
also goes to joe
; but these can be overridden in the virtual domain file.
For instance, if Joe runs some mailing lists in example.com under the local
uid joeslists
you can do::
example : joe@localhost
example-users : joeslists@localhost
example-users-request : joe@localhost
example-devel : joeslists@localhost
example-devel-request : joe@localhost
and it'll work.
Greylisting and DNSBLs
You can get simple greylisting as quickly as::
apt-get install greylistd
greylistd-setup-exim4
I reduce the delay from greylisting, and the collateral damage from DNSBLs,
by only greylisting mail if the sender is listed in one of a variety of DNSBLs.
To achieve this, add something like the following to the greylistd stanzas in
30_exim4-config_check_rcpt
and 40_exim4-config_check_data
::
dnslists = sbl-xbl.spamhaus.org \
: list.dsbl.org \
: dnsbl.sorbs.net
(Amended 2007-09-22: I used to use relays.ordb.org too, but that no longer works.)
Add any troublesome hosts (hi Chiark) to /etc/exim4/local_host_whitelist
and they'll be allowed through without any checking or greylisting.
Rejecting viruses
Viruses are bounced at SMTP time - this will usually not actually cause a bounce message (the virus's internal SMTP engine will ignore the SMTP failure), but false positives get a proper bounce message rather than having mail dropped silently, which is advantageous.
(Configuration taken from http://www.debian-administration.org/articles/141)
Add this to the ACL in 40_exim4-config_check_data
:
# Reject messages that have serious MIME errors.
# This calls the demime condition again, but it
# will return cached results.
deny message = Serious MIME defect detected ($demime_reason)
!acl = acl_whitelist_local_deny
demime = *
condition = ${if >{$demime_errorlevel}{2}{1}{0}}
#
# Reject file extensions used by worms.
#
deny message = This domain has a policy of not accepting certain types \
of attachments in mail as they may contain a virus. \
\
This mail has a file with a .$found_extension attachment and \
is not accepted. \
\
If you have a legitimate need to send this attachment, send it
in a compressed archive, and it will then be forwarded to the
recipient.
!acl = acl_whitelist_local_deny
#
deny message = This domain has a policy of not accepting certain types \
of attachments in mail as they may contain a virus. \
\
This mail has a file with a .$found_extension attachment and \
is not accepted. \
\
If you have a legitimate need to send this attachment, send it \
in a compressed archive, and it will then be forwarded to the \
recipient.
!acl = acl_whitelist_local_deny
demime = vbs:bat:pif:scr
.ifdef TEERGRUBE
delay = TEERGRUBE
.endif
# Reject messages containing malware.
deny message = This message contains a virus ($malware_name) and has been rejected
!acl = acl_whitelist_local_deny
malware = *
Filtering spam
apt-get install dspam libdspam7-drv-pgsql
dspam doesn't seem to like using passwordless (uid-based) authentication to PostgreSQL, so when prompted by dbconfig-common, tell it to use "ident" authentication for the administrative user, but "password" authentication for the database user. Leave the password blank and dbconfig-common will generate a random password, then generate a configuration file in /etc/dspam/dspam.d with that password in.
In /etc/dspam/dspam.conf, set, among others:
StorageDriver /usr/lib/dspam/libpgsql_drv.so
TrustedDeliveryAgent "/usr/sbin/exim4 -oi -oMr despammed"
Put this in /etc/exim4/conf.d/router/550_local_dspam:
dspam_router:
no_verify
check_local_user
condition = "${if and { \
{!def:h_X-My-Dspam:} \
{!eq {$received_protocol}{local}} \
{!eq {$received_protocol}{despammed}} \
{ <= {$message_size}{3M}} \
}\
{1}{0}}"
headers_add = "X-My-Dspam: scanned by $primary_hostname, $tod_full"
driver = accept
transport = dspam_transport
dspam_error_spam_router:
driver = accept
domains = spam.example.com
local_part_suffix = -spam
transport = dspam_error_spam_transport
dspam_error_ham_router:
driver = accept
domains = spam.example.com
local_part_suffix = -fp
transport = dspam_error_ham_transport
and this in /etc/exim4/conf.d/transport/40_local_dspam:
dspam_transport:
driver = pipe
command = "/usr/bin/dspam --deliver=innocent,spam --user ${lc:$local_part} -f '$sender_address' -oi -oMr despammed -- %u"
user = dspam
group = dspam
log_output = true
return_fail_output = true
return_path_add = false
message_prefix =
message_suffix =
dspam_error_spam_transport:
driver = pipe
command = "/usr/bin/dspam --source=error --class=spam --user ${lc:$local_part} -f '$sender_address' -oi -oMr despammed -- %u"
user = dspam
group = dspam
log_output = true
return_fail_output = true
return_path_add = false
message_prefix =
message_suffix =
dspam_error_ham_transport:
driver = pipe
command = "/usr/bin/dspam --source=error --class=innocent --user ${lc:$local_part} -f '$sender_address' -oi -oMr despammed -- %u"
user = dspam
group = dspam
log_output = true
return_fail_output = true
return_path_add = false
message_prefix =
message_suffix =
Now if user fred
gets a spam message which wasn't caught, he can bounce it
to fred-spam@spam.example.com
, and if one of his legitimate messages
gets classified as spam (a false positive) he can bounce it to
fred-fp@spam.example.com
.
Users can now filter mail using the X-DSPAM-Result header, for instance in Procmail:
DEFAULT=/home/fred/Maildir/
MAILDIR=/home/fred/Maildir/
:0:
* ^X-DSPAM-Result: spam
.spam/