Webzentriert

Auf zentral verwaltete Daten, wie sie etwa ein LDAP-Server bereithält, will man heutzutage über ein Webfrontend zugreifen. Neben Perl bietet auch PHP Möglichkeiten dazu.

In Pocket speichern vorlesen Druckansicht 1 Kommentar lesen
Lesezeit: 8 Min.
Von
  • Jan Kneschke
  • Kristian Köhntopp

Die weborientierte Skriptsprache PHP - ein rekursives Akronym namens ‘PHP: Hypertext Preprocessor’ - erlaubt die Entwicklung von komfortablen Frontends zum Zugriff auf LDAP-Server. Das macht auf die Bedürfnisse von Endbenutzern zugeschnittene Anwendungen möglich, ohne dass die sich durch eine Kommandozeile quälen oder mit trägen und allgemein gehaltenen Administrationsanwendungen herumärgern müssen.

Ein Webfrontend, in dem der Benutzer Zugriff auf seine persönlichen Daten erhält, die er unter seiner Verwaltung hat (Passwort, Urlaubsnachrichten, Signatur ...), erleichtert dem Administrator das tägliche Leben. Im Zusammenhang mit Webfrontends eignet sich besonders PHP, da es ein Leichtes ist, sichere und flexible Seiten zu entwickeln. Dieser Artikel demonstriert die LDAP-API von PHP an einem Beispiel, das die Grundfunktionen zum Ändern der Passwörter mit LDAP bereitstellt. Der Zugriff auf andere Attribute erfolgt analog und lässt sich leicht ergänzen.

Ein Zugriff auf einen LDAP-Baum folgt immer dem folgenden Schema:

  • Verbindung zum LDAP-Server aufbauen;
  • anonymes bind an den LDAP-Server;
  • Suche des Distinguished Name (DN), der zum Usernamen gehört;
  • Mit Hilfe des DN und des Passworts den bind durchführen;
  • Modifikationen vornehmen;
  • Auflösen der Bindung;
  • erbindung abbauen.
Mehr Infos

Distinguished Name

Was den Datenbanken der Primary Key ist, ist dem LDAP-Baum der Distiguished Name (DN). Er dient zur eindeutigen Benennung eines Knotens innerhalb des Baumes. Ausgehend von der Wurzel, dem Base-DN, erweitern alle anderen Knoten den DN um jeweils eine Ebene. Dabei muss jedes Mal wieder ein eindeutiger DN entstehen.

Auf den ersten Blick erscheint das etwas umständlich. Bei näherer Betrachtung ist es jedoch der einzig praktikable Weg: Um sich gegenüber dem LDAP-Server zu authentifizieren, benötigt man einen DN und ein Passwort. Letzteres ist dabei in dem Objekt enthalten, das durch den DN eindeutig bezeichnet wird. Da einem Benutzer nicht zuzumuten ist, sich den oftmals langen und aus vielen Komponenten bestehenden DN seines Eintrags im LDAP zu merken, verwendet man ein eindeutiges Attribut, das im dazugehörigen Objekt gespeichert ist. Prinzipiell kann jedes Attribut dazu dienen, aber in der Praxis verwendet man meist den Usernamen.

Mit Hilfe des ersten, anonymen Bind lässt sich dieses Attribut im LDAP suchen. Mit dem auf diese Weise ermittelten Benutzer-DN kann man den eigentlichen Anmeldeprozess durchführen. Danach stehen für die weiteren Aktionen die Rechte des Users bereit. Ein korrekt konfigurierter Server wird nach einem anonymen Bind (hoffentlich) nicht mehr als diese eine Suchoperation erlauben.

Die LDAP-API von PHP baut direkt auf der API von C auf, die verschiedene Hersteller (Netscape, Sun, OpenLDAP) bereitgestellt haben. Daher ist es recht einfach, vorhandenen C-Code nach PHP zu portieren. Um überhaupt mit LDAP arbeiten zu können, muss man eine Verbindung aufbauen und sich authentifizieren. Dazu dienen die Funktionen ldap_connect und ldap_bind, zu denen es ein entsprechendes Gegenstück gibt, um die Verbindung wieder abzubauen (ldap_close, ldap_unbind). ldap_close und ldap_unbind rufen dabei intern dieselbe Funktion auf, wobei ldap_unbind bevorzugt verwendet werden sollte, da diese Funktion der C-API folgt und damit der Code zwischen den beiden Sprachen portabler bleibt.

