summaryrefslogtreecommitdiffstats
path: root/source3/passdb/machine_account_secrets.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 17:20:00 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 17:20:00 +0000
commit8daa83a594a2e98f39d764422bfbdbc62c9efd44 (patch)
tree4099e8021376c7d8c05bdf8503093d80e9c7bad0 /source3/passdb/machine_account_secrets.c
parentInitial commit. (diff)
downloadsamba-8daa83a594a2e98f39d764422bfbdbc62c9efd44.tar.xz
samba-8daa83a594a2e98f39d764422bfbdbc62c9efd44.zip
Adding upstream version 2:4.20.0+dfsg.upstream/2%4.20.0+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'source3/passdb/machine_account_secrets.c')
-rw-r--r--source3/passdb/machine_account_secrets.c2080
1 files changed, 2080 insertions, 0 deletions
diff --git a/source3/passdb/machine_account_secrets.c b/source3/passdb/machine_account_secrets.c
new file mode 100644
index 0000000..c97b35e
--- /dev/null
+++ b/source3/passdb/machine_account_secrets.c
@@ -0,0 +1,2080 @@
+/*
+ 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 <http://www.gnu.org/licenses/>.
+*/
+
+/* 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 = 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;
+ }
+ 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)
+{
+ 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;
+ }
+
+ *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)
+{
+ 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;
+ }
+
+ 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;
+}