Rudi-Shell

“Rudi” steht für rudimentär, denn wir haben es mit einem rudimentären Shell-Ersatz via CGI-Interface zu tun. Ich hatte angefangen, ihn recht komplex zu gestalten und mich dann eines Besseren besonnen: Small is beautiful. Rudi kann inzwischen nicht weniger als ursprünglich geplant, aber die Oberfläche ist puristischer als noch während der ersten Entwicklungsphase. Nicht zuletzt sind auch die CGI-Quelldateien inzwischen viel kleiner und einfacher geworden. Puristischere Oberflächen erfordern andererseits etwas mehr Dokumentation, also beschreibe ich im Folgenden, teils bebildert, was man mit Rudi so anstellen kann - kurz: alles!

Feature-Übersicht

Mit Rudi kann man direkt über eine Weboberfläche Folgendes tun:

Systemvoraussetzungen

Ich habe die Rudi-Shell entwickelt für den Danisahne-Mod, genauer gesagt für Olivers (olistudent) Version mit Kernel 2.6. Getestet habe ich konkret auf meiner FritzBox Fon WLAN 7170 mit Firmware 29.04.29, ds-0.2.9_26-13, Kernel 2.6.13.1, Busybox 1.4.1, Haserl 0.9.16 CGI-Handler. Grundsätzlich sollte meiner Meinung nach aber Folgendes ausreichen (ohne DS-Mod/Freetz):

Server

Client

Was NICHT gebraucht wird

Platzbedarf der Rudi-Shell

Funktionsweise

Freetz Menü

Der Einstieg in die Rudi-Shell erfolgt in Freetz übers Hauptmenü (siehe Bild). Direkt kann sie ebenfalls erreicht werden, und zwar über http://fritz.box:81/cgi-bin/rudi_shell.cgi.

Die Oberfläche präsentiert sich spartanisch, aber funktionell und besteht aus folgenden Elementen:

Achtung, besonders wichtig: Die Rudi-Shell, insbesondere die Historie, funktioniert ohne Navigation auf der Hauptseite. D.h., Sie brauchen weder die Schaltflächen “Vor/Zurück” noch “Neu laden” des Browsers. Im Gegenteil, wenn Sie sie benutzen, werden erstens die Historie gelöscht und zweitens alle Schalter und Textfelder auf ihre Standardwerte zurückgesetzt.** Für die Techniker unter uns: Alles, was an Rudi-Shell dynamisch ist, passiert in einem unsichtbaren IFrame bzw. auf der Hauptseite durch javascript-basierte Änderungen am DOM der Seite.

Illustrierte Anwendungsfälle

Shell-Skript ausführen

Befehl eingeben, ausführen, Ergebnis anschauen - fertig.

Rudi-Shell: Shell-Skript ausführen

Historie verwenden

Historien-Liste öffnen, per Maus oder Tastatur durchblättern. Beim Blättern aktualisieren sich schon Befehlsfenster und Ergebniskonsole. Durch Drücken des Knopfes “Hist. löschen” wird die Historie bereinigt und ist wieder jungfräulich.

Rudi-Shell: History

Download Tar-Archiv

Man kann mittels eines Befehls wie tar c * ein Archiv erzeugen, im Beispiel von allen Dateien und Unterverzeichnissen im aktuellen Verzeichnis - übrigens bei Shellskript-Start immer /usr/mww/cgi-bin, weil von dort aus die CGI-Skripten ausgeführt werden. Wenn man zusätzlich den Download-Schalter vor dem Ausführen aktiviert und evtl. zur Bequemlichkeit noch den Dateiendung-Schalter .tar, erhält man das Archiv direkt als Download, den man speichern kann, wohin man will. Was hier technisch passiert, ist, daß die Standardausgabe der Shell - wir haben ja keine Zieldatei für Tar angegeben, also gilt die Standardausgabe - umgeleitet wird, und zwar in den Rückgabekanal des CGI-Skripts hin zum Browser des Benutzers.

Trouble-Shooting: Manchmal kommt es vor, daß Tar (oder auch andere Shell-Befehle) zusätzlich zur eigentlichen Ausgabe noch Meldungen (Fehler, Informationen, Warnungen) in einen anderen Ausgabekanal, die sog. Fehlerausgabe, schreiben. Da ohne weitere Vorkehrungen bei der Rudi-Shell - wie bei anderen interaktiven Shells auch - Standard- und Fehlerausgabe gebündelt werden, landen evtl. Informationen im Download, die wir dort gar nicht haben wollen. Im Falle unseres Tar-Archivs wird dadurch die Datei “verunreinigt” und ist nicht mehr entpackbar. Um das zu diagnostizieren, lassen wir uns das Tar-Archiv einfach kurzerhand auf die Konsole ausgeben (Download-Schalter deaktivieren). Das sieht dann so aus:

