Installing Postfix - Clamav - Spamassassin - Dovecot - Postfixadmin On Debian Squeeze
Installing Postfix - Clamav - Spamassassin - Dovecot - Postfixadmin On Debian Squeeze
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).
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'] = '[email protected]';
$CONF['default_aliases'] =array (
'abuse' => '[email protected]',
'hostmaster' => '[email protected]',
'postmaster' => '[email protected]',
'webmaster' => '[email protected]'
);
$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
[email protected].
Now we should have enough info in the database for testing the next steps.
mkdir /var/mail/vmail
chown mail:mail /var/mail/vmail
Create a read-only user on the postfixadmin database:
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:
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.
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.
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:
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:
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:
In /etc/postfix/master.cf, right above the line that starts with "smtp inet", add
the following:
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!!):
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:
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):
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:
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:
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:
On your computer, download a text file with the GTUBE signature line and use it as
the body of a test email:
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:
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):
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):
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:
# connection details
our $db_username = 'postfix';
our $db_password = 'something';
our $db_name = 'postfixadmin';
our $vacation_domain = 'autoreply.example.com';
# 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:
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 "[email protected]". 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".
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.
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:
$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:
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:
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.
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'
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:
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:
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.
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.
protocol lda {
# Space separated list of plugins to load (default is global mail_plugins).
mail_plugins = $mail_plugins sieve
}
Then restart dovecot:
disable_plaintext_auth = yes
For more info on troubleshooting ManageSieve, check out the last link in the
bibliography.