3 /* Copyright (C) 2004,2007 $ThePhpWikiProgrammingTeam
4 * This file is part of PhpWiki. Terms and Conditions see LICENSE. (GPL2)
10 * Define the vars LDAP_AUTH_HOST and LDAP_BASE_DN in config/config.ini
12 * Preferences are handled in _PassUser
17 * connect and bind to the LDAP host
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);
30 if (LDAP_AUTH_PASSWORD)
31 // Windows Active Directory Server is strict
32 $r = ldap_bind($this->_ldap, LDAP_AUTH_USER, LDAP_AUTH_PASSWORD);
34 $r = ldap_bind($this->_ldap, LDAP_AUTH_USER);
36 $r = true; // anonymous bind allowed
39 trigger_error(sprintf(_("Unable to bind LDAP server %s using %s %s"),
40 LDAP_AUTH_HOST, LDAP_AUTH_USER, LDAP_AUTH_PASSWORD),
51 * free and close the bound ressources
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);
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.
67 * @see http://www.faqs.org/rfcs/rfc4514.html LDAP String Representation of Distinguished Names
69 function _stringEscape($name) {
70 $name = strtr(utf8_encode($name),
82 * LDAP names may contain every utf-8 character. However we restrict them a bit for convenience.
83 * @see _stringEscape()
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;
92 * Construct the configured search filter and properly escape the userid.
93 * Apply LDAP_SEARCH_FIELD and optionally LDAP_SEARCH_FILTER.
95 * @param string $userid username, unquoted in the current charset.
97 * @return string The 3rd argument to ldap_search()
98 * @see http://www.faqs.org/rfcs/rfc4514.html LDAP String Representation of Distinguished Names
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);
106 $st_search = LDAP_SEARCH_FIELD
107 ? LDAP_SEARCH_FIELD."=$euserid"
114 * Passwords must not be escaped, but sent as "stringprep"'ed utf-8.
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
119 function checkPass($submitted_password) {
121 $this->_authmethod = 'LDAP';
122 $this->_userid = trim($this->_userid);
123 $userid = $this->_userid;
124 if (!$this->isValidName()) {
125 trigger_error(_("Invalid username."), E_USER_WARNING);
127 return $this->_tryNextPass($submitted_password);
129 if (!$this->_checkPassLength($submitted_password)) {
131 return WIKIAUTH_FORBIDDEN;
133 // A LDAP speciality: Empty passwords are always true for ldap_bind !!!
134 // So we have to disallow this regardless of PASSWORD_LENGTH_MINIMUM = 0
135 if (strlen($submitted_password) == 0) {
136 trigger_error(_("Empty password not allowed for LDAP"), E_USER_WARNING);
138 return $this->_tryNextPass($submitted_password);
139 //return WIKIAUTH_FORBIDDEN;
141 /*if (strstr($userid,'*')) { // should be safely escaped now
142 trigger_error(fmt("Invalid username '%s' for LDAP Auth", $userid),
144 return WIKIAUTH_FORBIDDEN;
147 if ($ldap = $this->_init()) {
148 $st_search = $this->_searchparam($userid);
149 if (!$this->_sr = ldap_search($ldap, LDAP_BASE_DN, $st_search)) {
150 trigger_error(_("Could not search in LDAP"), E_USER_WARNING);
152 return $this->_tryNextPass($submitted_password);
154 $info = ldap_get_entries($ldap, $this->_sr);
155 if (empty($info["count"])) {
157 trigger_error(_("User not found in LDAP"), E_USER_WARNING);
159 return $this->_tryNextPass($submitted_password);
161 // There may be more hits with this userid.
162 // Of course it would be better to narrow down the BASE_DN
163 for ($i = 0; $i < $info["count"]; $i++) {
164 $dn = $info[$i]["dn"];
165 // The password must be converted to utf-8, but unescaped.
166 // On wrong password the ldap server will return:
167 // "Unable to bind to server: Server is unwilling to perform"
168 // The @ catches this error message.
169 // If CHARSET=utf-8 the form should have already converted it to utf-8.
170 if ($r = @ldap_bind($ldap, $dn, $submitted_password)) {
171 // ldap_bind will return TRUE if everything matches
172 // Optionally get the mail from LDAP
173 if (!empty($info[$i]["mail"][0])) {
174 $this->_prefs->_prefs['email']->default_value = $info[$i]["mail"][0];
177 $this->_level = WIKIAUTH_USER;
178 return $this->_level;
180 // Try again, this time explicitly
181 if ($r = @ldap_bind($ldap, $dn, utf8_encode($submitted_password))) {
182 if (!empty($info[$i]["mail"][0])) {
183 $this->_prefs->_prefs['email']->default_value = $info[$i]["mail"][0];
186 $this->_level = WIKIAUTH_USER;
187 return $this->_level;
192 trigger_error(_("Wrong password: ") .
193 str_repeat("*", strlen($submitted_password)),
198 trigger_error(fmt("Could not connect to LDAP host %s", LDAP_AUTH_HOST), E_USER_WARNING);
201 return $this->_tryNextPass($submitted_password);
205 function userExists() {
206 $this->_userid = trim($this->_userid);
207 $userid = $this->_userid;
208 if (strstr($userid, '*')) {
209 trigger_error(fmt("Invalid username '%s' for LDAP Auth", $userid),
213 if ($ldap = $this->_init()) {
214 // Need to set the right root search information. see ../index.php
215 $st_search = $this->_searchparam($userid);
216 if (!$this->_sr = ldap_search($ldap, LDAP_BASE_DN, $st_search)) {
218 return $this->_tryNextUser();
220 $info = ldap_get_entries($ldap, $this->_sr);
222 if ($info["count"] > 0) {
224 UpgradeUser($GLOBALS['ForbiddenUser'], $this);
229 return $this->_tryNextUser();
232 function mayChangePass() {
242 // c-hanging-comment-ender-p: nil
243 // indent-tabs-mode: nil