Rudi-Shell: Tar Diagnose

Es gibt im Prinzip zwei Möglichkeiten, solche Verunreinigungen zu umgehen: Erstens kann man die Shell explizit anweisen, die Fehlerausgabe umzuleiten in eine Datei, auf eine andere Konsole oder ins Nirgendwo (/dev/null, das beliebte Faß ohne Boden), wie im folgenden Beispiel:

Rudi-Shell: Fehlerausgabe ins Nirvana umleiten

Die zweite (etwas unsicherere, da nicht immer vorhersehbare) Möglichkeit besteht schlicht in der Vermeidung von Fehlerausgaben, indem man vorher die Syntax von Befehlen, notwendige Berechtigungen etc. prüft. In unserem Fall kann man die Meldung vermeiden, indem man vorher in das passende Basisverzeichnis wechselt, aus dem heraus Tar operieren soll:

Rudi-Shell: Fehlerausgabe durch vorbereitende Schritte vermeiden

Download langer Konsolenausgabe

Lange Konsolenausgaben eines Skripts werden von Rudi auf knapp 64 KB gekürzt, weil je nach Browser das Umkopieren mehrerer hundert KB langer Ausgaben aus dem unsichtbaren IFrame, worin die Original-Ausgabe zunächst landet, den Browser extrem ausbremst. Außerdem ist solch ein langer Text im Browser nur schlecht zu analysieren, das sollte man lieber offline in einem leistungsfähigen Editor mit guten Suchfunktionen machen. Ein Beispiel für eine lange Ausgabe ist z.B. ls -leAR /, also die detaillierte Anzeige sämtlicher Dateien mit vollem Datum usw, rekursiv beginnend im Wurzelverzeichnis.

Es gibt browserbedingte Unterschiede, die beeinflussen, wieviel von diesen 64 KB tatsächlich beim Umkopieren im Hauptfenster ankommen. Der Internet Explorer schneidet gar nichts ab (er würde auch 1 MB umkopieren und daraufhin, warum auch immer, für eine Weile blockieren). Opera kappt den Text bei 32 KB, Firefox bereits bei 8 KB. Nun sind 8 KB nicht sehr viel, aber in den meisten Fällen ausreichend für normale Befehle. Im Hinblick auf die Historie, welche ja u.U. sehr viele Befehle mit zugehörigen Ausgaben speichern muß, ist die Begrenzung auch gesund.

Wer nun also eine lange Konsolenausgabe in voller Länge genießen will, aktiviert einfach den Download-Schalter und lädt sich das Ganze als Textdatei herunter:

Rudi-Shell: Lange Konsolenausgabe als Datei-Download

Datei-Upload

Das Ausführen von Befehlen und das Herunterladen von Dateien und Konsolen-Ausgaben sind bereits mächtige Werkzeuge, aber richtig Spaß macht die Arbeit mit der Rudi-Shell erst durch die Möglichkeit, beliebige Dateien hochzuladen. Das geht ganz einfach:

Rudi-Shell: Erfolgreicher Datei-Upload

Für die Techniker: Es handelt sich um einen normalen Form-based Upload gemäß RFC 1867.

Nun kann beim Upload auch mal etwas schief gehen. Dann benötigt man eine entsprechende, einigermaßen informative Rückmeldung des Systems. Auch hierfür ist gesorgt - das sieht dann so aus:

Rudi-Shell: Fehler beim Datei-Upload

Grenzen & Einschränkungen

