/* Unix SMB/CIFS implementation. Copyright (C) Andrew Tridgell 1992-2001 Copyright (C) Andrew Bartlett 2002 Copyright (C) Rafal Szczesniak 2002 Copyright (C) Tim Potter 2001 This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ /* the Samba secrets database stores any generated, private information such as the local SID and machine trust password */ #include "includes.h" #include "passdb.h" #include "../libcli/auth/libcli_auth.h" #include "secrets.h" #include "dbwrap/dbwrap.h" #include "../librpc/ndr/libndr.h" #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" #include "../libds/common/flags.h" #include "lib/util/string_wrappers.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); /** * Form a key for fetching the domain sid * * @param domain domain name * * @return keystring **/ static const char *domain_sid_keystr(const char *domain) { char *keystr; keystr = talloc_asprintf_strupper_m(talloc_tos(), "%s/%s", SECRETS_DOMAIN_SID, domain); SMB_ASSERT(keystr != NULL); return keystr; } static const char *domain_guid_keystr(const char *domain) { char *keystr; keystr = talloc_asprintf_strupper_m(talloc_tos(), "%s/%s", SECRETS_DOMAIN_GUID, domain); SMB_ASSERT(keystr != NULL); return keystr; } static const char *protect_ids_keystr(const char *domain) { char *keystr; keystr = talloc_asprintf_strupper_m(talloc_tos(), "%s/%s", SECRETS_PROTECT_IDS, domain); SMB_ASSERT(keystr != NULL); return keystr; } /* N O T E: never use this outside of passdb modules that store the SID on their own */ bool secrets_mark_domain_protected(const char *domain) { bool ret; ret = secrets_store(protect_ids_keystr(domain), "TRUE", 5); if (!ret) { DEBUG(0, ("Failed to protect the Domain IDs\n")); } return ret; } bool secrets_clear_domain_protection(const char *domain) { bool ret; void *protection = secrets_fetch(protect_ids_keystr(domain), NULL); if (protection) { SAFE_FREE(protection); ret = secrets_delete_entry(protect_ids_keystr(domain)); if (!ret) { DEBUG(0, ("Failed to remove Domain IDs protection\n")); } return ret; } return true; } bool secrets_store_domain_sid(const char *domain, const struct dom_sid *sid) { char *protect_ids; bool ret; struct dom_sid clean_sid = { 0 }; protect_ids = secrets_fetch(protect_ids_keystr(domain), NULL); if (protect_ids) { if (strncmp(protect_ids, "TRUE", 4)) { DEBUG(0, ("Refusing to store a Domain SID, " "it has been marked as protected!\n")); SAFE_FREE(protect_ids); return false; } } SAFE_FREE(protect_ids); /* * use a copy to prevent uninitialized memory from being carried over * to the tdb */ sid_copy(&clean_sid, sid); ret = secrets_store(domain_sid_keystr(domain), &clean_sid, sizeof(struct dom_sid)); /* Force a re-query */ if (ret) { /* * Do not call get_global_domain_sid() here, or we will call it * recursively. */ reset_global_sam_sid(); } return ret; } bool secrets_fetch_domain_sid(const char *domain, struct dom_sid *sid) { struct dom_sid *dyn_sid; size_t size = 0; dyn_sid = (struct dom_sid *)secrets_fetch(domain_sid_keystr(domain), &size); if (dyn_sid == NULL) return False; if (size != sizeof(struct dom_sid)) { SAFE_FREE(dyn_sid); return False; } *sid = *dyn_sid; SAFE_FREE(dyn_sid); return True; } bool secrets_store_domain_guid(const char *domain, const struct GUID *guid) { char *protect_ids; const char *key; protect_ids = secrets_fetch(protect_ids_keystr(domain), NULL); if (protect_ids) { if (strncmp(protect_ids, "TRUE", 4)) { DEBUG(0, ("Refusing to store a Domain SID, " "it has been marked as protected!\n")); SAFE_FREE(protect_ids); return false; } } SAFE_FREE(protect_ids); key = domain_guid_keystr(domain); return secrets_store(key, guid, sizeof(struct GUID)); } bool secrets_fetch_domain_guid(const char *domain, struct GUID *guid) { struct GUID *dyn_guid; const char *key; size_t size = 0; struct GUID new_guid; key = domain_guid_keystr(domain); dyn_guid = (struct GUID *)secrets_fetch(key, &size); if (!dyn_guid) { if (lp_server_role() == ROLE_DOMAIN_PDC || lp_server_role() == ROLE_IPA_DC) { new_guid = GUID_random(); if (!secrets_store_domain_guid(domain, &new_guid)) return False; dyn_guid = (struct GUID *)secrets_fetch(key, &size); } if (dyn_guid == NULL) { return False; } } if (size != sizeof(struct GUID)) { DEBUG(1,("UUID size %d is wrong!\n", (int)size)); SAFE_FREE(dyn_guid); return False; } *guid = *dyn_guid; SAFE_FREE(dyn_guid); return True; } /** * Form a key for fetching the machine trust account sec channel type * * @param domain domain name * * @return keystring **/ static const char *machine_sec_channel_type_keystr(const char *domain) { char *keystr; keystr = talloc_asprintf_strupper_m(talloc_tos(), "%s/%s", SECRETS_MACHINE_SEC_CHANNEL_TYPE, domain); SMB_ASSERT(keystr != NULL); return keystr; } /** * Form a key for fetching the machine trust account last change time * * @param domain domain name * * @return keystring **/ static const char *machine_last_change_time_keystr(const char *domain) { char *keystr; keystr = talloc_asprintf_strupper_m(talloc_tos(), "%s/%s", SECRETS_MACHINE_LAST_CHANGE_TIME, domain); SMB_ASSERT(keystr != NULL); return keystr; } /** * Form a key for fetching the machine previous trust account password * * @param domain domain name * * @return keystring **/ static const char *machine_prev_password_keystr(const char *domain) { char *keystr; keystr = talloc_asprintf_strupper_m(talloc_tos(), "%s/%s", SECRETS_MACHINE_PASSWORD_PREV, domain); SMB_ASSERT(keystr != NULL); return keystr; } /** * Form a key for fetching the machine trust account password * * @param domain domain name * * @return keystring **/ static const char *machine_password_keystr(const char *domain) { char *keystr; keystr = talloc_asprintf_strupper_m(talloc_tos(), "%s/%s", SECRETS_MACHINE_PASSWORD, domain); SMB_ASSERT(keystr != NULL); return keystr; } /** * Form a key for fetching the machine trust account password * * @param domain domain name * * @return stored password's key **/ static const char *trust_keystr(const char *domain) { char *keystr; keystr = talloc_asprintf_strupper_m(talloc_tos(), "%s/%s", SECRETS_MACHINE_ACCT_PASS, domain); SMB_ASSERT(keystr != NULL); return keystr; } /************************************************************************ Routine to get the default secure channel type for trust accounts ************************************************************************/ enum netr_SchannelType get_default_sec_channel(void) { if (IS_DC) { return SEC_CHAN_BDC; } else { return SEC_CHAN_WKSTA; } } /************************************************************************ Routine to get the trust account password for a domain. This only tries to get the legacy hashed version of the password. The user of this function must have locked the trust password file using the above secrets_lock_trust_account_password(). ************************************************************************/ bool secrets_fetch_trust_account_password_legacy(const char *domain, uint8_t ret_pwd[16], time_t *pass_last_set_time, enum netr_SchannelType *channel) { struct machine_acct_pass *pass; size_t size = 0; if (!(pass = (struct machine_acct_pass *)secrets_fetch( trust_keystr(domain), &size))) { DEBUG(5, ("secrets_fetch failed!\n")); return False; } if (size != sizeof(*pass)) { DEBUG(0, ("secrets were of incorrect size!\n")); BURN_FREE(pass, size); return False; } if (pass_last_set_time) { *pass_last_set_time = pass->mod_time; } memcpy(ret_pwd, pass->hash, 16); if (channel) { *channel = get_default_sec_channel(); } BURN_FREE(pass, size); return True; } /************************************************************************ Routine to delete all information related to the domain joined machine. ************************************************************************/ 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); if (!ok) { return false; } } tmpkey = domain_guid_keystr(domain); ok = secrets_delete(tmpkey); if (!ok) { return false; } tmpkey = machine_prev_password_keystr(domain); ok = secrets_delete(tmpkey); if (!ok) { return false; } tmpkey = machine_password_keystr(domain); ok = secrets_delete(tmpkey); if (!ok) { return false; } tmpkey = machine_sec_channel_type_keystr(domain); ok = secrets_delete(tmpkey); if (!ok) { return false; } tmpkey = machine_last_change_time_keystr(domain); ok = secrets_delete(tmpkey); if (!ok) { return false; } tmpkey = domain_sid_keystr(domain); ok = secrets_delete(tmpkey); if (!ok) { return false; } return true; } /************************************************************************ Routine to delete the domain sid ************************************************************************/ bool secrets_delete_domain_sid(const char *domain) { return secrets_delete_entry(domain_sid_keystr(domain)); } /************************************************************************ 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 support the secrets_tdb_sync module on secrets.ldb) ************************************************************************/ bool secrets_store_machine_pw_sync(const char *pass, const char *oldpass, const char *domain, const char *realm, const char *salting_principal, uint32_t supported_enc_types, const struct dom_sid *domain_sid, uint32_t last_change_time, uint32_t secure_channel_type, bool delete_join) { bool ret; uint8_t last_change_time_store[4]; TALLOC_CTX *frame = talloc_stackframe(); uint8_t sec_channel_bytes[4]; if (delete_join) { secrets_delete_machine_password_ex(domain, realm); TALLOC_FREE(frame); return true; } ret = secrets_store(machine_password_keystr(domain), pass, strlen(pass)+1); if (!ret) { TALLOC_FREE(frame); return ret; } if (oldpass) { ret = secrets_store(machine_prev_password_keystr(domain), oldpass, strlen(oldpass)+1); } else { ret = secrets_delete(machine_prev_password_keystr(domain)); } if (!ret) { TALLOC_FREE(frame); return ret; } if (secure_channel_type == 0) { /* We delete this and instead have the read code fall back to * a default based on server role, as our caller can't specify * this with any more certainty */ ret = secrets_delete(machine_sec_channel_type_keystr(domain)); if (!ret) { TALLOC_FREE(frame); return ret; } } else { SIVAL(&sec_channel_bytes, 0, secure_channel_type); ret = secrets_store(machine_sec_channel_type_keystr(domain), &sec_channel_bytes, sizeof(sec_channel_bytes)); if (!ret) { TALLOC_FREE(frame); return ret; } } SIVAL(&last_change_time_store, 0, last_change_time); ret = secrets_store(machine_last_change_time_keystr(domain), &last_change_time_store, sizeof(last_change_time)); if (!ret) { TALLOC_FREE(frame); return ret; } ret = secrets_store_domain_sid(domain, domain_sid); if (!ret) { TALLOC_FREE(frame); return ret; } if (realm != NULL) { char *key = des_salt_key(realm); if (salting_principal != NULL) { ret = secrets_store(key, salting_principal, strlen(salting_principal)+1); } else { ret = secrets_delete(key); } } TALLOC_FREE(frame); return ret; } /************************************************************************ Return the standard DES salt key ************************************************************************/ char* kerberos_standard_des_salt( void ) { fstring salt; fstr_sprintf( salt, "host/%s.%s@", lp_netbios_name(), lp_realm() ); (void)strlower_m( salt ); fstrcat( salt, lp_realm() ); return SMB_STRDUP( salt ); } /************************************************************************ ************************************************************************/ static char *des_salt_key(const char *realm) { char *keystr; keystr = talloc_asprintf_strupper_m(talloc_tos(), "%s/DES/%s", SECRETS_SALTING_PRINCIPAL, realm); SMB_ASSERT(keystr != NULL); return keystr; } /************************************************************************ ************************************************************************/ bool kerberos_secrets_store_des_salt( const char* salt ) { char* key; bool ret; key = des_salt_key(lp_realm()); if (key == NULL) { DEBUG(0,("kerberos_secrets_store_des_salt: failed to generate key!\n")); return False; } if ( !salt ) { DEBUG(8,("kerberos_secrets_store_des_salt: deleting salt\n")); secrets_delete_entry( key ); return True; } DEBUG(3,("kerberos_secrets_store_des_salt: Storing salt \"%s\"\n", salt)); ret = secrets_store( key, salt, strlen(salt)+1 ); TALLOC_FREE(key); return ret; } /************************************************************************ ************************************************************************/ static char* kerberos_secrets_fetch_des_salt( void ) { char *salt, *key; key = des_salt_key(lp_realm()); if (key == NULL) { DEBUG(0,("kerberos_secrets_fetch_des_salt: failed to generate key!\n")); return NULL; } salt = (char*)secrets_fetch( key, NULL ); TALLOC_FREE(key); return salt; } /************************************************************************ Routine to get the salting principal for this service. Caller must free if return is not null. ************************************************************************/ char *kerberos_secrets_fetch_salt_princ(void) { char *salt_princ_s; /* lookup new key first */ salt_princ_s = kerberos_secrets_fetch_des_salt(); if (salt_princ_s == NULL) { /* fall back to host/machine.realm@REALM */ salt_princ_s = kerberos_standard_des_salt(); } return salt_princ_s; } /************************************************************************ Routine to fetch the previous plaintext machine account password for a realm the password is assumed to be a null terminated ascii string. ************************************************************************/ char *secrets_fetch_prev_machine_password(const char *domain) { return (char *)secrets_fetch(machine_prev_password_keystr(domain), NULL); } /************************************************************************ Routine to fetch the last change time of the machine account password for a realm ************************************************************************/ time_t secrets_fetch_pass_last_set_time(const char *domain) { uint32_t *last_set_time; time_t pass_last_set_time; last_set_time = secrets_fetch(machine_last_change_time_keystr(domain), NULL); if (last_set_time) { pass_last_set_time = IVAL(last_set_time,0); SAFE_FREE(last_set_time); } else { pass_last_set_time = 0; } return pass_last_set_time; } /************************************************************************ Routine to fetch the plaintext machine account password for a realm the password is assumed to be a null terminated ascii string. ************************************************************************/ char *secrets_fetch_machine_password(const char *domain, time_t *pass_last_set_time, enum netr_SchannelType *channel) { char *ret; ret = (char *)secrets_fetch(machine_password_keystr(domain), NULL); if (pass_last_set_time) { *pass_last_set_time = secrets_fetch_pass_last_set_time(domain); } if (channel) { size_t size; uint32_t *channel_type; channel_type = (unsigned int *)secrets_fetch(machine_sec_channel_type_keystr(domain), &size); if (channel_type) { *channel = IVAL(channel_type,0); SAFE_FREE(channel_type); } else { *channel = get_default_sec_channel(); } } return ret; } static int password_nt_hash_destructor(struct secrets_domain_info1_password *pw) { ZERO_STRUCT(pw->nt_hash); return 0; } static int setup_password_zeroing(struct secrets_domain_info1_password *pw) { if (pw != NULL) { size_t i; talloc_keep_secret(pw->cleartext_blob.data); talloc_set_destructor(pw, password_nt_hash_destructor); for (i = 0; i < pw->num_keys; i++) { talloc_keep_secret(pw->keys[i].value.data); } } return 0; } 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); BURN_FREE(blob.data, blob.length); 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.info.info1->next_change != NULL) { setup_password_zeroing(sdib.info.info1->next_change->password); } setup_password_zeroing(sdib.info.info1->password); setup_password_zeroing(sdib.info.info1->old_password); setup_password_zeroing(sdib.info.info1->older_password); 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_LEVEL(lvl, secrets_domain_infoB, &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, bool upgrade) { 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 (!upgrade && 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; bool ok; #endif /* HAVE_ADS */ DATA_BLOB arc4_b = data_blob_null; const uint16_t max_keys = 3; 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 * * We don't include ENCTYPE_DES_CBC_CRC * and ENCTYPE_DES_CBC_MD5 * as they are no longer supported. * * 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; } talloc_keep_secret(arc4_b.data); #ifdef HAVE_ADS if (salt_principal == NULL) { goto no_kerberos; } krb5_ret = smb_krb5_init_context_common(&krb5_ctx); if (krb5_ret != 0) { DBG_ERR("kerberos init context failed (%s)\n", error_message(krb5_ret)); 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 = (krb5_data) { .data = discard_const(salt_data), .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; } talloc_keep_secret(cleartext_utf8_b.data); 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; } talloc_keep_secret(aes_256_b.data); 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; } talloc_keep_secret(aes_128_b.data); 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; 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; } talloc_keep_secret(p->cleartext_blob.data); mdfour(p->nt_hash.hash, p->cleartext_blob.data, p->cleartext_blob.length); talloc_set_destructor(p, password_nt_hash_destructor); 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_CANT_ACCESS_DOMAIN_INFO; } 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); BURN_FREE_STR(old_pw); BURN_FREE_STR(pw); 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); BURN_FREE_STR(old_pw); BURN_FREE_STR(pw); 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); BURN_FREE_STR(pw); BURN_FREE_STR(old_pw); 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); BURN_FREE_STR(old_pw); BURN_FREE_STR(pw); 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); BURN_FREE_STR(pw); BURN_FREE_STR(old_pw); 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); BURN_FREE_STR(pw); 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); BURN_FREE_STR(old_pw); 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); BURN_FREE_STR(old_pw); 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, true /* upgrade */); 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_str(info->domain_info.dns_domain.string, info->account_name, NULL /* userPrincipalName */, UF_WORKSTATION_TRUST_ACCOUNT, 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, false /* upgrade */); 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, NTSTATUS (*sync_pw2keytabs_fn)(void)) { 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, false /* upgrade */); 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; } if (prev == NULL && sync_pw2keytabs_fn != NULL) { status = sync_pw2keytabs_fn(); if (!NT_STATUS_IS_OK(status)) { DBG_ERR("Sync of machine password failed.\n"); dbwrap_transaction_cancel(db); TALLOC_FREE(frame); return status; } } *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; bool 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 structure 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 = mem_equal_const_time(sn->password->nt_hash.hash, cn->password->nt_hash.hash, 16); if (!cmp) { DBG_ERR("next password.nt_hash differs for %s.\n", domain); TALLOC_FREE(stored); return NT_STATUS_NETWORK_CREDENTIAL_CONFLICT; } cmp = mem_equal_const_time(stored->password->nt_hash.hash, cookie->password->nt_hash.hash, 16); if (!cmp) { 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, false /* upgrade */); 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, NTSTATUS (*sync_pw2keytabs_fn)(void)) { 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, false /* upgrade */); 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; } /* * For the clustered samba, it is important to have following order: * 1. dbwrap_transaction_commit() * 2. sync_pw2keytabs() * Only this order ensures a correct behavior of * the 'sync machine password script' that does: * 'onnode all net ads keytab create' * * If we would call sync_pw2keytabs() before committing the changes to * the secrets.tdb, it will not be updated on other nodes, so triggering * 'net ads keytab create' will not see the new password yet. * * This applies also to secrets_prepare_password_change(). */ 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; } if (sync_pw2keytabs_fn != NULL) { status = sync_pw2keytabs_fn(); if (!NT_STATUS_IS_OK(status)) { DBG_ERR("Sync of machine password failed.\n"); TALLOC_FREE(frame); return status; } } TALLOC_FREE(frame); return NT_STATUS_OK; }