PHP-LDAP-API
Verbindung aufnehmen
ldap_connect Netzwerkverbindung zum Server aufbauen
ldap_close Netzwerkverbindung zum Server trennen
ldap_bind Authentifizieren gegenüber dem Server
ldap_unbind Auflösen der Verbindung
Suchen
ldap_read Auslesen eines Knotens
ldap_list Auslesen aller Knoten direkt unterhalb des Knotens
ldap_search Auslesen aller Knoten unterhalb des Knotens
ldap_free_result Freigeben des Suchergebnisses
Daten lesen
ldap_count_entries Ermitteln der gefunden Knoten
ldap_first_entry Ersten Knoten auslesen
ldap_next_entry Sukzessives Lesen der weiteren Knoten
ldap_get_entries Lesen aller Knoten in ein mehrdimensionales Array
ldap_first_attribute Erstes Attribut auslesen
ldap_next_attribute Sukzessives Lesen der weiteren Attribute
ldap_get_attributes Lesen aller Attribute in ein mehrdimensionales Array
ldap_get_values Auslesen der Attribute und der Werte eines Knotens
ldap_get_dn Ermitteln des DNs des ausgelesenen Knotens
Sonstiges
ldap_explode_dn Zerlegen des DNs in seine einzelnen Teile
ldap_dn2ufn Um den DN für den Benutzer lesbarer zu machen
Modifikationen
ldap_add Anlegen eines Objektes
ldap_delete Löschen eines Objektes
ldap_mod_del ldap_mod_replace,ldap_modify ldap_mod_add, Ändern der Attribute eines Objektes

Wie erwähnt, benötigt man für ein ldap_bind den DN des Benutzers, mit dem er sich gegenüber dem LDAP-Server authentifizieren soll. Deswegen gilt es zuerst, eine Verbindung zwischen dem vom Frontend übergebenen Usernamen und dem DN des dazugehörigen Eintrags im LDAP zu finden. Schließlich können Anwender sich ihren kompletten DN kaum merken: cn=Jan Kneschke, ou=People, dc=Company, dc=Incremental, dc=Internet. Ein Username wie jk oder Jan Kneschke ist entschieden anwenderfreundlicher.

Mehr Infos

Der unbekannte LDAP-Server

Um sich einem LDAP-Server auf der Kommandozeile zu nähern, benötigt man zunächst den Base-DN, der sich (obwohl nicht standardisiert) bei recht vielen LDAP-Servern mittels

$ ldapsearch -h <host> -b "" -s base "objectclass=*"

ermitteln lässt. Im Ergebnis trägt das Attribut namingContexts die verfügbaren DN, die für ein weiteres Kennenlernen des LDAP-Baums nötig sind.

$ ldapsearch -h <host> -b "<wert von namingContexts>" "objectclass=*"

gibt einem anschließend alles in die Hand, was man für das erste Kennenlernen braucht. Mancher mag auf die Idee kommen, die anonyme Verbindung gegen den LDAP-Server zu verbieten, um diesen Zugriff von außen zu unterbinden. Die daraus entstehenden Probleme wurden oben beschrieben.

Ein Firewall, der unter anderem auch den Port 389 vom Internet fern hält, wäre aber auf jeden Fall angebracht. Zusätzlich sollte man den LDAP-Server mit passenden Access Control Lists so sichern, dass anonyme Benutzer nur auf die zur weiteren Anmeldung notwendigen Informationen zugreifen können.

Die Konzepte, die hinter der Rechteverwaltung mit Access Control Lists liegen, werden leider von der LDAP-Spezifikation nicht genormt, sodass bei einem Umzug auf den LDAP-Server eines anderen Herstellers mit großer Wahrscheinlichkeit an dieser Stelle zum einen ein neues Konzept als auch eine neue Notation der Rechtevergabe erlernt werden muss.

Dazu muss zunächst ein anonymous bind gegen den LDAP-Server gemacht werden, und dann beginnt mit Hilfe des passenden Usernamens die Suche nach dem DN. Dafür stehen drei Funktionen zur Verfügung, die die drei verschiedenen Suchvarianten der LDAP-API abdecken.

  • ldap_read: Lesen des Objekts, das durch den angegebenen DN bezeichnet wird.
  • ldap_list: Lesen der Objekte direkt unterhalb des angegebenen DN.
  • ldap_search: Lesen aller Objekte unterhalb des angegebenen DN.

Um die Suche einzugrenzen, übergibt man den Suchfunktionen einen Filter und (optional) die gewünschten Attribute. Zum Auffinden des Benutzer-DN muss der gesamte Baum nach einem Objekt durchsucht werden, das als Attribut uid den angegebenen Usernamen hat.

  ldap_search($link, $root_dn, "uid=$uid");

