Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401]

   1  <?php
   2  /**
   3   * LDAP driver.
   4   *
   5   * Provides a subset of ADOdb commands, allowing read-only access to an LDAP database.
   6   *
   7   * This file is part of ADOdb, a Database Abstraction Layer library for PHP.
   8   *
   9   * @package ADOdb
  10   * @link https://adodb.org Project's web site and documentation
  11   * @link https://github.com/ADOdb/ADOdb Source code and issue tracker
  12   *
  13   * The ADOdb Library is dual-licensed, released under both the BSD 3-Clause
  14   * and the GNU Lesser General Public Licence (LGPL) v2.1 or, at your option,
  15   * any later version. This means you can use it in proprietary products.
  16   * See the LICENSE.md file distributed with this source code for details.
  17   * @license BSD-3-Clause
  18   * @license LGPL-2.1-or-later
  19   *
  20   * @copyright 2000-2013 John Lim
  21   * @copyright 2014 Damien Regad, Mark Newnham and the ADOdb community
  22   * @author Joshua Eldridge <joshuae74@hotmail.com>
  23   */
  24  
  25  // security - hide paths
  26  if (!defined('ADODB_DIR')) die();
  27  
  28  if (!defined('LDAP_ASSOC')) {
  29  	 define('LDAP_ASSOC',ADODB_FETCH_ASSOC);
  30  	 define('LDAP_NUM',ADODB_FETCH_NUM);
  31  	 define('LDAP_BOTH',ADODB_FETCH_BOTH);
  32  }
  33  
  34  class ADODB_ldap extends ADOConnection {
  35  	 var $databaseType = 'ldap';
  36  	 var $dataProvider = 'ldap';
  37  
  38  	 # Connection information
  39  	 var $username = false;
  40  	 var $password = false;
  41  
  42  	 # Used during searches
  43  	 var $filter;
  44  	 var $dn;
  45  	 var $version;
  46  	 var $port = 389;
  47  
  48  	 # Options configuration information
  49  	 var $LDAP_CONNECT_OPTIONS;
  50  
  51  	 # error on binding, eg. "Binding: invalid credentials"
  52  	 var $_bind_errmsg = "Binding: %s";
  53  
  54  	 // returns true or false
  55  
  56  	function _connect( $host, $username, $password, $ldapbase)
  57  	 {
  58  	 	 global $LDAP_CONNECT_OPTIONS;
  59  
  60  	 	 if ( !function_exists( 'ldap_connect' ) ) return null;
  61  
  62  	 	 if (strpos($host,'ldap://') === 0 || strpos($host,'ldaps://') === 0) {
  63  	 	 	 $this->_connectionID = @ldap_connect($host);
  64  	 	 } else {
  65  	 	 	 $conn_info = array( $host,$this->port);
  66  
  67  	 	 	 if ( strstr( $host, ':' ) ) {
  68  	 	 	 	 $conn_info = explode( ':', $host );
  69  	 	 	 }
  70  
  71  	 	 	 $this->_connectionID = @ldap_connect( $conn_info[0], $conn_info[1] );
  72  	 	 }
  73  	 	 if (!$this->_connectionID) {
  74  	 	 	 $e = 'Could not connect to ' . $conn_info[0];
  75  	 	 	 $this->_errorMsg = $e;
  76  	 	 	 if ($this->debug) ADOConnection::outp($e);
  77  	 	 	 return false;
  78  	 	 }
  79  	 	 if( count( $LDAP_CONNECT_OPTIONS ) > 0 ) {
  80  	 	 	 $this->_inject_bind_options( $LDAP_CONNECT_OPTIONS );
  81  	 	 }
  82  
  83  	 	 if ($username) {
  84  	 	 	 $bind = @ldap_bind( $this->_connectionID, $username, $password );
  85  	 	 } else {
  86  	 	 	 $username = 'anonymous';
  87  	 	 	 $bind = @ldap_bind( $this->_connectionID );
  88  	 	 }
  89  
  90  	 	 if (!$bind) {
  91  	 	 	 $e = sprintf($this->_bind_errmsg,ldap_error($this->_connectionID));
  92  	 	 	 $this->_errorMsg = $e;
  93  	 	 	 if ($this->debug) ADOConnection::outp($e);
  94  	 	 	 return false;
  95  	 	 }
  96  	 	 $this->_errorMsg = '';
  97  	 	 $this->database = $ldapbase;
  98  	 	 return $this->_connectionID;
  99  	 }
 100  
 101  /*
 102  	 Valid Domain Values for LDAP Options:
 103  
 104  	 LDAP_OPT_DEREF (integer)
 105  	 LDAP_OPT_SIZELIMIT (integer)
 106  	 LDAP_OPT_TIMELIMIT (integer)
 107  	 LDAP_OPT_PROTOCOL_VERSION (integer)
 108  	 LDAP_OPT_ERROR_NUMBER (integer)
 109  	 LDAP_OPT_REFERRALS (boolean)
 110  	 LDAP_OPT_RESTART (boolean)
 111  	 LDAP_OPT_HOST_NAME (string)
 112  	 LDAP_OPT_ERROR_STRING (string)
 113  	 LDAP_OPT_MATCHED_DN (string)
 114  	 LDAP_OPT_SERVER_CONTROLS (array)
 115  	 LDAP_OPT_CLIENT_CONTROLS (array)
 116  
 117  	 Make sure to set this BEFORE calling Connect()
 118  
 119  	 Example:
 120  
 121  	 $LDAP_CONNECT_OPTIONS = Array(
 122  	 	 Array (
 123  	 	 	 "OPTION_NAME"=>LDAP_OPT_DEREF,
 124  	 	 	 "OPTION_VALUE"=>2
 125  	 	 ),
 126  	 	 Array (
 127  	 	 	 "OPTION_NAME"=>LDAP_OPT_SIZELIMIT,
 128  	 	 	 "OPTION_VALUE"=>100
 129  	 	 ),
 130  	 	 Array (
 131  	 	 	 "OPTION_NAME"=>LDAP_OPT_TIMELIMIT,
 132  	 	 	 "OPTION_VALUE"=>30
 133  	 	 ),
 134  	 	 Array (
 135  	 	 	 "OPTION_NAME"=>LDAP_OPT_PROTOCOL_VERSION,
 136  	 	 	 "OPTION_VALUE"=>3
 137  	 	 ),
 138  	 	 Array (
 139  	 	 	 "OPTION_NAME"=>LDAP_OPT_ERROR_NUMBER,
 140  	 	 	 "OPTION_VALUE"=>13
 141  	 	 ),
 142  	 	 Array (
 143  	 	 	 "OPTION_NAME"=>LDAP_OPT_REFERRALS,
 144  	 	 	 "OPTION_VALUE"=>FALSE
 145  	 	 ),
 146  	 	 Array (
 147  	 	 	 "OPTION_NAME"=>LDAP_OPT_RESTART,
 148  	 	 	 "OPTION_VALUE"=>FALSE
 149  	 	 )
 150  	 );
 151  */
 152  
 153  	function _inject_bind_options( $options ) {
 154  	 	 foreach( $options as $option ) {
 155  	 	 	 ldap_set_option( $this->_connectionID, $option["OPTION_NAME"], $option["OPTION_VALUE"] )
 156  	 	 	 	 or die( "Unable to set server option: " . $option["OPTION_NAME"] );
 157  	 	 }
 158  	 }
 159  
 160  	 /* returns _queryID or false */
 161  	function _query($sql,$inputarr=false)
 162  	 {
 163  	 	 $rs = @ldap_search( $this->_connectionID, $this->database, $sql );
 164  	 	 $this->_errorMsg = ($rs) ? '' : 'Search error on '.$sql.': '.ldap_error($this->_connectionID);
 165  	 	 return $rs;
 166  	 }
 167  
 168  	function ErrorMsg()
 169  	 {
 170  	 	 return $this->_errorMsg;
 171  	 }
 172  
 173  	function ErrorNo()
 174  	 {
 175  	 	 return @ldap_errno($this->_connectionID);
 176  	 }
 177  
 178  	 /* closes the LDAP connection */
 179  	function _close()
 180  	 {
 181  	 	 @ldap_close( $this->_connectionID );
 182  	 	 $this->_connectionID = false;
 183  	 }
 184  
 185  	function SelectDB($db) {
 186  	 	 $this->database = $db;
 187  	 	 return true;
 188  	 } // SelectDB
 189  
 190  	function ServerInfo()
 191  	 {
 192  	 	 if( !empty( $this->version ) ) {
 193  	 	 	 return $this->version;
 194  	 	 }
 195  
 196  	 	 $version = array();
 197  	 	 /*
 198  	 	 Determines how aliases are handled during search.
 199  	 	 LDAP_DEREF_NEVER (0x00)
 200  	 	 LDAP_DEREF_SEARCHING (0x01)
 201  	 	 LDAP_DEREF_FINDING (0x02)
 202  	 	 LDAP_DEREF_ALWAYS (0x03)
 203  	 	 The LDAP_DEREF_SEARCHING value means aliases are dereferenced during the search but
 204  	 	 not when locating the base object of the search. The LDAP_DEREF_FINDING value means
 205  	 	 aliases are dereferenced when locating the base object but not during the search.
 206  	 	 Default: LDAP_DEREF_NEVER
 207  	 	 */
 208  	 	 ldap_get_option( $this->_connectionID, LDAP_OPT_DEREF, $version['LDAP_OPT_DEREF'] ) ;
 209  	 	 switch ( $version['LDAP_OPT_DEREF'] ) {
 210  	 	 	 case 0:
 211  	 	 	 	 $version['LDAP_OPT_DEREF'] = 'LDAP_DEREF_NEVER';
 212  	 	 	 case 1:
 213  	 	 	 	 $version['LDAP_OPT_DEREF'] = 'LDAP_DEREF_SEARCHING';
 214  	 	 	 case 2:
 215  	 	 	 	 $version['LDAP_OPT_DEREF'] = 'LDAP_DEREF_FINDING';
 216  	 	 	 case 3:
 217  	 	 	 	 $version['LDAP_OPT_DEREF'] = 'LDAP_DEREF_ALWAYS';
 218  	 	 }
 219  
 220  	 	 /*
 221  	 	 A limit on the number of entries to return from a search.
 222  	 	 LDAP_NO_LIMIT (0) means no limit.
 223  	 	 Default: LDAP_NO_LIMIT
 224  	 	 */
 225  	 	 ldap_get_option( $this->_connectionID, LDAP_OPT_SIZELIMIT, $version['LDAP_OPT_SIZELIMIT'] );
 226  	 	 if ( $version['LDAP_OPT_SIZELIMIT'] == 0 ) {
 227  	 	 	 $version['LDAP_OPT_SIZELIMIT'] = 'LDAP_NO_LIMIT';
 228  	 	 }
 229  
 230  	 	 /*
 231  	 	 A limit on the number of seconds to spend on a search.
 232  	 	 LDAP_NO_LIMIT (0) means no limit.
 233  	 	 Default: LDAP_NO_LIMIT
 234  	 	 */
 235  	 	 ldap_get_option( $this->_connectionID, LDAP_OPT_TIMELIMIT, $version['LDAP_OPT_TIMELIMIT'] );
 236  	 	 if ( $version['LDAP_OPT_TIMELIMIT'] == 0 ) {
 237  	 	 	 $version['LDAP_OPT_TIMELIMIT'] = 'LDAP_NO_LIMIT';
 238  	 	 }
 239  
 240  	 	 /*
 241  	 	 Determines whether the LDAP library automatically follows referrals returned by LDAP servers or not.
 242  	 	 LDAP_OPT_ON
 243  	 	 LDAP_OPT_OFF
 244  	 	 Default: ON
 245  	 	 */
 246  	 	 ldap_get_option( $this->_connectionID, LDAP_OPT_REFERRALS, $version['LDAP_OPT_REFERRALS'] );
 247  	 	 if ( $version['LDAP_OPT_REFERRALS'] == 0 ) {
 248  	 	 	 $version['LDAP_OPT_REFERRALS'] = 'LDAP_OPT_OFF';
 249  	 	 } else {
 250  	 	 	 $version['LDAP_OPT_REFERRALS'] = 'LDAP_OPT_ON';
 251  	 	 }
 252  
 253  	 	 /*
 254  	 	 Determines whether LDAP I/O operations are automatically restarted if they abort prematurely.
 255  	 	 LDAP_OPT_ON
 256  	 	 LDAP_OPT_OFF
 257  	 	 Default: OFF
 258  	 	 */
 259  	 	 ldap_get_option( $this->_connectionID, LDAP_OPT_RESTART, $version['LDAP_OPT_RESTART'] );
 260  	 	 if ( $version['LDAP_OPT_RESTART'] == 0 ) {
 261  	 	 	 $version['LDAP_OPT_RESTART'] = 'LDAP_OPT_OFF';
 262  	 	 } else {
 263  	 	 	 $version['LDAP_OPT_RESTART'] = 'LDAP_OPT_ON';
 264  	 	 }
 265  
 266  	 	 /*
 267  	 	 This option indicates the version of the LDAP protocol used when communicating with the primary LDAP server.
 268  	 	 LDAP_VERSION2 (2)
 269  	 	 LDAP_VERSION3 (3)
 270  	 	 Default: LDAP_VERSION2 (2)
 271  	 	 */
 272  	 	 ldap_get_option( $this->_connectionID, LDAP_OPT_PROTOCOL_VERSION, $version['LDAP_OPT_PROTOCOL_VERSION'] );
 273  	 	 if ( $version['LDAP_OPT_PROTOCOL_VERSION'] == 2 ) {
 274  	 	 	 $version['LDAP_OPT_PROTOCOL_VERSION'] = 'LDAP_VERSION2';
 275  	 	 } else {
 276  	 	 	 $version['LDAP_OPT_PROTOCOL_VERSION'] = 'LDAP_VERSION3';
 277  	 	 }
 278  
 279  	 	 /* The host name (or list of hosts) for the primary LDAP server. */
 280  	 	 ldap_get_option( $this->_connectionID, LDAP_OPT_HOST_NAME, $version['LDAP_OPT_HOST_NAME'] );
 281  	 	 ldap_get_option( $this->_connectionID, LDAP_OPT_ERROR_NUMBER, $version['LDAP_OPT_ERROR_NUMBER'] );
 282  	 	 ldap_get_option( $this->_connectionID, LDAP_OPT_ERROR_STRING, $version['LDAP_OPT_ERROR_STRING'] );
 283  	 	 ldap_get_option( $this->_connectionID, LDAP_OPT_MATCHED_DN, $version['LDAP_OPT_MATCHED_DN'] );
 284  
 285  	 	 return $this->version = $version;
 286  	 }
 287  }
 288  
 289  /*--------------------------------------------------------------------------------------
 290  	 Class Name: Recordset
 291  --------------------------------------------------------------------------------------*/
 292  
 293  class ADORecordSet_ldap extends ADORecordSet{
 294  
 295  	 var $databaseType = "ldap";
 296  	 var $canSeek = false;
 297  	 var $_entryID; /* keeps track of the entry resource identifier */
 298  
 299  	function __construct($queryID,$mode=false)
 300  	 {
 301  	 	 if ($mode === false) {
 302  	 	 	 global $ADODB_FETCH_MODE;
 303  	 	 	 $mode = $ADODB_FETCH_MODE;
 304  	 	 }
 305  	 	 switch ($mode)
 306  	 	 {
 307  	 	 case ADODB_FETCH_NUM:
 308  	 	 	 $this->fetchMode = LDAP_NUM;
 309  	 	 	 break;
 310  	 	 case ADODB_FETCH_ASSOC:
 311  	 	 	 $this->fetchMode = LDAP_ASSOC;
 312  	 	 	 break;
 313  	 	 case ADODB_FETCH_DEFAULT:
 314  	 	 case ADODB_FETCH_BOTH:
 315  	 	 default:
 316  	 	 	 $this->fetchMode = LDAP_BOTH;
 317  	 	 	 break;
 318  	 	 }
 319  
 320  	 	 parent::__construct($queryID);
 321  	 }
 322  
 323  	function _initrs()
 324  	 {
 325  	 	 /*
 326  	 	 This could be teaked to respect the $COUNTRECS directive from ADODB
 327  	 	 It's currently being used in the _fetch() function and the
 328  	 	 GetAssoc() function
 329  	 	 */
 330  	 	 $this->_numOfRows = ldap_count_entries( $this->connection->_connectionID, $this->_queryID );
 331  	 }
 332  
 333  	 /*
 334  	 Return whole recordset as a multi-dimensional associative array
 335  	 */
 336  	function GetAssoc($force_array = false, $first2cols = false, $fetchMode = -1)
 337  	 {
 338  	 	 $records = $this->_numOfRows;
 339  	 	 $results = array();
 340  	 	 for ( $i=0; $i < $records; $i++ ) {
 341  	 	 	 foreach ( $this->fields as $k=>$v ) {
 342  	 	 	 	 if ( is_array( $v ) ) {
 343  	 	 	 	 	 if ( $v['count'] == 1 ) {
 344  	 	 	 	 	 	 $results[$i][$k] = $v[0];
 345  	 	 	 	 	 } else {
 346  	 	 	 	 	 	 array_shift( $v );
 347  	 	 	 	 	 	 $results[$i][$k] = $v;
 348  	 	 	 	 	 }
 349  	 	 	 	 }
 350  	 	 	 }
 351  	 	 }
 352  
 353  	 	 return $results;
 354  	 }
 355  
 356  	function GetRowAssoc($upper = ADODB_ASSOC_CASE)
 357  	 {
 358  	 	 $results = array();
 359  	 	 foreach ( $this->fields as $k=>$v ) {
 360  	 	 	 if ( is_array( $v ) ) {
 361  	 	 	 	 if ( $v['count'] == 1 ) {
 362  	 	 	 	 	 $results[$k] = $v[0];
 363  	 	 	 	 } else {
 364  	 	 	 	 	 array_shift( $v );
 365  	 	 	 	 	 $results[$k] = $v;
 366  	 	 	 	 }
 367  	 	 	 }
 368  	 	 }
 369  
 370  	 	 return $results;
 371  	 }
 372  
 373  	function GetRowNums()
 374  	 {
 375  	 	 $results = array();
 376  	 	 foreach ( $this->fields as $k=>$v ) {
 377  	 	 	 static $i = 0;
 378  	 	 	 if (is_array( $v )) {
 379  	 	 	 	 if ( $v['count'] == 1 ) {
 380  	 	 	 	 	 $results[$i] = $v[0];
 381  	 	 	 	 } else {
 382  	 	 	 	 	 array_shift( $v );
 383  	 	 	 	 	 $results[$i] = $v;
 384  	 	 	 	 }
 385  	 	 	 	 $i++;
 386  	 	 	 }
 387  	 	 }
 388  	 	 return $results;
 389  	 }
 390  
 391  	function _fetch()
 392  	 {
 393  	 	 if ( $this->_currentRow >= $this->_numOfRows && $this->_numOfRows >= 0 ) {
 394  	 	 	 return false;
 395  	 	 }
 396  
 397  	 	 if ( $this->_currentRow == 0 ) {
 398  	 	 	 $this->_entryID = ldap_first_entry( $this->connection->_connectionID, $this->_queryID );
 399  	 	 } else {
 400  	 	 	 $this->_entryID = ldap_next_entry( $this->connection->_connectionID, $this->_entryID );
 401  	 	 }
 402  
 403  	 	 $this->fields = ldap_get_attributes( $this->connection->_connectionID, $this->_entryID );
 404  	 	 $this->_numOfFields = $this->fields['count'];
 405  
 406  	 	 switch ( $this->fetchMode ) {
 407  
 408  	 	 	 case LDAP_ASSOC:
 409  	 	 	 	 $this->fields = $this->GetRowAssoc();
 410  	 	 	 	 break;
 411  
 412  	 	 	 case LDAP_NUM:
 413  	 	 	 	 $this->fields = array_merge($this->GetRowNums(),$this->GetRowAssoc());
 414  	 	 	 	 break;
 415  
 416  	 	 	 case LDAP_BOTH:
 417  	 	 	 default:
 418  	 	 	 	 $this->fields = $this->GetRowNums();
 419  	 	 	 	 break;
 420  	 	 }
 421  
 422  	 	 return is_array( $this->fields );
 423  	 }
 424  
 425  	function _close() {
 426  	 	 @ldap_free_result( $this->_queryID );
 427  	 	 $this->_queryID = false;
 428  	 }
 429  
 430  }