Home   Was ist ot ?   Regeln   Mitglieder   Maintainer   Impressum   FAQ/Hilfe  

Mail-Schnittstelle

Maintainer: Stefan Meretz, Version 3, 08.11.2000
Projekt-Typ:
Status: Archiv

Mailsteuerung von OpenTheory

(1) Es ist alles nicht so einfach. Es gibt - wie meistens - mehrere Varianten, wie das Problem der Mailsteuerung gelöst werden kann. Ich will keine der Varianten ausschliessen, sondern ihre Unterschiede sowie Vor- und Nachteile darstellen, damit den Hackern, die eine bestimmte Variante realisieren wollen, klar ist, worauf sie sich einlassen. Ich hoffe, das gelingt mir auch, denn wie gesagt: Es ist alles nicht so einfach. Vor der Darstellung des Ablaufes mit den technischen Details mache ich deswegen ausführliche Vorbemerkungen.

Vorbemerkungen

(2) Die Domäne opentheory.org sei mein Beispiel, sie kann selbstredend ersetzt werden. Ich gehe von Postfix als MTA aus, das Beispiel sollte aber auch auf Sendmail übertragbar sein. Unterschiede werde ich entsprechend benennen. Der grobe Ablauf der Mailsteuerung von OpenTheory sieht so aus (Serversicht):
1. Mailempfang und Übergabe an ein Auswerteskript
2. Auswertung der Mail, Zusammenstellen der Kommandos
3. Senden einer Bestätigungsmail an den User
4. Empfang des Bestätigungs-Replys vom User
5. Ausführen der Kommandos, ggf. Antwort-Mails

(3) Zur Verarbeitung gesendeter Kommandos müssen zwei Fragen beantwortet werden:
a. Wer hat das Kommando geschickt? - Absender: immer erforderlich
b. Auf welches Projekt beziehen sich die geschickten Kommandos? - Zielprojekt: meistens erforderlich

(4) Der Absender (Punkt a.) ist leicht aus der "From:"-Adresse im Mail-Header bestimmbar. Das Zielprojekt ist dagegen nicht so einfach zu bestimmen, denn diese Bestimmung hängt vom Ort (wo in der Mail steht das Kommando?) und der Syntax der Kommandos ab. Die Auswertung der Mail und das Zusammenstellen der Kommandos (s.o. Punkt 2.) kann in zweierlei Syntax erfolgen:

Address-based (ab)

(5) Als address-based Syntax bezeichne ich die Syntax, bei der die Steuerkommandos direkt in der Adresse stehen. Der Body enthält ggf. Argumente, aber keine (?) Tags. Diese Form der Steuerung halte ich für Simple-User für einfacher als die nächste Form.

(6) Da bei dieser Variante das betroffene Projekt mit in der Adresse steht, gäbe es bei x Kommandos auch für jedes Projekt x Einträge in /etc/aliases. Dies ist sehr unhandlich, unflexibel bei Änderungen der Kommandosyntax und u.U. auch nicht sehr performant bei großer Projektenanzahl. Hier ist es also besser, eine gesammelte Übergabe _aller_ Mails an die Domäne "opentheory.org" einzurichten und die Auswertung des Zielprojekts dem Skript zu überlassen. Diese Variante funktioniert nur, wenn "Bcc:"-Adressierung ausgeschlossen wird, da nur "To:"- und "Cc:"-Adressen im Mail-Header stehen und von Skript ausgewertet werden können.

Body-based (bb)

(7) Als body-based Syntax bezeichne ich die Syntax, bei der die Steuerkommandos im Mailbody stehen. Die einzelnen Kommandos sowie die Argumente werden durch Tags angegeben. Hierfür müsste eine BNF einwickelt werden. Diese Form der Steuerung halte ich für die mächtigere und daher v.a. auf Power-User zielende Form.

