xref: /plugin/pureldap/classes/ADClient.php (revision 08ace392be71b69ddc8b1eda246fad47272b7606)
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{
18e7339d5aSAndreas Gohr    /**
19e7339d5aSAndreas Gohr     * @var GroupHierarchyCache
20e7339d5aSAndreas Gohr     * @see getGroupHierarchyCache
21e7339d5aSAndreas Gohr     */
22e7339d5aSAndreas Gohr    protected $gch = null;
231078ec26SAndreas Gohr
241078ec26SAndreas Gohr    /** @inheritDoc */
251078ec26SAndreas Gohr    public function getUser($username, $fetchgroups = true)
261078ec26SAndreas Gohr    {
27*08ace392SAndreas Gohr        $entry = $this->getUserEntry($username);
28*08ace392SAndreas Gohr        if ($entry === null) return null;
29*08ace392SAndreas Gohr        return $this->entry2User($entry);
30*08ace392SAndreas Gohr    }
31*08ace392SAndreas Gohr
32*08ace392SAndreas Gohr    /**
33*08ace392SAndreas Gohr     * Get the LDAP entry for the given user
34*08ace392SAndreas Gohr     *
35*08ace392SAndreas Gohr     * @param string $username
36*08ace392SAndreas Gohr     * @return Entry|null
37*08ace392SAndreas Gohr     */
38*08ace392SAndreas Gohr    protected function getUserEntry($username)
39*08ace392SAndreas Gohr    {
401078ec26SAndreas Gohr        if (!$this->autoAuth()) return null;
41a1128cc0SAndreas Gohr        $username = $this->simpleUser($username);
421078ec26SAndreas Gohr
431078ec26SAndreas Gohr        $filter = Filters::and(
441078ec26SAndreas Gohr            Filters::equal('objectClass', 'user'),
45a1128cc0SAndreas Gohr            Filters::equal('sAMAccountName', $this->simpleUser($username))
461078ec26SAndreas Gohr        );
47b21740b4SAndreas Gohr        $this->debug('Searching ' . $filter->toString(), __FILE__, __LINE__);
481078ec26SAndreas Gohr
491078ec26SAndreas Gohr        try {
501078ec26SAndreas Gohr            /** @var Entries $entries */
519c590892SAndreas Gohr            $attributes = $this->userAttributes();
529c590892SAndreas Gohr            $entries = $this->ldap->search(Operations::search($filter, ...$attributes));
531078ec26SAndreas Gohr        } catch (OperationException $e) {
54b21740b4SAndreas Gohr            $this->fatal($e);
551078ec26SAndreas Gohr            return null;
561078ec26SAndreas Gohr        }
571078ec26SAndreas Gohr        if ($entries->count() !== 1) return null;
58*08ace392SAndreas Gohr        return $entries->first();
59*08ace392SAndreas Gohr    }
60*08ace392SAndreas Gohr
61*08ace392SAndreas Gohr    /** @inheritDoc */
62*08ace392SAndreas Gohr    public function setPassword($username, $newpass, $oldpass = null)
63*08ace392SAndreas Gohr    {
64*08ace392SAndreas Gohr        if (!$this->autoAuth()) return false;
65*08ace392SAndreas Gohr
66*08ace392SAndreas Gohr        $entry = $this->getUserEntry($username);
67*08ace392SAndreas Gohr        if ($entry === null) {
68*08ace392SAndreas Gohr            $this->error("User '$username' not found", __FILE__, __LINE__);
69*08ace392SAndreas Gohr            return false;
70*08ace392SAndreas Gohr        }
71*08ace392SAndreas Gohr
72*08ace392SAndreas Gohr        if ($oldpass) {
73*08ace392SAndreas Gohr            // if an old password is given, this is a self-service password change
74*08ace392SAndreas Gohr            // this has to be executed as the user themselves, not as the admin
75*08ace392SAndreas Gohr            if ($this->isAuthenticated !== $this->prepareBindUser($username)) {
76*08ace392SAndreas Gohr                if (!$this->authenticate($username, $oldpass)) {
77*08ace392SAndreas Gohr                    $this->error("Old password for '$username' is wrong", __FILE__, __LINE__);
78*08ace392SAndreas Gohr                    return false;
79*08ace392SAndreas Gohr                }
80*08ace392SAndreas Gohr            }
81*08ace392SAndreas Gohr
82*08ace392SAndreas Gohr            $entry->remove('unicodePwd', $this->encodePassword($oldpass));
83*08ace392SAndreas Gohr            $entry->add('unicodePwd', $this->encodePassword($newpass));
84*08ace392SAndreas Gohr        } else {
85*08ace392SAndreas Gohr            // run as admin user
86*08ace392SAndreas Gohr            $entry->set('unicodePwd', $this->encodePassword($newpass));
87*08ace392SAndreas Gohr        }
88*08ace392SAndreas Gohr
89*08ace392SAndreas Gohr        try {
90*08ace392SAndreas Gohr            $this->ldap->update($entry);
91*08ace392SAndreas Gohr        } catch (OperationException $e) {
92*08ace392SAndreas Gohr            $this->fatal($e);
93*08ace392SAndreas Gohr            return false;
94*08ace392SAndreas Gohr        }
95*08ace392SAndreas Gohr        return true;
96b21740b4SAndreas Gohr    }
971078ec26SAndreas Gohr
98b21740b4SAndreas Gohr    /** @inheritDoc */
99204fba68SAndreas Gohr    public function getGroups($match = null, $filtermethod = self::FILTER_EQUAL)
100b21740b4SAndreas Gohr    {
101b21740b4SAndreas Gohr        if (!$this->autoAuth()) return [];
102b21740b4SAndreas Gohr
103b21740b4SAndreas Gohr        $filter = Filters::and(
104b21740b4SAndreas Gohr            Filters::equal('objectClass', 'group')
105b21740b4SAndreas Gohr        );
106b21740b4SAndreas Gohr        if ($match !== null) {
107e7c3e817SAndreas Gohr            // FIXME this is a workaround that removes regex anchors and quoting as passed by the groupuser plugin
108e7c3e817SAndreas Gohr            // a proper fix requires splitbrain/dokuwiki#3028 to be implemented
109fce018daSAndreas Gohr            $match = ltrim($match, '^');
110fce018daSAndreas Gohr            $match = rtrim($match, '$');
111e7c3e817SAndreas Gohr            $match = stripslashes($match);
112fce018daSAndreas Gohr
113b21740b4SAndreas Gohr            $filter->add(Filters::$filtermethod('cn', $match));
114b21740b4SAndreas Gohr        }
115b21740b4SAndreas Gohr
116b21740b4SAndreas Gohr        $this->debug('Searching ' . $filter->toString(), __FILE__, __LINE__);
117b21740b4SAndreas Gohr        $search = Operations::search($filter, 'cn');
118b21740b4SAndreas Gohr        $paging = $this->ldap->paging($search);
119b21740b4SAndreas Gohr
120b21740b4SAndreas Gohr        $groups = [];
121b21740b4SAndreas Gohr        while ($paging->hasEntries()) {
122b21740b4SAndreas Gohr            try {
123b21740b4SAndreas Gohr                $entries = $paging->getEntries();
124*08ace392SAndreas Gohr            } catch (OperationException $e) {
125b21740b4SAndreas Gohr                $this->fatal($e);
126b21740b4SAndreas Gohr                return $groups; // we return what we got so far
127b21740b4SAndreas Gohr            }
128b21740b4SAndreas Gohr
129b21740b4SAndreas Gohr            foreach ($entries as $entry) {
130b21740b4SAndreas Gohr                /** @var Entry $entry */
131204fba68SAndreas Gohr                $groups[$entry->getDn()->toString()] = $this->cleanGroup($this->attr2str($entry->get('cn')));
132b21740b4SAndreas Gohr            }
133b21740b4SAndreas Gohr        }
134b21740b4SAndreas Gohr
1351b0eb9b3SAndreas Gohr        asort($groups);
136b21740b4SAndreas Gohr        return $groups;
137b21740b4SAndreas Gohr    }
138b21740b4SAndreas Gohr
139b21740b4SAndreas Gohr    /**
140b21740b4SAndreas Gohr     * Fetch users matching the given filters
141b21740b4SAndreas Gohr     *
142b21740b4SAndreas Gohr     * @param array $match
143b21740b4SAndreas Gohr     * @param string $filtermethod The method to use for filtering
144b21740b4SAndreas Gohr     * @return array
145b21740b4SAndreas Gohr     */
146204fba68SAndreas Gohr    public function getFilteredUsers($match, $filtermethod = self::FILTER_EQUAL)
147b21740b4SAndreas Gohr    {
148b21740b4SAndreas Gohr        if (!$this->autoAuth()) return [];
149b21740b4SAndreas Gohr
150b21740b4SAndreas Gohr        $filter = Filters::and(Filters::equal('objectClass', 'user'));
151b21740b4SAndreas Gohr        if (isset($match['user'])) {
152a1128cc0SAndreas Gohr            $filter->add(Filters::$filtermethod('sAMAccountName', $this->simpleUser($match['user'])));
153b21740b4SAndreas Gohr        }
154b21740b4SAndreas Gohr        if (isset($match['name'])) {
155b21740b4SAndreas Gohr            $filter->add(Filters::$filtermethod('displayName', $match['name']));
156b21740b4SAndreas Gohr        }
157b21740b4SAndreas Gohr        if (isset($match['mail'])) {
158b21740b4SAndreas Gohr            $filter->add(Filters::$filtermethod('mail', $match['mail']));
159b21740b4SAndreas Gohr        }
160b21740b4SAndreas Gohr        if (isset($match['grps'])) {
161b21740b4SAndreas Gohr            // memberOf can not be checked with a substring match, so we need to get the right groups first
162b21740b4SAndreas Gohr            $groups = $this->getGroups($match['grps'], $filtermethod);
163e7339d5aSAndreas Gohr            $groupDNs = array_keys($groups);
164e7339d5aSAndreas Gohr
165e7339d5aSAndreas Gohr            if ($this->config['recursivegroups']) {
166e7339d5aSAndreas Gohr                $gch = $this->getGroupHierarchyCache();
167e7339d5aSAndreas Gohr                foreach ($groupDNs as $dn) {
168e7339d5aSAndreas Gohr                    $groupDNs = array_merge($groupDNs, $gch->getChildren($dn));
169e7339d5aSAndreas Gohr                }
170e7339d5aSAndreas Gohr                $groupDNs = array_unique($groupDNs);
171e7339d5aSAndreas Gohr            }
172e7339d5aSAndreas Gohr
173b21740b4SAndreas Gohr            $or = Filters::or();
174e7339d5aSAndreas Gohr            foreach ($groupDNs as $dn) {
175204fba68SAndreas Gohr                // domain users membership is in primary group
176e7339d5aSAndreas Gohr                if ($this->dn2group($dn) === $this->config['primarygroup']) {
177204fba68SAndreas Gohr                    $or->add(Filters::equal('primaryGroupID', 513));
178204fba68SAndreas Gohr                    continue;
179204fba68SAndreas Gohr                }
1807a36c1b4SAndreas Gohr                // find members of this exact group
1817a36c1b4SAndreas Gohr                $or->add(Filters::equal('memberOf', $dn));
182b21740b4SAndreas Gohr            }
183b21740b4SAndreas Gohr            $filter->add($or);
184b21740b4SAndreas Gohr        }
185e7339d5aSAndreas Gohr
186b21740b4SAndreas Gohr        $this->debug('Searching ' . $filter->toString(), __FILE__, __LINE__);
1879c590892SAndreas Gohr        $attributes = $this->userAttributes();
1889c590892SAndreas Gohr        $search = Operations::search($filter, ...$attributes);
189b21740b4SAndreas Gohr        $paging = $this->ldap->paging($search);
190b21740b4SAndreas Gohr
191b21740b4SAndreas Gohr        $users = [];
192b21740b4SAndreas Gohr        while ($paging->hasEntries()) {
193b21740b4SAndreas Gohr            try {
194b21740b4SAndreas Gohr                $entries = $paging->getEntries();
195*08ace392SAndreas Gohr            } catch (OperationException $e) {
196b21740b4SAndreas Gohr                $this->fatal($e);
19780ac552fSAndreas Gohr                break; // we abort and return what we have so far
198b21740b4SAndreas Gohr            }
199b21740b4SAndreas Gohr
200b21740b4SAndreas Gohr            foreach ($entries as $entry) {
201*08ace392SAndreas Gohr                $userinfo = $this->entry2User($entry);
202746af42cSAndreas Gohr                $users[$userinfo['user']] = $userinfo;
203b21740b4SAndreas Gohr            }
204b21740b4SAndreas Gohr        }
205b21740b4SAndreas Gohr
2061b0eb9b3SAndreas Gohr        ksort($users);
207b21740b4SAndreas Gohr        return $users;
208b21740b4SAndreas Gohr    }
209b21740b4SAndreas Gohr
210a1128cc0SAndreas Gohr    /** @inheritDoc */
211a1128cc0SAndreas Gohr    public function cleanUser($user)
21280ac552fSAndreas Gohr    {
213a1128cc0SAndreas Gohr        return $this->simpleUser($user);
21480ac552fSAndreas Gohr    }
21580ac552fSAndreas Gohr
216a1128cc0SAndreas Gohr    /** @inheritDoc */
217a1128cc0SAndreas Gohr    public function cleanGroup($group)
218a1128cc0SAndreas Gohr    {
219a1128cc0SAndreas Gohr        return PhpString::strtolower($group);
220a1128cc0SAndreas Gohr    }
221a1128cc0SAndreas Gohr
222a1128cc0SAndreas Gohr    /** @inheritDoc */
223a1128cc0SAndreas Gohr    public function prepareBindUser($user)
224a1128cc0SAndreas Gohr    {
225*08ace392SAndreas Gohr        // add account suffix
226*08ace392SAndreas Gohr        return $this->qualifiedUser($user);
22780ac552fSAndreas Gohr    }
22880ac552fSAndreas Gohr
22980ac552fSAndreas Gohr    /**
230e7339d5aSAndreas Gohr     * Initializes the Group Cache for nested groups
231e7339d5aSAndreas Gohr     *
232e7339d5aSAndreas Gohr     * @return GroupHierarchyCache
233e7339d5aSAndreas Gohr     */
234e7339d5aSAndreas Gohr    public function getGroupHierarchyCache()
235e7339d5aSAndreas Gohr    {
236e7339d5aSAndreas Gohr        if ($this->gch === null) {
237e7339d5aSAndreas Gohr            if (!$this->autoAuth()) return null;
2385dcabedaSAndreas Gohr            $this->gch = new GroupHierarchyCache($this->ldap, $this->config['usefscache']);
239e7339d5aSAndreas Gohr        }
240e7339d5aSAndreas Gohr        return $this->gch;
241e7339d5aSAndreas Gohr    }
242e7339d5aSAndreas Gohr
243e7339d5aSAndreas Gohr    /**
244a1128cc0SAndreas Gohr     * userPrincipalName in the form <user>@<suffix>
245*08ace392SAndreas Gohr     *
246*08ace392SAndreas Gohr     * @param string $user
247*08ace392SAndreas Gohr     * @return string
24880ac552fSAndreas Gohr     */
249a1128cc0SAndreas Gohr    protected function qualifiedUser($user)
250a1128cc0SAndreas Gohr    {
251a1128cc0SAndreas Gohr        $user = $this->simpleUser($user); // strip any existing qualifiers
252a1128cc0SAndreas Gohr        if (!$this->config['suffix']) {
253a1128cc0SAndreas Gohr            $this->error('No account suffix set. Logins may fail.', __FILE__, __LINE__);
254a1128cc0SAndreas Gohr        }
255a1128cc0SAndreas Gohr
256a1128cc0SAndreas Gohr        return $user . '@' . $this->config['suffix'];
257a1128cc0SAndreas Gohr    }
258a1128cc0SAndreas Gohr
259a1128cc0SAndreas Gohr    /**
260a1128cc0SAndreas Gohr     * Removes the account suffix from the given user. Should match the SAMAccountName
261*08ace392SAndreas Gohr     *
262*08ace392SAndreas Gohr     * @param string $user
263*08ace392SAndreas Gohr     * @return string
264a1128cc0SAndreas Gohr     */
265a1128cc0SAndreas Gohr    protected function simpleUser($user)
26680ac552fSAndreas Gohr    {
26780ac552fSAndreas Gohr        $user = PhpString::strtolower($user);
268a1128cc0SAndreas Gohr        $user = preg_replace('/@.*$/', '', $user);
269a1128cc0SAndreas Gohr        $user = preg_replace('/^.*\\\\/', '', $user);
27080ac552fSAndreas Gohr        return $user;
27180ac552fSAndreas Gohr    }
27280ac552fSAndreas Gohr
27380ac552fSAndreas Gohr    /**
274b21740b4SAndreas Gohr     * Transform an LDAP entry to a user info array
275b21740b4SAndreas Gohr     *
276b21740b4SAndreas Gohr     * @param Entry $entry
277b21740b4SAndreas Gohr     * @return array
278b21740b4SAndreas Gohr     */
279b21740b4SAndreas Gohr    protected function entry2User(Entry $entry)
280b21740b4SAndreas Gohr    {
281b914569fSAndreas Gohr        $user = [
282a1128cc0SAndreas Gohr            'user' => $this->simpleUser($this->attr2str($entry->get('sAMAccountName'))),
2831078ec26SAndreas Gohr            'name' => $this->attr2str($entry->get('DisplayName')) ?: $this->attr2str($entry->get('Name')),
2841078ec26SAndreas Gohr            'mail' => $this->attr2str($entry->get('mail')),
2851078ec26SAndreas Gohr            'dn' => $entry->getDn()->toString(),
2861078ec26SAndreas Gohr            'grps' => $this->getUserGroups($entry), // we always return groups because its currently inexpensive
2871078ec26SAndreas Gohr        ];
288b914569fSAndreas Gohr
289b914569fSAndreas Gohr        // get additional attributes
290b914569fSAndreas Gohr        foreach ($this->config['attributes'] as $attr) {
291b914569fSAndreas Gohr            $user[$attr] = $this->attr2str($entry->get($attr));
292b914569fSAndreas Gohr        }
293b914569fSAndreas Gohr
294b914569fSAndreas Gohr        return $user;
2951078ec26SAndreas Gohr    }
2961078ec26SAndreas Gohr
2971078ec26SAndreas Gohr    /**
2981078ec26SAndreas Gohr     * Get the list of groups the given user is member of
2991078ec26SAndreas Gohr     *
3001078ec26SAndreas Gohr     * This method currently does no LDAP queries and thus is inexpensive.
3011078ec26SAndreas Gohr     *
3021078ec26SAndreas Gohr     * @param Entry $userentry
3031078ec26SAndreas Gohr     * @return array
3041078ec26SAndreas Gohr     */
3051078ec26SAndreas Gohr    protected function getUserGroups(Entry $userentry)
3061078ec26SAndreas Gohr    {
307e7339d5aSAndreas Gohr        $groups = [];
308e7339d5aSAndreas Gohr
309e7339d5aSAndreas Gohr        if ($userentry->has('memberOf')) {
310e7339d5aSAndreas Gohr            $groupDNs = $userentry->get('memberOf')->getValues();
311e7339d5aSAndreas Gohr
312e7339d5aSAndreas Gohr            if ($this->config['recursivegroups']) {
313e7339d5aSAndreas Gohr                $gch = $this->getGroupHierarchyCache();
314e7339d5aSAndreas Gohr                foreach ($groupDNs as $dn) {
315e7339d5aSAndreas Gohr                    $groupDNs = array_merge($groupDNs, $gch->getParents($dn));
316e7339d5aSAndreas Gohr                }
317e7339d5aSAndreas Gohr
318e7339d5aSAndreas Gohr                $groupDNs = array_unique($groupDNs);
319e7339d5aSAndreas Gohr            }
320e7339d5aSAndreas Gohr            $groups = array_map([$this, 'dn2group'], $groupDNs);
321e7339d5aSAndreas Gohr        }
322e7339d5aSAndreas Gohr
323e7339d5aSAndreas Gohr        $groups[] = $this->config['defaultgroup']; // always add default
3241078ec26SAndreas Gohr
3251078ec26SAndreas Gohr        // resolving the primary group in AD is complicated but basically never needed
3261078ec26SAndreas Gohr        // http://support.microsoft.com/?kbid=321360
3271078ec26SAndreas Gohr        $gid = $userentry->get('primaryGroupID')->firstValue();
3281078ec26SAndreas Gohr        if ($gid == 513) {
329e7339d5aSAndreas Gohr            $groups[] = $this->cleanGroup($this->config['primarygroup']);
33051e92298SAndreas Gohr        }
33151e92298SAndreas Gohr
332f17bb68bSAndreas Gohr        sort($groups);
333f17bb68bSAndreas Gohr        return $groups;
33451e92298SAndreas Gohr    }
33551e92298SAndreas Gohr
3369c590892SAndreas Gohr    /** @inheritDoc */
3379c590892SAndreas Gohr    protected function userAttributes()
3389c590892SAndreas Gohr    {
3399c590892SAndreas Gohr        $attr = parent::userAttributes();
340a1128cc0SAndreas Gohr        $attr[] = new Attribute('sAMAccountName');
3419c590892SAndreas Gohr        $attr[] = new Attribute('Name');
3429c590892SAndreas Gohr        $attr[] = new Attribute('primaryGroupID');
3439c590892SAndreas Gohr        $attr[] = new Attribute('memberOf');
3449c590892SAndreas Gohr
3459c590892SAndreas Gohr        return $attr;
3469c590892SAndreas Gohr    }
347e7339d5aSAndreas Gohr
348e7339d5aSAndreas Gohr    /**
349e7339d5aSAndreas Gohr     * Extract the group name from the DN
350e7339d5aSAndreas Gohr     *
351e7339d5aSAndreas Gohr     * @param string $dn
352e7339d5aSAndreas Gohr     * @return string
353e7339d5aSAndreas Gohr     */
354e7339d5aSAndreas Gohr    protected function dn2group($dn)
355e7339d5aSAndreas Gohr    {
356e7339d5aSAndreas Gohr        list($cn) = explode(',', $dn, 2);
357e7339d5aSAndreas Gohr        return $this->cleanGroup(substr($cn, 3));
358e7339d5aSAndreas Gohr    }
359*08ace392SAndreas Gohr
360*08ace392SAndreas Gohr    /**
361*08ace392SAndreas Gohr     * Encode a password for transmission over LDAP
362*08ace392SAndreas Gohr     *
363*08ace392SAndreas Gohr     * Passwords are encoded as UTF-16LE strings encapsulated in quotes.
364*08ace392SAndreas Gohr     *
365*08ace392SAndreas Gohr     * @param string $password The password to encode
366*08ace392SAndreas Gohr     * @return string
367*08ace392SAndreas Gohr     */
368*08ace392SAndreas Gohr    protected function encodePassword($password)
369*08ace392SAndreas Gohr    {
370*08ace392SAndreas Gohr        $password = "\"" . $password . "\"";
371*08ace392SAndreas Gohr
372*08ace392SAndreas Gohr        if (function_exists('iconv')) {
373*08ace392SAndreas Gohr            $adpassword = iconv('UTF-8', 'UTF-16LE', $password);
374*08ace392SAndreas Gohr        } elseif (function_exists('mb_convert_encoding')) {
375*08ace392SAndreas Gohr            $adpassword = mb_convert_encoding($password, "UTF-16LE", "UTF-8");
376*08ace392SAndreas Gohr        } else {
377*08ace392SAndreas Gohr            // this will only work for ASCII7 passwords
378*08ace392SAndreas Gohr            $adpassword = '';
379*08ace392SAndreas Gohr            for ($i = 0; $i < strlen($password); $i++) {
380*08ace392SAndreas Gohr                $adpassword .= "$password[$i]\000";
381*08ace392SAndreas Gohr            }
382*08ace392SAndreas Gohr        }
383*08ace392SAndreas Gohr        return $adpassword;
384*08ace392SAndreas Gohr    }
3851078ec26SAndreas Gohr}
386