auth: keep track of lastLogon and lastLogonTimestamp
authorDouglas Bagnall <douglas.bagnall@catalyst.net.nz>
Fri, 23 Oct 2015 03:57:56 +0000 (16:57 +1300)
committerKarolin Seeger <kseeger@samba.org>
Wed, 6 Jan 2016 09:06:29 +0000 (10:06 +0100)
lastLogon is supposed to be updated for every interactive or kerberos
login, and (according to testing against Windows2012r2) when the bad
password count is non-zero but the lockout time is zero. It is not
replicated.

lastLogonTimestamp is updated if the old value is more than 14 -
random.choice([0, 1, 2, 3, 4, 5]) days old, and it is replicated. The
14 in this calculation is the default, stored as
"msDS-LogonTimeSyncInterval", which we offer no interface for
changing.

The authsam_zero_bad_pwd_count() function is a convenient place to
update these values, as it is called upon a successful logon however
that logon is performed. That makes the function's name inaccurate, so
we rename it authsam_logon_success_accounting(). It also needs to be
told whet5her the login is interactive.

The password_lockout tests are extended to test lastLogon and
lasLogonTimestamp.

Signed-off-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
Reviewed-by: Garming Sam <garming@catalyst.net.nz>
Reviewed-by: Ralph Boehme <slow@samba.org>
(cherry picked from commit 795f4729ca94029fcee750fbebbe9bc3ea43a214)

BUG: https://bugzilla.samba.org/show_bug.cgi?id=11659
lastLogon and lastLogonTimestamp are not updated

source4/auth/ntlm/auth_sam.c
source4/auth/sam.c
source4/dsdb/tests/python/password_lockout.py
source4/kdc/hdb-samba4.c