(8) Mails mit bb-Kommandos werden an _eine_ zentrale Adresse geschickt, wofür es genau einen Alias gibt. Das Zielprojekt muss somit als Tag mit einem Wert explizit als Kommando mit im Body stehen. Eine nicht unwesentliche Ausnahme von dieser Logik sind die direkt an die Projekt-Mailinglisten gerichteten Mails, bei denen der Projektname (in ab-Logik!) in den Adressen steht. Diese werden dynamisch bei Projektneugründung durch einen periodischen Root-Cronjob in /etc/aliases geschrieben, wobei der Projektname gleich als Argument für den Skriptaufruf mit eingetragen wird. Das erspart eine aufwendige Auswertung der "To:"- und "Cc:"-Adressen im Mail-Header und funtioniert auch bei "Bcc:" Adressen (unabhängig von der Sinnhaftigkeit der Verwendung von Bcc).

(9) Zugefasst also nochmal die unterschiedliche Bestimmung des Zielprojekts:

(10) Lässt man die Mailinglisten-Mails der Projekte mal außen vor, dann kann man noch kürzer schreiben: Das Zielprojekt kommt bei

(11) Die Bestätigungsmails werden mit einem Reply-To in ab-Logik versandt.

(12) Noch eine letzte Vorbemerkung zur folgenden Darstellung: Als Schnittstellen zwischen den Modulen verwende ich DB-Tabellen. Diese sind ggf. auch teilweise oder ganz durch "RAM-basierte" Schnittstellen ersetzbar (z.B. als Übergabe von Datenstrukturen in Variablen). Tabellen, die verwendet werden, sind in der MySQL-Notation dargestellt (als create-Befehl). Wer mit Datenbanken nicht so vertraut ist, lese nur die Zeilen, bei denen hinter der Feldbezeichnung ein Datentyp steht: Das sind die Tabellenspalten.

Ablauf der Verarbeitung

(13) Im folgenden wird die Verarbeitung einfacher Mailinglisten-Mails der Projekte ausgeklammert, was die Darstellung vereinfacht. Die Verarbeitung auf dem ot Server eingehender Mails (o.g. Punkte 1 bis 5) erfolgt technisch schematisch so:

=> MTA => Receiver => Parser-{a|b} => Confirmer => Exekutor

(14) Im folgenden werden die einzelnen Schritte beschrieben. Wo nötig, unterscheide ich ab- und bb-Logik. Die Annahme ist, dass beide Varianten benutzt werden können. Die Mail in ab-Logik gehe an addr_cmd@opentheory.org, die in bb-Logik an adresse@opentheory.org. Eine Bcc-Addressierung wird ausgeschlossen.

=> MTA

(15) Die Mail an addr_cmd@opentheory.org bzw. adresse@opentheory.org trifft beim MTA auf dem ot-Server ein. Über einen Eintrag in /etc/postfix/virtual werden alle Mail an die Domäne opentheory.org an den Receiver weitergeleitet:

@opentheory.org   |/anypath/ot-receive.pl
Die neueren Sendmail-Versionen kennen auch virtuelle Domänen, so dass eine entsprechende Einstellung dort auch möglich sein sollte (wie, weiss ich aber nicht, über /etc/aliases geht es so einfach nicht).

=> Receiver (ot-receive.pl)

