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