summaryrefslogtreecommitdiff
path: root/source3/passdb
diff options
context:
space:
mode:
authorStefan Metzmacher <metze@samba.org>2017-05-19 16:28:17 +0200
committerStefan Metzmacher <metze@samba.org>2017-06-27 16:57:46 +0200
commit5f0038fba612afd7fc15b7ab321df979891170d8 (patch)
tree824f01a2eeb25ac35c294381d4f6154d385bdbad /source3/passdb
parenta59c9cba31a801d90db06b767cfd44776f4ede77 (diff)
downloadsamba-5f0038fba612afd7fc15b7ab321df979891170d8.tar.gz
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@samba.org> Reviewed-by: Andreas Schneider <asn@samba.org>
Diffstat (limited to 'source3/passdb')
-rw-r--r--source3/passdb/machine_account_secrets.c1391
1 files changed, 1391 insertions, 0 deletions
diff --git a/source3/passdb/machine_account_secrets.c b/source3/passdb/machine_account_secrets.c
index 9a96a3f38fb..b88fbe937bc 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,1380 @@ 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 gap 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;
+}