Bitbucket – Ein Schnelleinstieg mit Mercurial unter Linux

25 05 2013

Versucht man sich an etwas größeren Programmier-Projekten, bietet es sich irgendwann an, dieses mit einer Versionsverwaltung auszustatten. Denn überschreibt man sich beispielsweise einmal  seine Dateien mit einer alten Version oder findet man partout einen Fehler nicht, kann es äußerst sinnvoll sein, die Änderungen der letzten Tage nachvollziehen zu können. Freie Software findet man unter anderem mit Git oder Mercurial, aber wohin dann mit den Dateien? Man könnte diese auf eigenem Webspace einrichten, schneller und mit weiteren Funktionen geht das mit dem Service von Bitbucket. Hier kann man sich kostenlos eigene Repositories einrichten, auf die bis zu fünf Entwickler gleichzeitig zugreifen dürfen. Wollen mehr Entwickler an dem Projekt arbeiten, lässt sich diese Anzahl gegen Gebühr in verschiedenen Abstufungen erhöhen.

Wir möchten uns doch zunächst nur ein kleines Repository für uns selbst einrichten. Dazu legen wir zuallererst einen Bitbucket Account an. Die Webseite steht in deutscher Sprache zur Verfügung, diese können wir in unserem Benutzerkonto einstellen. Hier bietet es sich auch gleich an, einen SSH Schlüssel zur Kommunikation mit der Seite ohne Passwort zu hinterlegen. Haben wir das noch nicht getan, erzeugen wir via ssh-keygen ein Schlüsselpaar, dessen Public-Key Inhalt wir in Bitbucket unter der Option „SSH Schlüssel“ -> „Schlüssel hinzufügen“ einfügen.

$ ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/home/aaaaa/.ssh/id_rsa):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/aaaaa/.ssh/id_rsa.
Your public key has been saved in /home/aaaaa/.ssh/id_rsa.pub.

$ ls -l ~/.ssh
insgesamt 36
-rw------- 1 aaaaa aaaaa 1679 Mai 14 22:55 id_rsa
-rw-r--r-- 1 aaaaa aaaaa  398 Mai 14 22:55 id_rsa.pub

$ cat ~/.ssh/id_rsa.pub

Jetzt erzeugen wir unser Repository, in dem wir unseren Code halten möchten. Hierzu navigieren wir über „Repositorys“ -> „Repository erstellen“ und geben einen Projekt-Namen an. Legen wir fest, dass es sich um ein privates Repository handelt, kann niemand außer den definierten Entwicklern den Code einsehen, ansonsten ist dieser öffentlich verfügbar. Als Repository-Typ nutze ich Mercury, aber das muss jeder nach den eigenen Vorlieben entscheiden. Git bietet mehr Optionen, Mercury lässt sich dafür einfacher handhaben. Hinter „Ticket-Verfolgung“ versteckt sich ein Bugtracker, der auch bei privaten Code-Projekten für die Öffentlichkeit frei geschaltet werden. Selbiges gilt für das Wiki, in dem man eine Dokumentation erstellen kann. Haben wir die Programmiersprache gewählt, können wir das „Repository erstellen“.

bitbucket05

Natürlich benötigen wir noch die entsprechende Software zur Versionskontrolle. Mercurial installieren wir uns in Ubuntu über die Paketverwaltung.

$ sudo apt-get install mercurial

Damit könnten wir auf Befehlszeile direkt loslegen. Wünschen wir uns eine komfortable GUI, können wir zusätzlich auch noch EasyMercurial installieren, das zwar kaum Optionen mitbringt, dafür aber absolut simpel zu bedienen ist. Mehr Funktionen bietet TortoiseHg, Eine Einführung zu diesem Programm findet man bei Draketo, weshalb ich mir das hier spare. Auch für Eclipse finden sich mit EGit und MercurialEclipse zwei gute Plugins, welche bei heise kurz vorgestellt werden.

Beim ersten Start von EasyMercurial kümmern wir uns zunächst um die Einstellungen im Menü „File“ -> „Settings“. Im Tab „User details“ geben wir unseren Namen und die E-Mail Adresse ein. Unter „Remote“ -> „Set Push and Pull Location…“ tragen wir das gerade erstellte Bitbucket Repository ein. Die genaue Bezeichnung finden wir, wenn wir den Link „Ich habe ein bestehendes Projekt zum hoch pushen“ aufrufen.

bitbucket01 bitbucket07  bitbucket08

Mit einem Klick auf „Open“ laden wir nun unser Projekt-Verzeichnis als lokales Repository.

bitbucket02 bitbucket03

Im sich öffnenden erscheinenden Fenster wählen wir alle Dateien, die für unser Projekt notwendig sind. Diese selektieren wir und klicken auf den „Add“ Button, Cache oder sonstige temporäre Dateien bleiben außen vor.

bitbucket04  bitbucket06

Um diese Dateien zu übernehmen, bedarf es noch eines Commits. Hier tragen wir normalerweise einen Kommentar ein, was sich seit dem letzten Stand geändert hat. In unserem Fall ist dies die erste Version, weshalb wir dies auch so angeben. Ist das getan, senden wir die Dateien per „Push“ an das Remote Repository auf unserem Bitbucket Account. Je nach Anzahl und Größe der Dateien kann das einen Moment dauern. Dass sich etwas tut erkennen wir an dem psychedelischen Balken, der sich am unteren Bildrand hektisch hin- und herbewegt.

bitbucket09 bitbucket10 bitbucket11

Sind die Dateien auf dem Bitbucket Server angekommen, können wir diese nun in unserem Account unter dem Punkt „Quellcode“ sehen.

bitbucket12

Ändern wir unsere Dateien auf der heimischen Platte, erkennt das EasyMercurial automatisch und zeigt uns die modifizierten Dateien an. Möchten wir diese synchronisieren, gehen wir wie zuvor mit einem Commit und anschließendem Push vor. Aussagekräftige Beschreibungen beim Commit helfen uns später dabei, zu erkennen welche Modifikationen wir vorgenommen haben. In der History sehen wir die verschiedenen Commit-Punkte grafisch dargestellt.

bitbucket13

Advertisements




Installation eines LAMP Webservers mit Ubuntu

22 08 2012

Normalerweise nutze ich für meinen Test-Webserver die XAMPP Sammlung, da dieses keinerlei Installation erfordert und das komplette Paket portabel ist, also beispielsweise mit einem USB Stick überall hin mitgenommen und auf einem anderen Computer weiter verwendet werden kann. Trotzdem kann es manchmal von Vorteil sein, die Komponenten direkt zu installieren.

Die Mär, dass eine Installation eines funktionierenden Webservers mit allen wichtigen Komponenten für Testzwecke schwierig sei möchte ich heute ausräumen. Natürlich taugt ein solches System noch nicht für den Produktiveinsatz, sollte also nicht öffentlich an das Netz angebunden werden. Dazu benötigt es noch einigen Feinschliff insbesondere was die Sicherheit gegenüber böswilligen Angriffen angeht.

