Setting up DMS (docker-mailserver) with PostgreSQL, PostfixAdmin and Roundcubemail

Posted by on Tue, Aug 8, 2023

Some notes in the beginning:

DMS, docker mail server, is, as of now (2023-08-08), not intended to be used with a database and postfixadmin. It offers its user and domain management through a script and a text file, where you write in your accounts with hashed passwords or your aliases.

This is not a tutorial, it is my personal story with the topic. I might rewrite this as a tutorial at a later point in time.

Motivation

What I want to do is, replacing my current native setup with a docker solution that I can setup easily and reproducible in case of server change/malfunction or the need to restore a backup.

The native setup consists of

  • postfix - how obvious
  • PostgreSQL
  • PostfixAdmin
  • Dovecot
  • RoundcubeMail
  • Rspamd
  • imapproxy for Roundcube, for keeping connections low
    • (don’t think that I will transfer that thing…)
  • opendkimd

And of course, the new setup should be capable of the same.

Why?

My problem is of course a bit self-produced. I am a Arch Linux user - every-fuckin-where. And with that I sometimes get incompatibilities. As with Arch you get almost bleeding edge packages, the applications, e.g. roundcubemail or postfixadmin, aren’t ready for the newest PHP most of the time.

My motivation is now to move the email service with all its components into a docker deployment, so that any upgrade of my base system isn’t interfering with the service at all. Target is to be able to do an upgrade schedule of the machine as I see fit and having a different upgrade schedule for the services.

I did this approach already with a couple of services, e.g. nextcloud (AIO) and quassel-core.

Now there could be someone questioning my Arch Linux usage on server systems, of course…
Yes, I could move to an Ubuntu server with more “stability”, only to have a big act of upgrading to every new LTS version.
And for a move to another Linux distribution, I would have to move the services anyway…

Whatever.

Having the deployment in docker makes it even easier, if I would ever do this.

Get started…

For continuing, I expect that you have at least basic knowledge about all the involved services (postfix, dovecot, postgresql, dns entries, rspamd, postfixadmin, roundcubemail, lets encrypt, etc.). I won’t go into details, but may provide links for further reading, if you’re lucky. :)

Now let’s get started…

DMS compose.yml

My first steps were:

First, I retrieved the compose.yaml from the DMS github project repository and as well as the mailserver.env.
Then, I added the necessary images to the compose.yaml

diff --git a/compose.orig.yml b/compose.yml
--- a/compose.orig.yml
+++ b/compose.yml
@@ -23,9 +23,34 @@ services:
     restart: always
     stop_grace_period: 1m
     # Uncomment if using \`ENABLE_FAIL2BAN=1\`:
-    # cap_add:
-    #   - NET_ADMIN
+    cap_add:
+      - NET_ADMIN
     healthcheck:
-      test: "ss --listening --tcp | grep -P 'LISTEN.+:smtp' || exit 1"
+      test: \["CMD-SHELL", "ss --listening --tcp | grep -P 'LISTEN.+:smtp' || exit 1"\]
       timeout: 3s
       retries: 0
+  mailserverdb:
+    image: postgres:14
+    restart: always
+    env_file: db.env
+    volumes:
+      - ./docker-data/postgres/data:/var/lib/postgresql/data
+    healthcheck:
+      test: \["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}"\]
+      interval: 1s
+      timeout: 5s
+      retries: 10
+  postfixadmin:
+    image: postfixadmin:apache
+    restart: always
+    env_file: postfixadmin.env
+    ports:
+      - 8080:80
+  roundcube:
+    image: roundcube/roundcubemail:latest
+    restart: always
+    env_file: roundcube.env
+    ports:
+      - 8000:80
+    volumes:
+      - ./docker-data/roundcube/data:/var/roundcube/db

Then I changed the mailserver.env to reflect my settings (yours might of course differ…)

diff --git a/mailserver.orig.env b/mailserver.env
index 038e23b..7a6cbe5 100644
--- a/mailserver.orig.env
+++ b/mailserver.env
@@ -72,7 +72,7 @@ PERMIT_DOCKER=none
 # \`/etc/localtime\`, which you can alternatively mount into the container. The value of this variable
 # must follow the pattern \`AREA/ZONE\`, i.e. of you want to use Germany's time zone, use \`Europe/Berlin\`.
 # You can lookup all available timezones here: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones#List
-TZ=
+TZ=Europe/Berlin

 # In case you network interface differs from 'eth0', e.g. when you are using HostNetworking in Kubernetes,
 # you can set NETWORK_INTERFACE to whatever interface you want. This interface will then be used.
@@ -98,12 +98,12 @@ ENABLE_SRS=0
 # Enables the OpenDKIM service.
 # \*\*1\*\* => Enabled
 #   0   => Disabled
