2
0

Ldap.php 47 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462
  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 (empty($v)) unset($value[$i]);
  976. else if (!is_scalar($v)) {
  977. throw new InvalidArgumentException('Only scalar values allowed in LDAP data');
  978. }
  979. }
  980. $entry[$key] = array_values($value);
  981. } else {
  982. if (is_null($value)) $entry[$key] = array();
  983. else if (empty($value)) $entry[$key] = array();
  984. else $entry[$key] = array($value);
  985. }
  986. }
  987. $entry = array_change_key_case($entry, CASE_LOWER);
  988. }
  989. /**
  990. * Add new information to the LDAP repository
  991. *
  992. * @param string|Zend_Ldap_Dn $dn
  993. * @param array $entry
  994. * @return Zend_Ldap Provides a fluid interface
  995. * @throws Zend_Ldap_Exception
  996. */
  997. public function add($dn, array $entry)
  998. {
  999. if (!($dn instanceof Zend_Ldap_Dn)) {
  1000. $dn = Zend_Ldap_Dn::factory($dn, null);
  1001. }
  1002. self::prepareLdapEntryArray($entry);
  1003. foreach ($entry as $key => $value) {
  1004. if (is_array($value) && count($value) === 0) {
  1005. unset($entry[$key]);
  1006. }
  1007. }
  1008. $rdnParts = $dn->getRdn(Zend_Ldap_Dn::ATTR_CASEFOLD_LOWER);
  1009. foreach ($rdnParts as $key => $value) {
  1010. $value = Zend_Ldap_Dn::unescapeValue($value);
  1011. if (!array_key_exists($key, $entry) ||
  1012. !in_array($value, $entry[$key]) ||
  1013. count($entry[$key]) !== 1) {
  1014. $entry[$key] = array($value);
  1015. }
  1016. }
  1017. $adAttributes = array('distinguishedname', 'instancetype', 'name', 'objectcategory',
  1018. 'objectguid', 'usnchanged', 'usncreated', 'whenchanged', 'whencreated');
  1019. foreach ($adAttributes as $attr) {
  1020. if (array_key_exists($attr, $entry)) {
  1021. unset($entry[$attr]);
  1022. }
  1023. }
  1024. $isAdded = @ldap_add($this->getResource(), $dn->toString(), $entry);
  1025. if($isAdded === false) {
  1026. /**
  1027. * @see Zend_Ldap_Exception
  1028. */
  1029. require_once 'Zend/Ldap/Exception.php';
  1030. throw new Zend_Ldap_Exception($this, 'adding: ' . $dn->toString());
  1031. }
  1032. return $this;
  1033. }
  1034. /**
  1035. * Update LDAP registry
  1036. *
  1037. * @param string|Zend_Ldap_Dn $dn
  1038. * @param array $entry
  1039. * @return Zend_Ldap Provides a fluid interface
  1040. * @throws Zend_Ldap_Exception
  1041. */
  1042. public function update($dn, array $entry)
  1043. {
  1044. if (!($dn instanceof Zend_Ldap_Dn)) {
  1045. $dn = Zend_Ldap_Dn::factory($dn, null);
  1046. }
  1047. self::prepareLdapEntryArray($entry);
  1048. $rdnParts = $dn->getRdn(Zend_Ldap_Dn::ATTR_CASEFOLD_LOWER);
  1049. $adAttributes = array('distinguishedname', 'instancetype', 'name', 'objectcategory',
  1050. 'objectguid', 'usnchanged', 'usncreated', 'whenchanged', 'whencreated');
  1051. $stripAttributes = array_merge(array_keys($rdnParts), $adAttributes);
  1052. foreach ($stripAttributes as $attr) {
  1053. if (array_key_exists($attr, $entry)) {
  1054. unset($entry[$attr]);
  1055. }
  1056. }
  1057. if (count($entry) > 0) {
  1058. $isModified = @ldap_modify($this->getResource(), $dn->toString(), $entry);
  1059. if($isModified === false) {
  1060. /**
  1061. * @see Zend_Ldap_Exception
  1062. */
  1063. require_once 'Zend/Ldap/Exception.php';
  1064. throw new Zend_Ldap_Exception($this, 'updating: ' . $dn->toString());
  1065. }
  1066. }
  1067. return $this;
  1068. }
  1069. /**
  1070. * Save entry to LDAP registry.
  1071. *
  1072. * Internally decides if entry will be updated to added by calling
  1073. * {@link exists()}.
  1074. *
  1075. * @param string|Zend_Ldap_Dn $dn
  1076. * @param array $entry
  1077. * @return Zend_Ldap Provides a fluid interface
  1078. * @throws Zend_Ldap_Exception
  1079. */
  1080. public function save($dn, array $entry)
  1081. {
  1082. if ($dn instanceof Zend_Ldap_Dn) {
  1083. $dn = $dn->toString();
  1084. }
  1085. if ($this->exists($dn)) $this->update($dn, $entry);
  1086. else $this->add($dn, $entry);
  1087. return $this;
  1088. }
  1089. /**
  1090. * Delete an LDAP entry
  1091. *
  1092. * @param string|Zend_Ldap_Dn $dn
  1093. * @param boolean $recursively
  1094. * @return Zend_Ldap Provides a fluid interface
  1095. * @throws Zend_Ldap_Exception
  1096. */
  1097. public function delete($dn, $recursively = false)
  1098. {
  1099. if ($dn instanceof Zend_Ldap_Dn) {
  1100. $dn = $dn->toString();
  1101. }
  1102. if ($recursively === true) {
  1103. if ($this->countChildren($dn)>0) {
  1104. $children = $this->_getChildrenDns($dn);
  1105. foreach ($children as $c) {
  1106. $this->delete($c, true);
  1107. }
  1108. }
  1109. }
  1110. $isDeleted = @ldap_delete($this->getResource(), $dn);
  1111. if($isDeleted === false) {
  1112. /**
  1113. * @see Zend_Ldap_Exception
  1114. */
  1115. require_once 'Zend/Ldap/Exception.php';
  1116. throw new Zend_Ldap_Exception($this, 'deleting: ' . $dn);
  1117. }
  1118. return $this;
  1119. }
  1120. /**
  1121. * Retrieve the immediate children DNs of the given $parentDn
  1122. *
  1123. * This method is used in recursive methods like {@see delete()}
  1124. * or {@see copy()}
  1125. *
  1126. * @param string|Zend_Ldap_Dn $parentDn
  1127. * @return array of DNs
  1128. */
  1129. protected function _getChildrenDns($parentDn)
  1130. {
  1131. if ($parentDn instanceof Zend_Ldap_Dn) {
  1132. $parentDn = $parentDn->toString();
  1133. }
  1134. $children = array();
  1135. $search = @ldap_list($this->getResource(), $parentDn, '(objectClass=*)', array('dn'));
  1136. for ($entry = @ldap_first_entry($this->getResource(), $search);
  1137. $entry !== false;
  1138. $entry = @ldap_next_entry($this->getResource(), $entry)) {
  1139. $childDn = @ldap_get_dn($this->getResource(), $entry);
  1140. if ($childDn === false) {
  1141. /**
  1142. * @see Zend_Ldap_Exception
  1143. */
  1144. require_once 'Zend/Ldap/Exception.php';
  1145. throw new Zend_Ldap_Exception($this, 'getting dn');
  1146. }
  1147. $children[] = $childDn;
  1148. }
  1149. @ldap_free_result($search);
  1150. return $children;
  1151. }
  1152. /**
  1153. * Moves a LDAP entry from one DN to another subtree.
  1154. *
  1155. * @param string|Zend_Ldap_Dn $from
  1156. * @param string|Zend_Ldap_Dn $to
  1157. * @param boolean $recursively
  1158. * @param boolean $alwaysEmulate
  1159. * @return Zend_Ldap Provides a fluid interface
  1160. * @throws Zend_Ldap_Exception
  1161. */
  1162. public function moveToSubtree($from, $to, $recursively = false, $alwaysEmulate = false)
  1163. {
  1164. if ($from instanceof Zend_Ldap_Dn) {
  1165. $orgDnParts = $from->toArray();
  1166. } else {
  1167. $orgDnParts = Zend_Ldap_Dn::explodeDn($from);
  1168. }
  1169. if ($to instanceof Zend_Ldap_Dn) {
  1170. $newParentDnParts = $to->toArray();
  1171. } else {
  1172. $newParentDnParts = Zend_Ldap_Dn::explodeDn($to);
  1173. }
  1174. $newDnParts = array_merge(array(array_shift($orgDnParts)), $newParentDnParts);
  1175. $newDn = Zend_Ldap_Dn::fromArray($newDnParts);
  1176. return $this->rename($from, $newDn, $recursively, $alwaysEmulate);
  1177. }
  1178. /**
  1179. * Moves a LDAP entry from one DN to another DN.
  1180. *
  1181. * This is an alias for {@link rename()}
  1182. *
  1183. * @param string|Zend_Ldap_Dn $from
  1184. * @param string|Zend_Ldap_Dn $to
  1185. * @param boolean $recursively
  1186. * @param boolean $alwaysEmulate
  1187. * @return Zend_Ldap Provides a fluid interface
  1188. * @throws Zend_Ldap_Exception
  1189. */
  1190. public function move($from, $to, $recursively = false, $alwaysEmulate = false)
  1191. {
  1192. return $this->rename($from, $to, $recursively, $alwaysEmulate);
  1193. }
  1194. /**
  1195. * Renames a LDAP entry from one DN to another DN.
  1196. *
  1197. * This method implicitely moves the entry to another location within the tree.
  1198. *
  1199. * @param string|Zend_Ldap_Dn $from
  1200. * @param string|Zend_Ldap_Dn $to
  1201. * @param boolean $recursively
  1202. * @param boolean $alwaysEmulate
  1203. * @return Zend_Ldap Provides a fluid interface
  1204. * @throws Zend_Ldap_Exception
  1205. */
  1206. public function rename($from, $to, $recursively = false, $alwaysEmulate = false)
  1207. {
  1208. $emulate = (bool)$alwaysEmulate;
  1209. if (!function_exists('ldap_rename')) $emulate = true;
  1210. else if ($recursively) $emulate = true;
  1211. if ($emulate === false) {
  1212. if ($from instanceof Zend_Ldap_Dn) {
  1213. $from = $from->toString();
  1214. }
  1215. if ($to instanceof Zend_Ldap_Dn) {
  1216. $newDnParts = $to->toArray();
  1217. } else {
  1218. $newDnParts = Zend_Ldap_Dn::explodeDn($to);
  1219. }
  1220. $newRdn = Zend_Ldap_Dn::implodeRdn(array_shift($newDnParts));
  1221. $newParent = Zend_Ldap_Dn::implodeDn($newDnParts);
  1222. $isOK = @ldap_rename($this->getResource(), $from, $newRdn, $newParent, true);
  1223. if($isOK === false) {
  1224. /**
  1225. * @see Zend_Ldap_Exception
  1226. */
  1227. require_once 'Zend/Ldap/Exception.php';
  1228. throw new Zend_Ldap_Exception($this, 'renaming ' . $from . ' to ' . $to);
  1229. }
  1230. else if (!$this->exists($to)) $emulate = true;
  1231. }
  1232. if ($emulate) {
  1233. $this->copy($from, $to, $recursively);
  1234. $this->delete($from, $recursively);
  1235. }
  1236. return $this;
  1237. }
  1238. /**
  1239. * Copies a LDAP entry from one DN to another subtree.
  1240. *
  1241. * @param string|Zend_Ldap_Dn $from
  1242. * @param string|Zend_Ldap_Dn $to
  1243. * @param boolean $recursively
  1244. * @return Zend_Ldap Provides a fluid interface
  1245. * @throws Zend_Ldap_Exception
  1246. */
  1247. public function copyToSubtree($from, $to, $recursively = false)
  1248. {
  1249. if ($from instanceof Zend_Ldap_Dn) {
  1250. $orgDnParts = $from->toArray();
  1251. } else {
  1252. $orgDnParts = Zend_Ldap_Dn::explodeDn($from);
  1253. }
  1254. if ($to instanceof Zend_Ldap_Dn) {
  1255. $newParentDnParts = $to->toArray();
  1256. } else {
  1257. $newParentDnParts = Zend_Ldap_Dn::explodeDn($to);
  1258. }
  1259. $newDnParts = array_merge(array(array_shift($orgDnParts)), $newParentDnParts);
  1260. $newDn = Zend_Ldap_Dn::fromArray($newDnParts);
  1261. return $this->copy($from, $newDn, $recursively);
  1262. }
  1263. /**
  1264. * Copies a LDAP entry from one DN to another DN.
  1265. *
  1266. * @param string|Zend_Ldap_Dn $from
  1267. * @param string|Zend_Ldap_Dn $to
  1268. * @param boolean $recursively
  1269. * @return Zend_Ldap Provides a fluid interface
  1270. * @throws Zend_Ldap_Exception
  1271. */
  1272. public function copy($from, $to, $recursively = false)
  1273. {
  1274. $entry = $this->getEntry($from, array(), true);
  1275. if ($to instanceof Zend_Ldap_Dn) {
  1276. $toDnParts = $to->toArray();
  1277. } else {
  1278. $toDnParts = Zend_Ldap_Dn::explodeDn($to);
  1279. }
  1280. $this->add($to, $entry);
  1281. if ($recursively === true && $this->countChildren($from)>0) {
  1282. $children = $this->_getChildrenDns($from);
  1283. foreach ($children as $c) {
  1284. $cDnParts = Zend_Ldap_Dn::explodeDn($c);
  1285. $newChildParts = array_merge(array(array_shift($cDnParts)), $toDnParts);
  1286. $newChild = Zend_Ldap_Dn::implodeDn($newChildParts);
  1287. $this->copy($c, $newChild, true);
  1288. }
  1289. }
  1290. return $this;
  1291. }
  1292. /**
  1293. * Returns the specified DN as a Zend_Ldap_Node
  1294. *
  1295. * @param string|Zend_Ldap_Dn $dn
  1296. * @return Zend_Ldap_Node
  1297. * @throws Zend_Ldap_Exception
  1298. */
  1299. public function getNode($dn)
  1300. {
  1301. /**
  1302. * Zend_Ldap_Node
  1303. */
  1304. require_once 'Zend/Ldap/Node.php';
  1305. return Zend_Ldap_Node::fromLdap($dn, $this);
  1306. }
  1307. /**
  1308. * Returns the base node as a Zend_Ldap_Node
  1309. *
  1310. * @return Zend_Ldap_Node
  1311. * @throws Zend_Ldap_Exception
  1312. */
  1313. public function getBaseNode()
  1314. {
  1315. return $this->getNode($this->getBaseDn(), $this);
  1316. }
  1317. /**
  1318. * Returns the RootDSE
  1319. *
  1320. * @return Zend_Ldap_Node_RootDse
  1321. * @throws Zend_Ldap_Exception
  1322. */
  1323. public function getRootDse()
  1324. {
  1325. if ($this->_rootDse === null) {
  1326. /**
  1327. * @see Zend_Ldap_Node_Schema
  1328. */
  1329. require_once 'Zend/Ldap/Node/RootDse.php';
  1330. $this->_rootDse = Zend_Ldap_Node_RootDse::create($this);
  1331. }
  1332. return $this->_rootDse;
  1333. }
  1334. /**
  1335. * Returns the schema
  1336. *
  1337. * @return Zend_Ldap_Node_Schema
  1338. * @throws Zend_Ldap_Exception
  1339. */
  1340. public function getSchema()
  1341. {
  1342. if ($this->_schema === null) {
  1343. /**
  1344. * @see Zend_Ldap_Node_Schema
  1345. */
  1346. require_once 'Zend/Ldap/Node/Schema.php';
  1347. $this->_schema = Zend_Ldap_Node_Schema::create($this);
  1348. }
  1349. return $this->_schema;
  1350. }
  1351. }