Aufrufen eigener CGI-Scripts und PHP-Seiten

Begriffsdefinition

CGIs sind Scripts, die vom WWW-Server ausgeführt werden und die sich an die Common Gateway Interface Spezifikation halten. Im Folgenden geht es um den Aufruf von CGI-Scripts für Homepage-Besitzer am KIT, nicht um das Schreiben von CGIs selbst (hierzu kann man sich die Links unter 'Weiterführende Literatur' zu Gemüte führen).

Verantwortlichkeit

Für Besitzer einer Homepage ist es möglich, eigene CGI-Scripts zu benutzen und anzubieten; hierzu ist keine besondere Berechtigung oder Eintragung erforderlich. Die Scripts werden dabei unter der User-ID des Benutzers ausgeführt, entsprechende Vorsicht ist also geboten, um nicht Dritten unabsichtlich Zugang zu eigenen oder fremden Dateien oder zum Rechner zu ermöglichen. Jegliche Auswirkungen der Scripts liegen in der Verantwortung der Benutzer; externe Aufrufe werden so behandelt, als hätte der Eigentümer der Scripts sie selbst aufgerufen. Bei Missbrauch ist demzufolge auch derjenige verantwortlich, der das Script, über das der Missbrauch erfolgte, auf seinem Account angelegt hat.

Dabei bitte auch beachten, dass CGI-Scripts im Gegensatz zu statischen Web-Seiten jedesmal einen eigenen Prozess aufrufen - das ist sehr aufwendig, man sollte CGIs also nur einsetzen, wenn es wirklich notwendig ist, und die Zahl der Aufrufe darf nicht überhand nehmen. Erfolgen Änderungen des dynamischen Inhalts wesentlich seltener als er abgerufen wird, ist in der Regel ein cron-Job, der regelmäßig eine neue statische Seite erzeugt, effizienter. Wird ein CGI jede Sekunde aufgerufen, belastet das die Rechner bereits erheblich - sowas kann schon als missbräuchliche Benutzung gewertet werden. Man sollte also insbesondere auf Seiten verzichten, die CGIs einbinden und in kurzen Zeitabständen automatisch refresht werden.

Platzierung

Eine Datei, die im Verzeichnis public_html (also der Homepage) oder einem Unterverzeichnis davon liegt und die Endung .cgi hat, wird im Gegensatz zu anderen Dateien nicht übertragen, sondern ausgeführt, und nur die Ausgabe des Programmlaufs wird übermittelt.

Sicherheit

Da die Scripts unter der User-ID des Eigentümers aufgerufen werden, brauchen die Zugriffsrechte auch nur so gesetzt sein, dass der Eigentümer sie ausführen kann. So können z.B. CGI-Scripts, die Passwörter für Datenbank-Zugriffe enthalten, geschützt werden. Das Verzeichnis, in dem sich das Script befindet, muss allerdings wie bisher für den WWW-Server lesbar sein (in der Regel bedeutet dies Leserechte für alle); es darf allerdings nur für den Eigentümer schreibbar sein, sonst funktionieren die CGI-Scripts nicht!

CGI-Scripts können beliebige ausführbare Programme sein. Aus Sicherheitsgründen empfehlen wir aber, keine Shell-Scripts zu verwenden und in Perl- oder C-Programmen bei der Verwendung von Befehlen wie system() oder popen() äußerste Vorsicht walten zu lassen. In Benutzereingaben können sich Befehle (z.B. `rm -rf *`) verbergen, die beim unvorsichtigen Auswerten der Eingaben ausgeführt werden. Nur wenn man sich absolut sicher ist, dass keine Benutzereingaben ausgewertet werden oder bei diesen Auswertungen keine Interpretation erfolgt, sollte man Shell-Scripts (bzw. Calls, die eine Shell aufrufen) verwenden. Anderenfalls geht man das gleiche Risiko ein, wie wenn man sich beim Verlassen des Rechners nicht ausloggt - nur dass diese Sitzung jetzt weltweiten Zugang hat. Solche Shell-Scripts sollten immer mit der Zeile #!/bin/sh anfangen - für andere Shells kann nicht garantiert werden, dass sie immer unter dem jetzigen Pfad zu finden sein werden.

Auch in PHP kann man sich beliebig tief in's Knie schiessen - umsichtiges Programmieren ist auch hier angesagt. Guter Lesestoff für den Anfang: PHP Security Notes.

PHP

