Fast jedes Skript oder Programm öffnet und bearbeitet Dateien. Dieser Workshop zeigt, wo die Gefahren lauern, und erklärt Admins und Entwicklern, wie sie Fehler schon an der Wurzel neutralisieren.
Zahlreiche Fallen für den programmierenden Admin verbergen sich in ganz unschuldig aussehenden Dateioperationen, etwa ein File erzeugen, öffnen oder schließen. Teil 1 dieser Serie[1] hat bereits offene Dateien bei Programmstart und -ende behandelt; diese Folge gibt Hinweise, wie man weitere Operationen auf dem Dateisystem mit C, C++ und Shellskripten zähmt.
Die Wurzel des Übels
Unix ordnet Dateisysteme, Geräte und andere Ressourcen unterhalb des Wurzelverzeichnisses in einen Verzeichnisbaum ein, auf den viele Prozesse gleichzeitig zugreifen dürfen. Diese Parallel-Zugriffe führen oft zu so genannten Race Conditions, die sich als Sicherheitslücke manifestieren (siehe Kasten “Symlinks, Races und deren Folge”).
|
Listing 1: |
|---|
01 #!/bin/sh 02 for i in `find . -name "*.txt"`; do 03 tr "A-Z" "a-z" < $i > /tmp/unsicher.tmp 04 mv /tmp/unsicher.tmp $i 05 done |
Race Conditions treten ein, wenn ein Programm oder Skript davon ausgeht, dass sich seine Umgebung nicht verändert. Während er mit Dateien hantiert, kann ein Prozess aber allerlei Überraschungen erleben: Zwischen zwei Einzelaktionen erzeugt vielleicht ein anderer Prozess eine neue Datei oder ein Verzeichnis, löscht oder verschiebt es, der Admin hängt ein Dateisystem ein oder aus (»mount()«, »umount()«), ändert den Besitzer einer Datei, ein Benutzer hängt einen symbolischen Link um, erzeugt oder löscht einen Hardlink oder ändert die Rechte einer Datei.
Namensfrage
Verwundbar sind vor allem Programme und Skripte, die sorglos mit Dateinamen werkeln. Kein gängiges Dateisystem garantiert, dass ein Name sich zu zwei verschiedenen Zeitpunkten auf denselben Inode bezieht. Dabei riskiert der Programmierer dreierlei:
- Er operiert mit einer anderen Datei als beabsichtigt.
- Eigenschaften der Datei ändern sich unerwartet.
- Ein Benutzer oder ein Prozess erhält andere Zugriffsrechte
als gewollt.
Ganz zu verhindern sind solche Pannen auch nicht durch scharfe Sicherheitsmaßnahmen, doch sollte der Entwickler sein Programm darauf vorbereiten. Bei der Abwehr darf er immerhin davon ausgehen, dass Root gefährliche Situationen nicht absichtlich herbeiführt.
Beispiel: Mail-Admin
Auf dem System des Administrators Anton laufen zwei separate Mailserver, je einer für die Teams Rot und Blau. Jedes Teammitglied besitzt eingeschränkte Administratorrechte für die Mailboxen seiner Teamkollegen. Mehrere spezialisierte Programme mit SUID-Root-Bit verleihen ihnen die nötigen Vollmachten. Das Mailverzeichnis »/var/teammail« enthält unter anderem folgende Files:
$ cd /var/teammail; ls -ld * . drwxrwsrwt 2 root root 4096 ... ./ -rw-rw---- 1 bernd blau 11 ... bernd -rw-rw---- 1 renate rot 0 ... renate -rw-rw---- 1 rainer rot 738 ... rainer
Das Programm »leere-mbox« in Listing 2a leert die Mailbox eines Teamkollegen. Die Zugangskontrolle in den Zeilen 14 bis 23 stellt mit »stat()« sicher, dass die Gruppe der Mailboxdatei mit der Gruppe des Aufrufenden übereinstimmt. Anschließend öffnet »fopen()« die Datei (Zeile 25), löscht eventuell vorhandenen Inhalt oder erzeugt das File nötigenfalls neu. Zum Schluss setzt Antons Programm für den Fall, dass es die Datei neu anlegt, Gruppe und Rechte passend (Zeilen 27 bis 35).
Um die Mailbox ihres Kollegen Rainer (Team Rot) zu leeren, tippt Renate »leere-mbox rainer«. Ohne es zu ahnen hat Anton allerdings fünf kapitale Sicherheitslücken fabriziert, in die Renate nun fällt. Besser arbeitet das Programm in Listing 2b.
Falle 1: Berechtigungen
Öffnet ein SUID-Root-Programm eine Datei im Auftrag des Benutzers, tut es gut daran, die Zugriffsrechte des Users auf das gewünschte File zu überprüfen. Am besten überlässt der Entwickler diese Aufgabe dem Betriebssystem, denn das kennt alle Besonderheiten der diversen Dateisysteme. In »leere-mbox« verlässt sich Anton auf die Gruppe. Wenn Rainer seine Mailbox mit »chmod 600« geschützt hat, wird das Programm dennoch Renate den Zugriff erlauben.
Antons Programm begeht aber noch einen Fehler: Es liest erst die Datei-Informationen mit »stat()« (Zeile 15), prüft sie in Zeile 20 und öffnet dann die Datei (Zeile 25). Zwischen »stat()« und dem »fopen()«-Aufruf wechselt Rainer aber vielleicht ins Team Blau (Abbildung 3), die Daten aus dem Stat-Aufruf sind dann nicht mehr gültig.
Lösung für die Shell: Entweder schreibt der Admin ein kleines C-Programm, das die Rechte prüft, oder vermeidet solche Tests ganz. Noch besser ist es, für SUID-Root-Aufgaben auf Shellskripte zu verzichten – soweit technisch möglich.

