1<?php 2use dokuwiki\Extension\AuthPlugin; 3use dokuwiki\Utf8\Clean; 4use dokuwiki\Utf8\PhpString; 5use dokuwiki\Utf8\Sort; 6use dokuwiki\Logger; 7 8/** 9 * Active Directory authentication backend for DokuWiki 10 * 11 * This makes authentication with a Active Directory server much easier 12 * than when using the normal LDAP backend by utilizing the adLDAP library 13 * 14 * Usage: 15 * Set DokuWiki's local.protected.php auth setting to read 16 * 17 * $conf['authtype'] = 'authad'; 18 * 19 * $conf['plugin']['authad']['account_suffix'] = '@my.domain.org'; 20 * $conf['plugin']['authad']['base_dn'] = 'DC=my,DC=domain,DC=org'; 21 * $conf['plugin']['authad']['domain_controllers'] = 'srv1.domain.org,srv2.domain.org'; 22 * 23 * //optional: 24 * $conf['plugin']['authad']['sso'] = 1; 25 * $conf['plugin']['authad']['admin_username'] = 'root'; 26 * $conf['plugin']['authad']['admin_password'] = 'pass'; 27 * $conf['plugin']['authad']['real_primarygroup'] = 1; 28 * $conf['plugin']['authad']['use_ssl'] = 1; 29 * $conf['plugin']['authad']['use_tls'] = 1; 30 * $conf['plugin']['authad']['debug'] = 1; 31 * // warn user about expiring password this many days in advance: 32 * $conf['plugin']['authad']['expirywarn'] = 5; 33 * 34 * // get additional information to the userinfo array 35 * // add a list of comma separated ldap contact fields. 36 * $conf['plugin']['authad']['additional'] = 'field1,field2'; 37 * 38 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 39 * @author James Van Lommel <jamesvl@gmail.com> 40 * @link http://www.nosq.com/blog/2005/08/ldap-activedirectory-and-dokuwiki/ 41 * @author Andreas Gohr <andi@splitbrain.org> 42 * @author Jan Schumann <js@schumann-it.com> 43 */ 44class auth_plugin_authad extends AuthPlugin 45{ 46 47 /** 48 * @var array hold connection data for a specific AD domain 49 */ 50 protected $opts = []; 51 52 /** 53 * @var array open connections for each AD domain, as adLDAP objects 54 */ 55 protected $adldap = []; 56 57 /** 58 * @var bool message state 59 */ 60 protected $msgshown = false; 61 62 /** 63 * @var array user listing cache 64 */ 65 protected $users = []; 66 67 /** 68 * @var array filter patterns for listing users 69 */ 70 protected $pattern = []; 71 72 protected $grpsusers = []; 73 74 /** 75 * Constructor 76 */ 77 public function __construct() 78 { 79 global $INPUT; 80 parent::__construct(); 81 82 require_once(DOKU_PLUGIN.'authad/adLDAP/adLDAP.php'); 83 require_once(DOKU_PLUGIN.'authad/adLDAP/classes/adLDAPUtils.php'); 84 85 // we load the config early to modify it a bit here 86 $this->loadConfig(); 87 88 // additional information fields 89 if (isset($this->conf['additional'])) { 90 $this->conf['additional'] = str_replace(' ', '', $this->conf['additional']); 91 $this->conf['additional'] = explode(',', $this->conf['additional']); 92 } else $this->conf['additional'] = []; 93 94 // ldap extension is needed 95 if (!function_exists('ldap_connect')) { 96 if ($this->conf['debug']) 97 msg("AD Auth: PHP LDAP extension not found.", -1); 98 $this->success = false; 99 return; 100 } 101 102 // Prepare SSO 103 if (!empty($INPUT->server->str('REMOTE_USER'))) { 104 // make sure the right encoding is used 105 if ($this->getConf('sso_charset')) { 106 $INPUT->server->set( 107 'REMOTE_USER', 108 iconv($this->getConf('sso_charset'), 'UTF-8', $INPUT->server->str('REMOTE_USER')) 109 ); 110 } elseif (!Clean::isUtf8($INPUT->server->str('REMOTE_USER'))) { 111 $INPUT->server->set('REMOTE_USER', utf8_encode($INPUT->server->str('REMOTE_USER'))); 112 } 113 114 // trust the incoming user 115 if ($this->conf['sso']) { 116 $INPUT->server->set('REMOTE_USER', $this->cleanUser($INPUT->server->str('REMOTE_USER'))); 117 118 // we need to simulate a login 119 if (empty($_COOKIE[DOKU_COOKIE])) { 120 $INPUT->set('u', $INPUT->server->str('REMOTE_USER')); 121 $INPUT->set('p', 'sso_only'); 122 } 123 } 124 } 125 126 // other can do's are changed in $this->_loadServerConfig() base on domain setup 127 $this->cando['modName'] = (bool)$this->conf['update_name']; 128 $this->cando['modMail'] = (bool)$this->conf['update_mail']; 129 $this->cando['getUserCount'] = true; 130 } 131 132 /** 133 * Load domain config on capability check 134 * 135 * @param string $cap 136 * @return bool 137 */ 138 public function canDo($cap) 139 { 140 global $INPUT; 141 //capabilities depend on config, which may change depending on domain 142 $domain = $this->getUserDomain($INPUT->server->str('REMOTE_USER')); 143 $this->loadServerConfig($domain); 144 return parent::canDo($cap); 145 } 146 147 /** 148 * Check user+password [required auth function] 149 * 150 * Checks if the given user exists and the given 151 * plaintext password is correct by trying to bind 152 * to the LDAP server 153 * 154 * @author James Van Lommel <james@nosq.com> 155 * @param string $user 156 * @param string $pass 157 * @return bool 158 */ 159 public function checkPass($user, $pass) 160 { 161 global $INPUT; 162 if ($INPUT->server->str('REMOTE_USER') == $user && 163 $this->conf['sso'] 164 ) return true; 165 166 $adldap = $this->initAdLdap($this->getUserDomain($user)); 167 if (!$adldap) return false; 168 169 try { 170 return $adldap->authenticate($this->getUserName($user), $pass); 171 } catch (adLDAPException $e) { 172 // shouldn't really happen 173 return false; 174 } 175 } 176 177 /** 178 * Return user info [required auth function] 179 * 180 * Returns info about the given user needs to contain 181 * at least these fields: 182 * 183 * name string full name of the user 184 * mail string email address of the user 185 * grps array list of groups the user is in 186 * 187 * This AD specific function returns the following 188 * addional fields: 189 * 190 * dn string distinguished name (DN) 191 * uid string samaccountname 192 * lastpwd int timestamp of the date when the password was set 193 * expires true if the password expires 194 * expiresin int seconds until the password expires 195 * any fields specified in the 'additional' config option 196 * 197 * @author James Van Lommel <james@nosq.com> 198 * @param string $user 199 * @param bool $requireGroups (optional) - ignored, groups are always supplied by this plugin 200 * @return array 201 */ 202 public function getUserData($user, $requireGroups = true) 203 { 204 global $conf; 205 global $lang; 206 global $ID; 207 global $INPUT; 208 $adldap = $this->initAdLdap($this->getUserDomain($user)); 209 if (!$adldap) return []; 210 211 if ($user == '') return []; 212 213 $fields = ['mail', 'displayname', 'samaccountname', 'lastpwd', 'pwdlastset', 'useraccountcontrol']; 214 215 // add additional fields to read 216 $fields = array_merge($fields, $this->conf['additional']); 217 $fields = array_unique($fields); 218 $fields = array_filter($fields); 219 220 //get info for given user 221 $result = $adldap->user()->info($this->getUserName($user), $fields); 222 if ($result == false) { 223 return []; 224 } 225 226 //general user info 227 $info = []; 228 $info['name'] = $result[0]['displayname'][0]; 229 $info['mail'] = $result[0]['mail'][0]; 230 $info['uid'] = $result[0]['samaccountname'][0]; 231 $info['dn'] = $result[0]['dn']; 232 //last password set (Windows counts from January 1st 1601) 233 $info['lastpwd'] = $result[0]['pwdlastset'][0] / 10_000_000 - 11_644_473_600; 234 //will it expire? 235 $info['expires'] = !($result[0]['useraccountcontrol'][0] & 0x10000); //ADS_UF_DONT_EXPIRE_PASSWD 236 237 // additional information 238 foreach ($this->conf['additional'] as $field) { 239 if (isset($result[0][strtolower($field)])) { 240 $info[$field] = $result[0][strtolower($field)][0]; 241 } 242 } 243 244 // handle ActiveDirectory memberOf 245 $info['grps'] = $adldap->user()->groups($this->getUserName($user), (bool) $this->opts['recursive_groups']); 246 247 if (is_array($info['grps'])) { 248 foreach ($info['grps'] as $ndx => $group) { 249 $info['grps'][$ndx] = $this->cleanGroup($group); 250 } 251 } 252 253 // always add the default group to the list of groups 254 if (!is_array($info['grps']) || !in_array($conf['defaultgroup'], $info['grps'])) { 255 $info['grps'][] = $conf['defaultgroup']; 256 } 257 258 // add the user's domain to the groups 259 $domain = $this->getUserDomain($user); 260 if ($domain && !in_array("domain-$domain", $info['grps'])) { 261 $info['grps'][] = $this->cleanGroup("domain-$domain"); 262 } 263 264 // check expiry time 265 if ($info['expires'] && $this->conf['expirywarn']) { 266 try { 267 $expiry = $adldap->user()->passwordExpiry($user); 268 if (is_array($expiry)) { 269 $info['expiresat'] = $expiry['expiryts']; 270 $info['expiresin'] = round(($info['expiresat'] - time())/(24*60*60)); 271 272 // if this is the current user, warn him (once per request only) 273 if (($INPUT->server->str('REMOTE_USER') == $user) && 274 ($info['expiresin'] <= $this->conf['expirywarn']) && 275 !$this->msgshown 276 ) { 277 $msg = sprintf($this->getLang('authpwdexpire'), $info['expiresin']); 278 if ($this->canDo('modPass')) { 279 $url = wl($ID, ['do'=> 'profile']); 280 $msg .= ' <a href="'.$url.'">'.$lang['btn_profile'].'</a>'; 281 } 282 msg($msg); 283 $this->msgshown = true; 284 } 285 } 286 } catch (adLDAPException $e) { 287 // ignore. should usually not happen 288 } 289 } 290 291 return $info; 292 } 293 294 /** 295 * Make AD group names usable by DokuWiki. 296 * 297 * Removes backslashes ('\'), pound signs ('#'), and converts spaces to underscores. 298 * 299 * @author James Van Lommel (jamesvl@gmail.com) 300 * @param string $group 301 * @return string 302 */ 303 public function cleanGroup($group) 304 { 305 $group = str_replace('\\', '', $group); 306 $group = str_replace('#', '', $group); 307 $group = preg_replace('[\s]', '_', $group); 308 $group = PhpString::strtolower(trim($group)); 309 return $group; 310 } 311 312 /** 313 * Sanitize user names 314 * 315 * Normalizes domain parts, does not modify the user name itself (unlike cleanGroup) 316 * 317 * @author Andreas Gohr <gohr@cosmocode.de> 318 * @param string $user 319 * @return string 320 */ 321 public function cleanUser($user) 322 { 323 $domain = ''; 324 325 // get NTLM or Kerberos domain part 326 [$dom, $user] = sexplode('\\', $user, 2, ''); 327 if (!$user) $user = $dom; 328 if ($dom) $domain = $dom; 329 [$user, $dom] = sexplode('@', $user, 2, ''); 330 if ($dom) $domain = $dom; 331 332 // clean up both 333 $domain = PhpString::strtolower(trim($domain)); 334 $user = PhpString::strtolower(trim($user)); 335 336 // is this a known, valid domain or do we work without account suffix? if not discard 337 if ((!isset($this->conf[$domain]) || !is_array($this->conf[$domain])) && 338 $this->conf['account_suffix'] !== '') { 339 $domain = ''; 340 } 341 342 // reattach domain 343 if ($domain) $user = "$user@$domain"; 344 return $user; 345 } 346 347 /** 348 * Most values in LDAP are case-insensitive 349 * 350 * @return bool 351 */ 352 public function isCaseSensitive() 353 { 354 return false; 355 } 356 357 /** 358 * Create a Search-String useable by adLDAPUsers::all($includeDescription = false, $search = "*", $sorted = true) 359 * 360 * @param array $filter 361 * @return string 362 */ 363 protected function constructSearchString($filter) 364 { 365 if (!$filter) { 366 return '*'; 367 } 368 $adldapUtils = new adLDAPUtils($this->initAdLdap(null)); 369 $result = '*'; 370 if (isset($filter['name'])) { 371 $result .= ')(displayname=*' . $adldapUtils->ldapSlashes($filter['name']) . '*'; 372 unset($filter['name']); 373 } 374 375 if (isset($filter['user'])) { 376 $result .= ')(samAccountName=*' . $adldapUtils->ldapSlashes($filter['user']) . '*'; 377 unset($filter['user']); 378 } 379 380 if (isset($filter['mail'])) { 381 $result .= ')(mail=*' . $adldapUtils->ldapSlashes($filter['mail']) . '*'; 382 unset($filter['mail']); 383 } 384 return $result; 385 } 386 387 /** 388 * Return a count of the number of user which meet $filter criteria 389 * 390 * @param array $filter $filter array of field/pattern pairs, empty array for no filter 391 * @return int number of users 392 */ 393 public function getUserCount($filter = []) 394 { 395 $adldap = $this->initAdLdap(null); 396 if (!$adldap) { 397 Logger::debug("authad/auth.php getUserCount(): _adldap not set."); 398 return -1; 399 } 400 if ($filter == []) { 401 $result = $adldap->user()->all(); 402 } else { 403 $searchString = $this->constructSearchString($filter); 404 $result = $adldap->user()->all(false, $searchString); 405 if (isset($filter['grps'])) { 406 $this->users = array_fill_keys($result, false); 407 /** @var admin_plugin_usermanager $usermanager */ 408 $usermanager = plugin_load("admin", "usermanager", false); 409 $usermanager->setLastdisabled(true); 410 if (!isset($this->grpsusers[$this->filterToString($filter)])) { 411 $this->fillGroupUserArray($filter, $usermanager->getStart() + 3*$usermanager->getPagesize()); 412 } elseif (count($this->grpsusers[$this->filterToString($filter)]) < 413 $usermanager->getStart() + 3*$usermanager->getPagesize() 414 ) { 415 $this->fillGroupUserArray( 416 $filter, 417 $usermanager->getStart() + 418 3*$usermanager->getPagesize() - 419 count($this->grpsusers[$this->filterToString($filter)]) 420 ); 421 } 422 $result = $this->grpsusers[$this->filterToString($filter)]; 423 } else { 424 /** @var admin_plugin_usermanager $usermanager */ 425 $usermanager = plugin_load("admin", "usermanager", false); 426 $usermanager->setLastdisabled(false); 427 } 428 } 429 430 if (!$result) { 431 return 0; 432 } 433 return count($result); 434 } 435 436 /** 437 * 438 * create a unique string for each filter used with a group 439 * 440 * @param array $filter 441 * @return string 442 */ 443 protected function filterToString($filter) 444 { 445 $result = ''; 446 if (isset($filter['user'])) { 447 $result .= 'user-' . $filter['user']; 448 } 449 if (isset($filter['name'])) { 450 $result .= 'name-' . $filter['name']; 451 } 452 if (isset($filter['mail'])) { 453 $result .= 'mail-' . $filter['mail']; 454 } 455 if (isset($filter['grps'])) { 456 $result .= 'grps-' . $filter['grps']; 457 } 458 return $result; 459 } 460 461 /** 462 * Create an array of $numberOfAdds users passing a certain $filter, including belonging 463 * to a certain group and save them to a object-wide array. If the array 464 * already exists try to add $numberOfAdds further users to it. 465 * 466 * @param array $filter 467 * @param int $numberOfAdds additional number of users requested 468 * @return int number of Users actually add to Array 469 */ 470 protected function fillGroupUserArray($filter, $numberOfAdds) 471 { 472 if (isset($this->grpsusers[$this->filterToString($filter)])) { 473 $actualstart = count($this->grpsusers[$this->filterToString($filter)]); 474 } else { 475 $this->grpsusers[$this->filterToString($filter)] = []; 476 $actualstart = 0; 477 } 478 479 $i=0; 480 $count = 0; 481 $this->constructPattern($filter); 482 foreach ($this->users as $user => &$info) { 483 if ($i++ < $actualstart) { 484 continue; 485 } 486 if ($info === false) { 487 $info = $this->getUserData($user); 488 } 489 if ($this->filter($user, $info)) { 490 $this->grpsusers[$this->filterToString($filter)][$user] = $info; 491 if (($numberOfAdds > 0) && (++$count >= $numberOfAdds)) break; 492 } 493 } 494 return $count; 495 } 496 497 /** 498 * Bulk retrieval of user data 499 * 500 * @author Dominik Eckelmann <dokuwiki@cosmocode.de> 501 * 502 * @param int $start index of first user to be returned 503 * @param int $limit max number of users to be returned 504 * @param array $filter array of field/pattern pairs, null for no filter 505 * @return array userinfo (refer getUserData for internal userinfo details) 506 */ 507 public function retrieveUsers($start = 0, $limit = 0, $filter = []) 508 { 509 $adldap = $this->initAdLdap(null); 510 if (!$adldap) return []; 511 512 //if (!$this->users) { 513 //get info for given user 514 $result = $adldap->user()->all(false, $this->constructSearchString($filter)); 515 if (!$result) return []; 516 $this->users = array_fill_keys($result, false); 517 //} 518 519 $i = 0; 520 $count = 0; 521 $result = []; 522 523 if (!isset($filter['grps'])) { 524 /** @var admin_plugin_usermanager $usermanager */ 525 $usermanager = plugin_load("admin", "usermanager", false); 526 $usermanager->setLastdisabled(false); 527 $this->constructPattern($filter); 528 foreach ($this->users as $user => &$info) { 529 if ($i++ < $start) { 530 continue; 531 } 532 if ($info === false) { 533 $info = $this->getUserData($user); 534 } 535 $result[$user] = $info; 536 if (($limit > 0) && (++$count >= $limit)) break; 537 } 538 } else { 539 /** @var admin_plugin_usermanager $usermanager */ 540 $usermanager = plugin_load("admin", "usermanager", false); 541 $usermanager->setLastdisabled(true); 542 if (!isset($this->grpsusers[$this->filterToString($filter)]) || 543 count($this->grpsusers[$this->filterToString($filter)]) < ($start+$limit) 544 ) { 545 if(!isset($this->grpsusers[$this->filterToString($filter)])) { 546 $this->grpsusers[$this->filterToString($filter)] = []; 547 } 548 549 $this->fillGroupUserArray( 550 $filter, 551 $start+$limit - count($this->grpsusers[$this->filterToString($filter)]) +1 552 ); 553 } 554 if (!$this->grpsusers[$this->filterToString($filter)]) return []; 555 foreach ($this->grpsusers[$this->filterToString($filter)] as $user => &$info) { 556 if ($i++ < $start) { 557 continue; 558 } 559 $result[$user] = $info; 560 if (($limit > 0) && (++$count >= $limit)) break; 561 } 562 } 563 return $result; 564 } 565 566 /** 567 * Modify user data 568 * 569 * @param string $user nick of the user to be changed 570 * @param array $changes array of field/value pairs to be changed 571 * @return bool 572 */ 573 public function modifyUser($user, $changes) 574 { 575 $return = true; 576 $adldap = $this->initAdLdap($this->getUserDomain($user)); 577 if (!$adldap) { 578 msg($this->getLang('connectfail'), -1); 579 return false; 580 } 581 582 // password changing 583 if (isset($changes['pass'])) { 584 try { 585 $return = $adldap->user()->password($this->getUserName($user), $changes['pass']); 586 } catch (adLDAPException $e) { 587 if ($this->conf['debug']) msg('AD Auth: '.$e->getMessage(), -1); 588 $return = false; 589 } 590 if (!$return) msg($this->getLang('passchangefail'), -1); 591 } 592 593 // changing user data 594 $adchanges = []; 595 if (isset($changes['name'])) { 596 // get first and last name 597 $parts = explode(' ', $changes['name']); 598 $adchanges['surname'] = array_pop($parts); 599 $adchanges['firstname'] = implode(' ', $parts); 600 $adchanges['display_name'] = $changes['name']; 601 } 602 if (isset($changes['mail'])) { 603 $adchanges['email'] = $changes['mail']; 604 } 605 if ($adchanges !== []) { 606 try { 607 $return &= $adldap->user()->modify($this->getUserName($user), $adchanges); 608 } catch (adLDAPException $e) { 609 if ($this->conf['debug']) msg('AD Auth: '.$e->getMessage(), -1); 610 $return = false; 611 } 612 if (!$return) msg($this->getLang('userchangefail'), -1); 613 } 614 615 return $return; 616 } 617 618 /** 619 * Initialize the AdLDAP library and connect to the server 620 * 621 * When you pass null as domain, it will reuse any existing domain. 622 * Eg. the one of the logged in user. It falls back to the default 623 * domain if no current one is available. 624 * 625 * @param string|null $domain The AD domain to use 626 * @return adLDAP|bool true if a connection was established 627 */ 628 protected function initAdLdap($domain) 629 { 630 if (is_null($domain) && is_array($this->opts)) { 631 $domain = $this->opts['domain']; 632 } 633 634 $this->opts = $this->loadServerConfig((string) $domain); 635 if (isset($this->adldap[$domain])) return $this->adldap[$domain]; 636 637 // connect 638 try { 639 $this->adldap[$domain] = new adLDAP($this->opts); 640 return $this->adldap[$domain]; 641 } catch (Exception $e) { 642 if ($this->conf['debug']) { 643 msg('AD Auth: '.$e->getMessage(), -1); 644 } 645 $this->success = false; 646 $this->adldap[$domain] = null; 647 } 648 return false; 649 } 650 651 /** 652 * Get the domain part from a user 653 * 654 * @param string $user 655 * @return string 656 */ 657 public function getUserDomain($user) 658 { 659 [, $domain] = sexplode('@', $user, 2, ''); 660 return $domain; 661 } 662 663 /** 664 * Get the user part from a user 665 * 666 * When an account suffix is set, we strip the domain part from the user 667 * 668 * @param string $user 669 * @return string 670 */ 671 public function getUserName($user) 672 { 673 if ($this->conf['account_suffix'] !== '') { 674 [$user] = explode('@', $user, 2); 675 } 676 return $user; 677 } 678 679 /** 680 * Fetch the configuration for the given AD domain 681 * 682 * @param string $domain current AD domain 683 * @return array 684 */ 685 protected function loadServerConfig($domain) 686 { 687 // prepare adLDAP standard configuration 688 $opts = $this->conf; 689 690 $opts['domain'] = $domain; 691 692 // add possible domain specific configuration 693 if ($domain && is_array($this->conf[$domain])) foreach ($this->conf[$domain] as $key => $val) { 694 $opts[$key] = $val; 695 } 696 697 // handle multiple AD servers 698 $opts['domain_controllers'] = explode(',', $opts['domain_controllers']); 699 $opts['domain_controllers'] = array_map('trim', $opts['domain_controllers']); 700 $opts['domain_controllers'] = array_filter($opts['domain_controllers']); 701 702 // compatibility with old option name 703 if (empty($opts['admin_username']) && !empty($opts['ad_username'])) { 704 $opts['admin_username'] = $opts['ad_username']; 705 } 706 if (empty($opts['admin_password']) && !empty($opts['ad_password'])) { 707 $opts['admin_password'] = $opts['ad_password']; 708 } 709 $opts['admin_password'] = conf_decodeString($opts['admin_password']); // deobfuscate 710 711 // we can change the password if SSL is set 712 if ($opts['update_pass'] && ($opts['use_ssl'] || $opts['use_tls'])) { 713 $this->cando['modPass'] = true; 714 } else { 715 $this->cando['modPass'] = false; 716 } 717 718 // adLDAP expects empty user/pass as NULL, we're less strict FS#2781 719 if (empty($opts['admin_username'])) $opts['admin_username'] = null; 720 if (empty($opts['admin_password'])) $opts['admin_password'] = null; 721 722 // user listing needs admin priviledges 723 if (!empty($opts['admin_username']) && !empty($opts['admin_password'])) { 724 $this->cando['getUsers'] = true; 725 } else { 726 $this->cando['getUsers'] = false; 727 } 728 729 return $opts; 730 } 731 732 /** 733 * Returns a list of configured domains 734 * 735 * The default domain has an empty string as key 736 * 737 * @return array associative array(key => domain) 738 */ 739 public function getConfiguredDomains() 740 { 741 $domains = []; 742 if (empty($this->conf['account_suffix'])) return $domains; // not configured yet 743 744 // add default domain, using the name from account suffix 745 $domains[''] = ltrim($this->conf['account_suffix'], '@'); 746 747 // find additional domains 748 foreach ($this->conf as $key => $val) { 749 if (is_array($val) && isset($val['account_suffix'])) { 750 $domains[$key] = ltrim($val['account_suffix'], '@'); 751 } 752 } 753 Sort::ksort($domains); 754 755 return $domains; 756 } 757 758 /** 759 * Check provided user and userinfo for matching patterns 760 * 761 * The patterns are set up with $this->_constructPattern() 762 * 763 * @author Chris Smith <chris@jalakai.co.uk> 764 * 765 * @param string $user 766 * @param array $info 767 * @return bool 768 */ 769 protected function filter($user, $info) 770 { 771 foreach ($this->pattern as $item => $pattern) { 772 if ($item == 'user') { 773 if (!preg_match($pattern, $user)) return false; 774 } elseif ($item == 'grps') { 775 if (!count(preg_grep($pattern, $info['grps']))) return false; 776 } elseif (!preg_match($pattern, $info[$item])) { 777 return false; 778 } 779 } 780 return true; 781 } 782 783 /** 784 * Create a pattern for $this->_filter() 785 * 786 * @author Chris Smith <chris@jalakai.co.uk> 787 * 788 * @param array $filter 789 */ 790 protected function constructPattern($filter) 791 { 792 $this->pattern = []; 793 foreach ($filter as $item => $pattern) { 794 $this->pattern[$item] = '/'.str_replace('/', '\/', $pattern).'/i'; // allow regex characters 795 } 796 } 797} 798