Ubuntu 26.04 auf dem Raspi

Aus Debacher-Wiki
Zur Navigation springenZur Suche springen

Um mich mit der Server-Version von Ubuntu 26.04 auseinanderzusetzen, habe ich einen Raspi4 genutzt, mit 4 GB Hauptspeicher.

Grundinstallation

Im ersten Schritt nach der Installation des Ubuntu:

apt update
apt upgrade

Das Upgrade dauert leicht einmal mehr als 30 Minuten.

Ich habe mir dann noch gleich ein paar nützliche Hilfsmittel eingerichtet:

apt install synaptic apt-xapian-index
update-apt-xapian-index -vf

Installiert das grafische Tool zur Paketverwaltung. Dann kann ich leichter nach Paketen suchen.

Auch grafische Tools lassen sich per ssh aufrufen, wenn man beim Start der Verbindung den Parameter -X mit angibt:

ssh -X root@mein-server.domain

Da ich schon häufiger Probleme mit den Festplatten hatte, sind mir die Smartmontools wichtig:

apt install smartmontools

Im Netzwerk benötigt man dann oft auch whois Informationen

apt install whois

Mit folgendem Paket kann ich die Netzwerk-Geschwindigkeit überprüfen.

apt install speedtest-cli

Für die Nutzung ruft man auf:

speedtest-cli --secure

Das --secure ist wichtig, da http-Anfragen nicht mehr funktionieren und hiermit auf https umgestellt wird.

Firewall