Im Folgenden wollen wir uns die gebräuchlichste Software installieren. Die wären der Apache Webserver, als Datenbank MySQL sowie die Skriptsprache PHP. Nimmt man dann noch unser Betriebssystem hinzu, wissen wir jetzt auch, wofür LAMP steht, nämlich für Linux, Apache, MySQL, PHP.

Was das genau für Programme sind, was man damit überhaupt anstellen kann, das ist nicht Inhalt dieses Artikels. Hier soll es lediglich um die Installation in Linux gehen. Ein grundsätzliches Verständnis der Materie wird also vorausgesetzt.

Apache
Installieren des Apache Webservers:

sudo apt-get install apache2

Der Server soll nicht von außen erreichbar sein. Deshalb sagen wir Apache, dass er nur auf localhost hören soll. Hierzu bearbeiten wir die ports.conf:

sudo vi /etc/apache2/ports.conf

Listen 80
ändern in
Listen localhost:80

Jetzt können wir unseren Webserver schon testen, indem wir die Adresse http://localhost/ in unsere Browser-Adresszeile eintippen.

Wollen wir Subdomains erstellen, kopieren wir uns das Default-Template aus /etc/apache2/sites-available in eine neue Datei mit dem Namen unseres virtuellen Hosts und editieren diese. Meine Subdomain heißt „ramtatta“, außerdem habe ich den Default-Pfad von /var/www/ noch abgeändert in mein Home-Verzeichnis.

sudo cp /etc/apache2/sites-available/default /etc/apache2/sites-available/ramtatta
sudo vi /etc/apache2/sites-available/ramtatta

<VirtualHost *:80>
   ServerName ramtatta.localhost
   ServerAdmin webmaster@localhost
   DocumentRoot /home/aaaaa/htdocs/RamTatTa
   <Directory /home/aaaaa/htdocs/RamTatTa/>
      Options Indexes FollowSymLinks MultiViews
      AllowOverride All
      Order allow,deny
      allow from all
   </Directory>
   ErrorLog ${APACHE_LOG_DIR}/error.log
   LogLevel warn
   CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>

Damit die Umleitung funktioniert, müssen wir noch unsere hosts erweitern.
sudo vi /etc/hosts

Hinzufügen dieser Zeile:
127.0.0.1 ramtatta.localhost

Jetzt muss der virtuelle Host noch aktiviert und der Apache durchgestartet werden.
sudo a2ensite
sudo /etc/init.d/apache2 restart

Aufrufen können wir die Subdomain folgendermaßen: http://ramtatta.localhost/

Möchten wir unsere Links mittels mod_rewrite verschönern, so müssen wir dieses ggf. auch erst noch aktivieren.
sudo a2enmod rewrite
sudo /etc/init.d/apache2 restart

PHP
Installieren von PHP:

sudo apt-get install php5

Unsere HTML Seiten legen wir per Default in /var/www ab. PHP testen wir, indem wir hier eine Datei index.php mit folgendem Inhalt erzeugen.

vi /var/www/index.php
   <?php
   phpinfo();
   ?>

Laden wir jetzt wieder http://localhost/ im Browser, wird eine Seite mit vielen Informationen zu unserer Serverumgebung dargestellt.

MySQL
Installieren von MySQL. Den de facto Standard zur Administration von MySQL Datenbanken im Web stellt PHPMyAdmin dar. Um uns gleich daran zu gewöhnen, installieren wir das gleich mit.

sudo apt-get install mysql-server php5-mysql phpmyadmin

Ich selbst habe mich selbst an die Software Navicat gewöhnt. Auch in der kostenlosen Version bietet dieses Programm sehr viele schöne Funktionen und ist in seiner Eigenschaft als echtes Programm jederzeit flotter als die Weboberfläche von PHPMyAdmin. Einziges Manko ist, dass es sich eigentlich um Windows-Binaries handelt, das lediglich gebündelt mit der Wine Laufzeitumgebung als Linux-Version angeboten werden.

Auf die Datenbank können wir jetzt also auch zugreifen. Hierzu tippen wir in der Shell

mysql -u root

bzw. laden im Browser die Adresse http://localhost/phpmyadmin.

Eclipse
Einen sehr mächtigen Editor für die Entwicklung unserer PHP Dateien finden wir in Eclipse. Dieser ist grundsätzlich für die Java-Entwicklung gedacht, mit den PHP Development Tools (PDT) können wir diesen aber auch sehr gut für PHP verwenden.

Installation des Grundpaketes für Eclipse:

sudo apt-get install eclipse

Das Programm starten wir und klicken uns durch zur automatischen Installation der PDT:
Help -> Install new software… -> Work with: Indigo Update Site wählen -> Programming Languages -> PHP Development Tools (PDT) -> Installieren

Xdebug
Ein großer Nachteil von PHP ist, dass sich dieses normalerweise nicht wirklich debuggen lässt. Abhilfe schafft die Xdebug Erweiterung. Wir installieren zunächst das Paket.

sudo apt-get install php5-xdebug

Jetzt müssen wir das Binary und die Ini-Datei finden. Dazu aktualisieren wir unsere Datenbank für locate und lassen uns dann den Pfad für die xdebug.so und xdebug.ini ausgeben.

sudo updatedb

locate xdebug.so
/usr/lib/php5/20090626/xdebug.so

locate xdebug.ini
/etc/php5/cli/conf.d/xdebug.ini

Die Ini Datei editieren wir. Normalerweise sollte hier der Pfad für die Binary schon eingetragen sein, wir kontrollieren dies aber zur Sicherheit. Dann tragen wir noch ein paar Variablen ein, mit denen wir Xdebug aktivieren und starten den Apache neu.

sudo vi /etc/php5/cli/conf.d/xdebug.ini
   zend_extension=/usr/lib/php5/20090626/xdebug.so
   xdebug.remote_enable=1
   xdebug.remote_handler=dbgp
   xdebug.remote_mode=req
   xdebug.remote_host=127.0.0.1
   xdebug.remote_port=9000
sudo /etc/init.d/apache2 restart




Sichere Dateiuploads mit PHP

4 06 2012

Stellen wir auf unserer Homepage den Besucher über einen Dateiupload die Möglichkeit zur Verfügung, eigene Dateien auf unseren Server zu übertragen, ist eine gewisse Vorsicht anzuraten. Immerhin möchten wir keinen ausführbaren Code auf unseren Server eingeschleust bekommen.

Um die übertragenen Dateien zu prüfen, könnten wir beispielsweise auf die Idee kommen, die $_FILES Variable auszulesen, die nach einem Upload vom Server gefüllt wird. Schauen wir uns diese doch anhand eines kleinen Beispiels einmal an.

<!-- Formular, um eine Datei hochzuladen -->
<form enctype="multipart/form-data" action="index.php" method="post">
Datei hochladen: <input name="datei" type="file" /><br />
<input type="submit" />
</form>

