SOHO Mailserver with Postfix + Postgresql + Dovecot + SpamAssassin + Roundcube

This HowTo describes my Home-Mailserver Setup. Basically this is a sum-it-all-up article from various resources on the net. 

Used Software:

  • Arch Linux OS
  • Postfix MTA
  • PostgreSQL database backend
  • Dovecot IMAP Server
  • Roundcube Webmail + Apache Webserver
  • Spamassassin junk filter
  • Server-side filtering with Sieve
  • fetchmail (for pulling all spread accounts in this one place)

Preconditions in my setup:

  • Server behind Firewall/NAT
  • Dynamic IP (No-IP Plus managed DynDNS service with MX Record etc)
  • StartSSL certificate for both Web- and Mail-Server domain
  • ISP doesn't allow running an outgoing mail server directly, requires relaying through their mail gateway
  • Apache + PHP + Postgresql already running and working

1 . Preface

My intention for this was to run "my own" mail service. Not GMX, not Google, but my own. My own spam filtering, my own security measures, my own domain, my own SSL certificate, my own responsibilty. The result is a mail server setup, which could serve as a solid mail server solution for smaller offices that want to roll their own, too. Of course that would require a bit more reliable infrastructure. 

This howto assumes a running Apache or Nginx server for the webmail part, and knowledge about how to configure it. If you follow this guide, it's up to you to make sure you secure things properly. Use strong passwords for the webmail stuff, and only allow SSL encrypted connections from outside to the IMAP server and the Webmail application. I also use AppArmor for postfix, dovecot, nginx and php-fpm.

If you do not understand some of the settings in the config files, please read the manual of the given component.

2 . Prepare the domain DNS records

Assuming we want email addresses looking like @domain.net and the mail server running at mail.domain.net, we need

domain.net A <IP>
  MX mail.domain.net
mail.domain.net A <IP>

The record for mail.domain.net must not be a CNAME type record, it needs to be type A.

3 . Prepare the host interfaces

As the server runs with LAN IP it's required to set hostnames in /etc/hosts. Here both Apache and the Mail-$foo will listen on one interface.

192.168.1.2   domain.net   mail.domain.net

4 . Install required packages

This host runs Arch-Linux with community, extra and AUR enabled.

# yaourt -S postfix postfixadmin dovecot pigeonhole roundcubemail spamassassin \
razor fetchtmail perl-dbd-pg perl-lockfile-simple

5 . Setup Postgresql DB

We will need two DB schemas: one for postfix/postfixadmin/dovecot, one for roundcubemail.

The postfix DB is used mainly for storing the virtual domain/user configuration, so only Postfixadmin will need write access to it. Postfix and Dovecot only require read access.

$ createuser postfixadmin -D -R -S -P
$ createuser postfixro -D -R -S -P
$ createuser roundcube -D -R -S -P
$ createdb postfix -O postfixadmin
$ createdb roundcube -O roundcube

Now grant the the "postfixro" user read-only access to the postfix database

psql -d postfix
postfix=# ALTER DEFAULT PRIVILEGES IN SCHEMA public 
  GRANT SELECT ON TABLES TO postfixro;

6 . Setup PostfixAdmin

I installed postfixAdmin into a vhost which is only accessible on localhost, using following config (mind: AUR package of postfixadmin)

Alias /postfixAdmin "/usr/share/webapps/postfixAdmin"
<Directory "/usr/share/webapps/postfixAdmin">
      AllowOverride All
      Options FollowSymlinks
      Order allow,deny
      Allow from all
</Directory>

.. and included that in the local-only vhost.

Then edit /etc/webapps/postfixadmin/config.inc.php

$CONF['configured'] = true;
...
$CONF['database_type'] = 'pgsql';
$CONF['database_host'] = 'localhost';
$CONF['database_user'] = 'postfixadmin';
$CONF['database_password'] = 'secret';
...
$CONF['admin_email'] = 'postmaster@domain.net';
...
$CONF['smtp_server'] = 'mail.domain.net';
$CONF['smtp_port'] = '25';
...
# these will result in a maildir path like /var/mail/vmail/domain.com/user
$CONF['domain_path'] = 'YES';
$CONF['domain_in_mailbox'] = 'NO';

