password_lockout: Move more helper methods to a base class
[nivanova/samba-autobuild/.git] / source4 / dsdb / tests / python / password_lockout.py
index e6badbce4e611234e65f866dc28de07c5ab6f612..6bf0d8f58924cafe24196ab3e8c691adc4d4598a 100755 (executable)
@@ -33,7 +33,7 @@ from samba.tests import delete_force
 from samba.dcerpc import security, samr
 from samba.ndr import ndr_unpack
 
-parser = optparse.OptionParser("passwords.py [options] <host>")
+parser = optparse.OptionParser("password_lockout.py [options] <host>")
 sambaopts = options.SambaOptions(parser)
 parser.add_option_group(sambaopts)
 parser.add_option_group(options.VersionOptions(parser))
@@ -57,25 +57,20 @@ global_creds = credopts.get_credentials(lp)
 global_creds.set_gensec_features(global_creds.get_gensec_features() |
                                  gensec.FEATURE_SEAL)
 
-def insta_creds(template=global_creds):
-    # get a copy of the global creds or a the passed in creds
-    c = Credentials()
-    c.set_username("testuser")
-    c.set_password("thatsAcomplPASS1")
-    c.set_domain(template.get_domain())
-    c.set_realm(template.get_realm())
-    c.set_workstation(template.get_workstation())
-    c.set_gensec_features(c.get_gensec_features()
-                          | gensec.FEATURE_SEAL)
-    c.set_kerberos_state(template.get_kerberos_state())
-    return c
+template_creds = Credentials()
+template_creds.set_username("testuser")
+template_creds.set_password("thatsAcomplPASS1")
+template_creds.set_domain(global_creds.get_domain())
+template_creds.set_realm(global_creds.get_realm())
+template_creds.set_workstation(global_creds.get_workstation())
+template_creds.set_gensec_features(global_creds.get_gensec_features())
+template_creds.set_kerberos_state(global_creds.get_kerberos_state())
 
 #
 # Tests start here
 #
 
-class PasswordTests(samba.tests.TestCase):
-
+class BasePasswordTestCase(samba.tests.TestCase):
     def _open_samr_user(self, res):
         self.assertTrue("objectSid" in res[0])
 
@@ -93,41 +88,6 @@ class PasswordTests(samba.tests.TestCase):
         self.samr.SetUserInfo(samr_user, 16, acb_info)
         self.samr.Close(samr_user)
 
-    def _reset_ldap_lockoutTime(self, res):
-        self.ldb.modify_ldif("""
-dn: """ + str(res[0].dn) + """
-changetype: modify
-replace: lockoutTime
-lockoutTime: 0
-""")
-
-    def _reset_ldap_userAccountControl(self, res):
-        self.assertTrue("userAccountControl" in res[0])
-        self.assertTrue("msDS-User-Account-Control-Computed" in res[0])
-
-        uac = int(res[0]["userAccountControl"][0])
-        uacc = int(res[0]["msDS-User-Account-Control-Computed"][0])
-
-        uac |= uacc
-        uac = uac & ~dsdb.UF_LOCKOUT
-
-        self.ldb.modify_ldif("""
-dn: """ + str(res[0].dn) + """
-changetype: modify
-replace: userAccountControl
-userAccountControl: %d
-""" % uac)
-
-    def _reset_by_method(self, res, method):
-        if method is "ldap_userAccountControl":
-            self._reset_ldap_userAccountControl(res)
-        elif method is "ldap_lockoutTime":
-            self._reset_ldap_lockoutTime(res)
-        elif method is "samr":
-            self._reset_samr(res)
-        else:
-            self.assertTrue(False, msg="Invalid reset method[%s]" % method)
-
     def _check_attribute(self, res, name, value):
         if value is None:
             self.assertTrue(name not in res[0],
@@ -190,6 +150,7 @@ userAccountControl: %d
     def _check_account(self, dn,
                        badPwdCount=None,
                        badPasswordTime=None,
+                       logonCount=None,
                        lastLogon=None,
                        lastLogonTimestamp=None,
                        lockoutTime=None,
@@ -206,6 +167,7 @@ userAccountControl: %d
            "badPasswordTime",
            "lastLogon",
            "lastLogonTimestamp",
+           "logonCount",
            "lockoutTime",
            "userAccountControl",
            "msDS-User-Account-Control-Computed"
@@ -219,6 +181,7 @@ userAccountControl: %d
         self.assertTrue(len(res) == 1)
         self._check_attribute(res, "badPwdCount", badPwdCount)
         self._check_attribute(res, "badPasswordTime", badPasswordTime)
+        self._check_attribute(res, "logonCount", logonCount)
         self._check_attribute(res, "lastLogon", lastLogon)
         self._check_attribute(res, "lastLogonTimestamp", lastLogonTimestamp)
         self._check_attribute(res, "lockoutTime", lockoutTime)
@@ -226,6 +189,9 @@ userAccountControl: %d
         self._check_attribute(res, "msDS-User-Account-Control-Computed",
                               msDSUserAccountControlComputed)
 
+        lastLogon = int(res[0]["lastLogon"][0])
+        logonCount = int(res[0]["logonCount"][0])
+
         samr_user = self._open_samr_user(res)
         uinfo3 = self.samr.QueryUserInfo(samr_user, 3)
         uinfo5 = self.samr.QueryUserInfo(samr_user, 5)
@@ -253,14 +219,20 @@ userAccountControl: %d
 
         self.assertEquals(uinfo3.acct_flags, expected_acb_info)
         self.assertEquals(uinfo3.bad_password_count, expected_bad_password_count)
+        self.assertEquals(uinfo3.last_logon, lastLogon)
+        self.assertEquals(uinfo3.logon_count, logonCount)
 
         self.assertEquals(uinfo5.acct_flags, expected_acb_info)
         self.assertEquals(uinfo5.bad_password_count, effective_bad_password_count)
+        self.assertEquals(uinfo5.last_logon, lastLogon)
+        self.assertEquals(uinfo5.logon_count, logonCount)
 
         self.assertEquals(uinfo16.acct_flags, expected_acb_info)
 
         self.assertEquals(uinfo21.acct_flags, expected_acb_info)
         self.assertEquals(uinfo21.bad_password_count, effective_bad_password_count)
+        self.assertEquals(uinfo21.last_logon, lastLogon)
+        self.assertEquals(uinfo21.logon_count, logonCount)
 
         # check LDAP again and make sure the samr.QueryUserInfo
         # doesn't have any impact.
@@ -272,113 +244,34 @@ userAccountControl: %d
         time.sleep(0.01)
         return res
 
-    def assertLoginFailure(self, url, creds, lp, errno=ERR_INVALID_CREDENTIALS):
-        try:
-            ldb = SamDB(url=url, credentials=creds, lp=lp)
-            self.fail("Login unexpectedly succeeded")
-        except LdbError, (num, msg):
-            if errno is not None:
-                self.assertEquals(num, errno, ("Login failed in the wrong way"
-                                               "(got err %d, expected %d)" %
-                                               (num, errno)))
-
-    def setUp(self):
-        super(PasswordTests, self).setUp()
-
-        self.ldb = SamDB(url=host_url, session_info=system_session(lp),
-                         credentials=global_creds, lp=lp)
-
-        # Gets back the basedn
-        base_dn = self.ldb.domain_dn()
-
-        # Gets back the configuration basedn
-        configuration_dn = self.ldb.get_config_basedn().get_linearized()
-
-        # Get the old "dSHeuristics" if it was set
-        dsheuristics = self.ldb.get_dsheuristics()
-
-        # Reset the "dSHeuristics" as they were before
-        self.addCleanup(self.ldb.set_dsheuristics, dsheuristics)
-
-        res = self.ldb.search(base_dn,
-                         scope=SCOPE_BASE, attrs=["lockoutDuration", "lockOutObservationWindow", "lockoutThreshold"])
-
-        if "lockoutDuration" in res[0]:
-            lockoutDuration = res[0]["lockoutDuration"][0]
-        else:
-            lockoutDuration = 0
-
-        if "lockoutObservationWindow" in res[0]:
-            lockoutObservationWindow = res[0]["lockoutObservationWindow"][0]
-        else:
-            lockoutObservationWindow = 0
+    def _readd_user(self, creds, lockOutObservationWindow=0):
+        username = creds.get_username()
+        userpass = creds.get_password()
+        userdn = "cn=%s,cn=users,%s" % (username, self.base_dn)
 
-        if "lockoutThreshold" in res[0]:
-            lockoutThreshold = res[0]["lockoutThreshold"][0]
+        use_kerberos = creds.get_kerberos_state()
+        if use_kerberos == MUST_USE_KERBEROS:
+            logoncount_relation = 'greater'
+            lastlogon_relation = 'greater'
         else:
-            lockoutTreshold = 0
-
-        self.addCleanup(self.ldb.modify_ldif, """
-dn: """ + base_dn + """
-changetype: modify
-replace: lockoutDuration
-lockoutDuration: """ + str(lockoutDuration) + """
-replace: lockoutObservationWindow
-lockoutObservationWindow: """ + str(lockoutObservationWindow) + """
-replace: lockoutThreshold
-lockoutThreshold: """ + str(lockoutThreshold) + """
-""")
-
-        m = Message()
-        m.dn = Dn(self.ldb, base_dn)
-
-        self.account_lockout_duration = 10
-        account_lockout_duration_ticks = -int(self.account_lockout_duration * (1e7))
-
-        m["lockoutDuration"] = MessageElement(str(account_lockout_duration_ticks),
-                                              FLAG_MOD_REPLACE, "lockoutDuration")
-
-        account_lockout_threshold = 3
-        m["lockoutThreshold"] = MessageElement(str(account_lockout_threshold),
-                                               FLAG_MOD_REPLACE, "lockoutThreshold")
-
-        self.lockout_observation_window = 5
-        lockout_observation_window_ticks = -int(self.lockout_observation_window * (1e7))
-
-        m["lockOutObservationWindow"] = MessageElement(str(lockout_observation_window_ticks),
-                                                       FLAG_MOD_REPLACE, "lockOutObservationWindow")
-
-        self.ldb.modify(m)
-
-        # Set the "dSHeuristics" to activate the correct "userPassword" behaviour
-        self.ldb.set_dsheuristics("000000001")
-
-        # Get the old "minPwdAge"
-        minPwdAge = self.ldb.get_minPwdAge()
-
-        # Reset the "minPwdAge" as it was before
-        self.addCleanup(self.ldb.set_minPwdAge, minPwdAge)
-
-        # Set it temporarely to "0"
-        self.ldb.set_minPwdAge("0")
-
-        self.base_dn = self.ldb.domain_dn()
-
-        self.domain_sid = security.dom_sid(self.ldb.get_domain_sid())
-        self.samr = samr.samr("ncacn_ip_tcp:%s[sign]" % host, lp, global_creds)
-        self.samr_handle = self.samr.Connect2(None, security.SEC_FLAG_MAXIMUM_ALLOWED)
-        self.samr_domain = self.samr.OpenDomain(self.samr_handle, security.SEC_FLAG_MAXIMUM_ALLOWED, self.domain_sid)
+            logoncount_relation = 'equal'
+            if lockOutObservationWindow == 0:
+                lastlogon_relation = 'greater'
+            else:
+                lastlogon_relation = 'equal'
 
-        # (Re)adds the test user "testuser" with no password atm
-        delete_force(self.ldb, "cn=testuser,cn=users," + self.base_dn)
+        delete_force(self.ldb, userdn)
         self.ldb.add({
-             "dn": "cn=testuser,cn=users," + self.base_dn,
+             "dn": userdn,
              "objectclass": "user",
-             "sAMAccountName": "testuser"})
+             "sAMAccountName": username})
 