Da ein Username eindeutig sein sollte, erwartet man, dass maximal ein Eintrag gefunden wird. Dazu dient die Funktion ldap_count_entries, die die Anzahl der gefundenen Einträge zurückliefert.

Um nun endgültig den korrekten DN zu ermitteln, muss das gefundene Objekt eingelesen werden. Das erledigen die Funktionen ldap_first_entry und ldap_next_entry. ldap_first_entry holt das erste Objekt vom Server. Alle weiteren liest ldap_next_entry in einer Schleife. Nach dem Laden des LDAP-Objekts kann man endlich mit ldap_get_dn den DN erfahren, der für die richtige Authentifizierung des Users gegenüber dem LDAP-Server nötig ist.

Nach erfolgreicher Ermittlung des korrekten DN kann die eigentliche Authentifizierung gegenüber dem LDAP-Server erfolgen. Gelingt die, stehen ab diesem Zeitpunkt dem Benutzer alle Rechte zur Verfügung, die er gegenüber dem LDAP-Server hat. Er darf zum Beispiel sein Passwort ändern und erhält Einsicht in Attribute, die einem anonymen Benutzer verborgen bleiben.

Mehr Infos

Online-Ressourcen

[1] OpenLDAP (freie LDAP-Implementierung)

[2] PHP Manual

[3] LDAP-Schema-Viewer (nicht mehr aktiv)

[4] Summary of the X.500(96) User Schema for use withLDAP

[5] Seite des Autors zu LDAP (nicht mehr aktiv)

Die erworbenen Rechte werden im Folgenden zum Ändern des Attributes userpassword verwendet. Zum Ändern von LDAP-Attributen dient die Funktion ldap_modify, die Attribute anlegen, löschen und ihnen neue Werte zuweisen kann. Dabei ist zu beachten, dass man nur erlaubte Attribute verwendet. Welche erlaubt sind, ist durch die Objektklassen festgelegt, die dem durch einen DN bezeichneten Objekt zugeordnet sind.

Ein Attribut wird gelöscht, indem ldap_modify ein leeres Array übergeben wird. Wenn ein Attribut keinen Wert hat, wird es automatisch aus dem Objekt gelöscht, andernfalls wird es auf die angegebenen Werte gesetzt. Die Funktion verlangt ein Array als Parameter, um der LDAP-Eigenart Rechnung zu tragen, dass Attribute mehrere Werte annehmen können, falls dies in der Objektdefinition nicht anders verlangt wird. Auch ldap_modify hat Eigenarten: So kann man nicht einfach nur einen einzelnen Attributwert ändern, sondern muss immer alle Attribute vollständig neu schreiben - will man dies nicht, stehen die Funktionen ldap_mod_add, ldap_mod_del, ldap_mod_replace zur Verfügung, die gezielt Modifikationen vornehmen.

Nach dem erfolgreichen Abschluss der Änderungen baut ldap_unbind die Verbindung zum LDAP-Server ab. Obwohl die Verbindung am Ende des Skriptes von alleine geschlossen wird, sollte man dennoch selbst die Verbindung abbauen, da besonders das NDS von Novell allergisch auf offene Netzwerkverbindungen reagiert.

LDAP ist in Unternehmen recht erfolgreich, da seine offene und flexible Schnittstelle einen einfachen und doch sicheren Zugriff auf die Daten erlaubt. Die MTAs Sendmail, Qmail und SIMS können ihr Mailrouting über LDAP steuern, Microsoft ersetzt sein Domänenkonzept durch ActiveDirectory, das ebenfalls über eine LDAP-Schnittstelle verfügt, und das in der Zugriffskontrolle eingesetzte PAM hat natürlich auch ein pam_ldap-Modul, das sich zur Authentifizierung in Bezug auf Webserver verwenden lässt.

Die mit den meisten LDAP-Servern mitgelieferten Verwaltungswerkzeuge sind jedoch für viele Einsatzzwecke zu generisch und nicht auf spezifische Situationen anpassbar - in einem Callcenter, an einem Helpdesk oder in einer Anwendung zur Selbstpflege der eigenen Daten durch den Benutzer können sie nicht eingesetzt werden. PHP bietet mit seiner LDAP-Schnittstelle jedoch die Mittel, schnell und zuverlässig für spezifische Einsatzzwecke angepasste Webinterfaces entwickeln zu können.

