From 2f3ce37e4fb6d350cfcde4ec781d55f31c8af593 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Thu, 2 Mar 2017 16:00:01 +0100 Subject: [PATCH 1/5] dsdb/tests: remove duplicate test_smartcard_required3() from sam.py The function was 100% the same... Signed-off-by: Stefan Metzmacher Reviewed-by: Andrew Bartlett (cherry picked from commit 57e5bab22f4bb910549de3b72bd2bc78aa8b0b3b) --- source4/dsdb/tests/python/sam.py | 101 --------------------------------------- 1 file changed, 101 deletions(-) diff --git a/source4/dsdb/tests/python/sam.py b/source4/dsdb/tests/python/sam.py index 57151ca..f57454b 100755 --- a/source4/dsdb/tests/python/sam.py +++ b/source4/dsdb/tests/python/sam.py @@ -2642,107 +2642,6 @@ class SamTests(samba.tests.TestCase): delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn) - def test_smartcard_required3(self): - """Test the UF_SMARTCARD_REQUIRED behaviour""" - print "Testing UF_SMARTCARD_REQUIRED behaviour\n" - - delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn) - - ldb.add({ - "dn": "cn=ldaptestuser,cn=users," + self.base_dn, - "objectclass": "user", - "userAccountControl": str(UF_NORMAL_ACCOUNT|UF_SMARTCARD_REQUIRED|UF_ACCOUNTDISABLE), - }) - - res = ldb.search("cn=ldaptestuser,cn=users," + self.base_dn, - scope=SCOPE_BASE, - attrs=["sAMAccountType", "userAccountControl", - "pwdLastSet", "msDS-KeyVersionNumber", - "replPropertyMetaData"]) - self.assertTrue(len(res) == 1) - self.assertEqual(int(res[0]["sAMAccountType"][0]), - ATYPE_NORMAL_ACCOUNT) - self.assertEqual(int(res[0]["userAccountControl"][0]), - UF_NORMAL_ACCOUNT|UF_SMARTCARD_REQUIRED|UF_ACCOUNTDISABLE) - self.assertEqual(int(res[0]["pwdLastSet"][0]), 0) - self.assertEqual(int(res[0]["msDS-KeyVersionNumber"][0]), 1) - self.assertTrue(len(res[0]["replPropertyMetaData"]) == 1) - rpmd = ndr_unpack(drsblobs.replPropertyMetaDataBlob, - res[0]["replPropertyMetaData"][0]) - lastsetmd = self.find_repl_meta_data(rpmd, - drsuapi.DRSUAPI_ATTID_pwdLastSet) - self.assertIsNotNone(lastsetmd) - self.assertEqual(lastsetmd.version, 1) - nthashmd = self.find_repl_meta_data(rpmd, - drsuapi.DRSUAPI_ATTID_unicodePwd) - self.assertIsNotNone(nthashmd) - self.assertEqual(nthashmd.version, 1) - nthistmd = self.find_repl_meta_data(rpmd, - drsuapi.DRSUAPI_ATTID_ntPwdHistory) - self.assertIsNotNone(nthistmd) - self.assertEqual(nthistmd.version, 1) - lmhashmd = self.find_repl_meta_data(rpmd, - drsuapi.DRSUAPI_ATTID_dBCSPwd) - self.assertIsNotNone(lmhashmd) - self.assertEqual(lmhashmd.version, 1) - lmhistmd = self.find_repl_meta_data(rpmd, - drsuapi.DRSUAPI_ATTID_lmPwdHistory) - self.assertIsNotNone(lmhistmd) - self.assertEqual(lmhistmd.version, 1) - spcbmd = self.find_repl_meta_data(rpmd, - drsuapi.DRSUAPI_ATTID_supplementalCredentials) - self.assertIsNotNone(spcbmd) - self.assertEqual(spcbmd.version, 1) - - m = Message() - m.dn = Dn(ldb, "cn=ldaptestuser,cn=users," + self.base_dn) - m["userAccountControl"] = MessageElement( - str(UF_NORMAL_ACCOUNT|UF_SMARTCARD_REQUIRED), - FLAG_MOD_REPLACE, "userAccountControl") - ldb.modify(m) - - res = ldb.search("cn=ldaptestuser,cn=users," + self.base_dn, - scope=SCOPE_BASE, - attrs=["sAMAccountType", "userAccountControl", - "pwdLastSet", "msDS-KeyVersionNumber", - "replPropertyMetaData"]) - self.assertTrue(len(res) == 1) - self.assertEqual(int(res[0]["sAMAccountType"][0]), - ATYPE_NORMAL_ACCOUNT) - self.assertEqual(int(res[0]["userAccountControl"][0]), - UF_NORMAL_ACCOUNT|UF_SMARTCARD_REQUIRED) - self.assertEqual(int(res[0]["pwdLastSet"][0]), 0) - self.assertEqual(int(res[0]["msDS-KeyVersionNumber"][0]), 1) - self.assertTrue(len(res[0]["replPropertyMetaData"]) == 1) - rpmd = ndr_unpack(drsblobs.replPropertyMetaDataBlob, - res[0]["replPropertyMetaData"][0]) - lastsetmd = self.find_repl_meta_data(rpmd, - drsuapi.DRSUAPI_ATTID_pwdLastSet) - self.assertIsNotNone(lastsetmd) - self.assertEqual(lastsetmd.version, 1) - nthashmd = self.find_repl_meta_data(rpmd, - drsuapi.DRSUAPI_ATTID_unicodePwd) - self.assertIsNotNone(nthashmd) - self.assertEqual(nthashmd.version, 1) - nthistmd = self.find_repl_meta_data(rpmd, - drsuapi.DRSUAPI_ATTID_ntPwdHistory) - self.assertIsNotNone(nthistmd) - self.assertEqual(nthistmd.version, 1) - lmhashmd = self.find_repl_meta_data(rpmd, - drsuapi.DRSUAPI_ATTID_dBCSPwd) - self.assertIsNotNone(lmhashmd) - self.assertEqual(lmhashmd.version, 1) - lmhistmd = self.find_repl_meta_data(rpmd, - drsuapi.DRSUAPI_ATTID_lmPwdHistory) - self.assertIsNotNone(lmhistmd) - self.assertEqual(lmhistmd.version, 1) - spcbmd = self.find_repl_meta_data(rpmd, - drsuapi.DRSUAPI_ATTID_supplementalCredentials) - self.assertIsNotNone(spcbmd) - self.assertEqual(spcbmd.version, 1) - - delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn) - def test_isCriticalSystemObject(self): """Test the isCriticalSystemObject behaviour""" print "Testing isCriticalSystemObject behaviour\n" -- 1.9.1 From b6aa5fdd8362133361c11e26ff74694548415d1e Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Thu, 2 Mar 2017 17:19:21 +0100 Subject: [PATCH 2/5] ldb-samba: remember the error string of a failing bind in ildb_connect() BUG: https://bugzilla.samba.org/show_bug.cgi?id=9048 Signed-off-by: Stefan Metzmacher Reviewed-by: Andrew Bartlett (cherry picked from commit 4738754e7d7216f6acf790827459bb5da6b0a110) --- lib/ldb-samba/ldb_ildap.c | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/ldb-samba/ldb_ildap.c b/lib/ldb-samba/ldb_ildap.c index 65f11db..541971f 100644 --- a/lib/ldb-samba/ldb_ildap.c +++ b/lib/ldb-samba/ldb_ildap.c @@ -863,6 +863,7 @@ static int ildb_connect(struct ldb_context *ldb, const char *url, return LDB_SUCCESS; failed: + ldb_set_errstring(ldb, ldap_errstr(ildb->ldap, module, status)); talloc_free(module); if (NT_STATUS_IS_LDAP(status)) { return NT_STATUS_LDAP_CODE(status); -- 1.9.1 From 66992639e70e411f855055d561376e207182238e Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Fri, 24 Feb 2017 18:30:56 +0100 Subject: [PATCH 3/5] s4:ldap_server: match windows in the error messages of failing LDAP Bind requests This is important for some applications to detect the NT_STATUS_PASSWORD_MUST_CHANGE condition correctly. BUG: https://bugzilla.samba.org/show_bug.cgi?id=9048 Signed-off-by: Stefan Metzmacher Reviewed-by: Andrew Bartlett (cherry picked from commit 81ccdad9d045a7a6d6a569d1685bb0bf4e64d12a) --- source4/ldap_server/ldap_bind.c | 37 +++++++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/source4/ldap_server/ldap_bind.c b/source4/ldap_server/ldap_bind.c index c7715de..0753fbe 100644 --- a/source4/ldap_server/ldap_bind.c +++ b/source4/ldap_server/ldap_bind.c @@ -29,6 +29,37 @@ #include "param/param.h" #include "../lib/util/tevent_ntstatus.h" +static char *ldapsrv_bind_error_msg(TALLOC_CTX *mem_ctx, + HRESULT hresult, + uint32_t DSID, + NTSTATUS status) +{ + WERROR werr; + char *msg = NULL; + + status = nt_status_squash(status); + werr = ntstatus_to_werror(status); + + /* + * There are 4 lower case hex digits following 'v' at the end, + * but different Windows Versions return different values: + * + * Windows 2008R2 uses 'v1db1' + * Windows 2012R2 uses 'v2580' + * + * We just match Windows 2008R2 as that's what was referenced + * in https://bugzilla.samba.org/show_bug.cgi?id=9048 + */ + msg = talloc_asprintf(mem_ctx, "%08X: LdapErr: DSID-%08X, comment: " + "AcceptSecurityContext error, data %x, v1db1", + (unsigned)HRES_ERROR_V(hresult), + (unsigned)DSID, + (unsigned)W_ERROR_V(werr)); + + return msg; +} + + static NTSTATUS ldapsrv_BindSimple(struct ldapsrv_call *call) { struct ldap_BindRequest *req = &call->request->r.BindRequest; @@ -95,7 +126,8 @@ static NTSTATUS ldapsrv_BindSimple(struct ldapsrv_call *call) status = nt_status_squash(status); result = LDAP_INVALID_CREDENTIALS; - errstr = talloc_asprintf(reply, "Simple Bind Failed: %s", nt_errstr(status)); + errstr = ldapsrv_bind_error_msg(reply, HRES_SEC_E_INVALID_TOKEN, + 0x0C0903A9, status); } do_reply: @@ -346,7 +378,8 @@ static NTSTATUS ldapsrv_BindSASL(struct ldapsrv_call *call) status = nt_status_squash(status); if (result == 0) { result = LDAP_INVALID_CREDENTIALS; - errstr = talloc_asprintf(reply, "SASL:[%s]: %s", req->creds.SASL.mechanism, nt_errstr(status)); + errstr = ldapsrv_bind_error_msg(reply, HRES_SEC_E_LOGON_DENIED, + 0x0C0904DC, status); } talloc_unlink(conn, conn->gensec); conn->gensec = NULL; -- 1.9.1 From 1fafbc830c20b9ccd33cc5913ef03a0da0ff5c26 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Thu, 2 Mar 2017 16:41:20 +0100 Subject: [PATCH 4/5] dsdb/tests: add test_ldap_bind_must_change_pwd() This tests the error messages for failing LDAP Bind responses. BUG: https://bugzilla.samba.org/show_bug.cgi?id=9048 Signed-off-by: Stefan Metzmacher Reviewed-by: Andrew Bartlett (cherry picked from commit fdacca53bdc0d1021e4b32fee1f8eb7ed4345ee5) --- source4/dsdb/tests/python/sam.py | 177 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 177 insertions(+) diff --git a/source4/dsdb/tests/python/sam.py b/source4/dsdb/tests/python/sam.py index f57454b..10775d3 100755 --- a/source4/dsdb/tests/python/sam.py +++ b/source4/dsdb/tests/python/sam.py @@ -13,6 +13,7 @@ from samba.tests.subunitrun import SubunitOptions, TestProgram import samba.getopt as options +from samba.credentials import Credentials, DONT_USE_KERBEROS from samba.auth import system_session from ldb import SCOPE_BASE, LdbError from ldb import ERR_NO_SUCH_OBJECT, ERR_ATTRIBUTE_OR_VALUE_EXISTS @@ -22,6 +23,8 @@ from ldb import ERR_OBJECT_CLASS_VIOLATION from ldb import ERR_CONSTRAINT_VIOLATION from ldb import ERR_UNDEFINED_ATTRIBUTE_TYPE from ldb import ERR_INSUFFICIENT_ACCESS_RIGHTS +from ldb import ERR_INVALID_CREDENTIALS +from ldb import ERR_STRONG_AUTH_REQUIRED from ldb import Message, MessageElement, Dn from ldb import FLAG_MOD_ADD, FLAG_MOD_REPLACE, FLAG_MOD_DELETE from samba.samdb import SamDB @@ -47,6 +50,7 @@ from samba.dcerpc import drsuapi from samba.dcerpc import security from samba.tests import delete_force from samba import gensec +from samba import werror parser = optparse.OptionParser("sam.py [options] ") sambaopts = options.SambaOptions(parser) @@ -1626,6 +1630,179 @@ class SamTests(samba.tests.TestCase): delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn) + def test_ldap_bind_must_change_pwd(self): + """Test the error messages for failing LDAP binds""" + print "Test the error messages for failing LDAP binds\n" + + delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn) + + def format_error_msg(hresult_v, dsid_v, werror_v): + # + # There are 4 lower case hex digits following 'v' at the end, + # but different Windows Versions return different values: + # + # Windows 2008R2 uses 'v1db1' + # Windows 2012R2 uses 'v2580' + # + return "%08X: LdapErr: DSID-%08X, comment: AcceptSecurityContext error, data %x, v" % ( + hresult_v, dsid_v, werror_v) + + HRES_SEC_E_LOGON_DENIED = 0x8009030C + HRES_SEC_E_INVALID_TOKEN = 0x80090308 + + sasl_bind_dsid = 0x0C0904DC + simple_bind_dsid = 0x0C0903A9 + + error_msg_sasl_wrong_pw = format_error_msg( + HRES_SEC_E_LOGON_DENIED, + sasl_bind_dsid, + werror.WERR_LOGON_FAILURE) + error_msg_sasl_must_change = format_error_msg( + HRES_SEC_E_LOGON_DENIED, + sasl_bind_dsid, + werror.WERR_PASSWORD_MUST_CHANGE) + error_msg_simple_wrong_pw = format_error_msg( + HRES_SEC_E_INVALID_TOKEN, + simple_bind_dsid, + werror.WERR_LOGON_FAILURE) + error_msg_simple_must_change = format_error_msg( + HRES_SEC_E_INVALID_TOKEN, + simple_bind_dsid, + werror.WERR_PASSWORD_MUST_CHANGE) + + username = "ldaptestuser" + password = "thatsAcomplPASS2" + utf16pw = unicode('"' + password.encode('utf-8') + '"', 'utf-8').encode('utf-16-le') + + ldb.add({ + "dn": "cn=ldaptestuser,cn=users," + self.base_dn, + "objectclass": "user", + "sAMAccountName": username, + "userAccountControl": str(UF_NORMAL_ACCOUNT), + "unicodePwd": utf16pw, + }) + + res1 = ldb.search("cn=ldaptestuser,cn=users," + self.base_dn, + scope=SCOPE_BASE, + attrs=["sAMAccountName", "sAMAccountType", "userAccountControl", "pwdLastSet"]) + self.assertTrue(len(res1) == 1) + self.assertEqual(res1[0]["sAMAccountName"][0], username) + self.assertEqual(int(res1[0]["sAMAccountType"][0]), ATYPE_NORMAL_ACCOUNT) + self.assertEqual(int(res1[0]["userAccountControl"][0]), UF_NORMAL_ACCOUNT) + self.assertNotEqual(int(res1[0]["pwdLastSet"][0]), 0) + + # Open a second LDB connection with the user credentials. Use the + # command line credentials for informations like the domain, the realm + # and the workstation. + sasl_creds = Credentials() + sasl_creds.set_username(username) + sasl_creds.set_password(password) + sasl_creds.set_domain(creds.get_domain()) + sasl_creds.set_workstation(creds.get_workstation()) + sasl_creds.set_gensec_features(creds.get_gensec_features() | gensec.FEATURE_SEAL) + sasl_creds.set_kerberos_state(DONT_USE_KERBEROS) + + sasl_wrong_creds = Credentials() + sasl_wrong_creds.set_username(username) + sasl_wrong_creds.set_password("wrong") + sasl_wrong_creds.set_domain(creds.get_domain()) + sasl_wrong_creds.set_workstation(creds.get_workstation()) + sasl_wrong_creds.set_gensec_features(creds.get_gensec_features() | gensec.FEATURE_SEAL) + sasl_wrong_creds.set_kerberos_state(DONT_USE_KERBEROS) + + simple_creds = Credentials() + simple_creds.set_bind_dn("cn=ldaptestuser,cn=users," + self.base_dn) + simple_creds.set_username(username) + simple_creds.set_password(password) + simple_creds.set_domain(creds.get_domain()) + simple_creds.set_workstation(creds.get_workstation()) + simple_creds.set_gensec_features(creds.get_gensec_features() | gensec.FEATURE_SEAL) + simple_creds.set_kerberos_state(DONT_USE_KERBEROS) + + simple_wrong_creds = Credentials() + simple_wrong_creds.set_bind_dn("cn=ldaptestuser,cn=users," + self.base_dn) + simple_wrong_creds.set_username(username) + simple_wrong_creds.set_password("wrong") + simple_wrong_creds.set_domain(creds.get_domain()) + simple_wrong_creds.set_workstation(creds.get_workstation()) + simple_wrong_creds.set_gensec_features(creds.get_gensec_features() | gensec.FEATURE_SEAL) + simple_wrong_creds.set_kerberos_state(DONT_USE_KERBEROS) + + sasl_ldb = SamDB(url=host, credentials=sasl_creds, lp=lp) + self.assertIsNotNone(sasl_ldb) + sasl_ldb = None + + requires_strong_auth = False + try: + simple_ldb = SamDB(url=host, credentials=simple_creds, lp=lp) + self.assertIsNotNone(simple_ldb) + simple_ldb = None + except LdbError, (num, msg): + if num != ERR_STRONG_AUTH_REQUIRED: + raise + requires_strong_auth = True + + def assertLDAPErrorMsg(msg, expected_msg): + self.assertTrue(expected_msg in msg, + "msg[%s] does not contain expected[%s]" % ( + msg, expected_msg)) + + try: + ldb_fail = SamDB(url=host, credentials=sasl_wrong_creds, lp=lp) + self.fail() + except LdbError, (num, msg): + self.assertEquals(num, ERR_INVALID_CREDENTIALS) + self.assertTrue(error_msg_sasl_wrong_pw in msg) + + if not requires_strong_auth: + try: + ldb_fail = SamDB(url=host, credentials=simple_wrong_creds, lp=lp) + self.fail() + except LdbError, (num, msg): + self.assertEquals(num, ERR_INVALID_CREDENTIALS) + assertLDAPErrorMsg(msg, error_msg_simple_wrong_pw) + + m = Message() + m.dn = Dn(ldb, "cn=ldaptestuser,cn=users," + self.base_dn) + m["pls1"] = MessageElement(str(0), + FLAG_MOD_REPLACE, + "pwdLastSet") + ldb.modify(m) + + res1 = ldb.search("cn=ldaptestuser,cn=users," + self.base_dn, + scope=SCOPE_BASE, attrs=["pwdLastSet"]) + self.assertEqual(int(res1[0]["pwdLastSet"][0]), 0) + + try: + ldb_fail = SamDB(url=host, credentials=sasl_wrong_creds, lp=lp) + self.fail() + except LdbError, (num, msg): + self.assertEquals(num, ERR_INVALID_CREDENTIALS) + assertLDAPErrorMsg(msg, error_msg_sasl_wrong_pw) + + try: + ldb_fail = SamDB(url=host, credentials=sasl_creds, lp=lp) + self.fail() + except LdbError, (num, msg): + self.assertEquals(num, ERR_INVALID_CREDENTIALS) + assertLDAPErrorMsg(msg, error_msg_sasl_must_change) + + if not requires_strong_auth: + try: + ldb_fail = SamDB(url=host, credentials=simple_wrong_creds, lp=lp) + self.fail() + except LdbError, (num, msg): + self.assertEquals(num, ERR_INVALID_CREDENTIALS) + assertLDAPErrorMsg(msg, error_msg_simple_wrong_pw) + + try: + ldb_fail = SamDB(url=host, credentials=simple_creds, lp=lp) + self.fail() + except LdbError, (num, msg): + self.assertEquals(num, ERR_INVALID_CREDENTIALS) + assertLDAPErrorMsg(msg, error_msg_simple_must_change) + + delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn) def test_userAccountControl(self): """Test the userAccountControl behaviour""" -- 1.9.1 From 6f4caf290d79b724d58dd965171e41ec07bb37a1 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Thu, 2 Mar 2017 17:34:22 +0100 Subject: [PATCH 5/5] s4:selftest: run samba4.sam.python also against fl2008r2dc fl2008r2dc uses "ldap server require strong auth = no", which is required to test the simple bind error messages. BUG: https://bugzilla.samba.org/show_bug.cgi?id=9048 Signed-off-by: Stefan Metzmacher Reviewed-by: Andrew Bartlett Autobuild-User(master): Andrew Bartlett Autobuild-Date(master): Fri Mar 3 12:57:06 CET 2017 on sn-devel-144 (cherry picked from commit 845bf7fbfe51ffaf0e07b2cfbd035f54168b749f) --- source4/selftest/tests.py | 1 + 1 file changed, 1 insertion(+) diff --git a/source4/selftest/tests.py b/source4/selftest/tests.py index cc11bb6..0d00153 100755 --- a/source4/selftest/tests.py +++ b/source4/selftest/tests.py @@ -579,6 +579,7 @@ planoldpythontestsuite("ad_dc", "samba.tests.dcerpc.dnsserver", extra_args=['-U" planoldpythontestsuite("ad_dc", "samba.tests.dcerpc.raw_protocol", extra_args=['-U"$USERNAME%$PASSWORD"']) plantestsuite_loadlist("samba4.ldap.python(ad_dc_ntvfs)", "ad_dc_ntvfs", [python, os.path.join(samba4srcdir, "dsdb/tests/python/ldap.py"), '$SERVER', '-U"$USERNAME%$PASSWORD"', '--workgroup=$DOMAIN', '$LOADLIST', '$LISTOPT']) plantestsuite_loadlist("samba4.tokengroups.python(ad_dc_ntvfs)", "ad_dc_ntvfs:local", [python, os.path.join(samba4srcdir, "dsdb/tests/python/token_group.py"), '$SERVER', '-U"$USERNAME%$PASSWORD"', '--workgroup=$DOMAIN', '$LOADLIST', '$LISTOPT']) +plantestsuite("samba4.sam.python(fl2008r2dc)", "fl2008r2dc", [python, os.path.join(samba4srcdir, "dsdb/tests/python/sam.py"), '$SERVER', '-U"$USERNAME%$PASSWORD"', '--workgroup=$DOMAIN']) plantestsuite("samba4.sam.python(ad_dc_ntvfs)", "ad_dc_ntvfs", [python, os.path.join(samba4srcdir, "dsdb/tests/python/sam.py"), '$SERVER', '-U"$USERNAME%$PASSWORD"', '--workgroup=$DOMAIN']) plantestsuite("samba4.user_account_control.python(ad_dc_ntvfs)", "ad_dc_ntvfs", [python, os.path.join(samba4srcdir, "dsdb/tests/python/user_account_control.py"), '$SERVER', '-U"$USERNAME%$PASSWORD"', '--workgroup=$DOMAIN']) planoldpythontestsuite("ad_dc_ntvfs", "dsdb_schema_info", -- 1.9.1