diff --git a/lib/SimpleSAML/Auth/LDAP.php b/lib/SimpleSAML/Auth/LDAP.php index 2dcda1676fb2511792a6ef7c23a894547ec224ce..db78e2b11a9a8336fa5704b70664c16d70d965ec 100644 --- a/lib/SimpleSAML/Auth/LDAP.php +++ b/lib/SimpleSAML/Auth/LDAP.php @@ -29,6 +29,11 @@ class SimpleSAML_Auth_LDAP { */ private $ldap = null; + /** + * LDAP user: authz_id if SASL is in use, binding dn otherwise + */ + private $authz_id = null; + /** * Timeout value, in seconds. * @@ -294,25 +299,57 @@ class SimpleSAML_Auth_LDAP { * The DN used. * @param string $password * The password used. + * @param array $sasl_args + * Array of SASL options for SASL bind * @return bool - * Returns TRUE if successful, FALSE if LDAP_INVALID_CREDENTIALS. + * Returns TRUE if successful, FALSE if + * LDAP_INVALID_CREDENTIALS, LDAP_X_PROXY_AUTHZ_FAILURE, + * LDAP_INAPPROPRIATE_AUTH, LDAP_INSUFFICIENT_ACCESS * @throws SimpleSAML_Error_Exception on other errors */ - public function bind($dn, $password) { + public function bind($dn, $password, array $sasl_args = NULL) { + $authz_id = null; - // Bind, with error handling. - if (@ldap_bind($this->ldap, $dn, $password)) { + if ($sasl_args != NULL) { + if (!function_exists(ldap_sasl_bind)) { + $ex_msg = 'Library - missing SASL support'; + throw $this->makeException($ex_msg); + } + + // SASL Bind, with error handling. + $authz_id = $sasl_args['authz_id']; + $error = @ldap_sasl_bind($this->ldap, $dn, $password, + $sasl_args['mech'], + $sasl_args['realm'], + $sasl_args['authc_id'], + $sasl_args['authz_id'], + $sasl_args['props']); + } else { + // Simple Bind, with error handling. + $authz_id = $dn; + $error = @ldap_bind($this->ldap, $dn, $password); + } + if ($error === TRUE) { // Good. + $this->authz_id = $authz_id; SimpleSAML_Logger::debug('Library - LDAP bind(): Bind successful with DN \'' . $dn . '\''); return TRUE; } - /* Handle invalid DN/password. - * LDAP_INVALID_CREDENTIALS */ - if (ldap_errno($this->ldap) === 49) { + /* Handle errors + * LDAP_INVALID_CREDENTIALS + * LDAP_INSUFFICIENT_ACCESS */ + switch(ldap_errno($this->ldap)) { + case 47: /* LDAP_X_PROXY_AUTHZ_FAILURE */ + case 48: /* LDAP_INAPPROPRIATE_AUTH */ + case 49: /* LDAP_INVALID_CREDENTIALS */ + case 50: /* LDAP_INSUFFICIENT_ACCESS */ return FALSE; + break; + default; + break; } // Bad. @@ -503,6 +540,47 @@ class SimpleSAML_Auth_LDAP { return $string; } + /** + * Convert SASL authz_id into a DN + */ + private function authzid_to_dn($searchBase, $searchAttributes, $authz_id) { + if (preg_match("/^dn:/", $authz_id)) + return preg_replace("/^dn:/", "", $authz_id); + + if (preg_match("/^u:/", $authz_id)) + return $this->searchfordn($searchBase, $searchAttributes, + preg_replace("/^u:/", "", $authz_id)); + + return $authz_id; + } + + /** + * ldap_exop_whoami accessor, if available. Use requested authz_id + * otherwise. + * + * ldap_exop_whoami is not yet included in PHP. For reference, the + * feature request: http://bugs.php.net/bug.php?id=42060 + * And the patch against lastest PHP release: + * http://cvsweb.netbsd.org/bsdweb.cgi/pkgsrc/databases/php-ldap/files/ldap-ctrl-exop.patch + */ + public function whoami($searchBase, $searchAttributes) { + $authz_id = ''; + + if (function_exists('ldap_exop_whoami')) { + if (ldap_exop_whoami($this->ldap, $authz_id) !== true) + throw $this->getLDAPException('LDAP whoami exop failure'); + } else { + $authz_id = $this->authz_id; + } + + $dn = $this->authzid_to_dn($searchBase, $searchAttributes, $authz_id); + + if (!isset($dn) || ($dn == '')) + throw $this->getLDAPException('Cannot figure userID'); + + return $dn; + } + } ?> \ No newline at end of file diff --git a/modules/ldap/lib/Auth/Source/LDAP.php b/modules/ldap/lib/Auth/Source/LDAP.php index f49ff333cd75cb343b74314556adf9f1b4b381a5..a2c41a31b02f5c52530cda62cb832b7a73b88c4d 100644 --- a/modules/ldap/lib/Auth/Source/LDAP.php +++ b/modules/ldap/lib/Auth/Source/LDAP.php @@ -42,13 +42,14 @@ class sspmod_ldap_Auth_Source_LDAP extends sspmod_core_Auth_UserPassBase { * * @param string $username The username the user wrote. * @param string $password The password the user wrote. + * param array $sasl_arg Associative array of SASL options * @return array Associative array with the users attributes. */ - protected function login($username, $password) { + protected function login($username, $password, array $sasl_args = NULL) { assert('is_string($username)'); assert('is_string($password)'); - return $this->ldapConfig->login($username, $password); + return $this->ldapConfig->login($username, $password, $sasl_args); } } diff --git a/modules/ldap/lib/ConfigHelper.php b/modules/ldap/lib/ConfigHelper.php index 8f1dd5634224e97dbcc9e9bb77bee8ce1c24a4d5..7fc63ba2f1989fdc7a96f83f999ddfe89be81018 100644 --- a/modules/ldap/lib/ConfigHelper.php +++ b/modules/ldap/lib/ConfigHelper.php @@ -160,9 +160,10 @@ class sspmod_ldap_ConfigHelper { * * @param string $username The username the user wrote. * @param string $password The password the user wrote. + * @param arrray $sasl_args Array of SASL options for LDAP bind. * @return array Associative array with the users attributes. */ - public function login($username, $password) { + public function login($username, $password, array $sasl_args = NULL) { assert('is_string($username)'); assert('is_string($password)'); @@ -186,11 +187,15 @@ class sspmod_ldap_ConfigHelper { } } - if (!$ldap->bind($dn, $password)) { + if (!$ldap->bind($dn, $password, $sasl_args)) { SimpleSAML_Logger::info($this->location . ': '. $username . ' failed to authenticate. DN=' . $dn); throw new SimpleSAML_Error_Error('WRONGUSERPASS'); } + /* In case of SASL bind, authenticated and authorized DN may differ */ + if (isset($sasl_args)) + $dn = $ldap->whoami($this->searchBase, $this->searchAttributes); + /* Are privs needed to get the attributes? */ if ($this->privRead) { /* Yes, rebind with privs */