Wie erwähnt, gibt es kaum Grenzen bei dem, was man mit der Rudi-Shell auf und mit der Box anstellen kann. Was man noch verbessern könnte, wäre, dem Benutzer eine interaktive Shell-Session zu geben, welche den Aufruf eines Skripts überdauert. Das würde z.B. bedeuten, daß ein Verzeichniswechsel in Skript A noch Wirkung hätte für das anschließend ausgeführte Skript B und nicht nur innerhalb von A. auch würden in A geänderte oder gesetzte Umgebungsvariablen noch in B gelten etc. Das zu erreichen, wäre nicht besonders schwierig. Alles, was man dazu bräuchte, wäre ein virtuelles Terminal. Ich habe beispielsweise Screen installiert. Das Schöne an diesem Werkzeug ist, daß man es nicht nur interaktiv an der Konsole benutzen kann, sondern auch einer Detached Session, also einer vom Terminal abgetrennten Benutzersitzung, per Fernsteuerung Befehle schicken kann, die innerhalb der Sitzung ausgeführt werden. In Verbindung mit einer Log-Datei oder einer Named Pipe könnte man dann von außen die Ausgaben lesen und an den Web-Client weiterreichen. Genau dieses Feature hatte ich ursprünglich implementieren wollen, aber das war auch noch, als ich an einzeilige Kommandos anstatt an ein mehrzeiliges Skript-Fenster dachte. Inzwischen finde ich es so einfacher, eleganter und vor allem schlanker. Benutzer kleiner Boxen brauchen nicht extra Screen in die Firmware einzubauen, nur um ein klein wenig mehr Komfort in der Benutzersitzung zu haben. Potentielle Probleme mit Mehrfach-Logins in dieselbe Screen-Sitzung etc. müßten auch behandelt werden. Ich brauche das nicht. Wer es haben will - die Idee habe ich ja formuliert, der Weg ist gewiesen. Ausprobiert habe ich die Screen-Fernsteuerung via Kommandozeile bereits, sie funktioniert gut.

Was geht noch alles nicht? Na ja, es macht kaum Sinn, Programme aufzurufen, die ohne Benutzerinteraktion keinen Sinn ergeben, also z.B. den Midnight Commander (mc), tail -f, seitenweise Ausgaben mit more oder top in der nicht automatisch terminierenden Variante. Von interaktiven Editoren wie vi rede ich jetzt mal gar nicht - es gibt ja sed und awk für kommandozeilenbasiertes Editieren. Für interaktives gibt es Down- und Uploads (dazwischen offline am Client editieren, klar).

Tips & Tricks

Sicherer Zugriff via HTTPS

Achtung: Diese Anleitung wurde geschrieben bevor AVM eine eigene Fernzugrifffunktion integriert hat, weshalb die hier auch nicht erwähnt wird.

Jetzt haben wir endlich eine Shell, die über ein reines Web-Interface läuft, sind also unabhängig von Telnet, SSH & Co. Was jetzt noch schön wäre: sicherer Zugriff auf die Rudi-Shell oder am besten gleich die ganze Freetz-Konfiguration von außen über eine SSL-gesicherte Verbindung. Wir könnten dann aus dem Büro oder von jedem Internet-Café der Welt aus unseren Router konfigurieren, ohne auf Proxies oder Portbeschränkungen Rücksicht nehmen zu müssen. Port 443 für HTTPS ist bei 99% aller Proxies freigeschaltet. Wir bräuchten keinen SSH-Client und hätten trotzdem eine sicher verschlüsselte Verbindung vom Browser zum Router.

Genau das konfigurieren wir uns jetzt! Zunächst brauchen wir eine Firmware mit dem Paket stunnel. Das Paket aktiviert übrigens automatisch die beiden Shared Libraries für OpenSSL, nämlich libcrypto.so und libssl.so. Außerdem wird die zlib (libz.so) benötigt. Weitere Tips und Infos hierzu:

Als nächstes brauchen wir für den HTTPS-Server ein Serverzertifikat und ein Schlüsselpaar. Das bauen wir uns selbst mit OpenSSL. Das geht unter Linux prinzipiell genauso wie unter Windows, man sollte nur vorsichtshalber darauf achten, daß die Schlüsseldatei, welche am Ende auf der Box landet, UNIX-Zeilenenden hat. Ich erkläre im Folgenden, wie die Generierung eines Self-Signed Key unter Linux abläuft. Es genügt, folgendes Skript auszuführen:

    #!/bin/bash

    # Paßwortgeschützten Server Key erzeugen
    openssl genrsa -des3 -out server.key 1024

    # Ungeschützte Version extrahieren (der SSL-Server kann ja nicht
    # vor der Benutzung selbst ein Paßwort eingeben)
    openssl rsa -in server.key -out server.key.unsecure

    # Certificate Signing Request (CSR) mit persönlichen Daten erzeugen
    openssl req -new -key server.key -out server.csr

    # Ein Jahr gültiges, selbst signiertes Zertifikat anfordern
    openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt

    # CSR wird nicht mehr benötigt
    rm server.csr

    # Schlüssel + Zertifikat (in dieser Reihenfolge!)


    # in einer Datei zusammenführen
    cat server.key.unsecure server.crt > stunnel-key.pem

    # Nachschauen, ob auch alles da ist
    ls -l

