4 * Copyright (C) 2004,2007 $ThePhpWikiProgrammingTeam
6 * This file is part of PhpWiki.
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.
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.
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.
26 * Define the vars LDAP_AUTH_HOST and LDAP_BASE_DN in config/config.ini
28 * Preferences are handled in _PassUser
33 * connect and bind to the LDAP host
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);
47 if (LDAP_AUTH_PASSWORD)
48 // Windows Active Directory Server is strict
49 $r = ldap_bind($this->_ldap, LDAP_AUTH_USER, LDAP_AUTH_PASSWORD);
51 $r = ldap_bind($this->_ldap, LDAP_AUTH_USER);
53 $r = true; // anonymous bind allowed
56 trigger_error(sprintf(_("Unable to bind LDAP server %s using %s %s"),
57 LDAP_AUTH_HOST, LDAP_AUTH_USER, LDAP_AUTH_PASSWORD),
68 * free and close the bound ressources
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);
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.
85 * @see http://www.faqs.org/rfcs/rfc4514.html LDAP String Representation of Distinguished Names
87 function _stringEscape($name)
89 $name = strtr(utf8_encode($name),
101 * LDAP names may contain every utf-8 character. However we restrict them a bit for convenience.
102 * @see _stringEscape()
104 function isValidName($userid = false)
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;
112 * Construct the configured search filter and properly escape the userid.
113 * Apply LDAP_SEARCH_FIELD and optionally LDAP_SEARCH_FILTER.
115 * @param string $userid username, unquoted in the current charset.
117 * @return string The 3rd argument to ldap_search()
118 * @see http://www.faqs.org/rfcs/rfc4514.html LDAP String Representation of Distinguished Names
120 function _searchparam($userid)
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);
127 $st_search = LDAP_SEARCH_FIELD
128 ? LDAP_SEARCH_FIELD . "=$euserid"
135 * Passwords must not be escaped, but sent as "stringprep"'ed utf-8.
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
140 function checkPass($submitted_password)
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);
149 return $this->_tryNextPass($submitted_password);
151 if (!$this->_checkPassLength($submitted_password)) {
153 return WIKIAUTH_FORBIDDEN;
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);
160 return $this->_tryNextPass($submitted_password);
161 //return WIKIAUTH_FORBIDDEN;
163 /*if (strstr($userid,'*')) { // should be safely escaped now
164 trigger_error(fmt("Invalid username ā%sā for LDAP Auth", $userid),
166 return WIKIAUTH_FORBIDDEN;
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);
174 return $this->_tryNextPass($submitted_password);
176 $info = ldap_get_entries($ldap, $this->_sr);
177 if (empty($info["count"])) {
179 trigger_error(_("User not found in LDAP"), E_USER_WARNING);
181 return $this->_tryNextPass($submitted_password);
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];
198 $this->_level = WIKIAUTH_USER;
199 return $this->_level;
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];
207 $this->_level = WIKIAUTH_USER;
208 return $this->_level;
213 trigger_error(_("Wrong password: ") .
214 str_repeat("*", strlen($submitted_password)),
219 trigger_error(fmt("Could not connect to LDAP host %s", LDAP_AUTH_HOST), E_USER_WARNING);
222 return $this->_tryNextPass($submitted_password);
226 function userExists()
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),
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)) {
240 return $this->_tryNextUser();
242 $info = ldap_get_entries($ldap, $this->_sr);
244 if ($info["count"] > 0) {
246 UpgradeUser($GLOBALS['ForbiddenUser'], $this);
251 return $this->_tryNextUser();
254 function mayChangePass()
265 // c-hanging-comment-ender-p: nil
266 // indent-tabs-mode: nil