Default shell in macOS

Wer ein Gewohnheitstier wie ich ist, will nicht unbedingt von bash nach zsh wechseln, auch wenn es dafür bestimmt gute Gründe gibt. Immerhin hat Apple entschieden, dass wir mit zsh glücklich zu werden haben. Ausserdem lebe ich zwischen den Welten und auf meinen Linux-Installationen ist bash die voreingestellte Shell und es ist schön, dass Skripte auf beiden Plattformen meist ohne grosse Änderungen funktionieren.

Aber immerhin dürfen wir Apple-User noch zu bash wechseln. Ja, sogar zu vielen anderen Shells. Die Datei /etc/shells hält eine Liste an shells vor, die das OS unterstützt:

MyMac:~ eliyah$ cat /etc/shells
# List of acceptable shells for chpass(1).
# Ftpd will not allow users to connect who are not using
# one of these shells.

/bin/bash
/bin/csh
/bin/dash
/bin/ksh
/bin/sh
/bin/tcsh
/bin/zsh

Und da ist sie noch, die gute alte bash shell. Wenn man also diese Shell nutzen will, die ich auch in allen Beispielen hier zu macOS nutze, dann geht das mit diesem einfachen Befehl:

chsh -s /bin/bash

Die aktuell genutzte Shell verrät die Variable $SHELL. Aber die ändert sich erst, wenn man nach dem oben genannten Befehl das Teminalfenster schliesst und neu öffnet:

MyMac:~ eliyah$ echo $SHELL
/bin/bash

Jetzt funktionieren alle Beispiele hier im Blog (hoffentlich) wie beschrieben, die Die Kategorie OS X haben. Aber in den meisten Fällen funktionieren sie wahrscheinlich auch mit zsh.

Default shell in macOS

Instant Webserver

Manchmal braucht man kurzfristig einen Webserver, um eine Datei zu übertragen. Dann kann man natürlich mal eben einen Apache installieren, konfigurieren, Datei hinkopie… okay, lassen wir das.
Es gibt ja das Netzwerk-Schweizer-Taschenmesser-Tool nc, das kann doch helfen!

#!/bin/bash

FILE=$1
PORT=$2

{ echo -ne "HTTP/1.0 200 OK\r\nContent-Length: $(wc -c <$FILE)\r\n\r\n"; cat $FILE; } | nc -l ${PORT:=8080}

Dieses Script, etwa in der Datei instantwww.sh gespeichert, lässt sich einfach aufrufen mit der Datei als erste Option und (optional) einem TCP-Port als zweite.

./instantwww.sh Datei

Auf dem Gegenüber gibt man dann entweder die URL in den Browser ein oder nutzt ein Tool wie curl:

curl -o Datei http://[remote-IP]:8080/Datei

Das funktioniert unter Linux und macOS. Und ja, böse Hacker nutzen das, um Dateien von oder zu einem Opfer zu kopieren. 😱

Instant Webserver

Was ist Deine IP? Part 2

Ich habe vor einiger Zeit mal einen Post geschrieben darüber, wie man eine PHP Seite auf einem Webserver einrichtet, der einem seine eigene IP verrät. Diese Technik ist zuverlässig und kann auch Proxies anzeigen, aber wenn man nur mal eben seine eigene IPv4 wissen will, geht da auch per DNS-Abfrage an opendns:
dig @resolver4.opendns.com myip.opendns.com +short -4

Und damit man sich diesen Befehl nicht merken muss, schreibt man einfach diese Zeile in die Datei .bashrc oder .bash_profile

alias whatsmyip='dig @resolver4.opendns.com myip.opendns.com +short -4'

Jetzt reicht der Befehl whatsmyip und schon bekommt man seine IP angezeigt.

Was ist Deine IP? Part 2

Geleakte Passworte automatisch checken

Passworte aus Datenleaks werden in Passwortdatenbanken aufgenommen, um sie für Wordbook-Attacken zu nutzen. Aber nicht nur Hacker Pflegen diese Datenbanken, auch Securityanbieter. Viele Passwortmanager, vor allem die in Browsern, prüfen die in ihnen gespeicherten Passworte regelmäßig gegen solche Datenbanken.

Nach dem letzen Datenleak bei Twitter war mein Account auch betroffen, also hat mich Safari darauf hingewiesen. Damit dieser Check funktioniert, ohne das Passwort zu übertragen und damit selbst zu kompromittieren, wird ein Hash (Kryptographische Ableitung des Passworts, die man nicht zurück übersetzen kann) des Passworts erstellt und mit einer Datenbank bekannter Hashes verglichen. Wenn man also einen unbekannten Hash vergleicht, kann man keinen Rückschluss auf das eigentliche Passwort ziehen.