index 6da25a076f7345752833202c2afc6775779b0cd0..43c7a3d0047eb57c9673e94596235ecc52c9f007 100644 (file)
@@ -493,6 +493,7 @@ static NTSTATUS authsam_authenticate(struct auth4_context *auth_context,
                                     DATA_BLOB *user_sess_key, DATA_BLOB *lm_sess_key)
 {
        NTSTATUS nt_status;
+       bool interactive = (user_info->password_state == AUTH_PASSWORD_HASH);
        uint16_t acct_flags = samdb_result_acct_flags(msg, NULL);
        TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
        if (!tmp_ctx) {
@@ -528,7 +529,9 @@ static NTSTATUS authsam_authenticate(struct auth4_context *auth_context,
                return nt_status;
        }
 
-       nt_status = authsam_zero_bad_pwd_count(auth_context->sam_ctx, msg);
+       nt_status = authsam_logon_success_accounting(auth_context->sam_ctx,
+                                                    msg, domain_dn,
+                                                    interactive);
        if (!NT_STATUS_IS_OK(nt_status)) {
                TALLOC_FREE(tmp_ctx);
                return nt_status;
index f7bc6939dd71febd70fe0d456c238e94f190dc78..cdfe8dd2fda4f997c740473b89b40ea4b8664e7d 100644 (file)
@@ -83,6 +83,7 @@ const char *user_attrs[] = {
        "homeDirectory",
        "homeDrive",
        "lastLogon",
+       "lastLogonTimestamp",
        "lastLogoff",
        "accountExpires",
        "badPwdCount",
@@ -691,20 +692,132 @@ NTSTATUS authsam_update_bad_pwd_count(struct ldb_context *sam_ctx,
        return NT_STATUS_OK;
 }
 
-NTSTATUS authsam_zero_bad_pwd_count(struct ldb_context *sam_ctx,
-                                   const struct ldb_message *msg)
+
+static NTSTATUS authsam_update_lastlogon_timestamp(struct ldb_context *sam_ctx,
+                                           struct ldb_message *msg_mod,
+                                           struct ldb_dn *domain_dn,
+                                           NTTIME old_timestamp,
+                                           NTTIME now)
+{
+       /*
+        * We only set lastLogonTimestamp if the current value is older than
+        * now - msDS-LogonTimeSyncInterval days.
+        *
+        * msDS-LogonTimeSyncInterval is an int32_t number of days, while
+        * lastLogonTimestamp is in the 64 bit 100ns NTTIME format.
+        *
+        * The docs say: "the initial update, after the domain functional
+        * level is raised to DS_BEHAVIOR_WIN2003 or higher, is calculated as
+        * 14 days minus a random percentage of 5 days", but we aren't doing
+        * that. The blogosphere seems to think that this randomised update
+        * happens everytime, but [MS-ADA1] doesn't agree.
+        *
+        * Dochelp referred us to the following blog post:
+        * http://blogs.technet.com/b/askds/archive/2009/04/15/the-lastlogontimestamp-attribute-what-it-was-designed-for-and-how-it-works.aspx
+        *
+        * en msDS-LogonTimeSyncInterval is zero, the lastLogonTimestamp is
+        * not changed.
+        */
+       static const char *attrs[] = { "msDS-LogonTimeSyncInterval",
+                                       NULL };
+       int ret;
+       struct ldb_result *domain_res = NULL;
+       TALLOC_CTX *mem_ctx = NULL;
+       int32_t sync_interval;
+       NTTIME sync_interval_nt;
+
+       mem_ctx = talloc_new(msg_mod);
+       if (mem_ctx == NULL) {
+               return NT_STATUS_NO_MEMORY;
+       }
+
+       ret = dsdb_search_dn(sam_ctx, mem_ctx, &domain_res, domain_dn, attrs,
+                            0);
+       if (ret != LDB_SUCCESS || domain_res->count != 1) {
+               TALLOC_FREE(mem_ctx);
+               return NT_STATUS_INTERNAL_DB_CORRUPTION;
+       }
+
+       sync_interval = ldb_msg_find_attr_as_int(domain_res->msgs[0],
+                                                "msDS-LogonTimeSyncInterval",
+                                                14);
+       DEBUG(5, ("sync interval is %d\n", sync_interval));
+       if (sync_interval == 0){
+               /*
+                * Setting msDS-LogonTimeSyncInterval to zero is how you ask
+                * that nothing happens here.
+                */
+               TALLOC_FREE(mem_ctx);
+               return NT_STATUS_OK;
+       }
+       else if (sync_interval >= 5){
+               /*
+                * Subtract "a random percentage of 5" days. Presumably this
+                * percentage is between 0 and 100, and modulus is accurate
+                * enough.
+                */
+               uint32_t r = generate_random() % 6;
+               sync_interval -= r;
+               DEBUG(5, ("randomised sync interval is %d (-%d)\n", sync_interval, r));
+       }
+       /* In the case where sync_interval < 5 there is no randomisation */
+
+       sync_interval_nt = sync_interval * 24LL * 3600LL * 10000000LL;
+
+       DEBUG(5, ("old timestamp is %lld, threshold %lld, diff %lld\n",
+                 (long long int)old_timestamp,
+                 (long long int)(now - sync_interval_nt),
+                 (long long int)(old_timestamp - now + sync_interval_nt)));
+
+       if (old_timestamp > now){
+               DEBUG(0, ("lastLogonTimestamp is in the future! (%lld > %lld)\n",
+                         (long long int)old_timestamp, (long long int)now));
+               /* then what? */
+
+       } else if (old_timestamp < now - sync_interval_nt){
+               DEBUG(5, ("updating lastLogonTimestamp to %lld\n",
+                         (long long int)now));
+
+               /* The time has come to update lastLogonTimestamp */
+               ret = samdb_msg_add_int64(sam_ctx, msg_mod, msg_mod,
+                                         "lastLogonTimestamp", now);
+
+               if (ret != LDB_SUCCESS) {
+                       TALLOC_FREE(mem_ctx);
+                       return NT_STATUS_NO_MEMORY;
+               }
+       }
+       TALLOC_FREE(mem_ctx);
+       return NT_STATUS_OK;
+}
+
+
+
+/* Reset the badPwdCount to zero and update the lastLogon time. */
+NTSTATUS authsam_logon_success_accounting(struct ldb_context *sam_ctx,
+                                         const struct ldb_message *msg,
+                                         struct ldb_dn *domain_dn,
+                                         bool interactive_or_kerberos)
 {
        int ret;
+       NTSTATUS status;
        int badPwdCount;
        int64_t lockoutTime;
        struct ldb_message *msg_mod;
        TALLOC_CTX *mem_ctx;
+       struct timeval tv_now;
+       NTTIME now;
+       NTTIME lastLogonTimestamp;
+       NTTIME lastLogon;
 
        lockoutTime = ldb_msg_find_attr_as_int64(msg, "lockoutTime", 0);
        badPwdCount = ldb_msg_find_attr_as_int(msg, "badPwdCount", 0);
-       if (lockoutTime == 0 && badPwdCount == 0) {
-               return NT_STATUS_OK;
-       }
+       lastLogonTimestamp = \
+               ldb_msg_find_attr_as_int64(msg, "lastLogonTimestamp", 0);
+       lastLogon = ldb_msg_find_attr_as_int64(msg, "lastLogon", 0);
+
+       DEBUG(5, ("lastLogonTimestamp is %lld\n",
+                 (long long int)lastLogonTimestamp));
 
        mem_ctx = talloc_new(msg);
        if (mem_ctx == NULL) {
@@ -726,7 +839,7 @@ NTSTATUS authsam_zero_bad_pwd_count(struct ldb_context *sam_ctx,
                        TALLOC_FREE(mem_ctx);
                        return NT_STATUS_NO_MEMORY;
                }
-       } else {
+       } else if (badPwdCount != 0) {
                ret = samdb_msg_add_int(sam_ctx, msg_mod, msg_mod, "badPwdCount", 0);
                if (ret != LDB_SUCCESS) {
                        TALLOC_FREE(mem_ctx);
@@ -734,14 +847,37 @@ NTSTATUS authsam_zero_bad_pwd_count(struct ldb_context *sam_ctx,
                }
        }
 
-       ret = dsdb_replace(sam_ctx, msg_mod, 0);
-       if (ret != LDB_SUCCESS) {
-               DEBUG(0, ("Failed to set badPwdCount and lockoutTime to 0 on %s: %s\n",
-                         ldb_dn_get_linearized(msg_mod->dn), ldb_errstring(sam_ctx)));
+       tv_now = timeval_current();
+       now = timeval_to_nttime(&tv_now);
+
+       if (interactive_or_kerberos || lastLogon == 0 ||
+           (badPwdCount != 0 && lockoutTime == 0)) {
+               ret = samdb_msg_add_int64(sam_ctx, msg_mod, msg_mod,
+                                         "lastLogon", now);
+               if (ret != LDB_SUCCESS) {
+                       TALLOC_FREE(mem_ctx);
+                       return NT_STATUS_NO_MEMORY;
+               }
+       }
+       status = authsam_update_lastlogon_timestamp(sam_ctx, msg_mod, domain_dn,
+                                                   lastLogonTimestamp, now);
+       if (!NT_STATUS_IS_OK(status)) {
                TALLOC_FREE(mem_ctx);
-               return NT_STATUS_INTERNAL_ERROR;
+               return NT_STATUS_NO_MEMORY;
        }
 
+       if (msg_mod->num_elements > 0) {
+               ret = dsdb_replace(sam_ctx, msg_mod, 0);
+               if (ret != LDB_SUCCESS) {
+                       DEBUG(0, ("Failed to set badPwdCount and lockoutTime "
+                                 "to 0 and/or  lastlogon to now (%lld) "
+                                 "%s: %s\n", (long long int)now,
+                                 ldb_dn_get_linearized(msg_mod->dn),
+                                 ldb_errstring(sam_ctx)));
+                       TALLOC_FREE(mem_ctx);
+                       return NT_STATUS_INTERNAL_ERROR;
+               }
+       }
        TALLOC_FREE(mem_ctx);
        return NT_STATUS_OK;
 }
index fddbb8f24c80d3aa2d932c1c87e24a47d346eaf9..e669532b5122a6170c3af11fa45af73a0ebaa539 100755 (executable)
@@ -51,10 +51,24 @@ if len(args) < 1:
 host = args[0]
 
 lp = sambaopts.get_loadparm()
-creds = credopts.get_credentials(lp)
+global_creds = credopts.get_credentials(lp)
 
 # Force an encrypted connection
-creds.set_gensec_features(creds.get_gensec_features() | gensec.FEATURE_SEAL)
+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
 
 #
 # Tests start here
@@ -129,6 +143,12 @@ userAccountControl: %d
         if mode == "ignore":
             return
 
+        if mode == "absent":
+            self.assertFalse(name in res[0],
+                            msg="attr[%s] not missing on dn[%s]" %
+                            (name, res[0].dn))
+            return
+
         self.assertTrue(name in res[0],
                         msg="attr[%s] missing on dn[%s]" %
                         (name, res[0].dn))
@@ -136,39 +156,56 @@ userAccountControl: %d
                         msg="attr[%s]=%r on dn[%s]" %
                         (name, res[0][name], res[0].dn))
 
+
+        print  "%s = '%s'" % (name, res[0][name][0])
+
         if mode == "present":
             return
+
         if mode == "equal":
-            self.assertTrue(str(res[0][name][0]) == str(value),
-                            msg="attr[%s]=[%s] != [%s] on dn[%s]" %
-                            (name, str(res[0][name][0]), str(value), res[0].dn))
+            v = int(res[0][name][0])
+            value = int(value)
+            msg = ("attr[%s]=[%s] != [%s] on dn[%s]\n"
+                   "(diff %d; actual value is %s than expected)"  %
+                   (name, v, value, res[0].dn, v - value,
+                    ('less' if v < value else 'greater')))
+
+            self.assertTrue(v == value, msg)
             return
+
         if mode == "greater":
             v = int(res[0][name][0])
             self.assertTrue(v > int(value),
-                            msg="attr[%s]=[%s] <= [%s] on dn[%s]" %
-                            (name, v, int(value), res[0].dn))
+                            msg="attr[%s]=[%s] <= [%s] on dn[%s] (diff %d)" %
+                            (name, v, int(value), res[0].dn, v - int(value)))
             return
         if mode == "less":
             v = int(res[0][name][0])
             self.assertTrue(v < int(value),
-                            msg="attr[%s]=[%s] >= [%s] on dn[%s]" %
-                            (name, v, int(value), res[0].dn))
+                            msg="attr[%s]=[%s] >= [%s] on dn[%s] (diff %d)" %
+                            (name, v, int(value), res[0].dn, v - int(value)))
             return
         self.assertEqual(mode, not mode, "Invalid Mode[%s]" % mode)
 
     def _check_account(self, dn,
                        badPwdCount=None,
                        badPasswordTime=None,
+                       lastLogon=None,
+                       lastLogonTimestamp=None,
                        lockoutTime=None,
                        userAccountControl=None,
                        msDSUserAccountControlComputed=None,
-                       effective_bad_password_count=None):
-
+                       effective_bad_password_count=None,
+                       msg=None):
+        print '-=' * 36
+        if msg is not None:
+            print  "\033[01;32m %s \033[00m\n" % msg
         attrs = [
            "objectSid",
            "badPwdCount",
            "badPasswordTime",
+           "lastLogon",
+           "lastLogonTimestamp",
            "lockoutTime",
            "userAccountControl",
            "msDS-User-Account-Control-Computed"
@@ -182,6 +219,8 @@ userAccountControl: %d
         self.assertTrue(len(res) == 1)
         self._check_attribute(res, "badPwdCount", badPwdCount)
         self._check_attribute(res, "badPasswordTime", badPasswordTime)
+        self._check_attribute(res, "lastLogon", lastLogon)
+        self._check_attribute(res, "lastLogonTimestamp", lastLogonTimestamp)
         self._check_attribute(res, "lockoutTime", lockoutTime)
         self._check_attribute(res, "userAccountControl", userAccountControl)
         self._check_attribute(res, "msDS-User-Account-Control-Computed",
@@ -246,7 +285,8 @@ userAccountControl: %d
     def setUp(self):
         super(PasswordTests, self).setUp()
 
-        self.ldb = SamDB(url=host_url, session_info=system_session(lp), credentials=creds, lp=lp)
+        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()
@@ -325,7 +365,7 @@ lockoutThreshold: """ + str(lockoutThreshold) + """
         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, creds)
+        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)
 
@@ -339,6 +379,8 @@ lockoutThreshold: """ + str(lockoutThreshold) + """
         res = self._check_account("cn=testuser,cn=users," + self.base_dn,
                                   badPwdCount=0,
                                   badPasswordTime=0,
+                                  lastLogon=0,
+                                  lastLogonTimestamp=('absent', None),
                                   userAccountControl=
                                     dsdb.UF_NORMAL_ACCOUNT |
                                     dsdb.UF_ACCOUNTDISABLE |
@@ -353,6 +395,8 @@ lockoutThreshold: """ + str(lockoutThreshold) + """
         res = self._check_account("cn=testuser,cn=users," + self.base_dn,
                                   badPwdCount=0,
                                   badPasswordTime=0,
+                                  lastLogon=0,
+                                  lastLogonTimestamp=('absent', None),
                                   userAccountControl=
                                     dsdb.UF_NORMAL_ACCOUNT |
                                     dsdb.UF_ACCOUNTDISABLE |
@@ -381,6 +425,8 @@ userPassword: thatsAcomplPASS2
         res = self._check_account("cn=testuser,cn=users," + self.base_dn,
                                   badPwdCount=1,
                                   badPasswordTime=("greater", 0),
+                                  lastLogon=0,
+                                  lastLogonTimestamp=('absent', None),
                                   userAccountControl=
                                     dsdb.UF_NORMAL_ACCOUNT |
                                     dsdb.UF_ACCOUNTDISABLE |
@@ -404,6 +450,8 @@ userPassword: thatsAcomplPASS1
         res = self._check_account("cn=testuser,cn=users," + self.base_dn,
                                   badPwdCount=1,
                                   badPasswordTime=badPasswordTime,
+                                  lastLogon=0,
+                                  lastLogonTimestamp=('absent', None),
                                   userAccountControl=
                                     dsdb.UF_NORMAL_ACCOUNT |
                                     dsdb.UF_ACCOUNTDISABLE |
@@ -416,6 +464,8 @@ userPassword: thatsAcomplPASS1
         res = self._check_account("cn=testuser,cn=users," + self.base_dn,
                                   badPwdCount=1,
                                   badPasswordTime=badPasswordTime,
+                                  lastLogon=0,
+                                  lastLogonTimestamp=('absent', None),
                                   userAccountControl=
                                     dsdb.UF_NORMAL_ACCOUNT,
                                   msDSUserAccountControlComputed=0)
@@ -423,24 +473,22 @@ userPassword: thatsAcomplPASS1
         # 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 = Credentials()
-        creds2.set_username("testuser")
-        creds2.set_password("thatsAcomplPASS1")
-        creds2.set_domain(creds.get_domain())
-        creds2.set_realm(creds.get_realm())
-        creds2.set_workstation(creds.get_workstation())
-        creds2.set_gensec_features(creds2.get_gensec_features()
-                                                          | gensec.FEATURE_SEAL)
+        creds2 = insta_creds()
 
         self.ldb2 = SamDB(url=host_url, credentials=creds2, lp=lp)
 
         res = self._check_account("cn=testuser,cn=users," + self.base_dn,
                                   badPwdCount=0,
                                   badPasswordTime=badPasswordTime,
+                                  lastLogon=('greater', 0),
+                                  lastLogonTimestamp=('greater', 0),
                                   userAccountControl=
                                     dsdb.UF_NORMAL_ACCOUNT,
                                   msDSUserAccountControlComputed=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({
@@ -451,6 +499,8 @@ userPassword: thatsAcomplPASS1
         res = self._check_account("cn=testuser3,cn=users," + self.base_dn,
                                   badPwdCount=0,
                                   badPasswordTime=0,
+                                  lastLogon=0,
+                                  lastLogonTimestamp=('absent', None),
                                   userAccountControl=
                                     dsdb.UF_NORMAL_ACCOUNT |
                                     dsdb.UF_ACCOUNTDISABLE |
@@ -479,6 +529,8 @@ userPassword: thatsAcomplPASS2
         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 |
@@ -502,6 +554,8 @@ userPassword: thatsAcomplPASS1
         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 |
@@ -514,6 +568,8 @@ userPassword: thatsAcomplPASS1
         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)
@@ -521,19 +577,16 @@ userPassword: thatsAcomplPASS1
         # 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 = Credentials()
+        creds3 = insta_creds()
         creds3.set_username("testuser3")
         creds3.set_password("thatsAcomplPASS1")
-        creds3.set_domain(creds.get_domain())
-        creds3.set_realm(creds.get_realm())
-        creds3.set_workstation(creds.get_workstation())
-        creds3.set_gensec_features(creds3.get_gensec_features()
-                                                          | gensec.FEATURE_SEAL)
         self.ldb3 = SamDB(url=host_url, credentials=creds3, lp=lp)
 
         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)
@@ -546,10 +599,14 @@ userPassword: thatsAcomplPASS1
         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])
 
         # Change password on a connection as another user
 
@@ -571,6 +628,8 @@ userPassword: thatsAcomplPASS2
         res = self._check_account("cn=testuser,cn=users," + self.base_dn,
                                   badPwdCount=1,
                                   badPasswordTime=("greater", badPasswordTime),
+                                  lastLogon=lastLogon,
+                                  lastLogonTimestamp=lastLogonTimestamp,
                                   userAccountControl=
                                     dsdb.UF_NORMAL_ACCOUNT,
                                   msDSUserAccountControlComputed=0)
@@ -589,6 +648,8 @@ userPassword: thatsAcomplPASS2
         res = self._check_account("cn=testuser,cn=users," + self.base_dn,
                                   badPwdCount=1,
                                   badPasswordTime=badPasswordTime,
+                                  lastLogon=lastLogon,
+                                  lastLogonTimestamp=lastLogonTimestamp,
                                   userAccountControl=
                                     dsdb.UF_NORMAL_ACCOUNT,
                                   msDSUserAccountControlComputed=0)
@@ -611,6 +672,8 @@ userPassword: thatsAcomplPASS2
         res = self._check_account("cn=testuser,cn=users," + self.base_dn,
                                   badPwdCount=2,
                                   badPasswordTime=("greater", badPasswordTime),
+                                  lastLogon=lastLogon,
+                                  lastLogonTimestamp=lastLogonTimestamp,
                                   userAccountControl=
                                     dsdb.UF_NORMAL_ACCOUNT,
                                   msDSUserAccountControlComputed=0)
@@ -636,6 +699,8 @@ userPassword: thatsAcomplPASS2
         res = self._check_account("cn=testuser,cn=users," + self.base_dn,
                                   badPwdCount=3,
                                   badPasswordTime=("greater", badPasswordTime),
+                                  lastLogon=lastLogon,
+                                  lastLogonTimestamp=lastLogonTimestamp,
                                   lockoutTime=("greater", badPasswordTime),
                                   userAccountControl=
                                     dsdb.UF_NORMAL_ACCOUNT,
@@ -661,6 +726,8 @@ userPassword: thatsAcomplPASS2
         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,
@@ -685,6 +752,8 @@ userPassword: thatsAcomplPASS2
                                   badPwdCount=3,
                                   badPasswordTime=badPasswordTime,
                                   lockoutTime=lockoutTime,
+                                  lastLogon=lastLogon,
+                                  lastLogonTimestamp=lastLogonTimestamp,
                                   userAccountControl=
                                     dsdb.UF_NORMAL_ACCOUNT,
                                   msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
@@ -707,6 +776,8 @@ userPassword: thatsAcomplPASS2x
         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,
@@ -723,6 +794,8 @@ userPassword: thatsAcomplPASS2
         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,
@@ -746,6 +819,8 @@ userPassword: thatsAcomplPASS2x
         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,
@@ -763,6 +838,8 @@ userPassword: thatsAcomplPASS2x
         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,
@@ -788,16 +865,21 @@ unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2x\"".encode('utf-16-le'))
                                   badPwdCount=3,
                                   badPasswordTime=badPasswordTime,
                                   lockoutTime=lockoutTime,
+                                  lastLogon=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("cn=testuser,cn=users," + self.base_dn,
                                   badPwdCount=0,
                                   badPasswordTime=badPasswordTime,
                                   lockoutTime=0,
+                                  lastLogon=lastLogon,
+                                  lastLogonTimestamp=lastLogonTimestamp,
                                   userAccountControl=
                                     dsdb.UF_NORMAL_ACCOUNT,
                                   msDSUserAccountControlComputed=0)
@@ -817,6 +899,8 @@ unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2x\"".encode('utf-16-le'))
                                   badPwdCount=0,
                                   badPasswordTime=badPasswordTime,
                                   lockoutTime=0,
+                                  lastLogon=lastLogon,
+                                  lastLogonTimestamp=lastLogonTimestamp,
                                   userAccountControl=
                                     dsdb.UF_NORMAL_ACCOUNT,
                                   msDSUserAccountControlComputed=0)
@@ -840,6 +924,8 @@ userPassword: thatsAcomplPASS2XYZ
                                   badPwdCount=1,
                                   badPasswordTime=("greater", badPasswordTime),
                                   lockoutTime=0,
+                                  lastLogon=lastLogon,
+                                  lastLogonTimestamp=lastLogonTimestamp,
                                   userAccountControl=
                                     dsdb.UF_NORMAL_ACCOUNT,
                                   msDSUserAccountControlComputed=0)
@@ -864,6 +950,8 @@ userPassword: thatsAcomplPASS2XYZ
                                   badPwdCount=2,
                                   badPasswordTime=("greater", badPasswordTime),
                                   lockoutTime=0,
+                                  lastLogon=lastLogon,
+                                  lastLogonTimestamp=lastLogonTimestamp,
                                   userAccountControl=
                                     dsdb.UF_NORMAL_ACCOUNT,
                                   msDSUserAccountControlComputed=0)
@@ -874,6 +962,8 @@ userPassword: thatsAcomplPASS2XYZ
         res = self._check_account("cn=testuser,cn=users," + self.base_dn,
                                   badPwdCount=0,
                                   badPasswordTime=badPasswordTime,
+                                  lastLogon=lastLogon,
+                                  lastLogonTimestamp=lastLogonTimestamp,
                                   lockoutTime=0,
                                   userAccountControl=
                                     dsdb.UF_NORMAL_ACCOUNT,
@@ -895,10 +985,13 @@ userPassword: thatsAcomplPASS2XYZ
         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])
 
         # Change password on a connection as another user
 
@@ -920,6 +1013,8 @@ unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le'))
         res = self._check_account("cn=testuser,cn=users," + self.base_dn,
                                   badPwdCount=1,
                                   badPasswordTime=("greater", badPasswordTime),
+                                  lastLogon=lastLogon,
+                                  lastLogonTimestamp=lastLogon,
                                   userAccountControl=
                                     dsdb.UF_NORMAL_ACCOUNT,
                                   msDSUserAccountControlComputed=0)
@@ -938,6 +1033,8 @@ unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le'))
         res = self._check_account("cn=testuser,cn=users," + self.base_dn,
                                   badPwdCount=1,
                                   badPasswordTime=badPasswordTime,
+                                  lastLogon=lastLogon,
+                                  lastLogonTimestamp=lastLogon,
                                   userAccountControl=
                                     dsdb.UF_NORMAL_ACCOUNT,
                                   msDSUserAccountControlComputed=0)
@@ -960,6 +1057,8 @@ unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le'))
         res = self._check_account("cn=testuser,cn=users," + self.base_dn,
                                   badPwdCount=2,
                                   badPasswordTime=("greater", badPasswordTime),
+                                  lastLogon=lastLogon,
+                                  lastLogonTimestamp=lastLogon,
                                   userAccountControl=
                                     dsdb.UF_NORMAL_ACCOUNT,
                                   msDSUserAccountControlComputed=0)
@@ -973,6 +1072,8 @@ unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le'))
         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)
