• Willkommen im Linux Club - dem deutschsprachigen Supportforum für GNU/Linux. Registriere dich kostenlos, um alle Inhalte zu sehen und Fragen zu stellen.

E-Mail Header - Server Hops autom. auslesen

Hallo Forum,

ich möchte gerne eingegangene E-Mails (liegen in von cyrus IMAP verwalteten Verzeichnissen) auf im Header enthaltene Server Hops untersuchen und das Ergebnis wegschreiben. Also praktisch ein traceroute für e-Mails.
Ich habe zwar eine Website gefunden, mit der man das online machen könnte, aber ich möchte es bei mir lokal per Shell-Skript ausführen.
Gibt es dafür ein Tool?

Ich vermute, man könnte das mit awk machen. Aber dazu reicht es bei mir nicht.

Die Ausgabe würde ich mir ungefähr so vorstellen:
Code:
Zeitstempel, Empfänger, Absender, Hop n (IP, Zeitstempel), Hop n-1 (IP, Zeitstempel), Hop n-2 (IP, Zeitstempel), ..., Hop 2 (IP, Zeitstempel), Hop 1 (IP, Zeitstempel)

Danke für Tipps.

Gruß,

Radiergummi
 

spoensche

Moderator
Teammitglied
Du kannst die eingehenden e-Mails per Procmail oder Sieve filtern. Erfordert halt ein wenig Einarbeitung.
 
OP
R

radiergummi

Member
spoensche schrieb:
Du kannst die eingehenden e-Mails per Procmail oder Sieve filtern.
Danke für die Antwort, aber die Mails liegen ja schon gefiltert auf dem Server in ihren Verzeichnissen.
Ich möchte gerne wissen, welchen Weg die einzelnen Nachrichten vom Absender bis zu meinem Mail-Provider genommen haben. Diese Information liegt in den Mail-Headern vor, ist aber nur relativ aufwändig Mail für Mail einzeln auszuwerten.
Jetzt suche ich ein Tool, das das machen kann.

Gruß,

Radiergummi
 
radiergummi schrieb:
Gibt es dafür ein Tool?
Ist mir nicht bekannt

radiergummi schrieb:
Die Ausgabe würde ich mir ungefähr so vorstellen:
Code:
Zeitstempel, Empfänger, Absender, Hop n (IP, Zeitstempel), Hop n-1 (IP, Zeitstempel), Hop n-2 (IP, Zeitstempel), ..., Hop 2 (IP, Zeitstempel), Hop 1 (IP, Zeitstempel)

Die Lösung birgt einige Probleme, die mit dem imap fileformat zusammenhängen.
cyrus ist nicht qmail und qmail nicht dovecot. Deshalb Schritt für Schritt.
1. Abtrennen der relevanten Headerinformationen von body, mime_infos und attachements.

Code:
# formail -zx "Received:" < "./irgendein_imap_filename"
Testen mit unterschiedlichen files, mit/ohne attachements, mime text/plain, text/html, mixed, etc.
Prüfen, ob alle Informationen für deine Anforderungen in allen Fällen geliefert werden.
Die Reihenfolge - header wird immer von unter nach oben gelesen - spielt noch keine Rolle.
Ob IP und Servername oder nur Servername vorhanden, ist unwesentlich.
Wenn ok, dann nächster Schritt.

Ich hoffe, dieses Schreiben erscheint nicht arrogant.
Gruß
Gräfin Klara
 
OP
R

radiergummi

Member
spoensche schrieb:
Du kannst die eingehenden e-Mails per Procmail [...] filtern.
Huch, formail ist ja Teil von procmail.
@spoensche: Falls Du das gemeint hattest, dann entschuldige bitte, ich hatte es nicht so verstanden.

@Gräfin Klara

Ich habe mir die Header von einer Auswahl verschiedener Mail-Arten als Textfiles gespeichert (Thunderbird, Quellcode anzeigen, Speichern unter ...).
Diese Dateien habe ich dann mit formail so aufbereitet:
Code:
formail -zx "From:" -zx "Received:" < "./email.txt"

Die Ergebnisse haben gepasst.

