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{ 18*208fe81aSAndreas Gohr public const ADS_UF_DONT_EXPIRE_PASSWD = 0x10000; 190f498d06SAndreas Gohr 20e7339d5aSAndreas Gohr /** 21e7339d5aSAndreas Gohr * @var GroupHierarchyCache 22e7339d5aSAndreas Gohr * @see getGroupHierarchyCache 23e7339d5aSAndreas Gohr */ 24*208fe81aSAndreas 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; 43a1128cc0SAndreas Gohr $username = $this->simpleUser($username); 441078ec26SAndreas Gohr 451078ec26SAndreas Gohr $filter = Filters::and( 461078ec26SAndreas Gohr Filters::equal('objectClass', 'user'), 47a1128cc0SAndreas Gohr Filters::equal('sAMAccountName', $this->simpleUser($username)) 481078ec26SAndreas Gohr ); 49b21740b4SAndreas Gohr $this->debug('Searching ' . $filter->toString(), __FILE__, __LINE__); 501078ec26SAndreas Gohr 511078ec26SAndreas Gohr try { 529c590892SAndreas Gohr $attributes = $this->userAttributes(); 539c590892SAndreas Gohr $entries = $this->ldap->search(Operations::search($filter, ...$attributes)); 541078ec26SAndreas Gohr } catch (OperationException $e) { 55b21740b4SAndreas Gohr $this->fatal($e); 561078ec26SAndreas Gohr return null; 571078ec26SAndreas Gohr } 581078ec26SAndreas Gohr if ($entries->count() !== 1) return null; 5908ace392SAndreas Gohr return $entries->first(); 6008ace392SAndreas Gohr } 6108ace392SAndreas Gohr 6208ace392SAndreas Gohr /** @inheritDoc */ 6308ace392SAndreas Gohr public function setPassword($username, $newpass, $oldpass = null) 6408ace392SAndreas Gohr { 6508ace392SAndreas Gohr if (!$this->autoAuth()) return false; 6608ace392SAndreas Gohr 6708ace392SAndreas Gohr $entry = $this->getUserEntry($username); 6808ace392SAndreas Gohr if ($entry === null) { 6908ace392SAndreas Gohr $this->error("User '$username' not found", __FILE__, __LINE__); 7008ace392SAndreas Gohr return false; 7108ace392SAndreas Gohr } 7208ace392SAndreas Gohr 7308ace392SAndreas Gohr if ($oldpass) { 7408ace392SAndreas Gohr // if an old password is given, this is a self-service password change 7508ace392SAndreas Gohr // this has to be executed as the user themselves, not as the admin 7608ace392SAndreas Gohr if ($this->isAuthenticated !== $this->prepareBindUser($username)) { 7708ace392SAndreas Gohr if (!$this->authenticate($username, $oldpass)) { 7808ace392SAndreas Gohr $this->error("Old password for '$username' is wrong", __FILE__, __LINE__); 7908ace392SAndreas Gohr return false; 8008ace392SAndreas Gohr } 8108ace392SAndreas Gohr } 8208ace392SAndreas Gohr 8308ace392SAndreas Gohr $entry->remove('unicodePwd', $this->encodePassword($oldpass)); 8408ace392SAndreas Gohr $entry->add('unicodePwd', $this->encodePassword($newpass)); 8508ace392SAndreas Gohr } else { 8608ace392SAndreas Gohr // run as admin user 8708ace392SAndreas Gohr $entry->set('unicodePwd', $this->encodePassword($newpass)); 8808ace392SAndreas Gohr } 8908ace392SAndreas Gohr 9008ace392SAndreas Gohr try { 9108ace392SAndreas Gohr $this->ldap->update($entry); 9208ace392SAndreas Gohr } catch (OperationException $e) { 9308ace392SAndreas Gohr $this->fatal($e); 9408ace392SAndreas Gohr return false; 9508ace392SAndreas Gohr } 9608ace392SAndreas Gohr return true; 97b21740b4SAndreas Gohr } 981078ec26SAndreas Gohr 99b21740b4SAndreas Gohr /** @inheritDoc */ 100204fba68SAndreas Gohr public function getGroups($match = null, $filtermethod = self::FILTER_EQUAL) 101b21740b4SAndreas Gohr { 102b21740b4SAndreas Gohr if (!$this->autoAuth()) return []; 103b21740b4SAndreas Gohr 104b21740b4SAndreas Gohr $filter = Filters::and( 105b21740b4SAndreas Gohr Filters::equal('objectClass', 'group') 106b21740b4SAndreas Gohr ); 107b21740b4SAndreas Gohr if ($match !== null) { 108e7c3e817SAndreas Gohr // FIXME this is a workaround that removes regex anchors and quoting as passed by the groupuser plugin 109e7c3e817SAndreas Gohr // a proper fix requires splitbrain/dokuwiki#3028 to be implemented 110fce018daSAndreas Gohr $match = ltrim($match, '^'); 111fce018daSAndreas Gohr $match = rtrim($match, '$'); 112e7c3e817SAndreas Gohr $match = stripslashes($match); 113fce018daSAndreas Gohr 114b21740b4SAndreas Gohr $filter->add(Filters::$filtermethod('cn', $match)); 115b21740b4SAndreas Gohr } 116b21740b4SAndreas Gohr 117b21740b4SAndreas Gohr $this->debug('Searching ' . $filter->toString(), __FILE__, __LINE__); 118b21740b4SAndreas Gohr $search = Operations::search($filter, 'cn'); 119b21740b4SAndreas Gohr $paging = $this->ldap->paging($search); 120b21740b4SAndreas Gohr 121b21740b4SAndreas Gohr $groups = []; 122b21740b4SAndreas Gohr while ($paging->hasEntries()) { 123b21740b4SAndreas Gohr try { 124b21740b4SAndreas Gohr $entries = $paging->getEntries(); 12508ace392SAndreas Gohr } catch (OperationException $e) { 126b21740b4SAndreas Gohr $this->fatal($e); 127b21740b4SAndreas Gohr return $groups; // we return what we got so far 128b21740b4SAndreas Gohr } 129b21740b4SAndreas Gohr 130b21740b4SAndreas Gohr foreach ($entries as $entry) { 131b21740b4SAndreas Gohr /** @var Entry $entry */ 132204fba68SAndreas Gohr $groups[$entry->getDn()->toString()] = $this->cleanGroup($this->attr2str($entry->get('cn'))); 133b21740b4SAndreas Gohr } 134b21740b4SAndreas Gohr } 135b21740b4SAndreas Gohr 1361b0eb9b3SAndreas Gohr asort($groups); 137b21740b4SAndreas Gohr return $groups; 138b21740b4SAndreas Gohr } 139b21740b4SAndreas Gohr 140b21740b4SAndreas Gohr /** 141b21740b4SAndreas Gohr * Fetch users matching the given filters 142b21740b4SAndreas Gohr * 143b21740b4SAndreas Gohr * @param array $match 144b21740b4SAndreas Gohr * @param string $filtermethod The method to use for filtering 145b21740b4SAndreas Gohr * @return array 146b21740b4SAndreas Gohr */ 147204fba68SAndreas Gohr public function getFilteredUsers($match, $filtermethod = self::FILTER_EQUAL) 148b21740b4SAndreas Gohr { 149b21740b4SAndreas Gohr if (!$this->autoAuth()) return []; 150b21740b4SAndreas Gohr 151b21740b4SAndreas Gohr $filter = Filters::and(Filters::equal('objectClass', 'user')); 152b21740b4SAndreas Gohr if (isset($match['user'])) { 153a1128cc0SAndreas Gohr $filter->add(Filters::$filtermethod('sAMAccountName', $this->simpleUser($match['user']))); 154b21740b4SAndreas Gohr } 155b21740b4SAndreas Gohr if (isset($match['name'])) { 156b21740b4SAndreas Gohr $filter->add(Filters::$filtermethod('displayName', $match['name'])); 157b21740b4SAndreas Gohr } 158b21740b4SAndreas Gohr if (isset($match['mail'])) { 159b21740b4SAndreas Gohr $filter->add(Filters::$filtermethod('mail', $match['mail'])); 160b21740b4SAndreas Gohr } 161b21740b4SAndreas Gohr if (isset($match['grps'])) { 162b21740b4SAndreas Gohr // memberOf can not be checked with a substring match, so we need to get the right groups first 163b21740b4SAndreas Gohr $groups = $this->getGroups($match['grps'], $filtermethod); 164e7339d5aSAndreas Gohr $groupDNs = array_keys($groups); 165e7339d5aSAndreas Gohr 166e7339d5aSAndreas Gohr if ($this->config['recursivegroups']) { 167e7339d5aSAndreas Gohr $gch = $this->getGroupHierarchyCache(); 168e7339d5aSAndreas Gohr foreach ($groupDNs as $dn) { 169e7339d5aSAndreas Gohr $groupDNs = array_merge($groupDNs, $gch->getChildren($dn)); 170e7339d5aSAndreas Gohr } 171e7339d5aSAndreas Gohr $groupDNs = array_unique($groupDNs); 172e7339d5aSAndreas Gohr } 173e7339d5aSAndreas Gohr 174b21740b4SAndreas Gohr $or = Filters::or(); 175e7339d5aSAndreas Gohr foreach ($groupDNs as $dn) { 176204fba68SAndreas Gohr // domain users membership is in primary group 177e7339d5aSAndreas Gohr if ($this->dn2group($dn) === $this->config['primarygroup']) { 178204fba68SAndreas Gohr $or->add(Filters::equal('primaryGroupID', 513)); 179204fba68SAndreas Gohr continue; 180204fba68SAndreas Gohr } 1817a36c1b4SAndreas Gohr // find members of this exact group 1827a36c1b4SAndreas Gohr $or->add(Filters::equal('memberOf', $dn)); 183b21740b4SAndreas Gohr } 184b21740b4SAndreas Gohr $filter->add($or); 185b21740b4SAndreas Gohr } 186e7339d5aSAndreas Gohr 187b21740b4SAndreas Gohr $this->debug('Searching ' . $filter->toString(), __FILE__, __LINE__); 1889c590892SAndreas Gohr $attributes = $this->userAttributes(); 1899c590892SAndreas Gohr $search = Operations::search($filter, ...$attributes); 190b21740b4SAndreas Gohr $paging = $this->ldap->paging($search); 191b21740b4SAndreas Gohr 192b21740b4SAndreas Gohr $users = []; 193b21740b4SAndreas Gohr while ($paging->hasEntries()) { 194b21740b4SAndreas Gohr try { 195b21740b4SAndreas Gohr $entries = $paging->getEntries(); 19608ace392SAndreas Gohr } catch (OperationException $e) { 197b21740b4SAndreas Gohr $this->fatal($e); 19880ac552fSAndreas Gohr break; // we abort and return what we have so far 199b21740b4SAndreas Gohr } 200b21740b4SAndreas Gohr 201b21740b4SAndreas Gohr foreach ($entries as $entry) { 20208ace392SAndreas Gohr $userinfo = $this->entry2User($entry); 203746af42cSAndreas Gohr $users[$userinfo['user']] = $userinfo; 204b21740b4SAndreas Gohr } 205b21740b4SAndreas Gohr } 206b21740b4SAndreas Gohr 2071b0eb9b3SAndreas Gohr ksort($users); 208b21740b4SAndreas Gohr return $users; 209b21740b4SAndreas Gohr } 210b21740b4SAndreas Gohr 211a1128cc0SAndreas Gohr /** @inheritDoc */ 212a1128cc0SAndreas Gohr public function cleanUser($user) 21380ac552fSAndreas Gohr { 214a1128cc0SAndreas Gohr return $this->simpleUser($user); 21580ac552fSAndreas Gohr } 21680ac552fSAndreas Gohr 217a1128cc0SAndreas Gohr /** @inheritDoc */ 218a1128cc0SAndreas Gohr public function cleanGroup($group) 219a1128cc0SAndreas Gohr { 220a1128cc0SAndreas Gohr return PhpString::strtolower($group); 221a1128cc0SAndreas Gohr } 222a1128cc0SAndreas Gohr 223a1128cc0SAndreas Gohr /** @inheritDoc */ 224*208fe81aSAndreas Gohr protected function prepareBindUser($user) 225a1128cc0SAndreas Gohr { 22608ace392SAndreas Gohr // add account suffix 22708ace392SAndreas Gohr return $this->qualifiedUser($user); 22880ac552fSAndreas Gohr } 22980ac552fSAndreas Gohr 23080ac552fSAndreas Gohr /** 231e7339d5aSAndreas Gohr * Initializes the Group Cache for nested groups 232e7339d5aSAndreas Gohr * 233e7339d5aSAndreas Gohr * @return GroupHierarchyCache 234e7339d5aSAndreas Gohr */ 235e7339d5aSAndreas Gohr public function getGroupHierarchyCache() 236e7339d5aSAndreas Gohr { 237e7339d5aSAndreas Gohr if ($this->gch === null) { 238e7339d5aSAndreas Gohr if (!$this->autoAuth()) return null; 2395dcabedaSAndreas Gohr $this->gch = new GroupHierarchyCache($this->ldap, $this->config['usefscache']); 240e7339d5aSAndreas Gohr } 241e7339d5aSAndreas Gohr return $this->gch; 242e7339d5aSAndreas Gohr } 243e7339d5aSAndreas Gohr 244e7339d5aSAndreas Gohr /** 245a1128cc0SAndreas Gohr * userPrincipalName in the form <user>@<suffix> 24608ace392SAndreas Gohr * 24708ace392SAndreas Gohr * @param string $user 24808ace392SAndreas Gohr * @return string 24980ac552fSAndreas Gohr */ 250a1128cc0SAndreas Gohr protected function qualifiedUser($user) 251a1128cc0SAndreas Gohr { 252a1128cc0SAndreas Gohr $user = $this->simpleUser($user); // strip any existing qualifiers 253a1128cc0SAndreas Gohr if (!$this->config['suffix']) { 254a1128cc0SAndreas Gohr $this->error('No account suffix set. Logins may fail.', __FILE__, __LINE__); 255a1128cc0SAndreas Gohr } 256a1128cc0SAndreas Gohr 257a1128cc0SAndreas Gohr return $user . '@' . $this->config['suffix']; 258a1128cc0SAndreas Gohr } 259a1128cc0SAndreas Gohr 260a1128cc0SAndreas Gohr /** 261a1128cc0SAndreas Gohr * Removes the account suffix from the given user. Should match the SAMAccountName 26208ace392SAndreas Gohr * 26308ace392SAndreas Gohr * @param string $user 26408ace392SAndreas Gohr * @return string 265a1128cc0SAndreas Gohr */ 266a1128cc0SAndreas Gohr protected function simpleUser($user) 26780ac552fSAndreas Gohr { 26880ac552fSAndreas Gohr $user = PhpString::strtolower($user); 269a1128cc0SAndreas Gohr $user = preg_replace('/@.*$/', '', $user); 270a1128cc0SAndreas Gohr $user = preg_replace('/^.*\\\\/', '', $user); 27180ac552fSAndreas Gohr return $user; 27280ac552fSAndreas Gohr } 27380ac552fSAndreas Gohr 27480ac552fSAndreas Gohr /** 275b21740b4SAndreas Gohr * Transform an LDAP entry to a user info array 276b21740b4SAndreas Gohr * 277b21740b4SAndreas Gohr * @param Entry $entry 278b21740b4SAndreas Gohr * @return array 279b21740b4SAndreas Gohr */ 280b21740b4SAndreas Gohr protected function entry2User(Entry $entry) 281b21740b4SAndreas Gohr { 282b914569fSAndreas Gohr $user = [ 283a1128cc0SAndreas Gohr 'user' => $this->simpleUser($this->attr2str($entry->get('sAMAccountName'))), 2841078ec26SAndreas Gohr 'name' => $this->attr2str($entry->get('DisplayName')) ?: $this->attr2str($entry->get('Name')), 2851078ec26SAndreas Gohr 'mail' => $this->attr2str($entry->get('mail')), 2861078ec26SAndreas Gohr 'dn' => $entry->getDn()->toString(), 2871078ec26SAndreas Gohr 'grps' => $this->getUserGroups($entry), // we always return groups because its currently inexpensive 2881078ec26SAndreas Gohr ]; 289b914569fSAndreas Gohr 2900f498d06SAndreas Gohr // handle password expiry info 2910f498d06SAndreas Gohr $lastChange = $this->attr2str($entry->get('pwdlastset')); 2920f498d06SAndreas Gohr if ($lastChange) { 2930f498d06SAndreas Gohr $lastChange = (int)substr($lastChange, 0, -7); // remove last 7 digits (100ns intervals to seconds) 294*208fe81aSAndreas Gohr $lastChange -= 11_644_473_600; // convert from 1601 to 1970 epoch 2950f498d06SAndreas Gohr } 2960f498d06SAndreas Gohr $user['lastpwd'] = (int)$lastChange; 2970f498d06SAndreas Gohr $user['expires'] = !($this->attr2str($entry->get('useraccountcontrol')) & self::ADS_UF_DONT_EXPIRE_PASSWD); 2980f498d06SAndreas Gohr 299b914569fSAndreas Gohr // get additional attributes 300b914569fSAndreas Gohr foreach ($this->config['attributes'] as $attr) { 301b914569fSAndreas Gohr $user[$attr] = $this->attr2str($entry->get($attr)); 302b914569fSAndreas Gohr } 303b914569fSAndreas Gohr 304b914569fSAndreas Gohr return $user; 3051078ec26SAndreas Gohr } 3061078ec26SAndreas Gohr 3071078ec26SAndreas Gohr /** 3081078ec26SAndreas Gohr * Get the list of groups the given user is member of 3091078ec26SAndreas Gohr * 3101078ec26SAndreas Gohr * This method currently does no LDAP queries and thus is inexpensive. 3111078ec26SAndreas Gohr * 3121078ec26SAndreas Gohr * @param Entry $userentry 3131078ec26SAndreas Gohr * @return array 3141078ec26SAndreas Gohr */ 3151078ec26SAndreas Gohr protected function getUserGroups(Entry $userentry) 3161078ec26SAndreas Gohr { 317e7339d5aSAndreas Gohr $groups = []; 318e7339d5aSAndreas Gohr 319e7339d5aSAndreas Gohr if ($userentry->has('memberOf')) { 320e7339d5aSAndreas Gohr $groupDNs = $userentry->get('memberOf')->getValues(); 321e7339d5aSAndreas Gohr 322e7339d5aSAndreas Gohr if ($this->config['recursivegroups']) { 323e7339d5aSAndreas Gohr $gch = $this->getGroupHierarchyCache(); 324e7339d5aSAndreas Gohr foreach ($groupDNs as $dn) { 325e7339d5aSAndreas Gohr $groupDNs = array_merge($groupDNs, $gch->getParents($dn)); 326e7339d5aSAndreas Gohr } 327e7339d5aSAndreas Gohr 328e7339d5aSAndreas Gohr $groupDNs = array_unique($groupDNs); 329e7339d5aSAndreas Gohr } 330e7339d5aSAndreas Gohr $groups = array_map([$this, 'dn2group'], $groupDNs); 331e7339d5aSAndreas Gohr } 332e7339d5aSAndreas Gohr 333e7339d5aSAndreas Gohr $groups[] = $this->config['defaultgroup']; // always add default 3341078ec26SAndreas Gohr 3351078ec26SAndreas Gohr // resolving the primary group in AD is complicated but basically never needed 3361078ec26SAndreas Gohr // http://support.microsoft.com/?kbid=321360 3371078ec26SAndreas Gohr $gid = $userentry->get('primaryGroupID')->firstValue(); 3381078ec26SAndreas Gohr if ($gid == 513) { 339e7339d5aSAndreas Gohr $groups[] = $this->cleanGroup($this->config['primarygroup']); 34051e92298SAndreas Gohr } 34151e92298SAndreas Gohr 342f17bb68bSAndreas Gohr sort($groups); 343f17bb68bSAndreas Gohr return $groups; 34451e92298SAndreas Gohr } 34551e92298SAndreas Gohr 3469c590892SAndreas Gohr /** @inheritDoc */ 3479c590892SAndreas Gohr protected function userAttributes() 3489c590892SAndreas Gohr { 3499c590892SAndreas Gohr $attr = parent::userAttributes(); 350a1128cc0SAndreas Gohr $attr[] = new Attribute('sAMAccountName'); 3519c590892SAndreas Gohr $attr[] = new Attribute('Name'); 3529c590892SAndreas Gohr $attr[] = new Attribute('primaryGroupID'); 3539c590892SAndreas Gohr $attr[] = new Attribute('memberOf'); 3540f498d06SAndreas Gohr $attr[] = new Attribute('pwdlastset'); 3550f498d06SAndreas Gohr $attr[] = new Attribute('useraccountcontrol'); 3569c590892SAndreas Gohr 3579c590892SAndreas Gohr return $attr; 3589c590892SAndreas Gohr } 359e7339d5aSAndreas Gohr 360e7339d5aSAndreas Gohr /** 3610f498d06SAndreas Gohr * Queries the maximum password age from the AD server 3620f498d06SAndreas Gohr * 3630f498d06SAndreas Gohr * Note: we do not check if passwords actually are set to expire here. This is encoded in the lower 32bit 3640f498d06SAndreas Gohr * of the returned 64bit integer (see link below). We do not check this because it would require us to 3650f498d06SAndreas Gohr * actually do large integer math and we can simply assume it's enabled when the age check was requested in 3660f498d06SAndreas Gohr * DokuWiki configuration. 3670f498d06SAndreas Gohr * 3680f498d06SAndreas Gohr * @link http://msdn.microsoft.com/en-us/library/ms974598.aspx 3690f498d06SAndreas Gohr * @param bool $useCache should a filesystem cache be used if available? 3700f498d06SAndreas Gohr * @return int The maximum password age in seconds 3710f498d06SAndreas Gohr */ 3720f498d06SAndreas Gohr public function getMaxPasswordAge($useCache = true) 3730f498d06SAndreas Gohr { 3740f498d06SAndreas Gohr global $conf; 3750f498d06SAndreas Gohr $cachename = getCacheName('maxPwdAge', '.pureldap-maxPwdAge'); 3760f498d06SAndreas Gohr $cachetime = @filemtime($cachename); 3770f498d06SAndreas Gohr 3780f498d06SAndreas Gohr // valid file system cache? use it 3790f498d06SAndreas Gohr if ($useCache && $cachetime && (time() - $cachetime) < $conf['auth_security_timeout']) { 3800f498d06SAndreas Gohr return (int)file_get_contents($cachename); 3810f498d06SAndreas Gohr } 3820f498d06SAndreas Gohr 3830f498d06SAndreas Gohr if (!$this->autoAuth()) return 0; 3840f498d06SAndreas Gohr 3850f498d06SAndreas Gohr $attr = new Attribute('maxPwdAge'); 3860f498d06SAndreas Gohr try { 3870f498d06SAndreas Gohr $entry = $this->ldap->read( 3880f498d06SAndreas Gohr $this->getConf('base_dn'), 3890f498d06SAndreas Gohr [$attr] 3900f498d06SAndreas Gohr ); 3910f498d06SAndreas Gohr } catch (OperationException $e) { 3920f498d06SAndreas Gohr $this->fatal($e); 3930f498d06SAndreas Gohr return 0; 3940f498d06SAndreas Gohr } 3950f498d06SAndreas Gohr if (!$entry) return 0; 3960f498d06SAndreas Gohr $maxPwdAge = $entry->get($attr)->firstValue(); 3970f498d06SAndreas Gohr 3980f498d06SAndreas Gohr // MS returns 100 nanosecond intervals, we want seconds 3990f498d06SAndreas Gohr // we operate on strings to avoid integer overflow 4000f498d06SAndreas Gohr // we also want a positive value, so we trim off the leading minus sign 4010f498d06SAndreas Gohr // only then we convert to int 4020f498d06SAndreas Gohr $maxPwdAge = (int)ltrim(substr($maxPwdAge, 0, -7), '-'); 4030f498d06SAndreas Gohr 4040f498d06SAndreas Gohr file_put_contents($cachename, $maxPwdAge); 4050f498d06SAndreas Gohr return $maxPwdAge; 4060f498d06SAndreas Gohr } 4070f498d06SAndreas Gohr 4080f498d06SAndreas Gohr /** 409e7339d5aSAndreas Gohr * Extract the group name from the DN 410e7339d5aSAndreas Gohr * 411e7339d5aSAndreas Gohr * @param string $dn 412e7339d5aSAndreas Gohr * @return string 413e7339d5aSAndreas Gohr */ 414e7339d5aSAndreas Gohr protected function dn2group($dn) 415e7339d5aSAndreas Gohr { 416*208fe81aSAndreas Gohr [$cn] = explode(',', $dn, 2); 417e7339d5aSAndreas Gohr return $this->cleanGroup(substr($cn, 3)); 418e7339d5aSAndreas Gohr } 41908ace392SAndreas Gohr 42008ace392SAndreas Gohr /** 42108ace392SAndreas Gohr * Encode a password for transmission over LDAP 42208ace392SAndreas Gohr * 42308ace392SAndreas Gohr * Passwords are encoded as UTF-16LE strings encapsulated in quotes. 42408ace392SAndreas Gohr * 42508ace392SAndreas Gohr * @param string $password The password to encode 42608ace392SAndreas Gohr * @return string 42708ace392SAndreas Gohr */ 42808ace392SAndreas Gohr protected function encodePassword($password) 42908ace392SAndreas Gohr { 43008ace392SAndreas Gohr $password = "\"" . $password . "\""; 43108ace392SAndreas Gohr 43208ace392SAndreas Gohr if (function_exists('iconv')) { 43308ace392SAndreas Gohr $adpassword = iconv('UTF-8', 'UTF-16LE', $password); 43408ace392SAndreas Gohr } elseif (function_exists('mb_convert_encoding')) { 43508ace392SAndreas Gohr $adpassword = mb_convert_encoding($password, "UTF-16LE", "UTF-8"); 43608ace392SAndreas Gohr } else { 43708ace392SAndreas Gohr // this will only work for ASCII7 passwords 43808ace392SAndreas Gohr $adpassword = ''; 43908ace392SAndreas Gohr for ($i = 0; $i < strlen($password); $i++) { 44008ace392SAndreas Gohr $adpassword .= "$password[$i]\000"; 44108ace392SAndreas Gohr } 44208ace392SAndreas Gohr } 44308ace392SAndreas Gohr return $adpassword; 44408ace392SAndreas Gohr } 4451078ec26SAndreas Gohr} 446