Now browse to http://admin.localhost/postfixAdmin/setup.php and generate a setup_password. Put the resulting hash in /etc/webapps/postfixadmin/config.inc.php.

Finally, run the setup. This will install the DB-Schema for Postfix, and setup an admin user.

7 . Prepare Dovecot

The next step is to prepare Dovecot. I will use Dovecot LDA as delivery agent, and enable Sieve/ManageSieve for server-side filtering. Edit following files in /etc/dovecot (only changed lines are shown).

  • Edit dovecot.conf
protocols = imap sieve
listen = *, ::
  • Create dovecot-sql.conf.ext
driver = pgsql
connect = host=localhost dbname=postfix user=postfixro password=secret
default_pass_scheme = MD5
user_query = \
    SELECT '/var/mail/vmail/'||maildir AS home, '*:bytes='||quota AS quota_rule \
    FROM mailbox WHERE username = '%u' AND active = TRUE
password_query = \
    SELECT '/var/mail/vmail/'||maildir AS userdb_home, \
        username AS user, password, '*:bytes='||quota AS userdb_quota_rule \
    FROM mailbox WHERE username = '%u' AND active = TRUE

Set proper permissions on that file (contains DB password):

# chown root:dovecot dovecot-sql.conf.ext
# chmod 640 dovecot-sql.conf.ext
  • Edit conf.d/10-mail.conf
mail_location = maildir:/var/mail/vmail/%d/%n/
mail_uid = 8
mail_gid = 12
first_valid_uid = 8
last_valid_uid = 8
first_valid_gid = 12
last_valid_gid = 12
mail_plugins = quota

Note: the mail_location corresponds to $CONF['domain_path'] = 'YES'; and  $CONF['domain_in_mailbox'] = 'NO'; in the postfixAdmin configuration above, which will result in a nested directory structure /var/mail/vmail/domain.tld/username/ .

Also, the default UID/GID for user mail may differ on you Linux flavor (uid=8, gid=12 on Archlinux and CentOS/RedHat). Change it as required.

  • Edit conf.d/20-imap.conf
mail_plugins = $mail_plugins imap_quota
  • Edit conf.d/10-master.conf
service auth {
  unix_listener auth-userdb {
    mode = 0600
    user = mail
    group = mail
  }
  unix_listener /var/spool/postfix/private/auth {
    mode = 0666
    user = postfix
    group = postfix
  }
}

service auth-worker {
  user = $default_internal_user
}
  • Edit conf.d/10-auth.conf

WARNING: during the installation and configuration phase I allowed plain text auth. Once everything was running, I changed that back and allowed only non-plain text via SSL.

disable_plaintext_auth = no  #DANGEROUS
#!include auth-system.conf.ext
!include auth-sql.conf.ext
  • Edit conf.d/15-lda.conf
postmaster_address = postmaster@domain.net
protocol lda {
  mail_plugins = $mail_plugins sieve
}
  • Edit conf.d/90-plugin.conf

Note: /var/lib/dovecot/sieve/ has to be created and owned by mail:mail

plugin {
   sieve = ~/sieve/.dovecot.sieve
   sieve_global_path = /var/lib/dovecot/sieve/default.sieve
   sieve_dir = ~/sieve
   sieve_global_dir = /var/lib/dovecot/sieve/global/
}
  • Edit conf.d/10-ssl.conf

Note: for StartSSL combine the server and the intermediate cert in one .pem (with cat .. > ..), and specify the root ca

ssl = yes
ssl_cert = </etc/ssl/certs/mail.domain.net.pem
ssl_key = </etc/ssl/private/mail.domain.net.key
ssl_ca = </etc/ssl/certs/startssl-ca-bundle.crt

8 . Prepare Postfix

  • Edit /etc/postfix/main.cf
# basic domain settings
myhostname = mail.domain.net
mydomain = domain.net
mydestination = $myhostname, localhost
mynetworks = 192.168.1.0/24, 127.0.0.0/8
myorigin = $mydomain
relay_domains = proxy:pgsql:/etc/postfix/pgsql/relay_domains.cf

