11078ec26SAndreas Gohr<?php 21078ec26SAndreas Gohr 31078ec26SAndreas Gohrnamespace dokuwiki\plugin\pureldap\classes; 41078ec26SAndreas Gohr 580ac552fSAndreas Gohruse dokuwiki\Utf8\PhpString; 69c590892SAndreas Gohruse FreeDSx\Ldap\Entry\Attribute; 71078ec26SAndreas Gohruse FreeDSx\Ldap\Entry\Entries; 81078ec26SAndreas Gohruse FreeDSx\Ldap\Entry\Entry; 91078ec26SAndreas Gohruse FreeDSx\Ldap\Exception\OperationException; 101078ec26SAndreas Gohruse FreeDSx\Ldap\Operations; 111078ec26SAndreas Gohruse FreeDSx\Ldap\Search\Filters; 121078ec26SAndreas Gohr 13f17bb68bSAndreas Gohr/** 14f17bb68bSAndreas Gohr * Implement Active Directory Specifics 15f17bb68bSAndreas Gohr */ 161078ec26SAndreas Gohrclass ADClient extends Client 171078ec26SAndreas Gohr{ 18208fe81aSAndreas Gohr public const ADS_UF_DONT_EXPIRE_PASSWD = 0x10000; 190f498d06SAndreas Gohr 20e7339d5aSAndreas Gohr /** 21e7339d5aSAndreas Gohr * @var GroupHierarchyCache 22e7339d5aSAndreas Gohr * @see getGroupHierarchyCache 23e7339d5aSAndreas Gohr */ 24208fe81aSAndreas Gohr protected $gch; 251078ec26SAndreas Gohr 261078ec26SAndreas Gohr /** @inheritDoc */ 271078ec26SAndreas Gohr public function getUser($username, $fetchgroups = true) 281078ec26SAndreas Gohr { 2908ace392SAndreas Gohr $entry = $this->getUserEntry($username); 3008ace392SAndreas Gohr if ($entry === null) return null; 3108ace392SAndreas Gohr return $this->entry2User($entry); 3208ace392SAndreas Gohr } 3308ace392SAndreas Gohr 3408ace392SAndreas Gohr /** 3508ace392SAndreas Gohr * Get the LDAP entry for the given user 3608ace392SAndreas Gohr * 3708ace392SAndreas Gohr * @param string $username 3808ace392SAndreas Gohr * @return Entry|null 3908ace392SAndreas Gohr */ 4008ace392SAndreas Gohr protected function getUserEntry($username) 4108ace392SAndreas Gohr { 421078ec26SAndreas Gohr if (!$this->autoAuth()) return null; 43*9bafffeaSAndreas Gohr $samaccountname = $this->simpleUser($username); 44*9bafffeaSAndreas Gohr $userprincipal = $this->qualifiedUser($username); 451078ec26SAndreas Gohr 461078ec26SAndreas Gohr $filter = Filters::and( 471078ec26SAndreas Gohr Filters::equal('objectClass', 'user'), 48*9bafffeaSAndreas Gohr Filters::or( 49*9bafffeaSAndreas Gohr Filters::equal('sAMAccountName', $samaccountname), 50*9bafffeaSAndreas Gohr Filters::equal('userPrincipalName', $userprincipal) 51*9bafffeaSAndreas Gohr ) 52*9bafffeaSAndreas Gohr 531078ec26SAndreas Gohr ); 54b21740b4SAndreas Gohr $this->debug('Searching ' . $filter->toString(), __FILE__, __LINE__); 551078ec26SAndreas Gohr 561078ec26SAndreas Gohr try { 579c590892SAndreas Gohr $attributes = $this->userAttributes(); 589c590892SAndreas Gohr $entries = $this->ldap->search(Operations::search($filter, ...$attributes)); 591078ec26SAndreas Gohr } catch (OperationException $e) { 60b21740b4SAndreas Gohr $this->fatal($e); 611078ec26SAndreas Gohr return null; 621078ec26SAndreas Gohr } 631078ec26SAndreas Gohr if ($entries->count() !== 1) return null; 6408ace392SAndreas Gohr return $entries->first(); 6508ace392SAndreas Gohr } 6608ace392SAndreas Gohr 6708ace392SAndreas Gohr /** @inheritDoc */ 6808ace392SAndreas Gohr public function setPassword($username, $newpass, $oldpass = null) 6908ace392SAndreas Gohr { 7008ace392SAndreas Gohr if (!$this->autoAuth()) return false; 7108ace392SAndreas Gohr 7208ace392SAndreas Gohr $entry = $this->getUserEntry($username); 7308ace392SAndreas Gohr if ($entry === null) { 7408ace392SAndreas Gohr $this->error("User '$username' not found", __FILE__, __LINE__); 7508ace392SAndreas Gohr return false; 7608ace392SAndreas Gohr } 7708ace392SAndreas Gohr 7808ace392SAndreas Gohr if ($oldpass) { 7908ace392SAndreas Gohr // if an old password is given, this is a self-service password change 8008ace392SAndreas Gohr // this has to be executed as the user themselves, not as the admin 8108ace392SAndreas Gohr if ($this->isAuthenticated !== $this->prepareBindUser($username)) { 8208ace392SAndreas Gohr if (!$this->authenticate($username, $oldpass)) { 8308ace392SAndreas Gohr $this->error("Old password for '$username' is wrong", __FILE__, __LINE__); 8408ace392SAndreas Gohr return false; 8508ace392SAndreas Gohr } 8608ace392SAndreas Gohr } 8708ace392SAndreas Gohr 8808ace392SAndreas Gohr $entry->remove('unicodePwd', $this->encodePassword($oldpass)); 8908ace392SAndreas Gohr $entry->add('unicodePwd', $this->encodePassword($newpass)); 9008ace392SAndreas Gohr } else { 9108ace392SAndreas Gohr // run as admin user 9208ace392SAndreas Gohr $entry->set('unicodePwd', $this->encodePassword($newpass)); 9308ace392SAndreas Gohr } 9408ace392SAndreas Gohr 9508ace392SAndreas Gohr try { 9608ace392SAndreas Gohr $this->ldap->update($entry); 9708ace392SAndreas Gohr } catch (OperationException $e) { 9808ace392SAndreas Gohr $this->fatal($e); 9908ace392SAndreas Gohr return false; 10008ace392SAndreas Gohr } 10108ace392SAndreas Gohr return true; 102b21740b4SAndreas Gohr } 1031078ec26SAndreas Gohr 104b21740b4SAndreas Gohr /** @inheritDoc */ 105204fba68SAndreas Gohr public function getGroups($match = null, $filtermethod = self::FILTER_EQUAL) 106b21740b4SAndreas Gohr { 107b21740b4SAndreas Gohr if (!$this->autoAuth()) return []; 108b21740b4SAndreas Gohr 109b21740b4SAndreas Gohr $filter = Filters::and( 110b21740b4SAndreas Gohr Filters::equal('objectClass', 'group') 111b21740b4SAndreas Gohr ); 112b21740b4SAndreas Gohr if ($match !== null) { 113e7c3e817SAndreas Gohr // FIXME this is a workaround that removes regex anchors and quoting as passed by the groupuser plugin 114e7c3e817SAndreas Gohr // a proper fix requires splitbrain/dokuwiki#3028 to be implemented 115fce018daSAndreas Gohr $match = ltrim($match, '^'); 116fce018daSAndreas Gohr $match = rtrim($match, '$'); 117e7c3e817SAndreas Gohr $match = stripslashes($match); 118fce018daSAndreas Gohr 119b21740b4SAndreas Gohr $filter->add(Filters::$filtermethod('cn', $match)); 120b21740b4SAndreas Gohr } 121b21740b4SAndreas Gohr 122b21740b4SAndreas Gohr $this->debug('Searching ' . $filter->toString(), __FILE__, __LINE__); 123b21740b4SAndreas Gohr $search = Operations::search($filter, 'cn'); 124b21740b4SAndreas Gohr $paging = $this->ldap->paging($search); 125b21740b4SAndreas Gohr 126b21740b4SAndreas Gohr $groups = []; 127b21740b4SAndreas Gohr while ($paging->hasEntries()) { 128b21740b4SAndreas Gohr try { 129b21740b4SAndreas Gohr $entries = $paging->getEntries(); 13008ace392SAndreas Gohr } catch (OperationException $e) { 131b21740b4SAndreas Gohr $this->fatal($e); 132b21740b4SAndreas Gohr return $groups; // we return what we got so far 133b21740b4SAndreas Gohr } 134b21740b4SAndreas Gohr 135b21740b4SAndreas Gohr foreach ($entries as $entry) { 136b21740b4SAndreas Gohr /** @var Entry $entry */ 137204fba68SAndreas Gohr $groups[$entry->getDn()->toString()] = $this->cleanGroup($this->attr2str($entry->get('cn'))); 138b21740b4SAndreas Gohr } 139b21740b4SAndreas Gohr } 140b21740b4SAndreas Gohr 1411b0eb9b3SAndreas Gohr asort($groups); 142b21740b4SAndreas Gohr return $groups; 143b21740b4SAndreas Gohr } 144b21740b4SAndreas Gohr 145b21740b4SAndreas Gohr /** 146b21740b4SAndreas Gohr * Fetch users matching the given filters 147b21740b4SAndreas Gohr * 148b21740b4SAndreas Gohr * @param array $match 149b21740b4SAndreas Gohr * @param string $filtermethod The method to use for filtering 150b21740b4SAndreas Gohr * @return array 151b21740b4SAndreas Gohr */ 152204fba68SAndreas Gohr public function getFilteredUsers($match, $filtermethod = self::FILTER_EQUAL) 153b21740b4SAndreas Gohr { 154b21740b4SAndreas Gohr if (!$this->autoAuth()) return []; 155b21740b4SAndreas Gohr 156b21740b4SAndreas Gohr $filter = Filters::and(Filters::equal('objectClass', 'user')); 157b21740b4SAndreas Gohr if (isset($match['user'])) { 158a1128cc0SAndreas Gohr $filter->add(Filters::$filtermethod('sAMAccountName', $this->simpleUser($match['user']))); 159b21740b4SAndreas Gohr } 160b21740b4SAndreas Gohr if (isset($match['name'])) { 161b21740b4SAndreas Gohr $filter->add(Filters::$filtermethod('displayName', $match['name'])); 162b21740b4SAndreas Gohr } 163b21740b4SAndreas Gohr if (isset($match['mail'])) { 164b21740b4SAndreas Gohr $filter->add(Filters::$filtermethod('mail', $match['mail'])); 165b21740b4SAndreas Gohr } 166b21740b4SAndreas Gohr if (isset($match['grps'])) { 167b21740b4SAndreas Gohr // memberOf can not be checked with a substring match, so we need to get the right groups first 168b21740b4SAndreas Gohr $groups = $this->getGroups($match['grps'], $filtermethod); 169e7339d5aSAndreas Gohr $groupDNs = array_keys($groups); 170e7339d5aSAndreas Gohr 171e7339d5aSAndreas Gohr if ($this->config['recursivegroups']) { 172e7339d5aSAndreas Gohr $gch = $this->getGroupHierarchyCache(); 173e7339d5aSAndreas Gohr foreach ($groupDNs as $dn) { 174e7339d5aSAndreas Gohr $groupDNs = array_merge($groupDNs, $gch->getChildren($dn)); 175e7339d5aSAndreas Gohr } 176e7339d5aSAndreas Gohr $groupDNs = array_unique($groupDNs); 177e7339d5aSAndreas Gohr } 178e7339d5aSAndreas Gohr 179b21740b4SAndreas Gohr $or = Filters::or(); 180e7339d5aSAndreas Gohr foreach ($groupDNs as $dn) { 181204fba68SAndreas Gohr // domain users membership is in primary group 182e7339d5aSAndreas Gohr if ($this->dn2group($dn) === $this->config['primarygroup']) { 183204fba68SAndreas Gohr $or->add(Filters::equal('primaryGroupID', 513)); 184204fba68SAndreas Gohr continue; 185204fba68SAndreas Gohr } 1867a36c1b4SAndreas Gohr // find members of this exact group 1877a36c1b4SAndreas Gohr $or->add(Filters::equal('memberOf', $dn)); 188b21740b4SAndreas Gohr } 189b21740b4SAndreas Gohr $filter->add($or); 190b21740b4SAndreas Gohr } 191e7339d5aSAndreas Gohr 192b21740b4SAndreas Gohr $this->debug('Searching ' . $filter->toString(), __FILE__, __LINE__); 1939c590892SAndreas Gohr $attributes = $this->userAttributes(); 1949c590892SAndreas Gohr $search = Operations::search($filter, ...$attributes); 195b21740b4SAndreas Gohr $paging = $this->ldap->paging($search); 196b21740b4SAndreas Gohr 197b21740b4SAndreas Gohr $users = []; 198b21740b4SAndreas Gohr while ($paging->hasEntries()) { 199b21740b4SAndreas Gohr try { 200b21740b4SAndreas Gohr $entries = $paging->getEntries(); 20108ace392SAndreas Gohr } catch (OperationException $e) { 202b21740b4SAndreas Gohr $this->fatal($e); 20380ac552fSAndreas Gohr break; // we abort and return what we have so far 204b21740b4SAndreas Gohr } 205b21740b4SAndreas Gohr 206b21740b4SAndreas Gohr foreach ($entries as $entry) { 20708ace392SAndreas Gohr $userinfo = $this->entry2User($entry); 208746af42cSAndreas Gohr $users[$userinfo['user']] = $userinfo; 209b21740b4SAndreas Gohr } 210b21740b4SAndreas Gohr } 211b21740b4SAndreas Gohr 2121b0eb9b3SAndreas Gohr ksort($users); 213b21740b4SAndreas Gohr return $users; 214b21740b4SAndreas Gohr } 215b21740b4SAndreas Gohr 216a1128cc0SAndreas Gohr /** @inheritDoc */ 217a1128cc0SAndreas Gohr public function cleanUser($user) 21880ac552fSAndreas Gohr { 219a1128cc0SAndreas Gohr return $this->simpleUser($user); 22080ac552fSAndreas Gohr } 22180ac552fSAndreas Gohr 222a1128cc0SAndreas Gohr /** @inheritDoc */ 223a1128cc0SAndreas Gohr public function cleanGroup($group) 224a1128cc0SAndreas Gohr { 225a1128cc0SAndreas Gohr return PhpString::strtolower($group); 226a1128cc0SAndreas Gohr } 227a1128cc0SAndreas Gohr 228a1128cc0SAndreas Gohr /** @inheritDoc */ 229208fe81aSAndreas Gohr protected function prepareBindUser($user) 230a1128cc0SAndreas Gohr { 23108ace392SAndreas Gohr // add account suffix 23208ace392SAndreas Gohr return $this->qualifiedUser($user); 23380ac552fSAndreas Gohr } 23480ac552fSAndreas Gohr 23580ac552fSAndreas Gohr /** 236e7339d5aSAndreas Gohr * Initializes the Group Cache for nested groups 237e7339d5aSAndreas Gohr * 238e7339d5aSAndreas Gohr * @return GroupHierarchyCache 239e7339d5aSAndreas Gohr */ 240e7339d5aSAndreas Gohr public function getGroupHierarchyCache() 241e7339d5aSAndreas Gohr { 242e7339d5aSAndreas Gohr if ($this->gch === null) { 243e7339d5aSAndreas Gohr if (!$this->autoAuth()) return null; 2445dcabedaSAndreas Gohr $this->gch = new GroupHierarchyCache($this->ldap, $this->config['usefscache']); 245e7339d5aSAndreas Gohr } 246e7339d5aSAndreas Gohr return $this->gch; 247e7339d5aSAndreas Gohr } 248e7339d5aSAndreas Gohr 249e7339d5aSAndreas Gohr /** 250a1128cc0SAndreas Gohr * userPrincipalName in the form <user>@<suffix> 25108ace392SAndreas Gohr * 25208ace392SAndreas Gohr * @param string $user 25308ace392SAndreas Gohr * @return string 25480ac552fSAndreas Gohr */ 255a1128cc0SAndreas Gohr protected function qualifiedUser($user) 256a1128cc0SAndreas Gohr { 257a1128cc0SAndreas Gohr $user = $this->simpleUser($user); // strip any existing qualifiers 258a1128cc0SAndreas Gohr if (!$this->config['suffix']) { 259a1128cc0SAndreas Gohr $this->error('No account suffix set. Logins may fail.', __FILE__, __LINE__); 260a1128cc0SAndreas Gohr } 261a1128cc0SAndreas Gohr 262a1128cc0SAndreas Gohr return $user . '@' . $this->config['suffix']; 263a1128cc0SAndreas Gohr } 264a1128cc0SAndreas Gohr 265a1128cc0SAndreas Gohr /** 266a1128cc0SAndreas Gohr * Removes the account suffix from the given user. Should match the SAMAccountName 26708ace392SAndreas Gohr * 26808ace392SAndreas Gohr * @param string $user 26908ace392SAndreas Gohr * @return string 270a1128cc0SAndreas Gohr */ 271a1128cc0SAndreas Gohr protected function simpleUser($user) 27280ac552fSAndreas Gohr { 27380ac552fSAndreas Gohr $user = PhpString::strtolower($user); 274a1128cc0SAndreas Gohr $user = preg_replace('/@.*$/', '', $user); 275a1128cc0SAndreas Gohr $user = preg_replace('/^.*\\\\/', '', $user); 27680ac552fSAndreas Gohr return $user; 27780ac552fSAndreas Gohr } 27880ac552fSAndreas Gohr 27980ac552fSAndreas Gohr /** 280b21740b4SAndreas Gohr * Transform an LDAP entry to a user info array 281b21740b4SAndreas Gohr * 282b21740b4SAndreas Gohr * @param Entry $entry 283b21740b4SAndreas Gohr * @return array 284b21740b4SAndreas Gohr */ 285b21740b4SAndreas Gohr protected function entry2User(Entry $entry) 286b21740b4SAndreas Gohr { 287*9bafffeaSAndreas Gohr // prefer userPrincipalName over sAMAccountName 288*9bafffeaSAndreas Gohr $user = $this->simpleUser($this->attr2str($entry->get('userPrincipalName'))); 289*9bafffeaSAndreas Gohr if($user === '') $user = $this->simpleUser($this->attr2str($entry->get('sAMAccountName'))); 290*9bafffeaSAndreas Gohr 291b914569fSAndreas Gohr $user = [ 292*9bafffeaSAndreas Gohr 'user' => $user, 2931078ec26SAndreas Gohr 'name' => $this->attr2str($entry->get('DisplayName')) ?: $this->attr2str($entry->get('Name')), 2941078ec26SAndreas Gohr 'mail' => $this->attr2str($entry->get('mail')), 2951078ec26SAndreas Gohr 'dn' => $entry->getDn()->toString(), 2961078ec26SAndreas Gohr 'grps' => $this->getUserGroups($entry), // we always return groups because its currently inexpensive 2971078ec26SAndreas Gohr ]; 298b914569fSAndreas Gohr 2990f498d06SAndreas Gohr // handle password expiry info 3000f498d06SAndreas Gohr $lastChange = $this->attr2str($entry->get('pwdlastset')); 3010f498d06SAndreas Gohr if ($lastChange) { 3020f498d06SAndreas Gohr $lastChange = (int)substr($lastChange, 0, -7); // remove last 7 digits (100ns intervals to seconds) 303208fe81aSAndreas Gohr $lastChange -= 11_644_473_600; // convert from 1601 to 1970 epoch 3040f498d06SAndreas Gohr } 3050f498d06SAndreas Gohr $user['lastpwd'] = (int)$lastChange; 3060f498d06SAndreas Gohr $user['expires'] = !($this->attr2str($entry->get('useraccountcontrol')) & self::ADS_UF_DONT_EXPIRE_PASSWD); 3070f498d06SAndreas Gohr 308b914569fSAndreas Gohr // get additional attributes 309b914569fSAndreas Gohr foreach ($this->config['attributes'] as $attr) { 310b914569fSAndreas Gohr $user[$attr] = $this->attr2str($entry->get($attr)); 311b914569fSAndreas Gohr } 312b914569fSAndreas Gohr 313b914569fSAndreas Gohr return $user; 3141078ec26SAndreas Gohr } 3151078ec26SAndreas Gohr 3161078ec26SAndreas Gohr /** 3171078ec26SAndreas Gohr * Get the list of groups the given user is member of 3181078ec26SAndreas Gohr * 3191078ec26SAndreas Gohr * This method currently does no LDAP queries and thus is inexpensive. 3201078ec26SAndreas Gohr * 3211078ec26SAndreas Gohr * @param Entry $userentry 3221078ec26SAndreas Gohr * @return array 3231078ec26SAndreas Gohr */ 3241078ec26SAndreas Gohr protected function getUserGroups(Entry $userentry) 3251078ec26SAndreas Gohr { 326e7339d5aSAndreas Gohr $groups = []; 327e7339d5aSAndreas Gohr 328e7339d5aSAndreas Gohr if ($userentry->has('memberOf')) { 329e7339d5aSAndreas Gohr $groupDNs = $userentry->get('memberOf')->getValues(); 330e7339d5aSAndreas Gohr 331e7339d5aSAndreas Gohr if ($this->config['recursivegroups']) { 332e7339d5aSAndreas Gohr $gch = $this->getGroupHierarchyCache(); 333e7339d5aSAndreas Gohr foreach ($groupDNs as $dn) { 334e7339d5aSAndreas Gohr $groupDNs = array_merge($groupDNs, $gch->getParents($dn)); 335e7339d5aSAndreas Gohr } 336e7339d5aSAndreas Gohr 337e7339d5aSAndreas Gohr $groupDNs = array_unique($groupDNs); 338e7339d5aSAndreas Gohr } 339e7339d5aSAndreas Gohr $groups = array_map([$this, 'dn2group'], $groupDNs); 340e7339d5aSAndreas Gohr } 341e7339d5aSAndreas Gohr 342e7339d5aSAndreas Gohr $groups[] = $this->config['defaultgroup']; // always add default 3431078ec26SAndreas Gohr 3441078ec26SAndreas Gohr // resolving the primary group in AD is complicated but basically never needed 3451078ec26SAndreas Gohr // http://support.microsoft.com/?kbid=321360 3461078ec26SAndreas Gohr $gid = $userentry->get('primaryGroupID')->firstValue(); 3471078ec26SAndreas Gohr if ($gid == 513) { 348e7339d5aSAndreas Gohr $groups[] = $this->cleanGroup($this->config['primarygroup']); 34951e92298SAndreas Gohr } 35051e92298SAndreas Gohr 351f17bb68bSAndreas Gohr sort($groups); 352f17bb68bSAndreas Gohr return $groups; 35351e92298SAndreas Gohr } 35451e92298SAndreas Gohr 3559c590892SAndreas Gohr /** @inheritDoc */ 3569c590892SAndreas Gohr protected function userAttributes() 3579c590892SAndreas Gohr { 3589c590892SAndreas Gohr $attr = parent::userAttributes(); 359a1128cc0SAndreas Gohr $attr[] = new Attribute('sAMAccountName'); 360*9bafffeaSAndreas Gohr $attr[] = new Attribute('userPrincipalName'); 3619c590892SAndreas Gohr $attr[] = new Attribute('Name'); 3629c590892SAndreas Gohr $attr[] = new Attribute('primaryGroupID'); 3639c590892SAndreas Gohr $attr[] = new Attribute('memberOf'); 3640f498d06SAndreas Gohr $attr[] = new Attribute('pwdlastset'); 3650f498d06SAndreas Gohr $attr[] = new Attribute('useraccountcontrol'); 3669c590892SAndreas Gohr 3679c590892SAndreas Gohr return $attr; 3689c590892SAndreas Gohr } 369e7339d5aSAndreas Gohr 370e7339d5aSAndreas Gohr /** 3710f498d06SAndreas Gohr * Queries the maximum password age from the AD server 3720f498d06SAndreas Gohr * 3730f498d06SAndreas Gohr * Note: we do not check if passwords actually are set to expire here. This is encoded in the lower 32bit 3740f498d06SAndreas Gohr * of the returned 64bit integer (see link below). We do not check this because it would require us to 3750f498d06SAndreas Gohr * actually do large integer math and we can simply assume it's enabled when the age check was requested in 3760f498d06SAndreas Gohr * DokuWiki configuration. 3770f498d06SAndreas Gohr * 3780f498d06SAndreas Gohr * @link http://msdn.microsoft.com/en-us/library/ms974598.aspx 3790f498d06SAndreas Gohr * @param bool $useCache should a filesystem cache be used if available? 3800f498d06SAndreas Gohr * @return int The maximum password age in seconds 3810f498d06SAndreas Gohr */ 3820f498d06SAndreas Gohr public function getMaxPasswordAge($useCache = true) 3830f498d06SAndreas Gohr { 3840f498d06SAndreas Gohr global $conf; 3850f498d06SAndreas Gohr $cachename = getCacheName('maxPwdAge', '.pureldap-maxPwdAge'); 3860f498d06SAndreas Gohr $cachetime = @filemtime($cachename); 3870f498d06SAndreas Gohr 3880f498d06SAndreas Gohr // valid file system cache? use it 3890f498d06SAndreas Gohr if ($useCache && $cachetime && (time() - $cachetime) < $conf['auth_security_timeout']) { 3900f498d06SAndreas Gohr return (int)file_get_contents($cachename); 3910f498d06SAndreas Gohr } 3920f498d06SAndreas Gohr 3930f498d06SAndreas Gohr if (!$this->autoAuth()) return 0; 3940f498d06SAndreas Gohr 3950f498d06SAndreas Gohr $attr = new Attribute('maxPwdAge'); 3960f498d06SAndreas Gohr try { 3970f498d06SAndreas Gohr $entry = $this->ldap->read( 3980f498d06SAndreas Gohr $this->getConf('base_dn'), 3990f498d06SAndreas Gohr [$attr] 4000f498d06SAndreas Gohr ); 4010f498d06SAndreas Gohr } catch (OperationException $e) { 4020f498d06SAndreas Gohr $this->fatal($e); 4030f498d06SAndreas Gohr return 0; 4040f498d06SAndreas Gohr } 4050f498d06SAndreas Gohr if (!$entry) return 0; 4060f498d06SAndreas Gohr $maxPwdAge = $entry->get($attr)->firstValue(); 4070f498d06SAndreas Gohr 4080f498d06SAndreas Gohr // MS returns 100 nanosecond intervals, we want seconds 4090f498d06SAndreas Gohr // we operate on strings to avoid integer overflow 4100f498d06SAndreas Gohr // we also want a positive value, so we trim off the leading minus sign 4110f498d06SAndreas Gohr // only then we convert to int 4120f498d06SAndreas Gohr $maxPwdAge = (int)ltrim(substr($maxPwdAge, 0, -7), '-'); 4130f498d06SAndreas Gohr 4140f498d06SAndreas Gohr file_put_contents($cachename, $maxPwdAge); 4150f498d06SAndreas Gohr return $maxPwdAge; 4160f498d06SAndreas Gohr } 4170f498d06SAndreas Gohr 4180f498d06SAndreas Gohr /** 419e7339d5aSAndreas Gohr * Extract the group name from the DN 420e7339d5aSAndreas Gohr * 421e7339d5aSAndreas Gohr * @param string $dn 422e7339d5aSAndreas Gohr * @return string 423e7339d5aSAndreas Gohr */ 424e7339d5aSAndreas Gohr protected function dn2group($dn) 425e7339d5aSAndreas Gohr { 426208fe81aSAndreas Gohr [$cn] = explode(',', $dn, 2); 427e7339d5aSAndreas Gohr return $this->cleanGroup(substr($cn, 3)); 428e7339d5aSAndreas Gohr } 42908ace392SAndreas Gohr 43008ace392SAndreas Gohr /** 43108ace392SAndreas Gohr * Encode a password for transmission over LDAP 43208ace392SAndreas Gohr * 43308ace392SAndreas Gohr * Passwords are encoded as UTF-16LE strings encapsulated in quotes. 43408ace392SAndreas Gohr * 43508ace392SAndreas Gohr * @param string $password The password to encode 43608ace392SAndreas Gohr * @return string 43708ace392SAndreas Gohr */ 43808ace392SAndreas Gohr protected function encodePassword($password) 43908ace392SAndreas Gohr { 44008ace392SAndreas Gohr $password = "\"" . $password . "\""; 44108ace392SAndreas Gohr 44208ace392SAndreas Gohr if (function_exists('iconv')) { 44308ace392SAndreas Gohr $adpassword = iconv('UTF-8', 'UTF-16LE', $password); 44408ace392SAndreas Gohr } elseif (function_exists('mb_convert_encoding')) { 44508ace392SAndreas Gohr $adpassword = mb_convert_encoding($password, "UTF-16LE", "UTF-8"); 44608ace392SAndreas Gohr } else { 44708ace392SAndreas Gohr // this will only work for ASCII7 passwords 44808ace392SAndreas Gohr $adpassword = ''; 44908ace392SAndreas Gohr for ($i = 0; $i < strlen($password); $i++) { 45008ace392SAndreas Gohr $adpassword .= "$password[$i]\000"; 45108ace392SAndreas Gohr } 45208ace392SAndreas Gohr } 45308ace392SAndreas Gohr return $adpassword; 45408ace392SAndreas Gohr } 4551078ec26SAndreas Gohr} 456