[Patches Part 2] winbindd changes the local password and gets NT_STATUS_WRONG_PASSWORD for the remote change (bug #12782)

Stefan Metzmacher metze at samba.org
Mon Jun 26 06:42:10 UTC 2017


Hi,

> here're preparation patches in order to fix
> https://bugzilla.samba.org/show_bug.cgi?id=12782
> winbindd changes the local password and gets NT_STATUS_WRONG_PASSWORD
> for the remote change

here're the real fixes.

The problem is that we have two places where a password change can fail.

The first is likely only happening in a cluster, storing the password
may fail because ctdb is not in a good shape. Such a broken
node should not attempt to change the password on the server.
The resulting fix was:

commit 29b173d2a70745922d8345bfc6bd1da08951dfd3
Author:     Stefan Metzmacher <metze at samba.org>
AuthorDate: Sat Jan 31 10:42:09 2015 +0000
Commit:     Günther Deschner <gd at samba.org>
CommitDate: Thu Mar 12 17:13:43 2015 +0100

    s4:trust_utils: store new trust/machine passwords before trying it
remotely.

    If this fails we can still fallback to the old password...

    Before trying the password change we verify the dc knows our current
password.

    This should make the password changes much more robust.

    Signed-off-by: Stefan Metzmacher <metze at samba.org>
    Reviewed-by: Guenther Deschner <gd at samba.org>

As we were able to talk to the server milliseconds before,
I was assuming the password change is very unlikely to fail.

But it turns out that the remote password change can really fail.
E.g. if the server has RefusePasswordChange=1 it returns
STATUS_WRONG_PASSWORD.

The new strategy uses a more complex structure to store
the passwords (and more details) in secrets.tdb.

We now call secrets_prepare_password_change() before
doing the remote change, that will stores the new (pending) password.
to persistent storage.

After the remote change we call

- secrets_finish_password_change() on success,
- secrets_failed_password_change() if we got a disconnect during
  the remote change and don't know if the password was changed or not
- secrets_defer_password_change() if the password change was rejected.

A retry after a failed or deferred change will reuse the already stored
password, that should solve the case where we may changed the password
on one server and got a disconnect and later retry on a different server.

While being there introducing an NDR structure I added a lot of useful
stuff that will make future improvements easier to add without the need
to change the layout:
- avoid recalculating kerberos keys when generating the in memory
  keytab
- add support for multi-tenancy (joining more than one domain).

metze
-------------- next part --------------
From 918858e94203376a6d7bd4838e110adfe5a98c85 Mon Sep 17 00:00:00 2001
From: Stefan Metzmacher <metze at samba.org>
Date: Wed, 17 May 2017 11:35:20 +0200
Subject: [PATCH 01/15] lsa.idl: make lsa_DnsDomainInfo [public]

BUG: https://bugzilla.samba.org/show_bug.cgi?id=12782

Signed-off-by: Stefan Metzmacher <metze at samba.org>
Reviewed-by: Andreas Schneider <asn at samba.org>
---
 librpc/idl/lsa.idl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/librpc/idl/lsa.idl b/librpc/idl/lsa.idl
index 68569db..b3f4255 100644
--- a/librpc/idl/lsa.idl
+++ b/librpc/idl/lsa.idl
@@ -367,7 +367,7 @@ import "misc.idl", "security.idl";
 		uint8 log_is_full;
 	} lsa_AuditFullQueryInfo;
 