<?php
if (!empty($_FILES)){
	var_dump($_FILES);
}

Ausgabe:

Array
(
    [datei] => Array
        (
            [name] => avatar.jpg
            [type] => image/jpeg
            [tmp_name] => /tmp/phpLgZOUQ
            [error] => 0
            [size] => 8801
        )

)

Im vollständigen Beispiel sähe das dann in etwa so aus:

<?php
if (!empty($_FILES)){
	$upload_pfad = 'uploads/';	// Wo sollen die hochgeladenen Dateien abgelegt werden?
	$erlaubte_formate = array('image/gif', 'image/jpeg', 'image/png');	// Erlaubte Dateiformate
	$dateiname = $upload_pfad.basename($_FILES['datei']['name']);	// Dateiname inkl. Pfad

	echo '<p>Erkanntes Dateiformat: '.$_FILES['datei']['type'].'</p>';	// Ausgabe

	if (in_array($_FILES['datei']['type'], $erlaubte_formate)){	// Wenn es sich um eine datei.jpg handelt, gehen wir davon aus, dass es sich um eine Grafik handelt
		if(!move_uploaded_file($_FILES['datei']['tmp_name'], 'uploads/'.$_FILES['datei']['name'])) die('Konnte die Datei nicht hochladen');	// Datei in ihr Verzeichnis schieben
		echo '<img src="'.$dateiname.'" title="Hochgeladenes Bild" />';	// Ausgabe der Datei im Browser
	}
}

Ausgabe:

Erkanntes Dateiformat: image/jpeg

<img src="uploads/avatar.jpg" title="Hochgeladenes Bild" />

Nur leider kommen diese Werte vom Browser und lassen sich von daher relativ einfach manipulieren. Wir können uns darauf also nicht verlassen, da wir böswilligen Angreifern somit Tür und Tor öffnen. Die Unzuverlässigkeit erkennen wir deutlich daran, wenn wir unser Jpeg-Bild umbenennen in avatar.exe und den Upload damit testen.

Ausgabe:

Erkanntes Dateiformat: application/x-ms-dos-executable

An der Datei hat sich nichts geändert außer der Erweiterung. Es handelt sich also weiterhin um die gleiche Grafik, nur mit einem fehlerhaften Namenszusatz. Gefährlicher wird es, wenn beispielsweise eine EXE-Datei umbenannt wird, hier könnte einfach eine Bilddatei vorgetäuscht werden.

Somit erkennen wir schon, dass wir uns auch nicht einfach auf die Dateinamenserweiterung verlassen dürfen. Heißt die hochgeladene Datei schadcode.jpg, können wir keineswegs davon ausgehen, dass drin ist, was drauf steht.

<?php
if (!empty($_FILES)){
	$upload_pfad = 'uploads/';	// Wo sollen die hochgeladenen Dateien abgelegt werden?
	$erlaubte_formate = array('gif', 'jpg', 'png');	// Erlaubte Dateiformate
	$dateiname = $upload_pfad.basename($_FILES['datei']['name']);	// Dateiname inkl. Pfad

	$dateiinfo = pathinfo($_FILES['datei']['name']);
	$dateiformat = $dateiinfo['extension'];	// Auslesen der Dateierweiterung
	echo '<p>Erkanntes Dateiformat: '.$dateiformat.'</p>';	// Ausgabe

	if (in_array($dateiformat, $erlaubte_formate)){	// Wenn es sich um eine datei.jpg handelt, gehen wir davon aus, dass es sich um eine Grafik handelt
		if(!move_uploaded_file($_FILES['datei']['tmp_name'], 'uploads/'.$_FILES['datei']['name'])) die('Konnte die Datei nicht hochladen');	// Datei in ihr Verzeichnis schieben
		echo '<img src="'.$dateiname.'" title="Hochgeladenes Bild" />';	// Ausgabe der Datei im Browser
	}
}

Ausgabe:

Erkanntes Dateiformat: jpg

Auch dies ist keine geeignete Methode, denn die Erweiterung ist schnell verändert. Im Windows lässt sich zwar eine schadcode.jpg nicht starten, weil hier nur bestimmte Dateiformate ausgeführt werden, doch die meisten Webserver laufen unter einem Linux Derivat. Hier ist die Erweiterung vollkommen egal, es kommt nur darauf an, dass die Datei das Executable-Bit gesetzt hat. Im Beispiel sei verdeutlicht, dass auch eine .jpg Datei problemlos ausgeführt werden kann.

$ echo "echo Ich bin ausführbar" >schadcode.jpg
$ chmod u+x schadcode.jpg 
$ ./schadcode.jpg 
Ich bin ausführbar

Von daher sollten wir fremde Dateien niemals auf unserem System ausführen lassen. Idealerweise speichert man diese auch nicht innerhalb der vom Webserver lesbaren Dateistruktur (normalerweise htdocs), so dass diese gar nicht direkt vom Browser aus angesprochen werden können. Über unsere Skripte, die sich ja direkt auf dem Server befinden, sollten je nach Hoster auch andere Verzeichnisse ansprechbar sein. Auf ein Beispiel hierzu verzichte ich, da das etwas zu weit führen würde.

Um das Dateiformat einigermaßen sicher zu bestimmen, müssen wir den MIME Header der Datei lesen. Ab PHP >= 5.3 können wir dazu die finfo_file Funktion mit FILEINFO_MIME_TYPE nutzen. Für ältere PHP Versionen steht das Pendant mime_content_type zur Verfügung.

<?php
if (!empty($_FILES)){
	$upload_pfad = 'uploads/';	// Wo sollen die hochgeladenen Dateien abgelegt werden?
	$erlaubte_formate = array('image/gif', 'image/jpeg', 'image/png');	// Erlaubte Dateiformate
	$dateiname = $upload_pfad.basename($_FILES['datei']['name']);	// Dateiname inkl. Pfad

	$finfo = finfo_open(FILEINFO_MIME_TYPE);	// Gib den MIME Typ zurueck
	if ($finfo !== FALSE && !empty($finfo)){
	   	$dateiformat = finfo_file($finfo, $_FILES['datei']['tmp_name']);
		finfo_close($finfo);
	} else die('Fileinfo Datenbank konnte nicht geoeffnet werden');

	echo '<p>Erkanntes Dateiformat: '.$dateiformat.'</p>';	// Ausgabe

	if (in_array($dateiformat, $erlaubte_formate)){	// Dieser Dateityp entspricht unseren Vorgaben
		if(!move_uploaded_file($_FILES['datei']['tmp_name'], 'uploads/'.$_FILES['datei']['name'])) die('Konnte die Datei nicht hochladen');	// Datei in ihr Verzeichnis schieben
		echo '<img src="'.$dateiname.'" title="Hochgeladenes Bild" />';	// Ausgabe der Datei im Browser
	}
}

Ausgabe:

Erkanntes Dateiformat: image/jpeg
<img src="uploads/avatar.jpg" title="Hochgeladenes Bild" />

Nun sollte man sich auch nicht auf den Mime-Typ alleine verlassen. So würde nämlich auch eine schadcode.exe Datei akzeptiert werden, wenn nur der Mime-Typ passt. Laden wir doch einfach unsere avatar.exe hoch.

Erkanntes Dateiformat: image/jpeg
<img src="uploads/avatar.exe" title="Hochgeladenes Bild" />

Von daher sollte man die Erweiterung anhand des erkannten Typs stets selbst setzen, um absichtliche wie auch unabsichtliche Nutzerfehler zu vermeiden.

<?php
if (!empty($_FILES)){
	$upload_pfad = 'uploads/';	// Wo sollen die hochgeladenen Dateien abgelegt werden?
	$erlaubte_formate = array(
		'image/gif' => 'gif',
		'image/jpeg' => 'jpg',
		'image/png' => 'png');	// Erlaubte Dateiformate. Im multidimensionalen Array sind nun auch die zugehoerigen Dateierweiterungen angegeben

	$dateiname = $upload_pfad.pathinfo($_FILES['datei']['name'], PATHINFO_FILENAME);	// Mit pathinfo holen wir nur den Dateinamen ohne Erweiterung

	$finfo = finfo_open(FILEINFO_MIME_TYPE);	// Gib den MIME Typ zurueck
	if ($finfo !== FALSE && !empty($finfo) > 0){
	   	$dateiformat = finfo_file($finfo, $_FILES['datei']['tmp_name']);
		finfo_close($finfo);
	} else die('Konnte das Dateiformat nicht erkennen');

	echo '<p>Erkanntes Dateiformat: '.$dateiformat.'<br />';	// Ausgabe

	if (isset($erlaubte_formate[$dateiformat])){	// Existiert ein Eintrag mit dem Key?
		$dateiname .= '.'.$erlaubte_formate[$dateiformat];	// Haenge die Erweiterung an den Dateinamen an

		echo 'Ursprünglicher Dateiname: '.$_FILES['datei']['name'].'<br />';
		echo 'Aktueller Name: '.basename($dateiname).'</p>';

		if(!move_uploaded_file($_FILES['datei']['tmp_name'], $dateiname)) die('Konnte die Datei nicht hochladen');	// Datei in ihr Verzeichnis schieben
		echo '<img src="'.$dateiname.'" title="Hochgeladenes Bild" />';	// Ausgabe der Datei im Browser
	}
}

Die Ausgabe, wenn wir wieder unsere avatar.exe hochladen:

Erkanntes Dateiformat: image/jpeg
Ursprünglicher Dateiname: avatar.exe
Aktueller Name: avatar.jpg

<img src="uploads/avatar.jpg" title="Hochgeladenes Bild" />

Abschließend sei noch auf getID3() hingewiesen. Dieses umfangreiche Skript liest die verschiedensten Multimedia-Formate ein und liefert neben den Header Informationen noch viele weitere nützliche Informationen. In unserem Beispiel sähe der Einsatz dann so aus:

<?php
if (!empty($_FILES)){
	require_once('getid3/getid3.php');	// getID3() einbinden
	$getID3 = new getID3;

	$upload_pfad = 'uploads/';	// Wo sollen die hochgeladenen Dateien abgelegt werden?
	$erlaubte_formate = array('image/gif', 'image/jpeg', 'image/png');	// Erlaubte Dateiformate

	$dateiname = $upload_pfad.pathinfo($_FILES['datei']['name'], PATHINFO_FILENAME);	// Mit pathinfo holen wir nur den Dateinamen ohne Erweiterung

	$dateiinfo = $getID3->analyze($_FILES['datei']['tmp_name']);
	var_dump($dateiinfo);

	echo '<p>Erkanntes Dateiformat: '.$dateiinfo['mime_type'].'<br />';	// Ausgabe

	if (in_array($dateiinfo['mime_type'], $erlaubte_formate)){	// Dieser Dateityp entspricht unseren Vorgaben
		$dateiname .= '.'.$dateiinfo['fileformat'];	// Haenge die Erweiterung an den Dateinamen an

		echo 'Ursprünglicher Dateiname: '.$_FILES['datei']['name'].'<br />';
		echo 'Aktueller Name: '.basename($dateiname).'</p>';

		if(!move_uploaded_file($_FILES['datei']['tmp_name'], $dateiname)) die('Konnte die Datei nicht hochladen');	// Datei in ihr Verzeichnis schieben
		echo '<img src="'.$dateiname.'" title="Hochgeladenes Bild" />';	// Ausgabe der Datei im Browser
	}
}

Zur besseren Verdeutlichung erfolgt zunächst die Ausgabe des von getID3() gelieferten Arrays mit den Dateiinformationen. Danach folgt unsere normale Ausgabe. Da auch die Erweiterung geliefert wird, müssen wir diese nicht mehr selbst definieren:

array(11) {
  ["GETID3_VERSION"]=>
  string(14) "1.9.3-20111213"
  ["filesize"]=>
  int(8801)
  ["filename"]=>
  string(9) "phpb1p7Qd"
  ["filepath"]=>
  string(4) "/tmp"
  ["filenamepath"]=>
  string(14) "/tmp/phpb1p7Qd"
  ["avdataoffset"]=>
  int(0)
  ["avdataend"]=>
  int(8801)
  ["fileformat"]=>
  string(3) "jpg"
  ["video"]=>
  array(7) {
    ["dataformat"]=>
    string(3) "jpg"
    ["lossless"]=>
    bool(false)
    ["bits_per_sample"]=>
    int(24)
    ["pixel_aspect_ratio"]=>
    float(1)
    ["resolution_x"]=>
    int(72)
    ["resolution_y"]=>
    int(100)
    ["compression_ratio"]=>
    float(0.4074537037037)
  }
  ["encoding"]=>
  string(5) "UTF-8"
  ["mime_type"]=>
  string(10) "image/jpeg"
}

Erkanntes Dateiformat: image/jpeg
Ursprünglicher Dateiname: avatar.exe
Aktueller Name: avatar.jpg

<img src="uploads/avatar.jpg" title="Hochgeladenes Bild" />

Ist das jetzt wirklich sicher? Einigermaßen, doch muss für die maximale Sicherheit immer der maximale Aufwand betrieben werden. Mit Erkennung des Mime-Headers und idealerweise der Ablage der Dateien außerhalb des Web-Roots sollte man zumindest einigermaßen auf der sicheren Seite sein. Sicherer immerhin, als sich auf die durch $_FILES oder getimagesitze gelieferten Informationen zu verlassen. Letzteres hat sich in der Hinsicht in der Vergangenheit immer wieder durch seine Angreifbarkeit disqualifiziert.





PHP: Daten innerhalb von HTML Tags extrahieren

5 05 2012

