ILIAS  Release_4_4_x_branch Revision 61816
 All Data Structures Namespaces Files Functions Variables Groups Pages
LDAP.php
Go to the documentation of this file.
1 <?php
2 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */
3 
29 require_once "Auth/Container.php";
33 require_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 
230  function Auth_Container_LDAP($params)
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 ?>