(16) Der Receiver durchsucht die "To:"- und "Cc:"-Headereinträge nach Adressen an opentheory.org. Findet er keine, liegt eine "Bcc:"-Adressierung vor, die nicht akzeptiert wird - eine Bounce-Mail wird an den Absender (sofern vorhanden) und den Admin geschickt. Findet er Adressen, dann werden _alle_ zusammen mit der "Message-ID:" aus dem Header in eine DB-Tabelle geschrieben, sofern zu dieser Message-ID keine Einträge vorliegen. Gibt es keinen "Message-ID:"-Eintrag, wird "Date:" verwendet (sollte nach RFC822 nicht vorkommen). Eine Mail mit fünf Adressen, die den Receiver auch fünfmal erreicht, wird demnach nur beim _ersten_ Mal für _jede_ Adresse in die Tabelle geschrieben (sollte nur bei ab-Mails vorkommen). Header und Body werden getrennt, vom Header werden neben Msg-Id und Adresse auch die komplette "To:"-, "Cc:"- und "Subject:"-Zeile gespeichert (für die Mailinglisten-Mails) Struktur der Tabelle:

 create table receive (
rec_id int unsigned not null auto_increment,
msg_id varchar(128) not null,
proj_addr varchar(128) not null,
proj_id smallint unsigned null,
addr_to varchar(255) null,
addr_cc varchar(255) null,
subject varchar(255) null,
from varchar(255) not null,
memb_id smallint unsigned null,
body blob not null,
primary key( rec_id ),
unique key msg_id_proj_addr_idx( msg_id, proj_addr )
);
rec_id: Receive-ID, automatisch erzeugte ID (hier als P-Key)
msg_id: "Message_ID:"-Eintrag aus Header;
proj_addr: Mailadresse des Projekts aus "To:"- oder "Cc:"-Headereintrag ohne Domäne (also 'adresse' oder 'addr_cmd');
proj_id: Project-ID, füllt erst der Confirmer mit proj_addr
addr_to: komplette "To:"-Headerzeile
addr_cc: komplette "Cc:"-Headerzeile
subject: komplette "Subject:"-Headerzeile
from: komplette "From:"-Headerzeile
memb_id: Member-ID, füllt erst der Confirmer mit from
body: kompletter Mail-Body (inkl. Anhängen)

(17) Wie unterscheichen sich nun ab- und bb-Mails? Mail mit bb-Kommando-Syntax haben im Feld proj_addr den Eintrag "adresse" (aus adresse@opentheory.org). Nach dem Eintrag (also nur beim Receiver-Aufruf mit der ersten Mail) ruft der Receiver den Parser auf, und zwar in Abhängigkeit von proj_addr den Parser-a für die ab Syntax oder den Parser-b für die bb-Syntax.

=> Parser-{a|b} (ot-parser_a.pl | ot-parser_b.pl)

(18) Die Parser für die ab- oder bb-Syntax identifizieren die Kommando-Werte-Paare und schreiben diese in zwei Tabellen folgender Struktur:

 create table command (
rec_id int unsigned not null,
cmd_id int unsigned not null,
send timestamp,
status varchar( 10 ) not null default 'received',
primary key( cmd_id )
);
rec_id: Receive-ID (hier als F-Key-Referenz auf 'receive')
cmd_id: eindeutige 9-stellige Nummer (hier als P-Key)
send: Zeitstempel
status: Status (received, requested, confirmed, processed, error)

(19) Zwischen den Tabellen receive und command besteht eine 1:n-Relation: Eine empfangene Mail kann eine oder mehrere (zu bestätigende) Kommandos enthalten.

 create table cmdkeyval (
cmd_id int unsigned not null,
cmdkey varchar( 64 ),
cmdval blob
key cmd_id_idx( cmd_id )
);
cmd_id: eindeutige 9-stellige Nummer (hier als F-Key-Referenz auf 'command')
cmdkey: Kommandoschlüssel; identifiziert die anzuwendende Funktion
cmdval: Kommandowert; Argument zur Funktion

(20) Zwischen den Tabellen command und cmdkeyval besteht eine 1:n-Relation. Eine Funktion mit mehr als einem Argument erhält auch mehrere Einträge in cmdkeyval zu einem Eintrag in command. "Implizite" Argumente sind die Projekt-ID und die Member-ID, die bereits in der Tabelle receive stehen.

(21) Die folgende Tabelle umfasst alle Funktionen mit ihren Argumenten, die für die Mailsteuerung von OpenTheory realisiert werden müssen:

 Name       | Argument(e)          | Beschreibung