-ENABLE_OPENDKIM=1
+ENABLE_OPENDKIM=0

 # Enables the OpenDMARC service.
 # \*\*1\*\* => Enabled
 #   0   => Disabled
-ENABLE_OPENDMARC=1
+ENABLE_OPENDMARC=0


 # Enabled \`policyd-spf\` in Postfix's configuration. You will likely want to set this
@@ -111,7 +111,7 @@ ENABLE_OPENDMARC=1
 #
 # - 0     => Disabled
 # - \*\*1\*\* => Enabled
-ENABLE_POLICYD_SPF=1
+ENABLE_POLICYD_SPF=0

 # 1 => Enables POP3 service
 # empty => disables POP3
@@ -125,13 +125,13 @@ ENABLE_CLAMAV=0
 # Enables Rspamd
 # \*\*0\*\* => Disabled
 #   1   => Enabled
-ENABLE_RSPAMD=0
+ENABLE_RSPAMD=1

 # When \`ENABLE_RSPAMD=1\`, an internal Redis instance is enabled implicitly.
 # This setting provides an opt-out to allow using an external instance instead.
 # 0 => Disabled
 # 1 => Enabled
-ENABLE_RSPAMD_REDIS=
+ENABLE_RSPAMD_REDIS=1

 # When enabled,
 #
@@ -140,7 +140,7 @@ ENABLE_RSPAMD_REDIS=
 #
 # \*\*0\*\* => disabled
 # 1     => enabled
-RSPAMD_LEARN=0
+RSPAMD_LEARN=1

 # Controls whether the Rspamd Greylisting module is enabled.
 # This module can further assist in avoiding spam emails by greylisting
@@ -148,7 +148,7 @@ RSPAMD_LEARN=0
 #
 # \*\*0\*\* => disabled
 # 1     => enabled
-RSPAMD_GREYLISTING=0
+RSPAMD_GREYLISTING=1

 # Can be used to enable or disable the Hfilter group module.
 #
@@ -164,7 +164,7 @@ RSPAMD_HFILTER_HOSTNAME_UNKNOWN_SCORE=6
 # Amavis content filter (used for ClamAV & SpamAssassin)
 # 0 => Disabled
 # 1 => Enabled
-ENABLE_AMAVIS=1
+ENABLE_AMAVIS=0

 # -1/-2/-3 => Only show errors
 # \*\*0\*\*    => Show warnings
@@ -176,13 +176,13 @@ AMAVIS_LOGLEVEL=0
 # Note: Emails will be rejected, if they don't pass the block list checks!
 # \*\*0\*\* => DNS block lists are disabled
 # 1     => DNS block lists are enabled
-ENABLE_DNSBL=0
+ENABLE_DNSBL=1

 # If you enable Fail2Ban, don't forget to add the following lines to your \`compose.yaml\`:
 #    cap_add:
 #      - NET_ADMIN
 # Otherwise, \`nftables\` won't be able to ban IPs.
-ENABLE_FAIL2BAN=0
+ENABLE_FAIL2BAN=1

 # Fail2Ban blocktype
 # drop   => drop packet (send NO reply)
@@ -191,7 +191,7 @@ FAIL2BAN_BLOCKTYPE=drop

 # 1 => Enables Managesieve on port 4190
 # empty => disables Managesieve
-ENABLE_MANAGESIEVE=
+ENABLE_MANAGESIEVE=1

 # \*\*enforce\*\* => Allow other tests to complete. Reject attempts to deliver mail with a 550 SMTP reply, and log the helo/sender/recipient information. Repeat this test the next time the client connects.
 # drop => Drop the connection immediately with a 521 SMTP reply. Repeat this test the next time the client connects.
@@ -246,7 +246,7 @@ ENABLE_QUOTAS=1
 # Set the message size limit for all users. If set to zero, the size will be unlimited (not recommended!)
 #
 # empty => 10240000 (~10 MB)
-POSTFIX_MESSAGE_SIZE_LIMIT=
+POSTFIX_MESSAGE_SIZE_LIMIT=20480000

 # Mails larger than this limit won't be scanned.
 # ClamAV must be enabled (ENABLE_CLAMAV=1) for this.

Notes:

  • opendkim and opendmarc are disabled, as rspamd can do that nowadays
  • SPF is now in rspamd too, as it seems…
  • I don’t need Virus scanning as of now, it anyway has a huge impact on performance, so Amavis or ClamAV are disabled

Continuing with the configuration of the postgresql instance through an .env file:

POSTGRES_USER=postfix
POSTGRES_PASSWORD=<hidden password>
POSTGRES_DB=postfix

And the postfixadmin instance through an .env file:

