]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/WikiUser/LDAP.php
Use HTML 5 DOCTYPE; force CHARSET=utf-8
[SourceForge/phpwiki.git] / lib / WikiUser / LDAP.php
1 <?php
2
3 /*
4  * Copyright (C) 2004,2007 $ThePhpWikiProgrammingTeam
5  *
6  * This file is part of PhpWiki.
7  *
8  * PhpWiki is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * PhpWiki is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License along
19  * with PhpWiki; if not, write to the Free Software Foundation, Inc.,
20  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21  */
22
23 class _LDAPPassUser
24     extends _PassUser
25     /**
26      * Define the vars LDAP_AUTH_HOST and LDAP_BASE_DN in config/config.ini
27      *
28      * Preferences are handled in _PassUser
29      */
30 {
31     /**
32      * ->_init()
33      * connect and bind to the LDAP host
34      */
35     function _init()
36     {
37         if ($this->_ldap = ldap_connect(LDAP_AUTH_HOST)) { // must be a valid LDAP server!
38             global $LDAP_SET_OPTION;
39             if (!empty($LDAP_SET_OPTION)) {
40                 foreach ($LDAP_SET_OPTION as $key => $value) {
41                     //if (is_string($key) and defined($key))
42                     //    $key = constant($key);
43                     ldap_set_option($this->_ldap, $key, $value);
44                 }
45             }
46             if (LDAP_AUTH_USER)
47                 if (LDAP_AUTH_PASSWORD)
48                     // Windows Active Directory Server is strict
49                     $r = ldap_bind($this->_ldap, LDAP_AUTH_USER, LDAP_AUTH_PASSWORD);
50                 else
51                     $r = ldap_bind($this->_ldap, LDAP_AUTH_USER);
52             else
53                 $r = true; // anonymous bind allowed
54             if (!$r) {
55                 $this->_free();
56                 trigger_error(sprintf(_("Unable to bind LDAP server %s using %s %s"),
57                         LDAP_AUTH_HOST, LDAP_AUTH_USER, LDAP_AUTH_PASSWORD),
58                     E_USER_WARNING);
59                 return false;
60             }
61             return $this->_ldap;
62         } else {
63             return false;
64         }
65     }
66
67     /**
68      * free and close the bound ressources
69      */
70     function _free()
71     {
72         if (isset($this->_sr)   and is_resource($this->_sr)) ldap_free_result($this->_sr);
73         if (isset($this->_ldap) and is_resource($this->_ldap)) ldap_close($this->_ldap);
74         unset($this->_sr);
75         unset($this->_ldap);
76     }
77
78     /**
79      * LDAP names allow all chars but "*", "(", ")", "\", "NUL".
80      * " should be quoted as \"
81      * Quoting is done by \xx (two-digit hexcode). "*" <=> "\2a"
82      * Non-ascii chars must be converted to utf-8.
83      * Password should NOT be escaped, just converted to utf-8.
84      *
85      * @see http://www.faqs.org/rfcs/rfc4514.html LDAP String Representation of Distinguished Names
86      */
87     function _stringEscape($name)
88     {
89         $name = strtr(utf8_encode($name),
90             array("*" => "\\2a",
91                 "?" => "\\3f",
92                 "(" => "\\28",
93                 ")" => "\\29",
94                 "\\" => "\\5c",
95                 '"' => '\"',
96                 "\0" => "\\00"));
97         return $name;
98     }
99
100     /**
101      * LDAP names may contain every utf-8 character. However we restrict them a bit for convenience.
102      * @see _stringEscape()
103      */
104     function isValidName($userid = false)
105     {
106         if (!$userid) $userid = $this->_userid;
107         // We are more restrictive here, but must allow explitly utf-8
108         return preg_match("/^[\-\w_\.@ ]+$/u", $userid) and strlen($userid) < 64;
109     }
110
111     /**
112      * Construct the configured search filter and properly escape the userid.
113      * Apply LDAP_SEARCH_FIELD and optionally LDAP_SEARCH_FILTER.
114      *
115      * @param string $userid username, unquoted in the current charset.
116      * @access private
117      * @return string The 3rd argument to ldap_search()
118      * @see http://www.faqs.org/rfcs/rfc4514.html LDAP String Representation of Distinguished Names
119      */
120     function _searchparam($userid)
121     {
122         $euserid = $this->_stringEscape($userid);
123         // Need to set the right root search information. See config/config.ini
124         if (LDAP_SEARCH_FILTER) {
125             $st_search = str_replace("\$userid", $euserid, LDAP_SEARCH_FILTER);
126         } else {
127             $st_search = LDAP_SEARCH_FIELD
128                 ? LDAP_SEARCH_FIELD . "=$euserid"
129                 : "uid=$euserid";
130         }
131         return $st_search;
132     }
133
134     /**
135      * Passwords must not be escaped, but sent as "stringprep"'ed utf-8.
136      *
137      * @see http://www.faqs.org/rfcs/rfc4514.html LDAP String Representation of Distinguished Names
138      * @see http://www.faqs.org/rfcs/rfc3454.html stringprep
139      */
140     function checkPass($submitted_password)
141     {
142
143         $this->_authmethod = 'LDAP';
144         $this->_userid = trim($this->_userid);
145         $userid = $this->_userid;
146         if (!$this->isValidName()) {
147             trigger_error(_("Invalid username."), E_USER_WARNING);
148             $this->_free();
149             return $this->_tryNextPass($submitted_password);
150         }
151         if (!$this->_checkPassLength($submitted_password)) {
152             $this->_free();
153             return WIKIAUTH_FORBIDDEN;
154         }
155         // A LDAP speciality: Empty passwords are always true for ldap_bind !!!
156         // So we have to disallow this regardless of PASSWORD_LENGTH_MINIMUM = 0
157         if (strlen($submitted_password) == 0) {
158             trigger_error(_("Empty password not allowed for LDAP"), E_USER_WARNING);
159             $this->_free();
160             return $this->_tryNextPass($submitted_password);
161             //return WIKIAUTH_FORBIDDEN;
162         }
163         /*if (strstr($userid,'*')) { // should be safely escaped now
164             trigger_error(fmt("Invalid username ā€œ%sā€ for LDAP Auth", $userid),
165                           E_USER_WARNING);
166             return WIKIAUTH_FORBIDDEN;
167         }*/
168
169         if ($ldap = $this->_init()) {
170             $st_search = $this->_searchparam($userid);
171             if (!$this->_sr = ldap_search($ldap, LDAP_BASE_DN, $st_search)) {
172                 trigger_error(_("Could not search in LDAP"), E_USER_WARNING);
173                 $this->_free();
174                 return $this->_tryNextPass($submitted_password);
175             }
176             $info = ldap_get_entries($ldap, $this->_sr);
177             if (empty($info["count"])) {
178                 if (DEBUG)
179                     trigger_error(_("User not found in LDAP"), E_USER_WARNING);
180                 $this->_free();
181                 return $this->_tryNextPass($submitted_password);
182             }
183             // There may be more hits with this userid.
184             // Of course it would be better to narrow down the BASE_DN
185             for ($i = 0; $i < $info["count"]; $i++) {
186                 $dn = $info[$i]["dn"];
187                 // The password must be converted to utf-8, but unescaped.
188                 // On wrong password the ldap server will return:
189                 // "Unable to bind to server: Server is unwilling to perform"
190                 // The @ catches this error message.
191                 if ($r = @ldap_bind($ldap, $dn, $submitted_password)) {
192                     // ldap_bind will return TRUE if everything matches
193                     // Optionally get the mail from LDAP
194                     if (!empty($info[$i]["mail"][0])) {
195                         $this->_prefs->_prefs['email']->default_value = $info[$i]["mail"][0];
196                     }
197                     $this->_free();
198                     $this->_level = WIKIAUTH_USER;
199                     return $this->_level;
200                 } else {
201                     // Try again, this time explicitly
202                     if ($r = @ldap_bind($ldap, $dn, utf8_encode($submitted_password))) {
203                         if (!empty($info[$i]["mail"][0])) {
204                             $this->_prefs->_prefs['email']->default_value = $info[$i]["mail"][0];
205                         }
206                         $this->_free();
207                         $this->_level = WIKIAUTH_USER;
208                         return $this->_level;
209                     }
210                 }
211             }
212             if (DEBUG)
213                 trigger_error(_("Wrong password: ") .
214                         str_repeat("*", strlen($submitted_password)),
215                     E_USER_WARNING);
216             $this->_free();
217         } else {
218             $this->_free();
219             trigger_error(fmt("Could not connect to LDAP host %s", LDAP_AUTH_HOST), E_USER_WARNING);
220         }
221
222         return $this->_tryNextPass($submitted_password);
223     }
224
225
226     function userExists()
227     {
228         $this->_userid = trim($this->_userid);
229         $userid = $this->_userid;
230         if (strstr($userid, '*')) {
231             trigger_error(fmt("Invalid username ā€œ%sā€ for LDAP Auth", $userid),
232                 E_USER_WARNING);
233             return false;
234         }
235         if ($ldap = $this->_init()) {
236             // Need to set the right root search information. see ../index.php
237             $st_search = $this->_searchparam($userid);
238             if (!$this->_sr = ldap_search($ldap, LDAP_BASE_DN, $st_search)) {
239                 $this->_free();
240                 return $this->_tryNextUser();
241             }
242             $info = ldap_get_entries($ldap, $this->_sr);
243
244             if ($info["count"] > 0) {
245                 $this->_free();
246                 UpgradeUser($GLOBALS['ForbiddenUser'], $this);
247                 return true;
248             }
249         }
250         $this->_free();
251         return $this->_tryNextUser();
252     }
253
254     function mayChangePass()
255     {
256         return false;
257     }
258
259 }
260
261 // Local Variables:
262 // mode: php
263 // tab-width: 8
264 // c-basic-offset: 4
265 // c-hanging-comment-ender-p: nil
266 // indent-tabs-mode: nil
267 // End: