6define(
'ERR_INTERNAL', 1);
 
    7define(
'ERR_NO_USER', 2);
 
    8define(
'ERR_WRONG_PW', 3);
 
    9define(
'ERR_AS_DATA_INCONSIST', 4);
 
   10define(
'ERR_AS_INTERNAL', 5);
 
   11define(
'ERR_AS_ATTRIBUTE', 6);
 
   14if (!defined(
'LDAP_OPT_DIAGNOSTIC_MESSAGE')) {
 
   15    define(
'LDAP_OPT_DIAGNOSTIC_MESSAGE', 0x0032);
 
   61                        '\', tls=
'.var_export($enable_tls, true). 
   62                        ', debug=
'.var_export($debug, true). 
   63                        ', timeout=
'.var_export($timeout, true). 
   64                        ', referrals=
'.var_export($referrals, true)); 
   67         * Set debug level before calling connect. Note that this passes 
   68         * NULL to ldap_set_option, which is an undocumented feature. 
   70         * OpenLDAP 2.x.x or Netscape Directory SDK x.x needed for this option. 
   72        if ($debug && !ldap_set_option(null, LDAP_OPT_DEBUG_LEVEL, 7)) { 
   73                SimpleSAML\Logger::warning('Library - LDAP 
__construct(): Unable to 
set debug level (LDAP_OPT_DEBUG_LEVEL) to 7
'); 
   77         * Prepare a connection for to this LDAP server. Note that this function 
   78         * doesn't actually connect to the server.
 
   80        $this->ldap = @ldap_connect($hostname, $port);
 
   81        if ($this->ldap === 
false) {
 
   86        if (!@ldap_set_option($this->ldap, LDAP_OPT_PROTOCOL_VERSION, 3)) {
 
   87            throw $this->
makeException(
'Library - LDAP __construct(): Failed to set LDAP Protocol version (LDAP_OPT_PROTOCOL_VERSION) to 3', 
ERR_INTERNAL);
 
   91        if (!@ldap_set_option($this->ldap, LDAP_OPT_REFERRALS, $referrals)) {
 
   92            throw $this->
makeException(
'Library - LDAP __construct(): Failed to set LDAP Referrals (LDAP_OPT_REFERRALS) to '.$referrals, 
ERR_INTERNAL);
 
   99            if (!@ldap_set_option($this->ldap, LDAP_OPT_NETWORK_TIMEOUT, 
$timeout)) {
 
  102            if (!@ldap_set_option($this->ldap, LDAP_OPT_TIMELIMIT, 
$timeout)) {
 
  108        if (stripos($hostname, 
"ldaps:") === 
false && $enable_tls) {
 
  109            if (!@ldap_start_tls($this->ldap)) {
 
  129        if (empty($this->ldap)) {
 
  132            $errNo = @ldap_errno($this->ldap);
 
  141                SimpleSAML\Logger::error($description); 
  145                case ERR_INTERNAL:// 1 - ExInternal 
  146                    return new SimpleSAML_Error_Exception($description, $errNo); 
  147                case ERR_NO_USER:// 2 - ExUserNotFound 
  148                    return new SimpleSAML_Error_UserNotFound($description, $errNo); 
  149                case ERR_WRONG_PW:// 3 - ExInvalidCredential 
  150                    return new SimpleSAML_Error_InvalidCredential($description, $errNo); 
  151                case ERR_AS_DATA_INCONSIST:// 4 - ExAsDataInconsist 
  152                    return new SimpleSAML_Error_AuthSource('ldap
', $description); 
  153                case ERR_AS_INTERNAL:// 5 - ExAsInternal 
  154                    return new SimpleSAML_Error_AuthSource('ldap
', $description); 
  158                $description .= '; cause: \
''.ldap_error($this->ldap).
'\' (0x
'.dechex($errNo).')
'; 
  159                if (@ldap_get_option($this->ldap, LDAP_OPT_DIAGNOSTIC_MESSAGE, $extendedError) && !empty($extendedError)) { 
  160                    $description .= '; additional: \
''.$extendedError.
'\'';
 
  208    private function search(
$base, $attribute, $value, $searchFilter = 
null, $scope = 
"subtree")
 
  211        $attribute = self::escape_filter_value($attribute, 
false);
 
  212        $value = self::escape_filter_value($value, 
true);
 
  214        foreach ($attribute as $attr) {
 
  215            $filter .= 
'('.$attr.
'='.$value.
')';
 
  217        $filter = 
'(|'.$filter.
')';
 
  220        if ($searchFilter != 
null) {
 
  221            $filter = 
"(&".$filter.
"".$searchFilter.
")";
 
  226        if ($scope === 
'base') {
 
  227            $result = @ldap_read($this->ldap, 
$base, $filter, array(), 0, 0, $this->timeout, LDAP_DEREF_NEVER);
 
  228        } 
else if ($scope === 
'onelevel') {
 
  229            $result = @ldap_list($this->ldap, 
$base, $filter, array(), 0, 0, $this->timeout, LDAP_DEREF_NEVER);
 
  231            $result = @ldap_search($this->ldap, 
$base, $filter, array(), 0, 0, $this->timeout, LDAP_DEREF_NEVER);
 
  235            throw $this->makeException(
'Library - LDAP search(): Failed search on base \''.
$base.
'\' for \
''.$filter.
'\'');
 
  239        $count = @ldap_count_entries($this->ldap, 
$result);
 
  240        if ($count === 
false) {
 
  241            throw $this->makeException(
'Library - LDAP search(): Failed to get number of entries returned');
 
  242        } elseif ($count > 1) {
 
  244            throw $this->makeException(
'Library - LDAP search(): Found '.$count.
' entries searching base \''.
$base.
'\' for \
''.$filter.
'\'', 
ERR_AS_DATA_INCONSIST);
 
  245        } elseif ($count === 0) {
 
  247            throw $this->makeException(
'Library - LDAP search(): Found no entries searching base \''.
$base.
'\' for \
''.$filter.
'\'', 
ERR_NO_USER);
 
  252        $entry = @ldap_first_entry($this->ldap, 
$result);
 
  253        if ($entry === 
false) {
 
  254            throw $this->makeException(
'Library - LDAP search(): Unable to retrieve result after searching base \''.
$base.
'\' for \
''.$filter.
'\'');
 
  256        $dn = @ldap_get_dn($this->ldap, $entry);
 
  258            throw $this->makeException(
'Library - LDAP search(): Unable to get DN after searching base \''.
$base.
'\' for \
''.$filter.
'\'');
 
  291    public function searchfordn(
$base, $attribute, $value, $allowZeroHits = 
false, $searchFilter = 
null, $scope = 
'subtree')
 
  298                $result = $this->search(
$current, $attribute, $value, $searchFilter, $scope);
 
  311        if ($allowZeroHits) {
 
  316            throw $this->makeException(
'Library - LDAP searchfordn(): LDAP search returned zero entries for filter \'('.
 
  317                join(
' | ', $attribute).
' = '.$value.
')\' on base(s) \'('.join(
' & ', $bases).
')\'', 2);
 
  339            $filters = $this->escape_filter_value($filters, 
false);
 
  344        if (is_array($filters)) {
 
  345            foreach ($filters as $attribute => $value) {
 
  346                $filter .= 
"($attribute=$value)";
 
  348            if (count($filters) > 1) {
 
  349                $filter = ($and ? 
'(&' : 
'(|').$filter.
')';
 
  351        } elseif (is_string($filters)) {
 
  356        if ($filter == 
'' || $filter == 
'(=)') {
 
  357            throw $this->makeException(
'ldap:LdapConnection->search_manual : No search filters defined', 
ERR_INTERNAL);
 
  361        $bases = (array) $bases;
 
  363            throw $this->makeException(
'ldap:LdapConnection->search_manual : No base DNs were passed', 
ERR_INTERNAL);
 
  368        foreach ($bases as 
$base) {
 
  369            if ($scope === 
'base') {
 
  371            } 
else if ($scope === 
'onelevel') {
 
  377            if (
$result !== 
false && @ldap_count_entries($this->ldap, 
$result) > 0) {
 
  384            throw $this->makeException(
 
  385                'ldap:LdapConnection->search_manual : Failed to search LDAP using base(s) ['.
 
  386                implode(
'; ', $bases).
'] with filter ['.$filter.
']. LDAP error ['.
 
  387                ldap_error($this->ldap).
']' 
  389        } elseif (@ldap_count_entries($this->ldap, 
$result) < 1) {
 
  390            throw $this->makeException(
 
  391                'ldap:LdapConnection->search_manual : No entries found in LDAP using base(s) ['.
 
  392                implode(
'; ', $bases).
'] with filter ['.$filter.
']',
 
  400            throw $this->makeException(
 
  401                'ldap:LdapConnection->search_manual : Unable to retrieve entries from search results' 
  410            for ($j = 0; $j < $entry[
'count']; $j++) {
 
  412                $attribute = $entry[
$name];
 
  415                for ($k = 0; $k < $attribute[
'count']; $k++) {
 
  417                    if (strtolower(
$name) === 
'jpegphoto' || strtolower(
$name) === 
'objectguid') {
 
  448        if ($sasl_args != 
null) {
 
  449            if (!function_exists(
'ldap_sasl_bind')) {
 
  450                $ex_msg = 
'Library - missing SASL support';
 
  451                throw $this->makeException($ex_msg);
 
  455            $authz_id = $sasl_args[
'authz_id'];
 
  456            $error = @ldap_sasl_bind(
 
  462                $sasl_args[
'authc_id'],
 
  463                $sasl_args[
'authz_id'],
 
  469            $error = @ldap_bind($this->ldap, $dn, 
$password);
 
  472        if ($error === 
true) {
 
  474            $this->authz_id = $authz_id;
 
  482        switch (ldap_errno($this->ldap)) {
 
  498        throw $this->makeException(
'Library - LDAP bind(): Bind failed with DN \''.$dn.
'\'');
 
  513        if (!@ldap_set_option($this->ldap, $option, $value)) {
 
  514            throw $this->makeException(
 
  515                'ldap:LdapConnection->setOption : Failed to set LDAP option ['.
 
  516                $option.
'] with the value ['.$value.
'] error: '.ldap_error($this->ldap),
 
  523            'ldap:LdapConnection->setOption : Set the LDAP option ['.
 
  524            $option.
'] with the value ['.$value.
']' 
  560        $result = @ldap_read($this->ldap, $dn, 
'objectClass=*', 
$attributes, 0, 0, $this->timeout);
 
  562            throw $this->makeException(
'Library - LDAP getAttributes(): Failed to get attributes from DN \''.$dn.
'\'');
 
  564        $entry = @ldap_first_entry($this->ldap, 
$result);
 
  565        if ($entry === 
false) {
 
  566            throw $this->makeException(
'Library - LDAP getAttributes(): Could not get first entry from DN \''.$dn.
'\'');
 
  568        $attributes = @ldap_get_attributes($this->ldap, $entry); 
 
  570            throw $this->makeException(
'Library - LDAP getAttributes(): Could not get attributes of first entry from DN \''.$dn.
'\'');
 
  582            for ($j = 0; $j < $attribute[
'count']; $j++) {
 
  583                $value = $attribute[$j];
 
  585                if (!empty($maxsize) && strlen($value) > $maxsize) {
 
  588                        $name.
'\' exceeded maximum allowed 
size by 
'.(strlen($value) - $maxsize)); 
  592                // Base64 encode binary attributes 
  593                if (strtolower($name) === 'jpegphoto
' || strtolower($name) === 'objectguid
' || strtolower($name) === 'ms-ds-consistencyguid
') { 
  594                    $values[] = base64_encode($value); 
  601            $result[$name] = $values; 
  605        SimpleSAML\Logger::debug(
'Library - LDAP getAttributes(): Found attributes \'('.join(
',', array_keys(
$result)).
')\'');
 
  625        $username = addcslashes($username, 
',+"\\<>;*');
 
  627        if (isset(
$config[
'priv_user_dn'])) {
 
  630        if (isset(
$config[
'dnpattern'])) {
 
  631            $dn = str_replace(
'%username%', $username, 
$config[
'dnpattern']);
 
  633            $dn = $this->searchfordn(
$config[
'searchbase'], 
$config[
'searchattributes'], $username);
 
  640                SimpleSAML\Logger::info(
'Library - LDAP validate(): Failed to authenticate \''.$username.
'\' using DN \
''.$dn.
'\'');
 
  673            $val = str_replace(
'\\', 
'\5c', $val);
 
  674            $val = str_replace(
'*', 
'\2a', $val);
 
  675            $val = str_replace(
'(', 
'\28', $val);
 
  676            $val = str_replace(
')', 
'\29', $val);
 
  679            $val = self::asc2hex32($val);
 
  706        for (
$i = 0; 
$i < strlen($string); 
$i++) {
 
  707            $char = substr($string, 
$i, 1);
 
  708            if (ord($char) < 32) {
 
  709                $hex = dechex(ord($char));
 
  710                if (strlen($hex) == 1) {
 
  713                $string = str_replace($char, 
'\\'.$hex, $string);
 
  724        if (preg_match(
"/^dn:/", $authz_id)) {
 
  725            return preg_replace(
"/^dn:/", 
"", $authz_id);
 
  728        if (preg_match(
"/^u:/", $authz_id)) {
 
  729            return $this->searchfordn(
 
  732                preg_replace(
"/^u:/", 
"", $authz_id)
 
  750    public function whoami($searchBase, $searchAttributes)
 
  753        if (function_exists(
'ldap_exop_whoami')) {
 
  754            if (version_compare(phpversion(), 
'7', 
'<')) {
 
  755                if (ldap_exop_whoami($this->ldap, $authz_id) !== 
true) {
 
  756                    throw $this->makeException(
'LDAP whoami exop failure');
 
  759                if (($authz_id = ldap_exop_whoami($this->ldap)) === 
false) {
 
  760                    throw $this->makeException(
'LDAP whoami exop failure');
 
  764            $authz_id = $this->authz_id;
 
  767        $dn = $this->authzid_to_dn($searchBase, $searchAttributes, $authz_id);
 
  769        if (!isset($dn) || ($dn == 
'')) {
 
  770            throw $this->makeException(
'Cannot figure userID');
 
An exception for terminatinating execution or to throw for unit testing.
static arrayize($data, $index=0)
Put a non-array variable into an array.
search($base, $attribute, $value, $searchFilter=null, $scope="subtree")
Search for DN from a single base.
bind($dn, $password, array $sasl_args=null)
Bind to LDAP with a specific DN and password.
__construct($hostname, $enable_tls=true, $debug=false, $timeout=0, $port=389, $referrals=true)
Private constructor restricts instantiation to getInstance().
makeException($description, $type=null)
Convenience method to create an LDAPException as well as log the description.
whoami($searchBase, $searchAttributes)
ldap_exop_whoami accessor, if available.
authzid_to_dn($searchBase, $searchAttributes, $authz_id)
Convert SASL authz_id into a DN.
static escape_filter_value($values=array(), $singleValue=true)
Borrowed function from PEAR:LDAP.
validate($config, $username, $password=null)
Enter description here...
searchformultiple($bases, $filters, $attributes=array(), $and=true, $escape=true, $scope='subtree')
This method was created specifically for the ldap:AttributeAddUsersGroups->searchActiveDirectory() me...
$authz_id
LDAP user: authz_id if SASL is in use, binding dn otherwise.
searchfordn($base, $attribute, $value, $allowZeroHits=false, $searchFilter=null, $scope='subtree')
Search for a DN.
static asc2hex32($string)
Borrowed function from PEAR:LDAP.
getAttributes($dn, $attributes=null, $maxsize=null)
Search a given DN for attributes, and return the resulting associative array.
setOption($option, $value)
Applies an LDAP option to the current connection.
if(array_key_exists('yes', $_REQUEST)) $attributes
const ERR_AS_DATA_INCONSIST
const ERR_INTERNAL
Constants defining possible errors.
Attribute-related utility methods.