Source for file LDAP.php
Documentation is available at LDAP.php
// +----------------------------------------------------------------------+
// +----------------------------------------------------------------------+
// | Copyright (c) 1997-2003 The PHP Group |
// +----------------------------------------------------------------------+
// | This source file is subject to version 2.02 of the PHP license, |
// | that is bundled with this package in the file LICENSE, and is |
// | available at through the world-wide-web at |
// | http://www.php.net/license/2_02.txt. |
// | If you did not receive a copy of the PHP license and are unable to |
// | obtain it through the world-wide-web, please send a note to |
// | license@php.net so we can mail you a copy immediately. |
// +----------------------------------------------------------------------+
// | Authors: Jan Wagner <wagner@netsols.de> |
// +----------------------------------------------------------------------+
// $Id: LDAP.php,v 1.17 2004/06/14 09:25:40 jw Exp $
require_once "Auth/Container.php";
* Storage driver for fetching login data from LDAP
* This class is heavily based on the DB and File containers. By default it
* connects to localhost:389 and searches for uid=$username with the scope
* "sub". If no search base is specified, it will try to determine it via
* the namingContexts attribute. It takes its parameters in a hash, connects
* to the ldap server, binds anonymously, searches for the user, and tries
* to bind as the user with the supplied password. When a group was set, it
* will look for group membership of the authenticated user. If all goes
* well the authentication was successful.
* host: localhost (default), ldap.netsols.de or 127.0.0.1
* port: 389 (default) or 636 or whereever your server runs
* url: ldap://localhost:389/
* useful for ldaps://, works only with openldap2 ?
* it will be preferred over host and port
* version: LDAP version to use, ususally 2 (default) or 3,
* binddn: If set, searching for user will be done after binding
* as this user, if not set the bind will be anonymous.
* This is reported to make the container work with MS
* Active Directory, but should work with any server that
* is configured this way.
* This has to be a complete dn for now (basedn and
* userdn will not be appended).
* bindpw: The password to use for binding with binddn
* basedn: the base dn of your server
* userdn: gets prepended to basedn when searching for user
* userscope: Scope for user searching: one, sub (default), or base
* userattr: the user attribute to search for (default: uid)
* userfilter: filter that will be added to the search filter
* this way: (&(userattr=username)(userfilter))
* default: (objectClass=posixAccount)
* attributes: array of additional attributes to fetch from entry.
* these will added to auth data and can be retrieved via
* Auth::getAuthData(). An empty array will fetch all attributes,
* array('') will fetch no attributes at all (default)
* groupdn: gets prepended to basedn when searching for group
* groupattr: the group attribute to search for (default: cn)
* groupfilter: filter that will be added to the search filter when
* (&(groupattr=group)(memberattr=username)(groupfilter))
* default: (objectClass=groupOfUniqueNames)
* memberattr : the attribute of the group object where the user dn
* may be found (default: uniqueMember)
* memberisdn: whether the memberattr is the dn of the user (default)
* or the value of userattr (usually uid)
* group: the name of group to search for
* groupscope: Scope for group searching: one, sub (default), or base
* debug: Enable/Disable debugging output (default: false)
* To use this storage container, you have to use the following syntax:
* $a = new Auth("LDAP", array(
* 'basedn' => 'o=netsols,c=de',
* 'binddn' => 'cn=admin,o=netsols,c=de',
* 'bindpw' => 'password'));
* $a2 = new Auth('LDAP', array(
* 'url' => 'ldaps://ldap.netsols.de',
* 'basedn' => 'o=netsols,c=de',
* 'userdn' => 'ou=People',
* 'groupdn' => 'ou=Groups',
* 'groupfilter' => '(objectClass=posixGroup)',
* 'memberattr' => 'memberUid',
* This is a full blown example with user/group checking to an Active Directory
* $a3 = new Auth('LDAP', array(
* 'host' => 'ldap.netsols.de',
* 'basedn' => 'dc=netsols,dc=de',
* 'binddn' => 'cn=Jan Wagner,cn=Users,dc=netsols,dc=de',
* 'bindpw' => 'password',
* 'userattr' => 'samAccountName',
* 'userfilter' => '(objectClass=user)',
* 'attributes' => array(''),
* 'groupattr' => 'samAccountName',
* 'groupfilter' => '(objectClass=group)',
* 'memberattr' => 'member',
* 'groupdn' => 'cn=Users',
* The parameter values have to correspond
* to the ones for your LDAP server of course.
* When talking to a Microsoft ActiveDirectory server you have to
* use 'samaccountname' as the 'userattr' and follow special rules
* to translate the ActiveDirectory directory names into 'basedn'.
* The 'basedn' for the default 'Users' folder on an ActiveDirectory
* server for the ActiveDirectory Domain (which is not related to
* its DNS name) "win2000.example.org" would be:
* "CN=Users, DC=win2000, DC=example, DC=org'
* where every component of the domain name becomes a DC attribute
* of its own. If you want to use a custom users folder you have to
* replace "CN=Users" with a sequence of "OU" attributes that specify
* the path to your custom folder in reverse order.
* So the ActiveDirectory folder
* "win2000.example.org\Custom\Accounts"
* "OU=Accounts, OU=Custom, DC=win2000, DC=example, DC=org'
* It seems that binding anonymously to an Active Directory
* is not allowed, so you have to set binddn and bindpw for
* Example a3 shows a tested example for connenction to Windows 2000
* @author Jan Wagner <wagner@netsols.de>
* @version $Revision: 1.17 $
* Connection ID of LDAP Link
* Constructor of the container class
* @param $params, associative hash with host,port,basedn and userattr key
* @return object Returns an error object if something went wrong
return PEAR ::raiseError ('Auth_Container_LDAP: LDAP Extension not loaded', 41 , PEAR_ERROR_DIE );
$this->_parseOptions ($params);
* Connect to the LDAP server using the global options
* @return object Returns a PEAR error object if an error occurs.
$this->_debug ('Connecting with URL', __LINE__ );
$conn_params = array ($this->options['url']);
$this->_debug ('Connecting with host:port', __LINE__ );
return PEAR ::raiseError ('Auth_Container_LDAP: Could not connect to server.', 41 , PEAR_ERROR_DIE );
$this->_debug ('Successfully connected to server', __LINE__ );
$this->_debug (" Switching to LDAP version {$this->options['version']}" , __LINE__ );
@ldap_set_option ($this->conn_id, LDAP_OPT_PROTOCOL_VERSION , $this->options['version']);
// bind with credentials or anonymously
$this->_debug ('Binding with credentials', __LINE__ );
$this->_debug ('Binding anonymously', __LINE__ );
$bind_params = array ($this->conn_id);
return PEAR ::raiseError ("Auth_Container_LDAP: Could not bind to LDAP server.", 41 , PEAR_ERROR_DIE );
$this->_debug ('Binding was successful', __LINE__ );
* Disconnects (unbinds) from ldap server
if ($this->_isValidLink ()) {
$this->_debug ('disconnecting from server');
* Tries to find Basedn via namingContext Attribute
if ($this->options['basedn'] == "" && $this->_isValidLink ()) {
$this->_debug ("basedn not set, searching via namingContexts.", __LINE__ );
$result_id = @ldap_read ($this->conn_id, "", "(objectclass=*)", array ("namingContexts"));
if (@ldap_count_entries ($this->conn_id, $result_id) == 1 ) {
$this->_debug ("got result for namingContexts", __LINE__ );
$entry_id = @ldap_first_entry ($this->conn_id, $result_id);
$attrs = @ldap_get_attributes ($this->conn_id, $entry_id);
$basedn = $attrs['namingContexts'][0 ];
$this->_debug (" result for namingContexts was $basedn" , __LINE__ );
$this->options['basedn'] = $basedn;
@ldap_free_result ($result_id);
// if base ist still not set, raise error
if ($this->options['basedn'] == "") {
return PEAR ::raiseError ("Auth_Container_LDAP: LDAP search base not specified!", 41 , PEAR_ERROR_DIE );
* determines whether there is a valid ldap conenction or not
* Set some default options
$this->options['host'] = 'localhost';
$this->options['userscope'] = 'sub';
$this->options['userattr'] = "uid";
$this->options['userfilter'] = '(objectClass=posixAccount)';
$this->options['attributes'] = array (''); // no attributes
$this->options['groupscope'] = 'sub';
$this->options['groupattr'] = 'cn';
$this->options['groupfilter'] = '(objectClass=groupOfUniqueNames)';
$this->options['memberattr'] = 'uniqueMember';
$this->options['memberisdn'] = true;
* Parse options passed to the container class
function _parseOptions ($array)
foreach ($array as $key => $value) {
* Get search function for scope
* @return string ldap search function
function _scope2function ($scope)
$function = 'ldap_search';
* Fetch data from LDAP server
* Searches the LDAP server for the given username/password
// UTF8 Encode username for LDAPv3
if (@ldap_get_option ($this->conn_id, LDAP_OPT_PROTOCOL_VERSION , $ver) && $ver == 3 ) {
$this->_debug ('UTF8 encoding username for LDAPv3', __LINE__ );
$search_basedn = $this->options['userdn'];
if ($search_basedn != '' && substr($search_basedn, -1 ) != ',') {
$search_basedn .= $this->options['basedn'];
$attributes = $this->options['attributes'];
// make functions params array
$func_params = array ($this->conn_id, $search_basedn, $filter, $attributes);
// search function to use
$func_name = $this->_scope2function ($this->options['userscope']);
$this->_debug (" Searching with $func_name and filter $filter in $search_basedn" , __LINE__ );
$this->_debug ('User not found', __LINE__ );
} elseif (@ldap_count_entries ($this->conn_id, $result_id) == 1 ) { // did we get just one entry?
$this->_debug ('User was found', __LINE__ );
$entry_id = @ldap_first_entry ($this->conn_id, $result_id);
$user_dn = @ldap_get_dn ($this->conn_id, $entry_id);
if ($attributes = @ldap_get_attributes ($this->conn_id, $entry_id)) {
if (is_array($attributes) && isset ($attributes['count']) &&
$attributes['count'] > 0 )
$this->_debug ('Saving attributes to Auth data', __LINE__ );
$this->_auth_obj->setAuthData ('attributes', $attributes);
@ldap_free_result ($result_id);
// need to catch an empty password as openldap seems to return TRUE
// if anonymous binding is allowed
$this->_debug (" Bind as $user_dn" , __LINE__ );
// try binding as this user with the supplied password
if (@ldap_bind ($this->conn_id, $user_dn, $password)) {
$this->_debug ('Bind successful', __LINE__ );
// check group if appropiate
// decide whether memberattr value is a dn or the username
$this->_debug ('Checking group membership', __LINE__ );
$this->_debug ('Authenticated', __LINE__ );
return true; // user authenticated
$this->_debug ('NOT authenticated!', __LINE__ );
* Validate group membership
* Searches the LDAP server for group membership of the
* @param string Distinguished Name of the authenticated User
$filter = sprintf('(&(%s=%s)(%s=%s)%s)',
$search_basedn = $this->options['groupdn'];
if ($search_basedn != '' && substr($search_basedn, -1 ) != ',') {
$search_basedn .= $this->options['basedn'];
$func_params = array ($this->conn_id, $search_basedn, $filter,
array ($this->options['memberattr']));
$func_name = $this->_scope2function ($this->options['groupscope']);
$this->_debug (" Searching with $func_name and filter $filter in $search_basedn" , __LINE__ );
if (@ldap_count_entries ($this->conn_id, $result_id) == 1 ) {
@ldap_free_result ($result_id);
$this->_debug ('User is member of group', __LINE__ );
$this->_debug ('User is NOT member of group', __LINE__ );
* Outputs debugging messages
* @param string Debugging Message
* @param integer Line number
function _debug ($msg = '', $line = 0 )
if ($this->options['debug'] === true ) {
if ($msg == '' && $this->_isValidLink ()) {
$msg = 'LDAP_Error: ' . @ldap_err2str (@ldap_errno ($this->_conn_id));
print (" $line: $msg <br />" );
Documentation generated on Mon, 11 Mar 2019 13:52:32 -0400 by phpDocumentor 1.4.4. PEAR Logo Copyright © PHP Group 2004.
|