POSTFIXADMIN_DB_TYPE=pgsql     
#... - sqlite, mysqli, pgsql
POSTFIXADMIN_DB_NAME=postfix     
#.... - database name or path to database file (sqlite)
POSTFIXADMIN_DB_USER=postfix     
#... - mysqli/pgsql only (db server user name)
POSTFIXADMIN_DB_HOST=mailserverdb     
#... - hostname for database, default is localhost.
#POSTFIXADMIN_DB_PORT=
#... - port for the database (optional):
POSTFIXADMIN_DB_PASSWORD=<hidden password>
#... - mysqli/pgsql only (db server user password)
POSTFIXADMIN_ENCRYPT='php_crypt:SHA512::{SHA512-CRYPT}'
#... - database password encryption (e.g. md5crypt, SHA512-CRYPT)
POSTFIXADMIN_SETUP_PASSWORD='$2y$10$hYwsErlyEvAEK8mHuP0lLewicIrnFH6ZaJrCADb62Ann1ExwrAhH.'
#... - generated from setup.php or php -r "echo password_hash('mysecretpassword', PASSWORD_DEFAULT);"

Note: Setup password hash is an example.

Actually, postfixadmin is already accessible and can be setup and used. The rest of the services are of course still unusable.

Postfix configuration

Why it was a challenge for me…

It was a challenge for me as my configuration grew hysterically (historically) over eight years. The files of DMS and mine were not really comparable, so I got down to the approach of stripping both versions of all empty lines and comments, sorting the lines and then comparing what’s left. Which got me down to:

\# pseudocode and/or example lines!
# remove comments, empty lines and sort
cat main.cf | grep -v -E "^#|^$" | sort > old-main.cf
# doing this with both main.cf files, lets you take the approach of
vimdiff old-main.cf dms-main.cf

Configure postfix to access the postgresql database

The actual configuration of postfix comes down to the settings for the four mappings of

  • smtpd_sender_login_maps
  • virtual_alias_maps
  • virtual_mailbox_domains
  • virtual_mailbox_maps

As we are working with postfixadmin the correct queries need to be set

user = postfix
password = <hidden password>
dbname = postfix
hosts = postgresql://postfix:<hidden password>@mailserverdb/postfix

query = SELECT username AS allowedUser FROM mailbox WHERE username='%s' AND active = true UNION SELECT goto FROM alias WHERE address='%s' AND active = true
user = postfix
password = <hidden password>
dbname = postfix
hosts = postgresql://postfix:<hidden password>@mailserverdb/postfix

query = SELECT goto FROM alias where address = '%s' and active = '1'
table = alias
select_field = goto
where_field = address
additional_conditions = and active = '1'
user = postfix
password = <hidden password>
dbname = postfix
hosts = postgresql://postfix:<hidden password>@mailserverdb/postfix

query = SELECT domain FROM domain where domain = '%s' and backupmx = '0' and active = '1'
table = domain
select_field = domain
where_field = domain
additional_conditions = and backupmx = '0' and active = '1'
user = postfix
password = <hidden password>
dbname = postfix
hosts = postgresql://postfix:<hidden password>@mailserverdb/postfix

query = SELECT CONCAT(domain, '/', local_part) FROM mailbox WHERE username = '%s' and active = '1'
table = mailbox
select_field = CONCAT(domain, '/', local_part)
where_field = username
additional_conditions = and active = '1'

Configure dovecot

Now dovecot needs the same SQL access to the database managed by postfixadmin and also needs to know about the password hashing algorithm.

In DMS the /etc/dovecot/dovecot-sql.conf.ext needs to be overwritten.
Like this example:

\# I removed all the comments, to only show the actual important data

driver = pgsql
connect = host=mailserverdb dbname=postfix user=postfix password=<hidden password>                                                                                                                                       
default_pass_scheme = SHA512-CRYPT
user_query = \\                                                                                                                                                                                                                                 
  SELECT '/var/mail/%d/%n' as home, 'maildir:/var/mail/%d/%n' as mail, \\                                                                                                                                                                     
  5000 AS uid, 5000 AS gid, concat('dirsize:storage=', quota) AS quota \\                                                                                                                                                                       
  FROM mailbox WHERE username = '%u' AND active = true                                                                                                                                                                                         
password_query = \\                                                                                                                                                                                                                             
  SELECT username as user, password, '/var/mail/%d/%n' as userdb_home, \\                                                                                                                                                                      
  'maildir:/var/mail/%d/%n' as userdb_mail, 5000 as userdb_uid, 5000 as userdb_gid \\                                                                                                                                                          
  FROM mailbox WHERE username = '%u' AND active = true

pgsql

Remember what I said about DMS and databases? Yes? Good.

That DMS can work with the database you have to tweak it through user-patches.sh. I do not actually like that approach, especially if you would want to run the images without root policies enabled it will fail, but in my case it’s still okayish.