@@ -998,6 +1099,8 @@ unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le'))
         res = self._check_account("cn=testuser,cn=users," + self.base_dn,
                                   badPwdCount=3,
                                   badPasswordTime=("greater", badPasswordTime),
+                                  lastLogon=lastLogon,
+                                  lastLogonTimestamp=lastLogon,
                                   lockoutTime=("greater", badPasswordTime),
                                   userAccountControl=
                                     dsdb.UF_NORMAL_ACCOUNT,
@@ -1023,6 +1126,8 @@ unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le'))
         res = self._check_account("cn=testuser,cn=users," + self.base_dn,
                                   badPwdCount=3,
                                   badPasswordTime=badPasswordTime,
+                                  lastLogon=lastLogon,
+                                  lastLogonTimestamp=lastLogon,
                                   lockoutTime=lockoutTime,
                                   userAccountControl=
                                     dsdb.UF_NORMAL_ACCOUNT,
@@ -1046,6 +1151,8 @@ unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le'))
         res = self._check_account("cn=testuser,cn=users," + self.base_dn,
                                   badPwdCount=3,
                                   badPasswordTime=badPasswordTime,
+                                  lastLogon=lastLogon,
+                                  lastLogonTimestamp=lastLogon,
                                   lockoutTime=lockoutTime,
                                   userAccountControl=
                                     dsdb.UF_NORMAL_ACCOUNT,
@@ -1069,6 +1176,8 @@ unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2x\"".encode('utf-16-le'))
         res = self._check_account("cn=testuser,cn=users," + self.base_dn,
                                   badPwdCount=3,
                                   badPasswordTime=badPasswordTime,
+                                  lastLogon=lastLogon,
+                                  lastLogonTimestamp=lastLogon,
                                   lockoutTime=lockoutTime,
                                   userAccountControl=
                                     dsdb.UF_NORMAL_ACCOUNT,
@@ -1080,6 +1189,8 @@ unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2x\"".encode('utf-16-le'))
         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,
@@ -1098,6 +1209,8 @@ unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2x\"".encode('utf-16-le'))
         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,
@@ -1121,6 +1234,8 @@ unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le'))
         res = self._check_account("cn=testuser,cn=users," + self.base_dn,
                                   badPwdCount=1,
                                   badPasswordTime=("greater", badPasswordTime),
+                                  lastLogon=lastLogon,
+                                  lastLogonTimestamp=lastLogon,
                                   lockoutTime=0,
                                   userAccountControl=
                                     dsdb.UF_NORMAL_ACCOUNT,
@@ -1145,6 +1260,8 @@ unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le'))
         res = self._check_account("cn=testuser,cn=users," + self.base_dn,
                                   badPwdCount=2,
                                   badPasswordTime=("greater", badPasswordTime),
+                                  lastLogon=lastLogon,
+                                  lastLogonTimestamp=lastLogon,
                                   lockoutTime=0,
                                   userAccountControl=
                                     dsdb.UF_NORMAL_ACCOUNT,
@@ -1158,6 +1275,8 @@ unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le'))
         res = self._check_account("cn=testuser,cn=users," + self.base_dn,
                                   badPwdCount=2,
                                   badPasswordTime=badPasswordTime,
+                                  lastLogon=lastLogon,
+                                  lastLogonTimestamp=lastLogon,
                                   lockoutTime=0,
                                   userAccountControl=
                                     dsdb.UF_NORMAL_ACCOUNT,
@@ -1181,6 +1300,8 @@ unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le'))
         res = self._check_account("cn=testuser,cn=users," + self.base_dn,
                                   badPwdCount=3,
                                   badPasswordTime=("greater", badPasswordTime),
+                                  lastLogon=lastLogon,
+                                  lastLogonTimestamp=lastLogon,
                                   lockoutTime=("greater", badPasswordTime),
                                   userAccountControl=
                                     dsdb.UF_NORMAL_ACCOUNT,
@@ -1193,6 +1314,8 @@ unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le'))
         res = self._check_account("cn=testuser,cn=users," + self.base_dn,
                                   badPwdCount=3, effective_bad_password_count=0,
                                   badPasswordTime=badPasswordTime,
+                                  lastLogon=lastLogon,
+                                  lastLogonTimestamp=lastLogon,
                                   lockoutTime=lockoutTime,
                                   userAccountControl=
                                     dsdb.UF_NORMAL_ACCOUNT,
@@ -1207,34 +1330,44 @@ unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le'))
                                   badPwdCount=3, effective_bad_password_count=0,
                                   badPasswordTime=badPasswordTime,
                                   lockoutTime=lockoutTime,
+                                  lastLogon=lastLogon,
+                                  lastLogonTimestamp=lastLogon,
                                   userAccountControl=
                                     dsdb.UF_NORMAL_ACCOUNT,
                                   msDSUserAccountControlComputed=0)
 
     def _test_login_lockout(self, use_kerberos):
         # This unlocks by waiting for account_lockout_duration
-        print "Performs a lockout attempt against LDAP using NTLM or Kerberos"
+        if use_kerberos == MUST_USE_KERBEROS:
+            lastlogon_relation = 'greater'
+            print "Performs a lockout attempt against LDAP using Kerberos"
+        else:
+            lastlogon_relation = 'equal'
+            print "Performs a lockout attempt against LDAP using NTLM"
 
         # Change password on a connection as another user
-
         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])
+        firstLogon = lastLogon
+        lastLogonTimestamp = int(res[0]["lastLogonTimestamp"][0])
+        print firstLogon
+        print 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 = Credentials()
-        creds_lockout.set_username("testuser")
-        creds_lockout.set_domain(creds.get_domain())
-        creds_lockout.set_realm(creds.get_realm())
-        creds_lockout.set_workstation(creds.get_workstation())
-        creds_lockout.set_gensec_features(creds_lockout.get_gensec_features()
-                                          | gensec.FEATURE_SEAL)
+        creds_lockout = insta_creds()
         creds_lockout.set_kerberos_state(use_kerberos)
 
         # The wrong password
@@ -1245,9 +1378,12 @@ unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le'))
         res = self._check_account("cn=testuser,cn=users," + self.base_dn,
                                   badPwdCount=1,
                                   badPasswordTime=("greater", badPasswordTime),
+                                  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
@@ -1255,12 +1391,20 @@ unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le'))
 
         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("cn=testuser,cn=users," + self.base_dn,
                                   badPwdCount=0,
                                   badPasswordTime=badPasswordTime,
+                                  lastLogon=('greater', lastLogon),
+                                  lastLogonTimestamp=lastLogonTimestamp,
                                   userAccountControl=
                                     dsdb.UF_NORMAL_ACCOUNT,
-                                  msDSUserAccountControlComputed=0)
+                                  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")
@@ -1270,6 +1414,8 @@ unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le'))
         res = self._check_account("cn=testuser,cn=users," + self.base_dn,
                                   badPwdCount=1,
                                   badPasswordTime=("greater", badPasswordTime),
+                                  lastLogon=lastLogon,
+                                  lastLogonTimestamp=lastLogonTimestamp,
                                   userAccountControl=
                                     dsdb.UF_NORMAL_ACCOUNT,
                                   msDSUserAccountControlComputed=0)
@@ -1288,6 +1434,8 @@ unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le'))
         res = self._check_account("cn=testuser,cn=users," + self.base_dn,
                                   badPwdCount=2,
                                   badPasswordTime=("greater", badPasswordTime),
+                                  lastLogon=lastLogon,
+                                  lastLogonTimestamp=lastLogonTimestamp,
                                   userAccountControl=
                                     dsdb.UF_NORMAL_ACCOUNT,
                                   msDSUserAccountControlComputed=0)
@@ -1308,6 +1456,8 @@ unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le'))
         res = self._check_account("cn=testuser,cn=users," + self.base_dn,
                                   badPwdCount=3,
                                   badPasswordTime=("greater", badPasswordTime),
+                                  lastLogon=lastLogon,
+                                  lastLogonTimestamp=lastLogonTimestamp,
                                   lockoutTime=("greater", badPasswordTime),
                                   userAccountControl=
                                     dsdb.UF_NORMAL_ACCOUNT,
@@ -1326,6 +1476,8 @@ unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le'))
         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,
@@ -1342,12 +1494,14 @@ unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le'))
         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)
 
-        # The correct password
+        # 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)
@@ -1358,32 +1512,50 @@ unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le'))
         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)
 
+        # 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, effective_bad_password_count=0,
                                   badPasswordTime=badPasswordTime,
                                   lockoutTime=lockoutTime,
+                                  lastLogon=lastLogon,
+                                  lastLogonTimestamp=lastLogonTimestamp,
                                   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")
-        ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
+
+        creds_lockout2 = insta_creds(creds_lockout)
+
+        ldb_lockout = SamDB(url=host_url, credentials=creds_lockout2, lp=lp)
+        time.sleep(3)
 
         res = self._check_account("cn=testuser,cn=users," + self.base_dn,
                                   badPwdCount=0,
                                   badPasswordTime=badPasswordTime,
+                                  lastLogon=(lastlogon_relation, lastLogon),
+                                  lastLogonTimestamp=lastLogonTimestamp,
                                   lockoutTime=0,
                                   userAccountControl=
                                     dsdb.UF_NORMAL_ACCOUNT,
-                                  msDSUserAccountControlComputed=0)
+                                  msDSUserAccountControlComputed=0,
+                                  msg="lastLogon is way off")
+
+        lastLogon = int(res[0]["lastLogon"][0])
 
         # The wrong password
         creds_lockout.set_password("thatsAcomplPASS1x")
@@ -1397,6 +1569,8 @@ unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le'))
                                   badPwdCount=1,
                                   badPasswordTime=("greater", badPasswordTime),
                                   lockoutTime=0,
+                                  lastLogon=lastLogon,
+                                  lastLogonTimestamp=lastLogonTimestamp,
                                   userAccountControl=
                                     dsdb.UF_NORMAL_ACCOUNT,
                                   msDSUserAccountControlComputed=0)
@@ -1414,6 +1588,8 @@ unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le'))
                                   badPwdCount=2,
                                   badPasswordTime=("greater", badPasswordTime),
                                   lockoutTime=0,
+                                  lastLogon=lastLogon,
+                                  lastLogonTimestamp=lastLogonTimestamp,
                                   userAccountControl=
                                     dsdb.UF_NORMAL_ACCOUNT,
                                   msDSUserAccountControlComputed=0)
