[ Index ]

PHP Cross Reference of Moodle 1.9.3 [Build 15-Oct-2008]

title

Body

[close]

/auth/ldap/ -> auth.php (source)

   1  <?php
   2  
   3  /**
   4   * @author Martin Dougiamas
   5   * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
   6   * @package moodle multiauth
   7   *
   8   * Authentication Plugin: LDAP Authentication
   9   *
  10   * Authentication using LDAP (Lightweight Directory Access Protocol).
  11   *
  12   * 2006-08-28  File created.
  13   */
  14  
  15  if (!defined('MOODLE_INTERNAL')) {
  16      die('Direct access to this script is forbidden.');    ///  It must be included from a Moodle page
  17  }
  18  
  19  // See http://support.microsoft.com/kb/305144 to interprete these values.
  20  if (!defined('AUTH_AD_ACCOUNTDISABLE')) {
  21      define('AUTH_AD_ACCOUNTDISABLE', 0x0002);
  22  }
  23  if (!defined('AUTH_AD_NORMAL_ACCOUNT')) {
  24      define('AUTH_AD_NORMAL_ACCOUNT', 0x0200);
  25  }
  26  if (!defined('AUTH_NTLMTIMEOUT')) {  // timewindow for the NTLM SSO process, in secs...
  27      define('AUTH_NTLMTIMEOUT', 10);
  28  }
  29  
  30  
  31  require_once($CFG->libdir.'/authlib.php');
  32  
  33  /**
  34   * LDAP authentication plugin.
  35   */
  36  class auth_plugin_ldap extends auth_plugin_base {
  37  
  38      /**
  39       * Constructor with initialisation.
  40       */
  41      function auth_plugin_ldap() {
  42          $this->authtype = 'ldap';
  43          $this->config = get_config('auth/ldap');
  44          if (empty($this->config->ldapencoding)) {
  45              $this->config->ldapencoding = 'utf-8';
  46          }
  47          if (empty($this->config->user_type)) {
  48              $this->config->user_type = 'default';
  49          }
  50  
  51          $default = $this->ldap_getdefaults();
  52  
  53          //use defaults if values not given
  54          foreach ($default as $key => $value) {
  55              // watch out - 0, false are correct values too
  56              if (!isset($this->config->{$key}) or $this->config->{$key} == '') {
  57                  $this->config->{$key} = $value[$this->config->user_type];
  58              }
  59          }
  60  
  61          // Hack prefix to objectclass
  62          if (empty($this->config->objectclass)) {
  63              // Can't send empty filter
  64              $this->config->objectclass='(objectClass=*)';
  65          } else if (stripos($this->config->objectclass, 'objectClass=') === 0) {
  66              // Value is 'objectClass=some-string-here', so just add ()
  67              // around the value (filter _must_ have them).
  68              $this->config->objectclass = '('.$this->config->objectclass.')';
  69          } else if (stripos($this->config->objectclass, '(') !== 0) {
  70              // Value is 'some-string-not-starting-with-left-parentheses',
  71              // which is assumed to be the objectClass matching value.
  72              // So build a valid filter with it.
  73              $this->config->objectclass = '(objectClass='.$this->config->objectclass.')';
  74          } else {
  75              // There is an additional possible value
  76              // '(some-string-here)', that can be used to specify any
  77              // valid filter string, to select subsets of users based
  78              // on any criteria. For example, we could select the users
  79              // whose objectClass is 'user' and have the
  80              // 'enabledMoodleUser' attribute, with something like:
  81              //
  82              //   (&(objectClass=user)(enabledMoodleUser=1))
  83              //
  84              // This is only used in the functions that deal with the
  85              // whole potential set of users (currently sync_users()
  86              // and get_user_list() only).
  87              //
  88              // In this particular case we don't need to do anything,
  89              // so leave $this->config->objectclass as is.
  90          }
  91  
  92      }
  93  
  94      /**
  95       * Returns true if the username and password work and false if they are
  96       * wrong or don't exist.
  97       *
  98       * @param string $username The username (with system magic quotes)
  99       * @param string $password The password (with system magic quotes)
 100       *
 101       * @return bool Authentication success or failure.
 102       */
 103      function user_login($username, $password) {
 104          if (! function_exists('ldap_bind')) {
 105              print_error('auth_ldapnotinstalled','auth');
 106              return false;
 107          }
 108  
 109          if (!$username or !$password) {    // Don't allow blank usernames or passwords
 110              return false;
 111          }
 112  
 113          $textlib = textlib_get_instance();
 114          $extusername = $textlib->convert(stripslashes($username), 'utf-8', $this->config->ldapencoding);
 115          $extpassword = $textlib->convert(stripslashes($password), 'utf-8', $this->config->ldapencoding);
 116  
 117          //
 118          // Before we connect to LDAP, check if this is an AD SSO login
 119          // if we succeed in this block, we'll return success early.
 120          //
 121          $key = sesskey();
 122          if (!empty($this->config->ntlmsso_enabled) && $key === $password) {
 123              $cf = get_cache_flags('auth/ldap/ntlmsess');
 124              // We only get the cache flag if we retrieve it before
 125              // it expires (AUTH_NTLMTIMEOUT seconds).
 126              if (!isset($cf[$key]) || $cf[$key] === '') {
 127                  return false;
 128              }
 129  
 130              $sessusername = $cf[$key];
 131              if ($username === $sessusername) {
 132                  unset($sessusername);
 133                  unset($cf);
 134  
 135                  // Check that the user is inside one of the configured LDAP contexts
 136                  $validuser = false;
 137                  $ldapconnection = $this->ldap_connect();
 138                  if ($ldapconnection) {
 139                      // if the user is not inside the configured contexts,
 140                      // ldap_find_userdn returns false.
 141                      if ($this->ldap_find_userdn($ldapconnection, $extusername)) {
 142                          $validuser = true;
 143                      }
 144                      ldap_close($ldapconnection);
 145                  }
 146  
 147                  // Shortcut here - SSO confirmed
 148                  return $validuser;
 149              }
 150          } // End SSO processing
 151          unset($key);
 152  
 153          $ldapconnection = $this->ldap_connect();
 154          if ($ldapconnection) {
 155              $ldap_user_dn = $this->ldap_find_userdn($ldapconnection, $extusername);
 156  
 157              //if ldap_user_dn is empty, user does not exist
 158              if (!$ldap_user_dn) {
 159                  ldap_close($ldapconnection);
 160                  return false;
 161              }
 162  
 163              // Try to bind with current username and password
 164              $ldap_login = @ldap_bind($ldapconnection, $ldap_user_dn, $extpassword);
 165              ldap_close($ldapconnection);
 166              if ($ldap_login) {
 167                  return true;
 168              }
 169          }
 170          else {
 171              @ldap_close($ldapconnection);
 172              print_error('auth_ldap_noconnect','auth','',$this->config->host_url);
 173          }
 174          return false;
 175      }
 176  
 177      /**
 178       * reads userinformation from ldap and return it in array()
 179       *
 180       * Read user information from external database and returns it as array().
 181       * Function should return all information available. If you are saving
 182       * this information to moodle user-table you should honor syncronization flags
 183       *
 184       * @param string $username username (with system magic quotes)
 185       *
 186       * @return mixed array with no magic quotes or false on error
 187       */
 188      function get_userinfo($username) {
 189          $textlib = textlib_get_instance();
 190          $extusername = $textlib->convert(stripslashes($username), 'utf-8', $this->config->ldapencoding);
 191  
 192          $ldapconnection = $this->ldap_connect();
 193          $attrmap = $this->ldap_attributes();
 194  
 195          $result = array();
 196          $search_attribs = array();
 197  
 198          foreach ($attrmap as $key=>$values) {
 199              if (!is_array($values)) {
 200                  $values = array($values);
 201              }
 202              foreach ($values as $value) {
 203                  if (!in_array($value, $search_attribs)) {
 204                      array_push($search_attribs, $value);
 205                  }
 206              }
 207          }
 208  
 209          $user_dn = $this->ldap_find_userdn($ldapconnection, $extusername);
 210  
 211          if (!$user_info_result = ldap_read($ldapconnection, $user_dn, $this->config->objectclass, $search_attribs)) {
 212              return false; // error!
 213          }
 214          $user_entry = $this->ldap_get_entries($ldapconnection, $user_info_result);
 215          if (empty($user_entry)) {
 216              return false; // entry not found
 217          }
 218  
 219          foreach ($attrmap as $key=>$values) {
 220              if (!is_array($values)) {
 221                  $values = array($values);
 222              }
 223              $ldapval = NULL;
 224              foreach ($values as $value) {
 225                  if ($value == 'dn') {
 226                      $result[$key] = $user_dn;
 227                  }
 228                  if (!array_key_exists($value, $user_entry[0])) {
 229                      continue; // wrong data mapping!
 230                  }
 231                  if (is_array($user_entry[0][$value])) {
 232                      $newval = $textlib->convert($user_entry[0][$value][0], $this->config->ldapencoding, 'utf-8');
 233                  } else {
 234                      $newval = $textlib->convert($user_entry[0][$value], $this->config->ldapencoding, 'utf-8');
 235                  }
 236                  if (!empty($newval)) { // favour ldap entries that are set
 237                      $ldapval = $newval;
 238                  }
 239              }
 240              if (!is_null($ldapval)) {
 241                  $result[$key] = $ldapval;
 242              }
 243          }
 244  
 245          @ldap_close($ldapconnection);
 246          return $result;
 247      }
 248  
 249      /**
 250       * reads userinformation from ldap and return it in an object
 251       *
 252       * @param string $username username (with system magic quotes)
 253       * @return mixed object or false on error
 254       */
 255      function get_userinfo_asobj($username) {
 256          $user_array = $this->get_userinfo($username);
 257          if ($user_array == false) {
 258              return false; //error or not found
 259          }
 260          $user_array = truncate_userinfo($user_array);
 261          $user = new object();
 262          foreach ($user_array as $key=>$value) {
 263              $user->{$key} = $value;
 264          }
 265          return $user;
 266      }
 267  
 268      /**
 269       * returns all usernames from external database
 270       *
 271       * get_userlist returns all usernames from external database
 272       *
 273       * @return array
 274       */
 275      function get_userlist() {
 276          return $this->ldap_get_userlist("({$this->config->user_attribute}=*)");
 277      }
 278  
 279      /**
 280       * checks if user exists on external db
 281       *
 282       * @param string $username (with system magic quotes)
 283       */
 284      function user_exists($username) {
 285  
 286          $textlib = textlib_get_instance();
 287          $extusername = $textlib->convert(stripslashes($username), 'utf-8', $this->config->ldapencoding);
 288  
 289          //returns true if given username exist on ldap
 290          $users = $this->ldap_get_userlist("({$this->config->user_attribute}=".$this->filter_addslashes($extusername).")");
 291          return count($users);
 292      }
 293  
 294      /**
 295       * Creates a new user on external database.
 296       * By using information in userobject
 297       * Use user_exists to prevent dublicate usernames
 298       *
 299       * @param mixed $userobject  Moodle userobject  (with system magic quotes)
 300       * @param mixed $plainpass   Plaintext password (with system magic quotes)
 301       */
 302      function user_create($userobject, $plainpass) {
 303          $textlib = textlib_get_instance();
 304          $extusername = $textlib->convert(stripslashes($userobject->username), 'utf-8', $this->config->ldapencoding);
 305          $extpassword = $textlib->convert(stripslashes($plainpass), 'utf-8', $this->config->ldapencoding);
 306  
 307          switch ($this->config->passtype) {
 308              case 'md5':
 309                  $extpassword = '{MD5}' . base64_encode(pack('H*', md5($extpassword)));
 310                  break;
 311              case 'sha1':
 312                  $extpassword = '{SHA}' . base64_encode(pack('H*', sha1($extpassword)));
 313                  break;
 314              case 'plaintext':
 315              default:
 316                  break; // plaintext
 317          }
 318  
 319          $ldapconnection = $this->ldap_connect();
 320          $attrmap = $this->ldap_attributes();
 321  
 322          $newuser = array();
 323  
 324          foreach ($attrmap as $key => $values) {
 325              if (!is_array($values)) {
 326                  $values = array($values);
 327              }
 328              foreach ($values as $value) {
 329                  if (!empty($userobject->$key) ) {
 330                      $newuser[$value] = $textlib->convert(stripslashes($userobject->$key), 'utf-8', $this->config->ldapencoding);
 331                  }
 332              }
 333          }
 334  
 335          //Following sets all mandatory and other forced attribute values
 336          //User should be creted as login disabled untill email confirmation is processed
 337          //Feel free to add your user type and send patches to paca@sci.fi to add them
 338          //Moodle distribution
 339  
 340          switch ($this->config->user_type)  {
 341              case 'edir':
 342                  $newuser['objectClass']   = array("inetOrgPerson","organizationalPerson","person","top");
 343                  $newuser['uniqueId']      = $extusername;
 344                  $newuser['logindisabled'] = "TRUE";
 345                  $newuser['userpassword']  = $extpassword;
 346                  $uadd = ldap_add($ldapconnection, $this->config->user_attribute.'="'.$this->ldap_addslashes($userobject->username).','.$this->config->create_context.'"', $newuser);
 347                  break;
 348              case 'ad':
 349                  // User account creation is a two step process with AD. First you
 350                  // create the user object, then you set the password. If you try
 351                  // to set the password while creating the user, the operation
 352                  // fails.
 353  
 354                  // Passwords in Active Directory must be encoded as Unicode
 355                  // strings (UCS-2 Little Endian format) and surrounded with
 356                  // double quotes. See http://support.microsoft.com/?kbid=269190
 357                  if (!function_exists('mb_convert_encoding')) {
 358                      print_error ('auth_ldap_no_mbstring', 'auth');
 359                  }
 360  
 361                  // First create the user account, and mark it as disabled.
 362                  $newuser['objectClass'] = array('top','person','user','organizationalPerson');
 363                  $newuser['sAMAccountName'] = $extusername;
 364                  $newuser['userAccountControl'] = AUTH_AD_NORMAL_ACCOUNT |
 365                                                   AUTH_AD_ACCOUNTDISABLE;
 366                  $userdn = 'cn=' .  $this->ldap_addslashes($extusername) .
 367                            ',' . $this->config->create_context;
 368                  if (!ldap_add($ldapconnection, $userdn, $newuser)) {
 369                      print_error ('auth_ldap_ad_create_req', 'auth');
 370                  }
 371  
 372                  // Now set the password
 373                  unset($newuser);
 374                  $newuser['unicodePwd'] = mb_convert_encoding('"' . $extpassword . '"',
 375                                                               "UCS-2LE", "UTF-8");
 376                  if(!ldap_modify($ldapconnection, $userdn, $newuser)) {
 377                      // Something went wrong: delete the user account and error out
 378                      ldap_delete ($ldapconnection, $userdn);
 379                      print_error ('auth_ldap_ad_create_req', 'auth');
 380                  }
 381                  $uadd = true;
 382                  break;
 383              default:
 384                 print_error('auth_ldap_unsupportedusertype','auth','',$this->config->user_type);
 385          }
 386          ldap_close($ldapconnection);
 387          return $uadd;
 388  
 389      }
 390  
 391      function can_reset_password() {
 392          return !empty($this->config->stdchangepassword);
 393      }
 394  
 395      function can_signup() {
 396          return (!empty($this->config->auth_user_create) and !empty($this->config->create_context));
 397      }
 398  
 399      /**
 400       * Sign up a new user ready for confirmation.
 401       * Password is passed in plaintext.
 402       *
 403       * @param object $user new user object (with system magic quotes)
 404       * @param boolean $notify print notice with link and terminate
 405       */
 406      function user_signup($user, $notify=true) {
 407          global $CFG;
 408          require_once($CFG->dirroot.'/user/profile/lib.php');
 409          
 410          if ($this->user_exists($user->username)) {
 411              print_error('auth_ldap_user_exists', 'auth');
 412          }
 413  
 414          $plainslashedpassword = $user->password;
 415          unset($user->password);
 416  
 417          if (! $this->user_create($user, $plainslashedpassword)) {
 418              print_error('auth_ldap_create_error', 'auth');
 419          }
 420  
 421          if (! ($user->id = insert_record('user', $user)) ) {
 422              print_error('auth_emailnoinsert', 'auth');
 423          }
 424  
 425          /// Save any custom profile field information
 426          profile_save_data($user);
 427  
 428          $this->update_user_record($user->username);
 429          update_internal_user_password($user, $plainslashedpassword);
 430  
 431          $user = get_record('user', 'id', $user->id);
 432          events_trigger('user_created', $user);
 433  
 434          if (! send_confirmation_email($user)) {
 435              print_error('auth_emailnoemail', 'auth');
 436          }
 437  
 438          if ($notify) {
 439              global $CFG;
 440              $emailconfirm = get_string('emailconfirm');
 441              $navlinks = array();
 442              $navlinks[] = array('name' => $emailconfirm, 'link' => null, 'type' => 'misc');
 443              $navigation = build_navigation($navlinks);
 444  
 445              print_header($emailconfirm, $emailconfirm, $navigation);
 446              notice(get_string('emailconfirmsent', '', $user->email), "$CFG->wwwroot/index.php");
 447          } else {
 448              return true;
 449          }
 450      }
 451  
 452      /**
 453       * Returns true if plugin allows confirming of new users.
 454       *
 455       * @return bool
 456       */
 457      function can_confirm() {
 458          return $this->can_signup();
 459      }
 460  
 461      /**
 462       * Confirm the new user as registered.
 463       *
 464       * @param string $username (with system magic quotes)
 465       * @param string $confirmsecret (with system magic quotes)
 466       */
 467      function user_confirm($username, $confirmsecret) {
 468          $user = get_complete_user_data('username', $username);
 469  
 470          if (!empty($user)) {
 471              if ($user->confirmed) {
 472                  return AUTH_CONFIRM_ALREADY;
 473  
 474              } else if ($user->auth != 'ldap') {
 475                  return AUTH_CONFIRM_ERROR;
 476  
 477              } else if ($user->secret == stripslashes($confirmsecret)) {   // They have provided the secret key to get in
 478                  if (!$this->user_activate($username)) {
 479                      return AUTH_CONFIRM_FAIL;
 480                  }
 481                  if (!set_field("user", "confirmed", 1, "id", $user->id)) {
 482                      return AUTH_CONFIRM_FAIL;
 483                  }
 484                  if (!set_field("user", "firstaccess", time(), "id", $user->id)) {
 485                      return AUTH_CONFIRM_FAIL;
 486                  }
 487                  return AUTH_CONFIRM_OK;
 488              }
 489          } else {
 490              return AUTH_CONFIRM_ERROR;
 491          }
 492      }
 493  
 494      /**
 495       * return number of days to user password expires
 496       *
 497       * If userpassword does not expire it should return 0. If password is already expired
 498       * it should return negative value.
 499       *
 500       * @param mixed $username username (with system magic quotes)
 501       * @return integer
 502       */
 503      function password_expire($username) {
 504          $result = 0;
 505  
 506          $textlib = textlib_get_instance();
 507          $extusername = $textlib->convert(stripslashes($username), 'utf-8', $this->config->ldapencoding);
 508  
 509          $ldapconnection = $this->ldap_connect();
 510          $user_dn = $this->ldap_find_userdn($ldapconnection, $extusername);
 511          $search_attribs = array($this->config->expireattr);
 512          $sr = ldap_read($ldapconnection, $user_dn, '(objectClass=*)', $search_attribs);
 513          if ($sr)  {
 514              $info = $this->ldap_get_entries($ldapconnection, $sr);
 515              if (!empty ($info) and !empty($info[0][$this->config->expireattr][0])) {
 516                  $expiretime = $this->ldap_expirationtime2unix($info[0][$this->config->expireattr][0], $ldapconnection, $user_dn);
 517                  if ($expiretime != 0) {
 518                      $now = time();
 519                      if ($expiretime > $now) {
 520                          $result = ceil(($expiretime - $now) / DAYSECS);
 521                      }
 522                      else {
 523                          $result = floor(($expiretime - $now) / DAYSECS);
 524                      }
 525                  }
 526              }
 527          } else {
 528              error_log("ldap: password_expire did't find expiration time.");
 529          }
 530  
 531          //error_log("ldap: password_expire user $user_dn expires in $result days!");
 532          return $result;
 533      }
 534  
 535      /**
 536       * syncronizes user fron external db to moodle user table
 537       *
 538       * Sync is now using username attribute.
 539       *
 540       * Syncing users removes or suspends users that dont exists anymore in external db.
 541       * Creates new users and updates coursecreator status of users.
 542       *
 543       * @param int $bulk_insert_records will insert $bulkinsert_records per insert statement
 544       *                         valid only with $unsafe. increase to a couple thousand for
 545       *                         blinding fast inserts -- but test it: you may hit mysqld's
 546       *                         max_allowed_packet limit.
 547       * @param bool $do_updates will do pull in data updates from ldap if relevant
 548       */
 549      function sync_users ($bulk_insert_records = 1000, $do_updates = true) {
 550  
 551          global $CFG;
 552  
 553          $textlib = textlib_get_instance();
 554  
 555          $droptablesql = array(); /// sql commands to drop the table (because session scope could be a problem for
 556                                   /// some persistent drivers like ODBTP (mssql) or if this function is invoked
 557                                   /// from within a PHP application using persistent connections
 558          $temptable = $CFG->prefix . 'extuser';
 559          $createtemptablesql = '';
 560  
 561          // configure a temp table
 562          print "Configuring temp table\n";
 563          switch (strtolower($CFG->dbfamily)) {
 564              case 'mysql':
 565                  $droptablesql[] = 'DROP TEMPORARY TABLE ' . $temptable; // sql command to drop the table (because session scope could be a problem)
 566                  $createtemptablesql = 'CREATE TEMPORARY TABLE ' . $temptable . ' (username VARCHAR(64), PRIMARY KEY (username)) TYPE=MyISAM';
 567                  break;
 568              case 'postgres':
 569                  $droptablesql[] = 'DROP TABLE ' . $temptable; // sql command to drop the table (because session scope could be a problem)
 570                  $bulk_insert_records = 1; // no support for multiple sets of values
 571                  $createtemptablesql = 'CREATE TEMPORARY TABLE '. $temptable . ' (username VARCHAR(64), PRIMARY KEY (username))';
 572                  break;
 573              case 'mssql':
 574                  $temptable = '#'. $temptable; /// MSSQL temp tables begin with #
 575                  $droptablesql[] = 'DROP TABLE ' . $temptable; // sql command to drop the table (because session scope could be a problem)
 576                  $bulk_insert_records = 1; // no support for multiple sets of values
 577                  $createtemptablesql = 'CREATE TABLE ' . $temptable . ' (username VARCHAR(64), PRIMARY KEY (username))';
 578                  break;
 579              case 'oracle':
 580                  $droptablesql[] = 'TRUNCATE TABLE ' . $temptable; // oracle requires truncate before being able to drop a temp table
 581                  $droptablesql[] = 'DROP TABLE ' . $temptable; // sql command to drop the table (because session scope could be a problem)
 582                  $bulk_insert_records = 1; // no support for multiple sets of values
 583                  $createtemptablesql = 'CREATE GLOBAL TEMPORARY TABLE '.$temptable.' (username VARCHAR(64), PRIMARY KEY (username)) ON COMMIT PRESERVE ROWS';
 584                  break;
 585          }
 586  
 587  
 588          execute_sql_arr($droptablesql, true, false); /// Drop temp table to avoid persistence problems later
 589          echo "Creating temp table $temptable\n";
 590          if(! execute_sql($createtemptablesql, false) ){
 591              print  "Failed to create temporary users table - aborting\n";
 592              exit;
 593          }
 594  
 595          print "Connecting to ldap...\n";
 596          $ldapconnection = $this->ldap_connect();
 597  
 598          if (!$ldapconnection) {
 599              @ldap_close($ldapconnection);
 600              print get_string('auth_ldap_noconnect','auth',$this->config->host_url);
 601              exit;
 602          }
 603  
 604          ////
 605          //// get user's list from ldap to sql in a scalable fashion
 606          ////
 607          // prepare some data we'll need
 608          $filter = '(&('.$this->config->user_attribute.'=*)'.$this->config->objectclass.')';
 609  
 610          $contexts = explode(";",$this->config->contexts);
 611  
 612          if (!empty($this->config->create_context)) {
 613                array_push($contexts, $this->config->create_context);
 614          }
 615  
 616          $fresult = array();
 617          foreach ($contexts as $context) {
 618              $context = trim($context);
 619              if (empty($context)) {
 620                  continue;
 621              }
 622              begin_sql();
 623              if ($this->config->search_sub) {
 624                  //use ldap_search to find first user from subtree
 625                  $ldap_result = ldap_search($ldapconnection, $context,
 626                                             $filter,
 627                                             array($this->config->user_attribute));
 628              } else {
 629                  //search only in this context
 630                  $ldap_result = ldap_list($ldapconnection, $context,
 631                                           $filter,
 632                                           array($this->config->user_attribute));
 633              }
 634  
 635              if ($entry = ldap_first_entry($ldapconnection, $ldap_result)) {
 636                  do {
 637                      $value = ldap_get_values_len($ldapconnection, $entry, $this->config->user_attribute);
 638                      $value = $textlib->convert($value[0], $this->config->ldapencoding, 'utf-8');
 639                      // usernames are __always__ lowercase.
 640                      array_push($fresult, moodle_strtolower($value));
 641                      if (count($fresult) >= $bulk_insert_records) {
 642                          $this->ldap_bulk_insert($fresult, $temptable);
 643                          $fresult = array();
 644                      }
 645                  } while ($entry = ldap_next_entry($ldapconnection, $entry));
 646              }
 647              unset($ldap_result); // free mem
 648  
 649              // insert any remaining users and release mem
 650              if (count($fresult)) {
 651                  $this->ldap_bulk_insert($fresult, $temptable);
 652                  $fresult = array();
 653              }
 654              commit_sql();
 655          }
 656  
 657          /// preserve our user database
 658          /// if the temp table is empty, it probably means that something went wrong, exit
 659          /// so as to avoid mass deletion of users; which is hard to undo
 660          $count = get_record_sql('SELECT COUNT(username) AS count, 1 FROM ' . $temptable);
 661          $count = $count->{'count'};
 662          if ($count < 1) {
 663              print "Did not get any users from LDAP -- error? -- exiting\n";
 664              exit;
 665          } else {
 666              print "Got $count records from LDAP\n\n";
 667          }
 668  
 669  
 670  /// User removal
 671          // find users in DB that aren't in ldap -- to be removed!
 672          // this is still not as scalable (but how often do we mass delete?)
 673          if (!empty($this->config->removeuser)) {
 674              $sql = "SELECT u.id, u.username, u.email, u.auth 
 675                      FROM {$CFG->prefix}user u
 676                          LEFT JOIN $temptable e ON u.username = e.username
 677                      WHERE u.auth='ldap'
 678                          AND u.deleted=0
 679                          AND e.username IS NULL";
 680              $remove_users = get_records_sql($sql);
 681  
 682              if (!empty($remove_users)) {
 683                  print "User entries to remove: ". count($remove_users) . "\n";
 684  
 685                  foreach ($remove_users as $user) {
 686                      if ($this->config->removeuser == 2) {
 687                          if (delete_user($user)) {
 688                              echo "\t"; print_string('auth_dbdeleteuser', 'auth', array($user->username, $user->id)); echo "\n";
 689                          } else {
 690                              echo "\t"; print_string('auth_dbdeleteusererror', 'auth', $user->username); echo "\n";
 691                          }
 692                      } else if ($this->config->removeuser == 1) {
 693                          $updateuser = new object();
 694                          $updateuser->id = $user->id;
 695                          $updateuser->auth = 'nologin';
 696                          if (update_record('user', $updateuser)) {
 697                              echo "\t"; print_string('auth_dbsuspenduser', 'auth', array($user->username, $user->id)); echo "\n";
 698                          } else {
 699                              echo "\t"; print_string('auth_dbsuspendusererror', 'auth', $user->username); echo "\n";
 700                          }
 701                      }
 702                  }
 703              } else {
 704                  print "No user entries to be removed\n";
 705              }
 706              unset($remove_users); // free mem!
 707          }
 708  
 709  /// Revive suspended users
 710          if (!empty($this->config->removeuser) and $this->config->removeuser == 1) {
 711              $sql = "SELECT u.id, u.username
 712                      FROM $temptable e, {$CFG->prefix}user u
 713                      WHERE e.username=u.username
 714                          AND u.auth='nologin'";
 715              $revive_users = get_records_sql($sql);
 716  
 717              if (!empty($revive_users)) {
 718                  print "User entries to be revived: ". count($revive_users) . "\n";
 719  
 720                  begin_sql();
 721                  foreach ($revive_users as $user) {
 722                      $updateuser = new object();
 723                      $updateuser->id = $user->id;
 724                      $updateuser->auth = 'ldap';
 725                      if (update_record('user', $updateuser)) {
 726                          echo "\t"; print_string('auth_dbreviveser', 'auth', array($user->username, $user->id)); echo "\n";
 727                      } else {
 728                          echo "\t"; print_string('auth_dbreviveusererror', 'auth', $user->username); echo "\n";
 729                      }
 730                  }
 731                  commit_sql();
 732              } else {
 733                  print "No user entries to be revived\n";
 734              }
 735  
 736              unset($revive_users);
 737          }
 738  
 739  
 740  /// User Updates - time-consuming (optional)
 741          if ($do_updates) {
 742              // narrow down what fields we need to update
 743              $all_keys = array_keys(get_object_vars($this->config));
 744              $updatekeys = array();
 745              foreach ($all_keys as $key) {
 746                  if (preg_match('/^field_updatelocal_(.+)$/',$key, $match)) {
 747                      // if we have a field to update it from
 748                      // and it must be updated 'onlogin' we
 749                      // update it on cron
 750                      if ( !empty($this->config->{'field_map_'.$match[1]})
 751                           and $this->config->{$match[0]} === 'onlogin') {
 752                          array_push($updatekeys, $match[1]); // the actual key name
 753                      }
 754                  }
 755              }
 756              // print_r($all_keys); print_r($updatekeys);
 757              unset($all_keys); unset($key);
 758  
 759          } else {
 760              print "No updates to be done\n";
 761          }
 762          if ( $do_updates and !empty($updatekeys) ) { // run updates only if relevant
 763              $users = get_records_sql("SELECT u.username, u.id
 764                                        FROM {$CFG->prefix}user u
 765                                        WHERE u.deleted=0 AND u.auth='ldap'");
 766              if (!empty($users)) {
 767                  print "User entries to update: ". count($users). "\n";
 768  
 769                  $sitecontext = get_context_instance(CONTEXT_SYSTEM);
 770                  if (!empty($this->config->creators) and !empty($this->config->memberattribute)
 771                    and $roles = get_roles_with_capability('moodle/legacy:coursecreator', CAP_ALLOW)) {
 772                      $creatorrole = array_shift($roles);      // We can only use one, let's use the first one
 773                  } else {
 774                      $creatorrole = false;
 775                  }
 776  
 777                  begin_sql();
 778                  $xcount = 0;
 779                  $maxxcount = 100;
 780  
 781                  foreach ($users as $user) {
 782                      echo "\t"; print_string('auth_dbupdatinguser', 'auth', array($user->username, $user->id));
 783                      if (!$this->update_user_record(addslashes($user->username), $updatekeys)) {
 784                          echo " - ".get_string('skipped');
 785                      }
 786                      echo "\n";
 787                      $xcount++;
 788  
 789                      // update course creators if needed
 790                      if ($creatorrole !== false) {
 791                          if ($this->iscreator($user->username)) {
 792                              role_assign($creatorrole->id, $user->id, 0, $sitecontext->id, 0, 0, 0, 'ldap');
 793                          } else {
 794                              role_unassign($creatorrole->id, $user->id, 0, $sitecontext->id, 'ldap');
 795                          }
 796                      }
 797  
 798                      if ($xcount++ > $maxxcount) {
 799                          commit_sql();
 800                          begin_sql();
 801                          $xcount = 0;
 802                      }
 803                  }
 804                  commit_sql();
 805                  unset($users); // free mem
 806              }
 807          } else { // end do updates
 808              print "No updates to be done\n";
 809          }
 810  
 811  /// User Additions
 812          // find users missing in DB that are in LDAP
 813          // note that get_records_sql wants at least 2 fields returned,
 814          // and gives me a nifty object I don't want.
 815          // note: we do not care about deleted accounts anymore, this feature was replaced by suspending to nologin auth plugin
 816          $sql = "SELECT e.username, e.username
 817                  FROM $temptable e LEFT JOIN {$CFG->prefix}user u ON e.username = u.username
 818                  WHERE u.id IS NULL";
 819          $add_users = get_records_sql($sql); // get rid of the fat
 820  
 821          if (!empty($add_users)) {
 822              print "User entries to add: ". count($add_users). "\n";
 823  
 824              $sitecontext = get_context_instance(CONTEXT_SYSTEM);
 825              if (!empty($this->config->creators) and !empty($this->config->memberattribute)
 826                and $roles = get_roles_with_capability('moodle/legacy:coursecreator', CAP_ALLOW)) {
 827                  $creatorrole = array_shift($roles);      // We can only use one, let's use the first one
 828              } else {
 829                  $creatorrole = false;
 830              }
 831  
 832              begin_sql();
 833              foreach ($add_users as $user) {
 834                  $user = $this->get_userinfo_asobj(addslashes($user->username));
 835  
 836                  // prep a few params
 837                  $user->modified   = time();
 838                  $user->confirmed  = 1;
 839                  $user->auth       = 'ldap';
 840                  $user->mnethostid = $CFG->mnet_localhost_id;
 841                  if (empty($user->lang)) {
 842                      $user->lang = $CFG->lang;
 843                  }
 844  
 845                  $user = addslashes_recursive($user);
 846  
 847                  if ($id = insert_record('user',$user)) {
 848                      echo "\t"; print_string('auth_dbinsertuser', 'auth', array(stripslashes($user->username), $id)); echo "\n";
 849                      $userobj = $this->update_user_record($user->username);
 850                      if (!empty($this->config->forcechangepassword)) {
 851                          set_user_preference('auth_forcepasswordchange', 1, $userobj->id);
 852                      }
 853                  } else {
 854                      echo "\t"; print_string('auth_dbinsertusererror', 'auth', $user->username); echo "\n";
 855                  }
 856  
 857                  // add course creators if needed
 858                  if ($creatorrole !== false and $this->iscreator(stripslashes($user->username))) {
 859                      role_assign($creatorrole->id, $user->id, 0, $sitecontext->id, 0, 0, 0, 'ldap');
 860                  }
 861              }
 862              commit_sql();
 863              unset($add_users); // free mem
 864          } else {
 865              print "No users to be added\n";
 866          }
 867          return true;
 868      }
 869  
 870      /**
 871       * Update a local user record from an external source.
 872       * This is a lighter version of the one in moodlelib -- won't do
 873       * expensive ops such as enrolment.
 874       *
 875       * If you don't pass $updatekeys, there is a performance hit and
 876       * values removed from LDAP won't be removed from moodle.
 877       *
 878       * @param string $username username (with system magic quotes)
 879       */
 880      function update_user_record($username, $updatekeys = false) {
 881          global $CFG;
 882  
 883          //just in case check text case
 884          $username = trim(moodle_strtolower($username));
 885  
 886          // get the current user record
 887          $user = get_record('user', 'username', $username, 'mnethostid', $CFG->mnet_localhost_id);
 888          if (empty($user)) { // trouble
 889              error_log("Cannot update non-existent user: ".stripslashes($username));
 890              print_error('auth_dbusernotexist','auth','',$username);
 891              die;
 892          }
 893  
 894          // Protect the userid from being overwritten
 895          $userid = $user->id;
 896  
 897          if ($newinfo = $this->get_userinfo($username)) {
 898              $newinfo = truncate_userinfo($newinfo);
 899  
 900              if (empty($updatekeys)) { // all keys? this does not support removing values
 901                  $updatekeys = array_keys($newinfo);
 902              }
 903  
 904              foreach ($updatekeys as $key) {
 905                  if (isset($newinfo[$key])) {
 906                      $value = $newinfo[$key];
 907                  } else {
 908                      $value = '';
 909                  }
 910  
 911                  if (!empty($this->config->{'field_updatelocal_' . $key})) {
 912                      if ($user->{$key} != $value) { // only update if it's changed
 913                          set_field('user', $key, addslashes($value), 'id', $userid);
 914                      }
 915                  }
 916              }
 917          } else {
 918              return false;
 919          }
 920          return get_record_select('user', "id = $userid AND deleted = 0");
 921      }
 922  
 923      /**
 924       * Bulk insert in SQL's temp table
 925       * @param array $users is an array of usernames
 926       */
 927      function ldap_bulk_insert($users, $temptable) {
 928  
 929          // bulk insert -- superfast with $bulk_insert_records
 930          $sql = 'INSERT INTO ' . $temptable . ' (username) VALUES ';
 931          // make those values safe
 932          $users = addslashes_recursive($users);
 933          // join and quote the whole lot
 934          $sql = $sql . "('" . implode("'),('", $users) . "')";
 935          print "\t+ " . count($users) . " users\n";
 936          execute_sql($sql, false);
 937      }
 938  
 939  
 940      /**
 941       * Activates (enables) user in external db so user can login to external db
 942       *
 943       * @param mixed $username    username (with system magic quotes)
 944       * @return boolen result
 945       */
 946      function user_activate($username) {
 947          $textlib = textlib_get_instance();
 948          $extusername = $textlib->convert(stripslashes($username), 'utf-8', $this->config->ldapencoding);
 949  
 950          $ldapconnection = $this->ldap_connect();
 951  
 952          $userdn = $this->ldap_find_userdn($ldapconnection, $extusername);
 953          switch ($this->config->user_type)  {
 954              case 'edir':
 955                  $newinfo['loginDisabled']="FALSE";
 956                  break;
 957              case 'ad':
 958                  // We need to unset the ACCOUNTDISABLE bit in the
 959                  // userAccountControl attribute ( see
 960                  // http://support.microsoft.com/kb/305144 )
 961                  $sr = ldap_read($ldapconnection, $userdn, '(objectClass=*)',
 962                                  array('userAccountControl'));
 963                  $info = ldap_get_entries($ldapconnection, $sr);
 964                  $newinfo['userAccountControl'] = $info[0]['userAccountControl'][0]
 965                                                   & (~AUTH_AD_ACCOUNTDISABLE);
 966                  break;
 967              default:
 968                  error ('auth: ldap user_activate() does not support selected usertype:"'.$this->config->user_type.'" (..yet)');
 969          }
 970          $result = ldap_modify($ldapconnection, $userdn, $newinfo);
 971          ldap_close($ldapconnection);
 972          return $result;
 973      }
 974  
 975      /**
 976       * Disables user in external db so user can't login to external db
 977       *
 978       * @param mixed $username    username
 979       * @return boolean result
 980       */
 981  /*    function user_disable($username) {
 982          $textlib = textlib_get_instance();
 983          $extusername = $textlib->convert(stripslashes($username), 'utf-8', $this->config->ldapencoding);
 984  
 985          $ldapconnection = $this->ldap_connect();
 986  
 987          $userdn = $this->ldap_find_userdn($ldapconnection, $extusername);
 988          switch ($this->config->user_type)  {
 989              case 'edir':
 990                  $newinfo['loginDisabled']="TRUE";
 991                  break;
 992              case 'ad':
 993                  // We need to set the ACCOUNTDISABLE bit in the
 994                  // userAccountControl attribute ( see
 995                  // http://support.microsoft.com/kb/305144 )
 996                  $sr = ldap_read($ldapconnection, $userdn, '(objectClass=*)',
 997                                  array('userAccountControl'));
 998                  $info = auth_ldap_get_entries($ldapconnection, $sr);
 999                  $newinfo['userAccountControl'] = $info[0]['userAccountControl'][0]
1000                                                   | AUTH_AD_ACCOUNTDISABLE;
1001                  break;
1002              default:
1003                  error ('auth: ldap user_disable() does not support selected usertype (..yet)');
1004          }
1005          $result = ldap_modify($ldapconnection, $userdn, $newinfo);
1006          ldap_close($ldapconnection);
1007          return $result;
1008      }*/
1009  
1010      /**
1011       * Returns true if user should be coursecreator.
1012       *
1013       * @param mixed $username    username (without system magic quotes)
1014       * @return boolean result
1015       */
1016      function iscreator($username) {
1017          if (empty($this->config->creators) or empty($this->config->memberattribute)) {
1018              return null;
1019          }
1020  
1021          $textlib = textlib_get_instance();
1022          $extusername = $textlib->convert($username, 'utf-8', $this->config->ldapencoding);
1023  
1024          return (boolean)$this->ldap_isgroupmember($extusername, $this->config->creators);
1025      }
1026  
1027      /**
1028       * Called when the user record is updated.
1029       * Modifies user in external database. It takes olduser (before changes) and newuser (after changes)
1030       * conpares information saved modified information to external db.
1031       *
1032       * @param mixed $olduser     Userobject before modifications    (without system magic quotes)
1033       * @param mixed $newuser     Userobject new modified userobject (without system magic quotes)
1034       * @return boolean result
1035       *
1036       */
1037      function user_update($olduser, $newuser) {
1038  
1039          global $USER;
1040  
1041          if (isset($olduser->username) and isset($newuser->username) and $olduser->username != $newuser->username) {
1042              error_log("ERROR:User renaming not allowed in LDAP");
1043              return false;
1044          }
1045  
1046          if (isset($olduser->auth) and $olduser->auth != 'ldap') {
1047              return true; // just change auth and skip update
1048          }
1049  
1050          $attrmap = $this->ldap_attributes();
1051  
1052          // Before doing anything else, make sure really need to update anything
1053          // in the external LDAP server.
1054          $update_external = false;
1055          foreach ($attrmap as $key => $ldapkeys) {
1056              if (!empty($this->config->{'field_updateremote_'.$key})) {
1057                  $update_external = true;
1058                  break;
1059              }
1060          }
1061          if (!$update_external) {
1062              return true;
1063          }
1064  
1065          $textlib = textlib_get_instance();
1066          $extoldusername = $textlib->convert($olduser->username, 'utf-8', $this->config->ldapencoding);
1067  
1068          $ldapconnection = $this->ldap_connect();
1069  
1070          $search_attribs = array();
1071  
1072          foreach ($attrmap as $key => $values) {
1073              if (!is_array($values)) {
1074                  $values = array($values);
1075              }
1076              foreach ($values as $value) {
1077                  if (!in_array($value, $search_attribs)) {
1078                      array_push($search_attribs, $value);
1079                  }
1080              }
1081          }
1082  
1083          $user_dn = $this->ldap_find_userdn($ldapconnection, $extoldusername);
1084  
1085          $user_info_result = ldap_read($ldapconnection, $user_dn,
1086                                  $this->config->objectclass, $search_attribs);
1087  
1088          if ($user_info_result) {
1089  
1090              $user_entry = $this->ldap_get_entries($ldapconnection, $user_info_result);
1091              if (empty($user_entry)) {
1092                  $error = 'ldap: Could not find user while updating externally. '.
1093                           'Details follow: search base: \''.$user_dn.'\'; search filter: \''.
1094                           $this->config->objectclass.'\'; search attributes: ';
1095                  foreach ($search_attribs as $attrib) {
1096                      $error .= $attrib.' ';
1097                  }
1098                  error_log($error);
1099                  return false; // old user not found!
1100              } else if (count($user_entry) > 1) {
1101                  error_log('ldap: Strange! More than one user record found in ldap. Only using the first one.');
1102                  return false;
1103              }
1104              $user_entry = $user_entry[0];
1105  
1106              //error_log(var_export($user_entry) . 'fpp' );
1107  
1108              foreach ($attrmap as $key => $ldapkeys) {
1109                  // only process if the moodle field ($key) has changed and we
1110                  // are set to update LDAP with it
1111                  if (isset($olduser->$key) and isset($newuser->$key)
1112                    and $olduser->$key !== $newuser->$key
1113                    and !empty($this->config->{'field_updateremote_'. $key})) {
1114                      // for ldap values that could be in more than one
1115                      // ldap key, we will do our best to match
1116                      // where they came from
1117                      $ambiguous = true;
1118                      $changed   = false;
1119                      if (!is_array($ldapkeys)) {
1120                          $ldapkeys = array($ldapkeys);
1121                      }
1122                      if (count($ldapkeys) < 2) {
1123                          $ambiguous = false;
1124                      }
1125  
1126                      $nuvalue = $textlib->convert($newuser->$key, 'utf-8', $this->config->ldapencoding);
1127                      empty($nuvalue) ? $nuvalue = array() : $nuvalue;
1128                      $ouvalue = $textlib->convert($olduser->$key, 'utf-8', $this->config->ldapencoding);
1129  
1130                      foreach ($ldapkeys as $ldapkey) {
1131                          $ldapkey   = $ldapkey;
1132                          $ldapvalue = $user_entry[$ldapkey][0];
1133                          if (!$ambiguous) {
1134                              // skip update if the values already match
1135                              if ($nuvalue !== $ldapvalue) {
1136                                  //this might fail due to schema validation
1137                                  if (@ldap_modify($ldapconnection, $user_dn, array($ldapkey => $nuvalue))) {
1138                                      continue;
1139                                  } else {
1140                                      error_log('Error updating LDAP record. Error code: '
1141                                        . ldap_errno($ldapconnection) . '; Error string : '
1142                                        . ldap_err2str(ldap_errno($ldapconnection))
1143                                        . "\nKey ($key) - old moodle value: '$ouvalue' new value: '$nuvalue'");
1144                                      continue;
1145                                  }
1146                              }
1147                          } else {
1148                              // ambiguous
1149                              // value empty before in Moodle (and LDAP) - use 1st ldap candidate field
1150                              // no need to guess
1151                              if ($ouvalue === '') { // value empty before - use 1st ldap candidate
1152                                  //this might fail due to schema validation
1153                                  if (@ldap_modify($ldapconnection, $user_dn, array($ldapkey => $nuvalue))) {
1154                                      $changed = true;
1155                                      continue;
1156                                  } else {
1157                                      error_log('Error updating LDAP record. Error code: '
1158                                        . ldap_errno($ldapconnection) . '; Error string : '
1159                                        . ldap_err2str(ldap_errno($ldapconnection))
1160                                        . "\nKey ($key) - old moodle value: '$ouvalue' new value: '$nuvalue'");
1161                                      continue;
1162                                  }
1163                              }
1164  
1165                              // we found which ldap key to update!
1166                              if ($ouvalue !== '' and $ouvalue === $ldapvalue ) {
1167                                  //this might fail due to schema validation
1168                                  if (@ldap_modify($ldapconnection, $user_dn, array($ldapkey => $nuvalue))) {
1169                                      $changed = true;
1170                                      continue;
1171                                  } else {
1172                                      error_log('Error updating LDAP record. Error code: '
1173                                        . ldap_errno($ldapconnection) . '; Error string : '
1174                                        . ldap_err2str(ldap_errno($ldapconnection))
1175                                        . "\nKey ($key) - old moodle value: '$ouvalue' new value: '$nuvalue'");
1176                                      continue;
1177                                  }
1178                              }
1179                          }
1180                      }
1181  
1182                      if ($ambiguous and !$changed) {
1183                          error_log("Failed to update LDAP with ambiguous field $key".
1184                                    "  old moodle value: '" . $ouvalue .
1185                                    "' new value '" . $nuvalue );
1186                      }
1187                  }
1188              }
1189          } else {
1190              error_log("ERROR:No user found in LDAP");
1191              @ldap_close($ldapconnection);
1192              return false;
1193          }
1194  
1195          @ldap_close($ldapconnection);
1196  
1197          return true;
1198  
1199      }
1200  
1201      /**
1202       * changes userpassword in external db
1203       *
1204       * called when the user password is updated.
1205       * changes userpassword in external db
1206       *
1207       * @param  object  $user        User table object  (with system magic quotes)
1208       * @param  string  $newpassword Plaintext password (with system magic quotes)
1209       * @return boolean result
1210       *
1211       */
1212      function user_update_password($user, $newpassword) {
1213      /// called when the user password is updated -- it assumes it is called by an admin
1214      /// or that you've otherwise checked the user's credentials
1215      /// IMPORTANT: $newpassword must be cleartext, not crypted/md5'ed
1216  
1217          global $USER;
1218          $result = false;
1219          $username = $user->username;
1220  
1221          $textlib = textlib_get_instance();
1222          $extusername = $textlib->convert(stripslashes($username), 'utf-8', $this->config->ldapencoding);
1223          $extpassword = $textlib->convert(stripslashes($newpassword), 'utf-8', $this->config->ldapencoding);
1224  
1225          switch ($this->config->passtype) {
1226              case 'md5':
1227                  $extpassword = '{MD5}' . base64_encode(pack('H*', md5($extpassword)));
1228                  break;
1229              case 'sha1':
1230                  $extpassword = '{SHA}' . base64_encode(pack('H*', sha1($extpassword)));
1231                  break;
1232              case 'plaintext':
1233              default:
1234                  break; // plaintext
1235          }
1236  
1237          $ldapconnection = $this->ldap_connect();
1238  
1239          $user_dn = $this->ldap_find_userdn($ldapconnection, $extusername);
1240  
1241          if (!$user_dn) {
1242              error_log('LDAP Error in user_update_password(). No DN for: ' . stripslashes($user->username));
1243              return false;
1244          }
1245  
1246          switch ($this->config->user_type) {
1247              case 'edir':
1248                  //Change password
1249                  $result = ldap_modify($ldapconnection, $user_dn, array('userPassword' => $extpassword));
1250                  if (!$result) {
1251                      error_log('LDAP Error in user_update_password(). Error code: '
1252                                . ldap_errno($ldapconnection) . '; Error string : '
1253                                . ldap_err2str(ldap_errno($ldapconnection)));
1254                  }
1255                  //Update password expiration time, grace logins count
1256                  $search_attribs = array($this->config->expireattr, 'passwordExpirationInterval','loginGraceLimit' );
1257                  $sr = ldap_read($ldapconnection, $user_dn, '(objectClass=*)', $search_attribs);
1258                  if ($sr)  {
1259                      $info=$this->ldap_get_entries($ldapconnection, $sr);
1260                      $newattrs = array();
1261                      if (!empty($info[0][$this->config->expireattr][0])) {
1262                          //Set expiration time only if passwordExpirationInterval is defined
1263                          if (!empty($info[0]['passwordExpirationInterval'][0])) {
1264                             $expirationtime = time() + $info[0]['passwordExpirationInterval'][0];
1265                             $ldapexpirationtime = $this->ldap_unix2expirationtime($expirationtime);
1266                             $newattrs['passwordExpirationTime'] = $ldapexpirationtime;
1267                          }
1268  
1269                          //set gracelogin count
1270                          if (!empty($info[0]['loginGraceLimit'][0])) {
1271                             $newattrs['loginGraceRemaining']= $info[0]['loginGraceLimit'][0];
1272                          }
1273  
1274                          //Store attribute changes to ldap
1275                          $result = ldap_modify($ldapconnection, $user_dn, $newattrs);
1276                          if (!$result) {
1277                             error_log('LDAP Error in user_update_password() when modifying expirationtime and/or gracelogins. Error code: '
1278                                       . ldap_errno($ldapconnection) . '; Error string : '
1279                                       . ldap_err2str(ldap_errno($ldapconnection)));
1280                          }
1281                      }
1282                  }
1283                  else {
1284                      error_log('LDAP Error in user_update_password() when reading password expiration time. Error code: '
1285                                . ldap_errno($ldapconnection) . '; Error string : '
1286                                . ldap_err2str(ldap_errno($ldapconnection)));
1287                  }
1288                  break;
1289  
1290              case 'ad':
1291                  // Passwords in Active Directory must be encoded as Unicode
1292                  // strings (UCS-2 Little Endian format) and surrounded with
1293                  // double quotes. See http://support.microsoft.com/?kbid=269190
1294                  if (!function_exists('mb_convert_encoding')) {
1295                      error_log ('You need the mbstring extension to change passwords in Active Directory');
1296                      return false;
1297                  }
1298                  $extpassword = mb_convert_encoding('"'.$extpassword.'"', "UCS-2LE", $this->config->ldapencoding);
1299                  $result = ldap_modify($ldapconnection, $user_dn, array('unicodePwd' => $extpassword));
1300                  if (!$result) {
1301                      error_log('LDAP Error in user_update_password(). Error code: '
1302                                . ldap_errno($ldapconnection) . '; Error string : '
1303                                . ldap_err2str(ldap_errno($ldapconnection)));
1304                  }
1305                  break;
1306  
1307              default:
1308                  $usedconnection = &$ldapconnection;
1309                  // send ldap the password in cleartext, it will md5 it itself
1310                  $result = ldap_modify($ldapconnection, $user_dn, array('userPassword' => $extpassword));
1311                  if (!$result) {
1312                      error_log('LDAP Error in user_update_password(). Error code: '
1313                          . ldap_errno($ldapconnection) . '; Error string : '
1314                          . ldap_err2str(ldap_errno($ldapconnection)));
1315                  }
1316  
1317          }
1318  
1319          @ldap_close($ldapconnection);
1320          return $result;
1321      }
1322  
1323      //PRIVATE FUNCTIONS starts
1324      //private functions are named as ldap_*
1325  
1326      /**
1327       * returns predefined usertypes
1328       *
1329       * @return array of predefined usertypes
1330       */
1331      function ldap_suppported_usertypes() {
1332          $types = array();
1333          $types['edir']='Novell Edirectory';
1334          $types['rfc2307']='posixAccount (rfc2307)';
1335          $types['rfc2307bis']='posixAccount (rfc2307bis)';
1336          $types['samba']='sambaSamAccount (v.3.0.7)';
1337          $types['ad']='MS ActiveDirectory';
1338          $types['default']=get_string('default');
1339          return $types;
1340      }
1341  
1342  
1343      /**
1344       * Initializes needed variables for ldap-module
1345       *
1346       * Uses names defined in ldap_supported_usertypes.
1347       * $default is first defined as:
1348       * $default['pseudoname'] = array(
1349       *                      'typename1' => 'value',
1350       *                      'typename2' => 'value'
1351       *                      ....
1352       *                      );
1353       *
1354       * @return array of default values
1355       */
1356      function ldap_getdefaults() {
1357          $default['objectclass'] = array(
1358                              'edir' => 'User',
1359                              'rfc2307' => 'posixAccount',
1360                              'rfc2307bis' => 'posixAccount',
1361                              'samba' => 'sambaSamAccount',
1362                              'ad' => 'user',
1363                              'default' => '*'
1364                              );
1365          $default['user_attribute'] = array(
1366                              'edir' => 'cn',
1367                              'rfc2307' => 'uid',
1368                              'rfc2307bis' => 'uid',
1369                              'samba' => 'uid',
1370                              'ad' => 'cn',
1371                              'default' => 'cn'
1372                              );
1373          $default['memberattribute'] = array(
1374                              'edir' => 'member',
1375                              'rfc2307' => 'member',
1376                              'rfc2307bis' => 'member',
1377                              'samba' => 'member',
1378                              'ad' => 'member',
1379                              'default' => 'member'
1380                              );
1381          $default['memberattribute_isdn'] = array(
1382                              'edir' => '1',
1383                              'rfc2307' => '0',
1384                              'rfc2307bis' => '1',
1385                              'samba' => '0', //is this right?
1386                              'ad' => '1',
1387                              'default' => '0'
1388                              );
1389          $default['expireattr'] = array (
1390                              'edir' => 'passwordExpirationTime',
1391                              'rfc2307' => 'shadowExpire',
1392                              'rfc2307bis' => 'shadowExpire',
1393                              'samba' => '', //No support yet
1394                              'ad' => 'pwdLastSet',
1395                              'default' => ''
1396                              );
1397          return $default;
1398      }
1399  
1400      /**
1401       * return binaryfields of selected usertype
1402       *
1403       *
1404       * @return array
1405       */
1406      function ldap_getbinaryfields () {
1407          $binaryfields = array (
1408                              'edir' => array('guid'),
1409                              'rfc2307' => array(),
1410                              'rfc2307bis' => array(),
1411                              'samba' => array(),
1412                              'ad' => array(),
1413                              'default' => array()
1414                              );
1415          if (!empty($this->config->user_type)) {
1416              return $binaryfields[$this->config->user_type];
1417          }
1418          else {
1419              return $binaryfields['default'];
1420          }
1421      }
1422  
1423      function ldap_isbinary ($field) {
1424          if (empty($field)) {
1425              return false;
1426          }
1427          return array_search($field, $this->ldap_getbinaryfields());
1428      }
1429  
1430      /**
1431       * take expirationtime and return it as unixseconds
1432       *
1433       * takes expriration timestamp as readed from ldap
1434       * returns it as unix seconds
1435       * depends on $this->config->user_type variable
1436       *
1437       * @param mixed time   Time stamp readed from ldap as it is.
1438       * @param string $ldapconnection Just needed for Active Directory.
1439       * @param string $user_dn User distinguished name for the user we are checking password expiration (just needed for Active Directory).
1440       * @return timestamp
1441       */
1442      function ldap_expirationtime2unix ($time, $ldapconnection, $user_dn) {
1443          $result = false;
1444          switch ($this->config->user_type) {
1445              case 'edir':
1446                  $yr=substr($time,0,4);
1447                  $mo=substr($time,4,2);
1448                  $dt=substr($time,6,2);
1449                  $hr=substr($time,8,2);
1450                  $min=substr($time,10,2);
1451                  $sec=substr($time,12,2);
1452                  $result = mktime($hr,$min,$sec,$mo,$dt,$yr);
1453                  break;
1454              case 'rfc2307':
1455              case 'rfc2307bis':
1456                  $result = $time * DAYSECS; //The shadowExpire contains the number of DAYS between 01/01/1970 and the actual expiration date
1457                  break;
1458              case 'ad':
1459                  $result = $this->ldap_get_ad_pwdexpire($time, $ldapconnection, $user_dn);
1460                  break;
1461              default:
1462                  print_error('auth_ldap_usertypeundefined', 'auth');
1463          }
1464          return $result;
1465      }
1466  
1467      /**
1468       * takes unixtime and return it formated for storing in ldap
1469       *
1470       * @param integer unix time stamp
1471       */
1472      function ldap_unix2expirationtime($time) {
1473          $result = false;
1474          switch ($this->config->user_type) {
1475              case 'edir':
1476                  $result=date('YmdHis', $time).'Z';
1477                  break;
1478              case 'rfc2307':
1479              case 'rfc2307bis':
1480                  $result = $time ; //Already in correct format
1481                  break;
1482              default:
1483                  print_error('auth_ldap_usertypeundefined2', 'auth');
1484          }
1485          return $result;
1486  
1487      }
1488  
1489      /**
1490       * checks if user belong to specific group(s)
1491       * or is in a subtree.
1492       *
1493       * Returns true if user belongs group in grupdns string OR
1494       * if the DN of the user is in a subtree pf the DN provided
1495       * as "group"
1496       *
1497       * @param mixed $username    username
1498       * @param mixed $groupdns    string of group dn separated by ;
1499       *
1500       */
1501      function ldap_isgroupmember($extusername='', $groupdns='') {
1502      // Takes username and groupdn(s) , separated by ;
1503      // Returns true if user is member of any given groups
1504  
1505          $ldapconnection = $this->ldap_connect();
1506  
1507          if (empty($extusername) or empty($groupdns)) {
1508              return false;
1509              }
1510  
1511          if ($this->config->memberattribute_isdn) {
1512              $memberuser = $this->ldap_find_userdn($ldapconnection, $extusername);
1513          } else {
1514              $memberuser = $extusername;
1515          }
1516  
1517          if (empty($memberuser)) {
1518              return false;
1519          }
1520  
1521          $groups = explode(";",$groupdns);
1522  
1523          $result = false;
1524          foreach ($groups as $group) {
1525              $group = trim($group);
1526              if (empty($group)) {
1527                  continue;
1528              }
1529  
1530              // check cheaply if the user's DN sits in a subtree
1531              // of the "group" DN provided. Granted, this isn't
1532              // a proper LDAP group, but it's a popular usage.
1533              if (strpos(strrev($memberuser), strrev($group))===0) {
1534                  $result = true;
1535                  break;
1536              }
1537  
1538              //echo "Checking group $group for member $username\n";
1539              $search = ldap_read($ldapconnection, $group,  '('.$this->config->memberattribute.'='.$this->filter_addslashes($memberuser).')', array($this->config->memberattribute));
1540              if (!empty($search) and ldap_count_entries($ldapconnection, $search)) {
1541                  $info = $this->ldap_get_entries($ldapconnection, $search);
1542  
1543                  if (count($info) > 0 ) {
1544                      // user is member of group
1545                      $result = true;
1546                      break;
1547                  }
1548            }
1549          }
1550  
1551          return $result;
1552  
1553      }
1554  
1555      /**
1556       * connects to ldap server
1557       *
1558       * Tries connect to specified ldap servers.
1559       * Returns connection result or error.
1560       *
1561       * @return connection result
1562       */
1563      function ldap_connect($binddn='',$bindpwd='') {
1564          //Select bind password, With empty values use
1565          //ldap_bind_* variables or anonymous bind if ldap_bind_* are empty
1566          if ($binddn == '' and $bindpwd == '') {
1567              if (!empty($this->config->bind_dn)) {
1568                 $binddn = $this->config->bind_dn;
1569              }
1570              if (!empty($this->config->bind_pw)) {
1571                 $bindpwd = $this->config->bind_pw;
1572              }
1573          }
1574  
1575          $urls = explode(";",$this->config->host_url);
1576  
1577          foreach ($urls as $server) {
1578              $server = trim($server);
1579              if (empty($server)) {
1580                  continue;
1581              }
1582  
1583              $connresult = ldap_connect($server);
1584              //ldap_connect returns ALWAYS true
1585  
1586              if (!empty($this->config->version)) {
1587                  ldap_set_option($connresult, LDAP_OPT_PROTOCOL_VERSION, $this->config->version);
1588              }
1589  
1590              // Fix MDL-10921
1591              if ($this->config->user_type == 'ad') {
1592                   ldap_set_option($connresult, LDAP_OPT_REFERRALS, 0);
1593              }
1594  
1595              if (!empty($binddn)) {
1596                  //bind with search-user
1597                  //$debuginfo .= 'Using bind user'.$binddn.'and password:'.$bindpwd;
1598                  $bindresult=ldap_bind($connresult, $binddn,$bindpwd);
1599              }
1600              else {
1601                  //bind anonymously
1602                  $bindresult=@ldap_bind($connresult);
1603              }
1604  
1605              if (!empty($this->config->opt_deref)) {
1606                  ldap_set_option($connresult, LDAP_OPT_DEREF, $this->config->opt_deref);
1607              }
1608  
1609              if ($bindresult) {
1610                  return $connresult;
1611              }
1612  
1613              $debuginfo .= "<br/>Server: '$server' <br/> Connection: '$connresult'<br/> Bind result: '$bindresult'</br>";
1614          }
1615  
1616          //If any of servers are alive we have already returned connection
1617          print_error('auth_ldap_noconnect_all','auth','', $debuginfo);
1618          return false;
1619      }
1620  
1621      /**
1622       * retuns dn of username
1623       *
1624       * Search specified contexts for username and return user dn
1625       * like: cn=username,ou=suborg,o=org
1626       *
1627       * @param mixed $ldapconnection  $ldapconnection result
1628       * @param mixed $username username (external encoding no slashes)
1629       *
1630       */
1631  
1632      function ldap_find_userdn ($ldapconnection, $extusername) {
1633  
1634          //default return value
1635          $ldap_user_dn = FALSE;
1636  
1637          //get all contexts and look for first matching user
1638          $ldap_contexts = explode(";",$this->config->contexts);
1639  
1640          if (!empty($this->config->create_context)) {
1641            array_push($ldap_contexts, $this->config->create_context);
1642          }
1643  
1644          foreach ($ldap_contexts as $context) {
1645  
1646              $context = trim($context);
1647              if (empty($context)) {
1648                  continue;
1649              }
1650  
1651              if ($this->config->search_sub) {
1652                  //use ldap_search to find first user from subtree
1653                  $ldap_result = ldap_search($ldapconnection, $context, "(".$this->config->user_attribute."=".$this->filter_addslashes($extusername).")",array($this->config->user_attribute));
1654  
1655              }
1656              else {
1657                  //search only in this context
1658                  $ldap_result = ldap_list($ldapconnection, $context, "(".$this->config->user_attribute."=".$this->filter_addslashes($extusername).")",array($this->config->user_attribute));
1659              }
1660  
1661              $entry = ldap_first_entry($ldapconnection,$ldap_result);
1662  
1663              if ($entry) {
1664                  $ldap_user_dn = ldap_get_dn($ldapconnection, $entry);
1665                  break ;
1666              }
1667          }
1668  
1669          return $ldap_user_dn;
1670      }
1671  
1672      /**
1673       * retuns user attribute mappings between moodle and ldap
1674       *
1675       * @return array
1676       */
1677  
1678      function ldap_attributes () {
1679          $moodleattributes = array();
1680          foreach ($this->userfields as $field) {
1681              if (!empty($this->config->{"field_map_$field"})) {
1682                  $moodleattributes[$field] = $this->config->{"field_map_$field"};
1683                  if (preg_match('/,/',$moodleattributes[$field])) {
1684                      $moodleattributes[$field] = explode(',', $moodleattributes[$field]); // split ?
1685                  }
1686              }
1687          }
1688          $moodleattributes['username'] = $this->config->user_attribute;
1689          return $moodleattributes;
1690      }
1691  
1692      /**
1693       * return all usernames from ldap
1694       *
1695       * @return array
1696       */
1697  
1698      function ldap_get_userlist($filter="*") {
1699      /// returns all users from ldap servers
1700          $fresult = array();
1701  
1702          $ldapconnection = $this->ldap_connect();
1703  
1704          if ($filter=="*") {
1705             $filter = '(&('.$this->config->user_attribute.'=*)'.$this->config->objectclass.')';
1706          }
1707  
1708          $contexts = explode(";",$this->config->contexts);
1709  
1710          if (!empty($this->config->create_context)) {
1711                array_push($contexts, $this->config->create_context);
1712          }
1713  
1714          foreach ($contexts as $context) {
1715  
1716              $context = trim($context);
1717              if (empty($context)) {
1718                  continue;
1719              }
1720  
1721              if ($this->config->search_sub) {
1722                  //use ldap_search to find first user from subtree
1723                  $ldap_result = ldap_search($ldapconnection, $context,$filter,array($this->config->user_attribute));
1724              }
1725              else {
1726                  //search only in this context
1727                  $ldap_result = ldap_list($ldapconnection, $context,
1728                                           $filter,
1729                                           array($this->config->user_attribute));
1730              }
1731  
1732              $users = $this->ldap_get_entries($ldapconnection, $ldap_result);
1733  
1734              //add found users to list
1735              for ($i=0;$i<count($users);$i++) {
1736                  array_push($fresult, ($users[$i][$this->config->user_attribute][0]) );
1737              }
1738          }
1739  
1740          return $fresult;
1741      }
1742  
1743      /**
1744       * return entries from ldap
1745       *
1746       * Returns values like ldap_get_entries but is
1747       * binary compatible and return all attributes as array
1748       *
1749       * @return array ldap-entries
1750       */
1751  
1752      function ldap_get_entries($conn, $searchresult) {
1753      //Returns values like ldap_get_entries but is
1754      //binary compatible
1755          $i=0;
1756          $fresult=array();
1757          $entry = ldap_first_entry($conn, $searchresult);
1758          do {
1759              $attributes = @ldap_get_attributes($conn, $entry);
1760              for ($j=0; $j<$attributes['count']; $j++) {
1761                  $values = ldap_get_values_len($conn, $entry,$attributes[$j]);
1762                  if (is_array($values)) {
1763                  $fresult[$i][$attributes[$j]] = $values;
1764                  }
1765                  else {
1766                      $fresult[$i][$attributes[$j]] = array($values);
1767                  }
1768              }
1769              $i++;
1770          }
1771          while ($entry = @ldap_next_entry($conn, $entry));
1772          //were done
1773          return ($fresult);
1774      }
1775  
1776      /**
1777       * Returns true if this authentication plugin is 'internal'.
1778       *
1779       * @return bool
1780       */
1781      function is_internal() {
1782          return false;
1783      }
1784  
1785      /**
1786       * Returns true if this authentication plugin can change the user's
1787       * password.
1788       *
1789       * @return bool
1790       */
1791      function can_change_password() {
1792          return !empty($this->config->stdchangepassword) or !empty($this->config->changepasswordurl);
1793      }
1794  
1795      /**
1796       * Returns the URL for changing the user's pw, or empty if the default can
1797       * be used.
1798       *
1799       * @return string url
1800       */
1801      function change_password_url() {
1802          if (empty($this->config->stdchangepassword)) {
1803              return $this->config->changepasswordurl;
1804          } else {
1805              return '';
1806          }
1807      }
1808  
1809      /**
1810       * Will get called before the login page is shown, if NTLM SSO
1811       * is enabled, and the user is in the right network, we'll redirect
1812       * to the magic NTLM page for SSO...
1813       *
1814       */
1815      function loginpage_hook() {
1816          global $CFG;
1817  
1818          if ($_SERVER['REQUEST_METHOD'] === 'GET'    // Only on initial GET 
1819                                                      // of loginpage
1820              &&!empty($this->config->ntlmsso_enabled)// SSO enabled
1821              && !empty($this->config->ntlmsso_subnet)// have a subnet to test for
1822              && empty($_GET['authldap_skipntlmsso']) // haven't failed it yet
1823              && (isguestuser() || !isloggedin())     // guestuser or not-logged-in users
1824              && address_in_subnet($_SERVER['REMOTE_ADDR'],$this->config->ntlmsso_subnet)) {
1825              redirect("{$CFG->wwwroot}/auth/ldap/ntlmsso_attempt.php");
1826          }
1827      }
1828  
1829      /**
1830       * To be called from a page running under NTLM's
1831       * "Integrated Windows Authentication". 
1832       *
1833       * If successful, it will set a special "cookie" (not an HTTP cookie!) 
1834       * in cache_flags under the "auth/ldap/ntlmsess" "plugin" and return true.
1835       * The "cookie" will be picked up by ntlmsso_finish() to complete the
1836       * process.
1837       *
1838       * On failure it will return false for the caller to display an appropriate
1839       * error message (probably saying that Integrated Windows Auth isn't enabled!)
1840       *
1841       * NOTE that this code will execute under the OS user credentials, 
1842       * so we MUST avoid dealing with files -- such as session files.
1843       * (The caller should set $nomoodlecookie before including config.php)
1844       *
1845       */
1846      function ntlmsso_magic($sesskey) {
1847          if (isset($_SERVER['REMOTE_USER']) && !empty($_SERVER['REMOTE_USER'])) {
1848              $username = $_SERVER['REMOTE_USER'];
1849              $username = substr(strrchr($username, '\\'), 1); //strip domain info
1850              $username = moodle_strtolower($username); //compatibility hack
1851              set_cache_flag('auth/ldap/ntlmsess', $sesskey, $username, AUTH_NTLMTIMEOUT);
1852              return true;
1853          }
1854          return false;
1855      }
1856  
1857      /**
1858       * Find the session set by ntlmsso_magic(), validate it and 
1859       * call authenticate_user_login() to authenticate the user through
1860       * the auth machinery.
1861       * 
1862       * It is complemented by a similar check in user_login().
1863       * 
1864       * If it succeeds, it never returns. 
1865       *
1866       */
1867      function ntlmsso_finish() {
1868          global $CFG, $USER, $SESSION;
1869  
1870          $key = sesskey();
1871          $cf = get_cache_flags('auth/ldap/ntlmsess');
1872          if (!isset($cf[$key]) || $cf[$key] === '') {
1873              return false;
1874          }
1875          $username   = $cf[$key];
1876          // Here we want to trigger the whole authentication machinery
1877          // to make sure no step is bypassed...
1878          $user = authenticate_user_login($username, $key);
1879          if ($user) {
1880              add_to_log(SITEID, 'user', 'login', "view.php?id=$USER->id&course=".SITEID,
1881                         $user->id, 0, $user->id);
1882              $USER = complete_user_login($user);
1883  
1884              // Cleanup the key to prevent reuse...
1885              // and to allow re-logins with normal credentials
1886              unset_cache_flag('auth/ldap/ntlmsess', $key);
1887  
1888              /// Redirection
1889              if (user_not_fully_set_up($USER)) {
1890                  $urltogo = $CFG->wwwroot.'/user/edit.php';
1891                  // We don't delete $SESSION->wantsurl yet, so we get there later
1892              } else if (isset($SESSION->wantsurl) and (strpos($SESSION->wantsurl, $CFG->wwwroot) === 0)) {
1893                  $urltogo = $SESSION->wantsurl;    /// Because it's an address in this site
1894                  unset($SESSION->wantsurl);
1895              } else {
1896                  // no wantsurl stored or external - go to homepage
1897                  $urltogo = $CFG->wwwroot.'/';
1898                  unset($SESSION->wantsurl);
1899              }
1900              redirect($urltogo);
1901          }
1902          // Should never reach here.
1903          return false;
1904      }    
1905  
1906      /**
1907       * Sync roles for this user
1908       *
1909       * @param $user object user object (without system magic quotes)
1910       */
1911      function sync_roles($user) {
1912          $iscreator = $this->iscreator($user->username);
1913          if ($iscreator === null) {
1914              return; //nothing to sync - creators not configured
1915          }
1916  
1917          if ($roles = get_roles_with_capability('moodle/legacy:coursecreator', CAP_ALLOW)) {
1918              $creatorrole = array_shift($roles);      // We can only use one, let's use the first one
1919              $systemcontext = get_context_instance(CONTEXT_SYSTEM);
1920  
1921              if ($iscreator) { // Following calls will not create duplicates
1922                  role_assign($creatorrole->id, $user->id, 0, $systemcontext->id, 0, 0, 0, 'ldap');
1923              } else {
1924                  //unassign only if previously assigned by this plugin!
1925                  role_unassign($creatorrole->id, $user->id, 0, $systemcontext->id, 'ldap');
1926              }
1927          }
1928      }
1929  
1930      /**
1931       * Prints a form for configuring this authentication plugin.
1932       *
1933       * This function is called from admin/auth.php, and outputs a full page with
1934       * a form for configuring this plugin.
1935       *
1936       * @param array $page An object containing all the data for this page.
1937       */
1938      function config_form($config, $err, $user_fields) {
1939          include  'config.html';
1940      }
1941  
1942      /**
1943       * Processes and stores configuration data for this authentication plugin.
1944       */
1945      function process_config($config) {
1946          // set to defaults if undefined
1947          if (!isset($config->host_url))
1948              { $config->host_url = ''; }
1949          if (empty($config->ldapencoding))
1950              { $config->ldapencoding = 'utf-8'; }
1951          if (!isset($config->contexts))
1952              { $config->contexts = ''; }
1953          if (!isset($config->user_type))
1954              { $config->user_type = 'default'; }
1955          if (!isset($config->user_attribute))
1956              { $config->user_attribute = ''; }
1957          if (!isset($config->search_sub))
1958              { $config->search_sub = ''; }
1959          if (!isset($config->opt_deref))
1960              { $config->opt_deref = ''; }
1961          if (!isset($config->preventpassindb))
1962              { $config->preventpassindb = 0; }
1963          if (!isset($config->bind_dn))
1964              {$config->bind_dn = ''; }
1965          if (!isset($config->bind_pw))
1966              {$config->bind_pw = ''; }
1967          if (!isset($config->version))
1968              {$config->version = '2'; }
1969          if (!isset($config->objectclass))
1970              {$config->objectclass = ''; }
1971          if (!isset($config->memberattribute))
1972              {$config->memberattribute = ''; }
1973          if (!isset($config->memberattribute_isdn))
1974              {$config->memberattribute_isdn = ''; }
1975          if (!isset($config->creators))
1976              {$config->creators = ''; }
1977          if (!isset($config->create_context))
1978              {$config->create_context = ''; }
1979          if (!isset($config->expiration))
1980              {$config->expiration = ''; }
1981          if (!isset($config->expiration_warning))
1982              {$config->expiration_warning = '10'; }
1983          if (!isset($config->expireattr))
1984              {$config->expireattr = ''; }
1985          if (!isset($config->gracelogins))
1986              {$config->gracelogins = ''; }
1987          if (!isset($config->graceattr))
1988              {$config->graceattr = ''; }
1989          if (!isset($config->auth_user_create))
1990              {$config->auth_user_create = ''; }
1991          if (!isset($config->forcechangepassword))
1992              {$config->forcechangepassword = 0; }
1993          if (!isset($config->stdchangepassword))
1994              {$config->stdchangepassword = 0; }
1995          if (!isset($config->passtype))
1996              {$config->passtype = 'plaintext'; }
1997          if (!isset($config->changepasswordurl))
1998              {$config->changepasswordurl = ''; }
1999          if (!isset($config->removeuser))
2000              {$config->removeuser = 0; }
2001          if (!isset($config->ntlmsso_enabled))
2002              {$config->ntlmsso_enabled = 0; }
2003          if (!isset($config->ntlmsso_subnet))
2004              {$config->ntlmsso_subnet = ''; }
2005  
2006          // save settings
2007          set_config('host_url', $config->host_url, 'auth/ldap');
2008          set_config('ldapencoding', $config->ldapencoding, 'auth/ldap');
2009          set_config('host_url', $config->host_url, 'auth/ldap');
2010          set_config('contexts', $config->contexts, 'auth/ldap');
2011          set_config('user_type', $config->user_type, 'auth/ldap');
2012          set_config('user_attribute', $config->user_attribute, 'auth/ldap');
2013          set_config('search_sub', $config->search_sub, 'auth/ldap');
2014          set_config('opt_deref', $config->opt_deref, 'auth/ldap');
2015          set_config('preventpassindb', $config->preventpassindb, 'auth/ldap');
2016