1 | <?php |
---|
2 | /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */ |
---|
3 | |
---|
4 | /** |
---|
5 | * Storage driver for use against an LDAP server |
---|
6 | * |
---|
7 | * PHP versions 4 and 5 |
---|
8 | * |
---|
9 | * LICENSE: This source file is subject to version 3.01 of the PHP license |
---|
10 | * that is available through the world-wide-web at the following URI: |
---|
11 | * http://www.php.net/license/3_01.txt. If you did not receive a copy of |
---|
12 | * the PHP License and are unable to obtain it through the web, please |
---|
13 | * send a note to license@php.net so we can mail you a copy immediately. |
---|
14 | * |
---|
15 | * @category Authentication |
---|
16 | * @package Auth |
---|
17 | * @author Jan Wagner <wagner@netsols.de> |
---|
18 | * @author Adam Ashley <aashley@php.net> |
---|
19 | * @author Hugues Peeters <hugues.peeters@claroline.net> |
---|
20 | * @copyright 2001-2006 The PHP Group |
---|
21 | * @license http://www.php.net/license/3_01.txt PHP License 3.01 |
---|
22 | * @version CVS: $Id$ |
---|
23 | * @link http://pear.php.net/package/Auth |
---|
24 | */ |
---|
25 | |
---|
26 | /** |
---|
27 | * Include Auth_Container base class |
---|
28 | */ |
---|
29 | require_once "Auth/Container.php"; |
---|
30 | /** |
---|
31 | * Include PEAR package for error handling |
---|
32 | */ |
---|
33 | require_once "PEAR.php"; |
---|
34 | |
---|
35 | /** |
---|
36 | * Storage driver for fetching login data from LDAP |
---|
37 | * |
---|
38 | * This class is heavily based on the DB and File containers. By default it |
---|
39 | * connects to localhost:389 and searches for uid=$username with the scope |
---|
40 | * "sub". If no search base is specified, it will try to determine it via |
---|
41 | * the namingContexts attribute. It takes its parameters in a hash, connects |
---|
42 | * to the ldap server, binds anonymously, searches for the user, and tries |
---|
43 | * to bind as the user with the supplied password. When a group was set, it |
---|
44 | * will look for group membership of the authenticated user. If all goes |
---|
45 | * well the authentication was successful. |
---|
46 | * |
---|
47 | * Parameters: |
---|
48 | * |
---|
49 | * host: localhost (default), ldap.netsols.de or 127.0.0.1 |
---|
50 | * port: 389 (default) or 636 or whereever your server runs |
---|
51 | * url: ldap://localhost:389/ |
---|
52 | * useful for ldaps://, works only with openldap2 ? |
---|
53 | * it will be preferred over host and port |
---|
54 | * version: LDAP version to use, ususally 2 (default) or 3, |
---|
55 | * must be an integer! |
---|
56 | * referrals: If set, determines whether the LDAP library automatically |
---|
57 | * follows referrals returned by LDAP servers or not. Possible |
---|
58 | * values are true (default) or false. |
---|
59 | * binddn: If set, searching for user will be done after binding |
---|
60 | * as this user, if not set the bind will be anonymous. |
---|
61 | * This is reported to make the container work with MS |
---|
62 | * Active Directory, but should work with any server that |
---|
63 | * is configured this way. |
---|
64 | * This has to be a complete dn for now (basedn and |
---|
65 | * userdn will not be appended). |
---|
66 | * bindpw: The password to use for binding with binddn |
---|
67 | * basedn: the base dn of your server |
---|
68 | * userdn: gets prepended to basedn when searching for user |
---|
69 | * userscope: Scope for user searching: one, sub (default), or base |
---|
70 | * userattr: the user attribute to search for (default: uid) |
---|
71 | * userfilter: filter that will be added to the search filter |
---|
72 | * this way: (&(userattr=username)(userfilter)) |
---|
73 | * default: (objectClass=posixAccount) |
---|
74 | * attributes: array of additional attributes to fetch from entry. |
---|
75 | * these will added to auth data and can be retrieved via |
---|
76 | * Auth::getAuthData(). An empty array will fetch all attributes, |
---|
77 | * array('') will fetch no attributes at all (default) |
---|
78 | * If you add 'dn' as a value to this array, the users DN that was |
---|
79 | * used for binding will be added to auth data as well. |
---|
80 | * attrformat: The returned format of the additional data defined in the |
---|
81 | * 'attributes' option. Two formats are available. |
---|
82 | * LDAP returns data formatted in a |
---|
83 | * multidimensional array where each array starts with a |
---|
84 | * 'count' element providing the number of attributes in the |
---|
85 | * entry, or the number of values for attributes. When set |
---|
86 | * to this format, the only way to retrieve data from the |
---|
87 | * Auth object is by calling getAuthData('attributes'). |
---|
88 | * AUTH returns data formatted in a |
---|
89 | * structure more compliant with other Auth Containers, |
---|
90 | * where each attribute element can be directly called by |
---|
91 | * getAuthData() method from Auth. |
---|
92 | * For compatibily with previous LDAP container versions, |
---|
93 | * the default format is LDAP. |
---|
94 | * groupdn: gets prepended to basedn when searching for group |
---|
95 | * groupattr: the group attribute to search for (default: cn) |
---|
96 | * groupfilter: filter that will be added to the search filter when |
---|
97 | * searching for a group: |
---|
98 | * (&(groupattr=group)(memberattr=username)(groupfilter)) |
---|
99 | * default: (objectClass=groupOfUniqueNames) |
---|
100 | * memberattr : the attribute of the group object where the user dn |
---|
101 | * may be found (default: uniqueMember) |
---|
102 | * memberisdn: whether the memberattr is the dn of the user (default) |
---|
103 | * or the value of userattr (usually uid) |
---|
104 | * group: the name of group to search for |
---|
105 | * groupscope: Scope for group searching: one, sub (default), or base |
---|
106 | * start_tls: enable/disable the use of START_TLS encrypted connection |
---|
107 | * (default: false) |
---|
108 | * debug: Enable/Disable debugging output (default: false) |
---|
109 | * try_all: Whether to try all user accounts returned from the search |
---|
110 | * or just the first one. (default: false) |
---|
111 | * |
---|
112 | * To use this storage container, you have to use the following syntax: |
---|
113 | * |
---|
114 | * <?php |
---|
115 | * ... |
---|
116 | * |
---|
117 | * $a1 = new Auth("LDAP", array( |
---|
118 | * 'host' => 'localhost', |
---|
119 | * 'port' => '389', |
---|
120 | * 'version' => 3, |
---|
121 | * 'basedn' => 'o=netsols,c=de', |
---|
122 | * 'userattr' => 'uid' |
---|
123 | * 'binddn' => 'cn=admin,o=netsols,c=de', |
---|
124 | * 'bindpw' => 'password')); |
---|
125 | * |
---|
126 | * $a2 = new Auth('LDAP', array( |
---|
127 | * 'url' => 'ldaps://ldap.netsols.de', |
---|
128 | * 'basedn' => 'o=netsols,c=de', |
---|
129 | * 'userscope' => 'one', |
---|
130 | * 'userdn' => 'ou=People', |
---|
131 | * 'groupdn' => 'ou=Groups', |
---|
132 | * 'groupfilter' => '(objectClass=posixGroup)', |
---|
133 | * 'memberattr' => 'memberUid', |
---|
134 | * 'memberisdn' => false, |
---|
135 | * 'group' => 'admin' |
---|
136 | * )); |
---|
137 | * |
---|
138 | * $a3 = new Auth('LDAP', array( |
---|
139 | * 'host' => 'ldap.netsols.de', |
---|
140 | * 'port' => 389, |
---|
141 | * 'version' => 3, |
---|
142 | * 'referrals' => false, |
---|
143 | * 'basedn' => 'dc=netsols,dc=de', |
---|
144 | * 'binddn' => 'cn=Jan Wagner,cn=Users,dc=netsols,dc=de', |
---|
145 | * 'bindpw' => 'password', |
---|
146 | * 'userattr' => 'samAccountName', |
---|
147 | * 'userfilter' => '(objectClass=user)', |
---|
148 | * 'attributes' => array(''), |
---|
149 | * 'group' => 'testing', |
---|
150 | * 'groupattr' => 'samAccountName', |
---|
151 | * 'groupfilter' => '(objectClass=group)', |
---|
152 | * 'memberattr' => 'member', |
---|
153 | * 'memberisdn' => true, |
---|
154 | * 'groupdn' => 'cn=Users', |
---|
155 | * 'groupscope' => 'one', |
---|
156 | * 'debug' => true); |
---|
157 | * |
---|
158 | * The parameter values have to correspond |
---|
159 | * to the ones for your LDAP server of course. |
---|
160 | * |
---|
161 | * When talking to a Microsoft ActiveDirectory server you have to |
---|
162 | * use 'samaccountname' as the 'userattr' and follow special rules |
---|
163 | * to translate the ActiveDirectory directory names into 'basedn'. |
---|
164 | * The 'basedn' for the default 'Users' folder on an ActiveDirectory |
---|
165 | * server for the ActiveDirectory Domain (which is not related to |
---|
166 | * its DNS name) "win2000.example.org" would be: |
---|
167 | * "CN=Users, DC=win2000, DC=example, DC=org' |
---|
168 | * where every component of the domain name becomes a DC attribute |
---|
169 | * of its own. If you want to use a custom users folder you have to |
---|
170 | * replace "CN=Users" with a sequence of "OU" attributes that specify |
---|
171 | * the path to your custom folder in reverse order. |
---|
172 | * So the ActiveDirectory folder |
---|
173 | * "win2000.example.org\Custom\Accounts" |
---|
174 | * would become |
---|
175 | * "OU=Accounts, OU=Custom, DC=win2000, DC=example, DC=org' |
---|
176 | * |
---|
177 | * It seems that binding anonymously to an Active Directory |
---|
178 | * is not allowed, so you have to set binddn and bindpw for |
---|
179 | * user searching. |
---|
180 | * |
---|
181 | * LDAP Referrals need to be set to false for AD to work sometimes. |
---|
182 | * |
---|
183 | * Example a3 shows a full blown and tested example for connection to |
---|
184 | * Windows 2000 Active Directory with group mebership checking |
---|
185 | * |
---|
186 | * Note also that if you want an encrypted connection to an MS LDAP |
---|
187 | * server, then, on your webserver, you must specify |
---|
188 | * TLS_REQCERT never |
---|
189 | * in /etc/ldap/ldap.conf or in the webserver user's ~/.ldaprc (which |
---|
190 | * may or may not be read depending on your configuration). |
---|
191 | * |
---|
192 | * |
---|
193 | * @category Authentication |
---|
194 | * @package Auth |
---|
195 | * @author Jan Wagner <wagner@netsols.de> |
---|
196 | * @author Adam Ashley <aashley@php.net> |
---|
197 | * @author Hugues Peeters <hugues.peeters@claroline.net> |
---|
198 | * @copyright 2001-2006 The PHP Group |
---|
199 | * @license http://www.php.net/license/3_01.txt PHP License 3.01 |
---|
200 | * @version Release: 1.4.2 File: $Revision: 8713 $ |
---|
201 | * @link http://pear.php.net/package/Auth |
---|
202 | */ |
---|
203 | class Auth_Container_LDAP extends Auth_Container |
---|
204 | { |
---|
205 | |
---|
206 | // {{{ properties |
---|
207 | |
---|
208 | /** |
---|
209 | * Options for the class |
---|
210 | * @var array |
---|
211 | */ |
---|
212 | var $options = array(); |
---|
213 | |
---|
214 | /** |
---|
215 | * Connection ID of LDAP Link |
---|
216 | * @var string |
---|
217 | */ |
---|
218 | var $conn_id = false; |
---|
219 | |
---|
220 | // }}} |
---|
221 | |
---|
222 | // {{{ Auth_Container_LDAP() [constructor] |
---|
223 | |
---|
224 | /** |
---|
225 | * Constructor of the container class |
---|
226 | * |
---|
227 | * @param $params, associative hash with host,port,basedn and userattr key |
---|
228 | * @return object Returns an error object if something went wrong |
---|
229 | */ |
---|
230 | function Auth_Container_LDAP($params) |
---|
231 | { |
---|
232 | if (false === extension_loaded('ldap')) { |
---|
233 | return PEAR::raiseError('Auth_Container_LDAP: LDAP Extension not loaded', |
---|
234 | 41, PEAR_ERROR_DIE); |
---|
235 | } |
---|
236 | |
---|
237 | $this->_setDefaults(); |
---|
238 | |
---|
239 | if (is_array($params)) { |
---|
240 | $this->_parseOptions($params); |
---|
241 | } |
---|
242 | } |
---|
243 | |
---|
244 | // }}} |
---|
245 | // {{{ _prepare() |
---|
246 | |
---|
247 | /** |
---|
248 | * Prepare LDAP connection |
---|
249 | * |
---|
250 | * This function checks if we have already opened a connection to |
---|
251 | * the LDAP server. If that's not the case, a new connection is opened. |
---|
252 | * |
---|
253 | * @access private |
---|
254 | * @return mixed True or a PEAR error object. |
---|
255 | */ |
---|
256 | function _prepare() |
---|
257 | { |
---|
258 | if (!$this->_isValidLink()) { |
---|
259 | $res = $this->_connect(); |
---|
260 | if (PEAR::isError($res)) { |
---|
261 | return $res; |
---|
262 | } |
---|
263 | } |
---|
264 | return true; |
---|
265 | } |
---|
266 | |
---|
267 | // }}} |
---|
268 | // {{{ _connect() |
---|
269 | |
---|
270 | /** |
---|
271 | * Connect to the LDAP server using the global options |
---|
272 | * |
---|
273 | * @access private |
---|
274 | * @return object Returns a PEAR error object if an error occurs. |
---|
275 | */ |
---|
276 | function _connect() |
---|
277 | { |
---|
278 | // connect |
---|
279 | if (isset($this->options['url']) && $this->options['url'] != '') { |
---|
280 | $this->_debug('Connecting with URL', __LINE__); |
---|
281 | $conn_params = array($this->options['url']); |
---|
282 | } else { |
---|
283 | $this->_debug('Connecting with host:port', __LINE__); |
---|
284 | $conn_params = array($this->options['host'], $this->options['port']); |
---|
285 | } |
---|
286 | |
---|
287 | if (($this->conn_id = @call_user_func_array('ldap_connect', $conn_params)) === false) { |
---|
288 | return PEAR::raiseError('Auth_Container_LDAP: Could not connect to server.', 41); |
---|
289 | } |
---|
290 | $this->_debug('Successfully connected to server', __LINE__); |
---|
291 | |
---|
292 | // switch LDAP version |
---|
293 | if (is_numeric($this->options['version']) && $this->options['version'] > 2) { |
---|
294 | $this->_debug("Switching to LDAP version {$this->options['version']}", __LINE__); |
---|
295 | @ldap_set_option($this->conn_id, LDAP_OPT_PROTOCOL_VERSION, $this->options['version']); |
---|
296 | |
---|
297 | // start TLS if available |
---|
298 | if (isset($this->options['start_tls']) && $this->options['start_tls']) { |
---|
299 | $this->_debug("Starting TLS session", __LINE__); |
---|
300 | if (@ldap_start_tls($this->conn_id) === false) { |
---|
301 | return PEAR::raiseError('Auth_Container_LDAP: Could not start tls.', 41); |
---|
302 | } |
---|
303 | } |
---|
304 | } |
---|
305 | |
---|
306 | // switch LDAP referrals |
---|
307 | if (is_bool($this->options['referrals'])) { |
---|
308 | $this->_debug("Switching LDAP referrals to " . (($this->options['referrals']) ? 'true' : 'false'), __LINE__); |
---|
309 | @ldap_set_option($this->conn_id, LDAP_OPT_REFERRALS, $this->options['referrals']); |
---|
310 | } |
---|
311 | |
---|
312 | // bind with credentials or anonymously |
---|
313 | if (strlen($this->options['binddn']) && strlen($this->options['bindpw'])) { |
---|
314 | $this->_debug('Binding with credentials', __LINE__); |
---|
315 | $bind_params = array($this->conn_id, $this->options['binddn'], $this->options['bindpw']); |
---|
316 | } else { |
---|
317 | $this->_debug('Binding anonymously', __LINE__); |
---|
318 | $bind_params = array($this->conn_id); |
---|
319 | } |
---|
320 | |
---|
321 | // bind for searching |
---|
322 | if ((@call_user_func_array('ldap_bind', $bind_params)) === false) { |
---|
323 | $this->_debug(); |
---|
324 | $this->_disconnect(); |
---|
325 | return PEAR::raiseError("Auth_Container_LDAP: Could not bind to LDAP server.", 41); |
---|
326 | } |
---|
327 | $this->_debug('Binding was successful', __LINE__); |
---|
328 | |
---|
329 | return true; |
---|
330 | } |
---|
331 | |
---|
332 | // }}} |
---|
333 | // {{{ _disconnect() |
---|
334 | |
---|
335 | /** |
---|
336 | * Disconnects (unbinds) from ldap server |
---|
337 | * |
---|
338 | * @access private |
---|
339 | */ |
---|
340 | function _disconnect() |
---|
341 | { |
---|
342 | if ($this->_isValidLink()) { |
---|
343 | $this->_debug('disconnecting from server'); |
---|
344 | @ldap_unbind($this->conn_id); |
---|
345 | } |
---|
346 | } |
---|
347 | |
---|
348 | // }}} |
---|
349 | // {{{ _getBaseDN() |
---|
350 | |
---|
351 | /** |
---|
352 | * Tries to find Basedn via namingContext Attribute |
---|
353 | * |
---|
354 | * @access private |
---|
355 | */ |
---|
356 | function _getBaseDN() |
---|
357 | { |
---|
358 | $err = $this->_prepare(); |
---|
359 | if ($err !== true) { |
---|
360 | return PEAR::raiseError($err->getMessage(), $err->getCode()); |
---|
361 | } |
---|
362 | |
---|
363 | if ($this->options['basedn'] == "" && $this->_isValidLink()) { |
---|
364 | $this->_debug("basedn not set, searching via namingContexts.", __LINE__); |
---|
365 | |
---|
366 | $result_id = @ldap_read($this->conn_id, "", "(objectclass=*)", array("namingContexts")); |
---|
367 | |
---|
368 | if (@ldap_count_entries($this->conn_id, $result_id) == 1) { |
---|
369 | |
---|
370 | $this->_debug("got result for namingContexts", __LINE__); |
---|
371 | |
---|
372 | $entry_id = @ldap_first_entry($this->conn_id, $result_id); |
---|
373 | $attrs = @ldap_get_attributes($this->conn_id, $entry_id); |
---|
374 | $basedn = $attrs['namingContexts'][0]; |
---|
375 | |
---|
376 | if ($basedn != "") { |
---|
377 | $this->_debug("result for namingContexts was $basedn", __LINE__); |
---|
378 | $this->options['basedn'] = $basedn; |
---|
379 | } |
---|
380 | } |
---|
381 | @ldap_free_result($result_id); |
---|
382 | } |
---|
383 | |
---|
384 | // if base ist still not set, raise error |
---|
385 | if ($this->options['basedn'] == "") { |
---|
386 | return PEAR::raiseError("Auth_Container_LDAP: LDAP search base not specified!", 41); |
---|
387 | } |
---|
388 | return true; |
---|
389 | } |
---|
390 | |
---|
391 | // }}} |
---|
392 | // {{{ _isValidLink() |
---|
393 | |
---|
394 | /** |
---|
395 | * determines whether there is a valid ldap conenction or not |
---|
396 | * |
---|
397 | * @accessd private |
---|
398 | * @return boolean |
---|
399 | */ |
---|
400 | function _isValidLink() |
---|
401 | { |
---|
402 | if (is_resource($this->conn_id)) { |
---|
403 | if (get_resource_type($this->conn_id) == 'ldap link') { |
---|
404 | return true; |
---|
405 | } |
---|
406 | } |
---|
407 | return false; |
---|
408 | } |
---|
409 | |
---|
410 | // }}} |
---|
411 | // {{{ _setDefaults() |
---|
412 | |
---|
413 | /** |
---|
414 | * Set some default options |
---|
415 | * |
---|
416 | * @access private |
---|
417 | */ |
---|
418 | function _setDefaults() |
---|
419 | { |
---|
420 | $this->options['url'] = ''; |
---|
421 | $this->options['host'] = 'localhost'; |
---|
422 | $this->options['port'] = '389'; |
---|
423 | $this->options['version'] = 2; |
---|
424 | $this->options['referrals'] = true; |
---|
425 | $this->options['binddn'] = ''; |
---|
426 | $this->options['bindpw'] = ''; |
---|
427 | $this->options['basedn'] = ''; |
---|
428 | $this->options['userdn'] = ''; |
---|
429 | $this->options['userscope'] = 'sub'; |
---|
430 | $this->options['userattr'] = 'uid'; |
---|
431 | $this->options['userfilter'] = '(objectClass=posixAccount)'; |
---|
432 | $this->options['attributes'] = array(''); // no attributes |
---|
433 | // $this->options['attrformat'] = 'LDAP'; // returns attribute array as PHP LDAP functions return it |
---|
434 | $this->options['attrformat'] = 'AUTH'; // returns attribute like other Auth containers |
---|
435 | $this->options['group'] = ''; |
---|
436 | $this->options['groupdn'] = ''; |
---|
437 | $this->options['groupscope'] = 'sub'; |
---|
438 | $this->options['groupattr'] = 'cn'; |
---|
439 | $this->options['groupfilter'] = '(objectClass=groupOfUniqueNames)'; |
---|
440 | $this->options['memberattr'] = 'uniqueMember'; |
---|
441 | $this->options['memberisdn'] = true; |
---|
442 | $this->options['start_tls'] = false; |
---|
443 | $this->options['debug'] = false; |
---|
444 | $this->options['try_all'] = false; // Try all user ids returned not just the first one |
---|
445 | } |
---|
446 | |
---|
447 | // }}} |
---|
448 | // {{{ _parseOptions() |
---|
449 | |
---|
450 | /** |
---|
451 | * Parse options passed to the container class |
---|
452 | * |
---|
453 | * @access private |
---|
454 | * @param array |
---|
455 | */ |
---|
456 | function _parseOptions($array) |
---|
457 | { |
---|
458 | $array = $this->_setV12OptionsToV13($array); |
---|
459 | |
---|
460 | foreach ($array as $key => $value) { |
---|
461 | if (array_key_exists($key, $this->options)) { |
---|
462 | if ($key == 'attributes') { |
---|
463 | if (is_array($value)) { |
---|
464 | $this->options[$key] = $value; |
---|
465 | } else { |
---|
466 | $this->options[$key] = explode(',', $value); |
---|
467 | } |
---|
468 | } else { |
---|
469 | $this->options[$key] = $value; |
---|
470 | } |
---|
471 | } |
---|
472 | } |
---|
473 | } |
---|
474 | |
---|
475 | // }}} |
---|
476 | // {{{ _setV12OptionsToV13() |
---|
477 | |
---|
478 | /** |
---|
479 | * Adapt deprecated options from Auth 1.2 LDAP to Auth 1.3 LDAP |
---|
480 | * |
---|
481 | * @author Hugues Peeters <hugues.peeters@claroline.net> |
---|
482 | * @access private |
---|
483 | * @param array |
---|
484 | * @return array |
---|
485 | */ |
---|
486 | function _setV12OptionsToV13($array) |
---|
487 | { |
---|
488 | if (isset($array['useroc'])) |
---|
489 | $array['userfilter'] = "(objectClass=".$array['useroc'].")"; |
---|
490 | if (isset($array['groupoc'])) |
---|
491 | $array['groupfilter'] = "(objectClass=".$array['groupoc'].")"; |
---|
492 | if (isset($array['scope'])) |
---|
493 | $array['userscope'] = $array['scope']; |
---|
494 | |
---|
495 | return $array; |
---|
496 | } |
---|
497 | |
---|
498 | // }}} |
---|
499 | // {{{ _scope2function() |
---|
500 | |
---|
501 | /** |
---|
502 | * Get search function for scope |
---|
503 | * |
---|
504 | * @param string scope |
---|
505 | * @return string ldap search function |
---|
506 | */ |
---|
507 | function _scope2function($scope) |
---|
508 | { |
---|
509 | switch($scope) { |
---|
510 | case 'one': |
---|
511 | $function = 'ldap_list'; |
---|
512 | break; |
---|
513 | case 'base': |
---|
514 | $function = 'ldap_read'; |
---|
515 | break; |
---|
516 | default: |
---|
517 | $function = 'ldap_search'; |
---|
518 | break; |
---|
519 | } |
---|
520 | return $function; |
---|
521 | } |
---|
522 | |
---|
523 | // }}} |
---|
524 | // {{{ fetchData() |
---|
525 | |
---|
526 | /** |
---|
527 | * Fetch data from LDAP server |
---|
528 | * |
---|
529 | * Searches the LDAP server for the given username/password |
---|
530 | * combination. Escapes all LDAP meta characters in username |
---|
531 | * before performing the query. |
---|
532 | * |
---|
533 | * @param string Username |
---|
534 | * @param string Password |
---|
535 | * @return boolean |
---|
536 | */ |
---|
537 | function fetchData($username, $password) |
---|
538 | { |
---|
539 | $err = $this->_prepare(); |
---|
540 | if ($err !== true) { |
---|
541 | return PEAR::raiseError($err->getMessage(), $err->getCode()); |
---|
542 | } |
---|
543 | |
---|
544 | $err = $this->_getBaseDN(); |
---|
545 | if ($err !== true) { |
---|
546 | return PEAR::raiseError($err->getMessage(), $err->getCode()); |
---|
547 | } |
---|
548 | |
---|
549 | // UTF8 Encode username for LDAPv3 |
---|
550 | if (@ldap_get_option($this->conn_id, LDAP_OPT_PROTOCOL_VERSION, $ver) && $ver == 3) { |
---|
551 | $this->_debug('UTF8 encoding username for LDAPv3', __LINE__); |
---|
552 | $username = utf8_encode($username); |
---|
553 | } |
---|
554 | |
---|
555 | // make search filter |
---|
556 | $filter = sprintf('(&(%s=%s)%s)', |
---|
557 | $this->options['userattr'], |
---|
558 | $this->_quoteFilterString($username), |
---|
559 | $this->options['userfilter']); |
---|
560 | |
---|
561 | // make search base dn |
---|
562 | $search_basedn = $this->options['userdn']; |
---|
563 | if ($search_basedn != '' && substr($search_basedn, -1) != ',') { |
---|
564 | $search_basedn .= ','; |
---|
565 | } |
---|
566 | $search_basedn .= $this->options['basedn']; |
---|
567 | |
---|
568 | // attributes |
---|
569 | $attributes = $this->options['attributes']; |
---|
570 | |
---|
571 | // make functions params array |
---|
572 | $func_params = array($this->conn_id, $search_basedn, $filter, $attributes); |
---|
573 | |
---|
574 | // search function to use |
---|
575 | $func_name = $this->_scope2function($this->options['userscope']); |
---|
576 | |
---|
577 | $this->_debug("Searching with $func_name and filter $filter in $search_basedn", __LINE__); |
---|
578 | |
---|
579 | // search |
---|
580 | if (($result_id = @call_user_func_array($func_name, $func_params)) === false) { |
---|
581 | $this->_debug('User not found', __LINE__); |
---|
582 | } elseif (@ldap_count_entries($this->conn_id, $result_id) >= 1) { // did we get some possible results? |
---|
583 | |
---|
584 | $this->_debug('User(s) found', __LINE__); |
---|
585 | |
---|
586 | $first = true; |
---|
587 | $entry_id = null; |
---|
588 | |
---|
589 | do { |
---|
590 | |
---|
591 | // then get the user dn |
---|
592 | if ($first) { |
---|
593 | $entry_id = @ldap_first_entry($this->conn_id, $result_id); |
---|
594 | $first = false; |
---|
595 | } else { |
---|
596 | $entry_id = @ldap_next_entry($this->conn_id, $entry_id); |
---|
597 | if ($entry_id === false) |
---|
598 | break; |
---|
599 | } |
---|
600 | $user_dn = @ldap_get_dn($this->conn_id, $entry_id); |
---|
601 | |
---|
602 | // as the dn is not fetched as an attribute, we save it anyway |
---|
603 | if (is_array($attributes) && in_array('dn', $attributes)) { |
---|
604 | $this->_debug('Saving DN to AuthData', __LINE__); |
---|
605 | $this->_auth_obj->setAuthData('dn', $user_dn); |
---|
606 | } |
---|
607 | |
---|
608 | // fetch attributes |
---|
609 | if ($attributes = @ldap_get_attributes($this->conn_id, $entry_id)) { |
---|
610 | |
---|
611 | if (is_array($attributes) && isset($attributes['count']) && |
---|
612 | $attributes['count'] > 0) { |
---|
613 | |
---|
614 | // ldap_get_attributes() returns a specific multi dimensional array |
---|
615 | // format containing all the attributes and where each array starts |
---|
616 | // with a 'count' element providing the number of attributes in the |
---|
617 | // entry, or the number of values for attribute. For compatibility |
---|
618 | // reasons, it remains the default format returned by LDAP container |
---|
619 | // setAuthData(). |
---|
620 | // The code below optionally returns attributes in another format, |
---|
621 | // more compliant with other Auth containers, where each attribute |
---|
622 | // element are directly set in the 'authData' list. This option is |
---|
623 | // enabled by setting 'attrformat' to |
---|
624 | // 'AUTH' in the 'options' array. |
---|
625 | // eg. $this->options['attrformat'] = 'AUTH' |
---|
626 | |
---|
627 | if ( strtoupper($this->options['attrformat']) == 'AUTH' ) { |
---|
628 | $this->_debug('Saving attributes to Auth data in AUTH format', __LINE__); |
---|
629 | unset ($attributes['count']); |
---|
630 | foreach ($attributes as $attributeName => $attributeValue ) { |
---|
631 | if (is_int($attributeName)) continue; |
---|
632 | if (is_array($attributeValue) && isset($attributeValue['count'])) { |
---|
633 | unset ($attributeValue['count']); |
---|
634 | } |
---|
635 | if (count($attributeValue)<=1) $attributeValue = $attributeValue[0]; |
---|
636 | $this->_auth_obj->setAuthData($attributeName, $attributeValue); |
---|
637 | } |
---|
638 | } |
---|
639 | else |
---|
640 | { |
---|
641 | $this->_debug('Saving attributes to Auth data in LDAP format', __LINE__); |
---|
642 | $this->_auth_obj->setAuthData('attributes', $attributes); |
---|
643 | } |
---|
644 | } |
---|
645 | } |
---|
646 | @ldap_free_result($result_id); |
---|
647 | |
---|
648 | // need to catch an empty password as openldap seems to return TRUE |
---|
649 | // if anonymous binding is allowed |
---|
650 | if ($password != "") { |
---|
651 | $this->_debug("Bind as $user_dn", __LINE__); |
---|
652 | |
---|
653 | // try binding as this user with the supplied password |
---|
654 | if (@ldap_bind($this->conn_id, $user_dn, $password)) { |
---|
655 | $this->_debug('Bind successful', __LINE__); |
---|
656 | |
---|
657 | // check group if appropiate |
---|
658 | if (strlen($this->options['group'])) { |
---|
659 | // decide whether memberattr value is a dn or the username |
---|
660 | $this->_debug('Checking group membership', __LINE__); |
---|
661 | $return = $this->checkGroup(($this->options['memberisdn']) ? $user_dn : $username); |
---|
662 | $this->_disconnect(); |
---|
663 | return $return; |
---|
664 | } else { |
---|
665 | $this->_debug('Authenticated', __LINE__); |
---|
666 | $this->_disconnect(); |
---|
667 | return true; // user authenticated |
---|
668 | } // checkGroup |
---|
669 | } // bind |
---|
670 | } // non-empty password |
---|
671 | } while ($this->options['try_all'] == true); // interate through entries |
---|
672 | } // get results |
---|
673 | // default |
---|
674 | $this->_debug('NOT authenticated!', __LINE__); |
---|
675 | $this->_disconnect(); |
---|
676 | return false; |
---|
677 | } |
---|
678 | |
---|
679 | // }}} |
---|
680 | // {{{ checkGroup() |
---|
681 | |
---|
682 | /** |
---|
683 | * Validate group membership |
---|
684 | * |
---|
685 | * Searches the LDAP server for group membership of the |
---|
686 | * supplied username. Quotes all LDAP filter meta characters in |
---|
687 | * the user name before querying the LDAP server. |
---|
688 | * |
---|
689 | * @param string Distinguished Name of the authenticated User |
---|
690 | * @return boolean |
---|
691 | */ |
---|
692 | function checkGroup($user) |
---|
693 | { |
---|
694 | $err = $this->_prepare(); |
---|
695 | if ($err !== true) { |
---|
696 | return PEAR::raiseError($err->getMessage(), $err->getCode()); |
---|
697 | } |
---|
698 | |
---|
699 | // make filter |
---|
700 | $filter = sprintf('(&(%s=%s)(%s=%s)%s)', |
---|
701 | $this->options['groupattr'], |
---|
702 | $this->options['group'], |
---|
703 | $this->options['memberattr'], |
---|
704 | $this->_quoteFilterString($user), |
---|
705 | $this->options['groupfilter']); |
---|
706 | |
---|
707 | // make search base dn |
---|
708 | $search_basedn = $this->options['groupdn']; |
---|
709 | if ($search_basedn != '' && substr($search_basedn, -1) != ',') { |
---|
710 | $search_basedn .= ','; |
---|
711 | } |
---|
712 | $search_basedn .= $this->options['basedn']; |
---|
713 | |
---|
714 | $func_params = array($this->conn_id, $search_basedn, $filter, |
---|
715 | array($this->options['memberattr'])); |
---|
716 | $func_name = $this->_scope2function($this->options['groupscope']); |
---|
717 | |
---|
718 | $this->_debug("Searching with $func_name and filter $filter in $search_basedn", __LINE__); |
---|
719 | |
---|
720 | // search |
---|
721 | if (($result_id = @call_user_func_array($func_name, $func_params)) != false) { |
---|
722 | if (@ldap_count_entries($this->conn_id, $result_id) == 1) { |
---|
723 | @ldap_free_result($result_id); |
---|
724 | $this->_debug('User is member of group', __LINE__); |
---|
725 | return true; |
---|
726 | } |
---|
727 | } |
---|
728 | // default |
---|
729 | $this->_debug('User is NOT member of group', __LINE__); |
---|
730 | return false; |
---|
731 | } |
---|
732 | |
---|
733 | // }}} |
---|
734 | // {{{ _debug() |
---|
735 | |
---|
736 | /** |
---|
737 | * Outputs debugging messages |
---|
738 | * |
---|
739 | * @access private |
---|
740 | * @param string Debugging Message |
---|
741 | * @param integer Line number |
---|
742 | */ |
---|
743 | function _debug($msg = '', $line = 0) |
---|
744 | { |
---|
745 | if ($this->options['debug'] == true) { |
---|
746 | if ($msg == '' && $this->_isValidLink()) { |
---|
747 | $msg = 'LDAP_Error: ' . @ldap_err2str(@ldap_errno($this->_conn_id)); |
---|
748 | } |
---|
749 | print("$line: $msg <br />"); |
---|
750 | } |
---|
751 | } |
---|
752 | |
---|
753 | // }}} |
---|
754 | // {{{ _quoteFilterString() |
---|
755 | |
---|
756 | /** |
---|
757 | * Escapes LDAP filter special characters as defined in RFC 2254. |
---|
758 | * |
---|
759 | * @access private |
---|
760 | * @param string Filter String |
---|
761 | */ |
---|
762 | function _quoteFilterString($filter_str) |
---|
763 | { |
---|
764 | $metas = array( '\\', '*', '(', ')', "\x00"); |
---|
765 | $quoted_metas = array('\\\\', '\*', '\(', '\)', "\\\x00"); |
---|
766 | return str_replace($metas, $quoted_metas, $filter_str); |
---|
767 | } |
---|
768 | |
---|
769 | // }}} |
---|
770 | |
---|
771 | } |
---|
772 | |
---|
773 | ?> |
---|