Nachrichtenverwalter

Meine Freizeitgestaltung unterliegt einer klaren Pendelbewegung, und nach modernen und sogar populären Kommunikationstechnologien wende ich mich wieder einem sehr alten Medium zu, denn: Chat is indeed faster. But email creates evidence. Der Versand von E-Mails an eine überschaubare Anzahl von Empfängerinnen mittels einer einzigen Adresse lässt sich entweder mit einer Zeile in /etc/postfix/virtual bewerkstelligen, oder mit der Mailman Suite, bestehend aus den Projekten Mailman Core, Postorius, Hyperkitty, Mailmanclient, Hyperkitty Mailman plugin und Django-mailman3. Als wäre die Architektur nicht einschüchternd genug, gibt es zwei distinkte Einstiegspunkte in die Dokumentation, von denen nur einer zu einer stabilen Installationsleitung führt. Folgt man dieser Anleitung ohne jede Abweichung, denkt an das percent-encoding der Passwörter und konfiguriert die gewünschte Subdomain samt DKIM-Schlüssel und DNS-Einträgen für SPF, DKIM und DMARC (denn einige Mailprovider ignorieren die Subdomain Policy des bestehenden DMARC-Eintrags), gelangt man erstaunlich rasch zu einer funktionierenden Mailman-Instanz. Die Verwendung des Listennamens als Absenderadresse (DMARC mitigation) erzeugt in Verbindung mit use_esld = false; in /etc/rspamd/local.d/dkim_signing.conf (um die Reduktion von lists.eden.one auf die eSLD eden.one im Rahmen der DKIM-Signatur zu verhindern) wohlgeformte E-Mails, die allerdings unter Umständen mit der DKIM-Signatur der Absenderin verunziert sind:

Authentication-Results: posteo.de; dmarc=pass (p=quarantine dis=none) header.from=lists.eden.one Authentication-Results: posteo.de; spf=pass smtp.mailfrom=eden.one Authentication-Results: posteo.de; dkim=pass (2048-bit key) header.d=lists.eden.one header.i=@lists.eden.one header.b=RohmQgN4; dkim=fail reason="signature verification failed" (2048-bit key) header.d=eden.one header.i=@eden.one header.b=Io5jc3FE; dkim-atps=neutral

Dieses Problem lässt sich mit dem Parameter remove_dkim_headers: yes in /etc/mailman3/mailman.cfg (um die ursprüngliche DKIM-Signatur zu entfernen) leicht beheben:

Authentication-Results: posteo.de; dmarc=pass (p=quarantine dis=none) header.from=lists.eden.one Authentication-Results: posteo.de; spf=pass smtp.mailfrom=eden.one Authentication-Results: posteo.de; dkim=pass (2048-bit key) header.d=lists.eden.one header.i=@lists.eden.one header.b=0VbRo4SC; dkim-atps=neutral

Im nächsten Schritt soll die Kommunikation zwischen Mailman und Postfix, die standardmäßig unverschlüsselt und unauthentifiziert über Port 25 erfolgt, abgesichert werden:

# /etc/mailman3/mailman.cfg [mta] smtp_user: mailman_sender smtp_pass: mypassword smtp_secure_mode: starttls smtp_port: 587 smtp_verify_cert: no

Weil von diesem Moment an reject_sender_login_mismatch und smtpd_sender_login_maps greifen, benötigt mailman_sender einen Freibrief (und keinen Hack in mailman/src/mailman/mta/postfix.py, um die jeweils genutzten Listenadressen einzeln nachzutragen):

# /etc/postfix/controlled_envelope_senders @lists.eden.one mailman_sender

Zum Schluss verschiebe ich /opt/mailman/mm/mailman-hyperkitty.cfg nach /etc/mailman3/mailman-hyperkitty.cfg und vollziehe die Änderung in /etc/mailman3/mailman.cfg nach:

[archiver.hyperkitty] class: mailman_hyperkitty.Archiver enable: yes configuration: /etc/mailman3/mailman-hyperkitty.cfg

Nach diesen harmlosen Optimierungen verfalle ich auf die verhängnisvolle Idee, mailman-web und uwsgi aus Effizienzerwägungen über ein file socket kommunizieren zu lassen. Die initialen Änderungen in /etc/mailman3/uwsgi.ini und /etc/nginx/sites-enabled/mailman sind rasch erledigt, wenn man nicht vergisst, nginx die relevanten Umgebungsvariablen via uwsgi_params zu vermitteln (andernfalls: KeyError: 'REQUEST_METHOD'):

