[WIP] Re: [PATCH] Some fixes for Samba RODC

Andrew Bartlett abartlet at samba.org
Sun May 28 22:29:25 UTC 2017


On Tue, 2017-04-18 at 17:03 +1200, Garming Sam via samba-technical
wrote:
> Hi,
> 
> The next set of RODC patches I am working on resolve most of the
> remaining RODC issues I have outlined. The patches make the RODC
> actually properly get a RWDC connection in winbindd. There are still
> some edge cases where the RODC may reuse old read-only connections,
> so
> that still is yet to be completely resolved.

> The patches allow forwarding of wrong password to a RWDC -- directly
> forwarding which allows for success in NTLM, while using dummy
> password
> fields for Kerberos. Local successes can now be forwarded to the RWDC
> to
> unlock the account across the domain using ResetBadPasswordCount in
> SendToSam (MS-SAMS). The client side code appears to work correctly
> against Windows. The server implementation of the reset bad password
> count in Samba is currently missing an access check to ensure only
> RODC
> cached accounts are modified. Otherwise, it all appears to be
> functional
> (albeit without any written tests).

Attached are the current patches, which I hope to push tomorrow, as
I've reviewed them all.  They make the changes to winbindd required to
implement these important features, and fill a big gap in our RODC
support.

Thanks,

Andrew Bartlett

-- 
Andrew Bartlett
https://samba.org/~abartlet/
Authentication Developer, Samba Team         https://samba.org
Samba Development and Support, Catalyst IT   
https://catalyst.net.nz/services/samba



-------------- next part --------------
From e48da601974f857f0f93074e3fbcb7d9526594dc Mon Sep 17 00:00:00 2001
From: Garming Sam <garming at catalyst.net.nz>
Date: Mon, 10 Apr 2017 14:40:20 +1200
Subject: [PATCH 01/26] samba-tool/spn: Add a missing newline to error message

Signed-off-by: Garming Sam <garming at catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet at samba.org>
---
 python/samba/netcmd/spn.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/python/samba/netcmd/spn.py b/python/samba/netcmd/spn.py
index 03d072ec9b6..001728cc651 100644
--- a/python/samba/netcmd/spn.py
+++ b/python/samba/netcmd/spn.py
@@ -67,8 +67,8 @@ class cmd_spn_list(Command):
                 for e in spns:
                     self.outf.write("\t %s\n" % e)
             else:
-                self.outf.write("User %s has no servicePrincipalName" %
-                    res[0].dn)
+                self.outf.write("User %s has no servicePrincipalName\n" %
+                                res[0].dn)
         else:
             raise CommandError("User %s not found" % user)
 
-- 
2.11.0


From 02827e42a82ee15e0360e418055249823de57bd6 Mon Sep 17 00:00:00 2001
From: Garming Sam <garming at catalyst.net.nz>
Date: Wed, 26 Apr 2017 10:39:09 +1200
Subject: [PATCH 02/26] tests/password_lockout: Remove unused users from base

They take extra time to set-up...

Signed-off-by: Garming Sam <garming at catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet at samba.org>
---
 source4/dsdb/tests/python/password_lockout.py      | 14 ++++++++++++++
 source4/dsdb/tests/python/password_lockout_base.py | 12 ------------
 2 files changed, 14 insertions(+), 12 deletions(-)

diff --git a/source4/dsdb/tests/python/password_lockout.py b/source4/dsdb/tests/python/password_lockout.py
index 09cb179b56f..e1a716e4807 100755
--- a/source4/dsdb/tests/python/password_lockout.py
+++ b/source4/dsdb/tests/python/password_lockout.py
@@ -69,6 +69,20 @@ class PasswordTests(password_lockout_base.BasePasswordTestCase):
                          credentials=self.global_creds, lp=self.lp)
         super(PasswordTests, self).setUp()
 
+        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.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 _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
index 190cedf1a19..1ea6e50cceb 100644
--- a/source4/dsdb/tests/python/password_lockout_base.py
+++ b/source4/dsdb/tests/python/password_lockout_base.py
@@ -351,23 +351,11 @@ lockoutThreshold: """ + str(lockoutThreshold) + """
                                                    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()
-- 
2.11.0


From 7df5aee99e25338170cf15dadbafddf1d4051c28 Mon Sep 17 00:00:00 2001
From: Garming Sam <garming at catalyst.net.nz>
Date: Mon, 20 Mar 2017 15:37:12 +1300
Subject: [PATCH 03/26] libads: Check cldap flags in libads/ldap

Pass down request flags and check they are respected with the response
flags. Otherwise, error out and pretend the connection never happened.

Signed-off-by: Garming Sam <garming at catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet at samba.org>
---
 source3/libads/cldap.c       | 38 ++++++++++++++++++++++++++++++++++++++
 source3/libads/cldap.h       |  3 +++
 source3/libads/ldap.c        | 12 +++++++++++-
 source3/libsmb/dsgetdcname.c | 37 -------------------------------------
 4 files changed, 52 insertions(+), 38 deletions(-)

diff --git a/source3/libads/cldap.c b/source3/libads/cldap.c
index 586a04a0e42..f4022a19905 100644
--- a/source3/libads/cldap.c
+++ b/source3/libads/cldap.c
@@ -22,6 +22,7 @@
 
 #include "includes.h"
 #include "../libcli/cldap/cldap.h"
+#include "../librpc/gen_ndr/ndr_netlogon.h"
 #include "../lib/tsocket/tsocket.h"
 #include "../lib/util/tevent_ntstatus.h"
 #include "libads/cldap.h"
