[SCM] Samba Shared Repository - branch master updated

Andrew Bartlett abartlet at samba.org
Thu May 5 01:20:01 UTC 2022


The branch, master has been updated
       via  7a36b018889 dsdb: Do not reuse "ret" variable as return code and for memcmp() comparison
       via  2f17cbf3b29 tests/krb5: Allow passing expected etypes to get_keys()
       via  c294f729110 tests/passwords: Add tests for password history with simple binds
       via  08904752bba tests/passwords: Remove unused imports
       via  127fe361b83 selftest: Run some tests in the ad_dc_no_ntlm environment to show expected behaviour
       via  a9caf760b6f selftest: Rework password_lockout_base.py to allow logon_basics test to be run in ad_dc_no_ntlm
       via  f85f6f89f12 samba-tool user: Consistently return a tuple
       via  c3b2dae027e samba-tool user: Remove unused imports
       via  332b874a166 samba-tool tests: Remove unused variable
       via  5348bd80035 dsdb: Clarify that most errors in make_error_and_update_badPwdCount() are not returned
      from  ddeedcb6b2a gpo: Add Cert Auto Enroll Advanced Config

https://git.samba.org/?p=samba.git;a=shortlog;h=master


- Log -----------------------------------------------------------------
commit 7a36b01888995031d00dbdba208fc9f522658f86
Author: Andrew Bartlett <abartlet at samba.org>
Date:   Thu Mar 31 21:22:08 2022 +1300

    dsdb: Do not reuse "ret" variable as return code and for memcmp() comparison
    
    Signed-off-by: Andrew Bartlett <abartlet at samba.org>
    Reviewed-by: Joseph Sutton <josephsutton at catalyst.net.nz>
    
    Autobuild-User(master): Andrew Bartlett <abartlet at samba.org>
    Autobuild-Date(master): Thu May  5 01:19:54 UTC 2022 on sn-devel-184

commit 2f17cbf3b295663a91e4facb0dc8f09ef4a77f4a
Author: Joseph Sutton <josephsutton at catalyst.net.nz>
Date:   Mon Apr 11 15:43:00 2022 +1200

    tests/krb5: Allow passing expected etypes to get_keys()
    
    Signed-off-by: Joseph Sutton <josephsutton at catalyst.net.nz>
    Reviewed-by: Andrew Bartlett <abartlet at samba.org>

commit c294f729110f59b68c567bfe2b6da3a297a829a9
Author: Joseph Sutton <josephsutton at catalyst.net.nz>
Date:   Mon Apr 11 16:43:42 2022 +1200

    tests/passwords: Add tests for password history with simple binds
    
    Signed-off-by: Joseph Sutton <josephsutton at catalyst.net.nz>
    Reviewed-by: Andrew Bartlett <abartlet at samba.org>

commit 08904752bba49039cf90534e6285defa17d23a0b
Author: Joseph Sutton <josephsutton at catalyst.net.nz>
Date:   Mon Apr 11 16:37:10 2022 +1200

    tests/passwords: Remove unused imports
    
    Signed-off-by: Joseph Sutton <josephsutton at catalyst.net.nz>
    Reviewed-by: Andrew Bartlett <abartlet at samba.org>

commit 127fe361b83326d351944f9d641d75a5cee9deaa
Author: Andrew Bartlett <abartlet at samba.org>
Date:   Thu Mar 31 21:16:03 2022 +1300

    selftest: Run some tests in the ad_dc_no_ntlm environment to show expected behaviour
    
    Signed-off-by: Andrew Bartlett <abartlet at samba.org>
    Reviewed-by: Joseph Sutton <josephsutton at catalyst.net.nz>

commit a9caf760b6f952461ecd4894b0cab1c2648f1e96
Author: Andrew Bartlett <abartlet at samba.org>
Date:   Thu Mar 31 22:45:40 2022 +1300

    selftest: Rework password_lockout_base.py to allow logon_basics test to be run in ad_dc_no_ntlm
    
    We need to ensure that even if NTLM is disabled, that the test
    can still bootstrap and fail normally.
    
    Signed-off-by: Andrew Bartlett <abartlet at samba.org>
    Reviewed-by: Joseph Sutton <josephsutton at catalyst.net.nz>