Um an die Links in einem String zu gelangen, sieht man immer wieder abenteuerliche Regex http://de3.php.net/regex Konstrukte. Eines haben diese neben der schlechten Lesbarkeit häufig gemeinsam, nämlich dass sie nicht zuverlässig arbeiten. Statt also das Rad neu zu erfinden, sollte man hier besser auf die gegebenen Funktionen des Document Object Models zurück greifen.

Wir wollen also aus einem Text alle Werte in <a href=“http://www.example.org/„> heraus filtern. Zu beachten ist, dass damit keine unverlinkten URLs gesucht werden, sondern nur der Inhalt innerhalb des href=“…“

<?php

$text = 'Lorem ipsum dolor sit amet, consetetur http://example.org sadipscing 
elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam 
erat, sed diam voluptua. At <a 
href="http://suchen.mobile.de/auto/search.html?scopeId=C&isSearchRequest=true&sortOption.sortBy=price.consumerGrossEuro">vero</a> 
eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no 
sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, 
consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et 
dolore magna aliquyam erat, sed diam voluptua. <a 
href="http://www.example.com#link">www.example.net</a>At vero eos et 
accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea 
takimata sanctus est Lorem ipsum dolor sit amet.';

// Erzeuge ein neues DOM Dokument
$dom = new DOMDocument();
// Keine Fehlerpruefung erzwingen
$dom->strictErrorChecking = false;
// Den String einlesen
@$dom->loadHTML($text);
// Die Links innerhalb des <a ...> Tags suchen
$links = $dom->getElementsByTagName('a');
// Fuer alle gefundenen Links
foreach($links as $link) {
	// Wenn der a Tag ein Attribut href hat...
	if ($link->hasAttribute('href')) {
		// ...den Inhalt auslesen
		$href = $link->getAttribute('href');
		// Bildschirmausgabe
		echo $href.'<br />';
   }
}

Die Ausgabe liefert uns beide Links:
http://suchen.mobile.de/auto/search.html?scopeId=C&isSearchRequest=true&sortOption.sortBy=price.consumerGrossEuro
http://www.example.com#link

Mittels XPath kann man auch innerhalb beliebiger Tags nach Inhalten suchen. Nehmen wir an, wir möchten alle Texte innerhalb der <td>…</td> Tags von Tabellen. Das Beispiel entsprechend angepasst:

<?php

$text = 'Lorem ipsum dolor sit amet, consetetur http://example.org sadipscing 
elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam 
erat, sed diam voluptua. At <a 
href="http://suchen.mobile.de/auto/search.html?scopeId=C&isSearchRequest=true&sortOption.sortBy=price.consumerGrossEuro">vero</a> 
eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no 
sea takimata sanctus est Lorem ipsum dolor sit amet. 
<table>
<thead>
<tr><th>Kopf</th></tr>
</thead>
<tbody>
<tr><td>Zeile 1</td></tr>
<tr><td>Zeile 2</td></tr>
<tr><td>Zeile 3</td></tr>
</tbody>
</table>
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod 
tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. 
<a href="http://www.example.com#link">www.example.net</a>At vero eos et 
accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea 
takimata sanctus est Lorem ipsum dolor sit amet.';

$dom = new DOMDocument();
$dom->strictErrorChecking = false;
@$dom->loadHTML($text);
// XPath erzeugen
$xpath = new DOMXPath($dom);
// Der Pfad zu unseren gewuenschten Inhalten: table -> tbody -> tr -> td
// Würden wir den Wert eines Attributs suche, müssten wir dieses mit @ kennzeichnen
// Beispiel: $xpath->query('//a/@href');
$inhalte = $xpath->query('//table/tbody/tr/td');
foreach($inhalte as $inhalt) {
    echo $inhalt->nodeValue.'<br />';
}

Als Ausgabe erhalten wir:

Zeile 1
Zeile 2
Zeile 3




Medien einbinden mittels oEmbed

5 05 2012

Auf einer dynamischen Webseite das Einbinden von Object-Elementen zuzulassen birgt gewisse Risiken. Möchte man ein YouTube Video auf seiner eigenen Homepage anzeigen, präsentiert einem die Video-Plattform dazu den Code in Form eines Object-Elements bzw. neuerdings als Iframe an. Hiermit stehen die Tore für einen böswilligen Angriff weit offen, denn was genau da eingebunden wird lässt sich nur schwerlich automatisiert kontrollieren. Auf vielen Social Network Plattformen wird das Video automatisch angezeigt, sobald man lediglich die URL zur entsprechenden Seite einfügt. Doch wie funktioniert das?

Auch wenn die Nutzer dies nicht tun dürfen, kommt man nicht umhin, den Einbettungs-Code des entsprechenden Anbieters in seine Seite einzubinden. Doch wie kommt man da ran? Eine recht einfache Möglichkeit hierzu bietet oEmbed, das unter anderem von YouTube, Vimeo oder Soundcloud unterstützt wird. Über die API können wir auf die vom Anbieter zur Verfügung gestellten Informationen zugreifen. Verdeutlichen wir dies anhand eines Beispiels.

Wir möchten Big Buck Bunny von Vimeo auf unserer Seite einbinden. Hierzu benötigen wir zunächst die URL, unter der dieses Video eingebunden ist. Diese lautet http://vimeo.com/1084537. Praktischerweise finden wir auf oembed.com bereits eine Beschreibung für diesen Anbieter. Die Liste ist nicht vollständig, es kann sich also durchaus lohnen, in der jeweiligen Dokumentation nach dem Stichwort oEmbed zu suchen.

<?php

$vimeo = 'http://vimeo.com/api/oembed.xml?url=';
$video = 'http://vimeo.com/1084537';

// Zusammenbauen der URL
$url = $vimeo.urlencode($video);
// Holen der Informationen vom Anbieter. Wir nutzen das XML Format,
// meist wird aber auch JSON angeboten
$daten = @simplexml_load_file($url);
// Fehlerpruefung
if (preg_match('#^HTTP/... 4..#', $http_response_header[0])) die ('Kein Zugriff auf die Daten möglich: '.substr($http_response_header[0], 9));
// Ausgabe des Videos
echo $daten->html;

Das war es auch schon. In unserem Objekt haben wir alle Informationen zu dem Video, inklusive dem HTML Tag zur Einbindung. Hier die vollständige Ausgabe von $daten:

SimpleXMLElement Object
(
    [type] => video
    [version] => 1.0
    [provider_name] => Vimeo
    [provider_url] => http://vimeo.com/
    [title] => Big Buck Bunny
    [author_name] => Blender Foundation
    [author_url] => http://vimeo.com/user508904
    [is_plus] => 0
    [html] => < iframe src="http://player.vimeo.com/video/1084537" width="1280" height="720" frameborder="0" webkitAllowFullScreen mozallowfullscreen allowFullScreen></iframe>
    [width] => 1280
    [height] => 720
    [duration] => 596
    [description] => Big Buck Bunny tells the story of a giant rabbit with a heart bigger than himself. When one sunny day three rodents rudely harass him, something snaps... and the rabbit ain't no bunny anymore! In the typical cartoon tradition he prepares the nasty rodents a comical revenge.  Licensed under the Creative Commons Attribution license  http://www.bigbuckbunny.org
    [thumbnail_url] => http://b.vimeocdn.com/ts/209/636/20963649_1280.jpg
    [thumbnail_width] => 1280
    [thumbnail_height] => 720
    [video_id] => 1084537
)