# this is needed if your ISP doesn't allow to run a outgoing mailserver. Use their mail relay.
relayhost = mail.mnet-online.de

# enable auth via Dovecot
smtpd_sasl_auth_enable = yes
smtpd_sasl_path = private/auth
smtpd_sasl_type = dovecot

# virtual mail setup for Postgresql and Dovecot transport
virtual_mailbox_base = /var/mail/vmail
virtual_mailbox_limit = 512000000
virtual_mailbox_domains = proxy:pgsql:/etc/postfix/pgsql/virtual_domains_maps.cf
virtual_mailbox_maps = proxy:pgsql:/etc/postfix/pgsql/virtual_mailbox_maps.cf
virtual_alias_maps = proxy:pgsql:/etc/postfix/pgsql/virtual_alias_maps.cf
virtual_uid_maps = static:8
virtual_gid_maps = static:12
virtual_minimum_uid = 8
dovecot_destination_recipient_limit = 1
virtual_transport = dovecot-spamassassin

# also local accounts are handled via virtual users, configure aliases for those in PostfixAdmin
local_transport = virtual
local_recipient_maps = $virtual_mailbox_maps

# TLS server (receiving)
smtpd_tls_auth_only = yes
smtpd_tls_security_level = may
smtpd_tls_key_file = /etc/ssl/private/mail.domain.net.key
smtpd_tls_cert_file = /etc/ssl/certs/mail.domain.net_full.pem
smtpd_tls_CAfile = /etc/ssl/certs/startssl-ca-bundle.crt

## Following makes no sense with "..security_level = may"
## SSLv2/3 is still better than plain transfers
#smtpd_tls_mandatory_protocols = !SSLv2, !SSLv3

# TLS client (sending)
smtp_tls_security_level = may

# security and basic spam protection
smtpd_recipient_restrictions =
               permit_sasl_authenticated
               permit_mynetworks
               reject_unauth_destination
smtpd_relay_restrictions =
               permit_mynetworks
               permit_sasl_authenticated
               defer_unauth_destination
smtpd_client_restrictions =
               permit_sasl_authenticated
               reject_rbl_client zen.spamhaus.org

 

Next, we need to create the configuration for the postfix lookup tables in postgresql. Again, these are read-only lookup tables, so we use the postfixro user. See http://www.postfix.org/pgsql_table.5.html for more detailed information.

  • Create /etc/postfix/pgsql/relay_domains.cf
user = postfixro
password = secret
hosts = localhost
dbname = postfix
query = SELECT domain FROM domain WHERE domain='%s' and backupmx = true
  • Create /etc/postfix/pgsql/virtual_alias_maps.cf
user = postfixro
password = secret
hosts = localhost
dbname = postfix
query = SELECT goto FROM alias WHERE address='%s' AND active = true
  • Create /etc/postfix/pgsql/virtual_domains_maps.cf
user = postfixro
password = secret
hosts = localhost
dbname = postfix
query = SELECT domain FROM domain WHERE domain='%s' and backupmx = false and active = true
  • Create /etc/postfix/pgsql/virtual_mailbox_limits.cf
user = postfixro
password = secret
hosts = localhost
dbname = postfix
query = SELECT quota FROM mailbox WHERE username='%s'
  • Create /etc/postfix/pgsql/virtual_mailbox_maps.cf
user = postfixro
password = secret
hosts = localhost
dbname = postfix
query = SELECT maildir FROM mailbox WHERE username='%s' AND active = true

