1<?php 2// must be run within Dokuwiki 3if(!defined('DOKU_INC')) die(); 4 5/** 6 * LDAP authentication backend with local ACL 7 * 8 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 9 * @author Andreas Gohr <andi@splitbrain.org> 10 * @author Chris Smith <chris@jalakaic.co.uk> 11 * @author Jan Schumann <js@schumann-it.com> 12 * @author Trouble 13 * @author Dan Allen <dan.j.allen@gmail.com> 14 * @author <evaldas.auryla@pheur.org> 15 * @author Stephane Chazelas <stephane.chazelas@emerson.com> 16 * @author Steffen Schoch <schoch@dsb.net> 17 * @author Troels Liebe Bentsen <tlb@rapanden.dk> 18 * @author Philip Knack <p.knack@stollfuss.de> 19 * @author Klaus Vormweg <klaus.vormweg@gmx.de> 20 */ 21class auth_plugin_authldaplocal extends DokuWiki_Auth_Plugin { 22 /* @var resource $con holds the LDAP connection*/ 23 protected $con = null; 24 25 /* @var int $bound What type of connection does already exist? */ 26 protected $bound = 0; // 0: anonymous, 1: user, 2: superuser 27 28 /* @var array $users User data cache */ 29 protected $users = null; 30 31 /* @var array $_pattern User filter pattern */ 32 protected $_pattern = null; 33 34 /** 35 * Constructor 36 * 37 * Carry out sanity checks to ensure the object is 38 * able to operate. Set capabilities. 39 * 40 */ 41 public function __construct() { 42 parent::__construct(); 43 global $config_cascade; 44 45 if(!@is_readable($config_cascade['plainauth.users']['default'])) { 46 $this->success = false; 47 } else { 48 if(@is_writable($config_cascade['plainauth.users']['default'])) { 49 $this->cando['addUser'] = true; 50 $this->cando['delUser'] = true; 51 $this->cando['modLogin'] = true; 52 $this->cando['modGroups'] = true; 53 } 54 $this->cando['getUsers'] = true; 55 $this->cando['getGroups'] = true; 56 $this->cando['getUserCount'] = true; 57 $this->cando['logout'] = true; 58 } 59 // ldap extension is needed 60 if(!function_exists('ldap_connect')) { 61 $this->_debug("LDAP err: PHP LDAP extension not found.", -1, __LINE__, __FILE__); 62 $this->success = false; 63 return; 64 } 65 66 // Add the capabilities to change the password 67 $this->cando['modPass'] = $this->getConf('modPass'); 68 } 69 70 /** 71 * Check user+password 72 * 73 * Checks if the given user exists and the given 74 * plaintext password is correct by trying to bind 75 * to the LDAP server 76 * 77 * @param string $user 78 * @param string $pass 79 * @return bool 80 */ 81 public function checkPass($user, $pass) { 82 // reject empty password 83 if(empty($pass)) return false; 84 if(!$this->_openLDAP()) return false; 85 86 // check if local user exists 87 if($this->users === null) $this->_loadUserData(); 88 if(!isset($this->users[$user])) return false; 89 90 // indirect user bind 91 if($this->getConf('binddn') && $this->getConf('bindpw')) { 92 // use superuser credentials 93 if(!@ldap_bind($this->con, $this->getConf('binddn'), conf_decodeString($this->getConf('bindpw')))) { 94 $this->_debug('LDAP bind as superuser: '.htmlspecialchars(ldap_error($this->con)), 0, __LINE__, __FILE__); 95 return false; 96 } 97 $this->bound = 2; 98 } else if($this->getConf('binddn') && 99 $this->getConf('usertree') && 100 $this->getConf('userfilter') 101 ) { 102 // special bind string 103 $dn = $this->_makeFilter( 104 $this->getConf('binddn'), 105 array('user'=> $user, 'server'=> $this->getConf('server')) 106 ); 107 108 } else if(strpos($this->getConf('usertree'), '%{user}')) { 109 // direct user bind 110 $dn = $this->_makeFilter( 111 $this->getConf('usertree'), 112 array('user'=> $user, 'server'=> $this->getConf('server')) 113 ); 114 115 } else { 116 // Anonymous bind 117 if(!@ldap_bind($this->con)) { 118 msg("LDAP: can not bind anonymously", -1); 119 $this->_debug('LDAP anonymous bind: '.htmlspecialchars(ldap_error($this->con)), 0, __LINE__, __FILE__); 120 return false; 121 } 122 } 123 124 // Try to bind to with the dn if we have one. 125 if(!empty($dn)) { 126 // User/Password bind 127 if(!@ldap_bind($this->con, $dn, $pass)) { 128 $this->_debug("LDAP: bind with $dn failed", -1, __LINE__, __FILE__); 129 $this->_debug('LDAP user dn bind: '.htmlspecialchars(ldap_error($this->con)), 0, __LINE__, __FILE__); 130 return false; 131 } 132 $this->bound = 1; 133 return true; 134 } else { 135 // See if we can find the user 136 $info = $this->_getUserData($user, true); 137 if(empty($info['dn'])) { 138 return false; 139 } else { 140 $dn = $info['dn']; 141 } 142 143 // Try to bind with the dn provided 144 if(!@ldap_bind($this->con, $dn, $pass)) { 145 $this->_debug("LDAP: bind with $dn failed", -1, __LINE__, __FILE__); 146 $this->_debug('LDAP user bind: '.htmlspecialchars(ldap_error($this->con)), 0, __LINE__, __FILE__); 147 return false; 148 } 149 $this->bound = 1; 150 return true; 151 } 152 return false; 153 } 154 155 /** 156 * Return user info 157 * 158 * Returns info about the given user needs to contain 159 * at least these fields: 160 * 161 * name string full name of the user 162 * mail string email addres of the user 163 * grps array list of groups the user is in 164 * 165 * @param string $user 166 * @param bool $requireGroups (optional) - ignored, groups are always supplied by this plugin 167 * @return array containing user data or false 168 */ 169 public function getUserData($user, $requireGroups=true) { 170 return $this->_getUserData($user); 171 } 172 173 /** 174 * @param string $user 175 * @param bool $inbind authldap specific, true if in bind phase 176 * @return array containing user data or false 177 */ 178 protected function _getUserData($user, $inbind = false) { 179 global $conf; 180 if(!$this->_openLDAP()) return false; 181 182 // force superuser bind if wanted and not bound as superuser yet 183 if($this->getConf('binddn') && $this->getConf('bindpw') && $this->bound < 2) { 184 // use superuser credentials 185 if(!@ldap_bind($this->con, $this->getConf('binddn'), conf_decodeString($this->getConf('bindpw')))) { 186 $this->_debug('LDAP bind as superuser: '.htmlspecialchars(ldap_error($this->con)), 0, __LINE__, __FILE__); 187 return false; 188 } 189 $this->bound = 2; 190 } elseif($this->bound == 0 && !$inbind) { 191 // in some cases getUserData is called outside the authentication workflow 192 // eg. for sending email notification on subscribed pages. This data might not 193 // be accessible anonymously, so we try to rebind the current user here 194 list($loginuser, $loginsticky, $loginpass) = auth_getCookie(); 195 if($loginuser && $loginpass) { 196 $loginpass = auth_decrypt($loginpass, auth_cookiesalt(!$loginsticky, true)); 197 $this->checkPass($loginuser, $loginpass); 198 } 199 } 200 201 $info = array(); 202 $info['user'] = $user; 203 $info['server'] = $this->getConf('server'); 204 205 //get info for given user 206 $base = $this->_makeFilter($this->getConf('usertree'), $info); 207 if($this->getConf('userfilter')) { 208 $filter = $this->_makeFilter($this->getConf('userfilter'), $info); 209 } else { 210 $filter = "(ObjectClass=*)"; 211 } 212 213 $sr = $this->_ldapsearch($this->con, $base, $filter, $this->getConf('userscope')); 214 $result = @ldap_get_entries($this->con, $sr); 215 $this->_debug('LDAP user search: '.htmlspecialchars(ldap_error($this->con)), 0, __LINE__, __FILE__); 216 $this->_debug('LDAP search at: '.htmlspecialchars($base.' '.$filter), 0, __LINE__, __FILE__); 217 218 // Don't accept more or less than one response 219 if(!is_array($result) || $result['count'] != 1) { 220 return false; //user not found 221 } 222 223 $user_result = $result[0]; 224 ldap_free_result($sr); 225 226 // general user info 227 $info['dn'] = $user_result['dn']; 228 $info['gid'] = $user_result['gidnumber'][0]; 229 $info['mail'] = $user_result['mail'][0]; 230 if(isset($user_result['displayname'][0]) and $user_result['displayname'][0] != '') { 231 $info['name'] = $user_result['displayname'][0]; 232 } else { 233 $info['name'] = $user_result['cn'][0]; 234 } 235 $info['grps'] = array(); 236 237 // overwrite if other attribs are specified. 238 if(is_array($this->getConf('mapping'))) { 239 foreach($this->getConf('mapping') as $localkey => $key) { 240 if(is_array($key)) { 241 // use regexp to clean up user_result 242 list($key, $regexp) = each($key); 243 if($user_result[$key]) foreach($user_result[$key] as $grpkey => $grp) { 244 if($grpkey !== 'count' && preg_match($regexp, $grp, $match)) { 245 if($localkey == 'grps') { 246 $info[$localkey][] = $match[1]; 247 } else { 248 $info[$localkey] = $match[1]; 249 } 250 } 251 } 252 } else { 253 $info[$localkey] = $user_result[$key][0]; 254 } 255 } 256 } 257 $user_result = array_merge($info, $user_result); 258 259 //get groups for given user if grouptree is given 260 if($this->getConf('grouptree') || $this->getConf('groupfilter')) { 261 $base = $this->_makeFilter($this->getConf('grouptree'), $user_result); 262 $filter = $this->_makeFilter($this->getConf('groupfilter'), $user_result); 263 $sr = $this->_ldapsearch($this->con, $base, $filter, $this->getConf('groupscope'), array($this->getConf('groupkey'))); 264 $this->_debug('LDAP group search: '.htmlspecialchars(ldap_error($this->con)), 0, __LINE__, __FILE__); 265 $this->_debug('LDAP search at: '.htmlspecialchars($base.' '.$filter), 0, __LINE__, __FILE__); 266 267 if(!$sr) { 268 msg("LDAP: Reading group memberships failed", -1); 269 $this->_debug('LDAP group search: '.htmlspecialchars(ldap_error($this->con)), 0,__LINE__,__FILE__); 270 return false; 271 } 272 $result = ldap_get_entries($this->con, $sr); 273 ldap_free_result($sr); 274 275 if(is_array($result)) foreach($result as $grp) { 276 if(!empty($grp[$this->getConf('groupkey')])) { 277 $group = $grp[$this->getConf('groupkey')]; 278 if(is_array($group)){ 279 $group = $group[0]; 280 } else { 281 $this->_debug('groupkey did not return a detailed result', 0, __LINE__, __FILE__); 282 } 283 if($group === '') continue; 284 285 $this->_debug('LDAP usergroup: '.htmlspecialchars($group), 0, __LINE__, __FILE__); 286 $info['grps'][] = $group; 287 } 288 } 289 } 290 // merge local groups into group list 291 if($this->users === null) $this->_loadUserData(); 292 if(is_array($this->users[$user]['grps'])) { 293 foreach($this->users[$user]['grps'] as $group) { 294 if(in_array($group,$info['grps'])) continue; 295 $info['grps'][] = $group; 296 } 297 } 298 return $info; 299 } 300 301 /** 302 * Most values in LDAP are case-insensitive 303 * 304 * @return bool 305 */ 306 public function isCaseSensitive() { 307 return false; 308 } 309 310 /** 311 * Creates a string suitable for saving as a line 312 * in the file database 313 * (delimiters escaped, etc.) 314 * 315 * @param string $user 316 * @param string $pass 317 * @param string $name 318 * @param string $mail 319 * @param array $grps list of groups the user is in 320 * @return string 321 */ 322 protected function _createUserLine($user, $pass, $name, $mail, $grps) { 323 $groups = join(',', $grps); 324 $userline = array($user, $pass, $name, $mail, $groups); 325 $userline = str_replace('\\', '\\\\', $userline); // escape \ as \\ 326 $userline = str_replace(':', '\\:', $userline); // escape : as \: 327 $userline = join(':', $userline)."\n"; 328 return $userline; 329 } 330 331 /** 332 * Create a new User 333 * 334 * Returns false if the user already exists, null when an error 335 * occurred and true if everything went well. 336 * 337 * The new user will be added to the default group by this 338 * function if grps are not specified (default behaviour). 339 * 340 * @param string $user 341 * @param string $pwd 342 * @param string $name 343 * @param string $mail 344 * @param array $grps 345 * @return bool|null|string 346 */ 347 public function createUser($user, $pwd, $name, $mail, $grps = null) { 348 global $conf; 349 global $config_cascade; 350 351 // local user mustn't already exist 352 if($this->users === null) $this->_loadUserData(); 353 if(isset($this->users[$user])) { 354 msg('The user '.$user.' does already exist',-1); 355 return false; 356 } 357 // but the user must exist in LDAP 358 $info = $this->_getUserData($user); 359 if(empty($info['dn'])) { 360 msg('The user '.$user.' does not exist in LDAP',-1); 361 return false; 362 } 363 // fetch real name and email and groups from LDAP 364 $name = $info['name']; 365 $mail = $info['mail']; 366 $pass = ''; 367 if(is_array($grps)) { 368 $grps = array_merge($grps, $info['grps']); 369 } else { 370 $grps = $info['grps']; 371 } 372 373 // set default group if no groups specified 374 if(!is_array($grps) or !$grps) $grps = array($conf['defaultgroup']); 375 376 // prepare user line 377 $userline = $this->_createUserLine($user, $pass, $name, $mail, $grps); 378 379 if(!io_saveFile($config_cascade['plainauth.users']['default'], $userline, true)) { 380 msg($this->getLang('writefail'), -1); 381 return false; 382 } 383 384 $this->users[$user] = compact('pass','name','mail','grps'); 385 return true; 386 } 387 388 /** 389 * Modify user data 390 * 391 * @param string $user nick of the user to be changed 392 * @param array $changes array of field/value pairs to be changed (password will be clear text) 393 * @return bool 394 */ 395 public function modifyUser($user, $changes) { 396 global $ACT; 397 global $config_cascade; 398 399 // sanity checks, user must already exist and there must be something to change 400 if(($userinfo = $this->getUserData($user)) === false) { 401 msg($this->getLang('usernotexists'), -1); 402 return false; 403 } 404 405 // don't modify protected users 406 if(!empty($userinfo['protected'])) { 407 msg(sprintf($this->getLang('protected'), hsc($user)), -1); 408 return false; 409 } 410 411 if(!is_array($changes) || !count($changes)) return true; 412 413 // update userinfo with new data, remembering to encrypt any password 414 $newuser = $user; 415 foreach($changes as $field => $value) { 416 if($field == 'user') { 417 $newuser = $value; 418 continue; 419 } 420 if($field == 'pass') $value = auth_cryptPassword($value); 421 $userinfo[$field] = $value; 422 } 423 424 $userline = $this->_createUserLine($newuser, $userinfo['pass'], $userinfo['name'], $userinfo['mail'], $userinfo['grps']); 425 426 if(!io_replaceInFile($config_cascade['plainauth.users']['default'], '/^'.$user.':/', $userline, true)) { 427 msg('There was an error modifying your user data. You may need to register again.', -1); 428 // FIXME, io functions should be fail-safe so existing data isn't lost 429 $ACT = 'register'; 430 return false; 431 } 432 433 $this->users[$newuser] = $userinfo; 434 return true; 435 } 436 437 /** 438 * Remove one or more users from the list of registered users 439 * 440 * @param array $users array of users to be deleted 441 * @return int the number of users deleted 442 */ 443 public function deleteUsers($users) { 444 global $config_cascade; 445 446 if(!is_array($users) || empty($users)) return 0; 447 448 if($this->users === null) $this->_loadUserData(); 449 450 $deleted = array(); 451 foreach($users as $user) { 452 // don't delete protected users 453 if(!empty($this->users[$user]['protected'])) { 454 msg(sprintf($this->getLang('protected'), hsc($user)), -1); 455 continue; 456 } 457 if(isset($this->users[$user])) $deleted[] = preg_quote($user, '/'); 458 } 459 460 if(empty($deleted)) return 0; 461 462 $pattern = '/^('.join('|', $deleted).'):/'; 463 if (!io_deleteFromFile($config_cascade['plainauth.users']['default'], $pattern, true)) { 464 msg($this->getLang('writefail'), -1); 465 return 0; 466 } 467 468 // reload the user list and count the difference 469 $count = count($this->users); 470 $this->_loadUserData(); 471 $count -= count($this->users); 472 return $count; 473 } 474 475 /** 476 * Return a count of the number of user which meet $filter criteria 477 * 478 * @param array $filter 479 * @return int 480 */ 481 public function getUserCount($filter = array()) { 482 483 if($this->users === null) $this->_loadUserData(); 484 485 if(!count($filter)) return count($this->users); 486 487 $count = 0; 488 $this->_constructPattern($filter); 489 490 foreach($this->users as $user => $info) { 491 $count += $this->_filter($user, $info); 492 } 493 494 return $count; 495 } 496 497 /** 498 * Bulk retrieval of user data 499 * 500 * @param int $start index of first user to be returned 501 * @param int $limit max number of users to be returned 502 * @param array $filter array of field/pattern pairs 503 * @return array userinfo (refer getUserData for internal userinfo details) 504 */ 505 public function retrieveUsers($start = 0, $limit = 0, $filter = array()) { 506 507 if($this->users === null) $this->_loadUserData(); 508 509 ksort($this->users); 510 511 $i = 0; 512 $count = 0; 513 $out = array(); 514 $this->_constructPattern($filter); 515 516 foreach($this->users as $user => $info) { 517 if($this->_filter($user, $info)) { 518 if($i >= $start) { 519 $out[$user] = $info; 520 $count++; 521 if(($limit > 0) && ($count >= $limit)) break; 522 } 523 $i++; 524 } 525 } 526 527 return $out; 528 } 529 530 /** 531 * Make LDAP filter strings. 532 * 533 * Used by auth_getUserData to make the filter 534 * strings for grouptree and groupfilter 535 * 536 * @param string $filter ldap search filter with placeholders 537 * @param array $placeholders placeholders to fill in 538 * @return string 539 */ 540 protected function _makeFilter($filter, $placeholders) { 541 preg_match_all("/%{([^}]+)/", $filter, $matches, PREG_PATTERN_ORDER); 542 //replace each match 543 foreach($matches[1] as $match) { 544 //take first element if array 545 if(is_array($placeholders[$match])) { 546 $value = $placeholders[$match][0]; 547 } else { 548 $value = $placeholders[$match]; 549 } 550 $value = $this->_filterEscape($value); 551 $filter = str_replace('%{'.$match.'}', $value, $filter); 552 } 553 return $filter; 554 } 555 556 /** 557 * Only valid pageid's (no namespaces) for usernames 558 * 559 * @param string $user 560 * @return string 561 */ 562 public function cleanUser($user) { 563 global $conf; 564 return cleanID(str_replace(':', $conf['sepchar'], $user)); 565 } 566 567 /** 568 * Only valid pageid's (no namespaces) for groupnames 569 * 570 * @param string $group 571 * @return string 572 */ 573 public function cleanGroup($group) { 574 global $conf; 575 return cleanID(str_replace(':', $conf['sepchar'], $group)); 576 } 577 578 /** 579 * Load all user data 580 * 581 * loads the user file into a datastructure 582 */ 583 protected function _loadUserData() { 584 global $config_cascade; 585 586 $this->users = $this->_readUserFile($config_cascade['plainauth.users']['default']); 587 588 // support protected users 589 if(!empty($config_cascade['plainauth.users']['protected'])) { 590 $protected = $this->_readUserFile($config_cascade['plainauth.users']['protected']); 591 foreach(array_keys($protected) as $key) { 592 $protected[$key]['protected'] = true; 593 } 594 $this->users = array_merge($this->users, $protected); 595 } 596 } 597 598 /** 599 * Read user data from given file 600 * 601 * ignores non existing files 602 * 603 * @param string $file the file to load data from 604 * @return array 605 */ 606 protected function _readUserFile($file) { 607 $users = array(); 608 if(!file_exists($file)) return $users; 609 610 $lines = file($file); 611 foreach($lines as $line) { 612 $line = preg_replace('/#.*$/', '', $line); //ignore comments 613 $line = trim($line); 614 if(empty($line)) continue; 615 616 $row = $this->_splitUserData($line); 617 $row = str_replace('\\:', ':', $row); 618 $row = str_replace('\\\\', '\\', $row); 619 620 $groups = array_values(array_filter(explode(",", $row[4]))); 621 622 $users[$row[0]]['pass'] = $row[1]; 623 $users[$row[0]]['name'] = urldecode($row[2]); 624 $users[$row[0]]['mail'] = $row[3]; 625 $users[$row[0]]['grps'] = $groups; 626 } 627 return $users; 628 } 629 630 protected function _splitUserData($line){ 631 // due to a bug in PCRE 6.6, preg_split will fail with the regex we use here 632 // refer github issues 877 & 885 633 if ($this->_pregsplit_safe){ 634 return preg_split('/(?<![^\\\\]\\\\)\:/', $line, 5); // allow for : escaped as \: 635 } 636 637 $row = array(); 638 $piece = ''; 639 $len = strlen($line); 640 for($i=0; $i<$len; $i++){ 641 if ($line[$i]=='\\'){ 642 $piece .= $line[$i]; 643 $i++; 644 if ($i>=$len) break; 645 } else if ($line[$i]==':'){ 646 $row[] = $piece; 647 $piece = ''; 648 continue; 649 } 650 $piece .= $line[$i]; 651 } 652 $row[] = $piece; 653 654 return $row; 655 } 656 657 /** 658 * return true if $user + $info match $filter criteria, false otherwise 659 * 660 * @param string $user User login 661 * @param array $info User's userinfo array 662 * @return bool 663 */ 664 protected function _filter($user, $info) { 665 foreach($this->_pattern as $item => $pattern) { 666 if($item == 'user') { 667 if(!preg_match($pattern, $user)) return false; 668 } else if($item == 'grps') { 669 if(!count(preg_grep($pattern, $info['grps']))) return false; 670 } else { 671 if(!preg_match($pattern, $info[$item])) return false; 672 } 673 } 674 return true; 675 } 676 677 /** 678 * construct a filter pattern 679 * 680 * @param array $filter 681 */ 682 protected function _constructPattern($filter) { 683 $this->_pattern = array(); 684 foreach($filter as $item => $pattern) { 685 $this->_pattern[$item] = '/'.str_replace('/', '\/', $pattern).'/i'; // allow regex characters 686 } 687 } 688 689 /** 690 * Escape a string to be used in a LDAP filter 691 * 692 * Ported from Perl's Net::LDAP::Util escape_filter_value 693 * 694 * @param string $string 695 * @return string 696 */ 697 protected function _filterEscape($string) { 698 // see https://github.com/adldap/adLDAP/issues/22 699 return preg_replace_callback( 700 '/([\x00-\x1F\*\(\)\\\\])/', 701 function ($matches) { 702 return "\\".join("", unpack("H2", $matches[1])); 703 }, 704 $string 705 ); 706 } 707 708 /** 709 * Opens a connection to the configured LDAP server and sets the wanted 710 * option on the connection 711 */ 712 protected function _openLDAP() { 713 if($this->con) return true; // connection already established 714 715 if($this->getConf('debug')) { 716 ldap_set_option(NULL, LDAP_OPT_DEBUG_LEVEL, 7); 717 } 718 719 $this->bound = 0; 720 721 $port = $this->getConf('port'); 722 $bound = false; 723 $servers = explode(',', $this->getConf('server')); 724 foreach($servers as $server) { 725 $server = trim($server); 726 $this->con = @ldap_connect($server, $port); 727 if(!$this->con) { 728 continue; 729 } 730 731 /* 732 * When OpenLDAP 2.x.x is used, ldap_connect() will always return a resource as it does 733 * not actually connect but just initializes the connecting parameters. The actual 734 * connect happens with the next calls to ldap_* funcs, usually with ldap_bind(). 735 * 736 * So we should try to bind to server in order to check its availability. 737 */ 738 739 //set protocol version and dependend options 740 if($this->getConf('version')) { 741 if(!@ldap_set_option( 742 $this->con, LDAP_OPT_PROTOCOL_VERSION, 743 $this->getConf('version') 744 ) 745 ) { 746 msg('Setting LDAP Protocol version '.$this->getConf('version').' failed', -1); 747 $this->_debug('LDAP version set: '.htmlspecialchars(ldap_error($this->con)), 0, __LINE__, __FILE__); 748 } else { 749 //use TLS (needs version 3) 750 if($this->getConf('starttls')) { 751 if(!@ldap_start_tls($this->con)) { 752 msg('Starting TLS failed', -1); 753 $this->_debug('LDAP TLS set: '.htmlspecialchars(ldap_error($this->con)), 0, __LINE__, __FILE__); 754 } 755 } 756 // needs version 3 757 if($this->getConf('referrals') > -1) { 758 if(!@ldap_set_option( 759 $this->con, LDAP_OPT_REFERRALS, 760 $this->getConf('referrals') 761 ) 762 ) { 763 msg('Setting LDAP referrals failed', -1); 764 $this->_debug('LDAP referal set: '.htmlspecialchars(ldap_error($this->con)), 0, __LINE__, __FILE__); 765 } 766 } 767 } 768 } 769 770 //set deref mode 771 if($this->getConf('deref')) { 772 if(!@ldap_set_option($this->con, LDAP_OPT_DEREF, $this->getConf('deref'))) { 773 msg('Setting LDAP Deref mode '.$this->getConf('deref').' failed', -1); 774 $this->_debug('LDAP deref set: '.htmlspecialchars(ldap_error($this->con)), 0, __LINE__, __FILE__); 775 } 776 } 777 /* As of PHP 5.3.0 we can set timeout to speedup skipping of invalid servers */ 778 if(defined('LDAP_OPT_NETWORK_TIMEOUT')) { 779 ldap_set_option($this->con, LDAP_OPT_NETWORK_TIMEOUT, 1); 780 } 781 782 if($this->getConf('binddn') && $this->getConf('bindpw')) { 783 $bound = @ldap_bind($this->con, $this->getConf('binddn'), conf_decodeString($this->getConf('bindpw'))); 784 $this->bound = 2; 785 } else { 786 $bound = @ldap_bind($this->con); 787 } 788 if($bound) { 789 break; 790 } 791 } 792 793 if(!$bound) { 794 msg("LDAP: couldn't connect to LDAP server", -1); 795 $this->_debug(ldap_error($this->con), 0, __LINE__, __FILE__); 796 return false; 797 } 798 799 $this->cando['getUsers'] = true; 800 return true; 801 } 802 803 /** 804 * Wraps around ldap_search, ldap_list or ldap_read depending on $scope 805 * 806 * @param resource $link_identifier 807 * @param string $base_dn 808 * @param string $filter 809 * @param string $scope can be 'base', 'one' or 'sub' 810 * @param null|array $attributes 811 * @param int $attrsonly 812 * @param int $sizelimit 813 * @return resource 814 */ 815 protected function _ldapsearch($link_identifier, $base_dn, $filter, $scope = 'sub', $attributes = null, 816 $attrsonly = 0, $sizelimit = 0) { 817 if(is_null($attributes)) $attributes = array(); 818 819 if($scope == 'base') { 820 return @ldap_read( 821 $link_identifier, $base_dn, $filter, $attributes, 822 $attrsonly, $sizelimit 823 ); 824 } elseif($scope == 'one') { 825 return @ldap_list( 826 $link_identifier, $base_dn, $filter, $attributes, 827 $attrsonly, $sizelimit 828 ); 829 } else { 830 return @ldap_search( 831 $link_identifier, $base_dn, $filter, $attributes, 832 $attrsonly, $sizelimit 833 ); 834 } 835 } 836 837 /** 838 * Wrapper around msg() but outputs only when debug is enabled 839 * 840 * @param string $message 841 * @param int $err 842 * @param int $line 843 * @param string $file 844 * @return void 845 */ 846 protected function _debug($message, $err, $line, $file) { 847 if(!$this->getConf('debug')) return; 848 msg($message, $err, $line, $file); 849 } 850 851} 852