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; 439bafffeaSAndreas Gohr $samaccountname = $this->simpleUser($username); 449bafffeaSAndreas Gohr $userprincipal = $this->qualifiedUser($username); 451078ec26SAndreas Gohr 461078ec26SAndreas Gohr $filter = Filters::and( 471078ec26SAndreas Gohr Filters::equal('objectClass', 'user'), 489bafffeaSAndreas Gohr Filters::or( 499bafffeaSAndreas Gohr Filters::equal('sAMAccountName', $samaccountname), 509bafffeaSAndreas Gohr Filters::equal('userPrincipalName', $userprincipal) 519bafffeaSAndreas Gohr ) 529bafffeaSAndreas 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)) { 82*fb75804eSAndreas Gohr try { 83*fb75804eSAndreas Gohr $this->authenticate($username, $oldpass); 84*fb75804eSAndreas Gohr } catch (\Exception $e) { 8508ace392SAndreas Gohr $this->error("Old password for '$username' is wrong", __FILE__, __LINE__); 8608ace392SAndreas Gohr return false; 8708ace392SAndreas Gohr } 8808ace392SAndreas Gohr } 8908ace392SAndreas Gohr 9008ace392SAndreas Gohr $entry->remove('unicodePwd', $this->encodePassword($oldpass)); 9108ace392SAndreas Gohr $entry->add('unicodePwd', $this->encodePassword($newpass)); 9208ace392SAndreas Gohr } else { 9308ace392SAndreas Gohr // run as admin user 9408ace392SAndreas Gohr $entry->set('unicodePwd', $this->encodePassword($newpass)); 9508ace392SAndreas Gohr } 9608ace392SAndreas Gohr 9708ace392SAndreas Gohr try { 9808ace392SAndreas Gohr $this->ldap->update($entry); 9908ace392SAndreas Gohr } catch (OperationException $e) { 10008ace392SAndreas Gohr $this->fatal($e); 10108ace392SAndreas Gohr return false; 10208ace392SAndreas Gohr } 10308ace392SAndreas Gohr return true; 104b21740b4SAndreas Gohr } 1051078ec26SAndreas Gohr 106b21740b4SAndreas Gohr /** @inheritDoc */ 107204fba68SAndreas Gohr public function getGroups($match = null, $filtermethod = self::FILTER_EQUAL) 108b21740b4SAndreas Gohr { 109b21740b4SAndreas Gohr if (!$this->autoAuth()) return []; 110b21740b4SAndreas Gohr 111b21740b4SAndreas Gohr $filter = Filters::and( 112b21740b4SAndreas Gohr Filters::equal('objectClass', 'group') 113b21740b4SAndreas Gohr ); 114b21740b4SAndreas Gohr if ($match !== null) { 115e7c3e817SAndreas Gohr // FIXME this is a workaround that removes regex anchors and quoting as passed by the groupuser plugin 116e7c3e817SAndreas Gohr // a proper fix requires splitbrain/dokuwiki#3028 to be implemented 117fce018daSAndreas Gohr $match = ltrim($match, '^'); 118fce018daSAndreas Gohr $match = rtrim($match, '$'); 119e7c3e817SAndreas Gohr $match = stripslashes($match); 120fce018daSAndreas Gohr 121b21740b4SAndreas Gohr $filter->add(Filters::$filtermethod('cn', $match)); 122b21740b4SAndreas Gohr } 123b21740b4SAndreas Gohr 124b21740b4SAndreas Gohr $this->debug('Searching ' . $filter->toString(), __FILE__, __LINE__); 125b21740b4SAndreas Gohr $search = Operations::search($filter, 'cn'); 126b21740b4SAndreas Gohr $paging = $this->ldap->paging($search); 127b21740b4SAndreas Gohr 128b21740b4SAndreas Gohr $groups = []; 129b21740b4SAndreas Gohr while ($paging->hasEntries()) { 130b21740b4SAndreas Gohr try { 131b21740b4SAndreas Gohr $entries = $paging->getEntries(); 13208ace392SAndreas Gohr } catch (OperationException $e) { 133b21740b4SAndreas Gohr $this->fatal($e); 134b21740b4SAndreas Gohr return $groups; // we return what we got so far 135b21740b4SAndreas Gohr } 136b21740b4SAndreas Gohr 137b21740b4SAndreas Gohr foreach ($entries as $entry) { 138b21740b4SAndreas Gohr /** @var Entry $entry */ 139204fba68SAndreas Gohr $groups[$entry->getDn()->toString()] = $this->cleanGroup($this->attr2str($entry->get('cn'))); 140b21740b4SAndreas Gohr } 141b21740b4SAndreas Gohr } 142b21740b4SAndreas Gohr 1431b0eb9b3SAndreas Gohr asort($groups); 144b21740b4SAndreas Gohr return $groups; 145b21740b4SAndreas Gohr } 146b21740b4SAndreas Gohr 147b21740b4SAndreas Gohr /** 148b21740b4SAndreas Gohr * Fetch users matching the given filters 149b21740b4SAndreas Gohr * 150b21740b4SAndreas Gohr * @param array $match 151b21740b4SAndreas Gohr * @param string $filtermethod The method to use for filtering 152b21740b4SAndreas Gohr * @return array 153b21740b4SAndreas Gohr */ 154204fba68SAndreas Gohr public function getFilteredUsers($match, $filtermethod = self::FILTER_EQUAL) 155b21740b4SAndreas Gohr { 156b21740b4SAndreas Gohr if (!$this->autoAuth()) return []; 157b21740b4SAndreas Gohr 158b21740b4SAndreas Gohr $filter = Filters::and(Filters::equal('objectClass', 'user')); 159b21740b4SAndreas Gohr if (isset($match['user'])) { 160a1128cc0SAndreas Gohr $filter->add(Filters::$filtermethod('sAMAccountName', $this->simpleUser($match['user']))); 161b21740b4SAndreas Gohr } 162b21740b4SAndreas Gohr if (isset($match['name'])) { 163b21740b4SAndreas Gohr $filter->add(Filters::$filtermethod('displayName', $match['name'])); 164b21740b4SAndreas Gohr } 165b21740b4SAndreas Gohr if (isset($match['mail'])) { 166b21740b4SAndreas Gohr $filter->add(Filters::$filtermethod('mail', $match['mail'])); 167b21740b4SAndreas Gohr } 168b21740b4SAndreas Gohr if (isset($match['grps'])) { 169b21740b4SAndreas Gohr // memberOf can not be checked with a substring match, so we need to get the right groups first 170b21740b4SAndreas Gohr $groups = $this->getGroups($match['grps'], $filtermethod); 171e7339d5aSAndreas Gohr $groupDNs = array_keys($groups); 172e7339d5aSAndreas Gohr 173e7339d5aSAndreas Gohr if ($this->config['recursivegroups']) { 174e7339d5aSAndreas Gohr $gch = $this->getGroupHierarchyCache(); 175e7339d5aSAndreas Gohr foreach ($groupDNs as $dn) { 176e7339d5aSAndreas Gohr $groupDNs = array_merge($groupDNs, $gch->getChildren($dn)); 177e7339d5aSAndreas Gohr } 178e7339d5aSAndreas Gohr $groupDNs = array_unique($groupDNs); 179e7339d5aSAndreas Gohr } 180e7339d5aSAndreas Gohr 181b21740b4SAndreas Gohr $or = Filters::or(); 182e7339d5aSAndreas Gohr foreach ($groupDNs as $dn) { 183204fba68SAndreas Gohr // domain users membership is in primary group 184e7339d5aSAndreas Gohr if ($this->dn2group($dn) === $this->config['primarygroup']) { 185204fba68SAndreas Gohr $or->add(Filters::equal('primaryGroupID', 513)); 186204fba68SAndreas Gohr continue; 187204fba68SAndreas Gohr } 1887a36c1b4SAndreas Gohr // find members of this exact group 1897a36c1b4SAndreas Gohr $or->add(Filters::equal('memberOf', $dn)); 190b21740b4SAndreas Gohr } 191b21740b4SAndreas Gohr $filter->add($or); 192b21740b4SAndreas Gohr } 193e7339d5aSAndreas Gohr 194b21740b4SAndreas Gohr $this->debug('Searching ' . $filter->toString(), __FILE__, __LINE__); 1959c590892SAndreas Gohr $attributes = $this->userAttributes(); 1969c590892SAndreas Gohr $search = Operations::search($filter, ...$attributes); 197b21740b4SAndreas Gohr $paging = $this->ldap->paging($search); 198b21740b4SAndreas Gohr 199b21740b4SAndreas Gohr $users = []; 200b21740b4SAndreas Gohr while ($paging->hasEntries()) { 201b21740b4SAndreas Gohr try { 202b21740b4SAndreas Gohr $entries = $paging->getEntries(); 20308ace392SAndreas Gohr } catch (OperationException $e) { 204b21740b4SAndreas Gohr $this->fatal($e); 20580ac552fSAndreas Gohr break; // we abort and return what we have so far 206b21740b4SAndreas Gohr } 207b21740b4SAndreas Gohr 208b21740b4SAndreas Gohr foreach ($entries as $entry) { 20908ace392SAndreas Gohr $userinfo = $this->entry2User($entry); 210746af42cSAndreas Gohr $users[$userinfo['user']] = $userinfo; 211b21740b4SAndreas Gohr } 212b21740b4SAndreas Gohr } 213b21740b4SAndreas Gohr 2141b0eb9b3SAndreas Gohr ksort($users); 215b21740b4SAndreas Gohr return $users; 216b21740b4SAndreas Gohr } 217b21740b4SAndreas Gohr 218a1128cc0SAndreas Gohr /** @inheritDoc */ 219a1128cc0SAndreas Gohr public function cleanUser($user) 22080ac552fSAndreas Gohr { 221a1128cc0SAndreas Gohr return $this->simpleUser($user); 22280ac552fSAndreas Gohr } 22380ac552fSAndreas Gohr 224a1128cc0SAndreas Gohr /** @inheritDoc */ 225a1128cc0SAndreas Gohr public function cleanGroup($group) 226a1128cc0SAndreas Gohr { 227a1128cc0SAndreas Gohr return PhpString::strtolower($group); 228a1128cc0SAndreas Gohr } 229a1128cc0SAndreas Gohr 230a1128cc0SAndreas Gohr /** @inheritDoc */ 231208fe81aSAndreas Gohr protected function prepareBindUser($user) 232a1128cc0SAndreas Gohr { 23308ace392SAndreas Gohr // add account suffix 23408ace392SAndreas Gohr return $this->qualifiedUser($user); 23580ac552fSAndreas Gohr } 23680ac552fSAndreas Gohr 23780ac552fSAndreas Gohr /** 238e7339d5aSAndreas Gohr * Initializes the Group Cache for nested groups 239e7339d5aSAndreas Gohr * 240e7339d5aSAndreas Gohr * @return GroupHierarchyCache 241e7339d5aSAndreas Gohr */ 242e7339d5aSAndreas Gohr public function getGroupHierarchyCache() 243e7339d5aSAndreas Gohr { 244e7339d5aSAndreas Gohr if ($this->gch === null) { 245e7339d5aSAndreas Gohr if (!$this->autoAuth()) return null; 2465dcabedaSAndreas Gohr $this->gch = new GroupHierarchyCache($this->ldap, $this->config['usefscache']); 247e7339d5aSAndreas Gohr } 248e7339d5aSAndreas Gohr return $this->gch; 249e7339d5aSAndreas Gohr } 250e7339d5aSAndreas Gohr 251e7339d5aSAndreas Gohr /** 252a1128cc0SAndreas Gohr * userPrincipalName in the form <user>@<suffix> 25308ace392SAndreas Gohr * 25408ace392SAndreas Gohr * @param string $user 25508ace392SAndreas Gohr * @return string 25680ac552fSAndreas Gohr */ 257a1128cc0SAndreas Gohr protected function qualifiedUser($user) 258a1128cc0SAndreas Gohr { 259a1128cc0SAndreas Gohr $user = $this->simpleUser($user); // strip any existing qualifiers 260a1128cc0SAndreas Gohr if (!$this->config['suffix']) { 261a1128cc0SAndreas Gohr $this->error('No account suffix set. Logins may fail.', __FILE__, __LINE__); 262a1128cc0SAndreas Gohr } 263a1128cc0SAndreas Gohr 264a1128cc0SAndreas Gohr return $user . '@' . $this->config['suffix']; 265a1128cc0SAndreas Gohr } 266a1128cc0SAndreas Gohr 267a1128cc0SAndreas Gohr /** 268a1128cc0SAndreas Gohr * Removes the account suffix from the given user. Should match the SAMAccountName 26908ace392SAndreas Gohr * 27008ace392SAndreas Gohr * @param string $user 27108ace392SAndreas Gohr * @return string 272a1128cc0SAndreas Gohr */ 273a1128cc0SAndreas Gohr protected function simpleUser($user) 27480ac552fSAndreas Gohr { 27580ac552fSAndreas Gohr $user = PhpString::strtolower($user); 276a1128cc0SAndreas Gohr $user = preg_replace('/@.*$/', '', $user); 277a1128cc0SAndreas Gohr $user = preg_replace('/^.*\\\\/', '', $user); 27880ac552fSAndreas Gohr return $user; 27980ac552fSAndreas Gohr } 28080ac552fSAndreas Gohr 28180ac552fSAndreas Gohr /** 282b21740b4SAndreas Gohr * Transform an LDAP entry to a user info array 283b21740b4SAndreas Gohr * 284b21740b4SAndreas Gohr * @param Entry $entry 285b21740b4SAndreas Gohr * @return array 286b21740b4SAndreas Gohr */ 287b21740b4SAndreas Gohr protected function entry2User(Entry $entry) 288b21740b4SAndreas Gohr { 2899bafffeaSAndreas Gohr // prefer userPrincipalName over sAMAccountName 2909bafffeaSAndreas Gohr $user = $this->simpleUser($this->attr2str($entry->get('userPrincipalName'))); 2919bafffeaSAndreas Gohr if($user === '') $user = $this->simpleUser($this->attr2str($entry->get('sAMAccountName'))); 2929bafffeaSAndreas Gohr 293b914569fSAndreas Gohr $user = [ 2949bafffeaSAndreas Gohr 'user' => $user, 2951078ec26SAndreas Gohr 'name' => $this->attr2str($entry->get('DisplayName')) ?: $this->attr2str($entry->get('Name')), 2961078ec26SAndreas Gohr 'mail' => $this->attr2str($entry->get('mail')), 2971078ec26SAndreas Gohr 'dn' => $entry->getDn()->toString(), 2981078ec26SAndreas Gohr 'grps' => $this->getUserGroups($entry), // we always return groups because its currently inexpensive 2991078ec26SAndreas Gohr ]; 300b914569fSAndreas Gohr 3010f498d06SAndreas Gohr // handle password expiry info 3020f498d06SAndreas Gohr $lastChange = $this->attr2str($entry->get('pwdlastset')); 3030f498d06SAndreas Gohr if ($lastChange) { 3040f498d06SAndreas Gohr $lastChange = (int)substr($lastChange, 0, -7); // remove last 7 digits (100ns intervals to seconds) 305208fe81aSAndreas Gohr $lastChange -= 11_644_473_600; // convert from 1601 to 1970 epoch 3060f498d06SAndreas Gohr } 3070f498d06SAndreas Gohr $user['lastpwd'] = (int)$lastChange; 3080f498d06SAndreas Gohr $user['expires'] = !($this->attr2str($entry->get('useraccountcontrol')) & self::ADS_UF_DONT_EXPIRE_PASSWD); 3090f498d06SAndreas Gohr 310b914569fSAndreas Gohr // get additional attributes 311b914569fSAndreas Gohr foreach ($this->config['attributes'] as $attr) { 312b914569fSAndreas Gohr $user[$attr] = $this->attr2str($entry->get($attr)); 313b914569fSAndreas Gohr } 314b914569fSAndreas Gohr 315b914569fSAndreas Gohr return $user; 3161078ec26SAndreas Gohr } 3171078ec26SAndreas Gohr 3181078ec26SAndreas Gohr /** 3191078ec26SAndreas Gohr * Get the list of groups the given user is member of 3201078ec26SAndreas Gohr * 3211078ec26SAndreas Gohr * This method currently does no LDAP queries and thus is inexpensive. 3221078ec26SAndreas Gohr * 3231078ec26SAndreas Gohr * @param Entry $userentry 3241078ec26SAndreas Gohr * @return array 3251078ec26SAndreas Gohr */ 3261078ec26SAndreas Gohr protected function getUserGroups(Entry $userentry) 3271078ec26SAndreas Gohr { 328e7339d5aSAndreas Gohr $groups = []; 329e7339d5aSAndreas Gohr 330e7339d5aSAndreas Gohr if ($userentry->has('memberOf')) { 331e7339d5aSAndreas Gohr $groupDNs = $userentry->get('memberOf')->getValues(); 332e7339d5aSAndreas Gohr 333e7339d5aSAndreas Gohr if ($this->config['recursivegroups']) { 334e7339d5aSAndreas Gohr $gch = $this->getGroupHierarchyCache(); 335e7339d5aSAndreas Gohr foreach ($groupDNs as $dn) { 336e7339d5aSAndreas Gohr $groupDNs = array_merge($groupDNs, $gch->getParents($dn)); 337e7339d5aSAndreas Gohr } 338e7339d5aSAndreas Gohr 339e7339d5aSAndreas Gohr $groupDNs = array_unique($groupDNs); 340e7339d5aSAndreas Gohr } 341e7339d5aSAndreas Gohr $groups = array_map([$this, 'dn2group'], $groupDNs); 342e7339d5aSAndreas Gohr } 343e7339d5aSAndreas Gohr 344e7339d5aSAndreas Gohr $groups[] = $this->config['defaultgroup']; // always add default 3451078ec26SAndreas Gohr 3461078ec26SAndreas Gohr // resolving the primary group in AD is complicated but basically never needed 3471078ec26SAndreas Gohr // http://support.microsoft.com/?kbid=321360 3481078ec26SAndreas Gohr $gid = $userentry->get('primaryGroupID')->firstValue(); 3491078ec26SAndreas Gohr if ($gid == 513) { 350e7339d5aSAndreas Gohr $groups[] = $this->cleanGroup($this->config['primarygroup']); 35151e92298SAndreas Gohr } 35251e92298SAndreas Gohr 353f17bb68bSAndreas Gohr sort($groups); 354f17bb68bSAndreas Gohr return $groups; 35551e92298SAndreas Gohr } 35651e92298SAndreas Gohr 3579c590892SAndreas Gohr /** @inheritDoc */ 3589c590892SAndreas Gohr protected function userAttributes() 3599c590892SAndreas Gohr { 3609c590892SAndreas Gohr $attr = parent::userAttributes(); 361a1128cc0SAndreas Gohr $attr[] = new Attribute('sAMAccountName'); 3629bafffeaSAndreas Gohr $attr[] = new Attribute('userPrincipalName'); 3639c590892SAndreas Gohr $attr[] = new Attribute('Name'); 3649c590892SAndreas Gohr $attr[] = new Attribute('primaryGroupID'); 3659c590892SAndreas Gohr $attr[] = new Attribute('memberOf'); 3660f498d06SAndreas Gohr $attr[] = new Attribute('pwdlastset'); 3670f498d06SAndreas Gohr $attr[] = new Attribute('useraccountcontrol'); 3689c590892SAndreas Gohr 3699c590892SAndreas Gohr return $attr; 3709c590892SAndreas Gohr } 371e7339d5aSAndreas Gohr 372e7339d5aSAndreas Gohr /** 3730f498d06SAndreas Gohr * Queries the maximum password age from the AD server 3740f498d06SAndreas Gohr * 3750f498d06SAndreas Gohr * Note: we do not check if passwords actually are set to expire here. This is encoded in the lower 32bit 3760f498d06SAndreas Gohr * of the returned 64bit integer (see link below). We do not check this because it would require us to 3770f498d06SAndreas Gohr * actually do large integer math and we can simply assume it's enabled when the age check was requested in 3780f498d06SAndreas Gohr * DokuWiki configuration. 3790f498d06SAndreas Gohr * 3800f498d06SAndreas Gohr * @link http://msdn.microsoft.com/en-us/library/ms974598.aspx 3810f498d06SAndreas Gohr * @param bool $useCache should a filesystem cache be used if available? 3820f498d06SAndreas Gohr * @return int The maximum password age in seconds 3830f498d06SAndreas Gohr */ 3840f498d06SAndreas Gohr public function getMaxPasswordAge($useCache = true) 3850f498d06SAndreas Gohr { 3860f498d06SAndreas Gohr global $conf; 3870f498d06SAndreas Gohr $cachename = getCacheName('maxPwdAge', '.pureldap-maxPwdAge'); 3880f498d06SAndreas Gohr $cachetime = @filemtime($cachename); 3890f498d06SAndreas Gohr 3900f498d06SAndreas Gohr // valid file system cache? use it 3910f498d06SAndreas Gohr if ($useCache && $cachetime && (time() - $cachetime) < $conf['auth_security_timeout']) { 3920f498d06SAndreas Gohr return (int)file_get_contents($cachename); 3930f498d06SAndreas Gohr } 3940f498d06SAndreas Gohr 3950f498d06SAndreas Gohr if (!$this->autoAuth()) return 0; 3960f498d06SAndreas Gohr 3970f498d06SAndreas Gohr $attr = new Attribute('maxPwdAge'); 3980f498d06SAndreas Gohr try { 3990f498d06SAndreas Gohr $entry = $this->ldap->read( 4000f498d06SAndreas Gohr $this->getConf('base_dn'), 4010f498d06SAndreas Gohr [$attr] 4020f498d06SAndreas Gohr ); 4030f498d06SAndreas Gohr } catch (OperationException $e) { 4040f498d06SAndreas Gohr $this->fatal($e); 4050f498d06SAndreas Gohr return 0; 4060f498d06SAndreas Gohr } 4070f498d06SAndreas Gohr if (!$entry) return 0; 4080f498d06SAndreas Gohr $maxPwdAge = $entry->get($attr)->firstValue(); 4090f498d06SAndreas Gohr 4100f498d06SAndreas Gohr // MS returns 100 nanosecond intervals, we want seconds 4110f498d06SAndreas Gohr // we operate on strings to avoid integer overflow 4120f498d06SAndreas Gohr // we also want a positive value, so we trim off the leading minus sign 4130f498d06SAndreas Gohr // only then we convert to int 4140f498d06SAndreas Gohr $maxPwdAge = (int)ltrim(substr($maxPwdAge, 0, -7), '-'); 4150f498d06SAndreas Gohr 4160f498d06SAndreas Gohr file_put_contents($cachename, $maxPwdAge); 4170f498d06SAndreas Gohr return $maxPwdAge; 4180f498d06SAndreas Gohr } 4190f498d06SAndreas Gohr 4200f498d06SAndreas Gohr /** 421e7339d5aSAndreas Gohr * Extract the group name from the DN 422e7339d5aSAndreas Gohr * 423e7339d5aSAndreas Gohr * @param string $dn 424e7339d5aSAndreas Gohr * @return string 425e7339d5aSAndreas Gohr */ 426e7339d5aSAndreas Gohr protected function dn2group($dn) 427e7339d5aSAndreas Gohr { 428208fe81aSAndreas Gohr [$cn] = explode(',', $dn, 2); 429e7339d5aSAndreas Gohr return $this->cleanGroup(substr($cn, 3)); 430e7339d5aSAndreas Gohr } 43108ace392SAndreas Gohr 43208ace392SAndreas Gohr /** 43308ace392SAndreas Gohr * Encode a password for transmission over LDAP 43408ace392SAndreas Gohr * 43508ace392SAndreas Gohr * Passwords are encoded as UTF-16LE strings encapsulated in quotes. 43608ace392SAndreas Gohr * 43708ace392SAndreas Gohr * @param string $password The password to encode 43808ace392SAndreas Gohr * @return string 43908ace392SAndreas Gohr */ 44008ace392SAndreas Gohr protected function encodePassword($password) 44108ace392SAndreas Gohr { 44208ace392SAndreas Gohr $password = "\"" . $password . "\""; 44308ace392SAndreas Gohr 44408ace392SAndreas Gohr if (function_exists('iconv')) { 44508ace392SAndreas Gohr $adpassword = iconv('UTF-8', 'UTF-16LE', $password); 44608ace392SAndreas Gohr } elseif (function_exists('mb_convert_encoding')) { 44708ace392SAndreas Gohr $adpassword = mb_convert_encoding($password, "UTF-16LE", "UTF-8"); 44808ace392SAndreas Gohr } else { 44908ace392SAndreas Gohr // this will only work for ASCII7 passwords 45008ace392SAndreas Gohr $adpassword = ''; 45108ace392SAndreas Gohr for ($i = 0; $i < strlen($password); $i++) { 45208ace392SAndreas Gohr $adpassword .= "$password[$i]\000"; 45308ace392SAndreas Gohr } 45408ace392SAndreas Gohr } 45508ace392SAndreas Gohr return $adpassword; 45608ace392SAndreas Gohr } 4571078ec26SAndreas Gohr} 458