Gräfin Klara schrieb:
Ich hoffe, dieses Schreiben erscheint nicht arrogant.
Wie meinen? (ich musste erst mal nachsehen, wer oder was gemeint sein könnte)

Nein, das tut es nicht. Du hast Deinen Dir eigenen Stil und bist sehr hilfsbereit. Danke.

Gruß,

Radiergummi
 

gehrke

Administrator
Teammitglied
radiergummi schrieb:
Gräfin Klara schrieb:
Ich hoffe, dieses Schreiben erscheint nicht arrogant.
Nein, das tut es nicht. Du hast Deinen Dir eigenen Stil und bist sehr hilfsbereit. Danke.
So sehe ich das übrigenz auch. Und ich hoffe, dass wir diesen kleinen Disput über Bande jetzt beilegen können und ihn schon gar nicht in andere Threads hineintragen.
 
radiergummi schrieb:
Ich habe mir die Header von einer Auswahl verschiedener Mail-Arten als Textfiles gespeichert
Ich hoffe, du hast das komplette mail mitsamt attachements und body (und nicht nur den header) gespeichert und getestet.
Ein mail besteht aus:
1, header (From:, To:, Received:, etc) - nur das brauchen wir für dein traceroute
2. mime_info (Content-Type: ... wie ist der nachfolgende body kodiert)
3. body: (Hallo, text)
4. mime_info und andere infos (wie sind die attachements kodiert)
5. attachement(s)

Aus diesen Wust an Daten soll formail NUR definierte Bestandteile des Headers extrahieren.
radiergummi schrieb:
Die Ergebnisse haben gepasst.
ok
Wie willst du nun weiter vorgehen?
Willst du nur einzelne, von dir gespeicherte mails parsen, einen gesamten, lokalen tree oder direkt am imap_server?
 
OP
R

radiergummi

Member
Gräfin Klara schrieb:
Ich hoffe, du hast das komplette mail mitsamt attachements und body (und nicht nur den header) gespeichert und getestet.
Ja, so habe ich das gemacht. Ich hatte irgendwann einmal Probleme mit Mails, die von einem Mac kamen. Da hat TB im Darstellungsmodus "reiner Text" die Anhänge nicht gezeigt. Leider habe ich die nicht wieder gefunden und konnte deshalb diesen Fall mit formail nicht testen.


Gräfin Klara schrieb:
Wie willst du nun weiter vorgehen?
Ich habe das awk-Kapitel in Ditchen's "Shell-Skript Programmierung" schon mal aufgeschlagen, und wenn es keine Einwände gibt, hätte ich mir jetzt mit regular expressions die IP- und/oder Serveradressen und die Zeitstempel je "Received"- Abschnitt herausgesucht, ggf. mit einem
Code:
curl ipinfo.io/eine-ip-adresse
noch zusätzliche Informationen dazugelesen und dann in eine Datei geschrieben.

Das fertige Skript möchte ich über den cyrus Verzeichnisbaum laufen lassen, um überhaupt erst mal einen Überblick zu bekommen.

Da ich beim Thema Mail-Verschlüsselung (site2site) über gnu-anubis gestolpert bin, hätte ich versucht, dieses Skript anubis im MDA-Mode als Posteingangsverarbeitung mitzugeben. So weit bin ich aber noch nicht, jedenfalls wäre das so ungefähr mein Plan.

Bedenken oder Einwände?

Gruß,

Radiergummi
 
Vorab noch eine Frage.
Willst du die Resultate deiner scans im Thunderbird sehen?
Natürlich wäre jedes Resultat fix verbunden mit dem jeweiligen mail, ohne aber das mail selbst zu verändern.
 
OP
R

radiergummi

Member
Gräfin Klara schrieb:
Willst du die Resultate deiner scans im Thunderbird sehen?
Nein. Das Ergebnis wäre eine einfache Datei.


Gräfin Klara schrieb:
Natürlich wäre jedes Resultat fix verbunden mit dem jeweiligen mail, ohne aber das mail selbst zu verändern.
Die Einträge in der Datei referenzieren über Zeitstempel, Sender und Empfänger die Mails eindeutig. Das Skript liest die Mails nur, verändert werden sollen sie dabei nicht. Dabei fällt mir jetzt aber auf, dass ich nicht weiß, ob sich Anubis mit einem eigenen Hop im Header zeigen würde. Das wäre aber nicht schlimm, es sind ja bloß meine eigenen Mails und mit einem solchen Eintrag könnte man obendrein einen erneuten Scan der jeweiligen Nachricht verhindern.


