Merge branch 'wip_master_mdl-3941_add_support_for_LDAP_TLS' of git://github.com/iaren...
authorDan Poltawski <dan@moodle.com>
Tue, 8 Jan 2013 08:40:50 +0000 (16:40 +0800)
committerDan Poltawski <dan@moodle.com>
Tue, 8 Jan 2013 08:40:50 +0000 (16:40 +0800)
1  2 
auth/ldap/auth.php
enrol/ldap/lib.php

diff --combined auth/ldap/auth.php
@@@ -53,11 -53,6 +53,11 @@@ if (!defined('AUTH_NTLM_DEFAULT_FORMAT'
      define('AUTH_NTLM_DEFAULT_FORMAT', '%domain%\\%username%');
  }
  
 +// Allows us to retrieve a diagnostic message in case of LDAP operation error
 +if (!defined('LDAP_OPT_DIAGNOSTIC_MESSAGE')) {
 +    define('LDAP_OPT_DIAGNOSTIC_MESSAGE', 0x0032);
 +}
 +
  require_once($CFG->libdir.'/authlib.php');
  require_once($CFG->libdir.'/ldaplib.php');
  
@@@ -197,28 -192,11 +197,28 @@@ class auth_plugin_ldap extends auth_plu
  
          // Try to bind with current username and password
          $ldap_login = @ldap_bind($ldapconnection, $ldap_user_dn, $extpassword);
 -        $this->ldap_close();
 -        if ($ldap_login) {
 -            return true;
 +
 +        // If login fails and we are using MS Active Directory, retrieve the diagnostic
 +        // message to see if this is due to an expired password, or that the user is forced to
 +        // change the password on first login. If it is, only proceed if we can change
 +        // password from Moodle (otherwise we'll get stuck later in the login process).
 +        if (!$ldap_login && ($this->config->user_type == 'ad')
 +            && $this->can_change_password()
 +            && (!empty($this->config->expiration) and ($this->config->expiration == 1))) {
 +
 +            // We need to get the diagnostic message right after the call to ldap_bind(),
 +            // before any other LDAP operation.
 +            ldap_get_option($ldapconnection, LDAP_OPT_DIAGNOSTIC_MESSAGE, $diagmsg);
 +
 +            if ($this->ldap_ad_pwdexpired_from_diagmsg($diagmsg)) {
 +                // If login failed because user must change the password now or the
 +                // password has expired, let the user in. We'll catch this later in the
 +                // login process when we explicitly check for expired passwords.
 +                $ldap_login = true;
 +            }
          }
 -        return false;
 +        $this->ldap_close();
 +        return $ldap_login;
      }
  
      /**
              $info = ldap_get_entries_moodle($ldapconnection, $sr);
              if (!empty ($info)) {
                  $info = array_change_key_case($info[0], CASE_LOWER);
 -                if (!empty($info[$this->config->expireattr][0])) {
 +                if (isset($info[$this->config->expireattr][0])) {
                      $expiretime = $this->ldap_expirationtime2unix($info[$this->config->expireattr][0], $ldapconnection, $user_dn);
                      if ($expiretime != 0) {
                          $now = time();
  /// User removal
          // Find users in DB that aren't in ldap -- to be removed!
          // this is still not as scalable (but how often do we mass delete?)
 -        if ($this->config->removeuser !== AUTH_REMOVEUSER_KEEP) {
 +        if ($this->config->removeuser != AUTH_REMOVEUSER_KEEP) {
              $sql = 'SELECT u.*
                        FROM {user} u
                        LEFT JOIN {tmp_extuser} e ON (u.username = e.username AND u.mnethostid = e.mnethostid)
          if (!isset($config->host_url)) {
               $config->host_url = '';
          }
+         if (!isset($config->start_tls)) {
+              $config->start_tls = false;
+         }
          if (empty($config->ldapencoding)) {
           $config->ldapencoding = 'utf-8';
          }
  
          // Save settings
          set_config('host_url', trim($config->host_url), $this->pluginconfig);
+         set_config('start_tls', $config->start_tls, $this->pluginconfig);
          set_config('ldapencoding', trim($config->ldapencoding), $this->pluginconfig);
          set_config('pagesize', (int)trim($config->pagesize), $this->pluginconfig);
          set_config('contexts', $config->contexts, $this->pluginconfig);
          if($ldapconnection = ldap_connect_moodle($this->config->host_url, $this->config->ldap_version,
                                                   $this->config->user_type, $this->config->bind_dn,
                                                   $this->config->bind_pw, $this->config->opt_deref,
-                                                  $debuginfo)) {
+                                                  $debuginfo, $this->config->start_tls)) {
              $this->ldapconns = 1;
              $this->ldapconnection = $ldapconnection;
              return $ldapconnection;
          return '';
      }
  
 +    /**
 +     * Check if the diagnostic message for the LDAP login error tells us that the
 +     * login is denied because the user password has expired or the password needs
 +     * to be changed on first login (using interactive SMB/Windows logins, not
 +     * LDAP logins).
 +     *
 +     * @param string the diagnostic message for the LDAP login error
 +     * @return bool true if the password has expired or the password must be changed on first login
 +     */
 +    protected function ldap_ad_pwdexpired_from_diagmsg($diagmsg) {
 +        // The format of the diagnostic message is (actual examples from W2003 and W2008):
 +        // "80090308: LdapErr: DSID-0C090334, comment: AcceptSecurityContext error, data 52e, vece"  (W2003)
 +        // "80090308: LdapErr: DSID-0C090334, comment: AcceptSecurityContext error, data 773, vece"  (W2003)
 +        // "80090308: LdapErr: DSID-0C0903AA, comment: AcceptSecurityContext error, data 52e, v1771" (W2008)
 +        // "80090308: LdapErr: DSID-0C0903AA, comment: AcceptSecurityContext error, data 773, v1771" (W2008)
 +        // We are interested in the 'data nnn' part.
 +        //   if nnn == 773 then user must change password on first login
 +        //   if nnn == 532 then user password has expired
 +        $diagmsg = explode(',', $diagmsg);
 +        if (preg_match('/data (773|532)/i', trim($diagmsg[2]))) {
 +            return true;
 +        }
 +        return false;
 +    }
 +
  } // End of the class