Für PHP-Seiten gibt es zwei Möglichkeiten:

  1. Da PHP oft nur für einfache Einfügungen in Web-Seiten verwendet wird und dies nicht viel Zeit verbrauchen soll, ist PHP als Modul im WWW-Server integriert. Solche PHP-Seiten (bzw. Web-Seiten mit eingearbeitetem PHP-Code), die die Endung .php haben müssen, werden unter der Benutzer-Nummer des WWW-Servers ausgeführt und haben deswegen nur eingeschränkte Rechte, sind dafür aber auch schnell. Für die meisten Anwendungsfälle reicht dies völlig aus. (Und weil die Frage immer wieder kommt: Nein, man kann den Safe Mode nicht abschalten, sonst würde es ja gar keinen Sinn machen, ihn erst anzuschalten - auch wenn das praktisch wäre, so wie es praktisch wäre, auch ohne ec-Karte Geld abheben zu können.)
  2. Für den Fall, dass die Einschränkungen umgangen werden müssen (z.B. um Dateien zu schreiben o.ä.), stehen auch eigenständige PHP-Script-Interpreter zur Verfügung. Solche Scripts sind dann ganz normale CGIs (mit der Endung .cgi) wie oben beschrieben. Um den PHP-Script-Interpreter zu laden, muß die erste Zeile #!/usr/bin/php-cgi lauten.

Bitte beachten Sie, daß Short Tags nicht verwendet werden können, da diese inkompatibel zu XML sind - es muß also <?php statt nur <? geschrieben werden.

Bitte beachten Sie ebenfalls, dass seit PHP4 die Variable register_globals defaultmäßig auf OFF steht! Genauere Hinweise zur Änderung von bestehendem Code entnehmen Sie bitte der PHP-Dokumentation.

Eigene Einstellungen können Sie entweder über die Datei .htaccess vornehmen, oder besser noch innerhalb der PHP-Seite selber setzen. So können Sie z.B. die Werte post_max_size und upload_max_filesize zum Einstellen der maximalen Größe hochgeladener Dateien setzen.

Aufruf

CGI-Scripts werden genauso aufgerufen wie normale HTML-Seiten oder Download-Dateien - durch einen Link auf die zugehörige URL. Der einzige Unterschied besteht darin, dass der Server an der Endung .cgi erkennt, dass er nicht den Inhalt dieser Datei übertragen, sondern das Script ausführen und nur dessen Ausgabe übermitteln soll.

In der Regel reichen zum Aufruf relative URLs aus, die sich auf die URL der Seite beziehen, auf der der Link steht, bzw. die maximal den Pfad auf dem Server angeben, also ohne das https:// und den Servernamen. Dies erleichtert einen späteren Umzug, sollte er jemals notwendig sein. Für Links in Seiten auf anderen Rechnern müssen natürlich vollständige URLs verwendet werden; hier sollten immer nur die offiziellen Server-Namen (die in der Regel mit www beginnen) verwendet werden, niemals der Name eines Rechners selbst oder gar eine IP-Nummer. Nur für die offiziellen Server-Namen garantieren wir Kontinuität bei technisch bedingten Umzügen in neue Netze oder auf neue Hardware. Dies gilt natürlich ganz allgemein und nicht nur für CGIs.

 

Fehlersuche

Falls ein CGI-Script nicht läuft (die übliche Fehlermeldung lautet "Internal Server Error", da der Server das Ausführen von CGI-Scripts als interne Angelegenheit ansieht, obwohl es um Programme von Benutzern geht), kann man es zunächst einmal lokal testen. Diese Tests sollte man immer auf dem Rechner durchführen, auf dem der WWW-Server läuft, um sicherzustellen, dass man die gleiche Umgebung wie der WWW-Server selbst vorfindet. Ggf. sollte man sich also zuerst z.B. mit ssh www.student.kit.edu auf dem WWW-Server einloggen.

Der einfachste Test ist, das Programm direkt von der Shell aus aufzurufen (also ./script.cgi und nicht perl script.cgi, auch wenn man weiß, dass es ein Perl-Programm ist - das muss die Shell selbst herausfinden können, denn sonst kann es der WWW-Server nachher auch nicht).

Hier sieht man auch gleich, ob die Zugriffsrechte ausreichend gesetzt sind, d.h. ob man als Eigentümer das Script aufrufen kann. Hat man nur Lese-, aber keine Ausführ-Rechte, erscheint je nach Shell eine Fehlermeldung der Art cannot execute oder permission denied. In so einem Fall kann man mit chmod u+x script.cgi die notwendigen Ausführ-Rechte setzen.

