1<?php 2/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */ 3 4/** 5 * Storage driver for use against an LDAP server 6 * 7 * PHP versions 4 and 5 8 * 9 * LICENSE: This source file is subject to version 3.01 of the PHP license 10 * that is available through the world-wide-web at the following URI: 11 * http://www.php.net/license/3_01.txt. If you did not receive a copy of 12 * the PHP License and are unable to obtain it through the web, please 13 * send a note to license@php.net so we can mail you a copy immediately. 14 * 15 * @category Authentication 16 * @package Auth 17 * @author Jan Wagner <wagner@netsols.de> 18 * @author Adam Ashley <aashley@php.net> 19 * @author Hugues Peeters <hugues.peeters@claroline.net> 20 * @copyright 2001-2006 The PHP Group 21 * @license http://www.php.net/license/3_01.txt PHP License 3.01 22 * @version CVS: $Id: LDAP.php 237449 2007-06-12 03:11:27Z aashley $ 23 * @link http://pear.php.net/package/Auth 24 */ 25 26/** 27 * Include Auth_Container base class 28 */ 29require_once "Auth/Container.php"; 30/** 31 * Include PEAR package for error handling 32 */ 33require_once "PEAR.php"; 34 35/** 36 * Storage driver for fetching login data from LDAP 37 * 38 * This class is heavily based on the DB and File containers. By default it 39 * connects to localhost:389 and searches for uid=$username with the scope 40 * "sub". If no search base is specified, it will try to determine it via 41 * the namingContexts attribute. It takes its parameters in a hash, connects 42 * to the ldap server, binds anonymously, searches for the user, and tries 43 * to bind as the user with the supplied password. When a group was set, it 44 * will look for group membership of the authenticated user. If all goes 45 * well the authentication was successful. 46 * 47 * Parameters: 48 * 49 * host: localhost (default), ldap.netsols.de or 127.0.0.1 50 * port: 389 (default) or 636 or whereever your server runs 51 * url: ldap://localhost:389/ 52 * useful for ldaps://, works only with openldap2 ? 53 * it will be preferred over host and port 54 * version: LDAP version to use, ususally 2 (default) or 3, 55 * must be an integer! 56 * referrals: If set, determines whether the LDAP library automatically 57 * follows referrals returned by LDAP servers or not. Possible 58 * values are true (default) or false. 59 * binddn: If set, searching for user will be done after binding 60 * as this user, if not set the bind will be anonymous. 61 * This is reported to make the container work with MS 62 * Active Directory, but should work with any server that 63 * is configured this way. 64 * This has to be a complete dn for now (basedn and 65 * userdn will not be appended). 66 * bindpw: The password to use for binding with binddn 67 * basedn: the base dn of your server 68 * userdn: gets prepended to basedn when searching for user 69 * userscope: Scope for user searching: one, sub (default), or base 70 * userattr: the user attribute to search for (default: uid) 71 * userfilter: filter that will be added to the search filter 72 * this way: (&(userattr=username)(userfilter)) 73 * default: (objectClass=posixAccount) 74 * attributes: array of additional attributes to fetch from entry. 75 * these will added to auth data and can be retrieved via 76 * Auth::getAuthData(). An empty array will fetch all attributes, 77 * array('') will fetch no attributes at all (default) 78 * If you add 'dn' as a value to this array, the users DN that was 79 * used for binding will be added to auth data as well. 80 * attrformat: The returned format of the additional data defined in the 81 * 'attributes' option. Two formats are available. 82 * LDAP returns data formatted in a 83 * multidimensional array where each array starts with a 84 * 'count' element providing the number of attributes in the 85 * entry, or the number of values for attributes. When set 86 * to this format, the only way to retrieve data from the 87 * Auth object is by calling getAuthData('attributes'). 88 * AUTH returns data formatted in a 89 * structure more compliant with other Auth Containers, 90 * where each attribute element can be directly called by 91 * getAuthData() method from Auth. 92 * For compatibily with previous LDAP container versions, 93 * the default format is LDAP. 94 * groupdn: gets prepended to basedn when searching for group 95 * groupattr: the group attribute to search for (default: cn) 96 * groupfilter: filter that will be added to the search filter when 97 * searching for a group: 98 * (&(groupattr=group)(memberattr=username)(groupfilter)) 99 * default: (objectClass=groupOfUniqueNames) 100 * memberattr : the attribute of the group object where the user dn 101 * may be found (default: uniqueMember) 102 * memberisdn: whether the memberattr is the dn of the user (default) 103 * or the value of userattr (usually uid) 104 * group: the name of group to search for 105 * groupscope: Scope for group searching: one, sub (default), or base 106 * start_tls: enable/disable the use of START_TLS encrypted connection 107 * (default: false) 108 * debug: Enable/Disable debugging output (default: false) 109 * try_all: Whether to try all user accounts returned from the search 110 * or just the first one. (default: false) 111 * 112 * To use this storage container, you have to use the following syntax: 113 * 114 * <?php 115 * ... 116 * 117 * $a1 = new Auth("LDAP", array( 118 * 'host' => 'localhost', 119 * 'port' => '389', 120 * 'version' => 3, 121 * 'basedn' => 'o=netsols,c=de', 122 * 'userattr' => 'uid' 123 * 'binddn' => 'cn=admin,o=netsols,c=de', 124 * 'bindpw' => 'password')); 125 * 126 * $a2 = new Auth('LDAP', array( 127 * 'url' => 'ldaps://ldap.netsols.de', 128 * 'basedn' => 'o=netsols,c=de', 129 * 'userscope' => 'one', 130 * 'userdn' => 'ou=People', 131 * 'groupdn' => 'ou=Groups', 132 * 'groupfilter' => '(objectClass=posixGroup)', 133 * 'memberattr' => 'memberUid', 134 * 'memberisdn' => false, 135 * 'group' => 'admin' 136 * )); 137 * 138 * $a3 = new Auth('LDAP', array( 139 * 'host' => 'ldap.netsols.de', 140 * 'port' => 389, 141 * 'version' => 3, 142 * 'referrals' => false, 143 * 'basedn' => 'dc=netsols,dc=de', 144 * 'binddn' => 'cn=Jan Wagner,cn=Users,dc=netsols,dc=de', 145 * 'bindpw' => 'password', 146 * 'userattr' => 'samAccountName', 147 * 'userfilter' => '(objectClass=user)', 148 * 'attributes' => array(''), 149 * 'group' => 'testing', 150 * 'groupattr' => 'samAccountName', 151 * 'groupfilter' => '(objectClass=group)', 152 * 'memberattr' => 'member', 153 * 'memberisdn' => true, 154 * 'groupdn' => 'cn=Users', 155 * 'groupscope' => 'one', 156 * 'debug' => true); 157 * 158 * The parameter values have to correspond 159 * to the ones for your LDAP server of course. 160 * 161 * When talking to a Microsoft ActiveDirectory server you have to 162 * use 'samaccountname' as the 'userattr' and follow special rules 163 * to translate the ActiveDirectory directory names into 'basedn'. 164 * The 'basedn' for the default 'Users' folder on an ActiveDirectory 165 * server for the ActiveDirectory Domain (which is not related to 166 * its DNS name) "win2000.example.org" would be: 167 * "CN=Users, DC=win2000, DC=example, DC=org' 168 * where every component of the domain name becomes a DC attribute 169 * of its own. If you want to use a custom users folder you have to 170 * replace "CN=Users" with a sequence of "OU" attributes that specify 171 * the path to your custom folder in reverse order. 172 * So the ActiveDirectory folder 173 * "win2000.example.org\Custom\Accounts" 174 * would become 175 * "OU=Accounts, OU=Custom, DC=win2000, DC=example, DC=org' 176 * 177 * It seems that binding anonymously to an Active Directory 178 * is not allowed, so you have to set binddn and bindpw for 179 * user searching. 180 * 181 * LDAP Referrals need to be set to false for AD to work sometimes. 182 * 183 * Example a3 shows a full blown and tested example for connection to 184 * Windows 2000 Active Directory with group mebership checking 185 * 186 * Note also that if you want an encrypted connection to an MS LDAP 187 * server, then, on your webserver, you must specify 188 * TLS_REQCERT never 189 * in /etc/ldap/ldap.conf or in the webserver user's ~/.ldaprc (which 190 * may or may not be read depending on your configuration). 191 * 192 * 193 * @category Authentication 194 * @package Auth 195 * @author Jan Wagner <wagner@netsols.de> 196 * @author Adam Ashley <aashley@php.net> 197 * @author Hugues Peeters <hugues.peeters@claroline.net> 198 * @copyright 2001-2006 The PHP Group 199 * @license http://www.php.net/license/3_01.txt PHP License 3.01 200 * @version Release: @package_version@ File: $Revision: 237449 $ 201 * @link http://pear.php.net/package/Auth 202 */ 203class Auth_Container_LDAP extends Auth_Container 204{ 205 206 // {{{ properties 207 208 /** 209 * Options for the class 210 * @var array 211 */ 212 var $options = array(); 213 214 /** 215 * Connection ID of LDAP Link 216 * @var string 217 */ 218 var $conn_id = false; 219 220 // }}} 221 222 // {{{ Auth_Container_LDAP() [constructor] 223 224 /** 225 * Constructor of the container class 226 * 227 * @param $params, associative hash with host,port,basedn and userattr key 228 * @return object Returns an error object if something went wrong 229 */ 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 247 /** 248 * Prepare LDAP connection 249 * 250 * This function checks if we have already opened a connection to 251 * the LDAP server. If that's not the case, a new connection is opened. 252 * 253 * @access private 254 * @return mixed True or a PEAR error object. 255 */ 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 270 /** 271 * Connect to the LDAP server using the global options 272 * 273 * @access private 274 * @return object Returns a PEAR error object if an error occurs. 275 */ 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 if ((@call_user_func_array('ldap_bind', $bind_params)) === false) { 331 $this->log('Bind failed', AUTH_LOG_DEBUG); 332 $this->log('LDAP ERROR: '.ldap_errno($this->conn_id).': '.ldap_error($this->conn_id), AUTH_LOG_DEBUG); 333 $this->_disconnect(); 334 return PEAR::raiseError("Auth_Container_LDAP: Could not bind to LDAP server.", 41); 335 } 336 $this->log('Binding was successful', AUTH_LOG_DEBUG); 337 338 return true; 339 } 340 341 // }}} 342 // {{{ _disconnect() 343 344 /** 345 * Disconnects (unbinds) from ldap server 346 * 347 * @access private 348 */ 349 function _disconnect() 350 { 351 $this->log('Auth_Container_LDAP::_disconnect() called.', AUTH_LOG_DEBUG); 352 if ($this->_isValidLink()) { 353 $this->log('disconnecting from server'); 354 @ldap_unbind($this->conn_id); 355 } 356 } 357 358 // }}} 359 // {{{ _getBaseDN() 360 361 /** 362 * Tries to find Basedn via namingContext Attribute 363 * 364 * @access private 365 */ 366 function _getBaseDN() 367 { 368 $this->log('Auth_Container_LDAP::_getBaseDN() called.', AUTH_LOG_DEBUG); 369 $err = $this->_prepare(); 370 if ($err !== true) { 371 return PEAR::raiseError($err->getMessage(), $err->getCode()); 372 } 373 374 if ($this->options['basedn'] == "" && $this->_isValidLink()) { 375 $this->log("basedn not set, searching via namingContexts.", AUTH_LOG_DEBUG); 376 377 $result_id = @ldap_read($this->conn_id, "", "(objectclass=*)", array("namingContexts")); 378 379 if (@ldap_count_entries($this->conn_id, $result_id) == 1) { 380 381 $this->log("got result for namingContexts", AUTH_LOG_DEBUG); 382 383 $entry_id = @ldap_first_entry($this->conn_id, $result_id); 384 $attrs = @ldap_get_attributes($this->conn_id, $entry_id); 385 $basedn = $attrs['namingContexts'][0]; 386 387 if ($basedn != "") { 388 $this->log("result for namingContexts was $basedn", AUTH_LOG_DEBUG); 389 $this->options['basedn'] = $basedn; 390 } 391 } 392 @ldap_free_result($result_id); 393 } 394 395 // if base ist still not set, raise error 396 if ($this->options['basedn'] == "") { 397 return PEAR::raiseError("Auth_Container_LDAP: LDAP search base not specified!", 41); 398 } 399 return true; 400 } 401 402 // }}} 403 // {{{ _isValidLink() 404 405 /** 406 * determines whether there is a valid ldap conenction or not 407 * 408 * @accessd private 409 * @return boolean 410 */ 411 function _isValidLink() 412 { 413 if (is_resource($this->conn_id)) { 414 if (get_resource_type($this->conn_id) == 'ldap link') { 415 return true; 416 } 417 } 418 return false; 419 } 420 421 // }}} 422 // {{{ _setDefaults() 423 424 /** 425 * Set some default options 426 * 427 * @access private 428 */ 429 function _setDefaults() 430 { 431 $this->options['url'] = ''; 432 $this->options['host'] = 'localhost'; 433 $this->options['port'] = '389'; 434 $this->options['version'] = 2; 435 $this->options['referrals'] = true; 436 $this->options['binddn'] = ''; 437 $this->options['bindpw'] = ''; 438 $this->options['basedn'] = ''; 439 $this->options['userdn'] = ''; 440 $this->options['userscope'] = 'sub'; 441 $this->options['userattr'] = 'uid'; 442 $this->options['userfilter'] = '(objectClass=posixAccount)'; 443 $this->options['attributes'] = array(''); // no attributes 444 $this->options['attrformat'] = 'AUTH'; // returns attribute like other Auth containers 445 $this->options['group'] = ''; 446 $this->options['groupdn'] = ''; 447 $this->options['groupscope'] = 'sub'; 448 $this->options['groupattr'] = 'cn'; 449 $this->options['groupfilter'] = '(objectClass=groupOfUniqueNames)'; 450 $this->options['memberattr'] = 'uniqueMember'; 451 $this->options['memberisdn'] = true; 452 $this->options['start_tls'] = false; 453 $this->options['debug'] = false; 454 $this->options['try_all'] = false; // Try all user ids returned not just the first one 455 } 456 457 // }}} 458 // {{{ _parseOptions() 459 460 /** 461 * Parse options passed to the container class 462 * 463 * @access private 464 * @param array 465 */ 466 function _parseOptions($array) 467 { 468 $array = $this->_setV12OptionsToV13($array); 469 470 foreach ($array as $key => $value) { 471 if (array_key_exists($key, $this->options)) { 472 if ($key == 'attributes') { 473 if (is_array($value)) { 474 $this->options[$key] = $value; 475 } else { 476 $this->options[$key] = explode(',', $value); 477 } 478 } else { 479 $this->options[$key] = $value; 480 } 481 } 482 } 483 } 484 485 // }}} 486 // {{{ _setV12OptionsToV13() 487 488 /** 489 * Adapt deprecated options from Auth 1.2 LDAP to Auth 1.3 LDAP 490 * 491 * @author Hugues Peeters <hugues.peeters@claroline.net> 492 * @access private 493 * @param array 494 * @return array 495 */ 496 function _setV12OptionsToV13($array) 497 { 498 if (isset($array['useroc'])) 499 $array['userfilter'] = "(objectClass=".$array['useroc'].")"; 500 if (isset($array['groupoc'])) 501 $array['groupfilter'] = "(objectClass=".$array['groupoc'].")"; 502 if (isset($array['scope'])) 503 $array['userscope'] = $array['scope']; 504 505 return $array; 506 } 507 508 // }}} 509 // {{{ _scope2function() 510 511 /** 512 * Get search function for scope 513 * 514 * @param string scope 515 * @return string ldap search function 516 */ 517 function _scope2function($scope) 518 { 519 switch($scope) { 520 case 'one': 521 $function = 'ldap_list'; 522 break; 523 case 'base': 524 $function = 'ldap_read'; 525 break; 526 default: 527 $function = 'ldap_search'; 528 break; 529 } 530 return $function; 531 } 532 533 // }}} 534 // {{{ fetchData() 535 536 /** 537 * Fetch data from LDAP server 538 * 539 * Searches the LDAP server for the given username/password 540 * combination. Escapes all LDAP meta characters in username 541 * before performing the query. 542 * 543 * @param string Username 544 * @param string Password 545 * @return boolean 546 */ 547 function fetchData($username, $password) 548 { 549 $this->log('Auth_Container_LDAP::fetchData() called.', AUTH_LOG_DEBUG); 550 $err = $this->_prepare(); 551 if ($err !== true) { 552 return PEAR::raiseError($err->getMessage(), $err->getCode()); 553 } 554 555 $err = $this->_getBaseDN(); 556 if ($err !== true) { 557 return PEAR::raiseError($err->getMessage(), $err->getCode()); 558 } 559 560 // UTF8 Encode username for LDAPv3 561 if (@ldap_get_option($this->conn_id, LDAP_OPT_PROTOCOL_VERSION, $ver) && $ver == 3) { 562 $this->log('UTF8 encoding username for LDAPv3', AUTH_LOG_DEBUG); 563 $username = utf8_encode($username); 564 } 565 566 // make search filter 567 $filter = sprintf('(&(%s=%s)%s)', 568 $this->options['userattr'], 569 $this->_quoteFilterString($username), 570 $this->options['userfilter']); 571 572 // make search base dn 573 $search_basedn = $this->options['userdn']; 574 if ($search_basedn != '' && substr($search_basedn, -1) != ',') { 575 $search_basedn .= ','; 576 } 577 $search_basedn .= $this->options['basedn']; 578 579 // attributes 580 $searchAttributes = $this->options['attributes']; 581 582 // make functions params array 583 $func_params = array($this->conn_id, $search_basedn, $filter, $searchAttributes); 584 585 // search function to use 586 $func_name = $this->_scope2function($this->options['userscope']); 587 588 $this->log("Searching with $func_name and filter $filter in $search_basedn", AUTH_LOG_DEBUG); 589 590 // search 591 if (($result_id = @call_user_func_array($func_name, $func_params)) === false) { 592 $this->log('User not found', AUTH_LOG_DEBUG); 593 } elseif (@ldap_count_entries($this->conn_id, $result_id) >= 1) { // did we get some possible results? 594 595 $this->log('User(s) found', AUTH_LOG_DEBUG); 596 597 $first = true; 598 $entry_id = null; 599 600 do { 601 602 // then get the user dn 603 if ($first) { 604 $entry_id = @ldap_first_entry($this->conn_id, $result_id); 605 $first = false; 606 } else { 607 $entry_id = @ldap_next_entry($this->conn_id, $entry_id); 608 if ($entry_id === false) 609 break; 610 } 611 $user_dn = @ldap_get_dn($this->conn_id, $entry_id); 612 613 // as the dn is not fetched as an attribute, we save it anyway 614 if (is_array($searchAttributes) && in_array('dn', $searchAttributes)) { 615 $this->log('Saving DN to AuthData', AUTH_LOG_DEBUG); 616 $this->_auth_obj->setAuthData('dn', $user_dn); 617 } 618 619 // fetch attributes 620 if ($attributes = @ldap_get_attributes($this->conn_id, $entry_id)) { 621 622 if (is_array($attributes) && isset($attributes['count']) && 623 $attributes['count'] > 0) { 624 625 // ldap_get_attributes() returns a specific multi dimensional array 626 // format containing all the attributes and where each array starts 627 // with a 'count' element providing the number of attributes in the 628 // entry, or the number of values for attribute. For compatibility 629 // reasons, it remains the default format returned by LDAP container 630 // setAuthData(). 631 // The code below optionally returns attributes in another format, 632 // more compliant with other Auth containers, where each attribute 633 // element are directly set in the 'authData' list. This option is 634 // enabled by setting 'attrformat' to 635 // 'AUTH' in the 'options' array. 636 // eg. $this->options['attrformat'] = 'AUTH' 637 638 if ( strtoupper($this->options['attrformat']) == 'AUTH' ) { 639 $this->log('Saving attributes to Auth data in AUTH format', AUTH_LOG_DEBUG); 640 unset ($attributes['count']); 641 foreach ($attributes as $attributeName => $attributeValue ) { 642 if (is_int($attributeName)) continue; 643 if (is_array($attributeValue) && isset($attributeValue['count'])) { 644 unset ($attributeValue['count']); 645 } 646 if (count($attributeValue)<=1) $attributeValue = $attributeValue[0]; 647 $this->log('Storing additional field: '.$attributeName, AUTH_LOG_DEBUG); 648 $this->_auth_obj->setAuthData($attributeName, $attributeValue); 649 } 650 } 651 else 652 { 653 $this->log('Saving attributes to Auth data in LDAP format', AUTH_LOG_DEBUG); 654 $this->_auth_obj->setAuthData('attributes', $attributes); 655 } 656 } 657 } 658 @ldap_free_result($result_id); 659 660 // need to catch an empty password as openldap seems to return TRUE 661 // if anonymous binding is allowed 662 if ($password != "") { 663 $this->log("Bind as $user_dn", AUTH_LOG_DEBUG); 664 665 // try binding as this user with the supplied password 666 if (@ldap_bind($this->conn_id, $user_dn, $password)) { 667 $this->log('Bind successful', AUTH_LOG_DEBUG); 668 669 // check group if appropiate 670 if (strlen($this->options['group'])) { 671 // decide whether memberattr value is a dn or the username 672 $this->log('Checking group membership', AUTH_LOG_DEBUG); 673 $return = $this->checkGroup(($this->options['memberisdn']) ? $user_dn : $username); 674 $this->_disconnect(); 675 return $return; 676 } else { 677 $this->log('Authenticated', AUTH_LOG_DEBUG); 678 $this->_disconnect(); 679 return true; // user authenticated 680 } // checkGroup 681 } // bind 682 } // non-empty password 683 } while ($this->options['try_all'] == true); // interate through entries 684 } // get results 685 // default 686 $this->log('NOT authenticated!', AUTH_LOG_DEBUG); 687 $this->_disconnect(); 688 return false; 689 } 690 691 // }}} 692 // {{{ checkGroup() 693 694 /** 695 * Validate group membership 696 * 697 * Searches the LDAP server for group membership of the 698 * supplied username. Quotes all LDAP filter meta characters in 699 * the user name before querying the LDAP server. 700 * 701 * @param string Distinguished Name of the authenticated User 702 * @return boolean 703 */ 704 function checkGroup($user) 705 { 706 $this->log('Auth_Container_LDAP::checkGroup() called.', AUTH_LOG_DEBUG); 707 $err = $this->_prepare(); 708 if ($err !== true) { 709 return PEAR::raiseError($err->getMessage(), $err->getCode()); 710 } 711 712 // make filter 713 $filter = sprintf('(&(%s=%s)(%s=%s)%s)', 714 $this->options['groupattr'], 715 $this->options['group'], 716 $this->options['memberattr'], 717 $this->_quoteFilterString($user), 718 $this->options['groupfilter']); 719 720 // make search base dn 721 $search_basedn = $this->options['groupdn']; 722 if ($search_basedn != '' && substr($search_basedn, -1) != ',') { 723 $search_basedn .= ','; 724 } 725 $search_basedn .= $this->options['basedn']; 726 727 $func_params = array($this->conn_id, $search_basedn, $filter, 728 array($this->options['memberattr'])); 729 $func_name = $this->_scope2function($this->options['groupscope']); 730 731 $this->log("Searching with $func_name and filter $filter in $search_basedn", AUTH_LOG_DEBUG); 732 733 // search 734 if (($result_id = @call_user_func_array($func_name, $func_params)) != false) { 735 if (@ldap_count_entries($this->conn_id, $result_id) == 1) { 736 @ldap_free_result($result_id); 737 $this->log('User is member of group', AUTH_LOG_DEBUG); 738 return true; 739 } 740 } 741 // default 742 $this->log('User is NOT member of group', AUTH_LOG_DEBUG); 743 return false; 744 } 745 746 // }}} 747 // {{{ _quoteFilterString() 748 749 /** 750 * Escapes LDAP filter special characters as defined in RFC 2254. 751 * 752 * @access private 753 * @param string Filter String 754 */ 755 function _quoteFilterString($filter_str) 756 { 757 $metas = array( '\\', '*', '(', ')', "\x00"); 758 $quoted_metas = array('\\\\', '\*', '\(', '\)', "\\\x00"); 759 return str_replace($metas, $quoted_metas, $filter_str); 760 } 761 762 // }}} 763 764} 765 766?> 767