js-home.org, 22.05.2019
 

Ein Adressbuch in LDAP
2004-01-20Jürgen Schmitz
LDAP, das super universal alles kann und mach Werkzeug. In dieser Dokumentation beschreibe ich, wie ein LDAP-Server mit OpenLDAP aufgesetzt wird und man darin seine Adressbuch-Daten für Netscape, Mozilla & Co speichern kann. Natürlich geht dann, wenn es erst mal läuft, noch mehr.
1Vorwort
2Schritt 1: Konfigurationsdateien
 2.1 Der Ausgangspunkt
 2.2 Server-Konfig
3Schritt 2: Erzeugen von Wurzel und Daten
 3.1 Server starten
 3.2 Wurzelbehandlung
 3.3 Organisations-Einheiten
 3.4 Eine Adresse
4Schritt 3: So geht es weiter
 4.1 An den Browser
 4.2 Eine Adresse für Mozilla
 4.3 Besonderheiten - z.B. für Deutsch
 4.4 Eine andere Sortierung
  4.4.1  XCmail
5Einige Tools
6Nachwort und Impressum
 
1 Vorwort
Nachdem ich nun mehrmals versucht habe, den OpenLDAP zum Laufen zu bringen, ist es mir nun endlich gelungen - dank verschiedenen Quellen im Internet. Leider waren aber alle diese doch immer wieder sehr wage oder veraltet.

Deswegen habe ich mich entschlossen, hier nun einige Erfahrungen und das Setup zu beschreiben.

Ein paar Worte zu LDAP:

LDAP ist ein baumartiger Verzeichnisdienst - wow! Das heißt im Klartext, in einer Baumstruktur (wie ein Filesystem auch eins ist) werden beliebige Daten nach festgelegten Normen abgelegt. Die Normen besagen dabei nur, welche Daten unter welchen Datenbankschlüsseln abgelegt werden - sonst könnte kein Programm die Daten eines anderen lesen. So gibt es z.B. einen Schlüssel "mail", der eben zur Speicherung einer eMail-Adresse dient oder besser gesagt, zur Auffindung, denn was wirklich drin steht, ist eine Sache der Eingabe.

Damit klar wird, was ein Eintrag so alles enthält, gibt es für jeden Eintrag eine Angabe, welche Typen darin enthalten sind. Hm, machen wir ein Beispiel:

Ein Eintrag im LDAP sei eine Kennung. Die liegt dann irgendwo im LDAP-Baum rum (wie man das dann findet und anlegt sehen wir später - Stichwort Pfad). Damit nun aber klar wird, daß dieser Eintrag eine Kennung ist, hat sie, neben den Daten, die für einen Kennung nötig sind (Login, Name, Passwort, etc.) eine Typ-Angabe. Der Typ wäre dann z.B. Person, UnixKennung und Passwort - es werden nämlich mehrere Typen vergeben, so kann man das modularer aufbauen.

Im Laufe der Beschreibung zum Aufbau des LDAP-Servers wird das hoffentlich klarer. In Kapitel 3 Schritt 2: Erzeugen von Wurzel und Daten wird das ganze nochmal im konkreten Anwendungsfall gezeigt.
 
2 Schritt 1: Konfigurationsdateien
Als erstes gilt es, den OpenLDAP zu installieren - naja, das lassen wir mal hier weg, entweder Quellen selbst bauen oder über die Tools der jeweiligen Distribution installieren. Aber noch nicht starten!
 
2.1 Der Ausgangspunkt
Angefangen wird mit der Datei /etc/openldap/ldap.conf:

ldap.conf
##########/etc/openldap/ldap.conf#########

# Mehr Details auf der Man-Page ldap.conf(5)
# Diese Datei sollte global lesbar sein

# hier wird der LDAP Server angegeben, Rechnername oder IP Adresse
host 127.0.0.1

# wenn Sie eine Suche starten, ist dies der Ausgangspunkt
# (der erste Punkt im Verzeichnisbaum der Datenbank).

base  o=js-home.org

# das war auch schon alles in dieser Datei

############################################


Den Ausgangspunkt (o -> Origin) js-home.org ersetzt jeder natürlich durch einen eigenen Namen - beliebig.
 
