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: LDAP.php,v 1.43 2007/06/12 03:11:26 aashley Exp $ |
---|
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.6.1 File: $Revision: 1.43 $ |
---|
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 | $this->log('Auth_Container_LDAP::_connect() called.', AUTH_LOG_DEBUG); |
---|
279 | // connect |
---|
280 | if (isset($this->options['url']) && $this->options['url'] != '') { |
---|
281 | $this->log('Connecting with URL', AUTH_LOG_DEBUG); |
---|
282 | $conn_params = array($this->options['url']); |
---|
283 | } else { |
---|
284 | $this->log('Connecting with host:port', AUTH_LOG_DEBUG); |
---|
285 | $conn_params = array($this->options['host'], $this->options['port']); |
---|
286 | } |
---|
287 | |
---|
288 | if (($this->conn_id = @call_user_func_array('ldap_connect', $conn_params)) === false) { |
---|
289 | $this->log('Connection to server failed.', AUTH_LOG_DEBUG); |
---|
290 | $this->log('LDAP ERROR: '.ldap_errno($this->conn_id).': '.ldap_error($this->conn_id), AUTH_LOG_DEBUG); |
---|
291 | return PEAR::raiseError('Auth_Container_LDAP: Could not connect to server.', 41); |
---|
292 | } |
---|
293 | $this->log('Successfully connected to server', AUTH_LOG_DEBUG); |
---|
294 | |
---|
295 | // switch LDAP version |
---|
296 | if (is_numeric($this->options['version']) && $this->options['version'] > 2) { |
---|
297 | $this->log("Switching to LDAP version {$this->options['version']}", AUTH_LOG_DEBUG); |
---|
298 | @ldap_set_option($this->conn_id, LDAP_OPT_PROTOCOL_VERSION, $this->options['version']); |
---|
299 | |
---|
300 | // start TLS if available |
---|
301 | if (isset($this->options['start_tls']) && $this->options['start_tls']) { |
---|
302 | $this->log("Starting TLS session", AUTH_LOG_DEBUG); |
---|
303 | if (@ldap_start_tls($this->conn_id) === false) { |
---|
304 | $this->log('Could not start TLS session', AUTH_LOG_DEBUG); |
---|
305 | $this->log('LDAP ERROR: '.ldap_errno($this->conn_id).': '.ldap_error($this->conn_id), AUTH_LOG_DEBUG); |
---|
306 | return PEAR::raiseError('Auth_Container_LDAP: Could not start tls.', 41); |
---|
307 | } |
---|
308 | } |
---|
309 | } |
---|
310 | |
---|
311 | // switch LDAP referrals |
---|
312 | if (is_bool($this->options['referrals'])) { |
---|
313 | $this->log("Switching LDAP referrals to " . (($this->options['referrals']) ? 'true' : 'false'), AUTH_LOG_DEBUG); |
---|
314 | if (@ldap_set_option($this->conn_id, LDAP_OPT_REFERRALS, $this->options['referrals']) === false) { |
---|
315 | $this->log('Could not change LDAP referrals options', AUTH_LOG_DEBUG); |
---|
316 | $this->log('LDAP ERROR: '.ldap_errno($this->conn_id).': '.ldap_error($this->conn_id), AUTH_LOG_DEBUG); |
---|
317 | } |
---|
318 | } |
---|
319 | |
---|
320 | // bind with credentials or anonymously |
---|
321 | if (strlen($this->options['binddn']) && strlen($this->options['bindpw'])) { |
---|
322 | $this->log('Binding with credentials', AUTH_LOG_DEBUG); |
---|
323 | $bind_params = array($this->conn_id, $this->options['binddn'], $this->options['bindpw']); |
---|
324 | } else { |
---|
325 | $this->log('Binding anonymously', AUTH_LOG_DEBUG); |
---|
326 | $bind_params = array($this->conn_id); |
---|
327 | } |
---|
328 | |
---|
329 | // bind for searching |
---|
330 | if ((@call_user_func_array('ldap_bind', $bind_params)) === false) { |
---|
331 | $this->log('Bind failed', AUTH_LOG_DEBUG); |
---|
332 | $this->log('LDAP ERROR: '.ldap_errno($this->conn_id).': '.ldap_error($this->conn_id), AUTH_LOG_DEBUG); |
---|
333 | $this->_disconnect(); |
---|
334 | return PEAR::raiseError("Auth_Container_LDAP: Could not bind to LDAP server.", 41); |
---|
335 | } |
---|
336 | $this->log('Binding was successful', AUTH_LOG_DEBUG); |
---|
337 | |
---|
338 | return true; |
---|
339 | } |
---|
340 | |
---|
341 | // }}} |
---|
342 | // {{{ _disconnect() |
---|
343 | |
---|
344 | /** |
---|
345 | * Disconnects (unbinds) from ldap server |
---|
346 | * |
---|
347 | * @access private |
---|
348 | */ |
---|
349 | function _disconnect() |
---|
350 | { |
---|
351 | $this->log('Auth_Container_LDAP::_disconnect() called.', AUTH_LOG_DEBUG); |
---|
352 | if ($this->_isValidLink()) { |
---|
353 | $this->log('disconnecting from server'); |
---|
354 | @ldap_unbind($this->conn_id); |
---|
355 | } |
---|
356 | } |
---|
357 | |
---|
358 | // }}} |
---|
359 | // {{{ _getBaseDN() |
---|
360 | |
---|
361 | /** |
---|
362 | * Tries to find Basedn via namingContext Attribute |
---|
363 | * |
---|
364 | * @access private |
---|
365 | */ |
---|
366 | function _getBaseDN() |
---|
367 | { |
---|
368 | $this->log('Auth_Container_LDAP::_getBaseDN() called.', AUTH_LOG_DEBUG); |
---|
369 | $err = $this->_prepare(); |
---|
370 | if ($err !== true) { |
---|
371 | return PEAR::raiseError($err->getMessage(), $err->getCode()); |
---|
372 | } |
---|
373 | |
---|
374 | if ($this->options['basedn'] == "" && $this->_isValidLink()) { |
---|
375 | $this->log("basedn not set, searching via namingContexts.", AUTH_LOG_DEBUG); |
---|
376 | |
---|
377 | $result_id = @ldap_read($this->conn_id, "", "(objectclass=*)", array("namingContexts")); |
---|
378 | |
---|
379 | if (@ldap_count_entries($this->conn_id, $result_id) == 1) { |
---|
380 | |
---|
381 | $this->log("got result for namingContexts", AUTH_LOG_DEBUG); |
---|
382 | |
---|
383 | $entry_id = @ldap_first_entry($this->conn_id, $result_id); |
---|
384 | $attrs = @ldap_get_attributes($this->conn_id, $entry_id); |
---|
385 | $basedn = $attrs['namingContexts'][0]; |
---|
386 | |
---|
387 | if ($basedn != "") { |
---|
388 | $this->log("result for namingContexts was $basedn", AUTH_LOG_DEBUG); |
---|
389 | $this->options['basedn'] = $basedn; |
---|
390 | } |
---|
391 | } |
---|
392 | @ldap_free_result($result_id); |
---|
393 | } |
---|
394 | |
---|
395 | // if base ist still not set, raise error |
---|
396 | if ($this->options['basedn'] == "") { |
---|
397 | return PEAR::raiseError("Auth_Container_LDAP: LDAP search base not specified!", 41); |
---|
398 | } |
---|
399 | return true; |
---|
400 | } |
---|
401 | |
---|
402 | // }}} |
---|
403 | // {{{ _isValidLink() |
---|
404 | |
---|
405 | /** |
---|
406 | * determines whether there is a valid ldap conenction or not |
---|
407 | * |
---|
408 | * @accessd private |
---|
409 | * @return boolean |
---|
410 | */ |
---|
411 | function _isValidLink() |
---|
412 | { |
---|
413 | if (is_resource($this->conn_id)) { |
---|
414 | if (get_resource_type($this->conn_id) == 'ldap link') { |
---|
415 | return true; |
---|
416 | } |
---|
417 | } |
---|
418 | return false; |
---|
419 | } |
---|
420 | |
---|
421 | // }}} |
---|
422 | // {{{ _setDefaults() |
---|
423 | |
---|
424 | /** |
---|
425 | * Set some default options |
---|
426 | * |
---|
427 | * @access private |
---|
428 | */ |
---|
429 | function _setDefaults() |
---|
430 | { |
---|
431 | $this->options['url'] = ''; |
---|
432 | $this->options['host'] = 'localhost'; |
---|
433 | $this->options['port'] = '389'; |
---|
434 | $this->options['version'] = 2; |
---|
435 | $this->options['referrals'] = true; |
---|
436 | $this->options['binddn'] = ''; |
---|
437 | $this->options['bindpw'] = ''; |
---|
438 | $this->options['basedn'] = ''; |
---|
439 | $this->options['userdn'] = ''; |
---|
440 | $this->options['userscope'] = 'sub'; |
---|
441 | $this->options['userattr'] = 'uid'; |
---|
442 | $this->options['userfilter'] = '(objectClass=posixAccount)'; |
---|
443 | $this->options['attributes'] = array(''); // no attributes |
---|
444 | $this->options['attrformat'] = 'AUTH'; // returns attribute like other Auth containers |
---|
445 | $this->options['group'] = ''; |
---|
446 | $this->options['groupdn'] = ''; |
---|
447 | $this->options['groupscope'] = 'sub'; |
---|
448 | $this->options['groupattr'] = 'cn'; |
---|
449 | $this->options['groupfilter'] = '(objectClass=groupOfUniqueNames)'; |
---|
450 | $this->options['memberattr'] = 'uniqueMember'; |
---|
451 | $this->options['memberisdn'] = true; |
---|
452 | $this->options['start_tls'] = false; |
---|
453 | $this->options['debug'] = false; |
---|
454 | $this->options['try_all'] = false; // Try all user ids returned not just the first one |
---|
455 | } |
---|
456 | |
---|
457 | // }}} |
---|
458 | // {{{ _parseOptions() |
---|
459 | |
---|
460 | /** |
---|
461 | * Parse options passed to the container class |
---|
462 | * |
---|
463 | * @access private |
---|
464 | * @param array |
---|
465 | */ |
---|
466 | function _parseOptions($array) |
---|
467 | { |
---|
468 | $array = $this->_setV12OptionsToV13($array); |
---|
469 | |
---|
470 | foreach ($array as $key => $value) { |
---|
471 | if (array_key_exists($key, $this->options)) { |
---|
472 | if ($key == 'attributes') { |
---|
473 | if (is_array($value)) { |
---|
474 | $this->options[$key] = $value; |
---|
475 | } else { |
---|
476 | $this->options[$key] = explode(',', $value); |
---|
477 | } |
---|
478 | } else { |
---|
479 | $this->options[$key] = $value; |
---|
480 | } |
---|
481 | } |
---|
482 | } |
---|
483 | } |
---|
484 | |
---|
485 | // }}} |
---|
486 | // {{{ _setV12OptionsToV13() |
---|
487 | |
---|
488 | /** |
---|
489 | * Adapt deprecated options from Auth 1.2 LDAP to Auth 1.3 LDAP |
---|
490 | * |
---|
491 | * @author Hugues Peeters <hugues.peeters@claroline.net> |
---|
492 | * @access private |
---|
493 | * @param array |
---|
494 | * @return array |
---|
495 | */ |
---|
496 | function _setV12OptionsToV13($array) |
---|
497 | { |
---|
498 | if (isset($array['useroc'])) |
---|
499 | $array['userfilter'] = "(objectClass=".$array['useroc'].")"; |
---|
500 | if (isset($array['groupoc'])) |
---|
501 | $array['groupfilter'] = "(objectClass=".$array['groupoc'].")"; |
---|
502 | if (isset($array['scope'])) |
---|
503 | $array['userscope'] = $array['scope']; |
---|
504 | |
---|
505 | return $array; |
---|
506 | } |
---|
507 | |
---|
508 | // }}} |
---|
509 | // {{{ _scope2function() |
---|
510 | |
---|
511 | /** |
---|
512 | * Get search function for scope |
---|
513 | * |
---|
514 | * @param string scope |
---|
515 | * @return string ldap search function |
---|
516 | */ |
---|
517 | function _scope2function($scope) |
---|
518 | { |
---|
519 | switch($scope) { |
---|
520 | case 'one': |
---|
521 | $function = 'ldap_list'; |
---|
522 | break; |
---|
523 | case 'base': |
---|
524 | $function = 'ldap_read'; |
---|
525 | break; |
---|
526 | default: |
---|
527 | $function = 'ldap_search'; |
---|
528 | break; |
---|
529 | } |
---|
530 | return $function; |
---|
531 | } |
---|
532 | |
---|
533 | // }}} |
---|
534 | // {{{ fetchData() |
---|
535 | |
---|
536 | /** |
---|
537 | * Fetch data from LDAP server |
---|
538 | * |
---|
539 | * Searches the LDAP server for the given username/password |
---|
540 | * combination. Escapes all LDAP meta characters in username |
---|
541 | * before performing the query. |
---|
542 | * |
---|
543 | * @param string Username |
---|
544 | * @param string Password |
---|
545 | * @return boolean |
---|
546 | */ |
---|
547 | function fetchData($username, $password) |
---|
548 | { |
---|
549 | $this->log('Auth_Container_LDAP::fetchData() called.', AUTH_LOG_DEBUG); |
---|
550 | $err = $this->_prepare(); |
---|
551 | if ($err !== true) { |
---|
552 | return PEAR::raiseError($err->getMessage(), $err->getCode()); |
---|
553 | } |
---|
554 | |
---|
555 | $err = $this->_getBaseDN(); |
---|
556 | if ($err !== true) { |
---|
557 | return PEAR::raiseError($err->getMessage(), $err->getCode()); |
---|
558 | } |
---|
559 | |
---|
560 | // UTF8 Encode username for LDAPv3 |
---|
561 | if (@ldap_get_option($this->conn_id, LDAP_OPT_PROTOCOL_VERSION, $ver) && $ver == 3) { |
---|
562 | $this->log('UTF8 encoding username for LDAPv3', AUTH_LOG_DEBUG); |
---|
563 | $username = utf8_encode($username); |
---|
564 | } |
---|
565 | |
---|
566 | // make search filter |
---|
567 | $filter = sprintf('(&(%s=%s)%s)', |
---|
568 | $this->options['userattr'], |
---|
569 | $this->_quoteFilterString($username), |
---|
570 | $this->options['userfilter']); |
---|
571 | |
---|
572 | // make search base dn |
---|
573 | $search_basedn = $this->options['userdn']; |
---|
574 | if ($search_basedn != '' && substr($search_basedn, -1) != ',') { |
---|
575 | $search_basedn .= ','; |
---|
576 | } |
---|
577 | $search_basedn .= $this->options['basedn']; |
---|
578 | |
---|
579 | // attributes |
---|
580 | $searchAttributes = $this->options['attributes']; |
---|
581 | |
---|
582 | // make functions params array |
---|
583 | $func_params = array($this->conn_id, $search_basedn, $filter, $searchAttributes); |
---|
584 | |
---|
585 | // search function to use |
---|
586 | $func_name = $this->_scope2function($this->options['userscope']); |
---|
587 | |
---|
588 | $this->log("Searching with $func_name and filter $filter in $search_basedn", AUTH_LOG_DEBUG); |
---|
589 | |
---|
590 | // search |
---|
591 | if (($result_id = @call_user_func_array($func_name, $func_params)) === false) { |
---|
592 | $this->log('User not found', AUTH_LOG_DEBUG); |
---|
593 | } elseif (@ldap_count_entries($this->conn_id, $result_id) >= 1) { // did we get some possible results? |
---|
594 | |
---|
595 | $this->log('User(s) found', AUTH_LOG_DEBUG); |
---|
596 | |
---|
597 | $first = true; |
---|
598 | $entry_id = null; |
---|
599 | |
---|
600 | do { |
---|
601 | |
---|
602 | // then get the user dn |
---|
603 | if ($first) { |
---|
604 | $entry_id = @ldap_first_entry($this->conn_id, $result_id); |
---|
605 | $first = false; |
---|
606 | } else { |
---|
607 | $entry_id = @ldap_next_entry($this->conn_id, $entry_id); |
---|
608 | if ($entry_id === false) |
---|
609 | break; |
---|
610 | } |
---|
611 | $user_dn = @ldap_get_dn($this->conn_id, $entry_id); |
---|
612 | |
---|
613 | // as the dn is not fetched as an attribute, we save it anyway |
---|
614 | if (is_array($searchAttributes) && in_array('dn', $searchAttributes)) { |
---|
615 | $this->log('Saving DN to AuthData', AUTH_LOG_DEBUG); |
---|
616 | $this->_auth_obj->setAuthData('dn', $user_dn); |
---|
617 | } |
---|
618 | |
---|
619 | // fetch attributes |
---|
620 | if ($attributes = @ldap_get_attributes($this->conn_id, $entry_id)) { |
---|
621 | |
---|
622 | if (is_array($attributes) && isset($attributes['count']) && |
---|
623 | $attributes['count'] > 0) { |
---|
624 | |
---|
625 | // ldap_get_attributes() returns a specific multi dimensional array |
---|
626 | // format containing all the attributes and where each array starts |
---|
627 | // with a 'count' element providing the number of attributes in the |
---|
628 | // entry, or the number of values for attribute. For compatibility |
---|
629 | // reasons, it remains the default format returned by LDAP container |
---|
630 | // setAuthData(). |
---|
631 | // The code below optionally returns attributes in another format, |
---|
632 | // more compliant with other Auth containers, where each attribute |
---|
633 | // element are directly set in the 'authData' list. This option is |
---|
634 | // enabled by setting 'attrformat' to |
---|
635 | // 'AUTH' in the 'options' array. |
---|
636 | // eg. $this->options['attrformat'] = 'AUTH' |
---|
637 | |
---|
638 | if ( strtoupper($this->options['attrformat']) == 'AUTH' ) { |
---|
639 | $this->log('Saving attributes to Auth data in AUTH format', AUTH_LOG_DEBUG); |
---|
640 | unset ($attributes['count']); |
---|
641 | foreach ($attributes as $attributeName => $attributeValue ) { |
---|
642 | if (is_int($attributeName)) continue; |
---|
643 | if (is_array($attributeValue) && isset($attributeValue['count'])) { |
---|
644 | unset ($attributeValue['count']); |
---|
645 | } |
---|
646 | if (count($attributeValue)<=1) $attributeValue = $attributeValue[0]; |
---|
647 | $this->log('Storing additional field: '.$attributeName, AUTH_LOG_DEBUG); |
---|
648 | $this->_auth_obj->setAuthData($attributeName, $attributeValue); |
---|
649 | } |
---|
650 | } |
---|
651 | else |
---|
652 | { |
---|
653 | $this->log('Saving attributes to Auth data in LDAP format', AUTH_LOG_DEBUG); |
---|
654 | $this->_auth_obj->setAuthData('attributes', $attributes); |
---|
655 | } |
---|
656 | } |
---|
657 | } |
---|
658 | @ldap_free_result($result_id); |
---|
659 | |
---|
660 | // need to catch an empty password as openldap seems to return TRUE |
---|
661 | // if anonymous binding is allowed |
---|
662 | if ($password != "") { |
---|
663 | $this->log("Bind as $user_dn", AUTH_LOG_DEBUG); |
---|
664 | |
---|
665 | // try binding as this user with the supplied password |
---|
666 | if (@ldap_bind($this->conn_id, $user_dn, $password)) { |
---|
667 | $this->log('Bind successful', AUTH_LOG_DEBUG); |
---|
668 | |
---|
669 | // check group if appropiate |
---|
670 | if (strlen($this->options['group'])) { |
---|
671 | // decide whether memberattr value is a dn or the username |
---|
672 | $this->log('Checking group membership', AUTH_LOG_DEBUG); |
---|
673 | $return = $this->checkGroup(($this->options['memberisdn']) ? $user_dn : $username); |
---|
674 | $this->_disconnect(); |
---|
675 | return $return; |
---|
676 | } else { |
---|
677 | $this->log('Authenticated', AUTH_LOG_DEBUG); |
---|
678 | $this->_disconnect(); |
---|
679 | return true; // user authenticated |
---|
680 | } // checkGroup |
---|
681 | } // bind |
---|
682 | } // non-empty password |
---|
683 | } while ($this->options['try_all'] == true); // interate through entries |
---|
684 | } // get results |
---|
685 | // default |
---|
686 | $this->log('NOT authenticated!', AUTH_LOG_DEBUG); |
---|
687 | $this->_disconnect(); |
---|
688 | return false; |
---|
689 | } |
---|
690 | |
---|
691 | // }}} |
---|
692 | // {{{ checkGroup() |
---|
693 | |
---|
694 | /** |
---|
695 | * Validate group membership |
---|
696 | * |
---|
697 | * Searches the LDAP server for group membership of the |
---|
698 | * supplied username. Quotes all LDAP filter meta characters in |
---|
699 | * the user name before querying the LDAP server. |
---|
700 | * |
---|
701 | * @param string Distinguished Name of the authenticated User |
---|
702 | * @return boolean |
---|
703 | */ |
---|
704 | function checkGroup($user) |
---|
705 | { |
---|
706 | $this->log('Auth_Container_LDAP::checkGroup() called.', AUTH_LOG_DEBUG); |
---|
707 | $err = $this->_prepare(); |
---|
708 | if ($err !== true) { |
---|
709 | return PEAR::raiseError($err->getMessage(), $err->getCode()); |
---|
710 | } |
---|
711 | |
---|
712 | // make filter |
---|
713 | $filter = sprintf('(&(%s=%s)(%s=%s)%s)', |
---|
714 | $this->options['groupattr'], |
---|
715 | $this->options['group'], |
---|
716 | $this->options['memberattr'], |
---|
717 | $this->_quoteFilterString($user), |
---|
718 | $this->options['groupfilter']); |
---|
719 | |
---|
720 | // make search base dn |
---|
721 | $search_basedn = $this->options['groupdn']; |
---|
722 | if ($search_basedn != '' && substr($search_basedn, -1) != ',') { |
---|
723 | $search_basedn .= ','; |
---|
724 | } |
---|
725 | $search_basedn .= $this->options['basedn']; |
---|
726 | |
---|
727 | $func_params = array($this->conn_id, $search_basedn, $filter, |
---|
728 | array($this->options['memberattr'])); |
---|
729 | $func_name = $this->_scope2function($this->options['groupscope']); |
---|
730 | |
---|
731 | $this->log("Searching with $func_name and filter $filter in $search_basedn", AUTH_LOG_DEBUG); |
---|
732 | |
---|
733 | // search |
---|
734 | if (($result_id = @call_user_func_array($func_name, $func_params)) != false) { |
---|
735 | if (@ldap_count_entries($this->conn_id, $result_id) == 1) { |
---|
736 | @ldap_free_result($result_id); |
---|
737 | $this->log('User is member of group', AUTH_LOG_DEBUG); |
---|
738 | return true; |
---|
739 | } |
---|
740 | } |
---|
741 | // default |
---|
742 | $this->log('User is NOT member of group', AUTH_LOG_DEBUG); |
---|
743 | return false; |
---|
744 | } |
---|
745 | |
---|
746 | // }}} |
---|
747 | // {{{ _quoteFilterString() |
---|
748 | |
---|
749 | /** |
---|
750 | * Escapes LDAP filter special characters as defined in RFC 2254. |
---|
751 | * |
---|
752 | * @access private |
---|
753 | * @param string Filter String |
---|
754 | */ |
---|
755 | function _quoteFilterString($filter_str) |
---|
756 | { |
---|
757 | $metas = array( '\\', '*', '(', ')', "\x00"); |
---|
758 | $quoted_metas = array('\\\\', '\*', '\(', '\)', "\\\x00"); |
---|
759 | return str_replace($metas, $quoted_metas, $filter_str); |
---|
760 | } |
---|
761 | |
---|
762 | // }}} |
---|
763 | |
---|
764 | } |
---|
765 | |
---|
766 | ?> |
---|