File indexing completed on 2025-01-12 05:22:37
0001 <?php 0002 0003 /** 0004 * Zend Framework 0005 * 0006 * LICENSE 0007 * 0008 * This source file is subject to the new BSD license that is bundled 0009 * with this package in the file LICENSE.txt. 0010 * It is also available through the world-wide-web at this URL: 0011 * http://framework.zend.com/license/new-bsd 0012 * If you did not receive a copy of the license and are unable to 0013 * obtain it through the world-wide-web, please send an email 0014 * to license@zend.com so we can send you a copy immediately. 0015 * 0016 * @category Zend 0017 * @package Zend_Ldap 0018 * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) 0019 * @license http://framework.zend.com/license/new-bsd New BSD License 0020 * @version $Id$ 0021 */ 0022 0023 /** 0024 * @category Zend 0025 * @package Zend_Ldap 0026 * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) 0027 * @license http://framework.zend.com/license/new-bsd New BSD License 0028 */ 0029 class Zend_Ldap 0030 { 0031 const SEARCH_SCOPE_SUB = 1; 0032 const SEARCH_SCOPE_ONE = 2; 0033 const SEARCH_SCOPE_BASE = 3; 0034 0035 const ACCTNAME_FORM_DN = 1; 0036 const ACCTNAME_FORM_USERNAME = 2; 0037 const ACCTNAME_FORM_BACKSLASH = 3; 0038 const ACCTNAME_FORM_PRINCIPAL = 4; 0039 0040 /** 0041 * String used with ldap_connect for error handling purposes. 0042 * 0043 * @var string 0044 */ 0045 private $_connectString; 0046 0047 /** 0048 * The options used in connecting, binding, etc. 0049 * 0050 * @var array 0051 */ 0052 protected $_options = null; 0053 0054 /** 0055 * The raw LDAP extension resource. 0056 * 0057 * @var resource 0058 */ 0059 protected $_resource = null; 0060 0061 /** 0062 * FALSE if no user is bound to the LDAP resource 0063 * NULL if there has been an anonymous bind 0064 * username of the currently bound user 0065 * 0066 * @var boolean|null|string 0067 */ 0068 protected $_boundUser = false; 0069 0070 /** 0071 * Caches the RootDSE 0072 * 0073 * @var Zend_Ldap_Node 0074 */ 0075 protected $_rootDse = null; 0076 0077 /** 0078 * Caches the schema 0079 * 0080 * @var Zend_Ldap_Node 0081 */ 0082 protected $_schema = null; 0083 0084 /** 0085 * @deprecated will be removed, use {@see Zend_Ldap_Filter_Abstract::escapeValue()} 0086 * @param string $str The string to escape. 0087 * @return string The escaped string 0088 */ 0089 public static function filterEscape($str) 0090 { 0091 /** 0092 * @see Zend_Ldap_Filter_Abstract 0093 */ 0094 // require_once 'Zend/Ldap/Filter/Abstract.php'; 0095 return Zend_Ldap_Filter_Abstract::escapeValue($str); 0096 } 0097 0098 /** 0099 * @deprecated will be removed, use {@see Zend_Ldap_Dn::checkDn()} 0100 * @param string $dn The DN to parse 0101 * @param array $keys An optional array to receive DN keys (e.g. CN, OU, DC, ...) 0102 * @param array $vals An optional array to receive DN values 0103 * @return boolean True if the DN was successfully parsed or false if the string is 0104 * not a valid DN. 0105 */ 0106 public static function explodeDn($dn, array &$keys = null, array &$vals = null) 0107 { 0108 /** 0109 * @see Zend_Ldap_Dn 0110 */ 0111 // require_once 'Zend/Ldap/Dn.php'; 0112 return Zend_Ldap_Dn::checkDn($dn, $keys, $vals); 0113 } 0114 0115 /** 0116 * Constructor. 0117 * 0118 * @param array|Zend_Config $options Options used in connecting, binding, etc. 0119 * @return void 0120 * @throws Zend_Ldap_Exception if ext/ldap is not installed 0121 */ 0122 public function __construct($options = array()) 0123 { 0124 if (!extension_loaded('ldap')) { 0125 /** 0126 * @see Zend_Ldap_Exception 0127 */ 0128 // require_once 'Zend/Ldap/Exception.php'; 0129 throw new Zend_Ldap_Exception(null, 'LDAP extension not loaded', 0130 Zend_Ldap_Exception::LDAP_X_EXTENSION_NOT_LOADED); 0131 } 0132 $this->setOptions($options); 0133 } 0134 0135 /** 0136 * Destructor. 0137 * 0138 * @return void 0139 */ 0140 public function __destruct() 0141 { 0142 $this->disconnect(); 0143 } 0144 0145 /** 0146 * @return resource The raw LDAP extension resource. 0147 */ 0148 public function getResource() 0149 { 0150 if (!is_resource($this->_resource) || $this->_boundUser === false) { 0151 $this->bind(); 0152 } 0153 return $this->_resource; 0154 } 0155 0156 /** 0157 * Return the LDAP error number of the last LDAP command 0158 * 0159 * @return int 0160 */ 0161 public function getLastErrorCode() 0162 { 0163 $ret = @ldap_get_option($this->_resource, LDAP_OPT_ERROR_NUMBER, $err); 0164 if ($ret === true) { 0165 if ($err <= -1 && $err >= -17) { 0166 /** 0167 * @see Zend_Ldap_Exception 0168 */ 0169 // require_once 'Zend/Ldap/Exception.php'; 0170 /* For some reason draft-ietf-ldapext-ldap-c-api-xx.txt error 0171 * codes in OpenLDAP are negative values from -1 to -17. 0172 */ 0173 $err = Zend_Ldap_Exception::LDAP_SERVER_DOWN + (-$err - 1); 0174 } 0175 return $err; 0176 } 0177 return 0; 0178 } 0179 0180 /** 0181 * Return the LDAP error message of the last LDAP command 0182 * 0183 * @param int $errorCode 0184 * @param array $errorMessages 0185 * @return string 0186 */ 0187 public function getLastError(&$errorCode = null, array &$errorMessages = null) 0188 { 0189 $errorCode = $this->getLastErrorCode(); 0190 $errorMessages = array(); 0191 0192 /* The various error retrieval functions can return 0193 * different things so we just try to collect what we 0194 * can and eliminate dupes. 0195 */ 0196 $estr1 = @ldap_error($this->_resource); 0197 if ($errorCode !== 0 && $estr1 === 'Success') { 0198 $estr1 = @ldap_err2str($errorCode); 0199 } 0200 if (!empty($estr1)) { 0201 $errorMessages[] = $estr1; 0202 } 0203 0204 @ldap_get_option($this->_resource, LDAP_OPT_ERROR_STRING, $estr2); 0205 if (!empty($estr2) && !in_array($estr2, $errorMessages)) { 0206 $errorMessages[] = $estr2; 0207 } 0208 0209 $message = ''; 0210 if ($errorCode > 0) { 0211 $message = '0x' . dechex($errorCode) . ' '; 0212 } else { 0213 $message = ''; 0214 } 0215 if (count($errorMessages) > 0) { 0216 $message .= '(' . implode('; ', $errorMessages) . ')'; 0217 } else { 0218 $message .= '(no error message from LDAP)'; 0219 } 0220 return $message; 0221 } 0222 0223 /** 0224 * Get the currently bound user 0225 * 0226 * FALSE if no user is bound to the LDAP resource 0227 * NULL if there has been an anonymous bind 0228 * username of the currently bound user 0229 * 0230 * @return false|null|string 0231 */ 0232 public function getBoundUser() 0233 { 0234 return $this->_boundUser; 0235 } 0236 0237 /** 0238 * Sets the options used in connecting, binding, etc. 0239 * 0240 * Valid option keys: 0241 * host 0242 * port 0243 * useSsl 0244 * username 0245 * password 0246 * bindRequiresDn 0247 * baseDn 0248 * accountCanonicalForm 0249 * accountDomainName 0250 * accountDomainNameShort 0251 * accountFilterFormat 0252 * allowEmptyPassword 0253 * useStartTls 0254 * optRefferals 0255 * tryUsernameSplit 0256 * 0257 * @param array|Zend_Config $options Options used in connecting, binding, etc. 0258 * @return Zend_Ldap Provides a fluent interface 0259 * @throws Zend_Ldap_Exception 0260 */ 0261 public function setOptions($options) 0262 { 0263 if ($options instanceof Zend_Config) { 0264 $options = $options->toArray(); 0265 } 0266 0267 $permittedOptions = array( 0268 'host' => null, 0269 'port' => 0, 0270 'useSsl' => false, 0271 'username' => null, 0272 'password' => null, 0273 'bindRequiresDn' => false, 0274 'baseDn' => null, 0275 'accountCanonicalForm' => null, 0276 'accountDomainName' => null, 0277 'accountDomainNameShort' => null, 0278 'accountFilterFormat' => null, 0279 'allowEmptyPassword' => false, 0280 'useStartTls' => false, 0281 'optReferrals' => false, 0282 'tryUsernameSplit' => true, 0283 ); 0284 0285 foreach ($permittedOptions as $key => $val) { 0286 if (array_key_exists($key, $options)) { 0287 $val = $options[$key]; 0288 unset($options[$key]); 0289 /* Enforce typing. This eliminates issues like Zend_Config_Ini 0290 * returning '1' as a string (ZF-3163). 0291 */ 0292 switch ($key) { 0293 case 'port': 0294 case 'accountCanonicalForm': 0295 $permittedOptions[$key] = (int)$val; 0296 break; 0297 case 'useSsl': 0298 case 'bindRequiresDn': 0299 case 'allowEmptyPassword': 0300 case 'useStartTls': 0301 case 'optReferrals': 0302 case 'tryUsernameSplit': 0303 $permittedOptions[$key] = ($val === true || 0304 $val === '1' || strcasecmp($val, 'true') == 0); 0305 break; 0306 default: 0307 $permittedOptions[$key] = trim($val); 0308 break; 0309 } 0310 } 0311 } 0312 if (count($options) > 0) { 0313 $key = key($options); 0314 /** 0315 * @see Zend_Ldap_Exception 0316 */ 0317 // require_once 'Zend/Ldap/Exception.php'; 0318 throw new Zend_Ldap_Exception(null, "Unknown Zend_Ldap option: $key"); 0319 } 0320 $this->_options = $permittedOptions; 0321 return $this; 0322 } 0323 0324 /** 0325 * @return array The current options. 0326 */ 0327 public function getOptions() 0328 { 0329 return $this->_options; 0330 } 0331 0332 /** 0333 * @return string The hostname of the LDAP server being used to authenticate accounts 0334 */ 0335 protected function _getHost() 0336 { 0337 return $this->_options['host']; 0338 } 0339 0340 /** 0341 * @return int The port of the LDAP server or 0 to indicate that no port value is set 0342 */ 0343 protected function _getPort() 0344 { 0345 return $this->_options['port']; 0346 } 0347 0348 /** 0349 * @return boolean The default SSL / TLS encrypted transport control 0350 */ 0351 protected function _getUseSsl() 0352 { 0353 return $this->_options['useSsl']; 0354 } 0355 0356 /** 0357 * @return string The default acctname for binding 0358 */ 0359 protected function _getUsername() 0360 { 0361 return $this->_options['username']; 0362 } 0363 0364 /** 0365 * @return string The default password for binding 0366 */ 0367 protected function _getPassword() 0368 { 0369 return $this->_options['password']; 0370 } 0371 0372 /** 0373 * @return boolean Bind requires DN 0374 */ 0375 protected function _getBindRequiresDn() 0376 { 0377 return $this->_options['bindRequiresDn']; 0378 } 0379 0380 /** 0381 * Gets the base DN under which objects of interest are located 0382 * 0383 * @return string 0384 */ 0385 public function getBaseDn() 0386 { 0387 return $this->_options['baseDn']; 0388 } 0389 0390 /** 0391 * @return integer Either ACCTNAME_FORM_BACKSLASH, ACCTNAME_FORM_PRINCIPAL or 0392 * ACCTNAME_FORM_USERNAME indicating the form usernames should be canonicalized to. 0393 */ 0394 protected function _getAccountCanonicalForm() 0395 { 0396 /* Account names should always be qualified with a domain. In some scenarios 0397 * using non-qualified account names can lead to security vulnerabilities. If 0398 * no account canonical form is specified, we guess based in what domain 0399 * names have been supplied. 0400 */ 0401 0402 $accountCanonicalForm = $this->_options['accountCanonicalForm']; 0403 if (!$accountCanonicalForm) { 0404 $accountDomainName = $this->_getAccountDomainName(); 0405 $accountDomainNameShort = $this->_getAccountDomainNameShort(); 0406 if ($accountDomainNameShort) { 0407 $accountCanonicalForm = Zend_Ldap::ACCTNAME_FORM_BACKSLASH; 0408 } else if ($accountDomainName) { 0409 $accountCanonicalForm = Zend_Ldap::ACCTNAME_FORM_PRINCIPAL; 0410 } else { 0411 $accountCanonicalForm = Zend_Ldap::ACCTNAME_FORM_USERNAME; 0412 } 0413 } 0414 0415 return $accountCanonicalForm; 0416 } 0417 0418 /** 0419 * @return string The account domain name 0420 */ 0421 protected function _getAccountDomainName() 0422 { 0423 return $this->_options['accountDomainName']; 0424 } 0425 0426 /** 0427 * @return string The short account domain name 0428 */ 0429 protected function _getAccountDomainNameShort() 0430 { 0431 return $this->_options['accountDomainNameShort']; 0432 } 0433 0434 /** 0435 * @return string A format string for building an LDAP search filter to match 0436 * an account 0437 */ 0438 protected function _getAccountFilterFormat() 0439 { 0440 return $this->_options['accountFilterFormat']; 0441 } 0442 0443 /** 0444 * @return boolean Allow empty passwords 0445 */ 0446 protected function _getAllowEmptyPassword() 0447 { 0448 return $this->_options['allowEmptyPassword']; 0449 } 0450 0451 /** 0452 * @return boolean The default SSL / TLS encrypted transport control 0453 */ 0454 protected function _getUseStartTls() 0455 { 0456 return $this->_options['useStartTls']; 0457 } 0458 0459 /** 0460 * @return boolean Opt. Referrals 0461 */ 0462 protected function _getOptReferrals() 0463 { 0464 return $this->_options['optReferrals']; 0465 } 0466 0467 /** 0468 * @return boolean Try splitting the username into username and domain 0469 */ 0470 protected function _getTryUsernameSplit() 0471 { 0472 return $this->_options['tryUsernameSplit']; 0473 } 0474 0475 /** 0476 * @return string The LDAP search filter for matching directory accounts 0477 */ 0478 protected function _getAccountFilter($acctname) 0479 { 0480 /** 0481 * @see Zend_Ldap_Filter_Abstract 0482 */ 0483 // require_once 'Zend/Ldap/Filter/Abstract.php'; 0484 $this->_splitName($acctname, $dname, $aname); 0485 $accountFilterFormat = $this->_getAccountFilterFormat(); 0486 $aname = Zend_Ldap_Filter_Abstract::escapeValue($aname); 0487 if ($accountFilterFormat) { 0488 return sprintf($accountFilterFormat, $aname); 0489 } 0490 if (!$this->_getBindRequiresDn()) { 0491 // is there a better way to detect this? 0492 return sprintf("(&(objectClass=user)(sAMAccountName=%s))", $aname); 0493 } 0494 return sprintf("(&(objectClass=posixAccount)(uid=%s))", $aname); 0495 } 0496 0497 /** 0498 * @param string $name The name to split 0499 * @param string $dname The resulting domain name (this is an out parameter) 0500 * @param string $aname The resulting account name (this is an out parameter) 0501 * @return void 0502 */ 0503 protected function _splitName($name, &$dname, &$aname) 0504 { 0505 $dname = null; 0506 $aname = $name; 0507 0508 if (!$this->_getTryUsernameSplit()) { 0509 return; 0510 } 0511 0512 $pos = strpos($name, '@'); 0513 if ($pos) { 0514 $dname = substr($name, $pos + 1); 0515 $aname = substr($name, 0, $pos); 0516 } else { 0517 $pos = strpos($name, '\\'); 0518 if ($pos) { 0519 $dname = substr($name, 0, $pos); 0520 $aname = substr($name, $pos + 1); 0521 } 0522 } 0523 } 0524 0525 /** 0526 * @param string $acctname The name of the account 0527 * @return string The DN of the specified account 0528 * @throws Zend_Ldap_Exception 0529 */ 0530 protected function _getAccountDn($acctname) 0531 { 0532 /** 0533 * @see Zend_Ldap_Dn 0534 */ 0535 // require_once 'Zend/Ldap/Dn.php'; 0536 if (Zend_Ldap_Dn::checkDn($acctname)) return $acctname; 0537 $acctname = $this->getCanonicalAccountName($acctname, Zend_Ldap::ACCTNAME_FORM_USERNAME); 0538 $acct = $this->_getAccount($acctname, array('dn')); 0539 return $acct['dn']; 0540 } 0541 0542 /** 0543 * @param string $dname The domain name to check 0544 * @return boolean 0545 */ 0546 protected function _isPossibleAuthority($dname) 0547 { 0548 if ($dname === null) { 0549 return true; 0550 } 0551 $accountDomainName = $this->_getAccountDomainName(); 0552 $accountDomainNameShort = $this->_getAccountDomainNameShort(); 0553 if ($accountDomainName === null && $accountDomainNameShort === null) { 0554 return true; 0555 } 0556 if (strcasecmp($dname, $accountDomainName) == 0) { 0557 return true; 0558 } 0559 if (strcasecmp($dname, $accountDomainNameShort) == 0) { 0560 return true; 0561 } 0562 return false; 0563 } 0564 0565 /** 0566 * @param string $acctname The name to canonicalize 0567 * @param int $type The desired form of canonicalization 0568 * @return string The canonicalized name in the desired form 0569 * @throws Zend_Ldap_Exception 0570 */ 0571 public function getCanonicalAccountName($acctname, $form = 0) 0572 { 0573 $this->_splitName($acctname, $dname, $uname); 0574 0575 if (!$this->_isPossibleAuthority($dname)) { 0576 /** 0577 * @see Zend_Ldap_Exception 0578 */ 0579 // require_once 'Zend/Ldap/Exception.php'; 0580 throw new Zend_Ldap_Exception(null, 0581 "Binding domain is not an authority for user: $acctname", 0582 Zend_Ldap_Exception::LDAP_X_DOMAIN_MISMATCH); 0583 } 0584 0585 if (!$uname) { 0586 /** 0587 * @see Zend_Ldap_Exception 0588 */ 0589 // require_once 'Zend/Ldap/Exception.php'; 0590 throw new Zend_Ldap_Exception(null, "Invalid account name syntax: $acctname"); 0591 } 0592 0593 if (function_exists('mb_strtolower')) { 0594 $uname = mb_strtolower($uname, 'UTF-8'); 0595 } else { 0596 $uname = strtolower($uname); 0597 } 0598 0599 if ($form === 0) { 0600 $form = $this->_getAccountCanonicalForm(); 0601 } 0602 0603 switch ($form) { 0604 case Zend_Ldap::ACCTNAME_FORM_DN: 0605 return $this->_getAccountDn($acctname); 0606 case Zend_Ldap::ACCTNAME_FORM_USERNAME: 0607 return $uname; 0608 case Zend_Ldap::ACCTNAME_FORM_BACKSLASH: 0609 $accountDomainNameShort = $this->_getAccountDomainNameShort(); 0610 if (!$accountDomainNameShort) { 0611 /** 0612 * @see Zend_Ldap_Exception 0613 */ 0614 // require_once 'Zend/Ldap/Exception.php'; 0615 throw new Zend_Ldap_Exception(null, 'Option required: accountDomainNameShort'); 0616 } 0617 return "$accountDomainNameShort\\$uname"; 0618 case Zend_Ldap::ACCTNAME_FORM_PRINCIPAL: 0619 $accountDomainName = $this->_getAccountDomainName(); 0620 if (!$accountDomainName) { 0621 /** 0622 * @see Zend_Ldap_Exception 0623 */ 0624 // require_once 'Zend/Ldap/Exception.php'; 0625 throw new Zend_Ldap_Exception(null, 'Option required: accountDomainName'); 0626 } 0627 return "$uname@$accountDomainName"; 0628 default: 0629 /** 0630 * @see Zend_Ldap_Exception 0631 */ 0632 // require_once 'Zend/Ldap/Exception.php'; 0633 throw new Zend_Ldap_Exception(null, "Unknown canonical name form: $form"); 0634 } 0635 } 0636 0637 /** 0638 * @param array $attrs An array of names of desired attributes 0639 * @return array An array of the attributes representing the account 0640 * @throws Zend_Ldap_Exception 0641 */ 0642 protected function _getAccount($acctname, array $attrs = null) 0643 { 0644 $baseDn = $this->getBaseDn(); 0645 if (!$baseDn) { 0646 /** 0647 * @see Zend_Ldap_Exception 0648 */ 0649 // require_once 'Zend/Ldap/Exception.php'; 0650 throw new Zend_Ldap_Exception(null, 'Base DN not set'); 0651 } 0652 0653 $accountFilter = $this->_getAccountFilter($acctname); 0654 if (!$accountFilter) { 0655 /** 0656 * @see Zend_Ldap_Exception 0657 */ 0658 // require_once 'Zend/Ldap/Exception.php'; 0659 throw new Zend_Ldap_Exception(null, 'Invalid account filter'); 0660 } 0661 0662 if (!is_resource($this->getResource())) { 0663 $this->bind(); 0664 } 0665 0666 $accounts = $this->search($accountFilter, $baseDn, self::SEARCH_SCOPE_SUB, $attrs); 0667 $count = $accounts->count(); 0668 if ($count === 1) { 0669 $acct = $accounts->getFirst(); 0670 $accounts->close(); 0671 return $acct; 0672 } else if ($count === 0) { 0673 /** 0674 * @see Zend_Ldap_Exception 0675 */ 0676 // require_once 'Zend/Ldap/Exception.php'; 0677 $code = Zend_Ldap_Exception::LDAP_NO_SUCH_OBJECT; 0678 $str = "No object found for: $accountFilter"; 0679 } else { 0680 /** 0681 * @see Zend_Ldap_Exception 0682 */ 0683 // require_once 'Zend/Ldap/Exception.php'; 0684 $code = Zend_Ldap_Exception::LDAP_OPERATIONS_ERROR; 0685 $str = "Unexpected result count ($count) for: $accountFilter"; 0686 } 0687 $accounts->close(); 0688 /** 0689 * @see Zend_Ldap_Exception 0690 */ 0691 // require_once 'Zend/Ldap/Exception.php'; 0692 throw new Zend_Ldap_Exception($this, $str, $code); 0693 } 0694 0695 /** 0696 * @return Zend_Ldap Provides a fluent interface 0697 */ 0698 public function disconnect() 0699 { 0700 if (is_resource($this->_resource)) { 0701 @ldap_unbind($this->_resource); 0702 } 0703 $this->_resource = null; 0704 $this->_boundUser = false; 0705 return $this; 0706 } 0707 0708 /** 0709 * To connect using SSL it seems the client tries to verify the server 0710 * certificate by default. One way to disable this behavior is to set 0711 * 'TLS_REQCERT never' in OpenLDAP's ldap.conf and restarting Apache. Or, 0712 * if you really care about the server's cert you can put a cert on the 0713 * web server. 0714 * 0715 * @param string $host The hostname of the LDAP server to connect to 0716 * @param int $port The port number of the LDAP server to connect to 0717 * @param boolean $useSsl Use SSL 0718 * @param boolean $useStartTls Use STARTTLS 0719 * @return Zend_Ldap Provides a fluent interface 0720 * @throws Zend_Ldap_Exception 0721 */ 0722 public function connect($host = null, $port = null, $useSsl = null, $useStartTls = null) 0723 { 0724 if ($host === null) { 0725 $host = $this->_getHost(); 0726 } 0727 if ($port === null) { 0728 $port = $this->_getPort(); 0729 } else { 0730 $port = (int)$port; 0731 } 0732 if ($useSsl === null) { 0733 $useSsl = $this->_getUseSsl(); 0734 } else { 0735 $useSsl = (bool)$useSsl; 0736 } 0737 if ($useStartTls === null) { 0738 $useStartTls = $this->_getUseStartTls(); 0739 } else { 0740 $useStartTls = (bool)$useStartTls; 0741 } 0742 0743 if (!$host) { 0744 /** 0745 * @see Zend_Ldap_Exception 0746 */ 0747 // require_once 'Zend/Ldap/Exception.php'; 0748 throw new Zend_Ldap_Exception(null, 'A host parameter is required'); 0749 } 0750 0751 $useUri = false; 0752 /* Because ldap_connect doesn't really try to connect, any connect error 0753 * will actually occur during the ldap_bind call. Therefore, we save the 0754 * connect string here for reporting it in error handling in bind(). 0755 */ 0756 $hosts = array(); 0757 if (preg_match_all('~ldap(?:i|s)?://~', $host, $hosts, PREG_SET_ORDER) > 0) { 0758 $this->_connectString = $host; 0759 $useUri = true; 0760 $useSsl = false; 0761 } else { 0762 if ($useSsl) { 0763 $this->_connectString = 'ldaps://' . $host; 0764 $useUri = true; 0765 } else { 0766 $this->_connectString = 'ldap://' . $host; 0767 } 0768 if ($port) { 0769 $this->_connectString .= ':' . $port; 0770 } 0771 } 0772 0773 $this->disconnect(); 0774 0775 /* Only OpenLDAP 2.2 + supports URLs so if SSL is not requested, just 0776 * use the old form. 0777 */ 0778 $resource = ($useUri) ? @ldap_connect($this->_connectString) : @ldap_connect($host, $port); 0779 0780 if (is_resource($resource) === true) { 0781 $this->_resource = $resource; 0782 $this->_boundUser = false; 0783 0784 $optReferrals = ($this->_getOptReferrals()) ? 1 : 0; 0785 if (@ldap_set_option($resource, LDAP_OPT_PROTOCOL_VERSION, 3) && 0786 @ldap_set_option($resource, LDAP_OPT_REFERRALS, $optReferrals)) { 0787 if ($useSsl || !$useStartTls || @ldap_start_tls($resource)) { 0788 return $this; 0789 } 0790 } 0791 0792 /** 0793 * @see Zend_Ldap_Exception 0794 */ 0795 // require_once 'Zend/Ldap/Exception.php'; 0796 $zle = new Zend_Ldap_Exception($this, "$host:$port"); 0797 $this->disconnect(); 0798 throw $zle; 0799 } 0800 /** 0801 * @see Zend_Ldap_Exception 0802 */ 0803 // require_once 'Zend/Ldap/Exception.php'; 0804 throw new Zend_Ldap_Exception(null, "Failed to connect to LDAP server: $host:$port"); 0805 } 0806 0807 /** 0808 * @param string $username The username for authenticating the bind 0809 * @param string $password The password for authenticating the bind 0810 * @return Zend_Ldap Provides a fluent interface 0811 * @throws Zend_Ldap_Exception 0812 */ 0813 public function bind($username = null, $password = null) 0814 { 0815 $moreCreds = true; 0816 0817 // Security check: remove null bytes in password 0818 // @see https://net.educause.edu/ir/library/pdf/csd4875.pdf 0819 $password = str_replace("\0", '', $password); 0820 0821 if ($username === null) { 0822 $username = $this->_getUsername(); 0823 $password = $this->_getPassword(); 0824 $moreCreds = false; 0825 } 0826 0827 if (empty($username)) { 0828 /* Perform anonymous bind 0829 */ 0830 $username = null; 0831 $password = null; 0832 } else { 0833 /* Check to make sure the username is in DN form. 0834 */ 0835 /** 0836 * @see Zend_Ldap_Dn 0837 */ 0838 // require_once 'Zend/Ldap/Dn.php'; 0839 if (!Zend_Ldap_Dn::checkDn($username)) { 0840 if ($this->_getBindRequiresDn()) { 0841 /* moreCreds stops an infinite loop if _getUsername does not 0842 * return a DN and the bind requires it 0843 */ 0844 if ($moreCreds) { 0845 try { 0846 $username = $this->_getAccountDn($username); 0847 } catch (Zend_Ldap_Exception $zle) { 0848 switch ($zle->getCode()) { 0849 case Zend_Ldap_Exception::LDAP_NO_SUCH_OBJECT: 0850 case Zend_Ldap_Exception::LDAP_X_DOMAIN_MISMATCH: 0851 case Zend_Ldap_Exception::LDAP_X_EXTENSION_NOT_LOADED: 0852 throw $zle; 0853 } 0854 throw new Zend_Ldap_Exception(null, 0855 'Failed to retrieve DN for account: ' . $username . 0856 ' [' . $zle->getMessage() . ']', 0857 Zend_Ldap_Exception::LDAP_OPERATIONS_ERROR); 0858 } 0859 } else { 0860 /** 0861 * @see Zend_Ldap_Exception 0862 */ 0863 // require_once 'Zend/Ldap/Exception.php'; 0864 throw new Zend_Ldap_Exception(null, 'Binding requires username in DN form'); 0865 } 0866 } else { 0867 $username = $this->getCanonicalAccountName($username, 0868 $this->_getAccountCanonicalForm()); 0869 } 0870 } 0871 } 0872 0873 if (!is_resource($this->_resource)) { 0874 $this->connect(); 0875 } 0876 0877 if ($username !== null && $password === '' && $this->_getAllowEmptyPassword() !== true) { 0878 /** 0879 * @see Zend_Ldap_Exception 0880 */ 0881 // require_once 'Zend/Ldap/Exception.php'; 0882 $zle = new Zend_Ldap_Exception(null, 0883 'Empty password not allowed - see allowEmptyPassword option.'); 0884 } else { 0885 if (@ldap_bind($this->_resource, $username, $password)) { 0886 $this->_boundUser = $username; 0887 return $this; 0888 } 0889 0890 $message = ($username === null) ? $this->_connectString : $username; 0891 /** 0892 * @see Zend_Ldap_Exception 0893 */ 0894 // require_once 'Zend/Ldap/Exception.php'; 0895 switch ($this->getLastErrorCode()) { 0896 case Zend_Ldap_Exception::LDAP_SERVER_DOWN: 0897 /* If the error is related to establishing a connection rather than binding, 0898 * the connect string is more informative than the username. 0899 */ 0900 $message = $this->_connectString; 0901 } 0902 0903 $zle = new Zend_Ldap_Exception($this, $message); 0904 } 0905 $this->disconnect(); 0906 throw $zle; 0907 } 0908 0909 /** 0910 * A global LDAP search routine for finding information. 0911 * 0912 * Options can be either passed as single parameters according to the 0913 * method signature or as an array with one or more of the following keys 0914 * - filter 0915 * - baseDn 0916 * - scope 0917 * - attributes 0918 * - sort 0919 * - collectionClass 0920 * - sizelimit 0921 * - timelimit 0922 * 0923 * @param string|Zend_Ldap_Filter_Abstract|array $filter 0924 * @param string|Zend_Ldap_Dn|null $basedn 0925 * @param integer $scope 0926 * @param array $attributes 0927 * @param string|null $sort 0928 * @param string|null $collectionClass 0929 * @param integer $sizelimit 0930 * @param integer $timelimit 0931 * @return Zend_Ldap_Collection 0932 * @throws Zend_Ldap_Exception 0933 */ 0934 public function search($filter, $basedn = null, $scope = self::SEARCH_SCOPE_SUB, array $attributes = array(), 0935 $sort = null, $collectionClass = null, $sizelimit = 0, $timelimit = 0) 0936 { 0937 if (is_array($filter)) { 0938 $options = array_change_key_case($filter, CASE_LOWER); 0939 foreach ($options as $key => $value) { 0940 switch ($key) { 0941 case 'filter': 0942 case 'basedn': 0943 case 'scope': 0944 case 'sort': 0945 $$key = $value; 0946 break; 0947 case 'attributes': 0948 if (is_array($value)) { 0949 $attributes = $value; 0950 } 0951 break; 0952 case 'collectionclass': 0953 $collectionClass = $value; 0954 break; 0955 case 'sizelimit': 0956 case 'timelimit': 0957 $$key = (int)$value; 0958 } 0959 } 0960 } 0961 0962 if ($basedn === null) { 0963 $basedn = $this->getBaseDn(); 0964 } 0965 else if ($basedn instanceof Zend_Ldap_Dn) { 0966 $basedn = $basedn->toString(); 0967 } 0968 0969 if ($filter instanceof Zend_Ldap_Filter_Abstract) { 0970 $filter = $filter->toString(); 0971 } 0972 0973 switch ($scope) { 0974 case self::SEARCH_SCOPE_ONE: 0975 $search = @ldap_list($this->getResource(), $basedn, $filter, $attributes, 0, $sizelimit, $timelimit); 0976 break; 0977 case self::SEARCH_SCOPE_BASE: 0978 $search = @ldap_read($this->getResource(), $basedn, $filter, $attributes, 0, $sizelimit, $timelimit); 0979 break; 0980 case self::SEARCH_SCOPE_SUB: 0981 default: 0982 $search = @ldap_search($this->getResource(), $basedn, $filter, $attributes, 0, $sizelimit, $timelimit); 0983 break; 0984 } 0985 0986 if($search === false) { 0987 /** 0988 * @see Zend_Ldap_Exception 0989 */ 0990 // require_once 'Zend/Ldap/Exception.php'; 0991 throw new Zend_Ldap_Exception($this, 'searching: ' . $filter); 0992 } 0993 if ($sort !== null && is_string($sort)) { 0994 $isSorted = @ldap_sort($this->getResource(), $search, $sort); 0995 if($isSorted === false) { 0996 /** 0997 * @see Zend_Ldap_Exception 0998 */ 0999 // require_once 'Zend/Ldap/Exception.php'; 1000 throw new Zend_Ldap_Exception($this, 'sorting: ' . $sort); 1001 } 1002 } 1003 1004 /** 1005 * Zend_Ldap_Collection_Iterator_Default 1006 */ 1007 // require_once 'Zend/Ldap/Collection/Iterator/Default.php'; 1008 $iterator = new Zend_Ldap_Collection_Iterator_Default($this, $search); 1009 return $this->_createCollection($iterator, $collectionClass); 1010 } 1011 1012 /** 1013 * Extension point for collection creation 1014 * 1015 * @param Zend_Ldap_Collection_Iterator_Default $iterator 1016 * @param string|null $collectionClass 1017 * @return Zend_Ldap_Collection 1018 * @throws Zend_Ldap_Exception 1019 */ 1020 protected function _createCollection(Zend_Ldap_Collection_Iterator_Default $iterator, $collectionClass) 1021 { 1022 if ($collectionClass === null) { 1023 /** 1024 * Zend_Ldap_Collection 1025 */ 1026 // require_once 'Zend/Ldap/Collection.php'; 1027 return new Zend_Ldap_Collection($iterator); 1028 } else { 1029 $collectionClass = (string)$collectionClass; 1030 if (!class_exists($collectionClass)) { 1031 /** 1032 * @see Zend_Ldap_Exception 1033 */ 1034 // require_once 'Zend/Ldap/Exception.php'; 1035 throw new Zend_Ldap_Exception(null, 1036 "Class '$collectionClass' can not be found"); 1037 } 1038 if (!is_subclass_of($collectionClass, 'Zend_Ldap_Collection')) { 1039 /** 1040 * @see Zend_Ldap_Exception 1041 */ 1042 // require_once 'Zend/Ldap/Exception.php'; 1043 throw new Zend_Ldap_Exception(null, 1044 "Class '$collectionClass' must subclass 'Zend_Ldap_Collection'"); 1045 } 1046 return new $collectionClass($iterator); 1047 } 1048 } 1049 1050 /** 1051 * Count items found by given filter. 1052 * 1053 * @param string|Zend_Ldap_Filter_Abstract $filter 1054 * @param string|Zend_Ldap_Dn|null $basedn 1055 * @param integer $scope 1056 * @return integer 1057 * @throws Zend_Ldap_Exception 1058 */ 1059 public function count($filter, $basedn = null, $scope = self::SEARCH_SCOPE_SUB) 1060 { 1061 try { 1062 $result = $this->search($filter, $basedn, $scope, array('dn'), null); 1063 } catch (Zend_Ldap_Exception $e) { 1064 if ($e->getCode() === Zend_Ldap_Exception::LDAP_NO_SUCH_OBJECT) return 0; 1065 else throw $e; 1066 } 1067 return $result->count(); 1068 } 1069 1070 /** 1071 * Count children for a given DN. 1072 * 1073 * @param string|Zend_Ldap_Dn $dn 1074 * @return integer 1075 * @throws Zend_Ldap_Exception 1076 */ 1077 public function countChildren($dn) 1078 { 1079 return $this->count('(objectClass=*)', $dn, self::SEARCH_SCOPE_ONE); 1080 } 1081 1082 /** 1083 * Check if a given DN exists. 1084 * 1085 * @param string|Zend_Ldap_Dn $dn 1086 * @return boolean 1087 * @throws Zend_Ldap_Exception 1088 */ 1089 public function exists($dn) 1090 { 1091 return ($this->count('(objectClass=*)', $dn, self::SEARCH_SCOPE_BASE) == 1); 1092 } 1093 1094 /** 1095 * Search LDAP registry for entries matching filter and optional attributes 1096 * 1097 * Options can be either passed as single parameters according to the 1098 * method signature or as an array with one or more of the following keys 1099 * - filter 1100 * - baseDn 1101 * - scope 1102 * - attributes 1103 * - sort 1104 * - reverseSort 1105 * - sizelimit 1106 * - timelimit 1107 * 1108 * @param string|Zend_Ldap_Filter_Abstract|array $filter 1109 * @param string|Zend_Ldap_Dn|null $basedn 1110 * @param integer $scope 1111 * @param array $attributes 1112 * @param string|null $sort 1113 * @param boolean $reverseSort 1114 * @param integer $sizelimit 1115 * @param integer $timelimit 1116 * @return array 1117 * @throws Zend_Ldap_Exception 1118 */ 1119 public function searchEntries($filter, $basedn = null, $scope = self::SEARCH_SCOPE_SUB, 1120 array $attributes = array(), $sort = null, $reverseSort = false, $sizelimit = 0, $timelimit = 0) 1121 { 1122 if (is_array($filter)) { 1123 $filter = array_change_key_case($filter, CASE_LOWER); 1124 if (isset($filter['collectionclass'])) { 1125 unset($filter['collectionclass']); 1126 } 1127 if (isset($filter['reversesort'])) { 1128 $reverseSort = $filter['reversesort']; 1129 unset($filter['reversesort']); 1130 } 1131 } 1132 $result = $this->search($filter, $basedn, $scope, $attributes, $sort, null, $sizelimit, $timelimit); 1133 $items = $result->toArray(); 1134 if ((bool)$reverseSort === true) { 1135 $items = array_reverse($items, false); 1136 } 1137 return $items; 1138 } 1139 1140 /** 1141 * Get LDAP entry by DN 1142 * 1143 * @param string|Zend_Ldap_Dn $dn 1144 * @param array $attributes 1145 * @param boolean $throwOnNotFound 1146 * @return array 1147 * @throws Zend_Ldap_Exception 1148 */ 1149 public function getEntry($dn, array $attributes = array(), $throwOnNotFound = false) 1150 { 1151 try { 1152 $result = $this->search("(objectClass=*)", $dn, self::SEARCH_SCOPE_BASE, 1153 $attributes, null); 1154 return $result->getFirst(); 1155 } catch (Zend_Ldap_Exception $e){ 1156 if ($throwOnNotFound !== false) throw $e; 1157 } 1158 return null; 1159 } 1160 1161 /** 1162 * Prepares an ldap data entry array for insert/update operation 1163 * 1164 * @param array $entry 1165 * @return void 1166 * @throws InvalidArgumentException 1167 */ 1168 public static function prepareLdapEntryArray(array &$entry) 1169 { 1170 if (array_key_exists('dn', $entry)) unset($entry['dn']); 1171 foreach ($entry as $key => $value) { 1172 if (is_array($value)) { 1173 foreach ($value as $i => $v) { 1174 if ($v === null) unset($value[$i]); 1175 else if (!is_scalar($v)) { 1176 throw new InvalidArgumentException('Only scalar values allowed in LDAP data'); 1177 } else { 1178 $v = (string)$v; 1179 if (strlen($v) == 0) { 1180 unset($value[$i]); 1181 } else { 1182 $value[$i] = $v; 1183 } 1184 } 1185 } 1186 $entry[$key] = array_values($value); 1187 } else { 1188 if ($value === null) $entry[$key] = array(); 1189 else if (!is_scalar($value)) { 1190 throw new InvalidArgumentException('Only scalar values allowed in LDAP data'); 1191 } else { 1192 $value = (string)$value; 1193 if (strlen($value) == 0) { 1194 $entry[$key] = array(); 1195 } else { 1196 $entry[$key] = array($value); 1197 } 1198 } 1199 } 1200 } 1201 $entry = array_change_key_case($entry, CASE_LOWER); 1202 } 1203 1204 /** 1205 * Add new information to the LDAP repository 1206 * 1207 * @param string|Zend_Ldap_Dn $dn 1208 * @param array $entry 1209 * @return Zend_Ldap Provides a fluent interface 1210 * @throws Zend_Ldap_Exception 1211 */ 1212 public function add($dn, array $entry) 1213 { 1214 if (!($dn instanceof Zend_Ldap_Dn)) { 1215 $dn = Zend_Ldap_Dn::factory($dn, null); 1216 } 1217 self::prepareLdapEntryArray($entry); 1218 foreach ($entry as $key => $value) { 1219 if (is_array($value) && count($value) === 0) { 1220 unset($entry[$key]); 1221 } 1222 } 1223 1224 $rdnParts = $dn->getRdn(Zend_Ldap_Dn::ATTR_CASEFOLD_LOWER); 1225 foreach ($rdnParts as $key => $value) { 1226 $value = Zend_Ldap_Dn::unescapeValue($value); 1227 if (!array_key_exists($key, $entry)) { 1228 $entry[$key] = array($value); 1229 } else if (!in_array($value, $entry[$key])) { 1230 $entry[$key] = array_merge(array($value), $entry[$key]); 1231 } 1232 } 1233 $adAttributes = array('distinguishedname', 'instancetype', 'name', 'objectcategory', 1234 'objectguid', 'usnchanged', 'usncreated', 'whenchanged', 'whencreated'); 1235 foreach ($adAttributes as $attr) { 1236 if (array_key_exists($attr, $entry)) { 1237 unset($entry[$attr]); 1238 } 1239 } 1240 1241 $isAdded = @ldap_add($this->getResource(), $dn->toString(), $entry); 1242 if($isAdded === false) { 1243 /** 1244 * @see Zend_Ldap_Exception 1245 */ 1246 // require_once 'Zend/Ldap/Exception.php'; 1247 throw new Zend_Ldap_Exception($this, 'adding: ' . $dn->toString()); 1248 } 1249 return $this; 1250 } 1251 1252 /** 1253 * Update LDAP registry 1254 * 1255 * @param string|Zend_Ldap_Dn $dn 1256 * @param array $entry 1257 * @return Zend_Ldap Provides a fluent interface 1258 * @throws Zend_Ldap_Exception 1259 */ 1260 public function update($dn, array $entry) 1261 { 1262 if (!($dn instanceof Zend_Ldap_Dn)) { 1263 $dn = Zend_Ldap_Dn::factory($dn, null); 1264 } 1265 self::prepareLdapEntryArray($entry); 1266 1267 $rdnParts = $dn->getRdn(Zend_Ldap_Dn::ATTR_CASEFOLD_LOWER); 1268 foreach ($rdnParts as $key => $value) { 1269 $value = Zend_Ldap_Dn::unescapeValue($value); 1270 if (array_key_exists($key, $entry) && !in_array($value, $entry[$key])) { 1271 $entry[$key] = array_merge(array($value), $entry[$key]); 1272 } 1273 } 1274 1275 $adAttributes = array('distinguishedname', 'instancetype', 'name', 'objectcategory', 1276 'objectguid', 'usnchanged', 'usncreated', 'whenchanged', 'whencreated'); 1277 foreach ($adAttributes as $attr) { 1278 if (array_key_exists($attr, $entry)) { 1279 unset($entry[$attr]); 1280 } 1281 } 1282 1283 if (count($entry) > 0) { 1284 $isModified = @ldap_modify($this->getResource(), $dn->toString(), $entry); 1285 if($isModified === false) { 1286 /** 1287 * @see Zend_Ldap_Exception 1288 */ 1289 // require_once 'Zend/Ldap/Exception.php'; 1290 throw new Zend_Ldap_Exception($this, 'updating: ' . $dn->toString()); 1291 } 1292 } 1293 return $this; 1294 } 1295 1296 /** 1297 * Save entry to LDAP registry. 1298 * 1299 * Internally decides if entry will be updated to added by calling 1300 * {@link exists()}. 1301 * 1302 * @param string|Zend_Ldap_Dn $dn 1303 * @param array $entry 1304 * @return Zend_Ldap Provides a fluent interface 1305 * @throws Zend_Ldap_Exception 1306 */ 1307 public function save($dn, array $entry) 1308 { 1309 if ($dn instanceof Zend_Ldap_Dn) { 1310 $dn = $dn->toString(); 1311 } 1312 if ($this->exists($dn)) $this->update($dn, $entry); 1313 else $this->add($dn, $entry); 1314 return $this; 1315 } 1316 1317 /** 1318 * Delete an LDAP entry 1319 * 1320 * @param string|Zend_Ldap_Dn $dn 1321 * @param boolean $recursively 1322 * @return Zend_Ldap Provides a fluent interface 1323 * @throws Zend_Ldap_Exception 1324 */ 1325 public function delete($dn, $recursively = false) 1326 { 1327 if ($dn instanceof Zend_Ldap_Dn) { 1328 $dn = $dn->toString(); 1329 } 1330 if ($recursively === true) { 1331 if ($this->countChildren($dn)>0) { 1332 $children = $this->_getChildrenDns($dn); 1333 foreach ($children as $c) { 1334 $this->delete($c, true); 1335 } 1336 } 1337 } 1338 $isDeleted = @ldap_delete($this->getResource(), $dn); 1339 if($isDeleted === false) { 1340 /** 1341 * @see Zend_Ldap_Exception 1342 */ 1343 // require_once 'Zend/Ldap/Exception.php'; 1344 throw new Zend_Ldap_Exception($this, 'deleting: ' . $dn); 1345 } 1346 return $this; 1347 } 1348 1349 /** 1350 * Retrieve the immediate children DNs of the given $parentDn 1351 * 1352 * This method is used in recursive methods like {@see delete()} 1353 * or {@see copy()} 1354 * 1355 * @param string|Zend_Ldap_Dn $parentDn 1356 * @return array of DNs 1357 */ 1358 protected function _getChildrenDns($parentDn) 1359 { 1360 if ($parentDn instanceof Zend_Ldap_Dn) { 1361 $parentDn = $parentDn->toString(); 1362 } 1363 $children = array(); 1364 $search = @ldap_list($this->getResource(), $parentDn, '(objectClass=*)', array('dn')); 1365 for ($entry = @ldap_first_entry($this->getResource(), $search); 1366 $entry !== false; 1367 $entry = @ldap_next_entry($this->getResource(), $entry)) { 1368 $childDn = @ldap_get_dn($this->getResource(), $entry); 1369 if ($childDn === false) { 1370 /** 1371 * @see Zend_Ldap_Exception 1372 */ 1373 // require_once 'Zend/Ldap/Exception.php'; 1374 throw new Zend_Ldap_Exception($this, 'getting dn'); 1375 } 1376 $children[] = $childDn; 1377 } 1378 @ldap_free_result($search); 1379 return $children; 1380 } 1381 1382 /** 1383 * Moves a LDAP entry from one DN to another subtree. 1384 * 1385 * @param string|Zend_Ldap_Dn $from 1386 * @param string|Zend_Ldap_Dn $to 1387 * @param boolean $recursively 1388 * @param boolean $alwaysEmulate 1389 * @return Zend_Ldap Provides a fluent interface 1390 * @throws Zend_Ldap_Exception 1391 */ 1392 public function moveToSubtree($from, $to, $recursively = false, $alwaysEmulate = false) 1393 { 1394 if ($from instanceof Zend_Ldap_Dn) { 1395 $orgDnParts = $from->toArray(); 1396 } else { 1397 $orgDnParts = Zend_Ldap_Dn::explodeDn($from); 1398 } 1399 1400 if ($to instanceof Zend_Ldap_Dn) { 1401 $newParentDnParts = $to->toArray(); 1402 } else { 1403 $newParentDnParts = Zend_Ldap_Dn::explodeDn($to); 1404 } 1405 1406 $newDnParts = array_merge(array(array_shift($orgDnParts)), $newParentDnParts); 1407 $newDn = Zend_Ldap_Dn::fromArray($newDnParts); 1408 return $this->rename($from, $newDn, $recursively, $alwaysEmulate); 1409 } 1410 1411 /** 1412 * Moves a LDAP entry from one DN to another DN. 1413 * 1414 * This is an alias for {@link rename()} 1415 * 1416 * @param string|Zend_Ldap_Dn $from 1417 * @param string|Zend_Ldap_Dn $to 1418 * @param boolean $recursively 1419 * @param boolean $alwaysEmulate 1420 * @return Zend_Ldap Provides a fluent interface 1421 * @throws Zend_Ldap_Exception 1422 */ 1423 public function move($from, $to, $recursively = false, $alwaysEmulate = false) 1424 { 1425 return $this->rename($from, $to, $recursively, $alwaysEmulate); 1426 } 1427 1428 /** 1429 * Renames a LDAP entry from one DN to another DN. 1430 * 1431 * This method implicitely moves the entry to another location within the tree. 1432 * 1433 * @param string|Zend_Ldap_Dn $from 1434 * @param string|Zend_Ldap_Dn $to 1435 * @param boolean $recursively 1436 * @param boolean $alwaysEmulate 1437 * @return Zend_Ldap Provides a fluent interface 1438 * @throws Zend_Ldap_Exception 1439 */ 1440 public function rename($from, $to, $recursively = false, $alwaysEmulate = false) 1441 { 1442 $emulate = (bool)$alwaysEmulate; 1443 if (!function_exists('ldap_rename')) $emulate = true; 1444 else if ($recursively) $emulate = true; 1445 1446 if ($emulate === false) { 1447 if ($from instanceof Zend_Ldap_Dn) { 1448 $from = $from->toString(); 1449 } 1450 1451 if ($to instanceof Zend_Ldap_Dn) { 1452 $newDnParts = $to->toArray(); 1453 } else { 1454 $newDnParts = Zend_Ldap_Dn::explodeDn($to); 1455 } 1456 1457 $newRdn = Zend_Ldap_Dn::implodeRdn(array_shift($newDnParts)); 1458 $newParent = Zend_Ldap_Dn::implodeDn($newDnParts); 1459 $isOK = @ldap_rename($this->getResource(), $from, $newRdn, $newParent, true); 1460 if($isOK === false) { 1461 /** 1462 * @see Zend_Ldap_Exception 1463 */ 1464 // require_once 'Zend/Ldap/Exception.php'; 1465 throw new Zend_Ldap_Exception($this, 'renaming ' . $from . ' to ' . $to); 1466 } 1467 else if (!$this->exists($to)) $emulate = true; 1468 } 1469 if ($emulate) { 1470 $this->copy($from, $to, $recursively); 1471 $this->delete($from, $recursively); 1472 } 1473 return $this; 1474 } 1475 1476 /** 1477 * Copies a LDAP entry from one DN to another subtree. 1478 * 1479 * @param string|Zend_Ldap_Dn $from 1480 * @param string|Zend_Ldap_Dn $to 1481 * @param boolean $recursively 1482 * @return Zend_Ldap Provides a fluent interface 1483 * @throws Zend_Ldap_Exception 1484 */ 1485 public function copyToSubtree($from, $to, $recursively = false) 1486 { 1487 if ($from instanceof Zend_Ldap_Dn) { 1488 $orgDnParts = $from->toArray(); 1489 } else { 1490 $orgDnParts = Zend_Ldap_Dn::explodeDn($from); 1491 } 1492 1493 if ($to instanceof Zend_Ldap_Dn) { 1494 $newParentDnParts = $to->toArray(); 1495 } else { 1496 $newParentDnParts = Zend_Ldap_Dn::explodeDn($to); 1497 } 1498 1499 $newDnParts = array_merge(array(array_shift($orgDnParts)), $newParentDnParts); 1500 $newDn = Zend_Ldap_Dn::fromArray($newDnParts); 1501 return $this->copy($from, $newDn, $recursively); 1502 } 1503 1504 /** 1505 * Copies a LDAP entry from one DN to another DN. 1506 * 1507 * @param string|Zend_Ldap_Dn $from 1508 * @param string|Zend_Ldap_Dn $to 1509 * @param boolean $recursively 1510 * @return Zend_Ldap Provides a fluent interface 1511 * @throws Zend_Ldap_Exception 1512 */ 1513 public function copy($from, $to, $recursively = false) 1514 { 1515 $entry = $this->getEntry($from, array(), true); 1516 1517 if ($to instanceof Zend_Ldap_Dn) { 1518 $toDnParts = $to->toArray(); 1519 } else { 1520 $toDnParts = Zend_Ldap_Dn::explodeDn($to); 1521 } 1522 $this->add($to, $entry); 1523 1524 if ($recursively === true && $this->countChildren($from)>0) { 1525 $children = $this->_getChildrenDns($from); 1526 foreach ($children as $c) { 1527 $cDnParts = Zend_Ldap_Dn::explodeDn($c); 1528 $newChildParts = array_merge(array(array_shift($cDnParts)), $toDnParts); 1529 $newChild = Zend_Ldap_Dn::implodeDn($newChildParts); 1530 $this->copy($c, $newChild, true); 1531 } 1532 } 1533 return $this; 1534 } 1535 1536 /** 1537 * Returns the specified DN as a Zend_Ldap_Node 1538 * 1539 * @param string|Zend_Ldap_Dn $dn 1540 * @return Zend_Ldap_Node|null 1541 * @throws Zend_Ldap_Exception 1542 */ 1543 public function getNode($dn) 1544 { 1545 /** 1546 * Zend_Ldap_Node 1547 */ 1548 // require_once 'Zend/Ldap/Node.php'; 1549 return Zend_Ldap_Node::fromLdap($dn, $this); 1550 } 1551 1552 /** 1553 * Returns the base node as a Zend_Ldap_Node 1554 * 1555 * @return Zend_Ldap_Node 1556 * @throws Zend_Ldap_Exception 1557 */ 1558 public function getBaseNode() 1559 { 1560 return $this->getNode($this->getBaseDn(), $this); 1561 } 1562 1563 /** 1564 * Returns the RootDSE 1565 * 1566 * @return Zend_Ldap_Node_RootDse 1567 * @throws Zend_Ldap_Exception 1568 */ 1569 public function getRootDse() 1570 { 1571 if ($this->_rootDse === null) { 1572 /** 1573 * @see Zend_Ldap_Node_Schema 1574 */ 1575 // require_once 'Zend/Ldap/Node/RootDse.php'; 1576 $this->_rootDse = Zend_Ldap_Node_RootDse::create($this); 1577 } 1578 return $this->_rootDse; 1579 } 1580 1581 /** 1582 * Returns the schema 1583 * 1584 * @return Zend_Ldap_Node_Schema 1585 * @throws Zend_Ldap_Exception 1586 */ 1587 public function getSchema() 1588 { 1589 if ($this->_schema === null) { 1590 /** 1591 * @see Zend_Ldap_Node_Schema 1592 */ 1593 // require_once 'Zend/Ldap/Node/Schema.php'; 1594 $this->_schema = Zend_Ldap_Node_Schema::create($this); 1595 } 1596 return $this->_schema; 1597 } 1598 }