2.2 Server-Konfig
Als nächstes gilt des, den Ldap-Server "slapd" zu konfigurieren. Auch hier gehen wir von dem Ausgangspunkt "o=js-home.org" aus. Dazu sollte die Datei /etc/openldap/slapd.conf wie folgt aussehen:

slapd.conf
######### /etc/openldap/slapd.conf ###########
#
# See slapd.conf(5) for details on configuration options.
# This file should NOT be world readable.
# diese Datei sollte nicht global lesbar sein
#
include		/etc/openldap/schema/core.schema
include		/etc/openldap/schema/cosine.schema
include         /etc/openldap/schema/nis.schema
include         /etc/openldap/schema/misc.schema
include		/etc/openldap/schema/inetorgperson.schema
include		/etc/openldap/schema/extension.schema
include		/etc/openldap/schema/mozillaV06.schema

schemacheck     on

pidfile         /var/run/slapd.pid
argsfile        /var/run/slapd.args

allow bind_v2

# Einstellungen für die LDAP Datenbank
# hier wird die zu verwendende Datenbank bestimmt

database        ldbm

# Endung bzw. Verzeichniswurzel. Dies ist der oberste Eintrag im
# LDAP Verzeichnis
suffix          "o=js-home.org"

# Hier wird dir LDAP-Datenbank gespeichert
directory       /var/lib/ldap

# der (einzigartige) Name des Verwalters des Verzeichnisses
rootdn          "cn=Manager, o=js-home.org"

# Hier wird das Passwort der Verwalters festgelegt
rootpw         JShome
############################################


access to attribute=userPassword
        by dn="cn=Manager, o=js-home.org" write
        by anonymous auth
	by self write
        by  none
access to *
        by dn="cn=Manager, o=js-home.org" write
	by anonymous read
        by users write


In dieser Datei wird ein Schema "mozillaV06.schema" eingelesen. Dieser ist über Mozilla's Bugzilla im Bug 116692 zu bekommen.

Das Passwort "JShome" sollte man natürlich an seine Bedürfnisse anpassen, wie auch die "js-home.org" Texte.
 
3 Schritt 2: Erzeugen von Wurzel und Daten
LDAP ist, wie schon erwähnt, ein Baumartiger Verzeichnisdienst. Das heißt, es gibt eine Wurzel und darunter befinden sich dann Einträge - wobei sich unter Einträgen wiederum Einträge befinden können. Das ganze ist damit relativ ähnlich einem Dateisystem.

Nun gilt es diese Wurzel zu erzeugen. Wir erzeugen erstmal ein Verzeichnis /etc/openldap/ldif. Dann erzeugen wir eine Textdatei /etc/openldap/ldif/rootdb.ldif:

rootdb.ldif
dn: o=js-home.org
o: js-home.org
objectClass: top
objectClass: organization


Auch hier ist wieder "js-home.org" unsere Ausgangspunkt - also die Wurzel.

Nun ist eine Wurzel alleine ziemlich Sinnlos - und in einer Textdatei auch, denn sie soll ja ins LDAP.
 
3.1 Server starten
Zuerstmal, bevor irgendwas in die LDAP-Datenbank kann, muß LDAP laufen. Wir haben alles konfiguriert, also starten wir den Server nach bekannter Manier:

/etc/init.d/openldap start (openldap könnte auch anders heißen, z.B. nur ldap)

Den Start des LDAP-Servers sollte man dann, wenn alles läuft, in die Boot-Prozedur in den rcX.d-Verzeichnissen für die gewünschten Runlevel eintragen.
 
3.2 Wurzelbehandlung
Nun fügen wir also die Wurzel ein:

ldapadd -x -D "cn=Manager, o=js-home.org" -w JShome < /etc/openldap/ldif/rootdb.ldif


Wie jedesmal: js-home.org ist der Ausgangspunkt und an den eigenen Namen anzupassen - wie auch das Passwort "JShome".
 
3.3 Organisations-Einheiten
Wie in einem Filesystem auch ordnende Strukturen angelegt werden, Verzeichnisse, legt man diese auch im LDAP an. Dort kann aber, wie schon gesagt, jeder Eintrag weitere Einträge beinhalten. Allerdings gibt es spezielle Eintragtypen, die ou's - Orginisational Units.

Für unser LDAP-Adressbuch erzeugen wir eine ou "people" und legen darunter die Adressen ab.