Der gesamte Vorgang erfordert zwischendurch die Eingabe einer Passphrase für den Serverschlüssel sowie von persönlichen Daten für das Zertifikat. Das sieht dann inkl. Ein- und Ausgaben beispielsweise so aus:

	$ openssl genrsa -des3 -out server.key 1024
	Generating RSA private key, 1024 bit long modulus
	..................++++++
	.......++++++
	e is 65537 (0x10001)
	Enter pass phrase for server.key:
	Verifying - Enter pass phrase for server.key:

	$ openssl rsa -in server.key -out server.key.unsecure
	Enter pass phrase for server.key:
	writing RSA key

	$ openssl req -new -key server.key -out server.csr
	Enter pass phrase for server.key:
	You are about to be asked to enter information that will be incorporated
	into your certificate request.
	What you are about to enter is what is called a Distinguished Name or a DN.
	There are quite a few fields but you can leave some blank
	For some fields there will be a default value,
	If you enter '.', the field will be left blank.
	-----
	Country Name (2 letter code) [AU]:DE
	State or Province Name (full name) [Some-State]:Bavaria
	Locality Name (eg, city) []:Munich
	Organization Name (eg, company) [Internet Widgits Pty Ltd]:ACME Ltd.
	Organizational Unit Name (eg, section) []:
	Common Name (eg, YOUR name) []:Manni Muster
	Email Address []:manni@acme.de

	Please enter the following 'extra' attributes
	to be sent with your certificate request
	A challenge password []:
	An optional company name []:

	$ openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt
	Signature ok
	subject=/C=DE/ST=Bavaria/L=Munich/O=ACME Ltd./CN=Manni Muster/emailAddress=manni@acme.de
	Getting Private key
	Enter pass phrase for server.key:

	$ rm server.csr

	$ cat server.key.unsecure server.crt > stunnel-key.pem

	$ ls -l
	insgesamt 16
	-rw-r--r-- 1 ubuntu ubuntu  895 2007-02-26 21:50 server.crt
	-rw-r--r-- 1 ubuntu ubuntu  963 2007-02-26 21:41 server.key
	-rw-r--r-- 1 ubuntu ubuntu  887 2007-02-26 21:42 server.key.unsecure
	-rw-r--r-- 1 ubuntu ubuntu 1782 2007-02-26 22:00 stunnel-key.pem

Anschließend haben wir in Form der Datei stunnel-key.pem, was wir wollten: ein selbst signiertes Schlüsselpaar für unseren HTTPS-Server. Das muß jetzt nur noch irgendwie auf die Box. Dafür gibt es zwei Wege:

Wo auch immer die Schlüsseldatei liegt, wir müssen uns in der stunnel-Konfiguration lediglich auf den richtigen Ablageort beziehen. Weiter geht's:

Über die Freetz-Oberfläche sorgen wir dafür, daß stunnel als Dienst automatisch gestartet wird und geben unter Einstellungen → stunnel services folgende Konfiguration ein, um eben diese Web-Oberfläche, in der wir uns gerade aufhalten, zukünftig HTTPS-gesichert verfügbar zu machen:

	[freetz_web]
	cert = /tmp/stunnel-key.pem
	client = no
	accept = 443
	connect = 81

D.h. nichts anderes, als daß wir einen von uns Freetz getauften Service verfügbar machen, welcher eingehende SSL-Verbindungen auf dem HTTPS-Port 443 akzeptiert und diese nach dem Entschlüsseln an den Port 81 des Freetz-Webservers weiterleitet. Wichtig: Das Ganze läuft nicht im Client-, sondern im Server-Modus.

Das war's schon! Jetzt können wir ausprobieren, was passiert, wenn wir https://fritz.box aufrufen. Es sollten zunächst der Passwort-Dialog von Freetz und anschließend die Web-Oberfläche erscheinen.

Wenn wir jetzt noch Services für Port 80 (AVM-Oberfläche) und/oder Port 82 (WOL-Oberfläche) haben wollen, fügen wir einfach entsprechende Abschnitte in die Konfiguration ein nach obigem Muster.

Achtung: Um den oder die HTTPS-Ports nach außen verfügbar zu machen, müssen entweder die üblichen Einstellungen in /var/flash/ar7.cfg vorgenommen werden, also z.B. folgender Abschnitt unter forwardrules

        "tcp 0.0.0.0:443 0.0.0.0:443",

oder aber über die AVM-Oberfläche ein entsprechendes Port Forwarding auf ein virtuelles Interface konfiguriert werden. Das Ganze benötigen wir pro Service, d.h. wir müssen uns entscheiden, welcher Service den “Premium Port” 443 bekommt, der von überall her erreichbar sein sollte. Ich schlage vor, der Freetz-Oberfläche diesen Port zu geben, denn dadurch gelangen wir an die Rudi-Shell und können somit alles mit der Box anstellen, was wir wollen.

