From 5f0038fba612afd7fc15b7ab321df979891170d8 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Fri, 19 May 2017 16:28:17 +0200 Subject: 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 Reviewed-by: Andreas Schneider --- source3/passdb/machine_account_secrets.c | 1391 ++++++++++++++++++++++++++++++ 1 file changed, 1391 insertions(+) (limited to 'source3/passdb') 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; +} -- cgit v1.2.1