Then we need to configure a transport for piping the mails through spamassassin over to the dovecot LDA

  • Set proper permissions on all pgsql/* files created above (they contain DB passwords)
# chown root:postfix /etc/postfix/pgsql/*.cf
# chmod 640 /etc/postfix/pgsql/*.cf
  • Append to /etc/postfix/master.cf
dovecot-spamassassin   unix  -       n       n       -       -       pipe
  flags=DRhu user=mail:mail argv=/usr/bin/vendor_perl/spamc -f -e /usr/lib/dovecot/deliver
  -f ${sender} -d ${recipient}

9 . Prepare Spamassassin

Just install, and edit /etc/conf.d/spamd

SAHOME="/var/lib/spamassassin"
SPAMD_OPTS="--max-children 1 --username spamd --helper-home-dir ${SAHOME} -s ${SAHOME}/spamd.log -x -q --pidfile /var/run/spamd.pid"

More recent versions of Arch Linux use systemd. To achieve the same as above, copy /usr/lib/systemd/system/spamassassin.service to /etc/systemd/system/ and edit it.

ExecStart=/usr/bin/vendor_perl/spamd --max-children 1 --username spamd --helper-home-dir /var/lib/spamassassin -s /var/lib/spamassassin/spamd.log -x -q -d --pidfile /var/run/spamd.pid

Additionally, if you intend to fetch mail from different mail accounts with fetchmail, you need to setup a cronjob that runs sa-learn on your Junk folder and adds it to the "mail" user's bayesian DB. Simply create /etc/cron.daily/sa-learn and fill it with the .Junk folder of all users. Example for one user's INBOX:

#!/bin/sh
/usr/bin/vendor_perl/sa-learn -u mail --spam /var/mail/vmail/domain.net/tom/.Spam/*

See http://wiki.apache.org/spamassassin/SiteWideBayesFeedback for more Info and other techniques.

10 . Create domain and users in postfixAdmin

Login with the admin user created in (4.).

  1. In Domain List -> New Domain create a new domain domain.tld and enable create default aliases checkbox
  2. In Virtual List -> Add Mailbox create a new mailbox

11 . Start and Test

Now start postfix, dovecot and spamd, and send some test mail from external account to your newly created mailbox. See postfix and dovecot logs if things arrive properly. Use some mail client (thunderbird, claws-mail etc) and configure your mailbox as IMAP account and the SMTP for sending.

12 . Install roundcube

First, install the database schema

psql -U roundcube -d roundcube < /usr/share/webapps/roundcubemail/SQL/postgres.initial.sql

Then prepare the config

cd /etc/webapps/roundcubemail/config
cp db.inc.php.dist db.inc.php
cp main.inc.php.dist main.inc.php

In db.inc.php set the DSN like

$rcmail_config['db_dsnw'] = 'pgsql://roundcube:secret@localhost/roundcube';

In main.inc.php set

$rcmail_config['enable_installer'] = true;

Create a virtualhost or do a symlink into the default vhost:

ln -s /usr/share/webapps/roundcubemail /srv/http/roundcube

Now point your browser to http://localhost/roundcube/installer/ and follow the instructions. Afterwards delete the installer folder.

You should be able to log into your mailbox account.

13 . Setup fetchmail in postfixAdmin

For each Account you can configure any number of "external post boxes" to fetch mail from. I use it to get the mails from all boxes at GMX, gmail etc into one place. This can be set up directly in the PostfixAdmin WebUI.

Additionally you need a cronjob. Simply create /etc/cron.d/fetchmail with following content:

*/2 * * * * mail /usr/bin/perl /usr/share/doc/postfixadmin/ADDITIONS/fetchmail.pl &>/dev/null

Attention: This runs as user "mail" by default, so your user's custom bayes filters for spamassassin cannot be seen here, and filtering based on that will not work as expected. You need to create a cron job that read's the users Junk folder and creates a bayes database for the mail user. See Spamassassin section above.

14 . Final Steps

  • Install and configure opendkim to have DKIM signature signing and verification
  • Configure SPF records for your domain. In my case I had to include the ISP's mail relay, see host -t TXT kuther.net for an example
  • Replace spamassassin with DSPAM to have a spam filter with way less ressource consumption, easy statistics, clamav integration w/o the need of amavis-new and a overall accuracy of 99.x+ percent. I even turned off the spam filters of GMX and GMail, because my own spam filter knows my own mails better :-)
Category: 

Comments

Hey man, this tutorial help me a lot, thank you very much for it.Just one little observation, in the postfix pg files, you are using postfix database, while it should be postfixadmin.Greetings

Thanks for the comment. I'm glad it's useful for others. Not sure if I understand your oberservation correctly... the database is called postfix, postfixadmin is the pg user that owns it.