Also eine weitere Datei /etc/openldap/ldif/peopleou.ldif:

peopleou.diff
dn: ou=people,o=js-home.org
ou: people
objectClass: top
objectClass: organizationalUnit


"js-home.org" ist hier wiedermal der Ausgangspunkt. "dn" gibt den "Pfad" an.

Möchte man z.B. seine privaten Adressen von denen der Arbeit trennen, könnte man statt der "People"-OU 2 andere anlegen, eine "privat" und eine "arbeit". Alternativ könnte man diese beiden auch unter die "people"-OU packen (dn: ou=privat,ou=people,o=js-home.org).

Diese ou muß jetzt nur noch ins LDAP:

ldapadd -x -D "cn=Manager, o=js-home.org" -w JShome < /etc/openldap/ldif/peopleou.ldif

 
3.4 Eine Adresse
So, nun haben wir endlich alle Strukturen aufgebaut, um Adressen eingeben zu können.

Es gibt durchaus fertige Programme, die einem dann dabei helfen können, diese setzen jedoch oft eigene Strukturen voraus bzw. legen sie an. Wir versuchen es erstmal von Hand.

Wir wollen also nun im ou=people einen Adressbuch-Eintrag anlegen, erstmal nur eine eMail-Adresse. Also eine weitere Textdatei /etc/openldap/ldif/address1.ldif:

address1.ldif
dn: cn=Scott Tiger,ou=people,o=js-home.org
cn: Scott Tiger
givenName: Tiger
sn: Scott
mail: Tiger.Scott@typical-oracle.com
objectClass: top
objectClass: inetOrgPerson
objectClass: person
objectClass: organizationalPerson


Naja, den Typen "Scott" kennt vielleicht der eine oder andere, der schonmal mit Oracle in Kontakt war.

Rein damit ins LDAP:

ldapadd -x -D "cn=Manager, o=js-home.org" -w JShome < /etc/openldap/ldif/address1.ldif

 
4 Schritt 3: So geht es weiter
Sehen wir uns den letzten Eintrag aus Kapitel 3.4 Eine Adresse mit einer Adresse mal etwas genauer an (zur besseren Besprechung mit Zeilennummer):

(1) dn: cn=Scott Tiger,ou=people,o=js-home.org
(2) cn: Scott Tiger
(3) givenName: Tiger
(4) sn: Scott
(5) mail: Tiger.Scott@typical-oracle.com
(6) objectClass: top
(7) objectClass: inetOrgPerson
(8) objectClass: person
(9) objectClass: organizationalPerson


In Zeile 1 der dn - der Pfad. Ist also in der Organisationseinheit People und dem Ausgangspunkt js-home.org. Hier steht auch bereits der Name des Eintrags drin (cn).

In Zeile 2 steht dann der Name nochmal, das Feld ist Pflicht.

Die Zeile 3 enthält ein Feld, das es nur bei Personen gibt - genauer bis inetOrgPersonen. Dazu gleich ab Zeile 6.

Nummer 4 ist der Nachname, Pflicht bei Personen-Einträgen, 5 die eMail-Adresse, die verdanken wir auch wieder der inetOrgPerson.

Kommen wir also endlich zu den Zeilen 6 bis 9. Diese spezifizieren den Typ des Eintrags. Jeder Typ hat seine Menge an Feldern, wie eben z.B. mail und givenName aus inetOrgPerson.

Wollen wir mit Netscape bzw. Mozilla arbeiten, sollte man außerdem die objectClass "mozillaOrgPerson" aufnehmen. Diese steht im schon erwähnten Schemafile "mozilla".

 
4.1 An den Browser
Sehen wir uns noch schnell an, wie man dem Browser erzählt, daß es da ein LDAP mit Adressdaten gibt, exemplarisch für den Netscape 7-Browser:

In den Preferences im Bereich "Mail & Newsgroups" auf "Addresing" gehen. Dort einen Haken bei "Directory Server" und auf "Edit Directory". Im neuen Fenster per "Add" einen neuen Server erzeugen und im wiederum neuen Fenster dem ganzen einen Namen geben, wie z.B. "privat". Als "Hostname" den Namen des Servers, auf dem LDAP läuft und als "Base DN" den schon so oft erwähnten Ausgangspunkt "o=js-home.org". Der "Port" bleibt bei 389, fertig.

