Ldap.php 50 KB

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