Gruß,

Radiergummi
 
ok
radiergummi schrieb:
Bedenken oder Einwände?
Nicht wirklich

Hinweise:
1. Referenzieren von scan_result zu mail
Dafür würde ich auf jeden Fall die Message-Id verwenden.
Die Message-Id ist unique und ist definiert für diesen Zweck der Referenzierung in den RFC's für pop, tls/ssl und imap.
D.h. auch existierende Systeme können damit umgehen. Weiters ist die Message-Id ein fixer
Bestandteil des Headers und mit formail einfach zu extrahieren.
Ein Beispiel:
File xyz besteht aus {
mit formail extrahiertem header +
Subject
Message-Id
scan_result (als Body)
}
Dieses file kopieren in einen imap folder.
Damit ist dein scan systemweit mit dem mail referenziert und verbunden.

2. Anubis
ist ein Mail Delivery Agent und schreibt sich natürlich in den Header.
Mir ist die Aufgabenstellung von Anubis in diesem Zusammenhang nicht klar.

3. domain
Du wirst draufkommen, dass die domains der Sender eine wichtige Information darstellen.
Also xyz@domain.com. Das würde ich in das Projekt mit einkalkulieren.
Es ist nämlich so, dass der ursprüngliche und wirkliche Server nur über diese domain zu erfahren ist.

4. awk
Würde ich aus mehreren Gründen nicht verwenden.
Scan den gesamten imap folder mit formail und schreib alles in ein /tmp/file
Setze einen Marker für jedes mail, z.B. "!Message-Id: xy"
Für das entgültige parsen dieses files gibt es weit mehr Werkzeuge als für das parsen von Variablen.
Ich schätze das script auf ca. 20 Zeilen. Mit grep und Regex bin ich gerne behilflich.

Super Projekt! Gute Idee.
Auch ich hätte dafür eine sehr interessante Verwendung

Gruß
Gräfin Klara
 
Hier habe ich für dich eine Sammlung von Werkzeugen zusammengestellt, die du benötigen wirst.
Diese habe ich nur aus dem Ärmel geschüttelt und nicht getestet.
Sie kommen der Realität aber mit Sicherheit sehr nahe

Code:
FILE="filename"

Alle email addressen im file
# grep -E -o "\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,6}\b" "$FILE"

Alle IPv4 im file
# grep -E -o "[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}" "$FILE"

Alle domains nach "by" und "from"
# grep -P -o "(by|from) \K[0-9a-z\-\.]+\.[0-9a-z\-\.]+ " "$FILE"

Alle domains von <email@domain.com>
# grep -P -o "<[0-9A-Za-z\-\.]+@\K[0-9a-z\-\.]+" "$FILE"

© Gräfin Klara

Anpassung an deine Erfordernisse sicher notwendig,
aber als Hinweisgeber durchaus brauchbar.

Gruß
 
gehrke schrieb:
Copyright auf einen Forumsbeitrag - was soll das bedeuten?
Das © verpflichtet jeden, der den code wie abgebildet verwendet, mir einen freundlichen Gruß zu hinterlassen.
Für mehr reicht das © hierzulande nicht, das mußte ich schon schmerzlich erfahren. Urheberrecht geht anders.
 
OP
R

radiergummi

Member
Es hat jetzt doch einen Moment gedauert ...

Gräfin Klara schrieb:
Hier habe ich für dich eine Sammlung von Werkzeugen zusammengestellt, die du benötigen wirst.
Danke. Du wirst sie im zweiten Skript wiederfinden.


Hier das Skript, das die Mails durchsieht und die Header-Daten extrahiert.
Code:
#!/bin/bash
# ----------------------------------------------------------------------
# emtr-smd.sh
# scan mail directories
# ist Teil von emtr - e-mail trace route
# muss auf die Mail-Verzeichnisse lesend zugreifen können
# z. B. cyrus organisiert sich in /var/spool/imap ...
#
# bei ca. 170.000 Mails: Laufzeit ca. 15 Minuten, ca. 220MB Daten
#
# siehe hierzu
# https://linux-club.de/forum/viewtopic.php?f=86&t=123337
#
# ----------------------------------------------------------------------