@@ -1425,6 +1601,8 @@ unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le'))
                                   badPwdCount=2, effective_bad_password_count=0,
                                   badPasswordTime=badPasswordTime,
                                   lockoutTime=0,
+                                  lastLogon=lastLogon,
+                                  lastLogonTimestamp=lastLogonTimestamp,
                                   userAccountControl=
                                     dsdb.UF_NORMAL_ACCOUNT,
                                   msDSUserAccountControlComputed=0)
@@ -1441,6 +1619,8 @@ unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le'))
                                   badPwdCount=1,
                                   badPasswordTime=("greater", badPasswordTime),
                                   lockoutTime=0,
+                                  lastLogon=lastLogon,
+                                  lastLogonTimestamp=lastLogonTimestamp,
                                   userAccountControl=
                                     dsdb.UF_NORMAL_ACCOUNT,
                                   msDSUserAccountControlComputed=0)
@@ -1454,6 +1634,8 @@ unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le'))
                                   badPwdCount=0,
                                   badPasswordTime=badPasswordTime,
                                   lockoutTime=0,
+                                  lastLogon=("greater", lastLogon),
+                                  lastLogonTimestamp=lastLogonTimestamp,
                                   userAccountControl=
                                     dsdb.UF_NORMAL_ACCOUNT,
                                   msDSUserAccountControlComputed=0)