Jan Kneschke
ist Student and der FH Kiel, entwickelt Webanwendungen bei der NetUSE AG und pflegt verschiedene Open-Source-Projekte (ModLogAn, pxTools).

Kristian Köhntopp
ist Entwickler für Webanwendungen bei NetUSE AG, Kiel, an der Entwicklung von PHPLib sowie PHP beteiligt und betreut die deutsche FAQ der Newsgroup [news:de.comp.lang.php de.comp.lang.php]

Mehr Infos

Listing 1

# Hostname des LDAP-Servers
$def_host = "127.0.0.1";
# Username zur Authentifizierung
$def_user = "jank";
# das dazugehšrige Passwort
$def_pass = "test";
# Base-DN des LDAP-Baums
$base_dn = "dc=Company, dc=Incremental, dc=Internet";

function change_password($username,
$old_password, $new_password,
$new_password_retyped) {

global $def_host, $base_dn;

/* $username und $old_password dienen zur authentifizierung.
** $new_password und $new_passord_retyped sind das neue passwort.
*/

if (empty($username) || empty($new_password)) {
print "Fehler: Funktionsparameter sind nicht richtig gesetzt<br>";
return false;
}

if ($new_password != $new_password_retyped) {
print "Fehler: Passwšrter sind nicht gleich.<br>";
return false;
}

if (($conn_id = ldap_connect ($def_host)) == false) {
print "Fehler: Verbindung zum LDAP-Server konnte nicht hergestellt werden.<br>";
return false;
}

/* User-DN fŸr den Benutzer mir dem Usernamen $username ermitteln */

if (($link_id = ldap_bind ($conn_id)) == false) {
print "Fehler: Anonymer Bind fehlgeschlagen<br>";
return false;
}

if (($res_id = ldap_search
($link_id, $base_dn, "uid=$username")) == false) {
print "Fehler: Suche im LDAP-Baum fehlgeschlagen<br>";
return false;
}

if (ldap_count_entries($link_id, $res_id) != 1) {
print "Fehler: Username $username mehr als einmal gefunden<br>";
return false;
}

if (( $entry_id = ldap_first_entry($link_id, $res_id))== false) {
print "Fehler: Eintrag des Suchergenisses konnte nicht abgeholt werden<br>";
return false;
}

if (( $user_dn = ldap_get_dn($link_id, $entry_id)) == false) {
print "Fehler: Der User-DN konnte nicht ermittelt werden<br>";
return false;
}

/* Authentifizierung des User */
if (($link_id = ldap_bind
($conn_id, $user_dn, $old_password)) == false) {
print "Fehler: Authentifizierung fehlgeschlagen: $user_dn - $old_password<br>";
return false;
}

$change_passwd["userpassword"] = $new_password;

if (ldap_modify($link_id, $user_dn, $change_passwd) == false) {
print "Fehler: €ndern des Passwortes ist fehlgeschlagen<br>";
return false;
}

ldap_unbind ($link_id);
return true;
}

# "test1" als neues Passwort setzen:

if (change_password($def_user, $def_pass, "test1", "test1")
== true) {
print "PasswortŠnderung erfolgreich<br>";
}
Mehr Infos

Knoten, Objekte und Attribute

Bäume bestehen für gewöhnlich aus Wurzel, Ästen und Blättern. Alle drei Teile tragen in einem LDAP-Baum den Namen Knoten. Jeder dieser Knoten kann verschiedene Attribute enthalten, die über Objektklassen definiert sind. Objektklassen können dabei Attribute vorschreiben oder sie als optional definieren. Attribute dürfen, wenn sie multivalued sind, mehrere Werte enthalten.

Ein Attribut ist dabei für jeden Knoten zwingend erforderlich: objectclass. Es kann mehrere Werte haben und listet so die Objektklassen auf, denen der Knoten angehört. Jeder Knoten in einem LDAP-Baum muss dabei von der Objektklasse top sein. Erst dies ermöglicht die Suche nach allen Attributen, da sich daraus der allgemeine Filter ergibt: objectclass=*.

Die im Beispiel verwendeten Knoten mit Informationen über Benutzer und Passwörter gehören außerdem den Objektklassen Person und posixAccount an. Durch die Zugehörigkeit zu Person enthalten sie Informationen über den Namen des Benutzers, und durch die Zugehörigkeit zu posixAccount bekommen sie Unix-spezifische Attribute wie userpassword, uidnumber und gidnumber.

Mehr zu Objektklassen und ihren Attributen findet man im LDAP-Schema-Viewer [3] und im RFC 2256 [4].

(hb)