Abbildung 3: Renate ruft »leere-mbox rainer« auf. Gleich nach dem »stat()«-Kommando wechselt Rainer ins Team Blau und bekommt eine Mail. Renate hat schon die Zugangsgenehmigung erhalten und löscht unberechtigt Rainers Mailbox mit der neuen Mail.
Lösung für C und C++: Das Programm in Listing 2b gibt zuerst die Root-Rechte ab (Zeile 18) und öffnet dann die Datei mit »open()«, ohne sie zu überschreiben (Zeile 20). Nur wenn das klappt, darf der Anwender die Datei anfassen. Ein Wermutstropfen bleibt, denn Rainer kann immer noch nach dem Öffnen der Datei das Team wechseln. Hier helfen nur noch schwere Geschütze wie Posix Mandatory Locks[7].
Generell vermeidet der sicherheitsbewusste Entwickler die drei Funktionen »stat()«, »lstat()« und »access()« und weicht auf »open()« und »fstat()« aus.
Falle 2: Dateien öffnen
Die Funktionen der Open-Familie dienen Angreifern als Haupteinfallstor für Symlink-Attacken. Speziell »fopen()« erlaubt es dem Programmierer nicht, symbolische Links zu erkennen. Gefahr droht aber nicht nur durch den Dateinamen. Darf ein Angreifer irgendwo im Pfad in ein Verzeichnis schreiben, kann er dort Symlinks anlegen oder ganze Dateibäume verschieben oder löschen. Wie der Programmierer das überprüft, steht im Abschnitt “Falle 4”.
Lösung für die Shell: Der Admin sorgt für sichere Verzeichnisse. Setzt der Programmierer gleich am Anfang eines Skripts die Option »set -C«, überschreibt die Shell mit der Ausgabeumleitung »>« keine vorhandenen Dateien. Ein hilfreiches Bonbon: Die Zsh (und nur sie)[6] weigert sich mit »cd -s Verzeichnis« Symlinks im Pfad zu folgen.
Lösung für C und C++: Wichtig ist es, den Zugriffsmodus sorgsam zu wählen. Listing 2a (Zeile 25) macht das mit »w+« richtig, »fopen()« gewährt aber keinen Schutz vor Symlink-Angriffen. Das gelingt mit der Funktion »open()« und der Option »O_NOFOLLOW« (Listing 2b, Zeile 28). Leider gibt es »O_NOFOLLOW« nur auf GNU-Systemen, wenn »_GNU_SOURCE« definiert ist (Zeile 1). Wer einen File Pointer benötigt, behilft sich mit »fdopen()«:
int fd = open("Datei", O_RDWR);
FILE *f = fdopen(fd, "r+");
Die meisten Funktionen, die mit Pfadnamen oder mit File Pointern operieren, gibt es auch in Versionen, die Dateideskriptoren verwenden, etwa »stat()« und »fstat()«, »read()« und »fread()« sowie »truncate()« und »ftruncate()« (Listing 2b, Zeile 23). Verwirrend: Mal arbeitet eine Funktion, deren Name mit f beginnt, mit Namen oder File-Zeigern und ihre Schwester ohne f mit Deskriptoren, mal ist es genau umgekehrt.
|
Listing 2a: Mailbox unsicher |
|---|
01 #include <errno.h>
02 #include <limits.h>
03 #include <stdio.h>
04 #include <sys/types.h>
05 #include <sys/stat.h>
06 #include <unistd.h>
07
08 int main(int argc, char **argv) {
09 char fn[PATH_MAX];
10 struct stat buf;
11 int rc;
12
13 snprintf(fn, PATH_MAX, "/var/teammail/%s", argv[1]);
14 /* Zugriffsrecht prüfen */
15 rc = stat(fn, &buf);
16 if (rc != 0 && rc != ENOENT) {
17 fprintf(stderr, "Fehlern");
18 return 1;
19 }
20 if (rc == 0 && buf.st_gid != getgid()) {
21 fprintf(stderr, "Zugriff verweigertn");
22 return 1;
23 }
24 /* Datei erzeugen oder abschneiden */
25 if (fopen(fn, "w+") == NULL)
26 return 1;
27 /* Eigentümer und Rechte setzen */
28 if (chown(fn, buf.st_uid, getgid()) != 0) {
29 remove(fn);
30 return 1;
31 }
32 if (chmod(fn, 0066) != 0) {
33 remove(fn);
34 return 1;
35 }
36
37 return 0;
38 }
|
|
Listing 2b: Mailbox sicher |
|---|
01 #define _GNU_SOURCE
02 #include <fcntl.h>
03 #include <limits.h>
04 #include <stdio.h>
05 #include <sys/types.h>
06 #include <sys/stat.h>
07 #include <unistd.h>
08
09 int main(int argc, char **argv) {
10 char fn[PATH_MAX];
11 uid_t alte_euid;
12 int rc;
13 int fd;
14
15 snprintf(fn, PATH_MAX, "/var/teammail/%s", argv[1]);
16 /* Zum User werden */
17 alte_euid = geteuid();
18 seteuid(getuid());
19 /* Bestehende Datei öffnen */
20 fd = open(fn, O_NOFOLLOW);
21 if (fd >= 0) {
22 /* existiert, Zugriff erlaubt, abschneiden */
23 rc = ftruncate(fd, 0);
24 close(fd);
25 return (rc == 0);
26 }
27 /* Neue Datei erzeugen */
28 fd = open(fn, O_NOFOLLOW | O_CREAT | O_EXCL, 0660);
29 if (fd < 0)
30 return 1;
31 close(fd);
32 /* Als Root die Gruppe setzen */
33 seteuid(alte_euid);
34 if (fchown(fd, getuid(), getgid()) != 0) {
35 unlink(fn);
36 return 1;
37 }
38
39 return 0;
40 }
|
Falle 3: Dateien erzeugen
Neben dem bisher Gesagten sollte der Entwickler eine neue Datei auch gleich mit den korrekten Berechtigungen erzeugen. Setzt er diese zu spät, hat ein Angreifer die Datei vielleicht schon geöffnet[1]. Das »leere-mbox«-Programm in Listing 2a begeht diesen Fehler (Zeilen 25, 28, 32). Lösung für die Shell:
umask 077 set -C : > "Datei" set +C chmod Modus Datei
Lösung für C und C++: Wieder drängt sich »open()« auf. Die Optionen »O_ CREAT« und »O_EXCL« sorgen dafür, dass die Open-Operation die Datei nur erzeugt, wenn es sie noch nicht gibt. Das dritte Argument gibt die initialen Berechtigungen an (Listing 2b, Zeile 28). Wichtig: Wählt der Programmierer die Open-Variante mit nur zwei Argumenten, erhält die Datei zufällige Berechtigungen! Die Funktion »fchown()« ersetzt »chown()« (Zeile 34).
Falle 4: Sicheres Verzeichnis
Dateien in einem Verzeichnis sind sicher vor fremden Zugriffen, wenn das Verzeichnis und alle darüber liegenden nur Root oder dem Besitzer der Datei Schreibrechte einräumen. Die Dateirechte selbst schützen nur unvollkommen, da ein Angreifer mit Schreibrecht im Verzeichnis die Datei umbenennen und unter dem alten Namen ein neues File anlegen kann.
Schutz mit Gate Guardian
Seit dem ersten Teil dieser Serie [1] ist die Bibliothek Gate Guardian [4] weiter gewachsen und enthält ab Version 0.9.3 auch eine Shared Library sowie Funktionen zum sicheren Umgang mit Dateien. Mit ihrer Hilfe ist es leicht, für sichere Verzeichnisse zu sorgen. Eine andere Lösung steht in [3].
Lösung für die Shell: Das Programm »cwdsafe« in Listing 3 überprüft das Arbeitsverzeichnis und verwendet dazu Libgateguardian. Diese Bibliothek installiert der Admin wie üblich mit »./configure && make && make install« und übersetzt anschließend »cwdsafe.c« mit dem Aufruf »gcc -o cwdsafe cwdsafe.c -lgateguardian«. Wenn er das Programm noch an eine Stelle im Pfad kopiert (etwa »/usr/local/bin«), kann jeder User »cwdsafe« in seinen Skripten nutzen:
umask 077 cd Verzeichnis cwdsafe || exit 1 echo hallo > Datei
Lösung für C und C++: Analog zur Shell. Wie bei Gate Guardian üblich kann der Programmierer den kompletten Code einfach per Include-Direktive einbinden und aufrufen, ohne eine Bibliothek zu verwenden:
#include "fileguardian.c" [...] if (fileg_check_cwd_safety() != 0) exit(1);
Außerdem bietet Gate Guardian noch Funktionen an, die beliebige Verzeichnisse akzeptieren: »fileg_check_dir_safety_with_mode()« erlaubt es, die verbotenen Zugriffsrechte mit anzugeben, »fileg_safe_opendir()« öffnet das geprüfte Verzeichnis und liefert einen Dir-Pointer, »fileg_safe_opendir_with_ mode()« kombiniert beides.
Falle 5: Temporäre Dateien
Die beschriebenen Probleme übersehen Programmierer besonders leicht, wenn sie temporäre Daten speichern. Als Gegenmaßnahme hat das »/tmp«-Verzeichnis in der Regel das Sticky-Bit gesetzt. Auch wenn es Schreibrechte für alle bietet, darf damit nur der Eigentümer eine Datei löschen – neue Files anlegen darf aber jeder. Nur wer beim Öffnen (zum Lesen oder Schreiben) aufpasst, hat wenig zu befürchten. E
|
Listing 3: |
|---|
01 #include <fileguardian_headers.h>
02
03 int main(void) {
04 return !!fileg_check_cwd_safety();
05 }
|