commit f85f6f89f128882d96ba0613dc7647f00100e8d3
Author: Joseph Sutton <josephsutton at catalyst.net.nz>
Date:   Mon Apr 11 11:50:53 2022 +1200

    samba-tool user: Consistently return a tuple
    
    We would get an error when get_userPassword_hash() returned None, as
    get_virtual_crypt_value() would try to unpack the result as a 2-element
    tuple.
    
    Signed-off-by: Joseph Sutton <josephsutton at catalyst.net.nz>
    Reviewed-by: Andrew Bartlett <abartlet at samba.org>

commit c3b2dae027eeb980227160ab7ded7fe108b0ea14
Author: Joseph Sutton <josephsutton at catalyst.net.nz>
Date:   Mon Apr 11 11:50:25 2022 +1200

    samba-tool user: Remove unused imports
    
    Signed-off-by: Joseph Sutton <josephsutton at catalyst.net.nz>
    Reviewed-by: Andrew Bartlett <abartlet at samba.org>

commit 332b874a1665c0f3003bacfb3a3b28d55677cf74
Author: Joseph Sutton <josephsutton at catalyst.net.nz>
Date:   Mon Apr 11 13:15:23 2022 +1200

    samba-tool tests: Remove unused variable
    
    Signed-off-by: Joseph Sutton <josephsutton at catalyst.net.nz>
    Reviewed-by: Andrew Bartlett <abartlet at samba.org>

