Ldap.php 50 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560
  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 ($username === null) {
  750. /* Perform anonymous bind
  751. */
  752. $password = null;
  753. } else {
  754. /* Check to make sure the username is in DN form.
  755. */
  756. /**
  757. * @see Zend_Ldap_Dn
  758. */
  759. require_once 'Zend/Ldap/Dn.php';
  760. if (!Zend_Ldap_Dn::checkDn($username)) {
  761. if ($this->_getBindRequiresDn()) {
  762. /* moreCreds stops an infinite loop if _getUsername does not
  763. * return a DN and the bind requires it
  764. */
  765. if ($moreCreds) {
  766. try {
  767. $username = $this->_getAccountDn($username);
  768. } catch (Zend_Ldap_Exception $zle) {
  769. switch ($zle->getCode()) {
  770. case Zend_Ldap_Exception::LDAP_NO_SUCH_OBJECT:
  771. case Zend_Ldap_Exception::LDAP_X_DOMAIN_MISMATCH:
  772. case Zend_Ldap_Exception::LDAP_X_EXTENSION_NOT_LOADED:
  773. throw $zle;
  774. }
  775. throw new Zend_Ldap_Exception(null,
  776. 'Failed to retrieve DN for account: ' . $username .
  777. ' [' . $zle->getMessage() . ']',
  778. Zend_Ldap_Exception::LDAP_OPERATIONS_ERROR);
  779. }
  780. } else {
  781. /**
  782. * @see Zend_Ldap_Exception
  783. */
  784. require_once 'Zend/Ldap/Exception.php';
  785. throw new Zend_Ldap_Exception(null, 'Binding requires username in DN form');
  786. }
  787. } else {
  788. $username = $this->getCanonicalAccountName($username,
  789. $this->_getAccountCanonicalForm());
  790. }
  791. }
  792. }
  793. if (!is_resource($this->_resource)) {
  794. $this->connect();
  795. }
  796. if ($username !== null && $password === '' && $this->_getAllowEmptyPassword() !== true) {
  797. /**
  798. * @see Zend_Ldap_Exception
  799. */
  800. require_once 'Zend/Ldap/Exception.php';
  801. $zle = new Zend_Ldap_Exception(null,
  802. 'Empty password not allowed - see allowEmptyPassword option.');
  803. } else {
  804. if (@ldap_bind($this->_resource, $username, $password)) {
  805. $this->_boundUser = $username;
  806. return $this;
  807. }
  808. $message = ($username === null) ? $this->_connectString : $username;
  809. /**
  810. * @see Zend_Ldap_Exception
  811. */
  812. require_once 'Zend/Ldap/Exception.php';
  813. switch ($this->getLastErrorCode()) {
  814. case Zend_Ldap_Exception::LDAP_SERVER_DOWN:
  815. /* If the error is related to establishing a connection rather than binding,
  816. * the connect string is more informative than the username.
  817. */
  818. $message = $this->_connectString;
  819. }
  820. $zle = new Zend_Ldap_Exception($this, $message);
  821. }
  822. $this->disconnect();
  823. throw $zle;
  824. }
  825. /**
  826. * A global LDAP search routine for finding information.
  827. *
  828. * Options can be either passed as single parameters according to the
  829. * method signature or as an array with one or more of the following keys
  830. * - filter
  831. * - baseDn
  832. * - scope
  833. * - attributes
  834. * - sort
  835. * - collectionClass
  836. *
  837. * @param string|Zend_Ldap_Filter_Abstract|array $filter
  838. * @param string|Zend_Ldap_Dn|null $basedn
  839. * @param integer $scope
  840. * @param array $attributes
  841. * @param string|null $sort
  842. * @param string|null $collectionClass
  843. * @return Zend_Ldap_Collection
  844. * @throws Zend_Ldap_Exception
  845. */
  846. public function search($filter, $basedn = null, $scope = self::SEARCH_SCOPE_SUB,
  847. array $attributes = array(), $sort = null, $collectionClass = null)
  848. {
  849. if (is_array($filter)) {
  850. $options = array_change_key_case($filter, CASE_LOWER);
  851. foreach ($options as $key => $value) {
  852. switch ($key) {
  853. case 'filter':
  854. case 'basedn':
  855. case 'scope':
  856. case 'sort':
  857. $$key = $value;
  858. break;
  859. case 'attributes':
  860. if (is_array($value)) {
  861. $attributes = $value;
  862. }
  863. break;
  864. case 'collectionclass':
  865. $collectionClass = $value;
  866. break;
  867. }
  868. }
  869. }
  870. if ($basedn === null) {
  871. $basedn = $this->getBaseDn();
  872. }
  873. else if ($basedn instanceof Zend_Ldap_Dn) {
  874. $basedn = $basedn->toString();
  875. }
  876. if ($filter instanceof Zend_Ldap_Filter_Abstract) {
  877. $filter = $filter->toString();
  878. }
  879. switch ($scope) {
  880. case self::SEARCH_SCOPE_ONE:
  881. $search = @ldap_list($this->getResource(), $basedn, $filter, $attributes);
  882. break;
  883. case self::SEARCH_SCOPE_BASE:
  884. $search = @ldap_read($this->getResource(), $basedn, $filter, $attributes);
  885. break;
  886. case self::SEARCH_SCOPE_SUB:
  887. default:
  888. $search = @ldap_search($this->getResource(), $basedn, $filter, $attributes);
  889. break;
  890. }
  891. if($search === false) {
  892. /**
  893. * @see Zend_Ldap_Exception
  894. */
  895. require_once 'Zend/Ldap/Exception.php';
  896. throw new Zend_Ldap_Exception($this, 'searching: ' . $filter);
  897. }
  898. if (!is_null($sort) && is_string($sort)) {
  899. $isSorted = @ldap_sort($this->getResource(), $search, $sort);
  900. if($isSorted === false) {
  901. /**
  902. * @see Zend_Ldap_Exception
  903. */
  904. require_once 'Zend/Ldap/Exception.php';
  905. throw new Zend_Ldap_Exception($this, 'sorting: ' . $sort);
  906. }
  907. }
  908. /**
  909. * Zend_Ldap_Collection_Iterator_Default
  910. */
  911. require_once 'Zend/Ldap/Collection/Iterator/Default.php';
  912. $iterator = new Zend_Ldap_Collection_Iterator_Default($this, $search);
  913. if ($collectionClass === null) {
  914. /**
  915. * Zend_Ldap_Collection
  916. */
  917. require_once 'Zend/Ldap/Collection.php';
  918. return new Zend_Ldap_Collection($iterator);
  919. } else {
  920. $collectionClass = (string)$collectionClass;
  921. if (!class_exists($collectionClass)) {
  922. /**
  923. * @see Zend_Ldap_Exception
  924. */
  925. require_once 'Zend/Ldap/Exception.php';
  926. throw new Zend_Ldap_Exception(null,
  927. "Class '$collectionClass' can not be found");
  928. }
  929. if (!is_subclass_of($collectionClass, 'Zend_Ldap_Collection')) {
  930. /**
  931. * @see Zend_Ldap_Exception
  932. */
  933. require_once 'Zend/Ldap/Exception.php';
  934. throw new Zend_Ldap_Exception(null,
  935. "Class '$collectionClass' must subclass 'Zend_Ldap_Collection'");
  936. }
  937. return new $collectionClass($iterator);
  938. }
  939. }
  940. /**
  941. * Count items found by given filter.
  942. *
  943. * @param string|Zend_Ldap_Filter_Abstract $filter
  944. * @param string|Zend_Ldap_Dn|null $basedn
  945. * @param integer $scope
  946. * @return integer
  947. * @throws Zend_Ldap_Exception
  948. */
  949. public function count($filter, $basedn = null, $scope = self::SEARCH_SCOPE_SUB)
  950. {
  951. try {
  952. $result = $this->search($filter, $basedn, $scope, array('dn'), null);
  953. } catch (Zend_Ldap_Exception $e) {
  954. if ($e->getCode() === Zend_Ldap_Exception::LDAP_NO_SUCH_OBJECT) return 0;
  955. else throw $e;
  956. }
  957. return $result->count();
  958. }
  959. /**
  960. * Count children for a given DN.
  961. *
  962. * @param string|Zend_Ldap_Dn $dn
  963. * @return integer
  964. * @throws Zend_Ldap_Exception
  965. */
  966. public function countChildren($dn)
  967. {
  968. return $this->count('(objectClass=*)', $dn, self::SEARCH_SCOPE_ONE);
  969. }
  970. /**
  971. * Check if a given DN exists.
  972. *
  973. * @param string|Zend_Ldap_Dn $dn
  974. * @return boolean
  975. * @throws Zend_Ldap_Exception
  976. */
  977. public function exists($dn)
  978. {
  979. return ($this->count('(objectClass=*)', $dn, self::SEARCH_SCOPE_BASE) == 1);
  980. }
  981. /**
  982. * Search LDAP registry for entries matching filter and optional attributes
  983. *
  984. * Options can be either passed as single parameters according to the
  985. * method signature or as an array with one or more of the following keys
  986. * - filter
  987. * - baseDn
  988. * - scope
  989. * - attributes
  990. * - sort
  991. * - reverseSort
  992. *
  993. * @param string|Zend_Ldap_Filter_Abstract|array $filter
  994. * @param string|Zend_Ldap_Dn|null $basedn
  995. * @param integer $scope
  996. * @param array $attributes
  997. * @param string|null $sort
  998. * @param boolean $reverseSort
  999. * @return array
  1000. * @throws Zend_Ldap_Exception
  1001. */
  1002. public function searchEntries($filter, $basedn = null, $scope = self::SEARCH_SCOPE_SUB,
  1003. array $attributes = array(), $sort = null, $reverseSort = false)
  1004. {
  1005. if (is_array($filter)) {
  1006. $filter = array_change_key_case($filter, CASE_LOWER);
  1007. if (isset($filter['collectionclass'])) {
  1008. unset($filter['collectionclass']);
  1009. }
  1010. if (isset($filter['reversesort'])) {
  1011. $reverseSort = $filter['reversesort'];
  1012. unset($filter['reversesort']);
  1013. }
  1014. }
  1015. $result = $this->search($filter, $basedn, $scope, $attributes, $sort);
  1016. $items = $result->toArray();
  1017. if ((bool)$reverseSort === true) {
  1018. $items = array_reverse($items, false);
  1019. }
  1020. return $items;
  1021. }
  1022. /**
  1023. * Get LDAP entry by DN
  1024. *
  1025. * @param string|Zend_Ldap_Dn $dn
  1026. * @param array $attributes
  1027. * @param boolean $throwOnNotFound
  1028. * @return array
  1029. * @throws Zend_Ldap_Exception
  1030. */
  1031. public function getEntry($dn, array $attributes = array(), $throwOnNotFound = false)
  1032. {
  1033. try {
  1034. $result = $this->search("(objectClass=*)", $dn, self::SEARCH_SCOPE_BASE,
  1035. $attributes, null);
  1036. return $result->getFirst();
  1037. } catch (Zend_Ldap_Exception $e){
  1038. if ($throwOnNotFound !== false) throw $e;
  1039. }
  1040. return null;
  1041. }
  1042. /**
  1043. * Prepares an ldap data entry array for insert/update operation
  1044. *
  1045. * @param array $entry
  1046. * @return void
  1047. * @throws InvalidArgumentException
  1048. */
  1049. public static function prepareLdapEntryArray(array &$entry)
  1050. {
  1051. if (array_key_exists('dn', $entry)) unset($entry['dn']);
  1052. foreach ($entry as $key => $value) {
  1053. if (is_array($value)) {
  1054. foreach ($value as $i => $v) {
  1055. if (is_null($v)) unset($value[$i]);
  1056. else if (!is_scalar($v)) {
  1057. throw new InvalidArgumentException('Only scalar values allowed in LDAP data');
  1058. } else {
  1059. $v = (string)$v;
  1060. if (strlen($v) == 0) {
  1061. unset($value[$i]);
  1062. } else {
  1063. $value[$i] = $v;
  1064. }
  1065. }
  1066. }
  1067. $entry[$key] = array_values($value);
  1068. } else {
  1069. if (is_null($value)) $entry[$key] = array();
  1070. else if (!is_scalar($value)) {
  1071. throw new InvalidArgumentException('Only scalar values allowed in LDAP data');
  1072. } else {
  1073. $value = (string)$value;
  1074. if (strlen($value) == 0) {
  1075. $entry[$key] = array();
  1076. } else {
  1077. $entry[$key] = array($value);
  1078. }
  1079. }
  1080. }
  1081. }
  1082. $entry = array_change_key_case($entry, CASE_LOWER);
  1083. }
  1084. /**
  1085. * Add new information to the LDAP repository
  1086. *
  1087. * @param string|Zend_Ldap_Dn $dn
  1088. * @param array $entry
  1089. * @return Zend_Ldap Provides a fluid interface
  1090. * @throws Zend_Ldap_Exception
  1091. */
  1092. public function add($dn, array $entry)
  1093. {
  1094. if (!($dn instanceof Zend_Ldap_Dn)) {
  1095. $dn = Zend_Ldap_Dn::factory($dn, null);
  1096. }
  1097. self::prepareLdapEntryArray($entry);
  1098. foreach ($entry as $key => $value) {
  1099. if (is_array($value) && count($value) === 0) {
  1100. unset($entry[$key]);
  1101. }
  1102. }
  1103. $rdnParts = $dn->getRdn(Zend_Ldap_Dn::ATTR_CASEFOLD_LOWER);
  1104. foreach ($rdnParts as $key => $value) {
  1105. $value = Zend_Ldap_Dn::unescapeValue($value);
  1106. if (!array_key_exists($key, $entry) ||
  1107. !in_array($value, $entry[$key]) ||
  1108. count($entry[$key]) !== 1) {
  1109. $entry[$key] = array($value);
  1110. }
  1111. }
  1112. $adAttributes = array('distinguishedname', 'instancetype', 'name', 'objectcategory',
  1113. 'objectguid', 'usnchanged', 'usncreated', 'whenchanged', 'whencreated');
  1114. foreach ($adAttributes as $attr) {
  1115. if (array_key_exists($attr, $entry)) {
  1116. unset($entry[$attr]);
  1117. }
  1118. }
  1119. $isAdded = @ldap_add($this->getResource(), $dn->toString(), $entry);
  1120. if($isAdded === false) {
  1121. /**
  1122. * @see Zend_Ldap_Exception
  1123. */
  1124. require_once 'Zend/Ldap/Exception.php';
  1125. throw new Zend_Ldap_Exception($this, 'adding: ' . $dn->toString());
  1126. }
  1127. return $this;
  1128. }
  1129. /**
  1130. * Update LDAP registry
  1131. *
  1132. * @param string|Zend_Ldap_Dn $dn
  1133. * @param array $entry
  1134. * @return Zend_Ldap Provides a fluid interface
  1135. * @throws Zend_Ldap_Exception
  1136. */
  1137. public function update($dn, array $entry)
  1138. {
  1139. if (!($dn instanceof Zend_Ldap_Dn)) {
  1140. $dn = Zend_Ldap_Dn::factory($dn, null);
  1141. }
  1142. self::prepareLdapEntryArray($entry);
  1143. $rdnParts = $dn->getRdn(Zend_Ldap_Dn::ATTR_CASEFOLD_LOWER);
  1144. $adAttributes = array('distinguishedname', 'instancetype', 'name', 'objectcategory',
  1145. 'objectguid', 'usnchanged', 'usncreated', 'whenchanged', 'whencreated');
  1146. $stripAttributes = array_merge(array_keys($rdnParts), $adAttributes);
  1147. foreach ($stripAttributes as $attr) {
  1148. if (array_key_exists($attr, $entry)) {
  1149. unset($entry[$attr]);
  1150. }
  1151. }
  1152. if (count($entry) > 0) {
  1153. $isModified = @ldap_modify($this->getResource(), $dn->toString(), $entry);
  1154. if($isModified === false) {
  1155. /**
  1156. * @see Zend_Ldap_Exception
  1157. */
  1158. require_once 'Zend/Ldap/Exception.php';
  1159. throw new Zend_Ldap_Exception($this, 'updating: ' . $dn->toString());
  1160. }
  1161. }
  1162. return $this;
  1163. }
  1164. /**
  1165. * Save entry to LDAP registry.
  1166. *
  1167. * Internally decides if entry will be updated to added by calling
  1168. * {@link exists()}.
  1169. *
  1170. * @param string|Zend_Ldap_Dn $dn
  1171. * @param array $entry
  1172. * @return Zend_Ldap Provides a fluid interface
  1173. * @throws Zend_Ldap_Exception
  1174. */
  1175. public function save($dn, array $entry)
  1176. {
  1177. if ($dn instanceof Zend_Ldap_Dn) {
  1178. $dn = $dn->toString();
  1179. }
  1180. if ($this->exists($dn)) $this->update($dn, $entry);
  1181. else $this->add($dn, $entry);
  1182. return $this;
  1183. }
  1184. /**
  1185. * Delete an LDAP entry
  1186. *
  1187. * @param string|Zend_Ldap_Dn $dn
  1188. * @param boolean $recursively
  1189. * @return Zend_Ldap Provides a fluid interface
  1190. * @throws Zend_Ldap_Exception
  1191. */
  1192. public function delete($dn, $recursively = false)
  1193. {
  1194. if ($dn instanceof Zend_Ldap_Dn) {
  1195. $dn = $dn->toString();
  1196. }
  1197. if ($recursively === true) {
  1198. if ($this->countChildren($dn)>0) {
  1199. $children = $this->_getChildrenDns($dn);
  1200. foreach ($children as $c) {
  1201. $this->delete($c, true);
  1202. }
  1203. }
  1204. }
  1205. $isDeleted = @ldap_delete($this->getResource(), $dn);
  1206. if($isDeleted === false) {
  1207. /**
  1208. * @see Zend_Ldap_Exception
  1209. */
  1210. require_once 'Zend/Ldap/Exception.php';
  1211. throw new Zend_Ldap_Exception($this, 'deleting: ' . $dn);
  1212. }
  1213. return $this;
  1214. }
  1215. /**
  1216. * Retrieve the immediate children DNs of the given $parentDn
  1217. *
  1218. * This method is used in recursive methods like {@see delete()}
  1219. * or {@see copy()}
  1220. *
  1221. * @param string|Zend_Ldap_Dn $parentDn
  1222. * @return array of DNs
  1223. */
  1224. protected function _getChildrenDns($parentDn)
  1225. {
  1226. if ($parentDn instanceof Zend_Ldap_Dn) {
  1227. $parentDn = $parentDn->toString();
  1228. }
  1229. $children = array();
  1230. $search = @ldap_list($this->getResource(), $parentDn, '(objectClass=*)', array('dn'));
  1231. for ($entry = @ldap_first_entry($this->getResource(), $search);
  1232. $entry !== false;
  1233. $entry = @ldap_next_entry($this->getResource(), $entry)) {
  1234. $childDn = @ldap_get_dn($this->getResource(), $entry);
  1235. if ($childDn === false) {
  1236. /**
  1237. * @see Zend_Ldap_Exception
  1238. */
  1239. require_once 'Zend/Ldap/Exception.php';
  1240. throw new Zend_Ldap_Exception($this, 'getting dn');
  1241. }
  1242. $children[] = $childDn;
  1243. }
  1244. @ldap_free_result($search);
  1245. return $children;
  1246. }
  1247. /**
  1248. * Moves a LDAP entry from one DN to another subtree.
  1249. *
  1250. * @param string|Zend_Ldap_Dn $from
  1251. * @param string|Zend_Ldap_Dn $to
  1252. * @param boolean $recursively
  1253. * @param boolean $alwaysEmulate
  1254. * @return Zend_Ldap Provides a fluid interface
  1255. * @throws Zend_Ldap_Exception
  1256. */
  1257. public function moveToSubtree($from, $to, $recursively = false, $alwaysEmulate = false)
  1258. {
  1259. if ($from instanceof Zend_Ldap_Dn) {
  1260. $orgDnParts = $from->toArray();
  1261. } else {
  1262. $orgDnParts = Zend_Ldap_Dn::explodeDn($from);
  1263. }
  1264. if ($to instanceof Zend_Ldap_Dn) {
  1265. $newParentDnParts = $to->toArray();
  1266. } else {
  1267. $newParentDnParts = Zend_Ldap_Dn::explodeDn($to);
  1268. }
  1269. $newDnParts = array_merge(array(array_shift($orgDnParts)), $newParentDnParts);
  1270. $newDn = Zend_Ldap_Dn::fromArray($newDnParts);
  1271. return $this->rename($from, $newDn, $recursively, $alwaysEmulate);
  1272. }
  1273. /**
  1274. * Moves a LDAP entry from one DN to another DN.
  1275. *
  1276. * This is an alias for {@link rename()}
  1277. *
  1278. * @param string|Zend_Ldap_Dn $from
  1279. * @param string|Zend_Ldap_Dn $to
  1280. * @param boolean $recursively
  1281. * @param boolean $alwaysEmulate
  1282. * @return Zend_Ldap Provides a fluid interface
  1283. * @throws Zend_Ldap_Exception
  1284. */
  1285. public function move($from, $to, $recursively = false, $alwaysEmulate = false)
  1286. {
  1287. return $this->rename($from, $to, $recursively, $alwaysEmulate);
  1288. }
  1289. /**
  1290. * Renames a LDAP entry from one DN to another DN.
  1291. *
  1292. * This method implicitely moves the entry to another location within the tree.
  1293. *
  1294. * @param string|Zend_Ldap_Dn $from
  1295. * @param string|Zend_Ldap_Dn $to
  1296. * @param boolean $recursively
  1297. * @param boolean $alwaysEmulate
  1298. * @return Zend_Ldap Provides a fluid interface
  1299. * @throws Zend_Ldap_Exception
  1300. */
  1301. public function rename($from, $to, $recursively = false, $alwaysEmulate = false)
  1302. {
  1303. $emulate = (bool)$alwaysEmulate;
  1304. if (!function_exists('ldap_rename')) $emulate = true;
  1305. else if ($recursively) $emulate = true;
  1306. if ($emulate === false) {
  1307. if ($from instanceof Zend_Ldap_Dn) {
  1308. $from = $from->toString();
  1309. }
  1310. if ($to instanceof Zend_Ldap_Dn) {
  1311. $newDnParts = $to->toArray();
  1312. } else {
  1313. $newDnParts = Zend_Ldap_Dn::explodeDn($to);
  1314. }
  1315. $newRdn = Zend_Ldap_Dn::implodeRdn(array_shift($newDnParts));
  1316. $newParent = Zend_Ldap_Dn::implodeDn($newDnParts);
  1317. $isOK = @ldap_rename($this->getResource(), $from, $newRdn, $newParent, true);
  1318. if($isOK === false) {
  1319. /**
  1320. * @see Zend_Ldap_Exception
  1321. */
  1322. require_once 'Zend/Ldap/Exception.php';
  1323. throw new Zend_Ldap_Exception($this, 'renaming ' . $from . ' to ' . $to);
  1324. }
  1325. else if (!$this->exists($to)) $emulate = true;
  1326. }
  1327. if ($emulate) {
  1328. $this->copy($from, $to, $recursively);
  1329. $this->delete($from, $recursively);
  1330. }
  1331. return $this;
  1332. }
  1333. /**
  1334. * Copies a LDAP entry from one DN to another subtree.
  1335. *
  1336. * @param string|Zend_Ldap_Dn $from
  1337. * @param string|Zend_Ldap_Dn $to
  1338. * @param boolean $recursively
  1339. * @return Zend_Ldap Provides a fluid interface
  1340. * @throws Zend_Ldap_Exception
  1341. */
  1342. public function copyToSubtree($from, $to, $recursively = false)
  1343. {
  1344. if ($from instanceof Zend_Ldap_Dn) {
  1345. $orgDnParts = $from->toArray();
  1346. } else {
  1347. $orgDnParts = Zend_Ldap_Dn::explodeDn($from);
  1348. }
  1349. if ($to instanceof Zend_Ldap_Dn) {
  1350. $newParentDnParts = $to->toArray();
  1351. } else {
  1352. $newParentDnParts = Zend_Ldap_Dn::explodeDn($to);
  1353. }
  1354. $newDnParts = array_merge(array(array_shift($orgDnParts)), $newParentDnParts);
  1355. $newDn = Zend_Ldap_Dn::fromArray($newDnParts);
  1356. return $this->copy($from, $newDn, $recursively);
  1357. }
  1358. /**
  1359. * Copies a LDAP entry from one DN to another DN.
  1360. *
  1361. * @param string|Zend_Ldap_Dn $from
  1362. * @param string|Zend_Ldap_Dn $to
  1363. * @param boolean $recursively
  1364. * @return Zend_Ldap Provides a fluid interface
  1365. * @throws Zend_Ldap_Exception
  1366. */
  1367. public function copy($from, $to, $recursively = false)
  1368. {
  1369. $entry = $this->getEntry($from, array(), true);
  1370. if ($to instanceof Zend_Ldap_Dn) {
  1371. $toDnParts = $to->toArray();
  1372. } else {
  1373. $toDnParts = Zend_Ldap_Dn::explodeDn($to);
  1374. }
  1375. $this->add($to, $entry);
  1376. if ($recursively === true && $this->countChildren($from)>0) {
  1377. $children = $this->_getChildrenDns($from);
  1378. foreach ($children as $c) {
  1379. $cDnParts = Zend_Ldap_Dn::explodeDn($c);
  1380. $newChildParts = array_merge(array(array_shift($cDnParts)), $toDnParts);
  1381. $newChild = Zend_Ldap_Dn::implodeDn($newChildParts);
  1382. $this->copy($c, $newChild, true);
  1383. }
  1384. }
  1385. return $this;
  1386. }
  1387. /**
  1388. * Returns the specified DN as a Zend_Ldap_Node
  1389. *
  1390. * @param string|Zend_Ldap_Dn $dn
  1391. * @return Zend_Ldap_Node|null
  1392. * @throws Zend_Ldap_Exception
  1393. */
  1394. public function getNode($dn)
  1395. {
  1396. /**
  1397. * Zend_Ldap_Node
  1398. */
  1399. require_once 'Zend/Ldap/Node.php';
  1400. return Zend_Ldap_Node::fromLdap($dn, $this);
  1401. }
  1402. /**
  1403. * Returns the base node as a Zend_Ldap_Node
  1404. *
  1405. * @return Zend_Ldap_Node
  1406. * @throws Zend_Ldap_Exception
  1407. */
  1408. public function getBaseNode()
  1409. {
  1410. return $this->getNode($this->getBaseDn(), $this);
  1411. }
  1412. /**
  1413. * Returns the RootDSE
  1414. *
  1415. * @return Zend_Ldap_Node_RootDse
  1416. * @throws Zend_Ldap_Exception
  1417. */
  1418. public function getRootDse()
  1419. {
  1420. if ($this->_rootDse === null) {
  1421. /**
  1422. * @see Zend_Ldap_Node_Schema
  1423. */
  1424. require_once 'Zend/Ldap/Node/RootDse.php';
  1425. $this->_rootDse = Zend_Ldap_Node_RootDse::create($this);
  1426. }
  1427. return $this->_rootDse;
  1428. }
  1429. /**
  1430. * Returns the schema
  1431. *
  1432. * @return Zend_Ldap_Node_Schema
  1433. * @throws Zend_Ldap_Exception
  1434. */
  1435. public function getSchema()
  1436. {
  1437. if ($this->_schema === null) {
  1438. /**
  1439. * @see Zend_Ldap_Node_Schema
  1440. */
  1441. require_once 'Zend/Ldap/Node/Schema.php';
  1442. $this->_schema = Zend_Ldap_Node_Schema::create($this);
  1443. }
  1444. return $this->_schema;
  1445. }
  1446. }