diff --combined enrol/ldap/lib.php
@@@ -618,7 -618,7 +618,7 @@@ class enrol_ldap_plugin extends enrol_p
          if ($ldapconnection = ldap_connect_moodle($this->get_config('host_url'), $this->get_config('ldap_version'),
                                                    $this->get_config('user_type'), $this->get_config('bind_dn'),
                                                    $this->get_config('bind_pw'), $this->get_config('opt_deref'),
-                                                   $debuginfo)) {
+                                                   $debuginfo, $this->get_config('start_tls'))) {
              $this->ldapconns = 1;
              $this->ldapconnection = $ldapconnection;
              return $ldapconnection;
       * @param object role is a record from the mdl_role table.
       * @return array
       */
 -    protected function find_ext_enrolments ($ldapconnection, $memberuid, $role) {
 +    protected function find_ext_enrolments (&$ldapconnection, $memberuid, $role) {
          global $CFG;
          require_once($CFG->libdir.'/ldaplib.php');
  
          // Get all contexts and look for first matching user
          $ldap_contexts = explode(';', $ldap_contexts);
          $ldap_pagedresults = ldap_paged_results_supported($this->get_config('ldap_version'));
 -        $ldap_cookie = '';
          foreach ($ldap_contexts as $context) {
              $context = trim($context);
              if (empty($context)) {
                  continue;
              }
  
 +            $ldap_cookie = '';
              $flat_records = array();
              do {
                  if ($ldap_pagedresults) {