Da einige Anbieter teilweise ihre Medien gegen das Einbinden auf fremden Webseiten schützen oder auch mal ein Medium gelöscht werden kann, müssen wir prüfen, ob beim Zugriff alles in Ordnung ist. Sollte dies nicht der Fall sein, erhalten wir im Response Header einen entsprechenden HTTP Status Code. Greifen wir auf ein geschütztes Medium zu, sähe der Inhalt von $http_response_header beispielsweise so aus:

Array
(
    [0] => HTTP/1.1 403 Forbidden
    [1] => Server: nginx
    [2] => Date: Sat, 05 May 2012 11:05:32 GMT
    [3] => Content-Type: text/html; charset=utf-8
    [4] => Connection: close
    [5] => Cache-Control: no-cache
    [6] => Access-Control-Allow-Methods: GET, PUT, POST, DELETE
    [7] => Access-Control-Allow-Headers: Accept, Authorization, Content-Type, Origin
    [8] => Access-Control-Allow-Origin: *
    [9] => X-Runtime: 36
    [10] => X-Cacheable: NO:Cache-Control=no-cache
    [11] => Content-Length: 1
    [12] => X-Varnish: 1789823721
    [13] => Age: 0
    [14] => Via: 1.1 varnish
    [15] => X-Cache: MISS
)

Die Nutzung ist nicht nur auf Videos beschränkt, sondern kann für weitere Multimedia-Formate wie Audio oder Bilder genutzt werden. Um beispielsweise den Player von SoundCloud einzubinden, müssen wir lediglich den Link abändern. Die Dokumentation liefert uns eine Beschreibung aller möglichen Parameter. Gekürztes Beispiel:

<?php

$soundcloud = 'http://soundcloud.com/oembed?format=xml&show_comments=false&url=';
$audio = 'http://soundcloud.com/beastieboys/sets/hot-sauce-committee-part-two';

$url = $soundcloud.urlencode($audio);
$daten = @simplexml_load_file($url);

echo $daten->html;

Die komplette Ausgabe:

