Ldap.php 46 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444
  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. require_once 'Zend/Ldap/Exception.php';
  259. throw new Zend_Ldap_Exception(null, "Unknown Zend_Ldap option: $key");
  260. }
  261. $this->_options = $permittedOptions;
  262. return $this;
  263. }
  264. /**
  265. * @return array The current options.
  266. */
  267. public function getOptions()
  268. {
  269. return $this->_options;
  270. }
  271. /**
  272. * @return string The hostname of the LDAP server being used to authenticate accounts
  273. */
  274. protected function _getHost()
  275. {
  276. return $this->_options['host'];
  277. }
  278. /**
  279. * @return int The port of the LDAP server or 0 to indicate that no port value is set
  280. */
  281. protected function _getPort()
  282. {
  283. return $this->_options['port'];
  284. }
  285. /**
  286. * @return boolean The default SSL / TLS encrypted transport control
  287. */
  288. protected function _getUseSsl()
  289. {
  290. return $this->_options['useSsl'];
  291. }
  292. /**
  293. * @return string The default acctname for binding
  294. */
  295. protected function _getUsername()
  296. {
  297. return $this->_options['username'];
  298. }
  299. /**
  300. * @return string The default password for binding
  301. */
  302. protected function _getPassword()
  303. {
  304. return $this->_options['password'];
  305. }
  306. /**
  307. * @return boolean Bind requires DN
  308. */
  309. protected function _getBindRequiresDn()
  310. {
  311. return $this->_options['bindRequiresDn'];
  312. }
  313. /**
  314. * Gets the base DN under which objects of interest are located
  315. *
  316. * @return string
  317. */
  318. public function getBaseDn()
  319. {
  320. return $this->_options['baseDn'];
  321. }
  322. /**
  323. * @return string Either ACCTNAME_FORM_BACKSLASH, ACCTNAME_FORM_PRINCIPAL or
  324. * ACCTNAME_FORM_USERNAME indicating the form usernames should be canonicalized to.
  325. */
  326. protected function _getAccountCanonicalForm()
  327. {
  328. /* Account names should always be qualified with a domain. In some scenarios
  329. * using non-qualified account names can lead to security vulnerabilities. If
  330. * no account canonical form is specified, we guess based in what domain
  331. * names have been supplied.
  332. */
  333. $accountCanonicalForm = $this->_options['accountCanonicalForm'];
  334. if (!$accountCanonicalForm) {
  335. $accountDomainName = $this->_getAccountDomainName();
  336. $accountDomainNameShort = $this->_getAccountDomainNameShort();
  337. if ($accountDomainNameShort) {
  338. $accountCanonicalForm = Zend_Ldap::ACCTNAME_FORM_BACKSLASH;
  339. } else if ($accountDomainName) {
  340. $accountCanonicalForm = Zend_Ldap::ACCTNAME_FORM_PRINCIPAL;
  341. } else {
  342. $accountCanonicalForm = Zend_Ldap::ACCTNAME_FORM_USERNAME;
  343. }
  344. }
  345. return $accountCanonicalForm;
  346. }
  347. /**
  348. * @return string The account domain name
  349. */
  350. protected function _getAccountDomainName()
  351. {
  352. return $this->_options['accountDomainName'];
  353. }
  354. /**
  355. * @return string The short account domain name
  356. */
  357. protected function _getAccountDomainNameShort()
  358. {
  359. return $this->_options['accountDomainNameShort'];
  360. }
  361. /**
  362. * @return string A format string for building an LDAP search filter to match
  363. * an account
  364. */
  365. protected function _getAccountFilterFormat()
  366. {
  367. return $this->_options['accountFilterFormat'];
  368. }
  369. /**
  370. * @return boolean Allow empty passwords
  371. */
  372. protected function _getAllowEmptyPassword()
  373. {
  374. return $this->_options['allowEmptyPassword'];
  375. }
  376. /**
  377. * @return boolean The default SSL / TLS encrypted transport control
  378. */
  379. protected function _getUseStartTls()
  380. {
  381. return $this->_options['useStartTls'];
  382. }
  383. /**
  384. * @return boolean Opt. Referrals
  385. */
  386. protected function _getOptReferrals()
  387. {
  388. return $this->_options['optReferrals'];
  389. }
  390. /**
  391. * @return boolean Try splitting the username into username and domain
  392. */
  393. protected function _getTryUsernameSplit()
  394. {
  395. return $this->_options['tryUsernameSplit'];
  396. }
  397. /**
  398. * @return string The LDAP search filter for matching directory accounts
  399. */
  400. protected function _getAccountFilter($acctname)
  401. {
  402. /**
  403. * @see Zend_Ldap_Filter_Abstract
  404. */
  405. require_once 'Zend/Ldap/Filter/Abstract.php';
  406. $this->_splitName($acctname, $dname, $aname);
  407. $accountFilterFormat = $this->_getAccountFilterFormat();
  408. $aname = Zend_Ldap_Filter_Abstract::escapeValue($aname);
  409. if ($accountFilterFormat) {
  410. return sprintf($accountFilterFormat, $aname);
  411. }
  412. if (!$this->_getBindRequiresDn()) {
  413. // is there a better way to detect this?
  414. return sprintf("(&(objectClass=user)(sAMAccountName=%s))", $aname);
  415. }
  416. return sprintf("(&(objectClass=posixAccount)(uid=%s))", $aname);
  417. }
  418. /**
  419. * @param string $name The name to split
  420. * @param string $dname The resulting domain name (this is an out parameter)
  421. * @param string $aname The resulting account name (this is an out parameter)
  422. */
  423. protected function _splitName($name, &$dname, &$aname)
  424. {
  425. $dname = null;
  426. $aname = $name;
  427. if (!$this->_getTryUsernameSplit()) {
  428. return;
  429. }
  430. $pos = strpos($name, '@');
  431. if ($pos) {
  432. $dname = substr($name, $pos + 1);
  433. $aname = substr($name, 0, $pos);
  434. } else {
  435. $pos = strpos($name, '\\');
  436. if ($pos) {
  437. $dname = substr($name, 0, $pos);
  438. $aname = substr($name, $pos + 1);
  439. }
  440. }
  441. }
  442. /**
  443. * @param string $acctname The name of the account
  444. * @return string The DN of the specified account
  445. * @throws Zend_Ldap_Exception
  446. */
  447. protected function _getAccountDn($acctname)
  448. {
  449. /**
  450. * @see Zend_Ldap_Dn
  451. */
  452. require_once 'Zend/Ldap/Dn.php';
  453. if (Zend_Ldap_Dn::checkDn($acctname)) return $acctname;
  454. $acctname = $this->getCanonicalAccountName($acctname, Zend_Ldap::ACCTNAME_FORM_USERNAME);
  455. $acct = $this->_getAccount($acctname, array('dn'));
  456. return $acct['dn'];
  457. }
  458. /**
  459. * @param string $dname The domain name to check
  460. * @return boolean
  461. */
  462. protected function _isPossibleAuthority($dname)
  463. {
  464. if ($dname === null) {
  465. return true;
  466. }
  467. $accountDomainName = $this->_getAccountDomainName();
  468. $accountDomainNameShort = $this->_getAccountDomainNameShort();
  469. if ($accountDomainName === null && $accountDomainNameShort === null) {
  470. return true;
  471. }
  472. if (strcasecmp($dname, $accountDomainName) == 0) {
  473. return true;
  474. }
  475. if (strcasecmp($dname, $accountDomainNameShort) == 0) {
  476. return true;
  477. }
  478. return false;
  479. }
  480. /**
  481. * @param string $acctname The name to canonicalize
  482. * @param int $type The desired form of canonicalization
  483. * @return string The canonicalized name in the desired form
  484. * @throws Zend_Ldap_Exception
  485. */
  486. public function getCanonicalAccountName($acctname, $form = 0)
  487. {
  488. $this->_splitName($acctname, $dname, $uname);
  489. if (!$this->_isPossibleAuthority($dname)) {
  490. /**
  491. * @see Zend_Ldap_Exception
  492. */
  493. require_once 'Zend/Ldap/Exception.php';
  494. throw new Zend_Ldap_Exception(null,
  495. "Binding domain is not an authority for user: $acctname",
  496. Zend_Ldap_Exception::LDAP_X_DOMAIN_MISMATCH);
  497. }
  498. if (!$uname) {
  499. /**
  500. * @see Zend_Ldap_Exception
  501. */
  502. require_once 'Zend/Ldap/Exception.php';
  503. throw new Zend_Ldap_Exception(null, "Invalid account name syntax: $acctname");
  504. }
  505. if (function_exists('mb_strtolower')) {
  506. $uname = mb_strtolower($uname, 'UTF-8');
  507. } else {
  508. $uname = strtolower($uname);
  509. }
  510. if ($form === 0) {
  511. $form = $this->_getAccountCanonicalForm();
  512. }
  513. switch ($form) {
  514. case Zend_Ldap::ACCTNAME_FORM_DN:
  515. return $this->_getAccountDn($acctname);
  516. case Zend_Ldap::ACCTNAME_FORM_USERNAME:
  517. return $uname;
  518. case Zend_Ldap::ACCTNAME_FORM_BACKSLASH:
  519. $accountDomainNameShort = $this->_getAccountDomainNameShort();
  520. if (!$accountDomainNameShort) {
  521. /**
  522. * @see Zend_Ldap_Exception
  523. */
  524. require_once 'Zend/Ldap/Exception.php';
  525. throw new Zend_Ldap_Exception(null, 'Option required: accountDomainNameShort');
  526. }
  527. return "$accountDomainNameShort\\$uname";
  528. case Zend_Ldap::ACCTNAME_FORM_PRINCIPAL:
  529. $accountDomainName = $this->_getAccountDomainName();
  530. if (!$accountDomainName) {
  531. /**
  532. * @see Zend_Ldap_Exception
  533. */
  534. require_once 'Zend/Ldap/Exception.php';
  535. throw new Zend_Ldap_Exception(null, 'Option required: accountDomainName');
  536. }
  537. return "$uname@$accountDomainName";
  538. default:
  539. /**
  540. * @see Zend_Ldap_Exception
  541. */
  542. require_once 'Zend/Ldap/Exception.php';
  543. throw new Zend_Ldap_Exception(null, "Unknown canonical name form: $form");
  544. }
  545. }
  546. /**
  547. * @param array $attrs An array of names of desired attributes
  548. * @return array An array of the attributes representing the account
  549. * @throws Zend_Ldap_Exception
  550. */
  551. protected function _getAccount($acctname, array $attrs = null)
  552. {
  553. $baseDn = $this->getBaseDn();
  554. if (!$baseDn) {
  555. /**
  556. * @see Zend_Ldap_Exception
  557. */
  558. require_once 'Zend/Ldap/Exception.php';
  559. throw new Zend_Ldap_Exception(null, 'Base DN not set');
  560. }
  561. $accountFilter = $this->_getAccountFilter($acctname);
  562. if (!$accountFilter) {
  563. /**
  564. * @see Zend_Ldap_Exception
  565. */
  566. require_once 'Zend/Ldap/Exception.php';
  567. throw new Zend_Ldap_Exception(null, 'Invalid account filter');
  568. }
  569. if (!is_resource($this->getResource())) {
  570. $this->bind();
  571. }
  572. $accounts = $this->search($accountFilter, $baseDn, self::SEARCH_SCOPE_SUB, $attrs);
  573. $count = $accounts->count();
  574. if ($count === 1) {
  575. $acct = $accounts->getFirst();
  576. $accounts->close();
  577. return $acct;
  578. } else if ($count === 0) {
  579. /**
  580. * @see Zend_Ldap_Exception
  581. */
  582. require_once 'Zend/Ldap/Exception.php';
  583. $code = Zend_Ldap_Exception::LDAP_NO_SUCH_OBJECT;
  584. $str = "No object found for: $accountFilter";
  585. } else {
  586. /**
  587. * @see Zend_Ldap_Exception
  588. */
  589. require_once 'Zend/Ldap/Exception.php';
  590. $code = Zend_Ldap_Exception::LDAP_OPERATIONS_ERROR;
  591. $str = "Unexpected result count ($count) for: $accountFilter";
  592. }
  593. $accounts->close();
  594. /**
  595. * @see Zend_Ldap_Exception
  596. */
  597. require_once 'Zend/Ldap/Exception.php';
  598. throw new Zend_Ldap_Exception($this, $str, $code);
  599. }
  600. /**
  601. * @return Zend_Ldap Provides a fluent interface
  602. */
  603. public function disconnect()
  604. {
  605. if (is_resource($this->getResource())) {
  606. if (!extension_loaded('ldap')) {
  607. /**
  608. * @see Zend_Ldap_Exception
  609. */
  610. require_once 'Zend/Ldap/Exception.php';
  611. throw new Zend_Ldap_Exception(null, 'LDAP extension not loaded',
  612. Zend_Ldap_Exception::LDAP_X_EXTENSION_NOT_LOADED);
  613. }
  614. @ldap_unbind($this->getResource());
  615. }
  616. $this->_resource = null;
  617. return $this;
  618. }
  619. /**
  620. * @param string $host The hostname of the LDAP server to connect to
  621. * @param int $port The port number of the LDAP server to connect to
  622. * @param boolean $useSsl Use SSL
  623. * @param boolean $useStartTls Use STARTTLS
  624. * @return Zend_Ldap Provides a fluent interface
  625. * @throws Zend_Ldap_Exception
  626. */
  627. public function connect($host = null, $port = null, $useSsl = null, $useStartTls = null)
  628. {
  629. if ($host === null) {
  630. $host = $this->_getHost();
  631. }
  632. if ($port === null) {
  633. $port = $this->_getPort();
  634. } else {
  635. $port = (int)$port;
  636. }
  637. if ($useSsl === null) {
  638. $useSsl = $this->_getUseSsl();
  639. } else {
  640. $useSsl = (bool)$useSsl;
  641. }
  642. if ($useStartTls === null) {
  643. $useStartTls = $this->_getUseStartTls();
  644. } else {
  645. $useStartTls = (bool)$useStartTls;
  646. }
  647. if (!$host) {
  648. /**
  649. * @see Zend_Ldap_Exception
  650. */
  651. require_once 'Zend/Ldap/Exception.php';
  652. throw new Zend_Ldap_Exception(null, 'A host parameter is required');
  653. }
  654. /* To connect using SSL it seems the client tries to verify the server
  655. * certificate by default. One way to disable this behavior is to set
  656. * 'TLS_REQCERT never' in OpenLDAP's ldap.conf and restarting Apache. Or,
  657. * if you really care about the server's cert you can put a cert on the
  658. * web server.
  659. */
  660. $url = ($useSsl) ? "ldaps://$host" : "ldap://$host";
  661. if ($port) {
  662. $url .= ":$port";
  663. }
  664. /* Because ldap_connect doesn't really try to connect, any connect error
  665. * will actually occur during the ldap_bind call. Therefore, we save the
  666. * connect string here for reporting it in error handling in bind().
  667. */
  668. $this->_connectString = $url;
  669. $this->disconnect();
  670. if (!extension_loaded('ldap')) {
  671. /**
  672. * @see Zend_Ldap_Exception
  673. */
  674. require_once 'Zend/Ldap/Exception.php';
  675. throw new Zend_Ldap_Exception(null, 'LDAP extension not loaded',
  676. Zend_Ldap_Exception::LDAP_X_EXTENSION_NOT_LOADED);
  677. }
  678. /* Only OpenLDAP 2.2 + supports URLs so if SSL is not requested, just
  679. * use the old form.
  680. */
  681. $resource = ($useSsl) ? @ldap_connect($url) : @ldap_connect($host, $port);
  682. if (is_resource($resource) === true) {
  683. $this->_resource = $resource;
  684. $optReferrals = ($this->_getOptReferrals()) ? 1 : 0;
  685. if (@ldap_set_option($resource, LDAP_OPT_PROTOCOL_VERSION, 3) &&
  686. @ldap_set_option($resource, LDAP_OPT_REFERRALS, $optReferrals)) {
  687. if ($useSsl || !$useStartTls || @ldap_start_tls($resource)) {
  688. return $this;
  689. }
  690. }
  691. /**
  692. * @see Zend_Ldap_Exception
  693. */
  694. require_once 'Zend/Ldap/Exception.php';
  695. $zle = new Zend_Ldap_Exception($this, "$host:$port");
  696. $this->disconnect();
  697. throw $zle;
  698. }
  699. /**
  700. * @see Zend_Ldap_Exception
  701. */
  702. require_once 'Zend/Ldap/Exception.php';
  703. throw new Zend_Ldap_Exception(null, "Failed to connect to LDAP server: $host:$port");
  704. }
  705. /**
  706. * @param string $username The username for authenticating the bind
  707. * @param string $password The password for authenticating the bind
  708. * @return Zend_Ldap Provides a fluent interface
  709. * @throws Zend_Ldap_Exception
  710. */
  711. public function bind($username = null, $password = null)
  712. {
  713. $moreCreds = true;
  714. if ($username === null) {
  715. $username = $this->_getUsername();
  716. $password = $this->_getPassword();
  717. $moreCreds = false;
  718. }
  719. if ($username === null) {
  720. /* Perform anonymous bind
  721. */
  722. $password = null;
  723. } else {
  724. /* Check to make sure the username is in DN form.
  725. */
  726. /**
  727. * @see Zend_Ldap_Dn
  728. */
  729. require_once 'Zend/Ldap/Dn.php';
  730. if (!Zend_Ldap_Dn::checkDn($username)) {
  731. if ($this->_getBindRequiresDn()) {
  732. /* moreCreds stops an infinite loop if _getUsername does not
  733. * return a DN and the bind requires it
  734. */
  735. if ($moreCreds) {
  736. try {
  737. $username = $this->_getAccountDn($username);
  738. } catch (Zend_Ldap_Exception $zle) {
  739. switch ($zle->getCode()) {
  740. case Zend_Ldap_Exception::LDAP_NO_SUCH_OBJECT:
  741. case Zend_Ldap_Exception::LDAP_X_DOMAIN_MISMATCH:
  742. case Zend_Ldap_Exception::LDAP_X_EXTENSION_NOT_LOADED:
  743. throw $zle;
  744. }
  745. throw new Zend_Ldap_Exception(null,
  746. 'Failed to retrieve DN for account: ' . $username .
  747. ' [' . $zle->getMessage() . ']',
  748. Zend_Ldap_Exception::LDAP_OPERATIONS_ERROR);
  749. }
  750. } else {
  751. /**
  752. * @see Zend_Ldap_Exception
  753. */
  754. require_once 'Zend/Ldap/Exception.php';
  755. throw new Zend_Ldap_Exception(null, 'Binding requires username in DN form');
  756. }
  757. } else {
  758. $username = $this->getCanonicalAccountName($username,
  759. $this->_getAccountCanonicalForm());
  760. }
  761. }
  762. }
  763. if (!is_resource($this->getResource())) {
  764. $this->connect();
  765. }
  766. if ($username !== null && $password === '' && $this->_getAllowEmptyPassword() !== true) {
  767. /**
  768. * @see Zend_Ldap_Exception
  769. */
  770. require_once 'Zend/Ldap/Exception.php';
  771. $zle = new Zend_Ldap_Exception(null,
  772. 'Empty password not allowed - see allowEmptyPassword option.');
  773. } else {
  774. if (@ldap_bind($this->getResource(), $username, $password)) {
  775. return $this;
  776. }
  777. $message = ($username === null) ? $this->_connectString : $username;
  778. /**
  779. * @see Zend_Ldap_Exception
  780. */
  781. require_once 'Zend/Ldap/Exception.php';
  782. switch ($this->getLastErrorCode()) {
  783. case Zend_Ldap_Exception::LDAP_SERVER_DOWN:
  784. /* If the error is related to establishing a connection rather than binding,
  785. * the connect string is more informative than the username.
  786. */
  787. $message = $this->_connectString;
  788. }
  789. $zle = new Zend_Ldap_Exception($this, $message);
  790. }
  791. $this->disconnect();
  792. throw $zle;
  793. }
  794. /**
  795. * A global LDAP search routine for finding information.
  796. *
  797. * @param string|Zend_Ldap_Filter_Abstract $filter
  798. * @param string|Zend_Ldap_Dn $basedn
  799. * @param integer $scope
  800. * @param array $attributes
  801. * @param string $sort
  802. * @param string $collectionClass
  803. * @return Zend_Ldap_Collection
  804. * @throws Zend_Ldap_Exception
  805. */
  806. public function search($filter, $basedn = null, $scope = self::SEARCH_SCOPE_SUB,
  807. array $attributes = array(), $sort = null, $collectionClass = null)
  808. {
  809. if ($basedn === null) {
  810. $basedn = $this->getBaseDn();
  811. }
  812. else if ($basedn instanceof Zend_Ldap_Dn) {
  813. $basedn = $basedn->toString();
  814. }
  815. if ($filter instanceof Zend_Ldap_Filter_Abstract) {
  816. $filter = $filter->toString();
  817. }
  818. switch ($scope) {
  819. case self::SEARCH_SCOPE_ONE:
  820. $search = @ldap_list($this->getResource(), $basedn, $filter, $attributes);
  821. break;
  822. case self::SEARCH_SCOPE_BASE:
  823. $search = @ldap_read($this->getResource(), $basedn, $filter, $attributes);
  824. break;
  825. case self::SEARCH_SCOPE_SUB:
  826. default:
  827. $search = @ldap_search($this->getResource(), $basedn, $filter, $attributes);
  828. break;
  829. }
  830. if($search === false) {
  831. /**
  832. * @see Zend_Ldap_Exception
  833. */
  834. require_once 'Zend/Ldap/Exception.php';
  835. throw new Zend_Ldap_Exception($this, 'searching: ' . $filter);
  836. }
  837. if (!is_null($sort) && is_string($sort)) {
  838. $isSorted = @ldap_sort($this->getResource(), $search, $sort);
  839. if($search === false) {
  840. /**
  841. * @see Zend_Ldap_Exception
  842. */
  843. require_once 'Zend/Ldap/Exception.php';
  844. throw new Zend_Ldap_Exception($this, 'sorting: ' . $sort);
  845. }
  846. }
  847. /**
  848. * Zend_Ldap_Collection_Iterator_Default
  849. */
  850. require_once 'Zend/Ldap/Collection/Iterator/Default.php';
  851. $iterator = new Zend_Ldap_Collection_Iterator_Default($this, $search);
  852. if ($collectionClass === null) {
  853. /**
  854. * Zend_Ldap_Collection
  855. */
  856. require_once 'Zend/Ldap/Collection.php';
  857. return new Zend_Ldap_Collection($iterator);
  858. } else {
  859. /**
  860. * @todo implement checks
  861. */
  862. return new $collectionClass($iterator);
  863. }
  864. }
  865. /**
  866. * Count items found by given filter.
  867. *
  868. * @param string|Zend_Ldap_Filter_Abstract $filter
  869. * @param string|Zend_Ldap_Dn $basedn
  870. * @param integer $scope
  871. * @return integer
  872. * @throws Zend_Ldap_Exception
  873. */
  874. public function count($filter, $basedn = null, $scope = self::SEARCH_SCOPE_SUB)
  875. {
  876. try {
  877. $result = $this->search($filter, $basedn, $scope, array('dn'), null);
  878. } catch (Zend_Ldap_Exception $e) {
  879. if ($e->getCode() === Zend_Ldap_Exception::LDAP_NO_SUCH_OBJECT) return 0;
  880. else throw $e;
  881. }
  882. return $result->count();
  883. }
  884. /**
  885. * Count children for a given DN.
  886. *
  887. * @param string|Zend_Ldap_Dn $dn
  888. * @return integer
  889. * @throws Zend_Ldap_Exception
  890. */
  891. public function countChildren($dn)
  892. {
  893. return $this->count('(objectClass=*)', $dn, self::SEARCH_SCOPE_ONE);
  894. }
  895. /**
  896. * Check if a given DN exists.
  897. *
  898. * @param string|Zend_Ldap_Dn $dn
  899. * @return boolean
  900. * @throws Zend_Ldap_Exception
  901. */
  902. public function exists($dn)
  903. {
  904. return ($this->count('(objectClass=*)', $dn, self::SEARCH_SCOPE_BASE) == 1);
  905. }
  906. /**
  907. * Search LDAP registry for entries matching filter and optional attributes
  908. *
  909. * @param string|Zend_Ldap_Filter_Abstract $filter
  910. * @param string|Zend_Ldap_Dn $basedn
  911. * @param integer $scope
  912. * @param array $attributes
  913. * @param string $sort
  914. * @return array
  915. * @throws Zend_Ldap_Exception
  916. */
  917. public function searchEntries($filter, $basedn = null, $scope = self::SEARCH_SCOPE_SUB,
  918. array $attributes = array(), $sort = null)
  919. {
  920. $result = $this->search($filter, $basedn, $scope, $attributes, $sort);
  921. return $result->toArray();
  922. }
  923. /**
  924. * Get LDAP entry by DN
  925. *
  926. * @param string|Zend_Ldap_Dn $dn
  927. * @param array $attributes
  928. * @param boolean $throwOnNotFound
  929. * @return array
  930. * @throws Zend_Ldap_Exception
  931. */
  932. public function getEntry($dn, array $attributes = array(), $throwOnNotFound = false)
  933. {
  934. try {
  935. $result = $this->search("(objectClass=*)", $dn, self::SEARCH_SCOPE_BASE,
  936. $attributes, null);
  937. return $result->getFirst();
  938. } catch (Zend_Ldap_Exception $e){
  939. if ($throwOnNotFound !== false) throw $e;
  940. }
  941. return null;
  942. }
  943. /**
  944. * Prepares an ldap data entry array for insert/update operation
  945. *
  946. * @param array $entry
  947. * @return void
  948. * @throws InvalidArgumentException
  949. */
  950. public static function prepareLdapEntryArray(array &$entry)
  951. {
  952. if (array_key_exists('dn', $entry)) unset($entry['dn']);
  953. foreach ($entry as $key => $value) {
  954. if (is_array($value)) {
  955. foreach ($value as $i => $v) {
  956. if (is_null($v)) unset($value[$i]);
  957. else if (empty($v)) unset($value[$i]);
  958. else if (!is_scalar($v)) {
  959. throw new InvalidArgumentException('Only scalar values allowed in LDAP data');
  960. }
  961. }
  962. $entry[$key] = array_values($value);
  963. } else {
  964. if (is_null($value)) $entry[$key] = array();
  965. else if (empty($value)) $entry[$key] = array();
  966. else $entry[$key] = array($value);
  967. }
  968. }
  969. $entry = array_change_key_case($entry, CASE_LOWER);
  970. }
  971. /**
  972. * Add new information to the LDAP repository
  973. *
  974. * @param string|Zend_Ldap_Dn $dn
  975. * @param array $entry
  976. * @return Zend_Ldap *Provides a fluid interface*
  977. * @throws Zend_Ldap_Exception
  978. */
  979. public function add($dn, array $entry)
  980. {
  981. if (!($dn instanceof Zend_Ldap_Dn)) {
  982. $dn = Zend_Ldap_Dn::factory($dn, null);
  983. }
  984. self::prepareLdapEntryArray($entry);
  985. foreach ($entry as $key => $value) {
  986. if (is_array($value) && count($value) === 0) {
  987. unset($entry[$key]);
  988. }
  989. }
  990. $rdnParts = $dn->getRdn(Zend_Ldap_Dn::ATTR_CASEFOLD_LOWER);
  991. foreach ($rdnParts as $key => $value) {
  992. $value = Zend_Ldap_Dn::unescapeValue($value);
  993. if (!array_key_exists($key, $entry) ||
  994. !in_array($value, $entry[$key]) ||
  995. count($entry[$key]) !== 1) {
  996. $entry[$key] = array($value);
  997. }
  998. }
  999. $adAttributes = array('distinguishedname', 'instancetype', 'name', 'objectcategory',
  1000. 'objectguid', 'usnchanged', 'usncreated', 'whenchanged', 'whencreated');
  1001. foreach ($adAttributes as $attr) {
  1002. if (array_key_exists($attr, $entry)) {
  1003. unset($entry[$attr]);
  1004. }
  1005. }
  1006. $isAdded = @ldap_add($this->getResource(), $dn->toString(), $entry);
  1007. if($isAdded === false) {
  1008. /**
  1009. * @see Zend_Ldap_Exception
  1010. */
  1011. require_once 'Zend/Ldap/Exception.php';
  1012. throw new Zend_Ldap_Exception($this, 'adding: ' . $dn->toString());
  1013. }
  1014. return $this;
  1015. }
  1016. /**
  1017. * Update LDAP registry
  1018. *
  1019. * @param string|Zend_Ldap_Dn $dn
  1020. * @param array $entry
  1021. * @return Zend_Ldap *Provides a fluid interface*
  1022. * @throws Zend_Ldap_Exception
  1023. */
  1024. public function update($dn, array $entry)
  1025. {
  1026. if (!($dn instanceof Zend_Ldap_Dn)) {
  1027. $dn = Zend_Ldap_Dn::factory($dn, null);
  1028. }
  1029. self::prepareLdapEntryArray($entry);
  1030. $rdnParts = $dn->getRdn(Zend_Ldap_Dn::ATTR_CASEFOLD_LOWER);
  1031. $adAttributes = array('distinguishedname', 'instancetype', 'name', 'objectcategory',
  1032. 'objectguid', 'usnchanged', 'usncreated', 'whenchanged', 'whencreated');
  1033. $stripAttributes = array_merge(array_keys($rdnParts), $adAttributes);
  1034. foreach ($stripAttributes as $attr) {
  1035. if (array_key_exists($attr, $entry)) {
  1036. unset($entry[$attr]);
  1037. }
  1038. }
  1039. if (count($entry) > 0) {
  1040. $isModified = @ldap_modify($this->getResource(), $dn->toString(), $entry);
  1041. if($isModified === false) {
  1042. /**
  1043. * @see Zend_Ldap_Exception
  1044. */
  1045. require_once 'Zend/Ldap/Exception.php';
  1046. throw new Zend_Ldap_Exception($this, 'updating: ' . $dn->toString());
  1047. }
  1048. }
  1049. return $this;
  1050. }
  1051. /**
  1052. * Save entry to LDAP registry.
  1053. *
  1054. * Internally decides if entry will be updated to added by calling
  1055. * {@link exists()}.
  1056. *
  1057. * @param string|Zend_Ldap_Dn $dn
  1058. * @param array $entry
  1059. * @return Zend_Ldap *Provides a fluid interface*
  1060. * @throws Zend_Ldap_Exception
  1061. */
  1062. public function save($dn, array $entry)
  1063. {
  1064. if ($dn instanceof Zend_Ldap_Dn) {
  1065. $dn = $dn->toString();
  1066. }
  1067. if ($this->exists($dn)) $this->update($dn, $entry);
  1068. else $this->add($dn, $entry);
  1069. return $this;
  1070. }
  1071. /**
  1072. * Delete an LDAP entry
  1073. *
  1074. * @param string|Zend_Ldap_Dn $dn
  1075. * @param boolean $recursively
  1076. * @return Zend_Ldap *Provides a fluid interface*
  1077. * @throws Zend_Ldap_Exception
  1078. */
  1079. public function delete($dn, $recursively = false)
  1080. {
  1081. if ($dn instanceof Zend_Ldap_Dn) {
  1082. $dn = $dn->toString();
  1083. }
  1084. if ($recursively === true) {
  1085. if ($this->countChildren($dn)>0) {
  1086. $children = $this->_getChildrenDns($dn);
  1087. foreach ($children as $c) {
  1088. $this->delete($c, true);
  1089. }
  1090. }
  1091. }
  1092. $isDeleted = @ldap_delete($this->getResource(), $dn);
  1093. if($isDeleted === false) {
  1094. /**
  1095. * @see Zend_Ldap_Exception
  1096. */
  1097. require_once 'Zend/Ldap/Exception.php';
  1098. throw new Zend_Ldap_Exception($this, 'deleting: ' . $dn);
  1099. }
  1100. return $this;
  1101. }
  1102. /**
  1103. * Retrieve the immediate children DNs of the given $parentDn
  1104. *
  1105. * This method is used in recursive methods like {@see delete()}
  1106. * or {@see copy()}
  1107. *
  1108. * @param string| $parentDn
  1109. * @return array of DNs
  1110. */
  1111. protected function _getChildrenDns($parentDn)
  1112. {
  1113. if ($parentDn instanceof Zend_Ldap_Dn) {
  1114. $parentDn = $parentDn->toString();
  1115. }
  1116. $children = array();
  1117. $search = @ldap_list($this->getResource(), $parentDn, '(objectClass=*)', array('dn'));
  1118. for ($entry = @ldap_first_entry($this->getResource(), $search);
  1119. $entry !== false;
  1120. $entry = @ldap_next_entry($this->getResource(), $entry)) {
  1121. $childDn = @ldap_get_dn($this->getResource(), $entry);
  1122. if ($childDn === false) {
  1123. /**
  1124. * @see Zend_Ldap_Exception
  1125. */
  1126. require_once 'Zend/Ldap/Exception.php';
  1127. throw new Zend_Ldap_Exception($this, 'getting dn');
  1128. }
  1129. $children[] = $childDn;
  1130. }
  1131. @ldap_free_result($search);
  1132. return $children;
  1133. }
  1134. /**
  1135. * Moves a LDAP entry from one DN to another subtree.
  1136. *
  1137. * @param string|Zend_Ldap_Dn $from
  1138. * @param string|Zend_Ldap_Dn $to
  1139. * @param boolean $recursively
  1140. * @param boolean $alwaysEmulate
  1141. * @return Zend_Ldap *Provides a fluid interface*
  1142. * @throws Zend_Ldap_Exception
  1143. */
  1144. public function moveToSubtree($from, $to, $recursively = false, $alwaysEmulate = false)
  1145. {
  1146. if ($from instanceof Zend_Ldap_Dn) {
  1147. $orgDnParts = $from->toArray();
  1148. } else {
  1149. $orgDnParts = Zend_Ldap_Dn::explodeDn($from);
  1150. }
  1151. if ($to instanceof Zend_Ldap_Dn) {
  1152. $newParentDnParts = $to->toArray();
  1153. } else {
  1154. $newParentDnParts = Zend_Ldap_Dn::explodeDn($to);
  1155. }
  1156. $newDnParts = array_merge(array(array_shift($orgDnParts)), $newParentDnParts);
  1157. $newDn = Zend_Ldap_Dn::fromArray($newDnParts);
  1158. return $this->rename($from, $newDn, $recursively, $alwaysEmulate);
  1159. }
  1160. /**
  1161. * Moves a LDAP entry from one DN to another DN.
  1162. *
  1163. * This is an alias for {@link rename()}
  1164. *
  1165. * @param string|Zend_Ldap_Dn $from
  1166. * @param string|Zend_Ldap_Dn $to
  1167. * @param boolean $recursively
  1168. * @param boolean $alwaysEmulate
  1169. * @return Zend_Ldap *Provides a fluid interface*
  1170. * @throws Zend_Ldap_Exception
  1171. */
  1172. public function move($from, $to, $recursively = false, $alwaysEmulate = false)
  1173. {
  1174. return $this->rename($from, $to, $recursively, $alwaysEmulate);
  1175. }
  1176. /**
  1177. * Renames a LDAP entry from one DN to another DN.
  1178. *
  1179. * This method implicitely moves the entry to another location within the tree.
  1180. *
  1181. * @param string|Zend_Ldap_Dn $from
  1182. * @param string|Zend_Ldap_Dn $to
  1183. * @param boolean $recursively
  1184. * @param boolean $alwaysEmulate
  1185. * @return Zend_Ldap *Provides a fluid interface*
  1186. * @throws Zend_Ldap_Exception
  1187. */
  1188. public function rename($from, $to, $recursively = false, $alwaysEmulate = false)
  1189. {
  1190. $emulate = (bool)$alwaysEmulate;
  1191. if (!function_exists('ldap_rename')) $emulate = true;
  1192. else if ($recursively) $emulate = true;
  1193. if ($emulate === false) {
  1194. if ($from instanceof Zend_Ldap_Dn) {
  1195. $from = $from->toString();
  1196. }
  1197. if ($to instanceof Zend_Ldap_Dn) {
  1198. $newDnParts = $to->toArray();
  1199. } else {
  1200. $newDnParts = Zend_Ldap_Dn::explodeDn($to);
  1201. }
  1202. $newRdn = Zend_Ldap_Dn::implodeRdn(array_shift($newDnParts));
  1203. $newParent = Zend_Ldap_Dn::implodeDn($newDnParts);
  1204. $isOK = @ldap_rename($this->getResource(), $from, $newRdn, $newParent, true);
  1205. if($isOK === false) {
  1206. /**
  1207. * @see Zend_Ldap_Exception
  1208. */
  1209. require_once 'Zend/Ldap/Exception.php';
  1210. throw new Zend_Ldap_Exception($this, 'renaming ' . $from . ' to ' . $to);
  1211. }
  1212. else if (!$this->exists($to)) $emulate = true;
  1213. }
  1214. if ($emulate) {
  1215. $this->copy($from, $to, $recursively);
  1216. $this->delete($from, $recursively);
  1217. }
  1218. return $this;
  1219. }
  1220. /**
  1221. * Copies a LDAP entry from one DN to another subtree.
  1222. *
  1223. * @param string|Zend_Ldap_Dn $from
  1224. * @param string|Zend_Ldap_Dn $to
  1225. * @param boolean $recursively
  1226. * @return Zend_Ldap *Provides a fluid interface*
  1227. * @throws Zend_Ldap_Exception
  1228. */
  1229. public function copyToSubtree($from, $to, $recursively = false)
  1230. {
  1231. if ($from instanceof Zend_Ldap_Dn) {
  1232. $orgDnParts = $from->toArray();
  1233. } else {
  1234. $orgDnParts = Zend_Ldap_Dn::explodeDn($from);
  1235. }
  1236. if ($to instanceof Zend_Ldap_Dn) {
  1237. $newParentDnParts = $to->toArray();
  1238. } else {
  1239. $newParentDnParts = Zend_Ldap_Dn::explodeDn($to);
  1240. }
  1241. $newDnParts = array_merge(array(array_shift($orgDnParts)), $newParentDnParts);
  1242. $newDn = Zend_Ldap_Dn::fromArray($newDnParts);
  1243. return $this->copy($from, $newDn, $recursively);
  1244. }
  1245. /**
  1246. * Copies a LDAP entry from one DN to another DN.
  1247. *
  1248. * @param string|Zend_Ldap_Dn $from
  1249. * @param string|Zend_Ldap_Dn $to
  1250. * @param boolean $recursively
  1251. * @return Zend_Ldap *Provides a fluid interface*
  1252. * @throws Zend_Ldap_Exception
  1253. */
  1254. public function copy($from, $to, $recursively = false)
  1255. {
  1256. $entry = $this->getEntry($from, array(), true);
  1257. if ($to instanceof Zend_Ldap_Dn) {
  1258. $toDnParts = $to->toArray();
  1259. } else {
  1260. $toDnParts = Zend_Ldap_Dn::explodeDn($to);
  1261. }
  1262. $this->add($to, $entry);
  1263. if ($recursively === true && $this->countChildren($from)>0) {
  1264. $children = $this->_getChildrenDns($from);
  1265. foreach ($children as $c) {
  1266. $cDnParts = Zend_Ldap_Dn::explodeDn($c);
  1267. $newChildParts = array_merge(array(array_shift($cDnParts)), $toDnParts);
  1268. $newChild = Zend_Ldap_Dn::implodeDn($newChildParts);
  1269. $this->copy($c, $newChild, true);
  1270. }
  1271. }
  1272. return $this;
  1273. }
  1274. /**
  1275. * Returns the specified DN as a Zend_Ldap_Node
  1276. *
  1277. * @param string|Zend_Ldap_Dn $dn
  1278. * @return Zend_Ldap_Node
  1279. * @throws Zend_Ldap_Exception
  1280. */
  1281. public function getNode($dn)
  1282. {
  1283. /**
  1284. * Zend_Ldap_Node
  1285. */
  1286. require_once 'Zend/Ldap/Node.php';
  1287. return Zend_Ldap_Node::fromLdap($dn, $this);
  1288. }
  1289. /**
  1290. * Returns the base node as a Zend_Ldap_Node
  1291. *
  1292. * @return Zend_Ldap_Node
  1293. * @throws Zend_Ldap_Exception
  1294. */
  1295. public function getBaseNode()
  1296. {
  1297. return $this->getNode($this->getBaseDn(), $this);
  1298. }
  1299. /**
  1300. * Returns the RootDSE
  1301. *
  1302. * @return Zend_Ldap_Node_RootDse
  1303. * @throws Zend_Ldap_Exception
  1304. */
  1305. public function getRootDse()
  1306. {
  1307. if ($this->_rootDse === null) {
  1308. /**
  1309. * @see Zend_Ldap_Node_Schema
  1310. */
  1311. require_once 'Zend/Ldap/Node/RootDse.php';
  1312. $this->_rootDse = Zend_Ldap_Node_RootDse::create($this);
  1313. }
  1314. return $this->_rootDse;
  1315. }
  1316. /**
  1317. * Returns the schema
  1318. *
  1319. * @return Zend_Ldap_Node_Schema
  1320. * @throws Zend_Ldap_Exception
  1321. */
  1322. public function getSchema()
  1323. {
  1324. if ($this->_schema === null) {
  1325. /**
  1326. * @see Zend_Ldap_Node_Schema
  1327. */
  1328. require_once 'Zend/Ldap/Node/Schema.php';
  1329. $this->_schema = Zend_Ldap_Node_Schema::create($this);
  1330. }
  1331. return $this->_schema;
  1332. }
  1333. }