#!/usr/bin/env bash

apt-get update
apt-get install -y postfix-pgsql dovecot-pgsql

Yes, that simple. Still without nothing will work with the postgresql database. In an optimization step the cache of repository and packages could be cleaned aswell, but for now it’s only about a working system.

Sending and receiving mail

To get to the point where the services can send and receive mail, I had to find out that my settings in dovecot were wrong. The sql queries suggested the user vmail with the uid=1001 and the gid=1002 which is obviously not available in DMS. So I changed that to the docker user/group id=5000 and it began working inside dovecot at least. Logging into roundcube without errors at last.

In the postfix-main.cf I still had one error, that I accidentally put in the wrong hostname. Typical typo. Also it complained that the same domain shouldn’t be listed in mydestination and virtual_mailbox_domains, so I removed it from mydestination.

After a restart, postfix was able to receive mail. YAY.

Now for sending mails…

Initially I had problems here. But I found out that the settings in my overwrite main.cf (dms/config/postfix-main.cf) did have an error. Actually, the variable smtpd_tls_chain_files was still set to the default ‘snakeoil’ variant of DMS and I had to set this to my correct lets-encrypt certificate (first the key, then the fullchain.cem) and my postfix could also send mails.

Additional settings (DKIM, DMARC, SPF)

The only additional setting where you should check with dms to finally have the full chain working is the rspamd DKIM setting. Here you should actually follow the guide (rspamd tab!) to generate a DKIM key. It immediately spits out the settings you have to do in your DNS configuration.

Same for DMARC and SPF, as there’s no key required for this, just follow the guide from DMS.

Managesieve, rspamd webinterface

Last but not least I want to see how to get managesieve and rspamd webinterface running.

Managesieve

Managesieve needs some extra configuration in roundcubemail. So you have to add at least this to your config.inc.php

$config\['managesieve_host'\] = 'tls://domain.to';                                                                                                                                                                                                                                                                                                                                                                     
$config\['managesieve_auth_type'\] = 'PLAIN';

Not doing this, managesieve tries to access localhost, and with encryption and authentication, that will fail miserably. Been there, done that. ;)

rspamd web

This is also actually quite easy.

  • Copy out of the running container the file /etc/rspamd/local.d/worker-controller.inc
    • e.g. with this command:
      docker cp mailserver:/etc/rspamd/local.d/worker-controller.inc ./docker-data/dms/config/rspamd/
  • Run the command rspamadm pw inside the running container
    • docker-compose exec -it mailserver rspamadm pw
  • Copy the result into worker-controller.inc, like this:
    • password = "$2$wcopyxj5y8zeokzhbc6wm63tpq9magfg$f3omsrw4tgdq6x3pw16inkshms7tn3amix776refrt4pw2pujwdb";

Resulting file content:

\# documentation: https://rspamd.com/doc/workers/controller.html

bind_socket = "0.0.0.0:11334";
password = "$2$wcopyxj5y8zeokzhbc6wm63tpq9magfg$f3omsrw4tgdq6x3pw16inkshms7tn3amix776refrt4pw2pujwdb";