-	typedef struct {
+	typedef [public] struct {
 		/* it's important that we use the lsa_StringLarge here,
 		 * because otherwise windows clients result with such dns hostnames
 		 * e.g. w2k3-client.samba4.samba.orgsamba4.samba.org
-- 
1.9.1


From 6d9cc79c11df178330e509d545985a226732474a Mon Sep 17 00:00:00 2001
From: Stefan Metzmacher <metze at samba.org>
Date: Wed, 17 May 2017 11:35:37 +0200
Subject: [PATCH 02/15] netlogon.idl: make netr_TrustFlags [public]

BUG: https://bugzilla.samba.org/show_bug.cgi?id=12782

Signed-off-by: Stefan Metzmacher <metze at samba.org>
Reviewed-by: Andreas Schneider <asn at samba.org>
---
 librpc/idl/netlogon.idl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/librpc/idl/netlogon.idl b/librpc/idl/netlogon.idl
index 4f9da3d..4dd3769 100644
--- a/librpc/idl/netlogon.idl
+++ b/librpc/idl/netlogon.idl
@@ -1261,7 +1261,7 @@ interface netlogon
 
 	/****************/
 	/* Function 0x1d */
-	typedef [bitmap32bit] bitmap {
+	typedef [public,bitmap32bit] bitmap {
 		NETR_TRUST_FLAG_IN_FOREST = 0x00000001,
 		NETR_TRUST_FLAG_OUTBOUND  = 0x00000002,
 		NETR_TRUST_FLAG_TREEROOT  = 0x00000004,
-- 
1.9.1


From 23236e78af68b6c5742f54179c2cf459291f65f2 Mon Sep 17 00:00:00 2001
From: Stefan Metzmacher <metze at samba.org>
Date: Wed, 17 May 2017 10:09:01 +0200
Subject: [PATCH 03/15] netlogon.idl: use lsa_TrustType and lsa_TrustAttributes
 in netr_trust_extension

BUG: https://bugzilla.samba.org/show_bug.cgi?id=12782

Signed-off-by: Stefan Metzmacher <metze at samba.org>
Reviewed-by: Andreas Schneider <asn at samba.org>
---
 librpc/idl/netlogon.idl | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/librpc/idl/netlogon.idl b/librpc/idl/netlogon.idl
index 4dd3769..9a5159d 100644
--- a/librpc/idl/netlogon.idl
+++ b/librpc/idl/netlogon.idl
@@ -1369,8 +1369,8 @@ interface netlogon
 		[value(8)] uint32 size;
 		netr_TrustFlags flags;
 		uint32 parent_index;
-		uint32 trust_type;
-		uint32 trust_attributes;
+		lsa_TrustType trust_type;
+		lsa_TrustAttributes trust_attributes;
 	} netr_trust_extension;
 
 	typedef struct {
-- 
1.9.1


From 75d791c582d4e6634ddbcd56b828893a8a295cfe Mon Sep 17 00:00:00 2001
From: Stefan Metzmacher <metze at samba.org>
Date: Wed, 17 May 2017 10:11:18 +0200
Subject: [PATCH 04/15] secrets.idl: add secrets_domain_info that will be used
 in secrets.tdb for machine account trusts

This blob will be store in secrets.tdb. It makes it possible to store much
more useful details about the workstation trust.

The key feature that that triggered this change is the ability
to store details for the next password change before doing
the remote change. This will allow us to recover from failures.

While being there I also thought about possible new features,
which we may implement in the near future.

We also store the raw UTF16 like cleartext buffer as well as derived
keys like the NTHASH (arcfour-hmac-md5 key) and other kerberos keys.
This will allow us to avoid recalculating the keys for an in memory
keytab in future.

I also added pointer to an optional lsa_ForestTrustInformation structure,
which might be useful to implement multi-tenancy in future.

BUG: https://bugzilla.samba.org/show_bug.cgi?id=12782

Signed-off-by: Stefan Metzmacher <metze at samba.org>
Reviewed-by: Andreas Schneider <asn at samba.org>
---
 source3/librpc/idl/secrets.idl | 92 +++++++++++++++++++++++++++++++++++++++++-
 source3/librpc/wscript_build   |  2 +-
 2 files changed, 92 insertions(+), 2 deletions(-)

diff --git a/source3/librpc/idl/secrets.idl b/source3/librpc/idl/secrets.idl
index 1d0ba19..2c06fa6 100644
--- a/source3/librpc/idl/secrets.idl
+++ b/source3/librpc/idl/secrets.idl
@@ -1,6 +1,6 @@
 #include "idl_types.h"
 
-import "security.idl";
+import "misc.idl", "samr.idl", "lsa.idl", "netlogon.idl", "security.idl";
 
 /*
    IDL structures for secrets code
@@ -37,5 +37,95 @@ import "security.idl";
 		security_descriptor *sd;
 	} lsa_secret;
 
+	/*
+	 * This is the on-disc format the workstation trust.
+	 *
+	 * DO NOT CHANGE
+	 * without changing secrets_domain_info_version
+	 * and adding glue code. Discuss on samba-technical
+	 * first!
+	 */
+	typedef struct {
+		uint32 keytype;
+		uint32 iteration_count;
+		[flag(NDR_SECRET)] DATA_BLOB value;
+	} secrets_domain_info1_kerberos_key;
+
+	typedef struct {
+		NTTIME change_time;
+		[string,charset(UTF16)] uint16 change_server[];
+
+		[flag(NDR_SECRET)] DATA_BLOB cleartext_blob;
+		[flag(NDR_SECRET)] samr_Password nt_hash;
+
+		[string,charset(UTF16)] uint16 *salt_data;
+		uint32 default_iteration_count;
+		uint16 num_keys;
+		secrets_domain_info1_kerberos_key keys[num_keys];
+	} secrets_domain_info1_password;
+
+	typedef struct {
+		NTSTATUS local_status;
+		NTSTATUS remote_status;
+		NTTIME change_time;
+		[string,charset(UTF16)] uint16 change_server[];
+		[ref] secrets_domain_info1_password *password;
+	} secrets_domain_info1_change;
+
+	typedef [public] struct {
+		[value(0)] hyper reserved_flags;
+
+		NTTIME join_time;
+
+		[string,charset(UTF16)] uint16 computer_name[];
+		[string,charset(UTF16)] uint16 account_name[];
+		netr_SchannelType secure_channel_type;
+
+		lsa_DnsDomainInfo domain_info;
+		netr_TrustFlags trust_flags;
+		lsa_TrustType trust_type;
+		lsa_TrustAttributes trust_attributes;
+
+		/*
+		 * This is unused currently, it might
+		 * be useful to implement multi-tenancy (joining multiple domains)
+		 * in future.
+		 *
+		 * Or we could use it to do other filtering of domains.
+		 */
+		[value(NULL)] lsa_ForestTrustInformation *reserved_routing;
+
+		kerb_EncTypes supported_enc_types;
+		[string,charset(UTF16)] uint16 *salt_principal;
+
+		NTTIME password_last_change;
+		hyper password_changes;
+		secrets_domain_info1_change *next_change;
+
+		[ref] secrets_domain_info1_password *password;
+		secrets_domain_info1_password *old_password;
+		secrets_domain_info1_password *older_password;
+	} secrets_domain_info1;
+
+	typedef [v1_enum] enum {
+		SECRETS_DOMAIN_INFO_VERSION_1 = 0x00000001
+	} secrets_domain_info_version;
+
+	/*
+	 * If we ever need to change this we need to
+	 * change secrets_domain_info into
+	 * secrets_domain_info_v1
+	 */
+	typedef union {
+		[case(SECRETS_DOMAIN_INFO_VERSION_1)]
+			secrets_domain_info1 *info1;
+		[default];
+	} secrets_domain_infoU;
+
+	typedef [public] struct {
+		secrets_domain_info_version version;
+		[value(0)] uint32 reserved;
+		[switch_is(version)] secrets_domain_infoU info;
+	} secrets_domain_infoB;
 }
 
diff --git a/source3/librpc/wscript_build b/source3/librpc/wscript_build
index 1d8c17b..9260350 100644
--- a/source3/librpc/wscript_build
+++ b/source3/librpc/wscript_build
@@ -27,7 +27,7 @@ bld.SAMBA3_SUBSYSTEM('NDR_LEASES_DB',
 
 bld.SAMBA3_SUBSYSTEM('NDR_SECRETS',
 	source='gen_ndr/ndr_secrets.c',
-	public_deps='ndr NDR_SECURITY'
+	public_deps='ndr NDR_SAMR NDR_LSA NDR_NETLOGON NDR_SECURITY'
 	)
 
 bld.SAMBA3_SUBSYSTEM('NDR_PERFCOUNT',
-- 
1.9.1


From df2b1797d67ccb34cae1a10933eadc0c3709f128 Mon Sep 17 00:00:00 2001
From: Stefan Metzmacher <metze at samba.org>
Date: Fri, 19 May 2017 16:28:17 +0200
Subject: [PATCH 05/15] s3:secrets: add infrastructure to use
 secrets_domain_infoB to store credentials

We now store various hashed keys at change time and maintain a lot of details
that will help debugging failed password changes.

We keep storing the legacy values:
 SECRETS/SID/
 SECRETS/DOMGUID/
 SECRETS/MACHINE_LAST_CHANGE_TIME/
 SECRETS/MACHINE_PASSWORD/
 SECRETS/MACHINE_PASSWORD.PREV/
 SECRETS/SALTING_PRINCIPAL/DES/

This allows downgrades to older Samba versions.

BUG: https://bugzilla.samba.org/show_bug.cgi?id=12782

Signed-off-by: Stefan Metzmacher <metze at samba.org>
---
 source3/include/secrets.h                |   28 +
 source3/passdb/machine_account_secrets.c | 1393 ++++++++++++++++++++++++++++++
 2 files changed, 1421 insertions(+)

diff --git a/source3/include/secrets.h b/source3/include/secrets.h
index fc8e118..0363b6b 100644
--- a/source3/include/secrets.h
+++ b/source3/include/secrets.h
@@ -29,6 +29,7 @@
 #define SECRETS_MACHINE_LAST_CHANGE_TIME "SECRETS/MACHINE_LAST_CHANGE_TIME"
 #define SECRETS_MACHINE_SEC_CHANNEL_TYPE "SECRETS/MACHINE_SEC_CHANNEL_TYPE"
 #define SECRETS_MACHINE_TRUST_ACCOUNT_NAME "SECRETS/SECRETS_MACHINE_TRUST_ACCOUNT_NAME"
+#define SECRETS_MACHINE_DOMAIN_INFO "SECRETS/MACHINE_DOMAIN_INFO"
 /* this one is for storing trusted domain account password */
 #define SECRETS_DOMTRUST_ACCT_PASS "SECRETS/$DOMTRUST.ACC"
 
@@ -110,6 +111,33 @@ bool secrets_fetch_trusted_domain_password(const char *domain, char** pwd,
                                            struct dom_sid  *sid, time_t *pass_last_set_time);
 bool secrets_store_trusted_domain_password(const char* domain, const char* pwd,
                                            const struct dom_sid  *sid);
+struct libnet_JoinCtx;
+NTSTATUS secrets_store_JoinCtx(const struct libnet_JoinCtx *r);
+struct secrets_domain_info1;
+struct secrets_domain_info1_change;
+void secrets_debug_domain_info(int lvl, const struct secrets_domain_info1 *info,
+			       const char *name);
+char *secrets_domain_info_string(TALLOC_CTX *mem_ctx, const struct secrets_domain_info1 *info1,
+				 const char *name, bool include_secrets);
+NTSTATUS secrets_fetch_or_upgrade_domain_info(const char *domain,
+					TALLOC_CTX *mem_ctx,
+					struct secrets_domain_info1 **pinfo);
+NTSTATUS secrets_prepare_password_change(const char *domain, const char *dcname,
+					 const char *cleartext_unix,
+					 TALLOC_CTX *mem_ctx,
+					 struct secrets_domain_info1 **pinfo,
+					 struct secrets_domain_info1_change **pprev);
+NTSTATUS secrets_failed_password_change(const char *change_server,
+					NTSTATUS local_status,
+					NTSTATUS remote_status,
+					const struct secrets_domain_info1 *info);
+NTSTATUS secrets_defer_password_change(const char *change_server,
+				       NTSTATUS local_status,
+				       NTSTATUS remote_status,
+				       const struct secrets_domain_info1 *info);
+NTSTATUS secrets_finish_password_change(const char *change_server,
+					NTTIME change_time,
+					const struct secrets_domain_info1 *info);
 bool secrets_delete_machine_password_ex(const char *domain, const char *realm);
 bool secrets_delete_domain_sid(const char *domain);
 bool secrets_store_machine_password(const char *pass, const char *domain, enum netr_SchannelType sec_channel);
diff --git a/source3/passdb/machine_account_secrets.c b/source3/passdb/machine_account_secrets.c
index 9a96a3f..4ac197f 100644
--- a/source3/passdb/machine_account_secrets.c
+++ b/source3/passdb/machine_account_secrets.c
@@ -31,9 +31,17 @@
 #include "util_tdb.h"
 #include "libcli/security/security.h"
 
+#include "librpc/gen_ndr/libnet_join.h"
+#include "librpc/gen_ndr/ndr_secrets.h"
+#include "lib/crypto/crypto.h"
+#include "lib/krb5_wrap/krb5_samba.h"
+#include "lib/util/time_basic.h"
+
 #undef DBGC_CLASS
 #define DBGC_CLASS DBGC_PASSDB
 
+static char *domain_info_keystr(const char *domain);
+
 static char *des_salt_key(const char *realm);
 
 /**
@@ -379,6 +387,12 @@ bool secrets_delete_machine_password_ex(const char *domain, const char *realm)
 	const char *tmpkey = NULL;
 	bool ok;
 
+	tmpkey = domain_info_keystr(domain);
+	ok = secrets_delete(tmpkey);
+	if (!ok) {
+		return false;
+	}
+
 	if (realm != NULL) {
 		tmpkey = des_salt_key(domain);
 		ok = secrets_delete(tmpkey);
@@ -735,3 +749,1382 @@ char *secrets_fetch_machine_password(const char *domain,
 
 	return ret;
 }
+
+static char *domain_info_keystr(const char *domain)
+{
+	char *keystr;
+
+	keystr = talloc_asprintf_strupper_m(talloc_tos(), "%s/%s",
+					    SECRETS_MACHINE_DOMAIN_INFO,
+					    domain);
+	SMB_ASSERT(keystr != NULL);
+	return keystr;
+}
+
+/************************************************************************
+ Routine to get account password to trusted domain
+************************************************************************/
+
+static NTSTATUS secrets_fetch_domain_info1_by_key(const char *key,
+				TALLOC_CTX *mem_ctx,
+				struct secrets_domain_info1 **_info1)
+{
+	struct secrets_domain_infoB sdib = { .version = 0, };
+	enum ndr_err_code ndr_err;
+	/* unpacking structures */
+	DATA_BLOB blob;
+
+	/* fetching trusted domain password structure */
+	blob.data = (uint8_t *)secrets_fetch(key, &blob.length);
+	if (blob.data == NULL) {
+		DBG_NOTICE("secrets_fetch failed!\n");
+		return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+	}
+
+	/* unpack trusted domain password */
+	ndr_err = ndr_pull_struct_blob(&blob, mem_ctx, &sdib,
+			(ndr_pull_flags_fn_t)ndr_pull_secrets_domain_infoB);
+	SAFE_FREE(blob.data);
+	if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+		DBG_ERR("ndr_pull_struct_blob failed - %s!\n",
+			ndr_errstr(ndr_err));
+		return NT_STATUS_INTERNAL_DB_CORRUPTION;
+	}
+
+	if (sdib.version != SECRETS_DOMAIN_INFO_VERSION_1) {
+		DBG_ERR("sdib.version = %u\n", (unsigned)sdib.version);
+		return NT_STATUS_INTERNAL_DB_CORRUPTION;
+	}
+
+	*_info1 = sdib.info.info1;
+	return NT_STATUS_OK;;
+}
+
+static NTSTATUS secrets_fetch_domain_info(const char *domain,
+					  TALLOC_CTX *mem_ctx,
+					  struct secrets_domain_info1 **pinfo)
+{
+	char *key = domain_info_keystr(domain);
+	return secrets_fetch_domain_info1_by_key(key, mem_ctx, pinfo);
+}
+
+void secrets_debug_domain_info(int lvl, const struct secrets_domain_info1 *info1,
+			       const char *name)
+{
+	struct secrets_domain_infoB sdib = {
+		.version = SECRETS_DOMAIN_INFO_VERSION_1,
+	};
+
+	sdib.info.info1 = discard_const_p(struct secrets_domain_info1, info1);
+
+	ndr_print_debug((ndr_print_fn_t)ndr_print_secrets_domain_infoB,
+			name, &sdib);
+}
+
+char *secrets_domain_info_string(TALLOC_CTX *mem_ctx, const struct secrets_domain_info1 *info1,
+				 const char *name, bool include_secrets)
+{
+	TALLOC_CTX *frame = talloc_stackframe();
+	struct secrets_domain_infoB sdib = {
+		.version = SECRETS_DOMAIN_INFO_VERSION_1,
+	};
+	struct ndr_print *ndr = NULL;
+	char *ret = NULL;
+
+	sdib.info.info1 = discard_const_p(struct secrets_domain_info1, info1);
+
+	ndr = talloc_zero(frame, struct ndr_print);
+	if (ndr == NULL) {
+		TALLOC_FREE(frame);
+		return NULL;
+	}
+	ndr->private_data = talloc_strdup(ndr, "");
+	if (ndr->private_data == NULL) {
+		TALLOC_FREE(frame);
+		return NULL;
+	}
+	ndr->print = ndr_print_string_helper;
+	ndr->depth = 1;
+	ndr->print_secrets = include_secrets;
+
+	ndr_print_secrets_domain_infoB(ndr, name, &sdib);
+	ret = talloc_steal(mem_ctx, (char *)ndr->private_data);
+	TALLOC_FREE(frame);
+	return ret;
+}
+
+static NTSTATUS secrets_store_domain_info1_by_key(const char *key,
+					const struct secrets_domain_info1 *info1)
+{
+	struct secrets_domain_infoB sdib = {
+		.version = SECRETS_DOMAIN_INFO_VERSION_1,
+	};
+	/* packing structures */
+	DATA_BLOB blob;
+	enum ndr_err_code ndr_err;
+	bool ok;
+
+	sdib.info.info1 = discard_const_p(struct secrets_domain_info1, info1);
+
+	ndr_err = ndr_push_struct_blob(&blob, talloc_tos(), &sdib,
+			(ndr_push_flags_fn_t)ndr_push_secrets_domain_infoB);
+	if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+		return ndr_map_error2ntstatus(ndr_err);
+	}
+
+	ok = secrets_store(key, blob.data, blob.length);
+	data_blob_clear_free(&blob);
+	if (!ok) {
+		return NT_STATUS_INTERNAL_DB_ERROR;
+	}
+
+	return NT_STATUS_OK;
+}
+
+static NTSTATUS secrets_store_domain_info(const struct secrets_domain_info1 *info)
+{
+	TALLOC_CTX *frame = talloc_stackframe();
+	const char *domain = info->domain_info.name.string;
+	const char *realm = info->domain_info.dns_domain.string;
+	char *key = domain_info_keystr(domain);
+	struct db_context *db = NULL;
+	struct timeval last_change_tv;
+	const DATA_BLOB *cleartext_blob = NULL;
+	DATA_BLOB pw_blob = data_blob_null;
+	DATA_BLOB old_pw_blob = data_blob_null;
+	const char *pw = NULL;
+	const char *old_pw = NULL;
+	bool ok;
+	NTSTATUS status;
+	int ret;
+	int role = lp_server_role();
+
+	switch (info->secure_channel_type) {
+	case SEC_CHAN_WKSTA:
+	case SEC_CHAN_BDC:
+		if (role >= ROLE_ACTIVE_DIRECTORY_DC) {
+			DBG_ERR("AD_DC not supported for %s\n",
+				domain);
+			TALLOC_FREE(frame);
+			return NT_STATUS_INTERNAL_ERROR;
+		}
+
+		break;
+	default:
+		DBG_ERR("SEC_CHAN_* not supported for %s\n",
+			domain);
+		TALLOC_FREE(frame);
+		return NT_STATUS_INTERNAL_ERROR;
+	}
+
+	db = secrets_db_ctx();
+
+	ret = dbwrap_transaction_start(db);
+	if (ret != 0) {
+		DBG_ERR("dbwrap_transaction_start() failed for %s\n",
+			domain);
+		TALLOC_FREE(frame);
+		return NT_STATUS_INTERNAL_DB_ERROR;
+	}
+
+	ok = secrets_clear_domain_protection(domain);
+	if (!ok) {
+		DBG_ERR("secrets_clear_domain_protection(%s) failed\n",
+			domain);
+		dbwrap_transaction_cancel(db);
+		TALLOC_FREE(frame);
+		return NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
+	}
+
+	ok = secrets_delete_machine_password_ex(domain, realm);
+	if (!ok) {
+		DBG_ERR("secrets_delete_machine_password_ex(%s) failed\n",
+			domain);
+		dbwrap_transaction_cancel(db);
+		TALLOC_FREE(frame);
+		return NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
+	}
+
+	status = secrets_store_domain_info1_by_key(key, info);
+	if (!NT_STATUS_IS_OK(status)) {
+		DBG_ERR("secrets_store_domain_info1_by_key() failed "
+			"for %s - %s\n", domain, nt_errstr(status));
+		dbwrap_transaction_cancel(db);
+		TALLOC_FREE(frame);
+		return status;
+	}
+
+	/*
+	 * We use info->password_last_change instead
+	 * of info->password.change_time because
+	 * we may want to defer the next change approach
+	 * if the server rejected the change the last time,
+	 * e.g. due to RefusePasswordChange=1.
+	 */
+	nttime_to_timeval(&last_change_tv, info->password_last_change);
+
+	cleartext_blob = &info->password->cleartext_blob;
+	ok = convert_string_talloc(frame, CH_UTF16MUNGED, CH_UNIX,
+				   cleartext_blob->data,
+				   cleartext_blob->length,
+				   (void **)&pw_blob.data,
+				   &pw_blob.length);
+	if (!ok) {
+		status = NT_STATUS_UNMAPPABLE_CHARACTER;
+		if (errno == ENOMEM) {
+			status = NT_STATUS_NO_MEMORY;
+		}
+		DBG_ERR("convert_string_talloc(CH_UTF16MUNGED, CH_UNIX) "
+			"failed for pw of %s - %s\n",
+			domain, nt_errstr(status));
+		dbwrap_transaction_cancel(db);
+		TALLOC_FREE(frame);
+		return status;
+	}
+	pw = (const char *)pw_blob.data;
+	if (info->old_password != NULL) {
+		cleartext_blob = &info->old_password->cleartext_blob;
+		ok = convert_string_talloc(frame, CH_UTF16MUNGED, CH_UNIX,
+					   cleartext_blob->data,
+					   cleartext_blob->length,
+					   (void **)&old_pw_blob.data,
+					   &old_pw_blob.length);
+		if (!ok) {
+			status = NT_STATUS_UNMAPPABLE_CHARACTER;
+			if (errno == ENOMEM) {
+				status = NT_STATUS_NO_MEMORY;
+			}
+			DBG_ERR("convert_string_talloc(CH_UTF16MUNGED, CH_UNIX) "
+				"failed for old_pw of %s - %s\n",
+				domain, nt_errstr(status));
+			dbwrap_transaction_cancel(db);
+			data_blob_clear_free(&pw_blob);
+			TALLOC_FREE(frame);
+			return status;
+		}
+		old_pw = (const char *)old_pw_blob.data;
+	}
+
+	ok = secrets_store_machine_pw_sync(pw, old_pw,
+					   domain, realm,
+					   info->salt_principal,
+					   info->supported_enc_types,
+					   info->domain_info.sid,
+					   last_change_tv.tv_sec,
+					   info->secure_channel_type,
+					   false); /* delete_join */
+	data_blob_clear_free(&pw_blob);
+	data_blob_clear_free(&old_pw_blob);
+	if (!ok) {
+		DBG_ERR("secrets_store_machine_pw_sync(%s) failed\n",
+			domain);
+		dbwrap_transaction_cancel(db);
+		TALLOC_FREE(frame);
+		return NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
+	}
+
+	if (!GUID_all_zero(&info->domain_info.domain_guid)) {
+		ok = secrets_store_domain_guid(domain,
+				&info->domain_info.domain_guid);
+		if (!ok) {
+			DBG_ERR("secrets_store_domain_guid(%s) failed\n",
+				domain);
+			dbwrap_transaction_cancel(db);
+			TALLOC_FREE(frame);
+			return NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
+		}
+	}
+
+	ok = secrets_mark_domain_protected(domain);
+	if (!ok) {
+		DBG_ERR("secrets_mark_domain_protected(%s) failed\n",
+			domain);
+		dbwrap_transaction_cancel(db);
+		TALLOC_FREE(frame);
+		return NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
+	}
+
+	ret = dbwrap_transaction_commit(db);
+	if (ret != 0) {
+		DBG_ERR("dbwrap_transaction_commit() failed for %s\n",
+			domain);
+		TALLOC_FREE(frame);
+		return NT_STATUS_INTERNAL_DB_ERROR;
+	}
+
+	TALLOC_FREE(frame);
+	return NT_STATUS_OK;
+}
+
+static int secrets_domain_info_kerberos_keys(struct secrets_domain_info1_password *p,
+					     const char *salt_principal)
+{
+#ifdef HAVE_ADS
+	krb5_error_code krb5_ret;
+	krb5_context krb5_ctx = NULL;
+	DATA_BLOB cleartext_utf8_b = data_blob_null;
+	krb5_data cleartext_utf8;
+	krb5_data salt;
+	krb5_keyblock key;
+	DATA_BLOB aes_256_b = data_blob_null;
+	DATA_BLOB aes_128_b = data_blob_null;
+	DATA_BLOB des_md5_b = data_blob_null;
+	bool ok;
+#endif /* HAVE_ADS */
+	DATA_BLOB arc4_b = data_blob_null;
+	const uint16_t max_keys = 4;
+	struct secrets_domain_info1_kerberos_key *keys = NULL;
+	uint16_t idx = 0;
+	char *salt_data = NULL;
+
+	/*
+	 * We calculate:
+	 * ENCTYPE_AES256_CTS_HMAC_SHA1_96
+	 * ENCTYPE_AES128_CTS_HMAC_SHA1_96
+	 * ENCTYPE_ARCFOUR_HMAC
+	 * ENCTYPE_DES_CBC_MD5
+	 *
+	 * We don't include ENCTYPE_DES_CBC_CRC
+	 * as W2008R2 also doesn't store it anymore.
+	 *
+	 * Note we store all enctypes we support,
+	 * including the weak encryption types,
+	 * but that's no problem as we also
+	 * store the cleartext password anyway.
+	 *
+	 * Which values are then used to construct
+	 * a keytab is configured at runtime and the
+	 * configuration of msDS-SupportedEncryptionTypes.
+	 *
+	 * If we don't have kerberos support or no
+	 * salt, we only generate an entry for arcfour-hmac-md5.
+	 */
+	keys = talloc_zero_array(p,
+				 struct secrets_domain_info1_kerberos_key,
+				 max_keys);
+	if (keys == NULL) {
+		return ENOMEM;
+	}
+
+	arc4_b = data_blob_talloc(keys,
+				  p->nt_hash.hash,
+				  sizeof(p->nt_hash.hash));
+	if (arc4_b.data == NULL) {
+		DBG_ERR("data_blob_talloc failed for arcfour-hmac-md5.\n");
+		TALLOC_FREE(keys);
+		return ENOMEM;
+	}
+
+#ifdef HAVE_ADS
+	if (salt_principal == NULL) {
+		goto no_kerberos;
+	}
+
+	initialize_krb5_error_table();
+	krb5_ret = krb5_init_context(&krb5_ctx);
+	if (krb5_ret != 0) {
+		TALLOC_FREE(keys);
+		return krb5_ret;
+	}
+
+	krb5_ret = smb_krb5_salt_principal2data(krb5_ctx, salt_principal,
+						p, &salt_data);
+	if (krb5_ret != 0) {
+		DBG_ERR("smb_krb5_salt_principal2data(%s) failed: %s\n",
+			salt_principal,
+			smb_get_krb5_error_message(krb5_ctx, krb5_ret, keys));
+		krb5_free_context(krb5_ctx);
+		TALLOC_FREE(keys);
+		return krb5_ret;
+	}
+
+	salt.data = discard_const(salt_data);
+	salt.length = strlen(salt_data);
+
+	ok = convert_string_talloc(keys, CH_UTF16MUNGED, CH_UTF8,
+				   p->cleartext_blob.data,
+				   p->cleartext_blob.length,
+				   (void **)&cleartext_utf8_b.data,
+				   &cleartext_utf8_b.length);
+	if (!ok) {
+		if (errno != 0) {
+			krb5_ret = errno;
+		} else {
+			krb5_ret = EINVAL;
+		}
+		krb5_free_context(krb5_ctx);
+		TALLOC_FREE(keys);
+		return krb5_ret;
+	}
+	cleartext_utf8.data = (void *)cleartext_utf8_b.data;
+	cleartext_utf8.length = cleartext_utf8_b.length;
+
+	krb5_ret = smb_krb5_create_key_from_string(krb5_ctx,
+						   NULL,
+						   &salt,
+						   &cleartext_utf8,
+						   ENCTYPE_AES256_CTS_HMAC_SHA1_96,
+						   &key);
+	if (krb5_ret != 0) {
+		DBG_ERR("generation of a aes256-cts-hmac-sha1-96 key failed: %s\n",
+			smb_get_krb5_error_message(krb5_ctx, krb5_ret, keys));
+		krb5_free_context(krb5_ctx);
+		TALLOC_FREE(keys);
+		TALLOC_FREE(salt_data);
+		return krb5_ret;
+	}
+	aes_256_b = data_blob_talloc(keys,
+				     KRB5_KEY_DATA(&key),
+				     KRB5_KEY_LENGTH(&key));
+	krb5_free_keyblock_contents(krb5_ctx, &key);
+	if (aes_256_b.data == NULL) {
+		DBG_ERR("data_blob_talloc failed for aes-256.\n");
+		krb5_free_context(krb5_ctx);
+		TALLOC_FREE(keys);
+		TALLOC_FREE(salt_data);
+		return ENOMEM;
+	}
+
+	krb5_ret = smb_krb5_create_key_from_string(krb5_ctx,
+						   NULL,
+						   &salt,
+						   &cleartext_utf8,
+						   ENCTYPE_AES128_CTS_HMAC_SHA1_96,
+						   &key);
+	if (krb5_ret != 0) {
+		DBG_ERR("generation of a aes128-cts-hmac-sha1-96 key failed: %s\n",
+			smb_get_krb5_error_message(krb5_ctx, krb5_ret, keys));
+		krb5_free_context(krb5_ctx);
+		TALLOC_FREE(keys);
+		TALLOC_FREE(salt_data);
+		return krb5_ret;
+	}
+	aes_128_b = data_blob_talloc(keys,
+				     KRB5_KEY_DATA(&key),
+				     KRB5_KEY_LENGTH(&key));
+	krb5_free_keyblock_contents(krb5_ctx, &key);
+	if (aes_128_b.data == NULL) {
+		DBG_ERR("data_blob_talloc failed for aes-128.\n");
+		krb5_free_context(krb5_ctx);
+		TALLOC_FREE(keys);
+		TALLOC_FREE(salt_data);
+		return ENOMEM;
+	}
+
+	krb5_ret = smb_krb5_create_key_from_string(krb5_ctx,
+						   NULL,
+						   &salt,
+						   &cleartext_utf8,
+						   ENCTYPE_DES_CBC_MD5,
+						   &key);
+	if (krb5_ret != 0) {
+		DBG_ERR("generation of a des-cbc-md5 key failed: %s\n",
+			smb_get_krb5_error_message(krb5_ctx, krb5_ret, keys));
+		krb5_free_context(krb5_ctx);
+		TALLOC_FREE(keys);
+		TALLOC_FREE(salt_data);
+		return krb5_ret;
+	}
+	des_md5_b = data_blob_talloc(keys,
+				     KRB5_KEY_DATA(&key),
+				     KRB5_KEY_LENGTH(&key));
+	krb5_free_keyblock_contents(krb5_ctx, &key);
+	if (des_md5_b.data == NULL) {
+		DBG_ERR("data_blob_talloc failed for des-cbc-md5.\n");
+		krb5_free_context(krb5_ctx);
+		TALLOC_FREE(keys);
+		TALLOC_FREE(salt_data);
+		return ENOMEM;
+	}
+
+	krb5_free_context(krb5_ctx);
+no_kerberos:
+
+	if (aes_256_b.length != 0) {
+		keys[idx].keytype		= ENCTYPE_AES256_CTS_HMAC_SHA1_96;
+		keys[idx].iteration_count	= 4096;
+		keys[idx].value			= aes_256_b;
+		idx += 1;
+	}
+
+	if (aes_128_b.length != 0) {
+		keys[idx].keytype		= ENCTYPE_AES128_CTS_HMAC_SHA1_96;
+		keys[idx].iteration_count	= 4096;
+		keys[idx].value			= aes_128_b;
+		idx += 1;
+	}
+
+#endif /* HAVE_ADS */
+
+	keys[idx].keytype		= ENCTYPE_ARCFOUR_HMAC;
+	keys[idx].iteration_count	= 4096;
+	keys[idx].value			= arc4_b;
+	idx += 1;
+
+#ifdef HAVE_ADS
+	if (des_md5_b.length != 0) {
+		keys[idx].keytype		= ENCTYPE_DES_CBC_MD5;
+		keys[idx].iteration_count	= 4096;
+		keys[idx].value			= des_md5_b;
+		idx += 1;
+	}
+#endif /* HAVE_ADS */
+
+	p->salt_data = salt_data;
+	p->default_iteration_count = 4096;
+	p->num_keys = idx;
+	p->keys = keys;
+	return 0;
+}
+
+static NTSTATUS secrets_domain_info_password_create(TALLOC_CTX *mem_ctx,
+				const char *cleartext_unix,
+				const char *salt_principal,
+				NTTIME change_time,
+				const char *change_server,
+				struct secrets_domain_info1_password **_p)
+{
+	struct secrets_domain_info1_password *p = NULL;
+	bool ok;
+	size_t len;
+	int ret;
+
+	if (change_server == NULL) {
+		return NT_STATUS_INVALID_PARAMETER_MIX;
+	}
+
+	p = talloc_zero(mem_ctx, struct secrets_domain_info1_password);
+	if (p == NULL) {
+		return NT_STATUS_NO_MEMORY;
+	}
+	p->change_time = change_time;
+	p->change_server = talloc_strdup(p, change_server);
+	if (p->change_server == NULL) {
+		TALLOC_FREE(p);
+		return NT_STATUS_NO_MEMORY;
+	}
+	len = strlen(cleartext_unix);
+	ok = convert_string_talloc(p, CH_UNIX, CH_UTF16,
+				   cleartext_unix, len,
+				   (void **)&p->cleartext_blob.data,
+				   &p->cleartext_blob.length);
+	if (!ok) {
+		NTSTATUS status = NT_STATUS_UNMAPPABLE_CHARACTER;
+		if (errno == ENOMEM) {
+			status = NT_STATUS_NO_MEMORY;
+		}
+		TALLOC_FREE(p);
+		return status;
+	}
+	mdfour(p->nt_hash.hash,
+	       p->cleartext_blob.data,
+	       p->cleartext_blob.length);
+
+	ret = secrets_domain_info_kerberos_keys(p, salt_principal);
+	if (ret != 0) {
+		NTSTATUS status = krb5_to_nt_status(ret);
+		TALLOC_FREE(p);
+		return status;
+	}
+
+	*_p = p;
+	return NT_STATUS_OK;
+}
+
+NTSTATUS secrets_fetch_or_upgrade_domain_info(const char *domain,
+					TALLOC_CTX *mem_ctx,
+					struct secrets_domain_info1 **pinfo)
+{
+	TALLOC_CTX *frame = NULL;
+	struct secrets_domain_info1 *old = NULL;
+	struct secrets_domain_info1 *info = NULL;
+	const char *dns_domain = NULL;
+	const char *server = NULL;
+	struct db_context *db = NULL;
+	time_t last_set_time;
+	NTTIME last_set_nt;
+	enum netr_SchannelType channel;
+	char *pw = NULL;
+	char *old_pw = NULL;
+	struct dom_sid domain_sid;
+	struct GUID domain_guid;
+	bool ok;
+	NTSTATUS status;
+	int ret;
+
+	ok = strequal(domain, lp_workgroup());
+	if (ok) {
+		dns_domain = lp_dnsdomain();
+
+		if (dns_domain != NULL && dns_domain[0] == '\0') {
+			dns_domain = NULL;
+		}
+	}
+
+	last_set_time = secrets_fetch_pass_last_set_time(domain);
+	if (last_set_time == 0) {
+		return NT_STATUS_OK;
+	}
+	unix_to_nt_time(&last_set_nt, last_set_time);
+
+	frame = talloc_stackframe();
+
+	status = secrets_fetch_domain_info(domain, frame, &old);
+	if (NT_STATUS_IS_OK(status)) {
+		if (old->password_last_change >= last_set_nt) {
+			*pinfo = talloc_move(mem_ctx, &old);
+			TALLOC_FREE(frame);
+			return NT_STATUS_OK;
+		}
+		TALLOC_FREE(old);
+	}
+
+	info = talloc_zero(frame, struct secrets_domain_info1);
+	if (info == NULL) {
+		DBG_ERR("talloc_zero failed\n");
+		TALLOC_FREE(frame);
+		return NT_STATUS_NO_MEMORY;
+	}
+
+	db = secrets_db_ctx();
+
+	ret = dbwrap_transaction_start(db);
+	if (ret != 0) {
+		DBG_ERR("dbwrap_transaction_start() failed for %s\n",
+			domain);
+		TALLOC_FREE(frame);
+		return NT_STATUS_INTERNAL_DB_ERROR;
+	}
+
+	pw = secrets_fetch_machine_password(domain,
+					    &last_set_time,
+					    &channel);
+	if (pw == NULL) {
+		DBG_ERR("secrets_fetch_machine_password(%s) failed\n",
+			domain);
+		dbwrap_transaction_cancel(db);
+		TALLOC_FREE(frame);
+		return NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
+	}
+	unix_to_nt_time(&last_set_nt, last_set_time);
+
+	old_pw = secrets_fetch_prev_machine_password(domain);
+
+	ok = secrets_fetch_domain_sid(domain, &domain_sid);
+	if (!ok) {
+		DBG_ERR("secrets_fetch_domain_sid(%s) failed\n",
+			domain);
+		dbwrap_transaction_cancel(db);
+		TALLOC_FREE(frame);
+		return NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
+	}
+
+	ok = secrets_fetch_domain_guid(domain, &domain_guid);
+	if (!ok) {
+		domain_guid = GUID_zero();
+	}
+
+	info->computer_name = lp_netbios_name();
+	info->account_name = talloc_asprintf(frame, "%s$", info->computer_name);
+	if (info->account_name == NULL) {
+		DBG_ERR("talloc_asprintf(%s$) failed\n", info->computer_name);
+		dbwrap_transaction_cancel(db);
+		TALLOC_FREE(frame);
+		return NT_STATUS_NO_MEMORY;
+	}
+	info->secure_channel_type = channel;
+
+	info->domain_info.name.string = domain;
+	info->domain_info.dns_domain.string = dns_domain;
+	info->domain_info.dns_forest.string = dns_domain;
+	info->domain_info.domain_guid = domain_guid;
+	info->domain_info.sid = &domain_sid;
+
+	info->trust_flags = NETR_TRUST_FLAG_PRIMARY;
+	info->trust_flags |= NETR_TRUST_FLAG_OUTBOUND;
+
+	if (dns_domain != NULL) {
+		/*
+		 * We just assume all AD domains are
+		 * NETR_TRUST_FLAG_NATIVE these days.
+		 *
+		 * This isn't used anyway for now.
+		 */
+		info->trust_flags |= NETR_TRUST_FLAG_NATIVE;
+
+		info->trust_type = LSA_TRUST_TYPE_UPLEVEL;
+
+		server = info->domain_info.dns_domain.string;
+	} else {
+		info->trust_type = LSA_TRUST_TYPE_DOWNLEVEL;
+
+		server = talloc_asprintf(info,
+					 "%s#%02X",
+					 domain,
+					 NBT_NAME_PDC);
+		if (server == NULL) {
+			DBG_ERR("talloc_asprintf(%s#%02X) failed\n",
+				domain, NBT_NAME_PDC);
+			dbwrap_transaction_cancel(db);
+			TALLOC_FREE(frame);
+			return NT_STATUS_NO_MEMORY;
+		}
+	}
+	info->trust_attributes = LSA_TRUST_ATTRIBUTE_TREAT_AS_EXTERNAL;
+
+	info->join_time = 0;
+
+	/*
+	 * We don't have enough information about the configured
+	 * enctypes.
+	 */
+	info->supported_enc_types = 0;
+	info->salt_principal = NULL;
+	if (info->trust_type == LSA_TRUST_TYPE_UPLEVEL) {
+		char *p = NULL;
+
+		p = kerberos_secrets_fetch_salt_princ();
+		if (p == NULL) {
+			dbwrap_transaction_cancel(db);
+			TALLOC_FREE(frame);
+			return NT_STATUS_INTERNAL_ERROR;
+		}
+		info->salt_principal = talloc_strdup(info, p);
+		SAFE_FREE(p);
+		if (info->salt_principal == NULL) {
+			dbwrap_transaction_cancel(db);
+			TALLOC_FREE(frame);
+			return NT_STATUS_NO_MEMORY;
+		}
+	}
+
+	info->password_last_change = last_set_nt;
+	info->password_changes = 1;
+	info->next_change = NULL;
+
+	status = secrets_domain_info_password_create(info,
+						     pw,
+						     info->salt_principal,
+						     last_set_nt, server,
+						     &info->password);
+	if (!NT_STATUS_IS_OK(status)) {
+		DBG_ERR("secrets_domain_info_password_create(pw) failed "
+			"for %s - %s\n", domain, nt_errstr(status));
+		dbwrap_transaction_cancel(db);
+		TALLOC_FREE(frame);
+		return status;
+	}
+
+	/*
+	 * After a join we don't have old passwords.
+	 */
+	if (old_pw != NULL) {
+		status = secrets_domain_info_password_create(info,
+							     old_pw,
+							     info->salt_principal,
+							     0, server,
+							     &info->old_password);
+		if (!NT_STATUS_IS_OK(status)) {
+			DBG_ERR("secrets_domain_info_password_create(old) failed "
+				"for %s - %s\n", domain, nt_errstr(status));
+			dbwrap_transaction_cancel(db);
+			TALLOC_FREE(frame);
+			return status;
+		}
+		info->password_changes += 1;
+	} else {
+		info->old_password = NULL;
+	}
+	info->older_password = NULL;
+
+	secrets_debug_domain_info(DBGLVL_INFO, info, "upgrade");
+
+	status = secrets_store_domain_info(info);
+	if (!NT_STATUS_IS_OK(status)) {
+		DBG_ERR("secrets_store_domain_info() failed "
+			"for %s - %s\n", domain, nt_errstr(status));
+		dbwrap_transaction_cancel(db);
+		TALLOC_FREE(frame);
+		return status;
+	}
+
+	/*
+	 * We now reparse it.
+	 */
+	status = secrets_fetch_domain_info(domain, frame, &info);
+	if (!NT_STATUS_IS_OK(status)) {
+		DBG_ERR("secrets_fetch_domain_info() failed "
+			"for %s - %s\n", domain, nt_errstr(status));
+		dbwrap_transaction_cancel(db);
+		TALLOC_FREE(frame);
+		return status;
+	}
+
+	ret = dbwrap_transaction_commit(db);
+	if (ret != 0) {
+		DBG_ERR("dbwrap_transaction_commit() failed for %s\n",
+			domain);
+		dbwrap_transaction_cancel(db);
+		TALLOC_FREE(frame);
+		return NT_STATUS_INTERNAL_DB_ERROR;
+	}
+
+	*pinfo = talloc_move(mem_ctx, &info);
+	TALLOC_FREE(frame);
+	return NT_STATUS_OK;
+}
+
+NTSTATUS secrets_store_JoinCtx(const struct libnet_JoinCtx *r)
+{
+	TALLOC_CTX *frame = talloc_stackframe();
+	struct secrets_domain_info1 *old = NULL;
+	struct secrets_domain_info1 *info = NULL;
+	struct db_context *db = NULL;
+	struct timeval tv = timeval_current();
+	NTTIME now = timeval_to_nttime(&tv);
+	const char *domain = r->out.netbios_domain_name;
+	NTSTATUS status;
+	int ret;
+
+	info = talloc_zero(frame, struct secrets_domain_info1);
+	if (info == NULL) {
+		DBG_ERR("talloc_zero failed\n");
+		TALLOC_FREE(frame);
+		return NT_STATUS_NO_MEMORY;
+	}
+
+	info->computer_name = r->in.machine_name;
+	info->account_name = r->out.account_name;
+	info->secure_channel_type = r->in.secure_channel_type;
+
+	info->domain_info.name.string =
+		r->out.netbios_domain_name;
+	info->domain_info.dns_domain.string =
+		r->out.dns_domain_name;
+	info->domain_info.dns_forest.string =
+		r->out.forest_name;
+	info->domain_info.domain_guid = r->out.domain_guid;
+	info->domain_info.sid = r->out.domain_sid;
+
+	info->trust_flags = NETR_TRUST_FLAG_PRIMARY;
+	info->trust_flags |= NETR_TRUST_FLAG_OUTBOUND;
+	if (r->out.domain_is_ad) {
+		/*
+		 * We just assume all AD domains are
+		 * NETR_TRUST_FLAG_NATIVE these days.
+		 *
+		 * This isn't used anyway for now.
+		 */
+		info->trust_flags |= NETR_TRUST_FLAG_NATIVE;
+
+		info->trust_type = LSA_TRUST_TYPE_UPLEVEL;
+	} else {
+		info->trust_type = LSA_TRUST_TYPE_DOWNLEVEL;
+	}
+	info->trust_attributes = LSA_TRUST_ATTRIBUTE_TREAT_AS_EXTERNAL;
+
+	info->join_time = now;
+
+	info->supported_enc_types = r->out.set_encryption_types;
+	info->salt_principal = r->out.krb5_salt;
+
+	if (info->salt_principal == NULL && r->out.domain_is_ad) {
+		char *p = NULL;
+
+		ret = smb_krb5_salt_principal(info->domain_info.dns_domain.string,
+					      info->account_name,
+					      NULL /* userPrincipalName */,
+					      true /* is_computer */,
+					      info, &p);
+		if (ret != 0) {
+			status = krb5_to_nt_status(ret);
+			DBG_ERR("smb_krb5_salt_principal() failed "
+				"for %s - %s\n", domain, nt_errstr(status));
+			TALLOC_FREE(frame);
+			return status;
+		}
+		info->salt_principal = p;
+	}
+
+	info->password_last_change = now;
+	info->password_changes = 1;
+	info->next_change = NULL;
+
+	status = secrets_domain_info_password_create(info,
+						     r->in.machine_password,
+						     info->salt_principal,
+						     now, r->in.dc_name,
+						     &info->password);
+	if (!NT_STATUS_IS_OK(status)) {
+		DBG_ERR("secrets_domain_info_password_create(pw) failed "
+			"for %s - %s\n", domain, nt_errstr(status));
+		TALLOC_FREE(frame);
+		return status;
+	}
+
+	db = secrets_db_ctx();
+
+	ret = dbwrap_transaction_start(db);
+	if (ret != 0) {
+		DBG_ERR("dbwrap_transaction_start() failed for %s\n",
+			domain);
+		TALLOC_FREE(frame);
+		return NT_STATUS_INTERNAL_DB_ERROR;
+	}
+
+	status = secrets_fetch_or_upgrade_domain_info(domain, frame, &old);
+	if (NT_STATUS_EQUAL(status, NT_STATUS_CANT_ACCESS_DOMAIN_INFO)) {
+		DBG_DEBUG("no old join for domain(%s) available\n",
+			  domain);
+		old = NULL;
+	} else if (!NT_STATUS_IS_OK(status)) {
+		DBG_ERR("secrets_fetch_or_upgrade_domain_info(%s) failed\n",
+			domain);
+		dbwrap_transaction_cancel(db);
+		TALLOC_FREE(frame);
+		return status;
+	}
+
+	/*
+	 * We reuse values from an old join, so that
+	 * we still accept already granted kerberos tickets.
+	 */
+	if (old != NULL) {
+		info->old_password = old->password;
+		info->older_password = old->old_password;
+	}
+
+	secrets_debug_domain_info(DBGLVL_INFO, info, "join");
+
+	status = secrets_store_domain_info(info);
+	if (!NT_STATUS_IS_OK(status)) {
+		DBG_ERR("secrets_store_domain_info() failed "
+			"for %s - %s\n", domain, nt_errstr(status));
+		dbwrap_transaction_cancel(db);
+		TALLOC_FREE(frame);
+		return status;
+	}
+
+	ret = dbwrap_transaction_commit(db);
+	if (ret != 0) {
+		DBG_ERR("dbwrap_transaction_commit() failed for %s\n",
+			domain);
+		TALLOC_FREE(frame);
+		return NT_STATUS_INTERNAL_DB_ERROR;
+	}
+
+	TALLOC_FREE(frame);
+	return NT_STATUS_OK;
+}
+
+NTSTATUS secrets_prepare_password_change(const char *domain, const char *dcname,
+					 const char *cleartext_unix,
+					 TALLOC_CTX *mem_ctx,
+					 struct secrets_domain_info1 **pinfo,
+					 struct secrets_domain_info1_change **pprev)
+{
+	TALLOC_CTX *frame = talloc_stackframe();
+	struct db_context *db = NULL;
+	struct secrets_domain_info1 *info = NULL;
+	struct secrets_domain_info1_change *prev = NULL;
+	struct secrets_domain_info1_change *next = NULL;
+	struct timeval tv = timeval_current();
+	NTTIME now = timeval_to_nttime(&tv);
+	NTSTATUS status;
+	int ret;
+
+	db = secrets_db_ctx();
+
+	ret = dbwrap_transaction_start(db);
+	if (ret != 0) {
+		DBG_ERR("dbwrap_transaction_start() failed for %s\n",
+			domain);
+		TALLOC_FREE(frame);
+		return NT_STATUS_INTERNAL_DB_ERROR;
+	}
+
+	status = secrets_fetch_or_upgrade_domain_info(domain, frame, &info);
+	if (!NT_STATUS_IS_OK(status)) {
+		DBG_ERR("secrets_fetch_or_upgrade_domain_info(%s) failed\n",
+			domain);
+		dbwrap_transaction_cancel(db);
+		TALLOC_FREE(frame);
+		return status;
+	}
+
+	prev = info->next_change;
+	info->next_change = NULL;
+
+	next = talloc_zero(frame, struct secrets_domain_info1_change);
+	if (next == NULL) {
+		DBG_ERR("talloc_zero failed\n");
+		TALLOC_FREE(frame);
+		return NT_STATUS_NO_MEMORY;
+	}
+
+	if (prev != NULL) {
+		*next = *prev;
+	} else {
+		status = secrets_domain_info_password_create(next,
+							     cleartext_unix,
+							     info->salt_principal,
+							     now, dcname,
+							     &next->password);
+		if (!NT_STATUS_IS_OK(status)) {
+			DBG_ERR("secrets_domain_info_password_create(next) failed "
+				"for %s - %s\n", domain, nt_errstr(status));
+			dbwrap_transaction_cancel(db);
+			TALLOC_FREE(frame);
+			return status;
+		}
+	}
+
+	next->local_status = NT_STATUS_OK;
+	next->remote_status = NT_STATUS_NOT_COMMITTED;
+	next->change_time = now;
+	next->change_server = dcname;
+
+	info->next_change = next;
+
+	secrets_debug_domain_info(DBGLVL_INFO, info, "prepare_change");
+
+	status = secrets_store_domain_info(info);
+	if (!NT_STATUS_IS_OK(status)) {
+		DBG_ERR("secrets_store_domain_info() failed "
+			"for %s - %s\n", domain, nt_errstr(status));
+		dbwrap_transaction_cancel(db);
+		TALLOC_FREE(frame);
+		return status;
+	}
+
+	/*
+	 * We now reparse it.
+	 */
+	status = secrets_fetch_domain_info(domain, frame, &info);
+	if (!NT_STATUS_IS_OK(status)) {
+		DBG_ERR("secrets_fetch_domain_info(%s) failed\n", domain);
+		dbwrap_transaction_cancel(db);
+		TALLOC_FREE(frame);
+		return status;
+	}
+
+	ret = dbwrap_transaction_commit(db);
+	if (ret != 0) {
+		DBG_ERR("dbwrap_transaction_commit() failed for %s\n",
+			domain);
+		TALLOC_FREE(frame);
+		return NT_STATUS_INTERNAL_DB_ERROR;
+	}
+
+	*pinfo = talloc_move(mem_ctx, &info);
+	if (prev != NULL) {
+		*pprev = talloc_move(mem_ctx, &prev);
+	} else {
+		*pprev = NULL;
+	}
+
+	TALLOC_FREE(frame);
+	return NT_STATUS_OK;
+}
+
+static NTSTATUS secrets_check_password_change(const struct secrets_domain_info1 *cookie,
+					      TALLOC_CTX *mem_ctx,
+					      struct secrets_domain_info1 **pstored)
+{
+	const char *domain = cookie->domain_info.name.string;
+	struct secrets_domain_info1 *stored = NULL;
+	struct secrets_domain_info1_change *sn = NULL;
+	struct secrets_domain_info1_change *cn = NULL;
+	NTSTATUS status;
+	int cmp;
+
+	if (cookie->next_change == NULL) {
+		DBG_ERR("cookie->next_change == NULL for %s.\n", domain);
+		return NT_STATUS_INTERNAL_ERROR;
+	}
+
+	if (cookie->next_change->password == NULL) {
+		DBG_ERR("cookie->next_change->password == NULL for %s.\n", domain);
+		return NT_STATUS_INTERNAL_ERROR;
+	}
+
+	if (cookie->password == NULL) {
+		DBG_ERR("cookie->password == NULL for %s.\n", domain);
+		return NT_STATUS_INTERNAL_ERROR;
+	}
+
+	/*
+	 * Here we check that the given strucure still contains the
+	 * same secrets_domain_info1_change as currently stored.
+	 *
+	 * There's always a gab between secrets_prepare_password_change()
+	 * and the callers of secrets_check_password_change().
+	 */
+
+	status = secrets_fetch_domain_info(domain, mem_ctx, &stored);
+	if (!NT_STATUS_IS_OK(status)) {
+		DBG_ERR("secrets_fetch_domain_info(%s) failed\n", domain);
+		return status;
+	}
+
+	if (stored->next_change == NULL) {
+		/*
+		 * We hit a race..., the administrator
+		 * rejoined or something similar happened.
+		 */
+		DBG_ERR("stored->next_change == NULL for %s.\n", domain);
+		TALLOC_FREE(stored);
+		return NT_STATUS_NETWORK_CREDENTIAL_CONFLICT;
+	}
+
+	if (stored->password_last_change != cookie->password_last_change) {
+		struct timeval store_tv;
+		struct timeval_buf store_buf;
+		struct timeval cookie_tv;
+		struct timeval_buf cookie_buf;
+
+		nttime_to_timeval(&store_tv, stored->password_last_change);
+		nttime_to_timeval(&cookie_tv, cookie->password_last_change);
+
+		DBG_ERR("password_last_change differs %s != %s for %s.\n",
+			timeval_str_buf(&store_tv, false, false, &store_buf),
+			timeval_str_buf(&cookie_tv, false, false, &cookie_buf),
+			domain);
+		TALLOC_FREE(stored);
+		return NT_STATUS_NETWORK_CREDENTIAL_CONFLICT;
+	}
+
+	sn = stored->next_change;
+	cn = cookie->next_change;
+
+	if (sn->change_time != cn->change_time) {
+		struct timeval store_tv;
+		struct timeval_buf store_buf;
+		struct timeval cookie_tv;
+		struct timeval_buf cookie_buf;
+
+		nttime_to_timeval(&store_tv, sn->change_time);
+		nttime_to_timeval(&cookie_tv, cn->change_time);
+
+		DBG_ERR("next change_time differs %s != %s for %s.\n",
+			timeval_str_buf(&store_tv, false, false, &store_buf),
+			timeval_str_buf(&cookie_tv, false, false, &cookie_buf),
+			domain);
+		TALLOC_FREE(stored);
+		return NT_STATUS_NETWORK_CREDENTIAL_CONFLICT;
+	}
+
+	if (sn->password->change_time != cn->password->change_time) {
+		struct timeval store_tv;
+		struct timeval_buf store_buf;
+		struct timeval cookie_tv;
+		struct timeval_buf cookie_buf;
+
+		nttime_to_timeval(&store_tv, sn->password->change_time);
+		nttime_to_timeval(&cookie_tv, cn->password->change_time);
+
+		DBG_ERR("next password.change_time differs %s != %s for %s.\n",
+			timeval_str_buf(&store_tv, false, false, &store_buf),
+			timeval_str_buf(&cookie_tv, false, false, &cookie_buf),
+			domain);
+		TALLOC_FREE(stored);
+		return NT_STATUS_NETWORK_CREDENTIAL_CONFLICT;
+	}
+
+	cmp = memcmp(sn->password->nt_hash.hash,
+		     cn->password->nt_hash.hash,
+		     16);
+	if (cmp != 0) {
+		DBG_ERR("next password.nt_hash differs for %s.\n",
+			domain);
+		TALLOC_FREE(stored);
+		return NT_STATUS_NETWORK_CREDENTIAL_CONFLICT;
+	}
+
+	cmp = memcmp(stored->password->nt_hash.hash,
+		     cookie->password->nt_hash.hash,
+		     16);
+	if (cmp != 0) {
+		DBG_ERR("password.nt_hash differs for %s.\n",
+			domain);
+		TALLOC_FREE(stored);
+		return NT_STATUS_NETWORK_CREDENTIAL_CONFLICT;
+	}
+
+	*pstored = stored;
+	return NT_STATUS_OK;
+}
+
+static NTSTATUS secrets_abort_password_change(const char *change_server,
+				NTSTATUS local_status,
+				NTSTATUS remote_status,
+				const struct secrets_domain_info1 *cookie,
+				bool defer)
+{
+	const char *domain = cookie->domain_info.name.string;
+	TALLOC_CTX *frame = talloc_stackframe();
+	struct db_context *db = NULL;
+	struct secrets_domain_info1 *info = NULL;
+	const char *reason = defer ? "defer_change" : "failed_change";
+	struct timeval tv = timeval_current();
+	NTTIME now = timeval_to_nttime(&tv);
+	NTSTATUS status;
+	int ret;
+
+	db = secrets_db_ctx();
+
+	ret = dbwrap_transaction_start(db);
+	if (ret != 0) {
+		DBG_ERR("dbwrap_transaction_start() failed for %s\n",
+			domain);
+		TALLOC_FREE(frame);
+		return NT_STATUS_INTERNAL_DB_ERROR;
+	}
+
+	/*
+	 * secrets_check_password_change()
+	 * checks that cookie->next_change
+	 * is valid and the same as store
+	 * in the database.
+	 */
+	status = secrets_check_password_change(cookie, frame, &info);
+	if (!NT_STATUS_IS_OK(status)) {
+		DBG_ERR("secrets_check_password_change(%s) failed\n", domain);
+		dbwrap_transaction_cancel(db);
+		TALLOC_FREE(frame);
+		return status;
+	}
+
+	/*
+	 * Remember the last server and error.
+	 */
+	info->next_change->change_server = change_server;
+	info->next_change->change_time = now;
+	info->next_change->local_status = local_status;
+	info->next_change->remote_status = remote_status;
+
+	/*
+	 * Make sure the next automatic change is deferred.
+	 */
+	if (defer) {
+		info->password_last_change = now;
+	}
+
+	secrets_debug_domain_info(DBGLVL_WARNING, info, reason);
+
+	status = secrets_store_domain_info(info);
+	if (!NT_STATUS_IS_OK(status)) {
+		DBG_ERR("secrets_store_domain_info() failed "
+			"for %s - %s\n", domain, nt_errstr(status));
+		dbwrap_transaction_cancel(db);
+		TALLOC_FREE(frame);
+		return status;
+	}
+
+	ret = dbwrap_transaction_commit(db);
+	if (ret != 0) {
+		DBG_ERR("dbwrap_transaction_commit() failed for %s\n",
+			domain);
+		TALLOC_FREE(frame);
+		return NT_STATUS_INTERNAL_DB_ERROR;
+	}
+
+	TALLOC_FREE(frame);
+	return NT_STATUS_OK;
+}
+
+NTSTATUS secrets_failed_password_change(const char *change_server,
+					NTSTATUS local_status,
+					NTSTATUS remote_status,
+					const struct secrets_domain_info1 *cookie)
+{
+	static const bool defer = false;
+	return secrets_abort_password_change(change_server,
+					     local_status,
+					     remote_status,
+					     cookie, defer);
+}
+
+NTSTATUS secrets_defer_password_change(const char *change_server,
+				       NTSTATUS local_status,
+				       NTSTATUS remote_status,
+				       const struct secrets_domain_info1 *cookie)
+{
+	static const bool defer = true;
+	return secrets_abort_password_change(change_server,
+					     local_status,
+					     remote_status,
+					     cookie, defer);
+}
+
+NTSTATUS secrets_finish_password_change(const char *change_server,
+					NTTIME change_time,
+					const struct secrets_domain_info1 *cookie)
+{
+	const char *domain = cookie->domain_info.name.string;
+	TALLOC_CTX *frame = talloc_stackframe();
+	struct db_context *db = NULL;
+	struct secrets_domain_info1 *info = NULL;
+	struct secrets_domain_info1_change *nc = NULL;
+	NTSTATUS status;
+	int ret;
+
+	db = secrets_db_ctx();
+
+	ret = dbwrap_transaction_start(db);
+	if (ret != 0) {
+		DBG_ERR("dbwrap_transaction_start() failed for %s\n",
+			domain);
+		TALLOC_FREE(frame);
+		return NT_STATUS_INTERNAL_DB_ERROR;
+	}
+
+	/*
+	 * secrets_check_password_change()
+	 * checks that cookie->next_change
+	 * is valid and the same as store
+	 * in the database.
+	 */
+	status = secrets_check_password_change(cookie, frame, &info);
+	if (!NT_STATUS_IS_OK(status)) {
+		DBG_ERR("secrets_check_password_change(%s) failed\n", domain);
+		dbwrap_transaction_cancel(db);
+		TALLOC_FREE(frame);
+		return status;
+	}
+
+	nc = info->next_change;
+
+	nc->password->change_server = change_server;
+	nc->password->change_time = change_time;
+
+	info->password_last_change = change_time;
+	info->password_changes += 1;
+	info->next_change = NULL;
+
+	info->older_password = info->old_password;
+	info->old_password = info->password;
+	info->password = nc->password;
+
+	secrets_debug_domain_info(DBGLVL_WARNING, info, "finish_change");
+
+	status = secrets_store_domain_info(info);
+	if (!NT_STATUS_IS_OK(status)) {
+		DBG_ERR("secrets_store_domain_info() failed "
+			"for %s - %s\n", domain, nt_errstr(status));
+		dbwrap_transaction_cancel(db);
+		TALLOC_FREE(frame);
+		return status;
+	}
+
+	ret = dbwrap_transaction_commit(db);
+	if (ret != 0) {
+		DBG_ERR("dbwrap_transaction_commit() failed for %s\n",
+			domain);
+		TALLOC_FREE(frame);
+		return NT_STATUS_INTERNAL_DB_ERROR;
+	}
+
+	TALLOC_FREE(frame);
+	return NT_STATUS_OK;
+}
-- 
1.9.1


From 7ec6d463fbe6dcba4d8aa80876fd73f7f551c282 Mon Sep 17 00:00:00 2001
From: Stefan Metzmacher <metze at samba.org>
Date: Wed, 24 May 2017 18:05:40 +0200
Subject: [PATCH 06/15] net: add "net primarytrust dumpinfo" command that dumps
 the details of the workstation trust

BUG: https://bugzilla.samba.org/show_bug.cgi?id=12782

Signed-off-by: Stefan Metzmacher <metze at samba.org>
---
 source3/utils/net.c | 85 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 85 insertions(+)

diff --git a/source3/utils/net.c b/source3/utils/net.c
index 97d582f..00deb31 100644
--- a/source3/utils/net.c
+++ b/source3/utils/net.c
@@ -91,6 +91,83 @@ static void set_line_buffering(FILE *f)
 	setvbuf(f, NULL, _IOLBF, 0);
 }
 
+static int net_primarytrust_dumpinfo(struct net_context *c, int argc,
+				     const char **argv)
+{
+	int role = lp_server_role();
+	const char *domain = lp_workgroup();
+	struct secrets_domain_info1 *info = NULL;
+	bool include_secrets = c->opt_force;
+	char *str = NULL;
+	NTSTATUS status;
+
+	if (role >= ROLE_ACTIVE_DIRECTORY_DC) {
+		d_printf(_("net primarytrust dumpinfo is only supported "
+			 "on a DOMAIN_MEMBER for now.\n"));
+		return 1;
+	}
+
+	if (c->opt_stdin) {
+		set_line_buffering(stdin);
+		set_line_buffering(stdout);
+		set_line_buffering(stderr);
+	}
+
+	status = secrets_fetch_or_upgrade_domain_info(domain,
+						      talloc_tos(),
+						      &info);
+	if (!NT_STATUS_IS_OK(status)) {
+		d_fprintf(stderr,
+			  _("Unable to fetch the information for domain[%s] "
+			  "in the secrets database.\n"),
+			  domain);
+		return 1;
+	}
+
+	str = secrets_domain_info_string(info, info, domain, include_secrets);
+	if (str == NULL) {
+		d_fprintf(stderr, "secrets_domain_info_string() failed.\n");
+		return 1;
+	}
+
+	d_printf("%s", str);
+	if (!c->opt_force) {
+		d_printf(_("The password values are only included using "
+			 "-f flag.\n"));
+	}
+
+	TALLOC_FREE(info);
+	return 0;
+}
+
+/**
+ * Entrypoint for 'net primarytrust' code.
+ *
+ * @param argc Standard argc.
+ * @param argv Standard argv without initial components.
+ *
+ * @return Integer status (0 means success).
+ */
+
+static int net_primarytrust(struct net_context *c, int argc, const char **argv)
+{
+	struct functable func[] = {
+		{
+			"dumpinfo",
+			net_primarytrust_dumpinfo,
+			NET_TRANSPORT_LOCAL,
+			N_("Dump the details of the workstation trust"),
+			N_("  net [options] primarytrust dumpinfo'\n"
+			   "    Dump the details of the workstation trust "
+			   "in secrets.tdb.\n"
+			   "    Requires the -f flag to include the password values.")
+		},
+		{NULL, NULL, 0, NULL, NULL}
+	};
+
+	return net_run_function(c, argc, argv, "net primarytrust", func);
+}
+
 static int net_changesecretpw(struct net_context *c, int argc,
 			      const char **argv)
 {
@@ -570,6 +647,14 @@ static struct functable net_func[] = {
 		N_("  Use 'net help password' to get more information about "
 		   "'net password' commands.")
 	},
+	{
+		"primarytrust",
+		net_primarytrust,
+		NET_TRANSPORT_RPC,
+		N_("Run functions related to the primary workstation trust."),
+		N_("  Use 'net help primarytrust' to get more extensive information "
+		   "about 'net primarytrust' commands.")
+	},
 	{	"changetrustpw",
 		net_changetrustpw,
 		NET_TRANSPORT_ADS | NET_TRANSPORT_RPC,
-- 
1.9.1


From 710fdd8286fd7e6813cb94d66dd47e17c07f27dd Mon Sep 17 00:00:00 2001
From: Stefan Metzmacher <metze at samba.org>
Date: Wed, 17 May 2017 10:29:59 +0200
Subject: [PATCH 07/15] s3:libnet: make use of secrets_store_JoinCtx()

BUG: https://bugzilla.samba.org/show_bug.cgi?id=12782

Signed-off-by: Stefan Metzmacher <metze at samba.org>
---
 source3/libnet/libnet_join.c | 26 +++++---------------------
 1 file changed, 5 insertions(+), 21 deletions(-)

diff --git a/source3/libnet/libnet_join.c b/source3/libnet/libnet_join.c
index 99bba2a..591c177 100644
--- a/source3/libnet/libnet_join.c
+++ b/source3/libnet/libnet_join.c
@@ -987,31 +987,15 @@ static ADS_STATUS libnet_join_post_processing_ads_sync(TALLOC_CTX *mem_ctx,
 static bool libnet_join_joindomain_store_secrets(TALLOC_CTX *mem_ctx,
 						 struct libnet_JoinCtx *r)
 {
-	if (!secrets_store_domain_sid(r->out.netbios_domain_name,
-				      r->out.domain_sid))
-	{
-		DEBUG(1,("Failed to save domain sid\n"));
-		return false;
-	}
+	NTSTATUS status;
 
-	if (!secrets_store_machine_password(r->in.machine_password,
-					    r->out.netbios_domain_name,
-					    r->in.secure_channel_type))
-	{
-		DEBUG(1,("Failed to save machine password\n"));
+	status = secrets_store_JoinCtx(r);
+	if (!NT_STATUS_IS_OK(status)) {
+		DBG_ERR("secrets_store_JoinCtx() failed %s\n",
+			nt_errstr(status));
 		return false;
 	}
 
-	if (r->out.krb5_salt != NULL) {
-		bool ok;
-
-		ok = kerberos_secrets_store_des_salt(r->out.krb5_salt);
-		if (!ok) {
-			DEBUG(1,("Failed to save krb5 salt\n"));
-			return false;
-		}
-	}
-
 	return true;
 }
 
-- 
1.9.1


From 8e339c4e26ccdf462e041e576e4393924d47b2b1 Mon Sep 17 00:00:00 2001
From: Stefan Metzmacher <metze at samba.org>
Date: Mon, 22 May 2017 20:47:17 +0200
Subject: [PATCH 08/15] s3:trusts_util: make use the workstation password
 change more robust

We use secrets_{prepare,failed,defer,finish}_password_change() to make
the process more robust.

Even if we just just verified the current password with the DC
it can still happen that the remote password change will fail.

If a server has the RefusePasswordChange=1 under
HKLM\SYSTEM\CurrentControlSet\Services\Netlogon\Parameters,
it will reject NetrServerPasswordSet2() with NT_STATUS_WRONG_PASSWORD.

This results in a successful local change, but a failing remote change,
which means the domain membership is broken (as we don't fallback to
the previous password for ntlmssp nor kerberos yet).

An (at least Samba) RODC will also reject a password change,
see https://bugzilla.samba.org/show_bug.cgi?id=12773.

Even with this change we still have open problems, e.g. if the password was
changed, but we didn't get the servers response. In order to fix that we need
to use only netlogon and lsa over unprotected transports, just using schannel
authentication (which supports the fallback to the old password).

BUG: https://bugzilla.samba.org/show_bug.cgi?id=12782

Signed-off-by: Stefan Metzmacher <metze at samba.org>
---
 source3/libsmb/trusts_util.c | 204 +++++++++++++++++++++++++++++++++++++++----
 1 file changed, 187 insertions(+), 17 deletions(-)

diff --git a/source3/libsmb/trusts_util.c b/source3/libsmb/trusts_util.c
index ff7f256..d720b1e 100644
--- a/source3/libsmb/trusts_util.c
+++ b/source3/libsmb/trusts_util.c
@@ -24,6 +24,7 @@
 #include "rpc_client/cli_netlogon.h"
 #include "rpc_client/cli_pipe.h"
 #include "../librpc/gen_ndr/ndr_netlogon.h"
+#include "librpc/gen_ndr/secrets.h"
 #include "secrets.h"
 #include "passdb.h"
 #include "libsmb/libsmb.h"
@@ -114,11 +115,13 @@ NTSTATUS trust_pw_change(struct netlogon_creds_cli_context *context,
 	const char *context_name = NULL;
 	struct trust_pw_change_state *state;
 	struct cli_credentials *creds = NULL;
+	struct secrets_domain_info1 *info = NULL;
+	struct secrets_domain_info1_change *prev = NULL;
 	const struct samr_Password *current_nt_hash = NULL;
 	const struct samr_Password *previous_nt_hash = NULL;
 	uint8_t num_nt_hashes = 0;
 	uint8_t idx = 0;
-	const struct samr_Password *nt_hashes[1+1] = { NULL, };
+	const struct samr_Password *nt_hashes[1+3] = { NULL, };
 	uint8_t idx_nt_hashes = 0;
 	uint8_t idx_current = UINT8_MAX;
 	enum netr_SchannelType sec_channel_type = SEC_CHAN_NULL;
@@ -270,10 +273,75 @@ NTSTATUS trust_pw_change(struct netlogon_creds_cli_context *context,
 		return status;
 	}
 
-	idx_current = idx;
-	nt_hashes[idx++] = current_nt_hash;
-	if (previous_nt_hash != NULL) {
-		nt_hashes[idx++] = previous_nt_hash;
+	switch (sec_channel_type) {
+
+	case SEC_CHAN_WKSTA:
+	case SEC_CHAN_BDC:
+		status = secrets_prepare_password_change(domain, dcname,
+							 new_trust_pw_str,
+							 frame, &info, &prev);
+		if (!NT_STATUS_IS_OK(status)) {
+			DEBUG(0, ("secrets_prepare_password_change() failed for domain %s!\n",
+				  domain));
+			TALLOC_FREE(frame);
+			return NT_STATUS_INTERNAL_DB_CORRUPTION;
+		}
+		TALLOC_FREE(new_trust_pw_str);
+
+		if (prev != NULL) {
+			/*
+			 * We had a failure before, we may already
+			 * changed the password.
+			 */
+			nt_hashes[idx++] = &prev->password->nt_hash;
+
+			DEBUG(0,("%s : %s(%s): A password change was already "
+				 "started against '%s' at %s. Try to recover...\n",
+				 current_timestring(talloc_tos(), false),
+				 __func__, domain,
+				 prev->password->change_server,
+				 nt_time_string(talloc_tos(),
+				 prev->password->change_time)));
+			DEBUG(0,("%s : %s(%s): Last failure local[%s] remote[%s] "
+				 "against '%s' at %s.\n",
+				 current_timestring(talloc_tos(), false),
+				 __func__, domain,
+				 nt_errstr(prev->local_status),
+				 nt_errstr(prev->remote_status),
+				 prev->change_server,
+				 nt_time_string(talloc_tos(),
+				 prev->change_time)));
+		}
+
+		idx_current = idx;
+		nt_hashes[idx++] = &info->password->nt_hash;
+		if (info->old_password != NULL) {
+			nt_hashes[idx++] = &info->old_password->nt_hash;
+		}
+		if (info->older_password != NULL) {
+			nt_hashes[idx++] = &info->older_password->nt_hash;
+		}
+
+		/*
+		 * We use the password that's already persitent in
+		 * our database in order to handle failures.
+		 */
+		data_blob_clear_free(&new_trust_pw_blob);
+		new_trust_pw_blob = info->next_change->password->cleartext_blob;
+		break;
+
+	case SEC_CHAN_DNS_DOMAIN:
+	case SEC_CHAN_DOMAIN:
+		idx_current = idx;
+		nt_hashes[idx++] = current_nt_hash;
+		if (previous_nt_hash != NULL) {
+			nt_hashes[idx++] = previous_nt_hash;
+		}
+		break;
+
+	default:
+		smb_panic("Unsupported secure channel type");
+		break;
 	}
 	num_nt_hashes = idx;
 
@@ -301,11 +369,50 @@ NTSTATUS trust_pw_change(struct netlogon_creds_cli_context *context,
 		return status;
 	}
 
+	if (prev != NULL && idx_nt_hashes == 0) {
+		DEBUG(0,("%s : %s(%s): Verified new password remotely "
+			 "without changing %s\n",
+			 current_timestring(talloc_tos(), false),
+			 __func__, domain, context_name));
+
+		status = secrets_finish_password_change(prev->password->change_server,
+							prev->password->change_time,
+							info);
+		if (!NT_STATUS_IS_OK(status)) {
+			DEBUG(0, ("secrets_prepare_password_change() failed for domain %s!\n",
+				  domain));
+			TALLOC_FREE(frame);
+			return NT_STATUS_INTERNAL_DB_CORRUPTION;
+		}
+
+		DEBUG(0,("%s : %s(%s): Recovered previous password change.\n",
+			 current_timestring(talloc_tos(), false),
+			 __func__, domain));
+		TALLOC_FREE(frame);
+		return NT_STATUS_OK;
+	}
+
 	if (idx_nt_hashes != idx_current) {
 		DEBUG(0,("%s : %s(%s): Verified older password remotely "
 			 "skip changing %s\n",
 			 current_timestring(talloc_tos(), false),
 			 __func__, domain, context_name));
+
+		if (info == NULL) {
+			TALLOC_FREE(frame);
+			return NT_STATUS_TRUSTED_RELATIONSHIP_FAILURE;
+		}
+
+		status = secrets_defer_password_change(dcname,
+					NT_STATUS_TRUSTED_RELATIONSHIP_FAILURE,
+					NT_STATUS_NOT_COMMITTED,
+					info);
+		if (!NT_STATUS_IS_OK(status)) {
+			DEBUG(0, ("secrets_defer_password_change() failed for domain %s!\n",
+				  domain));
+			TALLOC_FREE(frame);
+			return NT_STATUS_INTERNAL_DB_CORRUPTION;
+		}
 		TALLOC_FREE(frame);
 		return NT_STATUS_TRUSTED_RELATIONSHIP_FAILURE;
 	}
@@ -323,16 +430,9 @@ NTSTATUS trust_pw_change(struct netlogon_creds_cli_context *context,
 
 	case SEC_CHAN_WKSTA:
 	case SEC_CHAN_BDC:
-		ok = secrets_store_machine_password(new_trust_pw_str,
-						    domain,
-						    sec_channel_type);
-		if (!ok) {
-			DEBUG(0, ("secrets_store_machine_password failed for domain %s!\n",
-				  domain));
-			TALLOC_FREE(frame);
-			return NT_STATUS_INTERNAL_DB_CORRUPTION;
-		}
-		TALLOC_FREE(new_trust_pw_str);
+		/*
+		 * we called secrets_prepare_password_change() above.
+		 */
 		break;
 
 	case SEC_CHAN_DNS_DOMAIN:
@@ -364,10 +464,48 @@ NTSTATUS trust_pw_change(struct netlogon_creds_cli_context *context,
 						      &new_trust_pw_blob,
 						      new_trust_version);
 	if (!NT_STATUS_IS_OK(status)) {
-		DEBUG(0,("%s : %s(%s) remote password change set with %s failed - %s\n",
+		NTSTATUS status2;
+		const char *fn = NULL;
+
+		ok = dcerpc_binding_handle_is_connected(b);
+
+		DEBUG(0,("%s : %s(%s) remote password change set with %s failed "
+			 "- %s (%s)\n",
 			 current_timestring(talloc_tos(), false),
 			 __func__, domain, context_name,
-			 nt_errstr(status)));
+			 nt_errstr(status),
+			 ok ? "connected": "disconnected"));
+
+		if (!ok) {
+			/*
+			 * The connection is broken, we don't
+			 * know if the password was changed,
+			 * we hope to have more luck next time.
+			 */
+			status2 = secrets_failed_password_change(dcname,
+							NT_STATUS_NOT_COMMITTED,
+							status,
+							info);
+			fn = "secrets_failed_password_change";
+		} else {
+			/*
+			 * The server rejected the change, we don't
+			 * retry and defer the change to the next
+			 * "machine password timeout" interval.
+			 */
+			status2 = secrets_defer_password_change(dcname,
+							NT_STATUS_NOT_COMMITTED,
+							status,
+							info);
+			fn = "secrets_defer_password_change";
+		}
+		if (!NT_STATUS_IS_OK(status2)) {
+			DEBUG(0, ("%s() failed for domain %s!\n",
+				  fn, domain));
+			TALLOC_FREE(frame);
+			return NT_STATUS_INTERNAL_DB_CORRUPTION;
+		}
+
 		TALLOC_FREE(frame);
 		return status;
 	}
@@ -376,6 +514,38 @@ NTSTATUS trust_pw_change(struct netlogon_creds_cli_context *context,
 		 current_timestring(talloc_tos(), false),
 		 __func__, domain, context_name));
 
+	switch (sec_channel_type) {
+
+	case SEC_CHAN_WKSTA:
+	case SEC_CHAN_BDC:
+		status = secrets_finish_password_change(
+					info->next_change->change_server,
+					info->next_change->change_time,
+					info);
+		if (!NT_STATUS_IS_OK(status)) {
+			DEBUG(0, ("secrets_finish_password_change() failed for domain %s!\n",
+				  domain));
+			TALLOC_FREE(frame);
+			return NT_STATUS_INTERNAL_DB_CORRUPTION;
+		}
+
+		DEBUG(0,("%s : %s(%s): Finished password change.\n",
+			 current_timestring(talloc_tos(), false),
+			 __func__, domain));
+		break;
+
+	case SEC_CHAN_DNS_DOMAIN:
+	case SEC_CHAN_DOMAIN:
+		/*
+		 * we used pdb_set_trusteddom_pw().
+		 */
+		break;
+
+	default:
+		smb_panic("Unsupported secure channel type");
+		break;
+	}
+
 	ok = cli_credentials_set_utf16_password(creds,
 						&new_trust_pw_blob,
 						CRED_SPECIFIED);
-- 
1.9.1


From e0b0156e280c1eb88e1578c3a79c62da6d5a467d Mon Sep 17 00:00:00 2001
From: Stefan Metzmacher <metze at samba.org>
Date: Tue, 23 May 2017 17:29:31 +0200
Subject: [PATCH 09/15] net: make use of secrets_*_password_change() for "net
 changesecretpw"

BUG: https://bugzilla.samba.org/show_bug.cgi?id=12782

Signed-off-by: Stefan Metzmacher <metze at samba.org>
---
 source3/utils/net.c | 51 ++++++++++++++++++++++++++++++++++++++++++++-------
 1 file changed, 44 insertions(+), 7 deletions(-)

diff --git a/source3/utils/net.c b/source3/utils/net.c
index 00deb31..bde2616 100644
--- a/source3/utils/net.c
+++ b/source3/utils/net.c
@@ -172,9 +172,23 @@ static int net_changesecretpw(struct net_context *c, int argc,
 			      const char **argv)
 {
         char *trust_pw;
-        enum netr_SchannelType sec_channel_type = SEC_CHAN_WKSTA;
+	int role = lp_server_role();
+
+	if (role != ROLE_DOMAIN_MEMBER) {
+		d_printf(_("Machine account password change only supported on a DOMAIN_MEMBER.\n"
+			   "Do NOT use this function unless you know what it does!\n"
+		           "This function will change the ADS Domain member "
+			   "machine account password in the secrets.tdb file!\n"));
+		return 1;
+	}
 
 	if(c->opt_force) {
+		struct secrets_domain_info1 *info = NULL;
+		struct secrets_domain_info1_change *prev = NULL;
+		NTSTATUS status;
+		struct timeval tv = timeval_current();
+		NTTIME now = timeval_to_nttime(&tv);
+
 		if (c->opt_stdin) {
 			set_line_buffering(stdin);
 			set_line_buffering(stdout);
@@ -188,14 +202,37 @@ static int net_changesecretpw(struct net_context *c, int argc,
 			    return 1;
 		}
 
-		if (!secrets_store_machine_password(trust_pw, lp_workgroup(), sec_channel_type)) {
-			    d_fprintf(stderr,
-				      _("Unable to write the machine account password in the secrets database"));
-			    return 1;
+		status = secrets_prepare_password_change(lp_workgroup(),
+							 "localhost",
+							 trust_pw,
+							 talloc_tos(),
+							 &info, &prev);
+		if (!NT_STATUS_IS_OK(status)) {
+			d_fprintf(stderr,
+			        _("Unable to write the machine account password in the secrets database"));
+			return 1;
 		}
-		else {
-		    d_printf(_("Modified trust account password in secrets database\n"));
+		if (prev != NULL) {
+			d_fprintf(stderr,
+			        _("Pending machine account password change found - aborting."));
+			status = secrets_failed_password_change("localhost",
+						NT_STATUS_REQUEST_NOT_ACCEPTED,
+						NT_STATUS_NOT_COMMITTED,
+						info);
+			if (!NT_STATUS_IS_OK(status)) {
+				d_fprintf(stderr,
+				        _("Failed to abort machine account password change"));
+			}
+			return 1;
 		}
+		status = secrets_finish_password_change("localhost", now, info);
+		if (!NT_STATUS_IS_OK(status)) {
+			d_fprintf(stderr,
+			        _("Unable to write the machine account password in the secrets database"));
+			return 1;
+		}
+
+		d_printf(_("Modified trust account password in secrets database\n"));
 	}
 	else {
 		d_printf(_("Machine account password change requires the -f flag.\n"
-- 
1.9.1


From ec2760734037b558f2ba43539108e4e4a4dcba23 Mon Sep 17 00:00:00 2001
From: Stefan Metzmacher <metze at samba.org>
Date: Tue, 23 May 2017 17:41:34 +0200
Subject: [PATCH 10/15] s3:libads: make use of secrets_*_password_change() in
 ads_change_trust_account_password()

BUG: https://bugzilla.samba.org/show_bug.cgi?id=12782

Signed-off-by: Stefan Metzmacher <metze at samba.org>
---
 source3/libads/util.c | 106 ++++++++++++++++++++++++++++++++++++++++++--------
 1 file changed, 90 insertions(+), 16 deletions(-)

diff --git a/source3/libads/util.c b/source3/libads/util.c
index b0754be..14dbf86 100644
--- a/source3/libads/util.c
+++ b/source3/libads/util.c
@@ -20,42 +20,116 @@
 #include "includes.h"
 #include "ads.h"
 #include "secrets.h"
+#include "librpc/gen_ndr/ndr_secrets.h"
 
 #ifdef HAVE_KRB5
-
 ADS_STATUS ads_change_trust_account_password(ADS_STRUCT *ads, char *host_principal)
 {
-	char *password;
-	char *new_password;
+	const char *password = NULL;
+	const char *new_password = NULL;
 	ADS_STATUS ret;
-	enum netr_SchannelType sec_channel_type;
-    
-	if ((password = secrets_fetch_machine_password(lp_workgroup(), NULL, &sec_channel_type)) == NULL) {
-		DEBUG(1,("Failed to retrieve password for principal %s\n", host_principal));
-		return ADS_ERROR_SYSTEM(ENOENT);
+	const char *domain = lp_workgroup();
+	struct secrets_domain_info1 *info = NULL;
+	struct secrets_domain_info1_change *prev = NULL;
+	const DATA_BLOB *cleartext_blob = NULL;
+	DATA_BLOB pw_blob = data_blob_null;
+	DATA_BLOB new_pw_blob = data_blob_null;
+	NTSTATUS status;
+	struct timeval tv = timeval_current();
+	NTTIME now = timeval_to_nttime(&tv);
+	int role = lp_server_role();
+	bool ok;
+
+	if (role != ROLE_DOMAIN_MEMBER) {
+		DBG_ERR("Machine account password change only supported on a DOMAIN_MEMBER.\n");
+		return ADS_ERROR_NT(NT_STATUS_INVALID_SERVER_STATE);
 	}
 
 	new_password = trust_pw_new_value(talloc_tos(), SEC_CHAN_WKSTA, SEC_ADS);
 	if (new_password == NULL) {
 		ret = ADS_ERROR_SYSTEM(errno);
 		DEBUG(1,("Failed to generate machine password\n"));
-		goto failed;
+		return ret;
+	}
+
+	status = secrets_prepare_password_change(domain,
+						 ads->auth.kdc_server,
+						 new_password,
+						 talloc_tos(),
+						 &info, &prev);
+	if (!NT_STATUS_IS_OK(status)) {
+		return ADS_ERROR_NT(status);
+	}
+	if (prev != NULL) {
+		status = NT_STATUS_REQUEST_NOT_ACCEPTED;
+		secrets_failed_password_change("localhost",
+					       status,
+					       NT_STATUS_NOT_COMMITTED,
+					       info);
+		return ADS_ERROR_NT(status);
+	}
+
+	cleartext_blob = &info->password->cleartext_blob;
+	ok = convert_string_talloc(talloc_tos(), CH_UTF16MUNGED, CH_UNIX,
+				   cleartext_blob->data,
+				   cleartext_blob->length,
+				   (void **)&pw_blob.data,
+				   &pw_blob.length);
+	if (!ok) {
+		status = NT_STATUS_UNMAPPABLE_CHARACTER;
+		if (errno == ENOMEM) {
+			status = NT_STATUS_NO_MEMORY;
+		}
+		DBG_ERR("convert_string_talloc(CH_UTF16MUNGED, CH_UNIX) "
+			"failed for password of %s - %s\n",
+			domain, nt_errstr(status));
+		return ADS_ERROR_NT(status);
+	}
+	password = (const char *)pw_blob.data;
+
+	cleartext_blob = &info->next_change->password->cleartext_blob;
+	ok = convert_string_talloc(talloc_tos(), CH_UTF16MUNGED, CH_UNIX,
+				   cleartext_blob->data,
+				   cleartext_blob->length,
+				   (void **)&new_pw_blob.data,
+				   &new_pw_blob.length);
+	if (!ok) {
+		status = NT_STATUS_UNMAPPABLE_CHARACTER;
+		if (errno == ENOMEM) {
+			status = NT_STATUS_NO_MEMORY;
+		}
+		DBG_ERR("convert_string_talloc(CH_UTF16MUNGED, CH_UNIX) "
+			"failed for new_password of %s - %s\n",
+			domain, nt_errstr(status));
+		secrets_failed_password_change("localhost",
+					       status,
+					       NT_STATUS_NOT_COMMITTED,
+					       info);
+		return ADS_ERROR_NT(status);
 	}
+	new_password = (const char *)new_pw_blob.data;
 
 	ret = kerberos_set_password(ads->auth.kdc_server, host_principal, password, host_principal, new_password, ads->auth.time_offset);
 
 	if (!ADS_ERR_OK(ret)) {
-		goto failed;
+		status = ads_ntstatus(ret);
+		DBG_ERR("kerberos_set_password(%s, %s) "
+			"failed for new_password of %s - %s\n",
+			ads->auth.kdc_server, host_principal,
+			domain, nt_errstr(status));
+		secrets_failed_password_change(ads->auth.kdc_server,
+					       NT_STATUS_NOT_COMMITTED,
+					       status,
+					       info);
+		return ret;
 	}
 
-	if (!secrets_store_machine_password(new_password, lp_workgroup(), sec_channel_type)) {
+	status = secrets_finish_password_change(ads->auth.kdc_server, now, info);
+	if (!NT_STATUS_IS_OK(status)) {
 		DEBUG(1,("Failed to save machine password\n"));
-		ret = ADS_ERROR_SYSTEM(EACCES);
-		goto failed;
+		return ADS_ERROR_NT(status);
 	}
 
-failed:
-	SAFE_FREE(password);
-	return ret;
+	return ADS_SUCCESS;
 }
 #endif
-- 
1.9.1


From a14b0fde8e53188827da864dcfc21e171c56739e Mon Sep 17 00:00:00 2001
From: Stefan Metzmacher <metze at samba.org>
Date: Tue, 23 May 2017 17:42:09 +0200
Subject: [PATCH 11/15] s3:secrets: remove unused
 secrets_store_[prev_]machine_password()

BUG: https://bugzilla.samba.org/show_bug.cgi?id=12782

Signed-off-by: Stefan Metzmacher <metze at samba.org>
---
 source3/include/secrets.h                |  1 -
 source3/passdb/machine_account_secrets.c | 49 --------------------------------
 2 files changed, 50 deletions(-)

diff --git a/source3/include/secrets.h b/source3/include/secrets.h
index 0363b6b..24ae5bd 100644
--- a/source3/include/secrets.h
+++ b/source3/include/secrets.h
@@ -140,7 +140,6 @@ NTSTATUS secrets_finish_password_change(const char *change_server,
 					const struct secrets_domain_info1 *info);
 bool secrets_delete_machine_password_ex(const char *domain, const char *realm);
 bool secrets_delete_domain_sid(const char *domain);
-bool secrets_store_machine_password(const char *pass, const char *domain, enum netr_SchannelType sec_channel);
 char *secrets_fetch_prev_machine_password(const char *domain);
 time_t secrets_fetch_pass_last_set_time(const char *domain);
 char *secrets_fetch_machine_password(const char *domain,
diff --git a/source3/passdb/machine_account_secrets.c b/source3/passdb/machine_account_secrets.c
index 4ac197f..ec8ed2c 100644
--- a/source3/passdb/machine_account_secrets.c
+++ b/source3/passdb/machine_account_secrets.c
@@ -450,55 +450,6 @@ bool secrets_delete_domain_sid(const char *domain)
 }
 
 /************************************************************************
- Routine to store the previous machine password (by storing the current password
- as the old)
-************************************************************************/
-
-static bool secrets_store_prev_machine_password(const char *domain)
-{
-	char *oldpass;
-	bool ret;
-
-	oldpass = (char *)secrets_fetch(machine_password_keystr(domain), NULL);
-	if (oldpass == NULL) {
-		return true;
-	}
-	ret = secrets_store(machine_prev_password_keystr(domain), oldpass, strlen(oldpass)+1);
-	SAFE_FREE(oldpass);
-	return ret;
-}
-
-/************************************************************************
- Routine to set the plaintext machine account password for a realm
- the password is assumed to be a null terminated ascii string.
- Before storing
-************************************************************************/
-
-bool secrets_store_machine_password(const char *pass, const char *domain,
-				    enum netr_SchannelType sec_channel)
-{
-	bool ret;
-	uint32_t last_change_time;
-	uint32_t sec_channel_type;
-
-	if (!secrets_store_prev_machine_password(domain)) {
-		return false;
-	}
-
-	ret = secrets_store(machine_password_keystr(domain), pass, strlen(pass)+1);
-	if (!ret)
-		return ret;
-
-	SIVAL(&last_change_time, 0, time(NULL));
-	ret = secrets_store(machine_last_change_time_keystr(domain), &last_change_time, sizeof(last_change_time));
-
-	SIVAL(&sec_channel_type, 0, sec_channel);
-	ret = secrets_store(machine_sec_channel_type_keystr(domain), &sec_channel_type, sizeof(sec_channel_type));
-
-	return ret;
-}
-
-/************************************************************************
  Set the machine trust account password, the old pw and last change
  time, domain SID and salting principals based on values passed in
  (added to supprt the secrets_tdb_sync module on secrets.ldb)
-- 
1.9.1


From c40a18fc238ec4d5c7924dac4c641f6f4123a5d5 Mon Sep 17 00:00:00 2001
From: Stefan Metzmacher <metze at samba.org>
Date: Thu, 22 Jun 2017 15:30:56 +0200
Subject: [PATCH 12/15] selftest:Samba3: call "net primarytrust dumpinfo"
 setup_nt4_member() after the join

Here we check that we get 'REDACTED SECRET VALUES' printed, in order
to avoid regression on the non '-f' behavior.

BUG: https://bugzilla.samba.org/show_bug.cgi?id=12782

Signed-off-by: Stefan Metzmacher <metze at samba.org>
---
 selftest/target/Samba3.pm | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/selftest/target/Samba3.pm b/selftest/target/Samba3.pm
index d93d98e..259a576 100755
--- a/selftest/target/Samba3.pm
+++ b/selftest/target/Samba3.pm
@@ -352,6 +352,16 @@ sub setup_nt4_member($$$)
 	    return undef;
 	}
 
+	my $cmd = "";
+	$cmd .= "SOCKET_WRAPPER_DEFAULT_IFACE=\"$ret->{SOCKET_WRAPPER_DEFAULT_IFACE}\" ";
+	$cmd .= "SELFTEST_WINBINDD_SOCKET_DIR=\"$ret->{SELFTEST_WINBINDD_SOCKET_DIR}\" ";
+	$cmd .= "$net $ret->{CONFIGURATION} primarytrust dumpinfo | grep -q 'REDACTED SECRET VALUES'";
+
+	if (system($cmd) != 0) {
+	    warn("check failed\n$cmd");
+	    return undef;
+	}
+
 	if (not $self->check_or_start($ret, "yes", "yes", "yes")) {
 	       return undef;
 	}
-- 
1.9.1


From 927fedb9f590e306386f077215646aec2eb2640f Mon Sep 17 00:00:00 2001
From: Stefan Metzmacher <metze at samba.org>
Date: Thu, 18 May 2017 11:37:25 +0200
Subject: [PATCH 13/15] s4:password_hash: make use of smb_krb5_salt_principal()
 and smb_krb5_salt_principal2data()

Signed-off-by: Stefan Metzmacher <metze at samba.org>
Reviewed-by: Andreas Schneider <asn at samba.org>
---
 source4/dsdb/samdb/ldb_modules/password_hash.c | 78 ++++----------------------
 1 file changed, 12 insertions(+), 66 deletions(-)

diff --git a/source4/dsdb/samdb/ldb_modules/password_hash.c b/source4/dsdb/samdb/ldb_modules/password_hash.c
index 1eb39da..96113b5 100644
--- a/source4/dsdb/samdb/ldb_modules/password_hash.c
+++ b/source4/dsdb/samdb/ldb_modules/password_hash.c
@@ -671,7 +671,8 @@ static int setup_kerberos_keys(struct setup_password_fields_io *io)
 {
 	struct ldb_context *ldb;
 	krb5_error_code krb5_ret;
-	krb5_principal salt_principal;
+	char *salt_principal = NULL;
+	char *salt_data = NULL;
 	krb5_data salt;
 	krb5_keyblock key;
 	krb5_data cleartext_data;
@@ -680,60 +681,12 @@ static int setup_kerberos_keys(struct setup_password_fields_io *io)
 	cleartext_data.data = (char *)io->n.cleartext_utf8->data;
 	cleartext_data.length = io->n.cleartext_utf8->length;
 
-	/* Many, many thanks to lukeh at padl.com for this
-	 * algorithm, described in his Nov 10 2004 mail to
-	 * samba-technical at lists.samba.org */
-
-	/*
-	 * Determine a salting principal
-	 */
-	if (io->u.is_computer) {
-		char *name;
-		char *saltbody;
-
-		name = strlower_talloc(io->ac, io->u.sAMAccountName);
-		if (!name) {
-			return ldb_oom(ldb);
-		}
-
-		if (name[strlen(name)-1] == '$') {
-			name[strlen(name)-1] = '\0';
-		}
-
-		saltbody = talloc_asprintf(io->ac, "%s.%s", name,
-					   io->ac->status->domain_data.dns_domain);
-		if (!saltbody) {
-			return ldb_oom(ldb);
-		}
-		
-		krb5_ret = smb_krb5_make_principal(io->smb_krb5_context->krb5_context,
-					       &salt_principal,
-					       io->ac->status->domain_data.realm,
-					       "host", saltbody, NULL);
-	} else if (io->u.user_principal_name) {
-		char *user_principal_name;
-		char *p;
-
-		user_principal_name = talloc_strdup(io->ac, io->u.user_principal_name);
-		if (!user_principal_name) {
-			return ldb_oom(ldb);
-		}
-
-		p = strchr(user_principal_name, '@');
-		if (p) {
-			p[0] = '\0';
-		}
-
-		krb5_ret = smb_krb5_make_principal(io->smb_krb5_context->krb5_context,
-					       &salt_principal,
-					       io->ac->status->domain_data.realm,
-					       user_principal_name, NULL);
-	} else {
-		krb5_ret = smb_krb5_make_principal(io->smb_krb5_context->krb5_context,
-					       &salt_principal,
-					       io->ac->status->domain_data.realm,
-					       io->u.sAMAccountName, NULL);
-	}
+	krb5_ret = smb_krb5_salt_principal(io->ac->status->domain_data.realm,
+					   io->u.sAMAccountName,
+					   io->u.user_principal_name,
+					   io->u.is_computer,
+					   io->ac,
+					   &salt_principal);
 	if (krb5_ret) {
 		ldb_asprintf_errstring(ldb,
 				       "setup_kerberos_keys: "
@@ -746,9 +699,8 @@ static int setup_kerberos_keys(struct setup_password_fields_io *io)
 	/*
 	 * create salt from salt_principal
 	 */
-	krb5_ret = smb_krb5_get_pw_salt(io->smb_krb5_context->krb5_context,
-				    salt_principal, &salt);
-	krb5_free_principal(io->smb_krb5_context->krb5_context, salt_principal);
+	krb5_ret = smb_krb5_salt_principal2data(io->smb_krb5_context->krb5_context,
+						salt_principal, io->ac, &salt_data);
 	if (krb5_ret) {
 		ldb_asprintf_errstring(ldb,
 				       "setup_kerberos_keys: "
@@ -757,14 +709,8 @@ static int setup_kerberos_keys(struct setup_password_fields_io *io)
 								  krb5_ret, io->ac));
 		return LDB_ERR_OPERATIONS_ERROR;
 	}
-	/* create a talloc copy */
-	io->g.salt = talloc_strndup(io->ac,
-				    (char *)salt.data,
-				    salt.length);
-	smb_krb5_free_data_contents(io->smb_krb5_context->krb5_context, &salt);
-	if (!io->g.salt) {
-		return ldb_oom(ldb);
-	}
+	io->g.salt = salt_data;
+
 	/* now use the talloced copy of the salt */
 	salt.data	= discard_const(io->g.salt);
 	salt.length	= strlen(io->g.salt);
-- 
1.9.1


From 6a1df88c673608db9395c216547b5af2849e2d59 Mon Sep 17 00:00:00 2001
From: Stefan Metzmacher <metze at samba.org>
Date: Thu, 18 May 2017 10:50:34 +0200
Subject: [PATCH 14/15] auth/credentials: make use of smb_krb5_salt_principal()
 in cli_credentials_get_keytab()

Signed-off-by: Stefan Metzmacher <metze at samba.org>
Reviewed-by: Andreas Schneider <asn at samba.org>
---
 auth/credentials/credentials_krb5.c | 38 +++++++++++++++++++++++++------------
 1 file changed, 26 insertions(+), 12 deletions(-)

diff --git a/auth/credentials/credentials_krb5.c b/auth/credentials/credentials_krb5.c
index 6544e42..6db294b 100644
--- a/auth/credentials/credentials_krb5.c
+++ b/auth/credentials/credentials_krb5.c
@@ -953,6 +953,7 @@ _PUBLIC_ struct cli_credentials *cli_credentials_shallow_copy(TALLOC_CTX *mem_ct
 	return dst;
 }
 
+#if 0
 static int smb_krb5_create_salt_principal(TALLOC_CTX *mem_ctx,
 					  const char *samAccountName,
 					  const char *realm,
@@ -1029,6 +1030,7 @@ out:
 	talloc_free(tmp_ctx);
 	return rc;
 }
+#endif
 
 /* Get the keytab (actually, a container containing the krb5_keytab)
  * attached to this context.  If this hasn't been done or set before,
@@ -1045,9 +1047,10 @@ _PUBLIC_ int cli_credentials_get_keytab(struct cli_credentials *cred,
 	krb5_keytab keytab;
 	TALLOC_CTX *mem_ctx;
 	const char *username = cli_credentials_get_username(cred);
+	const char *upn = NULL;
 	const char *realm = cli_credentials_get_realm(cred);
-	const char *error_string;
-	const char *salt_principal;
+	char *salt_principal = NULL;
+	bool is_computer = false;
 
 	if (cred->keytab_obtained >= (MAX(cred->principal_obtained, 
 					  cred->username_obtained))) {
@@ -1070,16 +1073,27 @@ _PUBLIC_ int cli_credentials_get_keytab(struct cli_credentials *cred,
 		return ENOMEM;
 	}
 
-	/*
-	 * FIXME: Currently there is no better way than to create the correct
-	 * salt principal by checking if the username ends with a '$'. It would
-	 * be better if it is part of the credentials.
-	 */
-	ret = smb_krb5_create_salt_principal(mem_ctx,
-					     username,
-					     realm,
-					     &salt_principal,
-					     &error_string);
+	switch (cred->secure_channel_type) {
+	case SEC_CHAN_WKSTA:
+	case SEC_CHAN_BDC:
+	case SEC_CHAN_RODC:
+		is_computer = true;
+		break;
+	default:
+		upn = cli_credentials_get_principal(cred, mem_ctx);
+		if (upn == NULL) {
+			TALLOC_FREE(mem_ctx);
+			return ENOMEM;
+		}
+		break;
+	}
+
+	ret = smb_krb5_salt_principal(realm,
+				      username, /* sAMAccountName */
+				      upn, /* userPrincipalName */
+				      is_computer,
+				      mem_ctx,
+				      &salt_principal);
 	if (ret) {
 		talloc_free(mem_ctx);
 		return ret;
-- 
1.9.1


From b9718c735bc9d19cd8bce4410845b952628e2985 Mon Sep 17 00:00:00 2001
From: Stefan Metzmacher <metze at samba.org>
Date: Thu, 18 May 2017 10:54:06 +0200
Subject: [PATCH 15/15] auth/credentials: remove unused
 smb_krb5_create_salt_principal()

Signed-off-by: Stefan Metzmacher <metze at samba.org>
Reviewed-by: Andreas Schneider <asn at samba.org>
---
 auth/credentials/credentials_krb5.c | 79 -------------------------------------
 1 file changed, 79 deletions(-)

diff --git a/auth/credentials/credentials_krb5.c b/auth/credentials/credentials_krb5.c
index 6db294b..b88497d 100644
--- a/auth/credentials/credentials_krb5.c
+++ b/auth/credentials/credentials_krb5.c
@@ -953,85 +953,6 @@ _PUBLIC_ struct cli_credentials *cli_credentials_shallow_copy(TALLOC_CTX *mem_ct
 	return dst;
 }
 
-#if 0
-static int smb_krb5_create_salt_principal(TALLOC_CTX *mem_ctx,
-					  const char *samAccountName,
-					  const char *realm,
-					  const char **salt_principal,
-					  const char **error_string)
-{
-	char *machine_username;
-	bool is_machine_account = false;
-	char *upper_realm;
-	TALLOC_CTX *tmp_ctx;
-	int rc = -1;
-
-	if (samAccountName == NULL) {
-		*error_string = "Cannot determine salt principal, no "
-				"saltPrincipal or samAccountName specified";
-		return rc;
-	}
-
-	if (realm == NULL) {
-		*error_string = "Cannot make principal without a realm";
-		return rc;
-	}
-
-	tmp_ctx = talloc_new(mem_ctx);
-	if (tmp_ctx == NULL) {
-		*error_string = "Cannot allocate talloc context";
-		return rc;
-	}
-
-	upper_realm = strupper_talloc(tmp_ctx, realm);
-	if (upper_realm == NULL) {
-		*error_string = "Cannot allocate to upper case realm";
-		goto out;
-	}
-
-	machine_username = strlower_talloc(tmp_ctx, samAccountName);
-	if (!machine_username) {
-		*error_string = "Cannot duplicate samAccountName";
-		goto out;
-	}
-
-	if (machine_username[strlen(machine_username) - 1] == '$') {
-		machine_username[strlen(machine_username) - 1] = '\0';
-		is_machine_account = true;
-	}
-
-	if (is_machine_account) {
-		char *lower_realm;
-
-		lower_realm = strlower_talloc(tmp_ctx, realm);
-		if (lower_realm == NULL) {
-			*error_string = "Cannot allocate to lower case realm";
-			goto out;
-		}
-
-		*salt_principal = talloc_asprintf(mem_ctx,
-						  "host/%s.%s@%s",
-						  machine_username,
-						  lower_realm,
-						  upper_realm);
-	} else {
-		*salt_principal = talloc_asprintf(mem_ctx,
-						  "%s@%s",
-						  machine_username,
-						  upper_realm);
-	}
-	if (*salt_principal == NULL) {
-		*error_string = "Cannot create salt principal";
-		goto out;
-	}
-
-	rc = 0;
-out:
-	talloc_free(tmp_ctx);
-	return rc;
-}
-#endif
-
 /* Get the keytab (actually, a container containing the krb5_keytab)
  * attached to this context.  If this hasn't been done or set before,
  * it will be generated from the password.
-- 
1.9.1

-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 836 bytes
Desc: OpenPGP digital signature
URL: <http://lists.samba.org/pipermail/samba-technical/attachments/20170626/954fc930/signature-0001.sig>


More information about the samba-technical mailing list