760 lines
25 KiB
PHP
760 lines
25 KiB
PHP
|
<?php
|
||
|
|
||
|
/**
|
||
|
* Zend Framework
|
||
|
*
|
||
|
* LICENSE
|
||
|
*
|
||
|
* This source file is subject to the new BSD license that is bundled
|
||
|
* with this package in the file LICENSE.txt.
|
||
|
* It is also available through the world-wide-web at this URL:
|
||
|
* http://framework.zend.com/license/new-bsd
|
||
|
* If you did not receive a copy of the license and are unable to
|
||
|
* obtain it through the world-wide-web, please send an email
|
||
|
* to license@zend.com so we can send you a copy immediately.
|
||
|
*
|
||
|
* @category Zend
|
||
|
* @package Zend_Ldap
|
||
|
* @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
|
||
|
* @license http://framework.zend.com/license/new-bsd New BSD License
|
||
|
* @version $Id: Ldap.php 11765 2008-10-09 01:53:43Z miallen $
|
||
|
*/
|
||
|
|
||
|
|
||
|
/**
|
||
|
* @category Zend
|
||
|
* @package Zend_Ldap
|
||
|
* @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
|
||
|
* @license http://framework.zend.com/license/new-bsd New BSD License
|
||
|
*/
|
||
|
class Zend_Ldap
|
||
|
{
|
||
|
|
||
|
const ACCTNAME_FORM_DN = 1;
|
||
|
const ACCTNAME_FORM_USERNAME = 2;
|
||
|
const ACCTNAME_FORM_BACKSLASH = 3;
|
||
|
const ACCTNAME_FORM_PRINCIPAL = 4;
|
||
|
|
||
|
/**
|
||
|
* String used with ldap_connect for error handling purposes.
|
||
|
*
|
||
|
* @var string
|
||
|
*/
|
||
|
private $_connectString;
|
||
|
|
||
|
/**
|
||
|
* The raw LDAP extension resource.
|
||
|
*
|
||
|
* @var resource
|
||
|
*/
|
||
|
protected $_resource = null;
|
||
|
|
||
|
/**
|
||
|
* @param string $str The string to escape.
|
||
|
* @return string The escaped string
|
||
|
*/
|
||
|
public static function filterEscape($str)
|
||
|
{
|
||
|
$ret = '';
|
||
|
$len = strlen($str);
|
||
|
for ($si = 0; $si < $len; $si++) {
|
||
|
$ch = $str[$si];
|
||
|
$ord = ord($ch);
|
||
|
if ($ord < 0x20 || $ord > 0x7e || strstr('*()\/', $ch)) {
|
||
|
$ch = '\\' . dechex($ord);
|
||
|
}
|
||
|
$ret .= $ch;
|
||
|
}
|
||
|
return $ret;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param string $dn The DN to parse
|
||
|
* @param array $keys An optional array to receive DN keys (e.g. CN, OU, DC, ...)
|
||
|
* @param array $vals An optional array to receive DN values
|
||
|
* @return bool True if the DN was successfully parsed or false if the string is not a valid DN.
|
||
|
*/
|
||
|
public static function explodeDn($dn, array &$keys = null, array &$vals = null)
|
||
|
{
|
||
|
/* This is a classic state machine parser. Each iteration of the
|
||
|
* loop processes one character. State 1 collects the key. When equals (=)
|
||
|
* is encountered the state changes to 2 where the value is collected
|
||
|
* until a comma (,) or semicolon (;) is encountered after which we switch back
|
||
|
* to state 1. If a backslash (\) is encountered, state 3 is used to collect the
|
||
|
* following character without engaging the logic of other states.
|
||
|
*/
|
||
|
$key = null;
|
||
|
$slen = strlen($dn);
|
||
|
$state = 1;
|
||
|
$ko = $vo = 0;
|
||
|
for ($di = 0; $di <= $slen; $di++) {
|
||
|
$ch = $di == $slen ? 0 : $dn[$di];
|
||
|
switch ($state) {
|
||
|
case 1: // collect key
|
||
|
if ($ch === '=') {
|
||
|
$key = trim(substr($dn, $ko, $di - $ko));
|
||
|
if ($keys !== null) {
|
||
|
$keys[] = $key;
|
||
|
}
|
||
|
$state = 2;
|
||
|
$vo = $di + 1;
|
||
|
} else if ($ch === ',' || $ch === ';') {
|
||
|
return false;
|
||
|
}
|
||
|
break;
|
||
|
case 2: // collect value
|
||
|
if ($ch === '\\') {
|
||
|
$state = 3;
|
||
|
} else if ($ch === ',' || $ch === ';' || $ch === 0) {
|
||
|
if ($vals !== null) {
|
||
|
$vals[] = trim(substr($dn, $vo, $di - $vo));
|
||
|
}
|
||
|
$state = 1;
|
||
|
$ko = $di + 1;
|
||
|
} else if ($ch === '=') {
|
||
|
return false;
|
||
|
}
|
||
|
break;
|
||
|
case 3: // escaped
|
||
|
$state = 2;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return $state === 1 && $ko > 0;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param array $options Options used in connecting, binding, etc.
|
||
|
* @return void
|
||
|
*/
|
||
|
public function __construct(array $options = array())
|
||
|
{
|
||
|
$this->setOptions($options);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets the options used in connecting, binding, etc.
|
||
|
*
|
||
|
* Valid option keys:
|
||
|
* host
|
||
|
* port
|
||
|
* useSsl
|
||
|
* username
|
||
|
* password
|
||
|
* bindRequiresDn
|
||
|
* baseDn
|
||
|
* accountCanonicalForm
|
||
|
* accountDomainName
|
||
|
* accountDomainNameShort
|
||
|
* accountFilterFormat
|
||
|
* allowEmptyPassword
|
||
|
* useStartTls
|
||
|
* optRefferals
|
||
|
*
|
||
|
* @param array $options Options used in connecting, binding, etc.
|
||
|
* @return Zend_Ldap Provides a fluent interface
|
||
|
* @throws Zend_Ldap_Exception
|
||
|
*/
|
||
|
public function setOptions(array $options)
|
||
|
{
|
||
|
$permittedOptions = array(
|
||
|
'host' => null,
|
||
|
'port' => null,
|
||
|
'useSsl' => null,
|
||
|
'username' => null,
|
||
|
'password' => null,
|
||
|
'bindRequiresDn' => null,
|
||
|
'baseDn' => null,
|
||
|
'accountCanonicalForm' => null,
|
||
|
'accountDomainName' => null,
|
||
|
'accountDomainNameShort' => null,
|
||
|
'accountFilterFormat' => null,
|
||
|
'allowEmptyPassword' => null,
|
||
|
'useStartTls' => null,
|
||
|
'optReferrals' => null,
|
||
|
);
|
||
|
|
||
|
$diff = array_diff_key($options, $permittedOptions);
|
||
|
if ($diff) {
|
||
|
list($key, $val) = each($diff);
|
||
|
require_once 'Zend/Ldap/Exception.php';
|
||
|
throw new Zend_Ldap_Exception(null, "Unknown Zend_Ldap option: $key");
|
||
|
}
|
||
|
|
||
|
foreach ($permittedOptions as $key => $val) {
|
||
|
if (!array_key_exists($key, $options)) {
|
||
|
$options[$key] = null;
|
||
|
} else {
|
||
|
/* Enforce typing. This eliminates issues like Zend_Config_Ini
|
||
|
* returning '1' as a string (ZF-3163).
|
||
|
*/
|
||
|
switch ($key) {
|
||
|
case 'port':
|
||
|
case 'accountCanonicalForm':
|
||
|
$options[$key] = (int)$options[$key];
|
||
|
break;
|
||
|
case 'useSsl':
|
||
|
case 'bindRequiresDn':
|
||
|
case 'allowEmptyPassword':
|
||
|
case 'useStartTls':
|
||
|
case 'optReferrals':
|
||
|
$val = $options[$key];
|
||
|
$options[$key] = $val === true ||
|
||
|
$val === '1' ||
|
||
|
strcasecmp($val, 'true') == 0;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
$this->_options = $options;
|
||
|
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @return array The current options.
|
||
|
*/
|
||
|
public function getOptions()
|
||
|
{
|
||
|
return $this->_options;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @return resource The raw LDAP extension resource.
|
||
|
*/
|
||
|
public function getResource()
|
||
|
{
|
||
|
/**
|
||
|
* @todo by reference?
|
||
|
*/
|
||
|
return $this->_resource;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @return string The hostname of the LDAP server being used to authenticate accounts
|
||
|
*/
|
||
|
protected function _getHost()
|
||
|
{
|
||
|
return $this->_options['host'];
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @return int The port of the LDAP server or 0 to indicate that no port value is set
|
||
|
*/
|
||
|
protected function _getPort()
|
||
|
{
|
||
|
if ($this->_options['port'])
|
||
|
return $this->_options['port'];
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @return string The default acctname for binding
|
||
|
*/
|
||
|
protected function _getUsername()
|
||
|
{
|
||
|
return $this->_options['username'];
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @return string The default password for binding
|
||
|
*/
|
||
|
protected function _getPassword()
|
||
|
{
|
||
|
return $this->_options['password'];
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @return boolean The default SSL / TLS encrypted transport control
|
||
|
*/
|
||
|
protected function _getUseSsl()
|
||
|
{
|
||
|
return $this->_options['useSsl'];
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @return string The default base DN under which objects of interest are located
|
||
|
*/
|
||
|
protected function _getBaseDn()
|
||
|
{
|
||
|
return $this->_options['baseDn'];
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @return string Either ACCTNAME_FORM_BACKSLASH, ACCTNAME_FORM_PRINCIPAL or ACCTNAME_FORM_USERNAME indicating the form usernames should be canonicalized to.
|
||
|
*/
|
||
|
protected function _getAccountCanonicalForm()
|
||
|
{
|
||
|
/* Account names should always be qualified with a domain. In some scenarios
|
||
|
* using non-qualified account names can lead to security vulnerabilities. If
|
||
|
* no account canonical form is specified, we guess based in what domain
|
||
|
* names have been supplied.
|
||
|
*/
|
||
|
|
||
|
$accountCanonicalForm = $this->_options['accountCanonicalForm'];
|
||
|
if (!$accountCanonicalForm) {
|
||
|
$accountDomainName = $this->_options['accountDomainName'];
|
||
|
$accountDomainNameShort = $this->_options['accountDomainNameShort'];
|
||
|
if ($accountDomainNameShort) {
|
||
|
$accountCanonicalForm = Zend_Ldap::ACCTNAME_FORM_BACKSLASH;
|
||
|
} else if ($accountDomainName) {
|
||
|
$accountCanonicalForm = Zend_Ldap::ACCTNAME_FORM_PRINCIPAL;
|
||
|
} else {
|
||
|
$accountCanonicalForm = Zend_Ldap::ACCTNAME_FORM_USERNAME;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return $accountCanonicalForm;
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* @return string A format string for building an LDAP search filter to match an account
|
||
|
*/
|
||
|
protected function _getAccountFilterFormat()
|
||
|
{
|
||
|
return $this->_options['accountFilterFormat'];
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @return string The LDAP search filter for matching directory accounts
|
||
|
*/
|
||
|
protected function _getAccountFilter($acctname)
|
||
|
{
|
||
|
$this->_splitName($acctname, $dname, $aname);
|
||
|
$accountFilterFormat = $this->_getAccountFilterFormat();
|
||
|
$aname = Zend_Ldap::filterEscape($aname);
|
||
|
if ($accountFilterFormat)
|
||
|
return sprintf($accountFilterFormat, $aname);
|
||
|
if (!$this->_options['bindRequiresDn']) {
|
||
|
// is there a better way to detect this?
|
||
|
return "(&(objectClass=user)(sAMAccountName=$aname))";
|
||
|
}
|
||
|
return "(&(objectClass=posixAccount)(uid=$aname))";
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param string $name The name to split
|
||
|
* @param string $dname The resulting domain name (this is an out parameter)
|
||
|
* @param string $aname The resulting account name (this is an out parameter)
|
||
|
*/
|
||
|
protected function _splitName($name, &$dname, &$aname)
|
||
|
{
|
||
|
$dname = NULL;
|
||
|
$aname = $name;
|
||
|
|
||
|
$pos = strpos($name, '@');
|
||
|
if ($pos) {
|
||
|
$dname = substr($name, $pos + 1);
|
||
|
$aname = substr($name, 0, $pos);
|
||
|
} else {
|
||
|
$pos = strpos($name, '\\');
|
||
|
if ($pos) {
|
||
|
$dname = substr($name, 0, $pos);
|
||
|
$aname = substr($name, $pos + 1);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param string $acctname The name of the account
|
||
|
* @return string The DN of the specified account
|
||
|
* @throws Zend_Ldap_Exception
|
||
|
*/
|
||
|
protected function _getAccountDn($acctname)
|
||
|
{
|
||
|
if (Zend_Ldap::explodeDn($acctname))
|
||
|
return $acctname;
|
||
|
$acctname = $this->getCanonicalAccountName($acctname, Zend_Ldap::ACCTNAME_FORM_USERNAME);
|
||
|
$acct = $this->_getAccount($acctname, array('dn'));
|
||
|
return $acct['dn'];
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param string $dname The domain name to check
|
||
|
* @return bool
|
||
|
*/
|
||
|
protected function _isPossibleAuthority($dname)
|
||
|
{
|
||
|
if ($dname === null)
|
||
|
return true;
|
||
|
$accountDomainName = $this->_options['accountDomainName'];
|
||
|
$accountDomainNameShort = $this->_options['accountDomainNameShort'];
|
||
|
if ($accountDomainName === null && $accountDomainNameShort === null)
|
||
|
return true;
|
||
|
if (strcasecmp($dname, $accountDomainName) == 0)
|
||
|
return true;
|
||
|
if (strcasecmp($dname, $accountDomainNameShort) == 0)
|
||
|
return true;
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param string $acctname The name to canonicalize
|
||
|
* @param int $type The desired form of canonicalization
|
||
|
* @return string The canonicalized name in the desired form
|
||
|
* @throws Zend_Ldap_Exception
|
||
|
*/
|
||
|
public function getCanonicalAccountName($acctname, $form = 0)
|
||
|
{
|
||
|
$this->_splitName($acctname, $dname, $uname);
|
||
|
|
||
|
if (!$this->_isPossibleAuthority($dname)) {
|
||
|
/**
|
||
|
* @see Zend_Ldap_Exception
|
||
|
*/
|
||
|
require_once 'Zend/Ldap/Exception.php';
|
||
|
throw new Zend_Ldap_Exception(null,
|
||
|
"Binding domain is not an authority for user: $acctname",
|
||
|
Zend_Ldap_Exception::LDAP_X_DOMAIN_MISMATCH);
|
||
|
}
|
||
|
|
||
|
if ($form === Zend_Ldap::ACCTNAME_FORM_DN)
|
||
|
return $this->_getAccountDn($acctname);
|
||
|
|
||
|
if (!$uname) {
|
||
|
/**
|
||
|
* @see Zend_Ldap_Exception
|
||
|
*/
|
||
|
require_once 'Zend/Ldap/Exception.php';
|
||
|
throw new Zend_Ldap_Exception(null, "Invalid account name syntax: $acctname");
|
||
|
}
|
||
|
|
||
|
$uname = strtolower($uname);
|
||
|
|
||
|
if ($form === 0)
|
||
|
$form = $this->_getAccountCanonicalForm();
|
||
|
|
||
|
switch ($form) {
|
||
|
case Zend_Ldap::ACCTNAME_FORM_USERNAME:
|
||
|
return $uname;
|
||
|
case Zend_Ldap::ACCTNAME_FORM_BACKSLASH:
|
||
|
$accountDomainNameShort = $this->_options['accountDomainNameShort'];
|
||
|
if (!$accountDomainNameShort) {
|
||
|
/**
|
||
|
* @see Zend_Ldap_Exception
|
||
|
*/
|
||
|
require_once 'Zend/Ldap/Exception.php';
|
||
|
throw new Zend_Ldap_Exception(null, 'Option required: accountDomainNameShort');
|
||
|
}
|
||
|
return "$accountDomainNameShort\\$uname";
|
||
|
case Zend_Ldap::ACCTNAME_FORM_PRINCIPAL:
|
||
|
$accountDomainName = $this->_options['accountDomainName'];
|
||
|
if (!$accountDomainName) {
|
||
|
/**
|
||
|
* @see Zend_Ldap_Exception
|
||
|
*/
|
||
|
require_once 'Zend/Ldap/Exception.php';
|
||
|
throw new Zend_Ldap_Exception(null, 'Option required: accountDomainName');
|
||
|
}
|
||
|
return "$uname@$accountDomainName";
|
||
|
default:
|
||
|
/**
|
||
|
* @see Zend_Ldap_Exception
|
||
|
*/
|
||
|
require_once 'Zend/Ldap/Exception.php';
|
||
|
throw new Zend_Ldap_Exception(null, "Unknown canonical name form: $form");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param array $attrs An array of names of desired attributes
|
||
|
* @return array An array of the attributes representing the account
|
||
|
* @throws Zend_Ldap_Exception
|
||
|
*/
|
||
|
private function _getAccount($acctname, array $attrs = null)
|
||
|
{
|
||
|
$baseDn = $this->_getBaseDn();
|
||
|
if (!$baseDn) {
|
||
|
/**
|
||
|
* @see Zend_Ldap_Exception
|
||
|
*/
|
||
|
require_once 'Zend/Ldap/Exception.php';
|
||
|
throw new Zend_Ldap_Exception(null, 'Base DN not set');
|
||
|
}
|
||
|
|
||
|
$accountFilter = $this->_getAccountFilter($acctname);
|
||
|
if (!$accountFilter) {
|
||
|
/**
|
||
|
* @see Zend_Ldap_Exception
|
||
|
*/
|
||
|
require_once 'Zend/Ldap/Exception.php';
|
||
|
throw new Zend_Ldap_Exception(null, 'Invalid account filter');
|
||
|
}
|
||
|
|
||
|
if (!is_resource($this->_resource))
|
||
|
$this->bind();
|
||
|
|
||
|
$resource = $this->_resource;
|
||
|
$str = $accountFilter;
|
||
|
$code = 0;
|
||
|
|
||
|
/**
|
||
|
* @todo break out search operation into simple function (private for now)
|
||
|
*/
|
||
|
|
||
|
if (!extension_loaded('ldap')) {
|
||
|
/**
|
||
|
* @see Zend_Ldap_Exception
|
||
|
*/
|
||
|
require_once 'Zend/Ldap/Exception.php';
|
||
|
throw new Zend_Ldap_Exception(null, 'LDAP extension not loaded');
|
||
|
}
|
||
|
|
||
|
$result = @ldap_search($resource,
|
||
|
$baseDn,
|
||
|
$accountFilter,
|
||
|
$attrs);
|
||
|
if (is_resource($result) === true) {
|
||
|
$count = @ldap_count_entries($resource, $result);
|
||
|
if ($count == 1) {
|
||
|
$entry = @ldap_first_entry($resource, $result);
|
||
|
if ($entry) {
|
||
|
$acct = array('dn' => @ldap_get_dn($resource, $entry));
|
||
|
$name = @ldap_first_attribute($resource, $entry, $berptr);
|
||
|
while ($name) {
|
||
|
$data = @ldap_get_values_len($resource, $entry, $name);
|
||
|
$acct[$name] = $data;
|
||
|
$name = @ldap_next_attribute($resource, $entry, $berptr);
|
||
|
}
|
||
|
@ldap_free_result($result);
|
||
|
return $acct;
|
||
|
}
|
||
|
} else if ($count == 0) {
|
||
|
/**
|
||
|
* @see Zend_Ldap_Exception
|
||
|
*/
|
||
|
require_once 'Zend/Ldap/Exception.php';
|
||
|
$code = Zend_Ldap_Exception::LDAP_NO_SUCH_OBJECT;
|
||
|
} else {
|
||
|
|
||
|
/**
|
||
|
* @todo limit search to 1 record and remove some of this logic?
|
||
|
*/
|
||
|
|
||
|
$resource = null;
|
||
|
$str = "$accountFilter: Unexpected result count: $count";
|
||
|
/**
|
||
|
* @see Zend_Ldap_Exception
|
||
|
*/
|
||
|
require_once 'Zend/Ldap/Exception.php';
|
||
|
$code = Zend_Ldap_Exception::LDAP_OPERATIONS_ERROR;
|
||
|
}
|
||
|
@ldap_free_result($result);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @see Zend_Ldap_Exception
|
||
|
*/
|
||
|
require_once 'Zend/Ldap/Exception.php';
|
||
|
throw new Zend_Ldap_Exception($resource, $str, $code);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @return Zend_Ldap Provides a fluent interface
|
||
|
*/
|
||
|
public function disconnect()
|
||
|
{
|
||
|
if (is_resource($this->_resource)) {
|
||
|
if (!extension_loaded('ldap')) {
|
||
|
/**
|
||
|
* @see Zend_Ldap_Exception
|
||
|
*/
|
||
|
require_once 'Zend/Ldap/Exception.php';
|
||
|
throw new Zend_Ldap_Exception(null, 'LDAP extension not loaded');
|
||
|
}
|
||
|
@ldap_unbind($this->_resource);
|
||
|
}
|
||
|
$this->_resource = null;
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param string $host The hostname of the LDAP server to connect to
|
||
|
* @param int $port The port number of the LDAP server to connect to
|
||
|
* @return Zend_Ldap Provides a fluent interface
|
||
|
* @throws Zend_Ldap_Exception
|
||
|
*/
|
||
|
public function connect($host = null, $port = 0, $useSsl = false)
|
||
|
{
|
||
|
if ($host === null)
|
||
|
$host = $this->_getHost();
|
||
|
if ($port === 0)
|
||
|
$port = $this->_getPort();
|
||
|
if ($useSsl === false)
|
||
|
$useSsl = $this->_getUseSsl();
|
||
|
|
||
|
if (!$host) {
|
||
|
/**
|
||
|
* @see Zend_Ldap_Exception
|
||
|
*/
|
||
|
require_once 'Zend/Ldap/Exception.php';
|
||
|
throw new Zend_Ldap_Exception(null, 'A host parameter is required');
|
||
|
}
|
||
|
|
||
|
/* To connect using SSL it seems the client tries to verify the server
|
||
|
* certificate by default. One way to disable this behavior is to set
|
||
|
* 'TLS_REQCERT never' in OpenLDAP's ldap.conf and restarting Apache. Or,
|
||
|
* if you really care about the server's cert you can put a cert on the
|
||
|
* web server.
|
||
|
*/
|
||
|
$url = $useSsl ? "ldaps://$host" : "ldap://$host";
|
||
|
if ($port) {
|
||
|
$url .= ":$port";
|
||
|
}
|
||
|
|
||
|
/* Because ldap_connect doesn't really try to connect, any connect error
|
||
|
* will actually occur during the ldap_bind call. Therefore, we save the
|
||
|
* connect string here for reporting it in error handling in bind().
|
||
|
*/
|
||
|
$this->_connectString = $url;
|
||
|
|
||
|
$this->disconnect();
|
||
|
|
||
|
if (!extension_loaded('ldap')) {
|
||
|
/**
|
||
|
* @see Zend_Ldap_Exception
|
||
|
*/
|
||
|
require_once 'Zend/Ldap/Exception.php';
|
||
|
throw new Zend_Ldap_Exception(null, 'LDAP extension not loaded');
|
||
|
}
|
||
|
|
||
|
/* Only OpenLDAP 2.2 + supports URLs so if SSL is not requested, just
|
||
|
* use the old form.
|
||
|
*/
|
||
|
$resource = $useSsl ? @ldap_connect($url) : @ldap_connect($host, $port);
|
||
|
|
||
|
if (is_resource($resource) === true) {
|
||
|
|
||
|
$this->_resource = $resource;
|
||
|
|
||
|
$optReferrals = $this->_options['optReferrals'] ? 1 : 0;
|
||
|
|
||
|
if (@ldap_set_option($resource, LDAP_OPT_PROTOCOL_VERSION, 3) &&
|
||
|
@ldap_set_option($resource, LDAP_OPT_REFERRALS, $optReferrals)) {
|
||
|
if ($useSsl ||
|
||
|
$this->_options['useStartTls'] !== true ||
|
||
|
@ldap_start_tls($resource)) {
|
||
|
return $this;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @see Zend_Ldap_Exception
|
||
|
*/
|
||
|
require_once 'Zend/Ldap/Exception.php';
|
||
|
|
||
|
$zle = new Zend_Ldap_Exception($resource, "$host:$port");
|
||
|
$this->disconnect();
|
||
|
throw $zle;
|
||
|
}
|
||
|
/**
|
||
|
* @see Zend_Ldap_Exception
|
||
|
*/
|
||
|
require_once 'Zend/Ldap/Exception.php';
|
||
|
throw new Zend_Ldap_Exception("Failed to connect to LDAP server: $host:$port");
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param string $username The username for authenticating the bind
|
||
|
* @param string $password The password for authenticating the bind
|
||
|
* @return Zend_Ldap Provides a fluent interface
|
||
|
* @throws Zend_Ldap_Exception
|
||
|
*/
|
||
|
public function bind($username = null, $password = null)
|
||
|
{
|
||
|
$moreCreds = true;
|
||
|
|
||
|
if ($username === null) {
|
||
|
$username = $this->_getUsername();
|
||
|
$password = $this->_getPassword();
|
||
|
$moreCreds = false;
|
||
|
}
|
||
|
|
||
|
if ($username === NULL) {
|
||
|
/* Perform anonymous bind
|
||
|
*/
|
||
|
$password = NULL;
|
||
|
} else {
|
||
|
/* Check to make sure the username is in DN form.
|
||
|
*/
|
||
|
if (!Zend_Ldap::explodeDn($username)) {
|
||
|
if ($this->_options['bindRequiresDn']) {
|
||
|
/* moreCreds stops an infinite loop if _getUsername does not
|
||
|
* return a DN and the bind requires it
|
||
|
*/
|
||
|
if ($moreCreds) {
|
||
|
try {
|
||
|
$username = $this->_getAccountDn($username);
|
||
|
} catch (Zend_Ldap_Exception $zle) {
|
||
|
/**
|
||
|
* @todo Temporary measure to deal with exception thrown for ldap extension not loaded
|
||
|
*/
|
||
|
if (strpos($zle->getMessage(), 'LDAP extension not loaded') !== false) {
|
||
|
throw $zle;
|
||
|
}
|
||
|
// end temporary measure
|
||
|
switch ($zle->getCode()) {
|
||
|
case Zend_Ldap_Exception::LDAP_NO_SUCH_OBJECT:
|
||
|
case Zend_Ldap_Exception::LDAP_X_DOMAIN_MISMATCH:
|
||
|
throw $zle;
|
||
|
}
|
||
|
throw new Zend_Ldap_Exception(null,
|
||
|
'Failed to retrieve DN for account: ' . $zle->getMessage(),
|
||
|
Zend_Ldap_Exception::LDAP_OPERATIONS_ERROR);
|
||
|
}
|
||
|
} else {
|
||
|
/**
|
||
|
* @see Zend_Ldap_Exception
|
||
|
*/
|
||
|
require_once 'Zend/Ldap/Exception.php';
|
||
|
throw new Zend_Ldap_Exception(null, 'Binding requires username in DN form');
|
||
|
}
|
||
|
} else {
|
||
|
$username = $this->getCanonicalAccountName($username,
|
||
|
Zend_Ldap::ACCTNAME_FORM_PRINCIPAL);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!is_resource($this->_resource))
|
||
|
$this->connect();
|
||
|
|
||
|
if ($username !== null &&
|
||
|
$password === '' &&
|
||
|
$this->_options['allowEmptyPassword'] !== true) {
|
||
|
/**
|
||
|
* @see Zend_Ldap_Exception
|
||
|
*/
|
||
|
require_once 'Zend/Ldap/Exception.php';
|
||
|
|
||
|
$zle = new Zend_Ldap_Exception(null,
|
||
|
'Empty password not allowed - see allowEmptyPassword option.');
|
||
|
} else {
|
||
|
if (@ldap_bind($this->_resource, $username, $password))
|
||
|
return $this;
|
||
|
|
||
|
$message = $username === null ? $this->_connectString : $username;
|
||
|
|
||
|
/**
|
||
|
* @see Zend_Ldap_Exception
|
||
|
*/
|
||
|
require_once 'Zend/Ldap/Exception.php';
|
||
|
|
||
|
switch (Zend_Ldap_Exception::getLdapCode($this)) {
|
||
|
case Zend_Ldap_Exception::LDAP_SERVER_DOWN:
|
||
|
/* If the error is related to establishing a connection rather than binding,
|
||
|
* the connect string is more informative than the username.
|
||
|
*/
|
||
|
$message = $this->_connectString;
|
||
|
}
|
||
|
|
||
|
$zle = new Zend_Ldap_Exception($this->_resource, $message);
|
||
|
}
|
||
|
$this->disconnect();
|
||
|
throw $zle;
|
||
|
}
|
||
|
}
|