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

Mail-Schnittstelle

Maintainer: Stefan Meretz, Version 4, 12.06.2001
Projekt-Typ: halboffen
Status: Archiv

Mailsteuerung von open theory

(1) 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. Vor der Darstellung des Ablaufes mit den technischen Details mache ich 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 open theory 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) und body-based (bb).

Address-based (ab)

(5) Als address-based Syntax bezeichne ich die Syntax, bei der die Steuerkommandos direkt in der Mail-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 der ab-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 entsprechend in der ab-Logik ausgewertet.

(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. 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} => Validator => 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 ot@opentheory.org. Eine Bcc-Addressierung wird ausgeschlossen.

=> MTA

(15) Die Mail an addr_cmd@opentheory.org bzw. ot@opentheory.org trifft beim MTA auf dem ot-Server ein. Diese Mails werden zur Verarbeitung weitergeleitet. Dies geschieht über Einträge in /etc/postfix/virtual

opentheory.org blablabla
your.name@opentheory.org username
username@opentheory.org username
listmaster@opentheory.org username
webmaster@opentheory.org username
anonym@opentheory.org username
@opentheory.org   ot_mail
Die erste Zeile definiert die (virtuelle) Domäne, die letzte Zeile sammelt alle verbliebenen Mails für die Domäne opentheory.org und leitet sie an den Alias "ot_mail" weiter. Dazwischen stehen Mailadressen, die nicht zur allgemeinen Verarbeitung weitergeleitet, sondern (an username) ausgeliefert werden sollen.

(16) Fehlt noch ein Eintrag in /etc/aliases

ot_mail:   "/usr/bin/perl |/anypath/otperl/ot_mail.pl"
Mails, die zum den Alias "ot_mail" weitergeleitet wurde, werden nun per Pipe an das Skript ot_mail.pl zur Verarbeitung übergeben. Diese doppelte Umleitung ist notwendig, weil Postfix aus Sicherheitsgründen im Usermode keine Weiterleitung an Skripte zulässt. Bei Sendmail wird analog die Datei /etc/mail/virtusertable für diesen Zweck verwendet (ich habe es allerdings nicht ausprobiert). Das Skript ot_mail.pl führt nacheinander die nächsten Verarbeitungsschritte durch.

=> Receiver (ot_receive.pm)

(17) 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 entsprechender Fehler wird in den unten beschriebenen Tabellen eingetragen. 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 not null default 0,
addr_to varchar(255) null,
addr_cc varchar(255) null,
subject varchar(255) null,
sender varchar(255) not null,
memb_id smallint unsigned not null default 0,
send varchar(64) not 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 Parser (benutzt proj_addr)
addr_to: komplette "To:"-Headerzeile
addr_cc: komplette "Cc:"-Headerzeile
subject: komplette "Subject:"-Headerzeile
sender: komplette "From:"-Headerzeile
memb_id: Member-Id
send: Absende-Datum und Uhrzeit
body: kompletter Mail-Body (inkl. Anhängen)

(17.1) Re: => Receiver (ot_receive.pm), 31.12.2002, 12:57, Ano Nym: uftuftuftu#jhnjhjhnjkjkkj

(18) Wie unterscheiden sich nun ab- und bb-Mails? Mail mit bb-Kommando-Syntax haben im Feld proj_addr den Eintrag "ot" (aus ot@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_parse_a.pm | ot_parse_b.pm)

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

 create table command (
rec_id int unsigned not null,
cmd_id int unsigned not null,
cmdnum int unsigned not null default 1,
cmdkey varchar( 64 ) not null default 'error',
cmdval blob null,
stcode int not null default 3,
status varchar( 16 ) not null default 'ok',
primary key( cmd_id, cmdnum )
);
rec_id: Receive-ID (hier als F-Key-Referenz auf 'receive')
cmd_id: eindeutige 9-stellige Nummer (P-Key Part 1)
cmdnum: Lfd. Nr. des Kommandos; >1 bei mehrargumentigen Kommandos (P-Key Part 2)
cmdkey: Kommandoschlüssel; identifiziert die anzuwendende Funktion
cmdval: Kommandowert; Argument zur Funktion
stcode: Status-Code ( < 0: Fehler)
status: Status-Text zum Status-Code

(20) Zwischen den Tabellen receive und command besteht eine 1:n-Relation: Eine empfangene Mail kann ein oder mehrere Kommandos enthalten und damit ein oder mehrere Datensätze in command erzeugen. Mehr als ein Kommando pro Mail ist nur im bb-Mode möglich. Die lfd. Nummmer cmdnum unterscheidet die zu einer Mail gehörenden Kommandos. "Implizites" Argument ist die Member-Id.