Add a forwarding for the compose.yml in the ports section of mailserver.
(      - "8003:11334" # rspamd web)

Last but not least, add a line to the volumes section where you mount the worker-controller.inc to it’s place and restart the deployment.

( - ./docker-data/dms/rspamd/worker-controller.inc:/etc/rspamd/local.d/worker-controller.inc:ro)

Finishing up

If you’ve read through this blog post, thank you for bearing with me through this journey.

For completness I will give you now almost all my config files here, with full content, especially those I showed only partly before, the small stuff i left out.
(Obviously anonymized and without passwords… :P)

And I condensed the config files to only contain actual configuration entries, removing empty lines and comments!

compose.yml

services:
  mailserver:
    image: ghcr.io/docker-mailserver/docker-mailserver:latest
    container_name: mailserver
    # Provide the FQDN of your mail server here (Your DNS MX record should point to this value)
    hostname: <the hostname>
    env_file: mailserver.env
    # More information about the mail-server ports:
    # https://docker-mailserver.github.io/docker-mailserver/latest/config/security/understanding-the-ports/
    # To avoid conflicts with yaml base-60 float, DO NOT remove the quotation marks.
    ports:
      - "25:25"    # SMTP  (explicit TLS => STARTTLS)
      - "143:143"  # IMAP4 (explicit TLS => STARTTLS)
      - "465:465"  # ESMTP (implicit TLS)
      - "587:587"  # ESMTP (explicit TLS => STARTTLS)
      - "993:993"  # IMAP4 (implicit TLS)
      - "8003:11334" # rspamd web
    volumes:
      - ./docker-data/dms/mail-data/:/var/mail/
      - ./docker-data/dms/mail-state/:/var/mail-state/
      - ./docker-data/dms/mail-logs/:/var/log/mail/
      - ./docker-data/dms/config/:/tmp/docker-mailserver/
      - ./docker-data/dms/rspamd/worker-controller.inc:/etc/rspamd/local.d/worker-controller.inc:ro
      - ./docker-data/dms/config/dovecot-override/conf.d/10-auth.conf:/etc/dovecot/conf.d/10-auth.conf:ro
      - ./docker-data/dms/config/dovecot-override/conf.d/10-mail.conf:/etc/dovecot/conf.d/10-mail.conf:ro
      - ./docker-data/dms/config/dovecot-override/conf.d/90-sieve.conf:/etc/dovecot/conf.d/90-sieve.conf:ro
      - ./docker-data/dms/config/dovecot-override/dovecot-sql.conf.ext:/etc/dovecot/dovecot-sql.conf.ext:ro
      - ./docker-data/dms/config/postfix-override/sql:/etc/postfix/sql:ro
      - ./docker-data/dms/certs:/tmp/dms/custom-certs/:ro
      - /etc/localtime:/etc/localtime:ro
    restart: always
    stop_grace_period: 1m
    # Uncomment if using \`ENABLE_FAIL2BAN=1\`:
    cap_add:
      - NET_ADMIN
    healthcheck:
      test: \["CMD-SHELL", "ss --listening --tcp | grep -P 'LISTEN.+:smtp' || exit 1"\]
      timeout: 3s
      retries: 0
  mailserverdb:
    image: postgres:14
    restart: always
    env_file: db.env
    volumes:
      - ./docker-data/postgres/data:/var/lib/postgresql/data
    healthcheck:
      test: \["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}"\]
      interval: 1s
      timeout: 5s
      retries: 10
  postfixadmin:
    image: postfixadmin:apache
    restart: always
    env_file: postfixadmin.env
    ports:
      - 8002:80
  roundcube:
    image: roundcube/roundcubemail:latest
    restart: always
    env_file: roundcube.env
    ports:
      - 8001:80
    volumes:
      - ./docker-data/roundcube/html:/var/www/html
      - ./docker-data/roundcube/data:/var/roundcube/db
      - ./docker-data/roundcube/config:/var/roundcube/config

mailserver.env

OVERRIDE_HOSTNAME=
DMS_DEBUG=0
LOG_LEVEL=info
SUPERVISOR_LOGLEVEL=
ONE_DIR=1
ACCOUNT_PROVISIONER=
POSTMASTER_ADDRESS=
ENABLE_UPDATE_CHECK=1
UPDATE_CHECK_INTERVAL=1d
PERMIT_DOCKER=none
TZ=Europe/Berlin
NETWORK_INTERFACE=
TLS_LEVEL=
SPOOF_PROTECTION=
ENABLE_SRS=0
ENABLE_OPENDKIM=0
ENABLE_OPENDMARC=0
ENABLE_POLICYD_SPF=0
ENABLE_POP3=
ENABLE_CLAMAV=0
ENABLE_RSPAMD=1
ENABLE_RSPAMD_REDIS=1
RSPAMD_LEARN=1
RSPAMD_GREYLISTING=1
RSPAMD_HFILTER=1
RSPAMD_HFILTER_HOSTNAME_UNKNOWN_SCORE=6
ENABLE_AMAVIS=0
AMAVIS_LOGLEVEL=0
ENABLE_DNSBL=1
ENABLE_FAIL2BAN=1
FAIL2BAN_BLOCKTYPE=drop
ENABLE_MANAGESIEVE=1
POSTSCREEN_ACTION=enforce
SMTP_ONLY=
SSL_TYPE=manual
SSL_CERT_PATH=/tmp/dms/custom-certs/fullchain.cer
SSL_KEY_PATH=/tmp/dms/custom-certs/cert.key
SSL_ALT_CERT_PATH=
SSL_ALT_KEY_PATH=
VIRUSMAILS_DELETE_DELAY=
POSTFIX_DAGENT=
POSTFIX_MAILBOX_SIZE_LIMIT=
ENABLE_QUOTAS=1
POSTFIX_MESSAGE_SIZE_LIMIT=20480000
CLAMAV_MESSAGE_SIZE_LIMIT=
PFLOGSUMM_TRIGGER=
PFLOGSUMM_RECIPIENT=
PFLOGSUMM_SENDER=
LOGWATCH_INTERVAL=
LOGWATCH_RECIPIENT=
LOGWATCH_SENDER=
REPORT_RECIPIENT=
REPORT_SENDER=
LOGROTATE_INTERVAL=weekly
POSTFIX_REJECT_UNKNOWN_CLIENT_HOSTNAME=0
POSTFIX_INET_PROTOCOLS=all
DOVECOT_INET_PROTOCOLS=all
ENABLE_SPAMASSASSIN=0
SPAMASSASSIN_SPAM_TO_INBOX=1
ENABLE_SPAMASSASSIN_KAM=0
MOVE_SPAM_TO_JUNK=1
SA_TAG=2.0
SA_TAG2=6.31
SA_KILL=10.0
SA_SPAM_SUBJECT=\*\*\*SPAM\*\*\*\*\*
ENABLE_FETCHMAIL=0
FETCHMAIL_POLL=300
ENABLE_GETMAIL=0
GETMAIL_POLL=5
ENABLE_LDAP=
LDAP_START_TLS=
LDAP_SERVER_HOST=
LDAP_SEARCH_BASE=
LDAP_BIND_DN=
LDAP_BIND_PW=
LDAP_QUERY_FILTER_USER=
LDAP_QUERY_FILTER_GROUP=
LDAP_QUERY_FILTER_ALIAS=
LDAP_QUERY_FILTER_DOMAIN=
DOVECOT_TLS=
DOVECOT_USER_FILTER=
DOVECOT_PASS_FILTER=
DOVECOT_MAILBOX_FORMAT=maildir
DOVECOT_AUTH_BIND=
ENABLE_POSTGREY=0
POSTGREY_DELAY=300
POSTGREY_MAX_AGE=35
POSTGREY_TEXT="Delayed by Postgrey"
POSTGREY_AUTO_WHITELIST_CLIENTS=5
ENABLE_SASLAUTHD=0
SASLAUTHD_MECHANISMS=
SASLAUTHD_MECH_OPTIONS=
SASLAUTHD_LDAP_SERVER=
SASLAUTHD_LDAP_BIND_DN=
SASLAUTHD_LDAP_PASSWORD=
SASLAUTHD_LDAP_SEARCH_BASE=
SASLAUTHD_LDAP_FILTER=
SASLAUTHD_LDAP_START_TLS=
SASLAUTHD_LDAP_TLS_CHECK_PEER=
SASLAUTHD_LDAP_TLS_CACERT_FILE=
SASLAUTHD_LDAP_TLS_CACERT_DIR=
SASLAUTHD_LDAP_PASSWORD_ATTR=
SASLAUTHD_LDAP_AUTH_METHOD=
SASLAUTHD_LDAP_MECH=
SRS_SENDER_CLASSES=envelope_sender
SRS_EXCLUDE_DOMAINS=
SRS_SECRET=
DEFAULT_RELAY_HOST=
RELAY_HOST=
RELAY_PORT=25
RELAY_USER=
RELAY_PASSWORD=

postfixadmin.env

POSTFIXADMIN_DB_TYPE=pgsql     
POSTFIXADMIN_DB_NAME=postfix     
POSTFIXADMIN_DB_USER=postfix     
POSTFIXADMIN_DB_HOST=mailserverdb
#POSTFIXADMIN_DB_PORT=     
POSTFIXADMIN_DB_PASSWORD=<hidden password>
POSTFIXADMIN_ENCRYPT='php_crypt:SHA512::{SHA512-CRYPT}'
POSTFIXADMIN_SETUP_PASSWORD='$2y$10$hYwsErlyEvAEK8mHuP0lLewicIrnFH6ZaJrCADb62Ann1ExwrAhH.'

roundcube.env

ROUNDCUBEMAIL_DEFAULT_HOST=tls://domain.to
#ROUNDCUBEMAIL_DEFAULT_PORT=143 # default
ROUNDCUBEMAIL_SMTP_SERVER=tls://domain.to
#ROUNDCUBEMAIL_SMTP_PORT=587 # default
#ROUNDCUBEMAIL_REQUEST_PATH=/ # default
ROUNDCUBEMAIL_PLUGINS=archive,zipdownload,password,managesieve
ROUNDCUBEMAIL_SKIN=elastic # defaults to larry
ROUNDCUBEMAIL_UPLOAD_MAX_FILESIZE=20M
# ROUNDCUBEMAIL_SPELLCHECK_URI
ROUNDCUBEMAIL_ASPELL_DICTS=de,en

db.env

POSTGRES_USER=postfix
POSTGRES_PASSWORD=<hidden password>
POSTGRES_DB=postfix

dovecot-override/conf.d/10-auth.conf

disable_plaintext_auth = yes
auth_realms = domain.to
auth_default_realm = domain.to
auth_username_chars = abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890.-_@
auth_username_format = %Lu
auth_mechanisms = plain login
!include auth-sql.conf.ext

dovecot-override/conf.d/10-mail.conf

mail_location = maildir:/var/mail/%d/%n
namespace inbox {
  inbox = yes
}
mail_uid = 5000
mail_gid = 5000
mail_privileged_group = docker
mail_plugins = $mail_plugins quota
maildir_stat_dirs = yes
maildir_copy_with_hardlinks = yes

dovecot-override/conf.d/90-sieve.conf

plugin {
  sieve = ~/.dovecot.sieve
  sieve_dir = ~/sieve
  sieve_before = /usr/lib/dovecot/sieve-global/before/
  sieve_after = /usr/lib/dovecot/sieve-global/after/
  sieve_extensions = +notify +imapflags +vnd.dovecot.pipe +vnd.dovecot.filter
  sieve_plugins = sieve_imapsieve sieve_extprograms
  recipient_delimiter = +
  sieve_max_script_size = 1M
  sieve_max_actions = 32
  sieve_pipe_bin_dir = /usr/lib/dovecot/sieve-pipe
  sieve_filter_bin_dir = /usr/lib/dovecot/sieve-filter
}

dovecot-override/dovecot-sql.conf.ext

driver = pgsql
connect = host=mailserverdb dbname=postfix user=postfix password=<hidden password>
default_pass_scheme = SHA512-CRYPT
user_query = \\
  SELECT '/var/mail/%d/%n' as home, 'maildir:/var/mail/%d/%n' as mail, \\
  5000 AS uid, 5000 AS gid, concat('dirsize:storage=', quota) AS quota \\
  FROM mailbox WHERE username = '%u' AND active = true
password_query = \\
  SELECT username as user, password, '/var/mail/%d/%n' as userdb_home, \\
  'maildir:/var/mail/%d/%n' as userdb_mail, 5000 as userdb_uid, 5000 as userdb_gid \\
  FROM mailbox WHERE username = '%u' AND active = true

postfix-accounts.cf (containing actually just a dummy)

dummy@domain.to|{SHA512-CRYPT}$6$0iuxO1kKZ.TjWX6V$zDfx8w11yHcxHogY8itJVnnoJhNDgfRXZFcmIDbzgicqQY/EimV6Zl6QlyU2tCPyLcbNYqFsxgEef31QzFFv91

postfix-main.cf

alias_database = hash:/etc/aliases
append_dot_mydomain = no
biff = no
broken_sasl_auth_clients = yes
compatibility_level = 2
disable_vrfy_command = yes
dms_smtpd_sender_restrictions = reject_authenticated_sender_login_mismatch, permit_sasl_authenticated, permit_mynetworks, warn_if_reject reject_non_fqdn_sender, reject_unknown_sender_domain, reject_unauth_pipelining, permit
header_checks = pcre:/etc/postfix/maps/header_checks.pcre
inet_interfaces = all
inet_protocols = all
local_recipient_maps = $virtual_alias_maps
mailbox_size_limit = 0
maximal_backoff_time = 8000s
maximal_queue_lifetime = 7d
message_size_limit = 21504000
milter_default_action = accept
milter_protocol = 6
minimal_backoff_time = 1000s
mua_sender_restrictions = reject_authenticated_sender_login_mismatch, $dms_smtpd_sender_restrictions
mydestination = localhost.$mydomain, localhost
myhostname = domain.to
mynetworks = 172.0.0.0/24 127.0.0.0/8 \[::ffff:127.0.0.0\]/104 \[::1\]/128 \[fe80::\]/64
mynetworks_style = host
myorigin = localhost
non_smtpd_milters =
proxy_read_maps = $local_recipient_maps $mydestination $virtual_alias_maps $virtual_alias_domains $virtual_mailbox_maps $virtual_mailbox_domains $relay_recipient_maps $relay_domains $canonical_maps $sender_canonical_maps $recipient_canonical_maps $relocated_maps $transport_maps $mynetworks $smtpd_sender_login_maps
postscreen_bare_newline_action = enforce
postscreen_dnsbl_action = enforce
postscreen_dnsbl_sites =
    b.barracudacentral.org\*2
    bl.mailspike.net=127.0.0.\[2;14;13;12;11;10\]
    bl.spameatingmonkey.net=127.0.0.2
    dnsbl.sorbs.net
    list.dnswl.org=127.0.\[0..255\].0\*-2
    list.dnswl.org=127.0.\[0..255\].1\*-3
    list.dnswl.org=127.0.\[0..255\].\[2..3\]\*-4
    psbl.surriel.com
    zen.spamhaus.org=127.0.0.\[2..11\]\*3
postscreen_dnsbl_threshold = 3
postscreen_dnsbl_whitelist_threshold = -1
postscreen_greet_action = enforce
readme_directory = no
recipient_delimiter = +
relayhost =
smtpd_banner = $myhostname ESMTP $mail_name (Arch Linux/GNU)
smtpd_client_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination, reject_unauth_pipelining
smtpd_delay_reject = yes
smtpd_helo_required = yes
smtpd_helo_restrictions = permit_mynetworks, warn_if_reject reject_non_fqdn_hostname, reject_invalid_hostname, reject_invalid_helo_hostname, permit
rspamd_milter = inet:localhost:11332
smtpd_milters = $rspamd_milter
smtpd_recipient_restrictions = permit_sasl_authenticated, permit_mynetworks, reject_unauth_destination, reject_unauth_pipelining, reject_invalid_helo_hostname, reject_non_fqdn_helo_hostname, reject_unknown_recipient_domain, check_policy_service inet:localhost:65265
smtpd_relay_restrictions = permit_mynetworks permit_sasl_authenticated defer_unauth_destination
smtpd_sasl_auth_enable = yes
smtpd_sasl_authenticated_header = yes
smtpd_sasl_local_domain = $mydomain
smtpd_sasl_path = /dev/shm/sasl-auth.sock
smtpd_sasl_security_options = noanonymous
smtpd_sasl_type = dovecot
smtpd_sender_login_maps = pgsql:/etc/postfix/sql/virtual_sender_login_maps.cf
smtpd_sender_restrictions = $dms_smtpd_sender_restrictions
smtpd_soft_error_limit = 3
smtpd_tls_CApath = /etc/ssl/certs
smtpd_tls_chain_files = /tmp/dms/custom-certs/cert.key /tmp/dms/custom-certs/fullchain.cer
smtpd_tls_dh1024_param_file = /etc/postfix/dhparams.pem
smtpd_tls_exclude_ciphers = aNULL, SEED, CAMELLIA, RSA+AES, SHA1
smtpd_tls_loglevel = 1
smtpd_tls_received_header = yes
smtpd_tls_mandatory_ciphers = high
smtpd_tls_mandatory_protocols = !SSLv2,!SSLv3,!TLSv1,!TLSv1.1
smtpd_tls_protocols = !SSLv2,!SSLv3,!TLSv1,!TLSv1.1
smtpd_tls_security_level = may
smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache
smtpd_tls_session_cache_timeout = 3600s
smtp_helo_timeout = 60s
smtp_tls_note_starttls_offer = yes
smtp_header_checks = pcre:/etc/postfix/maps/sender_header_filter.pcre
smtp_tls_CApath = /etc/ssl/certs
smtp_tls_loglevel = 1
smtp_tls_protocols = !SSLv2,!SSLv3,!TLSv1,!TLSv1.1
smtp_tls_security_level = may
tls_high_cipherlist = ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
tls_preempt_cipherlist = yes
tls_ssl_options = NO_COMPRESSION, NO_RENEGOTIATION
smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache
tls_random_source = dev:/dev/urandom
unknown_local_recipient_reject_code = 450
virtual_alias_maps = pgsql:/etc/postfix/sql/virtual_alias_maps.cf
virtual_mailbox_base = /var/mail
virtual_mailbox_domains = pgsql:/etc/postfix/sql/virtual_domains_maps.cf
virtual_mailbox_maps = pgsql:/etc/postfix/sql/virtual_mailbox_maps.cf
virtual_transport = lmtp:unix:/var/run/dovecot/lmtp

postfix-override/sql/virtual_*_maps.cf (all in one, separated by comment)

\# virtual_alias_maps.cf
user = postfix
dbname = postfix
hosts = postgresql://postfix:<hidden password>@mailserverdb/postfix
query = SELECT goto FROM alias where address = '%s' and active = '1'
table = alias
select_field = goto
where_field = address
additional_conditions = and active = '1'

# virtual_domains_maps.cf
user = postfix
dbname = postfix
hosts = postgresql://postfix:<hidden password>@mailserverdb/postfix
query = SELECT domain FROM domain where domain = '%s' and backupmx = '0' and active = '1'
table = domain
select_field = domain
where_field = domain
additional_conditions = and backupmx = '0' and active = '1'

# virtual_mailbox_maps.cf
user = postfix
dbname = postfix
hosts = postgresql://postfix:<hidden password>@mailserverdb/postfix
query = SELECT CONCAT(domain, '/', local_part) FROM mailbox WHERE username = '%s' and active = '1'
table = mailbox
select_field = CONCAT(domain, '/', local_part)
where_field = username
additional_conditions = and active = '1'

# virtual_sender_login_maps.cf
user = postfix
dbname = postfix
hosts = postgresql://postfix:<hidden password>@mailserverdb/postfix
query = SELECT username AS allowedUser FROM mailbox WHERE username='%s' AND active = true UNION SELECT goto FROM alias WHERE address='%s' AND active = true

At last, if anyone spots errors or has suggestions to improve the configurations, feel free to comment.



comments powered by Disqus