| 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 [email protected] so we can mail you a copy immediately. |
|---|
| 14 | * |
|---|
| 15 | * @category Authentication |
|---|
| 16 | * @package Auth |
|---|
| 17 | * @author Jan Wagner <[email protected]> |
|---|
| 18 | * @author Adam Ashley <[email protected]> |
|---|
| 19 | * @author Hugues Peeters <[email protected]> |
|---|
| 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 8713 2006-12-01 05:08:34Z kakinaka $ |
|---|
| 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 <[email protected]> |
|---|
| 196 | * @author Adam Ashley <[email protected]> |
|---|
| 197 | * @author Hugues Peeters <[email protected]> |
|---|
| 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 <[email protected]> |
|---|
| 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 | ?> |
|---|