-----------+----------------------+-------------------------------------
project | Projektname | aktuelles Projekt setzen (nur bb)
which | - | in welchen Projekten eingetragen
who | - | wer ist im Projekt eingetragen
comment | Absatz-Nr, Kommentar | einen Kommentar schicken
subscribe | {Projektname} | in ein Projekt eintragen
unsubscribe| {Projektname} | aus einem Projekt austragen
setname | Vorname, Nachname | Usernamen ändern
setoption | Option, ein/aus | Option setzen
newproject | Projektname, Langname| ein neues Projekt gründen
. | Beschreibung |
submit | Text | einen Projekttext einreichen
newversion | Langname, Beschreibg.| eine neue Version erzeugen
give | User-Email-Adresse | Maintainerschaft abgeben
addsub | Projektname | Projekt als Subprojekt zuordnen
uselist | Mailingliste | Übergeordnete Mailingliste verwenden
keywords | Liste der Keywords | Keywords setzen
title | Langname | Projektitel ändern
description| Beschreibung | Projektbeschreibung ändern
confirm | Cmd_id | Bestätigung eines Kommandos

(22) Wie diese Funktionen in der jeweiligen Syntax realisiert werden, ist damit noch nicht gesetzt. Da in der Tabelle cmdkeyval jedes Kommando nur ein Argument besitzt, sind die hier gelisteten Kommandos mit mehreren Argumenten zu zerlegen in mehrere einargumentige Kommandos. Das sollte aber kein Problem darstellen, zumal es in der Regel zu mehrargumentigen Kommandos auch ihre einargumentigen Pendants gibt. Wichtig ist nur, dass alle Argumente eines Kommandos (der Tabelle) auch vollständig in der Mail und schliesslich in der Tabelle cmdkeyval stehen.

(23) Besonderheit bei der bb-Syntax: Der bb-Parser ersetzt den Wert 'adresse' im Feld proj_addr der Tabelle receive durch den Wert aus dem Argument des project-Tags. Nach Abschluß ruft der Parser den Confirmer auf Argument ist der Syntaxmodus).

=> Confirmer (ot-confirm.pl)

(24) Der Confirmer füllt die Felder proj_id (mit addr_proj) und memb_id (mit Mailadresse aus from) der Tabelle receive. Wird keine memb_id gefunden, prüft der Confirmer, ob ein subscribe-Kommando vorliegt. Wenn nicht, wird an den Absender eine Fehler-Mail gesandt. Der Confirmer sendet dann für jeden Eintrag in der Tabelle command, der in convcmds keinen cmdkey='confirm' hat, eine Bestätigungsmail an den User (=Absender). Der Status in der Tabelle command wir auf 'requested' gesetzt.

(25) In Abhängigkeit vom Syntaxmodus wird das 'confirm' mit der 'cmd_id' in die "ReplyTo-Adresse" oder als Tag mit einem Wert in den Mailbody geschrieben. Der Syntaxmodus wird also durchgehalten. Der Confirmer ruft den Exekutor auf.

=> Exekutor

(26) Der Exekutor führt die eigentliche Abarbeitung der Kommandos durch. Zuerst werden alle Kommandos mit cmdkey='confirm' ausgeführt. Alle Kommandos in command mit cmd_id = keyval(cmdkey='confirm') werden auf den Wert 'confirmed' gesetzt. Dann werden alle Kommandos mit cmdkey<>'confirm' abgearbeitet. Der Exekutor prüft vorher jeweils, ob der Kommandosatz bzgl. Aller notwendigen Argmente (siehe Tabelle oben) vollständig ist. Fehler werden per Mail an den User gemeldet, der Status wird auf 'error' gesetzt. Im Falle der erfolgreichen Ausführung des Kommandos wird der Status auf 'processed' gesetzt. Im vorletzten Durchlauf werden alle Kommandos (Tabellen command und cmdkeyval) mit dem Status 'processed' gelöscht und im letzten Durchlauf schliesslich alle Einträge in der Tabelle receive, die keine Einträge in der Tabelle command mehr referenzieren. Alle Aktionen werden protokolliert.

(27) Die Einzelbeschreibung der Abarbeitung der Befehle spare ich mir hier. Hierbei handelt es sich im Wesentlichen um Inserts und Updates auf den ot-Tabellen.


Valid HTML 4.01 Transitional