init ()
{
# IMAP Verzeichnis
MailDir="/var/spool/imap"

# Ausgabedatei
FileOut="/irgend/ein/Verzeichnis/scan.txt"

# Zeile, um die einzelnen Header in $FileOut voneinander zu trennen
Trennzeile="### Nachricht Nr:"

# lösche den vorherigen Scan
rm $FileOut

# Zähler für verarbeitete Nachrichten
a=0
}

readImapHeaders ()
{
# lese alle Nachrichten in den Mail-Verzeichnissen 
# in und unter $MailDir und extrahiere die Header-Daten mit formail
# schreibe das Ergebnis in die Datei $FileOut
# ----------------------------------------------------------------------
# ein einfaches 
# 
#      for message in `find "$MailDir" -type f`
#      do
#         formail -zX "Message-ID:" -zX "From:" -zX "To:" -zX "Subject:" -zX "Date:" -zX "Received:" < "$message" >> $FileOut
#      done 
#
# geht nicht, da in den Pfadnamen Leerzeichen vorkommen können
# 
# siehe https://askubuntu.com/questions/343727/filenames-with-spaces-breaking-for-loop-find-command
# ----------------------------------------------------------------------

find "$MailDir" -type f -print0 | 
while IFS= read -r -d '' message
	do
		# Unsinn wegfiltern
		if [[ "$message" != *"Trash"* ]] && [[ "$message" != *"/cyrus."* ]]
		then
			a=`expr $a + 1`
			echo $Trennzeile" "$a" Pfad: "$message >> $FileOut
			formail -zX "Message-ID:" -zX "From:" -zX "To:" -zX "Subject:" -zX "Date:" -zX "Received:" < "$message" >> $FileOut
		fi
	done
}

init
readImapHeaders

Die vom Skript erzeugte Datei (im Beispiel scan.txt) enthält die gewünschten E-Mail-Header-Informationen - aber noch nicht in aufbereiteter Form.


Die Aufbereitung der Daten erfolgt mit dem zweiten Skript, das die Daten aus der Ausgabedatei des ersten Skripts (hier scan.txt) in eine fixe Struktur bringt und damit besser auswertbar macht.
Code:
#!/bin/bash
# ----------------------------------------------------------------------
# emtr-mkdb.sh
# e-mail trace route - make database
#
# - ist Teil von emtr - e-mail trace route
# - muss auf die Ausgabedatei des Skrips emtr-smd.sh lesend zugreifen können
# - stellt ausgewählte Felder der E-Mail-Header-Daten in einem einheitlichen 
#   Format als csv-Datei zur späteren vereinfachten Auswertung zur Verfügung.
#
# liest    $EmtrScan      (Text-Datei)
# schreibt $EmtrDB        (csv-Datei)
#
#
# siehe hierzu
# https://linux-club.de/forum/viewtopic.php?f=86&t=123337
#
# gehört zu emtr.sh und emtr-smd.sh
# ------------------------------------------------

BaseDir="/das/Programm/Verzeichnis/"
EmtrScan="scan.txt"
EmtrDB="emtr-db.csv"
Trennzeile="### Nachricht Nr:"


init ()
{
# wechsele ins Programmverzeichnis
cd $BaseDir

# lösche alte DB
rm $EmtrDB

# EMail-Zähler
msg=0
# Zeilenzähler	
i=0
# Hops-Zähler
hop=0

# Programmstart
Start=1

# Limit zur Begrenzung der verarbeiteten Header-Sets (beim Testen)
# Limit=0 -> kein Limit
Limit=50

# Arrays
typeset -a arrTS
typeset -a arrFromURL
typeset -a arrFromIP
typeset -a arrByURL
typeset -a arrByIP
# Variablen
MessageID="MessageID"
Sender="Sender"
Empfaenger="Empfaenger"
Subj="Subj"
Date="Date"

}