Gleich nach der ersten Übersicht ist mir aufgefallen, dass keinerlei Firewall aktiv ist. Bei Ubuntu ist dafür ufw installiert und muss noch aktiviert werden. Bei ufw kann man relativ komfortabel Ports sperren oder freigeben, teilweise sogar über vorkonfigurierte Pakete. Wichtig ist, dass man Port 22 freigibt, bevor man die Firewall aktiviert (https://ubuntu.com/server/docs/security-firewall, https://help.ubuntu.com/community/UFW):

ufw allow OpenSSH
ufw allow "Apache Full" 

Wer kommt auf die blöde Idee, einen Bezeichner mit Leerzeichen zu wählen?? Nun kann man die Firewall aktivieren mittels:

ufw enable

Würde es die App OpenSSH nicht geben, so würde man Port 22 freigeben mittels:

ufw allow 22

oder

ufw allow ssh

Die Apache-App steht erst zur Verfügung, wenn man den Webserver installiert hat. Die Liste der aktuell verfügbaren Apps kann man abrufen mittels:

ufw app list


Um nicht die syslog-Datei mit den ganzen Firewall-Meldungen zu füllen, habe ich noch die Datei /etc/rsyslog.d/20-ufw.conf editiert.

 # Log kernel generated UFW log messages to file
 :msg,contains,"[UFW " /var/log/ufw.log
 
 # Uncomment the following to stop logging anything that matches the last rule.
 # Doing this will stop logging kernel generated UFW log messages to the file
 # normally containing kern.* messages (eg, /var/log/kern.log)
 & stop

Die letzte Zeile beginnt normalerweise mit einem Kommentarzeichen, das nach Anweisung weg muss.

Inzwischen nutze ich ufw auch, um lästige IP-Adressbereiche zu blockieren:

ufw deny from 185.222.xxx.0/24 to any

Die Liste aller aktuellen Regeln kann man abfragen mit:

ufw status

Interessant ist auch die Version

ufw status numbered

Hier wird jeder Regel eine Nummer vorangestellt

Status: Aktiv

     Zu                         Aktion      Von
     --                         ------      ---
[ 1] Apache Full                ALLOW IN    Anywhere                  
[ 2] OpenSSH                    ALLOW IN    Anywhere                  
[ 3] mosh                       ALLOW IN    Anywhere                  
[ 4] Postfix                    ALLOW IN    Anywhere                  
[ 5] Dovecot POP3               ALLOW IN    Anywhere                  
[ 6] 21/tcp                     ALLOW IN    Anywhere                  
[ 7] 143                        ALLOW IN    Anywhere                  
[ 8] 993                        ALLOW IN    Anywhere                  
[ 9] 53/udp                     ALLOW IN    Anywhere                  
[10] Dovecot Secure POP3        ALLOW IN    Anywhere                  
[11] 5349/tcp                   ALLOW IN    Anywhere                  
[12] 5349/udp                   ALLOW IN    Anywhere                  
[13] Anywhere                   DENY IN     185.222.xxx.0/24          
[14] Apache Full (v6)           ALLOW IN    Anywhere (v6)             
[15] OpenSSH (v6)               ALLOW IN    Anywhere (v6)             
[16] mosh (v6)                  ALLOW IN    Anywhere (v6)             
[17] Postfix (v6)               ALLOW IN    Anywhere (v6)             
[18] Dovecot POP3 (v6)          ALLOW IN    Anywhere (v6)             
[19] 21/tcp (v6)                ALLOW IN    Anywhere (v6)             
[20] 143 (v6)                   ALLOW IN    Anywhere (v6)             
[21] 993 (v6)                   ALLOW IN    Anywhere (v6)             
[22] 53/udp (v6)                ALLOW IN    Anywhere (v6)             
[23] Dovecot Secure POP3 (v6)   ALLOW IN    Anywhere (v6)             
[24] 5349/tcp (v6)              ALLOW IN    Anywhere (v6)             
[25] 5349/udp (v6)              ALLOW IN    Anywhere (v6)             

Über diese Nummer kann man sie auch wieder löschen

ufw delete 13

würde also obige Regel wieder löschen.

An der Liste sieht man schon, dass man die Reihenfolge berücksichtigen muss. Viele Dienste sind schon erlaubt, bevor die Sperregel kommt. Also besser immer an Position 1 schieben mittels:

ufw insert 1 deny from 185.222.xxx.0/24 to any

Ob sich etwas tut, kann man abfragen mittels:

iptables  -v -n -L ufw-user-input

Bei mir sieht das Ergebnis folgendermaßen aus:

Chain ufw-user-input (1 references)
pkts bytes target     prot opt in     out     source               destination         
 100  6000 DROP       all  --  *      *       185.222.xxx.0/24     0.0.0.0/0           
 649 34606 ACCEPT     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            multiport dports 80,443 /* 'dapp_Apache%20Full' */
  85  4980 ACCEPT     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:22 /* 'dapp_OpenSSH' */
   0     0 ACCEPT     udp  --  *      *       0.0.0.0/0            0.0.0.0/0            multiport dports 60000:61000 /* 'dapp_mosh' */
 157  9380 ACCEPT     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:25 /* 'dapp_Postfix' */
...

Es wurden also 10 Pakete mit insgesamt 6000 Byte von der Regel blockiert.

Anpassungen

Die Spracheinstellungen anpassen. Bei mir war die benötigte Locale nicht vorhanden, wie der Aufruf von

locale -a

zeigte. Alles erst erzeugen mittels:

locale-gen de_DE.UTF-8

dann

update-locale LANG=de_DE.UTF-8

Danach neu anmelden, um den Effekt zu sehen.

~ # locale 
LANG=de_DE.UTF-8
LANGUAGE=
LC_CTYPE="de_DE.UTF-8"
LC_NUMERIC="de_DE.UTF-8"
LC_TIME="de_DE.UTF-8"
LC_COLLATE="de_DE.UTF-8"
LC_MONETARY="de_DE.UTF-8"
LC_MESSAGES="de_DE.UTF-8"
LC_PAPER="de_DE.UTF-8"
LC_NAME="de_DE.UTF-8"
LC_ADDRESS="de_DE.UTF-8"
LC_TELEPHONE="de_DE.UTF-8"
LC_MEASUREMENT="de_DE.UTF-8"
LC_IDENTIFICATION="de_DE.UTF-8"
LC_ALL=

Noch ein paar zusätzliche Pakete:

apt install bind9-host whois nmap lsof man-db
apt install arj bzip2 cabextract cpio file gzip nomarch pax unrar unzip zip lhasa p7zip ssl-cert lrzip lzop rpm2cpio unrar-free p7zip 
apt install gnupg logwatch

Hinweis: die vorher vorhandenen Pakete rar und p7zip-rar gibt es so nicht mehr.

Danach habe ich dann einen Reboot veranlasst.

Nachdem der Server wieder erreichbar war (hat recht lange gedauert), habe ich den Rechnernamen angepasst. Das hatte ich im Installimage vergessen zu machen.

joe /etc/hosts

dort alle Einträge mit dem Hetzner-Namen ersetzt (bei IPV4 und IPV6). Dann

joe /etc/hostname

dort ebenfalls den Namen ersetzt und dann

hostname -F /etc/hostname

Zum Abschluss wieder ein Neustart des Servers.

Verzeichnisse

Ein paar Verzeichnisse anlegen:

mkdir /var/log/ustat
mkdir /srv/www
mkdir /srv/www/typo3src
mkdir /etc/webalizer.d
mkdir /etc/logrotate.d/apache2-vhosts.d
mkdir /etc/vhosts-sicherung.d
mkdir /var/log/apache2-vhosts.d
mkdir /var/vmail
mkdir /var/log/vhosts-sicherung
mkdir /home/tmp
mkdir /home/server-sicherungen
mkdir /var/www
mkdir /var/www/vhosts
mkdir /var/www/htdocs
mkdir /var/www/htdocs/dummy
mkdir /var/www/htdocs/webalizer

Webserver

Diese Seite beschreibt die Einrichtung und Konfiguration von virtuellen Servern und beinhaltet:

  • MySQL bzw. MariaDB
  • Apache
  • letsencrypt

Apache-Vhosts.png

Viele Apache-Anwendungen benötigen eine Datenbank. Deshalb im ersten Schritt die Installation von MySQL.

Datenbank-MariaDB

Es gibt zwei bei Ubuntu verfügbare Versionen der Datenbank:

  • mysql
  • mariadb

Beide sind über die ursprünglichen MySQL-Befehle ansprechbar. MariaDB ist aber die offenere und moderne Version, sie will ich auf diesem Server nutzen.

Zur Installation von Server und Client dient der folgende Aufruf:

apt install mariadb-client mariadb-server mariadb-common

MySQL-Passwort

In aktuellen Datenbank-Versionen hat der Benutzer root keinen Zugriff mittels Passwort und kann daher z.B. auch nicht per phpmyadmin arbeiten. Der folgende MySQL-Befehl zeigt die Einstellung

MariaDB [(none)]> SELECT user,authentication_string,plugin,host FROM mysql.user;
+-------------+-----------------------+-----------------------+-----------+
| User        | authentication_string | plugin                | Host      |
+-------------+-----------------------+-----------------------+-----------+
| mariadb.sys |                       | mysql_native_password | localhost |
| root        | invalid               | mysql_native_password | localhost |
| mysql       | invalid               | mysql_native_password | localhost |
+-------------+-----------------------+-----------------------+-----------+
3 rows in set (0,002 sec)

MariaDB [(none)]> exit;
Bye

Ändern lässt sich diese Einstellung für root aktuell nicht. Nach der Installation von phpmyadmin gibt es aber einen Nutzer, der administrieren kann:

use mysql;
ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'password';
flush privileges;

Dann ergibt sich:

MariaDB [(none)]> SELECT user,authentication_string,plugin,host FROM mysql.user;
+-------------+-------------------------------------------+-----------------------+-----------+
| User        | authentication_string                     | plugin                | Host      |
+-------------+-------------------------------------------+-----------------------+-----------+
| mariadb.sys |                                           | mysql_native_password | localhost |
| root        | invalid                                   | mysql_native_password | localhost |
| mysql       | invalid                                   | mysql_native_password | localhost |
| phpmyadmin  | *A9172BC315E0BF4D14D021C4CD16374ED187B2B3 | mysql_native_password | localhost |
+-------------+-------------------------------------------+-----------------------+-----------+
4 rows in set (0,007 sec)

Das Passwort für phpmyadmin wird bei der Installation gesetzt. Will man es später einmal ändern, so muss man berücksichtigen, dass sich das Passwort für MySQL nicht mehr mit den altbekannten Kommandos setzen lässt, weil das Passwort nicht mehr im Feld password, sondern im Feld authentication_string zu finden ist:

use mysql;
UPDATE user SET authentication_string= password('password') WHERE User = 'phpmyadmin';
flush privileges;

Siehe auch https://stackoverflow.com/questions/30692812/mysql-user-db-does-not-have-password-columns-installing-mysql-on-osx

und https://www.digitalocean.com/community/tutorials/how-to-install-mysql-on-ubuntu-20-04-de

Nachdem das Passwort gesetzt ist, kann sich der Benutzer root auch per phpmyadmin anmelden.

Man kann man auch einen individuellen adminstrativen Benutzer anlegen:

CREATE USER administrator@localhost IDENTIFIED BY 'mystrongpassword';
GRANT ALL PRIVILEGES ON *.* TO administrator@localhost;
FLUSH PRIVILEGES;

oder besser sogar

GRANT ALL PRIVILEGES ON  *.* TO 'administrator'@'localhost' WITH GRANT OPTION;

MariaDB Logdatei

Bei der Fehlersuche im Mailsystem kann es hilfreich sein, wenn man nachvollziehen kann, was für Anfragen an die Datenbank gestellt wurden. Dazu kann man das Logging aktivieren, was im Normalfall nicht aktive ist, dazu fallen im Betrieb einfach zu viele Logging-Daten an.

Eine Abfrage wie

SHOW VARIABLES LIKE '%general%';

sollte normalerweise

general_log = OFF

liefern.

Die zugehörige Einstellung findet sich in der Datei

/etc/mysql/mariadb.conf.d/50-server.cnf

Dort (ab Zeile 52) die Kommentarzeichen vor den hervorgehobenen Zeilen entferne:

# Both location gets rotated by the cronjob.
# Be aware that this log type is a performance killer.
# Recommend only changing this at runtime for short testing periods if needed!
#general_log_file       = /var/log/mysql/mysql.log
#general_log            = 1

dann

mkdir -m 2750 /var/log/mysql
chown mysql mysql

und

systemctl restart mariadb

Nach erfolgreicher Analyse habe ich das Logging wieder abgestellt.

Weitere Informationen finden sich unter https://www.ionos.at/digitalguide/hosting/hosting-technik/mariadb-logs/

phpMyAdmin

Dieses php-Tool zur Verwaltung der Datenbank ist unheimlich praktisch. Es bietet lokalen Zugriff auf die Datenbank und erspart damit die Öffnung der Datenbank ins Netz. Bei der Installation wird auch gleich der Apache und die notwendigen php-Module mit installiert.

apt install phpmyadmin

Bei der Installation wird gefragt, welchen Webserver man nutzen möchte. Ich wähle den Apache. Am Ende der Installation kann man den Datenbank-Nutzer phpmyadmin anlegen lassen, sein Passwort wird erfragt.

Wer auf den Service mit dem ja dann bekannten Benutzer verzichten möchte, der kann auch vorher oder hinterher einen eigenen Benutzer anlegen.

Man muss also nicht dem Benutzer root den Zugriff ermöglichen. Aus Sicherheitsgründen ist es sowieso sinnvoller, einen individuellen Benutzer anzulegen, da oft genug versucht wird, sich über phpmyadmin einzuloggen. Ich habe mich auch angewöhnt, in der Datei /etc/apache2/conf-available/phpmyadmin.conf die Alias-Zeile auszukommentieren:

#Alias /phpmyadmin /usr/share/phpmyadmin

So kann ich erreichen, dass das Tool nicht allen virtuellen Systemen zur Verfügung steht, sondern die Berechtigung aktiv konfiguriert werden muss.

Nach diesen Schritten kann man aus dem Browser heraus auf http://<Servername>/phpmyadmin zugreifen.

letsencrypt

Für die verschlüsselte Übertragung von Webseiten werden Zertifikate von letsencrypt benötigt. Das notwendige Paket ist bei Ubuntu dabei, aber nur noch als Snap-Paket:

Auf https://certbot.eff.org findet sich eine Anleitung das Snap mit dem Certbot zu installieren. Wenn der snapd auf den Systemen bereits vorhanden ist, geht die Installation relativ einfach in folgenden Schritten (als root):

snap install core 
snap refresh core
snap install --classic certbot

Dann noch einen Link setzen:

ln -s /snap/bin/certbot /usr/bin/certbot

Für jede meiner virtuellen Domains habe ich dann eine letsencrypt.ini erstellt, mit folgendem Inhalt:

 # Aufruf mit: /usr/bin/certbot certonly --config /var/www/vhosts/meine-maildomain.de/letsencrypt.ini
 # Wir nutzen 4096 bit RSA key statt 2048
 rsa-key-size = 4096
 
 # allgemeine Angaben
 email = uwe@meine-maildomain.de
 authenticator = webroot
 
 # Domains fuer die wir Zertifikate beantragen, die erste in
 # der liste legt den Hauptnamen fest. Alle Domains müssen beim
 # Aufruf erreichbar sein
 domains = meine-maildomain.de, www.meine-maildomain.de
 
 # Dies ist das Verzeichnis zur Domain, wo letsencrypt seinen Hash in
 # /.well-known/acme-challenge schreiben will. Der Pfad muss auf / enden
 # es muss in der vserver.conf stehen:   Alias /.well-known   /var/www/htdocs/.well-known
 webroot-path = /var/www/htdocs/

Die Reihenfolge der Domains spielt insofern eine Rolle, als die erste Domain als Bezeichner für die Verzeichnisstruktur innerhalb von /etc/letsencrypt benutzt wird. Es bietet sich also an mit einer kürzeren Angabe zu beginnen.

Zum Erzeugen der Zertifikate dient dann der Aufruf:

/usr/bin/certbot certonly --config /var/www/vhosts/meine-maildomain.de/letsencrypt.ini

Die täglichen Aktualisierungsversuche für die Zertifikate übernimmt ein Con-Job, der automatisch angelegt wird (/etc/cron.d/certbot). Leider bekommt man dann keine Mails mehr, sondern muss in die Logdateien schauen, ob alles geklappt hat. Falls mir das auf Dauer nicht gefällt, so mache ich das wieder über den eigenen Cron-Job.

Was ich jetzt erst entdeckt habe, ist das Prinzip der Hooks. Man kann bei letsencript an mehreren Stellen Skripte hinterlegen, die bei Aktualisierung eines Zertifikates aufgerufen werden. Damit kann man z.B. den Webserver oder den Mailserver neu starten.


Zertifikate erweitern

Das Erweitern von letsencrypt Zertifikaten ist relativ einfach. Ich ergänze die Domains-Zeile in der Konfigurationsdatei und rufe den Erzeugungsprozess neu auf. Letsencrypt erkennt die Situation und fragt, ob ich das Zertifikat erweitern oder erneuern möchte. Wählt man hier Erweitern, so wird das passende Zertifikat neu erzeugt.


Zertifikat reduzieren

Manchmal soll eine Domain aus einem Zertifikat entfernt werden, weil man sie eventuell an anderer Stelle benötigt, oder sie nicht mehr verfügbar ist. Ich habe bisher keinen direkten Weg gefunden, wenn man vorgeht wie beim Erweitern und einfach die Liste verkürzt, dann legt Letsenctypt ein zusätzliches Zertifikat an, mit der Ergänzung -0001 am Namen. Man muss also das Zertifikat löschen und neu erstellen.

certbot delete --cert-name MeineDomain

Es geht auch interaktiv mit

certbot delete

es erscheint eine Liste der vorhandenen Zertifikate und man gibt die Nummer des Zertifikates an, das man löschen möchte.

Anschließend legt man das Zertifikat mit der verkleinerten Liste neu an.

Man muss aber damit rechnen, dass man von Letsencrypt Hinweis-Mails bekommt, wenn sich das eigentlich gelöschte Zertifikat dem Ablauf-Zeitpunkt nähert. So weit geht die Lösung nämlich nicht.

Dienste neu starten

In der Regel müssen z.B. die Mail-Dienste neu gestartet werden, wenn das benutzte Zertifikat erneuert wurde. Sonst wird weiterhin das alte Zertifikat benutzt. Letsencrypt kennt Hooks, über die Aktionen ausgelöst werden.

Hier die Datei /etc/letsencrypt/renewal-hooks/deploy/postfix-dovecot-reload.sh

#!/bin/sh
# Dieses script liegt ausführbar in: /etc/letsencrypt/renewal-hooks/deploy

for domain in $RENEWED_DOMAINS
do
    if [ "$domain" = "mail.<meine Domain>.de" ]
    then
        systemctl reload postfix
        systemctl reload dovecot
        
    fi
done

Automatische Aktualisierung

Danach kann man dann ausprobieren, ob der Certbot mit den vorhandenen Zertifikaten funktioniert:

certbot renew --dry-run

Denkbar ist dann ein Cronjob der Art:

/usr/bin/certbot renew

Meine Crontab habe ich dann angepasst, obwohl mir nicht ganz klar ist, ob die Aktualisierung nicht schon automatisch geschieht.

Mit

systemctl list-timers

kann man sich die Timer anschauen, die nicht über Cron laufen, sondern über den Systemd-Timer. Es gibt dort einen snap.certbot.renew.timer, der im Verzeichnis /etc/systemd/system/ zu finden ist, aber wohl nur für die Aktualisierung des Snaps zuständig ist.

Sonstige Timer sind übrigens meist im Verzeichnis /lib/systemd/system/ zu finden.

Ein neues Zertifikat erzeugt man dann mit

/usr/bin/certbot certonly --config /var/www/vhosts/<domain>/letsencrypt.ini


Apache

Der Apache Webserver ist ein enorm umfangreiches Stück Software. Ich hoffe, dass meine Konfiguration einigermaßen sinnvoll ist.

Installation

Die folgenden grundlegenden Pakete habe ich installiert

apt install apache2 apache2-bin apache2-data apache2-doc apache2-utils		

Dann ein paar Pakete für PHP

apt install libapache2-mod-php  php php-auth-sasl php-bz2 php-cli php-db php-gd php-imap php-log php-mail php-curl php-imagick php-intl
apt install php-mbstring php-mdb2 php-mysql php-net-smtp php-phpseclib php-soap php-tcpdf php-zip php-apcu php-opcache

Da sind eventuell noch Doppelungen drin, sowohl das Paket, als auch das Metapaket. Es ist aber das, was mir

dpkg --get-selections | grep php

lieferte.

Nicht vorhanden waren die Pakete php-gettext und php-recode, sowie in der aktuellen Version php-mdb2 und php-opcache. Die Ursachen muss ich noch recherchieren.

Nun noch ein paar Perl-Pakete:

apt install libapache2-mod-perl2 libapache2-reload-perl libarchive-zip-perl libauthen-sasl-perl libbsd-resource-perl libcairo-perl libgtk3-perl
apt install libcgi-fast-perl libfile-basedir-perl libfile-desktopentry-perl libfile-mimeinfo-perl libfont-afm-perl libgd-graph-perl libglib-perl 
apt install libhtml-form-perl libhtml-format-perl libhtml-template-perl libhttp-daemon-perl libimage-magick-perl libmailtools-perl libnet-dbus-perl libtie-ixhash-perl
apt install libx11-protocol-perl libxml-xpathengine-perl libdbi-perl libdbd-mysql libclass-dbi-mysql-perl

Nach erfolgter Konfiguration (s.u.) darf man nicht vergessen die notwendigen Ports in der Firewall frei zu geben:

ufw allow "Apache Full"

Server-Module

Vorsichtshalber noch einmal ein paar Apache-Module aktivieren:

a2enmod perl
a2enmod cgi
a2enmod expires
a2enmod headers 
a2enmod rewrite
a2enmod ssl
a2dismod status
service apache2 restart


Da ich viel mit CMS-Systemen arbeite, habe ich etwas an den PHP-Einstellungen gedreht in der /etc/php/8.3/apache2/php.ini:

post_max_size=12M
upload_max_filesize=12M
max_execution_time=240
max_input_vars = 1500
memory_limit = 512M

Typo3 benötigt unbedingt imagemagick (oder alternativ graphicsmagick)

apt install imagemagick imagemagick-doc
apt install graphicsmagick ghostscript 

Hinweis: Das Paket webalizer ist nicht vorhanden

locale

Anwendungen wie Owncloud benötigen Zugriff auf die locale-Einstellung über den Apache:

In der Datei: /etc/apache2/envvars

die Kommentarzeichen vor folgender Zeile entfernen (Zeile 30):

. /etc/default/locale

Reihenfolge-Probleme bei der Apache-Konfiguration

Bei der Apache-Konfiguration muss man sehr auf die Reihenfolge der einzelnen Einstellungen achten. Dabei spielt der Aufbau der Hauptkonfigurationsdatei /etc/apache2/apache2.conf eine wichtige Rolle. Hier werden der Reihe nach alle Dateien aus folgenden Unterverzeichnissen eingebunden:

  • mods-enabled
  • conf-enabled
  • sites-enabled

Die Dateien aus den einzelnen Verzeichnissen werden dann jeweils in alphabetischer Reihenfolge eingebunden, intern entsteht dabei eine einzige große Konfigurationsdatei. Innerhalb dieser virtuellen Datei spielen die Reihenfolgen eine Rolle.

Bei der Reihenfolge von Alias (auch SriptAlias) Anweisungen und Redirects, die in unterschiedlichen Kontexten auftreten, werden die Direktiven nach den üblichen Zusammenführungsregeln verarbeitet. Wenn jedoch mehrere Aliase oder Redirects im gleichen Kontext (z.B. im gleichen Abschnitt) auftreten, werden sie in einer bestimmten Reihenfolge verarbeitet.

Zunächst werden alle Umleitungen verarbeitet, bevor Aliase verarbeitet werden, und daher werden auf eine Anforderung, die mit einer Umleitung oder einer RedirectMatch übereinstimmt, niemals Aliase angewendet. Zweitens werden die Aliase und Redirects in der Reihenfolge verarbeitet, in der sie in den Konfigurationsdateien erscheinen, wobei die erste Übereinstimmung Vorrang hat.

Wenn also in der /etc/apache2/conf-available/postfixadmin.conf steht

Alias /postfixadmin /usr/share/postfixadmin/public

kann ich dies nicht im allgemeinen Teil der /etc/apache2/sites-available/000-default.conf überschreiben, weil die erst später inkludiert wird. Nur innerhalb einer Directory oder VirtualHost Struktur kann ich Veränderungen vornehmen, weil dies spezieller ist.

Aus dem gleichen Grund muss man, wenn zwei oder mehr dieser Direktiven auf denselben Unterpfad angewendet werden, den spezifischsten Pfad zuerst auflisten, damit alle Direktiven eine Wirkung haben. Zum Beispiel wird die folgende Konfiguration wie erwartet funktionieren:

ScriptAlias /cgi-bin/mailman/ /usr/lib/cgi-bin/mailman/
ScriptAlias /cgi-bin/ "/var/www/vhosts/default/cgi-bin/"

Aber wenn die beiden oben genannten Richtlinien in umgekehrter Reihenfolge angewendet würden, würde der /cgi-bin vor dem /cgi-bin/mailman-Alias stehen, so dass die letztere Richtlinie ignoriert würde.

Es ist sinnvoll beim Start von Apache auf die Meldungen im Syslog zu achten. Habe ich z.B. den Alias /postfixadmin bei den conf-enabled nicht auskommentiert, so erscheint bei meiner Konfiguration (s.u.) die Meldung

The Alias directive in /etc/apache2/sites-enabled/000-default.conf at line 19 will probably never match because it overlaps an earlier Alias.

/etc/apache2/sites-available/000-default.conf

Diese Datei besteht bei mir aus drei Teilen:

  • Zuerst die allgemeinen Einstellungen, die dann für alle virtuellen Systeme gelten, sofern sie nicht überschrieben werden.
  • Dann der virtuelle Default-Server für Port 80. Er würde aufgerufen, wenn jemand über die IP-Adresse, den Namen beim Provider (static.w.x.y.z.clients.your-server.de) oder eine nicht vorgesehene Domain kommt.
  • Der entsprechende virtuelle Default-Server für Port 443.

Die virtuellen Standard-Server sind auch im Zusammenhang mit letsencrypt-Zertifikaten für den Mailserver ganz praktisch. Ich muss nicht für mail.meine-maildomain.de eine Webseite konfigurieren, wenn ich ein Zertifikat erstellen oder erweitern möchte. Letsencrypt landet im Zweifelsfall hier und kann den Zugriff verifizieren. Der Standard-Server auf Port 443 erleichtert dann die Kontrolle der erstellten Zertifikate für den Mailserver, wenn das hier angegebene Zertifikat auch im Mailsystem genutzt wird.

ServerName default
ServerAdmin Uwe@meine-maildomain.de
 
ServerTokens Major
ServerSignature off
TraceEnable off
ProxyRequests off
 
UseCanonicalName Off
DocumentRoot /var/www/vhosts/default/httpdocs
 
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
 
CustomLog /var/log/apache2/vhosts_access.log vhost_combined
 
Alias /webmail       /var/www/htdocs/dummy
Alias /phpmyadmin    /var/www/htdocs/dummy
Alias /postfixadmin  /var/www/htdocs/dummy
 
Alias /webstat       /var/www/htdocs/webalizer
Alias /roundcube     /var/lib/roundcube
 
<IfDefine MAILMAN>
  ScriptAlias     /mailman/       /var/www/htdocs/dummy
  Alias           /mailmanicons/  /var/www/htdocs/dummy
  Alias           /pipermail/     /var/www/htdocs/dummy
</IfDefine>
 
<IfModule mod_userdir.c>
  UserDir disabled
</IfModule>
 
<IfModule mod_expires.c>
  ExpiresActive On
  ExpiresDefault "access plus 1 month"
  ExpiresByType text/html "access plus 1 week"
  ExpiresByType image/gif "access plus 1 week"
  ExpiresByType image/jpeg "access plus 1 week"
  ExpiresByType image/png "access plus 1 week"
  ExpiresByType text/css "access plus 1 week"
  ExpiresByType text/javascript "access plus 1 week"
  ExpiresByType application/x-javascript "access plus 1 week"
  ExpiresByType text/xml "access plus 1 week"
</IfModule>
 
<IfModule mod_setenvif.c>
  # SEO
  BrowserMatchNoCase (mindUp|meanpathbot|seoscanners|AiHitBot|BLEXBot|DotBot|linkdexbot|MJ12bot|SEOkicks-Robot) ist_ein_bot
  # Sammeln Backlinks & Links
  BrowserMatchNoCase (exabot|Baidu|Haosou|Semrush|MegaIndex|AhrefsBot|BacklinkCrawler|dlcbot|spbot) ist_ein_bot
  # Performance Testing
  BrowserMatchNoCase (200PleaseBot|LoadTimeBot) ist_ein_bot
  # BilderSuche
  BrowserMatchNoCase (psbot|Yandex) ist_ein_bot
  # Harvester & Marketing
  BrowserMatchNoCase (MegaIndex|Applebot|XoviBot|CareerBot|GrapeshotCrawler|iCjobs|magpie-crawler|proximic) ist_ein_bot
  # Nutzlos, Schlecht bzw. unbekannt
  BrowserMatchNoCase (PetalBot|360Spider|AfD-Verbotsverfahren|Barkrowler|PeoplePal|ltx71|CalendarAgent|JobboerseBot|GarlikCrawler) ist_ein_bot
  # Per IP
  SetEnvIfNoCase Remote_Addr (62\.138\.0\.25) ist_ein_bot
  # kyivstar.net
  SetEnvIfNoCase Remote_Addr ^(5\.248|46\.118|37\.115|178\.137) ist_ein_bot
  # Einträge von 2020
  BrowserMatchNoCase (MaviBot|oBot|MetaJobBot|seocompany|coccocbot-image|SeznamBot) ist_ein_bot
</IfModule>
 
<IfModule mod_ssl.c>
  SSLProtocol All -SSLv2 -SSLv3 -TLSv1 -TLSv1.1
  SSLHonorCipherOrder On
  SSLCompression off  
  SSLCipherSuite ALL:!aNULL:RC4+RSA:+HIGH:+MEDIUM:+LOW:+EXP:+eNULL
</IfModule>

<Directory "/var/www/vhosts">
  AllowOverride All
  Options +SymLinksIfOwnerMatch -Indexes
  Require all granted
 
  <IfModule mod_php.c>
    php_value date.timezone "Europe/Berlin"    
    php_value open_basedir /var/www/:/tmp/
    php_value include_path /var/www/:/tmp/
  </IfModule>
 
</Directory>
  
<VirtualHost *:80>
  Alias /.well-known   /var/www/htdocs/.well-known
  <Directory /var/www/vhosts/default/httpdocs>
    AllowOverride All
    Options None
    Require all granted
  </Directory>
 
</VirtualHost>
 
<IfModule mod_ssl.c>
  <VirtualHost _default_:443 >
    SSLEngine on
   # Wenn das erste Zertifikat mit Letsencryp erstellt ist, dann die Zertifikate austauschen
   # SSLCertificateFile      /etc/ssl/certs/ssl-cert-snakeoil.pem
   # SSLCertificateKeyFile /etc/ssl/private/ssl-cert-snakeoil.key
    SSLCertificateFile /etc/letsencrypt/live/<dummy>/fullchain.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/<dummy>/privkey.pem
 
    <FilesMatch "\.(cgi|shtml|phtml|php)$">
      SSLOptions +StdEnvVars
    </FilesMatch>
 
    <Directory /var/www/vhosts/default/httpdocs>
      SSLRequireSSL
      AllowOverride All
      Options None
      Require all granted
    </Directory>
 
  </VirtualHost>
 
</IfModule>

Die, hier auskommentierten SnakeOil-Zertifikate sind vom Server selbst erstellte Zertifikate, die man nur zum Testen nutzen sollte. Falls sie nicht vorhanden sind, dann kann man sie erzeugen mittels:

make-ssl-cert

Weitere Hinweise dazu unter https://www.thomas-krenn.com/de/wiki/Ubuntu_default_snakeoil_SSL-Zertifikat_erneuern

Die SSL-Einstellungen der Seite kann man unter https://www.cdn77.com/tls-test (Hinweis: deren Cache lässt sich anscheinend nicht löschen) und ganz ausführlich unter https://www.ssllabs.com/ssltest/ testen lassen. Es geht übrigens auch ohne externen Dienstleister mittels

nmap --script ssl-enum-ciphers -p 443 example.com

Ursprünglich hatte ich hier auch einen Alias für .well-known mir drin, das führt aber zu Problemen bei z.B. Nextcloud, hier wird mit Unterverzeichnissen von .well-known gearbeitet und dann treten Probleme mit der Reihenfolge auf.

Vserver-Konfiguration

Für jeden weiteren VServer erfolgt die Konfiguration in /etc/apache2/vhosts/sites-available/<dummy>.conf nach folgendem System, wobei <dummy> durch z.B. den Domainnamen ersetzt wird. Das zugehörige Verzeichnis wird dann mit diesem Namen unterhalb von /var/www/vhosts/ angelegt.

<VirtualHost *:80>
  ServerName   www.<dummy>:80
  ServerAlias <dummy>
  DocumentRoot /var/www/vhosts/<dummy>/httpdocs
 
  CustomLog  /var/log/apache2/<dummy>_access.log combined
  ErrorLog   /var/log/apache2/<dummy>_error.log
 
  CustomLog /var/log/apache2/vhosts_access.log vhost_combined
 
  ScriptAlias  /cgi-bin/ /var/www/vhosts/<dummy>/cgi-bin/
  Alias  /webstat       /var/www/vhosts/<dummy>/webstat
  Alias /.well-known   /var/www/htdocs/.well-known
 # Nur bei Bedarf aktivieren
 # Alias /phpmyadmin /usr/share/phpmyadmin
 # Alias /postfixadmin  /usr/share/postfixadmin/public
 
  <Directory /var/www/vhosts/<dummy>/httpdocs>
 
    <IfModule mod_setenvif.c>
      <RequireAll> 
        Require all granted
        Require not env ist_ein_bot
      </RequireAll> 
    </IfModule>
 
    <IfModule mod_php.c>
      php_admin_flag engine on
      php_admin_value include_path    "/var/www/vhosts/<dummy>/httpdocs:.:/tmp:./:/usr/share/php/PEAR/:/srv/www/typo3src:/usr/bin"
      php_admin_value open_basedir    "/var/www/vhosts/<dummy>/httpdocs:/tmp:.:/usr/share/php/PEAR/:/srv/www/typo3src:/usr/bin"
    </IfModule>
 
    Options -Includes +ExecCGI
  </Directory>
 
  <Directory "/var/www/vhosts/<dummy>/cgi-bin">
    AllowOverride None
    Options +ExecCGI -Includes
    Require all granted
  </Directory>
 
</VirtualHost>
 
# soll SSL aktiviert werden das _no entfernen
<IfModule mod_ssl_no.c>
 
  <VirtualHost *:443>
    ServerName   www.<dummy>:443
    ServerAlias <dummy>
    DocumentRoot /var/www/vhosts/<dummy>/httpdocs
 
    CustomLog  /var/log/apache2/<dummy>_access.log combined
    ErrorLog   /var/log/apache2/<dummy>_error.log
 
    CustomLog /var/log/apache2/vhosts_access.log vhost_combined
 
    ScriptAlias  /cgi-bin/ /var/www/vhosts/<dummy>/cgi-bin/
    Alias  /webstat /var/www/vhosts/<dummy>/webstat
    Alias /.well-known   /var/www/htdocs/.well-known
   # Nur bei Bedarf aktivieren
   # Alias /phpmyadmin /usr/share/phpmyadmin
   # Alias /postfixadmin  /usr/share/postfixadmin/public
    SSLEngine on
 
    <Directory /var/www/vhosts/<dummy>/httpdocs>
      <IfModule mod_setenvif.c>
        <RequireAll> 
          Require all granted
          Require not env ist_ein_bot
        </RequireAll> 
      </IfModule>
 
      <IfModule mod_php.c>
        php_admin_flag engine on
        php_admin_value include_path    "/var/www/vhosts/<dummy>/httpdocs:.:/tmp:./:/usr/share/php/PEAR/:/srv/www/typo3src:/usr/bin"
        php_admin_value open_basedir    "/var/www/vhosts/<dummy>/httpdocs:/tmp:.:/usr/share/php/PEAR/:/srv/www/typo3src:/usr/bin"
      </IfModule>
 
      Options -Includes +ExecCGI
    </Directory>
 
    <Directory "/var/www/vhosts/<dummy>/cgi-bin">
      AllowOverride None
      Options +ExecCGI -Includes
      Require all granted
    </Directory>
  
    SSLCertificateFile /etc/letsencrypt/live/<dummy>/fullchain.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/<dummy>/privkey.pem
  </VirtualHost>
 
</IfModule>

Verwaltungs-Vserver

Für die Nutzung von

  • postfixadmin
  • phpmyadmin
  • rspamd
  • eigenen CGI Scripten

habe ich mir einen speziellen virtuellen Server erstellt, der keine eigenen Ordner bekommt, sondern auf dem Default-Server aufsetzt. /etc/apache2/vhosts/sites-available/verwaltung.conf

<VirtualHost *:80>
  ServerName   verwaltung.<dummy>:80
  DocumentRoot /var/www/vhosts/default/httpdocs
 
  CustomLog  /var/log/apache2/verwaltung_access.log combined
  ErrorLog   /var/log/apache2/verwaltung_error.log
 
  CustomLog /var/log/apache2/vhosts_access.log vhost_combined
 
  ScriptAlias  /cgi-bin/ /var/www/vhosts/default/cgi-bin/
  Alias  /webstat        /var/www/vhosts/default/webstat
  Alias /phpmyadmin      /usr/share/phpmyadmin
  Alias /postfixadmin    /usr/share/postfixadmin/public
  Alias /.well-known     /var/www/htdocs/.well-known
  
  <Directory /var/www/vhosts/default/httpdocs>
 
    <IfModule mod_setenvif.c>
      <RequireAll> 
        Require all granted
        Require not env ist_ein_bot
      </RequireAll> 
    </IfModule>
  
    Options -Includes +ExecCGI
  </Directory>
 
  <Directory "/var/www/vhosts/default/cgi-bin">
    AllowOverride None
    Options +ExecCGI -Includes
    Require all granted
  </Directory>
 
  RewriteEngine On
  ProxyRequests Off
  <Location /rspamd>
    Order allow,deny
    Allow from all
  </Location>
  RewriteRule ^/rspamd$ /rspamd/ [R,L]
  RewriteRule ^/rspamd/(.*) http://localhost:11334/$1 [P,L]
 
</VirtualHost>
 
# soll SSL aktiviert werden das _no entfernen
<IfModule mod_ssl_no.c>
 
  <VirtualHost *:443>
    ServerName   verwaltung.<dummy>:443
    DocumentRoot /var/www/vhosts/default/httpdocs
 
    CustomLog  /var/log/apache2/verwaltung_access.log combined
    ErrorLog   /var/log/apache2/verwaltung_error.log
 
    ScriptAlias  /cgi-bin/  /var/www/vhosts/default/cgi-bin/
    Alias  /webstat         /var/www/vhosts/default/webstat
    Alias /phpmyadmin       /usr/share/phpmyadmin
    Alias /postfixadmin     /usr/share/postfixadmin/public
    Alias /.well-known      /var/www/htdocs/.well-known
 
    SSLEngine on
 
    <Directory /var/www/vhosts/default/httpdocs>
 
      <IfModule mod_setenvif.c>
        <RequireAll> 
          Require all granted
          Require not env ist_ein_bot
        </RequireAll> 
      </IfModule>
 
      Options -Includes +ExecCGI
    </Directory>
 
    <Directory "/var/www/vhosts/default/cgi-bin">
      AllowOverride None
      Options +ExecCGI -Includes
      Require all granted
    </Directory>
 
    SSLCertificateFile /etc/letsencrypt/live/verwaltung.<dummy>/fullchain.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/verwaltung.<dummy>/privkey.pem
 
    RewriteEngine On
    ProxyRequests Off
    <Location /rspamd>
      Order allow,deny
      Allow from all
    </Location>
    RewriteRule ^/rspamd$ /rspamd/ [R,L]
    RewriteRule ^/rspamd/(.*) http://localhost:11334/$1 [P,L]
 
  </VirtualHost>
 
</IfModule>

Logdateien

Da ich die Logdateien für meine virtuellen Server mit Webalizer auswerten möchte, brauche ich eine Erweiterung für Logrotate:

/var/log/apache2/*.log {
        daily
        missingok
        rotate 14
        compress
        delaycompress
        notifempty
        create 640 root adm
        sharedscripts
        postrotate
                if invoke-rc.d apache2 status > /dev/null 2>&1; then \
                    invoke-rc.d apache2 reload > /dev/null 2>&1; \
                fi;
        endscript
        prerotate
             /root/webalizer.sh
                if [ -d /etc/logrotate.d/httpd-prerotate ]; then \
                        run-parts /etc/logrotate.d/httpd-prerotate; \
                fi; \
        endscript
}

Entweder hängt man diesen Teil an /etc/logrotate.d/apache2 an oder man erstellt eine neue Datei /etc/logrotate.d/apache-vhosts .


Mailsystem

Das Mail-System besitzt folgende Komponenten bzw. Funktionen:

  • Postfixadmin zur Verwaltung der virtuellen Benutzer
  • Dovecot als POP und IMAP-Server
  • Postfix als Mailserver
  • rspamd über die Milter-Schnittstelle und
    • redis als leichte Datenbank für rspamd
    • spf und dkim sollen auch aktiviert sein
  • Roundcube als Mailclient

großes Bild zum Zusammenspiel von Postfix, Dovecot, rspamd und Postfixadmin

SPF, DKIM und DMARC gegen Spam

Mailserver sind von Natur aus vertrauensselig, was von Spammern ausgenutzt wird. Ich denke, dass mehr als 90% aller versendeten Nachrichten in die Kategorie Spam gehören. Der Mailserver Postfix beherrscht ein von Haus aus ein paar Möglichkeiten den Spam zu verringern:

  • Nutzung von RBL Listen in den smtpd_client_restrictions. Für jede eingehende Mail werden dann externe Server wie cbl.abuseat.org befragt, ob sie den sendenden Server kennen. Wenn ja, dann wird die Mail rejected. Damit dieses System effektiv wird, muss man aber mehrere derartige Server parallel nutzen.
  • Regeln über den formalen Aufbau einer Mail z.B. in den smtpd_helo_restrictions. Hier kann man mit reject_invalid_helo_hostname erreichen, dass nur Mails von Servern angenommen werden, bei denen der Name nicht korrekt ist.

Für weitergehende Untersuchungen, z.B. auf Viren oder Spam bindet man in der Regel zusätzliche Hilf-Programme ein, in der Regel SpamAssassin oder neuerdings auch rspamd. Diese Programme teilen dem Mailserver mit, wie mit der Mail weiter zu verfahren ist. Dabei taucht das Problem auf, dass eine Mail die der Mailserver angenommen hat auch zugestellt werden muss, sofern sich keinen direkten Schadcode beinhaltet. Normaler Spam muss also zugestellt werden, wenn die Mail nicht frühzeitig als Spam erkannt und daher nicht angenommen wird.

Die Spamfilter klassifizieren jede Mail anhand unterschiedlicher Regeln bzw. Kriterien und vergeben dabei einen Score. Je höher der Score, desto wahrscheinlicher handelt es sich um Spam.

Mit der Nutzung von SPF, DKIM und DMARC möchte ich erreichen, dass der Score meiner Mails auf anderen Servern gesenkt wird und sollte jemand mit einer meiner Domains illegal Mails verschicken, so soll sich deren Score erhöhen. Das ist die Idee hinter diesen Konzepten.

SPF

Das Policy Framework (SPF) dient dazu, die Mail anhand der IP Adresse des sendenden Mailservers zu überprüfen. Dazu wird in einem speziellen Namesereintrag vom Typ TXT festgelegt, welche Server berechtigt sind Mails mit dieser Domain zu verschicken.

Im einfachsten Fall sieht der TXT Eintrag folgendermaßen aus:

v=spf1 a mx -all

Ein

dig -t txt meine-maildomain.de 

liefert dann

meine-maildomain.de.	3600	IN	TXT	"v=spf1 a mx -all"

Damit wird ausgesagt, dass nur Server, die einen A- oder einen MX-Eintrag in dieser Domain besitzen berechtigt sind Mails abzuliefern. Über das -all wird festgelegt, wie zu verfahren ist, wenn sich ein Server meldet, der der Regel nicht entspricht.

  • -all : Fail der Server darf nicht senden
  • ~all : SoftFail der Server darf eigentlich nicht senden, sei aber großzügig
  • ?all : Neutral jeder Sender wird akzeptiert.

Ausführlichere Beschreibung der Syntax unter https://www.spf-record.de/syntax

Ein Vorteil von SPF ist, dass am sendenden Server keinerlei Konfiguration notwendig ist. Der größte Nachteil besteht darin, dass es bei Weiterleitungen Probleme gibt, da dann der sendende Server nicht mehr zum Absender passt.

Domainfactory generiert übrigens automatisch auch einen Catchall Eintrag für SPF.

DKIM

Auch das System Keys Identified Mail (DKIM) dient auch dazu nur bestimmte Server zum Senden zu authentifizieren. Das geschieht, indem jede ausgehende Mail im Header digital signiert wird. Der öffentlichen Schlüssel hierzu ist im Nameserver hinterlegt. Der private Schlüssel muss natürlich geschützt auf dem sendenden Server liegen. Da mehrere Server sendeberechtigt sein können, kann es auch mehrere derartige Schlüssel geben. Deshalb wird zusätzlich ein sog. Selektor mit im Mail-Header hinterlegt, damit der Empfänger den richtigen Schlüssel wählen kann.

dig -t txt 2020._domainkey.meine-maildomain.de

liefert dann

2020._domainkey.meine-maildomain.de. 3600 IN TXT "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQClkpAaTMA5GcACTbSfJ1vSGwJyHnv1IVzR4GEu3PMAAIvuYCWPONypEAErZJciY8gK+z3FoPaMdsB+eii4rozlN6Jtfx+Fi/SM0FWV1PvPVI0tkoIXa82lKfJi4hqbWDHXxQYfROEKNFWZtoG/C951MwNOiq34Vk3X0nNoBWfyHwIDAQAB"

In diesem Beispiel ist 2020 der Selector. Damit der Mailclient den richtigen Schlüssel abfragen kann, findet sich in DKIM-Header ein Bereich s=2020.

Weitere Informationen unter https://de.wikipedia.org/wiki/DomainKeys

Ein großer Vorteil von DKIM gegenüber SPF ist, dass Weiterleitungen kein Problem sind. Der versendende Server spielt ja keine Rolle. Ein großer Nachteil ist, dass der Server entsprechend konfiguriert werden muss, damit er die richtigen Header-Zeilen erzeugen kann.

DMARC

Das Konzept Message Authentication, Reporting and Conformance (DMARC) baut auf SPF und DKIM auf. Mit DMARC kann der Absender zusätzlich Empfehlungen geben, auf welche Art der Empfänger mit einer Mail umgeht, die in einem oder beiden Fällen nicht den Anforderungen entspricht.

Es gibt die folgenden Regeln

  • p=reject lehnt Mail ab, bei denen die Prüfung fehlgeschlagen ist
  • p=quarantine lässt Mail in den Spam-Ordner verschieben, bei denen die Überprüfung fehlgeschlagen ist
  • p=none weist den empfangenden Server an Berichte an die Mailadresse zu schicken, die per rua bzw. ruf hinterlegt wurde. Dazu ein Beispiel aus Wikipedia
v=DMARC1;p=quarantine;pct=100;rua=mailto:postmaster@example.org;ruf=mailto:forensik@example.org;adkim=s;aspf=r

Dann ein paar zusätzliche Parameter:

  • pct: gibt an, auf wieviel Prozent der Nachrichten die Regel angewandt wird (Standard: 100)
  • rua: Mail-Adresse, an die der aggregierte Bericht gesendet werden soll
  • ruf: Mail-Adresse, an die der forensische Bericht gesendet werden soll
  • fo: Festlegung, in welcher Situation Daten für einen Bericht gesammelt werden sollen:
    • 0 (Bericht, wenn SPF+DKIM fehlschlagen)
    • 1 (Bericht, wenn SPF oder DKIM fehlschlagen)
    • s (Bericht, wenn SPF fehlschlägt)
    • d (Bericht, wenn DKIM fehlschlägt)
  • adkim: müssen bei DKIM Domain und Subdomain übereinstimmen?
    • r (relaxed) ist Standard
    • s (strict)
  • aspf: müssen bei SPF Domain und Subdomain übereinstimmen?
    • r (relaxed) ist Standard
    • s (strict)
  • ri: Häufigkeit in Sekunden, mit der die Berichte gesendet werden. Standard: 86400 (ein Tag)


Weitere Informationen unter: https://de.wikipedia.org/wiki/DMARC

dig -t txt _dmarc.meine-maildomain.de

liefert hier

_dmarc.meine-maildomain.de. 3599 IN	TXT	"v=DMARC1; p=reject;"

Hinweis zu dig

Wer auf seinem Rechner nicht über das Programm dig verfügt, der kann die entsprechenden Anfragen über die folgenden Webseiten aufrufen:

Vorbereitungen

Für die folgende Beschreibung wird ein Benutzer vmail (uid 303) und eine Gruppe vmail (gid 303) benutzt. Der Benutzer hat als Homeverzeichnis /var/vmail, wo dann auch die eingehenden Mails liegen.

Da Gruppe und Benutzer nicht vorhanden sind, legt man sie neu an:

 groupadd -g 303 vmail
 mkdir /var/vmail
 chmod a+rxw /var/vmail
 useradd -d /var/vmail/ -s /bin/false -u 303 -g 303 vmail

Postfixadmin

Leider konnte ich das bei Ubuntu vorhandene Paket postfixadmin nicht funktionsfähig bekommen. Es gab immer wieder Fehlermeldungen darüber, dass bestimmte Dateien nicht gefunden werden können.

PHP Fatal error:  Uncaught Error: Failed opening required '/usr/share/postfixadmin/vendor/autoload.php' (include_path='.:/usr/share/php:/usr/share/postfixadmin/public') in 
/usr/share/postfixadmin/common.php:3\nStack trace:\n#0 /usr/share/postfixadmin/public/common.php(3): require_once()\n#1 /usr/share/postfixadmin/public/setup.php(4): 
require _once('...')\n#2 {main}\n  thrown in /usr/share/postfixadmin/common.php on line 3

Ich habe dann nach dieser Beschreibung https://kalilinuxtutorials.com/postfixadmin-setup-ubuntu/ das Paket von https://github.com/postfixadmin/postfixadmin/archive/refs/tags/v4.0.1.tar.gz ausprobiert, aber auch da bin ich nicht wirklich weitergekommen. Es gab beim Setup immer nicht nachvollziehbare Fehler.

Erfolg hatte ich dann aber mit der Version 3.3.11 nach dieser Beschreibung: https://de.linux-terminal.com/?p=3318

Bis zu Ubuntu 25.10 gab es das Modul php_imap. Seit Ubuntu 26.04 bzw. wohl seit PHP8.4 ist das Modul nur noch über PECL installierbar.

Installation von php-imap

Im ersten Schritt wird die Entwicklungumgebung installiert:

apt update
apt install -y libc-client-dev libkrb5-dev php8.5-dev php-pear build-essential

Danach kann man dann damit das Modul erstellen:

pecl install imap

Im Verlauf der Installation wird gefragt, ob SSL Support und Kerberos Support verfügbar sein sollen, ich habe alle Vorgaben einfach akzeptiert.

Dann muss das Modul noch aktiviert werden:

echo "extension=imap.so" | sudo tee /etc/php/8.5/mods-available/imap.ini

und

phpenmod imap

Danach muss dann der Webserver neu gestartet werden

systemctl restart apache2



Datenbanken

Für das Tool wird eine Datenbank mit folgenden Tabellen benötigt (So legt sie Postfixadmin über http://<server>/postfixadmin/setup.php an, bzw. aktualisiert sie beim Aufruf der Seite, das kann dann übrigens etwas dauern).

CREATE TABLE `admin` (
  `username` varchar(255) NOT NULL,
  `password` varchar(255) NOT NULL,
  `created` datetime NOT NULL DEFAULT '2000-01-01 00:00:00',
  `modified` datetime NOT NULL DEFAULT '2000-01-01 00:00:00',
  `active` tinyint(1) NOT NULL DEFAULT 1,
  `superadmin` tinyint(1) NOT NULL DEFAULT 0,
  `phone` varchar(30) CHARACTER SET utf8mb3 COLLATE utf8mb3_uca1400_ai_ci NOT NULL DEFAULT '',
  `email_other` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_uca1400_ai_ci NOT NULL DEFAULT '',
  `token` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_uca1400_ai_ci NOT NULL DEFAULT '',
  `token_validity` datetime NOT NULL DEFAULT '2000-01-01 00:00:00'
) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci COMMENT='Postfix Admin - Virtual Admins';

CREATE TABLE `alias` (
  `address` varchar(255) NOT NULL,
  `goto` text NOT NULL,
  `domain` varchar(255) NOT NULL,
  `created` datetime NOT NULL DEFAULT '2000-01-01 00:00:00',
  `modified` datetime NOT NULL DEFAULT '2000-01-01 00:00:00',
  `active` tinyint(1) NOT NULL DEFAULT 1
) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci COMMENT='Postfix Admin - Virtual Aliases';


CREATE TABLE `alias_domain` (
  `alias_domain` varchar(255) NOT NULL DEFAULT '',
  `target_domain` varchar(255) NOT NULL DEFAULT '',
  `created` datetime NOT NULL DEFAULT '2000-01-01 00:00:00',
  `modified` datetime NOT NULL DEFAULT '2000-01-01 00:00:00',
  `active` tinyint(1) NOT NULL DEFAULT 1
) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci COMMENT='Postfix Admin - Domain Aliases';

CREATE TABLE `config` (
  `id` int(11) NOT NULL,
  `name` varchar(20) CHARACTER SET latin1 COLLATE latin1_general_ci NOT NULL DEFAULT '',
  `value` varchar(20) CHARACTER SET latin1 COLLATE latin1_general_ci NOT NULL DEFAULT ''
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci COMMENT='PostfixAdmin settings';

CREATE TABLE `domain` (
  `domain` varchar(255) NOT NULL,
  `description` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_uca1400_ai_ci NOT NULL,
  `aliases` int(10) NOT NULL DEFAULT 0,
  `mailboxes` int(10) NOT NULL DEFAULT 0,
  `maxquota` bigint(20) NOT NULL DEFAULT 0,
  `quota` bigint(20) NOT NULL DEFAULT 0,
  `transport` varchar(255) NOT NULL,
  `backupmx` tinyint(1) NOT NULL DEFAULT 0,
  `created` datetime NOT NULL DEFAULT '2000-01-01 00:00:00',
  `modified` datetime NOT NULL DEFAULT '2000-01-01 00:00:00',
  `active` tinyint(1) NOT NULL DEFAULT 1,
  `password_expiry` int(11) DEFAULT 0
) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci COMMENT='Postfix Admin - Virtual Domains';

CREATE TABLE `domain_admins` (
  `username` varchar(255) NOT NULL,
  `domain` varchar(255) NOT NULL,
  `created` datetime NOT NULL DEFAULT '2000-01-01 00:00:00',
  `active` tinyint(1) NOT NULL DEFAULT 1,
  `id` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci COMMENT='Postfix Admin - Domain Admins';

CREATE TABLE `fetchmail` (
  `id` int(11) UNSIGNED NOT NULL,
  `mailbox` varchar(255) NOT NULL,
  `src_server` varchar(255) NOT NULL,
  `src_auth` enum('password','kerberos_v5','kerberos','kerberos_v4','gssapi','cram-md5','otp','ntlm','msn','ssh','any') CHARACTER SET utf8mb4 COLLATE utf8mb4_uca1400_ai_ci DEFAULT NULL,
  `src_user` varchar(255) NOT NULL,
  `src_password` varchar(255) NOT NULL,
  `src_folder` varchar(255) NOT NULL,
  `poll_time` int(11) UNSIGNED NOT NULL DEFAULT 10,
  `fetchall` tinyint(1) UNSIGNED NOT NULL DEFAULT 0,
  `keep` tinyint(1) UNSIGNED NOT NULL DEFAULT 0,
  `protocol` enum('POP3','IMAP','POP2','ETRN','AUTO') CHARACTER SET utf8mb4 COLLATE utf8mb4_uca1400_ai_ci DEFAULT NULL,
  `usessl` tinyint(1) UNSIGNED NOT NULL DEFAULT 0,
  `extra_options` text DEFAULT NULL,
  `returned_text` text DEFAULT NULL,
  `mda` varchar(255) NOT NULL,
  `date` timestamp NOT NULL DEFAULT '1999-12-31 23:00:00',
  `sslcertck` tinyint(1) NOT NULL DEFAULT 0,
  `sslcertpath` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_uca1400_ai_ci DEFAULT '',
  `sslfingerprint` varchar(255) DEFAULT '',
  `domain` varchar(255) DEFAULT '',
  `active` tinyint(1) NOT NULL DEFAULT 0,
  `created` timestamp NOT NULL DEFAULT '1999-12-31 23:00:00',
  `modified` timestamp NOT NULL DEFAULT current_timestamp(),
  `src_port` int(11) NOT NULL DEFAULT 0
) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci;

CREATE TABLE `log` (
  `timestamp` datetime NOT NULL DEFAULT '2000-01-01 00:00:00',
  `username` varchar(255) NOT NULL,
  `domain` varchar(255) NOT NULL,
  `action` varchar(255) NOT NULL,
  `data` text NOT NULL,
  `id` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci COMMENT='Postfix Admin - Log';

CREATE TABLE `mailbox` (
  `username` varchar(255) NOT NULL,
  `password` varchar(255) NOT NULL,
  `name` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_uca1400_ai_ci NOT NULL,
  `maildir` varchar(255) NOT NULL,
  `quota` bigint(20) NOT NULL DEFAULT 0,
  `local_part` varchar(255) NOT NULL,
  `domain` varchar(255) NOT NULL,
  `created` datetime NOT NULL DEFAULT '2000-01-01 00:00:00',
  `modified` datetime NOT NULL DEFAULT '2000-01-01 00:00:00',
  `active` tinyint(1) NOT NULL DEFAULT 1,
  `phone` varchar(30) CHARACTER SET utf8mb3 COLLATE utf8mb3_uca1400_ai_ci NOT NULL DEFAULT '',
  `email_other` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_uca1400_ai_ci NOT NULL DEFAULT '',
  `token` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_uca1400_ai_ci NOT NULL DEFAULT '',
  `token_validity` datetime NOT NULL DEFAULT '2000-01-01 00:00:00',
  `password_expiry` datetime NOT NULL DEFAULT '2000-01-01 00:00:00'
) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci COMMENT='Postfix Admin - Virtual Mailboxes';

CREATE TABLE `quota` (
  `username` varchar(255) NOT NULL,
  `path` varchar(100) NOT NULL,
  `current` bigint(20) NOT NULL DEFAULT 0
) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci;

CREATE TABLE `quota2` (
  `username` varchar(100) CHARACTER SET latin1 COLLATE latin1_general_ci NOT NULL,
  `bytes` bigint(20) NOT NULL DEFAULT 0,
  `messages` int(11) NOT NULL DEFAULT 0
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci;

CREATE TABLE `vacation` (
  `email` varchar(255) NOT NULL,
  `subject` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_uca1400_ai_ci NOT NULL,
  `body` text CHARACTER SET utf8mb3 COLLATE utf8mb3_uca1400_ai_ci NOT NULL,
  `cache` text NOT NULL,
  `domain` varchar(255) NOT NULL,
  `created` datetime NOT NULL DEFAULT '2000-01-01 00:00:00',
  `active` tinyint(1) NOT NULL DEFAULT 1,
  `modified` timestamp NOT NULL DEFAULT current_timestamp(),
  `activefrom` timestamp NOT NULL DEFAULT '1999-12-31 23:00:00',
  `activeuntil` timestamp NOT NULL DEFAULT '2038-01-17 23:00:00',
  `interval_time` int(11) NOT NULL DEFAULT 0
) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci COMMENT='Postfix Admin - Virtual Vacation';

CREATE TABLE `vacation_notification` (
  `on_vacation` varchar(255) CHARACTER SET latin1 COLLATE latin1_general_ci NOT NULL,
  `notified` varchar(255) CHARACTER SET latin1 COLLATE latin1_general_ci NOT NULL DEFAULT '',
  `notified_at` timestamp NOT NULL DEFAULT current_timestamp()
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_uca1400_ai_ci COMMENT='Postfix Admin - Virtual Vacation Notifications';

--
-- Indizes der exportierten Tabellen
--

ALTER TABLE `admin`
  ADD PRIMARY KEY (`username`);

ALTER TABLE `alias`
  ADD PRIMARY KEY (`address`),
  ADD KEY `domain` (`domain`);

ALTER TABLE `alias_domain`
  ADD PRIMARY KEY (`alias_domain`),
  ADD KEY `active` (`active`),
  ADD KEY `target_domain` (`target_domain`);

ALTER TABLE `config`
  ADD PRIMARY KEY (`id`),
  ADD UNIQUE KEY `name` (`name`);

ALTER TABLE `domain`
  ADD PRIMARY KEY (`domain`);

ALTER TABLE `domain_admins`
  ADD PRIMARY KEY (`id`),
  ADD KEY `username` (`username`);

ALTER TABLE `fetchmail`
  ADD PRIMARY KEY (`id`);

ALTER TABLE `log`
  ADD PRIMARY KEY (`id`),
  ADD KEY `timestamp` (`timestamp`),
  ADD KEY `domain_timestamp` (`domain`,`timestamp`);

ALTER TABLE `mailbox`
  ADD PRIMARY KEY (`username`),
  ADD KEY `domain` (`domain`);

ALTER TABLE `quota`
  ADD PRIMARY KEY (`username`,`path`);

ALTER TABLE `quota2`
  ADD PRIMARY KEY (`username`);

ALTER TABLE `vacation`
  ADD PRIMARY KEY (`email`),
  ADD KEY `email` (`email`);

ALTER TABLE `vacation_notification`
  ADD PRIMARY KEY (`on_vacation`,`notified`);

--
-- AUTO_INCREMENT für exportierte Tabellen
--

ALTER TABLE `config`
  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=2;

ALTER TABLE `domain_admins`
  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=2;

ALTER TABLE `fetchmail`
  MODIFY `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT;

ALTER TABLE `log`
  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=5;

--
-- Constraints der Tabelle `vacation_notification`
--
ALTER TABLE `vacation_notification`
  ADD CONSTRAINT `vacation_notification_pkey` FOREIGN KEY (`on_vacation`) REFERENCES `vacation` (`email`) ON DELETE CASCADE;
COMMIT;

Nicht alle Tabellen werden in meiner Konfiguration wirklich benutzt, aber es ist einfacher alle anzulegen.

/etc/postfixadmin/dbconfig.local.php

Die Datenbankanbindung wird in der Ubuntu-Version über diese (hier noch minimale) Datei konfiguriert.

<?php
$CONF['configured'] = true;
$CONF['default_language'] = 'de';
$CONF['database_type'] = 'mysqli';
$CONF['database_host'] = 'localhost';
$CONF['database_port'] = '3306';
$CONF['database_user'] = 'postfixadmin';
$CONF['database_password'] = 'password';
$CONF['database_name'] = 'postfixadmin';
 // on submission it will be echoed out to you as a hashed value.
$CONF['setup_password'] = '7caf48fdc954f6037dcc0a6438358db0:9fdf45d7c3dd759258e8af37d09b361218428af5';
$CONF['password_validation'] = array(
    '/.{5}/'                => 'password_too_short 5',      # minimum length 5 characters
    '/([a-zA-Z].*){3}/'     => 'password_no_characters 3',  # must contain at least 3 characters
    '/([0-9].*){2}/'        => 'password_no_digits 2',      # must contain at least 2 digits

);

Das setup_password muss man beim ersten Aufruf angeben und dann den von Postfixadmin erzeugten Hash in der Datei eintragen. In der Grundkonfiguration erwartet postfixadmin bei Passwörtern immer die hier angegebenen Bedingungen, das kann man in der Datei aber anpassen.

Die Datei lässt sich dann auch noch weiter anpassen:

 <?php
$CONF['configured'] = true;
$CONF['default_language'] = 'de';
$CONF['database_type'] = 'mysqli';
$CONF['database_host'] = 'localhost';
$CONF['database_port'] = '3306';
$CONF['database_user'] = 'postfixadmin';
$CONF['database_password'] = 'password';
$CONF['database_name'] = 'postfixadmin';
 // on submission it will be echoed out to you as a hashed value.
$CONF['setup_password'] = '7caf48fdc954f6037dcc0a6438358db0:9fdf45d7c3dd759258e8af37d09b361218428af5';
$CONF['password_validation'] = array(
    '/.{5}/'                => 'password_too_short 5',      # minimum length 5 characters
    '/([a-zA-Z].*){3}/'     => 'password_no_characters 3',  # must contain at least 3 characters
    '/([0-9].*){2}/'        => 'password_no_digits 2',      # must contain at least 2 digits

);

 $CONF['admin_email'] = 'postmaster@meine-maildomain.de';
 $CONF['page_size'] = '20';
 
 $CONF['default_aliases'] = array (
   'abuse' => 'abuse@meine-maildomain.de',
   'hostmaster' => 'hostmaster@meine-maildomain.de',
   'postmaster' => 'postmaster@meine-maildomain.de',
   'webmaster' => 'webmaster@meine-maildomain.de'
 );
 
 $CONF['quota'] = 'YES';
 $CONF['footer_text'] = 'Zurück zu Debacher.de';
 $CONF['footer_link'] = 'http://debacher.de';
  
 // Welcome Message
 // This message is send to every newly created mailbox.
 // Change the text between EOM.
 $CONF['welcome_text'] = <<<EOM
 Herzlich Willkommen, 
 
 zum Debacher E-Mail Postfach.
 
 Bei Fragen oder Problemen bitte
 Uwe Debacher ansprechen
 EOM;
 
 $CONF['show_undeliverable_exceptions']=array("meine-maildomain.de");
 $CONF['used_quotas'] = 'YES';
 $CONF['maxquota'] = '2048';
 $CONF['domain_quota_default'] = '0';

Dovecot

Eigentlich ist Dovecot nur ein Server für POP3 und Imap. Inzwischen kann dieses Programm deutlich mehr. Die Zugriffe auf die Datenbank mit den virtuellen Usern erfolgt in der Regel über Dovecot. Das geht bis zur Nachrichten-Zustellung in die Postfächer.

apt install dovecot-core dovecot-imapd dovecot-lmtpd dovecot-mysql dovecot-sieve dovecot-managesieved dovecot-pop3d dovecot-antispam
service dovecot stop

Die Versionsnummer von dovecot kann man abfragen mittels

dovecot --version

Tja da steht jetzt 2.4.2 und ab der Version 2.4 haben die Dovecot-Programmierer eine Reihe von Konfigurationseinstellungen geändert. Aus

ssl_cert = </etc/letsencrypt/live/meine-maildomain.de/fullchain.pem

wird jetzt

ssl_server_cert_file = </etc/letsencrypt/live/meine-maildomain.de/fullchain.pem

Und solche Änderungen tauchen jetzt viel auf. Das hält wieder einmal schön auf. Ich hangle mich anhand von https://linuxize.com/post/install-and-configure-postfix-and-dovecot/ durch die Änderungen.

/etc/dovecot/local.conf

Ich habe die Konfiguration in einer eigenen Datei /etc/dovecot/local.conf gesammelt: (hervorgehoben die Zeilen mit individueller Konfiguration)

 ##
 ## Protokollierung
 ##
 
 #mail_debug = yes
 #auth_debug = yes
 auth_debug_passwords = yes
 #auth_verbose = yes
 auth_verbose_passwords = sha1
 #verbose_proctitle = yes
 
 ##
 ## Grundeinstellungen
 ##
 
 first_valid_uid = 303
 mail_access_groups = postfix
 mail_privileged_group = vmail
 mail_uid = vmail
 mail_gid = vmail
 
 protocols = imap pop3 lmtp sieve
 
 # die folgenden Einstellungen sind nicht optimal, da die home-Verzeichnisse so nicht automatisch angelegt werden
 # geschickter wäre (dann auch dovecot-mysql.conf anpassen):
 #mail_home = /var/vmail/%d/%n
 #mail_location = maildir:/var/vmail/%d/%n/mail  oder  mail_location = maildir:~/Maildir
 
 mail_location = maildir:/var/vmail/%d/%n
 mail_home = /var/vmail/%d/%n/home
 
 mail_plugins = $mail_plugins quota
 
 disable_plaintext_auth = no
 auth_mechanisms = plain login
 
 ##
 ##TLS Konfiguration
 ##
 
 ssl = yes
 ssl_cert = </etc/letsencrypt/live/meine-maildomain.de/fullchain.pem
 ssl_key = </etc/letsencrypt/live/meine-maildomain.de/privkey.pem
 #ssl_cert = </etc/ssl/certs/ssl-cert-snakeoil.pem
 #ssl_key = </etc/ssl/private/ssl-cert-snakeoil.key
 
 # die beiden folgenden Parameter gibt es erst ab dc 2.3 (Ubuntu 20.04)
 ssl_dh = </etc/dovecot/dh4096.pem
 ssl_min_protocol = TLSv1.2
 #ssl_cipher_list = 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
 #ssl_prefer_server_ciphers = no
 
 ##
 ##Services
 ##
 
 service auth {
  unix_listener /var/spool/postfix/private/auth {
   group = postfix
   mode = 0660
   user = postfix
  }
 }
 
 service lmtp {
  unix_listener lmtp {
 #mode = 0666
  }
  unix_listener /var/spool/postfix/private/dovecot-lmtp {
  #mode = 0666
  user = postfix
  group = postfix
  }
 }
 
 service managesieve-login {
  inet_listener sieve {
  port = 4190 
  }
 }
 
 service quota-warning {
  executable = script /usr/local/bin/quota-warning.sh
  user = vmail
  unix_listener quota-warning {
        group = vmail
        mode = 0660
        user = vmail
  }
 }
 
 service quota-status {
  executable = quota-status -p postfix
  inet_listener {
 #    address = 127.0.0.1
    port = 12340
  }
  client_limit = 1
 }
 # Hinweis in main.cf: smtpd_recipient_restrictions = check_policy_service inet:127.0.0.1:12340, ..
 
 service dict {
   unix_listener dict {
       mode = 0600
       user = vmail
   }
 }
 
 dict {
   sqluserquota = mysql:/etc/dovecot/dovecot-dict-sql-user.conf
 }
 
 service imap {
  executable = imap imap-postlogin
 }
 
 service imap-postlogin {
  executable = script-login /usr/local/bin/postlogin.sh
  user = vmail
  unix_listener imap-postlogin {
  }
 }
 
 
 ##
 ## Protokolle
 ##
 
 protocol pop3 {
  pop3_uidl_format = %08Xu%08Xv
  mail_max_userip_connections = 5
 }
 
 protocol imap {
  mail_max_userip_connections = 8
  mail_plugins = $mail_plugins imap_quota imap_sieve 
 }
 
 protocol lmtp {
  mail_plugins = $mail_plugins sieve 
 }
 
 ##
 ## Client Authentifizierung
 ##
 
 passdb {
  args = /etc/dovecot/dovecot-mysql.conf
  driver = sql
 }
 
 userdb {
  args = /etc/dovecot/dovecot-mysql.conf
  driver = sql
 }
 ## Hinweis: Datei 10-auth.conf editieren und !include auth-system.conf.ext deaktivieren
 
 ##
 ## Postfächer
 ##
 
 namespace inbox {
  inbox = yes
  mailbox Drafts {
    auto = subscribe
    special_use = \Drafts
  }
  mailbox Junk {
    auto = subscribe
    autoexpunge = 26w
    special_use = \Junk
  }  
  mailbox "Junk-E-Mail" {
    special_use = \Junk
    autoexpunge = 26w
  }
  mailbox "Spam" {
    special_use = \Junk
    autoexpunge = 26w
  }
  mailbox Sent {
    auto = subscribe
    special_use = \Sent
  }
  mailbox "Sent Messages" {
    special_use = \Sent
  }
  mailbox Trash {
    auto = subscribe
    autoexpunge = 26w
    special_use = \Trash
  }
  mailbox "Gelöschte Elemente" {
    autoexpunge = 26w
    special_use = \Trash
  }
  mailbox "Papierkorb" {
    autoexpunge = 26w
    special_use = \Trash
  }
 }
 
 ##
 ##Plugins
 ##
 
 plugin {
  sieve = ~/.dovecot.sieve
  sieve_dir = ~/sieve
 
  sieve_plugins = sieve_imapsieve sieve_extprograms
  sieve_before = /var/vmail/sieve/global/spam-global.sieve
 
  #quota_rule = *:storage=1G
  #quota_rule2 = Trash:storage=+100M
 
  quota_grace = 10%%
 
  quota_warning = storage=66%% quota-warning 66 %u 
  quota_warning2 = storage=80%% quota-warning 80 %u
  quota_warning3 = storage=95%% quota-warning 95 %u
  #  quota = dict:User Quota::noenforcing:proxy::sqluserquota
  quota = dict:User Quota::proxy::sqluserquota
 
  quota_status_success = DUNNO
  quota_status_nouser = DUNNO
  quota_status_overquota = "552 5.2.2 Mailbox is over quota / Mailbox ist voll"
 
  ##Spam lernen
 
  # From elsewhere to Junk folder
  imapsieve_mailbox1_name = Junk
  imapsieve_mailbox1_causes = COPY
  imapsieve_mailbox1_before = file:/var/vmail/sieve/global/learn-spam.sieve
 
  # From Junk folder to elsewhere
  imapsieve_mailbox2_name = *
  imapsieve_mailbox2_from = Junk
  imapsieve_mailbox2_causes = COPY
  imapsieve_mailbox2_before = file:/var/vmail/sieve/global/learn-ham.sieve
 
  sieve_pipe_bin_dir = /usr/bin
 
  sieve_global_extensions = +vnd.dovecot.pipe
 }

Mit der auskommentierten Zeile

#  quota = dict:User Quota::noenforcing:proxy::sqluserquota

würde bei einem Postfach, das die Quotas überschritten hat, noch Mail angenommen werden (nonenforcing). Die aktive Zeile bewirkt, dass die Mail dann abgelehnt wird. Dann sollte man aber auch, wie angegeben, die main.cf von Postfix so erweitern, dass er von Dovecot über die Überfüllung des Postfaches informiert werden kann und dann die Annahme weiter Mails selber ablehnt.

Der Teil mit dem Lernen von SPAM und HAM hat mir etwas Probleme bereitet, mit fehlte anfangs das imap_sieve mail_plungin. Leider gibt es keine Fehlermeldungen in den Logdateien. Hilfreich war für mich https://doc.dovecot.org/configuration_manual/howto/antispam_with_sieve/. Wenn es funktioniert kann man das an einem erfolgreichen Aufruf von rspamc mittels

grep "pipe action" /var/log/mail.log

erkennen.


Zusätzlich habe ich die Authentisierung per pam deaktiviert, indem ich im Unterordner conf.d die Datei 10-auth.conf editiert und dort am Ende der Datei die einzig aktive Include-Zeile

!include auth-system.conf.ext

/etc/dovecot/dovecot-mysql.conf

Die in der Konfiguration erwähnte /etc/dovecot/dovecot-mysql.conf

 # Database driver: mysql, pgsql
 driver = mysql
 
 # Currently supported schemes include PLAIN, PLAIN-MD5, DIGEST-MD5, and CRYPT.
 default_pass_scheme = CRYPT
 
 # Database options
 #db_unix_socket = /var/lib/mysql/mysql.sock
 
 connect = host=/var/run/mysqld/mysqld.sock dbname=postfix user=postfix password=assword
 
 password_query = SELECT password FROM mailbox WHERE username = '%u' AND active = '1'
 user_query = SELECT \
    concat('/var/vmail/',maildir,'home/') as home, \    
    concat('maildir:/var/vmail/',maildir) as mail, \ 
    303 AS uid, \ 
    303 AS gid, \ 
    CONCAT('*:bytes=', IF(mailbox.quota = 0, domain.maxquota*1024000, mailbox.quota)) as quota_rule \
    FROM mailbox, domain WHERE username = '%u' AND mailbox.active = '1' AND domain.domain = '%d' AND domain.active = '1'
 
 iterate_query = SELECT username as user FROM mailbox WHERE active ='1' ORDER BY domain, username

/usr/local/bin/quota-warning.sh

In der Konfiguration taucht das Plugins quota_warning auf. Dazu gehört das folgende Script:

 #!/bin/sh
 PERCENT=$1
 USER=$2
 ADMIN="uwe@maine-maildomain.de"
 FROM="postmaster@maine-maildomain.de"
 
 msg="From: $FROM
 To: $USER
 Bcc: $ADMIN
 Subject: Quota-Warnung $PERCENT%  
 
 Lieber Nutzer,
      
 das Postfach $USER ist derzeit zu 
 
 $PERCENT% gefuellt.
 
 Bitte einige Mails loeschen und dann
 den Papierkorb leeren!
        
 Herzlichen Dank
 Das Mail-System."
 
 echo "$msg" | /usr/sbin/sendmail -f $FROM "$USER $ADMIN"
 
 exit 0

Die Auslastung der Quotas kann man sich im PostfixAdmin anschauen oder an der Konsole mittels

doveadm quota get -A

oder statt für alle auch für einen einzelnen Nutzer

doveadm quota get -u user@meine-maildomain.de

Man kann auch die Belegung neu berechnen lassen mittels

doveadm quota recalc -A

oder statt für alle auch für einen einzelnen Nutzer

doveadm quota recalc -u user@meine-maildomain.de

/usr/local/bin/postlogin.sh

Das folgende Script dient dazu, die Verzeichnisse home der einzelnen Mailbenutzer anzulegen. Da das mit dem welcome Plugin nicht funktioniert hat, bin ich auf das Postlogin von Dovecot ausgewichen. Dazu gehört dieses Script:

 #!/bin/sh
 local=${USER%%@*}
 domain=${USER##*@}
 
 if [ ! -d "/var/vmail/$domain/$local/home" ]; then
  mkdir  /var/vmail/$domain/$local/home
 fi 
 exec "$@"

Vor dem Dovecot-Neustart dann noch die Diffie-Hellman Parameter für Dovecot generieren

openssl dhparam -out /etc/dovecot/dh4096.pem 4096 

das dauert leider etwas.

/etc/dovecot/dovecot-dict-sql-user.conf

Diese Datei dient Dovecot dazu die jeweils aktuellen Quota-Werte an der richtigen Stelle in der MySQL-Datenbank zu speichern. Die erste Map für die Anzahl der Bytes, die zweite Map für die Anzahl der Nachrichten siehe https://wiki.dovecot.org/Quota/Dict .

 connect = host=/var/run/mysqld/mysqld.sock dbname=postfix user=postfix password=assword
  
 map {
    pattern = priv/quota/storage
    table = quota2
    username_field = username
    value_field = bytes
 }
  
 map {
    pattern = priv/quota/messages
    table = quota2
    username_field = username
    value_field = messages
}

Postfix

apt install postfix postfix-mysql postfix-policyd-spf-python alpine
service postfix stop

Für die Konfiguration ist es manchmal wichtig zu wissen, welche Versionsnummer das eigene Postfix-Programm hat. Diese Information liefert:

postconf -d mail_version

Die zentrale Konfigurationsdatei ist die master.cf. Hier werden die benötigten Dienste (z.B. smtp, uucp, ...) gestartet und zum Teil auch konfiguriert. Die eigentliche Konfiguration für den smtp Dienst befindet sich dann in der Datei main.cf. Weitere Dienste mit dem gleichen Programm, wie z.B. smtps oder submission übernehmen die dort gemachten Einstellungen, es sei den die Einstellung wird in einer Zeile, die mit " -o" beginnt überschrieben. Etwas ungewöhnlich ist eine Zeile wie -o smtpd_client_restrictions=$mua_client_restrictions, die so nicht fehlerfrei aktivierbar ist. Der rechte Teil ist nämlich quasi eine Variable, die in der main.cf mit Inhalt gefüllt werden muss.

/etc/postfix/master.cf

 #
 # Postfix master process configuration file.  For details on the format
 # of the file, see the master(5) manual page (command: "man 5 master" or
 # on-line: http://www.postfix.org/master.5.html).
 #
 # Do not forget to execute "postfix reload" after editing this file.
 #
 # ==========================================================================
 # service type  private unpriv  chroot  wakeup  maxproc command + args
 #               (yes)   (yes)   (no)    (never) (100)
 # ==========================================================================
 smtp      inet  n       -       y       -       -       smtpd
 #smtp      inet  n       -       y       -       1       postscreen
 #smtpd     pass  -       -       y       -       -       smtpd
 #dnsblog   unix  -       -       y       -       0       dnsblog
 #tlsproxy  unix  -       -       y       -       0       tlsproxy
 submission inet n       -       y       -       -       smtpd
   -o syslog_name=postfix/submission
   -o smtpd_tls_security_level=encrypt
   -o smtpd_sasl_auth_enable=yes
 #  -o smtpd_tls_auth_only=yes
 #  -o smtpd_reject_unlisted_recipient=no
   -o smtpd_client_restrictions=permit_sasl_authenticated,reject
 #  -o smtpd_helo_restrictions=$mua_helo_restrictions
   -o smtpd_sender_restrictions=reject_non_fqdn_sender,permit_sasl_authenticated,reject
 #  -o smtpd_recipient_restrictions=
   -o smtpd_relay_restrictions=reject_non_fqdn_recipient,reject_unknown_recipient_domain,permit_sasl_authenticated,reject
 #  -o milter_macro_daemon_name=ORIGINATING
 smtps     inet  n       -       y       -       -       smtpd
   -o syslog_name=postfix/smtps
   -o smtpd_tls_wrappermode=yes
   -o smtpd_sasl_auth_enable=yes
 #  -o smtpd_reject_unlisted_recipient=no
   -o smtpd_client_restrictions=permit_sasl_authenticated,reject
 #  -o smtpd_helo_restrictions=$mua_helo_restrictions
   -o smtpd_sender_restrictions=reject_non_fqdn_sender,permit_sasl_authenticated,reject
 #  -o smtpd_recipient_restrictions=
   -o smtpd_relay_restrictions=reject_non_fqdn_recipient,reject_unknown_recipient_domain,permit_sasl_authenticated,reject
 #  -o milter_macro_daemon_name=ORIGINATING
 #628       inet  n       -       y       -       -       qmqpd
 pickup    unix  n       -       y       60      1       pickup
 cleanup   unix  n       -       y       -       0       cleanup
 qmgr      unix  n       -       n       300     1       qmgr
 #qmgr     unix  n       -       n       300     1       oqmgr
 tlsmgr    unix  -       -       y       1000?   1       tlsmgr
 rewrite   unix  -       -       y       -       -       trivial-rewrite
 bounce    unix  -       -       y       -       0       bounce
 defer     unix  -       -       y       -       0       bounce
 trace     unix  -       -       y       -       0       bounce
 verify    unix  -       -       y       -       1       verify
 flush     unix  n       -       y       1000?   0       flush
 proxymap  unix  -       -       n       -       -       proxymap
 proxywrite unix -       -       n       -       1       proxymap
 smtp      unix  -       -       y       -       -       smtp
 relay     unix  -       -       y       -       -       smtp
        -o syslog_name=postfix/$service_name
 #       -o smtp_helo_timeout=5 -o smtp_connect_timeout=5
 showq     unix  n       -       y       -       -       showq
 error     unix  -       -       y       -       -       error
 retry     unix  -       -       y       -       -       error
 discard   unix  -       -       y       -       -       discard
 local     unix  -       n       n       -       -       local
 virtual   unix  -       n       n       -       -       virtual
 lmtp      unix  -       -       y       -       -       lmtp
 anvil     unix  -       -       y       -       1       anvil
 scache    unix  -       -       y       -       1       scache
 postlog   unix-dgram n  -       n       -       1       postlogd
 #
 # ====================================================================
 # Interfaces to non-Postfix software. Be sure to examine the manual
 # pages of the non-Postfix software to find out what options it wants.
 #
 # Many of the following services use the Postfix pipe(8) delivery
 # agent.  See the pipe(8) man page for information about ${recipient}
 # and other message envelope options.
 # ====================================================================
 #
 # maildrop. See the Postfix MAILDROP_README file for details.
 # Also specify in main.cf: maildrop_destination_recipient_limit=1
 #
 maildrop  unix  -       n       n       -       -       pipe
  flags=DRhu user=vmail argv=/usr/bin/maildrop -d ${recipient}
 #
 # ====================================================================
 #
 # Recent Cyrus versions can use the existing "lmtp" master.cf entry.
 #
 # Specify in cyrus.conf:
 #   lmtp    cmd="lmtpd -a" listen="localhost:lmtp" proto=tcp4
 #
 # Specify in main.cf one or more of the following:
 #  mailbox_transport = lmtp:inet:localhost
 #  virtual_transport = lmtp:inet:localhost
 #
 # ====================================================================
 #
 # Cyrus 2.1.5 (Amos Gouaux)
 # Also specify in main.cf: cyrus_destination_recipient_limit=1
 #
 #cyrus     unix  -       n       n       -       -       pipe
 #  user=cyrus argv=/cyrus/bin/deliver -e -r ${sender} -m ${extension} ${user}
 #
 # ====================================================================
 # Old example of delivery via Cyrus.
 #
 #old-cyrus unix  -       n       n       -       -       pipe
 #  flags=R user=cyrus argv=/cyrus/bin/deliver -e -m ${extension} ${user}
 #
 # ====================================================================
 #
 # See the Postfix UUCP_README file for configuration details.
 #
 #uucp      unix  -       n       n       -       -       pipe
 # flags=Fqhu user=uucp argv=uux -r -n -z -a$sender - $nexthop!rmail ($recipient)
 #
 # Other external delivery methods.
 #
 ifmail    unix  -       n       n       -       -       pipe
  flags=F user=ftn argv=/usr/lib/ifmail/ifmail -r $nexthop ($recipient)
 bsmtp     unix  -       n       n       -       -       pipe
  flags=Fq. user=bsmtp argv=/usr/lib/bsmtp/bsmtp -t$nexthop -f$sender $recipient
 scalemail-backend unix	-	n	n	-	2	pipe
  flags=R user=scalemail argv=/usr/lib/scalemail/bin/scalemail-store ${nexthop} ${user} ${extension}
 #mailman   unix  -       n       n       -       -       pipe
 # flags=FR user=list argv=/usr/lib/mailman/bin/postfix-to-mailman.py
 # ${nexthop} ${user}
 
 # ==========================================================================
 # service type  private unpriv  chroot  wakeup  maxproc command + args
 #               (yes)   (yes)   (yes)   (never) (100)
 # ==========================================================================
 # Added using postfix-add-policy script:
 policy-spf unix    -       n       n       -       0     spawn
      user=nobody argv=/usr/bin/policyd-spf

/etc/postfix/main.cf

(die einzelnen Parameter sind in http://www.postfix.org/postconf.5.html dokumentiert)

 # See /usr/share/postfix/main.cf.dist for a commented, more complete version
 
 ##
 ##Netzwerk und Namen
 ##
 myorigin = /etc/mailname
 append_dot_mydomain = no
 masquerade_domains = meine-maildomain.de
 myhostname = mail.meine-maildomain.de
 mydestination = $myhostname, meine-maildomain.de, localhost, hetzner.meine-maildomain.de
 #mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128
 
 smtpd_banner = $myhostname ESMTP $mail_name (Ubuntu)
 inet_interfaces = all
 inet_protocols = ipv4
 
 
 ##
 # Einstellungen
 ##
 compatibility_level = 3.6
 smtputf8_enable = no 
 biff = no
 delay_warning_time = 4h
 readme_directory = no
 unknown_address_reject_code = 552
 unknown_client_reject_code = 551
 unknown_hostname_reject_code = 550
 #maps_rbl_reject_code = 451
 message_strip_characters = \0
 defer_transports = 
 mailbox_command = 
 mailbox_transport = 
 relayhost = 
 mailbox_size_limit = 0
 recipient_delimiter = +
 
 
 ##
 # TLS parameters
 ##
 
 ## ausgehende Verbindungen
 smtp_tls_security_level=may
 smtp_tls_mandatory_protocols=!SSLv2, !SSLv3
 #smtp_tls_cert_file=/etc/ssl/certs/ssl-cert-snakeoil.pem
 #smtp_tls_key_file=/etc/ssl/private/ssl-cert-snakeoil.key
 smtp_tls_key_file = /etc/letsencrypt/live/meine-maildomain.de/privkey.pem
 smtp_tls_cert_file = /etc/letsencrypt/live/meine-maildomain.de/fullchain.pem
 smtp_tls_loglevel = 1
 smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache
 
 smtp_tls_note_starttls_offer = yes
 
 ## eingehende Verbindungen
 smtpd_tls_security_level=may
 smtpd_tls_mandatory_protocols=!SSLv2, !SSLv3
 #smtpd_tls_cert_file=/etc/ssl/certs/ssl-cert-snakeoil.pem
 #smtpd_tls_key_file=/etc/ssl/private/ssl-cert-snakeoil.key
 smtpd_tls_key_file=/etc/letsencrypt/live/meine-maildomain.de/privkey.pem
 smtpd_tls_cert_file=/etc/letsencrypt/live/meine-maildomain.de/fullchain.pem
 smtpd_tls_loglevel = 1
 smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache
 
 smtpd_tls_received_header = yes
 smtpd_tls_session_cache_timeout = 3600s
 smtpd_tls_dh1024_param_file = /etc/postfix/dh2048.pem
 smtpd_delay_reject = yes
 smtpd_helo_required = yes
 
 ## SASL für eingehende Verbindungen
 broken_sasl_auth_clients = yes
 smtpd_sasl_local_domain = 
 smtpd_sasl_path = private/auth
 smtpd_sasl_security_options = noanonymous
 smtpd_sasl_type = dovecot
 # das kann/sollte hier auf no gesetzt werden, wenn smtps/submission aktiviert sind
 smtpd_sasl_auth_enable = yes
 smtp_sasl_auth_enable = no
 smtp_sasl_security_options = 
 smtp_sasl_password_maps = 
 
 
 ##
 # Maps und Tabellen
 ##
 alias_maps = hash:/etc/aliases
 alias_database = hash:/etc/aliases
 
 relay_domains = $mydestination, hash:/etc/postfix/relay
 #virtual_alias_domains = hash:/etc/postfix/virtual
 virtual_mailbox_base = /var/vmail/
 
 virtual_alias_maps = 
   proxy:mysql:/etc/postfix/mysql_virtual_alias_maps.cf
   proxy:mysql:/etc/postfix/mysql_virtual_alias_domain_maps.cf
   proxy:mysql:/etc/postfix/mysql_virtual_alias_domain_catchall_maps.cf
 
 virtual_mailbox_domains =
   proxy:mysql:/etc/postfix/mysql_virtual_domains_maps.cf
 
 virtual_mailbox_maps =
   proxy:mysql:/etc/postfix/mysql_virtual_mailbox_maps.cf
   proxy:mysql:/etc/postfix/mysql_virtual_alias_domain_mailbox_maps.cf
 
 virtual_transport = lmtp:unix:private/dovecot-lmtp
 virtual_mailbox_limit = 0
 virtual_minimum_uid = 303
 virtual_uid_maps = static:303
 virtual_gid_maps = static:303
  
 
 ##
 # Restrictions
 ##
 smtpd_client_restrictions = 
   permit_sasl_authenticated
   permit_mynetworks
   reject_rbl_client cbl.abuseat.org
   reject_rbl_client ix.dnsbl.manitu.net
   reject_rbl_client bl.spamcop.net
   reject_rbl_client dul.dnsbl.sorbs.net
   reject_unknown_client
 
 smtpd_helo_restrictions = 
   permit_mynetworks
   reject_invalid_helo_hostname
 
 smtpd_sender_restrictions = 
   hash:/etc/postfix/access
   reject_unknown_sender_domain
 
 smtpd_recipient_restrictions = 
   check_policy_service inet:127.0.0.1:12340
   permit_sasl_authenticated
   permit_mynetworks
   reject_unauth_destination
 
 smtpd_relay_restrictions = 
   permit_mynetworks
   permit_sasl_authenticated
   reject_unauth_destination
 
 # Block clients that speak too early.
 smtpd_data_restrictions = reject_unauth_pipelining

 # Neu 3.1.2023 wegen https://www.postfix.org/smtp-smuggling.html
 smtpd_discard_ehlo_keywords = chunking, silent-discard

 ##
 # Spamfilter und DKIM-Signaturen via Rspamd
 ##
 
 #content_filter = smtp-amavis:[localhost]:10024
 
 smtpd_milters = inet:localhost:11332
 non_smtpd_milters = inet:localhost:11332
 milter_protocol = 6
 milter_mail_macros =  i {mail_addr} {client_addr} {client_name} {auth_authen}
 milter_default_action = accept
 
 
 ##
 # Limits 
 ##
 mailbox_size_limit = 0
 message_size_limit = 0
 strict_8bitmime = no
 strict_rfc821_envelopes = yes
 
 smtpd_client_message_rate_limit = 50
 smtpd_client_connection_rate_limit = 10
 smtpd_client_recipient_rate_limit = 50
 smtpd_client_connection_count_limit = 25
 bounce_queue_lifetime = 3d
 maximal_queue_lifetime = 3d
 
 smtpd_recipient_limit = 50

Auch für Postfix Diffie-Hellman-Parameter generieren

Die DH-Parameter für Postfix sind hier nur 2048 Bit stark, um zu älteren Mailservern kompatibel zu bleiben.

openssl dhparam -out /etc/postfix/dh2048.pem 2048


Postfix und MySQL

Damit Postfix MySQL-Tabellen nutzen kann muss man folgende Dateien einrichten:

/etc/postfix/mysql_virtual_alias_maps.cf
/etc/postfix/mysql_virtual_domains_maps.cf
/etc/postfix/mysql_virtual_mailbox_limit_maps.cf  darum kümmert sich Dovecot
/etc/postfix/mysql_virtual_mailbox_maps.cf
/etc/postfix/mysql_virtual_alias_domain_maps.cf
/etc/postfix/mysql_virtual_alias_domain_catchall_maps.cf
/etc/postfix/mysql_virtual_alias_domain_mailbox_maps.cf

Diese mysql-Dateien im Verzeichnis /etc/postfix muss man anpassen an die eigenen Datenbankeinstellungen. Muster dazu finden sich in der Datei /usr/share/doc/postfixadmin/DOCUMENTS/POSTFIX_CONF.txt.gz. Im Prinzip wird Postfix hier mitgeteilt, wie die Datenbanktabellen abgefragt werden. Benutzername und Passwort müssen hier an die eigenen Einstellungen angepasst werden.

mysql_virtual_alias_maps.cf

 user = postfix
 password = password
 hosts = localhost
 dbname = postfix
 query = SELECT goto FROM alias WHERE address='%s' AND active = '1'

mysql_virtual_domains_maps.cf

 user = postfix
 password = password
 hosts = localhost
 dbname = postfix
 query = SELECT domain FROM domain WHERE domain='%s' AND active = '1'

mysql_virtual_mailbox_limit_maps.cf

user = postfix
password = password
hosts = localhost
dbname = postfix
query = SELECT quota FROM mailbox WHERE username='%s' AND active = '1'

mysql_virtual_mailbox_maps.cf

 user = postfix
 password = password
 hosts = localhost
 dbname = postfix
 query = SELECT maildir FROM mailbox WHERE username='%s' AND active = '1'

mysql_virtual_alias_domain_maps.cf

 user = postfix
 password = password
 hosts = localhost
 dbname = postfix
 query = SELECT goto FROM alias,alias_domain WHERE alias_domain.alias_domain = '%d' and alias.address = CONCAT('%u', '@', alias_domain.target_domain) AND alias.active = 1 AND alias_domain.active='1'

mysql_virtual_alias_domain_catchall_maps.cf

 # handles catch-all settings of target-domain
 user = postfix
 password = password
 hosts = localhost
 dbname = postfix
 query  = SELECT goto FROM alias,alias_domain WHERE alias_domain.alias_domain = '%d' and alias.address = CONCAT('@', alias_domain.target_domain) AND alias.active = 1 AND alias_domain.active='1'

mysql_virtual_alias_domain_mailbox_maps.cf

 user = postfix
 password = password
 hosts = localhost
 dbname = postfix
 query = SELECT maildir FROM mailbox,alias_domain WHERE alias_domain.alias_domain = '%d' and mailbox.username = CONCAT('%u', '@', alias_domain.target_domain) AND mailbox.active = 1 AND alias_domain.active='1'

Firewall anpassen

 ufw allow Postfix
 ufw allow "Postfix SMTPS"
 ufw allow "Postfix Submission"
 ufw allow "Dovecot IMAP"
 ufw allow "Dovecot Secure IMAP"
 ufw allow "Dovecot POP3"
 ufw allow "Dovecot Secure POP3"

Vor dem Neustart der Dienste müssen die Datenbanken erzeugt werden:

postmap /etc/postfix/access
newaliases


Konfiguration der Clients

Thunderbird als Client-Programm lässt sich problemlos so konfigurieren, dass er mit den beiden verschlüsselten Protokollen arbeiten kann. Beispieleinstellungen ergeben sich aus dem folgenden Bildern.

Zum Einrichten es neuen Postfaches muss man im Idealfall nur die Mailadresse und das Passwort angeben

Bildschirmfoto von 2021-11-19 10-28-24.png

Thunderbird versucht dann sich vom Mailserver die passenden Einstellungen zu holen:

Bildschirmfoto von 2021-11-19 10-28-43.png

Voreingestellt ist hier ein Postausgang über Port 25.

Bildschirmfoto von 2021-11-19 11-12-31.png

Port 25 ist eigentlich für unverschlüsselte Verbindungen zuständig, über STARTTLS handeln die beteiligten Systeme aber eine Verschlüsselung aus, so dass auch hier verschlüsselt übertragen wird. Es gibt aber auch zwei weitere Ports, über die in der Regel nur verschlüsselte Verbindungen angenommen werden.

Wen man die freie Wahl hat, dann sollte man hier SMTPS über Port 465 auswählen.

Konfiguration von Thunderbird für die Nutzung von SMTPS

Konfiguration von Thunderbird für die Nutzung von Submission

Postfix und der Kampf gegen Spam

Heutzutage ist deutlich mehr als 90% aller verschickten Mails unerwünschter Spam. Die zentrale Stelle im Kampf gegen den Spam ist natürlich der Mailserver. Postfix hat eine Reihe von Mechanismen eingebaut, die versuchen Spam zu vermeiden:

  • RBL, ein System bei dem bestimmte Server gefragt werden, ob ihnen der abliefernde Server als Spamlieferant bekannt ist
  • Regeln wie invalid_hostname, hier überprüft Postfix, ob sich der Name, der zu der abliefernden IP-Adresse gehört zur gleichen IP wieder auflösen lässt
  • Access-Listen über die z.B. Mailadressen oder ganze Domains blockiert werden können

Wirklich Viren und Spam erkennen kann Postfix aber nicht. Dazu bedarf es eines externen Hilfsprogrammes, wie AmaVis oder rspamd. Diese Programme können allgemein nach zwei unterschiedlichen Konzepten eingebunden werden.

  • als Mail-Proxy
  • Über die Milter-Schnittstelle

AmaVis wird in der Regel als Proxy eingebunden. Dabei nimmt Postfix auf dem normalen Port z.B. 25 eine Mail an und gibt sie zur Untersuchung an AmaVis weiter. Wenn AmaVis mit der Mail fertig ist, dann gibt er die an einen zweiten Postfix-Prozess weiter, der z.B. auf Port 10025 lauscht. Dieses Verfahren kann man gut in den Header-Zeilen der Mail nachvollziehen, hier taucht der eigene Server nämlich doppelt auf. Nachteil bei dem Proxy-System ist, dass AmaVis die Mail erst sehen kann, wenn sie angenommen wurde. Auch, wenn sie als Spam klassifiziert wurde muss sie zugestellt werden. Konfiguriert wird der zweite Postfix-Daemon über die letzten Zeilen in der /etc/postfix/master.cf

127.0.0.1:10025 inet n    -       -       -       -     smtpd
    -o content_filter=
    -o smtpd_delay_reject=no
    ...

und der Zugriff auf AmaVis erolgt über eine Zeile in der main.cf

content_filter = smtp-amavis:[localhost]:10024

Über die Milter-Schnittstelle kann sich ein externes Programm wie rspamd in den Postfix-Prozess einklinken. Es wird also kein zweiter Postfix-Prozess benötigt. Außerdem ist das externe Programm so in der Lage eine Mail als Spam zu klassifizieren, ohne das sie letztendlich angenommen wurde. Postfix kann dann den Prozess abbrechen und dem sendenden Mailserver dabei eine entsprechende Fehlermeldung zukommen lassen. Damit sollten als Spam gekennzeichnete Mail überhaupt nicht mehr beim Empfänger auftauchen.

Für Amavis gibt es auch ein Milter-Programm, das ist aber schon einige Zeit nicht mehr aktualisiert worden, vor allem in den Ubuntu-Versionen nicht.

/var/vmail/sieve/global/spam-global.sieve

 require "fileinto";
 
 if header :contains "X-Spam-Flag" "YES" {
    fileinto "Junk";
 }
 
 if header :is "X-Spam" "Yes" {
    fileinto "Junk";
 }

/var/vmail/sieve/global/learn-ham.sieve

 require ["vnd.dovecot.pipe", "copy", "imapsieve", "environment", "variables"];
 
 if environment :matches "imap.mailbox" "*" {
    set "mailbox" "${1}";
 }
 
 if string "${mailbox}" "Trash" {
    stop;
 }
 
 pipe :copy "rspamc" ["learn_ham"];

/var/vmail/sieve/global/learn-spam.sieve

 require ["vnd.dovecot.pipe", "copy", "imapsieve"];
 pipe :copy "rspamc" ["learn_spam"];