1 <?php
2 /**
3 * Part of the Joomla Framework Client Package
4 *
5 * @copyright Copyright (C) 2005 - 2013 Open Source Matters, Inc. All rights reserved.
6 * @license GNU General Public License version 2 or later; see LICENSE
7 */
8
9 namespace Joomla\Ldap;
10
11 /**
12 * LDAP client class
13 *
14 * @since 1.0
15 */
16 class LdapClient
17 {
18 /**
19 * @var string Hostname of LDAP server
20 * @since 1.0
21 */
22 public $host = null;
23
24 /**
25 * @var bool Authorization Method to use
26 * @since 1.0
27 */
28 public $auth_method = null;
29
30 /**
31 * @var int Port of LDAP server
32 * @since 1.0
33 */
34 public $port = null;
35
36 /**
37 * @var string Base DN (e.g. o=MyDir)
38 * @since 1.0
39 */
40 public $base_dn = null;
41
42 /**
43 * @var string User DN (e.g. cn=Users,o=MyDir)
44 * @since 1.0
45 */
46 public $users_dn = null;
47
48 /**
49 * @var string Search String
50 * @since 1.0
51 */
52 public $search_string = null;
53
54 /**
55 * @var boolean Use LDAP Version 3
56 * @since 1.0
57 */
58 public $use_ldapV3 = null;
59
60 /**
61 * @var boolean No referrals (server transfers)
62 * @since 1.0
63 */
64 public $no_referrals = null;
65
66 /**
67 * @var boolean Negotiate TLS (encrypted communications)
68 * @since 1.0
69 */
70 public $negotiate_tls = null;
71
72 /**
73 * @var string Username to connect to server
74 * @since 1.0
75 */
76 public $username = null;
77
78 /**
79 *
80 * @var string Password to connect to server
81 * @since 1.0
82 */
83 public $password = null;
84
85 /**
86 * @var mixed LDAP Resource Identifier
87 * @since 1.0
88 */
89 private $resource = null;
90
91 /**
92 *
93 * @var string Current DN
94 * @since 1.0
95 */
96 private $dn = null;
97
98 /**
99 * Constructor
100 *
101 * @param object $configObj An object of configuration variables
102 *
103 * @since 1.0
104 */
105 public function __construct($configObj = null)
106 {
107 if (is_object($configObj))
108 {
109 $vars = get_class_vars(get_class($this));
110
111 foreach (array_keys($vars) as $var)
112 {
113 if (substr($var, 0, 1) != '_')
114 {
115 $param = $configObj->get($var);
116
117 if ($param)
118 {
119 $this->$var = $param;
120 }
121 }
122 }
123 }
124 }
125
126 /**
127 * Connect to server
128 *
129 * @return boolean True if successful
130 *
131 * @since 1.0
132 */
133 public function connect()
134 {
135 if ($this->host == '')
136 {
137 return false;
138 }
139
140 $this->resource = @ ldap_connect($this->host, $this->port);
141
142 if ($this->resource)
143 {
144 if ($this->use_ldapV3)
145 {
146 if (!@ldap_set_option($this->resource, LDAP_OPT_PROTOCOL_VERSION, 3))
147 {
148 return false;
149 }
150 }
151
152 if (!@ldap_set_option($this->resource, LDAP_OPT_REFERRALS, (int) $this->no_referrals))
153 {
154 return false;
155 }
156
157 if ($this->negotiate_tls)
158 {
159 if (!@ldap_start_tls($this->resource))
160 {
161 return false;
162 }
163 }
164
165 return true;
166 }
167 else
168 {
169 return false;
170 }
171 }
172
173 /**
174 * Close the connection
175 *
176 * @return void
177 *
178 * @since 1.0
179 */
180 public function close()
181 {
182 @ ldap_close($this->resource);
183 }
184
185 /**
186 * Sets the DN with some template replacements
187 *
188 * @param string $username The username
189 * @param string $nosub ...
190 *
191 * @return void
192 *
193 * @since 1.0
194 */
195 public function setDN($username, $nosub = 0)
196 {
197 if ($this->users_dn == '' || $nosub)
198 {
199 $this->dn = $username;
200 }
201 elseif (strlen($username))
202 {
203 $this->dn = str_replace('[username]', $username, $this->users_dn);
204 }
205 else
206 {
207 $this->dn = '';
208 }
209 }
210
211 /**
212 * Get the DN
213 *
214 * @return string The current dn
215 *
216 * @since 1.0
217 */
218 public function getDN()
219 {
220 return $this->dn;
221 }
222
223 /**
224 * Anonymously binds to LDAP directory
225 *
226 * @return array
227 *
228 * @since 1.0
229 */
230 public function anonymous_bind()
231 {
232 $bindResult = @ldap_bind($this->resource);
233
234 return $bindResult;
235 }
236
237 /**
238 * Binds to the LDAP directory
239 *
240 * @param string $username The username
241 * @param string $password The password
242 * @param string $nosub ...
243 *
244 * @return boolean
245 *
246 * @since 1.0
247 */
248 public function bind($username = null, $password = null, $nosub = 0)
249 {
250 if (is_null($username))
251 {
252 $username = $this->username;
253 }
254
255 if (is_null($password))
256 {
257 $password = $this->password;
258 }
259
260 $this->setDN($username, $nosub);
261 $bindResult = @ldap_bind($this->resource, $this->getDN(), $password);
262
263 return $bindResult;
264 }
265
266 /**
267 * Perform an LDAP search using comma separated search strings
268 *
269 * @param string $search search string of search values
270 *
271 * @return array Search results
272 *
273 * @since 1.0
274 */
275 public function simple_search($search)
276 {
277 $results = explode(';', $search);
278
279 foreach ($results as $key => $result)
280 {
281 $results[$key] = '(' . $result . ')';
282 }
283
284 return $this->search($results);
285 }
286
287 /**
288 * Performs an LDAP search
289 *
290 * @param array $filters Search Filters (array of strings)
291 * @param string $dnoverride DN Override
292 * @param array $attributes An array of attributes to return (if empty, all fields are returned).
293 *
294 * @return array Multidimensional array of results
295 *
296 * @since 1.0
297 */
298 public function search(array $filters, $dnoverride = null, array $attributes = array())
299 {
300 $result = array();
301
302 if ($dnoverride)
303 {
304 $dn = $dnoverride;
305 }
306 else
307 {
308 $dn = $this->base_dn;
309 }
310
311 $resource = $this->resource;
312
313 foreach ($filters as $search_filter)
314 {
315 $search_result = @ldap_search($resource, $dn, $search_filter, $attributes);
316
317 if ($search_result && ($count = @ldap_count_entries($resource, $search_result)) > 0)
318 {
319 for ($i = 0; $i < $count; $i++)
320 {
321 $result[$i] = array();
322
323 if (!$i)
324 {
325 $firstentry = @ldap_first_entry($resource, $search_result);
326 }
327 else
328 {
329 $firstentry = @ldap_next_entry($resource, $firstentry);
330 }
331
332 // Load user-specified attributes
333 $result_array = @ldap_get_attributes($resource, $firstentry);
334
335 // LDAP returns an array of arrays, fit this into attributes result array
336 foreach ($result_array as $ki => $ai)
337 {
338 if (is_array($ai))
339 {
340 $subcount = $ai['count'];
341 $result[$i][$ki] = array();
342
343 for ($k = 0; $k < $subcount; $k++)
344 {
345 $result[$i][$ki][$k] = $ai[$k];
346 }
347 }
348 }
349
350 $result[$i]['dn'] = @ldap_get_dn($resource, $firstentry);
351 }
352 }
353 }
354
355 return $result;
356 }
357
358 /**
359 * Replace an entry and return a true or false result
360 *
361 * @param string $dn The DN which contains the attribute you want to replace
362 * @param string $attribute The attribute values you want to replace
363 *
364 * @return mixed result of comparison (true, false, -1 on error)
365 *
366 * @since 1.0
367 */
368 public function replace($dn, $attribute)
369 {
370 return @ldap_mod_replace($this->resource, $dn, $attribute);
371 }
372
373 /**
374 * Modifies an entry and return a true or false result
375 *
376 * @param string $dn The DN which contains the attribute you want to modify
377 * @param string $attribute The attribute values you want to modify
378 *
379 * @return mixed result of comparison (true, false, -1 on error)
380 *
381 * @since 1.0
382 */
383 public function modify($dn, $attribute)
384 {
385 return @ldap_modify($this->resource, $dn, $attribute);
386 }
387
388 /**
389 * Removes attribute value from given dn and return a true or false result
390 *
391 * @param string $dn The DN which contains the attribute you want to remove
392 * @param string $attribute The attribute values you want to remove
393 *
394 * @return mixed result of comparison (true, false, -1 on error)
395 *
396 * @since 1.0
397 */
398 public function remove($dn, $attribute)
399 {
400 $resource = $this->resource;
401
402 return @ldap_mod_del($resource, $dn, $attribute);
403 }
404
405 /**
406 * Compare an entry and return a true or false result
407 *
408 * @param string $dn The DN which contains the attribute you want to compare
409 * @param string $attribute The attribute whose value you want to compare
410 * @param string $value The value you want to check against the LDAP attribute
411 *
412 * @return mixed result of comparison (true, false, -1 on error)
413 *
414 * @since 1.0
415 */
416 public function compare($dn, $attribute, $value)
417 {
418 return @ldap_compare($this->resource, $dn, $attribute, $value);
419 }
420
421 /**
422 * Read all or specified attributes of given dn
423 *
424 * @param string $dn The DN of the object you want to read
425 *
426 * @return mixed array of attributes or -1 on error
427 *
428 * @since 1.0
429 */
430 public function read($dn)
431 {
432 $base = substr($dn, strpos($dn, ',') + 1);
433 $cn = substr($dn, 0, strpos($dn, ','));
434 $result = @ldap_read($this->resource, $base, $cn);
435
436 if ($result)
437 {
438 return @ldap_get_entries($this->resource, $result);
439 }
440 else
441 {
442 return $result;
443 }
444 }
445
446 /**
447 * Deletes a given DN from the tree
448 *
449 * @param string $dn The DN of the object you want to delete
450 *
451 * @return boolean Result of operation
452 *
453 * @since 1.0
454 */
455 public function delete($dn)
456 {
457 return @ldap_delete($this->resource, $dn);
458 }
459
460 /**
461 * Create a new DN
462 *
463 * @param string $dn The DN where you want to put the object
464 * @param array $entries An array of arrays describing the object to add
465 *
466 * @return boolean Result of operation
467 *
468 * @since 1.0
469 */
470 public function create($dn, array $entries)
471 {
472 return @ldap_add($this->resource, $dn, $entries);
473 }
474
475 /**
476 * Add an attribute to the given DN
477 * Note: DN has to exist already
478 *
479 * @param string $dn The DN of the entry to add the attribute
480 * @param array $entry An array of arrays with attributes to add
481 *
482 * @return boolean Result of operation
483 *
484 * @since 1.0
485 */
486 public function add($dn, array $entry)
487 {
488 return @ldap_mod_add($this->resource, $dn, $entry);
489 }
490
491 /**
492 * Rename the entry
493 *
494 * @param string $dn The DN of the entry at the moment
495 * @param string $newdn The DN of the entry should be (only cn=newvalue)
496 * @param string $newparent The full DN of the parent (null by default)
497 * @param boolean $deleteolddn Delete the old values (default)
498 *
499 * @return boolean Result of operation
500 *
501 * @since 1.0
502 */
503 public function rename($dn, $newdn, $newparent, $deleteolddn)
504 {
505 return @ldap_rename($this->resource, $dn, $newdn, $newparent, $deleteolddn);
506 }
507
508 /**
509 * Returns the error message
510 *
511 * @return string error message
512 *
513 * @since 1.0
514 */
515 public function getErrorMsg()
516 {
517 return @ldap_error($this->resource);
518 }
519
520 /**
521 * Converts a dot notation IP address to net address (e.g. for Netware, etc)
522 *
523 * @param string $ip IP Address (e.g. xxx.xxx.xxx.xxx)
524 *
525 * @return string Net address
526 *
527 * @since 1.0
528 */
529 public static function ipToNetAddress($ip)
530 {
531 $parts = explode('.', $ip);
532 $address = '1#';
533
534 foreach ($parts as $int)
535 {
536 $tmp = dechex($int);
537
538 if (strlen($tmp) != 2)
539 {
540 $tmp = '0' . $tmp;
541 }
542
543 $address .= '\\' . $tmp;
544 }
545
546 return $address;
547 }
548
549 /**
550 * Extract readable network address from the LDAP encoded networkAddress attribute.
551 *
552 * Please keep this document block and author attribution in place.
553 *
554 * Novell Docs, see: http://developer.novell.com/ndk/doc/ndslib/schm_enu/data/sdk5624.html#sdk5624
555 * for Address types: http://developer.novell.com/ndk/doc/ndslib/index.html?page=/ndk/doc/ndslib/schm_enu/data/sdk4170.html
556 * LDAP Format, String:
557 * taggedData = uint32String "#" octetstring
558 * byte 0 = uint32String = Address Type: 0= IPX Address; 1 = IP Address
559 * byte 1 = char = "#" - separator
560 * byte 2+ = octetstring - the ordinal value of the address
561 * Note: with eDirectory 8.6.2, the IP address (type 1) returns
562 * correctly, however, an IPX address does not seem to. eDir 8.7 may correct this.
563 * Enhancement made by Merijn van de Schoot:
564 * If addresstype is 8 (UDP) or 9 (TCP) do some additional parsing like still returning the IP address
565 *
566 * @param string $networkaddress The network address
567 *
568 * @return array
569 *
570 * @author Jay Burrell, Systems & Networks, Mississippi State University
571 * @since 1.0
572 */
573 public static function LDAPNetAddr($networkaddress)
574 {
575 $addr = "";
576 $addrtype = (int) substr($networkaddress, 0, 1);
577
578 // Throw away bytes 0 and 1 which should be the addrtype and the "#" separator
579 $networkaddress = substr($networkaddress, 2);
580
581 if (($addrtype == 8) || ($addrtype = 9))
582 {
583 // TODO 1.6: If UDP or TCP, (TODO fill addrport and) strip portnumber information from address
584 $networkaddress = substr($networkaddress, (strlen($networkaddress) - 4));
585 }
586
587 $addrtypes = array(
588 'IPX',
589 'IP',
590 'SDLC',
591 'Token Ring',
592 'OSI',
593 'AppleTalk',
594 'NetBEUI',
595 'Socket',
596 'UDP',
597 'TCP',
598 'UDP6',
599 'TCP6',
600 'Reserved (12)',
601 'URL',
602 'Count');
603 $len = strlen($networkaddress);
604
605 if ($len > 0)
606 {
607 for ($i = 0; $i < $len; $i++)
608 {
609 $byte = substr($networkaddress, $i, 1);
610 $addr .= ord($byte);
611
612 if (($addrtype == 1) || ($addrtype == 8) || ($addrtype = 9))
613 {
614 // Dot separate IP addresses...
615 $addr .= ".";
616 }
617 }
618
619 if (($addrtype == 1) || ($addrtype == 8) || ($addrtype = 9))
620 {
621 // Strip last period from end of $addr
622 $addr = substr($addr, 0, strlen($addr) - 1);
623 }
624 }
625 else
626 {
627 $addr .= 'Address not available.';
628 }
629
630 return array('protocol' => $addrtypes[$addrtype], 'address' => $addr);
631 }
632
633 /**
634 * Generates a LDAP compatible password
635 *
636 * @param string $password Clear text password to encrypt
637 * @param string $type Type of password hash, either md5 or SHA
638 *
639 * @return string Encrypted password
640 *
641 * @since 1.0
642 */
643 public static function generatePassword($password, $type = 'md5')
644 {
645 switch (strtolower($type))
646 {
647 case 'sha':
648 $userpassword = '{SHA}' . base64_encode(pack('H*', sha1($password)));
649 break;
650
651 case 'md5':
652 default:
653 $userpassword = '{MD5}' . base64_encode(pack('H*', md5($password)));
654 break;
655 }
656
657 return $userpassword;
658 }
659 }
660