2
0

Ldap.php 47 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476
  1. <?php
  2. /**
  3. * Zend Framework
  4. *
  5. * LICENSE
  6. *
  7. * This source file is subject to the new BSD license that is bundled
  8. * with this package in the file LICENSE.txt.
  9. * It is also available through the world-wide-web at this URL:
  10. * http://framework.zend.com/license/new-bsd
  11. * If you did not receive a copy of the license and are unable to
  12. * obtain it through the world-wide-web, please send an email
  13. * to license@zend.com so we can send you a copy immediately.
  14. *
  15. * @category Zend
  16. * @package Zend_Ldap
  17. * @copyright Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
  18. * @license http://framework.zend.com/license/new-bsd New BSD License
  19. * @version $Id$
  20. */
  21. /**
  22. * @category Zend
  23. * @package Zend_Ldap
  24. * @copyright Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
  25. * @license http://framework.zend.com/license/new-bsd New BSD License
  26. */
  27. class Zend_Ldap
  28. {
  29. const SEARCH_SCOPE_SUB = 1;
  30. const SEARCH_SCOPE_ONE = 2;
  31. const SEARCH_SCOPE_BASE = 3;
  32. const ACCTNAME_FORM_DN = 1;
  33. const ACCTNAME_FORM_USERNAME = 2;
  34. const ACCTNAME_FORM_BACKSLASH = 3;
  35. const ACCTNAME_FORM_PRINCIPAL = 4;
  36. /**
  37. * String used with ldap_connect for error handling purposes.
  38. *
  39. * @var string
  40. */
  41. private $_connectString;
  42. /**
  43. * The options used in connecting, binding, etc.
  44. *
  45. * @var array
  46. */
  47. protected $_options = null;
  48. /**
  49. * The raw LDAP extension resource.
  50. *
  51. * @var resource
  52. */
  53. protected $_resource = null;
  54. /**
  55. * Caches the RootDSE
  56. *
  57. * @var Zend_Ldap_Node
  58. */
  59. protected $_rootDse = null;
  60. /**
  61. * Caches the schema
  62. *
  63. * @var Zend_Ldap_Node
  64. */
  65. protected $_schema = null;
  66. /**
  67. * @deprecated will be removed, use {@see Zend_Ldap_Filter_Abstract::escapeValue()}
  68. * @param string $str The string to escape.
  69. * @return string The escaped string
  70. */
  71. public static function filterEscape($str)
  72. {
  73. /**
  74. * @see Zend_Ldap_Filter_Abstract
  75. */
  76. require_once 'Zend/Ldap/Filter/Abstract.php';
  77. return Zend_Ldap_Filter_Abstract::escapeValue($str);
  78. }
  79. /**
  80. * @deprecated will be removed, use {@see Zend_Ldap_Dn::checkDn()}
  81. * @param string $dn The DN to parse
  82. * @param array $keys An optional array to receive DN keys (e.g. CN, OU, DC, ...)
  83. * @param array $vals An optional array to receive DN values
  84. * @return boolean True if the DN was successfully parsed or false if the string is
  85. * not a valid DN.
  86. */
  87. public static function explodeDn($dn, array &$keys = null, array &$vals = null)
  88. {
  89. /**
  90. * @see Zend_Ldap_Dn
  91. */
  92. require_once 'Zend/Ldap/Dn.php';
  93. return Zend_Ldap_Dn::checkDn($dn, $keys, $vals);
  94. }
  95. /**
  96. * Constructor.
  97. *
  98. * @param array|Zend_Config $options Options used in connecting, binding, etc.
  99. * @return void
  100. */
  101. public function __construct($options = array())
  102. {
  103. $this->setOptions($options);
  104. }
  105. /**
  106. * Destructor.
  107. *
  108. * @return void
  109. */
  110. public function __destruct()
  111. {
  112. $this->disconnect();
  113. }
  114. /**
  115. * @return resource The raw LDAP extension resource.
  116. */
  117. public function getResource()
  118. {
  119. return $this->_resource;
  120. }
  121. /**
  122. * Return the LDAP error number of the last LDAP command
  123. *
  124. * @return int
  125. */
  126. public function getLastErrorCode()
  127. {
  128. $ret = @ldap_get_option($this->getResource(), LDAP_OPT_ERROR_NUMBER, $err);
  129. if ($ret === true) {
  130. if ($err <= -1 && $err >= -17) {
  131. /**
  132. * @see Zend_Ldap_Exception
  133. */
  134. require_once 'Zend/Ldap/Exception.php';
  135. /* For some reason draft-ietf-ldapext-ldap-c-api-xx.txt error
  136. * codes in OpenLDAP are negative values from -1 to -17.
  137. */
  138. $err = Zend_Ldap_Exception::LDAP_SERVER_DOWN + (-$err - 1);
  139. }
  140. return $err;
  141. }
  142. return 0;
  143. }
  144. /**
  145. * Return the LDAP error message of the last LDAP command
  146. *
  147. * @param int $errorCode
  148. * @param array $errorMessages
  149. * @return string
  150. */
  151. public function getLastError(&$errorCode = null, array &$errorMessages = null)
  152. {
  153. $errorCode = $this->getLastErrorCode();
  154. $errorMessages = array();
  155. /* The various error retrieval functions can return
  156. * different things so we just try to collect what we
  157. * can and eliminate dupes.
  158. */
  159. $estr1 = @ldap_error($this->getResource());
  160. if ($errorCode !== 0 && $estr1 === 'Success') {
  161. $estr1 = @ldap_err2str($errorCode);
  162. }
  163. if (!empty($estr1)) {
  164. $errorMessages[] = $estr1;
  165. }
  166. @ldap_get_option($this->getResource(), LDAP_OPT_ERROR_STRING, $estr2);
  167. if (!empty($estr2) && !in_array($estr2, $errorMessages)) {
  168. $errorMessages[] = $estr2;
  169. }
  170. $message = '';
  171. if ($errorCode > 0) {
  172. $message = '0x' . dechex($errorCode) . ' ';
  173. } else {
  174. $message = '';
  175. }
  176. if (count($errorMessages) > 0) {
  177. $message .= '(' . implode('; ', $errorMessages) . ')';
  178. } else {
  179. $message .= '(no error message from LDAP)';
  180. }
  181. return $message;
  182. }
  183. /**
  184. * Sets the options used in connecting, binding, etc.
  185. *
  186. * Valid option keys:
  187. * host
  188. * port
  189. * useSsl
  190. * username
  191. * password
  192. * bindRequiresDn
  193. * baseDn
  194. * accountCanonicalForm
  195. * accountDomainName
  196. * accountDomainNameShort
  197. * accountFilterFormat
  198. * allowEmptyPassword
  199. * useStartTls
  200. * optRefferals
  201. * tryUsernameSplit
  202. *
  203. * @param array|Zend_Config $options Options used in connecting, binding, etc.
  204. * @return Zend_Ldap Provides a fluent interface
  205. * @throws Zend_Ldap_Exception
  206. */
  207. public function setOptions($options)
  208. {
  209. if ($options instanceof Zend_Config) {
  210. $options = $options->toArray();
  211. }
  212. $permittedOptions = array(
  213. 'host' => null,
  214. 'port' => 0,
  215. 'useSsl' => false,
  216. 'username' => null,
  217. 'password' => null,
  218. 'bindRequiresDn' => false,
  219. 'baseDn' => null,
  220. 'accountCanonicalForm' => null,
  221. 'accountDomainName' => null,
  222. 'accountDomainNameShort' => null,
  223. 'accountFilterFormat' => null,
  224. 'allowEmptyPassword' => false,
  225. 'useStartTls' => false,
  226. 'optReferrals' => false,
  227. 'tryUsernameSplit' => true,
  228. );
  229. foreach ($permittedOptions as $key => $val) {
  230. if (array_key_exists($key, $options)) {
  231. $val = $options[$key];
  232. unset($options[$key]);
  233. /* Enforce typing. This eliminates issues like Zend_Config_Ini
  234. * returning '1' as a string (ZF-3163).
  235. */
  236. switch ($key) {
  237. case 'port':
  238. case 'accountCanonicalForm':
  239. $permittedOptions[$key] = (int)$val;
  240. break;
  241. case 'useSsl':
  242. case 'bindRequiresDn':
  243. case 'allowEmptyPassword':
  244. case 'useStartTls':
  245. case 'optReferrals':
  246. case 'tryUsernameSplit':
  247. $permittedOptions[$key] = ($val === true ||
  248. $val === '1' || strcasecmp($val, 'true') == 0);
  249. break;
  250. default:
  251. $permittedOptions[$key] = trim($val);
  252. break;
  253. }
  254. }
  255. }
  256. if (count($options) > 0) {
  257. $key = key($options);
  258. /**
  259. * @see Zend_Ldap_Exception
  260. */
  261. require_once 'Zend/Ldap/Exception.php';
  262. throw new Zend_Ldap_Exception(null, "Unknown Zend_Ldap option: $key");
  263. }
  264. $this->_options = $permittedOptions;
  265. return $this;
  266. }
  267. /**
  268. * @return array The current options.
  269. */
  270. public function getOptions()
  271. {
  272. return $this->_options;
  273. }
  274. /**
  275. * @return string The hostname of the LDAP server being used to authenticate accounts
  276. */
  277. protected function _getHost()
  278. {
  279. return $this->_options['host'];
  280. }
  281. /**
  282. * @return int The port of the LDAP server or 0 to indicate that no port value is set
  283. */
  284. protected function _getPort()
  285. {
  286. return $this->_options['port'];
  287. }
  288. /**
  289. * @return boolean The default SSL / TLS encrypted transport control
  290. */
  291. protected function _getUseSsl()
  292. {
  293. return $this->_options['useSsl'];
  294. }
  295. /**
  296. * @return string The default acctname for binding
  297. */
  298. protected function _getUsername()
  299. {
  300. return $this->_options['username'];
  301. }
  302. /**
  303. * @return string The default password for binding
  304. */
  305. protected function _getPassword()
  306. {
  307. return $this->_options['password'];
  308. }
  309. /**
  310. * @return boolean Bind requires DN
  311. */
  312. protected function _getBindRequiresDn()
  313. {
  314. return $this->_options['bindRequiresDn'];
  315. }
  316. /**
  317. * Gets the base DN under which objects of interest are located
  318. *
  319. * @return string
  320. */
  321. public function getBaseDn()
  322. {
  323. return $this->_options['baseDn'];
  324. }
  325. /**
  326. * @return integer Either ACCTNAME_FORM_BACKSLASH, ACCTNAME_FORM_PRINCIPAL or
  327. * ACCTNAME_FORM_USERNAME indicating the form usernames should be canonicalized to.
  328. */
  329. protected function _getAccountCanonicalForm()
  330. {
  331. /* Account names should always be qualified with a domain. In some scenarios
  332. * using non-qualified account names can lead to security vulnerabilities. If
  333. * no account canonical form is specified, we guess based in what domain
  334. * names have been supplied.
  335. */
  336. $accountCanonicalForm = $this->_options['accountCanonicalForm'];
  337. if (!$accountCanonicalForm) {
  338. $accountDomainName = $this->_getAccountDomainName();
  339. $accountDomainNameShort = $this->_getAccountDomainNameShort();
  340. if ($accountDomainNameShort) {
  341. $accountCanonicalForm = Zend_Ldap::ACCTNAME_FORM_BACKSLASH;
  342. } else if ($accountDomainName) {
  343. $accountCanonicalForm = Zend_Ldap::ACCTNAME_FORM_PRINCIPAL;
  344. } else {
  345. $accountCanonicalForm = Zend_Ldap::ACCTNAME_FORM_USERNAME;
  346. }
  347. }
  348. return $accountCanonicalForm;
  349. }
  350. /**
  351. * @return string The account domain name
  352. */
  353. protected function _getAccountDomainName()
  354. {
  355. return $this->_options['accountDomainName'];
  356. }
  357. /**
  358. * @return string The short account domain name
  359. */
  360. protected function _getAccountDomainNameShort()
  361. {
  362. return $this->_options['accountDomainNameShort'];
  363. }
  364. /**
  365. * @return string A format string for building an LDAP search filter to match
  366. * an account
  367. */
  368. protected function _getAccountFilterFormat()
  369. {
  370. return $this->_options['accountFilterFormat'];
  371. }
  372. /**
  373. * @return boolean Allow empty passwords
  374. */
  375. protected function _getAllowEmptyPassword()
  376. {
  377. return $this->_options['allowEmptyPassword'];
  378. }
  379. /**
  380. * @return boolean The default SSL / TLS encrypted transport control
  381. */
  382. protected function _getUseStartTls()
  383. {
  384. return $this->_options['useStartTls'];
  385. }
  386. /**
  387. * @return boolean Opt. Referrals
  388. */
  389. protected function _getOptReferrals()
  390. {
  391. return $this->_options['optReferrals'];
  392. }
  393. /**
  394. * @return boolean Try splitting the username into username and domain
  395. */
  396. protected function _getTryUsernameSplit()
  397. {
  398. return $this->_options['tryUsernameSplit'];
  399. }
  400. /**
  401. * @return string The LDAP search filter for matching directory accounts
  402. */
  403. protected function _getAccountFilter($acctname)
  404. {
  405. /**
  406. * @see Zend_Ldap_Filter_Abstract
  407. */
  408. require_once 'Zend/Ldap/Filter/Abstract.php';
  409. $this->_splitName($acctname, $dname, $aname);
  410. $accountFilterFormat = $this->_getAccountFilterFormat();
  411. $aname = Zend_Ldap_Filter_Abstract::escapeValue($aname);
  412. if ($accountFilterFormat) {
  413. return sprintf($accountFilterFormat, $aname);
  414. }
  415. if (!$this->_getBindRequiresDn()) {
  416. // is there a better way to detect this?
  417. return sprintf("(&(objectClass=user)(sAMAccountName=%s))", $aname);
  418. }
  419. return sprintf("(&(objectClass=posixAccount)(uid=%s))", $aname);
  420. }
  421. /**
  422. * @param string $name The name to split
  423. * @param string $dname The resulting domain name (this is an out parameter)
  424. * @param string $aname The resulting account name (this is an out parameter)
  425. * @return void
  426. */
  427. protected function _splitName($name, &$dname, &$aname)
  428. {
  429. $dname = null;
  430. $aname = $name;
  431. if (!$this->_getTryUsernameSplit()) {
  432. return;
  433. }
  434. $pos = strpos($name, '@');
  435. if ($pos) {
  436. $dname = substr($name, $pos + 1);
  437. $aname = substr($name, 0, $pos);
  438. } else {
  439. $pos = strpos($name, '\\');
  440. if ($pos) {
  441. $dname = substr($name, 0, $pos);
  442. $aname = substr($name, $pos + 1);
  443. }
  444. }
  445. }
  446. /**
  447. * @param string $acctname The name of the account
  448. * @return string The DN of the specified account
  449. * @throws Zend_Ldap_Exception
  450. */
  451. protected function _getAccountDn($acctname)
  452. {
  453. /**
  454. * @see Zend_Ldap_Dn
  455. */
  456. require_once 'Zend/Ldap/Dn.php';
  457. if (Zend_Ldap_Dn::checkDn($acctname)) return $acctname;
  458. $acctname = $this->getCanonicalAccountName($acctname, Zend_Ldap::ACCTNAME_FORM_USERNAME);
  459. $acct = $this->_getAccount($acctname, array('dn'));
  460. return $acct['dn'];
  461. }
  462. /**
  463. * @param string $dname The domain name to check
  464. * @return boolean
  465. */
  466. protected function _isPossibleAuthority($dname)
  467. {
  468. if ($dname === null) {
  469. return true;
  470. }
  471. $accountDomainName = $this->_getAccountDomainName();
  472. $accountDomainNameShort = $this->_getAccountDomainNameShort();
  473. if ($accountDomainName === null && $accountDomainNameShort === null) {
  474. return true;
  475. }
  476. if (strcasecmp($dname, $accountDomainName) == 0) {
  477. return true;
  478. }
  479. if (strcasecmp($dname, $accountDomainNameShort) == 0) {
  480. return true;
  481. }
  482. return false;
  483. }
  484. /**
  485. * @param string $acctname The name to canonicalize
  486. * @param int $type The desired form of canonicalization
  487. * @return string The canonicalized name in the desired form
  488. * @throws Zend_Ldap_Exception
  489. */
  490. public function getCanonicalAccountName($acctname, $form = 0)
  491. {
  492. $this->_splitName($acctname, $dname, $uname);
  493. if (!$this->_isPossibleAuthority($dname)) {
  494. /**
  495. * @see Zend_Ldap_Exception
  496. */
  497. require_once 'Zend/Ldap/Exception.php';
  498. throw new Zend_Ldap_Exception(null,
  499. "Binding domain is not an authority for user: $acctname",
  500. Zend_Ldap_Exception::LDAP_X_DOMAIN_MISMATCH);
  501. }
  502. if (!$uname) {
  503. /**
  504. * @see Zend_Ldap_Exception
  505. */
  506. require_once 'Zend/Ldap/Exception.php';
  507. throw new Zend_Ldap_Exception(null, "Invalid account name syntax: $acctname");
  508. }
  509. if (function_exists('mb_strtolower')) {
  510. $uname = mb_strtolower($uname, 'UTF-8');
  511. } else {
  512. $uname = strtolower($uname);
  513. }
  514. if ($form === 0) {
  515. $form = $this->_getAccountCanonicalForm();
  516. }
  517. switch ($form) {
  518. case Zend_Ldap::ACCTNAME_FORM_DN:
  519. return $this->_getAccountDn($acctname);
  520. case Zend_Ldap::ACCTNAME_FORM_USERNAME:
  521. return $uname;
  522. case Zend_Ldap::ACCTNAME_FORM_BACKSLASH:
  523. $accountDomainNameShort = $this->_getAccountDomainNameShort();
  524. if (!$accountDomainNameShort) {
  525. /**
  526. * @see Zend_Ldap_Exception
  527. */
  528. require_once 'Zend/Ldap/Exception.php';
  529. throw new Zend_Ldap_Exception(null, 'Option required: accountDomainNameShort');
  530. }
  531. return "$accountDomainNameShort\\$uname";
  532. case Zend_Ldap::ACCTNAME_FORM_PRINCIPAL:
  533. $accountDomainName = $this->_getAccountDomainName();
  534. if (!$accountDomainName) {
  535. /**
  536. * @see Zend_Ldap_Exception
  537. */
  538. require_once 'Zend/Ldap/Exception.php';
  539. throw new Zend_Ldap_Exception(null, 'Option required: accountDomainName');
  540. }
  541. return "$uname@$accountDomainName";
  542. default:
  543. /**
  544. * @see Zend_Ldap_Exception
  545. */
  546. require_once 'Zend/Ldap/Exception.php';
  547. throw new Zend_Ldap_Exception(null, "Unknown canonical name form: $form");
  548. }
  549. }
  550. /**
  551. * @param array $attrs An array of names of desired attributes
  552. * @return array An array of the attributes representing the account
  553. * @throws Zend_Ldap_Exception
  554. */
  555. protected function _getAccount($acctname, array $attrs = null)
  556. {
  557. $baseDn = $this->getBaseDn();
  558. if (!$baseDn) {
  559. /**
  560. * @see Zend_Ldap_Exception
  561. */
  562. require_once 'Zend/Ldap/Exception.php';
  563. throw new Zend_Ldap_Exception(null, 'Base DN not set');
  564. }
  565. $accountFilter = $this->_getAccountFilter($acctname);
  566. if (!$accountFilter) {
  567. /**
  568. * @see Zend_Ldap_Exception
  569. */
  570. require_once 'Zend/Ldap/Exception.php';
  571. throw new Zend_Ldap_Exception(null, 'Invalid account filter');
  572. }
  573. if (!is_resource($this->getResource())) {
  574. $this->bind();
  575. }
  576. $accounts = $this->search($accountFilter, $baseDn, self::SEARCH_SCOPE_SUB, $attrs);
  577. $count = $accounts->count();
  578. if ($count === 1) {
  579. $acct = $accounts->getFirst();
  580. $accounts->close();
  581. return $acct;
  582. } else if ($count === 0) {
  583. /**
  584. * @see Zend_Ldap_Exception
  585. */
  586. require_once 'Zend/Ldap/Exception.php';
  587. $code = Zend_Ldap_Exception::LDAP_NO_SUCH_OBJECT;
  588. $str = "No object found for: $accountFilter";
  589. } else {
  590. /**
  591. * @see Zend_Ldap_Exception
  592. */
  593. require_once 'Zend/Ldap/Exception.php';
  594. $code = Zend_Ldap_Exception::LDAP_OPERATIONS_ERROR;
  595. $str = "Unexpected result count ($count) for: $accountFilter";
  596. }
  597. $accounts->close();
  598. /**
  599. * @see Zend_Ldap_Exception
  600. */
  601. require_once 'Zend/Ldap/Exception.php';
  602. throw new Zend_Ldap_Exception($this, $str, $code);
  603. }
  604. /**
  605. * @return Zend_Ldap Provides a fluent interface
  606. */
  607. public function disconnect()
  608. {
  609. if (is_resource($this->getResource())) {
  610. if (!extension_loaded('ldap')) {
  611. /**
  612. * @see Zend_Ldap_Exception
  613. */
  614. require_once 'Zend/Ldap/Exception.php';
  615. throw new Zend_Ldap_Exception(null, 'LDAP extension not loaded',
  616. Zend_Ldap_Exception::LDAP_X_EXTENSION_NOT_LOADED);
  617. }
  618. @ldap_unbind($this->getResource());
  619. }
  620. $this->_resource = null;
  621. return $this;
  622. }
  623. /**
  624. * @param string $host The hostname of the LDAP server to connect to
  625. * @param int $port The port number of the LDAP server to connect to
  626. * @param boolean $useSsl Use SSL
  627. * @param boolean $useStartTls Use STARTTLS
  628. * @return Zend_Ldap Provides a fluent interface
  629. * @throws Zend_Ldap_Exception
  630. */
  631. public function connect($host = null, $port = null, $useSsl = null, $useStartTls = null)
  632. {
  633. if ($host === null) {
  634. $host = $this->_getHost();
  635. }
  636. if ($port === null) {
  637. $port = $this->_getPort();
  638. } else {
  639. $port = (int)$port;
  640. }
  641. if ($useSsl === null) {
  642. $useSsl = $this->_getUseSsl();
  643. } else {
  644. $useSsl = (bool)$useSsl;
  645. }
  646. if ($useStartTls === null) {
  647. $useStartTls = $this->_getUseStartTls();
  648. } else {
  649. $useStartTls = (bool)$useStartTls;
  650. }
  651. if (!$host) {
  652. /**
  653. * @see Zend_Ldap_Exception
  654. */
  655. require_once 'Zend/Ldap/Exception.php';
  656. throw new Zend_Ldap_Exception(null, 'A host parameter is required');
  657. }
  658. /* To connect using SSL it seems the client tries to verify the server
  659. * certificate by default. One way to disable this behavior is to set
  660. * 'TLS_REQCERT never' in OpenLDAP's ldap.conf and restarting Apache. Or,
  661. * if you really care about the server's cert you can put a cert on the
  662. * web server.
  663. */
  664. $url = ($useSsl) ? "ldaps://$host" : "ldap://$host";
  665. if ($port) {
  666. $url .= ":$port";
  667. }
  668. /* Because ldap_connect doesn't really try to connect, any connect error
  669. * will actually occur during the ldap_bind call. Therefore, we save the
  670. * connect string here for reporting it in error handling in bind().
  671. */
  672. $this->_connectString = $url;
  673. $this->disconnect();
  674. if (!extension_loaded('ldap')) {
  675. /**
  676. * @see Zend_Ldap_Exception
  677. */
  678. require_once 'Zend/Ldap/Exception.php';
  679. throw new Zend_Ldap_Exception(null, 'LDAP extension not loaded',
  680. Zend_Ldap_Exception::LDAP_X_EXTENSION_NOT_LOADED);
  681. }
  682. /* Only OpenLDAP 2.2 + supports URLs so if SSL is not requested, just
  683. * use the old form.
  684. */
  685. $resource = ($useSsl) ? @ldap_connect($url) : @ldap_connect($host, $port);
  686. if (is_resource($resource) === true) {
  687. $this->_resource = $resource;
  688. $optReferrals = ($this->_getOptReferrals()) ? 1 : 0;
  689. if (@ldap_set_option($resource, LDAP_OPT_PROTOCOL_VERSION, 3) &&
  690. @ldap_set_option($resource, LDAP_OPT_REFERRALS, $optReferrals)) {
  691. if ($useSsl || !$useStartTls || @ldap_start_tls($resource)) {
  692. return $this;
  693. }
  694. }
  695. /**
  696. * @see Zend_Ldap_Exception
  697. */
  698. require_once 'Zend/Ldap/Exception.php';
  699. $zle = new Zend_Ldap_Exception($this, "$host:$port");
  700. $this->disconnect();
  701. throw $zle;
  702. }
  703. /**
  704. * @see Zend_Ldap_Exception
  705. */
  706. require_once 'Zend/Ldap/Exception.php';
  707. throw new Zend_Ldap_Exception(null, "Failed to connect to LDAP server: $host:$port");
  708. }
  709. /**
  710. * @param string $username The username for authenticating the bind
  711. * @param string $password The password for authenticating the bind
  712. * @return Zend_Ldap Provides a fluent interface
  713. * @throws Zend_Ldap_Exception
  714. */
  715. public function bind($username = null, $password = null)
  716. {
  717. $moreCreds = true;
  718. if ($username === null) {
  719. $username = $this->_getUsername();
  720. $password = $this->_getPassword();
  721. $moreCreds = false;
  722. }
  723. if ($username === null) {
  724. /* Perform anonymous bind
  725. */
  726. $password = null;
  727. } else {
  728. /* Check to make sure the username is in DN form.
  729. */
  730. /**
  731. * @see Zend_Ldap_Dn
  732. */
  733. require_once 'Zend/Ldap/Dn.php';
  734. if (!Zend_Ldap_Dn::checkDn($username)) {
  735. if ($this->_getBindRequiresDn()) {
  736. /* moreCreds stops an infinite loop if _getUsername does not
  737. * return a DN and the bind requires it
  738. */
  739. if ($moreCreds) {
  740. try {
  741. $username = $this->_getAccountDn($username);
  742. } catch (Zend_Ldap_Exception $zle) {
  743. switch ($zle->getCode()) {
  744. case Zend_Ldap_Exception::LDAP_NO_SUCH_OBJECT:
  745. case Zend_Ldap_Exception::LDAP_X_DOMAIN_MISMATCH:
  746. case Zend_Ldap_Exception::LDAP_X_EXTENSION_NOT_LOADED:
  747. throw $zle;
  748. }
  749. throw new Zend_Ldap_Exception(null,
  750. 'Failed to retrieve DN for account: ' . $username .
  751. ' [' . $zle->getMessage() . ']',
  752. Zend_Ldap_Exception::LDAP_OPERATIONS_ERROR);
  753. }
  754. } else {
  755. /**
  756. * @see Zend_Ldap_Exception
  757. */
  758. require_once 'Zend/Ldap/Exception.php';
  759. throw new Zend_Ldap_Exception(null, 'Binding requires username in DN form');
  760. }
  761. } else {
  762. $username = $this->getCanonicalAccountName($username,
  763. $this->_getAccountCanonicalForm());
  764. }
  765. }
  766. }
  767. if (!is_resource($this->getResource())) {
  768. $this->connect();
  769. }
  770. if ($username !== null && $password === '' && $this->_getAllowEmptyPassword() !== true) {
  771. /**
  772. * @see Zend_Ldap_Exception
  773. */
  774. require_once 'Zend/Ldap/Exception.php';
  775. $zle = new Zend_Ldap_Exception(null,
  776. 'Empty password not allowed - see allowEmptyPassword option.');
  777. } else {
  778. if (@ldap_bind($this->getResource(), $username, $password)) {
  779. return $this;
  780. }
  781. $message = ($username === null) ? $this->_connectString : $username;
  782. /**
  783. * @see Zend_Ldap_Exception
  784. */
  785. require_once 'Zend/Ldap/Exception.php';
  786. switch ($this->getLastErrorCode()) {
  787. case Zend_Ldap_Exception::LDAP_SERVER_DOWN:
  788. /* If the error is related to establishing a connection rather than binding,
  789. * the connect string is more informative than the username.
  790. */
  791. $message = $this->_connectString;
  792. }
  793. $zle = new Zend_Ldap_Exception($this, $message);
  794. }
  795. $this->disconnect();
  796. throw $zle;
  797. }
  798. /**
  799. * A global LDAP search routine for finding information.
  800. *
  801. * @param string|Zend_Ldap_Filter_Abstract $filter
  802. * @param string|Zend_Ldap_Dn|null $basedn
  803. * @param integer $scope
  804. * @param array $attributes
  805. * @param string|null $sort
  806. * @param string|null $collectionClass
  807. * @return Zend_Ldap_Collection
  808. * @throws Zend_Ldap_Exception
  809. */
  810. public function search($filter, $basedn = null, $scope = self::SEARCH_SCOPE_SUB,
  811. array $attributes = array(), $sort = null, $collectionClass = null)
  812. {
  813. if ($basedn === null) {
  814. $basedn = $this->getBaseDn();
  815. }
  816. else if ($basedn instanceof Zend_Ldap_Dn) {
  817. $basedn = $basedn->toString();
  818. }
  819. if ($filter instanceof Zend_Ldap_Filter_Abstract) {
  820. $filter = $filter->toString();
  821. }
  822. switch ($scope) {
  823. case self::SEARCH_SCOPE_ONE:
  824. $search = @ldap_list($this->getResource(), $basedn, $filter, $attributes);
  825. break;
  826. case self::SEARCH_SCOPE_BASE:
  827. $search = @ldap_read($this->getResource(), $basedn, $filter, $attributes);
  828. break;
  829. case self::SEARCH_SCOPE_SUB:
  830. default:
  831. $search = @ldap_search($this->getResource(), $basedn, $filter, $attributes);
  832. break;
  833. }
  834. if($search === false) {
  835. /**
  836. * @see Zend_Ldap_Exception
  837. */
  838. require_once 'Zend/Ldap/Exception.php';
  839. throw new Zend_Ldap_Exception($this, 'searching: ' . $filter);
  840. }
  841. if (!is_null($sort) && is_string($sort)) {
  842. $isSorted = @ldap_sort($this->getResource(), $search, $sort);
  843. if($search === false) {
  844. /**
  845. * @see Zend_Ldap_Exception
  846. */
  847. require_once 'Zend/Ldap/Exception.php';
  848. throw new Zend_Ldap_Exception($this, 'sorting: ' . $sort);
  849. }
  850. }
  851. /**
  852. * Zend_Ldap_Collection_Iterator_Default
  853. */
  854. require_once 'Zend/Ldap/Collection/Iterator/Default.php';
  855. $iterator = new Zend_Ldap_Collection_Iterator_Default($this, $search);
  856. if ($collectionClass === null) {
  857. /**
  858. * Zend_Ldap_Collection
  859. */
  860. require_once 'Zend/Ldap/Collection.php';
  861. return new Zend_Ldap_Collection($iterator);
  862. } else {
  863. $collectionClass = (string)$collectionClass;
  864. if (!class_exists($collectionClass)) {
  865. /**
  866. * @see Zend_Ldap_Exception
  867. */
  868. require_once 'Zend/Ldap/Exception.php';
  869. throw new Zend_Ldap_Exception(null,
  870. "Class '$collectionClass' can not be found");
  871. }
  872. if (!is_subclass_of($collectionClass, 'Zend_Ldap_Collection')) {
  873. /**
  874. * @see Zend_Ldap_Exception
  875. */
  876. require_once 'Zend/Ldap/Exception.php';
  877. throw new Zend_Ldap_Exception(null,
  878. "Class '$collectionClass' must subclass 'Zend_Ldap_Collection'");
  879. }
  880. return new $collectionClass($iterator);
  881. }
  882. }
  883. /**
  884. * Count items found by given filter.
  885. *
  886. * @param string|Zend_Ldap_Filter_Abstract $filter
  887. * @param string|Zend_Ldap_Dn|null $basedn
  888. * @param integer $scope
  889. * @return integer
  890. * @throws Zend_Ldap_Exception
  891. */
  892. public function count($filter, $basedn = null, $scope = self::SEARCH_SCOPE_SUB)
  893. {
  894. try {
  895. $result = $this->search($filter, $basedn, $scope, array('dn'), null);
  896. } catch (Zend_Ldap_Exception $e) {
  897. if ($e->getCode() === Zend_Ldap_Exception::LDAP_NO_SUCH_OBJECT) return 0;
  898. else throw $e;
  899. }
  900. return $result->count();
  901. }
  902. /**
  903. * Count children for a given DN.
  904. *
  905. * @param string|Zend_Ldap_Dn $dn
  906. * @return integer
  907. * @throws Zend_Ldap_Exception
  908. */
  909. public function countChildren($dn)
  910. {
  911. return $this->count('(objectClass=*)', $dn, self::SEARCH_SCOPE_ONE);
  912. }
  913. /**
  914. * Check if a given DN exists.
  915. *
  916. * @param string|Zend_Ldap_Dn $dn
  917. * @return boolean
  918. * @throws Zend_Ldap_Exception
  919. */
  920. public function exists($dn)
  921. {
  922. return ($this->count('(objectClass=*)', $dn, self::SEARCH_SCOPE_BASE) == 1);
  923. }
  924. /**
  925. * Search LDAP registry for entries matching filter and optional attributes
  926. *
  927. * @param string|Zend_Ldap_Filter_Abstract $filter
  928. * @param string|Zend_Ldap_Dn|null $basedn
  929. * @param integer $scope
  930. * @param array $attributes
  931. * @param string|null $sort
  932. * @return array
  933. * @throws Zend_Ldap_Exception
  934. */
  935. public function searchEntries($filter, $basedn = null, $scope = self::SEARCH_SCOPE_SUB,
  936. array $attributes = array(), $sort = null)
  937. {
  938. $result = $this->search($filter, $basedn, $scope, $attributes, $sort);
  939. return $result->toArray();
  940. }
  941. /**
  942. * Get LDAP entry by DN
  943. *
  944. * @param string|Zend_Ldap_Dn $dn
  945. * @param array $attributes
  946. * @param boolean $throwOnNotFound
  947. * @return array
  948. * @throws Zend_Ldap_Exception
  949. */
  950. public function getEntry($dn, array $attributes = array(), $throwOnNotFound = false)
  951. {
  952. try {
  953. $result = $this->search("(objectClass=*)", $dn, self::SEARCH_SCOPE_BASE,
  954. $attributes, null);
  955. return $result->getFirst();
  956. } catch (Zend_Ldap_Exception $e){
  957. if ($throwOnNotFound !== false) throw $e;
  958. }
  959. return null;
  960. }
  961. /**
  962. * Prepares an ldap data entry array for insert/update operation
  963. *
  964. * @param array $entry
  965. * @return void
  966. * @throws InvalidArgumentException
  967. */
  968. public static function prepareLdapEntryArray(array &$entry)
  969. {
  970. if (array_key_exists('dn', $entry)) unset($entry['dn']);
  971. foreach ($entry as $key => $value) {
  972. if (is_array($value)) {
  973. foreach ($value as $i => $v) {
  974. if (is_null($v)) unset($value[$i]);
  975. else if (!is_scalar($v)) {
  976. throw new InvalidArgumentException('Only scalar values allowed in LDAP data');
  977. } else {
  978. $v = (string)$v;
  979. if (strlen($v) == 0) {
  980. unset($value[$i]);
  981. } else {
  982. $value[$i] = $v;
  983. }
  984. }
  985. }
  986. $entry[$key] = array_values($value);
  987. } else {
  988. if (is_null($value)) $entry[$key] = array();
  989. else if (!is_scalar($value)) {
  990. throw new InvalidArgumentException('Only scalar values allowed in LDAP data');
  991. } else {
  992. $value = (string)$value;
  993. if (strlen($value) == 0) {
  994. $entry[$key] = array();
  995. } else {
  996. $entry[$key] = array($value);
  997. }
  998. }
  999. }
  1000. }
  1001. $entry = array_change_key_case($entry, CASE_LOWER);
  1002. }
  1003. /**
  1004. * Add new information to the LDAP repository
  1005. *
  1006. * @param string|Zend_Ldap_Dn $dn
  1007. * @param array $entry
  1008. * @return Zend_Ldap Provides a fluid interface
  1009. * @throws Zend_Ldap_Exception
  1010. */
  1011. public function add($dn, array $entry)
  1012. {
  1013. if (!($dn instanceof Zend_Ldap_Dn)) {
  1014. $dn = Zend_Ldap_Dn::factory($dn, null);
  1015. }
  1016. self::prepareLdapEntryArray($entry);
  1017. foreach ($entry as $key => $value) {
  1018. if (is_array($value) && count($value) === 0) {
  1019. unset($entry[$key]);
  1020. }
  1021. }
  1022. $rdnParts = $dn->getRdn(Zend_Ldap_Dn::ATTR_CASEFOLD_LOWER);
  1023. foreach ($rdnParts as $key => $value) {
  1024. $value = Zend_Ldap_Dn::unescapeValue($value);
  1025. if (!array_key_exists($key, $entry) ||
  1026. !in_array($value, $entry[$key]) ||
  1027. count($entry[$key]) !== 1) {
  1028. $entry[$key] = array($value);
  1029. }
  1030. }
  1031. $adAttributes = array('distinguishedname', 'instancetype', 'name', 'objectcategory',
  1032. 'objectguid', 'usnchanged', 'usncreated', 'whenchanged', 'whencreated');
  1033. foreach ($adAttributes as $attr) {
  1034. if (array_key_exists($attr, $entry)) {
  1035. unset($entry[$attr]);
  1036. }
  1037. }
  1038. $isAdded = @ldap_add($this->getResource(), $dn->toString(), $entry);
  1039. if($isAdded === false) {
  1040. /**
  1041. * @see Zend_Ldap_Exception
  1042. */
  1043. require_once 'Zend/Ldap/Exception.php';
  1044. throw new Zend_Ldap_Exception($this, 'adding: ' . $dn->toString());
  1045. }
  1046. return $this;
  1047. }
  1048. /**
  1049. * Update LDAP registry
  1050. *
  1051. * @param string|Zend_Ldap_Dn $dn
  1052. * @param array $entry
  1053. * @return Zend_Ldap Provides a fluid interface
  1054. * @throws Zend_Ldap_Exception
  1055. */
  1056. public function update($dn, array $entry)
  1057. {
  1058. if (!($dn instanceof Zend_Ldap_Dn)) {
  1059. $dn = Zend_Ldap_Dn::factory($dn, null);
  1060. }
  1061. self::prepareLdapEntryArray($entry);
  1062. $rdnParts = $dn->getRdn(Zend_Ldap_Dn::ATTR_CASEFOLD_LOWER);
  1063. $adAttributes = array('distinguishedname', 'instancetype', 'name', 'objectcategory',
  1064. 'objectguid', 'usnchanged', 'usncreated', 'whenchanged', 'whencreated');
  1065. $stripAttributes = array_merge(array_keys($rdnParts), $adAttributes);
  1066. foreach ($stripAttributes as $attr) {
  1067. if (array_key_exists($attr, $entry)) {
  1068. unset($entry[$attr]);
  1069. }
  1070. }
  1071. if (count($entry) > 0) {
  1072. $isModified = @ldap_modify($this->getResource(), $dn->toString(), $entry);
  1073. if($isModified === false) {
  1074. /**
  1075. * @see Zend_Ldap_Exception
  1076. */
  1077. require_once 'Zend/Ldap/Exception.php';
  1078. throw new Zend_Ldap_Exception($this, 'updating: ' . $dn->toString());
  1079. }
  1080. }
  1081. return $this;
  1082. }
  1083. /**
  1084. * Save entry to LDAP registry.
  1085. *
  1086. * Internally decides if entry will be updated to added by calling
  1087. * {@link exists()}.
  1088. *
  1089. * @param string|Zend_Ldap_Dn $dn
  1090. * @param array $entry
  1091. * @return Zend_Ldap Provides a fluid interface
  1092. * @throws Zend_Ldap_Exception
  1093. */
  1094. public function save($dn, array $entry)
  1095. {
  1096. if ($dn instanceof Zend_Ldap_Dn) {
  1097. $dn = $dn->toString();
  1098. }
  1099. if ($this->exists($dn)) $this->update($dn, $entry);
  1100. else $this->add($dn, $entry);
  1101. return $this;
  1102. }
  1103. /**
  1104. * Delete an LDAP entry
  1105. *
  1106. * @param string|Zend_Ldap_Dn $dn
  1107. * @param boolean $recursively
  1108. * @return Zend_Ldap Provides a fluid interface
  1109. * @throws Zend_Ldap_Exception
  1110. */
  1111. public function delete($dn, $recursively = false)
  1112. {
  1113. if ($dn instanceof Zend_Ldap_Dn) {
  1114. $dn = $dn->toString();
  1115. }
  1116. if ($recursively === true) {
  1117. if ($this->countChildren($dn)>0) {
  1118. $children = $this->_getChildrenDns($dn);
  1119. foreach ($children as $c) {
  1120. $this->delete($c, true);
  1121. }
  1122. }
  1123. }
  1124. $isDeleted = @ldap_delete($this->getResource(), $dn);
  1125. if($isDeleted === false) {
  1126. /**
  1127. * @see Zend_Ldap_Exception
  1128. */
  1129. require_once 'Zend/Ldap/Exception.php';
  1130. throw new Zend_Ldap_Exception($this, 'deleting: ' . $dn);
  1131. }
  1132. return $this;
  1133. }
  1134. /**
  1135. * Retrieve the immediate children DNs of the given $parentDn
  1136. *
  1137. * This method is used in recursive methods like {@see delete()}
  1138. * or {@see copy()}
  1139. *
  1140. * @param string|Zend_Ldap_Dn $parentDn
  1141. * @return array of DNs
  1142. */
  1143. protected function _getChildrenDns($parentDn)
  1144. {
  1145. if ($parentDn instanceof Zend_Ldap_Dn) {
  1146. $parentDn = $parentDn->toString();
  1147. }
  1148. $children = array();
  1149. $search = @ldap_list($this->getResource(), $parentDn, '(objectClass=*)', array('dn'));
  1150. for ($entry = @ldap_first_entry($this->getResource(), $search);
  1151. $entry !== false;
  1152. $entry = @ldap_next_entry($this->getResource(), $entry)) {
  1153. $childDn = @ldap_get_dn($this->getResource(), $entry);
  1154. if ($childDn === false) {
  1155. /**
  1156. * @see Zend_Ldap_Exception
  1157. */
  1158. require_once 'Zend/Ldap/Exception.php';
  1159. throw new Zend_Ldap_Exception($this, 'getting dn');
  1160. }
  1161. $children[] = $childDn;
  1162. }
  1163. @ldap_free_result($search);
  1164. return $children;
  1165. }
  1166. /**
  1167. * Moves a LDAP entry from one DN to another subtree.
  1168. *
  1169. * @param string|Zend_Ldap_Dn $from
  1170. * @param string|Zend_Ldap_Dn $to
  1171. * @param boolean $recursively
  1172. * @param boolean $alwaysEmulate
  1173. * @return Zend_Ldap Provides a fluid interface
  1174. * @throws Zend_Ldap_Exception
  1175. */
  1176. public function moveToSubtree($from, $to, $recursively = false, $alwaysEmulate = false)
  1177. {
  1178. if ($from instanceof Zend_Ldap_Dn) {
  1179. $orgDnParts = $from->toArray();
  1180. } else {
  1181. $orgDnParts = Zend_Ldap_Dn::explodeDn($from);
  1182. }
  1183. if ($to instanceof Zend_Ldap_Dn) {
  1184. $newParentDnParts = $to->toArray();
  1185. } else {
  1186. $newParentDnParts = Zend_Ldap_Dn::explodeDn($to);
  1187. }
  1188. $newDnParts = array_merge(array(array_shift($orgDnParts)), $newParentDnParts);
  1189. $newDn = Zend_Ldap_Dn::fromArray($newDnParts);
  1190. return $this->rename($from, $newDn, $recursively, $alwaysEmulate);
  1191. }
  1192. /**
  1193. * Moves a LDAP entry from one DN to another DN.
  1194. *
  1195. * This is an alias for {@link rename()}
  1196. *
  1197. * @param string|Zend_Ldap_Dn $from
  1198. * @param string|Zend_Ldap_Dn $to
  1199. * @param boolean $recursively
  1200. * @param boolean $alwaysEmulate
  1201. * @return Zend_Ldap Provides a fluid interface
  1202. * @throws Zend_Ldap_Exception
  1203. */
  1204. public function move($from, $to, $recursively = false, $alwaysEmulate = false)
  1205. {
  1206. return $this->rename($from, $to, $recursively, $alwaysEmulate);
  1207. }
  1208. /**
  1209. * Renames a LDAP entry from one DN to another DN.
  1210. *
  1211. * This method implicitely moves the entry to another location within the tree.
  1212. *
  1213. * @param string|Zend_Ldap_Dn $from
  1214. * @param string|Zend_Ldap_Dn $to
  1215. * @param boolean $recursively
  1216. * @param boolean $alwaysEmulate
  1217. * @return Zend_Ldap Provides a fluid interface
  1218. * @throws Zend_Ldap_Exception
  1219. */
  1220. public function rename($from, $to, $recursively = false, $alwaysEmulate = false)
  1221. {
  1222. $emulate = (bool)$alwaysEmulate;
  1223. if (!function_exists('ldap_rename')) $emulate = true;
  1224. else if ($recursively) $emulate = true;
  1225. if ($emulate === false) {
  1226. if ($from instanceof Zend_Ldap_Dn) {
  1227. $from = $from->toString();
  1228. }
  1229. if ($to instanceof Zend_Ldap_Dn) {
  1230. $newDnParts = $to->toArray();
  1231. } else {
  1232. $newDnParts = Zend_Ldap_Dn::explodeDn($to);
  1233. }
  1234. $newRdn = Zend_Ldap_Dn::implodeRdn(array_shift($newDnParts));
  1235. $newParent = Zend_Ldap_Dn::implodeDn($newDnParts);
  1236. $isOK = @ldap_rename($this->getResource(), $from, $newRdn, $newParent, true);
  1237. if($isOK === false) {
  1238. /**
  1239. * @see Zend_Ldap_Exception
  1240. */
  1241. require_once 'Zend/Ldap/Exception.php';
  1242. throw new Zend_Ldap_Exception($this, 'renaming ' . $from . ' to ' . $to);
  1243. }
  1244. else if (!$this->exists($to)) $emulate = true;
  1245. }
  1246. if ($emulate) {
  1247. $this->copy($from, $to, $recursively);
  1248. $this->delete($from, $recursively);
  1249. }
  1250. return $this;
  1251. }
  1252. /**
  1253. * Copies a LDAP entry from one DN to another subtree.
  1254. *
  1255. * @param string|Zend_Ldap_Dn $from
  1256. * @param string|Zend_Ldap_Dn $to
  1257. * @param boolean $recursively
  1258. * @return Zend_Ldap Provides a fluid interface
  1259. * @throws Zend_Ldap_Exception
  1260. */
  1261. public function copyToSubtree($from, $to, $recursively = false)
  1262. {
  1263. if ($from instanceof Zend_Ldap_Dn) {
  1264. $orgDnParts = $from->toArray();
  1265. } else {
  1266. $orgDnParts = Zend_Ldap_Dn::explodeDn($from);
  1267. }
  1268. if ($to instanceof Zend_Ldap_Dn) {
  1269. $newParentDnParts = $to->toArray();
  1270. } else {
  1271. $newParentDnParts = Zend_Ldap_Dn::explodeDn($to);
  1272. }
  1273. $newDnParts = array_merge(array(array_shift($orgDnParts)), $newParentDnParts);
  1274. $newDn = Zend_Ldap_Dn::fromArray($newDnParts);
  1275. return $this->copy($from, $newDn, $recursively);
  1276. }
  1277. /**
  1278. * Copies a LDAP entry from one DN to another DN.
  1279. *
  1280. * @param string|Zend_Ldap_Dn $from
  1281. * @param string|Zend_Ldap_Dn $to
  1282. * @param boolean $recursively
  1283. * @return Zend_Ldap Provides a fluid interface
  1284. * @throws Zend_Ldap_Exception
  1285. */
  1286. public function copy($from, $to, $recursively = false)
  1287. {
  1288. $entry = $this->getEntry($from, array(), true);
  1289. if ($to instanceof Zend_Ldap_Dn) {
  1290. $toDnParts = $to->toArray();
  1291. } else {
  1292. $toDnParts = Zend_Ldap_Dn::explodeDn($to);
  1293. }
  1294. $this->add($to, $entry);
  1295. if ($recursively === true && $this->countChildren($from)>0) {
  1296. $children = $this->_getChildrenDns($from);
  1297. foreach ($children as $c) {
  1298. $cDnParts = Zend_Ldap_Dn::explodeDn($c);
  1299. $newChildParts = array_merge(array(array_shift($cDnParts)), $toDnParts);
  1300. $newChild = Zend_Ldap_Dn::implodeDn($newChildParts);
  1301. $this->copy($c, $newChild, true);
  1302. }
  1303. }
  1304. return $this;
  1305. }
  1306. /**
  1307. * Returns the specified DN as a Zend_Ldap_Node
  1308. *
  1309. * @param string|Zend_Ldap_Dn $dn
  1310. * @return Zend_Ldap_Node
  1311. * @throws Zend_Ldap_Exception
  1312. */
  1313. public function getNode($dn)
  1314. {
  1315. /**
  1316. * Zend_Ldap_Node
  1317. */
  1318. require_once 'Zend/Ldap/Node.php';
  1319. return Zend_Ldap_Node::fromLdap($dn, $this);
  1320. }
  1321. /**
  1322. * Returns the base node as a Zend_Ldap_Node
  1323. *
  1324. * @return Zend_Ldap_Node
  1325. * @throws Zend_Ldap_Exception
  1326. */
  1327. public function getBaseNode()
  1328. {
  1329. return $this->getNode($this->getBaseDn(), $this);
  1330. }
  1331. /**
  1332. * Returns the RootDSE
  1333. *
  1334. * @return Zend_Ldap_Node_RootDse
  1335. * @throws Zend_Ldap_Exception
  1336. */
  1337. public function getRootDse()
  1338. {
  1339. if ($this->_rootDse === null) {
  1340. /**
  1341. * @see Zend_Ldap_Node_Schema
  1342. */
  1343. require_once 'Zend/Ldap/Node/RootDse.php';
  1344. $this->_rootDse = Zend_Ldap_Node_RootDse::create($this);
  1345. }
  1346. return $this->_rootDse;
  1347. }
  1348. /**
  1349. * Returns the schema
  1350. *
  1351. * @return Zend_Ldap_Node_Schema
  1352. * @throws Zend_Ldap_Exception
  1353. */
  1354. public function getSchema()
  1355. {
  1356. if ($this->_schema === null) {
  1357. /**
  1358. * @see Zend_Ldap_Node_Schema
  1359. */
  1360. require_once 'Zend/Ldap/Node/Schema.php';
  1361. $this->_schema = Zend_Ldap_Node_Schema::create($this);
  1362. }
  1363. return $this->_schema;
  1364. }
  1365. }