1173 |
jp_milcent |
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
|
1713 |
jp_milcent |
17 |
* @author Jan Wagner <wagner@netsols.de>
|
1173 |
jp_milcent |
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
|
1713 |
jp_milcent |
22 |
* @version CVS: $Id: LDAP.php,v 1.3 2007-11-19 15:11:00 jp_milcent Exp $
|
1173 |
jp_milcent |
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
|
1713 |
jp_milcent |
106 |
* start_tls: enable/disable the use of START_TLS encrypted connection
|
1173 |
jp_milcent |
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.
|
1713 |
jp_milcent |
180 |
*
|
1173 |
jp_milcent |
181 |
* LDAP Referrals need to be set to false for AD to work sometimes.
|
|
|
182 |
*
|
1713 |
jp_milcent |
183 |
* Example a3 shows a full blown and tested example for connection to
|
1173 |
jp_milcent |
184 |
* Windows 2000 Active Directory with group mebership checking
|
|
|
185 |
*
|
1713 |
jp_milcent |
186 |
* Note also that if you want an encrypted connection to an MS LDAP
|
|
|
187 |
* server, then, on your webserver, you must specify
|
1173 |
jp_milcent |
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
|
1713 |
jp_milcent |
200 |
* @version Release: 1.5.4 File: $Revision: 1.3 $
|
1173 |
jp_milcent |
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 |
{
|
1713 |
jp_milcent |
278 |
$this->log('Auth_Container_LDAP::_connect() called.', AUTH_LOG_DEBUG);
|
1173 |
jp_milcent |
279 |
// connect
|
|
|
280 |
if (isset($this->options['url']) && $this->options['url'] != '') {
|
1713 |
jp_milcent |
281 |
$this->log('Connecting with URL', AUTH_LOG_DEBUG);
|
1173 |
jp_milcent |
282 |
$conn_params = array($this->options['url']);
|
|
|
283 |
} else {
|
1713 |
jp_milcent |
284 |
$this->log('Connecting with host:port', AUTH_LOG_DEBUG);
|
1173 |
jp_milcent |
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) {
|
1713 |
jp_milcent |
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);
|
1173 |
jp_milcent |
291 |
return PEAR::raiseError('Auth_Container_LDAP: Could not connect to server.', 41);
|
|
|
292 |
}
|
1713 |
jp_milcent |
293 |
$this->log('Successfully connected to server', AUTH_LOG_DEBUG);
|
1173 |
jp_milcent |
294 |
|
|
|
295 |
// switch LDAP version
|
|
|
296 |
if (is_numeric($this->options['version']) && $this->options['version'] > 2) {
|
1713 |
jp_milcent |
297 |
$this->log("Switching to LDAP version {$this->options['version']}", AUTH_LOG_DEBUG);
|
1173 |
jp_milcent |
298 |
@ldap_set_option($this->conn_id, LDAP_OPT_PROTOCOL_VERSION, $this->options['version']);
|
1713 |
jp_milcent |
299 |
|
1173 |
jp_milcent |
300 |
// start TLS if available
|
1713 |
jp_milcent |
301 |
if (isset($this->options['start_tls']) && $this->options['start_tls']) {
|
|
|
302 |
$this->log("Starting TLS session", AUTH_LOG_DEBUG);
|
1173 |
jp_milcent |
303 |
if (@ldap_start_tls($this->conn_id) === false) {
|
1713 |
jp_milcent |
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);
|
1173 |
jp_milcent |
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'])) {
|
1713 |
jp_milcent |
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 |
}
|
1173 |
jp_milcent |
318 |
}
|
|
|
319 |
|
|
|
320 |
// bind with credentials or anonymously
|
|
|
321 |
if (strlen($this->options['binddn']) && strlen($this->options['bindpw'])) {
|
1713 |
jp_milcent |
322 |
$this->log('Binding with credentials', AUTH_LOG_DEBUG);
|
1173 |
jp_milcent |
323 |
$bind_params = array($this->conn_id, $this->options['binddn'], $this->options['bindpw']);
|
|
|
324 |
} else {
|
1713 |
jp_milcent |
325 |
$this->log('Binding anonymously', AUTH_LOG_DEBUG);
|
1173 |
jp_milcent |
326 |
$bind_params = array($this->conn_id);
|
|
|
327 |
}
|
|
|
328 |
|
|
|
329 |
// bind for searching
|
|
|
330 |
if ((@call_user_func_array('ldap_bind', $bind_params)) === false) {
|
1713 |
jp_milcent |
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);
|
1173 |
jp_milcent |
333 |
$this->_disconnect();
|
|
|
334 |
return PEAR::raiseError("Auth_Container_LDAP: Could not bind to LDAP server.", 41);
|
|
|
335 |
}
|
1713 |
jp_milcent |
336 |
$this->log('Binding was successful', AUTH_LOG_DEBUG);
|
1173 |
jp_milcent |
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 |
{
|
1713 |
jp_milcent |
351 |
$this->log('Auth_Container_LDAP::_disconnect() called.', AUTH_LOG_DEBUG);
|
1173 |
jp_milcent |
352 |
if ($this->_isValidLink()) {
|
1713 |
jp_milcent |
353 |
$this->log('disconnecting from server');
|
1173 |
jp_milcent |
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 |
{
|
1713 |
jp_milcent |
368 |
$this->log('Auth_Container_LDAP::_getBaseDN() called.', AUTH_LOG_DEBUG);
|
1173 |
jp_milcent |
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()) {
|
1713 |
jp_milcent |
375 |
$this->log("basedn not set, searching via namingContexts.", AUTH_LOG_DEBUG);
|
1173 |
jp_milcent |
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 |
|
1713 |
jp_milcent |
381 |
$this->log("got result for namingContexts", AUTH_LOG_DEBUG);
|
1173 |
jp_milcent |
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 != "") {
|
1713 |
jp_milcent |
388 |
$this->log("result for namingContexts was $basedn", AUTH_LOG_DEBUG);
|
1173 |
jp_milcent |
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
|
1713 |
jp_milcent |
490 |
*
|
1173 |
jp_milcent |
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 |
{
|
1713 |
jp_milcent |
549 |
$this->log('Auth_Container_LDAP::fetchData() called.', AUTH_LOG_DEBUG);
|
1173 |
jp_milcent |
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) {
|
1713 |
jp_milcent |
562 |
$this->log('UTF8 encoding username for LDAPv3', AUTH_LOG_DEBUG);
|
1173 |
jp_milcent |
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
|
1713 |
jp_milcent |
580 |
$searchAttributes = $this->options['attributes'];
|
1173 |
jp_milcent |
581 |
|
|
|
582 |
// make functions params array
|
1713 |
jp_milcent |
583 |
$func_params = array($this->conn_id, $search_basedn, $filter, $searchAttributes);
|
1173 |
jp_milcent |
584 |
|
|
|
585 |
// search function to use
|
|
|
586 |
$func_name = $this->_scope2function($this->options['userscope']);
|
|
|
587 |
|
1713 |
jp_milcent |
588 |
$this->log("Searching with $func_name and filter $filter in $search_basedn", AUTH_LOG_DEBUG);
|
1173 |
jp_milcent |
589 |
|
|
|
590 |
// search
|
|
|
591 |
if (($result_id = @call_user_func_array($func_name, $func_params)) === false) {
|
1713 |
jp_milcent |
592 |
$this->log('User not found', AUTH_LOG_DEBUG);
|
1173 |
jp_milcent |
593 |
} elseif (@ldap_count_entries($this->conn_id, $result_id) >= 1) { // did we get some possible results?
|
|
|
594 |
|
1713 |
jp_milcent |
595 |
$this->log('User(s) found', AUTH_LOG_DEBUG);
|
1173 |
jp_milcent |
596 |
|
|
|
597 |
$first = true;
|
|
|
598 |
$entry_id = null;
|
|
|
599 |
|
|
|
600 |
do {
|
1713 |
jp_milcent |
601 |
|
1173 |
jp_milcent |
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
|
1713 |
jp_milcent |
614 |
if (is_array($searchAttributes) && in_array('dn', $searchAttributes)) {
|
|
|
615 |
$this->log('Saving DN to AuthData', AUTH_LOG_DEBUG);
|
1173 |
jp_milcent |
616 |
$this->_auth_obj->setAuthData('dn', $user_dn);
|
|
|
617 |
}
|
1713 |
jp_milcent |
618 |
|
1173 |
jp_milcent |
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' ) {
|
1713 |
jp_milcent |
639 |
$this->log('Saving attributes to Auth data in AUTH format', AUTH_LOG_DEBUG);
|
1173 |
jp_milcent |
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];
|
1713 |
jp_milcent |
647 |
$this->log('Storing additional field: '.$attributeName, AUTH_LOG_DEBUG);
|
1173 |
jp_milcent |
648 |
$this->_auth_obj->setAuthData($attributeName, $attributeValue);
|
|
|
649 |
}
|
|
|
650 |
}
|
|
|
651 |
else
|
|
|
652 |
{
|
1713 |
jp_milcent |
653 |
$this->log('Saving attributes to Auth data in LDAP format', AUTH_LOG_DEBUG);
|
1173 |
jp_milcent |
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 != "") {
|
1713 |
jp_milcent |
663 |
$this->log("Bind as $user_dn", AUTH_LOG_DEBUG);
|
1173 |
jp_milcent |
664 |
|
|
|
665 |
// try binding as this user with the supplied password
|
|
|
666 |
if (@ldap_bind($this->conn_id, $user_dn, $password)) {
|
1713 |
jp_milcent |
667 |
$this->log('Bind successful', AUTH_LOG_DEBUG);
|
1173 |
jp_milcent |
668 |
|
|
|
669 |
// check group if appropiate
|
|
|
670 |
if (strlen($this->options['group'])) {
|
|
|
671 |
// decide whether memberattr value is a dn or the username
|
1713 |
jp_milcent |
672 |
$this->log('Checking group membership', AUTH_LOG_DEBUG);
|
1173 |
jp_milcent |
673 |
$return = $this->checkGroup(($this->options['memberisdn']) ? $user_dn : $username);
|
|
|
674 |
$this->_disconnect();
|
|
|
675 |
return $return;
|
|
|
676 |
} else {
|
1713 |
jp_milcent |
677 |
$this->log('Authenticated', AUTH_LOG_DEBUG);
|
1173 |
jp_milcent |
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
|
1713 |
jp_milcent |
686 |
$this->log('NOT authenticated!', AUTH_LOG_DEBUG);
|
1173 |
jp_milcent |
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 |
{
|
1713 |
jp_milcent |
706 |
$this->log('Auth_Container_LDAP::checkGroup() called.', AUTH_LOG_DEBUG);
|
1173 |
jp_milcent |
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 |
|
1713 |
jp_milcent |
731 |
$this->log("Searching with $func_name and filter $filter in $search_basedn", AUTH_LOG_DEBUG);
|
1173 |
jp_milcent |
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);
|
1713 |
jp_milcent |
737 |
$this->log('User is member of group', AUTH_LOG_DEBUG);
|
1173 |
jp_milcent |
738 |
return true;
|
|
|
739 |
}
|
|
|
740 |
}
|
|
|
741 |
// default
|
1713 |
jp_milcent |
742 |
$this->log('User is NOT member of group', AUTH_LOG_DEBUG);
|
1173 |
jp_milcent |
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 |
?>
|