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*0f498d06SAndreas Gohr const ADS_UF_DONT_EXPIRE_PASSWD = 0x10000; 19*0f498d06SAndreas Gohr 20e7339d5aSAndreas Gohr /** 21e7339d5aSAndreas Gohr * @var GroupHierarchyCache 22e7339d5aSAndreas Gohr * @see getGroupHierarchyCache 23e7339d5aSAndreas Gohr */ 24e7339d5aSAndreas Gohr protected $gch = null; 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 { 521078ec26SAndreas Gohr /** @var Entries $entries */ 539c590892SAndreas Gohr $attributes = $this->userAttributes(); 549c590892SAndreas Gohr $entries = $this->ldap->search(Operations::search($filter, ...$attributes)); 551078ec26SAndreas Gohr } catch (OperationException $e) { 56b21740b4SAndreas Gohr $this->fatal($e); 571078ec26SAndreas Gohr return null; 581078ec26SAndreas Gohr } 591078ec26SAndreas Gohr if ($entries->count() !== 1) return null; 6008ace392SAndreas Gohr return $entries->first(); 6108ace392SAndreas Gohr } 6208ace392SAndreas Gohr 6308ace392SAndreas Gohr /** @inheritDoc */ 6408ace392SAndreas Gohr public function setPassword($username, $newpass, $oldpass = null) 6508ace392SAndreas Gohr { 6608ace392SAndreas Gohr if (!$this->autoAuth()) return false; 6708ace392SAndreas Gohr 6808ace392SAndreas Gohr $entry = $this->getUserEntry($username); 6908ace392SAndreas Gohr if ($entry === null) { 7008ace392SAndreas Gohr $this->error("User '$username' not found", __FILE__, __LINE__); 7108ace392SAndreas Gohr return false; 7208ace392SAndreas Gohr } 7308ace392SAndreas Gohr 7408ace392SAndreas Gohr if ($oldpass) { 7508ace392SAndreas Gohr // if an old password is given, this is a self-service password change 7608ace392SAndreas Gohr // this has to be executed as the user themselves, not as the admin 7708ace392SAndreas Gohr if ($this->isAuthenticated !== $this->prepareBindUser($username)) { 7808ace392SAndreas Gohr if (!$this->authenticate($username, $oldpass)) { 7908ace392SAndreas Gohr $this->error("Old password for '$username' is wrong", __FILE__, __LINE__); 8008ace392SAndreas Gohr return false; 8108ace392SAndreas Gohr } 8208ace392SAndreas Gohr } 8308ace392SAndreas Gohr 8408ace392SAndreas Gohr $entry->remove('unicodePwd', $this->encodePassword($oldpass)); 8508ace392SAndreas Gohr $entry->add('unicodePwd', $this->encodePassword($newpass)); 8608ace392SAndreas Gohr } else { 8708ace392SAndreas Gohr // run as admin user 8808ace392SAndreas Gohr $entry->set('unicodePwd', $this->encodePassword($newpass)); 8908ace392SAndreas Gohr } 9008ace392SAndreas Gohr 9108ace392SAndreas Gohr try { 9208ace392SAndreas Gohr $this->ldap->update($entry); 9308ace392SAndreas Gohr } catch (OperationException $e) { 9408ace392SAndreas Gohr $this->fatal($e); 9508ace392SAndreas Gohr return false; 9608ace392SAndreas Gohr } 9708ace392SAndreas Gohr return true; 98b21740b4SAndreas Gohr } 991078ec26SAndreas Gohr 100b21740b4SAndreas Gohr /** @inheritDoc */ 101204fba68SAndreas Gohr public function getGroups($match = null, $filtermethod = self::FILTER_EQUAL) 102b21740b4SAndreas Gohr { 103b21740b4SAndreas Gohr if (!$this->autoAuth()) return []; 104b21740b4SAndreas Gohr 105b21740b4SAndreas Gohr $filter = Filters::and( 106b21740b4SAndreas Gohr Filters::equal('objectClass', 'group') 107b21740b4SAndreas Gohr ); 108b21740b4SAndreas Gohr if ($match !== null) { 109e7c3e817SAndreas Gohr // FIXME this is a workaround that removes regex anchors and quoting as passed by the groupuser plugin 110e7c3e817SAndreas Gohr // a proper fix requires splitbrain/dokuwiki#3028 to be implemented 111fce018daSAndreas Gohr $match = ltrim($match, '^'); 112fce018daSAndreas Gohr $match = rtrim($match, '$'); 113e7c3e817SAndreas Gohr $match = stripslashes($match); 114fce018daSAndreas Gohr 115b21740b4SAndreas Gohr $filter->add(Filters::$filtermethod('cn', $match)); 116b21740b4SAndreas Gohr } 117b21740b4SAndreas Gohr 118b21740b4SAndreas Gohr $this->debug('Searching ' . $filter->toString(), __FILE__, __LINE__); 119b21740b4SAndreas Gohr $search = Operations::search($filter, 'cn'); 120b21740b4SAndreas Gohr $paging = $this->ldap->paging($search); 121b21740b4SAndreas Gohr 122b21740b4SAndreas Gohr $groups = []; 123b21740b4SAndreas Gohr while ($paging->hasEntries()) { 124b21740b4SAndreas Gohr try { 125b21740b4SAndreas Gohr $entries = $paging->getEntries(); 12608ace392SAndreas Gohr } catch (OperationException $e) { 127b21740b4SAndreas Gohr $this->fatal($e); 128b21740b4SAndreas Gohr return $groups; // we return what we got so far 129b21740b4SAndreas Gohr } 130b21740b4SAndreas Gohr 131b21740b4SAndreas Gohr foreach ($entries as $entry) { 132b21740b4SAndreas Gohr /** @var Entry $entry */ 133204fba68SAndreas Gohr $groups[$entry->getDn()->toString()] = $this->cleanGroup($this->attr2str($entry->get('cn'))); 134b21740b4SAndreas Gohr } 135b21740b4SAndreas Gohr } 136b21740b4SAndreas Gohr 1371b0eb9b3SAndreas Gohr asort($groups); 138b21740b4SAndreas Gohr return $groups; 139b21740b4SAndreas Gohr } 140b21740b4SAndreas Gohr 141b21740b4SAndreas Gohr /** 142b21740b4SAndreas Gohr * Fetch users matching the given filters 143b21740b4SAndreas Gohr * 144b21740b4SAndreas Gohr * @param array $match 145b21740b4SAndreas Gohr * @param string $filtermethod The method to use for filtering 146b21740b4SAndreas Gohr * @return array 147b21740b4SAndreas Gohr */ 148204fba68SAndreas Gohr public function getFilteredUsers($match, $filtermethod = self::FILTER_EQUAL) 149b21740b4SAndreas Gohr { 150b21740b4SAndreas Gohr if (!$this->autoAuth()) return []; 151b21740b4SAndreas Gohr 152b21740b4SAndreas Gohr $filter = Filters::and(Filters::equal('objectClass', 'user')); 153b21740b4SAndreas Gohr if (isset($match['user'])) { 154a1128cc0SAndreas Gohr $filter->add(Filters::$filtermethod('sAMAccountName', $this->simpleUser($match['user']))); 155b21740b4SAndreas Gohr } 156b21740b4SAndreas Gohr if (isset($match['name'])) { 157b21740b4SAndreas Gohr $filter->add(Filters::$filtermethod('displayName', $match['name'])); 158b21740b4SAndreas Gohr } 159b21740b4SAndreas Gohr if (isset($match['mail'])) { 160b21740b4SAndreas Gohr $filter->add(Filters::$filtermethod('mail', $match['mail'])); 161b21740b4SAndreas Gohr } 162b21740b4SAndreas Gohr if (isset($match['grps'])) { 163b21740b4SAndreas Gohr // memberOf can not be checked with a substring match, so we need to get the right groups first 164b21740b4SAndreas Gohr $groups = $this->getGroups($match['grps'], $filtermethod); 165e7339d5aSAndreas Gohr $groupDNs = array_keys($groups); 166e7339d5aSAndreas Gohr 167e7339d5aSAndreas Gohr if ($this->config['recursivegroups']) { 168e7339d5aSAndreas Gohr $gch = $this->getGroupHierarchyCache(); 169e7339d5aSAndreas Gohr foreach ($groupDNs as $dn) { 170e7339d5aSAndreas Gohr $groupDNs = array_merge($groupDNs, $gch->getChildren($dn)); 171e7339d5aSAndreas Gohr } 172e7339d5aSAndreas Gohr $groupDNs = array_unique($groupDNs); 173e7339d5aSAndreas Gohr } 174e7339d5aSAndreas Gohr 175b21740b4SAndreas Gohr $or = Filters::or(); 176e7339d5aSAndreas Gohr foreach ($groupDNs as $dn) { 177204fba68SAndreas Gohr // domain users membership is in primary group 178e7339d5aSAndreas Gohr if ($this->dn2group($dn) === $this->config['primarygroup']) { 179204fba68SAndreas Gohr $or->add(Filters::equal('primaryGroupID', 513)); 180204fba68SAndreas Gohr continue; 181204fba68SAndreas Gohr } 1827a36c1b4SAndreas Gohr // find members of this exact group 1837a36c1b4SAndreas Gohr $or->add(Filters::equal('memberOf', $dn)); 184b21740b4SAndreas Gohr } 185b21740b4SAndreas Gohr $filter->add($or); 186b21740b4SAndreas Gohr } 187e7339d5aSAndreas Gohr 188b21740b4SAndreas Gohr $this->debug('Searching ' . $filter->toString(), __FILE__, __LINE__); 1899c590892SAndreas Gohr $attributes = $this->userAttributes(); 1909c590892SAndreas Gohr $search = Operations::search($filter, ...$attributes); 191b21740b4SAndreas Gohr $paging = $this->ldap->paging($search); 192b21740b4SAndreas Gohr 193b21740b4SAndreas Gohr $users = []; 194b21740b4SAndreas Gohr while ($paging->hasEntries()) { 195b21740b4SAndreas Gohr try { 196b21740b4SAndreas Gohr $entries = $paging->getEntries(); 19708ace392SAndreas Gohr } catch (OperationException $e) { 198b21740b4SAndreas Gohr $this->fatal($e); 19980ac552fSAndreas Gohr break; // we abort and return what we have so far 200b21740b4SAndreas Gohr } 201b21740b4SAndreas Gohr 202b21740b4SAndreas Gohr foreach ($entries as $entry) { 20308ace392SAndreas Gohr $userinfo = $this->entry2User($entry); 204746af42cSAndreas Gohr $users[$userinfo['user']] = $userinfo; 205b21740b4SAndreas Gohr } 206b21740b4SAndreas Gohr } 207b21740b4SAndreas Gohr 2081b0eb9b3SAndreas Gohr ksort($users); 209b21740b4SAndreas Gohr return $users; 210b21740b4SAndreas Gohr } 211b21740b4SAndreas Gohr 212a1128cc0SAndreas Gohr /** @inheritDoc */ 213a1128cc0SAndreas Gohr public function cleanUser($user) 21480ac552fSAndreas Gohr { 215a1128cc0SAndreas Gohr return $this->simpleUser($user); 21680ac552fSAndreas Gohr } 21780ac552fSAndreas Gohr 218a1128cc0SAndreas Gohr /** @inheritDoc */ 219a1128cc0SAndreas Gohr public function cleanGroup($group) 220a1128cc0SAndreas Gohr { 221a1128cc0SAndreas Gohr return PhpString::strtolower($group); 222a1128cc0SAndreas Gohr } 223a1128cc0SAndreas Gohr 224a1128cc0SAndreas Gohr /** @inheritDoc */ 225a1128cc0SAndreas Gohr public function prepareBindUser($user) 226a1128cc0SAndreas Gohr { 22708ace392SAndreas Gohr // add account suffix 22808ace392SAndreas Gohr return $this->qualifiedUser($user); 22980ac552fSAndreas Gohr } 23080ac552fSAndreas Gohr 23180ac552fSAndreas Gohr /** 232e7339d5aSAndreas Gohr * Initializes the Group Cache for nested groups 233e7339d5aSAndreas Gohr * 234e7339d5aSAndreas Gohr * @return GroupHierarchyCache 235e7339d5aSAndreas Gohr */ 236e7339d5aSAndreas Gohr public function getGroupHierarchyCache() 237e7339d5aSAndreas Gohr { 238e7339d5aSAndreas Gohr if ($this->gch === null) { 239e7339d5aSAndreas Gohr if (!$this->autoAuth()) return null; 2405dcabedaSAndreas Gohr $this->gch = new GroupHierarchyCache($this->ldap, $this->config['usefscache']); 241e7339d5aSAndreas Gohr } 242e7339d5aSAndreas Gohr return $this->gch; 243e7339d5aSAndreas Gohr } 244e7339d5aSAndreas Gohr 245e7339d5aSAndreas Gohr /** 246a1128cc0SAndreas Gohr * userPrincipalName in the form <user>@<suffix> 24708ace392SAndreas Gohr * 24808ace392SAndreas Gohr * @param string $user 24908ace392SAndreas Gohr * @return string 25080ac552fSAndreas Gohr */ 251a1128cc0SAndreas Gohr protected function qualifiedUser($user) 252a1128cc0SAndreas Gohr { 253a1128cc0SAndreas Gohr $user = $this->simpleUser($user); // strip any existing qualifiers 254a1128cc0SAndreas Gohr if (!$this->config['suffix']) { 255a1128cc0SAndreas Gohr $this->error('No account suffix set. Logins may fail.', __FILE__, __LINE__); 256a1128cc0SAndreas Gohr } 257a1128cc0SAndreas Gohr 258a1128cc0SAndreas Gohr return $user . '@' . $this->config['suffix']; 259a1128cc0SAndreas Gohr } 260a1128cc0SAndreas Gohr 261a1128cc0SAndreas Gohr /** 262a1128cc0SAndreas Gohr * Removes the account suffix from the given user. Should match the SAMAccountName 26308ace392SAndreas Gohr * 26408ace392SAndreas Gohr * @param string $user 26508ace392SAndreas Gohr * @return string 266a1128cc0SAndreas Gohr */ 267a1128cc0SAndreas Gohr protected function simpleUser($user) 26880ac552fSAndreas Gohr { 26980ac552fSAndreas Gohr $user = PhpString::strtolower($user); 270a1128cc0SAndreas Gohr $user = preg_replace('/@.*$/', '', $user); 271a1128cc0SAndreas Gohr $user = preg_replace('/^.*\\\\/', '', $user); 27280ac552fSAndreas Gohr return $user; 27380ac552fSAndreas Gohr } 27480ac552fSAndreas Gohr 27580ac552fSAndreas Gohr /** 276b21740b4SAndreas Gohr * Transform an LDAP entry to a user info array 277b21740b4SAndreas Gohr * 278b21740b4SAndreas Gohr * @param Entry $entry 279b21740b4SAndreas Gohr * @return array 280b21740b4SAndreas Gohr */ 281b21740b4SAndreas Gohr protected function entry2User(Entry $entry) 282b21740b4SAndreas Gohr { 283b914569fSAndreas Gohr $user = [ 284a1128cc0SAndreas Gohr 'user' => $this->simpleUser($this->attr2str($entry->get('sAMAccountName'))), 2851078ec26SAndreas Gohr 'name' => $this->attr2str($entry->get('DisplayName')) ?: $this->attr2str($entry->get('Name')), 2861078ec26SAndreas Gohr 'mail' => $this->attr2str($entry->get('mail')), 2871078ec26SAndreas Gohr 'dn' => $entry->getDn()->toString(), 2881078ec26SAndreas Gohr 'grps' => $this->getUserGroups($entry), // we always return groups because its currently inexpensive 2891078ec26SAndreas Gohr ]; 290b914569fSAndreas Gohr 291*0f498d06SAndreas Gohr // handle password expiry info 292*0f498d06SAndreas Gohr $lastChange = $this->attr2str($entry->get('pwdlastset')); 293*0f498d06SAndreas Gohr if ($lastChange) { 294*0f498d06SAndreas Gohr $lastChange = (int)substr($lastChange, 0, -7); // remove last 7 digits (100ns intervals to seconds) 295*0f498d06SAndreas Gohr $lastChange = $lastChange - 11644473600; // convert from 1601 to 1970 epoch 296*0f498d06SAndreas Gohr } 297*0f498d06SAndreas Gohr $user['lastpwd'] = (int)$lastChange; 298*0f498d06SAndreas Gohr $user['expires'] = !($this->attr2str($entry->get('useraccountcontrol')) & self::ADS_UF_DONT_EXPIRE_PASSWD); 299*0f498d06SAndreas Gohr 300b914569fSAndreas Gohr // get additional attributes 301b914569fSAndreas Gohr foreach ($this->config['attributes'] as $attr) { 302b914569fSAndreas Gohr $user[$attr] = $this->attr2str($entry->get($attr)); 303b914569fSAndreas Gohr } 304b914569fSAndreas Gohr 305b914569fSAndreas Gohr return $user; 3061078ec26SAndreas Gohr } 3071078ec26SAndreas Gohr 3081078ec26SAndreas Gohr /** 3091078ec26SAndreas Gohr * Get the list of groups the given user is member of 3101078ec26SAndreas Gohr * 3111078ec26SAndreas Gohr * This method currently does no LDAP queries and thus is inexpensive. 3121078ec26SAndreas Gohr * 3131078ec26SAndreas Gohr * @param Entry $userentry 3141078ec26SAndreas Gohr * @return array 3151078ec26SAndreas Gohr */ 3161078ec26SAndreas Gohr protected function getUserGroups(Entry $userentry) 3171078ec26SAndreas Gohr { 318e7339d5aSAndreas Gohr $groups = []; 319e7339d5aSAndreas Gohr 320e7339d5aSAndreas Gohr if ($userentry->has('memberOf')) { 321e7339d5aSAndreas Gohr $groupDNs = $userentry->get('memberOf')->getValues(); 322e7339d5aSAndreas Gohr 323e7339d5aSAndreas Gohr if ($this->config['recursivegroups']) { 324e7339d5aSAndreas Gohr $gch = $this->getGroupHierarchyCache(); 325e7339d5aSAndreas Gohr foreach ($groupDNs as $dn) { 326e7339d5aSAndreas Gohr $groupDNs = array_merge($groupDNs, $gch->getParents($dn)); 327e7339d5aSAndreas Gohr } 328e7339d5aSAndreas Gohr 329e7339d5aSAndreas Gohr $groupDNs = array_unique($groupDNs); 330e7339d5aSAndreas Gohr } 331e7339d5aSAndreas Gohr $groups = array_map([$this, 'dn2group'], $groupDNs); 332e7339d5aSAndreas Gohr } 333e7339d5aSAndreas Gohr 334e7339d5aSAndreas Gohr $groups[] = $this->config['defaultgroup']; // always add default 3351078ec26SAndreas Gohr 3361078ec26SAndreas Gohr // resolving the primary group in AD is complicated but basically never needed 3371078ec26SAndreas Gohr // http://support.microsoft.com/?kbid=321360 3381078ec26SAndreas Gohr $gid = $userentry->get('primaryGroupID')->firstValue(); 3391078ec26SAndreas Gohr if ($gid == 513) { 340e7339d5aSAndreas Gohr $groups[] = $this->cleanGroup($this->config['primarygroup']); 34151e92298SAndreas Gohr } 34251e92298SAndreas Gohr 343f17bb68bSAndreas Gohr sort($groups); 344f17bb68bSAndreas Gohr return $groups; 34551e92298SAndreas Gohr } 34651e92298SAndreas Gohr 3479c590892SAndreas Gohr /** @inheritDoc */ 3489c590892SAndreas Gohr protected function userAttributes() 3499c590892SAndreas Gohr { 3509c590892SAndreas Gohr $attr = parent::userAttributes(); 351a1128cc0SAndreas Gohr $attr[] = new Attribute('sAMAccountName'); 3529c590892SAndreas Gohr $attr[] = new Attribute('Name'); 3539c590892SAndreas Gohr $attr[] = new Attribute('primaryGroupID'); 3549c590892SAndreas Gohr $attr[] = new Attribute('memberOf'); 355*0f498d06SAndreas Gohr $attr[] = new Attribute('pwdlastset'); 356*0f498d06SAndreas Gohr $attr[] = new Attribute('useraccountcontrol'); 3579c590892SAndreas Gohr 3589c590892SAndreas Gohr return $attr; 3599c590892SAndreas Gohr } 360e7339d5aSAndreas Gohr 361e7339d5aSAndreas Gohr /** 362*0f498d06SAndreas Gohr * Queries the maximum password age from the AD server 363*0f498d06SAndreas Gohr * 364*0f498d06SAndreas Gohr * Note: we do not check if passwords actually are set to expire here. This is encoded in the lower 32bit 365*0f498d06SAndreas Gohr * of the returned 64bit integer (see link below). We do not check this because it would require us to 366*0f498d06SAndreas Gohr * actually do large integer math and we can simply assume it's enabled when the age check was requested in 367*0f498d06SAndreas Gohr * DokuWiki configuration. 368*0f498d06SAndreas Gohr * 369*0f498d06SAndreas Gohr * @link http://msdn.microsoft.com/en-us/library/ms974598.aspx 370*0f498d06SAndreas Gohr * @param bool $useCache should a filesystem cache be used if available? 371*0f498d06SAndreas Gohr * @return int The maximum password age in seconds 372*0f498d06SAndreas Gohr */ 373*0f498d06SAndreas Gohr public function getMaxPasswordAge($useCache = true) 374*0f498d06SAndreas Gohr { 375*0f498d06SAndreas Gohr global $conf; 376*0f498d06SAndreas Gohr $cachename = getCacheName('maxPwdAge', '.pureldap-maxPwdAge'); 377*0f498d06SAndreas Gohr $cachetime = @filemtime($cachename); 378*0f498d06SAndreas Gohr 379*0f498d06SAndreas Gohr // valid file system cache? use it 380*0f498d06SAndreas Gohr if ($useCache && $cachetime && (time() - $cachetime) < $conf['auth_security_timeout']) { 381*0f498d06SAndreas Gohr return (int)file_get_contents($cachename); 382*0f498d06SAndreas Gohr } 383*0f498d06SAndreas Gohr 384*0f498d06SAndreas Gohr if (!$this->autoAuth()) return 0; 385*0f498d06SAndreas Gohr 386*0f498d06SAndreas Gohr $attr = new Attribute('maxPwdAge'); 387*0f498d06SAndreas Gohr try { 388*0f498d06SAndreas Gohr $entry = $this->ldap->read( 389*0f498d06SAndreas Gohr $this->getConf('base_dn'), 390*0f498d06SAndreas Gohr [$attr] 391*0f498d06SAndreas Gohr ); 392*0f498d06SAndreas Gohr } catch (OperationException $e) { 393*0f498d06SAndreas Gohr $this->fatal($e); 394*0f498d06SAndreas Gohr return 0; 395*0f498d06SAndreas Gohr } 396*0f498d06SAndreas Gohr if (!$entry) return 0; 397*0f498d06SAndreas Gohr $maxPwdAge = $entry->get($attr)->firstValue(); 398*0f498d06SAndreas Gohr 399*0f498d06SAndreas Gohr // MS returns 100 nanosecond intervals, we want seconds 400*0f498d06SAndreas Gohr // we operate on strings to avoid integer overflow 401*0f498d06SAndreas Gohr // we also want a positive value, so we trim off the leading minus sign 402*0f498d06SAndreas Gohr // only then we convert to int 403*0f498d06SAndreas Gohr $maxPwdAge = (int)ltrim(substr($maxPwdAge, 0, -7), '-'); 404*0f498d06SAndreas Gohr 405*0f498d06SAndreas Gohr file_put_contents($cachename, $maxPwdAge); 406*0f498d06SAndreas Gohr return $maxPwdAge; 407*0f498d06SAndreas Gohr } 408*0f498d06SAndreas Gohr 409*0f498d06SAndreas Gohr /** 410e7339d5aSAndreas Gohr * Extract the group name from the DN 411e7339d5aSAndreas Gohr * 412e7339d5aSAndreas Gohr * @param string $dn 413e7339d5aSAndreas Gohr * @return string 414e7339d5aSAndreas Gohr */ 415e7339d5aSAndreas Gohr protected function dn2group($dn) 416e7339d5aSAndreas Gohr { 417e7339d5aSAndreas Gohr list($cn) = explode(',', $dn, 2); 418e7339d5aSAndreas Gohr return $this->cleanGroup(substr($cn, 3)); 419e7339d5aSAndreas Gohr } 42008ace392SAndreas Gohr 42108ace392SAndreas Gohr /** 42208ace392SAndreas Gohr * Encode a password for transmission over LDAP 42308ace392SAndreas Gohr * 42408ace392SAndreas Gohr * Passwords are encoded as UTF-16LE strings encapsulated in quotes. 42508ace392SAndreas Gohr * 42608ace392SAndreas Gohr * @param string $password The password to encode 42708ace392SAndreas Gohr * @return string 42808ace392SAndreas Gohr */ 42908ace392SAndreas Gohr protected function encodePassword($password) 43008ace392SAndreas Gohr { 43108ace392SAndreas Gohr $password = "\"" . $password . "\""; 43208ace392SAndreas Gohr 43308ace392SAndreas Gohr if (function_exists('iconv')) { 43408ace392SAndreas Gohr $adpassword = iconv('UTF-8', 'UTF-16LE', $password); 43508ace392SAndreas Gohr } elseif (function_exists('mb_convert_encoding')) { 43608ace392SAndreas Gohr $adpassword = mb_convert_encoding($password, "UTF-16LE", "UTF-8"); 43708ace392SAndreas Gohr } else { 43808ace392SAndreas Gohr // this will only work for ASCII7 passwords 43908ace392SAndreas Gohr $adpassword = ''; 44008ace392SAndreas Gohr for ($i = 0; $i < strlen($password); $i++) { 44108ace392SAndreas Gohr $adpassword .= "$password[$i]\000"; 44208ace392SAndreas Gohr } 44308ace392SAndreas Gohr } 44408ace392SAndreas Gohr return $adpassword; 44508ace392SAndreas Gohr } 4461078ec26SAndreas Gohr} 447