Wichtig: Es muß wohl nicht weiter erklärt werden, weshalb bei diesem Szenario einem sicheren Passwort für die Web-Oberfläche besondere Bedeutung zukommt…

HTTPS-Zugriff reloaded & improved

Die Aussicht auf ein Paket von (bei mir) 1.310 KB für die oben beschriebene Lösung ist natürlich ein K.O.-Kriterium für kleine Boxen, die sowieso schon mit dem Speicherplatz für einen Firmware-Mod haushalten müssen. Wer sowieso OpenSSL auf der Box für etwas anderes braucht, dem werden die 160 KB für stunnel + zlib zusätzlich nicht mehr viel ausmachen. Aber wer SSL nur für den HTTPS-Server benötigt, würde sich sicher über eine schlankere Variante freuen. Das Schöne ist: es gibt eine.

Es gibt eine für Embedded-Systeme optimierte Open-Source-SSL-Bibliothek Namens matrixssl. Außerdem hat jemand für OpenWRT den kleinen Wrapper matrixtunnel geschrieben, welcher unsere Alternative zu stunnel sein wird. Und es gab das Ganze auch bereits als Paket für Freetz. Es handelt sich um ein Paket und eine Bibliothek mit der Gesamtgröße von 110 KB(!). Das entspricht einer Platzersparnis von ca. 92% gegenüber der ersten Lösung und funktioniert genauso gut nach meinen bisherigen Erfahrungen. So schnell wie ohne Verschlüsselung ist das Browsen subjektiv mit beiden HTTPS-Varianten nicht, aber absolut in Ordnung zum Arbeiten.

Inzwischen wurde auch xrelayd der Nachfolger von matrixtunnel} in Freetz aufgenommen. Hier wird xyssl} (inzwischen polarssl) als Crypt-Lib eingesetzt.

Der Aufruf, den man am besten in einer der beim Start ausgeführten Dateien (siehe Beschreibung der stunnel-Variante) unterbringt, sieht beispielhaft so aus:

    matrixtunnel -A cert.pem  -p server_key.pem -d 443 -r 81 -P /tmp/matrixssl.pid

Ich verwende übrigens für -A und -p denselben Dateinamen und dieselbe kombinierte Datei mit Serverschlüssel und Zertifikat wie für stunnel (Bauanleitung s.o.). Gibt man zusätzlich -f an, startet der Server im Vordergrund und man kann die Ausgaben beobachten. Einen Debug-Schalter gibt es auch - einfach mal mit -? aufrufen und schauen.

Übrigens: matrixtunnel kann auch für jede Schnitstelle (IP-Adresse) eine andere Regel nutzen. Einfach die IP-Addresse vor dem Port (ip:port) angeben wie z.B.

    # ds_mod web über SSL auf LAN
    matrixtunnel -A mycert.pem -p mycert.pem -d 192.168.1.1:443 -r 192.168.1.1:81 -P /tmp/matrixssl.pid
    # eigene Internetseite über SSL auf Virtual IP für externen Zugang
    matrixtunnel -A mycert.pem -p mycert.pem -d 192.168.1.253:443 -r 192.168.1.253:82 -P /tmp/matrixssl.pid

Firmware remote flashen

Auch das geht mit Rudi wunderbar, wie ich seit längerer Zeit in einem Beitrag im Forum beschrieben habe. Hier nochmals der Code, den man in der Rudi-Shell auf einmal ausführen kann. Vorher stoppen wir am besten noch einige Freetz-Dienste, welche dem im Code aufgerufenen AVM-Skript nicht bekannt sind und die deshalb weiter laufen und Speicher verbrauchen würden.

    # Bevor wir anfangen, ein Hintergrundbefehl: notfalls in 10 min die Box
    # zwangsweise neu starten, das müßte für Download + FW-Update reichen.
    { sleep 600 ; reboot -f; } &

    {
    # Unnötige Dienste stoppen, aber websrv und dsld weiter laufen lassen
    prepare_fwupgrade start_from_internet
    # FW-Image herunterladen und direkt nach "/" entpacken
    wget -q -O - http://mein.server.xy/mein.image 2> /dev/null | tar -C / -x
    # Restliche Dienste stoppen
    prepare_fwupgrade end
    # Installation vorbereiten
    /var/install
    # Installation initialisieren
    /var/post_install
    # Box neu starten
    reboot
    }

Dieser Code funktioniert übrigens nicht nur in der Rudi-Shell, sondern grundsätzlich auch innerhalb einer Telnet- oder SSH-Sitzung.