-        res = self._check_account("cn=testuser,cn=users," + self.base_dn,
+        self.addCleanup(delete_force, self.ldb, userdn)
+
+        res = self._check_account(userdn,
                                   badPwdCount=0,
                                   badPasswordTime=0,
+                                  logonCount=0,
                                   lastLogon=0,
                                   lastLogonTimestamp=('absent', None),
                                   userAccountControl=
@@ -392,9 +285,10 @@ lockoutThreshold: """ + str(lockoutThreshold) + """
         # It doesn't create "lockoutTime" = 0.
         self._reset_samr(res)
 
-        res = self._check_account("cn=testuser,cn=users," + self.base_dn,
+        res = self._check_account(userdn,
                                   badPwdCount=0,
                                   badPasswordTime=0,
+                                  logonCount=0,
                                   lastLogon=0,
                                   lastLogonTimestamp=('absent', None),
                                   userAccountControl=
@@ -408,7 +302,7 @@ lockoutThreshold: """ + str(lockoutThreshold) + """
         # wrong old password
         try:
             self.ldb.modify_ldif("""
-dn: cn=testuser,cn=users,""" + self.base_dn + """
+dn: """ + userdn + """
 changetype: modify
 delete: userPassword
 userPassword: noPassword
@@ -420,11 +314,12 @@ userPassword: thatsAcomplPASS2
             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
             # Windows (2008 at least) seems to have some small bug here: it
             # returns "0000056A" on longer (always wrong) previous passwords.
-            self.assertTrue('00000056' in msg)
+            self.assertTrue('00000056' in msg, msg)
 
-        res = self._check_account("cn=testuser,cn=users," + self.base_dn,
+        res = self._check_account(userdn,
                                   badPwdCount=1,
                                   badPasswordTime=("greater", 0),
+                                  logonCount=0,
                                   lastLogon=0,
                                   lastLogonTimestamp=('absent', None),
                                   userAccountControl=
@@ -433,6 +328,7 @@ userPassword: thatsAcomplPASS2
                                     dsdb.UF_PASSWD_NOTREQD,
                                   msDSUserAccountControlComputed=
                                     dsdb.UF_PASSWORD_EXPIRED)
+        badPwdCount = int(res[0]["badPwdCount"][0])
         badPasswordTime = int(res[0]["badPasswordTime"][0])
 
         # Sets the initial user password with a "special" password change
@@ -440,16 +336,17 @@ userPassword: thatsAcomplPASS2
         # only be performed by someone which has password set privileges on the
         # account (at least in s4 we do handle it like that).
         self.ldb.modify_ldif("""
-dn: cn=testuser,cn=users,""" + self.base_dn + """
+dn: """ + userdn + """
 changetype: modify
 delete: userPassword
 add: userPassword
-userPassword: thatsAcomplPASS1
+userPassword: """ + userpass + """
 """)
 
-        res = self._check_account("cn=testuser,cn=users," + self.base_dn,
-                                  badPwdCount=1,
+        res = self._check_account(userdn,
+                                  badPwdCount=badPwdCount,
                                   badPasswordTime=badPasswordTime,
+                                  logonCount=0,
                                   lastLogon=0,
                                   lastLogonTimestamp=('absent', None),
                                   userAccountControl=
@@ -459,219 +356,310 @@ userPassword: thatsAcomplPASS1
                                   msDSUserAccountControlComputed=0)
 
         # Enables the user account
-        self.ldb.enable_account("(sAMAccountName=testuser)")
+        self.ldb.enable_account("(sAMAccountName=%s)" % username)
 
-        res = self._check_account("cn=testuser,cn=users," + self.base_dn,
-                                  badPwdCount=1,
+        res = self._check_account(userdn,
+                                  badPwdCount=badPwdCount,
                                   badPasswordTime=badPasswordTime,
+                                  logonCount=0,
                                   lastLogon=0,
                                   lastLogonTimestamp=('absent', None),
                                   userAccountControl=
                                     dsdb.UF_NORMAL_ACCOUNT,
                                   msDSUserAccountControlComputed=0)
+        if lockOutObservationWindow != 0:
+            time.sleep(lockOutObservationWindow + 1)
+            effective_bad_password_count = 0
+        else:
+            effective_bad_password_count = badPwdCount
 
-        # Open a second LDB connection with the user credentials. Use the
-        # command line credentials for informations like the domain, the realm
-        # and the workstation.
-        creds2 = insta_creds()
+        res = self._check_account(userdn,
+                                  badPwdCount=badPwdCount,
+                                  effective_bad_password_count=effective_bad_password_count,
+                                  badPasswordTime=badPasswordTime,
+                                  logonCount=0,
+                                  lastLogon=0,
+                                  lastLogonTimestamp=('absent', None),
+                                  userAccountControl=
+                                    dsdb.UF_NORMAL_ACCOUNT,
+                                  msDSUserAccountControlComputed=0)
 
-        self.ldb2 = SamDB(url=host_url, credentials=creds2, lp=lp)
+        ldb = SamDB(url=host_url, credentials=creds, lp=lp)
 
-        res = self._check_account("cn=testuser,cn=users," + self.base_dn,
-                                  badPwdCount=0,
+        if lockOutObservationWindow == 0:
+            badPwdCount = 0
+            effective_bad_password_count = 0
+        if use_kerberos == MUST_USE_KERBEROS:
+            badPwdCount = 0
+            effective_bad_password_count = 0
+
+        res = self._check_account(userdn,
+                                  badPwdCount=badPwdCount,
+                                  effective_bad_password_count=effective_bad_password_count,
                                   badPasswordTime=badPasswordTime,
-                                  lastLogon=('greater', 0),
-                                  lastLogonTimestamp=('greater', 0),
+                                  logonCount=(logoncount_relation, 0),
+                                  lastLogon=(lastlogon_relation, 0),
+                                  lastLogonTimestamp=('greater', badPasswordTime),
                                   userAccountControl=
                                     dsdb.UF_NORMAL_ACCOUNT,
                                   msDSUserAccountControlComputed=0)
 
+        logonCount = int(res[0]["logonCount"][0])
         lastLogon = int(res[0]["lastLogon"][0])
-        self.assertGreater(lastLogon, badPasswordTime)
-
-     # (Re)adds the test user "testuser3" with no password atm
-        delete_force(self.ldb, "cn=testuser3,cn=users," + self.base_dn)
-        self.ldb.add({
-             "dn": "cn=testuser3,cn=users," + self.base_dn,
-             "objectclass": "user",
-             "sAMAccountName": "testuser3"})
+        lastLogonTimestamp = int(res[0]["lastLogonTimestamp"][0])
+        if lastlogon_relation == 'greater':
+            self.assertGreater(lastLogon, badPasswordTime)
+            self.assertGreaterEqual(lastLogon, lastLogonTimestamp)
 
-        res = self._check_account("cn=testuser3,cn=users," + self.base_dn,
-                                  badPwdCount=0,
-                                  badPasswordTime=0,
-                                  lastLogon=0,
-                                  lastLogonTimestamp=('absent', None),
+        res = self._check_account(userdn,
+                                  badPwdCount=badPwdCount,
+                                  effective_bad_password_count=effective_bad_password_count,
+                                  badPasswordTime=badPasswordTime,
+                                  logonCount=logonCount,
+                                  lastLogon=lastLogon,
+                                  lastLogonTimestamp=lastLogonTimestamp,
                                   userAccountControl=
-                                    dsdb.UF_NORMAL_ACCOUNT |
-                                    dsdb.UF_ACCOUNTDISABLE |
-                                    dsdb.UF_PASSWD_NOTREQD,
-                                  msDSUserAccountControlComputed=
-                                    dsdb.UF_PASSWORD_EXPIRED)
+                                    dsdb.UF_NORMAL_ACCOUNT,
+                                  msDSUserAccountControlComputed=0)
+        return ldb
 
-        # Tests a password change when we don't have any password yet with a
-        # wrong old password
+    def assertLoginFailure(self, url, creds, lp, errno=ERR_INVALID_CREDENTIALS):
         try:
-            self.ldb.modify_ldif("""
-dn: cn=testuser3,cn=users,""" + self.base_dn + """
-changetype: modify
-delete: userPassword
-userPassword: noPassword
-add: userPassword
-userPassword: thatsAcomplPASS2
-""")
-            self.fail()
+            ldb = SamDB(url=url, credentials=creds, lp=lp)
+            self.fail("Login unexpectedly succeeded")
         except LdbError, (num, msg):
-            self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
-            # Windows (2008 at least) seems to have some small bug here: it
-            # returns "0000056A" on longer (always wrong) previous passwords.
-            self.assertTrue('00000056' in msg)
-
-        res = self._check_account("cn=testuser3,cn=users," + self.base_dn,
-                                  badPwdCount=1,
-                                  badPasswordTime=("greater", 0),
-                                  lastLogon=0,
-                                  lastLogonTimestamp=('absent', None),
-                                  userAccountControl=
-                                    dsdb.UF_NORMAL_ACCOUNT |
-                                    dsdb.UF_ACCOUNTDISABLE |
-                                    dsdb.UF_PASSWD_NOTREQD,
-                                  msDSUserAccountControlComputed=
-                                    dsdb.UF_PASSWORD_EXPIRED)
-        badPasswordTime3 = int(res[0]["badPasswordTime"][0])
+            if errno is not None:
+                self.assertEquals(num, errno, ("Login failed in the wrong way"
+                                               "(got err %d, expected %d)" %
+                                               (num, errno)))
 
-        # Sets the initial user password with a "special" password change
-        # I think that this internally is a password set operation and it can
-        # only be performed by someone which has password set privileges on the
-        # account (at least in s4 we do handle it like that).
-        self.ldb.modify_ldif("""
-dn: cn=testuser3,cn=users,""" + self.base_dn + """
-changetype: modify
-delete: userPassword
-add: userPassword
-userPassword: thatsAcomplPASS1
-""")
+    def setUp(self):
+        super(BasePasswordTestCase, self).setUp()
 
-        res = self._check_account("cn=testuser3,cn=users," + self.base_dn,
-                                  badPwdCount=1,
-                                  badPasswordTime=badPasswordTime3,
-                                  lastLogon=0,
-                                  lastLogonTimestamp=('absent', None),
-                                  userAccountControl=
-                                    dsdb.UF_NORMAL_ACCOUNT |
-                                    dsdb.UF_ACCOUNTDISABLE |
-                                    dsdb.UF_PASSWD_NOTREQD,
-                                  msDSUserAccountControlComputed=0)
+        self.ldb = SamDB(url=host_url, session_info=system_session(lp),
+                         credentials=global_creds, lp=lp)
 
-        # Enables the user account
-        self.ldb.enable_account("(sAMAccountName=testuser3)")
+        # Gets back the basedn
+        base_dn = self.ldb.domain_dn()
 
-        res = self._check_account("cn=testuser3,cn=users," + self.base_dn,
-                                  badPwdCount=1,
-                                  badPasswordTime=badPasswordTime3,
-                                  lastLogon=0,
-                                  lastLogonTimestamp=('absent', None),
-                                  userAccountControl=
-                                    dsdb.UF_NORMAL_ACCOUNT,
-                                  msDSUserAccountControlComputed=0)
+        # Gets back the configuration basedn
+        configuration_dn = self.ldb.get_config_basedn().get_linearized()
 
-        # Open a second LDB connection with the user credentials. Use the
-        # command line credentials for informations like the domain, the realm
-        # and the workstation.
-        creds3 = insta_creds()
-        creds3.set_username("testuser3")
-        creds3.set_password("thatsAcomplPASS1")
-        self.ldb3 = SamDB(url=host_url, credentials=creds3, lp=lp)
+        # Get the old "dSHeuristics" if it was set
+        dsheuristics = self.ldb.get_dsheuristics()
 
-        res = self._check_account("cn=testuser3,cn=users," + self.base_dn,
-                                  badPwdCount=0,
-                                  badPasswordTime=badPasswordTime3,
-                                  lastLogon=('greater', badPasswordTime3),
-                                  lastLogonTimestamp=('greater', badPasswordTime3),
-                                  userAccountControl=
-                                    dsdb.UF_NORMAL_ACCOUNT,
-                                  msDSUserAccountControlComputed=0)
+        # Reset the "dSHeuristics" as they were before
+        self.addCleanup(self.ldb.set_dsheuristics, dsheuristics)
 
-    def _test_userPassword_lockout_with_clear_change(self, method):
-        print "Performs a password cleartext change operation on 'userPassword'"
-        # Notice: This works only against Windows if "dSHeuristics" has been set
-        # properly
+        res = self.ldb.search(base_dn,
+                         scope=SCOPE_BASE, attrs=["lockoutDuration", "lockOutObservationWindow", "lockoutThreshold"])
 
-        res = self._check_account("cn=testuser,cn=users," + self.base_dn,
-                                  badPwdCount=0,
-                                  badPasswordTime=("greater", 0),
-                                  lastLogon=('greater', 0),
-                                  lastLogonTimestamp=('greater', 0),
-                                  userAccountControl=
-                                    dsdb.UF_NORMAL_ACCOUNT,
-                                  msDSUserAccountControlComputed=0)
-        badPasswordTime = int(res[0]["badPasswordTime"][0])
-        lastLogon = int(res[0]["lastLogon"][0])
-        lastLogonTimestamp = int(res[0]["lastLogonTimestamp"][0])
+        if "lockoutDuration" in res[0]:
+            lockoutDuration = res[0]["lockoutDuration"][0]
+        else:
+            lockoutDuration = 0
 
-        # Change password on a connection as another user
+        if "lockoutObservationWindow" in res[0]:
+            lockoutObservationWindow = res[0]["lockoutObservationWindow"][0]
+        else:
+            lockoutObservationWindow = 0
 
-        # Wrong old password
-        try:
-            self.ldb3.modify_ldif("""
-dn: cn=testuser,cn=users,""" + self.base_dn + """
+        if "lockoutThreshold" in res[0]:
+            lockoutThreshold = res[0]["lockoutThreshold"][0]
+        else:
+            lockoutTreshold = 0
+
+        self.addCleanup(self.ldb.modify_ldif, """
+dn: """ + base_dn + """
 changetype: modify
-delete: userPassword
-userPassword: thatsAcomplPASS1x
-add: userPassword
-userPassword: thatsAcomplPASS2
+replace: lockoutDuration
+lockoutDuration: """ + str(lockoutDuration) + """
+replace: lockoutObservationWindow
+lockoutObservationWindow: """ + str(lockoutObservationWindow) + """
+replace: lockoutThreshold
+lockoutThreshold: """ + str(lockoutThreshold) + """
 """)
-            self.fail()
-        except LdbError, (num, msg):
-            self.assertTrue('00000056' in msg)
-            self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
 
-        res = self._check_account("cn=testuser,cn=users," + self.base_dn,
+        m = Message()
+        m.dn = Dn(self.ldb, base_dn)
+
+        self.account_lockout_duration = 2
+        account_lockout_duration_ticks = -int(self.account_lockout_duration * (1e7))
+
+        m["lockoutDuration"] = MessageElement(str(account_lockout_duration_ticks),
+                                              FLAG_MOD_REPLACE, "lockoutDuration")
+
+        account_lockout_threshold = 3
+        m["lockoutThreshold"] = MessageElement(str(account_lockout_threshold),
+                                               FLAG_MOD_REPLACE, "lockoutThreshold")
+
+        self.lockout_observation_window = 2
+        lockout_observation_window_ticks = -int(self.lockout_observation_window * (1e7))
+
+        m["lockOutObservationWindow"] = MessageElement(str(lockout_observation_window_ticks),
+                                                       FLAG_MOD_REPLACE, "lockOutObservationWindow")
+
+        self.ldb.modify(m)
+
+        # Set the "dSHeuristics" to activate the correct "userPassword" behaviour
+        self.ldb.set_dsheuristics("000000001")
+
+        # Get the old "minPwdAge"
+        minPwdAge = self.ldb.get_minPwdAge()
+
+        # Reset the "minPwdAge" as it was before
+        self.addCleanup(self.ldb.set_minPwdAge, minPwdAge)
+
+        # Set it temporarely to "0"
+        self.ldb.set_minPwdAge("0")
+
+        self.base_dn = self.ldb.domain_dn()
+
+        self.domain_sid = security.dom_sid(self.ldb.get_domain_sid())
+        self.samr = samr.samr("ncacn_ip_tcp:%s[seal]" % host, lp, global_creds)
+        self.samr_handle = self.samr.Connect2(None, security.SEC_FLAG_MAXIMUM_ALLOWED)
+        self.samr_domain = self.samr.OpenDomain(self.samr_handle, security.SEC_FLAG_MAXIMUM_ALLOWED, self.domain_sid)
+
+        # (Re)adds the test user accounts
+        self.lockout1krb5_creds = self.insta_creds(template_creds,
+                                                   username="lockout1krb5",
+                                                   userpass="thatsAcomplPASS0",
+                                                   kerberos_state=MUST_USE_KERBEROS)
+        self.lockout1krb5_ldb = self._readd_user(self.lockout1krb5_creds)
+        self.lockout2krb5_creds = self.insta_creds(template_creds,
+                                                   username="lockout2krb5",
+                                                   userpass="thatsAcomplPASS0",
+                                                   kerberos_state=MUST_USE_KERBEROS)
+        self.lockout2krb5_ldb = self._readd_user(self.lockout2krb5_creds,
+                                        lockOutObservationWindow=self.lockout_observation_window)
+        self.lockout1ntlm_creds = self.insta_creds(template_creds,
+                                                   username="lockout1ntlm",
+                                                   userpass="thatsAcomplPASS0",
+                                                   kerberos_state=DONT_USE_KERBEROS)
+        self.lockout1ntlm_ldb = self._readd_user(self.lockout1ntlm_creds)
+        self.lockout2ntlm_creds = self.insta_creds(template_creds,
+                                                   username="lockout2ntlm",
+                                                   userpass="thatsAcomplPASS0",
+                                                   kerberos_state=DONT_USE_KERBEROS)
+        self.lockout2ntlm_ldb = self._readd_user(self.lockout2ntlm_creds,
+                                        lockOutObservationWindow=self.lockout_observation_window)
+
+    def tearDown(self):
+        super(BasePasswordTestCase, self).tearDown()
+
+    def _test_login_lockout(self, creds):
+        username = creds.get_username()
+        userpass = creds.get_password()
+        userdn = "cn=%s,cn=users,%s" % (username, self.base_dn)
+
+        use_kerberos = creds.get_kerberos_state()
+        # This unlocks by waiting for account_lockout_duration
+        if use_kerberos == MUST_USE_KERBEROS:
+            logoncount_relation = 'greater'
+            lastlogon_relation = 'greater'
+            print "Performs a lockout attempt against LDAP using Kerberos"
+        else:
+            logoncount_relation = 'equal'
+            lastlogon_relation = 'equal'
+            print "Performs a lockout attempt against LDAP using NTLM"
+
+        # Change password on a connection as another user
+        res = self._check_account(userdn,
+                                  badPwdCount=0,
+                                  badPasswordTime=("greater", 0),
+                                  logonCount=(logoncount_relation, 0),
+                                  lastLogon=("greater", 0),
+                                  lastLogonTimestamp=("greater", 0),
+                                  userAccountControl=
+                                    dsdb.UF_NORMAL_ACCOUNT,
+                                  msDSUserAccountControlComputed=0)
+        badPasswordTime = int(res[0]["badPasswordTime"][0])
+        logonCount = int(res[0]["logonCount"][0])
+        lastLogon = int(res[0]["lastLogon"][0])
+        firstLogon = lastLogon
+        lastLogonTimestamp = int(res[0]["lastLogonTimestamp"][0])
+        print firstLogon
+        print lastLogonTimestamp
+
+
+        self.assertGreater(lastLogon, badPasswordTime)
+        self.assertGreaterEqual(lastLogon, lastLogonTimestamp)
+
+        # Open a second LDB connection with the user credentials. Use the
+        # command line credentials for informations like the domain, the realm
+        # and the workstation.
+        creds_lockout = self.insta_creds(creds)
+
+        # The wrong password
+        creds_lockout.set_password("thatsAcomplPASS1x")
+
+        self.assertLoginFailure(host_url, creds_lockout, lp)
+
+        res = self._check_account(userdn,
                                   badPwdCount=1,
                                   badPasswordTime=("greater", badPasswordTime),
+                                  logonCount=logonCount,
                                   lastLogon=lastLogon,
                                   lastLogonTimestamp=lastLogonTimestamp,
                                   userAccountControl=
                                     dsdb.UF_NORMAL_ACCOUNT,
-                                  msDSUserAccountControlComputed=0)
+                                  msDSUserAccountControlComputed=0,
+                                  msg='lastlogontimestamp with wrong password')
         badPasswordTime = int(res[0]["badPasswordTime"][0])
 
         # Correct old password
-        self.ldb3.modify_ldif("""
-dn: cn=testuser,cn=users,""" + self.base_dn + """
-changetype: modify
-delete: userPassword
-userPassword: thatsAcomplPASS1
-add: userPassword
-userPassword: thatsAcomplPASS2
-""")
+        creds_lockout.set_password(userpass)
 
-        res = self._check_account("cn=testuser,cn=users," + self.base_dn,
-                                  badPwdCount=1,
+        ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
+
+        # lastLogonTimestamp should not change
+        # lastLogon increases if badPwdCount is non-zero (!)
+        res = self._check_account(userdn,
+                                  badPwdCount=0,
                                   badPasswordTime=badPasswordTime,
+                                  logonCount=(logoncount_relation, logonCount),
+                                  lastLogon=('greater', lastLogon),
+                                  lastLogonTimestamp=lastLogonTimestamp,
+                                  userAccountControl=
+                                    dsdb.UF_NORMAL_ACCOUNT,
+                                  msDSUserAccountControlComputed=0,
+                                  msg='LLTimestamp is updated to lastlogon')
+
+        logonCount = int(res[0]["logonCount"][0])
+        lastLogon = int(res[0]["lastLogon"][0])
+        self.assertGreater(lastLogon, badPasswordTime)
+        self.assertGreaterEqual(lastLogon, lastLogonTimestamp)
+
+        # The wrong password
+        creds_lockout.set_password("thatsAcomplPASS1x")
+
+        self.assertLoginFailure(host_url, creds_lockout, lp)
+
+        res = self._check_account(userdn,
+                                  badPwdCount=1,
+                                  badPasswordTime=("greater", badPasswordTime),
+                                  logonCount=logonCount,
                                   lastLogon=lastLogon,
                                   lastLogonTimestamp=lastLogonTimestamp,
                                   userAccountControl=
                                     dsdb.UF_NORMAL_ACCOUNT,
                                   msDSUserAccountControlComputed=0)
+        badPasswordTime = int(res[0]["badPasswordTime"][0])
+
+        # The wrong password
+        creds_lockout.set_password("thatsAcomplPASS1x")
 
-        # Wrong old password
         try:
-            self.ldb3.modify_ldif("""
-dn: cn=testuser,cn=users,""" + self.base_dn + """
-changetype: modify
-delete: userPassword
-userPassword: thatsAcomplPASS1x
-add: userPassword
-userPassword: thatsAcomplPASS2
-""")
+            ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
             self.fail()
+
         except LdbError, (num, msg):
-            self.assertTrue('00000056' in msg)
-            self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
+            self.assertEquals(num, ERR_INVALID_CREDENTIALS)
 
-        res = self._check_account("cn=testuser,cn=users," + self.base_dn,
+        res = self._check_account(userdn,
                                   badPwdCount=2,
                                   badPasswordTime=("greater", badPasswordTime),
+                                  logonCount=logonCount,
                                   lastLogon=lastLogon,
                                   lastLogonTimestamp=lastLogonTimestamp,
                                   userAccountControl=
@@ -681,24 +669,20 @@ userPassword: thatsAcomplPASS2
 
         print "two failed password change"
 
-        # Wrong old password
+        # The wrong password
+        creds_lockout.set_password("thatsAcomplPASS1x")
+
         try:
-            self.ldb3.modify_ldif("""
-dn: cn=testuser,cn=users,""" + self.base_dn + """
-changetype: modify
-delete: userPassword
-userPassword: thatsAcomplPASS1x
-add: userPassword
-userPassword: thatsAcomplPASS2
-""")
+            ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
             self.fail()
+
         except LdbError, (num, msg):
-            self.assertTrue('00000056' in msg)
-            self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
+            self.assertEquals(num, ERR_INVALID_CREDENTIALS)
 
-        res = self._check_account("cn=testuser,cn=users," + self.base_dn,
+        res = self._check_account(userdn,
                                   badPwdCount=3,
                                   badPasswordTime=("greater", badPasswordTime),
+                                  logonCount=logonCount,
                                   lastLogon=lastLogon,
                                   lastLogonTimestamp=lastLogonTimestamp,
                                   lockoutTime=("greater", badPasswordTime),
@@ -708,24 +692,18 @@ userPassword: thatsAcomplPASS2
         badPasswordTime = int(res[0]["badPasswordTime"][0])
         lockoutTime = int(res[0]["lockoutTime"][0])
 
-        # Wrong old password
+        # The wrong password
+        creds_lockout.set_password("thatsAcomplPASS1x")
         try:
-            self.ldb3.modify_ldif("""
-dn: cn=testuser,cn=users,""" + self.base_dn + """
-changetype: modify
-delete: userPassword
-userPassword: thatsAcomplPASS1x
-add: userPassword
-userPassword: thatsAcomplPASS2
-""")
+            ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
             self.fail()
         except LdbError, (num, msg):
-            self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
-            self.assertTrue('00000775' in msg)
+            self.assertEquals(num, ERR_INVALID_CREDENTIALS)
 
-        res = self._check_account("cn=testuser,cn=users," + self.base_dn,
+        res = self._check_account(userdn,
                                   badPwdCount=3,
                                   badPasswordTime=badPasswordTime,
+                                  logonCount=logonCount,
                                   lastLogon=lastLogon,
                                   lastLogonTimestamp=lastLogonTimestamp,
                                   lockoutTime=lockoutTime,
@@ -733,49 +711,37 @@ userPassword: thatsAcomplPASS2
                                     dsdb.UF_NORMAL_ACCOUNT,
                                   msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
 
-        # Wrong old password
+        # The wrong password
+        creds_lockout.set_password("thatsAcomplPASS1x")
         try:
-            self.ldb3.modify_ldif("""
-dn: cn=testuser,cn=users,""" + self.base_dn + """
-changetype: modify
-delete: userPassword
-userPassword: thatsAcomplPASS1x
-add: userPassword
-userPassword: thatsAcomplPASS2
-""")
+            ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
             self.fail()
         except LdbError, (num, msg):
-            self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
-            self.assertTrue('00000775' in msg)
+            self.assertEquals(num, ERR_INVALID_CREDENTIALS)
 
-        res = self._check_account("cn=testuser,cn=users," + self.base_dn,
+        res = self._check_account(userdn,
                                   badPwdCount=3,
                                   badPasswordTime=badPasswordTime,
-                                  lockoutTime=lockoutTime,
+                                  logonCount=logonCount,
                                   lastLogon=lastLogon,
                                   lastLogonTimestamp=lastLogonTimestamp,
+                                  lockoutTime=lockoutTime,
                                   userAccountControl=
                                     dsdb.UF_NORMAL_ACCOUNT,
                                   msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
 
+        # The correct password, but we are locked out
+        creds_lockout.set_password(userpass)
         try:
-            # Correct old password
-            self.ldb3.modify_ldif("""
-dn: cn=testuser,cn=users,""" + self.base_dn + """
-changetype: modify
-delete: userPassword
-userPassword: thatsAcomplPASS2
-add: userPassword
-userPassword: thatsAcomplPASS2x
-""")
+            ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
             self.fail()
         except LdbError, (num, msg):
-            self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
-            self.assertTrue('0000775' in msg)
+            self.assertEquals(num, ERR_INVALID_CREDENTIALS)
 
-        res = self._check_account("cn=testuser,cn=users," + self.base_dn,
+        res = self._check_account(userdn,
                                   badPwdCount=3,
                                   badPasswordTime=badPasswordTime,
+                                  logonCount=logonCount,
                                   lastLogon=lastLogon,
                                   lastLogonTimestamp=lastLogonTimestamp,
                                   lockoutTime=lockoutTime,
@@ -783,121 +749,91 @@ userPassword: thatsAcomplPASS2x
                                     dsdb.UF_NORMAL_ACCOUNT,
                                   msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
 
-        # Now reset the password, which does NOT change the lockout!
-        self.ldb.modify_ldif("""
-dn: cn=testuser,cn=users,""" + self.base_dn + """
-changetype: modify
-replace: userPassword
-userPassword: thatsAcomplPASS2
-""")
+        # wait for the lockout to end
+        time.sleep(self.account_lockout_duration + 1)
+        print self.account_lockout_duration + 1
 
-        res = self._check_account("cn=testuser,cn=users," + self.base_dn,
-                                  badPwdCount=3,
+        res = self._check_account(userdn,
+                                  badPwdCount=3, effective_bad_password_count=0,
                                   badPasswordTime=badPasswordTime,
+                                  logonCount=logonCount,
+                                  lockoutTime=lockoutTime,
                                   lastLogon=lastLogon,
                                   lastLogonTimestamp=lastLogonTimestamp,
-                                  lockoutTime=lockoutTime,
                                   userAccountControl=
                                     dsdb.UF_NORMAL_ACCOUNT,
-                                  msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
+                                  msDSUserAccountControlComputed=0)
 
-        try:
-            # Correct old password
-            self.ldb3.modify_ldif("""
-dn: cn=testuser,cn=users,""" + self.base_dn + """
-changetype: modify
-delete: userPassword
-userPassword: thatsAcomplPASS2
-add: userPassword
-userPassword: thatsAcomplPASS2x
-""")
-            self.fail()
-        except LdbError, (num, msg):
-            self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
-            self.assertTrue('0000775' in msg)
+        # The correct password after letting the timeout expire
 
-        res = self._check_account("cn=testuser,cn=users," + self.base_dn,
-                                  badPwdCount=3,
-                                  badPasswordTime=badPasswordTime,
-                                  lastLogon=lastLogon,
-                                  lastLogonTimestamp=lastLogonTimestamp,
-                                  lockoutTime=lockoutTime,
-                                  userAccountControl=
-                                    dsdb.UF_NORMAL_ACCOUNT,
-                                  msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
+        creds_lockout.set_password(userpass)
 
-        m = Message()
-        m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn)
-        m["userAccountControl"] = MessageElement(
-          str(dsdb.UF_LOCKOUT),
-          FLAG_MOD_REPLACE, "userAccountControl")
+        creds_lockout2 = self.insta_creds(creds_lockout)
 
-        self.ldb.modify(m)
+        ldb_lockout = SamDB(url=host_url, credentials=creds_lockout2, lp=lp)
+        time.sleep(3)
 
-        # This shows that setting the UF_LOCKOUT flag alone makes no difference
-        res = self._check_account("cn=testuser,cn=users," + self.base_dn,
-                                  badPwdCount=3,
+        res = self._check_account(userdn,
+                                  badPwdCount=0,
                                   badPasswordTime=badPasswordTime,
-                                  lastLogon=lastLogon,
+                                  logonCount=(logoncount_relation, logonCount),
+                                  lastLogon=(lastlogon_relation, lastLogon),
                                   lastLogonTimestamp=lastLogonTimestamp,
-                                  lockoutTime=lockoutTime,
+                                  lockoutTime=0,
                                   userAccountControl=
                                     dsdb.UF_NORMAL_ACCOUNT,
-                                  msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
+                                  msDSUserAccountControlComputed=0,
+                                  msg="lastLogon is way off")
 
-        # This shows that setting the UF_LOCKOUT flag makes no difference
+        logonCount = int(res[0]["logonCount"][0])
+        lastLogon = int(res[0]["lastLogon"][0])
+
+        # The wrong password
+        creds_lockout.set_password("thatsAcomplPASS1x")
         try:
-            # Correct old password
-            self.ldb3.modify_ldif("""
-dn: cn=testuser,cn=users,""" + self.base_dn + """
-changetype: modify
-delete: unicodePwd
-unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
-add: unicodePwd
-unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2x\"".encode('utf-16-le')) + """
-""")
+            ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
             self.fail()
         except LdbError, (num, msg):
-            self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
-            self.assertTrue('0000775' in msg)
+            self.assertEquals(num, ERR_INVALID_CREDENTIALS)
 
-        res = self._check_account("cn=testuser,cn=users," + self.base_dn,
-                                  badPwdCount=3,
-                                  badPasswordTime=badPasswordTime,
-                                  lockoutTime=lockoutTime,
+        res = self._check_account(userdn,
+                                  badPwdCount=1,
+                                  badPasswordTime=("greater", badPasswordTime),
+                                  logonCount=logonCount,
+                                  lockoutTime=0,
                                   lastLogon=lastLogon,
                                   lastLogonTimestamp=lastLogonTimestamp,
                                   userAccountControl=
                                     dsdb.UF_NORMAL_ACCOUNT,
-                                  msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
+                                  msDSUserAccountControlComputed=0)
+        badPasswordTime = int(res[0]["badPasswordTime"][0])
 
-        self._reset_by_method(res, method)
+        # The wrong password
+        creds_lockout.set_password("thatsAcomplPASS1x")
+        try:
+            ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
+            self.fail()
+        except LdbError, (num, msg):
+            self.assertEquals(num, ERR_INVALID_CREDENTIALS)
 
-        # Here bad password counts are reset without logon success.
-        res = self._check_account("cn=testuser,cn=users," + self.base_dn,
-                                  badPwdCount=0,
-                                  badPasswordTime=badPasswordTime,
+        res = self._check_account(userdn,
+                                  badPwdCount=2,
+                                  badPasswordTime=("greater", badPasswordTime),
+                                  logonCount=logonCount,
                                   lockoutTime=0,
                                   lastLogon=lastLogon,
                                   lastLogonTimestamp=lastLogonTimestamp,
                                   userAccountControl=
                                     dsdb.UF_NORMAL_ACCOUNT,
                                   msDSUserAccountControlComputed=0)
+        badPasswordTime = int(res[0]["badPasswordTime"][0])
 
-        # The correct password after doing the unlock
-
-        self.ldb3.modify_ldif("""
-dn: cn=testuser,cn=users,""" + self.base_dn + """
-changetype: modify
-delete: unicodePwd
-unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
-add: unicodePwd
-unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2x\"".encode('utf-16-le')) + """
-""")
+        time.sleep(self.lockout_observation_window + 1)
 
-        res = self._check_account("cn=testuser,cn=users," + self.base_dn,
-                                  badPwdCount=0,
+        res = self._check_account(userdn,
+                                  badPwdCount=2, effective_bad_password_count=0,
                                   badPasswordTime=badPasswordTime,
+                                  logonCount=logonCount,
                                   lockoutTime=0,
                                   lastLogon=lastLogon,
                                   lastLogonTimestamp=lastLogonTimestamp,
@@ -905,24 +841,18 @@ unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2x\"".encode('utf-16-le'))
                                     dsdb.UF_NORMAL_ACCOUNT,
                                   msDSUserAccountControlComputed=0)
 
-        # Wrong old password
+        # The wrong password
+        creds_lockout.set_password("thatsAcomplPASS1x")
         try:
-            self.ldb3.modify_ldif("""
-dn: cn=testuser,cn=users,""" + self.base_dn + """
-changetype: modify
-delete: userPassword
-userPassword: thatsAcomplPASS1xyz
-add: userPassword
-userPassword: thatsAcomplPASS2XYZ
-""")
+            ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
             self.fail()
         except LdbError, (num, msg):
-            self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
-            self.assertTrue('00000056' in msg)
+            self.assertEquals(num, ERR_INVALID_CREDENTIALS)
 
-        res = self._check_account("cn=testuser,cn=users," + self.base_dn,
+        res = self._check_account(userdn,
                                   badPwdCount=1,
                                   badPasswordTime=("greater", badPasswordTime),
+                                  logonCount=logonCount,
                                   lockoutTime=0,
                                   lastLogon=lastLogon,
                                   lastLogonTimestamp=lastLogonTimestamp,
@@ -931,176 +861,267 @@ userPassword: thatsAcomplPASS2XYZ
                                   msDSUserAccountControlComputed=0)
         badPasswordTime = int(res[0]["badPasswordTime"][0])
 
-        # Wrong old password
-        try:
-            self.ldb3.modify_ldif("""
-dn: cn=testuser,cn=users,""" + self.base_dn + """
-changetype: modify
-delete: userPassword
-userPassword: thatsAcomplPASS1xyz
-add: userPassword
-userPassword: thatsAcomplPASS2XYZ
-""")
-            self.fail()
-        except LdbError, (num, msg):
-            self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
-            self.assertTrue('00000056' in msg)
+        # The correct password without letting the timeout expire
+        creds_lockout.set_password(userpass)
+        ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
 
-        res = self._check_account("cn=testuser,cn=users," + self.base_dn,
-                                  badPwdCount=2,
-                                  badPasswordTime=("greater", badPasswordTime),
+        res = self._check_account(userdn,
+                                  badPwdCount=0,
+                                  badPasswordTime=badPasswordTime,
+                                  logonCount=(logoncount_relation, logonCount),
                                   lockoutTime=0,
-                                  lastLogon=lastLogon,
+                                  lastLogon=("greater", lastLogon),
                                   lastLogonTimestamp=lastLogonTimestamp,
                                   userAccountControl=
                                     dsdb.UF_NORMAL_ACCOUNT,
                                   msDSUserAccountControlComputed=0)
+
+    def _test_multiple_logon(self, creds):
+        # Test the happy case in which a user logs on correctly, then
+        # logs on correctly again, so that the bad password and
+        # lockout times are both zero the second time. The lastlogon
+        # time should increase.
+
+        # Open a second LDB connection with the user credentials. Use the
+        # command line credentials for informations like the domain, the realm
+        # and the workstation.
+        username = creds.get_username()
+        userdn = "cn=%s,cn=users,%s" % (username, self.base_dn)
+
+        use_kerberos = creds.get_kerberos_state()
+        if use_kerberos == MUST_USE_KERBEROS:
+            print "Testing multiple logon with Kerberos"
+            logoncount_relation = 'greater'
+            lastlogon_relation = 'greater'
+        else:
+            print "Testing multiple logon with NTLM"
+            logoncount_relation = 'equal'
+            lastlogon_relation = 'equal'
+
+        SamDB(url=host_url, credentials=self.insta_creds(creds), lp=lp)
+
+        res = self._check_account(userdn,
+                                  badPwdCount=0,
+                                  badPasswordTime=("greater", 0),
+                                  logonCount=(logoncount_relation, 0),
+                                  lastLogon=("greater", 0),
+                                  lastLogonTimestamp=("greater", 0),
+                                  userAccountControl=
+                                    dsdb.UF_NORMAL_ACCOUNT,
+                                  msDSUserAccountControlComputed=0)
         badPasswordTime = int(res[0]["badPasswordTime"][0])
+        logonCount = int(res[0]["logonCount"][0])
+        lastLogon = int(res[0]["lastLogon"][0])
+        lastLogonTimestamp = int(res[0]["lastLogonTimestamp"][0])
+        firstLogon = lastLogon
+        print "last logon is %d" % lastLogon
+        self.assertGreater(lastLogon, badPasswordTime)
+        self.assertGreaterEqual(lastLogon, lastLogonTimestamp)
 
-        self._reset_ldap_lockoutTime(res)
+        time.sleep(1)
+        SamDB(url=host_url, credentials=self.insta_creds(creds), lp=lp)
 
-        res = self._check_account("cn=testuser,cn=users," + self.base_dn,
+        res = self._check_account(userdn,
                                   badPwdCount=0,
                                   badPasswordTime=badPasswordTime,
-                                  lastLogon=lastLogon,
+                                  logonCount=(logoncount_relation, logonCount),
+                                  lastLogon=(lastlogon_relation, lastLogon),
+                                  lastLogonTimestamp=lastLogonTimestamp,
+                                  userAccountControl=
+                                  dsdb.UF_NORMAL_ACCOUNT,
+                                  msDSUserAccountControlComputed=0,
+                                  msg=("second logon, firstlogon was %s" %
+                                       firstLogon))
+
+
+        lastLogon = int(res[0]["lastLogon"][0])
+
+        time.sleep(1)
+
+        SamDB(url=host_url, credentials=self.insta_creds(creds), lp=lp)
+
+        res = self._check_account(userdn,
+                                  badPwdCount=0,
+                                  badPasswordTime=badPasswordTime,
+                                  logonCount=(logoncount_relation, logonCount),
+                                  lastLogon=(lastlogon_relation, lastLogon),
                                   lastLogonTimestamp=lastLogonTimestamp,
-                                  lockoutTime=0,
                                   userAccountControl=
                                     dsdb.UF_NORMAL_ACCOUNT,
                                   msDSUserAccountControlComputed=0)
 
-    def test_userPassword_lockout_with_clear_change_ldap_userAccountControl(self):
-        self._test_userPassword_lockout_with_clear_change("ldap_userAccountControl")
 
-    def test_userPassword_lockout_with_clear_change_ldap_lockoutTime(self):
-        self._test_userPassword_lockout_with_clear_change("ldap_lockoutTime")
+class PasswordTests(BasePasswordTestCase):
+    def _reset_ldap_lockoutTime(self, res):
+        self.ldb.modify_ldif("""
+dn: """ + str(res[0].dn) + """
+changetype: modify
+replace: lockoutTime
+lockoutTime: 0
+""")
+
+    def _reset_ldap_userAccountControl(self, res):
+        self.assertTrue("userAccountControl" in res[0])
+        self.assertTrue("msDS-User-Account-Control-Computed" in res[0])
+
+        uac = int(res[0]["userAccountControl"][0])
+        uacc = int(res[0]["msDS-User-Account-Control-Computed"][0])
+
+        uac |= uacc
+        uac = uac & ~dsdb.UF_LOCKOUT
+
+        self.ldb.modify_ldif("""
+dn: """ + str(res[0].dn) + """
+changetype: modify
+replace: userAccountControl
+userAccountControl: %d
+""" % uac)
+
+    def _reset_by_method(self, res, method):
+        if method is "ldap_userAccountControl":
+            self._reset_ldap_userAccountControl(res)
+        elif method is "ldap_lockoutTime":
+            self._reset_ldap_lockoutTime(res)
+        elif method is "samr":
+            self._reset_samr(res)
+        else:
+            self.assertTrue(False, msg="Invalid reset method[%s]" % method)
 
-    def test_userPassword_lockout_with_clear_change_samr(self):
-        self._test_userPassword_lockout_with_clear_change("samr")
+    def _test_userPassword_lockout_with_clear_change(self, creds, other_ldb, method,
+                                                     initial_lastlogon_relation=None):
+        # Notice: This works only against Windows if "dSHeuristics" has been set
+        # properly
+        username = creds.get_username()
+        userpass = creds.get_password()
+        userdn = "cn=%s,cn=users,%s" % (username, self.base_dn)
 
+        use_kerberos = creds.get_kerberos_state()
+        if use_kerberos == MUST_USE_KERBEROS:
+            logoncount_relation = 'greater'
+            lastlogon_relation = 'greater'
+            print "Performs a password cleartext change operation on 'userPassword' using Kerberos"
+        else:
+            logoncount_relation = 'equal'
+            lastlogon_relation = 'equal'
+            print "Performs a password cleartext change operation on 'userPassword' using NTLMSSP"
 
-    def test_unicodePwd_lockout_with_clear_change(self):
-        print "Performs a password cleartext change operation on 'unicodePwd'"
+        if initial_lastlogon_relation is not None:
+            lastlogon_relation = initial_lastlogon_relation
 
-        res = self._check_account("cn=testuser,cn=users," + self.base_dn,
+        res = self._check_account(userdn,
                                   badPwdCount=0,
                                   badPasswordTime=("greater", 0),
-                                  lastLogon=("greater", 0),
-                                  lastLogonTimestamp=("greater", 0),
+                                  logonCount=(logoncount_relation, 0),
+                                  lastLogon=(lastlogon_relation, 0),
+                                  lastLogonTimestamp=('greater', 0),
                                   userAccountControl=
                                     dsdb.UF_NORMAL_ACCOUNT,
                                   msDSUserAccountControlComputed=0)
         badPasswordTime = int(res[0]["badPasswordTime"][0])
+        logonCount = int(res[0]["logonCount"][0])
         lastLogon = int(res[0]["lastLogon"][0])
+        lastLogonTimestamp = int(res[0]["lastLogonTimestamp"][0])
+        if lastlogon_relation == 'greater':
+            self.assertGreater(lastLogon, badPasswordTime)
+            self.assertGreaterEqual(lastLogon, lastLogonTimestamp)
 
         # Change password on a connection as another user
 
         # Wrong old password
         try:
-            self.ldb3.modify_ldif("""
-dn: cn=testuser,cn=users,""" + self.base_dn + """
+            other_ldb.modify_ldif("""
+dn: """ + userdn + """
 changetype: modify
-delete: unicodePwd
-unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS1x\"".encode('utf-16-le')) + """
-add: unicodePwd
-unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
+delete: userPassword
+userPassword: thatsAcomplPASS1x
+add: userPassword
+userPassword: thatsAcomplPASS2
 """)
             self.fail()
         except LdbError, (num, msg):
-            self.assertTrue('00000056' in msg)
             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
+            self.assertTrue('00000056' in msg, msg)
 
-        res = self._check_account("cn=testuser,cn=users," + self.base_dn,
+        res = self._check_account(userdn,
                                   badPwdCount=1,
                                   badPasswordTime=("greater", badPasswordTime),
+                                  logonCount=logonCount,
                                   lastLogon=lastLogon,
-                                  lastLogonTimestamp=lastLogon,
+                                  lastLogonTimestamp=lastLogonTimestamp,
                                   userAccountControl=
                                     dsdb.UF_NORMAL_ACCOUNT,
                                   msDSUserAccountControlComputed=0)
         badPasswordTime = int(res[0]["badPasswordTime"][0])
 
         # Correct old password
-        self.ldb3.modify_ldif("""
-dn: cn=testuser,cn=users,""" + self.base_dn + """
+        other_ldb.modify_ldif("""
+dn: """ + userdn + """
 changetype: modify
-delete: unicodePwd
-unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS1\"".encode('utf-16-le')) + """
-add: unicodePwd
-unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
+delete: userPassword
+userPassword: """ + userpass + """
+add: userPassword
+userPassword: thatsAcomplPASS2
 """)
 
-        res = self._check_account("cn=testuser,cn=users," + self.base_dn,
+        res = self._check_account(userdn,
                                   badPwdCount=1,
                                   badPasswordTime=badPasswordTime,
+                                  logonCount=logonCount,
                                   lastLogon=lastLogon,
-                                  lastLogonTimestamp=lastLogon,
+                                  lastLogonTimestamp=lastLogonTimestamp,
                                   userAccountControl=
                                     dsdb.UF_NORMAL_ACCOUNT,
                                   msDSUserAccountControlComputed=0)
 
         # Wrong old password
         try:
-            self.ldb3.modify_ldif("""
-dn: cn=testuser,cn=users,""" + self.base_dn + """
+            other_ldb.modify_ldif("""
+dn: """ + userdn + """
 changetype: modify
-delete: unicodePwd
-unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS1\"".encode('utf-16-le')) + """
-add: unicodePwd
-unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
+delete: userPassword
+userPassword: thatsAcomplPASS1x
+add: userPassword
+userPassword: thatsAcomplPASS2
 """)
             self.fail()
         except LdbError, (num, msg):
-            self.assertTrue('00000056' in msg)
             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
+            self.assertTrue('00000056' in msg, msg)
 
-        res = self._check_account("cn=testuser,cn=users," + self.base_dn,
+        res = self._check_account(userdn,
                                   badPwdCount=2,
                                   badPasswordTime=("greater", badPasswordTime),
+                                  logonCount=logonCount,
                                   lastLogon=lastLogon,
-                                  lastLogonTimestamp=lastLogon,
+                                  lastLogonTimestamp=lastLogonTimestamp,
                                   userAccountControl=
                                     dsdb.UF_NORMAL_ACCOUNT,
                                   msDSUserAccountControlComputed=0)
         badPasswordTime = int(res[0]["badPasswordTime"][0])
 
-        # SAMR doesn't have any impact if dsdb.UF_LOCKOUT isn't present.
-        # It doesn't create "lockoutTime" = 0 and doesn't
-        # reset "badPwdCount" = 0.
-        self._reset_samr(res)
-
-        res = self._check_account("cn=testuser,cn=users," + self.base_dn,
-                                  badPwdCount=2,
-                                  badPasswordTime=badPasswordTime,
-                                  lastLogon=lastLogon,
-                                  lastLogonTimestamp=lastLogon,
-                                  userAccountControl=
-                                    dsdb.UF_NORMAL_ACCOUNT,
-                                  msDSUserAccountControlComputed=0)
-
         print "two failed password change"
 
         # Wrong old password
         try:
-            self.ldb3.modify_ldif("""
-dn: cn=testuser,cn=users,""" + self.base_dn + """
+            other_ldb.modify_ldif("""
+dn: """ + userdn + """
 changetype: modify
-delete: unicodePwd
-unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS1x\"".encode('utf-16-le')) + """
-add: unicodePwd
-unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
+delete: userPassword
+userPassword: thatsAcomplPASS1x
+add: userPassword
+userPassword: thatsAcomplPASS2
 """)
             self.fail()
         except LdbError, (num, msg):
-            self.assertTrue('00000056' in msg)
             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
+            self.assertTrue('00000056' in msg, msg)
 
-        # this is strange, why do we have lockoutTime=badPasswordTime here?
-        res = self._check_account("cn=testuser,cn=users," + self.base_dn,
+        res = self._check_account(userdn,
                                   badPwdCount=3,
                                   badPasswordTime=("greater", badPasswordTime),
+                                  logonCount=logonCount,
                                   lastLogon=lastLogon,
-                                  lastLogonTimestamp=lastLogon,
+                                  lastLogonTimestamp=lastLogonTimestamp,
                                   lockoutTime=("greater", badPasswordTime),
                                   userAccountControl=
                                     dsdb.UF_NORMAL_ACCOUNT,
@@ -1110,24 +1131,25 @@ unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le'))
 
         # Wrong old password
         try:
-            self.ldb3.modify_ldif("""
-dn: cn=testuser,cn=users,""" + self.base_dn + """
+            other_ldb.modify_ldif("""
+dn: """ + userdn + """
 changetype: modify
-delete: unicodePwd
-unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS1x\"".encode('utf-16-le')) + """
-add: unicodePwd
-unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
+delete: userPassword
+userPassword: thatsAcomplPASS1x
+add: userPassword
+userPassword: thatsAcomplPASS2
 """)
             self.fail()
         except LdbError, (num, msg):
             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
-            self.assertTrue('00000775' in msg)
+            self.assertTrue('00000775' in msg, msg)
 
-        res = self._check_account("cn=testuser,cn=users," + self.base_dn,
+        res = self._check_account(userdn,
                                   badPwdCount=3,
                                   badPasswordTime=badPasswordTime,
+                                  logonCount=logonCount,
                                   lastLogon=lastLogon,
-                                  lastLogonTimestamp=lastLogon,
+                                  lastLogonTimestamp=lastLogonTimestamp,
                                   lockoutTime=lockoutTime,
                                   userAccountControl=
                                     dsdb.UF_NORMAL_ACCOUNT,
@@ -1135,285 +1157,386 @@ unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le'))
 
         # Wrong old password
         try:
-            self.ldb3.modify_ldif("""
-dn: cn=testuser,cn=users,""" + self.base_dn + """
+            other_ldb.modify_ldif("""
+dn: """ + userdn + """
 changetype: modify
-delete: unicodePwd
-unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS1x\"".encode('utf-16-le')) + """
-add: unicodePwd
-unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
+delete: userPassword
+userPassword: thatsAcomplPASS1x
+add: userPassword
+userPassword: thatsAcomplPASS2
 """)
             self.fail()
         except LdbError, (num, msg):
             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
-            self.assertTrue('00000775' in msg)
+            self.assertTrue('00000775' in msg, msg)
 
-        res = self._check_account("cn=testuser,cn=users," + self.base_dn,
+        res = self._check_account(userdn,
                                   badPwdCount=3,
                                   badPasswordTime=badPasswordTime,
-                                  lastLogon=lastLogon,
-                                  lastLogonTimestamp=lastLogon,
+                                  logonCount=logonCount,
                                   lockoutTime=lockoutTime,
+                                  lastLogon=lastLogon,
+                                  lastLogonTimestamp=lastLogonTimestamp,
                                   userAccountControl=
                                     dsdb.UF_NORMAL_ACCOUNT,
                                   msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
 
         try:
             # Correct old password
-            self.ldb3.modify_ldif("""
-dn: cn=testuser,cn=users,""" + self.base_dn + """
+            other_ldb.modify_ldif("""
+dn: """ + userdn + """
 changetype: modify
-delete: unicodePwd
-unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
-add: unicodePwd
-unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2x\"".encode('utf-16-le')) + """
+delete: userPassword
+userPassword: thatsAcomplPASS2
+add: userPassword
+userPassword: thatsAcomplPASS2x
 """)
             self.fail()
         except LdbError, (num, msg):
             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
-            self.assertTrue('0000775' in msg)
+            self.assertTrue('00000775' in msg, msg)
 
-        res = self._check_account("cn=testuser,cn=users," + self.base_dn,
+        res = self._check_account(userdn,
                                   badPwdCount=3,
                                   badPasswordTime=badPasswordTime,
+                                  logonCount=logonCount,
                                   lastLogon=lastLogon,
-                                  lastLogonTimestamp=lastLogon,
+                                  lastLogonTimestamp=lastLogonTimestamp,
                                   lockoutTime=lockoutTime,
                                   userAccountControl=
                                     dsdb.UF_NORMAL_ACCOUNT,
                                   msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
 
-        # Now reset the lockout, by removing ACB_AUTOLOCK (which removes the lock, despite being a generated attribute)
-        self._reset_samr(res);
-
-        res = self._check_account("cn=testuser,cn=users," + self.base_dn,
-                                  badPwdCount=0,
-                                  badPasswordTime=badPasswordTime,
-                                  lastLogon=lastLogon,
-                                  lastLogonTimestamp=lastLogon,
-                                  lockoutTime=0,
-                                  userAccountControl=
-                                    dsdb.UF_NORMAL_ACCOUNT,
-                                  msDSUserAccountControlComputed=0)
-
-        # Correct old password
-        self.ldb3.modify_ldif("""
-dn: cn=testuser,cn=users,""" + self.base_dn + """
+        # Now reset the password, which does NOT change the lockout!
+        self.ldb.modify_ldif("""
+dn: """ + userdn + """
 changetype: modify
-delete: unicodePwd
-unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
-add: unicodePwd
-unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2x\"".encode('utf-16-le')) + """
+replace: userPassword
+userPassword: thatsAcomplPASS2
 """)
 
-        res = self._check_account("cn=testuser,cn=users," + self.base_dn,
-                                  badPwdCount=0,
+        res = self._check_account(userdn,
+                                  badPwdCount=3,
                                   badPasswordTime=badPasswordTime,
+                                  logonCount=logonCount,
                                   lastLogon=lastLogon,
-                                  lastLogonTimestamp=lastLogon,
-                                  lockoutTime=0,
+                                  lastLogonTimestamp=lastLogonTimestamp,
+                                  lockoutTime=lockoutTime,
                                   userAccountControl=
                                     dsdb.UF_NORMAL_ACCOUNT,
-                                  msDSUserAccountControlComputed=0)
+                                  msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
 
-        # Wrong old password
         try:
-            self.ldb3.modify_ldif("""
-dn: cn=testuser,cn=users,""" + self.base_dn + """
+            # Correct old password
+            other_ldb.modify_ldif("""
+dn: """ + userdn + """
 changetype: modify
-delete: unicodePwd
-unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS1x\"".encode('utf-16-le')) + """
-add: unicodePwd
-unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
+delete: userPassword
+userPassword: thatsAcomplPASS2
+add: userPassword
+userPassword: thatsAcomplPASS2x
 """)
             self.fail()
         except LdbError, (num, msg):
-            self.assertTrue('00000056' in msg)
             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
+            self.assertTrue('00000775' in msg, msg)
 
-        res = self._check_account("cn=testuser,cn=users," + self.base_dn,
-                                  badPwdCount=1,
-                                  badPasswordTime=("greater", badPasswordTime),
+        res = self._check_account(userdn,
+                                  badPwdCount=3,
+                                  badPasswordTime=badPasswordTime,
+                                  logonCount=logonCount,
                                   lastLogon=lastLogon,
-                                  lastLogonTimestamp=lastLogon,
-                                  lockoutTime=0,
+                                  lastLogonTimestamp=lastLogonTimestamp,
+                                  lockoutTime=lockoutTime,
                                   userAccountControl=
                                     dsdb.UF_NORMAL_ACCOUNT,
-                                  msDSUserAccountControlComputed=0)
-        badPasswordTime = int(res[0]["badPasswordTime"][0])
+                                  msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
 
-        # Wrong old password
+        m = Message()
+        m.dn = Dn(self.ldb, userdn)
+        m["userAccountControl"] = MessageElement(
+          str(dsdb.UF_LOCKOUT),
+          FLAG_MOD_REPLACE, "userAccountControl")
+
+        self.ldb.modify(m)
+
+        # This shows that setting the UF_LOCKOUT flag alone makes no difference
+        res = self._check_account(userdn,
+                                  badPwdCount=3,
+                                  badPasswordTime=badPasswordTime,
+                                  logonCount=logonCount,
+                                  lastLogon=lastLogon,
+                                  lastLogonTimestamp=lastLogonTimestamp,
+                                  lockoutTime=lockoutTime,
+                                  userAccountControl=
+                                    dsdb.UF_NORMAL_ACCOUNT,
+                                  msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
+
+        # This shows that setting the UF_LOCKOUT flag makes no difference
         try:
-            self.ldb3.modify_ldif("""
-dn: cn=testuser,cn=users,""" + self.base_dn + """
+            # Correct old password
+            other_ldb.modify_ldif("""
+dn: """ + userdn + """
 changetype: modify
 delete: unicodePwd
-unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS1x\"".encode('utf-16-le')) + """
-add: unicodePwd
 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
+add: unicodePwd
+unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2x\"".encode('utf-16-le')) + """
 """)
             self.fail()
         except LdbError, (num, msg):
-            self.assertTrue('00000056' in msg)
             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
+            self.assertTrue('00000775' in msg, msg)
 
-        res = self._check_account("cn=testuser,cn=users," + self.base_dn,
-                                  badPwdCount=2,
-                                  badPasswordTime=("greater", badPasswordTime),
+        res = self._check_account(userdn,
+                                  badPwdCount=3,
+                                  badPasswordTime=badPasswordTime,
+                                  logonCount=logonCount,
+                                  lockoutTime=lockoutTime,
                                   lastLogon=lastLogon,
-                                  lastLogonTimestamp=lastLogon,
+                                  lastLogonTimestamp=lastLogonTimestamp,
+                                  userAccountControl=
+                                    dsdb.UF_NORMAL_ACCOUNT,
+                                  msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
+
+        self._reset_by_method(res, method)
+
+        # Here bad password counts are reset without logon success.
+        res = self._check_account(userdn,
+                                  badPwdCount=0,
+                                  badPasswordTime=badPasswordTime,
+                                  logonCount=logonCount,
                                   lockoutTime=0,
+                                  lastLogon=lastLogon,
+                                  lastLogonTimestamp=lastLogonTimestamp,
                                   userAccountControl=
                                     dsdb.UF_NORMAL_ACCOUNT,
                                   msDSUserAccountControlComputed=0)
-        badPasswordTime = int(res[0]["badPasswordTime"][0])
 
-        # SAMR doesn't have any impact if dsdb.UF_LOCKOUT isn't present.
-        # It doesn't reset "badPwdCount" = 0.
-        self._reset_samr(res)
+        # The correct password after doing the unlock
 
-        res = self._check_account("cn=testuser,cn=users," + self.base_dn,
-                                  badPwdCount=2,
+        other_ldb.modify_ldif("""
+dn: """ + userdn + """
+changetype: modify
+delete: unicodePwd
+unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
+add: unicodePwd
+unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2x\"".encode('utf-16-le')) + """
+""")
+        userpass = "thatsAcomplPASS2x"
+        creds.set_password(userpass)
+
+        res = self._check_account(userdn,
+                                  badPwdCount=0,
                                   badPasswordTime=badPasswordTime,
-                                  lastLogon=lastLogon,
-                                  lastLogonTimestamp=lastLogon,
+                                  logonCount=logonCount,
                                   lockoutTime=0,
+                                  lastLogon=lastLogon,
+                                  lastLogonTimestamp=lastLogonTimestamp,
                                   userAccountControl=
                                     dsdb.UF_NORMAL_ACCOUNT,
                                   msDSUserAccountControlComputed=0)
 
         # Wrong old password
         try:
-            self.ldb3.modify_ldif("""
-dn: cn=testuser,cn=users,""" + self.base_dn + """
+            other_ldb.modify_ldif("""
+dn: """ + userdn + """
 changetype: modify
-delete: unicodePwd
-unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS1x\"".encode('utf-16-le')) + """
-add: unicodePwd
-unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
+delete: userPassword
+userPassword: thatsAcomplPASS1xyz
+add: userPassword
+userPassword: thatsAcomplPASS2XYZ
 """)
             self.fail()
         except LdbError, (num, msg):
-            self.assertTrue('00000056' in msg)
             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
+            self.assertTrue('00000056' in msg, msg)
 
-        res = self._check_account("cn=testuser,cn=users," + self.base_dn,
-                                  badPwdCount=3,
+        res = self._check_account(userdn,
+                                  badPwdCount=1,
                                   badPasswordTime=("greater", badPasswordTime),
+                                  logonCount=logonCount,
+                                  lockoutTime=0,
                                   lastLogon=lastLogon,
-                                  lastLogonTimestamp=lastLogon,
-                                  lockoutTime=("greater", badPasswordTime),
+                                  lastLogonTimestamp=lastLogonTimestamp,
                                   userAccountControl=
                                     dsdb.UF_NORMAL_ACCOUNT,
-                                  msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
+                                  msDSUserAccountControlComputed=0)
         badPasswordTime = int(res[0]["badPasswordTime"][0])
-        lockoutTime = int(res[0]["lockoutTime"][0])
 
-        time.sleep(self.account_lockout_duration + 1)
+        # Wrong old password
+        try:
+            other_ldb.modify_ldif("""
+dn: """ + userdn + """
+changetype: modify
+delete: userPassword
+userPassword: thatsAcomplPASS1xyz
+add: userPassword
+userPassword: thatsAcomplPASS2XYZ
+""")
+            self.fail()
+        except LdbError, (num, msg):
+            self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
+            self.assertTrue('00000056' in msg, msg)
 
-        res = self._check_account("cn=testuser,cn=users," + self.base_dn,
-                                  badPwdCount=3, effective_bad_password_count=0,
-                                  badPasswordTime=badPasswordTime,
+        res = self._check_account(userdn,
+                                  badPwdCount=2,
+                                  badPasswordTime=("greater", badPasswordTime),
+                                  logonCount=logonCount,
+                                  lockoutTime=0,
                                   lastLogon=lastLogon,
-                                  lastLogonTimestamp=lastLogon,
-                                  lockoutTime=lockoutTime,
+                                  lastLogonTimestamp=lastLogonTimestamp,
                                   userAccountControl=
                                     dsdb.UF_NORMAL_ACCOUNT,
                                   msDSUserAccountControlComputed=0)
+        badPasswordTime = int(res[0]["badPasswordTime"][0])
 
-        # SAMR doesn't have any impact if dsdb.UF_LOCKOUT isn't present.
-        # It doesn't reset "lockoutTime" = 0 and doesn't
-        # reset "badPwdCount" = 0.
-        self._reset_samr(res)
+        self._reset_ldap_lockoutTime(res)
 
-        res = self._check_account("cn=testuser,cn=users," + self.base_dn,
-                                  badPwdCount=3, effective_bad_password_count=0,
+        res = self._check_account(userdn,
+                                  badPwdCount=0,
                                   badPasswordTime=badPasswordTime,
-                                  lockoutTime=lockoutTime,
+                                  logonCount=logonCount,
                                   lastLogon=lastLogon,
-                                  lastLogonTimestamp=lastLogon,
+                                  lastLogonTimestamp=lastLogonTimestamp,
+                                  lockoutTime=0,
                                   userAccountControl=
                                     dsdb.UF_NORMAL_ACCOUNT,
                                   msDSUserAccountControlComputed=0)
 
-    def _test_login_lockout(self, use_kerberos):
-        # This unlocks by waiting for account_lockout_duration
-        if use_kerberos == MUST_USE_KERBEROS:
-            lastlogon_relation = 'greater'
-            print "Performs a lockout attempt against LDAP using Kerberos"
+    def test_userPassword_lockout_with_clear_change_krb5_ldap_userAccountControl(self):
+        self._test_userPassword_lockout_with_clear_change(self.lockout1krb5_creds,
+                                                          self.lockout2krb5_ldb,
+                                                          "ldap_userAccountControl")
+
+    def test_userPassword_lockout_with_clear_change_krb5_ldap_lockoutTime(self):
+        self._test_userPassword_lockout_with_clear_change(self.lockout1krb5_creds,
+                                                          self.lockout2krb5_ldb,
+                                                          "ldap_lockoutTime")
+
+    def test_userPassword_lockout_with_clear_change_krb5_samr(self):
+        self._test_userPassword_lockout_with_clear_change(self.lockout1krb5_creds,
+                                                          self.lockout2krb5_ldb,
+                                                          "samr")
+
+    def test_userPassword_lockout_with_clear_change_ntlm_ldap_userAccountControl(self):
+        self._test_userPassword_lockout_with_clear_change(self.lockout1ntlm_creds,
+                                                          self.lockout2ntlm_ldb,
+                                                          "ldap_userAccountControl",
+                                                          initial_lastlogon_relation='greater')
+
+    def test_userPassword_lockout_with_clear_change_ntlm_ldap_lockoutTime(self):
+        self._test_userPassword_lockout_with_clear_change(self.lockout1ntlm_creds,
+                                                          self.lockout2ntlm_ldb,
+                                                          "ldap_lockoutTime",
+                                                          initial_lastlogon_relation='greater')
+
+    def test_userPassword_lockout_with_clear_change_ntlm_samr(self):
+        self._test_userPassword_lockout_with_clear_change(self.lockout1ntlm_creds,
+                                                          self.lockout2ntlm_ldb,
+                                                          "samr",
+                                                          initial_lastlogon_relation='greater')
+
+    def _test_unicodePwd_lockout_with_clear_change(self, creds, other_ldb,
+                                                   initial_logoncount_relation=None):
+        print "Performs a password cleartext change operation on 'unicodePwd'"
+        username = creds.get_username()
+        userpass = creds.get_password()
+        userdn = "cn=%s,cn=users,%s" % (username, self.base_dn)
+        if initial_logoncount_relation is not None:
+            logoncount_relation = initial_logoncount_relation
         else:
-            lastlogon_relation = 'equal'
-            print "Performs a lockout attempt against LDAP using NTLM"
+            logoncount_relation = "greater"
 
-        # Change password on a connection as another user
-        res = self._check_account("cn=testuser,cn=users," + self.base_dn,
+        res = self._check_account(userdn,
                                   badPwdCount=0,
                                   badPasswordTime=("greater", 0),
+                                  logonCount=(logoncount_relation, 0),
                                   lastLogon=("greater", 0),
                                   lastLogonTimestamp=("greater", 0),
                                   userAccountControl=
                                     dsdb.UF_NORMAL_ACCOUNT,
                                   msDSUserAccountControlComputed=0)
         badPasswordTime = int(res[0]["badPasswordTime"][0])
+        logonCount = int(res[0]["logonCount"][0])
         lastLogon = int(res[0]["lastLogon"][0])
-        firstLogon = lastLogon
         lastLogonTimestamp = int(res[0]["lastLogonTimestamp"][0])
-        print firstLogon
-        print lastLogonTimestamp
-
+        self.assertGreater(lastLogonTimestamp, badPasswordTime)
+        self.assertGreaterEqual(lastLogon, lastLogonTimestamp)
 
-        self.assertGreater(lastLogon, badPasswordTime)
-
-        # Open a second LDB connection with the user credentials. Use the
-        # command line credentials for informations like the domain, the realm
-        # and the workstation.
-        creds_lockout = insta_creds()
-        creds_lockout.set_kerberos_state(use_kerberos)
-
-        # The wrong password
-        creds_lockout.set_password("thatsAcomplPASS1x")
+        # Change password on a connection as another user
 
-        self.assertLoginFailure(host_url, creds_lockout, lp)
+        # Wrong old password
+        try:
+            other_ldb.modify_ldif("""
+dn: """ + userdn + """
+changetype: modify
+delete: unicodePwd
+unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS1x\"".encode('utf-16-le')) + """
+add: unicodePwd
+unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
+""")
+            self.fail()
+        except LdbError, (num, msg):
+            self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
+            self.assertTrue('00000056' in msg, msg)
 
-        res = self._check_account("cn=testuser,cn=users," + self.base_dn,
+        res = self._check_account(userdn,
                                   badPwdCount=1,
                                   badPasswordTime=("greater", badPasswordTime),
+                                  logonCount=logonCount,
                                   lastLogon=lastLogon,
                                   lastLogonTimestamp=lastLogonTimestamp,
                                   userAccountControl=
                                     dsdb.UF_NORMAL_ACCOUNT,
-                                  msDSUserAccountControlComputed=0,
-                                  msg='lastlogontimestamp with wrong password')
+                                  msDSUserAccountControlComputed=0)
         badPasswordTime = int(res[0]["badPasswordTime"][0])
 
         # Correct old password
-        creds_lockout.set_password("thatsAcomplPASS1")
-
-        ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
+        old_utf16 = ("\"%s\"" % userpass).encode('utf-16-le')
+        invalid_utf16 = "\"thatsAcomplPASSX\"".encode('utf-16-le')
+        userpass = "thatsAcomplPASS2"
+        creds.set_password(userpass)
+        new_utf16 = ("\"%s\"" % userpass).encode('utf-16-le')
+
+        other_ldb.modify_ldif("""
+dn: """ + userdn + """
+changetype: modify
+delete: unicodePwd
+unicodePwd:: """ + base64.b64encode(old_utf16) + """
+add: unicodePwd
+unicodePwd:: """ + base64.b64encode(new_utf16) + """
+""")
 
-        # lastLogonTimestamp should not change
-        # lastLogon increases if badPwdCount is non-zero (!)
-        res = self._check_account("cn=testuser,cn=users," + self.base_dn,
-                                  badPwdCount=0,
+        res = self._check_account(userdn,
+                                  badPwdCount=1,
                                   badPasswordTime=badPasswordTime,
-                                  lastLogon=('greater', lastLogon),
+                                  logonCount=logonCount,
+                                  lastLogon=lastLogon,
                                   lastLogonTimestamp=lastLogonTimestamp,
                                   userAccountControl=
                                     dsdb.UF_NORMAL_ACCOUNT,
-                                  msDSUserAccountControlComputed=0,
-                                  msg='LLTimestamp is updated to lastlogon')
-
-        lastLogon = int(res[0]["lastLogon"][0])
-        self.assertGreater(lastLogon, badPasswordTime)
-
-        # The wrong password
-        creds_lockout.set_password("thatsAcomplPASS1x")
+                                  msDSUserAccountControlComputed=0)
 
-        self.assertLoginFailure(host_url, creds_lockout, lp)
+        # Wrong old password
+        try:
+            other_ldb.modify_ldif("""
+dn: """ + userdn + """
+changetype: modify
+delete: unicodePwd
+unicodePwd:: """ + base64.b64encode(old_utf16) + """
+add: unicodePwd
+unicodePwd:: """ + base64.b64encode(new_utf16) + """
+""")
+            self.fail()
+        except LdbError, (num, msg):
+            self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
+            self.assertTrue('00000056' in msg, msg)
 
-        res = self._check_account("cn=testuser,cn=users," + self.base_dn,
-                                  badPwdCount=1,
+        res = self._check_account(userdn,
+                                  badPwdCount=2,
                                   badPasswordTime=("greater", badPasswordTime),
+                                  logonCount=logonCount,
                                   lastLogon=lastLogon,
                                   lastLogonTimestamp=lastLogonTimestamp,
                                   userAccountControl=
@@ -1421,41 +1544,43 @@ unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le'))
                                   msDSUserAccountControlComputed=0)
         badPasswordTime = int(res[0]["badPasswordTime"][0])
 
-        # The wrong password
-        creds_lockout.set_password("thatsAcomplPASS1x")
-
-        try:
-            ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
-            self.fail()
-
-        except LdbError, (num, msg):
-            self.assertEquals(num, ERR_INVALID_CREDENTIALS)
+        # SAMR doesn't have any impact if dsdb.UF_LOCKOUT isn't present.
+        # It doesn't create "lockoutTime" = 0 and doesn't
+        # reset "badPwdCount" = 0.
+        self._reset_samr(res)
 
-        res = self._check_account("cn=testuser,cn=users," + self.base_dn,
+        res = self._check_account(userdn,
                                   badPwdCount=2,
-                                  badPasswordTime=("greater", badPasswordTime),
+                                  badPasswordTime=badPasswordTime,
+                                  logonCount=logonCount,
                                   lastLogon=lastLogon,
                                   lastLogonTimestamp=lastLogonTimestamp,
                                   userAccountControl=
                                     dsdb.UF_NORMAL_ACCOUNT,
                                   msDSUserAccountControlComputed=0)
-        badPasswordTime = int(res[0]["badPasswordTime"][0])
 
         print "two failed password change"
 
-        # The wrong password
-        creds_lockout.set_password("thatsAcomplPASS1x")
-
+        # Wrong old password
         try:
-            ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
+            other_ldb.modify_ldif("""
+dn: """ + userdn + """
+changetype: modify
+delete: unicodePwd
+unicodePwd:: """ + base64.b64encode(invalid_utf16) + """
+add: unicodePwd
+unicodePwd:: """ + base64.b64encode(new_utf16) + """
+""")
             self.fail()
-
         except LdbError, (num, msg):
-            self.assertEquals(num, ERR_INVALID_CREDENTIALS)
+            self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
+            self.assertTrue('00000056' in msg, msg)
 
-        res = self._check_account("cn=testuser,cn=users," + self.base_dn,
+        # this is strange, why do we have lockoutTime=badPasswordTime here?
+        res = self._check_account(userdn,
                                   badPwdCount=3,
                                   badPasswordTime=("greater", badPasswordTime),
+                                  logonCount=logonCount,
                                   lastLogon=lastLogon,
                                   lastLogonTimestamp=lastLogonTimestamp,
                                   lockoutTime=("greater", badPasswordTime),
@@ -1465,17 +1590,25 @@ unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le'))
         badPasswordTime = int(res[0]["badPasswordTime"][0])
         lockoutTime = int(res[0]["lockoutTime"][0])
 
-        # The wrong password
-        creds_lockout.set_password("thatsAcomplPASS1x")
+        # Wrong old password
         try:
-            ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
+            other_ldb.modify_ldif("""
+dn: """ + userdn + """
+changetype: modify
+delete: unicodePwd
+unicodePwd:: """ + base64.b64encode(invalid_utf16) + """
+add: unicodePwd
+unicodePwd:: """ + base64.b64encode(new_utf16) + """
+""")
             self.fail()
         except LdbError, (num, msg):
-            self.assertEquals(num, ERR_INVALID_CREDENTIALS)
+            self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
+            self.assertTrue('00000775' in msg, msg)
 
-        res = self._check_account("cn=testuser,cn=users," + self.base_dn,
+        res = self._check_account(userdn,
                                   badPwdCount=3,
                                   badPasswordTime=badPasswordTime,
+                                  logonCount=logonCount,
                                   lastLogon=lastLogon,
                                   lastLogonTimestamp=lastLogonTimestamp,
                                   lockoutTime=lockoutTime,
@@ -1483,17 +1616,25 @@ unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le'))
                                     dsdb.UF_NORMAL_ACCOUNT,
                                   msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
 
-        # The wrong password
-        creds_lockout.set_password("thatsAcomplPASS1x")
+        # Wrong old password
         try:
-            ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
+            other_ldb.modify_ldif("""
+dn: """ + userdn + """
+changetype: modify
+delete: unicodePwd
+unicodePwd:: """ + base64.b64encode(invalid_utf16) + """
+add: unicodePwd
+unicodePwd:: """ + base64.b64encode(new_utf16) + """
+""")
             self.fail()
         except LdbError, (num, msg):
-            self.assertEquals(num, ERR_INVALID_CREDENTIALS)
+            self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
+            self.assertTrue('00000775' in msg, msg)
 
-        res = self._check_account("cn=testuser,cn=users," + self.base_dn,
+        res = self._check_account(userdn,
                                   badPwdCount=3,
                                   badPasswordTime=badPasswordTime,
+                                  logonCount=logonCount,
                                   lastLogon=lastLogon,
                                   lastLogonTimestamp=lastLogonTimestamp,
                                   lockoutTime=lockoutTime,
@@ -1501,17 +1642,25 @@ unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le'))
                                     dsdb.UF_NORMAL_ACCOUNT,
                                   msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
 
-        # The correct password, but we are locked out
-        creds_lockout.set_password("thatsAcomplPASS1")
         try:
-            ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
+            # Correct old password
+            other_ldb.modify_ldif("""
+dn: """ + userdn + """
+changetype: modify
+delete: unicodePwd
+unicodePwd:: """ + base64.b64encode(new_utf16) + """
+add: unicodePwd
+unicodePwd:: """ + base64.b64encode(invalid_utf16) + """
+""")
             self.fail()
         except LdbError, (num, msg):
-            self.assertEquals(num, ERR_INVALID_CREDENTIALS)
+            self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
+            self.assertTrue('00000775' in msg, msg)
 
-        res = self._check_account("cn=testuser,cn=users," + self.base_dn,
+        res = self._check_account(userdn,
                                   badPwdCount=3,
                                   badPasswordTime=badPasswordTime,
+                                  logonCount=logonCount,
                                   lastLogon=lastLogon,
                                   lastLogonTimestamp=lastLogonTimestamp,
                                   lockoutTime=lockoutTime,
@@ -1519,213 +1668,194 @@ unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le'))
                                     dsdb.UF_NORMAL_ACCOUNT,
                                   msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
 
-        # wait for the lockout to end
-        time.sleep(self.account_lockout_duration + 1)
-        print self.account_lockout_duration + 1
+        # Now reset the lockout, by removing ACB_AUTOLOCK (which removes the lock, despite being a generated attribute)
+        self._reset_samr(res);
 
-        res = self._check_account("cn=testuser,cn=users," + self.base_dn,
-                                  badPwdCount=3, effective_bad_password_count=0,
+        res = self._check_account(userdn,
+                                  badPwdCount=0,
                                   badPasswordTime=badPasswordTime,
-                                  lockoutTime=lockoutTime,
+                                  logonCount=logonCount,
                                   lastLogon=lastLogon,
                                   lastLogonTimestamp=lastLogonTimestamp,
+                                  lockoutTime=0,
                                   userAccountControl=
                                     dsdb.UF_NORMAL_ACCOUNT,
                                   msDSUserAccountControlComputed=0)
 
-        lastLogon = int(res[0]["lastLogon"][0])
-
-        # The correct password after letting the timeout expire
-
-        creds_lockout.set_password("thatsAcomplPASS1")
-
-        creds_lockout2 = insta_creds(creds_lockout)
-
-        ldb_lockout = SamDB(url=host_url, credentials=creds_lockout2, lp=lp)
-        time.sleep(3)
+        # Correct old password
+        old_utf16 = ("\"%s\"" % userpass).encode('utf-16-le')
+        invalid_utf16 = "\"thatsAcomplPASSiX\"".encode('utf-16-le')
+        userpass = "thatsAcomplPASS2x"
+        creds.set_password(userpass)
+        new_utf16 = ("\"%s\"" % userpass).encode('utf-16-le')
+
+        other_ldb.modify_ldif("""
+dn: """ + userdn + """
+changetype: modify
+delete: unicodePwd
+unicodePwd:: """ + base64.b64encode(old_utf16) + """
+add: unicodePwd
+unicodePwd:: """ + base64.b64encode(new_utf16) + """
+""")
 
-        res = self._check_account("cn=testuser,cn=users," + self.base_dn,
+        res = self._check_account(userdn,
                                   badPwdCount=0,
                                   badPasswordTime=badPasswordTime,
-                                  lastLogon=(lastlogon_relation, lastLogon),
+                                  logonCount=logonCount,
+                                  lastLogon=lastLogon,
                                   lastLogonTimestamp=lastLogonTimestamp,
                                   lockoutTime=0,
                                   userAccountControl=
                                     dsdb.UF_NORMAL_ACCOUNT,
-                                  msDSUserAccountControlComputed=0,
-                                  msg="lastLogon is way off")
-
-        lastLogon = int(res[0]["lastLogon"][0])
+                                  msDSUserAccountControlComputed=0)
 
-        # The wrong password
-        creds_lockout.set_password("thatsAcomplPASS1x")
+        # Wrong old password
         try:
-            ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
+            other_ldb.modify_ldif("""
+dn: """ + userdn + """
+changetype: modify
+delete: unicodePwd
+unicodePwd:: """ + base64.b64encode(invalid_utf16) + """
+add: unicodePwd
+unicodePwd:: """ + base64.b64encode(new_utf16) + """
+""")
             self.fail()
         except LdbError, (num, msg):
-            self.assertEquals(num, ERR_INVALID_CREDENTIALS)
+            self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
+            self.assertTrue('00000056' in msg, msg)
 
-        res = self._check_account("cn=testuser,cn=users," + self.base_dn,
+        res = self._check_account(userdn,
                                   badPwdCount=1,
                                   badPasswordTime=("greater", badPasswordTime),
-                                  lockoutTime=0,
+                                  logonCount=logonCount,
                                   lastLogon=lastLogon,
                                   lastLogonTimestamp=lastLogonTimestamp,
+                                  lockoutTime=0,
                                   userAccountControl=
                                     dsdb.UF_NORMAL_ACCOUNT,
                                   msDSUserAccountControlComputed=0)
         badPasswordTime = int(res[0]["badPasswordTime"][0])
 
-        # The wrong password
-        creds_lockout.set_password("thatsAcomplPASS1x")
+        # Wrong old password
         try:
-            ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
+            other_ldb.modify_ldif("""
+dn: """ + userdn + """
+changetype: modify
+delete: unicodePwd
+unicodePwd:: """ + base64.b64encode(invalid_utf16) + """
+add: unicodePwd
+unicodePwd:: """ + base64.b64encode(new_utf16) + """
+""")
             self.fail()
         except LdbError, (num, msg):
-            self.assertEquals(num, ERR_INVALID_CREDENTIALS)
+            self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
+            self.assertTrue('00000056' in msg, msg)
 
-        res = self._check_account("cn=testuser,cn=users," + self.base_dn,
+        res = self._check_account(userdn,
                                   badPwdCount=2,
                                   badPasswordTime=("greater", badPasswordTime),
-                                  lockoutTime=0,
+                                  logonCount=logonCount,
                                   lastLogon=lastLogon,
                                   lastLogonTimestamp=lastLogonTimestamp,
+                                  lockoutTime=0,
                                   userAccountControl=
                                     dsdb.UF_NORMAL_ACCOUNT,
                                   msDSUserAccountControlComputed=0)
         badPasswordTime = int(res[0]["badPasswordTime"][0])
 
-        time.sleep(self.lockout_observation_window + 1)
+        # SAMR doesn't have any impact if dsdb.UF_LOCKOUT isn't present.
+        # It doesn't reset "badPwdCount" = 0.
+        self._reset_samr(res)
 
-        res = self._check_account("cn=testuser,cn=users," + self.base_dn,
-                                  badPwdCount=2, effective_bad_password_count=0,
+        res = self._check_account(userdn,
+                                  badPwdCount=2,
                                   badPasswordTime=badPasswordTime,
-                                  lockoutTime=0,
+                                  logonCount=logonCount,
                                   lastLogon=lastLogon,
                                   lastLogonTimestamp=lastLogonTimestamp,
+                                  lockoutTime=0,
                                   userAccountControl=
                                     dsdb.UF_NORMAL_ACCOUNT,
                                   msDSUserAccountControlComputed=0)
 
-        # The wrong password
-        creds_lockout.set_password("thatsAcomplPASS1x")
+        # Wrong old password
         try:
-            ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
+            other_ldb.modify_ldif("""
+dn: """ + userdn + """
+changetype: modify
+delete: unicodePwd
+unicodePwd:: """ + base64.b64encode(invalid_utf16) + """
+add: unicodePwd
+unicodePwd:: """ + base64.b64encode(new_utf16) + """
+""")
             self.fail()
         except LdbError, (num, msg):
-            self.assertEquals(num, ERR_INVALID_CREDENTIALS)
+            self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
+            self.assertTrue('00000056' in msg, msg)
 
-        res = self._check_account("cn=testuser,cn=users," + self.base_dn,
-                                  badPwdCount=1,
+        res = self._check_account(userdn,
+                                  badPwdCount=3,
                                   badPasswordTime=("greater", badPasswordTime),
-                                  lockoutTime=0,
+                                  logonCount=logonCount,
                                   lastLogon=lastLogon,
                                   lastLogonTimestamp=lastLogonTimestamp,
+                                  lockoutTime=("greater", badPasswordTime),
                                   userAccountControl=
                                     dsdb.UF_NORMAL_ACCOUNT,
-                                  msDSUserAccountControlComputed=0)
+                                  msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
         badPasswordTime = int(res[0]["badPasswordTime"][0])
+        lockoutTime = int(res[0]["lockoutTime"][0])
 
-        # The correct password without letting the timeout expire
-        creds_lockout.set_password("thatsAcomplPASS1")
-        ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
+        time.sleep(self.account_lockout_duration + 1)
 
-        res = self._check_account("cn=testuser,cn=users," + self.base_dn,
-                                  badPwdCount=0,
+        res = self._check_account(userdn,
+                                  badPwdCount=3, effective_bad_password_count=0,
                                   badPasswordTime=badPasswordTime,
-                                  lockoutTime=0,
-                                  lastLogon=("greater", lastLogon),
+                                  logonCount=logonCount,
+                                  lastLogon=lastLogon,
                                   lastLogonTimestamp=lastLogonTimestamp,
+                                  lockoutTime=lockoutTime,
                                   userAccountControl=
                                     dsdb.UF_NORMAL_ACCOUNT,
                                   msDSUserAccountControlComputed=0)
 
-    def test_login_lockout_ntlm(self):
-        self._test_login_lockout(DONT_USE_KERBEROS)
-
-    def test_login_lockout_kerberos(self):
-        self._test_login_lockout(MUST_USE_KERBEROS)
-
-    def _test_multiple_logon(self, use_kerberos):
-        # Test the happy case in which a user logs on correctly, then
-        # logs on correctly again, so that the bad password and
-        # lockout times are both zero the second time. The lastlogon
-        # time should increase.
-
-        # Open a second LDB connection with the user credentials. Use the
-        # command line credentials for informations like the domain, the realm
-        # and the workstation.
-        creds2 = insta_creds()
-        creds2.set_kerberos_state(use_kerberos)
-        self.assertEqual(creds2.get_kerberos_state(), use_kerberos)
-
-        if use_kerberos == MUST_USE_KERBEROS:
-            print "Testing multiple logon with Kerberos"
-            lastlogon_relation = 'greater'
-        else:
-            print "Testing multiple logon with NTLM"
-            lastlogon_relation = 'equal'
-
-        SamDB(url=host_url, credentials=insta_creds(creds2), lp=lp)
-
-        res = self._check_account("cn=testuser,cn=users," + self.base_dn,
-                                  badPwdCount=0,
-                                  badPasswordTime=("greater", 0),
-                                  lastLogon=("greater", 0),
-                                  lastLogonTimestamp=("greater", 0),
-                                  userAccountControl=
-                                    dsdb.UF_NORMAL_ACCOUNT,
-                                  msDSUserAccountControlComputed=0)
-        badPasswordTime = int(res[0]["badPasswordTime"][0])
-        lastLogon = int(res[0]["lastLogon"][0])
-        lastLogonTimestamp = int(res[0]["lastLogonTimestamp"][0])
-        firstLogon = lastLogon
-        print "last logon is %d" % lastLogon
-        self.assertGreater(lastLogon, badPasswordTime)
-
-        time.sleep(1)
-        SamDB(url=host_url, credentials=insta_creds(creds2), lp=lp)
+        # SAMR doesn't have any impact if dsdb.UF_LOCKOUT isn't present.
+        # It doesn't reset "lockoutTime" = 0 and doesn't
+        # reset "badPwdCount" = 0.
+        self._reset_samr(res)
 
-        res = self._check_account("cn=testuser,cn=users," + self.base_dn,
-                                  badPwdCount=0,
+        res = self._check_account(userdn,
+                                  badPwdCount=3, effective_bad_password_count=0,
                                   badPasswordTime=badPasswordTime,
-                                  lastLogon=(lastlogon_relation, lastLogon),
+                                  logonCount=logonCount,
+                                  lockoutTime=lockoutTime,
+                                  lastLogon=lastLogon,
                                   lastLogonTimestamp=lastLogonTimestamp,
                                   userAccountControl=
-                                  dsdb.UF_NORMAL_ACCOUNT,
-                                  msDSUserAccountControlComputed=0,
-                                  msg=("second logon, firstlogon was %s" %
-                                       firstLogon))
+                                    dsdb.UF_NORMAL_ACCOUNT,
+                                  msDSUserAccountControlComputed=0)
 
+    def test_unicodePwd_lockout_with_clear_change_krb5(self):
+        self._test_unicodePwd_lockout_with_clear_change(self.lockout1krb5_creds,
+                                                        self.lockout2krb5_ldb)
 
-        lastLogon = int(res[0]["lastLogon"][0])
+    def test_unicodePwd_lockout_with_clear_change_ntlm(self):
+        self._test_unicodePwd_lockout_with_clear_change(self.lockout1ntlm_creds,
+                                                        self.lockout2ntlm_ldb,
+                                                        initial_logoncount_relation="equal")
 
-        time.sleep(1)
+    def test_login_lockout_krb5(self):
+        self._test_login_lockout(self.lockout1krb5_creds)
 
-        SamDB(url=host_url, credentials=insta_creds(creds2), lp=lp)
+    def test_login_lockout_ntlm(self):
+        self._test_login_lockout(self.lockout1ntlm_creds)
 
-        res = self._check_account("cn=testuser,cn=users," + self.base_dn,
-                                  badPwdCount=0,
-                                  badPasswordTime=badPasswordTime,
-                                  lastLogon=(lastlogon_relation, lastLogon),
-                                  lastLogonTimestamp=lastLogonTimestamp,
-                                  userAccountControl=
-                                    dsdb.UF_NORMAL_ACCOUNT,
-                                  msDSUserAccountControlComputed=0)
+    def test_multiple_logon_krb5(self):
+        self._test_multiple_logon(self.lockout1krb5_creds)
 
     def test_multiple_logon_ntlm(self):
-        self._test_multiple_logon(DONT_USE_KERBEROS)
+        self._test_multiple_logon(self.lockout1ntlm_creds)
 
-    def test_multiple_logon_kerberos(self):
-        self._test_multiple_logon(MUST_USE_KERBEROS)
-
-    def tearDown(self):
-        super(PasswordTests, self).tearDown()
-        delete_force(self.ldb, "cn=testuser,cn=users," + self.base_dn)
-        delete_force(self.ldb, "cn=testuser2,cn=users," + self.base_dn)
-        delete_force(self.ldb, "cn=testuser3,cn=users," + self.base_dn)
-        # Close the second LDB connection (with the user credentials)
-        self.ldb2 = None
 
 host_url = "ldap://%s" % host