HTML emails mit mail.mailutils

Das Programm mail.mailutils (meist einfach mail) ist auf vielen Linux-Distributionen installiert. Man kann damit über die Shell Emails verschicken, was sehr praktisch ist für scripte und cron-jobs, die den Admin per Mail informieren sollen.

Manchmal will man auch mal ein wenig mit HTML spielen und sei es auch nur, um um den Text ein <pre> zu setzen, damit er mit einer Monospace-Schrift angezeigt wird und ASCII-Art und Tabellen ordentlich dargestellt werden.

So schickt man HTML:

# generiere random boundary
BOUNDARY=$(date | md5sum | head -c8)
echo "
--_$BOUNDARY_
Content-Type: text/html; charset=\"us-ascii\"
Content-Transfer-Encoding: quoted-printable

<html><pre>
Meine Tabelle

+-------+----------+
| id    | analysis |
+-------+----------+
| 36190 |      219 |
| 36192 |      219 |
| 36273 |      219 |
| 36275 |       -1 |
| 36276 |       -1 |
| 36289 |      219 |
| 36293 |      122 |
| 36371 |      219 |
| 36388 |      219 |
| 36393 |      219 |
| 36394 |       -1 |
| 36395 |       -1 |
| 36708 |      122 |
+-------+----------+
</pre></html>
--_$BOUNDARY_--

" | mail.mailutils -s "Mein Subject" \
-a Content-Type:"multipart/alternative; boundary="\"_$BOUNDARY_\"" \
-a From:mein@absender.tld mein@empfaenger.tld

Diese Tabelle sieht jetzt sauber aus, wenn sie den Empfänger erreicht. Die Leerzeilen vor und nach dem Boundary und/oder Headern sind wichtig!

Wenn ich ein Bild in das HTML einbetten will, wird es komplizierter. Das mail-Tool kann zwar mit -A Anhänge verschicken, aber die kann ich dann nicht im HTML nutzen, da ich die dazugehörige ID nicht kenne.
Also muss ich das händisch machen, damit ich alle Parameter kontrollieren kann. Da Email ein 7-Bit Medium ist, muss ich aus dem Binär-Bild ASCII machen und dann noch dem Mailprogramm erklären, wie es das ASCII wieder in eine Binär-Bilddatei zurückverwandelt. Ich benutze hier base64 dafür, da das standardmässig installiert ist und so ziemlich jedes Mailprogramm das versteht. Ausserdem muss ich dem Bild einen Namen geben, denn der Dateiname geht verloren beim encoden in base64, und dazu eine ID, mit der ich darauf verweisen kann im HTML-Code der Mail. So gehts:

# generiere random boundary und Image ID
BOUNDARY=$(date | md5sum | head -c8)
IMAGEID=$(date | md5sum | head -c10 | rev)
# meine Bilddatei
IMAGEFILE=graph.png
( echo "
--_$BOUNDARY_
Content-Type: text/html; charset=\"us-ascii\"
Content-Transfer-Encoding: quoted-printable

<html>
Mein Graph als Bild:<br>
<img contenttype=3D\"image/png\" src=3D\"cid:$IMAGEID\"><br>
Sieht gut aus!

</pre></html>

--_$BOUNDARY_
Content-Type: image/png;name=\"$IMAGEFILE\"
Content-Disposition: attachment;filename=\"$IMAGEFILE\"
Content-ID: <$IMAGEID>
Content-Transfer-Encoding: base64 
"
base64 $IMAGEFILE 
echo "
--_$BOUNDARY_--" ) |  mail.mailutils -s "Mein Subject" \
-a Content-Type:"multipart/related; boundary="\"_$BOUNDARY_\""; type="\"multipart/alternative\""" \
-a MIME-Version:"1.0" \
-a From:mein@absender.tld mein@empfaenger.tld

Es gibt ausser mail.mailutils noch viele andere Commandline-Mailer wie etwa mutt oder mailx. Mit Hilfe der Beispiele oben kann man auch diese Mailer dazu bringen, HTML zu verschicken. Einfach mal gucken, welche Tools auf dem System installiert sind. Auch OS X kann das.

HTML emails mit mail.mailutils

DNS und TTL

Ich kann ja verstehen, dass Google und auch OpenDNS mit ihren öffentlich zugänglichen Resolvern sich nicht so ganz an die Regeln halten (siehe unten). Sie behaupten zwar das Gegenteil:

Does Google Public DNS comply with the DNS standards set forth by the IETF?
Yes.

Aber so ganz stimmen tut das meines Erachtens nicht. Ich musste einen Dienst von einem Server auf einen anderen umziehen. So bin ich vorgegangen:

  1. Am Tag vorher TTL auf 300 Sekunden (5 Minuten) für den A record heruntersetzen und Seriennummer aktualisieren
  2. Am Tag der Umschaltung auf den Webservern (um die geht es hier) eine Baustellenseite einrichten
  3. Den DNS record umstellen, Zone neu laden und auch auf dem secondary einen Zonenrefresh anstossen
  4. Prüfen, dass die beiden autoritativen DNS Server richtig reagieren
  5. warten, dass 8.8.8.8 endlich auch die neuen Daten liefert

Dummerweise hatte ich vergessen, noch einen Hostnamen für den alten Server einzurichten, also alt.meine-domain.tld, damit ich dort noch Zugriff auf die Seite habe. Also:

  1. DNS Eintrag für A record alt eintragen

Mein SOA sieht so aus:

;; ANSWER SECTION:

meine-domain.tld. 86400 IN SOA adns1.eu-rack.com. ns.eu-rack.co.il. 2016081801 28800 7200 604800 86400

Der A record für www so:

;; ANSWER SECTION:

www.meine-domain.tld. 300 IN A 11.12.13.14

Passt also. Aber: 8.8.8.8 macht aus den 300 Sekunden eine Zufallszahl kleiner 300. Und sowohl der neue A record Eintrag für alt. und der veränderte A record für www. wurden erst nach über vier Stunden aktiv! Dabei habe ich auf der von Google bereitgestellten URL den Cache „geflushed“, wie sie es nennen: https://developers.google.com/speed/public-dns/cache

Vier Stunden passen mit keinem der Zeiten in meinem SOA zusammen. Dort gibt es 24h, 2h und noch andere, aber keine 4h. Alles ziemlich doof. Der Grund ist wohl, dass sie damit effektiv DNS tunneling und DNS DDoS Angriffe vereiteln. Aber dass es mir so das Leben zur Hölle macht, ist doch auch nicht wirklich cool.

iptables auf dem alten Server hat mir dann den Arsch gerettet. Alle Anfragen auf die IP werden weiter geleitet. Das ging aber nur, weil ich eine eigene IP für diese Webseite hatte und keine anderen Vhost darüber liefen:

echo "1" > /proc/sys/net/ipv4/ip_forward
iptables -t nat -A PREROUTING -d [OLD_IP] -p tcp --dport 80 -j DNAT --to-destination [NEW_IP]:80
iptables -t nat -A PREROUTING -d [OLD_IP] -p tcp --dport 443 -j DNAT --to-destination [NEW_IP]:443
iptables -t nat -A POSTROUTING -j MASQUERADE

Als Google dann sich endlich erbarmt hat, habe ich sie wieder gelöscht mit

iptables -t nat --flush

Den Eintrag für alt. konnte ich natürlich in der Zeit vergessen.

Gibt es eine Möglichkeit, dafür zu sorgen, dass das beim nächsten Umzug nicht wieder passiert? Wie bekomme ich Google und OpenDNS dazu, sich so zu verhalten, wie es sich gehört?

DNS und TTL

Frage: MAC Tabelle in Linux?

cisco_3548xl_mac_after

Wo ist die Tabelle mit allen MAC Adressen, die in letzter Zeit mit meiner Linux-Kiste kommuniziert haben?

Es geht nicht um die arp Tabelle. Die bekomme ich mit

arp

oder dem neuen Befehl

ip neigh show

heraus. Es geht um das, was auf einer Cisco der Befehl

show mac-address-table

auswirft. Etwa, wenn ich einen transparenten Proxy baue und wissen will, welche MAC hinter welchem Interface ist oder wenn ich einfach nur sehen will, welche MAC-Adressen ohne IP was von mir gewollt haben könnten.

Ich habe mich im /proc totgesucht, das ethtool befragt, Tante Google gequält, Kollegen ausgequetscht und es hat alles nichts gebracht. Wer hat eine Idee?

Frage: MAC Tabelle in Linux?

bye bye, netstat

Mit netstat kann man sehen, welche Verbindungen gerade aktiv sind und auf welchen Ports ein Prozess horcht.

Aber netstat ist langsam. Ich habe einen Server, auf dem einige 10k TCP Verbindungen offen sind und der trotz fantastischer CPU und RAM Ausstattung geschlagene 2 Minuten braucht, um netstat auszuführen (mit -n, also ohne Namensauflösung!).

Deutlich schneller, nämlich unter einer Sekunde, schafft das der Nachfolger ss, was wohl eine Abkürzung von socket stat oder so ähnlich sein soll. Die manpage sagt nur „another utility to investigate sockets“.

Das Gute ist, es versteht alle Optionen von netstat. Wer also historisch bedingte Schwierigkeiten hat, ss zu tippen oder bloss ein Gewohnheitstier ist, macht sich einfach ein alias in der .bashrc:

alias netstat=ss
bye bye, netstat

sudo-matisch

Mit sudo kann man mit den Rechten eines anderen Users Befehle ausführen. Üblicherweise ist das root und wird gebraucht, um ohne root zu sein, root-Rechte zu bekommen. Andere User gehen aber so auch: sudo -u username <command>

Welche Benutzer mit sudo andere Rechte erlangen dürfen, steht in der Datei /etc/sudoers, die man nur mit dem Befehl visudo bearbeiten sollte. Je nach OS und Distribution findet man hier andere Voreinstellungen. Auf meinem Mac habe ich mir etwa einen unpriviligierten User angelegt (ist sicherer), aber mir in der sudoers-Datei trotzdem alle Rechte zugestanden (ist komfortabler).

# User privilege specification
eliyah ALL=(ALL:ALL) ALL

Aber sudo lässt sich auch für andere Dinge nutzen, etwa Scripte. Man will nicht immer ein Script, das für nur einen Befehl root-Rechte benötigt, komplett als root laufen lassen. Etwa, weil es von einem anderen Prozess, der selbst keine root Rechte besitzt, getriggert wird. Ein Beispiel sind cgi’s für den Webserver. Ein anderes sind per ssh remote ausgeführte Kommandos.

Ausserdem will man in scripten oder remote kein Passwort eintippen müssen, wenn man per sudo Befehle ausführt, will aber in seinem System nicht gleich für alle Tür und Tor öffnen.

Der Trick ist, Befehle zu definieren, die ein User ohne Passwort ausführen darf. Das geht über die Cmnd_Alias Funktion. Hier im Beispiel eine Befehlsdefinition, der es einem User erlaubt, Passworte für andere User zu setzen und einer, der erlaubt, per tcpdump Traffic mitzulesen:

# Cmnd alias specification
Cmnd_Alias PASSWD = /usr/bin/passwd [A-z]*, !/usr/bin/passwd root
Cmnd_Alias TCPDUMP = /usr/sbin/tcpdump

Was man hier schön sehen kann ist, dass man nicht nur Programme, sondern auch Parameter bestimmen kann, die erlaubt sind. Mit dem Ausrufezeichen (!) kann man auch Dinge wieder ausschliessen, hier eben das Ändern des Passwortes für den User root.

Wenn der Befehlalias definiert ist, kann man bestimmten Usern diese Befehle erlauben, sogar ohne Eingabe des Passwortes.

# User privilege specification
eliyah ALL=(ALL:ALL) PASSWD
eliyah ALL=(ALL) NOPASSWD: TCPDUMP

Die verschiedenen ALLs erlauben, von wo aus man das darf. Wie im Detail, weiss ich nicht, interessiert mich auch nicht. Denn ich kann jetzt ein Script schreiben, das als User eliyah läuft und mit Hilfe von sudo ohne Passwortabfrage einen tcpdump machen kann.

sudo-matisch

OS X: Wer lauscht denn da?

Unter GNU Linux gibt es den Befehl netstat, mit dem man sich nicht nur anzeigen lassen kann, welche Ports gerade aktiv sind. Mit dem Parameter -pl (p für Prozess-ID und l für listen) sieht man auch, welcher Prozess gerade eine Tür geöffnet hat. Unter OS X ist das nicht ganz so einfach. Dort gibt es die BSD Variante von netstat, und die funktioniert anders. Mit folgendem Befehl sieht man, welche Ports geöffnet sind:

netstat -a | fgrep LISTEN

Bei mir erscheint da unter anderem etwa folgender Eintrag:

tcp4 0 0 192.168.2.2.54045 *.* LISTEN

Wer macht denn da Port 54045 auf und habe ich das erlaubt? Ich muss feststellen. Ja habe ich:

% lsof -nP -iTCP:54045 -sTCP:LISTEN
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
Skype 36967 eliyah 30u IPv4 0xf42576a198a9cfbb 0t0 TCP 192.168.2.2:54045 (LISTEN)

Denn ich habe Skype installiert, und das macht bekanntlich, was es will. Der Befehl lsof (List Open Files) bringt es zum Vorschein.

Ich habe bei meinem Mac auch ssh aktiviert, und wenn ich schaue, welcher Prozess diesen Dienst bereit stellt, kommt überraschendes zu Tage:

% sudo lsof -nP -iTCP:22 -sTCP:LISTEN
Password:
COMMAND PID USER   FD   TYPE             DEVICE SIZE/OFF NODE NAME
launchd   1 root   50u  IPv6 0xf42576a168a5fb6b      0t0  TCP *:22 (LISTEN)
launchd   1 root   51u  IPv4 0xf42576a168a676eb      0t0  TCP *:22 (LISTEN)
launchd   1 root   53u  IPv6 0xf42576a168a5fb6b      0t0  TCP *:22 (LISTEN)
launchd   1 root   54u  IPv4 0xf42576a168a676eb      0t0  TCP *:22 (LISTEN)

Das sudo ist nötig, da hier ein Port unter 1023 abgefragt wird. Der ssh-Dienst wird vom launchd selbst bereitgestellt. Das funktioniert ähnlich wie inetd oder xinetd unter Linux, nur dass launchd deutlich weitreichendere Aufgaben hat, was man schon an seiner Prozess-ID 1 erkennt.

OS X: Wer lauscht denn da?