ILIAS  release_5-1 Revision 5.0.0-5477-g43f3e3fab5f
LDAP.php
Go to the documentation of this file.
1<?php
2/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */
3
29require_once "Auth/Container.php";
33require_once "PEAR.php";
34
204{
205
206 // {{{ properties
207
212 var $options = array();
213
218 var $conn_id = false;
219
220 // }}}
221
222 // {{{ Auth_Container_LDAP() [constructor]
223
231 {
232 if (false === extension_loaded('ldap')) {
233 return PEAR::raiseError('Auth_Container_LDAP: LDAP Extension not loaded',
234 41, PEAR_ERROR_DIE);
235 }
236
237 $this->_setDefaults();
238
239 if (is_array($params)) {
240 $this->_parseOptions($params);
241 }
242 }
243
244 // }}}
245 // {{{ _prepare()
246
256 function _prepare()
257 {
258 if (!$this->_isValidLink()) {
259 $res = $this->_connect();
260 if (PEAR::isError($res)) {
261 return $res;
262 }
263 }
264 return true;
265 }
266
267 // }}}
268 // {{{ _connect()
269
276 function _connect()
277 {
278 $this->log('Auth_Container_LDAP::_connect() called.', AUTH_LOG_DEBUG);
279 // connect
280 if (isset($this->options['url']) && $this->options['url'] != '') {
281 $this->log('Connecting with URL', AUTH_LOG_DEBUG);
282 $conn_params = array($this->options['url']);
283 } else {
284 $this->log('Connecting with host:port', AUTH_LOG_DEBUG);
285 $conn_params = array($this->options['host'], $this->options['port']);
286 }
287
288 if (($this->conn_id = @call_user_func_array('ldap_connect', $conn_params)) === false) {
289 $this->log('Connection to server failed.', AUTH_LOG_DEBUG);
290 $this->log('LDAP ERROR: '.ldap_errno($this->conn_id).': '.ldap_error($this->conn_id), AUTH_LOG_DEBUG);
291 return PEAR::raiseError('Auth_Container_LDAP: Could not connect to server.', 41);
292 }
293 $this->log('Successfully connected to server', AUTH_LOG_DEBUG);
294
295 // switch LDAP version
296 if (is_numeric($this->options['version']) && $this->options['version'] > 2) {
297 $this->log("Switching to LDAP version {$this->options['version']}", AUTH_LOG_DEBUG);
298 @ldap_set_option($this->conn_id, LDAP_OPT_PROTOCOL_VERSION, $this->options['version']);
299
300 // start TLS if available
301 if (isset($this->options['start_tls']) && $this->options['start_tls']) {
302 $this->log("Starting TLS session", AUTH_LOG_DEBUG);
303 if (@ldap_start_tls($this->conn_id) === false) {
304 $this->log('Could not start TLS session', AUTH_LOG_DEBUG);
305 $this->log('LDAP ERROR: '.ldap_errno($this->conn_id).': '.ldap_error($this->conn_id), AUTH_LOG_DEBUG);
306 return PEAR::raiseError('Auth_Container_LDAP: Could not start tls.', 41);
307 }
308 }
309 }
310
311 // switch LDAP referrals
312 if (is_bool($this->options['referrals'])) {
313 $this->log("Switching LDAP referrals to " . (($this->options['referrals']) ? 'true' : 'false'), AUTH_LOG_DEBUG);
314 if (@ldap_set_option($this->conn_id, LDAP_OPT_REFERRALS, $this->options['referrals']) === false) {
315 $this->log('Could not change LDAP referrals options', AUTH_LOG_DEBUG);
316 $this->log('LDAP ERROR: '.ldap_errno($this->conn_id).': '.ldap_error($this->conn_id), AUTH_LOG_DEBUG);
317 }
318 }
319
320 // bind with credentials or anonymously
321 if (strlen($this->options['binddn']) && strlen($this->options['bindpw'])) {
322 $this->log('Binding with credentials', AUTH_LOG_DEBUG);
323 $bind_params = array($this->conn_id, $this->options['binddn'], $this->options['bindpw']);
324 } else {
325 $this->log('Binding anonymously', AUTH_LOG_DEBUG);
326 $bind_params = array($this->conn_id);
327 }
328
329 // bind for searching
330 // smeyer: set default network timeout
331 @call_user_func_array('ldap_set_options', array($this->conn_id,LDAP_OPT_NETWORK_TIMEOUT, ilLDAPServer::DEFAULT_NETWORK_TIMEOUT));
332 if ((@call_user_func_array('ldap_bind', $bind_params)) === false) {
333 $this->log('Bind failed', AUTH_LOG_DEBUG);
334 $this->log('LDAP ERROR: '.ldap_errno($this->conn_id).': '.ldap_error($this->conn_id), AUTH_LOG_DEBUG);
335 $this->_disconnect();
336 return PEAR::raiseError("Auth_Container_LDAP: Could not bind to LDAP server.", 41);
337 }
338 $this->log('Binding was successful', AUTH_LOG_DEBUG);
339
340 return true;
341 }
342
343 // }}}
344 // {{{ _disconnect()
345
351 function _disconnect()
352 {
353 $this->log('Auth_Container_LDAP::_disconnect() called.', AUTH_LOG_DEBUG);
354 if ($this->_isValidLink()) {
355 $this->log('disconnecting from server');
356 @ldap_unbind($this->conn_id);
357 }
358 }
359
360 // }}}
361 // {{{ _getBaseDN()
362
368 function _getBaseDN()
369 {
370 $this->log('Auth_Container_LDAP::_getBaseDN() called.', AUTH_LOG_DEBUG);
371 $err = $this->_prepare();
372 if ($err !== true) {
373 return PEAR::raiseError($err->getMessage(), $err->getCode());
374 }
375
376 if ($this->options['basedn'] == "" && $this->_isValidLink()) {
377 $this->log("basedn not set, searching via namingContexts.", AUTH_LOG_DEBUG);
378
379 $result_id = @ldap_read($this->conn_id, "", "(objectclass=*)", array("namingContexts"));
380
381 if (@ldap_count_entries($this->conn_id, $result_id) == 1) {
382
383 $this->log("got result for namingContexts", AUTH_LOG_DEBUG);
384
385 $entry_id = @ldap_first_entry($this->conn_id, $result_id);
386 $attrs = @ldap_get_attributes($this->conn_id, $entry_id);
387 $basedn = $attrs['namingContexts'][0];
388
389 if ($basedn != "") {
390 $this->log("result for namingContexts was $basedn", AUTH_LOG_DEBUG);
391 $this->options['basedn'] = $basedn;
392 }
393 }
394 @ldap_free_result($result_id);
395 }
396
397 // if base ist still not set, raise error
398 if ($this->options['basedn'] == "") {
399 return PEAR::raiseError("Auth_Container_LDAP: LDAP search base not specified!", 41);
400 }
401 return true;
402 }
403
404 // }}}
405 // {{{ _isValidLink()
406
413 function _isValidLink()
414 {
415 if (is_resource($this->conn_id)) {
416 if (get_resource_type($this->conn_id) == 'ldap link') {
417 return true;
418 }
419 }
420 return false;
421 }
422
423 // }}}
424 // {{{ _setDefaults()
425
431 function _setDefaults()
432 {
433 $this->options['url'] = '';
434 $this->options['host'] = 'localhost';
435 $this->options['port'] = '389';
436 $this->options['version'] = 2;
437 $this->options['referrals'] = true;
438 $this->options['binddn'] = '';
439 $this->options['bindpw'] = '';
440 $this->options['basedn'] = '';
441 $this->options['userdn'] = '';
442 $this->options['userscope'] = 'sub';
443 $this->options['userattr'] = 'uid';
444 $this->options['userfilter'] = '(objectClass=posixAccount)';
445 $this->options['attributes'] = array(''); // no attributes
446 $this->options['attrformat'] = 'AUTH'; // returns attribute like other Auth containers
447 $this->options['group'] = '';
448 $this->options['groupdn'] = '';
449 $this->options['groupscope'] = 'sub';
450 $this->options['groupattr'] = 'cn';
451 $this->options['groupfilter'] = '(objectClass=groupOfUniqueNames)';
452 $this->options['memberattr'] = 'uniqueMember';
453 $this->options['memberisdn'] = true;
454 $this->options['start_tls'] = false;
455 $this->options['debug'] = false;
456 $this->options['try_all'] = false; // Try all user ids returned not just the first one
457 }
458
459 // }}}
460 // {{{ _parseOptions()
461
468 function _parseOptions($array)
469 {
470 $array = $this->_setV12OptionsToV13($array);
471
472 foreach ($array as $key => $value) {
473 if (array_key_exists($key, $this->options)) {
474 if ($key == 'attributes') {
475 if (is_array($value)) {
476 $this->options[$key] = $value;
477 } else {
478 $this->options[$key] = explode(',', $value);
479 }
480 } else {
481 $this->options[$key] = $value;
482 }
483 }
484 }
485 }
486
487 // }}}
488 // {{{ _setV12OptionsToV13()
489
498 function _setV12OptionsToV13($array)
499 {
500 if (isset($array['useroc']))
501 $array['userfilter'] = "(objectClass=".$array['useroc'].")";
502 if (isset($array['groupoc']))
503 $array['groupfilter'] = "(objectClass=".$array['groupoc'].")";
504 if (isset($array['scope']))
505 $array['userscope'] = $array['scope'];
506
507 return $array;
508 }
509
510 // }}}
511 // {{{ _scope2function()
512
519 function _scope2function($scope)
520 {
521 switch($scope) {
522 case 'one':
523 $function = 'ldap_list';
524 break;
525 case 'base':
526 $function = 'ldap_read';
527 break;
528 default:
529 $function = 'ldap_search';
530 break;
531 }
532 return $function;
533 }
534
535 // }}}
536 // {{{ fetchData()
537
549 function fetchData($username, $password)
550 {
551 $this->log('Auth_Container_LDAP::fetchData() called.', AUTH_LOG_DEBUG);
552 $err = $this->_prepare();
553 if ($err !== true) {
554 return PEAR::raiseError($err->getMessage(), $err->getCode());
555 }
556
557 $err = $this->_getBaseDN();
558 if ($err !== true) {
559 return PEAR::raiseError($err->getMessage(), $err->getCode());
560 }
561
562 // UTF8 Encode username for LDAPv3
563 if (@ldap_get_option($this->conn_id, LDAP_OPT_PROTOCOL_VERSION, $ver) && $ver == 3) {
564 $this->log('UTF8 encoding username for LDAPv3', AUTH_LOG_DEBUG);
565 $username = utf8_encode($username);
566 }
567
568 // make search filter
569 $filter = sprintf('(&(%s=%s)%s)',
570 $this->options['userattr'],
571 $this->_quoteFilterString($username),
572 $this->options['userfilter']);
573
574 // make search base dn
575 $search_basedn = $this->options['userdn'];
576 if ($search_basedn != '' && substr($search_basedn, -1) != ',') {
577 $search_basedn .= ',';
578 }
579 $search_basedn .= $this->options['basedn'];
580
581 // attributes
582 $searchAttributes = $this->options['attributes'];
583
584 // make functions params array
585 $func_params = array($this->conn_id, $search_basedn, $filter, $searchAttributes);
586
587 // search function to use
588 $func_name = $this->_scope2function($this->options['userscope']);
589
590 $this->log("Searching with $func_name and filter $filter in $search_basedn", AUTH_LOG_DEBUG);
591
592 // search
593 if (($result_id = @call_user_func_array($func_name, $func_params)) === false) {
594 $this->log('User not found', AUTH_LOG_DEBUG);
595 } elseif (@ldap_count_entries($this->conn_id, $result_id) >= 1) { // did we get some possible results?
596
597 $this->log('User(s) found', AUTH_LOG_DEBUG);
598
599 $first = true;
600 $entry_id = null;
601
602 do {
603
604 // then get the user dn
605 if ($first) {
606 $entry_id = @ldap_first_entry($this->conn_id, $result_id);
607 $first = false;
608 } else {
609 $entry_id = @ldap_next_entry($this->conn_id, $entry_id);
610 if ($entry_id === false)
611 break;
612 }
613 $user_dn = @ldap_get_dn($this->conn_id, $entry_id);
614
615 // as the dn is not fetched as an attribute, we save it anyway
616 if (is_array($searchAttributes) && in_array('dn', $searchAttributes)) {
617 $this->log('Saving DN to AuthData', AUTH_LOG_DEBUG);
618 $this->_auth_obj->setAuthData('dn', $user_dn);
619 }
620
621 // fetch attributes
622 if ($attributes = @ldap_get_attributes($this->conn_id, $entry_id)) {
623
624 if (is_array($attributes) && isset($attributes['count']) &&
625 $attributes['count'] > 0) {
626
627 // ldap_get_attributes() returns a specific multi dimensional array
628 // format containing all the attributes and where each array starts
629 // with a 'count' element providing the number of attributes in the
630 // entry, or the number of values for attribute. For compatibility
631 // reasons, it remains the default format returned by LDAP container
632 // setAuthData().
633 // The code below optionally returns attributes in another format,
634 // more compliant with other Auth containers, where each attribute
635 // element are directly set in the 'authData' list. This option is
636 // enabled by setting 'attrformat' to
637 // 'AUTH' in the 'options' array.
638 // eg. $this->options['attrformat'] = 'AUTH'
639
640 if ( strtoupper($this->options['attrformat']) == 'AUTH' ) {
641 $this->log('Saving attributes to Auth data in AUTH format', AUTH_LOG_DEBUG);
642 unset ($attributes['count']);
643 foreach ($attributes as $attributeName => $attributeValue ) {
644 if (is_int($attributeName)) continue;
645 if (is_array($attributeValue) && isset($attributeValue['count'])) {
646 unset ($attributeValue['count']);
647 }
648 if (count($attributeValue)<=1) $attributeValue = $attributeValue[0];
649 $this->log('Storing additional field: '.$attributeName, AUTH_LOG_DEBUG);
650 $this->_auth_obj->setAuthData($attributeName, $attributeValue);
651 }
652 }
653 else
654 {
655 $this->log('Saving attributes to Auth data in LDAP format', AUTH_LOG_DEBUG);
656 $this->_auth_obj->setAuthData('attributes', $attributes);
657 }
658 }
659 }
660 @ldap_free_result($result_id);
661
662 // need to catch an empty password as openldap seems to return TRUE
663 // if anonymous binding is allowed
664 if ($password != "") {
665 $this->log("Bind as $user_dn", AUTH_LOG_DEBUG);
666
667 // try binding as this user with the supplied password
668 @call_user_func_array('ldap_set_options', array($this->conn_id,LDAP_OPT_NETWORK_TIMEOUT, ilLDAPServer::DEFAULT_NETWORK_TIMEOUT));
669 if (@ldap_bind($this->conn_id, $user_dn, $password)) {
670 $this->log('Bind successful', AUTH_LOG_DEBUG);
671
672 // check group if appropiate
673 if (strlen($this->options['group'])) {
674 // decide whether memberattr value is a dn or the username
675 $this->log('Checking group membership', AUTH_LOG_DEBUG);
676 $return = $this->checkGroup(($this->options['memberisdn']) ? $user_dn : $username);
677 $this->_disconnect();
678 return $return;
679 } else {
680 $this->log('Authenticated', AUTH_LOG_DEBUG);
681 $this->_disconnect();
682 return true; // user authenticated
683 } // checkGroup
684 } // bind
685 } // non-empty password
686 } while ($this->options['try_all'] == true); // interate through entries
687 } // get results
688 // default
689 $this->log('NOT authenticated!', AUTH_LOG_DEBUG);
690 $this->_disconnect();
691 return false;
692 }
693
694 // }}}
695 // {{{ checkGroup()
696
707 function checkGroup($user)
708 {
709 $this->log('Auth_Container_LDAP::checkGroup() called.', AUTH_LOG_DEBUG);
710 $err = $this->_prepare();
711 if ($err !== true) {
712 return PEAR::raiseError($err->getMessage(), $err->getCode());
713 }
714
715 // make filter
716 $filter = sprintf('(&(%s=%s)(%s=%s)%s)',
717 $this->options['groupattr'],
718 $this->options['group'],
719 $this->options['memberattr'],
720 $this->_quoteFilterString($user),
721 $this->options['groupfilter']);
722
723 // make search base dn
724 $search_basedn = $this->options['groupdn'];
725 if ($search_basedn != '' && substr($search_basedn, -1) != ',') {
726 $search_basedn .= ',';
727 }
728 $search_basedn .= $this->options['basedn'];
729
730 $func_params = array($this->conn_id, $search_basedn, $filter,
731 array($this->options['memberattr']));
732 $func_name = $this->_scope2function($this->options['groupscope']);
733
734 $this->log("Searching with $func_name and filter $filter in $search_basedn", AUTH_LOG_DEBUG);
735
736 // search
737 if (($result_id = @call_user_func_array($func_name, $func_params)) != false) {
738 if (@ldap_count_entries($this->conn_id, $result_id) == 1) {
739 @ldap_free_result($result_id);
740 $this->log('User is member of group', AUTH_LOG_DEBUG);
741 return true;
742 }
743 }
744 // default
745 $this->log('User is NOT member of group', AUTH_LOG_DEBUG);
746 return false;
747 }
748
749 // }}}
750 // {{{ _quoteFilterString()
751
758 function _quoteFilterString($filter_str)
759 {
760 $metas = array( '\\', '*', '(', ')', "\x00");
761 $quoted_metas = array('\\\\', '\*', '\‍(', '\‍)', "\\\x00");
762 return str_replace($metas, $quoted_metas, $filter_str);
763 }
764
765 // }}}
766
767}
768
769?>
const AUTH_LOG_DEBUG
Auth Log level - DEBUG.
Definition: Auth.php:59
const PEAR_ERROR_DIE
Definition: PEAR.php:34
_prepare()
Prepare LDAP connection.
Definition: LDAP.php:256
_quoteFilterString($filter_str)
Escapes LDAP filter special characters as defined in RFC 2254.
Definition: LDAP.php:758
_getBaseDN()
Tries to find Basedn via namingContext Attribute.
Definition: LDAP.php:368
_setV12OptionsToV13($array)
Adapt deprecated options from Auth 1.2 LDAP to Auth 1.3 LDAP.
Definition: LDAP.php:498
fetchData($username, $password)
Fetch data from LDAP server.
Definition: LDAP.php:549
_disconnect()
Disconnects (unbinds) from ldap server.
Definition: LDAP.php:351
_parseOptions($array)
Parse options passed to the container class.
Definition: LDAP.php:468
_setDefaults()
Set some default options.
Definition: LDAP.php:431
_scope2function($scope)
Get search function for scope.
Definition: LDAP.php:519
checkGroup($user)
Validate group membership.
Definition: LDAP.php:707
Auth_Container_LDAP($params)
Constructor of the container class.
Definition: LDAP.php:230
_connect()
Connect to the LDAP server using the global options.
Definition: LDAP.php:276
_isValidLink()
determines whether there is a valid ldap conenction or not
Definition: LDAP.php:413
log($message, $level=AUTH_LOG_DEBUG)
Log a message to the Auth log.
Definition: Container.php:246
isError($data, $code=null)
Tell whether a value is a PEAR error.
Definition: PEAR.php:279
& raiseError($message=null, $code=null, $mode=null, $options=null, $userinfo=null, $error_class=null, $skipmsg=false)
This method is a wrapper that returns an instance of the configured error class with this object's de...
Definition: PEAR.php:524
const DEFAULT_NETWORK_TIMEOUT
$params
Definition: example_049.php:96