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