Manchmal muß Netscape jetzt beendet und neu gestartet werden, damit das Zeugs auch im Adressbuch bekannt ist.

Wir öffnen dann das Addressbuch per "Window->Address Book". Dort sollte jetzt unser Buch "privat" stehen. Wählt man es aus, kann man nach Adressen darin suchen - sofern schon was drin steht.
 
4.2 Eine Adresse für Mozilla
Netscape und auch Mozilla kennen etliche Felder im Adressbuch. Einige davon sind in "inetOrgPerson" zu finden, andere bereits in "person" und "organizationalPerson". Aber einige sind erst in der objectClass "mozillaOrgPerson". Ein völlständiger Eintrag, der alle z.Z. möglichen Felder des Mozilla-Adressbuchs füllt, sieht so aus:

dn: cn=first last,ou=privat,o=js-home.org
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
objectclass: mozillaOrgPerson
givenName: first
sn: last
cn: first last
mail: last@js-home.org
mozillaSecondEmail: add@email
telephoneNumber: work phone
homePhone: home phone
facsimileTelephoneNumber: fax
pager: pager
mobile: mobile
homePostalAddress: home address 1
mozillaHomePostalAddress2: home address 1
mozillaHomeLocalityName: home city
mozillaHomeState: home state
mozillaHomePostalCode: home zip
mozillaHomeCountryName: home country
postalAddress: work address1
mozillaPostalAddress2: work address2
l: work city
st: work state
postalCode: work zip
c: work country
title: work title
ou: work departement
o: work organization
mozillaCustom1: custom 1
mozillaCustom2: custom 2
mozillaCustom3: custom 3
mozillaCustom4: custom 3
description: notes


Es ist aber nun Geschmackssache, ob mal all diese Felder benutzen möchte. Dazu kommt noch, daß Mozilla da z.Z. einen Bug hat und die ganzen Felder mit der Home-Adresse nicht richtig auswertet und nur die der Work-Adresse kennt. Das soll aber behoben werden.
 
4.3 Besonderheiten - z.B. für Deutsch
Wie immer, kommt LDAP nicht wirklich mit Umlauten klar. Und selbst wenn doch, kommen vielleicht die Programme, die darauf zugreifen dann damit nicht klar. Wie auch immer, wenn man sowas nicht garantieren kann, macht man was: man denkt sich was aus.

Konkret führt das dazu, daß man alle Einträge UTF8 kodiert, was bei den Buchstaben und Zahlen nicht weiter auffällt, bei Umlauten dagegen schon. Und weil Umlaute in UTF8 nicht mehr so richtig ASCII sind, wird das ganze dann noch durch den base64-Wolf gedreht.

Mein Vorname sieht dann so aus: "U2NobWl0eiBKw7xyZ2Vu".

Und damit LDAP bzw. die Clients das dann erkennen, werden die Einträge mit 2 Doppelpunkten statt einem vom Bereichner getrennt:

givenName:: SsO8cmdlbg==
sn: Schmitz
cn:: U2NobWl0eiBKw7xyZ2Vu


 
4.4 Eine andere Sortierung
Nach etlichen Experimenten und Überlegungen hab ich mein LDAP-Adressbuch etwas anders aufgebaut, als hier beschrieben. Ja, typisch, gell, dem "gemeinen Volk" gibt man eine simple Lösung, aber für die "besseren Herren" gibts statt dessen eine edle komplizierte aber mächtigere Lösung....

Ganz so ist es zwar nicht, aber das mit komplizierter und mächtiger könnte stimmen - zumindest aus meiner bisherigen Sicht und Erfahrung mit LDAP.

Die Idee ist jedenfalls ganz Simpel. Statt die Adressen direkt in die ou=people zu machen, lege ich dort erstmal eine Person an und erzeuge darin dann die Adresseinträge. Das einfach aus dem Grund, da mein altes Adressbuch für einige Personen mehrfache eMail-Adressen enthielt.

Ein Eintrag sieht dann so aus:

dn: cn=Schmitz Juergen,ou=people,o=js-home.org
objectclass: top
objectclass: person
objectclass: organizationalPerson
sn: Schmitz
cn: Juergen Schmitz

