Merge branch 'wip_master_mdl-3941_add_support_for_LDAP_TLS' of git://github.com/iaren...
[moodle.git] / auth / ldap / auth.php
index 87ac0fc..6cd7fd7 100644 (file)
@@ -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');
 
@@ -192,11 +197,28 @@ class auth_plugin_ldap extends auth_plugin_base {
 
         // 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;
     }
 
     /**
@@ -593,7 +615,7 @@ class auth_plugin_ldap extends auth_plugin_base {
             $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();
@@ -711,7 +733,7 @@ class auth_plugin_ldap extends auth_plugin_base {
 /// 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)
@@ -2127,4 +2149,29 @@ class auth_plugin_ldap extends auth_plugin_base {
         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