commit 5348bd80035025e91158088db8efdea971b70557
Author: Andrew Bartlett <abartlet at samba.org>
Date:   Fri Apr 1 12:06:45 2022 +1300

    dsdb: Clarify that most errors in make_error_and_update_badPwdCount() are not returned
    
    This is mainly just to be clear, and was done while failing to work around compiler
    warnings.
    
    For the curious it was gcc version 4.8.5 20150623 (Red Hat 4.8.5-44) (CentOS 7)
    build with -O3, which gave with other, later patches:
    
    ../../source4/dsdb/samdb/ldb_modules/password_hash.c: In function ‘check_password_restrictions_and_log’:
    ../../source4/dsdb/samdb/ldb_modules/password_hash.c:3231:5: error: assuming signed overflow does not occur when simplifying conditional to constant [-Werror=strict-overflow]
      if (ret == LDB_SUCCESS) {
         ^
    
    Regardless, we make it clear that all values assigned to "ret" are
    local small constants.
    
    Signed-off-by: Andrew Bartlett <abartlet at samba.org>
    Reviewed-by: Joseph Sutton <josephsutton at catalyst.net.nz>

-----------------------------------------------------------------------

Summary of changes:
 python/samba/netcmd/user.py                        |   8 +-
 python/samba/tests/krb5/kdc_base_test.py           |   7 +-
 python/samba/tests/krb5/protected_users_tests.py   |   9 +-
 python/samba/tests/samba_tool/user.py              |  17 +-
 selftest/knownfail.d/nt-hash-support-gone          |   8 +
 source4/dsdb/samdb/ldb_modules/password_hash.c     |  27 +--
 source4/dsdb/tests/python/password_lockout.py      |  14 +-
 source4/dsdb/tests/python/password_lockout_base.py |  34 +--
 source4/dsdb/tests/python/passwords.py             | 253 ++++++++++++++++++++-
 source4/selftest/tests.py                          |  18 +-
 10 files changed, 332 insertions(+), 63 deletions(-)
 create mode 100644 selftest/knownfail.d/nt-hash-support-gone


Changeset truncated at 500 lines:

diff --git a/python/samba/netcmd/user.py b/python/samba/netcmd/user.py
index 4d181ea793b..70be85c406a 100644
--- a/python/samba/netcmd/user.py
+++ b/python/samba/netcmd/user.py
@@ -22,8 +22,6 @@ import ldb
 import pwd
 import os
 import io
-import re
-import difflib
 import fcntl
 import signal
 import errno
@@ -33,11 +31,11 @@ import binascii
 from subprocess import Popen, PIPE, STDOUT, check_call, CalledProcessError
 from getpass import getpass
 from samba.auth import system_session
-from samba.samdb import SamDB, SamDBError, SamDBNotFoundError
+from samba.samdb import SamDB, SamDBError
 from samba.dcerpc import misc
 from samba.dcerpc import security
 from samba.dcerpc import drsblobs
-from samba.ndr import ndr_unpack, ndr_pack, ndr_print
+from samba.ndr import ndr_unpack
 from samba import (
     credentials,
     dsdb,
@@ -1480,7 +1478,7 @@ class GetPasswordCommand(Command):
             # the user password hashes. This indicates that password has been
             # changed without updating the supplemental credentials.
             if unicodePwd != bytearray(up.current_nt_hash.hash):
-                return None
+                return None, None
 
             scheme_prefix = "$%d$" % algorithm
             prefix = scheme_prefix
diff --git a/python/samba/tests/krb5/kdc_base_test.py b/python/samba/tests/krb5/kdc_base_test.py
index 16e3f7a6a73..7d180380d13 100644
--- a/python/samba/tests/krb5/kdc_base_test.py
+++ b/python/samba/tests/krb5/kdc_base_test.py
@@ -561,7 +561,7 @@ class KDCBaseTest(RawKerberosTest):
 
         return bind, identifier, attributes
 
-    def get_keys(self, samdb, dn):
+    def get_keys(self, samdb, dn, expected_etypes=None):
         admin_creds = self.get_admin_creds()
 
         bind, identifier, attributes = self.get_secrets(
@@ -599,9 +599,10 @@ class KDCBaseTest(RawKerberosTest):
                 pwd = attr.value_ctr.values[0].blob
                 keys[kcrypto.Enctype.RC4] = pwd.hex()
 
-        default_enctypes = self.get_default_enctypes()
+        if expected_etypes is None:
+            expected_etypes = self.get_default_enctypes()
 
-        self.assertCountEqual(default_enctypes, keys)
+        self.assertCountEqual(expected_etypes, keys)
 
         return keys
 
diff --git a/python/samba/tests/krb5/protected_users_tests.py b/python/samba/tests/krb5/protected_users_tests.py
index dfa6021453f..a03ccaf0c66 100755
--- a/python/samba/tests/krb5/protected_users_tests.py
+++ b/python/samba/tests/krb5/protected_users_tests.py
@@ -384,11 +384,10 @@ class ProtectedUsersTests(KDCBaseTest):
 
         client_creds.set_password(new_password)
 
-        keys = self.get_keys(samdb, client_dn)
-        self.assertEqual({kcrypto.Enctype.AES256,
-                          kcrypto.Enctype.AES128,
-                          kcrypto.Enctype.RC4},
-                         keys.keys())
+        self.get_keys(samdb, client_dn,
+                      expected_etypes={kcrypto.Enctype.AES256,
+                                       kcrypto.Enctype.AES128,
+                                       kcrypto.Enctype.RC4})
 
     # Test that DES-CBC-CRC cannot be used whether or not the user is
     # protected.
diff --git a/python/samba/tests/samba_tool/user.py b/python/samba/tests/samba_tool/user.py
index 904a51353ca..4563bb2d9a3 100644
--- a/python/samba/tests/samba_tool/user.py
+++ b/python/samba/tests/samba_tool/user.py
@@ -211,6 +211,8 @@ class UserCmdTestCase(SambaToolCmdTest):
         self.assertEqual(nidx, sc.sub.num_packages, "Unknown packages found")
 
     def test_setpassword(self):
+        expect_nt_hash = bool(int(os.environ.get("EXPECT_NT_HASH", "1")))
+
         for user in self.users:
             newpasswd = self.random_password(16)
             (result, out, err) = self.runsubcmd("user", "setpassword",
@@ -258,7 +260,6 @@ class UserCmdTestCase(SambaToolCmdTest):
             creds = credentials.Credentials()
             creds.set_anonymous()
             creds.set_password(newpasswd)
-            nthash = creds.get_nt_hash()
             unicodePwd = base64.b64encode(creds.get_nt_hash()).decode('utf8')
             virtualClearTextUTF8 = base64.b64encode(get_bytes(newpasswd)).decode('utf8')
             virtualClearTextUTF16 = base64.b64encode(get_string(newpasswd).encode('utf-16-le')).decode('utf8')
@@ -279,8 +280,11 @@ class UserCmdTestCase(SambaToolCmdTest):
                              "syncpasswords --no-wait: 'sAMAccountName': %s out[%s]" % (user["name"], out))
             self.assertMatch(out, "# unicodePwd::: REDACTED SECRET ATTRIBUTE",
                              "getpassword '# unicodePwd::: REDACTED SECRET ATTRIBUTE': out[%s]" % out)
-            self.assertMatch(out, "unicodePwd:: %s" % unicodePwd,
-                             "getpassword unicodePwd: out[%s]" % out)
+            if expect_nt_hash:
+                self.assertMatch(out, "unicodePwd:: %s" % unicodePwd,
+                                 "getpassword unicodePwd: out[%s]" % out)
+            else:
+                self.assertNotIn("unicodePwd:: %s" % unicodePwd, out)
             self.assertMatch(out, "# supplementalCredentials::: REDACTED SECRET ATTRIBUTE",
                              "getpassword '# supplementalCredentials::: REDACTED SECRET ATTRIBUTE': out[%s]" % out)
             self.assertMatch(out, "supplementalCredentials:: ",
@@ -302,8 +306,11 @@ class UserCmdTestCase(SambaToolCmdTest):
             self.assertMatch(out, "Got password OK", "getpassword without url")
             self.assertMatch(out, "sAMAccountName: %s" % (user["name"]),
                              "getpassword: 'sAMAccountName': %s out[%s]" % (user["name"], out))
-            self.assertMatch(out, "unicodePwd:: %s" % unicodePwd,
-                             "getpassword unicodePwd: out[%s]" % out)
+            if expect_nt_hash:
+                self.assertMatch(out, "unicodePwd:: %s" % unicodePwd,
+                                 "getpassword unicodePwd: out[%s]" % out)
+            else:
+                self.assertNotIn("unicodePwd:: %s" % unicodePwd, out)
             self.assertMatch(out, "supplementalCredentials:: ",
                              "getpassword supplementalCredentials: out[%s]" % out)
             self._verify_supplementalCredentials(out.replace("\nGot password OK\n", ""))
diff --git a/selftest/knownfail.d/nt-hash-support-gone b/selftest/knownfail.d/nt-hash-support-gone
new file mode 100644
index 00000000000..94672c402cb
--- /dev/null
+++ b/selftest/knownfail.d/nt-hash-support-gone
@@ -0,0 +1,8 @@
+^samba.tests.samba_tool.user.samba.tests.samba_tool.user.UserCmdTestCase.test_setpassword.ad_dc_no_ntlm:local
+^samba4.ldap.login_basics.python.ad_dc_no_ntlm..__main__.BasicUserAuthTests.test_login_basics_ntlm.ad_dc_no_ntlm
+^samba4.ldap.passwords.python.fl2003dc..__main__.PasswordTests.test_old_password_attempt_reuse.fl2003dc
+^samba4.ldap.passwords.python.fl2003dc..__main__.PasswordTests.test_old_password_rename_attempt_reuse.fl2003dc
+^samba4.ldap.passwords.python.fl2003dc..__main__.PasswordTests.test_old_password_rename_attempt_reuse_2.fl2003dc
+^samba4.ldap.passwords.python.fl2003dc..__main__.PasswordTests.test_old_password_rename_simple_bind.fl2003dc
+^samba4.ldap.passwords.python.fl2003dc..__main__.PasswordTests.test_old_password_rename_simple_bind_2.fl2003dc
+^samba4.ldap.passwords.python.fl2003dc..__main__.PasswordTests.test_old_password_simple_bind.fl2003dc
diff --git a/source4/dsdb/samdb/ldb_modules/password_hash.c b/source4/dsdb/samdb/ldb_modules/password_hash.c
index 6de8c230397..faf8a35818f 100644
--- a/source4/dsdb/samdb/ldb_modules/password_hash.c
+++ b/source4/dsdb/samdb/ldb_modules/password_hash.c
@@ -2609,7 +2609,8 @@ static int make_error_and_update_badPwdCount(struct setup_password_fields_io *io
 	struct ldb_message *mod_msg = NULL;
 	struct ldb_message *pso_msg = NULL;
 	NTSTATUS status;
-	int ret;
+	int ret; /* The errors we will actually return */
+	int dbg_ret; /* The errors we can only complain about in logs */
 
 	/* PSO search result is optional (NULL if no PSO applies) */
 	if (io->ac->pso_res != NULL) {
@@ -2646,8 +2647,8 @@ static int make_error_and_update_badPwdCount(struct setup_password_fields_io *io
 	 * Checking errors here is a bit pointless.
 	 * What can we do if we can't end the transaction?
 	 */
-	ret = ldb_next_del_trans(io->ac->module);
-	if (ret != LDB_SUCCESS) {
+	dbg_ret = ldb_next_del_trans(io->ac->module);
+	if (dbg_ret != LDB_SUCCESS) {
 		ldb_debug(ldb, LDB_DEBUG_FATAL,
 			  "Failed to abort transaction prior to update of badPwdCount of %s: %s",
 			  ldb_dn_get_linearized(io->ac->search_res->message->dn),
@@ -2659,8 +2660,8 @@ static int make_error_and_update_badPwdCount(struct setup_password_fields_io *io
 	}
 
 	/* Likewise, what should we do if we can't open a new transaction? */
-	ret = ldb_next_start_trans(io->ac->module);
-	if (ret != LDB_SUCCESS) {
+	dbg_ret = ldb_next_start_trans(io->ac->module);
+	if (dbg_ret != LDB_SUCCESS) {
 		ldb_debug(ldb, LDB_DEBUG_ERROR,
 			  "Failed to open transaction to update badPwdCount of %s: %s",
 			  ldb_dn_get_linearized(io->ac->search_res->message->dn),
@@ -2671,10 +2672,10 @@ static int make_error_and_update_badPwdCount(struct setup_password_fields_io *io
 		goto done;
 	}
 
-	ret = dsdb_module_modify(io->ac->module, mod_msg,
+	dbg_ret = dsdb_module_modify(io->ac->module, mod_msg,
 				 DSDB_FLAG_NEXT_MODULE,
 				 io->ac->req);
-	if (ret != LDB_SUCCESS) {
+	if (dbg_ret != LDB_SUCCESS) {
 		ldb_debug(ldb, LDB_DEBUG_ERROR,
 			  "Failed to update badPwdCount of %s: %s",
 			  ldb_dn_get_linearized(io->ac->search_res->message->dn),
@@ -2684,8 +2685,8 @@ static int make_error_and_update_badPwdCount(struct setup_password_fields_io *io
 		 */
 	}
 
-	ret = ldb_next_end_trans(io->ac->module);
-	if (ret != LDB_SUCCESS) {
+	dbg_ret = ldb_next_end_trans(io->ac->module);
+	if (dbg_ret != LDB_SUCCESS) {
 		ldb_debug(ldb, LDB_DEBUG_ERROR,
 			  "Failed to close transaction to update badPwdCount of %s: %s",
 			  ldb_dn_get_linearized(io->ac->search_res->message->dn),
@@ -2695,8 +2696,8 @@ static int make_error_and_update_badPwdCount(struct setup_password_fields_io *io
 		 */
 	}
 
-	ret = ldb_next_start_trans(io->ac->module);
-	if (ret != LDB_SUCCESS) {
+	dbg_ret = ldb_next_start_trans(io->ac->module);
+	if (dbg_ret != LDB_SUCCESS) {
 		ldb_debug(ldb, LDB_DEBUG_ERROR,
 			  "Failed to open transaction after update of badPwdCount of %s: %s",
 			  ldb_dn_get_linearized(io->ac->search_res->message->dn),
@@ -2841,8 +2842,8 @@ static int check_password_restrictions(struct setup_password_fields_io *io, WERR
 
 		/* checks the NT hash password history */
 		for (i = 0; i < io->o.nt_history_len; i++) {
-			ret = memcmp(io->n.nt_hash, io->o.nt_history[i].hash, 16);
-			if (ret == 0) {
+			int pw_cmp = memcmp(io->n.nt_hash, io->o.nt_history[i].hash, 16);
+			if (pw_cmp == 0) {
 				ret = LDB_ERR_CONSTRAINT_VIOLATION;
 				*werror = WERR_PASSWORD_RESTRICTION;
 				ldb_asprintf_errstring(ldb,
diff --git a/source4/dsdb/tests/python/password_lockout.py b/source4/dsdb/tests/python/password_lockout.py
index a1a0ae0e864..ed0502967e3 100755
--- a/source4/dsdb/tests/python/password_lockout.py
+++ b/source4/dsdb/tests/python/password_lockout.py
@@ -80,15 +80,21 @@ class PasswordTests(password_lockout_base.BasePasswordTestCase):
                                                    username="lockout2krb5",
                                                    userpass="thatsAcomplPASS0",
                                                    kerberos_state=MUST_USE_KERBEROS)
-        self.lockout2krb5_ldb = self._readd_user(self.lockout2krb5_creds,
-                                                 lockOutObservationWindow=self.lockout_observation_window)
+        self._readd_user(self.lockout2krb5_creds,
+                         lockOutObservationWindow=self.lockout_observation_window)
+        self.lockout2krb5_ldb = SamDB(url=self.host_url,
+                                      credentials=self.lockout2krb5_creds,
+                                      lp=lp)
 
         self.lockout2ntlm_creds = self.insta_creds(self.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)
+        self._readd_user(self.lockout2ntlm_creds,
+                         lockOutObservationWindow=self.lockout_observation_window)
+        self.lockout2ntlm_ldb = SamDB(url=self.host_url,
+                                      credentials=self.lockout2ntlm_creds,
+                                      lp=lp)
 
 
     def use_pso_lockout_settings(self, creds):
diff --git a/source4/dsdb/tests/python/password_lockout_base.py b/source4/dsdb/tests/python/password_lockout_base.py
index 5b872980b15..93371ef38f3 100644
--- a/source4/dsdb/tests/python/password_lockout_base.py
+++ b/source4/dsdb/tests/python/password_lockout_base.py
@@ -251,15 +251,26 @@ userPassword: """ + userpass + """
                                       username=username,
                                       userpass=userpass + "X",
                                       kerberos_state=use_kerberos)
+        if simple:
+            fail_creds.set_bind_dn(userdn)
+
         self._check_account_initial(userdn)
 
         # Fail once to get a badPasswordTime
         self.assertLoginFailure(ldap_url, fail_creds, self.lp)
 
-        # Succeed to reset everything to 0
-        ldb = self.assertLoginSuccess(ldap_url, creds, self.lp)
+        # Always reset with Simple bind or Kerberos, allows testing without NTLM
+        if simple or use_kerberos == MUST_USE_KERBEROS:
+            success_creds = creds
+        else:
+            success_creds = self.insta_creds(self.template_creds,
+                                             username=username,
+                                             userpass=userpass)
+            success_creds.set_bind_dn(userdn)
+            ldap_url = self.host_url_ldaps
 
-        return ldb
+        # Succeed to reset everything to 0
+        self.assertLoginSuccess(ldap_url, success_creds, self.lp)
 
     def assertLoginFailure(self, url, creds, lp, errno=ERR_INVALID_CREDENTIALS):
         try:
@@ -362,23 +373,20 @@ lockoutThreshold: """ + str(lockoutThreshold) + """
                                                    username="lockout1krb5",
                                                    userpass="thatsAcomplPASS0",
                                                    kerberos_state=MUST_USE_KERBEROS)
-        self.lockout1krb5_ldb = self._readd_user(self.lockout1krb5_creds)
+        self._readd_user(self.lockout1krb5_creds)
         self.lockout1ntlm_creds = self.insta_creds(self.template_creds,
                                                    username="lockout1ntlm",
                                                    userpass="thatsAcomplPASS0",
                                                    kerberos_state=DONT_USE_KERBEROS)
-        self.lockout1ntlm_ldb = self._readd_user(self.lockout1ntlm_creds)
+        self._readd_user(self.lockout1ntlm_creds)
         self.lockout1simple_creds = self.insta_creds(self.template_creds,
-                                                   username="lockout1simple",
-                                                   userpass="thatsAcomplPASS0",
-                                                   kerberos_state=DONT_USE_KERBEROS)
-        self.lockout1simple_ldb = self._readd_user(self.lockout1simple_creds,
-                                                   simple=True)
+                                                     username="lockout1simple",
+                                                     userpass="thatsAcomplPASS0",
+                                                     kerberos_state=DONT_USE_KERBEROS)
+        self._readd_user(self.lockout1simple_creds,
+                         simple=True)
 
     def delete_ldb_connections(self):
-        del self.lockout1krb5_ldb
-        del self.lockout1ntlm_ldb
-        del self.lockout1simple_ldb
         del self.ldb
 
     def tearDown(self):
diff --git a/source4/dsdb/tests/python/passwords.py b/source4/dsdb/tests/python/passwords.py
index d2268499be5..dbc21695eda 100755
--- a/source4/dsdb/tests/python/passwords.py
+++ b/source4/dsdb/tests/python/passwords.py
@@ -15,7 +15,6 @@ import time
 import os
 
 sys.path.insert(0, "bin/python")
-import samba
 
 from samba.tests.subunitrun import SubunitOptions, TestProgram
 from samba.tests.password_test import PasswordTestCase
@@ -24,17 +23,19 @@ import samba.getopt as options
 
 from samba.auth import system_session
 from samba.credentials import Credentials
-from samba.dcerpc import security
+from samba.dcerpc import drsblobs, misc, security
+from samba.drs_utils import drsuapi_connect
+from samba.ndr import ndr_unpack
 from ldb import SCOPE_BASE, LdbError
 from ldb import ERR_ATTRIBUTE_OR_VALUE_EXISTS
 from ldb import ERR_UNWILLING_TO_PERFORM, ERR_INSUFFICIENT_ACCESS_RIGHTS
 from ldb import ERR_NO_SUCH_ATTRIBUTE
 from ldb import ERR_CONSTRAINT_VIOLATION
+from ldb import ERR_INVALID_CREDENTIALS
 from ldb import Message, MessageElement, Dn
 from ldb import FLAG_MOD_ADD, FLAG_MOD_REPLACE, FLAG_MOD_DELETE
-from samba import gensec
+from samba import gensec, net, werror
 from samba.samdb import SamDB
-import samba.tests
 from samba.tests import delete_force
 
 parser = optparse.OptionParser("passwords.py [options] <host>")
@@ -72,12 +73,6 @@ class PasswordTests(PasswordTestCase):
         super(PasswordTests, self).setUp()
         self.ldb = SamDB(url=host, session_info=system_session(lp), credentials=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()
-
         # permit password changes during this test
         self.allow_password_changes()
 
@@ -150,6 +145,7 @@ add: userPassword
         creds2.set_gensec_features(creds2.get_gensec_features()
                                    | gensec.FEATURE_SEAL)
         self.ldb2 = SamDB(url=host, credentials=creds2, lp=lp)
+        self.creds = creds2
 
     def test_unicodePwd_hash_set(self):
         """Performs a password hash set operation on 'unicodePwd' which should be prevented"""
@@ -238,6 +234,241 @@ unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')).
             self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
             self.assertTrue('0000052D' in msg)
 
+    def test_old_password_simple_bind(self):
+        '''Shows that we can log in with the immediate previous password, but not any earlier passwords.'''
+
+        user_dn_str = f'CN=testuser,CN=Users,{self.base_dn}'
+        user_dn = Dn(self.ldb, user_dn_str)
+
+        # Change the account password.
+        m = Message(user_dn)
+        m['0'] = MessageElement(self.creds.get_password(),
+                                FLAG_MOD_DELETE, 'userPassword')
+        m['1'] = MessageElement('Password#2',
+                                FLAG_MOD_ADD, 'userPassword')
+        self.ldb.modify(m)
+
+        # Show we can still log in using the previous password.
+        self.creds.set_bind_dn(user_dn_str)
+        try:
+            SamDB(url=host_ldaps,
+                  credentials=self.creds, lp=lp)
+        except LdbError:
+            self.fail('failed to login with previous password!')
+
+        # Change the account password a second time.
+        m = Message(user_dn)
+        m['0'] = MessageElement('Password#2',
+                                FLAG_MOD_DELETE, 'userPassword')
+        m['1'] = MessageElement('Password#3',
+                                FLAG_MOD_ADD, 'userPassword')
+        self.ldb.modify(m)
+
+        # Show we can no longer log in using the original password.
+        try:
+            SamDB(url=host_ldaps,
+                  credentials=self.creds, lp=lp)
+        except LdbError as err:
+            HRES_SEC_E_INVALID_TOKEN = '80090308'
+
+            num, estr = err.args
+            self.assertEqual(ERR_INVALID_CREDENTIALS, num)
+            self.assertIn(HRES_SEC_E_INVALID_TOKEN, estr)
+        else:
+            self.fail('should have failed to login with previous password!')
+
+    def test_old_password_attempt_reuse(self):
+        '''Shows that we cannot reuse the original password after changing the password twice.'''
+        res = self.ldb.search(self.ldb.domain_dn(), scope=SCOPE_BASE,
+                              attrs=['pwdHistoryLength'])
+
+        history_len = int(res[0].get('pwdHistoryLength', idx=0))
+        self.assertGreaterEqual(history_len, 3)
+
+        user_dn_str = f'CN=testuser,CN=Users,{self.base_dn}'
+        user_dn = Dn(self.ldb, user_dn_str)
+
+        first_pwd = self.creds.get_password()
+        previous_pwd = first_pwd
+
+        for new_pwd in ['Password#0', 'Password#1']:
+            # Change the account password.
+            m = Message(user_dn)
+            m['0'] = MessageElement(previous_pwd,
+                                    FLAG_MOD_DELETE, 'userPassword')
+            m['1'] = MessageElement(new_pwd,
+                                    FLAG_MOD_ADD, 'userPassword')
+            self.ldb.modify(m)
+
+            # Show that the original password is in the history by trying to
+            # set it as our new password.
+            m = Message(user_dn)
+            m['0'] = MessageElement(new_pwd,
+                                    FLAG_MOD_DELETE, 'userPassword')
+            m['1'] = MessageElement(first_pwd,
+                                    FLAG_MOD_ADD, 'userPassword')
+            try:
+                self.ldb.modify(m)
+            except LdbError as err:
+                num, estr = err.args
+                self.assertEqual(ERR_CONSTRAINT_VIOLATION, num)
+                self.assertIn(f'{werror.WERR_PASSWORD_RESTRICTION:08X}', estr)
+            else:
+                self.fail('should not have been able to reuse password!')
+
+            previous_pwd = new_pwd
+
+    def test_old_password_rename_simple_bind(self):
+        '''Shows that we can log in with the previous password after renaming the account.'''
+        user_dn_str = f'CN=testuser,CN=Users,{self.base_dn}'
+        user_dn = Dn(self.ldb, user_dn_str)
+
+        # Change the account password.
+        m = Message(user_dn)
+        m['0'] = MessageElement(self.creds.get_password(),
+                                FLAG_MOD_DELETE, 'userPassword')
+        m['1'] = MessageElement('Password#2',
+                                FLAG_MOD_ADD, 'userPassword')
+        self.ldb.modify(m)
+
+        # Show we can still log in using the previous password.
+        self.creds.set_bind_dn(user_dn_str)
+        try:
+            SamDB(url=host_ldaps,
+                  credentials=self.creds, lp=lp)
+        except LdbError:
+            self.fail('failed to login with previous password!')
+
+        # Rename the account, causing the salt to change.
+        m = Message(user_dn)
+        m['1'] = MessageElement('testuser_2',
+                                FLAG_MOD_REPLACE, 'sAMAccountName')
+        self.ldb.modify(m)
+
+        # Show that a simple bind can still be performed using the previous
+        # password.
+        self.creds.set_username('testuser_2')
+        try:
+            SamDB(url=host_ldaps,
+                  credentials=self.creds, lp=lp)
+        except LdbError:


-- 
Samba Shared Repository



More information about the samba-cvs mailing list