dn: cn=address_1,cn=Schmitz Juergen,ou=people,o=js-home.org
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
objectclass: mozillaOrgPerson
givenName: Juergen
sn: Schmitz
cn: Schmitz Juergen
mail: js@XXXXXXXXXXXXXXXXXXX
telephoneNumber: +49-(0)-89-XXXXXXXXXXXX
mobile: +49-160-XXXXXXXXX
homePostalAddress: XXXXXXXXXXXXXXXXXXXXXX XX
mozillaHomeLocalityName: Muenchen
mozillaHomeState: BAY
mozillaHomePostalCode: 81XXX
mozillaHomeCountryName: Germany
postalAddress: XXXXXXXXXXXXXXXXXXXXXX XX
l: Trier
st: RLP
postalCode: 54290
c: Germany
mozillaNickname: Juergen
mozillaWorkUrl: http://www.js-home.org/
mozillaHomeUrl: http://www.js-home.org/

dn: cn=address_2,cn=Schmitz Juergen,ou=people,o=js-home.org
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
objectclass: mozillaOrgPerson
givenName: Juergen
sn: Schmitz
cn: Schmitz Juergen
mail: j.schmitz@XXXXXXXXXXXXX
mozillaNickname: Juergen


Auf die Art kann ich z.B. die eMail-Adressen von Bekannten sowohl bei ihrer Firma, wie auch privat einfach zusammenfassen.

 
4.4.1 XCmail
Da mein altes Adressbuch in XCmail existierte, wollte ich das natürlich relativ einfach nach LDAP bringen. Folgendes Script hat das dann gemacht:

xcmail2ldif.php
#!/usr/local/bin/php
<?php

$names=array();

function strconv($s)
{
	$r=utf8_encode($s);
	if ($r!=$s)
		$r=": ".base64_encode($r);
	else
		$r=" ".$r;
	return $r."\r\n";
}
function convert($address)
{
	global $names;
	if (sizeof($address)>0)
	{
		if (strlen($address["LASTNAME"])==0)
			$address["LASTNAME"]=$address["EMAIL"];
		if ($names[$address["LASTNAME"]."_".$address["FIRSTNAME"]]>0)
		{
		}
		else
		{
			echo "dn:".strconv("cn=".trim($address["LASTNAME"]." ".$address["FIRSTNAME"]).",ou=people,o=js-home.org");
			echo "objectclass: top\r\n";
			echo "objectclass: person\r\n";
			echo "objectclass: organizationalPerson\r\n";

			if ($address["LASTNAME"]) echo "sn:".strconv($address["LASTNAME"]);
			echo "cn:".strconv(trim($address["LASTNAME"]." ".$address["FIRSTNAME"]));

			echo "\r\n";

			$names[$address["LASTNAME"]."_".$address["FIRSTNAME"]]=0;
		}
		$names[$address["LASTNAME"]."_".$address["FIRSTNAME"]]++;
		$extra="cn=address_".$names[$address["LASTNAME"]."_".$address["FIRSTNAME"]].",";
		echo "dn:".strconv($extra."cn=".trim($address["LASTNAME"]." ".$address["FIRSTNAME"]).",ou=people,o=js-home.org");
		echo "objectclass: top\r\n";
		echo "objectclass: person\r\n";
		echo "objectclass: organizationalPerson\r\n";
		echo "objectclass: inetOrgPerson\r\n";
		echo "objectclass: mozillaOrgPerson\r\n";

		if ($address["FIRSTNAME"]) echo "givenName:".strconv($address["FIRSTNAME"]);
		if ($address["LASTNAME"]) echo "sn:".strconv($address["LASTNAME"]);
		echo "cn:".strconv(trim($address["LASTNAME"]." ".$address["FIRSTNAME"]));
		echo "mail:".strconv($address["EMAIL"]);
		if ($address["WORKPHONE"]) echo "telephoneNumber: ".$address["WORKPHONE"]."\r\n";
		if ($address["FAX"]) echo "facsimileTelephoneNumber: ".$address["FAX"]."\r\n";

		if ($address["MOBIL"]) echo "mobile: ".$address["MOBIL"]."\r\n";
		if ($address["ADDRESS"]) echo "homePostalAddress:".strconv($address["ADDRESS"]);
		if ($address["CITY"]) echo "mozillaHomeLocalityName:".strconv($address["CITY"]);
		if ($address["STATE"]) echo "mozillaHomeState:".strconv($address["STATE"]);
		if ($address["ZIP"]) echo "mozillaHomePostalCode:".strconv($address["ZIP"]);
		if ($address["COUNTRY"]) echo "mozillaHomeCountryName:".strconv($address["COUNTRY"]);

		if ($address["ADDRESS"]) echo "postalAddress:".strconv($address["ADDRESS"]);
		if ($address["CITY"]) echo "l:".strconv($address["CITY"]);
		if ($address["STATE"]) echo "st:".strconv($address["STATE"]);
		if ($address["ZIP"]) echo "postalCode:".strconv($address["ZIP"]);
		if ($address["COUNTRY"]) echo "c:".strconv($address["COUNTRY"]);

		if ($address["ALIAS"]) echo "mozillaNickname:".strconv($address["ALIAS"]);
		if ($address["URL"]) echo "mozillaWorkUrl:".strconv($address["URL"]);
		if ($address["URL"]) echo "mozillaHomeUrl:".strconv($address["URL"]);

		if ($address["NOTE"]) echo "description:".strconv($address["NOTE"]);
		echo "\r\n";
	}
}

