]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/WikiUser/LDAP.php
Replace tabs by spaces; remove EOL spaces
[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         $this->_userid = trim($this->_userid);
123         $userid = $this->_userid;
124         if (!$this->isValidName()) {
125             trigger_error(_("Invalid username."), E_USER_WARNING);
126             $this->_free();
127             return $this->_tryNextPass($submitted_password);
128         }
129         if (!$this->_checkPassLength($submitted_password)) {
130             $this->_free();
131             return WIKIAUTH_FORBIDDEN;
132         }
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);
137             $this->_free();
138             return $this->_tryNextPass($submitted_password);
139             //return WIKIAUTH_FORBIDDEN;
140         }
141         /*if (strstr($userid,'*')) { // should be safely escaped now
142             trigger_error(fmt("Invalid username '%s' for LDAP Auth", $userid),
143                           E_USER_WARNING);
144             return WIKIAUTH_FORBIDDEN;
145         }*/
146
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);
151                  $this->_free();
152                 return $this->_tryNextPass($submitted_password);
153             }
154             $info = ldap_get_entries($ldap, $this->_sr);
155             if (empty($info["count"])) {
156                 if (DEBUG)
157                     trigger_error(_("User not found in LDAP"), E_USER_WARNING);
158                     $this->_free();
159                 return $this->_tryNextPass($submitted_password);
160             }
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];
175                     }
176                         $this->_free();
177                     $this->_level = WIKIAUTH_USER;
178                     return $this->_level;
179                 } else {
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];
184                         }
185                         $this->_free();
186                         $this->_level = WIKIAUTH_USER;
187                         return $this->_level;
188                     }
189                 }
190             }
191             if (DEBUG)
192                 trigger_error(_("Wrong password: ") .
193                               str_repeat("*", strlen($submitted_password)),
194                               E_USER_WARNING);
195             $this->_free();
196         } else {
197             $this->_free();
198             trigger_error(fmt("Could not connect to LDAP host %s", LDAP_AUTH_HOST), E_USER_WARNING);
199         }
200
201         return $this->_tryNextPass($submitted_password);
202     }
203
204
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),
210                           E_USER_WARNING);
211             return false;
212         }
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)) {
217                  $this->_free();
218                 return $this->_tryNextUser();
219             }
220             $info = ldap_get_entries($ldap, $this->_sr);
221
222             if ($info["count"] > 0) {
223                  $this->_free();
224                 UpgradeUser($GLOBALS['ForbiddenUser'], $this);
225                 return true;
226             }
227         }
228          $this->_free();
229         return $this->_tryNextUser();
230     }
231
232     function mayChangePass() {
233         return false;
234     }
235
236 }
237
238 // Local Variables:
239 // mode: php
240 // tab-width: 8
241 // c-basic-offset: 4
242 // c-hanging-comment-ender-p: nil
243 // indent-tabs-mode: nil
244 // End:
245 ?>