"Sprechende" Message-IDs erzeugen mit dem INN
Ich fürchte, das hier wird ein eher technisch geprägter Special-Interest-Beitrag werden, aber sei es drum: es soll darum gehen, mit Hilfe des Newsservers INN Message-IDs für Usenet-Postings zu erzeugen, die neben ihrem primären Daseinszweck - ein Posting mit einer dauerhaft und global eindeutigen ID zu versehen - auch einen menschenlesbaren Inhalt haben.
Message-IDs, ihr Aufbau und ihre Funktion
Der Message-ID-Header verleiht jedem Beitrag im Usenet (jedem Posting) und jeder E-Mail eine absolut eindeutige, nur einmal vergebene Kennung, mit der das Posting oder die E-Mail referenziert werden können.
Funktion
Die Notwendigkeit dafür ist einmal technischer Art; so können Postings und E-Mails bspw. in Logdateien eindeutig bezeichnet werden. Auch ist die Message-ID für das Threading - also die Darstellung von Postings oder E-Mails in einem “Diskussionsfaden” erforderlich; denn dazu enthält jede E-Mail die Message-ID ihres Vorgängers, also die Kennung der Nachricht, auf die sie die Antwort enthält. Bei Postings ist das ähnlich; nur werden dort die Message-IDs aller (oder bei langen Threads zumindest etlicher) Vorgänger übermittelt. So können Newsreader und Mailprogramme Bezugsverkettungen aufbauen und E-Mails (oder Postings) im Zusammenhang darstellen, ohne sich dabei auf Zufälle wie einen identischen Betreff verlassen zu müssen.
Im Usenet hat die Message-ID noch eine weitere wichtige Bedeutung: Postings werden im Gegensatz zu E-Mails nicht nur an einen oder mehrere bestimmte Empfänger übermittelt, sondern im “Floodfill”-Verfahren an alle miteinander vernetzten Newsserver verteilt. Diese Newsserver sind aber regelmäßig nicht nur einfach, sondern vielfach miteinander vernetzt und erhalten daher jeden Beitrag mehr- oder vielfach auf verschiedenen Wegen. Um Übertragungskapazität zu sparen (und Duplikate zu vermeiden) lehnen sie daher die Annahme von Beiträgen, die sie schon kennen, ab und speichern diese auch nicht, falls sie doch übertragen werden. Daraus folgt, dass ein Posting, dass eine bereits einmal verwendete Message-ID enthält, nicht verteilt wird; und schlimmer noch, dass eine Änderung der Message-ID zur mehrfachen Verteilung und Speicherung von Postings, also zu unerwünschten Duplikaten führt. Deshalb sollte man an der Message-ID von Postings, die nicht lokal erzeugt wurden, auch niemals herumfummeln und generell genau wissen, was man tut, wenn man die Message-ID manipuliert.
Unabhängig von diesen technischen Aufgaben ist die Message-ID auch der geeignetste Weg, im Usenet auf einen bestimmten anderen Beitrag zu verweisen. “Schau mal in das Posting von Hans Meier von gestern, 17.32 Uhr, da steht die Antwort auf Deine Frage” ist so aufwendig - für Schreiber und Leser - wie unprofessionell. “Schau Dir mal <d648ee74-9def-e5ca-8c12-9a347fce0dc5@host.example>
an, da steht alles drin” ist hingegen schnell geschrieben (es genügt ein Kopieren der Message-ID) und ebenso schnell aufrufbar, weil Newsreader in der Regel den Aufruf von Postings anhand der Message-ID ermöglichen.
Aufbau einer Messsage-ID
Vom Aufbau her ähnelt eine Message-ID sehr einer E-Mail-Adresse: sie hat einen linken und einen rechten Teil, die durch ein @
-Symbol miteinander verknüpft sind und wird von spitzen Klammern (<>
) eingerahmt. Weil das sehr dem Konzept einer lokalen Adresse und einer Domain ähnelt, wird die Anforderung, dass Message-IDs global eindeutig sein müssen, am besten so umgesetzt, dass das die ID erzeugende System auf der rechten Seite seinen (weltweit eindeutigen) Hostnamen einsetzt und auf der linken Seite eine Zeichenfolge verwendet, die systemweit eindeutig ist, in der Regel also aus einem Timestamp, ggf. einer Prozess-ID oder einem Zähler besteht. Das Format wäre also <unique@hostname>
.
Das funktioniert prima, solange das System einen (eindeutigen) Hostnamen hat; für den typischen (Windows-)Heimrechner ist das allerdings nicht der Fall. Dann kann man die Erzeugung der ID entweder dem Newsserver überlassen, der ja in der Regel im Netz erreichbar sein muss und daher über eine IP-Adresse und einen auch als FQDN bezeichneten domainisierten Hostnamen (news1.provider.example
) verfügt. Alternativ kann man dem Newsreader einen Hostnamen (FQDN) vorgeben; das muss nicht zwingend der tatsächliche Hostname des Rechners sein, es genügt auch ein Name, der garantiert nicht anderweitig vergeben werden kann, wie ihn bspw. das Projekt My FQDN zur Verfügung stellt. Schließlich kann man, wenn alle Stricke reißen und man die Message-ID unbedingt selbst generieren will, auf die Mailadresse des Autors abstellen, für die Adresse localpart@domain
dann etwa so: <unique.localpart@domain>
.
Beispiele für Message-IDs
In der Praxis sieht bspw. eine durch den Newsserver INN erzeugte Message-ID so aus:
<r6d9hs$4cu$1@thangorodrim.ancalagon.de>
Auf der rechten Seite steht der Hostname des Newsservers (hier thangorodrim.ancalagon.de
), auf der linken Seite finden sich (nacheinander und getrennt durch $
) ein Timestamp (hier r6d9hs
), die Prozess-ID des Serverprozeses (hier 4cu
) und - für den Fall, dass in derselben Sekunde vom selben Prozess mehrere Beiträge entgegengenommen werden - ein Zähler (hier 1
). Timestamp und Prozess-ID werden dabei in einem Zahlensystem auf der Basis 32 kodiert; dezimal lautet der Timestamp also 912696892
und die Prozess-ID 4510
. Und tatsächlich: wie das Logfile des Newsservers zeigt, wurde der Beitrag von dem Prozess Nummer 4510 angenommen:
thangorodrim nnrpd[4510]: thangorodrim.ancalagon.de (127.0.0.1) connect - port 119
Beim Timestamp muss man nun noch wissen, dass der INN vom tatsächlichen Unix-Timestamp (also der Anzahl der Sekunden seit der Epoche, d.h. seit 01.01.1970 00:00:00) wahllos 673416000 abzieht, um eine kürzere Message-ID zu erhalten. Zählt man also zu dem Timestamp von 912696892 noch 673416000 hinzu, erhält man 1586112892 - und das ist umgerechnet der 05.04.2020, 18:54:52 Uhr: passt!
Der Newsreader slrn arbeitet ähnlich:
<slrn94p19c.ns.thomas@thangorodrim.ancalagon.de>
Die Message-ID besteht aus dem Namen des Newsreaders (slrn
), einem verkürzten Zeitstempel im 32er-Zahlensystem (94p19c
), der Prozess-ID (ns
) und der Mailadresse des Nutzers (bzw. seinem Usernamen und dem Hostnamen).
Vorteile einer selbst erzeugten Message-ID
Am einfachsten wäre es natürlich, die Erzeugung von Message-IDs dem Newsserver zu überlassen; andererseits hat die Erzeugung eigener Message-IDs durchaus Vorteile. Manche Newsreader benötigen das schon deshalb, weil sie eigene Postings auch ablegen und speichern wollen. Ganz allgemein hat eine selbst erzeugte Message-ID aber den Vorteil, dass man (nur) an ihr sehr einfach eigene Beiträge erkennen kann, auch dann, wenn man vielleicht wechselnde E-Mail-Adressen verwendet, und dass man nur so auch Antworten auf eigene Beiträge erkennt (weil diese eine Message-ID mit dem eigenen FQDN im References-Header tragen). So kann ein geeigneter Newsreader bspw. Antworten auf eigene Postings hervorheben, oder man kann sich mit einem Tool wie fupcheck per Mail über Antworten benachrichtigen lassen.
“Sprechende” Message-IDs
Noch schöner finde ich es persönlich, wenn die Message-ID meiner Postings nicht nur global eindeutig ist, sondern auch für den menschlichen Leser verwertbare Informationen enthält. So ist es ja theoretisch nett, dass ich aus der Message-ID <r6d9hs$4cu$1@thangorodrim.ancalagon.de>
herauslesen kann, dass es sich um ein Posting vom Abend des 05.04.2020 handelt - aber natürlich wird kaum je ein Mensch r6d9hs
in eine Dezimalzahl umwandeln, 673416000 hinzuzählen und das Ergebnis dann von einem UNIX-Timestamp in ein Datum konvertieren. Viel praktischer wäre es doch, wenn man einer Message-ID direkt ansehen könnte, von wann sie ist, und am besten vielleicht auch von wo, d.h. aus welcher Newsgroup. Dann muss man sich nicht wundern, wenn mal wieder jemand erst nach Monaten oder Jahren auf einen alten Beitrag antwortet, und man kann auch an einer zitierten Message-ID ungefähr ablesen, wo sie herkommt.
Ich erzeuge daher seit vielen, vielen Jahren unter Hinzunahme meines Hamsters “sprechende” Message-IDs, die aus den Initialen der (ersten) Newsgroup, in der der Beitrag erschienen ist, einem lesbaren Zeitstempel und einem Zähler bestehen. Der Beitrag mit der Message-ID
<dcsn.1908202159.235@meneldor.ancalagon.de>
ist mithin am 20.08.2019 um 21.59 Uhr von meinem Laptop meneldor
aus in der Newsgroup d.c.s.n
erschienen; letzteres kann man mit etwas Erfahrung als de.comm.software.newsreader
(oder auch de.comm.software.newsserver
- natürlich sind Initialen nicht eindeutig!) übersetzen. Außerdem handelte es sich um den 235. Beitrag im Jahr 2019; das ermöglicht mir zudem durch einen schlichten Blick in die Datei, in der der Zähler gespeichert wird, die Gesamtzahl meiner Beiträge im jeweiligen Jahr zu erfassen.
Klar, das ist eine Spielerei, aber eine für mich hilfreiche - und liebgewonnene - Spielerei.
“Sprechende” Message-IDs mit dem INN erzeugen
Nun habe ich in letzter Zeit öfters mal von verschiedenen Rechnern aus mit verschiedenen Newsreadern (nicht nur zu Testzwecken) auf meinem “hauseigenen” INN gepostet. Und da hätte ich dann gerne - auch ohne Hamster - meine sprechenden Message-IDs, die der INN für mich setzen müsste.
Der richtige Ansatzpunkt für solche Manipulationen ist der Filter filter_nnrpd.pl
(bei Debian in /etc/news/filter/
), der nur auf dem Server über den NNRP-Daemon (nnrpd
) gepostete Beiträge bearbeitet, nicht aber etwa ein- und durchgeleitete Beiträge wie der filter_innd.pl
- denn an Message-IDs schon geposteter Beiträge herumzufummeln ist ein absolutes No-Go!
Insbesondere wenn man den Server nicht alleine nutzt, möchte man die Veränderungen an der Message-ID auf eigene Postings beschränken; das kann man zum Beispiel durch einen Filter auf den Domain-Part (also die rechte Seite der Message-ID) lösen oder indem man nur Postings bearbeitet, die einen entsprechenden Zusatzheader (wie bspw. X-Fix-MID: yes
) gesetzt haben.
Im filter_nnrpd.pl
gibt es die Funktion filter_post
; dort sind wir richtig. Am Anfang sollte man auf jeden Fall den Rückgabewert der Funktion auf einen leeren String setzen, denn ansonsten werden Postings mit einer Fehlermeldung abgewiesen. Außerdem muss man das Verändern von Headern freischalten:
my $rval = ""; # assume we'll accept.
$modify_headers = 1;
# [...]
return $rval;
So weit, so gut. An der mit “[…]” markierten Stelle prüfen wir jetzt, ob das Posting modifiziert werden soll oder nicht; wenn ja, rufen wir eine - noch zu schreibende - Funktion dafür auf:
# Fix Message-ID
if (exists($hdr{'X-Fix-MID'})) {
# remove trigger header
delete $hdr{'X-Fix-MID'};
# fix MID
$hdr{'Message-ID'} = fix_mid($hdr{'Newsgroups'}, $hdr{'Message-ID'});
}
Ist die Headerzeile X-Fix-MID
auf irgendeinen Wert gesetzt, entfernen wir sie und setzen dann die Message-ID auf den Rückgabewert der Funktion fix_mid
. Und die sieht wie folgt aus:
#
# Message-ID
#
sub fix_mid($$) {
my ($newsgroups, $mid) = @_;
my ($fqdn, $element, $initial)= '';
# get FQDN
if (defined($mid) && $mid ne '') {
($fqdn) = $mid =~ /<.+\@(.+)>$/;
} else {
$fqdn = $inn::fromhost;
}
# get timestamp
my $datetime = strftime("%Y%m%d%H%M%S",localtime());
# get initials of first newsgroup
my($newsgroup,undef) = split /,/, $newsgroups;
my @newsgroup = split /\./, $newsgroup;
foreach $element (@newsgroup) {
$initial .= substr($element,0,1);
};
# get, increment and save counter
open CTR, '+</var/lib/news/mid-counter';
flock(CTR, 2);
my $counter = <CTR>;
$counter++;
seek(CTR,0,0);
print CTR $counter;
close CTR;
# return Message-ID
return sprintf('<%s.%s.%s@%s>', $initial, $datetime, $counter, $fqdn);
}
Wir übernehmen also den Domain-Part der bestehenden Message-ID; nur wenn es bisher keine Message-ID gibt, nehmen wir den Hostnamen des Newsservers. Dann generieren wir den Zeitstempel, die Initialen der (ersten) Newsgroup und laden und schreiben den Zähler; letzteres ist natürlich mit Plattenzugriffen verbunden. Am Schluss werden die Einzelteile zu einer Message-ID zusammengesetzt.
Voila!
Nachtrag vom 10.01.2021: Die meisten Newsreader nutzen natürlich mehrere parallele Threads zum Lesen und Posten. Das geht dann schief, wenn deshalb verschiedene nnrpd
-Prozesse gleichzeitig auf die Datei /var/lib/news/mid-counter
zugreifen. Daher bedarf es eines exklusiven Locks auf die Datei, schon beim Lesen, damit nicht mehrere Prozesse denselben Zahlenwert erhalten; und weil beim Schließen eines Dateihandles auch die Locks aufgegeben werden, muss die Datei zum Lesen und Schreiben geöffnet werden. Und wenn man das wiederum tut, muss man nach dem Lesen zurück an den Anfang der Datei springen, um den alten Inhalt mit der neuen Zahl zu überschreiben. Den Beispielcode oben habe ich entsprechend angepasst.
(Und wenn man das testen will, sollte man bedenken, dass filter_nnrpd.pl
immer dann, aber auch nur dann neu geladen wird, wenn ein neuer nnrpd
-Prozess startet - dementsprechend also nicht, wenn der Newsreader die Verbindung erst einmal offen hält und weitere Testposts dann über die noch bestehende Verbindung abgesetzt werden.)
Titelbild © Weissblick - stock.adobe.com
Kommentare
Ansicht der Kommentare: Linear | Verschachtelt