@@ -47,6 +48,43 @@ struct cldap_multi_netlogon_state {
 static void cldap_multi_netlogon_done(struct tevent_req *subreq);
 static void cldap_multi_netlogon_next(struct tevent_req *subreq);
 
+/****************************************************************
+****************************************************************/
+
+#define RETURN_ON_FALSE(x) if (!(x)) return false;
+
+bool check_cldap_reply_required_flags(uint32_t ret_flags,
+				      uint32_t req_flags)
+{
+	if (req_flags == 0) {
+		return true;
+	}
+
+	if (req_flags & DS_PDC_REQUIRED)
+		RETURN_ON_FALSE(ret_flags & NBT_SERVER_PDC);
+
+	if (req_flags & DS_GC_SERVER_REQUIRED)
+		RETURN_ON_FALSE(ret_flags & NBT_SERVER_GC);
+
+	if (req_flags & DS_ONLY_LDAP_NEEDED)
+		RETURN_ON_FALSE(ret_flags & NBT_SERVER_LDAP);
+
+	if ((req_flags & DS_DIRECTORY_SERVICE_REQUIRED) ||
+	    (req_flags & DS_DIRECTORY_SERVICE_PREFERRED))
+		RETURN_ON_FALSE(ret_flags & NBT_SERVER_DS);
+
+	if (req_flags & DS_KDC_REQUIRED)
+		RETURN_ON_FALSE(ret_flags & NBT_SERVER_KDC);
+
+	if (req_flags & DS_TIMESERV_REQUIRED)
+		RETURN_ON_FALSE(ret_flags & NBT_SERVER_TIMESERV);
+
+	if (req_flags & DS_WRITABLE_REQUIRED)
+		RETURN_ON_FALSE(ret_flags & NBT_SERVER_WRITABLE);
+
+	return true;
+}
+
 /*
  * Do a parallel cldap ping to the servers. The first "min_servers"
  * are fired directly, the remaining ones in 100msec intervals. If
diff --git a/source3/libads/cldap.h b/source3/libads/cldap.h
index 9e42782052a..7fa1bdf8d60 100644
--- a/source3/libads/cldap.h
+++ b/source3/libads/cldap.h
@@ -27,6 +27,9 @@
 
 /* The following definitions come from libads/cldap.c  */
 
+bool check_cldap_reply_required_flags(uint32_t ret_flags,
+				      uint32_t req_flags);
+
 struct tevent_req *cldap_multi_netlogon_send(
 	TALLOC_CTX *mem_ctx, struct tevent_context *ev,
 	const struct tsocket_address * const *servers,
diff --git a/source3/libads/ldap.c b/source3/libads/ldap.c
index fdb729e90da..b2c57480f1e 100644
--- a/source3/libads/ldap.c
+++ b/source3/libads/ldap.c
@@ -279,7 +279,12 @@ static bool ads_try_connect(ADS_STRUCT *ads, bool gc,
 	SAFE_FREE(ads->config.client_site_name);
 	SAFE_FREE(ads->server.workgroup);
 
-	ads->config.flags	       = cldap_reply.server_type;
+	if (!check_cldap_reply_required_flags(cldap_reply.server_type,
+					      ads->config.flags)) {
+		ret = false;
+		goto out;
+	}
+
 	ads->config.ldap_server_name   = SMB_STRDUP(cldap_reply.pdc_dns_name);
 	ads->config.realm              = SMB_STRDUP(cldap_reply.dns_domain);
 	if (!strupper_m(ads->config.realm)) {
@@ -305,6 +310,9 @@ static bool ads_try_connect(ADS_STRUCT *ads, bool gc,
 	sitename_store( cldap_reply.domain_name, cldap_reply.client_site);
 	sitename_store( cldap_reply.dns_domain, cldap_reply.client_site);
 
+	/* Leave this until last so that the flags are not clobbered */
+	ads->config.flags	       = cldap_reply.server_type;
+
 	ret = true;
 
  out:
@@ -334,7 +342,9 @@ static NTSTATUS cldap_ping_list(ADS_STRUCT *ads,const char *domain,
 			check_negative_conn_cache(domain, server)))
 			continue;
 
+		/* Returns ok only if it matches the correct server type */
 		ok = ads_try_connect(ads, false, &ip_list[i].ss);
+
 		if (ok) {
 			return NT_STATUS_OK;
 		}
diff --git a/source3/libsmb/dsgetdcname.c b/source3/libsmb/dsgetdcname.c
index b5bc51dffae..92fc312c6a4 100644
--- a/source3/libsmb/dsgetdcname.c
+++ b/source3/libsmb/dsgetdcname.c
@@ -279,43 +279,6 @@ static uint32_t get_cldap_reply_server_flags(struct netlogon_samlogon_response *
 /****************************************************************
 ****************************************************************/
 
-#define RETURN_ON_FALSE(x) if (!(x)) return false;
-
-static bool check_cldap_reply_required_flags(uint32_t ret_flags,
-					     uint32_t req_flags)
-{
-	if (req_flags == 0) {
-		return true;
-	}
-
-	if (req_flags & DS_PDC_REQUIRED)
-		RETURN_ON_FALSE(ret_flags & NBT_SERVER_PDC);
-
-	if (req_flags & DS_GC_SERVER_REQUIRED)
-		RETURN_ON_FALSE(ret_flags & NBT_SERVER_GC);
-
-	if (req_flags & DS_ONLY_LDAP_NEEDED)
-		RETURN_ON_FALSE(ret_flags & NBT_SERVER_LDAP);
-
-	if ((req_flags & DS_DIRECTORY_SERVICE_REQUIRED) ||
-	    (req_flags & DS_DIRECTORY_SERVICE_PREFERRED))
-		RETURN_ON_FALSE(ret_flags & NBT_SERVER_DS);
-
-	if (req_flags & DS_KDC_REQUIRED)
-		RETURN_ON_FALSE(ret_flags & NBT_SERVER_KDC);
-
-	if (req_flags & DS_TIMESERV_REQUIRED)
-		RETURN_ON_FALSE(ret_flags & NBT_SERVER_TIMESERV);
-
-	if (req_flags & DS_WRITABLE_REQUIRED)
-		RETURN_ON_FALSE(ret_flags & NBT_SERVER_WRITABLE);
-
-	return true;
-}
-
-/****************************************************************
-****************************************************************/
-
 static NTSTATUS dsgetdcname_cache_fetch(TALLOC_CTX *mem_ctx,
 					const char *domain_name,
 					const struct GUID *domain_guid,
-- 
2.11.0


From 1d79efe340b90e38bef6f5a1365e1b8487a5308c Mon Sep 17 00:00:00 2001
From: Garming Sam <garming at catalyst.net.nz>
Date: Mon, 20 Mar 2017 15:56:37 +1300
Subject: [PATCH 04/26] winbindd_cm: Add new parameter for dcip_to_name

This is used to check the appropriateness of the DC given.

Signed-off-by: Garming Sam <garming at catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet at samba.org>
---
 source3/winbindd/winbindd_cm.c | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/source3/winbindd/winbindd_cm.c b/source3/winbindd/winbindd_cm.c
index 157a19374b3..3f47ff9cd08 100644
--- a/source3/winbindd/winbindd_cm.c
+++ b/source3/winbindd/winbindd_cm.c
@@ -1325,12 +1325,13 @@ static bool add_sockaddr_to_array(TALLOC_CTX *mem_ctx,
 
 /*******************************************************************
  convert an ip to a name
+ For an AD Domain, it checks the requirements of the request flags.
 *******************************************************************/
 
 static bool dcip_to_name(TALLOC_CTX *mem_ctx,
 		const struct winbindd_domain *domain,
 		struct sockaddr_storage *pss,
-		char **name)
+		char **name, uint32_t request_flags)
 {
 	struct ip_service ip_list;
 	uint32_t nt_version = NETLOGON_NT_VERSION_1;
@@ -1362,6 +1363,7 @@ static bool dcip_to_name(TALLOC_CTX *mem_ctx,
 
 		ads = ads_init(domain->alt_name, domain->name, addr);
 		ads->auth.flags |= ADS_AUTH_NO_BIND;
+		ads->config.flags |= request_flags;
 
 		ads_status = ads_connect(ads);
 		if (ADS_ERR_OK(ads_status)) {
@@ -1652,7 +1654,7 @@ static bool find_new_dc(TALLOC_CTX *mem_ctx,
 	}
 
 	/* Try to figure out the name */
-	if (dcip_to_name(mem_ctx, domain, pss, dcname)) {
+	if (dcip_to_name(mem_ctx, domain, pss, dcname, 0)) {
 		return True;
 	}
 
@@ -1840,7 +1842,7 @@ static NTSTATUS cm_open_connection(struct winbindd_domain *domain,
 				TALLOC_FREE(mem_ctx);
 				return NT_STATUS_UNSUCCESSFUL;
 			}
-			if (dcip_to_name(mem_ctx, domain, &ss, &dcname)) {
+			if (dcip_to_name(mem_ctx, domain, &ss, &dcname, 0)) {
 				domain->dcname = talloc_strdup(domain,
 							       dcname);
 				if (domain->dcname == NULL) {
-- 
2.11.0


From 3253ac71d46b0ff712b792f6017964524fef534a Mon Sep 17 00:00:00 2001
From: Garming Sam <garming at catalyst.net.nz>
Date: Mon, 20 Mar 2017 17:04:12 +1300
Subject: [PATCH 05/26] winbindd_cm: Add new parameter to getdc and find_new_dc
 calls

This is to enforce the requirements on the remote DC.

Signed-off-by: Garming Sam <garming at catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet at samba.org>
---
 source3/winbindd/winbindd_cm.c | 31 ++++++++++++++++++++-----------
 1 file changed, 20 insertions(+), 11 deletions(-)

diff --git a/source3/winbindd/winbindd_cm.c b/source3/winbindd/winbindd_cm.c
index 3f47ff9cd08..c65fe1ca0f0 100644
--- a/source3/winbindd/winbindd_cm.c
+++ b/source3/winbindd/winbindd_cm.c
@@ -99,7 +99,8 @@ static NTSTATUS init_dc_connection_network(struct winbindd_domain *domain, bool
 static void set_dc_type_and_flags( struct winbindd_domain *domain );
 static bool set_dc_type_and_flags_trustinfo( struct winbindd_domain *domain );
 static bool get_dcs(TALLOC_CTX *mem_ctx, struct winbindd_domain *domain,
-		    struct dc_name_ip **dcs, int *num_dcs);
+		    struct dc_name_ip **dcs, int *num_dcs,
+		    uint32_t request_flags);
 
 /****************************************************************
  Child failed to find DC's. Reschedule check.
@@ -266,7 +267,7 @@ static bool fork_child_dc_connect(struct winbindd_domain *domain)
 		_exit(1);
 	}
 
-	if ((!get_dcs(mem_ctx, domain, &dcs, &num_dcs)) || (num_dcs == 0)) {
+	if ((!get_dcs(mem_ctx, domain, &dcs, &num_dcs, 0)) || (num_dcs == 0)) {
 		/* Still offline ? Can't find DC's. */
 		messaging_send_buf(winbind_messaging_context(),
 				   pid_to_procid(parent_pid),
@@ -769,7 +770,8 @@ static bool cm_is_ipc_credentials(struct cli_credentials *creds)
 
 static bool get_dc_name_via_netlogon(struct winbindd_domain *domain,
 				     fstring dcname,
-				     struct sockaddr_storage *dc_ss)
+				     struct sockaddr_storage *dc_ss,
+				     uint32_t request_flags)
 {
 	struct winbindd_domain *our_domain = NULL;
 	struct rpc_pipe_client *netlogon_pipe = NULL;
@@ -814,13 +816,17 @@ static bool get_dc_name_via_netlogon(struct winbindd_domain *domain,
 	if (our_domain->active_directory) {
 		struct netr_DsRGetDCNameInfo *domain_info = NULL;
 
+		/*
+		 * TODO request flags are not respected in the server
+		 * (and in some cases, like REQUIRE_PDC, causes an error)
+		 */
 		result = dcerpc_netr_DsRGetDCName(b,
 						  mem_ctx,
 						  our_domain->dcname,
 						  domain->name,
 						  NULL,
 						  NULL,
-						  DS_RETURN_DNS_NAME,
+						  request_flags|DS_RETURN_DNS_NAME,
 						  &domain_info,
 						  &werr);
 		if (NT_STATUS_IS_OK(result) && W_ERROR_IS_OK(werr)) {
@@ -1456,7 +1462,8 @@ static bool dcip_to_name(TALLOC_CTX *mem_ctx,
 *******************************************************************/
 
 static bool get_dcs(TALLOC_CTX *mem_ctx, struct winbindd_domain *domain,
-		    struct dc_name_ip **dcs, int *num_dcs)
+		    struct dc_name_ip **dcs, int *num_dcs,
+		    uint32_t request_flags)
 {
 	fstring dcname;
 	struct  sockaddr_storage ss;
@@ -1470,7 +1477,7 @@ static bool get_dcs(TALLOC_CTX *mem_ctx, struct winbindd_domain *domain,
 
 	/* If not our domain, get the preferred DC, by asking our primary DC */
 	if ( !is_our_domain
-		&& get_dc_name_via_netlogon(domain, dcname, &ss)
+		&& get_dc_name_via_netlogon(domain, dcname, &ss, request_flags)
 		&& add_one_dc_unique(mem_ctx, domain->name, dcname, &ss, dcs,
 		       num_dcs) )
 	{
@@ -1587,7 +1594,8 @@ static bool get_dcs(TALLOC_CTX *mem_ctx, struct winbindd_domain *domain,
 
 static bool find_new_dc(TALLOC_CTX *mem_ctx,
 			struct winbindd_domain *domain,
-			char **dcname, struct sockaddr_storage *pss, int *fd)
+			char **dcname, struct sockaddr_storage *pss, int *fd,
+			uint32_t request_flags)
 {
 	struct dc_name_ip *dcs = NULL;
 	int num_dcs = 0;
@@ -1606,7 +1614,7 @@ static bool find_new_dc(TALLOC_CTX *mem_ctx,
 	*fd = -1;
 
  again:
-	if (!get_dcs(mem_ctx, domain, &dcs, &num_dcs) || (num_dcs == 0))
+	if (!get_dcs(mem_ctx, domain, &dcs, &num_dcs, request_flags) || (num_dcs == 0))
 		return False;
 
 	for (i=0; i<num_dcs; i++) {
@@ -1654,7 +1662,7 @@ static bool find_new_dc(TALLOC_CTX *mem_ctx,
 	}
 
 	/* Try to figure out the name */
-	if (dcip_to_name(mem_ctx, domain, pss, dcname, 0)) {
+	if (dcip_to_name(mem_ctx, domain, pss, dcname, request_flags)) {
 		return True;
 	}
 
@@ -1812,6 +1820,7 @@ static NTSTATUS cm_open_connection(struct winbindd_domain *domain,
 	NTSTATUS result;
 	char *saf_servername;
 	int retries;
+	uint32_t request_flags = 0;
 
 	if ((mem_ctx = talloc_init("cm_open_connection")) == NULL) {
 		set_domain_offline(domain);
@@ -1842,7 +1851,7 @@ static NTSTATUS cm_open_connection(struct winbindd_domain *domain,
 				TALLOC_FREE(mem_ctx);
 				return NT_STATUS_UNSUCCESSFUL;
 			}
-			if (dcip_to_name(mem_ctx, domain, &ss, &dcname, 0)) {
+			if (dcip_to_name(mem_ctx, domain, &ss, &dcname, request_flags)) {
 				domain->dcname = talloc_strdup(domain,
 							       dcname);
 				if (domain->dcname == NULL) {
@@ -1888,7 +1897,7 @@ static NTSTATUS cm_open_connection(struct winbindd_domain *domain,
 		}
 
 		if ((fd == -1) &&
-		    !find_new_dc(mem_ctx, domain, &dcname, &domain->dcaddr, &fd))
+		    !find_new_dc(mem_ctx, domain, &dcname, &domain->dcaddr, &fd, request_flags))
 		{
 			/* This is the one place where we will
 			   set the global winbindd offline state
-- 
2.11.0


From cb61f9745c5ec3fb7cc97dc977199fd9d6ce085f Mon Sep 17 00:00:00 2001
From: Garming Sam <garming at catalyst.net.nz>
Date: Tue, 21 Mar 2017 11:15:13 +1300
Subject: [PATCH 06/26] winbindd_cm: Rename dcip_to_name to the more accurate
 dcip_check_name

Signed-off-by: Garming Sam <garming at catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet at samba.org>
---
 source3/winbindd/winbindd_cm.c | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/source3/winbindd/winbindd_cm.c b/source3/winbindd/winbindd_cm.c
index c65fe1ca0f0..9f73b47a7aa 100644
--- a/source3/winbindd/winbindd_cm.c
+++ b/source3/winbindd/winbindd_cm.c
@@ -1334,10 +1334,10 @@ static bool add_sockaddr_to_array(TALLOC_CTX *mem_ctx,
  For an AD Domain, it checks the requirements of the request flags.
 *******************************************************************/
 
-static bool dcip_to_name(TALLOC_CTX *mem_ctx,
-		const struct winbindd_domain *domain,
-		struct sockaddr_storage *pss,
-		char **name, uint32_t request_flags)
+static bool dcip_check_name(TALLOC_CTX *mem_ctx,
+			    const struct winbindd_domain *domain,
+			    struct sockaddr_storage *pss,
+			    char **name, uint32_t request_flags)
 {
 	struct ip_service ip_list;
 	uint32_t nt_version = NETLOGON_NT_VERSION_1;
@@ -1381,7 +1381,7 @@ static bool dcip_to_name(TALLOC_CTX *mem_ctx,
 			}
 			namecache_store(*name, 0x20, 1, &ip_list);
 
-			DEBUG(10,("dcip_to_name: flags = 0x%x\n", (unsigned int)ads->config.flags));
+			DEBUG(10,("dcip_check_name: flags = 0x%x\n", (unsigned int)ads->config.flags));
 
 			if (domain->primary && (ads->config.flags & NBT_SERVER_KDC)) {
 				if (ads_closest_dc(ads)) {
@@ -1662,7 +1662,7 @@ static bool find_new_dc(TALLOC_CTX *mem_ctx,
 	}
 
 	/* Try to figure out the name */
-	if (dcip_to_name(mem_ctx, domain, pss, dcname, request_flags)) {
+	if (dcip_check_name(mem_ctx, domain, pss, dcname, request_flags)) {
 		return True;
 	}
 
@@ -1851,7 +1851,7 @@ static NTSTATUS cm_open_connection(struct winbindd_domain *domain,
 				TALLOC_FREE(mem_ctx);
 				return NT_STATUS_UNSUCCESSFUL;
 			}
-			if (dcip_to_name(mem_ctx, domain, &ss, &dcname, request_flags)) {
+			if (dcip_check_name(mem_ctx, domain, &ss, &dcname, request_flags)) {
 				domain->dcname = talloc_strdup(domain,
 							       dcname);
 				if (domain->dcname == NULL) {
-- 
2.11.0


From 91a7d0c399c4adf01af4b2976d657dc78f46d886 Mon Sep 17 00:00:00 2001
From: Garming Sam <garming at catalyst.net.nz>
Date: Tue, 21 Mar 2017 11:56:39 +1300
Subject: [PATCH 07/26] winbindd_cm: Call dcip_check_name even when fetching
 from cache

This is so that we can ensure that the DC is RWDC if required.

Signed-off-by: Garming Sam <garming at catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet at samba.org>
---
 source3/winbindd/winbindd_cm.c | 52 +++++++++++++++++++++---------------------
 1 file changed, 26 insertions(+), 26 deletions(-)

diff --git a/source3/winbindd/winbindd_cm.c b/source3/winbindd/winbindd_cm.c
index 9f73b47a7aa..d024d842128 100644
--- a/source3/winbindd/winbindd_cm.c
+++ b/source3/winbindd/winbindd_cm.c
@@ -1836,39 +1836,39 @@ static NTSTATUS cm_open_connection(struct winbindd_domain *domain,
 	   before talking to it. It going down may have
 	   triggered the reconnection. */
 
-	if ( saf_servername && NT_STATUS_IS_OK(check_negative_conn_cache( domain->name, saf_servername))) {
+	if (saf_servername && NT_STATUS_IS_OK(check_negative_conn_cache(domain->name, saf_servername))) {
+		struct sockaddr_storage ss;
+		char *dcname = NULL;
+		bool resolved = true;
 
-		DEBUG(10,("cm_open_connection: saf_servername is '%s' for domain %s\n",
-			saf_servername, domain->name ));
+		DEBUG(10, ("cm_open_connection: saf_servername is '%s' for domain %s\n",
+			   saf_servername, domain->name));
 
 		/* convert an ip address to a name */
-		if (is_ipaddress( saf_servername ) ) {
-			char *dcname = NULL;
-			struct sockaddr_storage ss;
-
+		if (is_ipaddress(saf_servername)) {
 			if (!interpret_string_addr(&ss, saf_servername,
-						AI_NUMERICHOST)) {
+						   AI_NUMERICHOST)) {
 				TALLOC_FREE(mem_ctx);
 				return NT_STATUS_UNSUCCESSFUL;
 			}
-			if (dcip_check_name(mem_ctx, domain, &ss, &dcname, request_flags)) {
-				domain->dcname = talloc_strdup(domain,
-							       dcname);
-				if (domain->dcname == NULL) {
-					TALLOC_FREE(mem_ctx);
-					return NT_STATUS_NO_MEMORY;
-				}
-			} else {
-				winbind_add_failed_connection_entry(
-					domain, saf_servername,
-					NT_STATUS_UNSUCCESSFUL);
-			}
 		} else {
-			domain->dcname = talloc_strdup(domain, saf_servername);
+			if (!resolve_name(saf_servername, &ss, 0x20, true)) {
+				resolved = false;
+			}
+		}
+
+		if (resolved && dcip_check_name(mem_ctx, domain, &ss, &dcname, request_flags)) {
+			domain->dcname = talloc_strdup(domain,
+						       dcname);
 			if (domain->dcname == NULL) {
 				TALLOC_FREE(mem_ctx);
 				return NT_STATUS_NO_MEMORY;
 			}
+
+			domain->dcaddr = ss;
+		} else {
+			winbind_add_failed_connection_entry(domain, saf_servername,
+							    NT_STATUS_UNSUCCESSFUL);
 		}
 	}
 
@@ -1879,12 +1879,12 @@ static NTSTATUS cm_open_connection(struct winbindd_domain *domain,
 
 		result = NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND;
 
-		DEBUG(10,("cm_open_connection: dcname is '%s' for domain %s\n",
-			domain->dcname ? domain->dcname : "", domain->name ));
+		DEBUG(10, ("cm_open_connection: dcname is '%s' for domain %s\n",
+			   domain->dcname ? domain->dcname : "", domain->name));
 
-		if (domain->dcname != NULL
-			&& NT_STATUS_IS_OK(check_negative_conn_cache( domain->name, domain->dcname))
-			&& (resolve_name(domain->dcname, &domain->dcaddr, 0x20, true)))
+		if (domain->dcname != NULL &&
+		    NT_STATUS_IS_OK(check_negative_conn_cache(domain->name,
+							      domain->dcname)))
 		{
 			NTSTATUS status;
 
-- 
2.11.0


From 2f0f279aea9fee1ef7d7b07544da55a1bffdb11c Mon Sep 17 00:00:00 2001
From: Garming Sam <garming at catalyst.net.nz>
Date: Tue, 21 Mar 2017 12:24:30 +1300
Subject: [PATCH 08/26] winbindd_cm: Pass cm_open_connection the need_rw_dc
 flag

Signed-off-by: Garming Sam <garming at catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet at samba.org>
---
 source3/winbindd/winbindd_cm.c | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/source3/winbindd/winbindd_cm.c b/source3/winbindd/winbindd_cm.c
index d024d842128..ce149b294e2 100644
--- a/source3/winbindd/winbindd_cm.c
+++ b/source3/winbindd/winbindd_cm.c
@@ -1814,13 +1814,14 @@ NTSTATUS wb_open_internal_pipe(TALLOC_CTX *mem_ctx,
 }
 
 static NTSTATUS cm_open_connection(struct winbindd_domain *domain,
-				   struct winbindd_cm_conn *new_conn)
+				   struct winbindd_cm_conn *new_conn,
+				   bool need_rw_dc)
 {
 	TALLOC_CTX *mem_ctx;
 	NTSTATUS result;
 	char *saf_servername;
 	int retries;
-	uint32_t request_flags = 0;
+	uint32_t request_flags = need_rw_dc ? DS_WRITABLE_REQUIRED : 0;
 
 	if ((mem_ctx = talloc_init("cm_open_connection")) == NULL) {
 		set_domain_offline(domain);
@@ -2127,7 +2128,7 @@ static NTSTATUS init_dc_connection_network(struct winbindd_domain *domain, bool
 		set_dc_type_and_flags_trustinfo(domain);
 	}
 
-	result = cm_open_connection(domain, &domain->conn);
+	result = cm_open_connection(domain, &domain->conn, need_rw_dc);
 
 	if (NT_STATUS_IS_OK(result) && !domain->initialized) {
 		set_dc_type_and_flags(domain);
-- 
2.11.0


From d29f08c548559dc3e825af26efd1cd8ae41ca044 Mon Sep 17 00:00:00 2001
From: Garming Sam <garming at catalyst.net.nz>
Date: Tue, 4 Apr 2017 12:42:17 +1200
Subject: [PATCH 09/26] libads: Decide to have no fallback option

Before this change, it would always possibly choose another server at
random despite later using the original principal when it got back to
the connection initialization in the the winbind connection manager.
This caused bizarre authentication failures.

Signed-off-by: Garming Sam <garming at catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet at samba.org>
---
 source3/include/ads.h          | 1 +
 source3/libads/ldap.c          | 5 +++++
 source3/winbindd/winbindd_cm.c | 1 +
 3 files changed, 7 insertions(+)

diff --git a/source3/include/ads.h b/source3/include/ads.h
index 2b25c1c6c29..ebc5728f3a4 100644
--- a/source3/include/ads.h
+++ b/source3/include/ads.h
@@ -64,6 +64,7 @@ typedef struct ads_struct {
 		char *workgroup;
 		char *ldap_server;
 		bool gc;     /* Is this a global catalog server? */
+		bool no_fallback; /* Bail if the ldap_server is not available */
 	} server;
 
 	/* info needed to authenticate */
diff --git a/source3/libads/ldap.c b/source3/libads/ldap.c
index b2c57480f1e..c18837cc524 100644
--- a/source3/libads/ldap.c
+++ b/source3/libads/ldap.c
@@ -613,6 +613,11 @@ ADS_STATUS ads_connect(ADS_STRUCT *ads)
 		if (ads->server.gc == true) {
 			return ADS_ERROR(LDAP_OPERATIONS_ERROR);
 		}
+
+		if (ads->server.no_fallback) {
+			status = ADS_ERROR_NT(NT_STATUS_NOT_FOUND);
+			goto out;
+		}
 	}
 
 	ntstatus = ads_find_dc(ads);
diff --git a/source3/winbindd/winbindd_cm.c b/source3/winbindd/winbindd_cm.c
index ce149b294e2..7566a301b7c 100644
--- a/source3/winbindd/winbindd_cm.c
+++ b/source3/winbindd/winbindd_cm.c
@@ -1370,6 +1370,7 @@ static bool dcip_check_name(TALLOC_CTX *mem_ctx,
 		ads = ads_init(domain->alt_name, domain->name, addr);
 		ads->auth.flags |= ADS_AUTH_NO_BIND;
 		ads->config.flags |= request_flags;
+		ads->server.no_fallback = true;
 
 		ads_status = ads_connect(ads);
 		if (ADS_ERR_OK(ads_status)) {
-- 
2.11.0


From 643e8d024ca2fb645142c518caaae4eeccc86939 Mon Sep 17 00:00:00 2001
From: Garming Sam <garming at catalyst.net.nz>
Date: Mon, 3 Apr 2017 15:21:29 +1200
Subject: [PATCH 10/26] auth4: Add authoritative flag to check_password

Signed-off-by: Garming Sam <garming at catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet at samba.org>
---
 source4/auth/auth.h                |  4 +++-
 source4/auth/ntlm/auth.c           | 11 ++++++++---
 source4/auth/ntlm/auth_anonymous.c |  3 ++-
 source4/auth/ntlm/auth_developer.c |  3 ++-
 source4/auth/ntlm/auth_sam.c       | 17 +++++++++++------
 source4/auth/ntlm/auth_unix.c      |  3 ++-
 source4/auth/ntlm/auth_winbind.c   |  6 ++++--
 7 files changed, 32 insertions(+), 15 deletions(-)

diff --git a/source4/auth/auth.h b/source4/auth/auth.h
index e1b642eb92d..c12e233219f 100644
--- a/source4/auth/auth.h
+++ b/source4/auth/auth.h
@@ -63,7 +63,9 @@ struct auth_operations {
 
 	NTSTATUS (*check_password)(struct auth_method_context *ctx, TALLOC_CTX *mem_ctx,
 				   const struct auth_usersupplied_info *user_info,
-				   struct auth_user_info_dc **interim_info);
+				   struct auth_user_info_dc **interim_info,
+				   bool *authoritative);
+
 
 	/* Lookup a 'session info interim' return based only on the principal or DN */
 	NTSTATUS (*get_user_info_dc_principal)(TALLOC_CTX *mem_ctx,
diff --git a/source4/auth/ntlm/auth.c b/source4/auth/ntlm/auth.c
index c8c3e11e5b3..51d1ed399bf 100644
--- a/source4/auth/ntlm/auth.c
+++ b/source4/auth/ntlm/auth.c
@@ -371,10 +371,12 @@ static void auth_check_password_async_trigger(struct tevent_context *ev,
 		tevent_req_data(req, struct auth_check_password_state);
 	NTSTATUS status;
 	struct auth_method_context *method;
+	bool authoritative = true;
 
 	status = NT_STATUS_OK;
 
 	for (method=state->auth_ctx->methods; method; method = method->next) {
+		authoritative = true;
 
 		/* we fill in state->method here so debug messages in
 		   the callers know which method failed */
@@ -396,8 +398,10 @@ static void auth_check_password_async_trigger(struct tevent_context *ev,
 		status = method->ops->check_password(method,
 						     state,
 						     state->user_info,
-						     &state->user_info_dc);
-		if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_IMPLEMENTED)) {
+						     &state->user_info_dc,
+						     &authoritative);
+		if (!authoritative ||
+		    NT_STATUS_EQUAL(status, NT_STATUS_NOT_IMPLEMENTED)) {
 			DEBUG(11,("auth_check_password_send: "
 				  "%s passes to the next method\n",
 				  method->ops->name));
@@ -408,7 +412,8 @@ static void auth_check_password_async_trigger(struct tevent_context *ev,
 		break;
 	}
 
-	if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_IMPLEMENTED)) {
+	if (!authoritative ||
+	    NT_STATUS_EQUAL(status, NT_STATUS_NOT_IMPLEMENTED)) {
 		state->authoritative = 0;
 		status = NT_STATUS_NO_SUCH_USER;
 	}
diff --git a/source4/auth/ntlm/auth_anonymous.c b/source4/auth/ntlm/auth_anonymous.c
index 6d3d0ace82c..e8a9ed3b225 100644
--- a/source4/auth/ntlm/auth_anonymous.c
+++ b/source4/auth/ntlm/auth_anonymous.c
@@ -84,7 +84,8 @@ static NTSTATUS anonymous_want_check(struct auth_method_context *ctx,
 static NTSTATUS anonymous_check_password(struct auth_method_context *ctx,
 			      		 TALLOC_CTX *mem_ctx,
 					 const struct auth_usersupplied_info *user_info, 
-					 struct auth_user_info_dc **_user_info_dc)
+					 struct auth_user_info_dc **_user_info_dc,
+					 bool *authoritative)
 {
 	return auth_anonymous_user_info_dc(mem_ctx, lpcfg_netbios_name(ctx->auth_ctx->lp_ctx), _user_info_dc);
 }
diff --git a/source4/auth/ntlm/auth_developer.c b/source4/auth/ntlm/auth_developer.c
index e7e4be96ae8..870357795f6 100644
--- a/source4/auth/ntlm/auth_developer.c
+++ b/source4/auth/ntlm/auth_developer.c
@@ -49,7 +49,8 @@ static NTSTATUS name_to_ntstatus_want_check(struct auth_method_context *ctx,
 static NTSTATUS name_to_ntstatus_check_password(struct auth_method_context *ctx,
 			      		        TALLOC_CTX *mem_ctx,
 					        const struct auth_usersupplied_info *user_info, 
-					        struct auth_user_info_dc **_user_info_dc)
+					        struct auth_user_info_dc **_user_info_dc,
+						bool *authoritative)
 {
 	NTSTATUS nt_status;
 	struct auth_user_info_dc *user_info_dc;
diff --git a/source4/auth/ntlm/auth_sam.c b/source4/auth/ntlm/auth_sam.c
index f7da04e6573..54cc6437595 100644
--- a/source4/auth/ntlm/auth_sam.c
+++ b/source4/auth/ntlm/auth_sam.c
@@ -190,7 +190,8 @@ static NTSTATUS authsam_password_check_and_record(struct auth4_context *auth_con
 						  uint16_t acct_flags,
 						  const struct auth_usersupplied_info *user_info,
 						  DATA_BLOB *user_sess_key,
-						  DATA_BLOB *lm_sess_key)
+						  DATA_BLOB *lm_sess_key,
+						  bool *authoritative)
 {
 	NTSTATUS nt_status;
 	NTSTATUS auth_status;
@@ -495,7 +496,8 @@ static NTSTATUS authsam_authenticate(struct auth4_context *auth_context,
 				     struct ldb_dn *domain_dn,
 				     struct ldb_message *msg,
 				     const struct auth_usersupplied_info *user_info,
-				     DATA_BLOB *user_sess_key, DATA_BLOB *lm_sess_key)
+				     DATA_BLOB *user_sess_key, DATA_BLOB *lm_sess_key,
+				     bool *authoritative)
 {
 	NTSTATUS nt_status;
 	bool interactive = (user_info->password_state == AUTH_PASSWORD_HASH);
@@ -530,7 +532,8 @@ static NTSTATUS authsam_authenticate(struct auth4_context *auth_context,
 	nt_status = authsam_password_check_and_record(auth_context, tmp_ctx,
 						      domain_dn, msg, acct_flags,
 						      user_info,
-						      user_sess_key, lm_sess_key);
+						      user_sess_key, lm_sess_key,
+						      authoritative);
 	if (!NT_STATUS_IS_OK(nt_status)) {
 		TALLOC_FREE(tmp_ctx);
 		return nt_status;
@@ -572,7 +575,8 @@ static NTSTATUS authsam_authenticate(struct auth4_context *auth_context,
 static NTSTATUS authsam_check_password_internals(struct auth_method_context *ctx,
 						 TALLOC_CTX *mem_ctx,
 						 const struct auth_usersupplied_info *user_info, 
-						 struct auth_user_info_dc **user_info_dc)
+						 struct auth_user_info_dc **user_info_dc,
+						 bool *authoritative)
 {
 	NTSTATUS nt_status;
 	const char *account_name = user_info->mapped.account_name;
@@ -647,7 +651,7 @@ static NTSTATUS authsam_check_password_internals(struct auth_method_context *ctx
 	}
 
 	nt_status = authsam_authenticate(ctx->auth_ctx, tmp_ctx, ctx->auth_ctx->sam_ctx, domain_dn, msg, user_info,
-					 &user_sess_key, &lm_sess_key);
+					 &user_sess_key, &lm_sess_key, authoritative);
 	if (!NT_STATUS_IS_OK(nt_status)) {
 		talloc_free(tmp_ctx);
 		return nt_status;
@@ -882,7 +886,8 @@ static NTSTATUS authsam_failtrusts_want_check(struct auth_method_context *ctx,
 static NTSTATUS authsam_failtrusts_check_password(struct auth_method_context *ctx,
 						  TALLOC_CTX *mem_ctx,
 						  const struct auth_usersupplied_info *user_info,
-						  struct auth_user_info_dc **user_info_dc)
+						  struct auth_user_info_dc **user_info_dc,
+						  bool *authoritative)
 {
 	/*
 	 * This should a good error for now,
diff --git a/source4/auth/ntlm/auth_unix.c b/source4/auth/ntlm/auth_unix.c
index ad780bafc82..c01ec35e14c 100644
--- a/source4/auth/ntlm/auth_unix.c
+++ b/source4/auth/ntlm/auth_unix.c
@@ -713,7 +713,8 @@ static NTSTATUS authunix_want_check(struct auth_method_context *ctx,
 static NTSTATUS authunix_check_password(struct auth_method_context *ctx,
 					TALLOC_CTX *mem_ctx,
 					const struct auth_usersupplied_info *user_info,
-					struct auth_user_info_dc **user_info_dc)
+					struct auth_user_info_dc **user_info_dc,
+					bool *authoritative)
 {
 	TALLOC_CTX *check_ctx;
 	NTSTATUS nt_status;
diff --git a/source4/auth/ntlm/auth_winbind.c b/source4/auth/ntlm/auth_winbind.c
index f5bd22acef6..7c815fc020e 100644
--- a/source4/auth/ntlm/auth_winbind.c
+++ b/source4/auth/ntlm/auth_winbind.c
@@ -99,7 +99,8 @@ struct winbind_check_password_state {
 static NTSTATUS winbind_check_password(struct auth_method_context *ctx,
 				       TALLOC_CTX *mem_ctx,
 				       const struct auth_usersupplied_info *user_info, 
-				       struct auth_user_info_dc **user_info_dc)
+				       struct auth_user_info_dc **user_info_dc,
+				       bool *authoritative)
 {
 	NTSTATUS status;
 	struct dcerpc_binding_handle *irpc_handle;
@@ -207,7 +208,8 @@ static NTSTATUS winbind_check_password(struct auth_method_context *ctx,
 static NTSTATUS winbind_check_password_wbclient(struct auth_method_context *ctx,
 						TALLOC_CTX *mem_ctx,
 						const struct auth_usersupplied_info *user_info,
-						struct auth_user_info_dc **user_info_dc)
+						struct auth_user_info_dc **user_info_dc,
+						bool *authoritative)
 {
 	struct wbcAuthUserParams params;
 	struct wbcAuthUserInfo *info = NULL;
-- 
2.11.0


From eef5ffaeaf17159904bc16283c5d198396f580e1 Mon Sep 17 00:00:00 2001
From: Garming Sam <garming at catalyst.net.nz>
Date: Wed, 12 Apr 2017 14:12:32 +1200
Subject: [PATCH 11/26] winbindd: Do not run SAM auth stack in winbind SamLogon

pdbtest.s4winbind no longer is applicable without a live NETLOGON
connection.

Signed-off-by: Garming Sam <garming at catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet at samba.org>
---
 selftest/knownfail                   | 4 ++++
 source3/winbindd/winbindd_dual_srv.c | 4 +++-
 source3/winbindd/winbindd_pam.c      | 4 +++-
 source3/winbindd/winbindd_proto.h    | 1 +
 4 files changed, 11 insertions(+), 2 deletions(-)

diff --git a/selftest/knownfail b/selftest/knownfail
index 3cc945b4a31..c6047c85445 100644
--- a/selftest/knownfail
+++ b/selftest/knownfail
@@ -244,6 +244,10 @@
 ^samba4.winbind.struct.lookup_name_sid\(ad_member:local\)
 ^samba4.winbind.struct.getdcname\(nt4_member:local\) # Works in other modes, just not against the classic/NT4 DC
 #
+# This test is no longer valid given s4winbind needs a live NETLOGON server
+#
+^samba.blackbox.pdbtest.s4winbind\(ad_dc_ntvfs\).pdbtest
+#
 # Differences in our KDC compared to windows
 #
 ^samba4.krb5.kdc .*.as-req-pac-request # We should reply to a request for a PAC over UDP with KRB5KRB_ERR_RESPONSE_TOO_BIG unconditionally
diff --git a/source3/winbindd/winbindd_dual_srv.c b/source3/winbindd/winbindd_dual_srv.c
index 02b1adb2117..8007c7d38d1 100644
--- a/source3/winbindd/winbindd_dual_srv.c
+++ b/source3/winbindd/winbindd_dual_srv.c
@@ -890,7 +890,9 @@ NTSTATUS _winbind_SamLogon(struct pipes_struct *p,
 				       r->in.logon.network->identity_info.workstation.string,
 				       r->in.logon.network->challenge,
 				       lm_response, nt_response,
-				       &r->out.authoritative, &flags,
+				       &r->out.authoritative,
+				       true,
+				       &flags,
 				       &r->out.validation.sam3);
 	return status;
 }
diff --git a/source3/winbindd/winbindd_pam.c b/source3/winbindd/winbindd_pam.c
index c792cfe704f..4d3a7eeb1ba 100644
--- a/source3/winbindd/winbindd_pam.c
+++ b/source3/winbindd/winbindd_pam.c
@@ -2003,6 +2003,7 @@ NTSTATUS winbind_dual_SamLogon(struct winbindd_domain *domain,
 			       DATA_BLOB lm_response,
 			       DATA_BLOB nt_response,
 			       uint8_t *authoritative,
+			       bool skip_sam,
 			       uint32_t *flags,
 			       struct netr_SamInfo3 **info3)
 {
@@ -2017,7 +2018,7 @@ NTSTATUS winbind_dual_SamLogon(struct winbindd_domain *domain,
 	 * name_domain can also be lp_realm()
 	 * we need to check against domain->name.
 	 */
-	if (strequal(domain->name, get_global_sam_name())) {
+	if (!skip_sam && strequal(domain->name, get_global_sam_name())) {
 		DATA_BLOB chal_blob = data_blob_const(
 			chal, 8);
 
@@ -2172,6 +2173,7 @@ enum winbindd_result winbindd_dual_pam_auth_crap(struct winbindd_domain *domain,
 				       lm_resp,
 				       nt_resp,
 				       &authoritative,
+				       false,
 				       &flags,
 				       &info3);
 	if (!NT_STATUS_IS_OK(result)) {
diff --git a/source3/winbindd/winbindd_proto.h b/source3/winbindd/winbindd_proto.h
index 57b363a7bd8..6d6fafcb72f 100644
--- a/source3/winbindd/winbindd_proto.h
+++ b/source3/winbindd/winbindd_proto.h
@@ -454,6 +454,7 @@ NTSTATUS winbind_dual_SamLogon(struct winbindd_domain *domain,
 			       DATA_BLOB lm_response,
 			       DATA_BLOB nt_response,
 			       uint8_t *authoritative,
+			       bool skip_sam,
 			       uint32_t *flags,
 			       struct netr_SamInfo3 **info3);
 
-- 
2.11.0


From c4a640bfce9101535bec82eda00b2f590bee4406 Mon Sep 17 00:00:00 2001
From: Garming Sam <garming at catalyst.net.nz>
Date: Mon, 3 Apr 2017 15:26:12 +1200
Subject: [PATCH 12/26] auth_winbind: Allow badPwdCount to be set to 0 with
 this auth method

We rely on the other SAM modules to increment the badPwdCount locally,
but we must reset to 0 if the remote sends a success (to override our
failure).

Signed-off-by: Garming Sam <garming at catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet at samba.org>
---
 source4/auth/ntlm/auth_sam.c     | 29 -----------------------------
 source4/auth/ntlm/auth_winbind.c | 40 ++++++++++++++++++++++++++++++++++++++++
 source4/auth/sam.c               | 28 ++++++++++++++++++++++++++++
 3 files changed, 68 insertions(+), 29 deletions(-)

diff --git a/source4/auth/ntlm/auth_sam.c b/source4/auth/ntlm/auth_sam.c
index 54cc6437595..cfe7455501b 100644
--- a/source4/auth/ntlm/auth_sam.c
+++ b/source4/auth/ntlm/auth_sam.c
@@ -42,35 +42,6 @@ extern const char *user_attrs[];
 extern const char *domain_ref_attrs[];
 
 /****************************************************************************
- Look for the specified user in the sam, return ldb result structures
-****************************************************************************/
-
-static NTSTATUS authsam_search_account(TALLOC_CTX *mem_ctx, struct ldb_context *sam_ctx,
-				       const char *account_name,
-				       struct ldb_dn *domain_dn,
-				       struct ldb_message **ret_msg)
-{
-	int ret;
-
-	/* pull the user attributes */
-	ret = dsdb_search_one(sam_ctx, mem_ctx, ret_msg, domain_dn, LDB_SCOPE_SUBTREE,
-			      user_attrs,
-			      DSDB_SEARCH_SHOW_EXTENDED_DN,
-			      "(&(sAMAccountName=%s)(objectclass=user))",
-			      ldb_binary_encode_string(mem_ctx, account_name));
-	if (ret == LDB_ERR_NO_SUCH_OBJECT) {
-		DEBUG(3,("sam_search_user: Couldn't find user [%s] in samdb, under %s\n", 
-			 account_name, ldb_dn_get_linearized(domain_dn)));
-		return NT_STATUS_NO_SUCH_USER;		
-	}
-	if (ret != LDB_SUCCESS) {
-		return NT_STATUS_INTERNAL_DB_CORRUPTION;
-	}
-	
-	return NT_STATUS_OK;
-}
-
-/****************************************************************************
  Do a specific test for an smb password being correct, given a smb_password and
  the lanman and NT responses.
 ****************************************************************************/
diff --git a/source4/auth/ntlm/auth_winbind.c b/source4/auth/ntlm/auth_winbind.c
index 7c815fc020e..41819dca605 100644
--- a/source4/auth/ntlm/auth_winbind.c
+++ b/source4/auth/ntlm/auth_winbind.c
@@ -31,6 +31,7 @@
 #include "auth/auth_sam_reply.h"
 #include "libcli/security/security.h"
 #include "dsdb/samdb/samdb.h"
+#include "auth/auth_sam.h"
 
 _PUBLIC_ NTSTATUS auth4_winbind_init(TALLOC_CTX *);
 
@@ -107,6 +108,9 @@ static NTSTATUS winbind_check_password(struct auth_method_context *ctx,
 	struct winbind_check_password_state *s;
 	const struct auth_usersupplied_info *user_info_new;
 	struct netr_IdentityInfo *identity_info;
+	struct ldb_dn *domain_dn;
+	struct ldb_message *msg;
+
 
 	if (!ctx->auth_ctx->msg_ctx) {
 		DEBUG(0,("winbind_check_password: auth_context_create was called with out messaging context\n"));
@@ -190,6 +194,42 @@ static NTSTATUS winbind_check_password(struct auth_method_context *ctx,
 		return NT_STATUS_NOT_IMPLEMENTED;
 	}
 
+	/*
+	 * At best, reset the badPwdCount to 0 if the account exists.
+	 * This means that lockouts happen at a badPwdCount earlier than
+	 * normal, but makes it more fault tolerant.
+	 */
+	if (NT_STATUS_IS_OK(s->req.out.result)) {
+		const char *account_name = user_info->mapped.account_name;
+		const char *p = NULL;
+		p = strchr_m(account_name, '@');
+		if (p != NULL) {
+			const char *nt4_domain = NULL;
+			const char *nt4_account = NULL;
+
+			status = crack_name_to_nt4_name(mem_ctx,
+							ctx->auth_ctx->event_ctx,
+							ctx->auth_ctx->lp_ctx,
+							DRSUAPI_DS_NAME_FORMAT_USER_PRINCIPAL,
+							account_name,
+							&nt4_domain, &nt4_account);
+			if (NT_STATUS_IS_OK(status) &&
+			    lpcfg_is_mydomain(ctx->auth_ctx->lp_ctx, nt4_domain)) {
+				account_name = nt4_account;
+			}
+		}
+
+		domain_dn = ldb_get_default_basedn(ctx->auth_ctx->sam_ctx);
+		if (domain_dn != NULL) {
+			status = authsam_search_account(mem_ctx, ctx->auth_ctx->sam_ctx, account_name, domain_dn, &msg);
+			if (NT_STATUS_IS_OK(status)) {
+			    authsam_logon_success_accounting(ctx->auth_ctx->sam_ctx, msg,
+							     domain_dn,
+							     user_info->flags & USER_INFO_INTERACTIVE_LOGON);
+			}
+		}
+	}
+
 	status = make_user_info_dc_netlogon_validation(mem_ctx,
 						      user_info->client.account_name,
 						      s->req.in.validation_level,
diff --git a/source4/auth/sam.c b/source4/auth/sam.c
index 9b0f0618bae..9119ef54f43 100644
--- a/source4/auth/sam.c
+++ b/source4/auth/sam.c
@@ -837,6 +837,34 @@ static NTSTATUS authsam_update_lastlogon_timestamp(struct ldb_context *sam_ctx,
 	return NT_STATUS_OK;
 }
 
+/****************************************************************************
+ Look for the specified user in the sam, return ldb result structures
+****************************************************************************/
+
+NTSTATUS authsam_search_account(TALLOC_CTX *mem_ctx, struct ldb_context *sam_ctx,
+					 const char *account_name,
+					 struct ldb_dn *domain_dn,
+					 struct ldb_message **ret_msg)
+{
+	int ret;
+
+	/* pull the user attributes */
+	ret = dsdb_search_one(sam_ctx, mem_ctx, ret_msg, domain_dn, LDB_SCOPE_SUBTREE,
+			      user_attrs,
+			      DSDB_SEARCH_SHOW_EXTENDED_DN,
+			      "(&(sAMAccountName=%s)(objectclass=user))",
+			      ldb_binary_encode_string(mem_ctx, account_name));
+	if (ret == LDB_ERR_NO_SUCH_OBJECT) {
+		DEBUG(3,("sam_search_user: Couldn't find user [%s] in samdb, under %s\n",
+			 account_name, ldb_dn_get_linearized(domain_dn)));
+		return NT_STATUS_NO_SUCH_USER;
+	}
+	if (ret != LDB_SUCCESS) {
+		return NT_STATUS_INTERNAL_DB_CORRUPTION;
+	}
+
+	return NT_STATUS_OK;
+}
 
 
 /* Reset the badPwdCount to zero and update the lastLogon time. */
-- 
2.11.0


From 1c311635db15334e2ceb2c112a4966b9d47b714b Mon Sep 17 00:00:00 2001
From: Garming Sam <garming at catalyst.net.nz>
Date: Wed, 26 Apr 2017 13:41:03 +1200
Subject: [PATCH 13/26] tests/rodc: Test for NTLM wrong password forwarding

Signed-off-by: Garming Sam <garming at catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet at samba.org>
---
 selftest/knownfail                     | 1 +
 source4/dsdb/tests/python/rodc_rwdc.py | 4 +++-
 2 files changed, 4 insertions(+), 1 deletion(-)

diff --git a/selftest/knownfail b/selftest/knownfail
index c6047c85445..76578c9517a 100644
--- a/selftest/knownfail
+++ b/selftest/knownfail
@@ -335,3 +335,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.*.__main__.RodcRwdcTests.test_change_password_reveal_on_demand_ntlm
diff --git a/source4/dsdb/tests/python/rodc_rwdc.py b/source4/dsdb/tests/python/rodc_rwdc.py
index f0910c9e9c1..85fa85df187 100644
--- a/source4/dsdb/tests/python/rodc_rwdc.py
+++ b/source4/dsdb/tests/python/rodc_rwdc.py
@@ -561,7 +561,9 @@ class RodcRwdcTests(password_lockout_base.BasePasswordTestCase):
 
         creds2 = make_creds(username, password)
         self.try_ldap_logon(RWDC, creds2)
-        self.try_ldap_logon(RODC, creds2, errno)
+        # We can forward WRONG_PASSWORD over NTLM.
+        # This SHOULD succeed.
+        self.try_ldap_logon(RODC, creds2)
 
 
     def test_change_password_reveal_on_demand_ntlm(self):
-- 
2.11.0


From 187ccaff48574fc8174cfc86972c049ebcf91e62 Mon Sep 17 00:00:00 2001
From: Garming Sam <garming at catalyst.net.nz>
Date: Mon, 3 Apr 2017 15:22:08 +1200
Subject: [PATCH 14/26] rodc: Set non-authoritative for RODC bad passwords

This requires as a pre-requisite that the auth stack is not run twice.
We remove the knownfail introduced in the earlier patch.

Signed-off-by: Garming Sam <garming at catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet at samba.org>
---
 selftest/knownfail           | 1 -
 source4/auth/ntlm/auth_sam.c | 6 +++++-
 2 files changed, 5 insertions(+), 2 deletions(-)

diff --git a/selftest/knownfail b/selftest/knownfail
index 76578c9517a..c6047c85445 100644
--- a/selftest/knownfail
+++ b/selftest/knownfail
@@ -335,4 +335,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.*.__main__.RodcRwdcTests.test_change_password_reveal_on_demand_ntlm
diff --git a/source4/auth/ntlm/auth_sam.c b/source4/auth/ntlm/auth_sam.c
index cfe7455501b..0b175b5ecc7 100644
--- a/source4/auth/ntlm/auth_sam.c
+++ b/source4/auth/ntlm/auth_sam.c
@@ -174,6 +174,7 @@ static NTSTATUS authsam_password_check_and_record(struct auth4_context *auth_con
 	struct ldb_message *dom_msg;
 	struct samr_Password *lm_pwd;
 	struct samr_Password *nt_pwd;
+	bool am_rodc;
 
 	tmp_ctx = talloc_new(mem_ctx);
 	if (tmp_ctx == NULL) {
@@ -196,7 +197,6 @@ static NTSTATUS authsam_password_check_and_record(struct auth4_context *auth_con
 	}
 
 	if (lm_pwd == NULL && nt_pwd == NULL) {
-		bool am_rodc;
 		if (samdb_rodc(auth_context->sam_ctx, &am_rodc) == LDB_SUCCESS && am_rodc) {
 			/*
 			 * we don't have passwords for this
@@ -458,6 +458,10 @@ static NTSTATUS authsam_password_check_and_record(struct auth4_context *auth_con
 			  nt_errstr(nt_status)));
 	}
 
+	if (samdb_rodc(auth_context->sam_ctx, &am_rodc) == LDB_SUCCESS && am_rodc) {
+		*authoritative = false;
+	}
+
 	TALLOC_FREE(tmp_ctx);
 	return NT_STATUS_WRONG_PASSWORD;
 }
-- 
2.11.0


From 544215dd08b34f332153090b871ee218678e197a Mon Sep 17 00:00:00 2001
From: Garming Sam <garming at catalyst.net.nz>
Date: Mon, 3 Apr 2017 15:49:45 +1200
Subject: [PATCH 15/26] auth_sam: Make auth_sam_trigger_repl_secret more
 generic

Signed-off-by: Garming Sam <garming at catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet at samba.org>
---
 source4/auth/ntlm/auth_sam.c | 15 ++++++++++-----
 1 file changed, 10 insertions(+), 5 deletions(-)

diff --git a/source4/auth/ntlm/auth_sam.c b/source4/auth/ntlm/auth_sam.c
index 0b175b5ecc7..fed5dd3f308 100644
--- a/source4/auth/ntlm/auth_sam.c
+++ b/source4/auth/ntlm/auth_sam.c
@@ -108,7 +108,9 @@ static NTSTATUS authsam_password_ok(struct auth4_context *auth_context,
   send a message to the drepl server telling it to initiate a
   REPL_SECRET getncchanges extended op to fetch the users secrets
  */
-static void auth_sam_trigger_repl_secret(struct auth4_context *auth_context,
+static void auth_sam_trigger_repl_secret(TALLOC_CTX *mem_ctx,
+					 struct imessaging_context *msg_ctx,
+					 struct tevent_context *event_ctx,
 					 struct ldb_dn *user_dn)
 {
 	struct dcerpc_binding_handle *irpc_handle;
@@ -116,12 +118,12 @@ static void auth_sam_trigger_repl_secret(struct auth4_context *auth_context,
 	struct tevent_req *req;
 	TALLOC_CTX *tmp_ctx;
 
-	tmp_ctx = talloc_new(auth_context);
+	tmp_ctx = talloc_new(mem_ctx);
 	if (tmp_ctx == NULL) {
 		return;
 	}
 
-	irpc_handle = irpc_binding_handle_by_name(tmp_ctx, auth_context->msg_ctx,
+	irpc_handle = irpc_binding_handle_by_name(tmp_ctx, msg_ctx,
 						  "dreplsrv",
 						  &ndr_table_irpc);
 	if (irpc_handle == NULL) {
@@ -140,7 +142,7 @@ static void auth_sam_trigger_repl_secret(struct auth4_context *auth_context,
 	 * a callback and wait for it to be triggered!
 	 */
 	req = dcerpc_drepl_trigger_repl_secret_r_send(tmp_ctx,
-						      auth_context->event_ctx,
+						      event_ctx,
 						      irpc_handle,
 						      &r);
 
@@ -215,7 +217,10 @@ static NTSTATUS authsam_password_check_and_record(struct auth4_context *auth_con
 			 * replicated, we should be able to detect this
 			 * based on msDS-NeverRevealGroup.
 			 */
-			auth_sam_trigger_repl_secret(auth_context, msg->dn);
+			auth_sam_trigger_repl_secret(auth_context,
+						     auth_context->msg_ctx,
+						     auth_context->event_ctx,
+						     msg->dn);
 			TALLOC_FREE(tmp_ctx);
 			return NT_STATUS_NOT_IMPLEMENTED;
 		}
-- 
2.11.0


From 74feb2765a2edd3b02e1e2cd28d15cae39452a30 Mon Sep 17 00:00:00 2001
From: Garming Sam <garming at catalyst.net.nz>
Date: Mon, 3 Apr 2017 16:11:35 +1200
Subject: [PATCH 16/26] hdb: Dupe a copy of repl secrets into the KDC

When you have an RODC, this will force the fetch of secrets if not found here

Signed-off-by: Garming Sam <garming at catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet at samba.org>
---
 source4/kdc/db-glue.c     | 52 ++++++++++++++++++++++++++++++++++++++++++++++-
 source4/kdc/wscript_build |  2 +-
 2 files changed, 52 insertions(+), 2 deletions(-)

diff --git a/source4/kdc/db-glue.c b/source4/kdc/db-glue.c
index ce6a707bb43..9ac5a1d38f0 100644
--- a/source4/kdc/db-glue.c
+++ b/source4/kdc/db-glue.c
@@ -35,6 +35,9 @@
 #include "kdc/sdb.h"
 #include "kdc/samba_kdc.h"
 #include "kdc/db-glue.h"
+#include "librpc/gen_ndr/ndr_irpc_c.h"
+#include "lib/messaging/irpc.h"
+
 
 #define SAMBA_KVNO_GET_KRBTGT(kvno) \
 	((uint16_t)(((uint32_t)kvno) >> 16))
@@ -65,6 +68,52 @@ static const char *trust_attrs[] = {
 	NULL
 };
 
+/*
+  send a message to the drepl server telling it to initiate a
+  REPL_SECRET getncchanges extended op to fetch the users secrets
+ */
+static void auth_sam_trigger_repl_secret(TALLOC_CTX *mem_ctx,
+                                  struct imessaging_context *msg_ctx,
+                                  struct tevent_context *event_ctx,
+                                  struct ldb_dn *user_dn)
+{
+        struct dcerpc_binding_handle *irpc_handle;
+        struct drepl_trigger_repl_secret r;
+        struct tevent_req *req;
+        TALLOC_CTX *tmp_ctx;
+
+        tmp_ctx = talloc_new(mem_ctx);
+        if (tmp_ctx == NULL) {
+                return;
+        }
+
+        irpc_handle = irpc_binding_handle_by_name(tmp_ctx, msg_ctx,
+                                                  "dreplsrv",
+                                                  &ndr_table_irpc);
+        if (irpc_handle == NULL) {
+                DEBUG(1,(__location__ ": Unable to get binding handle for dreplsrv\n"));
+                TALLOC_FREE(tmp_ctx);
+                return;
+        }
+
+        r.in.user_dn = ldb_dn_get_linearized(user_dn);
+
+        /*
+         * 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!
+         */
+        req = dcerpc_drepl_trigger_repl_secret_r_send(tmp_ctx,
+                                                      event_ctx,
+                                                      irpc_handle,
+                                                      &r);
+
+        /* we aren't interested in a reply */
+        talloc_free(req);
+        TALLOC_FREE(tmp_ctx);
+}
 
 static time_t ldb_msg_find_krb5time_ldap_time(struct ldb_message *msg, const char *attr, time_t default_val)
 {
@@ -504,7 +553,8 @@ static krb5_error_code samba_kdc_message2entry_keys(krb5_context context,
 	if (allocated_keys == 0) {
 		if (kdc_db_ctx->rodc) {
 			/* We are on an RODC, but don't have keys for this account.  Signal this to the caller */
-			/* TODO:  We need to call a generalised version of auth_sam_trigger_repl_secret from here */
+			auth_sam_trigger_repl_secret(kdc_db_ctx, kdc_db_ctx->msg_ctx,
+						     kdc_db_ctx->ev_ctx, msg->dn);
 			return SDB_ERR_NOT_FOUND_HERE;
 		}
 
diff --git a/source4/kdc/wscript_build b/source4/kdc/wscript_build
index 6179e3e6426..1c54a36bdf8 100644
--- a/source4/kdc/wscript_build
+++ b/source4/kdc/wscript_build
@@ -140,7 +140,7 @@ bld.SAMBA_LIBRARY('pac',
 
 bld.SAMBA_LIBRARY('db-glue',
 	source='db-glue.c',
-	deps='ldb auth4_sam common_auth samba-credentials sdb samba-hostconfig com_err',
+	deps='ldb auth4_sam common_auth samba-credentials sdb samba-hostconfig com_err RPC_NDR_IRPC MESSAGING',
 	private_library=True,
         includes=kdc_include,
 	)
-- 
2.11.0


From 097a0a606288d4c3ed1d7c5c088f1f8cdc5a558d Mon Sep 17 00:00:00 2001
From: Garming Sam <garming at catalyst.net.nz>
Date: Tue, 4 Apr 2017 11:57:01 +1200
Subject: [PATCH 17/26] kdc: Send bad password via NETLOGON in RODC

This means that a RWDC will be collecting the badPwdCount to ensure
domain wide lockout.

TODO The parameters should be better constructed.

Signed-off-by: Garming Sam <garming at catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet at samba.org>
---
 source4/kdc/hdb-samba4.c  | 79 ++++++++++++++++++++++++++++++++++++++++++-----
 source4/kdc/wscript_build |  2 +-
 2 files changed, 73 insertions(+), 8 deletions(-)

diff --git a/source4/kdc/hdb-samba4.c b/source4/kdc/hdb-samba4.c
index c0d0e24713f..81ac60e38ba 100644
--- a/source4/kdc/hdb-samba4.c
+++ b/source4/kdc/hdb-samba4.c
@@ -43,6 +43,8 @@
 #include "dsdb/samdb/samdb.h"
 #include "param/param.h"
 #include "../lib/tsocket/tsocket.h"
+#include "librpc/gen_ndr/ndr_winbind_c.h"
+#include "lib/messaging/irpc.h"
 
 static krb5_error_code hdb_samba4_open(krb5_context context, HDB *db, int flags, mode_t mode)
 {
@@ -294,6 +296,61 @@ hdb_samba4_check_s4u2self(krb5_context context, HDB *db,
 	return ret;
 }
 
+static void send_bad_password_netlogon(TALLOC_CTX *mem_ctx,
+				       struct samba_kdc_db_context *kdc_db_ctx,
+				       struct auth_usersupplied_info *user_info)
+{
+	struct dcerpc_binding_handle *irpc_handle;
+	struct winbind_SamLogon req;
+	struct netr_IdentityInfo *identity_info;
+	struct netr_NetworkInfo *network_info;
+
+	irpc_handle = irpc_binding_handle_by_name(mem_ctx, kdc_db_ctx->msg_ctx,
+						  "winbind_server",
+						  &ndr_table_winbind);
+	if (irpc_handle == NULL) {
+		DEBUG(0, ("Winbind fowarding for [%s]\\[%s] failed, "
+			  "no winbind_server running!\n",
+			  user_info->mapped.domain_name, user_info->mapped.account_name));
+		return;
+	}
+
+	network_info = talloc_zero(mem_ctx, struct netr_NetworkInfo);
+	if (network_info == NULL) {
+		DEBUG(0, ("Winbind forwarding failed: No memory\n"));
+		return;
+	}
+
+	identity_info = &network_info->identity_info;
+	req.in.logon_level = 2;
+	req.in.logon.network = network_info;
+
+	identity_info->domain_name.string = user_info->mapped.domain_name;
+	identity_info->parameter_control = user_info->logon_parameters; /* TODO */
+	identity_info->logon_id_low = 0;
+	identity_info->logon_id_high = 0;
+	identity_info->account_name.string = user_info->mapped.account_name;
+	identity_info->workstation.string
+		= talloc_asprintf(identity_info, "krb5-bad-pw on RODC from %s",
+				  tsocket_address_string(user_info->remote_host,
+							 identity_info));
+	if (identity_info->workstation.string == NULL) {
+		DEBUG(0, ("Winbind forwarding failed: No memory allocating workstation string\n"));
+		return;
+	}
+
+	req.in.validation_level = 3;
+
+	/* 
+	 * The memory in identity_info and user_info only needs to be
+	 * valid until the end of this function call, as it will be
+	 * pushed to NDR during this call 
+	 */
+	
+	dcerpc_winbind_SamLogon_r_send(mem_ctx, kdc_db_ctx->ev_ctx,
+				       irpc_handle, &req);
+}
+
 static krb5_error_code hdb_samba4_auth_status(krb5_context context, HDB *db,
 					      hdb_entry_ex *entry,
 					      struct sockaddr *from_addr,
@@ -368,13 +425,6 @@ static krb5_error_code hdb_samba4_auth_status(krb5_context context, HDB *db,
 		NTSTATUS status;
 		int ret;
 
-		if (hdb_auth_status == HDB_AUTH_WRONG_PASSWORD) {
-			authsam_update_bad_pwd_count(kdc_db_ctx->samdb, p->msg, domain_dn);
-			status = NT_STATUS_WRONG_PASSWORD;
-		} else {
-			status = NT_STATUS_OK;
-		}
-
 		ret = tsocket_address_bsd_from_sockaddr(frame, from_addr,
 							sa_socklen,
 							&remote_host);
@@ -387,6 +437,21 @@ static krb5_error_code hdb_samba4_auth_status(krb5_context context, HDB *db,
 		ui.mapped.account_name = account_name;
 		ui.mapped.domain_name = domain_name;
 
+		if (hdb_auth_status == HDB_AUTH_WRONG_PASSWORD) {
+			authsam_update_bad_pwd_count(kdc_db_ctx->samdb, p->msg, domain_dn);
+			status = NT_STATUS_WRONG_PASSWORD;
+			/*
+			 * TODO We currently send a bad password via NETLOGON,
+			 * however, it should probably forward the ticket to
+			 * another KDC to allow login after password changes.
+			 */
+			if (kdc_db_ctx->rodc) {
+				send_bad_password_netlogon(frame, kdc_db_ctx, &ui);
+			}
+		} else {
+			status = NT_STATUS_OK;
+		}
+
 		log_authentication_event(kdc_db_ctx->msg_ctx,
 					 kdc_db_ctx->lp_ctx,
 					 &ui,
diff --git a/source4/kdc/wscript_build b/source4/kdc/wscript_build
index 1c54a36bdf8..c1f9a478582 100644
--- a/source4/kdc/wscript_build
+++ b/source4/kdc/wscript_build
@@ -47,7 +47,7 @@ if bld.CONFIG_GET('SAMBA_USES_MITKDC'):
 
 bld.SAMBA_LIBRARY('HDB_SAMBA4',
                   source='hdb-samba4.c hdb-samba4-plugin.c',
-                  deps='ldb auth4_sam common_auth samba-credentials hdb db-glue samba-hostconfig com_err sdb_hdb',
+                  deps='ldb auth4_sam common_auth samba-credentials hdb db-glue samba-hostconfig com_err sdb_hdb RPC_NDR_WINBIND',
                   includes=kdc_include,
                   private_library=True,
                   enabled=bld.CONFIG_SET('SAMBA4_USES_HEIMDAL')
-- 
2.11.0


From 1c0d8c090e85bb9c05f088c4caba42ce75facf13 Mon Sep 17 00:00:00 2001
From: Garming Sam <garming at catalyst.net.nz>
Date: Thu, 20 Apr 2017 16:55:58 +1200
Subject: [PATCH 18/26] netlogon_creds_cli: Do not corrupt authenticator state
 on application level errors

If the NETLOGON response was an error e.g. NT_STATUS_NOT_IMPLEMENTED, any subsequent
calls failed with NT_STATUS_ACCESS_DENIED. This is likely to be the cause of RODC DNS
updates falling off and never continuing.

Signed-off-by: Garming Sam <garming at catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet at samba.org>
---
 libcli/auth/netlogon_creds_cli.c | 32 +++++++++++++++++---------------
 1 file changed, 17 insertions(+), 15 deletions(-)

diff --git a/libcli/auth/netlogon_creds_cli.c b/libcli/auth/netlogon_creds_cli.c
index d55142e3ee2..ff30354d60e 100644
--- a/libcli/auth/netlogon_creds_cli.c
+++ b/libcli/auth/netlogon_creds_cli.c
@@ -2800,19 +2800,20 @@ static void netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords_done(struct tev
 		return;
 	}
 
-	if (tevent_req_nterror(req, result)) {
-		netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords_cleanup(req, result);
-		return;
-	}
-
 	*state->creds = state->tmp_creds;
 	status = netlogon_creds_cli_store(state->context,
 					  &state->creds);
+
 	if (tevent_req_nterror(req, status)) {
 		netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords_cleanup(req, status);
 		return;
 	}
 
+	if (tevent_req_nterror(req, result)) {
+		netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords_cleanup(req, result);
+		return;
+	}
+
 	tevent_req_done(req);
 }
 
@@ -3052,11 +3053,6 @@ static void netlogon_creds_cli_ServerGetTrustInfo_done(struct tevent_req *subreq
 		return;
 	}
 
-	if (tevent_req_nterror(req, result)) {
-		netlogon_creds_cli_ServerGetTrustInfo_cleanup(req, result);
-		return;
-	}
-
 	cmp = memcmp(state->new_owf_password.hash,
 		     zero.hash, sizeof(zero.hash));
 	if (cmp != 0) {
@@ -3078,6 +3074,11 @@ static void netlogon_creds_cli_ServerGetTrustInfo_done(struct tevent_req *subreq
 		return;
 	}
 
+	if (tevent_req_nterror(req, result)) {
+		netlogon_creds_cli_ServerGetTrustInfo_cleanup(req, result);
+		return;
+	}
+
 	tevent_req_done(req);
 }
 
@@ -3347,19 +3348,20 @@ static void netlogon_creds_cli_GetForestTrustInformation_done(struct tevent_req
 		return;
 	}
 
-	if (tevent_req_nterror(req, result)) {
-		netlogon_creds_cli_GetForestTrustInformation_cleanup(req, result);
-		return;
-	}
-
 	*state->creds = state->tmp_creds;
 	status = netlogon_creds_cli_store(state->context,
 					  &state->creds);
+
 	if (tevent_req_nterror(req, status)) {
 		netlogon_creds_cli_GetForestTrustInformation_cleanup(req, status);
 		return;
 	}
 
+	if (tevent_req_nterror(req, result)) {
+		netlogon_creds_cli_GetForestTrustInformation_cleanup(req, result);
+		return;
+	}
+
 	tevent_req_done(req);
 }
 
-- 
2.11.0


From 98724036f4857599cc90654427c414e2239a5d6a Mon Sep 17 00:00:00 2001
From: Garming Sam <garming at catalyst.net.nz>
Date: Tue, 11 Apr 2017 15:51:50 +1200
Subject: [PATCH 19/26] netlogon: Implement SendToSam along with its winbind
 forwarding

This allows you to forward bad password count resets to 0. Currently,
there is a missing access check for the RODC to ensure it only applies
to cached users (msDS-Allowed-Password-Replication-Group).

(further patches still need to address forcing a RWDC contact)

Signed-off-by: Garming Sam <garming at catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet at samba.org>
---
 libcli/auth/netlogon_creds_cli.c              | 260 ++++++++++++++++++++++++++
 libcli/auth/netlogon_creds_cli.h              |  11 ++
 librpc/idl/netlogon.idl                       |  39 +++-
 librpc/idl/winbind.idl                        |   5 +
 source3/rpc_server/netlogon/srv_netlog_nt.c   |   6 +-
 source3/winbindd/winbindd_dual_srv.c          |  25 +++
 source3/winbindd/winbindd_irpc.c              |  23 +++
 source4/auth/ntlm/auth_sam.c                  |  56 +++++-
 source4/auth/ntlm/auth_winbind.c              |   3 +-
 source4/auth/sam.c                            |  28 ++-
 source4/kdc/hdb-samba4.c                      |  30 ++-
 source4/rpc_server/netlogon/dcerpc_netlogon.c | 100 +++++++++-
 12 files changed, 571 insertions(+), 15 deletions(-)

diff --git a/libcli/auth/netlogon_creds_cli.c b/libcli/auth/netlogon_creds_cli.c
index ff30354d60e..fcab81491c6 100644
--- a/libcli/auth/netlogon_creds_cli.c
+++ b/libcli/auth/netlogon_creds_cli.c
@@ -31,6 +31,7 @@
 #include "../libcli/auth/schannel.h"
 #include "../librpc/gen_ndr/ndr_schannel.h"
 #include "../librpc/gen_ndr/ndr_netlogon_c.h"
+#include "../librpc/gen_ndr/ndr_netlogon.h"
 #include "../librpc/gen_ndr/server_id.h"
 #include "netlogon_creds_cli.h"
 #include "source3/include/messages.h"
@@ -3415,3 +3416,262 @@ NTSTATUS netlogon_creds_cli_GetForestTrustInformation(
 	TALLOC_FREE(frame);
 	return status;
 }
+
+struct netlogon_creds_cli_SendToSam_state {
+	struct tevent_context *ev;
+	struct netlogon_creds_cli_context *context;
+	struct dcerpc_binding_handle *binding_handle;
+
+	char *srv_name_slash;
+	enum dcerpc_AuthType auth_type;
+	enum dcerpc_AuthLevel auth_level;
+
+	DATA_BLOB opaque;
+
+	struct netlogon_creds_CredentialState *creds;
+	struct netlogon_creds_CredentialState tmp_creds;
+	struct netr_Authenticator req_auth;
+	struct netr_Authenticator rep_auth;
+};
+
+static void netlogon_creds_cli_SendToSam_cleanup(struct tevent_req *req,
+								 NTSTATUS status);
+static void netlogon_creds_cli_SendToSam_locked(struct tevent_req *subreq);
+
+struct tevent_req *netlogon_creds_cli_SendToSam_send(TALLOC_CTX *mem_ctx,
+						     struct tevent_context *ev,
+						     struct netlogon_creds_cli_context *context,
+						     struct dcerpc_binding_handle *b,
+						     struct netr_SendToSamBase *message)
+{
+	struct tevent_req *req;
+	struct netlogon_creds_cli_SendToSam_state *state;
+	struct tevent_req *subreq;
+	enum ndr_err_code ndr_err;
+
+	req = tevent_req_create(mem_ctx, &state,
+				struct netlogon_creds_cli_SendToSam_state);
+	if (req == NULL) {
+		return NULL;
+	}
+
+	state->ev = ev;
+	state->context = context;
+	state->binding_handle = b;
+
+	state->srv_name_slash = talloc_asprintf(state, "\\\\%s",
+						context->server.computer);
+	if (tevent_req_nomem(state->srv_name_slash, req)) {
+		return tevent_req_post(req, ev);
+	}
+
+	ndr_err = ndr_push_struct_blob(&state->opaque, mem_ctx, message,
+				       (ndr_push_flags_fn_t)ndr_push_netr_SendToSamBase);
+	if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+		NTSTATUS status = ndr_map_error2ntstatus(ndr_err);
+		tevent_req_nterror(req, status);
+		return tevent_req_post(req, ev);
+	}
+
+	dcerpc_binding_handle_auth_info(state->binding_handle,
+					&state->auth_type,
+					&state->auth_level);
+
+	subreq = netlogon_creds_cli_lock_send(state, state->ev,
+					      state->context);
+	if (tevent_req_nomem(subreq, req)) {
+		return tevent_req_post(req, ev);
+	}
+
+	tevent_req_set_callback(subreq,
+				netlogon_creds_cli_SendToSam_locked,
+				req);
+
+	return req;
+}
+
+static void netlogon_creds_cli_SendToSam_cleanup(struct tevent_req *req,
+							 NTSTATUS status)
+{
+	struct netlogon_creds_cli_SendToSam_state *state =
+		tevent_req_data(req,
+		struct netlogon_creds_cli_SendToSam_state);
+
+	if (state->creds == NULL) {
+		return;
+	}
+
+	if (!NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_ACCESS_DENIED) &&
+	    !NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT) &&
+	    !NT_STATUS_EQUAL(status, NT_STATUS_DOWNGRADE_DETECTED) &&
+	    !NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED) &&
+	    !NT_STATUS_EQUAL(status, NT_STATUS_RPC_SEC_PKG_ERROR)) {
+		TALLOC_FREE(state->creds);
+		return;
+	}
+
+	netlogon_creds_cli_delete(state->context, &state->creds);
+}
+
+static void netlogon_creds_cli_SendToSam_done(struct tevent_req *subreq);
+
+static void netlogon_creds_cli_SendToSam_locked(struct tevent_req *subreq)
+{
+	struct tevent_req *req =
+		tevent_req_callback_data(subreq,
+		struct tevent_req);
+	struct netlogon_creds_cli_SendToSam_state *state =
+		tevent_req_data(req,
+		struct netlogon_creds_cli_SendToSam_state);
+	NTSTATUS status;
+
+	status = netlogon_creds_cli_lock_recv(subreq, state,
+					      &state->creds);
+	TALLOC_FREE(subreq);
+	if (tevent_req_nterror(req, status)) {
+		return;
+	}
+
+	if (state->auth_type == DCERPC_AUTH_TYPE_SCHANNEL) {
+		switch (state->auth_level) {
+		case DCERPC_AUTH_LEVEL_INTEGRITY:
+		case DCERPC_AUTH_LEVEL_PRIVACY:
+			break;
+		default:
+			tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER_MIX);
+			return;
+		}
+	} else {
+		uint32_t tmp = state->creds->negotiate_flags;
+
+		if (tmp & NETLOGON_NEG_AUTHENTICATED_RPC) {
+			/*
+			 * if DCERPC_AUTH_TYPE_SCHANNEL is supported
+			 * it should be used, which means
+			 * we had a chance to verify no downgrade
+			 * happened.
+			 *
+			 * This relies on netlogon_creds_cli_check*
+			 * being called before, as first request after
+			 * the DCERPC bind.
+			 */
+			tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER_MIX);
+			return;
+		}
+	}
+
+	/*
+	 * we defer all callbacks in order to cleanup
+	 * the database record.
+	 */
+	tevent_req_defer_callback(req, state->ev);
+
+	state->tmp_creds = *state->creds;
+	netlogon_creds_client_authenticator(&state->tmp_creds,
+					    &state->req_auth);
+	ZERO_STRUCT(state->rep_auth);
+
+	if (state->tmp_creds.negotiate_flags & NETLOGON_NEG_SUPPORTS_AES) {
+		netlogon_creds_aes_encrypt(&state->tmp_creds,
+					   state->opaque.data,
+					   state->opaque.length);
+	} else {
+		netlogon_creds_arcfour_crypt(&state->tmp_creds,
+					     state->opaque.data,
+					     state->opaque.length);
+	}
+
+	subreq = dcerpc_netr_NetrLogonSendToSam_send(state, state->ev,
+						     state->binding_handle,
+						     state->srv_name_slash,
+						     state->tmp_creds.computer_name,
+						     &state->req_auth,
+						     &state->rep_auth,
+						     state->opaque.data,
+						     state->opaque.length);
+	if (tevent_req_nomem(subreq, req)) {
+		status = NT_STATUS_NO_MEMORY;
+		netlogon_creds_cli_SendToSam_cleanup(req, status);
+		return;
+	}
+
+	tevent_req_set_callback(subreq,
+				netlogon_creds_cli_SendToSam_done,
+				req);
+}
+
+static void netlogon_creds_cli_SendToSam_done(struct tevent_req *subreq)
+{
+	struct tevent_req *req =
+		tevent_req_callback_data(subreq,
+		struct tevent_req);
+	struct netlogon_creds_cli_SendToSam_state *state =
+		tevent_req_data(req,
+		struct netlogon_creds_cli_SendToSam_state);
+	NTSTATUS status;
+	NTSTATUS result;
+	bool ok;
+
+	status = dcerpc_netr_NetrLogonSendToSam_recv(subreq, state, &result);
+	TALLOC_FREE(subreq);
+	if (tevent_req_nterror(req, status)) {
+		netlogon_creds_cli_SendToSam_cleanup(req, status);
+		return;
+	}
+
+	ok = netlogon_creds_client_check(&state->tmp_creds,
+					 &state->rep_auth.cred);
+	if (!ok) {
+		status = NT_STATUS_ACCESS_DENIED;
+		tevent_req_nterror(req, status);
+		netlogon_creds_cli_SendToSam_cleanup(req, status);
+		return;
+	}
+
+	*state->creds = state->tmp_creds;
+	status = netlogon_creds_cli_store(state->context,
+					  &state->creds);
+
+	if (tevent_req_nterror(req, status)) {
+		netlogon_creds_cli_SendToSam_cleanup(req, status);
+		return;
+	}
+
+	/*
+	 * Creds must be stored before we send back application errors
+	 * e.g. NT_STATUS_NOT_IMPLEMENTED
+	 */
+	if (tevent_req_nterror(req, result)) {
+		netlogon_creds_cli_SendToSam_cleanup(req, result);
+		return;
+	}
+
+	tevent_req_done(req);
+}
+
+NTSTATUS netlogon_creds_cli_SendToSam(struct netlogon_creds_cli_context *context,
+				      struct dcerpc_binding_handle *b,
+				      struct netr_SendToSamBase *message)
+{
+	TALLOC_CTX *frame = talloc_stackframe();
+	struct tevent_context *ev;
+	struct tevent_req *req;
+	NTSTATUS status = NT_STATUS_OK;
+
+	ev = samba_tevent_context_init(frame);
+	if (ev == NULL) {
+		goto fail;
+	}
+	req = netlogon_creds_cli_SendToSam_send(frame, ev, context, b, message);
+	if (req == NULL) {
+		goto fail;
+	}
+	if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+		goto fail;
+	}
+
+	/* Ignore the result */
+ fail:
+	TALLOC_FREE(frame);
+	return status;
+}
diff --git a/libcli/auth/netlogon_creds_cli.h b/libcli/auth/netlogon_creds_cli.h
index 949e03bd460..7c737dd920a 100644
--- a/libcli/auth/netlogon_creds_cli.h
+++ b/libcli/auth/netlogon_creds_cli.h
@@ -181,4 +181,15 @@ NTSTATUS netlogon_creds_cli_GetForestTrustInformation(
 			TALLOC_CTX *mem_ctx,
 			struct lsa_ForestTrustInformation **forest_trust_info);
 
+struct tevent_req *netlogon_creds_cli_SendToSam_send(TALLOC_CTX *mem_ctx,
+						     struct tevent_context *ev,
+						     struct netlogon_creds_cli_context *context,
+						     struct dcerpc_binding_handle *b,
+						     struct netr_SendToSamBase *message);
+
+NTSTATUS netlogon_creds_cli_SendToSam(
+				struct netlogon_creds_cli_context *context,
+				struct dcerpc_binding_handle *b,
+				struct netr_SendToSamBase *message);
+
 #endif /* NETLOGON_CREDS_CLI_H */
diff --git a/librpc/idl/netlogon.idl b/librpc/idl/netlogon.idl
index e4b499fd85e..4d1a0ef237f 100644
--- a/librpc/idl/netlogon.idl
+++ b/librpc/idl/netlogon.idl
@@ -1466,9 +1466,46 @@ interface netlogon
 		[out,ref] samr_Password *password
 		);
 
+	typedef [public] enum {
+		SendToSamUpdatePassword = 0,
+		SendToSamResetBadPasswordCount = 1,
+		SendToSamUpdatePasswordForward = 2,
+		SendToSamUpdateLastLogonTimestamp = 3,
+		SendToSamResetSmartCardPassword = 4
+	} netr_SendToSamType;
+
+	typedef struct {
+		GUID guid;
+	} netr_SendToSamResetBadPasswordCount;
+
+	typedef [nodiscriminant, public,switch_type(netr_SendToSamType)] union {
+		/* TODO Implement other SendToSam message types
+		 * [case(SendToSamUpdatePassword)] netr_SendToSamUpdatePassword ...; */
+		[case(SendToSamResetBadPasswordCount)] netr_SendToSamResetBadPasswordCount reset_bad_password;
+		/*
+		 * [case(SendToSamUpdatePasswordForward)] netrSendToSamUpdatePasswordForward ...;
+		 * [case(SendToSamUpdateLastLogonTimestamp)] netrSendToSamUpdateLastLogonTimestamp ...;
+		 * [case(SendToSamResetSmartCardPassword)]   netrSendToSamResetSmartCardPassword ...;
+		 */
+		[default];
+	} netr_SendToSamMessage;
+
+	typedef [public] struct {
+		netr_SendToSamType message_type;
+		uint32 message_size;
+		[switch_is(message_type), subcontext(0), subcontext_size(message_size)] netr_SendToSamMessage message;
+	} netr_SendToSamBase;
+
 	/****************/
 	/* Function 0x20 */
-	[todo] WERROR netr_NETRLOGONSENDTOSAM();
+	NTSTATUS netr_NetrLogonSendToSam(
+		[in,unique] [string,charset(UTF16)] uint16 *server_name,
+		[in]  [string,charset(UTF16)] uint16 *computer_name,
+		[in,ref] netr_Authenticator *credential,
+		[out,ref] netr_Authenticator *return_authenticator,
+		[in,ref]  [size_is(buffer_len)] uint8 *opaque_buffer,
+		[in] uint32 buffer_len
+		);
 
 	/****************/
 	/* Function 0x21 */
diff --git a/librpc/idl/winbind.idl b/librpc/idl/winbind.idl
index 05db6b96b81..737d66abe70 100644
--- a/librpc/idl/winbind.idl
+++ b/librpc/idl/winbind.idl
@@ -211,4 +211,9 @@ interface winbind
 		[in] uint32 flags,
 		[out,ref] lsa_ForestTrustInformation **forest_trust_info
 		);
+
+	NTSTATUS winbind_SendToSam(
+		[in] netr_SendToSamBase message
+		);
+
 }
diff --git a/source3/rpc_server/netlogon/srv_netlog_nt.c b/source3/rpc_server/netlogon/srv_netlog_nt.c
index c36e6590b5c..83e68417c76 100644
--- a/source3/rpc_server/netlogon/srv_netlog_nt.c
+++ b/source3/rpc_server/netlogon/srv_netlog_nt.c
@@ -2277,11 +2277,11 @@ NTSTATUS _netr_ServerPasswordGet(struct pipes_struct *p,
 /****************************************************************
 ****************************************************************/
 
-WERROR _netr_NETRLOGONSENDTOSAM(struct pipes_struct *p,
-				struct netr_NETRLOGONSENDTOSAM *r)
+NTSTATUS _netr_NetrLogonSendToSam(struct pipes_struct *p,
+				struct netr_NetrLogonSendToSam *r)
 {
 	p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
-	return WERR_NOT_SUPPORTED;
+	return NT_STATUS_NOT_IMPLEMENTED;
 }
 
 /****************************************************************
diff --git a/source3/winbindd/winbindd_dual_srv.c b/source3/winbindd/winbindd_dual_srv.c
index 8007c7d38d1..0b17b78bd6b 100644
--- a/source3/winbindd/winbindd_dual_srv.c
+++ b/source3/winbindd/winbindd_dual_srv.c
@@ -1701,3 +1701,28 @@ done:
 	TALLOC_FREE(frame);
 	return WERR_OK;
 }
+
+NTSTATUS _winbind_SendToSam(struct pipes_struct *p, struct winbind_SendToSam *r)
+{
+	struct winbindd_domain *domain;
+	NTSTATUS status;
+	struct rpc_pipe_client *netlogon_pipe;
+
+	DEBUG(5, ("_winbind_SendToSam received\n"));
+	domain = wb_child_domain();
+	if (domain == NULL) {
+		return NT_STATUS_REQUEST_NOT_ACCEPTED;
+	}
+
+	status = cm_connect_netlogon(domain, &netlogon_pipe);
+	if (!NT_STATUS_IS_OK(status)) {
+		DEBUG(3, ("could not open handle to NETLOGON pipe\n"));
+		return status;
+	}
+
+	status = netlogon_creds_cli_SendToSam(domain->conn.netlogon_creds,
+					      netlogon_pipe->binding_handle,
+					      &r->in.message);
+
+	return status;
+}
diff --git a/source3/winbindd/winbindd_irpc.c b/source3/winbindd/winbindd_irpc.c
index c87707a4c0f..c6c786c8be1 100644
--- a/source3/winbindd/winbindd_irpc.c
+++ b/source3/winbindd/winbindd_irpc.c
@@ -255,6 +255,24 @@ static NTSTATUS wb_irpc_GetForestTrustInformation(struct irpc_message *msg,
 					domain, 45 /* timeout */);
 }
 
+static NTSTATUS wb_irpc_SendToSam(struct irpc_message *msg,
+				  struct winbind_SendToSam *req)
+{
+	/* TODO make sure that it is RWDC */
+	struct winbindd_domain *domain = find_our_domain();
+	if (domain == NULL) {
+		return NT_STATUS_NO_SUCH_DOMAIN;
+	}
+
+	DEBUG(5, ("wb_irpc_SendToSam called\n"));
+
+	return wb_irpc_forward_rpc_call(msg, msg,
+					winbind_event_context(),
+					req, NDR_WINBIND_SENDTOSAM,
+					"winbind_SendToSam",
+					domain, IRPC_CALL_TIMEOUT);
+}
+
 NTSTATUS wb_irpc_register(void)
 {
 	NTSTATUS status;
@@ -281,6 +299,11 @@ NTSTATUS wb_irpc_register(void)
 	if (!NT_STATUS_IS_OK(status)) {
 		return status;
 	}
+	status = IRPC_REGISTER(winbind_imessaging_context(), winbind, WINBIND_SENDTOSAM,
+			       wb_irpc_SendToSam, NULL);
+	if (!NT_STATUS_IS_OK(status)) {
+		return status;
+	}
 
 	return NT_STATUS_OK;
 }
diff --git a/source4/auth/ntlm/auth_sam.c b/source4/auth/ntlm/auth_sam.c
index fed5dd3f308..ee4a054c8c7 100644
--- a/source4/auth/ntlm/auth_sam.c
+++ b/source4/auth/ntlm/auth_sam.c
@@ -32,6 +32,7 @@
 #include "dsdb/common/util.h"
 #include "param/param.h"
 #include "librpc/gen_ndr/ndr_irpc_c.h"
+#include "librpc/gen_ndr/ndr_winbind_c.h"
 #include "lib/messaging/irpc.h"
 #include "libcli/auth/libcli_auth.h"
 #include "libds/common/roles.h"
@@ -103,6 +104,49 @@ static NTSTATUS authsam_password_ok(struct auth4_context *auth_context,
 	return NT_STATUS_OK;
 }
 
+static void auth_sam_trigger_zero_password(TALLOC_CTX *mem_ctx,
+					   struct imessaging_context *msg_ctx,
+					   struct tevent_context *event_ctx,
+					   struct netr_SendToSamBase *send_to_sam)
+{
+	struct dcerpc_binding_handle *irpc_handle;
+	struct winbind_SendToSam r;
+	struct tevent_req *req;
+	TALLOC_CTX *tmp_ctx;
+
+	tmp_ctx = talloc_new(mem_ctx);
+	if (tmp_ctx == NULL) {
+		return;
+	}
+
+	irpc_handle = irpc_binding_handle_by_name(tmp_ctx, msg_ctx,
+						  "winbind_server",
+						  &ndr_table_winbind);
+	if (irpc_handle == NULL) {
+		DEBUG(1,(__location__ ": Unable to get binding handle for winbind\n"));
+		TALLOC_FREE(tmp_ctx);
+		return;
+	}
+
+	r.in.message = *send_to_sam;
+
+	/*
+	 * 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!
+	 */
+	req = dcerpc_winbind_SendToSam_r_send(tmp_ctx,
+					      event_ctx,
+					      irpc_handle,
+					      &r);
+
+	/* we aren't interested in a reply */
+	talloc_free(req);
+	TALLOC_FREE(tmp_ctx);
+
+}
 
 /*
   send a message to the drepl server telling it to initiate a
@@ -482,6 +526,7 @@ static NTSTATUS authsam_authenticate(struct auth4_context *auth_context,
 	NTSTATUS nt_status;
 	bool interactive = (user_info->password_state == AUTH_PASSWORD_HASH);
 	uint32_t acct_flags = samdb_result_acct_flags(msg, NULL);
+	struct netr_SendToSamBase *send_to_sam = NULL;
 	TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
 	if (!tmp_ctx) {
 		return NT_STATUS_NO_MEMORY;
@@ -533,7 +578,16 @@ static NTSTATUS authsam_authenticate(struct auth4_context *auth_context,
 
 	nt_status = authsam_logon_success_accounting(auth_context->sam_ctx,
 						     msg, domain_dn,
-						     interactive);
+						     interactive,
+						     &send_to_sam);
+
+	if (send_to_sam != NULL) {
+		auth_sam_trigger_zero_password(tmp_ctx,
+					       auth_context->msg_ctx,
+					       auth_context->event_ctx,
+					       send_to_sam);
+	}
+
 	if (!NT_STATUS_IS_OK(nt_status)) {
 		TALLOC_FREE(tmp_ctx);
 		return nt_status;
diff --git a/source4/auth/ntlm/auth_winbind.c b/source4/auth/ntlm/auth_winbind.c
index 41819dca605..84f278ddd85 100644
--- a/source4/auth/ntlm/auth_winbind.c
+++ b/source4/auth/ntlm/auth_winbind.c
@@ -225,7 +225,8 @@ static NTSTATUS winbind_check_password(struct auth_method_context *ctx,
 			if (NT_STATUS_IS_OK(status)) {
 			    authsam_logon_success_accounting(ctx->auth_ctx->sam_ctx, msg,
 							     domain_dn,
-							     user_info->flags & USER_INFO_INTERACTIVE_LOGON);
+							     user_info->flags & USER_INFO_INTERACTIVE_LOGON,
+							     NULL);
 			}
 		}
 	}
diff --git a/source4/auth/sam.c b/source4/auth/sam.c
index 9119ef54f43..49e34baf145 100644
--- a/source4/auth/sam.c
+++ b/source4/auth/sam.c
@@ -30,6 +30,7 @@
 #include "dsdb/common/util.h"
 #include "libcli/ldap/ldap_ndr.h"
 #include "param/param.h"
+#include "librpc/gen_ndr/ndr_winbind_c.h"
 
 #define KRBTGT_ATTRS \
 	/* required for the krb5 kdc */		\
@@ -74,9 +75,14 @@ const char *user_attrs[] = {
 	 */
 	"lockoutTime",
 
+	/*
+	 * Needed for SendToSAM requests
+	 */
+	"objectGUID",
+
 	/* check 'allowed workstations' */
 	"userWorkstations",
-		       
+
 	/* required for user_info_dc, not access control: */
 	"displayName",
 	"scriptPath",
@@ -871,11 +877,13 @@ NTSTATUS authsam_search_account(TALLOC_CTX *mem_ctx, struct ldb_context *sam_ctx
 NTSTATUS authsam_logon_success_accounting(struct ldb_context *sam_ctx,
 					  const struct ldb_message *msg,
 					  struct ldb_dn *domain_dn,
-					  bool interactive_or_kerberos)
+					  bool interactive_or_kerberos,
+					  struct netr_SendToSamBase **send_to_sam)
 {
 	int ret;
 	NTSTATUS status;
 	int badPwdCount;
+	int dbBadPwdCount;
 	int64_t lockoutTime;
 	struct ldb_message *msg_mod;
 	TALLOC_CTX *mem_ctx;
@@ -890,8 +898,9 @@ NTSTATUS authsam_logon_success_accounting(struct ldb_context *sam_ctx,
 	}
 
 	lockoutTime = ldb_msg_find_attr_as_int64(msg, "lockoutTime", 0);
+	dbBadPwdCount = ldb_msg_find_attr_as_int(msg, "badPwdCount", 0);
 	if (interactive_or_kerberos) {
-		badPwdCount = ldb_msg_find_attr_as_int(msg, "badPwdCount", 0);
+		badPwdCount = dbBadPwdCount;
 	} else {
 		badPwdCount = samdb_result_effective_badPwdCount(sam_ctx, mem_ctx,
 								 domain_dn, msg);
@@ -971,13 +980,24 @@ NTSTATUS authsam_logon_success_accounting(struct ldb_context *sam_ctx,
 	}
 
 	if (!am_rodc) {
-		/* TODO Perform the (async) SendToSAM calls for MS-SAMS */
 		status = authsam_update_lastlogon_timestamp(sam_ctx, msg_mod, domain_dn,
 							    lastLogonTimestamp, now);
 		if (!NT_STATUS_IS_OK(status)) {
 			TALLOC_FREE(mem_ctx);
 			return NT_STATUS_NO_MEMORY;
 		}
+	} else {
+		/* Perform the (async) SendToSAM calls for MS-SAMS */
+		if (dbBadPwdCount != 0 && send_to_sam != NULL) {
+			struct netr_SendToSamBase *base_msg;
+			struct GUID guid = samdb_result_guid(msg, "objectGUID");
+			base_msg = talloc_zero(msg, struct netr_SendToSamBase);
+
+			base_msg->message_type = SendToSamResetBadPasswordCount;
+			base_msg->message_size = 16;
+			base_msg->message.reset_bad_password.guid = guid;
+			*send_to_sam = base_msg;
+		}
 	}
 
 	if (msg_mod->num_elements > 0) {
diff --git a/source4/kdc/hdb-samba4.c b/source4/kdc/hdb-samba4.c
index 81ac60e38ba..552eeeedf6b 100644
--- a/source4/kdc/hdb-samba4.c
+++ b/source4/kdc/hdb-samba4.c
@@ -296,6 +296,28 @@ hdb_samba4_check_s4u2self(krb5_context context, HDB *db,
 	return ret;
 }
 
+static void reset_bad_password_netlogon(TALLOC_CTX *mem_ctx,
+					struct samba_kdc_db_context *kdc_db_ctx,
+					struct netr_SendToSamBase *send_to_sam)
+{
+	struct dcerpc_binding_handle *irpc_handle;
+	struct winbind_SendToSam req;
+
+	irpc_handle = irpc_binding_handle_by_name(mem_ctx, kdc_db_ctx->msg_ctx,
+						  "winbind_server",
+						  &ndr_table_winbind);
+
+	if (irpc_handle == NULL) {
+		DEBUG(0, ("No winbind_server running!\n"));
+		return;
+	}
+
+	req.in.message = *send_to_sam;
+
+	dcerpc_winbind_SendToSam_r_send(mem_ctx, kdc_db_ctx->ev_ctx,
+					irpc_handle, &req);
+}
+
 static void send_bad_password_netlogon(TALLOC_CTX *mem_ctx,
 				       struct samba_kdc_db_context *kdc_db_ctx,
 				       struct auth_usersupplied_info *user_info)
@@ -396,8 +418,10 @@ static krb5_error_code hdb_samba4_auth_status(krb5_context context, HDB *db,
 	switch (hdb_auth_status) {
 	case HDB_AUTHZ_SUCCESS:
 	{
+		TALLOC_CTX *frame = talloc_stackframe();
 		struct samba_kdc_entry *p = talloc_get_type(entry->ctx,
 							    struct samba_kdc_entry);
+		struct netr_SendToSamBase *send_to_sam = NULL;
 
 		/*
 		 * TODO: We could log the AS-REQ authorization success here as
@@ -405,7 +429,11 @@ static krb5_error_code hdb_samba4_auth_status(krb5_context context, HDB *db,
 		 * in the PAC here or re-calculate it.
 		 */
 		authsam_logon_success_accounting(kdc_db_ctx->samdb, p->msg,
-						 domain_dn, true);
+						 domain_dn, true, &send_to_sam);
+		if (kdc_db_ctx->rodc && send_to_sam != NULL) {
+			reset_bad_password_netlogon(frame, kdc_db_ctx, send_to_sam);
+		}
+		talloc_free(frame);
 		break;
 	}
 	case HDB_AUTH_INVALID_SIGNATURE:
diff --git a/source4/rpc_server/netlogon/dcerpc_netlogon.c b/source4/rpc_server/netlogon/dcerpc_netlogon.c
index 420dcee3a47..e41cd17da12 100644
--- a/source4/rpc_server/netlogon/dcerpc_netlogon.c
+++ b/source4/rpc_server/netlogon/dcerpc_netlogon.c
@@ -2254,12 +2254,104 @@ static NTSTATUS dcesrv_netr_ServerPasswordGet(struct dcesrv_call_state *dce_call
 
 
 /*
-  netr_NETRLOGONSENDTOSAM
+  netr_NetrLogonSendToSam
 */
-static WERROR dcesrv_netr_NETRLOGONSENDTOSAM(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx,
-		       struct netr_NETRLOGONSENDTOSAM *r)
+static NTSTATUS dcesrv_netr_NetrLogonSendToSam(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx,
+					       struct netr_NetrLogonSendToSam *r)
 {
-	DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR);
+	struct netlogon_creds_CredentialState *creds;
+	struct ldb_context *sam_ctx;
+	NTSTATUS nt_status;
+	DATA_BLOB decrypted_blob;
+	enum ndr_err_code ndr_err;
+	struct netr_SendToSamBase base_msg = { 0 };
+
+	nt_status = dcesrv_netr_creds_server_step_check(dce_call,
+							mem_ctx,
+							r->in.computer_name,
+							r->in.credential,
+							r->out.return_authenticator,
+							&creds);
+
+	NT_STATUS_NOT_OK_RETURN(nt_status);
+
+	switch (creds->secure_channel_type) {
+	case SEC_CHAN_BDC:
+	case SEC_CHAN_RODC:
+		break;
+	case SEC_CHAN_WKSTA:
+	case SEC_CHAN_DNS_DOMAIN:
+	case SEC_CHAN_DOMAIN:
+	case SEC_CHAN_NULL:
+		return NT_STATUS_INVALID_PARAMETER;
+	default:
+		DEBUG(1, ("Client asked for an invalid secure channel type: %d\n",
+			  creds->secure_channel_type));
+		return NT_STATUS_INVALID_PARAMETER;
+	}
+
+	sam_ctx = samdb_connect(mem_ctx, dce_call->event_ctx,
+				dce_call->conn->dce_ctx->lp_ctx,
+				system_session(dce_call->conn->dce_ctx->lp_ctx), 0);
+	if (sam_ctx == NULL) {
+		return NT_STATUS_INVALID_SYSTEM_SERVICE;
+	}
+
+	/* Buffer is meant to be 16-bit aligned */
+	if (creds->negotiate_flags & NETLOGON_NEG_SUPPORTS_AES) {
+		netlogon_creds_aes_decrypt(creds, r->in.opaque_buffer, r->in.buffer_len);
+	} else {
+		netlogon_creds_arcfour_crypt(creds, r->in.opaque_buffer, r->in.buffer_len);
+	}
+
+	decrypted_blob.data = r->in.opaque_buffer;
+	decrypted_blob.length = r->in.buffer_len;
+
+	ndr_err = ndr_pull_struct_blob(&decrypted_blob, mem_ctx, &base_msg,
+				       (ndr_pull_flags_fn_t)ndr_pull_netr_SendToSamBase);
+
+	if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+		/* We only partially implement SendToSam */
+		return NT_STATUS_NOT_IMPLEMENTED;
+	}
+
+	/* Now 'send' to SAM */
+	switch (base_msg.message_type) {
+	case SendToSamResetBadPasswordCount:
+	{
+		struct ldb_message *msg = ldb_msg_new(mem_ctx);
+		struct ldb_dn *dn = NULL;
+		int ret = 0;
+
+
+		ret = dsdb_find_dn_by_guid(sam_ctx,
+					   mem_ctx,
+					   &base_msg.message.reset_bad_password.guid,
+					   0,
+					   &dn);
+		if (ret != LDB_SUCCESS) {
+			return NT_STATUS_INVALID_PARAMETER;
+		}
+
+		msg->dn = dn;
+
+		ret = samdb_msg_add_int(sam_ctx, mem_ctx, msg, "badPwdCount", 0);
+		if (ret != LDB_SUCCESS) {
+			return NT_STATUS_INVALID_PARAMETER;
+		}
+
+		ret = dsdb_replace(sam_ctx, msg, 0);
+		if (ret != LDB_SUCCESS) {
+			return NT_STATUS_INVALID_PARAMETER;
+		}
+
+		break;
+	}
+	default:
+		return NT_STATUS_NOT_IMPLEMENTED;
+	}
+
+	return NT_STATUS_OK;
 }
 
 
-- 
2.11.0


From cec71a8f3a24f95721018e93f4df90668fdb2594 Mon Sep 17 00:00:00 2001
From: Garming Sam <garming at catalyst.net.nz>
Date: Wed, 26 Apr 2017 16:11:28 +1200
Subject: [PATCH 20/26] selftest: Ensure rodc environment uses localdc as
 winbind partner

This is required for password lockout testing.

Signed-off-by: Garming Sam <garming at catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet at samba.org>
---
 selftest/target/Samba4.pm | 1 +
 1 file changed, 1 insertion(+)

diff --git a/selftest/target/Samba4.pm b/selftest/target/Samba4.pm
index b9367eaf460..e4d5efd9ec4 100755
--- a/selftest/target/Samba4.pm
+++ b/selftest/target/Samba4.pm
@@ -1622,6 +1622,7 @@ sub provision_rodc($$$)
 	$ctx->{smb_conf_extra_options} = "
 	max xmit = 32K
 	server max protocol = SMB2
+	password server = $dcvars->{DC_SERVER}
 
 [sysvol]
 	path = $ctx->{statedir}/sysvol
-- 
2.11.0


From e85ef07c7ba297e9eee01ff72dbb1d6dc56d0c27 Mon Sep 17 00:00:00 2001
From: Garming Sam <garming at catalyst.net.nz>
Date: Fri, 21 Apr 2017 15:21:58 +1200
Subject: [PATCH 21/26] tests/rodc: Add password lockout tests with RODC-auth,
 RWDC-check

This occurs when the password is preloaded, and the bad logins and
successes must be forwarded the the RWDC.

The password server MUST be localdc.

Signed-off-by: Garming Sam <garming at catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet at samba.org>
---
 source4/dsdb/tests/python/password_lockout_base.py |  70 ++--
 source4/dsdb/tests/python/rodc_rwdc.py             | 466 +++++++++++++++++++++
 2 files changed, 504 insertions(+), 32 deletions(-)

diff --git a/source4/dsdb/tests/python/password_lockout_base.py b/source4/dsdb/tests/python/password_lockout_base.py
index 1ea6e50cceb..992f51d7822 100644
--- a/source4/dsdb/tests/python/password_lockout_base.py
+++ b/source4/dsdb/tests/python/password_lockout_base.py
@@ -105,7 +105,8 @@ class BasePasswordTestCase(samba.tests.TestCase):
                        userAccountControl=None,
                        msDSUserAccountControlComputed=None,
                        effective_bad_password_count=None,
-                       msg=None):
+                       msg=None,
+                       badPwdCountOnly=False):
         print '-=' * 36
         if msg is not None:
             print  "\033[01;32m %s \033[00m\n" % msg
@@ -128,17 +129,18 @@ class BasePasswordTestCase(samba.tests.TestCase):
         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)
+        self._check_attribute(res, "badPasswordTime", badPasswordTime)
+        if not badPwdCountOnly:
+            self._check_attribute(res, "logonCount", logonCount)
+            self._check_attribute(res, "lastLogon", lastLogon)
+            self._check_attribute(res, "lastLogonTimestamp", lastLogonTimestamp)
+            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])
+            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)
@@ -148,16 +150,21 @@ class BasePasswordTestCase(samba.tests.TestCase):
         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
+        if not badPwdCountOnly:
+            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
+
+            self.assertEquals(uinfo3.acct_flags, expected_acb_info)
+            self.assertEquals(uinfo3.last_logon, lastLogon)
+            self.assertEquals(uinfo3.logon_count, logonCount)
 
         expected_bad_password_count = 0
         if badPwdCount is not None:
@@ -165,22 +172,21 @@ class BasePasswordTestCase(samba.tests.TestCase):
         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)
+        if not badPwdCountOnly:
+            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(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)
 
-        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.
diff --git a/source4/dsdb/tests/python/rodc_rwdc.py b/source4/dsdb/tests/python/rodc_rwdc.py
index 85fa85df187..87d1257d97f 100644
--- a/source4/dsdb/tests/python/rodc_rwdc.py
+++ b/source4/dsdb/tests/python/rodc_rwdc.py
@@ -112,7 +112,473 @@ def get_server_ref_from_samdb(samdb):
 
     return res[0]['serverReference'][0]
 
+class RodcRwdcCachedTests(password_lockout_base.BasePasswordTestCase):
+    counter = itertools.count(1).next
+
+    def _check_account_initial(self, dn):
+        self.force_replication()
+        return super(RodcRwdcCachedTests, self)._check_account_initial(dn)
+
+    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,
+                       badPwdCountOnly=False):
+        # Wait for the RWDC to get any delayed messages
+        # e.g. SendToSam or KRB5 bad passwords via winbindd
+        if (self.kerberos and isinstance(badPasswordTime, tuple) or
+            badPwdCount == 0):
+            time.sleep(5)
+
+        return super(RodcRwdcCachedTests,
+                     self)._check_account(dn, badPwdCount, badPasswordTime,
+                                          logonCount, lastLogon,
+                                          lastLogonTimestamp, lockoutTime,
+                                          userAccountControl,
+                                          msDSUserAccountControlComputed,
+                                          effective_bad_password_count, msg,
+                                          True)
+
+    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(RodcRwdcCachedTests, self).tearDown()
+        set_auto_replication(RWDC, True)
+
+    def setUp(self):
+        self.kerberos = False # To be set later
+
+        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(RodcRwdcCachedTests, self).setUp()
+        self.host_url = 'ldap://%s' % RODC
+
+        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,
+                                   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()
+
+    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)
+
+        self.kerberos = True
+
+        self.rodc_dn = get_server_ref_from_samdb(self.rodc_db)
+
+        res = self.rodc_db.search(self.rodc_dn,
+                                  scope=ldb.SCOPE_BASE,
+                                  attrs=['msDS-RevealOnDemandGroup'])
+
+        group = res[0]['msDS-RevealOnDemandGroup'][0]
+
+        m = ldb.Message()
+        m.dn = ldb.Dn(self.rwdc_db, group)
+        m['member'] = ldb.MessageElement(userdn, ldb.FLAG_MOD_ADD, 'member')
+        self.rwdc_db.modify(m)
+
+        m = ldb.Message()
+        m.dn = ldb.Dn(self.ldb, self.base_dn)
+
+        self.account_lockout_duration = 10
+        account_lockout_duration_ticks = -int(self.account_lockout_duration * (1e7))
+
+        m["lockoutDuration"] = ldb.MessageElement(str(account_lockout_duration_ticks),
+                                                  ldb.FLAG_MOD_REPLACE,
+                                                  "lockoutDuration")
+
+        self.lockout_observation_window = 10
+        lockout_observation_window_ticks = -int(self.lockout_observation_window * (1e7))
+
+        m["lockOutObservationWindow"] = ldb.MessageElement(str(lockout_observation_window_ticks),
+                                                           ldb.FLAG_MOD_REPLACE,
+                                                           "lockOutObservationWindow")
+
+        self.rwdc_db.modify(m)
+        self.force_replication()
+
+        self._test_login_lockout_rodc_rwdc(self.lockout1krb5_creds, userdn)
+
+    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)
+
+        self.kerberos = False
+
+        self.rodc_dn = get_server_ref_from_samdb(self.rodc_db)
+
+        res = self.rodc_db.search(self.rodc_dn,
+                                  scope=ldb.SCOPE_BASE,
+                                  attrs=['msDS-RevealOnDemandGroup'])
+
+        group = res[0]['msDS-RevealOnDemandGroup'][0]
+
+        m = ldb.Message()
+        m.dn = ldb.Dn(self.rwdc_db, group)
+        m['member'] = ldb.MessageElement(userdn, ldb.FLAG_MOD_ADD, 'member')
+        self.rwdc_db.modify(m)
+
+        self._test_login_lockout_rodc_rwdc(self.lockout1ntlm_creds, userdn)
+
+    def _test_login_lockout_rodc_rwdc(self, creds, userdn):
+        username = creds.get_username()
+        userpass = creds.get_password()
+
+        # 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)
+
+        badPasswordTime = 0
+        logonCount = 0
+        lastLogon = 0
+        lastLogonTimestamp=0
+        logoncount_relation = ''
+        lastlogon_relation = ''
+
+        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])
+
+        # 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=lockoutTime,
+                                  userAccountControl=
+                                    dsdb.UF_NORMAL_ACCOUNT,
+                                  msDSUserAccountControlComputed=0,
+                                  msg="lastLogon is way off")
+
+        # 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=lockoutTime,
+                                  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=lockoutTime,
+                                  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=lockoutTime,
+                                  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=lockoutTime,
+                                  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=lockoutTime,
+                                  lastLogon=("greater", lastLogon),
+                                  lastLogonTimestamp=lastLogonTimestamp,
+                                  userAccountControl=
+                                    dsdb.UF_NORMAL_ACCOUNT,
+                                  msDSUserAccountControlComputed=0)
 
 class RodcRwdcTests(password_lockout_base.BasePasswordTestCase):
     counter = itertools.count(1).next
-- 
2.11.0


From 27ea8e5672ef3603e4860a180bee1af6a9349c3f Mon Sep 17 00:00:00 2001
From: Garming Sam <garming at catalyst.net.nz>
Date: Wed, 26 Apr 2017 16:32:51 +1200
Subject: [PATCH 22/26] tests/rodc: Check SID restriction for SendToSam

Signed-off-by: Garming Sam <garming at catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet at samba.org>
---
 selftest/knownfail                     |  1 +
 source4/dsdb/tests/python/rodc_rwdc.py | 72 ++++++++++++++++++++++++++++++++++
 2 files changed, 73 insertions(+)

diff --git a/selftest/knownfail b/selftest/knownfail
index c6047c85445..6a98cd4b55b 100644
--- a/selftest/knownfail
+++ b/selftest/knownfail
@@ -335,3 +335,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__.RodcRwdcCachedTests.test_login_lockout_not_revealed
diff --git a/source4/dsdb/tests/python/rodc_rwdc.py b/source4/dsdb/tests/python/rodc_rwdc.py
index 87d1257d97f..b2b5dbdb1a5 100644
--- a/source4/dsdb/tests/python/rodc_rwdc.py
+++ b/source4/dsdb/tests/python/rodc_rwdc.py
@@ -283,6 +283,78 @@ class RodcRwdcCachedTests(password_lockout_base.BasePasswordTestCase):
 
         self._test_login_lockout_rodc_rwdc(self.lockout1ntlm_creds, userdn)
 
+    def test_login_lockout_not_revealed(self):
+        '''Test that SendToSam is restricted by preloaded users/groups'''
+
+        username = self.lockout1ntlm_creds.get_username()
+        userpass = self.lockout1ntlm_creds.get_password()
+        userdn = "cn=%s,cn=users,%s" % (username, self.base_dn)
+
+        # Preload but do not add to revealed group
+        preload_rodc_user(userdn)
+
+        self.kerberos = False
+
+        creds = self.lockout1ntlm_creds
+
+        # 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)
+
+        badPasswordTime = 0
+        logonCount = 0
+        lastLogon = 0
+        lastLogonTimestamp=0
+        logoncount_relation = ''
+        lastlogon_relation = ''
+
+        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])
+
+        # BadPwdCount on RODC increases alongside RWDC
+        res = self.rodc_db.search(userdn, attrs=['badPwdCount'])
+        self.assertTrue('badPwdCount' in res[0])
+        self.assertEqual(int(res[0]['badPwdCount'][0]), 1)
+
+        # Correct old password
+        creds_lockout.set_password(userpass)
+
+        ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout, lp=self.lp)
+
+        # Wait for potential SendToSam...
+        time.sleep(5)
+
+        # BadPwdCount on RODC decreases, but not the RWDC
+        res = self._check_account(userdn,
+                                  badPwdCount=1,
+                                  badPasswordTime=badPasswordTime,
+                                  logonCount=(logoncount_relation, logonCount),
+                                  lastLogon=('greater', lastLogon),
+                                  lastLogonTimestamp=lastLogonTimestamp,
+                                  userAccountControl=
+                                    dsdb.UF_NORMAL_ACCOUNT,
+                                  msDSUserAccountControlComputed=0,
+                                  msg='badPwdCount not reset on RWDC')
+
+        res = self.rodc_db.search(userdn, attrs=['badPwdCount'])
+        self.assertTrue('badPwdCount' in res[0])
+        self.assertEqual(int(res[0]['badPwdCount'][0]), 0)
+
     def _test_login_lockout_rodc_rwdc(self, creds, userdn):
         username = creds.get_username()
         userpass = creds.get_password()
-- 
2.11.0


From a3d638c6580c0074738013248d095bbc8bd50385 Mon Sep 17 00:00:00 2001
From: Garming Sam <garming at catalyst.net.nz>
Date: Wed, 19 Apr 2017 12:50:55 +1200
Subject: [PATCH 23/26] netlogon: Add necessary security checks for SendToSam

We eliminate a small race between GUID -> DN and ensure RODC can only
reset bad password count on accounts it is allowed to cache locally.

Signed-off-by: Garming Sam <garming at catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet at samba.org>
---
 selftest/knownfail                            |   1 -
 source4/rpc_server/netlogon/dcerpc_netlogon.c | 202 ++++++++++++++++++++++++++
 2 files changed, 202 insertions(+), 1 deletion(-)

diff --git a/selftest/knownfail b/selftest/knownfail
index 6a98cd4b55b..c6047c85445 100644
--- a/selftest/knownfail
+++ b/selftest/knownfail
@@ -335,4 +335,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__.RodcRwdcCachedTests.test_login_lockout_not_revealed
diff --git a/source4/rpc_server/netlogon/dcerpc_netlogon.c b/source4/rpc_server/netlogon/dcerpc_netlogon.c
index e41cd17da12..8094932c797 100644
--- a/source4/rpc_server/netlogon/dcerpc_netlogon.c
+++ b/source4/rpc_server/netlogon/dcerpc_netlogon.c
@@ -2252,6 +2252,185 @@ static NTSTATUS dcesrv_netr_ServerPasswordGet(struct dcesrv_call_state *dce_call
 	DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR);
 }
 
+/*
+  see if any SIDs in list1 are in list2
+ */
+static bool sid_list_match(const struct dom_sid **list1, const struct dom_sid **list2)
+{
+	unsigned int i, j;
+	/* do we ever have enough SIDs here to worry about O(n^2) ? */
+	for (i=0; list1[i]; i++) {
+		for (j=0; list2[j]; j++) {
+			if (dom_sid_equal(list1[i], list2[j])) {
+				return true;
+			}
+		}
+	}
+	return false;
+}
+
+/*
+  return an array of SIDs from a ldb_message given an attribute name
+  assumes the SIDs are in extended DN format
+ */
+static WERROR samdb_result_sid_array_dn(struct ldb_context *sam_ctx,
+					struct ldb_message *msg,
+					TALLOC_CTX *mem_ctx,
+					const char *attr,
+					const struct dom_sid ***sids)
+{
+	struct ldb_message_element *el;
+	unsigned int i;
+
+	el = ldb_msg_find_element(msg, attr);
+	if (!el) {
+		*sids = NULL;
+		return WERR_OK;
+	}
+
+	(*sids) = talloc_array(mem_ctx, const struct dom_sid *, el->num_values + 1);
+	W_ERROR_HAVE_NO_MEMORY(*sids);
+
+	for (i=0; i<el->num_values; i++) {
+		struct ldb_dn *dn = ldb_dn_from_ldb_val(mem_ctx, sam_ctx, &el->values[i]);
+		NTSTATUS status;
+		struct dom_sid *sid;
+
+		sid = talloc(*sids, struct dom_sid);
+		W_ERROR_HAVE_NO_MEMORY(sid);
+		status = dsdb_get_extended_dn_sid(dn, sid, "SID");
+		if (!NT_STATUS_IS_OK(status)) {
+			return WERR_INTERNAL_DB_CORRUPTION;
+		}
+		(*sids)[i] = sid;
+	}
+	(*sids)[i] = NULL;
+
+	return WERR_OK;
+}
+
+
+/*
+ * Return an array of SIDs from a ldb_message given an attribute name assumes
+ * the SIDs are in NDR form (with additional sids applied on the end).
+ */
+static WERROR samdb_result_sid_array_ndr(struct ldb_context *sam_ctx,
+					 struct ldb_message *msg,
+					 TALLOC_CTX *mem_ctx,
+					 const char *attr,
+					 const struct dom_sid ***sids,
+					 const struct dom_sid **additional_sids,
+					 unsigned int num_additional)
+{
+	struct ldb_message_element *el;
+	unsigned int i, j;
+
+	el = ldb_msg_find_element(msg, attr);
+	if (!el) {
+		*sids = NULL;
+		return WERR_OK;
+	}
+
+	/* Make array long enough for NULL and additional SID */
+	(*sids) = talloc_array(mem_ctx, const struct dom_sid *,
+			       el->num_values + num_additional + 1);
+	W_ERROR_HAVE_NO_MEMORY(*sids);
+
+	for (i=0; i<el->num_values; i++) {
+		enum ndr_err_code ndr_err;
+		struct dom_sid *sid;
+
+		sid = talloc(*sids, struct dom_sid);
+		W_ERROR_HAVE_NO_MEMORY(sid);
+
+		ndr_err = ndr_pull_struct_blob(&el->values[i], sid, sid,
+					       (ndr_pull_flags_fn_t)ndr_pull_dom_sid);
+		if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+			return WERR_INTERNAL_DB_CORRUPTION;
+		}
+		(*sids)[i] = sid;
+	}
+
+	for (j = 0; j < num_additional; j++) {
+		(*sids)[i++] = additional_sids[j];
+	}
+
+	(*sids)[i] = NULL;
+
+	return WERR_OK;
+}
+
+static bool sam_rodc_access_check(struct ldb_context *sam_ctx,
+				  TALLOC_CTX *mem_ctx,
+				  struct dom_sid *user_sid,
+				  struct ldb_dn *obj_dn)
+{
+	const char *rodc_attrs[] = { "msDS-KrbTgtLink", "msDS-NeverRevealGroup", "msDS-RevealOnDemandGroup", "objectGUID", NULL };
+	const char *obj_attrs[] = { "tokenGroups", "objectSid", "UserAccountControl", "msDS-KrbTgtLinkBL", NULL };
+	struct ldb_dn *rodc_dn;
+	int ret;
+	struct ldb_result *rodc_res = NULL, *obj_res = NULL;
+	const struct dom_sid *additional_sids[] = { NULL, NULL };
+	WERROR werr;
+	struct dom_sid *object_sid;
+	const struct dom_sid **never_reveal_sids, **reveal_sids, **token_sids;
+
+	rodc_dn = ldb_dn_new_fmt(mem_ctx, sam_ctx, "<SID=%s>",
+				 dom_sid_string(mem_ctx, user_sid));
+	if (!ldb_dn_validate(rodc_dn)) goto denied;
+
+	/* do the two searches we need */
+	ret = dsdb_search_dn(sam_ctx, mem_ctx, &rodc_res, rodc_dn, rodc_attrs,
+			     DSDB_SEARCH_SHOW_EXTENDED_DN);
+	if (ret != LDB_SUCCESS || rodc_res->count != 1) goto denied;
+
+	ret = dsdb_search_dn(sam_ctx, mem_ctx, &obj_res, obj_dn, obj_attrs, 0);
+	if (ret != LDB_SUCCESS || obj_res->count != 1) goto denied;
+
+	object_sid = samdb_result_dom_sid(mem_ctx, obj_res->msgs[0], "objectSid");
+
+	additional_sids[0] = object_sid;
+
+	werr = samdb_result_sid_array_dn(sam_ctx, rodc_res->msgs[0],
+					 mem_ctx, "msDS-NeverRevealGroup", &never_reveal_sids);
+	if (!W_ERROR_IS_OK(werr)) {
+		goto denied;
+	}
+
+	werr = samdb_result_sid_array_dn(sam_ctx, rodc_res->msgs[0],
+					 mem_ctx, "msDS-RevealOnDemandGroup", &reveal_sids);
+	if (!W_ERROR_IS_OK(werr)) {
+		goto denied;
+	}
+
+	/*
+	 * The SID list needs to include itself as well as the tokenGroups.
+	 *
+	 * TODO determine if sIDHistory is required for this check
+	 */
+	werr = samdb_result_sid_array_ndr(sam_ctx, obj_res->msgs[0],
+					  mem_ctx, "tokenGroups", &token_sids,
+					  additional_sids, 1);
+	if (!W_ERROR_IS_OK(werr) || token_sids==NULL) {
+		goto denied;
+	}
+
+	if (never_reveal_sids &&
+	    sid_list_match(token_sids, never_reveal_sids)) {
+		goto denied;
+	}
+
+	if (reveal_sids &&
+	    sid_list_match(token_sids, reveal_sids)) {
+		goto allowed;
+	}
+
+denied:
+	return false;
+allowed:
+	return true;
+
+}
 
 /*
   netr_NetrLogonSendToSam
@@ -2324,12 +2503,27 @@ static NTSTATUS dcesrv_netr_NetrLogonSendToSam(struct dcesrv_call_state *dce_cal
 		int ret = 0;
 
 
+		ret = ldb_transaction_start(sam_ctx);
+		if (ret != LDB_SUCCESS) {
+			return NT_STATUS_INTERNAL_ERROR;
+		}
+
 		ret = dsdb_find_dn_by_guid(sam_ctx,
 					   mem_ctx,
 					   &base_msg.message.reset_bad_password.guid,
 					   0,
 					   &dn);
 		if (ret != LDB_SUCCESS) {
+			ldb_transaction_cancel(sam_ctx);
+			return NT_STATUS_INVALID_PARAMETER;
+		}
+
+		if (creds->secure_channel_type == SEC_CHAN_RODC &&
+		    !sam_rodc_access_check(sam_ctx, mem_ctx, creds->sid, dn)) {
+			DEBUG(1, ("Client asked to reset bad password on "
+				  "an arbitrary user: %s\n",
+				  ldb_dn_get_linearized(dn)));
+			ldb_transaction_cancel(sam_ctx);
 			return NT_STATUS_INVALID_PARAMETER;
 		}
 
@@ -2337,14 +2531,22 @@ static NTSTATUS dcesrv_netr_NetrLogonSendToSam(struct dcesrv_call_state *dce_cal
 
 		ret = samdb_msg_add_int(sam_ctx, mem_ctx, msg, "badPwdCount", 0);
 		if (ret != LDB_SUCCESS) {
+			ldb_transaction_cancel(sam_ctx);
 			return NT_STATUS_INVALID_PARAMETER;
 		}
 
 		ret = dsdb_replace(sam_ctx, msg, 0);
 		if (ret != LDB_SUCCESS) {
+			ldb_transaction_cancel(sam_ctx);
 			return NT_STATUS_INVALID_PARAMETER;
 		}
 
+		ret = ldb_transaction_commit(sam_ctx);
+		if (ret != LDB_SUCCESS) {
+			ldb_transaction_cancel(sam_ctx);
+			return NT_STATUS_INTERNAL_ERROR;
+		}
+
 		break;
 	}
 	default:
-- 
2.11.0


From c5ecf4e0eae9db4953f422aa31f9a8503465e099 Mon Sep 17 00:00:00 2001
From: Garming Sam <garming at catalyst.net.nz>
Date: Fri, 21 Apr 2017 11:29:48 +1200
Subject: [PATCH 24/26] rpc_server: Move SID helpers into common

Signed-off-by: Garming Sam <garming at catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet at samba.org>
---
 source4/rpc_server/common/sid_helper.c        | 134 ++++++++++++++++++++++++++
 source4/rpc_server/drsuapi/getncchanges.c     | 109 +--------------------
 source4/rpc_server/netlogon/dcerpc_netlogon.c | 109 +--------------------
 source4/rpc_server/wscript_build              |   9 +-
 4 files changed, 144 insertions(+), 217 deletions(-)
 create mode 100644 source4/rpc_server/common/sid_helper.c

diff --git a/source4/rpc_server/common/sid_helper.c b/source4/rpc_server/common/sid_helper.c
new file mode 100644
index 00000000000..698249391ef
--- /dev/null
+++ b/source4/rpc_server/common/sid_helper.c
@@ -0,0 +1,134 @@
+/*
+   Unix SMB/CIFS implementation.
+
+   common sid helper functions
+
+   Copyright (C) Catalyst.NET Ltd 2017
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "rpc_server/dcerpc_server.h"
+#include "librpc/gen_ndr/ndr_security.h"
+#include "source4/dsdb/samdb/samdb.h"
+#include "rpc_server/common/sid_helper.h"
+#include "libcli/security/security.h"
+
+/*
+  see if any SIDs in list1 are in list2
+ */
+bool sid_list_match(const struct dom_sid **list1, const struct dom_sid **list2)
+{
+	unsigned int i, j;
+	/* do we ever have enough SIDs here to worry about O(n^2) ? */
+	for (i=0; list1[i]; i++) {
+		for (j=0; list2[j]; j++) {
+			if (dom_sid_equal(list1[i], list2[j])) {
+				return true;
+			}
+		}
+	}
+	return false;
+}
+
+/*
+ * Return an array of SIDs from a ldb_message given an attribute name assumes
+ * the SIDs are in NDR form (with additional sids applied on the end).
+ */
+WERROR samdb_result_sid_array_ndr(struct ldb_context *sam_ctx,
+				  struct ldb_message *msg,
+				  TALLOC_CTX *mem_ctx,
+				  const char *attr,
+					 const struct dom_sid ***sids,
+					 const struct dom_sid **additional_sids,
+					 unsigned int num_additional)
+{
+	struct ldb_message_element *el;
+	unsigned int i, j;
+
+	el = ldb_msg_find_element(msg, attr);
+	if (!el) {
+		*sids = NULL;
+		return WERR_OK;
+	}
+
+	/* Make array long enough for NULL and additional SID */
+	(*sids) = talloc_array(mem_ctx, const struct dom_sid *,
+			       el->num_values + num_additional + 1);
+	W_ERROR_HAVE_NO_MEMORY(*sids);
+
+	for (i=0; i<el->num_values; i++) {
+		enum ndr_err_code ndr_err;
+		struct dom_sid *sid;
+
+		sid = talloc(*sids, struct dom_sid);
+		W_ERROR_HAVE_NO_MEMORY(sid);
+
+		ndr_err = ndr_pull_struct_blob(&el->values[i], sid, sid,
+					       (ndr_pull_flags_fn_t)ndr_pull_dom_sid);
+		if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+			return WERR_INTERNAL_DB_CORRUPTION;
+		}
+		(*sids)[i] = sid;
+	}
+
+	for (j = 0; j < num_additional; j++) {
+		(*sids)[i++] = additional_sids[j];
+	}
+
+	(*sids)[i] = NULL;
+
+	return WERR_OK;
+}
+
+/*
+  return an array of SIDs from a ldb_message given an attribute name
+  assumes the SIDs are in extended DN format
+ */
+WERROR samdb_result_sid_array_dn(struct ldb_context *sam_ctx,
+				 struct ldb_message *msg,
+				 TALLOC_CTX *mem_ctx,
+				 const char *attr,
+				 const struct dom_sid ***sids)
+{
+	struct ldb_message_element *el;
+	unsigned int i;
+
+	el = ldb_msg_find_element(msg, attr);
+	if (!el) {
+		*sids = NULL;
+		return WERR_OK;
+	}
+
+	(*sids) = talloc_array(mem_ctx, const struct dom_sid *, el->num_values + 1);
+	W_ERROR_HAVE_NO_MEMORY(*sids);
+
+	for (i=0; i<el->num_values; i++) {
+		struct ldb_dn *dn = ldb_dn_from_ldb_val(mem_ctx, sam_ctx, &el->values[i]);
+		NTSTATUS status;
+		struct dom_sid *sid;
+
+		sid = talloc(*sids, struct dom_sid);
+		W_ERROR_HAVE_NO_MEMORY(sid);
+		status = dsdb_get_extended_dn_sid(dn, sid, "SID");
+		if (!NT_STATUS_IS_OK(status)) {
+			return WERR_INTERNAL_DB_CORRUPTION;
+		}
+		(*sids)[i] = sid;
+	}
+	(*sids)[i] = NULL;
+
+	return WERR_OK;
+}
diff --git a/source4/rpc_server/drsuapi/getncchanges.c b/source4/rpc_server/drsuapi/getncchanges.c
index 1f02f2c0936..ce8e7122ac5 100644
--- a/source4/rpc_server/drsuapi/getncchanges.c
+++ b/source4/rpc_server/drsuapi/getncchanges.c
@@ -32,6 +32,7 @@
 #include "libcli/security/session.h"
 #include "rpc_server/drsuapi/dcesrv_drsuapi.h"
 #include "rpc_server/dcerpc_server_proto.h"
+#include "rpc_server/common/sid_helper.h"
 #include "../libcli/drsuapi/drsuapi.h"
 #include "lib/util/binsearch.h"
 #include "lib/util/tsort.h"
@@ -1140,114 +1141,6 @@ static WERROR getncchanges_rid_alloc(struct drsuapi_bind_state *b_state,
 }
 
 /*
-  return an array of SIDs from a ldb_message given an attribute name
-  assumes the SIDs are in extended DN format
- */
-static WERROR samdb_result_sid_array_dn(struct ldb_context *sam_ctx,
-					struct ldb_message *msg,
-					TALLOC_CTX *mem_ctx,
-					const char *attr,
-					const struct dom_sid ***sids)
-{
-	struct ldb_message_element *el;
-	unsigned int i;
-
-	el = ldb_msg_find_element(msg, attr);
-	if (!el) {
-		*sids = NULL;
-		return WERR_OK;
-	}
-
-	(*sids) = talloc_array(mem_ctx, const struct dom_sid *, el->num_values + 1);
-	W_ERROR_HAVE_NO_MEMORY(*sids);
-
-	for (i=0; i<el->num_values; i++) {
-		struct ldb_dn *dn = ldb_dn_from_ldb_val(mem_ctx, sam_ctx, &el->values[i]);
-		NTSTATUS status;
-		struct dom_sid *sid;
-
-		sid = talloc(*sids, struct dom_sid);
-		W_ERROR_HAVE_NO_MEMORY(sid);
-		status = dsdb_get_extended_dn_sid(dn, sid, "SID");
-		if (!NT_STATUS_IS_OK(status)) {
-			return WERR_INTERNAL_DB_CORRUPTION;
-		}
-		(*sids)[i] = sid;
-	}
-	(*sids)[i] = NULL;
-
-	return WERR_OK;
-}
-
-
-/*
- * Return an array of SIDs from a ldb_message given an attribute name assumes
- * the SIDs are in NDR form (with additional sids applied on the end).
- */
-static WERROR samdb_result_sid_array_ndr(struct ldb_context *sam_ctx,
-					 struct ldb_message *msg,
-					 TALLOC_CTX *mem_ctx,
-					 const char *attr,
-					 const struct dom_sid ***sids,
-					 const struct dom_sid **additional_sids,
-					 unsigned int num_additional)
-{
-	struct ldb_message_element *el;
-	unsigned int i, j;
-
-	el = ldb_msg_find_element(msg, attr);
-	if (!el) {
-		*sids = NULL;
-		return WERR_OK;
-	}
-
-	/* Make array long enough for NULL and additional SID */
-	(*sids) = talloc_array(mem_ctx, const struct dom_sid *,
-			       el->num_values + num_additional + 1);
-	W_ERROR_HAVE_NO_MEMORY(*sids);
-
-	for (i=0; i<el->num_values; i++) {
-		enum ndr_err_code ndr_err;
-		struct dom_sid *sid;
-
-		sid = talloc(*sids, struct dom_sid);
-		W_ERROR_HAVE_NO_MEMORY(sid);
-
-		ndr_err = ndr_pull_struct_blob(&el->values[i], sid, sid,
-					       (ndr_pull_flags_fn_t)ndr_pull_dom_sid);
-		if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
-			return WERR_INTERNAL_DB_CORRUPTION;
-		}
-		(*sids)[i] = sid;
-	}
-
-	for (j = 0; j < num_additional; j++) {
-		(*sids)[i++] = additional_sids[j];
-	}
-
-	(*sids)[i] = NULL;
-
-	return WERR_OK;
-}
-
-/*
-  see if any SIDs in list1 are in list2
- */
-static bool sid_list_match(const struct dom_sid **list1, const struct dom_sid **list2)
-{
-	unsigned int i, j;
-	/* do we ever have enough SIDs here to worry about O(n^2) ? */
-	for (i=0; list1[i]; i++) {
-		for (j=0; list2[j]; j++) {
-			if (dom_sid_equal(list1[i], list2[j])) {
-				return true;
-			}
-		}
-	}
-	return false;
-}
-
-/*
   handle a DRSUAPI_EXOP_REPL_SECRET call
  */
 static WERROR getncchanges_repl_secret(struct drsuapi_bind_state *b_state,
diff --git a/source4/rpc_server/netlogon/dcerpc_netlogon.c b/source4/rpc_server/netlogon/dcerpc_netlogon.c
index 8094932c797..9392a3975d0 100644
--- a/source4/rpc_server/netlogon/dcerpc_netlogon.c
+++ b/source4/rpc_server/netlogon/dcerpc_netlogon.c
@@ -42,6 +42,7 @@
 #include "librpc/gen_ndr/ndr_winbind.h"
 #include "librpc/gen_ndr/ndr_winbind_c.h"
 #include "lib/socket/netif.h"
+#include "rpc_server/common/sid_helper.h"
 
 #define DCESRV_INTERFACE_NETLOGON_BIND(call, iface) \
        dcesrv_interface_netlogon_bind(call, iface)
@@ -2252,114 +2253,6 @@ static NTSTATUS dcesrv_netr_ServerPasswordGet(struct dcesrv_call_state *dce_call
 	DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR);
 }
 
-/*
-  see if any SIDs in list1 are in list2
- */
-static bool sid_list_match(const struct dom_sid **list1, const struct dom_sid **list2)
-{
-	unsigned int i, j;
-	/* do we ever have enough SIDs here to worry about O(n^2) ? */
-	for (i=0; list1[i]; i++) {
-		for (j=0; list2[j]; j++) {
-			if (dom_sid_equal(list1[i], list2[j])) {
-				return true;
-			}
-		}
-	}
-	return false;
-}
-
-/*
-  return an array of SIDs from a ldb_message given an attribute name
-  assumes the SIDs are in extended DN format
- */
-static WERROR samdb_result_sid_array_dn(struct ldb_context *sam_ctx,
-					struct ldb_message *msg,
-					TALLOC_CTX *mem_ctx,
-					const char *attr,
-					const struct dom_sid ***sids)
-{
-	struct ldb_message_element *el;
-	unsigned int i;
-
-	el = ldb_msg_find_element(msg, attr);
-	if (!el) {
-		*sids = NULL;
-		return WERR_OK;
-	}
-
-	(*sids) = talloc_array(mem_ctx, const struct dom_sid *, el->num_values + 1);
-	W_ERROR_HAVE_NO_MEMORY(*sids);
-
-	for (i=0; i<el->num_values; i++) {
-		struct ldb_dn *dn = ldb_dn_from_ldb_val(mem_ctx, sam_ctx, &el->values[i]);
-		NTSTATUS status;
-		struct dom_sid *sid;
-
-		sid = talloc(*sids, struct dom_sid);
-		W_ERROR_HAVE_NO_MEMORY(sid);
-		status = dsdb_get_extended_dn_sid(dn, sid, "SID");
-		if (!NT_STATUS_IS_OK(status)) {
-			return WERR_INTERNAL_DB_CORRUPTION;
-		}
-		(*sids)[i] = sid;
-	}
-	(*sids)[i] = NULL;
-
-	return WERR_OK;
-}
-
-
-/*
- * Return an array of SIDs from a ldb_message given an attribute name assumes
- * the SIDs are in NDR form (with additional sids applied on the end).
- */
-static WERROR samdb_result_sid_array_ndr(struct ldb_context *sam_ctx,
-					 struct ldb_message *msg,
-					 TALLOC_CTX *mem_ctx,
-					 const char *attr,
-					 const struct dom_sid ***sids,
-					 const struct dom_sid **additional_sids,
-					 unsigned int num_additional)
-{
-	struct ldb_message_element *el;
-	unsigned int i, j;
-
-	el = ldb_msg_find_element(msg, attr);
-	if (!el) {
-		*sids = NULL;
-		return WERR_OK;
-	}
-
-	/* Make array long enough for NULL and additional SID */
-	(*sids) = talloc_array(mem_ctx, const struct dom_sid *,
-			       el->num_values + num_additional + 1);
-	W_ERROR_HAVE_NO_MEMORY(*sids);
-
-	for (i=0; i<el->num_values; i++) {
-		enum ndr_err_code ndr_err;
-		struct dom_sid *sid;
-
-		sid = talloc(*sids, struct dom_sid);
-		W_ERROR_HAVE_NO_MEMORY(sid);
-
-		ndr_err = ndr_pull_struct_blob(&el->values[i], sid, sid,
-					       (ndr_pull_flags_fn_t)ndr_pull_dom_sid);
-		if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
-			return WERR_INTERNAL_DB_CORRUPTION;
-		}
-		(*sids)[i] = sid;
-	}
-
-	for (j = 0; j < num_additional; j++) {
-		(*sids)[i++] = additional_sids[j];
-	}
-
-	(*sids)[i] = NULL;
-
-	return WERR_OK;
-}
-
 static bool sam_rodc_access_check(struct ldb_context *sam_ctx,
 				  TALLOC_CTX *mem_ctx,
 				  struct dom_sid *user_sid,
diff --git a/source4/rpc_server/wscript_build b/source4/rpc_server/wscript_build
index f1b1f19455a..966e07ee641 100644
--- a/source4/rpc_server/wscript_build
+++ b/source4/rpc_server/wscript_build
@@ -7,10 +7,17 @@ bld.SAMBA_SUBSYSTEM('DCERPC_SHARE',
 	enabled=bld.CONFIG_SET('WITH_NTVFS_FILESERVER'),
 	)
 
+bld.SAMBA_SUBSYSTEM('DCERPC_SID_HELPER',
+	source='common/sid_helper.c',
+	autoproto='common/sid_helper.h',
+	deps='ldb',
+	enabled=bld.AD_DC_BUILD_IS_ENABLED(),
+	)
+
 bld.SAMBA_SUBSYSTEM('DCERPC_COMMON',
 	source='common/server_info.c common/forward.c common/reply.c dcesrv_auth.c common/loadparm.c',
 	autoproto='common/proto.h',
-	deps='ldb DCERPC_SHARE samba_server_gensec',
+	deps='ldb DCERPC_SHARE DCERPC_SID_HELPER samba_server_gensec',
 	enabled=bld.AD_DC_BUILD_IS_ENABLED()
 	)
 
-- 
2.11.0


From ffbf478240bd1202f103d1a5fbf6afa9c7e6a5be Mon Sep 17 00:00:00 2001
From: Garming Sam <garming at catalyst.net.nz>
Date: Mon, 22 May 2017 13:59:22 +1200
Subject: [PATCH 25/26] getncchanges: Do not filter EXOPs using highwatermark

Prior to this patch, any REPL_SECRETS could be filtered accidentally.

Signed-off-by: Garming Sam <garming at catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet at samba.org>
---
 source4/rpc_server/drsuapi/getncchanges.c | 43 ++++++++++++++++++-------------
 1 file changed, 25 insertions(+), 18 deletions(-)

diff --git a/source4/rpc_server/drsuapi/getncchanges.c b/source4/rpc_server/drsuapi/getncchanges.c
index ce8e7122ac5..da294a6623a 100644
--- a/source4/rpc_server/drsuapi/getncchanges.c
+++ b/source4/rpc_server/drsuapi/getncchanges.c
@@ -2358,28 +2358,35 @@ allowed:
 
 		extra_filter = lpcfg_parm_string(dce_call->conn->dce_ctx->lp_ctx, NULL, "drs", "object filter");
 
-		if (req10->uptodateness_vector != NULL) {
-			udv = req10->uptodateness_vector;
-		} else {
-			udv = &empty_udv;
-		}
+		if (req10->extended_op == DRSUAPI_EXOP_NONE) {
+			if (req10->uptodateness_vector != NULL) {
+				udv = req10->uptodateness_vector;
+			} else {
+				udv = &empty_udv;
+			}
 
-		getnc_state->min_usn = req10->highwatermark.highest_usn;
-		for (i = 0; i < udv->count; i++) {
-			bool match;
-			const struct drsuapi_DsReplicaCursor *cur =
-				&udv->cursors[i];
+			getnc_state->min_usn = req10->highwatermark.highest_usn;
+			for (i = 0; i < udv->count; i++) {
+				bool match;
+				const struct drsuapi_DsReplicaCursor *cur =
+					&udv->cursors[i];
 
-			match = GUID_equal(&invocation_id,
-					   &cur->source_dsa_invocation_id);
-			if (!match) {
-				continue;
-			}
-			if (cur->highest_usn > getnc_state->min_usn) {
-				getnc_state->min_usn = cur->highest_usn;
+				match = GUID_equal(&invocation_id,
+						   &cur->source_dsa_invocation_id);
+				if (!match) {
+					continue;
+				}
+				if (cur->highest_usn > getnc_state->min_usn) {
+					getnc_state->min_usn = cur->highest_usn;
+				}
+				break;
 			}
-			break;
+		} else {
+			/* We do not want REPL_SECRETS or REPL_SINGLE to return empty-handed */
+			udv = &empty_udv;
+			getnc_state->min_usn = 0;
 		}
+
 		getnc_state->max_usn = getnc_state->min_usn;
 
 		getnc_state->final_udv = talloc_zero(getnc_state,
-- 
2.11.0


From a92189af85417df1cd2e870b31e5344dca7de9e4 Mon Sep 17 00:00:00 2001
From: Garming Sam <garming at catalyst.net.nz>
Date: Mon, 22 May 2017 15:08:27 +1200
Subject: [PATCH 26/26] tests/rodc: Check that new passwords trigger wiping on
 RODC

This appears to have been working correctly, but we just haven't had a test for it.

Signed-off-by: Garming Sam <garming at catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet at samba.org>
---
 source4/dsdb/tests/python/rodc_rwdc.py | 34 ++++++++++++++++++++++++++++++++++
 1 file changed, 34 insertions(+)

diff --git a/source4/dsdb/tests/python/rodc_rwdc.py b/source4/dsdb/tests/python/rodc_rwdc.py
index b2b5dbdb1a5..371ff7415c3 100644
--- a/source4/dsdb/tests/python/rodc_rwdc.py
+++ b/source4/dsdb/tests/python/rodc_rwdc.py
@@ -172,6 +172,15 @@ class RodcRwdcCachedTests(password_lockout_base.BasePasswordTestCase):
             print stderr
             raise RodcRwdcTestException()
 
+    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 tearDown(self):
         super(RodcRwdcCachedTests, self).tearDown()
         set_auto_replication(RWDC, True)
@@ -215,6 +224,31 @@ class RodcRwdcCachedTests(password_lockout_base.BasePasswordTestCase):
         # make sure DCs are synchronized before the test
         self.force_replication()
 
+    def test_cache_and_flush_password(self):
+        username = self.lockout1krb5_creds.get_username()
+        userpass = self.lockout1krb5_creds.get_password()
+        userdn = "cn=%s,cn=users,%s" % (username, self.base_dn)
+
+        ldb_system = SamDB(session_info=system_session(self.lp),
+                           credentials=self.global_creds, lp=self.lp)
+
+        res = ldb_system.search(userdn, attrs=['unicodePwd'])
+        self.assertFalse('unicodePwd' in res[0])
+
+        preload_rodc_user(userdn)
+
+        res = ldb_system.search(userdn, attrs=['unicodePwd'])
+        self.assertTrue('unicodePwd' in res[0])
+
+        newpass = userpass + '!'
+
+        # Forcing replication should blank out password (when changed)
+        self._change_password(userdn, userpass, newpass)
+        self.force_replication()
+
+        res = ldb_system.search(userdn, attrs=['unicodePwd'])
+        self.assertFalse('unicodePwd' in res[0])
+
     def test_login_lockout_krb5(self):
         username = self.lockout1krb5_creds.get_username()
         userpass = self.lockout1krb5_creds.get_password()
-- 
2.11.0



More information about the samba-technical mailing list