@@ -1464,6 +1646,78 @@ unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le'))
     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)
+
+        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)
+
+        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,
+                                  msg=("second logon, firstlogon was %s" %
+                                       firstLogon))
+
+
+        lastLogon = int(res[0]["lastLogon"][0])
+
+        time.sleep(1)
+
+        SamDB(url=host_url, credentials=insta_creds(creds2), lp=lp)
+
+        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_ntlm(self):
+        self._test_multiple_logon(DONT_USE_KERBEROS)
+
+    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)
index 77f6a9079dd77c2a158fff62bd3a8447c6036bf6..c4a4bb4f7345bd47ad8a42de7e185655cb43d2e9 100644 (file)
@@ -184,10 +184,13 @@ static krb5_error_code hdb_samba4_auth_status(krb5_context context, HDB *db,
                                                                        struct samba_kdc_db_context);
        struct samba_kdc_entry *p = talloc_get_type(entry->ctx, struct samba_kdc_entry);
 
+       struct ldb_dn *domain_dn = ldb_get_default_basedn(kdc_db_ctx->samdb);
+
        if (hdb_auth_status == HDB_AUTH_WRONG_PASSWORD) {
-               authsam_update_bad_pwd_count(kdc_db_ctx->samdb, p->msg, ldb_get_default_basedn(kdc_db_ctx->samdb));
+               authsam_update_bad_pwd_count(kdc_db_ctx->samdb, p->msg, domain_dn);
        } else if (hdb_auth_status == HDB_AUTH_SUCCESS) {
-               authsam_zero_bad_pwd_count(kdc_db_ctx->samdb, p->msg);
+               authsam_logon_success_accounting(kdc_db_ctx->samdb, p->msg,
+                                                domain_dn, true);
        }
        return 0;
 }