Kleine Checkliste für Scripts:

  • Ist die Endung tatsächlich .cgi?
  • Sind die ersten beiden Zeichen in dem Script #! (ohne Leerzeichen davor)?
  • Kommt danach der vollständige Pfad zum Interpreter-Programm (z.B. /usr/bin/perl)?
  • Kommen nach dem Programm (und ggf. Optionen) keine weiteren Zeichen mehr?
    Der häufigste Fehler an dieser Stelle sind Scripts, die von einem Windows-System stammen und deswegen vor dem Zeilenendezeichen noch ein <Ctrl>-<M> haben. Man sieht diese Zeichen, wenn man die Scripts mit joe oder vi anschaut (in einem Windows-Editor sieht man diese Zeichen natürlich nicht, da es ja genau diese Windows-Programme sind, die das überflüssige Wagenrücklauf-Zeichen erzeugen). Beim Versuch, solche Scripts direkt in der Shell aufzurufen, erscheint in der Regel eine Fehlermeldung der Art " not found "... (man beachte das " am Zeilenanfang, das eigentlich am Ende stehen sollte, aber durch das überzählige Wagenrücklauf-Zeichen (Carriage Return, CR, -) nach vorne gerutscht ist); manchmal (je nach Shell) auch interpreter "..." not found oder No such file or directory.
    Abhilfe: Mit dem Kommando perl -pi -e 's/\r\$//' script.cgi kann man die störenden Zeichen entfernen (wobei für script.cgi natürlich der entsprechende Name des eigenen Scripts stehen muss). Dieses Kommando kann man auch einfach mal 'auf Verdacht' aufrufen, es macht nichts kaputt. Wenn's danach funktioniert, hat man die Fehlerursache gefunden (und sollte sich ggf. überlegen, warum man den Fehler nicht anhand obiger Beschreibung erkannt hat...) - in Zukunft also an dieser Stelle besser aufpassen.
  • Ist das so aufgerufene Interpreter-Programm auch tatsächlich auf dem WWW-Server installiert?
  • Hält sich das Script wirklich an die Common Gateway Interface Spezifikation? Scripts, die keinen korrekten Header liefern, führen immer zu Fehlern.
  • Häufig passiert es, dass man sich auf Dateien ohne Pfadangabe oder auf relative Pfade verläßt, was lokal ggf. funktioniert, aber nicht, wenn der WWW-Server das Script aufruft, da dieser das Arbeitsverzeichnis (current directory) anders setzt. Man sollte daher nur absolute Pfade verwenden oder aber explizit das Arbeitsverzeichnis setzen. Dabei bitte nur Pfade verwenden, die mit dem eigenen Home-Verzeichnis anfangen, so wie es in der Variablen $HOME gespeichert ist (kann man sich in der Shell mit echo $HOME anzeigen lassen). Andere Pfade, wie sie z.B. /bin/pwd ausgibt, können nicht garantiert werden, weil sie rechnerspezifisch sind oder dynamisch vom Auto-Mounter generiert werden.
  • Das oben gesagte gilt gleichermassen für Programm-Aufrufe, da auch die Variable $PATH nicht den gewohnten Umfang besitzt - im Zweifelsfall also $PATH selber setzen oder aber volle Pfadangaben für Nicht-Systemprogramme verwenden.
  • Zu guter Letzt: Entsprechen Eigentümer und Gruppe sowohl des Scripts als auch des oder der Unter-Verzeichnisse(s) des Home-Verzeichnisses dem zugehörigen Benutzer? Das Ändern allein der Gruppe eines Verzeichnisses führt bereits zum Fehlschlagen des Aufrufs.

Klappt soweit alles, kann man das Script auch aus der Shell mit dem Kommando cgitest aufrufen. Dieses Programm bietet dem CGI-Script eine Umgebung, die der beim Aufruf über den WWW-Server entspricht. So kann man meist erkennen, ob man sich in dem Script auf gewissen Gegebenheiten (wie z.B. eine gesetzte PATH-Variable im Environment) verlassen hat, die zwar in der normalen Shell gegeben sind, aber vom WWW-Server nicht so gesetzt werden. Sieht die Ausgabe beim Aufruf mit cgitest ordentlich aus, sollte man sicherstellen, dass man keine Fehler-Ausgaben übersehen hat, z.B. durch einen Aufruf der Art cgitest ./script.cgi >/dev/null.

cgitest muß übrigens auch auf dem Rechner aufgerufen werden, auf dem der WWW-Server läuft, da es dessen Konfigurationsdateien liest, um die richtige Umgebung erzeugen zu können. Wird es auf einem anderen Rechner aufgerufen, erhält man in der Regel eine Fehlermeldung, dass die Konfigurationsdatei des WWW-Servers nicht gelesen werden konnte.

Zuletzt kann man auch noch einen Blick in die Log-Dateien des Servers werfen: Die Kommandos myerrorlog und myscriptlog zeigen die benutzerspezifischen Einträge aus dem generellen Fehler-Log und dem speziellen Script-Log des WWW-Servers an. Auch dazu muss man natürlich auf dem WWW-Server eingeloggt sein, sonst erhält man logischerweise nur die Fehlermeldung, dass das Verzeichnis mit den Logdateien des WWW-Servers nicht verfügbar ist. Für WWW-Server, die im Cluster laufen, gibt es die speziellen Cluster-Varianten myclerrorlog und myclscriptlog, die alle Log-Files auf allen Cluster-Knoten anzeigen (sonst erhält man nur die Ausgabe des Servers, auf dem man gerade eingeloggt ist).

Die Fehlermeldung couldn't spawn child process kommt allerdings nicht von einem fehlerhaften CGI-Script, sondern von einem echten Ressourcen-Engpass des WWW-Servers. Hier hilft wirklich nur noch, den Administrator zu verständigen.

 

Weiterführende Literatur

Wer jetzt immer noch nicht weiterkommt und mir eine E-Mail schreiben möchte, sollte unbedingt erwähnen, dass die Hinweise auf dieser Seite befolgt wurden (sonst gibt es nur den Hinweis auf eben diese Seite) und um welche URL es geht, damit ich das Problem nachvollziehen kann.

Andreas Ley