processArrays ()
{
# Arrays in Datei schreiben
echo "\""$MessageID"\";\"HDR\";\""$Date"\";\""$Sender"\";\""$Empfaenger"\";\""$Subj"\";\""$Path"\";\"" >> $EmtrDB
while [ $hop -ge 1 ] ;
do
echo "\""$MessageID"\";\"HOP\";\""$hop"\";\""${arrTS[$hop]}"\";\""${arrFromURL[$hop]}"\";\""${arrFromIP[$hop]}"\";\""${arrByURL[$hop]}"\";\""${arrByIP[$hop]}"\";\"" >> $EmtrDB
hop=`expr $hop - 1`
done

# Arrays löschen
unset arrTS
unset arrFromURL
unset arrFromIP
unset arrByURL
unset arrByIP

# Variablen löschen
unset Path
unset MessageID
unset Sender
unset Empfaenger
unset Subj
unset Date
}

mkdb ()
{
while IFS= read -r Line
	do
	#
	# Vorarbeiten bei neuem Nachrichten-Header
	# --------------------------------------------------------------
		
		# falls es der Beginn eines neuen Headers ist,
		if [[ "$Line" == *"$Trennzeile"* ]]
		then

			# dann verarbeite den vorherigen. Es sei denn,
			# das Programm läuft gerade erst los ($Start= 1)
			if [[ $Start -eq 0 ]]
			then 
				processArrays

				# falls ein Limit gesetzt ist, stoppe das Skript entsprechend
				if [[ $Limit -gt 0 ]] && [[ $msg -eq $Limit ]]
				then
					echo "Abbruch, Limit erreicht."
					exit
				fi
			fi
			# Nachrichtenzähler +1
			msg=`expr $msg + 1`
			# Zeilenzähler auf 0
			i=0
			# Hop-Zähler auf 0
			hop=0
			# Pfad zur Nachricht
			s="/"								# Suchsstring, der Pfad beginnt mit "/"
			x=${Line#*$s}						# Rest ab erstem Auftauchen des Suchstrings
			p=$(( ${#Line} - ${#x} - ${#s} ))   # Position für Rest incl. Suchstring
			Path=${Line:$p}						# Rest incl. Suchstring, -> der Pfad
		fi
		
		i=`expr $i + 1`

		# wenn Hop gefunden (wenn "Received" in der Zeile steht),
		# erhöhe Anzahl um 1 
		# $hop dient als Index der Arrays

		if [[ "$Line" == *"Received:"* ]]
		then
			hop=`expr $hop + 1`
		fi
		
	#
	# ab hier erfolgt die Verarbeitung der Zeilen
	# bis zum nächsten Nachrichten-Header
	# --------------------------------------------------------------
		
		# suche Zeitstempel
		unset TS
		# erkannt wird 
		# 15 Feb 2017 08:06:06 +0100
		TS=`echo $Line | grep -E -o "\b([MTWFSonuedriat]{3}), (3[0-1]|2[0-9]|1[0-2]|0[1-9]) [JFMASOND][anebrpyulgpctov]{2} ([12][0-9]{3}) ([01][0-9]|2[0-4]):([0-5][0-9]):([0-5][0-9]) [+-][0-9]{4}?\b"`
		# noch nicht erkannt werden:
		# 15 Feb 2017 07:06:06 UT
		# 15 Feb 2017 08:06:06 +0100 (CET)
		if [[ -n $TS ]]
		then
			arrTS[$hop]=$TS
		fi

		# suche "from" Server-URL und -IP
		unset Furl
		Furl=`echo $Line | grep -P -o "from (localhost|\K[0-9a-z\-\.]+\.[0-9a-z\-\.]+ )"`
		if [[ -n $Furl ]]
		then
			arrFromURL[$hop]=$Furl
			unset Fip
			Fip=`echo $Line | grep -E -o "[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}"`
			if [[ -n $Fip ]]
			then
				arrFromIP[$hop]=$Fip
			fi
		fi

		# suche "by" Server-URL und -IP
		unset Burl
		Burl=`echo $Line | grep -P -o "by \K[0-9a-z\-\.]+\.[0-9a-z\-\.]+ "`
		if [[ -n $Burl ]]
		then
			arrByURL[$hop]=$Burl
			unset Bip
			Bip=`echo $Line | grep -E -o "[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}"`
			if [[ -n $Bip ]]
			then
				arrByIP[$hop]=$Bip
			fi
		fi
		
		# lese formail-Felder

		if [[ "$Line" == *"Message-ID:"* ]] 
		then
			MessageID=${Line:12}
			MessageID=${MessageID%?}     # ^M am Stringende entfernen
		fi
	
		if [[ "$Line" == *"From:"* ]] 
		then
			Sender=`echo $Line | grep -E -o "\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,6}\b"`
		fi
	
		if [[ "$Line" == *"To:"* ]] 
		then
			Empfaenger=`echo $Line | grep -E -o "\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,6}\b"`
		fi
	
		if [[ "$Line" == *"Subject:"* ]] 
		then
			Subj=${Line:9}
			Subj=${Subj%?}        # ^M am Stringende entfernen
		fi

		if [[ "$Line" == *"Date:"* ]] 
		then
			Date=${Line:6}
			Date=${Date%?}        # ^M am Stringende entfernen
		fi

		
		# ab hier sind wir nicht mehr am Programmstart. Die Sonderbehandlung des
		# ersten Durchlaufs kann entfallen
		Start=0
		
	done < "$EmtrScan"

}


init
if [[ $Limit -gt 0 ]]
then
	echo "Limit gesetzt, Abbruch nach "$Limit" Headern."
fi
mkdb


Schwierigkeiten gibt es noch bei der Erkennung der unterschiedlichen Formate der Zeitstempel, genauer, der Zeitzonenangaben.

Code:
das geht:
15 Feb 2017 08:06:06 +0100

noch nicht erkannt werden:
15 Feb 2017 07:06:06 UT
15 Feb 2017 08:06:06 +0100 (CET)

Die Laufzeiten will ich noch berechnen und die Anreicherung mit Geo-Daten fehlt auch noch.

Keine Idee habe ich für folgenden Punkt: für jeden Hop wird auch der jeweilige "Service" angegeben, der den Hop erzeugt hat. Das sind Ausdrücke oder Teile davon, die in runden Klammern stehen. Leider gibt es aber noch viele andere Elemente, die auch in runden Klammern stehen, so dass ich das nicht als Kriterium heranziehen kann.

Beispiele für diese "Services" wären:
Code:
([unix socket])
(Cyrus v2.4.18)
(Postfix)
(amavisd-new, port 10024)
(fetchmail-6.3.26)
(Exim 4.85_2)
(TLSv1.2:DHE-RSA-AES128-SHA:128)
...


Es wird ...

Anmerkungen und Kritik sind hochwillkommen.

Danke und Gruß,

Radiergummi
 
radiergummi schrieb:
Großartig! Sieht super aus!!

radiergummi schrieb:
Keine Idee habe ich für folgenden Punkt: für jeden Hop wird auch der jeweilige "Service" angegeben, der den Hop erzeugt hat....
Kannst du (xxx) nicht verwerfen, wenn keine eckigen Klammern [] innerhalb?
Bin mir leider nicht sicher ..
Meines Wissens stehen in () nur USER_AGENT und SERVER_AGENT wie thunderbird ... oder qmail ..

radiergummi schrieb:
Schwierigkeiten gibt es noch bei der Erkennung der unterschiedlichen Formate der Zeitstempel, genauer, der Zeitzonenangaben.
Alle Formate (egal ob erzeugt unter Linux oder Windows) verwenden dasselbe, alte Unix library.
Das Binary "date" verwendet es auch, also müßte es umgekehrt von "datum xx Zone" nach epochtime auch funktionieren.
Nimm auch date anstatt alles mit regex
Code:
# date -d "15 Feb 2017 07:06:06 UT" +%s
bzw.
TS="$(date -d 'irgend_ein_zeitformat' +%s)"
Sollte funktionieren.

Bin beeindruckt von deiner Arbeit!
 
OP
R

radiergummi

Member
Ich wollte nur kurz Feedback geben.
Momentan kann ich an der Sache nicht gescheit weiterarbeiten.
Ich melde mich, wenn es hier weiter geht.

Danke und Gruß,

Radiergummi
 
Oben