(21) Die folgende Tabelle umfasst alle Funktionen mit ihren Argumenten, die für die Mailsteuerung von open theory realisiert werden müssen (Projektname in geschweiften Klammern nur bei ab-mode, im bb-mode wird der Projektname als Kommando gesetzt):

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

(22) Wie diese Funktionen in der jeweiligen Syntax realisiert werden, ist damit noch nicht gesetzt. Da in der Tabelle command jedes Kommando nur ein Feld 'keyval' besitzt, sind die Argumente der Kommandos mit mehreren Argumenten in diesem einen Feld zu speichern. Separator bei mehrargumentigen Einträgen ist das Zeichen 'n' (Newline/Zeilenumbruch). Folgende Überprüfungen werden vom Parser durchgeführt:

(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 Validator auf.

=> Validator (ot_validate.pm)

(24) Der Validator prüft die Gültigkeit jedes command-Eintrages. Im Fehlerfall wird ein Fehlercode in die Tabelle command eingetragen (zur Auswertung durch Execute). Folgende Überprüfungen werden durchgeführt:

Der Status der Einträge wird auf "ok_to_confirm" gesetzt, sofern eine Bestätigung durch den User erforderlich ist (wertet der Executor aus). Der Validator ruft den Exekutor auf.

=> Exekutor (ot_execute.pm)

(25) Der Exekutor führt die eigentliche Abarbeitung der Kommandos in vier Schritten durch:

  1. Auswerten aller Kommandos, die eine Bestätigung durch den User erfordern (status = ok_to_confirm)
  2. Abarbeiten aller Kommandos, die gültig noch nicht abgeschlossen sind (status >= ok und status < ok_finished)
  3. Abarbeiten aller Kommandos, die einen Fehlerstatus haben (status < errors)
  4. Löschen aller Kommandos, die abgeschlossen sind (status = ok_finished)

1. Bestätigung (status = ok_to_confirm)

(26) An den Absender wird eine Mail geschickt mit einer Reply-Adresse im Format

confirm*command000000000@opentheory.org
wobei "command" das zu bestätigende Kommando ist. Der Status wird auf "ok_requested" gesetzt. Nur wenn der User auf die Bestätigungsaufforderung mit Reply antwortet, wird das Kommando ausgeführt. Welche Kommandos bestätigt werden müssen, zeigt die Tabelle unten.

2. Ausführung (status >= ok und status < ok_finished)

(27) Alle Kommandos, die gültig und noch nicht abgeschlossen sind, werden ausgeführt. Bestätigungen durch den User werden dabei registriert (setzen des Status auf ok_confirmed), und das zugehörige Kommando wird ausgeführt. Tritt ein Fehler auf, wird ein entsprechender Fehlerstatus gesetzt. Antworten werden per Mail verschickt. Der Status wird nach fehlerfreier Verarbeitung auf ok_finished gesetzt.

3. Fehlerbearbeitung (status < errors)

(28) Alle Kommandos, deren Status in vorherigen Verarbeitungsschritten auf einen Fehleratstatus gesetzt wurden, werden nun abgearbeitet. Eine Fehlermail wird an den User gesendet. Der Status wird nach (fehlerfreier) Verarbeitung auf ok_finished gesetzt.

4. Löschen (status = ok_finished)

(29) Im vorletzten Durchlauf werden alle Kommandos (Tabelle command) mit dem Status 'ok_finished' gelöscht und im letzten Durchlauf schliesslich alle Einträge in der Tabelle receive, die keine Einträge in der Tabelle command mehr referenzieren. Anschliessend sollte es in den Tabellen receive und command nur noch Einträge geben, die auf eine Bestätigung durch den User warten. Alle Aktionen werden protokolliert.

Beschreibung der Verarbeitung: address-based mode

(30) Die o.g. Kommandos haben im ab-mode folgende Syntax (addr_cmd-Teil der gesamten Mailadresse addr_cmd@opentheory.org):

 Name       | Argument(e)          | Syntax
-----------+----------------------+-------------------------------------
which | - | which*ot
who | {Projektname} | who*[projektname]
comment | {Projektname} | comment*[projektname.(absatznr)]
. | Absatz-Nr, Kommentar | - Kommentar im Body
subscribe | {Projektname} | subscribe*[projektname]
unsubscribe| {Projektname} | unsubscribe*[projektname]
setname | Vorname, Nachname | setname*ot - Vorname, Nachname im Body
setoption | Option, ein/aus | setoption*[optionname].[y|n]
newproject | Neuer Projektname, | newproject*ot - Neuer Projektname,
. | Langname, Beschreibg.| Langname und Beschreibung im Body
submit | {Projektname}, Text | submit*[projektname]
newversion | {Projektname} | newversion*[projektname]
. | Langname, Beschreibg.| - Langname Beschreibung im Body
give | {Projektname} | give*[projektname]
. | User-Email-Adresse | - User-Email-Adresse im Body
addsub | {Projektname} | addsub*[projektname]
. | Ziel-Projektname | - Ziel-Projektname im Body
gettree | {Projektname} | gettree*[projektname]
getlists | {Projektname} | getlists*[projektname]
uselist | {Projektname} | uselist*[projektname]
. | Mailingliste | - Mailingliste im Body
keywords | {Projektname} | keywords*[projektname]
. | Liste der Keywords | - Liste der Keywords im Body
title | {Projektname} | title*[projektname]
. | Langname | - Langname im Body
description| {Projektname} | description*[projektname]
. | Beschreibung | - Beschreibung im Body
confirm | Cmd_id | confirm*[Cmd_id]
help | Typ | help*[ot|maintainer|member]
(maillist) | Mailtext | [mailingliste] - Text im Body

(31) Kommandos mit mehr als einem Argument werden in mehrere einargumentige Kommandos aufgespalten. Da der Projektname - sofern er semantisch nur das Zielprojekt angibt - per update in der Tabelle receive gespeichert wird, besitzen nur wenige Kommandos mehr als ein Argument. Die aufgespaltenen Kommandos werden aus dem ursprünglichen Kommandonamen plus einem anhängenden '-' und einem Zähler erzeugt. Unterschieden nach Anzahl der resultierenden Argumente ergibt sich damit folgende Liste (der Bezug - Projektname oder ot allgemein - wird in geschweiften Klammern aufgeführt; * gibt die Befehle an, die nur von Mitgliedern bei open theory (ohne Projekt), im Projekt oder in der Mailingliste (ggf. im im Projekt!) ausgeführt werden können):

Beschreibung der Verarbeitung: body-based mode

(32) Da ich diesen Modus nicht realisiert habe, kann ich nur grobe Hinweise geben. Der bb-Mode fügt sich nahtlos in die Abfolge der Verarbeitungschritte ein. Resultat des bb-Parsers sind also Einträge in den Tabellen receive und command, die sich nicht von denen des ab-Parsers unterscheiden - außer der Tatsache, dass in einer bb-Mail viele Kommandos stehen können.

(33) Da es für den Datenbankzugriff zahlreiche Access-Funktionen gibt, besteht die Hauptaufgabe "nur" darin, eine geeignete Syntax zu definieren und dafür einen Parser zu schreiben.

Anhang

(34) Im folgenden werden technische Konstanten dokumentiert, wie sie bislang im ab-Modus verwendet werden.

Bit-Codes

(35)

Bitweise Kodierung der Kommandoeigenschaften

(36)

 Name       | 1 | 2 | 4 | 8 | 16| 32| 64|128|256|512|024|048|096| Summe
-----------+---+---+---+---+---+---+---+---+---+---+---+---+---+--------
which | | x | | | x | | | | | | | | | 18
who | x | x | x | | x | | | | | | | | | 23
subscribe | x | | | | x | | | | x | | | | | 273
unsubscribe| x | x | x | | x | | | | x | | | | | 279
gettree | x | | | | x | | | | | | x | | | 17+2048
getlists | x | | | | x | | | | | | x | | | 17+2048
confirm | | | | | | x | | | | | | | | 32
help | | | | | | x | | | | | x | | | 32+2048
submit | x | x | x | x | | x | | | x | | x | | | 303+2048
give | x | x | x | x | | x | | | x | | x | | | 303+2048
addsub | x | x | x | x | | x | | | | | x | | | 47+2048
uselist | x | x | x | x | | x | | | | | x | | | 47+2048
keywords | x | x | x | x | | x | | | | | x | | | 47+2048
title | x | x | x | x | | x | | | | | x | | | 47+2048
description| x | x | x | x | | x | | | | | x | | | 47+2048
setoption | | x | | | | | x | | | | x | | | 66+2048
comment | x | | | | | | x | | | | x | | | 65+2048
setname | | x | | | | | x | | | | x | | | 66+2048
newversion | x | x | x | x | | | x | | x | | x | | | 335+2048
newproject | | x | x | | | | | x | x | | x | | | 390+2048
(maillist) | x | x | | | | | x | | | x | | | | 579

Valid HTML 4.01 Transitional