Ldap.php 46 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442
  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 ($form === Zend_Ldap::ACCTNAME_FORM_DN) {
  499. return $this->_getAccountDn($acctname);
  500. }
  501. if (!$uname) {
  502. /**
  503. * @see Zend_Ldap_Exception
  504. */
  505. require_once 'Zend/Ldap/Exception.php';
  506. throw new Zend_Ldap_Exception(null, "Invalid account name syntax: $acctname");
  507. }
  508. $uname = strtolower($uname);
  509. if ($form === 0) {
  510. $form = $this->_getAccountCanonicalForm();
  511. }
  512. switch ($form) {
  513. case Zend_Ldap::ACCTNAME_FORM_USERNAME:
  514. return $uname;
  515. case Zend_Ldap::ACCTNAME_FORM_BACKSLASH:
  516. $accountDomainNameShort = $this->_getAccountDomainNameShort();
  517. if (!$accountDomainNameShort) {
  518. /**
  519. * @see Zend_Ldap_Exception
  520. */
  521. require_once 'Zend/Ldap/Exception.php';
  522. throw new Zend_Ldap_Exception(null, 'Option required: accountDomainNameShort');
  523. }
  524. return "$accountDomainNameShort\\$uname";
  525. case Zend_Ldap::ACCTNAME_FORM_PRINCIPAL:
  526. $accountDomainName = $this->_getAccountDomainName();
  527. if (!$accountDomainName) {
  528. /**
  529. * @see Zend_Ldap_Exception
  530. */
  531. require_once 'Zend/Ldap/Exception.php';
  532. throw new Zend_Ldap_Exception(null, 'Option required: accountDomainName');
  533. }
  534. return "$uname@$accountDomainName";
  535. default:
  536. /**
  537. * @see Zend_Ldap_Exception
  538. */
  539. require_once 'Zend/Ldap/Exception.php';
  540. throw new Zend_Ldap_Exception(null, "Unknown canonical name form: $form");
  541. }
  542. }
  543. /**
  544. * @param array $attrs An array of names of desired attributes
  545. * @return array An array of the attributes representing the account
  546. * @throws Zend_Ldap_Exception
  547. */
  548. private function _getAccount($acctname, array $attrs = null)
  549. {
  550. $baseDn = $this->getBaseDn();
  551. if (!$baseDn) {
  552. /**
  553. * @see Zend_Ldap_Exception
  554. */
  555. require_once 'Zend/Ldap/Exception.php';
  556. throw new Zend_Ldap_Exception(null, 'Base DN not set');
  557. }
  558. $accountFilter = $this->_getAccountFilter($acctname);
  559. if (!$accountFilter) {
  560. /**
  561. * @see Zend_Ldap_Exception
  562. */
  563. require_once 'Zend/Ldap/Exception.php';
  564. throw new Zend_Ldap_Exception(null, 'Invalid account filter');
  565. }
  566. if (!is_resource($this->getResource())) {
  567. $this->bind();
  568. }
  569. $accounts = $this->search($accountFilter, $baseDn, self::SEARCH_SCOPE_SUB, $attrs);
  570. $count = $accounts->count();
  571. if ($count === 1) {
  572. $acct = $accounts->getFirst();
  573. $accounts->close();
  574. return $acct;
  575. } else if ($count === 0) {
  576. /**
  577. * @see Zend_Ldap_Exception
  578. */
  579. require_once 'Zend/Ldap/Exception.php';
  580. $code = Zend_Ldap_Exception::LDAP_NO_SUCH_OBJECT;
  581. $str = "No object found for: $accountFilter";
  582. } else {
  583. /**
  584. * @see Zend_Ldap_Exception
  585. */
  586. require_once 'Zend/Ldap/Exception.php';
  587. $code = Zend_Ldap_Exception::LDAP_OPERATIONS_ERROR;
  588. $str = "Unexpected result count ($count) for: $accountFilter";
  589. }
  590. $accounts->close();
  591. /**
  592. * @see Zend_Ldap_Exception
  593. */
  594. require_once 'Zend/Ldap/Exception.php';
  595. throw new Zend_Ldap_Exception($this, $str, $code);
  596. }
  597. /**
  598. * @return Zend_Ldap Provides a fluent interface
  599. */
  600. public function disconnect()
  601. {
  602. if (is_resource($this->getResource())) {
  603. if (!extension_loaded('ldap')) {
  604. /**
  605. * @see Zend_Ldap_Exception
  606. */
  607. require_once 'Zend/Ldap/Exception.php';
  608. throw new Zend_Ldap_Exception(null, 'LDAP extension not loaded',
  609. Zend_Ldap_Exception::LDAP_X_EXTENSION_NOT_LOADED);
  610. }
  611. @ldap_unbind($this->getResource());
  612. }
  613. $this->_resource = null;
  614. return $this;
  615. }
  616. /**
  617. * @param string $host The hostname of the LDAP server to connect to
  618. * @param int $port The port number of the LDAP server to connect to
  619. * @param boolean $useSsl Use SSL
  620. * @param boolean $useStartTls Use STARTTLS
  621. * @return Zend_Ldap Provides a fluent interface
  622. * @throws Zend_Ldap_Exception
  623. */
  624. public function connect($host = null, $port = null, $useSsl = null, $useStartTls = null)
  625. {
  626. if ($host === null) {
  627. $host = $this->_getHost();
  628. }
  629. if ($port === null) {
  630. $port = $this->_getPort();
  631. } else {
  632. $port = (int)$port;
  633. }
  634. if ($useSsl === null) {
  635. $useSsl = $this->_getUseSsl();
  636. } else {
  637. $useSsl = (bool)$useSsl;
  638. }
  639. if ($useStartTls === null) {
  640. $useStartTls = $this->_getUseStartTls();
  641. } else {
  642. $useStartTls = (bool)$useStartTls;
  643. }
  644. if (!$host) {
  645. /**
  646. * @see Zend_Ldap_Exception
  647. */
  648. require_once 'Zend/Ldap/Exception.php';
  649. throw new Zend_Ldap_Exception(null, 'A host parameter is required');
  650. }
  651. /* To connect using SSL it seems the client tries to verify the server
  652. * certificate by default. One way to disable this behavior is to set
  653. * 'TLS_REQCERT never' in OpenLDAP's ldap.conf and restarting Apache. Or,
  654. * if you really care about the server's cert you can put a cert on the
  655. * web server.
  656. */
  657. $url = ($useSsl) ? "ldaps://$host" : "ldap://$host";
  658. if ($port) {
  659. $url .= ":$port";
  660. }
  661. /* Because ldap_connect doesn't really try to connect, any connect error
  662. * will actually occur during the ldap_bind call. Therefore, we save the
  663. * connect string here for reporting it in error handling in bind().
  664. */
  665. $this->_connectString = $url;
  666. $this->disconnect();
  667. if (!extension_loaded('ldap')) {
  668. /**
  669. * @see Zend_Ldap_Exception
  670. */
  671. require_once 'Zend/Ldap/Exception.php';
  672. throw new Zend_Ldap_Exception(null, 'LDAP extension not loaded',
  673. Zend_Ldap_Exception::LDAP_X_EXTENSION_NOT_LOADED);
  674. }
  675. /* Only OpenLDAP 2.2 + supports URLs so if SSL is not requested, just
  676. * use the old form.
  677. */
  678. $resource = ($useSsl) ? @ldap_connect($url) : @ldap_connect($host, $port);
  679. if (is_resource($resource) === true) {
  680. $this->_resource = $resource;
  681. $optReferrals = ($this->_getOptReferrals()) ? 1 : 0;
  682. if (@ldap_set_option($resource, LDAP_OPT_PROTOCOL_VERSION, 3) &&
  683. @ldap_set_option($resource, LDAP_OPT_REFERRALS, $optReferrals)) {
  684. if ($useSsl || !$useStartTls || @ldap_start_tls($resource)) {
  685. return $this;
  686. }
  687. }
  688. /**
  689. * @see Zend_Ldap_Exception
  690. */
  691. require_once 'Zend/Ldap/Exception.php';
  692. $zle = new Zend_Ldap_Exception($this, "$host:$port");
  693. $this->disconnect();
  694. throw $zle;
  695. }
  696. /**
  697. * @see Zend_Ldap_Exception
  698. */
  699. require_once 'Zend/Ldap/Exception.php';
  700. throw new Zend_Ldap_Exception(null, "Failed to connect to LDAP server: $host:$port");
  701. }
  702. /**
  703. * @param string $username The username for authenticating the bind
  704. * @param string $password The password for authenticating the bind
  705. * @return Zend_Ldap Provides a fluent interface
  706. * @throws Zend_Ldap_Exception
  707. */
  708. public function bind($username = null, $password = null)
  709. {
  710. $moreCreds = true;
  711. if ($username === null) {
  712. $username = $this->_getUsername();
  713. $password = $this->_getPassword();
  714. $moreCreds = false;
  715. }
  716. if ($username === null) {
  717. /* Perform anonymous bind
  718. */
  719. $password = null;
  720. } else {
  721. /* Check to make sure the username is in DN form.
  722. */
  723. /**
  724. * @see Zend_Ldap_Dn
  725. */
  726. require_once 'Zend/Ldap/Dn.php';
  727. if (!Zend_Ldap_Dn::checkDn($username)) {
  728. if ($this->_getBindRequiresDn()) {
  729. /* moreCreds stops an infinite loop if _getUsername does not
  730. * return a DN and the bind requires it
  731. */
  732. if ($moreCreds) {
  733. try {
  734. $username = $this->_getAccountDn($username);
  735. } catch (Zend_Ldap_Exception $zle) {
  736. switch ($zle->getCode()) {
  737. case Zend_Ldap_Exception::LDAP_NO_SUCH_OBJECT:
  738. case Zend_Ldap_Exception::LDAP_X_DOMAIN_MISMATCH:
  739. case Zend_Ldap_Exception::LDAP_X_EXTENSION_NOT_LOADED:
  740. throw $zle;
  741. }
  742. throw new Zend_Ldap_Exception(null,
  743. 'Failed to retrieve DN for account: ' . $username .
  744. ' [' . $zle->getMessage() . ']',
  745. Zend_Ldap_Exception::LDAP_OPERATIONS_ERROR);
  746. }
  747. } else {
  748. /**
  749. * @see Zend_Ldap_Exception
  750. */
  751. require_once 'Zend/Ldap/Exception.php';
  752. throw new Zend_Ldap_Exception(null, 'Binding requires username in DN form');
  753. }
  754. } else {
  755. $username = $this->getCanonicalAccountName($username,
  756. Zend_Ldap::ACCTNAME_FORM_PRINCIPAL);
  757. }
  758. }
  759. }
  760. if (!is_resource($this->getResource())) {
  761. $this->connect();
  762. }
  763. if ($username !== null && $password === '' && $this->_getAllowEmptyPassword() !== true) {
  764. /**
  765. * @see Zend_Ldap_Exception
  766. */
  767. require_once 'Zend/Ldap/Exception.php';
  768. $zle = new Zend_Ldap_Exception(null,
  769. 'Empty password not allowed - see allowEmptyPassword option.');
  770. } else {
  771. if (@ldap_bind($this->getResource(), $username, $password)) {
  772. return $this;
  773. }
  774. $message = ($username === null) ? $this->_connectString : $username;
  775. /**
  776. * @see Zend_Ldap_Exception
  777. */
  778. require_once 'Zend/Ldap/Exception.php';
  779. switch ($this->getLastErrorCode()) {
  780. case Zend_Ldap_Exception::LDAP_SERVER_DOWN:
  781. /* If the error is related to establishing a connection rather than binding,
  782. * the connect string is more informative than the username.
  783. */
  784. $message = $this->_connectString;
  785. }
  786. $zle = new Zend_Ldap_Exception($this, $message);
  787. }
  788. $this->disconnect();
  789. throw $zle;
  790. }
  791. /**
  792. * A global LDAP search routine for finding information.
  793. *
  794. * @param string|Zend_Ldap_Filter_Abstract $filter
  795. * @param string|Zend_Ldap_Dn $basedn
  796. * @param integer $scope
  797. * @param array $attributes
  798. * @param string $sort
  799. * @param string $collectionClass
  800. * @return Zend_Ldap_Collection
  801. * @throws Zend_Ldap_Exception
  802. */
  803. public function search($filter, $basedn = null, $scope = self::SEARCH_SCOPE_SUB,
  804. array $attributes = array(), $sort = null, $collectionClass = null)
  805. {
  806. if ($basedn === null) {
  807. $basedn = $this->getBaseDn();
  808. }
  809. else if ($basedn instanceof Zend_Ldap_Dn) {
  810. $basedn = $basedn->toString();
  811. }
  812. if ($filter instanceof Zend_Ldap_Filter_Abstract) {
  813. $filter = $filter->toString();
  814. }
  815. switch ($scope) {
  816. case self::SEARCH_SCOPE_ONE:
  817. $search = @ldap_list($this->getResource(), $basedn, $filter, $attributes);
  818. break;
  819. case self::SEARCH_SCOPE_BASE:
  820. $search = @ldap_read($this->getResource(), $basedn, $filter, $attributes);
  821. break;
  822. case self::SEARCH_SCOPE_SUB:
  823. default:
  824. $search = @ldap_search($this->getResource(), $basedn, $filter, $attributes);
  825. break;
  826. }
  827. if($search === false) {
  828. /**
  829. * @see Zend_Ldap_Exception
  830. */
  831. require_once 'Zend/Ldap/Exception.php';
  832. throw new Zend_Ldap_Exception($this, 'searching: ' . $filter);
  833. }
  834. if (!is_null($sort) && is_string($sort)) {
  835. $isSorted = @ldap_sort($this->getResource(), $search, $sort);
  836. if($search === false) {
  837. /**
  838. * @see Zend_Ldap_Exception
  839. */
  840. require_once 'Zend/Ldap/Exception.php';
  841. throw new Zend_Ldap_Exception($this, 'sorting: ' . $sort);
  842. }
  843. }
  844. /**
  845. * Zend_Ldap_Collection_Iterator_Default
  846. */
  847. require_once 'Zend/Ldap/Collection/Iterator/Default.php';
  848. $iterator = new Zend_Ldap_Collection_Iterator_Default($this, $search);
  849. if ($collectionClass === null) {
  850. /**
  851. * Zend_Ldap_Collection
  852. */
  853. require_once 'Zend/Ldap/Collection.php';
  854. return new Zend_Ldap_Collection($iterator);
  855. } else {
  856. /**
  857. * @todo implement checks
  858. */
  859. return new $collectionClass($iterator);
  860. }
  861. }
  862. /**
  863. * Count items found by given filter.
  864. *
  865. * @param string|Zend_Ldap_Filter_Abstract $filter
  866. * @param string|Zend_Ldap_Dn $basedn
  867. * @param integer $scope
  868. * @return integer
  869. * @throws Zend_Ldap_Exception
  870. */
  871. public function count($filter, $basedn = null, $scope = self::SEARCH_SCOPE_SUB)
  872. {
  873. try {
  874. $result = $this->search($filter, $basedn, $scope, array('dn'), null);
  875. } catch (Zend_Ldap_Exception $e) {
  876. if ($e->getCode() === Zend_Ldap_Exception::LDAP_NO_SUCH_OBJECT) return 0;
  877. else throw $e;
  878. }
  879. return $result->count();
  880. }
  881. /**
  882. * Count children for a given DN.
  883. *
  884. * @param string|Zend_Ldap_Dn $dn
  885. * @return integer
  886. * @throws Zend_Ldap_Exception
  887. */
  888. public function countChildren($dn)
  889. {
  890. return $this->count('(objectClass=*)', $dn, self::SEARCH_SCOPE_ONE);
  891. }
  892. /**
  893. * Check if a given DN exists.
  894. *
  895. * @param string|Zend_Ldap_Dn $dn
  896. * @return boolean
  897. * @throws Zend_Ldap_Exception
  898. */
  899. public function exists($dn)
  900. {
  901. return ($this->count('(objectClass=*)', $dn, self::SEARCH_SCOPE_BASE) == 1);
  902. }
  903. /**
  904. * Search LDAP registry for entries matching filter and optional attributes
  905. *
  906. * @param string|Zend_Ldap_Filter_Abstract $filter
  907. * @param string|Zend_Ldap_Dn $basedn
  908. * @param integer $scope
  909. * @param array $attributes
  910. * @param string $sort
  911. * @return array
  912. * @throws Zend_Ldap_Exception
  913. */
  914. public function searchEntries($filter, $basedn = null, $scope = self::SEARCH_SCOPE_SUB,
  915. array $attributes = array(), $sort = null)
  916. {
  917. $result = $this->search($filter, $basedn, $scope, $attributes, $sort);
  918. return $result->toArray();
  919. }
  920. /**
  921. * Get LDAP entry by DN
  922. *
  923. * @param string|Zend_Ldap_Dn $dn
  924. * @param array $attributes
  925. * @param boolean $throwOnNotFound
  926. * @return array
  927. * @throws Zend_Ldap_Exception
  928. */
  929. public function getEntry($dn, array $attributes = array(), $throwOnNotFound = false)
  930. {
  931. try {
  932. $result = $this->search("(objectClass=*)", $dn, self::SEARCH_SCOPE_BASE,
  933. $attributes, null);
  934. return $result->getFirst();
  935. } catch (Zend_Ldap_Exception $e){
  936. if ($throwOnNotFound !== false) throw $e;
  937. }
  938. return null;
  939. }
  940. /**
  941. * Prepares an ldap data entry array for insert/update operation
  942. *
  943. * @param array $entry
  944. * @return void
  945. * @throws InvalidArgumentException
  946. */
  947. public static function prepareLdapEntryArray(array &$entry)
  948. {
  949. if (array_key_exists('dn', $entry)) unset($entry['dn']);
  950. foreach ($entry as $key => $value) {
  951. if (is_array($value)) {
  952. foreach ($value as $i => $v) {
  953. if (is_null($v)) unset($value[$i]);
  954. else if (empty($v)) unset($value[$i]);
  955. else if (!is_scalar($v)) {
  956. throw new InvalidArgumentException('Only scalar values allowed in LDAP data');
  957. }
  958. }
  959. $entry[$key] = array_values($value);
  960. } else {
  961. if (is_null($value)) $entry[$key] = array();
  962. else if (empty($value)) $entry[$key] = array();
  963. else $entry[$key] = array($value);
  964. }
  965. }
  966. $entry = array_change_key_case($entry, CASE_LOWER);
  967. }
  968. /**
  969. * Add new information to the LDAP repository
  970. *
  971. * @param string|Zend_Ldap_Dn $dn
  972. * @param array $entry
  973. * @return Zend_Ldap *Provides a fluid interface*
  974. * @throws Zend_Ldap_Exception
  975. */
  976. public function add($dn, array $entry)
  977. {
  978. if (!($dn instanceof Zend_Ldap_Dn)) {
  979. $dn = Zend_Ldap_Dn::factory($dn, null);
  980. }
  981. self::prepareLdapEntryArray($entry);
  982. foreach ($entry as $key => $value) {
  983. if (is_array($value) && count($value) === 0) {
  984. unset($entry[$key]);
  985. }
  986. }
  987. $rdnParts = $dn->getRdn(Zend_Ldap_Dn::ATTR_CASEFOLD_LOWER);
  988. foreach ($rdnParts as $key => $value) {
  989. $value = Zend_Ldap_Dn::unescapeValue($value);
  990. if (!array_key_exists($key, $entry) ||
  991. !in_array($value, $entry[$key]) ||
  992. count($entry[$key]) !== 1) {
  993. $entry[$key] = array($value);
  994. }
  995. }
  996. $adAttributes = array('distinguishedname', 'instancetype', 'name', 'objectcategory',
  997. 'objectguid', 'usnchanged', 'usncreated', 'whenchanged', 'whencreated');
  998. foreach ($adAttributes as $attr) {
  999. if (array_key_exists($attr, $entry)) {
  1000. unset($entry[$attr]);
  1001. }
  1002. }
  1003. $isAdded = @ldap_add($this->getResource(), $dn->toString(), $entry);
  1004. if($isAdded === false) {
  1005. /**
  1006. * @see Zend_Ldap_Exception
  1007. */
  1008. require_once 'Zend/Ldap/Exception.php';
  1009. throw new Zend_Ldap_Exception($this, 'adding: ' . $dn->toString());
  1010. }
  1011. return $this;
  1012. }
  1013. /**
  1014. * Update LDAP registry
  1015. *
  1016. * @param string|Zend_Ldap_Dn $dn
  1017. * @param array $entry
  1018. * @return Zend_Ldap *Provides a fluid interface*
  1019. * @throws Zend_Ldap_Exception
  1020. */
  1021. public function update($dn, array $entry)
  1022. {
  1023. if (!($dn instanceof Zend_Ldap_Dn)) {
  1024. $dn = Zend_Ldap_Dn::factory($dn, null);
  1025. }
  1026. self::prepareLdapEntryArray($entry);
  1027. $rdnParts = $dn->getRdn(Zend_Ldap_Dn::ATTR_CASEFOLD_LOWER);
  1028. $adAttributes = array('distinguishedname', 'instancetype', 'name', 'objectcategory',
  1029. 'objectguid', 'usnchanged', 'usncreated', 'whenchanged', 'whencreated');
  1030. $stripAttributes = array_merge(array_keys($rdnParts), $adAttributes);
  1031. foreach ($stripAttributes as $attr) {
  1032. if (array_key_exists($attr, $entry)) {
  1033. unset($entry[$attr]);
  1034. }
  1035. }
  1036. if (count($entry) > 0) {
  1037. $isModified = @ldap_modify($this->getResource(), $dn->toString(), $entry);
  1038. if($isModified === false) {
  1039. /**
  1040. * @see Zend_Ldap_Exception
  1041. */
  1042. require_once 'Zend/Ldap/Exception.php';
  1043. throw new Zend_Ldap_Exception($this, 'updating: ' . $dn->toString());
  1044. }
  1045. }
  1046. return $this;
  1047. }
  1048. /**
  1049. * Save entry to LDAP registry.
  1050. *
  1051. * Internally decides if entry will be updated to added by calling
  1052. * {@link exists()}.
  1053. *
  1054. * @param string|Zend_Ldap_Dn $dn
  1055. * @param array $entry
  1056. * @return Zend_Ldap *Provides a fluid interface*
  1057. * @throws Zend_Ldap_Exception
  1058. */
  1059. public function save($dn, array $entry)
  1060. {
  1061. if ($dn instanceof Zend_Ldap_Dn) {
  1062. $dn = $dn->toString();
  1063. }
  1064. if ($this->exists($dn)) $this->update($dn, $entry);
  1065. else $this->add($dn, $entry);
  1066. return $this;
  1067. }
  1068. /**
  1069. * Delete an LDAP entry
  1070. *
  1071. * @param string|Zend_Ldap_Dn $dn
  1072. * @param boolean $recursively
  1073. * @return Zend_Ldap *Provides a fluid interface*
  1074. * @throws Zend_Ldap_Exception
  1075. */
  1076. public function delete($dn, $recursively = false)
  1077. {
  1078. if ($dn instanceof Zend_Ldap_Dn) {
  1079. $dn = $dn->toString();
  1080. }
  1081. if ($recursively === true) {
  1082. if ($this->countChildren($dn)>0) {
  1083. $children = $this->_getChildrenDns($dn);
  1084. foreach ($children as $c) {
  1085. $this->delete($c, true);
  1086. }
  1087. }
  1088. }
  1089. $isDeleted = @ldap_delete($this->getResource(), $dn);
  1090. if($isDeleted === false) {
  1091. /**
  1092. * @see Zend_Ldap_Exception
  1093. */
  1094. require_once 'Zend/Ldap/Exception.php';
  1095. throw new Zend_Ldap_Exception($this, 'deleting: ' . $dn);
  1096. }
  1097. return $this;
  1098. }
  1099. /**
  1100. * Retrieve the immediate children DNs of the given $parentDn
  1101. *
  1102. * This method is used in recursive methods like {@see delete()}
  1103. * or {@see copy()}
  1104. *
  1105. * @param string| $parentDn
  1106. * @return array of DNs
  1107. */
  1108. protected function _getChildrenDns($parentDn)
  1109. {
  1110. if ($parentDn instanceof Zend_Ldap_Dn) {
  1111. $parentDn = $parentDn->toString();
  1112. }
  1113. $children = array();
  1114. $search = @ldap_list($this->getResource(), $parentDn, '(objectClass=*)', array('dn'));
  1115. for ($entry = @ldap_first_entry($this->getResource(), $search);
  1116. $entry !== false;
  1117. $entry = @ldap_next_entry($this->getResource(), $entry)) {
  1118. $childDn = @ldap_get_dn($this->getResource(), $entry);
  1119. if ($childDn === false) {
  1120. /**
  1121. * @see Zend_Ldap_Exception
  1122. */
  1123. require_once 'Zend/Ldap/Exception.php';
  1124. throw new Zend_Ldap_Exception($this, 'getting dn');
  1125. }
  1126. $children[] = $childDn;
  1127. }
  1128. @ldap_free_result($search);
  1129. return $children;
  1130. }
  1131. /**
  1132. * Moves a LDAP entry from one DN to another subtree.
  1133. *
  1134. * @param string|Zend_Ldap_Dn $from
  1135. * @param string|Zend_Ldap_Dn $to
  1136. * @param boolean $recursively
  1137. * @param boolean $alwaysEmulate
  1138. * @return Zend_Ldap *Provides a fluid interface*
  1139. * @throws Zend_Ldap_Exception
  1140. */
  1141. public function moveToSubtree($from, $to, $recursively = false, $alwaysEmulate = false)
  1142. {
  1143. if ($from instanceof Zend_Ldap_Dn) {
  1144. $orgDnParts = $from->toArray();
  1145. } else {
  1146. $orgDnParts = Zend_Ldap_Dn::explodeDn($from);
  1147. }
  1148. if ($to instanceof Zend_Ldap_Dn) {
  1149. $newParentDnParts = $to->toArray();
  1150. } else {
  1151. $newParentDnParts = Zend_Ldap_Dn::explodeDn($to);
  1152. }
  1153. $newDnParts = array_merge(array(array_shift($orgDnParts)), $newParentDnParts);
  1154. $newDn = Zend_Ldap_Dn::fromArray($newDnParts);
  1155. return $this->rename($from, $newDn, $recursively, $alwaysEmulate);
  1156. }
  1157. /**
  1158. * Moves a LDAP entry from one DN to another DN.
  1159. *
  1160. * This is an alias for {@link rename()}
  1161. *
  1162. * @param string|Zend_Ldap_Dn $from
  1163. * @param string|Zend_Ldap_Dn $to
  1164. * @param boolean $recursively
  1165. * @param boolean $alwaysEmulate
  1166. * @return Zend_Ldap *Provides a fluid interface*
  1167. * @throws Zend_Ldap_Exception
  1168. */
  1169. public function move($from, $to, $recursively = false, $alwaysEmulate = false)
  1170. {
  1171. return $this->rename($from, $to, $recursively, $alwaysEmulate);
  1172. }
  1173. /**
  1174. * Renames a LDAP entry from one DN to another DN.
  1175. *
  1176. * This method implicitely moves the entry to another location within the tree.
  1177. *
  1178. * @param string|Zend_Ldap_Dn $from
  1179. * @param string|Zend_Ldap_Dn $to
  1180. * @param boolean $recursively
  1181. * @param boolean $alwaysEmulate
  1182. * @return Zend_Ldap *Provides a fluid interface*
  1183. * @throws Zend_Ldap_Exception
  1184. */
  1185. public function rename($from, $to, $recursively = false, $alwaysEmulate = false)
  1186. {
  1187. $emulate = (bool)$alwaysEmulate;
  1188. if (!function_exists('ldap_rename')) $emulate = true;
  1189. else if ($recursively) $emulate = true;
  1190. if ($emulate === false) {
  1191. if ($from instanceof Zend_Ldap_Dn) {
  1192. $from = $from->toString();
  1193. }
  1194. if ($to instanceof Zend_Ldap_Dn) {
  1195. $newDnParts = $to->toArray();
  1196. } else {
  1197. $newDnParts = Zend_Ldap_Dn::explodeDn($to);
  1198. }
  1199. $newRdn = Zend_Ldap_Dn::implodeRdn(array_shift($newDnParts));
  1200. $newParent = Zend_Ldap_Dn::implodeDn($newDnParts);
  1201. $isOK = @ldap_rename($this->getResource(), $from, $newRdn, $newParent, true);
  1202. if($isOK === false) {
  1203. /**
  1204. * @see Zend_Ldap_Exception
  1205. */
  1206. require_once 'Zend/Ldap/Exception.php';
  1207. throw new Zend_Ldap_Exception($this, 'renaming ' . $from . ' to ' . $to);
  1208. }
  1209. else if (!$this->exists($to)) $emulate = true;
  1210. }
  1211. if ($emulate) {
  1212. $this->copy($from, $to, $recursively);
  1213. $this->delete($from, $recursively);
  1214. }
  1215. return $this;
  1216. }
  1217. /**
  1218. * Copies a LDAP entry from one DN to another subtree.
  1219. *
  1220. * @param string|Zend_Ldap_Dn $from
  1221. * @param string|Zend_Ldap_Dn $to
  1222. * @param boolean $recursively
  1223. * @return Zend_Ldap *Provides a fluid interface*
  1224. * @throws Zend_Ldap_Exception
  1225. */
  1226. public function copyToSubtree($from, $to, $recursively = false)
  1227. {
  1228. if ($from instanceof Zend_Ldap_Dn) {
  1229. $orgDnParts = $from->toArray();
  1230. } else {
  1231. $orgDnParts = Zend_Ldap_Dn::explodeDn($from);
  1232. }
  1233. if ($to instanceof Zend_Ldap_Dn) {
  1234. $newParentDnParts = $to->toArray();
  1235. } else {
  1236. $newParentDnParts = Zend_Ldap_Dn::explodeDn($to);
  1237. }
  1238. $newDnParts = array_merge(array(array_shift($orgDnParts)), $newParentDnParts);
  1239. $newDn = Zend_Ldap_Dn::fromArray($newDnParts);
  1240. return $this->copy($from, $newDn, $recursively);
  1241. }
  1242. /**
  1243. * Copies a LDAP entry from one DN to another DN.
  1244. *
  1245. * @param string|Zend_Ldap_Dn $from
  1246. * @param string|Zend_Ldap_Dn $to
  1247. * @param boolean $recursively
  1248. * @return Zend_Ldap *Provides a fluid interface*
  1249. * @throws Zend_Ldap_Exception
  1250. */
  1251. public function copy($from, $to, $recursively = false)
  1252. {
  1253. $entry = $this->getEntry($from, array(), true);
  1254. if ($to instanceof Zend_Ldap_Dn) {
  1255. $toDnParts = $to->toArray();
  1256. } else {
  1257. $toDnParts = Zend_Ldap_Dn::explodeDn($to);
  1258. }
  1259. $this->add($to, $entry);
  1260. if ($recursively === true && $this->countChildren($from)>0) {
  1261. $children = $this->_getChildrenDns($from);
  1262. foreach ($children as $c) {
  1263. $cDnParts = Zend_Ldap_Dn::explodeDn($c);
  1264. $newChildParts = array_merge(array(array_shift($cDnParts)), $toDnParts);
  1265. $newChild = Zend_Ldap_Dn::implodeDn($newChildParts);
  1266. $this->copy($c, $newChild, true);
  1267. }
  1268. }
  1269. return $this;
  1270. }
  1271. /**
  1272. * Returns the specified DN as a Zend_Ldap_Node
  1273. *
  1274. * @param string|Zend_Ldap_Dn $dn
  1275. * @return Zend_Ldap_Node
  1276. * @throws Zend_Ldap_Exception
  1277. */
  1278. public function getNode($dn)
  1279. {
  1280. /**
  1281. * Zend_Ldap_Node
  1282. */
  1283. require_once 'Zend/Ldap/Node.php';
  1284. return Zend_Ldap_Node::fromLdap($dn, $this);
  1285. }
  1286. /**
  1287. * Returns the base node as a Zend_Ldap_Node
  1288. *
  1289. * @return Zend_Ldap_Node
  1290. * @throws Zend_Ldap_Exception
  1291. */
  1292. public function getBaseNode()
  1293. {
  1294. return $this->getNode($this->getBaseDn(), $this);
  1295. }
  1296. /**
  1297. * Returns the RootDSE
  1298. *
  1299. * @return Zend_Ldap_Node_RootDse
  1300. * @throws Zend_Ldap_Exception
  1301. */
  1302. public function getRootDse()
  1303. {
  1304. if ($this->_rootDse === null) {
  1305. /**
  1306. * @see Zend_Ldap_Node_Schema
  1307. */
  1308. require_once 'Zend/Ldap/Node/RootDse.php';
  1309. $this->_rootDse = Zend_Ldap_Node_RootDse::create($this);
  1310. }
  1311. return $this->_rootDse;
  1312. }
  1313. /**
  1314. * Returns the schema
  1315. *
  1316. * @return Zend_Ldap_Node_Schema
  1317. * @throws Zend_Ldap_Exception
  1318. */
  1319. public function getSchema()
  1320. {
  1321. if ($this->_schema === null) {
  1322. /**
  1323. * @see Zend_Ldap_Node_Schema
  1324. */
  1325. require_once 'Zend/Ldap/Node/Schema.php';
  1326. $this->_schema = Zend_Ldap_Node_Schema::create($this);
  1327. }
  1328. return $this->_schema;
  1329. }
  1330. }