SimpleXMLElement Object
(
    [version] => 1.0
    [type] => rich
    [provider-name] => SoundCloud
    [provider-url] => http://soundcloud.com
    [height] => 450
    [width] => 100%
    [title] => Hot Sauce Committee Part Two by Beastie Boys
    [description] => 2011 release, the long-awaited eighth album from the Hip Hop/Rock trio. Hot Sauce Committee Part Two was produced by Beastie Boys and mixed by Philippe Zdar. This album marks Mike "Mike D" Diamond, Adam "Ad Rock" Horovitz and Adam "MCA" Yauch's first full length effort since 2007's Grammy-winning all-instrumental The Mix-Up.
    [html] => <![CDATA[http://w.soundcloud.com/player/?url=http%3A%2F%2Fapi.soundcloud.com%2Fplaylists%2F737966&show_artwork=true]]>
)





Berechnung der zu fahrenden Distanz zwischen zwei Orten

7 04 2012

Bei der Punkkonzerte Datenbank setze ich eine Umkreissuche ein. Hierzu verwende ich die Daten der OpenGeoDB. So ist es möglich, nur nach Konzerten zu suchen, die in einem Umkreis von beispielsweise 50 km von meinem Heimatort stattfinden. Schön und gut, nur erfolgt diese Berechnung immer nach der Luftlinie, also dem kürzesten Weg von A nach B. Hier zeigt sich jedoch aufgrund der Straßenführung teilweise eine hohe Diskrepanz zur tatsächlich zu fahrenden Strecke.

Glücklicherweise stellen uns aktuelle Online-Routenplaner ihre Daten zur Berechnung des Straßen-Weges mittels einer API zur Verfügung. Freie Daten habe ich hierzu leider noch keine finden können. Die OpenStreetMap stellt ihre Daten zwar kostenfrei zum Download zur Verfügung, doch für ein paar Abfragen lohnt es erstmal nicht, sich gigabyteweise Daten in die eigene Datenbank zu pumpen.

Die Lizenzbedingungen der Google Maps sind recht verwirrend, dürften aber so zu verstehen sein, dass man die Daten nur in Verbindung mit der Anzeige einer Karte nutzen darf. Da ich lediglich die Distanz erhalten möchte und somit keine Verwendung für eine Karte habe, nutze ich für dieses Beispiel die Konkurrenz von Microsoft, Bing Maps.

Zunächst besorgen wir uns eine Windows Live ID im Bing Maps Account Center. Hier können wir unsere eigene E-Mail Adresse registrieren oder wir legen uns ein Hotmail Postfach an. Sind wir nun im Besitz eines Windows Live Zugangs, müssen wir uns nun den Account für die API erstellen. Sobald dies geschehen ist, finden wir im Menü unter „Create or view keys“ endlich die Möglichkeit, unseren Key zu erzeugen. Heraus kommt ein 64stelliger String, der so aussehen sollte (Beispiel-Key, zum Testen muss ein korrekter Key verwendet werden):
eN_U1StXBLh5N5mreI7RtdNXBS0_Ju9mclnMRNoNGyVkNeS7RcYRwVcP9NMCZMlS

Um nun eine Strecke von A nach B zu berechnen, benötigen wir nur noch die folgende URL:
http://dev.virtualearth.net/REST/v1/Routes?wp.1=%5BWegpunkt 1]&wp.2=[Wegpunkt 2]&key=[API Key]

Nehmen wir nun an, wir möchten von Gschlachtenbretzingen nach Michelbach an der Bilz fahren. Testen wir doch einfach einmal diese URL:
http://dev.virtualearth.net/REST/v1/Routes?wp.1=Gschlachtenbretzingen&wp.2=Michelbach an der Bilz-Gschlachtenbretzingen&key=eN_U1StXBLh5N5mreI7RtdNXBS0_Ju9mclnMRNoNGyVkNeS7RcYRwVcP9NMCZMlS

Das liefert uns einen riesigen Schwulst an Informationen im JSON Format. Irgendwo ziemlich am Ende versteckt sich die von uns gewünschte Information, die uns sagt, dass die travelDistance 3.158 km beträgt. Zwar ist das zurückgelieferte Format prädestiniert für die Weiterverarbeitung per JavaScript, doch wir möchten diese Informationen jetzt lieber im PHP haben, beispielsweise um diese gleich weiterzuverarbeiten.

<?php

// Die URL zum Dienst
$url = 'http://dev.virtualearth.net/REST/v1/Routes';

// Unser API Key
$key = 'eN_U1StXBLh5N5mreI7RtdNXBS0_Ju9mclnMRNoNGyVkNeS7RcYRwVcP9NMCZMlS';

// Alle Wegpunkte in einem Array. Koennen bis zu 25 sein. Es werden hier 
// verschiedene Moeglichkeiten der Angabe gezeigt
$a_wp = array(
	'Gschlachtenbretzingen',
	'Michelbach an der Bilz, BW, Deutschland',
	'74538 Rosengarten'
);

// Wie viele Wegpunkte haben wir im Array?
$anz_wp = count($a_wp);

// Erweitern der URL um die Wegpunkte
$url = $url.'?';
for($i = 0; $i < $anz_wp; $i++){
	$url .= 'wp.'.($i+1).'='.urlencode($a_wp[$i]).'&';
}

// Nun noch den API Key an die URL anhaengen
$url .= 'key='.$key;

// Wir holen uns die Daten vom Bing Server
$json_datei = file_get_contents($url);

// Der Server liefert die Daten im JSON Format, das ist eigentlich fuer
// Javascript gedacht. Deshalb muessen wir dieses umwandeln
$daten = json_decode($json_datei);

// Pruefen, ob wir die Daten auch korrekt erhalten haben
if ($daten->{'authenticationResultCode'} == 'ValidCredentials'){
	// Formatiere die Strecke
	$strecke =  number_format($daten->{'resourceSets'}[0]->{'resources'}[0]->{'travelDistance'}, 1, ',' , '.');
	// Formatiere die Dauer fuer die Fahrt
	$dauer = number_format(ceil($daten->{'resourceSets'}[0]->{'resources'}[0]->{'travelDuration'} / 60), 0, ',', '.');

	// Ausgabe unserer Informationen
	echo '<p>Die Strecke ';
	for($i = 0; $i < $anz_wp; $i++){
		echo $a_wp[$i];
		if($i+1 < $anz_wp) echo ' -> ';
	}
	echo ' beträgt '.$strecke.' Kilometer. Die Fahrt dürfte ohne Stau etwa '.$dauer.' Minuten dauern.</p>';
}

// Testausgabe mit allen erhaltenen Daten vom Bing Server zur Info
echo '<hr />';
echo '<p>Die zusammengesetzte URL: <a href="'.$url.'">'.$url.'</a></p>';
echo '<pre>';
print_r($daten);
echo '</pre>';

?>

Die Ausgabe sieht in diesem Fall wie folgt aus. In unserem Ergebnis sind noch viele weitere interessante Informationen versteckt. Ein genauerer Blick in die $daten kann sich also durchaus lohnen.

Die Strecke Gschlachtenbretzingen -> Michelbach an der Bilz, BW, Deutschland -> 74538 Rosengarten beträgt 12,1 Kilometer.
Die Fahrt dürfte ohne Stau etwa 17 Minuten dauern.




Formular-Spamming per PHP

30 03 2012

Ein Kontaktformular, über das der interessierte Besucher mit den Machern in Verbindung treten kann gehört zum guten Ton. Vergreifen sich die Webseiten-Macher selbst im guten Ton, möchte man denen auch gerne seine Meinung kundtun. Immer und immer wieder. Nehmen wir doch als fiktives Beispiel einen guten Deutschen, der zwar Zeit genug findet Minderheiten zu beschimpfen, allerdings nicht dafür, sein Kontaktformular vernünftig abzusichern. So ist es möglich, diesem ohne viel Aufwand zum Ersinnen mehr oder weniger einfallsreicher Texte Mails zukommen zu lassen. Als einziges Pflichtfeld ist die (falsch geschriebene) E-Mail gekennzeichnet, wobei noch nicht einmal eine rudimentäre Gültigkeitsprüfung stattfindet.

Plausibilitätsprüfung mangelhaft

Zum Test basteln wir uns ein Kontaktformular mit vier Feldern. Verlangt werden der Name, ein Betreff, die E-Mail Adresse sowie ein Text. Geprüft wird lediglich, ob die Angabe für die E-Mail nicht leer ist. Damit hätten wir zumindest von der Funktionalität das real existente Kontaktformular nachgebaut.

<?php
if (!empty($_POST)){
	// Die Pruefung beschraenkt sich darauf zu testen, ob das Feld "sender_email" nicht leer ist
	if (empty($_POST['sender_email'])){
		echo '<p>Bitte eine E-Mail Adresse angeben.</p>';
	}

	// Kein Fehler gefunden? Dann sende uns eine Mail
	else {
		// Aus der Angabe der E-Mail und des Namens wird der Absender erzeugt
		if (!empty($_POST['sender_name'])) $header = "From: ".$_POST['sender_name']." <".$_POST['sender_email'].">\n";
		else $header = "From: ".$_POST['sender_email']."\n";
		$header .= 'Content-Type: text/plain; charset="UTF-8";'."\n";
		$header .= 'Content-Transfer-Encoding: 8bit'."\n";
		$header .= "X-mailer: PHP/".phpversion()."\n";
		$header .= "\n";

		// Verschicke die Mail, der Betreff wird als ebensolcher in die Mail gesetzt, die Nachricht als Mail-Text
		if (mail('aaaaaprvdgrwwelt@example.org', $_POST['sender_betreff'], $_POST['sender_nachricht'], $header)){
			echo '<p>Die Anfrage wurde erfolgreich versendet.</p>';
		} else {
			echo '<p>Es ist ein Problem beim Mailversand aufgetreten.</p>';
		}
	}
}

?>

<!-- Das eigentliche Formular ausgeben -->
<form action="<?php echo $_SERVER['PHP_SELF']?>" method="post">
	<p><label>Name:</label> <input type="text" name="sender_name" <?php if (isset($_POST['sender_name'])) echo 'value="'.$_POST['sender_name'].'"'; ?> /></p>
	<p><label>*E-Mail:</label> <input type="text" name="sender_email" <?php if (isset($_POST['sender_email'])) echo 'value="'.$_POST['sender_email'].'"'; ?> /></p>
	<p><label>Betreff:</label> <input type="text" name="sender_betreff" <?php if (isset($_POST['sender_betreff'])) echo 'value="'.$_POST['sender_betreff'].'"'; ?> /></p>
	<p><label>Nachricht:</label> <textarea name="sender_nachricht"><?php if (isset($_POST['sender_nachricht'])) echo $_POST['sender_nachricht']; ?></textarea></p>
	<p><input type="submit" value="Senden" /></p>
</form>

Formular   E-Mail Formular

Mit wenig Aufwand wäre es uns möglich, dieses Formular immer und immer wieder abzusenden. Doch den händischen Aufwand ist uns das nicht wert. Also versuchen wir das Absenden zu automatisieren. Hierfür gibt es Tools, mit denen Spammer tagtäglich unschuldige Webseitenbetreiber belästigen. Daran ist nicht ganz einfach heranzukommen, also schauen wir, was wir nur mittels PHP selbst basteln können.

Das zu tun gibt es verschiedene Ansätze. Man könnte beispielsweise cURL nutzen. Für unser Beispiel verwende ich aus persönlicher Vorliebe und weil cURL nicht auf jedem PHP Webspace installiert ist fsockopen. Die einzelnen Zeilen in der Quelle sind kommentiert, großartig darauf eingehen möchte ich nicht, da man diese mit Grundlagenkenntnissen von PHP durchaus verstehen sollte.

<?php

// Die URL zum Formular. Kann im Quelltext des Formulars aus der Zeile
// <form action="[URL]"> gelesen werden
$url = 'http://demo.tiefergelegt.net/index.php';

// Die Felder, die wir fuellen wollen und der Wert, der gesetzt wird
$post_daten = array(
	'sender_email' => 'test123@example.org',
	'sender_name' => 'aaaaaprvdgrwwelt',
	'sender_betreff' => 'Liebe Gruesse',
	'sender_nachricht' => 'Lorem ipsum dolor sit amet'
);

// Aufsplitten der URL. Wir benoetigen spaeter die einzelnen Teile
$a_url = parse_url($url);

// Erstelle aus dem Array einen Query-String
$post_string =  http_build_query($post_daten);

// Oeffnen der Verbindung zum Remote Server
$fp = fsockopen($a_url['host'], 80) or die ('Konnte keine Verbindung öffnen zu '.$a_url['host']);

	// Senden der Daten
	fputs($fp, "POST ".$a_url['path']." HTTP/1.1\r\n");
	fputs($fp, "Host: ".$a_url['host']." \r\n");
	fputs($fp, "Content-Type: application/x-www-form-urlencoded\r\n");
	fputs($fp, "Content-Length: ".strlen($post_string)."\r\n");
	fputs($fp, "Connection: close\r\n\r\n");
	fputs($fp, $post_string);

// Verbindung wieder schliessen
fclose($fp);

// Bildschirmausgabe zur Information
echo 'Die Daten wurden übermittelt an '.$url.'<br />';
echo 'Der Post-String war '.$post_string;

?>

Meldung   E-Mail

Wollen wir nun nicht ständig den gleichen Text senden, den man relativ einfach aus dem Postfach löschen könnte und sind wir zudem viel zu unkreativ, uns selbst etwas auszudenken, automatisieren wir doch auch diesen Schritt. Wir benötigen lediglich zwei kleine Funktionen. Die erste, gen_woerter, erzeugt x zufaellig zusammengefügte Wörter. gen_email nutzt diese Funktion, um eine einigermaßen real aussehende Mailadresse zu generieren.

function gen_woerter($anzahl_min = 1, $anzahl_max = null, $laenge_min = 2, $laenge_max = 15){
	// Alle moeglichen Zeichen fuer die Woerter
	$zeichen = "abcdefghijklmnopqrstuvwxyz";
	// Variable initialisieren
	$woerter = '';

	// Da sowohl ein Wert fuer die minimale Anzahl Woerter als auch fuer die
	// maximale Anzahl angegeben wurde, muss ein zufaelliger Wert in diesem
	// Bereich erzeugt werden.
	if (is_numeric($anzahl_min) && is_numeric($anzahl)){
		$anzahl = mt_rand($anzahl_min, $anzahl_max);
	}
	// Ansonsten gilt der Wert von $anzahl_min
	else {
		$anzahl = $anzahl_min;
	}

	for ($i = 1; $i <= $anzahl; $i++){
		$wortlaenge = mt_rand($laenge_min, $laenge_max);

		// Erzeuge eine beliebige Zeichenfolge aus $zeichen mit einer
		// zufaelligen Laenge von $laenge_name
		for ($j = 1; $j <= $wortlaenge; $j++) {
			$woerter .= substr($zeichen, mt_rand(0, strlen($zeichen)), 1);
		}
		if ($i < $anzahl) $woerter .= ' ';
	}

	return $woerter;
}

function gen_email(){
	// Die moeglichen Top Level Domains, kann entsprechend erweitert werden
	$tlds = array("com","net","org","de");
	// Initialisieren der Variablen
	$email = "";

	// Erzeuge einen "Namen", nutze hierfuer die Funktion gen_woerter
	$email .= gen_woerter(1, null, 2, 15);

	// Eine E-Mail Adresse enthaelt immer ein @
	$email .= "@";

	// Das selbe Prozedere wie fuer den Namen nun auch fuer die Domain
	$email .= gen_woerter(1, null, 3, 15);

	// Haenge eine beliebige TLD aus unserem Array an
	$email .= '.'.$tlds[mt_rand(0, (sizeof($tlds)-1))];

	return $email;
}

Diese Funktionen nutzen wir in unserem Skript, um die Werte fuer die Felder zu füllen. Das könnte beispielsweisen so aussehen:

// Die Felder, die wir fuellen wollen und der Wert, der gesetzt wird
$post_daten = array(
	'sender_email' => gen_email(),
	'sender_name' => gen_woerter(1, 2, 3, 10),
	'sender_betreff' => gen_woerter(2, 5, 2, 12),
	'sender_nachricht' => gen_woerter(25, 50, 2, 12)
);

Das Ergebnis:

Das Ergebnis

Doch wie bringen wir das PHP Skript nun dazu, immer wieder auf das Formular loszugehen? Da gibt es mehrere Möglichkeiten. Wer einen Server inklusive Webspace sein eigen nennt und dazu noch crontab nutzen darf, stellt hier einfach einen minütlichen Aufruf ein. Das selbe können wir auch Remote mit unserem Linux PC machen, indem wir mit wget das Skript aufrufen (eine andere Alternative wäre die Verwendung von Lynx). Das sähe in etwa so aus:
* * * * * /usr/bin/wget "http://www.example.org/post.php"

Im Windows könnte das mit dem Taskplaner ähnlich funktionieren, wget gibt es auch für dieses System. Des weiteren könnten wir das Skript einfach im Hintergrund unserer eigenen Homepage laufen lassen, indem wir den Code in diese kopieren und die Ausgabezeilen löschen, bzw. per include die Seite aufrufen. Wir könnten auch eine eigene Webseite mit ein paar hübschen Bildchen erstellen, die diesen Code beinhaltet. Dann übernehmen unsere Besucher das Spamming.

Wichtiger Hinweis: Dieser Text soll nicht zur Nachahmung animieren. Das Spammen fremder Kontaktmailer könnte möglicherweise gegen irgendwelche Gesetze verstoßen. Von daher soll hier nur auf die technische Machbarkeit und die Notwendigkeit, geeignete Schutzmaßnahmen für ein Kontaktformular zu treffen hingewiesen werden. Den Beispielen mangelt es an einer vernünftigen Fehlerbehandlung, was aber egal ist, da wir diese ja eh niemals einsetzen werden.