Aber auch das hat Probleme: Da man in diesem Bereich niemandem vertrauen darf, kann auch ein vollständiger Hash ein Problem sein. Wenn in Zukunft dieser Hash plötzlich bekannt wird, könnte der Anbieter des Checks nachvollziehen, wer dieses Passwort nutzt. Deswegen hat https://haveibeenpwned.com/Passwords eine Web-API erstellt, der man nur die ersten 5 Stellen des Hashes übermittelt und die dann alle bekannten Hashes zurück gibt, die damit beginnen ohne die ersten 5 Stellen. Diese Liste ist relativ kurz und man kann dann lokal die übermittelten Passwort-Hashes mit dem vollständigen Hash vergleichen und so verstehen, ob das Passwort kompromittiert wurde, ohne den gesamten Hash zu übermitteln und ohne die gesamte Hash-Datenbank (>10GB) jeden Tag herunterzuladen.

Das kann man natürlich automatisieren. Und das geht so:

#!/bin/bash
# create SHA from list of passwords and compare them to haveibeenpwned.com

# in macOS the command to create sha-hashes is different
if [ $(uname) == "Darwin" ]; then
        function sha1sum {
                shasum -a 1
        }
fi
for PAWD in $(cat passwords.txt); do
         PAWDSHA=$(echo -n $PAWD | sha1sum | awk '{print $1}')
         PWND=0
         curl -s https://api.pwnedpasswords.com/range/${PAWDSHA:0:5} | fgrep -i ${PAWDSHA:5} >/dev/null && PWND=1 
         if [ ${PWND} -eq 1 ]; then
              echo "$PAWD was pwnd"
         fi
done

Im Script ist passwords.txt die Datei mit Klartext-Passworten. Das ist natürlich keine gute Idee und dient hier nur als Beispiel, woher die Klartextpassworte kommen. Besser ist es, die Passworte als SHA direkt zu speichern und zu vergleichen. Der echo-Command wiederum sollte ersetzt werden durch ein Script oder eine Funktion, die den oder die Besitzer:in des Passworts informiert, dass das Passwort kompromittiert wurde.

Und so sieht das Script in Aktion aus!

Geleakte Passworte automatisch checken

Filtern in Bash

Manchmal will man aus einer Variable oder einem Text alle Zahlen herausziehen oder verstecken. Das geht natürlich mit sed ganz gut, aber noch einfacher in der Bash selbst.

% VAR=1a2b3c4d
% echo "${VAR//[!0-9]/}"
1234
% echo "${VAR//[0-9]/}"
abcd

Man kann auch nur Buchstaben filtern, damit man Sonderzeichen mit herausfiltert. Oder oder…

% VAR="1a,2b.3c;4d"
% echo "${VAR//[!0-9]/}"
1234
% echo "${VAR//[!a-z]/}"
abcd
% echo "${VAR//[!a-z 0-9]/}"
1a2b3c4d
% echo "${VAR//[a-z 0-9]/}"
,.;
Filtern in Bash

Mehrere Variablen in bash gleichzeitig setzen

Wenn ich mit den Output von einem Befehl mehrere Variablen setzen will, kann ich dafür read und die <<< Dreieckklammern nutzen. So gehts:

% EINS=2
% ZWEI=1
% read EINS ZWEI <<< $({ echo 1; echo 2 ; })
% echo $EINS $ZWEI
1 2

Wenn man auf Google oder Bing nach <<< sucht, findet man nichts. Also, entweder bin ich der erste (!!!11!1!!elf!!11), der darüber schreibt, oder wahrscheinlicher, nach diesem String darf man aus technischen Gründen einfach nicht suchen.

Mehrere Variablen in bash gleichzeitig setzen

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

find nur eins

Ich musste in einem Ordner alle Dateien durchsuchen nach einem String, interessierte mich aber nur dafür, ob es überhaupt eine Datei gibt, die diesen String hat. Leider waren es zu viele Dateien für ein fgrep *, also musste ich mit find und exec arbeiten. Aber da find eine Schleife durch alle gefundenen Dateien macht, muss man es irgendwie nach dem ersten Treffer abwürgen. So gehts:

find . -type f -exec bash -c 'fgrep -lv <string> {} && kill $PPID ' \;
find nur eins