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