From 384ea93cd8848bc247eb94d20e8275f945c20a4c Mon Sep 17 00:00:00 2001 From: Garming Sam Date: Tue, 4 Apr 2017 13:11:16 +1200 Subject: [PATCH 01/24] whitespace: Remove some whitespace Signed-off-by: Garming Sam --- source4/dsdb/samdb/ldb_modules/repl_meta_data.c | 1 + source4/dsdb/samdb/ldb_modules/samldb.c | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/source4/dsdb/samdb/ldb_modules/repl_meta_data.c b/source4/dsdb/samdb/ldb_modules/repl_meta_data.c index d9b414d..a922f9b 100644 --- a/source4/dsdb/samdb/ldb_modules/repl_meta_data.c +++ b/source4/dsdb/samdb/ldb_modules/repl_meta_data.c @@ -3238,6 +3238,7 @@ static int replmd_modify(struct ldb_module *module, struct ldb_request *req) ret = ldb_module_send_referral(req, referral); talloc_free(ac); return ret; + } if (ret != LDB_SUCCESS) { diff --git a/source4/dsdb/samdb/ldb_modules/samldb.c b/source4/dsdb/samdb/ldb_modules/samldb.c index 2c47ff1..48edc28 100644 --- a/source4/dsdb/samdb/ldb_modules/samldb.c +++ b/source4/dsdb/samdb/ldb_modules/samldb.c @@ -2850,12 +2850,12 @@ static int samldb_service_principal_names_change(struct samldb_ctx *ac) if (ret != LDB_SUCCESS) { return ret; } - dns_hostname = talloc_strdup(ac, + dns_hostname = talloc_strdup(ac, ldb_msg_find_attr_as_string(msg, "dNSHostName", NULL)); if (dns_hostname == NULL) { return ldb_module_oom(ac->module); } - + talloc_free(msg); ret = dsdb_module_search_dn(ac->module, ac, &res, ac->msg->dn, -- 1.9.1 From b1bd34f4b21435e6a06bb427a8c317fa06b3d7c1 Mon Sep 17 00:00:00 2001 From: Garming Sam Date: Tue, 4 Apr 2017 12:21:34 +1200 Subject: [PATCH 02/24] winbindd: Make some debugging clearer Signed-off-by: Garming Sam --- source3/winbindd/winbindd_pam.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source3/winbindd/winbindd_pam.c b/source3/winbindd/winbindd_pam.c index a466015..146e9ca 100644 --- a/source3/winbindd/winbindd_pam.c +++ b/source3/winbindd/winbindd_pam.c @@ -1420,7 +1420,7 @@ static NTSTATUS winbind_samlogon_retry_loop(struct winbindd_domain *domain, */ DEBUG(3, ("This is the third problem for this " "particular call, adding DC to the " - "negative cache list\n")); + "negative cache list: %s %s\n", domain->name, domain->dcname)); add_failed_connection_entry(domain->name, domain->dcname, result); @@ -1530,7 +1530,7 @@ static NTSTATUS winbind_samlogon_retry_loop(struct winbindd_domain *domain, if (NT_STATUS_EQUAL(result, NT_STATUS_IO_TIMEOUT)) { DEBUG(3,("winbind_samlogon_retry_loop: sam_network_logon(ex) " - "returned NT_STATUS_IO_TIMEOUT after the retry." + "returned NT_STATUS_IO_TIMEOUT after the retry. " "Killing connections to domain %s\n", domainname)); invalidate_cm_connection(domain); -- 1.9.1 From 4d916bd55bbf4928357d23d2b30d763fa9565381 Mon Sep 17 00:00:00 2001 From: Garming Sam Date: Wed, 29 Mar 2017 10:32:39 +1300 Subject: [PATCH 03/24] samba_dnsupdate: Remove extra argument from debug Signed-off-by: Garming Sam --- source4/scripting/bin/samba_dnsupdate | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source4/scripting/bin/samba_dnsupdate b/source4/scripting/bin/samba_dnsupdate index 0687703..f9e835b 100755 --- a/source4/scripting/bin/samba_dnsupdate +++ b/source4/scripting/bin/samba_dnsupdate @@ -560,7 +560,7 @@ def call_samba_tool(d, op="add", zone=None): sys.exit(1) error_count = error_count + 1 if opts.verbose: - print("Failed 'samba-tool dns' based update: %s : %s" % (str(d), estr)) + print("Failed 'samba-tool dns' based update: %s" % (str(d))) except Exception, estr: if opts.fail_immediately: sys.exit(1) -- 1.9.1 From 3ed9f52dc201153e687e44500009d632329c4c80 Mon Sep 17 00:00:00 2001 From: Garming Sam Date: Tue, 4 Apr 2017 12:18:42 +1200 Subject: [PATCH 04/24] drsuapi.idl: Expose GetNCChanges req8 like req10 Signed-off-by: Garming Sam --- librpc/idl/drsuapi.idl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/librpc/idl/drsuapi.idl b/librpc/idl/drsuapi.idl index d08054f..2eb1d65 100644 --- a/librpc/idl/drsuapi.idl +++ b/librpc/idl/drsuapi.idl @@ -561,7 +561,7 @@ interface drsuapi [size_is(num_attids)] drsuapi_DsAttributeId attids[]; } drsuapi_DsPartialAttributeSet; - typedef struct { + typedef [public] struct { GUID destination_dsa_guid; GUID source_dsa_invocation_id; /* the 'invocationId' field of the CN=NTDS Settings object */ [ref] drsuapi_DsReplicaObjectIdentifier *naming_context; -- 1.9.1 From dcd7a4ab8a45238bb32c6dacee307046f34855b4 Mon Sep 17 00:00:00 2001 From: Garming Sam Date: Tue, 4 Apr 2017 13:13:16 +1200 Subject: [PATCH 05/24] replmd: Send RODC referrals preferably to the PDC The Windows protocol test suites check that a particular DC is used when sending referrals. Signed-off-by: Garming Sam BUG: https://bugzilla.samba.org/show_bug.cgi?id=12008 --- source4/dsdb/samdb/ldb_modules/repl_meta_data.c | 67 ++++++++++++++++--------- 1 file changed, 44 insertions(+), 23 deletions(-) diff --git a/source4/dsdb/samdb/ldb_modules/repl_meta_data.c b/source4/dsdb/samdb/ldb_modules/repl_meta_data.c index a922f9b..44ec1d4 100644 --- a/source4/dsdb/samdb/ldb_modules/repl_meta_data.c +++ b/source4/dsdb/samdb/ldb_modules/repl_meta_data.c @@ -41,6 +41,7 @@ #include "dsdb/common/proto.h" #include "dsdb/common/util.h" #include "../libds/common/flags.h" +#include "librpc/gen_ndr/irpc.h" #include "librpc/gen_ndr/ndr_misc.h" #include "librpc/gen_ndr/ndr_drsuapi.h" #include "librpc/gen_ndr/ndr_drsblobs.h" @@ -3153,6 +3154,47 @@ static int replmd_modify_handle_linked_attribs(struct ldb_module *module, } +static int send_rodc_referral(struct ldb_request *req, + struct ldb_context *ldb, + struct ldb_dn *dn) +{ + char *referral = NULL; + struct loadparm_context *lp_ctx = NULL; + struct ldb_dn *fsmo_role_dn = NULL; + struct ldb_dn *role_owner_dn = NULL; + const char *domain = NULL; + WERROR werr; + + lp_ctx = talloc_get_type(ldb_get_opaque(ldb, "loadparm"), + struct loadparm_context); + + werr = dsdb_get_fsmo_role_info(req, ldb, DREPL_PDC_MASTER, + &fsmo_role_dn, &role_owner_dn); + + if (W_ERROR_IS_OK(werr)) { + struct ldb_dn *server_dn = ldb_dn_copy(req, role_owner_dn); + if (server_dn != NULL) { + ldb_dn_remove_child_components(server_dn, 1); + domain = samdb_dn_to_dnshostname(ldb, req, + server_dn); + } + } + + if (domain == NULL) { + domain = lpcfg_dnsdomain(lp_ctx); + } + + referral = talloc_asprintf(req, "ldap://%s/%s", + domain, + ldb_dn_get_linearized(dn)); + if (referral == NULL) { + ldb_oom(ldb); + return LDB_ERR_OPERATIONS_ERROR; + } + + return ldb_module_send_referral(req, referral); +} + static int replmd_modify(struct ldb_module *module, struct ldb_request *req) { @@ -3225,17 +3267,7 @@ static int replmd_modify(struct ldb_module *module, struct ldb_request *req) msg, &ac->seq_num, t, is_schema_nc, &is_urgent, &rodc); if (rodc && (ret == LDB_ERR_REFERRAL)) { - struct loadparm_context *lp_ctx; - char *referral; - - lp_ctx = talloc_get_type(ldb_get_opaque(ldb, "loadparm"), - struct loadparm_context); - - referral = talloc_asprintf(req, - "ldap://%s/%s", - lpcfg_dnsdomain(lp_ctx), - ldb_dn_get_linearized(msg->dn)); - ret = ldb_module_send_referral(req, referral); + ret = send_rodc_referral(req, ldb, msg->dn); talloc_free(ac); return ret; @@ -3498,18 +3530,7 @@ static int replmd_rename_callback(struct ldb_request *req, struct ldb_reply *are msg, &ac->seq_num, t, is_schema_nc, &is_urgent, &rodc); if (rodc && (ret == LDB_ERR_REFERRAL)) { - struct ldb_dn *olddn = ac->req->op.rename.olddn; - struct loadparm_context *lp_ctx; - char *referral; - - lp_ctx = talloc_get_type(ldb_get_opaque(ldb, "loadparm"), - struct loadparm_context); - - referral = talloc_asprintf(req, - "ldap://%s/%s", - lpcfg_dnsdomain(lp_ctx), - ldb_dn_get_linearized(olddn)); - ret = ldb_module_send_referral(req, referral); + ret = send_rodc_referral(req, ldb, ac->req->op.rename.olddn); talloc_free(ares); return ldb_module_done(req, NULL, NULL, ret); } -- 1.9.1 From 96e4742dfab7a31c08c88cb5d397b481f683dcc3 Mon Sep 17 00:00:00 2001 From: Garming Sam Date: Tue, 14 Mar 2017 10:36:13 +1300 Subject: [PATCH 06/24] selftest: Add ldap rodc python test Signed-off-by: Garming Sam Pair-programmed-with: Douglas Bagnall BUG: https://bugzilla.samba.org/show_bug.cgi?id=12008 --- selftest/knownfail | 3 + source4/dsdb/tests/python/rodc.py | 208 ++++++++++++++++++++++++++++++++++++++ source4/selftest/tests.py | 6 ++ 3 files changed, 217 insertions(+) create mode 100755 source4/dsdb/tests/python/rodc.py diff --git a/selftest/knownfail b/selftest/knownfail index 07b4cdb..c91d227 100644 --- a/selftest/knownfail +++ b/selftest/knownfail @@ -317,3 +317,6 @@ # rap password tests don't function in the ad_dc_ntvfs:local environment # ^samba.tests.auth_log_pass_change.samba.tests.auth_log_pass_change.AuthLogPassChangeTests.test_rap_change_password\(ad_dc_ntvfs:local\) +^samba4.ldap.rodc.python\(rodc\).__main__.RodcTests.test_add.* +^samba4.ldap.rodc.python\(rodc\).__main__.RodcTests.test_delete.* +^samba4.ldap.rodc.python\(rodc\).__main__.RodcTests.test_modify_nonreplicated.* diff --git a/source4/dsdb/tests/python/rodc.py b/source4/dsdb/tests/python/rodc.py new file mode 100755 index 0000000..8fd9354 --- /dev/null +++ b/source4/dsdb/tests/python/rodc.py @@ -0,0 +1,208 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +import optparse +import sys +import os +import base64 +import random +import re +import uuid + +sys.path.insert(0, "bin/python") +import samba +from samba.tests.subunitrun import SubunitOptions, TestProgram + +import samba.getopt as options + +from samba.auth import system_session +import ldb +from samba.samdb import SamDB +from samba.ndr import ndr_pack, ndr_unpack +from samba.dcerpc import drsblobs + +import time + + +class RodcTestException(Exception): + pass + + +class RodcTests(samba.tests.TestCase): + + def setUp(self): + super(RodcTests, self).setUp() + self.samdb = SamDB(HOST, credentials=CREDS, + session_info=system_session(LP), lp=LP) + + self.base_dn = self.samdb.domain_dn() + + root = self.samdb.search(base='', scope=ldb.SCOPE_BASE, + attrs=['dsServiceName']) + self.service = root[0]['dsServiceName'][0] + self.tag = uuid.uuid4().hex + + def test_add_replicated_objects(self): + for o in ( + { + 'dn': "ou=%s1,%s" % (self.tag, self.base_dn), + "objectclass": "organizationalUnit" + }, + { + 'dn': "cn=%s2,%s" % (self.tag, self.base_dn), + "objectclass": "user" + }, + { + 'dn': "cn=%s3,%s" % (self.tag, self.base_dn), + "objectclass": "group" + }, + { + 'dn': "cn=%s4,%s" % (self.tag, self.service), + "objectclass": "NTDSConnection", + 'enabledConnection': 'TRUE', + 'fromServer': self.base_dn, + 'options': '0' + }, + ): + try: + self.samdb.add(o) + self.fail("Failed to fail to add %s" % o['dn']) + except ldb.LdbError as (ecode, emsg): + if ecode != ldb.ERR_REFERRAL: + print emsg + self.fail("Adding %s: ldb error: %s %s, wanted referral" % + (o['dn'], ecode, emsg)) + else: + m = re.search(r'(ldap://[^>]+)>', emsg) + if m is None: + self.fail("referral seems not to refer to anything") + address = m.group(1) + + try: + tmpdb = SamDB(address, credentials=CREDS, + session_info=system_session(LP), lp=LP) + tmpdb.add(o) + except ldb.LdbError, e: + self.fail("couldn't modify referred location %s" % + address) + + def test_modify_replicated_attributes(self): + # some timestamp ones + dn = 'CN=Guest,CN=Users,' + self.base_dn + value = 'hallooo' + for attr in ['carLicense', 'middleName']: + m = ldb.Message() + m.dn = ldb.Dn(self.samdb, dn) + m[attr] = ldb.MessageElement(value, + ldb.FLAG_MOD_REPLACE, + attr) + try: + self.samdb.modify(m) + self.fail("Failed to fail to modify %s %s" % (dn, attr)) + except ldb.LdbError as (ecode, emsg): + if ecode != ldb.ERR_REFERRAL: + self.fail("Failed to REFER when trying to modify %s %s" % + (dn, attr)) + + def test_modify_nonreplicated_attributes(self): + # some timestamp ones + dn = 'CN=Guest,CN=Users,' + self.base_dn + value = '123456789' + for attr in ['badPwdCount', 'lastLogon', 'lastLogoff']: + m = ldb.Message() + m.dn = ldb.Dn(self.samdb, dn) + m[attr] = ldb.MessageElement(value, + ldb.FLAG_MOD_REPLACE, + attr) + # Windows refers these ones even though they are non-replicated + try: + self.samdb.modify(m) + self.fail("Failed to fail to modify %s %s" % (dn, attr)) + except ldb.LdbError as (ecode, emsg): + if ecode != ldb.ERR_REFERRAL: + self.fail("Failed to REFER when trying to modify %s %s" % + (dn, attr)) + + def test_modify_nonreplicated_reps_attributes(self): + # some timestamp ones + dn = self.base_dn + + m = ldb.Message() + m.dn = ldb.Dn(self.samdb, dn) + attr = 'repsFrom' + + res = self.samdb.search(dn, scope=ldb.SCOPE_BASE, + attrs=['repsFrom']) + rep = ndr_unpack(drsblobs.repsFromToBlob, res[0]['repsFrom'][0], + allow_remaining=True) + rep.ctr.result_last_attempt = -1 + value = ndr_pack(rep) + + m[attr] = ldb.MessageElement(value, + ldb.FLAG_MOD_REPLACE, + attr) + try: + self.samdb.modify(m) + self.fail("Failed to fail to modify %s %s" % (dn, attr)) + except ldb.LdbError as (ecode, emsg): + if ecode != ldb.ERR_REFERRAL: + self.fail("Failed to REFER when trying to modify %s %s" % + (dn, attr)) + + def test_delete_special_objects(self): + dn = 'CN=Guest,CN=Users,' + self.base_dn + try: + self.samdb.delete(dn) + self.fail("Failed to fail to delete %s" % (dn)) + except ldb.LdbError as (ecode, emsg): + if ecode != ldb.ERR_REFERRAL: + print ecode, emsg + self.fail("Failed to REFER when trying to delete %s" % dn) + + def test_no_delete_nonexistent_objects(self): + dn = 'CN=does-not-exist-%s,CN=Users,%s' % (self.tag, self.base_dn) + try: + self.samdb.delete(dn) + self.fail("Failed to fail to delete %s" % (dn)) + except ldb.LdbError as (ecode, emsg): + if ecode != ldb.ERR_NO_SUCH_OBJECT: + print ecode, emsg + self.fail("Failed to NO_SUCH_OBJECT when trying to delete " + "%s (which does not exist)" % dn) + + + +def main(): + global HOST, CREDS, LP + parser = optparse.OptionParser("rodc.py [options] ") + + sambaopts = options.SambaOptions(parser) + versionopts = options.VersionOptions(parser) + credopts = options.CredentialsOptions(parser) + subunitopts = SubunitOptions(parser) + + parser.add_option_group(sambaopts) + parser.add_option_group(versionopts) + parser.add_option_group(credopts) + parser.add_option_group(subunitopts) + + opts, args = parser.parse_args() + + LP = sambaopts.get_loadparm() + CREDS = credopts.get_credentials(LP) + + try: + HOST = args[0] + except IndexError: + parser.print_usage() + sys.exit(1) + + if "://" not in HOST: + if os.path.isfile(HOST): + HOST = "tdb://%s" % HOST + else: + HOST = "ldap://%s" % HOST + + TestProgram(module=__name__, opts=subunitopts) + +main() diff --git a/source4/selftest/tests.py b/source4/selftest/tests.py index 6564ac7..a23af86 100755 --- a/source4/selftest/tests.py +++ b/source4/selftest/tests.py @@ -641,6 +641,12 @@ plantestsuite_loadlist("samba4.ldap.sort.python(ad_dc_ntvfs)", "ad_dc_ntvfs", [p plantestsuite_loadlist("samba4.ldap.vlv.python(ad_dc_ntvfs)", "ad_dc_ntvfs", [python, os.path.join(samba4srcdir, "dsdb/tests/python/vlv.py"), '$SERVER', '-U"$USERNAME%$PASSWORD"', '--workgroup=$DOMAIN', '$LOADLIST', '$LISTOPT']) plantestsuite_loadlist("samba4.ldap.linked_attributes.python(ad_dc_ntvfs)", "ad_dc_ntvfs:local", [python, os.path.join(samba4srcdir, "dsdb/tests/python/linked_attributes.py"), '$PREFIX_ABS/ad_dc_ntvfs/private/sam.ldb', '-U"$USERNAME%$PASSWORD"', '--workgroup=$DOMAIN', '$LOADLIST', '$LISTOPT']) +plantestsuite_loadlist("samba4.ldap.rodc.python(rodc)", "rodc", + [python, + os.path.join(samba4srcdir, "dsdb/tests/python/rodc.py"), + '$SERVER', '-U"$USERNAME%$PASSWORD"', + '--workgroup=$DOMAIN', '$LOADLIST', '$LISTOPT']) + for env in ["ad_dc_ntvfs", "fl2000dc", "fl2003dc", "fl2008r2dc"]: plantestsuite_loadlist("samba4.ldap_schema.python(%s)" % env, env, [python, os.path.join(samba4srcdir, "dsdb/tests/python/ldap_schema.py"), '$SERVER', '-U"$USERNAME%$PASSWORD"', '--workgroup=$DOMAIN', '$LOADLIST', '$LISTOPT']) plantestsuite("samba4.ldap.possibleInferiors.python(%s)" % env, env, [python, os.path.join(samba4srcdir, "dsdb/samdb/ldb_modules/tests/possibleinferiors.py"), "ldap://$SERVER", '-U"$USERNAME%$PASSWORD"', "-W$DOMAIN"]) -- 1.9.1 From 3611b69a350caeb8a3d96abc9c6cfcb0d30c9451 Mon Sep 17 00:00:00 2001 From: Garming Sam Date: Tue, 20 Sep 2016 04:25:34 +0000 Subject: [PATCH 07/24] rodc: Force all RODC add and delete to cause a referral Previously, you could add or delete and cause replication conflicts on an RODC. Modifies are already partly restricted in repl_meta_data and have more specific requirements, so they cannot be handled here. We still differ against Windows for modifies of non-replicated attributes over LDAP. Signed-off-by: Garming Sam Pair-programmed-with: Douglas Bagnall BUG: https://bugzilla.samba.org/show_bug.cgi?id=12008 --- selftest/knownfail | 3 +- source4/dsdb/samdb/ldb_modules/samldb.c | 70 +++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 2 deletions(-) diff --git a/selftest/knownfail b/selftest/knownfail index c91d227..85467ad 100644 --- a/selftest/knownfail +++ b/selftest/knownfail @@ -317,6 +317,5 @@ # rap password tests don't function in the ad_dc_ntvfs:local environment # ^samba.tests.auth_log_pass_change.samba.tests.auth_log_pass_change.AuthLogPassChangeTests.test_rap_change_password\(ad_dc_ntvfs:local\) -^samba4.ldap.rodc.python\(rodc\).__main__.RodcTests.test_add.* -^samba4.ldap.rodc.python\(rodc\).__main__.RodcTests.test_delete.* +# We currently don't send referrals for LDAP modify of non-replicated attrs ^samba4.ldap.rodc.python\(rodc\).__main__.RodcTests.test_modify_nonreplicated.* diff --git a/source4/dsdb/samdb/ldb_modules/samldb.c b/source4/dsdb/samdb/ldb_modules/samldb.c index 48edc28..971048d 100644 --- a/source4/dsdb/samdb/ldb_modules/samldb.c +++ b/source4/dsdb/samdb/ldb_modules/samldb.c @@ -44,6 +44,7 @@ #include "param/param.h" #include "libds/common/flag_mapping.h" #include "system/network.h" +#include "librpc/gen_ndr/irpc.h" struct samldb_ctx; enum samldb_add_type { @@ -3380,6 +3381,58 @@ static int samldb_verify_subnet(struct samldb_ctx *ac) return LDB_SUCCESS; } +static char *refer_if_rodc(struct ldb_context *ldb, struct ldb_request *req, + struct ldb_dn *dn) +{ + bool rodc = false; + struct loadparm_context *lp_ctx; + char *referral; + int ret; + WERROR err; + + if (ldb_request_get_control(req, DSDB_CONTROL_REPLICATED_UPDATE_OID) || + ldb_request_get_control(req, DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA)) { + return NULL; + } + + ret = samdb_rodc(ldb, &rodc); + if (ret != LDB_SUCCESS) { + DEBUG(4, (__location__ ": unable to tell if we are an RODC\n")); + return NULL; + } + + if (rodc) { + const char *domain = NULL; + struct ldb_dn *fsmo_role_dn; + struct ldb_dn *role_owner_dn; + ldb_set_errstring(ldb, "RODC modify is forbidden!"); + lp_ctx = talloc_get_type(ldb_get_opaque(ldb, "loadparm"), + struct loadparm_context); + + err = dsdb_get_fsmo_role_info(req, ldb, DREPL_PDC_MASTER, + &fsmo_role_dn, &role_owner_dn); + if (W_ERROR_IS_OK(err)) { + struct ldb_dn *server_dn = ldb_dn_copy(req, role_owner_dn); + if (server_dn != NULL) { + ldb_dn_remove_child_components(server_dn, 1); + + domain = samdb_dn_to_dnshostname(ldb, req, + server_dn); + } + } + if (domain == NULL) { + domain = lpcfg_dnsdomain(lp_ctx); + } + referral = talloc_asprintf(req, + "ldap://%s/%s", + domain, + ldb_dn_get_linearized(dn)); + return referral; + } + + return NULL; +} + /* add */ static int samldb_add(struct ldb_module *module, struct ldb_request *req) @@ -3388,6 +3441,7 @@ static int samldb_add(struct ldb_module *module, struct ldb_request *req) struct samldb_ctx *ac; struct ldb_message_element *el; int ret; + char *referral = NULL; ldb = ldb_module_get_ctx(module); ldb_debug(ldb, LDB_DEBUG_TRACE, "samldb_add\n"); @@ -3397,6 +3451,12 @@ static int samldb_add(struct ldb_module *module, struct ldb_request *req) return ldb_next_request(module, req); } + referral = refer_if_rodc(ldb, req, req->op.add.message->dn); + if (referral != NULL) { + ret = ldb_module_send_referral(req, referral); + return ret; + } + el = ldb_msg_find_element(req->op.add.message, "userParameters"); if (el != NULL && ldb_req_is_untrusted(req)) { const char *reason = "samldb_add: " @@ -3831,13 +3891,23 @@ static int samldb_prim_group_users_check(struct samldb_ctx *ac) static int samldb_delete(struct ldb_module *module, struct ldb_request *req) { struct samldb_ctx *ac; + char *referral = NULL; int ret; + struct ldb_context *ldb; if (ldb_dn_is_special(req->op.del.dn)) { /* do not manipulate our control entries */ return ldb_next_request(module, req); } + ldb = ldb_module_get_ctx(module); + + referral = refer_if_rodc(ldb, req, req->op.del.dn); + if (referral != NULL) { + ret = ldb_module_send_referral(req, referral); + return ret; + } + ac = samldb_ctx_init(module, req); if (ac == NULL) { return ldb_operr(ldb_module_get_ctx(module)); -- 1.9.1 From c560065cddaebe618116b3e342dccd6afdf4c50f Mon Sep 17 00:00:00 2001 From: Garming Sam Date: Mon, 10 Apr 2017 10:41:44 +1200 Subject: [PATCH 08/24] selftest: Make some assertions about RODC referrals Signed-off-by: Garming Sam --- source4/dsdb/tests/python/rodc.py | 55 ++++++++++++++++++++++++++++++++++----- 1 file changed, 49 insertions(+), 6 deletions(-) diff --git a/source4/dsdb/tests/python/rodc.py b/source4/dsdb/tests/python/rodc.py index 8fd9354..a306916 100755 --- a/source4/dsdb/tests/python/rodc.py +++ b/source4/dsdb/tests/python/rodc.py @@ -86,23 +86,42 @@ class RodcTests(samba.tests.TestCase): self.fail("couldn't modify referred location %s" % address) + if address.lower().startswith(self.samdb.domain_dns_name()): + self.fail("referral address did not give a specific DC") + def test_modify_replicated_attributes(self): # some timestamp ones dn = 'CN=Guest,CN=Users,' + self.base_dn value = 'hallooo' for attr in ['carLicense', 'middleName']: - m = ldb.Message() - m.dn = ldb.Dn(self.samdb, dn) - m[attr] = ldb.MessageElement(value, - ldb.FLAG_MOD_REPLACE, - attr) + msg = ldb.Message() + msg.dn = ldb.Dn(self.samdb, dn) + msg[attr] = ldb.MessageElement(value, + ldb.FLAG_MOD_REPLACE, + attr) try: - self.samdb.modify(m) + self.samdb.modify(msg) self.fail("Failed to fail to modify %s %s" % (dn, attr)) except ldb.LdbError as (ecode, emsg): if ecode != ldb.ERR_REFERRAL: self.fail("Failed to REFER when trying to modify %s %s" % (dn, attr)) + else: + m = re.search(r'(ldap://[^>]+)>', emsg) + if m is None: + self.fail("referral seems not to refer to anything") + address = m.group(1) + + try: + tmpdb = SamDB(address, credentials=CREDS, + session_info=system_session(LP), lp=LP) + tmpdb.modify(msg) + except ldb.LdbError, e: + self.fail("couldn't modify referred location %s" % + address) + + if address.lower().startswith(self.samdb.domain_dns_name()): + self.fail("referral address did not give a specific DC") def test_modify_nonreplicated_attributes(self): # some timestamp ones @@ -122,6 +141,14 @@ class RodcTests(samba.tests.TestCase): if ecode != ldb.ERR_REFERRAL: self.fail("Failed to REFER when trying to modify %s %s" % (dn, attr)) + else: + m = re.search(r'(ldap://[^>]+)>', emsg) + if m is None: + self.fail("referral seems not to refer to anything") + address = m.group(1) + + if address.lower().startswith(self.samdb.domain_dns_name()): + self.fail("referral address did not give a specific DC") def test_modify_nonreplicated_reps_attributes(self): # some timestamp ones @@ -148,6 +175,14 @@ class RodcTests(samba.tests.TestCase): if ecode != ldb.ERR_REFERRAL: self.fail("Failed to REFER when trying to modify %s %s" % (dn, attr)) + else: + m = re.search(r'(ldap://[^>]+)>', emsg) + if m is None: + self.fail("referral seems not to refer to anything") + address = m.group(1) + + if address.lower().startswith(self.samdb.domain_dns_name()): + self.fail("referral address did not give a specific DC") def test_delete_special_objects(self): dn = 'CN=Guest,CN=Users,' + self.base_dn @@ -158,6 +193,14 @@ class RodcTests(samba.tests.TestCase): if ecode != ldb.ERR_REFERRAL: print ecode, emsg self.fail("Failed to REFER when trying to delete %s" % dn) + else: + m = re.search(r'(ldap://[^>]+)>', emsg) + if m is None: + self.fail("referral seems not to refer to anything") + address = m.group(1) + + if address.lower().startswith(self.samdb.domain_dns_name()): + self.fail("referral address did not give a specific DC") def test_no_delete_nonexistent_objects(self): dn = 'CN=does-not-exist-%s,CN=Users,%s' % (self.tag, self.base_dn) -- 1.9.1 From d1efd0a75513e21737d66dc046b01ed228dff675 Mon Sep 17 00:00:00 2001 From: Garming Sam Date: Wed, 5 Apr 2017 14:30:28 +1200 Subject: [PATCH 09/24] password_lockout: Move all helper methods to a base class This is so that we can import the login tests into the RODC-RWDC tests. Signed-off-by: Garming Sam --- source4/dsdb/tests/python/password_lockout.py | 1230 ++++++++++++------------- 1 file changed, 615 insertions(+), 615 deletions(-) diff --git a/source4/dsdb/tests/python/password_lockout.py b/source4/dsdb/tests/python/password_lockout.py index 42cadc1..6bf0d8f 100755 --- a/source4/dsdb/tests/python/password_lockout.py +++ b/source4/dsdb/tests/python/password_lockout.py @@ -70,8 +70,7 @@ template_creds.set_kerberos_state(global_creds.get_kerberos_state()) # Tests start here # -class PasswordTests(samba.tests.TestCase): - +class BasePasswordTestCase(samba.tests.TestCase): def _open_samr_user(self, res): self.assertTrue("objectSid" in res[0]) @@ -89,41 +88,6 @@ class PasswordTests(samba.tests.TestCase): self.samr.SetUserInfo(samr_user, 16, acb_info) self.samr.Close(samr_user) - def _reset_ldap_lockoutTime(self, res): - self.ldb.modify_ldif(""" -dn: """ + str(res[0].dn) + """ -changetype: modify -replace: lockoutTime -lockoutTime: 0 -""") - - def _reset_ldap_userAccountControl(self, res): - self.assertTrue("userAccountControl" in res[0]) - self.assertTrue("msDS-User-Account-Control-Computed" in res[0]) - - uac = int(res[0]["userAccountControl"][0]) - uacc = int(res[0]["msDS-User-Account-Control-Computed"][0]) - - uac |= uacc - uac = uac & ~dsdb.UF_LOCKOUT - - self.ldb.modify_ldif(""" -dn: """ + str(res[0].dn) + """ -changetype: modify -replace: userAccountControl -userAccountControl: %d -""" % uac) - - def _reset_by_method(self, res, method): - if method is "ldap_userAccountControl": - self._reset_ldap_userAccountControl(res) - elif method is "ldap_lockoutTime": - self._reset_ldap_lockoutTime(res) - elif method is "samr": - self._reset_samr(res) - else: - self.assertTrue(False, msg="Invalid reset method[%s]" % method) - def _check_attribute(self, res, name, value): if value is None: self.assertTrue(name not in res[0], @@ -470,7 +434,7 @@ userPassword: """ + userpass + """ (num, errno))) def setUp(self): - super(PasswordTests, self).setUp() + super(BasePasswordTestCase, self).setUp() self.ldb = SamDB(url=host_url, session_info=system_session(lp), credentials=global_creds, lp=lp) @@ -580,60 +544,56 @@ lockoutThreshold: """ + str(lockoutThreshold) + """ self.lockout2ntlm_ldb = self._readd_user(self.lockout2ntlm_creds, lockOutObservationWindow=self.lockout_observation_window) - def _test_userPassword_lockout_with_clear_change(self, creds, other_ldb, method, - initial_lastlogon_relation=None): - # Notice: This works only against Windows if "dSHeuristics" has been set - # properly + def tearDown(self): + super(BasePasswordTestCase, self).tearDown() + + def _test_login_lockout(self, creds): username = creds.get_username() userpass = creds.get_password() userdn = "cn=%s,cn=users,%s" % (username, self.base_dn) use_kerberos = creds.get_kerberos_state() + # This unlocks by waiting for account_lockout_duration if use_kerberos == MUST_USE_KERBEROS: logoncount_relation = 'greater' lastlogon_relation = 'greater' - print "Performs a password cleartext change operation on 'userPassword' using Kerberos" + print "Performs a lockout attempt against LDAP using Kerberos" else: logoncount_relation = 'equal' lastlogon_relation = 'equal' - print "Performs a password cleartext change operation on 'userPassword' using NTLMSSP" - - if initial_lastlogon_relation is not None: - lastlogon_relation = initial_lastlogon_relation + print "Performs a lockout attempt against LDAP using NTLM" + # Change password on a connection as another user res = self._check_account(userdn, badPwdCount=0, badPasswordTime=("greater", 0), logonCount=(logoncount_relation, 0), - lastLogon=(lastlogon_relation, 0), - lastLogonTimestamp=('greater', 0), + lastLogon=("greater", 0), + lastLogonTimestamp=("greater", 0), userAccountControl= dsdb.UF_NORMAL_ACCOUNT, msDSUserAccountControlComputed=0) badPasswordTime = int(res[0]["badPasswordTime"][0]) logonCount = int(res[0]["logonCount"][0]) lastLogon = int(res[0]["lastLogon"][0]) + firstLogon = lastLogon lastLogonTimestamp = int(res[0]["lastLogonTimestamp"][0]) - if lastlogon_relation == 'greater': - self.assertGreater(lastLogon, badPasswordTime) - self.assertGreaterEqual(lastLogon, lastLogonTimestamp) + print firstLogon + print lastLogonTimestamp - # Change password on a connection as another user - # Wrong old password - try: - other_ldb.modify_ldif(""" -dn: """ + userdn + """ -changetype: modify -delete: userPassword -userPassword: thatsAcomplPASS1x -add: userPassword -userPassword: thatsAcomplPASS2 -""") - self.fail() - except LdbError, (num, msg): - self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) - self.assertTrue('00000056' in msg, msg) + self.assertGreater(lastLogon, badPasswordTime) + self.assertGreaterEqual(lastLogon, lastLogonTimestamp) + + # Open a second LDB connection with the user credentials. Use the + # command line credentials for informations like the domain, the realm + # and the workstation. + creds_lockout = self.insta_creds(creds) + + # The wrong password + creds_lockout.set_password("thatsAcomplPASS1x") + + self.assertLoginFailure(host_url, creds_lockout, lp) res = self._check_account(userdn, badPwdCount=1, @@ -643,43 +603,58 @@ userPassword: thatsAcomplPASS2 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 - other_ldb.modify_ldif(""" -dn: """ + userdn + """ -changetype: modify -delete: userPassword -userPassword: """ + userpass + """ -add: userPassword -userPassword: thatsAcomplPASS2 -""") + creds_lockout.set_password(userpass) + + ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp) + # lastLogonTimestamp should not change + # lastLogon increases if badPwdCount is non-zero (!) res = self._check_account(userdn, - badPwdCount=1, + badPwdCount=0, badPasswordTime=badPasswordTime, + logonCount=(logoncount_relation, logonCount), + lastLogon=('greater', lastLogon), + lastLogonTimestamp=lastLogonTimestamp, + userAccountControl= + dsdb.UF_NORMAL_ACCOUNT, + msDSUserAccountControlComputed=0, + msg='LLTimestamp is updated to lastlogon') + + logonCount = int(res[0]["logonCount"][0]) + lastLogon = int(res[0]["lastLogon"][0]) + self.assertGreater(lastLogon, badPasswordTime) + self.assertGreaterEqual(lastLogon, lastLogonTimestamp) + + # The wrong password + creds_lockout.set_password("thatsAcomplPASS1x") + + self.assertLoginFailure(host_url, creds_lockout, lp) + + res = self._check_account(userdn, + badPwdCount=1, + badPasswordTime=("greater", badPasswordTime), logonCount=logonCount, lastLogon=lastLogon, lastLogonTimestamp=lastLogonTimestamp, userAccountControl= dsdb.UF_NORMAL_ACCOUNT, msDSUserAccountControlComputed=0) + badPasswordTime = int(res[0]["badPasswordTime"][0]) + + # The wrong password + creds_lockout.set_password("thatsAcomplPASS1x") - # Wrong old password try: - other_ldb.modify_ldif(""" -dn: """ + userdn + """ -changetype: modify -delete: userPassword -userPassword: thatsAcomplPASS1x -add: userPassword -userPassword: thatsAcomplPASS2 -""") + ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp) self.fail() + except LdbError, (num, msg): - self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) - self.assertTrue('00000056' in msg, msg) + self.assertEquals(num, ERR_INVALID_CREDENTIALS) res = self._check_account(userdn, badPwdCount=2, @@ -694,20 +669,15 @@ userPassword: thatsAcomplPASS2 print "two failed password change" - # Wrong old password + # The wrong password + creds_lockout.set_password("thatsAcomplPASS1x") + try: - other_ldb.modify_ldif(""" -dn: """ + userdn + """ -changetype: modify -delete: userPassword -userPassword: thatsAcomplPASS1x -add: userPassword -userPassword: thatsAcomplPASS2 -""") + ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp) self.fail() + except LdbError, (num, msg): - self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) - self.assertTrue('00000056' in msg, msg) + self.assertEquals(num, ERR_INVALID_CREDENTIALS) res = self._check_account(userdn, badPwdCount=3, @@ -722,20 +692,13 @@ userPassword: thatsAcomplPASS2 badPasswordTime = int(res[0]["badPasswordTime"][0]) lockoutTime = int(res[0]["lockoutTime"][0]) - # Wrong old password + # The wrong password + creds_lockout.set_password("thatsAcomplPASS1x") try: - other_ldb.modify_ldif(""" -dn: """ + userdn + """ -changetype: modify -delete: userPassword -userPassword: thatsAcomplPASS1x -add: userPassword -userPassword: thatsAcomplPASS2 -""") + ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp) self.fail() except LdbError, (num, msg): - self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) - self.assertTrue('00000775' in msg, msg) + self.assertEquals(num, ERR_INVALID_CREDENTIALS) res = self._check_account(userdn, badPwdCount=3, @@ -748,46 +711,32 @@ userPassword: thatsAcomplPASS2 dsdb.UF_NORMAL_ACCOUNT, msDSUserAccountControlComputed=dsdb.UF_LOCKOUT) - # Wrong old password + # The wrong password + creds_lockout.set_password("thatsAcomplPASS1x") try: - other_ldb.modify_ldif(""" -dn: """ + userdn + """ -changetype: modify -delete: userPassword -userPassword: thatsAcomplPASS1x -add: userPassword -userPassword: thatsAcomplPASS2 -""") + ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp) self.fail() except LdbError, (num, msg): - self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) - self.assertTrue('00000775' in msg, msg) + self.assertEquals(num, ERR_INVALID_CREDENTIALS) res = self._check_account(userdn, badPwdCount=3, badPasswordTime=badPasswordTime, logonCount=logonCount, - lockoutTime=lockoutTime, lastLogon=lastLogon, lastLogonTimestamp=lastLogonTimestamp, + lockoutTime=lockoutTime, userAccountControl= dsdb.UF_NORMAL_ACCOUNT, msDSUserAccountControlComputed=dsdb.UF_LOCKOUT) + # The correct password, but we are locked out + creds_lockout.set_password(userpass) try: - # Correct old password - other_ldb.modify_ldif(""" -dn: """ + userdn + """ -changetype: modify -delete: userPassword -userPassword: thatsAcomplPASS2 -add: userPassword -userPassword: thatsAcomplPASS2x -""") + ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp) self.fail() except LdbError, (num, msg): - self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) - self.assertTrue('00000775' in msg, msg) + self.assertEquals(num, ERR_INVALID_CREDENTIALS) res = self._check_account(userdn, badPwdCount=3, @@ -800,104 +749,76 @@ userPassword: thatsAcomplPASS2x dsdb.UF_NORMAL_ACCOUNT, msDSUserAccountControlComputed=dsdb.UF_LOCKOUT) - # Now reset the password, which does NOT change the lockout! - self.ldb.modify_ldif(""" -dn: """ + userdn + """ -changetype: modify -replace: userPassword -userPassword: thatsAcomplPASS2 -""") + # wait for the lockout to end + time.sleep(self.account_lockout_duration + 1) + print self.account_lockout_duration + 1 res = self._check_account(userdn, - badPwdCount=3, + badPwdCount=3, effective_bad_password_count=0, badPasswordTime=badPasswordTime, logonCount=logonCount, + lockoutTime=lockoutTime, lastLogon=lastLogon, lastLogonTimestamp=lastLogonTimestamp, - lockoutTime=lockoutTime, userAccountControl= dsdb.UF_NORMAL_ACCOUNT, - msDSUserAccountControlComputed=dsdb.UF_LOCKOUT) + msDSUserAccountControlComputed=0) - try: - # Correct old password - other_ldb.modify_ldif(""" -dn: """ + userdn + """ -changetype: modify -delete: userPassword -userPassword: thatsAcomplPASS2 -add: userPassword -userPassword: thatsAcomplPASS2x -""") - self.fail() - except LdbError, (num, msg): - self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) - self.assertTrue('00000775' in msg, msg) + # The correct password after letting the timeout expire + + creds_lockout.set_password(userpass) + + creds_lockout2 = self.insta_creds(creds_lockout) + + ldb_lockout = SamDB(url=host_url, credentials=creds_lockout2, lp=lp) + time.sleep(3) res = self._check_account(userdn, - badPwdCount=3, + badPwdCount=0, badPasswordTime=badPasswordTime, - logonCount=logonCount, - lastLogon=lastLogon, + logonCount=(logoncount_relation, logonCount), + lastLogon=(lastlogon_relation, lastLogon), lastLogonTimestamp=lastLogonTimestamp, - lockoutTime=lockoutTime, + lockoutTime=0, userAccountControl= dsdb.UF_NORMAL_ACCOUNT, - msDSUserAccountControlComputed=dsdb.UF_LOCKOUT) - - m = Message() - m.dn = Dn(self.ldb, userdn) - m["userAccountControl"] = MessageElement( - str(dsdb.UF_LOCKOUT), - FLAG_MOD_REPLACE, "userAccountControl") - - self.ldb.modify(m) + msDSUserAccountControlComputed=0, + msg="lastLogon is way off") - # This shows that setting the UF_LOCKOUT flag alone makes no difference - res = self._check_account(userdn, - badPwdCount=3, - badPasswordTime=badPasswordTime, - logonCount=logonCount, - lastLogon=lastLogon, - lastLogonTimestamp=lastLogonTimestamp, - lockoutTime=lockoutTime, - userAccountControl= - dsdb.UF_NORMAL_ACCOUNT, - msDSUserAccountControlComputed=dsdb.UF_LOCKOUT) + logonCount = int(res[0]["logonCount"][0]) + lastLogon = int(res[0]["lastLogon"][0]) - # This shows that setting the UF_LOCKOUT flag makes no difference + # The wrong password + creds_lockout.set_password("thatsAcomplPASS1x") try: - # Correct old password - other_ldb.modify_ldif(""" -dn: """ + userdn + """ -changetype: modify -delete: unicodePwd -unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """ -add: unicodePwd -unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2x\"".encode('utf-16-le')) + """ -""") + ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp) self.fail() except LdbError, (num, msg): - self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) - self.assertTrue('00000775' in msg, msg) + self.assertEquals(num, ERR_INVALID_CREDENTIALS) res = self._check_account(userdn, - badPwdCount=3, - badPasswordTime=badPasswordTime, + badPwdCount=1, + badPasswordTime=("greater", badPasswordTime), logonCount=logonCount, - lockoutTime=lockoutTime, + lockoutTime=0, lastLogon=lastLogon, lastLogonTimestamp=lastLogonTimestamp, userAccountControl= dsdb.UF_NORMAL_ACCOUNT, - msDSUserAccountControlComputed=dsdb.UF_LOCKOUT) + msDSUserAccountControlComputed=0) + badPasswordTime = int(res[0]["badPasswordTime"][0]) - self._reset_by_method(res, method) + # The wrong password + creds_lockout.set_password("thatsAcomplPASS1x") + try: + ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp) + self.fail() + except LdbError, (num, msg): + self.assertEquals(num, ERR_INVALID_CREDENTIALS) - # Here bad password counts are reset without logon success. res = self._check_account(userdn, - badPwdCount=0, - badPasswordTime=badPasswordTime, + badPwdCount=2, + badPasswordTime=("greater", badPasswordTime), logonCount=logonCount, lockoutTime=0, lastLogon=lastLogon, @@ -905,22 +826,12 @@ unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2x\"".encode('utf-16-le')) userAccountControl= dsdb.UF_NORMAL_ACCOUNT, msDSUserAccountControlComputed=0) + badPasswordTime = int(res[0]["badPasswordTime"][0]) - # The correct password after doing the unlock - - other_ldb.modify_ldif(""" -dn: """ + userdn + """ -changetype: modify -delete: unicodePwd -unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """ -add: unicodePwd -unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2x\"".encode('utf-16-le')) + """ -""") - userpass = "thatsAcomplPASS2x" - creds.set_password(userpass) + time.sleep(self.lockout_observation_window + 1) res = self._check_account(userdn, - badPwdCount=0, + badPwdCount=2, effective_bad_password_count=0, badPasswordTime=badPasswordTime, logonCount=logonCount, lockoutTime=0, @@ -930,20 +841,13 @@ unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2x\"".encode('utf-16-le')) dsdb.UF_NORMAL_ACCOUNT, msDSUserAccountControlComputed=0) - # Wrong old password + # The wrong password + creds_lockout.set_password("thatsAcomplPASS1x") try: - other_ldb.modify_ldif(""" -dn: """ + userdn + """ -changetype: modify -delete: userPassword -userPassword: thatsAcomplPASS1xyz -add: userPassword -userPassword: thatsAcomplPASS2XYZ -""") + ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp) self.fail() except LdbError, (num, msg): - self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) - self.assertTrue('00000056' in msg, msg) + self.assertEquals(num, ERR_INVALID_CREDENTIALS) res = self._check_account(userdn, badPwdCount=1, @@ -957,96 +861,159 @@ userPassword: thatsAcomplPASS2XYZ msDSUserAccountControlComputed=0) badPasswordTime = int(res[0]["badPasswordTime"][0]) - # Wrong old password - try: - other_ldb.modify_ldif(""" -dn: """ + userdn + """ -changetype: modify -delete: userPassword -userPassword: thatsAcomplPASS1xyz -add: userPassword -userPassword: thatsAcomplPASS2XYZ -""") - self.fail() - except LdbError, (num, msg): - self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) - self.assertTrue('00000056' in msg, msg) + # The correct password without letting the timeout expire + creds_lockout.set_password(userpass) + ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp) res = self._check_account(userdn, - badPwdCount=2, - badPasswordTime=("greater", badPasswordTime), - logonCount=logonCount, + badPwdCount=0, + badPasswordTime=badPasswordTime, + logonCount=(logoncount_relation, logonCount), lockoutTime=0, - lastLogon=lastLogon, + lastLogon=("greater", lastLogon), lastLogonTimestamp=lastLogonTimestamp, userAccountControl= dsdb.UF_NORMAL_ACCOUNT, msDSUserAccountControlComputed=0) + + def _test_multiple_logon(self, creds): + # Test the happy case in which a user logs on correctly, then + # logs on correctly again, so that the bad password and + # lockout times are both zero the second time. The lastlogon + # time should increase. + + # Open a second LDB connection with the user credentials. Use the + # command line credentials for informations like the domain, the realm + # and the workstation. + username = creds.get_username() + userdn = "cn=%s,cn=users,%s" % (username, self.base_dn) + + use_kerberos = creds.get_kerberos_state() + if use_kerberos == MUST_USE_KERBEROS: + print "Testing multiple logon with Kerberos" + logoncount_relation = 'greater' + lastlogon_relation = 'greater' + else: + print "Testing multiple logon with NTLM" + logoncount_relation = 'equal' + lastlogon_relation = 'equal' + + SamDB(url=host_url, credentials=self.insta_creds(creds), lp=lp) + + res = self._check_account(userdn, + badPwdCount=0, + badPasswordTime=("greater", 0), + logonCount=(logoncount_relation, 0), + lastLogon=("greater", 0), + lastLogonTimestamp=("greater", 0), + userAccountControl= + dsdb.UF_NORMAL_ACCOUNT, + msDSUserAccountControlComputed=0) badPasswordTime = int(res[0]["badPasswordTime"][0]) + logonCount = int(res[0]["logonCount"][0]) + lastLogon = int(res[0]["lastLogon"][0]) + lastLogonTimestamp = int(res[0]["lastLogonTimestamp"][0]) + firstLogon = lastLogon + print "last logon is %d" % lastLogon + self.assertGreater(lastLogon, badPasswordTime) + self.assertGreaterEqual(lastLogon, lastLogonTimestamp) - self._reset_ldap_lockoutTime(res) + time.sleep(1) + SamDB(url=host_url, credentials=self.insta_creds(creds), lp=lp) res = self._check_account(userdn, badPwdCount=0, badPasswordTime=badPasswordTime, - logonCount=logonCount, - lastLogon=lastLogon, + logonCount=(logoncount_relation, logonCount), + lastLogon=(lastlogon_relation, lastLogon), + lastLogonTimestamp=lastLogonTimestamp, + userAccountControl= + dsdb.UF_NORMAL_ACCOUNT, + msDSUserAccountControlComputed=0, + msg=("second logon, firstlogon was %s" % + firstLogon)) + + + lastLogon = int(res[0]["lastLogon"][0]) + + time.sleep(1) + + SamDB(url=host_url, credentials=self.insta_creds(creds), lp=lp) + + res = self._check_account(userdn, + badPwdCount=0, + badPasswordTime=badPasswordTime, + logonCount=(logoncount_relation, logonCount), + lastLogon=(lastlogon_relation, lastLogon), lastLogonTimestamp=lastLogonTimestamp, - lockoutTime=0, userAccountControl= dsdb.UF_NORMAL_ACCOUNT, msDSUserAccountControlComputed=0) - def test_userPassword_lockout_with_clear_change_krb5_ldap_userAccountControl(self): - self._test_userPassword_lockout_with_clear_change(self.lockout1krb5_creds, - self.lockout2krb5_ldb, - "ldap_userAccountControl") - def test_userPassword_lockout_with_clear_change_krb5_ldap_lockoutTime(self): - self._test_userPassword_lockout_with_clear_change(self.lockout1krb5_creds, - self.lockout2krb5_ldb, - "ldap_lockoutTime") +class PasswordTests(BasePasswordTestCase): + def _reset_ldap_lockoutTime(self, res): + self.ldb.modify_ldif(""" +dn: """ + str(res[0].dn) + """ +changetype: modify +replace: lockoutTime +lockoutTime: 0 +""") - def test_userPassword_lockout_with_clear_change_krb5_samr(self): - self._test_userPassword_lockout_with_clear_change(self.lockout1krb5_creds, - self.lockout2krb5_ldb, - "samr") + def _reset_ldap_userAccountControl(self, res): + self.assertTrue("userAccountControl" in res[0]) + self.assertTrue("msDS-User-Account-Control-Computed" in res[0]) - def test_userPassword_lockout_with_clear_change_ntlm_ldap_userAccountControl(self): - self._test_userPassword_lockout_with_clear_change(self.lockout1ntlm_creds, - self.lockout2ntlm_ldb, - "ldap_userAccountControl", - initial_lastlogon_relation='greater') + uac = int(res[0]["userAccountControl"][0]) + uacc = int(res[0]["msDS-User-Account-Control-Computed"][0]) - def test_userPassword_lockout_with_clear_change_ntlm_ldap_lockoutTime(self): - self._test_userPassword_lockout_with_clear_change(self.lockout1ntlm_creds, - self.lockout2ntlm_ldb, - "ldap_lockoutTime", - initial_lastlogon_relation='greater') + uac |= uacc + uac = uac & ~dsdb.UF_LOCKOUT - def test_userPassword_lockout_with_clear_change_ntlm_samr(self): - self._test_userPassword_lockout_with_clear_change(self.lockout1ntlm_creds, - self.lockout2ntlm_ldb, - "samr", - initial_lastlogon_relation='greater') + self.ldb.modify_ldif(""" +dn: """ + str(res[0].dn) + """ +changetype: modify +replace: userAccountControl +userAccountControl: %d +""" % uac) - def _test_unicodePwd_lockout_with_clear_change(self, creds, other_ldb, - initial_logoncount_relation=None): - print "Performs a password cleartext change operation on 'unicodePwd'" + def _reset_by_method(self, res, method): + if method is "ldap_userAccountControl": + self._reset_ldap_userAccountControl(res) + elif method is "ldap_lockoutTime": + self._reset_ldap_lockoutTime(res) + elif method is "samr": + self._reset_samr(res) + else: + self.assertTrue(False, msg="Invalid reset method[%s]" % method) + + def _test_userPassword_lockout_with_clear_change(self, creds, other_ldb, method, + initial_lastlogon_relation=None): + # Notice: This works only against Windows if "dSHeuristics" has been set + # properly username = creds.get_username() userpass = creds.get_password() userdn = "cn=%s,cn=users,%s" % (username, self.base_dn) - if initial_logoncount_relation is not None: - logoncount_relation = initial_logoncount_relation + + use_kerberos = creds.get_kerberos_state() + if use_kerberos == MUST_USE_KERBEROS: + logoncount_relation = 'greater' + lastlogon_relation = 'greater' + print "Performs a password cleartext change operation on 'userPassword' using Kerberos" else: - logoncount_relation = "greater" + logoncount_relation = 'equal' + lastlogon_relation = 'equal' + print "Performs a password cleartext change operation on 'userPassword' using NTLMSSP" + + if initial_lastlogon_relation is not None: + lastlogon_relation = initial_lastlogon_relation res = self._check_account(userdn, badPwdCount=0, badPasswordTime=("greater", 0), logonCount=(logoncount_relation, 0), - lastLogon=("greater", 0), - lastLogonTimestamp=("greater", 0), + lastLogon=(lastlogon_relation, 0), + lastLogonTimestamp=('greater', 0), userAccountControl= dsdb.UF_NORMAL_ACCOUNT, msDSUserAccountControlComputed=0) @@ -1054,8 +1021,9 @@ userPassword: thatsAcomplPASS2XYZ logonCount = int(res[0]["logonCount"][0]) lastLogon = int(res[0]["lastLogon"][0]) lastLogonTimestamp = int(res[0]["lastLogonTimestamp"][0]) - self.assertGreater(lastLogonTimestamp, badPasswordTime) - self.assertGreaterEqual(lastLogon, lastLogonTimestamp) + if lastlogon_relation == 'greater': + self.assertGreater(lastLogon, badPasswordTime) + self.assertGreaterEqual(lastLogon, lastLogonTimestamp) # Change password on a connection as another user @@ -1064,10 +1032,10 @@ userPassword: thatsAcomplPASS2XYZ other_ldb.modify_ldif(""" dn: """ + userdn + """ changetype: modify -delete: unicodePwd -unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS1x\"".encode('utf-16-le')) + """ -add: unicodePwd -unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """ +delete: userPassword +userPassword: thatsAcomplPASS1x +add: userPassword +userPassword: thatsAcomplPASS2 """) self.fail() except LdbError, (num, msg): @@ -1086,19 +1054,13 @@ unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) badPasswordTime = int(res[0]["badPasswordTime"][0]) # Correct old password - old_utf16 = ("\"%s\"" % userpass).encode('utf-16-le') - invalid_utf16 = "\"thatsAcomplPASSX\"".encode('utf-16-le') - userpass = "thatsAcomplPASS2" - creds.set_password(userpass) - new_utf16 = ("\"%s\"" % userpass).encode('utf-16-le') - other_ldb.modify_ldif(""" dn: """ + userdn + """ changetype: modify -delete: unicodePwd -unicodePwd:: """ + base64.b64encode(old_utf16) + """ -add: unicodePwd -unicodePwd:: """ + base64.b64encode(new_utf16) + """ +delete: userPassword +userPassword: """ + userpass + """ +add: userPassword +userPassword: thatsAcomplPASS2 """) res = self._check_account(userdn, @@ -1116,10 +1078,10 @@ unicodePwd:: """ + base64.b64encode(new_utf16) + """ other_ldb.modify_ldif(""" dn: """ + userdn + """ changetype: modify -delete: unicodePwd -unicodePwd:: """ + base64.b64encode(old_utf16) + """ -add: unicodePwd -unicodePwd:: """ + base64.b64encode(new_utf16) + """ +delete: userPassword +userPassword: thatsAcomplPASS1x +add: userPassword +userPassword: thatsAcomplPASS2 """) self.fail() except LdbError, (num, msg): @@ -1137,21 +1099,6 @@ unicodePwd:: """ + base64.b64encode(new_utf16) + """ msDSUserAccountControlComputed=0) badPasswordTime = int(res[0]["badPasswordTime"][0]) - # SAMR doesn't have any impact if dsdb.UF_LOCKOUT isn't present. - # It doesn't create "lockoutTime" = 0 and doesn't - # reset "badPwdCount" = 0. - self._reset_samr(res) - - res = self._check_account(userdn, - badPwdCount=2, - badPasswordTime=badPasswordTime, - logonCount=logonCount, - lastLogon=lastLogon, - lastLogonTimestamp=lastLogonTimestamp, - userAccountControl= - dsdb.UF_NORMAL_ACCOUNT, - msDSUserAccountControlComputed=0) - print "two failed password change" # Wrong old password @@ -1159,17 +1106,16 @@ unicodePwd:: """ + base64.b64encode(new_utf16) + """ other_ldb.modify_ldif(""" dn: """ + userdn + """ changetype: modify -delete: unicodePwd -unicodePwd:: """ + base64.b64encode(invalid_utf16) + """ -add: unicodePwd -unicodePwd:: """ + base64.b64encode(new_utf16) + """ +delete: userPassword +userPassword: thatsAcomplPASS1x +add: userPassword +userPassword: thatsAcomplPASS2 """) self.fail() except LdbError, (num, msg): self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) self.assertTrue('00000056' in msg, msg) - # this is strange, why do we have lockoutTime=badPasswordTime here? res = self._check_account(userdn, badPwdCount=3, badPasswordTime=("greater", badPasswordTime), @@ -1188,10 +1134,10 @@ unicodePwd:: """ + base64.b64encode(new_utf16) + """ other_ldb.modify_ldif(""" dn: """ + userdn + """ changetype: modify -delete: unicodePwd -unicodePwd:: """ + base64.b64encode(invalid_utf16) + """ -add: unicodePwd -unicodePwd:: """ + base64.b64encode(new_utf16) + """ +delete: userPassword +userPassword: thatsAcomplPASS1x +add: userPassword +userPassword: thatsAcomplPASS2 """) self.fail() except LdbError, (num, msg): @@ -1214,10 +1160,36 @@ unicodePwd:: """ + base64.b64encode(new_utf16) + """ other_ldb.modify_ldif(""" dn: """ + userdn + """ changetype: modify -delete: unicodePwd -unicodePwd:: """ + base64.b64encode(invalid_utf16) + """ -add: unicodePwd -unicodePwd:: """ + base64.b64encode(new_utf16) + """ +delete: userPassword +userPassword: thatsAcomplPASS1x +add: userPassword +userPassword: thatsAcomplPASS2 +""") + self.fail() + except LdbError, (num, msg): + self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) + self.assertTrue('00000775' in msg, msg) + + res = self._check_account(userdn, + badPwdCount=3, + badPasswordTime=badPasswordTime, + logonCount=logonCount, + lockoutTime=lockoutTime, + lastLogon=lastLogon, + lastLogonTimestamp=lastLogonTimestamp, + userAccountControl= + dsdb.UF_NORMAL_ACCOUNT, + msDSUserAccountControlComputed=dsdb.UF_LOCKOUT) + + try: + # Correct old password + other_ldb.modify_ldif(""" +dn: """ + userdn + """ +changetype: modify +delete: userPassword +userPassword: thatsAcomplPASS2 +add: userPassword +userPassword: thatsAcomplPASS2x """) self.fail() except LdbError, (num, msg): @@ -1235,15 +1207,81 @@ unicodePwd:: """ + base64.b64encode(new_utf16) + """ dsdb.UF_NORMAL_ACCOUNT, msDSUserAccountControlComputed=dsdb.UF_LOCKOUT) + # Now reset the password, which does NOT change the lockout! + self.ldb.modify_ldif(""" +dn: """ + userdn + """ +changetype: modify +replace: userPassword +userPassword: thatsAcomplPASS2 +""") + + res = self._check_account(userdn, + badPwdCount=3, + badPasswordTime=badPasswordTime, + logonCount=logonCount, + lastLogon=lastLogon, + lastLogonTimestamp=lastLogonTimestamp, + lockoutTime=lockoutTime, + userAccountControl= + dsdb.UF_NORMAL_ACCOUNT, + msDSUserAccountControlComputed=dsdb.UF_LOCKOUT) + + try: + # Correct old password + other_ldb.modify_ldif(""" +dn: """ + userdn + """ +changetype: modify +delete: userPassword +userPassword: thatsAcomplPASS2 +add: userPassword +userPassword: thatsAcomplPASS2x +""") + self.fail() + except LdbError, (num, msg): + self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) + self.assertTrue('00000775' in msg, msg) + + res = self._check_account(userdn, + badPwdCount=3, + badPasswordTime=badPasswordTime, + logonCount=logonCount, + lastLogon=lastLogon, + lastLogonTimestamp=lastLogonTimestamp, + lockoutTime=lockoutTime, + userAccountControl= + dsdb.UF_NORMAL_ACCOUNT, + msDSUserAccountControlComputed=dsdb.UF_LOCKOUT) + + m = Message() + m.dn = Dn(self.ldb, userdn) + m["userAccountControl"] = MessageElement( + str(dsdb.UF_LOCKOUT), + FLAG_MOD_REPLACE, "userAccountControl") + + self.ldb.modify(m) + + # This shows that setting the UF_LOCKOUT flag alone makes no difference + res = self._check_account(userdn, + badPwdCount=3, + badPasswordTime=badPasswordTime, + logonCount=logonCount, + lastLogon=lastLogon, + lastLogonTimestamp=lastLogonTimestamp, + lockoutTime=lockoutTime, + userAccountControl= + dsdb.UF_NORMAL_ACCOUNT, + msDSUserAccountControlComputed=dsdb.UF_LOCKOUT) + + # This shows that setting the UF_LOCKOUT flag makes no difference try: # Correct old password other_ldb.modify_ldif(""" dn: """ + userdn + """ changetype: modify delete: unicodePwd -unicodePwd:: """ + base64.b64encode(new_utf16) + """ +unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """ add: unicodePwd -unicodePwd:: """ + base64.b64encode(invalid_utf16) + """ +unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2x\"".encode('utf-16-le')) + """ """) self.fail() except LdbError, (num, msg): @@ -1254,50 +1292,47 @@ unicodePwd:: """ + base64.b64encode(invalid_utf16) + """ badPwdCount=3, badPasswordTime=badPasswordTime, logonCount=logonCount, + lockoutTime=lockoutTime, lastLogon=lastLogon, lastLogonTimestamp=lastLogonTimestamp, - lockoutTime=lockoutTime, userAccountControl= dsdb.UF_NORMAL_ACCOUNT, msDSUserAccountControlComputed=dsdb.UF_LOCKOUT) - # Now reset the lockout, by removing ACB_AUTOLOCK (which removes the lock, despite being a generated attribute) - self._reset_samr(res); + self._reset_by_method(res, method) + # Here bad password counts are reset without logon success. res = self._check_account(userdn, badPwdCount=0, badPasswordTime=badPasswordTime, logonCount=logonCount, + lockoutTime=0, lastLogon=lastLogon, lastLogonTimestamp=lastLogonTimestamp, - lockoutTime=0, userAccountControl= dsdb.UF_NORMAL_ACCOUNT, msDSUserAccountControlComputed=0) - # Correct old password - old_utf16 = ("\"%s\"" % userpass).encode('utf-16-le') - invalid_utf16 = "\"thatsAcomplPASSiX\"".encode('utf-16-le') - userpass = "thatsAcomplPASS2x" - creds.set_password(userpass) - new_utf16 = ("\"%s\"" % userpass).encode('utf-16-le') + # The correct password after doing the unlock other_ldb.modify_ldif(""" dn: """ + userdn + """ changetype: modify delete: unicodePwd -unicodePwd:: """ + base64.b64encode(old_utf16) + """ +unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """ add: unicodePwd -unicodePwd:: """ + base64.b64encode(new_utf16) + """ +unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2x\"".encode('utf-16-le')) + """ """) + userpass = "thatsAcomplPASS2x" + creds.set_password(userpass) res = self._check_account(userdn, badPwdCount=0, badPasswordTime=badPasswordTime, logonCount=logonCount, + lockoutTime=0, lastLogon=lastLogon, lastLogonTimestamp=lastLogonTimestamp, - lockoutTime=0, userAccountControl= dsdb.UF_NORMAL_ACCOUNT, msDSUserAccountControlComputed=0) @@ -1307,10 +1342,10 @@ unicodePwd:: """ + base64.b64encode(new_utf16) + """ other_ldb.modify_ldif(""" dn: """ + userdn + """ changetype: modify -delete: unicodePwd -unicodePwd:: """ + base64.b64encode(invalid_utf16) + """ -add: unicodePwd -unicodePwd:: """ + base64.b64encode(new_utf16) + """ +delete: userPassword +userPassword: thatsAcomplPASS1xyz +add: userPassword +userPassword: thatsAcomplPASS2XYZ """) self.fail() except LdbError, (num, msg): @@ -1321,9 +1356,9 @@ unicodePwd:: """ + base64.b64encode(new_utf16) + """ badPwdCount=1, badPasswordTime=("greater", badPasswordTime), logonCount=logonCount, + lockoutTime=0, lastLogon=lastLogon, lastLogonTimestamp=lastLogonTimestamp, - lockoutTime=0, userAccountControl= dsdb.UF_NORMAL_ACCOUNT, msDSUserAccountControlComputed=0) @@ -1334,10 +1369,10 @@ unicodePwd:: """ + base64.b64encode(new_utf16) + """ other_ldb.modify_ldif(""" dn: """ + userdn + """ changetype: modify -delete: unicodePwd -unicodePwd:: """ + base64.b64encode(invalid_utf16) + """ -add: unicodePwd -unicodePwd:: """ + base64.b64encode(new_utf16) + """ +delete: userPassword +userPassword: thatsAcomplPASS1xyz +add: userPassword +userPassword: thatsAcomplPASS2XYZ """) self.fail() except LdbError, (num, msg): @@ -1348,20 +1383,18 @@ unicodePwd:: """ + base64.b64encode(new_utf16) + """ badPwdCount=2, badPasswordTime=("greater", badPasswordTime), logonCount=logonCount, + lockoutTime=0, lastLogon=lastLogon, lastLogonTimestamp=lastLogonTimestamp, - lockoutTime=0, userAccountControl= dsdb.UF_NORMAL_ACCOUNT, msDSUserAccountControlComputed=0) badPasswordTime = int(res[0]["badPasswordTime"][0]) - # SAMR doesn't have any impact if dsdb.UF_LOCKOUT isn't present. - # It doesn't reset "badPwdCount" = 0. - self._reset_samr(res) + self._reset_ldap_lockoutTime(res) res = self._check_account(userdn, - badPwdCount=2, + badPwdCount=0, badPasswordTime=badPasswordTime, logonCount=logonCount, lastLogon=lastLogon, @@ -1371,89 +1404,50 @@ unicodePwd:: """ + base64.b64encode(new_utf16) + """ dsdb.UF_NORMAL_ACCOUNT, msDSUserAccountControlComputed=0) - # Wrong old password - try: - other_ldb.modify_ldif(""" -dn: """ + userdn + """ -changetype: modify -delete: unicodePwd -unicodePwd:: """ + base64.b64encode(invalid_utf16) + """ -add: unicodePwd -unicodePwd:: """ + base64.b64encode(new_utf16) + """ -""") - self.fail() - except LdbError, (num, msg): - self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) - self.assertTrue('00000056' in msg, msg) - - res = self._check_account(userdn, - badPwdCount=3, - badPasswordTime=("greater", badPasswordTime), - logonCount=logonCount, - lastLogon=lastLogon, - lastLogonTimestamp=lastLogonTimestamp, - lockoutTime=("greater", badPasswordTime), - userAccountControl= - dsdb.UF_NORMAL_ACCOUNT, - msDSUserAccountControlComputed=dsdb.UF_LOCKOUT) - badPasswordTime = int(res[0]["badPasswordTime"][0]) - lockoutTime = int(res[0]["lockoutTime"][0]) - - time.sleep(self.account_lockout_duration + 1) + def test_userPassword_lockout_with_clear_change_krb5_ldap_userAccountControl(self): + self._test_userPassword_lockout_with_clear_change(self.lockout1krb5_creds, + self.lockout2krb5_ldb, + "ldap_userAccountControl") - res = self._check_account(userdn, - badPwdCount=3, effective_bad_password_count=0, - badPasswordTime=badPasswordTime, - logonCount=logonCount, - lastLogon=lastLogon, - lastLogonTimestamp=lastLogonTimestamp, - lockoutTime=lockoutTime, - userAccountControl= - dsdb.UF_NORMAL_ACCOUNT, - msDSUserAccountControlComputed=0) + def test_userPassword_lockout_with_clear_change_krb5_ldap_lockoutTime(self): + self._test_userPassword_lockout_with_clear_change(self.lockout1krb5_creds, + self.lockout2krb5_ldb, + "ldap_lockoutTime") - # SAMR doesn't have any impact if dsdb.UF_LOCKOUT isn't present. - # It doesn't reset "lockoutTime" = 0 and doesn't - # reset "badPwdCount" = 0. - self._reset_samr(res) + def test_userPassword_lockout_with_clear_change_krb5_samr(self): + self._test_userPassword_lockout_with_clear_change(self.lockout1krb5_creds, + self.lockout2krb5_ldb, + "samr") - res = self._check_account(userdn, - badPwdCount=3, effective_bad_password_count=0, - badPasswordTime=badPasswordTime, - logonCount=logonCount, - lockoutTime=lockoutTime, - lastLogon=lastLogon, - lastLogonTimestamp=lastLogonTimestamp, - userAccountControl= - dsdb.UF_NORMAL_ACCOUNT, - msDSUserAccountControlComputed=0) + def test_userPassword_lockout_with_clear_change_ntlm_ldap_userAccountControl(self): + self._test_userPassword_lockout_with_clear_change(self.lockout1ntlm_creds, + self.lockout2ntlm_ldb, + "ldap_userAccountControl", + initial_lastlogon_relation='greater') - def test_unicodePwd_lockout_with_clear_change_krb5(self): - self._test_unicodePwd_lockout_with_clear_change(self.lockout1krb5_creds, - self.lockout2krb5_ldb) + def test_userPassword_lockout_with_clear_change_ntlm_ldap_lockoutTime(self): + self._test_userPassword_lockout_with_clear_change(self.lockout1ntlm_creds, + self.lockout2ntlm_ldb, + "ldap_lockoutTime", + initial_lastlogon_relation='greater') - def test_unicodePwd_lockout_with_clear_change_ntlm(self): - self._test_unicodePwd_lockout_with_clear_change(self.lockout1ntlm_creds, - self.lockout2ntlm_ldb, - initial_logoncount_relation="equal") + def test_userPassword_lockout_with_clear_change_ntlm_samr(self): + self._test_userPassword_lockout_with_clear_change(self.lockout1ntlm_creds, + self.lockout2ntlm_ldb, + "samr", + initial_lastlogon_relation='greater') - def _test_login_lockout(self, creds): + def _test_unicodePwd_lockout_with_clear_change(self, creds, other_ldb, + initial_logoncount_relation=None): + print "Performs a password cleartext change operation on 'unicodePwd'" username = creds.get_username() userpass = creds.get_password() userdn = "cn=%s,cn=users,%s" % (username, self.base_dn) - - use_kerberos = creds.get_kerberos_state() - # This unlocks by waiting for account_lockout_duration - if use_kerberos == MUST_USE_KERBEROS: - logoncount_relation = 'greater' - lastlogon_relation = 'greater' - print "Performs a lockout attempt against LDAP using Kerberos" + if initial_logoncount_relation is not None: + logoncount_relation = initial_logoncount_relation else: - logoncount_relation = 'equal' - lastlogon_relation = 'equal' - print "Performs a lockout attempt against LDAP using NTLM" + logoncount_relation = "greater" - # Change password on a connection as another user res = self._check_account(userdn, badPwdCount=0, badPasswordTime=("greater", 0), @@ -1466,24 +1460,26 @@ unicodePwd:: """ + base64.b64encode(new_utf16) + """ badPasswordTime = int(res[0]["badPasswordTime"][0]) logonCount = int(res[0]["logonCount"][0]) lastLogon = int(res[0]["lastLogon"][0]) - firstLogon = lastLogon lastLogonTimestamp = int(res[0]["lastLogonTimestamp"][0]) - print firstLogon - print lastLogonTimestamp - - - self.assertGreater(lastLogon, badPasswordTime) + self.assertGreater(lastLogonTimestamp, badPasswordTime) self.assertGreaterEqual(lastLogon, lastLogonTimestamp) - # Open a second LDB connection with the user credentials. Use the - # command line credentials for informations like the domain, the realm - # and the workstation. - creds_lockout = self.insta_creds(creds) - - # The wrong password - creds_lockout.set_password("thatsAcomplPASS1x") + # Change password on a connection as another user - self.assertLoginFailure(host_url, creds_lockout, lp) + # Wrong old password + try: + other_ldb.modify_ldif(""" +dn: """ + userdn + """ +changetype: modify +delete: unicodePwd +unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS1x\"".encode('utf-16-le')) + """ +add: unicodePwd +unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """ +""") + self.fail() + except LdbError, (num, msg): + self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) + self.assertTrue('00000056' in msg, msg) res = self._check_account(userdn, badPwdCount=1, @@ -1493,40 +1489,52 @@ unicodePwd:: """ + base64.b64encode(new_utf16) + """ lastLogonTimestamp=lastLogonTimestamp, userAccountControl= dsdb.UF_NORMAL_ACCOUNT, - msDSUserAccountControlComputed=0, - msg='lastlogontimestamp with wrong password') + msDSUserAccountControlComputed=0) badPasswordTime = int(res[0]["badPasswordTime"][0]) # Correct old password - creds_lockout.set_password(userpass) + old_utf16 = ("\"%s\"" % userpass).encode('utf-16-le') + invalid_utf16 = "\"thatsAcomplPASSX\"".encode('utf-16-le') + userpass = "thatsAcomplPASS2" + creds.set_password(userpass) + new_utf16 = ("\"%s\"" % userpass).encode('utf-16-le') - ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp) + other_ldb.modify_ldif(""" +dn: """ + userdn + """ +changetype: modify +delete: unicodePwd +unicodePwd:: """ + base64.b64encode(old_utf16) + """ +add: unicodePwd +unicodePwd:: """ + base64.b64encode(new_utf16) + """ +""") - # lastLogonTimestamp should not change - # lastLogon increases if badPwdCount is non-zero (!) res = self._check_account(userdn, - badPwdCount=0, + badPwdCount=1, badPasswordTime=badPasswordTime, - logonCount=(logoncount_relation, logonCount), - lastLogon=('greater', lastLogon), + logonCount=logonCount, + lastLogon=lastLogon, lastLogonTimestamp=lastLogonTimestamp, userAccountControl= dsdb.UF_NORMAL_ACCOUNT, - msDSUserAccountControlComputed=0, - msg='LLTimestamp is updated to lastlogon') - - logonCount = int(res[0]["logonCount"][0]) - lastLogon = int(res[0]["lastLogon"][0]) - self.assertGreater(lastLogon, badPasswordTime) - self.assertGreaterEqual(lastLogon, lastLogonTimestamp) - - # The wrong password - creds_lockout.set_password("thatsAcomplPASS1x") + msDSUserAccountControlComputed=0) - self.assertLoginFailure(host_url, creds_lockout, lp) + # Wrong old password + try: + other_ldb.modify_ldif(""" +dn: """ + userdn + """ +changetype: modify +delete: unicodePwd +unicodePwd:: """ + base64.b64encode(old_utf16) + """ +add: unicodePwd +unicodePwd:: """ + base64.b64encode(new_utf16) + """ +""") + self.fail() + except LdbError, (num, msg): + self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) + self.assertTrue('00000056' in msg, msg) res = self._check_account(userdn, - badPwdCount=1, + badPwdCount=2, badPasswordTime=("greater", badPasswordTime), logonCount=logonCount, lastLogon=lastLogon, @@ -1536,39 +1544,39 @@ unicodePwd:: """ + base64.b64encode(new_utf16) + """ msDSUserAccountControlComputed=0) badPasswordTime = int(res[0]["badPasswordTime"][0]) - # The wrong password - creds_lockout.set_password("thatsAcomplPASS1x") - - try: - ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp) - self.fail() - - except LdbError, (num, msg): - self.assertEquals(num, ERR_INVALID_CREDENTIALS) + # SAMR doesn't have any impact if dsdb.UF_LOCKOUT isn't present. + # It doesn't create "lockoutTime" = 0 and doesn't + # reset "badPwdCount" = 0. + self._reset_samr(res) res = self._check_account(userdn, badPwdCount=2, - badPasswordTime=("greater", badPasswordTime), + badPasswordTime=badPasswordTime, logonCount=logonCount, lastLogon=lastLogon, lastLogonTimestamp=lastLogonTimestamp, userAccountControl= dsdb.UF_NORMAL_ACCOUNT, msDSUserAccountControlComputed=0) - badPasswordTime = int(res[0]["badPasswordTime"][0]) print "two failed password change" - # The wrong password - creds_lockout.set_password("thatsAcomplPASS1x") - + # Wrong old password try: - ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp) + other_ldb.modify_ldif(""" +dn: """ + userdn + """ +changetype: modify +delete: unicodePwd +unicodePwd:: """ + base64.b64encode(invalid_utf16) + """ +add: unicodePwd +unicodePwd:: """ + base64.b64encode(new_utf16) + """ +""") self.fail() - except LdbError, (num, msg): - self.assertEquals(num, ERR_INVALID_CREDENTIALS) + self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) + self.assertTrue('00000056' in msg, msg) + # this is strange, why do we have lockoutTime=badPasswordTime here? res = self._check_account(userdn, badPwdCount=3, badPasswordTime=("greater", badPasswordTime), @@ -1582,13 +1590,20 @@ unicodePwd:: """ + base64.b64encode(new_utf16) + """ badPasswordTime = int(res[0]["badPasswordTime"][0]) lockoutTime = int(res[0]["lockoutTime"][0]) - # The wrong password - creds_lockout.set_password("thatsAcomplPASS1x") + # Wrong old password try: - ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp) + other_ldb.modify_ldif(""" +dn: """ + userdn + """ +changetype: modify +delete: unicodePwd +unicodePwd:: """ + base64.b64encode(invalid_utf16) + """ +add: unicodePwd +unicodePwd:: """ + base64.b64encode(new_utf16) + """ +""") self.fail() except LdbError, (num, msg): - self.assertEquals(num, ERR_INVALID_CREDENTIALS) + self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) + self.assertTrue('00000775' in msg, msg) res = self._check_account(userdn, badPwdCount=3, @@ -1601,13 +1616,20 @@ unicodePwd:: """ + base64.b64encode(new_utf16) + """ dsdb.UF_NORMAL_ACCOUNT, msDSUserAccountControlComputed=dsdb.UF_LOCKOUT) - # The wrong password - creds_lockout.set_password("thatsAcomplPASS1x") + # Wrong old password try: - ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp) + other_ldb.modify_ldif(""" +dn: """ + userdn + """ +changetype: modify +delete: unicodePwd +unicodePwd:: """ + base64.b64encode(invalid_utf16) + """ +add: unicodePwd +unicodePwd:: """ + base64.b64encode(new_utf16) + """ +""") self.fail() except LdbError, (num, msg): - self.assertEquals(num, ERR_INVALID_CREDENTIALS) + self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) + self.assertTrue('00000775' in msg, msg) res = self._check_account(userdn, badPwdCount=3, @@ -1620,13 +1642,20 @@ unicodePwd:: """ + base64.b64encode(new_utf16) + """ dsdb.UF_NORMAL_ACCOUNT, msDSUserAccountControlComputed=dsdb.UF_LOCKOUT) - # The correct password, but we are locked out - creds_lockout.set_password(userpass) try: - ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp) + # Correct old password + other_ldb.modify_ldif(""" +dn: """ + userdn + """ +changetype: modify +delete: unicodePwd +unicodePwd:: """ + base64.b64encode(new_utf16) + """ +add: unicodePwd +unicodePwd:: """ + base64.b64encode(invalid_utf16) + """ +""") self.fail() except LdbError, (num, msg): - self.assertEquals(num, ERR_INVALID_CREDENTIALS) + self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) + self.assertTrue('00000775' in msg, msg) res = self._check_account(userdn, badPwdCount=3, @@ -1639,213 +1668,187 @@ unicodePwd:: """ + base64.b64encode(new_utf16) + """ dsdb.UF_NORMAL_ACCOUNT, msDSUserAccountControlComputed=dsdb.UF_LOCKOUT) - # wait for the lockout to end - time.sleep(self.account_lockout_duration + 1) - print self.account_lockout_duration + 1 + # Now reset the lockout, by removing ACB_AUTOLOCK (which removes the lock, despite being a generated attribute) + self._reset_samr(res); res = self._check_account(userdn, - badPwdCount=3, effective_bad_password_count=0, + badPwdCount=0, badPasswordTime=badPasswordTime, logonCount=logonCount, - lockoutTime=lockoutTime, lastLogon=lastLogon, lastLogonTimestamp=lastLogonTimestamp, + lockoutTime=0, userAccountControl= dsdb.UF_NORMAL_ACCOUNT, msDSUserAccountControlComputed=0) - # The correct password after letting the timeout expire - - creds_lockout.set_password(userpass) - - creds_lockout2 = self.insta_creds(creds_lockout) + # Correct old password + old_utf16 = ("\"%s\"" % userpass).encode('utf-16-le') + invalid_utf16 = "\"thatsAcomplPASSiX\"".encode('utf-16-le') + userpass = "thatsAcomplPASS2x" + creds.set_password(userpass) + new_utf16 = ("\"%s\"" % userpass).encode('utf-16-le') - ldb_lockout = SamDB(url=host_url, credentials=creds_lockout2, lp=lp) - time.sleep(3) + other_ldb.modify_ldif(""" +dn: """ + userdn + """ +changetype: modify +delete: unicodePwd +unicodePwd:: """ + base64.b64encode(old_utf16) + """ +add: unicodePwd +unicodePwd:: """ + base64.b64encode(new_utf16) + """ +""") res = self._check_account(userdn, badPwdCount=0, badPasswordTime=badPasswordTime, - logonCount=(logoncount_relation, logonCount), - lastLogon=(lastlogon_relation, lastLogon), + logonCount=logonCount, + lastLogon=lastLogon, lastLogonTimestamp=lastLogonTimestamp, lockoutTime=0, userAccountControl= dsdb.UF_NORMAL_ACCOUNT, - msDSUserAccountControlComputed=0, - msg="lastLogon is way off") - - logonCount = int(res[0]["logonCount"][0]) - lastLogon = int(res[0]["lastLogon"][0]) + msDSUserAccountControlComputed=0) - # The wrong password - creds_lockout.set_password("thatsAcomplPASS1x") + # Wrong old password try: - ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp) + other_ldb.modify_ldif(""" +dn: """ + userdn + """ +changetype: modify +delete: unicodePwd +unicodePwd:: """ + base64.b64encode(invalid_utf16) + """ +add: unicodePwd +unicodePwd:: """ + base64.b64encode(new_utf16) + """ +""") self.fail() except LdbError, (num, msg): - self.assertEquals(num, ERR_INVALID_CREDENTIALS) + self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) + self.assertTrue('00000056' in msg, msg) res = self._check_account(userdn, badPwdCount=1, badPasswordTime=("greater", badPasswordTime), logonCount=logonCount, - lockoutTime=0, lastLogon=lastLogon, lastLogonTimestamp=lastLogonTimestamp, + lockoutTime=0, userAccountControl= dsdb.UF_NORMAL_ACCOUNT, msDSUserAccountControlComputed=0) badPasswordTime = int(res[0]["badPasswordTime"][0]) - # The wrong password - creds_lockout.set_password("thatsAcomplPASS1x") + # Wrong old password try: - ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp) + other_ldb.modify_ldif(""" +dn: """ + userdn + """ +changetype: modify +delete: unicodePwd +unicodePwd:: """ + base64.b64encode(invalid_utf16) + """ +add: unicodePwd +unicodePwd:: """ + base64.b64encode(new_utf16) + """ +""") self.fail() except LdbError, (num, msg): - self.assertEquals(num, ERR_INVALID_CREDENTIALS) + self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) + self.assertTrue('00000056' in msg, msg) res = self._check_account(userdn, badPwdCount=2, badPasswordTime=("greater", badPasswordTime), logonCount=logonCount, - lockoutTime=0, lastLogon=lastLogon, lastLogonTimestamp=lastLogonTimestamp, + lockoutTime=0, userAccountControl= dsdb.UF_NORMAL_ACCOUNT, msDSUserAccountControlComputed=0) badPasswordTime = int(res[0]["badPasswordTime"][0]) - time.sleep(self.lockout_observation_window + 1) + # SAMR doesn't have any impact if dsdb.UF_LOCKOUT isn't present. + # It doesn't reset "badPwdCount" = 0. + self._reset_samr(res) res = self._check_account(userdn, - badPwdCount=2, effective_bad_password_count=0, + badPwdCount=2, badPasswordTime=badPasswordTime, logonCount=logonCount, - lockoutTime=0, lastLogon=lastLogon, lastLogonTimestamp=lastLogonTimestamp, + lockoutTime=0, userAccountControl= dsdb.UF_NORMAL_ACCOUNT, msDSUserAccountControlComputed=0) - # The wrong password - creds_lockout.set_password("thatsAcomplPASS1x") + # Wrong old password try: - ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp) + other_ldb.modify_ldif(""" +dn: """ + userdn + """ +changetype: modify +delete: unicodePwd +unicodePwd:: """ + base64.b64encode(invalid_utf16) + """ +add: unicodePwd +unicodePwd:: """ + base64.b64encode(new_utf16) + """ +""") self.fail() except LdbError, (num, msg): - self.assertEquals(num, ERR_INVALID_CREDENTIALS) + self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) + self.assertTrue('00000056' in msg, msg) res = self._check_account(userdn, - badPwdCount=1, + badPwdCount=3, badPasswordTime=("greater", badPasswordTime), logonCount=logonCount, - lockoutTime=0, lastLogon=lastLogon, lastLogonTimestamp=lastLogonTimestamp, + lockoutTime=("greater", badPasswordTime), userAccountControl= dsdb.UF_NORMAL_ACCOUNT, - msDSUserAccountControlComputed=0) + msDSUserAccountControlComputed=dsdb.UF_LOCKOUT) badPasswordTime = int(res[0]["badPasswordTime"][0]) + lockoutTime = int(res[0]["lockoutTime"][0]) - # The correct password without letting the timeout expire - creds_lockout.set_password(userpass) - ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp) + time.sleep(self.account_lockout_duration + 1) res = self._check_account(userdn, - badPwdCount=0, + badPwdCount=3, effective_bad_password_count=0, badPasswordTime=badPasswordTime, - logonCount=(logoncount_relation, logonCount), - lockoutTime=0, - lastLogon=("greater", lastLogon), + logonCount=logonCount, + lastLogon=lastLogon, lastLogonTimestamp=lastLogonTimestamp, + lockoutTime=lockoutTime, userAccountControl= dsdb.UF_NORMAL_ACCOUNT, msDSUserAccountControlComputed=0) - - def test_login_lockout_krb5(self): - self._test_login_lockout(self.lockout1krb5_creds) - - def test_login_lockout_ntlm(self): - self._test_login_lockout(self.lockout1ntlm_creds) - - def _test_multiple_logon(self, creds): - # Test the happy case in which a user logs on correctly, then - # logs on correctly again, so that the bad password and - # lockout times are both zero the second time. The lastlogon - # time should increase. - - # Open a second LDB connection with the user credentials. Use the - # command line credentials for informations like the domain, the realm - # and the workstation. - username = creds.get_username() - userdn = "cn=%s,cn=users,%s" % (username, self.base_dn) - - use_kerberos = creds.get_kerberos_state() - if use_kerberos == MUST_USE_KERBEROS: - print "Testing multiple logon with Kerberos" - logoncount_relation = 'greater' - lastlogon_relation = 'greater' - else: - print "Testing multiple logon with NTLM" - logoncount_relation = 'equal' - lastlogon_relation = 'equal' - - SamDB(url=host_url, credentials=self.insta_creds(creds), lp=lp) - - res = self._check_account(userdn, - badPwdCount=0, - badPasswordTime=("greater", 0), - logonCount=(logoncount_relation, 0), - lastLogon=("greater", 0), - lastLogonTimestamp=("greater", 0), - userAccountControl= - dsdb.UF_NORMAL_ACCOUNT, - msDSUserAccountControlComputed=0) - badPasswordTime = int(res[0]["badPasswordTime"][0]) - logonCount = int(res[0]["logonCount"][0]) - lastLogon = int(res[0]["lastLogon"][0]) - lastLogonTimestamp = int(res[0]["lastLogonTimestamp"][0]) - firstLogon = lastLogon - print "last logon is %d" % lastLogon - self.assertGreater(lastLogon, badPasswordTime) - self.assertGreaterEqual(lastLogon, lastLogonTimestamp) - - time.sleep(1) - SamDB(url=host_url, credentials=self.insta_creds(creds), lp=lp) + # SAMR doesn't have any impact if dsdb.UF_LOCKOUT isn't present. + # It doesn't reset "lockoutTime" = 0 and doesn't + # reset "badPwdCount" = 0. + self._reset_samr(res) res = self._check_account(userdn, - badPwdCount=0, + badPwdCount=3, effective_bad_password_count=0, badPasswordTime=badPasswordTime, - logonCount=(logoncount_relation, logonCount), - lastLogon=(lastlogon_relation, lastLogon), + logonCount=logonCount, + lockoutTime=lockoutTime, + lastLogon=lastLogon, lastLogonTimestamp=lastLogonTimestamp, userAccountControl= - dsdb.UF_NORMAL_ACCOUNT, - msDSUserAccountControlComputed=0, - msg=("second logon, firstlogon was %s" % - firstLogon)) - + dsdb.UF_NORMAL_ACCOUNT, + msDSUserAccountControlComputed=0) - lastLogon = int(res[0]["lastLogon"][0]) + def test_unicodePwd_lockout_with_clear_change_krb5(self): + self._test_unicodePwd_lockout_with_clear_change(self.lockout1krb5_creds, + self.lockout2krb5_ldb) - time.sleep(1) + def test_unicodePwd_lockout_with_clear_change_ntlm(self): + self._test_unicodePwd_lockout_with_clear_change(self.lockout1ntlm_creds, + self.lockout2ntlm_ldb, + initial_logoncount_relation="equal") - SamDB(url=host_url, credentials=self.insta_creds(creds), lp=lp) + def test_login_lockout_krb5(self): + self._test_login_lockout(self.lockout1krb5_creds) - res = self._check_account(userdn, - badPwdCount=0, - badPasswordTime=badPasswordTime, - logonCount=(logoncount_relation, logonCount), - lastLogon=(lastlogon_relation, lastLogon), - lastLogonTimestamp=lastLogonTimestamp, - userAccountControl= - dsdb.UF_NORMAL_ACCOUNT, - msDSUserAccountControlComputed=0) + def test_login_lockout_ntlm(self): + self._test_login_lockout(self.lockout1ntlm_creds) def test_multiple_logon_krb5(self): self._test_multiple_logon(self.lockout1krb5_creds) @@ -1854,9 +1857,6 @@ unicodePwd:: """ + base64.b64encode(new_utf16) + """ self._test_multiple_logon(self.lockout1ntlm_creds) - def tearDown(self): - super(PasswordTests, self).tearDown() - host_url = "ldap://%s" % host TestProgram(module=__name__, opts=subunitopts) -- 1.9.1 From b9119a752104187a2471d317024e4ef1aa69d353 Mon Sep 17 00:00:00 2001 From: Garming Sam Date: Thu, 6 Apr 2017 15:53:25 +1200 Subject: [PATCH 10/24] password_lockout: Factor out a base testcase This allows it to be used for the RODC testing. Signed-off-by: Garming Sam --- source4/dsdb/tests/python/password_lockout.py | 901 +------------------- source4/dsdb/tests/python/password_lockout_base.py | 908 +++++++++++++++++++++ 2 files changed, 917 insertions(+), 892 deletions(-) create mode 100644 source4/dsdb/tests/python/password_lockout_base.py diff --git a/source4/dsdb/tests/python/password_lockout.py b/source4/dsdb/tests/python/password_lockout.py index 6bf0d8f..2e345d9 100755 --- a/source4/dsdb/tests/python/password_lockout.py +++ b/source4/dsdb/tests/python/password_lockout.py @@ -53,905 +53,22 @@ host = args[0] lp = sambaopts.get_loadparm() global_creds = credopts.get_credentials(lp) -# Force an encrypted connection -global_creds.set_gensec_features(global_creds.get_gensec_features() | - gensec.FEATURE_SEAL) - -template_creds = Credentials() -template_creds.set_username("testuser") -template_creds.set_password("thatsAcomplPASS1") -template_creds.set_domain(global_creds.get_domain()) -template_creds.set_realm(global_creds.get_realm()) -template_creds.set_workstation(global_creds.get_workstation()) -template_creds.set_gensec_features(global_creds.get_gensec_features()) -template_creds.set_kerberos_state(global_creds.get_kerberos_state()) +import password_lockout_base # # Tests start here # -class BasePasswordTestCase(samba.tests.TestCase): - def _open_samr_user(self, res): - self.assertTrue("objectSid" in res[0]) - - (domain_sid, rid) = ndr_unpack(security.dom_sid, res[0]["objectSid"][0]).split() - self.assertEquals(self.domain_sid, domain_sid) - - return self.samr.OpenUser(self.samr_domain, security.SEC_FLAG_MAXIMUM_ALLOWED, rid) - - def _reset_samr(self, res): - - # Now reset the lockout, by removing ACB_AUTOLOCK (which removes the lock, despite being a generated attribute) - samr_user = self._open_samr_user(res) - acb_info = self.samr.QueryUserInfo(samr_user, 16) - acb_info.acct_flags &= ~samr.ACB_AUTOLOCK - self.samr.SetUserInfo(samr_user, 16, acb_info) - self.samr.Close(samr_user) - - def _check_attribute(self, res, name, value): - if value is None: - self.assertTrue(name not in res[0], - msg="attr[%s]=%r on dn[%s]" % - (name, res[0], res[0].dn)) - return - - if isinstance(value, tuple): - (mode, value) = value - else: - mode = "equal" - - 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)) - self.assertTrue(len(res[0][name]) == 1, - 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": - 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] (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] (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, - logonCount=None, - lastLogon=None, - lastLogonTimestamp=None, - lockoutTime=None, - userAccountControl=None, - msDSUserAccountControlComputed=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", - "logonCount", - "lockoutTime", - "userAccountControl", - "msDS-User-Account-Control-Computed" - ] - - # in order to prevent some time resolution problems we sleep for - # 10 micro second - time.sleep(0.01) - - res = self.ldb.search(dn, scope=SCOPE_BASE, attrs=attrs) - self.assertTrue(len(res) == 1) - self._check_attribute(res, "badPwdCount", badPwdCount) - self._check_attribute(res, "badPasswordTime", badPasswordTime) - self._check_attribute(res, "logonCount", logonCount) - self._check_attribute(res, "lastLogon", lastLogon) - self._check_attribute(res, "lastLogonTimestamp", lastLogonTimestamp) - self._check_attribute(res, "lockoutTime", lockoutTime) - self._check_attribute(res, "userAccountControl", userAccountControl) - self._check_attribute(res, "msDS-User-Account-Control-Computed", - msDSUserAccountControlComputed) - - lastLogon = int(res[0]["lastLogon"][0]) - logonCount = int(res[0]["logonCount"][0]) - - samr_user = self._open_samr_user(res) - uinfo3 = self.samr.QueryUserInfo(samr_user, 3) - uinfo5 = self.samr.QueryUserInfo(samr_user, 5) - uinfo16 = self.samr.QueryUserInfo(samr_user, 16) - uinfo21 = self.samr.QueryUserInfo(samr_user, 21) - self.samr.Close(samr_user) - - expected_acb_info = 0 - if userAccountControl & dsdb.UF_NORMAL_ACCOUNT: - expected_acb_info |= samr.ACB_NORMAL - if userAccountControl & dsdb.UF_ACCOUNTDISABLE: - expected_acb_info |= samr.ACB_DISABLED - if userAccountControl & dsdb.UF_PASSWD_NOTREQD: - expected_acb_info |= samr.ACB_PWNOTREQ - if msDSUserAccountControlComputed & dsdb.UF_LOCKOUT: - expected_acb_info |= samr.ACB_AUTOLOCK - if msDSUserAccountControlComputed & dsdb.UF_PASSWORD_EXPIRED: - expected_acb_info |= samr.ACB_PW_EXPIRED - - expected_bad_password_count = 0 - if badPwdCount is not None: - expected_bad_password_count = badPwdCount - if effective_bad_password_count is None: - effective_bad_password_count = expected_bad_password_count - - self.assertEquals(uinfo3.acct_flags, expected_acb_info) - self.assertEquals(uinfo3.bad_password_count, expected_bad_password_count) - self.assertEquals(uinfo3.last_logon, lastLogon) - self.assertEquals(uinfo3.logon_count, logonCount) - - self.assertEquals(uinfo5.acct_flags, expected_acb_info) - self.assertEquals(uinfo5.bad_password_count, effective_bad_password_count) - self.assertEquals(uinfo5.last_logon, lastLogon) - self.assertEquals(uinfo5.logon_count, logonCount) - - self.assertEquals(uinfo16.acct_flags, expected_acb_info) - - self.assertEquals(uinfo21.acct_flags, expected_acb_info) - self.assertEquals(uinfo21.bad_password_count, effective_bad_password_count) - self.assertEquals(uinfo21.last_logon, lastLogon) - self.assertEquals(uinfo21.logon_count, logonCount) - - # check LDAP again and make sure the samr.QueryUserInfo - # doesn't have any impact. - res2 = self.ldb.search(dn, scope=SCOPE_BASE, attrs=attrs) - self.assertEquals(res[0], res2[0]) - - # in order to prevent some time resolution problems we sleep for - # 10 micro second - time.sleep(0.01) - return res - - def _readd_user(self, creds, lockOutObservationWindow=0): - username = creds.get_username() - userpass = creds.get_password() - userdn = "cn=%s,cn=users,%s" % (username, self.base_dn) - - use_kerberos = creds.get_kerberos_state() - if use_kerberos == MUST_USE_KERBEROS: - logoncount_relation = 'greater' - lastlogon_relation = 'greater' - else: - logoncount_relation = 'equal' - if lockOutObservationWindow == 0: - lastlogon_relation = 'greater' - else: - lastlogon_relation = 'equal' - - delete_force(self.ldb, userdn) - self.ldb.add({ - "dn": userdn, - "objectclass": "user", - "sAMAccountName": username}) - - self.addCleanup(delete_force, self.ldb, userdn) - - res = self._check_account(userdn, - badPwdCount=0, - badPasswordTime=0, - logonCount=0, - lastLogon=0, - lastLogonTimestamp=('absent', None), - userAccountControl= - dsdb.UF_NORMAL_ACCOUNT | - dsdb.UF_ACCOUNTDISABLE | - dsdb.UF_PASSWD_NOTREQD, - msDSUserAccountControlComputed= - dsdb.UF_PASSWORD_EXPIRED) - - # SAMR doesn't have any impact if dsdb.UF_LOCKOUT isn't present. - # It doesn't create "lockoutTime" = 0. - self._reset_samr(res) - - res = self._check_account(userdn, - badPwdCount=0, - badPasswordTime=0, - logonCount=0, - lastLogon=0, - lastLogonTimestamp=('absent', None), - userAccountControl= - dsdb.UF_NORMAL_ACCOUNT | - dsdb.UF_ACCOUNTDISABLE | - dsdb.UF_PASSWD_NOTREQD, - msDSUserAccountControlComputed= - dsdb.UF_PASSWORD_EXPIRED) - - # Tests a password change when we don't have any password yet with a - # wrong old password - try: - self.ldb.modify_ldif(""" -dn: """ + userdn + """ -changetype: modify -delete: userPassword -userPassword: noPassword -add: userPassword -userPassword: thatsAcomplPASS2 -""") - self.fail() - except LdbError, (num, msg): - self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) - # Windows (2008 at least) seems to have some small bug here: it - # returns "0000056A" on longer (always wrong) previous passwords. - self.assertTrue('00000056' in msg, msg) - - res = self._check_account(userdn, - badPwdCount=1, - badPasswordTime=("greater", 0), - logonCount=0, - lastLogon=0, - lastLogonTimestamp=('absent', None), - userAccountControl= - dsdb.UF_NORMAL_ACCOUNT | - dsdb.UF_ACCOUNTDISABLE | - dsdb.UF_PASSWD_NOTREQD, - msDSUserAccountControlComputed= - dsdb.UF_PASSWORD_EXPIRED) - badPwdCount = int(res[0]["badPwdCount"][0]) - badPasswordTime = int(res[0]["badPasswordTime"][0]) - - # Sets the initial user password with a "special" password change - # I think that this internally is a password set operation and it can - # only be performed by someone which has password set privileges on the - # account (at least in s4 we do handle it like that). - self.ldb.modify_ldif(""" -dn: """ + userdn + """ -changetype: modify -delete: userPassword -add: userPassword -userPassword: """ + userpass + """ -""") - - res = self._check_account(userdn, - badPwdCount=badPwdCount, - badPasswordTime=badPasswordTime, - logonCount=0, - lastLogon=0, - lastLogonTimestamp=('absent', None), - userAccountControl= - dsdb.UF_NORMAL_ACCOUNT | - dsdb.UF_ACCOUNTDISABLE | - dsdb.UF_PASSWD_NOTREQD, - msDSUserAccountControlComputed=0) - - # Enables the user account - self.ldb.enable_account("(sAMAccountName=%s)" % username) - - res = self._check_account(userdn, - badPwdCount=badPwdCount, - badPasswordTime=badPasswordTime, - logonCount=0, - lastLogon=0, - lastLogonTimestamp=('absent', None), - userAccountControl= - dsdb.UF_NORMAL_ACCOUNT, - msDSUserAccountControlComputed=0) - if lockOutObservationWindow != 0: - time.sleep(lockOutObservationWindow + 1) - effective_bad_password_count = 0 - else: - effective_bad_password_count = badPwdCount - - res = self._check_account(userdn, - badPwdCount=badPwdCount, - effective_bad_password_count=effective_bad_password_count, - badPasswordTime=badPasswordTime, - logonCount=0, - lastLogon=0, - lastLogonTimestamp=('absent', None), - userAccountControl= - dsdb.UF_NORMAL_ACCOUNT, - msDSUserAccountControlComputed=0) - - ldb = SamDB(url=host_url, credentials=creds, lp=lp) - - if lockOutObservationWindow == 0: - badPwdCount = 0 - effective_bad_password_count = 0 - if use_kerberos == MUST_USE_KERBEROS: - badPwdCount = 0 - effective_bad_password_count = 0 - - res = self._check_account(userdn, - badPwdCount=badPwdCount, - effective_bad_password_count=effective_bad_password_count, - badPasswordTime=badPasswordTime, - logonCount=(logoncount_relation, 0), - lastLogon=(lastlogon_relation, 0), - lastLogonTimestamp=('greater', badPasswordTime), - userAccountControl= - dsdb.UF_NORMAL_ACCOUNT, - msDSUserAccountControlComputed=0) - - logonCount = int(res[0]["logonCount"][0]) - lastLogon = int(res[0]["lastLogon"][0]) - lastLogonTimestamp = int(res[0]["lastLogonTimestamp"][0]) - if lastlogon_relation == 'greater': - self.assertGreater(lastLogon, badPasswordTime) - self.assertGreaterEqual(lastLogon, lastLogonTimestamp) - - res = self._check_account(userdn, - badPwdCount=badPwdCount, - effective_bad_password_count=effective_bad_password_count, - badPasswordTime=badPasswordTime, - logonCount=logonCount, - lastLogon=lastLogon, - lastLogonTimestamp=lastLogonTimestamp, - userAccountControl= - dsdb.UF_NORMAL_ACCOUNT, - msDSUserAccountControlComputed=0) - return ldb - - def assertLoginFailure(self, url, creds, lp, errno=ERR_INVALID_CREDENTIALS): - try: - ldb = SamDB(url=url, credentials=creds, lp=lp) - self.fail("Login unexpectedly succeeded") - except LdbError, (num, msg): - if errno is not None: - self.assertEquals(num, errno, ("Login failed in the wrong way" - "(got err %d, expected %d)" % - (num, errno))) - +class PasswordTests(password_lockout_base.BasePasswordTestCase): def setUp(self): - super(BasePasswordTestCase, self).setUp() - - self.ldb = SamDB(url=host_url, session_info=system_session(lp), - credentials=global_creds, lp=lp) - - # Gets back the basedn - base_dn = self.ldb.domain_dn() - - # Gets back the configuration basedn - configuration_dn = self.ldb.get_config_basedn().get_linearized() - - # Get the old "dSHeuristics" if it was set - dsheuristics = self.ldb.get_dsheuristics() - - # Reset the "dSHeuristics" as they were before - self.addCleanup(self.ldb.set_dsheuristics, dsheuristics) - - res = self.ldb.search(base_dn, - scope=SCOPE_BASE, attrs=["lockoutDuration", "lockOutObservationWindow", "lockoutThreshold"]) - - if "lockoutDuration" in res[0]: - lockoutDuration = res[0]["lockoutDuration"][0] - else: - lockoutDuration = 0 - - if "lockoutObservationWindow" in res[0]: - lockoutObservationWindow = res[0]["lockoutObservationWindow"][0] - else: - lockoutObservationWindow = 0 - - if "lockoutThreshold" in res[0]: - lockoutThreshold = res[0]["lockoutThreshold"][0] - else: - lockoutTreshold = 0 - - self.addCleanup(self.ldb.modify_ldif, """ -dn: """ + base_dn + """ -changetype: modify -replace: lockoutDuration -lockoutDuration: """ + str(lockoutDuration) + """ -replace: lockoutObservationWindow -lockoutObservationWindow: """ + str(lockoutObservationWindow) + """ -replace: lockoutThreshold -lockoutThreshold: """ + str(lockoutThreshold) + """ -""") - - m = Message() - m.dn = Dn(self.ldb, base_dn) - - self.account_lockout_duration = 2 - account_lockout_duration_ticks = -int(self.account_lockout_duration * (1e7)) - - m["lockoutDuration"] = MessageElement(str(account_lockout_duration_ticks), - FLAG_MOD_REPLACE, "lockoutDuration") - - account_lockout_threshold = 3 - m["lockoutThreshold"] = MessageElement(str(account_lockout_threshold), - FLAG_MOD_REPLACE, "lockoutThreshold") - - self.lockout_observation_window = 2 - lockout_observation_window_ticks = -int(self.lockout_observation_window * (1e7)) - - m["lockOutObservationWindow"] = MessageElement(str(lockout_observation_window_ticks), - FLAG_MOD_REPLACE, "lockOutObservationWindow") - - self.ldb.modify(m) - - # Set the "dSHeuristics" to activate the correct "userPassword" behaviour - self.ldb.set_dsheuristics("000000001") - - # Get the old "minPwdAge" - minPwdAge = self.ldb.get_minPwdAge() - - # Reset the "minPwdAge" as it was before - self.addCleanup(self.ldb.set_minPwdAge, minPwdAge) - - # Set it temporarely to "0" - self.ldb.set_minPwdAge("0") - - self.base_dn = self.ldb.domain_dn() - - self.domain_sid = security.dom_sid(self.ldb.get_domain_sid()) - self.samr = samr.samr("ncacn_ip_tcp:%s[seal]" % host, lp, global_creds) - self.samr_handle = self.samr.Connect2(None, security.SEC_FLAG_MAXIMUM_ALLOWED) - self.samr_domain = self.samr.OpenDomain(self.samr_handle, security.SEC_FLAG_MAXIMUM_ALLOWED, self.domain_sid) - - # (Re)adds the test user accounts - self.lockout1krb5_creds = self.insta_creds(template_creds, - username="lockout1krb5", - userpass="thatsAcomplPASS0", - kerberos_state=MUST_USE_KERBEROS) - self.lockout1krb5_ldb = self._readd_user(self.lockout1krb5_creds) - self.lockout2krb5_creds = self.insta_creds(template_creds, - username="lockout2krb5", - userpass="thatsAcomplPASS0", - kerberos_state=MUST_USE_KERBEROS) - self.lockout2krb5_ldb = self._readd_user(self.lockout2krb5_creds, - lockOutObservationWindow=self.lockout_observation_window) - self.lockout1ntlm_creds = self.insta_creds(template_creds, - username="lockout1ntlm", - userpass="thatsAcomplPASS0", - kerberos_state=DONT_USE_KERBEROS) - self.lockout1ntlm_ldb = self._readd_user(self.lockout1ntlm_creds) - self.lockout2ntlm_creds = self.insta_creds(template_creds, - username="lockout2ntlm", - userpass="thatsAcomplPASS0", - kerberos_state=DONT_USE_KERBEROS) - self.lockout2ntlm_ldb = self._readd_user(self.lockout2ntlm_creds, - lockOutObservationWindow=self.lockout_observation_window) - - def tearDown(self): - super(BasePasswordTestCase, self).tearDown() - - def _test_login_lockout(self, creds): - username = creds.get_username() - userpass = creds.get_password() - userdn = "cn=%s,cn=users,%s" % (username, self.base_dn) - - use_kerberos = creds.get_kerberos_state() - # This unlocks by waiting for account_lockout_duration - if use_kerberos == MUST_USE_KERBEROS: - logoncount_relation = 'greater' - lastlogon_relation = 'greater' - print "Performs a lockout attempt against LDAP using Kerberos" - else: - logoncount_relation = 'equal' - lastlogon_relation = 'equal' - print "Performs a lockout attempt against LDAP using NTLM" - - # Change password on a connection as another user - res = self._check_account(userdn, - badPwdCount=0, - badPasswordTime=("greater", 0), - logonCount=(logoncount_relation, 0), - lastLogon=("greater", 0), - lastLogonTimestamp=("greater", 0), - userAccountControl= - dsdb.UF_NORMAL_ACCOUNT, - msDSUserAccountControlComputed=0) - badPasswordTime = int(res[0]["badPasswordTime"][0]) - logonCount = int(res[0]["logonCount"][0]) - lastLogon = int(res[0]["lastLogon"][0]) - firstLogon = lastLogon - lastLogonTimestamp = int(res[0]["lastLogonTimestamp"][0]) - print firstLogon - print lastLogonTimestamp - - - self.assertGreater(lastLogon, badPasswordTime) - self.assertGreaterEqual(lastLogon, lastLogonTimestamp) - - # Open a second LDB connection with the user credentials. Use the - # command line credentials for informations like the domain, the realm - # and the workstation. - creds_lockout = self.insta_creds(creds) - - # The wrong password - creds_lockout.set_password("thatsAcomplPASS1x") - - self.assertLoginFailure(host_url, creds_lockout, lp) - - res = self._check_account(userdn, - badPwdCount=1, - badPasswordTime=("greater", badPasswordTime), - logonCount=logonCount, - lastLogon=lastLogon, - lastLogonTimestamp=lastLogonTimestamp, - userAccountControl= - dsdb.UF_NORMAL_ACCOUNT, - msDSUserAccountControlComputed=0, - msg='lastlogontimestamp with wrong password') - badPasswordTime = int(res[0]["badPasswordTime"][0]) - - # Correct old password - creds_lockout.set_password(userpass) - - ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp) - - # lastLogonTimestamp should not change - # lastLogon increases if badPwdCount is non-zero (!) - res = self._check_account(userdn, - badPwdCount=0, - badPasswordTime=badPasswordTime, - logonCount=(logoncount_relation, logonCount), - lastLogon=('greater', lastLogon), - lastLogonTimestamp=lastLogonTimestamp, - userAccountControl= - dsdb.UF_NORMAL_ACCOUNT, - msDSUserAccountControlComputed=0, - msg='LLTimestamp is updated to lastlogon') - - logonCount = int(res[0]["logonCount"][0]) - lastLogon = int(res[0]["lastLogon"][0]) - self.assertGreater(lastLogon, badPasswordTime) - self.assertGreaterEqual(lastLogon, lastLogonTimestamp) - - # The wrong password - creds_lockout.set_password("thatsAcomplPASS1x") - - self.assertLoginFailure(host_url, creds_lockout, lp) - - res = self._check_account(userdn, - badPwdCount=1, - badPasswordTime=("greater", badPasswordTime), - logonCount=logonCount, - lastLogon=lastLogon, - lastLogonTimestamp=lastLogonTimestamp, - userAccountControl= - dsdb.UF_NORMAL_ACCOUNT, - msDSUserAccountControlComputed=0) - badPasswordTime = int(res[0]["badPasswordTime"][0]) - - # The wrong password - creds_lockout.set_password("thatsAcomplPASS1x") - - try: - ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp) - self.fail() - - except LdbError, (num, msg): - self.assertEquals(num, ERR_INVALID_CREDENTIALS) - - res = self._check_account(userdn, - badPwdCount=2, - badPasswordTime=("greater", badPasswordTime), - logonCount=logonCount, - lastLogon=lastLogon, - lastLogonTimestamp=lastLogonTimestamp, - userAccountControl= - dsdb.UF_NORMAL_ACCOUNT, - msDSUserAccountControlComputed=0) - badPasswordTime = int(res[0]["badPasswordTime"][0]) - - print "two failed password change" - - # The wrong password - creds_lockout.set_password("thatsAcomplPASS1x") - - try: - ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp) - self.fail() - - except LdbError, (num, msg): - self.assertEquals(num, ERR_INVALID_CREDENTIALS) - - res = self._check_account(userdn, - badPwdCount=3, - badPasswordTime=("greater", badPasswordTime), - logonCount=logonCount, - lastLogon=lastLogon, - lastLogonTimestamp=lastLogonTimestamp, - lockoutTime=("greater", badPasswordTime), - userAccountControl= - dsdb.UF_NORMAL_ACCOUNT, - msDSUserAccountControlComputed=dsdb.UF_LOCKOUT) - badPasswordTime = int(res[0]["badPasswordTime"][0]) - lockoutTime = int(res[0]["lockoutTime"][0]) - - # The wrong password - creds_lockout.set_password("thatsAcomplPASS1x") - try: - ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp) - self.fail() - except LdbError, (num, msg): - self.assertEquals(num, ERR_INVALID_CREDENTIALS) - - res = self._check_account(userdn, - badPwdCount=3, - badPasswordTime=badPasswordTime, - logonCount=logonCount, - lastLogon=lastLogon, - lastLogonTimestamp=lastLogonTimestamp, - lockoutTime=lockoutTime, - userAccountControl= - dsdb.UF_NORMAL_ACCOUNT, - msDSUserAccountControlComputed=dsdb.UF_LOCKOUT) - - # The wrong password - creds_lockout.set_password("thatsAcomplPASS1x") - try: - ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp) - self.fail() - except LdbError, (num, msg): - self.assertEquals(num, ERR_INVALID_CREDENTIALS) - - res = self._check_account(userdn, - badPwdCount=3, - badPasswordTime=badPasswordTime, - logonCount=logonCount, - lastLogon=lastLogon, - lastLogonTimestamp=lastLogonTimestamp, - lockoutTime=lockoutTime, - userAccountControl= - dsdb.UF_NORMAL_ACCOUNT, - msDSUserAccountControlComputed=dsdb.UF_LOCKOUT) - - # The correct password, but we are locked out - creds_lockout.set_password(userpass) - try: - ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp) - self.fail() - except LdbError, (num, msg): - self.assertEquals(num, ERR_INVALID_CREDENTIALS) - - res = self._check_account(userdn, - badPwdCount=3, - badPasswordTime=badPasswordTime, - logonCount=logonCount, - lastLogon=lastLogon, - lastLogonTimestamp=lastLogonTimestamp, - lockoutTime=lockoutTime, - userAccountControl= - dsdb.UF_NORMAL_ACCOUNT, - msDSUserAccountControlComputed=dsdb.UF_LOCKOUT) - - # wait for the lockout to end - time.sleep(self.account_lockout_duration + 1) - print self.account_lockout_duration + 1 - - res = self._check_account(userdn, - badPwdCount=3, effective_bad_password_count=0, - badPasswordTime=badPasswordTime, - logonCount=logonCount, - lockoutTime=lockoutTime, - lastLogon=lastLogon, - lastLogonTimestamp=lastLogonTimestamp, - userAccountControl= - dsdb.UF_NORMAL_ACCOUNT, - msDSUserAccountControlComputed=0) - - # The correct password after letting the timeout expire - - creds_lockout.set_password(userpass) - - creds_lockout2 = self.insta_creds(creds_lockout) - - ldb_lockout = SamDB(url=host_url, credentials=creds_lockout2, lp=lp) - time.sleep(3) - - res = self._check_account(userdn, - badPwdCount=0, - badPasswordTime=badPasswordTime, - logonCount=(logoncount_relation, logonCount), - lastLogon=(lastlogon_relation, lastLogon), - lastLogonTimestamp=lastLogonTimestamp, - lockoutTime=0, - userAccountControl= - dsdb.UF_NORMAL_ACCOUNT, - msDSUserAccountControlComputed=0, - msg="lastLogon is way off") - - logonCount = int(res[0]["logonCount"][0]) - lastLogon = int(res[0]["lastLogon"][0]) - - # The wrong password - creds_lockout.set_password("thatsAcomplPASS1x") - try: - ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp) - self.fail() - except LdbError, (num, msg): - self.assertEquals(num, ERR_INVALID_CREDENTIALS) - - res = self._check_account(userdn, - badPwdCount=1, - badPasswordTime=("greater", badPasswordTime), - logonCount=logonCount, - lockoutTime=0, - lastLogon=lastLogon, - lastLogonTimestamp=lastLogonTimestamp, - userAccountControl= - dsdb.UF_NORMAL_ACCOUNT, - msDSUserAccountControlComputed=0) - badPasswordTime = int(res[0]["badPasswordTime"][0]) - - # The wrong password - creds_lockout.set_password("thatsAcomplPASS1x") - try: - ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp) - self.fail() - except LdbError, (num, msg): - self.assertEquals(num, ERR_INVALID_CREDENTIALS) - - res = self._check_account(userdn, - badPwdCount=2, - badPasswordTime=("greater", badPasswordTime), - logonCount=logonCount, - lockoutTime=0, - lastLogon=lastLogon, - lastLogonTimestamp=lastLogonTimestamp, - userAccountControl= - dsdb.UF_NORMAL_ACCOUNT, - msDSUserAccountControlComputed=0) - badPasswordTime = int(res[0]["badPasswordTime"][0]) - - time.sleep(self.lockout_observation_window + 1) - - res = self._check_account(userdn, - badPwdCount=2, effective_bad_password_count=0, - badPasswordTime=badPasswordTime, - logonCount=logonCount, - lockoutTime=0, - lastLogon=lastLogon, - lastLogonTimestamp=lastLogonTimestamp, - userAccountControl= - dsdb.UF_NORMAL_ACCOUNT, - msDSUserAccountControlComputed=0) - - # The wrong password - creds_lockout.set_password("thatsAcomplPASS1x") - try: - ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp) - self.fail() - except LdbError, (num, msg): - self.assertEquals(num, ERR_INVALID_CREDENTIALS) - - res = self._check_account(userdn, - badPwdCount=1, - badPasswordTime=("greater", badPasswordTime), - logonCount=logonCount, - lockoutTime=0, - lastLogon=lastLogon, - lastLogonTimestamp=lastLogonTimestamp, - userAccountControl= - dsdb.UF_NORMAL_ACCOUNT, - msDSUserAccountControlComputed=0) - badPasswordTime = int(res[0]["badPasswordTime"][0]) - - # The correct password without letting the timeout expire - creds_lockout.set_password(userpass) - ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp) - - res = self._check_account(userdn, - badPwdCount=0, - badPasswordTime=badPasswordTime, - logonCount=(logoncount_relation, logonCount), - lockoutTime=0, - lastLogon=("greater", lastLogon), - lastLogonTimestamp=lastLogonTimestamp, - userAccountControl= - dsdb.UF_NORMAL_ACCOUNT, - msDSUserAccountControlComputed=0) - - def _test_multiple_logon(self, creds): - # Test the happy case in which a user logs on correctly, then - # logs on correctly again, so that the bad password and - # lockout times are both zero the second time. The lastlogon - # time should increase. - - # Open a second LDB connection with the user credentials. Use the - # command line credentials for informations like the domain, the realm - # and the workstation. - username = creds.get_username() - userdn = "cn=%s,cn=users,%s" % (username, self.base_dn) - - use_kerberos = creds.get_kerberos_state() - if use_kerberos == MUST_USE_KERBEROS: - print "Testing multiple logon with Kerberos" - logoncount_relation = 'greater' - lastlogon_relation = 'greater' - else: - print "Testing multiple logon with NTLM" - logoncount_relation = 'equal' - lastlogon_relation = 'equal' - - SamDB(url=host_url, credentials=self.insta_creds(creds), lp=lp) - - res = self._check_account(userdn, - badPwdCount=0, - badPasswordTime=("greater", 0), - logonCount=(logoncount_relation, 0), - lastLogon=("greater", 0), - lastLogonTimestamp=("greater", 0), - userAccountControl= - dsdb.UF_NORMAL_ACCOUNT, - msDSUserAccountControlComputed=0) - badPasswordTime = int(res[0]["badPasswordTime"][0]) - logonCount = int(res[0]["logonCount"][0]) - lastLogon = int(res[0]["lastLogon"][0]) - lastLogonTimestamp = int(res[0]["lastLogonTimestamp"][0]) - firstLogon = lastLogon - print "last logon is %d" % lastLogon - self.assertGreater(lastLogon, badPasswordTime) - self.assertGreaterEqual(lastLogon, lastLogonTimestamp) - - time.sleep(1) - SamDB(url=host_url, credentials=self.insta_creds(creds), lp=lp) - - res = self._check_account(userdn, - badPwdCount=0, - badPasswordTime=badPasswordTime, - logonCount=(logoncount_relation, logonCount), - lastLogon=(lastlogon_relation, lastLogon), - lastLogonTimestamp=lastLogonTimestamp, - userAccountControl= - dsdb.UF_NORMAL_ACCOUNT, - msDSUserAccountControlComputed=0, - msg=("second logon, firstlogon was %s" % - firstLogon)) - - - lastLogon = int(res[0]["lastLogon"][0]) - - time.sleep(1) - - SamDB(url=host_url, credentials=self.insta_creds(creds), lp=lp) - - res = self._check_account(userdn, - badPwdCount=0, - badPasswordTime=badPasswordTime, - logonCount=(logoncount_relation, logonCount), - lastLogon=(lastlogon_relation, lastLogon), - lastLogonTimestamp=lastLogonTimestamp, - userAccountControl= - dsdb.UF_NORMAL_ACCOUNT, - msDSUserAccountControlComputed=0) - + self.host = host + self.host_url = host_url + self.lp = lp + self.global_creds = global_creds + self.ldb = SamDB(url=self.host_url, session_info=system_session(self.lp), + credentials=self.global_creds, lp=self.lp) + super(PasswordTests, self).setUp() -class PasswordTests(BasePasswordTestCase): def _reset_ldap_lockoutTime(self, res): self.ldb.modify_ldif(""" dn: """ + str(res[0].dn) + """ diff --git a/source4/dsdb/tests/python/password_lockout_base.py b/source4/dsdb/tests/python/password_lockout_base.py new file mode 100644 index 0000000..f63b7f2 --- /dev/null +++ b/source4/dsdb/tests/python/password_lockout_base.py @@ -0,0 +1,908 @@ +import samba + +from samba.auth import system_session +from samba.credentials import Credentials, DONT_USE_KERBEROS, MUST_USE_KERBEROS +from ldb import SCOPE_BASE, LdbError +from ldb import ERR_CONSTRAINT_VIOLATION +from ldb import ERR_INVALID_CREDENTIALS +from ldb import Message, MessageElement, Dn +from ldb import FLAG_MOD_REPLACE +from samba import gensec, dsdb +from samba.samdb import SamDB +import samba.tests +from samba.tests import delete_force +from samba.dcerpc import security, samr +from samba.ndr import ndr_unpack + +import time + +class BasePasswordTestCase(samba.tests.TestCase): + def _open_samr_user(self, res): + # Force an encrypted connection + self.assertTrue("objectSid" in res[0]) + + (domain_sid, rid) = ndr_unpack(security.dom_sid, res[0]["objectSid"][0]).split() + self.assertEquals(self.domain_sid, domain_sid) + + return self.samr.OpenUser(self.samr_domain, security.SEC_FLAG_MAXIMUM_ALLOWED, rid) + + def _reset_samr(self, res): + + # Now reset the lockout, by removing ACB_AUTOLOCK (which removes the lock, despite being a generated attribute) + samr_user = self._open_samr_user(res) + acb_info = self.samr.QueryUserInfo(samr_user, 16) + acb_info.acct_flags &= ~samr.ACB_AUTOLOCK + self.samr.SetUserInfo(samr_user, 16, acb_info) + self.samr.Close(samr_user) + + def _check_attribute(self, res, name, value): + if value is None: + self.assertTrue(name not in res[0], + msg="attr[%s]=%r on dn[%s]" % + (name, res[0], res[0].dn)) + return + + if isinstance(value, tuple): + (mode, value) = value + else: + mode = "equal" + + 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)) + self.assertTrue(len(res[0][name]) == 1, + 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": + 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] (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] (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, + logonCount=None, + lastLogon=None, + lastLogonTimestamp=None, + lockoutTime=None, + userAccountControl=None, + msDSUserAccountControlComputed=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", + "logonCount", + "lockoutTime", + "userAccountControl", + "msDS-User-Account-Control-Computed" + ] + + # in order to prevent some time resolution problems we sleep for + # 10 micro second + time.sleep(0.01) + + res = self.ldb.search(dn, scope=SCOPE_BASE, attrs=attrs) + self.assertTrue(len(res) == 1) + self._check_attribute(res, "badPwdCount", badPwdCount) + self._check_attribute(res, "badPasswordTime", badPasswordTime) + self._check_attribute(res, "logonCount", logonCount) + self._check_attribute(res, "lastLogon", lastLogon) + self._check_attribute(res, "lastLogonTimestamp", lastLogonTimestamp) + self._check_attribute(res, "lockoutTime", lockoutTime) + self._check_attribute(res, "userAccountControl", userAccountControl) + self._check_attribute(res, "msDS-User-Account-Control-Computed", + msDSUserAccountControlComputed) + + lastLogon = int(res[0]["lastLogon"][0]) + logonCount = int(res[0]["logonCount"][0]) + + samr_user = self._open_samr_user(res) + uinfo3 = self.samr.QueryUserInfo(samr_user, 3) + uinfo5 = self.samr.QueryUserInfo(samr_user, 5) + uinfo16 = self.samr.QueryUserInfo(samr_user, 16) + uinfo21 = self.samr.QueryUserInfo(samr_user, 21) + self.samr.Close(samr_user) + + expected_acb_info = 0 + if userAccountControl & dsdb.UF_NORMAL_ACCOUNT: + expected_acb_info |= samr.ACB_NORMAL + if userAccountControl & dsdb.UF_ACCOUNTDISABLE: + expected_acb_info |= samr.ACB_DISABLED + if userAccountControl & dsdb.UF_PASSWD_NOTREQD: + expected_acb_info |= samr.ACB_PWNOTREQ + if msDSUserAccountControlComputed & dsdb.UF_LOCKOUT: + expected_acb_info |= samr.ACB_AUTOLOCK + if msDSUserAccountControlComputed & dsdb.UF_PASSWORD_EXPIRED: + expected_acb_info |= samr.ACB_PW_EXPIRED + + expected_bad_password_count = 0 + if badPwdCount is not None: + expected_bad_password_count = badPwdCount + if effective_bad_password_count is None: + effective_bad_password_count = expected_bad_password_count + + self.assertEquals(uinfo3.acct_flags, expected_acb_info) + self.assertEquals(uinfo3.bad_password_count, expected_bad_password_count) + self.assertEquals(uinfo3.last_logon, lastLogon) + self.assertEquals(uinfo3.logon_count, logonCount) + + self.assertEquals(uinfo5.acct_flags, expected_acb_info) + self.assertEquals(uinfo5.bad_password_count, effective_bad_password_count) + self.assertEquals(uinfo5.last_logon, lastLogon) + self.assertEquals(uinfo5.logon_count, logonCount) + + self.assertEquals(uinfo16.acct_flags, expected_acb_info) + + self.assertEquals(uinfo21.acct_flags, expected_acb_info) + self.assertEquals(uinfo21.bad_password_count, effective_bad_password_count) + self.assertEquals(uinfo21.last_logon, lastLogon) + self.assertEquals(uinfo21.logon_count, logonCount) + + # check LDAP again and make sure the samr.QueryUserInfo + # doesn't have any impact. + res2 = self.ldb.search(dn, scope=SCOPE_BASE, attrs=attrs) + self.assertEquals(res[0], res2[0]) + + # in order to prevent some time resolution problems we sleep for + # 10 micro second + time.sleep(0.01) + return res + + def _readd_user(self, creds, lockOutObservationWindow=0): + username = creds.get_username() + userpass = creds.get_password() + userdn = "cn=%s,cn=users,%s" % (username, self.base_dn) + + use_kerberos = creds.get_kerberos_state() + if use_kerberos == MUST_USE_KERBEROS: + logoncount_relation = 'greater' + lastlogon_relation = 'greater' + else: + logoncount_relation = 'equal' + if lockOutObservationWindow == 0: + lastlogon_relation = 'greater' + else: + lastlogon_relation = 'equal' + + delete_force(self.ldb, userdn) + self.ldb.add({ + "dn": userdn, + "objectclass": "user", + "sAMAccountName": username}) + + self.addCleanup(delete_force, self.ldb, userdn) + + res = self._check_account(userdn, + badPwdCount=0, + badPasswordTime=0, + logonCount=0, + lastLogon=0, + lastLogonTimestamp=('absent', None), + userAccountControl= + dsdb.UF_NORMAL_ACCOUNT | + dsdb.UF_ACCOUNTDISABLE | + dsdb.UF_PASSWD_NOTREQD, + msDSUserAccountControlComputed= + dsdb.UF_PASSWORD_EXPIRED) + + # SAMR doesn't have any impact if dsdb.UF_LOCKOUT isn't present. + # It doesn't create "lockoutTime" = 0. + self._reset_samr(res) + + res = self._check_account(userdn, + badPwdCount=0, + badPasswordTime=0, + logonCount=0, + lastLogon=0, + lastLogonTimestamp=('absent', None), + userAccountControl= + dsdb.UF_NORMAL_ACCOUNT | + dsdb.UF_ACCOUNTDISABLE | + dsdb.UF_PASSWD_NOTREQD, + msDSUserAccountControlComputed= + dsdb.UF_PASSWORD_EXPIRED) + + # Tests a password change when we don't have any password yet with a + # wrong old password + try: + self.ldb.modify_ldif(""" +dn: """ + userdn + """ +changetype: modify +delete: userPassword +userPassword: noPassword +add: userPassword +userPassword: thatsAcomplPASS2 +""") + self.fail() + except LdbError, (num, msg): + self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) + # Windows (2008 at least) seems to have some small bug here: it + # returns "0000056A" on longer (always wrong) previous passwords. + self.assertTrue('00000056' in msg, msg) + + res = self._check_account(userdn, + badPwdCount=1, + badPasswordTime=("greater", 0), + logonCount=0, + lastLogon=0, + lastLogonTimestamp=('absent', None), + userAccountControl= + dsdb.UF_NORMAL_ACCOUNT | + dsdb.UF_ACCOUNTDISABLE | + dsdb.UF_PASSWD_NOTREQD, + msDSUserAccountControlComputed= + dsdb.UF_PASSWORD_EXPIRED) + badPwdCount = int(res[0]["badPwdCount"][0]) + badPasswordTime = int(res[0]["badPasswordTime"][0]) + + # Sets the initial user password with a "special" password change + # I think that this internally is a password set operation and it can + # only be performed by someone which has password set privileges on the + # account (at least in s4 we do handle it like that). + self.ldb.modify_ldif(""" +dn: """ + userdn + """ +changetype: modify +delete: userPassword +add: userPassword +userPassword: """ + userpass + """ +""") + + res = self._check_account(userdn, + badPwdCount=badPwdCount, + badPasswordTime=badPasswordTime, + logonCount=0, + lastLogon=0, + lastLogonTimestamp=('absent', None), + userAccountControl= + dsdb.UF_NORMAL_ACCOUNT | + dsdb.UF_ACCOUNTDISABLE | + dsdb.UF_PASSWD_NOTREQD, + msDSUserAccountControlComputed=0) + + # Enables the user account + self.ldb.enable_account("(sAMAccountName=%s)" % username) + + res = self._check_account(userdn, + badPwdCount=badPwdCount, + badPasswordTime=badPasswordTime, + logonCount=0, + lastLogon=0, + lastLogonTimestamp=('absent', None), + userAccountControl= + dsdb.UF_NORMAL_ACCOUNT, + msDSUserAccountControlComputed=0) + if lockOutObservationWindow != 0: + time.sleep(lockOutObservationWindow + 1) + effective_bad_password_count = 0 + else: + effective_bad_password_count = badPwdCount + + res = self._check_account(userdn, + badPwdCount=badPwdCount, + effective_bad_password_count=effective_bad_password_count, + badPasswordTime=badPasswordTime, + logonCount=0, + lastLogon=0, + lastLogonTimestamp=('absent', None), + userAccountControl= + dsdb.UF_NORMAL_ACCOUNT, + msDSUserAccountControlComputed=0) + + ldb = SamDB(url=self.host_url, credentials=creds, lp=self.lp) + + if lockOutObservationWindow == 0: + badPwdCount = 0 + effective_bad_password_count = 0 + if use_kerberos == MUST_USE_KERBEROS: + badPwdCount = 0 + effective_bad_password_count = 0 + + res = self._check_account(userdn, + badPwdCount=badPwdCount, + effective_bad_password_count=effective_bad_password_count, + badPasswordTime=badPasswordTime, + logonCount=(logoncount_relation, 0), + lastLogon=(lastlogon_relation, 0), + lastLogonTimestamp=('greater', badPasswordTime), + userAccountControl= + dsdb.UF_NORMAL_ACCOUNT, + msDSUserAccountControlComputed=0) + + logonCount = int(res[0]["logonCount"][0]) + lastLogon = int(res[0]["lastLogon"][0]) + lastLogonTimestamp = int(res[0]["lastLogonTimestamp"][0]) + if lastlogon_relation == 'greater': + self.assertGreater(lastLogon, badPasswordTime) + self.assertGreaterEqual(lastLogon, lastLogonTimestamp) + + res = self._check_account(userdn, + badPwdCount=badPwdCount, + effective_bad_password_count=effective_bad_password_count, + badPasswordTime=badPasswordTime, + logonCount=logonCount, + lastLogon=lastLogon, + lastLogonTimestamp=lastLogonTimestamp, + userAccountControl= + dsdb.UF_NORMAL_ACCOUNT, + msDSUserAccountControlComputed=0) + return ldb + + def assertLoginFailure(self, url, creds, lp, errno=ERR_INVALID_CREDENTIALS): + try: + ldb = SamDB(url=url, credentials=creds, lp=lp) + self.fail("Login unexpectedly succeeded") + except LdbError, (num, msg): + if errno is not None: + self.assertEquals(num, errno, ("Login failed in the wrong way" + "(got err %d, expected %d)" % + (num, errno))) + + def setUp(self): + super(BasePasswordTestCase, self).setUp() + + self.global_creds.set_gensec_features(self.global_creds.get_gensec_features() | + gensec.FEATURE_SEAL) + + self.template_creds = Credentials() + self.template_creds.set_username("testuser") + self.template_creds.set_password("thatsAcomplPASS1") + self.template_creds.set_domain(self.global_creds.get_domain()) + self.template_creds.set_realm(self.global_creds.get_realm()) + self.template_creds.set_workstation(self.global_creds.get_workstation()) + self.template_creds.set_gensec_features(self.global_creds.get_gensec_features()) + self.template_creds.set_kerberos_state(self.global_creds.get_kerberos_state()) + + + # Gets back the basedn + base_dn = self.ldb.domain_dn() + + # Gets back the configuration basedn + configuration_dn = self.ldb.get_config_basedn().get_linearized() + + # Get the old "dSHeuristics" if it was set + dsheuristics = self.ldb.get_dsheuristics() + + # Reset the "dSHeuristics" as they were before + self.addCleanup(self.ldb.set_dsheuristics, dsheuristics) + + res = self.ldb.search(base_dn, + scope=SCOPE_BASE, attrs=["lockoutDuration", "lockOutObservationWindow", "lockoutThreshold"]) + + if "lockoutDuration" in res[0]: + lockoutDuration = res[0]["lockoutDuration"][0] + else: + lockoutDuration = 0 + + if "lockoutObservationWindow" in res[0]: + lockoutObservationWindow = res[0]["lockoutObservationWindow"][0] + else: + lockoutObservationWindow = 0 + + if "lockoutThreshold" in res[0]: + lockoutThreshold = res[0]["lockoutThreshold"][0] + else: + lockoutTreshold = 0 + + self.addCleanup(self.ldb.modify_ldif, """ +dn: """ + base_dn + """ +changetype: modify +replace: lockoutDuration +lockoutDuration: """ + str(lockoutDuration) + """ +replace: lockoutObservationWindow +lockoutObservationWindow: """ + str(lockoutObservationWindow) + """ +replace: lockoutThreshold +lockoutThreshold: """ + str(lockoutThreshold) + """ +""") + + m = Message() + m.dn = Dn(self.ldb, base_dn) + + self.account_lockout_duration = 2 + account_lockout_duration_ticks = -int(self.account_lockout_duration * (1e7)) + + m["lockoutDuration"] = MessageElement(str(account_lockout_duration_ticks), + FLAG_MOD_REPLACE, "lockoutDuration") + + account_lockout_threshold = 3 + m["lockoutThreshold"] = MessageElement(str(account_lockout_threshold), + FLAG_MOD_REPLACE, "lockoutThreshold") + + self.lockout_observation_window = 2 + lockout_observation_window_ticks = -int(self.lockout_observation_window * (1e7)) + + m["lockOutObservationWindow"] = MessageElement(str(lockout_observation_window_ticks), + FLAG_MOD_REPLACE, "lockOutObservationWindow") + + self.ldb.modify(m) + + # Set the "dSHeuristics" to activate the correct "userPassword" behaviour + self.ldb.set_dsheuristics("000000001") + + # Get the old "minPwdAge" + minPwdAge = self.ldb.get_minPwdAge() + + # Reset the "minPwdAge" as it was before + self.addCleanup(self.ldb.set_minPwdAge, minPwdAge) + + # Set it temporarely to "0" + self.ldb.set_minPwdAge("0") + + self.base_dn = self.ldb.domain_dn() + + self.domain_sid = security.dom_sid(self.ldb.get_domain_sid()) + self.samr = samr.samr("ncacn_ip_tcp:%s[seal]" % self.host, self.lp, self.global_creds) + self.samr_handle = self.samr.Connect2(None, security.SEC_FLAG_MAXIMUM_ALLOWED) + self.samr_domain = self.samr.OpenDomain(self.samr_handle, security.SEC_FLAG_MAXIMUM_ALLOWED, self.domain_sid) + + # (Re)adds the test user accounts + self.lockout1krb5_creds = self.insta_creds(self.template_creds, + username="lockout1krb5", + userpass="thatsAcomplPASS0", + kerberos_state=MUST_USE_KERBEROS) + self.lockout1krb5_ldb = self._readd_user(self.lockout1krb5_creds) + self.lockout2krb5_creds = self.insta_creds(self.template_creds, + username="lockout2krb5", + userpass="thatsAcomplPASS0", + kerberos_state=MUST_USE_KERBEROS) + self.lockout2krb5_ldb = self._readd_user(self.lockout2krb5_creds, + lockOutObservationWindow=self.lockout_observation_window) + self.lockout1ntlm_creds = self.insta_creds(self.template_creds, + username="lockout1ntlm", + userpass="thatsAcomplPASS0", + kerberos_state=DONT_USE_KERBEROS) + self.lockout1ntlm_ldb = self._readd_user(self.lockout1ntlm_creds) + self.lockout2ntlm_creds = self.insta_creds(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) + + def tearDown(self): + super(BasePasswordTestCase, self).tearDown() + + def _test_login_lockout(self, creds): + username = creds.get_username() + userpass = creds.get_password() + userdn = "cn=%s,cn=users,%s" % (username, self.base_dn) + + use_kerberos = creds.get_kerberos_state() + # This unlocks by waiting for account_lockout_duration + if use_kerberos == MUST_USE_KERBEROS: + logoncount_relation = 'greater' + lastlogon_relation = 'greater' + print "Performs a lockout attempt against LDAP using Kerberos" + else: + logoncount_relation = 'equal' + lastlogon_relation = 'equal' + print "Performs a lockout attempt against LDAP using NTLM" + + # Change password on a connection as another user + res = self._check_account(userdn, + badPwdCount=0, + badPasswordTime=("greater", 0), + logonCount=(logoncount_relation, 0), + lastLogon=("greater", 0), + lastLogonTimestamp=("greater", 0), + userAccountControl= + dsdb.UF_NORMAL_ACCOUNT, + msDSUserAccountControlComputed=0) + badPasswordTime = int(res[0]["badPasswordTime"][0]) + logonCount = int(res[0]["logonCount"][0]) + lastLogon = int(res[0]["lastLogon"][0]) + firstLogon = lastLogon + lastLogonTimestamp = int(res[0]["lastLogonTimestamp"][0]) + print firstLogon + print lastLogonTimestamp + + + self.assertGreater(lastLogon, badPasswordTime) + self.assertGreaterEqual(lastLogon, lastLogonTimestamp) + + # Open a second LDB connection with the user credentials. Use the + # command line credentials for informations like the domain, the realm + # and the workstation. + creds_lockout = self.insta_creds(creds) + + # The wrong password + creds_lockout.set_password("thatsAcomplPASS1x") + + self.assertLoginFailure(self.host_url, creds_lockout, self.lp) + + res = self._check_account(userdn, + badPwdCount=1, + badPasswordTime=("greater", badPasswordTime), + logonCount=logonCount, + lastLogon=lastLogon, + lastLogonTimestamp=lastLogonTimestamp, + userAccountControl= + dsdb.UF_NORMAL_ACCOUNT, + msDSUserAccountControlComputed=0, + msg='lastlogontimestamp with wrong password') + badPasswordTime = int(res[0]["badPasswordTime"][0]) + + # Correct old password + creds_lockout.set_password(userpass) + + ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout, lp=self.lp) + + # lastLogonTimestamp should not change + # lastLogon increases if badPwdCount is non-zero (!) + res = self._check_account(userdn, + badPwdCount=0, + badPasswordTime=badPasswordTime, + logonCount=(logoncount_relation, logonCount), + lastLogon=('greater', lastLogon), + lastLogonTimestamp=lastLogonTimestamp, + userAccountControl= + dsdb.UF_NORMAL_ACCOUNT, + msDSUserAccountControlComputed=0, + msg='LLTimestamp is updated to lastlogon') + + logonCount = int(res[0]["logonCount"][0]) + lastLogon = int(res[0]["lastLogon"][0]) + self.assertGreater(lastLogon, badPasswordTime) + self.assertGreaterEqual(lastLogon, lastLogonTimestamp) + + # The wrong password + creds_lockout.set_password("thatsAcomplPASS1x") + + self.assertLoginFailure(self.host_url, creds_lockout, self.lp) + + res = self._check_account(userdn, + badPwdCount=1, + badPasswordTime=("greater", badPasswordTime), + logonCount=logonCount, + lastLogon=lastLogon, + lastLogonTimestamp=lastLogonTimestamp, + userAccountControl= + dsdb.UF_NORMAL_ACCOUNT, + msDSUserAccountControlComputed=0) + badPasswordTime = int(res[0]["badPasswordTime"][0]) + + # The wrong password + creds_lockout.set_password("thatsAcomplPASS1x") + + try: + ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout, lp=self.lp) + self.fail() + + except LdbError, (num, msg): + self.assertEquals(num, ERR_INVALID_CREDENTIALS) + + res = self._check_account(userdn, + badPwdCount=2, + badPasswordTime=("greater", badPasswordTime), + logonCount=logonCount, + lastLogon=lastLogon, + lastLogonTimestamp=lastLogonTimestamp, + userAccountControl= + dsdb.UF_NORMAL_ACCOUNT, + msDSUserAccountControlComputed=0) + badPasswordTime = int(res[0]["badPasswordTime"][0]) + + print "two failed password change" + + # The wrong password + creds_lockout.set_password("thatsAcomplPASS1x") + + try: + ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout, lp=self.lp) + self.fail() + + except LdbError, (num, msg): + self.assertEquals(num, ERR_INVALID_CREDENTIALS) + + res = self._check_account(userdn, + badPwdCount=3, + badPasswordTime=("greater", badPasswordTime), + logonCount=logonCount, + lastLogon=lastLogon, + lastLogonTimestamp=lastLogonTimestamp, + lockoutTime=("greater", badPasswordTime), + userAccountControl= + dsdb.UF_NORMAL_ACCOUNT, + msDSUserAccountControlComputed=dsdb.UF_LOCKOUT) + badPasswordTime = int(res[0]["badPasswordTime"][0]) + lockoutTime = int(res[0]["lockoutTime"][0]) + + # The wrong password + creds_lockout.set_password("thatsAcomplPASS1x") + try: + ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout, lp=self.lp) + self.fail() + except LdbError, (num, msg): + self.assertEquals(num, ERR_INVALID_CREDENTIALS) + + res = self._check_account(userdn, + badPwdCount=3, + badPasswordTime=badPasswordTime, + logonCount=logonCount, + lastLogon=lastLogon, + lastLogonTimestamp=lastLogonTimestamp, + lockoutTime=lockoutTime, + userAccountControl= + dsdb.UF_NORMAL_ACCOUNT, + msDSUserAccountControlComputed=dsdb.UF_LOCKOUT) + + # The wrong password + creds_lockout.set_password("thatsAcomplPASS1x") + try: + ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout, lp=self.lp) + self.fail() + except LdbError, (num, msg): + self.assertEquals(num, ERR_INVALID_CREDENTIALS) + + res = self._check_account(userdn, + badPwdCount=3, + badPasswordTime=badPasswordTime, + logonCount=logonCount, + lastLogon=lastLogon, + lastLogonTimestamp=lastLogonTimestamp, + lockoutTime=lockoutTime, + userAccountControl= + dsdb.UF_NORMAL_ACCOUNT, + msDSUserAccountControlComputed=dsdb.UF_LOCKOUT) + + # The correct password, but we are locked out + creds_lockout.set_password(userpass) + try: + ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout, lp=self.lp) + self.fail() + except LdbError, (num, msg): + self.assertEquals(num, ERR_INVALID_CREDENTIALS) + + res = self._check_account(userdn, + badPwdCount=3, + badPasswordTime=badPasswordTime, + logonCount=logonCount, + lastLogon=lastLogon, + lastLogonTimestamp=lastLogonTimestamp, + lockoutTime=lockoutTime, + userAccountControl= + dsdb.UF_NORMAL_ACCOUNT, + msDSUserAccountControlComputed=dsdb.UF_LOCKOUT) + + # wait for the lockout to end + time.sleep(self.account_lockout_duration + 1) + print self.account_lockout_duration + 1 + + res = self._check_account(userdn, + badPwdCount=3, effective_bad_password_count=0, + badPasswordTime=badPasswordTime, + logonCount=logonCount, + lockoutTime=lockoutTime, + lastLogon=lastLogon, + lastLogonTimestamp=lastLogonTimestamp, + userAccountControl= + dsdb.UF_NORMAL_ACCOUNT, + msDSUserAccountControlComputed=0) + + # The correct password after letting the timeout expire + + creds_lockout.set_password(userpass) + + creds_lockout2 = self.insta_creds(creds_lockout) + + ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout2, lp=self.lp) + time.sleep(3) + + res = self._check_account(userdn, + badPwdCount=0, + badPasswordTime=badPasswordTime, + logonCount=(logoncount_relation, logonCount), + lastLogon=(lastlogon_relation, lastLogon), + lastLogonTimestamp=lastLogonTimestamp, + lockoutTime=0, + userAccountControl= + dsdb.UF_NORMAL_ACCOUNT, + msDSUserAccountControlComputed=0, + msg="lastLogon is way off") + + logonCount = int(res[0]["logonCount"][0]) + lastLogon = int(res[0]["lastLogon"][0]) + + # The wrong password + creds_lockout.set_password("thatsAcomplPASS1x") + try: + ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout, lp=self.lp) + self.fail() + except LdbError, (num, msg): + self.assertEquals(num, ERR_INVALID_CREDENTIALS) + + res = self._check_account(userdn, + badPwdCount=1, + badPasswordTime=("greater", badPasswordTime), + logonCount=logonCount, + lockoutTime=0, + lastLogon=lastLogon, + lastLogonTimestamp=lastLogonTimestamp, + userAccountControl= + dsdb.UF_NORMAL_ACCOUNT, + msDSUserAccountControlComputed=0) + badPasswordTime = int(res[0]["badPasswordTime"][0]) + + # The wrong password + creds_lockout.set_password("thatsAcomplPASS1x") + try: + ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout, lp=self.lp) + self.fail() + except LdbError, (num, msg): + self.assertEquals(num, ERR_INVALID_CREDENTIALS) + + res = self._check_account(userdn, + badPwdCount=2, + badPasswordTime=("greater", badPasswordTime), + logonCount=logonCount, + lockoutTime=0, + lastLogon=lastLogon, + lastLogonTimestamp=lastLogonTimestamp, + userAccountControl= + dsdb.UF_NORMAL_ACCOUNT, + msDSUserAccountControlComputed=0) + badPasswordTime = int(res[0]["badPasswordTime"][0]) + + time.sleep(self.lockout_observation_window + 1) + + res = self._check_account(userdn, + badPwdCount=2, effective_bad_password_count=0, + badPasswordTime=badPasswordTime, + logonCount=logonCount, + lockoutTime=0, + lastLogon=lastLogon, + lastLogonTimestamp=lastLogonTimestamp, + userAccountControl= + dsdb.UF_NORMAL_ACCOUNT, + msDSUserAccountControlComputed=0) + + # The wrong password + creds_lockout.set_password("thatsAcomplPASS1x") + try: + ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout, lp=self.lp) + self.fail() + except LdbError, (num, msg): + self.assertEquals(num, ERR_INVALID_CREDENTIALS) + + res = self._check_account(userdn, + badPwdCount=1, + badPasswordTime=("greater", badPasswordTime), + logonCount=logonCount, + lockoutTime=0, + lastLogon=lastLogon, + lastLogonTimestamp=lastLogonTimestamp, + userAccountControl= + dsdb.UF_NORMAL_ACCOUNT, + msDSUserAccountControlComputed=0) + badPasswordTime = int(res[0]["badPasswordTime"][0]) + + # The correct password without letting the timeout expire + creds_lockout.set_password(userpass) + ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout, lp=self.lp) + + res = self._check_account(userdn, + badPwdCount=0, + badPasswordTime=badPasswordTime, + logonCount=(logoncount_relation, logonCount), + lockoutTime=0, + lastLogon=("greater", lastLogon), + lastLogonTimestamp=lastLogonTimestamp, + userAccountControl= + dsdb.UF_NORMAL_ACCOUNT, + msDSUserAccountControlComputed=0) + + def _test_multiple_logon(self, creds): + # Test the happy case in which a user logs on correctly, then + # logs on correctly again, so that the bad password and + # lockout times are both zero the second time. The lastlogon + # time should increase. + + # Open a second LDB connection with the user credentials. Use the + # command line credentials for informations like the domain, the realm + # and the workstation. + username = creds.get_username() + userdn = "cn=%s,cn=users,%s" % (username, self.base_dn) + + use_kerberos = creds.get_kerberos_state() + if use_kerberos == MUST_USE_KERBEROS: + print "Testing multiple logon with Kerberos" + logoncount_relation = 'greater' + lastlogon_relation = 'greater' + else: + print "Testing multiple logon with NTLM" + logoncount_relation = 'equal' + lastlogon_relation = 'equal' + + SamDB(url=self.host_url, credentials=self.insta_creds(creds), lp=self.lp) + + res = self._check_account(userdn, + badPwdCount=0, + badPasswordTime=("greater", 0), + logonCount=(logoncount_relation, 0), + lastLogon=("greater", 0), + lastLogonTimestamp=("greater", 0), + userAccountControl= + dsdb.UF_NORMAL_ACCOUNT, + msDSUserAccountControlComputed=0) + badPasswordTime = int(res[0]["badPasswordTime"][0]) + logonCount = int(res[0]["logonCount"][0]) + lastLogon = int(res[0]["lastLogon"][0]) + lastLogonTimestamp = int(res[0]["lastLogonTimestamp"][0]) + firstLogon = lastLogon + print "last logon is %d" % lastLogon + self.assertGreater(lastLogon, badPasswordTime) + self.assertGreaterEqual(lastLogon, lastLogonTimestamp) + + time.sleep(1) + SamDB(url=self.host_url, credentials=self.insta_creds(creds), lp=self.lp) + + res = self._check_account(userdn, + badPwdCount=0, + badPasswordTime=badPasswordTime, + logonCount=(logoncount_relation, logonCount), + lastLogon=(lastlogon_relation, lastLogon), + lastLogonTimestamp=lastLogonTimestamp, + userAccountControl= + dsdb.UF_NORMAL_ACCOUNT, + msDSUserAccountControlComputed=0, + msg=("second logon, firstlogon was %s" % + firstLogon)) + + + lastLogon = int(res[0]["lastLogon"][0]) + + time.sleep(1) + + SamDB(url=self.host_url, credentials=self.insta_creds(creds), lp=self.lp) + + res = self._check_account(userdn, + badPwdCount=0, + badPasswordTime=badPasswordTime, + logonCount=(logoncount_relation, logonCount), + lastLogon=(lastlogon_relation, lastLogon), + lastLogonTimestamp=lastLogonTimestamp, + userAccountControl= + dsdb.UF_NORMAL_ACCOUNT, + msDSUserAccountControlComputed=0) -- 1.9.1 From d7254c58af563823f6c73102934f2fcf35a7627c Mon Sep 17 00:00:00 2001 From: Garming Sam Date: Thu, 6 Apr 2017 16:21:53 +1200 Subject: [PATCH 11/24] password_lockout: Move lockoutObservationWindow tests from setUp These should not belong in the setUp, and should be a separate test. Signed-off-by: Garming Sam --- source4/dsdb/tests/python/password_lockout.py | 25 ++++++++++++ source4/dsdb/tests/python/password_lockout_base.py | 45 ++++++++++++++++++++++ 2 files changed, 70 insertions(+) diff --git a/source4/dsdb/tests/python/password_lockout.py b/source4/dsdb/tests/python/password_lockout.py index 2e345d9..c843e37 100755 --- a/source4/dsdb/tests/python/password_lockout.py +++ b/source4/dsdb/tests/python/password_lockout.py @@ -973,6 +973,31 @@ unicodePwd:: """ + base64.b64encode(new_utf16) + """ def test_multiple_logon_ntlm(self): self._test_multiple_logon(self.lockout1ntlm_creds) + def test_lockout_observation_window(self): + lockout3krb5_creds = self.insta_creds(self.template_creds, + username="lockout3krb5", + userpass="thatsAcomplPASS0", + kerberos_state=MUST_USE_KERBEROS) + self._testing_add_user(lockout3krb5_creds) + + lockout4krb5_creds = self.insta_creds(self.template_creds, + username="lockout4krb5", + userpass="thatsAcomplPASS0", + kerberos_state=MUST_USE_KERBEROS) + self._testing_add_user(lockout4krb5_creds, + lockOutObservationWindow=self.lockout_observation_window) + + lockout3ntlm_creds = self.insta_creds(self.template_creds, + username="lockout3ntlm", + userpass="thatsAcomplPASS0", + kerberos_state=DONT_USE_KERBEROS) + self._testing_add_user(lockout3ntlm_creds) + lockout4ntlm_creds = self.insta_creds(self.template_creds, + username="lockout4ntlm", + userpass="thatsAcomplPASS0", + kerberos_state=DONT_USE_KERBEROS) + self._testing_add_user(lockout4ntlm_creds, + lockOutObservationWindow=self.lockout_observation_window) host_url = "ldap://%s" % host diff --git a/source4/dsdb/tests/python/password_lockout_base.py b/source4/dsdb/tests/python/password_lockout_base.py index f63b7f2..a340b33 100644 --- a/source4/dsdb/tests/python/password_lockout_base.py +++ b/source4/dsdb/tests/python/password_lockout_base.py @@ -196,6 +196,51 @@ class BasePasswordTestCase(samba.tests.TestCase): userpass = creds.get_password() userdn = "cn=%s,cn=users,%s" % (username, self.base_dn) + delete_force(self.ldb, userdn) + self.ldb.add({ + "dn": userdn, + "objectclass": "user", + "sAMAccountName": username}) + + self.addCleanup(delete_force, self.ldb, userdn) + + # Sets the initial user password with a "special" password change + # I think that this internally is a password set operation and it can + # only be performed by someone which has password set privileges on the + # account (at least in s4 we do handle it like that). + self.ldb.modify_ldif(""" +dn: """ + userdn + """ +changetype: modify +delete: userPassword +add: userPassword +userPassword: """ + userpass + """ +""") + # Enables the user account + self.ldb.enable_account("(sAMAccountName=%s)" % username) + + use_kerberos = creds.get_kerberos_state() + fail_creds = self.insta_creds(self.template_creds, + username=username, + userpass=userpass+"X", + kerberos_state=use_kerberos) + + # Fail once to get a badPasswordTime + try: + ldb = SamDB(url=self.host_url, credentials=fail_creds, lp=self.lp) + self.fail() + except LdbError, (num, msg): + self.assertEquals(num, ERR_INVALID_CREDENTIALS) + + # Succeed to reset everything to 0 + ldb = SamDB(url=self.host_url, credentials=creds, lp=self.lp) + + return ldb + + def _testing_add_user(self, creds, lockOutObservationWindow=0): + username = creds.get_username() + userpass = creds.get_password() + userdn = "cn=%s,cn=users,%s" % (username, self.base_dn) + use_kerberos = creds.get_kerberos_state() if use_kerberos == MUST_USE_KERBEROS: logoncount_relation = 'greater' -- 1.9.1 From 98f9af5629c5ec80d052e956903086393cead053 Mon Sep 17 00:00:00 2001 From: Garming Sam Date: Thu, 6 Apr 2017 16:57:13 +1200 Subject: [PATCH 12/24] password_lockout: Move some unnecessary methods from base Signed-off-by: Garming Sam --- source4/dsdb/tests/python/password_lockout.py | 188 +++++++++++++++++++++ source4/dsdb/tests/python/password_lockout_base.py | 188 --------------------- 2 files changed, 188 insertions(+), 188 deletions(-) diff --git a/source4/dsdb/tests/python/password_lockout.py b/source4/dsdb/tests/python/password_lockout.py index c843e37..09cb179 100755 --- a/source4/dsdb/tests/python/password_lockout.py +++ b/source4/dsdb/tests/python/password_lockout.py @@ -973,6 +973,194 @@ unicodePwd:: """ + base64.b64encode(new_utf16) + """ def test_multiple_logon_ntlm(self): self._test_multiple_logon(self.lockout1ntlm_creds) + def _testing_add_user(self, creds, lockOutObservationWindow=0): + username = creds.get_username() + userpass = creds.get_password() + userdn = "cn=%s,cn=users,%s" % (username, self.base_dn) + + use_kerberos = creds.get_kerberos_state() + if use_kerberos == MUST_USE_KERBEROS: + logoncount_relation = 'greater' + lastlogon_relation = 'greater' + else: + logoncount_relation = 'equal' + if lockOutObservationWindow == 0: + lastlogon_relation = 'greater' + else: + lastlogon_relation = 'equal' + + delete_force(self.ldb, userdn) + self.ldb.add({ + "dn": userdn, + "objectclass": "user", + "sAMAccountName": username}) + + self.addCleanup(delete_force, self.ldb, userdn) + + res = self._check_account(userdn, + badPwdCount=0, + badPasswordTime=0, + logonCount=0, + lastLogon=0, + lastLogonTimestamp=('absent', None), + userAccountControl= + dsdb.UF_NORMAL_ACCOUNT | + dsdb.UF_ACCOUNTDISABLE | + dsdb.UF_PASSWD_NOTREQD, + msDSUserAccountControlComputed= + dsdb.UF_PASSWORD_EXPIRED) + + # SAMR doesn't have any impact if dsdb.UF_LOCKOUT isn't present. + # It doesn't create "lockoutTime" = 0. + self._reset_samr(res) + + res = self._check_account(userdn, + badPwdCount=0, + badPasswordTime=0, + logonCount=0, + lastLogon=0, + lastLogonTimestamp=('absent', None), + userAccountControl= + dsdb.UF_NORMAL_ACCOUNT | + dsdb.UF_ACCOUNTDISABLE | + dsdb.UF_PASSWD_NOTREQD, + msDSUserAccountControlComputed= + dsdb.UF_PASSWORD_EXPIRED) + + # Tests a password change when we don't have any password yet with a + # wrong old password + try: + self.ldb.modify_ldif(""" +dn: """ + userdn + """ +changetype: modify +delete: userPassword +userPassword: noPassword +add: userPassword +userPassword: thatsAcomplPASS2 +""") + self.fail() + except LdbError, (num, msg): + self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) + # Windows (2008 at least) seems to have some small bug here: it + # returns "0000056A" on longer (always wrong) previous passwords. + self.assertTrue('00000056' in msg, msg) + + res = self._check_account(userdn, + badPwdCount=1, + badPasswordTime=("greater", 0), + logonCount=0, + lastLogon=0, + lastLogonTimestamp=('absent', None), + userAccountControl= + dsdb.UF_NORMAL_ACCOUNT | + dsdb.UF_ACCOUNTDISABLE | + dsdb.UF_PASSWD_NOTREQD, + msDSUserAccountControlComputed= + dsdb.UF_PASSWORD_EXPIRED) + badPwdCount = int(res[0]["badPwdCount"][0]) + badPasswordTime = int(res[0]["badPasswordTime"][0]) + + # Sets the initial user password with a "special" password change + # I think that this internally is a password set operation and it can + # only be performed by someone which has password set privileges on the + # account (at least in s4 we do handle it like that). + self.ldb.modify_ldif(""" +dn: """ + userdn + """ +changetype: modify +delete: userPassword +add: userPassword +userPassword: """ + userpass + """ +""") + + res = self._check_account(userdn, + badPwdCount=badPwdCount, + badPasswordTime=badPasswordTime, + logonCount=0, + lastLogon=0, + lastLogonTimestamp=('absent', None), + userAccountControl= + dsdb.UF_NORMAL_ACCOUNT | + dsdb.UF_ACCOUNTDISABLE | + dsdb.UF_PASSWD_NOTREQD, + msDSUserAccountControlComputed=0) + + # Enables the user account + self.ldb.enable_account("(sAMAccountName=%s)" % username) + + res = self._check_account(userdn, + badPwdCount=badPwdCount, + badPasswordTime=badPasswordTime, + logonCount=0, + lastLogon=0, + lastLogonTimestamp=('absent', None), + userAccountControl= + dsdb.UF_NORMAL_ACCOUNT, + msDSUserAccountControlComputed=0) + if lockOutObservationWindow != 0: + time.sleep(lockOutObservationWindow + 1) + effective_bad_password_count = 0 + else: + effective_bad_password_count = badPwdCount + + res = self._check_account(userdn, + badPwdCount=badPwdCount, + effective_bad_password_count=effective_bad_password_count, + badPasswordTime=badPasswordTime, + logonCount=0, + lastLogon=0, + lastLogonTimestamp=('absent', None), + userAccountControl= + dsdb.UF_NORMAL_ACCOUNT, + msDSUserAccountControlComputed=0) + + ldb = SamDB(url=self.host_url, credentials=creds, lp=self.lp) + + if lockOutObservationWindow == 0: + badPwdCount = 0 + effective_bad_password_count = 0 + if use_kerberos == MUST_USE_KERBEROS: + badPwdCount = 0 + effective_bad_password_count = 0 + + res = self._check_account(userdn, + badPwdCount=badPwdCount, + effective_bad_password_count=effective_bad_password_count, + badPasswordTime=badPasswordTime, + logonCount=(logoncount_relation, 0), + lastLogon=(lastlogon_relation, 0), + lastLogonTimestamp=('greater', badPasswordTime), + userAccountControl= + dsdb.UF_NORMAL_ACCOUNT, + msDSUserAccountControlComputed=0) + + logonCount = int(res[0]["logonCount"][0]) + lastLogon = int(res[0]["lastLogon"][0]) + lastLogonTimestamp = int(res[0]["lastLogonTimestamp"][0]) + if lastlogon_relation == 'greater': + self.assertGreater(lastLogon, badPasswordTime) + self.assertGreaterEqual(lastLogon, lastLogonTimestamp) + + res = self._check_account(userdn, + badPwdCount=badPwdCount, + effective_bad_password_count=effective_bad_password_count, + badPasswordTime=badPasswordTime, + logonCount=logonCount, + lastLogon=lastLogon, + lastLogonTimestamp=lastLogonTimestamp, + userAccountControl= + dsdb.UF_NORMAL_ACCOUNT, + msDSUserAccountControlComputed=0) + return ldb + + def _reset_samr(self, res): + + # Now reset the lockout, by removing ACB_AUTOLOCK (which removes the lock, despite being a generated attribute) + samr_user = self._open_samr_user(res) + acb_info = self.samr.QueryUserInfo(samr_user, 16) + acb_info.acct_flags &= ~samr.ACB_AUTOLOCK + self.samr.SetUserInfo(samr_user, 16, acb_info) + self.samr.Close(samr_user) + def test_lockout_observation_window(self): lockout3krb5_creds = self.insta_creds(self.template_creds, username="lockout3krb5", diff --git a/source4/dsdb/tests/python/password_lockout_base.py b/source4/dsdb/tests/python/password_lockout_base.py index a340b33..ba2f904 100644 --- a/source4/dsdb/tests/python/password_lockout_base.py +++ b/source4/dsdb/tests/python/password_lockout_base.py @@ -26,15 +26,6 @@ class BasePasswordTestCase(samba.tests.TestCase): return self.samr.OpenUser(self.samr_domain, security.SEC_FLAG_MAXIMUM_ALLOWED, rid) - def _reset_samr(self, res): - - # Now reset the lockout, by removing ACB_AUTOLOCK (which removes the lock, despite being a generated attribute) - samr_user = self._open_samr_user(res) - acb_info = self.samr.QueryUserInfo(samr_user, 16) - acb_info.acct_flags &= ~samr.ACB_AUTOLOCK - self.samr.SetUserInfo(samr_user, 16, acb_info) - self.samr.Close(samr_user) - def _check_attribute(self, res, name, value): if value is None: self.assertTrue(name not in res[0], @@ -236,185 +227,6 @@ userPassword: """ + userpass + """ return ldb - def _testing_add_user(self, creds, lockOutObservationWindow=0): - username = creds.get_username() - userpass = creds.get_password() - userdn = "cn=%s,cn=users,%s" % (username, self.base_dn) - - use_kerberos = creds.get_kerberos_state() - if use_kerberos == MUST_USE_KERBEROS: - logoncount_relation = 'greater' - lastlogon_relation = 'greater' - else: - logoncount_relation = 'equal' - if lockOutObservationWindow == 0: - lastlogon_relation = 'greater' - else: - lastlogon_relation = 'equal' - - delete_force(self.ldb, userdn) - self.ldb.add({ - "dn": userdn, - "objectclass": "user", - "sAMAccountName": username}) - - self.addCleanup(delete_force, self.ldb, userdn) - - res = self._check_account(userdn, - badPwdCount=0, - badPasswordTime=0, - logonCount=0, - lastLogon=0, - lastLogonTimestamp=('absent', None), - userAccountControl= - dsdb.UF_NORMAL_ACCOUNT | - dsdb.UF_ACCOUNTDISABLE | - dsdb.UF_PASSWD_NOTREQD, - msDSUserAccountControlComputed= - dsdb.UF_PASSWORD_EXPIRED) - - # SAMR doesn't have any impact if dsdb.UF_LOCKOUT isn't present. - # It doesn't create "lockoutTime" = 0. - self._reset_samr(res) - - res = self._check_account(userdn, - badPwdCount=0, - badPasswordTime=0, - logonCount=0, - lastLogon=0, - lastLogonTimestamp=('absent', None), - userAccountControl= - dsdb.UF_NORMAL_ACCOUNT | - dsdb.UF_ACCOUNTDISABLE | - dsdb.UF_PASSWD_NOTREQD, - msDSUserAccountControlComputed= - dsdb.UF_PASSWORD_EXPIRED) - - # Tests a password change when we don't have any password yet with a - # wrong old password - try: - self.ldb.modify_ldif(""" -dn: """ + userdn + """ -changetype: modify -delete: userPassword -userPassword: noPassword -add: userPassword -userPassword: thatsAcomplPASS2 -""") - self.fail() - except LdbError, (num, msg): - self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) - # Windows (2008 at least) seems to have some small bug here: it - # returns "0000056A" on longer (always wrong) previous passwords. - self.assertTrue('00000056' in msg, msg) - - res = self._check_account(userdn, - badPwdCount=1, - badPasswordTime=("greater", 0), - logonCount=0, - lastLogon=0, - lastLogonTimestamp=('absent', None), - userAccountControl= - dsdb.UF_NORMAL_ACCOUNT | - dsdb.UF_ACCOUNTDISABLE | - dsdb.UF_PASSWD_NOTREQD, - msDSUserAccountControlComputed= - dsdb.UF_PASSWORD_EXPIRED) - badPwdCount = int(res[0]["badPwdCount"][0]) - badPasswordTime = int(res[0]["badPasswordTime"][0]) - - # Sets the initial user password with a "special" password change - # I think that this internally is a password set operation and it can - # only be performed by someone which has password set privileges on the - # account (at least in s4 we do handle it like that). - self.ldb.modify_ldif(""" -dn: """ + userdn + """ -changetype: modify -delete: userPassword -add: userPassword -userPassword: """ + userpass + """ -""") - - res = self._check_account(userdn, - badPwdCount=badPwdCount, - badPasswordTime=badPasswordTime, - logonCount=0, - lastLogon=0, - lastLogonTimestamp=('absent', None), - userAccountControl= - dsdb.UF_NORMAL_ACCOUNT | - dsdb.UF_ACCOUNTDISABLE | - dsdb.UF_PASSWD_NOTREQD, - msDSUserAccountControlComputed=0) - - # Enables the user account - self.ldb.enable_account("(sAMAccountName=%s)" % username) - - res = self._check_account(userdn, - badPwdCount=badPwdCount, - badPasswordTime=badPasswordTime, - logonCount=0, - lastLogon=0, - lastLogonTimestamp=('absent', None), - userAccountControl= - dsdb.UF_NORMAL_ACCOUNT, - msDSUserAccountControlComputed=0) - if lockOutObservationWindow != 0: - time.sleep(lockOutObservationWindow + 1) - effective_bad_password_count = 0 - else: - effective_bad_password_count = badPwdCount - - res = self._check_account(userdn, - badPwdCount=badPwdCount, - effective_bad_password_count=effective_bad_password_count, - badPasswordTime=badPasswordTime, - logonCount=0, - lastLogon=0, - lastLogonTimestamp=('absent', None), - userAccountControl= - dsdb.UF_NORMAL_ACCOUNT, - msDSUserAccountControlComputed=0) - - ldb = SamDB(url=self.host_url, credentials=creds, lp=self.lp) - - if lockOutObservationWindow == 0: - badPwdCount = 0 - effective_bad_password_count = 0 - if use_kerberos == MUST_USE_KERBEROS: - badPwdCount = 0 - effective_bad_password_count = 0 - - res = self._check_account(userdn, - badPwdCount=badPwdCount, - effective_bad_password_count=effective_bad_password_count, - badPasswordTime=badPasswordTime, - logonCount=(logoncount_relation, 0), - lastLogon=(lastlogon_relation, 0), - lastLogonTimestamp=('greater', badPasswordTime), - userAccountControl= - dsdb.UF_NORMAL_ACCOUNT, - msDSUserAccountControlComputed=0) - - logonCount = int(res[0]["logonCount"][0]) - lastLogon = int(res[0]["lastLogon"][0]) - lastLogonTimestamp = int(res[0]["lastLogonTimestamp"][0]) - if lastlogon_relation == 'greater': - self.assertGreater(lastLogon, badPasswordTime) - self.assertGreaterEqual(lastLogon, lastLogonTimestamp) - - res = self._check_account(userdn, - badPwdCount=badPwdCount, - effective_bad_password_count=effective_bad_password_count, - badPasswordTime=badPasswordTime, - logonCount=logonCount, - lastLogon=lastLogon, - lastLogonTimestamp=lastLogonTimestamp, - userAccountControl= - dsdb.UF_NORMAL_ACCOUNT, - msDSUserAccountControlComputed=0) - return ldb - def assertLoginFailure(self, url, creds, lp, errno=ERR_INVALID_CREDENTIALS): try: ldb = SamDB(url=url, credentials=creds, lp=lp) -- 1.9.1 From 7598a0e849275200f198d6e6e58887b6d1b78b40 Mon Sep 17 00:00:00 2001 From: Garming Sam Date: Fri, 7 Apr 2017 14:41:05 +1200 Subject: [PATCH 13/24] sam.c: Make NTLM login set logonCount when unset Previously, it only bothered if it was being incremented. Now on first logon, it should turn the unset logonCount to 0. Signed-off-by: Garming Sam --- source4/auth/sam.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/source4/auth/sam.c b/source4/auth/sam.c index 7df23d5..8e84e3e 100644 --- a/source4/auth/sam.c +++ b/source4/auth/sam.c @@ -901,6 +901,16 @@ NTSTATUS authsam_logon_success_accounting(struct ldb_context *sam_ctx, TALLOC_FREE(mem_ctx); return NT_STATUS_NO_MEMORY; } + } else { + /* Set an unset logonCount to 0 on first successful login */ + if (ldb_msg_find_ldb_val(msg, "logonCount") == NULL) { + ret = samdb_msg_add_int(sam_ctx, msg_mod, msg_mod, + "logonCount", 0); + if (ret != LDB_SUCCESS) { + TALLOC_FREE(mem_ctx); + return NT_STATUS_NO_MEMORY; + } + } } ret = samdb_rodc(sam_ctx, &am_rodc); -- 1.9.1 From 716d84519ee1dcec7da0c5e4994b467de42908f1 Mon Sep 17 00:00:00 2001 From: Garming Sam Date: Mon, 10 Apr 2017 10:16:57 +1200 Subject: [PATCH 14/24] tests/rodc: Add a number of tests for RODC-RWDC interaction This tests password fallback to RWDC in preloaded and non-preloaded cases. It also tests some basic scenarios around what things are replicated between the two DCs. Signed-off-by: Garming Sam Pair-programmed-with: Douglas Bagnall --- selftest/knownfail | 1 + source4/dsdb/tests/python/rodc_rwdc.py | 581 +++++++++++++++++++++++++++++++++ source4/selftest/tests.py | 7 + 3 files changed, 589 insertions(+) create mode 100644 source4/dsdb/tests/python/rodc_rwdc.py diff --git a/selftest/knownfail b/selftest/knownfail index 85467ad..08c5ce3 100644 --- a/selftest/knownfail +++ b/selftest/knownfail @@ -319,3 +319,4 @@ ^samba.tests.auth_log_pass_change.samba.tests.auth_log_pass_change.AuthLogPassChangeTests.test_rap_change_password\(ad_dc_ntvfs:local\) # We currently don't send referrals for LDAP modify of non-replicated attrs ^samba4.ldap.rodc.python\(rodc\).__main__.RodcTests.test_modify_nonreplicated.* +^samba4.ldap.rodc_rwdc.python.*.__main__.RodcRwdcTests.test_change_password_reveal_on_demand_kerberos diff --git a/source4/dsdb/tests/python/rodc_rwdc.py b/source4/dsdb/tests/python/rodc_rwdc.py new file mode 100644 index 0000000..5c6f866 --- /dev/null +++ b/source4/dsdb/tests/python/rodc_rwdc.py @@ -0,0 +1,581 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +"""Test communication of credentials etc, between an RODC and a RWDC. + +How does it work when the password is changed on the RWDC? +""" + +import optparse +import sys +import base64 +import uuid +import subprocess +import itertools +import time + +sys.path.insert(0, "bin/python") +import samba +import ldb + +from samba.tests.subunitrun import SubunitOptions, TestProgram +import samba.getopt as options + +from samba.auth import system_session +from samba.samdb import SamDB +from samba.credentials import Credentials, DONT_USE_KERBEROS, MUST_USE_KERBEROS +from samba import gensec, dsdb + + +def passwd_encode(pw): + return base64.b64encode(('"%s"' % pw).encode('utf-16-le')) + + +class RodcRwdcTestException(Exception): + pass + + +def make_creds(username, password, kerberos_state=None): + # use the global CREDS as a template + c = Credentials() + c.set_username(username) + c.set_password(password) + c.set_domain(CREDS.get_domain()) + c.set_realm(CREDS.get_realm()) + c.set_workstation(CREDS.get_workstation()) + + if kerberos_state is None: + kerberos_state = CREDS.get_kerberos_state() + c.set_kerberos_state(kerberos_state) + + print '-' * 73 + if kerberos_state == MUST_USE_KERBEROS: + print "we seem to be using kerberos for %s %s" % (username, password) + elif kerberos_state == DONT_USE_KERBEROS: + print "NOT using kerberos for %s %s" % (username, password) + else: + print "kerberos state is %s" % kerberos_state + + c.set_gensec_features(c.get_gensec_features() | + gensec.FEATURE_SEAL) + return c + + +def set_auto_replication(dc, allow): + credstring = '-U%s%%%s' % (CREDS.get_username(), + CREDS.get_password()) + + on_or_off = '-' if allow else '+' + + for opt in ['DISABLE_INBOUND_REPL', + 'DISABLE_OUTBOUND_REPL']: + cmd = ['bin/samba-tool', + 'drs', 'options', + credstring, dc, + "--dsa-option=%s%s" % (on_or_off, opt)] + + p = subprocess.Popen(cmd, + stderr=subprocess.PIPE, + stdout=subprocess.PIPE) + stdout, stderr = p.communicate() + if p.returncode: + if 'LDAP_REFERRAL' not in stderr: + raise RodcRwdcTestException() + print ("ignoring +%s REFERRAL error; assuming %s is RODC" % + (opt, dc)) + + +def preload_rodc_user(user_dn): + credstring = '-U%s%%%s' % (CREDS.get_username(), + CREDS.get_password()) + + set_auto_replication(RWDC, True) + cmd = ['bin/samba-tool', + 'rodc', 'preload', + user_dn, + credstring, + '--server', RWDC,] + + print ' '.join(cmd) + subprocess.check_call(cmd) + set_auto_replication(RWDC, False) + + + +def get_server_ref_from_samdb(samdb): + server_name = samdb.get_serverName() + res = samdb.search(server_name, + scope=ldb.SCOPE_BASE, + attrs=['serverReference']) + + return res[0]['serverReference'][0] + + + +class RodcRwdcTests(samba.tests.TestCase): + counter = itertools.count(1).next + + def force_replication(self, base=None): + if base is None: + base = self.base_dn + + # XXX feels like a horrendous way to do it. + credstring = '-U%s%%%s' % (CREDS.get_username(), + CREDS.get_password()) + cmd = ['bin/samba-tool', + 'drs', 'replicate', + RODC, RWDC, base, + credstring, + '--sync-forced'] + + p = subprocess.Popen(cmd, + stderr=subprocess.PIPE, + stdout=subprocess.PIPE) + stdout, stderr = p.communicate() + if p.returncode: + print "failed with code %s" % p.returncode + print ' '.join(cmd) + print "stdout" + print stdout + print "stderr" + print stderr + raise RodcRwdcTestException() + + def tearDown(self): + super(RodcRwdcTests, self).tearDown() + self.rwdc_db.set_dsheuristics(self.rwdc_dsheuristics) + CREDS.set_kerberos_state(DONT_USE_KERBEROS) + set_auto_replication(RWDC, True) + + def setUp(self): + super(RodcRwdcTests, self).setUp() + self.rodc_db = SamDB('ldap://%s' % RODC, credentials=CREDS, + session_info=system_session(LP), lp=LP) + + self.rwdc_db = SamDB('ldap://%s' % RWDC, credentials=CREDS, + session_info=system_session(LP), lp=LP) + + self.base_dn = self.rwdc_db.domain_dn() + + root = self.rodc_db.search(base='', scope=ldb.SCOPE_BASE, + attrs=['dsServiceName']) + self.service = root[0]['dsServiceName'][0] + self.tag = uuid.uuid4().hex + + self.rwdc_dsheuristics = self.rwdc_db.get_dsheuristics() + self.rwdc_db.set_dsheuristics("000000001") + + set_auto_replication(RWDC, False) + + # make sure DCs are synchronized before the test + self.force_replication() + self.rwdc_dn = get_server_ref_from_samdb(self.rwdc_db) + self.rodc_dn = get_server_ref_from_samdb(self.rodc_db) + + def assertReferral(self, fn, *args, **kwargs): + try: + fn(*args, **kwargs) + self.fail("failed to raise ldap referral") + except ldb.LdbError as (code, msg): + self.assertEqual(code, ldb.ERR_REFERRAL, + "expected referral, got %s %s" % (code, msg)) + + def _test_rodc_dsheuristics(self): + d = self.rodc_db.get_dsheuristics() + self.assertReferral(self.rodc_db.set_dsheuristics, "000000001") + self.assertReferral(self.rodc_db.set_dsheuristics, d) + + def TEST_rodc_heuristics_kerberos(self): + CREDS.set_kerberos_state(MUST_USE_KERBEROS) + self._test_rodc_dsheuristics() + + def TEST_rodc_heuristics_ntlm(self): + CREDS.set_kerberos_state(DONT_USE_KERBEROS) + self._test_rodc_dsheuristics() + + def _test_add(self, objects, cross_ncs=False): + for o in objects: + dn = o['dn'] + if cross_ncs: + base = str(self.rwdc_db.get_config_basedn()) + controls = ["search_options:1:2"] + cn = dn.split(',', 1)[0] + expression = '(%s)' % cn + else: + base = dn + controls = [] + expression = None + + try: + res = self.rodc_db.search(base, + expression=expression, + scope=ldb.SCOPE_SUBTREE, + attrs=['dn'], + controls=controls) + self.assertEqual(len(res), 0) + except ldb.LdbError, e: + if e.args[0] != ldb.ERR_NO_SUCH_OBJECT: + raise + + try: + self.rwdc_db.add(o) + except ldb.LdbError as (ecode, emsg): + self.fail("Failed to add %s to rwdc: ldb error: %s %s" % + (ecode, emsg)) + + if cross_ncs: + self.force_replication(base=base) + else: + self.force_replication() + + try: + res = self.rodc_db.search(base, + expression=expression, + scope=ldb.SCOPE_SUBTREE, + attrs=['dn'], + controls=controls) + self.assertEqual(len(res), 1) + except ldb.LdbError, e: + self.assertNotEqual(e.args[0], ldb.ERR_NO_SUCH_OBJECT, + "replication seems to have failed") + + def _test_add_replicated_objects(self, mode): + tag = "%s%s" % (self.tag, mode) + self._test_add([ + { + 'dn': "ou=%s1,%s" % (tag, self.base_dn), + "objectclass": "organizationalUnit" + }, + { + 'dn': "cn=%s2,%s" % (tag, self.base_dn), + "objectclass": "user" + }, + { + 'dn': "cn=%s3,%s" % (tag, self.base_dn), + "objectclass": "group" + }, + ]) + + def test_add_replicated_objects_kerberos(self): + CREDS.set_kerberos_state(MUST_USE_KERBEROS) + self._test_add_replicated_objects('kerberos') + + def test_add_replicated_objects_ntlm(self): + CREDS.set_kerberos_state(DONT_USE_KERBEROS) + self._test_add_replicated_objects('ntlm') + + def _test_add_replicated_connections(self, mode): + tag = "%s%s" % (self.tag, mode) + self._test_add([ + { + 'dn': "cn=%sfoofoofoo,%s" % (tag, self.service), + "objectclass": "NTDSConnection", + 'enabledConnection': 'TRUE', + 'fromServer': self.base_dn, + 'options': '0' + }, + ], cross_ncs=True) + + def test_add_replicated_connections_kerberos(self): + CREDS.set_kerberos_state(MUST_USE_KERBEROS) + self._test_add_replicated_connections('kerberos') + + def test_add_replicated_connections_ntlm(self): + CREDS.set_kerberos_state(DONT_USE_KERBEROS) + self._test_add_replicated_connections('ntlm') + + def _test_modify_replicated_attributes(self): + dn = 'CN=Guest,CN=Users,' + self.base_dn + value = self.tag + for attr in ['carLicense', 'middleName']: + m = ldb.Message() + m.dn = ldb.Dn(self.rwdc_db, dn) + m[attr] = ldb.MessageElement(value, + ldb.FLAG_MOD_REPLACE, + attr) + try: + self.rwdc_db.modify(m) + except ldb.LdbError as e: + self.fail("Failed to modify %s %s on RWDC %s with %s" % + (dn, attr, RWDC, e)) + + self.force_replication() + + try: + res = self.rodc_db.search(dn, + scope=ldb.SCOPE_SUBTREE, + attrs=[attr]) + results = [x[attr][0] for x in res] + self.assertEqual(results, [value]) + except ldb.LdbError, e: + self.assertNotEqual(e.args[0], ldb.ERR_NO_SUCH_OBJECT, + "replication seems to have failed") + + def test_modify_replicated_attributes_kerberos(self): + CREDS.set_kerberos_state(MUST_USE_KERBEROS) + self._test_modify_replicated_attributes() + + def test_modify_replicated_attributes_ntlm(self): + CREDS.set_kerberos_state(DONT_USE_KERBEROS) + self._test_modify_replicated_attributes() + + def _test_add_modify_delete(self): + dn = "cn=%s_add_modify,%s" % (self.tag, self.base_dn) + values = ["%s%s" % (i, self.tag) for i in range(3)] + attr = "carLicense" + self._test_add([ + { + 'dn': dn, + "objectclass": "user", + attr: values[0] + }, + ]) + self.force_replication() + for value in values[1:]: + + m = ldb.Message() + m.dn = ldb.Dn(self.rwdc_db, dn) + m[attr] = ldb.MessageElement(value, + ldb.FLAG_MOD_REPLACE, + attr) + try: + self.rwdc_db.modify(m) + except ldb.LdbError as e: + self.fail("Failed to modify %s %s on RWDC %s with %s" % + (dn, attr, RWDC, e)) + + self.force_replication() + + try: + res = self.rodc_db.search(dn, + scope=ldb.SCOPE_SUBTREE, + attrs=[attr]) + results = [x[attr][0] for x in res] + self.assertEqual(results, [value]) + except ldb.LdbError, e: + self.assertNotEqual(e.args[0], ldb.ERR_NO_SUCH_OBJECT, + "replication seems to have failed") + + self.rwdc_db.delete(dn) + self.force_replication() + try: + res = self.rodc_db.search(dn, + scope=ldb.SCOPE_SUBTREE, + attrs=[attr]) + if len(res) > 0: + self.fail("Failed to delete %s" % (dn)) + except ldb.LdbError, e: + self.assertEqual(e.args[0], ldb.ERR_NO_SUCH_OBJECT, + "Failed to delete %s" % (dn)) + + def test_add_modify_delete_kerberos(self): + CREDS.set_kerberos_state(MUST_USE_KERBEROS) + self._test_add_modify_delete() + + def test_add_modify_delete_ntlm(self): + CREDS.set_kerberos_state(DONT_USE_KERBEROS) + self._test_add_modify_delete() + + def _new_user(self): + username = "u%sX%s" % (self.tag[:12], self.counter()) + password = 'password#1' + dn = 'CN=%s,CN=Users,%s' % (username, self.base_dn) + o = { + 'dn': dn, + "objectclass": "user", + 'sAMAccountName': username, + } + try: + self.rwdc_db.add(o) + except ldb.LdbError as e: + self.fail("Failed to add %s to rwdc: ldb error: %s" % (o, e)) + + self.rwdc_db.modify_ldif("dn: %s\n" + "changetype: modify\n" + "delete: userPassword\n" + "add: userPassword\n" + "userPassword: %s\n" % (dn, password)) + self.rwdc_db.enable_account("(sAMAccountName=%s)" % username) + return (dn, username, password) + + def _change_password(self, user_dn, old_password, new_password): + self.rwdc_db.modify_ldif( + "dn: %s\n" + "changetype: modify\n" + "delete: userPassword\n" + "userPassword: %s\n" + "add: userPassword\n" + "userPassword: %s\n" % (user_dn, old_password, new_password)) + + def try_ldap_logon(self, server, creds, errno=None): + try: + tmpdb = SamDB('ldap://%s' % server, credentials=creds, + session_info=system_session(LP), lp=LP) + if errno is not None: + self.fail("logon failed to fail with ldb error %s" % errno) + except ldb.LdbError as (code, msg): + if code != errno: + if errno is None: + self.fail("logon incorrectly raised ldb error (code=%s)" % + code) + else: + self.fail("logon failed to raise correct ldb error" + "Expected: %s Got: %s" % + (errno, code)) + + + def zero_min_password_age(self): + min_pwd_age = int(self.rwdc_db.get_minPwdAge()) + if min_pwd_age != 0: + self.rwdc_db.set_minPwdAge('0') + + def _test_ldap_change_password(self, errno=None): + self.zero_min_password_age() + + dn, username, password = self._new_user() + creds1 = make_creds(username, password) + + # With NTLM, this should fail on RODC before replication, + # because the user isn't known. + self.try_ldap_logon(RODC, creds1, ldb.ERR_INVALID_CREDENTIALS) + self.force_replication() + + # Now the user is replicated to RODC, so logon should work + self.try_ldap_logon(RODC, creds1) + + passwords = ['password#%s' % i for i in range(1, 6)] + for prev, password in zip(passwords[:-1], passwords[1:]): + self._change_password(dn, prev, password) + + # The password has changed enough times to make the old + # password invalid (though with kerberos that doesn't matter). + # For NTLM, the old creds should always fail + self.try_ldap_logon(RODC, creds1, errno) + self.try_ldap_logon(RWDC, creds1, errno) + + creds2 = make_creds(username, password) + + # new creds work straight away with NTLM, because although it + # doesn't have the password, it knows the user and forwards + # the query. + self.try_ldap_logon(RODC, creds2) + self.try_ldap_logon(RWDC, creds2) + + self.force_replication() + + # After another replication check RODC still works and fails, + # as appropriate to various creds + self.try_ldap_logon(RODC, creds2) + self.try_ldap_logon(RODC, creds1, errno) + + prev = password + password = 'password#6' + self._change_password(dn, prev, password) + creds3 = make_creds(username, password) + + # previous password should still work. + self.try_ldap_logon(RWDC, creds2) + self.try_ldap_logon(RODC, creds2) + + # new password should still work. + self.try_ldap_logon(RWDC, creds3) + self.try_ldap_logon(RODC, creds3) + + # old password should still fail (but not on kerberos). + self.try_ldap_logon(RWDC, creds1, errno) + self.try_ldap_logon(RODC, creds1, errno) + + def test_ldap_change_password_kerberos(self): + CREDS.set_kerberos_state(MUST_USE_KERBEROS) + self._test_ldap_change_password() + + def test_ldap_change_password_ntlm(self): + CREDS.set_kerberos_state(DONT_USE_KERBEROS) + self._test_ldap_change_password(ldb.ERR_INVALID_CREDENTIALS) + + def _test_ldap_change_password_reveal_on_demand(self, errno=None): + self.zero_min_password_age() + + res = self.rodc_db.search(self.rodc_dn, + scope=ldb.SCOPE_BASE, + attrs=['msDS-RevealOnDemandGroup']) + + group = res[0]['msDS-RevealOnDemandGroup'][0] + + user_dn, username, password = self._new_user() + creds1 = make_creds(username, password) + + m = ldb.Message() + m.dn = ldb.Dn(self.rwdc_db, group) + m['member'] = ldb.MessageElement(user_dn, ldb.FLAG_MOD_ADD, 'member') + self.rwdc_db.modify(m) + + # Against Windows, this will just forward if no account exists on the KDC + # Therefore, this does not error on Windows. + self.try_ldap_logon(RODC, creds1, ldb.ERR_INVALID_CREDENTIALS) + + self.force_replication() + + # The proxy case + self.try_ldap_logon(RODC, creds1) + preload_rodc_user(user_dn) + + # Now the user AND password are replicated to RODC, so logon should work (not proxy case) + self.try_ldap_logon(RODC, creds1) + + passwords = ['password#%s' % i for i in range(1, 6)] + for prev, password in zip(passwords[:-1], passwords[1:]): + self._change_password(user_dn, prev, password) + + # The password has changed enough times to make the old + # password invalid, but the RODC shouldn't know that. + self.try_ldap_logon(RODC, creds1) + self.try_ldap_logon(RWDC, creds1, errno) + + creds2 = make_creds(username, password) + self.try_ldap_logon(RWDC, creds2) + self.try_ldap_logon(RODC, creds2, errno) + + + def test_change_password_reveal_on_demand_ntlm(self): + CREDS.set_kerberos_state(DONT_USE_KERBEROS) + self._test_ldap_change_password_reveal_on_demand(ldb.ERR_INVALID_CREDENTIALS) + + def test_change_password_reveal_on_demand_kerberos(self): + CREDS.set_kerberos_state(MUST_USE_KERBEROS) + self._test_ldap_change_password_reveal_on_demand() + +def main(): + global RODC, RWDC, CREDS, LP + parser = optparse.OptionParser( + "rodc_rwdc.py [options] ") + + sambaopts = options.SambaOptions(parser) + versionopts = options.VersionOptions(parser) + credopts = options.CredentialsOptions(parser) + subunitopts = SubunitOptions(parser) + + parser.add_option_group(sambaopts) + parser.add_option_group(versionopts) + parser.add_option_group(credopts) + parser.add_option_group(subunitopts) + + opts, args = parser.parse_args() + + LP = sambaopts.get_loadparm() + CREDS = credopts.get_credentials(LP) + CREDS.set_gensec_features(CREDS.get_gensec_features() | + gensec.FEATURE_SEAL) + + try: + RODC, RWDC = args + except ValueError: + parser.print_usage() + sys.exit(1) + + set_auto_replication(RWDC, True) + try: + TestProgram(module=__name__, opts=subunitopts) + finally: + set_auto_replication(RWDC, True) + +main() diff --git a/source4/selftest/tests.py b/source4/selftest/tests.py index a23af86..6f4f8f9 100755 --- a/source4/selftest/tests.py +++ b/source4/selftest/tests.py @@ -647,6 +647,13 @@ plantestsuite_loadlist("samba4.ldap.rodc.python(rodc)", "rodc", '$SERVER', '-U"$USERNAME%$PASSWORD"', '--workgroup=$DOMAIN', '$LOADLIST', '$LISTOPT']) +plantestsuite_loadlist("samba4.ldap.rodc_rwdc.python(rodc)", "rodc:local", + [python, + os.path.join(samba4srcdir, + "dsdb/tests/python/rodc_rwdc.py"), + '$SERVER', '$DC_SERVER', '-U"$USERNAME%$PASSWORD"', + '--workgroup=$DOMAIN', '$LOADLIST', '$LISTOPT']) + for env in ["ad_dc_ntvfs", "fl2000dc", "fl2003dc", "fl2008r2dc"]: plantestsuite_loadlist("samba4.ldap_schema.python(%s)" % env, env, [python, os.path.join(samba4srcdir, "dsdb/tests/python/ldap_schema.py"), '$SERVER', '-U"$USERNAME%$PASSWORD"', '--workgroup=$DOMAIN', '$LOADLIST', '$LISTOPT']) plantestsuite("samba4.ldap.possibleInferiors.python(%s)" % env, env, [python, os.path.join(samba4srcdir, "dsdb/samdb/ldb_modules/tests/possibleinferiors.py"), "ldap://$SERVER", '-U"$USERNAME%$PASSWORD"', "-W$DOMAIN"]) -- 1.9.1 From 05c599d71a8a22ed122e6d701b9da081cd354375 Mon Sep 17 00:00:00 2001 From: Garming Sam Date: Thu, 6 Apr 2017 16:26:26 +1200 Subject: [PATCH 15/24] password_lockout: Tests against RODC (once preloaded) In this scenario, both the login server and the verification server are the RODC. This tests that a user is locked out correctly once the lockout limit is reached and they are also unlocked correctly when the lockout time period expires. Signed-off-by: Garming Sam --- selftest/knownfail | 1 + source4/dsdb/tests/python/password_lockout_base.py | 12 ++ source4/dsdb/tests/python/rodc_rwdc.py | 134 ++++++++++++++++++++- 3 files changed, 145 insertions(+), 2 deletions(-) diff --git a/selftest/knownfail b/selftest/knownfail index 08c5ce3..66d3d09 100644 --- a/selftest/knownfail +++ b/selftest/knownfail @@ -320,3 +320,4 @@ # We currently don't send referrals for LDAP modify of non-replicated attrs ^samba4.ldap.rodc.python\(rodc\).__main__.RodcTests.test_modify_nonreplicated.* ^samba4.ldap.rodc_rwdc.python.*.__main__.RodcRwdcTests.test_change_password_reveal_on_demand_kerberos +^samba4.ldap.rodc_rwdc.python\(rodc\).__main__.RodcRwdcTests.test_login_lockout_.* diff --git a/source4/dsdb/tests/python/password_lockout_base.py b/source4/dsdb/tests/python/password_lockout_base.py index ba2f904..b63da8e 100644 --- a/source4/dsdb/tests/python/password_lockout_base.py +++ b/source4/dsdb/tests/python/password_lockout_base.py @@ -85,6 +85,17 @@ class BasePasswordTestCase(samba.tests.TestCase): return self.assertEqual(mode, not mode, "Invalid Mode[%s]" % mode) + def _check_account_initial(self, userdn): + self._check_account(userdn, + badPwdCount=0, + badPasswordTime=0, + logonCount=0, + lastLogon=0, + lastLogonTimestamp=("absent", None), + userAccountControl= + dsdb.UF_NORMAL_ACCOUNT, + msDSUserAccountControlComputed=0) + def _check_account(self, dn, badPwdCount=None, badPasswordTime=None, @@ -214,6 +225,7 @@ userPassword: """ + userpass + """ username=username, userpass=userpass+"X", kerberos_state=use_kerberos) + self._check_account_initial(userdn) # Fail once to get a badPasswordTime try: diff --git a/source4/dsdb/tests/python/rodc_rwdc.py b/source4/dsdb/tests/python/rodc_rwdc.py index 5c6f866..f60ec39 100644 --- a/source4/dsdb/tests/python/rodc_rwdc.py +++ b/source4/dsdb/tests/python/rodc_rwdc.py @@ -24,7 +24,10 @@ from samba.auth import system_session from samba.samdb import SamDB from samba.credentials import Credentials, DONT_USE_KERBEROS, MUST_USE_KERBEROS from samba import gensec, dsdb +from ldb import SCOPE_BASE, LdbError, ERR_INVALID_CREDENTIALS +from samba.dcerpc import security, samr +import password_lockout_base def passwd_encode(pw): return base64.b64encode(('"%s"' % pw).encode('utf-16-le')) @@ -111,7 +114,7 @@ def get_server_ref_from_samdb(samdb): -class RodcRwdcTests(samba.tests.TestCase): +class RodcRwdcTests(password_lockout_base.BasePasswordTestCase): counter = itertools.count(1).next def force_replication(self, base=None): @@ -140,6 +143,10 @@ class RodcRwdcTests(samba.tests.TestCase): print stderr raise RodcRwdcTestException() + def _check_account_initial(self, dn): + self.force_replication() + return super(RodcRwdcTests, self)._check_account_initial(dn) + def tearDown(self): super(RodcRwdcTests, self).tearDown() self.rwdc_db.set_dsheuristics(self.rwdc_dsheuristics) @@ -147,13 +154,30 @@ class RodcRwdcTests(samba.tests.TestCase): set_auto_replication(RWDC, True) def setUp(self): - super(RodcRwdcTests, self).setUp() self.rodc_db = SamDB('ldap://%s' % RODC, credentials=CREDS, session_info=system_session(LP), lp=LP) self.rwdc_db = SamDB('ldap://%s' % RWDC, credentials=CREDS, session_info=system_session(LP), lp=LP) + # Define variables for BasePasswordTestCase + self.lp = LP + self.global_creds = CREDS + self.host = RWDC + self.host_url = 'ldap://%s' % RWDC + self.ldb = SamDB(url='ldap://%s' % RWDC, session_info=system_session(self.lp), + credentials=self.global_creds, lp=self.lp) + + super(RodcRwdcTests, self).setUp() + self.host = RODC + self.host_url = 'ldap://%s' % RODC + self.ldb = SamDB(url='ldap://%s' % RODC, session_info=system_session(self.lp), + credentials=self.global_creds, lp=self.lp) + + self.samr = samr.samr("ncacn_ip_tcp:%s[seal]" % self.host, self.lp, self.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) + self.base_dn = self.rwdc_db.domain_dn() root = self.rodc_db.search(base='', scope=ldb.SCOPE_BASE, @@ -544,6 +568,112 @@ class RodcRwdcTests(samba.tests.TestCase): CREDS.set_kerberos_state(MUST_USE_KERBEROS) self._test_ldap_change_password_reveal_on_demand() + def test_login_lockout_krb5(self): + username = self.lockout1krb5_creds.get_username() + userpass = self.lockout1krb5_creds.get_password() + userdn = "cn=%s,cn=users,%s" % (username, self.base_dn) + + preload_rodc_user(userdn) + + use_kerberos = self.lockout1krb5_creds.get_kerberos_state() + fail_creds = self.insta_creds(self.template_creds, + username=username, + userpass=userpass+"X", + kerberos_state=use_kerberos) + + try: + ldb = SamDB(url=self.host_url, credentials=fail_creds, lp=self.lp) + self.fail() + except LdbError, (num, msg): + self.assertEquals(num, ERR_INVALID_CREDENTIALS) + + # Succeed to reset everything to 0 + success_creds = self.insta_creds(self.template_creds, + username=username, + userpass=userpass, + kerberos_state=use_kerberos) + + ldb = SamDB(url=self.host_url, credentials=success_creds, lp=self.lp) + + self._test_login_lockout(self.lockout1krb5_creds) + + def test_login_lockout_ntlm(self): + username = self.lockout1ntlm_creds.get_username() + userpass = self.lockout1ntlm_creds.get_password() + userdn = "cn=%s,cn=users,%s" % (username, self.base_dn) + + preload_rodc_user(userdn) + + use_kerberos = self.lockout1ntlm_creds.get_kerberos_state() + fail_creds = self.insta_creds(self.template_creds, + username=username, + userpass=userpass+"X", + kerberos_state=use_kerberos) + + try: + ldb = SamDB(url=self.host_url, credentials=fail_creds, lp=self.lp) + self.fail() + except LdbError, (num, msg): + self.assertEquals(num, ERR_INVALID_CREDENTIALS) + + # Succeed to reset everything to 0 + ldb = SamDB(url=self.host_url, credentials=self.lockout1ntlm_creds, lp=self.lp) + + self._test_login_lockout(self.lockout1ntlm_creds) + + def test_multiple_logon_krb5(self): + username = self.lockout1krb5_creds.get_username() + userpass = self.lockout1krb5_creds.get_password() + userdn = "cn=%s,cn=users,%s" % (username, self.base_dn) + + preload_rodc_user(userdn) + + use_kerberos = self.lockout1krb5_creds.get_kerberos_state() + fail_creds = self.insta_creds(self.template_creds, + username=username, + userpass=userpass+"X", + kerberos_state=use_kerberos) + + try: + ldb = SamDB(url=self.host_url, credentials=fail_creds, lp=self.lp) + self.fail() + except LdbError, (num, msg): + self.assertEquals(num, ERR_INVALID_CREDENTIALS) + + # Succeed to reset everything to 0 + success_creds = self.insta_creds(self.template_creds, + username=username, + userpass=userpass, + kerberos_state=use_kerberos) + + ldb = SamDB(url=self.host_url, credentials=success_creds, lp=self.lp) + + self._test_multiple_logon(self.lockout1krb5_creds) + + def test_multiple_logon_ntlm(self): + username = self.lockout1ntlm_creds.get_username() + userdn = "cn=%s,cn=users,%s" % (username, self.base_dn) + userpass = self.lockout1ntlm_creds.get_password() + + preload_rodc_user(userdn) + + use_kerberos = self.lockout1ntlm_creds.get_kerberos_state() + fail_creds = self.insta_creds(self.template_creds, + username=username, + userpass=userpass+"X", + kerberos_state=use_kerberos) + + try: + ldb = SamDB(url=self.host_url, credentials=fail_creds, lp=self.lp) + self.fail() + except LdbError, (num, msg): + self.assertEquals(num, ERR_INVALID_CREDENTIALS) + + # Succeed to reset everything to 0 + ldb = SamDB(url=self.host_url, credentials=self.lockout1ntlm_creds, lp=self.lp) + + self._test_multiple_logon(self.lockout1ntlm_creds) + def main(): global RODC, RWDC, CREDS, LP parser = optparse.OptionParser( -- 1.9.1 From ce11660682cadef9088edc57f88fe52b277189ff Mon Sep 17 00:00:00 2001 From: Garming Sam Date: Fri, 17 Mar 2017 16:09:06 +1300 Subject: [PATCH 16/24] drepl: Add partial attribute set in the case of repl secret Against Windows, the call will always fail without it. Signed-off-by: Garming Sam --- source4/dsdb/repl/drepl_out_helpers.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source4/dsdb/repl/drepl_out_helpers.c b/source4/dsdb/repl/drepl_out_helpers.c index ac0b947..d526f45 100644 --- a/source4/dsdb/repl/drepl_out_helpers.c +++ b/source4/dsdb/repl/drepl_out_helpers.c @@ -498,7 +498,7 @@ static void dreplsrv_op_pull_source_get_changes_trigger(struct tevent_req *req) return; } replica_flags &= ~DRSUAPI_DRS_WRIT_REP; - } else if (partition->rodc_replica) { + } else if (partition->rodc_replica || state->op->extended_op == DRSUAPI_EXOP_REPL_SECRET) { bool for_schema = false; if (ldb_dn_compare_base(schema_dn, partition->dn) == 0) { for_schema = true; -- 1.9.1 From dd477cd8d8f827b5bbb994d8accd24fa79f49c6c Mon Sep 17 00:00:00 2001 From: Garming Sam Date: Fri, 24 Mar 2017 10:24:21 +1300 Subject: [PATCH 17/24] rodc: Allow local RODC changes with version 0 These changes will get clobbered by RWDCs through replication. This behaviour is required for lockoutTime to enforce the password lockout locally on the RODC (and is consistent with Windows). Signed-off-by: Garming Sam --- source4/dsdb/samdb/ldb_modules/repl_meta_data.c | 14 ++++++++++++-- source4/dsdb/samdb/samdb.h | 6 ++++++ source4/setup/schema_samba4.ldif | 2 ++ 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/source4/dsdb/samdb/ldb_modules/repl_meta_data.c b/source4/dsdb/samdb/ldb_modules/repl_meta_data.c index 44ec1d4..851b4ad 100644 --- a/source4/dsdb/samdb/ldb_modules/repl_meta_data.c +++ b/source4/dsdb/samdb/ldb_modules/repl_meta_data.c @@ -235,7 +235,6 @@ static bool replmd_check_urgent_attribute(const struct ldb_message_element *el) return false; } - static int replmd_replicated_apply_isDeleted(struct replmd_replicated_request *ar); /* @@ -1506,6 +1505,7 @@ static int replmd_update_rpmd_element(struct ldb_context *ldb, md1 = &omd->ctr.ctr1.array[i]; md1->version++; md1->attid = attid; + if (md1->attid == DRSUAPI_ATTID_isDeleted) { const struct ldb_val *rdn_val = ldb_dn_get_rdn_val(msg->dn); const char* rdn; @@ -1532,6 +1532,15 @@ static int replmd_update_rpmd_element(struct ldb_context *ldb, md1->originating_usn = *seq_num; md1->local_usn = *seq_num; + if (ldb_request_get_control(req, DSDB_CONTROL_FORCE_RODC_LOCAL_CHANGE) != NULL) { + /* Force version to 0 to be overriden later via replication */ + bool am_rodc = false; + int ret = samdb_rodc(ldb, &am_rodc); + if (ret == LDB_SUCCESS && am_rodc) { + md1->version = 0; + } + } + return LDB_SUCCESS; } @@ -1837,7 +1846,8 @@ static int replmd_update_rpmd(struct ldb_module *module, /*if we are RODC and this is a DRSR update then its ok*/ if (!ldb_request_get_control(req, DSDB_CONTROL_REPLICATED_UPDATE_OID) - && !ldb_request_get_control(req, DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA)) { + && !ldb_request_get_control(req, DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA) + && !ldb_request_get_control(req, DSDB_CONTROL_FORCE_RODC_LOCAL_CHANGE)) { unsigned instanceType; ret = samdb_rodc(ldb, rodc); diff --git a/source4/dsdb/samdb/samdb.h b/source4/dsdb/samdb/samdb.h index 1e427d0..5dce37e 100644 --- a/source4/dsdb/samdb/samdb.h +++ b/source4/dsdb/samdb/samdb.h @@ -183,6 +183,12 @@ struct dsdb_control_password_user_account_control { /* passed when we want to thoroughly delete linked attributes */ #define DSDB_CONTROL_REPLMD_VANISH_LINKS "1.3.6.1.4.1.7165.4.3.29" +/* + * lockoutTime is a replicated attribute, but must be modified before + * connectivity occurs to allow password lockouts. + */ +#define DSDB_CONTROL_FORCE_RODC_LOCAL_CHANGE "1.3.6.1.4.1.7165.4.3.31" + #define DSDB_EXTENDED_REPLICATED_OBJECTS_OID "1.3.6.1.4.1.7165.4.4.1" struct dsdb_extended_replicated_object { struct ldb_message *msg; diff --git a/source4/setup/schema_samba4.ldif b/source4/setup/schema_samba4.ldif index 0189fb5..d8bc2d9 100644 --- a/source4/setup/schema_samba4.ldif +++ b/source4/setup/schema_samba4.ldif @@ -222,6 +222,8 @@ #Allocated: DSDB_CONTROL_SKIP_DUPLICATES_CHECK_OID 1.3.6.1.4.1.7165.4.3.28 #Allocated: DSDB_CONTROL_REPLMD_VANISH_LINKS 1.3.6.1.4.1.7165.4.3.29 #Allocated: LDB_CONTROL_RECALCULATE_RDN_OID 1.3.6.1.4.1.7165.4.3.30 +#Allocated: DSDB_CONTROL_FORCE_RODC_LOCAL_CHANGE 1.3.6.1.4.1.7165.4.3.31 + # Extended 1.3.6.1.4.1.7165.4.4.x #Allocated: DSDB_EXTENDED_REPLICATED_OBJECTS_OID 1.3.6.1.4.1.7165.4.4.1 -- 1.9.1 From c50da82125a353739d61ccd6962835a2fde3a874 Mon Sep 17 00:00:00 2001 From: Garming Sam Date: Thu, 30 Mar 2017 15:50:01 +1300 Subject: [PATCH 18/24] replmd: Reduce calls to ldb_request_get_control Signed-off-by: Garming Sam --- source4/dsdb/samdb/ldb_modules/repl_meta_data.c | 41 ++++++++++++++++--------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/source4/dsdb/samdb/ldb_modules/repl_meta_data.c b/source4/dsdb/samdb/ldb_modules/repl_meta_data.c index 851b4ad..4f077c0 100644 --- a/source4/dsdb/samdb/ldb_modules/repl_meta_data.c +++ b/source4/dsdb/samdb/ldb_modules/repl_meta_data.c @@ -1367,6 +1367,7 @@ static int replmd_update_rpmd_element(struct ldb_context *ldb, const struct GUID *our_invocation_id, NTTIME now, bool is_schema_nc, + bool is_forced_rodc, struct ldb_request *req) { uint32_t i; @@ -1532,13 +1533,9 @@ static int replmd_update_rpmd_element(struct ldb_context *ldb, md1->originating_usn = *seq_num; md1->local_usn = *seq_num; - if (ldb_request_get_control(req, DSDB_CONTROL_FORCE_RODC_LOCAL_CHANGE) != NULL) { + if (is_forced_rodc) { /* Force version to 0 to be overriden later via replication */ - bool am_rodc = false; - int ret = samdb_rodc(ldb, &am_rodc); - if (ret == LDB_SUCCESS && am_rodc) { - md1->version = 0; - } + md1->version = 0; } return LDB_SUCCESS; @@ -1561,7 +1558,8 @@ static int replmd_update_rpmd_rdn_attr(struct ldb_context *ldb, struct replPropertyMetaDataBlob *omd, struct replmd_replicated_request *ar, NTTIME now, - bool is_schema_nc) + bool is_schema_nc, + bool is_forced_rodc) { const char *rdn_name = ldb_dn_get_rdn_name(msg->dn); const struct dsdb_attribute *rdn_attr = @@ -1592,7 +1590,8 @@ static int replmd_update_rpmd_rdn_attr(struct ldb_context *ldb, return replmd_update_rpmd_element(ldb, msg, &new_el, NULL, omd, ar->schema, &ar->seq_num, &ar->our_invocation_id, - now, is_schema_nc, ar->req); + now, is_schema_nc, is_forced_rodc, + ar->req); } @@ -1639,6 +1638,7 @@ static int replmd_update_rpmd(struct ldb_module *module, bool rmd_is_provided; bool rmd_is_just_resorted = false; const char *not_rename_attrs[4 + msg->num_elements]; + bool is_forced_rodc = false; if (rename_attrs) { attrs = rename_attrs; @@ -1655,6 +1655,17 @@ static int replmd_update_rpmd(struct ldb_module *module, ldb = ldb_module_get_ctx(module); + ret = samdb_rodc(ldb, rodc); + if (ret != LDB_SUCCESS) { + DEBUG(4, (__location__ ": unable to tell if we are an RODC\n")); + *rodc = false; + } + + if (*rodc && + ldb_request_get_control(req, DSDB_CONTROL_FORCE_RODC_LOCAL_CHANGE)) { + is_forced_rodc = true; + } + our_invocation_id = samdb_ntds_invocation_id(ldb); if (!our_invocation_id) { /* this happens during an initial vampire while @@ -1791,6 +1802,7 @@ static int replmd_update_rpmd(struct ldb_module *module, &omd, schema, seq_num, our_invocation_id, now, is_schema_nc, + is_forced_rodc, req); if (ret != LDB_SUCCESS) { return ret; @@ -1847,13 +1859,10 @@ static int replmd_update_rpmd(struct ldb_module *module, /*if we are RODC and this is a DRSR update then its ok*/ if (!ldb_request_get_control(req, DSDB_CONTROL_REPLICATED_UPDATE_OID) && !ldb_request_get_control(req, DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA) - && !ldb_request_get_control(req, DSDB_CONTROL_FORCE_RODC_LOCAL_CHANGE)) { + && !is_forced_rodc) { unsigned instanceType; - ret = samdb_rodc(ldb, rodc); - if (ret != LDB_SUCCESS) { - DEBUG(4, (__location__ ": unable to tell if we are an RODC\n")); - } else if (*rodc) { + if (*rodc) { ldb_set_errstring(ldb, "RODC modify is forbidden!"); return LDB_ERR_REFERRAL; } @@ -4953,7 +4962,8 @@ static int replmd_replicated_apply_add(struct replmd_replicated_request *ar) rdn_val = ldb_dn_get_rdn_val(msg->dn); ret = replmd_update_rpmd_rdn_attr(ldb, msg, rdn_val, NULL, - md, ar, now, is_schema_nc); + md, ar, now, is_schema_nc, + false); if (ret != LDB_SUCCESS) { ldb_asprintf_errstring(ldb, "%s: error during DRS repl ADD: %s", __func__, ldb_errstring(ldb)); return replmd_replicated_request_error(ar, ret); @@ -5683,7 +5693,8 @@ static int replmd_replicated_apply_merge(struct replmd_replicated_request *ar) if (renamed) { ret = replmd_update_rpmd_rdn_attr(ldb, msg, new_rdn, old_rdn, - &nmd, ar, now, is_schema_nc); + &nmd, ar, now, is_schema_nc, + false); if (ret != LDB_SUCCESS) { ldb_asprintf_errstring(ldb, "%s: error during DRS repl merge: %s", __func__, ldb_errstring(ldb)); return replmd_replicated_request_error(ar, ret); -- 1.9.1 From fa10c5f7df62086f95513f49224fd654004cefb9 Mon Sep 17 00:00:00 2001 From: Garming Sam Date: Tue, 28 Mar 2017 14:34:01 +1300 Subject: [PATCH 19/24] password-lockout: Allow RODC to ensure lockout and lockout reset Prior to this, the modification of lockoutTime triggered referrals. Signed-off-by: Garming Sam --- selftest/knownfail | 1 - source4/auth/sam.c | 79 +++++++++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 66 insertions(+), 14 deletions(-) diff --git a/selftest/knownfail b/selftest/knownfail index 66d3d09..08c5ce3 100644 --- a/selftest/knownfail +++ b/selftest/knownfail @@ -320,4 +320,3 @@ # We currently don't send referrals for LDAP modify of non-replicated attrs ^samba4.ldap.rodc.python\(rodc\).__main__.RodcTests.test_modify_nonreplicated.* ^samba4.ldap.rodc_rwdc.python.*.__main__.RodcRwdcTests.test_change_password_reveal_on_demand_kerberos -^samba4.ldap.rodc_rwdc.python\(rodc\).__main__.RodcRwdcTests.test_login_lockout_.* diff --git a/source4/auth/sam.c b/source4/auth/sam.c index 8e84e3e..9b0f061 100644 --- a/source4/auth/sam.c +++ b/source4/auth/sam.c @@ -702,13 +702,36 @@ NTSTATUS authsam_update_bad_pwd_count(struct ldb_context *sam_ctx, } if (msg_mod != NULL) { - ret = dsdb_modify(sam_ctx, msg_mod, 0); + struct ldb_request *req; + + ret = ldb_build_mod_req(&req, sam_ctx, sam_ctx, + msg_mod, + NULL, + NULL, + ldb_op_default_callback, + NULL); if (ret != LDB_SUCCESS) { - DEBUG(0, ("Failed to update badPwdCount, badPasswordTime or set lockoutTime on %s: %s\n", - ldb_dn_get_linearized(msg_mod->dn), ldb_errstring(sam_ctx))); - TALLOC_FREE(mem_ctx); - return NT_STATUS_INTERNAL_ERROR; + goto done; + } + + ret = ldb_request_add_control(req, + DSDB_CONTROL_FORCE_RODC_LOCAL_CHANGE, + false, NULL); + if (ret != LDB_SUCCESS) { + talloc_free(req); + goto done; } + + ret = dsdb_autotransaction_request(sam_ctx, req); + talloc_free(req); + } + +done: + if (ret != LDB_SUCCESS) { + DEBUG(0, ("Failed to update badPwdCount, badPasswordTime or set lockoutTime on %s: %s\n", + ldb_dn_get_linearized(msg_mod->dn), ldb_errstring(sam_ctx))); + TALLOC_FREE(mem_ctx); + return NT_STATUS_INTERNAL_ERROR; } TALLOC_FREE(mem_ctx); @@ -930,17 +953,47 @@ NTSTATUS authsam_logon_success_accounting(struct ldb_context *sam_ctx, } if (msg_mod->num_elements > 0) { - ret = dsdb_replace(sam_ctx, msg_mod, 0); + unsigned int i; + struct ldb_request *req; + + /* mark all the message elements as LDB_FLAG_MOD_REPLACE */ + for (i=0;inum_elements;i++) { + msg_mod->elements[i].flags = LDB_FLAG_MOD_REPLACE; + } + + ret = ldb_build_mod_req(&req, sam_ctx, sam_ctx, + msg_mod, + NULL, + NULL, + ldb_op_default_callback, + NULL); 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; + goto done; + } + + ret = ldb_request_add_control(req, + DSDB_CONTROL_FORCE_RODC_LOCAL_CHANGE, + false, NULL); + if (ret != LDB_SUCCESS) { + talloc_free(req); + goto done; } + + ret = dsdb_autotransaction_request(sam_ctx, req); + talloc_free(req); } + +done: + 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; } -- 1.9.1 From 6b4897cad942000e5313b3ed108650e853af6ebc Mon Sep 17 00:00:00 2001 From: Garming Sam Date: Tue, 28 Mar 2017 14:29:26 +1300 Subject: [PATCH 20/24] join.py: Allow RODC to have push replication at join Normally DsAddEntry connects to DRSUAPI, however not in the RODC case. This meant that it never called DsReplicaUpdateRefs and so never got push-replication after join. Signed-off-by: Garming Sam --- python/samba/join.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/python/samba/join.py b/python/samba/join.py index 88b8d4b..6a92435 100644 --- a/python/samba/join.py +++ b/python/samba/join.py @@ -964,8 +964,10 @@ class dc_join(object): if not ctx.RODC: r.options |= drsuapi.DRSUAPI_DRS_WRIT_REP - if ctx.drsuapi: - ctx.drsuapi.DsReplicaUpdateRefs(ctx.drsuapi_handle, 1, r) + if ctx.drsuapi is None: + ctx.drsuapi_connect() + + ctx.drsuapi.DsReplicaUpdateRefs(ctx.drsuapi_handle, 1, r) def join_finalise(ctx): """Finalise the join, mark us synchronised and setup secrets db.""" -- 1.9.1 From 48db98a56d56152cbbfe0369d0f7f70c5a15be54 Mon Sep 17 00:00:00 2001 From: Garming Sam Date: Wed, 29 Mar 2017 13:16:48 +1300 Subject: [PATCH 21/24] rodc/dns: Do not put a trailing dot at end of a DNS record This causes RESOLV_WRAPPER to not detect the record correctly (while also creating inconsistent and possibly breaking records). Signed-off-by: Garming Sam --- source4/dsdb/dns/dns_update.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/source4/dsdb/dns/dns_update.c b/source4/dsdb/dns/dns_update.c index 0591e34..959223d 100644 --- a/source4/dsdb/dns/dns_update.c +++ b/source4/dsdb/dns/dns_update.c @@ -531,36 +531,35 @@ static NTSTATUS dnsupdate_dnsupdate_RODC(struct irpc_message *msg, return NT_STATUS_OK; } - for (i=0; ir->in.dns_names->count; i++) { struct NL_DNS_NAME_INFO *n = &r->in.dns_names->names[i]; switch (n->type) { case NlDnsLdapAtSite: - dprintf(st->fd, "SRV _ldap._tcp.%s._sites.%s. %s %u\n", + dprintf(st->fd, "SRV _ldap._tcp.%s._sites.%s %s %u\n", site, dnsdomain, hostname, n->port); break; case NlDnsGcAtSite: - dprintf(st->fd, "SRV _ldap._tcp.%s._sites.gc._msdcs.%s. %s %u\n", + dprintf(st->fd, "SRV _ldap._tcp.%s._sites.gc._msdcs.%s %s %u\n", site, dnsdomain, hostname, n->port); break; case NlDnsDsaCname: - dprintf(st->fd, "CNAME %s._msdcs.%s. %s\n", + dprintf(st->fd, "CNAME %s._msdcs.%s %s\n", ntdsguid, dnsforest, hostname); break; case NlDnsKdcAtSite: - dprintf(st->fd, "SRV _kerberos._tcp.%s._sites.dc._msdcs.%s. %s %u\n", + dprintf(st->fd, "SRV _kerberos._tcp.%s._sites.dc._msdcs.%s %s %u\n", site, dnsdomain, hostname, n->port); break; case NlDnsDcAtSite: - dprintf(st->fd, "SRV _ldap._tcp.%s._sites.dc._msdcs.%s. %s %u\n", + dprintf(st->fd, "SRV _ldap._tcp.%s._sites.dc._msdcs.%s %s %u\n", site, dnsdomain, hostname, n->port); break; case NlDnsRfc1510KdcAtSite: - dprintf(st->fd, "SRV _kerberos._tcp.%s._sites.%s. %s %u\n", + dprintf(st->fd, "SRV _kerberos._tcp.%s._sites.%s %s %u\n", site, dnsdomain, hostname, n->port); break; case NlDnsGenericGcAtSite: - dprintf(st->fd, "SRV _gc._tcp.%s._sites.%s. %s %u\n", + dprintf(st->fd, "SRV _gc._tcp.%s._sites.%s %s %u\n", site, dnsforest, hostname, n->port); break; } -- 1.9.1 From 37ce2e5f7aeebc92d2b6e6709da57880d289d380 Mon Sep 17 00:00:00 2001 From: Garming Sam Date: Mon, 3 Apr 2017 15:31:14 +1200 Subject: [PATCH 22/24] dns_update: RODC updates should use lower case realm This is consistent with the standard update list we write. Signed-off-by: Garming Sam --- source4/dsdb/dns/dns_update.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source4/dsdb/dns/dns_update.c b/source4/dsdb/dns/dns_update.c index 959223d..8962b08 100644 --- a/source4/dsdb/dns/dns_update.c +++ b/source4/dsdb/dns/dns_update.c @@ -515,7 +515,7 @@ static NTSTATUS dnsupdate_dnsupdate_RODC(struct irpc_message *msg, /* find dnsdomain and dnsforest */ - dnsdomain = lpcfg_realm(s->task->lp_ctx); + dnsdomain = lpcfg_dnsdomain(s->task->lp_ctx); dnsforest = dnsdomain; /* find the hostname */ -- 1.9.1 From 0e8e6aba472d728e77c26623ebf5c71607601fca Mon Sep 17 00:00:00 2001 From: Garming Sam Date: Wed, 29 Mar 2017 11:24:50 +1300 Subject: [PATCH 23/24] drepl_server: Allow refresh of partitions on UpdateRef When we call UpdateRef, the push replication will not begin until the drepl_server has done its periodic refresh. If UpdateRefs is called, we should just send an IRPC message to call the refresh. NOTE: This has the same dependencies and issues as repl_secrets in auth_sam.c in terms of IRPC implementation. Signed-off-by: Garming Sam --- source4/rpc_server/drsuapi/dcesrv_drsuapi.h | 4 ++- source4/rpc_server/drsuapi/getncchanges.c | 4 ++- source4/rpc_server/drsuapi/updaterefs.c | 51 ++++++++++++++++++++++++++--- 3 files changed, 53 insertions(+), 6 deletions(-) diff --git a/source4/rpc_server/drsuapi/dcesrv_drsuapi.h b/source4/rpc_server/drsuapi/dcesrv_drsuapi.h index d376f7e..38375b5 100644 --- a/source4/rpc_server/drsuapi/dcesrv_drsuapi.h +++ b/source4/rpc_server/drsuapi/dcesrv_drsuapi.h @@ -40,7 +40,9 @@ struct drsuapi_bind_state { /* prototypes of internal functions */ -WERROR drsuapi_UpdateRefs(struct drsuapi_bind_state *b_state, TALLOC_CTX *mem_ctx, +WERROR drsuapi_UpdateRefs(struct imessaging_context *msg_ctx, + struct tevent_context *event_ctx, + struct drsuapi_bind_state *b_state, TALLOC_CTX *mem_ctx, struct drsuapi_DsReplicaUpdateRefsRequest1 *req); WERROR dcesrv_drsuapi_DsReplicaUpdateRefs(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, struct drsuapi_DsReplicaUpdateRefs *r); diff --git a/source4/rpc_server/drsuapi/getncchanges.c b/source4/rpc_server/drsuapi/getncchanges.c index 3f2ef8d..1f02f2c 100644 --- a/source4/rpc_server/drsuapi/getncchanges.c +++ b/source4/rpc_server/drsuapi/getncchanges.c @@ -2933,7 +2933,9 @@ allowed: to send notifies using the GC SPN */ ureq.options |= (req10->replica_flags & DRSUAPI_DRS_REF_GCSPN); - werr = drsuapi_UpdateRefs(b_state, mem_ctx, &ureq); + werr = drsuapi_UpdateRefs(dce_call->msg_ctx, + dce_call->event_ctx, b_state, + mem_ctx, &ureq); if (!W_ERROR_IS_OK(werr)) { DEBUG(0,(__location__ ": Failed UpdateRefs on %s for %s in DsGetNCChanges - %s\n", drs_ObjectIdentifier_to_string(mem_ctx, ncRoot), ureq.dest_dsa_dns_name, diff --git a/source4/rpc_server/drsuapi/updaterefs.c b/source4/rpc_server/drsuapi/updaterefs.c index 6fdbf2e..457f393 100644 --- a/source4/rpc_server/drsuapi/updaterefs.c +++ b/source4/rpc_server/drsuapi/updaterefs.c @@ -27,6 +27,8 @@ #include "rpc_server/drsuapi/dcesrv_drsuapi.h" #include "auth/session.h" #include "librpc/gen_ndr/ndr_drsuapi.h" +#include "librpc/gen_ndr/ndr_irpc_c.h" +#include "lib/messaging/irpc.h" struct repsTo { uint32_t count; @@ -121,6 +123,10 @@ static WERROR uref_del_dest(struct ldb_context *sam_ctx, TALLOC_CTX *mem_ctx, return WERR_OK; } +struct drepl_refresh_state { + struct dreplsrv_refresh r; +}; + /** * @brief Update the references for the given NC and the destination DSA object * @@ -128,6 +134,9 @@ static WERROR uref_del_dest(struct ldb_context *sam_ctx, TALLOC_CTX *mem_ctx, * will validate the request to update reference and then will add/del a repsTo * to the specified server referenced by its DSA GUID in the request. * + * @param[in] msg_ctx Messaging context for sending partition + * refresh in dreplsrv + * * @param[in] b_state A bind_state object * * @param[in] mem_ctx A talloc context for memory allocation @@ -139,7 +148,9 @@ static WERROR uref_del_dest(struct ldb_context *sam_ctx, TALLOC_CTX *mem_ctx, * @return WERR_OK is success, different error * otherwise. */ -WERROR drsuapi_UpdateRefs(struct drsuapi_bind_state *b_state, TALLOC_CTX *mem_ctx, +WERROR drsuapi_UpdateRefs(struct imessaging_context *msg_ctx, + struct tevent_context *event_ctx, + struct drsuapi_bind_state *b_state, TALLOC_CTX *mem_ctx, struct drsuapi_DsReplicaUpdateRefsRequest1 *req) { WERROR werr; @@ -147,6 +158,10 @@ WERROR drsuapi_UpdateRefs(struct drsuapi_bind_state *b_state, TALLOC_CTX *mem_ct struct ldb_dn *dn; struct ldb_dn *nc_root; struct ldb_context *sam_ctx = b_state->sam_ctx_system?b_state->sam_ctx_system:b_state->sam_ctx; + struct dcerpc_binding_handle *irpc_handle; + struct tevent_req *subreq; + struct drepl_refresh_state *state; + DEBUG(4,("DsReplicaUpdateRefs for host '%s' with GUID %s options 0x%08x nc=%s\n", req->dest_dsa_dns_name, GUID_string(mem_ctx, &req->dest_dsa_guid), @@ -201,7 +216,7 @@ WERROR drsuapi_UpdateRefs(struct drsuapi_bind_state *b_state, TALLOC_CTX *mem_ct if (req->options & DRSUAPI_DRS_ADD_REF) { struct repsFromTo1 dest; struct repsFromTo1OtherInfo oi; - + ZERO_STRUCT(dest); ZERO_STRUCT(oi); @@ -222,9 +237,36 @@ WERROR drsuapi_UpdateRefs(struct drsuapi_bind_state *b_state, TALLOC_CTX *mem_ct if (ldb_transaction_commit(sam_ctx) != LDB_SUCCESS) { DEBUG(0,(__location__ ": Failed to commit transaction on samdb: %s\n", ldb_errstring(sam_ctx))); - return WERR_DS_DRA_INTERNAL_ERROR; + return WERR_DS_DRA_INTERNAL_ERROR; } + state = talloc_zero(mem_ctx, struct drepl_refresh_state); + if (state == NULL) { + return WERR_OK; + } + + irpc_handle = irpc_binding_handle_by_name(mem_ctx, msg_ctx, + "dreplsrv", &ndr_table_irpc); + if (irpc_handle == NULL) { + /* dreplsrv is not running yet */ + TALLOC_FREE(state); + return WERR_OK; + } + + /* + * [Taken from auth_sam_trigger_repl_secret in auth_sam.c] + * + * This seem to rely on the current IRPC implementation, + * which delivers the message in the _send function. + * + * TODO: we need a ONE_WAY IRPC handle and register + * a callback and wait for it to be triggered! + */ + subreq = dcerpc_dreplsrv_refresh_r_send(state, event_ctx, + irpc_handle, &state->r); + TALLOC_FREE(subreq); + TALLOC_FREE(state); + return WERR_OK; failed: @@ -278,7 +320,8 @@ WERROR dcesrv_drsuapi_DsReplicaUpdateRefs(struct dcesrv_call_state *dce_call, TA } } - werr = drsuapi_UpdateRefs(b_state, mem_ctx, req); + werr = drsuapi_UpdateRefs(dce_call->msg_ctx, dce_call->event_ctx, + b_state, mem_ctx, req); #if 0 NDR_PRINT_FUNCTION_DEBUG(drsuapi_DsReplicaUpdateRefs, NDR_BOTH, r); -- 1.9.1 From fe7aa02ff198689784c8889e40f67cb31a8e0956 Mon Sep 17 00:00:00 2001 From: Garming Sam Date: Wed, 29 Mar 2017 15:21:04 +1300 Subject: [PATCH 24/24] updaterefs: Do not open transaction even when unnecessary This can be called during GetNCChanges (a generally read-only call), it is not wise to be blocking the database for no reason. Signed-off-by: Garming Sam --- source4/rpc_server/drsuapi/updaterefs.c | 53 +++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/source4/rpc_server/drsuapi/updaterefs.c b/source4/rpc_server/drsuapi/updaterefs.c index 457f393..9cf3556 100644 --- a/source4/rpc_server/drsuapi/updaterefs.c +++ b/source4/rpc_server/drsuapi/updaterefs.c @@ -35,6 +35,43 @@ struct repsTo { struct repsFromToBlob *r; }; +static WERROR uref_check_dest(struct ldb_context *sam_ctx, TALLOC_CTX *mem_ctx, + struct ldb_dn *dn, struct GUID *dest_guid, + uint32_t options) +{ + struct repsTo reps; + WERROR werr; + unsigned int i; + bool found = false; + + werr = dsdb_loadreps(sam_ctx, mem_ctx, dn, "repsTo", &reps.r, &reps.count); + if (!W_ERROR_IS_OK(werr)) { + return werr; + } + + for (i=0; idest_dsa_guid, + req->options); + if (W_ERROR_EQUAL(werr, WERR_DS_DRA_REF_ALREADY_EXISTS) || + W_ERROR_EQUAL(werr, WERR_DS_DRA_REF_NOT_FOUND)) { + if (req->options & DRSUAPI_DRS_GETCHG_CHECK) { + return WERR_OK; + } + return werr; + } + if (ldb_transaction_start(sam_ctx) != LDB_SUCCESS) { DEBUG(0,(__location__ ": Failed to start transaction on samdb: %s\n", ldb_errstring(sam_ctx))); -- 1.9.1