js#vista.png msort nsort
js#vista.png msort nsort
This howto describes one way to build a scalable, secure, full-featured mail platform. It offers virtual hosting of mailboxes in maildir format with support for quotas and server-side filtering, domain aliasing, address aliasing, address forwarding, catchall addresses. Relaying is secured with STARTTLS and SMTP-AUTH. Incoming mails are checked for viruses, spam, and checked against SPF policy and DNSBL.
In order to achieve scalabality the setup will be split accross 3 servers:
1 MX server, where most of the security features sit (faramir.middle.earth) 1 SMTP relay, to allow users to send mails to the outside world (ectelion.middle.earth) 1 Mailstore server, where the mailbox sits (denetor.middle.earth)
Of course more MX can be added using DNS MX records to your domains, more relay servers can be added using DNS round-robin, and more mail stores can be added using mechanisms described in this howto. At the opposite you can merge easily the MX and relay part, or the relay and the mailstore part. Merging MX and mailstore involves some modifications.
Preliminary Note
In this howto we will assume you have a working Debian server. As well sudo is supposed to be installed on the systems and you must be a sudoer.
Configuring LDAP
The users' information will be stored in an LDAP directory. Here we will install it on the relay server.
First let's install the necessary packages:
sudo apt-get install slapd ldap-utils
For the tutorial we will use the following LDAP parameters:
ldapBase: dc=middle,dc=earth
adminDn: cn=admin,dc=middle,dc=earth
adminPwd: thirdAge
In addition we will use a specific LDAP schema. Most of the attributes and objects are standards except one or two. Caution: as there are a lot of standard attributes you will have to take care none of them is defined twice.
So let's add the schema in openldap in /etc/ldap/schema/mailMEO.schema:
attributetype ( 2.16.840.1.113730.3.1.13
NAME 'mailLocalAddress' DESC 'RFC822 email address of this recipient' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} )
attributetype ( 2.16.840.1.113730.3.1.16
NAME 'mailQuota' DESC 'Maiximal amount of disk space for a mailbox in kilobytes' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 )
attributetype ( 2.16.840.1.113730.3.1.18
NAME 'mailHost' DESC 'FQDN of the SMTP/MTA of this recipient' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} SINGLE-VALUE )
attributetype ( 2.16.840.1.113730.3.1.22
NAME 'mailCopyAddress' DESC 'RFC822 email shadow copy address' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} )
attributetype ( 2.16.840.1.113730.3.1.47
NAME 'mailRoutingAddress' DESC 'RFC822 routing address of this recipient' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} )
attributetype ( 2.16.840.1.113730.3.1.49
NAME 'spamassassinUserPrefs' DESC 'SpamAssassin user preferences' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} )
objectclass ( 2.16.840.1.113730.3.2.147
NAME 'inetLocalMailRecipient' DESC 'Internet local mail recipient' SUP top AUXILIARY MAY ( mailLocalAddress $ mailHost $ mailRoutingAddress $ mailCopyAddress $ mailQuota $ spamassassinUserPrefs ) )
objectclass ( 2.16.840.1.113730.3.2.148
NAME 'inetMailForwarder' DESC 'Internet mail Forward Address' SUP top AUXILIARY MAY ( mailHost $ mailRoutingAddress ) )
Then make sure you add the needed schema in /etc/ldap/slapd.conf:
… include /etc/ldap/schema/inetorgperson.schema include /etc/ldap/schema/mailMEO.schema …
… and check the suffix (debconf should have already configured it when you installed slapd):
suffix “dc=middle,dc=earth”
Now we add few ACLs the daemons will need to bind to LDAP.
A readonly access to the userPassword attribute for dovecot:
access to attrs=userPassword,shadowLastChange
by dn="cn=admin,dc=middle,dc=earth" write by dn="uid=dovecot,dc=middle,dc=earth" read by anonymous auth by self write by * none
A readonly access to other attributes for exim and dovecot:
access to *
by dn="cn=admin,dc=middle,dc=earth" write by dn="uid=dovecot,dc=middle,dc=earth" read by dn="uid=exim,dc=middle,dc=earth" read by * read by anonymous none
The last ACL disables anonymous reads but enables reading (search) for every authenticated user, which you may not want.
We can now restart slapd for changes to take effect:
sudo /etc/init.d/slapd restart
We have to create the users of the previous ACL. To do it we will use the following user.ldif file:
dn: uid=exim,dc=middle,dc=earth objectClass: account objectClass: simpleSecurityObject objectClass: top uid: exim userPassword:: e01ENX1hOElTeXAwV2hnVzFSVnhHd0hCNDF3PT0=
dn: uid=dovecot,dc=middle,dc=earth objectClass: account objectClass: simpleSecurityObject objectClass: top uid: dovecot userPassword:: e01ENX1yZGp2Q1lPNmtDRm1scXAyVWQwa0xBPT0=
The user/pass are:
dovecot/dovecotpopper
exim4/eximmta
To feed the directory type:
ldapadd -x -D cn=admin,dc=middle,dc=earth -W < users.ldif
Here is another ldif file that contains sample data (caution, this sample contains IP addresses that won't suit your setup, change them manually):
dn: ou=domains,dc=middle,dc=earth objectClass: organizationalUnit objectClass: top ou: domains
dn: dc=middle.earth,ou=domains,dc=middle,dc=earth dc: middle.earth objectClass: dNSDomain objectClass: top objectClass: inetLocalMailRecipient objectClass: domainRelatedObject objectClass: posixAccount mailLocalAddress: catchall@middle.earth cn: catchall gidNumber: 8 homeDirectory: /var/mail/middle.earth/c/catchall uid: catchall uidNumber: 8 userPassword:: e01ENX1EV3RteGErOFROanJKNUFXZWt1Z0tBPT0= mailQuota: 102400 mailHost: denetor.middle.earth associatedDomain: middle.earth associatedDomain: lotr.middle.earth
dn: uid=sam,dc=middle.earth,ou=domains,dc=middle,dc=earth cn: sam displayName: Sam Gamji gidNumber: 8 homeDirectory: /var/mail/middle.earth/s/sam mail: sam@middle.earth mailHost: 172.16.16.23 mailQuota: 102400 objectClass: inetLocalMailRecipient objectClass: inetOrgPerson objectClass: posixAccount objectClass: top sn: Gamji uidNumber: 8 uid: sam userPassword:: e01ENX1NeVV5M1BxaHkvWWVLaVpyMXlOaExBPT0= mailLocalAddress: sam@middle.earth mailLocalAddress: gamji@middle.earth mailLocalAddress: shire@middle.earth
dn: uid=frodo,dc=middle.earth,ou=domains,dc=middle,dc=earth cn: frodo displayName: Frodo Baggins gidNumber: 8 givenName: Frodo homeDirectory: /var/mail/middle.earth/f/frodo mail: frodo@middle.earth mailHost: 172.16.16.23 mailQuota: 102400 objectClass: inetLocalMailRecipient objectClass: inetOrgPerson objectClass: posixAccount objectClass: top sn: Baggins uidNumber: 8 uid: frodo userPassword:: e01ENX04UGlDRHVnWEdCMmNhRktnbDljTmpRPT0= mailLocalAddress: frodo@middle.earth mailLocalAddress: baggins@middle.earth mailLocalAddress: shire@middle.earth
dn: uid=gmail,dc=middle.earth,ou=domains,dc=middle,dc=earth cn: gmail mail: alxgomz@gmail.com mailHost: 172.16.16.23 mailRoutingAddress: alxgomz@gmail.com objectClass: inetMailForwarder objectClass: inetOrgPerson objectClass: top sn: alias to Gmail address uid: gmail
Configuring MTAs
Exim4 is the MTA we will use on the MX, the relay server and the mailstores.
The Relay server
First we add the volatile repository in the file /etc/apt/source.list.d/volatile.list:
deb http://volatile.debian.org/debian-volatile lenny/volatile main
… and update the apt database:
sudo apt-get update
Then we install exim4 (most people want to proceed with 'internet site' type installation, and let exim listen on 0.0.0.0). Answer YES when prompted for splitted configuration files:
sudo apt-get install exim4-daemon-heavy clamav-daemon clamav-freshclam openssl
In order to use TLS over the SMTP sessions we need to have a certificate. It can be either a certificates delivered by a certification authority, or a self-signed cert. Here we generate a self-signed cert, but be aware that such certificate will produce warnings on the client side.
RSA key:
openssl genrsa 2048
chmod 640 exim.key
Certificate request:
openssl req -new -key exim.key -out exim.csr
Certificate:
openssl x509 -req -signkey exim.key -in exim.csr -days 9999 -out exim.c
File copy:
chown Debian-exim exim.key
sudo cp exim.key exim.crt /etc/exim4
Activate TLS in /etc/exim4/update-exim4.conf.conf:
… MAIN_TLS_ENABLE='true' …
Create the main macro definition file /etc/exim4/conf.d/main/04_mailMEOmacrodefs:
ldap_default_servers = ldap.middle.earth .ifndef MAILMEO_DOMAINROOT MAILMEO_DOMAINROOT = ou=domains,dc=middle,dc=earth .endif
MAILMEO_DOMAINROOT defines the LDAP root dn where we store domains and users info.
As a modern relay server our server will authenticate users before relaying their mail: this is SMTP-AUTH.
In order to do this we will create the new config file /etc/exim4/conf.d/auth/50_mailMEO_authsmtp:
plain_server:
driver = plaintext public_name = PLAIN server_condition = ${if ldapauth \ {user="uid=${quote_ldap_dn:${extract{1}{@}{$2}{$value} fail}},\ dc=${quote_ldap_dn:${extract{2}{@}{$2}{$value} fail}},\ MAILMEO_DOMAINROOT" \ pass=${quote:$3} \ ldap:///}{yes}{no}} server_set_id = $auth2 server_prompts = : .ifndef AUTH_SERVER_ALLOW_NOTLS_PASSWORDS server_advertise_condition = ${if eq{$tls_cipher}{}{}{*}} .endif
login_server:
driver = plaintext public_name = LOGIN server_condition = ${if ldapauth \ {user="uid=${quote_ldap_dn:${extract{1}{@}{$1}{$value} fail}},\ dc=${quote_ldap_dn:${extract{2}{@}{$1}{$value} fail}},\ MAILMEO_DOMAINROOT" \ pass=${quote:$2} \ ldap:///}{yes}{no}} server_set_id = $auth1 server_prompts = "Username:: : Password::" .ifndef AUTH_SERVER_ALLOW_NOTLS_PASSWORDS server_advertise_condition = ${if eq{$tls_cipher}{}{}{*}} .endif
As we store password in a encrypted form we have to use a clear-text password mechanism for authentication: either PLAIN or LOGIN (or both). It is then recommended to advertise AUTH only for crypted SMTP sessions. To make AUTH available even for clears sessions, define AUTH_SERVER_ALLOW_NOTLS_PASSWORDS (eg = true) in the /etc/exim4/conf.d/main/04_mailMEOmacrodefs file.
Additionally we will ask exim to bind on the submission port (587), which *should* be preferred by MUA to submit mails, and to advertise TLS. In /etc/exim4/update-exim4.conf.conf change dc_local_interfaces:
dc_local_interfaces='0.0.0.0:0.0.0.0.587'
Antivirus scanning is done by clamav, and is tightly integrated in exim4. All is needed is activating an option in /etc/exim4/conf.d/main/02_exim4-config_options:
av_scanner = clamd:/var/run/clamav/clamd.ctl
Uncomment 3 lines in /etc/exim4/conf.d/acl/40_exim4-config_check_data:
deny
malware = * message = This message was detected as possible malware ($malware_name).
Add user clamav to the Debian-exim group:
sudo adduser clamav Debian-exim
We restart clamav and exim4 and we're done with the relay server.
sudo /etc/init.d/clamav-daemon restart
sudo /etc/init.d/exim4 restart
The Mailstore server (denetor)
This server hosts the mailboxes on its filesystem. Spam checking is done here too. You may find it strange to scan for spam on the mail store but, to me it's the best way to do it… Let me explain why.
Spam scanning is really a resource hug, so it make sense to do it lately after every other filter (DNSBL or so)have done their jobs. Spam scanning is prone to false positives (at least when you have a lot of mailboxes with different profiles like for an ISP) so it 's very risky to reject mails based such scanning. At last doing spam scanning at smtp time prohibit users specific setting (at least its not coherent as soon as you have multiples recipients for a mail). So deferring spam scanning on mailstore allows to store spam in a junk folder based on scanning that really suits each users because it's done at delivery time.
Enough talking, let's start with packages installation (install just like previously).
To benefit from the best features of dovecot we need to use the 1.2.x version. Unfortunately, Debian chips an old 1.0 version which lacks important features like quotawarning. So we're going to add the backports repository (which are now official Debian repositories). Add the following file: /etc/apt/sources.list.d/backports.list
deb http://backports.debian.org/debian-backports lenny-backports main
and run
sudo apt-get update
sudo apt-get install spamassassin exim4-daemon-heavy
sudo apt-get -t lenny-backports install dovecot-imapd dovecot-pop3d
Definition of exim's macros in /etc/exim4/conf.d/main/04_mailMEOmacrodefs:
ldap_default_servers = ldap.middle.earth # mailMEO macros definitions .ifndef MAILMEO_DOMAINROOT MAILMEO_DOMAINROOT = ou=domains,dc=middle,dc=earth .endif
.ifndef MAILMEO_MAINDOMAIN MAILMEO_MAINDOMAIN = ${lookup ldap {USER=userid=exim,dc=middle,dc=earth PASS=eximmta ldap:///MAILMEO_DOMAINROOT?dc?one?(associatedDomain=$domain)}} .endif domainlist mailMEO_domains = <\n ${sg{${lookup ldapm {\
USER=userid=exim,dc=middle,dc=earth PASS=eximmta \ ldap:///MAILMEO_DOMAINROOT?associatedDomain?one?\ (&(objectClass=inetLocalMailRecipient)(objectClass=dNSDomain)(mailHost=${loo
kup dnsdb{a=$primary_hostname}{$value}fail}))}}}{,}{\\n}}
mailMEO_domains returns the list of domains handled in LDAP. To enable management of a domain in LDAP, just create an LDAP entry using the following template (change it to suit your needs):
dn: dc=%MYDOMAIN.TLD%,ou=domains,dc=middle,dc=earth dc: middle.earth objectClass: dNSDomain objectClass: top objectClass: inetLocalMailRecipient objectClass: domainRelatedObject mailHost: %IPADDR_OF_MAILSTORE% associatedDomain: %MYDOMAIN%
We have to specify exim to accept thoose domains this is done by adding the domainlist to the rcpt acl file /etc/exim4/conf.d/acl/30_exim4-config_check_rcpt: change
require
message = relay not permitted domains = +local_domains : +relay_to_domains
to
require
message = relay not permitted domains = +local_domains : +relay_to_domains : +mailMEO_domains
The MAILMEO_MAINDOMAIN introduces a feature I would call “domain aliasing”. It makes it possible for all addresses living in a domain to also exist in another domain. In the sample data lotr.middle.earth is a domain alias of middle.earth so frodo's mailbox can be reached using address frodo@middle.earth or frodo@lotr.middle.earth. middle.earth is the “main” domain, and is some what privileged. For example authentication (POP/IMAP/SMTP) is only possible using “main domain” credentials. To add a domain alias to am existing domain, just add another “associatedDomain” attribute to the domain object.
Now let's add one router for each type of address. Those routers will defines which message has to be handled by which transport.
The Aliases:
These are secondary addresses for a mailbox. They must belong to the same domain as destination address. One Alias can be added to several mailboxes, in which case all the mailboxes receive mails sent to the alias address. In the sample data, baggins@middle.earth is an alias for frodo@middle.earth and shire@middle.earth is an alias for both, frodo@middle.earth and sam@middle.earth.
To add an alias to an existing mailbox just add a mailLocalAddress attribute with the mail address of the alias.
The file /etc/exim4/conf.d/router/070_mailMEO_alias is the router for such address: mailMEO_alias:
mailMEO_alias:
driver = redirect debug_print = "R: locally aliased from $local_part@$domain" domains = +mailMEO_domains qualify_domain = MAILMEO_MAINDOMAIN check_ancestor = true local_parts = <\n ${sg{\ ${sg{\ ${lookup ldapm \ {USER=userid=exim,dc=middle,dc=earth PASS=eximmta \ ldap:///dc=MAILMEO_MAINDOMAIN,MAILMEO_DOMAINROOT?mailLocalAddress?one?\ (&(objectClass=inetLocalMailRecipient)(objectClass=inetOrgPerson)(mailLocalAddress=$local_part@$domain))}\ }}{([\\w\\-\\.]+)@([\\w\\-]+\\.)([\\w\\-]+)}{\$1}}\ }{,}{\\n}}
data = ${sg{\ ${lookup ldapm \ {USER=userid=exim,dc=middle,dc=earth PASS=eximmta \ ldap:///dc=MAILMEO_MAINDOMAIN,MAILMEO_DOMAINROOT?uid?one?\ (&(objectClass=inetLocalMailRecipient)(objectClass=inetOrgPerson)(mailLocalAddress=$local_part@$domain))}}\ }{([\\w\-\.]+)}{\$1@$domain}\ }
The Forwarders:
Forwarders are addresses that forward mails to one or several addresses. They are quite similar to aliases except that they can forward mails to addresses not belonging to their domains or even remote addresses. To create mail forwarders, create an LDAP entry under the domain entry following the template:
dn: uid=gmail,dc=middle.earth,ou=domains,dc=middle,dc=earth cn: %FWD_LOCALPART% mail: %DEST_MAILADDR% mailHost: %IPADDR_OF_MAILSTORE% mailRoutingAddress: %DEST_MAILADDR% objectClass: inetMailForwarder objectClass: inetOrgPerson objectClass: top sn: Alias address uid: %FWD_LOCALPART%
The file /etc/exim4/conf.d/router/071_mailMEO_fwd this kind of address:
mailMEO_fwd_routes:
driver = redirect debug_print = "R: Forwarded from $local_part@$domain" domains = +mailMEO_domains qualify_domain = MAILMEO_MAINDOMAIN check_ancestor = true forbid_pipe = true forbid_file = true forbid_exim_filter = true local_parts = ${lookup ldap \ {USER=userid=exim,dc=middle,dc=earth PASS=eximmta \ ldap:///dc=MAILMEO_MAINDOMAIN,MAILMEO_DOMAINROOT?uid?one?\ (&(uid=$local_part)(objectClass=inetOrgPerson)(objectClass=inetMailForwarder))}\ } data = ${lookup ldap \ {USER=userid=exim,dc=middle,dc=earth PASS=eximmta \ ldap:///dc=MAILMEO_MAINDOMAIN,MAILMEO_DOMAINROOT?mailRoutingAddress?one?\ (&(uid=$local_part)(objectClass=inetOrgPerson)(objectClass=inetMailForwarder))}\ }
The Catchall:
Catchalls are kind of garbage mailboxes that will receive every mails sent to a domain whatever the localpart is. You can mix regular mailbox and catchall mailbox in a domain (of course only one catchall per domain is allowed). To add a catchaal address to a domain, add the posixAccount to the domain entry (and all the needed attributes), a mailLocalAddress and mailQuota attributes:
objectClass: posixAccount mailLocalAddress: %CATCHALL_ADDR% gidNumber: %gID% homeDirectory: %MAILDIR_PATH% uid: %CATCHALL_LOCALPART% uidNumber: %UID% userPassword:: %HASH_PASS_STR% mailQuota: %KB%
The file /etc/exim4/conf.d/router/079_mailMEO_catchall defines catchall routing:
mailMEO_catchall:
driver = redirect debug_print = "R: domain catchall for $domain <- $local_part" domains = <\n ${sg{${lookup ldapm {\ USER=userid=exim,dc=middle,dc=earth PASS=eximmta \ ldap:///ou=domains,dc=middle,dc=earth?associatedDomain?one?\ (&(objectClass=inetLocalMailRecipient)(objectClass=posixAccount)(objectClass=dNSDomain)(mailHost=$primary_hostname))}}}{,}{\\n}} qualify_domain = MAILMEO_MAINDOMAIN data = ${lookup ldap \ {USER=userid=exim,dc=middle,dc=earth PASS=eximmta \ ldap:///dc=MAILMEO_MAINDOMAIN,MAILMEO_DOMAINROOT?uid?base?}\ }
The Virtual Users:
Well… It's the mailbox that users will check for mails! To create a user add an LDAP entry under the domain following this template:
dn: uid=%LOCALPART%,dc=%DOMAIN%,ou=domains,dc=middle,dc=earth cn: %SOMETHING_DESCRIPTIVE% displayName: %SOMETHING_DESCRIPTIVE% gidNumber: %GID% givenName: %SOMETHING_DESCRIPTIVE% homeDirectory: %MAILDIR_PATH% mail: %EMAIL_ADDR% mailHost: %IPADDR_OF_MAILSTORE% mailQuota: %KB% objectClass: inetLocalMailRecipient objectClass: inetOrgPerson objectClass: posixAccount objectClass: top sn: %SOMETHING_DESCRIPTIVE% uidNumber: %UID uid: %LOCALPART% userPassword:: %HASH_PASS_STR% mailLocalAddress: %EMAIL_ADDR%
Please note that the main email address *MUST* be set as a mailLocalAddress just like aliases.
Routing is done using the file /etc/exim4/conf.d/router/077_mailMEO_users:
mailMEO_virtual:
driver = accept debug_print = "R: mailMEO virtual for $local_part@$domain" domains = +mailMEO_domains local_parts = ${lookup ldap\ {USER=userid=exim,dc=middle,dc=earth PASS=eximmta \ ldap:///dc=MAILMEO_MAINDOMAIN,MAILMEO_DOMAINROOT?uid?sub?\ (&(objectClass=inetLocalMailRecipient)(uid=$local_part))}\ } transport = mailMEO_virtual_delivery
Once done with the routers we have to add several transports.
The first one obvious, it delivers mails in the mailboxes of virtual users. To do so we will use the dovecot LDA because it's reliable and natively implements cool stuffs like quota or sieve filtering (Dovecot rules!).
This is a typical example as described on dovecot wiki and is in the file /etc/exim4/conf.d/transport/50_mailMEO_dovecot:
mailMEO_virtual_delivery:
driver = pipe command = /usr/lib/dovecot/deliver -d $local_part@$domain -f $sender_address message_prefix = message_suffix = delivery_date_add envelope_to_add return_path_add log_output user = mail temp_errors = 64 : 69 : 70: 71 : 72 : 73 : 74 : 75 : 78
Spamassassin:
This router is used to check mails for spam using the spamassassin daemon.
spamcheck_router:
no_verify condition = ${if and { {<{$message_size}{90K}} {!def:header_X-Spam-Flag:} {!eq {$received_protocol}{spam-scanned}}} {1}{0}} driver = accept transport = spamcheck
The spamcheck transport is used to process mails in spamassassin daemon.
SA transport is configured in /etc/exim4/conf.d/transport/50_mailMEO_spamcheck:
spamcheck:
driver = pipe command = /usr/sbin/exim4 -oMr spam-scanned -bS use_bsmtp = true transport_filter = /usr/bin/spamc -u $local_part@$domain home_directory = "/dev/shm" current_directory = "/dev/shm" # must use a privileged user to set $received_protocol on the way back in! user = mail group = mail log_output = true return_fail_output = true return_path_add = false message_prefix = message_suffix =
Let's configure SpamAssassin while we are dealing with it.
Most of the config is stored in /etc/spamassassin/local.cf:
user_scores_dsn ldap://ldap.middle.earth/ou=domains,dc=middle,dc=earth?spamassassinUserPrefs?sub?(&(mailLocalAddress=USERNAME)(objectClass=inetLocalMailRecipient)) user_scores_ldap_username uid=exim,dc=middle,dc=earth user_scores_ldap_password eximmta clear_headers add_header all Flag _YESNO_ add_header spam Result _SCORE_/_REQD_ (_TESTS_)
With this config you can have specific settings for each user, just use the spamassassinUserPrefs attribute using the form 'item value'.
We have to enable spamd in /etc/default/spamassassin:
ENABLED=1 OPTIONS=“-x –ldap-config -u nobody –max-children 5”
… and start it.
sudo /etc/init.d/spamassassin restart
We can now restart exim as well:
sudo /etc/init.d/exim4 restart
At this point, mails can't be sent to the mailstore yet (dovecot must be configured… we'll do it later), and most of the security features are not implemented.
The MX server (faramir):
It's here where we will add security features.
As the MX server will do virus scanning too it needs to have volatile repository in the file /etc/apt/sources.list.d/volatile.list:
deb http://volatile.debian.org/debian-volatile lenny/volatile main
… and the backports for a newer dovecot version, in the /etc/apt/sources.list.d/backports.list:
deb http://backports.debian.org/debian-backports lenny-backports main
Update the apt database:
sudo apt-get update
We can now install the needed packages:
sudo apt-get install clamav-daemon clamav-freshclam exim4-daemon-heavy libmail-spf-query-perl
sudo apt-get -t lenny-backports install dovecot-imapd dovecot-pop3d
Proceed with exim4 installation just like for the relay server.
The file /etc/exim4/conf.d/main/04_mailMEOmacrodefs defines the macros we will use in other config files:
ldap_default_servers = ldap.middle.earth # mailMEO macros definitions .ifndef MAILMEO_DOMAINROOT MAILMEO_DOMAINROOT = ou=domains,dc=middle,dc=earth .endif
.ifndef MAILMEO_MAINDOMAIN MAILMEO_MAINDOMAIN = ${lookup ldap {USER=userid=exim,dc=middle,dc=earth PASS=eximmta ldap:///MAILMEO_DOMAINROOT?dc?one?(associatedDomain=$domain)}} .endif domainlist mailMEO_domains = <\n ${sg{${lookup ldapm {\
USER=userid=exim,dc=middle,dc=earth PASS=eximmta \ ldap:///MAILMEO_DOMAINROOT?associatedDomain?one?\ (&(objectClass=inetLocalMailRecipient)(objectClass=dNSDomain))}}}{,}{\\n}}
.ifndef CHECK_RCPT_IP_DNSBLS CHECK_RCPT_IP_DNSBLS = cbl.abuseat.org:dnsbl.njabl.org:sbl.spamhaus.org .endif
.ifndef CHECK_RCPT_SPF CHECK_RCPT_SPF = true .endif
CHECK_RCPT_SPF enabled SPF checking at SMTP time, and reject mail for which spf check failed.
CHECK_RCPT_IP_DNSBL enables DNSBL lookups. The blacklists used here are trustworthy and should not list smarthosts of big MSP. As a consequence, we will choose to reject mails based on thoose DNSBL instead of just warn (which is the default in exin4). Go to the file /etc/exim4/conf.d/acl/30_exim4-config_check_rcpt and change:
.ifdef CHECK_RCPT_IP_DNSBLS
warn message = X-Warning: $sender_host_address is listed at $dnslist_domain ($dnslist_value: $dnslist_text) log_message = $sender_host_address is listed at $dnslist_domain ($dnslist_value: $dnslist_text) dnslists = CHECK_RCPT_IP_DNSBLS .endif
to
.ifdef CHECK_RCPT_IP_DNSBLS
deny message = Access denied: $sender_host_address is listed at $dnslist_domain ($dnslist_value: $dnslist_text) dnslists = CHECK_RCPT_IP_DNSBLS .endif
We have to specify exim to accept the domains defined by mailMEO_domains in /etc/exim4/conf.d/acl/30_exim4-config_check_rcpt: change
require
message = relay not permitted domains = +local_domains : +relay_to_domains
to
require
message = relay not permitted domains = +local_domains : +relay_to_domains : +mailMEO_domains
Now we enable virus scanning just like we did on the relay server in /etc/exim4/conf.d/main/02_exim4-config_options:
av_scanner = clamd:/var/run/clamav/clamd.ctl
Uncomment 3 lines in /etc/exim4/conf.d/acl/40_exim4-config_check_data:
deny
malware = * message = This message was detected as possible malware ($malware_name).
Add user clamav to the Debian-exim group:
sudo adduser clamav Debian-exim
sudo /etc/init.d/clamav-daemon restart
The main purpose of the MX server is to route mails to the mailstore server where the mailbox is hosted.
In Exim, routing can be done using the manualroute driver, which will send mails to remote hosts using SMTP.
We will needed 2 drivers of this kind to handle users accounts, aliases and forwarders on one side and catchall accounts on the other side.
All is in /etc/exim4/conf.d/router/075_mailMEOroutes:
mailMEO_routes:
debug_print = "R: $local_part@$domain routed with mailMEO_routes to $0" driver = manualroute domains = +mailMEO_domains transport = remote_smtp local_parts = <\n ${sg{\ ${sg{\ ${lookup ldapm \ {USER=userid=exim,dc=middle,dc=earth PASS=eximmta \ ldap:///dc=MAILMEO_MAINDOMAIN,MAILMEO_DOMAINROOT?mailLocalAddress?one?\ (&(objectClass=inetLocalMailRecipient)(mailLocalAddress=$local_part@$domain))}\ }}{([\\w\\-\\.]+)@([\\w\\-]+\\.)([\\w\\-]+)}{\$1}}\ , ${lookup ldap \ {USER=userid=exim,dc=middle,dc=earth PASS=eximmta \ ldap:///dc=MAILMEO_MAINDOMAIN,MAILMEO_DOMAINROOT?uid?one?\ (uid=$local_part)}}\ }{,}{\\n}} route_data = ${lookup ldap \ {USER=userid=exim,dc=middle,dc=earth PASS=eximmta \ ldap:///dc=MAILMEO_MAINDOMAIN,MAILMEO_DOMAINROOT?mailHost?base?}} host_find_failed = defer same_domain_copy_routing = yes
mailMEO_catchall_routes:
debug_print = "R: $local_part@$domain routed with mailMEO_catchall_route to $0" driver = manualroute domains = <\n ${sg{\ ${lookup ldapm {\ USER=userid=exim,dc=middle,dc=earth PASS=eximmta \ ldap:///MAILMEO_DOMAINROOT?associatedDomain?one?\ (&(objectClass=inetLocalMailRecipient)(objectClass=posixAccount)(objectClass=dNSDomain))}}\ }{,}{\\n}} transport = remote_smtp route_data = ${lookup ldap \ {USER=userid=exim,dc=middle,dc=earth PASS=eximmta \ ldap:///dc=MAILMEO_MAINDOMAIN,MAILMEO_DOMAINROOT?mailHost?base?}} host_find_failed = defer same_domain_copy_routing = yes
A quick explanation: this router first validate the domain is stored in ldap, then it checks that the address exists and at last requests the hostname of the mail server where the mailbox sits.
We don't need to do anything else for the MX concerning the MTA part. So we restart exim and will come back later for the dovecot part.
sudo /etc/init.d/exim4 restart
Configuring Dovecot
Dovecot is used by users to retrieve mails via POP3 or IMAP.
Of course Dovecot has to be installed on the mailstore, but if if we have several mailstores (or if we plan to add more). We will need a POP/IMAP proxy that will “route” connexion to the mailstore hosting the mailbox we want to retrieve mails from. Dovecot being a really great POP/IMAP server, it is also possible to make it act as proxy on the mx server (but it can be placed somewhere else).
Dovecot on the mailstore:
We will now configure dovecot on the mailstore. There are 2 configuration files to edit, the global /etc/dovecot/dovecot.conf:
protocols = imap imaps pop3 pop3s managesieve disable_plaintext_auth = no log_timestamp = “%Y-%m-%d %H:%M:%S ” mail_location = maildir:%h/MailDir mail_privileged_group = mail #mail_debug = yes first_valid_uid = 8 last_valid_uid = 8 first_valid_gid = 8 last_valid_gid = 8 protocol imap {
mail_plugins = quota imap_quota
} protocol pop3 {
pop3_uidl_format = %08Xu%08Xv mail_plugins = quota
} protocol managesieve {
login_executable = /usr/lib/dovecot/managesieve-login mail_executable = /usr/lib/dovecot/managesieve
} protocol lda {
postmaster_address = postmaster@denetor.middle.earth hostname = denetor@middle.earth mail_plugins = quota sieve auth_socket_path = /var/run/dovecot/auth-master sieve_global_path = /var/sieve/global sieve=~/.dovecot.sieve
} auth default {
mechanisms = plain login passdb ldap { args = /etc/dovecot/dovecot-ldap.conf } userdb ldap { args = /etc/dovecot/dovecot-ldap.conf } userdb prefetch { } user = root socket listen { master { path = /var/run/dovecot/auth-master mode = 0660 group = mail } client { path = /var/run/dovecot/auth-client mode = 0660 group = mail } }
} dict { } plugin {
quota = maildir:User quota quota_warning = storage=90%% /usr/local/bin/quota-warning.sh 90 sieve_before = /var/sieve/global
}
… and the ldap part configuration file /etc/dovecot/dovecot-ldap.conf:
uris = ldap://ldap.middle.earth dn = uid=dovecot,dc=middle,dc=earth dnpass = dovecotpopper ldap_version = 3 base = dc=%d,ou=domains,dc=middle,dc=earth scope = subtree user_attrs = homeDirectory=home,uidNumber=uid,gidNumber=gid,mailQuota=quota_rule=*:storage=%$ user_filter = (&(objectClass=inetLocalMailRecipient)(objectClass=posixAccount)(uid=%n)) pass_attrs = mailRoutingAddress=user,userPassword=password,homeDirectory=userdb_home,uidNumber=userdb_uid,gidNumber=userdb_gid,mailQuota=userdb_quota_rule=*:storage=%$ pass_filter = (&(objectClass=inetLocalMailRecipient)(objectClass=posixAccount)(uid=%n)) default_pass_scheme = LDAP-MD5
As part of the sieve filtering we have defined a global filter that will be used to store in the 'Junk' mailfolder mails classified as spam by spamassassin.
sudo mkdir /var/sieve
This filter is /var/sieve/global:
require “fileinto”; if header :contains [“X-Spam-Flag”] [“Yes”] {
fileinto "Junk"; stop;
}
sudo chown mail -R /var/sieve
As part of the quota plugin we need to create a short script that will warn users in case they reach the threshold limit. It will sit in /usr/local/bin/quota-warning.sh:
#!/bin/sh
PERCENT=$1 FROM=“postmaster@denetor.middle.earth” qwf=“/tmp/quota.warning.$$”
echo “From: $FROM To: $USER To: postmaster@domain.org Subject: Your email quota is $PERCENT% full Content-Type: text/plain; charset='UTF-8'
This message is automatically created by mail delivery software.
The size of your mailbox has exceeded a warning threshold that is set by the system administrator. You *must* delete mails or empty some folders or you may loose emails in the future.”» $qwf
cat $qwf | /usr/sbin/sendmail -f $FROM “$USER” rm -f $qwf
exit 0
sudo chmod +x /usr/local/bin/quota-warning.sh
Dovecot on the MX:
On this host we configure Dovecot to act like a proxy. Proxying to the right mailstore in controlled by the LDAP attribute mailHost. Even if each user has its own mailHost attribute, the configuration presented in this howto involves a domain must be hosted on a single mailstore.
The dovecot config is as follows, /etc/dovecot/dovecot.conf:
protocols = imap imaps pop3 pop3s managesieve disable_plaintext_auth = no log_timestamp = “%Y-%m-%d %H:%M:%S ” login_process_per_connection = no login_processes_count = 8 mail_uid = 8 mail_gid = 8 mail_privileged_group = mail first_valid_uid = 8 last_valid_uid = 8 first_valid_gid = 8 last_valid_gid = 8 protocol imap { } protocol pop3 {
pop3_uidl_format = %08Xu%08Xv
} protocol managesieve { } auth default {
mechanisms = plain login passdb ldap { args = /etc/dovecot/dovecot-ldap.conf } userdb passwd { } userdb static { } user = nobody
} dict { } plugin { }
… and /etc/dovecot/dovecot-ldap.conf:
uris = ldap://ldap.middle.earth dn = uid=dovecot,dc=middle,dc=earth dnpass = dovecotpopper base = dc=%d,ou=domains,dc=middle,dc=earth pass_attrs==nopassword=1,=password=,=proxy=y,mailHost=host,=destuser=%u pass_filter = (&(objectClass=inetLocalMailRecipient)(objectClass=posixAccount)(uid=%n))
Extra Notes:
If you want to use several mailstores and benefit of bayesian filtering site wide, you can use an sql database to store bayesian data and share it on all mailstores (unfortunately, bayesian storage in ldap in not possible now). To manage users, domains and all kind of addresses you can use any ldap client, as long as you follow the examples in sample data. PHPLDAPAdmin can be a good alternative if you don't feel confortable with ldif format. There dozens of webmail around the web which you can use in this setup. I personnaly have a preferrence for roundcube webmail which offers features that integrates very well here, mostly because it has plugins for sieve filtering, LDAP directory and more. As users informations are stored in LDAP it is quite easy to setup a centralized address book that can be queried by many mail clients. If you use such directory, most clients will use the "mail" LDAP Attribute, so addresses not defined as mail won't show up during search.
Security Notes:
In this howto, all operations are run by the "mail" user. This may not be wise to do it in production (read "real internet") environment. You can create a specific user that would replace the "mail" user, tweaking the config will then be necessary. Dovecot is shipped with default certicates that should be replaced in production environment (the procedure for exim relay applies).