. */ /** * Provide an access point to LDAP databases. * * @version 1.0 * @date 27.11.2009 * @author Wim van Ravesteijn * @license http://opensource.org/licenses/gpl-license.php GNU Public License */ class LDAP { private $ldapUri; // LDAP server uri private $baseDn; // LDAP base DN private $authOnAttr; // On which attributes to use 'LDAP->auth()' private $anonDn; // DN to use for anonymous login private $anonPwd; // Password to use for anonymous login private $isBound; // Are we bound to server? private $ldapConn; // LDAP link identifier private $res; // LDAP resource private $myDn; // DN of logged in user private $lastError; // Last error message /** * Default constructor * * @throws Exception: when constructor is called with invalid arguments * * @param string ldapUri: uri of the LDAP server * @param string baseDn: base DN of the LDAP server connection * @param string authOnAttr: comma-separated list of attributes on which 'LDAP->auth()' works (default: uid) * @param string anonDn: DN to use when anonymous login is required (default: empty; login without authentication) * @param string anonPwd: password to use when anonymous login is required (default: empty) */ public function __construct($ldapUri, $baseDn, $authOnAttr="uid", $anonDn="", $anonPwd="") { $this->ldapConn = false; $this->res = false; $this->ldapUri = false; $this->baseDn = false; $this->isBound = false; $this->myDn = false; $this->lastError = ""; if( !(strlen($ldapUri)>0) ) throw new Exception('LDAP->__construct: illegal ldap Uri'); if( !(strlen($baseDn)>0) ) throw new Exception('LDAP->__construct: illegal base DN'); if( !$this->setAuthOnAttr($authOnAttr) ) throw new Exception('LDAP->__construct: illegal auth on Attribute'); $this->conn($ldapUri); $this->baseDn = $baseDn; $this->anonDn = $anonDn; $this->anonPwd = $anonPwd; } /** * Default destructor */ public function __destruct() { if( $this->res ) @ldap_free_result($this->res); if( $this->ldapConn ) @ldap_unbind($this->ldapConn); // Make sure you do not use this object after calling this // destructor, 'cause then things are gonna get messy } /** * Set attributes on which to authenticate * * @param string authOnAttr: comma-separated list of attributes on which to authenticate * @return boolean: true in case of successful parsing, false otherwise */ public function setAuthOnAttr($authOnAttr) { $a = explode(",", $authOnAttr); if( is_array($a) ) { $this->authOnAttr = $a; return true; }else { return false; } } /** * Authenticate a user. Will first search for the DN belonging to the user defined by 'uid' (on the attributes defined * in the constructor) and then try to authenticate using the found DN and provided password. * Note: the anonymous login should have search permission on the attributes * * @param string uid: attribute value used to search for a DN * @param string pwd: password to authenticate with * @return boolean: true in case of successful authentication, false otherwise */ public function auth($uid, $pwd) { if( !$this->isBound ) $this->bindAnon(); // if not bound, do anonymous login if ($pwd == "" ) return false; // LDAP considers a bind with a DN but with an empty password equivalent to an anonymous bind // Search for the DN belonging to $uid $searchfor = "(|"; foreach($this->authOnAttr as $authAttr) { $searchfor .= "(".quotemeta($authAttr)."=".quotemeta($uid).")"; } $searchfor .= ")"; $this->res = @ldap_search($this->ldapConn, $this->baseDn, $searchfor, array('dn')); if( !$this->res ) { $this->lastError = 'LDAP->auth: ldap_search: '.ldap_error($this->ldapConn); return false; } if( !$entr = ldap_first_entry($this->ldapConn, $this->res) ) { $this->lastError = 'LDAP->auth: ldap_first_entry: '.ldap_error($this->ldapConn); return false; } if( !$authDn = @ldap_get_dn($this->ldapConn, $entr) ) { $this->lastError = 'LDAP->auth: ldap_get_dn: '.ldap_error($this->ldapConn); return false; } // Free results @ldap_free_result($this->res); $this->res = false; // Unbind from server if( !@ldap_unbind($this->ldapConn) ) { $this->lastError = 'LDAP->auth: ldap_unbind: '.ldap_error($this->ldapConn); return false; } // Bind to server again if( !$this->conn($this->ldapUri) ) { return false; // error message will be set by LDAP->conn() } // Try to bind using the found DN and provided password, and return result return $this->bind($authDn, $pwd); } /** * Log-in to server. * * @param string dn: DN to authenticate with * @param string pwd: password to authenticate with * @return boolean: true in case of successful authentication, false otherwise */ public function bind($dn, $pwd) { if( !@ldap_bind($this->ldapConn, $dn, $pwd) ) { $this->lastError = ldap_error($this->ldapConn); return false; } $this->isBound = true; $this->myDn = $dn; return true; } /** * Make anonymous connection to server. If anonymous credentials provided to constructor, use these, otherwise log-in * without credentials. * * @return boolean: true in case of success, false otherwise */ public function bindAnon() { if( strlen($this->anonDn)>0 ) { // Bind with anonymous credentials (they are available) if( !@ldap_bind($this->ldapConn, $this->anonDn, $this->anonPwd) ) { $this->lastError = 'LDAP->bindAnon: '.ldap_error($this->ldapConn); return false; } }else { // Bind fully anonymous (no anonymous credentials available) if( !@ldap_bind($this->ldapConn) ) { $this->lastError = 'LDAP->bindAnon: '.ldap_error($this->ldapConn); return false; } } $this->isBound = true; return true; } /** * Connect to server (no authentication) * * @param string ldapUri: server uri to connect to * @return boolean: true in case of successful connection, false otherwise */ public function conn($ldapUri) { if( !($this->ldapConn = @ldap_connect($ldapUri)) ) { $this->lastError = 'LDAP->conn: connect to '.$ldapUri.' failed.'; return false; } $this->ldapUri = $ldapUri; return true; } /** * Empty result resources */ public function delResults() { if( $this->res ) @ldap_free_result($this->res); $this->res=false; } /** * Read the data of an entry * * @param string dn: DN of the entry to fetch * @return array: array containing the data of an entry, or false in case it cannot be found */ public function getData($dn) { if( !$this->res = @ldap_read($this->ldapConn, $dn, '(objectClass=*)') ) { $this->lastError = 'LDAP->getData: couldn\'t read entry: '.ldap_error($this->ldapConn); return false; } if( !$data=@ldap_get_entries($this->ldapConn, $this->res) ) { $this->lastError = 'LDAP->getData: couldn\'t get entries: '.ldap_error($this->ldapConn); return false; } return $data[0]; } /** * Get the uid of the logged in user * * @return string: uid of the logged in user, or false if non-existent */ public function getMyUid() { if( !$this->res = ldap_read($this->ldapConn, $this->myDn, '(objectClass=*)', array('uid')) ) { $this->lastError = 'LDAP->getMyUid: couldn\'t read entry: '.ldap_error($this->ldapConn); return false; } if( !$entr = ldap_first_entry($this->ldapConn, $this->res) ) { $this->lastError = 'LDAP->getMyUid: couldn\'t get entries: '.ldap_error($this->ldapConn); return false; } if( !$allUids = ldap_get_values($this->ldapConn, $entr, 'cn') ) { $this->lastError = 'LDAP->getMyUid: couldn\'t get values: '.ldap_error($this->ldapConn); return false; } ldap_free_result($this->res); $this->res = false; return $allUids[0]; } /** * Get the DN of the logged in user * * @return string: DN of the logged in user */ public function getMyDn() { return $this->myDn; } /** * Get the BaseDN of the current LDAP connection * * @return string: BaseDN of the current LDAP connection */ public function getBaseDn() { return $this->baseDn; } /** * Search for entries and sort the result on the first requested attribute * * @param array searchFor: search for specific value for the attribute (key of array) * @param string objectClass: search for certain object class (may be '*' for all entries) * @param array attrs: array of requested attributes * @param string basedn (optional): which part of the tree to search in (relative to the base DN) * @return array: an array of all values */ public function search(array $searchFor, $objectClass, $attrs=array(), $baseDn="") { $filter = "(&(objectClass=".self::escape_filter_value($objectClass).")"; foreach( $searchFor as $key=>$value ) { $filter .= "(".self::escape_filter_value($key)."=".self::escape_filter_value($value).")"; } $filter .= ")"; if( $baseDn=="" ) $baseDn = $this->baseDn; else $baseDn .= "," . $this->baseDn; $this->res = @ldap_search($this->ldapConn, $baseDn, $filter, $attrs); if( !$this->res ) { $this->lastError = 'LDAP->search: search failed: '.ldap_error($this->ldapConn); return false; } if ($attrs[0]=='country') $attrs[0]='c'; if( !@ldap_sort($this->ldapConn, $this->res, $attrs[0]) ) { $this->lasterror = 'LDAP->search: sort failed: '.ldap_error($this->ldapConn); return false; } $data = @ldap_get_entries($this->ldapConn, $this->res); if( !$data ) { $this->lasterror = 'LDAP->search: get entries failed: '.ldap_error($this->ldapConn); return false; } //if( !$data['count'] ) { // $this->lastError = 'LDAP->search: no entries found'; // return false; //} return $data; } /** * Search of all entries in a certain part of the LDAP tree * * @param string objectClass: search for certain object class (may be '*' for all entries) * @param array attrs: array of requested attributes * @param string baseDn (optional): which part of the tree to search in (relative to the base DN) * @return array: an array of all values */ public function searchAll($objectClass, $attrs, $baseDn="") { $filter='(&(objectClass='.quotemeta($objectClass).'))'; if( $baseDn=="" ) $baseDn = $this->baseDn; else $baseDn .= "," . $this->baseDn; $this->res = @ldap_search($this->ldapConn, $baseDn, $filter, $attrs); if( !$this->res ) { $this->lastError = 'LDAP->searchAll: search failed: '.ldap_error($this->ldapConn); return false; } if ($attrs[0]=='country') $attrs[0]='c'; if( !@ldap_sort($this->ldapConn, $this->res, $attrs[0]) ) { $this->lastError = 'LDAP->searchAll: sort failed: '.ldap_error($this->ldapConn); return false; } $data = @ldap_get_entries($this->ldapConn, $this->res); if( !$data ) { $this->lastError = 'LDAP->searchAll: get entries failed: '.ldap_error($this->ldapConn); return false; } return $data; } /** * Perform certain search and return results * * @param string filter: filter to be provided to LDAP (example: "(&(uid=Wim)(objectClass=posixAccount))") * @param array attrs: array of requested attributes * @param string baseDn (optional): which part of the tree to search in (relative to the base DN) * @return array: an array of all values */ public function searchQuery($filter, $attrs, $baseDn="") { if( $baseDn=="" ) $baseDn = $this->baseDn; else $baseDn .= "," . $this->baseDn; $this->res = @ldap_search($this->ldapConn, $baseDn, $filter, $attrs); if( !$this->res ) { $this->lastError = 'LDAP->searchQuery: search failed: '.ldap_error($this->ldapConn); return false; } if ($attrs[0]=='country') $attrs[0]='c'; if( !@ldap_sort($this->ldapConn, $this->res, $attrs[0]) ) { $this->lastError = 'LDAP->searchQuery: sort failed: '.ldap_error($this->ldapConn); return false; } $data = @ldap_get_entries($this->ldapConn, $this->res); if( !$data ) { $this->lastError = 'LDAP->searchQuery: get entries failed: '.ldap_error($this->ldapConn); return false; } return $data; } /** * Add a new entry * * @param string dn: DN of the new entry * @param array data: array containing the new attributes and values * @return boolean: true in case of success, false otherwise */ public function add($dn, $data) { return @ldap_add($this->ldapConn, $dn, $data); } /** * Delete an entry * * @param string dn: DN of the entry to be deleted * @return boolean: true in case of success, false otherwise */ public function delete($dn) { return @ldap_delete($this->ldapConn, $dn); } /** * Modify an existing entry * * @param string dn: DN of the entry to be modified * @param array data: array containing the new attributes and values * @return boolean: true in case of success, false otherwise */ public function modify($dn, $data) { return @ldap_modify($this->ldapConn, $dn, $data); } /** * Add attribute values to current attributes * * @param string dn: DN of the entry to be modified * @param array data: array containing the new attributes and values * @return boolean: true in case of success, false otherwise */ public function modAdd($dn, $data) { return @ldap_mod_add($this->ldapConn, $dn, $data); } /** * Delete attribute values from current attributes * * @param string dn: DN of the entry to be modified * @param array data: array containing the attributes to be deleted * @return boolean: true in case of success, false otherwise */ public function modDelete($dn, $data) { return @ldap_mod_replace($this->ldapConn, $dn, $data); } /** * Replace attribute values with new ones * * @param string dn: DN of the entry to be modified * @param array data: array containing the modified attributes and values * @return boolean: true in case of success, false otherwise */ public function modReplace($dn, $data) { return @ldap_mod_replace($this->ldapConn, $dn, $data); } /** * Get the last error generated by LDAP * * @return string: last error message generated by LDAP */ public function getError() { return @ldap_error($this->ldapConn); } /** * Get the last error reported by this class, probably resulting in a 'false' return * * @return string: last error reported by this class */ public function getLastError() { return $this->lastError; } /** * The following functions are based on the Net_LDAP2_Util interface class. * * PHP version 5 * * @category Net * @package Net_LDAP2 * @author Benedikt Hallinger * @copyright 2009 Benedikt Hallinger * @license http://www.gnu.org/licenses/lgpl-3.0.txt LGPLv3 * @version SVN: $Id: Util.php 286718 2009-08-03 07:30:49Z beni $ * @link http://pear.php.net/package/Net_LDAP2/ */ /** * Escapes the given VALUES according to RFC 2254 so that they can be safely used in LDAP filters. * * Any control characters with an ACII code < 32 as well as the characters with special meaning in * LDAP filters "*", "(", ")", and "\" (the backslash) are converted into the representation of a * backslash followed by two hex digits representing the hexadecimal value of the character. * * @param string string to escape * @return string escaped input */ private function escape_filter_value($string) { // Escaping of filter meta characters $string = str_replace('\\', '\5c', $string); $string = str_replace('*', '\2a', $string); $string = str_replace('(', '\28', $string); $string = str_replace(')', '\29', $string); // ASCII < 32 escaping $string = self::asc2hex32($string); if (null === $string) $string = '\0'; // apply escaped "null" if string is empty return $string; } /** * Converts all ASCII chars < 32 to "\HEX" * * @param string $string String to convert * @return string */ private function asc2hex32($string) { for( $i = 0; $i < strlen($string); $i++ ) { $char = substr($string, $i, 1); if( ord($char) < 32 ) { $hex = dechex(ord($char)); if( strlen($hex) == 1 ) $hex = '0'.$hex; $string = str_replace($char, '\\'.$hex, $string); } } return $string; } } ?>