# /etc/mailman3/uwsgi.ini [uwsgi] # http-socket = 0.0.0.0:8000 socket = /opt/mailman/mm/var/mailman.sock chmod-socket = 664 # /etc/nginx/sites-enabled/mailman location / { #proxy_pass http://127.0.0.1:8000; uwsgi_pass unix:/opt/mailman/mm/var/mailman.sock; include /etc/mailman3/uwsgi_params; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $remote_addr; }

Um mailman-web von den neuen Verhältnissen zu überzeugen, müssen zwei weitere Einstellungen angepasst werden:

# /etc/mailman3/settings.py # override http://localhost:8000 in mailman_web/settings/mailman.py POSTORIUS_TEMPLATE_BASE_URL = 'https://lists.eden.one' # /etc/mailman3/mailman-hyperkitty.cfg [general] # base_url: https://127.0.0.1:8000/archives/ base_url: https://lists.eden.one/archives/

Nun funktioniert alles – außer der Archivierung von Nachrichten mit Hyperkitty (obwohl sie zuvor, mit http-socket, kein Problem war):

# /opt/mailman/web/logs/mailmanweb.log PermissionError at /archives/list/testlist@lists.eden.one/thread/T6MCILAVQYV5QIOGHJB3JNJ26Z7E2Z35/reattach-suggest [Errno 13] Permission denied: 'fulltext_index'

Es stellt sich heraus, dass das Verzeichnis fulltext_index durch das Whoosh-Backend der Haystack-Komponente referenziert wird. Ich probiere es mit einer vollständigen Pfadangabe –

# /etc/mailman3/settings.py # override the faulty PATH in mailman_web/settings/mailman.py HAYSTACK_CONNECTIONS = { 'default': { 'ENGINE': 'haystack.backends.whoosh_backend.WhooshEngine', 'PATH': "/opt/mailman/fulltext_index", }, }

– und ernte einen Teilerfolg (eine andere Fehlermeldung):

ERROR 2022-12-01 09:18:00,111 1337915 hyperkitty.views.mailman Access to the archiving API endpoint was forbidden from IP 123.456.789.012, your MAILMAN_ARCHIVER_FROM setting may be misconfigured

Offenbar eine weitere Nebenwirkung des Wechsels von http-socket zu socket, die sich durch die Angabe der öffentlichen IP des Mailservers beheben lässt:

# /etc/mailman3/settings.py # MAILMAN_ARCHIVER_FROM = ('127.0.0.1', '::1') MAILMAN_ARCHIVER_FROM = ('123.456.789.012', '::1')

Natürlich frage ich mich langsam, ob ein socket wirklich so viel effizienter ist, aber genau in diesem Moment enden die Fehlermeldungen, und Hyperkitty arbeitet wie gewünscht: Threads lassen sich im Archiv suchen und neu zusammenfügen, und kombinierbare Threads werden sogar automatisch vorgeschlagen, wenn sich deren Betreffzeilen gleichen. Fröhliches reattachment führt allerdings dazu, dass Hyperkitty auf den Archivseiten aller Testlisten vergeblich nach anzuzeigenden Threads sucht (hyperkitty.models.thread.Thread.DoesNotExist: Thread matching query does not exist.), aber mein Debugging-Konto für diesen Monat ist schon weit überzogen.

Unterstützung bei der tastenden Fehlersuche erhalte ich ausnahmsweise nicht über eine der vielen Stackexchange-Seiten, sondern über mailman-users[at]mailman3.org, die zu meiner großen Befriedigung weniger sauber konfiguriert ist als meine Listen:

Authentication-Results: posteo.de; dmarc=pass (p=none dis=none) header.from=mailman3.org Authentication-Results: posteo.de; dkim=pass (1024-bit key) header.d=mailman3.org header.i=@mailman3.org header.b=0eJkGPKA; dkim=fail reason="signature verification failed" (2048-bit key) header.d=eden.one header.i=@eden.one header.b=l4srDqkL; dkim-atps=neutral

mailman-users[at]mailman3.org wird exklusiv von Mark Sapiro betreut, der meine Anmerkung zum Hyperkitty-Konfigurationspfad unverzüglich umsetzt und für neue Admins entmutigende Hinweise wie diesen bereithält:

Note in the above, django-admin is the command you use to invoke Django's management which may be django-admin, manage.py, mailman-web or something else depending on how Mailman is installed.

Jedes Projekt sollte einen so freundlichen und geduldigen resident sage haben. Sehr angenehm ist auch der minimale Platzbedarf der Mailman-Datenbanken (< 20KB), vor allem im Vergleich zu gewissen raumgreifenden Sozialanwendungen. Allerdings trägt die Mailman Suite mit ihren verschiedenen Django-Anwendungen allein etwa zwei Prozentpunkte zur Systemlast bei (~ 5% CPU, ~ 15% RAM), obwohl Reichweite und Aktivität meiner Testlisten im nicht messbaren Bereich liegen.