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