if ($addr=file($argv[1]))
{
	echo "dn: ou=people,o=js-home.org\r\n";
	echo "ou: people\r\n";
	echo "objectClass: top\r\n";
	echo "objectClass: organizationalUnit\r\n";
	echo "\r\n";

	$address=array();
	foreach($addr as $l)
	{
		if (ereg("^ADDRESS.*",$l,$res))
		{
			convert($address);
			$address=array();
		}
		else if (ereg("\.([A-Z]+):(.+)",$l,$res))
		{
			$address[$res[1]]=trim(str_replace("\\n"," ",str_replace(",","_",trim($res[2]))));
		}
	}
}

convert($address);

?>


Es erzeugt auch gleich die ou=people. Als Argument gibt man den Dateinamen des XCmail-Adressbuchs an (~/.XCmail/addressbook) und auf der Standard-Ausgabe bekommt man den ldif-Code, den man dem ldapadd übergibt.

Es kümmert sich um die UTF-8-Sachen, das evtl. nötige base64 und den 2. Doppelpunkt und ersetzt außerdem Leerzeichen durch Unterstriche, wo sie nicht erlaubt sind.

Das Script ist in PHP beschrieben.
 
5 Einige Tools
Zu guter letzt noch einige Programme um mit der nun laufenden LDAP-Datenbank einfach arbeiten zu können.

Genaugenommen hat es kein Programm in meine Bookmarks geschafft, immerhin aber eins auf meinen Web-Server:

  • PHP LDAP Admin eignet sich recht gut, um die LDAP-Datenbank zu warten und sich das leidige ldapadd zu sparen. Also, wer seine ou=people erzeugt hat, kann dann die Adressen damit eingeben oder auch neue ou's erzeugen oder auch die Ausgabe des XCmail-Konverters einlesen.
  • Freshmeat.net bietet einige interessante Tools.
  • darunter den Java LDAP Browser
  • und andere Adressbook-Tools, die aber alle sehr oft nur mit ihrem Aufbau der LDAP-Struktur arbeiten wollten. Naja, das vereinfacht die Eingabe, aber wenn man mehr will als ein Adressbuch wird es damit nicht leichter.

 
6 Nachwort und Impressum
So, ich hoffe, das hat etwas geholfen. Im Forum gibts Raum für Fragen und Diskussionen - bitte nur dort. eMails sind natürlich willkommen, bei Fragen werde ich jedoch jeden bitten, diese im Forum zu stellen, damit auch andere Leute, die auf das Stoßen, schnell zur Lösung finden.

Inzwischen gibts auch weitere Text, um den aufgebauten LDAP-Server für weitere Funktionen zu nutzen. Einfach mal in der Wissensbasis stöbern!

© 2004 js-home.org, alle Rechte liegen beim Autor; Kopien, auch auszugsweise, nur mit Genehmigung; Verlinkung bitte immer nur auf das Inhaltsverzeichnis.

Achja, wer mag, darf mal auf einiger der Banner klicken - am besten mal jedes ;-) und wer weiß, vielleicht kauft man ja dort dann was...




All actions are logged, copyright © JS