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
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);
46 if (LDAP_AUTH_PASSWORD)
47 // Windows Active Directory Server is strict
48 $r = ldap_bind($this->_ldap, LDAP_AUTH_USER, LDAP_AUTH_PASSWORD);
50 $r = ldap_bind($this->_ldap, LDAP_AUTH_USER);
52 $r = true; // anonymous bind allowed
55 trigger_error(sprintf(_("Unable to bind LDAP server %s using %s %s"),
56 LDAP_AUTH_HOST, LDAP_AUTH_USER, LDAP_AUTH_PASSWORD),
67 * free and close the bound ressources
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);
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.
83 * @see http://www.faqs.org/rfcs/rfc4514.html LDAP String Representation of Distinguished Names
85 function _stringEscape($name) {
86 $name = strtr(utf8_encode($name),
98 * LDAP names may contain every utf-8 character. However we restrict them a bit for convenience.
99 * @see _stringEscape()
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;
108 * Construct the configured search filter and properly escape the userid.
109 * Apply LDAP_SEARCH_FIELD and optionally LDAP_SEARCH_FILTER.
111 * @param string $userid username, unquoted in the current charset.
113 * @return string The 3rd argument to ldap_search()
114 * @see http://www.faqs.org/rfcs/rfc4514.html LDAP String Representation of Distinguished Names
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);
122 $st_search = LDAP_SEARCH_FIELD
123 ? LDAP_SEARCH_FIELD."=$euserid"
130 * Passwords must not be escaped, but sent as "stringprep"'ed utf-8.
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
135 function checkPass($submitted_password) {
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);
143 return $this->_tryNextPass($submitted_password);
145 if (!$this->_checkPassLength($submitted_password)) {
147 return WIKIAUTH_FORBIDDEN;
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);
154 return $this->_tryNextPass($submitted_password);
155 //return WIKIAUTH_FORBIDDEN;
157 /*if (strstr($userid,'*')) { // should be safely escaped now
158 trigger_error(fmt("Invalid username '%s' for LDAP Auth", $userid),
160 return WIKIAUTH_FORBIDDEN;
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);
168 return $this->_tryNextPass($submitted_password);
170 $info = ldap_get_entries($ldap, $this->_sr);
171 if (empty($info["count"])) {
173 trigger_error(_("User not found in LDAP"), E_USER_WARNING);
175 return $this->_tryNextPass($submitted_password);
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];
193 $this->_level = WIKIAUTH_USER;
194 return $this->_level;
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];
202 $this->_level = WIKIAUTH_USER;
203 return $this->_level;
208 trigger_error(_("Wrong password: ") .
209 str_repeat("*", strlen($submitted_password)),
214 trigger_error(fmt("Could not connect to LDAP host %s", LDAP_AUTH_HOST), E_USER_WARNING);
217 return $this->_tryNextPass($submitted_password);
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),
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)) {
234 return $this->_tryNextUser();
236 $info = ldap_get_entries($ldap, $this->_sr);
238 if ($info["count"] > 0) {
240 UpgradeUser($GLOBALS['ForbiddenUser'], $this);
245 return $this->_tryNextUser();
248 function mayChangePass() {
258 // c-hanging-comment-ender-p: nil
259 // indent-tabs-mode: nil