Most of the online documentation that we can currently find about setting up this insane pile of moving parts are either outdated or frustrating by their lack of clear explanations. I wanted to learn how to build a reasonably secure mail system and to understand how things interact with each other on such a setup so that I can later debug problems that arise with it.
Most of the steps can be found in different other howtos, but they're generally split apart the Internet. I'll be stitching, cutting, upgrading and completing the steps needed to have a full setup with the postfixadmin web interface. You can find the bibliography at the end of the article.
I will probably skip over some details if there's already a good howto that explains things very well. I'll try and point out those reference materials as I go along, so make sure to consult them if there's something you don't understand.
Note: this howto can very well be used with Debian Wheezy. Simply skip the steps about adding "apt" sources and pinning for backports and wheezy, and start with package installation directly.
Objectives
We want to install a full mail stack with encrypted IMAP and SMTP access for client connections (no plaintext on the network whenever possible), and also classic unencrypted SMTP for reception to the domains our server will be hosting, because it's unfortunately necessary. This server will give users control over e-mail redirections and "vacation" auto-replies. The whole thing needs to filter out unwanted crap like spam and virii.
Implementation
In order to give users control over mailboxes and mail aliases, and also over auto-reply messages, we'll install postfixadmin. We'll need to integrate the underlying tools to postfixadmin's database.
Since I already have MySQL, Apache2 and PHP5 in place on the target machine, I won't cover installation of these components. We'll be concentrating on the mail-related software in this howto, and the database and webserver setup will be left out as an exercise. You can easily find documentation about setting up a LAMP server on the Internetz.
Visualising the message flow
With e-mail, the way I like to conceptualize things is by imagining the path the email will follow along the course of its life. From the sender, to the SMTP server, via a bunch of checks, then to storage, and to the receiver via IMAP.
The setup can be complexified for scaling to a lot more users with larger infrastructures in order to include multiple underlying machines. Simply make sure to always see the whole system as a pipeline through which an email transits. For this howto, we'll be keeping things relatively simple and install everything on the same machine.
We will be using Milters (mail filters). Milter comes from Sendmail, and is a protocol used for filtering mail during or after the SMTP session (reception). They usually use a unix socket file to communicate with the MTA, which is faster than initializing TCP connections to localhost. They let your server decide whether it wants to accept the email or not while it is still being fed to it -- in other words, before the mail is even queued -- so it can potentially free your system of some costly (slow) disk operations.
Postfix is fully capable of delivering mail to users' Maildir storage, but for performance reasons, we'll let Dovecot handle final delivery. This is because Dovecot automatically generates an index of messages while storing it to disk, which should make accessing them via IMAP much faster.
Let's visualize things a bit. They say a picture is worth a thousand words, so let's use words to draw a picture:
,--> spamass-milter <--> SpamAssassin
other MTAs |,--> clamav-milter <--> ClamAV
_____ |,--> milter-greylist
d[ o_0 ]b |
,-----. ----> SMTP (Postfix ------------+-> Dovecot-LDA
|| ::: || <-. unauthenticated) / |
{{ }} \ / |
`--to domains local v
not hosted recipient Local Maildir
here / storage
,,,, encrypted \ / |
(^__^) ,------> SMTP (Postfix -----' |
v__--__v / authenticated) /
[] /
/ \ <-------- IMAP (Dovecot) <--------------'
/ \ encrypted
user
We'll build the whole thing by somewhat following the arrows, building small steps at a time and testing that everything works properly after every step. We'll start with unauthenticated SMTP and see if mail gets to the the local mailboxes, then we'll add Dovecot and see if we can retrieve the stored email, and finally we'll setup the authenticated SMTP and see if we can use it to send email abroad. The milters will be kept out till the end: this'll keep the other steps easier to test because we won't risk having our tests rejected or delayed.
Installing
If you're using wheezy, skip adding squeeze-backports to sources.list.d and the pin in preferences.d. Also, you won't need to specify any release from which to install packages (everything can just come from wheezy directly).
We'll be installing dovecot 2.x, which is in squeeze-backports:
echo deb http://backports.debian.org/debian-backports squeeze-backports main > /etc/apt/sources.list.d/squeeze_backports.list
The postfixadmin package is only available in wheezy. However, since it's quite simply a web application that doesn't require specific versions of its dependencies, we can install the package directly in squeeze. So first, we'll want to ensure we can access wheezy packages, but that they don't upgrade automatically:
echo deb http://debian.mirror.iweb.ca/debian wheezy main > /etc/apt/sources.list.d/wheezy.list
cat > /etc/apt/preferences.d/no_auto_wheezy <<EOF
Package: *
Pin: release n=squeeze
Pin-Priority: 900
Package: *
Pin: release n=wheezy
Pin-Priority: -10
EOF
Move on to installing packages. We'll want to install postfixadmin's dependencies from squeeze, but the package itself from wheezy:
apt-get update
apt-get install postfix postfix-mysql openssl ssl-cert
apt-get install php5-imap php5-mysql wwwconfig-common dbconfig-common
apt-get -t squeeze-backports install dovecot-imapd dovecot-mysql
apt-get -t wheezy install postfixadmin
Let postfixadmin create its own database during installation. This'll make our work easier.
Configuring
Priming the database
Since we'd like to be able to test each step of the configuration, we'll need to start by building the database structure and by priming it with some data. This might seem a bit backwards, since our starting point is the web app that will control accounts when everything is set up, but actually the web app only depends on the presence of the database and the web server to let us play with it (e.g. you can control accounts as long as you have a database. Postfix and Dovecot will be using the data from the database when we'll tell them to).
We'll start by modifying the config file to our needs. Let's use the /etc/postfixadmin/config.local.php
file so that our changes survive package upgrades. Here's an example of what you might want to override (declare) in this file:
<?
$CONF['default_language'] = 'en';
$CONF['admin_email'] = 'some-email@example.com';
$CONF['default_aliases'] =array (
'abuse' => 'abuse@example.com',
'hostmaster' => 'hostmaster@example.com',
'postmaster' => 'postmaster@example.com',
'webmaster' => 'webmaster@example.com'
);
$CONF['domain_path'] = 'YES';
$CONF['domain_in_mailbox'] = 'NO';
$CONF['aliases'] = 0; // optional : I'm setting a default of unlimited aliases for new domains
$CONF['mailboxes'] = 0; // idem
$CONF['backup'] = 'NO'; // In my case, the users won't need this (I'll automate the backup)
$CONF['sendmail'] = 'NO'; // It's better to have a real webmail
$CONF['fetchmail'] = 'NO'; // This could be useful, but for now I prefer simplifying the interface
$CONF['user_footer_link'] = 'http://your.host/postfixadmin/';
$CONF['footer_text'] = 'Return to http://your.host/postfixadmin/';
$CONF['footer_link'] = 'http://your.host/postfixadmin/';
$CONF['welcome_text'] = <<<EOM
Hi,
Welcome to the team, sonny! Here at "someprovider" inc. we pride ourselves in our world domination efforts.
blah blah blah blah
EOM;
$CONF['create_mailbox_subdirs_prefix'] = ''; // recommended value that should be used with Dovecot.
$CONF['theme_logo'] = 'images/logo-default.png'; // optional.. it's the right-most part of a URL so it needs to be accessible via your web server
$CONF['theme_css'] = 'css/default.css'; // idem
Go to the postfixadmin's setup page on your web server: http://your.host/postfixadmin/setup.php This will create the tables in which postfixadmin keeps accounts and other info.
Next we need to create a setup password. This password will ensure that setup.php
is not used by just anybody to create unwanted "super admin" accounts. Enter a password for the setup page and send the form. Then, copy the line that's printed out with your password's hash (should look like a line with $CONF in the example configuration overrides above) and paste it at the end of /etc/postfixadmin/config.local.php
.
Visit setup.php
again (reload the page). This time, type in the setup password, then an email address, and enter a password for the new super admin account and send the form. This email address needs to be from a domain that actually exists, not on the server you're setting up, else you'll get the message "Admin is not a valid email address!". If you really need to use an address from a domain that does not resolve, you can add the line "$CONF['emailcheck_resolve_domain']='NO';
" to config.local.php
). That email will then have super admin privileges on the data: this means that it can administrate all of the domains understood by postfix and manage administrator accounts (other accounts that can administrate a subset of the domains).
Now go to the main postfix admin page and login with the super admin you've just created. Once inside, create a new domain: in the menus, hover over 'Domain List' and click on 'New Domain'. For this howto, I'll create the domain example.com
Create a new mailbox by hovering over 'Virtual List' and clicking on 'Add Mailbox'. I'll create the mailbox named someone
, thus creating the e-mail someone@example.com
.
Now we should have enough info in the database for testing the next steps.
Basic SMTP with virtual domains and mailboxes with Postfix
Create the directory that will hold the mailboxes for the virtual accounts and give it to the mail user so that Dovecot, our final LDA, can create directories and files in there:
mkdir /var/mail/vmail
chown mail:mail /var/mail/vmail
Create a read-only user on the postfixadmin database:
mysql -p -e "GRANT SELECT ON postfixadmin.* TO postfix@localhost IDENTIFIED BY 'something';"
We'll put all config files for accessing Postfix's virtual resources (in the database) via MySQL into a directory that we need to create:
mkdir /etc/postfix/virtual
Now, under the directory we've just created, we'll create a bunch of config files:
relay_domains.cf
user = postfix
password = something
hosts = localhost
dbname = postfixadmin
query = SELECT domain FROM domain WHERE domain='%s' AND backupmx = true
alias_maps.cf
user = postfix
password = something
hosts = localhost
dbname = postfixadmin
query = SELECT goto FROM alias a INNER JOIN domain d ON a.domain=d.domain WHERE a.address='%s' AND d.active = true AND a.active = true
domains_maps.cf
user = postfix
password = something
hosts = localhost
dbname = postfixadmin
query = SELECT domain FROM domain WHERE domain='%s' AND backupmx = false AND active = true
mailbox_maps.cf
user = postfix
password = something
hosts = localhost
dbname = postfixadmin
query = SELECT maildir FROM mailbox m INNER JOIN domain d ON m.domain=d.domain WHERE m.username='%s' AND m.active = true AND d.active = true
mailbox_limits.cf
user = postfix
password = something
hosts = localhost
dbname = postfixadmin
query = SELECT quota FROM mailbox m INNER JOIN domain d ON m.domain=d.domain WHERE m.username='%s' AND m.active = true AND d.active = true
If you've read the tutorials that I link to in the bibliography, or searched for tutorials about virtual domains and mailboxes at all, you might have observed that contrary to all the other tutorials out there, I don't use simple SELECT
statements in alias_maps.cf
, mailbox_maps.cf
and mailbox_limits.cf
. This is because what they teach you is buggy! When you disable a domain in postfixadmin you expect that domain to cease working altogether. With simple SELECT
statements you can still login to individual mailboxes and send out e-mail even though the domain has been disabled! So to fix that I use an INNER JOIN
on the domain
table to check whether the appropriate domain is active or not. With this, when you disable a domain in the web interface, it stops working for real; expect support calls if users were still working with their accounts at that moment
The newly created files contain a database password so you might like to tighten permissions a little bit so that only the postfix daemon is authorized to read them:
chown -R root:postfix /etc/postfix/virtual
chmod 0750 /etc/postfix/virtual
chmod 0640 /etc/postfix/virtual/*.cf
With the above permissions you should be fine, but if the postfix daemon logs a bunch of messages like the following in /var/log/mail.err
, it means the daemon can't access the files in the /etc/postfix/virtual
directory so you should fix their permissions:
Sep 21 23:59:39 debian-squeeze postfix/proxymap[2510]: fatal: open /etc/postfix/virtual/relay_domains.cf: Permission denied
Now that we have everything, we need to indicate to postfix that it needs to use those configuration files:
postconf -e 'relay_domains = proxy:mysql:/etc/postfix/virtual/relay_domains.cf'
postconf -e 'virtual_alias_maps = proxy:mysql:/etc/postfix/virtual/alias_maps.cf'
postconf -e 'virtual_mailbox_domains = proxy:mysql:/etc/postfix/virtual/domains_maps.cf'
postconf -e 'virtual_mailbox_maps = proxy:mysql:/etc/postfix/virtual/mailbox_maps.cf'
postconf -e 'virtual_create_maildirsize = yes'
postconf -e 'virtual_mailbox_extended = yes'
postconf -e 'virtual_mailbox_limit_maps = proxy:mysql:/etc/postfix/virtual/mailbox_limit_maps.cf'
postconf -e 'virtual_mailbox_limit_override = yes'
postconf -e "virtual_maildir_limit_message = Sorry, the user's maildir has overdrawn his diskspace quota, please try again later."
postconf -e 'virtual_overquota_bounce = yes'
postconf -e 'virtual_mailbox_base = /var/mail/vmail'
postconf -e "virtual_minimum_uid = $(id -u mail)"
postconf -e 'virtual_transport = dovecot'
postconf -e 'dovecot_destination_recipient_limit = 1'
postconf -e "virtual_uid_maps = static:$(id -u mail)"
postconf -e "virtual_gid_maps = static:$(id -g mail)"
postconf -e 'transport_maps = hash:/etc/postfix/transport'
touch /etc/postfix/transport
postmap /etc/postfix/transport
Now add the following line to the end of /etc/postfix/master.cf
:
dovecot unix - n n - - pipe
flags=DRhu user=mail:mail argv=/usr/lib/dovecot/deliver -f ${sender} -d ${user}@${nexthop} -a ${recipient}
And reload config:
postfix reload
To save up on storage space used, we'll enable reading and writing gzip files in Dovecot. Delivery and retrieval will use a little more CPU with this setting, but e-mail files should take at least twice as less disk space. Add the following line to /etc/dovecot/conf.d/10-mail.conf
:
mail_plugins = zlib
And in /etc/dovecot/conf.d/90-plugin.conf
, add configuration for the plugin inside the plugin block, like this:
plugin {
zlib_save_level = 6
zlib_save = gz
}
XXX: here there's a bug: dovecot doesn't know about users at this point because we haven't configured its userdb dictionary method. You can either comment out "virtual_transport" to have postfix do the final delivery, or configure userdb and all glue from next section (auth with dovecot) before testing delivery. Since this HOWTO is getting really old, I won't reorganize everything, but a notice about this problem seemed required.
Now restart Dovecot to enable the plugin:
service dovecot restart
Testing delivery
There we are, we have a functional virtual mailboxes-based SMTP reception system. It's not yet fit for relaying mail for users of the domains your server is hosting, but that'll come later. If you'd like to test out your setup, you can use the following:
apt-get install swaks
swaks --to=someone@example.com --server=localhost
ls -l /var/mail/vmail/example.com/someone/new # You should see the delivered mail there.
Mail retrieval with Dovecot
Next stop, mail retrieval with dovecot. In dovecot 2.x, most of the configuration has been broken down into files in /etc/dovecot/conf.d
, but some of it stayed in files laying in /etc/dovecot
. Let's begin by configuring the SQL connection. We need to tell dovecot where to find user passwords. Remove contents of /etc/dovecot/dovecot-sql.conf.ext
and replace it with the following:
driver = mysql
connect = host=127.0.0.1 dbname=postfixadmin user=postfix password=something
default_pass_scheme = MD5-CRYPT
password_query = SELECT username as user, password, CONCAT('/var/mail/vmail/', maildir) as userdb_home, 8 as userdb_uid, 8 as userdb_gid FROM mailbox INNER JOIN domain ON mailbox.domain=domain.domain WHERE username='%u' AND domain.active = true AND mailbox.active = true;
user_query = SELECT CONCAT('/var/mail/vmail/', maildir) as home, 8 as uid, 8 as gid FROM mailbox INNER JOIN domain ON mailbox.domain=domain.domain WHERE username='%u' AND domain.active = true AND mailbox.active = true;
In a similar fashion to what we did in the postfix SQL config files, the above queries will prevent users to login to dovecot and postfix (via SASL for sending out email) when their domain has been disabled.
Now, replace the contents of /etc/dovecot/conf.d/10-mail.conf
with the following:
mail_location = maildir:%h
mail_uid = mail
mail_gid = mail
first_valid_uid = 8
first_valid_gid = 8
namespace inbox {
inbox = yes
}
Replace the contents of 10-auth.conf
by:
auth_mechanisms = plain login
passdb {
driver = sql
args = /etc/dovecot/dovecot-sql.conf.ext
}
userdb {
driver = sql
args = /etc/dovecot/dovecot-sql.conf.ext
}
Open up the file 10-master.conf
in an editor and somewhere near the end of the file, uncomment and change the line #user = root
so that it looks something like the following:
service auth-worker {
user = nobody
}
Replace the contents of 10-ssl.conf
by the following. For the howto, we'll be using the self-signed certificate that's created by the dovecot package upon installation. If you're using your own certificate change the path to point to the right files for your case:
ssl = required
ssl_cert = </etc/ssl/certs/dovecot.pem
ssl_key = </etc/ssl/private/dovecot.pem
Now, restart the service:
service dovecot restart
Testing mail retrieval with Dovecot
And now we should be able to test a connection. There's no quick and handy tool for testing this out, so brace yourselves: we'll have to make an IMAP session manually (it's not very complicated, really). From your computer, or another point which should have acces to the IMAP server:
openssl s_client -connect youhost.domain:143 -starttls imap
[... you'll see a bunch of info about the SSL certificate bein printed out]
. login someone@example.com something
[... list of capabilities]
. select inbox
[... if you did at least one test in the last step, you should see 1 EXISTS in the output]
. fetch 1 rfc822.text
[... message body]
. logout
Let's try logging in without encryption to see if we get through (we shouldn't !). If the server responds that login was successful there's a problem somewhere.
telnet localhost 143
. login someone@example.com something
* BAD [ALERT] Plaintext authentication not allowed without SSL/TLS, but your client did it anyway. If anyone was listening, the password was exposed.
. logout
Mail delivery for authenticated users
We're close to going round the loop in the graph. We'll configure SMTP to let people relay mail to external domains (e.g. send mail to domains that we are not hosting), but only authenticated users should be able to do that, else you have an open relay and can be the source of much spam in the world! Authentication and reception of e-mails that are to be relayed elsewhere needs to be encrypted.
We'll need to configure Postfix so that it knows how to behave with SSL connections, and then we'll tell Postfix to use Dovecot's SASL library for authentication (Postfix will use Dovecot's authentication mechanism we configured earlier).
Dovecot SASL
First things first: let's tell Dovecot how to "expose" SASL, its login facilities. In /etc/dovecot/conf.d/10-master.conf
, modify the block "service auth" so that it looks like this:
service auth {
unix_listener /var/spool/postfix/private/auth {
mode = 0660
user = postfix
group = postfix
}
}
When you're done, restart dovecot:
service dovecot restart
Postfix STARTTLS + SASL on port 25
Let's now work on Postfix. First, configure TLS. For this example, I'll use the same cert as was used with Dovecot:
postconf -e 'smtpd_tls_cert_file = /etc/ssl/certs/dovecot.pem'
postconf -e 'smtpd_tls_key_file = /etc/ssl/private/dovecot.pem'
postconf -e 'smtp_tls_session_cache_database = btree:$data_directory/smtp_tls_session_cache'
postconf -e 'smtpd_tls_session_cache_database = btree:$data_directory/smtpd_tls_session_cache'
postconf -e 'smtp_tls_security_level = may'
postconf -e 'smtpd_tls_security_level = may'
postconf -e 'smtpd_tls_ask_ccert = no'
postconf -e 'smtpd_tls_loglevel = 0'
postconf -e 'tls_random_source = dev:/dev/urandom'
Then, we want to tell it to use dovecot's SASL style authentication:
postconf -e 'smtpd_sasl_auth_enable = yes'
postconf -e 'smtpd_tls_auth_only = yes'
postconf -e 'smtpd_sasl_type = dovecot'
postconf -e 'smtpd_sasl_path = private/auth'
postconf -e 'smtpd_sasl_exceptions_networks = $mynetworks'
postconf -e 'smtpd_sasl_security_options = noanonymous'
postconf -e 'smtpd_sasl_tls_security_options = noanonymous'
postconf -e 'broken_sasl_auth_clients = yes'
postconf -e 'smtpd_relay_restrictions = permit_mynetworks,permit_sasl_authenticated,reject_unauth_destination'
postconf -e 'smtpd_recipient_restrictions = permit_mynetworks,reject_non_fqdn_recipient,permit_sasl_authenticated,reject_unauth_destination,reject_rbl_client zen.spamhaus.org,reject_rhsbl_helo dbl.spamhaus.org,reject_rhsbl_sender dbl.spamhaus.org'
postconf -e 'smtpd_sender_restrictions = permit_mynetworks,reject_unknown_sender_domain'
postconf -e 'smtpd_helo_restrictions = reject_invalid_helo_hostname'
postconf -e 'smtpd_data_restrictions = reject_unauth_pipelining,reject_multi_recipient_bounce,permit'
# Those two configs should help a little bit with older spammers
postconf -e 'smtpd_helo_required = yes'
postconf -e 'disable_vrfy_command = yes'
postfix reload
Postfix STARTTLS + SASL on port 587 (mail submission)
Some ISPs have taken very drastic measures to block some worms and spam and they decided to entirely block port 25 out of home users. For those users, mail delivery will not work. So we need to configure Postfix to listen to another port: the Mail Submission port, 587.
In /etc/postfix/master.cf
, right above the line that starts with "smtp inet", add the following:
submission inet n - - - - smtpd
-o smtpd_tls_security_level=encrypt
-o smtpd_sasl_auth_enable=yes
-o smtpd_sasl_type=dovecot
-o smtpd_sasl_path=private/auth
-o smtpd_sasl_security_options=noanonymous
-o smtpd_client_restrictions=permit_sasl_authenticated,reject
-o smtpd_sender_login_maps=proxy:mysql:/etc/postfix/virtual/alias_maps.cf
-o smtpd_sender_restrictions=reject_sender_login_mismatch
-o smtpd_recipient_restrictions=reject_non_fqdn_recipient,permit_sasl_authenticated,reject
Now reload postfix config:
postfix reload
Testing mail relay
To test mail relay for authenticated users, you should go to another computer which should have access to the mail server.
First, let's see if things work. Watch out not to put any sensible password here. you can remove the --auth-password argument to have it ask the password upon startup, which will be echoed to the screen (d'oh!!):
swaks --tls --from someone@example.com --to external@email_address --server server.domain:25 --auth LOGIN --auth-user someone@example.com --auth-password something
swaks --tls --from someone@example.com --to external@email_address --server server.domain:587 --auth LOGIN --auth-user someone@example.com --auth-password something
Now let's verify that relaying without being authenticated fails:
# This should give "Relay access denied"
swaks --tls --from someone@example.com --to external@email_address --server server.domain:25
# This should give "Client host rejected: Access denied"
swaks --tls --from someone@example.com --to external@email_address --server server.domain:587
Filtering out undesired crap
Now that we're able to receive and send email and let users read them, we should make our users' lives better by filtering crap out. We'll use four different tools to reject undesired messages: ClamAV, greylisting, SpamAssassin and Sender Policy Framework checking.
Like described at the beginning of the howto, we'll be using Milters so that we can reject crap before queueing it on the disk, so legitimate senders will know that their message didn't get through with a bounce that states a reason why it wasn't accepted.
In all cases, we'll want to set postfix to accept mail by default when a milter fails to function properly, else we'd loose e-mails because of the errors:
postconf -e 'milter_default_action = accept'
clamav-milter
Scanning mail with ClamAV is all about preventing virii and also a bunch of phishing and scamming messages from getting to users. This helps you to remove infections from spreading on your corporate / community computers (because -- yes -- some people will open those infected files if they reach them). Running ClamAV uses a good amount of RAM, so if you're setting up a mail system for yourself and you think you'll be wise enough to avoid infected files, you can skip this step. However, scanning for virii is almost mandatory for a system that will deliver mail for a good number of users, especially when a bunch of users are not computer geeks. But even for computer geeks, the sheer volume of crap that can get to their email is going to be annoying them badly.
So let's start. First, install the milter and some additional signature files for filtering more email-related badness (we need to install the backports version of the package to get up-to-date signatures correctly):
apt-get install clamav-milter
apt-get -t squeeze-backports install clamav-unofficial-sigs
Next, we need to tell it to let postfix have write access to it's socket. Edit /etc/default/clamav-milter
and uncomment the last line:
SOCKET_RWGROUP=postfix
Note that if you are running wheezy with the wheezy-updates source (and you should!) with the clamav-milter version 0.98.5, the file in /etc/default
doesn't exist anymore and the bug with regards to the socket permission has been fixed. So you should skip editing the file as mentioned just above and instead ensure you have the following in your /etc/clamav/clamav-milter.conf
:
MilterSocketGroup postfix
MilterSocketMode 660
Postfix runs in a chroot by default, so we'll have to reconfigure the milter to place its socket inside postfix's chroot directory. But first, we need to create a directory for the socket:
mkdir /var/spool/postfix/clamav
chown clamav /var/spool/postfix/clamav
We also want to change the action taken when an infected e-mail is detected to reject it immediately. To ensure our config stays through upgrades, we'll do it the debian way: run dpkg-reconfigure clamav-milter
and answer the following:
Handle configuration automatically --> yes
User for daemon --> clamav
Additional groups --> none (empty field)
path to socket --> /var/spool/postfix/clamav/clamav-milter.ctl
group owner for the socket --> clamav
permissions (mode) for socket --> 660
remove stale socket --> yes
wait timeout for clamd --> 120
foreground --> no
chroot --> none (empty field)
pid file --> /var/run/clamav/clamav-milter.pid
temporary path --> /tmp
clamd socket --> unix:/var/run/clamav/clamd.ctl
hosts excluded for scanning --> none (empty field)
mail whitelist --> none (empty field)
action for "infected" mail --> reject
action on error --> defer
reason for rejection --> Rejecting harmful email: %v found.
headers -> replace
log file --> /var/log/clamav/clamav-milter.log
disable log file locking --> no
maximum log file size --> 0
log time --> yes
use syslog --> no
log facility (type of syslog message) --> LOG_LOCAL6
verbose logging --> no
log level when infected --> off
log level when no threat --> off
size limit for scanned messages --> 25
The above reconfiguration should have automatically restarted clamav-milter. Verify that the socket is in place. Normally, the clam daemon should be enabled in the boot procedure, but it's possible that you need to start it manually right after install: service clamav-daemon start
Next, we'll configure Postfix to use the milter to inspect incoming e-mail. Remember the smtp process is running inside a chroot, so the path needs to have its root at the top of the chroot dir:
postconf -e 'smtpd_milters = unix:/clamav/clamav-milter.ctl'
postfix reload
Testing the filter by sending a virus
To test this out, we'll have to send a malicious file to the SMTP server in the hope that it will block it. For this, you can install the package clamav-testfiles
on your computer; it will install sample virus files under /usr/share/clamav-testfiles
. Let's send one as an e-mail attachment:
swaks --to=someone@example.com --server=localhost --attach - --suppress-data < /usr/share/clamav-testfiles/clam.exe
You should see the rejection message you configured towards the end:
<** 550 5.7.1 Rejecting harmful email: ClamAV-Test-File found.
spamass-milter
Next thing we want to add in the filtering pipe is spam filtering. SpamAssassin is a powerful solution used by many for this purpose. We'll integrate it with postfix via a milter so that we can reject very awful mail right up front.
Let's start by installing the necessary packages:
apt-get install spamass-milter
Now, in /etc/default/spamass-milter
we want to add "-m" so that it doesn't change the subject header (required for running as a milter with Postfix), "-r -1" so that it rejects what SpamAssassin flags as spam and "-I" to avoid scanning mail sent by logged-in users:
OPTIONS="-u spamass-milter -i 127.0.0.1 -m -r -1 -I"
Restart the milter:
service spamass-milter restart
The spamass-milter connects, by default, to a spamd instance running on the same server (localhost). We need to configure SpamAssassin and start it. However, since it runs under root by default, we'll start by creating a dedicated user for the daemon:
adduser --shell /bin/false --home /var/lib/spamassassin --disabled-password --disabled-login --gecos "" spamd
Open up /etc/default/spamassassin
and change three lines in it to enable the daemon, the automatic update for rules and also to add options so that we use the user we created above instead of "root" and so that it uses the helper directory we created above. The lines should look like the following:
ENABLED=1
CRON=1
OPTIONS="--create-prefs --max-children 5 --helper-home-dir=/var/lib/spamassassin -u spamd -g spamd -x"
Now let's update the rules for the first time and restart the daemon:
sa-update
service spamassassin restart
We have all the needed pieces, so let's tell postfix to use the milter:
postconf -e 'smtpd_milters = unix:/clamav/clamav-milter.ctl, unix:/spamass/spamass.sock'
postfix reload
Testing the spam filter
For testing this, we'll use the special string from GTUBE. This string triggers a special rule that sets the score to a very high value so that SpamAssassin flags the mail as spam without a doubt.
Since we installed the package "clamav-unofficial-sigs" earlier, ClamAV will be catching a bunch of spams, phishing and scams by signature. When I first tried using the GTUBE signature to verify spam filtering, it was filtered by ClamAV instead of SpamAssassin, which is not what we want to verify here. So I've had to disable the clamav milter in /etc/postfix/main.cf
so that the emails goes straight to spamass-milter.
On your computer, download a text file with the GTUBE signature line and use it as the body of a test email:
wget -O /tmp/gtube.txt https://spamassassin.apache.org/gtube/gtube.txt
swaks --from some_existing@email.address --to=someone@example.com --server=your.domain --body=/tmp/gtube.txt
The email should be blocked with the message:
<** 550 5.7.1 Blocked by SpamAssassin
Don't forget to re-enable the clamav milter after your tests are done.
milter-greylist
Greylisting is a simple method that drives off a good portion of dumb spammer bots. It bases on the assumption that spammers won't come back after their deed is done. So when it first receives mail from a sender, it refuses it with a "Temporarily unavailable" error message that has the purpose to make the client try again later. The sender is then accepted from the second trial onward. Since most spammers don't come back, they'll get that defferal message and it'll prevent them from sending you their crap.
The reason we're installing this milter last is that it can be a bit annoying when testing the other milters to have to wait around 30mins before being able to send actual tests. However, since greylisting is lightweight and cuts out a bunch of old spammers, we'll place it as the first one in the list so that we avoid running ClamAV and SpamAssassin on most useless spam.
Let's install the milter:
apt-get install milter-greylist
Edit the config file /etc/milter-greylist/greylist.conf
place the socket file under Postfix's chroot directory. We'll also want to edit the ACLs properly so that the milter works like we intend it to (you probably want to edit the list "my network" so that if reflects your server's real situation). Here's a condensed version of the resulting config file. Add more ACLs as you see fit:
pidfile "/var/run/milter-greylist.pid"
dumpfile "/var/lib/milter-greylist/greylist.db" 600
dumpfreq 10m
socket "/var/spool/postfix/milter-greylist/milter-greylist.sock" 660
user "greylist"
quiet
list "my network" addr { 127.0.0.1/8 192.0.2.0/24 }
list "broken mta" addr { \
[... cut for brevity. You might consider keeping this list here from default config]
}
racl whitelist list "my network"
racl whitelist list "broken mta"
racl greylist default delay 30m autowhite 30d
Now, edit /etc/default/milter-greylist
and set the following (make sure that the socket path is good, since the value that is commented out by default points to a different filename than the one set by default in the milter config file -- the filename that we're using):
ENABLED=1
SOCKET="/var/spool/postfix/milter-greylist/milter-greylist.sock"
Let's create the directory where the socket will be held. Because of a longstanding bug in the debian package we need to hack our way to having the right permissions for the socket. We can then restart the milter:
mkdir /var/spool/postfix/milter-greylist
chmod 2755 /var/spool/postfix/milter-greylist
chown greylist:postfix /var/spool/postfix/milter-greylist
service milter-greylist restart
The only step left is to let Postfix know how to use that milter. We'll want to place it before the two milters we set above since they are both very slow and CPU intensive. This way, greylisting will remove dumb spammers while also avoiding them to overload your CPU (which could easily lead to a denial of service):
postconf -e 'milter_connect_macros = i b j _ {daemon_name} {if_name} {client_addr}'
postconf -e 'smtpd_milters = unix:/milter-greylist/milter-greylist.sock, unix:/clamav/clamav-milter.ctl, unix:/spamass/spamass.sock'
postfix reload
Testing Greylist
If you want to have a better understanding of what's going on, you can comment out the line "quiet" in /etc/milter-greylist/greylist.conf
. This will make the rejection message specify how much time is left for the greylisting.
Send an e-mail to a user on the server in the same manner as you did above when testing delivery, although for this test you'll need to send the e-mail from another computer (since we whitelisted 127.0.0.1):
swaks --to=someone@example.com --server=your.host
You should get rejected with a message looking like this:
<** 451 4.7.1 Greylisting in action, please come back in 00:23:33
Wait until that period is elapsed and re-try sending your mail. You should now be accepted.
Make sure you go back to the config file and comment out the "quiet" line again so that you don't tell spammers how much time they need to wait.
Additional functionality
Vacation autoreplies
Postfixadmin comes with a script for handling vacation/away messages (e.g. autoreplies). By default, though, that script is not functional. And the reason for this is that some of its dependencies are in "non-free". So in order to activate this feature, we'll first have to ensure that we are using the non-free section. Since I don't yet, here's how I've added it:
sed -i 's/\(main\)$/\1 contrib non-free/' /etc/apt/sources.list
apt-get update
Now, all the remaining steps come almost exaclty as they were written on this tutorial (and also documented in /usr/share/doc/postfixadmin/examples/VIRTUAL_VACATION/INSTALL.TXT.gz, which comes with the postfixadmin package).
First, install the script's dependencies:
apt-get install libmail-sender-perl libdbd-mysql-perl libemail-valid-perl libmime-perl liblog-log4perl-perl liblog-dispatch-perl libgetopt-argvfile-perl libmime-charset-perl libmime-encwords-perl
Next, we'll create a user solely for running the script. It will run as a transport for postfix, so we need to isolate it from accessing anything useful. We'll also copy the script into the user's home directory. Lastly, we'll setup a log directory in which the user is able to write so that we can have something to debug problems:
groupadd -r -g 65501 vacation
useradd -r -u 65501 -g vacation -d /var/spool/vacation -s /sbin/nologin vacation
mkdir /var/spool/vacation
cp /usr/share/doc/postfixadmin/examples/VIRTUAL_VACATION/vacation.pl.gz /var/spool/vacation/
gunzip /var/spool/vacation/vacation.pl.gz
chown -R vacation:vacation /var/spool/vacation
chmod -R 0700 /var/spool/vacation
The script parses, if it exists, an alternate configuration file in which we can override some values. But first, since the script needs to be able to update the vacation_notification table, let's grant insertion privileges to the "postfix" user so that we can use the lesser-priviledged account with it:
mysql -e 'GRANT INSERT,UPDATE ON postfixadmin.vacation_notification TO "postfix"@"localhost"';
Create the file /etc/postfixadmin/vacation.conf
and set its content to the following:
# db_type - uncomment one of these
our $db_type = 'mysql';
# leave empty for connection via UNIX socket
our $db_host = '';
# connection details
our $db_username = 'postfix';
our $db_password = 'something';
our $db_name = 'postfixadmin';
our $vacation_domain = 'autoreply.example.com';
# Set to 1 to enable logging to syslog.
our $syslog = 1;
# 2 = debug + info, 1 = info only, 0 = error only
our $log_level = 1;
# notification interval, in seconds
# set to 0 to notify only once
# e.g. 1 day ...
#my $interval = 60*60*24;
# disabled by default
our $interval = 0;
# perl will crash if the imported script doesn't end with a positive value .... wth
1;
Now, we need to enable the feature in the postfixadmin configuration. Edit /etc/postfixadmin/config.local.php
and add the following lines:
$CONF['vacation'] = 'YES';
$CONF['vacation_domain'] = 'autoreply.example.com';
Last but not least, we need to teach postfix how to handle mail directed to that subdomain we configured above. First, we'll setup a new transport which sends mail to the perl script we just installed, then we'll map the subdomain to that transport.
Edit /etc/postfix/master.cf
and add the following to the end of the file:
vacation unix - n n - - pipe
flags=Rq user=vacation argv=/var/spool/vacation/vacation.pl -f ${sender} ${recipient}
Since we've already created an empty transport map file earlier, we now only need to add an entry to it and to refresh it so that mail to the special subdomain is sent to the transport we just created. Edit /etc/postfix/transport
and add the following line:
autoreply.example.com vacation
Now refresh the compiled form of the file, add a final configuration item that's needed so that things go well with multiple recipients, and reload postfix so that it considers the new transport:
postmap /etc/postfix/transport
postconf -e 'vacation_destination_recipient_limit = 1'
postfix reload
Testing vacation messages
If you need more output from the vacation script to better debug what's happening, edit /etc/postfixadmin/vacation.conf
and change the value for "$log_level" to 2.
To test this feature, we will first need to set the vacation message for an address. Login to postfixadmin as the super admin and go to the "Virtual list" and then click on the link named "Set vacation" beside "someone@example.com". Now type some text and hit the "Change/save vacation message" button. You should now see the link you used is marked "VACATION IS ON".
Now send an email to that address and see what happens:
swaks --from some_existing@email.address --to=someone@example.com --server=hostname.yourdomain
You should see something similar to the following in syslog:
Dec 4 02:54:42 debian-squeeze /var/spool/vacation/vacation.pl: DEBUG - Script argument SMTP recipient is : 'someone#example.com@autoreply.example.com' and smtp_sender : 'some_existing@email.address'
Dec 4 02:54:42 debian-squeeze /var/spool/vacation/vacation.pl: DEBUG - Converted autoreply mailbox back to normal style - from someone#example.com@autoreply.example.com to someone@example.com
Dec 4 02:54:42 debian-squeeze /var/spool/vacation/vacation.pl: DEBUG - Email headers have to: 'someone@example.com' and From: 'some_existing@email.address'
Dec 4 02:54:43 debian-squeeze /var/spool/vacation/vacation.pl: DEBUG - Found 'someone@example.com' has vacation active
Dec 4 02:54:43 debian-squeeze /var/spool/vacation/vacation.pl: DEBUG - Attempting to send vacation response for: unknown to: some_existing@email.address, someone@example.com, someone@example.com (test_mode = 0)
Dec 4 02:54:43 debian-squeeze /var/spool/vacation/vacation.pl: DEBUG - Asked to send vacation reply to someone@example.com thanks to unknown
Dec 4 02:54:43 debian-squeeze /var/spool/vacation/vacation.pl: DEBUG - Will send vacation response for unknown: FROM: someone@example.com (orig_to: someone@example.com), TO: some_existing@email.address; VACATION SUBJECT: Out of Office ; VACATION BODY: I will be away from january until mars.#015#012For urgent matters you can contact this person.
Dec 4 02:54:43 debian-squeeze postfix/smtpd[4267]: connect from localhost[127.0.0.1]
Dec 4 02:54:43 debian-squeeze milter-greylist: smfi_getsymval failed for {i}
Dec 4 02:54:43 debian-squeeze milter-greylist: (unknown id): Sender IP 127.0.0.1 and address <someone@example.com> are SPF-compliant, bypassing greylist
Dec 4 02:54:43 debian-squeeze postfix/smtpd[4267]: 0864440DBB: client=localhost[127.0.0.1]
Dec 4 02:54:43 debian-squeeze postfix/cleanup[4272]: 0864440DBB: message-id=<20121204_075443_042036.someone@example.com>
Dec 4 02:54:43 debian-squeeze milter-greylist: smfi_getsymval failed for {if_addr}
Dec 4 02:54:43 debian-squeeze postfix/qmgr[4091]: 0864440DBB: from=<someone@example.com>, size=585, nrcpt=1 (queue active)
Dec 4 02:54:43 debian-squeeze postfix/smtpd[4267]: disconnect from localhost[127.0.0.1]
Dec 4 02:54:43 debian-squeeze /var/spool/vacation/vacation.pl: DEBUG - Vacation response sent to some_existing@email.address, from someone@example.com
Dec 4 02:54:43 debian-squeeze postfix/pipe[4277]: 8C70B40851: to=<someone#example.com@autoreply.example.com>, orig_to=<someone@example.com>, relay=vacation, delay=16, delays=16/0.03/0/0.41, dsn=2.0.0, status=sent (delivered via vacation service)
Dec 4 02:54:43 debian-squeeze postfix/qmgr[4091]: 8C70B40851: removed
Dec 4 02:54:43 debian-squeeze postfix/smtp[4279]: 0864440DBB: to=<some_existing@email.address>, relay=your.hostname[1.2.3.4]:25, delay=0.7, delays=0.07/0.03/0.49/0.1, dsn=2.0.0, status=sent (250 2.0.0 Ok: queued as 98826FC1EFA)
Dec 4 02:54:43 debian-squeeze postfix/qmgr[4091]: 0864440DBB: removed
If all went well you should now have received an email with the appropriate subject and body. If you're having difficulty understanding the above log output, here's a short summary of what it says:
- The script searches whether the addressed email has its vacation message set and sends a message with the subject and body that were set.
- The script then connects to localhost to send an e-mail from the receiver to the sender and the message is queued.
- The script exits after succeeding in its duty
- The queued message is processed by postfix and is sent to the original sender.
If you changed the "$log_level" to 2 earlier, don't forget to set it back to 1 else you'll have lots of useless lines in your logs.
With the above settings, if a notification gets sent to the original sender but you don't receive it and want to test it again, you'll have to make a manipulation: either truncate the "vacation_notification" table or change "vacation.conf" to set "$interval" to something very low, like 1 (see above). Another trick is to remove the auto-reply in postfixadmin, which will delete all notifications for that address, and then to set it back.
quota
In order to get quota to work, you need to tell dovecot how to get quota information, and then to activate it in the postfixadmin interface.
First, let's load the plugin by adding it to the list of plugins in /etc/dovecot/conf.d/10-mail.conf
:
mail_plugins = $mail_plugins zlib quota
and also in /etc/dovecot/conf.d/20-imap.conf
:
# Space separated list of plugins to load (default is global mail_plugins).
mail_plugins = $mail_plugins imap_quota
Next, we need to tell dovecot to use per-user quota values. We'll add a second rule in there so that the Trash directory has a little bit more space so that people can do some cleanup when they are overquota. Edit /etc/dovecot/conf.d/90-quota.conf
:
plugin {
# [...]
quota = maildir:User quota
quota_rule2 = Trash:storage=+100M
}
Telling dovecot where to find quota info is pretty simple, we need to modify "user_query" in dovecot's SQL configuration so that it returns a quota_rule column with dovecot's quota_rule format. Since we're using prefetch for userdb, we'll have to add the column to the "password_query" too. Edit /etc/dovecot/dovecot-sql.conf.ext
and add the column. The two queries should now look like this:
password_query = SELECT username as user, password, CONCAT('/var/mail/vmail/', maildir) as userdb_home, 8 as userdb_uid, 8 as userdb_gid, concat('*:bytes=', quota) as userdb_quota_rule FROM mailbox INNER JOIN domain ON mailbox.domain=domain.domain WHERE username='%u' AND domain.active = true AND mailbox.active = true;
user_query = SELECT CONCAT('/var/mail/vmail/', maildir) as home, 8 as uid, 8 as gid, concat('*:bytes=', quota) as quota_rule FROM mailbox INNER JOIN domain ON mailbox.domain=domain.domain WHERE username='%u' AND domain.active = true AND mailbox.active = true;
Now to enable quotas in the postfixadmin interface, edit /etc/postfixadmin/config.local.php
and add the following to it:
$CONF['quota'] = 'YES';
$CONF['maxquota'] = 0; // I decided to not set a quota by default, but change this value to a number of Mb that should be the default value for your case.
Finally, restart dovecot:
service dovecot restart
Testing user quotas
In order to test this feature, we'll have to set a low quota on a user. Login to postfixadmin and click on the "Edit" link beside a user, then specify a low quota and click on the "Save" button. For this example we'll set a quota of 1Mb to the user "someone@example.com".
Find a file slightly below the quota you set (note that the contents of the mail itself is also accounted for in the quota, so the attachment shouldn't be exactly as big as the limit), and send it as an attachment to that user. We'll generate one with random content:
dd if=/dev/urandom of=900k_file bs=1024 count=900
swaks --from some_existing@email.address --to=someone@example.com --server=hostname.yourdomain --attach - --suppress-data < 900k_file
This first attachment should be able to get to destination (supposing you haven't already sent more than 100kb of test emails). If you launch that swaks command again, you'll get an answer that looks odd at first: the server tells you "OK message queued as <some_message_ID>". However, if you look at the syslog, you'll see a message about an email being sent to some_existing@email.address:
Dec 4 16:48:09 debian-squeeze postfix/pipe[5316]: 3B73A40DA7: to=<someone@example.com>, relay=dovecot, delay=1.1, delays=0.97/0.02/0/0.1, dsn=2.0.0, status=sent (delivered via dovecot service)
Dec 4 16:48:09 debian-squeeze postfix/qmgr[4601]: 3B73A40DA7: removed
Dec 4 16:48:09 debian-squeeze postfix/cleanup[5313]: AA1D940DB0: message-id=<dovecot-1354657689-665110-0@hostname.yourdomain>
Dec 4 16:48:09 debian-squeeze postfix/qmgr[4601]: AA1D940DB0: from=<>, size=1930, nrcpt=1 (queue active)
Dec 4 16:48:10 debian-squeeze postfix/smtp[5323]: AA1D940DB0: to=<some_existing@email.address>, relay=your_mx[1.2.3.4]:25, delay=0.47, delays=0.01/0.01/0.35/0.1, dsn=2.0.0, status=sent (250 2.0.0 Ok: queued as 13017FC3158)
Dec 4 16:48:10 debian-squeeze postfix/qmgr[4601]: AA1D940DB0: removed
Now in your inbox, you should see a message got to you that says your e-mail was rejected and in the body, you can see the cause which is because the user's inbox is over quota.
Configuring Mailman
We'd like to be able to control mailing lists under lists.example.com
. Make sure to create the appropriate DNS entry and to have it pointed to the mail server's IP address. Since this tutorial does a pretty good job of explaining the process and showing how to do things, we'll mostly stick to the steps there and keep explanations only to what differs that tutorial or needs further clarification.
If you remember we chose at the beginning of this howto to assume you already had a web server. We'll be working with Apache, so if you need to adapt to other software, you'll need to do it on your own. But it shouldn't be fairly difficult.
apt-get install mailman
ln -s /etc/mailman/apache.conf /etc/apache2/sites-available/lists.example.com
a2ensite lists.example.com
Edit /etc/mailman/apache.conf
, uncomment the vhost block at the end and change it to suit your needs. (see the tutorial mentioned above for details)
mkdir /var/www/lists
apache2ctl graceful
Edit /etc/mailman/mm_cfg.py
and ensure the following configuration is present:
[...]
DEFAULT_URL_PATTERN = 'http://%s/'
[...]
DEFAULT_EMAIL_HOST = 'lists.example.com'
[...]
DEFAULT_URL_HOST = 'lists.example.com'
[...]
GLOBAL_PIPELINE.insert(1, 'SpamAssassin')
SPAMASSASSIN_HOST='127.0.0.1:783'
postconf -e 'relay_domains = proxy:mysql:/etc/postfix/virtual/relay_domains.cf lists.example.com'
postconf -e 'mailman_destination_recipient_limit = 1'
Edit /etc/postfix/transport
and append the following line:
lists.example.com mailman
postmap /etc/postfix/transport
postfix reload
newlist --urlhost=lists.example.com --emailhost=lists.example.com mailman
Edit /etc/aliases
and append to it the following lines:
mailman: "|/var/lib/mailman/mail/mailman post mailman"
mailman-admin: "|/var/lib/mailman/mail/mailman admin mailman"
mailman-bounces: "|/var/lib/mailman/mail/mailman bounces mailman"
mailman-confirm: "|/var/lib/mailman/mail/mailman confirm mailman"
mailman-join: "|/var/lib/mailman/mail/mailman join mailman"
mailman-leave: "|/var/lib/mailman/mail/mailman leave mailman"
mailman-owner: "|/var/lib/mailman/mail/mailman owner mailman"
mailman-request: "|/var/lib/mailman/mail/mailman request mailman"
mailman-subscribe: "|/var/lib/mailman/mail/mailman subscribe mailman"
mailman-unsubscribe: "|/var/lib/mailman/mail/mailman unsubscribe mailman"
newaliases
service mailman start
Now visit lists.example.com with a browser and your should see the mailman interface.
We'll skip testing the setup for this piece of software for brevity.
However, it would require us to create a new list and subscribe more than one address to it, and then to send a message to the list and see if it gets sent to all members.
If you want to be able to manage lists on more than one subdomain, check out the last link in the bibliography section: it has a section about configuring Mailman for this purpose. It needs you to perform more operations before Mailman is functional, but you won't need to manually add aliases to /etc/aliases
every time there is a new list.
Varia
DNS
Some of the software we've installed need to perform lots of DNS lookups, so in order to speed up subsequent lookups to host names we've already seen, you might consider setting up a DNS caching service: it can make something between a good and a huge difference on performance. Here's a quick example of such a setup with dnsmasq:
apt-get install dnsmasq
Edit /etc/dnsmasq.conf
and uncomment and modify the lines #interface=
and #bind-interfaces
so that they look like:
interface=lo
bind-interfaces
Now edit /etc/resolv.conf
and add a nameserver line for 127.0.0.1 in the first position (e.g. above all others):
nameserver 127.0.0.1
Finally, restart dnsmasq and then all services that process e-mails so that they consider the new contents of the /etc/resolv.conf
file.
Sieve / ManageSieve
Server-side filters have the advantage that once they are set up, users can use whatever client and messages will get delivered to the right directory without the email client application having to do anything. Also, users won't loose their filters if they loose their laptop/desktop or if their desktop disk explodes.
It can also manage vacation messages for you, so it could be an interesting replacement to the feature from postfixadmin we're using above which is pretty clunky to setup.
However, sieve filters are not very user-friendly for most non-geek users. Users basically need to learn a (simple -- but still...) programming language to be able to write their filters. Roundcube and Squirrelmail (last updated in 2009!) have plugins to interact with ManageSieve to manage your server-side filters. That's something I'd need to explore. I just hope that some plugins for some clients do a good job of making it easy to build filters. Thunderbird's "Sieve" add-on is not very user-friendly for non-IT people. If you only want to use the vacation message feature from sieve, then Thunderbird's "Sieve Out of Office" add-on might be more interesting and easy to use.
Sieve is pretty easy to setup. Just install the following packages:
apt-get install dovecot-managesieved dovecot-sieve
then edit /etc/dovecot/15-lda.conf
and uncomment the line that sets plugins and add "sieve" to the list:
protocol lda {
# Space separated list of plugins to load (default is global mail_plugins).
mail_plugins = $mail_plugins sieve
}
Then restart dovecot:
service dovecot restart
Watch out! ManageSieve might accept unencrypted connections. This could be an easy way to leak your users' passwords so you need to verify that this is not happening. After following the above guide, dovecot's ManageSieve server should offer no options for the "SASL" capability as long as you haven't initiated a TLS transaction. If the "SASL" capability is empty and the "STARTTLS" capability is visible, then your server is rejecting non-encrypted logins (which is what we want):
$ telnet your.server 4190
Trying 1.2.3.4...
Connected to you.server.
Escape character is '^]'.
"IMPLEMENTATION" "Dovecot Pigeonhole"
"SIEVE" "fileinto reject envelope encoded-character vacation subaddress comparator-i;ascii-numeric relational regex imap4flags copy include variables body enotify environment mailbox date ihave"
"NOTIFY" "mailto"
"SASL" ""
"STARTTLS"
"VERSION" "1.0"
OK "Dovecot ready."
If you're seeing "STARTTLS" in the server's welcome message but the "SASL" capability is not an empty list, then you need to correct this. This can be enforced by setting the following in /etc/dovecot/conf.d/10-auth.conf
:
disable_plaintext_auth = yes
For more info on troubleshooting ManageSieve, check out the last link in the bibliography.
Bibliography
- http://www.postfix.org/documentation.html -- The info on this page is very useful to understand how postfix works. You should definitely stop here first.
- http://www.postfix.org/TLS_README.html
- http://www.postfix.org/MILTER_README.html
- http://wiki2.dovecot.org/FrontPage -- The official Dovecot 2.x documentation.
- http://wiki2.dovecot.org/LDA -- how to setup delivery via Dovecot and what interest does it have.
- http://wiki2.dovecot.org/Quota -- quota settings
- http://www.interpause.com/index.php/Howto/Setup_Postfix%2B_dovecot_sasl%2B_dovecot_imaps%2B_maildir%2B_virtual_accounts%2B_mysql%2B_postfixadmin%2B_PositiveSSL_certificate%2B_Roundcube_on_Ubuntu_10.04 -- this was as near a howto of what I wanted to do as I could find.
- http://www.debuntu.org/how-to-virtual-emails-accounts-with-postfix-and-dovecot
- http://codepoets.co.uk/2009/postfixadmin-setupinstall-guide-for-virtual-mail-users-on-postfix/
- http://www.andybev.com/index.php/Setup_clamav_with_Postfix_on_Debian_Lenny_in_a_chroot
- http://wiki.dovecot.org/HowTo/DovecotLDAPostfixAdminMySQL
- http://postfixadmin.sourceforge.net/
- http://www.malgouyres.fr/linux/spamass-milter_postfix_en.html
- http://blog.rimuhosting.com/2011/03/08/setup-mail-hosting-on-debian-squeeze/
- http://www.howtoforge.com/virtual-users-and-domains-with-postfix-courier-mysql-and-squirrelmail-debian-squeeze -- (this one is using courrier, but the steps are very similar)
- http://johnny.chadda.se/article/mail-server-howto-postfix-and-dovecot-with-mysql-and-tlsssl-postgrey-and-dspam/ -- another good howto, but dates back to Debian etch.
- https://spamassassin.apache.org/gtube/ -- describes one way to test triggering a very high score in SpamAssassin.
- http://craigballinger.com/blog/2009/08/postfix-vacation-autoresponder/ -- howto configure vacation messages once postfixadmin is working.
- http://www.debianadmin.com/how-to-filter-spam-with-spamassassin-and-postfix-in-debian.html
- http://www.howtoforge.com/how-to-install-and-configure-mailman-with-postfix-on-debian-squeeze
- https://library.linode.com/email/mailman/debian-6-squeeze -- configuration with virtual hosts.
- http://en.gentoo-wiki.com/wiki/Postfix/TLS#Configuration -- Some info on TLS encryption for Postfix.
- http://wiki2.dovecot.org/Pigeonhole/Sieve/Troubleshooting -- Debbugin Sieve rules
- http://wiki2.dovecot.org/Pigeonhole/ManageSieve/Troubleshooting -- If you have trouble with ManageSieve
that was a lot a moving parts -- when i first saw that you mentioned vacation message systems, i started wondering again why we wrote our own at koumbit, but then i saw just how complex they were to set up and saw how tempting it must have been to avoid those existing ones.
also, i should learn about seive someday.
thanks for the comments!
@Yllar: that's great, glad this post could be useful. I didn't need saslauthd, though, since dovecot does include its own sasl daemon. so it's one less part to maintain. do you know of any advantages saslauthd would have over dovecot's own sasl daemon?
@mvc: yeah that's what I learned when delving into email server setups: there are lots of parts involved and you need to understand each of them to be able to glue them together and later debug them. what's more frightening: the setup in this post is not overly complex compared to what you can imagine for places where the email architecture has to be highly available and process planet-sized loads of email.
also, the (not so fun) thing with "vacation" auto-reply messages is that they need to be tightly knit to your architecture: the solution needs to know whether the destination is valid and active (enabled), if an auto-reply message was set and retrieve that message to send a copy of it to the sender. so in this tutorial the script is tied in to the PostfixAdmin database structure. but on other setups, the script would need to be tied in to the specific database structure, the ldap store, or some other method that the the infrastructure is using.
Thanks for taking the time to write this tutorial, works nicely on a fresh wheezy install with a few minor changes:
I had to change "proxy:mysql:" --> "mysql:" in main.cf and other postfix config files
Once postfix is configured (but before configuring dovecot) you say "You should see the delivered mail", but of course it won't work until dovecot is configured (we'll get a "user unknown" error).
This is just a common pitfall: don't list your virtual domains as "myhostname" or "mydestination" in postfix's main.cf
greylist: Debian wheezy configures a "grey users" list in /etc/milter-greylist/greylist.conf. Add your users there, or set it to default for all with "racl greylist default delay 30m autowhite 30d" instead of the "default whitelist" that comes with the package.
There's something funny going on with spamassassing trying to write autolearn stuff to the $HOME/.spamassassin file of the only "real" user in the system, regardless of the sender or recipient, I need to look into it.
Hi, I tried to follow your guide step-by-step on a fresh Debian 7 (Wheezy) system. Unfortunately Debian 7's postfix package doesn't contain VDA patch, so after addig postfix options to main.cf and reload I get a bunch of error messages:
Reloading Postfix configuration.../usr/sbin/postconf: warning: /etc/postfix/main.cf: unused parameter: virtual_overquota_bounce=yes /usr/sbin/postconf: warning: /etc/postfix/main.cf: unused parameter: virtual_mailbox_limit_maps=proxy:mysql:/etc/postfix/virtual/mailbox_limit_maps.cf /usr/sbin/postconf: warning: /etc/postfix/main.cf: unused parameter: virtual_maildir_limit_message=Sorry, the user's maildir has overdrawn his diskspace quota, please try again later. /usr/sbin/postconf: warning: /etc/postfix/main.cf: unused parameter: virtual_create_maildirsize=yes /usr/sbin/postconf: warning: /etc/postfix/main.cf: unused parameter: virtual_mailbox_extended=yes /usr/sbin/postconf: warning: /etc/postfix/main.cf: unused parameter: virtual_mailbox_limit_override=yes
Quota will not work this way under Debian 7.
Ah, well this is interesting. I confirm that I'm seeing those errors, too, and didn't know why they were showing.
I must admit that quota's been unused after all on the servers I set this up on. If you don't use the quota feature you can ignore those and the rest will work, or just comment out those lines.
It's unfortunate, though.. Some poeple might want to use quota. If you find a way to fix configuration for the quota, please let me know and I'll update this post after testing that it works.
Dear LeLutin,
I have been trying to install an email server and thus bumped into your tutorial. Must say this is undoubtedly one of the best tutorials around, atleast for a newbee like me. Well...following your tutorial i have now got stuck at 'Testing vacation messages' part.
When I am trying to send an email as....
swaks --from myemail@gmail.com --to=someone@example.com --server=hostname.yourdomain
its giving an error stating....
=== Trying localhost:25... Error connecting 0.0.0.0 to localhost:25: IO::Socket::INET: connect: Connection refused
The /var/log/mail.log shows the following.........
Feb 13 15:39:01 xxxxxxx postfix/pickup[3874]: 2F6EA4C193C: uid=0 from=
Feb 13 15:39:01 xxxxxxx postfix/cleanup[3941]: 2F6EA4C193C: message-id=<20140213100901.2F6EA4C193C@xxxxxxx>
Feb 13 15:39:01 xxxxxxx postfix/qmgr[3873]: 2F6EA4C193C: from=<root@mydomain>, size=795, nrcpt=1 (queue active)
Feb 13 15:39:01 xxxxxxx postfix/local[3945]: 2F6EA4C193C: to=<root@mydomain>, orig_to=, relay=local, delay=0.12, delays=0.08/0/0/0.03, dsn=2.0.0, status=sent (delivered to command: procmail -a "$EXTENSION")
Feb 13 15:39:01 xxxxxxx postfix/qmgr[3873]: 2F6EA4C193C: removed
Feb 13 15:55:14 xxxxxxx postfix/postfix-script[4069]: refreshing the Postfix mail system
Feb 13 15:55:14 xxxxxxx postfix/master[2416]: reload -- version 2.9.6, configuration /etc/postfix
Feb 13 15:55:14 xxxxxxx postfix/master[2416]: fatal: /etc/postfix/master.cf: line 129: field "private": bad value: "???"
The Line 129: in /etc/postfix/master.cf is..........
vacation unix - n n - - pipe flags=Rq user=vacation argv=/var/spool/vacation/vacation.pl -f ${sender} ${recipient}
As mentioned above I am a new to this linux world and unable to figure out whats going wrong. Any kind of help in resolving this problem and getting the email server created would be highly appreciated.
Sincerely thanking you alot in advance. Warm regards
I used parts of your guide to build an updated tutorial for Debian Wheezy. Your guide was very helpful!
Run your own mail server on debian wheezy
Hi there surajit. I'm glad this text is useful
According to the portion of mail.log that you posted, the last line indicates that there's an error in the master.cf configuration file somewhere around the "private" field.
Normally, the "private" field is the third one, so the first one after "unix". Maybe there's a special character that's invisible in your file. Make sure that there's no extra contents in there.
What you can do is to erase text around the third column and re-type it manually to ensure there are only ascii letters and spaces there.
For example, you could erase "unix - n" and then type it back again (don't copy paste, just erase and type again).
Hope that helps.
Very good article! Thank you.
There is no such package. Maybe you mean dovecot-managesieved?
oh you're right Yuriy, thanks for finding that out!
I've corrected the package name
About vacation
with only insert access I get error like:
Command died with status 255: "/var/spool/vacation/vacation.pl". Command output: DBD::mysql::st execute failed: UPDATE command denied to user 'postfix'@'localhost' for table 'vacation_notification' at /var/spool/vacation/vacation.pl line 298, line 25. DBD::mysql::st execute failed: UPDATE command denied to
user 'postfix'@'localhost' for table 'vacation_notification' at
/var/spool/vacation/vacation.pl line 298, line 25.
So I`ve add all privilegues:
mysql -e 'GRANT ALL PRIVILEGES ON postfix.vacation_notification TO "postfix"@"localhost";'
Ah, you're right Yuriy. I'll change the page to read "GRANT INSTALL,UPDATE" for that line.
Thanks.
Hi,
Thank you very much for this clear documentation, setting up a mail server is definitly not an easy task.
However, I discovered something strange in my logs after setting up postfix. We are running our services on a dedicated OVH mail server. You suggest the following:
postconf -e 'smtpd_relay_restrictions = permit_mynetworks,permit_sasl_authenticated,reject_unauth_destination'
In my case, that means that any other OVH dedicated server in the same subnet as mine could use my smtp to spam anybody =)
I changed that to:
postconf -e 'smtpd_relay_restrictions = permit_sasl_authenticated,reject'
Actually, the default value (for debian at least) for the
mynetworks
setting is the following:i.e. localhost for ipv4 and ipv6.
So if you kept that setting to its default value, the permissions in the howto should be fine: all exceptions to authentication are meant for localhost.
If your
mynetworks
setting is different, then yes you should definitely review permissions so that you don't let everyone send mail through your machine.I've just updated the post to make the howto setup postfixadmin and dovecot to both use the salted SHA512 password hashing method instead of the default of MD5.
Using MD5 is not recommended for storing passwords since it suffers from known collision issues which make cracking hashes a lot faster. Since postfixadmin uses md5-crypt as its default method, we need to change this to a better alternative.
Note to users who used my howto before: changing the password hashing method will break all passwords (you will need to reset all passwords to make them work again), so you should plan things before implementing this change. I haven't looked into implementing an easier transition yet, but I will probably look into it in the coming days. The best clue for now is this page on dovecot's wiki : http://wiki2.dovecot.org/HowTo/ConvertPasswordSchemes
I had to revert the last update. After testing things more thoroughly, I hit bugs with the salted SHA512 password hashing method: I couldn't login to the postfixadmin interface anymore (however, logging in via dovecot with the SSHA512 was working great).
I asked on the #postfixadmin IRC channel about this and was told that support for salted methods was only properly added in the upcoming 3.0 release (current trunk -- and actually I was told that 3.0 beta was supposed to be release very soon).
I've just made a small adjustment to the howto that disables per-user configuration files for spamassassin (adds
-x
to options in/etc/default/spamassassin
). Since we're configuring virtual email addresses, per-user (e.g. system user) configuration files are irrelevent.This gets rid of error messages about not being able to create user configuration file, so it'll help with uncluttering your log files.
Those of you that might acutally want per-user config files for spamassassin, you must use
-x
like suggested in the howto, combined with--virtual-config-dir=pattern
as specified in spamd documentationanother note: current howto doesn't specify a global bayesian filter file. it might be fine for the majority of crapmail, but some of you might want to make spamassassin learn to hate more spam.
you can define per-email-address filter files but I won't cover here how to configure this. You can also specify one global bayesian file for all users with the following lines in
/etc/spamassassin/local.cf
:you'll have to ensure the
.spamassassin
directory is writable by the user spamd is running under. with current howto indications, that would be userspamd
, but...in Debian wheezy, the user
debian-spamd
is automatically created with spamassassin package installation. if you change user and group in options in/etc/default/spamassassin
to bedebian-spamd
instead, you won't have to mess around with permissions for the bayesian file.Hey there Moazzam,
That's a very interesting question for which I don't really have an answer. From what I could gather from dovecot's quota documentation0 there's only an example for per-user quota support.
However, if I enable the quota support in config.local.php, I can see a "Limit (Mb)" line in the form for domain modification. So there must be a way to enable domain-based quota but I can't find documentation about how to set this up.
Maybe your best bet there would be to either ask on postfixadmin's mailing list or on their IRC channel.
/etc/default/clamav-milter
file can be safely erased (it's not parsed anymore).Hello, I used your tutorial with success on a Nginx server, thank you for a great job of simplifying and explaining things. I never thought that setting up a mail server would be so complicated ! I have several things which do not work: I am using Thunderbird to log to the mail server and I can send and receive mails OK, but cannot use the "Cc" or "Bcc"... Only the mails sent to the "To" are effectively sent. Is there something I am missing ? On an other hand, clamAV, Spamassassin and greylist do not appear to work. "Corrupted" mails go through untouched. I reverified the step by step from the tuto but couldn't find anything. Any ideas ?
@John_Nginx: ah! good to know for the cc/bcc. I was thinking about it for the last couple of days and just couldn't come up with any reason why mail sent via CC: and Bcc: headers wouldn't work if To: was working. After all they are all sent the exact same way, except for the Bcc: ones which get set as one exclusive mail for each Bcc destination and get stripped out of all other emails so that only the concerned destination knows that it was sent a copy.
As for the clamav, greylist and spamassassin not working, maybe one of them is erroring out, which because of the configuration
milter_default_action = accept
would cause postfix to deliver the mail instead of just loosing it.I'd suggest you take a look at your logs in /var/log/mail.log (and maybe mail.err): there's probably some error messages when you're receiving mail that could point to the issue at hand.
If you're really not able to find the issue that way, or if you don't know where to look, I suggest then to tear out all milters from option
smtpd_milter
and add them back in one by one (or only one at a time would make things even more obvious) and run the associated tests from each milter's section in the howto.You could also paste some error lines in a comment here if you want me to help you out (not too much text at once please.. maybe just the lines that would correspond to one mail being received by your server).
Hope that helps!
I am on archlinux, and as you pointed out back in 2013, a lot is outdated.. including our wikis I think. I just want to say a big thank you.. not because I have yet done your tutorial, nor because I have everything working because of it but because thanks to just going through it, you made me understand far more of this mail stack beast I have been contending with.
I could instantly now hack away on stuff and even if it won't work quite yet I now know more what I am doing and what things are.
Thank you very much for this!!!
Kind regards,
@arch.