Abbildung 4: Ein Hacker (rechts) nutzt einen Bug in den Linux-Quellen für einen Symlink-Angriff. Wenn ein Benutzer den Kernel übersetzt, überschreibt er unbebabsichtigt die verlinkte Datei. Glücklicherweise funktioniert dieser Angriff mit »gcc« nicht, wenn Root das File übersetzt.

Abbildung 5: Oben: Ein argloses »find … | xargs …« erwischt die falschen Dateien. Unten: Mit den GNU-spezifischen Optionen »-print0« und »-0« überleben Leerzeichen in Dateinamen die Pipeline.
Der Linux-Kernel 2.6.10 enthält für die IA64-Architektur einige Skripte, die unvorsichtig schreiben. Listing 4 zeigt den Anfang von »linux-2.6.10/arch/ia64/scripts/check-gas«: Zeilen 5 und 6 belegen die Variable »out« mit dem Namen »/tmp/out PID.o« für eine temporäre Datei, die der Compiler-Aufruf in Zeile 7 schreibt. Wie leicht ein Angreifer damit per Symlink Dateien überschreiben kann, belegt Abbildung 4. Den gleichen Fehler wie in den Linux-Quellen machen Programmierer ungewöhnlich häufig. Auf dem Debian-Woody-System (3.0r4) des Autors findet der Befehl
find / -type f -perm +111 -print0 | xargs -0 grep '/tmp.*$$'
stolze 85 Skripte in etwa 550 Paketen, die auf einer Zeile Konstrukte wie »/tmp/out$$« enthalten. Nach persönlichen Erfahrungen ist ungefähr jedes dritte solche Skript anfällig für Symlink-Angriffe.
Lösung für die Shell: Der »mktemp«-Befehl erzeugt eine neue Datei, deren Name zum Teil von Mktemp berechnet wird, ohne auf Race Conditions hereinzufallen (Listing 5a). Im Notfall genügt auch ein beliebiger, vom Programmierer bestimmter Dateiname, wenn das Skript mit »set -C« das Überschreiben bestehender Dateien abschaltet. Listing 5a sorgt außerdem per Signal-Trap dafür, dass das Skript die temporäre Datei bei einem Programmabbruch löscht.
Lösung für C und C++: Die Funktion »tmpnam()« erzeugt einen Dateinamen, der noch frei ist. Wie man damit eine Datei sicher erzeugt, steht unter “Falle 3”. Viele Ratgeber empfehlen stattdessen zwar »mkstemp()«, doch liefert diese Routine keinen Pfad zurück. Daher kann der Programmierer das Verzeichnis nicht mehr auf Sicherheit abklopfen.
Mit Gate Guardian entstehen sichere Temp-Files mit einer der beiden Funktionen »fileg_safe_tmpfile()« oder »fileg_safe_tmpfile_with_name()« (siehe Listing 5b), sie legen entweder eine sichere temporäre Datei an oder geben »-1« zurück, wenn das nicht möglich ist.
|
Listing 4: Bug in den |
|---|
01 #!/bin/sh
02 dir=$(dirname $0)
03 CC=$1
04 OBJDUMP=$2
05 tmp=${TMPDIR:-/tmp}
06 out=$tmp/out$$.o
07 $CC -c $dir/check-gas-asm.S -o $out
|
|
Listing 5a: Temp-Datei in der |
|---|
01 TMPF=""
02 TMPDIR=${TMPDIR-/tmp}
03
04 clean_up () {
05 test ! x"$TMPF" = x && rm -f "$TMPF"
06 }
07
08 trap clean_up 0 1 2 13 15
09 umask 077
10 TMPF=`mktemp -q "$TMPDIR/foo.XXXXXXXX"` || exit 1
|
|
Listing 5b: Temporäre Datei |
|---|
01 #include <fileguardian_headers.h>
02
03 int main(void) {
04 char name[L_tmpnam];
05 FILE *f = fileg_safe_tmpfile_with_name(name);
06 return 0;
07
|
|
Listing 6: Verbessertes Listing |
|---|
01 #!/bin/sh
02 IFS=" tn"
03 PATH="/bin:/usr/bin"
04 umask 077
05 TDIR=${TDIR-/tmp}
06 TFILE=`mktemp -q "$TDIR/sicherer.XXXXXXXX"` || exit 1
07 for i in **/*.txt; do
08 tr "A-Z" "a-z" < "$i" > "$TFILE"
09 mv "$TFILE" "$i"
10 done
|
Falle 6: Dateien löschen
Für das Entfernen einer Datei bieten sich die Funktionen »unlink()« und »remove()« an. Beide nehmen den Dateinamen als Argument. Wenn jedoch ein anderer Prozess die Datei bereits gelöscht und eine neue mit demselben Namen erzeugt hat, entfernen beide Funktionen die falsche Datei. Dagegen gibt es keinerlei Schutz.
Manchmal möchte der Programmierer ganz sicher sein, dass er die gespeicherten Daten auch wirklich löscht. Ein simples »unlink()« reicht nicht aus, weil ein Angreifer einen Hardlink auf die Datei erzeugen kann. Das darf jeder Benutzer mit Leserecht für das Verzeichnis, in dem die Datei liegt. Damit überwindet der Angreifer zwar nicht die Dateirechte, aber er konserviert die Daten, bis er das System geknackt hat.
Lösung: Das Programm Wipe[5] überschreibt die Daten mehrmals mit bestimmten und zufälligen Mustern und entfernt anschließend die Datei.
Falle 7: Space in Dateinamen
Mit Leerzeichen in Dateinamen rechnet offenbar kaum ein Skriptautor. Eine kurze Suche nach Shellskripten (»grep “#!/bin/sh” *«) in den Verzeichnissen »/bin« und »/usr/bin« fördert gleich mehrere Kandidaten zu Tage: Bei dem Dateinamen »leer zeichen« müssen unter anderem »colormake«, »ps2ps«, »ps2ascii« und »xdvi« passen. Denn statt die gewünschte Datei zu bearbeiten, fummeln sie fröhlich an den beiden Files »leer« und »zeichen« herum. Besonders schlau gibt sich »gzexe«: Es verpackt die Datei zwar erfolgreich in ein selbstextrahierendes Skript, das dann beim Auspacken allerdings versagt.
Lösung: Der vorsichtige Admin setzt Shellvariablen unter allen Umständen in einfache oder doppelte Anführungszeichen (Listing 6). Haarig ist allerdings die Kommando-Substitution mit umgekehrten Anführungszeichen (Listing 1, Zeile 2) oder per »$( Befehl)«.
Am besten ist es, bei solchen Arbeiten auf Befehle auszuweichen, die auch Sonderzeichen verdauen. In Listing 6, Zeile 7 ersetzt »**/*.txt« das Kommando »find«. Den Doppelstern kennt die Zsh – neuerdings auch die Bash – seit Urzeiten. Er steht als Platzhalter für alle Dateien in allen Unterverzeichnissen.
Befehlskette
Die Shellvariable »$*« (Liste aller Optionen) wird ebenfalls oft falsch verwendet. Das folgende Skript »bad.sh« funktioniert zum Beispiel nicht zuverlässig:
#!/bin/sh grep $*
Der Befehl »bad.sh hallo “leer zeichen”« durchsucht die Dateien »leer« und »zeichen«. Auch mit Anführungszeichen versagt das Skript: »grep “$*”«. Jetzt sucht Grep nach der Zeichenkette »hallo leer zeichen« auf der Standardeingabe. Lösung: Richtig geht es mit »$@« in doppelten Anführungsstrichen, also »grep “$@”«. Die Shell merkt sich in dieser Variablen die Wortgrenzen unabhängig von enthaltenen Leerzeichen.
Teuflisches Duo
Geradezu gemeingefährlich werden die beliebten Befehle »find« und »xargs« in Kommandos wie »find / -name “*~” | xargs rm«. In der Pipe geht die Information über eingebettete Leerzeichen, Zeilenumbrüche und Tabulatoren verloren. Dumm, wenn Find auf eine Datei namens »/etc/passwd bak~« stößt: »rm« löscht gnadenlos sowohl »/etc/passwd« als auch »/bak~«. Abbildung 5 führt das vor – aus nahe liegenden Gründen mit anderen Dateinamen.
Lösung: Die GNU-Variante von Find kennt die Option »-print0«. Damit trennt Find die Ausgabe durch Nullzeichen wie eine Zeichenkette in C. Auf der anderen Seite der Pipe sorgt die Xargs-Option »-0« dafür, dass sich beide Programme richtig verstehen. Aber Achtung: Das klappt ausschließlich mit Xargs. So ist etwa »find /tmp -name “*~” -print0 | Befehl« fast immer eine Todsünde – all die schönen Unix-Tools fangen leider mit »-print0« nichts an.
Detail-Arbeit
Bei der Arbeit mit Dateien steckt der Teufel im Detail. Dabei kratzt dieser Workshop zum Teil nur an der Oberfläche. Eine spätere Folge geht auf wichtige Themen wie Pufferüberläufe durch zu lange Pfadnamen ein. Zunächst wird sich Folge 3 mit überraschenden und böswilligen Programmeingaben befassen. (fjl)
|
Infos |
|---|
|
[1] Dominik Vogt, “Umweltverschmutzung – Sicheres Programmieren für Administratoren, Folge 1”: Linux-Magazin 02/05, S. 54 [2] Peer Heinlein, “Alltagstauglich – Bash- Einsatz im echten Administrator-Leben”: Linux-Magazin-Sonderheft 04/04, S. 18, Errata dazu: [https://www.linux-magazin.de/Produkte/Bestellen/lms_2004_4.html] [3] John Viega und Matt Messier, “Secure Programming Cookbook”: O\’Reilly, [http://www.secureprogramming.com] [4] Dominik Vogt, Gate-Guardian-Projekt: [http://www.sourceforge.net/projects/gateguardian/] [5] Wipe: [http://abaababa.ouvaton.org/wipe/] [6] Zsh: [http://zsh.sunsite.dk] [7] Marc André Selig, “Admin-Workshop zu Locks”: Linux-Magazin 12/04, S. 72 |
|
Der Autor |
|---|
|
Dipl.-Math. Dominik Vogt ist langjähriger Software-Entwickler und Systemadministrator. Zurzeit arbeitet er als freiberuflicher EDV-Berater mit Schwerpunkt Softwaresicherheit. In seiner Freizeit werkelt er am Windowmanager Fvwm. |








