]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/WikiUser/LDAP.php
Activated Id substitution for Subversion
[SourceForge/phpwiki.git] / lib / WikiUser / LDAP.php
1 <?php //-*-php-*-
2 rcs_id('$Id$');
3 /* Copyright (C) 2004,2007 $ThePhpWikiProgrammingTeam
4  * This file is part of PhpWiki. Terms and Conditions see LICENSE. (GPL2)
5  */
6
7 class _LDAPPassUser
8 extends _PassUser
9 /**
10  * Define the vars LDAP_AUTH_HOST and LDAP_BASE_DN in config/config.ini
11  *
12  * Preferences are handled in _PassUser
13  */
14 {
15     /**
16      * ->_init()
17      * connect and bind to the LDAP host
18      */
19     function _init() {
20         if ($this->_ldap = ldap_connect(LDAP_AUTH_HOST)) { // must be a valid LDAP server!
21             global $LDAP_SET_OPTION;
22             if (!empty($LDAP_SET_OPTION)) {
23                 foreach ($LDAP_SET_OPTION as $key => $value) {
24                     //if (is_string($key) and defined($key))
25                     //    $key = constant($key);
26                     ldap_set_option($this->_ldap, $key, $value);
27                 }
28             }
29             if (LDAP_AUTH_USER)
30                 if (LDAP_AUTH_PASSWORD)
31                     // Windows Active Directory Server is strict
32                     $r = ldap_bind($this->_ldap, LDAP_AUTH_USER, LDAP_AUTH_PASSWORD); 
33                 else
34                     $r = ldap_bind($this->_ldap, LDAP_AUTH_USER); 
35             else
36                 $r = true; // anonymous bind allowed
37             if (!$r) {
38                 $this->_free();
39                 trigger_error(sprintf(_("Unable to bind LDAP server %s using %s %s"),
40                                       LDAP_AUTH_HOST, LDAP_AUTH_USER, LDAP_AUTH_PASSWORD), 
41                               E_USER_WARNING);
42                 return false;
43             }
44             return $this->_ldap;
45         } else {
46             return false;
47         }
48     }
49
50     /**    
51      * free and close the bound ressources
52      */
53     function _free() {
54         if (isset($this->_sr)   and is_resource($this->_sr))   ldap_free_result($this->_sr);
55         if (isset($this->_ldap) and is_resource($this->_ldap)) ldap_close($this->_ldap);
56         unset($this->_sr);
57         unset($this->_ldap);
58     }
59
60     /**
61      * LDAP names allow all chars but "*", "(", ")", "\", "NUL". 
62      * " should be quoted as \"
63      * Quoting is done by \xx (two-digit hexcode). "*" <=> "\2a"
64      * Non-ascii chars must be converted to utf-8.
65      * Password should NOT be escaped, just converted to utf-8.
66      *
67      * @see http://www.faqs.org/rfcs/rfc4514.html LDAP String Representation of Distinguished Names
68      */
69     function _stringEscape($name) {
70         $name = strtr(utf8_encode($name), 
71                       array("*" => "\\2a",
72                             "?" => "\\3f",
73                             "(" => "\\28",
74                             ")" => "\\29",
75                             "\\" => "\\5c",
76                             '"'  => '\"',
77                             "\0" => "\\00"));
78         return $name;
79     }
80
81     /**
82      * LDAP names may contain every utf-8 character. However we restrict them a bit for convenience.
83      * @see _stringEscape()
84      */
85     function isValidName ($userid = false) {
86         if (!$userid) $userid = $this->_userid;
87         // We are more restrictive here, but must allow explitly utf-8
88         return preg_match("/^[\-\w_\.@ ]+$/u", $userid) and strlen($userid) < 64;
89     }
90
91     /**
92      * Construct the configured search filter and properly escape the userid.
93      * Apply LDAP_SEARCH_FIELD and optionally LDAP_SEARCH_FILTER.
94      *
95      * @param string $userid username, unquoted in the current charset.
96      * @access private
97      * @return string The 3rd argument to ldap_search()
98      * @see http://www.faqs.org/rfcs/rfc4514.html LDAP String Representation of Distinguished Names
99      */
100     function _searchparam($userid) {
101         $euserid = $this->_stringEscape($userid);
102         // Need to set the right root search information. See config/config.ini
103         if (LDAP_SEARCH_FILTER) {
104             $st_search = str_replace("\$userid", $euserid, LDAP_SEARCH_FILTER);
105         } else {
106             $st_search = LDAP_SEARCH_FIELD
107                 ? LDAP_SEARCH_FIELD."=$euserid"
108                 : "uid=$euserid";
109         }
110         return $st_search;
111     }
112
113     /**
114      * Passwords must not be escaped, but sent as "stringprep"'ed utf-8. 
115      *
116      * @see http://www.faqs.org/rfcs/rfc4514.html LDAP String Representation of Distinguished Names
117      * @see http://www.faqs.org/rfcs/rfc3454.html stringprep
118      */
119     function checkPass($submitted_password) {
120
121         $this->_authmethod = 'LDAP';
122         $userid = $this->_userid;
123         if (!$this->isValidName()) {
124             trigger_error(_("Invalid username."), E_USER_WARNING);
125             $this->_free();
126             return $this->_tryNextPass($submitted_password);
127         }
128         if (!$this->_checkPassLength($submitted_password)) {
129             $this->_free();
130             return WIKIAUTH_FORBIDDEN;
131         }
132         // A LDAP speciality: Empty passwords are always true for ldap_bind !!! 
133         // So we have to disallow this regardless of PASSWORD_LENGTH_MINIMUM = 0
134         if (strlen($submitted_password) == 0) {
135             trigger_error(_("Empty password not allowed for LDAP"), E_USER_WARNING);
136             $this->_free();
137             return $this->_tryNextPass($submitted_password);
138             //return WIKIAUTH_FORBIDDEN;
139         }
140         /*if (strstr($userid,'*')) { // should be safely escaped now
141             trigger_error(fmt("Invalid username '%s' for LDAP Auth", $userid), 
142                           E_USER_WARNING);
143             return WIKIAUTH_FORBIDDEN;
144         }*/
145
146         if ($ldap = $this->_init()) {
147             $st_search = $this->_searchparam($userid);
148             if (!$this->_sr = ldap_search($ldap, LDAP_BASE_DN, $st_search)) {
149                 trigger_error(_("Could not search in LDAP"), E_USER_WARNING);
150                 $this->_free();
151                 return $this->_tryNextPass($submitted_password);
152             }
153             $info = ldap_get_entries($ldap, $this->_sr); 
154             if (empty($info["count"])) {
155                 if (DEBUG)
156                     trigger_error(_("User not found in LDAP"), E_USER_WARNING);
157                 $this->_free();
158                 return $this->_tryNextPass($submitted_password);
159             }
160             // There may be more hits with this userid.
161             // Of course it would be better to narrow down the BASE_DN
162             for ($i = 0; $i < $info["count"]; $i++) {
163                 $dn = $info[$i]["dn"];
164                 // The password must be converted to utf-8, but unescaped.
165                 // On wrong password the ldap server will return: 
166                 // "Unable to bind to server: Server is unwilling to perform"
167                 // The @ catches this error message.
168                 // If CHARSET=utf-8 the form should have already converted it to utf-8.
169                 if ($r = @ldap_bind($ldap, $dn, $submitted_password)) {
170                     // ldap_bind will return TRUE if everything matches
171                     // Optionally get the mail from LDAP
172                     if (!empty($info[$i]["mail"][0])) {
173                         $this->_prefs->_prefs['email']->default_value = $info[$i]["mail"][0];
174                     }
175                     $this->_free();
176                     $this->_level = WIKIAUTH_USER;
177                     return $this->_level;
178                 } else {
179                     // Try again, this time explicitly
180                     if ($r = @ldap_bind($ldap, $dn, utf8_encode($submitted_password))) {
181                         if (!empty($info[$i]["mail"][0])) {
182                             $this->_prefs->_prefs['email']->default_value = $info[$i]["mail"][0];
183                         }
184                         $this->_free();
185                         $this->_level = WIKIAUTH_USER;
186                         return $this->_level;
187                     }
188                 }
189             }
190             if (DEBUG)
191                 trigger_error(_("Wrong password: ") . 
192                               str_repeat("*", strlen($submitted_password)), 
193                               E_USER_WARNING);
194             $this->_free();
195         } else {
196             $this->_free();
197             trigger_error(fmt("Could not connect to LDAP host %s", LDAP_AUTH_HOST), E_USER_WARNING);
198         }
199
200         return $this->_tryNextPass($submitted_password);
201     }
202
203
204     function userExists() {
205         $userid = $this->_userid;
206         /*if (strstr($userid, '*')) {
207             trigger_error(fmt("Invalid username '%s' for LDAP Auth", $userid),
208                           E_USER_WARNING);
209             return false;
210         }*/
211         if ($ldap = $this->_init()) {
212             // Need to set the right root search information. see ../index.php
213             $st_search = $this->_searchparam($userid);
214             if (!$this->_sr = ldap_search($ldap, LDAP_BASE_DN, $st_search)) {
215                 $this->_free();
216                 return $this->_tryNextUser();
217             }
218             $info = ldap_get_entries($ldap, $this->_sr); 
219
220             if ($info["count"] > 0) {
221                 $this->_free();
222                 UpgradeUser($GLOBALS['ForbiddenUser'], $this);
223                 return true;
224             }
225         }
226         $this->_free();
227         return $this->_tryNextUser();
228     }
229
230     function mayChangePass() {
231         return false;
232     }
233
234 }
235
236 // $Log: not supported by cvs2svn $
237 // Revision 1.9  2007/06/13 12:48:14  rurban
238 // fix wrong fix from 1.3.13p1
239 //
240 // Revision 1.8  2007/06/07 16:31:33  rurban
241 // Important! Fixes bug #1732882 ldap_bind with empty password
242 // Adds diagnostics on other ldap failures
243 // Fix password quoting
244 //
245 // Revision 1.7  2007/05/30 21:56:17  rurban
246 // Back to default uid for LDAP
247 //
248 // Revision 1.6  2007/05/29 16:56:15  rurban
249 // Allow more password und userid chars. uid => cn: default for certain testusers
250 //
251 // Revision 1.5  2005/10/10 19:43:49  rurban
252 // add DBAUTH_PREF_INSERT: self-creating users. by John Stevens
253 //
254 // Revision 1.4  2004/12/26 17:11:17  rurban
255 // just copyright
256 //
257 // Revision 1.3  2004/12/20 16:05:01  rurban
258 // gettext msg unification
259 //
260 // Revision 1.2  2004/12/19 00:58:02  rurban
261 // Enforce PASSWORD_LENGTH_MINIMUM in almost all PassUser checks,
262 // Provide an errormessage if so. Just PersonalPage and BogoLogin not.
263 // Simplify httpauth logout handling and set sessions for all methods.
264 // fix main.php unknown index "x" getLevelDescription() warning.
265 //
266 // Revision 1.1  2004/11/01 10:43:58  rurban
267 // seperate PassUser methods into seperate dir (memory usage)
268 // fix WikiUser (old) overlarge data session
269 // remove wikidb arg from various page class methods, use global ->_dbi instead
270 // ...
271 //
272
273 // Local Variables:
274 // mode: php
275 // tab-width: 8
276 // c-basic-offset: 4
277 // c-hanging-comment-ender-p: nil
278 // indent-tabs-mode: nil
279 // End:
280 ?>